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