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