diff options
author | Peter Breitenlohner <peb@mppmu.mpg.de> | 2010-12-27 13:09:41 +0000 |
---|---|---|
committer | Peter Breitenlohner <peb@mppmu.mpg.de> | 2010-12-27 13:09:41 +0000 |
commit | 76c75273b2dc011336a51be9a6fc7153e7708a41 (patch) | |
tree | 5bd3e13ff9ddb762274c905db752d660195e72c1 /Build/source | |
parent | e4f0bc64d33b184afe1e8533d1e52c64f33c3e69 (diff) |
adapt to epspdf update
git-svn-id: svn://tug.org/texlive/trunk@20869 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source')
7 files changed, 2350 insertions, 15 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/ChangeLog b/Build/source/texk/texlive/linked_scripts/ChangeLog index 710e8be6e32..9c0759cb2b8 100644 --- a/Build/source/texk/texlive/linked_scripts/ChangeLog +++ b/Build/source/texk/texlive/linked_scripts/ChangeLog @@ -1,10 +1,15 @@ +2010-12-27 Peter Breitenlohner <peb@mppmu.mpg.de> + + * Makefile.am (texmf_dist_perl_scripts): epspdf => epspdf.rb, + epspdftk => epspdftk.tcl. + 2010-12-23 Peter Breitenlohner <peb@mppmu.mpg.de> * Makefile.am (install-links): Check symlink creation. 2010-12-23 Peter Breitenlohner <peb@mppmu.mpg.de> - * Makefile.am: Distinguish shell and perl (or lus, ...) scripts. + * Makefile.am: Distinguish shell and perl (or lua, ...) scripts. [WIN32]: Skip symlinks from bindir to texmf*/scripts/; install wrapper binaries for perl scripts. diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.am b/Build/source/texk/texlive/linked_scripts/Makefile.am index 7e7e912ace7..54b7178ab5a 100644 --- a/Build/source/texk/texlive/linked_scripts/Makefile.am +++ b/Build/source/texk/texlive/linked_scripts/Makefile.am @@ -58,8 +58,8 @@ texmf_dist_perl_scripts = \ context/perl/mptopdf.pl \ de-macro/de-macro \ dviasm/dviasm.py \ - epspdf/epspdf \ - epspdf/epspdftk \ + epspdf/epspdf.rb \ + epspdf/epspdftk.tcl \ epstopdf/epstopdf.pl \ fig4latex/fig4latex \ findhyph/findhyph \ diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.in b/Build/source/texk/texlive/linked_scripts/Makefile.in index fae17bfebca..4dab733ae1a 100644 --- a/Build/source/texk/texlive/linked_scripts/Makefile.in +++ b/Build/source/texk/texlive/linked_scripts/Makefile.in @@ -211,8 +211,8 @@ texmf_dist_perl_scripts = \ context/perl/mptopdf.pl \ de-macro/de-macro \ dviasm/dviasm.py \ - epspdf/epspdf \ - epspdf/epspdftk \ + epspdf/epspdf.rb \ + epspdf/epspdftk.tcl \ epstopdf/epstopdf.pl \ fig4latex/fig4latex \ findhyph/findhyph \ diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf deleted file mode 100755 index 4492608f398..00000000000 --- a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -script=`kpsewhich -format=texmfscripts epspdf.rb` -"$script" $* diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb new file mode 100755 index 00000000000..94da56c4c99 --- /dev/null +++ b/Build/source/texk/texlive/linked_scripts/epspdf/epspdf.rb @@ -0,0 +1,1479 @@ +#!/usr/bin/env ruby + +# epspdf conversion utility, main source + +##### +# Copyright (C) 2006, 2008, 2009, 2010 Siep Kroonenberg +# n dot s dot kroonenberg at rug dot nl +# +# This program is free software, licensed under the GNU GPL, >=2.0. +# This software comes with absolutely NO WARRANTY. Use at your own risk! +##### + +# Operations on a PostScript- or pdf file. +# The focus is on converting between eps and pdf. + +# Code is organized as a set of single-step conversion methods plus an +# any-to-any conversion chaining them together. + +# `Target' is not a parameter; all conversions write to a new temp +# file. Conversions can be chained: object.conversion1( params +# ).conversion2( params ) ... The final temp file is moved or copied to +# the desired destination by the main program (which can be epspdf.rb +# itself). + +# The use of exception handling makes it unnecessary to inspect return +# values. + +########################################### + +# some initialization + +# add directory of this source to loadpath +# WARNING +# readlink apparently only works right from the directory of the symlink + +$SCRIPTDIR = File.dirname( File.expand_path( __FILE__ )) +if RUBY_PLATFORM !~ /win32|mingw/ and File.symlink?( __FILE__ ) + savedir = Dir.pwd + Dir.chdir( $SCRIPTDIR ) + # puts File.readlink( __FILE__ ) + $SCRIPTDIR = File.dirname( File.expand_path( File.readlink( __FILE__ ))) + Dir.chdir( savedir ) +end +$:.unshift( $SCRIPTDIR ) +# puts $: + +# turn on warnings +#$VERBOSE = 1 + +$from_gui = nil + + +########################################### + +# Error handling + +# internal error: method should not have been called +# under the current conditions +class EPCallError < StandardError; end + +# Can't get a valid boundingbox for an eps file +class EPBBError < StandardError; end + +# copying failed +class EPCopyError < StandardError; end + +# system call failed +class EPSystemError < StandardError; end + +########################################### + +# PostScript header file for grayscaling + +$GRAYHEAD = File.join( $SCRIPTDIR, "makegray.pro" ) + +########################################### + +# handle auto-detected and saved settings + +#require 'epspdfrc' +require 'epspdfrc' + +########################################### + +# Transient options. These will never be saved. +# We set default values here. + +$options = { + 'type' => nil, + 'page' => nil, + 'bbox' => false, + 'gray' => false, + 'gRAY' => false, + 'info' => false +} + +class << $options + + # create shortcut methods $options.x and $options.x= + # for reading and writing hash elements. + + $options.each_key { |k| + eval "def #{k} ; self[\'#{k}\'] ; end" + eval "def #{k}=(v) ; self[\'#{k}\']=v ; end" + } + +end # class + +########################################### + +def hash_prn( h ) + if h.empty? + s = "No parameters" + $/ + else + s = "Parameters were:" + $/ + h.each_key { |k| s = s + ' ' + k.to_s + ' => ' + h[ k ].to_s + $/ } + end + s +end + +########################################### + +require 'fileutils' +#include FileUtils::Verbose +include FileUtils + +########################################### + +# Mode strings for file i/o + +if ARCH == 'w32' + $W = 'wb' + $A = 'ab' + $R = 'rb' +else + $W = 'w' + $A = 'a' + $R = 'r' +end + +########################################### + +# copy a slice of a file; is there no such standard function? +# some eps files are very large; don't slurp the file at one go. +# Mode: can be set to append 'a' rather than write 'w'. + +def sliceFile( source, dest, len, offs, mode=$W ) + buffer = '' + File.open( source ) do |s| + s.binmode if ARCH == 'w32' + s.seek( offs, IO::SEEK_SET ) + if s.pos == offs + begin + File.open( dest, mode ) do |d| + d.binmode if ARCH == 'w32' + tocopy = len + while tocopy>0 and s.read( [ tocopy, 16384 ].min, buffer ) + tocopy = tocopy - d.write( buffer ) + end # while + end # do + rescue + fail EPCopyError, "Failure to copy to #{dest}" + end + end # if s.seek + end # |s| (automatic closing) + # return value true if anything has been copied + File.size?( dest ) # nil if zero-length +end # def + +# write our own file copy, to bypass bug in FileUtils.cp + +def ccp( source, dest ) + sliceFile( source, dest, File.size( source ), 0, $W ) +end + +########################################### + +# logging + +def write_log( s ) + if test( ?e, LOGFILE ) and File.size( LOGFILE ) > 100000 + rm( LOGFILE_OLD ) if test( ?e, LOGFILE_OLD ) + ccp( LOGFILE, LOGFILE_OLD ) + File.truncate( LOGFILE, 0 ) + end + File.open( LOGFILE, 'a' ) { |f| + f.print( "#{$$} #{Time.now.strftime('%Y/%m/%d %H:%M:%S')} #{s}\n" ) + } + puts( s ) if $from_gui + +end + +########################################### + +require 'tmpdir' + +#$DEBUG=1 + +# save filenames for cleanup at end +$tempfiles = [] + +def mktemp( ext ) + isdone = nil + (0..99).each do |i| + fname = Dir.tmpdir + File::SEPARATOR + \ + sprintf( '%d_%02d.%s', $$, i, ext ) # $$ is process id + next if test( ?e, fname ) + File.open( fname, 'w' ) do |f|; end # creates empty file + isdone = 1 + $tempfiles.unshift( fname ) + return fname + end # each do |i| + fail StandardError, "Cannot create temp file" unless isdone +end # def + +def cleantemp + write_log( "Cleaning tempfiles" + $/ + $tempfiles.join( $/ ) ) + $tempfiles.each{ |tf|; rm( tf ) } +end + +########################################### + +# identifying file type + +# EPS with preview starts with 0xC5D0D3C6 +# Encapsulated PostScript starts with %!PS-Adobe-n.n EPSF-n.n +# but %!PS-Adobe-n.n will also be identified as Encapsulated PostScript +# if the filename extension suggests it. +# PostScript starts with %! +# PDF starts with %PDF-version + +def identify( path ) + filestart = nil + File.open( path, $R ) do |f| + filestart = f.read( 23 ) + end # this syntax automatically closes the file at end of block + case filestart + when /^\xc5\xd0\xd3\xc6/ + 'epsPreview' + when /^%!PS-Adobe-\d\.\d EPSF-\d\.\d/ + 'eps' + when /^%!PS-Adobe-\d\.\d/ + ( path =~ /\.ep(i|s|si|sf)$/i ) ? 'eps' : 'ps' + when /^%!/ + 'ps' + when /^%PDF/ + 'pdf' + else + 'other' + end # case +end # def + +########################################### + +# Boundingboxes; first standard, then hires + +#changes: +#hires boundingboxes +#numbers may start with a plus-sign. DSC manual is ambiguous, +# PostScript manual allows it, but of course PS != DSC +#left-right and lower-upper need not be in natural order + +BB_PAT = /^\s*%%BoundingBox:\s*([-+]?\d+)((\s+[-+]?\d+){3})\s*$/ +BB_END = /^\s*%%BoundingBox:\s*\(\s*atend\s*\)\s*$/ + +class Bb + + attr_accessor :llx, :lly, :urx, :ury # strings + + def initialize( llx, lly, urx, ury ) + @llx, @lly, @urx, @ury = llx, lly, urx, ury + # guarantee valid syntax: + [@llx, @lly, @urx, @ury].each { |l| l = l.to_i.to_s } + @llx, @urx = @urx, @llx if @llx.to_i > @urx.to_i + @lly, @ury = @ury, @lly if @lly.to_i > @ury.to_i + end + + def Bb.from_comment( s ) + return nil unless s =~ BB_PAT + llx, lly, urx, ury = + s.sub( /^\s*%%BoundingBox:\s*/, '' ).split( /\s+/ ) + Bb.new( llx, lly, urx, ury ) + end + + def height + ( @ury.to_i - @lly.to_i ).to_s + end + + def width + ( @urx.to_i - @llx.to_i ).to_s + end + + def valid + @llx.to_i < @urx.to_i and @lly.to_i < @ury.to_i + end + + def non_negative + valid and @llx.to_i >= 0 and @lly.to_i >= 0 + end + + def copy + Bb.new( @llx, @lly, @urx, @ury ) + end + + def prn + "#{@llx} #{@lly} #{@urx} #{@ury}" + end + + def expand + i = ( $settings.bb_spread ).to_i + return if i <= 0 + @llx = ( [ 0, @llx.to_i - i ].max ).to_s + @lly = ( [ 0, @lly.to_i - i ].max ).to_s + @urx = ( @urx.to_i + i ).to_s + @ury = ( @ury.to_i + i ).to_s + end + + ################## + + # wrapper code for a boundingbox; + # moves lower left corner of eps to (0,0) + # and defines a page size identical to the eps width and height. + # The gsave in the wrapper code should be matched by + # a grestore at the end of the PostScript code. + # This grestore can be specified on the Ghostscript command-line. + + def wrapper + fail EPBBError, prn unless valid + fname = mktemp( 'ps' ) + File.open( fname, $W ) do |f| + f.binmode if ARCH == 'w32' + f.write( "%%BoundingBox: 0 0 #{width} #{height}\n" + + "<< /PageSize [#{width} #{height}] >>" + + " setpagedevice\n" + + "gsave #{(-(@llx.to_i)).to_s} #{(-(@lly.to_i)).to_s}" + + " translate\n" ) > 0 + end # open + return fname + end # wrapper + + ################## + + # convert boundingbox to boundingbox comment + + def comment + fail EPBBError, prn unless valid + "%%BoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}" + end + +end # class Bb + +# [-+](\d+(\.\d*)?|\.\d+)([eE]\d+)? PostScript number +HRBB_PAT = /^\s*%%HiResBoundingBox:\s*[-+]?(\d+(\.\d*)?|\.\d+)([eE]\d+)?((\s[-+]?(\d+(\.\d*)?|\.\d+)([eE]\d+)?){3})\s*$/ +HRBB_END = /^\s*%%HiResBoundingBox:\s*\(\s*atend\s*\)\s*$/ + +class HRBb + + attr_accessor :llx, :lly, :urx, :ury + + def initialize( llx, lly, urx, ury ) + @llx, @lly, @urx, @ury = llx, lly, urx, ury + [@llx, @lly, @urx, @ury].each do |l| + if l =~ /\./ + # make floats conform to Ruby syntax: + # decimal dots must be padded with digits on either side + l.sub!( /^\./, '0.' ) + l.sub!( /\.(?!\d)/, '.0' ) # (?!\d): zero-width neg. lookahead + end + l = l.to_f.to_s + end + @llx, @urx = @urx, @llx if @llx.to_f > @urx.to_f + @lly, @ury = @ury, @lly if @lly.to_f > @ury.to_f + end + + def HRBb.from_hrcomment( s ) + return nil unless s =~ HRBB_PAT + llx, lly, urx, ury = + s.sub( /^\s*%%HiResBoundingBox:\s*/, '' ).split( /\s+/ ) + HRBb.new( llx, lly, urx, ury ) + end + + def height + ( @ury.to_f - @lly.to_f ).to_s + end + + def width + ( @urx.to_f - @llx.to_f ).to_s + end + + def valid + @llx.to_f < @urx.to_f and @lly.to_f < @ury.to_f + end + + def non_negative + valid and @llx.to_f >= 0 and @lly.to_f >= 0 + end + + def copy + HRBb.new( @llx, @lly, @urx, @ury ) + end + + def prn + "#{@llx} #{@lly} #{@urx} #{@ury}" + end + + ################## + + # wrapper code for a hires boundingbox; + # moves lower left corner of eps to (0,0) + # and defines a page size identical to the eps width and height. + # The gsave in the wrapper code should be matched by + # a grestore at the end of the PostScript code. + # This grestore can be specified on the Ghostscript command-line. + + def wrapper + fail EPBBError, prn unless valid + fname = mktemp( 'ps' ) + File.open( fname, $W ) do |f| + f.binmode if ARCH == 'w32' + f.write( + "%%BoundingBox: 0 0 #{width.to_f.ceil} #{height.to_f.ceil}\n" + + "%%HiResBoundingBox: 0 0 #{width.to_f} #{height.to_f}\n" + + "<< /PageSize [#{width} #{height}] >>" + + " setpagedevice\n" + + "gsave #{(-(@llx.to_f)).to_s} #{(-(@lly.to_f)).to_s}" + + " translate\n" ) > 0 + end # open + return fname + end # wrapper + + ################## + + # convert hiresboundingbox to hires boundingbox comment + + def hrcomment + fail EPBBError, prn unless valid + "%%HiResBoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}" + end + +end # class HRBb + +########################################### + +# PsPdf class definition + +class PsPdf + + protected + + SAFESIZE = 16000 + + public + + # class methods ### + + def PsPdf.pdf_options + "-dPDFSETTINGS=/#{$settings.pdf_target}" + \ + ( $settings.pdf_version == 'default' ? '' : \ + " -dCompatibilityLevel=#{$settings.pdf_version}" ) + \ + ($settings.pdf_custom ? ' ' + $settings.pdf_custom : '') + end + + def PsPdf.gs_options + "-dNOPAUSE -dBATCH -q -dSAFER" + end + + def PsPdf.ps_options( sep ) + # the sep(arable_color) option forces a cmyk color model, + # which should improve chances of grayscaling later on. + if sep + case $settings.ps_options + when /-level\dsep\b/ + $settings.ps_options + when /-level\d\b/ + $settings.ps_options.sub( /(-level\d)/, '\1sep' ) + else + $settings.ps_options + " -level3sep" + end # case + else + $settings.ps_options + end # ifthenelse sep + end + + # instance methods ### + + attr_accessor :path, :bb, :hrbb, :type, :npages, :atfront, :hr_atfront + + ################## + + def initialize( params={} ) + + ext = params[ 'ext' ] + file = params[ 'file' ] + + if not ext and not file + @path = nil + @type = nil + elsif not file + @path = mktemp( ext ) + @type = case ext.downcase + when 'pdf' + 'pdf' + when 'eps' + 'eps' + when 'ps' + 'ps' + else + 'other' + end + else + @path = file + @type = identify( file ) + @npages = pdf_pages if @type == 'pdf' # should we do this? + @npages = 1 if @type =~ /^eps/ + end + end # initialize + + def file_info + if @npages and @type !~ /^eps/ + return "File type of #{@path} is #{@type} with #{@npages} pages" + else + return "File type of #{@path} is #{@type}" + end + end + + ################## + + # debug string for EPCallError + + def buginfo( params = nil ) + b = "Source was: " + @path + $/ + b = b + param_hash_prn( params ) if params + b + end + + ################## + + # Find boundingbox, simple case. + # We shall call this method only for eps PsPdf objects which were + # converted by pdftops or Ghostscript, so we can be sure that + # the boundingbox is not (atend). + # We also assume that the hrbb lies within the bb. + # The file is not rewritten. + + def find_bb_simple + + fail EPCallError, buginfo unless @type == 'eps' + @bb = nil + @hrbb = nil + slurp = '' + File.open( @path, $R ) do |fl| + slurp = fl.read( [File.size(@path), SAFESIZE].min ) + end + lines = slurp.split( /\r\n?|\n/ ) + # look for a bb or a hrbb + # if a valid bb is found, check the next line for hrbb + # but look no further; we don't want to mistake a hrbb of + # an included eps for the hrbb of the outer eps. + lines.each do |l| + if l =~ BB_PAT + @bb = Bb.from_comment( l ) + elsif l =~ HRBB_PAT + @hrbb = HRBb.from_hrcomment( l ) + elsif @bb + break # stop looking; we expect hrbb next to bb + end + break if @bb and @hrbb + end # do |l| + fail EPBBError, @path unless @bb and @bb.valid + + end # def find_bb_simple + + ################## + + def pdf_pages + + fail EPCallError, buginfo unless @type == 'pdf' + + @npages = nil + + # get n. of pages; the Ghostscript pdf2dsc.ps script will + # create a list of pages. It seems to ignore logical p. numbers. + + dsc = mktemp( "dsc" ) + cmd = "\"#{$settings.gs_prog}\" -dNODISPLAY -q" + + " -sPDFname=\"#{@path}\" -sDSCname=\"#{dsc}\" pdf2dsc.ps" + write_log cmd # if $DEBUG + fail EPSystemError, cmd unless system( cmd ) and test( ?s, dsc ) + lines = [] + File.open( dsc, $R ) do |f| + lines = f.read( SAFESIZE ).split( /\r\n?|\n/ ) + end + lines.each do |l| + if l =~ /^%%Pages:\s+(\d+)\s*$/ + @npages = $1.to_i + break + end # if =~ + end # do |l| + + return @npages + + end # pdf_pages + + ############################################################# + + # direct conversions. These methods return a PsPdf object, + # and raise an exception in case of failure. + # Direct conversions convert at most once between PostScript and pdf. + # They always write to a temporary file. + + ################## + + # eps_clean: write source eps to an eps without preview, and + # with a boundingbox in the header. + # clean up any potential problems + # the eps is always written to a new file. + + def eps_clean + + fail EPCallError, buginfo if @type != 'eps' and @type != 'epsPreview' + atend = nil + hr_atend = nil + slurp = '' + offset, ps_len = nil, nil + if @type == 'eps' + offset = 0 + ps_len = File.size( @path ) + else + # read ToC; see Adobe EPS spec + File.open( @path, $R ) do |fl| + # bug workaround for unpack + if "\001\000\000\000".unpack( 'V' )[0] == 1 + dummy, offset, ps_len = fl.read( 12 ).unpack( 'VVV' ) + else + dummy, offset, ps_len = fl.read( 12 ).unpack( 'NNN' ) + end + end # File + end # ifthenelse @type + + # [hires] boundingbox unknown and possibly atend + @bb, @atfront, @hrbb, @hr_atfront = nil, nil, nil, nil + + # limit search for boundingbox comments. + # For very large eps files, we don't want to scan the entire file. + # A boundingbox comment should be in the header or trailer, + # so scanning the first and last few KB should be plenty. + + File.open( @path, $R ) do |fl| + fl.seek( offset, IO::SEEK_SET ) + slurp = fl.read( [ps_len,SAFESIZE].min ) + end + + # We capture both lines and separators, as an easy way to + # keep track of how many bytes have been read. + # we assume that if there is a hires bb then + # bb and hires bb are on consecutive lines. + # Otherwise the logic would get too messy. + + # The epsfile will be reconstituted from: + # a series of lines and line separators; then either + # - a bbox or + # - (bbox or hrbbox), separator, (hrbbox or bbox) + # the big blob + # possibly a trailer with removed bb comments + + pre_lines = slurp.split( /(\r\n?|\n)/ ) + bb_comment = '' + # initialize indices of bb comments to less than smallest index + i_bb = -1 + i_hrbb = -1 + i = -1 + i_end = -1 + pre_length = 0 + pre_lines.each do |l| + pre_length += l.length + i += 1 + next if l =~ /(\r\n?|\n)/ + if l =~ BB_PAT + @bb = Bb.from_comment( l ) + @atfront = true + i_bb = i + elsif l =~ BB_END + atend = true + i_bb = i + elsif l =~ HRBB_PAT + @hrbb = HRBb.from_hrcomment( l ) + @hr_atfront = true + i_hrbb = i + elsif l =~ HRBB_END + hr_atend = true + i_hrbb = i + elsif @bb or atend + i_end = i + break # stop looking; we expect hrbb next to bb + end # =~ BB_PAT + end # do |l| + if atend or hr_atend + if ps_len > SAFESIZE + File.open( @path, $R ) do |fl| + fl.seek( offset + ps_len - SAFESIZE, IO::SEEK_SET ) + slurp = fl.read( SAFESIZE ) + end + end # else use old slurp + post_lines = slurp.split( /(\r\n?|\n)/ ) + # initialize indices of atend bb comments to more than largest index + j = post_lines.length + j_bb = j + j_hrbb = j + j_end = j + post_length = 0 + post_lines.reverse_each do |l| + post_length += l.length + j -= 1 + next if l =~ /(\r\n?|\n)/ + if l =~ BB_PAT + bb_comment = l + @bb = Bb.from_comment( bb_comment ) + j_bb = j + elsif l =~ HRBB_PAT + bb_comment = l + @hrbb = HRBb.from_hrcomment( bb_comment ) + j_hrbb = j + end + if (@bb or !atend) and (@hrbb or !hr_atend) + j_end = j + break + end + end # do + #post_lines.slice([j_bb,j_hrbb].min .. -1).each do |l| + # post_block = post_block + l + end # if atend + + fail EPBBError, @path unless @bb.valid + # in case of discrepancy, drop @hrbb. + # we accept a `safety margin': a difference >1pt is not a discrepancy. + @hrbb = nil if @hrbb and + (( @bb.llx.to_i > @hrbb.llx.to_f.floor ) or + ( @bb.lly.to_i > @hrbb.lly.to_f.floor ) or + ( @bb.urx.to_i < @hrbb.urx.to_f.ceil ) or + ( @bb.ury.to_i < @hrbb.ury.to_f.ceil )) + + retVal = PsPdf.new( 'ext' => 'eps' ) + + # always rewrite the eps, to get normalized, ruby-compatible bb syntax. + # we modify part of the header, and possibly of the trailer. + + # offset, length of middle part, which can be copied byte for byte + cp_start = offset + pre_length + cp_len = ps_len - pre_length + cp_len -= post_length if ( atend or hr_atend ) + # replace boundingbox comments + if atend + pre_lines[i_bb].sub!( BB_END, @bb.comment ) + post_lines[j_bb].sub!( BB_PAT, '%%' ) + else + pre_lines[i_bb].sub!( BB_PAT, @bb.comment ) + end + if @hrbb + # replace valid hires boundingbox comments + if hr_atend + pre_lines[i_hrbb].sub!( HRBB_END, @hrbb.hrcomment ) + post_lines[j_hrbb].sub!( HRBB_PAT, '%%' ) + else + pre_lines[i_hrbb].sub!( HRBB_PAT, @hrbb.hrcomment ) + end + elsif i_hrbb >= 0 # invalid hires bb + # erase invalid hr boundingbox comments + if atend + pre_lines[i_hrbb].sub!( HRBB_END, '%%' ) + post_lines[j_hrbb].sub!( HRBB_PAT, '%%' ) + else + pre_lines[i_hrbb].sub!( HRBB_PAT, '%%' ) + end + end # test for @hrbb + + File.open( retVal.path, $W ) do |fl| + fl.write( pre_lines[ 0 .. i_end ].join ) + end + sliceFile( @path, retVal.path, cp_len, cp_start, $A ) + if ( atend or hr_atend ) + File.open( retVal.path, $A ) do |fl| + fl.write( post_lines[ j_end .. -1 ].join ) + end + end + retVal.bb = @bb.copy + retVal.hrbb = @hrbb.copy if @hrbb + retVal.atfront = true + retVal.hr_atfront = true + retVal.npages = 1 + return retVal + end # eps_clean + + ################## + + # Use the Ghostscript bbox device to give an eps a tight boundingbox. + # Here, we don't test for use_hires_bb. + # The eps should already have been cleaned up by eps_clean + # and the current boundingbox should not contain negative coordinates, + # otherwise the bbox output device may give incorrect results. + # Maybe we should test whether gs' bbox device can handle + # nonnegative coordinates. + # The boundingbox in the eps is rewritten, but the eps is + # not otherwise converted. + # We don't create a new PsPdf object. + + def fix_bb + + fail EPCallError, buginfo \ + unless @type == 'eps' and @bb.non_negative + # let ghostscript calculate new boundingbox + cmd = $settings.gs_prog + ' ' + PsPdf.gs_options + + " -sDEVICE=bbox \"" + @path + "\" 2>&1" + write_log cmd # if $DEBUG + bb_output = `#{cmd}` + # inspect the result + fail EPSystemError, cmd unless $? == 0 and bb_output + bb_output.split(/\r\n?|\n/).each do |b| + if b =~ BB_PAT + bb_temp = Bb.from_comment( b ) + fail EPBBError, bb_output unless bb_temp.valid + bb_temp.expand unless $settings.use_hires_bb + @bb = bb_temp.copy + elsif b =~ HRBB_PAT + bb_temp = HRBb.from_hrcomment( b ) + @hrbb = bb_temp.valid ? bb_temp.copy : nil + end + end # do |b| + fail EPBBError, bb_output unless @bb + # this won't happen, but we deal with it anyway: + @hrbb = HRBb.new( @bb.llx, @bb.lly, @bb.urx, @bb.ury ) unless @hrbb + + # locate current [hr]boundingbox, which ha[s|ve] to be replaced + # assumptions: both in header, and hrbb no later than + # first line after bb. + slurp = '' + File.open( @path, $R ) do |fl| + slurp = fl.read( [File.size(@path),SAFESIZE].min ) + end + pre_lines = slurp.split( /(\r\n?|\n)/ ) + i_bb = -1 + i_hrbb = -1 + i = -1 + i_end = -1 + pre_length = 0 + pre_lines.each do |l| + pre_length += l.length + i += 1 + next if l =~ /(\r\n?|\n)/ + if l =~ BB_PAT + i_bb = i + elsif l =~ HRBB_PAT + i_hrbb = i + elsif i_bb >= 0 + i_end = i + break # stop looking; we expect hrbb next to bb + end # =~ BB_PAT + end # do |l,i| + fail EPBBError, "No boundingbox found in #{@path}" if i_bb < 0 + + # replace boundingbox[es] by editing initial part pre_block + # and copying the rest byte for byte + if i_hrbb < 0 + # no old hrbb; replace bb with new bb and new hrbb + # pre_lines[i_bb+1] should match /\r\n?|\n/ + pre_lines[i_bb].sub!( BB_PAT, @bb.comment + + pre_lines[i_bb+1] + @hrbb.hrcomment ) + else + pre_lines[i_bb].sub!( BB_PAT, @bb.comment ) + pre_lines[i_hrbb].sub!( HRBB_PAT, @hrbb.hrcomment ) + end + oldpath = @path + @path = mktemp( 'eps' ) + File.open( @path, $W ) { |fl| + fl.write( pre_lines[ 0 .. i_end ].join ) } + sliceFile( oldpath, @path, \ + File.size( oldpath ) - pre_length, pre_length, $A ) + return self + + end # fix_bb + + ################## + + # Convert eps to pdf. + # The eps should already have a boundingbox in the header. + + def eps_to_pdf( params={} ) + + gray = params[ 'gray' ] + + fail EPCallError, buginfo( params ) unless @type == 'eps' and @atfront + + wrp = ( $settings.use_hires_bb and @hrbb ) ? @hrbb.wrapper : @bb.wrapper + + retVal = PsPdf.new( 'ext' => 'pdf' ) + cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + + " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" + + " -sOutputFile=\"#{retVal.path}\"" + + ( gray ? (' "' + $GRAYHEAD + '"') : "" ) + + " \"#{wrp}\" \"#{@path}\" -c grestore" + write_log cmd # if $DEBUG + fail EPSystemError, cmd \ + unless system( cmd ) and test( ?s, retVal.path ) + retVal.npages = 1 + return retVal + + end # eps_to_pdf + + ################## + + # Convert source pdf to eps. + # The option sep_color is ignored if pdftops is not available. + + def pdf_to_eps( params={} ) + + page = params[ 'page' ] ? params[ 'page' ].to_i : 1 + sep = params[ 'sep' ] + + fail EPCallError, buginfo( params ) unless @type == 'pdf' + fail EPCallError, buginfo( params ) \ + unless page > 0 and page <= @npages + retVal = PsPdf.new( 'ext' => 'eps' ) + if $settings.pdftops_prog and $settings.use_pdftops + cmd = "\"#{$settings.pdftops_prog}\"" + + " #{PsPdf.ps_options( sep )}" + + " -paper match -eps -f #{page} -l #{page}" + + " \"#{@path}\" \"#{retVal.path}\"" + else + cmd = "\"#{$settings.gs_prog}\" -sDEVICE=epswrite -r600" + + " #{PsPdf.gs_options}" + + " -dFirstPage=#{page}" + + " -dLastPage=#{page}" + + " -sOutputFile=\"#{retVal.path}\" \"#{@path}\"" + end + write_log cmd # if $DEBUG + fail EPSystemError, cmd unless \ + system( cmd ) and test( ?s, retVal.path ) + +# fix for incorrect DSC header produced by some versions of pdftops: +# if necessary, change line `% Produced by ...' into `%%Produced by ...' +# this is usually the second line. +# otherwise the DSC header would be terminated before the bbox comment +# match first chk_ze chars against `% Produced by' + chk_size = [ 1500, File.size( retVal.path ) ].min + slurp = '' + File.open( retVal.path, $R ) do |fl| + slurp = fl.read( chk_size ) + end # File + pdfpat = /([\r\n])% Produced by/m + if slurp =~ pdfpat + newpath = mktemp( 'eps' ) + write_log "pdftops header fix #{retVal.path} => #{newpath}" + File.open( newpath, $W ) do |fl2| + fl2.write( slurp.sub( pdfpat, '\1%% Produced by)' ) ) + end + sliceFile( retVal.path, newpath, File.size( retVal.path ) - chk_size, + chk_size, $A ) + retVal.path = newpath + end # if =~ +# end fix for incorrect DSC header produced by some versions of pdftops + retVal.atfront = 1 + retVal.find_bb_simple + retVal.npages = 1 + return retVal + + end # pdf_to_eps + + ################## + + def ps_to_pdf( params={} ) + + gray = params[ 'gray' ] + + fail EPCallError, buginfo( params ) \ + unless @type == 'ps' + + retVal = PsPdf.new( 'ext' => 'pdf' ) + cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + + " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" + + " -sOutputFile=\"#{retVal.path}\"" + + ( gray ? (' "' + $GRAYHEAD + '"') : "" ) + " \"#{@path}\"" + write_log cmd # if $DEBUG + fail EPSystemError, cmd \ + unless system( cmd ) and test( ?s, retVal.path ) + + retVal.pdf_pages + return retVal + + end # def ps_to_pdf + + ################## + + def pdf_to_ps( params={} ) + + sep = params[ 'sep' ] + page = params[ 'page' ] ? params[ 'page' ].to_s : nil + + fail EPCallError, buginfo( params ) unless @type == 'pdf' + retVal = PsPdf.new( 'ext' => 'ps' ) + if $settings.pdftops_prog and $settings.use_pdftops + cmd = "\"#{$settings.pdftops_prog}\" #{PsPdf.ps_options( sep )}" + + ( page ? " -f #{page} -l #{page}" : '' ) + + " -paper match \"#{@path}\" \"#{retVal.path}\"" + else + cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + + " -sDEVICE=pswrite -r600" + + ( page ? " -dFirstPage=#{page} -dLastPage#{page}" : '' ) + + " -sOutputFile=\"#{retVal.path}\"" + " \"#{@path}\"" + end + write_log cmd # if $DEBUG + fail EPSystemError, cmd unless \ + system( cmd ) and test( ?s, retVal.path ) + retVal.npages = @npages + return retVal + + end # pdf_to_ps + + ################## + + # all possible conversions, as concatenations of direct conversions. + + def any_to_any( params={} ) + + type = params[ 'type' ] + page = params[ 'page' ] + bbox = params[ 'bbox' ] + gray = params[ 'gray' ] + gRAY = params[ 'gRAY' ] # try harder to grayscale + gray = 1 if gRAY + bbox = nil if type == 'ps' + + fail EPCallError, buginfo( params ) \ + unless ( type=='eps' or type=='pdf' or type=='ps' ) + fail EPCallError, buginfo( params ) \ + if @type=='other' + fail EPCallError, buginfo( params ) \ + if type=='ps' and bbox + fail EPCallError, buginfo( params ) \ + if @type=='eps' and type=='ps' + + # gRAY tries harder to grayscale, by converting color first to cmyk + # even if it requires an additional eps - pdf - eps roundtrip. + # Normally, conversion to cmyk is done only if it doesn't take + # an extra roundtrip. + # The separable color conversion is an option of pdftops. + + pp = self + + pp = pp.eps_clean if pp.type == 'epsPreview' + pp = pp.eps_clean if pp.type == 'eps' and \ + not ( pp.bb and pp.atfront ) # => not yet `cleaned' + #pp.pdf_pages if pp.type == 'pdf' and not pp.npages + # now also done in initialize + + case pp.type + when 'eps' + + # in some cases extra eps => pdf => eps roundtrip: + # roundtrip guarantees bb.non_negative, necessary for fix_bb. + # pdf_to_ps( sep ) improves chances of grayscaling. + + case type + when 'eps' + pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \ + if gRAY or ( bbox and not pp.bb.non_negative ) + pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_eps if gray + pp = pp.fix_bb if bbox + return pp + + when 'pdf' + pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \ + if gRAY or ( bbox and not pp.bb.non_negative ) + pp = pp.fix_bb if bbox + return pp.eps_to_pdf( 'gray' => gray ) + + when 'ps' + if gRAY + pp = pp.eps_to_pdf.pdf_to_ps( 'sep' => gray ) + pp = pp.ps_to_pdf( 'gray' => gray ).pdf_to_ps + else + pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_ps + end + return pp + + end # case type + + when 'pdf' + + case type + when 'eps' + if not gray + pp = pp.pdf_to_eps( 'page' => page ) + pp = pp.fix_bb if bbox + return pp + else + pp = pp.pdf_to_eps( 'page' => page, 'sep' => 1 ) + pp = pp.fix_bb if bbox + pp = pp.eps_to_pdf( 'gray' => 1 ) + return pp.pdf_to_eps + end + + when 'pdf' + return pp unless ( gray or bbox or page ) + if bbox or not $settings.pdftops_prog + pp = pp.pdf_to_eps( 'page' => page, 'sep' => gray ) + pp = pp.fix_bb if bbox + return pp.eps_to_pdf( 'gray' => gray ) + else + pp = pp.pdf_to_ps( 'page' => page, 'sep' => gray ) + return pp.ps_to_pdf( 'gray' => gray ) + end + + when 'ps' + if gray + pp = pp.pdf_to_ps( 'sep' => 1 ) + pp = pp.ps_to_pdf( 'gray' => 1 ) + end + return pp.pdf_to_ps + + end # case type + + when 'ps' + + case type + when 'eps' + if gRAY + pp = pp.ps_to_pdf.pdf_to_eps( 'page' => page, 'sep' => 1 ) + pp = pp.eps_to_pdf( 'gray' => 1 ) + pp = pp.pdf_to_eps + else + pp = pp.ps_to_pdf( 'gray' => gray ) + pp = pp.pdf_to_eps( 'page' => page ) + end + return pp.fix_bb + + when 'pdf' + if bbox + pp = pp.ps_to_pdf.pdf_to_eps( 'sep' => gray, 'page' => page ) + pp = pp.fix_bb + return pp = pp.eps_to_pdf( 'gray' => gray ) + elsif page + pp = pp.ps_to_pdf.pdf_to_ps( 'sep' => gray, 'page' => page ) + return pp = pp.ps_to_pdf( 'gray' => gray ) + else + pp = pp.ps_to_pdf.pdf_to_ps( 'sep' => 1 ) if gRAY + return pp.ps_to_pdf( 'gray' => gray ) + end + + when 'ps' + return pp unless page or gray + if gRAY + pp = pp.ps_to_pdf.pdf_to_ps( 'page' => page, 'sep' => 1 ) + return pp.ps_to_pdf( 'gray' => 1 ).pdf_to_ps + else + pp = pp.ps_to_pdf( 'gray' => gray ) + return pp = pp.pdf_to_ps( 'page' => page ) + end + + end # case type + + end # case pp.type + + #raise EPCallError + fail "Unsupported conversion" + # this shouldn't happen anymore + + end # any_to_any + +end # class PsPdf + +################################# +# main program + +require 'optparse' + +def gui( action ) + case action + when 'config_w' then + puts "pdftops_prog=#{$settings.pdftops_prog}" if ARCH=='w32' + puts "pdf_viewer=#{$settings.pdf_viewer}" if ARCH=='unix' + puts "ps_viewer=#{$settings.ps_viewer}" if ARCH=='unix' + puts "defaultDir=#{$settings.defaultDir}" + puts "ignore_pdftops=#{$settings.ignore_pdftops}" + puts "pdf_target=#{$settings.pdf_target}" + puts "pdf_version=#{$settings.pdf_version}" + puts "pdf_custom=#{$settings.pdf_custom}" + puts "ps_options=#{$settings.ps_options}" + puts "ignore_hires_bb=#{$settings.ignore_hires_bb}" + puts "bb_spread=#{$settings.bb_spread}" + exit + when 'config_r' then + while line = gets + (varname, val) = line.split('=', 2) + varname.strip! if varname + val.strip! if val + if $settings.has_key?( varname ) + val = nil if val == '' + $settings[varname].val = val + # puts( "\"#{val}\" assigned to #{varname}" ) + end + end # while + $settings.write_settings + exit + when nil then + $from_gui = true + else + abort( "Action should be omitted or 'config_w' or 'config_r'" ) + end # case +end + +# create a pause to examine temp files +def abortt( msg ) + if $DEBUG + $stderr.puts msg + "\nPress <enter> to finish" + $stdin.gets + end + fail +end + +save_settings = false + +opts = OptionParser.new do |opts| + # for help output + opts.banner = "Convert between [e]ps and pdf formats" + opts.separator "Usage: epspdf.rb [options] infile [outfile]" + opts.separator "" + opts.separator "Default for outfile is file.pdf" + + " if infile is file.eps or file.ps" + opts.separator "Default for outfile is file.eps" + + " if infile is file.pdf" + opts.separator "" + + opts.on( "-g", "--gray", "--grey", + "Convert to grayscale;", + "success not guaranteed" ) do |opt| + $options.gray = true + end + + opts.on( "-G", "--GRAY", "--GREY", + "Try harder to convert to grayscale" ) do |opt| + $options.gRAY = true + end + + opts.on( "-p PAGENUMBER", "--pagenumber=PAGENUMBER", + "Page to be converted or selected", Integer ) do |opt| + $options.page = opt + end + + opts.on( "-b", "--bbox", "--BoundingBox", + "Compute tight boundingbox" ) do |opt| + $options.bbox = true + end + + opts.on( "-n", "--no-hires", + "Don't use hires boundingbox" ) do |opt| + $settings.ignore_hires_bb = '1' + end + + opts.on( "-r", "--hires", + "Use hires boundingbox" ) do |opt| + $settings.ignore_hires_bb = '0' + end + + opts.on( "-T TARGET", "--target=TARGET", + PDF_TARGETS, + "Target use of pdf; one of", + "#{PDF_TARGETS.join( ', ' )}" ) do |opt| + $settings.pdf_target = opt + end + + opts.on( "-N PDFVERSION", "--pdfversion=PDFVERSION", + PDF_VERSIONS, + "Pdf version to be generated" ) do |opt| + $settings.pdf_version = opt + end + + opts.on( "-V PDFVERSION", "--version=PDFVERSION", + PDF_VERSIONS, + "Deprecated; use `-N' or `--pdfversion'." ) do |opt| + if opt == "" + puts EPVERSION + exit + end + $settings.pdf_version = opt + end + + opts.on( "-I", + "Ignore pdftops even if available", + "(default: use if available)" ) do |opt| + $settings.ignore_pdftops = '1' + end + + opts.on( "-U", + "Use pdftops if available", + "(overrides previous -I setting)" ) do |opt| + $settings.ignore_pdftops = '0' + end + + opts.on( "-C CUSTOMOPTIONS", "--custom=CUSTOMOPTIONS", + "Custom options for conversion to pdf,", + "view Use.htm and ps2pdf.htm from", + "the Ghostscript documentation set" ) do |opt| + $settings.pdf_custom = opt + end + + opts.on( "-P PSOPTIONS", "--psoptions=PSOPTIONS", + "Options for pdftops; default -level3,", + "don't include -eps or page number options;", + "these will be generated by the program" ) do |opt| + $settings.ps_options = opt + end + + opts.on( "-i", "--info", + "Info: display detected filetype" ) do |opt| + $options.info = true + end + + opts.on( "-s", + "Save (some) settings" ) do |opt| + save_settings = true + end + + opts.on( "-d", "Debug: don't remove temp files" ) do |opt| + $DEBUG = 1 + end + + opts.on( "--gui[=ACTION]", "Do not use; reserved for GUI" ) do |opt| + gui( opt ) + end + + opts.separator "" + + opts.on( "-v", "Prints version info" ) do |opt| + puts EPVERSION + exit + end + + opts.on_tail("-h", "--help", "Show this message") do + puts "Epspdf version " + EPVERSION + puts "Copyright (C) " + COPYRIGHT + " Siep Kroonenberg" + puts opts + exit + end +end # opts + +# hack alert! we support `--version' for version info although +# --version is still interpreted as desired pdf output version + +if ARGV.length == 1 and ARGV[0] == '--version' + puts EPVERSION + exit +end + +# save original command-line for later reporting +cmdline = "#{$0} #{ARGV.join( sep=' ' )}" + +# parse options destructively +begin + opts.parse!( ARGV ) +rescue OptionParser::ParseError => e + STDERR.puts e.message, "\n", opts + exit( -1 ) +end + +# log cmdline AFTER we found out whether we run from gui +write_log( cmdline ) + +$options.page = 1 if $options.bbox and not $options.page + +$settings.write_settings if save_settings + +if ARGV.length < 1 + if not save_settings # help output + puts opts + abort + else + exit + end +elsif $options.info + p = PsPdf.new( 'file' => ARGV[0] ) + puts( p.file_info ) + exit +elsif ARGV.length > 1 and + File.expand_path( ARGV[0] ) == File.expand_path( ARGV[1] ) + abort " Input and output files should be different." +else + infile = ARGV[0] + abort( infile + " not found or empty" ) unless test( ?s, infile ) +end + +# done with options + +######################################## + +source = PsPdf.new( 'file' => infile ) + +# We aren't finicky about the extension of the input file, +# but still want to check whether pdf is pdf. + +case source.type +when 'eps', 'epsPreview', 'ps' + abort "Wrong extension; input is not in pdf format" \ + if infile =~ /\.pdf$/i +when 'pdf' + abort "Wrong extension; input is in pdf format" \ + if infile !~ /\.pdf$/i +else + abort "Invalid input file type; not [e]ps or pdf" \ + if source.type == 'other' +end # case source.type + +# find or construct output file name +if ARGV.length > 1 + outfile = ARGV[1] +else + case source.type + when 'eps', 'epsPreview', 'ps' + outfile = infile.sub( /\.[^.]+$/, '.pdf' ) + when 'pdf' + outfile = infile.sub( /\.[^.]+$/, '.eps' ) + end # case +end # ifthenelse ARGV.length +outfile = File.expand_path( outfile ) + +$options.type = case outfile +when /\.pdf$/i + "pdf" +when /\.ps$/i + "ps" +when /\.eps$/i + "eps" +else + nil +end # case outfile + +abort "Unknown or unsupported output file extension" \ + unless $options.type +#abort "Output format not supported without xpdf utilities" \ +# if outfile == 'ps' and not $settings.pstopdf_prog + +pp = PsPdf.new( 'file' => infile ) + +begin # inner rescue block + pp = pp.any_to_any( $options ) + write_log( pp.file_info ) if $from_gui + ccp( pp.path, outfile ) +rescue EPCallError => exc + mess = + "Wrong method call or conversion not supported or wrong page number" + + $/ + exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + exit 1 +rescue EPBBError => exc + mess = "Boundingbox problem" + $/ + + exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + exit 1 +rescue EPCopyError => exc + mess = "Copying problem" + $/ + + exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + exit 1 +rescue EPSystemError => exc + mess = "Problem with system call" + $/ + + exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + exit 1 +rescue StandardError => exc + mess = exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + exit 1 +end # rescue block + +cleantemp unless $DEBUG == 1 +__END__ diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk deleted file mode 100755 index 6d3465a8c4b..00000000000 --- a/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -script=`kpsewhich -format=texmfscripts epspdftk.rb` -if test "x$1" = "x"; then - "$script" $HOME -else - "$script" $* -fi
\ No newline at end of file diff --git a/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl new file mode 100755 index 00000000000..ff29eff893d --- /dev/null +++ b/Build/source/texk/texlive/linked_scripts/epspdf/epspdftk.tcl @@ -0,0 +1,861 @@ +#!/usr/bin/env wish + +# epspdf conversion utility, GUI frontend + +##### +# Copyright (C) 2006, 2008, 2009, 2010 Siep Kroonenberg +# n dot s dot kroonenberg at rug dot nl +# +# This program is free software, licensed under the GNU GPL, >=2.0. +# This software comes with absolutely NO WARRANTY. Use at your own risk! +##### + +package require Tk + +# tired of writing out this combined test again and again +set classic_unix [expr {$::tcl_platform(platform) eq "unix" && \ + $::tcl_platform(os) ne "Darwin"}] + +# normally, epspdf.rb should be in the same directory +# and ruby should be on the searchpath. +# However, the Windows installer version includes a Ruby subset +# and wraps this script in a starpack. + +### calling epspdf.rb ######################### + +# Get full path of epspdf.rb. It should be in the same directory as +# either this script or of the starpack containing this script. +# For non-windows versions, epspdftk might be called via a symlink. +# For the windows-only starpack, a ruby subset is included. +# Otherwise, ruby should be on the searchpath. + +proc set_progs {} { + set scriptfile [file normalize [info script]] + # starpack edition? + set starred 0 + if {$::tcl_platform(platform) eq "windows"} { + foreach l [info loaded] { + if {[lindex $l 1] eq "tclkitpath"} { + set starred 1 + break + } + } + } + set syml 0 + if {$::tcl_platform(platform) eq "unix" && \ + ! [catch {file readlink [$scriptfile]}]} { + set syml 1 + } + if {$starred} { + set eproot [file dirname [file normalize [info nameofexecutable]]] + set ::ruby [file normalize "$eproot/../rubysub/bin/ruby.exe"] + } else { + set eproot [file dirname $scriptfile] + if {$::tcl_platform(platform) eq "unix" && \ + ! [catch {file readlink $scriptfile}]} { + # evaluate readlink from symlink directory + set savedir [pwd] + cd $eproot + set eproot [file dirname [file normalize [file readlink $scriptfile]]] + cd $savedir + } + set ::ruby "ruby" + } + set ::epspdf_rb [file join $eproot "epspdf.rb"] + if {! [file exists $::epspdf_rb]} { + tk_messageBox -type ok -icon error -message "Epspdf.rb not found" + exit 1 + } + + # no luck with other platforms + if {$::tcl_platform(platform) eq "windows"} { + wm iconbitmap . [file join $eproot "epspdf.ico"] + } +} + +set_progs + +# call epspdf.rb with parameter list l (should be a list): + +set result "" +proc run_epspdf {res args} { + upvar $res result + set failed [catch [linsert $args 0 exec $::ruby $::epspdf_rb --gui] result] + if {$failed} { + wm deiconify .log_t + tk_messageBox -icon error -type ok -message "Error; see log window" + } + # update log window with $result + .log_t.text configure -state normal + .log_t.text insert end "$result\n" + .log_t.text yview moveto 1 + .log_t.text configure -state disabled + + # it is up to the caller to do anything else about failure or not. + # the user, at least, has been warned. + return [expr ! $failed] +} + +### read configured settings ######################################## + +# for checking configured viewers under non-osx unix +proc is_valid {x} { + if {[catch {exec which $x}]} {return 0} else {return 1} +} + +# create a global empty settings array +array set settings [list] + +# ask epspdf.rb for currently configured settings. +# this does not include automatically configured or transient settings. +# the availability of viewers is handled here. +proc getsettings {} { + if [catch {exec $::ruby $::epspdf_rb --gui=config_w} set_str] { + error "Epspdf configuration error: $set_str" + } + #puts "settings from epspdf.rb:\n$set_str" + set l [split $set_str "\r\n"] + foreach e $l { + # $e is either a string "var=value" + # or the empty string between <cr> and <lf> + set i [string first "=" $e] + if {$i>0} { + #puts $e + set ::settings([string range $e 0 [expr $i-1]]) \ + [string range $e [expr $i+1] end] + } + } + + # unix: viewer settings + # configured viewer, if valid, heads the list + if {$::classic_unix} { + + set ::ps_viewers {} + if {$::settings(ps_viewer) ne "" && [is_valid $::settings(ps_viewer)]} { + lappend ::ps_viewers $::settings(ps_viewer) + } + foreach v {evince okular gv kghostview ghostview} { + if {$v ne $::settings(ps_viewer) && [is_valid $v]} { + lappend ::ps_viewers $v + } + } + + set ::pdf_viewers {} + if {$::settings(pdf_viewer) ne "" && [is_valid $::settings(pdf_viewer)]} { + lappend ::pdf_viewers $::settings(pdf_viewer) + } + foreach v {evince okular kpdf xpdf epdfview acroread \ + gv kghostview ghostview} { + if {$v ne $::settings(pdf_viewer) && [is_valid $v]} { + lappend ::pdf_viewers $v + } + } + + if {[llength ::pdf_viewers] == 0 && [llength ::ps_viewers] != 0} { + lappend ::pdf_viewers [lindex $::ps_viewers 0] + } + if {[llength ::pdf_viewers] == 0} { + tk_messageBox -message "No viewers found" + } elseif {[llength ::ps_viewers] == 0} { + tk_messageBox -message "No PostScript viewers found" + } + if {[llength ::pdf_viewers] > 0} { + set ::settings(pdf_viewer) [lindex $::pdf_viewers 0] + } + if {[llength ::ps_viewers] > 0} { + set ::settings(ps_viewer) [lindex $::ps_viewers 0] + } + } +} + +getsettings + +# directory and other file data + +if {$::argc > 0 && [file isdirectory [lindex $::argv 0]]} { + set gfile(dir) [lindex $::argv 0] +} elseif {[file isdirectory $::settings(defaultDir)]} { + set gfile(dir) $::settings(defaultDir) +} else { + set gfile(dir) $::env(HOME) +} +set gfile(path) "" +set gfile(type) "" +set gfile(name) "" +set gfile(npages) "" + +# transient options +array set options [list gray "color" format "pdf" bbox 0 clean 1 \ + pages "single" page 1] + +proc viewable {} { + if { $::gfile(name) eq "" || ! [file exists $::gfile(path)] || \ + $::gfile(type) eq "other" || $::gfile(type) eq ""} { + return 0 + } elseif {! $::classic_unix} { + return 1 + } elseif {$::settings(ps_viewer) ne "" && [regexp {ps} $::gfile(type)]} { + return 1 + } elseif {$::settings(pdf_viewer) ne "" && $::gfile(type) eq "pdf"} { + return 1 + } else { + return 0 + } +} + +### groundwork for GUI ################################# + +# padding for buttons and frames +proc packb {b args} { + eval [linsert $args 0 pack $b -padx 3 -pady 3] + $b configure -border 2 +} + +proc packf {f args} { + eval [linsert $args 0 pack $f -padx 3 -pady 3] +} + +# dummy widget for vertical spacing +set idummy -1 +proc spacing {w} { + incr ::idummy + pack [label $w.$::idummy -text " "] +} + +# bold font +font create boldfont +# create dummy label widget to get at the default label font properties +label .dummy +font configure boldfont -family [font actual [.dummy cget -font] -family] +font configure boldfont -size [font actual [.dummy cget -font] -size] +font configure boldfont -weight bold +destroy .dummy + +# bitmaps for mycombo (no combobox in core tk8.4) +image create bitmap dwnarrow -data { +#define dwnarrow_width 15 +#define dwnarrow_height 10 +static unsigned char dwnarrow_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, 0x03, + 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 }; +} + +image create bitmap uparrow -data { +#define uparrow_width 15 +#define uparrow_height 10 +static unsigned char uparrow_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, + 0xf8, 0x0f, 0xfc, 0x1f, 0x00, 0x00, 0x00, 0x00 }; +} + +# mycombo +proc mycombo {w} { + # entry widget and dropdown button + frame $w + frame $w.ef + entry $w.ef.e -width 30 -borderwidth 1 + pack $w.ef.e -side left + button $w.ef.b -image dwnarrow -command "droplist $w" -borderwidth 1 + pack $w.ef.b -side right + pack $w.ef + # 'drop-down' listbox; width should match entry widget above + toplevel $w.lf -bd 0 + listbox $w.lf.l -yscrollcommand "$w.lf.s set" -height 4 -width 30 \ + -bd 1 -relief raised + grid $w.lf.l -column 0 -row 0 -sticky news + scrollbar $w.lf.s -command "$w.lf.l yview" -bd 1 + grid $w.lf.s -column 1 -row 0 -sticky ns + grid columnconfigure $w.lf 0 -weight 1 + wm overrideredirect $w.lf 1 + wm transient $w.lf + wm withdraw $w.lf + # next two bindings: + # final parameter: unmap/toggle listbox + bind $w.lf.l <KeyRelease-space> {update_e %W [%W get active] 1} + bind $w.lf.l <KeyRelease-Tab> {update_e %W [%W get active] 1} + bind $w.lf.l <FocusOut> {update_e %W [%W get active] 1} + bind $w.lf.l <1> {update_e %W [%W get @%x,%y] 0} + bind $w.lf.l <Double-1> {update_e %W [%W get @%x,%y] 1} + bind $w.ef.e <Return> {update_l %W} + bind $w.ef.e <Tab> {update_l %W} + bind $w.ef.e <FocusOut> {update_l %W} + return $w +} + +# toggle state of listbox. +# this involves calculating the place where it should appear +# and toggling the arrow image. +proc droplist {w} { + # $w.ef is the frame with the entry widget + # $w.lf is the toplevel with the listbox + # which needs to turn up right below $w.ef + if {[wm state $w.lf] eq "withdrawn" || [wm state $w.lf] eq "iconified"} { + set lfx [winfo rootx $w.ef] + set lfy [expr [winfo rooty $w.ef] + [winfo height $w.ef]] + wm geometry $w.lf [format "+%d+%d" $lfx $lfy] + wm deiconify $w.lf + $w.ef.b configure -image uparrow + } else { + wm withdraw $w.lf + $w.ef.b configure -image dwnarrow + } +} + +proc update_e {lbox vl unmap} { + set w [winfo parent [winfo parent $lbox]] + $w.ef.e delete 0 end + $w.ef.e insert 0 $vl + if {$unmap} {droplist $w} +} + +# entry => list +proc update_l {v} { + set t [$v get] + set w [winfo parent [winfo parent $v]] + for {set i 0} {$i<[$w.lf.l size]} {incr i} { + if {$t eq [$w.lf.l get $i]} { + $w.lf.l see $i + $w.lf.l activate $i + return + } + } + # $t not found + if {[$v validate]} { + $w.lf.l insert 0 $t + $w.lf.l see 0 + $w.lf.l activate 0 + } else { + tk_messageBox -message "Not a program" + } +} + +# end mycombo + +### and now the actual GUI ################################### + +wm title . "PostScript- and pdf conversions" + +# help toplevel + +proc readhelp {} { + .help_t.text configure -state normal + set helpfile [regsub {\.rb$} $::epspdf_rb {.help}] + if {[catch {set fid [open $helpfile r]}]} { + .help_t.text insert end "No helpfile $helpfile found\n" + } else { + while {[gets $fid line] >= 0} {.help_t.text insert end "$line\n"} + close $fid + } + .help_t.text configure -state disabled + .help_t.text yview moveto 0 +} + +toplevel .help_t +wm title .help_t "EpsPdf help" +packb [button .help_t.done -text "Close" -command {wm withdraw .help_t}] \ + -side bottom -anchor e -padx 50 +text .help_t.text -wrap word -width 60 -height 20 -setgrid 1 \ + -yscrollcommand ".help_t.scroll set" +scrollbar .help_t.scroll -command ".help_t.text yview" +pack .help_t.scroll -side right -fill y +pack .help_t.text -expand 1 -fill both +readhelp +.help_t.text configure -state disabled +wm withdraw .help_t + +# log toplevel + +toplevel .log_t +wm title .log_t "Epspdf log" +packb [button .log_t.b -text "Close" -command {wm withdraw .log_t}] \ + -side bottom -anchor e -padx 50 +text .log_t.text -wrap word -relief flat -height 15 -width 60 \ + -setgrid 1 -yscrollcommand ".log_t.scroll set" +scrollbar .log_t.scroll -command ".log_t.text yview" +pack .log_t.scroll -side right -fill y +pack .log_t.text -expand 1 -fill both +.log_t.text configure -state disabled +wm withdraw .log_t + +### configure toplevel ################### + +toplevel .config_t +wm title .config_t "Configure epspdf" + +# The settings array is edited directly in the configuration screen: +# for most control widgets, we need a global external variable anyway, +# so we better use the global that is already there. +# If the configuration screen is closed via the cancel button, +# the original values will be re-read from disk. +# If it is closed via the ok button, the new settings are written to disk. + +# viewers (not on windows or osx) + +if {$::classic_unix} { + packf [frame .config_t.viewf] -ipadx 4 -fill x + grid [label .config_t.viewf.title -font boldfont -text "Viewers"] \ + -row 0 -column 0 -sticky w + grid [label .config_t.viewf.lb_pdf -text "Pdf"] \ + -row 1 -column 0 -sticky w + grid [mycombo .config_t.viewf.pdf] -row 1 -column 1 -sticky e + .config_t.viewf.pdf.ef.e configure -vcmd {is_valid %P} -validate none + .config_t.viewf.pdf.ef.e configure -textvariable settings(pdf_viewer) + grid [label .config_t.viewf.lb_ps -text "PostScript"] \ + -row 2 -column 0 -sticky w + grid [mycombo .config_t.viewf.ps] -row 2 -column 1 -sticky e -pady 4 + .config_t.viewf.ps.ef.e configure -vcmd {is_valid %P} -validate none + .config_t.viewf.ps.ef.e configure -textvariable settings(ps_viewer) + grid columnconfigure .config_t.viewf 1 -weight 1 -pad 2 + + .config_t.viewf.pdf.lf.l configure -listvariable ::pdf_viewers + .config_t.viewf.ps.lf.l configure -listvariable ::ps_viewers + + spacing .config_t +} + +# settings for conversion to pdf + +packf [frame .config_t.pdff] -ipadx 4 -fill x +pack [label .config_t.pdff.title -font boldfont -text "Conversion to pdf"] \ + -anchor w + +pack [label .config_t.pdff.l_target -text "Target use"] -anchor w +pack [frame .config_t.pdff.f_targets] -fill x +foreach t {default printer prepress screen ebook} { + pack [radiobutton .config_t.pdff.f_targets.$t \ + -variable settings(pdf_target) \ + -text $t -value $t] -side left -padx 2 -pady 4 -anchor w +} + +pack [label .config_t.pdff.l_version -text "Pdf version"] -anchor w +pack [frame .config_t.pdff.f_version] -fill x +foreach t {1.2 1.3 1.4 default} { + regsub {\.} $t _ tp ; # replace dot in name: dots are path separators! + pack [radiobutton .config_t.pdff.f_version.$tp \ + -variable settings(pdf_version) \ + -text $t -value $t] -side left -padx 2 -pady 4 -anchor w +} + +pack [label .config_t.pdff.l_gs \ + -text "Custom Ghostscript/ps2pdf parameters"] -anchor w +pack [entry .config_t.pdff.e_gs -border 1] -fill x -padx 2 -pady 2 +.config_t.pdff.e_gs configure -textvariable settings(pdf_custom) + +spacing .config_t + +# settings for conversion to EPS and PostScript + +packf [frame .config_t.psf] -ipadx 4 -fill x +pack [label .config_t.psf.l_ps -text "Conversion to EPS and PostScript" \ + -font boldfont] -anchor w +if {$tcl_platform(platform) eq "windows"} { + pack [label .config_t.psf.l_pdftops -text "Find pdftops"] -anchor w + pack [frame .config_t.psf.findf] -anchor w + pack [entry .config_t.psf.findf.e -width 40] -side left -padx 4 + .config_t.psf.findf.e configure -textvariable settings(pdftops_prog) + # epspdfrc.rb already checked on this setting so we don't + packb [button .config_t.psf.findf.b -text "Browse..." \ + -command find_pdftops] -side left +} + +proc find_pdftops {} { + set try [tk_getOpenFile -title "Find pdftops.exe" \ + -filetypes {{"Programs" {.exe}}} -initialdir "c:/"] + if {$try ne ""} { + .config_t.psf.findf.e delete 0 end + .config_t.psf.findf.e insert 0 $try + } +} + +pack [checkbutton .config_t.psf.c \ + -text "Use pdftops if available (recommended)"] -anchor w +.config_t.psf.c configure \ + -variable settings(ignore_pdftops) -onvalue 0 -offvalue 1 + +spacing .config_t + +# hires boundingbox setting + +packf [frame .config_t.hiresf] -ipadx 4 -fill x +pack [label .config_t.hiresf.title -font boldfont -text "Hires BoundingBox"] \ + -anchor w +pack [label .config_t.hiresf.l -text "Uncheck to prevent clipping"] \ + -anchor w + +pack [checkbutton .config_t.hiresf.c \ + -text "Use hires boundingbox if possible"] -anchor w +.config_t.hiresf.c configure \ + -variable settings(ignore_hires_bb) -onvalue 0 -offvalue 1 + +spacing .config_t + +# buttons for closing the configuration screen + +pack [frame .config_t.buttonsf] -fill x +packb [button .config_t.buttonsf.done -text "Done" -command putsettings] \ + -side right +packb [button .config_t.buttonsf.cancel -text "Cancel" \ + -command cancelsettings] -side right + +wm transient .config_t +wm overrideredirect .config_t +wm withdraw .config_t + +proc edit_settings {} { + wm deiconify .config_t + raise .config_t + grab set .config_t +} + +# store new settings +proc putsettings {} { + if {$::classic_unix} { + wm withdraw .config_t.viewf.pdf.lf + wm withdraw .config_t.viewf.ps.lf + } + wm withdraw .config_t + grab release .config_t + write_settings +} + +proc write_settings {} { + set s "" + foreach el [array names ::settings] { + set s "$s$el=$::settings($el)\n" + } + #puts "\nsettings for epspdf.rb\n$s" + if [catch {exec $::ruby $::epspdf_rb --gui=config_r << $s} result] { + error "Epspdf configuration error: $result" + } +} + +proc cancelsettings {} { + if {$::classic_unix} { + wm withdraw .config_t.viewf.pdf.lf + wm withdraw .config_t.viewf.ps.lf + } + wm withdraw .config_t + grab release .config_t + # re-read config file / reg entries + getsettings +} + +### main screen ############################# + +proc show_w {w} { + wm deiconify $w + raise $w +} + +# buttons to call up configure-, log- and help screens + +pack [frame .topf] -fill x +packb [button .topf.config_t -text "Configure" \ + -command edit_settings] -side left +packb [button .topf.help_t -text "Help" \ + -command {show_w .help_t}] -side right +packb [button .topf.logb -text "Show log" -command {show_w .log_t}] \ + -side right -anchor w + +# file info in grid layout + +packf [frame .infof -relief sunken -border 1] -fill x +grid [label .infof.dir_label -text "Directory" -anchor w] \ + -row 1 -column 1 -sticky w +grid [label .infof.dir_value -textvariable gfile(dir) -anchor w] \ + -row 1 -column 2 -sticky w + +grid [label .infof.name_label -text "File" -anchor w] \ + -row 2 -column 1 -sticky w +grid [label .infof.name_value -textvariable gfile(name) -anchor w] \ + -row 2 -column 2 -sticky w + +grid [label .infof.type_label -text "Type" -anchor w] \ + -row 3 -column 1 -sticky w +grid [label .infof.type_value -textvariable gfile(type) -anchor w] \ + -row 3 -column 2 -sticky w + +grid [label .infof.npages_label -text "Pages" -anchor w] \ + -row 4 -column 1 -sticky w +grid [label .infof.npages_value -textvariable gfile(npages) -anchor w] \ + -row 4 -column 2 -sticky w + +grid columnconfigure .infof 1 -weight 1 -pad 2 +grid columnconfigure .infof 2 -weight 3 -pad 2 + +spacing . + +# conversion options + +pack [frame .optsf] -fill x + +# grayscaling +pack [frame .optsf.gray] -side left -anchor nw +pack [label .optsf.gray.l -text "Grayscaling"] -anchor w +pack [radiobutton .optsf.gray.off -text "No color conversion" \ + -variable options(gray) -value "color"] -anchor w +pack [radiobutton .optsf.gray.gray -text "Try to grayscale" \ + -variable options(gray) -value "gray"] -anchor w +pack [radiobutton .optsf.gray.gRAY -text "Try harder to grayscale" \ + -variable options(gray) -value "gRAY"] -anchor w + +# output format +pack [label .optsf.format] -side right -anchor ne +pack [label .optsf.format.l -text "Output format"] -anchor w +pack [radiobutton .optsf.format.pdf -text "pdf" -command set_widget_states \ + -variable options(format) -value "pdf"] -anchor w +pack [radiobutton .optsf.format.eps -text "eps" -command set_widget_states \ + -variable options(format) -value "eps"] -anchor w +pack [radiobutton .optsf.format.ps -text "ps" -command set_widget_states \ + -variable options(format) -value "ps"] -anchor w + +spacing . + +# boundingbox +pack [checkbutton .bbox -text "Compute tight boundingbox" \ + -variable options(bbox) -command set_widget_states] -anchor w + +# page selection +pack [frame .pagesf] -fill x +pack [radiobutton .pagesf.all -text "Convert all pages" \ + -variable options(pages) -value "all" -command set_widget_states] \ + -side left +pack [radiobutton .pagesf.single -text "Page:" \ + -variable options(pages) -value "single" -command set_widget_states] \ + -side left +pack [entry .pagesf.e -width 6 -textvariable ::options(page)] -side left +#.pagesf.e configure -vcmd {page_valid %W} -validate focusout \ +# -invcmd "focusAndFlash %W [.pagesf.e cget -fg] [.pagesf.e cget -bg]" +.pagesf.e configure -vcmd {page_valid %W} -validate focusout \ + -invcmd "see_red %W" + +spacing . + +# temp files +pack [checkbutton .clean -text "Remove temp files" \ + -variable options(clean)] -anchor w + +proc focusAndFlash {w fg bg {count 9}} { + focus $w + if {$count<1} { + $w configure -foreground $fg -background $bg + } else { + if {$count%2} { + $w configure -foreground $bg -background $fg + } else { + $w configure -foreground $fg -background $bg + } + after 200 [list focusAndFlash $w $fg $bg [expr {$count-1}]] + } +} + +proc see_red {w} { + focus $w + set orig [$w cget -bg] + $w configure -bg red + update idletasks + after 1000 + $w configure -bg $orig + update idletasks +} + +proc page_valid {w} { + # w entry widget + set p [$w get] + set isvalid 1 + #puts "check pageno $p" + if {! [string is integer $p] || $p<=0} { + set isvalid 0 + } elseif {$::gfile(npages) ne "" && [scan $p "%d"] > $::gfile(npages)} { + set isvalid 0 + } + return $isvalid +} + +# end conversion options + +pack [label .status -justify left] -side bottom -anchor w -fill x -expand 1 + +# main buttons + +pack [frame .bottomf] -side bottom -fill x +packb [button .bottomf.view -text "View" -command view] -side left +packb [button .bottomf.open -text "Open" -command openDialog] \ + -side left -padx 2 +packb [button .bottomf.convert -text "Convert and save..." \ + -command saveDialog] -side left -padx 2 +packb [button .bottomf.done -text "Done" -command exit] -side right + +proc view {} { + if {! [viewable]} { + tk_messageBox -icon warning -type ok \ + -message "No viewer for $::gfile(path)" + return + } + if {$::tcl_platform(os) eq "Darwin"} { + catch {exec open $::gfile(path) &} + } elseif {$::tcl_platform(platform) ne "unix"} { + # use shortname to sidestep quoting problems + catch {exec cmd /x /c start [file attributes $::gfile(path) -shortname]} + } else { + if {$::gfile(type) eq "pdf"} { + catch {exec $::settings(pdf_viewer) $::gfile(path) &} + } elseif {$::gfile(type) ne "other" && $::gfile(type) ne ""} { + catch {exec $::settings(ps_viewer) $::gfile(path) &} + } + } +} + +proc openDialog {} { + set types { + {"Encapsulated PostScript" {.eps .epi .epsi}} + {"General PostScript" {.ps .prn}} + {"Pdf" {.pdf}} + {"All files" *} + } + if {[file isdirectory $::gfile(dir)]} { + set try [tk_getOpenFile -filetypes $types -initialdir $::gfile(dir)] + } else { + set try [tk_getOpenFile -filetypes $types] + } + if {$try != ""} { + set ::gfile(path) [file normalize $try] + set ::gfile(dir) [file dirname $::gfile(path)] + set ::gfile(name) [file tail $::gfile(path)] + if {[run_epspdf result -i $::gfile(path)]} { + # parse output + regexp { is (\w+)(?: with (\d+) pages)?[\r\n]*$} $result \ + mtc ::gfile(type) ::gfile(npages) + if {[regexp {^eps} $::gfile(type)]} {set ::gfile(npages) 1} + } + set ::settings(defaultDir) $::gfile(dir) + putsettings + } + set_widget_states +} + +proc saveDialog {} { + if {$::options(format) eq "pdf"} { + set types {{"Pdf" {.pdf}}} + } elseif {$::options(format) eq "ps"} { + set types {{"PostScript" {.ps}}} + } elseif {$::options(format) eq "eps"} { + set types {{"Encapsulated PostScript" {.eps}}} + } + set try [tk_getSaveFile -filetypes $types -initialdir $::gfile(dir)] + if {$try != ""} { + # fix extension + set try [file normalize $try] + set idot [string last "." [file tail $try]] + if {$idot == -1} { + append try ".$::options(format)" + } else { + # don't bother checking whether the extension is already correct + set try [string range $try 0 [string last "." $try]] + append try $::options(format) + } + # epspdf can read persistent options from configuration. + # only options from the options array need to be converted to parameters. + set cmd [list run_epspdf result] + if {$::options(gray) eq "gray"} { + lappend cmd "-g" + } elseif {$::options(gray) eq "gRAY"} { + lappend cmd "-G" + } + if {$::options(bbox)} {lappend cmd "-b"} + if {! $::options(clean)} {lappend cmd "-d"} + if {$::options(pages) eq "single"} { + if {$::options(page) eq ""} {set ::options(page) 1} + lappend cmd "-p" $::options(page) + } + lappend cmd $::gfile(path) + lappend cmd $try + # $cmd/run_epspdf never bombs, but returns 1 (success) or 0 (fail) + .status configure -text "Working..." -justify "left" + foreach b {view open convert done} { + .bottomf.$b configure -state disabled + } + update idletasks; # force immediate redisplay main window + + if {[eval $cmd]} { + # if failure, there has already been an error message + set ::gfile(path) $try + set ::gfile(dir) [file dirname $try] + set ::gfile(type) $::options(format) + set ::gfile(name) [file tail $try] + set ::gfile(path) [file normalize $try] + set ::gfile(dir) [file dirname $::gfile(path)] + set ::gfile(name) [file tail $::gfile(path)] + # parse result output + regexp { is (\w+)(?: with (\d+) pages)?[\r\n]*$} \ + [string range $result [string last "File type of" $result] end] \ + mtc ::gfile(type) ::gfile(npages) + if {$::gfile(type) eq "eps"} {set ::gfile(npages) 1} + set ::settings(defaultDir) $::gfile(dir) + putsettings + } + .status configure -text "" + foreach b {view open convert done} { + .bottomf.$b configure -state normal + } + set_widget_states + } +} + +proc set_widget_states {} { + # states (normal/disabled) for various widgets. + # their values should already be mutually consistent. + # widgets concerned: + # view / save + # output format / bbox / single page / page no. + + # view + if {[viewable]} { + .bottomf.view configure -state normal + } else { + .bottomf.view configure -state disabled + } + + # convert + if {$::gfile(path) ne "" && [file exists $::gfile(path)] && \ + $::gfile(type) ne "other"} { + .bottomf.convert configure -state normal + } else { + .bottomf.convert configure -state disabled + } + + # type + if {[regexp {ps|pdf} $::gfile(type)]} { + foreach f {pdf eps ps} {.optsf.format.$f configure -state normal} + } else { + foreach f {pdf eps ps} {.optsf.format.$f configure -state disabled} + } + + # pages and page + foreach f {all single} {.pagesf.$f configure -state normal} + if {$::options(format) eq "eps"} { + .pagesf.all configure -state disabled + set ::options(pages) "single" + } + if {$::options(format) eq "ps"} { + .pagesf.single configure -state disabled + set ::options(pages) "all" + } + if {$::options(pages) eq "all"} { + .pagesf.e configure -state disabled + } else { + .pagesf.e configure -state normal + } + + # boundingbox + if {$::options(format) eq "eps"} { + .bbox configure -state normal + } elseif {$::options(pages) eq "all"} { + .bbox configure -state disabled + set ::options(bbox) 0 + } else { + .bbox configure -state normal + } +} + +set_widget_states |