From f43ed3f714713e40993b5acda3cab8b2ad3f4bb8 Mon Sep 17 00:00:00 2001 From: Siep Kroonenberg Date: Sun, 7 Dec 2014 18:30:33 +0000 Subject: epspdf version 0.6.1 git-svn-id: svn://tug.org/texlive/trunk@35762 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/scripts/epspdf/epspdf.help | 295 ++-- Master/texmf-dist/scripts/epspdf/epspdf.ico | Bin 9662 -> 0 bytes Master/texmf-dist/scripts/epspdf/epspdf.tlu | 1771 ++++++++++++++----------- Master/texmf-dist/scripts/epspdf/epspdftk.tcl | 79 +- 4 files changed, 1153 insertions(+), 992 deletions(-) delete mode 100644 Master/texmf-dist/scripts/epspdf/epspdf.ico (limited to 'Master/texmf-dist/scripts/epspdf') diff --git a/Master/texmf-dist/scripts/epspdf/epspdf.help b/Master/texmf-dist/scripts/epspdf/epspdf.help index 9b43bc63c1b..269e4148471 100644 --- a/Master/texmf-dist/scripts/epspdf/epspdf.help +++ b/Master/texmf-dist/scripts/epspdf/epspdf.help @@ -1,47 +1,63 @@ Epspdf and epsdftk +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 X + 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 +6 Change history + 6.1 Version 0.6 + 6.2 Version 0.5 + 6.3 Version 0.4 +Epspdf and epsdftk ****************** -This manual is for epspdf, version 0.6.0. +This manual is for epspdf, version 0.6.1. -Copyright (C) 2006, 2008, 2009, 2010, 2011, 2013 Siep Kroonenberg. +Copyright (C) 2006, 2008, 2009, 2010, 2011, 2013, 2014 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. -What has changed? -***************** - -Improvements: - * Grayscaling should now also work on bitmapped images. - - * Revised multistep conversions, with less risk of losing high-level - structure - - * Rewritten in TeXLua, which is a component of both TeX Live and of - MikTeX. Command-line use no longer requires an external scripting - language. - -I have been testing with Ghostscript version 9.05 and 9.06. - -For more details, see *note Version 0.6::. - 1 Usage ******* 1.1 Overview ============ -Epspdftk converts files between eps, pdf and general PostScript. The +Epspdftk converts files between eps, pdf and general PostScript. The main screen lets you open a file, select some options and convert the file. -When opening a file, epspdf tries to find out file type and, in the -case of a pdf file, the number of pages. This information is displayed -in the box in the upper part of the screen. - - +When opening a file, epspdf tries to find out file type and, in the case +of a pdf file, the number of pages. This information is displayed in +the box in the upper part of the screen. 1.2 Viewing =========== @@ -66,11 +82,11 @@ 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. +selecting all pages. Converting to eps implies selecting a single page. 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 +picked an existing page. If you don't like that, convert the entire document to pdf first - which will be done behind the scenes anyhow. Specifying options such as grayscaling or page selection may require a @@ -79,25 +95,25 @@ multistep conversion. 1.4 Converting ============== -The Convert and save... button calls up a file save dialog. After a +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. 1.5 The configuration screen ============================ - Epspdftk also has a configuration screen. The settings here are +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 +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 X, there is no such configuration option. Epspdf +For Windows and Mac OS X, there is no such configuration option. Epspdf will use the program associated with the file type, which can be configured outside epspdf. @@ -105,10 +121,12 @@ configured outside epspdf. ----------------------------------- Double-check the setting "Target use" under "Conversion to pdf". -"prepress" is for pdfs which are going to be printed commercially. The +"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. With luck, Ghostscript fonts and +Often, printshops insist on this. With luck, Ghostscript fonts and system fonts will be used for fonts which were not originally embedded. +Note that "printer" may not work with some versions of Ghostscript, due +to a bug in the handling of color. On the other hand, you may prefer "screen" if file size is a concern. See also the Ghostscript documentation, in particular Use.htm and @@ -119,7 +137,7 @@ 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 - although even -version 1.4 is far from bleeding-edge. Converting to a lower version +version 1.4 is far from bleeding-edge. Converting to a lower version may lead to rasterization of vector data and (much) larger file sizes. For prepress use, the printshop or publisher may require a low version number but otherwise leave this at "default". @@ -128,37 +146,40 @@ number but otherwise leave this at "default". ------------------------------------------ For conversion the other way you may opt not to use pdftops even if it -is available. In this case, the conversion will be done by Ghostscript. -Characters may be converted to drawn shapes or little bitmaps so this -is not recommended if the file uses fonts. +is available. In this case, the conversion will be done by Ghostscript. +With older Ghostscript versions, characters may be converted to drawn +shapes or little bitmaps, so use pdftops if available. With newer +Ghostscript versions, with an eps2write rather than an epswrite output +device, Ghostscript does a much better job. -Under Windows and MikTeX, this is also the place to point the program to -the location of pdftops.exe. +Under MikTeX, this is also the place to point the program to the +location of pdftops.exe. 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. +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 +on your searchpath. This is the case if you installed epspdf as a TeX Live package. -The first parameter of epspdftk is interpreted as startup directory for -the file browser. - -Basic usage: +Basic usage of epspdf itself: - epspdf [OPTIONS] INFILE OUTFILE + epspdf [OPTIONS] INFILE [OUTFILE] 2.1 Option summary ================== -Typing `epspdf --help' gives you the following summary: +Typing 'epspdf --help' gives you the following summary: $ epspdf --help - Epspdf version 0.6.0 - Copyright (c) 2006, 2008, 2009, 2010, 2011, 2013 Siep Kroonenberg + Epspdf version 0.6.1 + Copyright (c) 2006, 2008, 2009, 2010, 2011, 2013, 2014 Siep Kroonenberg Convert between [e]ps and pdf formats Usage: epspdf[.tlu] [options] infile [outfile] @@ -178,6 +199,7 @@ Typing `epspdf --help' gives you the following summary: -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 @@ -201,56 +223,56 @@ 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 +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. +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 normally 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 +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 +depending on the "Target use" option. According to the Ghostscript documentation, it embeds all fonts without exception for prepress- and -printer settings. Epspdf enforces this also for default settings. For +printer settings. Epspdf enforces this also for default settings. For screen- and ebook settings, 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 +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 currently 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. +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 from eps to pdf usually translates the bottom-left corner to -the (0,0) origin and sets the page to the size of the graphic. Anything +the (0,0) origin and sets the page to the size of the graphic. Anything outside the boundingbox should be cut off. I have seen Illustrator-generated pdf files with negative coordinates -for the lower-left corner. However, this does not seem to cause +for the lower-left corner. However, this does not seem to cause problems. 3.5 Orientation =============== There appears to be no reliable way to set orientation for PostScript- -and pdf files. This may result part of a graphic being cut off after +and pdf files. This may result part of a graphic being cut off after conversion or even everything falling outside the page / mediabox / boundingbox. @@ -258,38 +280,37 @@ boundingbox. ===================================================== Microsoft Office 2010 can now export to pdf, either the entire document -or a selection. This removes a major headache for Windows users. +or a selection. This removes a major headache for Windows users. -As a last resort, 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 Imagesetter. Pay attention to the +As a last resort, 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. +"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: +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' - + available from 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' - + '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 + 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. @@ -297,74 +318,98 @@ _The GIMP_ ============ Mac OS X'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 +and save them as pdf or PostScript. In fact, in many cases it is an excellent alternative to epspdf. 4.3 Windows =========== -Windows is not a particularly friendly environment for PostScript and -pdf. A couple of command-line utilities try to fill the gap, _e.g._ +Windows is not a particularly friendly environment for PostScript or +pdf. A couple of command-line utilities try to fill the gap, _e.g._ 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. +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. +Windows Explorer and "open" with _bitmap2eps_, which uses sam2p or bmeps +in the background. 5 Troubleshooting ***************** -5.1 Part of the graphic gets cut off +5.1 No options for output format visible +======================================== + +Widen the window, to make the output format radio buttons visible. + +5.2 Ghostscript stackunderflow error +==================================== + +Due to an error in Ghostscript color handling when converting to pdf, +selecting "printer" as target may lead to an error 'stackunderflow in +.setdistillerparams'. 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 +experiment with the PostScript Output option. Don't choose Optimize for Speed. -5.2 Fonts look ugly +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. Try to get hold of a more -recent Ghostscript and of either the xpdf suite or the Poppler -utilities, both of which include pdftops. +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 still handles more cases. It is part of the +xpdf suite and of the Poppler utilities. -5.3 The page has been converted to a bitmap +5.5 The page has been converted to a bitmap =========================================== This may happen if the page contains features such as transparency which are not supported by intermediate formats. Set pdf target use and target version both to "default" to avoid -unnecessary conversions: `-T default -N default' +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.4 Resources for troubleshooting +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 OS X 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:\Documents and Settings\YOUR USER NAME\Application Data' or -`c:\Users\YOUR USER NAME\AppData\Roaming'. Otherwise, open a command -prompt and type `echo %APPDATA%', which will produce this information. +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 OS X 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:\Documents and Settings\YOUR USER NAME\Application Data' or +'c:\Users\YOUR USER NAME\AppData\Roaming'. Otherwise, open a command +prompt and type 'echo %APPDATA%' to display this information. 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 +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 +-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 Ps2pdf.htm and Use.htm. For pdftops, type `pdftops --h'. For Unix, there is also a man page, and for Windows there is a +Ghostscript- and pdftops documentation. For Ghostscript, the most +important files are Ps2pdf.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. 6 Change history @@ -380,7 +425,7 @@ 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 +wrapper file (same method as Heiko Oberdiek's pdfcrop). Such a conversion preserves advanced features which might otherwise get lost during a PostScript round-trip. @@ -388,7 +433,7 @@ I no longer own a Mac, and therefore no longer try to provide an AppleScript wrapper. The current version has no provisions for custom Ghostscript- or pdftops -parameters. I may of may not re-add those in a future update. The +parameters. I may of may not re-add those in a future update. The corresponding command-line options are accepted but have no effect. 6.2 Version 0.5 @@ -399,25 +444,25 @@ 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'. +single executable containing 'epspdftk.tcl' and a Tcl/Tk runtime. See +. Epspdf now uses its own subdirectory for both the logfile and the -configuration file. For Linux/Unix/Mac OS X 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 +configuration file. For Linux/Unix/Mac OS X 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. +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 +The "Open with..." option for Windows has been dropped for technical reasons. The "-version" option now prints the version string instead of setting @@ -428,14 +473,14 @@ The "-info" option now also prints the number of pages for pdf files. 6.3 Version 0.4 =============== -Hi-res boundingboxes are now supported. By default, conversion from eps +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 +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 +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/Master/texmf-dist/scripts/epspdf/epspdf.ico b/Master/texmf-dist/scripts/epspdf/epspdf.ico deleted file mode 100644 index be136986560..00000000000 Binary files a/Master/texmf-dist/scripts/epspdf/epspdf.ico and /dev/null differ diff --git a/Master/texmf-dist/scripts/epspdf/epspdf.tlu b/Master/texmf-dist/scripts/epspdf/epspdf.tlu index ae793d7f344..463fb22f35a 100755 --- a/Master/texmf-dist/scripts/epspdf/epspdf.tlu +++ b/Master/texmf-dist/scripts/epspdf/epspdf.tlu @@ -4,66 +4,72 @@ kpse.set_program_name('texlua') -- epspdf conversion utility --- First texlua version +-- 0.6.0: first texlua version +-- 0.6.1: allow TeX installation on path with spaces -ep_version = '0.6.0' -ep_copyright = '2006, 2008, 2009, 2010, 2011, 2013' +ep_version = '0.6.1' +ep_copyright = '2006, 2008, 2009, 2010, 2011, 2013, 2014' --[[ +Note. TeX code for cropping pdfs adapted from Heiko Oberdiek's pdfcrop utility Program structure -SETUP -- some globals -- utilities -- system info -- some infrastructure - logging, temp files -- initializing (persistent) settings and associated utilities -- initializing (transient) options -MAIN FUNCTIONS/METHODS -- boundingboxes and their methods -- PsPdf objects: - - globals - - identify function + +- early initialization +- functions for: + - error handling + - file- and path utilities + - other general utilities + - infrastructure: logging and temporary files + - reading and writing settings + - gui function + - boundingboxes + - manipulating [e]ps- and pdf files +- the PsPdf object: + - creator functions + - boundingbox handling - one-step conversion methods - any_to_any method -INITIALIZATION -- parsing and interpreting rc file -- parsing and interpreting command-line -- non-conversion runs -- start of logging and creation of temp directory -CONVERSION -- call any_to_any - -TODO +- 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 -- use epdf library only optionally - custom options for gs and pdftops -Use absolute, normalized names for gs_prog and pdftops_prog but use -input- and output files as-is. - -MAYBE NOT NEEDED - -We can probably dispense with [hr]bb:wrapper() --]] -- some general utilities and globals --------------------------- --[[ -I think we get by just fine with simple-minded error handling. At -most, we just call a function which tries to first write the error -message to log before re-raising the error. +Simple-minded error handling. At most, we call a function which +tries to write the error message to log before re-raising the error. -The gui can capture error messages if necessary. +When run from the Tcl/Tk gui, this gui will capture error messages. --]] -eol = nil -path_sep = nil +-- early initializations + +eol = false +path_sep = false if os.type=='unix' then eol='\n' path_sep = ':' @@ -72,11 +78,65 @@ else path_sep = ';' end --- whether epspdf is run from the epspsdtk gui +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 +is_miktex = false +is_tl_w32 = false + +-- 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 -from_gui = false +-- logging ------------------------ + +-- we open and close the logfile anew for each write. +-- failure to open constitutes no 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 +-- error- and debug ------------------------- function errror(mess) if logfile then pcall(write_log, mess) end @@ -84,10 +144,14 @@ function errror(mess) error(mess, 2) end +function warn(mess) + if logfile then write_log(mess) end + print(mess) +end + function dbg(mess) if options.debug then - if logfile then write_log(mess) end - print(mess) + warn(mess) end end @@ -105,6 +169,8 @@ end --]] +-- file- and path utilities ---------------- + function ep_shortname(path) if os.type=='unix' then return path @@ -116,6 +182,29 @@ function ep_shortname(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, '\\', '/') @@ -124,21 +213,18 @@ function fw(path) end end -cwd = fw(lfs.currentdir()) -source_dir = false -- directory of input file; to be determined -dest_dir = false -- directory of output file; to be determined - -function absolute_path(path, reldir) +function absolute_path(path) --[[ Return absolute normalized version of path, interpreted from the directory from where the program was called. - If reldir, then interpret path from reldir instead. 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. @@ -146,14 +232,10 @@ function absolute_path(path, reldir) path = fw(path) - local present_dir = lfs.currentdir() + local present_dir = fw(lfs.currentdir()) lfs.chdir(cwd) - if reldir then - if not lfs.chdir(reldir) then return nil end - end - local parentdir local filename @@ -201,7 +283,7 @@ 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 full path, although we only need a yes or no answer +-- we return the original string, although we only need a yes or no answer function find_on_path (prog) if os.type ~= 'unix' then @@ -209,15 +291,14 @@ function find_on_path (prog) end for d in string.gmatch(os.getenv('PATH'), '[^:]+') do if lfs.isfile(d..'/'..prog) then - return absolute_path(d..'/'..prog) + return prog end end return false end -- find_on_path --- OTOH, on windows we do not rely so much on the searchpath --- so we just test whether the file exists and is an exe file. --- only used for pdftops. +-- On Windows, we do not count so much on the existing searchpath +-- so is_prog tests whether the file exists and is an exe file. function is_prog (path) -- 1. test for and if necessary add extension @@ -240,6 +321,62 @@ function is_prog (path) end end -- is_prog +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 + -- if d then dbg('system tempdir: '..d) end + -- cygwin: $TEMP=/tmp, root '/' being root of cygwin installation + if d and not dir_writable(d) then + dbg('unfortunately, '..d..' not writable') + 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 @@ -290,14 +427,12 @@ end -- tab_combine -- An initial chunk of size bufsize should be plenty to include -- any interesting header information. -bufsize=16000 - 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 sz = lfs.attributes(source).size + local sz = lfs.attributes(source, 'size') if not offset then offset = 0 elseif offset>sz then @@ -332,134 +467,29 @@ function slice_file(source, dest, len, offset, mode) d:close() end -- slice_file --- system info -------------------------------------------- - --- safe mode? TODO -options = {safer = string.match(arg[0], 'repspdf')} - --- Windows: miktex, TL or neither --- no support yet for separate ghostscript -is_miktex = false -is_tl_w = 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_w = 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 = fw(os.selfdir)..'/mgs.exe' - gs_prog = 'mgs.exe' - rt = string.gsub(os.selfdir,'[\\/][^\\/]+[\\/][^\\/]+$', '') - if not lfs.isdir(rt..'/miktex') then - -- 64-bits: binaries one level deeper - rt = string.gsub(rt, '[\\/][^\\/]+$', '') - end - if rt=='' then errror('Unexpected MiKTeX directory layout') end - if not lfs.isdir(rt..'/miktex') then - errror('Unexpected MiKTeX directory layout') - end - os.setenv('MIKTEX_GS_LIB', rt..'/ghostscript/base;'..rt..'/fonts') - elseif is_tl_w then - -- windows/TeX Live - -- grandparent of texlua.exe directory .. ... - rt = string.gsub(os.selfdir,'[\\/][^\\/]+[\\/][^\\/]+$', '') - ..'/tlpkg/tlgs' - os.setenv('GS_LIB', rt..'/lib;'..rt..'/fonts') - os.setenv('Path', rt..'/bin'..';'..os.getenv('Path')) - gs_prog = 'gswin32c.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(ep_shortname(os.getenv('APPDATA'))) .. '/epspdf' -else - epsdir = os.getenv('HOME')..'/.epspdf' -end --- dbg('epsdir: '..epsdir) -rcfile = epsdir .. '/config' - --- 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 - --- log and log rotation - -logfile = epsdir .. '/epspdf.log' -log_bsl = string.gsub(logfile, '/', '\\') -oldlog = epsdir .. '/epspdf.log.old' - --- tag log entries with one random integer per epspdf run, --- in the absence of a lua process id built-in function - -logtag = math.random(0,999999) -- range is inclusive -logtag = string.format('%06d', logtag) - --- we open and close the logfile anew for each write. --- failure to open constitutes no error. -function write_log(s) - local f = io.open(logfile, 'a') - if f then - f:write(string.format('%s %s%s', - os.date('%Y/%m/%d %H:%M:%S', os.time()), s, eol)) - f:close() +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 - if from_gui then - print(s) -- intercepted by the gui + 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 -function log_cmd(cmd) - write_log('[' .. table.concat(cmd, '] [') .. ']') -end - -- temporary files ---------------------------------------- -tempdir = false -- will be created later -tempfiles = {} +-- tempdir = false -- will be created later and chdir-ed into +-- tempfiles initialized early to empty table -- We just name our temporary files nn. 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. --- Note: epspdf does all the real work from this temp directory. +-- Epspdf does all the real work from the temp directory. function mktemp(ext) local froot, fname, f, g @@ -502,8 +532,20 @@ function mktemp(ext) errror('Cannot create temporary file in '..tempdir) end +function waitasec() + -- stupid windows file locking + if os.type=='windows' and (tonumber(os.uname().version) or 0)>=6 then + os.execute('timeout /t 1 /nobreak >nul') + elseif os.type=='windows' then + os.execute('ping -n 1 localhost >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) @@ -518,6 +560,7 @@ function cleantemp() break end end + if os.type=='windows' then waitasec() end lfs.chdir('..') if empty then local res, mess @@ -528,94 +571,33 @@ function cleantemp() end end ---[[ - -settings - -Now: -1. initial values -Later: -2. try to read config file -3. command-line option parsing, including settings that are not stored - -The values in the settings array have lowest priority - lower than -autodetect and command-line options. We go for false rather than -undefined, because this results in an actual settings entry. -We ignore illegal settings in the config file. - ---]] - -pdf_targets = {'screen', 'ebook', 'printer', 'prepress', 'default'} -pdf_versions = {'1.2', '1.3', '1.4', 'default'} - -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 ') - ---[[ --- is bb_spread still a useful setting? --- look at gs options wrt boundingbox --- settings.bb_spread = 1 --- descriptions.bb_spread = 'Safety margin in points for (low-res) boundingbox' - -settings.use_hires_bb = false --- descriptions.use_hires_bb = 'Use high-resolution boundingbox if available' --- Ignored; hires bb always used ---]] +-- epsdevice ----------------------- --- because pdftops_prog is sometimes configurable, it is stored in settings. --- it will not be used for TeX Live and only be read and written on Windows. -settings.pdftops_prog = false ---[[ -if os.type == 'unix' then - settings.pdftops_prog = find_on_path('pdftops') -elseif os.type == 'windows' and not is_miktex then - settings.pdftops_prog = os.selfdir..'/pdftops.exe' +function 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 ---]] -descriptions.pdftops_prog = 'Full path to pdftops.exe (not used with TeX Live)' - -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 won't be used at all under osx 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 OS X' - --- default_dir, which is used on all platforms, is only for the gui. -if os.type == 'windows' then - settings.default_dir = - string.gsub(ep_shortname(os.getenv('USERPROFILE')), '\\', '/') -else - settings.default_dir = os.getenv('HOME') -end -descriptions.default_dir = - 'Epspdftk: initial directory; ignored by epspdf itself' +-- settings ----------------------- function write_settings (file) local f if file then - f = io.open(rcfile, 'wb') + f = io.open(file, 'wb') if not f then return end else -- stdout to be captured by epspdftk f = io.output() - if os.type=='windows' and not is_tl_w then + if os.type=='windows' and not is_tl_w32 then f:write('tl_w = no', eol) end end @@ -700,8 +682,6 @@ function read_settings(file) elseif k == 'pdftops_prog' then if is_miktex then settings.pdftops_prog = is_prog(v) - -- elseif os.type=='windows' then - -- settings.pdftops_prog = v end -- else ignore elseif k == 'ignore_pdftops' then vl = string.lower(string.sub(v,1,1)) @@ -732,63 +712,6 @@ function read_settings(file) end -- for end -- read settings --- command-line parameters: variables and functions ------------- - -function help (mess) - -- need to enforce an ordering, otherwise we could have used pairs(opts) - if mess then print(mess..eol) end - show_version() - print([[ - -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 -]]) - -- omitted below: no-op options - for _, k in ipairs({'page', 'gray', 'bbox', 'pdf_target', 'pdf_version', - 'pdftops_prog', 'use_pdftops', 'save', 'debug', 'version', 'help' }) do - help_opt(k) - end - if mess then os.exit(1) else os.exit() end -end - -function help_opt (o) - -- one line where possible - local indent_n = 12 - local intent_sp = string.rep(' ', indent_n) - local indent_fmt = '%-' .. tostring(indent_n) .. 's' - v = opts[o] - if v=='pdftops_prog' and (os.type=='unix' or is_tl_w) then - return - end - 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) pdf with bbox via pdftex, as in pdfcrop utility - --]===] --- TODO: multiple pages +-- TODO: multiple pages? -- (means additional parameter checking) -- Converting from pdf to pdf using luatex; no grayscaling @@ -1573,6 +1449,7 @@ function PsPdf:pdf_crop() -- options to be fulfilled: page, boundingbox -- only called directly. -- embeds the pdf with crop parameters into a new (lua)tex document + -- dbg('PsPdf:pdf_crop '..self.path) if not (options.bbox or options.page) then return self end @@ -1584,15 +1461,20 @@ function PsPdf:pdf_crop() if options.bbox then bb, hrbb = self:bb_from_gs(pg) else + -- dbg('about to use epdf') -- use [Trim|Crop|Media]Box instead + -- if os.type=='windows' then waitasec() end local dummy = epdf.open(self.path) + -- if os.type=='windows' then waitasec() end if not dummy then errror('Epdf: cannot open '..self.path) end + -- dbg('about to get catalog') dummy = dummy:getCatalog() if not dummy then errror('Cannot open catalog of '..self.path) end + -- dbg('got catalog') dummy = dummy:getPage(pg) if not dummy then errror('Epdf: cannot open page object '..tostring(pg)..' of '..self.path) @@ -1609,11 +1491,8 @@ function PsPdf:pdf_crop() hrbb = HRBb:from_rect(hrbb) end - -- location of luatex - local luatex_prog = fw(os.selfdir) .. '/luatex' -- absolute path - if os.type == 'windows' then - luatex_prog = luatex_prog .. '.exe' - end + -- luatex on searchpath + local luatex_prog = 'luatex' -- write TeX file which includes cropped pdf page -- adapted from Heiko Oberdiek's pdfcrop utility. @@ -1749,18 +1628,27 @@ function PsPdf:pdf_crop() \csname @@end\endcsname \end ]], - tex_miver, self.maver, options.page or 1, - hrbb.x1, hrbb.y1, hrbb.x2, hrbb.y2) + tex_miver, self.maver, options.page or 1, + hrbb.x1, hrbb.y1, hrbb.x2, hrbb.y2) local textemp = mktemp('tex') -- this also takes 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(dummy, '')) f:close() local cmd, res, psp - cmd = {luatex_prog, '--safer', '--no-shell-escape', textemp} - log_cmd(cmd) - res = os.spawn(cmd) + 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) return psp @@ -1773,7 +1661,7 @@ function PsPdf:eps_to_pdf() -- option to be fulfilled: gray -- set target and pdf version if applicable - -- dbg('PsPdf:eps_to_pdf') + -- dbg('PsPdf:eps_to_pdf '..self.path) if self.type~='eps' then errror('PsPdf:eps_to_pdf called for non-eps file '.. self.path) end @@ -1781,7 +1669,7 @@ function PsPdf:eps_to_pdf() if options.bbox and self.bb:nonnegative() then self = self:eps_crop() -- this sets options.bbox to false end - cmd = tab_combine({gs_options, pdf_options}) + cmd = tab_combine({{gs_prog}, gs_options, pdf_options}) -- dbg(table.concat(cmd,' ')) if options.gray then cmd = tab_combine({cmd, gray_options}) @@ -1793,7 +1681,7 @@ function PsPdf:eps_to_pdf() local psp = PsPdf:new('pdf') table.insert(cmd, '-sOutputFile#'..psp.path) -- dbg(table.concat(cmd,' ')) - cmd = tab_combine({cmd, pdf_tailoptions, {self.path}}) + cmd = tab_combine({cmd, pdf_tail_options, {self.path}}) -- dbg(table.concat(cmd,' ')) log_cmd(cmd) local res = os.spawn(cmd) @@ -1812,7 +1700,7 @@ function PsPdf:pdf_to_pdf() -- option to be fulfilled: gray and optionally page. -- do not call this just for page selection because -- pdf_crop can do this in a less invasive manner - -- dbg('PsPdf:pdf_to_pdf') + -- dbg('PsPdf:pdf_to_pdf '..self.path) if self.type~='pdf' then errror('PsPdf:pdf_to_pdf called for non-pdf file '.. self.path) end @@ -1820,7 +1708,7 @@ function PsPdf:pdf_to_pdf() 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_options, pdf_options}) + cmd = tab_combine({{gs_prog}, gs_options, pdf_options}) -- dbg(table.concat(cmd,' ')) if options.gray then cmd = tab_combine({cmd, gray_options}) @@ -1835,7 +1723,7 @@ function PsPdf:pdf_to_pdf() end local psp = PsPdf:new('pdf') table.insert(cmd, '-sOutputFile#'..psp.path) - cmd = tab_combine({cmd, pdf_tailoptions}) + cmd = tab_combine({cmd, pdf_tail_options}) -- dbg(table.concat(cmd,' ')) table.insert(cmd, self.path) -- dbg(table.concat(cmd,' ')) @@ -1853,17 +1741,22 @@ function PsPdf:pdf_to_eps() -- options to be fulfilled: bbox and page -- dbg(tostring(settings.pdftops_prog)) + -- dbg('pdf_to_eps '..self.path) local psp = PsPdf:new('eps') local cmd, res local page = false - if self.pages>1 then - page = 1 - if options.page then page = options.page end - if options.page and options.page > self.pages then - errror('PsPdf:pdf_to_eps called with non-existant page '.. options.page) + if self.pages > 1 then + if options.page then + page = options.page + if page > self.pages then + errror('PsPdf:pdf_to_eps called with non-existant page '.. + tostring(page)) + end + else + page = 1 end page = tostring(page) - end + end -- self.pages > 1 if pdftops then if page then cmd = tab_combine({{pdftops}, ps_options, @@ -1878,7 +1771,10 @@ function PsPdf:pdf_to_eps() 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 - res = os.spawn({'cmd', '/c', table.concat(cmd, ' ')..' 2>>'..log_bsl}) + -- 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 @@ -1932,18 +1828,23 @@ function PsPdf:pdf_to_eps() if not fout then errror('Cannot write new file '.. newfile) end fout:write(table.concat(pre_lines)) fout:close() + -- dbg('fixing '..psp.path..' to '..newfile) slice_file(psp.path, newfile, lfs.attributes(psp.path,'size') - offset, offset, 'ab') psp.path = newfile end -- needs_fixing else -- use ghostscript - cmd = tab_combine({gs_options, - {'-sDEVICE#epswrite', '-dLanguageLevel#3'}}) + local epsdev = 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'}}) -- the restrictions on eps files are apparently -- incompatible with grayscaling if options.page then - table.insert(cmd, '-dFirstPage='..page) - table.insert(cmd, '-dLastPage='..page) + table.insert(cmd, '-dFirstPage='..options.page) + table.insert(cmd, '-dLastPage='..options.page) end table.insert(cmd, '-sOutputFile='..psp.path) table.insert(cmd, self.path) @@ -1965,19 +1866,19 @@ end -- pdf_to_eps function PsPdf:ps_to_pdf() -- options to be fulfilled: gray - -- dbg('PsPdf:ps_to_pdf') + -- dbg('PsPdf:ps_to_pdf '..self.path) if self.type~='ps' then errror('PsPdf:ps_to_pdf called for non-ps file '.. self.path) end local cmd - cmd = tab_combine({gs_options, pdf_options}) + cmd = tab_combine({{gs_prog}, gs_options, pdf_options}) if options.gray then cmd = tab_combine({cmd, gray_options}) options.gray = false end local psp = PsPdf:new('pdf') table.insert(cmd, '-sOutputFile#'..psp.path) - cmd = tab_combine({cmd, pdf_tailoptions}) + cmd = tab_combine({cmd, pdf_tail_options}) table.insert(cmd, self.path) log_cmd(cmd) local res = os.spawn(cmd) @@ -1993,7 +1894,7 @@ end -- PsPdf:ps_to_pdf function PsPdf:pdf_to_ps() -- options to be fulfilled: page and, if not using pdftops, also gray - -- dbg('PsPdf:pdf_to_ps') + -- dbg('PsPdf:pdf_to_ps '..self.path) local psp = PsPdf:new('ps') local page = false if self.pages>1 then @@ -2013,10 +1914,9 @@ function PsPdf:pdf_to_ps() cmd = tab_combine({cmd, {'-f', page, '-l', page}}) end cmd = tab_combine({cmd, {'-paper', 'match', self.path, psp.path}}) - -- cmd[0] = pdftops else -- use ghostscript - cmd = tab_combine({gs_options, - {'-sDEVICE#ps2write', '-dLanguageLevel#3'}}) + cmd = tab_combine({{gs_prog}, gs_options, + {'-sDEVICE#ps2write', '-dHaveTrueTypes=true', '-dLanguageLevel#3'}}) if options.gray then cmd = tab_combine({cmd, gray_options}) -- dbg(table.concat(cmd,' ')) @@ -2031,14 +1931,16 @@ function PsPdf:pdf_to_ps() end options.page = false log_cmd(cmd) - -- if os.type=='windows' and pdftops and not is_miktex then - -- 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 - -- res = os.spawn({'cmd', '/c', table.concat(cmd, ' ')..' 2>>'..log_bsl}) - -- else + 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 + end if res and res==0 and lfs.attributes(psp.path, 'size')>0 then return psp else @@ -2050,7 +1952,7 @@ function PsPdf:any_to_any() -- weed out nonsense options - -- dbg('PsPdf:any_to_any') + -- dbg('PsPdf:any_to_any '..self.path) if options.type=='ps' then options.bbox = false -- dbg('Ignoring bbox option for ps output') @@ -2067,43 +1969,48 @@ function PsPdf:any_to_any() -- -- if options[o] then dbg('Do option '..o) end -- end - -- check source and destination filetypes - - if not self.type then - errror('any_to_any: cannot convert; unsupported source filetype') - end - if not options.type or options.type=='epsPreview' then - errror('any_to_any: cannot convert; unsupported destination filetype') - end - -- `distiller' settings depend on whether final output is pdf + -- '.setpdfwrite' is just some optimization option for ghostscript if options.type=='pdf' then - table.insert(pdf_options, '-dPDFSETTINGS#/'..settings.pdf_target) if settings.pdf_version~='default' then table.insert(pdf_options, '-dCompatibilityLevel#'..settings.pdf_version) end - -- below, try <> + -- below, consider adding <> if settings.pdf_target=='screen' or settings.pdf_target=='ebook' then - pdf_tailoptions = {'-c', - '.setpdfwrite', '-f'} + pdf_tailoptions = {'-c', '.setpdfwrite', '-f'} -- -f ensures that the input filename is not added to the -c string else - pdf_tailoptions = {'-c', - '.setpdfwrite <> setdistillerparams', '-f'} + pdf_tailoptions = { + '-c', '.setpdfwrite <> setdistillerparams', '-f'} + end + else + pdf_tailoptions = { + '-c', '.setpdfwrite <> setdistillerparams', '-f'} + end + + if options.type=='pdf' then + table.insert(pdf_options, '-dPDFSETTINGS#/'..settings.pdf_target) + if settings.pdf_version~='default' then + table.insert(pdf_options, '-dCompatibilityLevel#'..settings.pdf_version) end else table.insert(pdf_options, '-dPDFSETTINGS#/default') - pdf_tailoptions = {'-c', - '.setpdfwrite <> setdistillerparams', '-f'} end - -- each single-step conversion takes care of options it can handle - -- and sets those options to false. - -- for boundingboxes, eps_crop is either called explicitly - -- or called implicitly by another converter. - -- pdf_crop is always called explicitly and always as the last step + --[[ + each single-step conversion takes care of options it can handle + and sets those options to false. + for boundingboxes, eps_crop is either called explicitly + or called implicitly by another converter. + pdf_crop is always called explicitly and always as the last step + + all calls to 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- and output filenames. + --]] local psp = self + local newfile if psp.type=='eps' or psp.type=='epsPreview' then -- As a side effect of eps_clean, the modified source file is copied @@ -2129,9 +2036,11 @@ function PsPdf:any_to_any() elseif psp.type=='ps' then -- preliminary: - -- copy infile to a file in the temp directory, for gs -dSAFER - psp.path = mktemp(psp.type) - slice_file(infile, psp.path) + -- copy infile to a file in the temp directory, needed for gs -dSAFER + newfile = mktemp(psp.type) + slice_file(psp.path, newfile) + -- dbg(psp.path..' copied to '..newfile..' in '..lfs.currentdir()) + psp.path = newfile -- actual conversion if options.type=='eps' then @@ -2153,8 +2062,10 @@ function PsPdf:any_to_any() elseif psp.type=='pdf' then -- preliminary: -- copy infile to a file in the temp directory, for gs -dSAFER - psp.path = mktemp(psp.type) - slice_file(infile, psp.path) + newfile = mktemp(psp.type) + slice_file(psp.path, newfile) + -- dbg(psp.path..' copied to '..newfile..' in '..lfs.currentdir()) + psp.path = newfile -- actual conversion if options.type=='eps' then @@ -2206,433 +2117,695 @@ function PsPdf:any_to_any() end -- psp.type=='ps'|'pdf' 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 +-- 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. +-- TL/w32: add to searchpath +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(ep_shortname(os.getenv('APPDATA'))) .. '/epspdf' +else + epsdir = os.getenv('HOME')..'/.epspdf' +end +-- dbg('epsdir: '..epsdir) +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 - local f = io.open(logfile, 'ab') - f:write(eol) - f:close() + print_log('\n\nNew run') end end -- do elseif end -- if lfs...logfile write_log('epspdf '..table.concat(arg, ' ')) -infile = false -outfile = false +--[[ some debug output --- some debug output +dbg ('os is ' .. os.type .. ' and ' .. os.name) +dbg ('texlua in ' .. os.selfdir) +dbg('Ghostscript: ' .. gs_prog) +--]] + +--[[ + +settings, initial values + +The values in the settings array have lowest priority - lower than +autodetect and command-line options. We go for false rather than +undefined, because this results in an actual settings entry. +We ignore illegal settings in the config file. --- dbg ('os is ' .. os.type .. ' and ' .. os.name) --- dbg ('texlua in ' .. os.selfdir) --- dbg('Ghostscript: ' .. gs_prog) +--]] --- dbg('\nSettings are:\n') --- for k,v in pairs(settings) do dbg(k .. ' = ' .. tostring(v)) end +pdf_targets = {'screen', 'ebook', 'printer', 'prepress', 'default'} +pdf_versions = {'1.2', '1.3', '1.4', 'default'} --- Handle command-line +settings = {} +descriptions = {} -do +settings.pdf_target = 'default' +descriptions.pdf_target = 'One of ' .. join(pdf_targets, ', ', ' or ') - read_settings(rcfile) - - -- dbg('Defining cmdline options') - 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 - } - --]] +settings.pdf_version = 'default' +descriptions.pdf_version = 'One of ' .. join(pdf_versions, ', ', ' or ') + +--[[ +-- is bb_spread still a useful setting? +-- look at gs options wrt boundingbox +-- settings.bb_spread = 1 +-- descriptions.bb_spread = 'Safety margin in points for (low-res) boundingbox' + +settings.use_hires_bb = false +-- descriptions.use_hires_bb = 'Use high-resolution boundingbox if available' +-- Ignored; hires bb always used +--]] + +-- because pdftops_prog is sometimes configurable, it is stored in settings. +-- it will not be used for TeX Live and only be read and written on Windows. + +settings.pdftops_prog = false +descriptions.pdftops_prog = 'Full path to pdftops.exe (not used with TeX Live)' + +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 won't be used at all under osx 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 OS X' + +-- default_dir, which is used on all platforms, is only for the gui. + +if os.type == 'windows' then + settings.default_dir = + string.gsub(ep_shortname(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. - 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 - } - - if os.type=='windows' and not is_tl_w then - opts.pdftops_prog = { +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. + +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'} +-- may add custom options later + +-- `main program' inside scope-creating block ---------------------- + +do -- main program + + local infile = false + local in_dir = false -- directory of infile + local outfile = false + local out_dir = false -- directory of outfile + + -- dbg('\nSettings are:\n') + for k,v in pairs(settings) do dbg(k .. ' = ' .. tostring(v)) end + + do -- Handle settings and command-line inside nested scope -------------- + + read_settings(rcfile) + + -- dbg('Defining cmdline options') + local opts = {} + + opts.page = { type = 'string', val = nil, - forms = {'--pdftops'}, - placeholder = 'PATH', + forms = {'-p', '--page', '--pagenumber'}, + placeholder = 'PNUM', negforms = nil, - help = descriptions.pdftops_prog + help = 'Page number; must be a positive integer' } - end - - 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 - } - - 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 - -- dbg('parse argument '..tostring(i)..': '..arg[i]) - 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 - -- dbg(' try '..p) - 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 - -- some debug output + opts.gray = { + type = 'boolean', val = nil, + forms = {'-g', '--grey', '--gray', '-G', '--GREY', '--GRAY'}, + negforms = nil, + help = 'Convert to grayscale' + } - --[[ - if i<=#arg then - dbg('non-option arguments:') - for j=i,#arg do dbg(arg[j]) end - dbg(eol) - else - dbg('no non-option arguments') - end + opts.bbox = { + type = 'boolean', val = nil, + forms = {'-b', '--bbox', '--BoundingBox'}, + negforms = nil, + help = 'Compute tight boundingbox' + } - for i=1,#arg do dbg(arg[i]) end + ---[[ ignored; included for backward compatibility + opts.use_hires_bb = { + type = 'boolean', val = nil, + forms = {'-r', '--hires'}, + negforms = {'-n', '--no-hires'}, + } - dbg(eol..'Options from command-line:') - for p, o in pairs(opts) do - if o.val==nil then - dbg(p..': undefined') - else - dbg(p..': '..tostring(o.val)) - end - end + opts.custom = { + type = 'string', val = nil, + forms = {'-C', '--custom', '-P', '--psoptions'}, + negforms = nil + } --]] - -- check and interpret opts. - -- Copy to either settings or to options table. - -- abort (via help function) at syntax error. + 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 + } - -- page + if os.type=='windows' and not is_tl_w32 then + opts.pdftops_prog = { + type = 'string', val = nil, + forms = {'--pdftops'}, + placeholder = 'PATH', + negforms = nil, + help = descriptions.pdftops_prog + } + end + + opts.use_pdftops = { + type = 'boolean', val = nil, + forms = {'-U'}, + negforms = {'-I'}, + help = descriptions.use_pdftops + } - 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 + opts.info = { + type = 'boolean', val = nil, + forms = {'-i', '--info'}, + negforms = nil, + help = 'Info: display detected filetype and exit' + } - -- grayscaling + opts.help = { + type = 'boolean', val = nil, + forms = {'-h', '--help'}, + negforms = nil, + help = 'Display this help message and exit' + } - if opts.gray.val then - options.gray = true - else - options.gray = false - end + opts.version = { + type = 'boolean', val = nil, + forms = {'-v', '--version'}, + negforms = nil, + help = 'Display version info and exit' + } - -- boundingbox + opts.save = { + type = 'boolean', val = nil, + forms = {'-s', '--save'}, + negforms = nil, + help = 'Save some settings to configuration file' + } - if opts.bbox.val then - options.bbox = true - else - options.bbox = false - end + opts.debug = { + type = 'boolean', val = nil, + forms = {'-d'}, + negforms = nil, + help = 'Debug: do not remove temp files' + } - --[[ - -- using hires boundingbox + opts.gui = { + type = 'string', val = nil, + forms = {'--gui'}, + negforms = nil, + help = nil -- reserved for use by epspdftk + } - if opts.use_hires_bb.val~=nil then - settings.use_hires_bb = opts.use_hires_bb.val - end - --]] + -- a couple of functions only available during command-line parsing + + local function show_version () + print('Epspdf version '..ep_version..'\nCopyright (c) ' + ..ep_copyright..' 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', + 'pdftops_prog', 'use_pdftops', 'save', 'info', 'debug', + 'version', 'help'}) do + local v = opts[o] + if v~='pdftops_prog' or is_miktex then + 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)#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 - -- using pdftops + -- some debug output - if opts.use_pdftops.val~=nil then - settings.use_pdftops = opts.use_pdftops.val - end + --[[ + if i<=#arg then + dbg('non-option arguments:') + for j=i,#arg do dbg(arg[j]) end + dbg(eol) + else + dbg('no non-option arguments') + end - -- pdf target use + for i=1,#arg do dbg(arg[i]) end - if opts.pdf_target.val~=nil then - if in_list(opts.pdf_target.val, pdf_targets) then - settings.pdf_target = opts.pdf_target.val + dbg(eol..'Options from command-line:') + for p, o in pairs(opts) do + if o.val==nil then + dbg(p..': undefined') + else + dbg(p..': '..tostring(o.val)) + end + end + --]] + + -- 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 - help('Illegal value '..opts.pdf_target.val..' for pdf_target') + options.gray = false end - end - -- pdf version + -- boundingbox - if opts.pdf_version.val~=nil then - if in_list(opts.pdf_version.val, pdf_versions) then - settings.pdf_version = opts.pdf_version.val + if opts.bbox.val then + options.bbox = true else - help('Illegal value '..opts.pdf_version.val..' for pdf_version') + options.bbox = false end - end - -- pdftops program + --[[ + -- using hires boundingbox - -- pdftops has already been been initialized to false - if os.type=='windows' and not is_tl_w and opts.pdftops_prog.val then - settings.pdftops_prog = is_prog(opts.pdftops_prog.val) - if settings.use_pdftops then - pdftops = settings.pdftops_prog + if opts.use_hires_bb.val~=nil then + settings.use_hires_bb = opts.use_hires_bb.val end - elseif os.type=='windows' and not is_tl_w then - if settings.use_pdftops then - pdftops = is_prog(settings.pdftops_prog) + --]] + + -- using pdftops + + if opts.use_pdftops.val~=nil then + settings.use_pdftops = opts.use_pdftops.val end - elseif os.type=='windows' then - if settings.use_pdftops then - pdftops = os.selfdir..'/pdftops.exe' + + -- 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 - else - if settings.use_pdftops then - pdftops = find_on_path('pdftops') + + -- 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 - end - -- dbg('Option handling; pdftops is '..tostring(pdftops)) - -- other options + -- pdftops program; pdftops has already been been initialized to false - if opts.save.val then - write_settings(rcfile) - end + -- pdftops_prog as command-line option + if os.type=='windows' and not is_tl_w32 and opts.pdftops_prog.val then + settings.pdftops_prog = is_prog(opts.pdftops_prog.val) + end - if opts.debug.val then - options.debug = true - end + -- pdftops should be on the path. + -- for miktex, make it so if possible. + if os.type=='windows' and not is_tl_w32 then + if settings.use_pdftops then + pdftops = is_prog(settings.pdftops_prog) + if pdftops then + -- strip path and modify searchpath, to avoid paths with spaces + maybe_add_path(string.gsub(pdftops, '[\\/][^\\/]*$', ''), 'append') + pdftops = string.gsub(settings.pdftops_prog, '^.*[\\/]', '') + end + end + elseif os.type=='windows' then + if settings.use_pdftops then + pdftops = 'pdftops.exe' + end + else + if settings.use_pdftops then + pdftops = find_on_path('pdftops') + end + end + -- dbg('Option handling; pdftops is '..tostring(pdftops)) - if opts.info.val then - options.info = true - end + -- other options - if opts.help.val then - help() - end + if opts.save.val then + write_settings(rcfile) + end - if opts.version.val then - show_version() - os.exit() - end + if opts.debug.val then + options.debug = true + end - if opts.gui.val then - gui(opts.gui.val) - end + if opts.help.val then + help() + end - -- now we need 1 or 2 filenames, unless the user really only - -- wanted to save options without further action. + -- opts.info.val: need to get infile first - if i>#arg then - if opts.save.val then os.exit() else help('No filenames') end - end + if opts.version.val then + show_version() + os.exit() + end - infile = arg[i] - if i<#arg then - outfile = arg[i+1] - else + 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 - end - if (#arg>i and options.info) or (#arg>i+1) then - help('Surplus non-option parameters') - end + 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 - -- one final quick option - if opts.info.val then - info(infile) - 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 - -- add pdf_version and pdf_target to the options array, - -- from where it will be set to false when realized - if settings.pdf_target == 'default' then - options.pdf_target = false - else - options.pdf_target = settings.pdf_target - end - if settings.pdf_version == 'default' then - options.pdf_version = false - else - options.pdf_version = tonumber(settings.pdf_version) - end + -- one final quick option + if opts.info.val then + info(infile) + end -end -- decoding command-line + -- add pdf_version and pdf_target to the options array, + -- from where it will be set to false when realized + if settings.pdf_target == 'default' then + options.pdf_target = false + else + options.pdf_target = settings.pdf_target + end + if settings.pdf_version == 'default' then + options.pdf_version = false + else + options.pdf_version = tonumber(settings.pdf_version) + end --- dbg('After command-line processing\n Settings') --- -- print settings- and options array with dbg --- for k, v in pairs(settings) do --- dbg(k..': '..tostring(v)) --- end --- dbg(' Options') --- for k, v in pairs(options) do --- dbg(k..': '..tostring(v)) --- end + end -- decoding command-line ---[[ + --[[ + dbg('After command-line processing\n Settings') + -- print settings- and options array with dbg + for k, v in pairs(settings) do + dbg(k..': '..tostring(v)) + end + dbg(' Options') + for k, v in pairs(options) do + dbg(k..': '..tostring(v)) + end + --]] -Once it becomes clear that real work needs to be done, -we shall create a temp directory in the parent directory of the output file -and use that as working directory. + --[[ -1. consistent with the ghostscript -dSAFER option -2. we can move/rename rather than copy the final temp file - to the output file + 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. + because of gs -dSAFER restrictions, infile must be in (a + subdirectory of) the directory of the output file, e.g. in the + temp directory. - Also because of -dSAFER, we copy infile to the temp directory of - it is not in the same directory as outfile. + Also because of -dSAFER, we copy infile to the temp directory of + it is not in the same directory as outfile. ---]] + --]] -do - local source = io.open(infile) + source = io.open(infile) if not source then error(infile .. ' not readable') end source:close() - local in_dir + -- if options.debug then + -- warn('in: '..infile..'\nout: '..outfile..'\n\ncwd: '..cwd) + -- 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 - -- we need a writable dest_dir as parent for a temp directory, - -- in some cases even for option info - if not outfile then - dest_dir = in_dir - else - outfile, dest_dir = absolute_path(outfile) - end - lfs.chdir(dest_dir) - tempdir = os.tmpdir() -- relative path! - local c, e - c, e = lfs.chdir(tempdir) - if not c then - write_log(e) - tempdir = false - -- errror('Failure to create temporary directory') + -- 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') else - tempdir = lfs.currentdir() -- better for logging: absolute path - write_log('Working directory: '..tempdir) + -- dbg('temp directory '..tempdir) end + lfs.chdir(tempdir) - infile, source_dir = absolute_path(infile) intype = identify(infile) -- remaining cases: want a real conversion @@ -2640,15 +2813,6 @@ do error(infile..' has an unsupported filetype') end - if not outfile 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 - --sanity check on output filetype options.type = string.match(outfile, '.*%.([^%.]+)$') if not options.type or (options.type~='ps' @@ -2657,22 +2821,19 @@ do ' should have extension .eps, .ps or .pdf') end - -- if outfile equal to infile, copy to temp directory, then remove if outfile==infile then - infile = mktemp(intype) - slice_file(outfile, infile) - write_log('Copying '..outfile..' to temporary file '..infile..'.') + local insave = infile .. '.luasave' + move_or_copy(infile, insave) + infile = insave end - -- had some trouble under msw when removing outfile later so do it now + -- had some trouble under msw when removing outfile later so try it now if lfs.isfile(outfile) then os.remove(outfile) - if lfs.attributes(outfile) then - errror('Cannot overwrite '..outfile) - end + -- if removal fails but outfile is overwritable then no real problem end - local fout = io.open(outfile, 'w') + local fout = io.open(outfile, 'wb') if not fout then errror('Output file '..outfile..' not writable; aborting') else @@ -2681,17 +2842,11 @@ do source = PsPdf:from_path(infile) dest = source:any_to_any() - -- options will be read from the global options table - -- and turned off after they have been satisfied. - -- irrelevant options are quietly ignored. - - if os.type=='unix' then - write_log('Rename '..dest.path..' to '..outfile) - os.rename(dest.path, outfile) -- we picked our temp dir to make this possible - else - write_log('Copying '..dest.path..' to '..outfile) - slice_file(dest.path, outfile) + if not lfs.isfile(dest.path) then + errror('Failed to generate '..dest.path) end + write_log('Copying or moving '..dest.path..' to '..outfile) + move_or_copy(dest.path, outfile) if not options.debug then cleantemp() end diff --git a/Master/texmf-dist/scripts/epspdf/epspdftk.tcl b/Master/texmf-dist/scripts/epspdf/epspdftk.tcl index 32bb93011e9..b55db2c979f 100755 --- a/Master/texmf-dist/scripts/epspdf/epspdftk.tcl +++ b/Master/texmf-dist/scripts/epspdf/epspdftk.tcl @@ -3,7 +3,7 @@ # epspdf conversion utility, GUI frontend ##### -# Copyright (C) 2006, 2008, 2009, 2010, 2011, 2013 Siep Kroonenberg +# Copyright (C) 2006, 2008, 2009, 2010, 2011, 2013, 2014 Siep Kroonenberg # n dot s dot kroonenberg at rug dot nl # # This program is free software, licensed under the GNU GPL, >=2.0. @@ -44,77 +44,34 @@ proc write_log {s} { proc set_progs {} { set scriptfile [file normalize [info script]] - set syml 0 - if {$::tcl_platform(platform) eq "unix" && \ - ! [catch {file readlink [$scriptfile]}]} { - set syml 1 - } set eproot [file dirname $scriptfile] - if {$::tcl_platform(platform) eq "unix" && \ - ! [catch {file readlink $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 } - if {! [file exists [file join $eproot "epspdf.tlu"]]} { - # starpack edition? - set starred 0 - foreach l [info loaded] { - if {[lindex $l 1] eq "tclkitpath"} { - set starred 1 - break - } - } - if {$starred} { - set eproot [file dirname [file normalize [info nameofexecutable]]] - # here no testing for symlink - } - } - set ::texlua "texlua" + # 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 {! [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" - # no luck with other platforms - if {$::tcl_platform(platform) eq "windows"} { - wm iconbitmap . [file join $eproot "epspdf.ico"] - } + # icon for starpack: add with sdx or with Windows utilities } set_progs -# call epspdf.tlu with parameter list $args (should be a list) -# Return codes success/failure -# We also need stdout output. -# Tcl idiom: res is a variable _name_. -# The upvar construct makes it a reference parameter. - -#proc run_epspdf {res args} { -# upvar $res result -# if {$::ge_85} { -# set failed [catch [linsert $args 0 \ -# exec -ignorestderr $::texlua $::epspdf_tlu --gui=gui] result] -# } else { -# set failed [catch [linsert $args 0 \ -# exec $::texlua $::epspdf_tlu --gui=gui] result] -# } -# if {$failed} { -# # wm deiconify .log_t -# tk_messageBox -icon error -type ok -message "Error; see log window" -# } -# -# # write to log textbox -# write_log $result -# -# # it is up to the caller to do anything else about failure or not. -# # the user, at least, has been warned. -# return [expr ! $failed] -#} - ### configured and automatic settings ################################## # is_prog used for checking configured viewers under non-osx unix @@ -182,7 +139,7 @@ proc getsettings {} { if {$::settings(ps_viewer) ne "" && [is_prog $::settings(ps_viewer)]} { lappend ::ps_viewers $::settings(ps_viewer) } - foreach v {evince okular gv kghostview ghostview} { + foreach v {evince okular gv qpdfview} { if {$v ne $::settings(ps_viewer) && [is_prog $v]} { lappend ::ps_viewers $v } @@ -193,8 +150,7 @@ proc getsettings {} { if {$::settings(pdf_viewer) ne "" && [is_prog $::settings(pdf_viewer)]} { lappend ::pdf_viewers $::settings(pdf_viewer) } - foreach v {evince okular kpdf xpdf epdfview acroread \ - gv kghostview ghostview} { + foreach v {evince okular xpdf epdfview qpdfview zathura acroread gv} { if {$v ne $::settings(pdf_viewer) && [is_prog $v]} { lappend ::pdf_viewers $v } @@ -423,7 +379,12 @@ wm title . "PostScript- and pdf conversions" proc readhelp {} { .help_t.text configure -state normal - set helpfile [regsub {\.tlu$} $::epspdf_tlu {.help}] + # 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 { -- cgit v1.2.3