summaryrefslogtreecommitdiff
path: root/Build/source/texk/texlive/linked_scripts/epspdf
diff options
context:
space:
mode:
authorPeter Breitenlohner <peb@mppmu.mpg.de>2010-12-27 13:09:41 +0000
committerPeter Breitenlohner <peb@mppmu.mpg.de>2010-12-27 13:09:41 +0000
commit76c75273b2dc011336a51be9a6fc7153e7708a41 (patch)
tree5bd3e13ff9ddb762274c905db752d660195e72c1 /Build/source/texk/texlive/linked_scripts/epspdf
parente4f0bc64d33b184afe1e8533d1e52c64f33c3e69 (diff)
adapt to epspdf update
git-svn-id: svn://tug.org/texlive/trunk@20869 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/texlive/linked_scripts/epspdf')
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/epspdf/epspdf3
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/epspdf/epspdf.rb1479
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/epspdf/epspdftk7
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl861
4 files changed, 2340 insertions, 10 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf
deleted file mode 100755
index 4492608f398..00000000000
--- a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-script=`kpsewhich -format=texmfscripts epspdf.rb`
-"$script" $*
diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb
new file mode 100755
index 00000000000..94da56c4c99
--- /dev/null
+++ b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb
@@ -0,0 +1,1479 @@
+#!/usr/bin/env ruby
+
+# epspdf conversion utility, main source
+
+#####
+# Copyright (C) 2006, 2008, 2009, 2010 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 chaining them together.
+
+# `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.rb
+# itself).
+
+# The use of exception handling makes it unnecessary to inspect return
+# values.
+
+###########################################
+
+# some initialization
+
+# add directory of this source to loadpath
+# WARNING
+# readlink apparently only works right from the directory of the symlink
+
+$SCRIPTDIR = File.dirname( File.expand_path( __FILE__ ))
+if RUBY_PLATFORM !~ /win32|mingw/ and File.symlink?( __FILE__ )
+ savedir = Dir.pwd
+ Dir.chdir( $SCRIPTDIR )
+ # puts File.readlink( __FILE__ )
+ $SCRIPTDIR = File.dirname( File.expand_path( File.readlink( __FILE__ )))
+ Dir.chdir( savedir )
+end
+$:.unshift( $SCRIPTDIR )
+# puts $:
+
+# turn on warnings
+#$VERBOSE = 1
+
+$from_gui = nil
+
+
+###########################################
+
+# 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
+
+# copying failed
+class EPCopyError < StandardError; end
+
+# system call failed
+class EPSystemError < StandardError; end
+
+###########################################
+
+# PostScript header file for grayscaling
+
+$GRAYHEAD = File.join( $SCRIPTDIR, "makegray.pro" )
+
+###########################################
+
+# handle auto-detected and saved settings
+
+#require 'epspdfrc'
+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
+ begin
+ 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 # do
+ rescue
+ fail EPCopyError, "Failure to copy to #{dest}"
+ end
+ 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" )
+ }
+ puts( s ) if $from_gui
+
+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; first standard, then hires
+
+#changes:
+#hires boundingboxes
+#numbers may start with a plus-sign. DSC manual is ambiguous,
+# PostScript manual allows it, but of course PS != DSC
+#left-right and lower-upper need not be in natural order
+
+BB_PAT = /^\s*%%BoundingBox:\s*([-+]?\d+)((\s+[-+]?\d+){3})\s*$/
+BB_END = /^\s*%%BoundingBox:\s*\(\s*atend\s*\)\s*$/
+
+class Bb
+
+ attr_accessor :llx, :lly, :urx, :ury # strings
+
+ def initialize( llx, lly, urx, ury )
+ @llx, @lly, @urx, @ury = llx, lly, urx, ury
+ # guarantee valid syntax:
+ [@llx, @lly, @urx, @ury].each { |l| l = l.to_i.to_s }
+ @llx, @urx = @urx, @llx if @llx.to_i > @urx.to_i
+ @lly, @ury = @ury, @lly if @lly.to_i > @ury.to_i
+ end
+
+ def Bb.from_comment( 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 comment
+ fail EPBBError, prn unless valid
+ "%%BoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}"
+ end
+
+end # class Bb
+
+# [-+](\d+(\.\d*)?|\.\d+)([eE]\d+)? PostScript number
+HRBB_PAT = /^\s*%%HiResBoundingBox:\s*[-+]?(\d+(\.\d*)?|\.\d+)([eE]\d+)?((\s[-+]?(\d+(\.\d*)?|\.\d+)([eE]\d+)?){3})\s*$/
+HRBB_END = /^\s*%%HiResBoundingBox:\s*\(\s*atend\s*\)\s*$/
+
+class HRBb
+
+ 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 do |l|
+ if l =~ /\./
+ # make floats conform to Ruby syntax:
+ # decimal dots must be padded with digits on either side
+ l.sub!( /^\./, '0.' )
+ l.sub!( /\.(?!\d)/, '.0' ) # (?!\d): zero-width neg. lookahead
+ end
+ l = l.to_f.to_s
+ end
+ @llx, @urx = @urx, @llx if @llx.to_f > @urx.to_f
+ @lly, @ury = @ury, @lly if @lly.to_f > @ury.to_f
+ end
+
+ def HRBb.from_hrcomment( s )
+ return nil unless s =~ HRBB_PAT
+ llx, lly, urx, ury =
+ s.sub( /^\s*%%HiResBoundingBox:\s*/, '' ).split( /\s+/ )
+ HRBb.new( llx, lly, urx, ury )
+ end
+
+ def height
+ ( @ury.to_f - @lly.to_f ).to_s
+ end
+
+ def width
+ ( @urx.to_f - @llx.to_f ).to_s
+ end
+
+ def valid
+ @llx.to_f < @urx.to_f and @lly.to_f < @ury.to_f
+ end
+
+ def non_negative
+ valid and @llx.to_f >= 0 and @lly.to_f >= 0
+ end
+
+ def copy
+ HRBb.new( @llx, @lly, @urx, @ury )
+ end
+
+ def prn
+ "#{@llx} #{@lly} #{@urx} #{@ury}"
+ end
+
+ ##################
+
+ # wrapper code for a hires 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.to_f.ceil} #{height.to_f.ceil}\n" +
+ "%%HiResBoundingBox: 0 0 #{width.to_f} #{height.to_f}\n" +
+ "<< /PageSize [#{width} #{height}] >>" +
+ " setpagedevice\n" +
+ "gsave #{(-(@llx.to_f)).to_s} #{(-(@lly.to_f)).to_s}" +
+ " translate\n" ) > 0
+ end # open
+ return fname
+ end # wrapper
+
+ ##################
+
+ # convert hiresboundingbox to hires boundingbox comment
+
+ def hrcomment
+ fail EPBBError, prn unless valid
+ "%%HiResBoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}"
+ end
+
+end # class HRBb
+
+###########################################
+
+# 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 + " -level3sep"
+ end # case
+ else
+ $settings.ps_options
+ end # ifthenelse sep
+ end
+
+ # instance methods ###
+
+ attr_accessor :path, :bb, :hrbb, :type, :npages, :atfront, :hr_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 )
+ @npages = pdf_pages if @type == 'pdf' # should we do this?
+ @npages = 1 if @type =~ /^eps/
+ end
+ end # initialize
+
+ def file_info
+ if @npages and @type !~ /^eps/
+ return "File type of #{@path} is #{@type} with #{@npages} pages"
+ else
+ return "File type of #{@path} is #{@type}"
+ end
+ end
+
+ ##################
+
+ # debug string for EPCallError
+
+ def buginfo( params = nil )
+ b = "Source was: " + @path + $/
+ b = b + param_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).
+ # We also assume that the hrbb lies within the bb.
+ # The file is not rewritten.
+
+ def find_bb_simple
+
+ fail EPCallError, buginfo unless @type == 'eps'
+ @bb = nil
+ @hrbb = nil
+ slurp = ''
+ File.open( @path, $R ) do |fl|
+ slurp = fl.read( [File.size(@path), SAFESIZE].min )
+ end
+ lines = slurp.split( /\r\n?|\n/ )
+ # look for a bb or a hrbb
+ # if a valid bb is found, check the next line for hrbb
+ # but look no further; we don't want to mistake a hrbb of
+ # an included eps for the hrbb of the outer eps.
+ lines.each do |l|
+ if l =~ BB_PAT
+ @bb = Bb.from_comment( l )
+ elsif l =~ HRBB_PAT
+ @hrbb = HRBb.from_hrcomment( l )
+ elsif @bb
+ break # stop looking; we expect hrbb next to bb
+ end
+ break if @bb and @hrbb
+ 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.
+ # clean up any potential problems
+ # the eps is always written to a new file.
+
+ def eps_clean
+
+ fail EPCallError, buginfo if @type != 'eps' and @type != 'epsPreview'
+ atend = nil
+ hr_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
+
+ # [hires] boundingbox unknown and possibly atend
+ @bb, @atfront, @hrbb, @hr_atfront = nil, nil, 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.
+ # we assume that if there is a hires bb then
+ # bb and hires bb are on consecutive lines.
+ # Otherwise the logic would get too messy.
+
+ # The epsfile will be reconstituted from:
+ # a series of lines and line separators; then either
+ # - a bbox or
+ # - (bbox or hrbbox), separator, (hrbbox or bbox)
+ # the big blob
+ # possibly a trailer with removed bb comments
+
+ pre_lines = slurp.split( /(\r\n?|\n)/ )
+ bb_comment = ''
+ # initialize indices of bb comments to less than smallest index
+ i_bb = -1
+ i_hrbb = -1
+ i = -1
+ i_end = -1
+ pre_length = 0
+ pre_lines.each do |l|
+ pre_length += l.length
+ i += 1
+ next if l =~ /(\r\n?|\n)/
+ if l =~ BB_PAT
+ @bb = Bb.from_comment( l )
+ @atfront = true
+ i_bb = i
+ elsif l =~ BB_END
+ atend = true
+ i_bb = i
+ elsif l =~ HRBB_PAT
+ @hrbb = HRBb.from_hrcomment( l )
+ @hr_atfront = true
+ i_hrbb = i
+ elsif l =~ HRBB_END
+ hr_atend = true
+ i_hrbb = i
+ elsif @bb or atend
+ i_end = i
+ break # stop looking; we expect hrbb next to bb
+ end # =~ BB_PAT
+ end # do |l|
+ if atend or hr_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
+ end # else use old slurp
+ post_lines = slurp.split( /(\r\n?|\n)/ )
+ # initialize indices of atend bb comments to more than largest index
+ j = post_lines.length
+ j_bb = j
+ j_hrbb = j
+ j_end = j
+ post_length = 0
+ post_lines.reverse_each do |l|
+ post_length += l.length
+ j -= 1
+ next if l =~ /(\r\n?|\n)/
+ if l =~ BB_PAT
+ bb_comment = l
+ @bb = Bb.from_comment( bb_comment )
+ j_bb = j
+ elsif l =~ HRBB_PAT
+ bb_comment = l
+ @hrbb = HRBb.from_hrcomment( bb_comment )
+ j_hrbb = j
+ end
+ if (@bb or !atend) and (@hrbb or !hr_atend)
+ j_end = j
+ break
+ end
+ end # do
+ #post_lines.slice([j_bb,j_hrbb].min .. -1).each do |l|
+ # post_block = post_block + l
+ end # if atend
+
+ fail EPBBError, @path unless @bb.valid
+ # in case of discrepancy, drop @hrbb.
+ # we accept a `safety margin': a difference >1pt is not a discrepancy.
+ @hrbb = nil if @hrbb and
+ (( @bb.llx.to_i > @hrbb.llx.to_f.floor ) or
+ ( @bb.lly.to_i > @hrbb.lly.to_f.floor ) or
+ ( @bb.urx.to_i < @hrbb.urx.to_f.ceil ) or
+ ( @bb.ury.to_i < @hrbb.ury.to_f.ceil ))
+
+ retVal = PsPdf.new( 'ext' => 'eps' )
+
+ # always rewrite the eps, to get normalized, ruby-compatible bb syntax.
+ # we modify part of the header, and possibly of the trailer.
+
+ # offset, length of middle part, which can be copied byte for byte
+ cp_start = offset + pre_length
+ cp_len = ps_len - pre_length
+ cp_len -= post_length if ( atend or hr_atend )
+ # replace boundingbox comments
+ if atend
+ pre_lines[i_bb].sub!( BB_END, @bb.comment )
+ post_lines[j_bb].sub!( BB_PAT, '%%' )
+ else
+ pre_lines[i_bb].sub!( BB_PAT, @bb.comment )
+ end
+ if @hrbb
+ # replace valid hires boundingbox comments
+ if hr_atend
+ pre_lines[i_hrbb].sub!( HRBB_END, @hrbb.hrcomment )
+ post_lines[j_hrbb].sub!( HRBB_PAT, '%%' )
+ else
+ pre_lines[i_hrbb].sub!( HRBB_PAT, @hrbb.hrcomment )
+ end
+ elsif i_hrbb >= 0 # invalid hires bb
+ # erase invalid hr boundingbox comments
+ if atend
+ pre_lines[i_hrbb].sub!( HRBB_END, '%%' )
+ post_lines[j_hrbb].sub!( HRBB_PAT, '%%' )
+ else
+ pre_lines[i_hrbb].sub!( HRBB_PAT, '%%' )
+ end
+ end # test for @hrbb
+
+ File.open( retVal.path, $W ) do |fl|
+ fl.write( pre_lines[ 0 .. i_end ].join )
+ end
+ sliceFile( @path, retVal.path, cp_len, cp_start, $A )
+ if ( atend or hr_atend )
+ File.open( retVal.path, $A ) do |fl|
+ fl.write( post_lines[ j_end .. -1 ].join )
+ end
+ end
+ retVal.bb = @bb.copy
+ retVal.hrbb = @hrbb.copy if @hrbb
+ retVal.atfront = true
+ retVal.hr_atfront = true
+ retVal.npages = 1
+ return retVal
+ end # eps_clean
+
+ ##################
+
+ # Use the Ghostscript bbox device to give an eps a tight boundingbox.
+ # Here, we don't test for use_hires_bb.
+ # 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
+ # let ghostscript calculate new boundingbox
+ cmd = $settings.gs_prog + ' ' + PsPdf.gs_options +
+ " -sDEVICE=bbox \"" + @path + "\" 2>&1"
+ write_log cmd # if $DEBUG
+ bb_output = `#{cmd}`
+ # inspect the result
+ 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_comment( b )
+ fail EPBBError, bb_output unless bb_temp.valid
+ bb_temp.expand unless $settings.use_hires_bb
+ @bb = bb_temp.copy
+ elsif b =~ HRBB_PAT
+ bb_temp = HRBb.from_hrcomment( b )
+ @hrbb = bb_temp.valid ? bb_temp.copy : nil
+ end
+ end # do |b|
+ fail EPBBError, bb_output unless @bb
+ # this won't happen, but we deal with it anyway:
+ @hrbb = HRBb.new( @bb.llx, @bb.lly, @bb.urx, @bb.ury ) unless @hrbb
+
+ # locate current [hr]boundingbox, which ha[s|ve] to be replaced
+ # assumptions: both in header, and hrbb no later than
+ # first line after bb.
+ slurp = ''
+ File.open( @path, $R ) do |fl|
+ slurp = fl.read( [File.size(@path),SAFESIZE].min )
+ end
+ pre_lines = slurp.split( /(\r\n?|\n)/ )
+ i_bb = -1
+ i_hrbb = -1
+ i = -1
+ i_end = -1
+ pre_length = 0
+ pre_lines.each do |l|
+ pre_length += l.length
+ i += 1
+ next if l =~ /(\r\n?|\n)/
+ if l =~ BB_PAT
+ i_bb = i
+ elsif l =~ HRBB_PAT
+ i_hrbb = i
+ elsif i_bb >= 0
+ i_end = i
+ break # stop looking; we expect hrbb next to bb
+ end # =~ BB_PAT
+ end # do |l,i|
+ fail EPBBError, "No boundingbox found in #{@path}" if i_bb < 0
+
+ # replace boundingbox[es] by editing initial part pre_block
+ # and copying the rest byte for byte
+ if i_hrbb < 0
+ # no old hrbb; replace bb with new bb and new hrbb
+ # pre_lines[i_bb+1] should match /\r\n?|\n/
+ pre_lines[i_bb].sub!( BB_PAT, @bb.comment +
+ pre_lines[i_bb+1] + @hrbb.hrcomment )
+ else
+ pre_lines[i_bb].sub!( BB_PAT, @bb.comment )
+ pre_lines[i_hrbb].sub!( HRBB_PAT, @hrbb.hrcomment )
+ end
+ oldpath = @path
+ @path = mktemp( 'eps' )
+ File.open( @path, $W ) { |fl|
+ fl.write( pre_lines[ 0 .. i_end ].join ) }
+ sliceFile( oldpath, @path, \
+ File.size( oldpath ) - pre_length, pre_length, $A )
+ return self
+
+ end # fix_bb
+
+ ##################
+
+ # Convert eps to pdf.
+ # The eps should already have a boundingbox in the header.
+
+ def eps_to_pdf( params={} )
+
+ gray = params[ 'gray' ]
+
+ fail EPCallError, buginfo( params ) unless @type == 'eps' and @atfront
+
+ wrp = ( $settings.use_hires_bb and @hrbb ) ? @hrbb.wrapper : @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 )}" +
+ " -paper match -eps -f #{page} -l #{page}" +
+ " \"#{@path}\" \"#{retVal.path}\""
+ else
+ cmd = "\"#{$settings.gs_prog}\" -sDEVICE=epswrite -r600" +
+ " #{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 )
+
+# fix for incorrect DSC header produced by some versions of pdftops:
+# if necessary, change line `% Produced by ...' into `%%Produced by ...'
+# this is usually the second line.
+# otherwise the DSC header would be terminated before the bbox comment
+# match first chk_ze chars against `% Produced by'
+ chk_size = [ 1500, File.size( retVal.path ) ].min
+ slurp = ''
+ File.open( retVal.path, $R ) do |fl|
+ slurp = fl.read( chk_size )
+ end # File
+ pdfpat = /([\r\n])% Produced by/m
+ if slurp =~ pdfpat
+ newpath = mktemp( 'eps' )
+ write_log "pdftops header fix #{retVal.path} => #{newpath}"
+ File.open( newpath, $W ) do |fl2|
+ fl2.write( slurp.sub( pdfpat, '\1%% Produced by)' ) )
+ end
+ sliceFile( retVal.path, newpath, File.size( retVal.path ) - chk_size,
+ chk_size, $A )
+ retVal.path = newpath
+ end # if =~
+# end fix for incorrect DSC header produced by some versions of pdftops
+ retVal.atfront = 1
+ retVal.find_bb_simple
+ retVal.npages = 1
+ 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 @type == 'pdf'
+ retVal = PsPdf.new( 'ext' => 'ps' )
+ if $settings.pdftops_prog and $settings.use_pdftops
+ cmd = "\"#{$settings.pdftops_prog}\" #{PsPdf.ps_options( sep )}" +
+ ( page ? " -f #{page} -l #{page}" : '' ) +
+ " -paper match \"#{@path}\" \"#{retVal.path}\""
+ else
+ cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" +
+ " -sDEVICE=pswrite -r600" +
+ ( page ? " -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.npages = @npages
+ 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.
+
+ 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 ) # => not yet `cleaned'
+ #pp.pdf_pages if pp.type == 'pdf' and not pp.npages
+ # now also done in initialize
+
+ 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.
+
+ case type
+ when 'eps'
+ pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \
+ if gRAY or ( bbox and not pp.bb.non_negative )
+ pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_eps if gray
+ pp = pp.fix_bb if bbox
+ return pp
+
+ when 'pdf'
+ pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \
+ if gRAY or ( bbox and not pp.bb.non_negative )
+ pp = pp.fix_bb if bbox
+ return pp.eps_to_pdf( 'gray' => gray )
+
+ when 'ps'
+ if gRAY
+ pp = pp.eps_to_pdf.pdf_to_ps( 'sep' => gray )
+ pp = pp.ps_to_pdf( 'gray' => gray ).pdf_to_ps
+ else
+ pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_ps
+ end
+ return pp
+
+ 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.pdf_to_eps( 'sep' => gray, 'page' => page )
+ pp = pp.fix_bb
+ return pp = pp.eps_to_pdf( 'gray' => gray )
+ elsif page
+ pp = pp.ps_to_pdf.pdf_to_ps( 'sep' => gray, '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
+
+ #raise EPCallError
+ fail "Unsupported conversion"
+ # this shouldn't happen anymore
+
+ end # any_to_any
+
+end # class PsPdf
+
+#################################
+# main program
+
+require 'optparse'
+
+def gui( action )
+ case action
+ when 'config_w' then
+ puts "pdftops_prog=#{$settings.pdftops_prog}" if ARCH=='w32'
+ puts "pdf_viewer=#{$settings.pdf_viewer}" if ARCH=='unix'
+ puts "ps_viewer=#{$settings.ps_viewer}" if ARCH=='unix'
+ puts "defaultDir=#{$settings.defaultDir}"
+ puts "ignore_pdftops=#{$settings.ignore_pdftops}"
+ puts "pdf_target=#{$settings.pdf_target}"
+ puts "pdf_version=#{$settings.pdf_version}"
+ puts "pdf_custom=#{$settings.pdf_custom}"
+ puts "ps_options=#{$settings.ps_options}"
+ puts "ignore_hires_bb=#{$settings.ignore_hires_bb}"
+ puts "bb_spread=#{$settings.bb_spread}"
+ exit
+ when 'config_r' then
+ while line = gets
+ (varname, val) = line.split('=', 2)
+ varname.strip! if varname
+ val.strip! if val
+ if $settings.has_key?( varname )
+ val = nil if val == ''
+ $settings[varname].val = val
+ # puts( "\"#{val}\" assigned to #{varname}" )
+ end
+ end # while
+ $settings.write_settings
+ exit
+ when nil then
+ $from_gui = true
+ else
+ abort( "Action should be omitted or 'config_w' or 'config_r'" )
+ end # case
+end
+
+# create a pause to examine temp files
+def abortt( msg )
+ if $DEBUG
+ $stderr.puts msg + "\nPress <enter> to finish"
+ $stdin.gets
+ end
+ fail
+end
+
+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( "-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" ) do |opt|
+ $options.gRAY = true
+ 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( "-n", "--no-hires",
+ "Don't use hires boundingbox" ) do |opt|
+ $settings.ignore_hires_bb = '1'
+ end
+
+ opts.on( "-r", "--hires",
+ "Use hires boundingbox" ) do |opt|
+ $settings.ignore_hires_bb = '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( "-N PDFVERSION", "--pdfversion=PDFVERSION",
+ PDF_VERSIONS,
+ "Pdf version to be generated" ) do |opt|
+ $settings.pdf_version = opt
+ end
+
+ opts.on( "-V PDFVERSION", "--version=PDFVERSION",
+ PDF_VERSIONS,
+ "Deprecated; use `-N' or `--pdfversion'." ) do |opt|
+ if opt == ""
+ puts EPVERSION
+ exit
+ end
+ $settings.pdf_version = opt
+ end
+
+ 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( "-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 -level3,",
+ "don't include -eps or page number options;",
+ "these will be generated by the program" ) do |opt|
+ $settings.ps_options = opt
+ 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.on( "--gui[=ACTION]", "Do not use; reserved for GUI" ) do |opt|
+ gui( opt )
+ end
+
+ opts.separator ""
+
+ opts.on( "-v", "Prints version info" ) do |opt|
+ puts EPVERSION
+ exit
+ end
+
+ opts.on_tail("-h", "--help", "Show this message") do
+ puts "Epspdf version " + EPVERSION
+ puts "Copyright (C) " + COPYRIGHT + " Siep Kroonenberg"
+ puts opts
+ exit
+ end
+end # opts
+
+# hack alert! we support `--version' for version info although
+# --version is still interpreted as desired pdf output version
+
+if ARGV.length == 1 and ARGV[0] == '--version'
+ puts EPVERSION
+ exit
+end
+
+# save original command-line for later reporting
+cmdline = "#{$0} #{ARGV.join( sep=' ' )}"
+
+# parse options destructively
+begin
+ opts.parse!( ARGV )
+rescue OptionParser::ParseError => e
+ STDERR.puts e.message, "\n", opts
+ exit( -1 )
+end
+
+# log cmdline AFTER we found out whether we run from gui
+write_log( cmdline )
+
+$options.page = 1 if $options.bbox and not $options.page
+
+$settings.write_settings if save_settings
+
+if ARGV.length < 1
+ if not save_settings # help output
+ puts opts
+ abort
+ else
+ exit
+ end
+elsif $options.info
+ p = PsPdf.new( 'file' => ARGV[0] )
+ puts( p.file_info )
+ 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 )
+ write_log( pp.file_info ) if $from_gui
+ ccp( pp.path, outfile )
+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
+ exit 1
+rescue EPBBError => exc
+ mess = "Boundingbox problem" + $/ +
+ exc.message + $/ + exc.backtrace.join( $/ )
+ write_log( mess )
+ puts mess
+ exit 1
+rescue EPCopyError => exc
+ mess = "Copying problem" + $/ +
+ exc.message + $/ + exc.backtrace.join( $/ )
+ write_log( mess )
+ puts mess
+ exit 1
+rescue EPSystemError => exc
+ mess = "Problem with system call" + $/ +
+ exc.message + $/ + exc.backtrace.join( $/ )
+ write_log( mess )
+ puts mess
+ exit 1
+rescue StandardError => exc
+ mess = exc.message + $/ + exc.backtrace.join( $/ )
+ write_log( mess )
+ puts mess
+ exit 1
+end # rescue block
+
+cleantemp unless $DEBUG == 1
+__END__
diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk
deleted file mode 100755
index 6d3465a8c4b..00000000000
--- a/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/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/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl
new file mode 100755
index 00000000000..ff29eff893d
--- /dev/null
+++ b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl
@@ -0,0 +1,861 @@
+#!/usr/bin/env wish
+
+# epspdf conversion utility, GUI frontend
+
+#####
+# Copyright (C) 2006, 2008, 2009, 2010 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!
+#####
+
+package require Tk
+
+# tired of writing out this combined test again and again
+set classic_unix [expr {$::tcl_platform(platform) eq "unix" && \
+ $::tcl_platform(os) ne "Darwin"}]
+
+# normally, epspdf.rb should be in the same directory
+# and ruby should be on the searchpath.
+# However, the Windows installer version includes a Ruby subset
+# and wraps this script in a starpack.
+
+### calling epspdf.rb #########################
+
+# Get full path of epspdf.rb. It should be in the same directory as
+# either this script or of the starpack containing this script.
+# For non-windows versions, epspdftk might be called via a symlink.
+# For the windows-only starpack, a ruby subset is included.
+# Otherwise, ruby should be on the searchpath.
+
+proc set_progs {} {
+ set scriptfile [file normalize [info script]]
+ # starpack edition?
+ set starred 0
+ if {$::tcl_platform(platform) eq "windows"} {
+ foreach l [info loaded] {
+ if {[lindex $l 1] eq "tclkitpath"} {
+ set starred 1
+ break
+ }
+ }
+ }
+ set syml 0
+ if {$::tcl_platform(platform) eq "unix" && \
+ ! [catch {file readlink [$scriptfile]}]} {
+ set syml 1
+ }
+ if {$starred} {
+ set eproot [file dirname [file normalize [info nameofexecutable]]]
+ set ::ruby [file normalize "$eproot/../rubysub/bin/ruby.exe"]
+ } else {
+ set eproot [file dirname $scriptfile]
+ if {$::tcl_platform(platform) eq "unix" && \
+ ! [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
+ }
+ set ::ruby "ruby"
+ }
+ set ::epspdf_rb [file join $eproot "epspdf.rb"]
+ if {! [file exists $::epspdf_rb]} {
+ tk_messageBox -type ok -icon error -message "Epspdf.rb not found"
+ exit 1
+ }
+
+ # no luck with other platforms
+ if {$::tcl_platform(platform) eq "windows"} {
+ wm iconbitmap . [file join $eproot "epspdf.ico"]
+ }
+}
+
+set_progs
+
+# call epspdf.rb with parameter list l (should be a list):
+
+set result ""
+proc run_epspdf {res args} {
+ upvar $res result
+ set failed [catch [linsert $args 0 exec $::ruby $::epspdf_rb --gui] result]
+ if {$failed} {
+ wm deiconify .log_t
+ tk_messageBox -icon error -type ok -message "Error; see log window"
+ }
+ # update log window with $result
+ .log_t.text configure -state normal
+ .log_t.text insert end "$result\n"
+ .log_t.text yview moveto 1
+ .log_t.text configure -state disabled
+
+ # it is up to the caller to do anything else about failure or not.
+ # the user, at least, has been warned.
+ return [expr ! $failed]
+}
+
+### read configured settings ########################################
+
+# for checking configured viewers under non-osx unix
+proc is_valid {x} {
+ if {[catch {exec which $x}]} {return 0} else {return 1}
+}
+
+# create a global empty settings array
+array set settings [list]
+
+# ask epspdf.rb for currently configured settings.
+# this does not include automatically configured or transient settings.
+# the availability of viewers is handled here.
+proc getsettings {} {
+ if [catch {exec $::ruby $::epspdf_rb --gui=config_w} set_str] {
+ error "Epspdf configuration error: $set_str"
+ }
+ #puts "settings from epspdf.rb:\n$set_str"
+ set l [split $set_str "\r\n"]
+ foreach e $l {
+ # $e is either a string "var=value"
+ # or the empty string between <cr> and <lf>
+ set i [string first "=" $e]
+ if {$i>0} {
+ #puts $e
+ set ::settings([string range $e 0 [expr $i-1]]) \
+ [string range $e [expr $i+1] end]
+ }
+ }
+
+ # unix: viewer settings
+ # configured viewer, if valid, heads the list
+ if {$::classic_unix} {
+
+ set ::ps_viewers {}
+ if {$::settings(ps_viewer) ne "" && [is_valid $::settings(ps_viewer)]} {
+ lappend ::ps_viewers $::settings(ps_viewer)
+ }
+ foreach v {evince okular gv kghostview ghostview} {
+ if {$v ne $::settings(ps_viewer) && [is_valid $v]} {
+ lappend ::ps_viewers $v
+ }
+ }
+
+ set ::pdf_viewers {}
+ if {$::settings(pdf_viewer) ne "" && [is_valid $::settings(pdf_viewer)]} {
+ lappend ::pdf_viewers $::settings(pdf_viewer)
+ }
+ foreach v {evince okular kpdf xpdf epdfview acroread \
+ gv kghostview ghostview} {
+ if {$v ne $::settings(pdf_viewer) && [is_valid $v]} {
+ lappend ::pdf_viewers $v
+ }
+ }
+
+ if {[llength ::pdf_viewers] == 0 && [llength ::ps_viewers] != 0} {
+ lappend ::pdf_viewers [lindex $::ps_viewers 0]
+ }
+ if {[llength ::pdf_viewers] == 0} {
+ tk_messageBox -message "No viewers found"
+ } elseif {[llength ::ps_viewers] == 0} {
+ tk_messageBox -message "No PostScript viewers found"
+ }
+ if {[llength ::pdf_viewers] > 0} {
+ set ::settings(pdf_viewer) [lindex $::pdf_viewers 0]
+ }
+ if {[llength ::ps_viewers] > 0} {
+ set ::settings(ps_viewer) [lindex $::ps_viewers 0]
+ }
+ }
+}
+
+getsettings
+
+# directory and other file data
+
+if {$::argc > 0 && [file isdirectory [lindex $::argv 0]]} {
+ set gfile(dir) [lindex $::argv 0]
+} elseif {[file isdirectory $::settings(defaultDir)]} {
+ set gfile(dir) $::settings(defaultDir)
+} else {
+ set gfile(dir) $::env(HOME)
+}
+set gfile(path) ""
+set gfile(type) ""
+set gfile(name) ""
+set gfile(npages) ""
+
+# transient options
+array set options [list gray "color" format "pdf" bbox 0 clean 1 \
+ pages "single" page 1]
+
+proc viewable {} {
+ if { $::gfile(name) eq "" || ! [file exists $::gfile(path)] || \
+ $::gfile(type) eq "other" || $::gfile(type) eq ""} {
+ return 0
+ } elseif {! $::classic_unix} {
+ return 1
+ } elseif {$::settings(ps_viewer) ne "" && [regexp {ps} $::gfile(type)]} {
+ return 1
+ } elseif {$::settings(pdf_viewer) ne "" && $::gfile(type) eq "pdf"} {
+ return 1
+ } else {
+ return 0
+ }
+}
+
+### groundwork for GUI #################################
+
+# padding for buttons and frames
+proc packb {b args} {
+ eval [linsert $args 0 pack $b -padx 3 -pady 3]
+ $b configure -border 2
+}
+
+proc packf {f args} {
+ eval [linsert $args 0 pack $f -padx 3 -pady 3]
+}
+
+# dummy widget for vertical spacing
+set idummy -1
+proc spacing {w} {
+ incr ::idummy
+ pack [label $w.$::idummy -text " "]
+}
+
+# bold font
+font create boldfont
+# create dummy label widget to get at the default label font properties
+label .dummy
+font configure boldfont -family [font actual [.dummy cget -font] -family]
+font configure boldfont -size [font actual [.dummy cget -font] -size]
+font configure boldfont -weight bold
+destroy .dummy
+
+# bitmaps for mycombo (no combobox in core tk8.4)
+image create bitmap dwnarrow -data {
+#define dwnarrow_width 15
+#define dwnarrow_height 10
+static unsigned char dwnarrow_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, 0x03,
+ 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 };
+}
+
+image create bitmap uparrow -data {
+#define uparrow_width 15
+#define uparrow_height 10
+static unsigned char uparrow_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07,
+ 0xf8, 0x0f, 0xfc, 0x1f, 0x00, 0x00, 0x00, 0x00 };
+}
+
+# mycombo
+proc mycombo {w} {
+ # entry widget and dropdown button
+ frame $w
+ frame $w.ef
+ entry $w.ef.e -width 30 -borderwidth 1
+ pack $w.ef.e -side left
+ button $w.ef.b -image dwnarrow -command "droplist $w" -borderwidth 1
+ pack $w.ef.b -side right
+ pack $w.ef
+ # 'drop-down' listbox; width should match entry widget above
+ toplevel $w.lf -bd 0
+ listbox $w.lf.l -yscrollcommand "$w.lf.s set" -height 4 -width 30 \
+ -bd 1 -relief raised
+ grid $w.lf.l -column 0 -row 0 -sticky news
+ scrollbar $w.lf.s -command "$w.lf.l yview" -bd 1
+ grid $w.lf.s -column 1 -row 0 -sticky ns
+ grid columnconfigure $w.lf 0 -weight 1
+ wm overrideredirect $w.lf 1
+ wm transient $w.lf
+ wm withdraw $w.lf
+ # next two bindings:
+ # final parameter: unmap/toggle listbox
+ bind $w.lf.l <KeyRelease-space> {update_e %W [%W get active] 1}
+ bind $w.lf.l <KeyRelease-Tab> {update_e %W [%W get active] 1}
+ bind $w.lf.l <FocusOut> {update_e %W [%W get active] 1}
+ bind $w.lf.l <1> {update_e %W [%W get @%x,%y] 0}
+ bind $w.lf.l <Double-1> {update_e %W [%W get @%x,%y] 1}
+ bind $w.ef.e <Return> {update_l %W}
+ bind $w.ef.e <Tab> {update_l %W}
+ bind $w.ef.e <FocusOut> {update_l %W}
+ return $w
+}
+
+# toggle state of listbox.
+# this involves calculating the place where it should appear
+# and toggling the arrow image.
+proc droplist {w} {
+ # $w.ef is the frame with the entry widget
+ # $w.lf is the toplevel with the listbox
+ # which needs to turn up right below $w.ef
+ if {[wm state $w.lf] eq "withdrawn" || [wm state $w.lf] eq "iconified"} {
+ set lfx [winfo rootx $w.ef]
+ set lfy [expr [winfo rooty $w.ef] + [winfo height $w.ef]]
+ wm geometry $w.lf [format "+%d+%d" $lfx $lfy]
+ wm deiconify $w.lf
+ $w.ef.b configure -image uparrow
+ } else {
+ wm withdraw $w.lf
+ $w.ef.b configure -image dwnarrow
+ }
+}
+
+proc update_e {lbox vl unmap} {
+ set w [winfo parent [winfo parent $lbox]]
+ $w.ef.e delete 0 end
+ $w.ef.e insert 0 $vl
+ if {$unmap} {droplist $w}
+}
+
+# entry => list
+proc update_l {v} {
+ set t [$v get]
+ set w [winfo parent [winfo parent $v]]
+ for {set i 0} {$i<[$w.lf.l size]} {incr i} {
+ if {$t eq [$w.lf.l get $i]} {
+ $w.lf.l see $i
+ $w.lf.l activate $i
+ return
+ }
+ }
+ # $t not found
+ if {[$v validate]} {
+ $w.lf.l insert 0 $t
+ $w.lf.l see 0
+ $w.lf.l activate 0
+ } else {
+ tk_messageBox -message "Not a program"
+ }
+}
+
+# end mycombo
+
+### and now the actual GUI ###################################
+
+wm title . "PostScript- and pdf conversions"
+
+# help toplevel
+
+proc readhelp {} {
+ .help_t.text configure -state normal
+ set helpfile [regsub {\.rb$} $::epspdf_rb {.help}]
+ if {[catch {set fid [open $helpfile r]}]} {
+ .help_t.text insert end "No helpfile $helpfile found\n"
+ } else {
+ while {[gets $fid line] >= 0} {.help_t.text insert end "$line\n"}
+ close $fid
+ }
+ .help_t.text configure -state disabled
+ .help_t.text yview moveto 0
+}
+
+toplevel .help_t
+wm title .help_t "EpsPdf help"
+packb [button .help_t.done -text "Close" -command {wm withdraw .help_t}] \
+ -side bottom -anchor e -padx 50
+text .help_t.text -wrap word -width 60 -height 20 -setgrid 1 \
+ -yscrollcommand ".help_t.scroll set"
+scrollbar .help_t.scroll -command ".help_t.text yview"
+pack .help_t.scroll -side right -fill y
+pack .help_t.text -expand 1 -fill both
+readhelp
+.help_t.text configure -state disabled
+wm withdraw .help_t
+
+# log toplevel
+
+toplevel .log_t
+wm title .log_t "Epspdf log"
+packb [button .log_t.b -text "Close" -command {wm withdraw .log_t}] \
+ -side bottom -anchor e -padx 50
+text .log_t.text -wrap word -relief flat -height 15 -width 60 \
+ -setgrid 1 -yscrollcommand ".log_t.scroll set"
+scrollbar .log_t.scroll -command ".log_t.text yview"
+pack .log_t.scroll -side right -fill y
+pack .log_t.text -expand 1 -fill both
+.log_t.text configure -state disabled
+wm withdraw .log_t
+
+### configure toplevel ###################
+
+toplevel .config_t
+wm title .config_t "Configure epspdf"
+
+# The settings array is edited directly in the configuration screen:
+# for most control widgets, we need a global external variable anyway,
+# so we better use the global that is already there.
+# If the configuration screen is closed via the cancel button,
+# the original values will be re-read from disk.
+# If it is closed via the ok button, the new settings are written to disk.
+
+# viewers (not on windows or osx)
+
+if {$::classic_unix} {
+ packf [frame .config_t.viewf] -ipadx 4 -fill x
+ grid [label .config_t.viewf.title -font boldfont -text "Viewers"] \
+ -row 0 -column 0 -sticky w
+ grid [label .config_t.viewf.lb_pdf -text "Pdf"] \
+ -row 1 -column 0 -sticky w
+ grid [mycombo .config_t.viewf.pdf] -row 1 -column 1 -sticky e
+ .config_t.viewf.pdf.ef.e configure -vcmd {is_valid %P} -validate none
+ .config_t.viewf.pdf.ef.e configure -textvariable settings(pdf_viewer)
+ grid [label .config_t.viewf.lb_ps -text "PostScript"] \
+ -row 2 -column 0 -sticky w
+ grid [mycombo .config_t.viewf.ps] -row 2 -column 1 -sticky e -pady 4
+ .config_t.viewf.ps.ef.e configure -vcmd {is_valid %P} -validate none
+ .config_t.viewf.ps.ef.e configure -textvariable settings(ps_viewer)
+ grid columnconfigure .config_t.viewf 1 -weight 1 -pad 2
+
+ .config_t.viewf.pdf.lf.l configure -listvariable ::pdf_viewers
+ .config_t.viewf.ps.lf.l configure -listvariable ::ps_viewers
+
+ spacing .config_t
+}
+
+# settings for conversion to pdf
+
+packf [frame .config_t.pdff] -ipadx 4 -fill x
+pack [label .config_t.pdff.title -font boldfont -text "Conversion to pdf"] \
+ -anchor w
+
+pack [label .config_t.pdff.l_target -text "Target use"] -anchor w
+pack [frame .config_t.pdff.f_targets] -fill x
+foreach t {default printer prepress screen ebook} {
+ pack [radiobutton .config_t.pdff.f_targets.$t \
+ -variable settings(pdf_target) \
+ -text $t -value $t] -side left -padx 2 -pady 4 -anchor w
+}
+
+pack [label .config_t.pdff.l_version -text "Pdf version"] -anchor w
+pack [frame .config_t.pdff.f_version] -fill x
+foreach t {1.2 1.3 1.4 default} {
+ regsub {\.} $t _ tp ; # replace dot in name: dots are path separators!
+ pack [radiobutton .config_t.pdff.f_version.$tp \
+ -variable settings(pdf_version) \
+ -text $t -value $t] -side left -padx 2 -pady 4 -anchor w
+}
+
+pack [label .config_t.pdff.l_gs \
+ -text "Custom Ghostscript/ps2pdf parameters"] -anchor w
+pack [entry .config_t.pdff.e_gs -border 1] -fill x -padx 2 -pady 2
+.config_t.pdff.e_gs configure -textvariable settings(pdf_custom)
+
+spacing .config_t
+
+# settings for conversion to EPS and PostScript
+
+packf [frame .config_t.psf] -ipadx 4 -fill x
+pack [label .config_t.psf.l_ps -text "Conversion to EPS and PostScript" \
+ -font boldfont] -anchor w
+if {$tcl_platform(platform) eq "windows"} {
+ pack [label .config_t.psf.l_pdftops -text "Find pdftops"] -anchor w
+ pack [frame .config_t.psf.findf] -anchor w
+ pack [entry .config_t.psf.findf.e -width 40] -side left -padx 4
+ .config_t.psf.findf.e configure -textvariable settings(pdftops_prog)
+ # epspdfrc.rb already checked on this setting so we don't
+ packb [button .config_t.psf.findf.b -text "Browse..." \
+ -command find_pdftops] -side left
+}
+
+proc find_pdftops {} {
+ set try [tk_getOpenFile -title "Find pdftops.exe" \
+ -filetypes {{"Programs" {.exe}}} -initialdir "c:/"]
+ if {$try ne ""} {
+ .config_t.psf.findf.e delete 0 end
+ .config_t.psf.findf.e insert 0 $try
+ }
+}
+
+pack [checkbutton .config_t.psf.c \
+ -text "Use pdftops if available (recommended)"] -anchor w
+.config_t.psf.c configure \
+ -variable settings(ignore_pdftops) -onvalue 0 -offvalue 1
+
+spacing .config_t
+
+# hires boundingbox setting
+
+packf [frame .config_t.hiresf] -ipadx 4 -fill x
+pack [label .config_t.hiresf.title -font boldfont -text "Hires BoundingBox"] \
+ -anchor w
+pack [label .config_t.hiresf.l -text "Uncheck to prevent clipping"] \
+ -anchor w
+
+pack [checkbutton .config_t.hiresf.c \
+ -text "Use hires boundingbox if possible"] -anchor w
+.config_t.hiresf.c configure \
+ -variable settings(ignore_hires_bb) -onvalue 0 -offvalue 1
+
+spacing .config_t
+
+# buttons for closing the configuration screen
+
+pack [frame .config_t.buttonsf] -fill x
+packb [button .config_t.buttonsf.done -text "Done" -command putsettings] \
+ -side right
+packb [button .config_t.buttonsf.cancel -text "Cancel" \
+ -command cancelsettings] -side right
+
+wm transient .config_t
+wm overrideredirect .config_t
+wm withdraw .config_t
+
+proc edit_settings {} {
+ wm deiconify .config_t
+ raise .config_t
+ grab set .config_t
+}
+
+# store new settings
+proc putsettings {} {
+ if {$::classic_unix} {
+ wm withdraw .config_t.viewf.pdf.lf
+ wm withdraw .config_t.viewf.ps.lf
+ }
+ wm withdraw .config_t
+ grab release .config_t
+ write_settings
+}
+
+proc write_settings {} {
+ set s ""
+ foreach el [array names ::settings] {
+ set s "$s$el=$::settings($el)\n"
+ }
+ #puts "\nsettings for epspdf.rb\n$s"
+ if [catch {exec $::ruby $::epspdf_rb --gui=config_r << $s} result] {
+ error "Epspdf configuration error: $result"
+ }
+}
+
+proc cancelsettings {} {
+ if {$::classic_unix} {
+ wm withdraw .config_t.viewf.pdf.lf
+ wm withdraw .config_t.viewf.ps.lf
+ }
+ wm withdraw .config_t
+ grab release .config_t
+ # re-read config file / reg entries
+ getsettings
+}
+
+### main screen #############################
+
+proc show_w {w} {
+ wm deiconify $w
+ raise $w
+}
+
+# buttons to call up configure-, log- and help screens
+
+pack [frame .topf] -fill x
+packb [button .topf.config_t -text "Configure" \
+ -command edit_settings] -side left
+packb [button .topf.help_t -text "Help" \
+ -command {show_w .help_t}] -side right
+packb [button .topf.logb -text "Show log" -command {show_w .log_t}] \
+ -side right -anchor w
+
+# file info in grid layout
+
+packf [frame .infof -relief sunken -border 1] -fill x
+grid [label .infof.dir_label -text "Directory" -anchor w] \
+ -row 1 -column 1 -sticky w
+grid [label .infof.dir_value -textvariable gfile(dir) -anchor w] \
+ -row 1 -column 2 -sticky w
+
+grid [label .infof.name_label -text "File" -anchor w] \
+ -row 2 -column 1 -sticky w
+grid [label .infof.name_value -textvariable gfile(name) -anchor w] \
+ -row 2 -column 2 -sticky w
+
+grid [label .infof.type_label -text "Type" -anchor w] \
+ -row 3 -column 1 -sticky w
+grid [label .infof.type_value -textvariable gfile(type) -anchor w] \
+ -row 3 -column 2 -sticky w
+
+grid [label .infof.npages_label -text "Pages" -anchor w] \
+ -row 4 -column 1 -sticky w
+grid [label .infof.npages_value -textvariable gfile(npages) -anchor w] \
+ -row 4 -column 2 -sticky w
+
+grid columnconfigure .infof 1 -weight 1 -pad 2
+grid columnconfigure .infof 2 -weight 3 -pad 2
+
+spacing .
+
+# conversion options
+
+pack [frame .optsf] -fill x
+
+# grayscaling
+pack [frame .optsf.gray] -side left -anchor nw
+pack [label .optsf.gray.l -text "Grayscaling"] -anchor w
+pack [radiobutton .optsf.gray.off -text "No color conversion" \
+ -variable options(gray) -value "color"] -anchor w
+pack [radiobutton .optsf.gray.gray -text "Try to grayscale" \
+ -variable options(gray) -value "gray"] -anchor w
+pack [radiobutton .optsf.gray.gRAY -text "Try harder to grayscale" \
+ -variable options(gray) -value "gRAY"] -anchor w
+
+# output format
+pack [label .optsf.format] -side right -anchor ne
+pack [label .optsf.format.l -text "Output format"] -anchor w
+pack [radiobutton .optsf.format.pdf -text "pdf" -command set_widget_states \
+ -variable options(format) -value "pdf"] -anchor w
+pack [radiobutton .optsf.format.eps -text "eps" -command set_widget_states \
+ -variable options(format) -value "eps"] -anchor w
+pack [radiobutton .optsf.format.ps -text "ps" -command set_widget_states \
+ -variable options(format) -value "ps"] -anchor w
+
+spacing .
+
+# boundingbox
+pack [checkbutton .bbox -text "Compute tight boundingbox" \
+ -variable options(bbox) -command set_widget_states] -anchor w
+
+# page selection
+pack [frame .pagesf] -fill x
+pack [radiobutton .pagesf.all -text "Convert all pages" \
+ -variable options(pages) -value "all" -command set_widget_states] \
+ -side left
+pack [radiobutton .pagesf.single -text "Page:" \
+ -variable options(pages) -value "single" -command set_widget_states] \
+ -side left
+pack [entry .pagesf.e -width 6 -textvariable ::options(page)] -side left
+#.pagesf.e configure -vcmd {page_valid %W} -validate focusout \
+# -invcmd "focusAndFlash %W [.pagesf.e cget -fg] [.pagesf.e cget -bg]"
+.pagesf.e configure -vcmd {page_valid %W} -validate focusout \
+ -invcmd "see_red %W"
+
+spacing .
+
+# temp files
+pack [checkbutton .clean -text "Remove temp files" \
+ -variable options(clean)] -anchor w
+
+proc focusAndFlash {w fg bg {count 9}} {
+ focus $w
+ if {$count<1} {
+ $w configure -foreground $fg -background $bg
+ } else {
+ if {$count%2} {
+ $w configure -foreground $bg -background $fg
+ } else {
+ $w configure -foreground $fg -background $bg
+ }
+ after 200 [list focusAndFlash $w $fg $bg [expr {$count-1}]]
+ }
+}
+
+proc see_red {w} {
+ focus $w
+ set orig [$w cget -bg]
+ $w configure -bg red
+ update idletasks
+ after 1000
+ $w configure -bg $orig
+ update idletasks
+}
+
+proc page_valid {w} {
+ # w entry widget
+ set p [$w get]
+ set isvalid 1
+ #puts "check pageno $p"
+ if {! [string is integer $p] || $p<=0} {
+ set isvalid 0
+ } elseif {$::gfile(npages) ne "" && [scan $p "%d"] > $::gfile(npages)} {
+ set isvalid 0
+ }
+ return $isvalid
+}
+
+# end conversion options
+
+pack [label .status -justify left] -side bottom -anchor w -fill x -expand 1
+
+# main buttons
+
+pack [frame .bottomf] -side bottom -fill x
+packb [button .bottomf.view -text "View" -command view] -side left
+packb [button .bottomf.open -text "Open" -command openDialog] \
+ -side left -padx 2
+packb [button .bottomf.convert -text "Convert and save..." \
+ -command saveDialog] -side left -padx 2
+packb [button .bottomf.done -text "Done" -command exit] -side right
+
+proc view {} {
+ if {! [viewable]} {
+ tk_messageBox -icon warning -type ok \
+ -message "No viewer for $::gfile(path)"
+ return
+ }
+ if {$::tcl_platform(os) eq "Darwin"} {
+ catch {exec open $::gfile(path) &}
+ } elseif {$::tcl_platform(platform) ne "unix"} {
+ # use shortname to sidestep quoting problems
+ catch {exec cmd /x /c start [file attributes $::gfile(path) -shortname]}
+ } else {
+ if {$::gfile(type) eq "pdf"} {
+ catch {exec $::settings(pdf_viewer) $::gfile(path) &}
+ } elseif {$::gfile(type) ne "other" && $::gfile(type) ne ""} {
+ catch {exec $::settings(ps_viewer) $::gfile(path) &}
+ }
+ }
+}
+
+proc openDialog {} {
+ set types {
+ {"Encapsulated PostScript" {.eps .epi .epsi}}
+ {"General PostScript" {.ps .prn}}
+ {"Pdf" {.pdf}}
+ {"All files" *}
+ }
+ if {[file isdirectory $::gfile(dir)]} {
+ set try [tk_getOpenFile -filetypes $types -initialdir $::gfile(dir)]
+ } else {
+ set try [tk_getOpenFile -filetypes $types]
+ }
+ if {$try != ""} {
+ set ::gfile(path) [file normalize $try]
+ set ::gfile(dir) [file dirname $::gfile(path)]
+ set ::gfile(name) [file tail $::gfile(path)]
+ if {[run_epspdf result -i $::gfile(path)]} {
+ # parse output
+ regexp { is (\w+)(?: with (\d+) pages)?[\r\n]*$} $result \
+ mtc ::gfile(type) ::gfile(npages)
+ if {[regexp {^eps} $::gfile(type)]} {set ::gfile(npages) 1}
+ }
+ set ::settings(defaultDir) $::gfile(dir)
+ putsettings
+ }
+ set_widget_states
+}
+
+proc saveDialog {} {
+ if {$::options(format) eq "pdf"} {
+ set types {{"Pdf" {.pdf}}}
+ } elseif {$::options(format) eq "ps"} {
+ set types {{"PostScript" {.ps}}}
+ } elseif {$::options(format) eq "eps"} {
+ set types {{"Encapsulated PostScript" {.eps}}}
+ }
+ set try [tk_getSaveFile -filetypes $types -initialdir $::gfile(dir)]
+ if {$try != ""} {
+ # fix extension
+ set try [file normalize $try]
+ set idot [string last "." [file tail $try]]
+ if {$idot == -1} {
+ append try ".$::options(format)"
+ } else {
+ # don't bother checking whether the extension is already correct
+ set try [string range $try 0 [string last "." $try]]
+ append try $::options(format)
+ }
+ # epspdf can read persistent options from configuration.
+ # only options from the options array need to be converted to parameters.
+ set cmd [list run_epspdf result]
+ if {$::options(gray) eq "gray"} {
+ lappend cmd "-g"
+ } elseif {$::options(gray) eq "gRAY"} {
+ lappend cmd "-G"
+ }
+ if {$::options(bbox)} {lappend cmd "-b"}
+ if {! $::options(clean)} {lappend cmd "-d"}
+ if {$::options(pages) eq "single"} {
+ if {$::options(page) eq ""} {set ::options(page) 1}
+ lappend cmd "-p" $::options(page)
+ }
+ lappend cmd $::gfile(path)
+ lappend cmd $try
+ # $cmd/run_epspdf never bombs, but returns 1 (success) or 0 (fail)
+ .status configure -text "Working..." -justify "left"
+ foreach b {view open convert done} {
+ .bottomf.$b configure -state disabled
+ }
+ update idletasks; # force immediate redisplay main window
+
+ if {[eval $cmd]} {
+ # if failure, there has already been an error message
+ set ::gfile(path) $try
+ set ::gfile(dir) [file dirname $try]
+ set ::gfile(type) $::options(format)
+ set ::gfile(name) [file tail $try]
+ set ::gfile(path) [file normalize $try]
+ set ::gfile(dir) [file dirname $::gfile(path)]
+ set ::gfile(name) [file tail $::gfile(path)]
+ # parse result output
+ regexp { is (\w+)(?: with (\d+) pages)?[\r\n]*$} \
+ [string range $result [string last "File type of" $result] end] \
+ mtc ::gfile(type) ::gfile(npages)
+ if {$::gfile(type) eq "eps"} {set ::gfile(npages) 1}
+ set ::settings(defaultDir) $::gfile(dir)
+ putsettings
+ }
+ .status configure -text ""
+ foreach b {view open convert done} {
+ .bottomf.$b configure -state normal
+ }
+ set_widget_states
+ }
+}
+
+proc set_widget_states {} {
+ # states (normal/disabled) for various widgets.
+ # their values should already be mutually consistent.
+ # widgets concerned:
+ # view / save
+ # output format / bbox / single page / page no.
+
+ # view
+ if {[viewable]} {
+ .bottomf.view configure -state normal
+ } else {
+ .bottomf.view configure -state disabled
+ }
+
+ # convert
+ if {$::gfile(path) ne "" && [file exists $::gfile(path)] && \
+ $::gfile(type) ne "other"} {
+ .bottomf.convert configure -state normal
+ } else {
+ .bottomf.convert configure -state disabled
+ }
+
+ # type
+ if {[regexp {ps|pdf} $::gfile(type)]} {
+ foreach f {pdf eps ps} {.optsf.format.$f configure -state normal}
+ } else {
+ foreach f {pdf eps ps} {.optsf.format.$f configure -state disabled}
+ }
+
+ # pages and page
+ foreach f {all single} {.pagesf.$f configure -state normal}
+ if {$::options(format) eq "eps"} {
+ .pagesf.all configure -state disabled
+ set ::options(pages) "single"
+ }
+ if {$::options(format) eq "ps"} {
+ .pagesf.single configure -state disabled
+ set ::options(pages) "all"
+ }
+ if {$::options(pages) eq "all"} {
+ .pagesf.e configure -state disabled
+ } else {
+ .pagesf.e configure -state normal
+ }
+
+ # boundingbox
+ if {$::options(format) eq "eps"} {
+ .bbox configure -state normal
+ } elseif {$::options(pages) eq "all"} {
+ .bbox configure -state disabled
+ set ::options(bbox) 0
+ } else {
+ .bbox configure -state normal
+ }
+}
+
+set_widget_states