diff options
Diffstat (limited to 'Master/texmf-dist/scripts/epspdf/epspdf.rb')
-rwxr-xr-x | Master/texmf-dist/scripts/epspdf/epspdf.rb | 1123 |
1 files changed, 1123 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/epspdf/epspdf.rb b/Master/texmf-dist/scripts/epspdf/epspdf.rb new file mode 100755 index 00000000000..fb391b6dde6 --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/epspdf.rb @@ -0,0 +1,1123 @@ +#!/usr/bin/env ruby + +# epspdf conversion utility, main source + +##### +# Copyright (C) 2006 Siep Kroonenberg +# n dot s dot kroonenberg at rug dot nl +# +# This program is free software, licensed under the GNU GPL, >=2.0. +# This software comes with absolutely NO WARRANTY. Use at your own risk! +##### + +# Operations on a PostScript- or pdf file. +# The focus is on converting between eps and pdf. + +# Code is organized as a set of single-step conversion methods plus an +# any-to-any conversion. + +# `Target' is not a parameter; all conversions write to a new temp +# file. Conversions can be chained: object.conversion1( params +# ).conversion2( params ) ... The final temp file is moved or copied to +# the desired destination by the main program (which can be epspdf +# itself). + +# The use of exception handling makes it unnecessary to inspect return +# values. + +# Limitation +# HiRes boundingboxes are ignored. +# Two reasons: +# - they get lost anyhow on conversion to pdf +# - They are not required so when you find one you have to make sure +# that it doesn't refer to an included eps. This would +# require additional logic. + +########################################### + +# some initialization in case this file is used as main program + +if __FILE__ == $0 + + # epspdfboot.rb: modify loadpath in a way which works both + # with a full Ruby and with a custom subset ruby + + require __FILE__.sub( /epspdf\.rb/, 'epspdfboot.rb' ) + + # turn on warnings + #$VERBOSE = 1 + +end # if main program + +########################################### + +# Error handling + +# internal error: method should not have been called +# under the current conditions +class EPCallError < StandardError; end + +# Can't get a valid boundingbox for an eps file +class EPBBError < StandardError; end + +# system call failed +class EPSystemError < StandardError; end + +########################################### + +# PostScript header file for grayscaling + +$GRAYHEAD = File.join( File.dirname( __FILE__ ), "makegray.pro" ) + +########################################### + +# handle auto-detected and saved settings + +require 'epspdfrc' + +########################################### + +# Transient options. These will never be saved. +# We set default values here. + +$options = { + 'type' => nil, + 'page' => nil, + 'bbox' => false, + 'gray' => false, + 'gRAY' => false, + 'info' => false +} + +class << $options + + # create shortcut methods $options.x and $options.x= + # for reading and writing hash elements. + + $options.each_key { |k| + eval "def #{k} ; self[\'#{k}\'] ; end" + eval "def #{k}=(v) ; self[\'#{k}\']=v ; end" + } + +end # class + +########################################### + +def hash_prn( h ) + if h.empty? + s = "No parameters" + $/ + else + s = "Parameters were:" + $/ + h.each_key { |k| s = s + ' ' + k.to_s + ' => ' + h[ k ].to_s + $/ } + end + s +end + +########################################### + +require 'fileutils' +#include FileUtils::Verbose +include FileUtils + +########################################### + +# Mode strings for file i/o + +if ARCH == 'w32' + $W = 'wb' + $A = 'ab' + $R = 'rb' +else + $W = 'w' + $A = 'a' + $R = 'r' +end + +########################################### + +# copy a slice of a file; is there no such standard function? +# some eps files are very large; don't slurp the file at one go. +# Mode: can be set to append 'a' rather than write 'w'. + +def sliceFile( source, dest, len, offs, mode=$W ) + buffer = '' + File.open( source ) do |s| + s.binmode if ARCH == 'w32' + s.seek( offs, IO::SEEK_SET ) + if s.pos == offs + File.open( dest, mode ) do |d| + d.binmode if ARCH == 'w32' + tocopy = len + while tocopy>0 and s.read( [ tocopy, 16384 ].min, buffer ) + tocopy = tocopy - d.write( buffer ) + end # while + end # |d| (automatic closing) + end # if s.seek + end # |s| (automatic closing) + # return value true if anything has been copied + File.size?( dest ) # nil if zero-length +end # def + +# write our own file copy, to bypass bug in FileUtils.cp + +def ccp( source, dest ) + sliceFile( source, dest, File.size( source ), 0, $W ) +end + +########################################### + +# logging + +def write_log( s ) + if test( ?e, LOGFILE ) and File.size( LOGFILE ) > 100000 + rm( LOGFILE_OLD ) if test( ?e, LOGFILE_OLD ) + ccp( LOGFILE, LOGFILE_OLD ) + File.truncate( LOGFILE, 0 ) + end + File.open( LOGFILE, 'a' ) { |f| + f.print( "#{$$} #{Time.now.strftime('%Y/%m/%d %H:%M:%S')} #{s}\n" ) + } +end + +########################################### + +require 'tmpdir' + +#$DEBUG=1 + +# save filenames for cleanup at end +$tempfiles = [] + +def mktemp( ext ) + isdone = nil + (0..99).each do |i| + fname = Dir.tmpdir + File::SEPARATOR + \ + sprintf( '%d_%02d.%s', $$, i, ext ) # $$ is process id + next if test( ?e, fname ) + File.open( fname, 'w' ) do |f|; end # creates empty file + isdone = 1 + $tempfiles.unshift( fname ) + return fname + end # each do |i| + fail StandardError, "Cannot create temp file" unless isdone +end # def + +def cleantemp + write_log( "Cleaning tempfiles" + $/ + $tempfiles.join( $/ ) ) + $tempfiles.each{ |tf|; rm( tf ) } +end + +########################################### + +# identifying file type + +# EPS with preview starts with 0xC5D0D3C6 +# Encapsulated PostScript starts with %!PS-Adobe-n.n EPSF-n.n +# but %!PS-Adobe-n.n will also be identified as Encapsulated PostScript +# if the filename extension suggests it. +# PostScript starts with %! +# PDF starts with %PDF-version + +def identify( path ) + filestart = nil + File.open( path, $R ) do |f| + filestart = f.read( 23 ) + end # this syntax automatically closes the file at end of block + case filestart + when /^\xc5\xd0\xd3\xc6/ + 'epsPreview' + when /^%!PS-Adobe-\d\.\d EPSF-\d\.\d/ + 'eps' + when /^%!PS-Adobe-\d\.\d/ + ( path =~ /\.ep(i|s|si|sf)$/i ) ? 'eps' : 'ps' + when /^%!/ + 'ps' + when /^%PDF/ + 'pdf' + else + 'other' + end # case +end # def + +########################################### + +# Boundingboxes + +BB_PAT = /^\s*%%BoundingBox:\s*(-?\d+)((\s+-?\d+){3})\s*$/ +BB_END = /^\s*%%BoundingBox:\s*\(\s*atend\s*\)\s*$/ +# for multiline matching: +BB_PAT1 = /%%BoundingBox:\s*(-?\d+)((\s+-?\d+){3})/m +BB_END1 = /%%BoundingBox:\s*\(\s*atend\s*\)/m + +class Bb + + attr_accessor :llx, :lly, :urx, :ury + + def initialize( llx, lly, urx, ury ) + @llx, @lly, @urx, @ury = llx, lly, urx, ury + [@llx, @lly, @urx, @ury].each { |l| l = l.to_i.to_s } + end + + def Bb.from_bbcomment( s ) + return nil unless s =~ BB_PAT + llx, lly, urx, ury = + s.sub( /^\s*%%BoundingBox:\s*/, '' ).split( /\s+/ ) + Bb.new( llx, lly, urx, ury ) + end + + def height + ( @ury.to_i - @lly.to_i ).to_s + end + + def width + ( @urx.to_i - @llx.to_i ).to_s + end + + def valid + @llx.to_i < @urx.to_i and @lly.to_i < @ury.to_i + end + + def non_negative + valid and @llx.to_i >= 0 and @lly.to_i >= 0 + end + + def copy + Bb.new( @llx, @lly, @urx, @ury ) + end + + def prn + "#{@llx} #{@lly} #{@urx} #{@ury}" + end + + def expand + i = ( $settings.bb_spread ).to_i + return if i <= 0 + @llx = ( [ 0, @llx.to_i - i ].max ).to_s + @lly = ( [ 0, @lly.to_i - i ].max ).to_s + @urx = ( @urx.to_i + i ).to_s + @ury = ( @ury.to_i + i ).to_s + end + + ################## + + # wrapper code for a boundingbox; + # moves lower left corner of eps to (0,0) + # and defines a page size identical to the eps width and height. + # The gsave in the wrapper code should be matched by + # a grestore at the end of the PostScript code. + # This grestore can be specified on the Ghostscript command-line. + + def wrapper + fail EPBBError, prn unless valid + fname = mktemp( 'ps' ) + File.open( fname, $W ) do |f| + f.binmode if ARCH == 'w32' + f.write( "%%BoundingBox: 0 0 #{width} #{height}\n" + + "<< /PageSize [#{width} #{height}] >>" + + " setpagedevice\n" + + "gsave #{(-(@llx.to_i)).to_s} #{(-(@lly.to_i)).to_s}" + + " translate\n" ) > 0 + end # open + return fname + end # wrapper + + ################## + + # convert boundingbox to boundingbox comment + + def bbcomment + fail EPBBError, prn unless valid + "%%BoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}" + end + +end # class Bb + +########################################### + +# PsPdf class definition + +class PsPdf + + protected + + SAFESIZE = 16000 + + public + + # class methods ### + + def PsPdf.pdf_options + "-dPDFSETTINGS=/#{$settings.pdf_target}" + \ + ( $settings.pdf_version == 'default' ? '' : \ + " -dCompatibilityLevel=#{$settings.pdf_version}" ) + \ + ($settings.pdf_custom ? ' ' + $settings.pdf_custom : '') + end + + def PsPdf.gs_options + "-dNOPAUSE -dBATCH -q -dSAFER" + end + + def PsPdf.ps_options( sep ) + # the sep(arable_color) option forces a cmyk color model, + # which should improve chances of grayscaling later on. + if sep + case $settings.ps_options + when /-level\dsep\b/ + $settings.ps_options + when /-level\d\b/ + $settings.ps_options.sub( /(-level\d)/, '\1sep' ) + else + $settings.ps_options + " -level2sep" + end # case + else + $settings.ps_options + end # ifthenelse sep + end + + # instance methods ### + + attr_accessor :path, :bb, :type, :npages, :atfront + + ################## + + def initialize( params={} ) + + ext = params[ 'ext' ] + file = params[ 'file' ] + + if not ext and not file + @path = nil + @type = nil + elsif not file + @path = mktemp( ext ) + @type = case ext.downcase + when 'pdf' + 'pdf' + when 'eps' + 'eps' + when 'ps' + 'ps' + else + 'other' + end + else + @path = file + @type = identify( file ) + end + end # initialize + + ################## + + # debug string for EPCallError + + def buginfo( params = nil ) + b = "Source was: " + @path + $/ + b = b + hash_prn( params ) if params + b + end + + ################## + + # Find boundingbox, simple case. + # We shall call this method only for eps PsPdf objects which were + # converted by pdftops or Ghostscript, so we can be sure that + # the boundingbox is not (atend). The file is not rewritten. + + def find_bb_simple + + fail EPCallError, buginfo unless @type == 'eps' + @bb = nil + slurp = '' + File.open( @path, $R ) do |fl| + slurp = fl.read( [File.size(@path), SAFESIZE].min ) + end + lines = slurp.split( /\r\n?|\n/ ) + lines.each do |l| + if l =~ BB_PAT + @bb = Bb.from_bbcomment( l ) + break + end + end # do |l| + fail EPBBError, @path unless @bb and @bb.valid + + end # def find_bb_simple + + ################## + + def pdf_pages + + fail EPCallError, buginfo unless @type == 'pdf' + + @npages = nil + + # get n. of pages; the Ghostscript pdf2dsc.ps script will + # create a list of pages. It seems to ignore logical p. numbers. + + dsc = mktemp( "dsc" ) + cmd = "\"#{$settings.gs_prog}\" -dNODISPLAY -q" + + " -sPDFname=\"#{@path}\" -sDSCname=\"#{dsc}\" pdf2dsc.ps" + write_log cmd # if $DEBUG + fail EPSystemError, cmd unless system( cmd ) and test( ?s, dsc ) + lines = [] + File.open( dsc, $R ) do |f| + lines = f.read( SAFESIZE ).split( /\r\n?|\n/ ) + end + lines.each do |l| + if l =~ /^%%Pages:\s+(\d+)\s*$/ + @npages = $1.to_i + break + end # if =~ + end # do |l| + + return @npages + + end # pdf_pages + + ############################################################# + + # direct conversions. These methods return a PsPdf object, + # and raise an exception in case of failure. + # Direct conversions convert at most once between PostScript and pdf. + # They always write to a temporary file. + + ################## + + # eps_clean: write source eps to an eps without preview, and + # with a boundingbox in the header. + # the eps is always written to a new file. + + def eps_clean + + fail EPCallError, buginfo if @type != 'eps' and @type != 'epsPreview' + atend = nil + slurp = '' + offset, ps_len = nil, nil + if @type == 'eps' + offset = 0 + ps_len = File.size( @path ) + else + # read ToC; see Adobe EPS spec + File.open( @path, $R ) do |fl| + # bug workaround for unpack + if "\001\000\000\000".unpack( 'V' )[0] == 1 + dummy, offset, ps_len = fl.read( 12 ).unpack( 'VVV' ) + else + dummy, offset, ps_len = fl.read( 12 ).unpack( 'NNN' ) + end + end # File + end # ifthenelse @type + + # boundingbox unknown and possibly atend + @bb, @atfront = nil, nil + + # limit search for boundingbox comments. + # For very large eps files, we don't want to scan the entire file. + # A boundingbox comment should be in the header or trailer, + # so scanning the first and last few KB should be plenty. + + File.open( @path, $R ) do |fl| + fl.seek( offset, IO::SEEK_SET ) + slurp = fl.read( [ps_len,SAFESIZE].min ) + end + + # We capture both lines and separators, as an easy way to + # keep track of how many bytes have been read. + # It is not necessary to treat lines and separators differently. + + lines = slurp.split( /(\r\n?|\n)/ ) + pre_block = '' + post_block = '' + bb_comment = '' + lines.each do |l| + pre_block = pre_block + l + if l =~ BB_PAT + @bb = Bb.from_bbcomment( l ) + @atfront = true + break + elsif l =~ BB_END + atend = true + break + end # =~ BB_PAT + end # do |l| + if atend + if ps_len > SAFESIZE + File.open( @path, $R ) do |fl| + fl.seek( offset + ps_len - SAFESIZE, IO::SEEK_SET ) + slurp = fl.read( SAFESIZE ) + end + lines = slurp.split( /(\r\n?|\n)/ ) + end + lines.reverse_each do |l| + post_block = l + post_block + if l =~ BB_PAT + bb_comment = l + @bb = Bb.from_bbcomment( bb_comment ) + break + end # if + end # do + end # if atend + fail EPBBError, @path unless @bb.valid + retVal = PsPdf.new( 'ext' => 'eps' ) + if @type == 'eps' and @atfront # simple copy + ccp( @path, retVal.path ) + elsif @atfront # copy a slice + sliceFile( @path, retVal.path, ps_len, offset ) + elsif atend # relocate boundingbox + File.open( retVal.path, $W ) do |fl| + fl.write( pre_block.sub( BB_END1, bb_comment )) + end + sliceFile( @path, retVal.path, + ps_len - pre_block.length - post_block.length, + offset + pre_block.length, $A ) + File.open( retVal.path, $A ) do |fl| + fl.write( post_block.sub( BB_PAT1, '%' )) + end + end # if atend + retVal.bb = @bb.copy + retVal.atfront = true + return retVal + + end # eps_clean + + ################## + + # Use the Ghostscript bbox device to give an eps a tight boundingbox. + # Newer versions of Ghostscript can do that by themselves, but + # for the time being we just assume a bbox device. + # The eps should already have been cleaned up by eps_clean + # and the current boundingbox should not contain negative coordinates, + # otherwise the bbox output device may give incorrect results. + # Maybe we should test whether gs' bbox device can handle + # nonnegative coordinates. + # The boundingbox in the eps is rewritten, but the eps is + # not otherwise converted. + # We don't create a new PsPdf object. + + def fix_bb + + fail EPCallError, buginfo \ + unless @type == 'eps' and @bb.non_negative + cmd = "#{short_name($settings.gs_prog)} #{PsPdf.gs_options}" + + " -sDEVICE=bbox #{short_name(@path)} 2>&1" + write_log cmd # if $DEBUG + bb_output = `#{cmd}` + fail EPSystemError, cmd unless $? == 0 and bb_output + bb_output.split(/\r\n?|\n/).each do |b| + if b =~ BB_PAT + bb_temp = Bb.from_bbcomment( b ) + fail EPBBError, bb_output unless bb_temp.valid + bb_temp.expand + @bb = bb_temp.copy + # write a new eps file with a new boundingbox comment + ps_len = File.size( @path ) + slurp = '' + File.open( @path, $R ) do |fl| + slurp = fl.read( [ps_len,SAFESIZE].min ) + end + lines = slurp.split( /(\r\n?|\n)/ ) + pre_block = '' + pre_length = 0 + lines.each do |l| + pre_length = pre_length + l.length + if l =~ BB_PAT + pre_block = pre_block + bb_temp.bbcomment + oldpath = @path + @path = mktemp( 'eps' ) + File.open( @path, $W ) { |fl| fl.write( pre_block ) } + sliceFile( oldpath, @path, \ + ps_len - pre_length, pre_length, $A ) + return self + else + pre_block = pre_block + l + end + end # do |l| + fail EPBBError, bb_output + end # if =~ BB_PAT + end # do |b| + fail EPBBError, bb_output + + end # fix_bb + + ################## + + # Convert eps to pdf. + # The eps should already have a boundingbox in the header. + # When fix_bbox, let Ghostscript calculate a tight boundingbox. + # For this, we require non-negative boundingbox coordinates. + + def eps_to_pdf( params={} ) + + gray = params[ 'gray' ] + + fail EPCallError, buginfo( params ) unless @type == 'eps' and @atfront + + wrp = @bb.wrapper + + retVal = PsPdf.new( 'ext' => 'pdf' ) + cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + + " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" + + " -sOutputFile=\"#{retVal.path}\"" + + ( gray ? (' "' + $GRAYHEAD + '"') : "" ) + + " \"#{wrp}\" \"#{@path}\" -c grestore" + write_log cmd # if $DEBUG + fail EPSystemError, cmd \ + unless system( cmd ) and test( ?s, retVal.path ) + retVal.npages = 1 + return retVal + + end # eps_to_pdf + + ################## + + # Convert source pdf to eps. + # The option sep_color is ignored if pdftops is not available. + + def pdf_to_eps( params={} ) + + page = params[ 'page' ] ? params[ 'page' ].to_i : 1 + sep = params[ 'sep' ] + + fail EPCallError, buginfo( params ) unless @type == 'pdf' + fail EPCallError, buginfo( params ) \ + unless page > 0 and page <= @npages + retVal = PsPdf.new( 'ext' => 'eps' ) + if $settings.pdftops_prog and $settings.use_pdftops + cmd = "\"#{$settings.pdftops_prog}\"" + + " #{PsPdf.ps_options( sep )}" + + " -eps -f #{page} -l #{page}" + + " \"#{@path}\" \"#{retVal.path}\"" + else + cmd = "\"#{$settings.gs_prog}\" -sDEVICE=epswrite" + + " #{PsPdf.gs_options}" + + " -dFirstPage=#{page}" + + " -dLastPage=#{page}" + + " -sOutputFile=\"#{retVal.path}\" \"#{@path}\"" + end + write_log cmd # if $DEBUG + fail EPSystemError, cmd unless \ + system( cmd ) and test( ?s, retVal.path ) + retVal.atfront = 1 + retVal.find_bb_simple + return retVal + + end # pdf_to_eps + + ################## + + def ps_to_pdf( params={} ) + + gray = params[ 'gray' ] + + fail EPCallError, buginfo( params ) \ + unless @type == 'ps' + + retVal = PsPdf.new( 'ext' => 'pdf' ) + cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + + " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" + + " -sOutputFile=\"#{retVal.path}\"" + + ( gray ? (' "' + $GRAYHEAD + '"') : "" ) + " \"#{@path}\"" + write_log cmd # if $DEBUG + fail EPSystemError, cmd \ + unless system( cmd ) and test( ?s, retVal.path ) + + retVal.pdf_pages + return retVal + + end # def ps_to_pdf + + ################## + + def pdf_to_ps( params={} ) + + sep = params[ 'sep' ] + page = params[ 'page' ] ? params[ 'page' ].to_s : nil + + fail EPCallError, buginfo( params ) unless \ + $settings.pdftops_prog and $settings.use_pdftops and @type == 'pdf' + retVal = PsPdf.new( 'ext' => 'ps' ) + cmd = "\"#{$settings.pdftops_prog}\" #{PsPdf.ps_options( sep )}" + + ( page ? " -f #{page} -l #{page}" : '' ) + + " -paper match \"#{@path}\" \"#{retVal.path}\"" + write_log cmd # if $DEBUG + fail EPSystemError, cmd unless \ + system( cmd ) and test( ?s, retVal.path ) + return retVal + + end # pdf_to_ps + + ################## + + # all possible conversions, as concatenations of direct conversions. + + def any_to_any( params={} ) + + type = params[ 'type' ] + page = params[ 'page' ] + bbox = params[ 'bbox' ] + gray = params[ 'gray' ] + gRAY = params[ 'gRAY' ] # try harder to grayscale + gray = 1 if gRAY + bbox = nil if type == 'ps' + + fail EPCallError, buginfo( params ) \ + unless ( type=='eps' or type=='pdf' or type=='ps' ) + fail EPCallError, buginfo( params ) \ + if @type=='other' + fail EPCallError, buginfo( params ) \ + if type=='ps' and bbox + fail EPCallError, buginfo( params ) \ + if @type=='eps' and type=='ps' + + # gRAY tries harder to grayscale, by converting color first to cmyk + # even if it requires an additional eps - pdf - eps roundtrip. + # Normally, conversion to cmyk is done only if it doesn't take + # an extra roundtrip. + # The separable color conversion is an option of pdftops. + + gRAY = nil unless \ + ( $settings.pdftops_prog and $settings.use_pdftops ) + + pp = self + + pp = pp.eps_clean if pp.type == 'epsPreview' + pp = pp.eps_clean if pp.type == 'eps' and \ + not ( pp.bb and pp.atfront ) + pp.pdf_pages if pp.type == 'pdf' and not pp.npages + + case pp.type + when 'eps' + + # in some cases extra eps => pdf => eps roundtrip: + # roundtrip guarantees bb.non_negative, necessary for fix_bb. + # pdf_to_ps( sep ) improves chances of grayscaling. + + pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \ + if gRAY or ( bbox and not pp.bb.non_negative ) + + case type + when 'eps' + pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_eps if gray + pp = pp.fix_bb if bbox + return pp + + when 'pdf' + pp = pp.fix_bb if bbox + return pp.eps_to_pdf( 'gray' => gray ) + + end # case type + + when 'pdf' + + case type + when 'eps' + if not gray + pp = pp.pdf_to_eps( 'page' => page ) + pp = pp.fix_bb if bbox + return pp + else + pp = pp.pdf_to_eps( 'page' => page, 'sep' => 1 ) + pp = pp.fix_bb if bbox + pp = pp.eps_to_pdf( 'gray' => 1 ) + return pp.pdf_to_eps + end + + when 'pdf' + return pp unless ( gray or bbox or page ) + if bbox or not $settings.pdftops_prog + pp = pp.pdf_to_eps( 'page' => page, 'sep' => gray ) + pp = pp.fix_bb if bbox + return pp.eps_to_pdf( 'gray' => gray ) + else + pp = pp.pdf_to_ps( 'page' => page, 'sep' => gray ) + return pp.ps_to_pdf( 'gray' => gray ) + end + + when 'ps' + if gray + pp = pp.pdf_to_ps( 'sep' => 1 ) + pp = pp.ps_to_pdf( 'gray' => 1 ) + end + return pp.pdf_to_ps + + end # case type + + when 'ps' + + case type + when 'eps' + if gRAY + pp = pp.ps_to_pdf.pdf_to_eps( 'page' => page, 'sep' => 1 ) + pp = pp.eps_to_pdf( 'gray' => 1 ) + pp = pp.pdf_to_eps + else + pp = pp.ps_to_pdf( 'gray' => gray ) + pp = pp.pdf_to_eps( 'page' => page ) + end + return pp.fix_bb + + when 'pdf' + if bbox + pp = pp.ps_to_pdf( 'sep' => gray ) + pp = pp.pdf_to_eps( 'page' => page ) + pp = pp.fix_bb + return pp = pp.eps_to_pdf( 'gray' => gray ) + elsif page + pp = pp.ps_to_pdf( 'sep' => gray ) + pp = pp.pdf_to_ps( 'page' => page ) + return pp = pp.ps_to_pdf( 'gray' => gray ) + else + pp = pp.ps_to_pdf.pdf_to_ps( 'sep' => 1 ) if gRAY + return pp.ps_to_pdf( 'gray' => gray ) + end + + when 'ps' + return pp unless page or gray + if gRAY + pp = pp.ps_to_pdf.pdf_to_ps( 'page' => page, 'sep' => 1 ) + return pp.ps_to_pdf( 'gray' => 1 ).pdf_to_ps + else + pp = pp.ps_to_pdf( 'gray' => gray ) + return pp = pp.pdf_to_ps( 'page' => page ) + end + + end # case type + + end # case pp.type + + fail "Unsupported conversion" + + end # any_to_any + +end # class PsPdf + +################################# +# main program when not called from GUI + +if __FILE__ == $0 + +require 'optparse' + +# create a pause to examine temp files +def abortt( msg ) + if $DEBUG + $stderr.puts msg + "\nPress <enter> to finish" + $stdin.gets + end + fail +end + +begin # outer rescue block + +save_settings = false + +opts = OptionParser.new do |opts| + # for help output + opts.banner = "Convert between [e]ps and pdf formats" + opts.separator "Usage: epspdf.rb [options] infile [outfile]" + opts.separator "" + opts.separator "Default for outfile is file.pdf" + + " if infile is file.eps or file.ps" + opts.separator "Default for outfile is file.eps" + + " if infile is file.pdf" + opts.separator "" + + opts.on( "-I", + "Ignore pdftops even if available", + "(default: use if available)" ) do |opt| + $settings.ignore_pdftops = '1' + end + + opts.on( "-U", + "Use pdftops if available", + "(overrides previous -I setting)" ) do |opt| + $settings.ignore_pdftops = '0' + end + + opts.on( "-T TARGET", "--target=TARGET", PDF_TARGETS, + "Target use of pdf; one of", + "#{PDF_TARGETS.join( ', ' )}" ) do |opt| + $settings.pdf_target = opt + end + + opts.on( "-V VERSION", "--version=VERSION", PDF_VERSIONS, + "Pdf version to be generated;", + "one of #{PDF_VERSIONS.join( ', ' )}" ) do |opt| + $settings.pdf_version = opt + end + + opts.on( "-C CUSTOMOPTIONS", "--custom=CUSTOMOPTIONS", + "Custom options for conversion to pdf,", + "view Use.htm and ps2pdf.htm from", + "the Ghostscript documentation set" ) do |opt| + $settings.pdf_custom = opt + end + + opts.on( "-P PSOPTIONS", "--psoptions=PSOPTIONS", + "Options for pdftops; default -level2,", + "don't include -eps or page number options;", + "these will be generated by the program" ) do |opt| + $settings.ps_options = opt + end + + opts.on( "-p PAGENUMBER", "--pagenumber=PAGENUMBER", + "Page to be converted or selected", Integer ) do |opt| + $options.page = opt + end + + opts.on( "-b", "--bbox", "--BoundingBox", + "Compute tight boundingbox" ) do |opt| + $options.bbox = true + end + + opts.on( "-g", "--gray", "--grey", + "Convert to grayscale;", + "success not guaranteed" ) do |opt| + $options.gray = true + end + + opts.on( "-G", "--GRAY", "--GREY", + "Try harder to convert to grayscale;", + "success still not guaranteed" ) do |opt| + $options.gRAY = true + end + + opts.on( "-i", "--info", + "Info: display detected filetype" ) do |opt| + $options.info = true + end + + opts.on( "-s", + "Save (some) settings" ) do |opt| + save_settings = true + end + + opts.on( "-d", "Debug: don't remove temp files" ) do |opt| + $DEBUG = 1 + end + + opts.separator "" + + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end + + # opts.on_tail("--version", "Show version") do + # puts OptionParser::Version.join('.') + # exit + # end +end # opts + +# parse options destructively + +opts.parse!( ARGV ) + +$options.page = 1 if $options.bbox and not $options.page + +$settings.write_settings if save_settings + +if ARGV.length < 1 + puts opts if not save_settings # help output + # puts "Settings:" + # $settings.each_key do |k| + # puts "key #{k} value #{$settings[k].val}" + # end + abort +elsif $options.info + puts "File type of #{ARGV[0]} is #{PsPdf.new( 'file' => ARGV[0] ).type}" + exit +elsif ARGV.length > 1 and + File.expand_path( ARGV[0] ) == File.expand_path( ARGV[1] ) + abort " Input and output files should be different." +else + infile = ARGV[0] + abort( infile + " not found or empty" ) unless test( ?s, infile ) +end + +# done with options + +######################################## + +source = PsPdf.new( 'file' => infile ) + +# We aren't finicky about the extension of the input file, +# but still want to check whether pdf is pdf. + +case source.type +when 'eps', 'epsPreview', 'ps' + abort "Wrong extension; input is not in pdf format" \ + if infile =~ /\.pdf$/i +when 'pdf' + abort "Wrong extension; input is in pdf format" \ + if infile !~ /\.pdf$/i +else + abort "Invalid input file type; not [e]ps or pdf" \ + if source.type == 'other' +end # case source.type + +# find or construct output file name +if ARGV.length > 1 + outfile = ARGV[1] +else + case source.type + when 'eps', 'epsPreview', 'ps' + outfile = infile.sub( /\.[^.]+$/, '.pdf' ) + when 'pdf' + outfile = infile.sub( /\.[^.]+$/, '.eps' ) + end # case +end # ifthenelse ARGV.length +outfile = File.expand_path( outfile ) + +$options.type = case outfile +when /\.pdf$/i + "pdf" +when /\.ps$/i + "ps" +when /\.eps$/i + "eps" +else + nil +end # case outfile + +abort "Unknown or unsupported output file extension" \ + unless $options.type +abort "Output format not supported without xpdf utilities" \ + if outfile == 'ps' and not $settings.pstopdf_prog + +pp = PsPdf.new( 'file' => infile ) + +begin # inner rescue block + pp = pp.any_to_any( $options ) +rescue EPCallError => exc + mess = + "Wrong method call or conversion not supported or wrong page number" + + $/ + exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + fail StandardError +rescue EPBBError => exc + mess = "Boundingbox problem" + $/ + + exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + fail StandardError +rescue EPSystemError => exc + mess = "Problem with system call" + $/ + + exc.message + $/ + exc.backtrace.join( $/ ) + write_log( mess ) + puts mess + fail StandardError +end # rescue block + +ccp( pp.path, outfile ) + +cleantemp unless $DEBUG == 1 + +rescue => detail +write_log detail.backtrace.join( $/ ) +puts detail.backtrace.join( $/ ) +exit +end # outer rescue block + +end # if called as main program +__END__ |