summaryrefslogtreecommitdiff
path: root/foomatic-rip.in
diff options
context:
space:
mode:
Diffstat (limited to 'foomatic-rip.in')
-rwxr-xr-xfoomatic-rip.in6310
1 files changed, 6310 insertions, 0 deletions
diff --git a/foomatic-rip.in b/foomatic-rip.in
new file mode 100755
index 0000000..177b372
--- /dev/null
+++ b/foomatic-rip.in
@@ -0,0 +1,6310 @@
+#!@PERL@
+# The above Perl path may vary on your system; fix it!!! -*- perl -*-
+
+use strict;
+use POSIX;
+use Cwd;
+
+my $ripversion='$Revision: 3.43.2.9 $';
+#'# Fix emacs syntax highlighting
+
+# foomatic-rip is a spooler-independent filter script which takes
+# PostScript as standard input and generates the printer's page
+# description language (PDL)/raster format as standard output. This
+# kind of filter is usually called Raster Image Processor (RIP),
+# therefore the name "foomatic-rip".
+
+# Save it in one of the directories of your $PATH, so that it gets
+# found when called from the command line (for spooler-less printing),
+# link it to spooler-specific directories when you use CUPS or PPR:
+
+# ln -s /usr/bin/foomatic-rip /usr/lib/cups/filter/
+# ln -s /usr/bin/foomatic-rip /usr/lib/ppr/lib/
+# ln -s /usr/bin/foomatic-rip /usr/lib/ppr/interfaces/
+
+# Mark this filter world-readable and world-executable (note that most
+# spoolers run the print filters as a special user, as "lp", not as
+# "root" or as the user who sent the job).
+
+# See http://www.linuxprinting.org/cups-doc.html
+# http://www.linuxprinting.org/lpd-doc.html
+# http://www.linuxprinting.org/ppr-doc.html
+# http://www.linuxprinting.org/pdq-doc.html
+# http://www.linuxprinting.org/direct-doc.html
+# http://www.linuxprinting.org/ppd-doc.html
+
+# ==========================================================================
+#
+# User-configurable settings, edit them if needed
+#
+# ==========================================================================
+
+# What path to use for filter programs and such. Your printer driver
+# must be in the path, as must be the renderer, $enscriptcommand, and
+# possibly other stuff. The default path is often fine on Linux, but
+# may not be on other systems.
+#
+my $execpath = "@prefix@/bin:/usr/local/bin:/usr/bin:/bin";
+
+# CUPS raster drivers are searched here
+my $cupsfilterpath = "@prefix@/lib/cups/filter:/usr/local/lib/cups/filter:/usr/local/libexec/cups/filter:/opt/cups/filter:/usr/lib/cups/filter";
+
+# Location of the configuration file "filter.conf", this file can be
+# used to change the settings of foomatic-rip without editing
+# foomatic-rip. itself. This variable must contain the full pathname
+# of the directory which contains the configuration file, usually
+# "/etc/foomatic".
+# Some versions of configure do not fully expand $sysconfdir
+my $prefix = "@prefix@";
+my $configpath = "@sysconfdir@/foomatic";
+
+# For the stuff below, the settings in the configuration file have priority.
+
+# Set to 1 to insert postscript code for page accounting (CUPS only).
+my $ps_accounting = 1;
+my $accounting_prolog = "";
+
+# Enter here your personal command for converting non-postscript files
+# (especially text) to PostScript. If you leave it blank, at first the
+# line "textfilter: ..." from /etc/foomatic/filter.conf is read and
+# then the commands given on the list below are tried, beginning with
+# the first one.
+# You can set this to "a2ps", "enscript" or "mpage" to select one of the
+# default command strings.
+my $fileconverter = "";
+
+my($kid0,$kid1,$kid2,$kid3,$kid4);
+my($kidfailed,$kid3finished,$kid4finished);
+my($convkidfailed,$dockidfailed,$kid0finished,$kid1finished,$kid2finished);
+my($fileconverterpid,$rendererpid,$fileconverterhandle,$rendererhandle);
+my($jobhasjcl);
+
+# What 'echo' program to use. It needs -e and -n. Linux's builtin
+# and regular echo work fine; non-GNU platforms may need to install
+# gnu echo and put gecho here or something.
+#
+my $myecho = 'echo';
+
+# Set debug to 1 to enable the debug logfile for this filter; it will
+# appear as defined by $logfile. It will contain status from this
+# filter, plus the renderer's stderr output. You can also add a line
+# "debug: 1" to your /etc/foomatic/filter.conf to get all your
+# Foomatic filters into debug mode.
+#
+# WARNING: This logfile is a security hole; do not use in production.
+my $debug = 0;
+
+# This is the location of the debug logfile (and also the copy of the
+# processed PostScript data) in case you have enabled debugging above.
+# The logfile will get the extension ".log", the PostScript data ".ps".
+my $logfile = "/tmp/foomatic-rip";
+
+# End interesting enduser options
+
+# ==========================================================================
+#
+# foomatic-rip spooler-independent PS->Printer filter (RIP) of Foomatic
+#
+# Copyright 2002 - 2004 Grant Taylor <gtaylor@picante.com>
+# & Till Kamppeter <till.kamppeter@gmx.net>
+# & Helge Blischke <h.blischke@srz.de>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+# USA.
+#
+
+my $added_lf = "\n";
+
+# Flush everything immediately.
+$|=1;
+
+
+
+## Constants used by this filter
+
+# Error codes, as some spooles behave different depending on the reason why
+# the RIP failed, we return an error code. As I have only found a table of
+# error codes for the PPR spooler. If our spooler is really PPR, these
+# definitions get overwritten by the ones of the PPR version currently in
+# use.
+
+my $EXIT_PRINTED = 0; # file was printed normally
+my $EXIT_PRNERR = 1; # printer error occured
+my $EXIT_PRNERR_NORETRY = 2; # printer error with no hope of retry
+my $EXIT_JOBERR = 3; # job is defective
+my $EXIT_SIGNAL = 4; # terminated after catching signal
+my $EXIT_ENGAGED = 5; # printer is otherwise engaged (connection
+ # refused)
+my $EXIT_STARVED = 6; # starved for system resources
+my $EXIT_PRNERR_NORETRY_ACCESS_DENIED = 7; # bad password? bad port
+ # permissions?
+my $EXIT_PRNERR_NOT_RESPONDING = 8; # just doesn't answer at all
+ # (turned off?)
+my $EXIT_PRNERR_NORETRY_BAD_SETTINGS = 9; # interface settings are invalid
+my $EXIT_PRNERR_NO_SUCH_ADDRESS = 10; # address lookup failed, may be
+ # transient
+my $EXIT_PRNERR_NORETRY_NO_SUCH_ADDRESS = 11; # address lookup failed, not
+ # transient
+my $EXIT_INCAPABLE = 50; # printer wants (lacks) features
+ # or resources
+# Standard Unix signal names
+#my SIGHUP = 1;
+#my SIGINT = 2;
+#my SIGQUIT = 3;
+#my SIGKILL = 9;
+#my SIGTERM = 15;
+#my SIGUSR1 = 10;
+#my SIGUSR2 = 12;
+#my SIGTTIN = 21;
+#my SIGTTOU = 22;
+
+my $ESPIPE = 29; # the errno value when seeking a pipe or socket
+
+
+
+## Some important variables
+
+# We don't know yet, which spooler will be used. If we don't detect
+# one. we assume that we do spooler-less printing. Supported spoolers
+# are currently:
+
+# cups - CUPS - Common Unix Printing System
+# lpd - LPD - Line Printer Daemon
+# lprng - LPRng - LPR - New Generation
+# gnulpr - GNUlpr, an enhanced LPD (development stopped)
+# ppr - PPR (foomatic-rip runs as a PPR RIP)
+# ppr_int - PPR (foomatic-rip runs as an interface)
+# cps - CPS - Coherent Printing System
+# pdq - PDQ - Print, Don't Queue (development stopped)
+# direct - Direct, spooler-less printing
+
+my $spooler = 'direct';
+
+# PPD file name
+my $ppdfile = "";
+
+# Printer model
+my $model = "";
+
+# Printer queue name
+my $printer = "";
+
+# Printing options
+my $optstr = "";
+
+# Job title
+my $jobtitle = "";
+
+# Post pipe (command into which the output of this filter should be piped)
+my $postpipe = "";
+
+# Files to be printed
+my @filelist = ();
+
+# JCL prefix to put before the JCL options (Can be modified by a
+# "*JCLBegin:" keyword in the PPD file):
+my $jclbegin = "\033%-12345X\@PJL\n";
+
+# JCL command to switch the printer to the PostScript interpreter (Can
+# be modified by a "*JCLToPSInterpreter:" keyword in the PPD file):
+my $jcltointerpreter = "";
+
+# JCL command to close a print job (Can be modified by a "*JCLEnd:"
+# keyword in the PPD file):
+my $jclend = "\033%-12345X\@PJL RESET\n";
+
+# Under which name were we called and in which directory do we reside
+$0 =~ m!^(.*/)([^/]+)$!;
+my $programdir = $1;
+my $programname = $2;
+
+# Filters to convert non-PostScript files
+my @fileconverters =
+ (# a2ps (converts also other files than text)
+ 'a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o -',
+ # enscript
+ 'enscript -G @@-M @@PAGESIZE@@ @@-b "Page $%|@@JOBTITLE@@ ' .
+ '--margins=36:36:36:36 --mark-wrapped-lines=arrow --word-wrap -p-',
+ # mpage
+ 'mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -m36l36b36t36r ' .
+ '-f -P- -');
+
+# spooler-specific file converters, default for the specific spooler when
+# none of the converters above is chosen. Remove weird characters from the
+# command line arguments to enhance security
+my @fixed_args =
+ (defined($ARGV[0])?removespecialchars($ARGV[0]):"",
+ defined($ARGV[1])?removespecialchars($ARGV[1]):"",
+ defined($ARGV[2])?removespecialchars($ARGV[2]):"",
+ defined($ARGV[3])?removespecialchars($ARGV[3]):"",
+ defined($ARGV[4])?removespecialchars($ARGV[4]):"");
+my $spoolerfileconverters = {
+ 'cups' => "${programdir}texttops '$fixed_args[0]' '$fixed_args[1]' '$fixed_args[2]' " .
+ "'$fixed_args[3]' '$fixed_args[4] page-top=36 page-bottom=36 " .
+ "page-left=36 page-right=36 nolandscape cpi=12 lpi=7 " .
+ "columns=1 wrap'"
+ };
+
+## Config file
+
+# Read config file if present
+my %conf = readConfFile("$configpath/filter.conf");
+
+# Get execution path from config file
+$execpath = $conf{execpath} if defined $conf{execpath};
+$ENV{'PATH'} = $execpath;
+
+# Get CUPS filter path from config file
+$cupsfilterpath = $conf{cupsfilterpath} if defined $conf{cupsfilterpath};
+
+# Set debug mode
+$debug = $conf{debug} if defined $conf{debug};
+
+# Determine which filter to use for non-PostScript files to be converted
+# to PostScript
+if (defined $conf{textfilter}) {
+ $fileconverter = $conf{textfilter};
+ $fileconverter eq 'a2ps' and $fileconverter = $fileconverters[0];
+ $fileconverter eq 'enscript' and $fileconverter = $fileconverters[1];
+ $fileconverter eq 'mpage' and $fileconverter = $fileconverters[2];
+}
+
+
+
+## Environment variables;
+
+# "PPD": PPD file name for CUPS or PPR (if we run as PPR RIP)
+if (defined($ENV{'PPD'})) {
+ # Clean the file name from weird characters which could cause
+ # unexpected behaviour
+ $ppdfile = removespecialchars($ENV{'PPD'});
+ # CUPS and PPR (RIP filter) use the "PPD" environment variable to
+ # make the PPD file name available (we set CUPS here preliminarily,
+ # in the next step we check for PPR)
+ $spooler = 'cups';
+}
+
+# "PPR_VERSION": PPR
+if (defined($ENV{'PPR_VERSION'})) {
+ # We have PPR
+ $spooler = 'ppr';
+}
+
+# "PPR_RIPOPTS": PPR
+if (defined($ENV{'PPR_RIPOPTS'})) {
+ # PPR 1.5 allows the user to specify options for the PPR RIP with the
+ # "--ripopts" option on the "ppr" command line. They are provided to
+ # the RIP via the "PPR_RIPOPTS" environment variable.
+ # Clean the option string from weird characters which could cause
+ # unexpected behaviour
+ $optstr .= removespecialchars("$ENV{'PPR_RIPOPTS'} ");
+ # We have PPR
+ $spooler = 'ppr';
+}
+
+# "LPOPTS": Option settings for some LPD implementations (ex: GNUlpr)
+if (defined($ENV{'LPOPTS'})) {
+ my @lpopts = split(/,/, removespecialchars($ENV{'LPOPTS'}));
+ foreach my $opt (@lpopts) {
+ $opt =~ s/^\s+//;
+ $opt =~ s/\s+$//;
+ if ($opt =~ /\s+/) {
+ $opt = "\"$opt\"";
+ }
+ $optstr .= "$opt ";
+ }
+ # We have an LPD which accepts "-o" for options
+ $spooler = 'gnulpr';
+}
+
+
+
+## Named command line options
+
+# We do not use Getopt::Long because it does not work when between the
+# option and the argument is no space ("-w80" instead of "-w 80"). This
+# happens in the command line of LPRng, but also users could type in
+# options this way when printing without spooler.
+
+# Make one option string with a non-printable character as separator,
+# So we can parse it more easily.
+
+# To avoid the separator to be in the options itselves, it is filters
+# out of the options. This does not break anything as having non
+# printable characters in the command line options does not make sense
+# nor is this needed. This way misinterpretation and even abuse is
+# prevented.
+
+my $argstr = "\x01" .
+ join("\x01", map { removeunprintables($_) } @ARGV) . "\x01";
+
+# Debug mode activated via command line
+if ($argstr =~ s/\x01--debug\x01/\x01/) {
+ $debug = 1;
+}
+
+# Command line options for verbosity
+my $verbose = ($argstr =~ s/\x01-v\x01/\x01/);
+my $quiet = ($argstr =~ s/\x01-q\x01/\x01/);
+my $show_docs = ($argstr =~ s/\x01-d\x01/\x01/);
+my $do_docs;
+my $cupscolorprofile;
+
+# Where to send debugging log output to
+my $logh;
+
+if ($debug) {
+ # Grotesquely unsecure; use for debugging only
+ open LOG, "> ${logfile}.log";
+ $logh = *LOG;
+
+ use IO::Handle;
+ $logh->autoflush(1);
+} elsif (($quiet) && (!$verbose)) {
+ # Quiet mode, do not log
+ close $logh;
+ open LOG, "> /dev/null";
+ $logh = *LOG;
+
+ use IO::Handle;
+ $logh->autoflush(1);
+} else {
+ # Default: log to STDERR
+ $logh=*STDERR;
+}
+
+
+
+## Start debug logging
+if ($debug) {
+ # If we are not in debug mode, we do this later, as we must find out at
+ # first which spooler is used. When printing without spooler we
+ # suppress logging because foomatic-rip is called directly on the
+ # command line and so we avoid logging onto the console.
+ print $logh "foomatic-rip version $ripversion running...\n";
+ # Print the command line only in debug mode, Mac OS X adds very many
+ # options so that CUPS cannot handle the output of the command line
+ # in its log files. If CUPS encounters a line with more than 1024
+ # characters sent into its log files, it aborts the job with an error.
+ if (($debug) || ($spooler ne 'cups')) {
+ print $logh "called with arguments: '", join("', '",@ARGV), "'\n";
+ }
+}
+
+
+
+## Continue with named options
+
+# Check for LPRng first so we do not pick up bogus ppd files by the -p option
+if ($argstr =~ s/\x01--lprng\x01/\x01/) {
+ # We have LPRng
+ $spooler = 'lprng';
+}
+# 'PRINTCAP_ENTRY' environment variable is : LPRng
+# the :ppd=/path/to/ppdfile printcap entry should be used
+if (defined($ENV{'PRINTCAP_ENTRY'})){
+ $spooler = 'lprng';
+ my( @pc);
+ @pc = split( /\s*:\s*/, $ENV{'PRINTCAP_ENTRY'} );
+ shift @pc;
+ foreach (@pc) {
+ if( /^ppd=(.*)$/ or /^ppdfile=(.*)$/ ){
+ $ppdfile = removespecialchars($1) if $1;
+ }
+ }
+} elsif ($argstr =~ s/\x01--lprng\x01/\x01/g) {
+ # We have LPRng
+ $spooler = 'lprng';
+}
+
+
+# PPD file name given via the command line
+# allow duplicates, and use the last specified one
+while ( ($spooler ne 'lprng') and ($argstr =~ s/\x01-p(\x01|)([^\x01]+)\x01/\x01/)) {
+ $ppdfile = removeshellescapes($2);
+}
+while ($argstr =~ s/\x01--ppd(\x01|=|)([^\x01]+)\x01/\x01/) {
+ $ppdfile = removeshellescapes($2);
+}
+
+# Check for LPD/GNUlpr by typical options which the spooler puts onto
+# the filter's command line (options "-w": text width, "-l": text
+# length, "-i": indent, "-x", "-y": graphics size, "-c": raw printing,
+# "-n": user name, "-h": host name)
+if (($argstr =~ s/\x01-w(\x01|)\d+\x01/\x01/) ||
+ ($argstr =~ s/\x01-l(\x01|)\d+\x01/\x01/) ||
+ ($argstr =~ s/\x01-x(\x01|)\d+\x01/\x01/) ||
+ ($argstr =~ s/\x01-y(\x01|)\d+\x01/\x01/) ||
+ ($argstr =~ s/\x01-i(\x01|)\d+\x01/\x01/) ||
+ ($argstr =~ s/\x01-c\x01/\x01/) ||
+ ($argstr =~ s/\x01-n(\x01|)[^\x01]+\x01/\x01/) ||
+ ($argstr =~ s/\x01-h(\x01|)[^\x01]+\x01/\x01/)) {
+ # We have LPD or GNUlpr
+ if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
+ $spooler = 'lpd';
+ }
+}
+
+# LPRng delivers the option settings via the "-Z" argument
+if ($argstr =~ s/\x01-Z(\x01|)([^\x01]+)\x01/\x01/) {
+ my @lpopts = split(/,/, $2);
+ foreach my $opt (@lpopts) {
+ $opt =~ s/^\s+//;
+ $opt =~ s/\s+$//;
+ $opt = removeshellescapes($opt);
+ if ($opt =~ /\s+/) {
+ $opt = "\"$opt\"";
+ }
+ $optstr .= "$opt ";
+ }
+ # We have LPRng
+ $spooler = 'lprng';
+}
+
+# Job title and options for stock LPD
+if ($argstr =~ s/\x01-[jJ](\x01|)([^\x01]+)\x01/\x01/) {
+ # An LPD
+ $jobtitle = removeshellescapes($2);
+ # Classic LPD hack
+ if ($spooler eq "lpd") {
+ $optstr .= "$jobtitle ";
+ }
+}
+
+# Check for CPS
+if ($argstr =~ s/\x01--cps\x01/\x01/) {
+ # We have cps
+ $spooler = 'cps';
+}
+
+# Options for spooler-less printing, CPS, or PDQ
+while ($argstr =~ s/\x01-o(\x01|)([^\x01]+)\x01/\x01/) {
+ my $opt = $2;
+ $opt =~ s/^\s+//;
+ $opt =~ s/\s+$//;
+ $opt = removeshellescapes($opt);
+ if ($opt =~ /\s+/) {
+ $opt = "\"$opt\"";
+ }
+ $optstr .= "$opt ";
+ # If we don't print as a PPR RIP or as a CPS filter, we print without
+ # spooler (we check for PDQ later)
+ if (($spooler ne 'ppr') && ($spooler ne 'cps')) {
+ $spooler = 'direct';
+ }
+}
+
+# Printer for spooler-less printing or PDQ
+if ($argstr =~ s/\x01-d(\x01|)([^\x01]+)\x01/\x01/) {
+ $printer = removeshellescapes($2);
+}
+# Printer for spooler-less printing, PDQ, or LPRng
+if ($argstr =~ s/\x01-P(\x01|)([^\x01]+)\x01/\x01/) {
+ $printer = removeshellescapes($2);
+}
+
+# Were we called from a PDQ wrapper?
+if ($argstr =~ s/\x01--pdq\x01/\x01/) {
+ # We have PDQ
+ $spooler = 'pdq';
+}
+
+# Were we called to build the PDQ driver declaration file?
+# "--appendpdq=<file>" appends the data to the <file>,
+# "--genpdq=<file>" creates/overwrites <file> for the data, and
+# "--genpdq" writes to standard output
+my $genpdqfile = "";
+if (($argstr =~ s/\x01--(gen)(raw|)pdq(\x01|=|)([^\x01]*)\x01/\x01/) ||
+ ($argstr =~ s/\x01--(append)(raw|)pdq(\x01|=|)([^\x01]+)\x01/\x01/)) {
+ # Determine output file name
+ if (!$4) {
+ $genpdqfile = ">&STDOUT";
+ } else {
+ if ($1 eq 'gen') {
+ $genpdqfile = "> " . removeshellescapes($4);
+ } else {
+ $genpdqfile = ">> " . removeshellescapes($4);
+ }
+ }
+ # Do we want to have a PDQ driver declaration for a raw printer?
+ if ($2 eq 'raw') {
+ my $time = time();
+ my @pdqfile =
+"driver \"Raw-Printer-$time\" {
+ # This PDQ driver declaration file was generated automatically by
+ # foomatic-rip to allow raw (filter-less) printing.
+ language_driver all {
+ # We accept all file types and pass them through without any changes
+ filetype_regx \"\"
+ convert_exec {
+ ln -s \$INPUT \$OUTPUT
+ }
+ }
+ filter_exec {
+ ln -s \$INPUT \$OUTPUT
+ }
+}";
+ open PDQFILE, $genpdqfile or
+ rip_die("Cannot write PDQ driver declaration file",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ print PDQFILE join('', @pdqfile);
+ close PDQFILE;
+ exit $EXIT_PRINTED;
+ }
+ # We have PDQ
+ $spooler = 'pdq';
+}
+
+
+# remove extra spacing if running as LPRng filter
+$added_lf = "" if $spooler eq 'lprng';
+
+## Command line arguments without name
+
+# Remaining arguments
+my @rargs = split(/\x01/, $argstr);
+shift @rargs;
+
+# Load definitions for PPR error messages, check whether we run as
+# PPR interface or as PPR RIP
+my( $ppr_printer, $ppr_address, $ppr_options, $ppr_jobbreak, $ppr_feedback,
+ $ppr_codes, $ppr_jobname, $ppr_routing, $ppr_for, $ppr_filetype,
+ $ppr_filetoprint );
+if ($spooler eq 'ppr') {
+ # Read interface.sh so we will know the correct exit codes and
+ # also signal.sh for the signal codes
+ my $deffound = 0; # Did we find one of the definition files
+ my @definitions;
+ for my $file (("lib/interface.sh", "lib/signal.sh")) {
+
+ open FILE, "< $file" || do {
+ print $logh "error opening $file.\n";
+ next;
+ };
+
+ $deffound = 1;
+ while(my $line = <FILE>) {
+ # Translate the shell script to Perl
+ if (($line !~ m/^\s*$/) && ($line !~ m/^\s*\#/)) {
+ $line =~ s/^\s*([^\#\s]*)/\$$1;/;
+ push (@definitions, $line);
+ }
+ }
+ close FILE;
+ }
+
+ if ($deffound) {
+ # Apply the definitions loaded from PPR
+ eval join('',@definitions) || do {
+ print $logh "unable to evaluate definitions\n";
+ rip_die ("Error in definitions evaluation",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ };
+ }
+
+ # Check whether we run as a PPR interface (if not, we run as a PPR RIP)
+ if (($rargs[3] =~ /^\s*\d\d?\s*$/) &&
+ ($rargs[5] =~ /^\s*\d\d?\s*$/) &&
+ (($#rargs == 10) || ($#rargs == 9) || ($#rargs == 7))) {
+ # PPR calls interfaces with many command line parameters,
+ # where the forth and the sixth is a small integer
+ # number. In addition, we have 8 (PPR <= 1.31), 10
+ # (PPR>=1.32), 11 (PPR >= 1.50) command line parameters.
+ # We also check whether the current working directory is a
+ # PPR directory.
+
+ # Get all command line parameters
+ $ppr_printer = removeshellescapes($rargs[0]);
+ $ppr_address = $rargs[1];
+ $ppr_options = removeshellescapes($rargs[2]);
+ $ppr_jobbreak = $rargs[3];
+ $ppr_feedback = $rargs[4];
+ $ppr_codes = $rargs[5];
+ $ppr_jobname = removeshellescapes($rargs[6]);
+ $ppr_routing = removeshellescapes($rargs[7]);
+ $ppr_for = $rargs[8];
+ $ppr_filetype = $rargs[9];
+ $ppr_filetoprint = removeshellescapes($rargs[10]);
+
+ # Common job parameters
+ $printer = $ppr_printer;
+ $jobtitle = $ppr_jobname;
+ if ((!$jobtitle) && ($ppr_filetoprint)) {
+ $jobtitle = $ppr_filetoprint;
+ }
+ $optstr .= "$ppr_options $ppr_routing";
+
+ # Get the path of the PPD file from the queue configuration
+ $ppdfile = `LANG=en_US; ppad show $ppr_printer | grep PPDFile`;
+ $ppdfile = removeshellescapes($ppdfile);
+ $ppdfile =~ s/PPDFile:\s+//;
+ if ($ppdfile !~ m!^/!) {
+ $ppdfile = "../../share/ppr/PPDFiles/$ppdfile";
+ }
+ chomp($ppdfile);
+
+ # We have PPR and run as an interface
+ $spooler = 'ppr_int';
+ }
+}
+
+# CUPS
+my( $cups_jobid, $cups_user, $cups_jobtitle, $cups_copies, $cups_options,
+ $cups_filename );
+if ($spooler eq 'cups') {
+ # Get all command line parameters
+ $cups_jobid = removeshellescapes($rargs[0]);
+ $cups_user = removeshellescapes($rargs[1]);
+ $cups_jobtitle = removeshellescapes($rargs[2]);
+ $cups_copies = removeshellescapes($rargs[3]);
+ $cups_options = removeshellescapes($rargs[4]);
+ $cups_filename = removeshellescapes($rargs[5]);
+
+ # Common job parameters
+ #$printer = $cups_printer;
+ $jobtitle = $cups_jobtitle;
+ $optstr .= $cups_options;
+
+ # Check for and handle inputfile vs stdin
+ if ((defined($cups_filename)) && ($cups_filename) &&
+ ($cups_filename ne '-')) {
+ # We get the input from a file
+ @filelist = ($cups_filename);
+ print $logh "Getting input from file $cups_filename\n";
+ }
+}
+
+# LPD/LPRng/GNUlpr
+if (($spooler eq 'lpd') ||
+ ($spooler eq 'lprng' and !$ppdfile) ||
+ ($spooler eq 'gnulpr')) {
+
+ # Get PPD file name as the last command line argument
+ $ppdfile = removeshellescapes($rargs[$#rargs]);
+
+}
+
+
+# No spooler, CPS, or PDQ
+if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) {
+ # Which files do we want to print?
+ @filelist = map { removeshellescapes($_) } @rargs;
+}
+
+
+
+## Additional spooler-specific preparations
+
+# CUPS
+
+if ($spooler eq 'cups') {
+
+ # This piece of PostScript code (initial idea 2001 by Michael
+ # Allerhand (michael.allerhand at ed dot ac dot uk, vastly
+ # improved by Till Kamppeter in 2002) lets GhostScript output
+ # the page accounting information which CUPS needs on standard
+ # error.
+ # Redesign by Helge Blischke (2004-11-17):
+ # - As the PostScript job itself may define BeginPage and/or EndPage
+ # procedures, or the alternate pstops filter may have inserted
+ # such procedures, we make sure that the accounting routine
+ # will safely coexist with those. To achieve this, we force
+ # - the accountint stuff to be inserted at the very end of the
+ # PostScript job's setup section,
+ # - the accounting stuff just using the return value of the
+ # existing EndPage procedure, if any (and providing a default one
+ # if not).
+ # - As PostScript jobs may contain calls to setpagedevice "between"
+ # pages, e.g. to change media type, do in-job stapling, etc.,
+ # we cannot rely on the "showpage count since last pagedevice
+ # activation" but instead count the physical pages by ourselves
+ # (in a global dictionary).
+
+ if (defined $conf{ps_accounting}) {
+ $ps_accounting = $conf{ps_accounting};
+ }
+ $accounting_prolog = $ps_accounting ? "[{
+%% Code for writing CUPS accounting tags on standard error
+
+/cupsPSLevel2 % Determine whether we can do PostScript level 2 or newer
+ systemdict/languagelevel 2 copy
+ known{get exec}{pop pop 1}ifelse 2 ge
+def
+
+cupsPSLevel2
+{ % in case of level 2 or higher
+ currentglobal true setglobal % define a dictioary foomaticDict
+ globaldict begin % in global VM and establish a
+ /foomaticDict % pages count key there
+ <<
+ /PhysPages 0
+ >>def
+ end
+ setglobal
+}if
+
+/cupsGetNumCopies { % Read the number of Copies requested for the current
+ % page
+ cupsPSLevel2
+ {
+ % PS Level 2+: Get number of copies from Page Device dictionary
+ currentpagedevice /NumCopies get
+ }
+ {
+ % PS Level 1: Number of copies not in Page Device dictionary
+ null
+ }
+ ifelse
+ % Check whether the number is defined, if it is \"null\" use #copies
+ % instead
+ dup null eq {
+ pop #copies
+ }
+ if
+ % Check whether the number is defined now, if it is still \"null\" use 1
+ % instead
+ dup null eq {
+ pop 1
+ } if
+} bind def
+
+/cupsWrite { % write a string onto standard error
+ (%stderr) (w) file
+ exch writestring
+} bind def
+
+/cupsFlush % flush standard error to make it sort of unbuffered
+{
+ (%stderr)(w)file flushfile
+}bind def
+
+cupsPSLevel2
+{ % In language level 2, we try to do something reasonable
+ <<
+ /EndPage
+ [ % start the array that becomes the procedure
+ currentpagedevice/EndPage 2 copy known
+ {get} % get the existing EndPage procedure
+ {pop pop {exch pop 2 ne}bind}ifelse % there is none, define the default
+ /exec load % make sure it will be executed, whatever it is
+ /dup load % duplicate the result value
+ { % true: a sheet gets printed, do accounting
+ currentglobal true setglobal % switch to global VM ...
+ foomaticDict begin % ... and access our special dictionary
+ PhysPages 1 add % count the sheets printed (including this one)
+ dup /PhysPages exch def % and save the value
+ end % leave our dict
+ exch setglobal % return to previous VM
+ (PAGE: )cupsWrite % assemble and print the accounting string ...
+ 16 string cvs cupsWrite % ... the sheet count ...
+ ( )cupsWrite % ... a space ...
+ cupsGetNumCopies % ... the number of copies ...
+ 16 string cvs cupsWrite % ...
+ (\\n)cupsWrite % ... a newline
+ cupsFlush
+ }/if load
+ % false: current page gets discarded; do nothing
+ ]cvx bind % make the array executable and apply bind
+ >>setpagedevice
+}
+{
+ % In language level 1, we do no accounting currently, as there is no global VM
+ % the contents of which are undesturbed by save and restore.
+ % If we may be sure that showpage never gets called inside a page related save / restore pair
+ % we might implement an hack with showpage similar to the one above.
+}ifelse
+
+} stopped cleartomark
+" : "";
+
+ # On which queue are we printing?
+ # CUPS gives the PPD file the same name as the printer queue,
+ # so we can get the queue name from the name of the PPD file.
+ $ppdfile =~ m!^(.*/)([^/]+)\.ppd$!;
+ $printer = $2;
+}
+
+# No spooler, CPS, or PDQ
+
+if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) {
+
+ # Path for personal Foomatic configuration
+ my $user_default_path = "$ENV{'HOME'}/.foomatic";
+
+ if (!$ppdfile) {
+ if (!$printer) {
+ # No printer definition file selected, check whether we have a
+ # default printer defined.
+ for my $conf_file (("./.directconfig",
+ "./directconfig",
+ "./.config",
+ "$user_default_path/direct/.config",
+ "$user_default_path/direct.conf",
+ "$configpath/direct/.config",
+ "$configpath/direct.conf")) {
+ if (open CONFIG, "< $conf_file") {
+ while (my $line = <CONFIG>) {
+ chomp $line;
+ if ($line =~ /^default\s*:\s*([^:\s]+)\s*$/) {
+ $printer = $1;
+ last;
+ }
+ }
+ close CONFIG;
+ }
+ if ($printer) {
+ last;
+ }
+ }
+ }
+
+ # Neither in a config file nor on the command line a printer was
+ # selected.
+ if (!$printer) {
+ rip_die("No printer definition (option \"-P <name>\") " .
+ "specified!", $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+
+ # Search for the PPD file
+
+ # Search also common spooler-specific locations, this way a printer
+ # configured under a certain spooler can also be used without
+ # spooler
+
+ if (-r $printer) {
+ $ppdfile = $printer;
+ # CPS can have the PPD in the spool directory
+ } elsif (($spooler eq 'cps') &&
+ (-r "/var/spool/lpd/${printer}/${printer}.ppd")) {
+ $ppdfile = "/var/spool/lpd/${printer}/${printer}.ppd";
+ } elsif (($spooler eq 'cps') &&
+ (-r "/var/local/spool/lpd/${printer}/${printer}.ppd")) {
+ $ppdfile = "/var/local/spool/lpd/${printer}/${printer}.ppd";
+ } elsif (($spooler eq 'cps') &&
+ (-r "/var/local/lpd/${printer}/${printer}.ppd")) {
+ $ppdfile = "/var/local/lpd/${printer}/${printer}.ppd";
+ } elsif (($spooler eq 'cps') &&
+ (-r "/var/spool/lpd/${printer}.ppd")) {
+ $ppdfile = "/var/spool/lpd/${printer}.ppd";
+ } elsif (($spooler eq 'cps') &&
+ (-r "/var/local/spool/lpd/${printer}.ppd")) {
+ $ppdfile = "/var/local/spool/lpd/${printer}.ppd";
+ } elsif (($spooler eq 'cps') &&
+ (-r "/var/local/lpd/${printer}.ppd")) {
+ $ppdfile = "/var/local/lpd/${printer}.ppd";
+ } elsif (-r "${printer}.ppd") { # current dir
+ $ppdfile = "${printer}.ppd";
+ } elsif (-r "$user_default_path/${printer}.ppd") { # user dir
+ $ppdfile = "$user_default_path/${printer}.ppd";
+ } elsif (-r "$configpath/direct/${printer}.ppd") { # system dir
+ $ppdfile = "$configpath/direct/${printer}.ppd";
+ } elsif (-r "$configpath/${printer}.ppd") { # system dir
+ $ppdfile = "$configpath/${printer}.ppd";
+ } elsif (-r "/etc/cups/ppd/${printer}.ppd") { # CUPS config dir
+ $ppdfile = "/etc/cups/ppd/${printer}.ppd";
+ } elsif (-r "/usr/local/etc/cups/ppd/${printer}.ppd") {
+ $ppdfile = "/usr/local/etc/cups/ppd/${printer}.ppd";
+ } elsif (-r "/usr/share/ppr/PPDFiles/${printer}.ppd") { # PPR PPDs
+ $ppdfile = "/usr/share/ppr/PPDFiles/${printer}.ppd";
+ } elsif (-r "/usr/local/share/ppr/PPDFiles/${printer}.ppd") {
+ $ppdfile = "/usr/local/share/ppr/PPDFiles/${printer}.ppd";
+ } else {
+ rip_die ("There is no readable PPD file for the printer " .
+ "$printer, is it configured?",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ }
+}
+
+
+
+## Files to be printed (can be more than one for spooler-less printing)
+
+# Empty file list -> print STDIN
+if ($#filelist < 0) {
+ @filelist = ("<STDIN>");
+}
+
+# Check file list
+my $file;
+my $filecnt = 0;
+for $file (@filelist) {
+ if ($file ne "<STDIN>") {
+ if ($file =~ /^-/) {
+ rip_die ("Invalid argument: $file",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ } elsif (! -r $file) {
+ print $logh "File $file does not exist/is not readable\n";
+ splice(@filelist, $filecnt, 1);
+ $filecnt --;
+ }
+ }
+ $filecnt ++;
+}
+
+
+
+## When we print without spooler or with CPS do not log onto STDERR unless
+## the "-v" ('Verbose') is set or the debug mode is used
+if ((($spooler eq 'direct') || ($spooler eq 'cps') || ($genpdqfile)) &&
+ (!$verbose) && (!$debug)) {
+ close $logh;
+ open LOG, "> /dev/null";
+ $logh = *LOG;
+
+ use IO::Handle;
+ $logh->autoflush(1);
+}
+
+
+
+## Start logging
+if (!$debug) {
+ # If we are in debug mode, we do this earlier.
+ print $logh "foomatic-rip version $ripversion running...\n";
+ # Print the command line only in debug mode, Mac OS X adds very many
+ # options so that CUPS cannot handle the output of the command line
+ # in its log files. If CUPS encounters a line with more than 1024
+ # characters sent into its log files, it aborts the job with an error.
+ if (($debug) || ($spooler ne 'cups')) {
+ print $logh "called with arguments: '", join("', '",@ARGV), "'\n";
+ }
+}
+
+
+
+## PPD file
+
+# Load the PPD file and build a data structure for the renderer's
+# command line and the options
+open PPD, "< $ppdfile" || do {
+ print $logh "error opening $ppdfile.\n";
+ rip_die ("Unable to open PPD file $ppdfile",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+};
+
+print $logh "Parsing PPD file ...\n";
+
+my $dat = {}; # data structure for the options
+my $currentargument = ""; # We are currently reading this argument
+
+# If we have an old Foomatic 2.0.x PPD file, read its built-in Perl
+# data structure into @datablob and the default values in %ppddefaults
+# Then delete the $dat structure, replace it by the one "eval"ed from
+# @datablob, and correct the default settings according to the ones of
+# the main PPD structure
+my @datablob;
+
+# Parse the PPD file
+sub undossify( $ );
+while(<PPD>) {
+ # foomatic-rip should also work with PPD file downloaded under Windows.
+ $_ = undossify($_);
+ # Parse keywords
+ if (m!^\*NickName:\s*\"(.*)$!) {
+ # "*NickName: <code>"
+ my $line = $1;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $cmd = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $cmd .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $cmd .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $cmd .= $1;
+ $model = unhtmlify($cmd);
+ } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) {
+ # "*FoomaticIDs: <printer ID> <driver ID>"
+ my $id = $1;
+ my $driver = $2;
+ # Store the values
+ $dat->{'id'} = $id;
+ $dat->{'driver'} = $driver;
+ } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
+ # "*FoomaticRIPPostPipe: <code>"
+ my $line = $1;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $cmd = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $cmd .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $cmd .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $cmd .= $1;
+ $postpipe = unhtmlify($cmd);
+ } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) {
+ # "*FoomaticRIPCommandLine: <code>"
+ my $line = $1;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $cmd = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $cmd .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $cmd .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $cmd .= $1;
+ $dat->{'cmd'} = unhtmlify($cmd);
+ } elsif (m!^\*cupsFilter:\s*\"(.*)$!) {
+ # "*cupsFilter: <code>"
+ my $line = $1;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $cmd = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $cmd .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $cmd .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $cmd .= $1;
+ my $cupsfilterline = unhtmlify($cmd);
+ if ($cupsfilterline =~ /^\s*(\S+)\s+\d+\s+(\S+)\s*$/) {
+ print $logh "*cupsFilter: \"$cupsfilterline\"\n";
+ # Make a hash by mime type for all CUPS filters set in this PPD
+ $dat->{'cupsfilter'}{$1} = $2;
+ }
+ } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) {
+ # "*CustomPageSize True: <code>"
+ my $setting = "Custom";
+ my $translation = "Custom Size";
+ my $line = $1;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, "PageSize");
+ checkarg ($dat, "PageRegion");
+ # Make sure that the setting is in the data structure
+ checksetting ($dat, "PageSize", $setting);
+ checksetting ($dat, "PageRegion", $setting);
+ $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation;
+ $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $code = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $code .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $code .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $code .= $1;
+ if ($code !~ m!^%% FoomaticRIPOptionSetting!m) {
+ $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code;
+ $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code;
+ }
+ } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) {
+ # "*[JCL]OpenUI *<option>[/<translation>]: <type>"
+ my $argnametrans = $2;
+ my $argtype = $3;
+ my $argname;
+ my $translation = "";
+ if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) {
+ $argname = $1;
+ $translation = $2;
+ } else {
+ $argname = $argnametrans;
+ }
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the values
+ $dat->{'args_byname'}{$argname}{'comment'} = $translation;
+ # Set the argument type only if not defined yet, a
+ # definition in "*FoomaticRIPOption" has priority
+ if ( !($dat->{'args_byname'}{$argname}{'type'}) ) {
+ if ($argtype eq "PickOne") {
+ $dat->{'args_byname'}{$argname}{'type'} = 'enum';
+ } elsif ($argtype eq "PickMany") {
+ $dat->{'args_byname'}{$argname}{'type'} = 'pickmany';
+ } elsif ($argtype eq "Boolean") {
+ $dat->{'args_byname'}{$argname}{'type'} = 'bool';
+ }
+ }
+ # Mark in which argument we are currently, so that we can find
+ # the entries for the choices
+ $currentargument = $argname;
+ } elsif (m!^\*(JCL|)CloseUI:\s+\*([^:/\s]+)\s*$!) {
+ # "*[JCL]CloseUI *<option>"
+ my $argname = $2;
+ # Unmark the current argument to do not mis-interpret any keywords
+ # as choices
+ $currentargument = "";
+ } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s*$!) ||
+ (m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s+(\S+)\s*$!)){
+ # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]"
+ # <order> only used for 1-choice enum options
+ my $argname = $1;
+ my $argtype = $2;
+ my $argstyle = $3;
+ my $spot = $4;
+ my $order = $5;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the values
+ $dat->{'args_byname'}{$argname}{'type'} = $argtype;
+ if ($argstyle eq "PS") {
+ $dat->{'args_byname'}{$argname}{'style'} = 'G';
+ } elsif ($argstyle eq "CmdLine") {
+ $dat->{'args_byname'}{$argname}{'style'} = 'C';
+ } elsif ($argstyle eq "JCL") {
+ $dat->{'args_byname'}{$argname}{'style'} = 'J';
+ $dat->{'jcl'} = 1;
+ } elsif ($argstyle eq "Composite") {
+ $dat->{'args_byname'}{$argname}{'style'} = 'X';
+ }
+ $dat->{'args_byname'}{$argname}{'spot'} = $spot;
+ # $order only defined here for 1-choice enum options
+ if ($order) {
+ $dat->{'args_byname'}{$argname}{'order'} = $order;
+ }
+ } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) {
+ # "*FoomaticRIPOptionPrototype <option>: <code>"
+ # Used for numerical and string options only
+ my $argname = $1;
+ my $line = $2;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $proto = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $proto .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $proto .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $proto .= $1;
+ $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto);
+ } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*(\S+)\s+(\S+)\s*$!) {
+ # "*FoomaticRIPOptionRange <option>: <min> <max>"
+ # Used for numerical options only
+ my $argname = $1;
+ my $min = $2;
+ my $max = $3;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the values
+ $dat->{'args_byname'}{$argname}{'min'} = $min;
+ $dat->{'args_byname'}{$argname}{'max'} = $max;
+ } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*(\S+)\s*$!) {
+ # "*FoomaticRIPOptionMaxLength <option>: <length>"
+ # Used for string options only
+ my $argname = $1;
+ my $maxlength = $2;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the value
+ $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength;
+ } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) {
+ # "*FoomaticRIPOptionAllowedChars <option>: <code>"
+ # Used for string options only
+ my $argname = $1;
+ my $line = $2;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $code = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $code .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $code .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $code .= $1;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the value
+ $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code);
+ } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) {
+ # "*FoomaticRIPOptionAllowedRegExp <option>: <code>"
+ # Used for string options only
+ my $argname = $1;
+ my $line = $2;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $code = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $code .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $code .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $code .= $1;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the value
+ $dat->{'args_byname'}{$argname}{'allowedregexp'} =
+ unhtmlify($code);
+ } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) {
+ # "*OrderDependency: <order> <section> *<option>"
+ my $order = $1;
+ my $section = $2;
+ my $argname = $3;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the values
+ $dat->{'args_byname'}{$argname}{'order'} = $order;
+ $dat->{'args_byname'}{$argname}{'section'} = $section;
+ } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
+ # "*Default<option>: <value>"
+ my $argname = $1;
+ my $default = $2;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the value
+ $dat->{'args_byname'}{$argname}{'default'} = $default;
+ } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
+ # "*FoomaticRIPDefault<option>: <value>"
+ # Used for numerical options only
+ my $argname = $1;
+ my $default = $2;
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Store the value
+ $dat->{'args_byname'}{$argname}{'fdefault'} = $default;
+ } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) {
+ # "*<option> <choice>[/<translation>]: <code>"
+ my $settingtrans = $1;
+ my $line = $2;
+ my $translation = "";
+ my $setting = "";
+ if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) {
+ $setting = $1;
+ $translation = $2;
+ } else {
+ $setting = $settingtrans;
+ }
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $currentargument);
+ # Make sure that the setting is in the data structure (enum options)
+ my $bool =
+ ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool');
+ if ($bool) {
+ if (lc($setting) eq "true") {
+ if (!$dat->{'args_byname'}{$currentargument}{'comment'}) {
+ $dat->{'args_byname'}{$currentargument}{'comment'} =
+ $translation;
+ }
+ $dat->{'args_byname'}{$currentargument}{'comment_true'} =
+ $translation;
+ } else {
+ $dat->{'args_byname'}{$currentargument}{'comment_false'} =
+ $translation;
+ }
+ } else {
+ checksetting ($dat, $currentargument, $setting);
+ # Make sure that this argument has a default setting, even if
+ # none is defined in this PPD file
+ if (!$dat->{'args_byname'}{$currentargument}{'default'}) {
+ $dat->{'args_byname'}{$currentargument}{'default'} = $setting;
+ }
+ $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation;
+ }
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $code = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $code .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $code .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $code .= $1;
+ if ($code !~ m!^%% FoomaticRIPOptionSetting!) {
+ if ($bool) {
+ if (lc($setting) eq "true") {
+ $dat->{'args_byname'}{$currentargument}{'proto'} = $code;
+ } else {
+ $dat->{'args_byname'}{$currentargument}{'protof'} = $code;
+ }
+ } else {
+ $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code;
+ }
+ }
+ } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) ||
+ (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) {
+ # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>"
+ # For boolean options <choice> is not given
+ my $argname = $1;
+ my $setting = $2;
+ my $line = $3;
+ my $bool = 0;
+ if (!$line) {
+ $line = $setting;
+ $bool = 1;
+ }
+ # Make sure that the argument is in the data structure
+ checkarg ($dat, $argname);
+ # Make sure that the setting is in the data structure (enum options)
+ if (!$bool) {
+ checksetting ($dat, $argname, $setting);
+ # Make sure that this argument has a default setting, even if
+ # none is defined in this PPD file
+ if (!$dat->{'args_byname'}{$argname}{'default'}) {
+ $dat->{'args_byname'}{$argname}{'default'} = $setting;
+ }
+ }
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $code = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $code .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $code .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $code .= $1;
+ if ($bool) {
+ $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code);
+ } else {
+ $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code);
+ }
+ } elsif (m!^\*JCL(Begin|ToPSInterpreter|End):\s*\"(.*)$!) {
+ # "*JCL(Begin|ToPSInterpreter|End): <code>"
+ # The printer supports PJL/JCL when there is such a line
+ $dat->{'jcl'} = 1;
+ my $item = $1;
+ my $line = $2;
+ # Store the value
+ # Code string can have multiple lines, read all of them
+ my $code = "";
+ while ($line !~ m!\"!) {
+ if ($line =~ m!&&$!) {
+ # line continues in next line
+ $code .= substr($line, 0, -2);
+ } else {
+ # line ends here
+ $code .= "$line\n";
+ }
+ # Read next line
+ $line = <PPD>;
+ chomp $line;
+ }
+ $line =~ m!^([^\"]*)\"!;
+ $code .= $1;
+ if ($item eq 'Begin') {
+ $jclbegin = unhexify($code);
+ } elsif ($item eq 'ToPSInterpreter') {
+ $jcltointerpreter = unhexify($code);
+ } elsif ($item eq 'End') {
+ $jclend = unhexify($code);
+ }
+ } elsif (m!^\*\% COMDATA \#(.*)$!) {
+ # If we have an old Foomatic 2.0.x PPD file, collect its Perl data
+ push (@datablob, $1);
+ }
+}
+close PPD;
+
+# If we have an old Foomatic 2.0.x PPD file use its Perl data structure
+if ($#datablob >= 0) {
+ print $logh "${added_lf}You are using an old Foomatic 2.0 PPD file, consider " .
+ "upgrading.${added_lf}\n";
+ my $VAR1;
+ if (eval join('',@datablob)) {
+ # Overtake default settings from the main structure of the PPD file
+ for my $arg (@{$dat->{'args'}}) {
+ if ($arg->{'default'}) {
+ $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} =
+ $arg->{'default'};
+ }
+ }
+ undef $dat;
+ $dat = $VAR1;
+ $dat->{'jcl'} = $dat->{'pjl'};
+ } else {
+ # Perl structure broken
+ print $logh "${added_lf}Unable to evaluate datablob, print job may come " .
+ "out incorrectly or not at all.${added_lf}\n";
+ }
+}
+
+
+
+## We do not need to parse the PostScript job when we don't have
+## any options. If we have options, we must check whether the
+## default settings from the PPD file are valid and correct them
+## if nexessary.
+
+my $dontparse = 0;
+if ((!defined(@{$dat->{'args'}})) ||
+ ($#{$dat->{'args'}} < 0)) {
+ # We don't have any options, so we do not need to parse the
+ # PostScript data
+ $dontparse = 1;
+} else {
+ # Let the default value of a boolean option being 0 or 1 instead of
+ # "True" or "False", range-check the defaults of all options and
+ # issue warnings if the values are not valid
+ checkoptions($dat, 'default');
+
+ # Adobe's PPD specs do not support numerical
+ # options. Therefore the numerical options are mapped to
+ # enumerated options in the PPD file and their characteristics
+ # as a numerical option are stored in "*Foomatic..."
+ # keywords. A default must be between the enumerated
+ # fixed values. The default
+ # value must be given by a "*FoomaticRIPDefault<option>:
+ # <value>" line in the PPD file. But this value is only valid
+ # if the "official" default given by a "*Default<option>:
+ # <value>" line (it must be one of the enumerated values)
+ # points to the enumerated value which is closest to this
+ # value. This way a user can select a default value with a
+ # tool only supporting PPD files but not Foomatic extensions.
+ # This tool only modifies the "*Default<option>: <value>" line
+ # and if the "*FoomaticRIPDefault<option>: <value>" had always
+ # priority, the user's change in "*Default<option>: <value>"
+ # would have no effect.
+
+ for my $arg (@{$dat->{'args'}}) {
+ if ($arg->{'fdefault'}) {
+ if ($arg->{'default'}) {
+ if ($arg->{'type'} =~ /^(int|float)$/) {
+ if ($arg->{'fdefault'} < $arg->{'min'}) {
+ $arg->{'fdefault'} = $arg->{'min'};
+ }
+ if ($arg->{'fdefault'} > $arg->{'max'}) {
+ $arg->{'fdefault'} = $arg->{'max'};
+ }
+ my $mindiff = abs($arg->{'max'} - $arg->{'min'});
+ my $closestvalue;
+ for my $val (@{$arg->{'vals'}}) {
+ if (abs($arg->{'fdefault'} - $val->{'value'}) <
+ $mindiff) {
+ $mindiff =
+ abs($arg->{'fdefault'} - $val->{'value'});
+ $closestvalue = $val->{'value'};
+ }
+ }
+ if (($arg->{'default'} == $closestvalue) ||
+ (abs($arg->{'default'} - $closestvalue) /
+ $closestvalue < 0.001)) {
+ $arg->{'default'} = $arg->{'fdefault'};
+ }
+ }
+ } else {
+ $arg->{'default'} = $arg->{'fdefault'};
+ }
+ }
+ }
+}
+
+# Is our PPD for a CUPS raster driver
+if (my $cupsfilter = $dat->{'cupsfilter'}{"application/vnd.cups-raster"}) {
+
+ # Search filter in cupsfilterpath
+ # The %Y is a placeholder for the option settings
+ my $havefilter = 0;
+ for (split(':', $cupsfilterpath)) {
+ if (-x "$_/$cupsfilter") {
+ $havefilter=1;
+ $cupsfilter = "$_/$cupsfilter 0 '' '' 0 '%Y%X'";
+ last;
+ }
+ }
+
+ if (!$havefilter) {
+
+ # We do not have the required filter, so we assume that
+ # rendering this job is supposed to be done on a remote
+ # server. So we do not define a renderer command line and
+ # embed only the option settings (as we had a PostScript
+ # printer). This way the settings are # taken into account
+ # when the job is rendered on the server.
+ print $logh "${added_lf}CUPS filter for this PPD file not found " .
+ "assuming that job will be rendered on a remote server. Only " .
+ "the PostScript of the options will be inserted into the " .
+ "PostScript data stream.${added_lf}\n";
+
+ } else {
+
+ # use pstoraster script if available, otherwise run GhostScript
+ # directly
+ my $pstoraster = "pstoraster";
+ my $havepstoraster = 0;
+ for (split(':', $cupsfilterpath)) {
+ if (-x "$_/$pstoraster") {
+ $havepstoraster=1;
+ $pstoraster = "$_/$pstoraster 0 '' '' 0 '%X'";
+ last;
+ }
+ }
+
+ if (!$havepstoraster) {
+
+ # Build GhostScript command line
+ $pstoraster = "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOMEDIAATTRS -sDEVICE=cups -sOutputFile=-%W -"
+
+ }
+
+ # build GhostScript/CUPS driver command line
+ $dat->{'cmd'} = "$pstoraster | $cupsfilter";
+
+ # Set environment variables
+ $ENV{'PPD'} = $ppdfile;
+
+ }
+}
+
+# Was the RIP command line defined in the PPD file? If not, we assume a
+# PostScript printer and do not render/translate the input data
+if (!defined($dat->{'cmd'})) {
+ $dat->{'cmd'} = "cat%A%B%C%D%E%F%G%H%I%J%K%L%M%Z";
+ if ($dontparse) {
+ # No command line, no options, we have a raw queue, don't check
+ # whether the input is PostScript and ignore the "docs" option,
+ # simply pass the input data to the backend.
+ $dontparse = 2;
+ $model = "Raw queue";
+ }
+}
+
+
+
+## Summary for debugging
+print $logh "${added_lf}Parameter Summary\n";
+print $logh "-----------------${added_lf}\n";
+print $logh "Spooler: $spooler\n";
+print $logh "Printer: $printer\n";
+print $logh "PPD file: $ppdfile\n";
+print $logh "Printer model: $model\n";
+# Print the options string only in debug mode, Mac OS X adds very many
+# options so that CUPS cannot handle the output of the option string
+# in its log files. If CUPS encounters a line with more than 1024 characters
+# sent into its log files, it aborts the job with an error.
+if (($debug) || ($spooler ne 'cups')) {
+ print $logh "Options: $optstr\n";
+}
+print $logh "Job title: $jobtitle\n";
+print $logh "File(s) to be printed: ${added_lf}@filelist${added_lf}\n";
+
+
+
+## Parse options from command line ($optstr)
+
+# Before we start, save the defaults for printing documentation pages
+
+copyoptions($dat, 'default', 'userval');
+
+
+# The options are "foo='bar nut'", "foo", "nofoo", "'bar nut'", or
+# "foo:'bar nut'" (when GPR was used) all with spaces between...
+# In addition they can be preceeded by page ranges, separated with a
+# colon.
+
+my @opts;
+
+# Variable for PPR's backend interface name (parallel, tcpip, atalk, ...)
+
+my $backend = "";
+
+# Array to collect unknown options so that they can get passed to the
+# backend interface of PPR. For other spoolers we ignore them.
+
+my @backendoptions = ();
+
+# "foo='bar nut'"
+while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+=[\'\"].*?[\'\"]) ?!!i) {
+ push (@opts, $1);
+}
+
+# "foo:'bar nut'" (GPR separates option and setting with a colon ":")
+while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+:[\'\"].*?[\'\"]) ?!!i) {
+#while ($optstr =~ s!(\w+=[\'\"].*?[\'\"])!!i) {
+ push (@opts, $1);
+}
+
+# "'bar nut'", "'foo=bar nut'", "'foo:bar nut'"
+while ($optstr =~ s!([\'\"].+?[\'\"]) ?!!) {
+ my $opt = $1;
+ $opt =~ s/[\'\"]//g; # Make only sure that we didn't quote
+ # the option for a second time when we read
+ # rge options from the command line or
+ # environment variable
+ push (@opts, $opt);
+
+}
+
+# "foo", "nofoo"
+push(@opts, split(/ /,$optstr));
+
+# Now actually process those pesky options...
+
+for (@opts) {
+ print $logh "Pondering option '$_'\n";
+
+ # "docs" option to print help page
+ if ((lc($_) =~ /^\s*docs\s*$/) ||
+ (lc($_) =~ /^\s*docs\s*=\s*true\s*$/)) {
+ # The second one is necessary becuase CUPS 1.1.15 or newer sees
+ # "docs" as boolean option and modifies it to "docs=true"
+ $do_docs = 1;
+ next;
+ }
+
+ # "profile" option to supply a color correction profile to a
+ # CUPS raster driver
+ if (lc($_) =~ /^\s*profile=(\S+)\s*$/) {
+ $cupscolorprofile=$1;
+ $dat->{'cmd'} =~ s!\%X!profile=$cupscolorprofile!g;
+ $dat->{'cmd'} =~ s!\%W! -c\"<</cupsProfile($cupscolorprofile)>>setpagedevice\"!g;
+ next;
+ }
+
+ # Is the command line option limited to certain page ranges? If so,
+ # mark the setting with a hash key containing the ranges
+ my $optionset;
+ if (s/^(even|odd|[\d,-]+)://i) {
+ $optionset = "pages:$1";
+ } else {
+ $optionset = 'userval';
+ }
+
+ my $arg;
+ if ((m!([^=]+)=\'?(.*)\'?!) || (m!([^=:]+):\'?(.*)\'?!)) {
+ my ($aname, $avalue) = ($1, $2);
+
+ if (($optionset =~ /pages/) &&
+ ($arg = argbyname($aname)) &&
+ ((!defined($arg->{'section'})) ||
+ ($arg->{'section'} !~ /^(Any|Page)Setup/))) {
+ print $logh "This option is not a \"PageSetup\" or " .
+ "\"AnySetup\" option, so it cannot be restricted to " .
+ "a page range.\n";
+ next;
+ }
+
+ # At first look for the "backend" option to determine the PPR
+ # backend to use
+ if (($aname =~ m!^backend$!i) && ($spooler eq 'ppr_int')) {
+ # Backend interface name
+ $backend = $avalue;
+ } elsif ($aname =~ m!^media$!i) {
+
+ # Standard arguments?
+ # media=x,y,z
+ # sides=one|two-sided-long|short-edge
+
+ # Rummage around in the media= option for known media, source,
+ # etc types.
+ # We ought to do something sensible to make the common manual
+ # boolean option work when specified as a media= tray thing.
+ #
+ # Note that this fails miserably when the option value is in
+ # fact a number; they all look alike. It's unclear how many
+ # drivers do that. We may have to standardize the verbose
+ # names to make them work as selections, too.
+
+ my @values = split(',',$avalue);
+ for (@values) {
+ my $val;
+ if ($dat->{'args_byname'}{'PageSize'} and
+ $val=valbyname($dat->{'args_byname'}{'PageSize'},$_)) {
+ $dat->{'args_byname'}{'PageSize'}{$optionset} =
+ $val->{'value'};
+ # Keep "PageRegion" in sync
+ if ($dat->{'args_byname'}{'PageRegion'} and
+ $val=valbyname($dat->{'args_byname'}{'PageRegion'},
+ $_)) {
+ $dat->{'args_byname'}{'PageRegion'}{$optionset} =
+ $val->{'value'};
+ }
+ } elsif ($dat->{'args_byname'}{'PageSize'}
+ and /^Custom/) {
+ $dat->{'args_byname'}{'PageSize'}{$optionset} = $_;
+ # Keep "PageRegion" in sync
+ if ($dat->{'args_byname'}{'PageRegion'}) {
+ $dat->{'args_byname'}{'PageRegion'}{$optionset} =
+ $_;
+ }
+ } elsif ($dat->{'args_byname'}{'MediaType'} and
+ $val=valbyname($dat->{'args_byname'}{'MediaType'},
+ $_)) {
+ $dat->{'args_byname'}{'MediaType'}{$optionset} =
+ $val->{'value'};
+ } elsif ($dat->{'args_byname'}{'InputSlot'} and
+ $val=valbyname($dat->{'args_byname'}{'InputSlot'},
+ $_)) {
+ $dat->{'args_byname'}{'InputSlot'}{$optionset} =
+ $val->{'value'};
+ } elsif (lc($_) eq 'manualfeed') {
+ # Special case for our typical boolean manual
+ # feeder option if we didn't match an InputSlot above
+ if (defined($dat->{'args_byname'}{'ManualFeed'})) {
+ $dat->{'args_byname'}{'ManualFeed'}{$optionset} = 1;
+ }
+ } else {
+ print $logh "Unknown \"media\" component: \"$_\".\n";
+ }
+ }
+ } elsif ($aname =~ m!^sides$!i) {
+ # Handle the standard duplex option, mostly
+ if ($avalue =~ m!^two-sided!i) {
+ if (defined($dat->{'args_byname'}{'Duplex'})) {
+ # We set "Duplex" to '1' here, the real argument setting
+ # will be done later
+ $dat->{'args_byname'}{'Duplex'}{$optionset} = '1';
+ # Check the binding: "long edge" or "short edge"
+ if ($avalue =~ m!long-edge!i) {
+ if (defined($dat->{'args_byname'}{'Binding'})) {
+ $dat->{'args_byname'}{'Binding'}{$optionset} =
+ $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'LongEdge'}{'value'};
+ } else {
+ $dat->{'args_byname'}{'Duplex'}{$optionset} =
+ 'LongEdge';
+ }
+ } elsif ($avalue =~ m!short-edge!i) {
+ if (defined($dat->{'args_byname'}{'Binding'})) {
+ $dat->{'args_byname'}{'Binding'}{$optionset} =
+ $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'ShortEdge'}{'value'};
+ } else {
+ $dat->{'args_byname'}{'Duplex'}{$optionset} =
+ 'ShortEdge';
+ }
+ }
+ }
+ } elsif ($avalue =~ m!^one-sided!i) {
+ if (defined($dat->{'args_byname'}{'Duplex'})) {
+ # We set "Duplex" to '0' here, the real argument setting
+ # will be done later
+ $dat->{'args_byname'}{'Duplex'}{$optionset} = '0';
+ }
+ }
+
+ # We should handle the other half of this option - the
+ # BindEdge bit. Also, are there well-known ipp/cups
+ # options for Collate and StapleLocation? These may be
+ # here...
+
+ } else {
+ # Various non-standard printer-specific options
+ if ($arg = argbyname($aname)) {
+ if (defined(my $newvalue =
+ checkoptionvalue($dat, $aname, $avalue, 0))) {
+ # If the choice is valid, use it, otherwise
+ # ignore it.
+ $arg->{$optionset} = $newvalue;
+ # If this argument is PageSize or PageRegion,
+ # also set the other
+ syncpagesize($dat, $aname, $avalue, $optionset);
+ } else {
+ # Invalid choice, make log entry
+ print $logh "Invalid choice $aname=$avalue.\n";
+ }
+ } elsif ($spooler eq 'ppr_int') {
+ # Unknown option, pass it to PPR's backend interface
+ push (@backendoptions, "$aname=$avalue");
+ } else {
+ # Unknown option, make log entry
+ print $logh "Unknown option $aname=$avalue.\n";
+ }
+ }
+ } elsif (m!^([\d\.]+)x([\d\.]+)([A-Za-z]*)$!) {
+ my ($w, $h, $u) = ($1, $2, $3);
+ # Custom paper size
+ if (($w != 0) && ($h != 0) &&
+ ($arg=argbyname("PageSize")) &&
+ (defined($arg->{'vals_byname'}{'Custom'}))) {
+ $arg->{$optionset} = "Custom.${w}x${h}${u}";
+ # Keep "PageRegion" in sync
+ if ($dat->{'args_byname'}{'PageRegion'}) {
+ $dat->{'args_byname'}{'PageRegion'}{$optionset} =
+ $arg->{$optionset};
+ }
+ }
+ } elsif ((m!^\s*no(.+)\s*$!i) and ($arg=argbyname($1))) {
+ # standard bool args:
+ # landscape; what to do here?
+ # duplex; we should just handle this one OK now?
+ $arg->{$optionset} = 0;
+ } elsif (m!^\s*(.+)\s*$!) {
+ if ($arg=argbyname($1)) {
+ $arg->{$optionset} = 1;
+ } else {
+ print $logh "Unknown boolean option \"$1\".\n";
+ }
+ }
+}
+$do_docs = 1 if( $show_docs );
+
+
+## Were we called to build the PDQ driver declaration file?
+my @pdqfile;
+if ($genpdqfile) {
+ @pdqfile = buildpdqdriver($dat, 'userval');
+ open PDQFILE, $genpdqfile or
+ rip_die("Cannot write PDQ driver declaration file",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ print PDQFILE join('', @pdqfile);
+ close PDQFILE;
+ exit $EXIT_PRINTED;
+}
+
+
+
+## Set the $postpipe
+
+# $postpipe when running as a PPR RIP
+if ($spooler eq 'ppr') {
+ # The PPR RIP sends the data output to /dev/fd/3 instead of to STDOUT
+ if (-w "/dev/fd/3") {
+ $postpipe = "| cat - > /dev/fd/3";
+ } else {
+ $postpipe = "| cat - >&3";
+ }
+}
+
+# Set up PPR backend (if we run as a PPR interface).
+if ($spooler eq 'ppr_int') {
+
+ # Is the chosen backend installed and executable
+ if (!-x "interfaces/$backend") {
+ my $pwd = cwd;
+ print $logh "The backend interface $pwd/interfaces/$backend " .
+ "does not exist/is not executable!\n";
+ rip_die ("The backend interface $pwd/interfaces/$backend " .
+ "does not exist/is not executable!",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+
+ # foomatic-rip cannot use foomatic-rip as backend
+ if ($backend eq "foomatic-rip") {
+ print $logh "\"foomatic-rip\" cannot use itself as backend " .
+ "interface!\n";
+ ppr_die ($ppr_printer,
+ "\"foomatic-rip\" cannot use itself as backend interface!",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+
+ # Put the backend interface into the $postpipe
+ $postpipe = "| ( interfaces/$backend \"$ppr_printer\" ".
+ "\"$ppr_address\" \"" . join(" ",@backendoptions) .
+ "\" \"$ppr_jobbreak\" \"$ppr_feedback\" " .
+ "\"$ppr_codes\" \"$ppr_jobname\" \"$ppr_routing\" " .
+ "\"$ppr_for\" \"\" )";
+
+}
+
+# CUPS and PDQ have their own backends, they do not need a $postpipe
+if (($spooler eq 'cups') || ($spooler eq 'pdq')) {
+ # No $postpipe for CUPS or PDQ, even if one is defined in the PPD file
+ $postpipe = "";
+}
+
+# CPS needs always a $postpipe, set the default one for local printing
+# if none is set
+if (($spooler eq 'cps') && !$postpipe) {
+ $postpipe = "| cat - > \$LPDDEV";
+}
+
+if ($postpipe) {
+ print $logh "${added_lf}Output will be redirected to:\n$postpipe${added_lf}\n";
+}
+
+
+
+## Print documentation page when asked for
+my ($docgeneratorhandle, $docgeneratorpid,$retval);
+if ($do_docs) {
+ # Don't print the supplied files, STDIN will be redirected to the
+ # documentation page generator
+ @filelist = ("<STDIN>");
+ # Start the documentation page generator
+ ($docgeneratorhandle, $docgeneratorpid) =
+ getdocgeneratorhandle($dat);
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error opening documentation page generator",
+ $retval);
+ }
+ # Read the further data from the documentation page generator and
+ # not from STDIN
+ if (!close STDIN && $! != $ESPIPE) {
+ rip_die ("Couldn't close STDIN",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!open (STDIN, "<&$docgeneratorhandle")) {
+ rip_die ("Couldn't dup \$docgeneratorhandle",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if( $show_docs ){
+ while( <$docgeneratorhandle> ){
+ print;
+ }
+ exit(0);
+ }
+}
+
+
+
+
+## In debug mode save the data supposed to be fed into the
+## renderer also into a file, reset the file here
+
+if ($debug) {
+ system("> ${logfile}.ps");
+}
+
+
+
+## From here on we have to repeat all the rest of the program for
+## every file to print
+
+for $file (@filelist) {
+
+ print $logh
+"${added_lf}================================================\n${added_lf}".
+"File: $file\n${added_lf}" .
+"================================================\n${added_lf}";
+
+
+
+ ## If we do not print standard input, open the file to print
+ if ($file ne "<STDIN>") {
+ if (! -r $file) {
+ print $logh "File $file missing or not readable, skipping.\n";
+ next;
+ }
+ close STDIN;
+ open STDIN, "< $file" || do {
+ print $logh "Cannot open $file, skipping.\n";
+ next;
+ }
+ }
+
+
+
+ ## Do we have a raw queue
+ if ($dontparse == 2) {
+ # Raw queue, simply pass the input into the $postpipe (or to STDOUT
+ # when there is no $postpipe)
+ print $logh "Raw printing, executing \"cat $postpipe\"${added_lf}\n";
+ system("cat $postpipe");
+ next;
+ }
+
+
+
+ ## First, for arguments with a default, stick the default in as
+ ## the initial value for the "header" option set, this option set
+ ## consists of the PPD defaults, the options specified on the
+ ## command line, and the options set in the header part of the
+ ## PostScript file (all before the first page begins).
+
+ copyoptions($dat, 'userval', 'header');
+
+
+
+ ## Next, examine the PostScript job for traces of command-line and
+ ## JCL options. PPD-aware applications and spoolers stuff option
+ ## settings directly into the file, they do not necessarily send
+ ## PPD options by the command line. Also stuff in PostScript code
+ ## to apply option settings given by the command line and to set
+ ## the defaults given in the PPD file.
+
+ # Examination strategy: read lines from STDIN until the first
+ # %%Page: comment appears and save them as @psheader. This is the
+ # page-independent header part of the PostScript file. The
+ # PostScript interpreter (renderer) must execute this part once
+ # before rendering any assortment of pages. Then pages can be
+ # printed in any arbitrary selection or order. All option
+ # settings we find here will be collected in the default option
+ # set for the RIP command line.
+
+ # Now the pages will be read and sent to the renderer, one after
+ # the other. Every page is read into memory until the
+ # %%EndPageSetup comment appears (or a certain amount of lines was
+ # read). So we can get option settings only valid for this
+ # page. If we have such settings we set them in the modified
+ # command set for this page.
+
+ # If the renderer is not running yet (first page) we start it with
+ # the command line built from the current modified command set and
+ # send the first page to it, in the end we leave the renderer
+ # running and keep input and output pipes open, so that it can
+ # accept further pages. If the renderer is still running from
+ # the previous page and the current modified command set is the
+ # same as the one for the previous page, we send the page. If
+ # the command set is different, we close the renderer, re-start
+ # it with the command line built from the new modified command
+ # set, send the header again, and then the page.
+
+ # After the last page the trailer (%%Trailer) is sent.
+
+ # The output pipe of this program stays open all the time so that
+ # the spooler does not assume that the job has finished when the
+ # renderer is re-started.
+
+ # Non DSC-conforming documents will be read until a certain line
+ # number is reached. Command line or JCL options inserted later
+ # will be ignored.
+
+ # If options are implemented by PostScript code supposed to be
+ # stuffed into the job's PostScript data we stuff the code for all
+ # these options into our job data, So all default settings made in
+ # the PPD file (the user can have edited the PPD file to change
+ # them) are taken care of and command line options get also
+ # applied. To give priority to settings made by applications we
+ # insert the options's code in the beginnings of their respective
+ # sections, so that sommething, which is already inserted, gets
+ # executed after our code. Missing sections are automatically
+ # created. In non-DSC-conforming files we insert the option code
+ # in the beginning of the file. This is the same policy as used by
+ # the "pstops" filter of CUPS.
+
+ # If CUPS is the spooler, the option settings were already
+ # inserted by the "pstops" filter, so we don't insert them
+ # again. The only thing we do is correcting settings of numerical
+ # options when they were set to a value not available as choice in
+ # the PPD file, As "pstops" does not support "real" numerical
+ # options, it sees these settings as an invalid choice and stays
+ # with the default setting. In this case we correct the setting in
+ # the first occurence of the option's code, as this one is the one
+ # added by CUPS, later occurences come from applications and
+ # should not be touched.
+
+ # If the input is not PostScript (if there is no "%!" after
+ # $maxlinestopsstart lines) a file conversion filter will
+ # automatically be applied to the incoming data, so that we will
+ # process the resulting PostScript here. This way we have always
+ # PostScript data here and so we can apply the printer/driver
+ # features described in the PPD file.
+
+ # Supported file conversion filters are "a2ps", "enscript",
+ # "mpage", and spooler-specific filters. All filters convert
+ # plain text to PostScript, "a2ps" also other formats. The
+ # conversion filter is always used when one prints the
+ # documentation pages, as they are created as plain text,
+ # when CUPS is the spooler "pstops" is executed after the
+ # filter so that the default option settings from the PPD file
+ # and CUPS-specific options as N-up get applied. On regular
+ # printouts one gets always PostScript when CUPS or PPR is
+ # the spooler, so the filter is only used for regular
+ # printouts under LPD, LPRng, GNUlpr or without spooler.
+
+ my $maxlines = 1000; # Maximum number of lines to be read
+ # when the documenent is not
+ # DSC-conforming. "$maxlines = 0"
+ # means that all will be read
+ # and examined. If it is
+ # discovered that the input file
+ # is DSC-conforming, this will
+ # be set to 0.
+
+ my $maxlinestopsstart = 200; # That many lines are allowed until the
+ # "%!" indicating PS comes. These
+ # additional lines in the
+ # beginning are usually JCL
+ # commands. The lines will be
+ # ignored by our parsing but
+ # passed through.
+
+ my $maxlinesforpageoptions=200; # Unfortunately, CUPS does not bracket
+ # "PageSetup" option with
+ # "%%BeginPageSetup" and
+ # "%%EndPageSetup", so the options
+ # can simply stand after the
+ # page header and before the
+ # page code, without special
+ # marking. So buffer this amount
+ # of lines before printing the
+ # page to check for options.
+
+ my $maxnondsclinesinheader=1000; # If there is a block of more lines
+ # than this in the document
+ # header which is not in the
+ # "%%BeginProlog...%%EndProlog"
+ # or
+ # "%%BeginSetup...%%EndSetup"
+ # sections, the document is not
+ # considered as DSC-conforming
+ # and the rest gets passed
+ # through to the renderer without
+ # further parsing for options.
+
+ my $nondsclines = 0; # Amount of lines found which are not in
+ # a section (see
+ # $maxnondsclinesinheader).
+
+ my $nonpslines = 0; # lines before "%!" found yet.
+
+ my $more_stuff = 1; # there is more stuff in stdin.
+
+ my $linect = 0; # how many lines have we examined?
+
+ my $onelinebefore = ""; # The line before the current line
+ # (Non-DSC comments are ignored)
+
+ my $twolinesbefore = ""; # The line two lines before the current
+ # line (Non-DSC comments are ignored)
+
+ my @psheader = (); # The header of the PostScript file,
+ # to be sent after each start of the
+ # renderer
+
+ my @psfifo = (); # The input FIFO, data which we have
+ # pulled from stdin for examination,
+ # but not sent to the renderer yet.
+
+ my $passthru = 0; # 0: write data into @psfifo; 1: pass
+ # data directly to the renderer
+
+ my $isdscjob = 0; # Is the job DSC conforming
+
+ my $inheader = 1; # Are we still in the header, before
+ # first "%%Page:" comment?
+
+ my $optionset = 'header'; # Where do the option settings, which
+ # we have found, go?
+
+ my $optionsalsointoheader = 0; # 1: We are in a "%%BeginSetup...
+ # %%EndSetup" section after the first
+ # "%%Page:..." line (OpenOffice.org
+ # does this and intends the options here
+ # apply to the whole document and not
+ # only to the current page). We have to
+ # add all lines also to the end of the
+ # @psheader now and we have to set
+ # non-PostScript options also in the
+ # "header" optionset. 0: otherwise.
+
+ my $nestinglevel = 0; # Are we in the main document (0) or
+ # in an embedded document bracketed by
+ # "%%BeginDocument" and "%%EndDocument"
+ # (>0) We do not parse the PostScript
+ # in an embedded document.
+
+ my $inpageheader = 0; # Are we in the header of a page,
+ # between "%%BeginPageSetup" and
+ # "%%EndPageSetup" (1) or not (0).
+
+ my $lastpassthru = 0; # State of $passthru in previous line
+ # (to allow debug output when $passthru
+ # switches.
+
+ my $ignorepageheader = 0; # Will be set to 1 as soon as active
+ # code (not between "%%BeginPageSetup"
+ # and "%%EndPageSetup") appears after a
+ # "%%Page:" comment. In this case
+ # "%%BeginPageSetup" and
+ # "%%EndPageSetup" is not allowed any
+ # more on this page and will be ignored.
+ # Will be set to 0 when a new "%%Page:"
+ # comment appears.
+
+ my $printprevpage = 0; # We set this when encountering
+ # "%%Page:" and the previous page is not
+ # printed yet. Then it will be printed and
+ # the new page will be prepared in the
+ # next run of the loop (we don't read a
+ # new line and don't increase the
+ # $linect then).
+
+ $fileconverterhandle = undef; # File handle to the fileconverter process
+
+ $fileconverterpid = 0; # PID of the fileconverter process
+
+ $rendererhandle = undef; # File handle to the renderer process
+
+ $rendererpid = 0; # PID of the renderer process
+
+ my $prologfound = 0; # Did we find the
+ # "%%BeginProlog...%%EndProlog" section?
+
+ my $setupfound = 0; # Did we find the
+ # "%%BeginSetup...%%EndSetup" section?
+
+ my $pagesetupfound = 0; # special page setup handling needed
+
+ my $inprolog = 0; # We are between "%%BeginProlog" and
+ # "%%EndProlog".
+
+ my $insetup = 0; # We are between "%%BeginSetup" and
+ # "%%EndSetup".
+
+ my $infeature = 0; # We are between "%%BeginFeature" and
+ # "%%EndFeature".
+
+ my $postscriptsection = 'jclsetup'; # In which section of the PostScript
+ # file are we currently?
+
+ $nondsclines = 0; # Number of subsequent lines found which
+ # are at a non-DSC-conforming place,
+ # between the sections of the header.
+
+ my $optionreplaced = 0; # Will be set to 1 when we are in an
+ # option ("%%BeginFeature...
+ # %%EndFeature") which we have replaced.
+
+ $jobhasjcl = 0; # When the job does not start with
+ # PostScript directly, but is a
+ # PostScript job, we set this to 1
+ # to avoid adding the JCL options
+ # for the second time.
+
+ my $insertoptions = 1; # If we find out that a file with
+ # a DSC magic string
+ # ("%!PS-Adobe-") is not really
+ # DSC-conforming, we insert the
+ # options directly after the line
+ # with the magic string. We use
+ # this variable to store the
+ # number of the line with the
+ # magic string.
+
+ my $currentpage = 0; # The page which we are currently
+ # printing.
+
+ my $ooo110 = 0; # Flag to work around an application
+ # bug.
+
+ if ($dontparse) {
+ # We do not parse the PostScript to find Foomatic options, we check
+ # only whether we have PostScript.
+ $maxlines = 1;
+ }
+
+ print $logh "Reading PostScript input ...\n";
+
+ my $line; # Line to be read from stdin
+ do {
+ my $ignoreline = 0; # Comment line to be ignored when
+ # determining the last active line
+ # and the one before the last
+
+ if (($printprevpage) || ($line=<STDIN>)) {
+
+ if ($linect == $nonpslines) {
+ # In the beginning should be the postscript leader,
+ # sometimes after some JCL commands
+ if ($line !~ m/^.?%!/) { # There can be a Windows control
+ # character before "%!"
+ $nonpslines ++;
+ if ($maxlines == $nonpslines) {
+ $maxlines ++;
+ }
+ $jobhasjcl = 1;
+ if ($nonpslines > $maxlinestopsstart) {
+ # This is not a PostScript job, we must convert it
+ print $logh "${added_lf}Job does not start with \"%!\", " .
+ "is it PostScript?\n" .
+ "Starting file converter\n";
+ # Reset all variables but conserve the data which
+ # we have already read.
+ $jobhasjcl = 0;
+ $linect = 0;
+ $nonpslines = 1; # Take into account that the line
+ # of this run of the loop will be
+ # put into @psheader, so the
+ # first line read by the file
+ # converter is already the second
+ # line.
+ $maxlines = 1001;
+ $onelinebefore = "";
+ $twolinesbefore = "";
+ my $alreadyread = join('', @psheader, @psfifo) .
+ $line;
+ $line = "";
+ @psheader = ();
+ @psfifo = ();
+ # Start the file conversion filter
+ if (!$fileconverterpid) {
+ ($fileconverterhandle, $fileconverterpid) =
+ getfileconverterhandle
+ ($dat, $alreadyread);
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error opening file converter",
+ $retval);
+ }
+ } else {
+ rip_die("File conversion filter probably " .
+ "crashed",
+ $EXIT_JOBERR);
+ }
+ # Read the further data from the file converter and
+ # not from STDIN
+ if (!close STDIN && $! != $ESPIPE) {
+ rip_die ("Couldn't close STDIN",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!open (STDIN, "<&$fileconverterhandle")) {
+ rip_die ("Couldn't dup \$fileconverterhandle",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ }
+ } else {
+ # Do we have a DSC-conforming document?
+ if (($line =~ m/^.?%!PS-Adobe-/) &&
+ ($line !~ m/EPSF/)) {
+ # Do not stop parsing the document
+ if (!$dontparse) {
+ $maxlines = 0;
+ $isdscjob = 1;
+ $insertoptions = $linect + 1;
+ # We have written into @psfifo before,
+ # now we continue in @psheader and move
+ # over the data which is already in @psfifo
+ push (@psheader, @psfifo);
+ @psfifo = ();
+ }
+ print $logh
+ "--> This document is DSC-conforming!\n";
+ } else {
+ # Job is not DSC-conforming, stick in all PostScript
+ # option settings in the beginning
+ $line .= makeprologsection($dat, $optionset, 1);
+ $line .= makesetupsection($dat, $optionset, 1);
+ $line .= makepagesetupsection($dat, $optionset, 1);
+ $prologfound = 1;
+ $setupfound = 1;
+ $pagesetupfound = 1;
+ }
+ }
+ } else {
+ if ($line =~ m/^\s*\%\%BeginDocument[: ]/) {
+ # Beginning of an embedded document
+ # Note that Adobe Acrobat has a bug and so uses
+ # "%%BeginDocument " instead of "%%BeginDocument:"
+ $nestinglevel ++;
+ print $logh "Embedded document, " .
+ "nesting level now: $nestinglevel\n";
+ } elsif (($line =~ m/^\s*\%\%EndDocument/) &&
+ ($nestinglevel > 0)) {
+ # End of an embedded document
+ $nestinglevel --;
+ print $logh "End of Embedded document, " .
+ "nesting level now: $nestinglevel\n";
+ } elsif (($line =~ m/^\s*\%\%Creator[: ](.*)$/) &&
+ ($nestinglevel == 0)) {
+ # Here we set flags to treat particular bugs of the
+ # PostScript produced by certain applications
+ my $creator = $1;
+ if ($creator =~ /^\s*OpenOffice.org\s+1.1.\d+\s*$/) {
+ # OpenOffice.org 1.1.x
+ # The option settings supposed to affect the
+ # whole document are put into the "%%PageSetup"
+ # section of the first page
+ print $logh "Document created with " .
+ "OpenOffice.org 1.1.x\n";
+ $ooo110 = 1;
+ }
+ } elsif (($line =~ m/^\%\%BeginProlog/) &&
+ ($nestinglevel == 0)) {
+ # Note: Below is another place where a "Prolog"
+ # section start will be considered. There we assume
+ # start of the "Prolog" if the job is DSC-Conformimg,
+ # but an arbitrary comment starting with "%%Begin", but
+ # not a comment explicitly treated here, is found. This
+ # is done because many "dvips" (TeX/LaTeX) files miss
+ # the "%%BeginProlog" comment.
+ # Beginning of Prolog
+ print $logh "${added_lf}-----------\nFound: \%\%BeginProlog\n";
+ $inprolog = 1;
+ $postscriptsection = 'prolog' if $inheader;
+ $nondsclines = 0;
+ # Insert options for "Prolog"
+ if (!$prologfound) {
+ $line .= makeprologsection($dat, $optionset, 0);
+ }
+ $prologfound = 1;
+ } elsif (($line =~ m/^\%\%EndProlog/) &&
+ ($nestinglevel == 0)) {
+ # End of Prolog
+ print $logh "Found: \%\%EndProlog\n";
+ $inprolog = 0;
+ $insertoptions = $linect + 1;
+ } elsif (($line =~ m/^\%\%BeginSetup/) &&
+ ($nestinglevel == 0)) {
+ # Beginning of Setup
+ print $logh "${added_lf}-----------\nFound: \%\%BeginSetup\n";
+ $insetup = 1;
+ # We need to distinguish with the $inheader variable
+ # here whether we are in the header or on a page, as
+ # OpenOffice.org inserts a "%%BeginSetup...%%EndSetup"
+ # section after the first "%%Page:..." line and assumes
+ # this section to be valid for all pages.
+ $postscriptsection = 'setup' if $inheader;
+ $nondsclines = 0;
+ if ($inheader) {
+ # If there was no "Prolog" but there are
+ # options for the "Prolog", push a "Prolog"
+ # with these options onto the @psfifo here
+ if (!$prologfound) {
+ # "Prolog" missing, insert it here
+ $line = makeprologsection($dat, $optionset, 1) .
+ $line;
+ # Now we have a "Prolog"
+ $prologfound = 1;
+ }
+ # Insert options for "DocumentSetup" or "AnySetup"
+ if ($spooler ne 'cups') {
+ # For non-CUPS spoolers or no spooler at all, we leave
+ # everything as it is.
+ if (!$setupfound) {
+ $line .= makesetupsection($dat, $optionset, 0);
+ }
+ $setupfound = 1;
+ }
+ } else {
+ # Found option settings must be stuffed into both
+ # the header and the currrent page now. They will
+ # be written into both the "header" and the
+ # "currentpage" optionsets and the PostScript code
+ # lines of this section will not only go into the
+ # output stream, but also added to the end of the
+ # @psheader, so that they get repeated (to preserve
+ # the embedded PostScript option settings) on a
+ # restart of the renderer due to command line
+ # option changes
+ $optionsalsointoheader = 1;
+ print $logh "\"%%BeginSetup\" in page header\n";
+ }
+ } elsif (($line =~ m/^\%\%EndSetup/) &&
+ ($nestinglevel == 0)) {
+ # End of Setup
+ print $logh "Found: \%\%EndSetup\n";
+ $insetup = 0;
+ if ($inheader) {
+ if ($spooler eq 'cups') {
+ # In case of CUPS, we must insert the
+ # accounting stuff just before the
+ # %%EndSetup comment in order to leave any
+ # EndPage procedures that have been
+ # defined by either the pstops filter or
+ # the PostScript job itself fully
+ # functional.
+ if (!$setupfound) {
+ $line = makesetupsection($dat,
+ $optionset, 0) .
+ $line;
+ }
+ $setupfound = 1;
+ }
+ $insertoptions = $linect + 1;
+ } else {
+ # The "%%BeginSetup...%%EndSetup" which
+ # OpenOffice.org has inserted after the first
+ # "%%Page:..." line ends here, so the following
+ # options go only onto the current page again
+ $optionsalsointoheader = 0;
+ }
+ } elsif (($line =~ m/^\%\%Page:(.*)$/) &&
+ ($nestinglevel == 0)) {
+ if ((!$lastpassthru) && (!$inheader)) {
+ # In the last line we were not in passthru mode,
+ # so the last page is not printed. Prepare to do
+ # it now.
+ $printprevpage = 1;
+ # Print the previous page
+ $passthru = 1;
+ print $logh "New page found but previous not " .
+ "printed, print it now.\n";
+ } else {
+ # The previous page is printed, so we can prepare
+ # the current one
+ $printprevpage = 0;
+ print $logh "${added_lf}-----------\nNew page: $1\n";
+ # Count pages
+ $currentpage ++;
+ # We consider the beginning of the page already as
+ # page setup section, as some apps do not use
+ # "%%PageSetup" tags.
+ $postscriptsection = 'pagesetup';
+ # Save PostScript state before beginning the page
+ #$line .= "/foomatic-saved-state save def\n";
+ # Here begins a new page
+ if ($inheader) {
+ # Here we add some stuff which still belongs
+ # into the header
+ my $stillforheader;
+ # If there was no "Setup" but there are
+ # options for the "Setup", push a "Setup"
+ # with these options onto the @psfifo here
+ if (!$setupfound) {
+ # "Setup" missing, insert it here
+ $stillforheader =
+ makesetupsection($dat, $optionset, 1) .
+ $stillforheader;
+ # Now we have a "Setup"
+ $setupfound = 1;
+ }
+ # If there was no "Prolog" but there are
+ # options for the "Prolog", push a "Prolog"
+ # with these options onto the @psfifo here
+ if (!$prologfound) {
+ # "Prolog" missing, insert it here
+ $stillforheader =
+ makeprologsection($dat, $optionset, 1) .
+ $stillforheader;
+ # Now we have a "Prolog"
+ $prologfound = 1;
+ }
+ # Now we push this onto the header
+ push (@psheader, $stillforheader);
+ # The first page starts, so the header ends
+ $inheader = 0;
+ $nondsclines = 0;
+ # Option setting should go into the
+ # page-specific option set now
+ $optionset = 'currentpage';
+ } else {
+ # Restore PostScript state after completing the
+ # previous page:
+ #
+ # foomatic-saved-state restore
+ # %%Page: ...
+ # /foomatic-saved-state save def
+ #
+ # Print this directly, so that if we need to
+ # restart the renderer for this page due to
+ # a command line change this is done under the
+ # old instance of the renderer
+ #print $rendererhandle
+ # "foomatic-saved-state restore\n";
+
+ # Save the option settings of the previous page
+ copyoptions($dat, 'currentpage',
+ 'previouspage');
+ deleteoptions($dat, 'currentpage');
+ }
+ # Initialize the option set
+ copyoptions($dat, 'header', 'currentpage');
+ # Set command line options which apply only
+ # given pages
+ setoptionsforpage($dat, 'currentpage', $currentpage);
+ $pagesetupfound = 0;
+ if ($spooler eq 'cups') {
+ # Remove the "notfirst" flag from all options
+ # forseen for the "PageSetup" section, because
+ # when these are numerical options for CUPS.
+ # they have to be set to the correct value
+ # for every page
+ for my $arg (@{$dat->{'args'}}) {
+ if (($arg->{'section'} eq 'PageSetup') &&
+ (defined($arg->{'notfirst'}))) {
+ delete($arg->{'notfirst'});
+ }
+ }
+ }
+ # Insert PostScript option settings
+ # (options for section "PageSetup".
+ if ($isdscjob) {
+ $line .=
+ makepagesetupsection($dat, $optionset,
+ 0);
+ $pagesetupfound = 1;
+ }
+ # Now the page header comes, so buffer the data,
+ # because we must perhaps shut down and restart
+ # the renderer
+ $passthru = 0;
+ $ignorepageheader = 0;
+ $optionsalsointoheader = 0;
+ }
+ } elsif (($line =~ m/^\%\%BeginPageSetup/) &&
+ ($nestinglevel == 0) &&
+ (!$ignorepageheader)) {
+ # Start of the page header, up to %%EndPageSetup
+ # nothing of the page will be drawn, page-specific
+ # option settngs (as letter-head paper for page 1)
+ # go here
+ print $logh "${added_lf}Found: \%\%BeginPageSetup\n";
+ $passthru = 0;
+ $inpageheader = 1;
+ $postscriptsection = 'pagesetup';
+ if (($ooo110) && ($currentpage == 1)) {
+ $optionsalsointoheader = 1;
+ } else {
+ $optionsalsointoheader = 0;
+ }
+ } elsif (($line =~ m/^\%\%EndPageSetup/) &&
+ ($nestinglevel == 0) &&
+ (!$ignorepageheader)) {
+ # End of the page header, the page is ready to be
+ # printed
+ print $logh "Found: \%\%EndPageSetup\n";
+ print $logh "End of page header\n";
+ # We cannot for sure say that the page header ends here
+ # OpenOffice.org puts (due to a bug) a "%%BeginSetup...
+ # %%EndSetup" section after the first "%%Page:...". It
+ # is possible that CUPS inserts a "%%BeginPageSetup...
+ # %%EndPageSetup" before this section, which means that
+ # the options in the "%%BeginSetup...%%EndSetup" section
+ # are after the "%%EndPageSetup", so we continue for
+ # searching options up to the buffer size limit
+ # $maxlinesforpageoptions.
+ $passthru = 0;
+ $inpageheader = 0;
+ $optionsalsointoheader = 0;
+ } elsif ((($line =~ m/^\%\%(BeginFeature):\s*\*?([^\*\s=]+)\s+()(\S[^\r\n]*)\r?\n?$/) ||
+ ($line =~ m/^\s*\%\%\s*(FoomaticRIPOptionSetting):\s*([^\*\s=]+)\s*=\s*(\@?)([^\@\s][^\r\n]*)\r?\n?$/)) &&
+ ($nestinglevel == 0) &&
+ (!$optionreplaced) &&
+ ((!$passthru) || (!$isdscjob))) {
+ my ($linetype, $option, $fromcomposite, $value) =
+ ($1, $2, $3, $4);
+
+ # Mark that we are in a "Feature" section
+ if ($linetype eq 'BeginFeature') {
+ $infeature = 1;
+ }
+
+ # OK, we have an option. If it's not a
+ # *ostscript-style option (ie, it's command-line or
+ # JCL) then we should note that fact, since the
+ # attribute-to-filter option passing in CUPS is kind of
+ # funky, especially wrt boolean options.
+
+ print $logh "Found: $line";
+ if (my $arg=argbyname($option)) {
+ print $logh " Option: $option=" .
+ ($fromcomposite ? "From" : "") . $value;
+ if (($spooler eq 'cups') &&
+ ($linetype eq 'BeginFeature') &&
+ (!defined($arg->{'notfirst'})) &&
+ ($arg->{$optionset} ne $value) &&
+ (($inheader) ||
+ ($arg->{section} eq 'PageSetup'))) {
+ # We have the first occurence of an
+ # option setting and the spooler is CUPS,
+ # so this setting is inserted by "pstops".
+ # The value from the command line was not
+ # inserted by "pstops" so it seems to be
+ # not under the choices in the PPD.
+ # Possible reasons:
+ #
+ # - "pstops" ignores settings of numerical
+ # or string options which are not one of
+ # the choices in the PPD file, and inserts
+ # the default value instead.
+ #
+ # - On the command line an option was applied
+ # only to selected pages:
+ # "-o <page ranges>:<option>=<values>
+ # This is not supported by CUPS, so not
+ # taken care of by "pstops".
+ #
+ # We must fix this here by replacing the setting
+ # inserted by "pstops" with the exact setting
+ # given on the command line.
+
+ # $arg->{$optionset} is already
+ # range-checked, so do not check again here
+ # Insert DSC comment
+ my $dest = ((($inheader) && ($isdscjob)) ?
+ \@psheader : \@psfifo);
+ push(@{$dest},
+ "%%BeginFeature: " .
+ "*$option $arg->{$optionset}\n");
+ my $val;
+ if ($arg->{'style'} eq 'G') {
+ # PostScript option, insert the code
+ if ($arg->{'type'} eq 'bool') {
+ # Boolean option
+ if (defined($arg->{$optionset}) &&
+ $arg->{$optionset} == 1) {
+ push(@{$dest}, $arg->{'proto'} . "\n");
+ } elsif ($arg->{'protof'}) {
+ push(@{$dest}, $arg->{'protof'}. "\n");
+ }
+ } elsif ((($arg->{'type'} eq 'enum') ||
+ ($arg->{'type'} eq 'string') ||
+ ($arg->{'type'} eq 'password')) &&
+ (defined($val =
+ $arg->{'vals_byname'}{$arg->{$optionset}}))) {
+ # Enumerated choice of string or enum
+ # option
+ push(@{$dest}, $val->{'driverval'} . "\n");
+ } elsif ((($arg->{'type'} eq 'string') ||
+ ($arg->{'type'} eq 'password')) &&
+ ($arg->{$optionset} eq 'None')) {
+ # 'None' is mapped to the empty string in
+ # string options
+ my $driverval = $arg->{'proto'};
+ $driverval =~ s/\%s//g;
+ push(@{$dest}, $driverval . "\n");
+ } else {
+ # Setting for numerical or string option
+ # which is not under the enumerated choices
+ my $sprintfproto = $arg->{'proto'};
+ $sprintfproto =~ s/\%(?!s)/\%\%/g;
+ push(@{$dest},
+ sprintf($sprintfproto,
+ $arg->{$optionset}) .
+ "\n");
+ }
+ } else {
+ # Command line or JCL option
+ push(@{$dest},
+ "%% FoomaticRIPOptionSetting: " .
+ "$option=$arg->{$optionset}\n");
+ }
+ print $logh " --> Correcting numerical/string " .
+ "option to $option=$arg->{$optionset}" .
+ " (Command line argument)\n";
+ # We have replaced this option on the
+ # FIFO
+ $optionreplaced = 1;
+ }
+ # Mark that we have already found this option
+ $arg->{'notfirst'} = 1;
+ if (!$optionreplaced) {
+ if ($arg->{'style'} ne 'G') {
+ # "Controlled by '<Composite>'" setting of
+ # a member option of a composite option
+ if ($fromcomposite) {
+ $value = "From$value";
+ }
+ # Non-PostScript option
+ # Check whether it is valid
+ if (defined(my $newvalue =
+ checkoptionvalue($dat, $option,
+ $value, 0))) {
+ print $logh " --> Setting option\n";
+ # Valid choice, set it.
+ $arg->{$optionset} = $newvalue;
+ if ($optionsalsointoheader) {
+ $arg->{'header'} = $newvalue;
+ }
+ if (($arg->{'type'} eq 'enum') &&
+ (($option eq 'PageSize') ||
+ ($option eq 'PageRegion')) &&
+ ($newvalue =~ /^Custom/) &&
+ ($linetype eq
+ 'FoomaticRIPOptionSetting')) {
+ # Custom page size
+ $twolinesbefore =~
+ /^\s*([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s*$/;
+ my ($w, $h) = ($1, $2);
+ if (($w) && ($h) &&
+ ($w != 0) && ($h != 0)) {
+ $newvalue = "$newvalue.${w}x$h";
+ $arg->{$optionset} = $newvalue;
+ if ($optionsalsointoheader) {
+ $arg->{'header'} =
+ $newvalue;
+ }
+ }
+ }
+ # For a composite option insert the
+ # code from the member options with
+ # current setting "From<composite>"
+ # The code from the member options
+ # is chosen according to the setting
+ # of the composite option.
+ if (($arg->{'style'} eq 'X') &&
+ ($linetype eq
+ 'FoomaticRIPOptionSetting')) {
+ buildcommandline($dat, $optionset);
+ $line .= $arg->{$postscriptsection};
+ }
+ # If this argument is PageSize or
+ # PageRegion, also set the other
+ syncpagesize($dat, $option, $newvalue,
+ $optionset);
+ if ($optionsalsointoheader) {
+ syncpagesize($dat, $option,
+ $newvalue, 'header');
+ }
+ } else {
+ # Invalid option, log it.
+ print $logh " --> Invalid option " .
+ "setting found in job\n";
+ }
+ } elsif ($fromcomposite) {
+ # PostScript option, but we have to look up
+ # the PostScript code to be inserted from
+ # the setting of a composite option, as this
+ # option is set to "Controlled by
+ # '<Composite>'".
+ # Set the option
+ if (defined(my $newvalue =
+ checkoptionvalue
+ ($dat, $option,
+ "From$value", 0))) {
+ print $logh " --> Looking up setting " .
+ "in composite option '$value'\n";
+ # Valid choice, set it.
+ $arg->{$optionset} = $newvalue;
+ if ($optionsalsointoheader) {
+ $arg->{'header'} = $newvalue;
+ }
+ # Update composite options
+ buildcommandline($dat, $optionset);
+ # Substitute PostScript comment by
+ # the real code
+ $line = $arg->{'compositesubst'};
+ } else {
+ # Invalid option, log it.
+ print $logh " --> Invalid option " .
+ "setting found in job\n";
+ }
+ } else {
+ # it is a PostScript style option with
+ # the code readily inserted, no option
+ # for the renderer command line/JCL to set,
+ # no lookup of a composite option needed,
+ # so nothing to do here...
+ print $logh
+ " --> Option will be set by " .
+ "PostScript interpreter\n";
+ }
+ }
+ } else {
+ # This option is unknown to us. WTF?
+ print $logh "Unknown option $option=$value found " .
+ "in the job\n";
+ }
+ } elsif (($line =~ m/^\%\%EndFeature/) &&
+ ($nestinglevel == 0)) {
+ # End of Feature
+ $infeature = 0;
+ # If the option setting was replaced, it ends here, too,
+ # end the next option is not necessarily also replaced.
+ $optionreplaced = 0;
+ } elsif (($line =~ m/^\%\%Begin/) &&
+ ($isdscjob) &&
+ (!$prologfound) &&
+ ($nestinglevel == 0)) {
+ # In some PostScript files (especially when generated
+ # by "dvips" of TeX/LaTeX) the "%%BeginProlog" is
+ # missing, so assume that it was before the current
+ # line (the first line starting with "%%Begin".
+ print $logh "Job claims to be DSC-conforming, but " .
+ "\"%%BeginProlog\" was missing before first " .
+ "line with another \"%%Begin...\" comment " .
+ "(is this a TeX/LaTeX/dvips-generated PostScript " .
+ "file?). Assuming start of \"Prolog\" here.\n";
+ # Beginning of Prolog
+ $inprolog = 1;
+ $nondsclines = 0;
+ # Insert options for "Prolog" before the current line
+ if (!$prologfound) {
+ $line =
+ "%%BeginProlog\n" .
+ makeprologsection($dat, $optionset, 0) .
+ $line;
+ }
+ $prologfound = 1;
+ } elsif (($line =~ m/^\s*\%/) || ($line =~ m/^\s*$/)) {
+ # This is an unknown PostScript comment or a blank line,
+ # no active code
+ $ignoreline = 1;
+ } else {
+ # This line is active PostScript code
+ if ($inheader) {
+ if ((!$inprolog) && (!$insetup)) {
+ # Outside the "Prolog" and "Setup" section
+ # a correct DSC-conforming document has no
+ # active PostScript code, so consider the
+ # file as non-DSC-conforming when there are
+ # too many of such lines.
+ $nondsclines ++;
+ if ($nondsclines > $maxnondsclinesinheader) {
+ # Consider document as not DSC-conforming
+ print $logh "This job seems not to be " .
+ "DSC-conforming, DSC-comment for " .
+ "next section not found, stopping " .
+ "to parse the rest, passing it " .
+ "directly to the renderer.\n";
+ # Stop scanning for further option settings
+ $maxlines = 1;
+ $isdscjob = 0;
+ # Insert defaults and command line settings
+ # in the beginning of the job or after the
+ # last valid section
+ splice(@psheader, $insertoptions, 0,
+ ($prologfound ? () :
+ makeprologsection($dat, $optionset,
+ 1)),
+ ($setupfound ? () :
+ makesetupsection($dat, $optionset,
+ 1)),
+ ($pagesetupfound ? () :
+ makepagesetupsection($dat,
+ $optionset,
+ 1)));
+ $prologfound = 1;
+ $setupfound = 1;
+ $pagesetupfound = 1;
+ }
+ }
+ } else {
+ if (!$inpageheader) {
+ # PostScript code inside a page, but not between
+ # "%%BeginPageSetup" and "%%EndPageSetup", so
+ # we are perhaps already drawing onto a page now
+ if ($onelinebefore =~ m/^\%\%Page:/) {
+ print $logh "No page header or page " .
+ "header not DSC-conforming\n";
+ }
+ # Stop buffering lines to search for options
+ # placed not DSC-conforming
+ if (scalar(@psfifo) >=
+ $maxlinesforpageoptions) {
+ print $logh "Stopping search for " .
+ "page header options\n";
+ $passthru = 1;
+ # If there comes a page header now, ignore
+ # it
+ $ignorepageheader = 1;
+ $optionsalsointoheader = 0;
+ }
+ }
+ }
+ }
+ }
+
+ # Debug info
+ if ($lastpassthru != $passthru) {
+ if ($passthru) {
+ print $logh "Found:\n $line" .
+ " --> Output goes directly to the renderer now.\n${added_lf}";
+ } else {
+ print $logh "Found:\n $line" .
+ " --> Output goes to the FIFO buffer now.${added_lf}\n";
+ }
+ }
+
+ # We are in an option which was replaced, do not output
+ # the current line.
+ if ($optionreplaced) {
+ $line = "";
+ }
+
+ # If we are in a "%%BeginSetup...%%EndSetup" section after
+ # the first "%%Page:..." and the current line belongs to
+ # an option setting, we have to copy the line also to the
+ # @psheader.
+ if (($optionsalsointoheader) &&
+ (($infeature) || ($line =~ m/^\%\%EndFeature/))) {
+ push (@psheader, $line);
+ }
+
+ # Store or send the current line
+ if (($inheader) && ($isdscjob)) {
+ # We are still in the PostScript header, collect all lines
+ # in @psheader
+ push (@psheader, $line);
+ } else {
+ if (($passthru) && ($isdscjob)) {
+ if (!$lastpassthru) {
+ # We enter passthru mode with this line, so the
+ # command line can have changed, check it and
+ # close the renderer if needed
+ if (($rendererpid) &&
+ (!optionsequal($dat, 'currentpage',
+ 'previouspage', 0))) {
+ print $logh "Command line/JCL options " .
+ "changed, restarting renderer\n";
+ $retval = closerendererhandle
+ ($rendererhandle, $rendererpid);
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error closing renderer",
+ $retval);
+ }
+ $rendererpid = 0;
+ }
+ }
+ # Flush @psfifo and send line directly to the renderer
+ if (!$rendererpid) {
+ # No renderer running, start it
+ ($rendererhandle, $rendererpid) =
+ getrendererhandle
+ ($dat, join('', @psheader, @psfifo));
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error opening renderer",
+ $retval);
+ }
+ # @psfifo is sent out, flush it.
+ @psfifo = ();
+ }
+ if ($#psfifo >= 0) {
+ # Send @psfifo to renderer
+ print $rendererhandle join('', @psfifo);
+ # flush @psfifo
+ @psfifo = ();
+ }
+ # Send line to renderer
+ if (!$printprevpage) {
+ print $rendererhandle $line;
+ }
+ } else {
+ # Push the line onto the stack for later spitting up...
+ push (@psfifo, $line);
+ }
+ }
+
+ if (!$printprevpage) {
+ $linect++;
+ }
+
+ } else {
+ # EOF!
+ $more_stuff = 0;
+ # No PostScript header in the whole file? Then it's not
+ # PostScript, convert it.
+ # We open the file converter here when the file has less
+ # lines than the amount which we search for the PostScript
+ # header ($maxlinestopsstart).
+ if ($linect <= $nonpslines) {
+ # This is not a PostScript job, we must convert it
+ print $logh "${added_lf}Job does not start with \"%!\", " .
+ "is it PostScript?\n" .
+ "Starting file converter\n";
+ # Reset all variables but conserve the data which
+ # we have already read.
+ $jobhasjcl = 0;
+ $linect = 0;
+ $nonpslines = 0;
+ $maxlines = 1000;
+ $onelinebefore = "";
+ $twolinesbefore = "";
+ my $alreadyread = join('', @psheader, @psfifo);
+ @psheader = ();
+ @psfifo = ();
+ $line = "";
+ # Start the file conversion filter
+ if (!$fileconverterpid) {
+ ($fileconverterhandle, $fileconverterpid) =
+ getfileconverterhandle($dat, $alreadyread);
+ if ( defined($retval) and $retval != $EXIT_PRINTED) {
+ rip_die ("Error opening file converter",
+ $retval);
+ }
+ } else {
+ rip_die("File conversion filter probably " .
+ "crashed",
+ $EXIT_JOBERR);
+ }
+ # Read the further data from the file converter and
+ # not from STDIN
+ if (!close STDIN && $! != $ESPIPE) {
+ rip_die ("Couldn't close STDIN",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!open (STDIN, "<&$fileconverterhandle")) {
+ rip_die ("Couldn't dup \$fileconverterhandle",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ # Now we have new (converted) stuff in STDIN, so
+ # continue in the loop
+ $more_stuff = 1;
+ }
+ }
+
+ $lastpassthru = $passthru;
+
+ if ((!$ignoreline) && (!$printprevpage)) {
+ $twolinesbefore = $onelinebefore;
+ $onelinebefore = $line;
+ }
+
+ } while ((($maxlines == 0) or ($linect < $maxlines)) and
+ ($more_stuff != 0));
+
+ # Some buffer still containing data? Send it out to the renderer.
+ if (($more_stuff != 0) || ($inheader) || ($#psfifo >= 0)) {
+ # Flush @psfifo and send the remaining data to the renderer, this
+ # only happens with non-DSC-conforming jobs or non-Foomatic PPDs
+ if ($more_stuff) {
+ print $logh "Stopped parsing the PostScript data, ".
+ "sending rest directly to renderer.\n";
+ } else {
+ print $logh "Flushing FIFO.\n";
+ }
+ if ($inheader) {
+ # No page initialized yet? Copy the "header" option set into the
+ # "currentpage" option set, so that the renderer will find the
+ # options settings.
+ copyoptions($dat, 'header', 'currentpage');
+ $optionset = 'currentpage';
+ # If not done yet, insert defaults and command line settings
+ # in the beginning of the job or after the last valid section
+ splice(@psheader, $insertoptions, 0,
+ ($prologfound ? () :
+ makeprologsection($dat, $optionset, 1)),
+ ($setupfound ? () :
+ makesetupsection($dat, $optionset, 1)),
+ ($pagesetupfound ? () :
+ makepagesetupsection($dat, $optionset, 1)));
+ $prologfound = 1;
+ $setupfound = 1;
+ $pagesetupfound = 1;
+ }
+ if (($rendererpid) &&
+ (!optionsequal($dat, 'currentpage',
+ 'previouspage', 0))) {
+ print $logh "Command line/JCL options " .
+ "changed, restarting renderer\n";
+ $retval = closerendererhandle
+ ($rendererhandle, $rendererpid);
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error closing renderer",
+ $retval);
+ }
+ $rendererpid = 0;
+ }
+ if (!$rendererpid) {
+ ($rendererhandle, $rendererpid) =
+ getrendererhandle($dat, join('', @psheader, @psfifo));
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error opening renderer",
+ $retval);
+ }
+ # We have sent @psfifo now
+ @psfifo = ();
+ }
+ if ($#psfifo >= 0) {
+ # Send @psfifo to renderer
+ print $rendererhandle join('', @psfifo);
+ # flush @psfifo
+ @psfifo = ();
+ }
+ # Print the rest of the input data
+ if ($more_stuff) {
+ while (<STDIN>) {
+ print $rendererhandle $_;
+ }
+ }
+ }
+
+ # At every "%%Page:..." comment we have saved the PostScript state
+ # and we have increased the page number. So if the page number is
+ # non-zero we had at least one "%%Page:..." comment and so we have
+ # to give a restore the PostScript state.
+ #if ($currentpage > 0) {
+ # print $rendererhandle "foomatic-saved-state restore\n";
+ #}
+
+ # Close the renderer
+ if ($rendererpid) {
+ $retval = closerendererhandle ($rendererhandle, $rendererpid);
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error closing renderer",
+ $retval);
+ }
+ $rendererpid = 0;
+ }
+
+ # Close the file converter (if it was used)
+ if ($fileconverterpid) {
+ $retval = closefileconverterhandle
+ ($fileconverterhandle, $fileconverterpid);
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error closing file converter",
+ $retval);
+ }
+ $fileconverterpid = 0;
+ }
+}
+
+
+## Close the documentation page generator
+if ($docgeneratorpid) {
+ $retval = closedocgeneratorhandle
+ ($docgeneratorhandle, $docgeneratorpid);
+ if ($retval != $EXIT_PRINTED) {
+ rip_die ("Error closing documentation page generator",
+ $retval);
+ }
+ $docgeneratorpid = 0;
+}
+
+
+
+## Close last input file
+close STDIN;
+
+
+
+## Only for debugging
+if ($debug && 1) {
+ use Data::Dumper;
+ local $Data::Dumper::Purity=1;
+ local $Data::Dumper::Indent=1;
+ print $logh Dumper($dat);
+}
+
+
+
+## The End
+print $logh "${added_lf}Closing foomatic-rip.\n";
+close $logh;
+
+exit $retval;
+
+
+
+## Functions to let foomatic-rip fork to do several tasks in parallel.
+
+# To do the filtering without loading the whole file into memory we work
+# on a data stream, we read the data line by line analyse it to decide what
+# filters to use and start the filters if we have found out which we need.
+# We buffer the data only as long as we didn't determing which filters to
+# use for this piece of data and with which options. There are no temporary
+# files used.
+
+# foomatic-rip splits into up to 6 parallel processes to do the whole
+# filtering (listed in the order of the data flow):
+
+# KID0: Generate documentation pages (only jobs with "docs" option)
+# KID2: Put together already read data and current input stream for
+# feeding into the file conversion filter (only non-PostScript
+# and "docs" jobs)
+# KID1: Run the file conversion filter to convert non-PostScript
+# input into PostScript (only non-PostScript and "docs" jobs)
+# MAIN: Prepare the job auto-detecting the spooler, reading the PPD,
+# extracting the options from the command line, and parsing
+# the job data itself. It analyses the job data to check
+# whether it is PostScript and starts KID1/KID2 if not, it
+# also stuffs PostScript code from option settings into the
+# PostScript data stream. It starts the renderer (KID3/KID4)
+# as soon as it knows its command line and restarts it when
+# page-specific option settings need another command line
+# or different JCL commands.
+# KID3: The rendering process. In most cases GhostScript, "cat"
+# for native PostScript printers with their manufacturer's
+# PPD files.
+# KID4: Put together the JCL commands and the renderer's output
+# and send all that either to STDOUT or pipe it into the
+# command line defined with $postpipe.
+
+## This function runs the renderer command line (and if defined also
+## the postpipe) and returns a file handle for stuffing in the
+## PostScript data.
+sub getrendererhandle {
+
+ my ($dat, $prepend) = @_;
+
+ print $logh "${added_lf}Starting renderer\n";
+
+ # Catch signals
+ $retval = $EXIT_PRINTED;
+ use sigtrap qw(handler set_exit_prnerr USR1
+ handler set_exit_prnerr_noretry USR2
+ handler set_exit_engaged TTIN);
+
+ # Variables for the kid processes reporting their state
+
+ # Set up a pipe for the kids to pass their exit stat to the main process
+ pipe KID_MESSAGE, KID_MESSAGE_IN;
+
+ # When one kid fails put the exit stat here
+ $kidfailed = 0;
+
+ # When a kid exits successfully, mark it here
+ $kid3finished = 0;
+ $kid4finished = 0;
+
+ # Build the command line and get the JCL commands
+ buildcommandline($dat, 'currentpage');
+ my $commandline = $dat->{'currentcmd'};
+ my @jclprepend = @{$dat->{'jclprepend'}} if defined $dat->{'jclprepend'};
+ my @jclappend = @{$dat->{'jclappend'}} if defined $dat->{'jclappend'};
+
+ use IO::Handle;
+ pipe KID3_IN, KID3;
+ KID3->autoflush(1);
+ $kid3 = fork();
+ if (!defined($kid3)) {
+ close KID3;
+ close KID3_IN;
+ print $logh "$0: cannot fork for kid3!\n";
+ rip_die ("can't fork for kid3",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if ($kid3) {
+
+ # we are the parent; return a glob to the filehandle
+ close KID3_IN;
+
+ # Feed in the PostScript header and the FIFO contents
+ print KID3 $prepend;
+
+ KID3->flush();
+ return ( *KID3, $kid3 );
+
+ } else {
+ close KID3;
+
+ pipe KID4_IN, KID4;
+ KID4->autoflush(1);
+ $kid4 = fork();
+ if (!defined($kid4)) {
+ close KID4;
+ close KID4_IN;
+ print $logh "$0: cannot fork for kid4!\n";
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("can't fork for kid4",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+
+ if ($kid4) {
+ # parent, child of primary task; we are |commandline|
+ close KID4_IN;
+
+ print $logh "renderer PID kid4=$kid4\n";
+ print $logh "renderer command: $commandline\n";
+
+ if (!close STDIN && $! != $ESPIPE) {
+ close KID3_IN;
+ close KID4;
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN
+ "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("Couldn't close STDIN in $kid4",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!open (STDIN, "<&KID3_IN")) {
+ close KID3_IN;
+ close KID4;
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN
+ "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("Couldn't dup KID3_IN",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!close STDOUT) {
+ close KID3_IN;
+ close KID4;
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN
+ "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("Couldn't close STDOUT in $kid4",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!open (STDOUT, ">&KID4")) {
+ close KID3_IN;
+ close KID4;
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN
+ "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("Couldn't dup KID4",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if ($debug) {
+ if (!open (STDERR, ">&$logh")) {
+ close KID3_IN;
+ close KID4;
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN
+ "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("Couldn't dup logh to stderr",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ }
+
+ # Massage commandline to execute foomatic-gswrapper
+ my $havewrapper = 0;
+ for (split(':', $ENV{'PATH'})) {
+ if (-x "$_/foomatic-gswrapper") {
+ $havewrapper=1;
+ last;
+ }
+ }
+ if ($havewrapper) {
+ $commandline =~ s!^\s*gs\s!foomatic-gswrapper !g;
+ $commandline =~ s!(\|\s*)gs\s!\|foomatic-gswrapper !g;
+ $commandline =~ s!(;\s*)gs\s!; foomatic-gswrapper !g;
+ }
+
+ # If the renderer command line contains the "echo"
+ # command, replace the "echo" by the user-chosen $myecho
+ # (important for non-GNU systems where GNU echo is in a
+ # special path
+ $commandline =~ s!^\s*echo\s!$myecho !g;
+ $commandline =~ s!(\|\s*)echo\s!\|$myecho !g;
+ $commandline =~ s!(;\s*)echo\s!; $myecho !g;
+
+ # In debug mode save the data supposed to be fed into the
+ # renderer also into a file
+ if ($debug) {
+ $commandline = "tee -a ${logfile}.ps | ( $commandline )";
+ }
+
+ # Actually run the thing...
+ system("$commandline");
+ if ($? != 0) {
+ my $rendererretval = $? >> 8;
+ print $logh "renderer return value: $rendererretval\n";
+ my $renderersignal = $? & 127;
+ print $logh "renderer received signal: $rendererretval\n";
+ close STDOUT;
+ close KID4;
+ close STDIN;
+ close KID3_IN;
+ # Handle signals
+ if ($renderersignal == SIGUSR1) {
+ $retval = $EXIT_PRNERR;
+ } elsif ($renderersignal == SIGUSR2) {
+ $retval = $EXIT_PRNERR_NORETRY;
+ } elsif ($renderersignal == SIGTTIN) {
+ $retval = $EXIT_ENGAGED;
+ }
+ if ($retval != $EXIT_PRINTED) {
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $retval\n";
+ close KID_MESSAGE_IN;
+ exit $retval;
+ }
+ # Evaluate renderer result
+ if ($rendererretval == 0) {
+ # Success, exit with 0 and inform main process
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_PRINTED\n";
+ close KID_MESSAGE_IN;
+ exit $EXIT_PRINTED;
+ } elsif ($rendererretval == 1) {
+ # Syntax error? PostScript error?
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
+ close KID_MESSAGE_IN;
+ rip_die ("Possible error on renderer command line or PostScript error. Check options.",
+ $EXIT_JOBERR);
+ } elsif ($rendererretval == 139) {
+ # Seems to indicate a core dump
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
+ close KID_MESSAGE_IN;
+ rip_die ("The renderer may have dumped core.",
+ $EXIT_JOBERR);
+ } elsif ($rendererretval == 141) {
+ # Broken pipe, presumably additional filter interface
+ # exited.
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_PRNERR\n";
+ close KID_MESSAGE_IN;
+ rip_die ("A filter used in addition to the renderer" .
+ " itself may have failed.",
+ $EXIT_PRNERR);
+ } elsif (($rendererretval == 243) || ($retval == 255)) {
+ # PostScript error?
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_JOBERR\n";
+ close KID_MESSAGE_IN;
+ exit $EXIT_JOBERR;
+ } else {
+ # Unknown error
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_PRNERR\n";
+ close KID_MESSAGE_IN;
+ rip_die ("The renderer command line returned an" .
+ " unrecognized error code $rendererretval.",
+ $EXIT_PRNERR);
+ }
+ }
+ close STDOUT;
+ close KID4;
+ close STDIN;
+ close KID3_IN;
+ # When arrived here the renderer command line was successful
+ # So exit with zero exit value here and inform the main process
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "3 $EXIT_PRINTED\n";
+ close KID_MESSAGE_IN;
+ # Wait for postpipe/output child
+ waitpid($kid4, 0);
+ print $logh "KID3 finished\n";
+ exit $EXIT_PRINTED;
+ } else {
+ # child, trailing task on the pipe; we write jcl stuff
+ close KID4;
+ close KID3_IN;
+
+ my $fileh = *STDOUT;
+
+ # Do we have a $postpipe, if yes, launch the command(s) and
+ # point our output into it/them
+ if ($postpipe) {
+ if (!open PIPE,$postpipe) {
+ close KID4_IN;
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN
+ "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("cannot execute postpipe $postpipe",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ $fileh = *PIPE;
+ }
+
+ # Debug output
+ print $logh "JCL: " . join("", @jclprepend) . "<job data> ${added_lf}" .
+ join("", @jclappend) . "\n";
+
+ # wrap the JCL around the job data, if there are any
+ # options specified...
+ # Should the driver already have inserted JCL commands we merge
+ # our JCL header with the one from the driver
+ my $driverjcl = 0;
+ if ( @jclprepend > 1 ) {
+ # JCL header read from renderer output
+ my @jclheader = ();
+ # Determine magic string of JCL in use (usually "@PJL")
+ # For that we take the first part of the second JCL line up
+ # to the first space
+ if ($jclprepend[1] =~ /^(\S+)/) {
+ my $jclstr = $1;
+ # Read from the renderer output until the first non-JCL
+ # line appears
+ while (my $line = <KID4_IN>) {
+ push(@jclheader, $line);
+ last if ($line !~ /$jclstr/);
+ }
+ # If we had read at least two lines, at least one is
+ # a JCL header, so do the merging
+ if (@jclheader > 1) {
+ $driverjcl = 1;
+ # Discard the first and the last entry of the
+ # @jclprepend array, we only need the option settings
+ # to merge them in
+ pop(@jclprepend);
+ shift(@jclprepend);
+ # Line after which we insert new JCL commands in the
+ # JCL header of the job
+ my $insert = 1;
+ # Go through every JCL command in @jclprepend
+ for my $line (@jclprepend) {
+ # Search the command in the JCL header from the
+ # driver. As search term use only the string from
+ # the beginning of the line to the "=", so the
+ # command will also be found when it has another
+ # value
+ $line =~ /^([^=]+)/;
+ my $cmd = $1;
+ my $cmdfound = 0;
+ for (@jclheader) {
+ # If the command is there, replace it
+ $_ =~ s/$cmd\b.*(\r\n|\n|\r)/$line/ and
+ $cmdfound = 1;
+ }
+ if (!$cmdfound) {
+ # If the command is not found, insert it
+ if (@jclheader > 2) {
+ # @jclheader has more than one line,
+ # insert the new command beginning
+ # right after the first line and continuing
+ # after the previous inserted command
+ splice(@jclheader, $insert, 0, $line);
+ $insert ++;
+ } else {
+ # If we have only one line of JCL it
+ # is probably something like the
+ # "@PJL ENTER LANGUAGE=..." line
+ # which has to be in the end, but
+ # it also contains the
+ # "<esc>%-12345X" which has to be in the
+ # beginning of the job. So we split the
+ # line right before the $jclstr and
+ # append our command to the end of the
+ # first part and let the second part
+ # be a second JCL line.
+ $jclheader[0] =~
+ /^(.*?)($jclstr.*(\r\n|\n|\r))/;
+ my $first = "$1$line";
+ my $second = "$2";
+ my $third = $jclheader[1];
+ @jclheader = ($first, $second, $third);
+ }
+ }
+ }
+ # Now pass on the merged JCL header
+ print $fileh @jclheader;
+ } else {
+ # The driver didn't create a JCL header, simply
+ # prepend ours and then pass on the line which we
+ # already have read
+ print $fileh @jclprepend, @jclheader;
+ }
+ } else {
+ # No merging of JCL header possible, simply prepend it
+ print $fileh @jclprepend;
+ }
+ }
+
+ # The rest of the job data
+ while (<KID4_IN>) {
+ print $fileh $_;
+ }
+
+ # A JCL trailer
+ if (( @jclprepend > 1 ) && (!$driverjcl)) {
+ print $fileh @jclappend;
+ }
+
+ if (!close $fileh) {
+ close KID4_IN;
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN
+ "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_IN;
+ rip_die ("error closing $fileh",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ close KID4_IN;
+
+ print $logh "tail process done writing data to STDOUT\n";
+
+ # Handle signals of the backend interface
+ if ($retval != $EXIT_PRINTED) {
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "4 $retval\n";
+ close KID_MESSAGE_IN;
+ exit $retval;
+ }
+
+ # Successful exit, inform main process
+ close KID_MESSAGE;
+ print KID_MESSAGE_IN "4 $EXIT_PRINTED\n";
+ close KID_MESSAGE_IN;
+
+ print $logh "KID4 finished\n";
+ exit($EXIT_PRINTED);
+ }
+ }
+}
+
+
+
+## Close the renderer process and wait until all kid processes finish.
+
+sub closerendererhandle {
+
+ my ($rendererhandle, $rendererpid) = @_;
+
+ print $logh "${added_lf}Closing renderer\n";
+
+ # Do it!
+ close $rendererhandle;
+
+ # Wait for all kid processes to finish or one kid process to fail
+ close KID_MESSAGE_IN;
+ while ((!$kidfailed) &&
+ !(($kid3finished) &&
+ ($kid4finished))) {
+ my $message = <KID_MESSAGE>;
+ chomp $message;
+ if ($message =~ /(\d+)\s+(\d+)/) {
+ my $kid_id = $1;
+ my $exitstat = $2;
+ print $logh "KID$kid_id exited with status $exitstat\n";
+ if ($exitstat > 0) {
+ $kidfailed = $exitstat;
+ } elsif ($kid_id == 3) {
+ $kid3finished = 1;
+ } elsif ($kid_id == 4) {
+ $kid4finished = 1;
+ }
+ }
+ }
+
+ close KID_MESSAGE;
+
+ # If a kid failed, return the exit stat of this kid
+ if ($kidfailed != 0) {
+ $retval = $kidfailed;
+ }
+
+ print $logh "Renderer exit stat: $retval\n";
+ # Wait for renderer child
+ waitpid($rendererpid, 0);
+ print $logh "Renderer process finished\n";
+ return ($retval);
+}
+
+
+
+## This function is only used when the input data is not
+## PostScript. Then it runs a filter which converts non-PostScript
+## files into PostScript. The user can choose which filter he wants
+## to use. The filter command line is provided by $fileconverter.
+
+sub getfileconverterhandle {
+
+ # Already read data must be converted, too
+ my ($dat, $alreadyread) = @_;
+
+ print $logh "${added_lf}Starting converter for non-PostScript files\n";
+
+ # Determine with which command non-PostScript files are converted
+ # to PostScript
+ if ($fileconverter eq "") {
+ if ($spoolerfileconverters->{$spooler}) {
+ $fileconverter = $spoolerfileconverters->{$spooler};
+ } else {
+ for my $c (@fileconverters) {
+ ($c =~ m/^\s*(\S+)\s+/) || ($c = m/^\s*(\S+)$/);
+ my $command = $1;
+ if( -x $command ){
+ $fileconverter = $command;
+ } else {
+ for (split(':', $ENV{'PATH'})) {
+ if (-x "$_/$command") {
+ $fileconverter = $c;
+ last;
+ }
+ }
+ }
+ if ($fileconverter ne "") {
+ last;
+ }
+ }
+ }
+ if ($fileconverter eq "") {
+ $fileconverter = "echo \"Cannot convert file to " .
+ "PostScript!\" 1>&2";
+ }
+ }
+
+ # Insert the page size into the $fileconverter
+ if ($fileconverter =~ /\@\@([^@]+)\@\@PAGESIZE\@\@/) {
+ # We always use the "header" option swt here, with a
+ # non-PostScript file we have no "currentpage"
+ my $optstr = $1;
+ my $arg;
+ my $sizestr = (($arg = $dat->{'args_byname'}{'PageSize'})
+ ? $arg->{'header'}
+ : "");
+ if ($sizestr) {
+ # Use wider margins so that the pages come out completely on
+ # every printer model (especially HP inkjets)
+ if ($fileconverter =~ /^\s*(a2ps)\s+/) {
+ if (lc($sizestr) eq "letter") {
+ $sizestr = "Letterdj";
+ } elsif (lc($sizestr) eq "a4") {
+ $sizestr = "A4dj";
+ }
+ }
+ $optstr .= $sizestr;
+ } else {
+ $optstr = "";
+ }
+ $fileconverter =~ s/\@\@([^@]+)\@\@PAGESIZE\@\@/$optstr/;
+ }
+
+ # Insert the job title into the $fileconverter
+ if ($fileconverter =~ /\@\@([^@]+)\@\@JOBTITLE\@\@/) {
+ if ($do_docs) {
+ $jobtitle =
+ "Documentation for the $model";
+ }
+ my $titlearg = $1;
+ my ($arg, $optstr);
+ ($arg = $jobtitle) =~ s/\"/\\\"/g;
+ if (($titlearg =~ /\"/) || $arg) {
+ $optstr = $titlearg . ($titlearg =~ /\"/ ? '' : '"') .
+ ($arg ? "$arg\"" : '"');
+ } else {
+ $optstr = "";
+ }
+ $fileconverter =~ s/\@\@([^@]+)\@\@JOBTITLE\@\@/$optstr/;
+ }
+
+ # Apply "pstops" when having used a file converter under CUPS, so
+ # CUPS can stuff the default settings into the PostScript output
+ # of the file converter (so all CUPS settings get also applied when
+ # one prints the documentation pages (all other files we get
+ # already converted to PostScript by CUPS.
+ if ($spooler eq 'cups') {
+ $fileconverter .=
+ " | ${programdir}pstops '$rargs[0]' '$rargs[1]' '$rargs[2]' " .
+ "'$rargs[3]' '$rargs[4]'";
+ }
+
+ # Variables for the kid processes reporting their state
+
+ # Set up a pipe for the kids to pass their exit stat to the main process
+ pipe KID_MESSAGE_CONV, KID_MESSAGE_CONV_IN;
+
+ # When one kid fails put the exit stat here
+ $convkidfailed = 0;
+
+ # When a kid exits successfully, mark it here
+ $kid1finished = 0;
+ $kid2finished = 0;
+
+ use IO::Handle;
+ pipe KID1_IN, KID1;
+ KID1->autoflush(1);
+ my $kid1 = fork();
+ if (!defined($kid1)) {
+ close KID1;
+ close KID1_IN;
+ print $logh "$0: cannot fork for kid1!\n";
+ rip_die ("can't fork for kid1",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+
+ if ($kid1) {
+
+ # we are the parent; return a glob to the filehandle
+ close KID1;
+
+ return ( *KID1_IN, $kid1 );
+
+ } else {
+ # We go on reading the job data and stuff it into the file
+ # converter
+ close KID1_IN;
+
+ pipe KID2_IN, KID2;
+ KID2->autoflush(1);
+ $kid2 = fork();
+ if (!defined($kid2)) {
+ print $logh "$0: cannot fork for kid2!\n";
+ close KID1;
+ close KID2;
+ close KID2_IN;
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN
+ "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ rip_die ("can't fork for kid2",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+
+ if ($kid2) {
+ # parent, child of primary task; we are |$fileconverter|
+ close KID2;
+
+ print $logh "file converter PID kid2=$kid2\n";
+ if (($debug) || ($spooler ne 'cups')) {
+ print $logh "file converter command: $fileconverter\n";
+ }
+
+ if (!close STDIN && $! != $ESPIPE) {
+ close KID1;
+ close KID2_IN;
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN
+ "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_CONV_IN;
+ rip_die ("Couldn't close STDIN in $kid2",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!open (STDIN, "<&KID2_IN")) {
+ close KID1;
+ close KID2_IN;
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN
+ "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_CONV_IN;
+ rip_die ("Couldn't dup KID2_IN",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!close STDOUT) {
+ close KID1;
+ close KID2_IN;
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN
+ "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_CONV_IN;
+ rip_die ("Couldn't close STDOUT in $kid2",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if (!open (STDOUT, ">&KID1")) {
+ close KID1;
+ close KID2_IN;
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN
+ "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_CONV_IN;
+ rip_die ("Couldn't dup KID1",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ if ($debug) {
+ if (!open (STDERR, ">&$logh")) {
+ close KID1;
+ close KID2_IN;
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN
+ "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_CONV_IN;
+ rip_die ("Couldn't dup logh to stderr",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ }
+
+ # Actually run the thing...
+ system("$fileconverter");
+ if ($? != 0) {
+ my $fileconverterretval = $? >> 8;
+ print $logh "file converter return value: " .
+ "$fileconverterretval\n";
+ my $fileconvertersignal = $? & 127;
+ print $logh "file converter received signal: ".
+ "$fileconverterretval\n";
+ close STDOUT;
+ close KID1;
+ close STDIN;
+ close KID2_IN;
+ # Handle signals
+ if ($fileconvertersignal == SIGUSR1) {
+ $retval = $EXIT_PRNERR;
+ } elsif ($fileconvertersignal == SIGUSR2) {
+ $retval = $EXIT_PRNERR_NORETRY;
+ } elsif ($fileconvertersignal == SIGTTIN) {
+ $retval = $EXIT_ENGAGED;
+ }
+ if ($retval != $EXIT_PRINTED) {
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN "1 $retval\n";
+ close KID_MESSAGE_CONV_IN;
+ exit $retval;
+ }
+ # Evaluate fileconverter result
+ if ($fileconverterretval == 0) {
+ # Success, exit with 0 and inform main process
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n";
+ close KID_MESSAGE_CONV_IN;
+ exit $EXIT_PRINTED;
+ } else {
+ # Unknown error
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR\n";
+ close KID_MESSAGE_CONV_IN;
+ rip_die ("The file converter command line returned " .
+ "an unrecognized error code " .
+ "$fileconverterretval.",
+ $EXIT_PRNERR);
+ }
+ }
+ close STDOUT;
+ close KID1;
+ close STDIN;
+ close KID2_IN;
+ # When arrived here the fileconverter command line was
+ # successful.
+ # So exit with zero exit value here and inform the main process
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n";
+ close KID_MESSAGE_CONV_IN;
+ # Wait for input child
+ waitpid($kid1, 0);
+ print $logh "KID1 finished\n";
+ exit $EXIT_PRINTED;
+ } else {
+ # child, first part of the pipe, reading in the data from
+ # standard input and stuffing it into the file converter
+ # after putting in the already read data (in $alreadyread)
+ close KID1;
+ close KID2_IN;
+
+ # At first pass the data which we have already read to the
+ # filter
+ print KID2 $alreadyread;
+ # Then read the rest from standard input
+ while (<STDIN>) {
+ print KID2 $_;
+ }
+
+ if (!close STDIN && $! != $ESPIPE) {
+ close KID2;
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN
+ "2 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n";
+ close KID_MESSAGE_CONV_IN;
+ rip_die ("error closing STDIN",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ close KID2;
+
+ print $logh "tail process done reading data from STDIN\n";
+
+ # Successful exit, inform main process
+ close KID_MESSAGE_CONV;
+ print KID_MESSAGE_CONV_IN "2 $EXIT_PRINTED\n";
+ close KID_MESSAGE_CONV_IN;
+
+ print $logh "KID2 finished\n";
+ exit($EXIT_PRINTED);
+ }
+ }
+}
+
+
+
+## Close the file conversion process and wait until all kid processes
+## finish.
+
+sub closefileconverterhandle {
+
+ my ($fileconverterhandle, $fileconverterpid) = @_;
+
+ print $logh "${added_lf}Closing file converter\n";
+
+ # Do it!
+ close $fileconverterhandle;
+
+ # Wait for all kid processes to finish or one kid process to fail
+ close KID_MESSAGE_CONV_IN;
+ while ((!$convkidfailed) &&
+ !(($kid1finished) &&
+ ($kid2finished))) {
+ my $message = <KID_MESSAGE_CONV>;
+ chomp $message;
+ if ($message =~ /(\d+)\s+(\d+)/) {
+ my $kid_id = $1;
+ my $exitstat = $2;
+ print $logh "KID$kid_id exited with status $exitstat\n";
+ if ($exitstat > 0) {
+ $convkidfailed = $exitstat;
+ } elsif ($kid_id == 1) {
+ $kid1finished = 1;
+ } elsif ($kid_id == 2) {
+ $kid2finished = 1;
+ }
+ }
+ }
+
+ close KID_MESSAGE_CONV;
+
+ # If a kid failed, return the exit stat of this kid
+ if ($convkidfailed != 0) {
+ $retval = $convkidfailed;
+ }
+
+ print $logh "File converter exit stat: $retval\n";
+ # Wait for fileconverter child
+ waitpid($fileconverterpid, 0);
+ print $logh "File converter process finished\n";
+ return ($retval);
+}
+
+
+
+## Generate the documentation page and return a filehandle to get it
+
+sub getdocgeneratorhandle {
+
+ # The data structure with the options
+ my ($dat) = @_;
+
+ print $logh "${added_lf}Generating documentation page for the $model\n";
+
+ # Printer queue name
+ my $printerstr;
+ if ($printer) {
+ $printerstr = $printer;
+ } else {
+ $printerstr = "<printer>";
+ }
+
+ # Spooler-specific differences
+ my ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize);
+ if ($spooler eq 'cups') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("lpr -P $printerstr ",
+ "-o ", "", "=", "",
+ "-o ", "no", "", "=", "",
+ "-o ", "", "=", "",
+ "-o ", "", "=", "",
+ " "," <file>",
+ "\n Custom size: -o PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: -o PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'lpd') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("lpr -P $printerstr -J \"",
+ "", "", "=", "",
+ "", "", "", "=", "",
+ "", "", "=", "",
+ "", "", "=", "",
+ " ", "\" <file>",
+ "\n Custom size: PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'gnulpr') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("lpr -P $printerstr ",
+ "-o ", "", "=", "",
+ "-o ", "", "", "=", "",
+ "-o ", "", "=", "",
+ "-o ", "", "=", "",
+ " "," <file>",
+ "\n Custom size: -o PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: -o PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'lprng') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("lpr -P $printerstr ",
+ "-Z ", "", "=", "",
+ "-Z ", "", "", "=", "",
+ "-Z ", "", "=", "",
+ "-Z ", "", "=", "",
+ " "," <file>",
+ "\n Custom size: -Z PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: -Z PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'ppr') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("ppr -d $printerstr --ripopts \"",
+ "", "", "=", "",
+ "", "", "", "=", "",
+ "", "", "=", "",
+ "", "", "=", "",
+ " ","\" <file>",
+ "\n Custom size: PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'ppr-int') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("ppr -d $printerstr -i \"",
+ "", "", "=", "",
+ "", "", "", "=", "",
+ "", "", "=", "",
+ "", "", "=", "",
+ " ","\" <file>",
+ "\n Custom size: PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'cps') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("lpr -P $printerstr ",
+ "-o ", "", "=", "",
+ "-o ", "", "", "=", "",
+ "-o ", "", "=", "",
+ "-o ", "", "=", "",
+ " "," <file>",
+ "\n Custom size: -o PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: -o PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'direct') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("$programname -P $printerstr ",
+ "-o ", "", "=", "",
+ "-o ", "", "", "=", "",
+ "-o ", "", "=", "",
+ "-o ", "", "=", "",
+ " "," <file>",
+ "\n Custom size: -o PageSize=Custom." .
+ "<width>x<height>[<unit>]\n" .
+ " Units: pt (default), in, cm, mm\n" .
+ " Example: -o PageSize=Custom.4.0x6.0in\n");
+ } elsif ($spooler eq 'pdq') {
+ ($command,
+ $enumopt, $enumoptleft, $enumoptequal, $enumoptright,
+ $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal,
+ $booloptright,
+ $numopt, $numoptleft, $numoptequal, $numoptright,
+ $stropt, $stroptleft, $stroptequal, $stroptright,
+ $optsep, $trailer, $custompagesize) =
+ ("pdq -P $printerstr ",
+ "-o", "", "_", "",
+ "-o", "no", "", "_", "",
+ "-a", "", "=", "",
+ "-a", "", "=", "",
+ " "," <file>",
+ "\n" .
+ "Option 'PageWidth':\n".
+ " Page Width (for \"Custom\" page size)\n" .
+ " A floating point number argument\n" .
+ " Range: 0 <= x <= 100000\n" .
+ " Example: -aPageWidth=123.4\n" .
+ "\n" .
+ "Option 'PageHeight':\n" .
+ " Page Height (for \"Custom\" page size)\n" .
+ " A floating point number argument\n" .
+ " Range: 0 <= x <= 100000\n" .
+ " Example: -aPageHeight=234.5\n" .
+ "\n" .
+ "Option 'PageSizeUnit':\n" .
+ " Unit (for \"Custom\" page size)\n" .
+ " An enumerated choice argument\n" .
+ " Possible choices:\n" .
+ " o -oPageSizeUnit_pt: Points (1/72 inch)\n" .
+ " o -oPageSizeUnit_in: Inches\n" .
+ " o -oPageSizeUnit_cm: cm\n" .
+ " o -oPageSizeUnit_mm: mm\n" .
+ " Example: -oPageSizeUnit_mm\n");
+ }
+
+ # Variables for the kid processes reporting their state
+
+ # Set up a pipe for the kids to pass their exit stat to the main process
+ pipe KID_MESSAGE_DOC, KID_MESSAGE_DOC_IN;
+
+ # When the kid fails put the exit stat here
+ $dockidfailed = 0;
+
+ # When the kid exits successfully, mark it here
+ $kid0finished = 0;
+
+ use IO::Handle;
+ pipe KID0_IN, KID0;
+ KID0->autoflush(1);
+ my $kid0 = fork();
+ if (!defined($kid0)) {
+ close KID0;
+ close KID0_IN;
+ print $logh "$0: cannot fork for kid0!\n";
+ rip_die ("can't fork for kid0",
+ $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+
+ if ($kid0) {
+ # we are the parent; return a glob to the filehandle
+ close KID0;
+ print $logh "Documentation page generator PID kid0=$kid0\n";
+ return ( *KID0_IN, $kid0 );
+ }
+
+ # we are the kid; we generate the documentation page
+
+ close KID0_IN;
+
+ # Kill data on STDIN to satisfy PPR
+ if (($spooler eq 'ppr_int') || ($spooler eq 'ppr')) {
+ while (my $dummy = <STDIN>) {};
+ }
+ close STDIN
+ or print $logh "Error closing STDIN for docs print\n";
+
+ # write the job into KID0
+ select KID0;
+
+ print "\nInvokation summary for the $model\n\n";
+ print "Use the following command line:\n\n";
+ if ($booloptfalseprefix) {
+ # I think that what you want to indicate is that the prefix for a false
+ # boolean has this form: xxx [no]<switch> or something similar
+ print " ${command}${enumopt}${enumoptleft}<option>" .
+ "${enumoptequal}<choice>${enumoptright}${optsep}" .
+ "${boolopt}${booloptleft}\[${booloptfalseprefix}\]<switch>" .
+ "${booloptright}${optsep}" .
+ "${numopt}${numoptleft}<num. option>${numoptequal}" .
+ "<value>${numoptright}${optsep}" .
+ "${stropt}${stroptleft}<string option>${stroptequal}" .
+ "<string>${stroptright}" .
+ "${trailer}\n\n";
+ } else {
+ print " ${command}${enumopt}${enumoptleft}<option>" .
+ "${enumoptequal}<choice>${enumoptright}${optsep}" .
+ "${boolopt}${booloptleft}<switch>${booloptequal}" .
+ "<True/False>${booloptright}${optsep}" .
+ "${numopt}${numoptleft}<num. option>${numoptequal}" .
+ "<value>${numoptright}${optsep}" .
+ "${stropt}${stroptleft}<string option>${stroptequal}" .
+ "<string>${stroptright}" .
+ "${trailer}\n\n";
+ }
+
+ print "The following options are available for this printer:\n\n";
+
+ for my $arg (@{$dat->{'args'}}) {
+ my ($name,
+ $type,
+ $comment,
+ $spot,
+ $default) = ($arg->{'name'},
+ $arg->{'type'},
+ $arg->{'comment'},
+ $arg->{'spot'},
+ $arg->{'default'});
+
+ # Is this really an option? Otherwise skip it.
+ next if (!$type);
+
+ # We don't need "PageRegion", we have "PageSize"
+ next if ($name eq "PageRegion");
+
+ # Skip enumerated choice options with only one choice
+ next if (($type eq 'enum') && ($#{$arg->{'vals'}} < 1));
+
+ my $commentstr = "";
+ if ($comment) {
+ $commentstr = " $comment\n";
+ }
+
+ my $typestr;
+ if ($type eq "enum") {
+ $typestr = "An enumerated choice";
+ } elsif ($type eq "bool") {
+ $typestr = "A boolean";
+ } elsif ($type eq "int") {
+ $typestr = "An integer number";
+ } elsif ($type eq "float") {
+ $typestr = "A floating point number";
+ } elsif (($type eq "string") || ($type eq "password")) {
+ $typestr = "A string";
+ }
+
+ print "Option '$name':\n$commentstr $typestr argument\n";
+ print " This options corresponds to a PJL command\n" if ($arg->{'style'} eq 'J');
+
+ if ($type eq 'bool') {
+ print " Possible choices:\n";
+ if ($booloptfalseprefix) {
+ print " o $name: $arg->{'comment_true'}\n";
+ print " o $booloptfalseprefix$name: " .
+ "$arg->{'comment_false'}\n";
+ if (defined($default)) {
+ my $defstr = ($default ? "" : "$booloptfalseprefix");
+ print " Default: $defstr$name\n";
+ }
+ print " Example: ${boolopt}${booloptleft}${name}" .
+ "${booloptright}\n";
+ } else {
+ print " o True: $arg->{'comment_true'}\n";
+ print " o False: $arg->{'comment_false'}\n";
+ if (defined($default)) {
+ my $defstr = ($default ? "True" : "False");
+ print " Default: $defstr\n";
+ }
+ print " Example: ${boolopt}${booloptleft}${name}" .
+ "${booloptequal}True${booloptright}\n";
+ }
+ } elsif ($type eq 'enum') {
+ print " Possible choices:\n";
+ my $exarg;
+ my $havecustomsize = 0;
+ for (@{$arg->{'vals'}}) {
+ my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
+ print " o $choice: $comment\n";
+ if (($name eq "PageSize") && ($choice eq "Custom")) {
+ $havecustomsize = 1;
+ }
+ $exarg=$choice;
+ }
+ if (defined($default)) {
+ print " Default: $default\n";
+ }
+ print " Example: ${enumopt}${enumoptleft}${name}" .
+ "${enumoptequal}${exarg}${enumoptright}\n";
+ if ($havecustomsize) {
+ print $custompagesize;
+ }
+ } elsif ($type eq 'int' or $type eq 'float') {
+ my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
+ my $exarg;
+ if (defined($max)) {
+ print " Range: $min <= x <= $max\n";
+ $exarg=$max;
+ }
+ if (defined($default)) {
+ print " Default: $default\n";
+ $exarg=$default;
+ }
+ if (!$exarg) { $exarg=0; }
+ print " Example: ${numopt}${numoptleft}${name}" .
+ "${numoptequal}${exarg}${numoptright}\n";
+ } elsif ($type eq 'string' or $type eq 'password') {
+ my $maxlength = $arg->{'maxlength'};
+ if (defined($maxlength)) {
+ print " Maximum length: $maxlength characters\n";
+ }
+ if (defined($default)) {
+ print " Default: $default\n";
+ }
+ print " Examples/special settings:\n";
+ for (@{$arg->{'vals'}}) {
+ my ($value, $comment, $driverval, $proto) =
+ ($_->{'value'}, $_->{'comment'}, $_->{'driverval'},
+ $arg->{'proto'});
+ # Retrieve the original string from the prototype
+ # and the driverval
+ my $string;
+ if ($proto) {
+ my $s = index($proto, '%s');
+ my $l = length($driverval) - length($proto) + 2;
+ if (($s < 0) || ($l < 0)) {
+ $string = $driverval;
+ } else {
+ $string = substr($driverval, $s, $l);
+ }
+ } else {
+ $string = $driverval;
+ }
+ print " o ${stropt}${stroptleft}${name}" .
+ "${stroptequal}${value}${stroptright}";
+ if (($value ne $string) || ($comment ne $value)) {
+ print " (";
+ }
+ if ($value ne $string) {
+ if ($string eq '') {
+ print "blank string";
+ } else {
+ print "\"$string\"";
+ }
+ }
+ if (($value ne $string) && ($comment ne $value)) {
+ print ", ";
+ }
+ if ($value ne $comment) {
+ print "$comment";
+ }
+ if (($value ne $string) || ($comment ne $value)) {
+ print ")";
+ }
+ print "\n";
+ }
+ }
+
+ print "\n";
+ }
+
+ select STDOUT;
+ close KID0
+ or print $logh "Error closing KID0 for docs print\n";
+ close STDOUT
+ or print $logh "Error closing STDOUT for docs print\n";
+
+ # Finished successfully, inform main process
+ close KID_MESSAGE_DOC;
+ print KID_MESSAGE_DOC_IN "0 $EXIT_PRINTED\n";
+ close KID_MESSAGE_DOC_IN;
+
+ print $logh "KID0 finished\n";
+ exit($EXIT_PRINTED);
+
+}
+
+
+
+## Close the documentation page generation process and wait until the
+## kid process finishes.
+
+sub closedocgeneratorhandle {
+
+ my ($handle, $pid) = @_;
+
+ print $logh "${added_lf}Closing documentation page generator\n";
+
+ # Do it!
+ close $handle;
+
+ # Wait for the kid process to finish or the kid process to fail
+ close KID_MESSAGE_DOC_IN;
+ while ((!$dockidfailed) &&
+ (!$kid0finished)) {
+ my $message = <KID_MESSAGE_DOC>;
+ chomp $message;
+ if ($message =~ /(\d+)\s+(\d+)/) {
+ my $kid_id = $1;
+ my $exitstat = $2;
+ print $logh "KID$kid_id exited with status $exitstat\n";
+ if ($exitstat > 0) {
+ $dockidfailed = $exitstat;
+ } elsif ($kid_id eq "0") {
+ $kid0finished = 1;
+ }
+ }
+ }
+
+ close KID_MESSAGE_DOC;
+
+ # If the kid failed, return the exit stat of the kid
+ if ($dockidfailed != 0) {
+ $retval = $dockidfailed;
+ }
+
+ print $logh "Documentation page generator exit stat: $retval\n";
+ # Wait for fileconverter child
+ waitpid($pid, 0);
+ print $logh "Documentation page generator process finished\n";
+ return ($retval);
+}
+
+
+
+# Find an argument by name in a case-insensitive way
+sub argbyname {
+ my $name = $_[0];
+
+ for my $arg (@{$dat->{'args'}}) {
+ return $arg if (lc($name) eq lc($arg->{'name'}));
+ }
+
+ return undef;
+}
+
+sub valbyname {
+ my ($arg,$name) = @_;
+
+ for my $val (@{$arg->{'vals'}}) {
+ return $val if (lc($name) eq lc($val->{'value'}));
+ }
+
+ return undef;
+}
+
+# Write a Good-Bye letter and clean up before committing suicide (send
+# error message to caller)
+
+sub rip_die {
+ my ($message, $exitstat) = @_;
+ my $errmsg = "$!";
+ my $errcod = $! + 0;
+
+ # Close the documentation page generator (if it was used)
+ if ($docgeneratorpid) {
+ if ($kid0) {
+ print $logh "Killing process $kid0 (KID0)\n";
+ kill(9, $kid0);
+ }
+ $docgeneratorpid = 0;
+ }
+
+ # Close the file converter (if it was used)
+ if ($fileconverterpid) {
+ if ($kid2) {
+ print $logh "Killing process $kid2 (KID2)\n";
+ kill(9, $kid2);
+ }
+ if ($kid1) {
+ print $logh "Killing process $kid1 (KID1)\n";
+ kill(9, $kid1);
+ }
+ $fileconverterpid = 0;
+ }
+
+ # Close the renderer
+ if ($rendererpid) {
+ if ($kid4) {
+ print $logh "Killing process $kid4 (KID4)\n";
+ kill(9, $kid4);
+ }
+ if ($kid3) {
+ print $logh "Killing process $kid3 (KID3)\n";
+ kill(9, $kid3);
+ }
+ $rendererpid = 0;
+ }
+
+ print $logh "Process dying with \"$message\", exit stat: $exitstat\n\terror: $errmsg ($errcod)\n";
+ if ($spooler eq 'ppr_int') {
+ # Special error handling for PPR intefaces
+ $message =~ s/\\/\\\\/;
+ $message =~ s/\"/\\\"/;
+ my @messagelines = split("\n", $message);
+ my $firstline = "TRUE";
+ for my $line (@messagelines) {
+ system("lib/alert $printer $firstline \"$line\"");
+ $firstline = "FALSE";
+ }
+ } else {
+ print STDERR $message . "\n";
+ }
+ exit $exitstat;
+}
+
+# Signal handling routines
+
+sub set_exit_prnerr {
+ $retval = $EXIT_PRNERR;
+}
+
+sub set_exit_prnerr_noretry {
+ $retval = $EXIT_PRNERR_NORETRY;
+}
+
+sub set_exit_engaged {
+ $retval = $EXIT_ENGAGED;
+}
+
+# Read the config file
+
+sub readConfFile {
+ my ($file) = @_;
+
+ my %conf;
+ # Read config file if present
+ if (open CONF, "< $file") {
+ while (<CONF>)
+ {
+ $conf{$1}="$2" if (m/^\s*([^\#\s]\S*)\s*:\s*(.*)\s*$/);
+ }
+ close CONF;
+ }
+
+ return %conf;
+}
+
+sub removeunprintables {
+ # Remove unprintable characters
+ my $str = $_[0];
+ $str =~ s/[\x00-\x1f]//g;
+ return $str;
+}
+
+sub removeshellescapes {
+ # Remove shell escape characters
+ my $str = $_[0];
+ $str =~ s/[\|<>&!\$\'\"\#\*\?\(\)\[\]\{\}]//g;
+ return $str;
+}
+
+sub removespecialchars {
+ # Remove unprintable and shell escape characters
+ return removeshellescapes(removeunprintables($_[0]));
+}
+
+sub unhtmlify {
+ # Replace HTML/XML entities by the original characters
+ my $str = $_[0];
+ $str =~ s/\&apos;/\'/g;
+ $str =~ s/\&quot;/\"/g;
+ $str =~ s/\&gt;/\>/g;
+ $str =~ s/\&lt;/\</g;
+ $str =~ s/\&amp;/\&/g;
+ return $str;
+}
+
+sub unhexify {
+ # Replace hex notation for unprintable characters in PPD files
+ # by the actual characters ex: "<0A>" --> chr(hex("0A"))
+ my ($input) = @_;
+ my $output = "";
+ my $hexmode = 0;
+ my $firstdigit = "";
+ for (my $i = 0; $i < length($input); $i ++) {
+ my $c = substr($input, $i, 1);
+ if ($hexmode) {
+ if ($c eq ">") {
+ # End of hex string
+ $hexmode = 0;
+ } elsif ($c =~ /^[0-9a-fA-F]$/) {
+ # Hexadecimal digit, two of them give a character
+ if ($firstdigit ne "") {
+ $output .= chr(hex("$firstdigit$c"));
+ $firstdigit = "";
+ } else {
+ $firstdigit = $c;
+ }
+ }
+ } else {
+ if ($c eq "<") {
+ # Beginning of hex string
+ $hexmode = 1;
+ } else {
+ # Normal character
+ $output .= $c;
+ }
+ }
+ }
+ return $output;
+}
+
+sub undossify( $ ) {
+ # Remove "dossy" line ends ("\r\n") from a string
+ my $str = $_[0];
+ $str =~ s/\r\n/\n/gs;
+ $str =~ s/\r$//s;
+ return( $str );
+}
+
+sub checkarg {
+ # Check if there is already an argument record $argname in $dat, if not,
+ # create one
+ my ($dat, $argname) = @_;
+ return if defined($dat->{'args_byname'}{$argname});
+ # argument record
+ my $rec;
+ $rec->{'name'} = $argname;
+ # Insert record in 'args' array for browsing all arguments
+ push(@{$dat->{'args'}}, $rec);
+ # 'args_byname' hash for looking up arguments by name
+ $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}];
+ # Default execution style is 'G' (PostScript) since all arguments for
+ # which we don't find "*Foomatic..." keywords are usual PostScript
+ # options
+ $dat->{'args_byname'}{$argname}{'style'} = 'G';
+ # Default prototype for code to insert, used by enum options
+ $dat->{'args_byname'}{$argname}{'proto'} = '%s';
+ # stop Perl nattering about undefined to string comparisons
+ $dat->{'args_byname'}{$argname}{'type'} = '';
+ print $logh "Added option $argname\n";
+}
+
+sub checksetting {
+ # Check if there is already an choice record $setting in the $argname
+ # argument in $dat, if not, create one
+ my ($dat, $argname, $setting) = @_;
+ return if
+ defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting});
+ # setting record
+ my $rec;
+ $rec->{'value'} = $setting;
+ # Insert record in 'vals' array for browsing all settings
+ push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec);
+ # 'vals_byname' hash for looking up settings by name
+ $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} =
+ $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}];
+}
+
+sub removearg {
+ # remove the argument record $argname from $dat
+ my ($dat, $argname) = @_;
+ return if !defined($dat->{'args_byname'}{$argname});
+ # Remove 'args_byname' hash for looking up arguments by name
+ delete $dat->{'args_byname'}{$argname};
+ # Remove argument itself
+ for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
+ if ($dat->{'args'}[$i]{'name'} eq $argname) {
+ print $logh "Removing option " .
+ $argname . "\n";
+ splice(@{$dat->{'args'}}, $i, 1);
+ last;
+ }
+ }
+}
+
+sub removepsargs {
+ # remove all records of PostScript arguments from $dat
+ my ($dat) = @_;
+ return if !defined($dat);
+ for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
+ if ($dat->{'args'}[$i]{'style'} eq 'G') {
+ print $logh "Removing PostScript option " .
+ $dat->{'args'}[$i]{'name'} . "\n";
+ # Remove 'args_byname' hash for looking up arguments by name
+ delete $dat->{'args_byname'}{$dat->{'args'}[$i]{'name'}};
+ # Remove argument itself
+ splice(@{$dat->{'args'}}, $i, 1);
+ $i --;
+ }
+ }
+}
+
+sub checkoptionvalue {
+
+ ## This function checks whether a given value is valid for a given
+ ## option. If yes, it returns a cleaned value (e. g. always 0 or 1
+ ## for boolean options), otherwise "undef". If $forcevalue is set,
+ ## we always determine a corrected value to insert (we never return
+ ## "undef").
+
+ # Is $value valid for the option named $argname?
+ my ($dat, $argname, $value, $forcevalue) = @_;
+
+ # Record for option $argname
+ my $arg = $dat->{'args_byname'}{$argname};
+ $arg->{'type'} = '' if not defined $arg->{'type'};
+
+ if ($arg->{'type'} eq 'bool') {
+ my $lcvalue = lc($value);
+ if ((($lcvalue) eq 'true') ||
+ (($lcvalue) eq 'on') ||
+ (($lcvalue) eq 'yes') ||
+ (($lcvalue) eq '1')) {
+ return 1;
+ } elsif ((($lcvalue) eq 'false') ||
+ (($lcvalue) eq 'off') ||
+ (($lcvalue) eq 'no') ||
+ (($lcvalue) eq '0')) {
+ return 0;
+ } elsif ($forcevalue) {
+ # This maps Unknown to mean False. Good? Bad?
+ # It was done so in Foomatic 2.0.x, too.
+ my $name = $arg->{'name'};
+ print $logh
+ "The value $value for $name is not a " .
+ "choice!\n" .
+ " --> Using False instead!\n";
+ return 0;
+ }
+ } elsif ($arg->{'type'} eq 'enum') {
+ if ($value =~ /^None$/i) {
+ return 'None';
+ } elsif (defined($arg->{'vals_byname'}{$value})) {
+ return $value;
+ } elsif ((($arg->{'name'} eq "PageSize") ||
+ ($arg->{'name'} eq "PageRegion")) &&
+ (defined($arg->{'vals_byname'}{'Custom'})) &&
+ ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) {
+ # Custom paper size
+ return $value;
+ } elsif ($forcevalue) {
+ # wtf!? that's not a choice!
+ my $name = $arg->{'name'};
+ # Return the first entry of the list
+ my $firstentry = $arg->{'vals'}[0]{'value'};
+ print $logh
+ "The value $value for $name is not a " .
+ "choice!\n" .
+ " --> Using $firstentry instead!\n";
+ return $firstentry;
+ }
+ } elsif (($arg->{'type'} eq 'int') ||
+ ($arg->{'type'} eq 'float')) {
+ if (($value <= $arg->{'max'}) &&
+ ($value >= $arg->{'min'})) {
+ return $value;
+ } elsif ($forcevalue) {
+ my $name = $arg->{'name'};
+ my $newvalue;
+ if ($value > $arg->{'max'}) {
+ $newvalue = $arg->{'max'}
+ } elsif ($value < $arg->{'min'}) {
+ $newvalue = $arg->{'min'}
+ }
+ print $logh
+ "The value $value for $name is out of " .
+ "range!\n" .
+ " --> Using $newvalue instead!\n";
+ return $newvalue;
+ }
+ } elsif (($arg->{'type'} eq 'string') ||
+ ($arg->{'type'} eq 'password')) {
+ if (defined($arg->{'vals_byname'}{$value})) {
+ my $name = $arg->{'name'};
+ print $logh
+ "The value $value for $name is a predefined choice\n";
+ return $value;
+ } elsif (stringvalid($dat, $argname, $value)) {
+ # Check whether the string is one of the enumerated choices
+ my $sprintfproto = $arg->{'proto'};
+ $sprintfproto =~ s/\%(?!s)/\%\%/g;
+ my $driverval = sprintf($sprintfproto, $value);
+ for my $val (@{$arg->{'vals'}}) {
+ if (($val->{'driverval'} eq $driverval) ||
+ ($val->{'driverval'} eq $value)) {
+ my $name = $arg->{'name'};
+ print $logh
+ "The string $value for $name is the predefined " .
+ "choice $val->{value}\n";
+ return $val->{value};
+ }
+ }
+ # "None" is mapped to the empty string
+ if ($value eq 'None') {
+ my $name = $arg->{'name'};
+ print $logh
+ "Option $name: 'None' is the mapped to the " .
+ "empty string\n";
+ return '';
+ }
+ # No matching choice? Return the original string
+ return $value;
+ } elsif ($forcevalue) {
+ my $name = $arg->{'name'};
+ my $str = substr($value, 0, $arg->{'maxlength'});
+ if (stringvalid($dat, $argname, $str)) {
+ print $logh
+ "The string $value for $name is longer than " .
+ "$arg->{'maxlength'}, string shortened to $str\n";
+ return $str;
+ } elsif ($#{$arg->{'vals'}} >= 0) {
+ # First list item
+ my $firstentry = $arg->{'vals'}[0]{'value'};
+ print $logh
+ "The string $value for $name contains forbidden " .
+ "characters or does not match the regular expression " .
+ "defined for this option, using predefined choice " .
+ "$firstentry instead\n";
+ return $firstentry;
+ } else {
+ # We should not get here
+ rip_die("Option $name incorrectly defined in the " .
+ "PPD file!\n", $EXIT_PRNERR_NORETRY_BAD_SETTINGS);
+ }
+ }
+ }
+ return undef;
+}
+
+sub stringvalid {
+
+ ## Checks whether a user-supplied value for a string option is valid
+ ## It must be within the length limit, should only contain allowed
+ ## characters and match the given regexp
+
+ # Option and string
+ my ($dat, $argname, $value) = @_;
+
+ my $arg = $dat->{'args_byname'}{$argname};
+
+ # Maximum length
+ return 0 if (defined($arg->{'maxlength'}) &&
+ (length($value) > $arg->{'maxlength'}));
+
+ # Allowed characters
+ if ($arg->{'allowedchars'}) {
+ my $chars = $arg->{'allowedchars'};
+ $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
+ return 0 if $value !~ /^[$chars]*$/;
+ }
+
+ # Regular expression
+ if ($arg->{'allowedregexp'}) {
+ my $regexp = $arg->{'allowedregexp'};
+ $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
+ return 0 if $value !~ /$regexp/;
+ }
+
+ # All checks passed
+ return 1;
+}
+
+sub checkoptions {
+
+ ## Let the values of a boolean option being 0 or 1 instead of
+ ## "True" or "False", range-check the defaults of all options and
+ ## issue warnings if the values are not valid
+
+ # Option set to be examined
+ my ($dat, $optionset) = @_;
+
+ for my $arg (@{$dat->{'args'}}) {
+ if (defined($arg->{$optionset})) {
+ $arg->{$optionset} =
+ checkoptionvalue
+ ($dat, $arg->{'name'}, $arg->{$optionset}, 1);
+ }
+ }
+
+ # If the settings for "PageSize" and "PageRegion" are different,
+ # set the one for "PageRegion" to the one for "PageSize" and issue
+ # a warning.
+ if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne
+ $dat->{'args_byname'}{'PageRegion'}{$optionset}) {
+ print $logh "Settings for \"PageSize\" and \"PageRegion\" are " .
+ "different:\n" .
+ " PageSize: $dat->{'args_byname'}{'PageSize'}{$optionset}\n" .
+ " PageRegion: ".
+ "$dat->{'args_byname'}{'PageRegion'}{$optionset}\n" .
+ "Using the \"PageSize\" value " .
+ "\"$dat->{'args_byname'}{'PageSize'}{$optionset}\"," .
+ " for both.\n";
+ $dat->{'args_byname'}{'PageRegion'}{$optionset} =
+ $dat->{'args_byname'}{'PageSize'}{$optionset};
+ }
+}
+
+# If the PageSize or PageRegion was changed, also change the other
+
+sub syncpagesize {
+
+ # Name and value of the option we set, and the option set where we
+ # did the change
+ my ($dat, $name, $value, $optionset) = @_;
+
+ # Don't do anything if we were called with an option other than
+ # "PageSize" or "PageRegion"
+ return if (($name ne "PageSize") && ($name ne "PageRegion"));
+
+ # Don't do anything if not both "PageSize" and "PageRegion" exist
+ return if ((!defined($dat->{'args_byname'}{'PageSize'})) ||
+ (!defined($dat->{'args_byname'}{'PageRegion'})));
+
+ my $dest;
+
+ # "PageSize" --> "PageRegion"
+ if ($name eq "PageSize") {
+ $dest = "PageRegion";
+ }
+
+ # "PageRegion" --> "PageSize"
+ if ($name eq "PageRegion") {
+ $dest = "PageSize";
+ }
+
+ # Do it!
+ my $val;
+ if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) {
+ # Standard paper size
+ $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'};
+ } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) {
+ # Custom paper size
+ $dat->{'args_byname'}{$dest}{$optionset} = $value;
+ }
+}
+
+sub copyoptions {
+
+ ## Copy one option set into another one
+
+ # Source and destination option sets
+ my ($dat, $srcoptionset, $destoptionset) = @_;
+
+ for my $arg (@{$dat->{'args'}}) {
+ if (defined($arg->{$srcoptionset})) {
+ $arg->{$destoptionset} = $arg->{$srcoptionset};
+ }
+ }
+}
+
+sub deleteoptions {
+
+ ## Delete an option set
+
+ # option set to be removed
+ my ($dat, $optionset) = @_;
+
+ for my $arg (@{$dat->{'args'}}) {
+ if (defined($arg->{$optionset})) {
+ delete($arg->{$optionset});
+ }
+ }
+}
+
+sub optionsequal {
+
+ ## Compare two option sets, if they are equal, return 1, otherwise 0
+
+ # Option sets to be compared, flag to compare only command line and JCL
+ # options
+ my ($dat, $firstoptionset, $secondoptionset, $exceptPS) = @_;
+
+ for my $arg (@{$dat->{'args'}}) {
+ next if ($exceptPS && ($arg->{'style'} eq 'G'));
+ if ((defined($arg->{$firstoptionset})) &&
+ (defined($arg->{$secondoptionset}))) {
+ # Both entries exist
+ return 0 if $arg->{$firstoptionset} ne $arg->{$secondoptionset};
+ } elsif ((defined($arg->{$firstoptionset})) ||
+ (defined($arg->{$secondoptionset}))) {
+ # One entry exists
+ return 0;
+ }
+ # If no entry exists, the non-existing entries are considered as
+ # equal
+ }
+ return 1;
+}
+
+sub makeprologsection {
+
+ # option set to be used,
+ # $comments = 1: Add "%%BeginProlog...%%EndProlog"
+ my ($dat, $optionset, $comments) = @_;
+
+ # Collect data to be inserted here
+ my @output;
+
+ # Start comment
+ if ($comments) {
+ print $logh "\"Prolog\" section is missing, inserting it.\n";
+ push(@output, "%%BeginProlog\n");
+ }
+
+ # Generate the option code (not necessary when CUPS is spooler)
+ if ($spooler ne 'cups') {
+ print $logh "Inserting option code into \"Prolog\" section.\n";
+ buildcommandline ($dat, $optionset);
+ push(@output, @{$dat->{'prologprepend'}});
+ }
+
+ # End comment
+ if ($comments) {
+ push(@output, "%%EndProlog\n");
+ }
+
+ return join('', @output);
+}
+
+sub makesetupsection {
+
+ # option set to be used, $comments = 1: Add "%%BeginSetup...%%EndSetup"
+ my ($dat, $optionset, $comments) = @_;
+
+ # Collect data to be inserted here
+ my @output;
+
+ # Start comment
+ if ($comments) {
+ print $logh "\"Setup\" section is missing, inserting it.\n";
+ push(@output, "%%BeginSetup\n");
+ }
+
+ # PostScript code to generate accounting messages for CUPS
+ if ($spooler eq 'cups') {
+ print $logh "Inserting PostScript code for CUPS' page accounting\n";
+ push(@output, $accounting_prolog);
+ }
+
+ # Generate the option code (not necessary when CUPS is spooler)
+ if ($spooler ne 'cups') {
+ print $logh "Inserting option code into \"Setup\" section.\n";
+ buildcommandline ($dat, $optionset);
+ push(@output, @{$dat->{'setupprepend'}});
+ }
+
+ # End comment
+ if ($comments) {
+ push(@output, "%%EndSetup\n");
+ }
+
+ return join('', @output);
+}
+
+sub makepagesetupsection {
+
+ # option set to be used,
+ # $comments = 1: Add "%%BeginPageSetup...%%EndPageSetup"
+ my ($dat, $optionset, $comments) = @_;
+
+ # Collect data to be inserted here
+ my @output;
+
+ # Start comment
+ if ($comments) {
+ push(@output, "%%BeginPageSetup\n");
+ print $logh "\"PageSetup\" section is missing, inserting it.\n";
+ }
+
+ # Generate the option code (not necessary when CUPS is spooler)
+ print $logh "Inserting option code into \"PageSetup\" section.\n";
+ buildcommandline ($dat, $optionset);
+ if ($spooler ne 'cups') {
+ push(@output, @{$dat->{'pagesetupprepend'}});
+ } else {
+ push(@output, @{$dat->{'cupspagesetupprepend'}});
+ }
+
+ # End comment
+ if ($comments) {
+ push(@output, "%%EndPageSetup\n");
+ }
+
+ return join('', @output);
+}
+
+sub parsepageranges {
+
+ ## Parse a string containing page ranges and either check whether a
+ ## given page is in the ranges or, if the given page number is zero,
+ ## determine the score how specific this page range string is.
+
+ # String with page ranges and number of current page (0 for score)
+ my ($ranges, $page) = @_;
+
+ my $currentnumber = 0;
+ my $rangestart = 0;
+####### Question: is rangeend ever used?
+ my $rangeend = 0;
+ my $currentkeyword = '';
+ my $invalidrange = 0;
+ my $totalscore = 0;
+ my $pageinside = 0;
+ my $currentrange = '';
+
+ my $evaluaterange = sub {
+ # evaluate the current range: determine its score and whether the
+ # current page is member of it.
+ if ($invalidrange) {
+ # Range is invalid, issue a warning
+ print $logh " Invalid range: $currentrange\n";
+ } else {
+ # We have a valid range, evaluate it
+ if ($currentkeyword) {
+ if ($currentkeyword =~ /^even/i) {
+ # All even-numbered pages
+ $totalscore += 50000;
+ $pageinside = 1 if (($page % 2) == 0);
+ } elsif ($currentkeyword =~ /^odd/i) {
+ # All odd-numbered pages
+ $totalscore += 50000;
+ $pageinside = 1 if (($page % 2) == 1);
+ } else {
+ # Invalid range
+ print $logh " Invalid range: $currentrange\n";
+ }
+ } elsif (($rangestart == 0) && ($currentnumber > 0)) {
+ # Page range is a single page
+ $totalscore += 1;
+ $pageinside = 1 if ($page == $currentnumber);
+ } elsif (($rangestart > 0) && ($currentnumber > 0)) {
+ # Page range is a sequence of pages
+ $totalscore += (abs($currentnumber - $rangestart) + 1);
+ if ($currentnumber < $rangestart) {
+ my $tmp = $currentnumber;
+ $currentnumber = $rangestart;
+ $rangestart = $tmp;
+ }
+ $pageinside = 1 if (($page <= $currentnumber) &&
+ ($page >= $rangestart));
+ } elsif ($rangestart > 0) {
+ # Page range goes to the end of the document
+ $totalscore += 100000;
+ $pageinside = 1 if ($page >= $rangestart);
+ } else {
+ # Invalid range
+ print $logh " Invalid range: $currentrange\n";
+ }
+ }
+ # Range is evaluated, remove all recordings of the current range
+ $rangestart = 0;
+ $currentnumber = 0;
+ $currentkeyword = '';
+ $invalidrange = 0;
+ $currentrange = '';
+ };
+
+ for (my $i = 0; $i < length($ranges); $i ++) {
+ my $c = substr($ranges, $i, 1);
+ if (!$invalidrange) {
+ if ($c =~ /\d/) {
+ # Digit
+ if ($currentkeyword) {
+ # Add to keyword
+ $currentkeyword .= $c;
+ } else {
+ # Build a page number
+ $currentnumber *= 10;
+ $currentnumber += $c;
+ }
+ } elsif ($c =~ /[a-z_]/i) {
+ # Letter or underscore
+ if (($rangestart > 0) || ($rangeend > 0) ||
+ ($currentnumber > 0)) {
+ # Keyword not allowed after a page number or a
+ # page range
+ $invalidrange = 1;
+ } else {
+ # Build a keyword
+ $currentkeyword .= $c;
+ }
+ } elsif ($c eq '-') {
+ # Page range
+ if (($rangestart > 0) || ($currentkeyword)) {
+ # Keyword or two '-' not allowed in page range
+ $invalidrange = 1;
+ } else {
+ # Save start of range, reset page number
+ $rangestart = $currentnumber;
+ if ($rangestart == 0) {
+ $rangestart = 1;
+ }
+ $currentnumber = 0;
+ }
+ }
+ }
+ if ($c eq ',') {
+ # End of a range
+ &$evaluaterange();
+ } else {
+ # Make a string of the current range, for warnings
+ $currentrange .= $c;
+ }
+ }
+ # End of input string
+ &$evaluaterange();
+ # Return value
+ if (($page == 0) || ($pageinside)) {
+ return $totalscore;
+ } else {
+ return 0;
+ }
+}
+
+sub setoptionsforpage {
+
+ ## Set the options for a given page
+
+ # Foomatic data, name of the option set where to apply the options, and
+ # number of the page
+ my ($dat, $optionset, $page) = @_;
+
+ my $bestscore = 10000000;
+ my $value;
+ for my $arg (@{$dat->{'args'}}) {
+ $value = '';
+ for my $key (keys %{$arg}) {
+ next if $key !~ /^pages:(.*)$/;
+ my $pageranges = $1;
+ if (my $score = parsepageranges($pageranges, $page)) {
+ if ($score <= $bestscore) {
+ $bestscore = $score;
+ $value = $arg->{$key};
+ }
+ }
+ }
+ if ($value) {
+ $arg->{$optionset} = $value;
+ }
+ }
+}
+
+sub buildcommandline {
+
+ ## Build a renderer command line, based on the given option set
+
+ # Foomatic data and name of the option set to apply
+ my ($dat, $optionset) = @_;
+
+ # Construct the proper command line.
+ $dat->{'currentcmd'} = $dat->{'cmd'};
+ my @prologprepend;
+ my @setupprepend;
+ my @pagesetupprepend;
+ my @cupspagesetupprepend;
+ my @jclprepend;
+ my @jclappend;
+
+ # At first search for composite options and determine how they
+ # set their member options
+ for my $arg (@{$dat->{'args'}}) { $arg->{'order'} = 0 if !defined $arg->{'order'}; }
+ for my $arg (sort { $a->{'order'} <=> $b->{'order'} }
+ @{$dat->{'args'}}) {
+
+ # Here we are only interested in composite options, skip the others
+ next if $arg->{'style'} ne 'X';
+
+ my $name = $arg->{'name'};
+ # Check whether this composite option is controlled by another
+ # composite option, so nested composite options are possible.
+ my $userval = ($arg->{'fromcomposite'} ?
+ $arg->{'fromcomposite'} : $arg->{$optionset});
+
+ # Get the current setting
+ my $v = $arg->{'vals_byname'}{$userval};
+ my @settings = split(/\s+/s, $v->{'driverval'});
+ for my $s (@settings) {
+ my ($key, $value);
+ if ($s =~ /^([^=]+)=(.+)$/) {
+ $key = $1;
+ $value = $2;
+ } elsif ($s =~ /^no([^=]+)$/) {
+ $key = $1;
+ $value = 0;
+ } elsif ($s =~ /^([^=]+)$/) {
+ $key = $1;
+ $value = 1;
+ }
+ $a = $dat->{'args_byname'}{$key};
+ if ($a->{$optionset} eq "From$name") {
+ # We must set this option according to the
+ # composite option
+ $a->{'fromcomposite'} = $value;
+ # Mark the option telling by which composite option
+ # it is controlled
+ $a->{'controlledby'} = $name;
+ } else {
+ $a->{'fromcomposite'} = "";
+ }
+ }
+ # Remove PostScript code to be inserted after an appearance of the
+ # Composite option in the PostScript code.
+ undef $arg->{'jclsetup'};
+ undef $arg->{'prolog'};
+ undef $arg->{'setup'};
+ undef $arg->{'pagesetup'};
+ }
+
+ for my $arg (sort { $a->{'order'} <=> $b->{'order'} }
+ @{$dat->{'args'}}) {
+
+ # Composite options have no direct influence on the command
+ # line, skip them here
+ next if $arg->{'style'} eq 'X';
+
+ my $name = $arg->{'name'};
+ my $spot = $arg->{'spot'};
+ my $cmd = $arg->{'proto'};
+ my $cmdf = $arg->{'protof'};
+ my $type = ($arg->{'type'} || "");
+ my $section = $arg->{'section'};
+ my $userval = ($arg->{'fromcomposite'} ?
+ $arg->{'fromcomposite'} : $arg->{$optionset});
+ my $cmdvar = "";
+
+ # If we have both "PageSize" and "PageRegion" options, we kept
+ # them all the time in sync, so we don't need to insert the settings
+ # of both options. So skip "PageRegion".
+ next if (($name eq "PageRegion") &&
+ (defined($dat->{'args_byname'}{'PageSize'})) &&
+ (defined($dat->{'args_byname'}{'PageRegion'})));
+
+ # Build the command line snippet/PostScript/JCL code for the current
+ # option
+ if ($type eq 'bool') {
+
+ # If true, stick the proto into the command line, if false
+ # and we have a proto for false, stick that in
+ if (defined($userval) && $userval == 1) {
+ $cmdvar = $cmd;
+ } elsif ($cmdf) {
+ $userval = 0;
+ $cmdvar = $cmdf;
+ }
+
+ } elsif ($type eq 'int' or $type eq 'float') {
+
+ # If defined, process the proto and stick the result into
+ # the command line or postscript queue.
+ if (defined($userval)) {
+ my $min = $arg->{'min'};
+ my $max = $arg->{'max'};
+ # We have already range-checked, correct only
+ # floating point inaccuricies here
+ if ($userval < $min) {
+ $userval = $min;
+ }
+ if ($userval > $max) {
+ $userval = $max;
+ }
+ my $sprintfcmd = $cmd;
+ $sprintfcmd =~ s/\%(?!s)/\%\%/g;
+ $cmdvar = sprintf($sprintfcmd,
+ ($type eq 'int'
+ ? sprintf("%d", $userval)
+ : sprintf("%f", $userval)));
+ } else {
+ $userval = 'None';
+ }
+
+ } elsif ($type eq 'enum') {
+
+ # If defined, stick the selected value into the proto and
+ # thence into the commandline
+ if (defined($userval)) {
+ # CUPS assumes that options with the choices "Yes", "No",
+ # "On", "Off", "True", or "False" are boolean options and
+ # maps "-o Option=On" to "-o Option" and "-o Option=Off"
+ # to "-o noOption", which foomatic-rip maps to "0" and "1".
+ # So when "0" or "1" is unavailable in the option, we try
+ # "Yes", "No", "On", "Off", "True", and "False".
+ my $val;
+ my $found = 0;
+ if ($val=valbyname($arg,$userval)) {
+ $found = 1;
+ } elsif ($userval =~ /^Custom\.[\d\.]+x[\d\.]+[A-Za-z]*$/) {
+ # Custom paper size
+ $val = valbyname($arg,"Custom");
+ $found = 1;
+ } elsif ($userval eq '0') {
+ foreach (qw(No Off False None)) {
+ if ($val=valbyname($arg,$_)) {
+ $userval = $_;
+ $arg->{$optionset} = $userval;
+ $found = 1;
+ last;
+ }
+ }
+ } elsif ($userval eq '1') {
+ foreach (qw(Yes On True)) {
+ if ($val=valbyname($arg,$_)) {
+ $userval = $_;
+ $arg->{$optionset} = $userval;
+ $found = 1;
+ last;
+ }
+ }
+ } elsif ($userval eq 'LongEdge') {
+ # Handle different names for the choices of the
+ # "Duplex" option
+ foreach (qw(LongEdge DuplexNoTumble)) {
+ if ($val=valbyname($arg,$_)) {
+ $userval = $_;
+ $arg->{$optionset} = $userval;
+ $found = 1;
+ last;
+ }
+ }
+ } elsif ($userval eq 'ShortEdge') {
+ foreach (qw(ShortEdge DuplexTumble)) {
+ if ($val=valbyname($arg,$_)) {
+ $userval = $_;
+ $arg->{$optionset} = $userval;
+ $found = 1;
+ last;
+ }
+ }
+ }
+ if ($found) {
+ my $sprintfcmd = $cmd;
+ $sprintfcmd =~ s/\%(?!s)/\%\%/g;
+ $cmdvar = sprintf($sprintfcmd,
+ (defined($val->{'driverval'})
+ ? $val->{'driverval'}
+ : $val->{'value'}));
+ # Custom paper size
+ if ($userval =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/) {
+ my $width = $1;
+ my $height = $2;
+ my $unit = $3;
+ # convert width and height to PostScript points
+ if (lc($unit) eq "in") {
+ $width *= 72.0;
+ $height *= 72.0;
+ } elsif (lc($unit) eq "cm") {
+ $width *= (72.0/2.54);
+ $height *= (72.0/2.54);
+ } elsif (lc($unit) eq "mm") {
+ $width *= (72.0/25.4);
+ $height *= (72.0/25.4);
+ }
+ # Round width and height
+ $width =~ s/\.[0-4].*$// or
+ $width =~ s/\.[5-9].*$// and $width += 1;
+ $height =~ s/\.[0-4].*$// or
+ $height =~ s/\.[5-9].*$// and $height += 1;
+ # Insert width and height into the prototype
+ if ($cmdvar =~ /^\s*pop\W/s) {
+ # Custom page size for PostScript printers
+ $cmdvar = "$width $height 0 0 0\n$cmdvar";
+ } else {
+ # Custom page size for Foomatic/Gimp-Print
+ $cmdvar =~ s/\%0/$width/ or
+ $cmdvar =~ s/(\W)0(\W)/$1$width$2/ or
+ $cmdvar =~ s/^0(\W)/$width$1/m or
+ $cmdvar =~ s/(\W)0$/$1$width/m or
+ $cmdvar =~ s/^0$/$width/m;
+ $cmdvar =~ s/\%1/$height/ or
+ $cmdvar =~ s/(\W)0(\W)/$1$height$2/ or
+ $cmdvar =~ s/^0(\W)/$height$1/m or
+ $cmdvar =~ s/(\W)0$/$1$height/m or
+ $cmdvar =~ s/^0$/$height/m;
+ }
+ }
+ } else {
+ # User gave unknown value?
+ $userval = 'None';
+ print $logh "Value $userval for $name is not a valid choice.\n";
+ }
+ } else {
+ $userval = 'None';
+ }
+
+ } elsif (($type eq 'string') || ($type eq 'password')) {
+ # Stick the entered value into the proto and
+ # thence into the commandline
+ if (defined($userval)) {
+ my $val;
+ if ($val=valbyname($arg,$userval)) {
+ $userval = $val->{'value'};
+ $cmdvar = (defined($val->{'driverval'})
+ ? $val->{'driverval'}
+ : $val->{'value'});
+ } else {
+ my $sprintfcmd = $cmd;
+ $sprintfcmd =~ s/\%(?!s)/\%\%/g;
+ $cmdvar = sprintf($sprintfcmd, $userval);
+ }
+ } else {
+ $userval = 'None';
+ }
+
+ } else {
+ # Ignore unknown option types silently
+ }
+
+ # Insert the built snippet at the correct place
+ if ($arg->{'style'} eq 'G') {
+ # Place this Postscript command onto the prepend queue
+ # for the appropriate section.
+ if ($cmdvar) {
+ my $open = "[{\n%%BeginFeature: *$name $userval\n";
+ my $close = "\n%%EndFeature\n} stopped cleartomark\n";
+ if ($section eq "Prolog") {
+ push (@prologprepend, "$open$cmdvar$close");
+ my $a = $arg;
+ while ($a->{'controlledby'}) {
+ # Collect option PostScript code to be inserted when
+ # the composite option which controls this option
+ # is found in the PostScript code
+ $a = $dat->{'args_byname'}{$a->{'controlledby'}};
+ $a->{'prolog'} .= "$cmdvar\n";
+ }
+ } elsif ($section eq "AnySetup") {
+ if ($optionset ne 'currentpage') {
+ push (@setupprepend, "$open$cmdvar$close");
+ } elsif ($arg->{'header'} ne $userval) {
+ push (@pagesetupprepend, "$open$cmdvar$close");
+ push (@cupspagesetupprepend, "$open$cmdvar$close");
+ }
+ my $a = $arg;
+ while ($a->{'controlledby'}) {
+ # Collect option PostScript code to be inserted when
+ # the composite option which controls this option
+ # is found in the PostScript code
+ $a = $dat->{'args_byname'}{$a->{'controlledby'}};
+ $a->{'setup'} .= "$cmdvar\n";
+ $a->{'pagesetup'} .= "$cmdvar\n";
+ }
+ } elsif ($section eq "DocumentSetup") {
+ push (@setupprepend, "$open$cmdvar$close");
+ my $a = $arg;
+ while ($a->{'controlledby'}) {
+ # Collect option PostScript code to be inserted when
+ # the composite option which controls this option
+ # is found in the PostScript code
+ $a = $dat->{'args_byname'}{$a->{'controlledby'}};
+ $a->{'setup'} .= "$cmdvar\n";
+ }
+ } elsif ($section eq "PageSetup") {
+ push (@pagesetupprepend, "$open$cmdvar$close");
+ my $a = $arg;
+ while ($a->{'controlledby'}) {
+ # Collect option PostScript code to be inserted when
+ # the composite option which controls this option
+ # is found in the PostScript code
+ $a = $dat->{'args_byname'}{$a->{'controlledby'}};
+ $a->{'pagesetup'} .= "$cmdvar\n";
+ }
+ } elsif ($section eq "JCLSetup") {
+ # PJL/JCL argument
+ $dat->{'jcl'} = 1;
+ push (@jclprepend, unhexify($cmdvar));
+ my $a = $arg;
+ while ($a->{'controlledby'}) {
+ # Collect option PostScript code to be inserted when
+ # the composite option which controls this option
+ # is found in the PostScript code
+ $a = $dat->{'args_byname'}{$a->{'controlledby'}};
+ $a->{'jclsetup'} .= "$cmdvar\n";
+ }
+ } else {
+ push (@setupprepend, "$open$cmdvar$close");
+ my $a = $arg;
+ while ($a->{'controlledby'}) {
+ # Collect option PostScript code to be inserted when
+ # the composite option which controls this option
+ # is found in the PostScript code
+ $a = $dat->{'args_byname'}{$a->{'controlledby'}};
+ $a->{'setup'} .= "$cmdvar\n";
+ }
+ }
+ }
+ # Do we have an option which is set to "Controlled by
+ # '<Composite>'"? Then make PostScript code available
+ # for substitution of "%% FoomaticRIPOptionSetting: ..."
+ if ($arg->{'fromcomposite'}) {
+ $arg->{'compositesubst'} = "$cmdvar\n";
+ }
+ } elsif ($arg->{'style'} eq 'J') {
+ # JCL argument
+ $dat->{'jcl'} = 1;
+ # put JCL commands onto JCL stack...
+ push (@jclprepend, "\@PJL $cmdvar\n") if $cmdvar;
+ } elsif ($arg->{'style'} eq 'C') {
+ # command-line argument
+
+ # Insert the processed argument in the commandline
+ # just before every occurance of the spot marker.
+ $dat->{'currentcmd'} =~ s!\%$spot!$cmdvar\%$spot!g;
+ }
+ # Insert option into command line of CUPS raster driver
+ if ($dat->{'currentcmd'} =~ m!\%Y!) {
+ next if !defined($userval) or $userval eq "";
+ $dat->{'currentcmd'} =~ s!\%Y!$name=$userval \%Y!g;
+ }
+ # Remove the marks telling that this option is currently controlled
+ # by a composite option (setting "From<composite>")
+ undef $arg->{'fromcomposite'};
+ undef $arg->{'controlledby'};
+ }
+
+
+ ### Tidy up after computing option statements for all of P, J, and
+ ### C types:
+
+ ## C type finishing
+ # Pluck out all of the %n's from the command line prototype
+ my @letters = qw/A B C D E F G H I J K L M W X Y Z/;
+ for my $spot (@letters) {
+ # Remove the letter markers from the commandline
+ $dat->{'currentcmd'} =~ s!\%$spot!!g;
+ }
+
+ ## J type finishing
+ # Compute the proper stuff to say around the job
+
+ if ((defined($dat->{'jcl'})) && (!$jobhasjcl)) {
+
+ # Stick beginning of job cruft on the front of the jcl stuff...
+ unshift (@jclprepend, $jclbegin);
+
+ # Command to switch to the interpreter
+ push (@jclprepend, $jcltointerpreter);
+
+ # Arrange for JCL RESET command at end of job
+ push (@jclappend, $jclend);
+
+ # Put the JCL stuff into the data structure
+ @{$dat->{'jclprepend'}} = @jclprepend;
+ @{$dat->{'jclappend'}} = @jclappend;
+ }
+
+ ## G type finishing
+ # Save PostScript options
+ @{$dat->{'prologprepend'}} = @prologprepend;
+ @{$dat->{'setupprepend'}} = @setupprepend;
+ @{$dat->{'pagesetupprepend'}} = @pagesetupprepend;
+ @{$dat->{'cupspagesetupprepend'}} = @cupspagesetupprepend;
+}
+
+sub buildpdqdriver {
+
+ # Build a PDQ driver description file to use the given PPD file
+ # together with foomatic-rip with the PDQ printing system
+
+ # Foomatic data and name of the option set for the default settings
+ my ($dat, $optionset) = @_;
+
+ # Construct structure with driver information
+ my @pdqdriver = ();
+
+ # Construct option list
+ my @driveropts = ();
+
+ # Do we have a "Custom" setting for the page size?
+ # Then we have to insert the following into the "filter_exec" script.
+ my @setcustompagesize = ();
+
+ # Fata for a custom page size, to allow a custom size as default
+ my $pagewidth = 612;
+ my $pageheight = 792;
+ my $pageunit = "pt";
+
+
+
+ ## First, compute the various option/value clauses
+ for my $arg (@{$dat->{'args'}}) {
+
+ if ($arg->{'type'} eq "enum") {
+
+ # Option with only one choice, omit it, foomatic-rip will set
+ # this choice anyway.
+ next if ($#{$arg->{'vals'}} < 1);
+
+ my $nam = $arg->{'name'};
+
+ # Omit "PageRegion" option, it does the same as "PageSize".
+ next if $nam eq "PageRegion";
+
+ my $com = $arg->{'comment'};
+
+ # Assure that the comment is not empty
+ if (!$com) {
+ $com = $nam;
+ }
+
+ my $def = $arg->{$optionset};
+ $arg->{'varname'} = "$nam";
+ $arg->{'varname'} =~ s![\-\/\.]!\_!g;
+ my $varn = $arg->{'varname'};
+
+ # 1, if setting "PageSize=Custom" was found
+ # Then we must add options for page width and height
+ my $custompagesize = 0;
+
+ # If the default is a custom size we have to set also
+ # defaults for the width, height, and units of the page
+ if (($nam eq "PageSize") &&
+ ($def =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/)) {
+ $def = "Custom";
+ $pagewidth = $1;
+ $pageheight = $2;
+ $pageunit = $3;
+ }
+
+ # No quotes, thank you.
+ $com =~ s!\"!\\\"!g;
+
+ push(@driveropts,
+ " option {\n",
+ " var = \"$varn\"\n",
+ " desc = \"$com\"\n");
+
+ # get enumeration values for each enum arg
+ my ($ev, @vals, @valstmp);
+ for $ev (@{$arg->{'vals'}}) {
+ my $choiceshortname = $ev->{'value'};
+ my $choicename = "${nam}_${choiceshortname}";
+ my $val = " -o ${nam}=${choiceshortname}";
+ my $com = $ev->{'comment'};
+
+ # Assure that the comment is not empty
+ if (!$com) {
+ $com = $choiceshortname;
+ }
+
+ # stick another choice on driveropts
+ push(@valstmp,
+ " choice \"$choicename\" {\n",
+ " desc = \"$com\"\n",
+ " value = \"$val\"\n",
+ " }\n");
+ if (($nam eq "PageSize") &&
+ ($choiceshortname eq "Custom")) {
+ $custompagesize = 1;
+ if ($#setcustompagesize < 0) {
+ push(@setcustompagesize,
+ " # Custom page size settings\n",
+ " # We aren't really checking for " .
+ "legal vals.\n",
+ " if [ \"x\${$varn}\" == 'x$val' ]; " .
+ "then\n",
+ " $varn=\"\${$varn}.\${PageWidth}" .
+ "x\${PageHeight}\${PageSizeUnit}\"\n",
+ " fi\n\n");
+ }
+ }
+ }
+
+ push(@driveropts,
+ " default_choice \"" . $nam . "_" . $def . "\"\n",
+ @valstmp,
+ " }\n\n");
+
+ if ($custompagesize) {
+ # Add options to set the custom page size
+ push(@driveropts,
+ " argument {\n",
+ " var = \"PageWidth\"\n",
+ " desc = \"Page Width (for \\\"Custom\\\" page " .
+ "size)\"\n",
+ " def_value \"$pagewidth\"\n",
+ " help = \"Minimum value: 0, Maximum value: " .
+ "100000\"\n",
+ " }\n\n",
+ " argument {\n",
+ " var = \"PageHeight\"\n",
+ " desc = \"Page Height (for \\\"Custom\\\" page " .
+ "size)\"\n",
+ " def_value \"$pageheight\"\n",
+ " help = \"Minimum value: 0, Maximum value: " .
+ "100000\"\n",
+ " }\n\n",
+ " option {\n",
+ " var = \"PageSizeUnit\"\n",
+ " desc = \"Unit (for \\\"Custom\\\" page size)\"\n",
+ " default_choice \"PageSizeUnit_$pageunit\"\n",
+ " choice \"PageSizeUnit_pt\" {\n",
+ " desc = \"Points (1/72 inch)\"\n",
+ " value = \"pt\"\n",
+ " }\n",
+ " choice \"PageSizeUnit_in\" {\n",
+ " desc = \"Inches\"\n",
+ " value = \"in\"\n",
+ " }\n",
+ " choice \"PageSizeUnit_cm\" {\n",
+ " desc = \"cm\"\n",
+ " value = \"cm\"\n",
+ " }\n",
+ " choice \"PageSizeUnit_mm\" {\n",
+ " desc = \"mm\"\n",
+ " value = \"mm\"\n",
+ " }\n",
+ " }\n\n");
+ }
+
+ } elsif ($arg->{'type'} eq 'int' or $arg->{'type'} eq 'float') {
+
+ my $nam = $arg->{'name'};
+ my $com = $arg->{'comment'};
+
+ # Assure that the comment is not empty
+ if (!$com) {
+ $com = $nam;
+ }
+
+ my $def = $arg->{$optionset};
+ my $max = $arg->{'max'};
+ my $min = $arg->{'min'};
+ $arg->{'varname'} = "$nam";
+ $arg->{'varname'} =~ s![\-\/\.]!\_!g;
+ my $varn = $arg->{'varname'};
+ my $legal = $arg->{'legal'} =
+ "Minimum value: $min, Maximum value: $max";
+
+ my $defstr = "";
+ if ($def) {
+ $defstr = sprintf(" def_value \"%s\"\n", $def);
+ }
+
+ push(@driveropts,
+ " argument {\n",
+ " var = \"$varn\"\n",
+ " desc = \"$com\"\n",
+ $defstr,
+ " help = \"$legal\"\n",
+ " }\n\n");
+
+ } elsif ($arg->{'type'} eq 'bool') {
+
+ my $nam = $arg->{'name'};
+ my $com = $arg->{'comment'};
+
+ # Assure that the comment is not empty
+ if (!$com) {
+ $com = $nam;
+ }
+
+ my $tcom = $arg->{'comment_true'};
+ my $fcom = $arg->{'comment_false'};
+ my $def = $arg->{$optionset};
+ $arg->{'legal'} = "Value is a boolean flag";
+ $arg->{'varname'} = "$nam";
+ $arg->{'varname'} =~ s![\-\/\.]!\_!g;
+ my $varn = $arg->{'varname'};
+
+ my $defstr = "";
+ if ($def) {
+ $defstr = sprintf(" default_choice \"%s\"\n",
+ $def ? "$nam" : "no$nam");
+ } else {
+ $defstr = sprintf(" default_choice \"%s\"\n", "no$nam");
+ }
+ push(@driveropts,
+ " option {\n",
+ " var = \"$varn\"\n",
+ " desc = \"$com\"\n",
+ $defstr,
+ " choice \"$nam\" {\n",
+ " desc = \"$tcom\"\n",
+ " value = \" -o $nam=True\"\n",
+ " }\n",
+ " choice \"no$nam\" {\n",
+ " desc = \"$fcom\"\n",
+ " value = \" -o $nam=False\"\n",
+ " }\n",
+ " }\n\n");
+
+ } elsif ($arg->{'type'} eq 'string' or $arg->{'type'} eq 'password') {
+
+ my $nam = $arg->{'name'};
+ my $com = $arg->{'comment'};
+
+ # Assure that the comment is not empty
+ if (!$com) {
+ $com = $nam;
+ }
+
+ my $def = $arg->{$optionset};
+ my $maxlength = $arg->{'maxlength'};
+ my $proto = $arg->{'proto'};
+ $arg->{'varname'} = "$nam";
+ $arg->{'varname'} =~ s![\-\/\.]!\_!g;
+ my $varn = $arg->{'varname'};
+
+ my $legal;
+ if (defined($maxlength)) {
+ $legal .= "Maximum length: $maxlength characters, ";
+ }
+ $legal .= "Examples/special settings: ";
+ for (@{$arg->{'vals'}}) {
+ my ($value, $comment, $driverval) =
+ ($_->{'value'}, $_->{'comment'}, $_->{'driverval'});
+ # Retrieve the original string from the prototype
+ # and the driverval
+ my $string;
+ if ($proto) {
+ my $s = index($proto, '%s');
+ my $l = length($driverval) - length($proto) + 2;
+ if (($s < 0) || ($l < 0)) {
+ $string = $driverval;
+ } else {
+ $string = substr($driverval, $s, $l);
+ }
+ } else {
+ $string = $driverval;
+ }
+ if ($value ne $string) {
+ $legal .= "${value}: \\\"$string\\\"";
+ } else {
+ $legal .= "\\\"$value\\\"";
+ }
+ if ($comment && ($value ne $comment) &&
+ ($string ne $comment) &&
+ (($value ne 'None') || ($comment ne '(None)'))) {
+ $legal .= " ($comment)";
+ }
+ $legal .= "; ";
+ }
+ $legal =~ s/; $//;
+
+ $arg->{'legal'} = $legal;
+
+ my $defstr = "";
+ if ($def) {
+ $defstr = sprintf(" def_value \"%s\"\n", $def);
+ }
+
+ push(@driveropts,
+ " argument {\n",
+ " var = \"$varn\"\n",
+ " desc = \"$com\"\n",
+ $defstr,
+ " help = \"$legal\"\n",
+ " }\n\n");
+
+ }
+
+ }
+
+
+
+ ## Define the "docs" option to print the driver documentation page
+
+ push(@driveropts,
+ " option {\n",
+ " var = \"DRIVERDOCS\"\n",
+ " desc = \"Print driver usage information\"\n",
+ " default_choice \"nodocs\"\n",
+ " choice \"docs\" {\n",
+ " desc = \"Yes\"\n",
+ " value = \" -o docs\"\n",
+ " }\n",
+ " choice \"nodocs\" {\n",
+ " desc = \"No\"\n",
+ " value = \"\"\n",
+ " }\n",
+ " }\n\n");
+
+
+
+ ## Build the "foomatic-rip" command line
+ my $commandline = "foomatic-rip --pdq";
+ if ($printer) {
+ $commandline .= " -P $printer";
+ } else {
+ # Make sure that the PPD file is entered with an absolute path
+ if ($ppdfile !~ m!^/!) {
+ my $pwd = cwd;
+ $ppdfile = "$pwd/$ppdfile";
+ }
+ $commandline .= " --ppd=$ppdfile";
+ }
+ for my $arg (@{$dat->{'args'}}) {
+ if ($arg->{'varname'}) {
+ $commandline .= "\${$arg->{'varname'}}";
+ }
+ }
+ $commandline .= "\${DRIVERDOCS} \$INPUT > \$OUTPUT";
+
+
+
+ ## Now we generate code to build the command line snippets for the
+ ## numerical options
+
+ my @psfilter;
+ for my $arg (@{$dat->{'args'}}) {
+
+ # Only numerical and string options need to be treated here
+ next if (($arg->{'type'} ne 'int') &&
+ ($arg->{'type'} ne 'float') &&
+ ($arg->{'type'} ne 'string') &&
+ ($arg->{'type'} ne 'password'));
+
+ my $comment = $arg->{'comment'};
+ my $name = $arg->{'name'};
+ my $varname = $arg->{'varname'};
+
+ # If the option's variable is non-null, put in the
+ # argument. Otherwise this option is the empty
+ # string. Error checking?
+
+ push(@psfilter,
+ " # $comment\n",
+ (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ?
+ (" # We aren't really checking for max/min,\n",
+ " # this is done by foomatic-rip\n",
+ " if [ \"x\${$varname}\" != 'x' ]; then\n ") : ""),
+ #" $varname=`echo \${$varname} | perl -p -e \"s/'/'\\\\\\\\\\\\\\\\''/g\"`\n",
+ " $varname=\" -o $name='\${$varname}'\"\n",
+ (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ?
+ " fi\n" : ""),
+ "\n");
+ }
+
+ # Command execution
+
+ push(@psfilter,
+ " if ! test -e \$INPUT.ok; then\n",
+ " sh -c \"$commandline\"\n",
+ " if ! test -e \$OUTPUT; then \n",
+ " echo 'Error running foomatic-rip; no output!'\n",
+ " exit 1\n",
+ " fi\n",
+ " else\n",
+ " ln -s \$INPUT \$OUTPUT\n",
+ " fi\n\n");
+
+ my $version = time();
+ my $name = "$model-$version";
+ $name =~ s/\W/\-/g;
+ $name =~ s/\-+/\-/g;
+
+ my $pname = $model;
+
+ push (@pdqdriver,
+ "driver \"$name\" {\n\n",
+ " # This PDQ driver declaration file was generated " .
+ "automatically by\n",
+ " # foomatic-rip from information in the file $ppdfile.\n",
+ " # It allows printing with PDQ on the $pname.\n",
+ "\n",
+ " requires \"foomatic-rip\"\n\n",
+ @driveropts,
+ " language_driver all {\n",
+ " # We accept all file types and pass them to foomatic-rip\n",
+ " # (invoked in \"filter_exec {}\" section) without\n",
+ " # pre-filtering\n",
+ " filetype_regx \"\"\n",
+ " convert_exec {\n",
+ " ln -s \$INPUT \$OUTPUT\n",
+ " }\n",
+ " }\n\n",
+ " filter_exec {\n",
+ @setcustompagesize,
+ @psfilter,
+ " }\n",
+ "}\n");
+
+ return @pdqdriver;
+
+}
+
+
+
+# Emacs tabulator/indentation
+
+### Local Variables:
+### tab-width: 8
+### perl-indent-level: 4
+### End: