home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
tusportal.tus.k12.pa.us
/
tusportal.tus.k12.pa.us.tar
/
tusportal.tus.k12.pa.us
/
Wyse
/
latest-image.raw
/
0.img
/
usr
/
bin
/
foomatic-rip
< prev
next >
Wrap
Text File
|
2009-05-12
|
216KB
|
6,746 lines
#!/usr/bin/perl
# The above Perl path may vary on your system; fix it!!! -*- perl -*-
use strict;
use POSIX;
use Cwd;
my $ripversion='$Revision$';
#'# 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.openprinting.org/cups-doc.html
# http://www.openprinting.org/lpd-doc.html
# http://www.openprinting.org/ppr-doc.html
# http://www.openprinting.org/pdq-doc.html
# http://www.openprinting.org/direct-doc.html
# http://www.openprinting.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 = "/usr/bin:/usr/local/bin:/usr/bin:/bin";
# CUPS raster drivers are searched here
my $cupsfilterpath = "/usr/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 = "/usr";
my $configpath = "/etc/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';
# Which shell to use for executing shell commands. Some of the PPD files
# specify a FoomaticRIPCommandLine that makes use of constructs not available
# from a vanilla Bourne shell. On systems where /bin/sh is a vanilla Bourne
# we need to use a more "modern" shell to execute the command. This will
# be set via a 'preferred_shell: (shell)' setting in the foomatic.conf file
# or automatically detected at runtime later on in this program.
#
my $modern_shell = '';
# 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 - 2008 Grant Taylor <gtaylor@picante.com>
# & Till Kamppeter <till.kamppeter@gmail.com>
# & 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.
#
# strip out dangerous \x01 chars in arguments to avoid a security hole in cups.
for (my $i=0; $i<=$#ARGV; $i++)
{
if (defined($ARGV[$i]))
{
$ARGV[$i] =~ s/\001//g;
}
}
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
# The modern_shell() function will register the PIDs of all shell calls,
# so that rip_die() can kill these processes
my %pids;
# $kidgeneration stays 0 for the main process, child processes of the
# main process get $kidgeneration = 1, their children 2, ...
my $kidgeneration = 0;
# Catch signals
my $retval = $EXIT_PRINTED;
use sigtrap qw(handler set_exit_canceled normal-signals
handler set_exit_error error-signals
handler set_exit_prnerr USR1
handler set_exit_prnerr_noretry USR2
handler set_exit_engaged TTIN);
## 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
# solaris - Solaris LP (possibly some other SysV LP services as well)
# 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 ID
my $jobid = "";
# User who sent job
my $jobuser = ((getpwuid($<))[0] || `whoami` || "");
chomp $jobuser;
# Host from which job was sent
my $jobhost = `hostname`;
chomp $jobhost;
# Job title
my $jobtitle = "$jobuser\@$jobhost";
# Number of copies
my $copies = "1";
my $rbinumcopies = "0";
# Post pipe (command into which the output of this filter should be piped)
my $postpipe = "";
# job meta-data file path (for Solaris LP)
my $attrpath = '';
# Files to be printed
my @filelist = ();
# Where to send debugging log output. Initialized to STDERR until the command
# line arguments are parsed.
my $logh = *STDERR;
# 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";
# Prefix for starting every JCL command (Can be modified by
# "*FoomaticJCLPrefix:" keyword in the PPD file):
my $jclprefix = "\@PJL ";
# 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];
}
# Set the preferred shell for "system()" execution
(defined $conf{preferred_shell}) &&
($modern_shell = $conf{preferred_shell});
# if none was preferred, look for a shell that will work
foreach my $shell ('/bin/sh', '/bin/bash', '/bin/ksh', '/bin/zsh') {
if (($modern_shell eq '') && (-x $shell)) {
open(FD, "| ".$shell." -c \"((0<1))\" 2>/dev/null");
(close(FD) == 1) && ($modern_shell = $shell);
}
}
## Environment variables;
# "PPD": PPD file name for CUPS, Solaris, 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, Solaris LP, 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 Solaris LP and the PPR)
$spooler = 'cups';
}
# "SPOOLER_KEY": Solaris LP print service
if (defined($ENV{'SPOOLER_KEY'})) {
$spooler = 'solaris';
$ppdfile = $ENV{'PPD'};
# set the printer name from the PPD file name
($ppdfile =~ m!^.*/([^/]+)\.ppd$!) &&
($printer = $1);
# Solaris LP may augment the "options" string argument from the command
# line with an attributes file ($ATTRPATH)
(defined($attrpath = $ENV{'ATTRPATH'})) &&
($optstr = read_attribute_file($attrpath));
}
# "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";
# Version check
if ($argstr =~ /^\x01-(h|v|-help|-version)\x01$/i) {
my $ver;
if ($ripversion =~ /^\$Revision=(.*)\$$/) {
$ver = $1;
} else {
$ver = "Unknown";
}
print "foomatic-rip revision $ver\n";
print "\"man foomatic-rip\" for help.\n";
exit 0;
}
# 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;
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
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 = $2;
}
while ($argstr =~ s/\x01--ppd(\x01|=|)([^\x01]+)\x01/\x01/) {
$ppdfile = $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-h(\x01|)([^\x01]+)\x01/\x01/) {
# We have LPD or GNUlpr
if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
$spooler = 'lpd';
}
$jobhost = $2;
}
if ($argstr =~ s/\x01-n(\x01|)([^\x01]+)\x01/\x01/) {
# We have LPD or GNUlpr
if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) {
$spooler = 'lpd';
}
$jobuser = $2;
}
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/)) {
# 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') {
# Use CUPS font path ("FontPath" in /etc/cups/cupsd.conf)
if ($ENV{'CUPS_FONTPATH'}) {
$ENV{'GS_LIB'} = $ENV{'CUPS_FONTPATH'} .
($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : "");
} else {
if ($ENV{'CUPS_DATADIR'}) {
$ENV{'GS_LIB'} = "$ENV{'CUPS_DATADIR'}/fonts" .
($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : "");
}
}
# 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;
$jobid = $cups_jobid;
$jobtitle = $cups_jobtitle;
$jobuser = $cups_user;
$copies = $cups_copies;
$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";
}
}
# Solaris LP spooler
if ($spooler eq 'solaris') {
# Get all command line parameters
# $printer = # argv[0]
# ($rargs[0] =~ m!^.*/([^/]+)$!);
# $request_id = removeshellescapes($rargs[0]); # argv[1]
# $user_name = removeshellescapes($rargs[1]); # argv[2]
$jobtitle = removeshellescapes($rargs[2]); # argv[3]
# $copies = removeshellescapes($rargs[3]); # argv[4] # handled by the
# interface script
$optstr .= removeshellescapes($rargs[4]); # argv[5]
($#rargs > 4) && # argv[6...]
(@filelist = @rargs[5, $#rargs]);
}
# 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 = $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;
my $jclprefixset = 0;
# 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+?)\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!^\*FoomaticNoPageAccounting:\s*\"?\s*(\S+?)\s*\"?\s*$!) {
# "*FoomaticRIPNoPageAccounting: <boolean value>"
my $value = $1;
# Apply the value
if ($value =~ /^True$/i) {
# Driver is not compatible with page accounting according to the
# Foomatic database, so turn it off for this driver
$ps_accounting = 0;
$accounting_prolog = '';
print $logh "CUPS page accounting disabled by driver.\n";
}
} 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)\s*\"?\s*$!) ||
(m!^\*FoomaticRIPOption ([^/:\s]+):\s*\"?\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+?)\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+?)\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]+?)\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 (!defined ($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 (!defined ($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!^\*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix):\s*\"(.*)$!) {
# "*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix): <code>"
# The printer supports PJL/JCL when there is such a line
$dat->{'jcl'} = 1;
my $item = $2;
my $line = $3;
# 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);
$jclprefix = "" if (!$jclprefixset) && ($jclbegin !~ /PJL/s);
} elsif ($item eq 'ToPSInterpreter') {
$jcltointerpreter = unhexify($code);
} elsif ($item eq 'End') {
$jclend = unhexify($code);
} elsif ($item eq 'Prefix') {
$jclprefix = unhexify($code);
$jclprefixset = 1;
}
} 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'};
}
if ($arg->{'type'} eq 'int') {
$arg->{'fdefault'} = POSIX::floor($arg->{'fdefault'});
}
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 "Shell: $modern_shell\n";
print $logh "PPD file: $ppdfile\n";
print $logh "ATTR file: $attrpath\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";
print $logh "GhostScript extra search path ('GS_LIB'): $ENV{'GS_LIB'}\n"
if $ENV{'GS_LIB'};
## 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';
}
# Solaris options that have no reason to be
if (/^nobanner$/ || /^dest=.+$/ || /^protocol=.+$/) {
next;
}
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'})) {
# Default to long-edge binding here, for the case that
# there is no binding setting
$dat->{'args_byname'}{'Duplex'}{$optionset} =
'DuplexNoTumble';
# 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} =
'DuplexNoTumble';
}
} 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} =
'DuplexTumble';
}
}
}
} elsif ($avalue =~ m!^one-sided!i) {
if (defined($dat->{'args_byname'}{'Duplex'})) {
$dat->{'args_byname'}{'Duplex'}{$optionset} = 'None';
}
}
# 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) {
modern_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";
modern_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 $linesafterlastbeginfeature = ""; # All code lines after the last
# "%%BeginFeature:"
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.
my $saved = 0; # DSC line not processed yet
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) || ($saved) || ($line=<STDIN>)) {
$saved = 0;
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-/) {
# 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 =~ /^\%/) {
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) {
# One last update for the header
buildcommandline($dat, $optionset);
# 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'});
}
}
}
# 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;
}
# Insert PostScript option settings
# (options for section "PageSetup".
if ($isdscjob) {
$line .=
makepagesetupsection($dat, $optionset,
0);
$pagesetupfound = 1;
}
} 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;
$linesafterlastbeginfeature = "";
}
# 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" or
# "imagetops". The value from the command
# line was not inserted by "pstops" or
# "imagetops" so it seems to be not under
# the choices in the PPD. Possible
# reasons:
#
# - "pstops" and "imagetops" ignore 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" or "imagetops"
# 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);
my $val;
if ($arg->{'style'} eq 'G') {
# PostScript option, insert the code
if ($arg->{'type'} eq 'bool') {
# Boolean option
push(@{$dest},
"%%BeginFeature: *$option " .
($arg->{$optionset} == 1 ?
"True" : "False") . "\n");
if (defined($arg->{$optionset}) &&
$arg->{$optionset} == 1) {
push(@{$dest}, $arg->{'proto'} .
"\n");
} elsif ($arg->{'protof'}) {
push(@{$dest}, $arg->{'protof'} .
"\n");
}
# We have replaced this option on the
# FIFO
$optionreplaced = 1;
} 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},
"%%BeginFeature: " .
"*$option $arg->{$optionset}\n");
push(@{$dest}, $val->{'driverval'} . "\n");
# We have replaced this option on the
# FIFO
$optionreplaced = 1;
} elsif ((($arg->{'type'} eq 'string') ||
($arg->{'type'} eq
'password')) &&
($arg->{$optionset} eq 'None')) {
# 'None' is mapped to the empty string
# in string options
push(@{$dest},
"%%BeginFeature: " .
"*$option $arg->{$optionset}\n");
my $driverval = $arg->{'proto'};
$driverval =~ s/\%s//g;
push(@{$dest}, $driverval . "\n");
# We have replaced this option on the
# FIFO
$optionreplaced = 1;
} elsif (($arg->{'type'} eq 'int') ||
($arg->{'type'} eq 'float') ||
($arg->{'type'} eq 'string') ||
($arg->{'type'} eq 'password')) {
# Setting for numerical or string
# option which is not under the
# enumerated choices
push(@{$dest},
"%%BeginFeature: " .
"*$option $arg->{$optionset}\n");
my $sprintfproto = $arg->{'proto'};
$sprintfproto =~ s/\%(?!s)/\%\%/g;
push(@{$dest},
sprintf($sprintfproto,
$arg->{$optionset}) .
"\n");
# We have replaced this option on the
# FIFO
$optionreplaced = 1;
}
} else {
# Command line or JCL option
push(@{$dest},
"%% FoomaticRIPOptionSetting: " .
"$option=$arg->{$optionset}\n");
# We have replaced this option on the
# FIFO
$optionreplaced = 1;
}
print $logh " --> Correcting numerical/string " .
"option to $option=$arg->{$optionset}" .
" (Command line argument)\n" if
$optionreplaced;
}
# 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
$linesafterlastbeginfeature =~
/^[\s\r\n]*([\d\.]+)[\s\r\n]+([\d\.]+)[\s\r\n]+/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, and the next option is not necessarily also
# replaced.
$optionreplaced = 0;
$linesafterlastbeginfeature = "";
} 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*\%(\%?)RBINumCopies:\s*(\d+)\s*$/) &&
($nestinglevel == 0)) {
# RBINumCopies entry
$rbinumcopies = $2;
print $logh "Found: %${1}RBINumCopies: $rbinumcopies\n";
} 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 ($infeature) {
# Collect coe in a "%%BeginFeature: ... %%EndFeature"
# section, to get the values for a custom option
# setting
$linesafterlastbeginfeature .= $line;
}
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;
}
# Insert PostScript option settings
# (options for section "PageSetup".
if ($isdscjob && !$pagesetupfound) {
$line =
makepagesetupsection($dat, $optionset,
1) . $line;
$pagesetupfound = 1;
}
}
}
}
}
# Debug info
if ($lastpassthru != $passthru) {
if ($passthru) {
print $logh "Found: $line" .
" --> Output goes directly to the renderer now.\n${added_lf}";
} else {
print $logh "Found: $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;
while ($line=<STDIN>)
{
if ($line =~ /^\%\%[A-Za-z\s]{3,}/) {
print $logh "Found: $line" .
" --> Continue DSC parsing now.${added_lf}\n";
$saved = 1;
last;
} else {
print $rendererhandle $line;
$linect++;
}
}
}
} 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) {
# One last update for the header
buildcommandline($dat, $optionset);
# 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";
# Reset return value of the renderer
$retval = $EXIT_PRINTED;
# 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 {
$kidgeneration += 1;
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...
modern_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 {
$kidgeneration += 1;
# 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;
$cmd =~ s/^\s*(.*?)\s*$/$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
my $buf;
while (read(KID4_IN, $buf, 1024)) {
print $fileh $buf;
}
# 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 {
$kidgeneration += 1;
# 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...
modern_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 {
$kidgeneration += 1;
# 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
my $buf;
while (read(STDIN, $buf, 1024)) {
print KID2 $buf;
}
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 );
}
$kidgeneration += 1;
# 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 JCL 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;
# Log that we are dying ...
print $logh "Process dying with \"$message\", exit stat: $exitstat\n\terror: $errmsg ($errcod)\n";
print $logh "Cleaning up ...\n";
foreach my $killsignal (15, 9) {
# Kill all registered subshells
foreach my $pid (keys %pids) {
print $logh "Killing process $pid ($pids{$pid}) and its subprocesses with signal $killsignal\n";
# This call kills the process group with group ID $pid, the
# group which was formed from the initial process $pid which
# contains $pid and all its subprocesses
kill(-$killsignal, $pid);
# If the system does not support process groups and therefore
# the call above does not kill anything, kill at least $pid
kill($killsignal, $pid);
}
# Close the documentation page generator (if it was used)
if ($kid0) {
print $logh "Killing process $kid0 (KID0) with signal $killsignal\n";
kill($killsignal, $kid0);
}
# Close the file converter (if it was used)
if ($kid2) {
print $logh "Killing process $kid2 (KID2) with signal $killsignal\n";
kill($killsignal, $kid2);
}
if ($kid1) {
print $logh "Killing process $kid1 (KID1) with signal $killsignal\n";
kill($killsignal, $kid1);
}
# Close the renderer
if ($kid4) {
print $logh "Killing process $kid4 (KID4) with signal $killsignal\n";
kill($killsignal, $kid4);
}
if ($kid3) {
print $logh "Killing process $kid3 (KID3) with signal $killsignal\n";
kill($killsignal, $kid3);
}
# Wait some time for the processes to close
sleep(5 - $kidgeneration) if $killsignal != 9;
}
# Do the debug dump and the PPR error handling only from the main process
if ($kidgeneration == 0) { # We are the main process
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) {
modern_system("lib/alert $printer $firstline \"$line\"");
$firstline = "FALSE";
}
} else {
print STDERR $message . "\n";
}
if ($debug) {
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 $exitstat;
}
# Signal handling routines
sub do_nothing {
}
sub set_exit_canceled {
$retval = $EXIT_PRINTED;
rip_die ("Caught termination signal: Job canceled", $retval);
}
sub set_exit_error {
$retval = $EXIT_SIGNAL;
rip_die ("Caught error signal: Error in renderer, driver, or foomatic-rip", $retval);
}
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 {
my $str = $_[0];
# Replace HTML/XML entities by the original characters
$str =~ s/\'/\'/g;
$str =~ s/\"/\"/g;
$str =~ s/\>/\>/g;
$str =~ s/\</\</g;
$str =~ s/\&/\&/g;
# Replace special entities by job data
$rbinumcopies = $copies if !$rbinumcopies;
$str =~ s/\&job;/$jobid/g;
$str =~ s/\&user;/$jobuser/g;
$str =~ s/\&host;/$jobhost/g;
$str =~ s/\&title;/$jobtitle/g;
$str =~ s/\&copies;/$copies/g;
$str =~ s/\&rbinumcopies;/$rbinumcopies/g;
$str =~ s/\&options;/$optstr/g;
my ($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0..5];
my $yearstr = sprintf("%04d", $year + 1900);
my $monstr = sprintf("%02d", $mon + 1);
my $mdaystr = sprintf("%02d", $mday);
my $hourstr = sprintf("%02d", $hour);
my $minstr = sprintf("%02d", $min);
my $secstr = sprintf("%02d", $sec);
$str =~ s/\&year;/$yearstr/g;
$str =~ s/\&month;/$monstr/g;
$str =~ s/\&date;/$mdaystr/g;
$str =~ s/\&hour;/$hourstr/g;
$str =~ s/\&min;/$minstr/g;
$str =~ s/\&sec;/$secstr/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'})) {
if ($arg->{'type'} eq 'int') {
return POSIX::floor($value);
} else {
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'};
# Quote the slashes (if a slash is preceeded by an even number of
# backslashes, it is not already quoted)
$chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
return 0 if $value !~ /^[$chars]*$/;
}
# Regular expression
if ($arg->{'allowedregexp'}) {
my $regexp = $arg->{'allowedregexp'};
# Quote the slashes (if a slash is preceeded by an even number of
# backslashes, it is not already quoted)
$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;
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) || ($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 $value;
for my $arg (@{$dat->{'args'}}) {
$value = '';
my $bestscore = 10000000;
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 =~ /^(0|No|Off|False)$/i) {
foreach (qw(0 No Off False None)) {
if ($val=valbyname($arg,$_)) {
$userval = $_;
$arg->{$optionset} = $userval;
$found = 1;
last;
}
}
} elsif ($userval =~ /^(1|Yes|On|True)$/i) {
foreach (qw(1 Yes On True)) {
if ($val=valbyname($arg,$_)) {
$userval = $_;
$arg->{$optionset} = $userval;
$found = 1;
last;
}
}
} elsif ($userval =~ /^(LongEdge|DuplexNoTumble)$/i) {
# 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 =~ /^(ShortEdge|DuplexTumble)$/i) {
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/Gutenprint/
# 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 ";
if ($type eq 'bool') {
$open .= ($userval == 1 ? "True" : "False") . "\n";
} else {
$open .= "$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, "$jclprefix$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;
}
#
# Convert lp or ipp based attribute names (and values) to something that matches# PPD file options.
#
sub option_to_ppd {
my ($ipp_attribute) = @_;
my ($key, $value, $result) = ();
if (/([^=]+)=[\'\"]?(.*}[\'\"]?)/) { # key=value
($key, $value) = ($1, $2);
} elsif (/no(.+)/) { # BOOLEAN: no{key} (false)
($key, $value) = ($1, 'false');
} else { # BOOLEAN: {key} (true)
($key, $value) = ($1, 'true');
}
if (($key =~ /^job-/) || ($key =~ /^copies/) ||
($key =~ /^multiple-document-handling/) || ($key =~ /^number-up/) ||
($key =~ /^orientation-requested/) ||
($key =~ /^dest/) || ($key =~ /^protocol/) || ($key =~ /^banner/) ||
($key =~ /^page-ranges/)) {
# Ignored:
# job-*, multiple-document-handling are not supported by this
# filter
# dest, protocol, banner, number-up, orientation-requested are
# handled by the LP filtering or interface script
# NOTE - page-ranges should probably be handled here, but
# ignore it until we decide how to handle it.
} elsif (/^printer-resolution/) {
# value match on "123, 457" or on "123, 457, 8"
if (/([\d]+),([\s]*)([\d]+)((,([\s]*)([\d]+))??)/) {
$result = '$1x$2$3 '; # (width)x(height)(units)
}
} elsif (/^print-quality/) {
($value == 3) &&
($result = 'PrintoutMode=Draft');
($value == 4) &&
($result = 'PrintoutMode=Normal');
($value == 5) &&
($result = 'PrintoutMode=High');
} else {
# NOTE - if key == 'media', we may need to convert the values at some
# point. (see RFC2911, Section 14 for values)
$result = '$key=\"$value\"';
}
return ($result);
}
#
# Read the attributes file containing the various job meta-data, including
# requested capabilities
#
sub read_attribute_file {
my ($file) = @_;
my $result = "";
open (AFP, "<$file") ||
(print $logh "Unable to open IPP Attribute file ".$file.", ignored: ".$!);
while(<AFP>) {
$result .= option_to_ppd($_);
}
close (AFP);
return ($result);
}
sub modern_system {
my (@list) = @_;
if ($modern_shell |~ /.+/) {
# No "modern" shell other than the default shell was specified
$modern_shell = '/bin/sh';
}
my $pid = fork();
($pid < 0) && die "failed to fork()";
if ($pid == 0) { # child, execute the commands under a modern shell
# If the system supports process groups, we create a process
# group of this subshell process. All the children of this
# process (calls of external filters, renderers, or drivers)
# will be members of this process group and so by killing this
# process group we can kill all subprocesses and so we can
# cleanly cancel print jobs
eval("setpgrp()");
# Stop catching signals
#use sigtrap qw(die normal-signals error-signals
# handler do_nothing USR1 USR2 TTIN);
exec($modern_shell, "-c", @list);
rip_die("exec($modern_shell, \"-c\", @list);",
$EXIT_PRNERR_NORETRY_BAD_SETTINGS);
} else { # parent, register child's PID, wait for the child, and
# unregister the PID
$pids{$pid} = substr(join(" ", @list), 0, 100) .
(length(join(" ", @list)) > 100 ? "..." : "");
print $logh "Starting process $pid: \"$pids{$pid}\"\n";
waitpid($pid, 0);
print $logh "Process $pid ending: \"$pids{$pid}\"\n";
delete $pids{$pid};
}
}
# Emacs tabulator/indentation
### Local Variables:
### tab-width: 8
### perl-indent-level: 4
### End: