summaryrefslogtreecommitdiff
path: root/Build/source/texk/texlive/linked_scripts/epspdf
diff options
context:
space:
mode:
authorPeter Breitenlohner <peb@mppmu.mpg.de>2013-02-08 08:13:09 +0000
committerPeter Breitenlohner <peb@mppmu.mpg.de>2013-02-08 08:13:09 +0000
commit00fb7e2695e55961f249a46965ce64506d297893 (patch)
treee14ebea76fb343b677682de631930cafaa0b3def /Build/source/texk/texlive/linked_scripts/epspdf
parent16c40936344c67164a6ab224a3429780d4d62736 (diff)
Epspdf updated to 0.6 (sync Master => Build)
git-svn-id: svn://tug.org/texlive/trunk@29057 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/texlive/linked_scripts/epspdf')
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/epspdf/epspdf.rb1479
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/epspdf/epspdf.tlu2703
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl593
3 files changed, 3051 insertions, 1724 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb
deleted file mode 100755
index 47a0c1f65b9..00000000000
--- a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb
+++ /dev/null
@@ -1,1479 +0,0 @@
-#!/usr/bin/env ruby
-
-# epspdf conversion utility, main source
-
-#####
-# Copyright (C) 2006, 2008, 2009, 2010, 2011 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/epspdf.tlu b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.tlu
new file mode 100755
index 00000000000..ae793d7f344
--- /dev/null
+++ b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.tlu
@@ -0,0 +1,2703 @@
+#!/usr/bin/env texlua
+
+kpse.set_program_name('texlua')
+
+-- epspdf conversion utility
+
+-- First texlua version
+
+ep_version = '0.6.0'
+ep_copyright = '2006, 2008, 2009, 2010, 2011, 2013'
+
+--[[
+
+TeX code for cropping pdfs adapted from Heiko Oberdiek's pdfcrop utility
+
+Program structure
+SETUP
+- some globals
+- utilities
+- system info
+- some infrastructure - logging, temp files
+- initializing (persistent) settings and associated utilities
+- initializing (transient) options
+MAIN FUNCTIONS/METHODS
+- boundingboxes and their methods
+- PsPdf objects:
+ - globals
+ - identify function
+ - one-step conversion methods
+ - any_to_any method
+INITIALIZATION
+- parsing and interpreting rc file
+- parsing and interpreting command-line
+- non-conversion runs
+- start of logging and creation of temp directory
+CONVERSION
+- call any_to_any
+
+TODO
+
+- duplicating epstopdf options
+- use epdf library only optionally
+- custom options for gs and pdftops
+
+Use absolute, normalized names for gs_prog and pdftops_prog but use
+input- and output files as-is.
+
+MAYBE NOT NEEDED
+
+We can probably dispense with [hr]bb:wrapper()
+--]]
+
+-- some general utilities and globals ---------------------------
+
+--[[
+
+I think we get by just fine with simple-minded error handling. At
+most, we just call a function which tries to first write the error
+message to log before re-raising the error.
+
+The gui can capture error messages if necessary.
+
+--]]
+
+eol = nil
+path_sep = nil
+if os.type=='unix' then
+ eol='\n'
+ path_sep = ':'
+else
+ eol='\r\n'
+ path_sep = ';'
+end
+
+-- whether epspdf is run from the epspsdtk gui
+
+from_gui = false
+
+-- error- and debug
+
+function errror(mess)
+ if logfile then pcall(write_log, mess) end
+ -- ignore result of pcall: we can do nothing about failure
+ error(mess, 2)
+end
+
+function dbg(mess)
+ if options.debug then
+ if logfile then write_log(mess) end
+ print(mess)
+ end
+end
+
+--[[
+
+function dbg_opt()
+ if options.debug then
+ local mess = ''
+ for _, k in ipairs({'bbox', 'gray', 'page'}) do
+ mess = mess.. ' ' .. k .. ': ' .. tostring(options.k)
+ end
+ dbg(mess)
+ end
+end
+
+--]]
+
+function ep_shortname(path)
+ if os.type=='unix' then
+ return path
+ else
+ -- shortname appears not to work under miktex
+ -- so return original path as a fallback
+ local sp = lfs.shortname(path)
+ return sp or path
+ end
+end
+
+function fw(path)
+ if os.type=='windows' then
+ return string.gsub(path, '\\', '/')
+ else
+ return path
+ end
+end
+
+cwd = fw(lfs.currentdir())
+source_dir = false -- directory of input file; to be determined
+dest_dir = false -- directory of output file; to be determined
+
+function absolute_path(path, reldir)
+
+ --[[ Return absolute normalized version of path, interpreted
+ from the directory from where the program was called.
+ If reldir, then interpret path from reldir instead.
+
+ We use the fact that lfs.currentdir() always returns an absolute and
+ normalized path. So we go to the parent directory of path, ask for
+ the current directory and then combine the current directory with
+ the base filename.
+
+ The function returns nil if there is no valid parent path.
+ This might be an issue if path is a directory,
+ but we shall apply this function only on files.
+ It is ok if path itself does not exist. --]]
+
+ path = fw(path)
+
+ local present_dir = lfs.currentdir()
+
+ lfs.chdir(cwd)
+
+ if reldir then
+ if not lfs.chdir(reldir) then return nil end
+ end
+
+ local parentdir
+ local filename
+
+ if string.match(path, '/') then
+ parentdir, filename = string.match(path,'^(.*)/([^/]*)$')
+ if parentdir=='' then
+ parentdir = '/'
+ -- on unix, this is an absolute path. on windows, it is not
+ if os.type=='windows' then
+ lfs.chdir('/')
+ parentdir = fw(lfs.currentdir())
+ end
+ elseif os.type=='windows' and string.match(parentdir,'^[a-zA-Z]:$') then
+ parentdir = string.sub(parentdir,1,2)..'/'
+ else
+ if not lfs.chdir(parentdir) then
+ parentdir = nil
+ else
+ parentdir = fw(lfs.currentdir())
+ end
+ end
+ elseif os.type=='windows' and string.match(path,'^[a-zA-Z]:') then
+ -- windows: d:file
+ parentdir = string.sub(path,1,2)
+ if not lfs.chdir(parentdir) then
+ parentdir = nil
+ else
+ parentdir = fw(lfs.currentdir())
+ filename = string.sub(path,3)
+ end
+ else
+ parentdir = fw(lfs.currentdir())
+ filename = path
+ end
+ lfs.chdir(present_dir)
+ if not parentdir then
+ return nil
+ elseif string.sub(parentdir,-1)=='/' then
+ return parentdir..filename, parentdir
+ else
+ return parentdir..'/'..filename, parentdir
+ end
+end -- absolute_path
+
+-- check whether prog is on the searchpath.
+-- we need it only under unix,
+-- so we save ourselves the trouble of accommodating windows.
+-- we return the full path, although we only need a yes or no answer
+
+function find_on_path (prog)
+ if os.type ~= 'unix' then
+ errror('find_on_path: this is a unix-only function')
+ end
+ for d in string.gmatch(os.getenv('PATH'), '[^:]+') do
+ if lfs.isfile(d..'/'..prog) then
+ return absolute_path(d..'/'..prog)
+ end
+ end
+ return false
+end -- find_on_path
+
+-- OTOH, on windows we do not rely so much on the searchpath
+-- so we just test whether the file exists and is an exe file.
+-- only used for pdftops.
+
+function is_prog (path)
+ -- 1. test for and if necessary add extension
+ -- 2. test for existence
+ -- 3. returns either false or absolute path
+ if os.type ~= 'windows' then
+ errror('is_prog: this is a Windows-only function')
+ end
+ if not path then
+ return false
+ end
+ if not string.lower(string.sub(path,-4,-1))=='.exe' then
+ path = path..'.exe'
+ end
+ path = absolute_path(path)
+ if not (path and lfs.isfile(path)) then
+ return false
+ else
+ return path
+ end
+end -- is_prog
+
+-- check whether el occurs in array lst
+function in_list (el, lst)
+ if not lst then return false end
+ for _,p in ipairs(lst) do
+ if el == p then
+ return true
+ end
+ end
+ return false
+end -- in_list
+
+-- remove leading and trailing, but not embedded spaces
+function strip_outer_spaces(s)
+ s = string.gsub(s, '%s*$', '')
+ s = string.gsub(s, '^%s*', '')
+ return s
+end -- strip_outer_spaces
+
+function join(t, sep, lastsep)
+ -- there is a table function concat which does this,
+ -- but without optional different lastsep
+ if t==nil or #t<1 then return '' end -- or should we return nil?
+ local s = t[1]
+ for i=2,#t do -- ok if #t<2
+ if i==#t and lastsep then
+ s = s .. lastsep .. t[i]
+ else
+ s = s .. sep .. t[i]
+ end
+ end
+ return s
+end -- join
+
+-- combine several tables into one.
+-- the parameter is a table of tables.
+function tab_combine (t)
+ local res = {}
+ for _,tt in ipairs(t) do
+ for __, ttt in ipairs(tt) do
+ table.insert(res, ttt)
+ end
+ end
+ return res
+end -- tab_combine
+
+-- Copy a file in chunks, with optional length and offset.
+-- Since files may be very large, we copy them piecemeal.
+-- An initial chunk of size bufsize should be plenty to include
+-- any interesting header information.
+
+bufsize=16000
+
+function slice_file(source, dest, len, offset, mode)
+ -- The final three parameters can be independently left out by
+ -- specifying false as value
+ -- Assume caller ensured parameters of correct type.
+ -- We do not allow negative offsets.
+ local sz = lfs.attributes(source).size
+ if not offset then
+ offset = 0
+ elseif offset>sz then
+ offset = sz
+ end
+ if not len or len>sz-offset then
+ len = sz - offset
+ end
+ if not mode then mode = 'wb' end
+ -- dbg('copying '..len..' bytes of '..source..' to '..dest..' from '..offset
+ -- ..' in '..mode..' mode')
+ local buffer=''
+ local s=io.open(source, 'rb')
+ s:seek('set', offset)
+ local copied = 0
+ local d=io.open(dest, mode)
+ if not d then errror('slice_file: failed to copy to '..dest) end
+ local slen = len
+ while slen>0 do
+ if slen>=bufsize then
+ buffer = s:read(bufsize)
+ slen = slen - bufsize
+ else
+ buffer = s:read(slen)
+ slen = 0
+ end
+ if not d:write(buffer) then
+ errror('slice_file: failed to copy to '..dest)
+ end
+ end
+ s:close()
+ d:close()
+end -- slice_file
+
+-- system info --------------------------------------------
+
+-- safe mode? TODO
+options = {safer = string.match(arg[0], 'repspdf')}
+
+-- Windows: miktex, TL or neither
+-- no support yet for separate ghostscript
+is_miktex = false
+is_tl_w = false
+if os.type == 'windows' then
+ if string.find (string.lower(kpse.version()), 'miktex') then
+ is_miktex = true
+ else
+ local rt = string.gsub(os.selfdir,'[\\/][^\\/]+[\\/][^\\/]+$', '')
+ if not rt then
+ errror('Unrecognized TeX directory structure', 0)
+ elseif lfs.isfile(rt..'/release-texlive.txt') then
+ --[[
+ -- TL version is easy to determine but is not needed
+ local fin = io:open(rt..'release-texlive.txt', 'r')
+ if fin then
+ local l = fin:read('*line')
+ tl_ver = string.match(l, 'version%s+(%d+)$')
+ if tl_ver then tl_ver = tonumber(tl_ver) end
+ end -- if fin
+ --]]
+ is_tl_w = true
+ else
+ errror('Not MikTeX and no file ' .. rt ..
+ '/release-texlive.txt; TeX installation not supported.', 0)
+ end -- if isfile
+ end -- if not miktex
+end -- if windows
+
+-- without Ghostscript we are dead in the water
+gs_prog = false
+do
+ local rt=''
+ if os.type == 'unix' then
+ if find_on_path('gs') then
+ gs_prog = 'gs'
+ else
+ error('No ghostscript on searchpath!', 0)
+ end
+ elseif is_miktex then
+ -- gs_prog = fw(os.selfdir)..'/mgs.exe'
+ gs_prog = 'mgs.exe'
+ rt = string.gsub(os.selfdir,'[\\/][^\\/]+[\\/][^\\/]+$', '')
+ if not lfs.isdir(rt..'/miktex') then
+ -- 64-bits: binaries one level deeper
+ rt = string.gsub(rt, '[\\/][^\\/]+$', '')
+ end
+ if rt=='' then errror('Unexpected MiKTeX directory layout') end
+ if not lfs.isdir(rt..'/miktex') then
+ errror('Unexpected MiKTeX directory layout')
+ end
+ os.setenv('MIKTEX_GS_LIB', rt..'/ghostscript/base;'..rt..'/fonts')
+ elseif is_tl_w then
+ -- windows/TeX Live
+ -- grandparent of texlua.exe directory .. ...
+ rt = string.gsub(os.selfdir,'[\\/][^\\/]+[\\/][^\\/]+$', '')
+ ..'/tlpkg/tlgs'
+ os.setenv('GS_LIB', rt..'/lib;'..rt..'/fonts')
+ os.setenv('Path', rt..'/bin'..';'..os.getenv('Path'))
+ gs_prog = 'gswin32c.exe'
+ else
+ errror('Only TeX Live and MikTeX supported!', 0)
+ end
+end -- do
+
+-- directory for configuration and log
+epsdir = ''
+if os.type == 'windows' then
+ epsdir = fw(ep_shortname(os.getenv('APPDATA'))) .. '/epspdf'
+else
+ epsdir = os.getenv('HOME')..'/.epspdf'
+end
+-- dbg('epsdir: '..epsdir)
+rcfile = epsdir .. '/config'
+
+-- create epsdir if necessary
+if lfs.isfile(epsdir) then
+ error('Cannot continue; epspdf directory ' .. epsdir .. ' is a file')
+elseif not lfs.isdir(epsdir) then
+ if not lfs.mkdir(epsdir) then
+ error('Failed to create epspdf directory ' .. epsdir)
+ end
+end
+
+-- log and log rotation
+
+logfile = epsdir .. '/epspdf.log'
+log_bsl = string.gsub(logfile, '/', '\\')
+oldlog = epsdir .. '/epspdf.log.old'
+
+-- tag log entries with one random integer per epspdf run,
+-- in the absence of a lua process id built-in function
+
+logtag = math.random(0,999999) -- range is inclusive
+logtag = string.format('%06d', logtag)
+
+-- we open and close the logfile anew for each write.
+-- failure to open constitutes no error.
+function write_log(s)
+ local f = io.open(logfile, 'a')
+ if f then
+ f:write(string.format('%s %s%s',
+ os.date('%Y/%m/%d %H:%M:%S', os.time()), s, eol))
+ f:close()
+ end
+ if from_gui then
+ print(s) -- intercepted by the gui
+ end
+end
+
+function log_cmd(cmd)
+ write_log('[' .. table.concat(cmd, '] [') .. ']')
+end
+
+-- temporary files ----------------------------------------
+
+tempdir = false -- will be created later
+tempfiles = {}
+
+-- We just name our temporary files nn.<ext> with successive nn.
+-- We cannot exclude that another process uses our tempdir
+-- so we have to first check for each new file whether it already exists.
+-- Note: epspdf does all the real work from this temp directory.
+
+function mktemp(ext)
+ local froot, fname, f, g
+ for i=0,99 do
+ froot = string.format('%02d.', i)
+ fname = froot..ext
+ -- dbg('New temp file '..fname..'?')
+ if ext~='tex' then
+ if not lfs.isfile(fname) then
+ -- dbg(fname..' available')
+ f = io.open(fname, 'wb')
+ if not f then
+ errror('Cannot create temporary file '..fname)
+ end
+ f:close()
+ table.insert(tempfiles, fname)
+ return froot..ext -- no need to record pdf name
+ end
+ else
+ -- tex; we also need a pdf
+ if not lfs.isfile(fname) and not lfs.isfile(froot..'pdf') then
+ local f = io.open(fname, 'wb')
+ if not f then
+ errror('Cannot create temporary file '..fname)
+ end
+ f:close()
+ table.insert(tempfiles, fname)
+ fname = froot..'pdf'
+ g = io.open(fname, 'wb')
+ if not g then
+ errror('Cannot create temporary file '..fname)
+ end
+ g:close()
+ table.insert(tempfiles, fname)
+ table.insert(tempfiles, froot..'log')
+ return froot..ext -- no need to record pdf name
+ end
+ end -- if
+ end -- for
+ errror('Cannot create temporary file in '..tempdir)
+end
+
+function cleantemp()
+ lfs.chdir(tempdir)
+ for _,f in ipairs(tempfiles) do
+ if lfs.isfile(f) then
+ local success, mess = os.remove(f)
+ if not success then write_log(mess) end
+ end
+ end
+ local empty = true
+ for f in lfs.dir('.') do
+ if f ~= '.' and f ~= '..' then
+ empty = false
+ write_log('Temp dir '..tempdir..' contains '..f..' therefore not removed')
+ break
+ end
+ end
+ lfs.chdir('..')
+ if empty then
+ local res, mess
+ res, mess = lfs.rmdir(tempdir)
+ if not res then
+ write_log('Failed to remove empty '..tempdir..'\n'..mess)
+ end
+ end
+end
+
+--[[
+
+settings
+
+Now:
+1. initial values
+Later:
+2. try to read config file
+3. command-line option parsing, including settings that are not stored
+
+The values in the settings array have lowest priority - lower than
+autodetect and command-line options. We go for false rather than
+undefined, because this results in an actual settings entry.
+We ignore illegal settings in the config file.
+
+--]]
+
+pdf_targets = {'screen', 'ebook', 'printer', 'prepress', 'default'}
+pdf_versions = {'1.2', '1.3', '1.4', 'default'}
+
+settings = {}
+descriptions = {}
+
+settings.pdf_target = 'default'
+descriptions.pdf_target = 'One of ' .. join(pdf_targets, ', ', ' or ')
+
+settings.pdf_version = 'default'
+descriptions.pdf_version = 'One of ' .. join(pdf_versions, ', ', ' or ')
+
+--[[
+-- is bb_spread still a useful setting?
+-- look at gs options wrt boundingbox
+-- settings.bb_spread = 1
+-- descriptions.bb_spread = 'Safety margin in points for (low-res) boundingbox'
+
+settings.use_hires_bb = false
+-- descriptions.use_hires_bb = 'Use high-resolution boundingbox if available'
+-- Ignored; hires bb always used
+--]]
+
+-- because pdftops_prog is sometimes configurable, it is stored in settings.
+-- it will not be used for TeX Live and only be read and written on Windows.
+settings.pdftops_prog = false
+--[[
+if os.type == 'unix' then
+ settings.pdftops_prog = find_on_path('pdftops')
+elseif os.type == 'windows' and not is_miktex then
+ settings.pdftops_prog = os.selfdir..'/pdftops.exe'
+end
+--]]
+descriptions.pdftops_prog = 'Full path to pdftops.exe (not used with TeX Live)'
+
+settings.use_pdftops = true
+descriptions.use_pdftops = 'Use pdftops if available'
+
+-- epspdf stores ps- and pdf viewer settings on behalf of the gui interface
+-- but does not use them itself.
+-- They won't be used at all under osx or windows.
+
+settings.ps_viewer = false
+descriptions.ps_viewer =
+ 'Epspdftk: viewer for PostScript files; not used on Windows or OS X'
+
+settings.pdf_viewer = false
+descriptions.pdf_viewer =
+ 'Epspdftk: viewer for pdf files; not used on Windows or OS X'
+
+-- default_dir, which is used on all platforms, is only for the gui.
+
+if os.type == 'windows' then
+ settings.default_dir =
+ string.gsub(ep_shortname(os.getenv('USERPROFILE')), '\\', '/')
+else
+ settings.default_dir = os.getenv('HOME')
+end
+descriptions.default_dir =
+ 'Epspdftk: initial directory; ignored by epspdf itself'
+
+function write_settings (file)
+ local f
+ if file then
+ f = io.open(rcfile, 'wb')
+ if not f then
+ return
+ end
+ else -- stdout to be captured by epspdftk
+ f = io.output()
+ if os.type=='windows' and not is_tl_w then
+ f:write('tl_w = no', eol)
+ end
+ end
+ for k, v in pairs(settings) do
+ if k ~= 'pdftops_prog' or os.type=='windows' then
+ if descriptions[k] and file then
+ f:write(eol, '# ', descriptions[k], eol)
+ end
+ f:write(k, ' = ', tostring(v), eol)
+ end
+ end
+ if file then
+ f:close()
+ end
+end
+
+function read_settings(file)
+ -- read and interpret rcfile
+ -- we shall ignore illegal entries.
+ local contents
+ local f
+ if file then
+ f = io.open(rcfile, 'rb')
+ if not f then
+ return
+ end
+ else
+ f = io.input()
+ end
+ contents = f:read(10000)
+ if file then
+ f:close()
+ end
+ if not contents or contents=='' then
+ dbg('No settings read')
+ return
+ -- else
+ -- dbg(contents)
+ end
+ -- remove initial \r and \n characters
+ contents = string.gsub(contents, '^[\r\n]*', '');
+ -- gmatch chops contents into series of non-line-ending characters
+ -- possibly followed by line-ending characters.
+ local k, v, vl, vnum
+ for l in string.gmatch(contents, '[^\r\n]+[\r\n]*') do
+ l = string.match(l,'[^\r\n]*')
+ if not string.match(l, '^#') then
+ k, v = string.match(l, '^%s*([^%s]+)%s*=%s*(.*)$')
+ if v then v = string.gsub(v,'%s*$', '') end
+ -- now handle k and v
+ if k == 'pdf_target' then
+ -- ignore unless valid option
+ if in_list(v, pdf_targets) then
+ settings[k] = v
+ end
+ elseif k == 'pdf_version' then
+ -- ignore unless valid option
+ if in_list(v, pdf_versions) then
+ settings[k] = v
+ end
+ --[[
+ elseif k == 'ignore_hires_bb' then
+ vl = string.lower(string.sub(v,1,1))
+ if v == 0 or vl == 'n' or vl == 'f' then
+ settings.use_hires_bb = true
+ elseif v == 1 or vl == 'y' or vl == 't' then
+ settings.use_hires_bb = false
+ end
+ elseif k == 'use_hires_bb' then
+ vl = string.lower(string.sub(v,1,1))
+ if v == 0 or vl == 'n' or vl == 'f' then
+ settings.use_hires_bb = false
+ elseif v == 1 or vl == 'y' or vl == 't' then
+ settings.use_hires_bb = true
+ end
+ elseif k == 'bb_spread' then
+ vnum = tonumber(v)
+ if vnum and vnum >= 0 then
+ settings[k] = math.modf(v) -- truncate to integer
+ end
+ --]]
+ elseif k == 'pdftops_prog' then
+ if is_miktex then
+ settings.pdftops_prog = is_prog(v)
+ -- elseif os.type=='windows' then
+ -- settings.pdftops_prog = v
+ end -- else ignore
+ elseif k == 'ignore_pdftops' then
+ vl = string.lower(string.sub(v,1,1))
+ if v == 0 or vl == 'n' or vl == 'f' then
+ settings.use_pdftops = true
+ elseif v == 1 or vl == 'y' or vl == 't' then
+ settings.use_pdftops = false
+ end
+ elseif k == 'use_pdftops' then
+ vl = string.lower(string.sub(v,1,1))
+ if v == '0' or vl == 'n' or vl == 'f' then
+ settings.use_pdftops = false
+ elseif v == '1' or vl == 'y' or vl == 't' then
+ settings.use_pdftops = true
+ end
+ -- final three settings not used by epspdf itself but
+ -- passed along to epspdftk
+ elseif k == 'ps_viewer' then
+ settings.ps_viewer = v
+ elseif k == 'pdf_viewer' then
+ settings.pdf_viewer = v
+ elseif k == 'default_dir' then
+ settings.default_dir = v
+ elseif k == 'default_dir' then
+ settings.default_dir = v
+ end -- test for k
+ end -- not matching ^#
+ end -- for
+end -- read settings
+
+-- command-line parameters: variables and functions -------------
+
+function help (mess)
+ -- need to enforce an ordering, otherwise we could have used pairs(opts)
+ if mess then print(mess..eol) end
+ show_version()
+ print([[
+
+Convert between [e]ps and pdf formats
+Usage: epspdf[.tlu] [options] infile [outfile]
+Default for outfile is file.pdf if infile is file.eps or file.ps
+Default for outfile is file.eps if infile is file.pdf
+]])
+ -- omitted below: no-op options
+ for _, k in ipairs({'page', 'gray', 'bbox', 'pdf_target', 'pdf_version',
+ 'pdftops_prog', 'use_pdftops', 'save', 'debug', 'version', 'help' }) do
+ help_opt(k)
+ end
+ if mess then os.exit(1) else os.exit() end
+end
+
+function help_opt (o)
+ -- one line where possible
+ local indent_n = 12
+ local intent_sp = string.rep(' ', indent_n)
+ local indent_fmt = '%-' .. tostring(indent_n) .. 's'
+ v = opts[o]
+ if v=='pdftops_prog' and (os.type=='unix' or is_tl_w) then
+ return
+ end
+ if v and v.help then
+ local synt = join(v.forms, ', ')
+ if v.type ~= 'boolean' then synt = synt .. ' ' .. v.placeholder end
+ if string.len(synt)<indent_n then
+ print(string.format(indent_fmt, synt) .. v.help)
+ else
+ print(synt)
+ print(intent_sp .. v.help)
+ end
+ if v.negforms then
+ local neghelp = 'Reverses the above'
+ synt = join(v.negforms, ', ')
+ if string.len(synt)<indent_n then
+ print(string.format(indent_fmt, synt) .. neghelp)
+ else
+ print(synt)
+ print(intent_sp .. neghelp)
+ end
+ end
+ end
+end
+
+function show_version ()
+ print('Epspdf version '..ep_version..'\nCopyright (c) '
+ ..ep_copyright..' Siep Kroonenberg')
+end
+
+-- gui: reading and writing settings -----------
+
+function gui(action)
+ -- use stdin for reading settings from gui, and stdout for writing
+ if action=='config_w' then
+ -- called at start of epspdftk
+ write_settings() -- to pipe epspdf => epspdftk
+ os.exit()
+ elseif action=='config_r' then
+ read_settings() -- from 'pipe' epspdftk => epspdf
+ write_settings(rcfile)
+ os.exit()
+ else
+ from_gui = true
+ end
+end
+
+-- besides settings, which can be saved, we also use options which are not.
+-- we already have an options table with sole entry 'safer'
+-- the pdf output settings are converted to options array elements
+
+options.page = false
+options.gray = false
+options.bbox = false
+options.info = false
+options.debug = false
+options.type = false -- implied via output filename on command line
+
+-- boundingboxes ---------------------------------------------------
+
+-- Bb.coords names now same as those of epdf PDFRectangle
+
+Bb = {}
+Bb.coords = {'x1', 'y1', 'x2', 'y2'}
+
+function Bb:from_rect(r)
+ for _,k in ipairs(self.coords) do
+ if not r[k] or type(r[k])~='number' then
+ errror('from_rect called with illegal parameters')
+ end
+ -- sanity check on size
+ -- FIXME: this limit is far too high
+ if r[k]+.5==r[k] or r[k]-.5==r[k] then
+ errror('Bb:from_rect: ' .. r[k] ..' greater than maxint')
+ end
+ local b = {}
+ local eps = 0.000001
+ b.x1, b.x2 = math.floor(math.min(r.x1, r.x2) + eps),
+ math.ceil(math.max(r.x1, r.x2) - eps)
+ b.y1, b.y2 = math.floor(math.min(r.y1, r.y2) + eps),
+ math.ceil(math.max(r.y1, r.y2) - eps)
+ if b.x1==b.x2 or b.y1==b.y2 then
+ errror('from_rect: width or height is zero')
+ end
+ setmetatable(b, {__index=self})
+ return b
+ end
+end
+
+Bb.bb_pat = '^%s*%%%%BoundingBox:'
+Bb.bb_end = '^%s*%%%%BoundingBox:%s*%(%s*atend%s*%)'
+
+function Bb:from_comment(s)
+ local p = self.bb_pat..'%s*([-+%d]+)'..string.rep('%s+([-+%d]+)',3)
+ local b = {}
+ b.x1, b.y1, b.x2, b.y2 = string.match(s, p)
+ if not b.y2 then
+ errror('Bb.from_comment: illegal boundingbox string ' .. s)
+ end
+ for _,k in ipairs(self.coords) do
+ b[k] = tonumber(b[k])
+ end
+ return Bb:from_rect(b)
+end
+
+--[[
+
+function Bb:copy ()
+ local b = {}
+ for _,k in ipairs(self.coords) do b[k] = self[k] end
+ setmetatable(b, {__index=self})
+end
+
+function Bb:width()
+ return self.x2 - self.x1
+end
+
+function Bb:height()
+ return self.y2 - self.y1
+end
+
+function Bb:expand ()
+ -- in-place expansion; does not return an object.
+ -- any point in preserving non-negativity?
+ local i = settings.bb_spread
+ if i and i>0 then
+ -- if x1~=0 then x1 = x1-1 end
+ -- if y1~=0 then y1 = y1-1 end
+ self.x1 = self.x1 - 1
+ self.y1 = self.y1 - 1
+ self.x2 = self.x2 + 1
+ self.y2 = self.y2 + 1
+ end
+end
+
+-- no longer used: gs handles this
+-- call this via pcall
+function Bb:wrapper()
+ local fn = mktemp('ps')
+ local f = io.open(fn, 'wb')
+ f:write(string.format('%%%%BoundingBox: 0 0 %d %d\n',
+ self:width(), self:height())
+ .. string.format('<< /PageSize [%d %d] >> setpagedevice\n',
+ self:width(), self:height())
+ .. 'gsave\n'
+ .. string.format('%d %d translate\n', -self.x1, -self.y1))
+ f:close()
+ return fn
+end
+
+--]]
+
+function Bb:nonnegative ()
+ return self.x1>=0 and self.y1>=0
+end
+
+function Bb:comment()
+ -- if options.debug then print(debug.traceback()) end
+ return string.format('%%%%BoundingBox: %d %d %d %d',
+ self.x1, self.y1, self.x2, self.y2)
+end
+
+-- hires boundingboxes ---------------------------------------------
+
+HRBb = {}
+
+setmetatable(HRBb, {__index=Bb})
+
+function HRBb:from_rect(r)
+ for _,k in ipairs(self.coords) do
+ if not r[k] or type(r[k])~='number' then
+ errror('from_rect called with illegal parameters')
+ end
+ -- sanity check on size
+ if r[k]+.5==r[k] or r[k]-.5==r[k] then
+ errror('HRBb:from_rect: ' .. b[k] ..' greater than maxint')
+ end
+ local b = {}
+ b.x1, b.x2 = math.min(r.x1, r.x2), math.max(r.x1, r.x2)
+ b.y1, b.y2 = math.min(r.y1, r.y2), math.max(r.y1, r.y2)
+ if b.x1==b.x2 or b.y1==b.y2 then
+ errror('from_rect: width or height is zero')
+ end
+ setmetatable(b, {__index=self})
+ return b
+ end
+end
+
+HRBb.bb_pat = '^%s*%%%%HiResBoundingBox:'
+HRBb.bb_end = '^%s*%%%%HiResBoundingBox:%s*%(%s*atend%s*%)%s*$'
+
+function HRBb:from_comment(s)
+ -- dbg('hrbb from '..s)
+ local p = self.bb_pat..'%s*([-+.%deE]+)'..string.rep('%s+([-+.%deE]+)',3)
+ local b = {}
+ b.x1, b.y1, b.x2, b.y2 = string.match(s, p)
+ if not b.y2 then
+ errror('HRBb.from_comment: illegal boundingbox string ' .. s)
+ end
+ for _,k in ipairs(self.coords) do
+ b[k] = tonumber(b[k])
+ end
+ return HRBb:from_rect(b)
+end
+
+function HRBb:comment()
+ return string.format('%%%%HiResBoundingBox: %f %f %f %f',
+ self.x1, self.y1, self.x2, self.y2)
+end
+
+--[[
+
+function HRBb:expand ()
+ errror('HRBb:expand not available')
+end
+
+-- no longer used: gs handles this
+-- call this one also via pcall
+function HRBb:wrapper()
+
+ -- local fn = mktemp('ps')
+ -- local f = io.open(fn, 'wb')
+ -- f.write(string.format('<< /PageSize [%f %f] >> setpagedevice\n',
+ -- self.x2 - self.x1, self.y2 - self.y1))
+ -- f.write(string.format('gsave\n%f %f translate\n', -self.x1, -self.y1))
+ -- f:close()
+ -- return fn
+
+ return string.format(
+ '<< /PageSize [%f %f] >> setpagedevice gsave %f %f translate',
+ self.x2 - self.x1, self.y2 - self.y1, -self.x1, -self.y1)
+end
+
+--]]
+
+-- manipulating eps/ps/pdf files -----------------------------------
+
+-- command-line fragments for conversions
+-- We could make these `class attributes' for PsPdf but to what purpose?
+-- For Windows shell commands, we need to substitute `#' for `='
+-- when invoking Ghostscript. For simplicity, we do this across the board.
+
+gs_options = {gs_prog, '-q', '-dNOPAUSE', '-dBATCH', '-P-', '-dSAFER'}
+
+-- windows: use env vars rather than additional options
+-- may add custom options later
+
+
+
+pdf_options = {'-sDEVICE#pdfwrite'} -- '-dUseCIEColor' causes serious slowdown
+-- for final conversion to pdf;
+-- will be completed after reading settings and options
+gray_options = {'-dProcessColorModel#/DeviceGray',
+ '-sColorConversionStrategy#Gray'}
+-- below, '-f' guarantees that next string is interpreted as input file
+pdf_tailoptions = false -- to be set after option parsing
+
+pdftops = false
+-- gets a value if we are going to use pdftops
+
+ps_options = {'-level3'}
+-- may add custom options later
+
+
+function identify(path)
+ local f = io.open(path, 'rb')
+ if not f then
+ errror('Failure to open '..path..' for identification')
+ end
+ local filestart= f:read(23)
+ f:close()
+ if not filestart or filestart=='' then
+ return false
+ elseif string.match(filestart,'^\197\208\211\198') then -- c5 d0 d3 c6
+ return 'epsPreview'
+ elseif string.match(filestart,'^%%!PS%-Adobe%-%d%.%d EPSF%-%d%.%d') then
+ return 'eps'
+ elseif string.match(filestart,'^%%!PS%-Adobe%-%d%.%d') then
+ for _, p in ipairs({'.eps', '.epi', '.epsi', '.epsf'}) do
+ if string.sub(string.lower(path), -1-string.len(p),-1) == p then
+ return 'eps'
+ else
+ return 'ps'
+ end
+ end
+ return 'ps'
+ elseif string.match(filestart, '^%%PDF') then
+ return 'pdf'
+ else
+ return false
+ end
+end -- identify
+
+function pdf_props(path)
+ local pdfdoc = epdf.open(path)
+ if not pdfdoc then
+ errror('epdf.open failed on '..path)
+ end
+ local cat = pdfdoc:getCatalog()
+ if not cat then
+ errror('Cannot open pdf catalog of '..path)
+ end
+ local pg = cat:getNumPages()
+ if not pg then
+ errror('Cannot read n. of pages of '..path)
+ end
+ local maver = pdfdoc:getPDFMajorVersion()
+ if not maver then
+ errror('Cannot read pdf major version of '..path)
+ end
+ local miver = pdfdoc:getPDFMinorVersion()
+ if not miver then
+ errror('Cannot read pdf minor version of '..path)
+ end
+ if maver > 1 then
+ print(path..' has pdf major version \n'..tostring(maver)..
+ ' which is unsupported;\n'..
+ 'Continuing with fingers crossed...')
+ end
+ return pg, miver, maver
+end
+
+function info (infile)
+ local intype = identify(infile)
+ if not intype then
+ print(infile..' has an unsupported filetype.')
+ elseif intype~='pdf' then
+ print(infile..' has type '..intype..'.')
+ else
+ local pg, miver, maver = pdf_props(infile)
+ print(infile..' has type pdf, version '..tostring(maver)..
+ '.'..tostring(miver)..' and has '..tostring(pg)..' pages.')
+ end
+ os.exit()
+end
+
+-- PsPdf object -------------------------------------------------
+
+PsPdf = {}
+
+-- creators
+
+function PsPdf:new(ext)
+ -- dbg('PsPdf:new')
+ local psp = {}
+ setmetatable(psp, {__index = self})
+ -- assign temp file
+ psp.path = mktemp(string.lower(ext))
+ if string.lower(ext)=='pdf' then
+ psp.type = 'pdf'
+ elseif string.lower(ext)=='eps' then
+ psp.type = 'eps'
+ elseif string.lower(ext)=='ps' then
+ psp.type = 'ps'
+ else
+ psp.type = false
+ end
+ if psp.type=='eps' then
+ psp.pages = 1
+ end
+ psp.bb = false
+ psp.hrbb = false
+ return psp
+end -- PsPdf:new
+
+function PsPdf:from_path(path)
+ -- dbg('PsPdf:from_path')
+ local psp = {}
+ setmetatable(psp, {__index = self})
+ psp.path = path
+ if lfs.isfile(path) then
+ -- turn existing file into PsPdf object.
+ psp.type = identify(psp.path)
+ if psp.type=='pdf' then
+ psp.pages, psp.miver, psp.maver = pdf_props(psp.path)
+ end
+ else
+ errror('PsPdf:from_path called with non-existant file '..path)
+ end
+ if psp.type=='eps' then
+ psp.pages = 1
+ end
+ psp.bb = false
+ psp.hrbb = false
+ -- calculate when needed
+ return psp
+end -- PsPdf:from_path
+
+--[===[ getting boundingbox property from file itself --------------
+
+find_bb_simple: use only for eps PsPdf objects we generated
+ourselves, so we can assume that the bbox comments are in the header
+and the hires bb lies within the lores bb.
+Of course the file itself is not rewritten.
+
+--]===]
+
+function PsPdf:find_bb_simple()
+ -- dbg('PsPdf:find_bb_simple')
+ if self.type~='eps' then
+ errror('find_bb_simple called with non-eps file '..self.path)
+ end
+ self.bb = false
+ self.hrbb = false
+ local slurp = false
+ local f = io.open(self.path, 'rb')
+ if f then
+ slurp = f:read(bufsize)
+ f:close()
+ end
+ lines = {}
+ for l in string.gmatch(slurp, '[^\n\r]+') do
+ if string.match(l, Bb.bb_pat) then
+ self.bb = Bb:from_comment(l)
+ elseif string.match(l, HRBb.bb_pat) then
+ self.hrbb = HRBb:from_comment(l)
+ elseif self.bb then
+ break -- stop looking; we expect hrbb next to bb
+ end
+ if self.bb and self.hrbb then break end
+ end
+ if not self.bb then
+ errror('No valid boundingbox for generated file' .. self.path)
+ end
+ return self -- no real need for a return value
+end
+
+function PsPdf:bb_from_gs(pg)
+
+ -- dbg('bb_from_gs '..pg)
+
+ if self.type=='ps' then
+ errror('bb_from_gs called with ps file '..self.path)
+ -- not needed for generic PostScript,
+ -- page selection only works with pdf files, so we save ourselves
+ -- the trouble of picking the right bbox from a list
+ end
+ if self.type=='eps' and not self.bb:nonnegative() then
+ errror('bb_from_gs called on ' .. self.path ..
+ ' which has some negative boundingbox coordinates')
+ end
+ -- A pdf can also have negative ...Box coordinates, but apparently
+ -- for pdf the bbox returned by gs is relative to the lower-left corner.
+ -- Anyhow, with pdf it all works out even with negative coordinates.
+
+ -- Since Ghostscript writes the boundingbox comments to stderr,
+ -- we need a shell to intercept this output:
+
+ local bb_file = mktemp('dsc')
+ local cmdline = table.concat(gs_options,' ')
+ if self.type=='pdf' then
+ if not pg then pg=1 end
+ cmdline = cmdline .. ' -dFirstPage#' .. tostring(pg) ..
+ ' -dLastPage#' .. tostring(pg)
+ end
+ cmdline = cmdline .. ' -sDEVICE#bbox ' .. self.path .. ' 2>'..bb_file
+
+ -- execute shell command
+
+ local r, cmd
+ if os.type=='windows' then
+ -- redirection does not work right for os.execute on TL/w32 <= 2011
+ -- but it does when calling the cmd shell explicitly
+ cmd = {'cmd', '/c', cmdline}
+ log_cmd(cmd)
+ r = os.spawn(cmd)
+ else
+ write_log('os.execute: '..cmdline)
+ r = os.execute(cmdline)
+ end
+ if not r then
+ errror('Cannot get fixed boundingbox for '..self.path)
+ end
+
+ -- read new bbox from ghostscript output
+ -- can we really count on the plain bb coming first?
+ -- OTOH, I would rather not introduce unnecessary complexity
+ -- still, it may be better to match each line with [HR]Bb_pat
+
+ local bb = false
+ local hrbb = false
+ local fin = io.open(bb_file, 'r')
+ if fin then
+ for i=1,10 do -- actually, 2 should suffice
+ local l = fin:read("*line")
+ if not l then break end
+ if string.match(l, Bb.bb_pat) then
+ bb = Bb:from_comment(l)
+ end
+ if string.match(l, HRBb.bb_pat) then
+ hrbb = HRBb:from_comment(l)
+ end
+ end
+ fin:close()
+ end
+ if not bb or not hrbb then
+ errror('Cannot get fixed boundingbox for '..self.path)
+ end
+ return bb, hrbb
+end
+
+-- eps_clean: remove some problem features from eps (new file & object)
+
+function PsPdf:eps_clean()
+
+ -- return a PsPdf object referring to a new file
+ -- without a preview header and with boundingbox(es) in the header
+
+ local function bytes2num (s, i)
+ -- convert substring s[i..i+3] to a number.
+ -- by working byte for byte we avoid endian issues
+ local n = string.byte(s, i+3)
+ for j=2,0,-1 do n = 256*n + string.byte(s, i+j) end
+ return n
+ -- somehow the explicit expression below didn't work
+ -- return ((256 * (256 * (256 * string.byte(s,i+3)) + string.byte(s,i+2))
+ -- + string.byte(s,i+1)) + string.byte(s,i))
+ end
+
+ dbg('PsPdf:eps_clean '..self.path)
+ if self.type~='eps' and self.type~='epsPreview' then
+ errror('epsclean called with non-eps file ' .. self.path)
+ end
+ local offset, ps_length = false, false
+ local fin, fout
+ if self.type=='eps' then
+ offset = 0
+ ps_length = lfs.attributes(self.path, 'size')
+ else
+ -- read TOC; see Adobe EPS specification
+ -- interpret byte for byte, in case the platform is not little-endian
+ fin = io.open(self.path, 'rb')
+ if fin then
+ local toc = fin:read(12)
+ fin:close()
+ if toc and string.len(toc)==12 then
+ offset = bytes2num(toc, 5)
+ ps_length = bytes2num(toc, 9)
+ end
+ end
+ if not offset then
+ errror('Could not read preview header of ' .. self.path)
+ end
+ -- dbg(tostring(offset)..' '..tostring(ps_length))
+ end
+
+ -- create the PsPdf object which is to be returned
+
+ local psp
+ psp = PsPdf:new('eps')
+ -- dbg(psp.path)
+
+ -- read an initial and if necessary a final chunk of the file
+ -- to find boundingbox comments.
+
+ local atend = false
+ local hr_atend = false
+ local slurp -- the read buffer
+ local l -- contains current scanned line; split off from slurp
+ -- pre_lines: scanned header lines; alternately lines and eols
+ local pre_lines = {}
+ -- new_offset: offset plus combined length of scanned header lines
+ local new_offset = offset
+ -- post_lines: scanned trailer lines
+ local post_lines = {}
+ -- middle_length: ps_length minus scanned header- and and maybe trailer parts
+ -- this is the length of file that will be copied wholesale.
+ local middle_length
+ local i, i_bb, i_hrbb
+ local j, j_bb, j_hrbb, j_end
+ -- j_end: index of final scanned trailer line
+ -- no i_end necessary: for header lines we can use #pre_lines.
+
+ fin = io.open(self.path, 'rb')
+ if not fin then errror('Cannot read '..self.path) end
+ fin:seek('set', offset)
+
+ -- remaining, unscanned length of input buffer slurp
+ local unscanned = math.min(ps_length,bufsize)
+ -- dbg('bytes to be read: '..tostring(unscanned))
+ slurp = fin:read(unscanned)
+ -- dbg('Read from '..self.path..': '..string.len(slurp)..' bytes')
+
+ -- unnecessary:
+ psp.bb = nil
+ psp.hrbb = nil
+
+ i, i_bb, i_hrbb = 0, false, false
+ while unscanned>0 do
+ i = i+1
+ if string.find(slurp,'[\n\r]')==1 then
+ l,slurp = string.match(slurp, '^([\n\r]+)(.*)$')
+ else
+ l,slurp = string.match(slurp, '^([^\n\r]+)(.*)$')
+ if string.match(l, Bb.bb_end) then
+ atend = true
+ i_bb = i
+ elseif string.match(l, Bb.bb_pat) then
+ -- dbg(l)
+ psp.bb = Bb:from_comment(l)
+ -- dbg(psp.bb:comment())
+ -- from_comment errors out on failure; no need to check return value
+ i_bb = i
+ elseif string.match(l, HRBb.bb_end) then
+ hr_atend = true
+ i_hrbb = i
+ elseif string.match(l, HRBb.bb_pat) then
+ -- dbg(l)
+ psp.hrbb = HRBb:from_comment(l)
+ -- dbg(psp.hrbb:comment())
+ i_hrbb = i
+ end -- bbox line
+ end -- eol/non-eol
+ pre_lines[i] = l
+ unscanned = unscanned - string.len(l)
+ if (i_bb and (i_hrbb or (i_bb<(i-1)))) or unscanned<=0 then
+ -- condition i_bb<i-1:
+ -- We do not want to find the hrbb of an included eps.
+ -- Therefore we stop looking for hrbb if it is not next to bb
+ -- (with an intervening eol)
+ -- Note that the header comments are not necessarily terminated
+ -- with a %%EndComments line; see Adobe DSC spec 5001
+ break
+ end -- deciding whether to stop
+ end -- while
+ new_offset = offset + string.len(table.concat(pre_lines))
+ middle_length = ps_length - string.len(table.concat(pre_lines))
+
+ if atend or hr_atend then
+ -- find boundingbox comments, starting from end of postscript
+ if ps_length>bufsize then
+ fin:seek('set',offset+ps_length-bufsize)
+ unscanned = bufsize
+ slurp = fin:read(unscanned)
+ else
+ -- use what is left from old slurp
+ unscanned = string.len(slurp)
+ end
+ j = 1 -- count down from 0
+ j_bb, j_hrbb, j_end = false, false, false
+ while unscanned>0 do
+ j = j - 1
+ -- dbg(j)
+ if string.find(slurp,'[\n\r]', string.len(slurp)) then
+ -- dbg('eol(s)')
+ slurp,l = string.match(slurp, '^(.-)([\n\r]+)$')
+ -- '-': non-greedy matching
+ else
+ slurp,l = string.match(slurp, '^(.-)([^\n\r]+)$')
+ -- dbg(l)
+ if string.match(l, Bb.bb_pat) then
+ psp.bb = Bb:from_comment(l)
+ j_bb = j
+ elseif string.match(l, HRBb.bb_pat) then
+ psp.hrbb = HRBb:from_comment(l)
+ j_hrbb = j
+ end -- bbox line
+ end -- eol/non-eol
+ post_lines[j] = l
+ unscanned = unscanned - string.len(l)
+ if (psp.bb and
+ (psp.hrbb or not hr_atend or j_bb>(j+1))) or unscanned<=0 then
+ -- stop looking
+ j_end = j
+ break
+ end -- deciding whether to stop
+ end -- while
+ middle_length = middle_length -
+ string.len(table.concat(post_lines, '', j_end, 0))
+ end --if atend
+ fin:close()
+ -- fix boundingbox lines
+ if atend and j_bb then
+ -- pre_lines[i_bb] = post_lines[j_bb]
+ pre_lines[i_bb] = psp.bb:comment() -- WHY DOESNT THIS WORK ????
+ post_lines[j_bb] = ''
+ post_lines[j_bb+1] = ''
+ end
+ if hr_atend and j_hrbb then
+ -- dbg(psp.hrbb:comment())
+ -- pre_lines[i_hrbb] = post_lines[j_hrbb]
+ pre_lines[i_hrbb] = psp.hrbb:comment()
+ post_lines[j_hrbb] = ''
+ post_lines[j_hrbb+1] = ''
+ end
+ -- create cleaned eps file
+ fout = io.open(psp.path, 'wb')
+ if not fout then errror('Cannot create new file '..psp.path) end
+ fout:write(table.concat(pre_lines))
+ fout:close()
+ slice_file(self.path, psp.path, middle_length, new_offset, 'ab')
+ fout = io.open(psp.path, 'ab')
+ fout:write(table.concat(post_lines, '', j_end, 0))
+ fout:close()
+ return psp
+end -- eps_clean
+
+-- tight boundingbox (new file & object)
+
+function PsPdf:eps_crop()
+
+ -- not a proper conversion, although
+ -- we use the Ghostscript bbox device for a tight boundingbox.
+ -- We use both the regular and the hires boundingbox from gs.
+ -- The eps should already have been cleaned up by eps_clean,
+ -- and the current boundingbox should not contain negative coordinates,
+ -- otherwise the bbox output device may give incorrect results.
+ -- Only the boundingbox in the eps is rewritten.
+
+ dbg('PsPdf:eps_crop '..self.path)
+ if self.type~='eps' then
+ errror('eps_crop called with non-eps file ' .. self.path)
+ end
+
+ -- create the PsPdf object which is to be returned
+
+ local psp = PsPdf:new('eps')
+
+ -- read new bbox from ghostscript output
+
+ psp.bb, psp.hrbb = self:bb_from_gs()
+
+ -- rewrite header with new boundingboxes
+
+ local slurp -- the read buffer
+ local l -- contains current scanned line; split off from slurp
+ -- pre_lines: scanned header lines; alternately lines and eols
+ local pre_lines = {}
+ -- offset: combined length of scanned header lines
+ local offset = 0
+ local ps_length = lfs.attributes(self.path, 'size')
+ local i, i_bb, i_hrbb
+
+ fin = io.open(self.path, 'rb')
+ if not fin then errror('Cannot read '..self.path) end
+
+ -- remaining, unscanned length of input buffer slurp
+ local unscanned = math.min(ps_length,bufsize)
+ -- dbg('bytes to be read: '..tostring(unscanned))
+ slurp = fin:read(unscanned)
+ -- dbg('Read from '..self.path..': '..string.len(slurp)..' bytes')
+ i, i_bb, i_hrbb = 0, false, false
+ while unscanned>0 do
+ i = i+1
+ if string.find(slurp,'[\n\r]')==1 then
+ l,slurp = string.match(slurp, '^([\n\r]+)(.*)$')
+ else
+ l,slurp = string.match(slurp, '^([^\n\r]+)(.*)$')
+ if string.match(l, Bb.bb_pat) then
+ i_bb = i
+ elseif string.match(l, HRBb.bb_pat) then
+ i_hrbb = i
+ end -- bbox line
+ end -- eol/non-eol
+ pre_lines[i] = l
+ unscanned = unscanned - string.len(l)
+ if (i_bb and (i_hrbb or (i_bb<(i-1)))) or unscanned<=0 then
+ break
+ end
+ end -- while
+ fin:close()
+ offset = string.len(table.concat(pre_lines))
+
+ if i_hrbb then
+ pre_lines[i_bb] = psp.bb:comment()
+ pre_lines[i_hrbb] = psp.hrbb:comment()
+ else
+ -- jam both bbox comments into one slot, with an intervening eol.
+ -- for the sake of conformity, we copy an existing eol.
+ pre_lines[i_bb] = psp.bb:comment() .. pre_lines[i_bb-1] ..
+ psp.hrbb:comment()
+ end
+
+ -- write a new eps file
+
+ fout = io.open(psp.path, 'wb')
+ if not fout then errror('Cannot write new file '.. psp.path) end
+ fout:write(table.concat(pre_lines))
+ fout:close()
+ slice_file(self.path, psp.path,
+ lfs.attributes(self.path,'size') - offset, offset, 'ab')
+ options.bbox = false
+ -- dbg('eps_crop from '..self.path..' to '..psp.path)
+ return psp
+end -- eps_crop
+
+--[===[ real conversions involving a single call of gs or pdftops --------
+
+Each conversion fullfills all options that it can: gray, bbox and
+page. gray when converting to pdf, bbox when converting from eps or
+from pdf to pdf and page when converting from pdf. It then sets the
+fullfilled option(s) to false.
+
+We like to preserve fonts as fonts. gs does this when generating
+pdf, but may fail for fonts such as cid and large truetype when
+generating PostScript. In such cases, pdftops may succeed. However,
+it seems that if the page contains an element that does not cleanly
+convert, pdftops simply rasterizes the entire page, and that this
+choice is made per page.
+
+TODO: pdf => pdf with bbox via pdftex, as in pdfcrop utility
+
+--]===]
+
+-- TODO: multiple pages
+-- (means additional parameter checking)
+
+-- Converting from pdf to pdf using luatex; no grayscaling
+
+function PsPdf:pdf_crop()
+
+ -- options to be fulfilled: page, boundingbox
+ -- only called directly.
+ -- embeds the pdf with crop parameters into a new (lua)tex document
+ if not (options.bbox or options.page) then
+ return self
+ end
+ if options.page and options.page > self.pages then
+ errror('PsPdf:pdf_crop called with non-existent page '.. options.page)
+ end
+ local pg = options.page or 1
+ local bb, hrbb
+ if options.bbox then
+ bb, hrbb = self:bb_from_gs(pg)
+ else
+ -- use [Trim|Crop|Media]Box instead
+ local dummy = epdf.open(self.path)
+ if not dummy then
+ errror('Epdf: cannot open '..self.path)
+ end
+ dummy = dummy:getCatalog()
+ if not dummy then
+ errror('Cannot open catalog of '..self.path)
+ end
+ dummy = dummy:getPage(pg)
+ if not dummy then
+ errror('Epdf: cannot open page object '..tostring(pg)..' of '..self.path)
+ end
+ hrbb = dummy:getTrimBox()
+ if not hrbb then
+ hrbb = dummy:getCropBox()
+ end
+ if not hrbb then
+ hrbb = dummy:getMediaBox()
+ end
+ -- further checks, including for non-nil, by Bb:from_rect,
+ -- which errors out on failures
+ hrbb = HRBb:from_rect(hrbb)
+ end
+
+ -- location of luatex
+ local luatex_prog = fw(os.selfdir) .. '/luatex' -- absolute path
+ if os.type == 'windows' then
+ luatex_prog = luatex_prog .. '.exe'
+ end
+
+ -- write TeX file which includes cropped pdf page
+ -- adapted from Heiko Oberdiek's pdfcrop utility.
+ -- first, create a table with the component strings for the tex source
+ dummy = {}
+ dummy[1] = [[
+\catcode37 14 % percent
+\catcode33 12 % exclam
+\catcode34 12 % quote
+\catcode35 6 % hash
+\catcode39 12 % apostrophe
+\catcode40 12 % left parenthesis
+\catcode41 12 % right parenthesis
+\catcode45 12 % minus
+\catcode46 12 % period
+\catcode60 12 % less
+\catcode61 12 % equals
+\catcode62 12 % greater
+\catcode64 12 % at
+\catcode91 12 % left square
+\catcode93 12 % right square
+\catcode96 12 % back tick
+\catcode123 1 % left curly brace
+\catcode125 2 % right curly brace
+\catcode126 12 % tilde
+\catcode`\#=6 %
+\escapechar=92 %
+\def\IfUndefined#1#2#3{%
+ \begingroup\expandafter\expandafter\expandafter\endgroup
+ \expandafter\ifx\csname#1\endcsname\relax
+ #2%
+ \else
+ #3%
+ \fi
+}
+\begingroup
+ \newlinechar=10 %
+ \endlinechar=\newlinechar %
+ \ifnum0%
+ \directlua{%
+ if tex.enableprimitives then
+ tex.enableprimitives('TEST', {
+ 'luatexversion',
+ 'pdfoutput',
+ 'pdfcompresslevel',
+ 'pdfhorigin',
+ 'pdfvorigin',
+ 'pdfpagewidth',
+ 'pdfpageheight',
+ 'pdfmapfile',
+ 'pdfximage',
+ 'pdflastximage',
+ 'pdfrefximage',
+ 'pdfminorversion',
+ 'pdfobjcompresslevel',
+ })
+ tex.print('1')
+ end
+ }%
+ \ifx\TESTluatexversion\UnDeFiNeD\else 1\fi %
+ =11 %
+ \global\let\luatexversion\luatexversion %
+ \global\let\pdfoutput\TESTpdfoutput %
+ \global\let\pdfcompresslevel\TESTpdfcompresslevel %
+ \global\let\pdfhorigin\TESTpdfhorigin %
+ \global\let\pdfvorigin\TESTpdfvorigin %
+ \global\let\pdfpagewidth\TESTpdfpagewidth %
+ \global\let\pdfpageheight\TESTpdfpageheight %
+ \global\let\pdfmapfile\TESTpdfmapfile %
+ \global\let\pdfximage\TESTpdfximage %
+ \global\let\pdflastximage\TESTpdflastximage %
+ \global\let\pdfrefximage\TESTpdfrefximage %
+ \global\let\pdfminorversion\TESTpdfminorversion %
+ \global\let\pdfobjcompresslevel\TESTpdfobjcompresslevel %
+ \else %
+ \errmessage{%
+ Missing \string\luatexversion %
+ }%
+ \fi %
+\endgroup %
+
+\pdfoutput=1 %
+\pdfcompresslevel=9 %
+\csname pdfmapfile\endcsname{}
+\def\setpdfversion#1#2{%
+ \ifnum#2>1 %
+ \pdfobjcompresslevel=2 %
+ % including unsupported pdf version!
+ \pdfinclusionerrorlevel=0
+ \pdfminorversion=9\relax
+ \else
+ \ifnum#1>4 %
+ \pdfobjcompresslevel=2 %
+ \else
+ \pdfobjcompresslevel=0 %
+ \fi
+ \pdfminorversion=#1\relax
+ \fi
+}
+\def\page #1 [#2 #3 #4 #5]{%
+ \count0=#1\relax
+ \setbox0=\hbox{%
+ \pdfximage page #1 mediabox{]]
+ dummy[2] = self.path
+ dummy[3] = [[}%
+ \pdfrefximage\pdflastximage
+ }%
+ \pdfhorigin=-#2bp\relax
+ \pdfvorigin=#3bp\relax
+ \pdfpagewidth=#4bp\relax
+ \advance\pdfpagewidth by -#2bp\relax
+ \pdfpageheight=#5bp\relax
+ \advance\pdfpageheight by -#3bp\relax
+ \ht0=\pdfpageheight
+ \shipout\box0\relax
+}
+]]
+ -- pdf minor version to write to tex header
+ local tex_miver = false
+ if settings.pdf_version=='default' then
+ tex_miver = self.miver
+ else
+ -- in this case, gs should already have converted to
+ -- a sufficiently low version
+ tex_miver = tonumber(settings.pdf_version)
+ if tex_miver>self.miver then
+ errror('Pdf_crop: forgot to reduce pdf version')
+ end
+ end
+ dummy[4] = string.format([[
+\setpdfversion{%d}{%d}
+\page %d [%f %f %f %f]
+\csname @@end\endcsname
+\end
+]],
+ tex_miver, self.maver, options.page or 1,
+ hrbb.x1, hrbb.y1, hrbb.x2, hrbb.y2)
+
+ local textemp = mktemp('tex') -- this also takes care of pdf:
+ local pdftemp = string.gsub(textemp, 'tex$', 'pdf')
+ local f = io.open(textemp, 'w')
+ f:write(table.concat(dummy, ''))
+ f:close()
+ local cmd, res, psp
+ cmd = {luatex_prog, '--safer', '--no-shell-escape', textemp}
+ log_cmd(cmd)
+ res = os.spawn(cmd)
+ if res and res==0 and lfs.attributes(pdftemp, 'size')>0 then
+ psp = PsPdf:from_path(pdftemp)
+ return psp
+ else
+ errror('pdf_crop failed on '..self.path)
+ end
+end
+
+function PsPdf:eps_to_pdf()
+
+ -- option to be fulfilled: gray
+ -- set target and pdf version if applicable
+ -- dbg('PsPdf:eps_to_pdf')
+ if self.type~='eps' then
+ errror('PsPdf:eps_to_pdf called for non-eps file '.. self.path)
+ end
+ local cmd
+ if options.bbox and self.bb:nonnegative() then
+ self = self:eps_crop() -- this sets options.bbox to false
+ end
+ cmd = tab_combine({gs_options, pdf_options})
+ -- dbg(table.concat(cmd,' '))
+ if options.gray then
+ cmd = tab_combine({cmd, gray_options})
+ -- dbg(table.concat(cmd,' '))
+ options.gray = false
+ end
+ table.insert(cmd, '-dEPSCrop') -- always hires bb
+ -- dbg(table.concat(cmd,' '))
+ local psp = PsPdf:new('pdf')
+ table.insert(cmd, '-sOutputFile#'..psp.path)
+ -- dbg(table.concat(cmd,' '))
+ cmd = tab_combine({cmd, pdf_tailoptions, {self.path}})
+ -- dbg(table.concat(cmd,' '))
+ log_cmd(cmd)
+ local res = os.spawn(cmd)
+ if res and res==0 and lfs.attributes(psp.path, 'size')>0 then
+ psp.pages, psp.miver, psp.maver = pdf_props(psp.path)
+ return psp
+ else
+ errror('eps_to_pdf failed on '..self.path)
+ end
+end -- eps_to_pdf
+
+-- Converting from pdf to pdf with grayscaling and/or page selection
+
+function PsPdf:pdf_to_pdf()
+
+ -- option to be fulfilled: gray and optionally page.
+ -- do not call this just for page selection because
+ -- pdf_crop can do this in a less invasive manner
+ -- dbg('PsPdf:pdf_to_pdf')
+ if self.type~='pdf' then
+ errror('PsPdf:pdf_to_pdf called for non-pdf file '.. self.path)
+ end
+ local cmd
+ if options.page and options.page > self.pages then
+ errror('PsPdf:pdf_to_pdf called with non-existent page '.. options.page)
+ end
+ cmd = tab_combine({gs_options, pdf_options})
+ -- dbg(table.concat(cmd,' '))
+ if options.gray then
+ cmd = tab_combine({cmd, gray_options})
+ -- dbg(table.concat(cmd,' '))
+ options.gray = false
+ end
+ if options.page then
+ table.insert(cmd, '-dFirstPage#'..tostring(options.page))
+ table.insert(cmd, '-dLastPage#'..tostring(options.page))
+ -- dbg(table.concat(cmd,' '))
+ options.page = false
+ end
+ local psp = PsPdf:new('pdf')
+ table.insert(cmd, '-sOutputFile#'..psp.path)
+ cmd = tab_combine({cmd, pdf_tailoptions})
+ -- dbg(table.concat(cmd,' '))
+ table.insert(cmd, self.path)
+ -- dbg(table.concat(cmd,' '))
+ log_cmd(cmd)
+ local res = os.spawn(cmd)
+ if res and res==0 and lfs.attributes(psp.path, 'size')>0 then
+ psp.pages, psp.miver, psp.maver = pdf_props(psp.path)
+ return psp
+ else
+ errror('pdf_to_pdf failed on '..self.path)
+ end
+end -- pdf_to_pdf
+
+function PsPdf:pdf_to_eps()
+
+ -- options to be fulfilled: bbox and page
+ -- dbg(tostring(settings.pdftops_prog))
+ local psp = PsPdf:new('eps')
+ local cmd, res
+ local page = false
+ if self.pages>1 then
+ page = 1
+ if options.page then page = options.page end
+ if options.page and options.page > self.pages then
+ errror('PsPdf:pdf_to_eps called with non-existant page '.. options.page)
+ end
+ page = tostring(page)
+ end
+ if pdftops then
+ if page then
+ cmd = tab_combine({{pdftops}, ps_options,
+ {'-f', page, '-l', page,
+ '-eps', self.path, psp.path}})
+ else
+ cmd = tab_combine({{pdftops}, ps_options,
+ {'-eps', self.path, psp.path}})
+ end
+ options.page = false
+ log_cmd(cmd)
+ if os.type=='windows' then
+ -- suppress console output of 'No display font for...' messages,
+ -- which are usually harmless and for which I know no easy fix
+ res = os.spawn({'cmd', '/c', table.concat(cmd, ' ')..' 2>>'..log_bsl})
+ else
+ res = os.spawn(cmd)
+ end
+ if res and res==0 and lfs.attributes(psp.path, 'size')>0 then
+ psp.pages = 1
+ else
+ errror('pdf_to_eps failed on '..self.path)
+ end
+ -- fix for incorrect DSC header produced by some versions of pdftops:
+ -- if necessary, change line `% Produced by ...' into `%%Produced by ...'
+ -- this is usually the second line.
+ -- otherwise the DSC header would be terminated before the bbox comment.
+ -- this problem exists with pdftops from TL2011/w32.
+ local slurp -- input buffer
+ local fin = io.open(psp.path, 'rb')
+ if not fin then errror('Cannot read '..psp.path) end
+
+ -- remaining, unscanned length of input buffer slurp
+ local unscanned = math.min(lfs.attributes(psp.path, 'size'),bufsize)
+ slurp = fin:read(unscanned)
+ local i, i_bb = 0, false
+ local needs_fixing = false
+ local pre_lines = {}
+ local offset = 0
+ while unscanned>0 do
+ i = i+1
+ if string.find(slurp,'[\n\r]')==1 then
+ l,slurp = string.match(slurp, '^([\n\r]+)(.*)$')
+ else
+ l,slurp = string.match(slurp, '^([^\n\r]+)(.*)$')
+ if string.match(l, Bb.bb_pat) then
+ -- bbox line
+ i_bb = i
+ elseif string.match(l, '^%%%s') then -- `%' is escape char: doubled
+ -- %X with X printable would be ok
+ needs_fixing = true
+ -- fix rightaway
+ l = string.gsub(l, '^%%%s', '%%%%') -- same length
+ end
+ end -- eol/non-eol
+ pre_lines[i] = l
+ unscanned = unscanned - string.len(l)
+ offset = offset + string.len(l)
+ if i_bb then break end
+ end -- while
+ fin:close()
+ if needs_fixing then
+ -- write a new eps file
+ local newfile = mktemp('eps')
+ fout = io.open(newfile, 'wb')
+ if not fout then errror('Cannot write new file '.. newfile) end
+ fout:write(table.concat(pre_lines))
+ fout:close()
+ slice_file(psp.path, newfile,
+ lfs.attributes(psp.path,'size') - offset, offset, 'ab')
+ psp.path = newfile
+ end -- needs_fixing
+ else -- use ghostscript
+ cmd = tab_combine({gs_options,
+ {'-sDEVICE#epswrite', '-dLanguageLevel#3'}})
+ -- the restrictions on eps files are apparently
+ -- incompatible with grayscaling
+ if options.page then
+ table.insert(cmd, '-dFirstPage='..page)
+ table.insert(cmd, '-dLastPage='..page)
+ end
+ table.insert(cmd, '-sOutputFile='..psp.path)
+ table.insert(cmd, self.path)
+ options.page = false
+ log_cmd(cmd)
+ res = os.spawn(cmd)
+ if res and res==0 and lfs.attributes(psp.path, 'size')>0 then
+ psp.pages = 1
+ else
+ errror('pdf_to_eps failed on '..self.path)
+ end
+ end -- use ghostscript
+ psp:find_bb_simple()
+ if options.bbox then psp = psp:eps_crop() end
+ return psp
+
+end -- pdf_to_eps
+
+function PsPdf:ps_to_pdf()
+
+ -- options to be fulfilled: gray
+ -- dbg('PsPdf:ps_to_pdf')
+ if self.type~='ps' then
+ errror('PsPdf:ps_to_pdf called for non-ps file '.. self.path)
+ end
+ local cmd
+ cmd = tab_combine({gs_options, pdf_options})
+ if options.gray then
+ cmd = tab_combine({cmd, gray_options})
+ options.gray = false
+ end
+ local psp = PsPdf:new('pdf')
+ table.insert(cmd, '-sOutputFile#'..psp.path)
+ cmd = tab_combine({cmd, pdf_tailoptions})
+ table.insert(cmd, self.path)
+ log_cmd(cmd)
+ local res = os.spawn(cmd)
+ if res and res==0 and lfs.attributes(psp.path, 'size')>0 then
+ psp.pages, psp.miver, psp.maver = pdf_props(psp.path)
+ return psp
+ else
+ errror('ps_to_pdf failed on '..self.path)
+ end
+
+end -- PsPdf:ps_to_pdf
+
+function PsPdf:pdf_to_ps()
+
+ -- options to be fulfilled: page and, if not using pdftops, also gray
+ -- dbg('PsPdf:pdf_to_ps')
+ local psp = PsPdf:new('ps')
+ local page = false
+ if self.pages>1 then
+ if options.page and options.page > self.pages then
+ errror('PsPdf:pdf_to_ps called with non-existant page '.. options.page)
+ elseif options.page then
+ page = tostring(options.page)
+ psp.pages = 1
+ end
+ else
+ psp.pages = self.pages
+ end
+ local cmd, res
+ if pdftops then
+ cmd = tab_combine({{pdftops}, ps_options})
+ if page then
+ cmd = tab_combine({cmd, {'-f', page, '-l', page}})
+ end
+ cmd = tab_combine({cmd, {'-paper', 'match', self.path, psp.path}})
+ -- cmd[0] = pdftops
+ else -- use ghostscript
+ cmd = tab_combine({gs_options,
+ {'-sDEVICE#ps2write', '-dLanguageLevel#3'}})
+ if options.gray then
+ cmd = tab_combine({cmd, gray_options})
+ -- dbg(table.concat(cmd,' '))
+ options.gray = false
+ end
+ if page then
+ cmd = tab_combine({cmd, {'-dFirstPage#'..page, '-dLastPage#'..page}})
+ end
+ table.insert(cmd, '-sOutputFile#'..psp.path)
+ -- table.insert(cmd, '-f')
+ table.insert(cmd, self.path)
+ end
+ options.page = false
+ log_cmd(cmd)
+ -- if os.type=='windows' and pdftops and not is_miktex then
+ -- if os.type=='windows' and pdftops then
+ -- -- suppress console output of 'No display font for...' messages,
+ -- -- which are usually harmless and for which I know no easy fix
+ -- res = os.spawn({'cmd', '/c', table.concat(cmd, ' ')..' 2>>'..log_bsl})
+ -- else
+ res = os.spawn(cmd)
+ -- end
+ if res and res==0 and lfs.attributes(psp.path, 'size')>0 then
+ return psp
+ else
+ errror('pdf_to_ps failed on '..self.path)
+ end
+end -- PsPdf:pdf_to_ps
+
+function PsPdf:any_to_any()
+
+ -- weed out nonsense options
+
+ -- dbg('PsPdf:any_to_any')
+ if options.type=='ps' then
+ options.bbox = false
+ -- dbg('Ignoring bbox option for ps output')
+ end
+ if options.bbox and not options.page then
+ options.page = 1
+ -- dbg('Selecting page 1 for bbox')
+ end
+ if self.pages==1 then
+ options.page = false
+ -- dbg('dropping page selection; source is already a 1-page document')
+ end
+ -- for _,o in ipairs({'page', 'gray', 'bbox'}) do
+ -- -- if options[o] then dbg('Do option '..o) end
+ -- end
+
+ -- check source and destination filetypes
+
+ if not self.type then
+ errror('any_to_any: cannot convert; unsupported source filetype')
+ end
+ if not options.type or options.type=='epsPreview' then
+ errror('any_to_any: cannot convert; unsupported destination filetype')
+ end
+
+ -- `distiller' settings depend on whether final output is pdf
+ if options.type=='pdf' then
+ table.insert(pdf_options, '-dPDFSETTINGS#/'..settings.pdf_target)
+ if settings.pdf_version~='default' then
+ table.insert(pdf_options, '-dCompatibilityLevel#'..settings.pdf_version)
+ end
+ -- below, try <</NeverEmbed [/Times-Roman /TimesBold ...]>>
+ if settings.pdf_target=='screen' or settings.pdf_target=='ebook' then
+ pdf_tailoptions = {'-c',
+ '.setpdfwrite', '-f'}
+ -- -f ensures that the input filename is not added to the -c string
+ else
+ pdf_tailoptions = {'-c',
+ '.setpdfwrite <</NeverEmbed [ ]>> setdistillerparams', '-f'}
+ end
+ else
+ table.insert(pdf_options, '-dPDFSETTINGS#/default')
+ pdf_tailoptions = {'-c',
+ '.setpdfwrite <</NeverEmbed [ ]>> setdistillerparams', '-f'}
+ end
+
+ -- each single-step conversion takes care of options it can handle
+ -- and sets those options to false.
+ -- for boundingboxes, eps_crop is either called explicitly
+ -- or called implicitly by another converter.
+ -- pdf_crop is always called explicitly and always as the last step
+
+ local psp = self
+
+ if psp.type=='eps' or psp.type=='epsPreview' then
+ -- As a side effect of eps_clean, the modified source file is copied
+ -- to the temp subdirectory.
+ psp = psp:eps_clean()
+ if options.bbox and psp.bb:nonnegative() then
+ psp = psp:eps_crop()
+ end
+ if options.type=='eps' then
+ if options.gray or options.bbox then
+ -- bbox: eps_crop was apparently not applicable: pdf roundtrip
+ psp = psp:eps_to_pdf():pdf_to_eps()
+ end
+ elseif options.type=='pdf' then
+ psp = psp:eps_to_pdf()
+ if options.bbox then
+ psp = psp:pdf_crop()
+ end
+ elseif options.type=='ps' then
+ psp = psp:eps_to_pdf():pdf_to_ps()
+ end
+ return psp
+
+ elseif psp.type=='ps' then
+ -- preliminary:
+ -- copy infile to a file in the temp directory, for gs -dSAFER
+ psp.path = mktemp(psp.type)
+ slice_file(infile, psp.path)
+
+ -- actual conversion
+ if options.type=='eps' then
+ return psp:ps_to_pdf():pdf_to_eps()
+ elseif options.type=='pdf' then
+ if options.bbox or options.page then
+ return psp:ps_to_pdf():pdf_crop()
+ else
+ return psp:ps_to_pdf()
+ end
+ elseif options.type=='ps' then
+ if options.gray or options.page then
+ return psp:ps_to_pdf():pdf_to_ps()
+ else
+ return psp -- no conversion necessary
+ end
+ end -- pdf => ps
+
+ elseif psp.type=='pdf' then
+ -- preliminary:
+ -- copy infile to a file in the temp directory, for gs -dSAFER
+ psp.path = mktemp(psp.type)
+ slice_file(infile, psp.path)
+
+ -- actual conversion
+ if options.type=='eps' then
+ if options.gray then
+ -- one-step grayscaling available for gs/ps but not for gs/eps
+ return psp:pdf_to_pdf():pdf_to_eps()
+ else
+ return psp:pdf_to_eps()
+ end
+ elseif options.type=='pdf' then
+ -- pdf_crop can take care of bbox and page,
+ -- but not of gray and not of target use or pdf version
+ do
+ local need_gs = false
+ -- compare actual and required versions,
+ -- allowing for rounding differences
+ if settings.pdf_version~='default' and
+ (psp.maver+0.1*psp.miver)>tonumber(settings.pdf_version)-0.01 then
+ need_gs = true
+ end
+ if settings.pdf_target~='default' then
+ need_gs = true
+ end
+ if options.gray then
+ need_gs = true
+ end
+ local need_crop = false
+ if options.bbox then
+ need_crop = true
+ end
+ if (not need_gs) and options.page then
+ need_crop = true
+ end
+ if need_gs then
+ psp = psp:pdf_to_pdf()
+ end
+ if need_crop or (psp.pages>1 and options.page) then
+ psp = psp:pdf_crop()
+ end
+ return psp
+ end
+ elseif options.type=='ps' then
+ if options.gray and pdftops then
+ return psp:pdf_to_pdf():pdf_to_ps()
+ else
+ return psp:pdf_to_ps()
+ end
+ end -- pdf => ps
+ end -- psp.type=='ps'|'pdf'
+end -- any_to_any
+
+-- start logging ---------------------------------
+
+-- log rotate if logfile too big
+if lfs.attributes(logfile) and lfs.attributes(logfile).size > 100000 then
+ if lfs.attributes(oldlog) then
+ if os.remove(oldlog) then os.rename(logfile,oldlog) end
+ elseif lfs.attributes(logfile) then do
+ -- separate epsdir runs with empty lines
+ local f = io.open(logfile, 'ab')
+ f:write(eol)
+ f:close()
+ end end -- do elseif
+end -- if lfs...logfile
+
+write_log('epspdf '..table.concat(arg, ' '))
+
+infile = false
+outfile = false
+
+-- some debug output
+
+-- dbg ('os is ' .. os.type .. ' and ' .. os.name)
+-- dbg ('texlua in ' .. os.selfdir)
+-- dbg('Ghostscript: ' .. gs_prog)
+
+-- dbg('\nSettings are:\n')
+-- for k,v in pairs(settings) do dbg(k .. ' = ' .. tostring(v)) end
+
+-- Handle command-line
+
+do
+
+ read_settings(rcfile)
+
+ -- dbg('Defining cmdline options')
+ opts = {}
+
+ opts.page = {
+ type = 'string', val = nil,
+ forms = {'-p', '--page', '--pagenumber'},
+ placeholder = 'PNUM',
+ negforms = nil,
+ help = 'Page number; must be a positive integer'
+ }
+
+ opts.gray = {
+ type = 'boolean', val = nil,
+ forms = {'-g', '--grey', '--gray', '-G', '--GREY', '--GRAY'},
+ negforms = nil,
+ help = 'Convert to grayscale'
+ }
+
+ opts.bbox = {
+ type = 'boolean', val = nil,
+ forms = {'-b', '--bbox', '--BoundingBox'},
+ negforms = nil,
+ help = 'Compute tight boundingbox'
+ }
+
+---[[ ignored; included for backward compatibility
+ opts.use_hires_bb = {
+ type = 'boolean', val = nil,
+ forms = {'-r', '--hires'},
+ negforms = {'-n', '--no-hires'},
+ }
+
+ opts.custom = {
+ type = 'string', val = nil,
+ forms = {'-C', '--custom', '-P', '--psoptions'},
+ negforms = nil
+ }
+ --]]
+
+ opts.pdf_target = {
+ type = 'string', val = nil,
+ forms = {'-T', '--target'},
+ placeholder = 'TARGET',
+ negforms = nil,
+ help = descriptions.pdf_target
+ }
+ opts.pdf_version = {
+ type = 'string', val = nil,
+ forms = {'-N', '--pdfversion'},
+ placeholder = 'VERSION',
+ negforms = nil,
+ help = descriptions.pdf_version
+ }
+
+ if os.type=='windows' and not is_tl_w then
+ opts.pdftops_prog = {
+ type = 'string', val = nil,
+ forms = {'--pdftops'},
+ placeholder = 'PATH',
+ negforms = nil,
+ help = descriptions.pdftops_prog
+ }
+ end
+
+ opts.use_pdftops = {
+ type = 'boolean', val = nil,
+ forms = {'-U'},
+ negforms = {'-I'},
+ help = descriptions.use_pdftops
+ }
+
+ opts.info = {
+ type = 'boolean', val = nil,
+ forms = {'-i', '--info'},
+ negforms = nil,
+ help = 'Info: display detected filetype and exit'
+ }
+
+ opts.help = {
+ type = 'boolean', val = nil,
+ forms = {'-h', '--help'},
+ negforms = nil,
+ help = 'Display this help message and exit'
+ }
+
+ opts.version = {
+ type = 'boolean', val = nil,
+ forms = {'-v', '--version'},
+ negforms = nil,
+ help = 'Display version info and exit'
+ }
+
+ opts.save = {
+ type = 'boolean', val = nil,
+ forms = {'-s', '--save'},
+ negforms = nil,
+ help = 'Save some settings to configuration file'
+ }
+
+ opts.debug = {
+ type = 'boolean', val = nil,
+ forms = {'-d'},
+ negforms = nil,
+ help = 'Debug: do not remove temp files'
+ }
+
+ opts.gui = {
+ type = 'string', val = nil,
+ forms = {'--gui'},
+ negforms = nil,
+ help = nil -- reserved for use by epspdftk
+ }
+
+ if #arg < 1 then help('No parameters') end
+
+ -- command-line parsing
+
+ -- -r="tata tata" is parsed by [tex]lua as a single argument
+ -- lua/linux retains the quotes,
+ -- lua/windows strips them.
+ -- texlua strips them, both on unix and on windows.
+
+ local i=1
+ while i<=#arg and string.sub(arg[i],1,1)=='-' do
+ -- dbg('parse argument '..tostring(i)..': '..arg[i])
+ local parsed = false
+ local kk, vv = string.match(arg[i],'([^=]+)=(.*)$')
+ if kk==nil then
+ kk = arg[i] -- also vv==nil
+ else
+ vv = strip_outer_spaces(vv)
+ end
+ for p, o in pairs(opts) do
+ -- dbg(' try '..p)
+ if in_list(kk, o.forms) or in_list(kk, o.negforms) then
+ parsed = true
+ if o.type == 'boolean' then
+ if vv then help(kk..' should not have a parameter.') end
+ if in_list(kk, o.forms) then
+ o.val = true
+ else
+ o.val = false
+ end
+ elseif vv then
+ o.val = vv
+ else
+ i = i + 1
+ if i>#arg then
+ help('Missing parameter to '..kk)
+ end
+ o.val = strip_outer_spaces(arg[i])
+ end -- testing for o.type or vv
+ break -- for
+ end -- if in_list
+ end -- for
+ if not parsed then help('illegal parameter '..kk) end
+ i = i + 1
+ end -- while
+
+ -- some debug output
+
+ --[[
+ if i<=#arg then
+ dbg('non-option arguments:')
+ for j=i,#arg do dbg(arg[j]) end
+ dbg(eol)
+ else
+ dbg('no non-option arguments')
+ end
+
+ for i=1,#arg do dbg(arg[i]) end
+
+ dbg(eol..'Options from command-line:')
+ for p, o in pairs(opts) do
+ if o.val==nil then
+ dbg(p..': undefined')
+ else
+ dbg(p..': '..tostring(o.val))
+ end
+ end
+ --]]
+
+ -- check and interpret opts.
+ -- Copy to either settings or to options table.
+ -- abort (via help function) at syntax error.
+
+ -- page
+
+ if opts.page.val then
+ local pnum = tonumber(opts.page.val)
+ if pnum<=0 or math.floor(pnum) ~= pnum then
+ help(opts.page.val..' not a positive integer')
+ else
+ options.page = pnum
+ end
+ end
+
+ -- grayscaling
+
+ if opts.gray.val then
+ options.gray = true
+ else
+ options.gray = false
+ end
+
+ -- boundingbox
+
+ if opts.bbox.val then
+ options.bbox = true
+ else
+ options.bbox = false
+ end
+
+ --[[
+ -- using hires boundingbox
+
+ if opts.use_hires_bb.val~=nil then
+ settings.use_hires_bb = opts.use_hires_bb.val
+ end
+ --]]
+
+ -- using pdftops
+
+ if opts.use_pdftops.val~=nil then
+ settings.use_pdftops = opts.use_pdftops.val
+ end
+
+ -- pdf target use
+
+ if opts.pdf_target.val~=nil then
+ if in_list(opts.pdf_target.val, pdf_targets) then
+ settings.pdf_target = opts.pdf_target.val
+ else
+ help('Illegal value '..opts.pdf_target.val..' for pdf_target')
+ end
+ end
+
+ -- pdf version
+
+ if opts.pdf_version.val~=nil then
+ if in_list(opts.pdf_version.val, pdf_versions) then
+ settings.pdf_version = opts.pdf_version.val
+ else
+ help('Illegal value '..opts.pdf_version.val..' for pdf_version')
+ end
+ end
+
+ -- pdftops program
+
+ -- pdftops has already been been initialized to false
+ if os.type=='windows' and not is_tl_w and opts.pdftops_prog.val then
+ settings.pdftops_prog = is_prog(opts.pdftops_prog.val)
+ if settings.use_pdftops then
+ pdftops = settings.pdftops_prog
+ end
+ elseif os.type=='windows' and not is_tl_w then
+ if settings.use_pdftops then
+ pdftops = is_prog(settings.pdftops_prog)
+ end
+ elseif os.type=='windows' then
+ if settings.use_pdftops then
+ pdftops = os.selfdir..'/pdftops.exe'
+ end
+ else
+ if settings.use_pdftops then
+ pdftops = find_on_path('pdftops')
+ end
+ end
+ -- dbg('Option handling; pdftops is '..tostring(pdftops))
+
+ -- other options
+
+ if opts.save.val then
+ write_settings(rcfile)
+ end
+
+ if opts.debug.val then
+ options.debug = true
+ end
+
+ if opts.info.val then
+ options.info = true
+ end
+
+ if opts.help.val then
+ help()
+ end
+
+ if opts.version.val then
+ show_version()
+ os.exit()
+ end
+
+ if opts.gui.val then
+ gui(opts.gui.val)
+ end
+
+ -- now we need 1 or 2 filenames, unless the user really only
+ -- wanted to save options without further action.
+
+ if i>#arg then
+ if opts.save.val then os.exit() else help('No filenames') end
+ end
+
+ infile = arg[i]
+ if i<#arg then
+ outfile = arg[i+1]
+ else
+ outfile = false
+ end
+ if (#arg>i and options.info) or (#arg>i+1) then
+ help('Surplus non-option parameters')
+ end
+
+ -- one final quick option
+ if opts.info.val then
+ info(infile)
+ end
+
+ -- add pdf_version and pdf_target to the options array,
+ -- from where it will be set to false when realized
+ if settings.pdf_target == 'default' then
+ options.pdf_target = false
+ else
+ options.pdf_target = settings.pdf_target
+ end
+ if settings.pdf_version == 'default' then
+ options.pdf_version = false
+ else
+ options.pdf_version = tonumber(settings.pdf_version)
+ end
+
+end -- decoding command-line
+
+-- dbg('After command-line processing\n Settings')
+-- -- print settings- and options array with dbg
+-- for k, v in pairs(settings) do
+-- dbg(k..': '..tostring(v))
+-- end
+-- dbg(' Options')
+-- for k, v in pairs(options) do
+-- dbg(k..': '..tostring(v))
+-- end
+
+--[[
+
+Once it becomes clear that real work needs to be done,
+we shall create a temp directory in the parent directory of the output file
+and use that as working directory.
+
+1. consistent with the ghostscript -dSAFER option
+2. we can move/rename rather than copy the final temp file
+ to the output file
+
+ because of gs -dSAFER restrictions, infile must be in (a
+ subdirectory of) the directory of the output file, e.g. in the
+ temp directory.
+
+ Also because of -dSAFER, we copy infile to the temp directory of
+ it is not in the same directory as outfile.
+
+--]]
+
+do
+ local source = io.open(infile)
+ if not source then
+ error(infile .. ' not readable')
+ end
+ source:close()
+ local in_dir
+ infile, in_dir = absolute_path(infile)
+
+ -- we need a writable dest_dir as parent for a temp directory,
+ -- in some cases even for option info
+ if not outfile then
+ dest_dir = in_dir
+ else
+ outfile, dest_dir = absolute_path(outfile)
+ end
+ lfs.chdir(dest_dir)
+ tempdir = os.tmpdir() -- relative path!
+ local c, e
+ c, e = lfs.chdir(tempdir)
+ if not c then
+ write_log(e)
+ tempdir = false
+ -- errror('Failure to create temporary directory')
+ else
+ tempdir = lfs.currentdir() -- better for logging: absolute path
+ write_log('Working directory: '..tempdir)
+ end
+
+ infile, source_dir = absolute_path(infile)
+ intype = identify(infile)
+
+ -- remaining cases: want a real conversion
+ if not intype then
+ error(infile..' has an unsupported filetype')
+ end
+
+ if not outfile then
+ -- derive outfile from infile: [e]ps => pdf, pdf => eps
+ if intype=='pdf' then
+ outfile = string.gsub(infile,'%.[^%.]*$','eps')
+ else
+ outfile = string.gsub(infile,'%.[^%.]*$','.pdf')
+ end
+ end
+
+ --sanity check on output filetype
+ options.type = string.match(outfile, '.*%.([^%.]+)$')
+ if not options.type or (options.type~='ps'
+ and options.type~='eps' and options.type~='pdf') then
+ errror('Output file '..outfile..
+ ' should have extension .eps, .ps or .pdf')
+ end
+
+ -- if outfile equal to infile, copy to temp directory, then remove
+ if outfile==infile then
+ infile = mktemp(intype)
+ slice_file(outfile, infile)
+ write_log('Copying '..outfile..' to temporary file '..infile..'.')
+ end
+
+ -- had some trouble under msw when removing outfile later so do it now
+ if lfs.isfile(outfile) then
+ os.remove(outfile)
+ if lfs.attributes(outfile) then
+ errror('Cannot overwrite '..outfile)
+ end
+ end
+
+ local fout = io.open(outfile, 'w')
+ if not fout then
+ errror('Output file '..outfile..' not writable; aborting')
+ else
+ fout:close()
+ end
+
+ source = PsPdf:from_path(infile)
+ dest = source:any_to_any()
+ -- options will be read from the global options table
+ -- and turned off after they have been satisfied.
+ -- irrelevant options are quietly ignored.
+
+ if os.type=='unix' then
+ write_log('Rename '..dest.path..' to '..outfile)
+ os.rename(dest.path, outfile) -- we picked our temp dir to make this possible
+ else
+ write_log('Copying '..dest.path..' to '..outfile)
+ slice_file(dest.path, outfile)
+ end
+ if not options.debug then
+ cleantemp()
+ end
+ if lfs.isfile(outfile) and lfs.attributes(outfile, 'size')>0 then
+ os.exit()
+ else
+ errror('Conversion failed')
+ end
+end
diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl
index 361ac83d6b1..32bb93011e9 100755
--- a/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl
+++ b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl
@@ -3,7 +3,7 @@
# epspdf conversion utility, GUI frontend
#####
-# Copyright (C) 2006, 2008, 2009, 2010, 2011 Siep Kroonenberg
+# Copyright (C) 2006, 2008, 2009, 2010, 2011, 2013 Siep Kroonenberg
# n dot s dot kroonenberg at rug dot nl
#
# This program is free software, licensed under the GNU GPL, >=2.0.
@@ -16,54 +16,66 @@ package require Tk
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.
+# combo boxes and -ignorestderr introduced in Tk8.5
+set ge_85 [expr {[string index $::tcl_patchLevel 2] > 4}]
-### calling epspdf.rb #########################
+# normally, epspdf.tlu should be in the same directory
+# and texlua should be on the searchpath.
+# However, the Windows installer version wraps this script in a starpack.
-# Get full path of epspdf.rb. It should be in the same directory as
-# either this script or of the starpack containing this script.
+# logging: to a log window, not to a file
+proc write_log {s} {
+ if {[winfo exists .log_t.text]} {
+ .log_t.text configure -state normal
+ .log_t.text insert end "$s\n"
+ .log_t.text yview moveto 1
+ .log_t.text configure -state disabled
+ # } else {
+ # puts $s
+ }
+}
+
+### calling epspdf.tlu #########################
+
+# Get full path of epspdf.tlu. It should be in the same directory as
+# either this script or of the starpack containing this script,
# For non-windows versions, epspdftk might be called via a symlink.
-# For the windows-only starpack, a ruby subset is included.
-# Otherwise, ruby should be on the searchpath.
+# Pdftops done elsewhere.
proc set_progs {} {
set scriptfile [file normalize [info script]]
- # starpack edition?
- set starred 0
- if {$::tcl_platform(platform) eq "windows"} {
+ set syml 0
+ if {$::tcl_platform(platform) eq "unix" && \
+ ! [catch {file readlink [$scriptfile]}]} {
+ set syml 1
+ }
+ set eproot [file dirname $scriptfile]
+ if {$::tcl_platform(platform) eq "unix" && \
+ ! [catch {file readlink $scriptfile}]} {
+ # evaluate readlink from symlink directory
+ set savedir [pwd]
+ cd $eproot
+ set eproot [file dirname [file normalize [file readlink $scriptfile]]]
+ cd $savedir
+ }
+ if {! [file exists [file join $eproot "epspdf.tlu"]]} {
+ # starpack edition?
+ set starred 0
foreach l [info loaded] {
if {[lindex $l 1] eq "tclkitpath"} {
set starred 1
break
}
}
- }
- 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
+ if {$starred} {
+ set eproot [file dirname [file normalize [info nameofexecutable]]]
+ # here no testing for symlink
}
- 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"
+ set ::texlua "texlua"
+ set ::epspdf_tlu [file join $eproot "epspdf.tlu"]
+ if {! [file exists $::epspdf_tlu]} {
+ tk_messageBox -type ok -icon error -message "Epspdf.tlu not found"
exit 1
}
@@ -75,54 +87,90 @@ proc set_progs {} {
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}
+# call epspdf.tlu with parameter list $args (should be a list)
+# Return codes success/failure
+# We also need stdout output.
+# Tcl idiom: res is a variable _name_.
+# The upvar construct makes it a reference parameter.
+
+#proc run_epspdf {res args} {
+# upvar $res result
+# if {$::ge_85} {
+# set failed [catch [linsert $args 0 \
+# exec -ignorestderr $::texlua $::epspdf_tlu --gui=gui] result]
+# } else {
+# set failed [catch [linsert $args 0 \
+# exec $::texlua $::epspdf_tlu --gui=gui] result]
+# }
+# if {$failed} {
+# # wm deiconify .log_t
+# tk_messageBox -icon error -type ok -message "Error; see log window"
+# }
+#
+# # write to log textbox
+# write_log $result
+#
+# # it is up to the caller to do anything else about failure or not.
+# # the user, at least, has been warned.
+# return [expr ! $failed]
+#}
+
+### configured and automatic settings ##################################
+
+# is_prog used for checking configured viewers under non-osx unix
+proc is_prog {x} {
+ if {[expr {$::tcl_platform(platform) ne "unix"}]} {return 0}
+ # avoid current directory except with explicit directory
+ if {[expr {[string first "/" $x] >= 0 && \
+ [file executable [file normalize $x]]}]} {
+ return 1
+ }
+ # also check search path
+ set p [split $::env(PATH) ":"] ; # no need to accomodate msw
+ foreach d $p {
+ if {[expr {$d ne "" && [file executable [file join $d $x]]}]} {
+ return 1
+ }
+ }
+ return 0
}
# create a global empty settings array
-array set settings [list]
+array set ::settings [list]
+
+set ::is_tl 1
-# ask epspdf.rb for currently configured settings.
+# ask epspdf.tlu for currently configured settings.
# this does not include automatically configured or transient settings.
# the availability of viewers is handled here.
proc getsettings {} {
- if [catch {exec $::ruby $::epspdf_rb --gui=config_w} set_str] {
+ if [catch {exec $::texlua $::epspdf_tlu --gui=config_w} set_str] {
error "Epspdf configuration error: $set_str"
}
- #puts "settings from epspdf.rb:\n$set_str"
+ # write_log "settings from epspdf.tlu:\n$set_str\n"
set l [split $set_str "\r\n"]
+ if {$::tcl_platform(platform) eq "windows"} {
+ set ::is_tl 1
+ set settings(pdftops_prog) ""
+ }
foreach e $l {
- # $e is either a string "var=value"
+ # puts "settings: $e"
+ # $e is either a string "var = value"
# or the empty string between <cr> and <lf>
set i [string first "=" $e]
if {$i>0} {
- #puts $e
- set ::settings([string range $e 0 [expr $i-1]]) \
- [string range $e [expr $i+1] end]
+ # write_log $e
+ set para [string trim [string range $e 0 [expr $i-1]]]
+ set val [string trim [string range $e [expr $i+1] end]]
+ if {$val eq "true"} {set val 1}
+ if {$val eq "false"} {set val 0}
+ if {$para eq "tl_w"} {
+ set ::is_tl 0
+ write_log "TL for Windows not detected by epspdf"
+ } else {
+ set ::settings($para) $val
+ # write_log "setting $para is $val"
+ }
}
}
@@ -131,25 +179,27 @@ proc getsettings {} {
if {$::classic_unix} {
set ::ps_viewers {}
- if {$::settings(ps_viewer) ne "" && [is_valid $::settings(ps_viewer)]} {
+ if {$::settings(ps_viewer) ne "" && [is_prog $::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]} {
+ if {$v ne $::settings(ps_viewer) && [is_prog $v]} {
lappend ::ps_viewers $v
}
}
+ # puts [join $::ps_viewers " "]
set ::pdf_viewers {}
- if {$::settings(pdf_viewer) ne "" && [is_valid $::settings(pdf_viewer)]} {
+ if {$::settings(pdf_viewer) ne "" && [is_prog $::settings(pdf_viewer)]} {
lappend ::pdf_viewers $::settings(pdf_viewer)
}
foreach v {evince okular kpdf xpdf epdfview acroread \
gv kghostview ghostview} {
- if {$v ne $::settings(pdf_viewer) && [is_valid $v]} {
+ if {$v ne $::settings(pdf_viewer) && [is_prog $v]} {
lappend ::pdf_viewers $v
}
}
+ # puts [join $::pdf_viewers " "]
if {[llength ::pdf_viewers] == 0 && [llength ::ps_viewers] != 0} {
lappend ::pdf_viewers [lindex $::ps_viewers 0]
@@ -170,22 +220,33 @@ proc getsettings {} {
getsettings
+proc write_settings {} {
+ set s ""
+ foreach el [array names ::settings] {
+ set s "$s$el = $::settings($el)\n"
+ }
+ # write_log "\nsettings for epspdf.tlu\n$s\nend writing settings\n"
+ if [catch {exec $::texlua $::epspdf_tlu --gui=config_r << $s} result] {
+ error "Epspdf configuration error: $result"
+ }
+}
+
# directory and other file data
if {$::argc > 0 && [file isdirectory [lindex $::argv 0]]} {
- set gfile(dir) [lindex $::argv 0]
-} elseif {[file isdirectory $::settings(defaultDir)]} {
- set gfile(dir) $::settings(defaultDir)
+ set ::gfile(dir) [lindex $::argv 0]
+} elseif {[file isdirectory $::settings(default_dir)]} {
+ set ::gfile(dir) $::settings(default_dir)
} else {
- set gfile(dir) $::env(HOME)
+ set ::gfile(dir) $::env(HOME)
}
-set gfile(path) ""
-set gfile(type) ""
-set gfile(name) ""
-set gfile(npages) ""
+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 \
+array set ::options [list gray "color" format "pdf" bbox 0 clean 1 \
pages "single" page 1]
proc viewable {} {
@@ -249,83 +310,106 @@ static unsigned char uparrow_bits[] = {
}
# 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
+if {$::ge_85} {
+ proc update_combo {w vls} {
+ upvar $vls vs
+ set new [$w get]
+ if {$new ni $vs} {
+ if {[is_prog $new]} {
+ set vs [linsert $vs 0 $new]
+ $w configure -values $vs
+ } else {
+ tk_messageBox -title Error -icon error -message "$vl Not a program"
+ }
}
}
- # $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"
+} else {
+ 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 "toggle_list $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 <1> {update_e %W [%W index @%x,%y] 0}
+ bind $w.lf.l <Double-1> {update_e %W [%W index @%x,%y] 1}
+ bind $w.ef.e <Return> {update_l %W}
+ bind $w.ef.e <Tab> {update_l %W}
+ return $w
+ }
+
+ # toggle state of listbox.
+ # this involves calculating the place where it should appear
+ # and toggling the arrow image.
+ proc toggle_list {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
+ }
+ }
+
+ # note: in tcl/tk 8.5, values of (some) widget variables can be accessed
+ # directly and explicit use of upvar is unnecessary.
+
+ # list => entry; optionally toggle list display
+ proc update_e {v i toggle} {
+ set w [winfo parent [winfo parent $v]]
+ set lv [$w.lf.l cget -listvariable]
+ upvar $lv l
+ set tv [$w.ef.e cget -textvariable]
+ upvar $tv t
+ set t [lindex $l $i]
+ if {$toggle} {toggle_list $w}
+ }
+
+ # entry => list
+ proc update_l {v} {
+ set w [winfo parent [winfo parent $v]]
+ set tv [$w.ef.e cget -textvariable]
+ upvar $tv t
+ set lv [$w.lf.l cget -listvariable]
+ upvar $lv l
+ set found [lsearch $l $t]
+ if { $found < 0} {
+ set ok [$v validate]
+ if {$ok} {
+ lappend l $t
+ set l [lsort $l]
+ } else {
+ tk_messageBox -message "Not a program"
+ }
+ }
+ set the_index [lsearch $l $t]
+ $w.lf.l see $the_index
+ $w.lf.l activate $the_index
+ wm withdraw $w.lf
+ $w.ef.b configure -image dwnarrow
}
}
@@ -339,7 +423,7 @@ wm title . "PostScript- and pdf conversions"
proc readhelp {} {
.help_t.text configure -state normal
- set helpfile [regsub {\.rb$} $::epspdf_rb {.help}]
+ set helpfile [regsub {\.tlu$} $::epspdf_tlu {.help}]
if {[catch {set fid [open $helpfile r]}]} {
.help_t.text insert end "No helpfile $helpfile found\n"
} else {
@@ -397,19 +481,29 @@ if {$::classic_unix} {
-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)
+ if {$::ge_85} {
+ grid [ttk::combobox .config_t.viewf.pdf] -row 1 -column 1 -sticky e
+ .config_t.viewf.pdf configure -values $::pdf_viewers
+ .config_t.viewf.pdf configure -textvariable ::settings(pdf_viewer)
+ bind .config_t.viewf.pdf <Return> {update_combo %W $::pdf_viewers}
+ grid [ttk::combobox .config_t.viewf.ps] -row 2 -column 1 -sticky e
+ .config_t.viewf.ps configure -values $::ps_viewers
+ .config_t.viewf.ps configure -textvariable ::settings(ps_viewer)
+ bind .config_t.viewf.ps <Return> {update_combo %W $::ps_viewers}
+ } else {
+ grid [mycombo .config_t.viewf.pdf] -row 1 -column 1 -sticky e
+ .config_t.viewf.pdf.lf.l configure -listvariable ::pdf_viewers
+ .config_t.viewf.pdf.ef.e configure -textvariable ::settings(pdf_viewer)
+ .config_t.viewf.pdf.ef.e configure -vcmd {is_prog %P} -validate none
+ grid [mycombo .config_t.viewf.ps] -row 2 -column 1 -sticky e -pady 4
+ .config_t.viewf.ps.lf.l configure -listvariable ::ps_viewers
+ .config_t.viewf.ps.ef.e configure -textvariable ::settings(ps_viewer)
+ .config_t.viewf.ps.ef.e configure -vcmd {is_prog %P} -validate none
+ }
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
}
@@ -423,7 +517,7 @@ 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) \
+ -variable ::settings(pdf_target) \
-text $t -value $t] -side left -padx 2 -pady 4 -anchor w
}
@@ -432,14 +526,14 @@ 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) \
+ -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)
+#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
@@ -448,12 +542,13 @@ spacing .config_t
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"} {
+if {! $::is_tl} {
+ if {[string tolower [string range $::settings(pdftops_prog) end-3 end]] ne \
+ ".exe"} {set ::settings(pdftops_prog) ""}
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
+ .config_t.psf.findf.e configure -textvariable ::settings(pdftops_prog)
packb [button .config_t.psf.findf.b -text "Browse..." \
-command find_pdftops] -side left
}
@@ -469,25 +564,25 @@ proc find_pdftops {} {
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
+.config_t.psf.c configure -variable ::settings(use_pdftops) \
+ -onvalue 1 -offvalue 0
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
+## 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
@@ -509,7 +604,7 @@ proc edit_settings {} {
# store new settings
proc putsettings {} {
- if {$::classic_unix} {
+ if {$::classic_unix && ! $::ge_85} {
wm withdraw .config_t.viewf.pdf.lf
wm withdraw .config_t.viewf.ps.lf
}
@@ -518,19 +613,8 @@ proc putsettings {} {
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} {
+ if {$::classic_unix && ! $::ge_85} {
wm withdraw .config_t.viewf.pdf.lf
wm withdraw .config_t.viewf.ps.lf
}
@@ -562,22 +646,22 @@ packb [button .topf.logb -text "Show log" -command {show_w .log_t}] \
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] \
+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] \
+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] \
+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] \
+grid [label .infof.npages_value -textvariable ::gfile(npages) -anchor w] \
-row 4 -column 2 -sticky w
grid columnconfigure .infof 1 -weight 1 -pad 2
@@ -593,35 +677,35 @@ pack [frame .optsf] -fill x
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
+ -variable ::options(gray) -value "color"] -anchor w
+pack [radiobutton .optsf.gray.gray -text "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
+ -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
+ -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
+ -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
+ -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] \
+ -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] \
+ -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 \
@@ -633,7 +717,7 @@ spacing .
# temp files
pack [checkbutton .clean -text "Remove temp files" \
- -variable options(clean)] -anchor w
+ -variable ::options(clean)] -anchor w
proc focusAndFlash {w fg bg {count 9}} {
focus $w
@@ -708,6 +792,7 @@ proc view {} {
proc openDialog {} {
set types {
+ {"PostScript and pdf" {.eps .epi .epsi .ps .prn .pdf}}
{"Encapsulated PostScript" {.eps .epi .epsi}}
{"General PostScript" {.ps .prn}}
{"Pdf" {.pdf}}
@@ -722,13 +807,20 @@ proc openDialog {} {
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)]} {
+ set ::gfile(type) ""
+ if {! [catch {exec $::texlua $::epspdf_tlu --gui=gui -i $::gfile(path)} \
+ result]} {
# parse output
- regexp { is (\w+)(?: with (\d+) pages)?[\r\n]*$} $result \
+ regexp {has type (\w+)(?:.+ (\d+) pages)?\.} $result \
mtc ::gfile(type) ::gfile(npages)
if {[regexp {^eps} $::gfile(type)]} {set ::gfile(npages) 1}
}
- set ::settings(defaultDir) $::gfile(dir)
+ if {$::gfile(type) eq ""} {
+ # unsupported type
+ tk_messageBox -message "$try: unreadable or unsupported type" \
+ -title "Error" -icon error
+ }
+ set ::settings(default_dir) $::gfile(dir)
putsettings
}
set_widget_states
@@ -754,50 +846,55 @@ proc saveDialog {} {
set try [string range $try 0 [string last "." $try]]
append try $::options(format)
}
+ set try [file normalize $try]
# epspdf can read persistent options from configuration.
# only options from the options array need to be converted to parameters.
- set cmd [list run_epspdf result]
+ set args [list]
if {$::options(gray) eq "gray"} {
- lappend cmd "-g"
- } elseif {$::options(gray) eq "gRAY"} {
- lappend cmd "-G"
+ lappend args "-g"
}
- if {$::options(bbox)} {lappend cmd "-b"}
- if {! $::options(clean)} {lappend cmd "-d"}
+ if {$::options(bbox)} {lappend args "-b"}
+ if {! $::options(clean)} {lappend args "-d"}
if {$::options(pages) eq "single"} {
if {$::options(page) eq ""} {set ::options(page) 1}
- lappend cmd "-p" $::options(page)
+ lappend args "-p" $::options(page)
}
- lappend cmd $::gfile(path)
- lappend cmd $try
- # $cmd/run_epspdf never bombs, but returns 1 (success) or 0 (fail)
+ lappend args $::gfile(path) $try
.status configure -text "Working..." -justify "left"
foreach b {view open convert done} {
.bottomf.$b configure -state disabled
}
update idletasks; # force immediate redisplay main window
- 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]
+ if {$::ge_85} {
+ set failed [catch [linsert $args 0 \
+ exec -ignorestderr $::texlua $::epspdf_tlu --gui=gui] result]
+ } else {
+ set failed [catch [linsert $args 0 \
+ exec $::texlua $::epspdf_tlu --gui=gui] result]
+ }
+ write_log $result
+ if {$failed} {
+ tk_messageBox -icon error -type ok -message "Error; see log window"
+ } else {
set ::gfile(path) [file normalize $try]
set ::gfile(dir) [file dirname $::gfile(path)]
+ set ::gfile(type) $::options(format)
set ::gfile(name) [file tail $::gfile(path)]
# 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)
+ set ::settings(default_dir) $::gfile(dir)
putsettings
+ set ::options(page) 1
}
.status configure -text ""
foreach b {view open convert done} {
.bottomf.$b configure -state normal
}
+ focus .bottomf.view
set_widget_states
}
}
@@ -817,10 +914,15 @@ proc set_widget_states {} {
}
# convert
- if {$::gfile(path) ne "" && [file exists $::gfile(path)] && \
- $::gfile(type) ne "other"} {
- .bottomf.convert configure -state normal
- } else {
+ .bottomf.convert configure -state normal
+ if {$::gfile(path) eq "" || ! [file exists $::gfile(path)]} { \
+ .bottomf.convert configure -state disabled
+ }
+ if {$::gfile(type) eq "other"} {
+ .bottomf.convert configure -state disabled
+ }
+ if {$::gfile(npages) ne "" && $::options(pages) eq "single" && \
+ $::options(page) > $::gfile(npages)} {
.bottomf.convert configure -state disabled
}
@@ -856,6 +958,7 @@ proc set_widget_states {} {
} else {
.bbox configure -state normal
}
+ update idletasks
}
set_widget_states