├── .github └── workflows │ └── build.yml ├── .gitignore ├── AUTHORS ├── COPYING ├── COPYING.icons ├── Makefile.am ├── README.md ├── admin └── wxwin.m4 ├── bmpviewer.cpp ├── bmpviewer.h ├── bootstrap ├── configure.ac ├── diff-pdf.cpp ├── gtk-zoom-in.xpm ├── gtk-zoom-out.xpm ├── gutter.cpp ├── gutter.h └── win32 ├── collect-dlls.sh └── fonts.conf /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-linux: 7 | name: Build on Linux 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Install dependencies 13 | run: | 14 | sudo apt-get update && \ 15 | sudo apt-get install libpoppler-glib-dev poppler-utils libwxgtk3.0-gtk3-dev 16 | 17 | - name: Bootstrap 18 | run: ./bootstrap 19 | 20 | - name: Configure 21 | run: ./configure 22 | 23 | - name: Build 24 | run: make 25 | 26 | - name: Build source tarball 27 | run: make dist 28 | 29 | - name: Upload source tarball 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: source-tarball 33 | path: diff-pdf-*.tar.gz 34 | 35 | 36 | build-windows: 37 | name: Build on Windows 38 | runs-on: windows-latest 39 | 40 | defaults: 41 | run: 42 | shell: msys2 {0} 43 | 44 | steps: 45 | - uses: actions/checkout@v3 46 | 47 | - name: Setup MSYS2 environment 48 | uses: msys2/setup-msys2@v2 49 | with: 50 | msystem: UCRT64 51 | update: true 52 | install: mingw-w64-ucrt-x86_64-gcc automake autoconf pkg-config make zip mingw-w64-ucrt-x86_64-poppler mingw-w64-ucrt-x86_64-wxWidgets 53 | 54 | - name: Bootstrap 55 | run: ./bootstrap 56 | 57 | - name: Configure 58 | run: ./configure 59 | 60 | - name: Build 61 | run: make 62 | 63 | - name: Build Windows archive 64 | run: make windows-dist 65 | 66 | - uses: actions/upload-artifact@v4 67 | with: 68 | name: windows-binaries 69 | path: diff-pdf*.zip 70 | 71 | 72 | publish-release: 73 | name: Publish release 74 | if: startsWith(github.ref, 'refs/tags/v') 75 | runs-on: ubuntu-latest 76 | needs: [build-linux, build-windows] 77 | steps: 78 | - uses: actions/download-artifact@v4 79 | with: 80 | name: source-tarball 81 | 82 | - uses: actions/download-artifact@v4 83 | with: 84 | name: windows-binaries 85 | 86 | - uses: ncipollo/release-action@v1 87 | with: 88 | allowUpdates: true 89 | prerelease: true 90 | artifacts: "*.zip,*.tar.gz" 91 | token: ${{ secrets.GITHUB_TOKEN }} 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | admin/config.guess 2 | admin/config.sub 3 | admin/depcomp 4 | admin/install-sh 5 | admin/missing 6 | 7 | # generated by configure 8 | .deps/* 9 | config.log 10 | config.status 11 | Makefile 12 | Makefile.in 13 | 14 | configure 15 | autom4te.cache 16 | aclocal.m4 17 | mingw 18 | 19 | # compiled (and unlinked) objects 20 | *.o 21 | # the final binary 22 | diff-pdf 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 2 | Developers: 3 | ------------- 4 | 5 | Vaclav Slavik 6 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /COPYING.icons: -------------------------------------------------------------------------------- 1 | 2 | 3 | The gtk-zoom-in.xpm and gtk-zoom-out.xpm icons are under LGPL and were taken 4 | from GTK+ 2.16.5. 5 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | bin_PROGRAMS = diff-pdf 3 | 4 | diff_pdf_SOURCES = \ 5 | diff-pdf.cpp \ 6 | bmpviewer.cpp \ 7 | bmpviewer.h \ 8 | gutter.cpp \ 9 | gutter.h 10 | 11 | diff_pdf_CXXFLAGS = $(POPPLER_CFLAGS) $(WX_CXXFLAGS) 12 | diff_pdf_LDADD = $(POPPLER_LIBS) $(WX_LIBS) 13 | 14 | EXTRA_DIST = bootstrap gtk-zoom-in.xpm gtk-zoom-out.xpm README.md win32/fonts.conf win32/collect-dlls.sh 15 | 16 | windows-dist: diff-pdf-win-$(VERSION).zip 17 | 18 | diff-pdf-win-$(VERSION).zip: all 19 | rm -rf $@ windist 20 | $(srcdir)/win32/collect-dlls.sh windist diff-pdf.exe 21 | mkdir -p windist/fonts && cp -a $(srcdir)/win32/fonts.conf windist/fonts/ 22 | (cd windist && zip -9r ../$@ .) 23 | rm -rf windist 24 | 25 | .PHONY: windows-dist 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *Note: this repository is provided **as-is** and the code is not being actively 2 | developed. If you wish to improve it, that's greatly appreciated: please make 3 | the changes and submit a pull request, I'll gladly merge it or help you out 4 | with finishing it. However, please do not expect any kind of support, including 5 | implementation of feature requests or fixes. If you're not a developer and/or 6 | willing to get your hands dirty, this tool is probably not for you.* 7 | 8 | [![Build](https://github.com/vslavik/diff-pdf/actions/workflows/build.yml/badge.svg)](https://github.com/vslavik/diff-pdf/actions/workflows/build.yml) 9 | 10 | ## Usage 11 | 12 | diff-pdf is a tool for visually comparing two PDFs. 13 | 14 | It takes two PDF files as arguments. By default, its only output is its return 15 | code, which is 0 if there are no differences and 1 if the two PDFs differ. If 16 | given the `--output-diff` option, it produces a PDF file with visually 17 | highlighted differences: 18 | 19 | ``` 20 | $ diff-pdf --output-diff=diff.pdf a.pdf b.pdf 21 | ``` 22 | 23 | Another option is to compare the two files visually in a simple GUI, using 24 | the `--view` argument: 25 | 26 | ``` 27 | $ diff-pdf --view a.pdf b.pdf 28 | ``` 29 | 30 | This opens a window that lets you view the files' pages and zoom in on details. 31 | It is also possible to shift the two pages relatively to each other using 32 | Ctrl-arrows (Cmd-arrows on MacOS). This is useful for identifying translation-only differences. 33 | 34 | See the output of `$ diff-pdf --help` for complete list of options. 35 | 36 | 37 | ## Obtaining the binaries 38 | 39 | Precompiled version of the tool for Windows is available as part of 40 | [the latest release](https://github.com/vslavik/diff-pdf/releases/latest/) 41 | as a ZIP archive, which contains everything you need to run diff-pdf. It will 42 | work from any place you unpack it to. 43 | 44 | Alternatively, if you use [Chocolatey](https://chocolatey.org/), you can install 45 | diff-pdf on Windows with: 46 | ``` 47 | $ choco install diff-pdf 48 | ``` 49 | On Mac, if you use [Homebrew](https://brew.sh), you can use it to install diff-pdf with it: 50 | ``` 51 | $ brew install diff-pdf 52 | ``` 53 | On Mac, if you use [Macports](https://macports.org), you can install diff-pdf with: 54 | ``` 55 | $ port install diff-pdf 56 | ``` 57 | On Fedora and CentOS 8: 58 | ``` 59 | $ sudo dnf install diff-pdf 60 | ``` 61 | Precompiled version for openSUSE can be downloaded from the 62 | [openSUSE build service](http://software.opensuse.org). 63 | 64 | 65 | ## Compiling from sources 66 | 67 | The build system uses Automake and so a Unix or Unix-like environment (Cygwin 68 | or MSYS) is required. Compilation is done in the usual way: 69 | 70 | ``` 71 | $ ./bootstrap 72 | $ ./configure 73 | $ make 74 | $ make install 75 | ``` 76 | 77 | (Note that the first step, running the `./bootstrap` script, is only required 78 | when building sources checked from version control system, i.e. when `configure` 79 | and `Makefile.in` files are missing.) 80 | 81 | As for dependencies, diff-pdf requires the following libraries: 82 | 83 | - wxWidgets >= 3.0 84 | - Cairo >= 1.4 85 | - Poppler >= 0.10 86 | 87 | #### CentOS: 88 | 89 | ``` 90 | $ sudo yum groupinstall "Development Tools" 91 | $ sudo yum install wxGTK wxGTK-devel poppler-glib poppler-glib-devel 92 | ``` 93 | 94 | #### Ubuntu 24.04 / Debian 12 or newer: 95 | 96 | ``` 97 | $ sudo apt-get install make automake g++ 98 | $ sudo apt-get install libpoppler-glib-dev poppler-utils libwxgtk3.2-dev 99 | ``` 100 | 101 | #### Older versions of Ubuntu / Debian: 102 | 103 | ``` 104 | $ sudo apt-get install make automake g++ 105 | $ sudo apt-get install libpoppler-glib-dev poppler-utils libwxgtk3.0-gtk3-dev 106 | ``` 107 | 108 | #### macOS: 109 | Install Command Line Tools for Xcode: 110 | 111 | ``` 112 | $ xcode-select --install 113 | ``` 114 | 115 | and install [Homebrew](https://brew.sh) or [MacPorts](https://www.macports.org) to manage dependencies, then: 116 | 117 | ``` 118 | $ brew install automake autoconf wxmac poppler cairo pkg-config 119 | ``` 120 | 121 | or 122 | 123 | ``` 124 | $ sudo port install automake autoconf wxWidgets-3.0 poppler cairo pkgconfig 125 | ``` 126 | 127 | Note that many more libraries are required on Windows, where none of the 128 | libraries Cairo and Poppler use are normally available. At the time of writing, 129 | transitive cover of the above dependencies included fontconfig, freetype, glib, 130 | libpng, pixman, gettext, libiconv, libjpeg and zlib. 131 | 132 | 133 | ### Compiling on Windows using MSYS + MinGW 134 | 135 | 1. First of all, you will need working MinGW installation with MSYS2 environment 136 | and C++ compiler. Install MSYS2 by following [their instructions](https://www.msys2.org). 137 | 138 | 1. Once installed, launch the MSYS2 MinGW shell. It will open a terminal window; 139 | type `cd /c/directory/with/diff-pdf` to go to the directory with diff-pdf 140 | sources. 141 | 142 | 1. You will need to install additional MSYS components that are not normally 143 | included with MSYS, using these commands: 144 | 145 | ``` 146 | $ pacman -Syu 147 | $ pacman -S automake autoconf pkg-config make zip pactoys 148 | $ pacboy -S gcc:p poppler:p wxWidgets:p 149 | ``` 150 | 151 | 1. Build diff-pdf in the same way as in the instructions for Unix above: 152 | 153 | ``` 154 | $ ./bootstrap # only if building from git repository 155 | $ ./configure 156 | $ make 157 | ``` 158 | 159 | 1. To build a ZIP archive will all DLLs, run 160 | ``` 161 | $ make windows-dist 162 | ``` 163 | 164 | 165 | ## Installing 166 | 167 | On Unix, the usual `make install` is sufficient. 168 | 169 | On Windows, installation is not necessary, just copy the files somewhere. If 170 | you built it following the instructions above, all the necessary files will be 171 | in the created ZIP archive. 172 | -------------------------------------------------------------------------------- /admin/wxwin.m4: -------------------------------------------------------------------------------- 1 | dnl --------------------------------------------------------------------------- 2 | dnl Author: wxWidgets development team, 3 | dnl Francesco Montorsi, 4 | dnl Bob McCown (Mac-testing) 5 | dnl Creation date: 24/11/2001 6 | dnl --------------------------------------------------------------------------- 7 | 8 | dnl =========================================================================== 9 | dnl Table of Contents of this macro file: 10 | dnl ------------------------------------- 11 | dnl 12 | dnl SECTION A: wxWidgets main macros 13 | dnl - WX_CONFIG_OPTIONS 14 | dnl - WX_CONFIG_CHECK 15 | dnl - WXRC_CHECK 16 | dnl - WX_STANDARD_OPTIONS 17 | dnl - WX_CONVERT_STANDARD_OPTIONS_TO_WXCONFIG_FLAGS 18 | dnl - WX_DETECT_STANDARD_OPTION_VALUES 19 | dnl 20 | dnl SECTION B: wxWidgets-related utilities 21 | dnl - WX_LIKE_LIBNAME 22 | dnl - WX_ARG_ENABLE_YESNOAUTO 23 | dnl - WX_ARG_WITH_YESNOAUTO 24 | dnl 25 | dnl SECTION C: messages to the user 26 | dnl - WX_STANDARD_OPTIONS_SUMMARY_MSG 27 | dnl - WX_STANDARD_OPTIONS_SUMMARY_MSG_BEGIN 28 | dnl - WX_STANDARD_OPTIONS_SUMMARY_MSG_END 29 | dnl - WX_BOOLOPT_SUMMARY 30 | dnl 31 | dnl The special "WX_DEBUG_CONFIGURE" variable can be set to 1 to enable extra 32 | dnl debug output on stdout from these macros. 33 | dnl =========================================================================== 34 | 35 | 36 | dnl --------------------------------------------------------------------------- 37 | dnl Macros for wxWidgets detection. Typically used in configure.in as: 38 | dnl 39 | dnl AC_ARG_ENABLE(...) 40 | dnl AC_ARG_WITH(...) 41 | dnl ... 42 | dnl WX_CONFIG_OPTIONS 43 | dnl ... 44 | dnl ... 45 | dnl WX_CONFIG_CHECK([2.6.0], [wxWin=1]) 46 | dnl if test "$wxWin" != 1; then 47 | dnl AC_MSG_ERROR([ 48 | dnl wxWidgets must be installed on your system 49 | dnl but wx-config script couldn't be found. 50 | dnl 51 | dnl Please check that wx-config is in path, the directory 52 | dnl where wxWidgets libraries are installed (returned by 53 | dnl 'wx-config --libs' command) is in LD_LIBRARY_PATH or 54 | dnl equivalent variable and wxWidgets version is 2.3.4 or above. 55 | dnl ]) 56 | dnl fi 57 | dnl CPPFLAGS="$CPPFLAGS $WX_CPPFLAGS" 58 | dnl CXXFLAGS="$CXXFLAGS $WX_CXXFLAGS_ONLY" 59 | dnl CFLAGS="$CFLAGS $WX_CFLAGS_ONLY" 60 | dnl 61 | dnl LIBS="$LIBS $WX_LIBS" 62 | dnl 63 | dnl If you want to support standard --enable-debug/unicode/shared options, you 64 | dnl may do the following: 65 | dnl 66 | dnl ... 67 | dnl AC_CANONICAL_SYSTEM 68 | dnl 69 | dnl # define configure options 70 | dnl WX_CONFIG_OPTIONS 71 | dnl WX_STANDARD_OPTIONS([debug,unicode,shared,toolkit,wxshared]) 72 | dnl 73 | dnl # basic configure checks 74 | dnl ... 75 | dnl 76 | dnl # we want to always have DEBUG==WX_DEBUG and UNICODE==WX_UNICODE 77 | dnl WX_DEBUG=$DEBUG 78 | dnl WX_UNICODE=$UNICODE 79 | dnl 80 | dnl WX_CONVERT_STANDARD_OPTIONS_TO_WXCONFIG_FLAGS 81 | dnl WX_CONFIG_CHECK([2.8.0], [wxWin=1],,[html,core,net,base],[$WXCONFIG_FLAGS]) 82 | dnl WX_DETECT_STANDARD_OPTION_VALUES 83 | dnl 84 | dnl # write the output files 85 | dnl AC_CONFIG_FILES([Makefile ...]) 86 | dnl AC_OUTPUT 87 | dnl 88 | dnl # optional: just to show a message to the user 89 | dnl WX_STANDARD_OPTIONS_SUMMARY_MSG 90 | dnl 91 | dnl --------------------------------------------------------------------------- 92 | 93 | 94 | dnl --------------------------------------------------------------------------- 95 | dnl WX_CONFIG_OPTIONS 96 | dnl 97 | dnl adds support for --wx-prefix, --wx-exec-prefix, --with-wxdir and 98 | dnl --wx-config command line options 99 | dnl --------------------------------------------------------------------------- 100 | 101 | AC_DEFUN([WX_CONFIG_OPTIONS], 102 | [ 103 | AC_ARG_WITH(wxdir, 104 | [ --with-wxdir=PATH Use uninstalled version of wxWidgets in PATH], 105 | [ wx_config_name="$withval/wx-config" 106 | wx_config_args="--inplace"]) 107 | AC_ARG_WITH(wx-config, 108 | [ --with-wx-config=CONFIG wx-config script to use (optional)], 109 | wx_config_name="$withval" ) 110 | AC_ARG_WITH(wx-prefix, 111 | [ --with-wx-prefix=PREFIX Prefix where wxWidgets is installed (optional)], 112 | wx_config_prefix="$withval", wx_config_prefix="") 113 | AC_ARG_WITH(wx-exec-prefix, 114 | [ --with-wx-exec-prefix=PREFIX 115 | Exec prefix where wxWidgets is installed (optional)], 116 | wx_config_exec_prefix="$withval", wx_config_exec_prefix="") 117 | ]) 118 | 119 | dnl Helper macro for checking if wx version is at least $1.$2.$3, set's 120 | dnl wx_ver_ok=yes if it is: 121 | AC_DEFUN([_WX_PRIVATE_CHECK_VERSION], 122 | [ 123 | wx_ver_ok="" 124 | if test "x$WX_VERSION" != x ; then 125 | if test $wx_config_major_version -gt $1; then 126 | wx_ver_ok=yes 127 | else 128 | if test $wx_config_major_version -eq $1; then 129 | if test $wx_config_minor_version -gt $2; then 130 | wx_ver_ok=yes 131 | else 132 | if test $wx_config_minor_version -eq $2; then 133 | if test $wx_config_micro_version -ge $3; then 134 | wx_ver_ok=yes 135 | fi 136 | fi 137 | fi 138 | fi 139 | fi 140 | fi 141 | ]) 142 | 143 | dnl --------------------------------------------------------------------------- 144 | dnl WX_CONFIG_CHECK(VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND 145 | dnl [, WX-LIBS [, ADDITIONAL-WX-CONFIG-FLAGS]]]]) 146 | dnl 147 | dnl Test for wxWidgets, and define WX_C*FLAGS, WX_LIBS and WX_LIBS_STATIC 148 | dnl (the latter is for static linking against wxWidgets). Set WX_CONFIG_NAME 149 | dnl environment variable to override the default name of the wx-config script 150 | dnl to use. Set WX_CONFIG_PATH to specify the full path to wx-config - in this 151 | dnl case the macro won't even waste time on tests for its existence. 152 | dnl 153 | dnl Optional WX-LIBS argument contains comma- or space-separated list of 154 | dnl wxWidgets libraries to link against. If it is not specified then WX_LIBS 155 | dnl and WX_LIBS_STATIC will contain flags to link with all of the core 156 | dnl wxWidgets libraries. 157 | dnl 158 | dnl Optional ADDITIONAL-WX-CONFIG-FLAGS argument is appended to wx-config 159 | dnl invocation command in present. It can be used to fine-tune lookup of 160 | dnl best wxWidgets build available. 161 | dnl 162 | dnl Example use: 163 | dnl WX_CONFIG_CHECK([2.6.0], [wxWin=1], [wxWin=0], [html,core,net] 164 | dnl [--unicode --debug]) 165 | dnl --------------------------------------------------------------------------- 166 | 167 | dnl 168 | dnl Get the cflags and libraries from the wx-config script 169 | dnl 170 | AC_DEFUN([WX_CONFIG_CHECK], 171 | [ 172 | dnl do we have wx-config name: it can be wx-config or wxd-config or ... 173 | if test x${WX_CONFIG_NAME+set} != xset ; then 174 | WX_CONFIG_NAME=wx-config 175 | fi 176 | 177 | if test "x$wx_config_name" != x ; then 178 | WX_CONFIG_NAME="$wx_config_name" 179 | fi 180 | 181 | dnl deal with optional prefixes 182 | if test x$wx_config_exec_prefix != x ; then 183 | wx_config_args="$wx_config_args --exec-prefix=$wx_config_exec_prefix" 184 | WX_LOOKUP_PATH="$wx_config_exec_prefix/bin" 185 | fi 186 | if test x$wx_config_prefix != x ; then 187 | wx_config_args="$wx_config_args --prefix=$wx_config_prefix" 188 | WX_LOOKUP_PATH="$WX_LOOKUP_PATH:$wx_config_prefix/bin" 189 | fi 190 | if test "$cross_compiling" = "yes"; then 191 | wx_config_args="$wx_config_args --host=$host_alias" 192 | fi 193 | 194 | dnl don't search the PATH if WX_CONFIG_NAME is absolute filename 195 | if test -x "$WX_CONFIG_NAME" ; then 196 | AC_MSG_CHECKING(for wx-config) 197 | WX_CONFIG_PATH="$WX_CONFIG_NAME" 198 | AC_MSG_RESULT($WX_CONFIG_PATH) 199 | else 200 | AC_PATH_PROG(WX_CONFIG_PATH, $WX_CONFIG_NAME, no, "$WX_LOOKUP_PATH:$PATH") 201 | fi 202 | 203 | if test "$WX_CONFIG_PATH" != "no" ; then 204 | WX_VERSION="" 205 | 206 | min_wx_version=ifelse([$1], ,2.2.1,$1) 207 | if test -z "$5" ; then 208 | AC_MSG_CHECKING([for wxWidgets version >= $min_wx_version]) 209 | else 210 | AC_MSG_CHECKING([for wxWidgets version >= $min_wx_version ($5)]) 211 | fi 212 | 213 | dnl don't add the libraries ($4) to this variable as this would result in 214 | dnl an error when it's used with --version below 215 | WX_CONFIG_WITH_ARGS="$WX_CONFIG_PATH $wx_config_args $5" 216 | 217 | WX_VERSION=`$WX_CONFIG_WITH_ARGS --version 2>/dev/null` 218 | wx_config_major_version=`echo $WX_VERSION | \ 219 | sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` 220 | wx_config_minor_version=`echo $WX_VERSION | \ 221 | sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` 222 | wx_config_micro_version=`echo $WX_VERSION | \ 223 | sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` 224 | 225 | wx_requested_major_version=`echo $min_wx_version | \ 226 | sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` 227 | wx_requested_minor_version=`echo $min_wx_version | \ 228 | sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` 229 | wx_requested_micro_version=`echo $min_wx_version | \ 230 | sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` 231 | 232 | _WX_PRIVATE_CHECK_VERSION([$wx_requested_major_version], 233 | [$wx_requested_minor_version], 234 | [$wx_requested_micro_version]) 235 | 236 | if test -n "$wx_ver_ok"; then 237 | AC_MSG_RESULT(yes (version $WX_VERSION)) 238 | WX_LIBS=`$WX_CONFIG_WITH_ARGS --libs $4` 239 | 240 | dnl is this even still appropriate? --static is a real option now 241 | dnl and WX_CONFIG_WITH_ARGS is likely to contain it if that is 242 | dnl what the user actually wants, making this redundant at best. 243 | dnl For now keep it in case anyone actually used it in the past. 244 | AC_MSG_CHECKING([for wxWidgets static library]) 245 | WX_LIBS_STATIC=`$WX_CONFIG_WITH_ARGS --static --libs $4 2>/dev/null` 246 | if test "x$WX_LIBS_STATIC" = "x"; then 247 | AC_MSG_RESULT(no) 248 | else 249 | AC_MSG_RESULT(yes) 250 | fi 251 | 252 | dnl starting with version 2.2.6 wx-config has --cppflags argument 253 | wx_has_cppflags="" 254 | if test $wx_config_major_version -gt 2; then 255 | wx_has_cppflags=yes 256 | else 257 | if test $wx_config_major_version -eq 2; then 258 | if test $wx_config_minor_version -gt 2; then 259 | wx_has_cppflags=yes 260 | else 261 | if test $wx_config_minor_version -eq 2; then 262 | if test $wx_config_micro_version -ge 6; then 263 | wx_has_cppflags=yes 264 | fi 265 | fi 266 | fi 267 | fi 268 | fi 269 | 270 | dnl starting with version 2.7.0 wx-config has --rescomp option 271 | wx_has_rescomp="" 272 | if test $wx_config_major_version -gt 2; then 273 | wx_has_rescomp=yes 274 | else 275 | if test $wx_config_major_version -eq 2; then 276 | if test $wx_config_minor_version -ge 7; then 277 | wx_has_rescomp=yes 278 | fi 279 | fi 280 | fi 281 | if test "x$wx_has_rescomp" = x ; then 282 | dnl cannot give any useful info for resource compiler 283 | WX_RESCOMP= 284 | else 285 | WX_RESCOMP=`$WX_CONFIG_WITH_ARGS --rescomp` 286 | fi 287 | 288 | if test "x$wx_has_cppflags" = x ; then 289 | dnl no choice but to define all flags like CFLAGS 290 | WX_CFLAGS=`$WX_CONFIG_WITH_ARGS --cflags $4` 291 | WX_CPPFLAGS=$WX_CFLAGS 292 | WX_CXXFLAGS=$WX_CFLAGS 293 | 294 | WX_CFLAGS_ONLY=$WX_CFLAGS 295 | WX_CXXFLAGS_ONLY=$WX_CFLAGS 296 | else 297 | dnl we have CPPFLAGS included in CFLAGS included in CXXFLAGS 298 | WX_CPPFLAGS=`$WX_CONFIG_WITH_ARGS --cppflags $4` 299 | WX_CXXFLAGS=`$WX_CONFIG_WITH_ARGS --cxxflags $4` 300 | WX_CFLAGS=`$WX_CONFIG_WITH_ARGS --cflags $4` 301 | 302 | WX_CFLAGS_ONLY=`echo $WX_CFLAGS | sed "s@^$WX_CPPFLAGS *@@"` 303 | WX_CXXFLAGS_ONLY=`echo $WX_CXXFLAGS | sed "s@^$WX_CFLAGS *@@"` 304 | fi 305 | 306 | ifelse([$2], , :, [$2]) 307 | 308 | else 309 | 310 | if test "x$WX_VERSION" = x; then 311 | dnl no wx-config at all 312 | AC_MSG_RESULT(no) 313 | else 314 | AC_MSG_RESULT(no (version $WX_VERSION is not new enough)) 315 | fi 316 | 317 | WX_CFLAGS="" 318 | WX_CPPFLAGS="" 319 | WX_CXXFLAGS="" 320 | WX_LIBS="" 321 | WX_LIBS_STATIC="" 322 | WX_RESCOMP="" 323 | 324 | if test ! -z "$5"; then 325 | 326 | wx_error_message=" 327 | The configuration you asked for $PACKAGE_NAME requires a wxWidgets 328 | build with the following settings: 329 | $5 330 | but such build is not available. 331 | 332 | To see the wxWidgets builds available on this system, please use 333 | 'wx-config --list' command. To use the default build, returned by 334 | 'wx-config --selected-config', use the options with their 'auto' 335 | default values." 336 | 337 | fi 338 | 339 | wx_error_message=" 340 | The requested wxWidgets build couldn't be found. 341 | $wx_error_message 342 | 343 | If you still get this error, then check that 'wx-config' is 344 | in path, the directory where wxWidgets libraries are installed 345 | (returned by 'wx-config --libs' command) is in LD_LIBRARY_PATH 346 | or equivalent variable and wxWidgets version is $1 or above." 347 | 348 | ifelse([$3], , AC_MSG_ERROR([$wx_error_message]), [$3]) 349 | 350 | fi 351 | else 352 | 353 | WX_CFLAGS="" 354 | WX_CPPFLAGS="" 355 | WX_CXXFLAGS="" 356 | WX_LIBS="" 357 | WX_LIBS_STATIC="" 358 | WX_RESCOMP="" 359 | 360 | ifelse([$3], , :, [$3]) 361 | 362 | fi 363 | 364 | AC_SUBST(WX_CPPFLAGS) 365 | AC_SUBST(WX_CFLAGS) 366 | AC_SUBST(WX_CXXFLAGS) 367 | AC_SUBST(WX_CFLAGS_ONLY) 368 | AC_SUBST(WX_CXXFLAGS_ONLY) 369 | AC_SUBST(WX_LIBS) 370 | AC_SUBST(WX_LIBS_STATIC) 371 | AC_SUBST(WX_VERSION) 372 | AC_SUBST(WX_RESCOMP) 373 | 374 | dnl need to export also WX_VERSION_MINOR and WX_VERSION_MAJOR symbols 375 | dnl to support wxpresets bakefiles (we export also WX_VERSION_MICRO for completeness): 376 | WX_VERSION_MAJOR="$wx_config_major_version" 377 | WX_VERSION_MINOR="$wx_config_minor_version" 378 | WX_VERSION_MICRO="$wx_config_micro_version" 379 | AC_SUBST(WX_VERSION_MAJOR) 380 | AC_SUBST(WX_VERSION_MINOR) 381 | AC_SUBST(WX_VERSION_MICRO) 382 | ]) 383 | 384 | dnl --------------------------------------------------------------------------- 385 | dnl Get information on the wxrc program for making C++, Python and xrs 386 | dnl resource files. 387 | dnl 388 | dnl AC_ARG_ENABLE(...) 389 | dnl AC_ARG_WITH(...) 390 | dnl ... 391 | dnl WX_CONFIG_OPTIONS 392 | dnl ... 393 | dnl WX_CONFIG_CHECK(2.6.0, wxWin=1) 394 | dnl if test "$wxWin" != 1; then 395 | dnl AC_MSG_ERROR([ 396 | dnl wxWidgets must be installed on your system 397 | dnl but wx-config script couldn't be found. 398 | dnl 399 | dnl Please check that wx-config is in path, the directory 400 | dnl where wxWidgets libraries are installed (returned by 401 | dnl 'wx-config --libs' command) is in LD_LIBRARY_PATH or 402 | dnl equivalent variable and wxWidgets version is 2.6.0 or above. 403 | dnl ]) 404 | dnl fi 405 | dnl 406 | dnl WXRC_CHECK([HAVE_WXRC=1], [HAVE_WXRC=0]) 407 | dnl if test "x$HAVE_WXRC" != x1; then 408 | dnl AC_MSG_ERROR([ 409 | dnl The wxrc program was not installed or not found. 410 | dnl 411 | dnl Please check the wxWidgets installation. 412 | dnl ]) 413 | dnl fi 414 | dnl 415 | dnl CPPFLAGS="$CPPFLAGS $WX_CPPFLAGS" 416 | dnl CXXFLAGS="$CXXFLAGS $WX_CXXFLAGS_ONLY" 417 | dnl CFLAGS="$CFLAGS $WX_CFLAGS_ONLY" 418 | dnl 419 | dnl LDFLAGS="$LDFLAGS $WX_LIBS" 420 | dnl --------------------------------------------------------------------------- 421 | 422 | dnl --------------------------------------------------------------------------- 423 | dnl WXRC_CHECK([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) 424 | dnl 425 | dnl Test for wxWidgets' wxrc program for creating either C++, Python or XRS 426 | dnl resources. The variable WXRC will be set and substituted in the configure 427 | dnl script and Makefiles. 428 | dnl 429 | dnl Example use: 430 | dnl WXRC_CHECK([wxrc=1], [wxrc=0]) 431 | dnl --------------------------------------------------------------------------- 432 | 433 | dnl 434 | dnl wxrc program from the wx-config script 435 | dnl 436 | AC_DEFUN([WXRC_CHECK], 437 | [ 438 | AC_ARG_VAR([WXRC], [Path to wxWidget's wxrc resource compiler]) 439 | 440 | if test "x$WX_CONFIG_NAME" = x; then 441 | AC_MSG_ERROR([The wxrc tests must run after wxWidgets test.]) 442 | else 443 | 444 | AC_MSG_CHECKING([for wxrc]) 445 | 446 | if test "x$WXRC" = x ; then 447 | dnl wx-config --utility is a new addition to wxWidgets: 448 | _WX_PRIVATE_CHECK_VERSION(2,5,3) 449 | if test -n "$wx_ver_ok"; then 450 | WXRC=`$WX_CONFIG_WITH_ARGS --utility=wxrc` 451 | fi 452 | fi 453 | 454 | if test "x$WXRC" = x ; then 455 | AC_MSG_RESULT([not found]) 456 | ifelse([$2], , :, [$2]) 457 | else 458 | AC_MSG_RESULT([$WXRC]) 459 | ifelse([$1], , :, [$1]) 460 | fi 461 | 462 | AC_SUBST(WXRC) 463 | fi 464 | ]) 465 | 466 | dnl --------------------------------------------------------------------------- 467 | dnl WX_LIKE_LIBNAME([output-var] [prefix], [name]) 468 | dnl 469 | dnl Sets the "output-var" variable to the name of a library named with same 470 | dnl wxWidgets rule. 471 | dnl E.g. for output-var=='lib', name=='test', prefix='mine', sets 472 | dnl the $lib variable to: 473 | dnl 'mine_gtk2ud_test-2.8' 474 | dnl if WX_PORT=gtk2, WX_UNICODE=1, WX_DEBUG=1 and WX_RELEASE=28 475 | dnl --------------------------------------------------------------------------- 476 | AC_DEFUN([WX_LIKE_LIBNAME], 477 | [ 478 | wx_temp="$2""_""$WX_PORT" 479 | 480 | dnl add the [u][d] string 481 | if test "$WX_UNICODE" = "1"; then 482 | wx_temp="$wx_temp""u" 483 | fi 484 | if test "$WX_DEBUG" = "1"; then 485 | wx_temp="$wx_temp""d" 486 | fi 487 | 488 | dnl complete the name of the lib 489 | wx_temp="$wx_temp""_""$3""-$WX_VERSION_MAJOR.$WX_VERSION_MINOR" 490 | 491 | dnl save it in the user's variable 492 | $1=$wx_temp 493 | ]) 494 | 495 | dnl --------------------------------------------------------------------------- 496 | dnl WX_ARG_ENABLE_YESNOAUTO/WX_ARG_WITH_YESNOAUTO 497 | dnl 498 | dnl Two little custom macros which define the ENABLE/WITH configure arguments. 499 | dnl Macro arguments: 500 | dnl $1 = the name of the --enable / --with feature 501 | dnl $2 = the name of the variable associated 502 | dnl $3 = the description of that feature 503 | dnl $4 = the default value for that feature 504 | dnl $5 = additional action to do in case option is given with "yes" value 505 | dnl --------------------------------------------------------------------------- 506 | AC_DEFUN([WX_ARG_ENABLE_YESNOAUTO], 507 | [AC_ARG_ENABLE($1, 508 | AC_HELP_STRING([--enable-$1], [$3 (default is $4)]), 509 | [], [enableval="$4"]) 510 | 511 | dnl Show a message to the user about this option 512 | AC_MSG_CHECKING([for the --enable-$1 option]) 513 | if test "$enableval" = "yes" ; then 514 | AC_MSG_RESULT([yes]) 515 | $2=1 516 | $5 517 | elif test "$enableval" = "no" ; then 518 | AC_MSG_RESULT([no]) 519 | $2=0 520 | elif test "$enableval" = "auto" ; then 521 | AC_MSG_RESULT([will be automatically detected]) 522 | $2="auto" 523 | else 524 | AC_MSG_ERROR([ 525 | Unrecognized option value (allowed values: yes, no, auto) 526 | ]) 527 | fi 528 | ]) 529 | 530 | AC_DEFUN([WX_ARG_WITH_YESNOAUTO], 531 | [AC_ARG_WITH($1, 532 | AC_HELP_STRING([--with-$1], [$3 (default is $4)]), 533 | [], [withval="$4"]) 534 | 535 | dnl Show a message to the user about this option 536 | AC_MSG_CHECKING([for the --with-$1 option]) 537 | if test "$withval" = "yes" ; then 538 | AC_MSG_RESULT([yes]) 539 | $2=1 540 | $5 541 | dnl NB: by default we don't allow --with-$1=no option 542 | dnl since it does not make much sense ! 543 | elif test "$6" = "1" -a "$withval" = "no" ; then 544 | AC_MSG_RESULT([no]) 545 | $2=0 546 | elif test "$withval" = "auto" ; then 547 | AC_MSG_RESULT([will be automatically detected]) 548 | $2="auto" 549 | else 550 | AC_MSG_ERROR([ 551 | Unrecognized option value (allowed values: yes, auto) 552 | ]) 553 | fi 554 | ]) 555 | 556 | 557 | dnl --------------------------------------------------------------------------- 558 | dnl WX_STANDARD_OPTIONS([options-to-add]) 559 | dnl 560 | dnl Adds to the configure script one or more of the following options: 561 | dnl --enable-[debug|unicode|shared|wxshared|wxdebug] 562 | dnl --with-[gtk|msw|motif|x11|mac|dfb] 563 | dnl --with-wxversion 564 | dnl Then checks for their presence and eventually set the DEBUG, UNICODE, SHARED, 565 | dnl PORT, WX_SHARED, WX_DEBUG, variables to one of the "yes", "no", "auto" values. 566 | dnl 567 | dnl Note that e.g. UNICODE != WX_UNICODE; the first is the value of the 568 | dnl --enable-unicode option (in boolean format) while the second indicates 569 | dnl if wxWidgets was built in Unicode mode (and still is in boolean format). 570 | dnl --------------------------------------------------------------------------- 571 | AC_DEFUN([WX_STANDARD_OPTIONS], 572 | [ 573 | 574 | dnl the following lines will expand to WX_ARG_ENABLE_YESNOAUTO calls if and only if 575 | dnl the $1 argument contains respectively the debug,unicode or shared options. 576 | 577 | dnl be careful here not to set debug flag if only "wxdebug" was specified 578 | ifelse(regexp([$1], [\bdebug]), [-1],, 579 | [WX_ARG_ENABLE_YESNOAUTO([debug], [DEBUG], [Build in debug mode], [auto])]) 580 | 581 | ifelse(index([$1], [unicode]), [-1],, 582 | [WX_ARG_ENABLE_YESNOAUTO([unicode], [UNICODE], [Build in Unicode mode], [auto])]) 583 | 584 | ifelse(regexp([$1], [\bshared]), [-1],, 585 | [WX_ARG_ENABLE_YESNOAUTO([shared], [SHARED], [Build as shared library], [auto])]) 586 | 587 | dnl WX_ARG_WITH_YESNOAUTO cannot be used for --with-toolkit since it's an option 588 | dnl which must be able to accept the auto|gtk1|gtk2|msw|... values 589 | ifelse(index([$1], [toolkit]), [-1],, 590 | [ 591 | AC_ARG_WITH([toolkit], 592 | AC_HELP_STRING([--with-toolkit], 593 | [Build against a specific wxWidgets toolkit (default is auto)]), 594 | [], [withval="auto"]) 595 | 596 | dnl Show a message to the user about this option 597 | AC_MSG_CHECKING([for the --with-toolkit option]) 598 | if test "$withval" = "auto" ; then 599 | AC_MSG_RESULT([will be automatically detected]) 600 | TOOLKIT="auto" 601 | else 602 | TOOLKIT="$withval" 603 | 604 | dnl PORT must be one of the allowed values 605 | if test "$TOOLKIT" != "gtk1" -a "$TOOLKIT" != "gtk2" -a \ 606 | "$TOOLKIT" != "msw" -a "$TOOLKIT" != "motif" -a \ 607 | "$TOOLKIT" != "osx_carbon" -a "$TOOLKIT" != "osx_cocoa" -a \ 608 | "$TOOLKIT" != "dfb" -a "$TOOLKIT" != "x11"; then 609 | AC_MSG_ERROR([ 610 | Unrecognized option value (allowed values: auto, gtk1, gtk2, msw, motif, osx_carbon, osx_cocoa, dfb, x11) 611 | ]) 612 | fi 613 | 614 | AC_MSG_RESULT([$TOOLKIT]) 615 | fi 616 | ]) 617 | 618 | dnl ****** IMPORTANT ******* 619 | dnl Unlike for the UNICODE setting, you can build your program in 620 | dnl shared mode against a static build of wxWidgets. Thus we have the 621 | dnl following option which allows these mixtures. E.g. 622 | dnl 623 | dnl ./configure --disable-shared --with-wxshared 624 | dnl 625 | dnl will build your library in static mode against the first available 626 | dnl shared build of wxWidgets. 627 | dnl 628 | dnl Note that's not possible to do the viceversa: 629 | dnl 630 | dnl ./configure --enable-shared --without-wxshared 631 | dnl 632 | dnl Doing so you would try to build your library in shared mode against a static 633 | dnl build of wxWidgets. This is not possible (you would mix PIC and non PIC code) ! 634 | dnl A check for this combination of options is in WX_DETECT_STANDARD_OPTION_VALUES 635 | dnl (where we know what 'auto' should be expanded to). 636 | dnl 637 | dnl If you try to build something in ANSI mode against a UNICODE build 638 | dnl of wxWidgets or in RELEASE mode against a DEBUG build of wxWidgets, 639 | dnl then at best you'll get ton of linking errors ! 640 | dnl ************************ 641 | 642 | ifelse(index([$1], [wxshared]), [-1],, 643 | [ 644 | WX_ARG_WITH_YESNOAUTO( 645 | [wxshared], [WX_SHARED], 646 | [Force building against a shared build of wxWidgets, even if --disable-shared is given], 647 | [auto], [], [1]) 648 | ]) 649 | 650 | dnl Just like for SHARED and WX_SHARED it may happen that some adventurous 651 | dnl peoples will want to mix a wxWidgets release build with a debug build of 652 | dnl his app/lib. So, we have both DEBUG and WX_DEBUG variables. 653 | ifelse(index([$1], [wxdebug]), [-1],, 654 | [ 655 | WX_ARG_WITH_YESNOAUTO( 656 | [wxdebug], [WX_DEBUG], 657 | [Force building against a debug build of wxWidgets, even if --disable-debug is given], 658 | [auto], [], [1]) 659 | ]) 660 | 661 | dnl WX_ARG_WITH_YESNOAUTO cannot be used for --with-wxversion since it's an option 662 | dnl which accepts the "auto|2.6|2.7|2.8|2.9|3.0" etc etc values 663 | ifelse(index([$1], [wxversion]), [-1],, 664 | [ 665 | AC_ARG_WITH([wxversion], 666 | AC_HELP_STRING([--with-wxversion], 667 | [Build against a specific version of wxWidgets (default is auto)]), 668 | [], [withval="auto"]) 669 | 670 | dnl Show a message to the user about this option 671 | AC_MSG_CHECKING([for the --with-wxversion option]) 672 | if test "$withval" = "auto" ; then 673 | AC_MSG_RESULT([will be automatically detected]) 674 | WX_RELEASE="auto" 675 | else 676 | 677 | wx_requested_major_version=`echo $withval | \ 678 | sed 's/\([[0-9]]*\).\([[0-9]]*\).*/\1/'` 679 | wx_requested_minor_version=`echo $withval | \ 680 | sed 's/\([[0-9]]*\).\([[0-9]]*\).*/\2/'` 681 | 682 | dnl both vars above must be exactly 1 digit 683 | if test "${#wx_requested_major_version}" != "1" -o \ 684 | "${#wx_requested_minor_version}" != "1" ; then 685 | AC_MSG_ERROR([ 686 | Unrecognized option value (allowed values: auto, 2.6, 2.7, 2.8, 2.9, 3.0) 687 | ]) 688 | fi 689 | 690 | WX_RELEASE="$wx_requested_major_version"".""$wx_requested_minor_version" 691 | AC_MSG_RESULT([$WX_RELEASE]) 692 | fi 693 | ]) 694 | 695 | if test "$WX_DEBUG_CONFIGURE" = "1"; then 696 | echo "[[dbg]] DEBUG: $DEBUG, WX_DEBUG: $WX_DEBUG" 697 | echo "[[dbg]] UNICODE: $UNICODE, WX_UNICODE: $WX_UNICODE" 698 | echo "[[dbg]] SHARED: $SHARED, WX_SHARED: $WX_SHARED" 699 | echo "[[dbg]] TOOLKIT: $TOOLKIT, WX_TOOLKIT: $WX_TOOLKIT" 700 | echo "[[dbg]] VERSION: $VERSION, WX_RELEASE: $WX_RELEASE" 701 | fi 702 | ]) 703 | 704 | 705 | dnl --------------------------------------------------------------------------- 706 | dnl WX_CONVERT_STANDARD_OPTIONS_TO_WXCONFIG_FLAGS 707 | dnl 708 | dnl Sets the WXCONFIG_FLAGS string using the SHARED,DEBUG,UNICODE variable values 709 | dnl which are different from "auto". 710 | dnl Thus this macro needs to be called only once all options have been set. 711 | dnl --------------------------------------------------------------------------- 712 | AC_DEFUN([WX_CONVERT_STANDARD_OPTIONS_TO_WXCONFIG_FLAGS], 713 | [ 714 | if test "$WX_SHARED" = "1" ; then 715 | WXCONFIG_FLAGS="--static=no " 716 | elif test "$WX_SHARED" = "0" ; then 717 | WXCONFIG_FLAGS="--static=yes " 718 | fi 719 | 720 | if test "$WX_DEBUG" = "1" ; then 721 | WXCONFIG_FLAGS="$WXCONFIG_FLAGS""--debug=yes " 722 | elif test "$WX_DEBUG" = "0" ; then 723 | WXCONFIG_FLAGS="$WXCONFIG_FLAGS""--debug=no " 724 | fi 725 | 726 | dnl The user should have set WX_UNICODE=UNICODE 727 | if test "$WX_UNICODE" = "1" ; then 728 | WXCONFIG_FLAGS="$WXCONFIG_FLAGS""--unicode=yes " 729 | elif test "$WX_UNICODE" = "0" ; then 730 | WXCONFIG_FLAGS="$WXCONFIG_FLAGS""--unicode=no " 731 | fi 732 | 733 | if test "$TOOLKIT" != "auto" ; then 734 | WXCONFIG_FLAGS="$WXCONFIG_FLAGS""--toolkit=$TOOLKIT " 735 | fi 736 | 737 | if test "$WX_RELEASE" != "auto" ; then 738 | WXCONFIG_FLAGS="$WXCONFIG_FLAGS""--version=$WX_RELEASE " 739 | fi 740 | 741 | dnl strip out the last space of the string 742 | WXCONFIG_FLAGS=${WXCONFIG_FLAGS% } 743 | 744 | if test "$WX_DEBUG_CONFIGURE" = "1"; then 745 | echo "[[dbg]] WXCONFIG_FLAGS: $WXCONFIG_FLAGS" 746 | fi 747 | ]) 748 | 749 | 750 | dnl --------------------------------------------------------------------------- 751 | dnl _WX_SELECTEDCONFIG_CHECKFOR([RESULTVAR], [STRING], [MSG] 752 | dnl [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) 753 | dnl 754 | dnl Outputs the given MSG. Then searches the given STRING in the wxWidgets 755 | dnl additional CPP flags and put the result of the search in WX_$RESULTVAR 756 | dnl also adding the "yes" or "no" message result to MSG. 757 | dnl --------------------------------------------------------------------------- 758 | AC_DEFUN([_WX_SELECTEDCONFIG_CHECKFOR], 759 | [ 760 | if test "$$1" = "auto" ; then 761 | 762 | dnl The user does not have particular preferences for this option; 763 | dnl so we will detect the wxWidgets relative build setting and use it 764 | AC_MSG_CHECKING([$3]) 765 | 766 | dnl set WX_$1 variable to 1 if the $WX_SELECTEDCONFIG contains the $2 767 | dnl string or to 0 otherwise. 768 | dnl NOTE: 'expr match STRING REGEXP' cannot be used since on Mac it 769 | dnl doesn't work; we use 'expr STRING : REGEXP' instead 770 | WX_$1=$(expr "$WX_SELECTEDCONFIG" : ".*$2.*") 771 | 772 | if test "$WX_$1" != "0"; then 773 | WX_$1=1 774 | AC_MSG_RESULT([yes]) 775 | ifelse([$4], , :, [$4]) 776 | else 777 | WX_$1=0 778 | AC_MSG_RESULT([no]) 779 | ifelse([$5], , :, [$5]) 780 | fi 781 | else 782 | 783 | dnl Use the setting given by the user 784 | WX_$1=$$1 785 | fi 786 | ]) 787 | 788 | dnl --------------------------------------------------------------------------- 789 | dnl WX_DETECT_STANDARD_OPTION_VALUES 790 | dnl 791 | dnl Detects the values of the following variables: 792 | dnl 1) WX_RELEASE 793 | dnl 2) WX_UNICODE 794 | dnl 3) WX_DEBUG 795 | dnl 4) WX_SHARED (and also WX_STATIC) 796 | dnl 5) WX_PORT 797 | dnl from the previously selected wxWidgets build; this macro in fact must be 798 | dnl called *after* calling the WX_CONFIG_CHECK macro. 799 | dnl 800 | dnl Note that the WX_VERSION_MAJOR, WX_VERSION_MINOR symbols are already set 801 | dnl by WX_CONFIG_CHECK macro 802 | dnl --------------------------------------------------------------------------- 803 | AC_DEFUN([WX_DETECT_STANDARD_OPTION_VALUES], 804 | [ 805 | dnl IMPORTANT: WX_VERSION contains all three major.minor.micro digits, 806 | dnl while WX_RELEASE only the major.minor ones. 807 | WX_RELEASE="$WX_VERSION_MAJOR""$WX_VERSION_MINOR" 808 | if test $WX_RELEASE -lt 26 ; then 809 | 810 | AC_MSG_ERROR([ 811 | Cannot detect the wxWidgets configuration for the selected wxWidgets build 812 | since its version is $WX_VERSION < 2.6.0; please install a newer 813 | version of wxWidgets. 814 | ]) 815 | fi 816 | 817 | dnl The wx-config we are using understands the "--selected_config" 818 | dnl option which returns an easy-parseable string ! 819 | WX_SELECTEDCONFIG=$($WX_CONFIG_WITH_ARGS --selected_config) 820 | 821 | if test "$WX_DEBUG_CONFIGURE" = "1"; then 822 | echo "[[dbg]] Using wx-config --selected-config" 823 | echo "[[dbg]] WX_SELECTEDCONFIG: $WX_SELECTEDCONFIG" 824 | fi 825 | 826 | 827 | dnl we could test directly for WX_SHARED with a line like: 828 | dnl _WX_SELECTEDCONFIG_CHECKFOR([SHARED], [shared], 829 | dnl [if wxWidgets was built in SHARED mode]) 830 | dnl but wx-config --selected-config DOES NOT outputs the 'shared' 831 | dnl word when wx was built in shared mode; it rather outputs the 832 | dnl 'static' word when built in static mode. 833 | if test $WX_SHARED = "1"; then 834 | STATIC=0 835 | elif test $WX_SHARED = "0"; then 836 | STATIC=1 837 | elif test $WX_SHARED = "auto"; then 838 | STATIC="auto" 839 | fi 840 | 841 | dnl Now set the WX_UNICODE, WX_DEBUG, WX_STATIC variables 842 | _WX_SELECTEDCONFIG_CHECKFOR([UNICODE], [unicode], 843 | [if wxWidgets was built with UNICODE enabled]) 844 | _WX_SELECTEDCONFIG_CHECKFOR([DEBUG], [debug], 845 | [if wxWidgets was built in DEBUG mode]) 846 | _WX_SELECTEDCONFIG_CHECKFOR([STATIC], [static], 847 | [if wxWidgets was built in STATIC mode]) 848 | 849 | dnl init WX_SHARED from WX_STATIC 850 | if test "$WX_STATIC" != "0"; then 851 | WX_SHARED=0 852 | else 853 | WX_SHARED=1 854 | fi 855 | 856 | AC_SUBST(WX_UNICODE) 857 | AC_SUBST(WX_DEBUG) 858 | AC_SUBST(WX_SHARED) 859 | 860 | dnl detect the WX_PORT to use 861 | if test "$TOOLKIT" = "auto" ; then 862 | 863 | dnl The user does not have particular preferences for this option; 864 | dnl so we will detect the wxWidgets relative build setting and use it 865 | AC_MSG_CHECKING([which wxWidgets toolkit was selected]) 866 | 867 | WX_GTKPORT1=$(expr "$WX_SELECTEDCONFIG" : ".*gtk1.*") 868 | WX_GTKPORT2=$(expr "$WX_SELECTEDCONFIG" : ".*gtk2.*") 869 | WX_MSWPORT=$(expr "$WX_SELECTEDCONFIG" : ".*msw.*") 870 | WX_MOTIFPORT=$(expr "$WX_SELECTEDCONFIG" : ".*motif.*") 871 | WX_OSXCOCOAPORT=$(expr "$WX_SELECTEDCONFIG" : ".*osx_cocoa.*") 872 | WX_OSXCARBONPORT=$(expr "$WX_SELECTEDCONFIG" : ".*osx_carbon.*") 873 | WX_X11PORT=$(expr "$WX_SELECTEDCONFIG" : ".*x11.*") 874 | WX_DFBPORT=$(expr "$WX_SELECTEDCONFIG" : ".*dfb.*") 875 | 876 | WX_PORT="unknown" 877 | if test "$WX_GTKPORT1" != "0"; then WX_PORT="gtk1"; fi 878 | if test "$WX_GTKPORT2" != "0"; then WX_PORT="gtk2"; fi 879 | if test "$WX_MSWPORT" != "0"; then WX_PORT="msw"; fi 880 | if test "$WX_MOTIFPORT" != "0"; then WX_PORT="motif"; fi 881 | if test "$WX_OSXCOCOAPORT" != "0"; then WX_PORT="osx_cocoa"; fi 882 | if test "$WX_OSXCARBONPORT" != "0"; then WX_PORT="osx_carbon"; fi 883 | if test "$WX_X11PORT" != "0"; then WX_PORT="x11"; fi 884 | if test "$WX_DFBPORT" != "0"; then WX_PORT="dfb"; fi 885 | 886 | dnl NOTE: backward-compatible check for wx2.8; in wx2.9 the mac 887 | dnl ports are called 'osx_cocoa' and 'osx_carbon' (see above) 888 | WX_MACPORT=$(expr "$WX_SELECTEDCONFIG" : ".*mac.*") 889 | if test "$WX_MACPORT" != "0"; then WX_PORT="mac"; fi 890 | 891 | dnl check at least one of the WX_*PORT has been set ! 892 | 893 | if test "$WX_PORT" = "unknown" ; then 894 | AC_MSG_ERROR([ 895 | Cannot detect the currently installed wxWidgets port ! 896 | Please check your 'wx-config --cxxflags'... 897 | ]) 898 | fi 899 | 900 | AC_MSG_RESULT([$WX_PORT]) 901 | else 902 | 903 | dnl Use the setting given by the user 904 | if test -z "$TOOLKIT" ; then 905 | WX_PORT=$TOOLKIT 906 | else 907 | dnl try with PORT 908 | WX_PORT=$PORT 909 | fi 910 | fi 911 | 912 | AC_SUBST(WX_PORT) 913 | 914 | if test "$WX_DEBUG_CONFIGURE" = "1"; then 915 | echo "[[dbg]] Values of all WX_* options after final detection:" 916 | echo "[[dbg]] WX_DEBUG: $WX_DEBUG" 917 | echo "[[dbg]] WX_UNICODE: $WX_UNICODE" 918 | echo "[[dbg]] WX_SHARED: $WX_SHARED" 919 | echo "[[dbg]] WX_RELEASE: $WX_RELEASE" 920 | echo "[[dbg]] WX_PORT: $WX_PORT" 921 | fi 922 | 923 | dnl Avoid problem described in the WX_STANDARD_OPTIONS which happens when 924 | dnl the user gives the options: 925 | dnl ./configure --enable-shared --without-wxshared 926 | dnl or just do 927 | dnl ./configure --enable-shared 928 | dnl but there is only a static build of wxWidgets available. 929 | if test "$WX_SHARED" = "0" -a "$SHARED" = "1"; then 930 | AC_MSG_ERROR([ 931 | Cannot build shared library against a static build of wxWidgets ! 932 | This error happens because the wxWidgets build which was selected 933 | has been detected as static while you asked to build $PACKAGE_NAME 934 | as shared library and this is not possible. 935 | Use the '--disable-shared' option to build $PACKAGE_NAME 936 | as static library or '--with-wxshared' to use wxWidgets as shared library. 937 | ]) 938 | fi 939 | 940 | dnl now we can finally update the DEBUG,UNICODE,SHARED options 941 | dnl to their final values if they were set to 'auto' 942 | if test "$DEBUG" = "auto"; then 943 | DEBUG=$WX_DEBUG 944 | fi 945 | if test "$UNICODE" = "auto"; then 946 | UNICODE=$WX_UNICODE 947 | fi 948 | if test "$SHARED" = "auto"; then 949 | SHARED=$WX_SHARED 950 | fi 951 | if test "$TOOLKIT" = "auto"; then 952 | TOOLKIT=$WX_PORT 953 | fi 954 | 955 | dnl in case the user needs a BUILD=debug/release var... 956 | if test "$DEBUG" = "1"; then 957 | BUILD="debug" 958 | elif test "$DEBUG" = "0" -o "$DEBUG" = ""; then 959 | BUILD="release" 960 | fi 961 | 962 | dnl respect the DEBUG variable adding the optimize/debug flags 963 | dnl NOTE: the CXXFLAGS are merged together with the CPPFLAGS so we 964 | dnl don't need to set them, too 965 | if test "$DEBUG" = "1"; then 966 | CXXFLAGS="$CXXFLAGS -g -O0" 967 | CFLAGS="$CFLAGS -g -O0" 968 | else 969 | CXXFLAGS="$CXXFLAGS -O2" 970 | CFLAGS="$CFLAGS -O2" 971 | fi 972 | ]) 973 | 974 | dnl --------------------------------------------------------------------------- 975 | dnl WX_BOOLOPT_SUMMARY([name of the boolean variable to show summary for], 976 | dnl [what to print when var is 1], 977 | dnl [what to print when var is 0]) 978 | dnl 979 | dnl Prints $2 when variable $1 == 1 and prints $3 when variable $1 == 0. 980 | dnl This macro mainly exists just to make configure.ac scripts more readable. 981 | dnl 982 | dnl NOTE: you need to use the [" my message"] syntax for 2nd and 3rd arguments 983 | dnl if you want that m4 avoid to throw away the spaces prefixed to the 984 | dnl argument value. 985 | dnl --------------------------------------------------------------------------- 986 | AC_DEFUN([WX_BOOLOPT_SUMMARY], 987 | [ 988 | if test "x$$1" = "x1" ; then 989 | echo $2 990 | elif test "x$$1" = "x0" ; then 991 | echo $3 992 | else 993 | echo "$1 is $$1" 994 | fi 995 | ]) 996 | 997 | dnl --------------------------------------------------------------------------- 998 | dnl WX_STANDARD_OPTIONS_SUMMARY_MSG 999 | dnl 1000 | dnl Shows a summary message to the user about the WX_* variable contents. 1001 | dnl This macro is used typically at the end of the configure script. 1002 | dnl --------------------------------------------------------------------------- 1003 | AC_DEFUN([WX_STANDARD_OPTIONS_SUMMARY_MSG], 1004 | [ 1005 | echo 1006 | echo " The wxWidgets build which will be used by $PACKAGE_NAME $PACKAGE_VERSION" 1007 | echo " has the following settings:" 1008 | WX_BOOLOPT_SUMMARY([WX_DEBUG], [" - DEBUG build"], [" - RELEASE build"]) 1009 | WX_BOOLOPT_SUMMARY([WX_UNICODE], [" - UNICODE mode"], [" - ANSI mode"]) 1010 | WX_BOOLOPT_SUMMARY([WX_SHARED], [" - SHARED mode"], [" - STATIC mode"]) 1011 | echo " - VERSION: $WX_VERSION" 1012 | echo " - PORT: $WX_PORT" 1013 | ]) 1014 | 1015 | 1016 | dnl --------------------------------------------------------------------------- 1017 | dnl WX_STANDARD_OPTIONS_SUMMARY_MSG_BEGIN, WX_STANDARD_OPTIONS_SUMMARY_MSG_END 1018 | dnl 1019 | dnl Like WX_STANDARD_OPTIONS_SUMMARY_MSG macro but these two macros also gives info 1020 | dnl about the configuration of the package which used the wxpresets. 1021 | dnl 1022 | dnl Typical usage: 1023 | dnl WX_STANDARD_OPTIONS_SUMMARY_MSG_BEGIN 1024 | dnl echo " - Package setting 1: $SETTING1" 1025 | dnl echo " - Package setting 2: $SETTING1" 1026 | dnl ... 1027 | dnl WX_STANDARD_OPTIONS_SUMMARY_MSG_END 1028 | dnl 1029 | dnl --------------------------------------------------------------------------- 1030 | AC_DEFUN([WX_STANDARD_OPTIONS_SUMMARY_MSG_BEGIN], 1031 | [ 1032 | echo 1033 | echo " ----------------------------------------------------------------" 1034 | echo " Configuration for $PACKAGE_NAME $PACKAGE_VERSION successfully completed." 1035 | echo " Summary of main configuration settings for $PACKAGE_NAME:" 1036 | WX_BOOLOPT_SUMMARY([DEBUG], [" - DEBUG build"], [" - RELEASE build"]) 1037 | WX_BOOLOPT_SUMMARY([UNICODE], [" - UNICODE mode"], [" - ANSI mode"]) 1038 | WX_BOOLOPT_SUMMARY([SHARED], [" - SHARED mode"], [" - STATIC mode"]) 1039 | ]) 1040 | 1041 | AC_DEFUN([WX_STANDARD_OPTIONS_SUMMARY_MSG_END], 1042 | [ 1043 | WX_STANDARD_OPTIONS_SUMMARY_MSG 1044 | echo 1045 | echo " Now, just run make." 1046 | echo " ----------------------------------------------------------------" 1047 | echo 1048 | ]) 1049 | 1050 | 1051 | dnl --------------------------------------------------------------------------- 1052 | dnl Deprecated macro wrappers 1053 | dnl --------------------------------------------------------------------------- 1054 | 1055 | AC_DEFUN([AM_OPTIONS_WXCONFIG], [WX_CONFIG_OPTIONS]) 1056 | AC_DEFUN([AM_PATH_WXCONFIG], [ 1057 | WX_CONFIG_CHECK([$1],[$2],[$3],[$4],[$5]) 1058 | ]) 1059 | AC_DEFUN([AM_PATH_WXRC], [WXRC_CHECK([$1],[$2])]) 1060 | -------------------------------------------------------------------------------- /bmpviewer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of diff-pdf. 3 | * 4 | * Copyright (C) 2009 TT-Solutions. 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "bmpviewer.h" 21 | #include "gutter.h" 22 | 23 | #include 24 | 25 | BEGIN_EVENT_TABLE(BitmapViewer, wxScrolledWindow) 26 | EVT_LEFT_DOWN(BitmapViewer::OnMouseDown) 27 | EVT_LEFT_UP(BitmapViewer::OnMouseUp) 28 | EVT_MOTION(BitmapViewer::OnMouseMove) 29 | EVT_MOUSE_CAPTURE_LOST(BitmapViewer::OnMouseCaptureLost) 30 | EVT_SCROLLWIN(BitmapViewer::OnScrolling) 31 | EVT_SIZE(BitmapViewer::OnSizeChanged) 32 | END_EVENT_TABLE() 33 | 34 | BitmapViewer::BitmapViewer(wxWindow *parent) 35 | : wxScrolledWindow(parent, 36 | wxID_ANY, 37 | wxDefaultPosition, wxDefaultSize, 38 | wxFULL_REPAINT_ON_RESIZE) 39 | { 40 | m_gutter = NULL; 41 | m_zoom_factor = 1.0; 42 | 43 | SetScrollRate(1, 1); 44 | 45 | wxBitmap dummyBitmap(16, 16); 46 | m_content = new wxStaticBitmap(this, wxID_ANY, dummyBitmap); 47 | 48 | wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); 49 | sizer->Add(m_content, wxSizerFlags(1).Expand()); 50 | SetSizer(sizer); 51 | 52 | // we need to bind mouse-down event to m_content, as this scrolled window 53 | // will never see mouse events otherwise 54 | m_content->Connect 55 | ( 56 | wxEVT_LEFT_DOWN, 57 | wxMouseEventHandler(BitmapViewer::OnMouseDown), 58 | NULL, 59 | this 60 | ); 61 | } 62 | 63 | 64 | void BitmapViewer::SetBestFitZoom() 65 | { 66 | // compute highest scale factor that still doesn't need scrollbars: 67 | 68 | float scale_x = float(GetSize().x) / float(m_orig_image.GetWidth()); 69 | float scale_y = float(GetSize().y) / float(m_orig_image.GetHeight()); 70 | 71 | SetZoom(std::min(scale_x, scale_y)); 72 | } 73 | 74 | 75 | void BitmapViewer::UpdateBitmap() 76 | { 77 | int new_w = int(m_orig_image.GetWidth() * m_zoom_factor); 78 | int new_h = int(m_orig_image.GetHeight() * m_zoom_factor); 79 | 80 | if ( new_w != m_orig_image.GetWidth() || 81 | new_h != m_orig_image.GetHeight() ) 82 | { 83 | wxImage scaled = 84 | m_orig_image.Scale 85 | ( 86 | new_w, 87 | new_h, 88 | // we don't need HQ filtering when upscaling 89 | m_zoom_factor < 1.0 90 | ? wxIMAGE_QUALITY_HIGH 91 | : wxIMAGE_QUALITY_NORMAL 92 | ); 93 | m_content->SetBitmap(wxBitmap(scaled)); 94 | } 95 | else 96 | { 97 | m_content->SetBitmap(wxBitmap(m_orig_image)); 98 | } 99 | 100 | GetSizer()->FitInside(this); 101 | 102 | if ( m_gutter ) 103 | m_gutter->UpdateViewPos(this); 104 | } 105 | 106 | 107 | void BitmapViewer::Set(const wxImage& image) 108 | { 109 | m_orig_image = image; 110 | UpdateBitmap(); 111 | } 112 | 113 | 114 | void BitmapViewer::Set(cairo_surface_t *surface) 115 | { 116 | // Cairo's RGB24 surfaces use 32 bits per pixel, while wxImage uses 117 | // 24 bits per pixel, so we need to convert between the two representations 118 | // manually. Moreover, channels order is different too, RGB vs. BGR. 119 | 120 | const int w = cairo_image_surface_get_width(surface); 121 | const int h = cairo_image_surface_get_height(surface); 122 | 123 | wxImage img(w, h, false); 124 | 125 | unsigned char *p_out = img.GetData(); 126 | const unsigned char *p_in = cairo_image_surface_get_data(surface); 127 | const int stride = cairo_image_surface_get_stride(surface); 128 | 129 | for ( int y = 0; y < h; y++, p_in += stride ) 130 | { 131 | for ( int x = 0; x < w; x++ ) 132 | { 133 | *(p_out++) = *(p_in + 4 * x + 2); 134 | *(p_out++) = *(p_in + 4 * x + 1); 135 | *(p_out++) = *(p_in + 4 * x + 0); 136 | } 137 | } 138 | 139 | Set(img); 140 | } 141 | 142 | 143 | void BitmapViewer::AttachGutter(Gutter *g) 144 | { 145 | m_gutter = g; 146 | if ( g ) 147 | g->UpdateViewPos(this); 148 | } 149 | 150 | 151 | void BitmapViewer::OnMouseDown(wxMouseEvent& event) 152 | { 153 | wxPoint view_origin; 154 | GetViewStart(&view_origin.x, &view_origin.y); 155 | 156 | const wxPoint pos = event.GetPosition(); 157 | 158 | m_draggingPage = true; 159 | m_draggingLastMousePos = pos; 160 | CaptureMouse(); 161 | } 162 | 163 | 164 | void BitmapViewer::OnMouseUp(wxMouseEvent&) 165 | { 166 | m_draggingPage = false; 167 | ReleaseMouse(); 168 | } 169 | 170 | 171 | void BitmapViewer::OnMouseMove(wxMouseEvent& event) 172 | { 173 | event.Skip(); 174 | 175 | if ( !m_draggingPage ) 176 | return; 177 | 178 | wxPoint view_origin; 179 | GetViewStart(&view_origin.x, &view_origin.y); 180 | 181 | const wxPoint pos = event.GetPosition(); 182 | wxPoint new_pos = view_origin + (m_draggingLastMousePos - pos); 183 | 184 | Scroll(new_pos.x, new_pos.y); 185 | if ( m_gutter ) 186 | m_gutter->UpdateViewPos(this); 187 | 188 | m_draggingLastMousePos = pos; 189 | } 190 | 191 | 192 | void BitmapViewer::OnMouseCaptureLost(wxMouseCaptureLostEvent& event) 193 | { 194 | m_draggingPage = false; 195 | event.Skip(); 196 | } 197 | 198 | 199 | void BitmapViewer::OnScrolling(wxScrollWinEvent& event) 200 | { 201 | if ( m_gutter ) 202 | m_gutter->UpdateViewPos(this); 203 | 204 | event.Skip(); 205 | } 206 | 207 | void BitmapViewer::OnSizeChanged(wxSizeEvent& event) 208 | { 209 | if ( m_gutter ) 210 | m_gutter->UpdateViewPos(this); 211 | 212 | event.Skip(); 213 | } 214 | -------------------------------------------------------------------------------- /bmpviewer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of diff-pdf. 3 | * 4 | * Copyright (C) 2009 TT-Solutions. 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef _bmpviewer_h_ 21 | #define _bmpviewer_h_ 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | class Gutter; 32 | 33 | // widget for comfortable viewing of a bitmap, with high-quality zooming 34 | class BitmapViewer : public wxScrolledWindow 35 | { 36 | public: 37 | BitmapViewer(wxWindow *parent); 38 | 39 | // set the bitmap to be shown 40 | void Set(const wxImage& image); 41 | void Set(cairo_surface_t *surface); 42 | 43 | float GetZoom() const 44 | { 45 | return m_zoom_factor; 46 | } 47 | 48 | void SetZoom(float factor) 49 | { 50 | m_zoom_factor = factor; 51 | UpdateBitmap(); 52 | } 53 | 54 | // sets the zoom value to "best fit" for current window size 55 | void SetBestFitZoom(); 56 | 57 | // attaches a gutter that shows current scrolling position to the window 58 | void AttachGutter(Gutter *g); 59 | 60 | private: 61 | // update the content after some change (bitmap, zoom factor, ...) 62 | void UpdateBitmap(); 63 | 64 | void OnMouseDown(wxMouseEvent& event); 65 | void OnMouseUp(wxMouseEvent& event); 66 | void OnMouseMove(wxMouseEvent& event); 67 | void OnMouseCaptureLost(wxMouseCaptureLostEvent& event); 68 | void OnScrolling(wxScrollWinEvent& event); 69 | void OnSizeChanged(wxSizeEvent& event); 70 | 71 | private: 72 | wxStaticBitmap *m_content; 73 | wxImage m_orig_image; 74 | float m_zoom_factor; 75 | 76 | // is the user currently dragging the page around with the mouse? 77 | bool m_draggingPage; 78 | wxPoint m_draggingLastMousePos; 79 | 80 | Gutter *m_gutter; 81 | 82 | DECLARE_EVENT_TABLE() 83 | }; 84 | 85 | #endif // _bmpviewer_h_ 86 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Script to execute to initialize diff-pdf build system after checking out 4 | # pristine sources from a version control system: this script creates all 5 | # generated files which are needed for the build but not stored under version 6 | # control. 7 | 8 | # Copyright (C) 2005, 2006, 2007, 2008 Vadim Zeitlin 9 | # All Rights Reserved 10 | # 11 | # Redistribution and use in source and binary forms, with or without 12 | # modification, are permitted provided that the following conditions 13 | # are met: 14 | # 15 | # 1. Redistributions of source code must retain the above copyright 16 | # notice, this list of conditions and the following disclaimer. 17 | # 2. Redistributions in binary form must reproduce the above copyright 18 | # notice, this list of conditions and the following disclaimer in 19 | # the documentation and/or other materials provided with the 20 | # distribution. 21 | # 3. Neither the name of the Author nor the names of its contributors 22 | # may be used to endorse or promote products derived from this software 23 | # without specific prior written permission. 24 | # 25 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' 26 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 28 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 29 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 32 | # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 33 | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 35 | # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 | # SUCH DAMAGE. 37 | 38 | # $Id$ 39 | 40 | if [ ! -f configure.ac -o ! -f Makefile.am ]; then 41 | echo "Please run this script from the diff-pdf source directory." 42 | exit 2 43 | fi 44 | 45 | # use --foreign with automake because we lack standard GNU NEWS and AUTHOR 46 | # files, if they're added we can "upgrade" to (default) GNU strictness. Use 47 | # --copy to allow simultaneous use on windows under mingw and cygwin platforms. 48 | # Symlinking of files under mingw does not work out for cygwin and vice-versa. 49 | echo "Setting up build system for diff-pdf:" 50 | echo " - aclocal " && aclocal ${wx+-I} $wx -I admin && \ 51 | echo " - autoconf " && autoconf && \ 52 | echo " - automake " && automake --add-missing --copy --foreign && \ 53 | echo "Build setup successful, type \"./configure\" to configure diff-pdf now." && \ 54 | exit 0 55 | 56 | echo "Automatic build files setup failed!" 57 | 58 | exit 1 59 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | 2 | dnl This file is part of diff-pdf. 3 | dnl 4 | dnl Copyright (C) 2009 TT-Solutions. 5 | dnl 6 | dnl This program is free software; you can redistribute it and/or modify 7 | dnl it under the terms of the GNU General Public License as published by 8 | dnl the Free Software Foundation; either version 2 of the License, or 9 | dnl (at your option) any later version. 10 | dnl 11 | dnl This program is distributed in the hope that it will be useful, 12 | dnl but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | dnl GNU General Public License for more details. 15 | dnl 16 | dnl You should have received a copy of the GNU General Public License 17 | dnl along with this program. If not, see . 18 | 19 | 20 | AC_PREREQ(2.61) 21 | AC_INIT(diff-pdf, 0.5.2, [vaclav@slavik.io]) 22 | 23 | AC_CONFIG_SRCDIR([diff-pdf.cpp]) 24 | AC_CONFIG_AUX_DIR([admin]) 25 | 26 | AM_INIT_AUTOMAKE 27 | AM_MAINTAINER_MODE 28 | 29 | m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) 30 | 31 | dnl remember, "build" is where we compile, "host" is where the resulting 32 | dnl program runs (which may be different from "build" for cross-compilation) 33 | AC_CANONICAL_HOST 34 | 35 | 36 | dnl === Program checks === 37 | 38 | AC_PROG_CXX 39 | AC_LANG(C++) 40 | 41 | dnl === Library checks === 42 | 43 | PKG_CHECK_MODULES(POPPLER, 44 | [glib-2.0 >= 2.36 poppler-glib >= 0.10 cairo-pdf]) 45 | 46 | AM_OPTIONS_WXCONFIG 47 | AM_PATH_WXCONFIG([3.0.0], [wxfound=1], [wxfound=0], [core,base]) 48 | if test "$wxfound" != 1 ; then 49 | AC_MSG_ERROR([wxWidgets is required]) 50 | fi 51 | 52 | dnl === Generate output files === 53 | 54 | AC_CONFIG_FILES([ 55 | Makefile 56 | ]) 57 | AC_OUTPUT 58 | -------------------------------------------------------------------------------- /diff-pdf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of diff-pdf. 3 | * 4 | * Copyright (C) 2009 TT-Solutions. 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "bmpviewer.h" 21 | #include "gutter.h" 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | // ------------------------------------------------------------------------ 46 | // PDF rendering functions 47 | // ------------------------------------------------------------------------ 48 | 49 | bool g_verbose = false; 50 | bool g_skip_identical = false; 51 | bool g_mark_differences = false; 52 | long g_channel_tolerance = 0; 53 | long g_per_page_pixel_tolerance = 0; 54 | bool g_grayscale = false; 55 | // Resolution to use for rasterization, in DPI 56 | #define DEFAULT_RESOLUTION 300 57 | long g_resolution = DEFAULT_RESOLUTION; 58 | 59 | inline unsigned char to_grayscale(unsigned char r, unsigned char g, unsigned char b) 60 | { 61 | return (unsigned char)(0.2126 * r + 0.7152 * g + 0.0722 * b); 62 | } 63 | 64 | cairo_surface_t *render_page(PopplerPage *page) 65 | { 66 | double w, h; 67 | poppler_page_get_size(page, &w, &h); 68 | 69 | const int w_px = int((int)g_resolution * w / 72.0); 70 | const int h_px = int((int)g_resolution * h / 72.0); 71 | 72 | cairo_surface_t *surface = 73 | cairo_image_surface_create(CAIRO_FORMAT_RGB24, w_px, h_px); 74 | 75 | cairo_t *cr = cairo_create(surface); 76 | 77 | // clear the surface to white background: 78 | cairo_save(cr); 79 | cairo_set_source_rgb(cr, 1, 1, 1); 80 | cairo_rectangle(cr, 0, 0, w_px, h_px); 81 | cairo_fill(cr); 82 | cairo_restore(cr); 83 | 84 | // Scale so that PDF output covers the whole surface. Image surface is 85 | // created with transformation set up so that 1 coordinate unit is 1 pixel; 86 | // Poppler assumes 1 unit = 1 point. 87 | cairo_scale(cr, (int)g_resolution / 72.0, (int)g_resolution / 72.0); 88 | 89 | poppler_page_render(page, cr); 90 | 91 | cairo_show_page(cr); 92 | 93 | cairo_destroy(cr); 94 | 95 | return surface; 96 | } 97 | 98 | 99 | // Creates image of differences between s1 and s2. If the offset is specified, 100 | // then s2 is displaced by it. If thumbnail and thumbnail_width are specified, 101 | // then a thumbnail with highlighted differences is created too. 102 | cairo_surface_t *diff_images(int page, cairo_surface_t *s1, cairo_surface_t *s2, 103 | int offset_x = 0, int offset_y = 0, 104 | wxImage *thumbnail = NULL, int thumbnail_width = -1) 105 | { 106 | assert( s1 || s2 ); 107 | 108 | long pixel_diff_count = 0; 109 | wxRect r1, r2; 110 | 111 | if ( s1 ) 112 | { 113 | r1 = wxRect(0, 0, 114 | cairo_image_surface_get_width(s1), 115 | cairo_image_surface_get_height(s1)); 116 | } 117 | if ( s2 ) 118 | { 119 | r2 = wxRect(offset_x, offset_y, 120 | cairo_image_surface_get_width(s2), 121 | cairo_image_surface_get_height(s2)); 122 | } 123 | 124 | // compute union rectangle starting at [0,0] position 125 | wxRect rdiff(r1); 126 | rdiff.Union(r2); 127 | r1.Offset(-rdiff.x, -rdiff.y); 128 | r2.Offset(-rdiff.x, -rdiff.y); 129 | rdiff.Offset(-rdiff.x, -rdiff.y); 130 | 131 | bool changes = false; 132 | 133 | cairo_surface_t *diff = 134 | cairo_image_surface_create(CAIRO_FORMAT_RGB24, rdiff.width, rdiff.height); 135 | 136 | float thumbnail_scale; 137 | int thumbnail_height; 138 | 139 | if ( thumbnail ) 140 | { 141 | thumbnail_scale = float(thumbnail_width) / float(rdiff.width); 142 | thumbnail_height = int(rdiff.height * thumbnail_scale); 143 | thumbnail->Create(thumbnail_width, thumbnail_height); 144 | // initalize the thumbnail with a white rectangle: 145 | thumbnail->SetRGB(wxRect(), 255, 255, 255); 146 | } 147 | 148 | // clear the surface to white background if the merged images don't fully 149 | // overlap: 150 | if ( r1 != r2 ) 151 | { 152 | changes = true; 153 | 154 | cairo_t *cr = cairo_create(diff); 155 | cairo_set_source_rgb(cr, 1, 1, 1); 156 | cairo_rectangle(cr, 0, 0, rdiff.width, rdiff.height); 157 | cairo_fill(cr); 158 | cairo_destroy(cr); 159 | } 160 | 161 | const int stride1 = s1 ? cairo_image_surface_get_stride(s1) : 0; 162 | const int stride2 = s2 ? cairo_image_surface_get_stride(s2) : 0; 163 | const int stridediff = cairo_image_surface_get_stride(diff); 164 | 165 | const unsigned char *data1 = s1 ? cairo_image_surface_get_data(s1) : NULL; 166 | const unsigned char *data2 = s2 ? cairo_image_surface_get_data(s2) : NULL; 167 | unsigned char *datadiff = cairo_image_surface_get_data(diff); 168 | 169 | // we visualize the differences by taking one channel from s1 170 | // and the other two channels from s2: 171 | 172 | // first, copy s1 over: 173 | if ( s1 ) 174 | { 175 | unsigned char *out = datadiff + r1.y * stridediff + r1.x * 4; 176 | for ( int y = 0; 177 | y < r1.height; 178 | y++, data1 += stride1, out += stridediff ) 179 | { 180 | memcpy(out, data1, r1.width * 4); 181 | } 182 | } 183 | 184 | // then, copy B channel from s2 over it; also compare the two versions 185 | // to see if there are any differences: 186 | if ( s2 ) 187 | { 188 | unsigned char *out = datadiff + r2.y * stridediff + r2.x * 4; 189 | for ( int y = 0; 190 | y < r2.height; 191 | y++, data2 += stride2, out += stridediff ) 192 | { 193 | bool linediff = false; 194 | 195 | for ( int x = 0; x < r2.width * 4; x += 4 ) 196 | { 197 | unsigned char cr1 = *(out + x + 0); 198 | unsigned char cg1 = *(out + x + 1); 199 | unsigned char cb1 = *(out + x + 2); 200 | 201 | unsigned char cr2 = *(data2 + x + 0); 202 | unsigned char cg2 = *(data2 + x + 1); 203 | unsigned char cb2 = *(data2 + x + 2); 204 | 205 | if ( cr1 > (cr2+g_channel_tolerance) || cr1 < (cr2-g_channel_tolerance) 206 | || cg1 > (cg2+g_channel_tolerance) || cg1 < (cg2-g_channel_tolerance) 207 | || cb1 > (cb2+g_channel_tolerance) || cb1 < (cb2-g_channel_tolerance) 208 | ) 209 | { 210 | pixel_diff_count++; 211 | changes = true; 212 | linediff = true; 213 | 214 | if ( thumbnail ) 215 | { 216 | // calculate the coordinates in the thumbnail 217 | int tx = int((r2.x + x/4.0) * thumbnail_scale); 218 | int ty = int((r2.y + y) * thumbnail_scale); 219 | 220 | // Limit the coordinates to the thumbnail size (may be 221 | // off slightly due to rounding errors). 222 | // See https://github.com/vslavik/diff-pdf/pull/58 223 | tx = std::min(tx, thumbnail_width - 1); 224 | ty = std::min(ty, thumbnail_height - 1); 225 | 226 | // mark changes with red 227 | thumbnail->SetRGB(tx, ty, 255, 0, 0); 228 | } 229 | } 230 | 231 | if (g_grayscale) 232 | { 233 | // convert both images to grayscale, use blue for s1, red for s2 234 | unsigned char gray1 = to_grayscale(cr1, cg1, cb1); 235 | unsigned char gray2 = to_grayscale(cr2, cg2, cb2); 236 | *(out + x + 0) = gray2; 237 | *(out + x + 1) = (gray1 + gray2) / 2; 238 | *(out + x + 2) = gray1; 239 | } 240 | else 241 | { 242 | // change the B channel to be from s2; RG will be s1 243 | *(out + x + 2) = cb2; 244 | } 245 | } 246 | 247 | if (g_mark_differences && linediff) 248 | { 249 | for (int x = 0; x < (10 < r2.width ? 10 : r2.width) * 4; x+=4) 250 | { 251 | *(out + x + 0) = 0; 252 | *(out + x + 1) = 0; 253 | *(out + x + 2) = 255; 254 | } 255 | } 256 | } 257 | } 258 | 259 | // add background image of the page to the thumbnails 260 | if ( thumbnail ) 261 | { 262 | // copy the 'diff' surface into wxImage: 263 | wxImage bg(rdiff.width, rdiff.height); 264 | unsigned char *in = datadiff; 265 | unsigned char *out = bg.GetData(); 266 | for ( int y = 0; y < rdiff.height; y++, in += stridediff ) 267 | { 268 | for ( int x = 0; x < rdiff.width * 4; x += 4 ) 269 | { 270 | // cairo_surface_t uses BGR order, wxImage has RGB 271 | *(out++) = *(in + x + 2); 272 | *(out++) = *(in + x + 1); 273 | *(out++) = *(in + x + 0); 274 | } 275 | } 276 | 277 | // scale it to thumbnail size: 278 | bg.Rescale(thumbnail_width, thumbnail_height, wxIMAGE_QUALITY_HIGH); 279 | 280 | // and merge with the diff markers in *thumbnail, making it much 281 | // lighter in the process: 282 | in = bg.GetData(); 283 | out = thumbnail->GetData(); 284 | for ( int i = thumbnail_width * thumbnail_height; i > 0; i-- ) 285 | { 286 | if ( out[1] == 0 ) // G=0 ==> not white 287 | { 288 | // marked with red color, as place with differences -- don't 289 | // paint background image here, make the red as visible as 290 | // possible 291 | out += 3; 292 | in += 3; 293 | } 294 | else 295 | { 296 | // merge in lighter background image 297 | *(out++) = 128 + *(in++) / 2; 298 | *(out++) = 128 + *(in++) / 2; 299 | *(out++) = 128 + *(in++) / 2; 300 | } 301 | } 302 | 303 | // If there were no changes, indicate it by using green 304 | // (170,230,130) color for the thumbnail in gutter control: 305 | if ( !changes ) 306 | { 307 | out = thumbnail->GetData(); 308 | for ( int i = thumbnail_width * thumbnail_height; 309 | i > 0; 310 | i--, out += 3 ) 311 | { 312 | out[0] = 170/2 + out[0] / 2; 313 | out[1] = 230/2 + out[1] / 2; 314 | out[2] = 130/2 + out[2] / 2; 315 | } 316 | } 317 | } 318 | 319 | if ( g_verbose ) 320 | printf("page %d has %ld pixels that differ\n", page, pixel_diff_count); 321 | 322 | // If we specified a tolerance, then return if we have exceeded that for this page 323 | if ( g_per_page_pixel_tolerance == 0 ? changes : pixel_diff_count > g_per_page_pixel_tolerance) 324 | { 325 | return diff; 326 | } 327 | else 328 | { 329 | cairo_surface_destroy(diff); 330 | return NULL; 331 | } 332 | } 333 | 334 | 335 | // Compares given two pages. If cr_out is not NULL, then the diff image (either 336 | // differences or unmodified page, if there are no diffs) is drawn to it. 337 | // If thumbnail and thumbnail_width are specified, then a thumbnail with 338 | // highlighted differences is created too. 339 | bool page_compare(int page, cairo_t *cr_out, 340 | PopplerPage *page1, PopplerPage *page2, 341 | wxImage *thumbnail = NULL, int thumbnail_width = -1) 342 | { 343 | cairo_surface_t *img1 = page1 ? render_page(page1) : NULL; 344 | cairo_surface_t *img2 = page2 ? render_page(page2) : NULL; 345 | 346 | cairo_surface_t *diff = diff_images(page, img1, img2, 0, 0, 347 | thumbnail, thumbnail_width); 348 | const bool has_diff = (diff != NULL); 349 | 350 | if ( cr_out ) 351 | { 352 | if ( diff ) 353 | { 354 | // render the difference as high-resolution bitmap 355 | 356 | cairo_save(cr_out); 357 | cairo_scale(cr_out, 72.0 / g_resolution, 72.0 / g_resolution); 358 | 359 | cairo_set_source_surface(cr_out, diff ? diff : img1, 0, 0); 360 | cairo_paint(cr_out); 361 | 362 | cairo_restore(cr_out); 363 | } 364 | else 365 | { 366 | // save space (as well as improve rendering quality) in diff pdf 367 | // by writing unchanged pages in their original form rather than 368 | // a rasterized one 369 | 370 | if (!g_skip_identical) 371 | poppler_page_render(page1, cr_out); 372 | } 373 | 374 | if (diff || !g_skip_identical) 375 | cairo_show_page(cr_out); 376 | } 377 | 378 | if ( diff ) 379 | cairo_surface_destroy(diff); 380 | 381 | if ( img1 ) 382 | cairo_surface_destroy(img1); 383 | if ( img2 ) 384 | cairo_surface_destroy(img2); 385 | 386 | return !has_diff; 387 | } 388 | 389 | 390 | // Compares two documents, writing diff PDF into file named 'pdf_output' if 391 | // not NULL. if 'differences' is not NULL, puts a map of which pages differ 392 | // into it. If 'progress' is provided, it is updated to reflect comparison's 393 | // progress. If 'gutter' is set, then all the pages are added to it, with 394 | // their respective thumbnails (the gutter must be empty beforehand). 395 | bool doc_compare(PopplerDocument *doc1, PopplerDocument *doc2, 396 | const char *pdf_output, 397 | std::vector *differences, 398 | wxProgressDialog *progress = NULL, 399 | Gutter *gutter = NULL) 400 | { 401 | int pages_differ = 0; 402 | 403 | cairo_surface_t *surface_out = NULL; 404 | cairo_t *cr_out = NULL; 405 | 406 | if ( pdf_output ) 407 | { 408 | double w, h; 409 | poppler_page_get_size(poppler_document_get_page(doc1, 0), &w, &h); 410 | surface_out = cairo_pdf_surface_create(pdf_output, w, h); 411 | cr_out = cairo_create(surface_out); 412 | } 413 | 414 | int pages1 = poppler_document_get_n_pages(doc1); 415 | int pages2 = poppler_document_get_n_pages(doc2); 416 | int pages_total = pages1 > pages2 ? pages1 : pages2; 417 | 418 | if ( pages1 != pages2 ) 419 | { 420 | if ( g_verbose ) 421 | printf("pages count differs: %d vs %d\n", pages1, pages2); 422 | } 423 | 424 | for ( int page = 0; page < pages_total; page++ ) 425 | { 426 | if ( progress ) 427 | { 428 | progress->Update 429 | ( 430 | page, 431 | wxString::Format 432 | ( 433 | "Comparing page %d of %d...", 434 | page+1, 435 | pages_total 436 | ) 437 | ); 438 | } 439 | 440 | if ( pdf_output && page != 0 ) 441 | { 442 | double w, h; 443 | poppler_page_get_size(poppler_document_get_page(doc1, page), &w, &h); 444 | cairo_pdf_surface_set_size(surface_out, w, h); 445 | } 446 | 447 | PopplerPage *page1 = page < pages1 448 | ? poppler_document_get_page(doc1, page) 449 | : NULL; 450 | PopplerPage *page2 = page < pages2 451 | ? poppler_document_get_page(doc2, page) 452 | : NULL; 453 | 454 | bool page_same; 455 | 456 | if ( gutter ) 457 | { 458 | wxImage thumbnail; 459 | page_same = page_compare(page, cr_out, page1, page2, 460 | &thumbnail, Gutter::WIDTH); 461 | 462 | wxString label1("(null)"); 463 | wxString label2("(null)"); 464 | 465 | if ( page1 ) 466 | { 467 | gchar *label; 468 | g_object_get(page1, "label", &label, NULL); 469 | label1 = wxString::FromUTF8(label); 470 | g_free(label); 471 | } 472 | if ( page2 ) 473 | { 474 | gchar *label; 475 | g_object_get(page2, "label", &label, NULL); 476 | label2 = wxString::FromUTF8(label); 477 | g_free(label); 478 | } 479 | 480 | 481 | wxString label; 482 | if ( label1 == label2 ) 483 | label = label1; 484 | else 485 | label = label1 + " / " + label2; 486 | 487 | gutter->AddPage(label, thumbnail); 488 | } 489 | else 490 | { 491 | page_same = page_compare(page, cr_out, page1, page2); 492 | } 493 | 494 | if ( differences ) 495 | differences->push_back(!page_same); 496 | 497 | if ( !page_same ) 498 | { 499 | pages_differ ++; 500 | 501 | if ( g_verbose ) 502 | printf("page %d differs\n", page); 503 | 504 | // If we don't need to output all different pages in any 505 | // form (including verbose report of differing pages!), then 506 | // we can stop comparing the PDFs as soon as we find the first 507 | // difference. 508 | if ( !g_verbose && !pdf_output && !differences && !gutter ) 509 | break; 510 | } 511 | } 512 | 513 | if ( pdf_output ) 514 | { 515 | cairo_destroy(cr_out); 516 | cairo_surface_destroy(surface_out); 517 | } 518 | 519 | if (g_verbose) 520 | printf("%d of %d pages differ.\n", pages_differ, pages_total); 521 | 522 | // are doc1 and doc1 the same? 523 | return (pages_differ == 0) && (pages1 == pages2); 524 | } 525 | 526 | 527 | // ------------------------------------------------------------------------ 528 | // wxWidgets GUI 529 | // ------------------------------------------------------------------------ 530 | 531 | const int ID_PREV_PAGE = wxNewId(); 532 | const int ID_NEXT_PAGE = wxNewId(); 533 | const int ID_ZOOM_IN = wxNewId(); 534 | const int ID_ZOOM_OUT = wxNewId(); 535 | const int ID_OFFSET_LEFT = wxNewId(); 536 | const int ID_OFFSET_RIGHT = wxNewId(); 537 | const int ID_OFFSET_UP = wxNewId(); 538 | const int ID_OFFSET_DOWN = wxNewId(); 539 | const int ID_GUTTER = wxNewId(); 540 | 541 | #define BMP_ARTPROV(id) wxArtProvider::GetBitmap(id, wxART_TOOLBAR) 542 | 543 | #define BMP_PREV_PAGE BMP_ARTPROV(wxART_GO_BACK) 544 | #define BMP_NEXT_PAGE BMP_ARTPROV(wxART_GO_FORWARD) 545 | 546 | #define BMP_OFFSET_LEFT BMP_ARTPROV(wxART_GO_BACK) 547 | #define BMP_OFFSET_RIGHT BMP_ARTPROV(wxART_GO_FORWARD) 548 | #define BMP_OFFSET_UP BMP_ARTPROV(wxART_GO_UP) 549 | #define BMP_OFFSET_DOWN BMP_ARTPROV(wxART_GO_DOWN) 550 | 551 | #ifdef __WXGTK__ 552 | #define BMP_ZOOM_IN BMP_ARTPROV("gtk-zoom-in") 553 | #define BMP_ZOOM_OUT BMP_ARTPROV("gtk-zoom-out") 554 | #else 555 | #include "gtk-zoom-in.xpm" 556 | #include "gtk-zoom-out.xpm" 557 | #define BMP_ZOOM_IN wxBitmap(gtk_zoom_in_xpm) 558 | #define BMP_ZOOM_OUT wxBitmap(gtk_zoom_out_xpm) 559 | #endif 560 | 561 | static const float ZOOM_FACTOR_STEP = 1.2f; 562 | 563 | class DiffFrame : public wxFrame 564 | { 565 | public: 566 | DiffFrame(const wxString& title) 567 | : wxFrame(NULL, wxID_ANY, title) 568 | { 569 | m_cur_page = -1; 570 | 571 | CreateStatusBar(2); 572 | SetStatusBarPane(0); 573 | const int stat_widths[] = { -1, 150 }; 574 | SetStatusWidths(2, stat_widths); 575 | 576 | wxToolBar *toolbar = 577 | new wxToolBar 578 | ( 579 | this, wxID_ANY, 580 | wxDefaultPosition, wxDefaultSize, 581 | wxTB_HORIZONTAL | wxTB_FLAT | wxTB_HORZ_TEXT 582 | ); 583 | 584 | toolbar->AddTool(ID_PREV_PAGE, "Previous", BMP_PREV_PAGE, 585 | "Go to previous page (PgUp)"); 586 | toolbar->AddTool(ID_NEXT_PAGE, "Next", BMP_NEXT_PAGE, 587 | "Go to next page (PgDown)"); 588 | toolbar->AddTool(ID_ZOOM_IN, "Zoom in", BMP_ZOOM_IN, 589 | "Make the page larger (Ctrl +)"); 590 | toolbar->AddTool(ID_ZOOM_OUT, "Zoom out", BMP_ZOOM_OUT, 591 | "Make the page smaller (Ctrl -)"); 592 | toolbar->AddTool(ID_OFFSET_LEFT, "", BMP_OFFSET_LEFT, 593 | "Offset one of the pages to the left (Ctrl left)"); 594 | toolbar->AddTool(ID_OFFSET_RIGHT, "", BMP_OFFSET_RIGHT, 595 | "Offset one of the pages to the right (Ctrl right)"); 596 | toolbar->AddTool(ID_OFFSET_UP, "", BMP_OFFSET_UP, 597 | "Offset one of the pages up (Ctrl up)"); 598 | toolbar->AddTool(ID_OFFSET_DOWN, "", BMP_OFFSET_DOWN, 599 | "Offset one of the pages down (Ctrl down)"); 600 | 601 | toolbar->Realize(); 602 | SetToolBar(toolbar); 603 | 604 | wxAcceleratorEntry accels[8]; 605 | accels[0].Set(wxACCEL_NORMAL, WXK_PAGEUP, ID_PREV_PAGE); 606 | accels[1].Set(wxACCEL_NORMAL, WXK_PAGEDOWN, ID_NEXT_PAGE); 607 | accels[2].Set(wxACCEL_CTRL, (int)'=', ID_ZOOM_IN); 608 | accels[3].Set(wxACCEL_CTRL, (int)'-', ID_ZOOM_OUT); 609 | accels[4].Set(wxACCEL_CTRL, WXK_LEFT, ID_OFFSET_LEFT); 610 | accels[5].Set(wxACCEL_CTRL, WXK_RIGHT, ID_OFFSET_RIGHT); 611 | accels[6].Set(wxACCEL_CTRL, WXK_UP, ID_OFFSET_UP); 612 | accels[7].Set(wxACCEL_CTRL, WXK_DOWN, ID_OFFSET_DOWN); 613 | 614 | wxAcceleratorTable accel_table(8, accels); 615 | SetAcceleratorTable(accel_table); 616 | 617 | m_gutter = new Gutter(this, ID_GUTTER); 618 | 619 | m_viewer = new BitmapViewer(this); 620 | m_viewer->AttachGutter(m_gutter); 621 | m_viewer->SetFocus(); 622 | 623 | wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); 624 | sizer->Add(m_gutter, wxSizerFlags(0).Expand().Border(wxALL, 2)); 625 | sizer->Add(m_viewer, wxSizerFlags(1).Expand()); 626 | SetSizer(sizer); 627 | } 628 | 629 | void SetDocs(PopplerDocument *doc1, PopplerDocument *doc2) 630 | { 631 | m_doc1 = doc1; 632 | m_doc2 = doc2; 633 | 634 | wxProgressDialog progress("Comparing documents", 635 | "Comparing documents...", 636 | wxMax(poppler_document_get_n_pages(m_doc1), 637 | poppler_document_get_n_pages(m_doc2)), 638 | this, 639 | wxPD_SMOOTH | wxPD_REMAINING_TIME); 640 | 641 | 642 | doc_compare(m_doc1, m_doc2, NULL, &m_pages, &progress, m_gutter); 643 | 644 | progress.Pulse(); 645 | 646 | m_diff_count = 0; 647 | for ( std::vector::const_iterator i = m_pages.begin(); 648 | i != m_pages.end(); 649 | ++i ) 650 | { 651 | if ( *i ) 652 | m_diff_count++; 653 | } 654 | 655 | GoToPage(0); 656 | 657 | progress.Pulse(); 658 | 659 | m_viewer->SetBestFitZoom(); 660 | UpdateStatus(); 661 | 662 | progress.Hide(); 663 | } 664 | 665 | void GoToPage(int n) 666 | { 667 | m_cur_page = n; 668 | m_gutter->SetSelection(n); 669 | DoUpdatePage(); 670 | } 671 | 672 | private: 673 | void DoUpdatePage() 674 | { 675 | wxBusyCursor wait; 676 | 677 | const int pages1 = poppler_document_get_n_pages(m_doc1); 678 | const int pages2 = poppler_document_get_n_pages(m_doc2); 679 | 680 | PopplerPage *page1 = m_cur_page < pages1 681 | ? poppler_document_get_page(m_doc1, m_cur_page) 682 | : NULL; 683 | PopplerPage *page2 = m_cur_page < pages2 684 | ? poppler_document_get_page(m_doc2, m_cur_page) 685 | : NULL; 686 | 687 | cairo_surface_t *img1 = page1 ? render_page(page1) : NULL; 688 | cairo_surface_t *img2 = page2 ? render_page(page2) : NULL; 689 | 690 | wxImage thumbnail; 691 | cairo_surface_t *diff = diff_images 692 | ( 693 | m_cur_page, 694 | img1, img2, 695 | m_offset.x, m_offset.y, 696 | &thumbnail, Gutter::WIDTH 697 | ); 698 | 699 | m_viewer->Set(diff ? diff : img1); 700 | 701 | // Always update the diff map. It will be all-white if there were 702 | // no differences. 703 | m_gutter->SetThumbnail(m_cur_page, thumbnail); 704 | 705 | if ( img1 ) 706 | cairo_surface_destroy(img1); 707 | if ( img2 ) 708 | cairo_surface_destroy(img2); 709 | if ( diff ) 710 | cairo_surface_destroy(diff); 711 | 712 | UpdateStatus(); 713 | } 714 | 715 | void UpdateStatus() 716 | { 717 | SetStatusText 718 | ( 719 | wxString::Format 720 | ( 721 | "Page %d of %d; %d of them %s different, this page %s", 722 | m_cur_page + 1 /* humans prefer 1-based counting*/, 723 | (int)m_pages.size(), 724 | m_diff_count, 725 | m_diff_count == 1 ? "is" : "are", 726 | m_pages[m_cur_page] ? "differs" : "is unchanged" 727 | ), 728 | 0 729 | ); 730 | 731 | SetStatusText 732 | ( 733 | wxString::Format 734 | ( 735 | "%.1f%% [offset %d,%d]", 736 | m_viewer->GetZoom() * 100.0, 737 | m_offset.x, m_offset.y 738 | ), 739 | 1 740 | ); 741 | } 742 | 743 | void OnSetPage(wxCommandEvent& event) 744 | { 745 | GoToPage(event.GetSelection()); 746 | } 747 | 748 | void OnPrevPage(wxCommandEvent&) 749 | { 750 | if ( m_cur_page > 0 ) 751 | GoToPage(m_cur_page - 1); 752 | } 753 | 754 | void OnNextPage(wxCommandEvent&) 755 | { 756 | if ( m_cur_page < m_pages.size() - 1 ) 757 | GoToPage(m_cur_page + 1); 758 | } 759 | 760 | void OnUpdatePrevPage(wxUpdateUIEvent& event) 761 | { 762 | event.Enable(m_cur_page > 0); 763 | } 764 | 765 | void OnUpdateNextPage(wxUpdateUIEvent& event) 766 | { 767 | event.Enable(m_cur_page < m_pages.size() - 1); 768 | } 769 | 770 | void OnZoomIn(wxCommandEvent&) 771 | { 772 | wxBusyCursor wait; 773 | m_viewer->SetZoom(m_viewer->GetZoom() * ZOOM_FACTOR_STEP); 774 | UpdateStatus(); 775 | } 776 | 777 | void OnZoomOut(wxCommandEvent&) 778 | { 779 | wxBusyCursor wait; 780 | m_viewer->SetZoom(m_viewer->GetZoom() / ZOOM_FACTOR_STEP); 781 | UpdateStatus(); 782 | } 783 | 784 | void DoOffset(int x, int y) 785 | { 786 | m_offset.x += x; 787 | m_offset.y += y; 788 | DoUpdatePage(); 789 | } 790 | 791 | void OnOffsetLeft(wxCommandEvent&) { DoOffset(-1, 0); } 792 | void OnOffsetRight(wxCommandEvent&) { DoOffset(1, 0); } 793 | void OnOffsetUp(wxCommandEvent&) { DoOffset(0, -1); } 794 | void OnOffsetDown(wxCommandEvent&) { DoOffset(0, 1); } 795 | 796 | DECLARE_EVENT_TABLE() 797 | 798 | private: 799 | BitmapViewer *m_viewer; 800 | Gutter *m_gutter; 801 | PopplerDocument *m_doc1, *m_doc2; 802 | std::vector m_pages; 803 | int m_diff_count; 804 | int m_cur_page; 805 | wxPoint m_offset; 806 | }; 807 | 808 | BEGIN_EVENT_TABLE(DiffFrame, wxFrame) 809 | EVT_LISTBOX (ID_GUTTER, DiffFrame::OnSetPage) 810 | EVT_TOOL (ID_PREV_PAGE, DiffFrame::OnPrevPage) 811 | EVT_TOOL (ID_NEXT_PAGE, DiffFrame::OnNextPage) 812 | EVT_UPDATE_UI(ID_PREV_PAGE, DiffFrame::OnUpdatePrevPage) 813 | EVT_UPDATE_UI(ID_NEXT_PAGE, DiffFrame::OnUpdateNextPage) 814 | EVT_TOOL (ID_ZOOM_IN, DiffFrame::OnZoomIn) 815 | EVT_TOOL (ID_ZOOM_OUT, DiffFrame::OnZoomOut) 816 | EVT_TOOL (ID_OFFSET_LEFT, DiffFrame::OnOffsetLeft) 817 | EVT_TOOL (ID_OFFSET_RIGHT, DiffFrame::OnOffsetRight) 818 | EVT_TOOL (ID_OFFSET_UP, DiffFrame::OnOffsetUp) 819 | EVT_TOOL (ID_OFFSET_DOWN, DiffFrame::OnOffsetDown) 820 | END_EVENT_TABLE() 821 | 822 | 823 | class DiffPdfApp : public wxApp 824 | { 825 | public: 826 | DiffPdfApp() : m_tlw(NULL) {} 827 | 828 | virtual bool OnInit() 829 | { 830 | m_tlw = new DiffFrame(m_title); 831 | 832 | // like in LMI, maximize the window 833 | m_tlw->Maximize(); 834 | m_tlw->Show(); 835 | 836 | // yield so that size changes above take effect immediately (and so we 837 | // can query the window for its size) 838 | Yield(); 839 | 840 | return true; 841 | } 842 | 843 | void SetData(const wxString& file1, PopplerDocument *doc1, 844 | const wxString& file2, PopplerDocument *doc2) 845 | { 846 | m_title = wxString::Format("Differences between %s and %s", file1.c_str(), file2.c_str()); 847 | m_doc1 = doc1; 848 | m_doc2 = doc2; 849 | } 850 | 851 | protected: 852 | virtual void OnEventLoopEnter(wxEventLoopBase *loop) 853 | { 854 | wxApp::OnEventLoopEnter(loop); 855 | 856 | if ( loop->IsMain() ) 857 | SetFrameDocs(); 858 | } 859 | 860 | void SetFrameDocs() 861 | { 862 | wxASSERT( m_tlw ); 863 | wxASSERT( m_doc1 ); 864 | wxASSERT( m_doc2 ); 865 | 866 | m_tlw->SetDocs(m_doc1, m_doc2); 867 | } 868 | 869 | private: 870 | DiffFrame *m_tlw; 871 | wxString m_title; 872 | PopplerDocument *m_doc1, *m_doc2; 873 | }; 874 | 875 | IMPLEMENT_APP_NO_MAIN(DiffPdfApp); 876 | 877 | 878 | // ------------------------------------------------------------------------ 879 | // main() 880 | // ------------------------------------------------------------------------ 881 | 882 | int main(int argc, char *argv[]) 883 | { 884 | wxAppConsole::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE, "diff-pdf"); 885 | wxInitializer wxinitializer(argc, argv); 886 | 887 | static const wxCmdLineEntryDesc cmd_line_desc[] = 888 | { 889 | { wxCMD_LINE_SWITCH, 890 | "h", "help", "show this help message", 891 | wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP }, 892 | 893 | { wxCMD_LINE_SWITCH, 894 | "v", "verbose", "be verbose" }, 895 | 896 | { wxCMD_LINE_SWITCH, 897 | "s", "skip-identical", "only output pages with differences" }, 898 | 899 | { wxCMD_LINE_SWITCH, 900 | "m", "mark-differences", "additionally mark differences on left side" }, 901 | 902 | { wxCMD_LINE_SWITCH, 903 | "g", "grayscale", "only differences will be in color, unchanged parts will show as gray" }, 904 | 905 | { wxCMD_LINE_OPTION, 906 | NULL, "output-diff", "output differences to given PDF file", 907 | wxCMD_LINE_VAL_STRING }, 908 | 909 | { wxCMD_LINE_OPTION, 910 | NULL, "channel-tolerance", "consider channel values to be equal if within specified tolerance", 911 | wxCMD_LINE_VAL_NUMBER }, 912 | 913 | { wxCMD_LINE_OPTION, 914 | NULL, "per-page-pixel-tolerance", "total number of pixels allowed to be different per page before specifying the page is different", 915 | wxCMD_LINE_VAL_NUMBER }, 916 | 917 | { wxCMD_LINE_OPTION, 918 | NULL, "dpi", "rasterization resolution (default: " wxSTRINGIZE(DEFAULT_RESOLUTION) " dpi)", 919 | wxCMD_LINE_VAL_NUMBER }, 920 | 921 | { wxCMD_LINE_SWITCH, 922 | NULL, "view", "view the differences in a window" }, 923 | 924 | { wxCMD_LINE_PARAM, 925 | NULL, NULL, "file1.pdf", wxCMD_LINE_VAL_STRING }, 926 | { wxCMD_LINE_PARAM, 927 | NULL, NULL, "file2.pdf", wxCMD_LINE_VAL_STRING }, 928 | 929 | { wxCMD_LINE_NONE } 930 | }; 931 | 932 | wxCmdLineParser parser(cmd_line_desc, argc, argv); 933 | 934 | switch ( parser.Parse() ) 935 | { 936 | case -1: // --help 937 | return 0; 938 | 939 | case 0: // everything is ok; proceed 940 | break; 941 | 942 | default: // syntax error 943 | return 2; 944 | } 945 | 946 | if ( parser.Found("verbose") ) 947 | g_verbose = true; 948 | 949 | if ( parser.Found("skip-identical") ) 950 | g_skip_identical = true; 951 | 952 | if ( parser.Found("mark-differences") ) 953 | g_mark_differences = true; 954 | 955 | if ( parser.Found("grayscale") ) 956 | g_grayscale = true; 957 | 958 | wxFileName file1(parser.GetParam(0)); 959 | wxFileName file2(parser.GetParam(1)); 960 | file1.MakeAbsolute(); 961 | file2.MakeAbsolute(); 962 | const wxString url1 = wxFileSystem::FileNameToURL(file1); 963 | const wxString url2 = wxFileSystem::FileNameToURL(file2); 964 | 965 | GError *err = NULL; 966 | 967 | PopplerDocument *doc1 = poppler_document_new_from_file(url1.utf8_str(), NULL, &err); 968 | if ( !doc1 ) 969 | { 970 | fprintf(stderr, "Error opening %s: %s\n", (const char*) parser.GetParam(0).c_str(), err->message); 971 | g_error_free(err); 972 | return 3; 973 | } 974 | 975 | PopplerDocument *doc2 = poppler_document_new_from_file(url2.utf8_str(), NULL, &err); 976 | if ( !doc2 ) 977 | { 978 | fprintf(stderr, "Error opening %s: %s\n", (const char*) parser.GetParam(1).c_str(), err->message); 979 | g_error_free(err); 980 | return 3; 981 | } 982 | 983 | if ( parser.Found("per-page-pixel-tolerance", &g_per_page_pixel_tolerance) ) 984 | { 985 | if (g_per_page_pixel_tolerance < 0) { 986 | fprintf(stderr, "Invalid per-page-pixel-tolerance: %ld. Must be 0 or more\n", g_per_page_pixel_tolerance); 987 | return 2; 988 | } 989 | } 990 | 991 | if ( parser.Found("channel-tolerance", &g_channel_tolerance) ) 992 | { 993 | if (g_channel_tolerance < 0 || g_channel_tolerance > 255) { 994 | fprintf(stderr, "Invalid channel-tolerance: %ld. Valid range is 0(default, exact matching)-255\n", g_channel_tolerance); 995 | return 2; 996 | } 997 | } 998 | 999 | if ( parser.Found("dpi", &g_resolution) ) 1000 | { 1001 | if (g_resolution < 1 || g_resolution > 2400) { 1002 | fprintf(stderr, "Invalid dpi: %ld. Valid range is 1-2400 (default: %d)\n", g_resolution, DEFAULT_RESOLUTION); 1003 | return 2; 1004 | } 1005 | } 1006 | 1007 | 1008 | int retval = 0; 1009 | 1010 | wxString pdf_file; 1011 | if ( parser.Found("output-diff", &pdf_file) ) 1012 | { 1013 | retval = doc_compare(doc1, doc2, pdf_file.utf8_str(), NULL) ? 0 : 1; 1014 | } 1015 | else if ( parser.Found("view") ) 1016 | { 1017 | wxGetApp().SetData(parser.GetParam(0), doc1, 1018 | parser.GetParam(1), doc2); 1019 | retval = wxEntry(argc, argv); 1020 | } 1021 | else 1022 | { 1023 | retval = doc_compare(doc1, doc2, NULL, NULL) ? 0 : 1; 1024 | } 1025 | 1026 | g_object_unref(doc1); 1027 | g_object_unref(doc2); 1028 | 1029 | // MinGW doesn't reliably flush streams on exit, so flush them explicitly: 1030 | fflush(stdout); 1031 | fflush(stderr); 1032 | 1033 | return retval; 1034 | } 1035 | -------------------------------------------------------------------------------- /gtk-zoom-in.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *gtk_zoom_in_xpm[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "24 24 70 1", 5 | " c #2D3436", 6 | ". c #2E3436", 7 | "X c #2E3536", 8 | "o c #2F3537", 9 | "O c #2F3538", 10 | "+ c #2F3638", 11 | "@ c #313537", 12 | "# c #303638", 13 | "$ c #363B3B", 14 | "% c #363B3D", 15 | "& c #353C3C", 16 | "* c #373D3F", 17 | "= c #383C3E", 18 | "- c #3B4142", 19 | "; c #3C4242", 20 | ": c #424544", 21 | "> c #4B4F4B", 22 | ", c #525350", 23 | "< c #555753", 24 | "1 c #585A56", 25 | "2 c #616564", 26 | "3 c #666B69", 27 | "4 c #666C69", 28 | "5 c #6D7171", 29 | "6 c #6F7373", 30 | "7 c #777B7A", 31 | "8 c #8F908D", 32 | "9 c #8F9290", 33 | "0 c #989C98", 34 | "q c #999E9A", 35 | "w c #ACAFAB", 36 | "e c #ACAEAC", 37 | "r c #B2B4B1", 38 | "t c #B8BBB5", 39 | "y c #BEC1BC", 40 | "u c #BEC0BD", 41 | "i c #D1D4CF", 42 | "p c #D2D6CF", 43 | "a c #CED0D0", 44 | "s c #CFD0D0", 45 | "d c #D1D2D1", 46 | "f c #D3D6D0", 47 | "g c #D4D6D2", 48 | "h c #D4D7D2", 49 | "j c #D5D6D3", 50 | "k c #D5D7D3", 51 | "l c #D5D9D1", 52 | "z c #D6DAD2", 53 | "x c #D9DCD5", 54 | "c c #DCDED8", 55 | "v c #DDDFD9", 56 | "b c #DDE0DA", 57 | "n c #DFE1DC", 58 | "m c #DFE2DC", 59 | "M c #E0E2DC", 60 | "N c #E2E4DF", 61 | "B c #E3E4E0", 62 | "V c #E5E7E3", 63 | "C c #E6E7E3", 64 | "Z c #E8E9E6", 65 | "A c #E9EAE6", 66 | "S c #E9EAE7", 67 | "D c #EAEAE7", 68 | "F c #EBECE9", 69 | "G c #ECECE9", 70 | "H c #ECECEA", 71 | "J c #ECEDEA", 72 | "K c #EDEDEB", 73 | "L c #EEEEEC", 74 | "P c None", 75 | /* pixels */ 76 | "PPPPPPPPPPPPPPPPPPPPPPPP", 77 | "PPPPPP @ .PPPPPPPPPPPP", 78 | "PPPPP 6eklt9%PPPPPPPPPPP", 79 | "PPP ;dHDCBMvd2 PPPPPPPPP", 80 | "PPP;GLHHGGDMxl4PPPPPPPPP", 81 | "PP sLLLL88LLBld=PPPPPPPP", 82 | "P 5GGLLL11LLGbxq PPPPPPP", 83 | "P wDLLLL<1LLLAby PPPPPPP", 84 | "P dZG81<<<<18Gmk@PPPPPPP", 85 | "P zBL81<<<118GBk PPPPPPP", 86 | "P tmDLLL<&@PPPPP", 93 | "PPPPPPPPPPPPPPP#&,&@PPPP", 94 | "PPPPPPPPPPPPPPPP@&,$+PPP", 95 | "PPPPPPPPPPPPPPPPP+&,&#PP", 96 | "PPPPPPPPPPPPPPPPPP@&> PP", 97 | "PPPPPPPPPPPPPPPPPPP#@@PP", 98 | "PPPPPPPPPPPPPPPPPPPPPPPP", 99 | "PPPPPPPPPPPPPPPPPPPPPPPP" 100 | }; 101 | -------------------------------------------------------------------------------- /gtk-zoom-out.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *gtk_zoom_out_xpm[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "24 24 70 1", 5 | " c #2D3436", 6 | ". c #2E3436", 7 | "X c #2E3536", 8 | "o c #2F3537", 9 | "O c #2F3538", 10 | "+ c #2F3638", 11 | "@ c #313537", 12 | "# c #303638", 13 | "$ c #363B3B", 14 | "% c #363B3D", 15 | "& c #353C3C", 16 | "* c #373D3F", 17 | "= c #383C3E", 18 | "- c #3B4142", 19 | "; c #3C4242", 20 | ": c #424544", 21 | "> c #4B4F4B", 22 | ", c #525350", 23 | "< c #555753", 24 | "1 c #585A56", 25 | "2 c #616564", 26 | "3 c #666B69", 27 | "4 c #666C69", 28 | "5 c #6D7171", 29 | "6 c #6F7373", 30 | "7 c #777B7A", 31 | "8 c #8F908D", 32 | "9 c #8F9290", 33 | "0 c #989C98", 34 | "q c #999E9A", 35 | "w c #ACAFAB", 36 | "e c #ACAEAC", 37 | "r c #B2B4B1", 38 | "t c #B8BBB5", 39 | "y c #BEC1BC", 40 | "u c #BEC0BD", 41 | "i c #D1D4CF", 42 | "p c #D2D6CF", 43 | "a c #CED0D0", 44 | "s c #CFD0D0", 45 | "d c #D1D2D1", 46 | "f c #D3D6D0", 47 | "g c #D4D6D2", 48 | "h c #D4D7D2", 49 | "j c #D5D6D3", 50 | "k c #D5D7D3", 51 | "l c #D5D9D1", 52 | "z c #D6DAD2", 53 | "x c #D9DCD5", 54 | "c c #DCDED8", 55 | "v c #DDDFD9", 56 | "b c #DDE0DA", 57 | "n c #DFE1DC", 58 | "m c #DFE2DC", 59 | "M c #E0E2DC", 60 | "N c #E2E4DF", 61 | "B c #E3E4E0", 62 | "V c #E5E7E3", 63 | "C c #E6E7E3", 64 | "Z c #E8E9E6", 65 | "A c #E9EAE6", 66 | "S c #E9EAE7", 67 | "D c #EAEAE7", 68 | "F c #EBECE9", 69 | "G c #ECECE9", 70 | "H c #ECECEA", 71 | "J c #ECEDEA", 72 | "K c #EDEDEB", 73 | "L c #EEEEEC", 74 | "P c None", 75 | /* pixels */ 76 | "PPPPPPPPPPPPPPPPPPPPPPPP", 77 | "PPPPPP @ .PPPPPPPPPPPP", 78 | "PPPPP 6eklt9%PPPPPPPPPPP", 79 | "PPP ;dHDCBMvd2 PPPPPPPPP", 80 | "PPP;GLHHGGDMxl4PPPPPPPPP", 81 | "PP sLLLLLLLLBld=PPPPPPPP", 82 | "P 5GGLLLLLLLGbxq PPPPPPP", 83 | "P wDLLLLLLLLLAby PPPPPPP", 84 | "P dZG81<1<<18Gmk@PPPPPPP", 85 | "P zBL81<<<118GBk PPPPPPP", 86 | "P tmDLLLGLLLLHBr PPPPPPP", 87 | "P 8bmLLLLLLLLSS7 PPPPPPP", 88 | "PP=dxNLLLLLLHHa@PPPPPPPP", 89 | "PPP2lxbDHGHDHH;PPPPPPPPP", 90 | "PPP 4dxcmBCZs;&PPPPPPPPP", 91 | "PPPPP&quhkr7+PP:+#PPPPPP", 92 | "PPPPPP + @ PPP#>&@PPPPP", 93 | "PPPPPPPPPPPPPPP#&,&@PPPP", 94 | "PPPPPPPPPPPPPPPP@&,$+PPP", 95 | "PPPPPPPPPPPPPPPPP+&,&#PP", 96 | "PPPPPPPPPPPPPPPPPP@&> PP", 97 | "PPPPPPPPPPPPPPPPPPP#@@PP", 98 | "PPPPPPPPPPPPPPPPPPPPPPPP", 99 | "PPPPPPPPPPPPPPPPPPPPPPPP" 100 | }; 101 | -------------------------------------------------------------------------------- /gutter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of diff-pdf. 3 | * 4 | * Copyright (C) 2009 TT-Solutions. 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "gutter.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #define EXTRA_ROOM_FOR_SCROLLBAR 20 27 | 28 | Gutter::Gutter(wxWindow *parent, wxWindowID winid) 29 | : wxVListBox(parent, winid) 30 | { 31 | m_fontHeight = -1; 32 | 33 | SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); 34 | 35 | SetMinSize(wxSize(WIDTH + 2 * BORDER + EXTRA_ROOM_FOR_SCROLLBAR, -1)); 36 | } 37 | 38 | 39 | void Gutter::AddPage(const wxString& label, const wxImage& thumbnail) 40 | { 41 | m_labels.push_back(label); 42 | m_backgrounds.push_back(wxBitmap(thumbnail)); 43 | SetItemCount(m_backgrounds.size()); 44 | Refresh(); 45 | } 46 | 47 | void Gutter::SetThumbnail(int page, const wxImage& thumbnail) 48 | { 49 | m_backgrounds[page] = wxBitmap(thumbnail); 50 | Refresh(); 51 | } 52 | 53 | 54 | void Gutter::UpdateViewPos(wxScrolledWindow *win) 55 | { 56 | int sel = GetSelection(); 57 | if ( sel == wxNOT_FOUND ) 58 | return; 59 | 60 | int total_x, total_y; 61 | win->GetVirtualSize(&total_x, &total_y); 62 | 63 | float scale_x = float(m_backgrounds[sel].GetWidth()) / float(total_x); 64 | float scale_y = float(m_backgrounds[sel].GetHeight()) / float(total_y); 65 | 66 | win->GetViewStart(&m_viewPos.x, &m_viewPos.y); 67 | win->GetClientSize(&m_viewPos.width, &m_viewPos.height); 68 | 69 | m_viewPos.x = int(m_viewPos.x * scale_x); 70 | m_viewPos.y = int(m_viewPos.y * scale_y); 71 | m_viewPos.width = int(m_viewPos.width * scale_x); 72 | m_viewPos.height = int(m_viewPos.height * scale_y); 73 | 74 | Refresh(); 75 | } 76 | 77 | 78 | wxCoord Gutter::OnMeasureItem(size_t n) const 79 | { 80 | if ( m_fontHeight == -1 ) 81 | wxConstCast(this, Gutter)->m_fontHeight = GetCharHeight(); 82 | 83 | return m_backgrounds[n].GetHeight() + 3 * BORDER + m_fontHeight; 84 | } 85 | 86 | 87 | void Gutter::OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const 88 | { 89 | const int xoffset = (GetClientSize().x - WIDTH) / 2; 90 | const int yoffset = BORDER; 91 | 92 | 93 | dc.DrawBitmap(m_backgrounds[n], rect.x + xoffset, rect.y + yoffset); 94 | 95 | const wxString label = m_labels[n]; 96 | int tw; 97 | dc.GetTextExtent(label, &tw, NULL); 98 | dc.SetFont(GetFont()); 99 | dc.DrawText 100 | ( 101 | label, 102 | rect.x + xoffset + (WIDTH - tw) / 2, 103 | rect.y + yoffset + m_backgrounds[n].GetHeight() + BORDER 104 | ); 105 | 106 | if ( GetSelection() == n ) 107 | { 108 | // draw current position 109 | if ( m_viewPos.IsEmpty() ) 110 | return; 111 | 112 | wxRect view(m_viewPos); 113 | view.Offset(rect.GetTopLeft()); 114 | view.Offset(wxPoint(xoffset, yoffset)); 115 | 116 | dc.SetBrush(*wxTRANSPARENT_BRUSH); 117 | dc.SetPen(wxPen(*wxBLUE)); 118 | dc.DrawRectangle(view); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /gutter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of diff-pdf. 3 | * 4 | * Copyright (C) 2009 TT-Solutions. 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef _gutter_h_ 21 | #define _gutter_h_ 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | class WXDLLIMPEXP_FWD_CORE wxScrolledWindow; 30 | 31 | // widget showing places of differences as well as scroll window's position 32 | class Gutter : public wxVListBox 33 | { 34 | public: 35 | // standard width of the gutter image in pixels 36 | static const int WIDTH = 100; 37 | 38 | // standard border 39 | static const int BORDER = 5; 40 | 41 | Gutter(wxWindow *parent, wxWindowID winid); 42 | 43 | // Add a new page to the gutter, with thumbnail's background to be shown 44 | void AddPage(const wxString& label, const wxImage& thumbnail); 45 | 46 | // Set the bitmap with thumbnail's background to be shown 47 | void SetThumbnail(int page, const wxImage& thumbnail); 48 | 49 | // Updates shown view position, i.e. the visible subset of scrolled window. 50 | // The gutter will indicate this area with a blue rectangle. 51 | void UpdateViewPos(wxScrolledWindow *win); 52 | 53 | protected: 54 | virtual wxCoord OnMeasureItem(size_t n) const; 55 | virtual void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const; 56 | 57 | private: 58 | std::vector m_labels; 59 | std::vector m_backgrounds; 60 | wxRect m_viewPos; 61 | int m_fontHeight; 62 | }; 63 | 64 | #endif // _gutter_h_ 65 | -------------------------------------------------------------------------------- /win32/collect-dlls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if [[ -z $1 || ! -f $2 ]]; then 4 | echo "Usage: $0 outputPath binary.exe" >&2 5 | exit 1 6 | fi 7 | 8 | OUTPUT=$1 9 | EXE=$2 10 | 11 | mkdir -p $OUTPUT 12 | rm -rf $OUTPUT/* 13 | 14 | add_binary() 15 | { 16 | destfile="$OUTPUT/`basename $1`" 17 | if [ ! -f $destfile ] ; then 18 | cp -anv $1 $destfile 19 | objdump -x $1 | grep "DLL Name" | cut -d: -f2 >${destfile}.objdeps 20 | fi 21 | } 22 | 23 | last_file_count=0 24 | add_binary $EXE 25 | 26 | while true ; do 27 | file_count=`ls -1 $OUTPUT/*.objdeps | wc -l` 28 | echo "count=$file_count" 29 | if [ $file_count -eq $last_file_count ] ; then break ; fi 30 | last_file_count=$file_count 31 | 32 | cat $OUTPUT/*.objdeps | sort | uniq | while read i ; do 33 | dll=`echo $i` # fixup weird line endings 34 | if [[ -f /ucrt64/bin/$dll ]] ; then 35 | add_binary /ucrt64/bin/$dll 36 | fi 37 | done 38 | done 39 | 40 | rm -rf $OUTPUT/*.objdeps 41 | -------------------------------------------------------------------------------- /win32/fonts.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | WINDOWSFONTDIR 10 | WINDOWSTEMPDIR_FONTCONFIG_CACHE 11 | 12 | 13 | 17 | 18 | 19 | 64 | 65 | 66 | 67 | 68 | 69 | Nimbus Sans L 70 | 71 | Helvetica 72 | 73 | 74 | 75 | 76 | Nimbus Roman No9 L 77 | 78 | Times 79 | 80 | 81 | 82 | 83 | Nimbus Mono L 84 | 85 | Courier 86 | 87 | 88 | 89 | 90 | 91 | 92 | Liberation Sans 93 | Albany 94 | Albany AMT 95 | 96 | Arial 97 | 98 | 99 | 100 | 101 | Liberation Serif 102 | Thorndale 103 | Thorndale AMT 104 | 105 | Times New Roman 106 | 107 | 108 | 109 | 110 | Liberation Mono 111 | Cumberland 112 | Cumberland AMT 113 | 114 | Courier New 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | Helvetica 126 | 127 | Arial 128 | 129 | 130 | 131 | 132 | Times 133 | 134 | Times New Roman 135 | 136 | 137 | 138 | 139 | Courier 140 | 141 | Courier New 142 | 143 | 144 | 145 | 146 | 147 | 148 | Arial 149 | 150 | Helvetica 151 | 152 | 153 | 154 | 155 | Times New Roman 156 | 157 | Times 158 | 159 | 160 | 161 | 162 | Courier New 163 | 164 | Courier 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | Helvetica 176 | 177 | Nimbus Sans L 178 | 179 | 180 | 181 | 182 | Times 183 | 184 | Nimbus Roman No9 L 185 | 186 | 187 | 188 | 189 | Courier 190 | 191 | Nimbus Mono L 192 | 193 | 194 | 195 | 196 | 197 | 198 | Arial 199 | 200 | Liberation Sans 201 | Albany 202 | Albany AMT 203 | 204 | 205 | 206 | 207 | Times New Roman 208 | 209 | Liberation Serif 210 | Thorndale 211 | Thorndale AMT 212 | 213 | 214 | 215 | 216 | Courier New 217 | 218 | Liberation Mono 219 | Cumberland 220 | Cumberland AMT 221 | 222 | 223 | 224 | 225 | 226 | --------------------------------------------------------------------------------