# 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 if ARCH == 'unix' 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 # if unix 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