├── .github └── workflows │ └── ci.yml ├── .gitignore ├── COPYING ├── MANIFEST.in ├── README.rst ├── djvusmooth ├── djvusmooth.py ├── doc ├── README ├── changelog ├── credits ├── manpage.xml ├── screenshot.png └── todo ├── edit-text ├── extra └── djvusmooth.desktop ├── lib ├── __init__.py ├── config.py ├── dependencies.py ├── djvused.py ├── external_editor.py ├── gui │ ├── __init__.py │ ├── dialogs.py │ ├── flatten_text.py │ ├── history.py │ ├── main.py │ ├── maparea_browser.py │ ├── maparea_menu.py │ ├── maparea_properties.py │ ├── metadata.py │ ├── outline_browser.py │ ├── page.py │ ├── text_browser.py │ └── wxcompat.py ├── i18n.py ├── ipc.py ├── models │ ├── __init__.py │ ├── annotations.py │ ├── metadata.py │ ├── outline.py │ └── text.py ├── pkgconfig.py ├── text │ ├── __init__.py │ ├── levenshtein.py │ └── mangle.py └── varietes.py ├── po ├── es.po ├── pl.po └── ru.po ├── private ├── check-po ├── check-rst ├── update-i18n └── update-version └── setup.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | 7 | main: 8 | strategy: 9 | matrix: 10 | include: 11 | - os: ubuntu-18.04 12 | - os: ubuntu-20.04 13 | runs-on: ${{matrix.os}} 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: set up Python 2.7 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: 2.7 20 | - name: set up APT 21 | run: | 22 | printf '\n\nPackage: *\nPin: release o=Ubuntu\nPin-Priority: 9999\n' | sudo tee -a /etc/apt/preferences 23 | printf 'Apt::Install-Recommends "false";\n' | sudo tee -a /etc/apt/apt.conf 24 | sudo apt-get update 25 | - name: apt install deps 26 | run: 27 | sudo apt-get install -y 28 | docbook-xml 29 | docbook-xsl 30 | gettext 31 | libxml2-utils 32 | xsltproc 33 | - name: install nose 34 | run: | 35 | python -m pip install nose 36 | - name: run tests 37 | run: | 38 | python -m nose --with-doctest --verbose lib/varietes.py 39 | - name: install 40 | run: | 41 | python setup.py install --user 42 | - name: check docs 43 | run: | 44 | python -m pip install docutils 45 | dpkg-parsechangelog -ldoc/changelog --all 2>&1 >/dev/null | { ! grep .; } 46 | xmllint --nonet --noout --valid doc/*.xml 47 | private/check-rst 48 | - name: clean 49 | run: | 50 | python setup.py clean -a 51 | - name: run pydiatra 52 | run: | 53 | python -m pip install pydiatra 54 | python -m pydiatra -v . 55 | - name: run post-install checks 56 | run: | 57 | cd / 58 | command -v djvusmooth 59 | export MANPATH=/home/runner/.local/share/man MANWIDTH=80 60 | man 1 djvusmooth | grep -A 10 -w DJVUSMOOTH 61 | 62 | pypi: 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: check for namesquatting 66 | run: | 67 | set +e 68 | curl -fsS https://pypi.org/simple/djvusmooth/ 69 | [ $? -eq 22 ] || exit 1 70 | 71 | # vim:ts=2 sts=2 sw=2 et 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | /MANIFEST 3 | /build 4 | /dist 5 | /doc/*.1 6 | /locale/*/LC_MESSAGES/*.mo 7 | /po/*.pot 8 | -------------------------------------------------------------------------------- /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 Lesser 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 along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | exclude README.rst 3 | include MANIFEST.in 4 | include djvusmooth edit-text 5 | include djvusmooth.py 6 | include doc/*.1 7 | include doc/*.xml 8 | include doc/COPYING 9 | include doc/README 10 | include doc/changelog 11 | include doc/credits 12 | include extra/* 13 | include po/*.po 14 | include private/* 15 | recursive-include lib *.py 16 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | doc/README -------------------------------------------------------------------------------- /djvusmooth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding=UTF-8 3 | 4 | # Copyright © 2008 Jakub Wilk 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | if __name__ != '__main__': 18 | raise ImportError('This module is not intended for import') 19 | 20 | import sys 21 | 22 | from djvusmooth.gui.main import Application 23 | application = Application() 24 | application.start(sys.argv[1:]) 25 | 26 | # vim:ts=4 sts=4 sw=4 et 27 | -------------------------------------------------------------------------------- /djvusmooth.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | import sys 4 | import lib 5 | 6 | sys.modules['djvusmooth'] = lib 7 | 8 | # vim:ts=4 sts=4 sw=4 et 9 | -------------------------------------------------------------------------------- /doc/README: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | **djvusmooth** is a graphical editor for DjVu_ documents. 5 | 6 | It allows you to: 7 | 8 | * edit document metadata, 9 | * edit document outline (bookmarks), 10 | * add, remove or edit hyperlinks, 11 | * correct occasional errors in the hidden text layer. 12 | 13 | .. _DjVu: 14 | http://djvu.org 15 | 16 | Prerequisites 17 | ============= 18 | 19 | * Python 2.7 20 | * DjVuLibre_ (including command-line tools) 21 | * python-djvulibre_ 22 | * subprocess32_ 23 | * wxPython (3.0 or 2.8) in Unicode mode 24 | 25 | .. _DjVuLibre: 26 | https://djvu.sourceforge.net/ 27 | .. _python-djvulibre: 28 | https://pypi.org/project/python-djvulibre/ 29 | .. _subprocess32: 30 | https://pypi.org/project/subprocess32/ 31 | 32 | .. vim:ft=rst ts=3 sts=3 sw=3 et 33 | -------------------------------------------------------------------------------- /doc/changelog: -------------------------------------------------------------------------------- 1 | djvusmooth (0.3.1) UNRELEASED; urgency=low 2 | 3 | * 4 | 5 | -- Jakub Wilk Sat, 16 Feb 2019 14:37:33 +0100 6 | 7 | djvusmooth (0.3) unstable; urgency=low 8 | 9 | * Require the subprocess32 module. 10 | https://github.com/jwilk/djvusmooth/issues/23 11 | * Don't let bad editor lock UI. 12 | https://github.com/jwilk/djvusmooth/issues/20 13 | Thanks to Alexander Trufanov for the bug report. 14 | * Reorganize documentation. 15 | * Improve error handling. 16 | * Improve the setup script. 17 | 18 | -- Jakub Wilk Fri, 15 Feb 2019 15:12:35 +0100 19 | 20 | djvusmooth (0.2.19) unstable; urgency=low 21 | 22 | * Create temporary files in a subdirectory of /tmp. 23 | This is necessary, because some editors can't securely open files 24 | directly in /tmp. 25 | 26 | -- Jakub Wilk Sat, 16 Sep 2017 17:05:51 +0200 27 | 28 | djvusmooth (0.2.18) unstable; urgency=low 29 | 30 | * Improve the setup script: 31 | + Fix the “Environment” classifier. 32 | * Update bug tracker URLs. 33 | The project repo has moved to GitHub. 34 | 35 | -- Jakub Wilk Sun, 20 Nov 2016 20:38:27 +0100 36 | 37 | djvusmooth (0.2.17) unstable; urgency=low 38 | 39 | * Update the XDG Base Directory Specification URL. 40 | * Fix typo in the Polish translation. 41 | * Don't hardcode the Python interpreter path in script shebangs; use 42 | “#!/usr/bin/env python” instead. 43 | 44 | -- Jakub Wilk Fri, 15 Jul 2016 11:33:34 +0200 45 | 46 | djvusmooth (0.2.16) unstable; urgency=low 47 | 48 | * Drop support for Python 2.5. 49 | * Improve error handling. 50 | * Improve the manual page: 51 | + Use minus sign instead of hyphen in dates. 52 | * Remove python-djvulibre version checks. 53 | This fixes compatibility with python-djvulibre ≥ 0.5. 54 | Thanks to Daniel Stender for the bug report. 55 | https://github.com/jwilk/djvusmooth/issues/17 56 | 57 | -- Jakub Wilk Tue, 15 Sep 2015 11:12:06 +0200 58 | 59 | djvusmooth (0.2.15) unstable; urgency=low 60 | 61 | [ Jakub Wilk ] 62 | * Improve the setup script: 63 | + Run msgfmt with the --verbose option. 64 | * Improve error handling. 65 | * Use HTTPS URLs when they are available, in documentation and code. 66 | * Document that is possible to edit hyperlinks. 67 | Thanks to Janusz S. Bień for the bug report. 68 | https://github.com/jwilk/djvusmooth/issues/13 69 | * Use the notebook widget regardless of wxPython version. 70 | * Don't hardcode path to the binary in the desktop file. 71 | * Add experimental support for wxPython 3.0. 72 | Thanks to Olly Betts for the bug report and the initial patch. 73 | https://bugs.debian.org/758950 74 | * Drop support for wxPython 2.6. 75 | 76 | [ Daniel Stender ] 77 | * Add German translation for the desktop file. 78 | 79 | -- Jakub Wilk Tue, 07 Oct 2014 21:46:07 +0200 80 | 81 | djvusmooth (0.2.14) unstable; urgency=low 82 | 83 | [ Jakub Wilk ] 84 | * Add “Open recent” submenu. 85 | https://github.com/jwilk/djvusmooth/issues/7 86 | * Do not abort opening a new file if user chose to save the current one. 87 | * Check Python version at runtime. 88 | * Improve the manual page, as per man-pages(7) recommendations: 89 | + Remove the “AUTHOR” section. 90 | + Rename the “ENVIRONMENT VARIABLES” section as “ENVIRONMENT”. 91 | + Make “PORTABILITY” and “REPORTING BUGS” subsections of the “BUGS” 92 | section. 93 | * Improve the setup script: 94 | + Make “setup.py clean -a” remove compiled manual page (unless it was built 95 | by “setup.py sdist”). 96 | + Fix compatibility with Python 2.5 and 2.6 (broken in 0.2.12). 97 | * Improve error handling. 98 | 99 | [ C. Daniel Sanchez R. ] 100 | * Update the Spanish translation. 101 | 102 | [ Kyrill Detinov ] 103 | * Update the Russian translation. 104 | 105 | -- Jakub Wilk Thu, 21 Feb 2013 23:18:33 +0100 106 | 107 | djvusmooth (0.2.13) unstable; urgency=low 108 | 109 | * Add keyboard shortcuts for “Zoom in” and “Zoom out”. 110 | https://github.com/jwilk/djvusmooth/issues/8 111 | * Fix editing text in external editor: when a zone broader than line without 112 | sub-zones existed, djvusmooth incorrectly skipped it when importing text. 113 | Thanks to Janusz S. Bień for the bug report. 114 | * Fix segmentation fault on shutdown. 115 | Thanks to Janusz S. Bień for the bug report. 116 | 117 | -- Jakub Wilk Tue, 02 Oct 2012 12:02:42 +0200 118 | 119 | djvusmooth (0.2.12) unstable; urgency=low 120 | 121 | [ Jakub Wilk ] 122 | * Install desktop file. 123 | * Rename menu item “Edit → Outline → Remove” to “… → Remove all”. 124 | Thanks to Maxim Leyenson for the bug report. 125 | * Use our own minimal XDG Base Directory implementation instead of PyXDG. 126 | 127 | [ Kyrill Detinov ] 128 | * Update the Russian translation. 129 | 130 | [ C. Daniel Sanchez R. ] 131 | * Add Spanish translation. 132 | 133 | -- Jakub Wilk Wed, 13 Jun 2012 20:53:49 +0200 134 | 135 | djvusmooth (0.2.11) unstable; urgency=low 136 | 137 | * Let the setup.py script build and install binary message catalogs. 138 | * Don't throw exceptions when using “Fit width”, “Fit height” and “Stretch” 139 | zoom factors and no page is shown. Thanks to Sherwin Singer for the bug 140 | report. 141 | * Fix a typo in the manual page. 142 | 143 | -- Jakub Wilk Sun, 11 Dec 2011 22:57:54 +0100 144 | 145 | djvusmooth (0.2.10) unstable; urgency=low 146 | 147 | * Let the setup.py script build and install manual pages. Thanks to Kyrill 148 | Detinov and Markus Baertschi for bug reports. 149 | * Fix the code that was supposed to disable the GUI while an external editor 150 | is running. Thanks to Mike Thiery for the bug report. 151 | * Fix the manual page: djvusmooth uses XDG_CONFIG_HOME rather than 152 | XDG_DATA_HOME. 153 | 154 | -- Jakub Wilk Fri, 18 Feb 2011 12:13:43 +0100 155 | 156 | djvusmooth (0.2.9) unstable; urgency=low 157 | 158 | * Improve support for non-POSIX systems: 159 | + Windows: guess location of DjVuLibre DLLs and tools (requires 160 | python-djvulibre ≥ 0.3.3). 161 | + Windows: use the “Application Data” folder to store configuration files 162 | (unless the XDG_CONFIG_HOME environment variable is set). 163 | + Don't read configuration from ~/.DjVuSmooth (which is deprecated 164 | location anyway). 165 | + Don't rely on atomic renames when saving configuration files. 166 | * Reset the SIGCHLD signal to SIG_IGN on start. Thanks to Heinrich 167 | Schwietering for the bug report. 168 | https://bugs.debian.org/596232 169 | * Fix support for external editors that use the overwrite-by-rename 170 | technique. Thanks to Markus Baertschi for the bug report. 171 | 172 | -- Jakub Wilk Wed, 19 Jan 2011 21:07:53 +0100 173 | 174 | djvusmooth (0.2.8) unstable; urgency=low 175 | 176 | [ Kyrill Detinov ] 177 | * Update the Russian translation. 178 | 179 | [ Jakub Wilk ] 180 | * Handle directories with non-ASCII characters. 181 | https://bugs.debian.org/595002 182 | * Fix editing line/arrow annotations. 183 | https://bugs.debian.org/595012 184 | 185 | -- Jakub Wilk Tue, 31 Aug 2010 15:15:01 +0200 186 | 187 | djvusmooth (0.2.7) unstable; urgency=low 188 | 189 | [ Kyrill Detinov ] 190 | * Add Russian translation. 191 | 192 | [ Jakub Wilk ] 193 | * Handle non-ASCII metadata keys. 194 | Thanks to Aleš Kapica for the bug report. 195 | 196 | -- Jakub Wilk Sat, 26 Jun 2010 14:34:19 +0200 197 | 198 | djvusmooth (0.2.6) unstable; urgency=low 199 | 200 | * Add keyboard shortcut Ctrl+G for “Go to page…”. 201 | Thanks to Kyrill Detinov for the bug report and the patch. 202 | * Reopen document after save, so that it's possible to display non-cached 203 | pages. Thanks to Kyrill Detinov for the bug report. 204 | 205 | -- Jakub Wilk Tue, 15 Jun 2010 21:08:26 +0200 206 | 207 | djvusmooth (0.2.5) unstable; urgency=low 208 | 209 | * Fix setup.py to install all the required packages. 210 | Thanks to Kyrill Detinov for the bug report. 211 | 212 | -- Jakub Wilk Thu, 08 Apr 2010 18:06:50 +0200 213 | 214 | djvusmooth (0.2.4) unstable; urgency=low 215 | 216 | * Fix regression in handling sidebar events with wxWidgets 2.8. Thanks to 217 | Janusz S. Bień for the bug report. 218 | 219 | -- Jakub Wilk Wed, 31 Mar 2010 12:53:04 +0200 220 | 221 | djvusmooth (0.2.3) unstable; urgency=low 222 | 223 | * Fix issues with “Overprinted annotation properties” dialog: 224 | + Allow turning off the “allow visible” property. 225 | Thanks to Janusz S. Bień for the bug report. 226 | https://bugs.debian.org/574362 227 | + Disable the “allow visible” checkbox if a hyperlink has no border. 228 | https://bugs.debian.org/574338 229 | + Display real border width instead of hard-coded 1. 230 | + Use the term “width” rather than “thickness” consistently. 231 | * Document that using shadow border thinner than 3 pixels is not portable. 232 | https://bugs.debian.org/574361 233 | 234 | -- Jakub Wilk Wed, 17 Mar 2010 21:59:58 +0100 235 | 236 | djvusmooth (0.2.2) unstable; urgency=low 237 | 238 | * Fix crashes in the ‘Saving document’ and ‘Go to page’ dialogs. 239 | * Fix compatibility with wxWidgets 2.8. 240 | * Give a more helpful error messages if djvused is not available. 241 | 242 | -- Jakub Wilk Fri, 22 Jan 2010 19:36:03 +0100 243 | 244 | djvusmooth (0.2.1) unstable; urgency=low 245 | 246 | * Don't crash on non-ASCII file names. 247 | Thanks to Jean-Christophe Heger for the bug report. 248 | 249 | -- Jakub Wilk Mon, 11 Jan 2010 18:27:28 +0100 250 | 251 | djvusmooth (0.2.0) unstable; urgency=low 252 | 253 | [ Mateusz Turcza ] 254 | * Remember last visited directory. 255 | * Add Polish translation. 256 | 257 | [ Jakub Wilk ] 258 | * Follow the XDG Base Directory Specification. 259 | * Fix a typo in variable name that was causing random crashes. 260 | * Provide a manual page. 261 | 262 | -- Jakub Wilk Fri, 20 Nov 2009 22:45:08 +0100 263 | 264 | djvusmooth (0.1.4) unstable; urgency=low 265 | 266 | * Fix some issues with external editing. 267 | 268 | -- Jakub Wilk Mon, 02 Mar 2009 23:36:21 +0100 269 | 270 | djvusmooth (0.1.3) unstable; urgency=low 271 | 272 | * Don't assume UTF-8 locale and UTF-8-encoded text in DjVu files. 273 | 274 | -- Jakub Wilk Mon, 07 Jul 2008 01:12:34 +0200 275 | 276 | djvusmooth (0.1.2) unstable; urgency=low 277 | 278 | * Fix bookmarking current page when there are no bookmarks. 279 | * Fix flattening text. 280 | 281 | -- Jakub Wilk Tue, 20 May 2008 12:49:30 +0200 282 | 283 | djvusmooth (0.1.1) unstable; urgency=low 284 | 285 | * Don't require pkg-config nor development files of DjVuLibre. 286 | 287 | -- Jakub Wilk Wed, 14 May 2008 11:22:49 +0200 288 | 289 | djvusmooth (0.1) unstable; urgency=low 290 | 291 | * Initial release. 292 | 293 | -- Jakub Wilk Tue, 06 May 2008 23:28:58 +0200 294 | -------------------------------------------------------------------------------- /doc/credits: -------------------------------------------------------------------------------- 1 | djvusmooth development was supported by the Polish Ministry of Science 2 | and Higher Education's grant no. N N519 384036 (2009–2012, 3 | https://bitbucket.org/jsbien/ndt). 4 | -------------------------------------------------------------------------------- /doc/manpage.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | ]> 7 | 8 | 9 | 10 | 11 | &p; manual 12 | &p; 13 | 14 | Jakub Wilk 15 | jwilk@jwilk.net 16 | 17 | 18 | 19 | 20 | &p; 21 | 1 22 | &version; 23 | 24 | 25 | 26 | &p; 27 | graphical editor for DjVu 28 | 29 | 30 | 31 | 32 | &p; 33 | djvu-file 34 | 35 | 36 | 37 | 38 | Description 39 | 40 | &p; is a graphical editor for DjVu files, which allows one to: 41 | 42 | edit document metadata, 43 | edit document outline (bookmarks), 44 | add, remove or edit hyperlinks, 45 | correct occasional errors in the hidden text layer. 46 | 47 | 48 | 49 | 50 | 51 | Environment 52 | 53 | 54 | XDG_CONFIG_HOME 55 | 56 | 57 | Base directory for configuration files. The default is $HOME/.config. 58 | 59 | 60 | See XDG Base Directory 62 | Specification for details. 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Files 71 | 72 | 73 | $XDG_CONFIG_HOME/djvusmooth/djvusmooth.conf 74 | 75 | &p; configuration file 76 | 77 | 78 | 79 | 80 | 81 | 82 | Bugs 83 | 84 | Portability issues 85 | 86 | &p; allows one to create hyperlinks with shadow borders as thin as 1 pixel, as specified by the 87 | Lizardtech DjVu Reference. 88 | However, some DjVu browsers do not accept shadow borders thinner than 3 pixels. 89 | 90 | 91 | 92 | 93 | Reporting bugs 94 | 95 | Please report bugs at: 96 | 97 | 98 | 99 | 100 | 101 | 102 | See also 103 | 104 | 105 | djvu 106 | 1 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwilk-archive/djvusmooth/b198eb9bc2f88c5bf5c82f8a13f0f1315dc59717/doc/screenshot.png -------------------------------------------------------------------------------- /doc/todo: -------------------------------------------------------------------------------- 1 | Add “Save as” function. 2 | 3 | Allow adding/deleting lines/words. 4 | 5 | Fix translation of exception handling message. 6 | 7 | Improve error handling. 8 | 9 | Disable antialiasing for fonts. 10 | 11 | Improve hyperlinks resolving. 12 | 13 | Implement scroll by mouse dragging. 14 | 15 | Zoom should be aware of the current position. 16 | 17 | Use different shapes for different maparea nodes. 18 | 19 | Document how to build the package. 20 | 21 | .. vim:ts=3 sts=3 sw=3 et ft=rst 22 | -------------------------------------------------------------------------------- /edit-text: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding=UTF-8 3 | 4 | # Copyright © 2008-2015 Jakub Wilk 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | from __future__ import print_function 18 | 19 | if __name__ != '__main__': 20 | raise ImportError('This module is not intended for import') 21 | 22 | import argparse 23 | import sys 24 | 25 | import djvu.decode 26 | import djvu.sexpr 27 | 28 | from djvusmooth.text.mangle import import_, export 29 | 30 | def do_export(djvu_file_name, page_no): 31 | context = djvu.decode.Context() 32 | document = context.new_document(djvu.decode.FileURI(djvu_file_name)) 33 | text = djvu.decode.PageText(document.pages[page_no], details=djvu.decode.TEXT_DETAILS_WORD) 34 | text.wait() 35 | export(text.sexpr, sys.stdout) 36 | 37 | def do_import(djvu_file_name, page_no): 38 | context = djvu.decode.Context() 39 | document = context.new_document(djvu.decode.FileURI(djvu_file_name)) 40 | text = djvu.decode.PageText(document.pages[page_no], details=djvu.decode.TEXT_DETAILS_WORD) 41 | text.wait() 42 | print(import_(text.sexpr, sys.stdin)) 43 | 44 | def main(): 45 | ap = argparse.ArgumentParser() 46 | ap.add_argument('-p', '--page', dest='page_no', metavar='N', action='store', type=int, required=True) 47 | ag = ap.add_mutually_exclusive_group(required=True) 48 | ag.add_argument('-x', '--export', dest='action', action='store_const', const=do_export) 49 | ag.add_argument('-i', '--import', dest='action', action='store_const', const=do_import) 50 | ap.add_argument('file', metavar='FILE') 51 | options = ap.parse_args() 52 | options.action(options.file, options.page_no - 1) 53 | 54 | if __name__ == '__main__': 55 | main() 56 | 57 | # vim:ts=4 sts=4 sw=4 et 58 | -------------------------------------------------------------------------------- /extra/djvusmooth.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=djvusmooth 5 | GenericName=Graphical editor for DjVu 6 | GenericName[de]=Grafischer Editor für DjVu-Dateien 7 | GenericName[pl]=Graficzny edytor plików DjVu 8 | Exec=djvusmooth %f 9 | Terminal=false 10 | MimeType=image/x-djvu;image/x.djvu;image/vnd.djvu; 11 | Categories=Graphics; 12 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info < (2, 7): 4 | raise RuntimeError('Python >= 2.7 is required') 5 | if sys.version_info >= (3, 0): 6 | raise RuntimeError('Python 2.X is required') 7 | 8 | __version__ = '0.3.1' 9 | __author__ = 'Jakub Wilk ' 10 | 11 | # vim:ts=4 sts=4 sw=4 et 12 | -------------------------------------------------------------------------------- /lib/config.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2009-2015 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | import errno 17 | import os 18 | 19 | class xdg(object): 20 | ''' 21 | tiny replacement for PyXDG's xdg.BaseDirectory 22 | ''' 23 | 24 | xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or '' 25 | if os.name == 'nt' and xdg_config_home == '': 26 | # On Windows, use the “Application Data” folder if XDG_CONFIG_HOME is 27 | # not set. 28 | xdg_config_home = os.environ.get('APPDATA') or '' 29 | 30 | if not os.path.isabs(xdg_config_home): 31 | xdg_config_home = os.path.join(os.path.expanduser('~'), '.config') 32 | 33 | xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS') or '/etc/xdg' 34 | xdg_config_dirs = ( 35 | [xdg_config_home] + 36 | filter(os.path.abspath, xdg_config_dirs.split(os.path.pathsep)) 37 | ) 38 | 39 | @classmethod 40 | def save_config_path(xdg, resource): 41 | path = os.path.join(xdg.xdg_config_home, resource) 42 | try: 43 | os.makedirs(path, 0o700) 44 | except OSError: 45 | if not os.path.isdir(path): 46 | raise 47 | return path 48 | 49 | @classmethod 50 | def load_config_paths(xdg, resource): 51 | for config_dir in xdg.xdg_config_dirs: 52 | path = os.path.join(config_dir, resource) 53 | if os.path.exists(path): 54 | yield path 55 | 56 | class Config(object): 57 | 58 | def __init__(self, resource, legacy_path=None): 59 | self._dirty = False 60 | self._data = {} 61 | self._resource = resource 62 | for directory in xdg.load_config_paths(resource): 63 | self._load( 64 | os.path.join(directory, '{res}.conf'.format(res=resource)) 65 | ) 66 | self._legacy_path = legacy_path 67 | if legacy_path is not None: 68 | self._load(legacy_path) 69 | self._dirty = True 70 | 71 | def read(self, key, default): 72 | return self._data.get(key, default) 73 | 74 | def read_int(self, key, default): 75 | return int(self.read(key, default)) 76 | 77 | def read_bool(self, key, default): 78 | return bool(self.read_int(key, default)) 79 | 80 | def __setitem__(self, key, value): 81 | self._data[key] = value 82 | self._dirty = True 83 | 84 | def del_array(self, key): 85 | keys_to_delete = frozenset( 86 | k for k in self._data 87 | if k.startswith(key + '[') 88 | ) 89 | for k in keys_to_delete: 90 | del self._data[k] 91 | 92 | def _load(self, path): 93 | try: 94 | file = open(path, 'r') 95 | except IOError as ex: 96 | if ex.errno == errno.ENOENT: 97 | return 98 | try: 99 | for line in file: 100 | line = line.rstrip() 101 | try: 102 | key, value = line.split('=', 1) 103 | except ValueError: 104 | pass 105 | value = value.decode('UTF-8') 106 | self._data[key] = value 107 | finally: 108 | file.close() 109 | 110 | def flush(self): 111 | if not self._dirty: 112 | return 113 | directory = xdg.save_config_path(self._resource) 114 | path = os.path.join(directory, '{res}.conf'.format(res=self._resource)) 115 | tmp_path = path + '.tmp' 116 | file = open(tmp_path, 'w') 117 | try: 118 | for key, value in sorted(self._data.iteritems()): 119 | if isinstance(value, unicode): 120 | value = value.encode('UTF-8') 121 | file.write('{key}={val}\n'.format(key=key, val=value)) 122 | file.flush() 123 | os.fsync(file.fileno()) 124 | finally: 125 | file.close() 126 | if os.name != 'posix' and os.path.exists(path): 127 | # Atomic renames might be not supported. 128 | backup_path = path + '.bak' 129 | os.rename(path, backup_path) 130 | os.rename(tmp_path, path) 131 | os.remove(backup_path) 132 | else: 133 | os.rename(tmp_path, path) 134 | if self._legacy_path is not None: 135 | try: 136 | os.remove(self._legacy_path) 137 | except OSError as ex: 138 | if ex.errno != errno.ENOENT: 139 | raise 140 | self._dirty = False 141 | 142 | # vim:ts=4 sts=4 sw=4 et 143 | -------------------------------------------------------------------------------- /lib/dependencies.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2019 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | ''' 17 | Checks for djvusmooth dependencies. 18 | ''' 19 | 20 | djvulibre_path = None 21 | 22 | def _check_djvu(): 23 | # On Windows, special measures may be needed to find the DjVuLibre DLL. 24 | global djvulibre_path 25 | try: 26 | from djvu.dllpath import set_dll_search_path 27 | except ImportError: 28 | pass 29 | else: 30 | djvulibre_path = set_dll_search_path() 31 | try: 32 | import djvu.decode 33 | except ImportError as exc: 34 | raise ImportError('{exc}; is python-djvulibre installed?'.format(exc=exc)) 35 | del djvu # quieten pyflakes 36 | 37 | def _check_wx(): 38 | try: 39 | import wxversion 40 | except ImportError as exc: 41 | raise ImportError('{exc}; is wxPython installed?'.format(exc=exc)) 42 | for ver in ['2.8-unicode', '3.0']: 43 | try: 44 | wxversion.select(ver, optionsRequired=True) 45 | except wxversion.VersionError: 46 | continue 47 | else: 48 | break 49 | else: 50 | raise ImportError('wxPython 3.0 or 2.8 in Unicode mode is required') 51 | 52 | _check_djvu() 53 | _check_wx() 54 | 55 | # vim:ts=4 sts=4 sw=4 et 56 | -------------------------------------------------------------------------------- /lib/djvused.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2019 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | import os.path 17 | import threading 18 | 19 | from djvu.sexpr import Expression, Symbol 20 | 21 | from . import ipc 22 | 23 | djvused_path = None 24 | if os.name == 'nt': 25 | from . import dependencies 26 | djvused_path = os.path.join(dependencies.djvulibre_path, 'djvused.exe') 27 | else: 28 | from . import pkgconfig 29 | try: 30 | djvulibre_bin_path = os.path.join(pkgconfig.Package('ddjvuapi').variable('exec_prefix'), 'bin') 31 | except (IOError, OSError): 32 | pass 33 | else: 34 | djvused_path = os.path.join(djvulibre_bin_path, 'djvused') 35 | if djvused_path is None or not os.path.isfile(djvused_path): 36 | # Let's hope it's within $PATH... 37 | djvused_path = 'djvused' 38 | 39 | def _djvused_usability_check(): 40 | try: 41 | djvused = ipc.Subprocess( 42 | [djvused_path], 43 | stdout=ipc.PIPE, 44 | stderr=ipc.PIPE 45 | ) 46 | djvused.communicate() 47 | if djvused.returncode == 10: 48 | return 49 | except (IOError, OSError): 50 | pass 51 | raise IOError('{path!r} does not seem to be usable'.format(path=djvused_path)) 52 | 53 | _djvused_usability_check() 54 | 55 | class IOError(IOError): 56 | pass 57 | 58 | class StreamEditor(object): 59 | 60 | def __init__(self, file_name, autosave=False): 61 | self._file_name = file_name 62 | self._commands = [] 63 | self._autosave = autosave 64 | 65 | def clone(self): 66 | return StreamEditor(self._filename, self._autosave) 67 | 68 | def _add(self, *commands): 69 | for command in commands: 70 | if not isinstance(command, str): 71 | raise TypeError 72 | self._commands += commands 73 | 74 | def select_all(self): 75 | self._add('select') 76 | 77 | def select(self, page_id): 78 | self._add('select %s' % page_id) 79 | 80 | def select_shared_annotations(self): 81 | self._add('select-shared-ant') 82 | 83 | def create_shared_annotations(self): 84 | self._add('create-shared-ant') 85 | 86 | def set_annotations(self, annotations): 87 | self._add('set-ant') 88 | for annotation in annotations: 89 | self._add(str(annotation)) 90 | self._add('.') 91 | 92 | def remove_annotations(self): 93 | self._add('remove-ant') 94 | 95 | def print_annotations(self): 96 | self._add('print-ant') 97 | 98 | def set_metadata(self, meta): 99 | self._add('set-meta') 100 | for key, value in meta.iteritems(): 101 | value = unicode(value) 102 | self._add('%s\t%s' % (Expression(Symbol(key)), Expression(value))) 103 | self._add('.') 104 | 105 | def remove_metadata(self): 106 | self._add('remove-meta') 107 | 108 | def set_text(self, text): 109 | if text is None: 110 | self.remove_text() 111 | else: 112 | self._add('set-txt', str(text), '.') 113 | 114 | def remove_text(self): 115 | self._add('remove-txt') 116 | 117 | def set_outline(self, outline): 118 | if outline is None: 119 | outline = '' 120 | self._add('set-outline', str(outline), '.') 121 | 122 | def set_thumbnails(self, size): 123 | self._add('set-thumbnails %d' % size) 124 | 125 | def remove_thumbnails(self): 126 | self._add('remove-thumbnails') 127 | 128 | def set_page_title(self, title): 129 | self._add('set-page-title %s' % Expression(title)) 130 | 131 | def save_page(self, file_name, include=False): 132 | command = 'save-page' 133 | if include: 134 | command += '-with' 135 | self._add('%s %s' % command, file_name) 136 | 137 | def save_as_bundled(self, file_name): 138 | self._add('save-bundled %s' % file_name) 139 | 140 | def save_as_indirect(self, file_name): 141 | self._add('save-indirect %s' % file_name) 142 | 143 | def save(self): 144 | self._add('save') 145 | 146 | def _reader_thread(self, fo, result): 147 | result[0] = fo.read() 148 | 149 | def _execute(self, commands, save=False): 150 | args = [djvused_path] 151 | if save: 152 | args += '-s', 153 | args += self._file_name, 154 | djvused = ipc.Subprocess(args, 155 | stdin=ipc.PIPE, 156 | stdout=ipc.PIPE, 157 | stderr=ipc.PIPE 158 | ) 159 | result = [None] 160 | reader_thread = threading.Thread( 161 | target=self._reader_thread, 162 | args=(djvused.stdout, result) 163 | ) 164 | reader_thread.setDaemon(True) 165 | reader_thread.start() 166 | stdin = djvused.stdin 167 | for command in commands: 168 | stdin.write(command + '\n') 169 | stdin.close() 170 | djvused.wait() 171 | if djvused.returncode: 172 | raise IOError(djvused.stderr.readline().lstrip('* ')) 173 | reader_thread.join() 174 | return result[0] 175 | 176 | def commit(self): 177 | try: 178 | return self._execute(self._commands, save=self._autosave) 179 | finally: 180 | self._commands = [] 181 | 182 | # vim:ts=4 sts=4 sw=4 et 183 | -------------------------------------------------------------------------------- /lib/external_editor.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2019 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | import os.path 17 | import shutil 18 | import tempfile 19 | 20 | from . import ipc 21 | 22 | class temporary_file(object): 23 | 24 | def __init__(self, name): 25 | self.dir = self.fp = None 26 | self.dir = tempfile.mkdtemp(prefix='djvusmooth.') 27 | self.name = os.path.join(self.dir, name) 28 | self.fp = open(self.name, 'w+') 29 | 30 | def _reopen(self): 31 | if self.fp is None: 32 | self.fp = open(self.name, 'r+') 33 | 34 | def flush(self): 35 | if self.fp is None: 36 | return 37 | self.fp.close() 38 | self.fp = None 39 | 40 | def close(self): 41 | if self.dir is None: 42 | return 43 | self.flush() 44 | shutil.rmtree(self.dir) 45 | self.dir = None 46 | 47 | def seek(self, offset, whence=0): 48 | self._reopen() 49 | self.fp.seek(offset, whence) 50 | 51 | def write(self, s): 52 | self._reopen() 53 | self.fp.write(s) 54 | 55 | def read(self, n=-1): 56 | self._reopen() 57 | return self.fp.read(n) 58 | 59 | def __iter__(self): 60 | self._reopen() 61 | return iter(self.fp) 62 | 63 | def __enter__(self): 64 | return self 65 | 66 | def __exit__(self, exc, value, tb): 67 | self.close() 68 | 69 | class Editor(object): 70 | 71 | def __call__(self, path): 72 | raise NotImplementedError 73 | 74 | class RunMailcapEditor(object): 75 | 76 | def __call__(self, path): 77 | path = os.path.abspath(path) 78 | edit = ipc.Subprocess( 79 | ['edit', 'text/plain:{path}'.format(path=path)], 80 | stdin=ipc.PIPE, 81 | stdout=ipc.PIPE, 82 | ) 83 | edit.stdin.close() 84 | edit.stdout.close() 85 | edit.wait() 86 | 87 | class CustomEditor(object): 88 | 89 | def __init__(self, command, *extra_args): 90 | self._command = [command] 91 | self._command += extra_args 92 | 93 | def __call__(self, path): 94 | path = os.path.abspath(path) 95 | edit = ipc.Subprocess( 96 | self._command + [path], 97 | stdin=ipc.PIPE, 98 | stdout=ipc.PIPE, 99 | ) 100 | edit.stdin.close() 101 | edit.stdout.close() 102 | edit.wait() 103 | 104 | # vim:ts=4 sts=4 sw=4 et 105 | -------------------------------------------------------------------------------- /lib/gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwilk-archive/djvusmooth/b198eb9bc2f88c5bf5c82f8a13f0f1315dc59717/lib/gui/__init__.py -------------------------------------------------------------------------------- /lib/gui/dialogs.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | import wx 17 | 18 | class ProgressDialog(wx.ProgressDialog): 19 | 20 | def __init__(self, title, message, maximum=100, parent=None, style=(wx.PD_AUTO_HIDE | wx.PD_APP_MODAL)): 21 | wx.ProgressDialog.__init__(self, title, message, maximum, parent, style) 22 | self.__max = maximum 23 | self.__n = 0 24 | 25 | try: 26 | wx.ProgressDialog.Pulse 27 | except AttributeError: 28 | def Pulse(self): 29 | self.__n = (self.__n + 1) % self.__max 30 | self.Update(self.__n) 31 | 32 | try: 33 | NumberEntryDialog = wx.NumberEntryDialog 34 | except AttributeError: 35 | class NumberEntryDialog(wx.SingleChoiceDialog): 36 | def __init__(self, parent, message, prompt, caption, value, min, max, pos=wx.DefaultPosition): 37 | wx.SingleChoiceDialog.__init__(self, parent=parent, message=message, caption=caption, choices=map(str, xrange(min, max + 1)), pos=pos) 38 | self.SetSelection(value - min) 39 | 40 | def GetValue(self): 41 | return int(wx.SingleChoiceDialog.GetStringSelection(self)) 42 | 43 | __all__ = ['ProgressDialog', 'NumberEntryDialog'] 44 | 45 | # vim:ts=4 sts=4 sw=4 et 46 | -------------------------------------------------------------------------------- /lib/gui/flatten_text.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # Copyright © 2009 Mateusz Turcza 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | import wx 18 | 19 | import djvu.sexpr 20 | import djvu.const 21 | 22 | from djvusmooth.i18n import _ 23 | 24 | ZONES_MAP = ( 25 | (_('all'), djvu.const.TEXT_ZONE_PAGE), 26 | (_('columns'), djvu.const.TEXT_ZONE_COLUMN), 27 | (_('regions'), djvu.const.TEXT_ZONE_REGION), 28 | (_('paragraphs'), djvu.const.TEXT_ZONE_PARAGRAPH), 29 | (_('lines'), djvu.const.TEXT_ZONE_LINE), 30 | (_('words'), djvu.const.TEXT_ZONE_WORD), 31 | (_('characters'), djvu.const.TEXT_ZONE_CHARACTER) 32 | ) 33 | 34 | class FlattenTextDialog(wx.Dialog): 35 | 36 | def __init__(self, parent): 37 | wx.Dialog.__init__(self, parent, title=_('Flatten text')) 38 | sizer = wx.BoxSizer(wx.VERTICAL) 39 | self._scope_box = wx.RadioBox(self, 40 | label=(_('Scope') + ':'), 41 | choices=(_('current page'), _('all pages')), 42 | style=wx.RA_HORIZONTAL 43 | ) 44 | self._zone_box = wx.RadioBox(self, 45 | label=(_('Remove details') + ':'), 46 | choices=[label for label, type in ZONES_MAP], 47 | style=wx.RA_SPECIFY_COLS, majorDimension=2 48 | ) 49 | self._zone_box.SetSelection(len(ZONES_MAP) - 1) 50 | for box in self._scope_box, self._zone_box: 51 | sizer.Add(box, 0, wx.EXPAND | wx.ALL, 5) 52 | line = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL) 53 | sizer.Add(line, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5) 54 | button_sizer = wx.StdDialogButtonSizer() 55 | button = wx.Button(self, wx.ID_OK) 56 | button.SetDefault() 57 | button_sizer.AddButton(button) 58 | button = wx.Button(self, wx.ID_CANCEL) 59 | button_sizer.AddButton(button) 60 | button_sizer.Realize() 61 | sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 5) 62 | self.SetSizerAndFit(sizer) 63 | 64 | def get_scope(self): 65 | return self._scope_box.GetSelection() 66 | 67 | def get_zone(self): 68 | label, zone = ZONES_MAP[self._zone_box.GetSelection()] 69 | return zone 70 | 71 | __all__ = ['FlattenTextDialog'] 72 | 73 | # vim:ts=4 sts=4 sw=4 et 74 | -------------------------------------------------------------------------------- /lib/gui/history.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2012 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | import itertools 17 | 18 | import wx 19 | 20 | class FileHistory(object): 21 | 22 | def __init__(self, config): 23 | self._wx = wx.FileHistory() 24 | self._config = config 25 | to_add = [] 26 | for i in itertools.count(0): 27 | key = 'recent[{0}]'.format(i) 28 | path = config.read(key, None) 29 | if path is None: 30 | break 31 | to_add += [path] 32 | for path in reversed(to_add): 33 | self._add(path) 34 | 35 | def _add(self, path): 36 | self._wx.AddFileToHistory(path) 37 | 38 | def add(self, path): 39 | self._add(path) 40 | config = self._config 41 | config.del_array('recent') 42 | for n, path in enumerate(self): 43 | config['recent[{0}]'.format(n)] = path 44 | self._enable_menu_item() 45 | 46 | def set_menu(self, window, menu_item, on_click): 47 | menu = menu_item.GetSubMenu() 48 | self._wx.UseMenu(menu) 49 | self._wx.AddFilesToMenu() 50 | self._menu_item = menu_item 51 | id1 = wx.ID_FILE1 52 | id2 = id1 + self._get_max_length() 53 | def on_click_wrapper(event): 54 | n = event.GetId() - wx.ID_FILE1 55 | path = self[n] 56 | return on_click(path) 57 | window.Bind(wx.EVT_MENU_RANGE, on_click_wrapper, id=id1, id2=id2) 58 | self._enable_menu_item() 59 | 60 | def _enable_menu_item(self): 61 | self._menu_item.Enable(enable=bool(self)) 62 | 63 | def __len__(self): 64 | return self._wx.Count 65 | 66 | def __iter__(self): 67 | return ( 68 | self[n] 69 | for n in xrange(len(self)) 70 | ) 71 | 72 | def __getitem__(self, n): 73 | return self._wx.GetHistoryFile(n) 74 | 75 | def _get_max_length(self): 76 | return self._wx.GetMaxFiles() 77 | 78 | # vim:ts=4 sts=4 sw=4 et 79 | -------------------------------------------------------------------------------- /lib/gui/maparea_browser.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # Copyright © 2009 Mateusz Turcza 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | import wx 18 | import wx.lib.mixins.listctrl 19 | 20 | import djvusmooth.models.annotations 21 | from djvusmooth import models 22 | import djvusmooth.gui.maparea_menu 23 | from djvusmooth import gui 24 | from djvusmooth.i18n import _ 25 | from djvusmooth.gui import wxcompat 26 | 27 | class PageAnnotationsCallback(models.annotations.PageAnnotationsCallback): 28 | 29 | def __init__(self, owner): 30 | self._owner = owner 31 | 32 | def notify_node_change(self, node): 33 | wx.CallAfter(lambda: self._owner.on_node_change(node)) 34 | 35 | def notify_node_select(self, node): 36 | wx.CallAfter(lambda: self._owner.on_node_select(node)) 37 | 38 | def notify_node_deselect(self, node): 39 | pass 40 | 41 | def notify_node_add(self, node): 42 | wx.CallAfter(lambda: self._owner.on_node_add(node)) 43 | 44 | def notify_node_replace(self, node, other_node): 45 | wx.CallAfter(lambda: self._owner.on_node_replace(node, other_node)) 46 | 47 | def notify_node_delete(self, node): 48 | wx.CallAfter(lambda: self._owner.on_node_delete(node)) 49 | 50 | def item_to_id(item): 51 | try: 52 | return int(item) 53 | except TypeError: 54 | return item.GetId() 55 | 56 | class MapAreaBrowser( 57 | wx.ListCtrl, 58 | wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin, 59 | wx.lib.mixins.listctrl.TextEditMixin 60 | ): 61 | 62 | def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LC_REPORT): 63 | wx.ListCtrl.__init__(self, parent, id, pos, size, style) 64 | self.InsertColumn(0, _('URI')) 65 | self.InsertColumn(1, _('Comment')) 66 | self._have_items = False 67 | self._data = {} 68 | self._data_map = {} 69 | self.page = None 70 | wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self) 71 | wx.lib.mixins.listctrl.TextEditMixin.__init__(self) 72 | self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_selection_changed, self) 73 | self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.on_item_right_click, self) 74 | self.Bind(wx.EVT_CHAR, self.on_char, self) 75 | self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) 76 | 77 | def do_remove_node(self, node): 78 | node.delete() 79 | 80 | _WXK_TO_METHOD = { 81 | wx.WXK_DELETE: do_remove_node 82 | } 83 | 84 | def on_char(self, event): 85 | key_code = event.GetKeyCode() 86 | try: 87 | method = self._WXK_TO_METHOD[key_code] 88 | except KeyError: 89 | event.Skip() 90 | return 91 | item = self.GetFirstSelected() 92 | node = self.GetPyData(item) 93 | if node is None: 94 | return 95 | method(self, node) 96 | 97 | def on_key_down(self, event): 98 | return wxcompat.on_key_down(self, event) 99 | 100 | def on_node_add(self, node): 101 | item = self._insert_item(node) 102 | self.Focus(item) 103 | 104 | def on_node_replace(self, node, other_node): 105 | item = self._insert_item(other_node, replace_node=node) 106 | self.Focus(item) 107 | 108 | def on_node_change(self, node): 109 | self.on_node_replace(node, node) 110 | 111 | def on_node_delete(self, node): 112 | self._remove_item(node) 113 | 114 | def on_node_select(self, node): 115 | try: 116 | current_item = self._data_map[node] 117 | except KeyError: 118 | return 119 | selected_item = self.GetFirstSelected() 120 | if selected_item != current_item: 121 | self.Select(selected_item, False) 122 | self.Select(current_item, True) 123 | 124 | def on_item_right_click(self, event): 125 | item = event.m_itemIndex 126 | node = self.GetPyData(item) 127 | # Yup, we accept the fact that `node` can be `None` 128 | self.show_menu(node, event.GetPoint()) 129 | 130 | def show_menu(self, node, point): 131 | gui.maparea_menu.show_menu(self, self.page.annotations, node, point) 132 | 133 | def on_selection_changed(self, event): 134 | event.Skip() 135 | item = event.m_itemIndex 136 | node = self.GetPyData(item) 137 | if node is None: 138 | return 139 | node.notify_select() 140 | 141 | @apply 142 | def page(): 143 | def get(self): 144 | return self._page 145 | def set(self, value): 146 | if value is not True: 147 | self._page = value 148 | if self._page is not None: 149 | self._callback = PageAnnotationsCallback(self) 150 | self._page.annotations.register_callback(self._callback) 151 | self._model = self.page.annotations 152 | self._recreate_items() 153 | return property(get, set) 154 | 155 | def SetStringItem(self, item, col, label, super=False): 156 | wx.ListCtrl.SetStringItem(self, item, col, label) 157 | if super: 158 | return 159 | node = self.GetPyData(item) 160 | if node is None: 161 | return 162 | if col == 0: 163 | node.uri = label 164 | elif col == 1: 165 | node.comment = label 166 | 167 | def GetPyData(self, item): 168 | id = item_to_id(item) 169 | return self._data.get(id) 170 | 171 | def SetPyData(self, item, data): 172 | id = item_to_id(item) 173 | try: 174 | del self._data_map[self._data[id]] 175 | except KeyError: 176 | pass 177 | self._data[id] = data 178 | self._data_map[data] = id 179 | 180 | def _remove_all_items(self): 181 | wx.ListCtrl.DeleteAllItems(self) 182 | self._data.clear() 183 | self._data_map.clear() 184 | 185 | def _remove_item(self, node): 186 | item = self._data_map.pop(node) 187 | i = item_to_id(item) 188 | del self._data[i] 189 | self.DeleteItem(i) 190 | 191 | def _insert_item(self, node, replace_node=None): 192 | if replace_node in self._data_map: 193 | item = self._data_map[replace_node] 194 | i = item_to_id(item) 195 | else: 196 | i = self.GetItemCount() 197 | item = self.InsertStringItem(i, '') 198 | for i, s in enumerate((node.uri, node.comment)): 199 | self.SetStringItem(item, i, s, super=True) 200 | self.SetPyData(item, node) 201 | return item 202 | 203 | def _recreate_items(self): 204 | self._remove_all_items() 205 | self._nodes = [] 206 | if self.page is None: 207 | return 208 | for node in self._model.mapareas: 209 | self._insert_item(node) 210 | 211 | __all__ = ['MapAreaBrowser'] 212 | 213 | # vim:ts=4 sts=4 sw=4 et 214 | -------------------------------------------------------------------------------- /lib/gui/maparea_menu.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2009 Jakub Wilk 4 | # Copyright © 2009 Mateusz Turcza 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | import wx 18 | 19 | from djvusmooth.gui.maparea_properties import MapareaPropertiesDialog 20 | from djvusmooth.i18n import _ 21 | 22 | def show_menu(parent, annotations, node, point, origin=None): 23 | menu = wx.Menu() 24 | try: 25 | menu_item = menu.Append(wx.ID_ANY, _(u'&New hyperlink…')) 26 | parent.Bind(wx.EVT_MENU, lambda event: on_new_annotation(event, parent, annotations, origin), menu_item) 27 | if node is not None: 28 | menu_item = menu.Append(wx.ID_ANY, _(u'&Properties…')) 29 | parent.Bind(wx.EVT_MENU, lambda event: on_properties(event, parent, node), menu_item) 30 | menu_item = menu.Append(wx.ID_ANY, _('&Remove') + '\tDel') 31 | parent.Bind(wx.EVT_MENU, lambda event: on_delete(event, parent, node), menu_item) 32 | del menu_item 33 | parent.PopupMenu(menu, point) 34 | finally: 35 | menu.Destroy() 36 | 37 | def on_new_annotation(event, parent, annotations, origin): 38 | dialog = MapareaPropertiesDialog(parent, origin=origin) 39 | try: 40 | if dialog.ShowModal() != wx.ID_OK: 41 | return 42 | dialog.node.insert(annotations) 43 | finally: 44 | dialog.Destroy() 45 | 46 | def on_properties(event, parent, node): 47 | dialog = MapareaPropertiesDialog(parent, node) 48 | try: 49 | if dialog.ShowModal() != wx.ID_OK: 50 | return 51 | node.replace(dialog.node) 52 | finally: 53 | dialog.Destroy() 54 | 55 | def on_delete(event, parent, node): 56 | node.delete() 57 | 58 | # vim:ts=4 sts=4 sw=4 et 59 | -------------------------------------------------------------------------------- /lib/gui/maparea_properties.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2022 Jakub Wilk 4 | # Copyright © 2009 Mateusz Turcza 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | import wx 18 | import wx.lib.colourselect 19 | 20 | import djvu.const 21 | 22 | import djvusmooth.models.annotations 23 | from djvusmooth import models 24 | from djvusmooth.varietes import idict 25 | from djvusmooth.i18n import _ 26 | 27 | # See: 28 | # , 29 | # 30 | # for details. 31 | 32 | HTML_TARGETS = '_blank _self _parent _top'.split() 33 | 34 | class Shape(idict): 35 | def __init__(self, **kwargs): 36 | kwargs.setdefault('enabled', True) 37 | idict.__init__(self, **kwargs) 38 | 39 | SHAPE_TEXT = Shape(label=_('Text'), model_class=models.annotations.TextMapArea) 40 | SHAPE_LINE = Shape(label=_('Line'), model_class=models.annotations.LineMapArea, enabled=False) 41 | SHAPE_RECTANGLE = Shape(label=_('Rectangle'), model_class=models.annotations.RectangleMapArea) 42 | SHAPES = ( 43 | SHAPE_RECTANGLE, 44 | Shape(label=_('Oval'), model_class=models.annotations.OvalMapArea), 45 | Shape(label=_('Polygon'), model_class=models.annotations.PolygonMapArea, enabled=False), 46 | SHAPE_LINE, 47 | SHAPE_TEXT, 48 | ) 49 | 50 | SHADOW_BORDERS = ( 51 | idict(model_class=models.annotations.BorderShadowIn, label=_('Shadow in')), 52 | idict(model_class=models.annotations.BorderShadowOut, label=_('Shadow out')), 53 | idict(model_class=models.annotations.BorderEtchedIn, label=_('Etched in')), 54 | idict(model_class=models.annotations.BorderEtchedOut, label=_('Etched out')) 55 | ) 56 | 57 | def color_as_html(color): 58 | ''' 59 | >>> c = wx.Colour(6, 23, 42) 60 | >>> color_as_html(c) 61 | '#06172a' 62 | ''' 63 | return '#{0:02x}{1:02x}{2:02x}'.format(*color) 64 | 65 | class MapareaPropertiesDialog(wx.Dialog): 66 | 67 | DEFAULT_TEXT_WIDTH = 200 68 | DEFAULT_SPIN_WIDTH = 50 69 | 70 | def _setup_main_properties_box(self): 71 | node = self._node 72 | box = wx.StaticBox(self, label=_('Main properties')) 73 | box_sizer = wx.StaticBoxSizer(box) 74 | grid_sizer = wx.FlexGridSizer(0, 2, 5, 5) 75 | uri_label = wx.StaticText(self, label='URI: ') 76 | uri_edit = wx.TextCtrl(self, size=(self.DEFAULT_TEXT_WIDTH, -1)) 77 | if node is not None: 78 | uri_edit.SetValue(self._node.uri) 79 | target_label = wx.StaticText(self, label=(_('Target frame') + ': ')) 80 | target_edit = wx.ComboBox(self, 81 | size=(self.DEFAULT_TEXT_WIDTH, -1), 82 | style=wx.CB_DROPDOWN, 83 | choices=HTML_TARGETS 84 | ) 85 | if node is not None: 86 | target_edit.SetValue(self._node.target or '') 87 | comment_label = wx.StaticText(self, label=(_('Comment') + ': ')) 88 | comment_edit = wx.TextCtrl(self, size=(self.DEFAULT_TEXT_WIDTH, -1)) 89 | if node is not None: 90 | comment_edit.SetValue(self._node.comment) 91 | for widget in uri_label, uri_edit, target_label, target_edit, comment_label, comment_edit: 92 | grid_sizer.Add(widget) 93 | box_sizer.Add(grid_sizer, 0, wx.EXPAND | wx.ALL, 5) 94 | self._edit_uri = uri_edit 95 | self._edit_target = target_edit 96 | self._edit_comment = comment_edit 97 | return box_sizer 98 | 99 | def _setup_shape_box(self): 100 | box = wx.RadioBox(self, 101 | label=_('Shape'), 102 | choices=[shape.label for shape in SHAPES] 103 | ) 104 | for i, shape in enumerate(SHAPES): 105 | box.EnableItem(i, shape.enabled) 106 | self.Bind(wx.EVT_RADIOBOX, self.on_select_shape, box) 107 | self._edit_shape = box 108 | # It's too early to select proper shape. We'll do it later. 109 | return box 110 | 111 | def do_select_shape(self, shape): 112 | for _shape, items in self._specific_sizers.iteritems(): 113 | for item in items: 114 | self._sizer.Show(item, False, recursive=True) 115 | try: 116 | specific_sizers = self._specific_sizers[shape] 117 | except KeyError: 118 | pass 119 | else: 120 | for item in specific_sizers: 121 | self._sizer.Show(item, True, recursive=True) 122 | can_have_shadow_border = shape.model_class.can_have_shadow_border() 123 | for widget in self._edit_border_shadows: 124 | widget.Enable(can_have_shadow_border) 125 | self.Fit() 126 | 127 | def on_select_shape(self, event): 128 | shape = SHAPES[event.GetInt()] 129 | self.do_select_shape(shape) 130 | 131 | def enable_border_width(self, enable): 132 | for widget in self._edit_border_width, self._label_border_thickness: 133 | widget.Enable(enable) 134 | 135 | def enable_solid_border(self, enable): 136 | self._edit_border_solid_color.Enable(enable) 137 | 138 | def enable_always_visible_border(self, enable): 139 | self._edit_border_always_visible.Enable(enable) 140 | 141 | def on_select_no_border(self, event): 142 | self.enable_always_visible_border(False) 143 | self.enable_border_width(False) 144 | self.enable_solid_border(False) 145 | 146 | def on_select_solid_border(self, event): 147 | self.enable_always_visible_border(True) 148 | self.enable_border_width(False) 149 | self.enable_solid_border(True) 150 | 151 | def on_select_nonshadow_border(self, event): 152 | self.enable_always_visible_border(True) 153 | self.enable_border_width(False) 154 | self.enable_solid_border(False) 155 | 156 | def on_select_shadow_border(self, event): 157 | self.enable_always_visible_border(True) 158 | self.enable_border_width(True) 159 | self.enable_solid_border(False) 160 | 161 | def _setup_border_box(self): 162 | node = self._node 163 | try: 164 | border = node.border 165 | except AttributeError: 166 | border = None 167 | box = wx.StaticBox(self, label=_('Border')) 168 | box_sizer = wx.StaticBoxSizer(box, orient=wx.VERTICAL) 169 | box_grid_sizer = wx.FlexGridSizer(0, 3, 5, 10) 170 | border_width_sizer = wx.BoxSizer(wx.HORIZONTAL) 171 | border_width_label = wx.StaticText(self, label=(_('Width') + ': ')) 172 | border_width_edit = wx.SpinCtrl(self, size=(self.DEFAULT_SPIN_WIDTH, -1)) 173 | border_width_edit.SetRange(djvu.const.MAPAREA_SHADOW_BORDER_MIN_WIDTH, djvu.const.MAPAREA_SHADOW_BORDER_MAX_WIDTH) 174 | border_width_edit.SetValue(djvu.const.MAPAREA_SHADOW_BORDER_MIN_WIDTH) 175 | border_width_sizer.Add(border_width_label, 0, wx.ALIGN_CENTER_VERTICAL) 176 | border_width_sizer.Add(border_width_edit, 0, wx.ALIGN_CENTER_VERTICAL) 177 | radio_none = wx.RadioButton(self, label=_('None')) 178 | radio_xor = wx.RadioButton(self, label=_('XOR')) 179 | if isinstance(border, models.annotations.XorBorder): 180 | radio_xor.SetValue(True) 181 | radio_solid = wx.RadioButton(self, label=(_('Solid color') + ': ')) 182 | solid_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) 183 | if isinstance(border, models.annotations.SolidBorder): 184 | radio_solid.SetValue(True) 185 | solid_color_selector.Enable(True) 186 | solid_color_selector.SetColour(border.color) 187 | else: 188 | solid_color_selector.Enable(False) 189 | solid_sizer = wx.BoxSizer(wx.HORIZONTAL) 190 | solid_sizer.Add(radio_solid, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) 191 | solid_sizer.Add(solid_color_selector, 0, wx.ALIGN_CENTER_VERTICAL) 192 | for widget in radio_none, radio_xor, solid_sizer: 193 | box_grid_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) 194 | shadow_widgets = [] 195 | have_shadow_border = False 196 | for i, shadow_border in enumerate(SHADOW_BORDERS): 197 | if i == 2: 198 | box_grid_sizer.Add(border_width_sizer, 0, wx.ALIGN_CENTER_VERTICAL) 199 | widget = wx.RadioButton(self, label=shadow_border.label) 200 | if isinstance(border, shadow_border.model_class): 201 | widget.SetValue(True) 202 | have_shadow_border = True 203 | widget.model_class = shadow_border.model_class 204 | box_grid_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) 205 | shadow_widgets += widget, 206 | for widget in shadow_widgets: 207 | self.Bind(wx.EVT_RADIOBUTTON, self.on_select_shadow_border, widget) 208 | self.Bind(wx.EVT_RADIOBUTTON, self.on_select_nonshadow_border, radio_xor) 209 | self.Bind(wx.EVT_RADIOBUTTON, self.on_select_no_border, radio_none) 210 | self.Bind(wx.EVT_RADIOBUTTON, self.on_select_solid_border, radio_solid) 211 | avis_checkbox = wx.CheckBox(self, label=_('Always visible')) # TODO: hide it for irrelevant shapes, i.e. `line` and maybe `text` 212 | if border is None or isinstance(border, models.annotations.NoBorder): 213 | avis_checkbox.Enable(False) 214 | elif node is not None and node.border_always_visible is True: 215 | avis_checkbox.SetValue(True) 216 | box_sizer.Add(box_grid_sizer, 0, wx.EXPAND | wx.ALL, 5) 217 | box_sizer.Add(avis_checkbox, 0, wx.ALL, 5) 218 | self._edit_border_none = radio_none 219 | self._edit_border_xor = radio_xor 220 | self._edit_border_solid = radio_solid 221 | self._edit_border_solid_color = solid_color_selector 222 | self._edit_border_shadows = shadow_widgets 223 | self._edit_border_always_visible = avis_checkbox 224 | self._edit_border_width = border_width_edit 225 | self._label_border_thickness = border_width_label 226 | self.enable_border_width(have_shadow_border) 227 | if have_shadow_border: 228 | border_width_edit.SetValue(border.width) 229 | return box_sizer 230 | 231 | def _setup_extra_boxes(self): 232 | node = self._node 233 | extra_boxes = [ 234 | wx.StaticBox(self, label=label) 235 | for label in (_('Highlight color and opacity'), _('Line-specific properties'), _('Text-specific properties')) 236 | ] 237 | extra_sizers = map(wx.StaticBoxSizer, extra_boxes) 238 | highlight_specific_sizer, line_specific_sizer, text_specific_sizer = extra_sizers 239 | self._specific_sizers = { 240 | SHAPE_RECTANGLE: [highlight_specific_sizer], 241 | SHAPE_LINE: [line_specific_sizer], 242 | SHAPE_TEXT: [text_specific_sizer] 243 | } 244 | extra_grid_sizers = [wx.FlexGridSizer(0, 2, 5, 5) for i in extra_sizers] 245 | for extra_sizer, extra_grid_sizer in zip(extra_sizers, extra_grid_sizers): 246 | extra_sizer.Add(extra_grid_sizer, 0, wx.EXPAND | wx.ALL, 5) 247 | highlight_specific_sizer, line_specific_sizer, text_specific_sizer = extra_grid_sizers 248 | highlight_color_label = wx.CheckBox(self, label=(_('Highlight color') + ': ')) 249 | highlight_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) 250 | highlight_color_selector.SetColour(wx.BLUE) 251 | highlight_color_selector.Enable(False) 252 | def on_switch_highlight_color(event): 253 | highlight_color_selector.Enable(event.IsChecked()) 254 | self.Bind(wx.EVT_CHECKBOX, on_switch_highlight_color, highlight_color_label) 255 | opacity_label = wx.StaticText(self, label=(_('Opacity') + ': ')) 256 | opacity_slider = wx.Slider(self, 257 | value=djvu.const.MAPAREA_OPACITY_DEFAULT, 258 | size=(self.DEFAULT_TEXT_WIDTH, -1), 259 | style=(wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS) 260 | ) 261 | for widget in highlight_color_label, highlight_color_selector, opacity_label, opacity_slider: 262 | highlight_specific_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) 263 | if isinstance(node, models.annotations.RectangleMapArea): 264 | if node.highlight_color is None: 265 | highlight_color_label.SetValue(False) 266 | highlight_color_selector.Enable(False) 267 | else: 268 | highlight_color_label.SetValue(True) 269 | highlight_color_selector.Enable(True) 270 | highlight_color_selector.SetColour(node.highlight_color) 271 | opacity_slider.SetValue(node.opacity) 272 | line_width_label = wx.StaticText(self, label=(_('Line width') + ': ')) 273 | line_width_edit = wx.SpinCtrl(self, size=(self.DEFAULT_SPIN_WIDTH, -1)) 274 | line_width_edit.SetRange(djvu.const.MAPAREA_LINE_MIN_WIDTH, 99) 275 | line_width_edit.SetValue(djvu.const.MAPAREA_LINE_MIN_WIDTH) 276 | line_color_label = wx.StaticText(self, label=(_('Line color') + ': ')) 277 | line_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) 278 | line_arrow_checkbox = wx.CheckBox(self, label=_('Arrow')) 279 | dummy = (0, 0) 280 | for widget in line_arrow_checkbox, dummy, line_width_label, line_width_edit, line_color_label, line_color_selector: 281 | line_specific_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) 282 | if isinstance(node, models.annotations.LineMapArea): 283 | line_width_edit.SetValue(node.line_width) 284 | line_color_selector.SetColour(node.line_color) 285 | line_arrow_checkbox.SetValue(node.line_arrow) 286 | else: 287 | line_color_selector.SetColour(djvu.const.MAPAREA_LINE_COLOR_DEFAULT) 288 | text_background_color_label = wx.CheckBox(self, label=(_('Background color') + ': ')) 289 | text_background_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) 290 | text_background_color_selector.SetColour(wx.WHITE) 291 | text_background_color_selector.Enable(False) 292 | def on_switch_text_background_color(event): 293 | text_background_color_selector.Enable(event.IsChecked()) 294 | self.Bind(wx.EVT_CHECKBOX, on_switch_text_background_color, text_background_color_label) 295 | text_color_label = wx.StaticText(self, label=(_('Text color') + ': ')) 296 | text_color_selector = wx.lib.colourselect.ColourSelect(self, wx.ID_ANY) 297 | text_pushpin = wx.CheckBox(self, label=_('Pushpin')) 298 | for widget in text_background_color_label, text_background_color_selector, text_color_label, text_color_selector, text_pushpin: 299 | text_specific_sizer.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL) 300 | if isinstance(node, models.annotations.TextMapArea): 301 | if node.background_color is not None: 302 | text_background_color_label.SetValue(True) 303 | text_background_color_selector.Enable(True) 304 | text_background_color_selector.SetColour(node.background_color) 305 | else: 306 | text_background_color_label.SetValue(False) 307 | text_background_color_selector.Enable(False) 308 | text_color_selector.SetColour(node.text_color) 309 | text_pushpin.SetValue(node.pushpin) 310 | else: 311 | text_color_selector.SetColour(djvu.const.MAPAREA_LINE_COLOR_DEFAULT) 312 | self._edit_have_highlight = highlight_color_label 313 | self._edit_highlight_color = highlight_color_selector 314 | self._edit_opacity = opacity_slider 315 | self._edit_line_width = line_width_edit 316 | self._edit_line_color = line_color_selector 317 | self._edit_arrow = line_arrow_checkbox 318 | self._edit_background_nontrasparent = text_background_color_label 319 | self._edit_background_color = text_background_color_selector 320 | self._edit_text_color = text_color_selector 321 | self._edit_pushpin = text_pushpin 322 | return extra_sizers 323 | 324 | def __init__(self, parent, node=None, origin=None): 325 | wx.Dialog.__init__(self, parent, title=_('Overprinted annotation (hyperlink) properties')) 326 | self._node = node 327 | if origin is None: 328 | self._origin = None 329 | else: 330 | self._origin = tuple(origin) 331 | if len(self._origin) != 2: 332 | raise ValueError 333 | sizer = wx.BoxSizer(wx.VERTICAL) 334 | main_properties_box_sizer = self._setup_main_properties_box() 335 | shape_box_sizer = self._setup_shape_box() 336 | border_box_sizer = self._setup_border_box() 337 | extra_sizers = self._setup_extra_boxes() 338 | for box_sizer in [main_properties_box_sizer, shape_box_sizer, border_box_sizer] + extra_sizers: 339 | sizer.Add(box_sizer, 0, wx.EXPAND | wx.ALL, 5) 340 | line = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL) 341 | sizer.Add(line, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5) 342 | button_sizer = wx.StdDialogButtonSizer() 343 | button = wx.Button(self, wx.ID_OK) 344 | button.SetDefault() 345 | button_sizer.AddButton(button) 346 | button = wx.Button(self, wx.ID_CANCEL) 347 | button_sizer.AddButton(button) 348 | button_sizer.Realize() 349 | sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 5) 350 | self.SetSizer(sizer) 351 | self.Fit() 352 | self._sizer = sizer 353 | if self._node is None: 354 | i, shape = 0, SHAPE_RECTANGLE 355 | else: 356 | for i, shape in enumerate(SHAPES): 357 | if isinstance(node, shape.model_class): 358 | break 359 | else: 360 | raise TypeError 361 | self._edit_shape.SetSelection(i) 362 | self.do_select_shape(shape) 363 | 364 | def get_node(self): 365 | shape = SHAPES[self._edit_shape.GetSelection()] 366 | model_class = shape.model_class 367 | node = model_class.from_maparea(self._node, owner=None) 368 | node.uri = self._edit_uri.GetValue() 369 | node.target = self._edit_target.GetValue() or None 370 | node.comment = self._edit_comment.GetValue() 371 | if self._edit_border_none.GetValue(): 372 | node.border = models.annotations.NoBorder() 373 | elif self._edit_border_xor.GetValue(): 374 | node.border = models.annotations.XorBorder() 375 | elif self._edit_border_solid.GetValue(): 376 | color = color_as_html(self._edit_border_solid_color.GetColour()) 377 | node.border = models.annotations.SolidBorder(color) 378 | elif model_class.can_have_shadow_border(): 379 | for widget in self._edit_border_shadows: 380 | if widget.GetValue(): 381 | node.border = widget.model_class(self._edit_border_width.GetValue()) 382 | break 383 | node.border_always_visible = ( 384 | self._edit_border_always_visible.IsEnabled() and 385 | self._edit_border_always_visible.GetValue() 386 | ) 387 | if isinstance(node, models.annotations.RectangleMapArea): 388 | if self._edit_have_highlight.GetValue(): 389 | node.highlight_color = color_as_html(self._edit_highlight_color.GetColour()) 390 | else: 391 | node.highlight_color = None 392 | node.opacity = self._edit_opacity.GetValue() 393 | elif isinstance(node, models.annotations.LineMapArea): 394 | node.line_width = self._edit_line_width.GetValue() 395 | node.line_color = color_as_html(self._edit_line_color.GetColour()) 396 | node.line_arrow = self._edit_arrow.GetValue() 397 | elif isinstance(node, models.annotations.TextMapArea): 398 | if self._edit_background_nontrasparent.GetValue(): 399 | node.background_color = color_as_html(self._edit_background_color.GetColour()) 400 | else: 401 | node.background_color = None 402 | node.text_color = color_as_html(self._edit_text_color.GetColour()) 403 | node.pushpin = self._edit_pushpin.GetValue() 404 | if self._origin: 405 | node.origin = self._origin 406 | return node 407 | 408 | node = property(get_node) 409 | 410 | __all__ = ['MapareaPropertiesDialog'] 411 | 412 | # vim:ts=4 sts=4 sw=4 et 413 | -------------------------------------------------------------------------------- /lib/gui/metadata.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # Copyright © 2009 Mateusz Turcza 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | import wx 18 | import wx.grid 19 | import wx.lib.mixins.grid 20 | 21 | import djvu.sexpr 22 | 23 | from djvusmooth.i18n import _ 24 | 25 | LABELS = [_('key'), _('value')] 26 | 27 | class MetadataTable(wx.grid.PyGridTableBase): 28 | def __init__(self, model, known_keys): 29 | wx.grid.PyGridTableBase.__init__(self) 30 | self._model = model 31 | self._keys = sorted(model) 32 | self._keys.append(None) 33 | attr_normal = wx.grid.GridCellAttr() 34 | attr_known = wx.grid.GridCellAttr() 35 | font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) 36 | font.SetWeight(wx.FONTWEIGHT_BOLD) 37 | attr_known.SetFont(font) 38 | self._attrs = attr_normal, attr_known 39 | self._known_keys = known_keys 40 | 41 | def GetAttr(self, y, x, kind): 42 | key = self._keys[y] 43 | attr = self._attrs[x == 0 and key in self._known_keys] 44 | attr.IncRef() 45 | return attr 46 | 47 | def GetColLabelValue(self, n): 48 | return LABELS[n] 49 | 50 | def GetNumberRows(self): 51 | return len(self._keys) 52 | 53 | def GetNumberCols(self): 54 | return 2 55 | 56 | def GetValue(self, y, x): 57 | try: 58 | key = self._keys[y] 59 | if key is None: 60 | value = '' 61 | elif x: 62 | value = self._model[key] 63 | else: 64 | value = key 65 | except IndexError: 66 | value = '' 67 | return value 68 | 69 | def set_value(self, key, value): 70 | self._model[key] = value 71 | 72 | def set_new_key(self, y, new_key, value): 73 | assert isinstance(new_key, djvu.sexpr.Symbol) 74 | del self._model[self._keys[y]] 75 | self._model[new_key] = value 76 | self._keys[y] = new_key 77 | 78 | def add_new_key(self, new_key): 79 | assert isinstance(new_key, djvu.sexpr.Symbol) 80 | self._model[new_key] = '' 81 | y = len(self._keys) - 1 82 | self._keys[y:] = new_key, None 83 | self.GetView().ProcessTableMessage(wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)) 84 | 85 | def delete_key(self, y): 86 | key = self._keys[y] 87 | assert isinstance(key, djvu.sexpr.Symbol) 88 | del self._model[key] 89 | del self._keys[y] 90 | self.GetView().ProcessTableMessage(wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, y, 1)) 91 | 92 | def SetValue(self, y, x, value): 93 | key = self._keys[y] 94 | if x == 0: 95 | if value == key: 96 | pass 97 | elif not value: 98 | self.delete_key(y) 99 | # Delete a row 100 | elif value in self._model: 101 | pass # TODO: raise an exception 102 | else: 103 | value = djvu.sexpr.Symbol(value.encode('UTF-8')) 104 | if key is None: 105 | # Add a row 106 | self.add_new_key(value) 107 | else: 108 | self.set_new_key(y, value, self._model[key]) 109 | elif x == 1: 110 | self.set_value(key, value) 111 | 112 | class MetadataGrid(wx.grid.Grid, wx.lib.mixins.grid.GridAutoEditMixin): 113 | def __init__(self, parent, model, known_keys): 114 | wx.grid.Grid.__init__(self, parent) 115 | table = MetadataTable(model, known_keys) 116 | self.SetTable(table) 117 | self.SetRowLabelSize(0) 118 | self.SetDefaultEditor(wx.grid.GridCellAutoWrapStringEditor()) 119 | self.AutoSize() 120 | 121 | class MetadataDialog(wx.Dialog): 122 | 123 | def __init__(self, parent, models, known_keys): 124 | wx.Dialog.__init__(self, parent, title=_('Edit metadata'), style=(wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)) 125 | sizer = wx.BoxSizer(wx.VERTICAL) 126 | tabs = wx.Notebook(self, -1) 127 | for model in models: 128 | grid = MetadataGrid(tabs, model, known_keys) 129 | tabs.AddPage(grid, model.title) 130 | sizer.Add(tabs, 1, wx.EXPAND | wx.ALL, 5) 131 | line = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL) 132 | sizer.Add(line, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5) 133 | button_sizer = wx.StdDialogButtonSizer() 134 | button = wx.Button(self, wx.ID_OK) 135 | button.SetDefault() 136 | button_sizer.AddButton(button) 137 | button = wx.Button(self, wx.ID_CANCEL) 138 | button_sizer.AddButton(button) 139 | button_sizer.Realize() 140 | sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 5) 141 | self.SetSizerAndFit(sizer) 142 | 143 | __all__ = ['MetadataDialog'] 144 | 145 | # vim:ts=4 sts=4 sw=4 et 146 | -------------------------------------------------------------------------------- /lib/gui/outline_browser.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # Copyright © 2009 Mateusz Turcza 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | import wx 18 | 19 | import djvusmooth.models.outline 20 | from djvusmooth import models 21 | from djvusmooth.varietes import replace_control_characters 22 | from djvusmooth.i18n import _ 23 | from djvusmooth.gui import wxcompat 24 | 25 | def get_label_for_node(node): 26 | return replace_control_characters(' ', node.text) 27 | 28 | class OutlineCallback(models.outline.OutlineCallback): 29 | 30 | def __init__(self, browser): 31 | self._browser = browser 32 | 33 | def notify_node_change(self, node): 34 | wx.CallAfter(lambda: self._browser.on_node_change(node)) 35 | 36 | def notify_node_select(self, node): 37 | wx.CallAfter(lambda: self._browser.on_node_select(node)) 38 | 39 | def notify_node_children_change(self, node): 40 | wx.CallAfter(lambda: self._browser.on_node_children_change(node)) 41 | 42 | def notify_tree_change(self, node): 43 | wx.CallAfter(lambda: self._browser.on_tree_change(node)) 44 | 45 | class OutlineBrowser(wx.TreeCtrl): 46 | 47 | def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=(wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS)): 48 | wx.TreeCtrl.__init__(self, parent, id, pos, size, style) 49 | self._items = {} 50 | self._root_item = None 51 | self._document = None 52 | self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.on_begin_edit, self) 53 | self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.on_end_edit, self) 54 | self.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_selection_changed, self) 55 | self.Bind(wx.EVT_CHAR, self.on_char) 56 | self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) 57 | self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.on_begin_drag) 58 | self.Bind(wx.EVT_TREE_END_DRAG, self.on_end_drag) 59 | 60 | def on_begin_drag(self, event): 61 | drag_item = event.GetItem() 62 | if drag_item == self._root_item: 63 | event.Veto() 64 | return 65 | self._drag_item = drag_item 66 | node = self.GetPyData(drag_item) 67 | if node is not None: 68 | event.Allow() 69 | 70 | def on_end_drag(self, event): 71 | source = self._drag_item 72 | del self._drag_item 73 | target = event.GetItem() 74 | if not target.IsOk(): 75 | return 76 | if source == target: 77 | return 78 | source_node = self.GetPyData(source) 79 | if source_node is None: 80 | return 81 | target_node = self.GetPyData(target) 82 | if target_node is None: 83 | return 84 | 85 | target_ancestor = target_node 86 | while True: 87 | if target_ancestor is source_node: 88 | # Cannot move a node to its child 89 | return 90 | try: 91 | target_ancestor = target_ancestor.parent 92 | except StopIteration: 93 | break 94 | source_node.delete() 95 | target_node.add_child(source_node) 96 | source_node.notify_select() 97 | 98 | def do_goto_node(self, node): 99 | if isinstance(node, models.outline.RootNode): 100 | return 101 | uri = node.uri 102 | if uri.startswith('#'): 103 | try: 104 | n = int(buffer(uri, 1)) 105 | except ValueError: 106 | return # TODO: try to handle non-local URIs 107 | parent = wx.GetTopLevelParent(self) 108 | parent.page_no = n - 1 109 | else: 110 | return # TODO: try to handle non-local URIs 111 | 112 | def do_delete_node(self, node): 113 | try: 114 | node.delete() 115 | except NotImplementedError: 116 | return 117 | 118 | _WXK_TO_METHOD = { 119 | wx.WXK_RETURN: do_goto_node, 120 | wx.WXK_DELETE: do_delete_node 121 | } 122 | 123 | def on_char(self, event): 124 | key_code = event.GetKeyCode() 125 | try: 126 | method = self._WXK_TO_METHOD[key_code] 127 | except KeyError: 128 | event.Skip() 129 | return 130 | item = self.GetSelection() 131 | node = self.GetPyData(item) 132 | if node is None: 133 | return 134 | method(self, node) 135 | 136 | def on_key_down(self, event): 137 | return wxcompat.on_key_down(self, event) 138 | 139 | def on_node_select(self, node): 140 | try: 141 | item = self._items[node] 142 | except KeyError: 143 | return 144 | if self.GetSelection() != item: 145 | self.SelectItem(item) 146 | 147 | def on_node_change(self, node): 148 | try: 149 | item = self._items[node] 150 | except KeyError: 151 | return 152 | self.SetItemText(item, get_label_for_node(node)) 153 | 154 | def on_tree_change(self, model_node): 155 | self.document = True 156 | 157 | def on_node_children_change(self, node): 158 | if self._root_item is None: 159 | self._create_root_item() 160 | try: 161 | item = self._items[node] 162 | except KeyError: 163 | if isinstance(node, models.outline.RootNode): 164 | item = self.GetRootItem() 165 | else: 166 | return 167 | self.DeleteChildren(item) 168 | self._add_children(item, node) 169 | 170 | @apply 171 | def document(): 172 | def get(self): 173 | return self._document 174 | def set(self, value): 175 | if value is not True: 176 | self._document = value 177 | if self._document is not None: 178 | self._callback = OutlineCallback(self) 179 | self._document.outline.register_callback(self._callback) 180 | self._recreate_children() 181 | return property(get, set) 182 | 183 | def on_selection_changed(self, event): 184 | event.Skip() 185 | item = event.GetItem() 186 | if not item: 187 | return 188 | node = self.GetPyData(item) 189 | if node is None: 190 | return 191 | node.notify_select() 192 | 193 | def on_begin_edit(self, event): 194 | item = event.GetItem() 195 | if not self.do_begin_edit(item): 196 | event.Veto() 197 | 198 | def do_begin_edit(self, item): 199 | if item == self._root_item: 200 | return 201 | node = self.GetPyData(item) 202 | if node is None: 203 | return 204 | self.SetItemText(item, node.text) 205 | return True 206 | 207 | def on_end_edit(self, event): 208 | item = event.GetItem() 209 | if event.IsEditCancelled(): 210 | new_text = None 211 | else: 212 | new_text = event.GetLabel() 213 | if not self.do_end_edit(item, new_text): 214 | event.Veto() 215 | 216 | def do_end_edit(self, item, text): 217 | node = self.GetPyData(item) 218 | if node is None: 219 | return 220 | if text is None: 221 | text = node.text 222 | node.text = text 223 | return True 224 | 225 | def DeleteChildren(self, item): 226 | child = self.GetFirstChild(item)[0] 227 | while child: 228 | next_child = self.GetNextSibling(child) 229 | self.Delete(child) 230 | child = next_child 231 | 232 | def Delete(self, item): 233 | self.DeleteChildren(item) 234 | node = self.GetPyData(item) 235 | self._items.pop(node, None) 236 | wx.TreeCtrl.Delete(self, item) 237 | 238 | def DeleteAllItems(self): 239 | wx.TreeCtrl.DeleteAllItems(self) 240 | self._items.clear() 241 | 242 | def _add_children(self, item, nodes): 243 | for node in nodes: 244 | symbol = node.type 245 | label = get_label_for_node(node) 246 | child_item = self.AppendItem(item, label) 247 | self._add_children(child_item, node) 248 | self._items[node] = child_item 249 | self.SetPyData(child_item, node) 250 | 251 | def _create_root_item(self): 252 | node = self.document.outline.root 253 | if node: 254 | type = str(node.type) 255 | self._root_item = self.AddRoot(_(type)) 256 | self.SetPyData(self._root_item, node) 257 | return True 258 | else: 259 | self._root_item = None 260 | return False 261 | 262 | def _recreate_children(self): 263 | self.DeleteAllItems() 264 | assert not self._items 265 | self._root_item = None 266 | if self.document is None: 267 | return 268 | if self._create_root_item(): 269 | self._add_children(self._root_item, self.document.outline.root) 270 | 271 | __all__ = ['OutlineBrowser'] 272 | 273 | # vim:ts=4 sts=4 sw=4 et 274 | -------------------------------------------------------------------------------- /lib/gui/text_browser.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # Copyright © 2009 Mateusz Turcza 5 | # 6 | # This file is part of djvusmooth. 7 | # 8 | # djvusmooth is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License version 2 as published 10 | # by the Free Software Foundation. 11 | # 12 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 | # more details. 16 | 17 | import wx 18 | 19 | import djvusmooth.models.text 20 | from djvusmooth import models 21 | from djvusmooth.varietes import replace_control_characters 22 | from djvusmooth.i18n import _ 23 | from djvusmooth.gui import wxcompat 24 | 25 | def get_label_for_node(node): 26 | zone_type = str(node.type) 27 | if node.is_inner(): 28 | return _(zone_type) 29 | else: 30 | return _(zone_type) + ': ' + replace_control_characters(' ', node.text) 31 | 32 | class PageTextCallback(models.text.PageTextCallback): 33 | 34 | def __init__(self, browser): 35 | self._browser = browser 36 | 37 | def notify_node_change(self, node): 38 | wx.CallAfter(lambda: self._browser.on_node_change(node)) 39 | 40 | def notify_node_children_change(self, node): 41 | wx.CallAfter(lambda: self._browser.on_tree_change(node)) 42 | # FIXME: consider something lighter here 43 | 44 | def notify_tree_change(self, node): 45 | wx.CallAfter(lambda: self._browser.on_tree_change(node)) 46 | 47 | def notify_node_deselect(self, node): 48 | pass 49 | 50 | def notify_node_select(self, node): 51 | wx.CallAfter(lambda: self._browser.on_node_select(node)) 52 | 53 | class TextBrowser(wx.TreeCtrl): 54 | 55 | def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=(wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS)): 56 | wx.TreeCtrl.__init__(self, parent, id, pos, size, style) 57 | self._have_root = False 58 | self.page = None 59 | self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.on_begin_edit, self) 60 | self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.on_end_edit, self) 61 | self.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_selection_changed, self) 62 | self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) 63 | 64 | def on_key_down(self, event): 65 | return wxcompat.on_key_down(self, event) 66 | 67 | def on_node_change(self, node): 68 | try: 69 | item = self._items[node] 70 | except KeyError: 71 | return 72 | if node.is_inner(): 73 | return 74 | self.SetItemText(item, get_label_for_node(node)) 75 | 76 | def on_node_select(self, node): 77 | try: 78 | item = self._items[node] 79 | except KeyError: 80 | return 81 | if self.GetSelection() != item: 82 | self.SelectItem(item) 83 | 84 | def on_tree_change(self, model_node): 85 | self.page = True 86 | 87 | @apply 88 | def page(): 89 | def get(self): 90 | return self._page 91 | def set(self, value): 92 | if value is not True: 93 | self._page = value 94 | if self._page is not None: 95 | self._callback = PageTextCallback(self) 96 | self._page.text.register_callback(self._callback) 97 | self._recreate_children() 98 | return property(get, set) 99 | 100 | def on_selection_changed(self, event): 101 | event.Skip() 102 | item = event.GetItem() 103 | if not item: 104 | return 105 | node = self.GetPyData(item) 106 | if node is None: 107 | return 108 | node.notify_select() 109 | 110 | def on_begin_edit(self, event): 111 | item = event.GetItem() 112 | if not self.do_begin_edit(item): 113 | event.Veto() 114 | 115 | def do_begin_edit(self, item): 116 | node = self.GetPyData(item) 117 | if node is None: 118 | return 119 | if node.is_leaf(): 120 | self.SetItemText(item, node.text) 121 | return True 122 | 123 | def on_end_edit(self, event): 124 | item = event.GetItem() 125 | if event.IsEditCancelled(): 126 | new_text = None 127 | else: 128 | new_text = event.GetLabel() 129 | if not self.do_end_edit(item, new_text): 130 | event.Veto() 131 | 132 | def do_end_edit(self, item, text): 133 | node = self.GetPyData(item) 134 | if node is None: 135 | return 136 | if text is None: 137 | text = node.text 138 | node.text = text 139 | return True 140 | 141 | def _add_children(self, item, parent_node): 142 | if not parent_node.is_inner(): 143 | return 144 | for node in parent_node: 145 | symbol = node.type 146 | label = get_label_for_node(node) 147 | child_item = self.AppendItem(item, label) 148 | self._add_children(child_item, node) 149 | self._items[node] = child_item 150 | self.SetPyData(child_item, node) 151 | 152 | def _recreate_children(self): 153 | self._items = {} 154 | root = self.GetRootItem() 155 | if root.IsOk(): 156 | self.Delete(root) 157 | if self.page is None: 158 | return 159 | node = self.page.text.root 160 | if node is not None: 161 | label = get_label_for_node(node) 162 | root = self.AddRoot(label) 163 | self._items[node] = root 164 | self.SetPyData(root, node) 165 | self._have_root = True 166 | self._add_children(root, node) 167 | 168 | __all__ = ['TextBrowser'] 169 | 170 | # vim:ts=4 sts=4 sw=4 et 171 | -------------------------------------------------------------------------------- /lib/gui/wxcompat.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2014-2015 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | ''' 17 | work-arounds for wxPython 3.0 quirks 18 | ''' 19 | 20 | import wx 21 | 22 | # In wxPython 3.0, if the focus is on a wxTreeCtrl or a wxListCtrl, menu 23 | # events for at last Page Up, Page Down, Ctrl+Home and Ctrl+End are getting 24 | # lost. This module implements wxEVT_KEY_DOWN handler to work-around this 25 | # problem. 26 | # 27 | # https://bugs.debian.org/758950#26 28 | 29 | def iter_menu(menu): 30 | for item in menu.MenuItems: 31 | submenu = item.SubMenu 32 | if submenu is None: 33 | yield item 34 | else: 35 | for item in iter_menu(submenu): 36 | yield item 37 | 38 | def on_key_down(ctrl, event): 39 | window = wx.GetTopLevelParent(ctrl) 40 | menubar = window.MenuBar 41 | for menu, label in menubar.Menus: 42 | for item in iter_menu(menu): 43 | if item.Accel is None: 44 | continue 45 | if item.Accel.Flags != event.Modifiers: 46 | continue 47 | if item.Accel.KeyCode != event.KeyCode: 48 | continue 49 | break 50 | else: 51 | item = None 52 | if item is not None: 53 | break 54 | if item is None: 55 | event.Skip() 56 | else: 57 | evt = wx.CommandEvent(wx.EVT_MENU.typeId, item.Id) 58 | evt.SetEventObject(window) 59 | wx.PostEvent(window, evt) 60 | 61 | if wx.VERSION < (3, 0): 62 | del on_key_down # quieten pyflakes 63 | def on_key_down(ctrl, event): 64 | event.Skip() 65 | 66 | # vim:ts=4 sts=4 sw=4 et 67 | -------------------------------------------------------------------------------- /lib/i18n.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2009 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | import gettext 17 | import os 18 | import sys 19 | 20 | for dir in os.path.join(os.path.dirname(sys.argv[0]), 'locale'), None: 21 | try: 22 | _ = gettext.translation('djvusmooth', dir).ugettext 23 | break 24 | except IOError: 25 | pass 26 | else: 27 | def _(s): 28 | return s 29 | del dir 30 | 31 | # Some dummy translations: 32 | if False: 33 | _('page') 34 | _('column') 35 | _('region') 36 | _('para') 37 | _('line') 38 | _('word') 39 | _('char') 40 | _('bookmarks') 41 | 42 | # vim:ts=4 sts=4 sw=4 et 43 | -------------------------------------------------------------------------------- /lib/ipc.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2010-2019 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | '''interprocess communication''' 17 | 18 | import os 19 | import signal 20 | 21 | if os.name == 'posix': 22 | import subprocess32 as subprocess 23 | else: 24 | import subprocess 25 | 26 | Subprocess = subprocess.Popen 27 | PIPE = subprocess.PIPE 28 | 29 | # Protect from scanadf and possibly other software that sets SIGCHLD to 30 | # SIG_IGN. 31 | # https://bugs.debian.org/596232 32 | if os.name == 'posix': 33 | signal.signal(signal.SIGCHLD, signal.SIG_DFL) 34 | 35 | __all__ = ['Subprocess', 'PIPE'] 36 | 37 | # vim:ts=4 sts=4 sw=4 et 38 | -------------------------------------------------------------------------------- /lib/models/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | SHARED_ANNOTATIONS_PAGENO = -1 17 | 18 | class MultiPageModel(object): 19 | 20 | def get_page_model_class(self, n): 21 | raise NotImplementedError 22 | 23 | def __init__(self): 24 | self._pages = {} 25 | 26 | def __getitem__(self, n): 27 | if n not in self._pages: 28 | cls = self.get_page_model_class(n) 29 | self._pages[n] = cls(n, self.acquire_data(n)) 30 | return self._pages[n] 31 | 32 | def __setitem__(self, n, model): 33 | self._pages[n] = model 34 | 35 | def acquire_data(self, n): 36 | return {} 37 | 38 | def export(self, djvused): 39 | for id in sorted(self._pages): 40 | self._pages[id].export(djvused) 41 | 42 | __all__ = ['MultiPageModel', 'SHARED_ANNOTATIONS_PAGENO'] 43 | 44 | # vim:ts=4 sts=4 sw=4 et 45 | -------------------------------------------------------------------------------- /lib/models/metadata.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | 17 | ''' 18 | Models for metadata. 19 | 20 | See ``djvuchanges.txt``: 21 | - 4. Metadata Annotations. 22 | - 5. Document Annotations and Metadata. 23 | ''' 24 | 25 | from djvusmooth.models import MultiPageModel, SHARED_ANNOTATIONS_PAGENO 26 | 27 | class Metadata(MultiPageModel): 28 | 29 | def get_page_model_class(self, n): 30 | if n == SHARED_ANNOTATIONS_PAGENO: 31 | return SharedMetadata 32 | else: 33 | return PageMetadata 34 | 35 | class PageMetadata(dict): 36 | 37 | def __init__(self, n, original_data): 38 | self._old_data = None 39 | self._dirty = False 40 | self._n = n 41 | self.load(original_data, overwrite=True) 42 | 43 | def __setitem__(self, key, value): 44 | self._dirty = True 45 | dict.__setitem__(self, key, value) 46 | 47 | def clone(self): 48 | from copy import copy 49 | return copy(self) 50 | 51 | def load(self, original_data, overwrite=False): 52 | if self._old_data is not None or overwrite: 53 | self._old_data = dict(original_data) 54 | self.revert() 55 | 56 | def export_select(self, djvused): 57 | djvused.select(self._n + 1) 58 | 59 | def export(self, djvused): 60 | if not self._dirty: 61 | return 62 | self.export_select(djvused) 63 | djvused.set_metadata(self) 64 | 65 | def revert(self, key=None): 66 | if key is None: 67 | self.clear() 68 | self.update(self._old_data) 69 | self._dirty = False 70 | else: 71 | try: 72 | self[key] = self._old_data[key] 73 | except KeyError: 74 | del self[key] 75 | 76 | def is_dirty(self, key=None): 77 | if key is None: 78 | return self._dirty 79 | new_value = self[key] 80 | try: 81 | return self._old_data[key] == new_value 82 | except KeyError: 83 | return True 84 | 85 | class SharedMetadata(PageMetadata): 86 | 87 | def export_select(self, djvused): 88 | djvused.create_shared_annotations() 89 | 90 | __all__ = ['Metadata', 'PageMetadata', 'SharedMetadata'] 91 | 92 | # vim:ts=4 sts=4 sw=4 et 93 | -------------------------------------------------------------------------------- /lib/models/outline.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2012 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | ''' 17 | Models for document outline. 18 | 19 | See Lizardtech DjVu Reference (DjVu 3): 20 | - 4 What's new in DjVu File Format. 21 | - 8.3.3 Document Outline Chunk. 22 | ''' 23 | 24 | import weakref 25 | import itertools 26 | 27 | import djvu.sexpr 28 | import djvu.const 29 | 30 | from djvusmooth.varietes import not_overridden, wref, fix_uri, indents_to_tree 31 | 32 | class Node(object): 33 | 34 | def __init__(self, sexpr, owner): 35 | self._owner = owner 36 | self._type = None 37 | self._link_left = self._link_right = self._link_parent = wref(None) 38 | 39 | def _set_children(self, children): 40 | self._children = list(children) 41 | prev = None 42 | for child in self._children: 43 | child._link_left = wref(prev) 44 | child._link_parent = wref(self) 45 | prev = child 46 | prev = None 47 | for child in reversed(self._children): 48 | child._link_right = wref(prev) 49 | prev = child 50 | 51 | def add_child(self, node): 52 | self._children 53 | if self._children: 54 | self._children[-1]._link_ref = wref(node) 55 | node._link_right = wref(self._children[-1]) 56 | node._link_parent = wref(self) 57 | self._children += node, 58 | self._notify_children_change() 59 | 60 | def remove_child(self, child): 61 | child_idx = self._children.index(child) 62 | if child_idx - 1 >= 0: 63 | self._children[child_idx - 1]._link_right = child._link_right 64 | if child_idx + 1 < len(self._children): 65 | self._children[child_idx + 1]._link_left = child._link_left 66 | child._link_left = child._link_right = child._link_parent = wref(None) 67 | del self._children[child_idx] 68 | self._notify_children_change() 69 | 70 | uri = property() 71 | text = property() 72 | 73 | @property 74 | def sexpr(self): 75 | return self._construct_sexpr() 76 | 77 | def _construct_sexpr(self): 78 | raise NotImplementedError 79 | 80 | @property 81 | def type(self): 82 | return self._type 83 | 84 | def __getitem__(self, item): 85 | return self._children[item] 86 | 87 | def __len__(self): 88 | return len(self._children) 89 | 90 | def __iter__(self): 91 | return iter(self._children) 92 | 93 | @apply 94 | def left_sibling(): 95 | def get(self): 96 | link = self._link_left() 97 | if link is None: 98 | raise StopIteration 99 | return link 100 | return property(get) 101 | 102 | @apply 103 | def right_sibling(): 104 | def get(self): 105 | link = self._link_right() 106 | if link is None: 107 | raise StopIteration 108 | return link 109 | return property(get) 110 | 111 | @apply 112 | def parent(): 113 | def get(self): 114 | link = self._link_parent() 115 | if link is None: 116 | raise StopIteration 117 | return link 118 | return property(get) 119 | 120 | @apply 121 | def left_child(): 122 | def get(self): 123 | return iter(self).next() 124 | return property(get) 125 | 126 | def delete(self): 127 | raise NotImplementedError 128 | 129 | def notify_select(self): 130 | self._owner.notify_node_select(self) 131 | 132 | def _notify_children_change(self): 133 | return self._owner.notify_node_children_change(self) 134 | 135 | class RootNode(Node): 136 | 137 | def __init__(self, sexpr, owner): 138 | Node.__init__(self, sexpr, owner) 139 | sexpr = iter(sexpr) 140 | self._type = sexpr.next().value 141 | self._set_children(InnerNode(subexpr, owner) for subexpr in sexpr) 142 | 143 | def _construct_sexpr(self): 144 | return djvu.sexpr.Expression(itertools.chain( 145 | (self.type,), 146 | (child._construct_sexpr() for child in self._children) 147 | )) 148 | 149 | def export_as_plaintext(self, stream): 150 | for child in self: 151 | child.export_as_plaintext(stream, indent=0) 152 | 153 | class InnerNode(Node): 154 | 155 | def __init__(self, sexpr, owner): 156 | Node.__init__(self, sexpr, owner) 157 | sexpr = iter(sexpr) 158 | self._text = sexpr.next().value.decode('UTF-8', 'replace') 159 | self._uri = fix_uri(sexpr.next().value) 160 | self._set_children(InnerNode(subexpr, owner) for subexpr in sexpr) 161 | 162 | def _construct_sexpr(self): 163 | return djvu.sexpr.Expression(itertools.chain( 164 | (self.text, self.uri), 165 | (child._construct_sexpr() for child in self._children) 166 | )) 167 | 168 | @apply 169 | def uri(): 170 | def get(self): 171 | return self._uri 172 | def set(self, value): 173 | self._uri = value 174 | self._notify_change() 175 | return property(get, set) 176 | 177 | @apply 178 | def text(): 179 | def get(self): 180 | return self._text 181 | def set(self, value): 182 | self._text = value 183 | self._notify_change() 184 | return property(get, set) 185 | 186 | def _notify_change(self): 187 | self._owner.notify_node_change(self) 188 | 189 | def export_as_plaintext(self, stream, indent): 190 | stream.write(' ' * indent) 191 | stream.write(self.uri) 192 | stream.write(' ') 193 | stream.write(self.text.encode('UTF-8')) # TODO: what about control characters etc.? 194 | stream.write('\n') 195 | for child in self: 196 | child.export_as_plaintext(stream, indent=(indent + 1)) 197 | 198 | def delete(self): 199 | try: 200 | parent = self.parent 201 | except StopIteration: 202 | return 203 | parent.remove_child(self) 204 | 205 | class OutlineCallback(object): 206 | 207 | @not_overridden 208 | def notify_tree_change(self, node): 209 | pass 210 | 211 | @not_overridden 212 | def notify_node_change(self, node): 213 | pass 214 | 215 | @not_overridden 216 | def notify_node_children_change(self, node): 217 | pass 218 | 219 | @not_overridden 220 | def notify_node_select(self, node): 221 | pass 222 | 223 | class Outline(object): 224 | 225 | def __init__(self): 226 | self._callbacks = weakref.WeakKeyDictionary() 227 | self._original_sexpr = self.acquire_data() 228 | self.revert() 229 | 230 | def register_callback(self, callback): 231 | if not isinstance(callback, OutlineCallback): 232 | raise TypeError 233 | self._callbacks[callback] = 1 234 | 235 | @apply 236 | def root(): 237 | def get(self): 238 | return self._root 239 | return property(get) 240 | 241 | @apply 242 | def raw_value(): 243 | def get(self): 244 | return self._root.sexpr 245 | def set(self, sexpr): 246 | if not sexpr: 247 | sexpr = djvu.const.EMPTY_OUTLINE 248 | self._root = RootNode(sexpr, self) 249 | self.notify_tree_change() 250 | return property(get, set) 251 | 252 | def remove(self): 253 | self.raw_value = djvu.const.EMPTY_OUTLINE 254 | 255 | def revert(self): 256 | self.raw_value = self._original_sexpr 257 | self._dirty = False 258 | 259 | def acquire_data(self): 260 | return djvu.const.EMPTY_OUTLINE 261 | 262 | def notify_tree_change(self): 263 | self._dirty = True 264 | for callback in self._callbacks: 265 | callback.notify_tree_change(self._root) 266 | 267 | def notify_node_change(self, node): 268 | self._dirty = True 269 | for callback in self._callbacks: 270 | callback.notify_node_change(node) 271 | 272 | def notify_node_children_change(self, node): 273 | self._dirty = True 274 | for callback in self._callbacks: 275 | callback.notify_node_children_change(node) 276 | 277 | def notify_node_select(self, node): 278 | for callback in self._callbacks: 279 | callback.notify_node_select(node) 280 | 281 | def export(self, djvused): 282 | if self.root: 283 | value = self.raw_value 284 | else: 285 | value = None 286 | djvused.set_outline(value) 287 | 288 | def export_as_plaintext(self, stream): 289 | return self.root.export_as_plaintext(stream) 290 | 291 | def import_plaintext(self, lines): 292 | def fix_node(node): 293 | it = iter(node) 294 | it.next() 295 | for subnode in it: 296 | fix_node(subnode) 297 | text = node[0] 298 | if text is not None: 299 | node[0:1] = (text.split(None, 1) + ['', ''])[1::-1] 300 | tree = indents_to_tree(lines) 301 | fix_node(tree) 302 | tree[0:1] = djvu.const.EMPTY_OUTLINE 303 | self.raw_value = djvu.sexpr.Expression(tree) 304 | 305 | # vim:ts=4 sts=4 sw=4 et 306 | -------------------------------------------------------------------------------- /lib/models/text.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2014 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | ''' 17 | Models for hidden text. 18 | 19 | See Lizardtech DjVu Reference (DjVu 3): 20 | - 3.3.2 Hidden text. 21 | - 8.3.5 Text Chunk. 22 | ''' 23 | 24 | import copy 25 | import weakref 26 | import itertools 27 | 28 | import djvu.decode 29 | import djvu.sexpr 30 | 31 | from djvusmooth.varietes import not_overridden, wref 32 | from djvusmooth.models import MultiPageModel 33 | 34 | class Node(object): 35 | 36 | def __new__(cls, sexpr, owner): 37 | if len(sexpr) == 6 and isinstance(sexpr[5], djvu.sexpr.StringExpression): 38 | cls = LeafNode 39 | else: 40 | cls = InnerNode 41 | return object.__new__(cls) 42 | 43 | def __init__(self, sexpr, owner): 44 | self._owner = owner 45 | self._type = djvu.const.get_text_zone_type(sexpr[0].value) 46 | x0, y0, x1, y1 = (sexpr[i].value for i in xrange(1, 5)) 47 | self._x = x0 48 | self._y = y0 49 | self._w = x1 - x0 50 | self._h = y1 - y0 51 | self._link_left = self._link_right = self._link_parent = wref(None) 52 | 53 | @property 54 | def sexpr(self): 55 | return self._construct_sexpr() 56 | 57 | def _construct_sexpr(self): 58 | raise NotImplementedError 59 | 60 | @apply 61 | def separator(): 62 | def get(self): 63 | return djvu.const.TEXT_ZONE_SEPARATORS[self._type] 64 | return property(get) 65 | 66 | @apply 67 | def x(): 68 | def get(self): 69 | return self._x 70 | def set(self, value): 71 | self._x = value 72 | self._notify_change() 73 | return property(get, set) 74 | 75 | @apply 76 | def y(): 77 | def get(self): 78 | return self._y 79 | def set(self, value): 80 | self._y = value 81 | self._notify_change() 82 | return property(get, set) 83 | 84 | @apply 85 | def w(): 86 | def get(self): 87 | return self._w 88 | def set(self, value): 89 | self._w = value 90 | self._notify_change() 91 | return property(get, set) 92 | 93 | @apply 94 | def h(): 95 | def get(self): 96 | return self._h 97 | def set(self, value): 98 | self._h = value 99 | self._notify_change() 100 | return property(get, set) 101 | 102 | @apply 103 | def rect(): 104 | def get(self): 105 | return self._x, self._y, self._w, self._h 106 | def set(self, value): 107 | self._x, self._y, self._w, self._h = value 108 | self._notify_change() 109 | return property(get, set) 110 | 111 | @apply 112 | def type(): 113 | def get(self): 114 | return self._type 115 | return property(get) 116 | 117 | @apply 118 | def left_sibling(): 119 | def get(self): 120 | link = self._link_left() 121 | if link is None: 122 | raise StopIteration 123 | return link 124 | return property(get) 125 | 126 | @apply 127 | def right_sibling(): 128 | def get(self): 129 | link = self._link_right() 130 | if link is None: 131 | raise StopIteration 132 | return link 133 | return property(get) 134 | 135 | @apply 136 | def parent(): 137 | def get(self): 138 | link = self._link_parent() 139 | if link is None: 140 | raise StopIteration 141 | return link 142 | return property(get) 143 | 144 | @apply 145 | def left_child(): 146 | def get(self): 147 | raise StopIteration 148 | return property(get) 149 | 150 | def delete(self): 151 | try: 152 | parent = self.parent 153 | except StopIteration: 154 | return 155 | parent.remove_child(self) 156 | 157 | def remove_child(self, child): 158 | raise NotImplementedError 159 | 160 | def strip(self, zone_type): 161 | raise NotImplementedError 162 | 163 | def is_leaf(self): 164 | return False 165 | 166 | def is_inner(self): 167 | return False 168 | 169 | def notify_select(self): 170 | self._owner.notify_node_select(self) 171 | 172 | def notify_deselect(self): 173 | self._owner.notify_node_deselect(self) 174 | 175 | def _notify_change(self): 176 | return self._owner.notify_node_change(self) 177 | 178 | def _notify_children_change(self): 179 | return self._owner.notify_node_children_change(self) 180 | 181 | class LeafNode(Node): 182 | 183 | def is_leaf(self): 184 | return True 185 | 186 | def __init__(self, sexpr, owner): 187 | Node.__init__(self, sexpr, owner) 188 | self._text = sexpr[5].value.decode('UTF-8', 'replace') 189 | 190 | def _construct_sexpr(self): 191 | x, y, w, h = self.x, self.y, self.w, self.h 192 | return djvu.sexpr.Expression((self.type, x, y, x + w, y + h, self.text)) 193 | 194 | @apply 195 | def text(): 196 | def get(self): 197 | return self._text 198 | def set(self, value): 199 | self._text = value 200 | self._notify_change() 201 | return property(get, set) 202 | 203 | def remove_child(self, child): 204 | raise TypeError('{0!r} cannot have children'.format(self)) 205 | 206 | def strip(self, zone_type): 207 | if self.type <= zone_type: 208 | return self.text, self.separator 209 | return self 210 | 211 | def __getitem__(self, n): 212 | raise TypeError 213 | 214 | def __len__(self): 215 | raise TypeError 216 | 217 | def __iter__(self): 218 | raise TypeError 219 | 220 | class InnerNode(Node): 221 | 222 | def is_inner(self): 223 | return True 224 | 225 | def __init__(self, sexpr, owner): 226 | Node.__init__(self, sexpr, owner) 227 | self._set_children(Node(child, self._owner) for child in sexpr[5:]) 228 | 229 | def _set_children(self, children): 230 | self._children = list(children) 231 | prev = None 232 | for child in self._children: 233 | child._link_left = wref(prev) 234 | child._link_parent = wref(self) 235 | prev = child 236 | prev = None 237 | for child in reversed(self._children): 238 | child._link_right = wref(prev) 239 | prev = child 240 | 241 | def _construct_sexpr(self): 242 | x, y, w, h = self.x, self.y, self.w, self.h 243 | if self._children: 244 | child_sexprs = (child.sexpr for child in self) 245 | else: 246 | # FIXME: this needs a better solution 247 | child_sexprs = (djvu.sexpr.Expression(''),) 248 | return djvu.sexpr.Expression( 249 | itertools.chain( 250 | (self.type, x, y, x + w, y + h), 251 | child_sexprs 252 | ) 253 | ) 254 | 255 | @apply 256 | def text(): 257 | return property() 258 | 259 | @apply 260 | def left_child(): 261 | def get(self): 262 | return iter(self._children).next() 263 | return property(get) 264 | 265 | def remove_child(self, child): 266 | child_idx = self._children.index(child) 267 | if child_idx - 1 >= 0: 268 | self._children[child_idx - 1]._link_right = child._link_right 269 | if child_idx + 1 < len(self._children): 270 | self._children[child_idx + 1]._link_left = child._link_left 271 | child._link_left = child._link_right = child._link_parent = wref(None) 272 | del self._children[child_idx] 273 | self._notify_children_change() 274 | 275 | def strip(self, zone_type): 276 | stripped_children = [child.strip(zone_type) for child in self._children] 277 | if self.type <= zone_type: 278 | texts = [text for text, child_separator in stripped_children] 279 | return child_separator.join(texts), self.separator 280 | else: 281 | node_children = [child for child in stripped_children if isinstance(child, Node)] 282 | if node_children: 283 | self._set_children(node_children) 284 | return self 285 | else: 286 | texts = [text for text, child_separator in stripped_children] 287 | text = child_separator.join(texts) 288 | return Node( 289 | djvu.sexpr.Expression(( 290 | self.type, 291 | self.x, self.y, 292 | self.x + self.w, self.y + self.h, 293 | text 294 | )), self._owner 295 | ) 296 | return self 297 | 298 | def __getitem__(self, n): 299 | return self._children[n] 300 | 301 | def __len__(self): 302 | return len(self._children) 303 | 304 | def __iter__(self): 305 | return iter(self._children) 306 | 307 | class Text(MultiPageModel): 308 | 309 | def get_page_model_class(self, n): 310 | return PageText 311 | 312 | class PageTextCallback(object): 313 | 314 | @not_overridden 315 | def notify_node_change(self, node): 316 | pass 317 | 318 | @not_overridden 319 | def notify_node_children_change(self, node): 320 | pass 321 | 322 | @not_overridden 323 | def notify_node_select(self, node): 324 | pass 325 | 326 | @not_overridden 327 | def notify_node_deselect(self, node): 328 | pass 329 | 330 | @not_overridden 331 | def notify_tree_change(self, node): 332 | pass 333 | 334 | class PageText(object): 335 | 336 | def __init__(self, n, original_data): 337 | self._callbacks = weakref.WeakKeyDictionary() 338 | self._original_sexpr = original_data 339 | self.revert() 340 | self._n = n 341 | 342 | def register_callback(self, callback): 343 | if not isinstance(callback, PageTextCallback): 344 | raise TypeError 345 | self._callbacks[callback] = 1 346 | 347 | @apply 348 | def root(): 349 | def get(self): 350 | return self._root 351 | return property(get) 352 | 353 | @apply 354 | def raw_value(): 355 | def get(self): 356 | if self._root is None: 357 | return None 358 | return self._root.sexpr 359 | def set(self, sexpr): 360 | if sexpr: 361 | self._root = Node(sexpr, self) 362 | else: 363 | self._root = None 364 | self.notify_tree_change() 365 | return property(get, set) 366 | 367 | def strip(self, zone_type): 368 | zone_type = djvu.const.get_text_zone_type(zone_type) # ensure it's not a plain Symbol 369 | if self._root is None: 370 | return 371 | stripped_root = self._root.strip(zone_type) 372 | if not isinstance(stripped_root, Node): 373 | stripped_root = None 374 | self._root = stripped_root 375 | self.notify_tree_change() 376 | 377 | def clone(self): 378 | return copy.copy(self) 379 | 380 | def export(self, djvused): 381 | if not self._dirty: 382 | return 383 | djvused.select(self._n + 1) 384 | djvused.set_text(self.raw_value) 385 | 386 | def revert(self): 387 | self.raw_value = self._original_sexpr 388 | self._dirty = False 389 | 390 | def notify_node_change(self, node): 391 | self._dirty = True 392 | for callback in self._callbacks: 393 | callback.notify_node_change(node) 394 | 395 | def notify_node_children_change(self, node): 396 | self._dirty = True 397 | for callback in self._callbacks: 398 | callback.notify_node_children_change(node) 399 | 400 | def notify_node_select(self, node): 401 | for callback in self._callbacks: 402 | callback.notify_node_select(node) 403 | 404 | def notify_node_deselect(self, node): 405 | for callback in self._callbacks: 406 | callback.notify_node_deselect(node) 407 | 408 | def notify_tree_change(self): 409 | self._dirty = True 410 | for callback in self._callbacks: 411 | callback.notify_tree_change(self._root) 412 | 413 | def get_preorder_nodes(self): 414 | if self.root is None: 415 | return () 416 | return _get_preorder_nodes(self.root) 417 | 418 | def get_postorder_nodes(self): 419 | if self.root is None: 420 | return () 421 | return _get_postorder_nodes(self.root) 422 | 423 | def get_leafs(self): 424 | if self.root is None: 425 | return () 426 | return _get_leafs(self.root) 427 | 428 | def _get_preorder_nodes(node): 429 | yield node 430 | if isinstance(node, LeafNode): 431 | return 432 | for child in node: 433 | for item in _get_preorder_nodes(child): 434 | yield item 435 | 436 | def _get_postorder_nodes(node): 437 | if isinstance(node, InnerNode): 438 | for child in node: 439 | for item in _get_postorder_nodes(child): 440 | yield item 441 | yield node 442 | 443 | def _get_leafs(node): 444 | if isinstance(node, LeafNode): 445 | yield node 446 | else: 447 | for child in node: 448 | for item in _get_leafs(child): 449 | yield item 450 | 451 | __all__ = ['Text', 'PageText'] 452 | 453 | # vim:ts=4 sts=4 sw=4 et 454 | -------------------------------------------------------------------------------- /lib/pkgconfig.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2019 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | from . import ipc 17 | 18 | class IOError(IOError): 19 | pass 20 | 21 | class Package(object): 22 | 23 | def __init__(self, name): 24 | self._name = name 25 | 26 | def variable(self, variable_name): 27 | pkgconfig = ipc.Subprocess( 28 | ['pkg-config', '--variable=' + str(variable_name), self._name], 29 | stdout=ipc.PIPE, 30 | stderr=ipc.PIPE 31 | ) 32 | stdout, stderr = pkgconfig.communicate() 33 | if pkgconfig.returncode: 34 | raise IOError(stderr.strip()) 35 | return stdout.strip() 36 | 37 | # vim:ts=4 sts=4 sw=4 et 38 | -------------------------------------------------------------------------------- /lib/text/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwilk-archive/djvusmooth/b198eb9bc2f88c5bf5c82f8a13f0f1315dc59717/lib/text/__init__.py -------------------------------------------------------------------------------- /lib/text/levenshtein.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2015 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | class Operation(object): 17 | 18 | def __init__(self, cost): 19 | self.cost = cost 20 | 21 | def __cmp__(self, other): 22 | return cmp(self.cost, other.cost) 23 | 24 | def __repr__(self): 25 | return '{cls}(cost={cost})'.format( 26 | cls=type(self).__name__, 27 | cost=self.cost 28 | ) 29 | 30 | def __add__(self, other): 31 | return self.cost + other 32 | 33 | class Delete(Operation): 34 | pass 35 | class Insert(Operation): 36 | pass 37 | class Substitute(Operation): 38 | pass 39 | class Drop(Operation): 40 | pass 41 | class Append(Operation): 42 | pass 43 | 44 | def distance(s, t): 45 | len_s = len(s) 46 | len_t = len(t) 47 | d = [[None for j in xrange(len_t + 1)] for i in xrange(len_s + 1)] 48 | for i in xrange(len_s + 1): 49 | d[i][0] = Drop(i) 50 | for j in xrange(len_t + 1): 51 | d[0][j] = Append(j) 52 | for i in xrange(1, len_s + 1): 53 | for j in xrange(1, len_t + 1): 54 | subst_cost = int(s[i - 1] != t[j - 1]) 55 | d[i][j] = min( 56 | Delete(d[i - 1][j] + 1), 57 | Insert(d[i][j - 1] + 1), 58 | Substitute(d[i - 1][j - 1] + subst_cost) 59 | ) 60 | i = len_s 61 | j = len_t 62 | ops = [] 63 | while True: 64 | op = d[i][j] 65 | if isinstance(op, Delete): 66 | i -= 1 67 | ops += (i, s[i], ''), 68 | elif isinstance(op, Insert): 69 | j -= 1 70 | ops += (i, '', t[j]), 71 | elif isinstance(op, Substitute): 72 | i -= 1 73 | j -= 1 74 | if s[i] != t[j]: 75 | ops += (i, s[i], t[j],), 76 | elif isinstance(op, Append): 77 | ops += ((i, '', t[jj]) for jj in xrange(j - 1, -1, -1)) 78 | break 79 | elif isinstance(op, Drop): 80 | ops += ((ii, s[ii], '') for ii in xrange(i - 1, -1, -1)) 81 | break 82 | return reversed(ops) 83 | 84 | __all__ = ['distance'] 85 | 86 | # vim:ts=4 sts=4 sw=4 et 87 | -------------------------------------------------------------------------------- /lib/text/mangle.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2022 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | from __future__ import print_function 17 | 18 | import itertools 19 | 20 | import djvu.sexpr 21 | import djvu.const 22 | 23 | from djvusmooth.text.levenshtein import distance 24 | 25 | def mangle(s, t, input): 26 | s = s.decode('UTF-8', 'replace') 27 | t = t.decode('UTF-8', 'replace') 28 | if len(input) == 1 and isinstance(input[0], djvu.sexpr.StringExpression): 29 | yield t 30 | return 31 | input = tuple(map(lambda o: o.value, item) for item in input) 32 | j = 0 33 | current_word = '' 34 | for item in input: 35 | item[5] = item[5].decode('UTF-8', 'replace') 36 | for item in input: 37 | item[5] = len(item[5]) 38 | input_iter = iter(input) 39 | input_head = input_iter.next() 40 | for i, ot, to in distance(s, t): 41 | while i > j: 42 | if s[j] == ' ': 43 | yield input_head[:5] + [current_word] 44 | input_head = input_iter.next() 45 | current_word = '' 46 | else: 47 | current_word += s[j] 48 | j += 1 49 | if to == ' ': 50 | new_len = len(current_word) 51 | old_len = input_head[5] 52 | if new_len >= old_len: 53 | old_len = new_len * 2 54 | old_width = 0.0 + input_head[3] - input_head[1] 55 | spc_width = old_width / old_len 56 | new_width = old_width / old_len * new_len 57 | yield input_head[:3] + [int(input_head[1] + new_width), input_head[4], current_word] 58 | input_head[1] += int(new_width + spc_width) 59 | input_head[5] = old_len 60 | current_word = '' 61 | elif ot == ' ': 62 | current_word += to 63 | next_head = input_iter.next() 64 | input_head[2] = min(next_head[2], input_head[2]) 65 | input_head[3] = max(next_head[3], input_head[3]) 66 | input_head[4] = next_head[4] 67 | input_head[5] = input_head[5] + next_head[5] + len(to) 68 | else: 69 | current_word += to 70 | j = j + len(ot) 71 | len_s = len(s) 72 | while j < len_s: 73 | if s[j] == ' ': 74 | yield input_head[:5] + [current_word] 75 | input_head = input_iter.next() 76 | current_word = '' 77 | else: 78 | current_word += s[j] 79 | j += 1 80 | yield input_head[:5] + [current_word] 81 | 82 | def linearize_for_export(expr): 83 | if expr[0].value == djvu.const.TEXT_ZONE_CHARACTER: 84 | raise CharacterZoneFound 85 | if len(expr) == 6 and isinstance(expr[5], djvu.sexpr.StringExpression): 86 | yield expr[5].value 87 | elif expr[0].value == djvu.const.TEXT_ZONE_LINE: 88 | yield str.join(' ', linearize_for_export(expr[5:])) 89 | else: 90 | for subexpr in expr: 91 | if not isinstance(subexpr, djvu.sexpr.ListExpression): 92 | continue 93 | for item in linearize_for_export(subexpr): 94 | yield item 95 | 96 | def linearize_for_import(expr): 97 | if expr[0].value == djvu.const.TEXT_ZONE_CHARACTER: 98 | raise CharacterZoneFound 99 | if len(expr) == 6 and isinstance(expr[5], djvu.sexpr.StringExpression): 100 | yield expr 101 | elif expr[0].value == djvu.const.TEXT_ZONE_LINE: 102 | yield expr 103 | else: 104 | for subexpr in expr: 105 | if not isinstance(subexpr, djvu.sexpr.ListExpression): 106 | continue 107 | for item in linearize_for_import(subexpr): 108 | yield item 109 | 110 | def export(sexpr, stream): 111 | for line in linearize_for_export(sexpr): 112 | print(line, file=stream) 113 | 114 | def import_(sexpr, stdin): 115 | exported = tuple(linearize_for_export(sexpr)) 116 | inputs = tuple(linearize_for_import(sexpr)) 117 | stdin = tuple(line for line in stdin) 118 | if len(exported) != len(stdin): 119 | raise LengthChanged 120 | assert len(exported) == len(inputs) == len(stdin) 121 | dirty = False 122 | for n, line, xline, input in itertools.izip(itertools.count(1), stdin, exported, inputs): 123 | line = line.rstrip('\n') 124 | if line != xline: 125 | input[5:] = list(mangle(xline, line, input[5:])) 126 | dirty = True 127 | if not dirty: 128 | raise NothingChanged 129 | return sexpr 130 | 131 | class NothingChanged(Exception): 132 | pass 133 | 134 | class LengthChanged(Exception): 135 | pass 136 | 137 | class CharacterZoneFound(Exception): 138 | pass 139 | 140 | __all__ = [ 141 | 'import_', 'export', 142 | 'NothingChanged', 'CharacterZoneFound', 'LengthChanged' 143 | ] 144 | 145 | # vim:ts=4 sts=4 sw=4 et 146 | -------------------------------------------------------------------------------- /lib/varietes.py: -------------------------------------------------------------------------------- 1 | # encoding=UTF-8 2 | 3 | # Copyright © 2008-2022 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | import re 17 | import functools 18 | import warnings 19 | import weakref 20 | 21 | class NotOverriddenWarning(UserWarning): 22 | pass 23 | 24 | def not_overridden(f): 25 | r''' 26 | >>> warnings.filterwarnings('error', category=NotOverriddenWarning) 27 | >>> class B(object): 28 | ... @not_overridden 29 | ... def f(self, x, y): pass 30 | >>> class C(B): 31 | ... def f(self, x, y): return x * y 32 | >>> B().f(6, 7) 33 | Traceback (most recent call last): 34 | ... 35 | NotOverriddenWarning: `lib.varietes.B.f()` is not overridden 36 | >>> C().f(6, 7) 37 | 42 38 | ''' 39 | @functools.wraps(f) 40 | def new_f(self, *args, **kwargs): 41 | cls = type(self) 42 | warnings.warn( 43 | '`{mod}.{cls}.{func}()` is not overridden'.format(mod=cls.__module__, cls=cls.__name__, func=f.__name__), 44 | category=NotOverriddenWarning, 45 | stacklevel=2 46 | ) 47 | return f(self, *args, **kwargs) 48 | return new_f 49 | 50 | def wref(o): 51 | r''' 52 | Return a weak reference to object. This is almost the same as 53 | `weakref.ref()`, but accepts `None` too. 54 | 55 | >>> class O(object): 56 | ... pass 57 | >>> x = O() 58 | >>> xref = wref(x) 59 | >>> xref() is x 60 | True 61 | >>> del x 62 | >>> xref() is None 63 | True 64 | 65 | >>> xref = wref(None) 66 | >>> xref() is None 67 | True 68 | ''' 69 | if o is None: 70 | ref = weakref.ref(set()) 71 | assert ref() is None 72 | else: 73 | ref = weakref.ref(o) 74 | return ref 75 | 76 | def indents_to_tree(lines): 77 | r''' 78 | >>> lines = [ 79 | ... 'bacon', 80 | ... ' egg', 81 | ... ' eggs', 82 | ... 'ham', 83 | ... ' sausage', 84 | ... ' spam', 85 | ... ' bacon', 86 | ... ' egg' 87 | ... ] 88 | >>> indents_to_tree(lines) 89 | [None, ['bacon', ['egg', ['eggs']]], ['ham', ['sausage'], ['spam', ['bacon']], ['egg']]] 90 | 91 | ''' 92 | root = [None] 93 | memo = [(-1, root)] 94 | for line in lines: 95 | old_len = len(line) 96 | line = line.lstrip() 97 | current = [line] 98 | indent = old_len - len(line) 99 | while memo[-1][0] >= indent: 100 | memo.pop() 101 | memo[-1][1].append(current) 102 | memo += (indent, current), 103 | return root 104 | 105 | URI_SPECIAL_CHARACTERS = ( 106 | ':/?#[]@' + # RFC 3986, `gen-delims` 107 | '!$&()*+,;=' + # RFC 3986, `sub-delims` 108 | '%' # RFC 3986, `pct-encoded` 109 | ) 110 | 111 | def fix_uri(s): 112 | r''' 113 | >>> uri = 'http://example.com/' 114 | >>> fix_uri(uri) == uri 115 | True 116 | >>> uri = fix_uri('http://example.com/eggs and spam/') 117 | >>> uri 118 | 'http://example.com/eggs%20and%20spam/' 119 | >>> fix_uri(uri) == uri 120 | True 121 | ''' 122 | from urllib import quote 123 | if isinstance(s, unicode): 124 | s = s.encode('UTF-8') 125 | return quote(s, safe=URI_SPECIAL_CHARACTERS) 126 | 127 | replace_control_characters = re.compile('[\0-\x1F]+').sub 128 | 129 | _is_html_color = re.compile('^[#][0-9a-fA-F]{6}$').match 130 | 131 | def is_html_color(s): 132 | ''' 133 | >>> is_html_color('#000000') 134 | True 135 | >>> is_html_color('#ffffff') 136 | True 137 | >>> is_html_color('#FFFFFF') 138 | True 139 | >>> is_html_color('#c0c0f0') 140 | True 141 | >>> is_html_color('') 142 | False 143 | >>> is_html_color('#bcdefg') 144 | False 145 | >>> is_html_color('#ffffff ') 146 | False 147 | >>> is_html_color(' #ffffff') 148 | False 149 | ''' 150 | return bool(_is_html_color(s)) 151 | 152 | class idict(object): 153 | 154 | ''' 155 | >>> o = idict(eggs = 'spam', ham = 'eggs') 156 | >>> o 157 | lib.varietes.idict(eggs='spam', ham='eggs') 158 | >>> o.eggs 159 | 'spam' 160 | >>> o.ham 161 | 'eggs' 162 | >>> o.spam 163 | Traceback (most recent call last): 164 | ... 165 | AttributeError: 'idict' object has no attribute 'spam' 166 | ''' 167 | 168 | def __init__(self, **kwargs): 169 | self.__dict__.update(kwargs) 170 | 171 | def __repr__(self): 172 | return '{mod}.{cls}({init})'.format( 173 | mod=self.__module__, 174 | cls=type(self).__name__, 175 | init=str.join(', ', ('{k}={v!r}'.format(k=k, v=v) for k, v in self.__dict__.iteritems())) 176 | ) 177 | 178 | # vim:ts=4 sts=4 sw=4 et 179 | -------------------------------------------------------------------------------- /po/es.po: -------------------------------------------------------------------------------- 1 | # Spanish translation of djvusmooth messages. 2 | # Copyright (C) 2012 3 | # This file is distributed under the same license as the djvusmooth package. 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: djvusmooth 0.3.1\n" 7 | "Report-Msgid-Bugs-To: Jakub Wilk \n" 8 | "POT-Creation-Date: 2015-09-15 10:51+0200\n" 9 | "PO-Revision-Date: 2012-05-21 00:20-0500\n" 10 | "Last-Translator: C. Daniel Sanchez R. \n" 11 | "Language-Team: \n" 12 | "Language: es\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | 17 | #: lib/gui/main.py 18 | msgid "&About" 19 | msgstr "&Acerca de" 20 | 21 | #: lib/gui/main.py 22 | msgid "&Background" 23 | msgstr "&Fondo" 24 | 25 | #: lib/gui/main.py 26 | msgid "&Bookmark this page" 27 | msgstr "&Marcadores de esta página" 28 | 29 | #: lib/gui/main.py 30 | msgid "&Close" 31 | msgstr "&Cerrar" 32 | 33 | #: lib/gui/main.py 34 | msgid "&Color" 35 | msgstr "&Color" 36 | 37 | #: lib/gui/main.py 38 | msgid "&Edit" 39 | msgstr "&Editar" 40 | 41 | #: lib/gui/main.py 42 | msgid "&External editor" 43 | msgstr "&Editor externo" 44 | 45 | #: lib/gui/main.py 46 | msgid "&File" 47 | msgstr "&Archivo" 48 | 49 | #: lib/gui/main.py 50 | msgid "&First page" 51 | msgstr "&Primer página" 52 | 53 | #: lib/gui/main.py 54 | msgid "&Flatten" 55 | msgstr "&Aplanar" 56 | 57 | #: lib/gui/main.py 58 | msgid "&Foreground" 59 | msgstr "&Frente" 60 | 61 | #: lib/gui/main.py 62 | msgid "&Go" 63 | msgstr "&Ir" 64 | 65 | #: lib/gui/main.py 66 | msgid "&Go to page…" 67 | msgstr "&Ir a página..." 68 | 69 | #: lib/gui/main.py 70 | msgid "&Help" 71 | msgstr "&Ayuda" 72 | 73 | #: lib/gui/main.py 74 | msgid "&Hyperlinks" 75 | msgstr "&Hipervínculos" 76 | 77 | #: lib/gui/main.py 78 | msgid "&Image" 79 | msgstr "&Imagen" 80 | 81 | #: lib/gui/main.py 82 | msgid "&Last page" 83 | msgstr "&Última página" 84 | 85 | #: lib/gui/main.py 86 | msgid "&Metadata" 87 | msgstr "&Metadatos" 88 | 89 | #: lib/gui/maparea_menu.py 90 | msgid "&New hyperlink…" 91 | msgstr "&Nuevo hipervínculo..." 92 | 93 | #: lib/gui/main.py 94 | msgid "&Next page" 95 | msgstr "&Página siguiente" 96 | 97 | #: lib/gui/main.py 98 | msgid "&Non-raster data" 99 | msgstr "&Datos ajenos al bitmap" 100 | 101 | #: lib/gui/main.py 102 | msgid "&None" 103 | msgstr "&Ninguno" 104 | 105 | #: lib/gui/main.py 106 | msgid "&Open" 107 | msgstr "&Abrir" 108 | 109 | #: lib/gui/main.py 110 | msgid "&Outline" 111 | msgstr "&Bosquejo" 112 | 113 | #: lib/gui/main.py 114 | msgid "&Previous page" 115 | msgstr "&Página anterior" 116 | 117 | #: lib/gui/maparea_menu.py 118 | msgid "&Properties…" 119 | msgstr "&Propiedades..." 120 | 121 | #: lib/gui/main.py 122 | msgid "&Quit" 123 | msgstr "&Salir" 124 | 125 | #: lib/gui/main.py 126 | msgid "&Refresh" 127 | msgstr "&Refrescar" 128 | 129 | #: lib/gui/maparea_menu.py 130 | msgid "&Remove" 131 | msgstr "&Eliminar" 132 | 133 | #: lib/gui/main.py 134 | msgid "&Remove all" 135 | msgstr "&Eliminar todo" 136 | 137 | #: lib/gui/main.py 138 | msgid "&Save" 139 | msgstr "&Guardar" 140 | 141 | #: lib/gui/main.py 142 | msgid "&Settings" 143 | msgstr "&Opciones" 144 | 145 | #: lib/gui/main.py 146 | msgid "&Stencil" 147 | msgstr "&Patrón" 148 | 149 | #: lib/gui/main.py 150 | msgid "&Stretch" 151 | msgstr "&Estirar" 152 | 153 | #: lib/gui/main.py 154 | msgid "&Text" 155 | msgstr "&Texto" 156 | 157 | #: lib/gui/main.py 158 | msgid "&View" 159 | msgstr "&Ver" 160 | 161 | #: lib/gui/main.py 162 | msgid "&Zoom" 163 | msgstr "&Zoom" 164 | 165 | #: lib/gui/main.py 166 | msgid "(no title)" 167 | msgstr "(sin título)" 168 | 169 | #: lib/gui/main.py 170 | msgid "About…" 171 | msgstr "Acerca de..." 172 | 173 | #: lib/gui/main.py 174 | msgid "Add the current to document outline" 175 | msgstr "Agregar la página actual al bosquejo del documento" 176 | 177 | #: lib/gui/maparea_properties.py 178 | msgid "Always visible" 179 | msgstr "Siempre visible" 180 | 181 | #: lib/gui/main.py 182 | msgid "" 183 | "An unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author.\n" 184 | "\n" 185 | msgstr "" 186 | "Ha ocurrido una excepción no controlada. Idealmente, esto no debería ocurrir. Por favor, informe el error al autor.\n" 187 | "\n" 188 | 189 | #: lib/gui/maparea_properties.py 190 | msgid "Arrow" 191 | msgstr "Flecha" 192 | 193 | #: lib/gui/main.py 194 | msgid "Author" 195 | msgstr "Autor" 196 | 197 | #: lib/gui/maparea_properties.py 198 | msgid "Background color" 199 | msgstr "Color de fondo" 200 | 201 | #: lib/gui/maparea_properties.py 202 | msgid "Border" 203 | msgstr "Borde" 204 | 205 | #: lib/gui/main.py 206 | msgid "Cannot edit text with character zones." 207 | msgstr "No se puede editar el texto que esté en una zona para caracteres." 208 | 209 | #: lib/gui/main.py 210 | msgid "Close the document" 211 | msgstr "Cerrar el documento" 212 | 213 | #: lib/gui/maparea_browser.py lib/gui/maparea_properties.py 214 | msgid "Comment" 215 | msgstr "Comentario" 216 | 217 | #: lib/gui/main.py 218 | msgid "Decrease the magnification" 219 | msgstr "Disminuir la ampliación" 220 | 221 | #: lib/gui/main.py 222 | msgid "Display everything" 223 | msgstr "Mostrar todo" 224 | 225 | #: lib/gui/main.py 226 | msgid "Display only the background layer" 227 | msgstr "Mostrar solo la capa del fondo" 228 | 229 | #: lib/gui/main.py 230 | msgid "Display only the document bitonal stencil" 231 | msgstr "Mostrar solo el patrón bitonal del documento" 232 | 233 | #: lib/gui/main.py 234 | msgid "Display only the foreground layer" 235 | msgstr "Mostrar solo la capa del frente" 236 | 237 | #: lib/gui/main.py 238 | msgid "Display overprinted annotations" 239 | msgstr "Mostrar anotaciones encima" 240 | 241 | #: lib/gui/main.py 242 | msgid "Display the text layer" 243 | msgstr "Mostrar la capa de texto" 244 | 245 | #: lib/gui/main.py 246 | msgid "DjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*" 247 | msgstr "Archivos DjVu (*.djvu, *.djv)|*.djvu;*.djv|Todos los archivos|*" 248 | 249 | #: lib/gui/main.py 250 | msgid "Do you want to save your changes?" 251 | msgstr "¿Desea guardar los cambios?" 252 | 253 | #: lib/gui/main.py 254 | msgid "Document metadata" 255 | msgstr "Metadatos del documento" 256 | 257 | #: lib/gui/main.py 258 | msgid "Don't display non-raster data" 259 | msgstr "No mostrar los datos del bitmap" 260 | 261 | #: lib/gui/main.py 262 | msgid "Edit document outline in an external editor" 263 | msgstr "Editar el bosquejo en un editor externo" 264 | 265 | #: lib/gui/metadata.py 266 | msgid "Edit metadata" 267 | msgstr "Editar metadatos" 268 | 269 | #: lib/gui/main.py 270 | msgid "Edit page text in an external editor" 271 | msgstr "Editar texto de la página en un editor externo" 272 | 273 | #: lib/gui/main.py 274 | msgid "Edit the document or page metadata" 275 | msgstr "Editar los metadatos del documento o de la página" 276 | 277 | #: lib/gui/main.py 278 | msgid "Enter path to your favourite text editor." 279 | msgstr "Ingrese la ruta hacia su editor de texto favorito." 280 | 281 | #: lib/gui/main.py 282 | msgid "Error" 283 | msgstr "Error" 284 | 285 | #: lib/gui/maparea_properties.py 286 | msgid "Etched in" 287 | msgstr "Grabado in" 288 | 289 | #: lib/gui/maparea_properties.py 290 | msgid "Etched out" 291 | msgstr "Grabado out" 292 | 293 | #: lib/gui/main.py 294 | #, python-format 295 | msgid "" 296 | "External edit failed:\n" 297 | "%s" 298 | msgstr "" 299 | "Falló la edición externa:\n" 300 | "%s" 301 | 302 | #: lib/gui/main.py 303 | msgid "External editor…" 304 | msgstr "Editor externo..." 305 | 306 | #: lib/gui/main.py 307 | msgid "Fit &page" 308 | msgstr "Ajustar &página" 309 | 310 | #: lib/gui/main.py 311 | msgid "Fit &width" 312 | msgstr "Ajustar &ancho" 313 | 314 | #: lib/gui/flatten_text.py 315 | msgid "Flatten text" 316 | msgstr "Aplanar el texto" 317 | 318 | #: lib/gui/main.py 319 | msgid "Go to page" 320 | msgstr "Ir a la página" 321 | 322 | #: lib/gui/maparea_properties.py 323 | msgid "Highlight color" 324 | msgstr "Realzar color" 325 | 326 | #: lib/gui/maparea_properties.py 327 | msgid "Highlight color and opacity" 328 | msgstr "Realzar color y opacidad" 329 | 330 | #: lib/gui/main.py 331 | msgid "Hyperlinks" 332 | msgstr "Hipervínculos" 333 | 334 | #: lib/gui/main.py 335 | msgid "Increase the magnification" 336 | msgstr "Incrementar la ampliación" 337 | 338 | #: lib/gui/main.py 339 | msgid "Jump to first document page" 340 | msgstr "Saltar a la primer página del documento" 341 | 342 | #: lib/gui/main.py 343 | msgid "Jump to last document page" 344 | msgstr "Saltar a la última página del documento" 345 | 346 | #: lib/gui/main.py 347 | msgid "Jump to next document page" 348 | msgstr "Saltar a la siguiente página del documento" 349 | 350 | #: lib/gui/main.py 351 | msgid "Jump to page…" 352 | msgstr "Saltar a la página..." 353 | 354 | #: lib/gui/main.py 355 | msgid "Jump to previous document page" 356 | msgstr "Saltar a la página anterior del documento" 357 | 358 | #: lib/gui/main.py 359 | msgid "License" 360 | msgstr "Licencia" 361 | 362 | #: lib/gui/maparea_properties.py 363 | msgid "Line" 364 | msgstr "Línea" 365 | 366 | #: lib/gui/maparea_properties.py 367 | msgid "Line color" 368 | msgstr "Color de la línea" 369 | 370 | #: lib/gui/maparea_properties.py 371 | msgid "Line width" 372 | msgstr "Ancho de la línea" 373 | 374 | #: lib/gui/maparea_properties.py 375 | msgid "Line-specific properties" 376 | msgstr "Propiedades de la linea especifica" 377 | 378 | #: lib/gui/main.py 379 | #, python-format 380 | msgid "Link: %s" 381 | msgstr "Vínculo: %s" 382 | 383 | #: lib/gui/main.py 384 | #, python-format 385 | msgid "Magnify %d%%" 386 | msgstr "Aumentar %d%%" 387 | 388 | #: lib/gui/maparea_properties.py 389 | msgid "Main properties" 390 | msgstr "Propiedades principales" 391 | 392 | #: lib/gui/main.py 393 | msgid "More information about this program" 394 | msgstr "Más información acerca de este programa" 395 | 396 | #: lib/gui/main.py 397 | msgid "Neither display the foreground layer nor the background layer" 398 | msgstr "No mostrar las capas del frente ni del fondo" 399 | 400 | #: lib/gui/main.py 401 | msgid "No text layer to edit." 402 | msgstr "No hay capa de texto para editar." 403 | 404 | #: lib/gui/maparea_properties.py 405 | msgid "None" 406 | msgstr "Ninguno" 407 | 408 | #: lib/gui/main.py 409 | msgid "Number of lines changed." 410 | msgstr "Número de líneas cambiadas." 411 | 412 | #: lib/gui/main.py 413 | msgid "One &to one" 414 | msgstr "Uno &a uno" 415 | 416 | #: lib/gui/maparea_properties.py 417 | msgid "Opacity" 418 | msgstr "Opacidad" 419 | 420 | #: lib/gui/main.py 421 | msgid "Open &recent" 422 | msgstr "Abrir &reciente" 423 | 424 | #: lib/gui/main.py 425 | msgid "Open a DjVu document" 426 | msgstr "Abrir un documento DjVu" 427 | 428 | #: lib/gui/main.py 429 | msgid "Outline" 430 | msgstr "Bosquejo" 431 | 432 | #: lib/gui/maparea_properties.py 433 | msgid "Oval" 434 | msgstr "Óvalo" 435 | 436 | #: lib/gui/maparea_properties.py 437 | msgid "Overprinted annotation (hyperlink) properties" 438 | msgstr "Propiedades de la anotación (hipervínculo)" 439 | 440 | #: lib/gui/main.py 441 | #, python-format 442 | msgid "Page %(pageno)d of %(npages)d" 443 | msgstr "Página %(pageno)d de %(npages)d" 444 | 445 | #: lib/gui/main.py 446 | #, python-format 447 | msgid "Page %d metadata" 448 | msgstr "Metadatos de la página %d" 449 | 450 | #: lib/gui/maparea_properties.py 451 | msgid "Polygon" 452 | msgstr "Polígono" 453 | 454 | #: lib/gui/maparea_properties.py 455 | msgid "Pushpin" 456 | msgstr "Push-pin" 457 | 458 | #: lib/gui/main.py 459 | msgid "Quit the application" 460 | msgstr "Salir de la aplicación" 461 | 462 | #: lib/gui/maparea_properties.py 463 | msgid "Rectangle" 464 | msgstr "Rectángulo" 465 | 466 | #: lib/gui/main.py 467 | msgid "Refresh the window" 468 | msgstr "Refresca la ventana" 469 | 470 | #: lib/gui/flatten_text.py 471 | msgid "Remove details" 472 | msgstr "Eliminar detalles" 473 | 474 | #: lib/gui/main.py 475 | msgid "Remove details from page text" 476 | msgstr "Eliminar detalles del texto en la página" 477 | 478 | #: lib/gui/main.py 479 | msgid "Remove whole document outline" 480 | msgstr "Eliminar bosquejo de todo el documento" 481 | 482 | #: lib/gui/main.py 483 | msgid "Save the document" 484 | msgstr "Guardar el documento" 485 | 486 | #: lib/gui/main.py 487 | msgid "Saving document" 488 | msgstr "Guardando documento" 489 | 490 | #: lib/gui/main.py 491 | #, python-format 492 | msgid "" 493 | "Saving document failed:\n" 494 | "%s" 495 | msgstr "" 496 | "Falló el guardado del documento:\n" 497 | "%s" 498 | 499 | #: lib/gui/main.py 500 | msgid "Saving the document, please wait…" 501 | msgstr "Guardando documento, espere por favor..." 502 | 503 | #: lib/gui/flatten_text.py 504 | msgid "Scope" 505 | msgstr "Alcance" 506 | 507 | #: lib/gui/main.py 508 | msgid "Set full resolution magnification." 509 | msgstr "Ajustar a la máxima resolución" 510 | 511 | #: lib/gui/main.py 512 | msgid "Set magnification to fit page" 513 | msgstr "Ajustar ampliación al tamaño de la página" 514 | 515 | #: lib/gui/main.py 516 | msgid "Set magnification to fit page width" 517 | msgstr "Ajustar ampliación al ancho de la página" 518 | 519 | #: lib/gui/main.py 520 | msgid "Setup an external editor" 521 | msgstr "Configurar un editor externo" 522 | 523 | #: lib/gui/maparea_properties.py 524 | msgid "Shadow in" 525 | msgstr "Sombra in" 526 | 527 | #: lib/gui/maparea_properties.py 528 | msgid "Shadow out" 529 | msgstr "Sombra out" 530 | 531 | #: lib/gui/maparea_properties.py 532 | msgid "Shape" 533 | msgstr "Silueta" 534 | 535 | #: lib/gui/main.py 536 | msgid "Show &sidebar" 537 | msgstr "Mostrar &barra lateral" 538 | 539 | #: lib/gui/main.py 540 | msgid "Show/hide the sidebar" 541 | msgstr "Mostrar/ocultar la barra lateral" 542 | 543 | #: lib/gui/maparea_properties.py 544 | msgid "Solid color" 545 | msgstr "Color sólido" 546 | 547 | #: lib/gui/main.py 548 | msgid "Stretch the image to the window size" 549 | msgstr "Estirar la imagen al tamaño de la ventana" 550 | 551 | #: lib/gui/maparea_properties.py 552 | msgid "Target frame" 553 | msgstr "Marco objetivo" 554 | 555 | #: lib/gui/main.py lib/gui/maparea_properties.py 556 | msgid "Text" 557 | msgstr "Texto" 558 | 559 | #: lib/gui/maparea_properties.py 560 | msgid "Text color" 561 | msgstr "Color del texto" 562 | 563 | #: lib/gui/maparea_properties.py 564 | msgid "Text-specific properties" 565 | msgstr "Propiedades para un texto específico" 566 | 567 | #: lib/gui/maparea_browser.py 568 | msgid "URI" 569 | msgstr "URI" 570 | 571 | #: lib/gui/main.py 572 | #, python-format 573 | msgid "Unhandled exception: %s" 574 | msgstr "Excepción no controlada: %s" 575 | 576 | #: lib/gui/maparea_properties.py 577 | msgid "Width" 578 | msgstr "Ancho" 579 | 580 | #: lib/gui/maparea_properties.py 581 | msgid "XOR" 582 | msgstr "XOR" 583 | 584 | #: lib/gui/main.py 585 | msgid "Zoom &in" 586 | msgstr "Zoom &in" 587 | 588 | #: lib/gui/main.py 589 | msgid "Zoom &out" 590 | msgstr "Zoom &out" 591 | 592 | #: lib/gui/main.py 593 | msgid "[Text layer]" 594 | msgstr "[Capa de texto]" 595 | 596 | #: lib/gui/flatten_text.py 597 | msgid "all" 598 | msgstr "Todo" 599 | 600 | #: lib/gui/flatten_text.py 601 | msgid "all pages" 602 | msgstr "Todas las páginas" 603 | 604 | #: lib/i18n.py 605 | msgid "bookmarks" 606 | msgstr "Marcadores" 607 | 608 | #: lib/i18n.py 609 | msgid "char" 610 | msgstr "carácter" 611 | 612 | #: lib/gui/flatten_text.py 613 | msgid "characters" 614 | msgstr "caracteres" 615 | 616 | #: lib/i18n.py 617 | msgid "column" 618 | msgstr "columna" 619 | 620 | #: lib/gui/flatten_text.py 621 | msgid "columns" 622 | msgstr "columnas" 623 | 624 | #: lib/gui/flatten_text.py 625 | msgid "current page" 626 | msgstr "Página actual" 627 | 628 | #: lib/gui/metadata.py 629 | msgid "key" 630 | msgstr "clave" 631 | 632 | #: lib/i18n.py 633 | msgid "line" 634 | msgstr "línea" 635 | 636 | #: lib/gui/flatten_text.py 637 | msgid "lines" 638 | msgstr "líneas" 639 | 640 | #: lib/i18n.py 641 | msgid "page" 642 | msgstr "página" 643 | 644 | #: lib/i18n.py 645 | msgid "para" 646 | msgstr "párrafo" 647 | 648 | #: lib/gui/flatten_text.py 649 | msgid "paragraphs" 650 | msgstr "párrafos" 651 | 652 | #: lib/i18n.py 653 | msgid "region" 654 | msgstr "región" 655 | 656 | #: lib/gui/flatten_text.py 657 | msgid "regions" 658 | msgstr "regiones" 659 | 660 | #: lib/gui/metadata.py 661 | msgid "value" 662 | msgstr "valor" 663 | 664 | #: lib/i18n.py 665 | msgid "word" 666 | msgstr "palabra" 667 | 668 | #: lib/gui/flatten_text.py 669 | msgid "words" 670 | msgstr "palabras" 671 | -------------------------------------------------------------------------------- /po/pl.po: -------------------------------------------------------------------------------- 1 | # Polish translation of djvusmooth messages 2 | # Copyright © 2009 Mateusz Turcza 3 | # This file is distributed under the same license as the djvusmooth package. 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: djvusmooth 0.3.1\n" 7 | "Report-Msgid-Bugs-To: Jakub Wilk \n" 8 | "POT-Creation-Date: 2015-09-15 10:51+0200\n" 9 | "PO-Revision-Date: 2012-10-09 20:41+0200\n" 10 | "Last-Translator: Jakub Wilk \n" 11 | "Language-Team: none\n" 12 | "Language: pl\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | 17 | #: lib/gui/main.py 18 | msgid "&About" 19 | msgstr "&O programie" 20 | 21 | #: lib/gui/main.py 22 | msgid "&Background" 23 | msgstr "&Tło" 24 | 25 | #: lib/gui/main.py 26 | msgid "&Bookmark this page" 27 | msgstr "Dodaj stronę do z&akładek" 28 | 29 | #: lib/gui/main.py 30 | msgid "&Close" 31 | msgstr "&Zamknij" 32 | 33 | #: lib/gui/main.py 34 | msgid "&Color" 35 | msgstr "&Kolor" 36 | 37 | #: lib/gui/main.py 38 | msgid "&Edit" 39 | msgstr "&Edycja" 40 | 41 | #: lib/gui/main.py 42 | msgid "&External editor" 43 | msgstr "&Zewnętrzny edytor" 44 | 45 | #: lib/gui/main.py 46 | msgid "&File" 47 | msgstr "&Plik" 48 | 49 | #: lib/gui/main.py 50 | msgid "&First page" 51 | msgstr "Pi&erwsza strona" 52 | 53 | #: lib/gui/main.py 54 | msgid "&Flatten" 55 | msgstr "&Spłaszcz strukturę tekstu" 56 | 57 | #: lib/gui/main.py 58 | msgid "&Foreground" 59 | msgstr "&Pierwszy plan" 60 | 61 | #: lib/gui/main.py 62 | msgid "&Go" 63 | msgstr "&Idź" 64 | 65 | #: lib/gui/main.py 66 | msgid "&Go to page…" 67 | msgstr "I&dź do strony…" 68 | 69 | #: lib/gui/main.py 70 | msgid "&Help" 71 | msgstr "Pomo&c" 72 | 73 | #: lib/gui/main.py 74 | msgid "&Hyperlinks" 75 | msgstr "&Hiperłącza" 76 | 77 | #: lib/gui/main.py 78 | msgid "&Image" 79 | msgstr "O&braz" 80 | 81 | #: lib/gui/main.py 82 | msgid "&Last page" 83 | msgstr "&Ostatnia strona" 84 | 85 | #: lib/gui/main.py 86 | msgid "&Metadata" 87 | msgstr "&Metadane" 88 | 89 | #: lib/gui/maparea_menu.py 90 | msgid "&New hyperlink…" 91 | msgstr "&Nowe hiperłącze…" 92 | 93 | #: lib/gui/main.py 94 | msgid "&Next page" 95 | msgstr "&Następna strona" 96 | 97 | #: lib/gui/main.py 98 | msgid "&Non-raster data" 99 | msgstr "Dane &nierastrowe" 100 | 101 | #: lib/gui/main.py 102 | msgid "&None" 103 | msgstr "&Brak" 104 | 105 | #: lib/gui/main.py 106 | msgid "&Open" 107 | msgstr "&Otwórz" 108 | 109 | #: lib/gui/main.py 110 | msgid "&Outline" 111 | msgstr "&Konspekt" 112 | 113 | #: lib/gui/main.py 114 | msgid "&Previous page" 115 | msgstr "&Poprzednia strona" 116 | 117 | #: lib/gui/maparea_menu.py 118 | msgid "&Properties…" 119 | msgstr "&Właściwości…" 120 | 121 | #: lib/gui/main.py 122 | msgid "&Quit" 123 | msgstr "Za&kończ" 124 | 125 | #: lib/gui/main.py 126 | msgid "&Refresh" 127 | msgstr "&Odśwież" 128 | 129 | #: lib/gui/maparea_menu.py 130 | msgid "&Remove" 131 | msgstr "&Usuń" 132 | 133 | #: lib/gui/main.py 134 | msgid "&Remove all" 135 | msgstr "&Usuń wszystko" 136 | 137 | #: lib/gui/main.py 138 | msgid "&Save" 139 | msgstr "Za&pisz" 140 | 141 | #: lib/gui/main.py 142 | msgid "&Settings" 143 | msgstr "&Ustawienia" 144 | 145 | #: lib/gui/main.py 146 | msgid "&Stencil" 147 | msgstr "&Szablon" 148 | 149 | #: lib/gui/main.py 150 | msgid "&Stretch" 151 | msgstr "&Rozciągnij" 152 | 153 | #: lib/gui/main.py 154 | msgid "&Text" 155 | msgstr "&Tekst" 156 | 157 | #: lib/gui/main.py 158 | msgid "&View" 159 | msgstr "&Widok" 160 | 161 | #: lib/gui/main.py 162 | msgid "&Zoom" 163 | msgstr "&Powiększenie" 164 | 165 | #: lib/gui/main.py 166 | msgid "(no title)" 167 | msgstr "(brak tytułu)" 168 | 169 | #: lib/gui/main.py 170 | msgid "About…" 171 | msgstr "O programie…" 172 | 173 | #: lib/gui/main.py 174 | msgid "Add the current to document outline" 175 | msgstr "Dodaj do konspektu dokumentu" 176 | 177 | #: lib/gui/maparea_properties.py 178 | msgid "Always visible" 179 | msgstr "Zawsze widoczny" 180 | 181 | #: lib/gui/main.py 182 | msgid "" 183 | "An unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author.\n" 184 | "\n" 185 | msgstr "" 186 | "Wystąpił nieoczekiwany błąd. Jeśli ta sytuacja się powtórzy, powiadom autora programu.\n" 187 | "\n" 188 | 189 | #: lib/gui/maparea_properties.py 190 | msgid "Arrow" 191 | msgstr "Strzałka" 192 | 193 | #: lib/gui/main.py 194 | msgid "Author" 195 | msgstr "Autor" 196 | 197 | #: lib/gui/maparea_properties.py 198 | msgid "Background color" 199 | msgstr "Kolor tła" 200 | 201 | #: lib/gui/maparea_properties.py 202 | msgid "Border" 203 | msgstr "Obramowanie" 204 | 205 | #: lib/gui/main.py 206 | msgid "Cannot edit text with character zones." 207 | msgstr "Nie można edytować tekstu ze strefami znakowymi." 208 | 209 | #: lib/gui/main.py 210 | msgid "Close the document" 211 | msgstr "Zamknij dokument" 212 | 213 | #: lib/gui/maparea_browser.py lib/gui/maparea_properties.py 214 | msgid "Comment" 215 | msgstr "Komentarz" 216 | 217 | #: lib/gui/main.py 218 | msgid "Decrease the magnification" 219 | msgstr "Zmniejsz powiększenie" 220 | 221 | #: lib/gui/main.py 222 | msgid "Display everything" 223 | msgstr "Pokaż wszystko" 224 | 225 | #: lib/gui/main.py 226 | msgid "Display only the background layer" 227 | msgstr "Pokaż tylko tło" 228 | 229 | #: lib/gui/main.py 230 | msgid "Display only the document bitonal stencil" 231 | msgstr "Pokaż tylko dwubarwny szablon dokumentu" 232 | 233 | #: lib/gui/main.py 234 | msgid "Display only the foreground layer" 235 | msgstr "Pokaż tylko pierwszy plan" 236 | 237 | #: lib/gui/main.py 238 | msgid "Display overprinted annotations" 239 | msgstr "Pokazuj obwódki wokół hiperłączy" 240 | 241 | #: lib/gui/main.py 242 | msgid "Display the text layer" 243 | msgstr "Pokaż warstwę tekstową" 244 | 245 | #: lib/gui/main.py 246 | msgid "DjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*" 247 | msgstr "Pliki DjVu (*.djvu, *.djv)|*.djvu;*.djv|Wszystkie pliki|*" 248 | 249 | #: lib/gui/main.py 250 | msgid "Do you want to save your changes?" 251 | msgstr "Czy chcesz zapisać zmiany?" 252 | 253 | #: lib/gui/main.py 254 | msgid "Document metadata" 255 | msgstr "Metadane dokumentu" 256 | 257 | #: lib/gui/main.py 258 | msgid "Don't display non-raster data" 259 | msgstr "Nie pokazuj danych nierastrowych" 260 | 261 | #: lib/gui/main.py 262 | msgid "Edit document outline in an external editor" 263 | msgstr "Edytuj konspekt dokumentu w zewnętrznym edytorze" 264 | 265 | #: lib/gui/metadata.py 266 | msgid "Edit metadata" 267 | msgstr "Edytuj metadane" 268 | 269 | #: lib/gui/main.py 270 | msgid "Edit page text in an external editor" 271 | msgstr "Edytuj tekst strony w zewnętrznym edytorze" 272 | 273 | #: lib/gui/main.py 274 | msgid "Edit the document or page metadata" 275 | msgstr "Edytuj metadane dokumentu lub strony" 276 | 277 | #: lib/gui/main.py 278 | msgid "Enter path to your favourite text editor." 279 | msgstr "Podaj ścieżkę do edytora tekstowego." 280 | 281 | #: lib/gui/main.py 282 | msgid "Error" 283 | msgstr "Błąd" 284 | 285 | #: lib/gui/maparea_properties.py 286 | msgid "Etched in" 287 | msgstr "Wklęsły" 288 | 289 | #: lib/gui/maparea_properties.py 290 | msgid "Etched out" 291 | msgstr "Wypukły" 292 | 293 | #: lib/gui/main.py 294 | #, python-format 295 | msgid "" 296 | "External edit failed:\n" 297 | "%s" 298 | msgstr "" 299 | "Zewnętrzna edycja nie powiodła się:\n" 300 | "%s" 301 | 302 | #: lib/gui/main.py 303 | msgid "External editor…" 304 | msgstr "Zewnętrzny edytor…" 305 | 306 | #: lib/gui/main.py 307 | msgid "Fit &page" 308 | msgstr "D&opasuj do okna" 309 | 310 | #: lib/gui/main.py 311 | msgid "Fit &width" 312 | msgstr "Dopasuj &szerokość" 313 | 314 | #: lib/gui/flatten_text.py 315 | msgid "Flatten text" 316 | msgstr "Spłaszcz strukturę tekstu" 317 | 318 | #: lib/gui/main.py 319 | msgid "Go to page" 320 | msgstr "Idź do strony" 321 | 322 | #: lib/gui/maparea_properties.py 323 | msgid "Highlight color" 324 | msgstr "Kolor podświetlenia" 325 | 326 | #: lib/gui/maparea_properties.py 327 | msgid "Highlight color and opacity" 328 | msgstr "Kolor i przezroczystość podświetlenia" 329 | 330 | #: lib/gui/main.py 331 | msgid "Hyperlinks" 332 | msgstr "Hiperłącza" 333 | 334 | #: lib/gui/main.py 335 | msgid "Increase the magnification" 336 | msgstr "Zwiększ powiększenie" 337 | 338 | #: lib/gui/main.py 339 | msgid "Jump to first document page" 340 | msgstr "Przejdź do pierwszej strony dokumentu" 341 | 342 | #: lib/gui/main.py 343 | msgid "Jump to last document page" 344 | msgstr "Przejdź do ostatniej strony dokumentu" 345 | 346 | #: lib/gui/main.py 347 | msgid "Jump to next document page" 348 | msgstr "Przejdź do następnej strony dokumentu" 349 | 350 | #: lib/gui/main.py 351 | msgid "Jump to page…" 352 | msgstr "Przejdź do strony…" 353 | 354 | #: lib/gui/main.py 355 | msgid "Jump to previous document page" 356 | msgstr "Przejdź do poprzedniej strony dokumentu" 357 | 358 | #: lib/gui/main.py 359 | msgid "License" 360 | msgstr "Licencja" 361 | 362 | #: lib/gui/maparea_properties.py 363 | msgid "Line" 364 | msgstr "Linia" 365 | 366 | #: lib/gui/maparea_properties.py 367 | msgid "Line color" 368 | msgstr "Kolor linii" 369 | 370 | #: lib/gui/maparea_properties.py 371 | msgid "Line width" 372 | msgstr "Szerokość linii" 373 | 374 | #: lib/gui/maparea_properties.py 375 | msgid "Line-specific properties" 376 | msgstr "Właściwości linii" 377 | 378 | #: lib/gui/main.py 379 | #, python-format 380 | msgid "Link: %s" 381 | msgstr "Łącze: %s" 382 | 383 | #: lib/gui/main.py 384 | #, python-format 385 | msgid "Magnify %d%%" 386 | msgstr "Powiększ do %d%%" 387 | 388 | #: lib/gui/maparea_properties.py 389 | msgid "Main properties" 390 | msgstr "Właściwości" 391 | 392 | #: lib/gui/main.py 393 | msgid "More information about this program" 394 | msgstr "Więcej informacji o tym programie" 395 | 396 | #: lib/gui/main.py 397 | msgid "Neither display the foreground layer nor the background layer" 398 | msgstr "Nie pokazuj pierwszego planu ani tła" 399 | 400 | #: lib/gui/main.py 401 | msgid "No text layer to edit." 402 | msgstr "Brak warstwy tekstowej do edycji." 403 | 404 | #: lib/gui/maparea_properties.py 405 | msgid "None" 406 | msgstr "Brak" 407 | 408 | #: lib/gui/main.py 409 | msgid "Number of lines changed." 410 | msgstr "Liczba linii uległa zmianie." 411 | 412 | #: lib/gui/main.py 413 | msgid "One &to one" 414 | msgstr "&Jeden do jednego" 415 | 416 | #: lib/gui/maparea_properties.py 417 | msgid "Opacity" 418 | msgstr "Przezroczystość" 419 | 420 | #: lib/gui/main.py 421 | msgid "Open &recent" 422 | msgstr "Ostatnio otwie&rane" 423 | 424 | #: lib/gui/main.py 425 | msgid "Open a DjVu document" 426 | msgstr "Otwórz dokument DjVu" 427 | 428 | #: lib/gui/main.py 429 | msgid "Outline" 430 | msgstr "Konspekt" 431 | 432 | #: lib/gui/maparea_properties.py 433 | msgid "Oval" 434 | msgstr "Elipsa" 435 | 436 | #: lib/gui/maparea_properties.py 437 | msgid "Overprinted annotation (hyperlink) properties" 438 | msgstr "Właściwości adnotacji (hiperłącza)" 439 | 440 | #: lib/gui/main.py 441 | #, python-format 442 | msgid "Page %(pageno)d of %(npages)d" 443 | msgstr "Strona %(pageno)d z %(npages)d" 444 | 445 | #: lib/gui/main.py 446 | #, python-format 447 | msgid "Page %d metadata" 448 | msgstr "Metadane strony %d" 449 | 450 | #: lib/gui/maparea_properties.py 451 | msgid "Polygon" 452 | msgstr "Wielokąt" 453 | 454 | #: lib/gui/maparea_properties.py 455 | msgid "Pushpin" 456 | msgstr "Pinezka" 457 | 458 | #: lib/gui/main.py 459 | msgid "Quit the application" 460 | msgstr "Zamknij aplikację" 461 | 462 | #: lib/gui/maparea_properties.py 463 | msgid "Rectangle" 464 | msgstr "Prostokąt" 465 | 466 | #: lib/gui/main.py 467 | msgid "Refresh the window" 468 | msgstr "Odśwież okno" 469 | 470 | #: lib/gui/flatten_text.py 471 | msgid "Remove details" 472 | msgstr "Usuń szczegóły" 473 | 474 | #: lib/gui/main.py 475 | msgid "Remove details from page text" 476 | msgstr "Usuń szczegóły z tekstu" 477 | 478 | #: lib/gui/main.py 479 | msgid "Remove whole document outline" 480 | msgstr "Usuń cały konspekt dokumentu" 481 | 482 | #: lib/gui/main.py 483 | msgid "Save the document" 484 | msgstr "Zapisz dokument" 485 | 486 | #: lib/gui/main.py 487 | msgid "Saving document" 488 | msgstr "Zapisywanie dokumentu" 489 | 490 | #: lib/gui/main.py 491 | #, python-format 492 | msgid "" 493 | "Saving document failed:\n" 494 | "%s" 495 | msgstr "" 496 | "Zapisanie dokumentu nie powiodło się:\n" 497 | "%s" 498 | 499 | #: lib/gui/main.py 500 | msgid "Saving the document, please wait…" 501 | msgstr "Zapisywanie dokumentu, proszę czekać…" 502 | 503 | #: lib/gui/flatten_text.py 504 | msgid "Scope" 505 | msgstr "Zakres" 506 | 507 | #: lib/gui/main.py 508 | msgid "Set full resolution magnification." 509 | msgstr "Dopasuj powiększenie do pełnej rozdzielczości." 510 | 511 | #: lib/gui/main.py 512 | msgid "Set magnification to fit page" 513 | msgstr "Dopasuj powiększenie do całej strony" 514 | 515 | #: lib/gui/main.py 516 | msgid "Set magnification to fit page width" 517 | msgstr "Dopasuj powiększenie do całej szerokości strony" 518 | 519 | #: lib/gui/main.py 520 | msgid "Setup an external editor" 521 | msgstr "Ustawienia zewnętrznego edytora" 522 | 523 | #: lib/gui/maparea_properties.py 524 | msgid "Shadow in" 525 | msgstr "Cień wewnątrz" 526 | 527 | #: lib/gui/maparea_properties.py 528 | msgid "Shadow out" 529 | msgstr "Cień na zewnątrz" 530 | 531 | #: lib/gui/maparea_properties.py 532 | msgid "Shape" 533 | msgstr "Kształt" 534 | 535 | #: lib/gui/main.py 536 | msgid "Show &sidebar" 537 | msgstr "Pokaż panel &boczny" 538 | 539 | #: lib/gui/main.py 540 | msgid "Show/hide the sidebar" 541 | msgstr "Pokaż/ukryj panel boczny" 542 | 543 | #: lib/gui/maparea_properties.py 544 | msgid "Solid color" 545 | msgstr "Jednolity kolor" 546 | 547 | #: lib/gui/main.py 548 | msgid "Stretch the image to the window size" 549 | msgstr "Rozciągnij obraz do rozmiarów okna" 550 | 551 | #: lib/gui/maparea_properties.py 552 | msgid "Target frame" 553 | msgstr "Ramka docelowa" 554 | 555 | #: lib/gui/main.py lib/gui/maparea_properties.py 556 | msgid "Text" 557 | msgstr "Tekst" 558 | 559 | #: lib/gui/maparea_properties.py 560 | msgid "Text color" 561 | msgstr "Kolor tekstu" 562 | 563 | #: lib/gui/maparea_properties.py 564 | msgid "Text-specific properties" 565 | msgstr "Właściwości tekstu" 566 | 567 | #: lib/gui/maparea_browser.py 568 | msgid "URI" 569 | msgstr "URI" 570 | 571 | #: lib/gui/main.py 572 | #, python-format 573 | msgid "Unhandled exception: %s" 574 | msgstr "Nieobsłużony wyjątek: %s" 575 | 576 | #: lib/gui/maparea_properties.py 577 | msgid "Width" 578 | msgstr "Grubość" 579 | 580 | #: lib/gui/maparea_properties.py 581 | msgid "XOR" 582 | msgstr "XOR" 583 | 584 | #: lib/gui/main.py 585 | msgid "Zoom &in" 586 | msgstr "Pow&iększ" 587 | 588 | #: lib/gui/main.py 589 | msgid "Zoom &out" 590 | msgstr "P&omniejsz" 591 | 592 | #: lib/gui/main.py 593 | msgid "[Text layer]" 594 | msgstr "[Warstwa tekstowa]" 595 | 596 | #: lib/gui/flatten_text.py 597 | msgid "all" 598 | msgstr "wszystko" 599 | 600 | #: lib/gui/flatten_text.py 601 | msgid "all pages" 602 | msgstr "wszystkie strony" 603 | 604 | #: lib/i18n.py 605 | msgid "bookmarks" 606 | msgstr "zakładki" 607 | 608 | #: lib/i18n.py 609 | msgid "char" 610 | msgstr "znak" 611 | 612 | #: lib/gui/flatten_text.py 613 | msgid "characters" 614 | msgstr "znaki" 615 | 616 | #: lib/i18n.py 617 | msgid "column" 618 | msgstr "łam" 619 | 620 | #: lib/gui/flatten_text.py 621 | msgid "columns" 622 | msgstr "łamy" 623 | 624 | #: lib/gui/flatten_text.py 625 | msgid "current page" 626 | msgstr "bieżąca strona" 627 | 628 | #: lib/gui/metadata.py 629 | msgid "key" 630 | msgstr "klucz" 631 | 632 | #: lib/i18n.py 633 | msgid "line" 634 | msgstr "linia" 635 | 636 | #: lib/gui/flatten_text.py 637 | msgid "lines" 638 | msgstr "linie" 639 | 640 | #: lib/i18n.py 641 | msgid "page" 642 | msgstr "strona" 643 | 644 | #: lib/i18n.py 645 | msgid "para" 646 | msgstr "akapit" 647 | 648 | #: lib/gui/flatten_text.py 649 | msgid "paragraphs" 650 | msgstr "akapity" 651 | 652 | #: lib/i18n.py 653 | msgid "region" 654 | msgstr "region" 655 | 656 | #: lib/gui/flatten_text.py 657 | msgid "regions" 658 | msgstr "regiony" 659 | 660 | #: lib/gui/metadata.py 661 | msgid "value" 662 | msgstr "wartość" 663 | 664 | #: lib/i18n.py 665 | msgid "word" 666 | msgstr "słowo" 667 | 668 | #: lib/gui/flatten_text.py 669 | msgid "words" 670 | msgstr "słowa" 671 | -------------------------------------------------------------------------------- /po/ru.po: -------------------------------------------------------------------------------- 1 | # Russian translation for djvusmooth. 2 | # Copyright © 2010 Kyrill Detinov 3 | # This file is distributed under the same license as the djvusmooth package. 4 | # Kyrill Detinov , 2010. 5 | # Kyrill Detinov , 2011, 2012, 2013. 6 | # 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: djvusmooth 0.3.1\n" 10 | "Report-Msgid-Bugs-To: Jakub Wilk \n" 11 | "POT-Creation-Date: 2015-09-15 10:51+0200\n" 12 | "PO-Revision-Date: 2013-02-21 18:33+0300\n" 13 | "Last-Translator: Kyrill Detinov \n" 14 | "Language-Team: openSUSE Translation Team\n" 15 | "Language: ru\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: lib/gui/main.py 21 | msgid "&About" 22 | msgstr "&О программе" 23 | 24 | #: lib/gui/main.py 25 | msgid "&Background" 26 | msgstr "&Фон" 27 | 28 | #: lib/gui/main.py 29 | msgid "&Bookmark this page" 30 | msgstr "Добавить &закладку на эту страницу" 31 | 32 | #: lib/gui/main.py 33 | msgid "&Close" 34 | msgstr "&Закрыть" 35 | 36 | #: lib/gui/main.py 37 | msgid "&Color" 38 | msgstr "&Цвет" 39 | 40 | #: lib/gui/main.py 41 | msgid "&Edit" 42 | msgstr "&Правка" 43 | 44 | #: lib/gui/main.py 45 | msgid "&External editor" 46 | msgstr "&Внешний редактор" 47 | 48 | #: lib/gui/main.py 49 | msgid "&File" 50 | msgstr "&Файл" 51 | 52 | #: lib/gui/main.py 53 | msgid "&First page" 54 | msgstr "Перв&ая страница" 55 | 56 | #: lib/gui/main.py 57 | msgid "&Flatten" 58 | msgstr "&Упростить структуру" 59 | 60 | #: lib/gui/main.py 61 | msgid "&Foreground" 62 | msgstr "&Передний план" 63 | 64 | #: lib/gui/main.py 65 | msgid "&Go" 66 | msgstr "П&ерейти" 67 | 68 | #: lib/gui/main.py 69 | msgid "&Go to page…" 70 | msgstr "Перейти &на страницу…" 71 | 72 | #: lib/gui/main.py 73 | msgid "&Help" 74 | msgstr "&Справка" 75 | 76 | #: lib/gui/main.py 77 | msgid "&Hyperlinks" 78 | msgstr "&Ссылки" 79 | 80 | #: lib/gui/main.py 81 | msgid "&Image" 82 | msgstr "&Изображение" 83 | 84 | #: lib/gui/main.py 85 | msgid "&Last page" 86 | msgstr "Последн&яя страница" 87 | 88 | #: lib/gui/main.py 89 | msgid "&Metadata" 90 | msgstr "&Метаданные" 91 | 92 | #: lib/gui/maparea_menu.py 93 | msgid "&New hyperlink…" 94 | msgstr "&Новая ссылка…" 95 | 96 | #: lib/gui/main.py 97 | msgid "&Next page" 98 | msgstr "&Следующая страница" 99 | 100 | #: lib/gui/main.py 101 | msgid "&Non-raster data" 102 | msgstr "&Нерастровые данные" 103 | 104 | #: lib/gui/main.py 105 | msgid "&None" 106 | msgstr "&Ничего" 107 | 108 | #: lib/gui/main.py 109 | msgid "&Open" 110 | msgstr "&Открыть" 111 | 112 | #: lib/gui/main.py 113 | msgid "&Outline" 114 | msgstr "&Оглавление" 115 | 116 | #: lib/gui/main.py 117 | msgid "&Previous page" 118 | msgstr "&Предыдущая страница" 119 | 120 | #: lib/gui/maparea_menu.py 121 | msgid "&Properties…" 122 | msgstr "&Свойства…" 123 | 124 | #: lib/gui/main.py 125 | msgid "&Quit" 126 | msgstr "&Выйти" 127 | 128 | #: lib/gui/main.py 129 | msgid "&Refresh" 130 | msgstr "&Обновить" 131 | 132 | #: lib/gui/maparea_menu.py 133 | msgid "&Remove" 134 | msgstr "&Удалить" 135 | 136 | #: lib/gui/main.py 137 | msgid "&Remove all" 138 | msgstr "&Удалить всё" 139 | 140 | #: lib/gui/main.py 141 | msgid "&Save" 142 | msgstr "&Сохранить" 143 | 144 | #: lib/gui/main.py 145 | msgid "&Settings" 146 | msgstr "&Настройка" 147 | 148 | #: lib/gui/main.py 149 | msgid "&Stencil" 150 | msgstr "&Шаблон" 151 | 152 | #: lib/gui/main.py 153 | msgid "&Stretch" 154 | msgstr "&Растянуть" 155 | 156 | #: lib/gui/main.py 157 | msgid "&Text" 158 | msgstr "&Текст" 159 | 160 | #: lib/gui/main.py 161 | msgid "&View" 162 | msgstr "&Вид" 163 | 164 | #: lib/gui/main.py 165 | msgid "&Zoom" 166 | msgstr "&Масштаб" 167 | 168 | #: lib/gui/main.py 169 | msgid "(no title)" 170 | msgstr "(без названия)" 171 | 172 | #: lib/gui/main.py 173 | msgid "About…" 174 | msgstr "О программе…" 175 | 176 | #: lib/gui/main.py 177 | msgid "Add the current to document outline" 178 | msgstr "Добавить в оглавление" 179 | 180 | #: lib/gui/maparea_properties.py 181 | msgid "Always visible" 182 | msgstr "Всегда показывать" 183 | 184 | #: lib/gui/main.py 185 | msgid "" 186 | "An unhandled exception occurred. Ideally, this should not happen. Please report the bug to the author.\n" 187 | "\n" 188 | msgstr "" 189 | "Непредвиденная ситуация. Так не должно быть. Пожалуйста, сообщите об ошибке автору.\n" 190 | "\n" 191 | 192 | #: lib/gui/maparea_properties.py 193 | msgid "Arrow" 194 | msgstr "Стрелка" 195 | 196 | #: lib/gui/main.py 197 | msgid "Author" 198 | msgstr "Автор" 199 | 200 | #: lib/gui/maparea_properties.py 201 | msgid "Background color" 202 | msgstr "Цвет фона" 203 | 204 | #: lib/gui/maparea_properties.py 205 | msgid "Border" 206 | msgstr "Рамка" 207 | 208 | #: lib/gui/main.py 209 | msgid "Cannot edit text with character zones." 210 | msgstr "Невозможно редактировать текст с зоной символов" 211 | 212 | #: lib/gui/main.py 213 | msgid "Close the document" 214 | msgstr "Закрыть документ" 215 | 216 | #: lib/gui/maparea_browser.py lib/gui/maparea_properties.py 217 | msgid "Comment" 218 | msgstr "Примечание" 219 | 220 | #: lib/gui/main.py 221 | msgid "Decrease the magnification" 222 | msgstr "Уменьшить масштаб" 223 | 224 | #: lib/gui/main.py 225 | msgid "Display everything" 226 | msgstr "Показать всё" 227 | 228 | #: lib/gui/main.py 229 | msgid "Display only the background layer" 230 | msgstr "Показать только фон" 231 | 232 | #: lib/gui/main.py 233 | msgid "Display only the document bitonal stencil" 234 | msgstr "Показать только битональный шаблон" 235 | 236 | #: lib/gui/main.py 237 | msgid "Display only the foreground layer" 238 | msgstr "Показать только передний план" 239 | 240 | #: lib/gui/main.py 241 | msgid "Display overprinted annotations" 242 | msgstr "Показывать комментарии (ссылки)" 243 | 244 | #: lib/gui/main.py 245 | msgid "Display the text layer" 246 | msgstr "Показать текстовый слой" 247 | 248 | #: lib/gui/main.py 249 | msgid "DjVu files (*.djvu, *.djv)|*.djvu;*.djv|All files|*" 250 | msgstr "DjVu файлы (*.djvu, *.djv)|*.djvu;*.djv|All files|*" 251 | 252 | #: lib/gui/main.py 253 | msgid "Do you want to save your changes?" 254 | msgstr "Вы хотите сохранить изменения?" 255 | 256 | #: lib/gui/main.py 257 | msgid "Document metadata" 258 | msgstr "Метаданные документа" 259 | 260 | #: lib/gui/main.py 261 | msgid "Don't display non-raster data" 262 | msgstr "Не показывать нерастровые данные" 263 | 264 | #: lib/gui/main.py 265 | msgid "Edit document outline in an external editor" 266 | msgstr "Редактировать оглавление документа в редакторе" 267 | 268 | #: lib/gui/metadata.py 269 | msgid "Edit metadata" 270 | msgstr "Изменить метаданные" 271 | 272 | #: lib/gui/main.py 273 | msgid "Edit page text in an external editor" 274 | msgstr "Редактировать во внешнем редакторе" 275 | 276 | #: lib/gui/main.py 277 | msgid "Edit the document or page metadata" 278 | msgstr "Изменить документ или метаданные" 279 | 280 | #: lib/gui/main.py 281 | msgid "Enter path to your favourite text editor." 282 | msgstr "Путь к текстовому редактору" 283 | 284 | #: lib/gui/main.py 285 | msgid "Error" 286 | msgstr "Ошибка" 287 | 288 | #: lib/gui/maparea_properties.py 289 | msgid "Etched in" 290 | msgstr "Вогнутая" 291 | 292 | #: lib/gui/maparea_properties.py 293 | msgid "Etched out" 294 | msgstr "Выпуклая" 295 | 296 | #: lib/gui/main.py 297 | #, python-format 298 | msgid "" 299 | "External edit failed:\n" 300 | "%s" 301 | msgstr "" 302 | "Ошибка внешнего редактирования:\n" 303 | "%s" 304 | 305 | #: lib/gui/main.py 306 | msgid "External editor…" 307 | msgstr "&Внешний редактор…" 308 | 309 | #: lib/gui/main.py 310 | msgid "Fit &page" 311 | msgstr "Страница &целиком" 312 | 313 | #: lib/gui/main.py 314 | msgid "Fit &width" 315 | msgstr "По &ширине страницы" 316 | 317 | #: lib/gui/flatten_text.py 318 | msgid "Flatten text" 319 | msgstr "Упростить структуру текста" 320 | 321 | #: lib/gui/main.py 322 | msgid "Go to page" 323 | msgstr "Перейти к странице" 324 | 325 | #: lib/gui/maparea_properties.py 326 | msgid "Highlight color" 327 | msgstr "Подсветка" 328 | 329 | #: lib/gui/maparea_properties.py 330 | msgid "Highlight color and opacity" 331 | msgstr "Цвет и прозрачность" 332 | 333 | #: lib/gui/main.py 334 | msgid "Hyperlinks" 335 | msgstr "Ссылки" 336 | 337 | #: lib/gui/main.py 338 | msgid "Increase the magnification" 339 | msgstr "Увеличить масштаб" 340 | 341 | #: lib/gui/main.py 342 | msgid "Jump to first document page" 343 | msgstr "Перейти к первой странице" 344 | 345 | #: lib/gui/main.py 346 | msgid "Jump to last document page" 347 | msgstr "Перейти к последней странице" 348 | 349 | #: lib/gui/main.py 350 | msgid "Jump to next document page" 351 | msgstr "Перейти к следующей странице" 352 | 353 | #: lib/gui/main.py 354 | msgid "Jump to page…" 355 | msgstr "Перейти на страницу…" 356 | 357 | #: lib/gui/main.py 358 | msgid "Jump to previous document page" 359 | msgstr "Перейти к предыдущей странице" 360 | 361 | #: lib/gui/main.py 362 | msgid "License" 363 | msgstr "Лицензия" 364 | 365 | #: lib/gui/maparea_properties.py 366 | msgid "Line" 367 | msgstr "Линия" 368 | 369 | #: lib/gui/maparea_properties.py 370 | msgid "Line color" 371 | msgstr "Цвет линии" 372 | 373 | #: lib/gui/maparea_properties.py 374 | msgid "Line width" 375 | msgstr "Ширина линии" 376 | 377 | #: lib/gui/maparea_properties.py 378 | msgid "Line-specific properties" 379 | msgstr "Параметры, специфичные для строки" 380 | 381 | #: lib/gui/main.py 382 | #, python-format 383 | msgid "Link: %s" 384 | msgstr "Ссылка: %s" 385 | 386 | #: lib/gui/main.py 387 | #, python-format 388 | msgid "Magnify %d%%" 389 | msgstr "Увеличение %d%%" 390 | 391 | #: lib/gui/maparea_properties.py 392 | msgid "Main properties" 393 | msgstr "Основные свойства" 394 | 395 | #: lib/gui/main.py 396 | msgid "More information about this program" 397 | msgstr "Больше информации о программе" 398 | 399 | #: lib/gui/main.py 400 | msgid "Neither display the foreground layer nor the background layer" 401 | msgstr "Не показывать фон и передний план" 402 | 403 | #: lib/gui/main.py 404 | msgid "No text layer to edit." 405 | msgstr "Нет текстового слоя для редактирования" 406 | 407 | #: lib/gui/maparea_properties.py 408 | msgid "None" 409 | msgstr "Ничего" 410 | 411 | #: lib/gui/main.py 412 | msgid "Number of lines changed." 413 | msgstr "Количество строк изменено" 414 | 415 | #: lib/gui/main.py 416 | msgid "One &to one" 417 | msgstr "&Один к одному" 418 | 419 | #: lib/gui/maparea_properties.py 420 | msgid "Opacity" 421 | msgstr "Прозрачность" 422 | 423 | #: lib/gui/main.py 424 | msgid "Open &recent" 425 | msgstr "&Недавние файлы" 426 | 427 | #: lib/gui/main.py 428 | msgid "Open a DjVu document" 429 | msgstr "Открыть DjVu документ" 430 | 431 | #: lib/gui/main.py 432 | msgid "Outline" 433 | msgstr "Оглавление" 434 | 435 | #: lib/gui/maparea_properties.py 436 | msgid "Oval" 437 | msgstr "Овал" 438 | 439 | #: lib/gui/maparea_properties.py 440 | msgid "Overprinted annotation (hyperlink) properties" 441 | msgstr "Вид комментария (ссылки)" 442 | 443 | #: lib/gui/main.py 444 | #, python-format 445 | msgid "Page %(pageno)d of %(npages)d" 446 | msgstr "Страница %(pageno)d из %(npages)d" 447 | 448 | #: lib/gui/main.py 449 | #, python-format 450 | msgid "Page %d metadata" 451 | msgstr "Метаданные страницы %d" 452 | 453 | #: lib/gui/maparea_properties.py 454 | msgid "Polygon" 455 | msgstr "Многоугольник" 456 | 457 | #: lib/gui/maparea_properties.py 458 | msgid "Pushpin" 459 | msgstr "Канцелярская кнопка" 460 | 461 | #: lib/gui/main.py 462 | msgid "Quit the application" 463 | msgstr "Выйти из программы" 464 | 465 | #: lib/gui/maparea_properties.py 466 | msgid "Rectangle" 467 | msgstr "Прямоугольник" 468 | 469 | #: lib/gui/main.py 470 | msgid "Refresh the window" 471 | msgstr "Обновить окно" 472 | 473 | #: lib/gui/flatten_text.py 474 | msgid "Remove details" 475 | msgstr "Удалить детали" 476 | 477 | #: lib/gui/main.py 478 | msgid "Remove details from page text" 479 | msgstr "Уменьшить детализацию текста" 480 | 481 | #: lib/gui/main.py 482 | msgid "Remove whole document outline" 483 | msgstr "Удалить оглавление всего документа" 484 | 485 | #: lib/gui/main.py 486 | msgid "Save the document" 487 | msgstr "Сохранить документ" 488 | 489 | #: lib/gui/main.py 490 | msgid "Saving document" 491 | msgstr "Сохранение документа" 492 | 493 | #: lib/gui/main.py 494 | #, python-format 495 | msgid "" 496 | "Saving document failed:\n" 497 | "%s" 498 | msgstr "" 499 | "Не удалось сохранить файл:\n" 500 | "%s" 501 | 502 | #: lib/gui/main.py 503 | msgid "Saving the document, please wait…" 504 | msgstr "Сохранение документа, подождите…" 505 | 506 | #: lib/gui/flatten_text.py 507 | msgid "Scope" 508 | msgstr "Область" 509 | 510 | #: lib/gui/main.py 511 | msgid "Set full resolution magnification." 512 | msgstr "Фактический размер" 513 | 514 | #: lib/gui/main.py 515 | msgid "Set magnification to fit page" 516 | msgstr "Показать страницу целиком" 517 | 518 | #: lib/gui/main.py 519 | msgid "Set magnification to fit page width" 520 | msgstr "Масштабировать по ширине страницы" 521 | 522 | #: lib/gui/main.py 523 | msgid "Setup an external editor" 524 | msgstr "Выбрать внешний редактор" 525 | 526 | #: lib/gui/maparea_properties.py 527 | msgid "Shadow in" 528 | msgstr "Затемнение внутрь" 529 | 530 | #: lib/gui/maparea_properties.py 531 | msgid "Shadow out" 532 | msgstr "Затемнение наружу" 533 | 534 | #: lib/gui/maparea_properties.py 535 | msgid "Shape" 536 | msgstr "Форма" 537 | 538 | #: lib/gui/main.py 539 | msgid "Show &sidebar" 540 | msgstr "Показать &боковую панель" 541 | 542 | #: lib/gui/main.py 543 | msgid "Show/hide the sidebar" 544 | msgstr "Показать/скрыть боковую панель" 545 | 546 | #: lib/gui/maparea_properties.py 547 | msgid "Solid color" 548 | msgstr "Сплошная заливка" 549 | 550 | #: lib/gui/main.py 551 | msgid "Stretch the image to the window size" 552 | msgstr "Масштабировать страницу по размеру окна" 553 | 554 | #: lib/gui/maparea_properties.py 555 | msgid "Target frame" 556 | msgstr "Цель фрейма" 557 | 558 | #: lib/gui/main.py lib/gui/maparea_properties.py 559 | msgid "Text" 560 | msgstr "Текст" 561 | 562 | #: lib/gui/maparea_properties.py 563 | msgid "Text color" 564 | msgstr "Цвет текста" 565 | 566 | #: lib/gui/maparea_properties.py 567 | msgid "Text-specific properties" 568 | msgstr "Параметры, специфичные для текста" 569 | 570 | #: lib/gui/maparea_browser.py 571 | msgid "URI" 572 | msgstr "URI" 573 | 574 | #: lib/gui/main.py 575 | #, python-format 576 | msgid "Unhandled exception: %s" 577 | msgstr "Непредвиденная ситуация: %s" 578 | 579 | #: lib/gui/maparea_properties.py 580 | msgid "Width" 581 | msgstr "Ширина" 582 | 583 | #: lib/gui/maparea_properties.py 584 | msgid "XOR" 585 | msgstr "XOR" 586 | 587 | #: lib/gui/main.py 588 | msgid "Zoom &in" 589 | msgstr "У&величить" 590 | 591 | #: lib/gui/main.py 592 | msgid "Zoom &out" 593 | msgstr "У&меньшить" 594 | 595 | #: lib/gui/main.py 596 | msgid "[Text layer]" 597 | msgstr "[Текстовый слой]" 598 | 599 | #: lib/gui/flatten_text.py 600 | msgid "all" 601 | msgstr "все" 602 | 603 | #: lib/gui/flatten_text.py 604 | msgid "all pages" 605 | msgstr "все страницы" 606 | 607 | #: lib/i18n.py 608 | msgid "bookmarks" 609 | msgstr "закладки" 610 | 611 | #: lib/i18n.py 612 | msgid "char" 613 | msgstr "символ" 614 | 615 | #: lib/gui/flatten_text.py 616 | msgid "characters" 617 | msgstr "символы" 618 | 619 | #: lib/i18n.py 620 | msgid "column" 621 | msgstr "столбец" 622 | 623 | #: lib/gui/flatten_text.py 624 | msgid "columns" 625 | msgstr "столбцы" 626 | 627 | #: lib/gui/flatten_text.py 628 | msgid "current page" 629 | msgstr "текущая страница" 630 | 631 | #: lib/gui/metadata.py 632 | msgid "key" 633 | msgstr "ключ" 634 | 635 | #: lib/i18n.py 636 | msgid "line" 637 | msgstr "строка" 638 | 639 | #: lib/gui/flatten_text.py 640 | msgid "lines" 641 | msgstr "строки" 642 | 643 | #: lib/i18n.py 644 | msgid "page" 645 | msgstr "страница" 646 | 647 | #: lib/i18n.py 648 | msgid "para" 649 | msgstr "параграф" 650 | 651 | #: lib/gui/flatten_text.py 652 | msgid "paragraphs" 653 | msgstr "параграфы" 654 | 655 | #: lib/i18n.py 656 | msgid "region" 657 | msgstr "область" 658 | 659 | #: lib/gui/flatten_text.py 660 | msgid "regions" 661 | msgstr "области" 662 | 663 | #: lib/gui/metadata.py 664 | msgid "value" 665 | msgstr "значение" 666 | 667 | #: lib/i18n.py 668 | msgid "word" 669 | msgstr "слово" 670 | 671 | #: lib/gui/flatten_text.py 672 | msgid "words" 673 | msgstr "слова" 674 | -------------------------------------------------------------------------------- /private/check-po: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright © 2015 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | color() 17 | { 18 | sed -e 's/^/ /' \ 19 | | grep --color=auto '.*' 20 | } 21 | 22 | color_msgid() 23 | { 24 | sed -n -e '/^msgid/ { s/^/ /; p }' \ 25 | | grep --color=auto ' "[^"].*' 26 | } 27 | 28 | set -e 29 | rc=0 30 | for po in "$@" 31 | do 32 | ok=yes 33 | m=$(msgfmt --check -o /dev/null "$po" 2>&1 || true) 34 | if [ -n "$m" ] 35 | then 36 | [ -z "$ok" ] || printf '%s:\n' "$po" 37 | printf '%s:\n' '- errors from msgfmt --check' 38 | printf '%s' "$m" | color 39 | ok= 40 | fi 41 | if command -v i18nspector > /dev/null 42 | then 43 | m=$(i18nspector "$po") 44 | if [ -n "$m" ] 45 | then 46 | [ -z "$ok" ] || printf '%s:\n' "$po" 47 | printf '%s:\n' '- warnings from i18nspector' 48 | printf '%s' "$m" | color 49 | ok= 50 | fi 51 | fi 52 | m=$(msgattrib --no-wrap --only-fuzzy "$po") 53 | if [ -n "$m" ] 54 | then 55 | [ -z "$ok" ] || printf '%s:\n' "$po" 56 | printf '%s:\n' '- fuzzy messages' 57 | printf '%s' "$m" | color_msgid 58 | ok= 59 | fi 60 | m=$(msgattrib --no-wrap --untranslated "$po") 61 | if [ -n "$m" ] 62 | then 63 | [ -z "$ok" ] || printf '%s:\n' "$po" 64 | printf '%s:\n' '- untranslated messages' 65 | printf '%s' "$m" | color_msgid 66 | ok= 67 | fi 68 | if [ -z "$ok" ] 69 | then 70 | printf '\n' 71 | rc=1 72 | fi 73 | done 74 | exit $rc 75 | 76 | # vim:ts=4 sts=4 sw=4 et 77 | -------------------------------------------------------------------------------- /private/check-rst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright © 2016-2022 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as 9 | # published by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 | # for more details. 15 | 16 | set -e -u 17 | here=${0%/*} 18 | here=${here#./} 19 | root="$here/../" 20 | root=${root#private/../} 21 | rst2xml=$(command -v rst2xml) \ 22 | || rst2xml=$(command -v rst2xml.py) \ 23 | || { printf 'rst2xml not found\n' >&2; exit 1; } 24 | rst2xml=${rst2xml##*/} 25 | options='--input-encoding=UTF-8 --strict' 26 | if [ $# -eq 0 ] 27 | then 28 | grep -rwl 'ft=rst' "${root}doc" 29 | else 30 | printf '%s\n' "$@" 31 | fi | 32 | xargs -t -I{} "$rst2xml" $options {} /dev/null 33 | 34 | # vim:ts=4 sts=4 sw=4 et 35 | -------------------------------------------------------------------------------- /private/update-i18n: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # Copyright © 2009-2015 Jakub Wilk 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | project_name = $(shell head -n1 doc/changelog | cut -d' ' -f1) 17 | project_version = $(shell head -n1 doc/changelog | sed -n -e 's/.*(\([0-9.]\+\)).*/\1/ p') 18 | bugs_address = $(shell sed -n -e '/ -- \(.*\) .*/ {s//\1/p;q}' doc/changelog) 19 | 20 | po_files = $(wildcard po/*.po) 21 | mo_files = $(patsubst po/%.po,locale/%/LC_MESSAGES/$(project_name).mo,$(po_files)) 22 | source_files = $(shell find . -name '*.py' -o -name 'build' -prune -a -false) 23 | 24 | gettext_common_options = \ 25 | --add-location=file \ 26 | --sort-output \ 27 | --no-wrap \ 28 | 29 | xgettext_options = \ 30 | $(gettext_common_options) \ 31 | --language=python \ 32 | --keyword=_ --keyword=N_ \ 33 | --package-name=$(project_name) \ 34 | --package-version=$(project_version) \ 35 | --msgid-bugs-address='$(bugs_address)' \ 36 | 37 | msgmerge_options = \ 38 | $(gettext_common_options) \ 39 | --update \ 40 | --verbose \ 41 | 42 | .PHONY: all 43 | 44 | all: po/$(project_name).pot $(po_files) $(mo_files) 45 | 46 | po/$(project_name).pot: $(source_files) 47 | xgettext $(xgettext_options) $(source_files) -o $(@) 48 | 49 | %.po: po/$(project_name).pot 50 | msgmerge $(msgmerge_options) $(@) $(<) 51 | 52 | locale/%/LC_MESSAGES/$(project_name).mo: po/%.po 53 | python setup.py build_mo 54 | 55 | # vim:ts=4 sts=4 sw=4 noet 56 | -------------------------------------------------------------------------------- /private/update-version: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export version=${1:?"no version number provided"} 3 | set -e 4 | set -x 5 | dch -m -v "$version" -u low -c doc/changelog 6 | perl -pi -e 's/^__version__ = \047\K[0-9.]+/$ENV{version}/' lib/__init__.py 7 | perl -pi -e 's/ 4 | # 5 | # This file is part of djvusmooth. 6 | # 7 | # djvusmooth is free software; you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License version 2 as published 9 | # by the Free Software Foundation. 10 | # 11 | # djvusmooth is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | # more details. 15 | 16 | ''' 17 | *djvusmooth* is a graphical editor for DjVu documents. 18 | ''' 19 | 20 | exec {b''}.pop() # Python 2.7 is required 21 | 22 | import glob 23 | import os 24 | import re 25 | 26 | import distutils.core 27 | import distutils.errors 28 | from distutils.command.build import build as distutils_build 29 | from distutils.command.clean import clean as distutils_clean 30 | from distutils.command.sdist import sdist as distutils_sdist 31 | 32 | try: 33 | import distutils644 34 | except ImportError: 35 | pass 36 | else: 37 | distutils644.install() 38 | 39 | data_files = [] 40 | 41 | if os.name == 'posix': 42 | data_files += [ 43 | ('share/applications', ['extra/djvusmooth.desktop']) 44 | ] 45 | 46 | def get_version(): 47 | with open('doc/changelog', 'r') as file: 48 | line = file.readline() 49 | return line.split()[1].strip('()') 50 | 51 | class build_mo(distutils_build): 52 | 53 | description = 'build binary message catalogs' 54 | 55 | def run(self): 56 | if os.name != 'posix': 57 | return 58 | for poname in glob.iglob('po/*.po'): 59 | lang, _ = os.path.splitext(os.path.basename(poname)) 60 | modir = os.path.join('locale', lang, 'LC_MESSAGES') 61 | if not os.path.isdir(modir): 62 | os.makedirs(modir) 63 | moname = os.path.join(modir, 'djvusmooth.mo') 64 | command = ['msgfmt', '-o', moname, '--verbose', '--check', '--check-accelerators', poname] 65 | self.make_file([poname], moname, distutils.spawn.spawn, [command]) 66 | data_files.append((os.path.join('share', modir), [moname])) 67 | 68 | class check_po(distutils_build): 69 | 70 | description = 'perform some checks on message catalogs' 71 | 72 | def run(self): 73 | for poname in glob.iglob('po/*.po'): 74 | checkname = poname + '.sdist-check' 75 | with open(checkname, 'w'): 76 | pass 77 | try: 78 | for feature in 'untranslated', 'fuzzy': 79 | with open(checkname, 'w'): 80 | pass 81 | command = ['msgattrib', '--' + feature, '-o', checkname, poname] 82 | distutils.spawn.spawn(command) 83 | with open(checkname) as file: 84 | entries = file.read() 85 | if entries: 86 | raise IOError(None, '{po} has {n} entries'.format(po=poname, n=feature)) 87 | finally: 88 | os.unlink(checkname) 89 | 90 | class build_doc(distutils_build): 91 | 92 | description = 'build documentation' 93 | 94 | _url_regex = re.compile( 95 | r'^(\\%https?://.*)', 96 | re.MULTILINE 97 | ) 98 | 99 | _date_regex = re.compile( 100 | '"(?P[0-9]{2})/(?P[0-9]{2})/(?P[0-9]{4})"' 101 | ) 102 | 103 | def build_man(self, manname, commandline): 104 | self.spawn(commandline) 105 | with open(manname, 'r+') as file: 106 | contents = file.read() 107 | # Format URLs: 108 | contents = self._url_regex.sub( 109 | lambda m: r'\m[blue]\fI{0}\fR\m[]'.format(*m.groups()), 110 | contents, 111 | ) 112 | # Use RFC 3339 date format: 113 | contents = self._date_regex.sub( 114 | lambda m: '{year}-{month}-{day}'.format(**m.groupdict()), 115 | contents 116 | ) 117 | file.seek(0) 118 | file.truncate() 119 | file.write(contents) 120 | 121 | def run(self): 122 | if os.name != 'posix': 123 | return 124 | xmlname = 'doc/manpage.xml' 125 | manname = 'doc/djvusmooth.1' 126 | command = [ 127 | 'xsltproc', '--nonet', 128 | '--param', 'man.authors.section.enabled', '0', 129 | '--param', 'man.charmap.use.subset', '0', 130 | '--param', 'man.font.links', '"I"', 131 | '--output', 'doc/', 132 | 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl', 133 | xmlname, 134 | ] 135 | self.make_file([xmlname], manname, self.build_man, [manname, command]) 136 | data_files.append(('share/man/man1', [manname])) 137 | 138 | distutils_build.sub_commands[:0] = [ 139 | ('build_doc', None), 140 | ('build_mo', None) 141 | ] 142 | 143 | class clean(distutils_clean): 144 | 145 | def run(self): 146 | distutils_clean.run(self) 147 | if not self.all: 148 | return 149 | for manname in glob.iglob('doc/*.1'): 150 | with open(manname, 'r') as file: 151 | stamp = file.readline() 152 | if stamp != sdist.manpage_stamp: 153 | self.execute(os.unlink, [manname], 'removing {0}'.format(manname)) 154 | 155 | class sdist(distutils_sdist): 156 | 157 | manpage_stamp = '''.\\" [created by setup.py sdist]\n''' 158 | 159 | def run(self): 160 | self.run_command('build_doc') 161 | self.run_command('check_po') 162 | self.run_command('build_mo') 163 | return distutils_sdist.run(self) 164 | 165 | def _rewrite_manpage(self, manname): 166 | with open(manname, 'r') as file: 167 | contents = file.read() 168 | os.unlink(manname) 169 | with open(manname, 'w') as file: 170 | file.write(self.manpage_stamp) 171 | file.write(contents) 172 | 173 | def _maybe_move_file(self, base_dir, src, dst): 174 | src = os.path.join(base_dir, src) 175 | dst = os.path.join(base_dir, dst) 176 | if os.path.exists(src): 177 | self.move_file(src, dst) 178 | 179 | def make_release_tree(self, base_dir, files): 180 | distutils_sdist.make_release_tree(self, base_dir, files) 181 | for manname in glob.iglob(os.path.join(base_dir, 'doc/*.1')): 182 | self.execute(self._rewrite_manpage, [manname], 'rewriting {0}'.format(manname)) 183 | self._maybe_move_file(base_dir, 'COPYING', 'doc/COPYING') 184 | 185 | classifiers = ''' 186 | Development Status :: 4 - Beta 187 | Environment :: X11 Applications :: GTK 188 | Intended Audience :: End Users/Desktop 189 | License :: OSI Approved :: GNU General Public License (GPL) 190 | Operating System :: OS Independent 191 | Programming Language :: Python 192 | Programming Language :: Python :: 2 193 | Programming Language :: Python :: 2.7 194 | Topic :: Text Processing 195 | Topic :: Multimedia :: Graphics 196 | '''.strip().splitlines() 197 | 198 | distutils.core.setup( 199 | name='djvusmooth', 200 | version=get_version(), 201 | license='GNU GPL 2', 202 | description='graphical editor for DjVu', 203 | long_description=__doc__.strip(), 204 | classifiers=classifiers, 205 | url='https://jwilk.net/software/djvusmooth', 206 | author='Jakub Wilk', 207 | author_email='jwilk@jwilk.net', 208 | packages=( 209 | ['djvusmooth'] + 210 | ['djvusmooth.{mod}'.format(mod=mod) for mod in ['gui', 'models', 'text']] 211 | ), 212 | package_dir=dict(djvusmooth='lib'), 213 | scripts=['djvusmooth'], 214 | data_files=data_files, 215 | cmdclass=dict( 216 | build_doc=build_doc, 217 | build_mo=build_mo, 218 | check_po=check_po, 219 | clean=clean, 220 | sdist=sdist, 221 | ), 222 | ) 223 | 224 | # vim:ts=4 sts=4 sw=4 et 225 | --------------------------------------------------------------------------------