2 # The above Perl path may vary on your system; fix it!!! -*- perl -*-
8 my $ripversion='$Revision=3.0.2.131$';
9 #'# Fix emacs syntax highlighting
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".
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:
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/
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).
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
36 # ==========================================================================
38 # User-configurable settings, edit them if needed
40 # ==========================================================================
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.
47 my $execpath = "/usr/bin:/usr/local/bin:/usr/bin:/bin";
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";
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
57 # Some versions of configure do not fully expand $sysconfdir
59 my $configpath = "/etc/foomatic";
61 # For the stuff below, the settings in the configuration file have priority.
63 # Set to 1 to insert postscript code for page accounting (CUPS only).
64 my $ps_accounting = 1;
65 my $accounting_prolog = "";
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
72 # You can set this to "a2ps", "enscript" or "mpage" to select one of the
73 # default command strings.
74 my $fileconverter = '';
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);
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.
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.
95 my $modern_shell = '';
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.
103 # WARNING: This logfile is a security hole; do not use in production.
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";
111 # End interesting enduser options
113 # ==========================================================================
115 # foomatic-rip spooler-independent PS->Printer filter (RIP) of Foomatic
117 # Copyright 2002 - 2008 Grant Taylor <gtaylor@picante.com>
118 # & Till Kamppeter <till.kamppeter@gmail.com>
119 # & Helge Blischke <h.blischke@srz.de>
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.
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.
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,
139 # Flush everything immediately.
144 ## Constants used by this filter
146 # Error codes, as some spooles behave different depending on the reason why
147 # the RIP failed, we return an error code. As I have only found a table of
148 # error codes for the PPR spooler. If our spooler is really PPR, these
149 # definitions get overwritten by the ones of the PPR version currently in
152 my $EXIT_PRINTED = 0; # file was printed normally
153 my $EXIT_PRNERR = 1; # printer error occured
154 my $EXIT_PRNERR_NORETRY = 2; # printer error with no hope of retry
155 my $EXIT_JOBERR = 3; # job is defective
156 my $EXIT_SIGNAL = 4; # terminated after catching signal
157 my $EXIT_ENGAGED = 5; # printer is otherwise engaged (connection
159 my $EXIT_STARVED = 6; # starved for system resources
160 my $EXIT_PRNERR_NORETRY_ACCESS_DENIED = 7; # bad password? bad port
162 my $EXIT_PRNERR_NOT_RESPONDING = 8; # just doesn't answer at all
164 my $EXIT_PRNERR_NORETRY_BAD_SETTINGS = 9; # interface settings are invalid
165 my $EXIT_PRNERR_NO_SUCH_ADDRESS = 10; # address lookup failed, may be
167 my $EXIT_PRNERR_NORETRY_NO_SUCH_ADDRESS = 11; # address lookup failed, not
169 my $EXIT_INCAPABLE = 50; # printer wants (lacks) features
171 # Standard Unix signal names
182 my $ESPIPE = 29; # the errno value when seeking a pipe or socket
184 # The modern_shell() function will register the PIDs of all shell calls,
185 # so that rip_die() can kill these processes
188 # $kidgeneration stays 0 for the main process, child processes of the
189 # main process get $kidgeneration = 1, their children 2, ...
190 my $kidgeneration = 0;
193 my $retval = $EXIT_PRINTED;
194 use sigtrap qw(handler set_exit_canceled normal-signals
195 handler set_exit_error error-signals
196 handler set_exit_prnerr USR1
197 handler set_exit_prnerr_noretry USR2
198 handler set_exit_engaged TTIN);
201 ## Some important variables
203 # We don't know yet, which spooler will be used. If we don't detect
204 # one. we assume that we do spooler-less printing. Supported spoolers
207 # cups - CUPS - Common Unix Printing System
208 # solaris - Solaris LP (possibly some other SysV LP services as well)
209 # lpd - LPD - Line Printer Daemon
210 # lprng - LPRng - LPR - New Generation
211 # gnulpr - GNUlpr, an enhanced LPD (development stopped)
212 # ppr - PPR (foomatic-rip runs as a PPR RIP)
213 # ppr_int - PPR (foomatic-rip runs as an interface)
214 # cps - CPS - Coherent Printing System
215 # pdq - PDQ - Print, Don't Queue (development stopped)
216 # direct - Direct, spooler-less printing
218 my $spooler = 'direct';
236 my $jobuser = ((getpwuid($<))[0] || `whoami` || "");
239 # Host from which job was sent
240 my $jobhost = `hostname`;
244 my $jobtitle = "$jobuser\@$jobhost";
248 my $rbinumcopies = "0";
250 # Post pipe (command into which the output of this filter should be piped)
253 # job meta-data file path (for Solaris LP)
256 # Files to be printed
259 # Where to send debugging log output. Initialized to STDERR until the command
260 # line arguments are parsed.
263 # JCL prefix to put before the JCL options (Can be modified by a
264 # "*JCLBegin:" keyword in the PPD file):
265 my $jclbegin = "\033%-12345X\@PJL\n";
267 # JCL command to switch the printer to the PostScript interpreter (Can
268 # be modified by a "*JCLToPSInterpreter:" keyword in the PPD file):
269 my $jcltointerpreter = "";
271 # JCL command to close a print job (Can be modified by a "*JCLEnd:"
272 # keyword in the PPD file):
273 my $jclend = "\033%-12345X\@PJL RESET\n";
275 # Prefix for starting every JCL command (Can be modified by
276 # "*FoomaticJCLPrefix:" keyword in the PPD file):
277 my $jclprefix = "\@PJL ";
279 # Under which name were we called and in which directory do we reside
280 $0 =~ m!^(.*/)([^/]+)$!;
282 my $programname = $2;
284 # Filters to convert non-PostScript files
286 (# a2ps (converts also other files than text)
287 'a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o -',
289 'enscript -G @@-M @@PAGESIZE@@ @@-b "Page $%|@@JOBTITLE@@ ' .
290 '--margins=36:36:36:36 --mark-wrapped-lines=arrow --word-wrap -p-',
292 'mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -m36l36b36t36r ' .
295 # spooler-specific file converters, default for the specific spooler when
296 # none of the converters above is chosen. Remove weird characters from the
297 # command line arguments to enhance security
299 (defined($ARGV[0])?removespecialchars($ARGV[0]):"",
300 defined($ARGV[1])?removespecialchars($ARGV[1]):"",
301 defined($ARGV[2])?removespecialchars($ARGV[2]):"",
302 defined($ARGV[3])?removespecialchars($ARGV[3]):"",
303 defined($ARGV[4])?removespecialchars($ARGV[4]):"");
304 my $spoolerfileconverters = {
305 'cups' => "${programdir}texttops '$fixed_args[0]' '$fixed_args[1]' '$fixed_args[2]' " .
306 "'$fixed_args[3]' '$fixed_args[4] page-top=36 page-bottom=36 " .
307 "page-left=36 page-right=36 nolandscape cpi=12 lpi=7 " .
313 # Read config file if present
314 my %conf = readConfFile("$configpath/filter.conf");
316 # Get execution path from config file
317 $execpath = $conf{execpath} if defined $conf{execpath};
318 $ENV{'PATH'} = $execpath;
320 # Get CUPS filter path from config file
321 $cupsfilterpath = $conf{cupsfilterpath} if defined $conf{cupsfilterpath};
324 $debug = $conf{debug} if defined $conf{debug};
326 # Determine which filter to use for non-PostScript files to be converted
328 if (defined $conf{textfilter}) {
329 $fileconverter = $conf{textfilter};
330 $fileconverter eq 'a2ps' and $fileconverter = $fileconverters[0];
331 $fileconverter eq 'enscript' and $fileconverter = $fileconverters[1];
332 $fileconverter eq 'mpage' and $fileconverter = $fileconverters[2];
335 # Set the preferred shell for "system()" execution
336 (defined $conf{preferred_shell}) &&
337 ($modern_shell = $conf{preferred_shell});
338 # if none was preferred, look for a shell that will work
339 foreach my $shell ('/bin/sh', '/bin/bash', '/bin/ksh', '/bin/zsh') {
340 if (($modern_shell eq '') && (-x $shell)) {
341 open(FD, "| ".$shell." -c \"((0<1))\" 2>/dev/null");
342 (close(FD) == 1) && ($modern_shell = $shell);
346 ## Environment variables;
348 # "PPD": PPD file name for CUPS, Solaris, or PPR (if we run as PPR RIP)
349 if (defined($ENV{'PPD'})) {
350 # Clean the file name from weird characters which could cause
351 # unexpected behaviour
352 $ppdfile = removespecialchars($ENV{'PPD'});
353 # CUPS, Solaris LP, and PPR (RIP filter) use the "PPD" environment variable
354 # to make the PPD file name available (we set CUPS here preliminarily,
355 # in the next step we check for Solaris LP and the PPR)
359 # "SPOOLER_KEY": Solaris LP print service
360 if (defined($ENV{'SPOOLER_KEY'})) {
361 $spooler = 'solaris';
363 $ppdfile = $ENV{'PPD'};
364 # set the printer name from the PPD file name
365 ($ppdfile =~ m!^.*/([^/]+)\.ppd$!) &&
368 # Solaris LP may augment the "options" string argument from the command
369 # line with an attributes file ($ATTRPATH)
370 (defined($attrpath = $ENV{'ATTRPATH'})) &&
371 ($optstr = read_attribute_file($attrpath));
375 if (defined($ENV{'PPR_VERSION'})) {
381 if (defined($ENV{'PPR_RIPOPTS'})) {
382 # PPR 1.5 allows the user to specify options for the PPR RIP with the
383 # "--ripopts" option on the "ppr" command line. They are provided to
384 # the RIP via the "PPR_RIPOPTS" environment variable.
385 # Clean the option string from weird characters which could cause
386 # unexpected behaviour
387 $optstr .= removespecialchars("$ENV{'PPR_RIPOPTS'} ");
392 # "LPOPTS": Option settings for some LPD implementations (ex: GNUlpr)
393 if (defined($ENV{'LPOPTS'})) {
394 my @lpopts = split(/,/, removespecialchars($ENV{'LPOPTS'}));
395 foreach my $opt (@lpopts) {
403 # We have an LPD which accepts "-o" for options
409 ## Named command line options
411 # We do not use Getopt::Long because it does not work when between the
412 # option and the argument is no space ("-w80" instead of "-w 80"). This
413 # happens in the command line of LPRng, but also users could type in
414 # options this way when printing without spooler.
416 # Make one option string with a non-printable character as separator,
417 # So we can parse it more easily.
419 # To avoid the separator to be in the options itselves, it is filters
420 # out of the options. This does not break anything as having non
421 # printable characters in the command line options does not make sense
422 # nor is this needed. This way misinterpretation and even abuse is
425 my $argstr = "\x01" .
426 join("\x01", map { removeunprintables($_) } @ARGV) . "\x01";
429 if ($argstr =~ /^\x01-(h|v|-help|-version)\x01$/i) {
431 if ($ripversion =~ /^\$Revision=(.*)\$$/) {
436 print "foomatic-rip revision $ver\n";
437 print "\"man foomatic-rip\" for help.\n";
441 # Debug mode activated via command line
442 if ($argstr =~ s/\x01--debug\x01/\x01/) {
446 # Command line options for verbosity
447 my $verbose = ($argstr =~ s/\x01-v\x01/\x01/);
448 my $quiet = ($argstr =~ s/\x01-q\x01/\x01/);
449 my $show_docs = ($argstr =~ s/\x01-d\x01/\x01/);
451 my $cupscolorprofile;
454 # Grotesquely unsecure; use for debugging only
455 open LOG, "> ${logfile}.log";
460 } elsif (($quiet) && (!$verbose)) {
461 # Quiet mode, do not log
462 open LOG, "> /dev/null";
468 # Default: log to STDERR
474 ## Start debug logging
476 # If we are not in debug mode, we do this later, as we must find out at
477 # first which spooler is used. When printing without spooler we
478 # suppress logging because foomatic-rip is called directly on the
479 # command line and so we avoid logging onto the console.
480 print $logh "foomatic-rip version $ripversion running...\n";
481 # Print the command line only in debug mode, Mac OS X adds very many
482 # options so that CUPS cannot handle the output of the command line
483 # in its log files. If CUPS encounters a line with more than 1024
484 # characters sent into its log files, it aborts the job with an error.
485 if (($debug) || ($spooler ne 'cups')) {
486 print $logh "called with arguments: '", join("', '",@ARGV), "'\n";
492 ## Continue with named options
494 # Check for LPRng first so we do not pick up bogus ppd files by the -p option
495 if ($argstr =~ s/\x01--lprng\x01/\x01/) {
499 # 'PRINTCAP_ENTRY' environment variable is : LPRng
500 # the :ppd=/path/to/ppdfile printcap entry should be used
501 if (defined($ENV{'PRINTCAP_ENTRY'})){
504 @pc = split( /\s*:\s*/, $ENV{'PRINTCAP_ENTRY'} );
507 if( /^ppd=(.*)$/ or /^ppdfile=(.*)$/ ){
508 $ppdfile = removespecialchars($1) if $1;
511 } elsif ($argstr =~ s/\x01--lprng\x01/\x01/g) {
517 # PPD file name given via the command line
518 # allow duplicates, and use the last specified one
519 while ( ($spooler ne 'lprng') and ($argstr =~ s/\x01-p(\x01|)([^\x01]+)\x01/\x01/)) {
522 while ($argstr =~ s/\x01--ppd(\x01|=|)([^\x01]+)\x01/\x01/) {
526 # Check for LPD/GNUlpr by typical options which the spooler puts onto
527 # the filter's command line (options "-w": text width, "-l": text
528 # length, "-i": indent, "-x", "-y": graphics size, "-c": raw printing,
529 # "-n": user name, "-h": host name)
530 if ($argstr =~ s/\x01-h(\x01|)([^\x01]+)\x01/\x01/) {
531 # We have LPD or GNUlpr
532 if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
537 if ($argstr =~ s/\x01-n(\x01|)([^\x01]+)\x01/\x01/) {
538 # We have LPD or GNUlpr
539 if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
544 if (($argstr =~ s/\x01-w(\x01|)\d+\x01/\x01/) ||
545 ($argstr =~ s/\x01-l(\x01|)\d+\x01/\x01/) ||
546 ($argstr =~ s/\x01-x(\x01|)\d+\x01/\x01/) ||
547 ($argstr =~ s/\x01-y(\x01|)\d+\x01/\x01/) ||
548 ($argstr =~ s/\x01-i(\x01|)\d+\x01/\x01/) ||
549 ($argstr =~ s/\x01-c\x01/\x01/)) {
550 # We have LPD or GNUlpr
551 if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
556 # LPRng delivers the option settings via the "-Z" argument
557 if ($argstr =~ s/\x01-Z(\x01|)([^\x01]+)\x01/\x01/) {
558 my @lpopts = split(/,/, $2);
559 foreach my $opt (@lpopts) {
562 $opt = removeshellescapes($opt);
572 # Job title and options for stock LPD
573 if ($argstr =~ s/\x01-[jJ](\x01|)([^\x01]+)\x01/\x01/) {
575 $jobtitle = removeshellescapes($2);
577 if ($spooler eq "lpd") {
578 $optstr .= "$jobtitle ";
583 if ($argstr =~ s/\x01--cps\x01/\x01/) {
588 # Options for spooler-less printing, CPS, or PDQ
589 while ($argstr =~ s/\x01-o(\x01|)([^\x01]+)\x01/\x01/) {
593 $opt = removeshellescapes($opt);
598 # If we don't print as a PPR RIP or as a CPS filter, we print without
599 # spooler (we check for PDQ later)
600 if (($spooler ne 'ppr') && ($spooler ne 'cps')) {
605 # Printer for spooler-less printing or PDQ
606 if ($argstr =~ s/\x01-d(\x01|)([^\x01]+)\x01/\x01/) {
607 $printer = removeshellescapes($2);
609 # Printer for spooler-less printing, PDQ, or LPRng
610 if ($argstr =~ s/\x01-P(\x01|)([^\x01]+)\x01/\x01/) {
611 $printer = removeshellescapes($2);
614 # Were we called from a PDQ wrapper?
615 if ($argstr =~ s/\x01--pdq\x01/\x01/) {
620 # Were we called to build the PDQ driver declaration file?
621 # "--appendpdq=<file>" appends the data to the <file>,
622 # "--genpdq=<file>" creates/overwrites <file> for the data, and
623 # "--genpdq" writes to standard output
625 if (($argstr =~ s/\x01--(gen)(raw|)pdq(\x01|=|)([^\x01]*)\x01/\x01/) ||
626 ($argstr =~ s/\x01--(append)(raw|)pdq(\x01|=|)([^\x01]+)\x01/\x01/)) {
627 # Determine output file name
629 $genpdqfile = ">&STDOUT";
632 $genpdqfile = "> " . removeshellescapes($4);
634 $genpdqfile = ">> " . removeshellescapes($4);
637 # Do we want to have a PDQ driver declaration for a raw printer?
641 "driver \"Raw-Printer-$time\" {
642 # This PDQ driver declaration file was generated automatically by
643 # foomatic-rip to allow raw (filter-less) printing.
644 language_driver all {
645 # We accept all file types and pass them through without any changes
648 ln -s \$INPUT \$OUTPUT
652 ln -s \$INPUT \$OUTPUT
655 open PDQFILE, $genpdqfile or
656 rip_die("Cannot write PDQ driver declaration file",
657 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
658 print PDQFILE join('', @pdqfile);
667 # remove extra spacing if running as LPRng filter
668 $added_lf = "" if $spooler eq 'lprng';
670 ## Command line arguments without name
672 # Remaining arguments
673 my @rargs = split(/\x01/, $argstr);
676 # Load definitions for PPR error messages, check whether we run as
677 # PPR interface or as PPR RIP
678 my( $ppr_printer, $ppr_address, $ppr_options, $ppr_jobbreak, $ppr_feedback,
679 $ppr_codes, $ppr_jobname, $ppr_routing, $ppr_for, $ppr_filetype,
681 if ($spooler eq 'ppr') {
682 # Read interface.sh so we will know the correct exit codes and
683 # also signal.sh for the signal codes
684 my $deffound = 0; # Did we find one of the definition files
686 for my $file (("lib/interface.sh", "lib/signal.sh")) {
688 open FILE, "< $file" || do {
689 print $logh "error opening $file.\n";
694 while(my $line = <FILE>) {
695 # Translate the shell script to Perl
696 if (($line !~ m/^\s*$/) && ($line !~ m/^\s*\#/)) {
697 $line =~ s/^\s*([^\#\s]*)/\$$1;/;
698 push (@definitions, $line);
705 # Apply the definitions loaded from PPR
706 eval join('',@definitions) || do {
707 print $logh "unable to evaluate definitions\n";
708 rip_die ("Error in definitions evaluation",
709 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
713 # Check whether we run as a PPR interface (if not, we run as a PPR RIP)
714 if (($rargs[3] =~ /^\s*\d\d?\s*$/) &&
715 ($rargs[5] =~ /^\s*\d\d?\s*$/) &&
716 (($#rargs == 10) || ($#rargs == 9) || ($#rargs == 7))) {
717 # PPR calls interfaces with many command line parameters,
718 # where the forth and the sixth is a small integer
719 # number. In addition, we have 8 (PPR <= 1.31), 10
720 # (PPR>=1.32), 11 (PPR >= 1.50) command line parameters.
721 # We also check whether the current working directory is a
724 # Get all command line parameters
725 $ppr_printer = removeshellescapes($rargs[0]);
726 $ppr_address = $rargs[1];
727 $ppr_options = removeshellescapes($rargs[2]);
728 $ppr_jobbreak = $rargs[3];
729 $ppr_feedback = $rargs[4];
730 $ppr_codes = $rargs[5];
731 $ppr_jobname = removeshellescapes($rargs[6]);
732 $ppr_routing = removeshellescapes($rargs[7]);
733 $ppr_for = $rargs[8];
734 $ppr_filetype = $rargs[9];
735 $ppr_filetoprint = removeshellescapes($rargs[10]);
737 # Common job parameters
738 $printer = $ppr_printer;
739 $jobtitle = $ppr_jobname;
740 if ((!$jobtitle) && ($ppr_filetoprint)) {
741 $jobtitle = $ppr_filetoprint;
743 $optstr .= "$ppr_options $ppr_routing";
745 # Get the path of the PPD file from the queue configuration
746 $ppdfile = `LANG=en_US; ppad show $ppr_printer | grep PPDFile`;
747 $ppdfile = removeshellescapes($ppdfile);
748 $ppdfile =~ s/PPDFile:\s+//;
749 if ($ppdfile !~ m!^/!) {
750 $ppdfile = "../../share/ppr/PPDFiles/$ppdfile";
754 # We have PPR and run as an interface
755 $spooler = 'ppr_int';
760 my( $cups_jobid, $cups_user, $cups_jobtitle, $cups_copies, $cups_options,
762 if ($spooler eq 'cups') {
764 # Use CUPS font path ("FontPath" in /etc/cups/cupsd.conf)
765 if ($ENV{'CUPS_FONTPATH'}) {
766 $ENV{'GS_LIB'} = $ENV{'CUPS_FONTPATH'} .
767 ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : "");
769 if ($ENV{'CUPS_DATADIR'}) {
770 $ENV{'GS_LIB'} = "$ENV{'CUPS_DATADIR'}/fonts" .
771 ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : "");
775 # Get all command line parameters
776 $cups_jobid = removeshellescapes($rargs[0]);
777 $cups_user = removeshellescapes($rargs[1]);
778 $cups_jobtitle = removeshellescapes($rargs[2]);
779 $cups_copies = removeshellescapes($rargs[3]);
780 $cups_options = removeshellescapes($rargs[4]);
781 $cups_filename = removeshellescapes($rargs[5]);
783 # Common job parameters
784 #$printer = $cups_printer;
785 $jobid = $cups_jobid;
786 $jobtitle = $cups_jobtitle;
787 $jobuser = $cups_user;
788 $copies = $cups_copies;
789 $optstr .= $cups_options;
791 # Check for and handle inputfile vs stdin
792 if ((defined($cups_filename)) && ($cups_filename) &&
793 ($cups_filename ne '-')) {
794 # We get the input from a file
795 @filelist = ($cups_filename);
796 print $logh "Getting input from file $cups_filename\n";
801 if ($spooler eq 'solaris') {
802 # Get all command line parameters
803 # $printer = # argv[0]
804 # ($rargs[0] =~ m!^.*/([^/]+)$!);
805 # $request_id = removeshellescapes($rargs[0]); # argv[1]
806 # $user_name = removeshellescapes($rargs[1]); # argv[2]
807 $jobtitle = removeshellescapes($rargs[2]); # argv[3]
808 # $copies = removeshellescapes($rargs[3]); # argv[4] # handled by the
810 $optstr .= removeshellescapes($rargs[4]); # argv[5]
811 ($#rargs > 4) && # argv[6...]
812 (@filelist = @rargs[5, $#rargs]);
816 if (($spooler eq 'lpd') ||
817 ($spooler eq 'lprng' and !$ppdfile) ||
818 ($spooler eq 'gnulpr')) {
820 # Get PPD file name as the last command line argument
821 $ppdfile = $rargs[$#rargs];
826 # No spooler, CPS, or PDQ
827 if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) {
828 # Which files do we want to print?
829 @filelist = map { removeshellescapes($_) } @rargs;
834 ## Additional spooler-specific preparations
838 if ($spooler eq 'cups') {
840 # This piece of PostScript code (initial idea 2001 by Michael
841 # Allerhand (michael.allerhand at ed dot ac dot uk, vastly
842 # improved by Till Kamppeter in 2002) lets GhostScript output
843 # the page accounting information which CUPS needs on standard
845 # Redesign by Helge Blischke (2004-11-17):
846 # - As the PostScript job itself may define BeginPage and/or EndPage
847 # procedures, or the alternate pstops filter may have inserted
848 # such procedures, we make sure that the accounting routine
849 # will safely coexist with those. To achieve this, we force
850 # - the accountint stuff to be inserted at the very end of the
851 # PostScript job's setup section,
852 # - the accounting stuff just using the return value of the
853 # existing EndPage procedure, if any (and providing a default one
855 # - As PostScript jobs may contain calls to setpagedevice "between"
856 # pages, e.g. to change media type, do in-job stapling, etc.,
857 # we cannot rely on the "showpage count since last pagedevice
858 # activation" but instead count the physical pages by ourselves
859 # (in a global dictionary).
861 if (defined $conf{ps_accounting}) {
862 $ps_accounting = $conf{ps_accounting};
864 $accounting_prolog = $ps_accounting ? "[{
865 %% Code for writing CUPS accounting tags on standard error
867 /cupsPSLevel2 % Determine whether we can do PostScript level 2 or newer
868 systemdict/languagelevel 2 copy
869 known{get exec}{pop pop 1}ifelse 2 ge
873 { % in case of level 2 or higher
874 currentglobal true setglobal % define a dictioary foomaticDict
875 globaldict begin % in global VM and establish a
876 /foomaticDict % pages count key there
884 /cupsGetNumCopies { % Read the number of Copies requested for the current
888 % PS Level 2+: Get number of copies from Page Device dictionary
889 currentpagedevice /NumCopies get
892 % PS Level 1: Number of copies not in Page Device dictionary
896 % Check whether the number is defined, if it is \"null\" use #copies
902 % Check whether the number is defined now, if it is still \"null\" use 1
909 /cupsWrite { % write a string onto standard error
914 /cupsFlush % flush standard error to make it sort of unbuffered
916 (%stderr)(w)file flushfile
920 { % In language level 2, we try to do something reasonable
923 [ % start the array that becomes the procedure
924 currentpagedevice/EndPage 2 copy known
925 {get} % get the existing EndPage procedure
926 {pop pop {exch pop 2 ne}bind}ifelse % there is none, define the default
927 /exec load % make sure it will be executed, whatever it is
928 /dup load % duplicate the result value
929 { % true: a sheet gets printed, do accounting
930 currentglobal true setglobal % switch to global VM ...
931 foomaticDict begin % ... and access our special dictionary
932 PhysPages 1 add % count the sheets printed (including this one)
933 dup /PhysPages exch def % and save the value
935 exch setglobal % return to previous VM
936 (PAGE: )cupsWrite % assemble and print the accounting string ...
937 16 string cvs cupsWrite % ... the sheet count ...
938 ( )cupsWrite % ... a space ...
939 cupsGetNumCopies % ... the number of copies ...
940 16 string cvs cupsWrite % ...
941 (\\n)cupsWrite % ... a newline
944 % false: current page gets discarded; do nothing
945 ]cvx bind % make the array executable and apply bind
949 % In language level 1, we do no accounting currently, as there is no global VM
950 % the contents of which are undesturbed by save and restore.
951 % If we may be sure that showpage never gets called inside a page related save / restore pair
952 % we might implement an hack with showpage similar to the one above.
955 } stopped cleartomark
958 # On which queue are we printing?
959 # CUPS gives the PPD file the same name as the printer queue,
960 # so we can get the queue name from the name of the PPD file.
961 $ppdfile =~ m!^(.*/)([^/]+)\.ppd$!;
965 # No spooler, CPS, or PDQ
967 if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) {
969 # Path for personal Foomatic configuration
970 my $user_default_path = "$ENV{'HOME'}/.foomatic";
974 # No printer definition file selected, check whether we have a
975 # default printer defined.
976 for my $conf_file (("./.directconfig",
979 "$user_default_path/direct/.config",
980 "$user_default_path/direct.conf",
981 "$configpath/direct/.config",
982 "$configpath/direct.conf")) {
983 if (open CONFIG, "< $conf_file") {
984 while (my $line = <CONFIG>) {
986 if ($line =~ /^default\s*:\s*([^:\s]+)\s*$/) {
999 # Neither in a config file nor on the command line a printer was
1002 rip_die("No printer definition (option \"-P <name>\") " .
1003 "specified!", $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
1006 # Search for the PPD file
1008 # Search also common spooler-specific locations, this way a printer
1009 # configured under a certain spooler can also be used without
1013 $ppdfile = $printer;
1014 # CPS can have the PPD in the spool directory
1015 } elsif (($spooler eq 'cps') &&
1016 (-r "/var/spool/lpd/${printer}/${printer}.ppd")) {
1017 $ppdfile = "/var/spool/lpd/${printer}/${printer}.ppd";
1018 } elsif (($spooler eq 'cps') &&
1019 (-r "/var/local/spool/lpd/${printer}/${printer}.ppd")) {
1020 $ppdfile = "/var/local/spool/lpd/${printer}/${printer}.ppd";
1021 } elsif (($spooler eq 'cps') &&
1022 (-r "/var/local/lpd/${printer}/${printer}.ppd")) {
1023 $ppdfile = "/var/local/lpd/${printer}/${printer}.ppd";
1024 } elsif (($spooler eq 'cps') &&
1025 (-r "/var/spool/lpd/${printer}.ppd")) {
1026 $ppdfile = "/var/spool/lpd/${printer}.ppd";
1027 } elsif (($spooler eq 'cps') &&
1028 (-r "/var/local/spool/lpd/${printer}.ppd")) {
1029 $ppdfile = "/var/local/spool/lpd/${printer}.ppd";
1030 } elsif (($spooler eq 'cps') &&
1031 (-r "/var/local/lpd/${printer}.ppd")) {
1032 $ppdfile = "/var/local/lpd/${printer}.ppd";
1033 } elsif (-r "${printer}.ppd") { # current dir
1034 $ppdfile = "${printer}.ppd";
1035 } elsif (-r "$user_default_path/${printer}.ppd") { # user dir
1036 $ppdfile = "$user_default_path/${printer}.ppd";
1037 } elsif (-r "$configpath/direct/${printer}.ppd") { # system dir
1038 $ppdfile = "$configpath/direct/${printer}.ppd";
1039 } elsif (-r "$configpath/${printer}.ppd") { # system dir
1040 $ppdfile = "$configpath/${printer}.ppd";
1041 } elsif (-r "/etc/cups/ppd/${printer}.ppd") { # CUPS config dir
1042 $ppdfile = "/etc/cups/ppd/${printer}.ppd";
1043 } elsif (-r "/usr/local/etc/cups/ppd/${printer}.ppd") {
1044 $ppdfile = "/usr/local/etc/cups/ppd/${printer}.ppd";
1045 } elsif (-r "/usr/share/ppr/PPDFiles/${printer}.ppd") { # PPR PPDs
1046 $ppdfile = "/usr/share/ppr/PPDFiles/${printer}.ppd";
1047 } elsif (-r "/usr/local/share/ppr/PPDFiles/${printer}.ppd") {
1048 $ppdfile = "/usr/local/share/ppr/PPDFiles/${printer}.ppd";
1050 rip_die ("There is no readable PPD file for the printer " .
1051 "$printer, is it configured?",
1052 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
1059 ## Files to be printed (can be more than one for spooler-less printing)
1061 # Empty file list -> print STDIN
1062 if ($#filelist < 0) {
1063 @filelist = ("<STDIN>");
1069 for $file (@filelist) {
1070 if ($file ne "<STDIN>") {
1071 if ($file =~ /^-/) {
1072 rip_die ("Invalid argument: $file",
1073 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
1074 } elsif (! -r $file) {
1075 print $logh "File $file does not exist/is not readable\n";
1076 splice(@filelist, $filecnt, 1);
1085 ## When we print without spooler or with CPS do not log onto STDERR unless
1086 ## the "-v" ('Verbose') is set or the debug mode is used
1087 if ((($spooler eq 'direct') || ($spooler eq 'cps') || ($genpdqfile)) &&
1088 (!$verbose) && (!$debug)) {
1090 open LOG, "> /dev/null";
1094 $logh->autoflush(1);
1101 # If we are in debug mode, we do this earlier.
1102 print $logh "foomatic-rip version $ripversion running...\n";
1103 # Print the command line only in debug mode, Mac OS X adds very many
1104 # options so that CUPS cannot handle the output of the command line
1105 # in its log files. If CUPS encounters a line with more than 1024
1106 # characters sent into its log files, it aborts the job with an error.
1107 if (($debug) || ($spooler ne 'cups')) {
1108 print $logh "called with arguments: '", join("', '",@ARGV), "'\n";
1116 # Load the PPD file and build a data structure for the renderer's
1117 # command line and the options
1118 open PPD, "< $ppdfile" || do {
1119 print $logh "error opening $ppdfile.\n";
1120 rip_die ("Unable to open PPD file $ppdfile",
1121 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
1124 print $logh "Parsing PPD file ...\n";
1126 my $dat = {}; # data structure for the options
1127 my $currentargument = ""; # We are currently reading this argument
1129 # If we have an old Foomatic 2.0.x PPD file, read its built-in Perl
1130 # data structure into @datablob and the default values in %ppddefaults
1131 # Then delete the $dat structure, replace it by the one "eval"ed from
1132 # @datablob, and correct the default settings according to the ones of
1133 # the main PPD structure
1135 my $jclprefixset = 0;
1137 # Parse the PPD file
1140 # foomatic-rip should also work with PPD file downloaded under Windows.
1143 if (m!^\*NickName:\s*\"(.*)$!) {
1144 # "*NickName: <code>"
1147 # Code string can have multiple lines, read all of them
1149 while ($line !~ m!\"!) {
1150 if ($line =~ m!&&$!) {
1151 # line continues in next line
1152 $cmd .= substr($line, 0, -2);
1161 $line =~ m!^([^\"]*)\"!;
1163 $model = unhtmlify($cmd);
1164 } elsif (m!^\*FoomaticIDs:\s*\"?\s*(\S+?)\s+(\S+?)\s*\"?\s*$!) {
1165 # "*FoomaticIDs: <printer ID> <driver ID>"
1170 $dat->{'driver'} = $driver;
1171 } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
1172 # "*FoomaticRIPPostPipe: <code>"
1175 # Code string can have multiple lines, read all of them
1177 while ($line !~ m!\"!) {
1178 if ($line =~ m!&&$!) {
1179 # line continues in next line
1180 $cmd .= substr($line, 0, -2);
1189 $line =~ m!^([^\"]*)\"!;
1191 $postpipe = unhtmlify($cmd);
1192 } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) {
1193 # "*FoomaticRIPCommandLine: <code>"
1196 # Code string can have multiple lines, read all of them
1198 while ($line !~ m!\"!) {
1199 if ($line =~ m!&&$!) {
1200 # line continues in next line
1201 $cmd .= substr($line, 0, -2);
1210 $line =~ m!^([^\"]*)\"!;
1212 $dat->{'cmd'} = unhtmlify($cmd);
1213 } elsif (m!^\*FoomaticNoPageAccounting:\s*\"?\s*(\S+?)\s*\"?\s*$!) {
1214 # "*FoomaticRIPNoPageAccounting: <boolean value>"
1217 if ($value =~ /^True$/i) {
1218 # Driver is not compatible with page accounting according to the
1219 # Foomatic database, so turn it off for this driver
1221 $accounting_prolog = '';
1222 print $logh "CUPS page accounting disabled by driver.\n";
1224 } elsif (m!^\*cupsFilter:\s*\"(.*)$!) {
1225 # "*cupsFilter: <code>"
1228 # Code string can have multiple lines, read all of them
1230 while ($line !~ m!\"!) {
1231 if ($line =~ m!&&$!) {
1232 # line continues in next line
1233 $cmd .= substr($line, 0, -2);
1242 $line =~ m!^([^\"]*)\"!;
1244 my $cupsfilterline = unhtmlify($cmd);
1245 if ($cupsfilterline =~ /^\s*(\S+)\s+\d+\s+(\S+)\s*$/) {
1246 print $logh "*cupsFilter: \"$cupsfilterline\"\n";
1247 # Make a hash by mime type for all CUPS filters set in this PPD
1248 $dat->{'cupsfilter'}{$1} = $2;
1250 } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) {
1251 # "*CustomPageSize True: <code>"
1252 my $setting = "Custom";
1253 my $translation = "Custom Size";
1255 # Make sure that the argument is in the data structure
1256 checkarg ($dat, "PageSize");
1257 checkarg ($dat, "PageRegion");
1258 # Make sure that the setting is in the data structure
1259 checksetting ($dat, "PageSize", $setting);
1260 checksetting ($dat, "PageRegion", $setting);
1261 $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation;
1262 $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation;
1264 # Code string can have multiple lines, read all of them
1266 while ($line !~ m!\"!) {
1267 if ($line =~ m!&&$!) {
1268 # line continues in next line
1269 $code .= substr($line, 0, -2);
1278 $line =~ m!^([^\"]*)\"!;
1280 if ($code !~ m!^%% FoomaticRIPOptionSetting!m) {
1281 $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code;
1282 $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code;
1284 } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) {
1285 # "*[JCL]OpenUI *<option>[/<translation>]: <type>"
1286 my $argnametrans = $2;
1289 my $translation = "";
1290 if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) {
1294 $argname = $argnametrans;
1296 # Make sure that the argument is in the data structure
1297 checkarg ($dat, $argname);
1299 $dat->{'args_byname'}{$argname}{'comment'} = $translation;
1300 # Set the argument type only if not defined yet, a
1301 # definition in "*FoomaticRIPOption" has priority
1302 if ( !($dat->{'args_byname'}{$argname}{'type'}) ) {
1303 if ($argtype eq "PickOne") {
1304 $dat->{'args_byname'}{$argname}{'type'} = 'enum';
1305 } elsif ($argtype eq "PickMany") {
1306 $dat->{'args_byname'}{$argname}{'type'} = 'pickmany';
1307 } elsif ($argtype eq "Boolean") {
1308 $dat->{'args_byname'}{$argname}{'type'} = 'bool';
1311 # Mark in which argument we are currently, so that we can find
1312 # the entries for the choices
1313 $currentargument = $argname;
1314 } elsif (m!^\*(JCL|)CloseUI:\s+\*([^:/\s]+)\s*$!) {
1315 # "*[JCL]CloseUI *<option>"
1317 # Unmark the current argument to do not mis-interpret any keywords
1319 $currentargument = "";
1320 } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*\"?\s*(\S+?)\s+(\S+)\s+(\S)\s*\"?\s*$!) ||
1321 (m!^\*FoomaticRIPOption ([^/:\s]+):\s*\"?\s*(\S+?)\s+(\S+)\s+(\S)\s+(\S+?)\s*\"?\s*$!)){
1322 # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]"
1323 # <order> only used for 1-choice enum options
1329 # Make sure that the argument is in the data structure
1330 checkarg ($dat, $argname);
1332 $dat->{'args_byname'}{$argname}{'type'} = $argtype;
1333 if ($argstyle eq "PS") {
1334 $dat->{'args_byname'}{$argname}{'style'} = 'G';
1335 } elsif ($argstyle eq "CmdLine") {
1336 $dat->{'args_byname'}{$argname}{'style'} = 'C';
1337 } elsif ($argstyle eq "JCL") {
1338 $dat->{'args_byname'}{$argname}{'style'} = 'J';
1340 } elsif ($argstyle eq "Composite") {
1341 $dat->{'args_byname'}{$argname}{'style'} = 'X';
1343 $dat->{'args_byname'}{$argname}{'spot'} = $spot;
1344 # $order only defined here for 1-choice enum options
1346 $dat->{'args_byname'}{$argname}{'order'} = $order;
1348 } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) {
1349 # "*FoomaticRIPOptionPrototype <option>: <code>"
1350 # Used for numerical and string options only
1353 # Make sure that the argument is in the data structure
1354 checkarg ($dat, $argname);
1356 # Code string can have multiple lines, read all of them
1358 while ($line !~ m!\"!) {
1359 if ($line =~ m!&&$!) {
1360 # line continues in next line
1361 $proto .= substr($line, 0, -2);
1364 $proto .= "$line\n";
1370 $line =~ m!^([^\"]*)\"!;
1372 $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto);
1373 } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*\"?\s*(\S+?)\s+(\S+?)\s*\"?\s*$!) {
1374 # "*FoomaticRIPOptionRange <option>: <min> <max>"
1375 # Used for numerical options only
1379 # Make sure that the argument is in the data structure
1380 checkarg ($dat, $argname);
1382 $dat->{'args_byname'}{$argname}{'min'} = $min;
1383 $dat->{'args_byname'}{$argname}{'max'} = $max;
1384 } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*\"?\s*(\S+?)\s*\"?\s*$!) {
1385 # "*FoomaticRIPOptionMaxLength <option>: <length>"
1386 # Used for string options only
1389 # Make sure that the argument is in the data structure
1390 checkarg ($dat, $argname);
1392 $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength;
1393 } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) {
1394 # "*FoomaticRIPOptionAllowedChars <option>: <code>"
1395 # Used for string options only
1399 # Code string can have multiple lines, read all of them
1401 while ($line !~ m!\"!) {
1402 if ($line =~ m!&&$!) {
1403 # line continues in next line
1404 $code .= substr($line, 0, -2);
1413 $line =~ m!^([^\"]*)\"!;
1415 # Make sure that the argument is in the data structure
1416 checkarg ($dat, $argname);
1418 $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code);
1419 } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) {
1420 # "*FoomaticRIPOptionAllowedRegExp <option>: <code>"
1421 # Used for string options only
1425 # Code string can have multiple lines, read all of them
1427 while ($line !~ m!\"!) {
1428 if ($line =~ m!&&$!) {
1429 # line continues in next line
1430 $code .= substr($line, 0, -2);
1439 $line =~ m!^([^\"]*)\"!;
1441 # Make sure that the argument is in the data structure
1442 checkarg ($dat, $argname);
1444 $dat->{'args_byname'}{$argname}{'allowedregexp'} =
1446 } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) {
1447 # "*OrderDependency: <order> <section> *<option>"
1451 # Make sure that the argument is in the data structure
1452 checkarg ($dat, $argname);
1454 $dat->{'args_byname'}{$argname}{'order'} = $order;
1455 $dat->{'args_byname'}{$argname}{'section'} = $section;
1456 } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
1457 # "*Default<option>: <value>"
1460 # Make sure that the argument is in the data structure
1461 checkarg ($dat, $argname);
1463 $dat->{'args_byname'}{$argname}{'default'} = $default;
1464 } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*\"?\s*([^/:\s]+?)\s*\"?\s*$!) {
1465 # "*FoomaticRIPDefault<option>: <value>"
1466 # Used for numerical options only
1469 # Make sure that the argument is in the data structure
1470 checkarg ($dat, $argname);
1472 $dat->{'args_byname'}{$argname}{'fdefault'} = $default;
1473 } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) {
1474 # "*<option> <choice>[/<translation>]: <code>"
1475 my $settingtrans = $1;
1477 my $translation = "";
1479 if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) {
1483 $setting = $settingtrans;
1485 # Make sure that the argument is in the data structure
1486 checkarg ($dat, $currentargument);
1487 # Make sure that the setting is in the data structure (enum options)
1489 ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool');
1491 if (lc($setting) eq "true") {
1492 if (!$dat->{'args_byname'}{$currentargument}{'comment'}) {
1493 $dat->{'args_byname'}{$currentargument}{'comment'} =
1496 $dat->{'args_byname'}{$currentargument}{'comment_true'} =
1499 $dat->{'args_byname'}{$currentargument}{'comment_false'} =
1503 checksetting ($dat, $currentargument, $setting);
1504 # Make sure that this argument has a default setting, even if
1505 # none is defined in this PPD file
1506 if (!defined ($dat->{'args_byname'}{$currentargument}{'default'})) {
1507 $dat->{'args_byname'}{$currentargument}{'default'} = $setting;
1509 $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation;
1512 # Code string can have multiple lines, read all of them
1514 while ($line !~ m!\"!) {
1515 if ($line =~ m!&&$!) {
1516 # line continues in next line
1517 $code .= substr($line, 0, -2);
1526 $line =~ m!^([^\"]*)\"!;
1528 if ($code !~ m!^%% FoomaticRIPOptionSetting!) {
1530 if (lc($setting) eq "true") {
1531 $dat->{'args_byname'}{$currentargument}{'proto'} = $code;
1533 $dat->{'args_byname'}{$currentargument}{'protof'} = $code;
1536 $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code;
1539 } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) ||
1540 (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) {
1541 # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>"
1542 # For boolean options <choice> is not given
1551 # Make sure that the argument is in the data structure
1552 checkarg ($dat, $argname);
1553 # Make sure that the setting is in the data structure (enum options)
1555 checksetting ($dat, $argname, $setting);
1556 # Make sure that this argument has a default setting, even if
1557 # none is defined in this PPD file
1558 if (!defined ($dat->{'args_byname'}{$argname}{'default'})) {
1559 $dat->{'args_byname'}{$argname}{'default'} = $setting;
1563 # Code string can have multiple lines, read all of them
1565 while ($line !~ m!\"!) {
1566 if ($line =~ m!&&$!) {
1567 # line continues in next line
1568 $code .= substr($line, 0, -2);
1577 $line =~ m!^([^\"]*)\"!;
1580 $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code);
1582 $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code);
1584 } elsif (m!^\*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix):\s*\"(.*)$!) {
1585 # "*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix): <code>"
1586 # The printer supports PJL/JCL when there is such a line
1591 # Code string can have multiple lines, read all of them
1593 while ($line !~ m!\"!) {
1594 if ($line =~ m!&&$!) {
1595 # line continues in next line
1596 $code .= substr($line, 0, -2);
1605 $line =~ m!^([^\"]*)\"!;
1607 if ($item eq 'Begin') {
1608 $jclbegin = unhexify($code);
1609 $jclprefix = "" if (!$jclprefixset) && ($jclbegin !~ /PJL/s);
1610 } elsif ($item eq 'ToPSInterpreter') {
1611 $jcltointerpreter = unhexify($code);
1612 } elsif ($item eq 'End') {
1613 $jclend = unhexify($code);
1614 } elsif ($item eq 'Prefix') {
1615 $jclprefix = unhexify($code);
1618 } elsif (m!^\*\% COMDATA \#(.*)$!) {
1619 # If we have an old Foomatic 2.0.x PPD file, collect its Perl data
1620 push (@datablob, $1);
1625 # If we have an old Foomatic 2.0.x PPD file use its Perl data structure
1626 if ($#datablob >= 0) {
1627 print $logh "${added_lf}You are using an old Foomatic 2.0 PPD file, consider " .
1628 "upgrading.${added_lf}\n";
1630 if (eval join('',@datablob)) {
1631 # Overtake default settings from the main structure of the PPD file
1632 for my $arg (@{$dat->{'args'}}) {
1633 if ($arg->{'default'}) {
1634 $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} =
1640 $dat->{'jcl'} = $dat->{'pjl'};
1642 # Perl structure broken
1643 print $logh "${added_lf}Unable to evaluate datablob, print job may come " .
1644 "out incorrectly or not at all.${added_lf}\n";
1650 ## We do not need to parse the PostScript job when we don't have
1651 ## any options. If we have options, we must check whether the
1652 ## default settings from the PPD file are valid and correct them
1656 if ((!defined(@{$dat->{'args'}})) ||
1657 ($#{$dat->{'args'}} < 0)) {
1658 # We don't have any options, so we do not need to parse the
1662 # Let the default value of a boolean option being 0 or 1 instead of
1663 # "True" or "False", range-check the defaults of all options and
1664 # issue warnings if the values are not valid
1665 checkoptions($dat, 'default');
1667 # Adobe's PPD specs do not support numerical
1668 # options. Therefore the numerical options are mapped to
1669 # enumerated options in the PPD file and their characteristics
1670 # as a numerical option are stored in "*Foomatic..."
1671 # keywords. A default must be between the enumerated
1672 # fixed values. The default
1673 # value must be given by a "*FoomaticRIPDefault<option>:
1674 # <value>" line in the PPD file. But this value is only valid
1675 # if the "official" default given by a "*Default<option>:
1676 # <value>" line (it must be one of the enumerated values)
1677 # points to the enumerated value which is closest to this
1678 # value. This way a user can select a default value with a
1679 # tool only supporting PPD files but not Foomatic extensions.
1680 # This tool only modifies the "*Default<option>: <value>" line
1681 # and if the "*FoomaticRIPDefault<option>: <value>" had always
1682 # priority, the user's change in "*Default<option>: <value>"
1683 # would have no effect.
1685 for my $arg (@{$dat->{'args'}}) {
1686 if ($arg->{'fdefault'}) {
1687 if ($arg->{'default'}) {
1688 if ($arg->{'type'} =~ /^(int|float)$/) {
1689 if ($arg->{'fdefault'} < $arg->{'min'}) {
1690 $arg->{'fdefault'} = $arg->{'min'};
1692 if ($arg->{'fdefault'} > $arg->{'max'}) {
1693 $arg->{'fdefault'} = $arg->{'max'};
1695 if ($arg->{'type'} eq 'int') {
1696 $arg->{'fdefault'} = POSIX::floor($arg->{'fdefault'});
1698 my $mindiff = abs($arg->{'max'} - $arg->{'min'});
1700 for my $val (@{$arg->{'vals'}}) {
1701 if (abs($arg->{'fdefault'} - $val->{'value'}) <
1704 abs($arg->{'fdefault'} - $val->{'value'});
1705 $closestvalue = $val->{'value'};
1708 if (($arg->{'default'} == $closestvalue) ||
1709 (abs($arg->{'default'} - $closestvalue) /
1710 $closestvalue < 0.001)) {
1711 $arg->{'default'} = $arg->{'fdefault'};
1715 $arg->{'default'} = $arg->{'fdefault'};
1721 # Is our PPD for a CUPS raster driver
1722 if (my $cupsfilter = $dat->{'cupsfilter'}{"application/vnd.cups-raster"}) {
1724 # Search filter in cupsfilterpath
1725 # The %Y is a placeholder for the option settings
1727 for (split(':', $cupsfilterpath)) {
1728 if (-x "$_/$cupsfilter") {
1730 $cupsfilter = "$_/$cupsfilter 0 '' '' 0 '%Y%X'";
1737 # We do not have the required filter, so we assume that
1738 # rendering this job is supposed to be done on a remote
1739 # server. So we do not define a renderer command line and
1740 # embed only the option settings (as we had a PostScript
1741 # printer). This way the settings are # taken into account
1742 # when the job is rendered on the server.
1743 print $logh "${added_lf}CUPS filter for this PPD file not found " .
1744 "assuming that job will be rendered on a remote server. Only " .
1745 "the PostScript of the options will be inserted into the " .
1746 "PostScript data stream.${added_lf}\n";
1750 # use pstoraster script if available, otherwise run GhostScript
1752 my $pstoraster = "pstoraster";
1753 my $havepstoraster = 0;
1754 for (split(':', $cupsfilterpath)) {
1755 if (-x "$_/$pstoraster") {
1757 $pstoraster = "$_/$pstoraster 0 '' '' 0 '%X'";
1762 if (!$havepstoraster) {
1764 # Build GhostScript command line
1765 $pstoraster = "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOMEDIAATTRS -sDEVICE=cups -sOutputFile=-%W -"
1769 # build GhostScript/CUPS driver command line
1770 $dat->{'cmd'} = "$pstoraster | $cupsfilter";
1772 # Set environment variables
1773 $ENV{'PPD'} = $ppdfile;
1778 # Was the RIP command line defined in the PPD file? If not, we assume a
1779 # PostScript printer and do not render/translate the input data
1780 if (!defined($dat->{'cmd'})) {
1781 $dat->{'cmd'} = "cat%A%B%C%D%E%F%G%H%I%J%K%L%M%Z";
1783 # No command line, no options, we have a raw queue, don't check
1784 # whether the input is PostScript and ignore the "docs" option,
1785 # simply pass the input data to the backend.
1787 $model = "Raw queue";
1793 ## Summary for debugging
1794 print $logh "${added_lf}Parameter Summary\n";
1795 print $logh "-----------------${added_lf}\n";
1796 print $logh "Spooler: $spooler\n";
1797 print $logh "Printer: $printer\n";
1798 print $logh "Shell: $modern_shell\n";
1799 print $logh "PPD file: $ppdfile\n";
1800 print $logh "ATTR file: $attrpath\n";
1801 print $logh "Printer model: $model\n";
1802 # Print the options string only in debug mode, Mac OS X adds very many
1803 # options so that CUPS cannot handle the output of the option string
1804 # in its log files. If CUPS encounters a line with more than 1024 characters
1805 # sent into its log files, it aborts the job with an error.
1806 if (($debug) || ($spooler ne 'cups')) {
1807 print $logh "Options: $optstr\n";
1809 print $logh "Job title: $jobtitle\n";
1810 print $logh "File(s) to be printed: ${added_lf}@filelist${added_lf}\n";
1811 print $logh "GhostScript extra search path ('GS_LIB'): $ENV{'GS_LIB'}\n"
1816 ## Parse options from command line ($optstr)
1818 # Before we start, save the defaults for printing documentation pages
1820 copyoptions($dat, 'default', 'userval');
1823 # The options are "foo='bar nut'", "foo", "nofoo", "'bar nut'", or
1824 # "foo:'bar nut'" (when GPR was used) all with spaces between...
1825 # In addition they can be preceeded by page ranges, separated with a
1830 # Variable for PPR's backend interface name (parallel, tcpip, atalk, ...)
1834 # Array to collect unknown options so that they can get passed to the
1835 # backend interface of PPR. For other spoolers we ignore them.
1837 my @backendoptions = ();
1840 while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+=[\'\"].*?[\'\"]) ?!!i) {
1844 # "foo:'bar nut'" (GPR separates option and setting with a colon ":")
1845 while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+:[\'\"].*?[\'\"]) ?!!i) {
1846 #while ($optstr =~ s!(\w+=[\'\"].*?[\'\"])!!i) {
1850 # "'bar nut'", "'foo=bar nut'", "'foo:bar nut'"
1851 while ($optstr =~ s!([\'\"].+?[\'\"]) ?!!) {
1853 $opt =~ s/[\'\"]//g; # Make only sure that we didn't quote
1854 # the option for a second time when we read
1855 # rge options from the command line or
1856 # environment variable
1862 push(@opts, split(/ /,$optstr));
1864 # Now actually process those pesky options...
1867 print $logh "Pondering option '$_'\n";
1869 # "docs" option to print help page
1870 if ((lc($_) =~ /^\s*docs\s*$/) ||
1871 (lc($_) =~ /^\s*docs\s*=\s*true\s*$/)) {
1872 # The second one is necessary becuase CUPS 1.1.15 or newer sees
1873 # "docs" as boolean option and modifies it to "docs=true"
1878 # "profile" option to supply a color correction profile to a
1879 # CUPS raster driver
1880 if (lc($_) =~ /^\s*profile=(\S+)\s*$/) {
1881 $cupscolorprofile=$1;
1882 $dat->{'cmd'} =~ s!\%X!profile=$cupscolorprofile!g;
1883 $dat->{'cmd'} =~ s!\%W! -c\"<</cupsProfile($cupscolorprofile)>>setpagedevice\"!g;
1887 # Is the command line option limited to certain page ranges? If so,
1888 # mark the setting with a hash key containing the ranges
1890 if (s/^(even|odd|[\d,-]+)://i) {
1891 $optionset = "pages:$1";
1893 $optionset = 'userval';
1896 # Solaris options that have no reason to be
1897 if (/^nobanner$/ || /^dest=.+$/ || /^protocol=.+$/) {
1902 if ((m!([^=]+)=\'?(.*)\'?!) || (m!([^=:]+):\'?(.*)\'?!)) {
1903 my ($aname, $avalue) = ($1, $2);
1905 if (($optionset =~ /pages/) &&
1906 ($arg = argbyname($aname)) &&
1907 ((!defined($arg->{'section'})) ||
1908 ($arg->{'section'} !~ /^(Any|Page)Setup/))) {
1909 print $logh "This option is not a \"PageSetup\" or " .
1910 "\"AnySetup\" option, so it cannot be restricted to " .
1915 # At first look for the "backend" option to determine the PPR
1917 if (($aname =~ m!^backend$!i) && ($spooler eq 'ppr_int')) {
1918 # Backend interface name
1920 } elsif ($aname =~ m!^media$!i) {
1922 # Standard arguments?
1924 # sides=one|two-sided-long|short-edge
1926 # Rummage around in the media= option for known media, source,
1928 # We ought to do something sensible to make the common manual
1929 # boolean option work when specified as a media= tray thing.
1931 # Note that this fails miserably when the option value is in
1932 # fact a number; they all look alike. It's unclear how many
1933 # drivers do that. We may have to standardize the verbose
1934 # names to make them work as selections, too.
1936 my @values = split(',',$avalue);
1939 if ($dat->{'args_byname'}{'PageSize'} and
1940 $val=valbyname($dat->{'args_byname'}{'PageSize'},$_)) {
1941 $dat->{'args_byname'}{'PageSize'}{$optionset} =
1943 # Keep "PageRegion" in sync
1944 if ($dat->{'args_byname'}{'PageRegion'} and
1945 $val=valbyname($dat->{'args_byname'}{'PageRegion'},
1947 $dat->{'args_byname'}{'PageRegion'}{$optionset} =
1950 } elsif ($dat->{'args_byname'}{'PageSize'}
1952 $dat->{'args_byname'}{'PageSize'}{$optionset} = $_;
1953 # Keep "PageRegion" in sync
1954 if ($dat->{'args_byname'}{'PageRegion'}) {
1955 $dat->{'args_byname'}{'PageRegion'}{$optionset} =
1958 } elsif ($dat->{'args_byname'}{'MediaType'} and
1959 $val=valbyname($dat->{'args_byname'}{'MediaType'},
1961 $dat->{'args_byname'}{'MediaType'}{$optionset} =
1963 } elsif ($dat->{'args_byname'}{'InputSlot'} and
1964 $val=valbyname($dat->{'args_byname'}{'InputSlot'},
1966 $dat->{'args_byname'}{'InputSlot'}{$optionset} =
1968 } elsif (lc($_) eq 'manualfeed') {
1969 # Special case for our typical boolean manual
1970 # feeder option if we didn't match an InputSlot above
1971 if (defined($dat->{'args_byname'}{'ManualFeed'})) {
1972 $dat->{'args_byname'}{'ManualFeed'}{$optionset} = 1;
1975 print $logh "Unknown \"media\" component: \"$_\".\n";
1978 } elsif ($aname =~ m!^sides$!i) {
1979 # Handle the standard duplex option, mostly
1980 if ($avalue =~ m!^two-sided!i) {
1981 if (defined($dat->{'args_byname'}{'Duplex'})) {
1982 # Default to long-edge binding here, for the case that
1983 # there is no binding setting
1984 $dat->{'args_byname'}{'Duplex'}{$optionset} =
1986 # Check the binding: "long edge" or "short edge"
1987 if ($avalue =~ m!long-edge!i) {
1988 if (defined($dat->{'args_byname'}{'Binding'})) {
1989 $dat->{'args_byname'}{'Binding'}{$optionset} =
1990 $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'LongEdge'}{'value'};
1992 $dat->{'args_byname'}{'Duplex'}{$optionset} =
1995 } elsif ($avalue =~ m!short-edge!i) {
1996 if (defined($dat->{'args_byname'}{'Binding'})) {
1997 $dat->{'args_byname'}{'Binding'}{$optionset} =
1998 $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'ShortEdge'}{'value'};
2000 $dat->{'args_byname'}{'Duplex'}{$optionset} =
2005 } elsif ($avalue =~ m!^one-sided!i) {
2006 if (defined($dat->{'args_byname'}{'Duplex'})) {
2007 $dat->{'args_byname'}{'Duplex'}{$optionset} = 'None';
2011 # We should handle the other half of this option - the
2012 # BindEdge bit. Also, are there well-known ipp/cups
2013 # options for Collate and StapleLocation? These may be
2017 # Various non-standard printer-specific options
2018 if ($arg = argbyname($aname)) {
2019 if (defined(my $newvalue =
2020 checkoptionvalue($dat, $aname, $avalue, 0))) {
2021 # If the choice is valid, use it, otherwise
2023 $arg->{$optionset} = $newvalue;
2024 # If this argument is PageSize or PageRegion,
2025 # also set the other
2026 syncpagesize($dat, $aname, $avalue, $optionset);
2028 # Invalid choice, make log entry
2029 print $logh "Invalid choice $aname=$avalue.\n";
2031 } elsif ($spooler eq 'ppr_int') {
2032 # Unknown option, pass it to PPR's backend interface
2033 push (@backendoptions, "$aname=$avalue");
2035 # Unknown option, make log entry
2036 print $logh "Unknown option $aname=$avalue.\n";
2039 } elsif (m!^([\d\.]+)x([\d\.]+)([A-Za-z]*)$!) {
2040 my ($w, $h, $u) = ($1, $2, $3);
2042 if (($w != 0) && ($h != 0) &&
2043 ($arg=argbyname("PageSize")) &&
2044 (defined($arg->{'vals_byname'}{'Custom'}))) {
2045 $arg->{$optionset} = "Custom.${w}x${h}${u}";
2046 # Keep "PageRegion" in sync
2047 if ($dat->{'args_byname'}{'PageRegion'}) {
2048 $dat->{'args_byname'}{'PageRegion'}{$optionset} =
2052 } elsif ((m!^\s*no(.+)\s*$!i) and ($arg=argbyname($1))) {
2053 # standard bool args:
2054 # landscape; what to do here?
2055 # duplex; we should just handle this one OK now?
2056 $arg->{$optionset} = 0;
2057 } elsif (m!^\s*(.+)\s*$!) {
2058 if ($arg=argbyname($1)) {
2059 $arg->{$optionset} = 1;
2061 print $logh "Unknown boolean option \"$1\".\n";
2065 $do_docs = 1 if( $show_docs );
2068 ## Were we called to build the PDQ driver declaration file?
2071 @pdqfile = buildpdqdriver($dat, 'userval');
2072 open PDQFILE, $genpdqfile or
2073 rip_die("Cannot write PDQ driver declaration file",
2074 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
2075 print PDQFILE join('', @pdqfile);
2082 ## Set the $postpipe
2084 # $postpipe when running as a PPR RIP
2085 if ($spooler eq 'ppr') {
2086 # The PPR RIP sends the data output to /dev/fd/3 instead of to STDOUT
2087 if (-w "/dev/fd/3") {
2088 $postpipe = "| cat - > /dev/fd/3";
2090 $postpipe = "| cat - >&3";
2094 # Set up PPR backend (if we run as a PPR interface).
2095 if ($spooler eq 'ppr_int') {
2097 # Is the chosen backend installed and executable
2098 if (!-x "interfaces/$backend") {
2100 print $logh "The backend interface $pwd/interfaces/$backend " .
2101 "does not exist/is not executable!\n";
2102 rip_die ("The backend interface $pwd/interfaces/$backend " .
2103 "does not exist/is not executable!",
2104 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
2107 # foomatic-rip cannot use foomatic-rip as backend
2108 if ($backend eq "foomatic-rip") {
2109 print $logh "\"foomatic-rip\" cannot use itself as backend " .
2111 ppr_die ($ppr_printer,
2112 "\"foomatic-rip\" cannot use itself as backend interface!",
2113 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
2116 # Put the backend interface into the $postpipe
2117 $postpipe = "| ( interfaces/$backend \"$ppr_printer\" ".
2118 "\"$ppr_address\" \"" . join(" ",@backendoptions) .
2119 "\" \"$ppr_jobbreak\" \"$ppr_feedback\" " .
2120 "\"$ppr_codes\" \"$ppr_jobname\" \"$ppr_routing\" " .
2121 "\"$ppr_for\" \"\" )";
2125 # CUPS and PDQ have their own backends, they do not need a $postpipe
2126 if (($spooler eq 'cups') || ($spooler eq 'pdq')) {
2127 # No $postpipe for CUPS or PDQ, even if one is defined in the PPD file
2131 # CPS needs always a $postpipe, set the default one for local printing
2133 if (($spooler eq 'cps') && !$postpipe) {
2134 $postpipe = "| cat - > \$LPDDEV";
2138 print $logh "${added_lf}Output will be redirected to:\n$postpipe${added_lf}\n";
2143 ## Print documentation page when asked for
2144 my ($docgeneratorhandle, $docgeneratorpid,$retval);
2146 # Don't print the supplied files, STDIN will be redirected to the
2147 # documentation page generator
2148 @filelist = ("<STDIN>");
2149 # Start the documentation page generator
2150 ($docgeneratorhandle, $docgeneratorpid) =
2151 getdocgeneratorhandle($dat);
2152 if ($retval != $EXIT_PRINTED) {
2153 rip_die ("Error opening documentation page generator",
2156 # Read the further data from the documentation page generator and
2158 if (!close STDIN && $! != $ESPIPE) {
2159 rip_die ("Couldn't close STDIN",
2160 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
2162 if (!open (STDIN, "<&$docgeneratorhandle")) {
2163 rip_die ("Couldn't dup \$docgeneratorhandle",
2164 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
2167 while( <$docgeneratorhandle> ){
2177 ## In debug mode save the data supposed to be fed into the
2178 ## renderer also into a file, reset the file here
2181 modern_system("> ${logfile}.ps");
2186 ## From here on we have to repeat all the rest of the program for
2187 ## every file to print
2189 for $file (@filelist) {
2192 "${added_lf}================================================\n${added_lf}".
2193 "File: $file\n${added_lf}" .
2194 "================================================\n${added_lf}";
2198 ## If we do not print standard input, open the file to print
2199 if ($file ne "<STDIN>") {
2201 print $logh "File $file missing or not readable, skipping.\n";
2205 open STDIN, "< $file" || do {
2206 print $logh "Cannot open $file, skipping.\n";
2213 ## Do we have a raw queue
2214 if ($dontparse == 2) {
2215 # Raw queue, simply pass the input into the $postpipe (or to STDOUT
2216 # when there is no $postpipe)
2217 print $logh "Raw printing, executing \"cat $postpipe\"${added_lf}\n";
2218 modern_system("cat $postpipe");
2224 ## First, for arguments with a default, stick the default in as
2225 ## the initial value for the "header" option set, this option set
2226 ## consists of the PPD defaults, the options specified on the
2227 ## command line, and the options set in the header part of the
2228 ## PostScript file (all before the first page begins).
2230 copyoptions($dat, 'userval', 'header');
2234 ## Next, examine the PostScript job for traces of command-line and
2235 ## JCL options. PPD-aware applications and spoolers stuff option
2236 ## settings directly into the file, they do not necessarily send
2237 ## PPD options by the command line. Also stuff in PostScript code
2238 ## to apply option settings given by the command line and to set
2239 ## the defaults given in the PPD file.
2241 # Examination strategy: read lines from STDIN until the first
2242 # %%Page: comment appears and save them as @psheader. This is the
2243 # page-independent header part of the PostScript file. The
2244 # PostScript interpreter (renderer) must execute this part once
2245 # before rendering any assortment of pages. Then pages can be
2246 # printed in any arbitrary selection or order. All option
2247 # settings we find here will be collected in the default option
2248 # set for the RIP command line.
2250 # Now the pages will be read and sent to the renderer, one after
2251 # the other. Every page is read into memory until the
2252 # %%EndPageSetup comment appears (or a certain amount of lines was
2253 # read). So we can get option settings only valid for this
2254 # page. If we have such settings we set them in the modified
2255 # command set for this page.
2257 # If the renderer is not running yet (first page) we start it with
2258 # the command line built from the current modified command set and
2259 # send the first page to it, in the end we leave the renderer
2260 # running and keep input and output pipes open, so that it can
2261 # accept further pages. If the renderer is still running from
2262 # the previous page and the current modified command set is the
2263 # same as the one for the previous page, we send the page. If
2264 # the command set is different, we close the renderer, re-start
2265 # it with the command line built from the new modified command
2266 # set, send the header again, and then the page.
2268 # After the last page the trailer (%%Trailer) is sent.
2270 # The output pipe of this program stays open all the time so that
2271 # the spooler does not assume that the job has finished when the
2272 # renderer is re-started.
2274 # Non DSC-conforming documents will be read until a certain line
2275 # number is reached. Command line or JCL options inserted later
2278 # If options are implemented by PostScript code supposed to be
2279 # stuffed into the job's PostScript data we stuff the code for all
2280 # these options into our job data, So all default settings made in
2281 # the PPD file (the user can have edited the PPD file to change
2282 # them) are taken care of and command line options get also
2283 # applied. To give priority to settings made by applications we
2284 # insert the options's code in the beginnings of their respective
2285 # sections, so that sommething, which is already inserted, gets
2286 # executed after our code. Missing sections are automatically
2287 # created. In non-DSC-conforming files we insert the option code
2288 # in the beginning of the file. This is the same policy as used by
2289 # the "pstops" filter of CUPS.
2291 # If CUPS is the spooler, the option settings were already
2292 # inserted by the "pstops" filter, so we don't insert them
2293 # again. The only thing we do is correcting settings of numerical
2294 # options when they were set to a value not available as choice in
2295 # the PPD file, As "pstops" does not support "real" numerical
2296 # options, it sees these settings as an invalid choice and stays
2297 # with the default setting. In this case we correct the setting in
2298 # the first occurence of the option's code, as this one is the one
2299 # added by CUPS, later occurences come from applications and
2300 # should not be touched.
2302 # If the input is not PostScript (if there is no "%!" after
2303 # $maxlinestopsstart lines) a file conversion filter will
2304 # automatically be applied to the incoming data, so that we will
2305 # process the resulting PostScript here. This way we have always
2306 # PostScript data here and so we can apply the printer/driver
2307 # features described in the PPD file.
2309 # Supported file conversion filters are "a2ps", "enscript",
2310 # "mpage", and spooler-specific filters. All filters convert
2311 # plain text to PostScript, "a2ps" also other formats. The
2312 # conversion filter is always used when one prints the
2313 # documentation pages, as they are created as plain text,
2314 # when CUPS is the spooler "pstops" is executed after the
2315 # filter so that the default option settings from the PPD file
2316 # and CUPS-specific options as N-up get applied. On regular
2317 # printouts one gets always PostScript when CUPS or PPR is
2318 # the spooler, so the filter is only used for regular
2319 # printouts under LPD, LPRng, GNUlpr or without spooler.
2321 my $maxlines = 1000; # Maximum number of lines to be read
2322 # when the documenent is not
2323 # DSC-conforming. "$maxlines = 0"
2324 # means that all will be read
2325 # and examined. If it is
2326 # discovered that the input file
2327 # is DSC-conforming, this will
2330 my $maxlinestopsstart = 200; # That many lines are allowed until the
2331 # "%!" indicating PS comes. These
2332 # additional lines in the
2333 # beginning are usually JCL
2334 # commands. The lines will be
2335 # ignored by our parsing but
2338 my $maxlinesforpageoptions=200; # Unfortunately, CUPS does not bracket
2339 # "PageSetup" option with
2340 # "%%BeginPageSetup" and
2341 # "%%EndPageSetup", so the options
2342 # can simply stand after the
2343 # page header and before the
2344 # page code, without special
2345 # marking. So buffer this amount
2346 # of lines before printing the
2347 # page to check for options.
2349 my $maxnondsclinesinheader=1000; # If there is a block of more lines
2350 # than this in the document
2351 # header which is not in the
2352 # "%%BeginProlog...%%EndProlog"
2354 # "%%BeginSetup...%%EndSetup"
2355 # sections, the document is not
2356 # considered as DSC-conforming
2357 # and the rest gets passed
2358 # through to the renderer without
2359 # further parsing for options.
2361 my $nondsclines = 0; # Amount of lines found which are not in
2363 # $maxnondsclinesinheader).
2365 my $nonpslines = 0; # lines before "%!" found yet.
2367 my $more_stuff = 1; # there is more stuff in stdin.
2369 my $linect = 0; # how many lines have we examined?
2371 my $onelinebefore = ""; # The line before the current line
2372 # (Non-DSC comments are ignored)
2374 my $twolinesbefore = ""; # The line two lines before the current
2375 # line (Non-DSC comments are ignored)
2377 my $linesafterlastbeginfeature = ""; # All code lines after the last
2380 my @psheader = (); # The header of the PostScript file,
2381 # to be sent after each start of the
2384 my @psfifo = (); # The input FIFO, data which we have
2385 # pulled from stdin for examination,
2386 # but not sent to the renderer yet.
2388 my $passthru = 0; # 0: write data into @psfifo; 1: pass
2389 # data directly to the renderer
2391 my $isdscjob = 0; # Is the job DSC conforming
2393 my $inheader = 1; # Are we still in the header, before
2394 # first "%%Page:" comment?
2396 my $optionset = 'header'; # Where do the option settings, which
2397 # we have found, go?
2399 my $optionsalsointoheader = 0; # 1: We are in a "%%BeginSetup...
2400 # %%EndSetup" section after the first
2401 # "%%Page:..." line (OpenOffice.org
2402 # does this and intends the options here
2403 # apply to the whole document and not
2404 # only to the current page). We have to
2405 # add all lines also to the end of the
2406 # @psheader now and we have to set
2407 # non-PostScript options also in the
2408 # "header" optionset. 0: otherwise.
2410 my $nestinglevel = 0; # Are we in the main document (0) or
2411 # in an embedded document bracketed by
2412 # "%%BeginDocument" and "%%EndDocument"
2413 # (>0) We do not parse the PostScript
2414 # in an embedded document.
2416 my $inpageheader = 0; # Are we in the header of a page,
2417 # between "%%BeginPageSetup" and
2418 # "%%EndPageSetup" (1) or not (0).
2420 my $lastpassthru = 0; # State of $passthru in previous line
2421 # (to allow debug output when $passthru
2424 my $ignorepageheader = 0; # Will be set to 1 as soon as active
2425 # code (not between "%%BeginPageSetup"
2426 # and "%%EndPageSetup") appears after a
2427 # "%%Page:" comment. In this case
2428 # "%%BeginPageSetup" and
2429 # "%%EndPageSetup" is not allowed any
2430 # more on this page and will be ignored.
2431 # Will be set to 0 when a new "%%Page:"
2434 my $printprevpage = 0; # We set this when encountering
2435 # "%%Page:" and the previous page is not
2436 # printed yet. Then it will be printed and
2437 # the new page will be prepared in the
2438 # next run of the loop (we don't read a
2439 # new line and don't increase the
2442 $fileconverterhandle = undef; # File handle to the fileconverter process
2444 $fileconverterpid = 0; # PID of the fileconverter process
2446 $rendererhandle = undef; # File handle to the renderer process
2448 $rendererpid = 0; # PID of the renderer process
2450 my $prologfound = 0; # Did we find the
2451 # "%%BeginProlog...%%EndProlog" section?
2453 my $setupfound = 0; # Did we find the
2454 # "%%BeginSetup...%%EndSetup" section?
2456 my $pagesetupfound = 0; # special page setup handling needed
2458 my $inprolog = 0; # We are between "%%BeginProlog" and
2461 my $insetup = 0; # We are between "%%BeginSetup" and
2464 my $infeature = 0; # We are between "%%BeginFeature" and
2467 my $postscriptsection = 'jclsetup'; # In which section of the PostScript
2468 # file are we currently?
2470 $nondsclines = 0; # Number of subsequent lines found which
2471 # are at a non-DSC-conforming place,
2472 # between the sections of the header.
2474 my $optionreplaced = 0; # Will be set to 1 when we are in an
2475 # option ("%%BeginFeature...
2476 # %%EndFeature") which we have replaced.
2478 $jobhasjcl = 0; # When the job does not start with
2479 # PostScript directly, but is a
2480 # PostScript job, we set this to 1
2481 # to avoid adding the JCL options
2482 # for the second time.
2484 my $insertoptions = 1; # If we find out that a file with
2485 # a DSC magic string
2486 # ("%!PS-Adobe-") is not really
2487 # DSC-conforming, we insert the
2488 # options directly after the line
2489 # with the magic string. We use
2490 # this variable to store the
2491 # number of the line with the
2494 my $currentpage = 0; # The page which we are currently
2497 my $ooo110 = 0; # Flag to work around an application
2500 my $saved = 0; # DSC line not processed yet
2503 # We do not parse the PostScript to find Foomatic options, we check
2504 # only whether we have PostScript.
2508 print $logh "Reading PostScript input ...\n";
2510 my $line; # Line to be read from stdin
2512 my $ignoreline = 0; # Comment line to be ignored when
2513 # determining the last active line
2514 # and the one before the last
2516 if (($printprevpage) || ($saved) || ($line=<STDIN>)) {
2519 if ($linect == $nonpslines) {
2520 # In the beginning should be the postscript leader,
2521 # sometimes after some JCL commands
2522 if ($line !~ m/^.?%!/) { # There can be a Windows control
2523 # character before "%!"
2525 if ($maxlines == $nonpslines) {
2529 if ($nonpslines > $maxlinestopsstart) {
2530 # This is not a PostScript job, we must convert it
2531 print $logh "${added_lf}Job does not start with \"%!\", " .
2532 "is it PostScript?\n" .
2533 "Starting file converter\n";
2534 # Reset all variables but conserve the data which
2535 # we have already read.
2538 $nonpslines = 1; # Take into account that the line
2539 # of this run of the loop will be
2540 # put into @psheader, so the
2541 # first line read by the file
2542 # converter is already the second
2545 $onelinebefore = "";
2546 $twolinesbefore = "";
2547 my $alreadyread = join('', @psheader, @psfifo) .
2552 # Start the file conversion filter
2553 if (!$fileconverterpid) {
2554 ($fileconverterhandle, $fileconverterpid) =
2555 getfileconverterhandle
2556 ($dat, $alreadyread);
2557 if ($retval != $EXIT_PRINTED) {
2558 rip_die ("Error opening file converter",
2562 rip_die("File conversion filter probably " .
2566 # Read the further data from the file converter and
2568 if (!close STDIN && $! != $ESPIPE) {
2569 rip_die ("Couldn't close STDIN",
2570 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
2572 if (!open (STDIN, "<&$fileconverterhandle")) {
2573 rip_die ("Couldn't dup \$fileconverterhandle",
2574 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
2578 # Do we have a DSC-conforming document?
2579 if ($line =~ m/^.?%!PS-Adobe-/) {
2580 # Do not stop parsing the document
2584 $insertoptions = $linect + 1;
2585 # We have written into @psfifo before,
2586 # now we continue in @psheader and move
2587 # over the data which is already in @psfifo
2588 push (@psheader, @psfifo);
2592 "--> This document is DSC-conforming!\n";
2594 # Job is not DSC-conforming, stick in all PostScript
2595 # option settings in the beginning
2596 $line .= makeprologsection($dat, $optionset, 1);
2597 $line .= makesetupsection($dat, $optionset, 1);
2598 $line .= makepagesetupsection($dat, $optionset, 1);
2601 $pagesetupfound = 1;
2605 if ($line =~ /^\%/) {
2606 if ($line =~ m/^\s*\%\%BeginDocument[: ]/) {
2607 # Beginning of an embedded document
2608 # Note that Adobe Acrobat has a bug and so uses
2609 # "%%BeginDocument " instead of "%%BeginDocument:"
2611 print $logh "Embedded document, " .
2612 "nesting level now: $nestinglevel\n";
2613 } elsif (($line =~ m/^\s*\%\%EndDocument/) &&
2614 ($nestinglevel > 0)) {
2615 # End of an embedded document
2617 print $logh "End of Embedded document, " .
2618 "nesting level now: $nestinglevel\n";
2619 } elsif (($line =~ m/^\s*\%\%Creator[: ](.*)$/) &&
2620 ($nestinglevel == 0)) {
2621 # Here we set flags to treat particular bugs of the
2622 # PostScript produced by certain applications
2624 if ($creator =~ /^\s*OpenOffice.org\s+1.1.\d+\s*$/) {
2625 # OpenOffice.org 1.1.x
2626 # The option settings supposed to affect the
2627 # whole document are put into the "%%PageSetup"
2628 # section of the first page
2629 print $logh "Document created with " .
2630 "OpenOffice.org 1.1.x\n";
2633 } elsif (($line =~ m/^\%\%BeginProlog/) &&
2634 ($nestinglevel == 0)) {
2635 # Note: Below is another place where a "Prolog"
2636 # section start will be considered. There we assume
2637 # start of the "Prolog" if the job is DSC-Conformimg,
2638 # but an arbitrary comment starting with "%%Begin", but
2639 # not a comment explicitly treated here, is found. This
2640 # is done because many "dvips" (TeX/LaTeX) files miss
2641 # the "%%BeginProlog" comment.
2642 # Beginning of Prolog
2643 print $logh "${added_lf}-----------\nFound: \%\%BeginProlog\n";
2645 $postscriptsection = 'prolog' if $inheader;
2647 # Insert options for "Prolog"
2648 if (!$prologfound) {
2649 $line .= makeprologsection($dat, $optionset, 0);
2652 } elsif (($line =~ m/^\%\%EndProlog/) &&
2653 ($nestinglevel == 0)) {
2655 print $logh "Found: \%\%EndProlog\n";
2657 $insertoptions = $linect + 1;
2658 } elsif (($line =~ m/^\%\%BeginSetup/) &&
2659 ($nestinglevel == 0)) {
2660 # Beginning of Setup
2661 print $logh "${added_lf}-----------\nFound: \%\%BeginSetup\n";
2663 # We need to distinguish with the $inheader variable
2664 # here whether we are in the header or on a page, as
2665 # OpenOffice.org inserts a "%%BeginSetup...%%EndSetup"
2666 # section after the first "%%Page:..." line and assumes
2667 # this section to be valid for all pages.
2668 $postscriptsection = 'setup' if $inheader;
2671 # If there was no "Prolog" but there are
2672 # options for the "Prolog", push a "Prolog"
2673 # with these options onto the @psfifo here
2674 if (!$prologfound) {
2675 # "Prolog" missing, insert it here
2677 makeprologsection($dat, $optionset, 1) .
2679 # Now we have a "Prolog"
2682 # Insert options for "DocumentSetup" or "AnySetup"
2683 if ($spooler ne 'cups') {
2684 # For non-CUPS spoolers or no spooler at all,
2685 # we leave everything as it is.
2688 makesetupsection($dat, $optionset, 0);
2693 # Found option settings must be stuffed into both
2694 # the header and the currrent page now. They will
2695 # be written into both the "header" and the
2696 # "currentpage" optionsets and the PostScript code
2697 # lines of this section will not only go into the
2698 # output stream, but also added to the end of the
2699 # @psheader, so that they get repeated (to preserve
2700 # the embedded PostScript option settings) on a
2701 # restart of the renderer due to command line
2703 $optionsalsointoheader = 1;
2704 print $logh "\"%%BeginSetup\" in page header\n";
2706 } elsif (($line =~ m/^\%\%EndSetup/) &&
2707 ($nestinglevel == 0)) {
2709 print $logh "Found: \%\%EndSetup\n";
2712 if ($spooler eq 'cups') {
2713 # In case of CUPS, we must insert the
2714 # accounting stuff just before the
2715 # %%EndSetup comment in order to leave any
2716 # EndPage procedures that have been
2717 # defined by either the pstops filter or
2718 # the PostScript job itself fully
2721 $line = makesetupsection($dat,
2727 $insertoptions = $linect + 1;
2729 # The "%%BeginSetup...%%EndSetup" which
2730 # OpenOffice.org has inserted after the first
2731 # "%%Page:..." line ends here, so the following
2732 # options go only onto the current page again
2733 $optionsalsointoheader = 0;
2735 } elsif (($line =~ m/^\%\%Page:(.*)$/) &&
2736 ($nestinglevel == 0)) {
2737 if ((!$lastpassthru) && (!$inheader)) {
2738 # In the last line we were not in passthru mode,
2739 # so the last page is not printed. Prepare to do
2742 # Print the previous page
2744 print $logh "New page found but previous not " .
2745 "printed, print it now.\n";
2747 # The previous page is printed, so we can prepare
2750 print $logh "${added_lf}-----------\nNew page: $1\n";
2753 # We consider the beginning of the page already as
2754 # page setup section, as some apps do not use
2755 # "%%PageSetup" tags.
2756 $postscriptsection = 'pagesetup';
2757 # Save PostScript state before beginning the page
2758 #$line .= "/foomatic-saved-state save def\n";
2759 # Here begins a new page
2761 # One last update for the header
2762 buildcommandline($dat, $optionset);
2763 # Here we add some stuff which still belongs
2766 # If there was no "Setup" but there are
2767 # options for the "Setup", push a "Setup"
2768 # with these options onto the @psfifo here
2770 # "Setup" missing, insert it here
2772 makesetupsection($dat, $optionset, 1) .
2774 # Now we have a "Setup"
2777 # If there was no "Prolog" but there are
2778 # options for the "Prolog", push a "Prolog"
2779 # with these options onto the @psfifo here
2780 if (!$prologfound) {
2781 # "Prolog" missing, insert it here
2783 makeprologsection($dat, $optionset,
2786 # Now we have a "Prolog"
2789 # Now we push this onto the header
2790 push (@psheader, $stillforheader);
2791 # The first page starts, so the header ends
2794 # Option setting should go into the
2795 # page-specific option set now
2796 $optionset = 'currentpage';
2798 # Restore PostScript state after completing the
2801 # foomatic-saved-state restore
2803 # /foomatic-saved-state save def
2805 # Print this directly, so that if we need to
2806 # restart the renderer for this page due to
2807 # a command line change this is done under the
2808 # old instance of the renderer
2809 #print $rendererhandle
2810 # "foomatic-saved-state restore\n";
2812 # Save the option settings of the previous page
2813 copyoptions($dat, 'currentpage',
2815 deleteoptions($dat, 'currentpage');
2817 # Initialize the option set
2818 copyoptions($dat, 'header', 'currentpage');
2819 # Set command line options which apply only
2821 setoptionsforpage($dat, 'currentpage', $currentpage);
2822 $pagesetupfound = 0;
2823 if ($spooler eq 'cups') {
2824 # Remove the "notfirst" flag from all options
2825 # forseen for the "PageSetup" section, because
2826 # when these are numerical options for CUPS.
2827 # they have to be set to the correct value
2829 for my $arg (@{$dat->{'args'}}) {
2830 if (($arg->{'section'} eq 'PageSetup') &&
2831 (defined($arg->{'notfirst'}))) {
2832 delete($arg->{'notfirst'});
2836 # Now the page header comes, so buffer the data,
2837 # because we must perhaps shut down and restart
2840 $ignorepageheader = 0;
2841 $optionsalsointoheader = 0;
2843 } elsif (($line =~ m/^\%\%BeginPageSetup/) &&
2844 ($nestinglevel == 0) &&
2845 (!$ignorepageheader)) {
2846 # Start of the page header, up to %%EndPageSetup
2847 # nothing of the page will be drawn, page-specific
2848 # option settngs (as letter-head paper for page 1)
2850 print $logh "${added_lf}Found: \%\%BeginPageSetup\n";
2853 $postscriptsection = 'pagesetup';
2854 if (($ooo110) && ($currentpage == 1)) {
2855 $optionsalsointoheader = 1;
2857 $optionsalsointoheader = 0;
2859 # Insert PostScript option settings
2860 # (options for section "PageSetup".
2863 makepagesetupsection($dat, $optionset,
2865 $pagesetupfound = 1;
2867 } elsif (($line =~ m/^\%\%EndPageSetup/) &&
2868 ($nestinglevel == 0) &&
2869 (!$ignorepageheader)) {
2870 # End of the page header, the page is ready to be
2872 print $logh "Found: \%\%EndPageSetup\n";
2873 print $logh "End of page header\n";
2874 # We cannot for sure say that the page header ends here
2875 # OpenOffice.org puts (due to a bug) a "%%BeginSetup...
2876 # %%EndSetup" section after the first "%%Page:...". It
2877 # is possible that CUPS inserts a "%%BeginPageSetup...
2878 # %%EndPageSetup" before this section, which means that
2879 # the options in the "%%BeginSetup...%%EndSetup"
2880 # section are after the "%%EndPageSetup", so we
2881 # continue for searching options up to the buffer size
2882 # limit $maxlinesforpageoptions.
2885 $optionsalsointoheader = 0;
2886 } elsif ((($line =~ m/^\%\%(BeginFeature):\s*\*?([^\*\s=]+)\s+()(\S[^\r\n]*)\r?\n?$/) ||
2887 ($line =~ m/^\s*\%\%\s*(FoomaticRIPOptionSetting):\s*([^\*\s=]+)\s*=\s*(\@?)([^\@\s][^\r\n]*)\r?\n?$/)) &&
2888 ($nestinglevel == 0) &&
2889 (!$optionreplaced) &&
2890 ((!$passthru) || (!$isdscjob))) {
2891 my ($linetype, $option, $fromcomposite, $value) =
2894 # Mark that we are in a "Feature" section
2895 if ($linetype eq 'BeginFeature') {
2897 $linesafterlastbeginfeature = "";
2900 # OK, we have an option. If it's not a
2901 # *ostscript-style option (ie, it's command-line or
2902 # JCL) then we should note that fact, since the
2903 # attribute-to-filter option passing in CUPS is kind of
2904 # funky, especially wrt boolean options.
2906 print $logh "Found: $line";
2907 if (my $arg=argbyname($option)) {
2908 print $logh " Option: $option=" .
2909 ($fromcomposite ? "From" : "") . $value;
2910 if (($spooler eq 'cups') &&
2911 ($linetype eq 'BeginFeature') &&
2912 (!defined($arg->{'notfirst'})) &&
2913 ($arg->{$optionset} ne $value) &&
2915 ($arg->{section} eq 'PageSetup'))) {
2917 # We have the first occurence of an option
2918 # setting and the spooler is CUPS, so this
2919 # setting is inserted by "pstops" or
2920 # "imagetops". The value from the command
2921 # line was not inserted by "pstops" or
2922 # "imagetops" so it seems to be not under
2923 # the choices in the PPD. Possible
2926 # - "pstops" and "imagetops" ignore settings
2927 # of numerical or string options which are
2928 # not one of the choices in the PPD file,
2929 # and inserts the default value instead.
2931 # - On the command line an option was applied
2932 # only to selected pages:
2933 # "-o <page ranges>:<option>=<values>
2934 # This is not supported by CUPS, so not
2935 # taken care of by "pstops".
2937 # We must fix this here by replacing the
2938 # setting inserted by "pstops" or "imagetops"
2939 # with the exact setting given on the command
2942 # $arg->{$optionset} is already
2943 # range-checked, so do not check again here
2944 # Insert DSC comment
2945 my $dest = ((($inheader) && ($isdscjob)) ?
2946 \@psheader : \@psfifo);
2948 if ($arg->{'style'} eq 'G') {
2949 # PostScript option, insert the code
2950 if ($arg->{'type'} eq 'bool') {
2953 "%%BeginFeature: *$option " .
2954 ($arg->{$optionset} == 1 ?
2955 "True" : "False") . "\n");
2956 if (defined($arg->{$optionset}) &&
2957 $arg->{$optionset} == 1) {
2958 push(@{$dest}, $arg->{'proto'} .
2960 } elsif ($arg->{'protof'}) {
2961 push(@{$dest}, $arg->{'protof'} .
2964 # We have replaced this option on the
2966 $optionreplaced = 1;
2967 } elsif ((($arg->{'type'} eq 'enum') ||
2968 ($arg->{'type'} eq 'string') ||
2972 $arg->{'vals_byname'}{$arg->{$optionset}}))) {
2973 # Enumerated choice of string or enum
2976 "%%BeginFeature: " .
2977 "*$option $arg->{$optionset}\n");
2978 push(@{$dest}, $val->{'driverval'} . "\n");
2979 # We have replaced this option on the
2981 $optionreplaced = 1;
2982 } elsif ((($arg->{'type'} eq 'string') ||
2985 ($arg->{$optionset} eq 'None')) {
2986 # 'None' is mapped to the empty string
2989 "%%BeginFeature: " .
2990 "*$option $arg->{$optionset}\n");
2991 my $driverval = $arg->{'proto'};
2992 $driverval =~ s/\%s//g;
2993 push(@{$dest}, $driverval . "\n");
2994 # We have replaced this option on the
2996 $optionreplaced = 1;
2997 } elsif (($arg->{'type'} eq 'int') ||
2998 ($arg->{'type'} eq 'float') ||
2999 ($arg->{'type'} eq 'string') ||
3000 ($arg->{'type'} eq 'password')) {
3001 # Setting for numerical or string
3002 # option which is not under the
3003 # enumerated choices
3005 "%%BeginFeature: " .
3006 "*$option $arg->{$optionset}\n");
3007 my $sprintfproto = $arg->{'proto'};
3008 $sprintfproto =~ s/\%(?!s)/\%\%/g;
3010 sprintf($sprintfproto,
3011 $arg->{$optionset}) .
3013 # We have replaced this option on the
3015 $optionreplaced = 1;
3018 # Command line or JCL option
3020 "%% FoomaticRIPOptionSetting: " .
3021 "$option=$arg->{$optionset}\n");
3022 # We have replaced this option on the
3024 $optionreplaced = 1;
3026 print $logh " --> Correcting numerical/string " .
3027 "option to $option=$arg->{$optionset}" .
3028 " (Command line argument)\n" if
3031 # Mark that we have already found this option
3032 $arg->{'notfirst'} = 1;
3033 if (!$optionreplaced) {
3034 if ($arg->{'style'} ne 'G') {
3035 # "Controlled by '<Composite>'" setting of
3036 # a member option of a composite option
3037 if ($fromcomposite) {
3038 $value = "From$value";
3040 # Non-PostScript option
3041 # Check whether it is valid
3042 if (defined(my $newvalue =
3043 checkoptionvalue($dat, $option,
3045 print $logh " --> Setting option\n";
3046 # Valid choice, set it.
3047 $arg->{$optionset} = $newvalue;
3048 if ($optionsalsointoheader) {
3049 $arg->{'header'} = $newvalue;
3051 if (($arg->{'type'} eq 'enum') &&
3052 (($option eq 'PageSize') ||
3053 ($option eq 'PageRegion')) &&
3054 ($newvalue =~ /^Custom/) &&
3056 'FoomaticRIPOptionSetting')) {
3058 $linesafterlastbeginfeature =~
3059 /^[\s\r\n]*([\d\.]+)[\s\r\n]+([\d\.]+)[\s\r\n]+/s;
3060 my ($w, $h) = ($1, $2);
3062 ($w != 0) && ($h != 0)) {
3064 "$newvalue.${w}x$h";
3065 $arg->{$optionset} = $newvalue;
3066 if ($optionsalsointoheader) {
3072 # For a composite option insert the
3073 # code from the member options with
3074 # current setting "From<composite>"
3075 # The code from the member options
3076 # is chosen according to the setting
3077 # of the composite option.
3078 if (($arg->{'style'} eq 'X') &&
3080 'FoomaticRIPOptionSetting')) {
3081 buildcommandline($dat, $optionset);
3083 $arg->{$postscriptsection};
3085 # If this argument is PageSize or
3086 # PageRegion, also set the other
3087 syncpagesize($dat, $option, $newvalue,
3089 if ($optionsalsointoheader) {
3090 syncpagesize($dat, $option,
3091 $newvalue, 'header');
3094 # Invalid option, log it.
3095 print $logh " --> Invalid option " .
3096 "setting found in job\n";
3098 } elsif ($fromcomposite) {
3099 # PostScript option, but we have to look up
3100 # the PostScript code to be inserted from
3101 # the setting of a composite option, as
3102 # this option is set to "Controlled by
3105 if (defined(my $newvalue =
3108 "From$value", 0))) {
3109 print $logh " --> Looking up setting " .
3110 "in composite option '$value'\n";
3111 # Valid choice, set it.
3112 $arg->{$optionset} = $newvalue;
3113 if ($optionsalsointoheader) {
3114 $arg->{'header'} = $newvalue;
3116 # Update composite options
3117 buildcommandline($dat, $optionset);
3118 # Substitute PostScript comment by
3120 $line = $arg->{'compositesubst'};
3122 # Invalid option, log it.
3123 print $logh " --> Invalid option " .
3124 "setting found in job\n";
3127 # it is a PostScript style option with
3128 # the code readily inserted, no option
3129 # for the renderer command line/JCL to set,
3130 # no lookup of a composite option needed,
3131 # so nothing to do here...
3133 " --> Option will be set by " .
3134 "PostScript interpreter\n";
3138 # This option is unknown to us. WTF?
3139 print $logh "Unknown option $option=$value found " .
3142 } elsif (($line =~ m/^\%\%EndFeature/) &&
3143 ($nestinglevel == 0)) {
3146 # If the option setting was replaced, it ends here,
3147 # too, and the next option is not necessarily also
3149 $optionreplaced = 0;
3150 $linesafterlastbeginfeature = "";
3151 } elsif (($line =~ m/^\%\%Begin/) &&
3154 ($nestinglevel == 0)) {
3155 # In some PostScript files (especially when generated
3156 # by "dvips" of TeX/LaTeX) the "%%BeginProlog" is
3157 # missing, so assume that it was before the current
3158 # line (the first line starting with "%%Begin".
3159 print $logh "Job claims to be DSC-conforming, but " .
3160 "\"%%BeginProlog\" was missing before first " .
3161 "line with another \"%%Begin...\" comment " .
3162 "(is this a TeX/LaTeX/dvips-generated PostScript " .
3163 "file?). Assuming start of \"Prolog\" here.\n";
3164 # Beginning of Prolog
3167 # Insert options for "Prolog" before the current line
3168 if (!$prologfound) {
3171 makeprologsection($dat, $optionset, 0) .
3175 } elsif (($line =~ m/^\s*\%(\%?)RBINumCopies:\s*(\d+)\s*$/) &&
3176 ($nestinglevel == 0)) {
3177 # RBINumCopies entry
3179 print $logh "Found: %${1}RBINumCopies: $rbinumcopies\n";
3180 } elsif (($line =~ m/^\s*\%/) || ($line =~ m/^\s*$/)) {
3181 # This is an unknown PostScript comment or a blank
3182 # line, no active code
3186 # This line is active PostScript code
3188 # Collect coe in a "%%BeginFeature: ... %%EndFeature"
3189 # section, to get the values for a custom option
3191 $linesafterlastbeginfeature .= $line;
3194 if ((!$inprolog) && (!$insetup)) {
3195 # Outside the "Prolog" and "Setup" section
3196 # a correct DSC-conforming document has no
3197 # active PostScript code, so consider the
3198 # file as non-DSC-conforming when there are
3199 # too many of such lines.
3201 if ($nondsclines > $maxnondsclinesinheader) {
3202 # Consider document as not DSC-conforming
3203 print $logh "This job seems not to be " .
3204 "DSC-conforming, DSC-comment for " .
3205 "next section not found, stopping " .
3206 "to parse the rest, passing it " .
3207 "directly to the renderer.\n";
3208 # Stop scanning for further option settings
3211 # Insert defaults and command line settings
3212 # in the beginning of the job or after the
3213 # last valid section
3214 splice(@psheader, $insertoptions, 0,
3215 ($prologfound ? () :
3216 makeprologsection($dat, $optionset,
3219 makesetupsection($dat, $optionset,
3221 ($pagesetupfound ? () :
3222 makepagesetupsection($dat,
3227 $pagesetupfound = 1;
3231 if (!$inpageheader) {
3232 # PostScript code inside a page, but not between
3233 # "%%BeginPageSetup" and "%%EndPageSetup", so
3234 # we are perhaps already drawing onto a page now
3235 if ($onelinebefore =~ m/^\%\%Page:/) {
3236 print $logh "No page header or page " .
3237 "header not DSC-conforming\n";
3239 # Stop buffering lines to search for options
3240 # placed not DSC-conforming
3241 if (scalar(@psfifo) >=
3242 $maxlinesforpageoptions) {
3243 print $logh "Stopping search for " .
3244 "page header options\n";
3246 # If there comes a page header now, ignore
3248 $ignorepageheader = 1;
3249 $optionsalsointoheader = 0;
3251 # Insert PostScript option settings
3252 # (options for section "PageSetup".
3253 if ($isdscjob && !$pagesetupfound) {
3255 makepagesetupsection($dat, $optionset,
3257 $pagesetupfound = 1;
3265 if ($lastpassthru != $passthru) {
3267 print $logh "Found: $line" .
3268 " --> Output goes directly to the renderer now.\n${added_lf}";
3270 print $logh "Found: $line" .
3271 " --> Output goes to the FIFO buffer now.${added_lf}\n";
3275 # We are in an option which was replaced, do not output
3277 if ($optionreplaced) {
3281 # If we are in a "%%BeginSetup...%%EndSetup" section after
3282 # the first "%%Page:..." and the current line belongs to
3283 # an option setting, we have to copy the line also to the
3285 if (($optionsalsointoheader) &&
3286 (($infeature) || ($line =~ m/^\%\%EndFeature/))) {
3287 push (@psheader, $line);
3290 # Store or send the current line
3291 if (($inheader) && ($isdscjob)) {
3292 # We are still in the PostScript header, collect all lines
3294 push (@psheader, $line);
3296 if (($passthru) && ($isdscjob)) {
3297 if (!$lastpassthru) {
3298 # We enter passthru mode with this line, so the
3299 # command line can have changed, check it and
3300 # close the renderer if needed
3301 if (($rendererpid) &&
3302 (!optionsequal($dat, 'currentpage',
3303 'previouspage', 0))) {
3304 print $logh "Command line/JCL options " .
3305 "changed, restarting renderer\n";
3306 $retval = closerendererhandle
3307 ($rendererhandle, $rendererpid);
3308 if ($retval != $EXIT_PRINTED) {
3309 rip_die ("Error closing renderer",
3315 # Flush @psfifo and send line directly to the renderer
3316 if (!$rendererpid) {
3317 # No renderer running, start it
3318 ($rendererhandle, $rendererpid) =
3320 ($dat, join('', @psheader, @psfifo));
3321 if ($retval != $EXIT_PRINTED) {
3322 rip_die ("Error opening renderer",
3325 # @psfifo is sent out, flush it.
3328 if ($#psfifo >= 0) {
3329 # Send @psfifo to renderer
3330 print $rendererhandle join('', @psfifo);
3334 # Send line to renderer
3335 if (!$printprevpage) {
3336 print $rendererhandle $line;
3338 while ($line=<STDIN>)
3340 if ($line =~ /^\%\%[A-Za-z\s]{3,}/) {
3341 print $logh "Found: $line" .
3342 " --> Continue DSC parsing now.${added_lf}\n";
3346 print $rendererhandle $line;
3352 # Push the line onto the stack for later spitting up...
3353 push (@psfifo, $line);
3357 if (!$printprevpage) {
3364 # No PostScript header in the whole file? Then it's not
3365 # PostScript, convert it.
3366 # We open the file converter here when the file has less
3367 # lines than the amount which we search for the PostScript
3368 # header ($maxlinestopsstart).
3369 if ($linect <= $nonpslines) {
3370 # This is not a PostScript job, we must convert it
3371 print $logh "${added_lf}Job does not start with \"%!\", " .
3372 "is it PostScript?\n" .
3373 "Starting file converter\n";
3374 # Reset all variables but conserve the data which
3375 # we have already read.
3380 $onelinebefore = "";
3381 $twolinesbefore = "";
3382 my $alreadyread = join('', @psheader, @psfifo);
3386 # Start the file conversion filter
3387 if (!$fileconverterpid) {
3388 ($fileconverterhandle, $fileconverterpid) =
3389 getfileconverterhandle($dat, $alreadyread);
3390 if ( defined($retval) and $retval != $EXIT_PRINTED) {
3391 rip_die ("Error opening file converter",
3395 rip_die("File conversion filter probably " .
3399 # Read the further data from the file converter and
3401 if (!close STDIN && $! != $ESPIPE) {
3402 rip_die ("Couldn't close STDIN",
3403 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3405 if (!open (STDIN, "<&$fileconverterhandle")) {
3406 rip_die ("Couldn't dup \$fileconverterhandle",
3407 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3409 # Now we have new (converted) stuff in STDIN, so
3410 # continue in the loop
3415 $lastpassthru = $passthru;
3417 if ((!$ignoreline) && (!$printprevpage)) {
3418 $twolinesbefore = $onelinebefore;
3419 $onelinebefore = $line;
3422 } while ((($maxlines == 0) or ($linect < $maxlines)) and
3423 ($more_stuff != 0));
3425 # Some buffer still containing data? Send it out to the renderer.
3426 if (($more_stuff != 0) || ($inheader) || ($#psfifo >= 0)) {
3427 # Flush @psfifo and send the remaining data to the renderer, this
3428 # only happens with non-DSC-conforming jobs or non-Foomatic PPDs
3430 print $logh "Stopped parsing the PostScript data, ".
3431 "sending rest directly to renderer.\n";
3433 print $logh "Flushing FIFO.\n";
3436 # One last update for the header
3437 buildcommandline($dat, $optionset);
3438 # No page initialized yet? Copy the "header" option set into the
3439 # "currentpage" option set, so that the renderer will find the
3441 copyoptions($dat, 'header', 'currentpage');
3442 $optionset = 'currentpage';
3443 # If not done yet, insert defaults and command line settings
3444 # in the beginning of the job or after the last valid section
3445 splice(@psheader, $insertoptions, 0,
3446 ($prologfound ? () :
3447 makeprologsection($dat, $optionset, 1)),
3449 makesetupsection($dat, $optionset, 1)),
3450 ($pagesetupfound ? () :
3451 makepagesetupsection($dat, $optionset, 1)));
3454 $pagesetupfound = 1;
3456 if (($rendererpid) &&
3457 (!optionsequal($dat, 'currentpage',
3458 'previouspage', 0))) {
3459 print $logh "Command line/JCL options " .
3460 "changed, restarting renderer\n";
3461 $retval = closerendererhandle
3462 ($rendererhandle, $rendererpid);
3463 if ($retval != $EXIT_PRINTED) {
3464 rip_die ("Error closing renderer",
3469 if (!$rendererpid) {
3470 ($rendererhandle, $rendererpid) =
3471 getrendererhandle($dat, join('', @psheader, @psfifo));
3472 if ($retval != $EXIT_PRINTED) {
3473 rip_die ("Error opening renderer",
3476 # We have sent @psfifo now
3479 if ($#psfifo >= 0) {
3480 # Send @psfifo to renderer
3481 print $rendererhandle join('', @psfifo);
3485 # Print the rest of the input data
3488 print $rendererhandle $_;
3493 # At every "%%Page:..." comment we have saved the PostScript state
3494 # and we have increased the page number. So if the page number is
3495 # non-zero we had at least one "%%Page:..." comment and so we have
3496 # to give a restore the PostScript state.
3497 #if ($currentpage > 0) {
3498 # print $rendererhandle "foomatic-saved-state restore\n";
3501 # Close the renderer
3503 $retval = closerendererhandle ($rendererhandle, $rendererpid);
3504 if ($retval != $EXIT_PRINTED) {
3505 rip_die ("Error closing renderer",
3511 # Close the file converter (if it was used)
3512 if ($fileconverterpid) {
3513 $retval = closefileconverterhandle
3514 ($fileconverterhandle, $fileconverterpid);
3515 if ($retval != $EXIT_PRINTED) {
3516 rip_die ("Error closing file converter",
3519 $fileconverterpid = 0;
3524 ## Close the documentation page generator
3525 if ($docgeneratorpid) {
3526 $retval = closedocgeneratorhandle
3527 ($docgeneratorhandle, $docgeneratorpid);
3528 if ($retval != $EXIT_PRINTED) {
3529 rip_die ("Error closing documentation page generator",
3532 $docgeneratorpid = 0;
3537 ## Close last input file
3542 ## Only for debugging
3545 local $Data::Dumper::Purity=1;
3546 local $Data::Dumper::Indent=1;
3547 print $logh Dumper($dat);
3553 print $logh "${added_lf}Closing foomatic-rip.\n";
3560 ## Functions to let foomatic-rip fork to do several tasks in parallel.
3562 # To do the filtering without loading the whole file into memory we work
3563 # on a data stream, we read the data line by line analyse it to decide what
3564 # filters to use and start the filters if we have found out which we need.
3565 # We buffer the data only as long as we didn't determing which filters to
3566 # use for this piece of data and with which options. There are no temporary
3569 # foomatic-rip splits into up to 6 parallel processes to do the whole
3570 # filtering (listed in the order of the data flow):
3572 # KID0: Generate documentation pages (only jobs with "docs" option)
3573 # KID2: Put together already read data and current input stream for
3574 # feeding into the file conversion filter (only non-PostScript
3576 # KID1: Run the file conversion filter to convert non-PostScript
3577 # input into PostScript (only non-PostScript and "docs" jobs)
3578 # MAIN: Prepare the job auto-detecting the spooler, reading the PPD,
3579 # extracting the options from the command line, and parsing
3580 # the job data itself. It analyses the job data to check
3581 # whether it is PostScript and starts KID1/KID2 if not, it
3582 # also stuffs PostScript code from option settings into the
3583 # PostScript data stream. It starts the renderer (KID3/KID4)
3584 # as soon as it knows its command line and restarts it when
3585 # page-specific option settings need another command line
3586 # or different JCL commands.
3587 # KID3: The rendering process. In most cases GhostScript, "cat"
3588 # for native PostScript printers with their manufacturer's
3590 # KID4: Put together the JCL commands and the renderer's output
3591 # and send all that either to STDOUT or pipe it into the
3592 # command line defined with $postpipe.
3594 ## This function runs the renderer command line (and if defined also
3595 ## the postpipe) and returns a file handle for stuffing in the
3597 sub getrendererhandle {
3599 my ($dat, $prepend) = @_;
3601 print $logh "${added_lf}Starting renderer\n";
3603 # Reset return value of the renderer
3604 $retval = $EXIT_PRINTED;
3606 # Set up a pipe for the kids to pass their exit stat to the main process
3607 pipe KID_MESSAGE, KID_MESSAGE_IN;
3609 # When one kid fails put the exit stat here
3612 # When a kid exits successfully, mark it here
3616 # Build the command line and get the JCL commands
3617 buildcommandline($dat, 'currentpage');
3618 my $commandline = $dat->{'currentcmd'};
3619 my @jclprepend = @{$dat->{'jclprepend'}} if defined $dat->{'jclprepend'};
3620 my @jclappend = @{$dat->{'jclappend'}} if defined $dat->{'jclappend'};
3626 if (!defined($kid3)) {
3629 print $logh "$0: cannot fork for kid3!\n";
3630 rip_die ("can't fork for kid3",
3631 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3635 # we are the parent; return a glob to the filehandle
3638 # Feed in the PostScript header and the FIFO contents
3639 print KID3 $prepend;
3642 return ( *KID3, $kid3 );
3645 $kidgeneration += 1;
3649 $SIG{PIPE} = 'DEFAULT';
3653 if (!defined($kid4)) {
3656 print $logh "$0: cannot fork for kid4!\n";
3658 print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3659 close KID_MESSAGE_IN;
3660 rip_die ("can't fork for kid4",
3661 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3665 # parent, child of primary task; we are |commandline|
3668 print $logh "renderer PID kid4=$kid4\n";
3669 print $logh "renderer command: $commandline\n";
3671 if (!close STDIN && $! != $ESPIPE) {
3675 print KID_MESSAGE_IN
3676 "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3677 close KID_MESSAGE_IN;
3678 rip_die ("Couldn't close STDIN in $kid4",
3679 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3681 if (!open (STDIN, "<&KID3_IN")) {
3685 print KID_MESSAGE_IN
3686 "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3687 close KID_MESSAGE_IN;
3688 rip_die ("Couldn't dup KID3_IN",
3689 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3691 if (!close STDOUT) {
3695 print KID_MESSAGE_IN
3696 "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3697 close KID_MESSAGE_IN;
3698 rip_die ("Couldn't close STDOUT in $kid4",
3699 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3701 if (!open (STDOUT, ">&KID4")) {
3705 print KID_MESSAGE_IN
3706 "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3707 close KID_MESSAGE_IN;
3708 rip_die ("Couldn't dup KID4",
3709 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3712 if (!open (STDERR, ">&$logh")) {
3716 print KID_MESSAGE_IN
3717 "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3718 close KID_MESSAGE_IN;
3719 rip_die ("Couldn't dup logh to stderr",
3720 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3724 # Massage commandline to execute foomatic-gswrapper
3725 my $havewrapper = 0;
3726 for (split(':', $ENV{'PATH'})) {
3727 if (-x "$_/foomatic-gswrapper") {
3733 $commandline =~ s!^\s*gs\s!foomatic-gswrapper !g;
3734 $commandline =~ s!(\|\s*)gs\s!\|foomatic-gswrapper !g;
3735 $commandline =~ s!(;\s*)gs\s!; foomatic-gswrapper !g;
3738 # If the renderer command line contains the "echo"
3739 # command, replace the "echo" by the user-chosen $myecho
3740 # (important for non-GNU systems where GNU echo is in a
3742 $commandline =~ s!^\s*echo\s!$myecho !g;
3743 $commandline =~ s!(\|\s*)echo\s!\|$myecho !g;
3744 $commandline =~ s!(;\s*)echo\s!; $myecho !g;
3746 # In debug mode save the data supposed to be fed into the
3747 # renderer also into a file
3749 $commandline = "tee -a ${logfile}.ps | ( $commandline )";
3752 # Actually run the thing...
3753 modern_system("$commandline");
3755 my $rendererretval = $? >> 8;
3756 print $logh "renderer return value: $rendererretval\n";
3757 my $renderersignal = $? & 127;
3758 print $logh "renderer received signal: $rendererretval\n";
3764 if ($renderersignal == SIGUSR1) {
3765 $retval = $EXIT_PRNERR;
3766 } elsif ($renderersignal == SIGUSR2) {
3767 $retval = $EXIT_PRNERR_NORETRY;
3768 } elsif ($renderersignal == SIGTTIN) {
3769 $retval = $EXIT_ENGAGED;
3771 if ($retval != $EXIT_PRINTED) {
3773 print KID_MESSAGE_IN "3 $retval\n";
3774 close KID_MESSAGE_IN;
3777 # Evaluate renderer result
3778 if ($rendererretval == 0) {
3779 # Success, exit with 0 and inform main process
3781 print KID_MESSAGE_IN "3 $EXIT_PRINTED\n";
3782 close KID_MESSAGE_IN;
3784 } elsif ($rendererretval == 1) {
3785 # Syntax error? PostScript error?
3787 print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
3788 close KID_MESSAGE_IN;
3789 rip_die ("Possible error on renderer command line or PostScript error. Check options.",
3791 } elsif ($rendererretval == 139) {
3792 # Seems to indicate a core dump
3794 print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
3795 close KID_MESSAGE_IN;
3796 rip_die ("The renderer may have dumped core.",
3798 } elsif ($rendererretval == 141) {
3799 # Broken pipe, presumably additional filter interface
3802 print KID_MESSAGE_IN "3 $EXIT_PRNERR\n";
3803 close KID_MESSAGE_IN;
3804 rip_die ("A filter used in addition to the renderer" .
3805 " itself may have failed.",
3807 } elsif (($rendererretval == 243) || ($retval == 255)) {
3810 print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
3811 close KID_MESSAGE_IN;
3816 print KID_MESSAGE_IN "3 $EXIT_PRNERR\n";
3817 close KID_MESSAGE_IN;
3818 rip_die ("The renderer command line returned an" .
3819 " unrecognized error code $rendererretval.",
3827 # When arrived here the renderer command line was successful
3828 # So exit with zero exit value here and inform the main process
3830 # Wait for postpipe/output child
3833 print KID_MESSAGE_IN "3 $EXIT_SIGNAL\n";
3835 print KID_MESSAGE_IN "3 $EXIT_PRINTED\n";
3837 close KID_MESSAGE_IN;
3838 print $logh "KID3 finished with $?\n";
3841 $kidgeneration += 1;
3843 # child, trailing task on the pipe; we write jcl stuff
3847 my $fileh = *STDOUT;
3849 # Do we have a $postpipe, if yes, launch the command(s) and
3850 # point our output into it/them
3852 if (!open PIPE,$postpipe) {
3855 print KID_MESSAGE_IN
3856 "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3857 close KID_MESSAGE_IN;
3858 rip_die ("cannot execute postpipe $postpipe",
3859 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3865 print $logh "JCL: " . join("", @jclprepend) . "<job data> ${added_lf}" .
3866 join("", @jclappend) . "\n";
3868 # wrap the JCL around the job data, if there are any
3869 # options specified...
3870 # Should the driver already have inserted JCL commands we merge
3871 # our JCL header with the one from the driver
3873 if ( @jclprepend > 1 ) {
3874 # JCL header read from renderer output
3876 # Determine magic string of JCL in use (usually "@PJL")
3877 # For that we take the first part of the second JCL line up
3878 # to the first space
3879 if ($jclprepend[1] =~ /^(\S+)/) {
3881 # Read from the renderer output until the first non-JCL
3883 while (my $line = <KID4_IN>) {
3884 push(@jclheader, $line);
3885 last if ($line !~ /$jclstr/);
3887 # If we had read at least two lines, at least one is
3888 # a JCL header, so do the merging
3889 if (@jclheader > 1) {
3891 # Discard the first and the last entry of the
3892 # @jclprepend array, we only need the option settings
3896 # Line after which we insert new JCL commands in the
3897 # JCL header of the job
3899 # Go through every JCL command in @jclprepend
3900 for my $line (@jclprepend) {
3901 # Search the command in the JCL header from the
3902 # driver. As search term use only the string from
3903 # the beginning of the line to the "=", so the
3904 # command will also be found when it has another
3906 $line =~ /^([^=]+)/;
3908 $cmd =~ s/^\s*(.*?)\s*$/$1/;
3911 # If the command is there, replace it
3912 $_ =~ s/$cmd\b.*(\r\n|\n|\r)/$line/ and
3916 # If the command is not found, insert it
3917 if (@jclheader > 2) {
3918 # @jclheader has more than one line,
3919 # insert the new command beginning
3920 # right after the first line and continuing
3921 # after the previous inserted command
3922 splice(@jclheader, $insert, 0, $line);
3925 # If we have only one line of JCL it
3926 # is probably something like the
3927 # "@PJL ENTER LANGUAGE=..." line
3928 # which has to be in the end, but
3929 # it also contains the
3930 # "<esc>%-12345X" which has to be in the
3931 # beginning of the job. So we split the
3932 # line right before the $jclstr and
3933 # append our command to the end of the
3934 # first part and let the second part
3935 # be a second JCL line.
3937 /^(.*?)($jclstr.*(\r\n|\n|\r))/;
3938 my $first = "$1$line";
3940 my $third = $jclheader[1];
3941 @jclheader = ($first, $second, $third);
3945 # Now pass on the merged JCL header
3946 print $fileh @jclheader;
3948 # The driver didn't create a JCL header, simply
3949 # prepend ours and then pass on the line which we
3951 print $fileh @jclprepend, @jclheader;
3954 # No merging of JCL header possible, simply prepend it
3955 print $fileh @jclprepend;
3959 # The rest of the job data
3961 while (read(KID4_IN, $buf, 1024)) {
3966 if (( @jclprepend > 1 ) && (!$driverjcl)) {
3967 print $fileh @jclappend;
3970 if (!close $fileh) {
3973 print KID_MESSAGE_IN
3974 "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
3975 close KID_MESSAGE_IN;
3976 rip_die ("error closing $fileh",
3977 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
3981 print $logh "tail process done writing data to STDOUT\n";
3983 # Handle signals of the backend interface
3984 if ($retval != $EXIT_PRINTED) {
3986 print KID_MESSAGE_IN "4 $retval\n";
3987 close KID_MESSAGE_IN;
3991 # Successful exit, inform main process
3993 print KID_MESSAGE_IN "4 $EXIT_PRINTED\n";
3994 close KID_MESSAGE_IN;
3996 print $logh "KID4 finished\n";
3997 exit($EXIT_PRINTED);
4004 ## Close the renderer process and wait until all kid processes finish.
4006 sub closerendererhandle {
4008 my ($rendererhandle, $rendererpid) = @_;
4010 print $logh "${added_lf}Closing renderer\n";
4013 close $rendererhandle;
4015 # Wait for all kid processes to finish or one kid process to fail
4016 close KID_MESSAGE_IN;
4017 while ((!$kidfailed) &&
4018 !(($kid3finished) &&
4020 my $message = <KID_MESSAGE>;
4022 if ($message =~ /(\d+)\s+(\d+)/) {
4025 print $logh "KID$kid_id exited with status $exitstat\n";
4026 if ($exitstat > 0) {
4027 $kidfailed = $exitstat;
4028 } elsif ($kid_id == 3) {
4030 } elsif ($kid_id == 4) {
4038 # If a kid failed, return the exit stat of this kid
4039 if ($kidfailed != 0) {
4040 $retval = $kidfailed;
4043 print $logh "Renderer exit stat: $retval\n";
4044 # Wait for renderer child
4045 waitpid($rendererpid, 0);
4046 print $logh "Renderer process finished\n";
4052 ## This function is only used when the input data is not
4053 ## PostScript. Then it runs a filter which converts non-PostScript
4054 ## files into PostScript. The user can choose which filter he wants
4055 ## to use. The filter command line is provided by $fileconverter.
4057 sub getfileconverterhandle {
4059 # Already read data must be converted, too
4060 my ($dat, $alreadyread) = @_;
4062 print $logh "${added_lf}Starting converter for non-PostScript files\n";
4064 # Determine with which command non-PostScript files are converted
4066 if ($fileconverter eq "") {
4067 if ($spoolerfileconverters->{$spooler}) {
4068 $fileconverter = $spoolerfileconverters->{$spooler};
4070 for my $c (@fileconverters) {
4071 ($c =~ m/^\s*(\S+)\s+/) || ($c = m/^\s*(\S+)$/);
4074 $fileconverter = $command;
4076 for (split(':', $ENV{'PATH'})) {
4077 if (-x "$_/$command") {
4078 $fileconverter = $c;
4083 if ($fileconverter ne "") {
4088 if ($fileconverter eq "") {
4089 $fileconverter = "echo \"Cannot convert file to " .
4090 "PostScript!\" 1>&2";
4094 # Insert the page size into the $fileconverter
4095 if ($fileconverter =~ /\@\@([^@]+)\@\@PAGESIZE\@\@/) {
4096 # We always use the "header" option swt here, with a
4097 # non-PostScript file we have no "currentpage"
4100 my $sizestr = (($arg = $dat->{'args_byname'}{'PageSize'})
4104 # Use wider margins so that the pages come out completely on
4105 # every printer model (especially HP inkjets)
4106 if ($fileconverter =~ /^\s*(a2ps)\s+/) {
4107 if (lc($sizestr) eq "letter") {
4108 $sizestr = "Letterdj";
4109 } elsif (lc($sizestr) eq "a4") {
4113 $optstr .= $sizestr;
4117 $fileconverter =~ s/\@\@([^@]+)\@\@PAGESIZE\@\@/$optstr/;
4120 # Insert the job title into the $fileconverter
4121 if ($fileconverter =~ /\@\@([^@]+)\@\@JOBTITLE\@\@/) {
4124 "Documentation for the $model";
4128 ($arg = $jobtitle) =~ s/\"/\\\"/g;
4129 if (($titlearg =~ /\"/) || $arg) {
4130 $optstr = $titlearg . ($titlearg =~ /\"/ ? '' : '"') .
4131 ($arg ? "$arg\"" : '"');
4135 $fileconverter =~ s/\@\@([^@]+)\@\@JOBTITLE\@\@/$optstr/;
4138 # Apply "pstops" when having used a file converter under CUPS, so
4139 # CUPS can stuff the default settings into the PostScript output
4140 # of the file converter (so all CUPS settings get also applied when
4141 # one prints the documentation pages (all other files we get
4142 # already converted to PostScript by CUPS).
4143 if ($spooler eq 'cups') {
4145 " | ${programdir}pstops '$rargs[0]' '$rargs[1]' '$rargs[2]' " .
4146 "'$rargs[3]' '$rargs[4]'";
4149 # Variables for the kid processes reporting their state
4151 # Set up a pipe for the kids to pass their exit stat to the main process
4152 pipe KID_MESSAGE_CONV, KID_MESSAGE_CONV_IN;
4154 # When one kid fails put the exit stat here
4157 # When a kid exits successfully, mark it here
4165 if (!defined($kid1)) {
4168 print $logh "$0: cannot fork for kid1!\n";
4169 rip_die ("can't fork for kid1",
4170 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4175 # we are the parent; return a glob to the filehandle
4178 return ( *KID1_IN, $kid1 );
4181 $kidgeneration += 1;
4183 # We go on reading the job data and stuff it into the file
4187 $SIG{PIPE} = 'DEFAULT';
4191 if (!defined($kid2)) {
4192 print $logh "$0: cannot fork for kid2!\n";
4196 close KID_MESSAGE_CONV;
4197 print KID_MESSAGE_CONV_IN
4198 "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
4199 rip_die ("can't fork for kid2",
4200 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4204 # parent, child of primary task; we are |$fileconverter|
4207 print $logh "file converter PID kid2=$kid2\n";
4208 if (($debug) || ($spooler ne 'cups')) {
4209 print $logh "file converter command: $fileconverter\n";
4212 if (!close STDIN && $! != $ESPIPE) {
4215 close KID_MESSAGE_CONV;
4216 print KID_MESSAGE_CONV_IN
4217 "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
4218 close KID_MESSAGE_CONV_IN;
4219 rip_die ("Couldn't close STDIN in $kid2",
4220 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4222 if (!open (STDIN, "<&KID2_IN")) {
4225 close KID_MESSAGE_CONV;
4226 print KID_MESSAGE_CONV_IN
4227 "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
4228 close KID_MESSAGE_CONV_IN;
4229 rip_die ("Couldn't dup KID2_IN",
4230 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4232 if (!close STDOUT) {
4235 close KID_MESSAGE_CONV;
4236 print KID_MESSAGE_CONV_IN
4237 "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
4238 close KID_MESSAGE_CONV_IN;
4239 rip_die ("Couldn't close STDOUT in $kid2",
4240 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4242 if (!open (STDOUT, ">&KID1")) {
4245 close KID_MESSAGE_CONV;
4246 print KID_MESSAGE_CONV_IN
4247 "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
4248 close KID_MESSAGE_CONV_IN;
4249 rip_die ("Couldn't dup KID1",
4250 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4253 if (!open (STDERR, ">&$logh")) {
4256 close KID_MESSAGE_CONV;
4257 print KID_MESSAGE_CONV_IN
4258 "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
4259 close KID_MESSAGE_CONV_IN;
4260 rip_die ("Couldn't dup logh to stderr",
4261 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4265 # Actually run the thing...
4266 modern_system("$fileconverter");
4268 my $fileconverterretval = $? >> 8;
4269 print $logh "file converter return value: " .
4270 "$fileconverterretval\n";
4271 my $fileconvertersignal = $? & 127;
4272 print $logh "file converter received signal: ".
4273 "$fileconverterretval\n";
4279 if ($fileconvertersignal == SIGUSR1) {
4280 $retval = $EXIT_PRNERR;
4281 } elsif ($fileconvertersignal == SIGUSR2) {
4282 $retval = $EXIT_PRNERR_NORETRY;
4283 } elsif ($fileconvertersignal == SIGTTIN) {
4284 $retval = $EXIT_ENGAGED;
4286 if ($retval != $EXIT_PRINTED) {
4287 close KID_MESSAGE_CONV;
4288 print KID_MESSAGE_CONV_IN "1 $retval\n";
4289 close KID_MESSAGE_CONV_IN;
4292 # Evaluate fileconverter result
4293 if ($fileconverterretval == 0) {
4294 # Success, exit with 0 and inform main process
4295 close KID_MESSAGE_CONV;
4296 print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n";
4297 close KID_MESSAGE_CONV_IN;
4301 close KID_MESSAGE_CONV;
4302 print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR\n";
4303 close KID_MESSAGE_CONV_IN;
4304 rip_die ("The file converter command line returned " .
4305 "an unrecognized error code " .
4306 "$fileconverterretval.",
4314 # When arrived here the fileconverter command line was
4316 # So exit with zero exit value here and inform the main process
4317 close KID_MESSAGE_CONV;
4318 print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n";
4319 close KID_MESSAGE_CONV_IN;
4320 # Wait for input child
4322 print $logh "KID1 finished\n";
4325 $kidgeneration += 1;
4327 # child, first part of the pipe, reading in the data from
4328 # standard input and stuffing it into the file converter
4329 # after putting in the already read data (in $alreadyread)
4333 # At first pass the data which we have already read to the
4335 print KID2 $alreadyread;
4336 # Then read the rest from standard input
4338 while (read(STDIN, $buf, 1024)) {
4342 if (!close STDIN && $! != $ESPIPE) {
4344 close KID_MESSAGE_CONV;
4345 print KID_MESSAGE_CONV_IN
4346 "2 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
4347 close KID_MESSAGE_CONV_IN;
4348 rip_die ("error closing STDIN",
4349 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4353 print $logh "tail process done reading data from STDIN\n";
4355 # Successful exit, inform main process
4356 close KID_MESSAGE_CONV;
4357 print KID_MESSAGE_CONV_IN "2 $EXIT_PRINTED\n";
4358 close KID_MESSAGE_CONV_IN;
4360 print $logh "KID2 finished\n";
4361 exit($EXIT_PRINTED);
4368 ## Close the file conversion process and wait until all kid processes
4371 sub closefileconverterhandle {
4373 my ($fileconverterhandle, $fileconverterpid) = @_;
4375 print $logh "${added_lf}Closing file converter\n";
4378 close $fileconverterhandle;
4380 # Wait for all kid processes to finish or one kid process to fail
4381 close KID_MESSAGE_CONV_IN;
4382 while ((!$convkidfailed) &&
4383 !(($kid1finished) &&
4385 my $message = <KID_MESSAGE_CONV>;
4387 if ($message =~ /(\d+)\s+(\d+)/) {
4390 print $logh "KID$kid_id exited with status $exitstat\n";
4391 if ($exitstat > 0) {
4392 $convkidfailed = $exitstat;
4393 } elsif ($kid_id == 1) {
4395 } elsif ($kid_id == 2) {
4401 close KID_MESSAGE_CONV;
4403 # If a kid failed, return the exit stat of this kid
4404 if ($convkidfailed != 0) {
4405 $retval = $convkidfailed;
4408 print $logh "File converter exit stat: $retval\n";
4409 # Wait for fileconverter child
4410 waitpid($fileconverterpid, 0);
4411 print $logh "File converter process finished\n";
4417 ## Generate the documentation page and return a filehandle to get it
4419 sub getdocgeneratorhandle {
4421 # The data structure with the options
4424 print $logh "${added_lf}Generating documentation page for the $model\n";
4426 # Printer queue name
4429 $printerstr = $printer;
4431 $printerstr = "<printer>";
4434 # Spooler-specific differences
4436 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4437 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4439 $numopt, $numoptleft, $numoptequal, $numoptright,
4440 $stropt, $stroptleft, $stroptequal, $stroptright,
4441 $optsep, $trailer, $custompagesize);
4442 if ($spooler eq 'cups') {
4444 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4445 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4447 $numopt, $numoptleft, $numoptequal, $numoptright,
4448 $stropt, $stroptleft, $stroptequal, $stroptright,
4449 $optsep, $trailer, $custompagesize) =
4450 ("lpr -P $printerstr ",
4452 "-o ", "no", "", "=", "",
4456 "\n Custom size: -o PageSize=Custom." .
4457 "<width>x<height>[<unit>]\n" .
4458 " Units: pt (default), in, cm, mm\n" .
4459 " Example: -o PageSize=Custom.4.0x6.0in\n");
4460 } elsif ($spooler eq 'lpd') {
4462 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4463 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4465 $numopt, $numoptleft, $numoptequal, $numoptright,
4466 $stropt, $stroptleft, $stroptequal, $stroptright,
4467 $optsep, $trailer, $custompagesize) =
4468 ("lpr -P $printerstr -J \"",
4470 "", "", "", "=", "",
4474 "\n Custom size: PageSize=Custom." .
4475 "<width>x<height>[<unit>]\n" .
4476 " Units: pt (default), in, cm, mm\n" .
4477 " Example: PageSize=Custom.4.0x6.0in\n");
4478 } elsif ($spooler eq 'gnulpr') {
4480 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4481 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4483 $numopt, $numoptleft, $numoptequal, $numoptright,
4484 $stropt, $stroptleft, $stroptequal, $stroptright,
4485 $optsep, $trailer, $custompagesize) =
4486 ("lpr -P $printerstr ",
4488 "-o ", "", "", "=", "",
4492 "\n Custom size: -o PageSize=Custom." .
4493 "<width>x<height>[<unit>]\n" .
4494 " Units: pt (default), in, cm, mm\n" .
4495 " Example: -o PageSize=Custom.4.0x6.0in\n");
4496 } elsif ($spooler eq 'lprng') {
4498 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4499 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4501 $numopt, $numoptleft, $numoptequal, $numoptright,
4502 $stropt, $stroptleft, $stroptequal, $stroptright,
4503 $optsep, $trailer, $custompagesize) =
4504 ("lpr -P $printerstr ",
4506 "-Z ", "", "", "=", "",
4510 "\n Custom size: -Z PageSize=Custom." .
4511 "<width>x<height>[<unit>]\n" .
4512 " Units: pt (default), in, cm, mm\n" .
4513 " Example: -Z PageSize=Custom.4.0x6.0in\n");
4514 } elsif ($spooler eq 'ppr') {
4516 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4517 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4519 $numopt, $numoptleft, $numoptequal, $numoptright,
4520 $stropt, $stroptleft, $stroptequal, $stroptright,
4521 $optsep, $trailer, $custompagesize) =
4522 ("ppr -d $printerstr --ripopts \"",
4524 "", "", "", "=", "",
4528 "\n Custom size: PageSize=Custom." .
4529 "<width>x<height>[<unit>]\n" .
4530 " Units: pt (default), in, cm, mm\n" .
4531 " Example: PageSize=Custom.4.0x6.0in\n");
4532 } elsif ($spooler eq 'ppr-int') {
4534 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4535 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4537 $numopt, $numoptleft, $numoptequal, $numoptright,
4538 $stropt, $stroptleft, $stroptequal, $stroptright,
4539 $optsep, $trailer, $custompagesize) =
4540 ("ppr -d $printerstr -i \"",
4542 "", "", "", "=", "",
4546 "\n Custom size: PageSize=Custom." .
4547 "<width>x<height>[<unit>]\n" .
4548 " Units: pt (default), in, cm, mm\n" .
4549 " Example: PageSize=Custom.4.0x6.0in\n");
4550 } elsif ($spooler eq 'cps') {
4552 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4553 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4555 $numopt, $numoptleft, $numoptequal, $numoptright,
4556 $stropt, $stroptleft, $stroptequal, $stroptright,
4557 $optsep, $trailer, $custompagesize) =
4558 ("lpr -P $printerstr ",
4560 "-o ", "", "", "=", "",
4564 "\n Custom size: -o PageSize=Custom." .
4565 "<width>x<height>[<unit>]\n" .
4566 " Units: pt (default), in, cm, mm\n" .
4567 " Example: -o PageSize=Custom.4.0x6.0in\n");
4568 } elsif ($spooler eq 'direct') {
4570 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4571 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4573 $numopt, $numoptleft, $numoptequal, $numoptright,
4574 $stropt, $stroptleft, $stroptequal, $stroptright,
4575 $optsep, $trailer, $custompagesize) =
4576 ("$programname -P $printerstr ",
4578 "-o ", "", "", "=", "",
4582 "\n Custom size: -o PageSize=Custom." .
4583 "<width>x<height>[<unit>]\n" .
4584 " Units: pt (default), in, cm, mm\n" .
4585 " Example: -o PageSize=Custom.4.0x6.0in\n");
4586 } elsif ($spooler eq 'pdq') {
4588 $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
4589 $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
4591 $numopt, $numoptleft, $numoptequal, $numoptright,
4592 $stropt, $stroptleft, $stroptequal, $stroptright,
4593 $optsep, $trailer, $custompagesize) =
4594 ("pdq -P $printerstr ",
4596 "-o", "no", "", "_", "",
4601 "Option 'PageWidth':\n".
4602 " Page Width (for \"Custom\" page size)\n" .
4603 " A floating point number argument\n" .
4604 " Range: 0 <= x <= 100000\n" .
4605 " Example: -aPageWidth=123.4\n" .
4607 "Option 'PageHeight':\n" .
4608 " Page Height (for \"Custom\" page size)\n" .
4609 " A floating point number argument\n" .
4610 " Range: 0 <= x <= 100000\n" .
4611 " Example: -aPageHeight=234.5\n" .
4613 "Option 'PageSizeUnit':\n" .
4614 " Unit (for \"Custom\" page size)\n" .
4615 " An enumerated choice argument\n" .
4616 " Possible choices:\n" .
4617 " o -oPageSizeUnit_pt: Points (1/72 inch)\n" .
4618 " o -oPageSizeUnit_in: Inches\n" .
4619 " o -oPageSizeUnit_cm: cm\n" .
4620 " o -oPageSizeUnit_mm: mm\n" .
4621 " Example: -oPageSizeUnit_mm\n");
4624 # Variables for the kid processes reporting their state
4626 # Set up a pipe for the kids to pass their exit stat to the main process
4627 pipe KID_MESSAGE_DOC, KID_MESSAGE_DOC_IN;
4629 # When the kid fails put the exit stat here
4632 # When the kid exits successfully, mark it here
4639 if (!defined($kid0)) {
4642 print $logh "$0: cannot fork for kid0!\n";
4643 rip_die ("can't fork for kid0",
4644 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
4648 # we are the parent; return a glob to the filehandle
4650 print $logh "Documentation page generator PID kid0=$kid0\n";
4651 return ( *KID0_IN, $kid0 );
4654 $kidgeneration += 1;
4656 # we are the kid; we generate the documentation page
4659 $SIG{PIPE} = 'DEFAULT';
4661 # Kill data on STDIN to satisfy PPR
4662 if (($spooler eq 'ppr_int') || ($spooler eq 'ppr')) {
4663 while (my $dummy = <STDIN>) {};
4666 or print $logh "Error closing STDIN for docs print\n";
4668 # write the job into KID0
4671 print "\nInvokation summary for the $model\n\n";
4672 print "Use the following command line:\n\n";
4673 if ($booloptfalseprefix) {
4674 # I think that what you want to indicate is that the prefix for a false
4675 # boolean has this form: xxx [no]<switch> or something similar
4676 print " ${command}${enumopt}${enumoptleft}<option>" .
4677 "${enumoptequal}<choice>${enumoptright}${optsep}" .
4678 "${boolopt}${booloptleft}\[${booloptfalseprefix}\]<switch>" .
4679 "${booloptright}${optsep}" .
4680 "${numopt}${numoptleft}<num. option>${numoptequal}" .
4681 "<value>${numoptright}${optsep}" .
4682 "${stropt}${stroptleft}<string option>${stroptequal}" .
4683 "<string>${stroptright}" .
4686 print " ${command}${enumopt}${enumoptleft}<option>" .
4687 "${enumoptequal}<choice>${enumoptright}${optsep}" .
4688 "${boolopt}${booloptleft}<switch>${booloptequal}" .
4689 "<True/False>${booloptright}${optsep}" .
4690 "${numopt}${numoptleft}<num. option>${numoptequal}" .
4691 "<value>${numoptright}${optsep}" .
4692 "${stropt}${stroptleft}<string option>${stroptequal}" .
4693 "<string>${stroptright}" .
4697 print "The following options are available for this printer:\n\n";
4699 for my $arg (@{$dat->{'args'}}) {
4704 $default) = ($arg->{'name'},
4710 # Is this really an option? Otherwise skip it.
4713 # We don't need "PageRegion", we have "PageSize"
4714 next if ($name eq "PageRegion");
4716 # Skip enumerated choice options with only one choice
4717 next if (($type eq 'enum') && ($#{$arg->{'vals'}} < 1));
4719 my $commentstr = "";
4721 $commentstr = " $comment\n";
4725 if ($type eq "enum") {
4726 $typestr = "An enumerated choice";
4727 } elsif ($type eq "bool") {
4728 $typestr = "A boolean";
4729 } elsif ($type eq "int") {
4730 $typestr = "An integer number";
4731 } elsif ($type eq "float") {
4732 $typestr = "A floating point number";
4733 } elsif (($type eq "string") || ($type eq "password")) {
4734 $typestr = "A string";
4737 print "Option '$name':\n$commentstr $typestr argument\n";
4738 print " This options corresponds to a JCL command\n" if ($arg->{'style'} eq 'J');
4740 if ($type eq 'bool') {
4741 print " Possible choices:\n";
4742 if ($booloptfalseprefix) {
4743 print " o $name: $arg->{'comment_true'}\n";
4744 print " o $booloptfalseprefix$name: " .
4745 "$arg->{'comment_false'}\n";
4746 if (defined($default)) {
4747 my $defstr = ($default ? "" : "$booloptfalseprefix");
4748 print " Default: $defstr$name\n";
4750 print " Example: ${boolopt}${booloptleft}${name}" .
4751 "${booloptright}\n";
4753 print " o True: $arg->{'comment_true'}\n";
4754 print " o False: $arg->{'comment_false'}\n";
4755 if (defined($default)) {
4756 my $defstr = ($default ? "True" : "False");
4757 print " Default: $defstr\n";
4759 print " Example: ${boolopt}${booloptleft}${name}" .
4760 "${booloptequal}True${booloptright}\n";
4762 } elsif ($type eq 'enum') {
4763 print " Possible choices:\n";
4765 my $havecustomsize = 0;
4766 for (@{$arg->{'vals'}}) {
4767 my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
4768 print " o $choice: $comment\n";
4769 if (($name eq "PageSize") && ($choice eq "Custom")) {
4770 $havecustomsize = 1;
4774 if (defined($default)) {
4775 print " Default: $default\n";
4777 print " Example: ${enumopt}${enumoptleft}${name}" .
4778 "${enumoptequal}${exarg}${enumoptright}\n";
4779 if ($havecustomsize) {
4780 print $custompagesize;
4782 } elsif ($type eq 'int' or $type eq 'float') {
4783 my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
4785 if (defined($max)) {
4786 print " Range: $min <= x <= $max\n";
4789 if (defined($default)) {
4790 print " Default: $default\n";
4793 if (!$exarg) { $exarg=0; }
4794 print " Example: ${numopt}${numoptleft}${name}" .
4795 "${numoptequal}${exarg}${numoptright}\n";
4796 } elsif ($type eq 'string' or $type eq 'password') {
4797 my $maxlength = $arg->{'maxlength'};
4798 if (defined($maxlength)) {
4799 print " Maximum length: $maxlength characters\n";
4801 if (defined($default)) {
4802 print " Default: $default\n";
4804 print " Examples/special settings:\n";
4805 for (@{$arg->{'vals'}}) {
4806 my ($value, $comment, $driverval, $proto) =
4807 ($_->{'value'}, $_->{'comment'}, $_->{'driverval'},
4809 # Retrieve the original string from the prototype
4813 my $s = index($proto, '%s');
4814 my $l = length($driverval) - length($proto) + 2;
4815 if (($s < 0) || ($l < 0)) {
4816 $string = $driverval;
4818 $string = substr($driverval, $s, $l);
4821 $string = $driverval;
4823 print " o ${stropt}${stroptleft}${name}" .
4824 "${stroptequal}${value}${stroptright}";
4825 if (($value ne $string) || ($comment ne $value)) {
4828 if ($value ne $string) {
4829 if ($string eq '') {
4830 print "blank string";
4832 print "\"$string\"";
4835 if (($value ne $string) && ($comment ne $value)) {
4838 if ($value ne $comment) {
4841 if (($value ne $string) || ($comment ne $value)) {
4853 or print $logh "Error closing KID0 for docs print\n";
4855 or print $logh "Error closing STDOUT for docs print\n";
4857 # Finished successfully, inform main process
4858 close KID_MESSAGE_DOC;
4859 print KID_MESSAGE_DOC_IN "0 $EXIT_PRINTED\n";
4860 close KID_MESSAGE_DOC_IN;
4862 print $logh "KID0 finished\n";
4863 exit($EXIT_PRINTED);
4869 ## Close the documentation page generation process and wait until the
4870 ## kid process finishes.
4872 sub closedocgeneratorhandle {
4874 my ($handle, $pid) = @_;
4876 print $logh "${added_lf}Closing documentation page generator\n";
4881 # Wait for the kid process to finish or the kid process to fail
4882 close KID_MESSAGE_DOC_IN;
4883 while ((!$dockidfailed) &&
4885 my $message = <KID_MESSAGE_DOC>;
4887 if ($message =~ /(\d+)\s+(\d+)/) {
4890 print $logh "KID$kid_id exited with status $exitstat\n";
4891 if ($exitstat > 0) {
4892 $dockidfailed = $exitstat;
4893 } elsif ($kid_id eq "0") {
4899 close KID_MESSAGE_DOC;
4901 # If the kid failed, return the exit stat of the kid
4902 if ($dockidfailed != 0) {
4903 $retval = $dockidfailed;
4906 print $logh "Documentation page generator exit stat: $retval\n";
4907 # Wait for fileconverter child
4909 print $logh "Documentation page generator process finished\n";
4915 # Find an argument by name in a case-insensitive way
4919 for my $arg (@{$dat->{'args'}}) {
4920 return $arg if (lc($name) eq lc($arg->{'name'}));
4927 my ($arg,$name) = @_;
4929 for my $val (@{$arg->{'vals'}}) {
4930 return $val if (lc($name) eq lc($val->{'value'}));
4936 # Write a Good-Bye letter and clean up before committing suicide (send
4937 # error message to caller)
4940 my ($message, $exitstat) = @_;
4942 my $errcod = $! + 0;
4944 # Log that we are dying ...
4945 print $logh "Process dying with \"$message\", exit stat: $exitstat\n\terror: $errmsg ($errcod)\n";
4947 print $logh "Cleaning up ...\n";
4948 foreach my $killsignal (15, 9) {
4950 # Kill all registered subshells
4951 foreach my $pid (keys %pids) {
4952 print $logh "Killing process $pid ($pids{$pid}) and its subprocesses with signal $killsignal\n";
4953 # This call kills the process group with group ID $pid, the
4954 # group which was formed from the initial process $pid which
4955 # contains $pid and all its subprocesses
4956 kill(-$killsignal, $pid);
4957 # If the system does not support process groups and therefore
4958 # the call above does not kill anything, kill at least $pid
4959 kill($killsignal, $pid);
4962 # Close the documentation page generator (if it was used)
4964 print $logh "Killing process $kid0 (KID0) with signal $killsignal\n";
4965 kill($killsignal, $kid0);
4968 # Close the file converter (if it was used)
4970 print $logh "Killing process $kid2 (KID2) with signal $killsignal\n";
4971 kill($killsignal, $kid2);
4974 print $logh "Killing process $kid1 (KID1) with signal $killsignal\n";
4975 kill($killsignal, $kid1);
4978 # Close the renderer
4980 print $logh "Killing process $kid4 (KID4) with signal $killsignal\n";
4981 kill($killsignal, $kid4);
4984 print $logh "Killing process $kid3 (KID3) with signal $killsignal\n";
4985 kill($killsignal, $kid3);
4988 # Wait some time for the processes to close
4989 sleep(5 - $kidgeneration) if $killsignal != 9;
4992 # Do the debug dump and the PPR error handling only from the main process
4993 if ($kidgeneration == 0) { # We are the main process
4995 if ($spooler eq 'ppr_int') {
4996 # Special error handling for PPR intefaces
4997 $message =~ s/\\/\\\\/;
4998 $message =~ s/\"/\\\"/;
4999 my @messagelines = split("\n", $message);
5000 my $firstline = "TRUE";
5001 for my $line (@messagelines) {
5002 modern_system("lib/alert $printer $firstline \"$line\"");
5003 $firstline = "FALSE";
5006 print STDERR $message . "\n";
5010 local $Data::Dumper::Purity=1;
5011 local $Data::Dumper::Indent=1;
5012 print $logh Dumper($dat);
5017 print $logh "${added_lf}Closing foomatic-rip.\n";
5023 # Signal handling routines
5028 sub set_exit_canceled {
5029 $retval = $EXIT_PRINTED;
5030 rip_die ("Caught termination signal: Job canceled", $retval);
5033 sub set_exit_error {
5034 $retval = $EXIT_SIGNAL;
5035 rip_die ("Caught error signal: Error in renderer, driver, or foomatic-rip", $retval);
5038 sub set_exit_prnerr {
5039 $retval = $EXIT_PRNERR;
5042 sub set_exit_prnerr_noretry {
5043 $retval = $EXIT_PRNERR_NORETRY;
5046 sub set_exit_engaged {
5047 $retval = $EXIT_ENGAGED;
5050 # Read the config file
5056 # Read config file if present
5057 if (open CONF, "< $file") {
5060 $conf{$1}="$2" if (m/^\s*([^\#\s]\S*)\s*:\s*(.*?)\s*$/);
5068 sub removeunprintables {
5069 # Remove unprintable characters
5071 $str =~ s/[\x00-\x1f]//g;
5075 sub removeshellescapes {
5076 # Remove shell escape characters
5078 $str =~ s/[\|<>&!\$\'\"\#\*\?\(\)\[\]\{\}]//g;
5082 sub removespecialchars {
5083 # Remove unprintable and shell escape characters
5084 return removeshellescapes(removeunprintables($_[0]));
5090 # Replace HTML/XML entities by the original characters
5091 $str =~ s/\'/\'/g;
5092 $str =~ s/\"/\"/g;
5093 $str =~ s/\>/\>/g;
5094 $str =~ s/\</\</g;
5095 $str =~ s/\&/\&/g;
5097 # Replace special entities by job data
5098 $rbinumcopies = $copies if !$rbinumcopies;
5099 $str =~ s/\&job;/$jobid/g;
5100 $str =~ s/\&user;/$jobuser/g;
5101 $str =~ s/\&host;/$jobhost/g;
5102 $str =~ s/\&title;/$jobtitle/g;
5103 $str =~ s/\&copies;/$copies/g;
5104 $str =~ s/\&rbinumcopies;/$rbinumcopies/g;
5105 $str =~ s/\&options;/$optstr/g;
5107 my ($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0..5];
5108 my $yearstr = sprintf("%04d", $year + 1900);
5109 my $monstr = sprintf("%02d", $mon + 1);
5110 my $mdaystr = sprintf("%02d", $mday);
5111 my $hourstr = sprintf("%02d", $hour);
5112 my $minstr = sprintf("%02d", $min);
5113 my $secstr = sprintf("%02d", $sec);
5115 $str =~ s/\&year;/$yearstr/g;
5116 $str =~ s/\&month;/$monstr/g;
5117 $str =~ s/\&date;/$mdaystr/g;
5118 $str =~ s/\&hour;/$hourstr/g;
5119 $str =~ s/\&min;/$minstr/g;
5120 $str =~ s/\&sec;/$secstr/g;
5126 # Replace hex notation for unprintable characters in PPD files
5127 # by the actual characters ex: "<0A>" --> chr(hex("0A"))
5131 my $firstdigit = "";
5132 for (my $i = 0; $i < length($input); $i ++) {
5133 my $c = substr($input, $i, 1);
5138 } elsif ($c =~ /^[0-9a-fA-F]$/) {
5139 # Hexadecimal digit, two of them give a character
5140 if ($firstdigit ne "") {
5141 $output .= chr(hex("$firstdigit$c"));
5149 # Beginning of hex string
5160 sub undossify( $ ) {
5161 # Remove "dossy" line ends ("\r\n") from a string
5163 $str =~ s/\r\n/\n/gs;
5169 # Check if there is already an argument record $argname in $dat, if not,
5171 my ($dat, $argname) = @_;
5172 return if defined($dat->{'args_byname'}{$argname});
5175 $rec->{'name'} = $argname;
5176 # Insert record in 'args' array for browsing all arguments
5177 push(@{$dat->{'args'}}, $rec);
5178 # 'args_byname' hash for looking up arguments by name
5179 $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}];
5180 # Default execution style is 'G' (PostScript) since all arguments for
5181 # which we don't find "*Foomatic..." keywords are usual PostScript
5183 $dat->{'args_byname'}{$argname}{'style'} = 'G';
5184 # Default prototype for code to insert, used by enum options
5185 $dat->{'args_byname'}{$argname}{'proto'} = '%s';
5186 # stop Perl nattering about undefined to string comparisons
5187 $dat->{'args_byname'}{$argname}{'type'} = '';
5188 print $logh "Added option $argname\n";
5192 # Check if there is already an choice record $setting in the $argname
5193 # argument in $dat, if not, create one
5194 my ($dat, $argname, $setting) = @_;
5196 defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting});
5199 $rec->{'value'} = $setting;
5200 # Insert record in 'vals' array for browsing all settings
5201 push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec);
5202 # 'vals_byname' hash for looking up settings by name
5203 $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} =
5204 $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}];
5208 # remove the argument record $argname from $dat
5209 my ($dat, $argname) = @_;
5210 return if !defined($dat->{'args_byname'}{$argname});
5211 # Remove 'args_byname' hash for looking up arguments by name
5212 delete $dat->{'args_byname'}{$argname};
5213 # Remove argument itself
5214 for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
5215 if ($dat->{'args'}[$i]{'name'} eq $argname) {
5216 print $logh "Removing option " .
5218 splice(@{$dat->{'args'}}, $i, 1);
5225 # remove all records of PostScript arguments from $dat
5227 return if !defined($dat);
5228 for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
5229 if ($dat->{'args'}[$i]{'style'} eq 'G') {
5230 print $logh "Removing PostScript option " .
5231 $dat->{'args'}[$i]{'name'} . "\n";
5232 # Remove 'args_byname' hash for looking up arguments by name
5233 delete $dat->{'args_byname'}{$dat->{'args'}[$i]{'name'}};
5234 # Remove argument itself
5235 splice(@{$dat->{'args'}}, $i, 1);
5241 sub checkoptionvalue {
5243 ## This function checks whether a given value is valid for a given
5244 ## option. If yes, it returns a cleaned value (e. g. always 0 or 1
5245 ## for boolean options), otherwise "undef". If $forcevalue is set,
5246 ## we always determine a corrected value to insert (we never return
5249 # Is $value valid for the option named $argname?
5250 my ($dat, $argname, $value, $forcevalue) = @_;
5252 # Record for option $argname
5253 my $arg = $dat->{'args_byname'}{$argname};
5254 $arg->{'type'} = '' if not defined $arg->{'type'};
5256 if ($arg->{'type'} eq 'bool') {
5257 my $lcvalue = lc($value);
5258 if ((($lcvalue) eq 'true') ||
5259 (($lcvalue) eq 'on') ||
5260 (($lcvalue) eq 'yes') ||
5261 (($lcvalue) eq '1')) {
5263 } elsif ((($lcvalue) eq 'false') ||
5264 (($lcvalue) eq 'off') ||
5265 (($lcvalue) eq 'no') ||
5266 (($lcvalue) eq '0')) {
5268 } elsif ($forcevalue) {
5269 # This maps Unknown to mean False. Good? Bad?
5270 # It was done so in Foomatic 2.0.x, too.
5271 my $name = $arg->{'name'};
5273 "The value $value for $name is not a " .
5275 " --> Using False instead!\n";
5278 } elsif ($arg->{'type'} eq 'enum') {
5279 if ($value =~ /^None$/i) {
5281 } elsif (defined($arg->{'vals_byname'}{$value})) {
5283 } elsif ((($arg->{'name'} eq "PageSize") ||
5284 ($arg->{'name'} eq "PageRegion")) &&
5285 (defined($arg->{'vals_byname'}{'Custom'})) &&
5286 ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) {
5289 } elsif ($forcevalue) {
5290 # wtf!? that's not a choice!
5291 my $name = $arg->{'name'};
5292 # Return the first entry of the list
5293 my $firstentry = $arg->{'vals'}[0]{'value'};
5295 "The value $value for $name is not a " .
5297 " --> Using $firstentry instead!\n";
5300 } elsif (($arg->{'type'} eq 'int') ||
5301 ($arg->{'type'} eq 'float')) {
5302 if (($value <= $arg->{'max'}) &&
5303 ($value >= $arg->{'min'})) {
5304 if ($arg->{'type'} eq 'int') {
5305 return POSIX::floor($value);
5309 } elsif ($forcevalue) {
5310 my $name = $arg->{'name'};
5312 if ($value > $arg->{'max'}) {
5313 $newvalue = $arg->{'max'}
5314 } elsif ($value < $arg->{'min'}) {
5315 $newvalue = $arg->{'min'}
5318 "The value $value for $name is out of " .
5320 " --> Using $newvalue instead!\n";
5323 } elsif (($arg->{'type'} eq 'string') ||
5324 ($arg->{'type'} eq 'password')) {
5325 if (defined($arg->{'vals_byname'}{$value})) {
5326 my $name = $arg->{'name'};
5328 "The value $value for $name is a predefined choice\n";
5330 } elsif (stringvalid($dat, $argname, $value)) {
5331 # Check whether the string is one of the enumerated choices
5332 my $sprintfproto = $arg->{'proto'};
5333 $sprintfproto =~ s/\%(?!s)/\%\%/g;
5334 my $driverval = sprintf($sprintfproto, $value);
5335 for my $val (@{$arg->{'vals'}}) {
5336 if (($val->{'driverval'} eq $driverval) ||
5337 ($val->{'driverval'} eq $value)) {
5338 my $name = $arg->{'name'};
5340 "The string $value for $name is the predefined " .
5341 "choice $val->{value}\n";
5342 return $val->{value};
5345 # "None" is mapped to the empty string
5346 if ($value eq 'None') {
5347 my $name = $arg->{'name'};
5349 "Option $name: 'None' is the mapped to the " .
5353 # No matching choice? Return the original string
5355 } elsif ($forcevalue) {
5356 my $name = $arg->{'name'};
5357 my $str = substr($value, 0, $arg->{'maxlength'});
5358 if (stringvalid($dat, $argname, $str)) {
5360 "The string $value for $name is longer than " .
5361 "$arg->{'maxlength'}, string shortened to $str\n";
5363 } elsif ($#{$arg->{'vals'}} >= 0) {
5365 my $firstentry = $arg->{'vals'}[0]{'value'};
5367 "The string $value for $name contains forbidden " .
5368 "characters or does not match the regular expression " .
5369 "defined for this option, using predefined choice " .
5370 "$firstentry instead\n";
5373 # We should not get here
5374 rip_die("Option $name incorrectly defined in the " .
5375 "PPD file!\n", $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
5384 ## Checks whether a user-supplied value for a string option is valid
5385 ## It must be within the length limit, should only contain allowed
5386 ## characters and match the given regexp
5389 my ($dat, $argname, $value) = @_;
5391 my $arg = $dat->{'args_byname'}{$argname};
5394 return 0 if (defined($arg->{'maxlength'}) &&
5395 (length($value) > $arg->{'maxlength'}));
5397 # Allowed characters
5398 if ($arg->{'allowedchars'}) {
5399 my $chars = $arg->{'allowedchars'};
5400 # Quote the slashes (if a slash is preceeded by an even number of
5401 # backslashes, it is not already quoted)
5402 $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
5403 return 0 if $value !~ /^[$chars]*$/;
5406 # Regular expression
5407 if ($arg->{'allowedregexp'}) {
5408 my $regexp = $arg->{'allowedregexp'};
5409 # Quote the slashes (if a slash is preceeded by an even number of
5410 # backslashes, it is not already quoted)
5411 $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
5412 return 0 if $value !~ /$regexp/;
5421 ## Let the values of a boolean option being 0 or 1 instead of
5422 ## "True" or "False", range-check the defaults of all options and
5423 ## issue warnings if the values are not valid
5425 # Option set to be examined
5426 my ($dat, $optionset) = @_;
5428 for my $arg (@{$dat->{'args'}}) {
5429 if (defined($arg->{$optionset})) {
5430 $arg->{$optionset} =
5432 ($dat, $arg->{'name'}, $arg->{$optionset}, 1);
5436 # If the settings for "PageSize" and "PageRegion" are different,
5437 # set the one for "PageRegion" to the one for "PageSize" and issue
5439 if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne
5440 $dat->{'args_byname'}{'PageRegion'}{$optionset}) {
5441 print $logh "Settings for \"PageSize\" and \"PageRegion\" are " .
5443 " PageSize: $dat->{'args_byname'}{'PageSize'}{$optionset}\n" .
5445 "$dat->{'args_byname'}{'PageRegion'}{$optionset}\n" .
5446 "Using the \"PageSize\" value " .
5447 "\"$dat->{'args_byname'}{'PageSize'}{$optionset}\"," .
5449 $dat->{'args_byname'}{'PageRegion'}{$optionset} =
5450 $dat->{'args_byname'}{'PageSize'}{$optionset};
5454 # If the PageSize or PageRegion was changed, also change the other
5458 # Name and value of the option we set, and the option set where we
5460 my ($dat, $name, $value, $optionset) = @_;
5462 # Don't do anything if we were called with an option other than
5463 # "PageSize" or "PageRegion"
5464 return if (($name ne "PageSize") && ($name ne "PageRegion"));
5466 # Don't do anything if not both "PageSize" and "PageRegion" exist
5467 return if ((!defined($dat->{'args_byname'}{'PageSize'})) ||
5468 (!defined($dat->{'args_byname'}{'PageRegion'})));
5472 # "PageSize" --> "PageRegion"
5473 if ($name eq "PageSize") {
5474 $dest = "PageRegion";
5477 # "PageRegion" --> "PageSize"
5478 if ($name eq "PageRegion") {
5484 if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) {
5485 # Standard paper size
5486 $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'};
5487 } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) {
5489 $dat->{'args_byname'}{$dest}{$optionset} = $value;
5495 ## Copy one option set into another one
5497 # Source and destination option sets
5498 my ($dat, $srcoptionset, $destoptionset) = @_;
5500 for my $arg (@{$dat->{'args'}}) {
5501 if (defined($arg->{$srcoptionset})) {
5502 $arg->{$destoptionset} = $arg->{$srcoptionset};
5509 ## Delete an option set
5511 # option set to be removed
5512 my ($dat, $optionset) = @_;
5514 for my $arg (@{$dat->{'args'}}) {
5515 if (defined($arg->{$optionset})) {
5516 delete($arg->{$optionset});
5523 ## Compare two option sets, if they are equal, return 1, otherwise 0
5525 # Option sets to be compared, flag to compare only command line and JCL
5527 my ($dat, $firstoptionset, $secondoptionset, $exceptPS) = @_;
5529 for my $arg (@{$dat->{'args'}}) {
5530 next if ($exceptPS && ($arg->{'style'} eq 'G'));
5531 if ((defined($arg->{$firstoptionset})) &&
5532 (defined($arg->{$secondoptionset}))) {
5533 # Both entries exist
5534 return 0 if $arg->{$firstoptionset} ne $arg->{$secondoptionset};
5535 } elsif ((defined($arg->{$firstoptionset})) ||
5536 (defined($arg->{$secondoptionset}))) {
5540 # If no entry exists, the non-existing entries are considered as
5546 sub makeprologsection {
5548 # option set to be used,
5549 # $comments = 1: Add "%%BeginProlog...%%EndProlog"
5550 my ($dat, $optionset, $comments) = @_;
5552 # Collect data to be inserted here
5557 print $logh "\"Prolog\" section is missing, inserting it.\n";
5558 push(@output, "%%BeginProlog\n");
5561 # Generate the option code (not necessary when CUPS is spooler)
5562 if ($spooler ne 'cups') {
5563 print $logh "Inserting option code into \"Prolog\" section.\n";
5564 buildcommandline ($dat, $optionset);
5565 push(@output, @{$dat->{'prologprepend'}});
5570 push(@output, "%%EndProlog\n");
5573 return join('', @output);
5576 sub makesetupsection {
5578 # option set to be used, $comments = 1: Add "%%BeginSetup...%%EndSetup"
5579 my ($dat, $optionset, $comments) = @_;
5581 # Collect data to be inserted here
5586 print $logh "\"Setup\" section is missing, inserting it.\n";
5587 push(@output, "%%BeginSetup\n");
5590 # PostScript code to generate accounting messages for CUPS
5591 if ($spooler eq 'cups') {
5592 print $logh "Inserting PostScript code for CUPS' page accounting\n";
5593 push(@output, $accounting_prolog);
5596 # Generate the option code (not necessary when CUPS is spooler)
5597 if ($spooler ne 'cups') {
5598 print $logh "Inserting option code into \"Setup\" section.\n";
5599 buildcommandline ($dat, $optionset);
5600 push(@output, @{$dat->{'setupprepend'}});
5605 push(@output, "%%EndSetup\n");
5608 return join('', @output);
5611 sub makepagesetupsection {
5613 # option set to be used,
5614 # $comments = 1: Add "%%BeginPageSetup...%%EndPageSetup"
5615 my ($dat, $optionset, $comments) = @_;
5617 # Collect data to be inserted here
5622 push(@output, "%%BeginPageSetup\n");
5623 print $logh "\"PageSetup\" section is missing, inserting it.\n";
5626 # Generate the option code (not necessary when CUPS is spooler)
5627 print $logh "Inserting option code into \"PageSetup\" section.\n";
5628 buildcommandline ($dat, $optionset);
5629 if ($spooler ne 'cups') {
5630 push(@output, @{$dat->{'pagesetupprepend'}});
5632 push(@output, @{$dat->{'cupspagesetupprepend'}});
5637 push(@output, "%%EndPageSetup\n");
5640 return join('', @output);
5643 sub parsepageranges {
5645 ## Parse a string containing page ranges and either check whether a
5646 ## given page is in the ranges or, if the given page number is zero,
5647 ## determine the score how specific this page range string is.
5649 # String with page ranges and number of current page (0 for score)
5650 my ($ranges, $page) = @_;
5652 my $currentnumber = 0;
5654 my $currentkeyword = '';
5655 my $invalidrange = 0;
5658 my $currentrange = '';
5660 my $evaluaterange = sub {
5661 # evaluate the current range: determine its score and whether the
5662 # current page is member of it.
5663 if ($invalidrange) {
5664 # Range is invalid, issue a warning
5665 print $logh " Invalid range: $currentrange\n";
5667 # We have a valid range, evaluate it
5668 if ($currentkeyword) {
5669 if ($currentkeyword =~ /^even/i) {
5670 # All even-numbered pages
5671 $totalscore += 50000;
5672 $pageinside = 1 if (($page % 2) == 0);
5673 } elsif ($currentkeyword =~ /^odd/i) {
5674 # All odd-numbered pages
5675 $totalscore += 50000;
5676 $pageinside = 1 if (($page % 2) == 1);
5679 print $logh " Invalid range: $currentrange\n";
5681 } elsif (($rangestart == 0) && ($currentnumber > 0)) {
5682 # Page range is a single page
5684 $pageinside = 1 if ($page == $currentnumber);
5685 } elsif (($rangestart > 0) && ($currentnumber > 0)) {
5686 # Page range is a sequence of pages
5687 $totalscore += (abs($currentnumber - $rangestart) + 1);
5688 if ($currentnumber < $rangestart) {
5689 my $tmp = $currentnumber;
5690 $currentnumber = $rangestart;
5693 $pageinside = 1 if (($page <= $currentnumber) &&
5694 ($page >= $rangestart));
5695 } elsif ($rangestart > 0) {
5696 # Page range goes to the end of the document
5697 $totalscore += 100000;
5698 $pageinside = 1 if ($page >= $rangestart);
5701 print $logh " Invalid range: $currentrange\n";
5704 # Range is evaluated, remove all recordings of the current range
5707 $currentkeyword = '';
5712 for (my $i = 0; $i < length($ranges); $i ++) {
5713 my $c = substr($ranges, $i, 1);
5714 if (!$invalidrange) {
5717 if ($currentkeyword) {
5719 $currentkeyword .= $c;
5721 # Build a page number
5722 $currentnumber *= 10;
5723 $currentnumber += $c;
5725 } elsif ($c =~ /[a-z_]/i) {
5726 # Letter or underscore
5727 if (($rangestart > 0) || ($currentnumber > 0)) {
5728 # Keyword not allowed after a page number or a
5733 $currentkeyword .= $c;
5735 } elsif ($c eq '-') {
5737 if (($rangestart > 0) || ($currentkeyword)) {
5738 # Keyword or two '-' not allowed in page range
5741 # Save start of range, reset page number
5742 $rangestart = $currentnumber;
5743 if ($rangestart == 0) {
5754 # Make a string of the current range, for warnings
5755 $currentrange .= $c;
5758 # End of input string
5761 if (($page == 0) || ($pageinside)) {
5768 sub setoptionsforpage {
5770 ## Set the options for a given page
5772 # Foomatic data, name of the option set where to apply the options, and
5773 # number of the page
5774 my ($dat, $optionset, $page) = @_;
5777 for my $arg (@{$dat->{'args'}}) {
5779 my $bestscore = 10000000;
5780 for my $key (keys %{$arg}) {
5781 next if $key !~ /^pages:(.*)$/;
5782 my $pageranges = $1;
5783 if (my $score = parsepageranges($pageranges, $page)) {
5784 if ($score <= $bestscore) {
5785 $bestscore = $score;
5786 $value = $arg->{$key};
5791 $arg->{$optionset} = $value;
5796 sub buildcommandline {
5798 ## Build a renderer command line, based on the given option set
5800 # Foomatic data and name of the option set to apply
5801 my ($dat, $optionset) = @_;
5803 # Construct the proper command line.
5804 $dat->{'currentcmd'} = $dat->{'cmd'};
5807 my @pagesetupprepend;
5808 my @cupspagesetupprepend;
5812 # At first search for composite options and determine how they
5813 # set their member options
5814 for my $arg (@{$dat->{'args'}}) { $arg->{'order'} = 0 if !defined $arg->{'order'}; }
5815 for my $arg (sort { $a->{'order'} <=> $b->{'order'} }
5816 @{$dat->{'args'}}) {
5818 # Here we are only interested in composite options, skip the others
5819 next if $arg->{'style'} ne 'X';
5821 my $name = $arg->{'name'};
5822 # Check whether this composite option is controlled by another
5823 # composite option, so nested composite options are possible.
5824 my $userval = ($arg->{'fromcomposite'} ?
5825 $arg->{'fromcomposite'} : $arg->{$optionset});
5827 # Get the current setting
5828 my $v = $arg->{'vals_byname'}{$userval};
5829 my @settings = split(/\s+/s, $v->{'driverval'});
5830 for my $s (@settings) {
5832 if ($s =~ /^([^=]+)=(.+)$/) {
5835 } elsif ($s =~ /^no([^=]+)$/) {
5838 } elsif ($s =~ /^([^=]+)$/) {
5842 $a = $dat->{'args_byname'}{$key};
5843 if ($a->{$optionset} eq "From$name") {
5844 # We must set this option according to the
5846 $a->{'fromcomposite'} = $value;
5847 # Mark the option telling by which composite option
5849 $a->{'controlledby'} = $name;
5851 $a->{'fromcomposite'} = "";
5854 # Remove PostScript code to be inserted after an appearance of the
5855 # Composite option in the PostScript code.
5856 undef $arg->{'jclsetup'};
5857 undef $arg->{'prolog'};
5858 undef $arg->{'setup'};
5859 undef $arg->{'pagesetup'};
5862 for my $arg (sort { $a->{'order'} <=> $b->{'order'} }
5863 @{$dat->{'args'}}) {
5865 # Composite options have no direct influence on the command
5866 # line, skip them here
5867 next if $arg->{'style'} eq 'X';
5869 my $name = $arg->{'name'};
5870 my $spot = $arg->{'spot'};
5871 my $cmd = $arg->{'proto'};
5872 my $cmdf = $arg->{'protof'};
5873 my $type = ($arg->{'type'} || "");
5874 my $section = $arg->{'section'};
5875 my $userval = ($arg->{'fromcomposite'} ?
5876 $arg->{'fromcomposite'} : $arg->{$optionset});
5879 # If we have both "PageSize" and "PageRegion" options, we kept
5880 # them all the time in sync, so we don't need to insert the settings
5881 # of both options. So skip "PageRegion".
5882 next if (($name eq "PageRegion") &&
5883 (defined($dat->{'args_byname'}{'PageSize'})) &&
5884 (defined($dat->{'args_byname'}{'PageRegion'})));
5886 # Build the command line snippet/PostScript/JCL code for the current
5888 if ($type eq 'bool') {
5890 # If true, stick the proto into the command line, if false
5891 # and we have a proto for false, stick that in
5892 if (defined($userval) && $userval == 1) {
5899 } elsif ($type eq 'int' or $type eq 'float') {
5901 # If defined, process the proto and stick the result into
5902 # the command line or postscript queue.
5903 if (defined($userval)) {
5904 my $min = $arg->{'min'};
5905 my $max = $arg->{'max'};
5906 # We have already range-checked, correct only
5907 # floating point inaccuricies here
5908 if ($userval < $min) {
5911 if ($userval > $max) {
5914 my $sprintfcmd = $cmd;
5915 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
5916 $cmdvar = sprintf($sprintfcmd,
5918 ? sprintf("%d", $userval)
5919 : sprintf("%f", $userval)));
5924 } elsif ($type eq 'enum') {
5926 # If defined, stick the selected value into the proto and
5927 # thence into the commandline
5928 if (defined($userval)) {
5929 # CUPS assumes that options with the choices "Yes", "No",
5930 # "On", "Off", "True", or "False" are boolean options and
5931 # maps "-o Option=On" to "-o Option" and "-o Option=Off"
5932 # to "-o noOption", which foomatic-rip maps to "0" and "1".
5933 # So when "0" or "1" is unavailable in the option, we try
5934 # "Yes", "No", "On", "Off", "True", and "False".
5937 if ($val=valbyname($arg,$userval)) {
5939 } elsif ($userval =~ /^Custom\.[\d\.]+x[\d\.]+[A-Za-z]*$/) {
5941 $val = valbyname($arg,"Custom");
5943 } elsif ($userval =~ /^(0|No|Off|False)$/i) {
5944 foreach (qw(0 No Off False None)) {
5945 if ($val=valbyname($arg,$_)) {
5947 $arg->{$optionset} = $userval;
5952 } elsif ($userval =~ /^(1|Yes|On|True)$/i) {
5953 foreach (qw(1 Yes On True)) {
5954 if ($val=valbyname($arg,$_)) {
5956 $arg->{$optionset} = $userval;
5961 } elsif ($userval =~ /^(LongEdge|DuplexNoTumble)$/i) {
5962 # Handle different names for the choices of the
5964 foreach (qw(LongEdge DuplexNoTumble)) {
5965 if ($val=valbyname($arg,$_)) {
5967 $arg->{$optionset} = $userval;
5972 } elsif ($userval =~ /^(ShortEdge|DuplexTumble)$/i) {
5973 foreach (qw(ShortEdge DuplexTumble)) {
5974 if ($val=valbyname($arg,$_)) {
5976 $arg->{$optionset} = $userval;
5983 my $sprintfcmd = $cmd;
5984 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
5985 $cmdvar = sprintf($sprintfcmd,
5986 (defined($val->{'driverval'})
5987 ? $val->{'driverval'}
5988 : $val->{'value'}));
5990 if ($userval =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/) {
5994 # convert width and height to PostScript points
5995 if (lc($unit) eq "in") {
5998 } elsif (lc($unit) eq "cm") {
5999 $width *= (72.0/2.54);
6000 $height *= (72.0/2.54);
6001 } elsif (lc($unit) eq "mm") {
6002 $width *= (72.0/25.4);
6003 $height *= (72.0/25.4);
6005 # Round width and height
6006 $width =~ s/\.[0-4].*$// or
6007 $width =~ s/\.[5-9].*$// and $width += 1;
6008 $height =~ s/\.[0-4].*$// or
6009 $height =~ s/\.[5-9].*$// and $height += 1;
6010 # Insert width and height into the prototype
6011 if ($cmdvar =~ /^\s*pop\W/s) {
6012 # Custom page size for PostScript printers
6013 $cmdvar = "$width $height 0 0 0\n$cmdvar";
6015 # Custom page size for Foomatic/Gutenprint/
6017 $cmdvar =~ s/\%0/$width/ or
6018 $cmdvar =~ s/(\W)0(\W)/$1$width$2/ or
6019 $cmdvar =~ s/^0(\W)/$width$1/m or
6020 $cmdvar =~ s/(\W)0$/$1$width/m or
6021 $cmdvar =~ s/^0$/$width/m;
6022 $cmdvar =~ s/\%1/$height/ or
6023 $cmdvar =~ s/(\W)0(\W)/$1$height$2/ or
6024 $cmdvar =~ s/^0(\W)/$height$1/m or
6025 $cmdvar =~ s/(\W)0$/$1$height/m or
6026 $cmdvar =~ s/^0$/$height/m;
6030 # User gave unknown value?
6032 print $logh "Value $userval for $name is not a valid choice.\n";
6038 } elsif (($type eq 'string') || ($type eq 'password')) {
6039 # Stick the entered value into the proto and
6040 # thence into the commandline
6041 if (defined($userval)) {
6043 if ($val=valbyname($arg,$userval)) {
6044 $userval = $val->{'value'};
6045 $cmdvar = (defined($val->{'driverval'})
6046 ? $val->{'driverval'}
6049 my $sprintfcmd = $cmd;
6050 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
6051 $cmdvar = sprintf($sprintfcmd, $userval);
6058 # Ignore unknown option types silently
6061 # Insert the built snippet at the correct place
6062 if ($arg->{'style'} eq 'G') {
6063 # Place this Postscript command onto the prepend queue
6064 # for the appropriate section.
6066 my $open = "[{\n%%BeginFeature: *$name ";
6067 if ($type eq 'bool') {
6068 $open .= ($userval == 1 ? "True" : "False") . "\n";
6070 $open .= "$userval\n";
6072 my $close = "\n%%EndFeature\n} stopped cleartomark\n";
6073 if ($section eq "Prolog") {
6074 push (@prologprepend, "$open$cmdvar$close");
6076 while ($a->{'controlledby'}) {
6077 # Collect option PostScript code to be inserted when
6078 # the composite option which controls this option
6079 # is found in the PostScript code
6080 $a = $dat->{'args_byname'}{$a->{'controlledby'}};
6081 $a->{'prolog'} .= "$cmdvar\n";
6083 } elsif ($section eq "AnySetup") {
6084 if ($optionset ne 'currentpage') {
6085 push (@setupprepend, "$open$cmdvar$close");
6086 } elsif ($arg->{'header'} ne $userval) {
6087 push (@pagesetupprepend, "$open$cmdvar$close");
6088 push (@cupspagesetupprepend, "$open$cmdvar$close");
6091 while ($a->{'controlledby'}) {
6092 # Collect option PostScript code to be inserted when
6093 # the composite option which controls this option
6094 # is found in the PostScript code
6095 $a = $dat->{'args_byname'}{$a->{'controlledby'}};
6096 $a->{'setup'} .= "$cmdvar\n";
6097 $a->{'pagesetup'} .= "$cmdvar\n";
6099 } elsif ($section eq "DocumentSetup") {
6100 push (@setupprepend, "$open$cmdvar$close");
6102 while ($a->{'controlledby'}) {
6103 # Collect option PostScript code to be inserted when
6104 # the composite option which controls this option
6105 # is found in the PostScript code
6106 $a = $dat->{'args_byname'}{$a->{'controlledby'}};
6107 $a->{'setup'} .= "$cmdvar\n";
6109 } elsif ($section eq "PageSetup") {
6110 push (@pagesetupprepend, "$open$cmdvar$close");
6112 while ($a->{'controlledby'}) {
6113 # Collect option PostScript code to be inserted when
6114 # the composite option which controls this option
6115 # is found in the PostScript code
6116 $a = $dat->{'args_byname'}{$a->{'controlledby'}};
6117 $a->{'pagesetup'} .= "$cmdvar\n";
6119 } elsif ($section eq "JCLSetup") {
6122 push (@jclprepend, unhexify($cmdvar));
6124 while ($a->{'controlledby'}) {
6125 # Collect option PostScript code to be inserted when
6126 # the composite option which controls this option
6127 # is found in the PostScript code
6128 $a = $dat->{'args_byname'}{$a->{'controlledby'}};
6129 $a->{'jclsetup'} .= "$cmdvar\n";
6132 push (@setupprepend, "$open$cmdvar$close");
6134 while ($a->{'controlledby'}) {
6135 # Collect option PostScript code to be inserted when
6136 # the composite option which controls this option
6137 # is found in the PostScript code
6138 $a = $dat->{'args_byname'}{$a->{'controlledby'}};
6139 $a->{'setup'} .= "$cmdvar\n";
6143 # Do we have an option which is set to "Controlled by
6144 # '<Composite>'"? Then make PostScript code available
6145 # for substitution of "%% FoomaticRIPOptionSetting: ..."
6146 if ($arg->{'fromcomposite'}) {
6147 $arg->{'compositesubst'} = "$cmdvar\n";
6149 } elsif ($arg->{'style'} eq 'J') {
6152 # put JCL commands onto JCL stack...
6153 push (@jclprepend, "$jclprefix$cmdvar\n") if $cmdvar;
6154 } elsif ($arg->{'style'} eq 'C') {
6155 # command-line argument
6157 # Insert the processed argument in the commandline
6158 # just before every occurance of the spot marker.
6159 $dat->{'currentcmd'} =~ s!\%$spot!$cmdvar\%$spot!g;
6161 # Insert option into command line of CUPS raster driver
6162 if ($dat->{'currentcmd'} =~ m!\%Y!) {
6163 next if !defined($userval) or $userval eq "";
6164 $dat->{'currentcmd'} =~ s!\%Y!$name=$userval \%Y!g;
6166 # Remove the marks telling that this option is currently controlled
6167 # by a composite option (setting "From<composite>")
6168 undef $arg->{'fromcomposite'};
6169 undef $arg->{'controlledby'};
6173 ### Tidy up after computing option statements for all of P, J, and
6177 # Pluck out all of the %n's from the command line prototype
6178 my @letters = qw/A B C D E F G H I J K L M W X Y Z/;
6179 for my $spot (@letters) {
6180 # Remove the letter markers from the commandline
6181 $dat->{'currentcmd'} =~ s!\%$spot!!g;
6185 # Compute the proper stuff to say around the job
6187 if ((defined($dat->{'jcl'})) && (!$jobhasjcl)) {
6189 # Stick beginning of job cruft on the front of the jcl stuff...
6190 unshift (@jclprepend, $jclbegin);
6192 # Command to switch to the interpreter
6193 push (@jclprepend, $jcltointerpreter);
6195 # Arrange for JCL RESET command at end of job
6196 push (@jclappend, $jclend);
6198 # Put the JCL stuff into the data structure
6199 @{$dat->{'jclprepend'}} = @jclprepend;
6200 @{$dat->{'jclappend'}} = @jclappend;
6204 # Save PostScript options
6205 @{$dat->{'prologprepend'}} = @prologprepend;
6206 @{$dat->{'setupprepend'}} = @setupprepend;
6207 @{$dat->{'pagesetupprepend'}} = @pagesetupprepend;
6208 @{$dat->{'cupspagesetupprepend'}} = @cupspagesetupprepend;
6211 sub buildpdqdriver {
6213 # Build a PDQ driver description file to use the given PPD file
6214 # together with foomatic-rip with the PDQ printing system
6216 # Foomatic data and name of the option set for the default settings
6217 my ($dat, $optionset) = @_;
6219 # Construct structure with driver information
6222 # Construct option list
6223 my @driveropts = ();
6225 # Do we have a "Custom" setting for the page size?
6226 # Then we have to insert the following into the "filter_exec" script.
6227 my @setcustompagesize = ();
6229 # Fata for a custom page size, to allow a custom size as default
6230 my $pagewidth = 612;
6231 my $pageheight = 792;
6232 my $pageunit = "pt";
6236 ## First, compute the various option/value clauses
6237 for my $arg (@{$dat->{'args'}}) {
6239 if ($arg->{'type'} eq "enum") {
6241 # Option with only one choice, omit it, foomatic-rip will set
6242 # this choice anyway.
6243 next if ($#{$arg->{'vals'}} < 1);
6245 my $nam = $arg->{'name'};
6247 # Omit "PageRegion" option, it does the same as "PageSize".
6248 next if $nam eq "PageRegion";
6250 my $com = $arg->{'comment'};
6252 # Assure that the comment is not empty
6257 my $def = $arg->{$optionset};
6258 $arg->{'varname'} = "$nam";
6259 $arg->{'varname'} =~ s![\-\/\.]!\_!g;
6260 my $varn = $arg->{'varname'};
6262 # 1, if setting "PageSize=Custom" was found
6263 # Then we must add options for page width and height
6264 my $custompagesize = 0;
6266 # If the default is a custom size we have to set also
6267 # defaults for the width, height, and units of the page
6268 if (($nam eq "PageSize") &&
6269 ($def =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/)) {
6276 # No quotes, thank you.
6277 $com =~ s!\"!\\\"!g;
6281 " var = \"$varn\"\n",
6282 " desc = \"$com\"\n");
6284 # get enumeration values for each enum arg
6285 my ($ev, @vals, @valstmp);
6286 for $ev (@{$arg->{'vals'}}) {
6287 my $choiceshortname = $ev->{'value'};
6288 my $choicename = "${nam}_${choiceshortname}";
6289 my $val = " -o ${nam}=${choiceshortname}";
6290 my $com = $ev->{'comment'};
6292 # Assure that the comment is not empty
6294 $com = $choiceshortname;
6297 # stick another choice on driveropts
6299 " choice \"$choicename\" {\n",
6300 " desc = \"$com\"\n",
6301 " value = \"$val\"\n",
6303 if (($nam eq "PageSize") &&
6304 ($choiceshortname eq "Custom")) {
6305 $custompagesize = 1;
6306 if ($#setcustompagesize < 0) {
6307 push(@setcustompagesize,
6308 " # Custom page size settings\n",
6309 " # We aren't really checking for " .
6311 " if [ \"x\${$varn}\" = 'x$val' ]; " .
6313 " $varn=\"\${$varn}.\${PageWidth}" .
6314 "x\${PageHeight}\${PageSizeUnit}\"\n",
6321 " default_choice \"" . $nam . "_" . $def . "\"\n",
6325 if ($custompagesize) {
6326 # Add options to set the custom page size
6329 " var = \"PageWidth\"\n",
6330 " desc = \"Page Width (for \\\"Custom\\\" page " .
6332 " def_value \"$pagewidth\"\n",
6333 " help = \"Minimum value: 0, Maximum value: " .
6337 " var = \"PageHeight\"\n",
6338 " desc = \"Page Height (for \\\"Custom\\\" page " .
6340 " def_value \"$pageheight\"\n",
6341 " help = \"Minimum value: 0, Maximum value: " .
6345 " var = \"PageSizeUnit\"\n",
6346 " desc = \"Unit (for \\\"Custom\\\" page size)\"\n",
6347 " default_choice \"PageSizeUnit_$pageunit\"\n",
6348 " choice \"PageSizeUnit_pt\" {\n",
6349 " desc = \"Points (1/72 inch)\"\n",
6350 " value = \"pt\"\n",
6352 " choice \"PageSizeUnit_in\" {\n",
6353 " desc = \"Inches\"\n",
6354 " value = \"in\"\n",
6356 " choice \"PageSizeUnit_cm\" {\n",
6358 " value = \"cm\"\n",
6360 " choice \"PageSizeUnit_mm\" {\n",
6362 " value = \"mm\"\n",
6367 } elsif ($arg->{'type'} eq 'int' or $arg->{'type'} eq 'float') {
6369 my $nam = $arg->{'name'};
6370 my $com = $arg->{'comment'};
6372 # Assure that the comment is not empty
6377 my $def = $arg->{$optionset};
6378 my $max = $arg->{'max'};
6379 my $min = $arg->{'min'};
6380 $arg->{'varname'} = "$nam";
6381 $arg->{'varname'} =~ s![\-\/\.]!\_!g;
6382 my $varn = $arg->{'varname'};
6383 my $legal = $arg->{'legal'} =
6384 "Minimum value: $min, Maximum value: $max";
6388 $defstr = sprintf(" def_value \"%s\"\n", $def);
6393 " var = \"$varn\"\n",
6394 " desc = \"$com\"\n",
6396 " help = \"$legal\"\n",
6399 } elsif ($arg->{'type'} eq 'bool') {
6401 my $nam = $arg->{'name'};
6402 my $com = $arg->{'comment'};
6404 # Assure that the comment is not empty
6409 my $tcom = $arg->{'comment_true'};
6410 my $fcom = $arg->{'comment_false'};
6411 my $def = $arg->{$optionset};
6412 $arg->{'legal'} = "Value is a boolean flag";
6413 $arg->{'varname'} = "$nam";
6414 $arg->{'varname'} =~ s![\-\/\.]!\_!g;
6415 my $varn = $arg->{'varname'};
6419 $defstr = sprintf(" default_choice \"%s\"\n",
6420 $def ? "$nam" : "no$nam");
6422 $defstr = sprintf(" default_choice \"%s\"\n", "no$nam");
6426 " var = \"$varn\"\n",
6427 " desc = \"$com\"\n",
6429 " choice \"$nam\" {\n",
6430 " desc = \"$tcom\"\n",
6431 " value = \" -o $nam=True\"\n",
6433 " choice \"no$nam\" {\n",
6434 " desc = \"$fcom\"\n",
6435 " value = \" -o $nam=False\"\n",
6439 } elsif ($arg->{'type'} eq 'string' or $arg->{'type'} eq 'password') {
6441 my $nam = $arg->{'name'};
6442 my $com = $arg->{'comment'};
6444 # Assure that the comment is not empty
6449 my $def = $arg->{$optionset};
6450 my $maxlength = $arg->{'maxlength'};
6451 my $proto = $arg->{'proto'};
6452 $arg->{'varname'} = "$nam";
6453 $arg->{'varname'} =~ s![\-\/\.]!\_!g;
6454 my $varn = $arg->{'varname'};
6457 if (defined($maxlength)) {
6458 $legal .= "Maximum length: $maxlength characters, ";
6460 $legal .= "Examples/special settings: ";
6461 for (@{$arg->{'vals'}}) {
6462 my ($value, $comment, $driverval) =
6463 ($_->{'value'}, $_->{'comment'}, $_->{'driverval'});
6464 # Retrieve the original string from the prototype
6468 my $s = index($proto, '%s');
6469 my $l = length($driverval) - length($proto) + 2;
6470 if (($s < 0) || ($l < 0)) {
6471 $string = $driverval;
6473 $string = substr($driverval, $s, $l);
6476 $string = $driverval;
6478 if ($value ne $string) {
6479 $legal .= "${value}: \\\"$string\\\"";
6481 $legal .= "\\\"$value\\\"";
6483 if ($comment && ($value ne $comment) &&
6484 ($string ne $comment) &&
6485 (($value ne 'None') || ($comment ne '(None)'))) {
6486 $legal .= " ($comment)";
6492 $arg->{'legal'} = $legal;
6496 $defstr = sprintf(" def_value \"%s\"\n", $def);
6501 " var = \"$varn\"\n",
6502 " desc = \"$com\"\n",
6504 " help = \"$legal\"\n",
6513 ## Define the "docs" option to print the driver documentation page
6517 " var = \"DRIVERDOCS\"\n",
6518 " desc = \"Print driver usage information\"\n",
6519 " default_choice \"nodocs\"\n",
6520 " choice \"docs\" {\n",
6521 " desc = \"Yes\"\n",
6522 " value = \" -o docs\"\n",
6524 " choice \"nodocs\" {\n",
6532 ## Build the "foomatic-rip" command line
6533 my $commandline = "foomatic-rip --pdq";
6535 $commandline .= " -P $printer";
6537 # Make sure that the PPD file is entered with an absolute path
6538 if ($ppdfile !~ m!^/!) {
6540 $ppdfile = "$pwd/$ppdfile";
6542 $commandline .= " --ppd=$ppdfile";
6544 for my $arg (@{$dat->{'args'}}) {
6545 if ($arg->{'varname'}) {
6546 $commandline .= "\${$arg->{'varname'}}";
6549 $commandline .= "\${DRIVERDOCS} \$INPUT > \$OUTPUT";
6553 ## Now we generate code to build the command line snippets for the
6554 ## numerical options
6557 for my $arg (@{$dat->{'args'}}) {
6559 # Only numerical and string options need to be treated here
6560 next if (($arg->{'type'} ne 'int') &&
6561 ($arg->{'type'} ne 'float') &&
6562 ($arg->{'type'} ne 'string') &&
6563 ($arg->{'type'} ne 'password'));
6565 my $comment = $arg->{'comment'};
6566 my $name = $arg->{'name'};
6567 my $varname = $arg->{'varname'};
6569 # If the option's variable is non-null, put in the
6570 # argument. Otherwise this option is the empty
6571 # string. Error checking?
6575 (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ?
6576 (" # We aren't really checking for max/min,\n",
6577 " # this is done by foomatic-rip\n",
6578 " if [ \"x\${$varname}\" != 'x' ]; then\n ") : ""),
6579 #" $varname=`echo \${$varname} | perl -p -e \"s/'/'\\\\\\\\\\\\\\\\''/g\"`\n",
6580 " $varname=\" -o $name='\${$varname}'\"\n",
6581 (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ?
6589 " if ! test -e \$INPUT.ok; then\n",
6590 " sh -c \"$commandline\"\n",
6591 " if ! test -e \$OUTPUT; then \n",
6592 " echo 'Error running foomatic-rip; no output!'\n",
6596 " ln -s \$INPUT \$OUTPUT\n",
6599 my $version = time();
6600 my $name = "$model-$version";
6602 $name =~ s/\-+/\-/g;
6607 "driver \"$name\" {\n\n",
6608 " # This PDQ driver declaration file was generated " .
6609 "automatically by\n",
6610 " # foomatic-rip from information in the file $ppdfile.\n",
6611 " # It allows printing with PDQ on the $pname.\n",
6613 " requires \"foomatic-rip\"\n\n",
6615 " language_driver all {\n",
6616 " # We accept all file types and pass them to foomatic-rip\n",
6617 " # (invoked in \"filter_exec {}\" section) without\n",
6618 " # pre-filtering\n",
6619 " filetype_regx \"\"\n",
6620 " convert_exec {\n",
6621 " ln -s \$INPUT \$OUTPUT\n",
6635 # Convert lp or ipp based attribute names (and values) to something that matches# PPD file options.
6638 my ($ipp_attribute) = @_;
6639 my ($key, $value, $result) = ();
6641 if (/([^=]+)=[\'\"]?(.*}[\'\"]?)/) { # key=value
6642 ($key, $value) = ($1, $2);
6643 } elsif (/no(.+)/) { # BOOLEAN: no{key} (false)
6644 ($key, $value) = ($1, 'false');
6645 } else { # BOOLEAN: {key} (true)
6646 ($key, $value) = ($1, 'true');
6649 if (($key =~ /^job-/) || ($key =~ /^copies/) ||
6650 ($key =~ /^multiple-document-handling/) || ($key =~ /^number-up/) ||
6651 ($key =~ /^orientation-requested/) ||
6652 ($key =~ /^dest/) || ($key =~ /^protocol/) || ($key =~ /^banner/) ||
6653 ($key =~ /^page-ranges/)) {
6655 # job-*, multiple-document-handling are not supported by this
6657 # dest, protocol, banner, number-up, orientation-requested are
6658 # handled by the LP filtering or interface script
6659 # NOTE - page-ranges should probably be handled here, but
6660 # ignore it until we decide how to handle it.
6661 } elsif (/^printer-resolution/) {
6662 # value match on "123, 457" or on "123, 457, 8"
6663 if (/([\d]+),([\s]*)([\d]+)((,([\s]*)([\d]+))??)/) {
6664 $result = '$1x$2$3 '; # (width)x(height)(units)
6666 } elsif (/^print-quality/) {
6668 ($result = 'PrintoutMode=Draft');
6670 ($result = 'PrintoutMode=Normal');
6672 ($result = 'PrintoutMode=High');
6674 # NOTE - if key == 'media', we may need to convert the values at some
6675 # point. (see RFC2911, Section 14 for values)
6676 $result = '$key=\"$value\"';
6683 # Read the attributes file containing the various job meta-data, including
6684 # requested capabilities
6686 sub read_attribute_file {
6690 open (AFP, "<$file") ||
6691 (print $logh "Unable to open IPP Attribute file ".$file.", ignored: ".$!);
6694 $result .= option_to_ppd($_);
6705 if ($modern_shell |~ /.+/) {
6706 # No "modern" shell other than the default shell was specified
6707 $modern_shell = '/bin/sh';
6711 ($pid < 0) && die "failed to fork()";
6713 if ($pid == 0) { # child, execute the commands under a modern shell
6714 # If the system supports process groups, we create a process
6715 # group of this subshell process. All the children of this
6716 # process (calls of external filters, renderers, or drivers)
6717 # will be members of this process group and so by killing this
6718 # process group we can kill all subprocesses and so we can
6719 # cleanly cancel print jobs
6720 $SIG{PIPE} = 'DEFAULT';
6722 # Stop catching signals
6723 #use sigtrap qw(die normal-signals error-signals
6724 # handler do_nothing USR1 USR2 TTIN);
6725 exec($modern_shell, "-c", @list);
6726 rip_die("exec($modern_shell, \"-c\", @list);",
6727 $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
6728 } else { # parent, register child's PID, wait for the child, and
6729 # unregister the PID
6730 $pids{$pid} = substr(join(" ", @list), 0, 100) .
6731 (length(join(" ", @list)) > 100 ? "..." : "");
6732 print $logh "Starting process $pid: \"$pids{$pid}\"\n";
6734 print $logh "Process $pid ending: \"$pids{$pid}\"\n";
6739 # Emacs tabulator/indentation
6741 ### Local Variables:
6743 ### perl-indent-level: 4