diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/epspdf |
Initial commit
Diffstat (limited to 'support/epspdf')
-rw-r--r-- | support/epspdf/README | 17 | ||||
-rw-r--r-- | support/epspdf/doc/COPYING | 339 | ||||
-rw-r--r-- | support/epspdf/doc/Changelog | 189 | ||||
-rw-r--r-- | support/epspdf/doc/epspdf.pdf | bin | 0 -> 218565 bytes | |||
-rw-r--r-- | support/epspdf/doc/epspdf.texi | 680 | ||||
-rw-r--r-- | support/epspdf/doc/images/cnv_linux.png | bin | 0 -> 21912 bytes | |||
-rw-r--r-- | support/epspdf/doc/images/config_lnx.png | bin | 0 -> 18740 bytes | |||
-rw-r--r-- | support/epspdf/doc/images/epspdf.png | bin | 0 -> 4126 bytes | |||
-rw-r--r-- | support/epspdf/doc/images/logo.pdf | bin | 0 -> 5388 bytes | |||
-rw-r--r-- | support/epspdf/doc/images/logo.svg | 222 | ||||
-rw-r--r-- | support/epspdf/doc/images/main_wx.png | bin | 0 -> 12164 bytes | |||
-rw-r--r-- | support/epspdf/doc/pstexi.tex | 49 | ||||
-rw-r--r-- | support/epspdf/epspdf.help | 513 | ||||
-rwxr-xr-x | support/epspdf/epspdf.tlu | 2691 | ||||
-rw-r--r-- | support/epspdf/epspdf4tk.cmd | 1 | ||||
-rwxr-xr-x | support/epspdf/epspdftk.tcl | 737 |
16 files changed, 5438 insertions, 0 deletions
diff --git a/support/epspdf/README b/support/epspdf/README new file mode 100644 index 0000000000..9869ec0941 --- /dev/null +++ b/support/epspdf/README @@ -0,0 +1,17 @@ +Epspdftk.tcl is a GUI ps/eps/pdf converter. Epspdf.tlu, its +command-line backend, can be used by itself. + +Both are scripts: epspdf.tlu is written in texlua and uses the +texlua script interpreter from TeX Live or MiKTeX. Epspdftk.tcl +requires Tcl/Tk, either a full installation or a tclkit runtime. TeX +Live already includes a tclkit runtime for Windows. + +Epspdftk.tcl can find the other files as long are they are all in +the same directory. epspdf4tk.cmd is needed only when running +epspdftk.tcl under Windows. + +Full documentation is available in the doc subdirectory. + +Windows MiKTeX users: you may prefer the epspdf-setup package, which +installs a version of epspdftk which includes a regular installer +and requires no software beyond MiKTeX. diff --git a/support/epspdf/doc/COPYING b/support/epspdf/doc/COPYING new file mode 100644 index 0000000000..1bf1526387 --- /dev/null +++ b/support/epspdf/doc/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/support/epspdf/doc/Changelog b/support/epspdf/doc/Changelog new file mode 100644 index 0000000000..cf31923aec --- /dev/null +++ b/support/epspdf/doc/Changelog @@ -0,0 +1,189 @@ +2019-02-01 0.6.4 + + pdf_crop: epdf replaced by pdfe if available (LuaTeX >= 1.9) + + Better handling of pdfs with negative coordinates in MediaBox and + some corner cases + + Ghostscript: support for later versions of pdf format + + Eps files with large boundingbox coordinates: added a -r300 + resolution parameter when asking Ghostscript to calculate their + boundingbox. Hopefully, this prevents grossly inaccurate results. + + Pdftops on Windows: always use pdftops from searchpath + + Some refactoring + + epspdftk: no more black console windows under windows + + epspdftk: use of ttk widgets; no more support for Tk versions + prior to 8.5. + + epspdftk: updated selection of viewers under `plain unix' + + Updates to the manual + +2016-05-06 0.6.3 + + Pdf page selection and cropping: simplified and updated the + generated TeX file to be compiled by LuaTeX. Now compatible with + both LuaTeX 0.95 and older versions. + + Windows: test whether pdftops is in TeX binary directory. If so, + do not offer a configuration option for the location of pdftops. + + Minor tweaks to the manual + + Windows executable once more based on Tclkits from Patrick Thoyts + at http://www.patthoyts.tk/tclkit/ + +2015-10-28 0.6.2 + + Replace obsolete luatex primitives \pdfpagewidth and + \pdfpageheight with \pagewidth and \pageheight respectively + + Minor tweaks to the manual + +2014-11-30 0.6.1 + + Windows ececutable based on a newer tclkit, from the KitCreator + project (http://kitcreator.rkeene.org/fossil/index). This + necessitated a new test for being a starpack, viz. by checking the + name of the executable, instead of the previous test for + tclkitpath. + + Epspdf now modifies the process searchpath and calls luatex, + ghostscript and pdftops by basename to sidestep problems with + spaces in path names, especially on Windows. + + Removed use of cmd /c, which was a bug workaround for some old + version of LuaTeX. + + Some code cleanup(?): fewer globals, all non-trivial + initialization in one block following the function definitions. + + The temporary directory has been placed under %TEMP% under + Windows, and under either $TEMPDIR or /tmp on other systems. + + To give Windows >= Vista a better chance to remove temporary + files, epspdf first waits one second. + + Check for epswrite/eps2write ghostscript output devices. + + epspdftk.tcl: updated list of Unix PostScript- and pdf viewers. + +2013-02-07 0.6.0 + + Command-line component rewritten in TeXLua + + Texlua's epdf library replaces Ghostscript's pdf2dsc.ps. + + Grayscaling done by Ghostscript; both old grayscaling options now + do the same thing. + + When going from eps to pdf, a PostScript wrapper is no longer + used. Instead, Ghostscript now takes care of translating the eps + boundingbox to a pdf MediaBox. This implies that the hires + boundingbox will be used if available. + + Pdf crop adapted from Heiko Oberdiek's pdfcrop perl script + + ATM custom options for GhostScript and pdftops are no-ops. + + AppData is once more read from the environment, because texlua + does not provide access to the registry. + + AppleScript wrapper dropped since I no longer own a Mac. + +2011-02-27 0.5.3 + + Windows: AppData now read from registry, because this value may not + always be available as environment variable + + AppleScript wrapper now uses `do shell script' with some + heuristic path additions, instead of `tell application Terminal + do script' + +2010-12-25 0.5.2 + + Eliminate need for wrappers under Unix, as suggested by + Peter Breitenlohner; update manual + +2010-05-09 0.5.1 + + Bug fix: test for i_hrbb >= 0 instead of true + +2010-02-28 0.5 + + GUI module replaced with separate Tcl/Tk frontend. To this end, + epspdf.rb now has options for reading/writing settings over + stdin/stdout. + + Epspdf.rb now also aborts on Unix if Ghostscript is missing. + + Both logs and config moved to subdirectory: %APPDATA%/epspdf + under Windows, $HOME/.epspdf otherwise. On-demand log window. + + Logo/icon added + + Documentation updates + + Manual in info format is now part of the distribution + +2009-09-11 0.4.3 + + Patch for Ruby 1.9 compatibility (Ruben Stein). Patch for + compatibility with newer Ghostscript under Windows. Fix for option + gRAY with ps => pdf. `epspdf --version' now prints + version. Documentation updates. + +2009-09-11 0.4.2 + + More liberal documentation license. Slight documentation updates. + +2009-03-12 0.4.1 + + Documentation: added section on bitmaps. Windows setup program: + added missing Tcl/Tk licenses. Mac: double-clickable epspdftk.app + with included epspdftk replaces double-clickable epspdf.app + add-on. + +2009-03-02 0.4 + + Handles hires boundingbox. Can use MikTeX Ghostscript. Updated, + streamlined Ruby subset. Simpler testing of prerequisites. In + particular, no pdftops version testing. More tolerant boundingbox + parsing. Windows: View with... button. OS X: use open command for + viewing. Converting... text in main window. + +2008-07-28 0.3 + + TeX Live integration. Packaging for CTAN. Bug fixes: more tolerant + pdftops version testing; no inappropriate warning about missing + viewers under Mac OS X. + +2006-11-20 0.2.12 + + Bug fixes: log rotation now works. Solved quoting problems under + w2k by using short names. Minor updates to documentation. + +2006-11-03 0.2.11 + + Minor fixes. Changes in documentation. + +2006-10-29 0.2.10 + + Computed boundingbox padded by 1pt, to prevent some + cropping. Amount bb_spread is manually configurable. The File Open + dialog remembers the last directory visited (between function + calls, not between program sessions). Changes in documentation. + +2006-08-14 0.2.01 + + epspdf.app now packaged separately. Bug fix to Applescript applet + epspdf.app for starting up epspdftk under Mac OS X. + +2006-07-30 0.2 + + Original release diff --git a/support/epspdf/doc/epspdf.pdf b/support/epspdf/doc/epspdf.pdf Binary files differnew file mode 100644 index 0000000000..92ff2e1311 --- /dev/null +++ b/support/epspdf/doc/epspdf.pdf diff --git a/support/epspdf/doc/epspdf.texi b/support/epspdf/doc/epspdf.texi new file mode 100644 index 0000000000..1c87952cae --- /dev/null +++ b/support/epspdf/doc/epspdf.texi @@ -0,0 +1,680 @@ +\input texinfo @c -*-texinfo -*- +@c %**start of header +@setfilename epspdf.info +@settitle Epspdf and epspdftk User Guide 0.6 + +@dircategory TeX +@direntry +* EpsPDF: (epspdf). Portable GUI- and command-line EPS/PS/PDF conversion +@end direntry + +@afourpaper +@c %**end of header + +@c Actually, we do not produce html anymore +@ifhtml +@alias chapter = unnumbered +@alias section = unnumberedsec +@alias subsection = unnumberedsubsec +@end ifhtml + +@tex +\input pstexi +@end tex + +@iftex +@textfonts +@rm +@raggedbottom +@hyphenation{epspdf-tk epspdf} +@end iftex + +@set version 0.6.4 + +@setchapternewpage off +@paragraphindent none + +@copying +This manual is for epspdf and epspdftk, version @value{version}. + +Copyright @copyright{} 2006-2019 Siep Kroonenberg. + +@quotation +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without any warranty. +@end quotation +@end copying + +@titlepage +@sp 1 +@ifnotinfo +@center @image{images/logo,,.4in} +@end ifnotinfo +@sp 2 +@center @titlefont {Epspdf and epspdftk User Manual} +@sp 1 +@center A cross-platform GUI- and command-line converter for [e]ps and pdf +@sp 2 +@center Siep Kroonenberg (@email{siepo at bitmuis dot nl}) +@vskip 0pt plus 1filll +@insertcopying +@end titlepage + +@contents + +@ifnottex +@node Top, Usage, (dir), (dir) +@top Epspdf and epspdftk + +@insertcopying +@end ifnottex + +@menu +* Usage:: +* Command-line usage:: +* Notes on PostScript and pdf:: +* Bitmapped graphics for @LaTeX{} and pdflatex:: +* Troubleshooting:: +* Requirements:: +* Change history:: + +@detailmenu + --- The Detailed Node Listing --- + +Usage + +* Overview:: +* Viewing:: +* Conversion options:: +* Converting:: +* The configuration screen:: + +Command-line usage + +* Option summary:: + +Notes on PostScript and pdf + +* Bitmapped and vector:: +* Font embedding:: +* Eps preview headers:: +* Bounding- and other boxes:: +* Orientation:: +* Exporting PostScript or pdf from Windows programs:: + +Bitmapped graphics for @LaTeX{} and pdflatex + +* Linux:: +* Mac OS:: +* Windows:: + +Troubleshooting + +* No options for output format visible:: +* Ghostscript stackunderflow error:: +* Part of the graphic gets cut off:: +* Fonts look ugly:: +* The page has been converted to a bitmap:: +* Temporary files are not removed:: +* Resources for troubleshooting:: + +Change history + +* Version 6.4:: +* Version 0.6:: +* Version 0.5:: +* Version 0.4:: + +@end detailmenu +@end menu + +@node Usage, Command-line usage, Top, Top +@chapter Usage + +@menu +* Overview:: +* Viewing:: +* Conversion options:: +* Converting:: +* The configuration screen:: +@end menu + +@node Overview, Viewing, Usage, Usage +@section Overview + +Epspdftk is a GUI program that converts files between eps, pdf and +general PostScript in any direction. + +It lets you interactively select a file, set conversion options, and +save in the same of another format. A configuration screen offers +some additional conversion options. + +@ifnotinfo +@noindent @image{images/main_wx,,2.9in} +@iftex +@hfill +@end iftex +@image{images/cnv_linux,,2.9in} +@end ifnotinfo + +@node Viewing, Conversion options, Overview, Usage +@section Viewing + +The View button at the bottom of the screen invokes an +external viewer. + +@i{Windows and Mac OS:} Epspdftk simply tries to use the default Open +command. + +Under Linux the PostScript- and pdf viewers are +configurable; see @ref{The configuration screen}. + +The View button is grayed if epspdf thinks that there is no +previewer for the current file. + +@node Conversion options, Converting, Viewing, Usage +@section Conversion options + +@itemize +@item +@anchor{gray}Grayscaling + +@item +Compute tight boundingbox. This option is only available +if a single page is converted. + +@item +Page selection. The only possibilities are selecting a single page +or selecting all pages. Converting to eps implies selecting a single +page. +@end itemize + +@i{Note.} For general PostScript files, there is no quick way to +determine the number of pages, so the program may not check beforehand +whether you picked an existing page. If you don't like that, convert the +entire document to pdf first -- which will be done behind the scenes +anyway. + +Specifying options such as grayscaling or page selection may require +a multistep conversion. + +@node Converting, The configuration screen, Conversion options, Usage +@section Converting + +The Convert and save@dots{} button calls up a file save dialog. After a +successful conversion, the result becomes the new current file, so you +can judge the result by pressing the View button again -- if epspdftk +thinks that there is a suitable viewer. + +@node The configuration screen, , Converting, Usage +@section The configuration screen + +@ifnotinfo +@image{images/config_lnx,3in} +@end ifnotinfo + +Epspdftk also has a configuration screen. The settings here are +preserved between sessions. + +@subsection Configuring viewers + +Under Unix, the preferred PostScript- and pdf viewers can be configured +in this screen. Epspdf looks for a number of PostScript- and pdf +viewers, from which you can select one, but you can also enter one +manually. + +For Windows and Mac OS there is no such configuration option. Epspdf +will use the program associated with the file type, which can be +configured outside epspdf. + +@subsection Options for converting to pdf +Double-check the setting ``Target use'' under ``Conversion to +pdf''. ``prepress'' is for pdfs which are going to be printed +commercially. The options prepress, printer and default will try to +embed all fonts. Often, printshops insist on this. + +On the other hand, you may prefer ``screen'' if file size is a +concern. See also the Ghostscript documentation, in particular Use.htm +and VectorDevices.htm. + +Specifying anything other than ``default'' may cause an additional +conversion step and possibly also loss of high-level structure. + +As to pdf versions: this is a trade-off between more features for higher +versions and better compatibility for lower versions. Versions below 1.4 +do not support transparency. Converting a page or graphic with +transparencies to version 1.3 will probably result in the whole page or +graphic getting rasterized. For prepress use, consult your printshop or +publisher. Otherwise leave the pdf version at ``default''. + +@subsection Options for converting to PostScript +For conversion to plain or Encapsulated PostScript, pdftops sometimes +does a better job of preserving fonts than Ghostscript. Under Windows, +current versions of both MiKTeX and TeX Live include pdftops. Still, you +may opt @i{not} to use pdftops even if it is available, in which case +Ghostscript will be used instead. + +@node Command-line usage, Notes on PostScript and pdf, Usage, Top +@chapter Command-line usage + +epspdf.tlu is the backend of epspdftk, but it can also be used +standalone. It shares configuration settings with epspdftk.tcl. + +The first parameter of the epspdftk GUI program is interpreted as +startup directory for the file browser. Epspdf itself has a more +elaborate command-line interface. + +Below, we assume that there is a suitable wrapper or symlink for epspdf +on your searchpath. This is the case if you installed epspdf as a TeX +Live package. + +Basic usage of epspdf itself: + +@example +epspdf [@var{options}] @var{infile} [@var{outfile}] +@end example + +@menu +* Option summary:: +@end menu + +@node Option summary, , Command-line usage, Command-line usage +@section Option summary + +Typing @kbd{epspdf --help} gives you the following summary: +@example +$ epspdf --help +Epspdf version 0.6.4 +Copyright (c) 2006-2019 Siep Kroonenberg + +Convert between [e]ps and pdf formats +Usage: epspdf[.tlu] [options] infile [outfile] +Default for outfile is file.pdf if infile is file.eps or file.ps +Default for outfile is file.eps if infile is file.pdf + +-p, --page, --pagenumber PNUM + Page number; must be a positive integer +-g, --grey, --gray, -G, --GREY, --GRAY + Convert to grayscale +-b, --bbox, --BoundingBox + Compute tight boundingbox +-T, --target TARGET + One of screen, ebook, printer, prepress or default +-N, --pdfversion VERSION + One of 1.2, 1.3, 1.4, 1.5, 1.6, 1.7 or default +-U Use pdftops if available +-I Reverses the above +-s, --save Save some settings to configuration file +-i, --info Info: display detected filetype and exit +-d Debug: do not remove temp files +-v, --version + Display version info and exit +-h, --help Display this help message and exit +@end example + +@node Notes on PostScript and pdf, Bitmapped graphics for @LaTeX{} and pdflatex, Command-line usage, Top +@chapter Notes on PostScript and pdf + +@menu +* Bitmapped and vector:: +* Font embedding:: +* Eps preview headers:: +* Bounding- and other boxes:: +* Orientation:: +* Exporting PostScript or pdf from Windows programs:: +@end menu + +@node Bitmapped and vector, Font embedding, Notes on PostScript and pdf, Notes on PostScript and pdf +@section Bitmapped and vector + +Pictures can be described either in terms of pixels, or more abstractly, +in terms of geometric shapes, fonts and text. + +Bitmapped or pixel-based graphics are appropriate for photographs and +screenshots, but less so for diagrams and spreadsheet-generated +graphics. + +A file in PostScript- or pdf format can contain both types of graphic +data. + +Vector graphics can be freely scaled without losing sharpness or +becoming pixellated. If bitmapped graphics are enlarged too much, +individual pixels become apparent. With low-resolution bitmaps this +happens sooner than with high-resolution bitmaps, but high-resolution +bitmaps have (much) larger file sizes, and take longer to process. + +So avoid converting vector to bitmap. However, converting from bitmap to +vector is also best avoided, since it is very hard to do well. + +Epspdf usually avoids conversion from vector to bitmap and never +converts the other way. With the screen- and ebook ``Target use'' +option, included bitmaps tend to be downsampled, i.e. reduced to a lower +resolution. + +@node Font embedding, Eps preview headers, Bitmapped and vector, Notes on PostScript and pdf +@section Font embedding + +When converting to pdf, Ghostscript handles font embedding differently +depending on the ``Target use'' option. According to the Ghostscript +documentation, it embeds all fonts without exception for all targets +except ``screen'', for which standard fonts such as Times may be +omitted. + +@node Eps preview headers, Bounding- and other boxes, Font embedding, Notes on PostScript and pdf +@section Eps preview headers + +Preview headers are quietly stripped from eps files. These preview +headers are used by e.g. desktop-publishing software to represent eps +files on screen without having to interpret the PostScript code +itself. Epspdf has no option to preserve or add them. + +@node Bounding- and other boxes, Orientation, Eps preview headers, Notes on PostScript and pdf +@section Bounding- and other boxes + +A PostScript file may have a page size and a boundingbox defined. A pdf +file may have a mediabox, a trimbox and various other boxes. Ghostscript +by itself normally converts the PostScript page -- which is anchored at +(0,0) -- to the pdf mediabox. + +Conversion to pdf usually translates the bottom-left corner to the (0,0) +origin and sets the @dots{}box to the size of the +graphic. Anything outside the @dots{}box should be cut off. + +@node Orientation, Exporting PostScript or pdf from Windows programs, Bounding- and other boxes, Notes on PostScript and pdf +@section Orientation + +Ghostscript may not be able to determine the right orientation of a +PostScript- or pdf file. This may result in part of a graphic being cut +off after conversion or even everything falling outside the page / +mediabox / boundingbox. + +@node Exporting PostScript or pdf from Windows programs, , Orientation, Notes on PostScript and pdf +@section Exporting PostScript or pdf from Windows programs + +Microsoft Office 2010 can now export to pdf, either the entire document +or a selection. This removes a major headache for Windows users. Windows +10 also includes a pdf printer. + +As a last resort on earlier Windows versions, you can ``print'' to a +PostScript file. From some programs, you can print a selection. A +suitable driver which comes with Windows is Generic / MS Publisher Color +Printer. Pay attention to the printer properties: choose ``Outline'' for +font downloading and avoid the ``Optimize for speed'' setting for +PostScript Output Option. In my tests, ``Encapsulated PostScript'' did +not look very promising either. Try e.g. ``Archive'' instead. These +options can be found under the Advanced button. + +@node Bitmapped graphics for @LaTeX{} and pdflatex, Troubleshooting, Notes on PostScript and pdf, Top +@chapter Bitmapped graphics for @LaTeX{} and pdflatex + +pdflatex can use graphics in .png format (best for screenshots) and .jpg +format (best for photographs) directly. However, for @LaTeX{} you are +stuck with .eps format. Tips for converting to .eps: + +@menu +* Linux:: +* Mac OS:: +* Windows:: +@end menu + +@node Linux, Mac OS, Bitmapped graphics for @LaTeX{} and pdflatex, Bitmapped graphics for @LaTeX{} and pdflatex +@section Linux + +@table @emph +@item sam2p +This command-line bitmap-to-PostScript/pdf conversion utility is +available from @url{http://code.google.com/p/sam2p/} and may already be +packaged for your distribution. It produces very small files: +@samp{sam2p image.png image.eps} +@item ImageMagick/convert +@command{convert} from the ImageMagick package is a command-line +utility: @samp{convert image.png image.eps} +@item The GIMP +This is the premier open source image editing program. It is often +pre-installed on Linux, and is also available for other platforms. The +GIMP can save in eps- and pdf format. +@end table + +@node Mac OS, Windows, Linux, Bitmapped graphics for @LaTeX{} and pdflatex +@section Mac OS + +Mac OS's built-in Preview application can read most bitmapped formats +and save them as pdf or PostScript. In fact, in many cases it is an +excellent alternative to epspdf. + +@node Windows, , Mac OS, Bitmapped graphics for @LaTeX{} and pdflatex +@section Windows + +I know of no built-in facility for converting bitmaps to eps or pdf, +apart from ``printing'' to PostScript or pdf. However, many image +editors can convert to eps or pdf. A couple of command-line utilities: +@example +sam2p image.png image.eps +@end example +or +@example +bmeps -c image.png image.eps +@end example +sam2p is distributed with TeX Live (Windows only), bmeps both with TeX +Live and with MikTeX. Without the @code{-c} option, bmeps produces a +grayscale image. It produces larger files than sam2p. + +With TeX Live, you can convert to eps by right-clicking an image in +Windows Explorer and ``open'' with @emph{bitmap2eps}, which uses sam2p +or bmeps in the background. + +@node Troubleshooting, Change history, Bitmapped graphics for @LaTeX{} and pdflatex, Top +@chapter Troubleshooting + +@menu +* No options for output format visible:: +* Ghostscript stackunderflow error:: +* Part of the graphic gets cut off:: +* Fonts look ugly:: +* The page has been converted to a bitmap:: +* Temporary files are not removed:: +* Resources for troubleshooting:: +@end menu + +@node No options for output format visible, Ghostscript stackunderflow error, Troubleshooting, Troubleshooting +@section No options for output format visible + +Widen the window, to make the output format radio buttons visible. + +@node Ghostscript stackunderflow error, Part of the graphic gets cut off, No options for output format visible, Troubleshooting +@section Ghostscript stackunderflow error + +Some Ghostscript versions, @emph{e.g.} 9.10, have an error in color +handling when converting to pdf: selecting ``printer'' as target may +lead to an error @code{stackunderflow in .setdistillerparams}. If you +run into this, use the ``default'' or ``prepress'' target instead. + +@node Part of the graphic gets cut off, Fonts look ugly, Ghostscript stackunderflow error, Troubleshooting +@section Part of the graphic gets cut off + +If the PostScript file was generated with the Windows PostScript driver, +experiment with the PostScript Output option. Don't choose Optimize for +Speed. + +@node Fonts look ugly, The page has been converted to a bitmap, Part of the graphic gets cut off, Troubleshooting +@section Fonts look ugly + +If Ghostscript has to do the conversion from pdf to ps then text may not +remain text, but may be replaced by bitmaps. Newer Ghostscript versions +tend to do better in this respect than older ones, but as to preserving +fonts, the pdftops utility may still handle more cases. It is part of the +xpdf suite and of the Poppler utilities. + +@node The page has been converted to a bitmap, Temporary files are not removed, Fonts look ugly, Troubleshooting +@section The page has been converted to a bitmap + +The usual cause is that the page contains features such as transparency which +are not supported by the target format. + +Otherwise, set pdf target use and target version both to ``default'' to +avoid unnecessary conversions: @code{-T default -N default} + +@node Temporary files are not removed, Resources for troubleshooting, The page has been converted to a bitmap, Troubleshooting +@section Temporary files are not removed + +@itemize +@item +Command-line: make sure that you did not specify the option @code{-d}. +@item +Epspdftk GUI: make sure that `Remove temp files' is checked. +@item +Windows: this platform is notorious for excessive file locking. I +already inserted a Windows-specific delay before attempting to delete +the temporary files, but this is not always enough. However, you should +have no trouble removing temporary files manually. +@end itemize + +@node Resources for troubleshooting, , Temporary files are not removed, Troubleshooting +@section Resources for troubleshooting + +@i{Logfile.} The GUI has a button for viewing log output. This same +output is also written to a file @file{epspdf.log}. For Linux/Unix/Mac +Mac OS this is in a subdirectory .epspdf of your home directory; for +Windows it is in a subdirectory epspdf of @file{%APPDATA%}. This APPDATA +directory may be @file{c:\Users\@var{your user +name}\AppData\Roaming}. Type +@example +echo %APPDATA% +@end example +in a Command Prompt window to find out which. + +The logfile lists all epspdf calls and all Ghostscript- and pdftops +calls plus error information. + +@i{Temporary files.} The temporary files may give clues as +well. Uncheck the button ``Remove temp files'', or for the +command-line version, give a -d parameter to keep the temporary +files. Check the log(file) as to which temporary files have been +created. + +@anchor{Documentation}@i{Ghostscript- and pdftops documentation.} For +Ghostscript, the most important files are VectorDevices.htm and Use.htm. For +pdftops, type @kbd{pdftops -h}. For Unix, there is also a man page, and +for Windows there is a file pdftops.txt in the distribution zip. TeX +Live includes the man page in pdf format: pdftops.pdf. + +@node Requirements +@appendix Requirements + +Epspdftk consists of a GUI front end and a command-line back end. + +The command-line component, which can be used separately from the front +end, requires ghostscript and a not too old @TeX{} installation with +luatex. It may further benefit from the presence of pdftops. MiKTeX and +TeX Live for Windows meet these requirements out of the box. + +The front end requires a Tcl/Tk installation, or at least a tclkit +runtime. TeX Live for Windows contains such a tclkit since late 2018, +and epspdftk as a TeX Live package will make use of it. The minimum +version is 8.5. + +For Windows there is an alternate epspdf-setup package on CTAN with an +embedded Tcl/Tk runtime and a regular Windows installer, which also +creates a menu shortcut and an uninstaller. + +@node Change history, , Troubleshooting, Top +@appendix Change history + +@menu +* Version 6.4:: +* Version 0.6:: +* Version 0.5:: +* Version 0.4:: +@end menu + +@node Version 6.4 +@section Version 6.4 + +Compatibility changes for luatex 1.9 and later. + +The location of pdftops is no longer configurable; it is only searched +for on the searchpath. + +Some corner cases should be handled more successfully. + +The GUI now requires Tcl/Tk version 8.5 or higher. On Windows, +epspdf.tlu is now invoked via a batchfile. This should prevent black +console windows popping up. + +@node Version 0.6, Version 0.5, Change history, Change history +@section Version 0.6 + +The command-line backend component has been rewritten in texlua and +therefore no longer needs an external scripting language. + +Grayscaling is now done by Ghostscript's color options for pdf +output. This also works for bitmaps. + +Croppping of pdfs is now accomplished by running luatex on a suitable +wrapper file (same method as Heiko Oberdiek's pdfcrop). Such a +conversion preserves advanced features which might otherwise get lost. + +I no longer try to provide an AppleScript wrapper. + +The current version has no provisions for custom Ghostscript- or pdftops +parameters. The corresponding command-line options are accepted but have +no effect. + +@node Version 0.5, Version 0.4, Version 0.6, Change history +@section Version 0.5 + +The GUI has been rewritten in Tcl/Tk, removing the dependence on the +Ruby/Tk interface library. + +The Windows installer now installs a small Ruby subset and the standard +epspdf distribution, but with epspdftk.tcl replaced with a starpack: a +single executable containing @file{epspdftk.tcl} and a Tcl/Tk +runtime. See @url{http://wiki.tcl.tk/52}. + +Epspdf now uses its own subdirectory for both the logfile and the +configuration file. For Linux/Unix/Mac OS this is +@file{$HOME/.epspdf}, for Windows it is @file{%APPDATA%\epspdf}. +On all supported platforms, settings are stored in the file +@file{config} in this directory. Under Windows, the registry is no +longer used for this. + +A button has been added to view log output. + +There is a second new button ``Remove temp files'', which is normally +checked, causing temporary files to be deleted after each +conversion. Unchecking this button may be useful for troubleshooting. In +previous versions, temporary files were deleted at the end of the entire +epspdftk session but that has become less practical now that the GUI and +epspdf itself have become two separate programs. + +The ``Open with@dots{}'' option for Windows has been dropped for technical +reasons. + +The ``--version'' option now prints the version string instead of +setting the desired pdf version. + +The ``--info'' option now also prints the number of pages for pdf +files. + +@node Version 0.4, , Version 0.5, Change history +@section Version 0.4 + +Hi-res boundingboxes are now supported. By default, conversion from eps +to pdf now uses the hires boundingbox as ``page'' to determine the page +dimensions of the pdf file. Other conversions preserve or generate a +hires boundingbox. + +Under Windows, the new version looks for an installed @TeX{} and will +use its private Ghostscript if it cannot find a separately installed +Ghostscript. @TeX{} Live's pdftops, being on the searchpath, will be +used unless epspdf finds another copy first. + +There is now a ``-v'' (lowercase) option to print the version string. + +@ifhtml +@section @url{index.html,Main page} +@end ifhtml + +@bye diff --git a/support/epspdf/doc/images/cnv_linux.png b/support/epspdf/doc/images/cnv_linux.png Binary files differnew file mode 100644 index 0000000000..d452692afc --- /dev/null +++ b/support/epspdf/doc/images/cnv_linux.png diff --git a/support/epspdf/doc/images/config_lnx.png b/support/epspdf/doc/images/config_lnx.png Binary files differnew file mode 100644 index 0000000000..a80400c209 --- /dev/null +++ b/support/epspdf/doc/images/config_lnx.png diff --git a/support/epspdf/doc/images/epspdf.png b/support/epspdf/doc/images/epspdf.png Binary files differnew file mode 100644 index 0000000000..db0f09c6ac --- /dev/null +++ b/support/epspdf/doc/images/epspdf.png diff --git a/support/epspdf/doc/images/logo.pdf b/support/epspdf/doc/images/logo.pdf Binary files differnew file mode 100644 index 0000000000..153b771fb2 --- /dev/null +++ b/support/epspdf/doc/images/logo.pdf diff --git a/support/epspdf/doc/images/logo.svg b/support/epspdf/doc/images/logo.svg new file mode 100644 index 0000000000..fa54577adc --- /dev/null +++ b/support/epspdf/doc/images/logo.svg @@ -0,0 +1,222 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1052.3622" + height="744.09448" + id="svg2" + version="1.1" + inkscape:version="0.47pre4 r22446" + sodipodi:docname="epspdf.svg" + style="display:inline"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective10" /> + <inkscape:perspective + id="perspective2921" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective2984" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective3898" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath2886"> + <path + id="path2888" + style="fill:none;stroke:#000000;stroke-width:1.15759587;display:inline" + d="m 662.69592,363.42152 c 0,148.23849 -120.17108,268.40953 -268.40958,268.40953 -148.2385,0 -268.40954,-120.17104 -268.40954,-268.40953 0,-148.23851 120.17104,-268.40955 268.40954,-268.40955 148.2385,0 268.40958,120.17104 268.40958,268.40955 z m -123.7647,-10e-6 c 0,-79.88516 -64.75972,-144.64488 -144.64488,-144.64488 -79.88517,0 -144.64489,64.75972 -144.64489,144.64488 0,79.88517 64.75972,144.64489 144.64489,144.64489 79.88516,0 144.64488,-64.75972 144.64488,-144.64489 z" /> + </clipPath> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.1295785" + inkscape:cx="404.62267" + inkscape:cy="380.57534" + inkscape:document-units="px" + inkscape:current-layer="layer2" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="1272" + inkscape:window-height="911" + inkscape:window-x="402" + inkscape:window-y="117" + inkscape:window-maximized="0" + inkscape:snap-global="false" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="false" + inkscape:snap-page="true" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Layer" + style="display:inline"> + <g + id="g3853" + clip-path="url(#clipPath2886)"> + <path + id="path3822" + d="m 423.25,469.15625 c -10.09348,1.24175 -20.18906,2.46673 -30.28125,3.71875 -18.4916,31.25922 -36.9755,62.52301 -55.46875,93.78125 18.55393,33.75824 37.05144,67.54777 55.59375,101.3125 27.78142,-3.72777 55.55723,-7.49833 83.34375,-11.1875 -26.58572,-27.90445 -53.14375,-55.83532 -79.71875,-83.75 9.84306,-34.73978 19.69161,-69.478 29.53125,-104.21875 -1,0.11458 -2,0.22917 -3,0.34375 z" + style="fill:#0b2817;stroke:none" /> + <path + style="fill:#d40000;stroke:none" + d="m 316.05617,71.823338 78.15822,81.661702 -28.70783,102.88966 -27.3834,11.06187 1.51539,-106.43282 -96.3054,-58.82396 72.72302,-30.356452 z" + id="path3830" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccccc" + id="path3832" + d="m 472.3726,655.52027 -78.15822,-81.66169 28.70783,-102.88968 27.3834,-11.06187 -1.51539,106.43283 96.3054,58.82396 -72.72302,30.35645 z" + style="fill:#0b2817;stroke:none" /> + <path + id="path3836" + d="m 241.96875,100.40625 c -21.83042,16.85792 -43.68483,33.68489 -65.5,50.5625 36.97454,10.86566 73.93656,21.77405 110.90625,32.65625 8.85895,34.99878 17.67974,70.00731 26.5625,105 8.90627,-6.74614 17.84062,-13.45586 26.78125,-20.15625 0.3408,-36.31262 0.75616,-72.62486 1.09375,-108.9375 -32.91375,-19.9839 -65.82147,-39.97811 -98.75,-59.9375 l -0.80274,0.59632 -0.29101,0.21618 z" + style="fill:#800000;stroke:none" /> + <path + id="path3838" + d="m 472.125,440.53125 c -8.13836,6.11062 -16.25394,12.25194 -24.40625,18.34375 -0.3408,36.31262 -0.75616,72.62486 -1.09375,108.9375 32.90907,20.00202 65.8493,39.95316 98.75,59.96875 22.18542,-17.1381 44.39191,-34.24901 66.5625,-51.40625 -36.97454,-10.86566 -73.93656,-21.77405 -110.90625,-32.65625 -8.84881,-34.99871 -17.65722,-70.00773 -26.53125,-105 -0.79167,0.60417 -1.58333,1.20833 -2.375,1.8125 z" + style="fill:none;stroke:none" /> + <path + style="fill:#800000;stroke:none" + d="m 180.60315,150.00273 108.51786,31.642 26.58312,103.45898 -18.18379,23.27156 -51.90404,-92.93123 -112.8149,-2.79034 47.80175,-62.65097 z" + id="path3842" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccccc" + id="path3844" + d="m 607.82562,577.34089 -108.51785,-31.642 -26.58313,-103.45898 18.18379,-23.27156 51.90405,92.93123 112.8149,2.79034 -47.80176,62.65097 z" + style="fill:#0055d4;stroke:none" /> + <path + id="path3848" + d="m 130.75,211.8125 c -10.49227,25.50931 -20.9624,51.02775 -31.46875,76.53125 37.45067,-9.08351 74.91791,-18.09905 112.375,-27.15625 25.1662,25.88587 50.33612,51.76813 75.5,77.65625 4.37516,-10.28715 8.74305,-20.57753 13.09375,-30.875 -17.81224,-31.63098 -35.64932,-63.24804 -53.46875,-94.875 -38.49971,-0.86603 -77.00012,-1.70338 -115.5,-2.5625 l -0.38302,0.92374 -0.14823,0.35751 z" + style="fill:#550000;stroke:none" /> + <path + id="path3850" + d="m 500.09375,391.28125 c -3.97605,9.36589 -7.97624,18.72165 -11.9375,28.09375 17.82214,31.63127 35.67073,63.24772 53.5,94.875 38.49971,0.86603 77.00012,1.70338 115.5,2.5625 10.6772,-25.93315 21.31525,-51.88246 32,-77.8125 -37.45067,9.08351 -74.91791,18.09905 -112.375,27.15625 -25.1662,-25.88587 -50.33612,-51.76813 -75.5,-77.65625 -0.39583,0.92708 -0.79167,1.85417 -1.1875,2.78125 z" + style="fill:#0044aa;stroke:none" /> + <path + style="fill:#550000;stroke:none" + d="m 102.38709,285.43458 109.80022,-26.85616 74.75114,76.30654 -4.11184,29.24566 -91.41583,-54.52878 -99.095738,53.99095 10.072048,-78.15821 z" + id="path3854" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccccc" + id="path3856" + d="m 686.04168,441.90904 -109.80022,26.85616 -74.75115,-76.30655 4.11184,-29.24566 91.41584,54.52879 99.09574,-53.99094 -10.07205,78.1582 z" + style="fill:#0044aa;stroke:none" /> + <path + id="path3860" + d="m 190.21875,307.53125 c -33.42637,18.33463 -66.85842,36.65894 -100.28125,55 3.727767,27.78142 7.498332,55.55723 11.1875,83.34375 27.89398,-26.57536 55.8145,-53.12291 83.71875,-79.6875 34.75009,9.833 69.49943,19.66867 104.25,29.5 -1.30917,-11.09907 -2.70355,-22.18816 -4.0625,-33.28125 -31.26916,-18.482 -62.54438,-36.95375 -93.8125,-55.4375 l -0.87158,0.49026 -0.12842,0.0722 z" + style="fill:none;stroke:none" /> + <path + id="path3862" + d="M 684.46875,284.1875 C 657.5,309.84375 630.53125,335.5 603.5625,361.15625 c -34.7397,-9.83292 -69.47847,-19.66913 -104.21875,-29.5 1.30917,11.09907 2.70355,22.18816 4.0625,33.28125 31.26916,18.482 62.54438,36.95375 93.8125,55.4375 33.74768,-18.54378 67.5271,-37.03031 101.28125,-55.5625 -3.72777,-27.78142 -7.49833,-55.55723 -11.1875,-83.34375 -0.94792,0.90625 -1.89583,1.8125 -2.84375,2.71875 z" + style="fill:#003380;stroke:none" /> + <path + style="fill:#2ca05a;stroke:none" + d="m 102.36592,441.83003 81.6617,-78.15822 102.88966,28.70783 11.06187,27.3834 -106.43282,-1.51539 -58.82396,96.3054 -30.35645,-72.72302 z" + id="path3866" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccccc" + id="path3868" + d="m 686.06285,285.5136 -81.66169,78.15822 -102.88968,-28.70783 -11.06187,-27.3834 106.43283,1.51539 58.82396,-96.3054 30.35645,72.72302 z" + style="fill:#003380;stroke:none" /> + <path + id="path3872" + d="m 189.46875,417.03125 c -19.77769,32.59589 -39.58367,65.17478 -59.34375,97.78125 17.08893,22.22516 34.23322,44.40895 51.375,66.59375 10.86566,-36.97454 21.77405,-73.93656 32.65625,-110.90625 35.00918,-8.85901 70.02824,-17.67932 105.03125,-26.5625 -6.7258,-8.9192 -13.45366,-17.83687 -20.1875,-26.75 -36.31263,-0.35053 -72.62485,-0.77776 -108.9375,-1.125 l -0.52256,0.8526 -0.0712,0.11615 z" + style="fill:#217844;stroke:none" /> + <path + id="path3874" + d="m 605.84375,149.6875 c -10.53125,35.71875 -21.0625,71.4375 -31.59375,107.15625 -34.99878,8.85895 -70.00731,17.67974 -105,26.5625 6.7258,8.9192 13.45366,17.83687 20.1875,26.75 36.31263,0.35053 72.62485,0.77776 108.9375,1.125 19.97403,-32.91341 39.95665,-65.82184 59.90625,-98.75 -17.07951,-22.22439 -34.21034,-44.41053 -51.34375,-66.59375 -0.36458,1.25 -0.72917,2.5 -1.09375,3.75 z" + style="fill:#002255;stroke:none" /> + <path + style="fill:#217844;stroke:none" + d="m 180.54531,577.28305 31.642,-108.51786 103.45898,-26.58312 23.27156,18.18379 -92.93123,51.90404 -2.79034,112.8149 -62.65097,-47.80175 z" + id="path3878" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccccc" + id="path3880" + d="m 607.88347,150.06058 -31.642,108.51785 -103.45898,26.58313 -23.27156,-18.18379 92.93123,-51.90405 2.79034,-112.8149 62.65097,47.80176 z" + style="fill:#002255;stroke:none" /> + <path + id="path3884" + d="m 337.6875,458.09375 c -31.3543,17.66642 -62.70751,35.3348 -94.0625,53 -0.85617,38.52053 -1.68305,77.04177 -2.53125,115.5625 25.94402,10.60347 51.85869,21.28092 77.78125,31.9375 -9.08351,-37.45067 -18.09905,-74.91791 -27.15625,-112.375 25.89614,-25.16635 51.78983,-50.33522 77.6875,-75.5 -10.28715,-4.37516 -20.57753,-8.74305 -30.875,-13.09375 l -0.84375,0.46875 z" + style="fill:#16502d;stroke:none" /> + <path + id="path3886" + d="m 470.5,72.5625 c 8.73958,36.1875 17.47917,72.375 26.21875,108.5625 -25.89614,25.16635 -51.78983,50.33522 -77.6875,75.5 10.28715,4.37516 20.57753,8.74305 30.875,13.09375 31.64123,-17.81253 63.26906,-35.64899 94.90625,-53.46875 0.85617,-38.52053 1.68305,-77.04177 2.53125,-115.5625 C 521.39973,90.084032 495.48506,79.406582 469.5625,68.75 c 0.3125,1.270833 0.625,2.541667 0.9375,3.8125 z" + style="fill:none;stroke:none" /> + <path + style="fill:#16502d;stroke:none" + d="M 315.97716,655.49911 289.121,545.69889 l 76.30654,-74.75115 29.24566,4.11184 -54.52878,91.41583 53.99095,99.09574 -78.15821,-10.07204 z" + id="path3890" + sodipodi:nodetypes="ccccccc" /> + <path + sodipodi:nodetypes="ccccccc" + id="path3892" + d="m 472.45162,71.844518 26.85616,109.800212 -76.30655,74.75116 -29.24566,-4.11184 54.52879,-91.41584 -53.99094,-99.095741 78.1582,10.072049 z" + style="fill:#ff0000;stroke:none" /> + <path + id="path3820" + d="m 393.96875,59.5625 c -27.32378,3.660109 -54.64045,7.374185 -81.96875,11 26.57646,27.903358 53.12273,55.8355 79.6875,83.75 -9.83292,34.7397 -19.66913,69.47847 -29.5,104.21875 11.09907,-1.30917 22.18816,-2.70355 33.28125,-4.0625 18.4916,-31.25922 36.9755,-62.52301 55.46875,-93.78125 C 432.38357,126.92926 413.88606,93.139727 395.34375,59.375 l -0.99083,0.135113 -0.38417,0.05239 z" + style="fill:#d40000;stroke:none" /> + </g> + <path + sodipodi:type="arc" + style="fill:none;stroke:none" + id="path2985" + sodipodi:cx="408.40744" + sodipodi:cy="364.15735" + sodipodi:rx="205.37555" + sodipodi:ry="205.37555" + d="m 613.78299,364.15735 a 205.37555,205.37555 0 1 1 -410.7511,0 205.37555,205.37555 0 1 1 410.7511,0 z" + transform="matrix(1.0258634,0,0,1.0258634,-23.842106,-8.5330716)" /> + </g> +</svg> diff --git a/support/epspdf/doc/images/main_wx.png b/support/epspdf/doc/images/main_wx.png Binary files differnew file mode 100644 index 0000000000..068dd2d8fa --- /dev/null +++ b/support/epspdf/doc/images/main_wx.png diff --git a/support/epspdf/doc/pstexi.tex b/support/epspdf/doc/pstexi.tex new file mode 100644 index 0000000000..63e86cb3e3 --- /dev/null +++ b/support/epspdf/doc/pstexi.tex @@ -0,0 +1,49 @@ +% change texinfo fonts +% usage: put the following code early in your file, +% but after \input texinfo + +%%%%%%%%%%%%%%%% +% @tex +% \input pstexi +% @end tex +% +% @textfonts +% @rm +%%%%%%%%%%%%%%%% + +% Set the font macro #1 to the font named #2, adding on the +% specified font prefix (normally `cm'). +% #3 is the font's design size, #4 is a scale factor +% \def\setfont#1#2#3#4{\font#1=\fontprefix#2#3 scaled #4} +% UPDATE: a new implementation uses a fifth encoding(?) parameter, +% which we simply ignore. +\newcount\atsize +\def\setfont#1#2#3#4#5{% + \def\mag{#4} + \global\atsize=\mag + \global\multiply\atsize #3 + \global\divide\atsize 1000 + \global\font#1=#2 at \atsize pt} + +% \fontprefix not usable outside cm/lm/ec +\gdef\fontprefix{} + +\gdef\rmshape{pplr7t} +\gdef\rmbshape{pplb7t} %where the normal face is bold +\gdef\bfshape{pplb7t} +\gdef\bxshape{pplb7t} +\gdef\ttshape{texnansi-lmtt10} +\gdef\ttbshape{texnansi-lmtk10} +\gdef\ttslshape{texnansi-lmtto10} +\gdef\itshape{pplri7t} +\gdef\itbshape{pplbi7t} +\gdef\slshape{pplri7t} +\gdef\slbshape{pplbi7t} +\gdef\sfshape{texnansi-lmss10} +\gdef\sfbshape{texnansi-lmssbx10} +\gdef\scshape{pplrc7t} +\gdef\scbshape{pplbc7t} + +\definetextfontsizexi + +\global\let\subtitlerm=\tenrm diff --git a/support/epspdf/epspdf.help b/support/epspdf/epspdf.help new file mode 100644 index 0000000000..c200849f0a --- /dev/null +++ b/support/epspdf/epspdf.help @@ -0,0 +1,513 @@ +Epspdf and epspdftk +1 Usage + 1.1 Overview + 1.2 Viewing + 1.3 Conversion options + 1.4 Converting + 1.5 The configuration screen + 1.5.1 Configuring viewers + 1.5.2 Options for converting to pdf + 1.5.3 Options for converting to PostScript +2 Command-line usage + 2.1 Option summary +3 Notes on PostScript and pdf + 3.1 Bitmapped and vector + 3.2 Font embedding + 3.3 Eps preview headers + 3.4 Bounding- and other boxes + 3.5 Orientation + 3.6 Exporting PostScript or pdf from Windows programs +4 Bitmapped graphics for LaTeX and pdflatex + 4.1 Linux + 4.2 Mac OS + 4.3 Windows +5 Troubleshooting + 5.1 No options for output format visible + 5.2 Ghostscript stackunderflow error + 5.3 Part of the graphic gets cut off + 5.4 Fonts look ugly + 5.5 The page has been converted to a bitmap + 5.6 Temporary files are not removed + 5.7 Resources for troubleshooting +Appendix A Requirements +Appendix B Change history + B.1 Version 6.4 + B.2 Version 0.6 + B.3 Version 0.5 + B.4 Version 0.4 +Epspdf and epspdftk +******************* + +This manual is for epspdf and epspdftk, version 0.6.4. + +Copyright (C) 2006-2019 Siep Kroonenberg. + + Copying and distribution of this file, with or without + modification, are permitted in any medium without royalty provided + the copyright notice and this notice are preserved. This file is + offered as-is, without any warranty. + +1 Usage +******* + +1.1 Overview +============ + +Epspdftk is a GUI program that converts files between eps, pdf and +general PostScript in any direction. + +It lets you interactively select a file, set conversion options, and +save in the same of another format. A configuration screen offers some +additional conversion options. + +1.2 Viewing +=========== + +The View button at the bottom of the screen invokes an external viewer. + +Windows and Mac OS: Epspdftk simply tries to use the default Open +command. + +Under Linux the PostScript- and pdf viewers are configurable; see *note +The configuration screen::. + +The View button is grayed if epspdf thinks that there is no previewer +for the current file. + +1.3 Conversion options +====================== + + * Grayscaling + + * Compute tight boundingbox. This option is only available if a + single page is converted. + + * Page selection. The only possibilities are selecting a single page + or selecting all pages. Converting to eps implies selecting a + single page. + +Note. For general PostScript files, there is no quick way to determine +the number of pages, so the program may not check beforehand whether you +picked an existing page. If you don't like that, convert the entire +document to pdf first - which will be done behind the scenes anyway. + +Specifying options such as grayscaling or page selection may require a +multistep conversion. + +1.4 Converting +============== + +The Convert and save... button calls up a file save dialog. After a +successful conversion, the result becomes the new current file, so you +can judge the result by pressing the View button again - if epspdftk +thinks that there is a suitable viewer. + +1.5 The configuration screen +============================ + +Epspdftk also has a configuration screen. The settings here are +preserved between sessions. + +1.5.1 Configuring viewers +------------------------- + +Under Unix, the preferred PostScript- and pdf viewers can be configured +in this screen. Epspdf looks for a number of PostScript- and pdf +viewers, from which you can select one, but you can also enter one +manually. + +For Windows and Mac OS there is no such configuration option. Epspdf +will use the program associated with the file type, which can be +configured outside epspdf. + +1.5.2 Options for converting to pdf +----------------------------------- + +Double-check the setting "Target use" under "Conversion to pdf". +"prepress" is for pdfs which are going to be printed commercially. The +options prepress, printer and default will try to embed all fonts. +Often, printshops insist on this. + +On the other hand, you may prefer "screen" if file size is a concern. +See also the Ghostscript documentation, in particular Use.htm and +VectorDevices.htm. + +Specifying anything other than "default" may cause an additional +conversion step and possibly also loss of high-level structure. + +As to pdf versions: this is a trade-off between more features for higher +versions and better compatibility for lower versions. Versions below +1.4 do not support transparency. Converting a page or graphic with +transparencies to version 1.3 will probably result in the whole page or +graphic getting rasterized. For prepress use, consult your printshop or +publisher. Otherwise leave the pdf version at "default". + +1.5.3 Options for converting to PostScript +------------------------------------------ + +For conversion to plain or Encapsulated PostScript, pdftops sometimes +does a better job of preserving fonts than Ghostscript. Under Windows, +current versions of both MiKTeX and TeX Live include pdftops. Still, +you may opt not to use pdftops even if it is available, in which case +Ghostscript will be used instead. + +2 Command-line usage +******************** + +epspdf.tlu is the backend of epspdftk, but it can also be used +standalone. It shares configuration settings with epspdftk.tcl. + +The first parameter of the epspdftk GUI program is interpreted as +startup directory for the file browser. Epspdf itself has a more +elaborate command-line interface. + +Below, we assume that there is a suitable wrapper or symlink for epspdf +on your searchpath. This is the case if you installed epspdf as a TeX +Live package. + +Basic usage of epspdf itself: + + epspdf [OPTIONS] INFILE [OUTFILE] + +2.1 Option summary +================== + +Typing 'epspdf --help' gives you the following summary: + $ epspdf --help + Epspdf version 0.6.4 + Copyright (c) 2006-2019 Siep Kroonenberg + + Convert between [e]ps and pdf formats + Usage: epspdf[.tlu] [options] infile [outfile] + Default for outfile is file.pdf if infile is file.eps or file.ps + Default for outfile is file.eps if infile is file.pdf + + -p, --page, --pagenumber PNUM + Page number; must be a positive integer + -g, --grey, --gray, -G, --GREY, --GRAY + Convert to grayscale + -b, --bbox, --BoundingBox + Compute tight boundingbox + -T, --target TARGET + One of screen, ebook, printer, prepress or default + -N, --pdfversion VERSION + One of 1.2, 1.3, 1.4, 1.5, 1.6, 1.7 or default + -U Use pdftops if available + -I Reverses the above + -s, --save Save some settings to configuration file + -i, --info Info: display detected filetype and exit + -d Debug: do not remove temp files + -v, --version + Display version info and exit + -h, --help Display this help message and exit + +3 Notes on PostScript and pdf +***************************** + +3.1 Bitmapped and vector +======================== + +Pictures can be described either in terms of pixels, or more abstractly, +in terms of geometric shapes, fonts and text. + +Bitmapped or pixel-based graphics are appropriate for photographs and +screenshots, but less so for diagrams and spreadsheet-generated +graphics. + +A file in PostScript- or pdf format can contain both types of graphic +data. + +Vector graphics can be freely scaled without losing sharpness or +becoming pixellated. If bitmapped graphics are enlarged too much, +individual pixels become apparent. With low-resolution bitmaps this +happens sooner than with high-resolution bitmaps, but high-resolution +bitmaps have (much) larger file sizes, and take longer to process. + +So avoid converting vector to bitmap. However, converting from bitmap +to vector is also best avoided, since it is very hard to do well. + +Epspdf usually avoids conversion from vector to bitmap and never +converts the other way. With the screen- and ebook "Target use" option, +included bitmaps tend to be downsampled, i.e. reduced to a lower +resolution. + +3.2 Font embedding +================== + +When converting to pdf, Ghostscript handles font embedding differently +depending on the "Target use" option. According to the Ghostscript +documentation, it embeds all fonts without exception for all targets +except "screen", for which standard fonts such as Times may be omitted. + +3.3 Eps preview headers +======================= + +Preview headers are quietly stripped from eps files. These preview +headers are used by e.g. desktop-publishing software to represent eps +files on screen without having to interpret the PostScript code itself. +Epspdf has no option to preserve or add them. + +3.4 Bounding- and other boxes +============================= + +A PostScript file may have a page size and a boundingbox defined. A pdf +file may have a mediabox, a trimbox and various other boxes. +Ghostscript by itself normally converts the PostScript page - which is +anchored at (0,0) - to the pdf mediabox. + +Conversion to pdf usually translates the bottom-left corner to the (0,0) +origin and sets the ...box to the size of the graphic. Anything outside +the ...box should be cut off. + +3.5 Orientation +=============== + +Ghostscript may not be able to determine the right orientation of a +PostScript- or pdf file. This may result in part of a graphic being cut +off after conversion or even everything falling outside the page / +mediabox / boundingbox. + +3.6 Exporting PostScript or pdf from Windows programs +===================================================== + +Microsoft Office 2010 can now export to pdf, either the entire document +or a selection. This removes a major headache for Windows users. +Windows 10 also includes a pdf printer. + +As a last resort on earlier Windows versions, you can "print" to a +PostScript file. From some programs, you can print a selection. A +suitable driver which comes with Windows is Generic / MS Publisher Color +Printer. Pay attention to the printer properties: choose "Outline" for +font downloading and avoid the "Optimize for speed" setting for +PostScript Output Option. In my tests, "Encapsulated PostScript" did +not look very promising either. Try e.g. "Archive" instead. These +options can be found under the Advanced button. + +4 Bitmapped graphics for LaTeX and pdflatex +******************************************* + +pdflatex can use graphics in .png format (best for screenshots) and .jpg +format (best for photographs) directly. However, for LaTeX you are +stuck with .eps format. Tips for converting to .eps: + +4.1 Linux +========= + +_sam2p_ + This command-line bitmap-to-PostScript/pdf conversion utility is + available from <http://code.google.com/p/sam2p/> and may already be + packaged for your distribution. It produces very small files: + 'sam2p image.png image.eps' +_ImageMagick/convert_ + 'convert' from the ImageMagick package is a command-line utility: + 'convert image.png image.eps' +_The GIMP_ + This is the premier open source image editing program. It is often + pre-installed on Linux, and is also available for other platforms. + The GIMP can save in eps- and pdf format. + +4.2 Mac OS +========== + +Mac OS's built-in Preview application can read most bitmapped formats +and save them as pdf or PostScript. In fact, in many cases it is an +excellent alternative to epspdf. + +4.3 Windows +=========== + +I know of no built-in facility for converting bitmaps to eps or pdf, +apart from "printing" to PostScript or pdf. However, many image editors +can convert to eps or pdf. A couple of command-line utilities: + sam2p image.png image.eps +or + bmeps -c image.png image.eps +sam2p is distributed with TeX Live (Windows only), bmeps both with TeX +Live and with MikTeX. Without the '-c' option, bmeps produces a +grayscale image. It produces larger files than sam2p. + +With TeX Live, you can convert to eps by right-clicking an image in +Windows Explorer and "open" with _bitmap2eps_, which uses sam2p or bmeps +in the background. + +5 Troubleshooting +***************** + +5.1 No options for output format visible +======================================== + +Widen the window, to make the output format radio buttons visible. + +5.2 Ghostscript stackunderflow error +==================================== + +Some Ghostscript versions, _e.g._ 9.10, have an error in color handling +when converting to pdf: selecting "printer" as target may lead to an +error 'stackunderflow in .setdistillerparams'. If you run into this, +use the "default" or "prepress" target instead. + +5.3 Part of the graphic gets cut off +==================================== + +If the PostScript file was generated with the Windows PostScript driver, +experiment with the PostScript Output option. Don't choose Optimize for +Speed. + +5.4 Fonts look ugly +=================== + +If Ghostscript has to do the conversion from pdf to ps then text may not +remain text, but may be replaced by bitmaps. Newer Ghostscript versions +tend to do better in this respect than older ones, but as to preserving +fonts, the pdftops utility may still handle more cases. It is part of +the xpdf suite and of the Poppler utilities. + +5.5 The page has been converted to a bitmap +=========================================== + +The usual cause is that the page contains features such as transparency +which are not supported by the target format. + +Otherwise, set pdf target use and target version both to "default" to +avoid unnecessary conversions: '-T default -N default' + +5.6 Temporary files are not removed +=================================== + + * Command-line: make sure that you did not specify the option '-d'. + * Epspdftk GUI: make sure that 'Remove temp files' is checked. + * Windows: this platform is notorious for excessive file locking. I + already inserted a Windows-specific delay before attempting to + delete the temporary files, but this is not always enough. + However, you should have no trouble removing temporary files + manually. + +5.7 Resources for troubleshooting +================================= + +Logfile. The GUI has a button for viewing log output. This same output +is also written to a file 'epspdf.log'. For Linux/Unix/Mac Mac OS this +is in a subdirectory .epspdf of your home directory; for Windows it is +in a subdirectory epspdf of '%APPDATA%'. This APPDATA directory may be +'c:\Users\YOUR USER NAME\AppData\Roaming'. Type + echo %APPDATA% +in a Command Prompt window to find out which. + +The logfile lists all epspdf calls and all Ghostscript- and pdftops +calls plus error information. + +Temporary files. The temporary files may give clues as well. Uncheck +the button "Remove temp files", or for the command-line version, give a +-d parameter to keep the temporary files. Check the log(file) as to +which temporary files have been created. + +Ghostscript- and pdftops documentation. For Ghostscript, the most +important files are VectorDevices.htm and Use.htm. For pdftops, type +'pdftops -h'. For Unix, there is also a man page, and for Windows there +is a file pdftops.txt in the distribution zip. TeX Live includes the +man page in pdf format: pdftops.pdf. + +Appendix A Requirements +*********************** + +Epspdftk consists of a GUI front end and a command-line back end. + +The command-line component, which can be used separately from the front +end, requires ghostscript and a not too old TeX installation with +luatex. It may further benefit from the presence of pdftops. MiKTeX +and TeX Live for Windows meet these requirements out of the box. + +The front end requires a Tcl/Tk installation, or at least a tclkit +runtime. TeX Live for Windows contains such a tclkit since late 2018, +and epspdftk as a TeX Live package will make use of it. The minimum +version is 8.5. + +For Windows there is an alternate epspdf-setup package on CTAN with an +embedded Tcl/Tk runtime and a regular Windows installer, which also +creates a menu shortcut and an uninstaller. + +Appendix B Change history +************************* + +B.1 Version 6.4 +=============== + +Compatibility changes for luatex 1.9 and later. + +The location of pdftops is no longer configurable; it is only searched +for on the searchpath. + +Some corner cases should be handled more successfully. + +The GUI now requires Tcl/Tk version 8.5 or higher. On Windows, +epspdf.tlu is now invoked via a batchfile. This should prevent black +console windows popping up. + +B.2 Version 0.6 +=============== + +The command-line backend component has been rewritten in texlua and +therefore no longer needs an external scripting language. + +Grayscaling is now done by Ghostscript's color options for pdf output. +This also works for bitmaps. + +Croppping of pdfs is now accomplished by running luatex on a suitable +wrapper file (same method as Heiko Oberdiek's pdfcrop). Such a +conversion preserves advanced features which might otherwise get lost. + +I no longer try to provide an AppleScript wrapper. + +The current version has no provisions for custom Ghostscript- or pdftops +parameters. The corresponding command-line options are accepted but +have no effect. + +B.3 Version 0.5 +=============== + +The GUI has been rewritten in Tcl/Tk, removing the dependence on the +Ruby/Tk interface library. + +The Windows installer now installs a small Ruby subset and the standard +epspdf distribution, but with epspdftk.tcl replaced with a starpack: a +single executable containing 'epspdftk.tcl' and a Tcl/Tk runtime. See +<http://wiki.tcl.tk/52>. + +Epspdf now uses its own subdirectory for both the logfile and the +configuration file. For Linux/Unix/Mac OS this is '$HOME/.epspdf', for +Windows it is '%APPDATA%\epspdf'. On all supported platforms, settings +are stored in the file 'config' in this directory. Under Windows, the +registry is no longer used for this. + +A button has been added to view log output. + +There is a second new button "Remove temp files", which is normally +checked, causing temporary files to be deleted after each conversion. +Unchecking this button may be useful for troubleshooting. In previous +versions, temporary files were deleted at the end of the entire epspdftk +session but that has become less practical now that the GUI and epspdf +itself have become two separate programs. + +The "Open with..." option for Windows has been dropped for technical +reasons. + +The "-version" option now prints the version string instead of setting +the desired pdf version. + +The "-info" option now also prints the number of pages for pdf files. + +B.4 Version 0.4 +=============== + +Hi-res boundingboxes are now supported. By default, conversion from eps +to pdf now uses the hires boundingbox as "page" to determine the page +dimensions of the pdf file. Other conversions preserve or generate a +hires boundingbox. + +Under Windows, the new version looks for an installed TeX and will use +its private Ghostscript if it cannot find a separately installed +Ghostscript. TeX Live's pdftops, being on the searchpath, will be used +unless epspdf finds another copy first. + +There is now a "-v" (lowercase) option to print the version string. + diff --git a/support/epspdf/epspdf.tlu b/support/epspdf/epspdf.tlu new file mode 100755 index 0000000000..0007ade074 --- /dev/null +++ b/support/epspdf/epspdf.tlu @@ -0,0 +1,2691 @@ +#!/usr/bin/env texlua + +kpse.set_program_name('texlua') + +--[[ +epspdf conversion utility + +0.6.0: first texlua version +0.6.1: allow TeX installation on path with spaces +0.6.2: compatibility fix for luatex 0.81 +0.6.3: compatibility fixes for luatex 0.9x +0.6.4: adaptations for newer versions of LuaTeX and ghostscript; + some refactoring; better handling of some corner cases + +Copyright (C) 2006-2019 Siep Kroonenberg + +siepo at bitmuis nl + +This program is free software, licensed under the GNU GPL, >=2.0. +This software comes with absolutely NO WARRANTY. Use at your own risk! + +Note. +TeX code for cropping pdfs adapted from Heiko Oberdiek's pdfcrop utility + +Program structure + +- early initialization +- functions for: + - error handling + - file- and path utilities + - other general utilities + - infrastructure: logging and temporary files + - reading and writing settings + - gui function for communicating with frontend + - boundingboxes + - manipulating [e]ps- and pdf files +- the PsPdf object: + - creator functions + - boundingbox handling + - one-step conversion methods +- any_to_any function. + this function checks options, the one-step converters check for success. +- main initialization section: + - collecting system information + - infrastructure: setting up logging and temp directory + - settings: + - defining settings-, descriptions-, options- and auxiliary tables + - read settings + - defining commandline options and help function + - parsing commandline and performing non-conversion options +- calling any_to_any +- finishing up + +all calls to external programs work on temporary files with a simple +generated filename. The current directory is a newly-created +temporary directory. So no need to quote names of input- and output +filenames. + +POSSIBLE EXTENSIONS + +- duplicating epstopdf options +- custom options for gs and pdftops + +--]] + +-- some general utilities and globals --------------------------- + +-- early initializations + +eol = false +path_sep = false +if os.type=='unix' then + eol='\n' + path_sep = ':' +else + eol='\r\n' + path_sep = ';' +end + +infile = false +outfile = false + +bufsize=16000 -- for reading and writing files + +-- these `declarations' are not really needed; +-- they are here mainly for my own peace of mind + +from_gui = false -- whether epspdf is run from the epspsdtk gui + +cwd = '' + +-- Windows: miktex, TL or neither. +-- Actual values determined during system-dependent initialization +is_miktex = false +is_tl_w32 = false + +-- Luatex 1.09 and later replace epdf with pdfe + +-- some global file- and directory names +gs_prog = false +pdftops = false +epsdir = false +rcfile = false +logfile = false +tempdir = false +tempfiles = {} +-- childpath = false +-- os.getenv('path') returns the parents path, +-- so we need to keep track ourselves of the child path + +options = false -- actual conversion options +settings = false -- persistent settings; may be stored in config file +descriptions = false -- help strings for settings +gs_options = false +pdf_options = false +pdf_tail_options = false +ps_options = false +gray_options = false + +-- logging ------------------------ + +-- we open and close the logfile anew for each write. +-- failure to open does not constitute an error. + +function print_log(s) + local f = io.open(logfile, 'a') + if f then + f:write(s,eol) + f:close() + end + if from_gui then + print(s) -- intercepted by the gui + end +end + +function write_log(s) + print_log(string.format('%s %s', + os.date('%Y/%m/%d %H:%M:%S', os.time()), s)) +end + +function log_cmd(cmd) + write_log('[' .. table.concat(cmd, '] [') .. ']') +end + +-- error- and debug ------------------------- + +-- Simple-minded error handling. At most, we call a function which +-- tries to write the error message to log before re-raising the error. + +-- When run from the Tcl/Tk gui, this gui will capture error messages. + +function errror(mess) + if infile and outfile then + mess = 'Failure to convert '..infile..' to '..outfile..':\n'..mess + end + if logfile then pcall(write_log, mess) end + -- ignore result of pcall: we can do nothing about failure + error(mess, 2) +end + +function warn(mess) + if logfile then write_log(mess) end + print(mess) +end + +-- function dbg(mess) +-- if options.debug then +-- warn(mess) +-- end +-- end + +-- file- and path utilities ---------------- + +-- function ep_shortname(path) +-- if os.type=='unix' then +-- return path +-- else +-- -- shortname appears not to work under miktex +-- -- so return original path as a fallback +-- local sp = lfs.shortname(path) +-- return sp or path +-- end +-- end + +-- prepend or append dir to path if necessary +function maybe_add_path(dir, append) + local dircmp = path_sep .. dir .. path_sep + local pathcmp = path_sep .. kpse.var_value('PATH') .. path_sep + -- case folding + if os.name=='windows' or os.name=='cygwin' or os.name=='macosx' then + dircmp = string.lower(dir) + pathcmp = string.lower(pathcmp) + end + -- slash flipping + if os.type=='windows' then + pathcmp = (string.gsub(pathcmp, '/', '\\')) + dircmp = (string.gsub(dircmp, '/', '\\')) + end + if not string.find(pathcmp, dircmp, 1, true) then + if not append then -- prepend + os.setenv('PATH', dir..path_sep..kpse.var_value('PATH')) + else -- append + os.setenv('PATH', kpse.var_value('PATH')..path_sep..dir) + end + end +end + +function fw(path) + if os.type=='windows' then + return string.gsub(path, '\\', '/') + else + return path + end +end + +function absolute_path(path) + + --[[ Return absolute normalized version of path, interpreted + from the directory from where the program was called. + + We use the fact that lfs.currentdir() always returns an absolute and + normalized path. So we go to the parent directory of path, ask for + the current directory and then combine the current directory with + the base filename. + + On windows, texlua has no trouble cd-ing into a UNC path. + + The function returns nil if there is no valid parent path. + This might be an issue if path is a directory, + but we shall apply this function only on files. + It is ok if path itself does not exist. --]] + + path = fw(path) + + local present_dir = fw(lfs.currentdir()) + + lfs.chdir(cwd) + + local parentdir + local filename + + if string.match(path, '/') then + parentdir, filename = string.match(path,'^(.*)/([^/]*)$') + if parentdir=='' then + parentdir = '/' + -- on unix, this is an absolute path. on windows, it is not + if os.type=='windows' then + lfs.chdir('/') + parentdir = fw(lfs.currentdir()) + end + elseif os.type=='windows' and string.match(parentdir,'^[a-zA-Z]:$') then + parentdir = string.sub(parentdir,1,2)..'/' + else + if not lfs.chdir(parentdir) then + parentdir = nil + else + parentdir = fw(lfs.currentdir()) + end + end + elseif os.type=='windows' and string.match(path,'^[a-zA-Z]:') then + -- windows: d:file + parentdir = string.sub(path,1,2) + if not lfs.chdir(parentdir) then + parentdir = nil + else + parentdir = fw(lfs.currentdir()) + filename = string.sub(path,3) + end + else + parentdir = fw(lfs.currentdir()) + filename = path + end + lfs.chdir(present_dir) + if not parentdir then + return nil + elseif string.sub(parentdir,-1)=='/' then + return parentdir..filename, parentdir + else + return parentdir..'/'..filename, parentdir + end +end -- absolute_path + +-- check whether prog is on the searchpath. +-- we need it only under unix, +-- so we save ourselves the trouble of accommodating windows. +-- we return the original string, although we only need a yes or no answer + +function find_on_path (prog) + if os.type == 'unix' then + for d in string.gmatch(os.getenv('PATH'), '[^:]+') do + if lfs.isfile(d..'/'..prog) then + return prog + end + end + else + for d in string.gmatch(os.getenv('PATH'), '[^;]+') do + if lfs.isfile(d..'\\'..prog) then + return prog + end + end + end + return false +end -- find_on_path + +function dir_writable(d) + -- because directory attributes do not tell the whole story, + -- we actually try to create a file in the directory. + if not lfs.isdir(d) then + return false + end + -- try to create a new file, write to it and delete it afterwards + for i=1,1000 do + local s = d .. '/' .. tostring(i) + if not lfs.isfile(s) then + local fh = io.open(s, "w") + if fh then + fh:write('test') + fh:close() + if lfs.isfile(s) then + if lfs.attributes(s, 'size') > 0 then + os.remove(s) + return true + else + os.remove(s) + return false -- open and write resulted in empty file + end -- lfs.attributes + else + return false -- open and write did not result in a file + end -- lfs.isfile + end -- fh + return false -- filename available; could not open for write + end -- not lfs.isfile + end -- for + return false +end + +function system_tempdir () + local d + if os.type=='windows' then + d = os.getenv('TEMP') + if not d then + d = os.getenv('TMP') + end + else + d = os.getenv('TMPDIR') + if not d then + d = '/tmp' + end + end + -- cygwin: $TEMP=/tmp, root '/' being root of cygwin installation + if d and not dir_writable(d) then + d = false + end + return d +end + +-- other general utilities --------------------------- + +-- check whether el occurs in array lst +function in_list (el, lst) + if not lst then return false end + for _,p in ipairs(lst) do + if el == p then + return true + end + end + return false +end -- in_list + +-- remove leading and trailing, but not embedded spaces +function strip_outer_spaces(s) + s = string.gsub(s, '%s*$', '') + s = string.gsub(s, '^%s*', '') + return s +end -- strip_outer_spaces + +function join(t, sep, lastsep) + -- there is a table function concat which does this, + -- but without optional different lastsep + if t==nil or #t<1 then return '' end -- or should we return nil? + local s = t[1] + for i=2,#t do -- ok if #t<2 + if i==#t and lastsep then + s = s .. lastsep .. t[i] + else + s = s .. sep .. t[i] + end + end + return s +end -- join + +-- combine several tables into one. +-- the parameter is a table of tables. +function tab_combine (t) + local res = {} + for _,tt in ipairs(t) do + for __, ttt in ipairs(tt) do + table.insert(res, ttt) + end + end + return res +end -- tab_combine + +-- files ---------------------------------------------------- +-- Copy a file in chunks, with optional length and offset. +-- Since files may be very large, we copy them piecemeal. +-- An initial chunk of size bufsize should be plenty to include +-- any interesting header information. + +function slice_file(source, dest, len, offset, mode) + -- The final three parameters can be independently left out by + -- specifying false as value + -- Assume caller ensured parameters of correct type. + -- We do not allow negative offsets. + + local copy2self = false + if source==dest then copy2self = true end + if os.type=='unix' and + lfs.attributes(source,'ino')==lfs.attributes(dest,'ino') then + copy2self = true + end + if copy2self and not len and not offset and mode~='ab' then + return -- nothing to do + elseif copy2self then + errror('slice_file invoked with identical source and destination '.. + source..' and non-simple copy') + end + -- in practice any_to_any already checks for this. + -- in addition, the main program makes a backup if infile==outfile + + local sz = lfs.attributes(source, 'size') + if not offset then + offset = 0 + elseif offset>sz then + offset = sz + end + if not len or len>sz-offset then + len = sz - offset + end + if not mode then mode = 'wb' end + local buffer='' + local s=io.open(source, 'rb') + s:seek('set', offset) + local copied = 0 + local d=io.open(dest, mode) + if not d then errror('slice_file: failed to copy to '..dest) end + local slen = len + while slen>0 do + if slen>=bufsize then + buffer = s:read(bufsize) + slen = slen - bufsize + else + buffer = s:read(slen) + slen = 0 + end + if not d:write(buffer) then + errror('slice_file: failed to copy to '..dest) + end + end + s:close() + d:close() +end -- slice_file + +function move_or_copy(source, dest) + if lfs.isfile(dest) and lfs.attributes(dest, 'size')>0 then + warn('Removing old '..dest) + os.remove(dest) -- in case of failure, go ahead anyway + end + -- Windows: try first renaming in-place before moving to the right directory + if os.type == 'windows' then + if os.rename(source, source..'.renamed') then + source = source..'.renamed' + end + end + if not os.rename(source, dest) then + slice_file(source, dest) -- bails out on failure + local ok, err_mess = os.remove(source) + if not ok then + warn('Failed to remove old ' .. source .. ': ' .. err_mess) + end + end +end + +-- temporary files ---------------------------------------- + +-- tempdir = false -- will be created later and chdir-ed into +-- tempfiles initialized early to empty table + +-- We just name our temporary files nn.<ext> with successive nn. +-- We cannot exclude that another process uses our tempdir +-- so we have to first check for each new file whether it already exists. +-- Epspdf does all the real work from the temp directory. + +function mktemp(ext) + local froot, fname, f, g + for i=0,99 do + froot = string.format('%02d.', i) + fname = froot..ext + if ext~='tex' then + if not lfs.isfile(fname) then + f = io.open(fname, 'wb') + if not f then + errror('Cannot create temporary file '..fname) + end + f:close() + table.insert(tempfiles, fname) + return froot..ext -- no need to record pdf name + end + else + -- tex; we also need a pdf + if not lfs.isfile(fname) and not lfs.isfile(froot..'pdf') then + local f = io.open(fname, 'wb') + if not f then + errror('Cannot create temporary file '..fname) + end + f:close() + table.insert(tempfiles, fname) + fname = froot..'pdf' + g = io.open(fname, 'wb') + if not g then + errror('Cannot create temporary file '..fname) + end + g:close() + table.insert(tempfiles, fname) + table.insert(tempfiles, froot..'log') + return froot..ext -- no need to record pdf name + end + end -- if + end -- for + errror('Cannot create temporary file in '..tempdir) +end + +function waitasec() + -- stupid windows file locking; assume vista or later + if os.type=='windows' then + os.execute('timeout /t 1 /nobreak >nul') + -- else do nothing + end + -- error checking pointless +end + +function cleantemp() + lfs.chdir(tempdir) + if os.type=='windows' then waitasec() end + for _,f in ipairs(tempfiles) do + if lfs.isfile(f) then + local success, mess = os.remove(f) + if not success then write_log(mess) end + end + end + local empty = true + for f in lfs.dir('.') do + if f ~= '.' and f ~= '..' then + empty = false + write_log('Temp dir '..tempdir..' contains '..f..' therefore not removed') + break + end + end + if os.type=='windows' then waitasec() end + lfs.chdir('..') + if empty then + local res, mess + res, mess = lfs.rmdir(tempdir) + if not res then + write_log('Failed to remove empty '..tempdir..'\n'..mess) + end + end +end + +-- gs_epsdevice ----------------------- + +function gs_epsdevice() + local gh = io.popen(gs_prog..' -help') + local s = gh:read("*a") + gh:close() + if string.find(s,'eps2write') then + return 'eps2write' + elseif string.find(s,'epswrite') then + return 'epswrite' + else + return false + end +end + +-- settings ----------------------- + +function write_settings (file) + local f + if file then + f = io.open(file, 'wb') + if not f then + return + end + else -- stdout to be captured by epspdftk + f = io.output() + end + for k, v in pairs(settings) do + if descriptions[k] and file then + f:write(eol, '# ', descriptions[k], eol) + end + f:write(k, ' = ', tostring(v), eol) + end + if file then + f:close() + end +end + +function read_settings(file) + -- read and interpret rcfile + -- we shall ignore illegal entries. + local contents + local f + if file then + f = io.open(rcfile, 'rb') + if not f then + return + end + else + f = io.input() + end + contents = f:read(10000) + if file then + f:close() + end + if not contents or contents=='' then + return + end + -- remove initial \r and \n characters + contents = string.gsub(contents, '^[\r\n]*', ''); + -- gmatch chops contents into series of non-line-ending characters + -- possibly followed by line-ending characters i.e. in lines + local k, v, vl, vnum + for l in string.gmatch(contents, '[^\r\n]+[\r\n]*') do + l = string.match(l,'[^\r\n]*') + if not string.match(l, '^#') then + k, v = string.match(l, '^%s*([^%s]+)%s*=%s*(.*)$') + if v then v = string.gsub(v,'%s*$', '') end + -- now handle k and v + if k == 'pdf_target' then + -- ignore unless valid option + if in_list(v, pdf_targets) then + settings[k] = v + end + elseif k == 'pdf_version' then + -- ignore unless valid option + if in_list(v, pdf_versions) then + settings[k] = v + end + elseif k == 'ignore_pdftops' then + vl = string.lower(string.sub(v,1,1)) + if v == 0 or vl == 'n' or vl == 'f' then + settings.use_pdftops = true + elseif v == 1 or vl == 'y' or vl == 't' then + settings.use_pdftops = false + end + elseif k == 'use_pdftops' then + vl = string.lower(string.sub(v,1,1)) + if v == '0' or vl == 'n' or vl == 'f' then + settings.use_pdftops = false + elseif v == '1' or vl == 'y' or vl == 't' then + settings.use_pdftops = true + end + -- final three settings not used by epspdf itself but + -- passed along to epspdftk + elseif k == 'ps_viewer' then + settings.ps_viewer = v + elseif k == 'pdf_viewer' then + settings.pdf_viewer = v + elseif k == 'default_dir' then + settings.default_dir = v + end -- test for k + end -- not matching ^# + end -- for +end -- read settings + +function version_added() + for _, v in pairs(pdf_options) do + if string.find(v, '-dCompatibilityLevel') then + return true + end + end + return false +end + +function maybe_add_version_parameter() + if options.type=='pdf' and not options.bbox and settings.pdf_version and + settings.pdf_version~='default' and not version_added() then + table.insert(pdf_options, '-dCompatibilityLevel#'..settings.pdf_version) + end +end + +-- gui: reading and writing settings ----------- + +function gui(action) + -- use stdin for reading settings from gui, and stdout for writing + if action=='config_w' then + -- called at start of epspdftk + write_settings() -- to pipe epspdf => epspdftk + os.exit() + elseif action=='config_r' then + read_settings() -- from 'pipe' epspdftk => epspdf + write_settings(rcfile) + os.exit() + else + from_gui = true + end +end + +-- boundingboxes --------------------------------------------------- + +-- [HR]Bb.coords names same as those of epdf PDFRectangle +-- but the new pdfe simply uses an array + +Bb = {} +Bb.coords = {'x1', 'y1', 'x2', 'y2'} + +function Bb:from_rect(r) + -- also handle the case that r is a 4-element array: + if not r.x1 then r.x1 = r[1] end + if not r.y1 then r.y1 = r[2] end + if not r.x2 then r.x2 = r[3] end + if not r.y2 then r.y2 = r[4] end + + for _,k in ipairs(self.coords) do + if not r[k] or type(r[k])~='number' then + errror('from_rect called with illegal parameters') + end + -- sanity check on size + -- FIXME: this limit is far too high + if r[k]+.5==r[k] or r[k]-.5==r[k] then + errror('Bb:from_rect: ' .. r[k] ..' greater than maxint') + end + local b = {} + local eps = 0.000001 + b.x1, b.x2 = math.floor(math.min(r.x1, r.x2) + eps), + math.ceil(math.max(r.x1, r.x2) - eps) + b.y1, b.y2 = math.floor(math.min(r.y1, r.y2) + eps), + math.ceil(math.max(r.y1, r.y2) - eps) + if b.x1==b.x2 or b.y1==b.y2 then + errror('from_rect: width or height is zero') + end + setmetatable(b, {__index=self}) + return b + end +end + +Bb.bb_pat = '^%s*%%%%BoundingBox:' +Bb.bb_end = '^%s*%%%%BoundingBox:%s*%(%s*atend%s*%)' + +function Bb:from_comment(s) + local p = self.bb_pat..'%s*([-+%d]+)'..string.rep('%s+([-+%d]+)',3) + local b = {} + b.x1, b.y1, b.x2, b.y2 = string.match(s, p) + if not b.y2 then + errror('Bb.from_comment: illegal boundingbox string ' .. s) + end + for _,k in ipairs(self.coords) do + b[k] = tonumber(b[k]) + end + return Bb:from_rect(b) +end + +function Bb:nonnegative () + return self.x1>=0 and self.y1>=0 +end + +function Bb:comment() + -- if options.debug then print(debug.traceback()) end + return string.format('%%%%BoundingBox: %d %d %d %d', + self.x1, self.y1, self.x2, self.y2) +end + +-- hires boundingboxes --------------------------------------------- + +HRBb = {} + +setmetatable(HRBb, {__index=Bb}) + +function HRBb:from_rect(r) + -- also handle the case that r is a 4-element array: + if not r.x1 then r.x1 = r[1] end + if not r.y1 then r.y1 = r[2] end + if not r.x2 then r.x2 = r[3] end + if not r.y2 then r.y2 = r[4] end + + for _,k in ipairs(self.coords) do + if not r[k] or type(r[k])~='number' then + errror('from_rect called with illegal parameters') + end + -- sanity check on size + if r[k]+.5==r[k] or r[k]-.5==r[k] then + errror('HRBb:from_rect: ' .. b[k] ..' greater than maxint') + end + local b = {} + b.x1, b.x2 = math.min(r.x1, r.x2), math.max(r.x1, r.x2) + b.y1, b.y2 = math.min(r.y1, r.y2), math.max(r.y1, r.y2) + if b.x1==b.x2 or b.y1==b.y2 then + errror('from_rect: width or height is zero') + end + setmetatable(b, {__index=self}) + return b + end +end + +HRBb.bb_pat = '^%s*%%%%HiResBoundingBox:' +HRBb.bb_end = '^%s*%%%%HiResBoundingBox:%s*%(%s*atend%s*%)%s*$' + +function HRBb:from_comment(s) + local p = self.bb_pat..'%s*([-+.%deE]+)'..string.rep('%s+([-+.%deE]+)',3) + local b = {} + b.x1, b.y1, b.x2, b.y2 = string.match(s, p) + if not b.y2 then + errror('HRBb.from_comment: illegal boundingbox string ' .. s) + end + for _,k in ipairs(self.coords) do + b[k] = tonumber(b[k]) + end + return HRBb:from_rect(b) +end + +function HRBb:nonnegative () + return self.x1>=0 and self.y1>=0 +end + +function HRBb:comment() + return string.format('%%%%HiResBoundingBox: %f %f %f %f', + self.x1, self.y1, self.x2, self.y2) +end + +--[[ + +-- no longer used: gs handles this +-- call this one also via pcall +function HRBb:wrapper() + + -- local fn = mktemp('ps') + -- local f = io.open(fn, 'wb') + -- f.write(string.format('<< /PageSize [%f %f] >> setpagedevice\n', + -- self.x2 - self.x1, self.y2 - self.y1)) + -- f.write(string.format('gsave\n%f %f translate\n', -self.x1, -self.y1)) + -- f:close() + -- return fn + + return string.format( + '<< /PageSize [%f %f] >> setpagedevice gsave %f %f translate', + self.x2 - self.x1, self.y2 - self.y1, -self.x1, -self.y1) +end + +--]] + +-- manipulating eps/ps/pdf files ----------------------------------- + +function identify() + local f = io.open(infile, 'rb') + if not f then + errror('Failure to open '..infile..' for identification') + end + local filestart= f:read(23) + f:close() + if not filestart or filestart=='' then + return false + elseif string.match(filestart,'^\197\208\211\198') then -- c5 d0 d3 c6 + return 'epsPreview' + elseif string.match(filestart,'^%%!PS%-Adobe%-%d%.%d EPSF%-%d%.%d') then + return 'eps' + elseif string.match(filestart,'^%%!PS%-Adobe%-%d%.%d') then + for _, p in ipairs({'.eps', '.epi', '.epsi', '.epsf'}) do + if string.sub(string.lower(infile), -1-string.len(p),-1) == p then + return 'eps' + else + return 'ps' + end + end + return 'ps' + elseif string.match(filestart, '^%%PDF') then + return 'pdf' + else + return false + end +end -- identify + +function pdf_props(path) + local pdfdoc, pgs, maver, miver + if pdfe then + pdfdoc = pdfe.open(path) + if pdfdoc then + pgs = pdfe.getnofpages(pdfdoc) + maver, miver = pdfe.getversion(pdfdoc) + end + if not (pdfdoc and pgs and maver and miver) then + errror('pdfe failed to get information about '..path) + end + pdfe.close(pdfdoc) + else + local cat + pdfdoc = epdf.open(path) + if pdfdoc then + cat = pdfdoc:getCatalog() + if cat then + pgs = cat:getNumPages() + end + maver = pdfdoc:getPDFMajorVersion() + miver = pdfdoc:getPDFMinorVersion() + end + if not (pdfdoc and pgs and maver and miver) then + errror('epdf failed to get information about '..path) + end + -- epdf.close(pdfdoc) + end + -- if os.type=='windows' then waitasec() end + if maver > 1 then + print(path..' has pdf major version \n'..tostring(maver).. + ' which is unsupported;\n'.. + 'Continuing with fingers crossed...') + end + return pgs, miver, maver +end -- pdf_props + +function info() + local intype = identify() + if not intype then + print(infile..' has an unsupported filetype.') + elseif intype~='pdf' then + print(infile..' has type '..intype..'.') + else + local pgs, miver, maver = pdf_props(infile) + print(infile..' has type pdf, version '..tostring(maver).. + '.'..tostring(miver)..' and has '..tostring(pgs)..' pages.') + end + os.exit() +end + +-- PsPdf object ------------------------------------------------- + +PsPdf = {} + +-- creators + +function PsPdf:new(ext) + local psp = {} + setmetatable(psp, {__index = self}) + -- assign temp file + psp.path = mktemp(string.lower(ext)) + if string.lower(ext)=='pdf' then + psp.type = 'pdf' + elseif string.lower(ext)=='eps' then + psp.type = 'eps' + elseif string.lower(ext)=='ps' then + psp.type = 'ps' + else + psp.type = false + end + if psp.type=='eps' then + psp.pages = 1 + end + psp.bb = false + psp.hrbb = false + return psp +end -- PsPdf:new + +function PsPdf:from_path(path) + local psp = {} + setmetatable(psp, {__index = self}) + psp.path = path + if lfs.isfile(path) then + -- turn existing file into PsPdf object. + psp.type = identify(psp.path) + if psp.type=='pdf' then + psp.pages, psp.miver, psp.maver = pdf_props(psp.path) + end + else + errror('PsPdf:from_path called with non-existant file '..path) + end + if psp.type=='eps' then + psp.pages = 1 + end + psp.bb = false + psp.hrbb = false + -- only calculate when needed + return psp +end -- PsPdf:from_path + +-- do we need to downgrade the pdf to a lower version? +-- consider [e]ps lower than any pdf version +function PsPdf:to_downgrade() + if self.type~='pdf' then + return false + elseif options.type~='pdf' then + return true + elseif settings.pdf_version=='default' then + return false + elseif settings.pdf_version~='default' and + self.maver+0.1*self.miver-0.001 > tonumber(settings.pdf_version) then + -- -0.001: exact binary representation of pdf_version not guaranteed + return true + else + return false + end +end + +--[[ getting boundingbox property from file itself -------------- + +get_bb_simple: use only for eps PsPdf objects we generated +ourselves, so we can assume that the bbox comments are in the header +and the hires bb lies within the lores bb. +Of course the file itself is not rewritten. + +--]] + +function PsPdf:get_bb_simple() + if self.type~='eps' then + errror('get_bb_simple called with non-eps file '..self.path) + end + self.bb = false + self.hrbb = false + local slurp = false + local f = io.open(self.path, 'rb') + if f then + slurp = f:read(bufsize) + f:close() + end + lines = {} + for l in string.gmatch(slurp, '[^\n\r]+') do + if string.match(l, Bb.bb_pat) then + self.bb = Bb:from_comment(l) + elseif string.match(l, HRBb.bb_pat) then + self.hrbb = HRBb:from_comment(l) + elseif self.bb then + break -- stop looking; we expect hrbb next to bb + end + if self.bb and self.hrbb then break end + end + if not self.bb then + errror('No valid boundingbox for generated file' .. self.path) + end + return self -- no real need for a return value +end + +function PsPdf:bb_from_gs() + + if self.type=='ps' then + errror('bb_from_gs called with ps file '..self.path) + -- not needed for generic PostScript, + -- page selection only works with pdf files, so we save ourselves + -- the trouble of picking the right bbox from a list + end + if self.type=='eps' and not self.bb:nonnegative() then + errror('bb_from_gs called on ' .. self.path .. + ' which has some negative boundingbox coordinates') + -- any_to_any should guard against such an invocation + end + + -- Since Ghostscript writes the boundingbox comments to stderr, + -- we need a shell to intercept this output: + + local bb_file = mktemp('dsc') + -- a somewhat low resolution parameter may help gs + -- deal with eps files with large coordinates + -- but this may impact the accuracy of the HRBb + local cmdline + if self.type=='eps' and (self.bb.x2 > 850 or self.bb.y2 > 850) then + cmdline = gs_prog .. ' -r300 ' .. table.concat(gs_options,' ') + else + cmdline = gs_prog .. ' ' .. table.concat(gs_options,' ') + end + if self.type=='pdf' then + pg = options.page or 1 + cmdline = cmdline .. ' -dFirstPage#' .. tostring(pg) .. + ' -dLastPage#' .. tostring(pg) + end + cmdline = cmdline .. ' -sDEVICE#bbox ' .. self.path .. ' 2>'..bb_file + + -- execute shell command + + local r, cmd + write_log('os.execute: '..cmdline) + r = os.execute(cmdline) + if not r then + errror('Cannot get fixed boundingbox for '..self.path) + end + + -- read new bbox from ghostscript output + -- can we really count on the plain bb coming first? + -- OTOH, I would rather not introduce unnecessary complexity + -- still, it may be better to match each line with [HR]Bb_pat + + local bb = false + local hrbb = false + local fin = io.open(bb_file, 'r') + if fin then + for i=1,10 do -- actually, 2 should suffice + local l = fin:read("*line") + if not l then break end + if string.match(l, Bb.bb_pat) then + bb = Bb:from_comment(l) + end + if string.match(l, HRBb.bb_pat) then + hrbb = HRBb:from_comment(l) + end + end + fin:close() + end + if not bb or not hrbb then + errror('Cannot get fixed boundingbox for '..self.path) + end + return bb, hrbb +end + +-- eps_clean: remove some problem features from eps (new file & object) + +function PsPdf:eps_clean() + + -- return a PsPdf object referring to a new file + -- without a preview header and with boundingbox(es) in the header. + -- return a new file even if no changes were needed. + + local function bytes2num (s, i) + -- convert substring s[i..i+3] to a number. + -- by working byte for byte we avoid endian issues + local n = string.byte(s, i+3) + for j=2,0,-1 do n = 256*n + string.byte(s, i+j) end + return n + -- somehow the explicit expression below didn't work + -- return ((256 * (256 * (256 * string.byte(s,i+3)) + string.byte(s,i+2)) + -- + string.byte(s,i+1)) + string.byte(s,i)) + end + + if self.type~='eps' and self.type~='epsPreview' then + errror('epsclean called with non-eps file ' .. self.path) + end + local offset, ps_length = false, false + local fin, fout + if self.type=='eps' then + offset = 0 + ps_length = lfs.attributes(self.path, 'size') + else + -- read TOC; see Adobe EPS specification + -- interpret byte for byte, in case the platform is not little-endian + fin = io.open(self.path, 'rb') + if fin then + local toc = fin:read(12) + fin:close() + if toc and string.len(toc)==12 then + offset = bytes2num(toc, 5) + ps_length = bytes2num(toc, 9) + end + end + if not offset then + errror('Could not read preview header of ' .. self.path) + end + end + + -- create the PsPdf object which is to be returned + + local psp + psp = PsPdf:new('eps') + + -- read an initial and if necessary a final chunk of the file + -- to find boundingbox comments. + + local atend = false + local hr_atend = false + local slurp -- the read buffer + local l -- contains current scanned line; split off from slurp + -- pre_lines: scanned header lines; alternately lines and eols + local pre_lines = {} + -- new_offset: offset plus combined length of scanned header lines + local new_offset = offset + -- post_lines: scanned trailer lines + local post_lines = {} + -- middle_length: ps_length minus scanned header- and and maybe trailer parts + -- this is the length of file that will be copied wholesale. + local middle_length + local i, i_bb, i_hrbb + local j, j_bb, j_hrbb, j_end + -- j_end: index of final scanned trailer line + -- no i_end necessary: for header lines we can use #pre_lines. + + fin = io.open(self.path, 'rb') + if not fin then errror('Cannot read '..self.path) end + fin:seek('set', offset) + + -- remaining, unscanned length of input buffer slurp + local unscanned = math.min(ps_length,bufsize) + slurp = fin:read(unscanned) + + -- unnecessary: + psp.bb = nil + psp.hrbb = nil + + i, i_bb, i_hrbb = 0, false, false + while unscanned>0 do + i = i+1 + if string.find(slurp,'[\n\r]')==1 then + l,slurp = string.match(slurp, '^([\n\r]+)(.*)$') + else + l,slurp = string.match(slurp, '^([^\n\r]+)(.*)$') + if string.match(l, Bb.bb_end) then + atend = true + i_bb = i + elseif string.match(l, Bb.bb_pat) then + psp.bb = Bb:from_comment(l) + -- from_comment errors out on failure; no need to check return value + i_bb = i + elseif string.match(l, HRBb.bb_end) then + hr_atend = true + i_hrbb = i + elseif string.match(l, HRBb.bb_pat) then + psp.hrbb = HRBb:from_comment(l) + i_hrbb = i + end -- bbox line + end -- eol/non-eol + pre_lines[i] = l + unscanned = unscanned - string.len(l) + if (i_bb and (i_hrbb or (i_bb<(i-1)))) or unscanned<=0 then + -- condition i_bb<i-1: + -- We do not want to find the hrbb of an included eps. + -- Therefore we stop looking for hrbb if it is not next to bb + -- (with an intervening eol) + -- Note that the header comments are not necessarily terminated + -- with a %%EndComments line; see Adobe DSC spec 5001 + break + end -- deciding whether to stop + end -- while + new_offset = offset + string.len(table.concat(pre_lines)) + middle_length = ps_length - string.len(table.concat(pre_lines)) + + if atend or hr_atend then + -- find boundingbox comments, starting from end of postscript + if ps_length>bufsize then + fin:seek('set',offset+ps_length-bufsize) + unscanned = bufsize + slurp = fin:read(unscanned) + else + -- use what is left from old slurp + unscanned = string.len(slurp) + end + j = 1 -- count down from 0 + j_bb, j_hrbb, j_end = false, false, false + while unscanned>0 do + j = j - 1 + if string.find(slurp,'[\n\r]', string.len(slurp)) then + slurp,l = string.match(slurp, '^(.-)([\n\r]+)$') + -- '-': non-greedy matching + else + slurp,l = string.match(slurp, '^(.-)([^\n\r]+)$') + if string.match(l, Bb.bb_pat) then + psp.bb = Bb:from_comment(l) + j_bb = j + elseif string.match(l, HRBb.bb_pat) then + psp.hrbb = HRBb:from_comment(l) + j_hrbb = j + end -- bbox line + end -- eol/non-eol + post_lines[j] = l + unscanned = unscanned - string.len(l) + if (psp.bb and + (psp.hrbb or not hr_atend or j_bb>(j+1))) or unscanned<=0 then + -- stop looking + j_end = j + break + end -- deciding whether to stop + end -- while + middle_length = middle_length - + string.len(table.concat(post_lines, '', j_end, 0)) + end --if atend + fin:close() + -- fix boundingbox lines + if atend and j_bb then + -- pre_lines[i_bb] = post_lines[j_bb] + pre_lines[i_bb] = psp.bb:comment() -- WHY DOESNT THIS WORK ???? + post_lines[j_bb] = '' + post_lines[j_bb+1] = '' + end + if hr_atend and j_hrbb then + -- pre_lines[i_hrbb] = post_lines[j_hrbb] + pre_lines[i_hrbb] = psp.hrbb:comment() + post_lines[j_hrbb] = '' + post_lines[j_hrbb+1] = '' + end + -- create cleaned eps file + fout = io.open(psp.path, 'wb') + if not fout then errror('Cannot create new file '..psp.path) end + fout:write(table.concat(pre_lines)) + fout:close() + slice_file(self.path, psp.path, middle_length, new_offset, 'ab') + fout = io.open(psp.path, 'ab') + fout:write(table.concat(post_lines, '', j_end, 0)) + fout:close() + return psp +end -- eps_clean + +-- tight boundingbox (new file & object) + +function PsPdf:eps_crop() + + -- conversion is not done by an external program, although + -- we invoke Ghostscript with a bbox device for a tight boundingbox. + -- We use both the regular and the hires boundingbox from gs. + -- The eps should already have been cleaned up by eps_clean, + -- and the current boundingbox should not contain negative coordinates, + -- otherwise the bbox output device may give incorrect results. + -- Only the boundingbox in the eps is rewritten. + + -- create the PsPdf object which is to be returned + + local psp = PsPdf:new('eps') + + -- read new bbox from ghostscript output + + psp.bb, psp.hrbb = self:bb_from_gs() + + -- rewrite header with new boundingboxes + + local slurp -- the read buffer + local l -- contains current scanned line; split off from slurp + -- pre_lines: scanned header lines; alternately lines and eols + local pre_lines = {} + -- offset: combined length of scanned header lines + local offset = 0 + local ps_length = lfs.attributes(self.path, 'size') + local i, i_bb, i_hrbb + + fin = io.open(self.path, 'rb') + if not fin then errror('Cannot read '..self.path) end + + -- remaining, unscanned length of input buffer slurp + local unscanned = math.min(ps_length,bufsize) + slurp = fin:read(unscanned) + i, i_bb, i_hrbb = 0, false, false + while unscanned>0 do + i = i+1 + if string.find(slurp,'[\n\r]')==1 then + l,slurp = string.match(slurp, '^([\n\r]+)(.*)$') + else + l,slurp = string.match(slurp, '^([^\n\r]+)(.*)$') + if string.match(l, Bb.bb_pat) then + i_bb = i + elseif string.match(l, HRBb.bb_pat) then + i_hrbb = i + end -- bbox line + end -- eol/non-eol + pre_lines[i] = l + unscanned = unscanned - string.len(l) + if (i_bb and (i_hrbb or (i_bb<(i-1)))) or unscanned<=0 then + break + end + end -- while + fin:close() + offset = string.len(table.concat(pre_lines)) + + if i_hrbb then + pre_lines[i_bb] = psp.bb:comment() + pre_lines[i_hrbb] = psp.hrbb:comment() + else + -- jam both bbox comments into one slot, with an intervening eol. + -- for the sake of conformity, we copy an existing eol. + pre_lines[i_bb] = psp.bb:comment() .. pre_lines[i_bb-1] .. + psp.hrbb:comment() + end + + -- write a new eps file + + fout = io.open(psp.path, 'wb') + if not fout then errror('Cannot write new file '.. psp.path) end + fout:write(table.concat(pre_lines)) + fout:close() + slice_file(self.path, psp.path, + lfs.attributes(self.path,'size') - offset, offset, 'ab') + options.bbox = false + return psp +end -- eps_crop + +--[[ + +most of these conversions involve a single invocation of gs, pdftops or texlua + +Each conversion fullfills all options that it can: gray, bbox and +page. gray when converting to pdf, bbox when converting from eps or +from pdf to pdf and page when converting from pdf. It then sets the +fullfilled option(s) to false. + +We make sure to do the tight boundingbox before a file format downgrade: +rasterization of the page or graphic frustrates boundingbox calculation. + +We like to preserve fonts as fonts. gs does this when generating +pdf, but may fail for fonts such as cid and large truetype when +generating PostScript. In such cases, pdftops may succeed. However, +it seems that if the page contains an element that does not cleanly +convert, pdftops simply rasterizes the entire page, and that this +choice is made per page. + +--]] + +-- Converting from pdf to pdf using luatex; no grayscaling here + +function PsPdf:getpgbox() + if options.page and options.page > self.pages then + errror('PsPdf:getpgbox called with non-existent page '.. options.page) + end + local pg = options.page or 1 + local bb, hrbb, pgbox, pdfdoc, ppage + if pdfe then + pdfdoc = pdfe.open(self.path) + if pdfdoc then + ppage = pdfe.getpage(pdfdoc, pg) + if not ppage then errror('did not get page') end + if ppage then + pgbox = pdfe.getbox(ppage, 'MediaBox') + if not pgbox then + pgbox = pdfe.getbox(ppage, 'CropBox') + if not pgbox then + pgbox = pdfe.getbox(ppage, 'TrimBox') + if not pgbox then + errror('No box acquired') + end + end + end + end + end + pdfe.close(pdfdoc) + else + local cat + pdfdoc = epdf.open(self.path) + if pdfdoc then + cat = pdfdoc:getCatalog() + if cat then + ppage = cat:getPage(pg) + if ppage then + pgbox = ppage:getMediaBox() + if not pgbox then + pgbox = ppage:getCropBox() + if not pgbox then + pgbox = ppage:getTrimBox() + end + end + end + end + end + end + -- normalization and further checks, including for non-nil, + -- by HRBb:from_rect, which errors out on failures + if pgbox then + pgbox = HRBb:from_rect(pgbox) + -- this also converts numeric array indices to x1 ... y2 if necessary + else + errror('Cannot get page box from '..self.path..' page '..pg) + end + return pgbox +end -- PsPdf:getpgbox + +function PsPdf:pdf_crop() + + -- options to be fulfilled: page, boundingbox if possible + -- embeds the pdf with boundingbox parameters into a new (lua)tex document + if options.page and options.page > self.pages then + errror('PsPdf:pdf_crop called with non-existent page '.. options.page) + end + local bb, hrbb, pgbox + pgbox = self:getpgbox() + if options.bbox and pgbox:nonnegative() then + bb, hrbb = self:bb_from_gs() + options.bbox = false + else + hrbb = pgbox + end + + -- luatex is on searchpath + local luatex_prog = 'luatex' + + -- write TeX file which includes cropped pdf page + -- adapted from Heiko Oberdiek's pdfcrop utility. + -- the table `pieces' will contain the component strings for the tex source + -- first, for texlua <= 0.81 + local pieces = {} + if status.luatex_version <= 80 then + pieces[1] = '\\pdfoutput=1\n' + else + pieces[1] = [[ +\edef\pdfminorversion {\pdfvariable minorversion} +\edef\pdfcompresslevel {\pdfvariable compresslevel} +\edef\pdfobjcompresslevel {\pdfvariable objcompresslevel} +\edef\pdfdecimaldigits {\pdfvariable decimaldigits} +\edef\pdfhorigin {\pdfvariable horigin} +\edef\pdfvorigin {\pdfvariable vorigin} +\let\pdfpagewidth\pagewidth +\let\pdfpageheight\pageheight +\let\pdfximage\saveimageresource +\let\pdfrefximage\useimageresource +\let\pdflastximage\lastsavedimageresourceindex + +\outputmode=1 +]] + end + if self.maver > 1 then + pieces[2] = '\\pdfminorversion=9\n' + else + pieces[2] = '\\pdfminorversion=' .. self.miver .. '\n' + end + if self.maver > 1 or self.miver > 4 then + pieces[3] = [[ +\pdfcompresslevel=9 +\pdfobjcompresslevel=2 +\pdfdecimaldigits=4 +]] + else + pieces[3] = [[ +\pdfcompresslevel=9 +\pdfobjcompresslevel=0 +\pdfdecimaldigits=4 +]] + end + pieces[4] = [[ +\def\page #1 [#2 #3 #4 #5]{% + \count0=#1\relax + \setbox0=\hbox{% + \pdfximage page #1 mediabox{]] + pieces[5] = self.path + pieces[6] = [[}% + \pdfrefximage\pdflastximage + }% + \pdfhorigin=#2bp\relax + \pdfvorigin=#3bp\relax + \pagewidth=#4bp\relax + \pageheight=#5bp\relax + \ht0=\pageheight + \shipout\box0\relax +} +]] + pieces[7] = string.format([[ +\page %d [%f %f %f %f] +\csname @@end\endcsname +\end +]], + options.page or 1, -hrbb.x1, hrbb.y1, hrbb.x2-hrbb.x1, hrbb.y2-hrbb.y1) + + local textemp = mktemp('tex') -- this also took care of pdf: + local pdftemp = string.gsub(textemp, 'tex$', 'pdf') + -- if os.type=='windows' then waitasec() end + local f = io.open(textemp, 'w') + -- if os.type=='windows' then waitasec() end + f:write(table.concat(pieces, '')) + f:close() + local cmd, res, psp + -- if os.type=='unix' then + cmd = {luatex_prog, '--safer', '--no-shell-escape', textemp} + log_cmd(cmd) + res = os.spawn(cmd) + -- else + -- cmd = luatex_prog..' --safer --no-shell-escape '..textemp + -- log_cmd({cmd}) + -- -- os.execute('timeout /t 1 /nobreak >nul') + -- res = os.execute(cmd) + -- end + if res and res==0 and lfs.attributes(pdftemp, 'size')>0 then + psp = PsPdf:from_path(pdftemp) + options.bbox = false + options.page = false + return psp + else + errror('pdf_crop failed on '..self.path) + end +end + +function PsPdf:eps_to_pdf() + + -- option to be fulfilled: gray + -- set target and maybe pdf version if applicable + if self.type~='eps' then + errror('PsPdf:eps_to_pdf called for non-eps file '.. self.path) + end + local cmd + maybe_add_version_parameter() + cmd = tab_combine({{gs_prog}, gs_options, pdf_options}) + if options.gray then + cmd = tab_combine({cmd, gray_options}) + end + table.insert(cmd, '-dEPSCrop') -- uses existing hires bb + local psp = PsPdf:new('pdf') + table.insert(cmd, '-sOutputFile#'..psp.path) + cmd = tab_combine({cmd, pdf_tail_options}) + table.insert(cmd, self.path) + log_cmd(cmd) + local res = os.spawn(cmd) + if res and res==0 and lfs.attributes(psp.path, 'size')>0 then + psp.pages, psp.miver, psp.maver = pdf_props(psp.path) + options.gray = false + return psp + else + errror('eps_to_pdf failed on '..self.path) + end +end -- eps_to_pdf + +-- Converting from pdf to pdf with grayscaling and/or page selection +-- or just eliminating negative boundingbox + +function PsPdf:pdf_to_pdf() + + -- option to be fulfilled: gray, optionally page + -- side effect: makes bbox non-negative. + -- do not call this just for page selection because + -- pdf_crop can do this in a less invasive manner + if self.type~='pdf' then + errror('PsPdf:pdf_to_pdf called for non-pdf file '.. self.path) + end + local cmd + if options.page and options.page > self.pages then + errror('PsPdf:pdf_to_pdf called with non-existent page '.. options.page) + end + cmd = tab_combine({{gs_prog}, gs_options, pdf_options}) + if options.gray then + cmd = tab_combine({cmd, gray_options}) + end + if options.page then + table.insert(cmd, '-dFirstPage#'..tostring(options.page)) + table.insert(cmd, '-dLastPage#'..tostring(options.page)) + end + maybe_add_version_parameter() + local psp = PsPdf:new('pdf') + table.insert(cmd, '-sOutputFile#'..psp.path) + cmd = tab_combine({cmd, pdf_tail_options}) + table.insert(cmd, self.path) + log_cmd(cmd) + local res = os.spawn(cmd) + if res and res==0 and lfs.attributes(psp.path, 'size')>0 then + psp.pages, psp.miver, psp.maver = pdf_props(psp.path) + options.gray = false + options.page = false + return psp + else + errror('pdf_to_pdf failed on '..self.path) + end +end -- pdf_to_pdf + +function PsPdf:pdf_to_eps() + + -- options to be fulfilled: page + local psp = PsPdf:new('eps') + local cmd, res + -- any_to_any already checked the validity and relevance of options.page + if pdftops then + if options.page then + cmd = tab_combine({{pdftops}, ps_options, + {'-f', options.page, '-l', options.page, + '-eps', self.path, psp.path}}) + else + cmd = tab_combine({{pdftops}, ps_options, + {'-eps', self.path, psp.path}}) + end + log_cmd(cmd) + if os.type=='windows' then + -- suppress console output of 'No display font for...' messages, + -- which are usually harmless and for which I know no easy fix + -- pdftops -q does not do the trick on Windows, + -- and redirection to logfile gives access denied under miktex + -- res = os.spawn({'cmd', '/c', table.concat(cmd, ' ')..' 2>>'..log_bsl}) + res = os.execute(table.concat(cmd, ' ')..' 2>nul') + else + res = os.spawn(cmd) + end + if res and res==0 and lfs.attributes(psp.path, 'size')>0 then + psp.pages = 1 + options.page = false + else + errror('pdf_to_eps failed on '..self.path) + end + -- fix for incorrect DSC header produced by some versions of pdftops: + -- if necessary, change line `% Produced by ...' into `%%Produced by ...' + -- this is usually the second line. + -- otherwise the DSC header would be terminated before the bbox comment. + -- this problem exists with pdftops from TL2011/w32. + local slurp -- input buffer + local fin = io.open(psp.path, 'rb') + if not fin then errror('Cannot read '..psp.path) end + + -- remaining, unscanned length of input buffer slurp + local unscanned = math.min(lfs.attributes(psp.path, 'size'),bufsize) + slurp = fin:read(unscanned) + local i, i_bb = 0, false + local needs_fixing = false + local pre_lines = {} + local offset = 0 + while unscanned>0 do + i = i+1 + if string.find(slurp,'[\n\r]')==1 then + l,slurp = string.match(slurp, '^([\n\r]+)(.*)$') + else + l,slurp = string.match(slurp, '^([^\n\r]+)(.*)$') + if string.match(l, Bb.bb_pat) then + -- bbox line + i_bb = i + elseif string.match(l, '^%%%s') then -- `%' is escape char: doubled + -- %X with X printable would be ok + needs_fixing = true + -- fix rightaway + l = string.gsub(l, '^%%%s', '%%%%') -- same length + end + end -- eol/non-eol + pre_lines[i] = l + unscanned = unscanned - string.len(l) + offset = offset + string.len(l) + if i_bb then break end + end -- while + fin:close() + if needs_fixing then + -- write a new eps file + local newfile = mktemp('eps') + fout = io.open(newfile, 'wb') + if not fout then errror('Cannot write new file '.. newfile) end + fout:write(table.concat(pre_lines)) + fout:close() + slice_file(psp.path, newfile, + lfs.attributes(psp.path,'size') - offset, offset, 'ab') + psp.path = newfile + end -- needs_fixing + else -- use ghostscript + local epsdev = gs_epsdevice() + if not epsdev then + errror('Conversion to eps not supported by this ghostscript') + end + cmd = tab_combine({{gs_prog}, gs_options, + {'-sDEVICE#'..epsdev, '-dHaveTrueTypes=true', '-dLanguageLevel#3'}}) + if options.gray then + cmd = tab_combine({cmd, gray_options}) + end + if options.page then + table.insert(cmd, '-dFirstPage='..options.page) + table.insert(cmd, '-dLastPage='..options.page) + end + table.insert(cmd, '-sOutputFile='..psp.path) + table.insert(cmd, self.path) + log_cmd(cmd) + res = os.spawn(cmd) + if res and res==0 and lfs.attributes(psp.path, 'size')>0 then + psp.pages = 1 + options.page = false + options.gray = false + else + errror('pdf_to_eps failed on '..self.path) + end + end -- use ghostscript + psp:get_bb_simple() + return psp + +end -- pdf_to_eps + +function PsPdf:ps_to_pdf() + + -- options to be fulfilled: gray + if self.type~='ps' then + errror('PsPdf:ps_to_pdf called for non-ps file '.. self.path) + end + local cmd + cmd = tab_combine({{gs_prog}, gs_options, pdf_options}) + if options.gray then + cmd = tab_combine({cmd, gray_options}) + end + local psp = PsPdf:new('pdf') + table.insert(cmd, '-sOutputFile#'..psp.path) + cmd = tab_combine({cmd, pdf_tail_options}) + table.insert(cmd, self.path) + log_cmd(cmd) + local res = os.spawn(cmd) + if res and res==0 and lfs.attributes(psp.path, 'size')>0 then + psp.pages, psp.miver, psp.maver = pdf_props(psp.path) + options.gray = false + return psp + else + errror('ps_to_pdf failed on '..self.path) + end + +end -- PsPdf:ps_to_pdf + +function PsPdf:pdf_to_ps() + + -- options to be fulfilled: page and, if not using pdftops, also gray + local psp = PsPdf:new('ps') + -- options.page checked by any_to_any + psp.pages = self.pages + local cmd, res + if pdftops then + cmd = tab_combine({{pdftops}, ps_options}) + if options.page then + cmd = tab_combine({cmd, {'-f', options.page, '-l', options.page}}) + end + cmd = tab_combine({cmd, {'-paper', 'match', self.path, psp.path}}) + else -- use ghostscript + cmd = tab_combine({{gs_prog}, gs_options, + {'-sDEVICE#ps2write', '-dHaveTrueTypes=true', '-dLanguageLevel#3'}}) + if options.gray then + cmd = tab_combine({cmd, gray_options}) + end + if options.page then + cmd = tab_combine({cmd, + {'-dFirstPage#'..options.page, '-dLastPage#'..options.page}}) + end + table.insert(cmd, '-sOutputFile#'..psp.path) + table.insert(cmd, self.path) + end + log_cmd(cmd) + if os.type=='windows' and pdftops then + -- suppress console output of 'No display font for...' messages, + -- which are usually harmless and for which I know no easy fix + -- pdftops -q does not do the trick on Windows, + -- and redirection to logfile gives access denied under miktex + -- res = os.spawn({'cmd', '/c', table.concat(cmd, ' ')..' 2>>'..log_bsl}) + res = os.execute(table.concat(cmd, ' ')..' 2>nul') + else + res = os.spawn(cmd) + end + if res and res==0 and lfs.attributes(psp.path, 'size')>0 then + options.page = false + -- gs will have grayscaled if requested but pdftops will not + if not pdftops then options.gray = false end + return psp + else + errror('pdf_to_ps failed on '..self.path) + end +end -- PsPdf:pdf_to_ps + +function PsPdf:ps_to_ps() + -- we do not accept a page option since we do not know + -- which pages are available. + -- if no gray option then there is no point in invoking this function. + local psp = PsPdf:new('ps') + local cmd = tab_combine({{gs_prog}, gs_options, + {'-sDEVICE#ps2write', '-dHaveTrueTypes=true', '-dLanguageLevel#3'}}) + if options.gray then + cmd = tab_combine({cmd, gray_options}) + end + table.insert(cmd, '-sOutputFile#'..psp.path) + table.insert(cmd, self.path) + log_cmd(cmd) + local res = os.spawn(cmd) + if res and res==0 and lfs.attributes(psp.path, 'size')>0 then + options.gray = false + return psp + else + errror('ps_to_ps failed on '..self.path) + end +end -- PsPdf:ps_to_ps + +function any_to_any() + + local psp = PsPdf:from_path(infile) + + -- sanitize some options + + if options.type=='ps' then + options.bbox = false + end + + if (options.bbox or options.type=='eps') and not options.page then + options.page = 1 + end + + if psp.type=='pdf' then + if options.page then + local pgs = pdf_props(infile) + if pgs<options.page then + errror(infile..' has only '..pgs..' page(s).') + elseif pgs==1 then + options.page = false + end + end + elseif psp.type=='eps' then + options.page = false + end + -- psp.type=='ps': pages not yet known but maybe not needed + + local do_something = false + if psp.type=='eps' then + do_something = true -- do at least cleanup + elseif psp.type~=options.type then + do_something = true + elseif options.page then + do_something = true + elseif options.bbox then + do_something = true + elseif options.gray then + do_something = true + elseif options.type=='pdf' then + if psp:to_downgrade() then + do_something = true + elseif settings.pdf_target and settings.pdf_target~=default then + do_something = true + end + end + if not do_something then + if infile~=outfile then + slice_file(infile, outfile) + end + return true + end + + -- `distiller' settings + -- these depend on whether final output is pdf + -- '.setpdfwrite' is just some optimization option for ghostscript + if options.type=='pdf' then + if settings.pdf_target and (settings.pdf_target~='default') then + table.insert(pdf_options, '-dPDFSETTINGS#/'..settings.pdf_target) + end + if settings.pdf_target=='screen' or settings.pdf_target=='ebook' then + pdf_tail_options = {'-c', '.setpdfwrite', '-f'} + -- -f ensures that the input filename is not added to the -c string + else + pdf_tail_options = { + '-c', '.setpdfwrite <</NeverEmbed [ ] >> setdistillerparams', '-f'} + end + end + + --[[ Actual conversions + each single-step conversion takes care of options it can handle + and sets those options to false. + Cropping a pdf is best be done before converting to postscript or + a low (<1.4) pdf version. + + all invocations of external programs work on temporary files + in the then-current temporary directory, with a simple generated + filename. So no need to quote names of input- or output filenames. + --]] + + local newfile + + if psp.type=='eps' or psp.type=='epsPreview' then + -- As a side effect of eps_clean, the modified or unmodified source file + -- is copied to our temp subdirectory. + -- We always create a new file. + psp = psp:eps_clean() + if options.bbox and psp.bb:nonnegative() then + psp = psp:eps_crop() + end + if options.type=='eps' then + if options.gray or options.bbox then + -- bbox: eps_crop was apparently not applicable: pdf roundtrip + psp = psp:eps_to_pdf() + if options.bbox then + psp = psp:pdf_crop() + end + psp = psp:pdf_to_eps() + end + elseif options.type=='pdf' then + psp = psp:eps_to_pdf() + if options.bbox then + psp = psp:pdf_crop() + end + if psp:to_downgrade() then + maybe_add_version_parameter() + psp = psp:pdf_to_pdf() + end + elseif options.type=='ps' then + -- often, the eps file is fine as a postscript file. + -- however, it may lack a showpage operator, or have a weird + -- boundingbox. converting back-and-forth to pdf solves both problems. + -- eps_to_pdf will take care of grayscaling + -- a tight boundingbox option is not supported for ps output. + psp = psp:eps_to_pdf():pdf_to_ps() + end + slice_file(psp.path, outfile) + return true + + elseif psp.type=='ps' then + + -- preliminary: + -- copy infile to a file in the temp directory, needed for gs -dSAFER + newfile = mktemp(psp.type) + slice_file(psp.path, newfile) + psp.path = newfile + + if options.type=='ps' and not options.page then + psp = psp:ps_to_ps() + slice_file(psp.path, outfile) + return true + end + + -- remaining options require initial conversion to pdf + psp = psp:ps_to_pdf() + -- AFAIK, all high-level ps constructs are covered by any pdf version + -- so this option should not cause unnecessary loss of structure + maybe_add_version_parameter() + + if options.page and options.page>1 then + -- could not check page option before + local pgs = pdf_props(psp.path) + if pgs<options.page then + errror(infile..' has only '..pgs..' page(s).') + elseif pgs==1 then + options.page = false + end + end + + -- actual conversion + if options.type=='eps' then + if options.bbox then + psp = psp:pdf_crop() + end + psp = psp:pdf_to_eps() + elseif options.type=='pdf' then + if options.bbox or options.page then + psp = psp:pdf_crop() + -- else no action needed + end + elseif options.type=='ps' then + psp = psp:pdf_to_ps() + end -- pdf => ps + slice_file(psp.path, outfile) + return true + + elseif psp.type=='pdf' then + + if options.type=='pdf' and settings.pdf_target=='default' and + not options.gray and not options.bbox and not options.page and + not psp:to_downgrade() then + if infile~=outfile then + slice_file(infile, outfile) + end + return true + end + + -- preliminary: + -- copy infile to a file in the temp directory, needed for gs -dSAFER + newfile = mktemp(psp.type) + slice_file(psp.path, newfile) + psp = PsPdf:from_path(newfile) + local pgbox + + -- actual conversion + if options.type=='eps' then + if options.bbox then + pgbox = psp:getpgbox() -- page n. available from options table + if not pgbox:nonnegative() or options.gray then + -- fix in extra pdf-to-pdf step + psp = psp:pdf_to_pdf() + end + -- we want to calculate a tight boundingbox before conversion to eps, + -- because this conversion may cause rasterization and baffle + -- gs' boundingbox calculations + psp = psp:pdf_crop() + elseif options.gray and pdftops then + psp = psp:pdf_to_pdf() + end + psp = psp:pdf_to_eps() + elseif options.type=='pdf' then + -- pdf_crop can take care of bbox and page, + -- but not of gray and not of some pdf options + if options.bbox then + pgbox = psp:getpgbox() + if not pgbox:nonnegative() then + -- pdf_to_pdf fixes negative bbox parameters + -- and also takes care of page and gray + psp = psp:pdf_to_pdf() + end + psp = psp:pdf_crop() + end + need_gs = false + if psp:to_downgrade() then + need_gs = true + maybe_add_version_parameter() + end + if settings.pdf_target~='default' then + need_gs = true + end + if options.gray then + need_gs = true + end + if (not need_gs) and options.page then + psp = psp:pdf_crop() + -- less invasive that page selection by gs + end + if need_gs then + psp = psp:pdf_to_pdf() -- will handle page selection too + end + elseif options.type=='ps' then + if options.gray and pdftops then + psp = psp:pdf_to_pdf():pdf_to_ps() + else + psp = psp:pdf_to_ps() + end + end -- pdf => ps + slice_file(psp.path, outfile) + return true + end +end -- any_to_any + +-- system-dependent initialization ----------------------------------- + +-- current directory, at program start +cwd = lfs.currentdir() +if os.type == 'windows' then cwd = string.gsub(cwd, '\\', '/') end + +-- child searchpath initially set to parent searchpath +-- childpath = os.getenv('PATH') + +-- prepend (lua)tex directory to searchpath, if not already there +maybe_add_path(os.selfdir, false) + +-- Windows: miktex, TL or neither. is_miktex, is_tl_w32 +-- no support yet for separate ghostscript +is_miktex = false +is_tl_w32 = false + +if os.type == 'windows' then + if string.find (string.lower(kpse.version()), 'miktex') then + is_miktex = true + else + local rt = string.gsub(os.selfdir,'[\\/][^\\/]+[\\/][^\\/]+$', '') + if not rt then + errror('Unrecognized TeX directory structure', 0) + elseif lfs.isfile(rt..'/release-texlive.txt') then + --[[ + -- TL version is easy to determine but is not needed + local fin = io:open(rt..'release-texlive.txt', 'r') + if fin then + local l = fin:read('*line') + tl_ver = string.match(l, 'version%s+(%d+)$') + if tl_ver then tl_ver = tonumber(tl_ver) end + end -- if fin + --]] + is_tl_w32 = true + else + errror('Not MikTeX and no file ' .. rt .. + '/release-texlive.txt; TeX installation not supported.', 0) + end -- if isfile + end -- if not miktex +end -- if windows + +-- without Ghostscript we are dead in the water. +gs_prog = false +do + local rt='' + if os.type == 'unix' then + if find_on_path('gs') then + gs_prog = 'gs' + else + error('No ghostscript on searchpath!', 0) + end + elseif is_miktex then + gs_prog = 'mgs.exe' + -- neither MiKTeX's nor TL's ghostscript need GS_LIB to be set + elseif is_tl_w32 then + -- windows/TeX Live + -- grandparent of texlua.exe directory .. ... + -- rt = string.gsub(os.selfdir,'[\\/][^\\/]+[\\/][^\\/]+$', '') + -- ..'/tlpkg/tlgs' + -- maybe_add_path(rt..'/bin', false) + -- -- gs_prog = 'gswin32c.exe' + -- ---[[ problems with (at least) grayscaling + gs_prog = 'rungs.exe' + --]] + else + errror('Only TeX Live and MikTeX supported!', 0) + end +end -- do + +-- directory for configuration and log +epsdir = '' +if os.type == 'windows' then + epsdir = fw(os.getenv('APPDATA')) .. '/epspdf' +else + epsdir = os.getenv('HOME')..'/.epspdf' +end +rcfile = epsdir .. '/config' +logfile = epsdir .. '/epspdf.log' + +-- create epsdir if necessary +if lfs.isfile(epsdir) then + error('Cannot continue; epspdf directory ' .. epsdir .. ' is a file') +elseif not lfs.isdir(epsdir) then + if not lfs.mkdir(epsdir) then + error('Failed to create epspdf directory ' .. epsdir) + end +end + +-- start logging --------------------------------- + +-- log rotate if logfile too big +if lfs.attributes(logfile) and lfs.attributes(logfile).size > 100000 then + local oldlog = logfile .. '.old' + if lfs.attributes(oldlog) then + if os.remove(oldlog) then os.rename(logfile,oldlog) end + elseif lfs.attributes(logfile) then do + -- separate epsdir runs with empty lines + print_log('\n\nNew run') + end end -- do elseif +end -- if lfs...logfile + +write_log('epspdf '..table.concat(arg, ' ')) + +--[[ + +settings, initial values + +priority, from low to high: + +- built-in defaults +- settings read from and written to the configuration file +- command-line options, defined in the opts table + +The options- and settings tables are initialized from built-in +defaults. We go for false rather than undefined, because this +results in an actual settings- or options entry. + +Command-line options are copied to either options or settings. +We ignore illegal settings in the config file. + +--]] + +pdf_targets = {'screen', 'ebook', 'printer', 'prepress', 'default'} +pdf_versions = {'1.2', '1.3', '1.4', '1.5', '1.6', '1.7', 'default'} +-- ghostscript will substitute higher versions with +-- the highest-supported version + +settings = {} +descriptions = {} + +settings.pdf_target = 'default' +descriptions.pdf_target = 'One of ' .. join(pdf_targets, ', ', ' or ') + +settings.pdf_version = 'default' +descriptions.pdf_version = 'One of ' .. join(pdf_versions, ', ', ' or ') + +settings.use_pdftops = true +descriptions.use_pdftops = 'Use pdftops if available' + +-- epspdf stores ps- and pdf viewer settings on behalf of the gui interface +-- but does not use them itself. +-- They will not be used at all under macos or windows. + +settings.ps_viewer = false +descriptions.ps_viewer = + 'Epspdftk: viewer for PostScript files; not used on Windows or OS X' + +settings.pdf_viewer = false +descriptions.pdf_viewer = + 'Epspdftk: viewer for pdf files; not used on Windows or Mac OS' + +-- default_dir, which is used on all platforms, is only for the gui. + +if os.type == 'windows' then + settings.default_dir = + string.gsub(os.getenv('USERPROFILE'), '\\', '/') +else + settings.default_dir = os.getenv('HOME') +end +descriptions.default_dir = + 'Epspdftk: initial directory; ignored by epspdf itself' + +-- options ------------------------------------- + +-- besides settings, which can be saved, we also use options which are not. +-- these are mostly conversion options. + +options = {} +options.page = false +options.gray = false +options.bbox = false +options.debug = false +options.type = false -- implied via output filename on command line + +-- command-line fragments for conversions -------------------- + +-- We could make these `class attributes' for PsPdf but to what purpose? +-- For Windows shell commands, we need to substitute `#' for `=' +-- when invoking Ghostscript. For simplicity, we do this across the board. +-- -P- : do not look first in current directory + +gs_options = {'-q', '-dNOPAUSE', '-dBATCH', '-P-', '-dSAFER'} + +-- may add custom options later + +pdf_options = {'-sDEVICE#pdfwrite'} -- '-dUseCIEColor' causes serious slowdown +-- options for final conversion to pdf; +-- will be completed after reading settings and options +pdf_tail_options = {'-c', '.setpdfwrite', '-f'} +gray_options = {'-dProcessColorModel#/DeviceGray', + '-sColorConversionStrategy#Gray'} + +pdftops = false +-- gets a value only if we are going to use pdftops + +ps_options = {'-level3'} + +-- `main program' inside scope-creating block ---------------------- + +do -- main program + + local in_dir = false -- directory of infile + local out_dir = false -- directory of outfile + + do -- Handle settings and command-line inside nested scope -------------- + + read_settings(rcfile) + + local opts = {} + + opts.page = { + type = 'string', val = nil, + forms = {'-p', '--page', '--pagenumber'}, + placeholder = 'PNUM', + negforms = nil, + help = 'Page number; must be a positive integer' + } + + opts.gray = { + type = 'boolean', val = nil, + forms = {'-g', '--grey', '--gray', '-G', '--GREY', '--GRAY'}, + negforms = nil, + help = 'Convert to grayscale' + } + + opts.bbox = { + type = 'boolean', val = nil, + forms = {'-b', '--bbox', '--BoundingBox'}, + negforms = nil, + help = 'Compute tight boundingbox' + } + + ---[[ ignored; included for backward compatibility + opts.use_hires_bb = { + type = 'boolean', val = nil, + forms = {'-r', '--hires'}, + negforms = {'-n', '--no-hires'}, + } + + opts.custom = { + type = 'string', val = nil, + forms = {'-C', '--custom', '-P', '--psoptions'}, + negforms = nil + } + --]] + + opts.pdf_target = { + type = 'string', val = nil, + forms = {'-T', '--target'}, + placeholder = 'TARGET', + negforms = nil, + help = descriptions.pdf_target + } + opts.pdf_version = { + type = 'string', val = nil, + forms = {'-N', '--pdfversion'}, + placeholder = 'VERSION', + negforms = nil, + help = descriptions.pdf_version + } + + opts.use_pdftops = { + type = 'boolean', val = nil, + forms = {'-U'}, + negforms = {'-I'}, + help = descriptions.use_pdftops + } + + opts.info = { + type = 'boolean', val = nil, + forms = {'-i', '--info'}, + negforms = nil, + help = 'Info: display detected filetype and exit' + } + + opts.help = { + type = 'boolean', val = nil, + forms = {'-h', '--help'}, + negforms = nil, + help = 'Display this help message and exit' + } + + opts.version = { + type = 'boolean', val = nil, + forms = {'-v', '--version'}, + negforms = nil, + help = 'Display version info and exit' + } + + opts.save = { + type = 'boolean', val = nil, + forms = {'-s', '--save'}, + negforms = nil, + help = 'Save some settings to configuration file' + } + + opts.debug = { + type = 'boolean', val = nil, + forms = {'-d'}, + negforms = nil, + help = 'Debug: do not remove temp files' + } + + opts.gui = { + type = 'string', val = nil, + forms = {'--gui'}, + negforms = nil, + help = nil -- reserved for use by epspdftk + } + + -- a couple of functions only available during command-line parsing + + local function show_version () + print('Epspdf version 0.6.4\nCopyright (c) 2006-2019 Siep Kroonenberg') + end + + local function help (mess) -- requires opts array + if mess then + print(mess..eol) + end + show_version() + -- below, string.gsub unindents its long-string parameter. + -- string.format removes the second return value of string.gsub. + print( -- string.format('%s', + string.gsub([[ + + Convert between [e]ps and pdf formats + Usage: epspdf[.tlu] [options] infile [outfile] + Default for outfile is file.pdf if infile is file.eps or file.ps + Default for outfile is file.eps if infile is file.pdf + ]], '([\r\n]+) ', '%1')) --) + + -- need to enforce an ordering, otherwise we could have used pairs(opts) + -- omitted below: no-op options + -- one line where possible + local indent_n = 12 + local intent_sp = string.rep(' ', indent_n) + local indent_fmt = '%-' .. tostring(indent_n) .. 's' + for _, o in ipairs({'page', 'gray', 'bbox', 'pdf_target', 'pdf_version', + 'use_pdftops', 'save', 'info', 'debug', 'version', 'help'}) do + local v = opts[o] + if v and v.help then + local synt = join(v.forms, ', ') + if v.type ~= 'boolean' then synt = synt .. ' ' .. v.placeholder end + if string.len(synt)<indent_n then + print(string.format(indent_fmt, synt) .. v.help) + else + print(synt) + print(intent_sp .. v.help) + end + if v.negforms then + local neghelp = 'Reverses the above' + synt = join(v.negforms, ', ') + if string.len(synt)<indent_n then + print(string.format(indent_fmt, synt) .. neghelp) + else + print(synt) + print(intent_sp .. neghelp) + end + end + end + end -- for + if mess then os.exit(1) else os.exit() end + end -- help + + if #arg < 1 then help('No parameters') end + + -- command-line parsing + + -- -r="tata tata" is parsed by [tex]lua as a single argument + -- lua/linux retains the quotes, + -- lua/windows strips them. + -- texlua strips them, both on unix and on windows. + + local i=1 + while i<=#arg and string.sub(arg[i],1,1)=='-' do + local parsed = false + local kk, vv = string.match(arg[i],'([^=]+)=(.*)$') + if kk==nil then + kk = arg[i] -- also vv==nil + else + vv = strip_outer_spaces(vv) + end + for p, o in pairs(opts) do + if in_list(kk, o.forms) or in_list(kk, o.negforms) then + parsed = true + if o.type == 'boolean' then + if vv then help(kk..' should not have a parameter.') end + if in_list(kk, o.forms) then + o.val = true + else + o.val = false + end + elseif vv then + o.val = vv + else + i = i + 1 + if i>#arg then + help('Missing parameter to '..kk) + end + o.val = strip_outer_spaces(arg[i]) + end -- testing for o.type or vv + break -- for + end -- if in_list + end -- for + if not parsed then help('illegal parameter '..kk) end + i = i + 1 + end -- while + + -- check and interpret opts. + -- Copy to either settings or to options table. + -- at syntax error, abort via help function. + + -- page + + if opts.page.val then + local pnum = tonumber(opts.page.val) + if pnum<=0 or math.floor(pnum) ~= pnum then + help(opts.page.val..' not a positive integer') + else + options.page = pnum + end + end + + -- grayscaling + + if opts.gray.val then + options.gray = true + else + options.gray = false + end + + -- boundingbox + + if opts.bbox.val then + options.bbox = true + else + options.bbox = false + end + + --[[ + -- using hires boundingbox + + if opts.use_hires_bb.val~=nil then + settings.use_hires_bb = opts.use_hires_bb.val + end + --]] + + -- using pdftops + + if opts.use_pdftops.val~=nil then + settings.use_pdftops = opts.use_pdftops.val + end + + -- pdf target use + + if opts.pdf_target.val~=nil then + if in_list(opts.pdf_target.val, pdf_targets) then + settings.pdf_target = opts.pdf_target.val + else + help('Illegal value '..opts.pdf_target.val..' for pdf_target') + end + end + + -- pdf version + + if opts.pdf_version.val~=nil then + if in_list(opts.pdf_version.val, pdf_versions) then + settings.pdf_version = opts.pdf_version.val + else + help('Illegal value '..opts.pdf_version.val..' for pdf_version') + end + end + + -- pdftops should be on the path + if settings.use_pdftops then + if os.type=='windows' then + pdftops = find_on_path('pdftops.exe') + else + pdftops = find_on_path('pdftops') + end + end + + -- other options + + if opts.save.val then + write_settings(rcfile) + end + + if opts.debug.val then + options.debug = true + end + + if opts.help.val then + help() + end + + -- opts.info.val: do later; need to get infile first + + if opts.version.val then + show_version() + os.exit() + end + + if opts.gui.val then + gui(opts.gui.val) + end + + -- now we need 1 or 2 filenames, unless the user really only + -- wanted to save options without further action. + + if i>#arg then + if opts.save.val then os.exit() else help('No filenames') end + end + + infile = arg[i] + outfile = false + if i<#arg then + outfile = arg[i+1] + end + if (#arg>i and opts.info.val) or (#arg>i+1) then + help('Surplus non-option parameters') + end + + if not outfile and not opts.info.val then + -- derive outfile from infile: [e]ps => pdf, pdf => eps + if intype=='pdf' then + outfile = string.gsub(infile,'%.[^%.]*$','eps') + else + outfile = string.gsub(infile,'%.[^%.]*$','.pdf') + end + end + + -- one final quick option + if opts.info.val then + info() + end + + end -- do (decoding command-line) + + --[[ + + Once it becomes clear that real work needs to be done, + we shall create a temp directory. + + because of gs -dSAFER restrictions, infile must be in (a + subdirectory of) the directory of the output file, e.g. in the + temp directory. So we copy infile to the temp directory. + + --]] + + if not lfs.isfile(infile) or lfs.attributes(infile, 'size')==0 then + errror(infile..' does not exist or is empty.') + end + infile, in_dir = absolute_path(infile) + outfile, out_dir = absolute_path(outfile) + if not out_dir then + errror('Invalid output directory for '.. outfile) + end + + -- directory for temporary files + + -- previously, we used a subdirectory of the target directory. + -- however, since under windows cleanup may fail, we now try to use + -- a directory under a dedicated temp directory, which has a better chance + -- of getting cleaned up by the system. + + lfs.chdir(system_tempdir() or out_dir) + -- no check for failure; we create a subdirectory in + -- whatever is the current directory + tempdir = os.tmpdir() + if not tempdir then + errror('Cannot create directory for temporary files') + end + lfs.chdir(tempdir) + + -- determine filetype from first few bytes of file + intype = identify(infile) + + -- remaining cases: want a real conversion + if not intype then + errror(infile..' has an unsupported filetype') + end + + -- valid output filetype? + options.type = string.match(outfile, '.*%.([^%.]+)$') + if not options.type or (options.type~='ps' and + options.type~='eps' and options.type~='pdf') then + errror('Output file '..outfile.. + ' should have extension .eps, .ps or .pdf') + end + + if outfile==infile then + slice_file(infile, infile..'.backup') + -- copy, not move, since outfile may be infile if there was nothing to do + end + + result = any_to_any(infile, outfile) + if not lfs.isfile(outfile) or lfs.attributes(outfile, 'size')==0 then + errror('Failed to generate '..outfile) + end + + if not options.debug then + cleantemp() + end +end diff --git a/support/epspdf/epspdf4tk.cmd b/support/epspdf/epspdf4tk.cmd new file mode 100644 index 0000000000..0bd30ab7a4 --- /dev/null +++ b/support/epspdf/epspdf4tk.cmd @@ -0,0 +1 @@ +@texlua "%~dp0epspdf.tlu" %*
diff --git a/support/epspdf/epspdftk.tcl b/support/epspdf/epspdftk.tcl new file mode 100755 index 0000000000..26ed17efcf --- /dev/null +++ b/support/epspdf/epspdftk.tcl @@ -0,0 +1,737 @@ +#!/usr/bin/env wish + +# epspdf conversion utility, GUI frontend + +##### +# Copyright (C) 2006-2019 Siep Kroonenberg +# siepo at bitmuis dot nl +# +# This program is free software, licensed under the GNU GPL, >=2.0. +# This software comes with absolutely NO WARRANTY. Use at your own risk! +##### + +package require Tk + +if {[string index $::tcl_patchLevel 2] <5} { + tk_messageBox -message "Tcl/Tk version >= 8.5 required;\naborting..." + exit +} + +# tired of writing out this combined test again and again +set classic_unix [expr {$::tcl_platform(platform) eq "unix" && \ + $::tcl_platform(os) ne "Darwin"}] + +# normally, epspdf.tlu should be in the same directory +# and texlua should be on the searchpath. +# However, the Windows installer version wraps this script in a starpack. + +# logging: to a log window, not to a file +proc write_log {s} { + if {[winfo exists .log_t.text]} { + .log_t.text configure -state normal + .log_t.text insert end "$s\n" + .log_t.text yview moveto 1 + .log_t.text configure -state disabled + # } else { + # puts $s + } +} + +### invoking epspdf.tlu ######################### + +# Get full path of epspdf.tlu. It should be in the same directory as +# either this script or of the starpack containing this script, +# For non-windows versions, epspdftk might be called via a symlink. +# Pdftops done elsewhere. + +proc set_progs {} { + set scriptfile [file normalize [info script]] + set eproot [file dirname $scriptfile] + # if symlink, get the directory of the file it points to + if {! [catch {file readlink $scriptfile}]} { + # evaluate readlink from symlink directory + set savedir [pwd] + cd $eproot + set eproot [file dirname [file normalize [file readlink $scriptfile]]] + cd $savedir + } + # find the lua script + set ::epspdf_tlu [file join $eproot "epspdf.tlu"] + if {! [file exists $::epspdf_tlu]} { + # if starpack, look in binary directory + set eproot [file dirname [file normalize [info nameofexecutable]]] + set ::epspdf_tlu [file join $eproot "epspdf.tlu"] + } + if {$::tcl_platform(platform) eq "windows"} { + # For actual conversions, epspdf invokes third-party command-line programs. + # To prevent the appearing of transient black console windows for such + # invocations, epspdf will be invoked via a batchfile. + # This is not necessary for getting info about a file. + set ::epspdf_cmd [file join $eproot "epspdf4tk.cmd"] + } + if {! [file exists $::epspdf_tlu]} { + tk_messageBox -type ok -icon error -message "Epspdf.tlu not found" + exit 1 + } + # texlua should be on the searchpath + set ::texlua "texlua" + + # icon for starpack: add with sdx or with Windows utilities +} + +set_progs + +### configured and automatic settings ################################## + +# is_prog used for checking configured viewers under non-osx unix +proc is_prog {x} { + if {[expr {$::tcl_platform(platform) ne "unix"}]} {return 0} + # avoid current directory except with explicit directory + if {[expr {[string first "/" $x] >= 0 && \ + [file executable [file normalize $x]]}]} { + return 1 + } + # also check search path + set p [split $::env(PATH) ":"] ; # no need to accomodate msw + foreach d $p { + if {[expr {$d ne "" && [file executable [file join $d $x]]}]} { + return 1 + } + } + return 0 +} + +# create a global empty settings array +array set ::settings [list] + +# ask epspdf.tlu for currently configured settings. +# this does not include automatically configured or transient settings. +# the availability of viewers is handled here. +proc getsettings {} { + if [catch {exec $::texlua $::epspdf_tlu --gui=config_w} set_str] { + error "Epspdf configuration error: $set_str" + } + # write_log "settings from epspdf.tlu:\n$set_str\n" + set l [split $set_str "\r\n"] + foreach e $l { + # puts "settings: $e" + # $e is either a string "var = value" + # or the empty string between <cr> and <lf> + set i [string first "=" $e] + if {$i>0} { + # write_log $e + set para [string trim [string range $e 0 [expr $i-1]]] + set val [string trim [string range $e [expr $i+1] end]] + if {$val eq "true"} {set val 1} + if {$val eq "false"} {set val 0} + set ::settings($para) $val + # write_log "setting $para is $val" + } + } + + # unix: viewer settings + # configured viewer, if valid, heads the list + if {$::classic_unix} { + + set ::ps_viewers {} + if {$::settings(ps_viewer) ne "" && [is_prog $::settings(ps_viewer)]} { + lappend ::ps_viewers $::settings(ps_viewer) + } + foreach v {evince okular gv qpdfview} { + if {$v ne $::settings(ps_viewer) && [is_prog $v]} { + lappend ::ps_viewers $v + } + } + # puts [join $::ps_viewers " "] + + set ::pdf_viewers {} + if {$::settings(pdf_viewer) ne "" && [is_prog $::settings(pdf_viewer)]} { + lappend ::pdf_viewers $::settings(pdf_viewer) + } + foreach v {evince okular mupdf qpdfview texworks xpdf zathura} { + if {$v ne $::settings(pdf_viewer) && [is_prog $v]} { + lappend ::pdf_viewers $v + } + } + # puts [join $::pdf_viewers " "] + + if {[llength ::pdf_viewers] == 0 && [llength ::ps_viewers] != 0} { + lappend ::pdf_viewers [lindex $::ps_viewers 0] + } + if {[llength ::pdf_viewers] == 0} { + tk_messageBox -message "No viewers found" + } elseif {[llength ::ps_viewers] == 0} { + tk_messageBox -message "No PostScript viewers found" + } + if {[llength ::pdf_viewers] > 0} { + set ::settings(pdf_viewer) [lindex $::pdf_viewers 0] + } + if {[llength ::ps_viewers] > 0} { + set ::settings(ps_viewer) [lindex $::ps_viewers 0] + } + } +} + +getsettings + +proc write_settings {} { + set s "" + foreach el [array names ::settings] { + set s "$s$el = $::settings($el)\n" + } + # write_log "\nsettings for epspdf.tlu\n$s\nend writing settings\n" + if [catch {exec $::texlua $::epspdf_tlu --gui=config_r << $s} result] { + error "Epspdf configuration error: $result" + } +} + +# directory and other file data + +if {$::argc > 0 && [file isdirectory [lindex $::argv 0]]} { + set ::gfile(dir) [lindex $::argv 0] +} elseif {[file isdirectory $::settings(default_dir)]} { + set ::gfile(dir) $::settings(default_dir) +} else { + set ::gfile(dir) $::env(HOME) +} +set ::gfile(path) "" +set ::gfile(type) "" +set ::gfile(name) "" +set ::gfile(npages) "" + +# transient options +array set ::options [list gray "color" format "pdf" bbox 0 clean 1 \ + pages "single" page 1] + +proc viewable {} { + if { $::gfile(name) eq "" || ! [file exists $::gfile(path)] || \ + $::gfile(type) eq "other" || $::gfile(type) eq ""} { + return 0 + } elseif {! $::classic_unix} { + return 1 + } elseif {$::settings(ps_viewer) ne "" && [regexp {ps} $::gfile(type)]} { + return 1 + } elseif {$::settings(pdf_viewer) ne "" && $::gfile(type) eq "pdf"} { + return 1 + } else { + return 0 + } +} + +### groundwork for GUI ################################# + +# padding for buttons and frames +proc ppack {b args} {pack $b {*}$args -padx 3 -pady 3} + +# bold font +font create bfont {*}[font configure TkDefaultFont] +font configure bfont -weight bold + +proc update_combo {w vls} { + upvar $vls vs + set new [$w get] + if {$new ni $vs} { + if {[is_prog $new]} { + set vs [linsert $vs 0 $new] + $w configure -values $vs + } else { + tk_messageBox -title Error -icon error -message "$vl Not a program" + } + } +} + +### and now the actual GUI ################################### + +wm title . "PostScript- and pdf conversions" + +# help toplevel + +proc readhelp {} { + .help_t.text configure -state normal + # this also works for the starpack: + set helpfile [file join [file dirname $::epspdf_tlu] "epspdf.help"] + if {! [file exists $helpfile]} { + # helpfile in starpack + set helpfile [file join [file dirname [info script]] "epspdf.help"] + } + if {[catch {set fid [open $helpfile r]}]} { + .help_t.text insert end "No helpfile $helpfile found\n" + } else { + while {[gets $fid line] >= 0} {.help_t.text insert end "$line\n"} + close $fid + } + .help_t.text configure -state disabled + .help_t.text yview moveto 0 +} + +toplevel .help_t +wm title .help_t "EpsPdf help" +pack [ttk::frame .help_t.bg -padding 3] -fill both -expand 1 +ppack [ttk::button .help_t.done -text "Close" -command {wm withdraw .help_t}] \ + -in .help_t.bg -side bottom -anchor e -padx 50 +text .help_t.text -wrap word -width 60 -height 20 -setgrid 1 \ + -yscrollcommand ".help_t.scroll set" +ttk::scrollbar .help_t.scroll -command ".help_t.text yview" +pack .help_t.scroll -in .help_t.bg -side right -fill y +pack .help_t.text -in .help_t.bg -expand 1 -fill both +readhelp +.help_t.text configure -state disabled +wm withdraw .help_t + +# log toplevel + +toplevel .log_t +wm title .log_t "Epspdf log" +pack [ttk::frame .log_t.bg -padding 3] -fill both -expand 1 +ppack [ttk::button .log_t.b -text "Close" -command {wm withdraw .log_t}] \ + -in .log_t.bg -side bottom -anchor e -padx 50 +text .log_t.text -wrap word -relief flat -height 15 -width 60 \ + -setgrid 1 -yscrollcommand ".log_t.scroll set" +ttk::scrollbar .log_t.scroll -command ".log_t.text yview" +pack .log_t.scroll -in .log_t.bg -side right -fill y +pack .log_t.text -in .log_t.bg -expand 1 -fill both +.log_t.text configure -state disabled +wm withdraw .log_t + +### configure toplevel ################### + +toplevel .config_t +wm title .config_t "Configure epspdf" +pack [ttk::frame .config_t.bg -padding 3] -fill both -expand 1 + +# The settings array is edited directly in the configuration screen: +# for most control widgets, we need a global external variable anyway, +# so we better use the global that is already there. +# If the configuration screen is closed via the cancel button, +# the original values will be re-read from disk. +# If it is closed via the ok button, the new settings are written to disk. + +# viewers (not on windows or osx) + +if {$::classic_unix} { + ppack [ttk::frame .config_t.viewf] -in .config_t.bg -ipadx 4 -fill x + grid [ttk::label .config_t.viewf.title -font bfont -text "Viewers"] \ + -row 0 -column 0 -sticky w + grid [ttk::label .config_t.viewf.lb_pdf -text "Pdf"] \ + -row 1 -column 0 -sticky w + grid [ttk::label .config_t.viewf.lb_ps -text "PostScript"] \ + -row 2 -column 0 -sticky w + grid [ttk::combobox .config_t.viewf.pdf] -row 1 -column 1 -sticky e + .config_t.viewf.pdf configure -values $::pdf_viewers + .config_t.viewf.pdf configure -textvariable ::settings(pdf_viewer) + bind .config_t.viewf.pdf <Return> {update_combo %W $::pdf_viewers} + grid [ttk::combobox .config_t.viewf.ps] -row 2 -column 1 -sticky e + .config_t.viewf.ps configure -values $::ps_viewers + .config_t.viewf.ps configure -textvariable ::settings(ps_viewer) + bind .config_t.viewf.ps <Return> {update_combo %W $::ps_viewers} + grid columnconfigure .config_t.viewf 1 -weight 1 -pad 2 +} + +# settings for conversion to pdf + +ppack [ttk::frame .config_t.pdff] \ + -in .config_t.bg -ipadx 4 -fill x -pady [list 10 0] +pack [ttk::label .config_t.pdff.title -font bfont -text "Conversion to pdf"] \ + -anchor w + +pack [ttk::label .config_t.pdff.l_target -text "Target use"] -anchor w +pack [ttk::frame .config_t.pdff.f_targets] -fill x +foreach t {default printer prepress screen ebook} { + pack [ttk::radiobutton .config_t.pdff.f_targets.$t \ + -variable ::settings(pdf_target) \ + -text $t -value $t] -side left -padx 2 -pady 4 -anchor w +} + +pack [ttk::label .config_t.pdff.l_version -text "Pdf version"] -anchor w +pack [ttk::frame .config_t.pdff.f_version] -fill x +foreach t {1.2 1.3 1.4 1.5 1.6 1.7 default} { + regsub {\.} $t _ tp ; # replace dot in name: dots are path separators! + pack [ttk::radiobutton .config_t.pdff.f_version.$tp \ + -variable ::settings(pdf_version) \ + -text $t -value $t] -side left -padx 2 -pady 4 -anchor w +} + +# settings for conversion to EPS and PostScript + +ppack [ttk::frame .config_t.psf] \ + -in .config_t.bg -pady [list 10 0] -ipadx 4 -fill x +pack [ttk::label .config_t.psf.l_ps -text "Conversion to EPS and PostScript" \ + -font bfont] -anchor w + +pack [ttk::checkbutton .config_t.psf.c \ + -text "Use pdftops if available"] -anchor w +.config_t.psf.c configure -variable ::settings(use_pdftops) \ + -onvalue 1 -offvalue 0 + +# buttons for closing the configuration screen + +pack [ttk::frame .config_t.buttonsf] -in .config_t.bg -pady [list 10 0] -fill x +ppack [ttk::button .config_t.buttonsf.done -text "Done" -command putsettings] \ + -side right +ppack [ttk::button .config_t.buttonsf.cancel -text "Cancel" \ + -command cancelsettings] -side right + +wm transient .config_t +wm overrideredirect .config_t +wm withdraw .config_t + +proc edit_settings {} { + wm deiconify .config_t + raise .config_t + grab set .config_t +} + +# store new settings +proc putsettings {} { + wm withdraw .config_t + grab release .config_t + write_settings +} + +proc cancelsettings {} { + wm withdraw .config_t + grab release .config_t + # re-read config file / reg entries + getsettings +} + +### main screen ############################# + +proc show_w {w} { + wm deiconify $w + raise $w +} + +pack [ttk::frame .bg -padding 3] -fill both -expand 1 + +# buttons to call up configure-, log- and help screens + +pack [ttk::frame .topf] -in .bg -fill x +ppack [ttk::button .topf.config_t -text "Configure" \ + -command edit_settings] -side left +ppack [ttk::button .topf.help_t -text "Help" \ + -command {show_w .help_t}] -side right +ppack [ttk::button .topf.logb -text "Show log" -command {show_w .log_t}] \ + -side right -anchor w + +# file info in grid layout + +ppack [ttk::frame .infof -relief sunken -border 1 -padding 3] -in .bg -fill x +grid [ttk::label .infof.dir_label -text "Directory" -anchor w] \ + -row 1 -column 1 -sticky w +grid [ttk::label .infof.dir_value -textvariable ::gfile(dir) -anchor w] \ + -row 1 -column 2 -sticky w + +grid [ttk::label .infof.name_label -text "File" -anchor w] \ + -row 2 -column 1 -sticky w +grid [ttk::label .infof.name_value -textvariable ::gfile(name) -anchor w] \ + -row 2 -column 2 -sticky w + +grid [ttk::label .infof.type_label -text "Type" -anchor w] \ + -row 3 -column 1 -sticky w +grid [ttk::label .infof.type_value -textvariable ::gfile(type) -anchor w] \ + -row 3 -column 2 -sticky w + +grid [ttk::label .infof.npages_label -text "Pages" -anchor w] \ + -row 4 -column 1 -sticky w +grid [ttk::label .infof.npages_value -textvariable ::gfile(npages) -anchor w] \ + -row 4 -column 2 -sticky w + +grid columnconfigure .infof 1 -weight 1 -pad 2 +grid columnconfigure .infof 2 -weight 3 -pad 2 + +# conversion options + +pack [ttk::frame .optsf] -in .bg -pady [list 10 0] -fill x + +# grayscaling +pack [ttk::frame .optsf.gray] -side left -anchor nw +pack [ttk::label .optsf.gray.l -text "Grayscaling"] -anchor w +pack [ttk::radiobutton .optsf.gray.off -text "No color conversion" \ + -variable ::options(gray) -value "color"] -anchor w +pack [ttk::radiobutton .optsf.gray.gray -text "Grayscale" \ + -variable ::options(gray) -value "gray"] -anchor w + +# output format +pack [ttk::label .optsf.format] -side right -anchor ne +pack [ttk::label .optsf.format.l -text "Output format"] -anchor w +ttk::radiobutton .optsf.format.pdf -text "pdf" \ + -command set_widget_states -variable ::options(format) -value "pdf" +ttk::radiobutton .optsf.format.eps -text "eps" -command set_widget_states \ + -variable ::options(format) -value "eps" +ttk::radiobutton .optsf.format.ps -text "ps" -command set_widget_states \ + -variable ::options(format) -value "ps" +pack .optsf.format.pdf -anchor w +pack .optsf.format.eps -anchor w +pack .optsf.format.ps -anchor w + +# boundingbox +pack [ttk::checkbutton .bbox -text "Compute tight boundingbox" \ + -variable ::options(bbox) -command set_widget_states] \ + -in .bg -anchor w -pady [list 10 0] + +# page selection +pack [ttk::frame .pagesf] -in .bg -fill x +pack [ttk::radiobutton .pagesf.all -text "Convert all pages" \ + -variable ::options(pages) -value "all" -command set_widget_states] \ + -side left +pack [ttk::radiobutton .pagesf.single -text "Page:" \ + -variable ::options(pages) -value "single" -command set_widget_states] \ + -side left -padx [list 10 0] +pack [entry .pagesf.e -width 6 -textvariable ::options(page)] -side left +#.pagesf.e configure -vcmd {page_valid %W} -validate focusout \ +# -invcmd "focusAndFlash %W [.pagesf.e cget -fg] [.pagesf.e cget -bg]" +.pagesf.e configure -vcmd {page_valid %W} -validate focusout \ + -invcmd "see_red %W" + +# temp files +pack [ttk::checkbutton .clean -text "Remove temp files" \ + -variable ::options(clean)] -in .bg -anchor w -pady [list 10 0] + +proc focusAndFlash {w fg bg {count 9}} { + focus $w + if {$count<1} { + $w configure -foreground $fg -background $bg + } else { + if {$count%2} { + $w configure -foreground $bg -background $fg + } else { + $w configure -foreground $fg -background $bg + } + after 200 [list focusAndFlash $w $fg $bg [expr {$count-1}]] + } +} + +proc see_red {w} { + focus $w + set orig [$w cget -bg] + $w configure -bg red + update idletasks + after 1000 + $w configure -bg $orig + update idletasks +} + +proc page_valid {w} { + # w entry widget + set p [$w get] + set isvalid 1 + #puts "check pageno $p" + if {! [string is integer $p] || $p<=0} { + set isvalid 0 + } elseif {$::gfile(npages) ne "" && [scan $p "%d"] > $::gfile(npages)} { + set isvalid 0 + } + return $isvalid +} + +# end conversion options + +pack [ttk::label .status -justify left -text "Idle"] \ + -in .bg -side bottom -anchor w -fill x -expand 1 + +# main buttons + +pack [ttk::frame .bottomf] -in .bg -side bottom -fill x +ppack [ttk::button .bottomf.view -text "View" -command view] -side left +ppack [ttk::button .bottomf.open -text "Open" -command openDialog] \ + -side left -padx 2 +ppack [ttk::button .bottomf.convert -text "Convert and save..." \ + -command saveDialog] -side left -padx 2 +ppack [ttk::button .bottomf.done -text "Done" -command exit] -side right + +proc view {} { + if {! [viewable]} { + tk_messageBox -icon warning -type ok \ + -message "No viewer for $::gfile(path)" + return + } + if {$::tcl_platform(os) eq "Darwin"} { + catch {exec open $::gfile(path) &} + } elseif {$::tcl_platform(platform) ne "unix"} { + # use shortname to sidestep quoting problems + catch {exec cmd /x /c start [file attributes $::gfile(path) -shortname]} + } else { + if {$::gfile(type) eq "pdf"} { + catch {exec $::settings(pdf_viewer) $::gfile(path) &} + } elseif {$::gfile(type) ne "other" && $::gfile(type) ne ""} { + catch {exec $::settings(ps_viewer) $::gfile(path) &} + } + } +} + +proc openDialog {} { + set types { + {"PostScript and pdf" {.eps .epi .epsi .ps .prn .pdf}} + {"Encapsulated PostScript" {.eps .epi .epsi}} + {"General PostScript" {.ps .prn}} + {"Pdf" {.pdf}} + {"All files" *} + } + if {[file isdirectory $::gfile(dir)]} { + set try [tk_getOpenFile -filetypes $types -initialdir $::gfile(dir)] + } else { + set try [tk_getOpenFile -filetypes $types] + } + if {$try != ""} { + set ::gfile(path) [file normalize $try] + set ::gfile(dir) [file dirname $::gfile(path)] + set ::gfile(name) [file tail $::gfile(path)] + set ::gfile(type) "" + if {! [catch {exec $::texlua $::epspdf_tlu --gui=gui -i $::gfile(path)} \ + result]} { + # parse output + regexp {has type (\w+)(?:.+ (\d+) pages)?\.} $result \ + mtc ::gfile(type) ::gfile(npages) + if {[regexp {^eps} $::gfile(type)]} {set ::gfile(npages) 1} + } + if {$::gfile(type) eq ""} { + # unsupported type + tk_messageBox -message "$try: unreadable or unsupported type" \ + -title "Error" -icon error + } + set ::settings(default_dir) $::gfile(dir) + putsettings + } + set_widget_states +} + +proc saveDialog {} { + if {$::options(format) eq "pdf"} { + set types {{"Pdf" {.pdf}}} + } elseif {$::options(format) eq "ps"} { + set types {{"PostScript" {.ps}}} + } elseif {$::options(format) eq "eps"} { + set types {{"Encapsulated PostScript" {.eps}}} + } + set try [tk_getSaveFile -filetypes $types -initialdir $::gfile(dir)] + if {$try != ""} { + # fix extension + set try [file normalize $try] + set idot [string last "." [file tail $try]] + if {$idot == -1} { + append try ".$::options(format)" + } else { + # don't bother checking whether the extension is already correct + set try [string range $try 0 [string last "." $try]] + append try $::options(format) + } + set try [file normalize $try] + # epspdf can read persistent options from configuration. + # only options from the options array need to be converted to parameters. + set args [list] + if {$::options(gray) eq "gray"} {lappend args "-g"} + if {$::options(bbox)} {lappend args "-b"} + if {! $::options(clean)} {lappend args "-d"} + if {$::options(pages) eq "single"} { + if {$::options(page) eq ""} {set ::options(page) 1} + lappend args "-p" $::options(page) + } + lappend args $::gfile(path) $try + .status configure -text "Working..." -justify "left" + foreach b {view open convert done} { + .bottomf.$b configure -state disabled + } + update idletasks; # force immediate redisplay main window + + set cmd {} ; # necessary? if- and else blocks do not seem to define scopes + if {$::tcl_platform(platform) == "windows"} { + set cmd [linsert $args 0 exec -ignorestderr $::epspdf_cmd --gui=gui] + } else { + set cmd [linsert $args 0 exec -ignorestderr \ + $::texlua $::epspdf_tlu --gui=gui] + } + set failed [catch $cmd result] + write_log $result + if $failed { + tk_messageBox -icon error -type ok -message "Error; see log window" + } else { + set ::gfile(path) [file normalize $try] + set ::gfile(dir) [file dirname $::gfile(path)] + set ::gfile(type) $::options(format) + set ::gfile(name) [file tail $::gfile(path)] + if {$::gfile(type) eq "pdf"} { + if {! [catch { + exec $::texlua $::epspdf_tlu --gui=gui -i $::gfile(path)} \ + result]} { + regexp {(\d+) pages\.$} $result mtc ::gfile(npages) + } + } elseif {$::gfile(type) eq "eps"} { + set ::gfile(npages) 1 + } + set ::settings(default_dir) $::gfile(dir) + putsettings + set ::options(page) 1 + } + .status configure -text "Idle" + foreach b {view open convert done} { + .bottomf.$b configure -state normal + } + focus .bottomf.view + set_widget_states + } +} + +proc set_widget_states {} { + # states (normal/disabled) for various widgets. + # their values should already be mutually consistent. + # widgets concerned: + # view / save + # output format / bbox / single page / page no. + + # view + if {[viewable]} { + .bottomf.view configure -state normal + } else { + .bottomf.view configure -state disabled + } + + # convert + .bottomf.convert configure -state normal + if {$::gfile(path) eq "" || ! [file exists $::gfile(path)]} { \ + .bottomf.convert configure -state disabled + } + if {$::gfile(type) eq "other"} { + .bottomf.convert configure -state disabled + } + if {$::gfile(npages) ne "" && $::options(pages) eq "single" && \ + $::options(page) > $::gfile(npages)} { + .bottomf.convert configure -state disabled + } + + # type + if {[regexp {ps|pdf} $::gfile(type)]} { + foreach f {pdf eps ps} {.optsf.format.$f configure -state normal} + } else { + foreach f {pdf eps ps} {.optsf.format.$f configure -state disabled} + } + + # pages and page + foreach f {all single} {.pagesf.$f configure -state normal} + if {$::options(format) eq "eps"} { + .pagesf.all configure -state disabled + set ::options(pages) "single" + } + if {$::options(format) eq "ps"} { + .pagesf.single configure -state disabled + set ::options(pages) "all" + } + if {$::options(pages) eq "all"} { + .pagesf.e configure -state disabled + } else { + .pagesf.e configure -state normal + } + + # boundingbox + if {$::options(format) eq "eps"} { + .bbox configure -state normal + } elseif {$::options(pages) eq "all"} { + .bbox configure -state disabled + set ::options(bbox) 0 + } else { + .bbox configure -state normal + } + update idletasks +} + +set_widget_states |