summaryrefslogtreecommitdiff
path: root/support/epspdf
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/epspdf
Initial commit
Diffstat (limited to 'support/epspdf')
-rw-r--r--support/epspdf/README17
-rw-r--r--support/epspdf/doc/COPYING339
-rw-r--r--support/epspdf/doc/Changelog189
-rw-r--r--support/epspdf/doc/epspdf.pdfbin0 -> 218565 bytes
-rw-r--r--support/epspdf/doc/epspdf.texi680
-rw-r--r--support/epspdf/doc/images/cnv_linux.pngbin0 -> 21912 bytes
-rw-r--r--support/epspdf/doc/images/config_lnx.pngbin0 -> 18740 bytes
-rw-r--r--support/epspdf/doc/images/epspdf.pngbin0 -> 4126 bytes
-rw-r--r--support/epspdf/doc/images/logo.pdfbin0 -> 5388 bytes
-rw-r--r--support/epspdf/doc/images/logo.svg222
-rw-r--r--support/epspdf/doc/images/main_wx.pngbin0 -> 12164 bytes
-rw-r--r--support/epspdf/doc/pstexi.tex49
-rw-r--r--support/epspdf/epspdf.help513
-rwxr-xr-xsupport/epspdf/epspdf.tlu2691
-rw-r--r--support/epspdf/epspdf4tk.cmd1
-rwxr-xr-xsupport/epspdf/epspdftk.tcl737
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
new file mode 100644
index 0000000000..92ff2e1311
--- /dev/null
+++ b/support/epspdf/doc/epspdf.pdf
Binary files differ
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
new file mode 100644
index 0000000000..d452692afc
--- /dev/null
+++ b/support/epspdf/doc/images/cnv_linux.png
Binary files differ
diff --git a/support/epspdf/doc/images/config_lnx.png b/support/epspdf/doc/images/config_lnx.png
new file mode 100644
index 0000000000..a80400c209
--- /dev/null
+++ b/support/epspdf/doc/images/config_lnx.png
Binary files differ
diff --git a/support/epspdf/doc/images/epspdf.png b/support/epspdf/doc/images/epspdf.png
new file mode 100644
index 0000000000..db0f09c6ac
--- /dev/null
+++ b/support/epspdf/doc/images/epspdf.png
Binary files differ
diff --git a/support/epspdf/doc/images/logo.pdf b/support/epspdf/doc/images/logo.pdf
new file mode 100644
index 0000000000..153b771fb2
--- /dev/null
+++ b/support/epspdf/doc/images/logo.pdf
Binary files differ
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
new file mode 100644
index 0000000000..068dd2d8fa
--- /dev/null
+++ b/support/epspdf/doc/images/main_wx.png
Binary files differ
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