home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / bin / foomatic-rip < prev    next >
Encoding:
Text File  |  2007-03-27  |  205.7 KB  |  6,587 lines

  1. #!/usr/bin/perl
  2. # The above Perl path may vary on your system; fix it!!! -*- perl -*-
  3.  
  4. use strict;
  5. use POSIX;
  6. use Cwd;
  7.  
  8. my $ripversion='$Revision$';
  9. #'# Fix emacs syntax highlighting
  10.  
  11. # foomatic-rip is a spooler-independent filter script which takes
  12. # PostScript as standard input and generates the printer's page
  13. # description language (PDL)/raster format as standard output. This
  14. # kind of filter is usually called Raster Image Processor (RIP),
  15. # therefore the name "foomatic-rip".
  16.  
  17. # Save it in one of the directories of your $PATH, so that it gets
  18. # found when called from the command line (for spooler-less printing),
  19. # link it to spooler-specific directories when you use CUPS or PPR:
  20.  
  21. #    ln -s /usr/bin/foomatic-rip /usr/lib/cups/filter/
  22. #    ln -s /usr/bin/foomatic-rip /usr/lib/ppr/lib/
  23. #    ln -s /usr/bin/foomatic-rip /usr/lib/ppr/interfaces/
  24.  
  25. # Mark this filter world-readable and world-executable (note that most
  26. # spoolers run the print filters as a special user, as "lp", not as
  27. # "root" or as the user who sent the job).
  28.  
  29. # See http://www.openprinting.org/cups-doc.html
  30. #     http://www.openprinting.org/lpd-doc.html
  31. #     http://www.openprinting.org/ppr-doc.html
  32. #     http://www.openprinting.org/pdq-doc.html
  33. #     http://www.openprinting.org/direct-doc.html
  34. #     http://www.openprinting.org/ppd-doc.html
  35.  
  36. # ==========================================================================
  37. #
  38. #    User-configurable settings, edit them if needed
  39. #
  40. # ==========================================================================
  41.  
  42. # What path to use for filter programs and such.  Your printer driver
  43. # must be in the path, as must be the renderer, $enscriptcommand, and
  44. # possibly other stuff.     The default path is often fine on Linux, but
  45. # may not be on other systems.
  46. #
  47. my $execpath = "/usr/bin:/usr/local/bin:/usr/bin:/bin";
  48.  
  49. # CUPS raster drivers are searched here
  50. my $cupsfilterpath = "/usr/lib/cups/filter:/usr/local/lib/cups/filter:/usr/local/libexec/cups/filter:/opt/cups/filter:/usr/lib/cups/filter";
  51.  
  52. # Location of the configuration file "filter.conf", this file can be
  53. # used to change the settings of foomatic-rip without editing
  54. # foomatic-rip. itself. This variable must contain the full pathname 
  55. # of the directory which contains the configuration file, usually
  56. # "/etc/foomatic".
  57. # Some versions of configure do not fully expand $sysconfdir
  58. my $prefix = "/usr";
  59. my $configpath = "/etc/foomatic";
  60.  
  61. # For the stuff below, the settings in the configuration file have priority.
  62.  
  63. # Set to 1 to insert postscript code for page accounting (CUPS only).
  64. my $ps_accounting = 1;
  65. my $accounting_prolog = "";
  66.  
  67. # Enter here your personal command for converting non-postscript files
  68. # (especially text) to PostScript. If you leave it blank, at first the
  69. # line "textfilter: ..." from /etc/foomatic/filter.conf is read and
  70. # then the commands given on the list below are tried, beginning with
  71. # the first one.
  72. # You can set this to "a2ps", "enscript" or "mpage" to select one of the 
  73. # default command strings.
  74. my $fileconverter = '';
  75.  
  76. my($kid0,$kid1,$kid2,$kid3,$kid4);
  77. my($kidfailed,$kid3finished,$kid4finished);
  78. my($convkidfailed,$dockidfailed,$kid0finished,$kid1finished,$kid2finished);
  79. my($fileconverterpid,$rendererpid,$fileconverterhandle,$rendererhandle);
  80. my($jobhasjcl);
  81.  
  82. # What 'echo' program to use.  It needs -e and -n.  Linux's builtin
  83. # and regular echo work fine; non-GNU platforms may need to install
  84. # gnu echo and put gecho here or something.
  85. #
  86. my $myecho = 'echo';
  87.  
  88. # Which shell to use for executing shell commands.  Some of the PPD files
  89. # specify a FoomaticRIPCommandLine that makes use of constructs not available
  90. # from a vanilla Bourne shell.  On systems where /bin/sh is a vanilla Bourne
  91. # we need to use a more "modern" shell to execute the command.  This will
  92. # be set via a 'preferred_shell: (shell)' setting in the foomatic.conf file
  93. # or automatically detected at runtime later on in this program.
  94. #
  95. my $modern_shell = '';
  96.  
  97. # Set debug to 1 to enable the debug logfile for this filter; it will
  98. # appear as defined by $logfile. It will contain status from this
  99. # filter, plus the renderer's stderr output. You can also add a line
  100. # "debug: 1" to your /etc/foomatic/filter.conf to get all your
  101. # Foomatic filters into debug mode.
  102. #
  103. # WARNING: This logfile is a security hole; do not use in production.
  104. my $debug = 0;
  105.  
  106. # This is the location of the debug logfile (and also the copy of the
  107. # processed PostScript data) in case you have enabled debugging above.
  108. # The logfile will get the extension ".log", the PostScript data ".ps".
  109. my $logfile = "/tmp/foomatic-rip";
  110.  
  111. # End interesting enduser options
  112.  
  113. # ==========================================================================
  114. #
  115. # foomatic-rip spooler-independent PS->Printer filter (RIP) of Foomatic
  116. #
  117. # Copyright 2002 - 2007 Grant Taylor <gtaylor@picante.com>
  118. #         & Till Kamppeter <till.kamppeter@gmail.com>
  119. #                & Helge Blischke <h.blischke@srz.de>
  120. #
  121. #  This program is free software; you can redistribute it and/or modify it
  122. #  under the terms of the GNU General Public License as published by the
  123. #  Free Software Foundation; either version 2 of the License, or (at your
  124. #  option) any later version.
  125. #
  126. #  This program is distributed in the hope that it will be useful, but
  127. #  WITHOUT ANY WARRANTY; without even the implied warranty of
  128. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
  129. #  Public License for more details.
  130. #
  131. #  You should have received a copy of the GNU General Public License
  132. #  along with this program; if not, write to the Free Software
  133. #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
  134. #  USA.
  135. #
  136.  
  137. my $added_lf = "\n";
  138.  
  139. # Flush everything immediately.
  140. $|=1;
  141.  
  142.  
  143.  
  144. ## Constants used by this filter
  145.  
  146. # Error codes, as some spooles behave different depending on the reason why
  147. # the RIP failed, we return an error code. As I have only found a table of
  148. # error codes for the PPR spooler. If our spooler is really PPR, these
  149. # definitions get overwritten by the ones of the PPR version currently in
  150. # use.
  151.  
  152. my $EXIT_PRINTED = 0;         # file was printed normally
  153. my $EXIT_PRNERR = 1;          # printer error occured
  154. my $EXIT_PRNERR_NORETRY = 2;  # printer error with no hope of retry
  155. my $EXIT_JOBERR = 3;          # job is defective
  156. my $EXIT_SIGNAL = 4;          # terminated after catching signal
  157. my $EXIT_ENGAGED = 5;         # printer is otherwise engaged (connection 
  158.                            # refused)
  159. my $EXIT_STARVED = 6;         # starved for system resources
  160. my $EXIT_PRNERR_NORETRY_ACCESS_DENIED = 7;     # bad password? bad port
  161.                                             # permissions?
  162. my $EXIT_PRNERR_NOT_RESPONDING = 8;            # just doesn't answer at all 
  163.                                             # (turned off?)
  164. my $EXIT_PRNERR_NORETRY_BAD_SETTINGS = 9;      # interface settings are invalid
  165. my $EXIT_PRNERR_NO_SUCH_ADDRESS = 10;          # address lookup failed, may be 
  166.                                             # transient
  167. my $EXIT_PRNERR_NORETRY_NO_SUCH_ADDRESS = 11;  # address lookup failed, not 
  168.                                             # transient
  169. my $EXIT_INCAPABLE = 50;                       # printer wants (lacks) features
  170.                                             # or resources
  171. # Standard Unix signal names
  172. #my SIGHUP = 1;
  173. #my SIGINT = 2;
  174. #my SIGQUIT = 3;
  175. #my SIGKILL = 9;
  176. #my SIGTERM = 15;
  177. #my SIGUSR1 = 10;
  178. #my SIGUSR2 = 12;
  179. #my SIGTTIN = 21;
  180. #my SIGTTOU = 22;
  181.  
  182. my $ESPIPE = 29;    # the errno value when seeking a pipe or socket
  183.  
  184.  
  185.  
  186. ## Some important variables
  187.  
  188. # We don't know yet, which spooler will be used. If we don't detect
  189. # one.  we assume that we do spooler-less printing. Supported spoolers
  190. # are currently:
  191.  
  192. #    cups    - CUPS - Common Unix Printing System
  193. #    solaris - Solaris LP (possibly some other SysV LP services as well)
  194. #    lpd     - LPD - Line Printer Daemon
  195. #    lprng   - LPRng - LPR - New Generation
  196. #    gnulpr  - GNUlpr, an enhanced LPD (development stopped)
  197. #    ppr     - PPR (foomatic-rip runs as a PPR RIP)
  198. #    ppr_int - PPR (foomatic-rip runs as an interface)
  199. #    cps     - CPS - Coherent Printing System
  200. #    pdq     - PDQ - Print, Don't Queue (development stopped)
  201. #    direct  - Direct, spooler-less printing
  202.  
  203. my $spooler = 'direct';
  204.  
  205. # PPD file name
  206. my $ppdfile = "";
  207.  
  208. # Printer model
  209. my $model = "";
  210.  
  211. # Printer queue name
  212. my $printer = "";
  213.  
  214. # Printing options
  215. my $optstr = "";
  216.  
  217. # Job ID
  218. my $jobid = "";
  219.  
  220. # User who sent job
  221. my $jobuser = ((getpwuid($<))[0] || `whoami` || "");
  222. chomp $jobuser;
  223.  
  224. # Host from which job was sent
  225. my $jobhost = `hostname`;
  226. chomp $jobhost;
  227.  
  228. # Job title
  229. my $jobtitle = "$jobuser\@$jobhost";
  230.  
  231. # Number of copies
  232. my $copies = "1";
  233.  
  234. # Post pipe (command into which the output of this filter should be piped)
  235. my $postpipe = "";
  236.  
  237. # job meta-data file path (for Solaris LP)
  238. my $attrpath = '';
  239.  
  240. # Files to be printed
  241. my @filelist = ();
  242.  
  243. # Where to send debugging log output.  Initialized to STDERR until the command
  244. # line arguments are parsed.
  245. my $logh = *STDERR;
  246.  
  247. # JCL prefix to put before the JCL options (Can be modified by a
  248. # "*JCLBegin:" keyword in the PPD file):
  249. my $jclbegin = "\033%-12345X\@PJL\n";
  250.  
  251. # JCL command to switch the printer to the PostScript interpreter (Can
  252. # be modified by a "*JCLToPSInterpreter:" keyword in the PPD file):
  253. my $jcltointerpreter = "";
  254.  
  255. # JCL command to close a print job (Can be modified by a "*JCLEnd:"
  256. # keyword in the PPD file):
  257. my $jclend = "\033%-12345X\@PJL RESET\n";
  258.  
  259. # Prefix for starting every JCL command (Can be modified by
  260. # "*FoomaticJCLPrefix:" keyword in the PPD file):
  261. my $jclprefix = "\@PJL ";
  262.  
  263. # Under which name were we called and in which directory do we reside
  264. $0 =~ m!^(.*/)([^/]+)$!;
  265. my $programdir = $1;
  266. my $programname = $2;
  267.  
  268. # Filters to convert non-PostScript files
  269. my @fileconverters = 
  270.   (# a2ps (converts also other files than text)
  271.    'a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o -',
  272.    # enscript
  273.    'enscript -G @@-M @@PAGESIZE@@ @@-b "Page $%|@@JOBTITLE@@ ' .
  274.    '--margins=36:36:36:36 --mark-wrapped-lines=arrow --word-wrap -p-',
  275.    # mpage
  276.    'mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -m36l36b36t36r ' .
  277.    '-f -P- -');
  278.  
  279. # spooler-specific file converters, default for the specific spooler when
  280. # none of the converters above is chosen. Remove weird characters from the
  281. # command line arguments to enhance security
  282. my @fixed_args = 
  283.     (defined($ARGV[0])?removespecialchars($ARGV[0]):"",
  284.      defined($ARGV[1])?removespecialchars($ARGV[1]):"",
  285.      defined($ARGV[2])?removespecialchars($ARGV[2]):"",
  286.      defined($ARGV[3])?removespecialchars($ARGV[3]):"",
  287.      defined($ARGV[4])?removespecialchars($ARGV[4]):"");
  288. my $spoolerfileconverters = {
  289.     'cups' => "${programdir}texttops '$fixed_args[0]' '$fixed_args[1]' '$fixed_args[2]' " .
  290.         "'$fixed_args[3]' '$fixed_args[4] page-top=36 page-bottom=36 " .
  291.     "page-left=36 page-right=36 nolandscape cpi=12 lpi=7 " .
  292.     "columns=1 wrap'"
  293.     };
  294.  
  295. ## Config file
  296.  
  297. # Read config file if present
  298. my %conf = readConfFile("$configpath/filter.conf");
  299.  
  300. # Get execution path from config file
  301. $execpath = $conf{execpath} if defined $conf{execpath};
  302. $ENV{'PATH'} = $execpath;
  303.  
  304. # Get CUPS filter path from config file
  305. $cupsfilterpath = $conf{cupsfilterpath} if defined $conf{cupsfilterpath};
  306.  
  307. # Set debug mode
  308. $debug = $conf{debug} if defined $conf{debug};
  309.  
  310. # Determine which filter to use for non-PostScript files to be converted
  311. # to PostScript
  312. if (defined $conf{textfilter}) {
  313.     $fileconverter = $conf{textfilter};
  314.     $fileconverter eq 'a2ps' and $fileconverter = $fileconverters[0];
  315.     $fileconverter eq 'enscript' and $fileconverter = $fileconverters[1];
  316.     $fileconverter eq 'mpage' and $fileconverter = $fileconverters[2];
  317. }
  318.  
  319. # Set the preferred shell for "system()" execution
  320. (defined $conf{preferred_shell}) &&
  321.     ($modern_shell = $conf{preferred_shell});
  322. # if none was preferred, look for a shell that will work
  323. foreach my $shell ('/bin/sh', '/bin/bash', '/bin/ksh', '/bin/zsh') {
  324.     if (($modern_shell eq '') && (-x $shell))  {
  325.         open(FD, "| ".$shell." -c \"((0<1))\" 2>/dev/null");
  326.         (close(FD) == 1) && ($modern_shell = $shell);
  327.     }
  328. }
  329.  
  330. ## Environment variables;
  331.  
  332. # "PPD": PPD file name for CUPS, Solaris, or PPR (if we run as PPR RIP)
  333. if (defined($ENV{'PPD'})) {
  334.     # Clean the file name from weird characters which could cause
  335.     # unexpected behaviour
  336.     $ppdfile = removespecialchars($ENV{'PPD'});
  337.     # CUPS, Solaris LP, and PPR (RIP filter) use the "PPD" environment variable
  338.     # to make the PPD file name available (we set CUPS here preliminarily,
  339.     # in the next step we check for Solaris LP and the PPR)
  340.     $spooler = 'cups';
  341. }
  342.  
  343. # "SPOOLER_KEY": Solaris LP print service
  344. if (defined($ENV{'SPOOLER_KEY'})) {
  345.     $spooler = 'solaris';
  346.  
  347.     $ppdfile = $ENV{'PPD'};
  348.     # set the printer name from the PPD file name
  349.     ($ppdfile =~ m!^.*/([^/]+)\.ppd$!) &&
  350.         ($printer = $1);
  351.  
  352.     # Solaris LP may augment the "options" string argument from the command
  353.     # line with an attributes file ($ATTRPATH)
  354.     (defined($attrpath = $ENV{'ATTRPATH'})) &&
  355.         ($optstr = read_attribute_file($attrpath));
  356. }
  357.  
  358. # "PPR_VERSION": PPR
  359. if (defined($ENV{'PPR_VERSION'})) {
  360.     # We have PPR
  361.     $spooler = 'ppr';
  362. }
  363.  
  364. # "PPR_RIPOPTS": PPR
  365. if (defined($ENV{'PPR_RIPOPTS'})) {
  366.     # PPR 1.5 allows the user to specify options for the PPR RIP with the 
  367.     # "--ripopts" option on the "ppr" command line. They are provided to
  368.     # the RIP via the "PPR_RIPOPTS" environment variable.
  369.     # Clean the option string from weird characters which could cause
  370.     # unexpected behaviour
  371.     $optstr .= removespecialchars("$ENV{'PPR_RIPOPTS'} ");
  372.     # We have PPR
  373.     $spooler = 'ppr';
  374. }
  375.  
  376. # "LPOPTS": Option settings for some LPD implementations (ex: GNUlpr)
  377. if (defined($ENV{'LPOPTS'})) {
  378.     my @lpopts = split(/,/, removespecialchars($ENV{'LPOPTS'}));
  379.     foreach my $opt (@lpopts) {
  380.     $opt =~ s/^\s+//;
  381.     $opt =~ s/\s+$//;
  382.     if ($opt =~ /\s+/) {
  383.         $opt = "\"$opt\"";
  384.     }
  385.     $optstr .= "$opt ";
  386.     }
  387.     # We have an LPD which accepts "-o" for options
  388.     $spooler = 'gnulpr';
  389. }
  390.  
  391.  
  392.  
  393. ## Named command line options
  394.  
  395. # We do not use Getopt::Long because it does not work when between the
  396. # option and the argument is no space ("-w80" instead of "-w 80"). This
  397. # happens in the command line of LPRng, but also users could type in
  398. # options this way when printing without spooler.
  399.  
  400. # Make one option string with a non-printable character as separator,
  401. # So we can parse it more easily.
  402.  
  403. # To avoid the separator to be in the options itselves, it is filters
  404. # out of the options. This does not break anything as having non
  405. # printable characters in the command line options does not make sense
  406. # nor is this needed. This way misinterpretation and even abuse is
  407. # prevented.
  408.  
  409. my $argstr = "\x01" . 
  410.     join("\x01", map { removeunprintables($_) } @ARGV) . "\x01";
  411.  
  412. # Debug mode activated via command line
  413. if ($argstr =~ s/\x01--debug\x01/\x01/) {
  414.     $debug = 1;
  415. }
  416.  
  417. # Command line options for verbosity
  418. my $verbose = ($argstr =~ s/\x01-v\x01/\x01/);
  419. my $quiet = ($argstr =~ s/\x01-q\x01/\x01/);
  420. my $show_docs = ($argstr =~ s/\x01-d\x01/\x01/);
  421. my $do_docs;
  422. my $cupscolorprofile;
  423.  
  424. if ($debug) {
  425.     # Grotesquely unsecure; use for debugging only
  426.     open LOG, "> ${logfile}.log";
  427.     $logh = *LOG;
  428.  
  429.     use IO::Handle;
  430.     $logh->autoflush(1);
  431. } elsif (($quiet) && (!$verbose)) {
  432.     # Quiet mode, do not log
  433.     open LOG, "> /dev/null";
  434.     $logh = *LOG;
  435.  
  436.     use IO::Handle;
  437.     $logh->autoflush(1);
  438. } else {
  439.     # Default: log to STDERR
  440.     $logh=*STDERR;
  441. }
  442.  
  443.  
  444.  
  445. ## Start debug logging
  446. if ($debug) {
  447.     # If we are not in debug mode, we do this later, as we must find out at
  448.     # first which spooler is used. When printing without spooler we
  449.     # suppress logging because foomatic-rip is called directly on the
  450.     # command line and so we avoid logging onto the console.
  451.     print $logh "foomatic-rip version $ripversion running...\n";
  452.     # Print the command line only in debug mode, Mac OS X adds very many
  453.     # options so that CUPS cannot handle the output of the command line
  454.     # in its log files. If CUPS encounters a line with more than 1024
  455.     # characters sent into its log files, it aborts the job with an error.
  456.     if (($debug) || ($spooler ne 'cups')) {
  457.     print $logh "called with arguments: '", join("', '",@ARGV), "'\n";
  458.     }
  459. }
  460.  
  461.  
  462.  
  463. ## Continue with named options
  464.  
  465. # Check for LPRng first so we do not pick up bogus ppd files by the -p option
  466. if ($argstr =~ s/\x01--lprng\x01/\x01/) {
  467.     # We have LPRng
  468.     $spooler = 'lprng';
  469. }
  470. # 'PRINTCAP_ENTRY' environment variable is : LPRng
  471. #  the :ppd=/path/to/ppdfile printcap entry should be used
  472. if (defined($ENV{'PRINTCAP_ENTRY'})){
  473.     $spooler = 'lprng';
  474.     my( @pc);
  475.     @pc = split( /\s*:\s*/, $ENV{'PRINTCAP_ENTRY'} );
  476.     shift @pc;
  477.     foreach (@pc) {
  478.         if( /^ppd=(.*)$/ or  /^ppdfile=(.*)$/ ){
  479.             $ppdfile = removespecialchars($1) if $1;
  480.         }
  481.     }
  482. } elsif ($argstr =~ s/\x01--lprng\x01/\x01/g) {
  483.     # We have LPRng
  484.     $spooler = 'lprng';
  485. }
  486.  
  487.  
  488. # PPD file name given via the command line
  489. # allow duplicates, and use the last specified one
  490. while ( ($spooler ne 'lprng') and ($argstr =~ s/\x01-p(\x01|)([^\x01]+)\x01/\x01/)) {
  491.     $ppdfile = removeshellescapes($2);
  492. }
  493. while ($argstr =~ s/\x01--ppd(\x01|=|)([^\x01]+)\x01/\x01/) {
  494.     $ppdfile = removeshellescapes($2);
  495. }
  496.  
  497. # Check for LPD/GNUlpr by typical options which the spooler puts onto
  498. # the filter's command line (options "-w": text width, "-l": text
  499. # length, "-i": indent, "-x", "-y": graphics size, "-c": raw printing,
  500. # "-n": user name, "-h": host name)
  501. if ($argstr =~ s/\x01-h(\x01|)([^\x01]+)\x01/\x01/) {
  502.     # We have LPD or GNUlpr
  503.     if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
  504.     $spooler = 'lpd';
  505.     }
  506.     $jobhost = $2;
  507. }
  508. if ($argstr =~ s/\x01-n(\x01|)([^\x01]+)\x01/\x01/) {
  509.     # We have LPD or GNUlpr
  510.     if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
  511.     $spooler = 'lpd';
  512.     }
  513.     $jobuser = $2;
  514. }
  515. if (($argstr =~ s/\x01-w(\x01|)\d+\x01/\x01/) ||
  516.     ($argstr =~ s/\x01-l(\x01|)\d+\x01/\x01/) || 
  517.     ($argstr =~ s/\x01-x(\x01|)\d+\x01/\x01/) ||
  518.     ($argstr =~ s/\x01-y(\x01|)\d+\x01/\x01/) || 
  519.     ($argstr =~ s/\x01-i(\x01|)\d+\x01/\x01/) ||
  520.     ($argstr =~ s/\x01-c\x01/\x01/)) {
  521.     # We have LPD or GNUlpr
  522.     if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
  523.     $spooler = 'lpd';
  524.     }
  525. }
  526.  
  527. # LPRng delivers the option settings via the "-Z" argument
  528. if ($argstr =~ s/\x01-Z(\x01|)([^\x01]+)\x01/\x01/) {
  529.     my @lpopts = split(/,/, $2);
  530.     foreach my $opt (@lpopts) {
  531.     $opt =~ s/^\s+//;
  532.     $opt =~ s/\s+$//;
  533.     $opt = removeshellescapes($opt);
  534.     if ($opt =~ /\s+/) {
  535.         $opt = "\"$opt\"";
  536.     }
  537.     $optstr .= "$opt ";
  538.     }
  539.     # We have LPRng
  540.     $spooler = 'lprng';
  541. }
  542.  
  543. # Job title and options for stock LPD
  544. if ($argstr =~ s/\x01-[jJ](\x01|)([^\x01]+)\x01/\x01/) {
  545.     # An LPD
  546.     $jobtitle = removeshellescapes($2);
  547.     # Classic LPD hack
  548.     if ($spooler eq "lpd") {
  549.     $optstr .= "$jobtitle ";
  550.     }
  551. }
  552.  
  553. # Check for CPS
  554. if ($argstr =~ s/\x01--cps\x01/\x01/) {
  555.     # We have cps
  556.     $spooler = 'cps';
  557. }
  558.  
  559. # Options for spooler-less printing, CPS, or PDQ
  560. while ($argstr =~ s/\x01-o(\x01|)([^\x01]+)\x01/\x01/) {
  561.     my $opt = $2;
  562.     $opt =~ s/^\s+//;
  563.     $opt =~ s/\s+$//;
  564.     $opt = removeshellescapes($opt);
  565.     if ($opt =~ /\s+/) {
  566.     $opt = "\"$opt\"";
  567.     }
  568.     $optstr .= "$opt ";
  569.     # If we don't print as a PPR RIP or as a CPS filter, we print without
  570.     # spooler (we check for PDQ later)
  571.     if (($spooler ne 'ppr') && ($spooler ne 'cps')) {
  572.     $spooler = 'direct';
  573.     }
  574. }
  575.  
  576. # Printer for spooler-less printing or PDQ
  577. if ($argstr =~ s/\x01-d(\x01|)([^\x01]+)\x01/\x01/) {
  578.     $printer = removeshellescapes($2);
  579. }
  580. # Printer for spooler-less printing, PDQ, or LPRng
  581. if ($argstr =~ s/\x01-P(\x01|)([^\x01]+)\x01/\x01/) {
  582.     $printer = removeshellescapes($2);
  583. }
  584.  
  585. # Were we called from a PDQ wrapper?
  586. if ($argstr =~ s/\x01--pdq\x01/\x01/) {
  587.     # We have PDQ
  588.     $spooler = 'pdq';
  589. }
  590.  
  591. # Were we called to build the PDQ driver declaration file?
  592. # "--appendpdq=<file>" appends the data to the <file>,
  593. # "--genpdq=<file>" creates/overwrites <file> for the data, and
  594. # "--genpdq" writes to standard output
  595. my $genpdqfile = "";
  596. if (($argstr =~ s/\x01--(gen)(raw|)pdq(\x01|=|)([^\x01]*)\x01/\x01/) ||
  597.     ($argstr =~ s/\x01--(append)(raw|)pdq(\x01|=|)([^\x01]+)\x01/\x01/)) {
  598.     # Determine output file name
  599.     if (!$4) {
  600.     $genpdqfile = ">&STDOUT";
  601.     } else {
  602.     if ($1 eq 'gen') {
  603.         $genpdqfile = "> " . removeshellescapes($4);
  604.     } else {
  605.         $genpdqfile = ">> " . removeshellescapes($4);
  606.     }
  607.     }
  608.     # Do we want to have a PDQ driver declaration for a raw printer?
  609.     if ($2 eq 'raw') {
  610.     my $time = time();
  611.     my @pdqfile =
  612. "driver \"Raw-Printer-$time\" {
  613.   # This PDQ driver declaration file was generated automatically by
  614.   # foomatic-rip to allow raw (filter-less) printing.
  615.   language_driver all {
  616.     # We accept all file types and pass them through without any changes
  617.     filetype_regx \"\"
  618.     convert_exec {
  619.       ln -s \$INPUT \$OUTPUT
  620.     }
  621.   }
  622.   filter_exec {
  623.     ln -s \$INPUT \$OUTPUT
  624.   }
  625. }";
  626.     open PDQFILE, $genpdqfile or
  627.         rip_die("Cannot write PDQ driver declaration file",
  628.             $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  629.     print PDQFILE join('', @pdqfile);
  630.     close PDQFILE;
  631.     exit $EXIT_PRINTED;
  632.     }
  633.     # We have PDQ
  634.     $spooler = 'pdq';
  635. }
  636.  
  637.  
  638. # remove extra spacing if running as LPRng filter
  639. $added_lf = "" if $spooler eq 'lprng';
  640.  
  641. ## Command line arguments without name
  642.  
  643. # Remaining arguments
  644. my @rargs = split(/\x01/, $argstr);
  645. shift @rargs;
  646.  
  647. # Load definitions for PPR error messages, check whether we run as
  648. # PPR interface or as PPR RIP
  649. my( $ppr_printer, $ppr_address, $ppr_options, $ppr_jobbreak, $ppr_feedback,
  650.     $ppr_codes, $ppr_jobname, $ppr_routing, $ppr_for, $ppr_filetype,
  651.     $ppr_filetoprint );
  652. if ($spooler eq 'ppr') {
  653.     # Read interface.sh so we will know the correct exit codes and
  654.     # also signal.sh for the signal codes
  655.     my $deffound = 0; # Did we find one of the definition files
  656.     my @definitions;
  657.     for my $file (("lib/interface.sh", "lib/signal.sh")) {
  658.     
  659.     open FILE, "< $file" || do {
  660.         print $logh "error opening $file.\n";
  661.         next;
  662.     };
  663.     
  664.     $deffound = 1;
  665.     while(my $line = <FILE>) {
  666.         # Translate the shell script to Perl
  667.         if (($line !~ m/^\s*$/) && ($line !~ m/^\s*\#/)) {
  668.         $line =~ s/^\s*([^\#\s]*)/\$$1;/;
  669.         push (@definitions, $line);
  670.         }
  671.     }
  672.     close FILE;
  673.     }
  674.  
  675.     if ($deffound) {
  676.     # Apply the definitions loaded from PPR
  677.     eval join('',@definitions) || do {
  678.         print $logh "unable to evaluate definitions\n";
  679.         rip_die ("Error in definitions evaluation",
  680.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  681.     };
  682.     }
  683.  
  684.     # Check whether we run as a PPR interface (if not, we run as a PPR RIP)
  685.     if (($rargs[3] =~ /^\s*\d\d?\s*$/) &&
  686.     ($rargs[5] =~ /^\s*\d\d?\s*$/) &&
  687.     (($#rargs == 10) || ($#rargs == 9) || ($#rargs == 7))) {
  688.     # PPR calls interfaces with many command line parameters,
  689.     # where the forth and the sixth is a small integer
  690.     # number. In addition, we have 8 (PPR <= 1.31), 10
  691.     # (PPR>=1.32), 11 (PPR >= 1.50) command line parameters.
  692.     # We also check whether the current working directory is a
  693.     # PPR directory.
  694.     
  695.     # Get all command line parameters
  696.     $ppr_printer = removeshellescapes($rargs[0]);
  697.     $ppr_address = $rargs[1];
  698.     $ppr_options = removeshellescapes($rargs[2]);
  699.     $ppr_jobbreak = $rargs[3];
  700.     $ppr_feedback = $rargs[4];
  701.     $ppr_codes = $rargs[5];
  702.     $ppr_jobname = removeshellescapes($rargs[6]);
  703.     $ppr_routing = removeshellescapes($rargs[7]);
  704.     $ppr_for = $rargs[8];
  705.     $ppr_filetype = $rargs[9];
  706.     $ppr_filetoprint = removeshellescapes($rargs[10]);
  707.     
  708.     # Common job parameters
  709.     $printer = $ppr_printer;
  710.     $jobtitle = $ppr_jobname;
  711.     if ((!$jobtitle) && ($ppr_filetoprint)) {
  712.         $jobtitle = $ppr_filetoprint;
  713.     }
  714.     $optstr .= "$ppr_options $ppr_routing";
  715.     
  716.     # Get the path of the PPD file from the queue configuration
  717.     $ppdfile = `LANG=en_US; ppad show $ppr_printer | grep PPDFile`;
  718.     $ppdfile = removeshellescapes($ppdfile);
  719.     $ppdfile =~ s/PPDFile:\s+//;
  720.     if ($ppdfile !~ m!^/!) {
  721.         $ppdfile = "../../share/ppr/PPDFiles/$ppdfile";
  722.     }
  723.     chomp($ppdfile);
  724.     
  725.     # We have PPR and run as an interface
  726.     $spooler = 'ppr_int';
  727.     }
  728. }
  729.  
  730. # CUPS
  731. my( $cups_jobid, $cups_user, $cups_jobtitle, $cups_copies, $cups_options,
  732.     $cups_filename );
  733. if ($spooler eq 'cups') {
  734.  
  735.     # Use CUPS font path ("FontPath" in /etc/cups/cupsd.conf)
  736.     if ($ENV{'CUPS_FONTPATH'}) {
  737.     $ENV{'GS_LIB'} = $ENV{'CUPS_FONTPATH'} .
  738.         ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : "");
  739.     } else {
  740.     if ($ENV{'CUPS_DATADIR'}) {
  741.         $ENV{'GS_LIB'} = "$ENV{'CUPS_DATADIR'}/fonts" .
  742.         ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : "");
  743.     }
  744.     }
  745.  
  746.     # Get all command line parameters
  747.     $cups_jobid = removeshellescapes($rargs[0]);
  748.     $cups_user = removeshellescapes($rargs[1]);
  749.     $cups_jobtitle = removeshellescapes($rargs[2]);
  750.     $cups_copies = removeshellescapes($rargs[3]);
  751.     $cups_options = removeshellescapes($rargs[4]);
  752.     $cups_filename = removeshellescapes($rargs[5]);
  753.  
  754.     # Common job parameters
  755.     #$printer = $cups_printer;
  756.     $jobid = $cups_jobid;
  757.     $jobtitle = $cups_jobtitle;
  758.     $jobuser = $cups_user;
  759.     $copies = $cups_copies;
  760.     $optstr .= $cups_options;
  761.  
  762.     # Check for and handle inputfile vs stdin
  763.     if ((defined($cups_filename)) && ($cups_filename) &&
  764.     ($cups_filename ne '-')) {
  765.     # We get the input from a file
  766.     @filelist = ($cups_filename);
  767.     print $logh "Getting input from file $cups_filename\n";
  768.     }
  769. }
  770.  
  771. # Solaris LP spooler
  772. if ($spooler eq 'solaris') {
  773.     # Get all command line parameters
  774.     # $printer =                            # argv[0]
  775.     #                        ($rargs[0] =~ m!^.*/([^/]+)$!);
  776.     # $request_id = removeshellescapes($rargs[0]);  # argv[1]
  777.     # $user_name = removeshellescapes($rargs[1]);   # argv[2]
  778.     $jobtitle = removeshellescapes($rargs[2]);      # argv[3]
  779.     # $copies = removeshellescapes($rargs[3]);      # argv[4] # handled by the
  780.     #                                    interface script
  781.     $optstr .= removeshellescapes($rargs[4]);       # argv[5]
  782.     ($#rargs > 4) &&                        # argv[6...]
  783.         (@filelist = @rargs[5, $#rargs]);
  784. }
  785.  
  786. # LPD/LPRng/GNUlpr
  787. if (($spooler eq 'lpd') ||
  788.     ($spooler eq 'lprng' and !$ppdfile) || 
  789.     ($spooler eq 'gnulpr')) {
  790.  
  791.     # Get PPD file name as the last command line argument
  792.     $ppdfile = removeshellescapes($rargs[$#rargs]);
  793.  
  794. }
  795.  
  796.  
  797. # No spooler, CPS, or PDQ
  798. if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) {
  799.     # Which files do we want to print?
  800.     @filelist = map { removeshellescapes($_) } @rargs;
  801. }
  802.  
  803.  
  804.  
  805. ## Additional spooler-specific preparations
  806.  
  807. # CUPS
  808.  
  809. if ($spooler eq 'cups') {
  810.  
  811.     # This piece of PostScript code (initial idea 2001 by Michael
  812.     # Allerhand (michael.allerhand at ed dot ac dot uk, vastly
  813.     # improved by Till Kamppeter in 2002) lets GhostScript output
  814.     # the page accounting information which CUPS needs on standard
  815.     # error.
  816.     # Redesign by Helge Blischke (2004-11-17):
  817.     # - As the PostScript job itself may define BeginPage and/or EndPage
  818.     #   procedures, or the alternate pstops filter may have inserted
  819.     #   such procedures, we make sure that the accounting routine 
  820.     #   will safely coexist with those. To achieve this, we force
  821.     #   - the accountint stuff to be inserted at the very end of the
  822.     #     PostScript job's setup section,
  823.     #   - the accounting stuff just using the return value of the 
  824.     #     existing EndPage procedure, if any (and providing a default one
  825.     #     if not).
  826.     # - As PostScript jobs may contain calls to setpagedevice "between"
  827.     #   pages, e.g. to change media type, do in-job stapling, etc.,
  828.     #   we cannot rely on the "showpage count since last pagedevice
  829.     #   activation" but instead count the physical pages by ourselves
  830.     #   (in a global dictionary).
  831.  
  832.     if (defined $conf{ps_accounting}) {
  833.     $ps_accounting = $conf{ps_accounting};
  834.     }
  835.     $accounting_prolog = $ps_accounting ? "[{
  836. %% Code for writing CUPS accounting tags on standard error
  837.  
  838. /cupsPSLevel2 % Determine whether we can do PostScript level 2 or newer
  839.     systemdict/languagelevel 2 copy
  840.     known{get exec}{pop pop 1}ifelse 2 ge
  841. def
  842.  
  843. cupsPSLevel2
  844. {                    % in case of level 2 or higher
  845.     currentglobal true setglobal    % define a dictioary foomaticDict
  846.     globaldict begin        % in global VM and establish a
  847.     /foomaticDict            % pages count key there
  848.     <<
  849.         /PhysPages 0
  850.     >>def
  851.     end
  852.     setglobal
  853. }if
  854.  
  855. /cupsGetNumCopies { % Read the number of Copies requested for the current
  856.             % page
  857.     cupsPSLevel2
  858.     {
  859.     % PS Level 2+: Get number of copies from Page Device dictionary
  860.     currentpagedevice /NumCopies get
  861.     }
  862.     {
  863.     % PS Level 1: Number of copies not in Page Device dictionary
  864.     null
  865.     }
  866.     ifelse
  867.     % Check whether the number is defined, if it is \"null\" use #copies 
  868.     % instead
  869.     dup null eq {
  870.     pop #copies
  871.     }
  872.     if
  873.     % Check whether the number is defined now, if it is still \"null\" use 1
  874.     % instead
  875.     dup null eq {
  876.     pop 1
  877.     } if
  878. } bind def
  879.  
  880. /cupsWrite { % write a string onto standard error
  881.     (%stderr) (w) file
  882.     exch writestring
  883. } bind def
  884.  
  885. /cupsFlush    % flush standard error to make it sort of unbuffered
  886. {
  887.     (%stderr)(w)file flushfile
  888. }bind def
  889.  
  890. cupsPSLevel2
  891. {                % In language level 2, we try to do something reasonable
  892.   <<
  893.     /EndPage
  894.     [                    % start the array that becomes the procedure
  895.       currentpagedevice/EndPage 2 copy known
  896.       {get}                    % get the existing EndPage procedure
  897.       {pop pop {exch pop 2 ne}bind}ifelse    % there is none, define the default
  898.       /exec load                % make sure it will be executed, whatever it is
  899.       /dup load                    % duplicate the result value
  900.       {                    % true: a sheet gets printed, do accounting
  901.         currentglobal true setglobal        % switch to global VM ...
  902.         foomaticDict begin            % ... and access our special dictionary
  903.         PhysPages 1 add            % count the sheets printed (including this one)
  904.         dup /PhysPages exch def        % and save the value
  905.         end                    % leave our dict
  906.         exch setglobal                % return to previous VM
  907.         (PAGE: )cupsWrite             % assemble and print the accounting string ...
  908.         16 string cvs cupsWrite            % ... the sheet count ...
  909.         ( )cupsWrite                % ... a space ...
  910.         cupsGetNumCopies             % ... the number of copies ...
  911.         16 string cvs cupsWrite            % ...
  912.         (\\n)cupsWrite                % ... a newline
  913.         cupsFlush
  914.       }/if load
  915.                     % false: current page gets discarded; do nothing    
  916.     ]cvx bind                % make the array executable and apply bind
  917.   >>setpagedevice
  918. }
  919. {
  920.     % In language level 1, we do no accounting currently, as there is no global VM
  921.     % the contents of which are undesturbed by save and restore. 
  922.     % If we may be sure that showpage never gets called inside a page related save / restore pair
  923.     % we might implement an hack with showpage similar to the one above.
  924. }ifelse
  925.  
  926. } stopped cleartomark
  927. " : "";
  928.  
  929.     # On which queue are we printing?
  930.     # CUPS gives the PPD file the same name as the printer queue,
  931.     # so we can get the queue name from the name of the PPD file.
  932.     $ppdfile =~ m!^(.*/)([^/]+)\.ppd$!;
  933.     $printer = $2;
  934. }
  935.  
  936. # No spooler, CPS, or PDQ
  937.  
  938. if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) {
  939.  
  940.     # Path for personal Foomatic configuration
  941.     my $user_default_path = "$ENV{'HOME'}/.foomatic";
  942.  
  943.     if (!$ppdfile) {
  944.     if (!$printer) {
  945.         # No printer definition file selected, check whether we have a
  946.         # default printer defined.
  947.         for my $conf_file (("./.directconfig",
  948.                 "./directconfig",
  949.                 "./.config",
  950.                 "$user_default_path/direct/.config",
  951.                 "$user_default_path/direct.conf",
  952.                 "$configpath/direct/.config",
  953.                 "$configpath/direct.conf")) {
  954.         if (open CONFIG, "< $conf_file") {
  955.             while (my $line = <CONFIG>) {
  956.             chomp $line;
  957.             if ($line =~ /^default\s*:\s*([^:\s]+)\s*$/) {
  958.                 $printer = $1;
  959.                 last;
  960.             }
  961.             }
  962.             close CONFIG;
  963.         }
  964.         if ($printer) {
  965.             last;
  966.         }
  967.         }
  968.     }
  969.  
  970.     # Neither in a config file nor on the command line a printer was
  971.     # selected.
  972.     if (!$printer) {
  973.         rip_die("No printer definition (option \"-P <name>\") " .
  974.             "specified!", $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  975.     }
  976.     
  977.     # Search for the PPD file
  978.     
  979.     # Search also common spooler-specific locations, this way a printer
  980.     # configured under a certain spooler can also be used without
  981.     # spooler
  982.  
  983.     if (-r $printer) {
  984.         $ppdfile = $printer;
  985.     # CPS can have the PPD in the spool directory
  986.     } elsif (($spooler eq 'cps') &&
  987.          (-r "/var/spool/lpd/${printer}/${printer}.ppd")) {
  988.         $ppdfile = "/var/spool/lpd/${printer}/${printer}.ppd";
  989.     } elsif (($spooler eq 'cps') &&
  990.          (-r "/var/local/spool/lpd/${printer}/${printer}.ppd")) {
  991.         $ppdfile = "/var/local/spool/lpd/${printer}/${printer}.ppd";
  992.     } elsif (($spooler eq 'cps') &&
  993.          (-r "/var/local/lpd/${printer}/${printer}.ppd")) {
  994.         $ppdfile = "/var/local/lpd/${printer}/${printer}.ppd";
  995.     } elsif (($spooler eq 'cps') &&
  996.          (-r "/var/spool/lpd/${printer}.ppd")) {
  997.         $ppdfile = "/var/spool/lpd/${printer}.ppd";
  998.     } elsif (($spooler eq 'cps') &&
  999.          (-r "/var/local/spool/lpd/${printer}.ppd")) {
  1000.         $ppdfile = "/var/local/spool/lpd/${printer}.ppd";
  1001.     } elsif (($spooler eq 'cps') &&
  1002.          (-r "/var/local/lpd/${printer}.ppd")) {
  1003.         $ppdfile = "/var/local/lpd/${printer}.ppd";
  1004.     } elsif (-r "${printer}.ppd") { # current dir
  1005.         $ppdfile = "${printer}.ppd";
  1006.     } elsif (-r "$user_default_path/${printer}.ppd") { # user dir
  1007.         $ppdfile = "$user_default_path/${printer}.ppd";
  1008.     } elsif (-r "$configpath/direct/${printer}.ppd") { # system dir
  1009.         $ppdfile = "$configpath/direct/${printer}.ppd";
  1010.     } elsif (-r "$configpath/${printer}.ppd") { # system dir
  1011.         $ppdfile = "$configpath/${printer}.ppd";
  1012.     } elsif (-r "/etc/cups/ppd/${printer}.ppd") { # CUPS config dir
  1013.         $ppdfile = "/etc/cups/ppd/${printer}.ppd";
  1014.     } elsif (-r "/usr/local/etc/cups/ppd/${printer}.ppd") {
  1015.         $ppdfile = "/usr/local/etc/cups/ppd/${printer}.ppd";
  1016.     } elsif (-r "/usr/share/ppr/PPDFiles/${printer}.ppd") { # PPR PPDs
  1017.         $ppdfile = "/usr/share/ppr/PPDFiles/${printer}.ppd";
  1018.     } elsif (-r "/usr/local/share/ppr/PPDFiles/${printer}.ppd") {
  1019.         $ppdfile = "/usr/local/share/ppr/PPDFiles/${printer}.ppd";
  1020.     } else {
  1021.         rip_die ("There is no readable PPD file for the printer " .
  1022.              "$printer, is it configured?",
  1023.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  1024.     }
  1025.     }
  1026. }
  1027.  
  1028.  
  1029.  
  1030. ## Files to be printed (can be more than one for spooler-less printing)
  1031.  
  1032. # Empty file list -> print STDIN
  1033. if ($#filelist < 0) {
  1034.     @filelist = ("<STDIN>");
  1035. }
  1036.  
  1037. # Check file list
  1038. my $file;
  1039. my $filecnt = 0;
  1040. for $file (@filelist) {
  1041.     if ($file ne "<STDIN>") {
  1042.     if ($file =~ /^-/) {
  1043.         rip_die ("Invalid argument: $file",
  1044.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  1045.     } elsif (! -r $file) {
  1046.         print $logh "File $file does not exist/is not readable\n";
  1047.         splice(@filelist, $filecnt, 1);
  1048.         $filecnt --;
  1049.     }
  1050.     }
  1051.     $filecnt ++;
  1052. }
  1053.  
  1054.  
  1055.  
  1056. ## When we print without spooler or with CPS do not log onto STDERR unless 
  1057. ## the "-v" ('Verbose') is set or the debug mode is used
  1058. if ((($spooler eq 'direct') || ($spooler eq 'cps') || ($genpdqfile)) && 
  1059.     (!$verbose) && (!$debug)) {
  1060.     close $logh;
  1061.     open LOG, "> /dev/null";
  1062.     $logh = *LOG;
  1063.  
  1064.     use IO::Handle;
  1065.     $logh->autoflush(1);
  1066. }
  1067.  
  1068.  
  1069.  
  1070. ## Start logging
  1071. if (!$debug) {
  1072.     # If we are in debug mode, we do this earlier.
  1073.     print $logh "foomatic-rip version $ripversion running...\n";
  1074.     # Print the command line only in debug mode, Mac OS X adds very many
  1075.     # options so that CUPS cannot handle the output of the command line
  1076.     # in its log files. If CUPS encounters a line with more than 1024
  1077.     # characters sent into its log files, it aborts the job with an error.
  1078.     if (($debug) || ($spooler ne 'cups')) {
  1079.     print $logh "called with arguments: '", join("', '",@ARGV), "'\n";
  1080.     }
  1081. }
  1082.  
  1083.  
  1084.  
  1085. ## PPD file
  1086.  
  1087. # Load the PPD file and build a data structure for the renderer's
  1088. # command line and the options
  1089. open PPD, "< $ppdfile" || do {
  1090.     print $logh "error opening $ppdfile.\n";
  1091.     rip_die ("Unable to open PPD file $ppdfile",
  1092.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  1093. };
  1094.  
  1095. print $logh "Parsing PPD file ...\n";
  1096.  
  1097. my $dat = {};              # data structure for the options
  1098. my $currentargument = "";  # We are currently reading this argument
  1099.  
  1100. # If we have an old Foomatic 2.0.x PPD file, read its built-in Perl
  1101. # data structure into @datablob and the default values in %ppddefaults
  1102. # Then delete the $dat structure, replace it by the one "eval"ed from
  1103. # @datablob, and correct the default settings according to the ones of
  1104. # the main PPD structure
  1105. my @datablob;
  1106. my $jclprefixset = 0;
  1107.  
  1108. # Parse the PPD file
  1109. sub undossify( $ );
  1110. while(<PPD>) {
  1111.     # foomatic-rip should also work with PPD file downloaded under Windows.
  1112.     $_ = undossify($_);
  1113.     # Parse keywords
  1114.     if (m!^\*NickName:\s*\"(.*)$!) {
  1115.     # "*NickName: <code>"
  1116.     my $line = $1;
  1117.     # Store the value
  1118.     # Code string can have multiple lines, read all of them
  1119.     my $cmd = "";
  1120.     while ($line !~ m!\"!) {
  1121.         if ($line =~ m!&&$!) {
  1122.         # line continues in next line
  1123.         $cmd .= substr($line, 0, -2);
  1124.         } else {
  1125.         # line ends here
  1126.         $cmd .= "$line\n";
  1127.         }
  1128.         # Read next line
  1129.         $line = <PPD>;
  1130.         chomp $line;
  1131.     }
  1132.     $line =~ m!^([^\"]*)\"!;
  1133.     $cmd .= $1;
  1134.     $model = unhtmlify($cmd);
  1135.     } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) {
  1136.     # "*FoomaticIDs: <printer ID> <driver ID>"
  1137.     my $id = $1;
  1138.     my $driver = $2;
  1139.     # Store the values
  1140.     $dat->{'id'} = $id;
  1141.     $dat->{'driver'} = $driver;
  1142.     } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1143.     # "*FoomaticRIPPostPipe: <code>"
  1144.     my $line = $1;
  1145.     # Store the value
  1146.     # Code string can have multiple lines, read all of them
  1147.     my $cmd = "";
  1148.     while ($line !~ m!\"!) {
  1149.         if ($line =~ m!&&$!) {
  1150.         # line continues in next line
  1151.         $cmd .= substr($line, 0, -2);
  1152.         } else {
  1153.         # line ends here
  1154.         $cmd .= "$line\n";
  1155.         }
  1156.         # Read next line
  1157.         $line = <PPD>;
  1158.         chomp $line;
  1159.     }
  1160.     $line =~ m!^([^\"]*)\"!;
  1161.     $cmd .= $1;
  1162.     $postpipe = unhtmlify($cmd);
  1163.     } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) {
  1164.     # "*FoomaticRIPCommandLine: <code>"
  1165.     my $line = $1;
  1166.     # Store the value
  1167.     # Code string can have multiple lines, read all of them
  1168.     my $cmd = "";
  1169.     while ($line !~ m!\"!) {
  1170.         if ($line =~ m!&&$!) {
  1171.         # line continues in next line
  1172.         $cmd .= substr($line, 0, -2);
  1173.         } else {
  1174.         # line ends here
  1175.         $cmd .= "$line\n";
  1176.         }
  1177.         # Read next line
  1178.         $line = <PPD>;
  1179.         chomp $line;
  1180.     }
  1181.     $line =~ m!^([^\"]*)\"!;
  1182.     $cmd .= $1;
  1183.     $dat->{'cmd'} = unhtmlify($cmd);
  1184.     } elsif (m!^\*cupsFilter:\s*\"(.*)$!) {
  1185.     # "*cupsFilter: <code>"
  1186.     my $line = $1;
  1187.     # Store the value
  1188.     # Code string can have multiple lines, read all of them
  1189.     my $cmd = "";
  1190.     while ($line !~ m!\"!) {
  1191.         if ($line =~ m!&&$!) {
  1192.         # line continues in next line
  1193.         $cmd .= substr($line, 0, -2);
  1194.         } else {
  1195.         # line ends here
  1196.         $cmd .= "$line\n";
  1197.         }
  1198.         # Read next line
  1199.         $line = <PPD>;
  1200.         chomp $line;
  1201.     }
  1202.     $line =~ m!^([^\"]*)\"!;
  1203.     $cmd .= $1;
  1204.     my $cupsfilterline = unhtmlify($cmd);
  1205.     if ($cupsfilterline =~ /^\s*(\S+)\s+\d+\s+(\S+)\s*$/) {
  1206.         print $logh "*cupsFilter: \"$cupsfilterline\"\n"; 
  1207.         # Make a hash by mime type for all CUPS filters set in this PPD
  1208.         $dat->{'cupsfilter'}{$1} = $2;
  1209.     }
  1210.     } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) {
  1211.     # "*CustomPageSize True: <code>"
  1212.     my $setting = "Custom";
  1213.     my $translation = "Custom Size";
  1214.     my $line = $1;
  1215.     # Make sure that the argument is in the data structure
  1216.     checkarg ($dat, "PageSize");
  1217.     checkarg ($dat, "PageRegion");
  1218.     # Make sure that the setting is in the data structure
  1219.     checksetting ($dat, "PageSize", $setting);
  1220.     checksetting ($dat, "PageRegion", $setting);
  1221.     $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1222.     $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1223.     # Store the value
  1224.     # Code string can have multiple lines, read all of them
  1225.     my $code = "";
  1226.     while ($line !~ m!\"!) {
  1227.         if ($line =~ m!&&$!) {
  1228.         # line continues in next line
  1229.         $code .= substr($line, 0, -2);
  1230.         } else {
  1231.         # line ends here
  1232.         $code .= "$line\n";
  1233.         }
  1234.         # Read next line
  1235.         $line = <PPD>;
  1236.         chomp $line;
  1237.     }
  1238.     $line =~ m!^([^\"]*)\"!;
  1239.     $code .= $1;
  1240.     if ($code !~ m!^%% FoomaticRIPOptionSetting!m) {
  1241.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1242.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1243.     }
  1244.     } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) {
  1245.     # "*[JCL]OpenUI *<option>[/<translation>]: <type>"
  1246.     my $argnametrans = $2;
  1247.     my $argtype = $3;
  1248.     my $argname;
  1249.     my $translation = "";
  1250.     if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1251.         $argname = $1;
  1252.         $translation = $2;
  1253.     } else {
  1254.         $argname = $argnametrans;
  1255.     }
  1256.     # Make sure that the argument is in the data structure
  1257.     checkarg ($dat, $argname);
  1258.     # Store the values
  1259.     $dat->{'args_byname'}{$argname}{'comment'} = $translation;
  1260.     # Set the argument type only if not defined yet, a
  1261.     # definition in "*FoomaticRIPOption" has priority
  1262.     if ( !($dat->{'args_byname'}{$argname}{'type'}) ) {
  1263.         if ($argtype eq "PickOne") {
  1264.         $dat->{'args_byname'}{$argname}{'type'} = 'enum';
  1265.         } elsif ($argtype eq "PickMany") {
  1266.         $dat->{'args_byname'}{$argname}{'type'} = 'pickmany';
  1267.         } elsif ($argtype eq "Boolean") {
  1268.         $dat->{'args_byname'}{$argname}{'type'} = 'bool';
  1269.         }
  1270.     }
  1271.     # Mark in which argument we are currently, so that we can find
  1272.     # the entries for the choices
  1273.     $currentargument = $argname;
  1274.     } elsif (m!^\*(JCL|)CloseUI:\s+\*([^:/\s]+)\s*$!) {
  1275.     # "*[JCL]CloseUI *<option>"
  1276.     my $argname = $2;
  1277.     # Unmark the current argument to do not mis-interpret any keywords
  1278.     # as choices
  1279.     $currentargument = "";
  1280.     } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s*$!) ||
  1281.          (m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s+(\S+)\s*$!)){
  1282.     # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]"
  1283.     # <order> only used for 1-choice enum options
  1284.     my $argname = $1;
  1285.     my $argtype = $2;
  1286.     my $argstyle = $3;
  1287.     my $spot = $4;
  1288.     my $order = $5;
  1289.     # Make sure that the argument is in the data structure
  1290.     checkarg ($dat, $argname);
  1291.     # Store the values
  1292.     $dat->{'args_byname'}{$argname}{'type'} = $argtype;
  1293.     if ($argstyle eq "PS") {
  1294.         $dat->{'args_byname'}{$argname}{'style'} = 'G';
  1295.     } elsif ($argstyle eq "CmdLine") {
  1296.         $dat->{'args_byname'}{$argname}{'style'} = 'C';
  1297.     } elsif ($argstyle eq "JCL") {
  1298.         $dat->{'args_byname'}{$argname}{'style'} = 'J';
  1299.         $dat->{'jcl'} = 1;
  1300.     } elsif ($argstyle eq "Composite") {
  1301.         $dat->{'args_byname'}{$argname}{'style'} = 'X';
  1302.     }
  1303.     $dat->{'args_byname'}{$argname}{'spot'} = $spot;
  1304.     # $order only defined here for 1-choice enum options
  1305.     if ($order) {
  1306.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  1307.     }
  1308.     } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) {
  1309.     # "*FoomaticRIPOptionPrototype <option>: <code>"
  1310.     # Used for numerical and string options only
  1311.     my $argname = $1;
  1312.     my $line = $2;
  1313.     # Make sure that the argument is in the data structure
  1314.     checkarg ($dat, $argname);
  1315.     # Store the value
  1316.     # Code string can have multiple lines, read all of them
  1317.     my $proto = "";
  1318.     while ($line !~ m!\"!) {
  1319.         if ($line =~ m!&&$!) {
  1320.         # line continues in next line
  1321.         $proto .= substr($line, 0, -2);
  1322.         } else {
  1323.         # line ends here
  1324.         $proto .= "$line\n";
  1325.         }
  1326.         # Read next line
  1327.         $line = <PPD>;
  1328.         chomp $line;
  1329.     }
  1330.     $line =~ m!^([^\"]*)\"!;
  1331.     $proto .= $1;
  1332.     $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto);
  1333.     } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*(\S+)\s+(\S+)\s*$!) {
  1334.     # "*FoomaticRIPOptionRange <option>: <min> <max>"
  1335.     # Used for numerical options only
  1336.     my $argname = $1;
  1337.     my $min = $2;
  1338.     my $max = $3;
  1339.     # Make sure that the argument is in the data structure
  1340.     checkarg ($dat, $argname);
  1341.     # Store the values
  1342.     $dat->{'args_byname'}{$argname}{'min'} = $min;
  1343.     $dat->{'args_byname'}{$argname}{'max'} = $max;
  1344.     } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*(\S+)\s*$!) {
  1345.     # "*FoomaticRIPOptionMaxLength <option>: <length>"
  1346.     # Used for string options only
  1347.     my $argname = $1;
  1348.     my $maxlength = $2;
  1349.     # Make sure that the argument is in the data structure
  1350.     checkarg ($dat, $argname);
  1351.     # Store the value
  1352.     $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength;
  1353.     } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) {
  1354.     # "*FoomaticRIPOptionAllowedChars <option>: <code>"
  1355.     # Used for string options only
  1356.     my $argname = $1;
  1357.     my $line = $2;
  1358.     # Store the value
  1359.     # Code string can have multiple lines, read all of them
  1360.     my $code = "";
  1361.     while ($line !~ m!\"!) {
  1362.         if ($line =~ m!&&$!) {
  1363.         # line continues in next line
  1364.         $code .= substr($line, 0, -2);
  1365.         } else {
  1366.         # line ends here
  1367.         $code .= "$line\n";
  1368.         }
  1369.         # Read next line
  1370.         $line = <PPD>;
  1371.         chomp $line;
  1372.     }
  1373.     $line =~ m!^([^\"]*)\"!;
  1374.     $code .= $1;
  1375.     # Make sure that the argument is in the data structure
  1376.     checkarg ($dat, $argname);
  1377.     # Store the value
  1378.     $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code);
  1379.     } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) {
  1380.     # "*FoomaticRIPOptionAllowedRegExp <option>: <code>"
  1381.     # Used for string options only
  1382.     my $argname = $1;
  1383.     my $line = $2;
  1384.     # Store the value
  1385.     # Code string can have multiple lines, read all of them
  1386.     my $code = "";
  1387.     while ($line !~ m!\"!) {
  1388.         if ($line =~ m!&&$!) {
  1389.         # line continues in next line
  1390.         $code .= substr($line, 0, -2);
  1391.         } else {
  1392.         # line ends here
  1393.         $code .= "$line\n";
  1394.         }
  1395.         # Read next line
  1396.         $line = <PPD>;
  1397.         chomp $line;
  1398.     }
  1399.     $line =~ m!^([^\"]*)\"!;
  1400.     $code .= $1;
  1401.     # Make sure that the argument is in the data structure
  1402.     checkarg ($dat, $argname);
  1403.     # Store the value
  1404.     $dat->{'args_byname'}{$argname}{'allowedregexp'} =
  1405.         unhtmlify($code);
  1406.     } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) {
  1407.     # "*OrderDependency: <order> <section> *<option>"
  1408.     my $order = $1;
  1409.     my $section = $2;
  1410.     my $argname = $3;
  1411.     # Make sure that the argument is in the data structure
  1412.     checkarg ($dat, $argname);
  1413.     # Store the values
  1414.     $dat->{'args_byname'}{$argname}{'order'} = $order;
  1415.     $dat->{'args_byname'}{$argname}{'section'} = $section;
  1416.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1417.     # "*Default<option>: <value>"
  1418.     my $argname = $1;
  1419.     my $default = $2;
  1420.     # Make sure that the argument is in the data structure
  1421.     checkarg ($dat, $argname);
  1422.     # Store the value
  1423.     $dat->{'args_byname'}{$argname}{'default'} = $default;
  1424.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1425.     # "*FoomaticRIPDefault<option>: <value>"
  1426.     # Used for numerical options only
  1427.     my $argname = $1;
  1428.     my $default = $2;
  1429.     # Make sure that the argument is in the data structure
  1430.     checkarg ($dat, $argname);
  1431.     # Store the value
  1432.     $dat->{'args_byname'}{$argname}{'fdefault'} = $default;
  1433.     } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) {
  1434.     # "*<option> <choice>[/<translation>]: <code>"
  1435.     my $settingtrans = $1;
  1436.     my $line = $2;
  1437.     my $translation = "";
  1438.     my $setting = "";
  1439.     if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1440.         $setting = $1;
  1441.         $translation = $2;
  1442.     } else {
  1443.         $setting = $settingtrans;
  1444.     }
  1445.     # Make sure that the argument is in the data structure
  1446.     checkarg ($dat, $currentargument);
  1447.     # Make sure that the setting is in the data structure (enum options)
  1448.     my $bool =
  1449.         ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool');
  1450.     if ($bool) {
  1451.         if (lc($setting) eq "true") {
  1452.         if (!$dat->{'args_byname'}{$currentargument}{'comment'}) {
  1453.             $dat->{'args_byname'}{$currentargument}{'comment'} =
  1454.             $translation;
  1455.         }
  1456.         $dat->{'args_byname'}{$currentargument}{'comment_true'} =
  1457.             $translation;
  1458.         } else {
  1459.         $dat->{'args_byname'}{$currentargument}{'comment_false'} =
  1460.             $translation;
  1461.         }
  1462.     } else {
  1463.         checksetting ($dat, $currentargument, $setting);
  1464.         # Make sure that this argument has a default setting, even if 
  1465.         # none is defined in this PPD file
  1466.         if (!$dat->{'args_byname'}{$currentargument}{'default'}) {
  1467.         $dat->{'args_byname'}{$currentargument}{'default'} = $setting;
  1468.         }
  1469.         $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation;
  1470.     }
  1471.     # Store the value
  1472.     # Code string can have multiple lines, read all of them
  1473.     my $code = "";
  1474.     while ($line !~ m!\"!) {
  1475.         if ($line =~ m!&&$!) {
  1476.         # line continues in next line
  1477.         $code .= substr($line, 0, -2);
  1478.         } else {
  1479.         # line ends here
  1480.         $code .= "$line\n";
  1481.         }
  1482.         # Read next line
  1483.         $line = <PPD>;
  1484.         chomp $line;
  1485.     }
  1486.     $line =~ m!^([^\"]*)\"!;
  1487.     $code .= $1;
  1488.     if ($code !~ m!^%% FoomaticRIPOptionSetting!) {
  1489.         if ($bool) {
  1490.         if (lc($setting) eq "true") {
  1491.             $dat->{'args_byname'}{$currentargument}{'proto'} = $code;
  1492.         } else {
  1493.             $dat->{'args_byname'}{$currentargument}{'protof'} = $code;
  1494.         }
  1495.         } else {
  1496.         $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code;
  1497.         }
  1498.     }
  1499.     } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) ||
  1500.          (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) {
  1501.     # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>"
  1502.     # For boolean options <choice> is not given
  1503.     my $argname = $1;
  1504.     my $setting = $2;
  1505.     my $line = $3;
  1506.     my $bool = 0;
  1507.     if (!$line) {
  1508.         $line = $setting;
  1509.         $bool = 1;
  1510.     }
  1511.     # Make sure that the argument is in the data structure
  1512.     checkarg ($dat, $argname);
  1513.     # Make sure that the setting is in the data structure (enum options)
  1514.     if (!$bool) {
  1515.         checksetting ($dat, $argname, $setting);
  1516.         # Make sure that this argument has a default setting, even if 
  1517.         # none is defined in this PPD file
  1518.         if (!$dat->{'args_byname'}{$argname}{'default'}) {
  1519.         $dat->{'args_byname'}{$argname}{'default'} = $setting;
  1520.         }
  1521.     }
  1522.     # Store the value
  1523.     # Code string can have multiple lines, read all of them
  1524.     my $code = "";
  1525.     while ($line !~ m!\"!) {
  1526.         if ($line =~ m!&&$!) {
  1527.         # line continues in next line
  1528.         $code .= substr($line, 0, -2);
  1529.         } else {
  1530.         # line ends here
  1531.         $code .= "$line\n";
  1532.         }
  1533.         # Read next line
  1534.         $line = <PPD>;
  1535.         chomp $line;
  1536.     }
  1537.     $line =~ m!^([^\"]*)\"!;
  1538.     $code .= $1;
  1539.     if ($bool) {
  1540.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code);
  1541.     } else {
  1542.         $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code);
  1543.     }
  1544.     } elsif (m!^\*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix):\s*\"(.*)$!) {
  1545.     # "*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix): <code>"
  1546.     # The printer supports PJL/JCL when there is such a line 
  1547.     $dat->{'jcl'} = 1;
  1548.     my $item = $2;
  1549.     my $line = $3;
  1550.     # Store the value
  1551.     # Code string can have multiple lines, read all of them
  1552.     my $code = "";
  1553.     while ($line !~ m!\"!) {
  1554.         if ($line =~ m!&&$!) {
  1555.         # line continues in next line
  1556.         $code .= substr($line, 0, -2);
  1557.         } else {
  1558.         # line ends here
  1559.         $code .= "$line\n";
  1560.         }
  1561.         # Read next line
  1562.         $line = <PPD>;
  1563.         chomp $line;
  1564.     }
  1565.     $line =~ m!^([^\"]*)\"!;
  1566.     $code .= $1;
  1567.     if ($item eq 'Begin') {
  1568.         $jclbegin = unhexify($code);
  1569.         $jclprefix = "" if (!$jclprefixset) && ($jclbegin !~ /PJL/s);
  1570.     } elsif ($item eq 'ToPSInterpreter') {
  1571.         $jcltointerpreter = unhexify($code);
  1572.     } elsif ($item eq 'End') {
  1573.         $jclend = unhexify($code);
  1574.     } elsif ($item eq 'Prefix') {
  1575.         $jclprefix = unhexify($code);
  1576.         $jclprefixset = 1;
  1577.     }
  1578.     } elsif (m!^\*\% COMDATA \#(.*)$!) {
  1579.     # If we have an old Foomatic 2.0.x PPD file, collect its Perl data
  1580.     push (@datablob, $1);
  1581.     }
  1582. }
  1583. close PPD;
  1584.  
  1585. # If we have an old Foomatic 2.0.x PPD file use its Perl data structure
  1586. if ($#datablob >= 0) {
  1587.     print $logh "${added_lf}You are using an old Foomatic 2.0 PPD file, consider " .
  1588.     "upgrading.${added_lf}\n";
  1589.     my $VAR1;
  1590.     if (eval join('',@datablob)) {
  1591.     # Overtake default settings from the main structure of the PPD file
  1592.     for my $arg (@{$dat->{'args'}}) {
  1593.         if ($arg->{'default'}) {
  1594.         $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} = 
  1595.             $arg->{'default'};
  1596.         }
  1597.     }
  1598.     undef $dat;
  1599.     $dat = $VAR1;
  1600.     $dat->{'jcl'} = $dat->{'pjl'};
  1601.     } else {
  1602.     # Perl structure broken
  1603.     print $logh "${added_lf}Unable to evaluate datablob, print job may come " .
  1604.         "out incorrectly or not at all.${added_lf}\n";
  1605.     }
  1606. }
  1607.  
  1608.  
  1609.  
  1610. ## We do not need to parse the PostScript job when we don't have
  1611. ## any options. If we have options, we must check whether the
  1612. ## default settings from the PPD file are valid and correct them
  1613. ## if nexessary.
  1614.  
  1615. my $dontparse = 0;
  1616. if ((!defined(@{$dat->{'args'}})) ||
  1617.     ($#{$dat->{'args'}} < 0)) {
  1618.     # We don't have any options, so we do not need to parse the
  1619.     # PostScript data
  1620.     $dontparse = 1;
  1621. } else {
  1622.     # Let the default value of a boolean option being 0 or 1 instead of
  1623.     # "True" or "False", range-check the defaults of all options and
  1624.     # issue warnings if the values are not valid
  1625.     checkoptions($dat, 'default');
  1626.  
  1627.     # Adobe's PPD specs do not support numerical
  1628.     # options. Therefore the numerical options are mapped to
  1629.     # enumerated options in the PPD file and their characteristics
  1630.     # as a numerical option are stored in "*Foomatic..."
  1631.     # keywords. A default must be between the enumerated
  1632.     # fixed values. The default
  1633.     # value must be given by a "*FoomaticRIPDefault<option>:
  1634.     # <value>" line in the PPD file. But this value is only valid
  1635.     # if the "official" default given by a "*Default<option>:
  1636.     # <value>" line (it must be one of the enumerated values)
  1637.     # points to the enumerated value which is closest to this
  1638.     # value. This way a user can select a default value with a
  1639.     # tool only supporting PPD files but not Foomatic extensions.
  1640.     # This tool only modifies the "*Default<option>: <value>" line
  1641.     # and if the "*FoomaticRIPDefault<option>: <value>" had always
  1642.     # priority, the user's change in "*Default<option>: <value>"
  1643.     # would have no effect.
  1644.  
  1645.     for my $arg (@{$dat->{'args'}}) {
  1646.     if ($arg->{'fdefault'}) {
  1647.         if ($arg->{'default'}) {
  1648.         if ($arg->{'type'} =~ /^(int|float)$/) {
  1649.             if ($arg->{'fdefault'} < $arg->{'min'}) {
  1650.             $arg->{'fdefault'} = $arg->{'min'};
  1651.             }
  1652.             if ($arg->{'fdefault'} > $arg->{'max'}) {
  1653.             $arg->{'fdefault'} = $arg->{'max'};
  1654.             }
  1655.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  1656.             my $closestvalue;
  1657.             for my $val (@{$arg->{'vals'}}) {
  1658.             if (abs($arg->{'fdefault'} - $val->{'value'}) <
  1659.                 $mindiff) {
  1660.                 $mindiff = 
  1661.                 abs($arg->{'fdefault'} - $val->{'value'});
  1662.                 $closestvalue = $val->{'value'};
  1663.             }
  1664.             }
  1665.             if (($arg->{'default'} == $closestvalue) ||
  1666.             (abs($arg->{'default'} - $closestvalue) /
  1667.              $closestvalue < 0.001)) {
  1668.             $arg->{'default'} = $arg->{'fdefault'};
  1669.             }
  1670.         }
  1671.         } else {
  1672.         $arg->{'default'} = $arg->{'fdefault'};
  1673.         }
  1674.     }
  1675.     }
  1676. }
  1677.  
  1678. # Is our PPD for a CUPS raster driver
  1679. if (my $cupsfilter = $dat->{'cupsfilter'}{"application/vnd.cups-raster"}) {
  1680.  
  1681.     # Search filter in cupsfilterpath
  1682.     # The %Y is a placeholder for the option settings
  1683.     my $havefilter = 0;
  1684.     for (split(':', $cupsfilterpath)) {
  1685.     if (-x "$_/$cupsfilter") {
  1686.         $havefilter=1;
  1687.         $cupsfilter = "$_/$cupsfilter 0 '' '' 0 '%Y%X'";
  1688.         last;
  1689.     }
  1690.     }
  1691.  
  1692.     if (!$havefilter) {
  1693.  
  1694.     # We do not have the required filter, so we assume that
  1695.     # rendering this job is supposed to be done on a remote
  1696.     # server. So we do not define a renderer command line and
  1697.     # embed only the option settings (as we had a PostScript
  1698.     # printer). This way the settings are # taken into account
  1699.     # when the job is rendered on the server.
  1700.     print $logh "${added_lf}CUPS filter for this PPD file not found " .
  1701.         "assuming that job will be rendered on a remote server. Only " .
  1702.         "the PostScript of the options will be inserted into the " .
  1703.         "PostScript data stream.${added_lf}\n";
  1704.  
  1705.     } else {
  1706.  
  1707.     # use pstoraster script if available, otherwise run GhostScript
  1708.     # directly
  1709.     my $pstoraster = "pstoraster";
  1710.     my $havepstoraster = 0;
  1711.     for (split(':', $cupsfilterpath)) {
  1712.         if (-x "$_/$pstoraster") {
  1713.         $havepstoraster=1;
  1714.         $pstoraster = "$_/$pstoraster 0 '' '' 0 '%X'";
  1715.         last;
  1716.         }
  1717.     }
  1718.  
  1719.     if (!$havepstoraster) {
  1720.  
  1721.         # Build GhostScript command line
  1722.         $pstoraster = "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOMEDIAATTRS -sDEVICE=cups -sOutputFile=-%W -"
  1723.         
  1724.     }
  1725.  
  1726.     # build GhostScript/CUPS driver command line
  1727.     $dat->{'cmd'} = "$pstoraster | $cupsfilter";
  1728.  
  1729.     # Set environment variables
  1730.     $ENV{'PPD'} = $ppdfile;
  1731.     
  1732.     }
  1733. }
  1734.  
  1735. # Was the RIP command line defined in the PPD file? If not, we assume a
  1736. # PostScript printer and do not render/translate the input data
  1737. if (!defined($dat->{'cmd'})) {
  1738.     $dat->{'cmd'} = "cat%A%B%C%D%E%F%G%H%I%J%K%L%M%Z";
  1739.     if ($dontparse) {
  1740.     # No command line, no options, we have a raw queue, don't check
  1741.     # whether the input is PostScript and ignore the "docs" option,
  1742.     # simply pass the input data to the backend.
  1743.     $dontparse = 2;
  1744.     $model = "Raw queue";
  1745.     }
  1746. }
  1747.  
  1748.  
  1749.  
  1750. ## Summary for debugging
  1751. print $logh "${added_lf}Parameter Summary\n";
  1752. print $logh "-----------------${added_lf}\n";
  1753. print $logh "Spooler: $spooler\n";
  1754. print $logh "Printer: $printer\n";
  1755. print $logh "Shell: $modern_shell\n";
  1756. print $logh "PPD file: $ppdfile\n";
  1757. print $logh "ATTR file: $attrpath\n";
  1758. print $logh "Printer model: $model\n";
  1759. # Print the options string only in debug mode, Mac OS X adds very many
  1760. # options so that CUPS cannot handle the output of the option string
  1761. # in its log files. If CUPS encounters a line with more than 1024 characters
  1762. # sent into its log files, it aborts the job with an error.
  1763. if (($debug) || ($spooler ne 'cups')) {
  1764.     print $logh "Options: $optstr\n";
  1765. }
  1766. print $logh "Job title: $jobtitle\n";
  1767. print $logh "File(s) to be printed: ${added_lf}@filelist${added_lf}\n";
  1768. print $logh "GhostScript extra search path ('GS_LIB'): $ENV{'GS_LIB'}\n"
  1769.     if $ENV{'GS_LIB'};
  1770.  
  1771.  
  1772.  
  1773. ## Parse options from command line ($optstr)
  1774.  
  1775. # Before we start, save the defaults for printing documentation pages
  1776.  
  1777. copyoptions($dat, 'default', 'userval');
  1778.  
  1779.  
  1780. # The options are "foo='bar nut'", "foo", "nofoo", "'bar nut'", or
  1781. # "foo:'bar nut'" (when GPR was used) all with spaces between...
  1782. # In addition they can be preceeded by page ranges, separated with a
  1783. # colon.
  1784.  
  1785. my @opts;
  1786.  
  1787. # Variable for PPR's backend interface name (parallel, tcpip, atalk, ...)
  1788.  
  1789. my $backend = "";
  1790.  
  1791. # Array to collect unknown options so that they can get passed to the
  1792. # backend interface of PPR. For other spoolers we ignore them.
  1793.  
  1794. my @backendoptions = ();
  1795.  
  1796. # "foo='bar nut'"
  1797. while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+=[\'\"].*?[\'\"]) ?!!i) {
  1798.     push (@opts, $1);
  1799. }
  1800.  
  1801. # "foo:'bar nut'" (GPR separates option and setting with a colon ":")
  1802. while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+:[\'\"].*?[\'\"]) ?!!i) {
  1803. #while ($optstr =~ s!(\w+=[\'\"].*?[\'\"])!!i) {
  1804.     push (@opts, $1);
  1805. }
  1806.  
  1807. # "'bar nut'", "'foo=bar nut'", "'foo:bar nut'"
  1808. while ($optstr =~ s!([\'\"].+?[\'\"]) ?!!) {
  1809.     my $opt = $1;
  1810.     $opt =~ s/[\'\"]//g; # Make only sure that we didn't quote
  1811.                          # the option for a second time when we read
  1812.                          # rge options from the command line or
  1813.                          # environment variable
  1814.     push (@opts, $opt);
  1815.     
  1816. }
  1817.  
  1818. # "foo", "nofoo"
  1819. push(@opts, split(/ /,$optstr));
  1820.  
  1821. # Now actually process those pesky options...
  1822.  
  1823. for (@opts) {
  1824.     print $logh "Pondering option '$_'\n";
  1825.  
  1826.     # "docs" option to print help page
  1827.     if ((lc($_) =~ /^\s*docs\s*$/) ||
  1828.     (lc($_) =~ /^\s*docs\s*=\s*true\s*$/)) {
  1829.     # The second one is necessary becuase CUPS 1.1.15 or newer sees
  1830.     # "docs" as boolean option and modifies it to "docs=true"
  1831.         $do_docs = 1;
  1832.     next;
  1833.     }
  1834.  
  1835.     # "profile" option to supply a color correction profile to a
  1836.     # CUPS raster driver
  1837.     if (lc($_) =~ /^\s*profile=(\S+)\s*$/) {
  1838.     $cupscolorprofile=$1;
  1839.     $dat->{'cmd'} =~ s!\%X!profile=$cupscolorprofile!g;
  1840.     $dat->{'cmd'} =~ s!\%W! -c\"<</cupsProfile($cupscolorprofile)>>setpagedevice\"!g;
  1841.     next;
  1842.     }
  1843.  
  1844.     # Is the command line option limited to certain page ranges? If so,
  1845.     # mark the setting with a hash key containing the ranges
  1846.     my $optionset;
  1847.     if (s/^(even|odd|[\d,-]+)://i) {
  1848.     $optionset = "pages:$1";
  1849.     } else {
  1850.     $optionset = 'userval';
  1851.     }
  1852.  
  1853.     # Solaris options that have no reason to be
  1854.     if (/^nobanner$/ || /^dest=.+$/ || /^protocol=.+$/) {
  1855.         next;
  1856.     }
  1857.  
  1858.     my $arg;
  1859.     if ((m!([^=]+)=\'?(.*)\'?!) || (m!([^=:]+):\'?(.*)\'?!)) {
  1860.         my ($aname, $avalue) = ($1, $2);
  1861.  
  1862.     if (($optionset =~ /pages/) &&
  1863.         ($arg = argbyname($aname)) &&
  1864.         ((!defined($arg->{'section'})) ||
  1865.          ($arg->{'section'} !~ /^(Any|Page)Setup/))) {
  1866.         print $logh "This option is not a \"PageSetup\" or " .
  1867.         "\"AnySetup\" option, so it cannot be restricted to " .
  1868.         "a page range.\n";
  1869.         next;
  1870.     }
  1871.  
  1872.     # At first look for the "backend" option to determine the PPR
  1873.     # backend to use
  1874.     if (($aname =~ m!^backend$!i) && ($spooler eq 'ppr_int')) {
  1875.         # Backend interface name
  1876.         $backend = $avalue;
  1877.         } elsif ($aname =~ m!^media$!i) {
  1878.  
  1879.         # Standard arguments?
  1880.         # media=x,y,z
  1881.         # sides=one|two-sided-long|short-edge
  1882.  
  1883.         # Rummage around in the media= option for known media, source, 
  1884.         # etc types.
  1885.         # We ought to do something sensible to make the common manual
  1886.         # boolean option work when specified as a media= tray thing.
  1887.         # 
  1888.         # Note that this fails miserably when the option value is in
  1889.         # fact a number; they all look alike.  It's unclear how many
  1890.         # drivers do that.  We may have to standardize the verbose
  1891.         # names to make them work as selections, too.
  1892.  
  1893.             my @values = split(',',$avalue);
  1894.             for (@values) {
  1895.         my $val;
  1896.                 if ($dat->{'args_byname'}{'PageSize'} and
  1897.                     $val=valbyname($dat->{'args_byname'}{'PageSize'},$_)) {
  1898.                     $dat->{'args_byname'}{'PageSize'}{$optionset} = 
  1899.                         $val->{'value'};
  1900.             # Keep "PageRegion" in sync
  1901.             if ($dat->{'args_byname'}{'PageRegion'} and
  1902.             $val=valbyname($dat->{'args_byname'}{'PageRegion'},
  1903.                        $_)) {
  1904.             $dat->{'args_byname'}{'PageRegion'}{$optionset} = 
  1905.                 $val->{'value'};
  1906.             }
  1907.                 } elsif ($dat->{'args_byname'}{'PageSize'} 
  1908.              and /^Custom/) {
  1909.             $dat->{'args_byname'}{'PageSize'}{$optionset} = $_;
  1910.             # Keep "PageRegion" in sync
  1911.             if ($dat->{'args_byname'}{'PageRegion'}) {
  1912.             $dat->{'args_byname'}{'PageRegion'}{$optionset} = 
  1913.                 $_;
  1914.             }
  1915.                 } elsif ($dat->{'args_byname'}{'MediaType'} and
  1916.                          $val=valbyname($dat->{'args_byname'}{'MediaType'},
  1917.                     $_)) {
  1918.                     $dat->{'args_byname'}{'MediaType'}{$optionset} =
  1919.                         $val->{'value'};
  1920.                 } elsif ($dat->{'args_byname'}{'InputSlot'} and
  1921.                          $val=valbyname($dat->{'args_byname'}{'InputSlot'},
  1922.                     $_)) {
  1923.                     $dat->{'args_byname'}{'InputSlot'}{$optionset} = 
  1924.                         $val->{'value'};
  1925.                 } elsif (lc($_) eq 'manualfeed') {
  1926.                     # Special case for our typical boolean manual
  1927.                     # feeder option if we didn't match an InputSlot above
  1928.                     if (defined($dat->{'args_byname'}{'ManualFeed'})) {
  1929.                         $dat->{'args_byname'}{'ManualFeed'}{$optionset} = 1;
  1930.                     }
  1931.                 } else {
  1932.                     print $logh "Unknown \"media\" component: \"$_\".\n";
  1933.                 }
  1934.             }
  1935.         } elsif ($aname =~ m!^sides$!i) {
  1936.             # Handle the standard duplex option, mostly
  1937.             if ($avalue =~ m!^two-sided!i) {
  1938.                 if (defined($dat->{'args_byname'}{'Duplex'})) {
  1939.             # We set "Duplex" to '1' here, the real argument setting
  1940.             # will be done later
  1941.                     $dat->{'args_byname'}{'Duplex'}{$optionset} = '1';
  1942.             # Check the binding: "long edge" or "short edge"
  1943.             if ($avalue =~ m!long-edge!i) {
  1944.             if (defined($dat->{'args_byname'}{'Binding'})) {
  1945.                 $dat->{'args_byname'}{'Binding'}{$optionset} =
  1946.       $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'LongEdge'}{'value'};
  1947.             } else {
  1948.                 $dat->{'args_byname'}{'Duplex'}{$optionset} = 
  1949.                 'LongEdge';
  1950.             }
  1951.             } elsif ($avalue =~ m!short-edge!i) {
  1952.             if (defined($dat->{'args_byname'}{'Binding'})) {
  1953.                 $dat->{'args_byname'}{'Binding'}{$optionset} =
  1954.       $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'ShortEdge'}{'value'};
  1955.             } else {
  1956.                 $dat->{'args_byname'}{'Duplex'}{$optionset} = 
  1957.                 'ShortEdge';
  1958.             }
  1959.             }
  1960.                 }
  1961.             } elsif ($avalue =~ m!^one-sided!i) {
  1962.                 if (defined($dat->{'args_byname'}{'Duplex'})) {
  1963.             # We set "Duplex" to '0' here, the real argument setting
  1964.             # will be done later
  1965.                     $dat->{'args_byname'}{'Duplex'}{$optionset} = '0';
  1966.                 }
  1967.             }
  1968.  
  1969.             # We should handle the other half of this option - the
  1970.             # BindEdge bit.  Also, are there well-known ipp/cups
  1971.             # options for Collate and StapleLocation?  These may be
  1972.             # here...
  1973.  
  1974.         } else {
  1975.             # Various non-standard printer-specific options
  1976.             if ($arg = argbyname($aname)) {
  1977.         if (defined(my $newvalue =
  1978.             checkoptionvalue($dat, $aname, $avalue, 0))) {
  1979.             # If the choice is valid, use it, otherwise
  1980.             # ignore it.
  1981.             $arg->{$optionset} = $newvalue;
  1982.             # If this argument is PageSize or PageRegion,
  1983.             # also set the other
  1984.             syncpagesize($dat, $aname, $avalue, $optionset);
  1985.         } else {
  1986.             # Invalid choice, make log entry
  1987.             print $logh "Invalid choice $aname=$avalue.\n";
  1988.         }
  1989.             } elsif ($spooler eq 'ppr_int') {
  1990.                 # Unknown option, pass it to PPR's backend interface
  1991.         push (@backendoptions, "$aname=$avalue");
  1992.             } else {
  1993.         # Unknown option, make log entry
  1994.         print $logh "Unknown option $aname=$avalue.\n";
  1995.         }
  1996.         }
  1997.     } elsif (m!^([\d\.]+)x([\d\.]+)([A-Za-z]*)$!) {
  1998.     my ($w, $h, $u) = ($1, $2, $3);
  1999.     # Custom paper size
  2000.     if (($w != 0) && ($h != 0) &&
  2001.         ($arg=argbyname("PageSize")) &&
  2002.         (defined($arg->{'vals_byname'}{'Custom'}))) {
  2003.             $arg->{$optionset} = "Custom.${w}x${h}${u}";
  2004.         # Keep "PageRegion" in sync
  2005.         if ($dat->{'args_byname'}{'PageRegion'}) {
  2006.         $dat->{'args_byname'}{'PageRegion'}{$optionset} = 
  2007.             $arg->{$optionset};
  2008.         }
  2009.     }
  2010.     } elsif ((m!^\s*no(.+)\s*$!i) and ($arg=argbyname($1))) {
  2011.         # standard bool args:
  2012.         # landscape; what to do here?
  2013.         # duplex; we should just handle this one OK now?
  2014.     $arg->{$optionset} = 0;
  2015.     } elsif (m!^\s*(.+)\s*$!) {
  2016.         if ($arg=argbyname($1)) {
  2017.             $arg->{$optionset} = 1;
  2018.         } else {
  2019.             print $logh "Unknown boolean option \"$1\".\n";
  2020.         }
  2021.     }
  2022. }
  2023. $do_docs = 1 if( $show_docs );
  2024.  
  2025.  
  2026. ## Were we called to build the PDQ driver declaration file?
  2027. my @pdqfile;
  2028. if ($genpdqfile) {
  2029.     @pdqfile = buildpdqdriver($dat, 'userval');
  2030.     open PDQFILE, $genpdqfile or
  2031.     rip_die("Cannot write PDQ driver declaration file",
  2032.         $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  2033.     print PDQFILE join('', @pdqfile);
  2034.     close PDQFILE;
  2035.     exit $EXIT_PRINTED;
  2036. }
  2037.  
  2038.  
  2039.  
  2040. ## Set the $postpipe
  2041.  
  2042. # $postpipe when running as a PPR RIP
  2043. if ($spooler eq 'ppr') {
  2044.     # The PPR RIP sends the data output to /dev/fd/3 instead of to STDOUT
  2045.     if (-w "/dev/fd/3") {
  2046.     $postpipe = "| cat - > /dev/fd/3";
  2047.     } else {
  2048.     $postpipe = "| cat - >&3";
  2049.     }
  2050. }
  2051.  
  2052. # Set up PPR backend (if we run as a PPR interface).
  2053. if ($spooler eq 'ppr_int') {
  2054.  
  2055.     # Is the chosen backend installed and executable
  2056.     if (!-x "interfaces/$backend") {
  2057.     my $pwd = cwd;
  2058.     print $logh "The backend interface $pwd/interfaces/$backend " .
  2059.         "does not exist/is not executable!\n";
  2060.     rip_die ("The backend interface $pwd/interfaces/$backend " .
  2061.          "does not exist/is not executable!",
  2062.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  2063.     }
  2064.  
  2065.     # foomatic-rip cannot use foomatic-rip as backend
  2066.     if ($backend eq "foomatic-rip") {
  2067.     print $logh "\"foomatic-rip\" cannot use itself as backend " .
  2068.         "interface!\n";
  2069.     ppr_die ($ppr_printer,
  2070.          "\"foomatic-rip\" cannot use itself as backend interface!",
  2071.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  2072.     }
  2073.  
  2074.     # Put the backend interface into the $postpipe
  2075.     $postpipe = "| ( interfaces/$backend \"$ppr_printer\" ".
  2076.     "\"$ppr_address\" \"" . join(" ",@backendoptions) .
  2077.     "\" \"$ppr_jobbreak\" \"$ppr_feedback\" " .
  2078.     "\"$ppr_codes\" \"$ppr_jobname\" \"$ppr_routing\" " .
  2079.     "\"$ppr_for\" \"\" )";
  2080.  
  2081. }
  2082.  
  2083. # CUPS and PDQ have their own backends, they do not need a $postpipe
  2084. if (($spooler eq 'cups') || ($spooler eq 'pdq')) {
  2085.     # No $postpipe for CUPS or PDQ, even if one is defined in the PPD file
  2086.     $postpipe = "";
  2087. }
  2088.  
  2089. # CPS needs always a $postpipe, set the default one for local printing
  2090. # if none is set
  2091. if (($spooler eq 'cps') && !$postpipe) {
  2092.     $postpipe = "| cat - > \$LPDDEV";
  2093. }
  2094.  
  2095. if ($postpipe) {
  2096.     print $logh "${added_lf}Output will be redirected to:\n$postpipe${added_lf}\n";
  2097. }
  2098.  
  2099.  
  2100.  
  2101. ## Print documentation page when asked for
  2102. my ($docgeneratorhandle, $docgeneratorpid,$retval);
  2103. if ($do_docs) {
  2104.     # Don't print the supplied files, STDIN will be redirected to the
  2105.     # documentation page generator
  2106.     @filelist = ("<STDIN>");
  2107.     # Start the documentation page generator
  2108.     ($docgeneratorhandle, $docgeneratorpid) =
  2109.     getdocgeneratorhandle($dat);
  2110.     if ($retval != $EXIT_PRINTED) {
  2111.     rip_die ("Error opening documentation page generator",
  2112.          $retval);
  2113.     }
  2114.     # Read the further data from the documentation page generator and
  2115.     # not from STDIN
  2116.     if (!close STDIN && $! != $ESPIPE) {
  2117.     rip_die ("Couldn't close STDIN",
  2118.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  2119.     }
  2120.     if (!open (STDIN, "<&$docgeneratorhandle")) {
  2121.     rip_die ("Couldn't dup \$docgeneratorhandle",
  2122.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  2123.     }
  2124.     if( $show_docs ){
  2125.         while( <$docgeneratorhandle> ){
  2126.             print;
  2127.         }
  2128.         exit(0);
  2129.     }
  2130. }
  2131.  
  2132.  
  2133.  
  2134.  
  2135. ## In debug mode save the data supposed to be fed into the
  2136. ## renderer also into a file, reset the file here
  2137.  
  2138. if ($debug) {
  2139.     modern_system("> ${logfile}.ps");
  2140. }
  2141.  
  2142.  
  2143.         
  2144. ## From here on we have to repeat all the rest of the program for
  2145. ## every file to print
  2146.  
  2147. for $file (@filelist) {
  2148.  
  2149.     print $logh
  2150. "${added_lf}================================================\n${added_lf}".
  2151. "File: $file\n${added_lf}" .
  2152. "================================================\n${added_lf}";
  2153.  
  2154.  
  2155.  
  2156.     ## If we do not print standard input, open the file to print
  2157.     if ($file ne "<STDIN>") {
  2158.     if (! -r $file) {
  2159.         print $logh "File $file missing or not readable, skipping.\n";
  2160.         next;
  2161.     }
  2162.         close STDIN;
  2163.         open STDIN, "< $file" || do {
  2164.         print $logh "Cannot open $file, skipping.\n";
  2165.         next;
  2166.     }
  2167.     }
  2168.  
  2169.  
  2170.  
  2171.     ## Do we have a raw queue
  2172.     if ($dontparse == 2) {
  2173.     # Raw queue, simply pass the input into the $postpipe (or to STDOUT
  2174.     # when there is no $postpipe)
  2175.     print $logh "Raw printing, executing \"cat $postpipe\"${added_lf}\n";
  2176.     modern_system("cat $postpipe");
  2177.     next;
  2178.     }
  2179.  
  2180.  
  2181.  
  2182.     ## First, for arguments with a default, stick the default in as
  2183.     ## the initial value for the "header" option set, this option set
  2184.     ## consists of the PPD defaults, the options specified on the
  2185.     ## command line, and the options set in the header part of the
  2186.     ## PostScript file (all before the first page begins).
  2187.  
  2188.     copyoptions($dat, 'userval', 'header');
  2189.  
  2190.  
  2191.  
  2192.     ## Next, examine the PostScript job for traces of command-line and
  2193.     ## JCL options. PPD-aware applications and spoolers stuff option
  2194.     ## settings directly into the file, they do not necessarily send
  2195.     ## PPD options by the command line. Also stuff in PostScript code
  2196.     ## to apply option settings given by the command line and to set
  2197.     ## the defaults given in the PPD file.
  2198.  
  2199.     # Examination strategy: read lines from STDIN until the first
  2200.     # %%Page: comment appears and save them as @psheader. This is the
  2201.     # page-independent header part of the PostScript file. The
  2202.     # PostScript interpreter (renderer) must execute this part once
  2203.     # before rendering any assortment of pages. Then pages can be
  2204.     # printed in any arbitrary selection or order. All option
  2205.     # settings we find here will be collected in the default option
  2206.     # set for the RIP command line.
  2207.  
  2208.     # Now the pages will be read and sent to the renderer, one after
  2209.     # the other. Every page is read into memory until the
  2210.     # %%EndPageSetup comment appears (or a certain amount of lines was
  2211.     # read). So we can get option settings only valid for this
  2212.     # page. If we have such settings we set them in the modified
  2213.     # command set for this page.
  2214.  
  2215.     # If the renderer is not running yet (first page) we start it with
  2216.     # the command line built from the current modified command set and
  2217.     # send the first page to it, in the end we leave the renderer
  2218.     # running and keep input and output pipes open, so that it can
  2219.     # accept further pages. If the renderer is still running from
  2220.     # the previous page and the current modified command set is the
  2221.     # same as the one for the previous page, we send the page. If
  2222.     # the command set is different, we close the renderer, re-start
  2223.     # it with the command line built from the new modified command
  2224.     # set, send the header again, and then the page.
  2225.  
  2226.     # After the last page the trailer (%%Trailer) is sent.
  2227.  
  2228.     # The output pipe of this program stays open all the time so that
  2229.     # the spooler does not assume that the job has finished when the
  2230.     # renderer is re-started.
  2231.  
  2232.     # Non DSC-conforming documents will be read until a certain line
  2233.     # number is reached. Command line or JCL options inserted later
  2234.     # will be ignored.
  2235.  
  2236.     # If options are implemented by PostScript code supposed to be
  2237.     # stuffed into the job's PostScript data we stuff the code for all
  2238.     # these options into our job data, So all default settings made in
  2239.     # the PPD file (the user can have edited the PPD file to change
  2240.     # them) are taken care of and command line options get also
  2241.     # applied. To give priority to settings made by applications we
  2242.     # insert the options's code in the beginnings of their respective
  2243.     # sections, so that sommething, which is already inserted, gets
  2244.     # executed after our code. Missing sections are automatically
  2245.     # created. In non-DSC-conforming files we insert the option code
  2246.     # in the beginning of the file. This is the same policy as used by
  2247.     # the "pstops" filter of CUPS.
  2248.  
  2249.     # If CUPS is the spooler, the option settings were already
  2250.     # inserted by the "pstops" filter, so we don't insert them
  2251.     # again. The only thing we do is correcting settings of numerical
  2252.     # options when they were set to a value not available as choice in
  2253.     # the PPD file, As "pstops" does not support "real" numerical
  2254.     # options, it sees these settings as an invalid choice and stays
  2255.     # with the default setting. In this case we correct the setting in
  2256.     # the first occurence of the option's code, as this one is the one
  2257.     # added by CUPS, later occurences come from applications and
  2258.     # should not be touched.
  2259.  
  2260.     # If the input is not PostScript (if there is no "%!" after
  2261.     # $maxlinestopsstart lines) a file conversion filter will
  2262.     # automatically be applied to the incoming data, so that we will
  2263.     # process the resulting PostScript here. This way we have always
  2264.     # PostScript data here and so we can apply the printer/driver
  2265.     # features described in the PPD file.
  2266.  
  2267.     # Supported file conversion filters are "a2ps", "enscript",
  2268.     # "mpage", and spooler-specific filters. All filters convert
  2269.     # plain text to PostScript, "a2ps" also other formats. The
  2270.     # conversion filter is always used when one prints the
  2271.     # documentation pages, as they are created as plain text,
  2272.     # when CUPS is the spooler "pstops" is executed after the
  2273.     # filter so that the default option settings from the PPD file
  2274.     # and CUPS-specific options as N-up get applied. On regular
  2275.     # printouts one gets always PostScript when CUPS or PPR is
  2276.     # the spooler, so the filter is only used for regular
  2277.     # printouts under LPD, LPRng, GNUlpr or without spooler.
  2278.  
  2279.     my $maxlines = 1000;            # Maximum number of lines to be read
  2280.                                     # when the documenent is not
  2281.                                     # DSC-conforming. "$maxlines = 0"
  2282.                                     # means that all will be read
  2283.                                     # and examined. If it is
  2284.                                     # discovered that the input file
  2285.                                     # is DSC-conforming, this will
  2286.                                     # be set to 0.
  2287.  
  2288.     my $maxlinestopsstart = 200;    # That many lines are allowed until the
  2289.                                     # "%!" indicating PS comes. These
  2290.                                     # additional lines in the
  2291.                                     # beginning are usually JCL
  2292.                                     # commands. The lines will be
  2293.                                     # ignored by our parsing but
  2294.                                     # passed through.
  2295.  
  2296.     my $maxlinesforpageoptions=200; # Unfortunately, CUPS does not bracket
  2297.                                     # "PageSetup" option with
  2298.                                     # "%%BeginPageSetup" and
  2299.                                     # "%%EndPageSetup", so the options
  2300.                                     # can simply stand after the
  2301.                                     # page header and before the
  2302.                                     # page code, without special
  2303.                                     # marking. So buffer this amount
  2304.                                     # of lines before printing the
  2305.                                     # page to check for options.
  2306.  
  2307.     my $maxnondsclinesinheader=1000; # If there is a block of more lines
  2308.                                     # than this in the document
  2309.                                     # header which is not in the
  2310.                                     # "%%BeginProlog...%%EndProlog"
  2311.                                     # or
  2312.                                     # "%%BeginSetup...%%EndSetup"
  2313.                                     # sections, the document is not
  2314.                                     # considered as DSC-conforming
  2315.                                     # and the rest gets passed
  2316.                                     # through to the renderer without
  2317.                                     # further parsing for options.
  2318.  
  2319.     my $nondsclines = 0;            # Amount of lines found which are not in
  2320.                                     # a section (see 
  2321.                                     # $maxnondsclinesinheader).
  2322.  
  2323.     my $nonpslines = 0;             # lines before "%!" found yet.
  2324.  
  2325.     my $more_stuff = 1;             # there is more stuff in stdin.
  2326.  
  2327.     my $linect = 0;                 # how many lines have we examined?
  2328.  
  2329.     my $onelinebefore = "";         # The line before the current line
  2330.                                     # (Non-DSC comments are ignored)
  2331.  
  2332.     my $twolinesbefore = "";        # The line two lines before the current 
  2333.                                     # line (Non-DSC comments are ignored)
  2334.  
  2335.     my $linesafterlastbeginfeature = ""; # All code lines after the last
  2336.                                     # "%%BeginFeature:"
  2337.  
  2338.     my @psheader = ();              # The header of the PostScript file, 
  2339.                                     # to be sent after each start of the
  2340.                                     # renderer
  2341.  
  2342.     my @psfifo = ();                # The input FIFO, data which we have
  2343.                                     # pulled from stdin for examination,
  2344.                                     # but not sent to the renderer yet.
  2345.  
  2346.     my $passthru = 0;               # 0: write data into @psfifo; 1: pass
  2347.                                     # data directly to the renderer
  2348.  
  2349.     my $isdscjob = 0;               # Is the job DSC conforming
  2350.  
  2351.     my $inheader = 1;               # Are we still in the header, before
  2352.                                     # first "%%Page:" comment?
  2353.  
  2354.     my $optionset = 'header';       # Where do the option settings, which 
  2355.                                     # we have found, go?
  2356.  
  2357.     my $optionsalsointoheader = 0;  # 1: We are in a "%%BeginSetup...
  2358.                                     # %%EndSetup" section after the first
  2359.                                     # "%%Page:..." line (OpenOffice.org
  2360.                                     # does this and intends the options here
  2361.                                     # apply to the whole document and not
  2362.                                     # only to the current page). We have to
  2363.                                     # add all lines also to the end of the
  2364.                                     # @psheader now and we have to set
  2365.                                     # non-PostScript options also in the
  2366.                                     # "header" optionset. 0: otherwise.
  2367.  
  2368.     my $nestinglevel = 0;           # Are we in the main document (0) or
  2369.                                     # in an embedded document bracketed by
  2370.                                     # "%%BeginDocument" and "%%EndDocument"
  2371.                                     # (>0) We do not parse the PostScript
  2372.                                     # in an embedded document.
  2373.  
  2374.     my $inpageheader = 0;           # Are we in the header of a page,
  2375.                                     # between "%%BeginPageSetup" and
  2376.                                     # "%%EndPageSetup" (1) or not (0).
  2377.  
  2378.     my $lastpassthru = 0;           # State of $passthru in previous line
  2379.                                     # (to allow debug output when $passthru
  2380.                                     # switches.
  2381.  
  2382.     my $ignorepageheader = 0;       # Will be set to 1 as soon as active 
  2383.                                     # code (not between "%%BeginPageSetup" 
  2384.                                     # and "%%EndPageSetup") appears after a
  2385.                                     # "%%Page:" comment. In this case
  2386.                                     # "%%BeginPageSetup" and
  2387.                                     # "%%EndPageSetup" is not allowed any 
  2388.                                     # more on this page and will be ignored.
  2389.                                     # Will be set to 0 when a new "%%Page:" 
  2390.                                     # comment appears.
  2391.  
  2392.     my $printprevpage = 0;          # We set this when encountering
  2393.                                     # "%%Page:" and the previous page is not
  2394.                                     # printed yet. Then it will be printed and 
  2395.                                     # the new page will be prepared in the
  2396.                                     # next run of the loop (we don't read a
  2397.                                     # new line and don't increase the
  2398.                                     # $linect then).
  2399.  
  2400.     $fileconverterhandle = undef;   # File handle to the fileconverter process
  2401.  
  2402.     $fileconverterpid = 0;          # PID of the fileconverter process
  2403.  
  2404.     $rendererhandle = undef;        # File handle to the renderer process
  2405.  
  2406.     $rendererpid = 0;               # PID of the renderer process
  2407.  
  2408.     my $prologfound = 0;            # Did we find the
  2409.                                     # "%%BeginProlog...%%EndProlog" section?
  2410.  
  2411.     my $setupfound = 0;             # Did we find the
  2412.                                     # "%%BeginSetup...%%EndSetup" section?
  2413.  
  2414.     my $pagesetupfound = 0;         # special page setup handling needed
  2415.  
  2416.     my $inprolog = 0;               # We are between "%%BeginProlog" and
  2417.                                     # "%%EndProlog".
  2418.  
  2419.     my $insetup = 0;                # We are between "%%BeginSetup" and
  2420.                                     # "%%EndSetup".
  2421.  
  2422.     my $infeature = 0;              # We are between "%%BeginFeature" and
  2423.                                     # "%%EndFeature".
  2424.  
  2425.     my $postscriptsection = 'jclsetup'; # In which section of the PostScript
  2426.                                     # file are we currently?
  2427.  
  2428.     $nondsclines = 0;            # Number of subsequent lines found which
  2429.                                     # are at a non-DSC-conforming place,
  2430.                                     # between the sections of the header.
  2431.  
  2432.     my $optionreplaced = 0;         # Will be set to 1 when we are in an
  2433.                                     # option ("%%BeginFeature...
  2434.                                     # %%EndFeature") which we have replaced.
  2435.  
  2436.     $jobhasjcl = 0;                 # When the job does not start with
  2437.                                     # PostScript directly, but is a
  2438.                                     # PostScript job, we set this to 1
  2439.                                     # to avoid adding the JCL options
  2440.                                     # for the second time.
  2441.  
  2442.     my $insertoptions = 1;          # If we find out that a file with
  2443.                                     # a DSC magic string
  2444.                                     # ("%!PS-Adobe-") is not really
  2445.                                     # DSC-conforming, we insert the
  2446.                                     # options directly after the line
  2447.                                     # with the magic string. We use
  2448.                                     # this variable to store the
  2449.                                     # number of the line with the
  2450.                                     # magic string.
  2451.  
  2452.     my $currentpage = 0;            # The page which we are currently
  2453.                                     # printing.
  2454.  
  2455.     my $ooo110 = 0;                 # Flag to work around an application 
  2456.                                     # bug.
  2457.  
  2458.     my $saved = 0;                  # DSC line not processed yet
  2459.     
  2460.     if ($dontparse) {
  2461.     # We do not parse the PostScript to find Foomatic options, we check
  2462.     # only whether we have PostScript.
  2463.     $maxlines = 1;
  2464.     }
  2465.  
  2466.     print $logh "Reading PostScript input ...\n";
  2467.  
  2468.     my $line;                       # Line to be read from stdin
  2469.     do {
  2470.     my $ignoreline = 0;         # Comment line to be ignored when
  2471.                                 # determining the last active line 
  2472.                                 # and the one before the last
  2473.  
  2474.     if (($printprevpage) || ($saved) || ($line=<STDIN>)) {
  2475.             $saved = 0;
  2476.  
  2477.         if ($linect == $nonpslines) {
  2478.         # In the beginning should be the postscript leader,
  2479.                 # sometimes after some JCL commands
  2480.         if ($line !~ m/^.?%!/) { # There can be a Windows control 
  2481.                                  # character before "%!"
  2482.             $nonpslines ++;
  2483.             if ($maxlines == $nonpslines) {
  2484.             $maxlines ++;
  2485.             }
  2486.             $jobhasjcl = 1;
  2487.             if ($nonpslines > $maxlinestopsstart) {
  2488.             # This is not a PostScript job, we must convert it
  2489.             print $logh "${added_lf}Job does not start with \"%!\", " . 
  2490.                  "is it PostScript?\n" .
  2491.                  "Starting file converter\n";
  2492.             # Reset all variables but conserve the data which
  2493.             # we have already read.
  2494.             $jobhasjcl = 0;
  2495.             $linect =  0;
  2496.             $nonpslines = 1; # Take into account that the line
  2497.                              # of this run of the loop will be
  2498.                              # put into @psheader, so the
  2499.                              # first line read by the file
  2500.                              # converter is already the second
  2501.                              # line.
  2502.             $maxlines = 1001;
  2503.             $onelinebefore = "";
  2504.             $twolinesbefore = "";
  2505.             my $alreadyread = join('', @psheader, @psfifo) . 
  2506.                 $line;
  2507.             $line = "";
  2508.             @psheader = ();
  2509.             @psfifo = ();
  2510.             # Start the file conversion filter
  2511.             if (!$fileconverterpid) {
  2512.                 ($fileconverterhandle, $fileconverterpid) =
  2513.                 getfileconverterhandle
  2514.                 ($dat, $alreadyread);
  2515.                 if ($retval != $EXIT_PRINTED) {
  2516.                 rip_die ("Error opening file converter",
  2517.                      $retval);
  2518.                 }
  2519.             } else {
  2520.                 rip_die("File conversion filter probably " .
  2521.                     "crashed",
  2522.                     $EXIT_JOBERR);
  2523.             }
  2524.             # Read the further data from the file converter and
  2525.             # not from STDIN
  2526.             if (!close STDIN && $! != $ESPIPE) {
  2527.                 rip_die ("Couldn't close STDIN",
  2528.                      $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  2529.             }
  2530.             if (!open (STDIN, "<&$fileconverterhandle")) {
  2531.                 rip_die ("Couldn't dup \$fileconverterhandle",
  2532.                      $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  2533.             }
  2534.             }
  2535.         } else {
  2536.             # Do we have a DSC-conforming document?
  2537.             if ($line =~ m/^.?%!PS-Adobe-/) {
  2538.             # Do not stop parsing the document
  2539.             if (!$dontparse) {
  2540.                 $maxlines = 0;
  2541.                 $isdscjob = 1;
  2542.                 $insertoptions = $linect + 1;
  2543.                 # We have written into @psfifo before,
  2544.                 # now we continue in @psheader and move
  2545.                 # over the data which is already in @psfifo
  2546.                 push (@psheader, @psfifo);
  2547.                 @psfifo = ();
  2548.             }
  2549.             print $logh 
  2550.                 "--> This document is DSC-conforming!\n";
  2551.             } else {
  2552.             # Job is not DSC-conforming, stick in all PostScript
  2553.             # option settings in the beginning
  2554.             $line .= makeprologsection($dat, $optionset, 1);
  2555.             $line .= makesetupsection($dat, $optionset, 1);
  2556.             $line .= makepagesetupsection($dat, $optionset, 1);
  2557.             $prologfound = 1;
  2558.             $setupfound = 1;
  2559.             $pagesetupfound = 1;
  2560.             }
  2561.         }
  2562.         } else {
  2563.         if ($line =~ /^\%\%/) {
  2564.             if ($line =~ m/^\s*\%\%BeginDocument[: ]/) {
  2565.             # Beginning of an embedded document
  2566.             # Note that Adobe Acrobat has a bug and so uses
  2567.             # "%%BeginDocument " instead of "%%BeginDocument:"
  2568.             $nestinglevel ++;
  2569.             print $logh "Embedded document, " .
  2570.                 "nesting level now: $nestinglevel\n";
  2571.             } elsif (($line =~ m/^\s*\%\%EndDocument/) &&
  2572.                  ($nestinglevel > 0)) {
  2573.             # End of an embedded document
  2574.             $nestinglevel --;
  2575.             print $logh "End of Embedded document, " .
  2576.                 "nesting level now: $nestinglevel\n";
  2577.             } elsif (($line =~ m/^\s*\%\%Creator[: ](.*)$/) &&
  2578.                  ($nestinglevel == 0)) {
  2579.             # Here we set flags to treat particular bugs of the
  2580.             # PostScript produced by certain applications
  2581.             my $creator = $1;
  2582.             if ($creator =~ /^\s*OpenOffice.org\s+1.1.\d+\s*$/) {
  2583.                 # OpenOffice.org 1.1.x
  2584.                 # The option settings supposed to affect the
  2585.                 # whole document are put into the "%%PageSetup"
  2586.                 # section of the first page
  2587.                 print $logh "Document created with " .
  2588.                 "OpenOffice.org 1.1.x\n";
  2589.                 $ooo110 = 1;
  2590.             }
  2591.             } elsif (($line =~ m/^\%\%BeginProlog/) &&
  2592.                  ($nestinglevel == 0)) {
  2593.             # Note: Below is another place where a "Prolog"
  2594.             # section start will be considered. There we assume
  2595.             # start of the "Prolog" if the job is DSC-Conformimg,
  2596.             # but an arbitrary comment starting with "%%Begin", but
  2597.             # not a comment explicitly treated here, is found. This
  2598.             # is done because many "dvips" (TeX/LaTeX) files miss
  2599.             # the "%%BeginProlog" comment.
  2600.             # Beginning of Prolog
  2601.             print $logh "${added_lf}-----------\nFound: \%\%BeginProlog\n";
  2602.             $inprolog = 1;
  2603.             $postscriptsection = 'prolog' if $inheader;
  2604.             $nondsclines = 0;
  2605.             # Insert options for "Prolog"
  2606.             if (!$prologfound) {
  2607.                 $line .= makeprologsection($dat, $optionset, 0);
  2608.             }
  2609.             $prologfound = 1;
  2610.             } elsif (($line =~ m/^\%\%EndProlog/) &&
  2611.                  ($nestinglevel == 0)) {
  2612.             # End of Prolog
  2613.             print $logh "Found: \%\%EndProlog\n";
  2614.             $inprolog = 0;
  2615.             $insertoptions = $linect + 1;
  2616.             } elsif (($line =~ m/^\%\%BeginSetup/) &&
  2617.                  ($nestinglevel == 0)) {
  2618.             # Beginning of Setup
  2619.             print $logh "${added_lf}-----------\nFound: \%\%BeginSetup\n";
  2620.             $insetup = 1;
  2621.             # We need to distinguish with the $inheader variable
  2622.             # here whether we are in the header or on a page, as
  2623.             # OpenOffice.org inserts a "%%BeginSetup...%%EndSetup"
  2624.             # section after the first "%%Page:..." line and assumes
  2625.             # this section to be valid for all pages.
  2626.             $postscriptsection = 'setup' if $inheader;
  2627.             $nondsclines = 0;
  2628.             if ($inheader) {
  2629.                 # If there was no "Prolog" but there are
  2630.                 # options for the "Prolog", push a "Prolog"
  2631.                 # with these options onto the @psfifo here
  2632.                 if (!$prologfound) {
  2633.                 # "Prolog" missing, insert it here
  2634.                 $line =
  2635.                     makeprologsection($dat, $optionset, 1) .
  2636.                     $line;
  2637.                 # Now we have a "Prolog"
  2638.                 $prologfound = 1;
  2639.                 }
  2640.                 # Insert options for "DocumentSetup" or "AnySetup"
  2641.                 if ($spooler ne 'cups') {
  2642.                 # For non-CUPS spoolers or no spooler at all, 
  2643.                 # we leave everything as it is.
  2644.                 if (!$setupfound) {
  2645.                     $line .= 
  2646.                     makesetupsection($dat, $optionset, 0);
  2647.                 }
  2648.                 $setupfound = 1;
  2649.                 }
  2650.             } else {
  2651.                 # Found option settings must be stuffed into both
  2652.                 # the header and the currrent page now. They will
  2653.                 # be written into both the "header" and the
  2654.                 # "currentpage" optionsets and the PostScript code
  2655.                 # lines of this section will not only go into the
  2656.                 # output stream, but also added to the end of the
  2657.                 # @psheader, so that they get repeated (to preserve
  2658.                 # the embedded PostScript option settings) on a 
  2659.                 # restart of the renderer due to command line 
  2660.                 # option changes
  2661.                 $optionsalsointoheader = 1;
  2662.                 print $logh "\"%%BeginSetup\" in page header\n";
  2663.             }
  2664.             } elsif (($line =~ m/^\%\%EndSetup/) &&
  2665.                  ($nestinglevel == 0)) {
  2666.             # End of Setup
  2667.             print $logh "Found: \%\%EndSetup\n";
  2668.             $insetup = 0;
  2669.             if ($inheader) {
  2670.                 if ($spooler eq 'cups') {
  2671.                 # In case of CUPS, we must insert the
  2672.                 # accounting stuff just before the
  2673.                 # %%EndSetup comment in order to leave any
  2674.                 # EndPage procedures that have been
  2675.                 # defined by either the pstops filter or
  2676.                 # the PostScript job itself fully
  2677.                 # functional.
  2678.                 if (!$setupfound) {
  2679.                     $line = makesetupsection($dat, 
  2680.                                  $optionset, 0) . 
  2681.                                  $line;     
  2682.                 }
  2683.                 $setupfound = 1;
  2684.                 }
  2685.                 $insertoptions = $linect + 1;
  2686.             } else {
  2687.                 # The "%%BeginSetup...%%EndSetup" which
  2688.                 # OpenOffice.org has inserted after the first
  2689.                 # "%%Page:..." line ends here, so the following
  2690.                 # options go only onto the current page again
  2691.                 $optionsalsointoheader = 0;
  2692.             }
  2693.             } elsif (($line =~ m/^\%\%Page:(.*)$/) &&
  2694.                  ($nestinglevel == 0)) {
  2695.             if ((!$lastpassthru) && (!$inheader)) {
  2696.                 # In the last line we were not in passthru mode,
  2697.                 # so the last page is not printed. Prepare to do
  2698.                 # it now.
  2699.                 $printprevpage = 1;
  2700.                 # Print the previous page
  2701.                 $passthru = 1;
  2702.                 print $logh "New page found but previous not " . 
  2703.                 "printed, print it now.\n";
  2704.             } else {
  2705.                 # The previous page is printed, so we can prepare
  2706.                 # the current one
  2707.                 $printprevpage = 0;
  2708.                 print $logh "${added_lf}-----------\nNew page: $1\n";
  2709.                 # Count pages
  2710.                 $currentpage ++;
  2711.                 # We consider the beginning of the page already as
  2712.                 # page setup section, as some apps do not use
  2713.                 # "%%PageSetup" tags.
  2714.                 $postscriptsection = 'pagesetup';
  2715.                 # Save PostScript state before beginning the page
  2716.                 #$line .= "/foomatic-saved-state save def\n";
  2717.                 # Here begins a new page
  2718.                 if ($inheader) {
  2719.                 # Here we add some stuff which still belongs
  2720.                 # into the header
  2721.                 my $stillforheader;
  2722.                 # If there was no "Setup" but there are
  2723.                 # options for the "Setup", push a "Setup"
  2724.                 # with these options onto the @psfifo here
  2725.                 if (!$setupfound) {
  2726.                     # "Setup" missing, insert it here
  2727.                     $stillforheader = 
  2728.                     makesetupsection($dat, $optionset, 1) .
  2729.                     $stillforheader;
  2730.                     # Now we have a "Setup"
  2731.                     $setupfound = 1;
  2732.                 }
  2733.                 # If there was no "Prolog" but there are
  2734.                 # options for the "Prolog", push a "Prolog"
  2735.                 # with these options onto the @psfifo here
  2736.                 if (!$prologfound) {
  2737.                     # "Prolog" missing, insert it here
  2738.                     $stillforheader = 
  2739.                     makeprologsection($dat, $optionset,
  2740.                               1) .
  2741.                     $stillforheader;
  2742.                     # Now we have a "Prolog"
  2743.                     $prologfound = 1;
  2744.                 }
  2745.                 # Now we push this onto the header
  2746.                 push (@psheader, $stillforheader);
  2747.                 # The first page starts, so the header ends
  2748.                 $inheader = 0;
  2749.                 $nondsclines = 0;
  2750.                 # Option setting should go into the
  2751.                 # page-specific option set now
  2752.                 $optionset = 'currentpage';
  2753.                 } else {
  2754.                 # Restore PostScript state after completing the
  2755.                 # previous page:
  2756.                 # 
  2757.                 #   foomatic-saved-state restore
  2758.                 #   %%Page: ...
  2759.                 #   /foomatic-saved-state save def
  2760.                 #
  2761.                 # Print this directly, so that if we need to
  2762.                 # restart the renderer for this page due to
  2763.                 # a command line change this is done under the
  2764.                 # old instance of the renderer
  2765.                 #print $rendererhandle
  2766.                 #    "foomatic-saved-state restore\n";
  2767.  
  2768.                 # Save the option settings of the previous page
  2769.                 copyoptions($dat, 'currentpage',
  2770.                         'previouspage');
  2771.                 deleteoptions($dat, 'currentpage');
  2772.                 }
  2773.                 # Initialize the option set
  2774.                 copyoptions($dat, 'header', 'currentpage');
  2775.                 # Set command line options which apply only
  2776.                 # given pages
  2777.                 setoptionsforpage($dat, 'currentpage', $currentpage);
  2778.                 $pagesetupfound = 0;
  2779.                 if ($spooler eq 'cups') {
  2780.                 # Remove the "notfirst" flag from all options
  2781.                 # forseen for the "PageSetup" section, because
  2782.                 # when these are numerical options for CUPS.
  2783.                 # they have to be set to the correct value
  2784.                 # for every page
  2785.                 for my $arg (@{$dat->{'args'}}) {
  2786.                     if (($arg->{'section'} eq 'PageSetup') &&
  2787.                     (defined($arg->{'notfirst'}))) {
  2788.                     delete($arg->{'notfirst'});
  2789.                     }
  2790.                 }
  2791.                 }
  2792.                 # Insert PostScript option settings
  2793.                 # (options for section "PageSetup".
  2794.                 if ($isdscjob) {
  2795.                 $line .= 
  2796.                     makepagesetupsection($dat, $optionset,
  2797.                              0);
  2798.                 $pagesetupfound = 1;
  2799.                 }
  2800.                 # Now the page header comes, so buffer the data,
  2801.                 # because we must perhaps shut down and restart 
  2802.                 # the renderer
  2803.                 $passthru = 0;
  2804.                 $ignorepageheader = 0;
  2805.                 $optionsalsointoheader = 0;
  2806.             }
  2807.             } elsif (($line =~ m/^\%\%BeginPageSetup/) &&
  2808.                  ($nestinglevel == 0) &&
  2809.                  (!$ignorepageheader))  {
  2810.             # Start of the page header, up to %%EndPageSetup
  2811.             # nothing of the page will be drawn, page-specific
  2812.             # option settngs (as letter-head paper for page 1)
  2813.             # go here
  2814.             print $logh "${added_lf}Found: \%\%BeginPageSetup\n";
  2815.             $passthru = 0;
  2816.             $inpageheader = 1;        
  2817.             $postscriptsection = 'pagesetup';
  2818.             if (($ooo110) && ($currentpage == 1)) {
  2819.                 $optionsalsointoheader = 1;
  2820.             } else {
  2821.                 $optionsalsointoheader = 0;
  2822.             }
  2823.             } elsif (($line =~ m/^\%\%EndPageSetup/) &&
  2824.                  ($nestinglevel == 0) &&
  2825.                  (!$ignorepageheader)) {
  2826.             # End of the page header, the page is ready to be
  2827.             # printed
  2828.             print $logh "Found: \%\%EndPageSetup\n";
  2829.             print $logh "End of page header\n";
  2830.             # We cannot for sure say that the page header ends here
  2831.             # OpenOffice.org puts (due to a bug) a "%%BeginSetup...
  2832.             # %%EndSetup" section after the first "%%Page:...". It
  2833.             # is possible that CUPS inserts a "%%BeginPageSetup...
  2834.             # %%EndPageSetup" before this section, which means that
  2835.             # the options in the "%%BeginSetup...%%EndSetup"
  2836.             # section are after the "%%EndPageSetup", so we
  2837.             # continue for searching options up to the buffer size
  2838.             # limit $maxlinesforpageoptions.
  2839.             $passthru = 0;
  2840.             $inpageheader = 0;
  2841.             $optionsalsointoheader = 0;
  2842.             } elsif ((($line =~ m/^\%\%(BeginFeature):\s*\*?([^\*\s=]+)\s+()(\S[^\r\n]*)\r?\n?$/) ||
  2843.                   ($line =~ m/^\s*\%\%\s*(FoomaticRIPOptionSetting):\s*([^\*\s=]+)\s*=\s*(\@?)([^\@\s][^\r\n]*)\r?\n?$/)) &&
  2844.                  ($nestinglevel == 0) &&
  2845.                  (!$optionreplaced) &&
  2846.                  ((!$passthru) || (!$isdscjob))) {
  2847.             my ($linetype, $option, $fromcomposite, $value) = 
  2848.                 ($1, $2, $3, $4);
  2849.  
  2850.             # Mark that we are in a "Feature" section
  2851.             if ($linetype eq 'BeginFeature') {
  2852.                 $infeature = 1;
  2853.                 $linesafterlastbeginfeature = "";
  2854.             }
  2855.  
  2856.             # OK, we have an option.  If it's not a
  2857.             # *ostscript-style option (ie, it's command-line or
  2858.             # JCL) then we should note that fact, since the
  2859.             # attribute-to-filter option passing in CUPS is kind of
  2860.             # funky, especially wrt boolean options.  
  2861.  
  2862.             print $logh "Found: $line";
  2863.             if (my $arg=argbyname($option)) {
  2864.                 print $logh "   Option: $option=" .
  2865.                 ($fromcomposite ? "From" : "") . $value;
  2866.                 if (($spooler eq 'cups') &&
  2867.                 ($linetype eq 'BeginFeature') &&
  2868.                 (!defined($arg->{'notfirst'})) &&
  2869.                 ($arg->{$optionset} ne $value) &&
  2870.                 (($inheader) ||
  2871.                  ($arg->{section} eq 'PageSetup'))) {
  2872.  
  2873.                 # We have the first occurence of an option
  2874.                 # setting and the spooler is CUPS, so this
  2875.                 # setting is inserted by "pstops" or
  2876.                 # "imagetops". The value from the command
  2877.                 # line was not inserted by "pstops" or
  2878.                 # "imagetops" so it seems to be not under
  2879.                 # the choices in the PPD. Possible
  2880.                 # reasons:
  2881.                 #
  2882.                 # - "pstops" and "imagetops" ignore settings 
  2883.                 #   of numerical or string options which are
  2884.                 #   not one of the choices in the PPD file,
  2885.                 #   and inserts the default value instead.
  2886.                 #
  2887.                 # - On the command line an option was applied
  2888.                 #   only to selected pages:
  2889.                 #    "-o <page ranges>:<option>=<values>
  2890.                 #   This is not supported by CUPS, so not
  2891.                 #   taken care of by "pstops".
  2892.                 #
  2893.                 # We must fix this here by replacing the
  2894.                 # setting inserted by "pstops" or "imagetops"
  2895.                 # with the exact setting given on the command
  2896.                 # line.
  2897.  
  2898.                 # $arg->{$optionset} is already 
  2899.                 # range-checked, so do not check again here
  2900.                 # Insert DSC comment
  2901.                 my $dest = ((($inheader) && ($isdscjob)) ?
  2902.                         \@psheader : \@psfifo);
  2903.                 push(@{$dest},
  2904.                      "%%BeginFeature: " .
  2905.                      "*$option $arg->{$optionset}\n");
  2906.                 my $val;
  2907.                 if ($arg->{'style'} eq 'G') {
  2908.                     # PostScript option, insert the code
  2909.                     if ($arg->{'type'} eq 'bool') {
  2910.                     # Boolean option
  2911.                     if (defined($arg->{$optionset}) && 
  2912.                         $arg->{$optionset} == 1) {
  2913.                         push(@{$dest}, $arg->{'proto'} .
  2914.                          "\n");
  2915.                     } elsif ($arg->{'protof'}) {
  2916.                         push(@{$dest}, $arg->{'protof'} .
  2917.                          "\n");
  2918.                     }
  2919.                     } elsif ((($arg->{'type'} eq 'enum') ||
  2920.                           ($arg->{'type'} eq 'string') ||
  2921.                           ($arg->{'type'} eq
  2922.                            'password')) &&
  2923.                          (defined($val =
  2924.                               $arg->{'vals_byname'}{$arg->{$optionset}}))) {
  2925.                     # Enumerated choice of string or enum 
  2926.                     # option
  2927.                     push(@{$dest}, $val->{'driverval'} . "\n");
  2928.                     } elsif ((($arg->{'type'} eq 'string') ||
  2929.                           ($arg->{'type'} eq 
  2930.                            'password')) &&
  2931.                          ($arg->{$optionset} eq 'None')) {
  2932.                     # 'None' is mapped to the empty string 
  2933.                     # in string options
  2934.                     my $driverval = $arg->{'proto'};
  2935.                     $driverval =~ s/\%s//g;
  2936.                     push(@{$dest}, $driverval . "\n");
  2937.                     } else {
  2938.                     # Setting for numerical or string
  2939.                     # option which is not under the
  2940.                     # enumerated choices
  2941.                     my $sprintfproto = $arg->{'proto'};
  2942.                     $sprintfproto =~ s/\%(?!s)/\%\%/g;
  2943.                     push(@{$dest},
  2944.                          sprintf($sprintfproto,
  2945.                              $arg->{$optionset}) .
  2946.                          "\n");
  2947.                     }
  2948.                 } else {
  2949.                     # Command line or JCL option
  2950.                     push(@{$dest},
  2951.                      "%% FoomaticRIPOptionSetting: " .
  2952.                      "$option=$arg->{$optionset}\n");
  2953.                 }
  2954.                 print $logh " --> Correcting numerical/string " .
  2955.                     "option to $option=$arg->{$optionset}" .
  2956.                     " (Command line argument)\n";
  2957.                 # We have replaced this option on the 
  2958.                 # FIFO
  2959.                 $optionreplaced = 1;
  2960.                 }
  2961.                 # Mark that we have already found this option
  2962.                 $arg->{'notfirst'} = 1;
  2963.                 if (!$optionreplaced) {
  2964.                 if ($arg->{'style'} ne 'G') {
  2965.                     # "Controlled by '<Composite>'" setting of
  2966.                     # a member option of a composite option
  2967.                     if ($fromcomposite) {
  2968.                     $value = "From$value";
  2969.                     }
  2970.                     # Non-PostScript option
  2971.                     # Check whether it is valid
  2972.                     if (defined(my $newvalue =
  2973.                         checkoptionvalue($dat, $option,
  2974.                                  $value, 0))) {
  2975.                     print $logh " --> Setting option\n";
  2976.                     # Valid choice, set it.
  2977.                     $arg->{$optionset} = $newvalue;
  2978.                     if ($optionsalsointoheader) {
  2979.                         $arg->{'header'} = $newvalue;
  2980.                     }
  2981.                     if (($arg->{'type'} eq 'enum') &&
  2982.                         (($option eq 'PageSize') ||
  2983.                          ($option eq 'PageRegion')) &&
  2984.                         ($newvalue =~ /^Custom/) &&
  2985.                         ($linetype eq 
  2986.                          'FoomaticRIPOptionSetting')) {
  2987.                         # Custom page size
  2988.                         $linesafterlastbeginfeature =~ 
  2989.                         /^[\s\r\n]*([\d\.]+)[\s\r\n]+([\d\.]+)[\s\r\n]+/s;
  2990.                         my ($w, $h) = ($1, $2);
  2991.                         if (($w) && ($h) && 
  2992.                         ($w != 0) && ($h != 0)) {
  2993.                         $newvalue =
  2994.                             "$newvalue.${w}x$h";
  2995.                         $arg->{$optionset} = $newvalue;
  2996.                         if ($optionsalsointoheader) {
  2997.                             $arg->{'header'} =
  2998.                             $newvalue;
  2999.                         }
  3000.                         }
  3001.                     }
  3002.                     # For a composite option insert the
  3003.                     # code from the member options with
  3004.                     # current setting "From<composite>"
  3005.                     # The code from the member options
  3006.                     # is chosen according to the setting 
  3007.                     # of the composite option.
  3008.                     if (($arg->{'style'} eq 'X') &&
  3009.                         ($linetype eq 
  3010.                          'FoomaticRIPOptionSetting')) {
  3011.                         buildcommandline($dat, $optionset);
  3012.                         $line .=
  3013.                         $arg->{$postscriptsection};
  3014.                     }
  3015.                     # If this argument is PageSize or 
  3016.                     # PageRegion, also set the other
  3017.                     syncpagesize($dat, $option, $newvalue, 
  3018.                              $optionset);
  3019.                     if ($optionsalsointoheader) {
  3020.                         syncpagesize($dat, $option, 
  3021.                              $newvalue, 'header');
  3022.                     }
  3023.                     } else {
  3024.                     # Invalid option, log it.
  3025.                     print $logh " --> Invalid option " .
  3026.                         "setting found in job\n";
  3027.                     }
  3028.                 } elsif ($fromcomposite) {
  3029.                     # PostScript option, but we have to look up
  3030.                     # the PostScript code to be inserted from
  3031.                     # the setting of a composite option, as
  3032.                     # this option is set to "Controlled by 
  3033.                     # '<Composite>'".
  3034.                     # Set the option
  3035.                     if (defined(my $newvalue =
  3036.                         checkoptionvalue
  3037.                         ($dat, $option,
  3038.                          "From$value", 0))) {
  3039.                     print $logh " --> Looking up setting " .
  3040.                         "in composite option '$value'\n";
  3041.                     # Valid choice, set it.
  3042.                     $arg->{$optionset} = $newvalue;
  3043.                     if ($optionsalsointoheader) {
  3044.                         $arg->{'header'} = $newvalue;
  3045.                     }
  3046.                     # Update composite options
  3047.                     buildcommandline($dat, $optionset);
  3048.                     # Substitute PostScript comment by
  3049.                     # the real code
  3050.                     $line = $arg->{'compositesubst'};
  3051.                     } else {
  3052.                     # Invalid option, log it.
  3053.                     print $logh " --> Invalid option " .
  3054.                         "setting found in job\n";
  3055.                     }
  3056.                 } else {
  3057.                     # it is a PostScript style option with
  3058.                     # the code readily inserted, no option
  3059.                     # for the renderer command line/JCL to set,
  3060.                     # no lookup of a composite option needed,
  3061.                     # so nothing to do here...
  3062.                     print $logh 
  3063.                     " --> Option will be set by " .
  3064.                     "PostScript interpreter\n";
  3065.                 }
  3066.                 }
  3067.             } else {
  3068.                 # This option is unknown to us.  WTF?
  3069.                 print $logh "Unknown option $option=$value found " .
  3070.                 "in the job\n";
  3071.             }
  3072.             } elsif (($line =~ m/^\%\%EndFeature/) &&
  3073.                  ($nestinglevel == 0)) {
  3074.             # End of Feature
  3075.             $infeature = 0;
  3076.             # If the option setting was replaced, it ends here,
  3077.             # too, and the next option is not necessarily also
  3078.             # replaced.
  3079.             $optionreplaced = 0;
  3080.             $linesafterlastbeginfeature = "";
  3081.             } elsif (($line =~ m/^\%\%Begin/) &&
  3082.                  ($isdscjob) &&
  3083.                  (!$prologfound) &&
  3084.                  ($nestinglevel == 0)) {
  3085.             # In some PostScript files (especially when generated
  3086.             # by "dvips" of TeX/LaTeX) the "%%BeginProlog" is
  3087.             # missing, so assume that it was before the current
  3088.             # line (the first line starting with "%%Begin".
  3089.             print $logh "Job claims to be DSC-conforming, but " . 
  3090.                 "\"%%BeginProlog\" was missing before first " .
  3091.                 "line with another \"%%Begin...\" comment " .
  3092.                 "(is this a TeX/LaTeX/dvips-generated PostScript " .
  3093.                 "file?). Assuming start of \"Prolog\" here.\n";
  3094.             # Beginning of Prolog
  3095.             $inprolog = 1;
  3096.             $nondsclines = 0;
  3097.             # Insert options for "Prolog" before the current line
  3098.             if (!$prologfound) {
  3099.                 $line =
  3100.                 "%%BeginProlog\n" .
  3101.                 makeprologsection($dat, $optionset, 0) .
  3102.                 $line;
  3103.             }
  3104.             $prologfound = 1;
  3105.             } elsif (($line =~ m/^\s*\%/) || ($line =~ m/^\s*$/)) {
  3106.             # This is an unknown PostScript comment or a blank
  3107.             # line, no active code
  3108.             $ignoreline = 1;
  3109.             }
  3110.         } else {
  3111.             # This line is active PostScript code
  3112.             if ($infeature) {
  3113.             # Collect coe in a "%%BeginFeature: ... %%EndFeature"
  3114.             # section, to get the values for a custom option
  3115.             # setting
  3116.             $linesafterlastbeginfeature .= $line;
  3117.             }
  3118.             if ($inheader) {
  3119.             if ((!$inprolog) && (!$insetup)) {
  3120.                 # Outside the "Prolog" and "Setup" section
  3121.                 # a correct DSC-conforming document has no
  3122.                 # active PostScript code, so consider the
  3123.                 # file as non-DSC-conforming when there are
  3124.                 # too many of such lines.
  3125.                 $nondsclines ++;
  3126.                 if ($nondsclines > $maxnondsclinesinheader) {
  3127.                 # Consider document as not DSC-conforming
  3128.                 print $logh "This job seems not to be " .
  3129.                     "DSC-conforming, DSC-comment for " .
  3130.                     "next section not found, stopping " .
  3131.                     "to parse the rest, passing it " .
  3132.                     "directly to the renderer.\n";
  3133.                 # Stop scanning for further option settings
  3134.                 $maxlines = 1;
  3135.                 $isdscjob = 0;
  3136.                 # Insert defaults and command line settings
  3137.                 # in the beginning of the job or after the
  3138.                 # last valid section
  3139.                 splice(@psheader, $insertoptions, 0,
  3140.                        ($prologfound ? () :
  3141.                     makeprologsection($dat, $optionset, 
  3142.                               1)),
  3143.                        ($setupfound ? () :
  3144.                     makesetupsection($dat, $optionset,
  3145.                              1)),
  3146.                        ($pagesetupfound ? () :
  3147.                     makepagesetupsection($dat,
  3148.                                  $optionset, 
  3149.                                  1)));
  3150.                 $prologfound = 1;
  3151.                 $setupfound = 1;
  3152.                 $pagesetupfound = 1;
  3153.                 }
  3154.             }
  3155.             } else {
  3156.             if (!$inpageheader) {
  3157.                 # PostScript code inside a page, but not between
  3158.                 # "%%BeginPageSetup" and "%%EndPageSetup", so 
  3159.                 # we are perhaps already drawing onto a page now
  3160.                 if ($onelinebefore =~ m/^\%\%Page:/) {
  3161.                 print $logh "No page header or page " .
  3162.                     "header not DSC-conforming\n";
  3163.                 }
  3164.                 # Stop buffering lines to search for options 
  3165.                 # placed not DSC-conforming
  3166.                 if (scalar(@psfifo) >= 
  3167.                 $maxlinesforpageoptions) {
  3168.                 print $logh "Stopping search for " .
  3169.                     "page header options\n";
  3170.                 $passthru = 1;
  3171.                 # If there comes a page header now, ignore 
  3172.                 # it
  3173.                 $ignorepageheader = 1;
  3174.                 $optionsalsointoheader = 0;
  3175.                 }
  3176.             }
  3177.             }
  3178.         }
  3179.         }
  3180.         
  3181.         # Debug info
  3182.         if ($lastpassthru != $passthru) {
  3183.         if ($passthru) {
  3184.             print $logh "Found: $line" . 
  3185.             " --> Output goes directly to the renderer now.\n${added_lf}";
  3186.         } else {
  3187.             print $logh "Found: $line" . 
  3188.             " --> Output goes to the FIFO buffer now.${added_lf}\n";
  3189.         }
  3190.         }
  3191.  
  3192.         # We are in an option which was replaced, do not output
  3193.         # the current line.
  3194.         if ($optionreplaced) {
  3195.         $line = "";
  3196.         }
  3197.  
  3198.         # If we are in a "%%BeginSetup...%%EndSetup" section after
  3199.         # the first "%%Page:..." and the current line belongs to
  3200.         # an option setting, we have to copy the line also to the
  3201.         # @psheader.
  3202.         if (($optionsalsointoheader) && 
  3203.         (($infeature) || ($line =~ m/^\%\%EndFeature/))) {
  3204.         push (@psheader, $line);
  3205.         }
  3206.  
  3207.         # Store or send the current line
  3208.         if (($inheader) && ($isdscjob)) {
  3209.         # We are still in the PostScript header, collect all lines 
  3210.         # in @psheader
  3211.         push (@psheader, $line);
  3212.         } else {
  3213.         if (($passthru) && ($isdscjob)) {
  3214.             if (!$lastpassthru) {
  3215.             # We enter passthru mode with this line, so the
  3216.             # command line can have changed, check it and
  3217.             # close the renderer if needed
  3218.             if (($rendererpid) &&
  3219.                 (!optionsequal($dat, 'currentpage',
  3220.                        'previouspage', 0))) {
  3221.                 print $logh "Command line/JCL options " .
  3222.                 "changed, restarting renderer\n";
  3223.                 $retval = closerendererhandle
  3224.                 ($rendererhandle, $rendererpid);
  3225.                 if ($retval != $EXIT_PRINTED) {
  3226.                 rip_die ("Error closing renderer",
  3227.                      $retval);
  3228.                 }
  3229.                 $rendererpid = 0;
  3230.             }
  3231.             }
  3232.             # Flush @psfifo and send line directly to the renderer
  3233.             if (!$rendererpid) {
  3234.             # No renderer running, start it
  3235.             ($rendererhandle, $rendererpid) =
  3236.                 getrendererhandle
  3237.                 ($dat, join('', @psheader, @psfifo));
  3238.             if ($retval != $EXIT_PRINTED) {
  3239.                 rip_die ("Error opening renderer",
  3240.                      $retval);
  3241.             }
  3242.             # @psfifo is sent out, flush it.
  3243.             @psfifo = ();
  3244.             }
  3245.             if ($#psfifo >= 0) {
  3246.             # Send @psfifo to renderer
  3247.             print $rendererhandle join('', @psfifo);
  3248.             # flush @psfifo
  3249.             @psfifo = ();
  3250.             }
  3251.             # Send line to renderer
  3252.             if (!$printprevpage) {
  3253.             print $rendererhandle $line;
  3254.             
  3255.             while ($line=<STDIN>)
  3256.             {
  3257.               if ($line =~ /^\%\%[A-Za-z\s]{3,}/) {
  3258.                     print $logh "Found: $line" . 
  3259.                             " --> Continue DSC parsing now.${added_lf}\n";
  3260.                 $saved = 1;
  3261.                 last;
  3262.               } else {
  3263.                 print $rendererhandle $line;
  3264.                 $linect++;
  3265.               }  
  3266.             }
  3267.             }
  3268.         } else {
  3269.             # Push the line onto the stack for later spitting up...
  3270.             push (@psfifo, $line);
  3271.         }
  3272.         }
  3273.         
  3274.         if (!$printprevpage) {
  3275.         $linect++;
  3276.         }
  3277.  
  3278.     } else {
  3279.         # EOF!
  3280.         $more_stuff = 0;
  3281.         # No PostScript header in the whole file? Then it's not
  3282.         # PostScript, convert it.
  3283.         # We open the file converter here when the file has less
  3284.         # lines than the amount which we search for the PostScript
  3285.         # header ($maxlinestopsstart).
  3286.         if ($linect <= $nonpslines) {
  3287.         # This is not a PostScript job, we must convert it
  3288.         print $logh "${added_lf}Job does not start with \"%!\", " . 
  3289.             "is it PostScript?\n" .
  3290.             "Starting file converter\n";
  3291.         # Reset all variables but conserve the data which
  3292.         # we have already read.
  3293.         $jobhasjcl = 0;
  3294.         $linect = 0;
  3295.         $nonpslines = 0;
  3296.         $maxlines = 1000;
  3297.         $onelinebefore = "";
  3298.         $twolinesbefore = "";
  3299.         my $alreadyread = join('', @psheader, @psfifo);
  3300.         @psheader = ();
  3301.         @psfifo = ();
  3302.         $line = "";
  3303.         # Start the file conversion filter
  3304.         if (!$fileconverterpid) {
  3305.             ($fileconverterhandle, $fileconverterpid) =
  3306.             getfileconverterhandle($dat, $alreadyread);
  3307.             if ( defined($retval) and $retval != $EXIT_PRINTED) {
  3308.             rip_die ("Error opening file converter",
  3309.                  $retval);
  3310.             }
  3311.         } else {
  3312.             rip_die("File conversion filter probably " .
  3313.                 "crashed",
  3314.                 $EXIT_JOBERR);
  3315.         }
  3316.         # Read the further data from the file converter and
  3317.         # not from STDIN
  3318.         if (!close STDIN && $! != $ESPIPE) {
  3319.             rip_die ("Couldn't close STDIN",
  3320.                  $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3321.         }
  3322.         if (!open (STDIN, "<&$fileconverterhandle")) {
  3323.             rip_die ("Couldn't dup \$fileconverterhandle",
  3324.                  $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3325.         }
  3326.         # Now we have new (converted) stuff in STDIN, so
  3327.         # continue in the loop
  3328.         $more_stuff = 1;
  3329.         }
  3330.     }
  3331.  
  3332.     $lastpassthru = $passthru;
  3333.     
  3334.     if ((!$ignoreline) && (!$printprevpage)) {
  3335.         $twolinesbefore = $onelinebefore;
  3336.         $onelinebefore = $line;
  3337.     }
  3338.  
  3339.     } while ((($maxlines == 0) or ($linect < $maxlines)) and
  3340.          ($more_stuff != 0));
  3341.  
  3342.     # Some buffer still containing data? Send it out to the renderer.
  3343.     if (($more_stuff != 0) || ($inheader) || ($#psfifo >= 0)) {
  3344.     # Flush @psfifo and send the remaining data to the renderer, this
  3345.     # only happens with non-DSC-conforming jobs or non-Foomatic PPDs
  3346.     if ($more_stuff) {
  3347.         print $logh "Stopped parsing the PostScript data, ".
  3348.         "sending rest directly to renderer.\n";
  3349.     } else {
  3350.         print $logh "Flushing FIFO.\n";
  3351.     }
  3352.     if ($inheader) {
  3353.         # No page initialized yet? Copy the "header" option set into the
  3354.         # "currentpage" option set, so that the renderer will find the
  3355.         # options settings.
  3356.         copyoptions($dat, 'header', 'currentpage');
  3357.         $optionset = 'currentpage';
  3358.         # If not done yet, insert defaults and command line settings
  3359.         # in the beginning of the job or after the last valid section
  3360.         splice(@psheader, $insertoptions, 0,
  3361.            ($prologfound ? () :
  3362.             makeprologsection($dat, $optionset, 1)),
  3363.            ($setupfound ? () :
  3364.             makesetupsection($dat, $optionset, 1)),
  3365.            ($pagesetupfound ? () :
  3366.             makepagesetupsection($dat, $optionset, 1)));
  3367.         $prologfound = 1;
  3368.         $setupfound = 1;
  3369.         $pagesetupfound = 1;
  3370.     }
  3371.     if (($rendererpid) &&
  3372.         (!optionsequal($dat, 'currentpage',
  3373.                'previouspage', 0))) {
  3374.         print $logh "Command line/JCL options " .
  3375.         "changed, restarting renderer\n";
  3376.         $retval = closerendererhandle
  3377.         ($rendererhandle, $rendererpid);
  3378.         if ($retval != $EXIT_PRINTED) {
  3379.         rip_die ("Error closing renderer",
  3380.              $retval);
  3381.         }
  3382.         $rendererpid = 0;
  3383.     }
  3384.     if (!$rendererpid) {
  3385.         ($rendererhandle, $rendererpid) =
  3386.         getrendererhandle($dat, join('', @psheader, @psfifo));
  3387.         if ($retval != $EXIT_PRINTED) {
  3388.         rip_die ("Error opening renderer",
  3389.              $retval);
  3390.         }
  3391.         # We have sent @psfifo now
  3392.         @psfifo = ();
  3393.     }
  3394.     if ($#psfifo >= 0) {
  3395.         # Send @psfifo to renderer
  3396.         print $rendererhandle join('', @psfifo);
  3397.         # flush @psfifo
  3398.         @psfifo = ();
  3399.     }
  3400.     # Print the rest of the input data
  3401.     if ($more_stuff) {
  3402.         while (<STDIN>) {
  3403.         print $rendererhandle $_;
  3404.         }
  3405.     }
  3406.     }
  3407.  
  3408.     # At every "%%Page:..." comment we have saved the PostScript state
  3409.     # and we have increased the page number. So if the page number is
  3410.     # non-zero we had at least one "%%Page:..." comment and so we have
  3411.     # to give a restore the PostScript state.
  3412.     #if ($currentpage > 0) {
  3413.     #    print $rendererhandle "foomatic-saved-state restore\n";
  3414.     #}
  3415.     
  3416.     # Close the renderer
  3417.     if ($rendererpid) {
  3418.     $retval = closerendererhandle ($rendererhandle, $rendererpid);
  3419.     if ($retval != $EXIT_PRINTED) {
  3420.         rip_die ("Error closing renderer",
  3421.              $retval);
  3422.     }
  3423.     $rendererpid = 0;
  3424.     }
  3425.  
  3426.     # Close the file converter (if it was used)
  3427.     if ($fileconverterpid) {
  3428.     $retval = closefileconverterhandle
  3429.         ($fileconverterhandle, $fileconverterpid);
  3430.     if ($retval != $EXIT_PRINTED) {
  3431.         rip_die ("Error closing file converter",
  3432.              $retval);
  3433.     }
  3434.     $fileconverterpid = 0;
  3435.     }
  3436. }
  3437.  
  3438.  
  3439. ## Close the documentation page generator
  3440. if ($docgeneratorpid) {
  3441.     $retval = closedocgeneratorhandle
  3442.     ($docgeneratorhandle, $docgeneratorpid);
  3443.     if ($retval != $EXIT_PRINTED) {
  3444.     rip_die ("Error closing documentation page generator",
  3445.          $retval);
  3446.     }
  3447.     $docgeneratorpid = 0;
  3448. }
  3449.  
  3450.  
  3451.  
  3452. ## Close last input file
  3453. close STDIN;
  3454.  
  3455.  
  3456.  
  3457. ## Only for debugging
  3458. if ($debug && 1) {
  3459.     use Data::Dumper;
  3460.     local $Data::Dumper::Purity=1;
  3461.     local $Data::Dumper::Indent=1;
  3462.     print $logh Dumper($dat);
  3463. }
  3464.  
  3465.  
  3466.  
  3467. ## The End
  3468. print $logh "${added_lf}Closing foomatic-rip.\n";
  3469. close $logh;
  3470.  
  3471. exit $retval;
  3472.  
  3473.  
  3474.  
  3475. ## Functions to let foomatic-rip fork to do several tasks in parallel.
  3476.  
  3477. # To do the filtering without loading the whole file into memory we work
  3478. # on a data stream, we read the data line by line analyse it to decide what
  3479. # filters to use and start the filters if we have found out which we need.
  3480. # We buffer the data only as long as we didn't determing which filters to
  3481. # use for this piece of data and with which options. There are no temporary
  3482. # files used.
  3483.  
  3484. # foomatic-rip splits into up to 6 parallel processes to do the whole
  3485. # filtering (listed in the order of the data flow):
  3486.  
  3487. #    KID0: Generate documentation pages (only jobs with "docs" option)
  3488. #    KID2: Put together already read data and current input stream for
  3489. #          feeding into the file conversion filter (only non-PostScript
  3490. #          and "docs" jobs)
  3491. #    KID1: Run the file conversion filter to convert non-PostScript
  3492. #          input into PostScript (only non-PostScript and "docs" jobs)
  3493. #    MAIN: Prepare the job auto-detecting the spooler, reading the PPD,
  3494. #          extracting the options from the command line, and parsing
  3495. #          the job data itself. It analyses the job data to check
  3496. #          whether it is PostScript and starts KID1/KID2 if not, it
  3497. #          also stuffs PostScript code from option settings into the
  3498. #          PostScript data stream. It starts the renderer (KID3/KID4)
  3499. #          as soon as it knows its command line and restarts it when
  3500. #          page-specific option settings need another command line
  3501. #          or different JCL commands.
  3502. #    KID3: The rendering process. In most cases GhostScript, "cat"
  3503. #          for native PostScript printers with their manufacturer's
  3504. #          PPD files.
  3505. #    KID4: Put together the JCL commands and the renderer's output
  3506. #          and send all that either to STDOUT or pipe it into the
  3507. #          command line defined with $postpipe.
  3508.  
  3509. ## This function runs the renderer command line (and if defined also
  3510. ## the postpipe) and returns a file handle for stuffing in the
  3511. ## PostScript data.
  3512. sub getrendererhandle {
  3513.  
  3514.     my ($dat, $prepend) = @_;
  3515.  
  3516.     print $logh "${added_lf}Starting renderer\n";
  3517.  
  3518.     # Catch signals
  3519.     $retval = $EXIT_PRINTED;
  3520.     use sigtrap qw(handler set_exit_prnerr USR1 
  3521.            handler set_exit_prnerr_noretry USR2
  3522.            handler set_exit_engaged TTIN);
  3523.  
  3524.     # Variables for the kid processes reporting their state
  3525.  
  3526.     # Set up a pipe for the kids to pass their exit stat to the main process
  3527.     pipe KID_MESSAGE, KID_MESSAGE_IN;
  3528.  
  3529.     # When one kid fails put the exit stat here
  3530.     $kidfailed = 0;
  3531.  
  3532.     # When a kid exits successfully, mark it here
  3533.     $kid3finished = 0;
  3534.     $kid4finished = 0;
  3535.  
  3536.     # Build the command line and get the JCL commands
  3537.     buildcommandline($dat, 'currentpage');
  3538.     my $commandline = $dat->{'currentcmd'};
  3539.     my @jclprepend = @{$dat->{'jclprepend'}} if defined $dat->{'jclprepend'};
  3540.     my @jclappend  = @{$dat->{'jclappend'}}  if defined $dat->{'jclappend'};
  3541.  
  3542.     use IO::Handle;
  3543.     pipe KID3_IN, KID3;
  3544.     KID3->autoflush(1);
  3545.     $kid3 = fork();
  3546.     if (!defined($kid3)) {
  3547.     close KID3;
  3548.     close KID3_IN;
  3549.         print $logh "$0: cannot fork for kid3!\n";
  3550.     rip_die ("can't fork for kid3",
  3551.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3552.     }
  3553.     if ($kid3) {
  3554.  
  3555.         # we are the parent; return a glob to the filehandle
  3556.         close KID3_IN;
  3557.  
  3558.     # Feed in the PostScript header and the FIFO contents
  3559.     print KID3 $prepend;
  3560.  
  3561.         KID3->flush();
  3562.         return ( *KID3, $kid3 );
  3563.  
  3564.     } else {
  3565.         close KID3;
  3566.  
  3567.         pipe KID4_IN, KID4;
  3568.     KID4->autoflush(1);
  3569.         $kid4 = fork();
  3570.         if (!defined($kid4)) {
  3571.         close KID4;
  3572.         close KID4_IN;
  3573.             print $logh "$0: cannot fork for kid4!\n";
  3574.         close KID_MESSAGE;
  3575.         print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3576.         close KID_MESSAGE_IN;
  3577.         rip_die ("can't fork for kid4",
  3578.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3579.         }
  3580.         
  3581.         if ($kid4) {
  3582.             # parent, child of primary task; we are |commandline|
  3583.             close KID4_IN;
  3584.  
  3585.             print $logh "renderer PID kid4=$kid4\n";
  3586.         print $logh "renderer command: $commandline\n";
  3587.             
  3588.             if (!close STDIN && $! != $ESPIPE) {
  3589.         close KID3_IN;
  3590.         close KID4;
  3591.         close KID_MESSAGE;
  3592.         print KID_MESSAGE_IN
  3593.             "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3594.         close KID_MESSAGE_IN;
  3595.         rip_die ("Couldn't close STDIN in $kid4",
  3596.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3597.         }
  3598.             if (!open (STDIN, "<&KID3_IN")) {
  3599.         close KID3_IN;
  3600.         close KID4;
  3601.         close KID_MESSAGE;
  3602.         print KID_MESSAGE_IN
  3603.             "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3604.         close KID_MESSAGE_IN;
  3605.         rip_die ("Couldn't dup KID3_IN",
  3606.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3607.         }
  3608.             if (!close STDOUT) {
  3609.         close KID3_IN;
  3610.         close KID4;
  3611.         close KID_MESSAGE;
  3612.         print KID_MESSAGE_IN
  3613.             "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3614.         close KID_MESSAGE_IN;
  3615.         rip_die ("Couldn't close STDOUT in $kid4",
  3616.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3617.         }
  3618.             if (!open (STDOUT, ">&KID4")) {
  3619.         close KID3_IN;
  3620.         close KID4;
  3621.         close KID_MESSAGE;
  3622.         print KID_MESSAGE_IN
  3623.             "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3624.         close KID_MESSAGE_IN;
  3625.         rip_die ("Couldn't dup KID4",
  3626.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3627.         }
  3628.         if ($debug) {
  3629.         if (!open (STDERR, ">&$logh")) {
  3630.             close KID3_IN;
  3631.             close KID4;
  3632.             close KID_MESSAGE;
  3633.             print KID_MESSAGE_IN
  3634.             "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3635.             close KID_MESSAGE_IN;
  3636.             rip_die ("Couldn't dup logh to stderr",
  3637.                  $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3638.         }
  3639.         }
  3640.  
  3641.         # Massage commandline to execute foomatic-gswrapper
  3642.         my $havewrapper = 0;
  3643.         for (split(':', $ENV{'PATH'})) {
  3644.         if (-x "$_/foomatic-gswrapper") {
  3645.             $havewrapper=1;
  3646.             last;
  3647.         }
  3648.         }
  3649.         if ($havewrapper) {
  3650.         $commandline =~ s!^\s*gs\s!foomatic-gswrapper !g;
  3651.         $commandline =~ s!(\|\s*)gs\s!\|foomatic-gswrapper !g;
  3652.         $commandline =~ s!(;\s*)gs\s!; foomatic-gswrapper !g;
  3653.         }
  3654.  
  3655.         # If the renderer command line contains the "echo"
  3656.         # command, replace the "echo" by the user-chosen $myecho
  3657.         # (important for non-GNU systems where GNU echo is in a
  3658.         # special path
  3659.         $commandline =~ s!^\s*echo\s!$myecho !g;
  3660.         $commandline =~ s!(\|\s*)echo\s!\|$myecho !g;
  3661.         $commandline =~ s!(;\s*)echo\s!; $myecho !g;
  3662.  
  3663.         # In debug mode save the data supposed to be fed into the
  3664.         # renderer also into a file
  3665.         if ($debug) {
  3666.         $commandline = "tee -a ${logfile}.ps | ( $commandline )";
  3667.         }
  3668.         
  3669.         # Actually run the thing...
  3670.         modern_system("$commandline");
  3671.             if ($? != 0) {
  3672.         my $rendererretval = $? >> 8;
  3673.         print $logh "renderer return value: $rendererretval\n";
  3674.         my $renderersignal = $? & 127;
  3675.         print $logh "renderer received signal: $rendererretval\n";
  3676.         close STDOUT;
  3677.         close KID4;
  3678.         close STDIN;
  3679.         close KID3_IN;
  3680.         # Handle signals
  3681.         if ($renderersignal == SIGUSR1) {
  3682.             $retval = $EXIT_PRNERR;
  3683.         } elsif ($renderersignal == SIGUSR2) {
  3684.             $retval = $EXIT_PRNERR_NORETRY;
  3685.         } elsif ($renderersignal == SIGTTIN) {
  3686.             $retval = $EXIT_ENGAGED;
  3687.         }
  3688.         if ($retval != $EXIT_PRINTED) {
  3689.             close KID_MESSAGE;
  3690.             print KID_MESSAGE_IN "3 $retval\n";
  3691.             close KID_MESSAGE_IN;
  3692.             exit $retval;
  3693.         }
  3694.         # Evaluate renderer result
  3695.         if ($rendererretval == 0) {
  3696.             # Success, exit with 0 and inform main process
  3697.             close KID_MESSAGE;
  3698.             print KID_MESSAGE_IN "3 $EXIT_PRINTED\n";
  3699.             close KID_MESSAGE_IN;
  3700.             exit $EXIT_PRINTED;
  3701.         } elsif ($rendererretval == 1) {
  3702.             # Syntax error? PostScript error?
  3703.             close KID_MESSAGE;
  3704.             print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
  3705.             close KID_MESSAGE_IN;
  3706.             rip_die ("Possible error on renderer command line or PostScript error. Check options.",
  3707.                  $EXIT_JOBERR);
  3708.         } elsif ($rendererretval == 139) {
  3709.             # Seems to indicate a core dump
  3710.             close KID_MESSAGE;
  3711.             print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
  3712.             close KID_MESSAGE_IN;
  3713.             rip_die ("The renderer may have dumped core.",
  3714.                  $EXIT_JOBERR);
  3715.         } elsif ($rendererretval == 141) {
  3716.             # Broken pipe, presumably additional filter interface
  3717.             # exited.
  3718.             close KID_MESSAGE;
  3719.             print KID_MESSAGE_IN "3 $EXIT_PRNERR\n";
  3720.             close KID_MESSAGE_IN;
  3721.             rip_die ("A filter used in addition to the renderer" .
  3722.                  " itself may have failed.",
  3723.                  $EXIT_PRNERR);
  3724.         } elsif (($rendererretval == 243) || ($retval == 255)) {
  3725.             # PostScript error?
  3726.             close KID_MESSAGE;
  3727.             print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
  3728.             close KID_MESSAGE_IN;
  3729.             exit $EXIT_JOBERR;
  3730.         } else {
  3731.             # Unknown error
  3732.             close KID_MESSAGE;
  3733.             print KID_MESSAGE_IN "3 $EXIT_PRNERR\n";
  3734.             close KID_MESSAGE_IN;
  3735.             rip_die ("The renderer command line returned an" .
  3736.                  " unrecognized error code $rendererretval.",
  3737.                  $EXIT_PRNERR);
  3738.         }
  3739.         }
  3740.         close STDOUT;
  3741.         close KID4;
  3742.         close STDIN;
  3743.         close KID3_IN;
  3744.         # When arrived here the renderer command line was successful
  3745.         # So exit with zero exit value here and inform the main process
  3746.         close KID_MESSAGE;
  3747.         print KID_MESSAGE_IN "3 $EXIT_PRINTED\n";
  3748.         close KID_MESSAGE_IN;
  3749.         # Wait for postpipe/output child
  3750.         waitpid($kid4, 0);
  3751.         print $logh "KID3 finished\n";
  3752.         exit $EXIT_PRINTED;
  3753.         } else {
  3754.             # child, trailing task on the pipe; we write jcl stuff
  3755.             close KID4;
  3756.         close KID3_IN;
  3757.  
  3758.             my $fileh = *STDOUT;
  3759.  
  3760.         # Do we have a $postpipe, if yes, launch the command(s) and
  3761.         # point our output into it/them
  3762.             if ($postpipe) {
  3763.                 if (!open PIPE,$postpipe) {
  3764.             close KID4_IN;
  3765.                     close KID_MESSAGE;
  3766.                     print KID_MESSAGE_IN
  3767.             "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3768.             close KID_MESSAGE_IN;
  3769.                     rip_die ("cannot execute postpipe $postpipe",
  3770.                              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3771.                 }
  3772.                 $fileh = *PIPE;
  3773.             }
  3774.  
  3775.         # Debug output
  3776.         print $logh "JCL: " . join("", @jclprepend) . "<job data> ${added_lf}" .
  3777.         join("", @jclappend) . "\n";
  3778.  
  3779.             # wrap the JCL around the job data, if there are any
  3780.             # options specified...
  3781.         # Should the driver already have inserted JCL commands we merge
  3782.         # our JCL header with the one from the driver
  3783.         my $driverjcl = 0;
  3784.         if ( @jclprepend > 1 ) {
  3785.         # JCL header read from renderer output
  3786.         my @jclheader = ();
  3787.         # Determine magic string of JCL in use (usually "@PJL")
  3788.         # For that we take the first part of the second JCL line up
  3789.         # to the first space
  3790.         if ($jclprepend[1] =~ /^(\S+)/) {
  3791.             my $jclstr = $1;
  3792.             # Read from the renderer output until the first non-JCL
  3793.             # line appears
  3794.             while (my $line = <KID4_IN>) {
  3795.             push(@jclheader, $line);
  3796.             last if ($line !~ /$jclstr/);
  3797.             }
  3798.             # If we had read at least two lines, at least one is
  3799.             # a JCL header, so do the merging
  3800.             if (@jclheader > 1) {
  3801.             $driverjcl = 1;
  3802.             # Discard the first and the last entry of the
  3803.             # @jclprepend array, we only need the option settings
  3804.             # to merge them in
  3805.             pop(@jclprepend);
  3806.             shift(@jclprepend);
  3807.             # Line after which we insert new JCL commands in the
  3808.             # JCL header of the job
  3809.             my $insert = 1;
  3810.             # Go through every JCL command in @jclprepend
  3811.             for my $line (@jclprepend) {
  3812.                 # Search the command in the JCL header from the
  3813.                 # driver. As search term use only the string from
  3814.                 # the beginning of the line to the "=", so the
  3815.                 # command will also be found when it has another
  3816.                 # value
  3817.                 $line =~ /^([^=]+)/;
  3818.                 my $cmd = $1;
  3819.                 my $cmdfound = 0;
  3820.                 for (@jclheader) {
  3821.                 # If the command is there, replace it
  3822.                 $_ =~ s/$cmd\b.*(\r\n|\n|\r)/$line/ and 
  3823.                     $cmdfound = 1;
  3824.                 }
  3825.                 if (!$cmdfound) {
  3826.                 # If the command is not found, insert it
  3827.                 if (@jclheader > 2) {
  3828.                     # @jclheader has more than one line,
  3829.                     # insert the new command beginning
  3830.                     # right after the first line and continuing
  3831.                     # after the previous inserted command
  3832.                     splice(@jclheader, $insert, 0, $line);
  3833.                     $insert ++;
  3834.                 } else {
  3835.                     # If we have only one line of JCL it
  3836.                     # is probably something like the
  3837.                     # "@PJL ENTER LANGUAGE=..." line
  3838.                     # which has to be in the end, but
  3839.                     # it also contains the
  3840.                     # "<esc>%-12345X" which has to be in the
  3841.                     # beginning of the job. So we split the
  3842.                     # line right before the $jclstr and
  3843.                     # append our command to the end of the
  3844.                     # first part and let the second part
  3845.                     # be a second JCL line.
  3846.                     $jclheader[0] =~ 
  3847.                     /^(.*?)($jclstr.*(\r\n|\n|\r))/;
  3848.                     my $first = "$1$line";
  3849.                     my $second = "$2";
  3850.                     my $third = $jclheader[1];
  3851.                     @jclheader = ($first, $second, $third);
  3852.                 }
  3853.                 }
  3854.             }
  3855.             # Now pass on the merged JCL header
  3856.             print $fileh @jclheader;
  3857.             } else {
  3858.             # The driver didn't create a JCL header, simply
  3859.             # prepend ours and then pass on the line which we
  3860.             # already have read
  3861.             print $fileh @jclprepend, @jclheader;
  3862.             }
  3863.         } else {
  3864.             # No merging of JCL header possible, simply prepend it
  3865.             print $fileh @jclprepend;
  3866.         }
  3867.         }
  3868.  
  3869.         # The rest of the job data
  3870.         my $buf;
  3871.             while (read(KID4_IN, $buf, 1024)) {
  3872.                 print $fileh $buf;
  3873.             }
  3874.  
  3875.         # A JCL trailer
  3876.         if (( @jclprepend > 1 ) && (!$driverjcl)) {
  3877.         print $fileh @jclappend;
  3878.         }
  3879.             
  3880.             if (!close $fileh) {
  3881.         close KID4_IN;
  3882.         close KID_MESSAGE;
  3883.         print KID_MESSAGE_IN
  3884.             "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  3885.         close KID_MESSAGE_IN;
  3886.         rip_die ("error closing $fileh",
  3887.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  3888.         }
  3889.         close KID4_IN;
  3890.  
  3891.             print $logh "tail process done writing data to STDOUT\n";
  3892.  
  3893.         # Handle signals of the backend interface
  3894.         if ($retval != $EXIT_PRINTED) {
  3895.         close KID_MESSAGE;
  3896.         print KID_MESSAGE_IN "4 $retval\n";
  3897.         close KID_MESSAGE_IN;
  3898.         exit $retval;
  3899.         }
  3900.  
  3901.         # Successful exit, inform main process
  3902.         close KID_MESSAGE;
  3903.         print KID_MESSAGE_IN "4 $EXIT_PRINTED\n";
  3904.         close KID_MESSAGE_IN;
  3905.  
  3906.         print $logh "KID4 finished\n";
  3907.             exit($EXIT_PRINTED);
  3908.         }
  3909.     }
  3910. }
  3911.  
  3912.  
  3913.  
  3914. ## Close the renderer process and wait until all kid processes finish.
  3915.  
  3916. sub closerendererhandle {
  3917.  
  3918.     my ($rendererhandle, $rendererpid) = @_;
  3919.  
  3920.     print $logh "${added_lf}Closing renderer\n";
  3921.  
  3922.     # Do it!
  3923.     close $rendererhandle;
  3924.  
  3925.     # Wait for all kid processes to finish or one kid process to fail
  3926.     close KID_MESSAGE_IN;
  3927.     while ((!$kidfailed) &&
  3928.        !(($kid3finished) &&
  3929.          ($kid4finished))) {
  3930.     my $message = <KID_MESSAGE>;
  3931.     chomp $message;
  3932.     if ($message =~ /(\d+)\s+(\d+)/) {
  3933.         my $kid_id = $1;
  3934.         my $exitstat = $2;
  3935.         print $logh "KID$kid_id exited with status $exitstat\n";
  3936.         if ($exitstat > 0) {
  3937.         $kidfailed = $exitstat;
  3938.         } elsif ($kid_id == 3) {
  3939.         $kid3finished = 1;
  3940.         } elsif ($kid_id == 4) {
  3941.         $kid4finished = 1;
  3942.         }
  3943.     }
  3944.     }
  3945.  
  3946.     close KID_MESSAGE;
  3947.  
  3948.     # If a kid failed, return the exit stat of this kid
  3949.     if ($kidfailed != 0) {
  3950.     $retval = $kidfailed;
  3951.     }
  3952.  
  3953.     print $logh "Renderer exit stat: $retval\n";
  3954.     # Wait for renderer child
  3955.     waitpid($rendererpid, 0);
  3956.     print $logh "Renderer process finished\n";
  3957.     return ($retval);
  3958. }
  3959.  
  3960.  
  3961.  
  3962. ## This function is only used when the input data is not
  3963. ## PostScript. Then it runs a filter which converts non-PostScript
  3964. ## files into PostScript. The user can choose which filter he wants
  3965. ## to use. The filter command line is provided by $fileconverter.
  3966.  
  3967. sub getfileconverterhandle {
  3968.  
  3969.     # Already read data must be converted, too
  3970.     my ($dat, $alreadyread) = @_;
  3971.  
  3972.     print $logh "${added_lf}Starting converter for non-PostScript files\n";
  3973.  
  3974.     # Determine with which command non-PostScript files are converted
  3975.     # to PostScript
  3976.     if ($fileconverter eq "") {
  3977.     if ($spoolerfileconverters->{$spooler}) {
  3978.         $fileconverter = $spoolerfileconverters->{$spooler};
  3979.     } else {
  3980.         for my $c (@fileconverters) {
  3981.         ($c =~ m/^\s*(\S+)\s+/) || ($c = m/^\s*(\S+)$/);
  3982.         my $command = $1;
  3983.         if( -x $command ){
  3984.             $fileconverter = $command;
  3985.         } else {
  3986.         for (split(':', $ENV{'PATH'})) {
  3987.             if (-x "$_/$command") {
  3988.             $fileconverter = $c;
  3989.             last;
  3990.             }
  3991.         }
  3992.         }
  3993.         if ($fileconverter ne "") {
  3994.             last;
  3995.         }
  3996.         }
  3997.     }
  3998.     if ($fileconverter eq "") {
  3999.         $fileconverter = "echo \"Cannot convert file to " .
  4000.         "PostScript!\" 1>&2";
  4001.     }
  4002.     }
  4003.  
  4004.     # Insert the page size into the $fileconverter
  4005.     if ($fileconverter =~ /\@\@([^@]+)\@\@PAGESIZE\@\@/) {
  4006.     # We always use the "header" option swt here, with a
  4007.     # non-PostScript file we have no "currentpage"
  4008.     my $optstr = $1;
  4009.     my $arg;
  4010.     my $sizestr = (($arg = $dat->{'args_byname'}{'PageSize'})
  4011.                ? $arg->{'header'}
  4012.                : "");
  4013.     if ($sizestr) {
  4014.         # Use wider margins so that the pages come out completely on
  4015.         # every printer model (especially HP inkjets)
  4016.         if ($fileconverter =~ /^\s*(a2ps)\s+/) {
  4017.         if (lc($sizestr) eq "letter") {
  4018.             $sizestr = "Letterdj";
  4019.         } elsif (lc($sizestr) eq "a4") {
  4020.             $sizestr = "A4dj";
  4021.         }
  4022.         }
  4023.         $optstr .= $sizestr;
  4024.     } else {
  4025.         $optstr = "";
  4026.     }
  4027.     $fileconverter =~ s/\@\@([^@]+)\@\@PAGESIZE\@\@/$optstr/;
  4028.     }
  4029.  
  4030.     # Insert the job title into the $fileconverter
  4031.     if ($fileconverter =~ /\@\@([^@]+)\@\@JOBTITLE\@\@/) {
  4032.     if ($do_docs) {
  4033.         $jobtitle =
  4034.         "Documentation for the $model";
  4035.     }
  4036.     my $titlearg = $1;
  4037.     my ($arg, $optstr);
  4038.     ($arg = $jobtitle) =~ s/\"/\\\"/g;
  4039.     if (($titlearg =~ /\"/) || $arg) {
  4040.         $optstr = $titlearg . ($titlearg =~ /\"/ ? '' : '"') .
  4041.         ($arg ? "$arg\"" : '"');
  4042.     } else {
  4043.         $optstr = "";
  4044.     }
  4045.     $fileconverter =~ s/\@\@([^@]+)\@\@JOBTITLE\@\@/$optstr/;
  4046.     }
  4047.  
  4048.     # Apply "pstops" when having used a file converter under CUPS, so
  4049.     # CUPS can stuff the default settings into the PostScript output
  4050.     # of the file converter (so all CUPS settings get also applied when
  4051.     # one prints the documentation pages (all other files we get
  4052.     # already converted to PostScript by CUPS).
  4053.     if ($spooler eq 'cups') {
  4054.     $fileconverter .=
  4055.         " | ${programdir}pstops '$rargs[0]' '$rargs[1]' '$rargs[2]' " .
  4056.         "'$rargs[3]' '$rargs[4]'";
  4057.     }
  4058.  
  4059.     # Variables for the kid processes reporting their state
  4060.  
  4061.     # Set up a pipe for the kids to pass their exit stat to the main process
  4062.     pipe KID_MESSAGE_CONV, KID_MESSAGE_CONV_IN;
  4063.  
  4064.     # When one kid fails put the exit stat here
  4065.     $convkidfailed = 0;
  4066.  
  4067.     # When a kid exits successfully, mark it here
  4068.     $kid1finished = 0;
  4069.     $kid2finished = 0;
  4070.  
  4071.     use IO::Handle;
  4072.     pipe KID1_IN, KID1;
  4073.     KID1->autoflush(1);
  4074.     my $kid1 = fork();
  4075.     if (!defined($kid1)) {
  4076.     close KID1;
  4077.     close KID1_IN;
  4078.         print $logh "$0: cannot fork for kid1!\n";
  4079.     rip_die ("can't fork for kid1",
  4080.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4081.     }
  4082.  
  4083.     if ($kid1) {
  4084.  
  4085.         # we are the parent; return a glob to the filehandle
  4086.         close KID1;
  4087.  
  4088.         return ( *KID1_IN, $kid1 );
  4089.  
  4090.     } else {
  4091.     # We go on reading the job data and stuff it into the file
  4092.     # converter
  4093.         close KID1_IN;
  4094.  
  4095.         pipe KID2_IN, KID2;
  4096.     KID2->autoflush(1);
  4097.         $kid2 = fork();
  4098.         if (!defined($kid2)) {
  4099.             print $logh "$0: cannot fork for kid2!\n";
  4100.         close KID1;
  4101.         close KID2;
  4102.         close KID2_IN;
  4103.         close KID_MESSAGE_CONV;
  4104.         print KID_MESSAGE_CONV_IN 
  4105.         "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  4106.         rip_die ("can't fork for kid2",
  4107.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4108.         }
  4109.         
  4110.         if ($kid2) {
  4111.             # parent, child of primary task; we are |$fileconverter|
  4112.             close KID2;
  4113.  
  4114.             print $logh "file converter PID kid2=$kid2\n";
  4115.         if (($debug) || ($spooler ne 'cups')) {
  4116.         print $logh "file converter command: $fileconverter\n";
  4117.         }
  4118.             
  4119.             if (!close STDIN && $! != $ESPIPE) {
  4120.         close KID1;
  4121.         close KID2_IN;
  4122.         close KID_MESSAGE_CONV;
  4123.         print KID_MESSAGE_CONV_IN 
  4124.             "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  4125.         close KID_MESSAGE_CONV_IN;
  4126.         rip_die ("Couldn't close STDIN in $kid2",
  4127.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4128.         }
  4129.             if (!open (STDIN, "<&KID2_IN")) {
  4130.         close KID1;
  4131.         close KID2_IN;
  4132.         close KID_MESSAGE_CONV;
  4133.         print KID_MESSAGE_CONV_IN 
  4134.             "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  4135.         close KID_MESSAGE_CONV_IN;
  4136.         rip_die ("Couldn't dup KID2_IN",
  4137.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4138.         }
  4139.             if (!close STDOUT) {
  4140.         close KID1;
  4141.         close KID2_IN;
  4142.         close KID_MESSAGE_CONV;
  4143.         print KID_MESSAGE_CONV_IN
  4144.             "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  4145.         close KID_MESSAGE_CONV_IN;
  4146.         rip_die ("Couldn't close STDOUT in $kid2",
  4147.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4148.         }
  4149.             if (!open (STDOUT, ">&KID1")) {
  4150.         close KID1;
  4151.         close KID2_IN;
  4152.         close KID_MESSAGE_CONV;
  4153.         print KID_MESSAGE_CONV_IN
  4154.             "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  4155.         close KID_MESSAGE_CONV_IN;
  4156.         rip_die ("Couldn't dup KID1",
  4157.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4158.         }
  4159.         if ($debug) {
  4160.         if (!open (STDERR, ">&$logh")) {
  4161.             close KID1;
  4162.             close KID2_IN;
  4163.             close KID_MESSAGE_CONV;
  4164.             print KID_MESSAGE_CONV_IN
  4165.             "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  4166.             close KID_MESSAGE_CONV_IN;
  4167.             rip_die ("Couldn't dup logh to stderr",
  4168.                  $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4169.         }
  4170.         }
  4171.  
  4172.         # Actually run the thing...
  4173.         modern_system("$fileconverter");
  4174.             if ($? != 0) {
  4175.         my $fileconverterretval = $? >> 8;
  4176.         print $logh "file converter return value: " .
  4177.             "$fileconverterretval\n";
  4178.         my $fileconvertersignal = $? & 127;
  4179.         print $logh "file converter received signal: ".
  4180.             "$fileconverterretval\n";
  4181.         close STDOUT;
  4182.         close KID1;
  4183.         close STDIN;
  4184.         close KID2_IN;
  4185.         # Handle signals
  4186.         if ($fileconvertersignal == SIGUSR1) {
  4187.             $retval = $EXIT_PRNERR;
  4188.         } elsif ($fileconvertersignal == SIGUSR2) {
  4189.             $retval = $EXIT_PRNERR_NORETRY;
  4190.         } elsif ($fileconvertersignal == SIGTTIN) {
  4191.             $retval = $EXIT_ENGAGED;
  4192.         }
  4193.         if ($retval != $EXIT_PRINTED) {
  4194.             close KID_MESSAGE_CONV;
  4195.             print KID_MESSAGE_CONV_IN "1 $retval\n";
  4196.             close KID_MESSAGE_CONV_IN;
  4197.             exit $retval;
  4198.         }
  4199.         # Evaluate fileconverter result
  4200.         if ($fileconverterretval == 0) {
  4201.             # Success, exit with 0 and inform main process
  4202.             close KID_MESSAGE_CONV;
  4203.             print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n";
  4204.             close KID_MESSAGE_CONV_IN;
  4205.             exit $EXIT_PRINTED;
  4206.         } else {
  4207.             # Unknown error
  4208.             close KID_MESSAGE_CONV;
  4209.             print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR\n";
  4210.             close KID_MESSAGE_CONV_IN;
  4211.             rip_die ("The file converter command line returned " . 
  4212.                  "an unrecognized error code " .
  4213.                  "$fileconverterretval.",
  4214.                  $EXIT_PRNERR);
  4215.         }
  4216.         }
  4217.         close STDOUT;
  4218.         close KID1;
  4219.         close STDIN;
  4220.         close KID2_IN;
  4221.         # When arrived here the fileconverter command line was
  4222.         # successful.
  4223.         # So exit with zero exit value here and inform the main process
  4224.         close KID_MESSAGE_CONV;
  4225.         print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n";
  4226.         close KID_MESSAGE_CONV_IN;
  4227.         # Wait for input child
  4228.         waitpid($kid1, 0);
  4229.         print $logh "KID1 finished\n";
  4230.         exit $EXIT_PRINTED;
  4231.         } else {
  4232.             # child, first part of the pipe, reading in the data from
  4233.         # standard input and stuffing it into the file converter
  4234.         # after putting in the already read data (in $alreadyread)
  4235.             close KID1;
  4236.         close KID2_IN;
  4237.  
  4238.         # At first pass the data which we have already read to the
  4239.         # filter
  4240.         print KID2 $alreadyread;
  4241.         # Then read the rest from standard input
  4242.         my $buf;
  4243.         while (read(STDIN, $buf, 1024)) { 
  4244.         print KID2 $buf; 
  4245.         }
  4246.  
  4247.             if (!close STDIN && $! != $ESPIPE) {
  4248.         close KID2;
  4249.         close KID_MESSAGE_CONV;
  4250.         print KID_MESSAGE_CONV_IN
  4251.             "2 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
  4252.         close KID_MESSAGE_CONV_IN;
  4253.         rip_die ("error closing STDIN",
  4254.              $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4255.         }
  4256.         close KID2;
  4257.  
  4258.             print $logh "tail process done reading data from STDIN\n";
  4259.  
  4260.         # Successful exit, inform main process
  4261.         close KID_MESSAGE_CONV;
  4262.         print KID_MESSAGE_CONV_IN "2 $EXIT_PRINTED\n";
  4263.         close KID_MESSAGE_CONV_IN;
  4264.  
  4265.         print $logh "KID2 finished\n";
  4266.             exit($EXIT_PRINTED);
  4267.         }
  4268.     }
  4269. }
  4270.  
  4271.  
  4272.  
  4273. ## Close the file conversion process and wait until all kid processes
  4274. ## finish.
  4275.  
  4276. sub closefileconverterhandle {
  4277.  
  4278.     my ($fileconverterhandle, $fileconverterpid) = @_;
  4279.  
  4280.     print $logh "${added_lf}Closing file converter\n";
  4281.  
  4282.     # Do it!
  4283.     close $fileconverterhandle;
  4284.  
  4285.     # Wait for all kid processes to finish or one kid process to fail
  4286.     close KID_MESSAGE_CONV_IN;
  4287.     while ((!$convkidfailed) &&
  4288.        !(($kid1finished) &&
  4289.          ($kid2finished))) {
  4290.     my $message = <KID_MESSAGE_CONV>;
  4291.     chomp $message;
  4292.     if ($message =~ /(\d+)\s+(\d+)/) {
  4293.         my $kid_id = $1;
  4294.         my $exitstat = $2;
  4295.         print $logh "KID$kid_id exited with status $exitstat\n";
  4296.         if ($exitstat > 0) {
  4297.         $convkidfailed = $exitstat;
  4298.         } elsif ($kid_id == 1) {
  4299.         $kid1finished = 1;
  4300.         } elsif ($kid_id == 2) {
  4301.         $kid2finished = 1;
  4302.         }
  4303.     }
  4304.     }
  4305.  
  4306.     close KID_MESSAGE_CONV;
  4307.  
  4308.     # If a kid failed, return the exit stat of this kid
  4309.     if ($convkidfailed != 0) {
  4310.     $retval = $convkidfailed;
  4311.     }
  4312.  
  4313.     print $logh "File converter exit stat: $retval\n";
  4314.     # Wait for fileconverter child
  4315.     waitpid($fileconverterpid, 0);
  4316.     print $logh "File converter process finished\n";
  4317.     return ($retval);
  4318. }
  4319.  
  4320.  
  4321.  
  4322. ## Generate the documentation page and return a filehandle to get it
  4323.  
  4324. sub getdocgeneratorhandle {
  4325.  
  4326.     # The data structure with the options
  4327.     my ($dat) = @_;
  4328.  
  4329.     print $logh "${added_lf}Generating documentation page for the $model\n";
  4330.  
  4331.     # Printer queue name
  4332.     my $printerstr;
  4333.     if ($printer) {
  4334.     $printerstr = $printer;
  4335.     } else {
  4336.     $printerstr = "<printer>";
  4337.     }
  4338.     
  4339.     # Spooler-specific differences
  4340.     my ($command,
  4341.     $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4342.     $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4343.     $booloptright,
  4344.     $numopt, $numoptleft, $numoptequal, $numoptright,
  4345.     $stropt, $stroptleft, $stroptequal, $stroptright,
  4346.     $optsep, $trailer, $custompagesize);
  4347.     if ($spooler eq 'cups') {
  4348.     ($command,
  4349.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4350.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4351.      $booloptright,
  4352.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4353.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4354.      $optsep, $trailer, $custompagesize) =
  4355.          ("lpr -P $printerstr ",
  4356.           "-o ", "", "=", "",
  4357.           "-o ", "no", "", "=", "",
  4358.           "-o ", "", "=", "",
  4359.           "-o ", "", "=", "",
  4360.           " "," <file>",
  4361.           "\n  Custom size: -o PageSize=Custom." .
  4362.           "<width>x<height>[<unit>]\n" .
  4363.           "               Units: pt (default), in, cm, mm\n" .
  4364.           "  Example: -o PageSize=Custom.4.0x6.0in\n");
  4365.     } elsif ($spooler eq 'lpd') {
  4366.     ($command,
  4367.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4368.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4369.      $booloptright,
  4370.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4371.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4372.      $optsep, $trailer, $custompagesize) =
  4373.          ("lpr -P $printerstr -J \"",
  4374.           "", "", "=", "",
  4375.           "", "", "", "=", "",
  4376.           "", "", "=", "",
  4377.           "", "", "=", "",
  4378.           " ", "\" <file>",
  4379.           "\n  Custom size: PageSize=Custom." .
  4380.           "<width>x<height>[<unit>]\n" .
  4381.           "               Units: pt (default), in, cm, mm\n" .
  4382.           "  Example: PageSize=Custom.4.0x6.0in\n");
  4383.     } elsif ($spooler eq 'gnulpr') {
  4384.     ($command,
  4385.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4386.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4387.      $booloptright,
  4388.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4389.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4390.      $optsep, $trailer, $custompagesize) =
  4391.          ("lpr -P $printerstr ",
  4392.           "-o ", "", "=", "",
  4393.           "-o ", "", "", "=", "",
  4394.           "-o ", "", "=", "",
  4395.           "-o ", "", "=", "",
  4396.           " "," <file>",
  4397.           "\n  Custom size: -o PageSize=Custom." .
  4398.           "<width>x<height>[<unit>]\n" .
  4399.           "               Units: pt (default), in, cm, mm\n" .
  4400.           "  Example: -o PageSize=Custom.4.0x6.0in\n");
  4401.     } elsif ($spooler eq 'lprng') {
  4402.     ($command,
  4403.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4404.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4405.      $booloptright,
  4406.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4407.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4408.      $optsep, $trailer, $custompagesize) =
  4409.          ("lpr -P $printerstr ",
  4410.           "-Z ", "", "=", "",
  4411.           "-Z ", "", "", "=", "",
  4412.           "-Z ", "", "=", "",
  4413.           "-Z ", "", "=", "",
  4414.           " "," <file>",
  4415.           "\n  Custom size: -Z PageSize=Custom." .
  4416.           "<width>x<height>[<unit>]\n" .
  4417.           "               Units: pt (default), in, cm, mm\n" .
  4418.           "  Example: -Z PageSize=Custom.4.0x6.0in\n");
  4419.     } elsif ($spooler eq 'ppr') {
  4420.     ($command,
  4421.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4422.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4423.      $booloptright,
  4424.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4425.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4426.      $optsep, $trailer, $custompagesize) =
  4427.          ("ppr -d $printerstr --ripopts \"",
  4428.           "", "", "=", "",
  4429.           "", "", "", "=", "",
  4430.           "", "", "=", "",
  4431.           "", "", "=", "",
  4432.           " ","\" <file>",
  4433.           "\n  Custom size: PageSize=Custom." .
  4434.           "<width>x<height>[<unit>]\n" .
  4435.           "               Units: pt (default), in, cm, mm\n" .
  4436.           "  Example: PageSize=Custom.4.0x6.0in\n");
  4437.     } elsif ($spooler eq 'ppr-int') {
  4438.     ($command,
  4439.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4440.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4441.      $booloptright,
  4442.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4443.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4444.      $optsep, $trailer, $custompagesize) =
  4445.          ("ppr -d $printerstr -i \"",
  4446.           "", "", "=", "",
  4447.           "", "", "", "=", "",
  4448.           "", "", "=", "",
  4449.           "", "", "=", "",
  4450.           " ","\" <file>",
  4451.           "\n  Custom size: PageSize=Custom." .
  4452.           "<width>x<height>[<unit>]\n" .
  4453.           "               Units: pt (default), in, cm, mm\n" .
  4454.           "  Example: PageSize=Custom.4.0x6.0in\n");
  4455.     } elsif ($spooler eq 'cps') {
  4456.     ($command,
  4457.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4458.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4459.      $booloptright,
  4460.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4461.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4462.      $optsep, $trailer, $custompagesize) =
  4463.          ("lpr -P $printerstr ",
  4464.           "-o ", "", "=", "",
  4465.           "-o ", "", "", "=", "",
  4466.           "-o ", "", "=", "",
  4467.           "-o ", "", "=", "",
  4468.           " "," <file>",
  4469.           "\n  Custom size: -o PageSize=Custom." .
  4470.           "<width>x<height>[<unit>]\n" .
  4471.           "               Units: pt (default), in, cm, mm\n" .
  4472.           "  Example: -o PageSize=Custom.4.0x6.0in\n");
  4473.     } elsif ($spooler eq 'direct') {
  4474.     ($command,
  4475.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4476.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4477.      $booloptright,
  4478.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4479.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4480.      $optsep, $trailer, $custompagesize) =
  4481.          ("$programname -P $printerstr ",
  4482.           "-o ", "", "=", "",
  4483.           "-o ", "", "", "=", "",
  4484.           "-o ", "", "=", "",
  4485.           "-o ", "", "=", "",
  4486.           " "," <file>",
  4487.           "\n  Custom size: -o PageSize=Custom." .
  4488.           "<width>x<height>[<unit>]\n" .
  4489.           "               Units: pt (default), in, cm, mm\n" .
  4490.           "  Example: -o PageSize=Custom.4.0x6.0in\n");
  4491.     } elsif ($spooler eq 'pdq') {
  4492.     ($command,
  4493.      $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
  4494.      $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
  4495.      $booloptright,
  4496.      $numopt, $numoptleft, $numoptequal, $numoptright,
  4497.      $stropt, $stroptleft, $stroptequal, $stroptright,
  4498.      $optsep, $trailer, $custompagesize) =
  4499.          ("pdq -P $printerstr ",
  4500.           "-o", "", "_", "",
  4501.           "-o", "no", "", "_", "",
  4502.           "-a", "", "=", "",
  4503.           "-a", "", "=", "",
  4504.           " "," <file>",
  4505.           "\n" .
  4506.           "Option 'PageWidth':\n". 
  4507.           "  Page Width (for \"Custom\" page size)\n" .
  4508.           "  A floating point number argument\n" .
  4509.           "  Range: 0 <= x <= 100000\n" .
  4510.           "  Example: -aPageWidth=123.4\n" .
  4511.           "\n" .
  4512.           "Option 'PageHeight':\n" .
  4513.           "  Page Height (for \"Custom\" page size)\n" .
  4514.           "  A floating point number argument\n" .
  4515.           "  Range: 0 <= x <= 100000\n" .
  4516.           "  Example: -aPageHeight=234.5\n" .
  4517.           "\n" .
  4518.           "Option 'PageSizeUnit':\n" .
  4519.           "  Unit (for \"Custom\" page size)\n" .
  4520.           "  An enumerated choice argument\n" .
  4521.           "  Possible choices:\n" .
  4522.           "   o -oPageSizeUnit_pt: Points (1/72 inch)\n" .
  4523.           "   o -oPageSizeUnit_in: Inches\n" .
  4524.           "   o -oPageSizeUnit_cm: cm\n" .
  4525.           "   o -oPageSizeUnit_mm: mm\n" .
  4526.           "  Example: -oPageSizeUnit_mm\n");
  4527.     }
  4528.  
  4529.     # Variables for the kid processes reporting their state
  4530.  
  4531.     # Set up a pipe for the kids to pass their exit stat to the main process
  4532.     pipe KID_MESSAGE_DOC, KID_MESSAGE_DOC_IN;
  4533.  
  4534.     # When the kid fails put the exit stat here
  4535.     $dockidfailed = 0;
  4536.  
  4537.     # When the kid exits successfully, mark it here
  4538.     $kid0finished = 0;
  4539.  
  4540.     use IO::Handle;
  4541.     pipe KID0_IN, KID0;
  4542.     KID0->autoflush(1);
  4543.     my $kid0 = fork();
  4544.     if (!defined($kid0)) {
  4545.     close KID0;
  4546.     close KID0_IN;
  4547.         print $logh "$0: cannot fork for kid0!\n";
  4548.     rip_die ("can't fork for kid0",
  4549.          $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  4550.     }
  4551.  
  4552.     if ($kid0) {
  4553.         # we are the parent; return a glob to the filehandle
  4554.         close KID0;
  4555.     print $logh "Documentation page generator PID kid0=$kid0\n";
  4556.         return ( *KID0_IN, $kid0 );
  4557.     }
  4558.  
  4559.     # we are the kid; we generate the documentation page
  4560.  
  4561.     close KID0_IN;
  4562.  
  4563.     # Kill data on STDIN to satisfy PPR
  4564.     if (($spooler eq 'ppr_int') || ($spooler eq 'ppr')) {
  4565.     while (my $dummy = <STDIN>) {};
  4566.     }
  4567.     close STDIN
  4568.     or print $logh "Error closing STDIN for docs print\n";
  4569.  
  4570.     # write the job into KID0
  4571.     select KID0;
  4572.  
  4573.     print "\nInvokation summary for the $model\n\n";
  4574.     print "Use the following command line:\n\n";
  4575.     if ($booloptfalseprefix) {
  4576.     # I think that what you want to indicate is that the prefix for a false
  4577.     # boolean has this form:  xxx [no]<switch> or something similar
  4578.     print "   ${command}${enumopt}${enumoptleft}<option>" .
  4579.         "${enumoptequal}<choice>${enumoptright}${optsep}" .
  4580.         "${boolopt}${booloptleft}\[${booloptfalseprefix}\]<switch>" .
  4581.         "${booloptright}${optsep}" .
  4582.         "${numopt}${numoptleft}<num. option>${numoptequal}" .
  4583.         "<value>${numoptright}${optsep}" .
  4584.         "${stropt}${stroptleft}<string option>${stroptequal}" .
  4585.         "<string>${stroptright}" .
  4586.         "${trailer}\n\n";
  4587.     } else {
  4588.     print "   ${command}${enumopt}${enumoptleft}<option>" .
  4589.         "${enumoptequal}<choice>${enumoptright}${optsep}" .
  4590.         "${boolopt}${booloptleft}<switch>${booloptequal}" .
  4591.         "<True/False>${booloptright}${optsep}" .
  4592.         "${numopt}${numoptleft}<num. option>${numoptequal}" .
  4593.         "<value>${numoptright}${optsep}" .
  4594.         "${stropt}${stroptleft}<string option>${stroptequal}" .
  4595.         "<string>${stroptright}" .
  4596.         "${trailer}\n\n";
  4597.     }
  4598.     
  4599.     print "The following options are available for this printer:\n\n";
  4600.  
  4601.     for my $arg (@{$dat->{'args'}}) {
  4602.         my ($name,
  4603.             $type,
  4604.             $comment,
  4605.             $spot,
  4606.             $default) = ($arg->{'name'},
  4607.                          $arg->{'type'},
  4608.                          $arg->{'comment'},
  4609.                          $arg->{'spot'},
  4610.                          $arg->{'default'});
  4611.  
  4612.     # Is this really an option? Otherwise skip it.
  4613.     next if (!$type);
  4614.  
  4615.     # We don't need "PageRegion", we have "PageSize"
  4616.     next if ($name eq "PageRegion");
  4617.  
  4618.     # Skip enumerated choice options with only one choice
  4619.     next if (($type eq 'enum') && ($#{$arg->{'vals'}} < 1));
  4620.  
  4621.     my $commentstr = "";
  4622.     if ($comment) {
  4623.         $commentstr = "  $comment\n";
  4624.     }
  4625.  
  4626.     my $typestr;
  4627.         if ($type eq "enum") {
  4628.         $typestr = "An enumerated choice";
  4629.     } elsif ($type eq "bool") {
  4630.         $typestr = "A boolean";
  4631.     } elsif ($type eq "int") {
  4632.         $typestr = "An integer number";
  4633.     } elsif ($type eq "float") {
  4634.         $typestr = "A floating point number";
  4635.     } elsif (($type eq "string") || ($type eq "password")) {
  4636.         $typestr = "A string";
  4637.     }
  4638.  
  4639.         print "Option '$name':\n$commentstr  $typestr argument\n";
  4640.         print "  This options corresponds to a JCL command\n" if ($arg->{'style'} eq 'J');
  4641.         
  4642.         if ($type eq 'bool') {
  4643.             print "  Possible choices:\n";
  4644.         if ($booloptfalseprefix) {
  4645.         print "   o $name: $arg->{'comment_true'}\n";
  4646.         print "   o $booloptfalseprefix$name: " .
  4647.             "$arg->{'comment_false'}\n";
  4648.         if (defined($default)) {
  4649.             my $defstr = ($default ? "" : "$booloptfalseprefix");
  4650.             print "  Default: $defstr$name\n";
  4651.         }
  4652.         print "  Example: ${boolopt}${booloptleft}${name}" .
  4653.             "${booloptright}\n";
  4654.         } else {
  4655.         print "   o True: $arg->{'comment_true'}\n";
  4656.         print "   o False: $arg->{'comment_false'}\n";
  4657.         if (defined($default)) {
  4658.             my $defstr = ($default ? "True" : "False");
  4659.             print "  Default: $defstr\n";
  4660.         }
  4661.         print "  Example: ${boolopt}${booloptleft}${name}" .
  4662.             "${booloptequal}True${booloptright}\n";
  4663.         }
  4664.         } elsif ($type eq 'enum') {
  4665.             print "  Possible choices:\n";
  4666.             my $exarg;
  4667.         my $havecustomsize = 0;
  4668.             for (@{$arg->{'vals'}}) {
  4669.                 my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  4670.                 print "   o $choice: $comment\n";
  4671.         if (($name eq "PageSize") && ($choice eq "Custom")) {
  4672.             $havecustomsize = 1;
  4673.         }
  4674.                 $exarg=$choice;
  4675.             }
  4676.             if (defined($default)) {
  4677.                 print "  Default: $default\n";
  4678.             }
  4679.             print "  Example: ${enumopt}${enumoptleft}${name}" .
  4680.         "${enumoptequal}${exarg}${enumoptright}\n";
  4681.         if ($havecustomsize) {
  4682.         print $custompagesize;
  4683.         }
  4684.         } elsif ($type eq 'int' or $type eq 'float') {
  4685.             my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  4686.             my $exarg;
  4687.             if (defined($max)) {
  4688.                 print "  Range: $min <= x <= $max\n";
  4689.                 $exarg=$max;
  4690.             }
  4691.             if (defined($default)) {
  4692.                 print "  Default: $default\n";
  4693.                 $exarg=$default;
  4694.             }
  4695.             if (!$exarg) { $exarg=0; }
  4696.             print "  Example: ${numopt}${numoptleft}${name}" .
  4697.         "${numoptequal}${exarg}${numoptright}\n";
  4698.         } elsif ($type eq 'string' or $type eq 'password') {
  4699.             my $maxlength = $arg->{'maxlength'};
  4700.             if (defined($maxlength)) {
  4701.                 print "  Maximum length: $maxlength characters\n";
  4702.             }
  4703.             if (defined($default)) {
  4704.                 print "  Default: $default\n";
  4705.             }
  4706.             print "  Examples/special settings:\n";
  4707.             for (@{$arg->{'vals'}}) {
  4708.                 my ($value, $comment, $driverval, $proto) = 
  4709.             ($_->{'value'}, $_->{'comment'}, $_->{'driverval'},
  4710.              $arg->{'proto'});
  4711.         # Retrieve the original string from the prototype
  4712.         # and the driverval
  4713.         my $string;
  4714.         if ($proto) {
  4715.             my $s = index($proto, '%s');
  4716.             my $l = length($driverval) - length($proto) + 2;
  4717.             if (($s < 0) || ($l < 0)) {
  4718.             $string = $driverval;
  4719.             } else {
  4720.             $string = substr($driverval, $s, $l);
  4721.             }
  4722.         } else {
  4723.             $string = $driverval;
  4724.         }
  4725.         print "   o ${stropt}${stroptleft}${name}" .
  4726.             "${stroptequal}${value}${stroptright}";
  4727.         if (($value ne $string) || ($comment ne $value)) {
  4728.             print " (";
  4729.         }
  4730.         if ($value ne $string) {
  4731.             if ($string eq '') {
  4732.             print "blank string";
  4733.             } else {
  4734.             print "\"$string\"";
  4735.             }
  4736.         }
  4737.         if (($value ne $string) && ($comment ne $value)) {
  4738.             print ", ";
  4739.         }
  4740.         if ($value ne $comment) {
  4741.             print "$comment";
  4742.         }
  4743.         if (($value ne $string) || ($comment ne $value)) {
  4744.             print ")";
  4745.         }
  4746.         print "\n";
  4747.             }
  4748.         }
  4749.  
  4750.         print "\n";
  4751.     }
  4752.     
  4753.     select STDOUT;
  4754.     close KID0 
  4755.         or print $logh "Error closing KID0 for docs print\n";
  4756.     close STDOUT
  4757.         or print $logh "Error closing STDOUT for docs print\n";
  4758.  
  4759.     # Finished successfully, inform main process
  4760.     close KID_MESSAGE_DOC;
  4761.     print KID_MESSAGE_DOC_IN "0 $EXIT_PRINTED\n";
  4762.     close KID_MESSAGE_DOC_IN;
  4763.  
  4764.     print $logh "KID0 finished\n";
  4765.     exit($EXIT_PRINTED);
  4766.  
  4767. }
  4768.  
  4769.  
  4770.  
  4771. ## Close the documentation page generation process and wait until the
  4772. ## kid process finishes.
  4773.  
  4774. sub closedocgeneratorhandle {
  4775.  
  4776.     my ($handle, $pid) = @_;
  4777.  
  4778.     print $logh "${added_lf}Closing documentation page generator\n";
  4779.  
  4780.     # Do it!
  4781.     close $handle;
  4782.  
  4783.     # Wait for the kid process to finish or the kid process to fail
  4784.     close KID_MESSAGE_DOC_IN;
  4785.     while ((!$dockidfailed) &&
  4786.        (!$kid0finished)) {
  4787.     my $message = <KID_MESSAGE_DOC>;
  4788.     chomp $message;
  4789.     if ($message =~ /(\d+)\s+(\d+)/) {
  4790.         my $kid_id = $1;
  4791.         my $exitstat = $2;
  4792.         print $logh "KID$kid_id exited with status $exitstat\n";
  4793.         if ($exitstat > 0) {
  4794.         $dockidfailed = $exitstat;
  4795.         } elsif ($kid_id eq "0") {
  4796.         $kid0finished = 1;
  4797.         }
  4798.     }
  4799.     }
  4800.  
  4801.     close KID_MESSAGE_DOC;
  4802.  
  4803.     # If the kid failed, return the exit stat of the kid
  4804.     if ($dockidfailed != 0) {
  4805.     $retval = $dockidfailed;
  4806.     }
  4807.  
  4808.     print $logh "Documentation page generator exit stat: $retval\n";
  4809.     # Wait for fileconverter child
  4810.     waitpid($pid, 0);
  4811.     print $logh "Documentation page generator process finished\n";
  4812.     return ($retval);
  4813. }
  4814.  
  4815.  
  4816.  
  4817. # Find an argument by name in a case-insensitive way
  4818. sub argbyname {
  4819.     my $name = $_[0];
  4820.  
  4821.     for my $arg (@{$dat->{'args'}}) {
  4822.         return $arg if (lc($name) eq lc($arg->{'name'}));
  4823.     }
  4824.  
  4825.     return undef;
  4826. }
  4827.  
  4828. sub valbyname {
  4829.     my ($arg,$name) = @_;
  4830.  
  4831.     for my $val (@{$arg->{'vals'}}) {
  4832.         return $val if (lc($name) eq lc($val->{'value'}));
  4833.     }
  4834.  
  4835.     return undef;
  4836. }
  4837.  
  4838. # Write a Good-Bye letter and clean up before committing suicide (send
  4839. # error message to caller)
  4840.  
  4841. sub rip_die {
  4842.     my ($message, $exitstat) = @_;
  4843.     my $errmsg = "$!";
  4844.     my $errcod = $! + 0;
  4845.  
  4846.     # Close the documentation page generator (if it was used)
  4847.     if ($docgeneratorpid) {
  4848.     if ($kid0) {
  4849.         print $logh "Killing process $kid0 (KID0)\n";
  4850.         kill(9, $kid0);
  4851.     }
  4852.     $docgeneratorpid = 0;
  4853.     }
  4854.  
  4855.     # Close the file converter (if it was used)
  4856.     if ($fileconverterpid) {
  4857.     if ($kid2) {
  4858.         print $logh "Killing process $kid2 (KID2)\n";
  4859.         kill(9, $kid2);
  4860.     }
  4861.     if ($kid1) {
  4862.         print $logh "Killing process $kid1 (KID1)\n";
  4863.         kill(9, $kid1);
  4864.     }
  4865.     $fileconverterpid = 0;
  4866.     }
  4867.  
  4868.     # Close the renderer
  4869.     if ($rendererpid) {
  4870.     if ($kid4) {
  4871.         print $logh "Killing process $kid4 (KID4)\n";
  4872.         kill(9, $kid4);
  4873.     }
  4874.     if ($kid3) {
  4875.         print $logh "Killing process $kid3 (KID3)\n";
  4876.         kill(9, $kid3);
  4877.     }
  4878.     $rendererpid = 0;
  4879.     }
  4880.  
  4881.     print $logh "Process dying with \"$message\", exit stat: $exitstat\n\terror: $errmsg ($errcod)\n";
  4882.     if ($spooler eq 'ppr_int') {
  4883.     # Special error handling for PPR intefaces
  4884.     $message =~ s/\\/\\\\/;
  4885.     $message =~ s/\"/\\\"/;
  4886.     my @messagelines = split("\n", $message);
  4887.     my $firstline = "TRUE";
  4888.     for my $line (@messagelines) {
  4889.         modern_system("lib/alert $printer $firstline \"$line\"");
  4890.         $firstline = "FALSE";
  4891.     }
  4892.     } else {
  4893.     print STDERR $message . "\n";
  4894.     }
  4895.     if ($debug) {
  4896.     use Data::Dumper;
  4897.     local $Data::Dumper::Purity=1;
  4898.     local $Data::Dumper::Indent=1;
  4899.     print $logh Dumper($dat);
  4900.     }
  4901.     exit $exitstat;
  4902. }
  4903.  
  4904. # Signal handling routines
  4905.  
  4906. sub set_exit_prnerr {
  4907.     $retval = $EXIT_PRNERR;
  4908. }
  4909.  
  4910. sub set_exit_prnerr_noretry {
  4911.     $retval = $EXIT_PRNERR_NORETRY;
  4912. }
  4913.  
  4914. sub set_exit_engaged {
  4915.     $retval = $EXIT_ENGAGED;
  4916. }
  4917.  
  4918. # Read the config file
  4919.  
  4920. sub readConfFile {
  4921.     my ($file) = @_;
  4922.  
  4923.     my %conf;
  4924.     # Read config file if present
  4925.     if (open CONF, "< $file") {
  4926.     while (<CONF>)
  4927.     {
  4928.         $conf{$1}="$2" if (m/^\s*([^\#\s]\S*)\s*:\s*(.*?)\s*$/);
  4929.     }
  4930.     close CONF;
  4931.     }
  4932.  
  4933.     return %conf;
  4934. }
  4935.  
  4936. sub removeunprintables {
  4937.     # Remove unprintable characters
  4938.     my $str = $_[0];
  4939.     $str =~ s/[\x00-\x1f]//g;
  4940.     return $str;
  4941. }
  4942.  
  4943. sub removeshellescapes {
  4944.     # Remove shell escape characters
  4945.     my $str = $_[0];
  4946.     $str =~ s/[\|<>&!\$\'\"\#\*\?\(\)\[\]\{\}]//g;
  4947.     return $str;
  4948. }
  4949.  
  4950. sub removespecialchars {
  4951.     # Remove unprintable and shell escape characters
  4952.     return removeshellescapes(removeunprintables($_[0]));
  4953. }
  4954.  
  4955. sub unhtmlify {
  4956.     my $str = $_[0];
  4957.  
  4958.     # Replace HTML/XML entities by the original characters
  4959.     $str =~ s/\'/\'/g;
  4960.     $str =~ s/\"/\"/g;
  4961.     $str =~ s/\>/\>/g;
  4962.     $str =~ s/\</\</g;
  4963.     $str =~ s/\&/\&/g;
  4964.  
  4965.     # Replace special entities by job data
  4966.     $str =~ s/\&job;/$jobid/g;
  4967.     $str =~ s/\&user;/$jobuser/g;
  4968.     $str =~ s/\&host;/$jobhost/g;
  4969.     $str =~ s/\&title;/$jobtitle/g;
  4970.     $str =~ s/\&copies;/$copies/g;
  4971.     $str =~ s/\&options;/$optstr/g;
  4972.     
  4973.     my ($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0..5];
  4974.     my $yearstr = sprintf("%04d", $year + 1900);
  4975.     my $monstr = sprintf("%02d", $mon + 1);
  4976.     my $mdaystr = sprintf("%02d", $mday);
  4977.     my $hourstr = sprintf("%02d", $hour);
  4978.     my $minstr = sprintf("%02d", $min);
  4979.     my $secstr = sprintf("%02d", $sec);
  4980.  
  4981.     $str =~ s/\&year;/$yearstr/g;
  4982.     $str =~ s/\&month;/$monstr/g;
  4983.     $str =~ s/\&date;/$mdaystr/g;
  4984.     $str =~ s/\&hour;/$hourstr/g;    
  4985.     $str =~ s/\&min;/$minstr/g;    
  4986.     $str =~ s/\&sec;/$secstr/g;    
  4987.     
  4988.     return $str;
  4989. }
  4990.  
  4991. sub unhexify {
  4992.     # Replace hex notation for unprintable characters in PPD files
  4993.     # by the actual characters ex: "<0A>" --> chr(hex("0A"))
  4994.     my ($input) = @_;
  4995.     my $output = "";
  4996.     my $hexmode = 0;
  4997.     my $firstdigit = "";
  4998.     for (my $i = 0; $i < length($input); $i ++) {
  4999.     my $c = substr($input, $i, 1);
  5000.     if ($hexmode) {
  5001.         if ($c eq ">") {
  5002.         # End of hex string
  5003.         $hexmode = 0;
  5004.         } elsif ($c =~ /^[0-9a-fA-F]$/) {
  5005.         # Hexadecimal digit, two of them give a character
  5006.         if ($firstdigit ne "") {
  5007.             $output .= chr(hex("$firstdigit$c"));
  5008.             $firstdigit = "";
  5009.         } else {
  5010.             $firstdigit = $c;
  5011.         }
  5012.         }
  5013.     } else {
  5014.         if ($c eq "<") {
  5015.         # Beginning of hex string
  5016.         $hexmode = 1;
  5017.         } else {
  5018.         # Normal character
  5019.         $output .= $c;
  5020.         }
  5021.     }
  5022.     }
  5023.     return $output;
  5024. }
  5025.  
  5026. sub undossify( $ ) {
  5027.     # Remove "dossy" line ends ("\r\n") from a string
  5028.     my $str = $_[0];
  5029.     $str =~ s/\r\n/\n/gs;
  5030.     $str =~ s/\r$//s;
  5031.     return( $str );
  5032. }
  5033.  
  5034. sub checkarg {
  5035.     # Check if there is already an argument record $argname in $dat, if not,
  5036.     # create one
  5037.     my ($dat, $argname) = @_;
  5038.     return if defined($dat->{'args_byname'}{$argname});
  5039.     # argument record
  5040.     my $rec;
  5041.     $rec->{'name'} = $argname;
  5042.     # Insert record in 'args' array for browsing all arguments
  5043.     push(@{$dat->{'args'}}, $rec);
  5044.     # 'args_byname' hash for looking up arguments by name
  5045.     $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}];
  5046.     # Default execution style is 'G' (PostScript) since all arguments for
  5047.     # which we don't find "*Foomatic..." keywords are usual PostScript
  5048.     # options
  5049.     $dat->{'args_byname'}{$argname}{'style'} = 'G';
  5050.     # Default prototype for code to insert, used by enum options
  5051.     $dat->{'args_byname'}{$argname}{'proto'} = '%s';
  5052.     # stop Perl nattering about undefined to string comparisons
  5053.     $dat->{'args_byname'}{$argname}{'type'} = '';
  5054.     print $logh "Added option $argname\n";
  5055. }
  5056.  
  5057. sub checksetting {
  5058.     # Check if there is already an choice record $setting in the $argname
  5059.     # argument in $dat, if not, create one
  5060.     my ($dat, $argname, $setting) = @_;
  5061.     return if 
  5062.     defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting});
  5063.     # setting record
  5064.     my $rec;
  5065.     $rec->{'value'} = $setting;
  5066.     # Insert record in 'vals' array for browsing all settings
  5067.     push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec);
  5068.     # 'vals_byname' hash for looking up settings by name
  5069.     $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} = 
  5070.     $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}];
  5071. }
  5072.  
  5073. sub removearg {
  5074.     # remove the argument record $argname from $dat
  5075.     my ($dat, $argname) = @_;
  5076.     return if !defined($dat->{'args_byname'}{$argname});
  5077.     # Remove 'args_byname' hash for looking up arguments by name
  5078.     delete $dat->{'args_byname'}{$argname};
  5079.     # Remove argument itself
  5080.     for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
  5081.     if ($dat->{'args'}[$i]{'name'} eq $argname) {
  5082.         print $logh "Removing option " .
  5083.         $argname . "\n";
  5084.         splice(@{$dat->{'args'}}, $i, 1);
  5085.         last;
  5086.     }
  5087.     }
  5088. }
  5089.  
  5090. sub removepsargs {
  5091.     # remove all records of PostScript arguments from $dat
  5092.     my ($dat) = @_;
  5093.     return if !defined($dat);
  5094.     for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
  5095.     if ($dat->{'args'}[$i]{'style'} eq 'G') {
  5096.         print $logh "Removing PostScript option " .
  5097.         $dat->{'args'}[$i]{'name'} . "\n";
  5098.         # Remove 'args_byname' hash for looking up arguments by name
  5099.         delete $dat->{'args_byname'}{$dat->{'args'}[$i]{'name'}};
  5100.         # Remove argument itself
  5101.         splice(@{$dat->{'args'}}, $i, 1);
  5102.         $i --;
  5103.     }
  5104.     }
  5105. }
  5106.  
  5107. sub checkoptionvalue {
  5108.  
  5109.     ## This function checks whether a given value is valid for a given
  5110.     ## option. If yes, it returns a cleaned value (e. g. always 0 or 1
  5111.     ## for boolean options), otherwise "undef". If $forcevalue is set,
  5112.     ## we always determine a corrected value to insert (we never return
  5113.     ## "undef").
  5114.  
  5115.     # Is $value valid for the option named $argname?
  5116.     my ($dat, $argname, $value, $forcevalue) = @_;
  5117.  
  5118.     # Record for option $argname
  5119.     my $arg = $dat->{'args_byname'}{$argname};
  5120.     $arg->{'type'} = '' if not defined $arg->{'type'};
  5121.  
  5122.     if ($arg->{'type'} eq 'bool') {
  5123.     my $lcvalue = lc($value);
  5124.     if ((($lcvalue) eq 'true') ||
  5125.         (($lcvalue) eq 'on') ||
  5126.         (($lcvalue) eq 'yes') ||
  5127.         (($lcvalue) eq '1')) {
  5128.         return 1;
  5129.     } elsif ((($lcvalue) eq 'false') ||
  5130.          (($lcvalue) eq 'off') ||
  5131.          (($lcvalue) eq 'no') ||
  5132.          (($lcvalue) eq '0')) {
  5133.         return 0;
  5134.     } elsif ($forcevalue) {
  5135.         # This maps Unknown to mean False.  Good?  Bad?
  5136.         # It was done so in Foomatic 2.0.x, too.
  5137.         my $name = $arg->{'name'};
  5138.         print $logh 
  5139.         "The value $value for $name is not a " .
  5140.         "choice!\n" .
  5141.         " --> Using False instead!\n";
  5142.         return 0;
  5143.     }
  5144.     } elsif ($arg->{'type'} eq 'enum') {
  5145.     if ($value =~ /^None$/i) {
  5146.         return 'None';
  5147.     } elsif (defined($arg->{'vals_byname'}{$value})) {
  5148.         return $value;
  5149.     } elsif ((($arg->{'name'} eq "PageSize") ||
  5150.           ($arg->{'name'} eq "PageRegion")) &&
  5151.          (defined($arg->{'vals_byname'}{'Custom'})) &&
  5152.          ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) {
  5153.         # Custom paper size
  5154.         return $value;
  5155.     } elsif ($forcevalue) {
  5156.         # wtf!?  that's not a choice!
  5157.         my $name = $arg->{'name'};
  5158.         # Return the first entry of the list
  5159.         my $firstentry = $arg->{'vals'}[0]{'value'};
  5160.         print $logh 
  5161.         "The value $value for $name is not a " .
  5162.         "choice!\n" .
  5163.         " --> Using $firstentry instead!\n";
  5164.         return $firstentry;
  5165.     }
  5166.     } elsif (($arg->{'type'} eq 'int') ||
  5167.          ($arg->{'type'} eq 'float')) {
  5168.     if (($value <= $arg->{'max'}) &&
  5169.         ($value >= $arg->{'min'})) {
  5170.         return $value;
  5171.     } elsif ($forcevalue) {
  5172.         my $name = $arg->{'name'};
  5173.         my $newvalue;
  5174.         if ($value > $arg->{'max'}) {
  5175.         $newvalue = $arg->{'max'}
  5176.         } elsif ($value < $arg->{'min'}) {
  5177.         $newvalue = $arg->{'min'}
  5178.         }
  5179.         print $logh 
  5180.         "The value $value for $name is out of " .
  5181.         "range!\n" .
  5182.         " --> Using $newvalue instead!\n";
  5183.         return $newvalue;
  5184.     }
  5185.     } elsif (($arg->{'type'} eq 'string') ||
  5186.          ($arg->{'type'} eq 'password')) {
  5187.     if (defined($arg->{'vals_byname'}{$value})) {
  5188.         my $name = $arg->{'name'};
  5189.         print $logh 
  5190.         "The value $value for $name is a predefined choice\n";
  5191.         return $value;
  5192.     } elsif (stringvalid($dat, $argname, $value)) {
  5193.         # Check whether the string is one of the enumerated choices
  5194.         my $sprintfproto = $arg->{'proto'};
  5195.         $sprintfproto =~ s/\%(?!s)/\%\%/g;
  5196.         my $driverval = sprintf($sprintfproto, $value);
  5197.         for my $val (@{$arg->{'vals'}}) {
  5198.         if (($val->{'driverval'} eq $driverval) ||
  5199.             ($val->{'driverval'} eq $value)) {
  5200.             my $name = $arg->{'name'};
  5201.             print $logh 
  5202.             "The string $value for $name is the predefined " .
  5203.             "choice $val->{value}\n";
  5204.             return $val->{value};
  5205.         }
  5206.         }
  5207.         # "None" is mapped to the empty string
  5208.         if ($value eq 'None') {
  5209.         my $name = $arg->{'name'};
  5210.         print $logh 
  5211.             "Option $name: 'None' is the mapped to the " .
  5212.             "empty string\n";
  5213.         return '';
  5214.         }
  5215.         # No matching choice? Return the original string
  5216.         return $value;
  5217.     } elsif ($forcevalue) {
  5218.         my $name = $arg->{'name'};
  5219.         my $str = substr($value, 0, $arg->{'maxlength'});
  5220.         if (stringvalid($dat, $argname, $str)) {
  5221.         print $logh 
  5222.             "The string $value for $name is longer than " .
  5223.             "$arg->{'maxlength'}, string shortened to $str\n";
  5224.         return $str;
  5225.         } elsif ($#{$arg->{'vals'}} >= 0) {
  5226.         # First list item
  5227.         my $firstentry = $arg->{'vals'}[0]{'value'};
  5228.         print $logh 
  5229.             "The string $value for $name contains forbidden " .
  5230.             "characters or does not match the regular expression " .
  5231.             "defined for this option, using predefined choice " .
  5232.             "$firstentry instead\n";
  5233.         return $firstentry;
  5234.         } else {
  5235.         # We should not get here
  5236.         rip_die("Option $name incorrectly defined in the " .
  5237.             "PPD file!\n", $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
  5238.         }
  5239.     }
  5240.     }
  5241.     return undef;
  5242. }
  5243.  
  5244. sub stringvalid {
  5245.  
  5246.     ## Checks whether a user-supplied value for a string option is valid
  5247.     ## It must be within the length limit, should only contain allowed
  5248.     ## characters and match the given regexp
  5249.  
  5250.     # Option and string
  5251.     my ($dat, $argname, $value) = @_;
  5252.  
  5253.     my $arg = $dat->{'args_byname'}{$argname};
  5254.  
  5255.     # Maximum length
  5256.     return 0 if (defined($arg->{'maxlength'}) &&
  5257.          (length($value) > $arg->{'maxlength'}));
  5258.  
  5259.     # Allowed characters
  5260.     if ($arg->{'allowedchars'}) {
  5261.     my $chars = $arg->{'allowedchars'};
  5262.     # Quote the slashes (if a slash is preceeded by an even number of
  5263.     # backslashes, it is not already quoted)
  5264.     $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  5265.     return 0 if $value !~ /^[$chars]*$/;
  5266.     }
  5267.  
  5268.     # Regular expression
  5269.     if ($arg->{'allowedregexp'}) {
  5270.     my $regexp = $arg->{'allowedregexp'};
  5271.     # Quote the slashes (if a slash is preceeded by an even number of
  5272.     # backslashes, it is not already quoted)
  5273.     $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  5274.     return 0 if $value !~ /$regexp/;
  5275.     }
  5276.  
  5277.     # All checks passed
  5278.     return 1;
  5279. }
  5280.  
  5281. sub checkoptions {
  5282.  
  5283.     ## Let the values of a boolean option being 0 or 1 instead of
  5284.     ## "True" or "False", range-check the defaults of all options and
  5285.     ## issue warnings if the values are not valid
  5286.  
  5287.     # Option set to be examined
  5288.     my ($dat, $optionset) = @_;
  5289.  
  5290.     for my $arg (@{$dat->{'args'}}) {
  5291.     if (defined($arg->{$optionset})) {
  5292.         $arg->{$optionset} =
  5293.         checkoptionvalue
  5294.         ($dat, $arg->{'name'}, $arg->{$optionset}, 1);
  5295.     }
  5296.     }
  5297.  
  5298.     # If the settings for "PageSize" and "PageRegion" are different,
  5299.     # set the one for "PageRegion" to the one for "PageSize" and issue
  5300.     # a warning.
  5301.     if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne
  5302.     $dat->{'args_byname'}{'PageRegion'}{$optionset}) {
  5303.     print $logh "Settings for \"PageSize\" and \"PageRegion\" are " .
  5304.         "different:\n" .
  5305.         "   PageSize: $dat->{'args_byname'}{'PageSize'}{$optionset}\n" .
  5306.         "   PageRegion: ".
  5307.         "$dat->{'args_byname'}{'PageRegion'}{$optionset}\n" .
  5308.         "Using the \"PageSize\" value " .
  5309.         "\"$dat->{'args_byname'}{'PageSize'}{$optionset}\"," .
  5310.         " for both.\n";
  5311.     $dat->{'args_byname'}{'PageRegion'}{$optionset} =
  5312.         $dat->{'args_byname'}{'PageSize'}{$optionset};
  5313.     }
  5314. }
  5315.  
  5316. # If the PageSize or PageRegion was changed, also change the other
  5317.  
  5318. sub syncpagesize {
  5319.     
  5320.     # Name and value of the option we set, and the option set where we
  5321.     # did the change
  5322.     my ($dat, $name, $value, $optionset) = @_;
  5323.  
  5324.     # Don't do anything if we were called with an option other than
  5325.     # "PageSize" or "PageRegion"
  5326.     return if (($name ne "PageSize") && ($name ne "PageRegion"));
  5327.     
  5328.     # Don't do anything if not both "PageSize" and "PageRegion" exist
  5329.     return if ((!defined($dat->{'args_byname'}{'PageSize'})) ||
  5330.            (!defined($dat->{'args_byname'}{'PageRegion'})));
  5331.     
  5332.     my $dest;
  5333.     
  5334.     # "PageSize" --> "PageRegion"
  5335.     if ($name eq "PageSize") {
  5336.     $dest = "PageRegion";
  5337.     }
  5338.     
  5339.     # "PageRegion" --> "PageSize"
  5340.     if ($name eq "PageRegion") {
  5341.     $dest = "PageSize";
  5342.     }
  5343.     
  5344.     # Do it!
  5345.     my $val;
  5346.     if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) {
  5347.     # Standard paper size
  5348.     $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'};
  5349.     } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) {
  5350.     # Custom paper size
  5351.     $dat->{'args_byname'}{$dest}{$optionset} = $value;
  5352.     }
  5353. }
  5354.  
  5355. sub copyoptions {
  5356.  
  5357.     ## Copy one option set into another one
  5358.  
  5359.     # Source and destination option sets
  5360.     my ($dat, $srcoptionset, $destoptionset) = @_;
  5361.  
  5362.     for my $arg (@{$dat->{'args'}}) {
  5363.     if (defined($arg->{$srcoptionset})) {
  5364.         $arg->{$destoptionset} = $arg->{$srcoptionset};
  5365.     }
  5366.     }
  5367. }
  5368.  
  5369. sub deleteoptions {
  5370.  
  5371.     ## Delete an option set
  5372.  
  5373.     # option set to be removed
  5374.     my ($dat, $optionset) = @_;
  5375.  
  5376.     for my $arg (@{$dat->{'args'}}) {
  5377.     if (defined($arg->{$optionset})) {
  5378.         delete($arg->{$optionset});
  5379.     }
  5380.     }
  5381. }
  5382.  
  5383. sub optionsequal {
  5384.  
  5385.     ## Compare two option sets, if they are equal, return 1, otherwise 0
  5386.  
  5387.     # Option sets to be compared, flag to compare only command line and JCL
  5388.     # options
  5389.     my ($dat, $firstoptionset, $secondoptionset, $exceptPS) = @_;
  5390.  
  5391.     for my $arg (@{$dat->{'args'}}) {
  5392.     next if ($exceptPS && ($arg->{'style'} eq 'G'));
  5393.     if ((defined($arg->{$firstoptionset})) &&
  5394.         (defined($arg->{$secondoptionset}))) {
  5395.         # Both entries exist
  5396.         return 0 if $arg->{$firstoptionset} ne $arg->{$secondoptionset};
  5397.     } elsif ((defined($arg->{$firstoptionset})) ||
  5398.          (defined($arg->{$secondoptionset}))) {
  5399.         # One entry exists
  5400.         return 0;
  5401.     }
  5402.     # If no entry exists, the non-existing entries are considered as
  5403.     # equal
  5404.     }
  5405.     return 1;
  5406. }
  5407.  
  5408. sub makeprologsection {
  5409.  
  5410.     # option set to be used,
  5411.     # $comments = 1: Add "%%BeginProlog...%%EndProlog"
  5412.     my ($dat, $optionset, $comments) = @_;
  5413.     
  5414.     # Collect data to be inserted here
  5415.     my @output;
  5416.  
  5417.     # Start comment
  5418.     if ($comments) {
  5419.     print $logh "\"Prolog\" section is missing, inserting it.\n";
  5420.     push(@output, "%%BeginProlog\n");
  5421.     }
  5422.  
  5423.     # Generate the option code (not necessary when CUPS is spooler)
  5424.     if ($spooler ne 'cups') {
  5425.     print $logh "Inserting option code into \"Prolog\" section.\n";
  5426.     buildcommandline ($dat, $optionset);
  5427.     push(@output, @{$dat->{'prologprepend'}});
  5428.     }
  5429.  
  5430.     # End comment
  5431.     if ($comments) {
  5432.     push(@output, "%%EndProlog\n");
  5433.     }
  5434.  
  5435.     return join('', @output);
  5436. }
  5437.  
  5438. sub makesetupsection {
  5439.  
  5440.     # option set to be used, $comments = 1: Add "%%BeginSetup...%%EndSetup"
  5441.     my ($dat, $optionset, $comments) = @_;
  5442.     
  5443.     # Collect data to be inserted here
  5444.     my @output;
  5445.  
  5446.     # Start comment
  5447.     if ($comments) {
  5448.     print $logh "\"Setup\" section is missing, inserting it.\n";
  5449.     push(@output, "%%BeginSetup\n");
  5450.     }
  5451.  
  5452.     # PostScript code to generate accounting messages for CUPS
  5453.     if ($spooler eq 'cups') {
  5454.     print $logh "Inserting PostScript code for CUPS' page accounting\n";
  5455.     push(@output, $accounting_prolog);
  5456.     }
  5457.  
  5458.     # Generate the option code (not necessary when CUPS is spooler)
  5459.     if ($spooler ne 'cups') {
  5460.     print $logh "Inserting option code into \"Setup\" section.\n";
  5461.     buildcommandline ($dat, $optionset);
  5462.     push(@output, @{$dat->{'setupprepend'}});
  5463.     }
  5464.  
  5465.     # End comment
  5466.     if ($comments) {
  5467.     push(@output, "%%EndSetup\n");
  5468.     }
  5469.  
  5470.     return join('', @output);
  5471. }
  5472.  
  5473. sub makepagesetupsection {
  5474.  
  5475.     # option set to be used,
  5476.     # $comments = 1: Add "%%BeginPageSetup...%%EndPageSetup"
  5477.     my ($dat, $optionset, $comments) = @_;
  5478.     
  5479.     # Collect data to be inserted here
  5480.     my @output;
  5481.  
  5482.     # Start comment
  5483.     if ($comments) {
  5484.     push(@output, "%%BeginPageSetup\n");
  5485.     print $logh "\"PageSetup\" section is missing, inserting it.\n";
  5486.     }
  5487.  
  5488.     # Generate the option code (not necessary when CUPS is spooler)
  5489.     print $logh "Inserting option code into \"PageSetup\" section.\n";
  5490.     buildcommandline ($dat, $optionset);
  5491.     if ($spooler ne 'cups') {
  5492.     push(@output, @{$dat->{'pagesetupprepend'}});
  5493.     } else {
  5494.     push(@output, @{$dat->{'cupspagesetupprepend'}});
  5495.     }
  5496.  
  5497.     # End comment
  5498.     if ($comments) {
  5499.     push(@output, "%%EndPageSetup\n");
  5500.     }
  5501.  
  5502.     return join('', @output);
  5503. }
  5504.  
  5505. sub parsepageranges {
  5506.  
  5507.     ## Parse a string containing page ranges and either check whether a
  5508.     ## given page is in the ranges or, if the given page number is zero,
  5509.     ## determine the score how specific this page range string is.
  5510.  
  5511.     # String with page ranges and number of current page (0 for score)
  5512.     my ($ranges, $page) = @_;
  5513.     
  5514.     my $currentnumber = 0;
  5515.     my $rangestart = 0;
  5516. ####### Question: is rangeend ever used?
  5517.     my $rangeend = 0;
  5518.     my $currentkeyword = '';
  5519.     my $invalidrange = 0;
  5520.     my $totalscore = 0;
  5521.     my $pageinside = 0;
  5522.     my $currentrange = '';
  5523.  
  5524.     my $evaluaterange = sub {
  5525.     # evaluate the current range: determine its score and whether the
  5526.     # current page is member of it.
  5527.     if ($invalidrange) {
  5528.         # Range is invalid, issue a warning
  5529.         print $logh "   Invalid range: $currentrange\n";
  5530.     } else {
  5531.         # We have a valid range, evaluate it
  5532.         if ($currentkeyword) {
  5533.         if ($currentkeyword =~ /^even/i) {
  5534.             # All even-numbered pages
  5535.             $totalscore += 50000;
  5536.             $pageinside = 1 if (($page % 2) == 0);
  5537.         } elsif ($currentkeyword =~ /^odd/i) {
  5538.             # All odd-numbered pages
  5539.             $totalscore += 50000;
  5540.             $pageinside = 1 if (($page % 2) == 1);
  5541.         } else {
  5542.             # Invalid range
  5543.             print $logh "   Invalid range: $currentrange\n";
  5544.         }
  5545.         } elsif (($rangestart == 0) && ($currentnumber > 0)) {
  5546.         # Page range is a single page
  5547.         $totalscore += 1;
  5548.         $pageinside = 1 if ($page == $currentnumber);
  5549.         } elsif (($rangestart > 0) && ($currentnumber > 0)) {
  5550.         # Page range is a sequence of pages
  5551.         $totalscore += (abs($currentnumber - $rangestart) + 1);
  5552.         if ($currentnumber < $rangestart) {
  5553.             my $tmp = $currentnumber;
  5554.             $currentnumber = $rangestart;
  5555.             $rangestart = $tmp;
  5556.         }
  5557.         $pageinside = 1 if (($page <= $currentnumber) &&
  5558.                     ($page >= $rangestart));
  5559.         } elsif ($rangestart > 0) {
  5560.         # Page range goes to the end of the document
  5561.         $totalscore += 100000;
  5562.         $pageinside = 1 if ($page >= $rangestart);
  5563.         } else {
  5564.         # Invalid range
  5565.         print $logh "   Invalid range: $currentrange\n";
  5566.         }
  5567.     }
  5568.     # Range is evaluated, remove all recordings of the current range
  5569.     $rangestart = 0;
  5570.     $currentnumber = 0;
  5571.     $currentkeyword = '';
  5572.     $invalidrange = 0;
  5573.     $currentrange = '';
  5574.     };
  5575.  
  5576.     for (my $i = 0; $i < length($ranges); $i ++) {
  5577.     my $c = substr($ranges, $i, 1);
  5578.     if (!$invalidrange) {
  5579.         if ($c =~ /\d/) {
  5580.         # Digit
  5581.         if ($currentkeyword) {
  5582.             # Add to keyword
  5583.             $currentkeyword .= $c;
  5584.         } else {
  5585.             # Build a page number
  5586.             $currentnumber *= 10;
  5587.             $currentnumber += $c;
  5588.         }
  5589.         } elsif ($c =~ /[a-z_]/i) {
  5590.         # Letter or underscore
  5591.         if (($rangestart > 0) || ($rangeend > 0) ||
  5592.             ($currentnumber > 0)) {
  5593.             # Keyword not allowed after a page number or a
  5594.             # page range
  5595.             $invalidrange = 1;
  5596.         } else {
  5597.             # Build a keyword
  5598.             $currentkeyword .= $c;
  5599.         }
  5600.         } elsif ($c eq '-') {
  5601.         # Page range 
  5602.         if (($rangestart > 0) || ($currentkeyword)) {
  5603.             # Keyword or two '-' not allowed in page range
  5604.             $invalidrange = 1;
  5605.         } else {
  5606.             # Save start of range, reset page number
  5607.             $rangestart = $currentnumber;
  5608.             if ($rangestart == 0) {
  5609.             $rangestart = 1;
  5610.             }
  5611.             $currentnumber = 0;
  5612.         }
  5613.         } 
  5614.     }
  5615.     if ($c eq ',') {
  5616.         # End of a range
  5617.         &$evaluaterange();
  5618.     } else {
  5619.         # Make a string of the current range, for warnings
  5620.         $currentrange .= $c;
  5621.     }
  5622.     }
  5623.     # End of input string
  5624.     &$evaluaterange();
  5625.     # Return value
  5626.     if (($page == 0) || ($pageinside)) {
  5627.     return $totalscore;
  5628.     } else {
  5629.     return 0;
  5630.     }
  5631. }
  5632.  
  5633. sub setoptionsforpage {
  5634.  
  5635.     ## Set the options for a given page
  5636.  
  5637.     # Foomatic data, name of the option set where to apply the options, and
  5638.     # number of the page
  5639.     my ($dat, $optionset, $page) = @_;
  5640.  
  5641.     my $bestscore = 10000000;
  5642.     my $value;
  5643.     for my $arg (@{$dat->{'args'}}) {
  5644.     $value = '';
  5645.     for my $key (keys %{$arg}) {
  5646.         next if $key !~ /^pages:(.*)$/;
  5647.         my $pageranges = $1;
  5648.         if (my $score = parsepageranges($pageranges, $page)) {
  5649.         if ($score <= $bestscore) {
  5650.             $bestscore = $score;
  5651.             $value = $arg->{$key};
  5652.         }
  5653.         }
  5654.     }
  5655.     if ($value) {
  5656.         $arg->{$optionset} = $value;
  5657.     }
  5658.     }
  5659. }
  5660.  
  5661. sub buildcommandline {
  5662.  
  5663.     ## Build a renderer command line, based on the given option set
  5664.  
  5665.     # Foomatic data and name of the option set to apply
  5666.     my ($dat, $optionset) = @_;
  5667.  
  5668.     # Construct the proper command line.
  5669.     $dat->{'currentcmd'} = $dat->{'cmd'};
  5670.     my @prologprepend;
  5671.     my @setupprepend;
  5672.     my @pagesetupprepend;
  5673.     my @cupspagesetupprepend;
  5674.     my @jclprepend;
  5675.     my @jclappend;
  5676.  
  5677.     # At first search for composite options and determine how they
  5678.     # set their member options
  5679.     for my $arg (@{$dat->{'args'}}) { $arg->{'order'} = 0 if !defined $arg->{'order'}; }
  5680.     for my $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  5681.           @{$dat->{'args'}}) {
  5682.  
  5683.     # Here we are only interested in composite options, skip the others
  5684.     next if $arg->{'style'} ne 'X';
  5685.     
  5686.     my $name = $arg->{'name'};
  5687.     # Check whether this composite option is controlled by another
  5688.     # composite option, so nested composite options are possible.
  5689.     my $userval = ($arg->{'fromcomposite'} ?
  5690.                $arg->{'fromcomposite'} : $arg->{$optionset});
  5691.  
  5692.     # Get the current setting
  5693.     my $v = $arg->{'vals_byname'}{$userval};
  5694.     my @settings = split(/\s+/s, $v->{'driverval'});
  5695.     for my $s (@settings) {
  5696.         my ($key, $value);
  5697.         if ($s =~ /^([^=]+)=(.+)$/) {
  5698.         $key = $1;
  5699.         $value = $2;
  5700.         } elsif ($s =~ /^no([^=]+)$/) {
  5701.         $key = $1;
  5702.         $value = 0;
  5703.         } elsif ($s =~ /^([^=]+)$/) {
  5704.         $key = $1;
  5705.         $value = 1;
  5706.         }
  5707.         $a = $dat->{'args_byname'}{$key};
  5708.         if ($a->{$optionset} eq "From$name") {
  5709.         # We must set this option according to the
  5710.         # composite option
  5711.         $a->{'fromcomposite'} = $value;
  5712.         # Mark the option telling by which composite option
  5713.         # it is controlled
  5714.         $a->{'controlledby'} = $name;
  5715.         } else {
  5716.         $a->{'fromcomposite'} = "";
  5717.         }
  5718.     }
  5719.     # Remove PostScript code to be inserted after an appearance of the
  5720.     # Composite option in the PostScript code.
  5721.     undef $arg->{'jclsetup'};
  5722.     undef $arg->{'prolog'};
  5723.     undef $arg->{'setup'};
  5724.     undef $arg->{'pagesetup'};
  5725.     }
  5726.  
  5727.     for my $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  5728.           @{$dat->{'args'}}) {
  5729.     
  5730.     # Composite options have no direct influence on the command
  5731.     # line, skip them here
  5732.     next if $arg->{'style'} eq 'X';
  5733.  
  5734.     my $name = $arg->{'name'};
  5735.     my $spot = $arg->{'spot'};
  5736.     my $cmd = $arg->{'proto'};
  5737.     my $cmdf = $arg->{'protof'};
  5738.     my $type = ($arg->{'type'} || "");
  5739.     my $section = $arg->{'section'};
  5740.     my $userval = ($arg->{'fromcomposite'} ?
  5741.                $arg->{'fromcomposite'} : $arg->{$optionset});
  5742.     my $cmdvar = "";
  5743.  
  5744.     # If we have both "PageSize" and "PageRegion" options, we kept
  5745.     # them all the time in sync, so we don't need to insert the settings
  5746.     # of both options. So skip "PageRegion".
  5747.     next if (($name eq "PageRegion") &&
  5748.          (defined($dat->{'args_byname'}{'PageSize'})) &&
  5749.          (defined($dat->{'args_byname'}{'PageRegion'})));
  5750.  
  5751.     # Build the command line snippet/PostScript/JCL code for the current
  5752.     # option
  5753.     if ($type eq 'bool') {
  5754.  
  5755.         # If true, stick the proto into the command line, if false
  5756.         # and we have a proto for false, stick that in
  5757.         if (defined($userval) && $userval == 1) {
  5758.         $cmdvar = $cmd;
  5759.         } elsif ($cmdf) {
  5760.         $userval = 0;
  5761.         $cmdvar = $cmdf;
  5762.         }
  5763.  
  5764.     } elsif ($type eq 'int' or $type eq 'float') {
  5765.  
  5766.         # If defined, process the proto and stick the result into
  5767.         # the command line or postscript queue.
  5768.         if (defined($userval)) {
  5769.         my $min = $arg->{'min'};
  5770.         my $max = $arg->{'max'};
  5771.         # We have already range-checked, correct only
  5772.         # floating point inaccuricies here
  5773.         if ($userval < $min) {
  5774.             $userval = $min;
  5775.         }
  5776.         if ($userval > $max) {
  5777.             $userval = $max;
  5778.         }
  5779.         my $sprintfcmd = $cmd;
  5780.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5781.         $cmdvar = sprintf($sprintfcmd,
  5782.                   ($type eq 'int' 
  5783.                    ? sprintf("%d", $userval)
  5784.                    : sprintf("%f", $userval)));
  5785.         } else {
  5786.         $userval = 'None';
  5787.         }
  5788.  
  5789.     } elsif ($type eq 'enum') {
  5790.  
  5791.         # If defined, stick the selected value into the proto and
  5792.         # thence into the commandline
  5793.         if (defined($userval)) {
  5794.         # CUPS assumes that options with the choices "Yes", "No",
  5795.         # "On", "Off", "True", or "False" are boolean options and
  5796.         # maps "-o Option=On" to "-o Option" and "-o Option=Off"
  5797.         # to "-o noOption", which foomatic-rip maps to "0" and "1".
  5798.         # So when "0" or "1" is unavailable in the option, we try
  5799.         # "Yes", "No", "On", "Off", "True", and "False".
  5800.         my $val;
  5801.         my $found = 0;
  5802.         if ($val=valbyname($arg,$userval)) {
  5803.             $found = 1;
  5804.         } elsif ($userval =~ /^Custom\.[\d\.]+x[\d\.]+[A-Za-z]*$/) {
  5805.             # Custom paper size
  5806.             $val = valbyname($arg,"Custom");
  5807.             $found = 1;
  5808.         } elsif ($userval eq '0') {
  5809.             foreach (qw(No Off False None)) {
  5810.             if ($val=valbyname($arg,$_)) {
  5811.                 $userval = $_;
  5812.                 $arg->{$optionset} = $userval;
  5813.                 $found = 1;
  5814.                 last;
  5815.             }
  5816.             }
  5817.         } elsif ($userval eq '1') {
  5818.             foreach (qw(Yes On True)) {
  5819.             if ($val=valbyname($arg,$_)) {
  5820.                 $userval = $_;
  5821.                 $arg->{$optionset} = $userval;
  5822.                 $found = 1;
  5823.                 last;
  5824.             }
  5825.             }
  5826.         } elsif ($userval eq 'LongEdge') {
  5827.             # Handle different names for the choices of the
  5828.             # "Duplex" option
  5829.             foreach (qw(LongEdge DuplexNoTumble)) {
  5830.             if ($val=valbyname($arg,$_)) {
  5831.                 $userval = $_;
  5832.                 $arg->{$optionset} = $userval;
  5833.                 $found = 1;
  5834.                 last;
  5835.             }
  5836.             }
  5837.         } elsif ($userval eq 'ShortEdge') {
  5838.             foreach (qw(ShortEdge DuplexTumble)) {
  5839.             if ($val=valbyname($arg,$_)) {
  5840.                 $userval = $_;
  5841.                 $arg->{$optionset} = $userval;
  5842.                 $found = 1;
  5843.                 last;
  5844.             }
  5845.             }
  5846.         }
  5847.         if ($found) {
  5848.             my $sprintfcmd = $cmd;
  5849.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5850.             $cmdvar = sprintf($sprintfcmd,
  5851.                       (defined($val->{'driverval'})
  5852.                        ? $val->{'driverval'}
  5853.                        : $val->{'value'}));
  5854.             # Custom paper size
  5855.             if ($userval =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/) {
  5856.             my $width = $1;
  5857.             my $height = $2;
  5858.             my $unit = $3;
  5859.             # convert width and height to PostScript points
  5860.             if (lc($unit) eq "in") {
  5861.                 $width *= 72.0;
  5862.                 $height *= 72.0;
  5863.             } elsif (lc($unit) eq "cm") {
  5864.                 $width *= (72.0/2.54);
  5865.                 $height *= (72.0/2.54);
  5866.             } elsif (lc($unit) eq "mm") {
  5867.                 $width *= (72.0/25.4);
  5868.                 $height *= (72.0/25.4);
  5869.             }
  5870.             # Round width and height
  5871.             $width =~ s/\.[0-4].*$// or
  5872.                 $width =~ s/\.[5-9].*$// and $width += 1;
  5873.             $height =~ s/\.[0-4].*$// or
  5874.                 $height =~ s/\.[5-9].*$// and $height += 1;
  5875.             # Insert width and height into the prototype
  5876.             if ($cmdvar =~ /^\s*pop\W/s) {
  5877.                 # Custom page size for PostScript printers
  5878.                 $cmdvar = "$width $height 0 0 0\n$cmdvar";
  5879.             } else {
  5880.                 # Custom page size for Foomatic/Gimp-Print
  5881.                 $cmdvar =~ s/\%0/$width/ or
  5882.                 $cmdvar =~ s/(\W)0(\W)/$1$width$2/ or
  5883.                 $cmdvar =~ s/^0(\W)/$width$1/m or
  5884.                 $cmdvar =~ s/(\W)0$/$1$width/m or
  5885.                 $cmdvar =~ s/^0$/$width/m;
  5886.                 $cmdvar =~ s/\%1/$height/ or
  5887.                 $cmdvar =~ s/(\W)0(\W)/$1$height$2/ or
  5888.                 $cmdvar =~ s/^0(\W)/$height$1/m or
  5889.                 $cmdvar =~ s/(\W)0$/$1$height/m or
  5890.                 $cmdvar =~ s/^0$/$height/m;
  5891.             }
  5892.             }
  5893.         } else {
  5894.             # User gave unknown value?
  5895.             $userval = 'None';
  5896.             print $logh "Value $userval for $name is not a valid choice.\n";
  5897.         }
  5898.         } else {
  5899.         $userval = 'None';
  5900.         }
  5901.  
  5902.     } elsif (($type eq 'string') || ($type eq 'password')) {
  5903.         # Stick the entered value into the proto and
  5904.         # thence into the commandline
  5905.         if (defined($userval)) {
  5906.         my $val;
  5907.         if ($val=valbyname($arg,$userval)) {
  5908.             $userval = $val->{'value'};
  5909.             $cmdvar = (defined($val->{'driverval'})
  5910.                        ? $val->{'driverval'}
  5911.                        : $val->{'value'});
  5912.         } else {
  5913.             my $sprintfcmd = $cmd;
  5914.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5915.             $cmdvar = sprintf($sprintfcmd, $userval);
  5916.         }
  5917.         } else {
  5918.         $userval = 'None';
  5919.         }
  5920.  
  5921.     } else {
  5922.         # Ignore unknown option types silently
  5923.     }
  5924.         
  5925.     # Insert the built snippet at the correct place
  5926.     if ($arg->{'style'} eq 'G') {
  5927.         # Place this Postscript command onto the prepend queue
  5928.         # for the appropriate section.
  5929.         if ($cmdvar) {
  5930.         my $open = "[{\n%%BeginFeature: *$name $userval\n";
  5931.         my $close = "\n%%EndFeature\n} stopped cleartomark\n";
  5932.         if ($section eq "Prolog") {
  5933.             push (@prologprepend, "$open$cmdvar$close");
  5934.             my $a = $arg;
  5935.             while ($a->{'controlledby'}) {
  5936.             # Collect option PostScript code to be inserted when
  5937.             # the composite option which controls this option
  5938.             # is found in the PostScript code
  5939.             $a = $dat->{'args_byname'}{$a->{'controlledby'}};
  5940.             $a->{'prolog'} .= "$cmdvar\n";
  5941.             }
  5942.         } elsif ($section eq "AnySetup") {
  5943.             if ($optionset ne 'currentpage') {
  5944.             push (@setupprepend, "$open$cmdvar$close");
  5945.             } elsif ($arg->{'header'} ne $userval) {
  5946.             push (@pagesetupprepend, "$open$cmdvar$close");
  5947.             push (@cupspagesetupprepend, "$open$cmdvar$close");
  5948.             }
  5949.             my $a = $arg;
  5950.             while ($a->{'controlledby'}) {
  5951.             # Collect option PostScript code to be inserted when
  5952.             # the composite option which controls this option
  5953.             # is found in the PostScript code
  5954.             $a = $dat->{'args_byname'}{$a->{'controlledby'}};
  5955.             $a->{'setup'} .= "$cmdvar\n";
  5956.             $a->{'pagesetup'} .= "$cmdvar\n";
  5957.             }
  5958.         } elsif ($section eq "DocumentSetup") {
  5959.             push (@setupprepend, "$open$cmdvar$close");
  5960.             my $a = $arg;
  5961.             while ($a->{'controlledby'}) {
  5962.             # Collect option PostScript code to be inserted when
  5963.             # the composite option which controls this option
  5964.             # is found in the PostScript code
  5965.             $a = $dat->{'args_byname'}{$a->{'controlledby'}};
  5966.             $a->{'setup'} .= "$cmdvar\n";
  5967.             }
  5968.         } elsif ($section eq "PageSetup") {
  5969.             push (@pagesetupprepend, "$open$cmdvar$close");
  5970.             my $a = $arg;
  5971.             while ($a->{'controlledby'}) {
  5972.             # Collect option PostScript code to be inserted when
  5973.             # the composite option which controls this option
  5974.             # is found in the PostScript code
  5975.             $a = $dat->{'args_byname'}{$a->{'controlledby'}};
  5976.             $a->{'pagesetup'} .= "$cmdvar\n";
  5977.             }
  5978.         } elsif ($section eq "JCLSetup") {
  5979.             # PJL/JCL argument
  5980.             $dat->{'jcl'} = 1;
  5981.             push (@jclprepend, unhexify($cmdvar));
  5982.             my $a = $arg;
  5983.             while ($a->{'controlledby'}) {
  5984.             # Collect option PostScript code to be inserted when
  5985.             # the composite option which controls this option
  5986.             # is found in the PostScript code
  5987.             $a = $dat->{'args_byname'}{$a->{'controlledby'}};
  5988.             $a->{'jclsetup'} .= "$cmdvar\n";
  5989.             }
  5990.         } else {
  5991.             push (@setupprepend, "$open$cmdvar$close");
  5992.             my $a = $arg;
  5993.             while ($a->{'controlledby'}) {
  5994.             # Collect option PostScript code to be inserted when
  5995.             # the composite option which controls this option
  5996.             # is found in the PostScript code
  5997.             $a = $dat->{'args_byname'}{$a->{'controlledby'}};
  5998.             $a->{'setup'} .= "$cmdvar\n";
  5999.             }
  6000.         }
  6001.         }
  6002.         # Do we have an option which is set to "Controlled by 
  6003.         # '<Composite>'"? Then make PostScript code available
  6004.         # for substitution of "%% FoomaticRIPOptionSetting: ..." 
  6005.         if ($arg->{'fromcomposite'}) {
  6006.         $arg->{'compositesubst'} = "$cmdvar\n";
  6007.         }
  6008.     } elsif ($arg->{'style'} eq 'J') {
  6009.         # JCL argument
  6010.         $dat->{'jcl'} = 1;
  6011.         # put JCL commands onto JCL stack...
  6012.         push (@jclprepend, "$jclprefix$cmdvar\n") if $cmdvar;
  6013.     } elsif ($arg->{'style'} eq 'C') {
  6014.         # command-line argument
  6015.  
  6016.         # Insert the processed argument in the commandline
  6017.         # just before every occurance of the spot marker.
  6018.         $dat->{'currentcmd'} =~ s!\%$spot!$cmdvar\%$spot!g;
  6019.     }
  6020.     # Insert option into command line of CUPS raster driver
  6021.     if ($dat->{'currentcmd'} =~ m!\%Y!) {
  6022.         next if !defined($userval) or $userval eq "";
  6023.         $dat->{'currentcmd'} =~ s!\%Y!$name=$userval \%Y!g;
  6024.     }
  6025.     # Remove the marks telling that this option is currently controlled
  6026.     # by a composite option (setting "From<composite>")
  6027.         undef $arg->{'fromcomposite'};
  6028.     undef $arg->{'controlledby'};
  6029.     }
  6030.     
  6031.  
  6032.     ### Tidy up after computing option statements for all of P, J, and
  6033.     ### C types:
  6034.  
  6035.     ## C type finishing
  6036.     # Pluck out all of the %n's from the command line prototype
  6037.     my @letters = qw/A B C D E F G H I J K L M W X Y Z/;
  6038.     for my $spot (@letters) {
  6039.     # Remove the letter markers from the commandline
  6040.     $dat->{'currentcmd'} =~ s!\%$spot!!g;
  6041.     }
  6042.  
  6043.     ## J type finishing
  6044.     # Compute the proper stuff to say around the job
  6045.  
  6046.     if ((defined($dat->{'jcl'})) && (!$jobhasjcl)) {
  6047.  
  6048.     # Stick beginning of job cruft on the front of the jcl stuff...
  6049.     unshift (@jclprepend, $jclbegin);
  6050.  
  6051.     # Command to switch to the interpreter
  6052.     push (@jclprepend, $jcltointerpreter);
  6053.     
  6054.     # Arrange for JCL RESET command at end of job
  6055.     push (@jclappend, $jclend);
  6056.  
  6057.     # Put the JCL stuff into the data structure
  6058.     @{$dat->{'jclprepend'}} = @jclprepend;
  6059.     @{$dat->{'jclappend'}} = @jclappend;
  6060.     }
  6061.  
  6062.     ## G type finishing
  6063.     # Save PostScript options
  6064.     @{$dat->{'prologprepend'}} = @prologprepend;
  6065.     @{$dat->{'setupprepend'}} = @setupprepend;
  6066.     @{$dat->{'pagesetupprepend'}} = @pagesetupprepend;
  6067.     @{$dat->{'cupspagesetupprepend'}} = @cupspagesetupprepend;
  6068. }
  6069.  
  6070. sub buildpdqdriver {
  6071.  
  6072.     # Build a PDQ driver description file to use the given PPD file
  6073.     # together with foomatic-rip with the PDQ printing system
  6074.  
  6075.     # Foomatic data and name of the option set for the default settings
  6076.     my ($dat, $optionset) = @_;
  6077.  
  6078.     # Construct structure with driver information
  6079.     my @pdqdriver = ();
  6080.  
  6081.     # Construct option list
  6082.     my @driveropts = ();
  6083.  
  6084.     # Do we have a "Custom" setting for the page size?
  6085.     # Then we have to insert the following into the "filter_exec" script.
  6086.     my @setcustompagesize = ();
  6087.  
  6088.     # Fata for a custom page size, to allow a custom size as default
  6089.     my $pagewidth = 612;
  6090.     my $pageheight = 792;
  6091.     my $pageunit = "pt";
  6092.  
  6093.  
  6094.  
  6095.     ## First, compute the various option/value clauses
  6096.     for my $arg (@{$dat->{'args'}}) {
  6097.  
  6098.     if ($arg->{'type'} eq "enum") {
  6099.         
  6100.         # Option with only one choice, omit it, foomatic-rip will set 
  6101.         # this choice anyway.
  6102.         next if ($#{$arg->{'vals'}} < 1);
  6103.  
  6104.         my $nam = $arg->{'name'};
  6105.  
  6106.         # Omit "PageRegion" option, it does the same as "PageSize".
  6107.         next if $nam eq "PageRegion";
  6108.  
  6109.         my $com = $arg->{'comment'};
  6110.  
  6111.         # Assure that the comment is not empty
  6112.         if (!$com) {
  6113.         $com = $nam;
  6114.         }
  6115.  
  6116.         my $def = $arg->{$optionset};
  6117.         $arg->{'varname'} = "$nam";
  6118.         $arg->{'varname'} =~ s![\-\/\.]!\_!g;
  6119.         my $varn = $arg->{'varname'};
  6120.  
  6121.         # 1, if setting "PageSize=Custom" was found
  6122.         # Then we must add options for page width and height
  6123.         my $custompagesize = 0;
  6124.  
  6125.         # If the default is a custom size we have to set also
  6126.         # defaults for the width, height, and units of the page
  6127.         if (($nam eq "PageSize") &&
  6128.         ($def =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/)) {
  6129.         $def = "Custom";
  6130.         $pagewidth = $1;
  6131.         $pageheight = $2;
  6132.         $pageunit = $3;
  6133.         }
  6134.  
  6135.         # No quotes, thank you.
  6136.         $com =~ s!\"!\\\"!g;
  6137.         
  6138.         push(@driveropts,
  6139.          "  option {\n",
  6140.          "    var = \"$varn\"\n",
  6141.          "    desc = \"$com\"\n");
  6142.         
  6143.         # get enumeration values for each enum arg
  6144.         my ($ev, @vals, @valstmp);
  6145.         for $ev (@{$arg->{'vals'}}) {
  6146.         my $choiceshortname = $ev->{'value'};
  6147.         my $choicename = "${nam}_${choiceshortname}";
  6148.         my $val = " -o ${nam}=${choiceshortname}";
  6149.         my $com = $ev->{'comment'};
  6150.  
  6151.         # Assure that the comment is not empty
  6152.         if (!$com) {
  6153.             $com = $choiceshortname;
  6154.         }
  6155.  
  6156.         # stick another choice on driveropts
  6157.         push(@valstmp,
  6158.              "    choice \"$choicename\" {\n",
  6159.              "      desc = \"$com\"\n",
  6160.              "      value = \"$val\"\n",
  6161.              "    }\n");
  6162.         if (($nam eq "PageSize") && 
  6163.             ($choiceshortname eq "Custom")) {
  6164.             $custompagesize = 1;
  6165.             if ($#setcustompagesize < 0) {
  6166.             push(@setcustompagesize,
  6167.                  "      # Custom page size settings\n",
  6168.                  "      # We aren't really checking for " .
  6169.                  "legal vals.\n",
  6170.                  "      if [ \"x\${$varn}\" = 'x$val' ]; " .
  6171.                  "then\n",
  6172.                  "        $varn=\"\${$varn}.\${PageWidth}" .
  6173.                  "x\${PageHeight}\${PageSizeUnit}\"\n",
  6174.                  "      fi\n\n");
  6175.             }
  6176.         }
  6177.         }
  6178.  
  6179.         push(@driveropts,
  6180.          "    default_choice \"" . $nam . "_" . $def . "\"\n",
  6181.          @valstmp,
  6182.          "  }\n\n");
  6183.  
  6184.         if ($custompagesize) {
  6185.         # Add options to set the custom page size
  6186.         push(@driveropts,
  6187.              "  argument {\n",
  6188.              "    var = \"PageWidth\"\n",
  6189.              "    desc = \"Page Width (for \\\"Custom\\\" page " .
  6190.              "size)\"\n",
  6191.              "    def_value \"$pagewidth\"\n",
  6192.              "    help = \"Minimum value: 0, Maximum value: " .
  6193.              "100000\"\n",
  6194.              "  }\n\n",
  6195.              "  argument {\n",
  6196.              "    var = \"PageHeight\"\n",
  6197.              "    desc = \"Page Height (for \\\"Custom\\\" page " .
  6198.              "size)\"\n",
  6199.              "    def_value \"$pageheight\"\n",
  6200.              "    help = \"Minimum value: 0, Maximum value: " .
  6201.              "100000\"\n",
  6202.              "  }\n\n",
  6203.              "  option {\n",
  6204.              "    var = \"PageSizeUnit\"\n",
  6205.              "    desc = \"Unit (for \\\"Custom\\\" page size)\"\n",
  6206.              "    default_choice \"PageSizeUnit_$pageunit\"\n",
  6207.              "    choice \"PageSizeUnit_pt\" {\n",
  6208.              "      desc = \"Points (1/72 inch)\"\n",
  6209.              "      value = \"pt\"\n",
  6210.              "    }\n",
  6211.              "    choice \"PageSizeUnit_in\" {\n",
  6212.              "      desc = \"Inches\"\n",
  6213.              "      value = \"in\"\n",
  6214.              "    }\n",
  6215.              "    choice \"PageSizeUnit_cm\" {\n",
  6216.              "      desc = \"cm\"\n",
  6217.              "      value = \"cm\"\n",
  6218.              "    }\n",
  6219.              "    choice \"PageSizeUnit_mm\" {\n",
  6220.              "      desc = \"mm\"\n",
  6221.              "      value = \"mm\"\n",
  6222.              "    }\n",
  6223.              "  }\n\n");        
  6224.         }
  6225.         
  6226.     } elsif ($arg->{'type'} eq 'int' or $arg->{'type'} eq 'float') {
  6227.         
  6228.         my $nam = $arg->{'name'};
  6229.         my $com = $arg->{'comment'};
  6230.  
  6231.         # Assure that the comment is not empty
  6232.         if (!$com) {
  6233.         $com = $nam;
  6234.         }
  6235.  
  6236.         my $def = $arg->{$optionset};
  6237.         my $max = $arg->{'max'};
  6238.         my $min = $arg->{'min'};
  6239.         $arg->{'varname'} = "$nam";
  6240.         $arg->{'varname'} =~ s![\-\/\.]!\_!g;
  6241.         my $varn = $arg->{'varname'};
  6242.         my $legal = $arg->{'legal'} = 
  6243.         "Minimum value: $min, Maximum value: $max";
  6244.         
  6245.         my $defstr = "";
  6246.         if ($def) {
  6247.         $defstr = sprintf("    def_value \"%s\"\n", $def);
  6248.         }
  6249.         
  6250.         push(@driveropts,
  6251.          "  argument {\n",
  6252.          "    var = \"$varn\"\n",
  6253.          "    desc = \"$com\"\n",
  6254.          $defstr,
  6255.          "    help = \"$legal\"\n",
  6256.          "  }\n\n");
  6257.         
  6258.     } elsif ($arg->{'type'} eq 'bool') {
  6259.         
  6260.         my $nam = $arg->{'name'};
  6261.         my $com = $arg->{'comment'};
  6262.  
  6263.         # Assure that the comment is not empty
  6264.         if (!$com) {
  6265.         $com = $nam;
  6266.         }
  6267.  
  6268.         my $tcom = $arg->{'comment_true'};
  6269.         my $fcom = $arg->{'comment_false'};
  6270.         my $def = $arg->{$optionset};
  6271.         $arg->{'legal'} = "Value is a boolean flag";
  6272.         $arg->{'varname'} = "$nam";
  6273.         $arg->{'varname'} =~ s![\-\/\.]!\_!g;
  6274.         my $varn = $arg->{'varname'};
  6275.         
  6276.         my $defstr = "";
  6277.         if ($def) {
  6278.         $defstr = sprintf("    default_choice \"%s\"\n", 
  6279.                   $def ? "$nam" : "no$nam");
  6280.         } else {
  6281.         $defstr = sprintf("    default_choice \"%s\"\n", "no$nam");
  6282.         }
  6283.         push(@driveropts,
  6284.          "  option {\n",
  6285.          "    var = \"$varn\"\n",
  6286.          "    desc = \"$com\"\n",
  6287.          $defstr,
  6288.          "    choice \"$nam\" {\n",
  6289.          "      desc = \"$tcom\"\n",
  6290.          "      value = \" -o $nam=True\"\n",
  6291.          "    }\n",
  6292.          "    choice \"no$nam\" {\n",
  6293.          "      desc = \"$fcom\"\n",
  6294.          "      value = \" -o $nam=False\"\n",
  6295.          "    }\n",
  6296.          "  }\n\n");
  6297.  
  6298.     } elsif ($arg->{'type'} eq 'string' or $arg->{'type'} eq 'password') {
  6299.         
  6300.         my $nam = $arg->{'name'};
  6301.         my $com = $arg->{'comment'};
  6302.  
  6303.         # Assure that the comment is not empty
  6304.         if (!$com) {
  6305.         $com = $nam;
  6306.         }
  6307.  
  6308.         my $def = $arg->{$optionset};
  6309.         my $maxlength = $arg->{'maxlength'};
  6310.         my $proto = $arg->{'proto'};
  6311.         $arg->{'varname'} = "$nam";
  6312.         $arg->{'varname'} =~ s![\-\/\.]!\_!g;
  6313.         my $varn = $arg->{'varname'};
  6314.  
  6315.         my $legal;
  6316.             if (defined($maxlength)) {
  6317.                 $legal .= "Maximum length: $maxlength characters, ";
  6318.             }
  6319.             $legal .= "Examples/special settings: ";
  6320.             for (@{$arg->{'vals'}}) {
  6321.                 my ($value, $comment, $driverval) = 
  6322.             ($_->{'value'}, $_->{'comment'}, $_->{'driverval'});
  6323.         # Retrieve the original string from the prototype
  6324.         # and the driverval
  6325.         my $string;
  6326.         if ($proto) {
  6327.             my $s = index($proto, '%s');
  6328.             my $l = length($driverval) - length($proto) + 2;
  6329.             if (($s < 0) || ($l < 0)) {
  6330.             $string = $driverval;
  6331.             } else {
  6332.             $string = substr($driverval, $s, $l);
  6333.             }
  6334.         } else {
  6335.             $string = $driverval;
  6336.         }
  6337.         if ($value ne $string) {
  6338.             $legal .= "${value}: \\\"$string\\\"";
  6339.         } else {
  6340.             $legal .= "\\\"$value\\\"";
  6341.         }
  6342.         if ($comment && ($value ne $comment) && 
  6343.             ($string ne $comment) && 
  6344.             (($value ne 'None') || ($comment ne '(None)'))) {
  6345.             $legal .= " ($comment)";
  6346.         }
  6347.         $legal .= "; ";
  6348.         }
  6349.         $legal =~ s/; $//;
  6350.  
  6351.         $arg->{'legal'} = $legal;
  6352.         
  6353.         my $defstr = "";
  6354.         if ($def) {
  6355.         $defstr = sprintf("    def_value \"%s\"\n", $def);
  6356.         }
  6357.         
  6358.         push(@driveropts,
  6359.          "  argument {\n",
  6360.          "    var = \"$varn\"\n",
  6361.          "    desc = \"$com\"\n",
  6362.          $defstr,
  6363.          "    help = \"$legal\"\n",
  6364.          "  }\n\n");
  6365.         
  6366.     }
  6367.     
  6368.     }
  6369.     
  6370.  
  6371.  
  6372.     ## Define the "docs" option to print the driver documentation page
  6373.  
  6374.     push(@driveropts,
  6375.      "  option {\n",
  6376.      "    var = \"DRIVERDOCS\"\n",
  6377.      "    desc = \"Print driver usage information\"\n",
  6378.      "    default_choice \"nodocs\"\n", 
  6379.      "    choice \"docs\" {\n",
  6380.      "      desc = \"Yes\"\n",
  6381.      "      value = \" -o docs\"\n",
  6382.      "    }\n",
  6383.      "    choice \"nodocs\" {\n",
  6384.      "      desc = \"No\"\n",
  6385.      "      value = \"\"\n",
  6386.      "    }\n",
  6387.      "  }\n\n");
  6388.     
  6389.  
  6390.  
  6391.     ## Build the "foomatic-rip" command line
  6392.     my $commandline = "foomatic-rip --pdq";
  6393.     if ($printer) {
  6394.     $commandline .= " -P $printer";
  6395.     } else {
  6396.     # Make sure that the PPD file is entered with an absolute path
  6397.     if ($ppdfile !~ m!^/!) {    
  6398.         my $pwd = cwd;
  6399.         $ppdfile = "$pwd/$ppdfile";
  6400.     }
  6401.     $commandline .= " --ppd=$ppdfile";
  6402.     }
  6403.     for my $arg (@{$dat->{'args'}}) {
  6404.     if ($arg->{'varname'}) {
  6405.         $commandline .= "\${$arg->{'varname'}}";
  6406.     }
  6407.     }
  6408.     $commandline .= "\${DRIVERDOCS} \$INPUT > \$OUTPUT";
  6409.  
  6410.  
  6411.         
  6412.     ## Now we generate code to build the command line snippets for the
  6413.     ## numerical options
  6414.  
  6415.     my @psfilter;
  6416.     for my $arg (@{$dat->{'args'}}) {
  6417.         
  6418.     # Only numerical and string options need to be treated here
  6419.     next if (($arg->{'type'} ne 'int') && 
  6420.          ($arg->{'type'} ne 'float') &&
  6421.          ($arg->{'type'} ne 'string') &&
  6422.          ($arg->{'type'} ne 'password'));
  6423.  
  6424.     my $comment = $arg->{'comment'};
  6425.     my $name = $arg->{'name'};
  6426.     my $varname = $arg->{'varname'};
  6427.             
  6428.     # If the option's variable is non-null, put in the
  6429.     # argument.  Otherwise this option is the empty
  6430.     # string.  Error checking?
  6431.             
  6432.     push(@psfilter,
  6433.          "      # $comment\n",
  6434.          (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ?
  6435.           ("      # We aren't really checking for max/min,\n",
  6436.            "      # this is done by foomatic-rip\n",
  6437.            "      if [ \"x\${$varname}\" != 'x' ]; then\n  ") : ""),
  6438.          #"      $varname=`echo \${$varname} | perl -p -e \"s/'/'\\\\\\\\\\\\\\\\''/g\"`\n",
  6439.          "      $varname=\" -o $name='\${$varname}'\"\n",
  6440.          (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ?
  6441.           "      fi\n" : ""),
  6442.          "\n");
  6443.     }
  6444.  
  6445.     # Command execution
  6446.  
  6447.     push(@psfilter,
  6448.      "      if ! test -e \$INPUT.ok; then\n",
  6449.      "        sh -c \"$commandline\"\n",
  6450.      "        if ! test -e \$OUTPUT; then \n",
  6451.      "          echo 'Error running foomatic-rip; no output!'\n",
  6452.      "          exit 1\n",
  6453.      "        fi\n",
  6454.      "      else\n",
  6455.      "        ln -s \$INPUT \$OUTPUT\n",
  6456.      "      fi\n\n");
  6457.     
  6458.     my $version = time();
  6459.     my $name = "$model-$version";
  6460.     $name =~ s/\W/\-/g;
  6461.     $name =~ s/\-+/\-/g;
  6462.     
  6463.     my $pname = $model;
  6464.     
  6465.     push (@pdqdriver,
  6466.       "driver \"$name\" {\n\n",
  6467.       "  # This PDQ driver declaration file was generated " .
  6468.       "automatically by\n",
  6469.       "  # foomatic-rip from information in the file $ppdfile.\n",
  6470.       "  # It allows printing with PDQ on the $pname.\n",
  6471.       "\n",
  6472.       "  requires \"foomatic-rip\"\n\n",
  6473.       @driveropts,
  6474.       "  language_driver all {\n",
  6475.       "    # We accept all file types and pass them to foomatic-rip\n",
  6476.       "    # (invoked in \"filter_exec {}\" section) without\n", 
  6477.           "    # pre-filtering\n",
  6478.       "    filetype_regx \"\"\n",
  6479.       "    convert_exec {\n",
  6480.       "      ln -s \$INPUT \$OUTPUT\n",
  6481.       "    }\n",
  6482.       "  }\n\n",
  6483.       "  filter_exec {\n",
  6484.       @setcustompagesize,
  6485.       @psfilter,
  6486.       "  }\n",
  6487.       "}\n");
  6488.     
  6489.     return @pdqdriver;
  6490.  
  6491. }
  6492.  
  6493. #
  6494. # Convert lp or ipp based attribute names (and values) to something that matches# PPD file options.
  6495. #
  6496. sub option_to_ppd {
  6497.     my ($ipp_attribute) = @_;
  6498.     my ($key, $value, $result) = ();
  6499.  
  6500.     if (/([^=]+)=[\'\"]?(.*}[\'\"]?)/) { # key=value
  6501.         ($key, $value) = ($1, $2);
  6502.     } elsif (/no(.+)/) {                 # BOOLEAN: no{key} (false)
  6503.         ($key, $value) = ($1, 'false');
  6504.     } else {                             # BOOLEAN: {key} (true)
  6505.         ($key, $value) = ($1, 'true');
  6506.     }
  6507.  
  6508.     if (($key =~ /^job-/) || ($key =~ /^copies/) ||
  6509.         ($key =~ /^multiple-document-handling/) || ($key =~ /^number-up/) ||
  6510.         ($key =~ /^orientation-requested/) ||
  6511.         ($key =~ /^dest/) || ($key =~ /^protocol/) || ($key =~ /^banner/) ||
  6512.         ($key =~ /^page-ranges/)) {
  6513.         # Ignored:
  6514.         #    job-*, multiple-document-handling are not supported by this
  6515.         #             filter
  6516.         #    dest, protocol, banner, number-up, orientation-requested are
  6517.         #             handled by the LP filtering or interface script
  6518.         #    NOTE - page-ranges should probably be handled here, but
  6519.         #             ignore it until we decide how to handle it.
  6520.     } elsif (/^printer-resolution/) {
  6521.         # value match on "123, 457" or on "123, 457, 8"
  6522.         if (/([\d]+),([\s]*)([\d]+)((,([\s]*)([\d]+))??)/) {
  6523.             $result = '$1x$2$3 '; # (width)x(height)(units)
  6524.         }
  6525.     } elsif (/^print-quality/) {
  6526.         ($value == 3) &&
  6527.             ($result = 'PrintoutMode=Draft');
  6528.         ($value == 4) &&
  6529.             ($result = 'PrintoutMode=Normal');
  6530.         ($value == 5) &&
  6531.             ($result = 'PrintoutMode=High');
  6532.     } else {
  6533.         # NOTE - if key == 'media', we may need to convert the values at some
  6534.         #        point. (see RFC2911, Section 14 for values)
  6535.         $result = '$key=\"$value\"';
  6536.     }
  6537.  
  6538.     return ($result);
  6539. }
  6540.  
  6541. #
  6542. # Read the attributes file containing the various job meta-data, including
  6543. # requested capabilities
  6544. #
  6545. sub read_attribute_file {
  6546.     my ($file) = @_;
  6547.     my $result = "";
  6548.  
  6549.     open (AFP, "<$file") ||
  6550.         (print $logh "Unable to open IPP Attribute file ".$file.", ignored: ".$!);
  6551.  
  6552.     while(<AFP>) {
  6553.         $result .= option_to_ppd($_);
  6554.     }
  6555.  
  6556.     close (AFP);
  6557.  
  6558.     return ($result);
  6559. }
  6560.  
  6561. sub modern_system {
  6562.     my (@list) = @_;
  6563.  
  6564.     if (($modern_shell =~ /.+/) && ($modern_shell ne '/bin/sh')) {
  6565.         # a "modern" shell other than the default shell was specified
  6566.         my $pid = fork();
  6567.  
  6568.         ($pid < 0) && die "failed to fork()";
  6569.  
  6570.         if ($pid == 0) {  # child, execute the commands under a modern shell
  6571.             exec($modern_shell, "-c", @list);
  6572.             die "exec($modern_shell, \"-c\", @list);";
  6573.         } else {  # parent, wait for the child
  6574.             waitpid($pid, 0);
  6575.         }
  6576.     } else {  # the system shell is "modern" enough.
  6577.         system(@list);
  6578.     }
  6579. }
  6580.  
  6581. # Emacs tabulator/indentation
  6582.  
  6583. ### Local Variables:
  6584. ### tab-width: 8
  6585. ### perl-indent-level: 4
  6586. ### End:
  6587.