summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/epspdf
diff options
context:
space:
mode:
authorSiep Kroonenberg <siepo@cybercomm.nl>2008-07-04 10:17:06 +0000
committerSiep Kroonenberg <siepo@cybercomm.nl>2008-07-04 10:17:06 +0000
commit4a697a53a3c9c2795d549c7d5eb7a6c88c7911a9 (patch)
treeb2f5967ce6865b615213754aa2aa3d41574502bc /Master/texmf-dist/scripts/epspdf
parentcdbc5d3bd4d89338a033d47d1368e8e30d273544 (diff)
Added: epspdf conversion utility
git-svn-id: svn://tug.org/texlive/trunk@9222 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/epspdf')
-rwxr-xr-xMaster/texmf-dist/scripts/epspdf/epspdf3
-rw-r--r--Master/texmf-dist/scripts/epspdf/epspdf.help155
-rwxr-xr-xMaster/texmf-dist/scripts/epspdf/epspdf.rb1123
-rw-r--r--Master/texmf-dist/scripts/epspdf/epspdfboot.rb40
-rwxr-xr-xMaster/texmf-dist/scripts/epspdf/epspdfrc.rb507
-rwxr-xr-xMaster/texmf-dist/scripts/epspdf/epspdftk7
-rwxr-xr-xMaster/texmf-dist/scripts/epspdf/epspdftk.rb958
-rw-r--r--Master/texmf-dist/scripts/epspdf/makegray.pro29
8 files changed, 2822 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/epspdf/epspdf b/Master/texmf-dist/scripts/epspdf/epspdf
new file mode 100755
index 00000000000..4492608f398
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/epspdf
@@ -0,0 +1,3 @@
+#!/bin/sh
+script=`kpsewhich -format=texmfscripts epspdf.rb`
+"$script" $*
diff --git a/Master/texmf-dist/scripts/epspdf/epspdf.help b/Master/texmf-dist/scripts/epspdf/epspdf.help
new file mode 100644
index 00000000000..90d9c795a0d
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/epspdf.help
@@ -0,0 +1,155 @@
+ Epspdf and epspdftk User Guide
+
+ A multiplatform GUI- and command-line converter for [e]ps and pdf.
+
+ Siep Kroonenberg,
+ siepo at cybercomm dot nl
+
+Overview
+
+ 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 half of the screen.
+
+Viewing
+
+ The View button at the bottom of the screen calls an appropriate external
+ viewer. For Windows this is determined by the default Open command, for OS
+ X it is always Preview, and under Linux it is configurable; see below
+ under [1]The Configuration screen.
+
+ The View button is grayed if epspdf thinks that there is no previewer for
+ the current file.
+
+Conversion options
+
+ Grayscaling: Simple color figures can be converted to grayscale.
+ Grayscaling will fail for bitmapped data and for advanced color models.
+ 'Try harder to grayscale' will handle a few more cases, but may take
+ longer and still won't handle bitmaps.
+
+ Call for assistance: If a PostScript color expert is reading this: please
+ have a look at makegray.pro and see whether you can come up with something
+ that handles more color models.
+
+ 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. When converting to eps you are restricted to a single
+ page.
+
+ For general PostScript files, there is no quick way to determine the
+ number of pages, so the program won't check beforehand whether you picked
+ an existing page. If you picked a non-existent page, the program will
+ abort. 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
+ several roundtrips between PostScript or eps and pdf. If epspdf cannot use
+ pdftops then some combinations of options will not be available.
+
+Converting
+
+ The Convert and save... button calls up a file save dialog, with
+ appropriate default extension. After a successful conversion, the result
+ becomes the new current file, so you can judge the result by pressing the
+ View button again.
+
+The configuration screen
+
+ Epspdftk also has a configuration screen. The settings here are preserved
+ between sessions. Pay particular attention to 'Intended use' under PDF
+ CONVERSION. Choose 'prepress' for pdfs which are intended for commercial
+ printing. This option will try to embed all fonts, among other things.
+ More and more often, printshops insist on this. With luck, Ghostscript
+ fonts and system fonts will be used for fonts which were not originally
+ embedded.
+
+ Under Windows, this screen also lets you tell the program where to find
+ pdftops.exe. Under Linux, it lets you pick PostScript- and pdf previewers.
+
+Adding standard fonts to an existing pdf
+
+ You can add standard fonts such as Times and Helvetica to an existing pdf:
+ first, convert to ps, with 'Convert all pages' selected. Then, make sure
+ that in the configuration screen 'Intended use' is set to prepress, then
+ convert back to pdf.
+
+ If this option is set to screen, then standard fonts will be removed
+ instead of added. This option may also affect the resolution of bitmaps.
+
+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
+ currently has no option to preserve or add them.
+
+Exporting PostScript from Windows programs
+
+ Often, the only way to get EPS or PostScript from a Windows program is by
+ 'printing' 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 printer properties: avoid the
+ 'Optimize for speed' setting and choose Outline for font downloading.
+ These options can be found under the Advanced button.
+
+ Other possibilities are the TpX and wmf2eps programs, which both have
+ options to write clipboard contents to an emf file. Wmf2eps uses a printer
+ driver in the background. For faithful conversion, pick wmf2eps; for
+ subsequent editing, choose TpX. Both programs are availbable from [2]CTAN.
+
+Command-line use
+
+ Epspdf.rb, the library for epspdftk.rb, does double duty as command-line
+ version of epspdftk.rb. Type epspdf.rb -h for help. It shares
+ configuration settings with epspdftk.
+
+Troubleshooting
+
+ Grayscaling fails
+
+ See above, under [3]Grayscaling. Epspdf is limited by what can be done by
+ the backend tools, i.e. Ghostscript and pdftops.
+
+ Part of the graphic gets cut off
+
+ I have seen this happen with eps files generated by printing landscape
+ PowerPoint slides to PostScript with the Encapsulated PostScript output
+ option. Using the Archive output option instead solved the problem. In
+ general, experiment a bit in generating your PostScript.
+
+ Resources for troubleshooting
+
+ Logfile. Epspdf and epspdftk maintain a log file epspdf.log in your home
+ directory or, in case of Windows, your profile directory. This profile
+ directory may be c:\Documents and Settings\<your user name>. Otherwise,
+ open a DOS box and type 'echo %USERPROFILE%', which will produce this
+ information.
+
+ This logfile lists all Ghostscript- and pdftops program calls plus error
+ information. 'EPCallError' is probably a program error. 'EPSystemError' is
+ more likely caused by a faulty file, but may be a program error as well.
+
+ Temporary files. The temporary files may give clues as well. They will be
+ deleted at the end of the session (for the command-line version, give a -d
+ parameter to keep the temporary files). Check the logfile which temporary
+ files have been created.
+
+ [4]Main page...
+
+ --------------------------------------------------------------------------
+
+ Last revised: Noverber 20, 2006
+
+References
+
+ Visible links
+ 1. http://tex.aanhet.net/epspdf/epspdf_UG.html#configure
+ 2. http://www.tug.org/ctan.html
+ 3. http://tex.aanhet.net/epspdf/epspdf_UG.html#gray
+ 4. http://tex.aanhet.net/epspdf/index.html
diff --git a/Master/texmf-dist/scripts/epspdf/epspdf.rb b/Master/texmf-dist/scripts/epspdf/epspdf.rb
new file mode 100755
index 00000000000..fb391b6dde6
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/epspdf.rb
@@ -0,0 +1,1123 @@
+#!/usr/bin/env ruby
+
+# epspdf conversion utility, main source
+
+#####
+# Copyright (C) 2006 Siep Kroonenberg
+# n dot s dot kroonenberg at rug 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!
+#####
+
+# Operations on a PostScript- or pdf file.
+# The focus is on converting between eps and pdf.
+
+# Code is organized as a set of single-step conversion methods plus an
+# any-to-any conversion.
+
+# `Target' is not a parameter; all conversions write to a new temp
+# file. Conversions can be chained: object.conversion1( params
+# ).conversion2( params ) ... The final temp file is moved or copied to
+# the desired destination by the main program (which can be epspdf
+# itself).
+
+# The use of exception handling makes it unnecessary to inspect return
+# values.
+
+# Limitation
+# HiRes boundingboxes are ignored.
+# Two reasons:
+# - they get lost anyhow on conversion to pdf
+# - They are not required so when you find one you have to make sure
+# that it doesn't refer to an included eps. This would
+# require additional logic.
+
+###########################################
+
+# some initialization in case this file is used as main program
+
+if __FILE__ == $0
+
+ # epspdfboot.rb: modify loadpath in a way which works both
+ # with a full Ruby and with a custom subset ruby
+
+ require __FILE__.sub( /epspdf\.rb/, 'epspdfboot.rb' )
+
+ # turn on warnings
+ #$VERBOSE = 1
+
+end # if main program
+
+###########################################
+
+# Error handling
+
+# internal error: method should not have been called
+# under the current conditions
+class EPCallError < StandardError; end
+
+# Can't get a valid boundingbox for an eps file
+class EPBBError < StandardError; end
+
+# system call failed
+class EPSystemError < StandardError; end
+
+###########################################
+
+# PostScript header file for grayscaling
+
+$GRAYHEAD = File.join( File.dirname( __FILE__ ), "makegray.pro" )
+
+###########################################
+
+# handle auto-detected and saved settings
+
+require 'epspdfrc'
+
+###########################################
+
+# Transient options. These will never be saved.
+# We set default values here.
+
+$options = {
+ 'type' => nil,
+ 'page' => nil,
+ 'bbox' => false,
+ 'gray' => false,
+ 'gRAY' => false,
+ 'info' => false
+}
+
+class << $options
+
+ # create shortcut methods $options.x and $options.x=
+ # for reading and writing hash elements.
+
+ $options.each_key { |k|
+ eval "def #{k} ; self[\'#{k}\'] ; end"
+ eval "def #{k}=(v) ; self[\'#{k}\']=v ; end"
+ }
+
+end # class
+
+###########################################
+
+def hash_prn( h )
+ if h.empty?
+ s = "No parameters" + $/
+ else
+ s = "Parameters were:" + $/
+ h.each_key { |k| s = s + ' ' + k.to_s + ' => ' + h[ k ].to_s + $/ }
+ end
+ s
+end
+
+###########################################
+
+require 'fileutils'
+#include FileUtils::Verbose
+include FileUtils
+
+###########################################
+
+# Mode strings for file i/o
+
+if ARCH == 'w32'
+ $W = 'wb'
+ $A = 'ab'
+ $R = 'rb'
+else
+ $W = 'w'
+ $A = 'a'
+ $R = 'r'
+end
+
+###########################################
+
+# copy a slice of a file; is there no such standard function?
+# some eps files are very large; don't slurp the file at one go.
+# Mode: can be set to append 'a' rather than write 'w'.
+
+def sliceFile( source, dest, len, offs, mode=$W )
+ buffer = ''
+ File.open( source ) do |s|
+ s.binmode if ARCH == 'w32'
+ s.seek( offs, IO::SEEK_SET )
+ if s.pos == offs
+ File.open( dest, mode ) do |d|
+ d.binmode if ARCH == 'w32'
+ tocopy = len
+ while tocopy>0 and s.read( [ tocopy, 16384 ].min, buffer )
+ tocopy = tocopy - d.write( buffer )
+ end # while
+ end # |d| (automatic closing)
+ end # if s.seek
+ end # |s| (automatic closing)
+ # return value true if anything has been copied
+ File.size?( dest ) # nil if zero-length
+end # def
+
+# write our own file copy, to bypass bug in FileUtils.cp
+
+def ccp( source, dest )
+ sliceFile( source, dest, File.size( source ), 0, $W )
+end
+
+###########################################
+
+# logging
+
+def write_log( s )
+ if test( ?e, LOGFILE ) and File.size( LOGFILE ) > 100000
+ rm( LOGFILE_OLD ) if test( ?e, LOGFILE_OLD )
+ ccp( LOGFILE, LOGFILE_OLD )
+ File.truncate( LOGFILE, 0 )
+ end
+ File.open( LOGFILE, 'a' ) { |f|
+ f.print( "#{$$} #{Time.now.strftime('%Y/%m/%d %H:%M:%S')} #{s}\n" )
+ }
+end
+
+###########################################
+
+require 'tmpdir'
+
+#$DEBUG=1
+
+# save filenames for cleanup at end
+$tempfiles = []
+
+def mktemp( ext )
+ isdone = nil
+ (0..99).each do |i|
+ fname = Dir.tmpdir + File::SEPARATOR + \
+ sprintf( '%d_%02d.%s', $$, i, ext ) # $$ is process id
+ next if test( ?e, fname )
+ File.open( fname, 'w' ) do |f|; end # creates empty file
+ isdone = 1
+ $tempfiles.unshift( fname )
+ return fname
+ end # each do |i|
+ fail StandardError, "Cannot create temp file" unless isdone
+end # def
+
+def cleantemp
+ write_log( "Cleaning tempfiles" + $/ + $tempfiles.join( $/ ) )
+ $tempfiles.each{ |tf|; rm( tf ) }
+end
+
+###########################################
+
+# identifying file type
+
+# EPS with preview starts with 0xC5D0D3C6
+# Encapsulated PostScript starts with %!PS-Adobe-n.n EPSF-n.n
+# but %!PS-Adobe-n.n will also be identified as Encapsulated PostScript
+# if the filename extension suggests it.
+# PostScript starts with %!
+# PDF starts with %PDF-version
+
+def identify( path )
+ filestart = nil
+ File.open( path, $R ) do |f|
+ filestart = f.read( 23 )
+ end # this syntax automatically closes the file at end of block
+ case filestart
+ when /^\xc5\xd0\xd3\xc6/
+ 'epsPreview'
+ when /^%!PS-Adobe-\d\.\d EPSF-\d\.\d/
+ 'eps'
+ when /^%!PS-Adobe-\d\.\d/
+ ( path =~ /\.ep(i|s|si|sf)$/i ) ? 'eps' : 'ps'
+ when /^%!/
+ 'ps'
+ when /^%PDF/
+ 'pdf'
+ else
+ 'other'
+ end # case
+end # def
+
+###########################################
+
+# Boundingboxes
+
+BB_PAT = /^\s*%%BoundingBox:\s*(-?\d+)((\s+-?\d+){3})\s*$/
+BB_END = /^\s*%%BoundingBox:\s*\(\s*atend\s*\)\s*$/
+# for multiline matching:
+BB_PAT1 = /%%BoundingBox:\s*(-?\d+)((\s+-?\d+){3})/m
+BB_END1 = /%%BoundingBox:\s*\(\s*atend\s*\)/m
+
+class Bb
+
+ attr_accessor :llx, :lly, :urx, :ury
+
+ def initialize( llx, lly, urx, ury )
+ @llx, @lly, @urx, @ury = llx, lly, urx, ury
+ [@llx, @lly, @urx, @ury].each { |l| l = l.to_i.to_s }
+ end
+
+ def Bb.from_bbcomment( s )
+ return nil unless s =~ BB_PAT
+ llx, lly, urx, ury =
+ s.sub( /^\s*%%BoundingBox:\s*/, '' ).split( /\s+/ )
+ Bb.new( llx, lly, urx, ury )
+ end
+
+ def height
+ ( @ury.to_i - @lly.to_i ).to_s
+ end
+
+ def width
+ ( @urx.to_i - @llx.to_i ).to_s
+ end
+
+ def valid
+ @llx.to_i < @urx.to_i and @lly.to_i < @ury.to_i
+ end
+
+ def non_negative
+ valid and @llx.to_i >= 0 and @lly.to_i >= 0
+ end
+
+ def copy
+ Bb.new( @llx, @lly, @urx, @ury )
+ end
+
+ def prn
+ "#{@llx} #{@lly} #{@urx} #{@ury}"
+ end
+
+ def expand
+ i = ( $settings.bb_spread ).to_i
+ return if i <= 0
+ @llx = ( [ 0, @llx.to_i - i ].max ).to_s
+ @lly = ( [ 0, @lly.to_i - i ].max ).to_s
+ @urx = ( @urx.to_i + i ).to_s
+ @ury = ( @ury.to_i + i ).to_s
+ end
+
+ ##################
+
+ # wrapper code for a boundingbox;
+ # moves lower left corner of eps to (0,0)
+ # and defines a page size identical to the eps width and height.
+ # The gsave in the wrapper code should be matched by
+ # a grestore at the end of the PostScript code.
+ # This grestore can be specified on the Ghostscript command-line.
+
+ def wrapper
+ fail EPBBError, prn unless valid
+ fname = mktemp( 'ps' )
+ File.open( fname, $W ) do |f|
+ f.binmode if ARCH == 'w32'
+ f.write( "%%BoundingBox: 0 0 #{width} #{height}\n" +
+ "<< /PageSize [#{width} #{height}] >>" +
+ " setpagedevice\n" +
+ "gsave #{(-(@llx.to_i)).to_s} #{(-(@lly.to_i)).to_s}" +
+ " translate\n" ) > 0
+ end # open
+ return fname
+ end # wrapper
+
+ ##################
+
+ # convert boundingbox to boundingbox comment
+
+ def bbcomment
+ fail EPBBError, prn unless valid
+ "%%BoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}"
+ end
+
+end # class Bb
+
+###########################################
+
+# PsPdf class definition
+
+class PsPdf
+
+ protected
+
+ SAFESIZE = 16000
+
+ public
+
+ # class methods ###
+
+ def PsPdf.pdf_options
+ "-dPDFSETTINGS=/#{$settings.pdf_target}" + \
+ ( $settings.pdf_version == 'default' ? '' : \
+ " -dCompatibilityLevel=#{$settings.pdf_version}" ) + \
+ ($settings.pdf_custom ? ' ' + $settings.pdf_custom : '')
+ end
+
+ def PsPdf.gs_options
+ "-dNOPAUSE -dBATCH -q -dSAFER"
+ end
+
+ def PsPdf.ps_options( sep )
+ # the sep(arable_color) option forces a cmyk color model,
+ # which should improve chances of grayscaling later on.
+ if sep
+ case $settings.ps_options
+ when /-level\dsep\b/
+ $settings.ps_options
+ when /-level\d\b/
+ $settings.ps_options.sub( /(-level\d)/, '\1sep' )
+ else
+ $settings.ps_options + " -level2sep"
+ end # case
+ else
+ $settings.ps_options
+ end # ifthenelse sep
+ end
+
+ # instance methods ###
+
+ attr_accessor :path, :bb, :type, :npages, :atfront
+
+ ##################
+
+ def initialize( params={} )
+
+ ext = params[ 'ext' ]
+ file = params[ 'file' ]
+
+ if not ext and not file
+ @path = nil
+ @type = nil
+ elsif not file
+ @path = mktemp( ext )
+ @type = case ext.downcase
+ when 'pdf'
+ 'pdf'
+ when 'eps'
+ 'eps'
+ when 'ps'
+ 'ps'
+ else
+ 'other'
+ end
+ else
+ @path = file
+ @type = identify( file )
+ end
+ end # initialize
+
+ ##################
+
+ # debug string for EPCallError
+
+ def buginfo( params = nil )
+ b = "Source was: " + @path + $/
+ b = b + hash_prn( params ) if params
+ b
+ end
+
+ ##################
+
+ # Find boundingbox, simple case.
+ # We shall call this method only for eps PsPdf objects which were
+ # converted by pdftops or Ghostscript, so we can be sure that
+ # the boundingbox is not (atend). The file is not rewritten.
+
+ def find_bb_simple
+
+ fail EPCallError, buginfo unless @type == 'eps'
+ @bb = nil
+ slurp = ''
+ File.open( @path, $R ) do |fl|
+ slurp = fl.read( [File.size(@path), SAFESIZE].min )
+ end
+ lines = slurp.split( /\r\n?|\n/ )
+ lines.each do |l|
+ if l =~ BB_PAT
+ @bb = Bb.from_bbcomment( l )
+ break
+ end
+ end # do |l|
+ fail EPBBError, @path unless @bb and @bb.valid
+
+ end # def find_bb_simple
+
+ ##################
+
+ def pdf_pages
+
+ fail EPCallError, buginfo unless @type == 'pdf'
+
+ @npages = nil
+
+ # get n. of pages; the Ghostscript pdf2dsc.ps script will
+ # create a list of pages. It seems to ignore logical p. numbers.
+
+ dsc = mktemp( "dsc" )
+ cmd = "\"#{$settings.gs_prog}\" -dNODISPLAY -q" +
+ " -sPDFname=\"#{@path}\" -sDSCname=\"#{dsc}\" pdf2dsc.ps"
+ write_log cmd # if $DEBUG
+ fail EPSystemError, cmd unless system( cmd ) and test( ?s, dsc )
+ lines = []
+ File.open( dsc, $R ) do |f|
+ lines = f.read( SAFESIZE ).split( /\r\n?|\n/ )
+ end
+ lines.each do |l|
+ if l =~ /^%%Pages:\s+(\d+)\s*$/
+ @npages = $1.to_i
+ break
+ end # if =~
+ end # do |l|
+
+ return @npages
+
+ end # pdf_pages
+
+ #############################################################
+
+ # direct conversions. These methods return a PsPdf object,
+ # and raise an exception in case of failure.
+ # Direct conversions convert at most once between PostScript and pdf.
+ # They always write to a temporary file.
+
+ ##################
+
+ # eps_clean: write source eps to an eps without preview, and
+ # with a boundingbox in the header.
+ # the eps is always written to a new file.
+
+ def eps_clean
+
+ fail EPCallError, buginfo if @type != 'eps' and @type != 'epsPreview'
+ atend = nil
+ slurp = ''
+ offset, ps_len = nil, nil
+ if @type == 'eps'
+ offset = 0
+ ps_len = File.size( @path )
+ else
+ # read ToC; see Adobe EPS spec
+ File.open( @path, $R ) do |fl|
+ # bug workaround for unpack
+ if "\001\000\000\000".unpack( 'V' )[0] == 1
+ dummy, offset, ps_len = fl.read( 12 ).unpack( 'VVV' )
+ else
+ dummy, offset, ps_len = fl.read( 12 ).unpack( 'NNN' )
+ end
+ end # File
+ end # ifthenelse @type
+
+ # boundingbox unknown and possibly atend
+ @bb, @atfront = nil, nil
+
+ # limit search for boundingbox comments.
+ # For very large eps files, we don't want to scan the entire file.
+ # A boundingbox comment should be in the header or trailer,
+ # so scanning the first and last few KB should be plenty.
+
+ File.open( @path, $R ) do |fl|
+ fl.seek( offset, IO::SEEK_SET )
+ slurp = fl.read( [ps_len,SAFESIZE].min )
+ end
+
+ # We capture both lines and separators, as an easy way to
+ # keep track of how many bytes have been read.
+ # It is not necessary to treat lines and separators differently.
+
+ lines = slurp.split( /(\r\n?|\n)/ )
+ pre_block = ''
+ post_block = ''
+ bb_comment = ''
+ lines.each do |l|
+ pre_block = pre_block + l
+ if l =~ BB_PAT
+ @bb = Bb.from_bbcomment( l )
+ @atfront = true
+ break
+ elsif l =~ BB_END
+ atend = true
+ break
+ end # =~ BB_PAT
+ end # do |l|
+ if atend
+ if ps_len > SAFESIZE
+ File.open( @path, $R ) do |fl|
+ fl.seek( offset + ps_len - SAFESIZE, IO::SEEK_SET )
+ slurp = fl.read( SAFESIZE )
+ end
+ lines = slurp.split( /(\r\n?|\n)/ )
+ end
+ lines.reverse_each do |l|
+ post_block = l + post_block
+ if l =~ BB_PAT
+ bb_comment = l
+ @bb = Bb.from_bbcomment( bb_comment )
+ break
+ end # if
+ end # do
+ end # if atend
+ fail EPBBError, @path unless @bb.valid
+ retVal = PsPdf.new( 'ext' => 'eps' )
+ if @type == 'eps' and @atfront # simple copy
+ ccp( @path, retVal.path )
+ elsif @atfront # copy a slice
+ sliceFile( @path, retVal.path, ps_len, offset )
+ elsif atend # relocate boundingbox
+ File.open( retVal.path, $W ) do |fl|
+ fl.write( pre_block.sub( BB_END1, bb_comment ))
+ end
+ sliceFile( @path, retVal.path,
+ ps_len - pre_block.length - post_block.length,
+ offset + pre_block.length, $A )
+ File.open( retVal.path, $A ) do |fl|
+ fl.write( post_block.sub( BB_PAT1, '%' ))
+ end
+ end # if atend
+ retVal.bb = @bb.copy
+ retVal.atfront = true
+ return retVal
+
+ end # eps_clean
+
+ ##################
+
+ # Use the Ghostscript bbox device to give an eps a tight boundingbox.
+ # Newer versions of Ghostscript can do that by themselves, but
+ # for the time being we just assume a bbox device.
+ # 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.
+ # Maybe we should test whether gs' bbox device can handle
+ # nonnegative coordinates.
+ # The boundingbox in the eps is rewritten, but the eps is
+ # not otherwise converted.
+ # We don't create a new PsPdf object.
+
+ def fix_bb
+
+ fail EPCallError, buginfo \
+ unless @type == 'eps' and @bb.non_negative
+ cmd = "#{short_name($settings.gs_prog)} #{PsPdf.gs_options}" +
+ " -sDEVICE=bbox #{short_name(@path)} 2>&1"
+ write_log cmd # if $DEBUG
+ bb_output = `#{cmd}`
+ fail EPSystemError, cmd unless $? == 0 and bb_output
+ bb_output.split(/\r\n?|\n/).each do |b|
+ if b =~ BB_PAT
+ bb_temp = Bb.from_bbcomment( b )
+ fail EPBBError, bb_output unless bb_temp.valid
+ bb_temp.expand
+ @bb = bb_temp.copy
+ # write a new eps file with a new boundingbox comment
+ ps_len = File.size( @path )
+ slurp = ''
+ File.open( @path, $R ) do |fl|
+ slurp = fl.read( [ps_len,SAFESIZE].min )
+ end
+ lines = slurp.split( /(\r\n?|\n)/ )
+ pre_block = ''
+ pre_length = 0
+ lines.each do |l|
+ pre_length = pre_length + l.length
+ if l =~ BB_PAT
+ pre_block = pre_block + bb_temp.bbcomment
+ oldpath = @path
+ @path = mktemp( 'eps' )
+ File.open( @path, $W ) { |fl| fl.write( pre_block ) }
+ sliceFile( oldpath, @path, \
+ ps_len - pre_length, pre_length, $A )
+ return self
+ else
+ pre_block = pre_block + l
+ end
+ end # do |l|
+ fail EPBBError, bb_output
+ end # if =~ BB_PAT
+ end # do |b|
+ fail EPBBError, bb_output
+
+ end # fix_bb
+
+ ##################
+
+ # Convert eps to pdf.
+ # The eps should already have a boundingbox in the header.
+ # When fix_bbox, let Ghostscript calculate a tight boundingbox.
+ # For this, we require non-negative boundingbox coordinates.
+
+ def eps_to_pdf( params={} )
+
+ gray = params[ 'gray' ]
+
+ fail EPCallError, buginfo( params ) unless @type == 'eps' and @atfront
+
+ wrp = @bb.wrapper
+
+ retVal = PsPdf.new( 'ext' => 'pdf' )
+ cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" +
+ " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" +
+ " -sOutputFile=\"#{retVal.path}\"" +
+ ( gray ? (' "' + $GRAYHEAD + '"') : "" ) +
+ " \"#{wrp}\" \"#{@path}\" -c grestore"
+ write_log cmd # if $DEBUG
+ fail EPSystemError, cmd \
+ unless system( cmd ) and test( ?s, retVal.path )
+ retVal.npages = 1
+ return retVal
+
+ end # eps_to_pdf
+
+ ##################
+
+ # Convert source pdf to eps.
+ # The option sep_color is ignored if pdftops is not available.
+
+ def pdf_to_eps( params={} )
+
+ page = params[ 'page' ] ? params[ 'page' ].to_i : 1
+ sep = params[ 'sep' ]
+
+ fail EPCallError, buginfo( params ) unless @type == 'pdf'
+ fail EPCallError, buginfo( params ) \
+ unless page > 0 and page <= @npages
+ retVal = PsPdf.new( 'ext' => 'eps' )
+ if $settings.pdftops_prog and $settings.use_pdftops
+ cmd = "\"#{$settings.pdftops_prog}\"" +
+ " #{PsPdf.ps_options( sep )}" +
+ " -eps -f #{page} -l #{page}" +
+ " \"#{@path}\" \"#{retVal.path}\""
+ else
+ cmd = "\"#{$settings.gs_prog}\" -sDEVICE=epswrite" +
+ " #{PsPdf.gs_options}" +
+ " -dFirstPage=#{page}" +
+ " -dLastPage=#{page}" +
+ " -sOutputFile=\"#{retVal.path}\" \"#{@path}\""
+ end
+ write_log cmd # if $DEBUG
+ fail EPSystemError, cmd unless \
+ system( cmd ) and test( ?s, retVal.path )
+ retVal.atfront = 1
+ retVal.find_bb_simple
+ return retVal
+
+ end # pdf_to_eps
+
+ ##################
+
+ def ps_to_pdf( params={} )
+
+ gray = params[ 'gray' ]
+
+ fail EPCallError, buginfo( params ) \
+ unless @type == 'ps'
+
+ retVal = PsPdf.new( 'ext' => 'pdf' )
+ cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" +
+ " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" +
+ " -sOutputFile=\"#{retVal.path}\"" +
+ ( gray ? (' "' + $GRAYHEAD + '"') : "" ) + " \"#{@path}\""
+ write_log cmd # if $DEBUG
+ fail EPSystemError, cmd \
+ unless system( cmd ) and test( ?s, retVal.path )
+
+ retVal.pdf_pages
+ return retVal
+
+ end # def ps_to_pdf
+
+ ##################
+
+ def pdf_to_ps( params={} )
+
+ sep = params[ 'sep' ]
+ page = params[ 'page' ] ? params[ 'page' ].to_s : nil
+
+ fail EPCallError, buginfo( params ) unless \
+ $settings.pdftops_prog and $settings.use_pdftops and @type == 'pdf'
+ retVal = PsPdf.new( 'ext' => 'ps' )
+ cmd = "\"#{$settings.pdftops_prog}\" #{PsPdf.ps_options( sep )}" +
+ ( page ? " -f #{page} -l #{page}" : '' ) +
+ " -paper match \"#{@path}\" \"#{retVal.path}\""
+ write_log cmd # if $DEBUG
+ fail EPSystemError, cmd unless \
+ system( cmd ) and test( ?s, retVal.path )
+ return retVal
+
+ end # pdf_to_ps
+
+ ##################
+
+ # all possible conversions, as concatenations of direct conversions.
+
+ def any_to_any( params={} )
+
+ type = params[ 'type' ]
+ page = params[ 'page' ]
+ bbox = params[ 'bbox' ]
+ gray = params[ 'gray' ]
+ gRAY = params[ 'gRAY' ] # try harder to grayscale
+ gray = 1 if gRAY
+ bbox = nil if type == 'ps'
+
+ fail EPCallError, buginfo( params ) \
+ unless ( type=='eps' or type=='pdf' or type=='ps' )
+ fail EPCallError, buginfo( params ) \
+ if @type=='other'
+ fail EPCallError, buginfo( params ) \
+ if type=='ps' and bbox
+ fail EPCallError, buginfo( params ) \
+ if @type=='eps' and type=='ps'
+
+ # gRAY tries harder to grayscale, by converting color first to cmyk
+ # even if it requires an additional eps - pdf - eps roundtrip.
+ # Normally, conversion to cmyk is done only if it doesn't take
+ # an extra roundtrip.
+ # The separable color conversion is an option of pdftops.
+
+ gRAY = nil unless \
+ ( $settings.pdftops_prog and $settings.use_pdftops )
+
+ pp = self
+
+ pp = pp.eps_clean if pp.type == 'epsPreview'
+ pp = pp.eps_clean if pp.type == 'eps' and \
+ not ( pp.bb and pp.atfront )
+ pp.pdf_pages if pp.type == 'pdf' and not pp.npages
+
+ case pp.type
+ when 'eps'
+
+ # in some cases extra eps => pdf => eps roundtrip:
+ # roundtrip guarantees bb.non_negative, necessary for fix_bb.
+ # pdf_to_ps( sep ) improves chances of grayscaling.
+
+ pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \
+ if gRAY or ( bbox and not pp.bb.non_negative )
+
+ case type
+ when 'eps'
+ pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_eps if gray
+ pp = pp.fix_bb if bbox
+ return pp
+
+ when 'pdf'
+ pp = pp.fix_bb if bbox
+ return pp.eps_to_pdf( 'gray' => gray )
+
+ end # case type
+
+ when 'pdf'
+
+ case type
+ when 'eps'
+ if not gray
+ pp = pp.pdf_to_eps( 'page' => page )
+ pp = pp.fix_bb if bbox
+ return pp
+ else
+ pp = pp.pdf_to_eps( 'page' => page, 'sep' => 1 )
+ pp = pp.fix_bb if bbox
+ pp = pp.eps_to_pdf( 'gray' => 1 )
+ return pp.pdf_to_eps
+ end
+
+ when 'pdf'
+ return pp unless ( gray or bbox or page )
+ if bbox or not $settings.pdftops_prog
+ pp = pp.pdf_to_eps( 'page' => page, 'sep' => gray )
+ pp = pp.fix_bb if bbox
+ return pp.eps_to_pdf( 'gray' => gray )
+ else
+ pp = pp.pdf_to_ps( 'page' => page, 'sep' => gray )
+ return pp.ps_to_pdf( 'gray' => gray )
+ end
+
+ when 'ps'
+ if gray
+ pp = pp.pdf_to_ps( 'sep' => 1 )
+ pp = pp.ps_to_pdf( 'gray' => 1 )
+ end
+ return pp.pdf_to_ps
+
+ end # case type
+
+ when 'ps'
+
+ case type
+ when 'eps'
+ if gRAY
+ pp = pp.ps_to_pdf.pdf_to_eps( 'page' => page, 'sep' => 1 )
+ pp = pp.eps_to_pdf( 'gray' => 1 )
+ pp = pp.pdf_to_eps
+ else
+ pp = pp.ps_to_pdf( 'gray' => gray )
+ pp = pp.pdf_to_eps( 'page' => page )
+ end
+ return pp.fix_bb
+
+ when 'pdf'
+ if bbox
+ pp = pp.ps_to_pdf( 'sep' => gray )
+ pp = pp.pdf_to_eps( 'page' => page )
+ pp = pp.fix_bb
+ return pp = pp.eps_to_pdf( 'gray' => gray )
+ elsif page
+ pp = pp.ps_to_pdf( 'sep' => gray )
+ pp = pp.pdf_to_ps( 'page' => page )
+ return pp = pp.ps_to_pdf( 'gray' => gray )
+ else
+ pp = pp.ps_to_pdf.pdf_to_ps( 'sep' => 1 ) if gRAY
+ return pp.ps_to_pdf( 'gray' => gray )
+ end
+
+ when 'ps'
+ return pp unless page or gray
+ if gRAY
+ pp = pp.ps_to_pdf.pdf_to_ps( 'page' => page, 'sep' => 1 )
+ return pp.ps_to_pdf( 'gray' => 1 ).pdf_to_ps
+ else
+ pp = pp.ps_to_pdf( 'gray' => gray )
+ return pp = pp.pdf_to_ps( 'page' => page )
+ end
+
+ end # case type
+
+ end # case pp.type
+
+ fail "Unsupported conversion"
+
+ end # any_to_any
+
+end # class PsPdf
+
+#################################
+# main program when not called from GUI
+
+if __FILE__ == $0
+
+require 'optparse'
+
+# create a pause to examine temp files
+def abortt( msg )
+ if $DEBUG
+ $stderr.puts msg + "\nPress <enter> to finish"
+ $stdin.gets
+ end
+ fail
+end
+
+begin # outer rescue block
+
+save_settings = false
+
+opts = OptionParser.new do |opts|
+ # for help output
+ opts.banner = "Convert between [e]ps and pdf formats"
+ opts.separator "Usage: epspdf.rb [options] infile [outfile]"
+ opts.separator ""
+ opts.separator "Default for outfile is file.pdf" +
+ " if infile is file.eps or file.ps"
+ opts.separator "Default for outfile is file.eps" +
+ " if infile is file.pdf"
+ opts.separator ""
+
+ opts.on( "-I",
+ "Ignore pdftops even if available",
+ "(default: use if available)" ) do |opt|
+ $settings.ignore_pdftops = '1'
+ end
+
+ opts.on( "-U",
+ "Use pdftops if available",
+ "(overrides previous -I setting)" ) do |opt|
+ $settings.ignore_pdftops = '0'
+ end
+
+ opts.on( "-T TARGET", "--target=TARGET", PDF_TARGETS,
+ "Target use of pdf; one of",
+ "#{PDF_TARGETS.join( ', ' )}" ) do |opt|
+ $settings.pdf_target = opt
+ end
+
+ opts.on( "-V VERSION", "--version=VERSION", PDF_VERSIONS,
+ "Pdf version to be generated;",
+ "one of #{PDF_VERSIONS.join( ', ' )}" ) do |opt|
+ $settings.pdf_version = opt
+ end
+
+ opts.on( "-C CUSTOMOPTIONS", "--custom=CUSTOMOPTIONS",
+ "Custom options for conversion to pdf,",
+ "view Use.htm and ps2pdf.htm from",
+ "the Ghostscript documentation set" ) do |opt|
+ $settings.pdf_custom = opt
+ end
+
+ opts.on( "-P PSOPTIONS", "--psoptions=PSOPTIONS",
+ "Options for pdftops; default -level2,",
+ "don't include -eps or page number options;",
+ "these will be generated by the program" ) do |opt|
+ $settings.ps_options = opt
+ end
+
+ opts.on( "-p PAGENUMBER", "--pagenumber=PAGENUMBER",
+ "Page to be converted or selected", Integer ) do |opt|
+ $options.page = opt
+ end
+
+ opts.on( "-b", "--bbox", "--BoundingBox",
+ "Compute tight boundingbox" ) do |opt|
+ $options.bbox = true
+ end
+
+ opts.on( "-g", "--gray", "--grey",
+ "Convert to grayscale;",
+ "success not guaranteed" ) do |opt|
+ $options.gray = true
+ end
+
+ opts.on( "-G", "--GRAY", "--GREY",
+ "Try harder to convert to grayscale;",
+ "success still not guaranteed" ) do |opt|
+ $options.gRAY = true
+ end
+
+ opts.on( "-i", "--info",
+ "Info: display detected filetype" ) do |opt|
+ $options.info = true
+ end
+
+ opts.on( "-s",
+ "Save (some) settings" ) do |opt|
+ save_settings = true
+ end
+
+ opts.on( "-d", "Debug: don't remove temp files" ) do |opt|
+ $DEBUG = 1
+ end
+
+ opts.separator ""
+
+ opts.on_tail("-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+
+ # opts.on_tail("--version", "Show version") do
+ # puts OptionParser::Version.join('.')
+ # exit
+ # end
+end # opts
+
+# parse options destructively
+
+opts.parse!( ARGV )
+
+$options.page = 1 if $options.bbox and not $options.page
+
+$settings.write_settings if save_settings
+
+if ARGV.length < 1
+ puts opts if not save_settings # help output
+ # puts "Settings:"
+ # $settings.each_key do |k|
+ # puts "key #{k} value #{$settings[k].val}"
+ # end
+ abort
+elsif $options.info
+ puts "File type of #{ARGV[0]} is #{PsPdf.new( 'file' => ARGV[0] ).type}"
+ exit
+elsif ARGV.length > 1 and
+ File.expand_path( ARGV[0] ) == File.expand_path( ARGV[1] )
+ abort " Input and output files should be different."
+else
+ infile = ARGV[0]
+ abort( infile + " not found or empty" ) unless test( ?s, infile )
+end
+
+# done with options
+
+########################################
+
+source = PsPdf.new( 'file' => infile )
+
+# We aren't finicky about the extension of the input file,
+# but still want to check whether pdf is pdf.
+
+case source.type
+when 'eps', 'epsPreview', 'ps'
+ abort "Wrong extension; input is not in pdf format" \
+ if infile =~ /\.pdf$/i
+when 'pdf'
+ abort "Wrong extension; input is in pdf format" \
+ if infile !~ /\.pdf$/i
+else
+ abort "Invalid input file type; not [e]ps or pdf" \
+ if source.type == 'other'
+end # case source.type
+
+# find or construct output file name
+if ARGV.length > 1
+ outfile = ARGV[1]
+else
+ case source.type
+ when 'eps', 'epsPreview', 'ps'
+ outfile = infile.sub( /\.[^.]+$/, '.pdf' )
+ when 'pdf'
+ outfile = infile.sub( /\.[^.]+$/, '.eps' )
+ end # case
+end # ifthenelse ARGV.length
+outfile = File.expand_path( outfile )
+
+$options.type = case outfile
+when /\.pdf$/i
+ "pdf"
+when /\.ps$/i
+ "ps"
+when /\.eps$/i
+ "eps"
+else
+ nil
+end # case outfile
+
+abort "Unknown or unsupported output file extension" \
+ unless $options.type
+abort "Output format not supported without xpdf utilities" \
+ if outfile == 'ps' and not $settings.pstopdf_prog
+
+pp = PsPdf.new( 'file' => infile )
+
+begin # inner rescue block
+ pp = pp.any_to_any( $options )
+rescue EPCallError => exc
+ mess =
+ "Wrong method call or conversion not supported or wrong page number" +
+ $/ + exc.message + $/ + exc.backtrace.join( $/ )
+ write_log( mess )
+ puts mess
+ fail StandardError
+rescue EPBBError => exc
+ mess = "Boundingbox problem" + $/ +
+ exc.message + $/ + exc.backtrace.join( $/ )
+ write_log( mess )
+ puts mess
+ fail StandardError
+rescue EPSystemError => exc
+ mess = "Problem with system call" + $/ +
+ exc.message + $/ + exc.backtrace.join( $/ )
+ write_log( mess )
+ puts mess
+ fail StandardError
+end # rescue block
+
+ccp( pp.path, outfile )
+
+cleantemp unless $DEBUG == 1
+
+rescue => detail
+write_log detail.backtrace.join( $/ )
+puts detail.backtrace.join( $/ )
+exit
+end # outer rescue block
+
+end # if called as main program
+__END__
diff --git a/Master/texmf-dist/scripts/epspdf/epspdfboot.rb b/Master/texmf-dist/scripts/epspdf/epspdfboot.rb
new file mode 100644
index 00000000000..bdcebb77b26
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/epspdfboot.rb
@@ -0,0 +1,40 @@
+# Manipulate library path and maybe search path.
+
+# Problem to be solved:
+
+# We don't want to require Windows users to install Ruby and Tcl/Tk so
+# we offer a package with a built-in Ruby subset. This subset is
+# slightly modified from the subset created by rubyscript2exe
+# (http://www.erikveen.dds.nl/rubyscript2exe/).
+
+# The rubyscript2exe directory structure is simplified compared to the
+# original Ruby directory structure, so we need to modify the library
+# search path and also the general search path.
+
+# Assumptions:
+
+# - The script itself is at the root or in a toplevel subdirectory.
+# - If an rbconfig.rb is found at the root or in
+# a toplevel directory then there is a Ruby subset.
+# If that is the Ruby which is used
+# then it needs help to find its files.
+
+dir = File.expand_path( File.dirname( __FILE__ ) )
+parent = File.dirname( dir )
+parent_sl = parent.dup + ( parent =~ /[\\\/]$/ ? '' : '/' )
+dir_sl = dir.dup + ( parent =~ /[\\\/]$/ ? '' : '/' )
+spath = ENV['PATH'].dup
+
+if test( ?e, parent_sl + 'lib/rbconfig.rb' )
+ root_sl = parent_sl
+elsif test( ?e, dir_sl + 'lib/rbconfig.rb' )
+ root_sl = dir_sl
+else
+ root_sl = nil
+end
+
+if root_sl
+ $: << "#{root_sl}lib"
+end
+
+$:.unshift( dir )
diff --git a/Master/texmf-dist/scripts/epspdf/epspdfrc.rb b/Master/texmf-dist/scripts/epspdf/epspdfrc.rb
new file mode 100755
index 00000000000..f75d65b5339
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/epspdfrc.rb
@@ -0,0 +1,507 @@
+# epspdf conversion utility, configuration module
+
+#####
+# Copyright (C) 2006, 2008 Siep Kroonenberg
+# n dot s dot kroonenberg at rug 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!
+#####
+
+# This version has been adapted for TeX Live. The adaptations only
+# concern Windows. If TL is set then pdftops and
+# ghostscript are set to the version included in TeXLive, settings are
+# stored in HKCU\software\epspdftl (rather than epspdf) and the logfile
+# is %USERPROFILE%\epspdftl.log, rather than epspdf.log.
+# A future version should offer a dynamic way of settingt his option.
+
+# Settings are stored in a settings hash, with some methods added
+# for less unwieldy notation.
+#
+# In phase 1, some programs (converters, viewers) are autodetected.
+# In phase 2, user preferences are read from the epspdf configuration.
+#
+# For unix/osx, configuration info is stored in $HOME/.epspdfrc.
+# For w32, configuration info is stored in HKCU\software\epspdf.
+#
+# The UIs for settings are not here; they are:
+# 1. the epspdf.rb command-line if epspdf.rb is run as
+# a stand-alone program, and
+# 2. a configuration screen in epspdftk.rb.
+
+require 'rbconfig'
+include Config
+
+# for notational convenience, we keep ARCH separate
+
+ARCH = case CONFIG['arch']
+when /win32|mingw/
+ 'w32'
+when /darwin/
+ 'osx'
+else
+ 'unix'
+end
+
+TL = ENV.has_key?('TLROOT')
+if ARCH == 'w32'
+ require 'dl/win32'
+ require 'win32/registry'
+ include Win32 # this lets us abbreviate Win32::Registry to Registry
+ ShortPName = Win32API.new(
+ 'kernel32', 'GetShortPathName', ['P','P','N'], 'N' )
+ TLROOT = ENV['TLROOT']
+end
+
+# for Windows, use short names within backquotes
+# to avoid quotes within backquotes
+
+def short_name( fn )
+ return fn if ARCH != 'w32'
+ fn.gsub!( /\\/, '/' )
+ buffer = ' ' * 260
+ length = ShortPName.call( fn, buffer, buffer.size )
+ return ( length > 0 ) ? buffer.slice(0..length-1) : fn
+end
+
+# system-dependent location of logfile
+
+LOGFILE = case ARCH
+when 'w32'
+ TL ? "#{ENV['USERPROFILE']}\\epspdftl.log" :
+ "#{ENV['USERPROFILE']}\\epspdf.log"
+else
+ "#{ENV['HOME']}//epspdf.log"
+end
+LOGFILE_OLD = LOGFILE + '.old'
+
+# system-dependent locations of saved settings
+
+RC_FILE = "#{ENV['HOME']}/.epspdfrc"
+REG_K = TL ? 'epspdftl' : 'epspdf'
+REG_KEY = TL ? 'SOFTWARE\\epspdftl' : 'SOFTWARE\\epspdf'
+
+# hash of saved settings from rc file or registry
+
+$rc_settings = {}
+
+# find open command for extension (w32 only)
+
+def open_with( ext )
+ ext = '' unless ext
+ answer = nil
+ if ARCH == 'w32'
+ begin # open cl = HKCR\ext safely
+ Registry::HKEY_CLASSES_ROOT.open(
+ (ext =~ /^\./ ? ext : '.'+ext) ) do |cl|
+ begin # read ftype safely
+ ftype = cl.read_s( nil )
+ begin # open cmd = HKCR\ftype\shell... safely
+ Registry::HKEY_CLASSES_ROOT.open(
+ ftype + '\\shell\\open\\command' ) do |cmd|
+ begin # read answer safely
+ answer = cmd.read_s( nil )
+ if answer =~ /^"([^"]+)".*/
+ answer = $1
+ elsif answer =~ /^([^ ]+) .*/
+ answer = $1
+ end
+ answer = nil unless answer and test( ?s, answer )
+ rescue # answer not read
+ end # read answer safely
+ end # HKCR\ftype\shell... do |cmd|
+ rescue # cmd not opened
+ end # open cmd = HKCR\ftype\shell... safely
+ rescue # ftype not read
+ end # read ftype safely
+ end # HKCR\ext do |cl|
+ rescue # cl not opened
+ end # open cl = HKCR\ext safely
+ end # w32
+ answer
+end
+
+# test whether a string is a valid program call
+# 'which' also works with explicit paths.
+# 'which' returns an error for non-executable files.
+
+def is_a_program( s )
+ case ARCH
+ when 'w32'
+ nil
+ else
+ ( system "which #{s} >/dev/null" ) ? 1 : nil
+ end
+end # is_a_program
+
+# Parse version info from a prog from the xpdf suite.
+# This is used as validity test.
+
+def xpdf_version( arg )
+ retval = nil
+ arg = short_name( arg ) if ARCH == 'w32'
+ version_output = `#{arg} -v 2>&1`
+ if version_output and not version_output.empty?
+ vo = version_output.split(/\r\n?|\n/)
+ vo.grep(/version/i) { |a|
+ a =~ /^\D*(\d+\.\w*)\b/
+ retval = $& # matched part; nil if no match
+ }
+ end
+ retval
+end
+
+class Setting
+ attr_reader :val, :type, :comment
+ attr_writer :val
+
+ def initialize( v, t, c )
+ @val = v
+ @type = t
+ @comment = c
+ end
+end
+
+PDF_TARGETS = [ 'default', 'printer', 'prepress', 'screen', 'ebook' ]
+PDF_VERSIONS = [ '1.2', '1.3', '1.4', 'default' ]
+
+$settings = {
+ # converters
+ 'gs_prog' => Setting.new( nil, 'auto', nil ),
+ 'gs_version' => Setting.new( nil, 'auto', nil ),
+ 'pdftops_prog' => Setting.new( nil, ARCH=='w32' ? 'config' : 'auto', nil ),
+ # viewers
+ 'pdf_viewers' => Setting.new( nil, ARCH=='unix' ? 'auto' : nil, nil ),
+ 'ps_viewers' => Setting.new( nil, ARCH=='unix' ? 'auto' : nil, nil ),
+ 'pdf_viewer' => Setting.new( nil, ARCH=='unix' ? 'config' : 'auto', nil ),
+ 'ps_viewer' => Setting.new( nil, ARCH=='unix' ? 'config' : 'auto', nil ),
+ # conversion options
+ 'ignore_pdftops' => Setting.new( '0', 'config',
+ 'Ignore pdftops even if available; 1=yes, empty or 0=no(default)' ),
+ 'pdf_target' => Setting.new( 'prepress', 'config',
+ 'Target: screen, ebook, print, prepress (default) or default' ),
+ 'pdf_version' => Setting.new( '1.4', 'config',
+ 'Pdf version: e.g 1.4 or 1.2 (fewer features, more compatibility)' +
+ ' or unspecified' ),
+ 'pdf_custom' => Setting.new( '', 'config',
+ 'additional options for [e]ps to pdf conversion' ),
+ 'ps_options' => Setting.new( '-level2', 'config',
+ 'options for pdftops; default -level2' ),
+ 'bb_spread' => Setting.new( '1', 'config',
+ 'margin to be added to computed boundingbox; default 1' )
+}
+
+class << $settings
+
+ # create shortcut methods $settings.x and $settings.x=
+ # for reading and writing hash elements.
+
+ $settings.each_key { |k|
+ eval "def #{k} ; self[\'#{k}\'].val ; end"
+ eval "def #{k}=(v) ; self[\'#{k}\'].val=v ; end"
+ }
+
+ # use_pdftops is a boolean counterpart and inverse
+ # of the stored string attribute ignore_pdftops
+
+ def use_pdftops
+ case self['ignore_pdftops'].val
+ when /t(rue)?|y(es)?|1/i
+ false
+ when /f(alse)?|n(o)?|0/i
+ true
+ when 1
+ false
+ when 0
+ true
+ else
+ true
+ end
+ end
+
+ # we want use_pdftops = nil = default = true but
+ # we also want use_pdftops = nil <=> use_pdftops = false
+ # we give the second one priority.
+
+ def use_pdftops=( x )
+ self['ignore_pdftops'].val = case x
+ when /t(rue)?|y(es)?|1/i
+ '0'
+ when /f(alse)?|n(o)?|0/i
+ '1'
+ when 1
+ '0'
+ when 0
+ '1'
+ when false
+ '1'
+ when nil # try to avoid this assignment
+ '1'
+ when true
+ '0'
+ else
+ '1'
+ end
+ end
+
+ def accept_pdf_viewer( s )
+ if ARCH == 'unix'
+ if is_a_program( s )
+ self.pdf_viewer = s
+ self.pdf_viewers.unshift( s ) unless self.pdf_viewers.index( s )
+ return 1
+ else
+ return nil
+ end
+ else
+ return nil
+ end
+ end # accept_pdf_viewer
+
+ def accept_ps_viewer( s )
+ if ARCH == 'unix'
+ if is_a_program( s )
+ self.ps_viewer = s
+ self.pdf_viewer = s unless \
+ self.pdf_viewer and not self.pdf_viewer.empty?
+ self.ps_viewers.unshift( s ) unless self.ps_viewers.index( s )
+ self.pdf_viewers.push( s ) unless self.pdf_viewers.index( s )
+ return 1
+ else
+ return nil
+ end
+ else
+ return nil
+ end
+ end # accept_ps_viewer
+
+ # previously-configured settings into hash $rc_settings
+ # here no validity testing.
+ # we accept empty settings.
+
+ def read_settings
+ if ARCH == 'w32'
+ [Registry::HKEY_LOCAL_MACHINE,
+ Registry::HKEY_CURRENT_USER].each { |hk|
+ begin # open REG_KEY safely
+ hk.open( REG_KEY ) do |ep|
+ ep.each_value do |key, type, val|
+ $rc_settings[ key ] = val \
+ if $settings[ key ] and $settings[ key ].type == 'config'
+ end # do |key, type, val|
+ end # do |ep|
+ rescue
+ end # open REG_KEY safely
+ } # each |hk|
+ else
+ if test( ?s, RC_FILE )
+ lines = File.read( RC_FILE ).split( /\r\n?|\n/ )
+ lines.each do |l|
+ l = l.sub( /#.*$/, '' ) # remove trailing comments
+ if l =~ /^\s*(\S+)\s*=\s*(\S(.*\S)?)\s*$/
+ key, val = $1, $2
+ $rc_settings[ key ] = val \
+ if $settings[ key ] and $settings[ key ].type == 'config'
+ elsif l =~ /^\s*(\S+)\s*=\s*$/
+ key, val = $1, ''
+ $rc_settings[ key ] = '' \
+ if $settings[ key ] and $settings[ key ].type == 'config'
+ end # if l =~
+ end # do |l|
+ end # if ?s
+ end # if ARCH
+ end # def
+
+ def write_settings
+ if ARCH == 'w32'
+ begin
+ Registry::HKEY_CURRENT_USER.create(
+ 'SOFTWARE', desired = Registry::KEY_ALL_ACCESS) do |reg|
+ reg.create( REG_K, desired = Registry::KEY_ALL_ACCESS ) do |rk|
+ $settings.each do |k, s|
+ key = String.new( k )
+ st = Setting.new( s.val, s.type, s.comment )
+ # this copying looks kind of silly,
+ # but prevents a 'modifying frozen object' error.
+ if st.type == 'config'
+ vl = st.val ? st.val : ''
+ rk.write_s( key, vl )
+ end
+ end # do each
+ end # do |rk|
+ end # do |reg|
+ rescue => er # no real error handling for now
+ puts $!
+ puts er.backtrace.join( $/ )
+ puts 'Settings will be lost at next run'
+ end
+ else
+ File.open( RC_FILE, 'w' ) do |f|
+ f.write( "# This file will be overwritten by epspdf_tk[.rb]" \
+ + $/ )
+ $settings.each do |key, st|
+ if st.type == 'config'
+ f.write( $/ )
+ f.write( '# ' + st.comment + $/ ) if st.comment
+ # STDERR.puts "write setting for " + key
+ # STDERR.flush
+ f.write( key + " = " + ( st.val ? st.val : '' ) + $/ )
+ end
+ end # do |key, st|
+ end # do |f|
+ end # if
+ end # def
+
+ def get_settings
+
+ # phase 1: autodetect
+ # w32: gs_prog, gs_version, pdf_viewer, ps_viewer
+ # gs: search registry settings; the highest version wins.
+ # viewers: check file type registry entry for valid open commands
+ # w32/TL: gs_prog, gs_version, pdf_viewer, ps_viewer
+ # gs: from TL
+ # viewers: check file type registry entry for valid open commands
+
+ # unix: gs_prog, gs_version, pdftops_prog, pdf_viewers, ps_viewers
+ # osx: gs_prog, gs_version, pdftops_prog
+ # viewers: use Preview; don't check anything
+ # unix/osx are done together, except that for osx
+ # the viewers code is skipped.
+
+ case ARCH
+ when 'w32'
+ if !TL then # else use built-in texlive versions
+ # check registry for latest valid version of Ghostscript
+ # tentative values; HKLM and HKCU may provide different values
+ try_gs_version = nil
+ try_gs_prog = nil
+ [Registry::HKEY_LOCAL_MACHINE,
+ Registry::HKEY_CURRENT_USER].each { |hk|
+ puts hk.to_s if $DEBUG
+ hk.open('SOFTWARE') { |sof|
+ puts sof.to_s if $DEBUG
+ sof.each_key { |key, wtime|
+ puts key.to_s if $DEBUG
+ if key =~ /ghostscript/i
+ gh = hk.open('SOFTWARE\\' + key)
+ gh.each_key { |skey, wtime|
+ if skey =~ /^\d+\.\d+$/ # version number
+ if skey.to_f > try_gs_version.to_f
+ this_gs = hk.open('SOFTWARE\\' + key + '\\' + skey)
+ this_gs_prog = this_gs['GS_DLL'].sub(
+ /([\\\/])([^\\\/]+)$/, '\1gswin32c.exe')
+ if test(?e,this_gs_prog)
+ try_gs_version = skey
+ try_gs_prog = this_gs_prog
+ end # if ?e (exist)
+ end # skey.to_f > try_gs_version.to_f
+ end # if skey =~ /^\d+\.\d+$/
+ } # gh.each_key
+ end # if key =~ /ghostscript/i
+ } # sof.each_key
+ } # hk.open |sof|
+ } # [HKCU, HKLM].each
+ self.gs_version = try_gs_version
+ self.gs_prog = try_gs_prog
+ end
+
+ # viewers
+ self.pdf_viewer = open_with( 'pdf' )
+ self.ps_viewer = open_with( 'ps' )
+
+ when /unix|osx/
+ # gs
+ answer = `gs --version`
+ if answer and not answer.empty?
+ self.gs_prog = 'gs'
+ self.gs_version = answer.chomp
+ else
+ self.gs_prog = nil
+ self.gs_version = nil
+ end
+
+ # pdftops
+ self.pdftops_prog = xpdf_version( 'pdftops' ) ? 'pdftops' : nil
+
+ # viewers
+ vw = %w{ gpdf xpdf acroread evince ggv gv kghostview }
+ vw.delete_if { |x| not is_a_program( x ) }
+ vw = nil if vw.empty?
+ self.pdf_viewers = vw
+ self.pdf_viewer = vw ? vw[0] : nil
+ if vw
+ vw2 = vw.dup # copy the elements rather than the array ref
+ [ 'gpdf', 'xpdf', 'acroread' ].each { |pg|
+ vw2.delete( pg ) if vw2.index( pg )
+ }
+ vw2 = nil if vw2.empty?
+ self.ps_viewers = vw2
+ self.ps_viewer = vw2 ? vw2[ 0 ] : nil
+ end # if vw
+ end # case
+
+ # built-in defaults already set during initialization of $settings
+
+ # phase 2: pre-existing configuration
+ # w32: pdftops_prog
+ # unix (not osx): pdf_viewers
+
+ read_settings
+
+ case ARCH
+ when 'w32'
+ $rc_settings[ 'pdftops_prog' ] = (
+ TL ? (TLROOT.gsub( /\//, '\\' ) + '\\bin\\win32\\pdftops.exe') :
+ "z:\\aps\\staff\\ber\\miktex\\xpdf\\pdftops.exe" ) unless
+ ( $rc_settings[ 'pdftops_prog' ] and
+ ! $rc_settings[ 'pdftops_prog' ].empty? )
+ ptop = $rc_settings[ 'pdftops_prog' ]
+ if xpdf_version( ptop ) then
+ self.pdftops_prog = ptop
+ end
+ if TL
+ self.gs_prog = TLROOT.gsub( /\//, '\\' ) +
+ '\\tlpkg\\tlgs\\bin\\gswin32c.exe'
+ self.gs_version =
+ ( `#{short_name( self.gs_prog )} --version` ).chomp
+ end
+ when 'unix'
+ if v = $rc_settings[ 'pdf_viewer' ] and is_a_program( v )
+ self.pdf_viewer = v
+ self.pdf_viewers.unshift( v ) unless self.pdf_viewers.index( v )
+ end # if
+ if v = $rc_settings[ 'ps_viewer' ] and is_a_program( v )
+ self.ps_viewer = v
+ self.ps_viewers.unshift( v ) unless self.ps_viewers.index( v )
+ end # if
+ if self.ps_viewers and not self.pdf_viewers
+ self.pdf_viewers = self.ps_viewers.dup
+ self.pdf_viewer = self.ps_viewer
+ end # if
+ end # case ARCH
+
+ # no validity checks for pdf- and ps output options.
+ # reminder: self.s shortcut for self['s'].val
+ [ 'pdf_target', 'pdf_version', 'pdf_custom', 'ps_options' ].each { |p|
+ self[ p ].val = $rc_settings[ p ] if $rc_settings[ p ]
+ }
+ if $rc_settings.has_key?( 'ignore_pdftops' )
+ $settings.ignore_pdftops = case $rc_settings[ 'ignore_pdftops' ]
+ when /1|yes|true|y|t/i
+ '1'
+ when /0|no|false|n|f/i
+ '0'
+ end
+ else
+ $settings.ignore_pdftops = '0'
+ end
+
+ self.bb_spread = $rc_settings[ 'bb_spread' ] if
+ $rc_settings.has_key?( 'bb_spread' ) and
+ $rc_settings[ 'bb_spread' ] =~ /^[+-]?[\d]+$/
+
+ end # get_settings
+
+end # class << $settings
+
+$settings.get_settings
diff --git a/Master/texmf-dist/scripts/epspdf/epspdftk b/Master/texmf-dist/scripts/epspdf/epspdftk
new file mode 100755
index 00000000000..6d3465a8c4b
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/epspdftk
@@ -0,0 +1,7 @@
+#!/bin/sh
+script=`kpsewhich -format=texmfscripts epspdftk.rb`
+if test "x$1" = "x"; then
+ "$script" $HOME
+else
+ "$script" $*
+fi \ No newline at end of file
diff --git a/Master/texmf-dist/scripts/epspdf/epspdftk.rb b/Master/texmf-dist/scripts/epspdf/epspdftk.rb
new file mode 100755
index 00000000000..c09509c0c8f
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/epspdftk.rb
@@ -0,0 +1,958 @@
+#!/usr/bin/env ruby
+
+# epspdf conversion utility, GUI frontend
+
+#####
+# Copyright (C) 2006 Siep Kroonenberg
+# n dot s dot kroonenberg at rug 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!
+#####
+
+#$DEBUG=1
+
+# epspdfboot.rb: modify loadpath in a way which works both
+# with a full Ruby and with a custom subset ruby
+
+require __FILE__.sub( /epspdftk\.rbw?/, 'epspdfboot.rb' )
+
+##########################################
+
+# load conversion engine. epspdf also loads epspdfrc,
+# which detects platform and available converters.
+
+require 'epspdf'
+
+$VERBOSE = nil
+
+#require 'pp'
+
+if $DEBUG
+ puts ARCH
+ puts Config::CONFIG['arch']
+ puts "Ghostscript command #{$settings.gs_prog}" \
+ " and version #{$settings.gs_version}"
+ puts "Pdftops command #{$settings.pdftops_prog}" \
+ " and version #{$settings.pdftops_version}"
+end
+
+def viewable( p )
+ return nil unless p && p.path
+ return nil if p.type == 'other'
+ case ARCH
+ when 'osx'
+ return true
+ when 'w32'
+ return nil if p.type == 'epsPreview'
+ return open_with( File.extname( p.path ) )
+ else
+ return nil if p.type == 'epsPreview'
+ return true if p.type =~ /e?ps/ and ( not $settings.ps_viewer.empty? )
+ return true if p.type == 'pdf' and ( not $settings.pdf_viewer.empty? )
+ end # case ARCH
+ return nil
+end # viewable
+
+####################################
+
+# Tk and custom widgets
+
+require 'tk'
+require 'tkscrollbox'
+
+# For event field strings, which codify event details, see
+# the bind man page from the tk man pages (Ubuntu: tk8.x-doc package)
+
+####################################
+
+# combobox adapted from Takaaki Tateishi <ttate / jaist.ac.jp>
+# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/18167
+
+# literal content of an xbm file, created e.g. with the GIMP
+
+$DOWN_ARROW_IMG = <<STR
+#define dwnarrow.icn_width 15
+#define dwnarrow.icn_height 10
+static unsigned char dwnarrow.icn_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, 0x03,
+ 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 };
+STR
+
+class TkACombobox < TkFrame
+
+ include TkComposite
+
+ attr_reader :scrollbox, :tframe
+ attr_accessor :entries, :is_valid
+
+ def initialize_composite( params = {} )
+
+ @entries = []
+ @is_valid = proc{ |s| true }
+ @entry = nil
+
+ # entry widget plus arrow for drop-down listbox
+
+ @entry = TkEntry.new( @frame,
+ 'width' => 30, 'bd' => 1, 'highlightthickness' => 0 )
+ image = TkBitmapImage.new( 'data' => $DOWN_ARROW_IMG )
+ @bt_arrow = TkButton.new( @frame, 'bd' => 1, 'image' => image,
+ 'highlightthickness' => 0, 'command' => proc{ cmd_scrollbox } )
+ @entry.pack( 'side' => 'left', 'fill' => 'x', 'expand' => 'yes' )
+ @bt_arrow.pack( 'side' => 'right' )
+
+ # drop-down listbox
+
+ @tframe = TkToplevel.new( @frame )
+ @tframe.overrideredirect( 1 ) # no window manager decorations
+ @tframe.transient
+ @tframe.withdraw
+ @scrollbox = TkScrollbox.new( @tframe, 'width' => 30, 'border' => 1 )
+ @scrollbox.pack( 'fill' => 'both', 'expand' => 'yes' )
+ @scrollbox.bind( 'Any-Leave', proc{ hide_scrollbox } )
+ # "%y", "%P": event field string; see bind manual from tk8.x-docs
+ @scrollbox.bind( 'Double-1', proc{ |y| select_this( y ) }, "%y" )
+ @scrollbox.bind( 'Return', proc{ |y| select_this( y ) }, "%y" )
+
+ @entry.bind( 'FocusOut', proc{ entry_proc } )
+ @entry.bind( 'Return', proc{ entry_proc } )
+ @entry.bind( 'Double-1', proc{ entry_proc } )
+
+ delegate( :width, @entry, @scrollbox )
+
+ configure params if params
+
+ end # initialize_composite
+
+ def cmd_scrollbox
+ case @tframe.state
+ when "withdrawn"
+ show_scrollbox( @entries )
+ else
+ hide_scrollbox
+ end
+ end
+
+ def show_scrollbox( entries = nil )
+ width = TkWinfo.width( @frame )
+ height = 5 * TkWinfo.height( @entry )
+ rootx = TkWinfo.rootx( @frame )
+ rooty = TkWinfo.rooty( @frame ).to_i + TkWinfo.height( @frame ).to_i
+ geometry = "#{width}x#{height}+#{rootx}+#{rooty}"
+ @tframe.geometry( geometry )
+ @tframe.deiconify
+ entries.each{ |entry| @scrollbox.insert( "end", entry ) } if entries
+ end
+
+ def hide_scrollbox
+ @tframe.withdraw
+ @scrollbox.delete( 0, "end" )
+ @entry.focus
+ end
+
+ def select_this( y )
+ i = @scrollbox.nearest( y )
+ s = @scrollbox.get( i )
+ @entry.delete( 0, 'end' )
+ @entry.insert( 0, s )
+ @is_valid.call( s ) # activates side effects
+ hide_scrollbox
+ end
+
+ def entry_proc
+ #puts @entry.value
+ if @is_valid.call( @entry.value )
+ @entries.unshift( @entry.value ).sort! \
+ unless @entries.index( @entry.value )
+ @tframe.withdraw unless @tframe.state == 'withdrawn'
+ else
+ Tk.bell
+ @entry.focus( true )
+ end
+ end
+
+ def get_entry
+ @entry.value
+ end
+
+ def set_entry( s )
+ @entry.delete( 0, 'end' )
+ @entry.insert( 'end', s ) if s
+ end
+
+end # TkACombobox
+
+####################################
+
+# checkboxes and radioboxes with built-in variables.
+# use string- rather than symbol keys when creating these widgets.
+
+def zero_one( v ) # For Tcl/Tk, everything is a string
+ case v
+ when /t(rue)?|y(es)?|1/i
+ '1'
+ when /f(alse)?|n(o)?|0/i
+ '0'
+ when 1
+ '1'
+ when 0
+ '0'
+ else
+ nil
+ end
+end
+
+class TkACheck < TkFrame
+
+ include TkComposite
+
+ def initialize_composite( params = {} )
+
+ Kernel::fail StandardError, "TkACheck doesn\'t allow \'variable\' key" \
+ if params.has_key?( 'variable' )
+
+ v = params.delete( 'value' )
+ @value = TkVariable.new( zero_one( v ) )
+
+ @ck = TkCheckButton.new( @frame, :variable => @value \
+ ).pack( :padx => 0, :pady => 0, :ipadx => 0, :ipady => 0 )
+
+ delegate( 'DEFAULT', @ck )
+
+ configure params unless params.empty?
+
+ end # initialize_composite
+
+ def value
+ @value.value
+ end
+
+ def value=( v )
+ @value.value = zero_one( v )
+ end
+
+end # TkACheck
+
+class TkRadios < TkFrame
+
+ include TkComposite
+
+ def initialize_composite( params = {} )
+
+ Kernel::fail StandardError, "TkRadios doesn\'t allow \'variable\' key" \
+ if params.has_key?( 'variable' )
+
+ @value = TkVariable.new( params.delete( 'value' ) )
+ @values = params.delete( 'values' )
+ @packing = params.delete( 'packing' )
+ @buttons = []
+
+ if ( @values.kind_of? Hash )
+ @values.each_key { |k|
+ @buttons << TkRadioButton.new( @frame,
+ :text => @values[k] || k,
+ :variable => @value,
+ :value => k
+ ).pack( @packing )
+ }
+ elsif ( @values.kind_of? Array )
+ @values.each { |k|
+ @buttons << TkRadioButton.new( @frame,
+ :text => k,
+ :variable => @value,
+ :value => k
+ ).pack( @packing )
+ }
+ else
+ Kernel::fail StandardError, "TkRadios requires a values array or hash"
+ end
+
+ delegate( 'DEFAULT', @frame )
+ delegate( 'command', *@buttons )
+ delegate( 'state', *@buttons )
+
+ configure( params ) unless params.empty?
+
+ end # initialize_composite
+
+ def value
+ @value.value
+ end
+
+ def value=( v )
+ @value.value = v
+ end
+
+ def get_button_state( v )
+ @buttons.each { |b|
+ if b.value == v
+ return b.cget( 'state' )
+ end
+ }
+ return nil
+ end
+
+ def set_button_state( v, s )
+ @buttons.each { |b|
+ if b.value == v
+ b.configure( 'state' => s )
+ @value.value = nil if s == 'disabled' and @value.value == v
+ end
+ }
+ end
+
+end # TkRadios
+
+$root = TkRoot.new { title "PostScript- and pdf conversions" }
+
+##################################
+
+# Initial tests: no gs => bail out; no viewers => warning
+
+if not $settings.gs_prog
+ Tk.messageBox(
+ :icon=>'error',
+ :type=>'ok',
+ :parent=>$root,
+ :message=>"No Ghostscript found; aborting..."
+ )
+ abort
+end
+
+if not $settings.pdf_viewer
+ Tk.messageBox( :icon=>'warning', :type=>'ok', :message =>
+ "No PostScript- or pdf viewer found" )
+elsif not $settings.ps_viewer
+ Tk.messageBox( :icon=>'warning', :type=>'ok', :message =>
+ "No PostScript viewer found" )
+end
+
+########################################
+
+$defaultDir = ENV['PWD']
+
+$defaultDir = ARGV[0] if ((ARGV.length > 0) and File.directory?(ARGV[0]))
+
+$source = PsPdf.new # currently loaded PsPdf object
+
+$tl_config = nil # configuration screen
+$tl_wait = nil # instead of a wait cursor or progress bar
+
+# widgets which are configured on the fly,
+# e.g. by ep_config, get a global name.
+# They don't really have to be declared early.
+
+# buttons and option widgets
+$view_button = nil
+$page_radio = nil
+$page_entry = nil
+$type_radio = nil
+$bbox_check = nil
+
+# widgets which display file info
+$source_dir_label = nil
+$source_name_label = nil
+$source_type_label = nil
+
+def 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_button.configure( :state =>
+ ( viewable( $source ) ? 'normal' : 'disabled' ) )
+ $save_button.configure( :state =>
+ ( ( $source.type and $source.type != 'other' ) ? \
+ 'normal' : 'disabled' ) )
+
+ $type_radio.set_button_state( 'ps', ( \
+ ( $settings.pdftops_prog and $settings.use_pdftops and
+ ( $source.type =~ /ps|pdf/ ) ) ? \
+ 'normal' : 'disabled' ) )
+
+ $page_radio.set_button_state( 'all', 'normal' )
+ $page_radio.set_button_state( 'single', 'normal' )
+ $page_radio.set_button_state( 'all', 'disabled' ) if \
+ $type_radio.value == 'eps'
+ $page_radio.set_button_state( 'single', 'disabled' ) if \
+ $type_radio.value == 'ps'
+
+ $page_entry.configure( :state => \
+ ( $page_radio.value == 'all' ? 'disabled' : 'normal' ) )
+
+ $bbox_check.configure( :state => \
+ ( $page_radio.value == 'single' or $source.type =~ /eps/ ) \
+ ? 'normal' : 'disabled' )
+
+end # set_widget_states
+
+def load_file( path )
+ $source = PsPdf.new( 'file' => path )
+ # write info to labels
+ $source_dir_label.configure( :text => File.dirname( path ) )
+ $source_name_label.configure( :text => File.basename( path ) )
+ $source_type_label.configure( :text => $source.type )
+ $source.pdf_pages if ( $source.type == 'pdf' )
+ $source_npages_label.configure( :text => (
+ case $source.type
+ when 'pdf' then ( $source.npages ).to_s
+ when 'eps' then '1'
+ else ''
+ end
+ ) )
+ $page_entry.delete( 0, 'end' )
+ $page_entry.insert( 'end', '1' )
+ set_widget_states
+end
+
+# ipadx and ipady seem ineffective for frames
+
+FRM_PACK = {
+ :side => 'top',
+ :fill => 'x',
+ :expand => 1,
+ :padx => 4,
+ :pady => 4,
+ :anchor => 'w'
+}
+
+##################################
+
+# configuration screen (toplevel widget)
+
+$tl_config = nil
+
+def ep_config
+
+ if not $tl_config
+
+ $tl_config = TkToplevel.new( $root, :title => 'Configure epspdf' )
+
+ ### viewers frame
+
+ if ARCH == 'unix'
+ TkFrame.new( $tl_config ) { |frm|
+ frm.borderwidth 2
+ frm.relief 'groove'
+ TkLabel.new( frm, :text => 'VIEWERS'
+ ).pack( :side => 'top', :padx => 4, :anchor => 'w' )
+ TkFrame.new( frm ) { |fm|
+ TkLabel.new( fm, :text => 'Pdf viewer' ).grid(
+ :in => fm, :row => 0, :column => 0,
+ :sticky => 'w', :padx => 2 )
+ $pdf_vw_combo = TkACombobox.new( fm, :width => 30 )
+ $pdf_vw_combo.is_valid = lambda { |s| is_a_program( s ) }
+ $pdf_vw_combo.grid( :in => fm, :row => 0, :column => 1,
+ :pady => 2, :padx => 2, :sticky => 'e' )
+
+ TkLabel.new( fm, :text => 'PostScript viewer' ).grid(
+ :in => fm, :row => 1, :column => 0,
+ :sticky => 'w', :padx => 2 )
+ $ps_vw_combo = TkACombobox.new( fm, :width => 30 )
+ $ps_vw_combo.is_valid = lambda { |s| is_a_program( s ) }
+ $ps_vw_combo.grid( :in => fm, :row => 1, :column => 1,
+ :pady => 2, :padx => 2, :sticky => 'e' )
+ }.pack( :side => 'top', :anchor => 'w', :fill => 'x' )
+ }.pack( FRM_PACK )
+ end # if unix
+
+ ### postscript frame
+
+ TkFrame.new( $tl_config ){ |frm|
+ frm.borderwidth 2
+ frm.relief 'groove'
+ TkLabel.new( frm, :text => 'POSTSCRIPT CONVERSION'
+ ).pack( :side => 'top', :padx => 4, :anchor => 'w' )
+
+ if ARCH == 'w32' # others: assume pdftops on search path
+ TkLabel.new( frm, :text => "Find pdftops"
+ ).pack( :side => 'top', :anchor => 'w', :padx => 4 )
+
+ TkFrame.new( frm ) { |fm|
+ $pdftops_entry = TkEntry.new( fm, :width => 20
+ ).pack( 'side'=>'left', :padx => 4 )
+ TkButton.new( fm,
+ :text => 'Browse ...',
+ :command => lambda {
+ tryprog = Tk.getOpenFile(
+ :filetypes => [ [ 'Programs', [ '.exe' ] ] ],
+ :parent => fm
+ )
+ if tryprog and ( not tryprog.empty? )
+ if not xpdf_version( tryprog )
+ Tk.messageBox(
+ :icon => 'warning',
+ :type => 'ok',
+ :parent => fm,
+ :message => "pdftops missing or dysfunctional"
+ )
+ else
+ $pdftops_entry.delete( 0, 'end' )
+ $pdftops_entry.insert( 'end', tryprog )
+ end # ifthenelse xpdf_version
+ end # ifthenelse tryprog
+ } # command lambda
+ ).pack( :side => 'left' ) # TkButton
+ }.pack( :side => 'top', :pady => 4, :anchor => 'w' )
+ end # ARCH == 'w32'
+
+ $pdftops_check = TkACheck.new( frm,
+ 'text' => "Use pdftops if available"
+ ).pack( :side => 'top', :pady => 4, :anchor => 'w' )
+
+ }.pack( FRM_PACK ) # TkFrame |frm|
+
+ ### pdf frame
+
+ TkFrame.new( $tl_config ){ |frm|
+ frm.borderwidth 2
+ frm.relief 'groove'
+ TkLabel.new( frm, :text => 'PDF CONVERSION'
+ ).pack( :side => 'top', :padx => 4, :anchor => 'w' )
+
+ # internal packing
+ pack_pdf = {
+ :side => 'left', :padx => 2, :pady => 4, :anchor => 'w' }
+
+ TkLabel.new( frm, :text => 'Intended use'
+ ).pack( :side => 'top', :padx => 2, :pady => 0, :anchor => 'w' )
+ $pdf_target_radio = TkRadios.new( frm,
+ 'values' => PDF_TARGETS, 'packing' => pack_pdf
+ ).pack( :side => 'top', :anchor => 'w' )
+
+ # dummy widget for extra vertical space
+ TkLabel.new( frm, :text => ' ' ).pack
+
+ TkLabel.new( frm, :text => 'Pdf version'
+ ).pack( :side => 'top', :padx => 2, :pady => 0, :anchor => 'w' )
+
+ $pdf_version_radio = TkRadios.new( frm,
+ 'values' => PDF_VERSIONS, 'packing' => pack_pdf
+ ).pack( :side => 'top', :anchor => 'w' )
+
+ TkLabel.new( frm, :text => ' ' ).pack
+
+ TkLabel.new( frm, :text => 'Custom Ghostscript/ps2pdf parameters'
+ ).pack( :side => 'top', :padx => 2, :pady => 0, :anchor => 'w' )
+
+ $pdf_custom_entry = TkEntry.new( frm, :width => 50
+ ).pack( :side => 'top', :padx => 2, :pady => 2, :anchor => 'w' )
+
+ }.pack( FRM_PACK )
+
+ ### button frame
+
+ TkFrame.new( $tl_config ) { |frm|
+
+ TkButton.new( frm,
+ :text => 'Cancel',
+ :command => lambda {
+ $tl_config.withdraw
+ $root.focus
+ }
+ ).pack( :padx => 4, :side => 'left', :anchor => 'e' )
+
+ TkButton.new( frm,
+ :text => 'Done',
+ :command => lambda {
+ # copy settings from widgets to $settings
+ case ARCH
+ when 'unix' # viewers
+ $settings.accept_ps_viewer( $ps_vw_combo.get_entry )
+ $settings.accept_pdf_viewer( $pdf_vw_combo.get_entry )
+ when 'w32'
+ $settings.pdftops_prog = $pdftops_entry.get
+ end
+ $settings.use_pdftops = ( not $pdftops_check.value == '0' )
+ $settings.pdf_target = $pdf_target_radio.value
+ $settings.pdf_version = $pdf_version_radio.value
+ $settings.pdf_custom = $pdf_custom_entry.get
+ $settings.write_settings
+ $tl_config.withdraw
+ $root.focus
+ set_widget_states
+ } # lambda
+ ).pack( :side => 'left', :anchor => 'e' )
+
+ }.pack( :side => 'bottom', :padx => 4, :pady => 4, :anchor => 'e' )
+ else
+ $tl_config.deiconify
+ end # ifthenelse $tl_config
+
+ # copy data from $settings to widgets
+ if ARCH == 'unix'
+ $pdf_vw_combo.set_entry( $settings.pdf_viewer )
+ $pdf_vw_combo.entries = $settings.pdf_viewers[0..-1] # deep copy
+ $ps_vw_combo.set_entry( $settings.ps_viewer )
+ $ps_vw_combo.entries = $settings.ps_viewers[0..-1]
+ elsif ARCH == 'w32'
+ $pdftops_entry.delete( 0, 'end' )
+ $pdftops_entry.insert( 'end', $settings.pdftops_prog ) \
+ if $settings.pdftops_prog and not $settings.pdftops_prog.empty?
+ end # ifthenelse ARCH
+ $pdftops_check.value = $settings.use_pdftops ? '1' : '0'
+ $pdf_target_radio.value = $settings.pdf_target
+ $pdf_version_radio.value = $settings.pdf_version
+ $pdf_custom_entry.delete( 0, 'end' )
+ $pdf_custom_entry.insert( 'end', $settings.pdf_custom ) \
+ if not $settings.pdf_custom.empty?
+
+end # def ep_config
+
+###################################
+
+### makeshift help
+
+$tl_help = nil
+
+def ep_help
+ if not $tl_help
+ $tl_help = TkToplevel.new( $root, :title => 'Epspdftk help' )
+ TkFrame.new( $tl_help ) { |tf|
+ txt = TkText.new( tf,
+ 'width' => 80,
+ 'height' => 25
+ ) { |t|
+ TkScrollbar.new( tf,
+ 'command' => lambda{ |*args| t.yview( *args ) } ) { |sc|
+ t.yscrollcommand( lambda{ |first, last| sc.set first, last } )
+ }.pack( 'side' => 'right', 'fill' => 'y' )
+ }.pack( 'expand' => 'yes', 'fill' => 'both' )
+
+ txt.delete( '1.0', 'end' )
+
+ # LF ok for Win32 and Mac? Otherwise, split and join with $/
+
+ File.open( File.dirname( __FILE__ ) + '/epspdf.help', 'r' ) { |f|
+ $s = f.read
+ }
+
+ txt.insert( '1.0', $s )
+
+ }.pack( 'side' => 'top', 'fill' => 'x' )
+
+ TkButton.new( $tl_help,
+ :text => 'Cancel',
+ :command => lambda{
+ $tl_help.withdraw
+ $root.focus
+ }
+ ).pack( 'anchor' => 'e', 'padx' => 4, 'pady' => 4 )
+ else
+ $tl_help.deiconify
+ end # ifthenelse $tl_help
+
+end # def ep_help
+
+### some buttons
+
+TkFrame.new( $root ) { |frm|
+
+ TkButton.new( frm,
+ :text => "Configure",
+ :command => lambda { ep_config }
+ ).pack( :side => 'left', :padx => 4, :pady => 4 )
+
+ TkButton.new( frm,
+ :text => 'Help',
+ :command => lambda { ep_help }
+ ).pack( :side => 'right', :padx => 4, :pady => 4)
+
+}.pack( :side => 'top', :fill => 'x' ) # frm
+
+########################################
+
+### status info
+
+TkFrame.new( $root ){ |frm|
+ frm.borderwidth 2
+ frm.relief 'groove'
+
+ ### status info
+
+ TkFrame.new( frm ) { |fm|
+ TkLabel.new( fm,
+ :justify => 'left',
+ :text => 'Directory'
+ ).grid( :in => fm, :sticky => 'w', :row => 0, :column => 0 )
+ $source_dir_label = TkLabel.new( fm,
+ :justify => 'left'
+ ).grid( :in => fm, :sticky => 'w', :row => 0, :column => 1 )
+
+ TkLabel.new( fm,
+ :justify => 'left',
+ :text => 'File'
+ ).grid( :in => fm, :sticky => 'w', :row => 1, :column => 0 )
+ $source_name_label = TkLabel.new( frm,
+ :justify => 'left'
+ ).grid( :in => fm, :sticky => 'w', :row => 1, :column => 1 )
+
+ TkLabel.new( fm,
+ :justify => 'left',
+ :text => 'Type'
+ ).grid( :in => fm, :sticky => 'w', :row => 2, :column => 0 )
+ $source_type_label = TkLabel.new( fm,
+ :justify => 'left'
+ ).grid( :in => fm, :sticky => 'w', :row => 2, :column => 1 )
+
+ TkLabel.new( fm,
+ :justify => 'left',
+ :text => 'Pages'
+ ).grid( :in => fm, :sticky => 'w', :row => 3, :column => 0 )
+ $source_npages_label = TkLabel.new( fm,
+ :justify => 'left'
+ ).grid( :in => fm, :sticky => 'w', :row => 3, :column => 1 )
+
+ TkGrid.columnconfigure( fm, 0, :weight => 1, :minsize => 60, :pad =>4 )
+ TkGrid.columnconfigure( fm, 1, :weight => 3, :minsize => 60, :pad =>4 )
+ }.pack( :side => 'top', :anchor => 'w', :padx => 4 )
+
+}.pack( FRM_PACK )
+
+########################################
+
+### conversion options: see also $options, defined in epspdf.rb
+
+TkFrame.new( $root ) { |frm|
+
+ TkFrame.new( frm ) { |fm| # grayscaling, output format
+ TkFrame.new( fm ) { |f| # grayscaling
+ TkLabel.new( f, 'text' => 'Grayscaling'
+ ).pack( :side => 'top', :anchor => 'w' )
+
+ gray_options = {
+ 'gRAY' => 'Try harder to grayscale',
+ 'gray' => 'Try to grayscale',
+ 'color' => 'No color conversion'
+ }
+
+ $gray_radio = TkRadios.new( f,
+ 'values' => gray_options,
+ 'value' => 'color',
+ 'packing' => { 'side' => 'top', 'anchor' => 'w' }
+ ).pack( :side => 'bottom', :anchor => 'w' ) # $gray_radio
+ }.pack( :side => 'left', :padx => 4 ) # f: grayscaling
+
+ TkFrame.new( fm ) { |f| # output format
+ TkLabel.new( f, 'text' => 'Output format'
+ ).pack( :side => 'top', :anchor => 'w' )
+
+ $type_radio = TkRadios.new( f,
+ 'values' => [ 'pdf', 'eps', 'ps' ],
+ 'value' => 'pdf',
+ 'command' => lambda {
+ case 'value'
+ when 'eps'
+ $page_radio.value = 'single'
+ when 'ps'
+ $page_radio.value = 'all'
+ $bbox_check.value = '0'
+ end
+ set_widget_states
+ },
+ 'packing' => { 'side' => 'top', 'anchor' => 'w' }
+ ).pack( :side => 'bottom', :anchor => 'w' )
+ }.pack( :side => 'right', :padx => 4 ) # f: output format
+ }.pack( :side => 'top', :anchor => 'n', :fill => 'x' )
+
+ # dummy widget for extra vertical space
+ TkLabel.new( frm, :text => ' ' ).pack
+
+ $bbox_check = TkACheck.new( frm,
+ 'text' => 'Compute tight boundingbox',
+ 'value' => '0',
+ 'command' => lambda {
+ $page_radio.value = 'single' if 'value' == '1'
+ set_widget_states
+ }
+ ).pack( :side => 'top', :padx => 4, :anchor => 'w' )
+
+ TkLabel.new( frm, :text => ' ' ).pack
+
+ TkFrame.new( frm ) { |fm|
+ page_options = { 'all' => 'Convert all pages', 'single' => 'Page:' }
+ $page_radio = TkRadios.new( fm,
+ 'values' => page_options,
+ 'packing' => { 'side' => 'left', 'padx' => 4 },
+ 'value' => 'single',
+ 'command' => lambda {
+ $bbox_check.value = '0' if 'value' == 'all'
+ set_widget_states
+ }
+ ).pack( 'side' => 'left', :anchor => 'nw' )
+
+ $page_entry = TkEntry.new( fm,
+ :validate => 'focus',
+ :vcmd => [ lambda { |s|
+ ( s =~ /\d+/ ) and
+ ( s.to_i >= 1 ) and
+ ( $source.type != 'pdf' or s.to_i <= $source.npages )
+ }, "%P" ],
+ :invcmd => lambda { Tk.bell; $page_entry.focus },
+ :width => 5
+ ).pack( :side => 'left' )
+ $page_entry.delete( 0, 'end' )
+ $page_entry.insert( 'end', '1' )
+ }.pack( 'side' => 'left', :anchor => 'nw' )
+}.pack( FRM_PACK )
+
+########################################
+
+### view | open | save | done buttons
+
+TkFrame.new( $root ) { |frm|
+
+ $view_button = TkButton.new( frm,
+ :text => 'View',
+ :state => 'disabled',
+ :command => lambda {
+ # start a viewer
+ if ARCH == 'w32'
+ shell = Win32API.new("shell32", "ShellExecute",
+ ['l','p','p','p','p','l'], 'l')
+ shell.Call(0, 'open', $source.path, 0, 0, 1)
+ # shell.Call(0, 'open', $source.path, 0, 0, SW_SHOWNORMAL)
+ elsif ARCH == 'osx'
+ fork { exec "open -a Preview \"#{$source.path}\"" }
+ elsif viewable( $source )
+ viewer =
+ $source.type == 'pdf' ? $settings.pdf_viewer : $settings.ps_viewer
+ fork { exec "\"#{viewer}\" \"#{$source.path}\"" }
+ end
+ }
+ ).pack( :side=>'left', :anchor => 'w', :padx=>4, :pady=>4 )
+
+ TkFrame.new( frm ) { |fm|
+ TkButton.new( fm,
+ :text => 'Open..',
+ :command => lambda {
+ path = Tk.getOpenFile(
+ :initialdir => $defaultDir,
+ :filetypes => [
+ ['All files', '*'],
+ ['EPS', ['.eps']],
+ ['PDF', ['.pdf']],
+ ['PostScript', ['.ps']]
+ ],
+ :parent => $root
+ )
+ if path and ( not path.empty? )
+ load_file( path )
+ $defaultDir = File.expand_path( File.dirname( path ) )
+ end
+ }
+ ).pack( :side=>'left', :padx=>4, :pady=>4 )
+
+ $save_button = TkButton.new( fm,
+ :text => "Convert and save..",
+ :command => lambda {
+ $options.type = $type_radio.value
+ $options.page = \
+ ( ( $page_radio.value == 'single' ) ? $page_entry.value : nil )
+ $options.gray = ($gray_radio.value and $gray_radio.value != 'color' )
+ $options.gRAY = ($gray_radio.value and $gray_radio.value == 'gRAY' )
+ $options.bbox = ( ( $bbox_check.value == '1' ) \
+ and ( $page_radio.value == 'single' ) )
+ filetypes = case $options.type
+ when 'pdf' then [ ['Pdf', ['.pdf'] ] ]
+ when 'ps' then [ ['PostScript', ['.ps'] ] ]
+ when 'eps' then [ ['EPS (Encapsulated PostScript', ['.eps'] ] ]
+ end
+ path = nil
+ path = Tk.getSaveFile(
+ :filetypes => filetypes,
+ :parent => $root,
+ :initialdir => File.dirname( $source.path ),
+ :initialfile => File.basename( $source.path, ".*" ) +
+ '.' + $options.type,
+ :defaultextension => '.' + $options.type
+ )
+ return unless path && ! path.empty?
+ $defaultDir = File.expand_path( File.dirname( path ) )
+
+ # waitbox
+ if not $tl_wait
+ $tl_wait = TkToplevel.new( $root,
+ 'title' => 'Converting...',
+ 'geometry' =>
+ '+' + ( TkWinfo.rootx( $root ).to_i + 100 ).to_s +
+ '+' + ( TkWinfo.rooty( $root ).to_i + 100 ).to_s,
+ 'borderwidth' => 2,
+ 'relief' => 'solid'
+ )
+ $cnv_label = TkLabel.new( $tl_wait,
+ 'text' => 'converting...'
+ ).pack( 'side' => 'top' )
+ $tl_wait.overrideredirect( 1 )
+ end
+ $cnv_label.configure(
+ 'text' => "Converting #{$source.path} to \n #{path} ..."
+ )
+ $tl_wait.update
+ $tl_wait.deiconify
+ $tl_wait.raise
+ begin
+ result = $source.any_to_any( $options )
+ rescue EPCallError => exc
+ mess = "Wrong method call or conversion not supported" +
+ " or wrong page number"
+ write_log( mess + $/ +
+ exc.message + $/ + exc.backtrace.join( $/ ) )
+ Tk.messageBox(
+ 'icon' => 'error',
+ 'type' => 'ok',
+ 'parent' => $root,
+ 'message' => "#{mess}\nSee #{LOGFILE} for details."
+ )
+ rescue EPBBError => exc
+ mess = "Boundingbox problem" + $/ +
+ exc.message
+ write_log( mess + $/ + exc.backtrace.join( $/ ) )
+ Tk.messageBox(
+ 'icon' => 'error',
+ 'type' => 'ok',
+ 'parent' => $root,
+ 'message' => "#{mess}\nSee #{LOGFILE} for details."
+ )
+ rescue EPSystemError => exc
+ mess = "Problem with system call" + $/ +
+ exc.message
+ write_log( mess + $/ + exc.backtrace.join( $/ ) )
+ Tk.messageBox(
+ 'icon' => 'error',
+ 'type' => 'ok',
+ 'parent' => $root,
+ 'message' => "#{mess}\nSee #{LOGFILE} for details."
+ )
+ ensure
+ $tl_wait.withdraw
+ end
+ if result
+ ccp( result.path, path )
+ load_file( path )
+ Tk.messageBox(
+ 'icon' => 'info',
+ 'type' => 'ok',
+ 'parent' => $root,
+ 'message' => "Conversion completed"
+ )
+ end
+ }
+ ).pack( :side=>'left', :padx=>0, :pady=>4 )
+
+ TkButton.new( fm,
+ :text => "Done",
+ :command => lambda { $root.destroy }
+ ).pack( :side => 'left', :padx => 4, :pady => 4 )
+ }.pack( :side => 'right', :anchor => 'e' )
+}.pack( :side => 'bottom', :fill => 'x' )
+
+set_widget_states
+
+$root.raise
+
+Tk.mainloop
+cleantemp
diff --git a/Master/texmf-dist/scripts/epspdf/makegray.pro b/Master/texmf-dist/scripts/epspdf/makegray.pro
new file mode 100644
index 00000000000..67dc5705839
--- /dev/null
+++ b/Master/texmf-dist/scripts/epspdf/makegray.pro
@@ -0,0 +1,29 @@
+%!
+% makegray.pro, PostScript header file for grayscale conversion.
+
+%%%%%
+% Siep Kroonenberg, n dot s dot kroonenberg at rug dot nl
+% Public domain.
+%%%%%
+
+% This version only handles some basic color models,
+% and doesn't handle image data at all.
+% A more robust version would be warmly welcomed.
+
+%%%%<< /ProcessColorModel /DeviceGray >> setpagedevice
+userdict begin
+% .11*B + .59*G + .3*R
+/setrgbcolor {
+ 0.11 mul exch 0.59 mul add exch 0.3 mul add
+ systemdict begin setgray end
+} def
+% 1.0 - min(1.0, .3*C + .59*M + .11*Y + B)
+/setcmykcolor {
+ exch 0.11 mul add
+ exch 0.59 mul add
+ exch 0.3 mul add
+ dup 1 gt {pop 1} if
+ 1 exch sub
+ systemdict begin setgray end
+} def
+end