diff options
Diffstat (limited to 'Master/texmf-dist/scripts/epspdf/epspdf.rb')
-rwxr-xr-x | Master/texmf-dist/scripts/epspdf/epspdf.rb | 1479 |
1 files changed, 0 insertions, 1479 deletions
diff --git a/Master/texmf-dist/scripts/epspdf/epspdf.rb b/Master/texmf-dist/scripts/epspdf/epspdf.rb deleted file mode 100755 index 47a0c1f65b9..00000000000 --- a/Master/texmf-dist/scripts/epspdf/epspdf.rb +++ /dev/null @@ -1,1479 +0,0 @@ -#!/usr/bin/env ruby - -# epspdf conversion utility, main source - -##### -# Copyright (C) 2006, 2008, 2009, 2010, 2011 Siep Kroonenberg -# n dot s dot kroonenberg at rug dot nl -# -# This program is free software, licensed under the GNU GPL, >=2.0. -# This software comes with absolutely NO WARRANTY. Use at your own risk! -##### - -# Operations on a PostScript- or pdf file. -# The focus is on converting between eps and pdf. - -# Code is organized as a set of single-step conversion methods plus an -# any-to-any conversion chaining them together. - -# `Target' is not a parameter; all conversions write to a new temp -# file. Conversions can be chained: object.conversion1( params -# ).conversion2( params ) ... The final temp file is moved or copied to -# the desired destination by the main program (which can be epspdf.rb -# itself). - -# The use of exception handling makes it unnecessary to inspect return -# values. - -########################################### - -# some initialization - -# add directory of this source to loadpath -# WARNING -# readlink apparently only works right from the directory of the symlink - -$SCRIPTDIR = File.dirname( File.expand_path( __FILE__ )) -if RUBY_PLATFORM !~ /win32|mingw/ and File.symlink?( __FILE__ ) - savedir = Dir.pwd - Dir.chdir( $SCRIPTDIR ) - # puts File.readlink( __FILE__ ) - $SCRIPTDIR = File.dirname( File.expand_path( File.readlink( __FILE__ ))) - Dir.chdir( savedir ) -end -$:.unshift( $SCRIPTDIR ) -# puts $: - -# turn on warnings -#$VERBOSE = 1 - -$from_gui = nil - - -########################################### - -# Error handling - -# internal error: method should not have been called -# under the current conditions -class EPCallError < StandardError; end - -# Can't get a valid boundingbox for an eps file -class EPBBError < StandardError; end - -# copying failed -class EPCopyError < StandardError; end - -# system call failed -class EPSystemError < StandardError; end - -########################################### - -# PostScript header file for grayscaling - -$GRAYHEAD = File.join( $SCRIPTDIR, "makegray.pro" ) - -########################################### - -# handle auto-detected and saved settings - -#require 'epspdfrc' -require 'epspdfrc' - -########################################### - -# Transient options. These will never be saved. -# We set default values here. - -$options = { - 'type' => nil, - 'page' => nil, - 'bbox' => false, - 'gray' => false, - 'gRAY' => false, - 'info' => false -} - -class << $options - - # create shortcut methods $options.x and $options.x= - # for reading and writing hash elements. - - $options.each_key { |k| - eval "def #{k} ; self[\'#{k}\'] ; end" - eval "def #{k}=(v) ; self[\'#{k}\']=v ; end" - } - -end # class - -########################################### - -def hash_prn( h ) - if h.empty? - s = "No parameters" + $/ - else - s = "Parameters were:" + $/ - h.each_key { |k| s = s + ' ' + k.to_s + ' => ' + h[ k ].to_s + $/ } - end - s -end - -########################################### - -require 'fileutils' -#include FileUtils::Verbose -include FileUtils - -########################################### - -# Mode strings for file i/o - -if ARCH == 'w32' - $W = 'wb' - $A = 'ab' - $R = 'rb' -else - $W = 'w' - $A = 'a' - $R = 'r' -end - -########################################### - -# copy a slice of a file; is there no such standard function? -# some eps files are very large; don't slurp the file at one go. -# Mode: can be set to append 'a' rather than write 'w'. - -def sliceFile( source, dest, len, offs, mode=$W ) - buffer = '' - File.open( source ) do |s| - s.binmode if ARCH == 'w32' - s.seek( offs, IO::SEEK_SET ) - if s.pos == offs - begin - File.open( dest, mode ) do |d| - d.binmode if ARCH == 'w32' - tocopy = len - while tocopy>0 and s.read( [ tocopy, 16384 ].min, buffer ) - tocopy = tocopy - d.write( buffer ) - end # while - end # do - rescue - fail EPCopyError, "Failure to copy to #{dest}" - end - end # if s.seek - end # |s| (automatic closing) - # return value true if anything has been copied - File.size?( dest ) # nil if zero-length -end # def - -# write our own file copy, to bypass bug in FileUtils.cp - -def ccp( source, dest ) - sliceFile( source, dest, File.size( source ), 0, $W ) -end - -########################################### - -# logging - -def write_log( s ) - if test( ?e, LOGFILE ) and File.size( LOGFILE ) > 100000 - rm( LOGFILE_OLD ) if test( ?e, LOGFILE_OLD ) - ccp( LOGFILE, LOGFILE_OLD ) - File.truncate( LOGFILE, 0 ) - end - File.open( LOGFILE, 'a' ) { |f| - f.print( "#{$$} #{Time.now.strftime('%Y/%m/%d %H:%M:%S')} #{s}\n" ) - } - puts( s ) if $from_gui - -end - -########################################### - -require 'tmpdir' - -#$DEBUG=1 - -# save filenames for cleanup at end -$tempfiles = [] - -def mktemp( ext ) - isdone = nil - (0..99).each do |i| - fname = Dir.tmpdir + File::SEPARATOR + \ - sprintf( '%d_%02d.%s', $$, i, ext ) # $$ is process id - next if test( ?e, fname ) - File.open( fname, 'w' ) do |f|; end # creates empty file - isdone = 1 - $tempfiles.unshift( fname ) - return fname - end # each do |i| - fail StandardError, "Cannot create temp file" unless isdone -end # def - -def cleantemp - write_log( "Cleaning tempfiles" + $/ + $tempfiles.join( $/ ) ) - $tempfiles.each{ |tf|; rm( tf ) } -end - -########################################### - -# identifying file type - -# EPS with preview starts with 0xC5D0D3C6 -# Encapsulated PostScript starts with %!PS-Adobe-n.n EPSF-n.n -# but %!PS-Adobe-n.n will also be identified as Encapsulated PostScript -# if the filename extension suggests it. -# PostScript starts with %! -# PDF starts with %PDF-version - -def identify( path ) - filestart = nil - File.open( path, $R ) do |f| - filestart = f.read( 23 ) - end # this syntax automatically closes the file at end of block - case filestart - when /^\xc5\xd0\xd3\xc6/ - 'epsPreview' - when /^%!PS-Adobe-\d\.\d EPSF-\d\.\d/ - 'eps' - when /^%!PS-Adobe-\d\.\d/ - ( path =~ /\.ep(i|s|si|sf)$/i ) ? 'eps' : 'ps' - when /^%!/ - 'ps' - when /^%PDF/ - 'pdf' - else - 'other' - end # case -end # def - -########################################### - -# Boundingboxes; first standard, then hires - -#changes: -#hires boundingboxes -#numbers may start with a plus-sign. DSC manual is ambiguous, -# PostScript manual allows it, but of course PS != DSC -#left-right and lower-upper need not be in natural order - -BB_PAT = /^\s*%%BoundingBox:\s*([-+]?\d+)((\s+[-+]?\d+){3})\s*$/ -BB_END = /^\s*%%BoundingBox:\s*\(\s*atend\s*\)\s*$/ - -class Bb - - attr_accessor :llx, :lly, :urx, :ury # strings - - def initialize( llx, lly, urx, ury ) - @llx, @lly, @urx, @ury = llx, lly, urx, ury - # guarantee valid syntax: - [@llx, @lly, @urx, @ury].each { |l| l = l.to_i.to_s } - @llx, @urx = @urx, @llx if @llx.to_i > @urx.to_i - @lly, @ury = @ury, @lly if @lly.to_i > @ury.to_i - end - - def Bb.from_comment( s ) - return nil unless s =~ BB_PAT - llx, lly, urx, ury = - s.sub( /^\s*%%BoundingBox:\s*/, '' ).split( /\s+/ ) - Bb.new( llx, lly, urx, ury ) - end - - def height - ( @ury.to_i - @lly.to_i ).to_s - end - - def width - ( @urx.to_i - @llx.to_i ).to_s - end - - def valid - @llx.to_i < @urx.to_i and @lly.to_i < @ury.to_i - end - - def non_negative - valid and @llx.to_i >= 0 and @lly.to_i >= 0 - end - - def copy - Bb.new( @llx, @lly, @urx, @ury ) - end - - def prn - "#{@llx} #{@lly} #{@urx} #{@ury}" - end - - def expand - i = ( $settings.bb_spread ).to_i - return if i <= 0 - @llx = ( [ 0, @llx.to_i - i ].max ).to_s - @lly = ( [ 0, @lly.to_i - i ].max ).to_s - @urx = ( @urx.to_i + i ).to_s - @ury = ( @ury.to_i + i ).to_s - end - - ################## - - # wrapper code for a boundingbox; - # moves lower left corner of eps to (0,0) - # and defines a page size identical to the eps width and height. - # The gsave in the wrapper code should be matched by - # a grestore at the end of the PostScript code. - # This grestore can be specified on the Ghostscript command-line. - - def wrapper - fail EPBBError, prn unless valid - fname = mktemp( 'ps' ) - File.open( fname, $W ) do |f| - f.binmode if ARCH == 'w32' - f.write( "%%BoundingBox: 0 0 #{width} #{height}\n" + - "<< /PageSize [#{width} #{height}] >>" + - " setpagedevice\n" + - "gsave #{(-(@llx.to_i)).to_s} #{(-(@lly.to_i)).to_s}" + - " translate\n" ) > 0 - end # open - return fname - end # wrapper - - ################## - - # convert boundingbox to boundingbox comment - - def comment - fail EPBBError, prn unless valid - "%%BoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}" - end - -end # class Bb - -# [-+](\d+(\.\d*)?|\.\d+)([eE]\d+)? PostScript number -HRBB_PAT = /^\s*%%HiResBoundingBox:\s*[-+]?(\d+(\.\d*)?|\.\d+)([eE]\d+)?((\s[-+]?(\d+(\.\d*)?|\.\d+)([eE]\d+)?){3})\s*$/ -HRBB_END = /^\s*%%HiResBoundingBox:\s*\(\s*atend\s*\)\s*$/ - -class HRBb - - attr_accessor :llx, :lly, :urx, :ury - - def initialize( llx, lly, urx, ury ) - @llx, @lly, @urx, @ury = llx, lly, urx, ury - [@llx, @lly, @urx, @ury].each do |l| - if l =~ /\./ - # make floats conform to Ruby syntax: - # decimal dots must be padded with digits on either side - l.sub!( /^\./, '0.' ) - l.sub!( /\.(?!\d)/, '.0' ) # (?!\d): zero-width neg. lookahead - end - l = l.to_f.to_s - end - @llx, @urx = @urx, @llx if @llx.to_f > @urx.to_f - @lly, @ury = @ury, @lly if @lly.to_f > @ury.to_f - end - - def HRBb.from_hrcomment( s ) - return nil unless s =~ HRBB_PAT - llx, lly, urx, ury = - s.sub( /^\s*%%HiResBoundingBox:\s*/, '' ).split( /\s+/ ) - HRBb.new( llx, lly, urx, ury ) - end - - def height - ( @ury.to_f - @lly.to_f ).to_s - end - - def width - ( @urx.to_f - @llx.to_f ).to_s - end - - def valid - @llx.to_f < @urx.to_f and @lly.to_f < @ury.to_f - end - - def non_negative - valid and @llx.to_f >= 0 and @lly.to_f >= 0 - end - - def copy - HRBb.new( @llx, @lly, @urx, @ury ) - end - - def prn - "#{@llx} #{@lly} #{@urx} #{@ury}" - end - - ################## - - # wrapper code for a hires boundingbox; - # moves lower left corner of eps to (0,0) - # and defines a page size identical to the eps width and height. - # The gsave in the wrapper code should be matched by - # a grestore at the end of the PostScript code. - # This grestore can be specified on the Ghostscript command-line. - - def wrapper - fail EPBBError, prn unless valid - fname = mktemp( 'ps' ) - File.open( fname, $W ) do |f| - f.binmode if ARCH == 'w32' - f.write( - "%%BoundingBox: 0 0 #{width.to_f.ceil} #{height.to_f.ceil}\n" + - "%%HiResBoundingBox: 0 0 #{width.to_f} #{height.to_f}\n" + - "<< /PageSize [#{width} #{height}] >>" + - " setpagedevice\n" + - "gsave #{(-(@llx.to_f)).to_s} #{(-(@lly.to_f)).to_s}" + - " translate\n" ) > 0 - end # open - return fname - end # wrapper - - ################## - - # convert hiresboundingbox to hires boundingbox comment - - def hrcomment - fail EPBBError, prn unless valid - "%%HiResBoundingBox: #{@llx} #{@lly} #{@urx} #{@ury}" - end - -end # class HRBb - -########################################### - -# PsPdf class definition - -class PsPdf - - protected - - SAFESIZE = 16000 - - public - - # class methods ### - - def PsPdf.pdf_options - "-dPDFSETTINGS=/#{$settings.pdf_target}" + \ - ( $settings.pdf_version == 'default' ? '' : \ - " -dCompatibilityLevel=#{$settings.pdf_version}" ) + \ - ($settings.pdf_custom ? ' ' + $settings.pdf_custom : '') - end - - def PsPdf.gs_options - "-dNOPAUSE -dBATCH -q -dSAFER" - end - - def PsPdf.ps_options( sep ) - # the sep(arable_color) option forces a cmyk color model, - # which should improve chances of grayscaling later on. - if sep - case $settings.ps_options - when /-level\dsep\b/ - $settings.ps_options - when /-level\d\b/ - $settings.ps_options.sub( /(-level\d)/, '\1sep' ) - else - $settings.ps_options + " -level3sep" - end # case - else - $settings.ps_options - end # ifthenelse sep - end - - # instance methods ### - - attr_accessor :path, :bb, :hrbb, :type, :npages, :atfront, :hr_atfront - - ################## - - def initialize( params={} ) - - ext = params[ 'ext' ] - file = params[ 'file' ] - - if not ext and not file - @path = nil - @type = nil - elsif not file - @path = mktemp( ext ) - @type = case ext.downcase - when 'pdf' - 'pdf' - when 'eps' - 'eps' - when 'ps' - 'ps' - else - 'other' - end - else - @path = file - @type = identify( file ) - @npages = pdf_pages if @type == 'pdf' # should we do this? - @npages = 1 if @type =~ /^eps/ - end - end # initialize - - def file_info - if @npages and @type !~ /^eps/ - return "File type of #{@path} is #{@type} with #{@npages} pages" - else - return "File type of #{@path} is #{@type}" - end - end - - ################## - - # debug string for EPCallError - - def buginfo( params = nil ) - b = "Source was: " + @path + $/ - b = b + param_hash_prn( params ) if params - b - end - - ################## - - # Find boundingbox, simple case. - # We shall call this method only for eps PsPdf objects which were - # converted by pdftops or Ghostscript, so we can be sure that - # the boundingbox is not (atend). - # We also assume that the hrbb lies within the bb. - # The file is not rewritten. - - def find_bb_simple - - fail EPCallError, buginfo unless @type == 'eps' - @bb = nil - @hrbb = nil - slurp = '' - File.open( @path, $R ) do |fl| - slurp = fl.read( [File.size(@path), SAFESIZE].min ) - end - lines = slurp.split( /\r\n?|\n/ ) - # look for a bb or a hrbb - # if a valid bb is found, check the next line for hrbb - # but look no further; we don't want to mistake a hrbb of - # an included eps for the hrbb of the outer eps. - lines.each do |l| - if l =~ BB_PAT - @bb = Bb.from_comment( l ) - elsif l =~ HRBB_PAT - @hrbb = HRBb.from_hrcomment( l ) - elsif @bb - break # stop looking; we expect hrbb next to bb - end - break if @bb and @hrbb - end # do |l| - fail EPBBError, @path unless @bb and @bb.valid - - end # def find_bb_simple - - ################## - - def pdf_pages - - fail EPCallError, buginfo unless @type == 'pdf' - - @npages = nil - - # get n. of pages; the Ghostscript pdf2dsc.ps script will - # create a list of pages. It seems to ignore logical p. numbers. - - dsc = mktemp( "dsc" ) - cmd = "\"#{$settings.gs_prog}\" -dNODISPLAY -q" + - " -sPDFname=\"#{@path}\" -sDSCname=\"#{dsc}\" pdf2dsc.ps" - write_log cmd # if $DEBUG - fail EPSystemError, cmd unless system( cmd ) and test( ?s, dsc ) - lines = [] - File.open( dsc, $R ) do |f| - lines = f.read( SAFESIZE ).split( /\r\n?|\n/ ) - end - lines.each do |l| - if l =~ /^%%Pages:\s+(\d+)\s*$/ - @npages = $1.to_i - break - end # if =~ - end # do |l| - - return @npages - - end # pdf_pages - - ############################################################# - - # direct conversions. These methods return a PsPdf object, - # and raise an exception in case of failure. - # Direct conversions convert at most once between PostScript and pdf. - # They always write to a temporary file. - - ################## - - # eps_clean: write source eps to an eps without preview, and - # with a boundingbox in the header. - # clean up any potential problems - # the eps is always written to a new file. - - def eps_clean - - fail EPCallError, buginfo if @type != 'eps' and @type != 'epsPreview' - atend = nil - hr_atend = nil - slurp = '' - offset, ps_len = nil, nil - if @type == 'eps' - offset = 0 - ps_len = File.size( @path ) - else - # read ToC; see Adobe EPS spec - File.open( @path, $R ) do |fl| - # bug workaround for unpack - if "\001\000\000\000".unpack( 'V' )[0] == 1 - dummy, offset, ps_len = fl.read( 12 ).unpack( 'VVV' ) - else - dummy, offset, ps_len = fl.read( 12 ).unpack( 'NNN' ) - end - end # File - end # ifthenelse @type - - # [hires] boundingbox unknown and possibly atend - @bb, @atfront, @hrbb, @hr_atfront = nil, nil, nil, nil - - # limit search for boundingbox comments. - # For very large eps files, we don't want to scan the entire file. - # A boundingbox comment should be in the header or trailer, - # so scanning the first and last few KB should be plenty. - - File.open( @path, $R ) do |fl| - fl.seek( offset, IO::SEEK_SET ) - slurp = fl.read( [ps_len,SAFESIZE].min ) - end - - # We capture both lines and separators, as an easy way to - # keep track of how many bytes have been read. - # we assume that if there is a hires bb then - # bb and hires bb are on consecutive lines. - # Otherwise the logic would get too messy. - - # The epsfile will be reconstituted from: - # a series of lines and line separators; then either - # - a bbox or - # - (bbox or hrbbox), separator, (hrbbox or bbox) - # the big blob - # possibly a trailer with removed bb comments - - pre_lines = slurp.split( /(\r\n?|\n)/ ) - bb_comment = '' - # initialize indices of bb comments to less than smallest index - i_bb = -1 - i_hrbb = -1 - i = -1 - i_end = -1 - pre_length = 0 - pre_lines.each do |l| - pre_length += l.length - i += 1 - next if l =~ /(\r\n?|\n)/ - if l =~ BB_PAT - @bb = Bb.from_comment( l ) - @atfront = true - i_bb = i - elsif l =~ BB_END - atend = true - i_bb = i - elsif l =~ HRBB_PAT - @hrbb = HRBb.from_hrcomment( l ) - @hr_atfront = true - i_hrbb = i - elsif l =~ HRBB_END - hr_atend = true - i_hrbb = i - elsif @bb or atend - i_end = i - break # stop looking; we expect hrbb next to bb - end # =~ BB_PAT - end # do |l| - if atend or hr_atend - if ps_len > SAFESIZE - File.open( @path, $R ) do |fl| - fl.seek( offset + ps_len - SAFESIZE, IO::SEEK_SET ) - slurp = fl.read( SAFESIZE ) - end - end # else use old slurp - post_lines = slurp.split( /(\r\n?|\n)/ ) - # initialize indices of atend bb comments to more than largest index - j = post_lines.length - j_bb = j - j_hrbb = j - j_end = j - post_length = 0 - post_lines.reverse_each do |l| - post_length += l.length - j -= 1 - next if l =~ /(\r\n?|\n)/ - if l =~ BB_PAT - bb_comment = l - @bb = Bb.from_comment( bb_comment ) - j_bb = j - elsif l =~ HRBB_PAT - bb_comment = l - @hrbb = HRBb.from_hrcomment( bb_comment ) - j_hrbb = j - end - if (@bb or !atend) and (@hrbb or !hr_atend) - j_end = j - break - end - end # do - #post_lines.slice([j_bb,j_hrbb].min .. -1).each do |l| - # post_block = post_block + l - end # if atend - - fail EPBBError, @path unless @bb.valid - # in case of discrepancy, drop @hrbb. - # we accept a `safety margin': a difference >1pt is not a discrepancy. - @hrbb = nil if @hrbb and - (( @bb.llx.to_i > @hrbb.llx.to_f.floor ) or - ( @bb.lly.to_i > @hrbb.lly.to_f.floor ) or - ( @bb.urx.to_i < @hrbb.urx.to_f.ceil ) or - ( @bb.ury.to_i < @hrbb.ury.to_f.ceil )) - - retVal = PsPdf.new( 'ext' => 'eps' ) - - # always rewrite the eps, to get normalized, ruby-compatible bb syntax. - # we modify part of the header, and possibly of the trailer. - - # offset, length of middle part, which can be copied byte for byte - cp_start = offset + pre_length - cp_len = ps_len - pre_length - cp_len -= post_length if ( atend or hr_atend ) - # replace boundingbox comments - if atend - pre_lines[i_bb].sub!( BB_END, @bb.comment ) - post_lines[j_bb].sub!( BB_PAT, '%%' ) - else - pre_lines[i_bb].sub!( BB_PAT, @bb.comment ) - end - if @hrbb - # replace valid hires boundingbox comments - if hr_atend - pre_lines[i_hrbb].sub!( HRBB_END, @hrbb.hrcomment ) - post_lines[j_hrbb].sub!( HRBB_PAT, '%%' ) - else - pre_lines[i_hrbb].sub!( HRBB_PAT, @hrbb.hrcomment ) - end - elsif i_hrbb >= 0 # invalid hires bb - # erase invalid hr boundingbox comments - if atend - pre_lines[i_hrbb].sub!( HRBB_END, '%%' ) - post_lines[j_hrbb].sub!( HRBB_PAT, '%%' ) - else - pre_lines[i_hrbb].sub!( HRBB_PAT, '%%' ) - end - end # test for @hrbb - - File.open( retVal.path, $W ) do |fl| - fl.write( pre_lines[ 0 .. i_end ].join ) - end - sliceFile( @path, retVal.path, cp_len, cp_start, $A ) - if ( atend or hr_atend ) - File.open( retVal.path, $A ) do |fl| - fl.write( post_lines[ j_end .. -1 ].join ) - end - end - retVal.bb = @bb.copy - retVal.hrbb = @hrbb.copy if @hrbb - retVal.atfront = true - retVal.hr_atfront = true - retVal.npages = 1 - return retVal - end # eps_clean - - ################## - - # Use the Ghostscript bbox device to give an eps a tight boundingbox. - # Here, we don't test for use_hires_bb. - # The eps should already have been cleaned up by eps_clean - # and the current boundingbox should not contain negative coordinates, - # otherwise the bbox output device may give incorrect results. - # Maybe we should test whether gs' bbox device can handle - # nonnegative coordinates. - # The boundingbox in the eps is rewritten, but the eps is - # not otherwise converted. - # We don't create a new PsPdf object. - - def fix_bb - - fail EPCallError, buginfo \ - unless @type == 'eps' and @bb.non_negative - # let ghostscript calculate new boundingbox - cmd = $settings.gs_prog + ' ' + PsPdf.gs_options + - " -sDEVICE=bbox \"" + @path + "\" 2>&1" - write_log cmd # if $DEBUG - bb_output = `#{cmd}` - # inspect the result - fail EPSystemError, cmd unless $? == 0 and bb_output - bb_output.split(/\r\n?|\n/).each do |b| - if b =~ BB_PAT - bb_temp = Bb.from_comment( b ) - fail EPBBError, bb_output unless bb_temp.valid - bb_temp.expand unless $settings.use_hires_bb - @bb = bb_temp.copy - elsif b =~ HRBB_PAT - bb_temp = HRBb.from_hrcomment( b ) - @hrbb = bb_temp.valid ? bb_temp.copy : nil - end - end # do |b| - fail EPBBError, bb_output unless @bb - # this won't happen, but we deal with it anyway: - @hrbb = HRBb.new( @bb.llx, @bb.lly, @bb.urx, @bb.ury ) unless @hrbb - - # locate current [hr]boundingbox, which ha[s|ve] to be replaced - # assumptions: both in header, and hrbb no later than - # first line after bb. - slurp = '' - File.open( @path, $R ) do |fl| - slurp = fl.read( [File.size(@path),SAFESIZE].min ) - end - pre_lines = slurp.split( /(\r\n?|\n)/ ) - i_bb = -1 - i_hrbb = -1 - i = -1 - i_end = -1 - pre_length = 0 - pre_lines.each do |l| - pre_length += l.length - i += 1 - next if l =~ /(\r\n?|\n)/ - if l =~ BB_PAT - i_bb = i - elsif l =~ HRBB_PAT - i_hrbb = i - elsif i_bb >= 0 - i_end = i - break # stop looking; we expect hrbb next to bb - end # =~ BB_PAT - end # do |l,i| - fail EPBBError, "No boundingbox found in #{@path}" if i_bb < 0 - - # replace boundingbox[es] by editing initial part pre_block - # and copying the rest byte for byte - if i_hrbb < 0 - # no old hrbb; replace bb with new bb and new hrbb - # pre_lines[i_bb+1] should match /\r\n?|\n/ - pre_lines[i_bb].sub!( BB_PAT, @bb.comment + - pre_lines[i_bb+1] + @hrbb.hrcomment ) - else - pre_lines[i_bb].sub!( BB_PAT, @bb.comment ) - pre_lines[i_hrbb].sub!( HRBB_PAT, @hrbb.hrcomment ) - end - oldpath = @path - @path = mktemp( 'eps' ) - File.open( @path, $W ) { |fl| - fl.write( pre_lines[ 0 .. i_end ].join ) } - sliceFile( oldpath, @path, \ - File.size( oldpath ) - pre_length, pre_length, $A ) - return self - - end # fix_bb - - ################## - - # Convert eps to pdf. - # The eps should already have a boundingbox in the header. - - def eps_to_pdf( params={} ) - - gray = params[ 'gray' ] - - fail EPCallError, buginfo( params ) unless @type == 'eps' and @atfront - - wrp = ( $settings.use_hires_bb and @hrbb ) ? @hrbb.wrapper : @bb.wrapper - - retVal = PsPdf.new( 'ext' => 'pdf' ) - cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + - " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" + - " -sOutputFile=\"#{retVal.path}\"" + - ( gray ? (' "' + $GRAYHEAD + '"') : "" ) + - " \"#{wrp}\" \"#{@path}\" -c grestore" - write_log cmd # if $DEBUG - fail EPSystemError, cmd \ - unless system( cmd ) and test( ?s, retVal.path ) - retVal.npages = 1 - return retVal - - end # eps_to_pdf - - ################## - - # Convert source pdf to eps. - # The option sep_color is ignored if pdftops is not available. - - def pdf_to_eps( params={} ) - - page = params[ 'page' ] ? params[ 'page' ].to_i : 1 - sep = params[ 'sep' ] - - fail EPCallError, buginfo( params ) unless @type == 'pdf' - fail EPCallError, buginfo( params ) \ - unless page > 0 and page <= @npages - retVal = PsPdf.new( 'ext' => 'eps' ) - if $settings.pdftops_prog and $settings.use_pdftops - cmd = "\"#{$settings.pdftops_prog}\"" + - " #{PsPdf.ps_options( sep )}" + - " -paper match -eps -f #{page} -l #{page}" + - " \"#{@path}\" \"#{retVal.path}\"" - else - cmd = "\"#{$settings.gs_prog}\" -sDEVICE=epswrite -r600" + - " #{PsPdf.gs_options}" + - " -dFirstPage=#{page}" + - " -dLastPage=#{page}" + - " -sOutputFile=\"#{retVal.path}\" \"#{@path}\"" - end - write_log cmd # if $DEBUG - fail EPSystemError, cmd unless \ - system( cmd ) and test( ?s, retVal.path ) - -# fix for incorrect DSC header produced by some versions of pdftops: -# if necessary, change line `% Produced by ...' into `%%Produced by ...' -# this is usually the second line. -# otherwise the DSC header would be terminated before the bbox comment -# match first chk_ze chars against `% Produced by' - chk_size = [ 1500, File.size( retVal.path ) ].min - slurp = '' - File.open( retVal.path, $R ) do |fl| - slurp = fl.read( chk_size ) - end # File - pdfpat = /([\r\n])% Produced by/m - if slurp =~ pdfpat - newpath = mktemp( 'eps' ) - write_log "pdftops header fix #{retVal.path} => #{newpath}" - File.open( newpath, $W ) do |fl2| - fl2.write( slurp.sub( pdfpat, '\1%% Produced by)' ) ) - end - sliceFile( retVal.path, newpath, File.size( retVal.path ) - chk_size, - chk_size, $A ) - retVal.path = newpath - end # if =~ -# end fix for incorrect DSC header produced by some versions of pdftops - retVal.atfront = 1 - retVal.find_bb_simple - retVal.npages = 1 - return retVal - - end # pdf_to_eps - - ################## - - def ps_to_pdf( params={} ) - - gray = params[ 'gray' ] - - fail EPCallError, buginfo( params ) \ - unless @type == 'ps' - - retVal = PsPdf.new( 'ext' => 'pdf' ) - cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + - " -sDEVICE=pdfwrite #{PsPdf.pdf_options}" + - " -sOutputFile=\"#{retVal.path}\"" + - ( gray ? (' "' + $GRAYHEAD + '"') : "" ) + " \"#{@path}\"" - write_log cmd # if $DEBUG - fail EPSystemError, cmd \ - unless system( cmd ) and test( ?s, retVal.path ) - - retVal.pdf_pages - return retVal - - end # def ps_to_pdf - - ################## - - def pdf_to_ps( params={} ) - - sep = params[ 'sep' ] - page = params[ 'page' ] ? params[ 'page' ].to_s : nil - - fail EPCallError, buginfo( params ) unless @type == 'pdf' - retVal = PsPdf.new( 'ext' => 'ps' ) - if $settings.pdftops_prog and $settings.use_pdftops - cmd = "\"#{$settings.pdftops_prog}\" #{PsPdf.ps_options( sep )}" + - ( page ? " -f #{page} -l #{page}" : '' ) + - " -paper match \"#{@path}\" \"#{retVal.path}\"" - else - cmd = "\"#{$settings.gs_prog}\" #{PsPdf.gs_options}" + - " -sDEVICE=pswrite -r600" + - ( page ? " -dFirstPage=#{page} -dLastPage#{page}" : '' ) + - " -sOutputFile=\"#{retVal.path}\"" + " \"#{@path}\"" - end - write_log cmd # if $DEBUG - fail EPSystemError, cmd unless \ - system( cmd ) and test( ?s, retVal.path ) - retVal.npages = @npages - return retVal - - end # pdf_to_ps - - ################## - - # all possible conversions, as concatenations of direct conversions. - - def any_to_any( params={} ) - - type = params[ 'type' ] - page = params[ 'page' ] - bbox = params[ 'bbox' ] - gray = params[ 'gray' ] - gRAY = params[ 'gRAY' ] # try harder to grayscale - gray = 1 if gRAY - bbox = nil if type == 'ps' - - fail EPCallError, buginfo( params ) \ - unless ( type=='eps' or type=='pdf' or type=='ps' ) - fail EPCallError, buginfo( params ) \ - if @type=='other' - fail EPCallError, buginfo( params ) \ - if type=='ps' and bbox - fail EPCallError, buginfo( params ) \ - if @type=='eps' and type=='ps' - - # gRAY tries harder to grayscale, by converting color first to cmyk - # even if it requires an additional eps - pdf - eps roundtrip. - # Normally, conversion to cmyk is done only if it doesn't take - # an extra roundtrip. - # The separable color conversion is an option of pdftops. - - pp = self - - pp = pp.eps_clean if pp.type == 'epsPreview' - pp = pp.eps_clean if pp.type == 'eps' and \ - not ( pp.bb and pp.atfront ) # => not yet `cleaned' - #pp.pdf_pages if pp.type == 'pdf' and not pp.npages - # now also done in initialize - - case pp.type - when 'eps' - - # in some cases extra eps => pdf => eps roundtrip: - # roundtrip guarantees bb.non_negative, necessary for fix_bb. - # pdf_to_ps( sep ) improves chances of grayscaling. - - case type - when 'eps' - pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \ - if gRAY or ( bbox and not pp.bb.non_negative ) - pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_eps if gray - pp = pp.fix_bb if bbox - return pp - - when 'pdf' - pp = pp.eps_to_pdf.pdf_to_eps( 'sep' => gray ) \ - if gRAY or ( bbox and not pp.bb.non_negative ) - pp = pp.fix_bb if bbox - return pp.eps_to_pdf( 'gray' => gray ) - - when 'ps' - if gRAY - pp = pp.eps_to_pdf.pdf_to_ps( 'sep' => gray ) - pp = pp.ps_to_pdf( 'gray' => gray ).pdf_to_ps - else - pp = pp.eps_to_pdf( 'gray' => gray ).pdf_to_ps - end - return pp - - end # case type - - when 'pdf' - - case type - when 'eps' - if not gray - pp = pp.pdf_to_eps( 'page' => page ) - pp = pp.fix_bb if bbox - return pp - else - pp = pp.pdf_to_eps( 'page' => page, 'sep' => 1 ) - pp = pp.fix_bb if bbox - pp = pp.eps_to_pdf( 'gray' => 1 ) - return pp.pdf_to_eps - end - - when 'pdf' - return pp unless ( gray or bbox or page ) - if bbox or not $settings.pdftops_prog - pp = pp.pdf_to_eps( 'page' => page, 'sep' => gray ) - pp = pp.fix_bb if bbox - return pp.eps_to_pdf( 'gray' => gray ) - else - pp = pp.pdf_to_ps( 'page' => page, 'sep' => gray ) - return pp.ps_to_pdf( 'gray' => gray ) - end - - when 'ps' - if gray - pp = pp.pdf_to_ps( 'sep' => 1 ) - pp = pp.ps_to_pdf( 'gray' => 1 ) - end - return pp.pdf_to_ps - - end # case type - - when 'ps' - - case type - when 'eps' - if gRAY - pp = pp.ps_to_pdf.pdf_to_eps( 'page' => page, 'sep' => 1 ) - pp = pp.eps_to_pdf( 'gray' => 1 ) - pp = pp.pdf_to_eps - else - pp = pp.ps_to_pdf( 'gray' => gray ) - pp = pp.pdf_to_eps( 'page' => page ) - end - return pp.fix_bb - - when 'pdf' - if bbox - pp = pp.ps_to_pdf.pdf_to_eps( 'sep' => gray, 'page' => page ) - pp = pp.fix_bb - return pp = pp.eps_to_pdf( 'gray' => gray ) - elsif page - pp = pp.ps_to_pdf.pdf_to_ps( 'sep' => gray, 'page' => page ) - return pp = pp.ps_to_pdf( 'gray' => gray ) - else - pp = pp.ps_to_pdf.pdf_to_ps( 'sep' => 1 ) if gRAY - return pp.ps_to_pdf( 'gray' => gray ) - end - - when 'ps' - return pp unless page or gray - if gRAY - pp = pp.ps_to_pdf.pdf_to_ps( 'page' => page, 'sep' => 1 ) - return pp.ps_to_pdf( 'gray' => 1 ).pdf_to_ps - else - pp = pp.ps_to_pdf( 'gray' => gray ) - return pp = pp.pdf_to_ps( 'page' => page ) - end - - end # case type - - end # case pp.type - - #raise EPCallError - fail "Unsupported conversion" - # this shouldn't happen anymore - - end # any_to_any - -end # class PsPdf - -################################# -# main program - -require 'optparse' - -def gui( action ) - case action - when 'config_w' then - puts "pdftops_prog=#{$settings.pdftops_prog}" if ARCH=='w32' - puts "pdf_viewer=#{$settings.pdf_viewer}" if ARCH=='unix' - puts "ps_viewer=#{$settings.ps_viewer}" if ARCH=='unix' - puts "defaultDir=#{$settings.defaultDir}" - puts "ignore_pdftops=#{$settings.ignore_pdftops}" - puts "pdf_target=#{$settings.pdf_target}" - puts "pdf_version=#{$settings.pdf_version}" - puts "pdf_custom=#{$settings.pdf_custom}" - puts "ps_options=#{$settings.ps_options}" - puts "ignore_hires_bb=#{$settings.ignore_hires_bb}" - puts "bb_spread=#{$settings.bb_spread}" - exit - when 'config_r' then - while line = gets - (varname, val) = line.split('=', 2) - varname.strip! if varname - val.strip! if val - if $settings.has_key?( varname ) - val = nil if val == '' - $settings[varname].val = val - # puts( "\"#{val}\" assigned to #{varname}" ) - end - end # while - $settings.write_settings - exit - when nil then - $from_gui = true - else - abort( "Action should be omitted or 'config_w' or 'config_r'" ) - end # case -end - -# create a pause to examine temp files -def abortt( msg ) - if $DEBUG - $stderr.puts msg + "\nPress <enter> to finish" - $stdin.gets - end - fail -end - -save_settings = false - -opts = OptionParser.new do |opts| - # for help output - opts.banner = "Convert between [e]ps and pdf formats" - opts.separator "Usage: epspdf.rb [options] infile [outfile]" - opts.separator "" - opts.separator "Default for outfile is file.pdf" + - " if infile is file.eps or file.ps" - opts.separator "Default for outfile is file.eps" + - " if infile is file.pdf" - opts.separator "" - - opts.on( "-g", "--gray", "--grey", - "Convert to grayscale;", - "success not guaranteed" ) do |opt| - $options.gray = true - end - - opts.on( "-G", "--GRAY", "--GREY", - "Try harder to convert to grayscale" ) do |opt| - $options.gRAY = true - end - - opts.on( "-p PAGENUMBER", "--pagenumber=PAGENUMBER", - "Page to be converted or selected", Integer ) do |opt| - $options.page = opt - end - - opts.on( "-b", "--bbox", "--BoundingBox", - "Compute tight boundingbox" ) do |opt| - $options.bbox = true - end - - opts.on( "-n", "--no-hires", - "Don't use hires boundingbox" ) do |opt| - $settings.ignore_hires_bb = '1' - end - - opts.on( "-r", "--hires", - "Use hires boundingbox" ) do |opt| - $settings.ignore_hires_bb = '0' - end - - opts.on( "-T TARGET", "--target=TARGET", - PDF_TARGETS, - "Target use of pdf; one of", - "#{PDF_TARGETS.join( ', ' )}" ) do |opt| - $settings.pdf_target = opt - end - - opts.on( "-N PDFVERSION", "--pdfversion=PDFVERSION", - PDF_VERSIONS, - "Pdf version to be generated" ) do |opt| - $settings.pdf_version = opt - end - - opts.on( "-V PDFVERSION", "--version=PDFVERSION", - PDF_VERSIONS, - "Deprecated; use `-N' or `--pdfversion'." ) do |opt| - if opt == "" - puts EPVERSION - exit - end - $settings.pdf_version = opt - end - - opts.on( "-I", - "Ignore pdftops even if available", - "(default: use if available)" ) do |opt| - $settings.ignore_pdftops = '1' - end - - opts.on( "-U", - "Use pdftops if available", - "(overrides previous -I setting)" ) do |opt| - $settings.ignore_pdftops = '0' - end - - opts.on( "-C CUSTOMOPTIONS", "--custom=CUSTOMOPTIONS", - "Custom options for conversion to pdf,", - "view Use.htm and ps2pdf.htm from", - "the Ghostscript documentation set" ) do |opt| - $settings.pdf_custom = opt - end - - opts.on( "-P PSOPTIONS", "--psoptions=PSOPTIONS", - "Options for pdftops; default -level3,", - "don't include -eps or page number options;", - "these will be generated by the program" ) do |opt| - $settings.ps_options = opt - end - - opts.on( "-i", "--info", - "Info: display detected filetype" ) do |opt| - $options.info = true - end - - opts.on( "-s", - "Save (some) settings" ) do |opt| - save_settings = true - end - - opts.on( "-d", "Debug: don't remove temp files" ) do |opt| - $DEBUG = 1 - end - - opts.on( "--gui[=ACTION]", "Do not use; reserved for GUI" ) do |opt| - gui( opt ) - end - - opts.separator "" - - opts.on( "-v", "Prints version info" ) do |opt| - puts EPVERSION - exit - end - - opts.on_tail("-h", "--help", "Show this message") do - puts "Epspdf version " + EPVERSION - puts "Copyright (C) " + COPYRIGHT + " Siep Kroonenberg" - puts opts - exit - end -end # opts - -# hack alert! we support `--version' for version info although -# --version is still interpreted as desired pdf output version - -if ARGV.length == 1 and ARGV[0] == '--version' - puts EPVERSION - exit -end - -# save original command-line for later reporting -cmdline = "#{$0} #{ARGV.join( sep=' ' )}" - -# parse options destructively -begin - opts.parse!( ARGV ) -rescue OptionParser::ParseError => e - STDERR.puts e.message, "\n", opts - exit( -1 ) -end - -# log cmdline AFTER we found out whether we run from gui -write_log( cmdline ) - -$options.page = 1 if $options.bbox and not $options.page - -$settings.write_settings if save_settings - -if ARGV.length < 1 - if not save_settings # help output - puts opts - abort - else - exit - end -elsif $options.info - p = PsPdf.new( 'file' => ARGV[0] ) - puts( p.file_info ) - exit -elsif ARGV.length > 1 and - File.expand_path( ARGV[0] ) == File.expand_path( ARGV[1] ) - abort " Input and output files should be different." -else - infile = ARGV[0] - abort( infile + " not found or empty" ) unless test( ?s, infile ) -end - -# done with options - -######################################## - -source = PsPdf.new( 'file' => infile ) - -# We aren't finicky about the extension of the input file, -# but still want to check whether pdf is pdf. - -case source.type -when 'eps', 'epsPreview', 'ps' - abort "Wrong extension; input is not in pdf format" \ - if infile =~ /\.pdf$/i -when 'pdf' - abort "Wrong extension; input is in pdf format" \ - if infile !~ /\.pdf$/i -else - abort "Invalid input file type; not [e]ps or pdf" \ - if source.type == 'other' -end # case source.type - -# find or construct output file name -if ARGV.length > 1 - outfile = ARGV[1] -else - case source.type - when 'eps', 'epsPreview', 'ps' - outfile = infile.sub( /\.[^.]+$/, '.pdf' ) - when 'pdf' - outfile = infile.sub( /\.[^.]+$/, '.eps' ) - end # case -end # ifthenelse ARGV.length -outfile = File.expand_path( outfile ) - -$options.type = case outfile -when /\.pdf$/i - "pdf" -when /\.ps$/i - "ps" -when /\.eps$/i - "eps" -else - nil -end # case outfile - -abort "Unknown or unsupported output file extension" \ - unless $options.type -#abort "Output format not supported without xpdf utilities" \ -# if outfile == 'ps' and not $settings.pstopdf_prog - -pp = PsPdf.new( 'file' => infile ) - -begin # inner rescue block - pp = pp.any_to_any( $options ) - write_log( pp.file_info ) if $from_gui - ccp( pp.path, outfile ) -rescue EPCallError => exc - mess = - "Wrong method call or conversion not supported or wrong page number" + - $/ + exc.message + $/ + exc.backtrace.join( $/ ) - write_log( mess ) - puts mess - exit 1 -rescue EPBBError => exc - mess = "Boundingbox problem" + $/ + - exc.message + $/ + exc.backtrace.join( $/ ) - write_log( mess ) - puts mess - exit 1 -rescue EPCopyError => exc - mess = "Copying problem" + $/ + - exc.message + $/ + exc.backtrace.join( $/ ) - write_log( mess ) - puts mess - exit 1 -rescue EPSystemError => exc - mess = "Problem with system call" + $/ + - exc.message + $/ + exc.backtrace.join( $/ ) - write_log( mess ) - puts mess - exit 1 -rescue StandardError => exc - mess = exc.message + $/ + exc.backtrace.join( $/ ) - write_log( mess ) - puts mess - exit 1 -end # rescue block - -cleantemp unless $DEBUG == 1 -__END__ |