diff options
author | Siep Kroonenberg <siepo@cybercomm.nl> | 2008-07-04 10:17:06 +0000 |
---|---|---|
committer | Siep Kroonenberg <siepo@cybercomm.nl> | 2008-07-04 10:17:06 +0000 |
commit | 4a697a53a3c9c2795d549c7d5eb7a6c88c7911a9 (patch) | |
tree | b2f5967ce6865b615213754aa2aa3d41574502bc /Master/texmf-dist/scripts | |
parent | cdbc5d3bd4d89338a033d47d1368e8e30d273544 (diff) |
Added: epspdf conversion utility
git-svn-id: svn://tug.org/texlive/trunk@9222 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts')
-rwxr-xr-x | Master/texmf-dist/scripts/epspdf/epspdf | 3 | ||||
-rw-r--r-- | Master/texmf-dist/scripts/epspdf/epspdf.help | 155 | ||||
-rwxr-xr-x | Master/texmf-dist/scripts/epspdf/epspdf.rb | 1123 | ||||
-rw-r--r-- | Master/texmf-dist/scripts/epspdf/epspdfboot.rb | 40 | ||||
-rwxr-xr-x | Master/texmf-dist/scripts/epspdf/epspdfrc.rb | 507 | ||||
-rwxr-xr-x | Master/texmf-dist/scripts/epspdf/epspdftk | 7 | ||||
-rwxr-xr-x | Master/texmf-dist/scripts/epspdf/epspdftk.rb | 958 | ||||
-rw-r--r-- | Master/texmf-dist/scripts/epspdf/makegray.pro | 29 |
8 files changed, 2822 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/epspdf/epspdf b/Master/texmf-dist/scripts/epspdf/epspdf new file mode 100755 index 00000000000..4492608f398 --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/epspdf @@ -0,0 +1,3 @@ +#!/bin/sh +script=`kpsewhich -format=texmfscripts epspdf.rb` +"$script" $* diff --git a/Master/texmf-dist/scripts/epspdf/epspdf.help b/Master/texmf-dist/scripts/epspdf/epspdf.help new file mode 100644 index 00000000000..90d9c795a0d --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/epspdf.help @@ -0,0 +1,155 @@ + Epspdf and epspdftk User Guide + + A multiplatform GUI- and command-line converter for [e]ps and pdf. + + Siep Kroonenberg, + siepo at cybercomm dot nl + +Overview + + Epspdftk converts files between eps, pdf and general PostScript. The main + screen lets you open a file, select some options and convert the file. + + When opening a file, epspdf tries to find out file type and, in the case + of a pdf file, the number of pages. This information is displayed in the + box in the upper half of the screen. + +Viewing + + The View button at the bottom of the screen calls an appropriate external + viewer. For Windows this is determined by the default Open command, for OS + X it is always Preview, and under Linux it is configurable; see below + under [1]The Configuration screen. + + The View button is grayed if epspdf thinks that there is no previewer for + the current file. + +Conversion options + + Grayscaling: Simple color figures can be converted to grayscale. + Grayscaling will fail for bitmapped data and for advanced color models. + 'Try harder to grayscale' will handle a few more cases, but may take + longer and still won't handle bitmaps. + + Call for assistance: If a PostScript color expert is reading this: please + have a look at makegray.pro and see whether you can come up with something + that handles more color models. + + Compute tight boundingbox: This option is only available if a single page + is converted. + + Page selection: The only possibilities are selecting a single page or + selecting all pages. When converting to eps you are restricted to a single + page. + + For general PostScript files, there is no quick way to determine the + number of pages, so the program won't check beforehand whether you picked + an existing page. If you picked a non-existent page, the program will + abort. If you don't like that, convert the entire document to pdf first - + which will be done behind the scenes anyhow. + + Specifying options such as grayscaling or page selection may require + several roundtrips between PostScript or eps and pdf. If epspdf cannot use + pdftops then some combinations of options will not be available. + +Converting + + The Convert and save... button calls up a file save dialog, with + appropriate default extension. After a successful conversion, the result + becomes the new current file, so you can judge the result by pressing the + View button again. + +The configuration screen + + Epspdftk also has a configuration screen. The settings here are preserved + between sessions. Pay particular attention to 'Intended use' under PDF + CONVERSION. Choose 'prepress' for pdfs which are intended for commercial + printing. This option will try to embed all fonts, among other things. + More and more often, printshops insist on this. With luck, Ghostscript + fonts and system fonts will be used for fonts which were not originally + embedded. + + Under Windows, this screen also lets you tell the program where to find + pdftops.exe. Under Linux, it lets you pick PostScript- and pdf previewers. + +Adding standard fonts to an existing pdf + + You can add standard fonts such as Times and Helvetica to an existing pdf: + first, convert to ps, with 'Convert all pages' selected. Then, make sure + that in the configuration screen 'Intended use' is set to prepress, then + convert back to pdf. + + If this option is set to screen, then standard fonts will be removed + instead of added. This option may also affect the resolution of bitmaps. + +Eps preview headers + + Preview headers are quietly stripped from eps files. These preview headers + are used by e.g. desktop-publishing software to represent eps files on + screen without having to interpret the PostScript code itself. Epspdf + currently has no option to preserve or add them. + +Exporting PostScript from Windows programs + + Often, the only way to get EPS or PostScript from a Windows program is by + 'printing' to a PostScript file. From some programs, you can print a + selection. A suitable driver which comes with Windows is Generic / MS + Publisher Imagesetter. Pay attention to the printer properties: avoid the + 'Optimize for speed' setting and choose Outline for font downloading. + These options can be found under the Advanced button. + + Other possibilities are the TpX and wmf2eps programs, which both have + options to write clipboard contents to an emf file. Wmf2eps uses a printer + driver in the background. For faithful conversion, pick wmf2eps; for + subsequent editing, choose TpX. Both programs are availbable from [2]CTAN. + +Command-line use + + Epspdf.rb, the library for epspdftk.rb, does double duty as command-line + version of epspdftk.rb. Type epspdf.rb -h for help. It shares + configuration settings with epspdftk. + +Troubleshooting + + Grayscaling fails + + See above, under [3]Grayscaling. Epspdf is limited by what can be done by + the backend tools, i.e. Ghostscript and pdftops. + + Part of the graphic gets cut off + + I have seen this happen with eps files generated by printing landscape + PowerPoint slides to PostScript with the Encapsulated PostScript output + option. Using the Archive output option instead solved the problem. In + general, experiment a bit in generating your PostScript. + + Resources for troubleshooting + + Logfile. Epspdf and epspdftk maintain a log file epspdf.log in your home + directory or, in case of Windows, your profile directory. This profile + directory may be c:\Documents and Settings\<your user name>. Otherwise, + open a DOS box and type 'echo %USERPROFILE%', which will produce this + information. + + This logfile lists all Ghostscript- and pdftops program calls plus error + information. 'EPCallError' is probably a program error. 'EPSystemError' is + more likely caused by a faulty file, but may be a program error as well. + + Temporary files. The temporary files may give clues as well. They will be + deleted at the end of the session (for the command-line version, give a -d + parameter to keep the temporary files). Check the logfile which temporary + files have been created. + + [4]Main page... + + -------------------------------------------------------------------------- + + Last revised: Noverber 20, 2006 + +References + + Visible links + 1. http://tex.aanhet.net/epspdf/epspdf_UG.html#configure + 2. http://www.tug.org/ctan.html + 3. http://tex.aanhet.net/epspdf/epspdf_UG.html#gray + 4. http://tex.aanhet.net/epspdf/index.html 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__ diff --git a/Master/texmf-dist/scripts/epspdf/epspdfboot.rb b/Master/texmf-dist/scripts/epspdf/epspdfboot.rb new file mode 100644 index 00000000000..bdcebb77b26 --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/epspdfboot.rb @@ -0,0 +1,40 @@ +# Manipulate library path and maybe search path. + +# Problem to be solved: + +# We don't want to require Windows users to install Ruby and Tcl/Tk so +# we offer a package with a built-in Ruby subset. This subset is +# slightly modified from the subset created by rubyscript2exe +# (http://www.erikveen.dds.nl/rubyscript2exe/). + +# The rubyscript2exe directory structure is simplified compared to the +# original Ruby directory structure, so we need to modify the library +# search path and also the general search path. + +# Assumptions: + +# - The script itself is at the root or in a toplevel subdirectory. +# - If an rbconfig.rb is found at the root or in +# a toplevel directory then there is a Ruby subset. +# If that is the Ruby which is used +# then it needs help to find its files. + +dir = File.expand_path( File.dirname( __FILE__ ) ) +parent = File.dirname( dir ) +parent_sl = parent.dup + ( parent =~ /[\\\/]$/ ? '' : '/' ) +dir_sl = dir.dup + ( parent =~ /[\\\/]$/ ? '' : '/' ) +spath = ENV['PATH'].dup + +if test( ?e, parent_sl + 'lib/rbconfig.rb' ) + root_sl = parent_sl +elsif test( ?e, dir_sl + 'lib/rbconfig.rb' ) + root_sl = dir_sl +else + root_sl = nil +end + +if root_sl + $: << "#{root_sl}lib" +end + +$:.unshift( dir ) diff --git a/Master/texmf-dist/scripts/epspdf/epspdfrc.rb b/Master/texmf-dist/scripts/epspdf/epspdfrc.rb new file mode 100755 index 00000000000..f75d65b5339 --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/epspdfrc.rb @@ -0,0 +1,507 @@ +# epspdf conversion utility, configuration module + +##### +# Copyright (C) 2006, 2008 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! +##### + +# This version has been adapted for TeX Live. The adaptations only +# concern Windows. If TL is set then pdftops and +# ghostscript are set to the version included in TeXLive, settings are +# stored in HKCU\software\epspdftl (rather than epspdf) and the logfile +# is %USERPROFILE%\epspdftl.log, rather than epspdf.log. +# A future version should offer a dynamic way of settingt his option. + +# Settings are stored in a settings hash, with some methods added +# for less unwieldy notation. +# +# In phase 1, some programs (converters, viewers) are autodetected. +# In phase 2, user preferences are read from the epspdf configuration. +# +# For unix/osx, configuration info is stored in $HOME/.epspdfrc. +# For w32, configuration info is stored in HKCU\software\epspdf. +# +# The UIs for settings are not here; they are: +# 1. the epspdf.rb command-line if epspdf.rb is run as +# a stand-alone program, and +# 2. a configuration screen in epspdftk.rb. + +require 'rbconfig' +include Config + +# for notational convenience, we keep ARCH separate + +ARCH = case CONFIG['arch'] +when /win32|mingw/ + 'w32' +when /darwin/ + 'osx' +else + 'unix' +end + +TL = ENV.has_key?('TLROOT') +if ARCH == 'w32' + require 'dl/win32' + require 'win32/registry' + include Win32 # this lets us abbreviate Win32::Registry to Registry + ShortPName = Win32API.new( + 'kernel32', 'GetShortPathName', ['P','P','N'], 'N' ) + TLROOT = ENV['TLROOT'] +end + +# for Windows, use short names within backquotes +# to avoid quotes within backquotes + +def short_name( fn ) + return fn if ARCH != 'w32' + fn.gsub!( /\\/, '/' ) + buffer = ' ' * 260 + length = ShortPName.call( fn, buffer, buffer.size ) + return ( length > 0 ) ? buffer.slice(0..length-1) : fn +end + +# system-dependent location of logfile + +LOGFILE = case ARCH +when 'w32' + TL ? "#{ENV['USERPROFILE']}\\epspdftl.log" : + "#{ENV['USERPROFILE']}\\epspdf.log" +else + "#{ENV['HOME']}//epspdf.log" +end +LOGFILE_OLD = LOGFILE + '.old' + +# system-dependent locations of saved settings + +RC_FILE = "#{ENV['HOME']}/.epspdfrc" +REG_K = TL ? 'epspdftl' : 'epspdf' +REG_KEY = TL ? 'SOFTWARE\\epspdftl' : 'SOFTWARE\\epspdf' + +# hash of saved settings from rc file or registry + +$rc_settings = {} + +# find open command for extension (w32 only) + +def open_with( ext ) + ext = '' unless ext + answer = nil + if ARCH == 'w32' + begin # open cl = HKCR\ext safely + Registry::HKEY_CLASSES_ROOT.open( + (ext =~ /^\./ ? ext : '.'+ext) ) do |cl| + begin # read ftype safely + ftype = cl.read_s( nil ) + begin # open cmd = HKCR\ftype\shell... safely + Registry::HKEY_CLASSES_ROOT.open( + ftype + '\\shell\\open\\command' ) do |cmd| + begin # read answer safely + answer = cmd.read_s( nil ) + if answer =~ /^"([^"]+)".*/ + answer = $1 + elsif answer =~ /^([^ ]+) .*/ + answer = $1 + end + answer = nil unless answer and test( ?s, answer ) + rescue # answer not read + end # read answer safely + end # HKCR\ftype\shell... do |cmd| + rescue # cmd not opened + end # open cmd = HKCR\ftype\shell... safely + rescue # ftype not read + end # read ftype safely + end # HKCR\ext do |cl| + rescue # cl not opened + end # open cl = HKCR\ext safely + end # w32 + answer +end + +# test whether a string is a valid program call +# 'which' also works with explicit paths. +# 'which' returns an error for non-executable files. + +def is_a_program( s ) + case ARCH + when 'w32' + nil + else + ( system "which #{s} >/dev/null" ) ? 1 : nil + end +end # is_a_program + +# Parse version info from a prog from the xpdf suite. +# This is used as validity test. + +def xpdf_version( arg ) + retval = nil + arg = short_name( arg ) if ARCH == 'w32' + version_output = `#{arg} -v 2>&1` + if version_output and not version_output.empty? + vo = version_output.split(/\r\n?|\n/) + vo.grep(/version/i) { |a| + a =~ /^\D*(\d+\.\w*)\b/ + retval = $& # matched part; nil if no match + } + end + retval +end + +class Setting + attr_reader :val, :type, :comment + attr_writer :val + + def initialize( v, t, c ) + @val = v + @type = t + @comment = c + end +end + +PDF_TARGETS = [ 'default', 'printer', 'prepress', 'screen', 'ebook' ] +PDF_VERSIONS = [ '1.2', '1.3', '1.4', 'default' ] + +$settings = { + # converters + 'gs_prog' => Setting.new( nil, 'auto', nil ), + 'gs_version' => Setting.new( nil, 'auto', nil ), + 'pdftops_prog' => Setting.new( nil, ARCH=='w32' ? 'config' : 'auto', nil ), + # viewers + 'pdf_viewers' => Setting.new( nil, ARCH=='unix' ? 'auto' : nil, nil ), + 'ps_viewers' => Setting.new( nil, ARCH=='unix' ? 'auto' : nil, nil ), + 'pdf_viewer' => Setting.new( nil, ARCH=='unix' ? 'config' : 'auto', nil ), + 'ps_viewer' => Setting.new( nil, ARCH=='unix' ? 'config' : 'auto', nil ), + # conversion options + 'ignore_pdftops' => Setting.new( '0', 'config', + 'Ignore pdftops even if available; 1=yes, empty or 0=no(default)' ), + 'pdf_target' => Setting.new( 'prepress', 'config', + 'Target: screen, ebook, print, prepress (default) or default' ), + 'pdf_version' => Setting.new( '1.4', 'config', + 'Pdf version: e.g 1.4 or 1.2 (fewer features, more compatibility)' + + ' or unspecified' ), + 'pdf_custom' => Setting.new( '', 'config', + 'additional options for [e]ps to pdf conversion' ), + 'ps_options' => Setting.new( '-level2', 'config', + 'options for pdftops; default -level2' ), + 'bb_spread' => Setting.new( '1', 'config', + 'margin to be added to computed boundingbox; default 1' ) +} + +class << $settings + + # create shortcut methods $settings.x and $settings.x= + # for reading and writing hash elements. + + $settings.each_key { |k| + eval "def #{k} ; self[\'#{k}\'].val ; end" + eval "def #{k}=(v) ; self[\'#{k}\'].val=v ; end" + } + + # use_pdftops is a boolean counterpart and inverse + # of the stored string attribute ignore_pdftops + + def use_pdftops + case self['ignore_pdftops'].val + when /t(rue)?|y(es)?|1/i + false + when /f(alse)?|n(o)?|0/i + true + when 1 + false + when 0 + true + else + true + end + end + + # we want use_pdftops = nil = default = true but + # we also want use_pdftops = nil <=> use_pdftops = false + # we give the second one priority. + + def use_pdftops=( x ) + self['ignore_pdftops'].val = case x + when /t(rue)?|y(es)?|1/i + '0' + when /f(alse)?|n(o)?|0/i + '1' + when 1 + '0' + when 0 + '1' + when false + '1' + when nil # try to avoid this assignment + '1' + when true + '0' + else + '1' + end + end + + def accept_pdf_viewer( s ) + if ARCH == 'unix' + if is_a_program( s ) + self.pdf_viewer = s + self.pdf_viewers.unshift( s ) unless self.pdf_viewers.index( s ) + return 1 + else + return nil + end + else + return nil + end + end # accept_pdf_viewer + + def accept_ps_viewer( s ) + if ARCH == 'unix' + if is_a_program( s ) + self.ps_viewer = s + self.pdf_viewer = s unless \ + self.pdf_viewer and not self.pdf_viewer.empty? + self.ps_viewers.unshift( s ) unless self.ps_viewers.index( s ) + self.pdf_viewers.push( s ) unless self.pdf_viewers.index( s ) + return 1 + else + return nil + end + else + return nil + end + end # accept_ps_viewer + + # previously-configured settings into hash $rc_settings + # here no validity testing. + # we accept empty settings. + + def read_settings + if ARCH == 'w32' + [Registry::HKEY_LOCAL_MACHINE, + Registry::HKEY_CURRENT_USER].each { |hk| + begin # open REG_KEY safely + hk.open( REG_KEY ) do |ep| + ep.each_value do |key, type, val| + $rc_settings[ key ] = val \ + if $settings[ key ] and $settings[ key ].type == 'config' + end # do |key, type, val| + end # do |ep| + rescue + end # open REG_KEY safely + } # each |hk| + else + if test( ?s, RC_FILE ) + lines = File.read( RC_FILE ).split( /\r\n?|\n/ ) + lines.each do |l| + l = l.sub( /#.*$/, '' ) # remove trailing comments + if l =~ /^\s*(\S+)\s*=\s*(\S(.*\S)?)\s*$/ + key, val = $1, $2 + $rc_settings[ key ] = val \ + if $settings[ key ] and $settings[ key ].type == 'config' + elsif l =~ /^\s*(\S+)\s*=\s*$/ + key, val = $1, '' + $rc_settings[ key ] = '' \ + if $settings[ key ] and $settings[ key ].type == 'config' + end # if l =~ + end # do |l| + end # if ?s + end # if ARCH + end # def + + def write_settings + if ARCH == 'w32' + begin + Registry::HKEY_CURRENT_USER.create( + 'SOFTWARE', desired = Registry::KEY_ALL_ACCESS) do |reg| + reg.create( REG_K, desired = Registry::KEY_ALL_ACCESS ) do |rk| + $settings.each do |k, s| + key = String.new( k ) + st = Setting.new( s.val, s.type, s.comment ) + # this copying looks kind of silly, + # but prevents a 'modifying frozen object' error. + if st.type == 'config' + vl = st.val ? st.val : '' + rk.write_s( key, vl ) + end + end # do each + end # do |rk| + end # do |reg| + rescue => er # no real error handling for now + puts $! + puts er.backtrace.join( $/ ) + puts 'Settings will be lost at next run' + end + else + File.open( RC_FILE, 'w' ) do |f| + f.write( "# This file will be overwritten by epspdf_tk[.rb]" \ + + $/ ) + $settings.each do |key, st| + if st.type == 'config' + f.write( $/ ) + f.write( '# ' + st.comment + $/ ) if st.comment + # STDERR.puts "write setting for " + key + # STDERR.flush + f.write( key + " = " + ( st.val ? st.val : '' ) + $/ ) + end + end # do |key, st| + end # do |f| + end # if + end # def + + def get_settings + + # phase 1: autodetect + # w32: gs_prog, gs_version, pdf_viewer, ps_viewer + # gs: search registry settings; the highest version wins. + # viewers: check file type registry entry for valid open commands + # w32/TL: gs_prog, gs_version, pdf_viewer, ps_viewer + # gs: from TL + # viewers: check file type registry entry for valid open commands + + # unix: gs_prog, gs_version, pdftops_prog, pdf_viewers, ps_viewers + # osx: gs_prog, gs_version, pdftops_prog + # viewers: use Preview; don't check anything + # unix/osx are done together, except that for osx + # the viewers code is skipped. + + case ARCH + when 'w32' + if !TL then # else use built-in texlive versions + # check registry for latest valid version of Ghostscript + # tentative values; HKLM and HKCU may provide different values + try_gs_version = nil + try_gs_prog = nil + [Registry::HKEY_LOCAL_MACHINE, + Registry::HKEY_CURRENT_USER].each { |hk| + puts hk.to_s if $DEBUG + hk.open('SOFTWARE') { |sof| + puts sof.to_s if $DEBUG + sof.each_key { |key, wtime| + puts key.to_s if $DEBUG + if key =~ /ghostscript/i + gh = hk.open('SOFTWARE\\' + key) + gh.each_key { |skey, wtime| + if skey =~ /^\d+\.\d+$/ # version number + if skey.to_f > try_gs_version.to_f + this_gs = hk.open('SOFTWARE\\' + key + '\\' + skey) + this_gs_prog = this_gs['GS_DLL'].sub( + /([\\\/])([^\\\/]+)$/, '\1gswin32c.exe') + if test(?e,this_gs_prog) + try_gs_version = skey + try_gs_prog = this_gs_prog + end # if ?e (exist) + end # skey.to_f > try_gs_version.to_f + end # if skey =~ /^\d+\.\d+$/ + } # gh.each_key + end # if key =~ /ghostscript/i + } # sof.each_key + } # hk.open |sof| + } # [HKCU, HKLM].each + self.gs_version = try_gs_version + self.gs_prog = try_gs_prog + end + + # viewers + self.pdf_viewer = open_with( 'pdf' ) + self.ps_viewer = open_with( 'ps' ) + + when /unix|osx/ + # gs + answer = `gs --version` + if answer and not answer.empty? + self.gs_prog = 'gs' + self.gs_version = answer.chomp + else + self.gs_prog = nil + self.gs_version = nil + end + + # pdftops + self.pdftops_prog = xpdf_version( 'pdftops' ) ? 'pdftops' : nil + + # viewers + vw = %w{ gpdf xpdf acroread evince ggv gv kghostview } + vw.delete_if { |x| not is_a_program( x ) } + vw = nil if vw.empty? + self.pdf_viewers = vw + self.pdf_viewer = vw ? vw[0] : nil + if vw + vw2 = vw.dup # copy the elements rather than the array ref + [ 'gpdf', 'xpdf', 'acroread' ].each { |pg| + vw2.delete( pg ) if vw2.index( pg ) + } + vw2 = nil if vw2.empty? + self.ps_viewers = vw2 + self.ps_viewer = vw2 ? vw2[ 0 ] : nil + end # if vw + end # case + + # built-in defaults already set during initialization of $settings + + # phase 2: pre-existing configuration + # w32: pdftops_prog + # unix (not osx): pdf_viewers + + read_settings + + case ARCH + when 'w32' + $rc_settings[ 'pdftops_prog' ] = ( + TL ? (TLROOT.gsub( /\//, '\\' ) + '\\bin\\win32\\pdftops.exe') : + "z:\\aps\\staff\\ber\\miktex\\xpdf\\pdftops.exe" ) unless + ( $rc_settings[ 'pdftops_prog' ] and + ! $rc_settings[ 'pdftops_prog' ].empty? ) + ptop = $rc_settings[ 'pdftops_prog' ] + if xpdf_version( ptop ) then + self.pdftops_prog = ptop + end + if TL + self.gs_prog = TLROOT.gsub( /\//, '\\' ) + + '\\tlpkg\\tlgs\\bin\\gswin32c.exe' + self.gs_version = + ( `#{short_name( self.gs_prog )} --version` ).chomp + end + when 'unix' + if v = $rc_settings[ 'pdf_viewer' ] and is_a_program( v ) + self.pdf_viewer = v + self.pdf_viewers.unshift( v ) unless self.pdf_viewers.index( v ) + end # if + if v = $rc_settings[ 'ps_viewer' ] and is_a_program( v ) + self.ps_viewer = v + self.ps_viewers.unshift( v ) unless self.ps_viewers.index( v ) + end # if + if self.ps_viewers and not self.pdf_viewers + self.pdf_viewers = self.ps_viewers.dup + self.pdf_viewer = self.ps_viewer + end # if + end # case ARCH + + # no validity checks for pdf- and ps output options. + # reminder: self.s shortcut for self['s'].val + [ 'pdf_target', 'pdf_version', 'pdf_custom', 'ps_options' ].each { |p| + self[ p ].val = $rc_settings[ p ] if $rc_settings[ p ] + } + if $rc_settings.has_key?( 'ignore_pdftops' ) + $settings.ignore_pdftops = case $rc_settings[ 'ignore_pdftops' ] + when /1|yes|true|y|t/i + '1' + when /0|no|false|n|f/i + '0' + end + else + $settings.ignore_pdftops = '0' + end + + self.bb_spread = $rc_settings[ 'bb_spread' ] if + $rc_settings.has_key?( 'bb_spread' ) and + $rc_settings[ 'bb_spread' ] =~ /^[+-]?[\d]+$/ + + end # get_settings + +end # class << $settings + +$settings.get_settings diff --git a/Master/texmf-dist/scripts/epspdf/epspdftk b/Master/texmf-dist/scripts/epspdf/epspdftk new file mode 100755 index 00000000000..6d3465a8c4b --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/epspdftk @@ -0,0 +1,7 @@ +#!/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/Master/texmf-dist/scripts/epspdf/epspdftk.rb b/Master/texmf-dist/scripts/epspdf/epspdftk.rb new file mode 100755 index 00000000000..c09509c0c8f --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/epspdftk.rb @@ -0,0 +1,958 @@ +#!/usr/bin/env ruby + +# epspdf conversion utility, GUI frontend + +##### +# 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! +##### + +#$DEBUG=1 + +# epspdfboot.rb: modify loadpath in a way which works both +# with a full Ruby and with a custom subset ruby + +require __FILE__.sub( /epspdftk\.rbw?/, 'epspdfboot.rb' ) + +########################################## + +# load conversion engine. epspdf also loads epspdfrc, +# which detects platform and available converters. + +require 'epspdf' + +$VERBOSE = nil + +#require 'pp' + +if $DEBUG + puts ARCH + puts Config::CONFIG['arch'] + puts "Ghostscript command #{$settings.gs_prog}" \ + " and version #{$settings.gs_version}" + puts "Pdftops command #{$settings.pdftops_prog}" \ + " and version #{$settings.pdftops_version}" +end + +def viewable( p ) + return nil unless p && p.path + return nil if p.type == 'other' + case ARCH + when 'osx' + return true + when 'w32' + return nil if p.type == 'epsPreview' + return open_with( File.extname( p.path ) ) + else + return nil if p.type == 'epsPreview' + return true if p.type =~ /e?ps/ and ( not $settings.ps_viewer.empty? ) + return true if p.type == 'pdf' and ( not $settings.pdf_viewer.empty? ) + end # case ARCH + return nil +end # viewable + +#################################### + +# Tk and custom widgets + +require 'tk' +require 'tkscrollbox' + +# For event field strings, which codify event details, see +# the bind man page from the tk man pages (Ubuntu: tk8.x-doc package) + +#################################### + +# combobox adapted from Takaaki Tateishi <ttate / jaist.ac.jp> +# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/18167 + +# literal content of an xbm file, created e.g. with the GIMP + +$DOWN_ARROW_IMG = <<STR +#define dwnarrow.icn_width 15 +#define dwnarrow.icn_height 10 +static unsigned char dwnarrow.icn_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, 0x03, + 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 }; +STR + +class TkACombobox < TkFrame + + include TkComposite + + attr_reader :scrollbox, :tframe + attr_accessor :entries, :is_valid + + def initialize_composite( params = {} ) + + @entries = [] + @is_valid = proc{ |s| true } + @entry = nil + + # entry widget plus arrow for drop-down listbox + + @entry = TkEntry.new( @frame, + 'width' => 30, 'bd' => 1, 'highlightthickness' => 0 ) + image = TkBitmapImage.new( 'data' => $DOWN_ARROW_IMG ) + @bt_arrow = TkButton.new( @frame, 'bd' => 1, 'image' => image, + 'highlightthickness' => 0, 'command' => proc{ cmd_scrollbox } ) + @entry.pack( 'side' => 'left', 'fill' => 'x', 'expand' => 'yes' ) + @bt_arrow.pack( 'side' => 'right' ) + + # drop-down listbox + + @tframe = TkToplevel.new( @frame ) + @tframe.overrideredirect( 1 ) # no window manager decorations + @tframe.transient + @tframe.withdraw + @scrollbox = TkScrollbox.new( @tframe, 'width' => 30, 'border' => 1 ) + @scrollbox.pack( 'fill' => 'both', 'expand' => 'yes' ) + @scrollbox.bind( 'Any-Leave', proc{ hide_scrollbox } ) + # "%y", "%P": event field string; see bind manual from tk8.x-docs + @scrollbox.bind( 'Double-1', proc{ |y| select_this( y ) }, "%y" ) + @scrollbox.bind( 'Return', proc{ |y| select_this( y ) }, "%y" ) + + @entry.bind( 'FocusOut', proc{ entry_proc } ) + @entry.bind( 'Return', proc{ entry_proc } ) + @entry.bind( 'Double-1', proc{ entry_proc } ) + + delegate( :width, @entry, @scrollbox ) + + configure params if params + + end # initialize_composite + + def cmd_scrollbox + case @tframe.state + when "withdrawn" + show_scrollbox( @entries ) + else + hide_scrollbox + end + end + + def show_scrollbox( entries = nil ) + width = TkWinfo.width( @frame ) + height = 5 * TkWinfo.height( @entry ) + rootx = TkWinfo.rootx( @frame ) + rooty = TkWinfo.rooty( @frame ).to_i + TkWinfo.height( @frame ).to_i + geometry = "#{width}x#{height}+#{rootx}+#{rooty}" + @tframe.geometry( geometry ) + @tframe.deiconify + entries.each{ |entry| @scrollbox.insert( "end", entry ) } if entries + end + + def hide_scrollbox + @tframe.withdraw + @scrollbox.delete( 0, "end" ) + @entry.focus + end + + def select_this( y ) + i = @scrollbox.nearest( y ) + s = @scrollbox.get( i ) + @entry.delete( 0, 'end' ) + @entry.insert( 0, s ) + @is_valid.call( s ) # activates side effects + hide_scrollbox + end + + def entry_proc + #puts @entry.value + if @is_valid.call( @entry.value ) + @entries.unshift( @entry.value ).sort! \ + unless @entries.index( @entry.value ) + @tframe.withdraw unless @tframe.state == 'withdrawn' + else + Tk.bell + @entry.focus( true ) + end + end + + def get_entry + @entry.value + end + + def set_entry( s ) + @entry.delete( 0, 'end' ) + @entry.insert( 'end', s ) if s + end + +end # TkACombobox + +#################################### + +# checkboxes and radioboxes with built-in variables. +# use string- rather than symbol keys when creating these widgets. + +def zero_one( v ) # For Tcl/Tk, everything is a string + case v + when /t(rue)?|y(es)?|1/i + '1' + when /f(alse)?|n(o)?|0/i + '0' + when 1 + '1' + when 0 + '0' + else + nil + end +end + +class TkACheck < TkFrame + + include TkComposite + + def initialize_composite( params = {} ) + + Kernel::fail StandardError, "TkACheck doesn\'t allow \'variable\' key" \ + if params.has_key?( 'variable' ) + + v = params.delete( 'value' ) + @value = TkVariable.new( zero_one( v ) ) + + @ck = TkCheckButton.new( @frame, :variable => @value \ + ).pack( :padx => 0, :pady => 0, :ipadx => 0, :ipady => 0 ) + + delegate( 'DEFAULT', @ck ) + + configure params unless params.empty? + + end # initialize_composite + + def value + @value.value + end + + def value=( v ) + @value.value = zero_one( v ) + end + +end # TkACheck + +class TkRadios < TkFrame + + include TkComposite + + def initialize_composite( params = {} ) + + Kernel::fail StandardError, "TkRadios doesn\'t allow \'variable\' key" \ + if params.has_key?( 'variable' ) + + @value = TkVariable.new( params.delete( 'value' ) ) + @values = params.delete( 'values' ) + @packing = params.delete( 'packing' ) + @buttons = [] + + if ( @values.kind_of? Hash ) + @values.each_key { |k| + @buttons << TkRadioButton.new( @frame, + :text => @values[k] || k, + :variable => @value, + :value => k + ).pack( @packing ) + } + elsif ( @values.kind_of? Array ) + @values.each { |k| + @buttons << TkRadioButton.new( @frame, + :text => k, + :variable => @value, + :value => k + ).pack( @packing ) + } + else + Kernel::fail StandardError, "TkRadios requires a values array or hash" + end + + delegate( 'DEFAULT', @frame ) + delegate( 'command', *@buttons ) + delegate( 'state', *@buttons ) + + configure( params ) unless params.empty? + + end # initialize_composite + + def value + @value.value + end + + def value=( v ) + @value.value = v + end + + def get_button_state( v ) + @buttons.each { |b| + if b.value == v + return b.cget( 'state' ) + end + } + return nil + end + + def set_button_state( v, s ) + @buttons.each { |b| + if b.value == v + b.configure( 'state' => s ) + @value.value = nil if s == 'disabled' and @value.value == v + end + } + end + +end # TkRadios + +$root = TkRoot.new { title "PostScript- and pdf conversions" } + +################################## + +# Initial tests: no gs => bail out; no viewers => warning + +if not $settings.gs_prog + Tk.messageBox( + :icon=>'error', + :type=>'ok', + :parent=>$root, + :message=>"No Ghostscript found; aborting..." + ) + abort +end + +if not $settings.pdf_viewer + Tk.messageBox( :icon=>'warning', :type=>'ok', :message => + "No PostScript- or pdf viewer found" ) +elsif not $settings.ps_viewer + Tk.messageBox( :icon=>'warning', :type=>'ok', :message => + "No PostScript viewer found" ) +end + +######################################## + +$defaultDir = ENV['PWD'] + +$defaultDir = ARGV[0] if ((ARGV.length > 0) and File.directory?(ARGV[0])) + +$source = PsPdf.new # currently loaded PsPdf object + +$tl_config = nil # configuration screen +$tl_wait = nil # instead of a wait cursor or progress bar + +# widgets which are configured on the fly, +# e.g. by ep_config, get a global name. +# They don't really have to be declared early. + +# buttons and option widgets +$view_button = nil +$page_radio = nil +$page_entry = nil +$type_radio = nil +$bbox_check = nil + +# widgets which display file info +$source_dir_label = nil +$source_name_label = nil +$source_type_label = nil + +def 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_button.configure( :state => + ( viewable( $source ) ? 'normal' : 'disabled' ) ) + $save_button.configure( :state => + ( ( $source.type and $source.type != 'other' ) ? \ + 'normal' : 'disabled' ) ) + + $type_radio.set_button_state( 'ps', ( \ + ( $settings.pdftops_prog and $settings.use_pdftops and + ( $source.type =~ /ps|pdf/ ) ) ? \ + 'normal' : 'disabled' ) ) + + $page_radio.set_button_state( 'all', 'normal' ) + $page_radio.set_button_state( 'single', 'normal' ) + $page_radio.set_button_state( 'all', 'disabled' ) if \ + $type_radio.value == 'eps' + $page_radio.set_button_state( 'single', 'disabled' ) if \ + $type_radio.value == 'ps' + + $page_entry.configure( :state => \ + ( $page_radio.value == 'all' ? 'disabled' : 'normal' ) ) + + $bbox_check.configure( :state => \ + ( $page_radio.value == 'single' or $source.type =~ /eps/ ) \ + ? 'normal' : 'disabled' ) + +end # set_widget_states + +def load_file( path ) + $source = PsPdf.new( 'file' => path ) + # write info to labels + $source_dir_label.configure( :text => File.dirname( path ) ) + $source_name_label.configure( :text => File.basename( path ) ) + $source_type_label.configure( :text => $source.type ) + $source.pdf_pages if ( $source.type == 'pdf' ) + $source_npages_label.configure( :text => ( + case $source.type + when 'pdf' then ( $source.npages ).to_s + when 'eps' then '1' + else '' + end + ) ) + $page_entry.delete( 0, 'end' ) + $page_entry.insert( 'end', '1' ) + set_widget_states +end + +# ipadx and ipady seem ineffective for frames + +FRM_PACK = { + :side => 'top', + :fill => 'x', + :expand => 1, + :padx => 4, + :pady => 4, + :anchor => 'w' +} + +################################## + +# configuration screen (toplevel widget) + +$tl_config = nil + +def ep_config + + if not $tl_config + + $tl_config = TkToplevel.new( $root, :title => 'Configure epspdf' ) + + ### viewers frame + + if ARCH == 'unix' + TkFrame.new( $tl_config ) { |frm| + frm.borderwidth 2 + frm.relief 'groove' + TkLabel.new( frm, :text => 'VIEWERS' + ).pack( :side => 'top', :padx => 4, :anchor => 'w' ) + TkFrame.new( frm ) { |fm| + TkLabel.new( fm, :text => 'Pdf viewer' ).grid( + :in => fm, :row => 0, :column => 0, + :sticky => 'w', :padx => 2 ) + $pdf_vw_combo = TkACombobox.new( fm, :width => 30 ) + $pdf_vw_combo.is_valid = lambda { |s| is_a_program( s ) } + $pdf_vw_combo.grid( :in => fm, :row => 0, :column => 1, + :pady => 2, :padx => 2, :sticky => 'e' ) + + TkLabel.new( fm, :text => 'PostScript viewer' ).grid( + :in => fm, :row => 1, :column => 0, + :sticky => 'w', :padx => 2 ) + $ps_vw_combo = TkACombobox.new( fm, :width => 30 ) + $ps_vw_combo.is_valid = lambda { |s| is_a_program( s ) } + $ps_vw_combo.grid( :in => fm, :row => 1, :column => 1, + :pady => 2, :padx => 2, :sticky => 'e' ) + }.pack( :side => 'top', :anchor => 'w', :fill => 'x' ) + }.pack( FRM_PACK ) + end # if unix + + ### postscript frame + + TkFrame.new( $tl_config ){ |frm| + frm.borderwidth 2 + frm.relief 'groove' + TkLabel.new( frm, :text => 'POSTSCRIPT CONVERSION' + ).pack( :side => 'top', :padx => 4, :anchor => 'w' ) + + if ARCH == 'w32' # others: assume pdftops on search path + TkLabel.new( frm, :text => "Find pdftops" + ).pack( :side => 'top', :anchor => 'w', :padx => 4 ) + + TkFrame.new( frm ) { |fm| + $pdftops_entry = TkEntry.new( fm, :width => 20 + ).pack( 'side'=>'left', :padx => 4 ) + TkButton.new( fm, + :text => 'Browse ...', + :command => lambda { + tryprog = Tk.getOpenFile( + :filetypes => [ [ 'Programs', [ '.exe' ] ] ], + :parent => fm + ) + if tryprog and ( not tryprog.empty? ) + if not xpdf_version( tryprog ) + Tk.messageBox( + :icon => 'warning', + :type => 'ok', + :parent => fm, + :message => "pdftops missing or dysfunctional" + ) + else + $pdftops_entry.delete( 0, 'end' ) + $pdftops_entry.insert( 'end', tryprog ) + end # ifthenelse xpdf_version + end # ifthenelse tryprog + } # command lambda + ).pack( :side => 'left' ) # TkButton + }.pack( :side => 'top', :pady => 4, :anchor => 'w' ) + end # ARCH == 'w32' + + $pdftops_check = TkACheck.new( frm, + 'text' => "Use pdftops if available" + ).pack( :side => 'top', :pady => 4, :anchor => 'w' ) + + }.pack( FRM_PACK ) # TkFrame |frm| + + ### pdf frame + + TkFrame.new( $tl_config ){ |frm| + frm.borderwidth 2 + frm.relief 'groove' + TkLabel.new( frm, :text => 'PDF CONVERSION' + ).pack( :side => 'top', :padx => 4, :anchor => 'w' ) + + # internal packing + pack_pdf = { + :side => 'left', :padx => 2, :pady => 4, :anchor => 'w' } + + TkLabel.new( frm, :text => 'Intended use' + ).pack( :side => 'top', :padx => 2, :pady => 0, :anchor => 'w' ) + $pdf_target_radio = TkRadios.new( frm, + 'values' => PDF_TARGETS, 'packing' => pack_pdf + ).pack( :side => 'top', :anchor => 'w' ) + + # dummy widget for extra vertical space + TkLabel.new( frm, :text => ' ' ).pack + + TkLabel.new( frm, :text => 'Pdf version' + ).pack( :side => 'top', :padx => 2, :pady => 0, :anchor => 'w' ) + + $pdf_version_radio = TkRadios.new( frm, + 'values' => PDF_VERSIONS, 'packing' => pack_pdf + ).pack( :side => 'top', :anchor => 'w' ) + + TkLabel.new( frm, :text => ' ' ).pack + + TkLabel.new( frm, :text => 'Custom Ghostscript/ps2pdf parameters' + ).pack( :side => 'top', :padx => 2, :pady => 0, :anchor => 'w' ) + + $pdf_custom_entry = TkEntry.new( frm, :width => 50 + ).pack( :side => 'top', :padx => 2, :pady => 2, :anchor => 'w' ) + + }.pack( FRM_PACK ) + + ### button frame + + TkFrame.new( $tl_config ) { |frm| + + TkButton.new( frm, + :text => 'Cancel', + :command => lambda { + $tl_config.withdraw + $root.focus + } + ).pack( :padx => 4, :side => 'left', :anchor => 'e' ) + + TkButton.new( frm, + :text => 'Done', + :command => lambda { + # copy settings from widgets to $settings + case ARCH + when 'unix' # viewers + $settings.accept_ps_viewer( $ps_vw_combo.get_entry ) + $settings.accept_pdf_viewer( $pdf_vw_combo.get_entry ) + when 'w32' + $settings.pdftops_prog = $pdftops_entry.get + end + $settings.use_pdftops = ( not $pdftops_check.value == '0' ) + $settings.pdf_target = $pdf_target_radio.value + $settings.pdf_version = $pdf_version_radio.value + $settings.pdf_custom = $pdf_custom_entry.get + $settings.write_settings + $tl_config.withdraw + $root.focus + set_widget_states + } # lambda + ).pack( :side => 'left', :anchor => 'e' ) + + }.pack( :side => 'bottom', :padx => 4, :pady => 4, :anchor => 'e' ) + else + $tl_config.deiconify + end # ifthenelse $tl_config + + # copy data from $settings to widgets + if ARCH == 'unix' + $pdf_vw_combo.set_entry( $settings.pdf_viewer ) + $pdf_vw_combo.entries = $settings.pdf_viewers[0..-1] # deep copy + $ps_vw_combo.set_entry( $settings.ps_viewer ) + $ps_vw_combo.entries = $settings.ps_viewers[0..-1] + elsif ARCH == 'w32' + $pdftops_entry.delete( 0, 'end' ) + $pdftops_entry.insert( 'end', $settings.pdftops_prog ) \ + if $settings.pdftops_prog and not $settings.pdftops_prog.empty? + end # ifthenelse ARCH + $pdftops_check.value = $settings.use_pdftops ? '1' : '0' + $pdf_target_radio.value = $settings.pdf_target + $pdf_version_radio.value = $settings.pdf_version + $pdf_custom_entry.delete( 0, 'end' ) + $pdf_custom_entry.insert( 'end', $settings.pdf_custom ) \ + if not $settings.pdf_custom.empty? + +end # def ep_config + +################################### + +### makeshift help + +$tl_help = nil + +def ep_help + if not $tl_help + $tl_help = TkToplevel.new( $root, :title => 'Epspdftk help' ) + TkFrame.new( $tl_help ) { |tf| + txt = TkText.new( tf, + 'width' => 80, + 'height' => 25 + ) { |t| + TkScrollbar.new( tf, + 'command' => lambda{ |*args| t.yview( *args ) } ) { |sc| + t.yscrollcommand( lambda{ |first, last| sc.set first, last } ) + }.pack( 'side' => 'right', 'fill' => 'y' ) + }.pack( 'expand' => 'yes', 'fill' => 'both' ) + + txt.delete( '1.0', 'end' ) + + # LF ok for Win32 and Mac? Otherwise, split and join with $/ + + File.open( File.dirname( __FILE__ ) + '/epspdf.help', 'r' ) { |f| + $s = f.read + } + + txt.insert( '1.0', $s ) + + }.pack( 'side' => 'top', 'fill' => 'x' ) + + TkButton.new( $tl_help, + :text => 'Cancel', + :command => lambda{ + $tl_help.withdraw + $root.focus + } + ).pack( 'anchor' => 'e', 'padx' => 4, 'pady' => 4 ) + else + $tl_help.deiconify + end # ifthenelse $tl_help + +end # def ep_help + +### some buttons + +TkFrame.new( $root ) { |frm| + + TkButton.new( frm, + :text => "Configure", + :command => lambda { ep_config } + ).pack( :side => 'left', :padx => 4, :pady => 4 ) + + TkButton.new( frm, + :text => 'Help', + :command => lambda { ep_help } + ).pack( :side => 'right', :padx => 4, :pady => 4) + +}.pack( :side => 'top', :fill => 'x' ) # frm + +######################################## + +### status info + +TkFrame.new( $root ){ |frm| + frm.borderwidth 2 + frm.relief 'groove' + + ### status info + + TkFrame.new( frm ) { |fm| + TkLabel.new( fm, + :justify => 'left', + :text => 'Directory' + ).grid( :in => fm, :sticky => 'w', :row => 0, :column => 0 ) + $source_dir_label = TkLabel.new( fm, + :justify => 'left' + ).grid( :in => fm, :sticky => 'w', :row => 0, :column => 1 ) + + TkLabel.new( fm, + :justify => 'left', + :text => 'File' + ).grid( :in => fm, :sticky => 'w', :row => 1, :column => 0 ) + $source_name_label = TkLabel.new( frm, + :justify => 'left' + ).grid( :in => fm, :sticky => 'w', :row => 1, :column => 1 ) + + TkLabel.new( fm, + :justify => 'left', + :text => 'Type' + ).grid( :in => fm, :sticky => 'w', :row => 2, :column => 0 ) + $source_type_label = TkLabel.new( fm, + :justify => 'left' + ).grid( :in => fm, :sticky => 'w', :row => 2, :column => 1 ) + + TkLabel.new( fm, + :justify => 'left', + :text => 'Pages' + ).grid( :in => fm, :sticky => 'w', :row => 3, :column => 0 ) + $source_npages_label = TkLabel.new( fm, + :justify => 'left' + ).grid( :in => fm, :sticky => 'w', :row => 3, :column => 1 ) + + TkGrid.columnconfigure( fm, 0, :weight => 1, :minsize => 60, :pad =>4 ) + TkGrid.columnconfigure( fm, 1, :weight => 3, :minsize => 60, :pad =>4 ) + }.pack( :side => 'top', :anchor => 'w', :padx => 4 ) + +}.pack( FRM_PACK ) + +######################################## + +### conversion options: see also $options, defined in epspdf.rb + +TkFrame.new( $root ) { |frm| + + TkFrame.new( frm ) { |fm| # grayscaling, output format + TkFrame.new( fm ) { |f| # grayscaling + TkLabel.new( f, 'text' => 'Grayscaling' + ).pack( :side => 'top', :anchor => 'w' ) + + gray_options = { + 'gRAY' => 'Try harder to grayscale', + 'gray' => 'Try to grayscale', + 'color' => 'No color conversion' + } + + $gray_radio = TkRadios.new( f, + 'values' => gray_options, + 'value' => 'color', + 'packing' => { 'side' => 'top', 'anchor' => 'w' } + ).pack( :side => 'bottom', :anchor => 'w' ) # $gray_radio + }.pack( :side => 'left', :padx => 4 ) # f: grayscaling + + TkFrame.new( fm ) { |f| # output format + TkLabel.new( f, 'text' => 'Output format' + ).pack( :side => 'top', :anchor => 'w' ) + + $type_radio = TkRadios.new( f, + 'values' => [ 'pdf', 'eps', 'ps' ], + 'value' => 'pdf', + 'command' => lambda { + case 'value' + when 'eps' + $page_radio.value = 'single' + when 'ps' + $page_radio.value = 'all' + $bbox_check.value = '0' + end + set_widget_states + }, + 'packing' => { 'side' => 'top', 'anchor' => 'w' } + ).pack( :side => 'bottom', :anchor => 'w' ) + }.pack( :side => 'right', :padx => 4 ) # f: output format + }.pack( :side => 'top', :anchor => 'n', :fill => 'x' ) + + # dummy widget for extra vertical space + TkLabel.new( frm, :text => ' ' ).pack + + $bbox_check = TkACheck.new( frm, + 'text' => 'Compute tight boundingbox', + 'value' => '0', + 'command' => lambda { + $page_radio.value = 'single' if 'value' == '1' + set_widget_states + } + ).pack( :side => 'top', :padx => 4, :anchor => 'w' ) + + TkLabel.new( frm, :text => ' ' ).pack + + TkFrame.new( frm ) { |fm| + page_options = { 'all' => 'Convert all pages', 'single' => 'Page:' } + $page_radio = TkRadios.new( fm, + 'values' => page_options, + 'packing' => { 'side' => 'left', 'padx' => 4 }, + 'value' => 'single', + 'command' => lambda { + $bbox_check.value = '0' if 'value' == 'all' + set_widget_states + } + ).pack( 'side' => 'left', :anchor => 'nw' ) + + $page_entry = TkEntry.new( fm, + :validate => 'focus', + :vcmd => [ lambda { |s| + ( s =~ /\d+/ ) and + ( s.to_i >= 1 ) and + ( $source.type != 'pdf' or s.to_i <= $source.npages ) + }, "%P" ], + :invcmd => lambda { Tk.bell; $page_entry.focus }, + :width => 5 + ).pack( :side => 'left' ) + $page_entry.delete( 0, 'end' ) + $page_entry.insert( 'end', '1' ) + }.pack( 'side' => 'left', :anchor => 'nw' ) +}.pack( FRM_PACK ) + +######################################## + +### view | open | save | done buttons + +TkFrame.new( $root ) { |frm| + + $view_button = TkButton.new( frm, + :text => 'View', + :state => 'disabled', + :command => lambda { + # start a viewer + if ARCH == 'w32' + shell = Win32API.new("shell32", "ShellExecute", + ['l','p','p','p','p','l'], 'l') + shell.Call(0, 'open', $source.path, 0, 0, 1) + # shell.Call(0, 'open', $source.path, 0, 0, SW_SHOWNORMAL) + elsif ARCH == 'osx' + fork { exec "open -a Preview \"#{$source.path}\"" } + elsif viewable( $source ) + viewer = + $source.type == 'pdf' ? $settings.pdf_viewer : $settings.ps_viewer + fork { exec "\"#{viewer}\" \"#{$source.path}\"" } + end + } + ).pack( :side=>'left', :anchor => 'w', :padx=>4, :pady=>4 ) + + TkFrame.new( frm ) { |fm| + TkButton.new( fm, + :text => 'Open..', + :command => lambda { + path = Tk.getOpenFile( + :initialdir => $defaultDir, + :filetypes => [ + ['All files', '*'], + ['EPS', ['.eps']], + ['PDF', ['.pdf']], + ['PostScript', ['.ps']] + ], + :parent => $root + ) + if path and ( not path.empty? ) + load_file( path ) + $defaultDir = File.expand_path( File.dirname( path ) ) + end + } + ).pack( :side=>'left', :padx=>4, :pady=>4 ) + + $save_button = TkButton.new( fm, + :text => "Convert and save..", + :command => lambda { + $options.type = $type_radio.value + $options.page = \ + ( ( $page_radio.value == 'single' ) ? $page_entry.value : nil ) + $options.gray = ($gray_radio.value and $gray_radio.value != 'color' ) + $options.gRAY = ($gray_radio.value and $gray_radio.value == 'gRAY' ) + $options.bbox = ( ( $bbox_check.value == '1' ) \ + and ( $page_radio.value == 'single' ) ) + filetypes = case $options.type + when 'pdf' then [ ['Pdf', ['.pdf'] ] ] + when 'ps' then [ ['PostScript', ['.ps'] ] ] + when 'eps' then [ ['EPS (Encapsulated PostScript', ['.eps'] ] ] + end + path = nil + path = Tk.getSaveFile( + :filetypes => filetypes, + :parent => $root, + :initialdir => File.dirname( $source.path ), + :initialfile => File.basename( $source.path, ".*" ) + + '.' + $options.type, + :defaultextension => '.' + $options.type + ) + return unless path && ! path.empty? + $defaultDir = File.expand_path( File.dirname( path ) ) + + # waitbox + if not $tl_wait + $tl_wait = TkToplevel.new( $root, + 'title' => 'Converting...', + 'geometry' => + '+' + ( TkWinfo.rootx( $root ).to_i + 100 ).to_s + + '+' + ( TkWinfo.rooty( $root ).to_i + 100 ).to_s, + 'borderwidth' => 2, + 'relief' => 'solid' + ) + $cnv_label = TkLabel.new( $tl_wait, + 'text' => 'converting...' + ).pack( 'side' => 'top' ) + $tl_wait.overrideredirect( 1 ) + end + $cnv_label.configure( + 'text' => "Converting #{$source.path} to \n #{path} ..." + ) + $tl_wait.update + $tl_wait.deiconify + $tl_wait.raise + begin + result = $source.any_to_any( $options ) + rescue EPCallError => exc + mess = "Wrong method call or conversion not supported" + + " or wrong page number" + write_log( mess + $/ + + exc.message + $/ + exc.backtrace.join( $/ ) ) + Tk.messageBox( + 'icon' => 'error', + 'type' => 'ok', + 'parent' => $root, + 'message' => "#{mess}\nSee #{LOGFILE} for details." + ) + rescue EPBBError => exc + mess = "Boundingbox problem" + $/ + + exc.message + write_log( mess + $/ + exc.backtrace.join( $/ ) ) + Tk.messageBox( + 'icon' => 'error', + 'type' => 'ok', + 'parent' => $root, + 'message' => "#{mess}\nSee #{LOGFILE} for details." + ) + rescue EPSystemError => exc + mess = "Problem with system call" + $/ + + exc.message + write_log( mess + $/ + exc.backtrace.join( $/ ) ) + Tk.messageBox( + 'icon' => 'error', + 'type' => 'ok', + 'parent' => $root, + 'message' => "#{mess}\nSee #{LOGFILE} for details." + ) + ensure + $tl_wait.withdraw + end + if result + ccp( result.path, path ) + load_file( path ) + Tk.messageBox( + 'icon' => 'info', + 'type' => 'ok', + 'parent' => $root, + 'message' => "Conversion completed" + ) + end + } + ).pack( :side=>'left', :padx=>0, :pady=>4 ) + + TkButton.new( fm, + :text => "Done", + :command => lambda { $root.destroy } + ).pack( :side => 'left', :padx => 4, :pady => 4 ) + }.pack( :side => 'right', :anchor => 'e' ) +}.pack( :side => 'bottom', :fill => 'x' ) + +set_widget_states + +$root.raise + +Tk.mainloop +cleantemp diff --git a/Master/texmf-dist/scripts/epspdf/makegray.pro b/Master/texmf-dist/scripts/epspdf/makegray.pro new file mode 100644 index 00000000000..67dc5705839 --- /dev/null +++ b/Master/texmf-dist/scripts/epspdf/makegray.pro @@ -0,0 +1,29 @@ +%! +% makegray.pro, PostScript header file for grayscale conversion. + +%%%%% +% Siep Kroonenberg, n dot s dot kroonenberg at rug dot nl +% Public domain. +%%%%% + +% This version only handles some basic color models, +% and doesn't handle image data at all. +% A more robust version would be warmly welcomed. + +%%%%<< /ProcessColorModel /DeviceGray >> setpagedevice +userdict begin +% .11*B + .59*G + .3*R +/setrgbcolor { + 0.11 mul exch 0.59 mul add exch 0.3 mul add + systemdict begin setgray end +} def +% 1.0 - min(1.0, .3*C + .59*M + .11*Y + B) +/setcmykcolor { + exch 0.11 mul add + exch 0.59 mul add + exch 0.3 mul add + dup 1 gt {pop 1} if + 1 exch sub + systemdict begin setgray end +} def +end |