home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / share / perl5 / Foomatic / DB.pm next >
Encoding:
Perl POD Document  |  2006-05-23  |  144.7 KB  |  4,869 lines

  1.  
  2. package Foomatic::DB;
  3. use Exporter;
  4. @ISA = qw(Exporter);
  5.  
  6. @EXPORT_OK = qw(normalizename comment_filter
  7.         get_overview
  8.         getexecdocs
  9.         translate_printer_id
  10.         );
  11. @EXPORT = qw(ppdtoperl ppdfromvartoperl);
  12.  
  13. use Foomatic::Defaults qw(:DEFAULT $DEBUG);
  14. use Data::Dumper;
  15. use POSIX;                      # for rounding integers
  16. use strict;
  17.  
  18. my $ver = '$Revision: 3.56.2.26 $ ';
  19.  
  20. # constructor for Foomatic::DB
  21. sub new {
  22.     my $type = shift(@_);
  23.     my $this = bless {@_}, $type;
  24.     return $this;
  25. }
  26.  
  27. # A map from the database's internal one-letter driver types to English
  28. my %driver_types = ('F' => 'Filter',
  29.             'P' => 'Postscript',
  30.             'U' => 'Ghostscript Uniprint',
  31.             'G' => 'Ghostscript');
  32.  
  33. # Translate old numerical PostGreSQL printer IDs to the new clear text ones.
  34. sub translate_printer_id {
  35.     my ($oldid) = @_;
  36.     # Read translation table for the printer IDs
  37.     my $translation_table = "$libdir/db/oldprinterids";
  38.     open TRTAB, "< $translation_table" or return $oldid;
  39.     while (<TRTAB>) {
  40.     chomp;
  41.     my $searcholdid = quotemeta($oldid);
  42.     if (/^\s*$searcholdid\s+(\S+)\s*$/) {
  43.         # ID found, return new ID
  44.         my $newid = $1;
  45.         close TRTAB;
  46.         return $newid;
  47.     }
  48.     }
  49.     # ID not found, return original one
  50.     close TRTAB;
  51.     return $oldid;
  52. }
  53.  
  54. # List of driver names
  55. sub get_driverlist {
  56.     my ($this) = @_;
  57.     return $this->_get_xml_filelist('source/driver');
  58. }
  59.  
  60. # List of printer id's
  61. sub get_printerlist {
  62.     my ($this) = @_;
  63.     return $this->_get_xml_filelist('source/printer');
  64. }
  65.  
  66. sub get_overview {
  67.     my ($this, $rebuild) = @_;
  68.  
  69.     # "$this->{'overview'}" is a memory cache only for the current process
  70.     if ((!defined($this->{'overview'}))
  71.     or (defined($rebuild) and $rebuild)) {
  72.     # Generate overview Perl data structure from database
  73.     my $VAR1;
  74.     eval (`$bindir/foomatic-combo-xml -O -l '$libdir' | $bindir/foomatic-perl-data -O`) ||
  75.         die ("Could not run \"foomatic-combo-xml\"/\"foomatic-perl-data\"!");
  76.     $this->{'overview'} = $VAR1;
  77.     }
  78.  
  79.     return $this->{'overview'};
  80. }
  81.  
  82. sub get_overview_xml {
  83.     my ($this, $compile) = @_;
  84.  
  85.     open( FCX, "$bindir/foomatic-combo-xml -O -l '$libdir'|")
  86.     or die "Can't execute $bindir/foomatic-combo-xml -O -l '$libdir'";
  87.     $_ = join('', <FCX>);
  88.     close FCX;
  89.     return $_;
  90. }
  91.  
  92. sub get_combo_data_xml {
  93.     my ($this, $drv, $poid, $withoptions) = @_;
  94.  
  95.     # Insert the default option settings if there are some and the user
  96.     # desires it.
  97.     my $options = "";
  98.     if (($withoptions) && (defined($this->{'dat'}))) {
  99.     my $dat = $this->{'dat'};
  100.     for my $arg (@{$dat->{'args'}}) {
  101.         my $name = $arg->{'name'};
  102.         my $default = $arg->{'default'};
  103.         if (($name) && ($default)) {
  104.         $options .= " -o '$name'='$default'";
  105.         }
  106.     }
  107.     }
  108.  
  109.     open( FCX, "$bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'|")
  110.     or die "Can't execute $bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'";
  111.     $_ = join('', <FCX>);
  112.     close FCX;
  113.     return $_;
  114. }
  115.  
  116. sub get_printer {
  117.     my ($this, $poid) = @_;
  118.     # Generate printer Perl data structure from database
  119.     my $VAR1;
  120.     if (-r "$libdir/db/source/printer/$poid.xml") {
  121.     eval (`$bindir/foomatic-perl-data -P '$libdir/db/source/printer/$poid.xml'`) ||
  122.         die ("Could not run \"foomatic-perl-data\"!");
  123.     } else {
  124.     return undef;
  125.     }
  126.     return $VAR1;
  127. }
  128.  
  129. sub get_printer_xml {
  130.     my ($this, $poid) = @_;
  131.     return $this->_get_object_xml("source/printer/$poid", 1);
  132. }
  133.  
  134. sub get_driver {
  135.     my ($this, $drv) = @_;
  136.     # Generate driver Perl data structure from database
  137.     my $VAR1;
  138.     if (-r "$libdir/db/source/driver/$drv.xml") {
  139.     eval (`$bindir/foomatic-perl-data -D '$libdir/db/source/driver/$drv.xml'`) ||
  140.         die ("Could not run \"foomatic-perl-data\"!");
  141.     } else {
  142.     return undef;
  143.     }
  144.     return $VAR1;
  145. }
  146.  
  147. sub get_driver_xml {
  148.     my ($this, $drv) = @_;
  149.     return $this->_get_object_xml("source/driver/$drv", 1);
  150. }
  151.  
  152. # Utility query function sorts of things:
  153.  
  154. sub get_printers_for_driver {
  155.     my ($this, $drv) = @_;
  156.  
  157.     my $driver = $this->get_driver($drv);
  158.  
  159.     if (!defined($driver)) {return undef;}
  160.  
  161.     return map { $_->{'id'} } @{$driver->{'printers'}};
  162. }
  163.  
  164. # Routine lookup; just examine the overview
  165. sub get_drivers_for_printer {
  166.     my ($this, $printer) = @_;
  167.  
  168.     my @drivers = ();
  169.  
  170.     my $over = $this->get_overview();
  171.  
  172.     my $p;
  173.     for $p (@{$over}) {
  174.     if ($p->{'id'} eq $printer) {
  175.         return @{$p->{'drivers'}};
  176.     }
  177.     }
  178.  
  179.     return undef;
  180. }
  181.  
  182. # This function sorts the options at first by their group membership and
  183. # then by their names appearing in the list of functional areas. This way
  184. # it will be made easier to build the PPD file with option groups and in
  185. # user interfaces options will appear sorted by their functionality.
  186. sub sortargs {
  187.  
  188.     # All sorting done case-insensitive and characters which are not a
  189.     # letter or number are taken out!!
  190.  
  191.     # List of typical option names to appear at first
  192.     # The terms must fit to the beginning of the line, terms which must fit
  193.     # exactly must have '\$' in the end.
  194.     my @standardopts = (
  195.             # The most important composite option
  196.             "printoutmode",
  197.             # Options which appear in the "General" group in 
  198.             # CUPS and similar media handling options
  199.             "pagesize",
  200.             "papersize",
  201.             "mediasize",
  202.             "inputslot",
  203.             "papersource",
  204.             "mediasource",
  205.             "sheetfeeder",
  206.             "mediafeed",
  207.             "paperfeed",
  208.             "manualfeed",
  209.             "manual",
  210.             "outputtray",
  211.             "outputslot",
  212.             "outtray",
  213.             "faceup",
  214.             "facedown",
  215.             "mediatype",
  216.             "papertype",
  217.             "mediaweight",
  218.             "paperweight",
  219.             "duplex",
  220.             "sides",
  221.             "binding",
  222.             "tumble",
  223.             "notumble",
  224.             "media",
  225.             "paper",
  226.             # Other hardware options
  227.             "inktype",
  228.             "ink",
  229.             # Page choice/ordering options
  230.             "pageset",
  231.             "pagerange",
  232.             "pages",
  233.             "nup",
  234.             "numberup",
  235.             # Printout quality, colour/bw
  236.             "resolution",
  237.             "gsresolution",
  238.             "hwresolution",
  239.             "jclresolution",
  240.             "fastres",
  241.             "jclfastres",
  242.             "quality",
  243.             "printquality",
  244.             "printingquality",
  245.             "printoutquality",
  246.             "bitsperpixel",
  247.             "econo",
  248.             "jclecono",
  249.             "tonersav",
  250.             "photomode",
  251.             "photo",
  252.             "colormode",
  253.             "colourmode",
  254.             "color",
  255.             "colour",
  256.             "grayscale",
  257.             "gray",
  258.             "monochrome",
  259.             "mono",
  260.             "blackonly",
  261.             "colormodel",
  262.             "colourmodel",
  263.             "processcolormodel",
  264.             "processcolourmodel",
  265.             "printcolors",
  266.             "printcolours",
  267.             "outputtype",
  268.             "outputmode",
  269.             "printingmode",
  270.             "printoutmode",
  271.             "printmode",
  272.             "mode",
  273.             "imagetype",
  274.             "imagemode",
  275.             "image",
  276.             "dithering",
  277.             "dither",
  278.             "halftoning",
  279.             "halftone",
  280.             "floydsteinberg",
  281.             "ret\$",
  282.             "cret\$",
  283.             "photoret\$",
  284.             "smooth",
  285.             # Adjustments
  286.             "gammacorrection",
  287.             "gammacorr",
  288.             "gammageneral",
  289.             "mastergamma",
  290.             "stpgamma",
  291.             "gammablack",
  292.             "blackgamma",
  293.             "gammacyan",
  294.             "cyangamma",
  295.             "gammamagenta",
  296.             "magentagamma",
  297.             "gammayellow",
  298.             "yellowgamma",
  299.             "gammared",
  300.             "redgamma",
  301.             "gammagreen",
  302.             "greengamma",
  303.             "gammablue",
  304.             "bluegamma",
  305.             "gamma",
  306.             "density",
  307.             "stpdensity",
  308.             "hpljdensity",
  309.             "tonerdensity",
  310.             "inkdensity",
  311.             "brightness",
  312.             "stpbrightness",
  313.             "saturation",
  314.             "stpsaturation",
  315.             "hue",
  316.             "stphue",
  317.             "tint",
  318.             "stptint",
  319.             "contrast",
  320.             "stpcontrast",
  321.             "black",
  322.             "stpblack",
  323.             "cyan",
  324.             "stpcyan",
  325.             "magenta",
  326.             "stpmagenta",
  327.             "yellow",
  328.             "stpyellow",
  329.             "red",
  330.             "stpred",
  331.             "green",
  332.             "stpgreen",
  333.             "blue",
  334.             "stpblue"
  335.             );
  336.  
  337.     my @standardgroups = (
  338.               "general",
  339.               "media",
  340.               "quality",
  341.               "imag",
  342.               "color",
  343.               "output",
  344.               "finish",
  345.               "stapl",
  346.               "extra",
  347.               "install"
  348.               );
  349.  
  350.     my $compare;
  351.  
  352.     # Argument records
  353.     my $firstarg = $a;
  354.     my $secondarg = $b;
  355.  
  356.     # Bring the two option names into a standard form to compare them
  357.     # in a better way
  358.     my $first = normalizename(lc($firstarg->{'name'}));
  359.     $first =~ s/[\W_]//g;
  360.     my $second = normalizename(lc($secondarg->{'name'}));
  361.     $second =~ s/[\W_]//g;
  362.  
  363.     # group names
  364.     my $firstgr = $firstarg->{'group'};
  365.     my @firstgroup;
  366.     @firstgroup = split("/", $firstgr) if defined($firstgr); 
  367.     my $secondgr = $secondarg->{'group'};
  368.     my @secondgroup;
  369.     @secondgroup = split("/", $secondgr) if defined($secondgr);
  370.  
  371.     my $i = 0;
  372.  
  373.     # Compare groups
  374.     while ($firstgroup[$i] && $secondgroup[$i]) {
  375.  
  376.     # Normalize group names
  377.     my $firstgr = normalizename(lc($firstgroup[$i]));
  378.     $firstgr =~ s/[\W_]//g;
  379.     my $secondgr = normalizename(lc($secondgroup[$i]));
  380.     $secondgr =~ s/[\W_]//g;
  381.         
  382.     # Are the groups in the list of standard group names?
  383.     my $j;
  384.     for ($j = 0; $j <= $#standardgroups; $j++) {
  385.         my $firstinlist = ($firstgr =~ /^$standardgroups[$j]/);
  386.         my $secondinlist = ($secondgr =~ /^$standardgroups[$j]/);
  387.         if (($firstinlist) && (!$secondinlist)) {return -1};
  388.         if (($secondinlist) && (!$firstinlist)) {return 1};
  389.         if (($firstinlist) && ($secondinlist)) {last};
  390.     }
  391.  
  392.     # Compare normalized group names
  393.     $compare = $firstgr cmp $secondgr;
  394.     if ($compare != 0) {return $compare};
  395.  
  396.     # Compare original group names
  397.     $compare = $firstgroup[$i] cmp $secondgroup[$i];
  398.     if ($compare != 0) {return $compare};
  399.     
  400.     $i++;
  401.     }
  402.  
  403.     # The one with a deeper level in the group tree will come later
  404.     if ($firstgroup[$i]) {return 1};
  405.     if ($secondgroup[$i]) {return -1};
  406.  
  407.     # Sort by order parameter if the order parameters are different
  408.     if (defined($firstarg->{'order'}) && defined($secondarg->{'order'}) &&
  409.     $firstarg->{'order'} != $secondarg->{'order'}) {
  410.     return $firstarg->{'order'} cmp $secondarg->{'order'};
  411.     }
  412.  
  413.     # Check whether the argument names are in the @standardopts list
  414.     for ($i = 0; $i <= $#standardopts; $i++) {
  415.     my $firstinlist = ($first =~ /^$standardopts[$i]/);
  416.     my $secondinlist = ($second =~ /^$standardopts[$i]/);
  417.     if (($firstinlist) && (!$secondinlist)) {return -1};
  418.     if (($secondinlist) && (!$firstinlist)) {return 1};
  419.     if (($firstinlist) && ($secondinlist)) {last};
  420.     }
  421.  
  422.     # None of the search terms in the list, compare the standard-formed
  423.     # strings
  424.     $compare = ( $first cmp $second );
  425.     if ($compare != 0) {return $compare};
  426.  
  427.     # No other criteria fullfilled, compare the original input strings
  428.     return $firstarg->{'name'} cmp $secondarg->{'name'};
  429. }
  430.  
  431. sub sortvals {
  432.  
  433.     # All sorting done case-insensitive and characters which are not a letter
  434.     # or number are taken out!!
  435.  
  436.     # List of typical choice names to appear at first
  437.     # The terms must fit to the beginning of the line, terms which must fit
  438.     # exactly must have '\$' in the end.
  439.     my @standardvals = (
  440.             # Default setting
  441.             "default",
  442.             "printerdefault",
  443.             # "Neutral" setting
  444.             "None\$",
  445.             # Paper sizes
  446.             "letter\$",
  447.             #"legal",
  448.             "a4\$",
  449.             # Paper types
  450.             "plain",
  451.             # Printout Modes
  452.             "draft\$",
  453.             "draft\.gray",
  454.             "draft\.mono",
  455.             "draft\.",
  456.             "draft",
  457.             "normal\$",
  458.             "normal\.gray",
  459.             "normal\.mono",
  460.             "normal\.",
  461.             "normal",
  462.             "high\$",
  463.             "high\.gray",
  464.             "high\.mono",
  465.             "high\.",
  466.             "high",
  467.             "veryhigh\$",
  468.             "veryhigh\.gray",
  469.             "veryhigh\.mono",
  470.             "veryhigh\.",
  471.             "veryhigh",
  472.             "photo\$",
  473.             "photo\.gray",
  474.             "photo\.mono",
  475.             "photo\.",
  476.             "photo",
  477.             # Trays
  478.             "upper",
  479.             "top",
  480.             "middle",
  481.             "mid",
  482.             "lower",
  483.             "bottom",
  484.             "highcapacity",
  485.             "multipurpose",
  486.             "tray",
  487.             );
  488.  
  489.     # Do not waste time if the input strings are equal
  490.     if ($a eq $b) {return 0;}
  491.  
  492.     # Are the two strings numbers? Compare them numerically
  493.     if (($a =~ /^[\d\.]+$/) && ($b =~ /^[\d\.]+$/)) {
  494.     my $compare = ( $a <=> $b );
  495.     if ($compare != 0) {return $compare};
  496.     }
  497.  
  498.     # Bring the two option names into a standard form to compare them
  499.     # in a better way
  500.     my $first = lc($a);
  501.     $first =~ s/[\W_]//g;
  502.     my $second = lc($b);
  503.     $second =~ s/[\W_]//g;
  504.  
  505.     # Check whether they are in the @standardvals list
  506.     for (my $i = 0; $i <= $#standardvals; $i++) {
  507.     my $firstinlist = ($first =~ /^$standardvals[$i]/);
  508.     my $secondinlist = ($second =~ /^$standardvals[$i]/);
  509.     if (($firstinlist) && (!$secondinlist)) {return -1};
  510.     if (($secondinlist) && (!$firstinlist)) {return 1};
  511.     if (($firstinlist) && ($secondinlist)) {last};
  512.     }
  513.     
  514.     # None of the search terms in the list, compare the standard-formed 
  515.     # strings
  516.     my $compare = ( normalizename($first) cmp normalizename($second) );
  517.     if ($compare != 0) {return $compare};
  518.  
  519.     # No other criteria fullfilled, compare the original input strings
  520.     return $a cmp $b;
  521. }
  522.  
  523. # Take driver/pid arguments and generate a Perl data structure for the
  524. # Perl filter scripts. Sort the options and enumerated choices so that
  525. # they get presented more nicely on frontends which do not sort by
  526. # themselves
  527.  
  528. sub getdat ($ $ $) {
  529.     my ($this, $drv, $poid) = @_;
  530.  
  531.     my $ppdfile;
  532.  
  533.     # Do we have a link to a custom PPD file for this driver in the
  534.     # printer XML file? Then return the custom PPD
  535.  
  536.     my $p = $this->get_printer($poid);
  537.     if (defined($p->{'drivers'})) {
  538.     for my $d (@{$p->{'drivers'}}) {
  539.         next if ($d->{'id'} ne $drv);
  540.         $ppdfile = $d->{'ppd'} if defined($d->{'ppd'});
  541.         last;
  542.     }
  543.     }
  544.  
  545.     # Do we have a PostScript printer and a link to a manufacturer-
  546.     # supplied PPD file? Then return the manufacturer-supplied PPD
  547.  
  548.     if ($drv =~ /^Postscript$/i) {
  549.     $ppdfile = $p->{'ppdurl'} if defined($p->{'ppdurl'});
  550.     }
  551.  
  552.     # There is a link to a custom PPD, if it is installed on the local
  553.     # machine, use the custom PPD instead of generating one from the
  554.     # Foomatic data
  555.     if ($ppdfile) {
  556.     $ppdfile =~ s,^http://.*/(PPD/.*)$,$1,;
  557.     $ppdfile = $libdir . "/db/source/" . $ppdfile;
  558.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  559.     if (-r $ppdfile) {
  560.         $this->getdatfromppd($ppdfile);
  561.         $this->{'dat'}{'ppdfile'} = $ppdfile;
  562.         return $this->{'dat'};
  563.     }
  564.     }
  565.  
  566.     # Generate Perl data structure from database
  567.     my %dat;            # Our purpose in life...
  568.     my $VAR1;
  569.     eval (`$bindir/foomatic-combo-xml -d '$drv' -p '$poid' -l '$libdir' | $bindir/foomatic-perl-data -C`) ||
  570.     die ("Could not run \"foomatic-combo-xml\"/" .
  571.          "\"foomatic-perl-data\"!");
  572.     %dat = %{$VAR1};
  573.  
  574.     # Funky one-at-a-time cache thing
  575.     $this->{'dat'} = \%dat;
  576.  
  577.     # We do some additional stuff which is very awkward to implement in C
  578.     # now, so we do it here
  579.  
  580.     # Some clean-up
  581.     checklongnames($this->{'dat'});
  582.     sortoptions($this->{'dat'});
  583.     generalentries($this->{'dat'});
  584.  
  585.     return \%dat;
  586. }
  587.  
  588. sub getdatfromppd ($ $) {
  589.  
  590.     my ($this, $ppdfile) = @_;
  591.  
  592.     my $dat = ppdtoperl($ppdfile);
  593.     
  594.     if (!defined($dat)) {
  595.     die ("Unable to open PPD file \'$ppdfile\'\n");
  596.     }
  597.  
  598.     $this->{'dat'} = $dat;
  599.  
  600. }
  601.  
  602. sub ppdfromvartoperl ($);
  603. sub ppdtoperl($);
  604. sub perltoxml($);
  605.  
  606. sub ppdtoperl($) {
  607.  
  608.     # Build a Perl data structure of the printer/driver options
  609.  
  610.     my ($ppdfile) = @_;
  611.  
  612.     # Load the PPD file and send it to the parser
  613.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  614.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or return undef;
  615.     my @ppd = <PPD>;
  616.     close PPD;
  617.     return ppdfromvartoperl(\@ppd);
  618. }
  619.  
  620. sub ppdfromvartoperl ($) {
  621.  
  622.     my ($ppd) = @_;
  623.  
  624.     # Build a data structure for the renderer's command line and the
  625.     # options
  626.  
  627.     my $dat = {};              # data structure for the options
  628.     my $currentargument = "";  # We are currently reading this argument
  629.     my $currentgroup = "";     # We are currently in this group/subgroup
  630.     my @currentgrouptrans;     # Translation/long name for group/subgroup
  631.     my $isfoomatic = 0;        # Do we have a Foomatic PPD?
  632.  
  633.     # If we have an old Foomatic 2.0.x PPD file, read its built-in Perl
  634.     # data structure into @datablob and the default values in %ppddefaults
  635.     # Then delete the $dat structure, replace it by the one "eval"ed from
  636.     # @datablob, and correct the default settings according to the ones of
  637.     # the main PPD structure
  638.     my @datablob;
  639.     
  640.     # Parse the PPD file
  641.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  642.     $_ = $ppd->[$i];
  643.     # Foomatic should also work with PPD files downloaded under
  644.     # Windows.
  645.     $_ = undossify($_);
  646.     # Parse keywords
  647.     if (m!^\*NickName:\s*\"(.*)$!) {
  648.         # "*ShortNickName: <code>"
  649.         my $line = $1;
  650.         # Store the value
  651.         # Code string can have multiple lines, read all of them
  652.         my $cmd = "";
  653.         while ($line !~ m!\"!) {
  654.         if ($line =~ m!&&$!) {
  655.             # line continues in next line
  656.             $cmd .= substr($line, 0, -2);
  657.         } else {
  658.             # line ends here
  659.             $cmd .= "$line\n";
  660.         }
  661.         # Read next line
  662.         $i ++;
  663.         $line = $ppd->[$i];
  664.         chomp $line;
  665.         }
  666.         $line =~ m!^([^\"]*)\"!;
  667.         $cmd .= $1;
  668.         $dat->{'makemodel'} = unhtmlify($cmd);
  669.         $dat->{'makemodel'} =~ s/^([^,]+),.*$/$1/;
  670.         # The following fields are only valid for Foomatic PPDs
  671.         # they will be deleted when it turns out that this PPD
  672.         # is not a Foomatic PPD.
  673.         if ($dat->{'makemodel'} =~ /^(\S+)\s+(\S.*)$/) {
  674.         $dat->{'make'} = $1;
  675.         $dat->{'model'} = $2;
  676.         $dat->{'model'} =~ s/\s+Foomatic.*$//i;
  677.         }
  678.     } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) {
  679.         # "*FoomaticIDs: <printer ID> <driver ID>"
  680.         my $id = $1;
  681.         my $driver = $2;
  682.         # Store the values
  683.         $dat->{'id'} = $id;
  684.         $dat->{'driver'} = $driver;
  685.         $isfoomatic = 1;
  686.     } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  687.         # "*FoomaticRIPPostPipe: <code>"
  688.         my $line = $1;
  689.         # Store the value
  690.         # Code string can have multiple lines, read all of them
  691.         my $cmd = "";
  692.         while ($line !~ m!\"!) {
  693.         if ($line =~ m!&&$!) {
  694.             # line continues in next line
  695.             $cmd .= substr($line, 0, -2);
  696.         } else {
  697.             # line ends here
  698.             $cmd .= "$line\n";
  699.         }
  700.         # Read next line
  701.         $i ++;
  702.         $line = $ppd->[$i];
  703.         chomp $line;
  704.         }
  705.         $line =~ m!^([^\"]*)\"!;
  706.         $cmd .= $1;
  707.         $dat->{'postpipe'} = unhtmlify($cmd);
  708.     } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) {
  709.         # "*FoomaticRIPCommandLine: <code>"
  710.         my $line = $1;
  711.         # Store the value
  712.         # Code string can have multiple lines, read all of them
  713.         my $cmd = "";
  714.         while ($line !~ m!\"!) {
  715.         if ($line =~ m!&&$!) {
  716.             # line continues in next line
  717.             $cmd .= substr($line, 0, -2);
  718.         } else {
  719.             # line ends here
  720.             $cmd .= "$line\n";
  721.         }
  722.         # Read next line
  723.         $i ++;
  724.         $line = $ppd->[$i];
  725.         chomp $line;
  726.         }
  727.         $line =~ m!^([^\"]*)\"!;
  728.         $cmd .= $1;
  729.         $dat->{'cmd'} = unhtmlify($cmd);
  730.     } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) {
  731.         # "*CustomPageSize True: <code>"
  732.         my $setting = "Custom";
  733.         my $translation = "Custom Size";
  734.         my $line = $1;
  735.         # Make sure that the argument is in the data structure
  736.         checkarg ($dat, "PageSize");
  737.         checkarg ($dat, "PageRegion");
  738.         # "PageSize" and "PageRegion" must be both user-visible as they are
  739.         # options required by the PPD spec
  740.         undef $dat->{'args_byname'}{"PageSize"}{'hidden'};
  741.         undef $dat->{'args_byname'}{"PageRegion"}{'hidden'};
  742.         # Make sure that the setting is in the data structure
  743.         checksetting ($dat, "PageSize", $setting);
  744.         checksetting ($dat, "PageRegion", $setting);
  745.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation;
  746.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation;
  747.         # Store the value
  748.         # Code string can have multiple lines, read all of them
  749.         my $code = "";
  750.         while ($line !~ m!\"!) {
  751.         if ($line =~ m!&&$!) {
  752.             # line continues in next line
  753.             $code .= substr($line, 0, -2);
  754.         } else {
  755.             # line ends here
  756.             $code .= "$line\n";
  757.         }
  758.         # Read next line
  759.         $i ++;
  760.         $line = $ppd->[$i];
  761.         chomp $line;
  762.         }
  763.         $line =~ m!^([^\"]*)\"!;
  764.         $code .= $1;
  765.         if ($code !~ m!^%% FoomaticRIPOptionSetting!m) {
  766.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code;
  767.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code;
  768.         }
  769.     } elsif (m!^\*Open(Sub|)Group:\s*([^/]+)(/(.*)|)$!) {
  770.         # "*Open[Sub]Group: <group>[/<translation>]
  771.         my $group = $2;
  772.         chomp($group) if $group;
  773.         my $grouptrans = $4;
  774.         chomp($grouptrans) if $grouptrans;
  775.         if (!$grouptrans) {
  776.         $grouptrans = longname($group);
  777.         }
  778.         if ($currentgroup) {
  779.         $currentgroup .= "/";
  780.         }
  781.         $currentgroup .= $group;
  782.         push(@currentgrouptrans, $grouptrans);
  783.     } elsif (m!^\*Close(Sub|)Group:\s*([^/]+)$!) {
  784.         # "*Close[Sub]Group: <group>"
  785.         my $group = $2;
  786.         chomp($group) if $group;
  787.         $currentgroup =~ s!$group$!!;
  788.         $currentgroup =~ s!/$!!;
  789.         pop(@currentgrouptrans);
  790.     } elsif (m!^\*Close(Sub|)Group\s*$!) {
  791.         # "*Close[Sub]Group"
  792.         # NOTE: This expression is not Adobe-conforming
  793.         $currentgroup =~ s![^/]+$!!;
  794.         $currentgroup =~ s!/$!!;
  795.         pop(@currentgrouptrans);
  796.     } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) {
  797.         # "*[JCL]OpenUI *<option>[/<translation>]: <type>"
  798.         my $argnametrans = $2;
  799.         my $argtype = $3;
  800.         my $argname;
  801.         my $translation = "";
  802.         if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  803.         $argname = $1;
  804.         $translation = $2;
  805.         } else {
  806.         $argname = $argnametrans;
  807.         }
  808.         # Make sure that the argument is in the data structure
  809.         checkarg ($dat, $argname);
  810.         # This option has a non-Foomatic keyword, so this is not
  811.         # a hidden option
  812.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  813.         # Store the values
  814.         $dat->{'args_byname'}{$argname}{'comment'} = $translation;
  815.         $dat->{'args_byname'}{$argname}{'group'} = $currentgroup;
  816.         @{$dat->{'args_byname'}{$argname}{'grouptrans'}} =
  817.         @currentgrouptrans;
  818.         # Set the argument type only if not defined yet, a
  819.         # definition in "*FoomaticRIPOption" has priority
  820.         if (!defined($dat->{'args_byname'}{$argname}{'type'})) {
  821.         if ($argtype eq "PickOne") {
  822.             $dat->{'args_byname'}{$argname}{'type'} = 'enum';
  823.         } elsif ($argtype eq "PickMany") {
  824.             $dat->{'args_byname'}{$argname}{'type'} = 'pickmany';
  825.         } elsif ($argtype eq "Boolean") {
  826.             $dat->{'args_byname'}{$argname}{'type'} = 'bool';
  827.         }
  828.         }
  829.         # Mark in which argument we are currently, so that we can find
  830.         # the entries for the choices
  831.         $currentargument = $argname;
  832.     } elsif (m!^\*(JCL|)CloseUI:\s+\*([^:/\s]+)\s*$!) {
  833.         next if !$currentargument;
  834.         # "*[JCL]CloseUI *<option>"
  835.         my $argname = $2;
  836.         # Unmark the current argument to do not mis-interpret any 
  837.         # keywords as choices
  838.         $currentargument = "";
  839.     } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s*$!) ||
  840.          (m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s+(\S+)\s*$!)){
  841.         # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]"
  842.         # <order> only used for 1-choice enum options
  843.         my $argname = $1;
  844.         my $argtype = $2;
  845.         my $argstyle = $3;
  846.         my $spot = $4;
  847.         my $order = $5;
  848.         # Make sure that the argument is in the data structure
  849.         checkarg ($dat, $argname);
  850.         # Store the values
  851.         $dat->{'args_byname'}{$argname}{'type'} = $argtype;
  852.         if ($argstyle eq "PS") {
  853.         $dat->{'args_byname'}{$argname}{'style'} = 'G';
  854.         } elsif ($argstyle eq "CmdLine") {
  855.         $dat->{'args_byname'}{$argname}{'style'} = 'C';
  856.         } elsif ($argstyle eq "JCL") {
  857.         $dat->{'args_byname'}{$argname}{'style'} = 'J';
  858.         $dat->{'jcl'} = 1;
  859.         $dat->{'pjl'} = 1;
  860.         } elsif ($argstyle eq "Composite") {
  861.         $dat->{'args_byname'}{$argname}{'style'} = 'X';
  862.         }
  863.         $dat->{'args_byname'}{$argname}{'spot'} = $spot;
  864.         # $order only defined here for 1-choice enum options
  865.         if ($order) {
  866.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  867.         }
  868.     } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) {
  869.         # "*FoomaticRIPOptionPrototype <option>: <code>"
  870.         # Used for numerical and string options only
  871.         my $argname = $1;
  872.         my $line = $2;
  873.         # Make sure that the argument is in the data structure
  874.         checkarg ($dat, $argname);
  875.         # Store the value
  876.         # Code string can have multiple lines, read all of them
  877.         my $proto = "";
  878.         while ($line !~ m!\"!) {
  879.         if ($line =~ m!&&$!) {
  880.             # line continues in next line
  881.             $proto .= substr($line, 0, -2);
  882.         } else {
  883.             # line ends here
  884.             $proto .= "$line\n";
  885.         }
  886.         # Read next line
  887.         $i ++;
  888.         $line = $ppd->[$i];
  889.         chomp $line;
  890.         }
  891.         $line =~ m!^([^\"]*)\"!;
  892.         $proto .= $1;
  893.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto);
  894.     } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*(\S+)\s+(\S+)\s*$!) {
  895.         # "*FoomaticRIPOptionRange <option>: <min> <max>"
  896.         # Used for numerical options only
  897.         my $argname = $1;
  898.         my $min = $2;
  899.         my $max = $3;
  900.         # Make sure that the argument is in the data structure
  901.         checkarg ($dat, $argname);
  902.         # Store the values
  903.         $dat->{'args_byname'}{$argname}{'min'} = $min;
  904.         $dat->{'args_byname'}{$argname}{'max'} = $max;
  905.     } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*(\S+)\s*$!) {
  906.         # "*FoomaticRIPOptionMaxLength <option>: <length>"
  907.         # Used for string options only
  908.         my $argname = $1;
  909.         my $maxlength = $2;
  910.         # Make sure that the argument is in the data structure
  911.         checkarg ($dat, $argname);
  912.         # Store the value
  913.         $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength;
  914.     } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) {
  915.         # "*FoomaticRIPOptionAllowedChars <option>: <code>"
  916.         # Used for string options only
  917.         my $argname = $1;
  918.         my $line = $2;
  919.         # Store the value
  920.         # Code string can have multiple lines, read all of them
  921.         my $code = "";
  922.         while ($line !~ m!\"!) {
  923.         if ($line =~ m!&&$!) {
  924.             # line continues in next line
  925.             $code .= substr($line, 0, -2);
  926.         } else {
  927.             # line ends here
  928.             $code .= "$line\n";
  929.         }
  930.         # Read next line
  931.         $i ++;
  932.         $line = $ppd->[$i];
  933.         chomp $line;
  934.         }
  935.         $line =~ m!^([^\"]*)\"!;
  936.         $code .= $1;
  937.         # Make sure that the argument is in the data structure
  938.         checkarg ($dat, $argname);
  939.         # Store the value
  940.         $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code);
  941.     } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) {
  942.         # "*FoomaticRIPOptionAllowedRegExp <option>: <code>"
  943.         # Used for string options only
  944.         my $argname = $1;
  945.         my $line = $2;
  946.         # Store the value
  947.         # Code string can have multiple lines, read all of them
  948.         my $code = "";
  949.         while ($line !~ m!\"!) {
  950.         if ($line =~ m!&&$!) {
  951.             # line continues in next line
  952.             $code .= substr($line, 0, -2);
  953.         } else {
  954.             # line ends here
  955.             $code .= "$line\n";
  956.         }
  957.         # Read next line
  958.         $i ++;
  959.         $line = $ppd->[$i];
  960.         chomp $line;
  961.         }
  962.         $line =~ m!^([^\"]*)\"!;
  963.         $code .= $1;
  964.         # Make sure that the argument is in the data structure
  965.         checkarg ($dat, $argname);
  966.         # Store the value
  967.         $dat->{'args_byname'}{$argname}{'allowedregexp'} =
  968.         unhtmlify($code);
  969.     } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) {
  970.         next if !$currentargument;
  971.         # "*OrderDependency: <order> <section> *<option>"
  972.         my $order = $1;
  973.         my $section = $2;
  974.         my $argname = $3;
  975.         # Make sure that the argument is in the data structure
  976.         checkarg ($dat, $argname);
  977.         # This option has a non-Foomatic keyword, so this is not
  978.         # a hidden option
  979.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  980.         # Store the values
  981.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  982.         $dat->{'args_byname'}{$argname}{'section'} = $section;
  983.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  984.         next if !$currentargument;
  985.         # "*Default<option>: <value>"
  986.         my $argname = $1;
  987.         my $default = $2;
  988.         # Make sure that the argument is in the data structure
  989.         checkarg ($dat, $argname);
  990.         # This option has a non-Foomatic keyword, so this is not
  991.         # a hidden option
  992.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  993.         # Store the value
  994.         $dat->{'args_byname'}{$argname}{'default'} = $default;
  995.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  996.         # "*FoomaticRIPDefault<option>: <value>"
  997.         # Used for numerical options only
  998.         my $argname = $1;
  999.         my $default = $2;
  1000.         # Make sure that the argument is in the data structure
  1001.         checkarg ($dat, $argname);
  1002.         # Store the value
  1003.         $dat->{'args_byname'}{$argname}{'fdefault'} = $default;
  1004.     } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) {
  1005.         next if !$currentargument;
  1006.         # "*<option> <choice>[/<translation>]: <code>"
  1007.         my $settingtrans = $1;
  1008.         my $line = $2;
  1009.         my $translation = "";
  1010.         my $setting = "";
  1011.         if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1012.         $setting = $1;
  1013.         $translation = $2;
  1014.         } else {
  1015.         $setting = $settingtrans;
  1016.         }
  1017.         # Make sure that the argument is in the data structure
  1018.         checkarg ($dat, $currentargument);
  1019.         # This option has a non-Foomatic keyword, so this is not
  1020.         # a hidden option
  1021.         undef $dat->{'args_byname'}{$currentargument}{'hidden'};
  1022.         # Make sure that the setting is in the data structure (enum
  1023.         # options)
  1024.         my $bool =
  1025.         ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool');
  1026.         if ($bool) {
  1027.         if (lc($setting) eq "true") {
  1028.             if (!$dat->{'args_byname'}{$currentargument}{'comment'}) {
  1029.             $dat->{'args_byname'}{$currentargument}{'comment'} =
  1030.                 $translation;
  1031.             }
  1032.             $dat->{'args_byname'}{$currentargument}{'comment_true'} =
  1033.             $translation;
  1034.         } else {
  1035.             $dat->{'args_byname'}{$currentargument}{'comment_false'} =
  1036.             $translation;
  1037.         }
  1038.         } else {
  1039.         checksetting ($dat, $currentargument, $setting);
  1040.         $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation;
  1041.         # Make sure that this argument has a default setting, even
  1042.         # if none is defined in this PPD file
  1043.         if (!defined($dat->{'args_byname'}{$currentargument}{'default'}) ||
  1044.             ($dat->{'args_byname'}{$currentargument}{'default'} eq "")) {
  1045.             $dat->{'args_byname'}{$currentargument}{'default'} = $setting;
  1046.         }
  1047.         }
  1048.         # Store the value
  1049.         # Code string can have multiple lines, read all of them
  1050.         my $code = "";
  1051.         while ($line !~ m!\"!) {
  1052.         if ($line =~ m!&&$!) {
  1053.             # line continues in next line
  1054.             $code .= substr($line, 0, -2);
  1055.         } else {
  1056.             # line ends here
  1057.             $code .= "$line\n";
  1058.         }
  1059.         # Read next line
  1060.         $i ++;
  1061.         $line = $ppd->[$i];
  1062.         chomp $line;
  1063.         }
  1064.         $line =~ m!^([^\"]*)\"!;
  1065.         $code .= $1;
  1066.         if ($code !~ m!^%% FoomaticRIPOptionSetting!) {
  1067.         if ($bool) {
  1068.             if (lc($setting) eq "true") {
  1069.             $dat->{'args_byname'}{$currentargument}{'proto'} =
  1070.                 $code;
  1071.             } else {
  1072.             $dat->{'args_byname'}{$currentargument}{'protof'} =
  1073.                 $code;
  1074.             }
  1075.         } else {
  1076.             $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code;
  1077.         }
  1078.         }
  1079.     } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) ||
  1080.          (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) {
  1081.         # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>"
  1082.         # For boolean options <choice> is not given
  1083.         my $argname = $1;
  1084.         my $setting = $2;
  1085.         my $line = $3;
  1086.         my $bool = 0;
  1087.         if (!$line) {
  1088.         $line = $setting;
  1089.         $bool = 1;
  1090.         }
  1091.         # Make sure that the argument is in the data structure
  1092.         checkarg ($dat, $argname);
  1093.         # Make sure that the setting is in the data structure (enum
  1094.         # options)
  1095.         if (!$bool) {
  1096.         checksetting ($dat, $argname, $setting);
  1097.         # Make sure that this argument has a default setting, even
  1098.         # if none is defined in this PPD file
  1099.         if (!$dat->{'args_byname'}{$argname}{'default'}) {
  1100.             $dat->{'args_byname'}{$argname}{'default'} = $setting;
  1101.         }
  1102.         }
  1103.         # Store the value
  1104.         # Code string can have multiple lines, read all of them
  1105.         my $code = "";
  1106.         while ($line !~ m!\"!) {
  1107.         if ($line =~ m!&&$!) {
  1108.             # line continues in next line
  1109.             $code .= substr($line, 0, -2);
  1110.         } else {
  1111.             # line ends here
  1112.             $code .= "$line\n";
  1113.         }
  1114.         # Read next line
  1115.         $i ++;
  1116.         $line = $ppd->[$i];
  1117.         chomp $line;
  1118.         }
  1119.         $line =~ m!^([^\"]*)\"!;
  1120.         $code .= $1;
  1121.         if ($bool) {
  1122.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code);
  1123.         } else {
  1124.         $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code);
  1125.         }
  1126.     } elsif (m!^\*JCL(Begin|ToPSInterpreter|End):\s*\"(.*)$!) {
  1127.         # "*JCL(Begin|ToPSInterpreter|End): <code>"
  1128.         # The printer supports PJL/JCL when there is such a line 
  1129.         $dat->{'jcl'} = 1;
  1130.         $dat->{'pjl'} = 1;
  1131.         my $item = $1;
  1132.         my $line = $2;
  1133.         # Store the value
  1134.         # Code string can have multiple lines, read all of them
  1135.         my $code = "";
  1136.         while ($line !~ m!\"!) {
  1137.         if ($line =~ m!&&$!) {
  1138.             # line continues in next line
  1139.             $code .= substr($line, 0, -2);
  1140.         } else {
  1141.             # line ends here
  1142.             $code .= "$line\n";
  1143.         }
  1144.         # Read next line
  1145.         $i ++;
  1146.         $line = $ppd->[$i];
  1147.         chomp $line;
  1148.         }
  1149.         $line =~ m!^([^\"]*)\"!;
  1150.         $code .= $1;
  1151.         if ($item eq 'Begin') {
  1152.         $dat->{'jclbegin'} = unhexify($code);
  1153.         } elsif ($item eq 'ToPSInterpreter') {
  1154.         $dat->{'jcltointerpreter'} = unhexify($code);
  1155.         } elsif ($item eq 'End') {
  1156.         $dat->{'jclend'} = unhexify($code);
  1157.         }
  1158.     } elsif (m!^\*\% COMDATA \#(.*)$!) {
  1159.         # If we have an old Foomatic 2.0.x PPD file, collect its Perl 
  1160.         # data
  1161.         push (@datablob, $1);
  1162.     }
  1163.     }
  1164.  
  1165.     # If we have an old Foomatic 2.0.x PPD file use its Perl data structure
  1166.     if ($#datablob >= 0) {
  1167.     my $VAR1;
  1168.     if (eval join('',@datablob)) {
  1169.         # Overtake default settings from the main structure of the
  1170.         # PPD file
  1171.         for my $arg (@{$dat->{'args'}}) {
  1172.         if ($arg->{'default'}) {
  1173.             $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} = 
  1174.             $arg->{'default'};
  1175.         }
  1176.         }
  1177.         undef $dat;
  1178.         $dat = $VAR1;
  1179.         $dat->{'jcl'} = $dat->{'pjl'};
  1180.         $isfoomatic = 1;
  1181.     } else {
  1182.         # Perl structure broken
  1183.         warn "\nUnable to evaluate datablob, print jobs may come " .
  1184.         "out incorrectly or not at all.\n\n";
  1185.     }
  1186.     }
  1187.  
  1188.     # Set the defaults for the numerical options, taking into account
  1189.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  1190.     numericaldefaults($dat);
  1191.  
  1192.     # Some clean-up
  1193.     checklongnames($dat);
  1194.     generalentries($dat);
  1195.  
  1196.     # Remove make and model fields and sort the options if we don't have 
  1197.     # a Foomatic PPD file
  1198.     if (!$isfoomatic) {
  1199.     $dat->{'make'} = undef;
  1200.     $dat->{'model'} = undef;
  1201.     #sortoptions($dat, 1);
  1202.     }
  1203.  
  1204.     return $dat;
  1205. }
  1206.  
  1207. sub perltoxml ($) {
  1208.     my ($this, $dat) = @_;
  1209.  
  1210.     my $xml =
  1211.     "<foomatic><printer id=\"printer/" . $dat->{'id'} . "\">\n" .
  1212.     " <make>" . $dat->{'make'} . "</make>\n" .
  1213.     " <model>" . $dat->{'model'} . "</model>\n" .
  1214.     " <comments><en /></comments>\n" .
  1215.     "</printer>\n\n\n";
  1216.  
  1217.     $xml .=
  1218.     "<driver id=\"driver/" . $dat->{'driver'} . "\">\n" .
  1219.     " <name>" . $dat->{'driver'} . "</name>\n" .
  1220.     " <execution>\n" .
  1221.     "  <filter />\n" .
  1222.     "  <prototype>" . $dat->{'cmd'} . "</prototype>\n" .
  1223.     " </execution>\n" .
  1224.     "</driver>\n\n";
  1225.  
  1226.     $xml .= "<options>\n";
  1227.  
  1228.     foreach (@{$dat->{'args'}}) {
  1229.     my $type = $_->{'type'};
  1230.     my $optname = $_->{'name'};
  1231.     $xml .= " <option type=\"$type\" " .
  1232.         "id=\"opt/" . $dat->{'driver'} . "-" . $optname . "\">\n";
  1233.     $xml .=
  1234.         "  <arg_longname>\n" .
  1235.         "   <en>" . $_->{'comment'} . "</en>\n" .
  1236.         "  </arg_longname>\n" .
  1237.         "  <arg_shortname>\n" .
  1238.         "   <en>" . $_->{'name'} . "</en>\n" .
  1239.         "  </arg_shortname>\n" .
  1240.         "  <arg_execution>\n";
  1241.     $xml .= "   <arg_group>" . $_->{'group'} . "</arg_group>\n"
  1242.         if $_->{'group'};
  1243.     $xml .= "   <arg_order>" . $_->{'order'} . "</arg_order>\n"
  1244.         if $_->{'order'};
  1245.     $xml .= "   <arg_spot>" . $_->{'spot'} . "</arg_spot>\n"
  1246.         if $_->{'spot'};
  1247.     $xml .= "   <arg_proto>" . $_->{'proto'} . "</arg_proto>\n"
  1248.         if $_->{'proto'};
  1249.     $xml .= "  </arg_execution>\n";
  1250.  
  1251.     if ($type eq 'enum') {
  1252.         $xml .= "  <enum_vals>\n";
  1253.         my $vals_byname = $_->{'vals_byname'};
  1254.         foreach (keys(%{$vals_byname})) {
  1255.         my $val = $vals_byname->{$_};
  1256.         $xml .=
  1257.             "   <enum_val id=\"ev/" . $dat->{'driver'} . "-" .
  1258.             $optname . "-" . $_ . "\">\n";
  1259.         $xml .=
  1260.             "    <ev_longname>\n" .
  1261.             "     <en>" . $val->{'comment'} . "</en>\n" .
  1262.             "    </ev_longname>\n" .
  1263.             "    <ev_shortname>\n" .
  1264.             "     <en>$_</en>\n" .
  1265.             "    </ev_shortname>\n";
  1266.  
  1267.         $xml .=
  1268.             "    <ev_driverval>" .
  1269.             $val->{'driverval'} .
  1270.             "</ev_driverval>\n" if $val->{'driverval'};
  1271.  
  1272.         $xml .= "   </enum_val>\n";
  1273.         }
  1274.     }
  1275.  
  1276.     $xml .= " </option>\n";
  1277.     }
  1278.  
  1279.     $xml .= "</options>\n";
  1280.  
  1281.     return $xml;
  1282. }
  1283.  
  1284. sub ppdgetdefaults {
  1285.  
  1286.     # Read a PPD and get only the defaults and the postpipe.
  1287.     my ($this, $ppdfile) = @_;
  1288.  
  1289.     # Open the PPD file
  1290.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  1291.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or 
  1292.            die ("Unable to open PPD file \'$ppdfile\'\n");
  1293.  
  1294.     # We don't read the "COMDATA" lines of old Foomatic 2.0.x PPD files
  1295.     # here, because the defaults in the main PPD structure have priority.
  1296.     while(<PPD>) {
  1297.     # Foomatic should also work with PPD file downloaded under
  1298.     # Windows.
  1299.     $_ = undossify($_);
  1300.     # Parse keywords
  1301.     if (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1302.         # "*FoomaticRIPPostPipe: <code>"
  1303.         my $line = $1;
  1304.         # Store the value
  1305.         # Code string can have multiple lines, read all of them
  1306.         my $cmd = "";
  1307.         while ($line !~ m!\"!) {
  1308.         if ($line =~ m!&&$!) {
  1309.             # line continues in next line
  1310.             $cmd .= substr($line, 0, -2);
  1311.         } else {
  1312.             # line ends here
  1313.             $cmd .= "$line\n";
  1314.         }
  1315.         # Read next line
  1316.         $line = <PPD>;
  1317.         chomp $line;
  1318.         }
  1319.         $line =~ m!^([^\"]*)\"!;
  1320.         $cmd .= $1;
  1321.         $this->{'dat'}{'postpipe'} = unhtmlify($cmd);
  1322.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1323.         # "*Default<option>: <value>"
  1324.         my $argname = $1;
  1325.         my $default = $2;
  1326.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  1327.         # Store the value
  1328.         $this->{'dat'}{'args_byname'}{$argname}{'default'} =
  1329.             $default;
  1330.         }
  1331.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1332.         # "*FoomaticRIPDefault<option>: <value>"
  1333.         # Used for numerical options only
  1334.         my $argname = $1;
  1335.         my $default = $2;
  1336.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  1337.         # Store the value
  1338.         $this->{'dat'}{'args_byname'}{$argname}{'fdefault'} =
  1339.             $default;
  1340.         }
  1341.     }
  1342.     }
  1343.  
  1344.     close PPD;
  1345.  
  1346.     # Set the defaults for the numerical options, taking into account
  1347.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  1348.     #  similar to other places in the code
  1349.     numericaldefaults($this->{'dat'}); 
  1350.  
  1351. }
  1352.  
  1353. sub ppdvarsetdefaults {
  1354.  
  1355.     my ($this, @ppdlinesin) = @_;
  1356.  
  1357.     my @ppdlines;
  1358.     my $ppd;
  1359.  
  1360.     for (my $i = 0; $i < @ppdlinesin; $i ++) {
  1361.     my $line = $ppdlinesin[$i];
  1362.     # Remove a postpipe definition if one is there
  1363.     if ($line =~ m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1364.         # "*FoomaticRIPPostPipe: <code>"
  1365.         # Code string can have multiple lines, read all of them
  1366.         $line = $1;
  1367.         while ($line !~ m!\"!) {
  1368.         # Read next line
  1369.         $i++;
  1370.         $line = $ppdlinesin[$i];
  1371.         }
  1372.         # We also have to remove the "*End" line
  1373.         $i++;
  1374.         $line = $ppdlinesin[$i];
  1375.         if ($line !~ /^\*End/) {
  1376.         push(@ppdlines, $line);
  1377.         }
  1378.     } else {
  1379.         push(@ppdlines, $line);
  1380.     }
  1381.     }
  1382.     $ppd = join('', @ppdlines);
  1383.     # No option info read yet? Do not try to set deafaults
  1384.     return $ppd if !$this->{'dat'}{'args'};
  1385.  
  1386.     # If the settings for "PageSize" and "PageRegion" are different,
  1387.     # set the one for "PageRegion" to the one for "PageSize".
  1388.     if ($this->{'dat'}{'args_byname'}{'PageSize'}{'default'} ne
  1389.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'}) {
  1390.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'} =
  1391.         $this->{'dat'}{'args_byname'}{'PageSize'}{'default'}
  1392.     }
  1393.  
  1394.     # Numerical options: Set the "classical" default values
  1395.     # ("*Default<option>: <value>") to the value enumerated in the
  1396.     # list which is closest to the current default value.
  1397.     setnumericaldefaults($this->{'dat'}); 
  1398.  
  1399.     # Set the defaults in the PPD file to the current default
  1400.     # settings in the data structure
  1401.     for my $arg (@{$this->{'dat'}{'args'}}) {
  1402.     if (defined($arg->{'default'})) {
  1403.         my $name = $arg->{'name'};
  1404.         my $def = $arg->{'default'};
  1405.         if ($arg->{'type'} eq 'bool') {
  1406.         if ((lc($def) eq '1')   || (lc($def) eq 'on') || 
  1407.             (lc($def) eq 'yes') || (lc($def) eq 'true')) {
  1408.             $def='True';
  1409.         } elsif ((lc($def) eq '0')  || (lc($def) eq 'off') || 
  1410.              (lc($def) eq 'no') || (lc($def) eq 'false')) {
  1411.             $def='False';
  1412.         }
  1413.         $def = (checkoptionvalue($this->{'dat'}, $name, $def, 1) ?
  1414.             'True' : 'False');
  1415.         } elsif ($arg->{'type'} =~ /^(int|float)$/) {
  1416.         if (defined($arg->{'cdefault'})) {
  1417.             $def = $arg->{'cdefault'};
  1418.             undef $arg->{'cdefault'};
  1419.         }
  1420.         my $fdef = $arg->{'default'};
  1421.         $fdef = checkoptionvalue($this->{'dat'}, $name, $fdef, 1);
  1422.         $ppd =~ s!^(\*FoomaticRIPDefault$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$fdef$3!m;
  1423.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  1424.         } elsif ($arg->{'type'} =~ /^(string|password)$/) {
  1425.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  1426.         # An empty string cannot be an option name in a PPD file,
  1427.         # use "None" in this case, also substitute non-word characters
  1428.         # in the string to get a legal option name
  1429.         my $defcom = $def;
  1430.         my $defstr = $def;
  1431.         if ($def !~ /\S/) {
  1432.             $def = 'None';
  1433.             $defcom = '(None)';
  1434.             $defstr = '';
  1435.         } elsif ($def eq 'None') {
  1436.             $defcom = '(None)';
  1437.             $defstr = '';
  1438.         } else {
  1439.             $def =~ s/\W+/_/g;
  1440.             $def =~ s/^_+|_+$//g;
  1441.             $def = '_' if ($def eq '');
  1442.             $defcom =~ s/:/ /g;
  1443.             $defcom =~ s/^ +| +$//g;
  1444.         }
  1445.         # The default string is not available as an enumerated choice
  1446.         # ...
  1447.         if (($ppd !~ m!^\s*\*$arg->{name}\s+${def}[/:]!m) &&
  1448.             ($ppd !~ m!^\s*\*FoomaticRIPOptionSetting\s+$arg->{name}=${def}:!m)) {
  1449.             # ... build an appropriate PPD entry ...
  1450.             my $sprintfproto = $arg->{'proto'};
  1451.             $sprintfproto =~ s/\%(?!s)/\%\%/g;
  1452.             my $driverval = sprintf($sprintfproto, $defstr);
  1453.             my ($choicedef, $fchoicedef);
  1454.             if ($arg->{'style'} eq 'G') { # PostScript option
  1455.             $choicedef = sprintf("*%s %s/%s: \"%s\"", 
  1456.                          $name, $def, $defcom, $driverval);
  1457.             } else {
  1458.             my $header = sprintf
  1459.                 ("*FoomaticRIPOptionSetting %s=%s", $name, $def);
  1460.             $fchoicedef = ripdirective($header, $driverval); 
  1461.             if ($#{$arg->{'vals'}} >= 0) { # Visible non-PS option
  1462.                 $choicedef =
  1463.                 sprintf("*%s %s/%s: " .
  1464.                     "\"%%%% FoomaticRIPOptionSetting " .
  1465.                     "%s=%s\"", 
  1466.                     $name, $def, $defcom, $name, $def);
  1467.             }
  1468.             }
  1469.             if ($choicedef =~ /\n/s) {
  1470.             $choicedef .= "\n*End";
  1471.             }
  1472.             if ($fchoicedef =~ /\n/s) {
  1473.             $fchoicedef .= "\n*End";
  1474.             }
  1475.             if ($#{$arg->{'vals'}} == 0) {
  1476.             # ... and if there is only one choice, replace the one 
  1477.             # choice
  1478.             $ppd =~ s!^\*$name\s+.*?\".*?\"(\r?\n?\*End)?$!$choicedef!sm;
  1479.             $ppd =~ s!^\*FoomaticRIPOptionSetting\s+$name=.*?\".*?\"(\r?\n?\*End)?$!$fchoicedef!sm;
  1480.             } else {
  1481.             # ... and if there is no choice or more than one
  1482.             # choice, add a new choice for the default
  1483.             my $entrystr = 
  1484.                 ($choicedef ? "\n$choicedef" : "") .
  1485.                 ($fchoicedef ? "\n$fchoicedef" : "");
  1486.             for my $l ("Default$name:.*",
  1487.                    "OrderDependency.*$name",
  1488.                    "FoomaticRIPOptionMaxLength\\s+$name:.*",
  1489.                    "FoomaticRIPOptionPrototype\\s+$name:.*",
  1490.                    "FoomaticRIPOption\\s+$name:.*") {
  1491.                 $ppd =~ s!^(\*$l)$!$1$entrystr!m and last;
  1492.             }
  1493.             }
  1494.         }
  1495.         } else {
  1496.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 0);
  1497.         }
  1498.         $ppd =~ s!^(\*Default$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$def$3!m
  1499.         if defined($def);
  1500.     }
  1501.     }
  1502.  
  1503.     # Update the postpipe
  1504.     if ($this->{'dat'}{'postpipe'}) {
  1505.     my $header = "*FoomaticRIPPostPipe";
  1506.     my $code = $this->{'dat'}{'postpipe'};
  1507.     my $postpipestr = ripdirective($header, $code) . "\n";
  1508.     if ($postpipestr =~ /\n.*\n/s) {
  1509.         $postpipestr .= "*End\n";
  1510.     }
  1511.     #$ppd =~ s/(\*PPD[^a-zA-Z0-9].*\n)/$1$postpipestr/s;
  1512.     $ppd =~ s/((\r\n|\n\r|\r|\n))/$1$postpipestr/s;
  1513.     }
  1514.     
  1515.     return $ppd;
  1516. }
  1517.  
  1518. sub ppdsetdefaults {
  1519.  
  1520.     my ($this, $ppdfile) = @_;
  1521.     
  1522.     # Load the complete PPD file into memory
  1523.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  1524.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or
  1525.            die ("Unable to open PPD file \'$ppdfile\'\n");
  1526.     my @ppdlines = <PPD>;
  1527.     close PPD;
  1528.  
  1529.     # Set the defaults
  1530.     my $ppd = $this->ppdvarsetdefaults(@ppdlines);
  1531.     
  1532.     # Write back the modified PPD file
  1533.     open PPD, ($ppdfile !~ /\.gz$/i ? "> $ppdfile" : 
  1534.            "| $sysdeps->{'gzip'} > \'$ppdfile\'") or
  1535.     die ("Unable to open PPD file \'$ppdfile\' for writing\n");
  1536.     print PPD $ppd;
  1537.     close PPD;
  1538.     
  1539. }
  1540.  
  1541. # Some helper functions for reading the PPD file
  1542.  
  1543. sub unhtmlify {
  1544.     # Replace HTML/XML entities by the original characters
  1545.     my $str = $_[0];
  1546.     $str =~ s/\'/\'/g;
  1547.     $str =~ s/\"/\"/g;
  1548.     $str =~ s/\>/\>/g;
  1549.     $str =~ s/\</\</g;
  1550.     $str =~ s/\&/\&/g;
  1551.     return $str;
  1552. }
  1553.  
  1554. sub unhexify {
  1555.     # Replace hex notation for unprintable characters in PPD files
  1556.     # by the actual characters ex: "<0A>" --> chr(hex("0A"))
  1557.     my ($input) = @_;
  1558.     my $output = "";
  1559.     my $hexmode = 0;
  1560.     my $firstdigit = "";
  1561.     for (my $i = 0; $i < length($input); $i ++) {
  1562.     my $c = substr($input, $i, 1);
  1563.     if ($hexmode) {
  1564.         if ($c eq ">") {
  1565.         # End of hex string
  1566.         $hexmode = 0;
  1567.         } elsif ($c =~ /^[0-9a-fA-F]$/) {
  1568.         # Hexadecimal digit, two of them give a character
  1569.         if ($firstdigit ne "") {
  1570.             $output .= chr(hex("$firstdigit$c"));
  1571.             $firstdigit = "";
  1572.         } else {
  1573.             $firstdigit = $c;
  1574.         }
  1575.         }
  1576.     } else {
  1577.         if ($c eq "<") {
  1578.         # Beginning of hex string
  1579.         $hexmode = 1;
  1580.         } else {
  1581.         # Normal character
  1582.         $output .= $c;
  1583.         }
  1584.     }
  1585.     }
  1586.     return $output;
  1587. }
  1588.  
  1589. sub undossify {
  1590.     # Remove "dossy" line ends ("\r\n") from a string
  1591.     my ($str) = @_;
  1592.     $str = "" if( !defined($str) );
  1593.     $str =~ s/\r\n/\n/gs;
  1594.     $str =~ s/\r$//s;
  1595.     return $str;
  1596. }
  1597.  
  1598. sub checkarg {
  1599.     # Check if there is already an argument record $argname in $dat, if not,
  1600.     # create one
  1601.     my ($dat, $argname) = @_;
  1602.     return if defined($dat->{'args_byname'}{$argname});
  1603.     # argument record
  1604.     my $rec;
  1605.     $rec->{'name'} = $argname;
  1606.     # Insert record in 'args' array for browsing all arguments
  1607.     push(@{$dat->{'args'}}, $rec);
  1608.     # 'args_byname' hash for looking up arguments by name
  1609.     $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}];
  1610.     # Default execution style is 'G' (PostScript) since all arguments for
  1611.     # which we don't find "*Foomatic..." keywords are usual PostScript
  1612.     # options
  1613.     $dat->{'args_byname'}{$argname}{'style'} = 'G';
  1614.     # Default prototype for code to insert, used by enum options
  1615.     $dat->{'args_byname'}{$argname}{'proto'} = '%s';
  1616.     # Mark option as hidden by default, as options consisting of only Foomatic
  1617.     # keywords are hidden. As soon as the PPD parser finds a non-Foomatic
  1618.     # keyword, it removes this mark
  1619.     $dat->{'args_byname'}{$argname}{'hidden'} = 1;
  1620. }
  1621.  
  1622. sub checksetting {
  1623.     # Check if there is already an choice record $setting in the $argname
  1624.     # argument in $dat, if not, create one
  1625.     my ($dat, $argname, $setting) = @_;
  1626.     return if 
  1627.     defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting});
  1628.     # setting record
  1629.     my $rec;
  1630.     $rec->{'value'} = $setting;
  1631.     # Insert record in 'vals' array for browsing all settings
  1632.     push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec);
  1633.     # 'vals_byname' hash for looking up settings by name
  1634.     $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} = 
  1635.     $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}];
  1636. }
  1637.  
  1638. sub removearg {
  1639.     # remove the argument record $argname from $dat
  1640.     my ($dat, $argname) = @_;
  1641.     return if !defined($dat->{'args_byname'}{$argname});
  1642.     # Remove 'args_byname' hash for looking up arguments by name
  1643.     delete $dat->{'args_byname'}{$argname};
  1644.     # Remove argument itself
  1645.     for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
  1646.     if ($dat->{'args'}[$i]{'name'} eq $argname) {
  1647.         splice(@{$dat->{'args'}}, $i, 1);
  1648.         last;
  1649.     }
  1650.     }
  1651. }
  1652.  
  1653. sub booltoenum {
  1654.     # Turn the boolean argument $argname from $dat to an enumerated choice
  1655.     # equivalent to the original argument
  1656.     my ($dat, $argname) = @_;
  1657.     return if !defined($dat->{'args_byname'}{$argname});
  1658.     # Argument record
  1659.     my $arg = $dat->{'args_byname'}{$argname};
  1660.     # General settings
  1661.     $arg->{'type'} = 'enum';
  1662.     my $proto = $arg->{'proto'};
  1663.     $arg->{'proto'} = '%s';
  1664.     # Choice for 'true'
  1665.     if (!defined($arg->{'name_true'})) {
  1666.     $arg->{'name_true'} = $arg->{'name'};
  1667.     }
  1668.     checksetting($dat, $argname, 'true');
  1669.     my $truechoice = $arg->{'vals_byname'}{'true'};
  1670.     $truechoice->{'comment'} = longname($arg->{'name_true'});
  1671.     $truechoice->{'driverval'} = $proto;
  1672.     # Choice for 'false'
  1673.     if (!defined($arg->{'name_false'})) {
  1674.     $arg->{'name_false'} = "no$arg->{'name'}";
  1675.     }
  1676.     checksetting($dat, $argname, 'false');
  1677.     my $falsechoice = $arg->{'vals_byname'}{'false'};
  1678.     $falsechoice->{'comment'} = longname($arg->{'name_false'});
  1679.     $falsechoice->{'driverval'} = '';
  1680.     # Default value
  1681.     if ($arg->{'default'} eq '0') {
  1682.     $arg->{'default'} = 'false';
  1683.     } else {
  1684.     $arg->{'default'} = 'true';
  1685.     }
  1686. }
  1687.  
  1688. sub checkoptionvalue {
  1689.  
  1690.     ## This function checks whether a given value is valid for a given
  1691.     ## option. If yes, it returns a cleaned value (e. g. always 0 or 1
  1692.     ## for boolean options), otherwise "undef". If $forcevalue is set,
  1693.     ## we always determine a corrected value to insert (we never return
  1694.     ## "undef").
  1695.  
  1696.     # Is $value valid for the option named $argname?
  1697.     my ($dat, $argname, $value, $forcevalue) = @_;
  1698.  
  1699.     # Record for option $argname
  1700.     my $arg = $dat->{'args_byname'}{$argname};
  1701.  
  1702.     if ($arg->{'type'} eq 'bool') {
  1703.     if ((lc($value) eq 'true') ||
  1704.         (lc($value) eq 'on') ||
  1705.         (lc($value) eq 'yes') ||
  1706.         (lc($value) eq '1')) {
  1707.         return 1;
  1708.     } elsif ((lc($value) eq 'false') ||
  1709.          (lc($value) eq 'off') ||
  1710.          (lc($value) eq 'no') ||
  1711.          (lc($value) eq '0')) {
  1712.         return 0;
  1713.     } elsif ($forcevalue) {
  1714.         # This maps Unknown to mean False.  Good?  Bad?
  1715.         # It was done so in Foomatic 2.0.x, too.
  1716.         return 0;
  1717.     }
  1718.     } elsif ($arg->{'type'} eq 'enum') {
  1719.     if ($arg->{'vals_byname'}{$value}) {
  1720.         return $value;
  1721.     } elsif ((($arg->{'name'} eq "PageSize") ||
  1722.           ($arg->{'name'} eq "PageRegion")) &&
  1723.          (defined($arg->{'vals_byname'}{'Custom'})) &&
  1724.          ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) {
  1725.         # Custom paper size
  1726.         return $value;
  1727.     } elsif ($forcevalue) {
  1728.         # wtf!?  that's not a choice!
  1729.         # Return the first entry of the list
  1730.         my $firstentry = $arg->{'vals'}[0]{'value'};
  1731.         return $firstentry;
  1732.     }
  1733.     } elsif (($arg->{'type'} eq 'int') ||
  1734.          ($arg->{'type'} eq 'float')) {
  1735.     if (($value <= $arg->{'max'}) &&
  1736.         ($value >= $arg->{'min'})) {
  1737.         return $value;
  1738.     } elsif ($forcevalue) {
  1739.         my $newvalue;
  1740.         if ($value > $arg->{'max'}) {
  1741.         $newvalue = $arg->{'max'}
  1742.         } elsif ($value < $arg->{'min'}) {
  1743.         $newvalue = $arg->{'min'}
  1744.         }
  1745.         return $newvalue;
  1746.     }
  1747.     } elsif (($arg->{'type'} eq 'string') ||
  1748.          ($arg->{'type'} eq 'password')) {
  1749.     if (defined($arg->{'vals_byname'}{$value})) {
  1750.         return $value;
  1751.     } elsif (stringvalid($dat, $argname, $value)) {
  1752.         # Check whether the string is one of the enumerated choices
  1753.         my $sprintfproto = $arg->{'proto'};
  1754.         $sprintfproto =~ s/\%(?!s)/\%\%/g;
  1755.         my $driverval = sprintf($sprintfproto, $value);
  1756.         for my $val (@{$arg->{'vals'}}) {
  1757.         if (($val->{'driverval'} eq $driverval) ||
  1758.             ($val->{'driverval'} eq $value)) {
  1759.             return $val->{value};
  1760.         }
  1761.         }
  1762.         # No matching choice? Return the original string
  1763.         return $value;
  1764.     } elsif ($forcevalue) {
  1765.         my $str = substr($value, 0, $arg->{'maxlength'});
  1766.         if (stringvalid($dat, $argname, $str)) {
  1767.         return $str;
  1768.         } elsif ($#{$arg->{'vals'}} >= 0) {
  1769.         # First list item
  1770.         my $firstentry = $arg->{'vals'}[0]{'value'};
  1771.         return $firstentry;
  1772.         } else {
  1773.         # Empty string
  1774.         return 'None';
  1775.         }
  1776.     }
  1777.     }
  1778.     return undef;
  1779. }
  1780.  
  1781. sub stringvalid {
  1782.  
  1783.     ## Checks whether a user-supplied value for a string option is valid
  1784.     ## It must be within the length limit, should only contain allowed
  1785.     ## characters and match the given regexp
  1786.  
  1787.     # Option and string
  1788.     my ($dat, $argname, $value) = @_;
  1789.  
  1790.     my $arg = $dat->{'args_byname'}{$argname};
  1791.  
  1792.     # Maximum length
  1793.     return 0 if (defined($arg->{'maxlength'}) &&
  1794.          (length($value) > $arg->{'maxlength'}));
  1795.  
  1796.     # Allowed characters
  1797.     if ($arg->{'allowedchars'}) {
  1798.     my $chars = $arg->{'allowedchars'};
  1799.     $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  1800.     return 0 if $value !~ /^[$chars]*$/;
  1801.     }
  1802.  
  1803.     # Regular expression
  1804.     if ($arg->{'allowedregexp'}) {
  1805.     my $regexp = $arg->{'allowedregexp'};
  1806.     $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  1807.     return 0 if $value !~ /$regexp/;
  1808.     }
  1809.  
  1810.     # All checks passed
  1811.     return 1;
  1812. }
  1813.  
  1814. sub checkoptions {
  1815.  
  1816.     ## Let the values of a boolean option being 0 or 1 instead of
  1817.     ## "True" or "False", range-check the defaults of all options and
  1818.     ## issue warnings if the values are not valid
  1819.  
  1820.     # Option set to be examined
  1821.     my ($dat, $optionset) = @_;
  1822.  
  1823.     for my $arg (@{$dat->{'args'}}) {
  1824.     if (defined($arg->{$optionset})) {
  1825.         $arg->{$optionset} =
  1826.         checkoptionvalue
  1827.         ($dat, $arg->{'name'}, $arg->{$optionset}, 1);
  1828.     }
  1829.     }
  1830.  
  1831.     # If the settings for "PageSize" and "PageRegion" are different,
  1832.     # set the one for "PageRegion" to the one for "PageSize" and issue
  1833.     # a warning.
  1834.     if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne
  1835.     $dat->{'args_byname'}{'PageRegion'}{$optionset}) {
  1836.     $dat->{'args_byname'}{'PageRegion'}{$optionset} =
  1837.         $dat->{'args_byname'}{'PageSize'}{$optionset};
  1838.     }
  1839. }
  1840.  
  1841. # If the PageSize or PageRegion was changed, also change the other
  1842.  
  1843. sub syncpagesize {
  1844.     
  1845.     # Name and value of the option we set, and the option set where we
  1846.     # did the change
  1847.     my ($dat, $name, $value, $optionset) = @_;
  1848.  
  1849.     # Don't do anything if we were called with an option other than
  1850.     # "PageSize" or "PageRegion"
  1851.     return if (($name ne "PageSize") && ($name ne "PageRegion"));
  1852.     
  1853.     # Don't do anything if not both "PageSize" and "PageRegion" exist
  1854.     return if ((!defined($dat->{'args_byname'}{'PageSize'})) ||
  1855.            (!defined($dat->{'args_byname'}{'PageRegion'})));
  1856.     
  1857.     my $dest;
  1858.     
  1859.     # "PageSize" --> "PageRegion"
  1860.     if ($name eq "PageSize") {
  1861.     $dest = "PageRegion";
  1862.     }
  1863.     
  1864.     # "PageRegion" --> "PageSize"
  1865.     if ($name eq "PageRegion") {
  1866.     $dest = "PageSize";
  1867.     }
  1868.     
  1869.     # Do it!
  1870.     my $val;
  1871.     if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) {
  1872.     # Standard paper size
  1873.     $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'};
  1874.     } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) {
  1875.     # Custom paper size
  1876.     $dat->{'args_byname'}{$dest}{$optionset} = $value;
  1877.     }
  1878. }
  1879.  
  1880. sub sortoptions {
  1881.  
  1882.     my ($dat, $only_options) = @_;
  1883.  
  1884.     # The following stuff is very awkward to implement in C, so we do
  1885.     # it here.
  1886.  
  1887.     # Sort options with "sortargs" function
  1888.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  1889.     @{$dat->{'args'}} = @sortedarglist;
  1890.  
  1891.     return if $only_options;
  1892.  
  1893.     # Sort values of enumerated options with "sortvals" function
  1894.     for my $arg (@{$dat->{'args'}}) {
  1895.     next if $arg->{'type'} !~ /^(enum|string|password)$/;
  1896.            my @sortedvalslist = sort sortvals keys(%{$arg->{'vals_byname'}});
  1897.     @{$arg->{'vals'}} = ();
  1898.     for my $i (@sortedvalslist) {
  1899.         my $val = $arg->{'vals_byname'}{$i};
  1900.         push (@{$arg->{'vals'}}, $val);
  1901.     }
  1902.     }
  1903.  
  1904. }
  1905.  
  1906. sub numericaldefaults {
  1907.  
  1908.     my ($dat) = @_;
  1909.  
  1910.     # Adobe's PPD specs do not support numerical
  1911.     # options. Therefore the numerical options are mapped to
  1912.     # enumerated options in the PPD file and their characteristics
  1913.     # as a numerical option are stored in "*Foomatic..."
  1914.     # keywords. Especially a default value between the enumerated
  1915.     # fixed values can be used as the default value. Then this
  1916.     # value must be given by a "*FoomaticRIPDefault<option>:
  1917.     # <value>" line in the PPD file. But this value is only valid,
  1918.     # if the "official" default given by a "*Default<option>:
  1919.     # <value>" line (it must be one of the enumerated values)
  1920.     # points to the enumerated value which is closest to this
  1921.     # value. This way a user can select a default value with a
  1922.     # tool only supporting PPD files but not Foomatic extensions.
  1923.     # This tool only modifies the "*Default<option>: <value>" line
  1924.     # and if the "*FoomaticRIPDefault<option>: <value>" had always
  1925.     # priority, the user's change in "*Default<option>: <value>"
  1926.     # had no effect.
  1927.  
  1928.     for my $arg (@{$dat->{'args'}}) {
  1929.     if ($arg->{'fdefault'}) {
  1930.         if ($arg->{'default'}) {
  1931.         if ($arg->{'type'} =~ /^(int|float)$/) {
  1932.             if ($arg->{'fdefault'} < $arg->{'min'}) {
  1933.             $arg->{'fdefault'} = $arg->{'min'};
  1934.             }
  1935.             if ($arg->{'fdefault'} > $arg->{'max'}) {
  1936.             $arg->{'fdefault'} = $arg->{'max'};
  1937.             }
  1938.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  1939.             my $closestvalue;
  1940.             for my $val (@{$arg->{'vals'}}) {
  1941.             if (abs($arg->{'fdefault'} - $val->{'value'}) <
  1942.                 $mindiff) {
  1943.                 $mindiff = 
  1944.                 abs($arg->{'fdefault'} - $val->{'value'});
  1945.                 $closestvalue = $val->{'value'};
  1946.             }
  1947.             }
  1948.             if (($arg->{'default'} == $closestvalue) ||
  1949.             (abs($arg->{'default'} - $closestvalue) /
  1950.              $closestvalue < 0.001)) {
  1951.             $arg->{'default'} = $arg->{'fdefault'};
  1952.             }
  1953.         }
  1954.         } else {
  1955.         $arg->{'default'} = $arg->{'fdefault'};
  1956.         }
  1957.     }
  1958.     }
  1959. }
  1960.  
  1961. sub setnumericaldefaults {
  1962.  
  1963.     my ($dat) = @_;
  1964.  
  1965.     for my $arg (@{$dat->{'args'}}) {
  1966.     if ($arg->{'default'}) {
  1967.         if ($arg->{'type'} =~ /^(int|float)$/) {
  1968.         if ($arg->{'default'} < $arg->{'min'}) {
  1969.             $arg->{'default'} = $arg->{'min'};
  1970.             $arg->{'cdefault'} = $arg->{'default'};
  1971.         } elsif ($arg->{'default'} > $arg->{'max'}) {
  1972.             $arg->{'default'} = $arg->{'max'};
  1973.             $arg->{'cdefault'} = $arg->{'default'};
  1974.         } elsif (defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  1975.             $arg->{'cdefault'} = $arg->{'default'};
  1976.         } else {
  1977.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  1978.             my $closestvalue;
  1979.             for my $val (@{$arg->{'vals'}}) {
  1980.             if (abs($arg->{'default'} - $val->{'value'}) <
  1981.                 $mindiff) {
  1982.                 $mindiff = 
  1983.                 abs($arg->{'default'} - $val->{'value'});
  1984.                 $closestvalue = $val->{'value'};
  1985.             }
  1986.             }
  1987.             $arg->{'cdefault'} = $closestvalue;
  1988.         }
  1989.         }
  1990.     }
  1991.     }
  1992.  
  1993. }
  1994.  
  1995. sub generalentries {
  1996.  
  1997.     my ($dat) = @_;
  1998.  
  1999.     $dat->{'compiled-at'} = localtime(time());
  2000.     $dat->{'timestamp'} = time();
  2001.  
  2002.     my $user = `whoami`; chomp $user;
  2003.     my $host = `hostname`; chomp $host;
  2004.  
  2005.     $dat->{'compiled-by'} = "$user\@$host";
  2006.  
  2007. }
  2008.  
  2009. sub checklongnames {
  2010.  
  2011.     my ($dat) = @_;
  2012.  
  2013.     # Add missing longnames/translations
  2014.     for my $arg (@{$dat->{'args'}}) {
  2015.     if (!($arg->{'comment'})) {
  2016.         $arg->{'comment'} = longname($arg->{'name'});
  2017.     }
  2018.     for my $i (@{$arg->{'vals'}}) {
  2019.         if (!($i->{'comment'})) {
  2020.         $i->{'comment'} = longname($i->{'value'});
  2021.         }
  2022.     }
  2023.     }
  2024. }
  2025.  
  2026. sub cutguiname {
  2027.     
  2028.     # If $shortgui is set and $str is longer than 39 characters, return the
  2029.     # first 39 characters of $str, otherwise the complete $str. 
  2030.  
  2031.     my ($str, $shortgui) = @_;
  2032.  
  2033.     if (($shortgui) && (length($str) > 39)) {
  2034.     return substr($str, 0, 39);
  2035.     } else {
  2036.     return $str;
  2037.     }
  2038. }
  2039.  
  2040. sub ppd1284DeviceID {
  2041.  
  2042.     # Clean up IEEE-1284 device ID to only contain the fields relevant
  2043.     # to printer model auto-detection (MFG, MDL, DES, CMD, SKU), this
  2044.     # the line length limit of PPDs does not get exceeded on very long
  2045.     # ID strings.
  2046.  
  2047.     my ($id) = @_;
  2048.     my $ppdid = "";
  2049.     
  2050.     foreach my $field ("(MFG|MANUFACTURER)", "(MDL|MODEL)", "(CMD|COMMAND SET)", "(DES|DESCRIPTION)", "SKU") {
  2051.     if ($id =~ m/(\b$field:[^:;]+;)/is) {
  2052.         $ppdid .= $1;
  2053.     }
  2054.     }
  2055.  
  2056.     return $ppdid;
  2057. }
  2058.  
  2059. sub getppdheaderdata {
  2060.     
  2061.     my ($dat, $driver, $recdriver) = @_;
  2062.  
  2063.     # Complete IEEE 1284 ID string?
  2064.     my $ieee1284;
  2065.     $ieee1284 = $dat->{'general_ieee'} or $ieee1284 = $dat->{'pnp_ieee'} or
  2066.     $ieee1284 = $dat->{'par_ieee'} or $ieee1284 = $dat->{'usb_ieee'} or 
  2067.     $ieee1284 = $dat->{'snmp_ieee'} or $ieee1284 = "";
  2068.     # Extract data fields from the ID string
  2069.     my $ieeemake;
  2070.     my $ieeemodel;
  2071.     my $ieeecmd;
  2072.     my $ieeedes;
  2073.     if ($ieee1284) {
  2074.     $ieee1284 =~ /(MFG|MANUFACTURER):([^:;]+);/;
  2075.     $ieeemake = $2;
  2076.     $ieee1284 =~ /(MDL|MODEL):([^:;]+);/;
  2077.     $ieeemodel = $2;
  2078.     $ieee1284 =~ /(CMD|COMMANDS\s+SET):([^:;]+);/;
  2079.     $ieeecmd = $2;
  2080.     $ieee1284 =~ /(DES|DESCRIPTION):([^:;]+);/;
  2081.     $ieeedes = $2;
  2082.     }
  2083.     # Auto-detection data listed field by field in the printer XML file?
  2084.     my $pnpmake;
  2085.     $pnpmake = $ieeemake or $pnpmake = $dat->{'general_mfg'} or 
  2086.     $pnpmake = $dat->{'pnp_mfg'} or $pnpmake = $dat->{'par_mfg'} or
  2087.     $pnpmake = $dat->{'usb_mfg'} or $pnpmake = "";
  2088.     my $pnpmodel;
  2089.     $pnpmodel = $ieeemodel or $pnpmodel = $dat->{'general_mdl'} or
  2090.     $pnpmodel = $dat->{'pnp_mdl'} or $pnpmodel = $dat->{'par_mdl'} or
  2091.     $pnpmodel = $dat->{'usb_mdl'} or $pnpmodel = "";
  2092.     my $pnpcmd;
  2093.     $pnpcmd = $ieeecmd or $pnpcmd = $dat->{'general_cmd'} or 
  2094.     $pnpcmd = $dat->{'pnp_cmd'} or $pnpcmd = $dat->{'par_cmd'} or
  2095.     $pnpcmd = $dat->{'usb_cmd'} or $pnpcmd = "";
  2096.     my $pnpdescription;
  2097.     $pnpdescription = $ieeedes or
  2098.     $pnpdescription = $dat->{'general_des'} or
  2099.     $pnpdescription = $dat->{'pnp_des'} or 
  2100.     $pnpdescription = $dat->{'par_des'} or
  2101.     $pnpdescription = $dat->{'usb_des'} or
  2102.     $pnpdescription = "";
  2103.     if ((!$ieee1284) && ((($pnpmake) && ($pnpmodel)) || ($pnpdescription))){
  2104.     $ieee1284 .= "MFG:$pnpmake;" if $pnpmake;
  2105.     $ieee1284 .= "MDL:$pnpmodel;" if $pnpmodel;
  2106.     $ieee1284 .= "CMD:$pnpcmd;" if $pnpcmd;
  2107.     $ieee1284 .= "DES:$pnpdescription;" if $pnpdescription;
  2108.     }
  2109.  
  2110.     # Remove everything from the device ID which is not relevant to
  2111.     # auto-detection of the printer model.
  2112.     $ieee1284 = ppd1284DeviceID($ieee1284) if $ieee1284;
  2113.  
  2114.     my $make = $dat->{'make'};
  2115.     my $model = $dat->{'model'};
  2116.  
  2117.     $pnpmake = $make if !$pnpmake;
  2118.     $pnpmodel = $model if !$pnpmodel;
  2119.  
  2120.     # File name for the PPD file
  2121.     my $filename = join('-',($dat->{'make'},
  2122.                  $dat->{'model'},
  2123.                  $driver));;
  2124.     $filename =~ s![ /\(\)]!_!g;
  2125.     $filename =~ s![\+]!plus!g;
  2126.     $filename =~ s!__+!_!g;
  2127.     $filename =~ s!_$!!;
  2128.     $filename =~ s!^_!!;
  2129.     $filename =~ s!_-!-!;
  2130.     $filename =~ s!-_!-!;
  2131.     my $longname = "$filename.ppd";
  2132.  
  2133.     # Driver name
  2134.     my $drivername = $driver;
  2135.  
  2136.     # Do we use the recommended driver?
  2137.     my $driverrecommended = "";
  2138.     if ($driver eq $recdriver) {
  2139.     $driverrecommended = " (recommended)";
  2140.     }
  2141.     
  2142.     # evil special case.
  2143.     $drivername = "stp-4.0" if $drivername eq 'stp';
  2144.  
  2145.     # Nickname for the PPPD file
  2146.     my $nickname =
  2147.     "$make $model Foomatic/$drivername$driverrecommended";
  2148.     my $modelname = "$make $model";
  2149.     # Remove forbidden characters (Adobe PPD spec 4.3 section 5.3)
  2150.     $modelname =~ s/[^A-Za-z0-9 \.\/\-\+]//gs;
  2151.  
  2152.     return ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  2153.         $drivername,$nickname,$modelname);
  2154. }
  2155.  
  2156. #
  2157. # PPD generation
  2158. #
  2159.  
  2160. # member( $a, @b ) returns 1 if $a is in @b, 0 otherwise.
  2161. sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 };
  2162.  
  2163.  
  2164. sub setgroupandorder {
  2165.  
  2166.     # Set group of member options. Make also sure that the composite
  2167.     # option will be inserted into the PostScript code before all its
  2168.     # # members are inserted (by means of the section and the order #
  2169.     # number).
  2170.  
  2171.     # The composite option to be treated ($arg)
  2172.     my ($db, $arg, $members_in_subgroup) = @_;
  2173.     
  2174.     # The Perl data structure of the current printer/driver combo.
  2175.     my $dat = $db->{'dat'};
  2176.  
  2177.     # Here we are only interested in composite options, skip the others
  2178.     return if $arg->{'style'} ne 'X';
  2179.  
  2180.     my $name = $arg->{'name'};
  2181.     my $group = $arg->{'group'};
  2182.     my $order = $arg->{'order'};
  2183.     my $section = $arg->{'section'};
  2184.     my @members = @{$arg->{'members'}};
  2185.  
  2186.     for my $m (@members) {
  2187.     my $a = $dat->{'args_byname'}{$m};
  2188.  
  2189.     # If $members_in_subgroup is set, the group should be a
  2190.     # subgroup of the group where the composite option is
  2191.     # located, named as the composite option. Otherwise the
  2192.     # group will get a new main group.
  2193.     if (($members_in_subgroup) && ($group)) {
  2194.         $a->{'group'} = "$group/$name";
  2195.     } else {
  2196.         $a->{'group'} = "$name";
  2197.     }
  2198.  
  2199.     # If the member is composite, call this function on it recursively.
  2200.     # This sets the groups of the members of this composite member option
  2201.     # and also sets the section and order number of this composite
  2202.     # member, so that we can so that we can set section and order of the
  2203.     # currently treated option
  2204.     $db->setgroupandorder($a, $members_in_subgroup)
  2205.         if $a->{'style'} eq 'X';
  2206.  
  2207.     # Determine section and order number for the composite option
  2208.     # Order of the DSC sections of a PostScript file
  2209.     my @sectionorder = ("JCLSetup", "Prolog", "DocumentSetup", 
  2210.                 "AnySetup", "PageSetup");
  2211.  
  2212.     # Set default for missing section value in member
  2213.     if (!defined($a->{'section'})) {$a->{'section'} = "AnySetup";}
  2214.     my $minsection;
  2215.     for my $s (@sectionorder) {
  2216.         if (($s eq $arg->{'section'}) || ($s eq $a->{'section'})) {
  2217.         $minsection = $s;
  2218.         last;
  2219.         }
  2220.     }
  2221.  
  2222.     # If the current member option is in an earlier section,
  2223.     # put also the composite option into it. Do never put the
  2224.     # composite option into the JCL setup because in the JCL
  2225.     # header PostScript comments are not allowed.
  2226.     $arg->{'section'} = ($minsection ne "JCLSetup" ?
  2227.                  $minsection : "Prolog");
  2228.  
  2229.     # Let the order number of the composite option be less
  2230.     # than the order number of the current member
  2231.     if ($arg->{'order'} >= $a->{'order'}) {
  2232.         $arg->{'order'} = $a->{'order'} - 1;
  2233.         if ($arg->{'order'} < 0) {
  2234.         $arg->{'order'} = 0;
  2235.         }
  2236.     }
  2237.     }
  2238. }
  2239.  
  2240.  
  2241. # Return a generic Adobe-compliant PPD for the "foomatic-rip" filter script
  2242. # for all spoolers.  Built from the standard data; you must call getdat()
  2243. # first.
  2244. sub getppd (  $ $ $ ) {
  2245.  
  2246.     # If $shortgui is set, all GUI strings ("translations" in PPD
  2247.     # files) will be cut to a maximum length of 39 characters. This is
  2248.     # needed by the current (as of July 2003) version of the CUPS
  2249.     # PostScript driver for Windows.
  2250.  
  2251.     # If $members_in_subgroup is set, the member options of a composite
  2252.     # option go into a subgroup of the group where the composite option
  2253.     # is located. Otherwise the member options go into a new main group
  2254.  
  2255.     my ($db, $shortgui, $members_in_subgroup) = @_;
  2256.  
  2257.     die "you need to call getdat first!\n" 
  2258.     if (!defined($db->{'dat'}));
  2259.  
  2260.     # The Perl data structure of the current printer/driver combo.
  2261.     my $dat = $db->{'dat'};
  2262.  
  2263.     # Do we have a custom pre-made PPD? If so, return this one
  2264.     if (defined($dat->{'ppdfile'})) {
  2265.     my $ppdfile = $dat->{'ppdfile'};
  2266.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  2267.     if (-r $ppdfile) {
  2268.         # Load the complete PPD file into memory
  2269.         if (open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  2270.                "$sysdeps->{'gzip'} -cd \'$ppdfile\' |")) {
  2271.         my @ppdlines = <PPD>;
  2272.         close PPD;
  2273.         # Set the default values
  2274.         my $ppd = $db->ppdvarsetdefaults(@ppdlines);
  2275.         return $ppd;
  2276.         }
  2277.     }
  2278.     }
  2279.  
  2280.     my @optionblob; # Lines for command line and options in the PPD file
  2281.  
  2282.     # Insert the printer/driver IDs and the command line prototype
  2283.     # right before the option descriptions
  2284.  
  2285.     push(@optionblob, "*FoomaticIDs: $dat->{'id'} $dat->{'driver'}\n");
  2286.     my $header = "*FoomaticRIPCommandLine";
  2287.     my $cmdline = $dat->{'cmd'};
  2288.     my $cmdlinestr = ripdirective($header, $cmdline);
  2289.     if ($cmdline) {
  2290.     # Insert the "*FoomaticRIPCommandLine" directive, but only if
  2291.     # the command line prototype is not empty
  2292.     push(@optionblob, "$cmdlinestr\n");
  2293.     if ($cmdlinestr =~ /\n/s) {
  2294.         push(@optionblob, "*End\n");
  2295.     }
  2296.     }
  2297.  
  2298.     # Search for composite options and prepare the member options
  2299.     # of the found composite options
  2300.     for my $arg (@{$dat->{'args'}}) {
  2301.     # Here we are only interested in composite options, skip the others
  2302.     next if $arg->{'style'} ne 'X';
  2303.     my $name = $arg->{'name'};
  2304.     my $com  = $arg->{'comment'};
  2305.     my $group = $arg->{'group'};
  2306.     my $order = $arg->{'order'};
  2307.     my $section = $arg->{'section'};
  2308.  
  2309.     # The "PageRegion" option is generated automatically, so ignore an
  2310.     # already existing "PageRegion". 
  2311.     next if $name eq "PageRegion";
  2312.  
  2313.     # Set default for missing section value
  2314.     if (!defined($section)) {$arg->{'section'} = "AnySetup";}
  2315.  
  2316.     # Set default for missing tranaslation/longname
  2317.     if (!$com) {$com = longname($name);}
  2318.  
  2319.     my @members;
  2320.  
  2321.     # Go through all choices of the composite option to find its
  2322.     # member options
  2323.     for my $v (@{$arg->{'vals'}}) {
  2324.         my @settings = split(/\s+/s, $v->{'driverval'});
  2325.         for my $s (@settings) {
  2326.         if (($s =~ /^([^=]+)=/) ||
  2327.             ($s =~ /^[Nn][Oo]([^=]+)$/) ||
  2328.             ($s =~ /^([^=]+)$/)) {
  2329.             my $m = $1;
  2330.             # Does the found member exist for this printer/driver
  2331.             # combo?
  2332.             if (defined($dat->{'args_byname'}{$m})) {
  2333.             # Add it to the list of found member options
  2334.             if (!member($m, @members)) {
  2335.                 push(@members, $1);
  2336.             }
  2337.             # Clean up entries for boolean options
  2338.             if ($s !~ /=/) {
  2339.                 if ($s =~ /^[Nn][Oo]$m$/) {
  2340.                 $v->{'driverval'} =~
  2341.                     s/(^|\s)$s($|\s)/$1$m=false$2/;
  2342.                 } else {
  2343.                 $v->{'driverval'} =~ 
  2344.                     s/(^|\s)$s($|\s)/$1$m=true$2/;
  2345.                 }
  2346.             }
  2347.             } else {
  2348.             # Remove it from the choice of the composite
  2349.             # option
  2350.             $v->{'driverval'} =~ s/$s\s*//;
  2351.             $v->{'driverval'} =~ s/\s*$//;
  2352.             }
  2353.         }
  2354.         }
  2355.     }
  2356.  
  2357.     # Add the member list to the data structure of the composite
  2358.     # option. We nned it for the recursive setting of group names
  2359.     # and order numbers
  2360.     $arg->{'members'} = \@members;
  2361.  
  2362.     # Add a "From<Composite>" choice which will be the
  2363.     # default. Check also all members if they are hidden, if so,
  2364.     # this composite option is a forced composite option.
  2365.     my $nothiddenmemberfound = 0;
  2366.     for my $m (@members) {
  2367.         my $a = $dat->{'args_byname'}{$m};
  2368.  
  2369.         # Mark this member as being a member of the current
  2370.         # composite option
  2371.         $a->{'memberof'} = $name;
  2372.  
  2373.         # Convert boolean options to enumerated choice options, so
  2374.         # that we can add the "From<Composite>" choice.
  2375.         if ($a->{'type'} eq 'bool') {
  2376.         booltoenum($dat, $a->{'name'});
  2377.         }
  2378.  
  2379.         # Is this member option hidden?
  2380.         if (!$a->{'hidden'}) {
  2381.         $nothiddenmemberfound = 1;
  2382.         }
  2383.  
  2384.         # In case of a forced composite option mark the member option
  2385.         # as hidden.
  2386.         if (defined($arg->{'substyle'}) &&
  2387.         ($arg->{'substyle'} eq 'F')) {
  2388.         $a->{'hidden'} = 1;
  2389.         }
  2390.  
  2391.         # Do not add a "From<Composite>" choice to an option with only
  2392.         # one choice
  2393.         next if $#{$a->{'vals'}} < 1;
  2394.  
  2395.         if (!defined($a->{'vals_byname'}{"From$name"})) {
  2396.         # Add "From<Composite>" choice
  2397.         # setting record
  2398.         my $rec;
  2399.         $rec->{'value'} = "From$name";
  2400.         $rec->{'comment'} = "Controlled by '$com'";
  2401.         # We mark the driverval as invalid with a non-printable
  2402.         # character, this means that the code to insert will be an
  2403.         # empty string in the PPD.
  2404.         $rec->{'driverval'} = "\x01";
  2405.         # Insert record as the first item in the 'vals' array
  2406.         unshift(@{$a->{'vals'}}, $rec);
  2407.         # Update 'vals_byname' hash
  2408.         $a->{'vals_byname'}{$rec->{'value'}} = $a->{'vals'}[0];
  2409.         for (my $i = 1; $i <= $#{$a->{'vals'}}; $i ++) {
  2410.             $a->{'vals_byname'}{$a->{'vals'}[$i]{'value'}} =
  2411.             $a->{'vals'}[$i];
  2412.         }
  2413.         } else {
  2414.         # Only update the values
  2415.         $a->{'vals_byname'}{"From$name"}{'value'} = "From$name";
  2416.         $a->{'vals_byname'}{"From$name"}{'comment'} =
  2417.             "Controlled by '$com'";
  2418.         $a->{'vals_byname'}{"From$name"}{'driverval'} = "\x01";
  2419.         }
  2420.  
  2421.         # Set default to the new "From<Composite>" choice
  2422.         $a->{'default'} = "From$name";
  2423.     }
  2424.  
  2425.     # If all member options are hidden, this composite option is
  2426.     # a forced composite option and has to be marked appropriately
  2427.     if (!$nothiddenmemberfound) {
  2428.         $arg->{'substyle'} = 'F';
  2429.     }
  2430.     }
  2431.  
  2432.     # Now recursively set the groups and the order sections and numbers
  2433.     # for all composite options and their members.
  2434.     for my $arg (@{$dat->{'args'}}) {
  2435.     # The recursion should only be started in composite options
  2436.     # which are not member of another composite option.
  2437.     $db->setgroupandorder($arg, $members_in_subgroup) 
  2438.         if ($arg->{'style'} eq 'X') and (!$arg->{'memberof'});
  2439.     }
  2440.  
  2441.     # Sort options with "sortargs" function after they were re-grouped
  2442.     # due to the composite options
  2443.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  2444.     @{$dat->{'args'}} = @sortedarglist;
  2445.  
  2446.     # Construct the option entries for the PPD file
  2447.  
  2448.     my @groupstack; # In which group are we currently
  2449.  
  2450.     for my $arg (@{$dat->{'args'}}) {
  2451.     my $name = $arg->{'name'};
  2452.     my $type = $arg->{'type'};
  2453.     my $com  = $arg->{'comment'};
  2454.     my $default = $arg->{'default'};
  2455.     my $order = $arg->{'order'};
  2456.     my $spot = $arg->{'spot'};
  2457.     my $section = $arg->{'section'};
  2458.     my $cmd = $arg->{'proto'};
  2459.     my @group;
  2460.     @group = split("/", $arg->{'group'}) if defined($arg->{'group'});
  2461.     my $idx = $arg->{'idx'};
  2462.  
  2463.     # What is the execution style of the current option? Skip options
  2464.         # of unknown execution style
  2465.     my $optstyle = ($arg->{'style'} eq 'G' ? "PS" :
  2466.             ($arg->{'style'} eq 'J' ? "JCL" :
  2467.              ($arg->{'style'} eq 'C' ? "CmdLine" :
  2468.               ($arg->{'style'} eq 'X' ? "Composite" :
  2469.                "Unknown"))));
  2470.     next if $optstyle eq "Unknown";
  2471.  
  2472.     # The "PageRegion" option is generated automatically, so ignore an
  2473.     # already existing "PageRegion". 
  2474.     next if $name eq "PageRegion";
  2475.  
  2476.     # The command prototype should not be empty, set default
  2477.     if (!$cmd) {
  2478.         $cmd = "%s";
  2479.     }
  2480.  
  2481.     # Set default for missing section value
  2482.     if (!defined($section)) {$section = "AnySetup";}
  2483.  
  2484.     # Set default for missing tranaslation/longname
  2485.     if (!$com) {$com = longname($name);}
  2486.  
  2487.     # If for a string option the default value is not available under
  2488.     # the enumerated choices, add it here. Make the default choice also
  2489.     # the first list entry
  2490.     if ($type =~ /^(string|password)$/) {
  2491.         $arg->{'default'} =
  2492.         checkoptionvalue($dat, $name, $arg->{'default'}, 1);
  2493.         # An empty string cannot be an option name in a PPD file,
  2494.         # use "None" in this case
  2495.         my $defcom = $arg->{'default'};
  2496.         my $defstr = $arg->{'default'};
  2497.         if ($arg->{'default'} !~ /\S/) {
  2498.         $arg->{'default'} = 'None';
  2499.         $defcom = '(None)';
  2500.         $defstr = '';
  2501.         } elsif ($arg->{'default'} eq 'None') {
  2502.         $defcom = '(None)';
  2503.         $defstr = '';
  2504.         } else {
  2505.         $arg->{'default'} =~ s/\W+/_/g;
  2506.         $arg->{'default'} =~ s/^_+|_+$//g;
  2507.         $arg->{'default'} = '_' if ($arg->{'default'} eq '');
  2508.             $defcom =~ s/:/ /g;
  2509.         $defcom =~ s/^ +| +$//g;
  2510.         }
  2511.         $default = $arg->{'default'};
  2512.         # Generate a new choice
  2513.         if (!defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  2514.         checksetting($dat, $name, $arg->{'default'});
  2515.         my $newchoice = $arg->{'vals_byname'}{$arg->{'default'}};
  2516.         $newchoice->{'value'} = $arg->{'default'};
  2517.         $newchoice->{'comment'} = $defcom;
  2518.         $newchoice->{'driverval'} = $defstr;
  2519.         }
  2520.         # Bring the default entry to the first position
  2521.         my $index = 0;
  2522.         for (my $i = 0; $i <= $#{$arg->{vals}}; $i ++) {
  2523.         if ($arg->{vals}[$i]{'value'} eq $arg->{'default'}) {
  2524.             $index = $i;
  2525.             last;
  2526.         }
  2527.         }
  2528.         my $def = splice(@{$arg->{vals}}, $index, 1);
  2529.         unshift(@{$arg->{vals}}, $def);
  2530.     }
  2531.  
  2532.     # Do we have to open or close one or more groups here?
  2533.     # No group will be opened more than once, since the options
  2534.     # are sorted to have the members of every group together
  2535.  
  2536.     # Only take into account the groups of options which will be
  2537.     # visible user interface options in the PPD.
  2538.     if ((($type !~ /^(enum|string|password)$/) ||
  2539.          ($#{$arg->{'vals'}} > 0) || ($name eq "PageSize") ||
  2540.          ($arg->{'style'} eq 'G')) &&
  2541.         (!$arg->{'hidden'})){
  2542.         # Find the level on which the group path of the current option
  2543.         # (@group) differs from the group path of the last option
  2544.         # (@groupstack).
  2545.         my $level = 0;
  2546.         while (($level <= $#groupstack) and
  2547.            ($level <= $#group) and 
  2548.            ($groupstack[$level] eq $group[$level])) {
  2549.         $level++;
  2550.         }
  2551.         for (my $i = $#groupstack; $i >= $level; $i--) {
  2552.         # Close this group, the current option is not member
  2553.         # of it.
  2554.         push(@optionblob,
  2555.              sprintf("\n*Close%sGroup: %s\n",
  2556.                  ($i > 0 ? "Sub" : ""), $groupstack[$i])
  2557.              );
  2558.         pop(@groupstack);
  2559.         }
  2560.         for (my $i = $level; $i <= $#group; $i++) {
  2561.         # Open this group, the current option is a member
  2562.         # of it.
  2563.         push(@optionblob,
  2564.              sprintf("\n*Open%sGroup: %s/%s\n",
  2565.                  ($i > 0 ? "Sub" : ""), $group[$i], 
  2566.                  cutguiname(longname($group[$i]), $shortgui))
  2567.              );
  2568.         push(@groupstack, $group[$i]);
  2569.         }
  2570.     }
  2571.  
  2572.     if ($type =~ /^(enum|string|password)$/) {
  2573.         # Extra information for string options
  2574.         my ($stringextralines0, $stringextralines1) = ('', '');
  2575.         if ($type =~ /^(string|password)$/) {
  2576.         $stringextralines0 .= sprintf
  2577.              ("*FoomaticRIPOption %s: %s %s %s\n",
  2578.               $name, $type, $optstyle, $spot);
  2579.         my $header = sprintf
  2580.             ("*FoomaticRIPOptionPrototype %s",
  2581.              $name);
  2582.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  2583.         $stringextralines1 .= $foomaticstr;
  2584.         # Stuff to insert into command line/job is more than one
  2585.         # line? Let an "*End" line follow
  2586.         if ($foomaticstr =~ /\n.*\n/s) {
  2587.             $stringextralines1 .= "*End\n";
  2588.         }
  2589.  
  2590.         if ($arg->{'maxlength'}) {
  2591.             $stringextralines1 .= sprintf
  2592.              ("*FoomaticRIPOptionMaxLength %s: %s\n",
  2593.               $name, $arg->{'maxlength'});
  2594.         }
  2595.  
  2596.         if ($arg->{'allowedchars'}) {
  2597.             my $header = sprintf
  2598.             ("*FoomaticRIPOptionAllowedChars %s",
  2599.              $name);
  2600.             my $entrystr = ripdirective($header, 
  2601.                         $arg->{'allowedchars'}) . "\n";
  2602.             $stringextralines1 .= $entrystr;
  2603.             # Stuff to insert into command line/job is more than one
  2604.             # line? Let an "*End" line follow
  2605.             if ($entrystr =~ /\n.*\n/s) {
  2606.             $stringextralines1 .= "*End\n";
  2607.             }
  2608.         }
  2609.  
  2610.         if ($arg->{'allowedregexp'}) {
  2611.             my $header = sprintf
  2612.             ("*FoomaticRIPOptionAllowedRegExp %s",
  2613.              $name);
  2614.             my $entrystr = ripdirective($header, 
  2615.                         $arg->{'allowedregexp'}) .
  2616.                             "\n";
  2617.             $stringextralines1 .= $entrystr;
  2618.             # Stuff to insert into command line/job is more than one
  2619.             # line? Let an "*End" line follow
  2620.             if ($entrystr =~ /\n.*\n/s) {
  2621.             $stringextralines1 .= "*End\n";
  2622.             }
  2623.         }
  2624.  
  2625.         }
  2626.  
  2627.         # Skip zero or one choice arguments. Do not skip "PageSize",
  2628.         # since a PPD file without "PageSize" will break the CUPS
  2629.         # environment and also do not skip PostScript options. For
  2630.         # skipped options with one choice only "*Foomatic..."
  2631.         # definitions will be used. Skip also the hidden member
  2632.         # options of a forced composite option.
  2633.         if (((1 < scalar(@{$arg->{'vals'}})) ||
  2634.          ($name eq "PageSize") ||
  2635.          ($arg->{'style'} eq 'G')) &&
  2636.         (!$arg->{'hidden'})) {
  2637.  
  2638.         push(@optionblob,
  2639.              sprintf("\n*OpenUI *%s/%s: PickOne\n", $name, 
  2640.                  cutguiname($com, $shortgui)));
  2641.  
  2642.         if ($arg->{'style'} ne 'G') {
  2643.             # For non-PostScript options insert line with option
  2644.             # properties
  2645.             push(@optionblob, sprintf
  2646.              ("*FoomaticRIPOption %s: %s %s %s\n",
  2647.               $name, $type, $optstyle, $spot));
  2648.         }
  2649.  
  2650.         if ($type =~ /^(string|password)$/) {
  2651.             # Extra information for string options
  2652.             push(@optionblob, $stringextralines0, $stringextralines1);
  2653.         }
  2654.  
  2655.         push(@optionblob,
  2656.              sprintf("*OrderDependency: %s %s *%s\n", 
  2657.                  $order, $section, $name),
  2658.              sprintf("*Default%s: %s\n", 
  2659.                  $name,
  2660.                  (defined($default) ? 
  2661.                   checkoptionvalue($dat, $name, $default, 1) :
  2662.                   'Unknown')));
  2663.  
  2664.         if (!defined($default)) {
  2665.             my $whr = sprintf("%s %s driver %s",
  2666.                       $dat->{'make'},
  2667.                       $dat->{'model'},
  2668.                       $dat->{'driver'});
  2669.             warn "undefined default for $idx/$name on a $whr\n";
  2670.         }
  2671.         
  2672.         # If this is the page size argument; construct
  2673.         # PageRegion, ImageableArea, and PaperDimension clauses 
  2674.         # from it. Arguably this is all backwards, but what can
  2675.         # you do! ;)
  2676.         my @pageregion;
  2677.         my @imageablearea;
  2678.         my @paperdimension;
  2679.  
  2680.         # If we have a paper size named "Custom", or one with
  2681.         # one or both dimensions being zero, we must replace
  2682.         # this by an Adobe-complient custom paper size
  2683.         # definition.
  2684.         my $hascustompagesize = 0;
  2685.  
  2686.         # We take very big numbers now, to not impose linits.
  2687.         # Later, when we will have physical demensions of the
  2688.         # printers in the database.
  2689.         my $maxpagewidth = 100000;
  2690.         my $maxpageheight = 100000;
  2691.  
  2692.         # Start the PageRegion, ImageableArea, and PaperDimension
  2693.         # clauses
  2694.         if ($name eq "PageSize") {
  2695.             
  2696.             push(@pageregion,
  2697.              "*OpenUI *PageRegion: PickOne
  2698. *OrderDependency: $order $section *PageRegion
  2699. *DefaultPageRegion: $dat->{'args_byname'}{'PageSize'}{'default'}");
  2700.             push(@imageablearea, 
  2701.              "*DefaultImageableArea: $dat->{'args_byname'}{'PageSize'}{'default'}");
  2702.             push(@paperdimension, 
  2703.              "*DefaultPaperDimension: $dat->{'args_byname'}{'PageSize'}{'default'}");
  2704.         }
  2705.  
  2706.         for my $v (@{$arg->{'vals'}}) {
  2707.             my $psstr = "";
  2708.  
  2709.             if ($name eq "PageSize") {
  2710.             
  2711.             my $value = $v->{'value'}; # in a PPD, the value 
  2712.                                        # is the PPD name...
  2713.             my $comment = $v->{'comment'};
  2714.  
  2715.             # Here we have to fill in the absolute sizes of the 
  2716.             # papers. We consult a table when we could not read
  2717.             # the sizes out of the choices of the "PageSize"
  2718.             # option.
  2719.             my $size = $v->{'driverval'};
  2720.             if ($size =~ /([\d\.]+)x([\d\.]+)([a-z]+)\b/) {
  2721.                 # 2 positive integers separated by 
  2722.                 # an 'x' with a unit
  2723.                 my $w = $1;
  2724.                 my $h = $2;
  2725.                 my $u = $3;
  2726.                 if ($u =~ /^in(|ch(|es))$/i) {
  2727.                 $w *= 72.0;
  2728.                 $h *= 72.0;
  2729.                 } elsif ($u =~ /^mm$/i) {
  2730.                 $w *= 72.0/25.4;
  2731.                 $h *= 72.0/25.4;
  2732.                 } elsif ($u =~ /^cm$/i) {
  2733.                 $w *= 72.0/2.54;
  2734.                 $h *= 72.0/2.54;
  2735.                 }
  2736.                 $w = sprintf("%.2f", $w) if $w =~ /\./;
  2737.                 $h = sprintf("%.2f", $h) if $h =~ /\./;
  2738.                 $size = "$w $h";
  2739.             } elsif (($size =~ /(\d+)[x\s]+(\d+)/) ||
  2740.                 # 2 positive integers separated by 
  2741.                 # whitespace or an 'x'
  2742.                  ($size =~ /\-dDEVICEWIDTHPOINTS\=(\d+)\s+\-dDEVICEHEIGHTPOINTS\=(\d+)/)) {
  2743.                 # "-dDEVICEWIDTHPOINTS=..."/"-dDEVICEHEIGHTPOINTS=..."
  2744.                 $size = "$1 $2";
  2745.             } else {
  2746.                 $size = getpapersize($value);
  2747.             }
  2748.             $size =~ /^\s*([\d\.]+)\s+([\d\.]+)\s*$/;
  2749.             my $width = $1;
  2750.             my $height = $2;
  2751.             if ($maxpagewidth < $width) {
  2752.                 $maxpagewidth = $width;
  2753.             }
  2754.             if ($maxpageheight < $height) {
  2755.                 $maxpageheight = $height;
  2756.             }
  2757.             if (($value eq "Custom") ||
  2758.                 ($width == 0) || ($height == 0)) {
  2759.                 # This page size is either named "Custom" or
  2760.                 # at least one of its dimensions is not fixed
  2761.                 # (=0), so this printer/driver combo must
  2762.                 # support custom page sizes
  2763.                 $hascustompagesize = 1;
  2764.                 # We do not add this size to the PPD file
  2765.                 # because the Adobe standard foresees a
  2766.                 # special code block in the header of the
  2767.                 # PPD file to be inserted when a custom
  2768.                 # page size is requested.
  2769.                 next;
  2770.             }
  2771.             # Determine the unprintable margins
  2772.             # Zero margins when no margin info exists
  2773.             my ($left, $right, $top, $bottom) =
  2774.                 getmargins($dat, $width, $height, $value);
  2775.             # Insert margins in "*ImageableArea" line
  2776.             push(@imageablearea,
  2777.                  "*ImageableArea $value/$comment: " . 
  2778.                  "\"$left $bottom $right $top\"");
  2779.             push(@paperdimension,
  2780.                  "*PaperDimension $value/$comment: \"$size\"");
  2781.             }
  2782.             my $foomaticstr = "";
  2783.             # For PostScript options PostScript code must be 
  2784.             # inserted, unless they are member of a composite
  2785.             # option AND they are set to the "Controlled by
  2786.             # '<Composite>'" choice (driverval is "\x01")
  2787.             if (($arg->{'style'} eq 'G') &&
  2788.             ($v->{'driverval'} ne "\x01")) {
  2789.             # Ghostscript argument; offer up ps for
  2790.             # insertion
  2791.             my $sprintfcmd = $cmd;
  2792.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  2793.             $psstr = sprintf($sprintfcmd, 
  2794.                      (defined($v->{'driverval'})
  2795.                       ? $v->{'driverval'}
  2796.                       : $v->{'value'}));
  2797.             } else {
  2798.             # Option setting directive for Foomatic filter
  2799.             # 4 "%" because of the "sprintf" applied to it
  2800.             # In the end stay 2 "%" to have a PostScript 
  2801.             # comment
  2802.             $psstr = sprintf
  2803.                 ("%%%% FoomaticRIPOptionSetting: %s=%s",
  2804.                  $name, $v->{'value'});
  2805.             if ($v->{'driverval'} eq "\x01") {
  2806.                 # Only set the $foomaticstr when the selected
  2807.                 # choice is not the "Controlled by
  2808.                 # '<Composite>'" of a member of a collective
  2809.                 # option. Otherwise leave it out and let
  2810.                 # the value in the "FoomaticRIPOptionSetting"
  2811.                 # comment be "@<Composite>".
  2812.                 $psstr =~ s/=From/=\@/;
  2813.                 $foomaticstr = "";
  2814.             } else {
  2815.                 my $header = sprintf
  2816.                 ("*FoomaticRIPOptionSetting %s=%s",
  2817.                  $name, $v->{'value'});
  2818.                 my $sprintfcmd = $cmd;
  2819.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  2820.                 my $cmdval =
  2821.                 sprintf($sprintfcmd,
  2822.                     (defined($v->{'driverval'})
  2823.                      ? $v->{'driverval'}
  2824.                      : $v->{'value'}));
  2825.                 $foomaticstr = ripdirective($header, $cmdval) . 
  2826.                 "\n";
  2827.             }
  2828.             }
  2829.             # Make sure that the longname/translation exists
  2830.             if (!$v->{'comment'}) {
  2831.             if ($type !~ /^(string|password)$/) {
  2832.                 $v->{'comment'} = longname($v->{'value'});
  2833.             } else {
  2834.                 $v->{'comment'} = $v->{'value'};
  2835.             }
  2836.             }
  2837.             # Code supposed to be inserted into the PostScript
  2838.             # data when this choice is selected.
  2839.             push(@optionblob,
  2840.              sprintf("*%s %s/%s: \"%s\"\n", 
  2841.                  $name, $v->{'value'},
  2842.                  cutguiname($v->{'comment'}, $shortgui),
  2843.                  $psstr));
  2844.             # PostScript code is more than one line? Let an "*End"
  2845.             # line follow
  2846.             if ($psstr =~ /\n/s) {
  2847.             push(@optionblob, "*End\n");
  2848.             }
  2849.             # If we have a command line or JCL option, insert the
  2850.             # code here. For security reasons command line snippets
  2851.             # cannot be inserted into the "official" choice entry,
  2852.             # otherwise the appropriate RIP filter could execute
  2853.             # arbitrary code.
  2854.             push(@optionblob, $foomaticstr);
  2855.             # Stuff to insert into command line/job is more than one
  2856.             # line? Let an "*End" line follow
  2857.             if ($foomaticstr =~ /\n.*\n/s) {
  2858.             push(@optionblob, "*End\n");
  2859.             }
  2860.             # In modern PostScript interpreters "PageRegion" 
  2861.             # and "PageSize" are the same option, so we fill 
  2862.             # in the "PageRegion" the same
  2863.             # way as the "PageSize" choices.
  2864.             if ($name eq "PageSize") {
  2865.             push(@pageregion,
  2866.                  sprintf("*PageRegion %s/%s: \"%s\"", 
  2867.                      $v->{'value'}, $v->{'comment'},
  2868.                      $psstr));
  2869.             if ($psstr =~ /\n/s) {
  2870.                 push(@pageregion, "*End");
  2871.             }
  2872.             }
  2873.         }
  2874.         
  2875.         push(@optionblob,
  2876.              sprintf("*CloseUI: *%s\n", $name));
  2877.  
  2878.         if ($name eq "PageSize") {
  2879.             # Close the PageRegion, ImageableArea, and 
  2880.             # PaperDimension clauses
  2881.             push(@pageregion,
  2882.              "*CloseUI: *PageRegion");
  2883.  
  2884.             my $paperdim = join("\n", 
  2885.                     ("", @pageregion, "", 
  2886.                      @imageablearea, "",
  2887.                      @paperdimension, ""));
  2888.             push (@optionblob, $paperdim);
  2889.  
  2890.             # Make the header entries for a custom page size
  2891.             if ($hascustompagesize) {
  2892.             my $maxpaperdim = 
  2893.                 ($maxpageheight > $maxpagewidth ?
  2894.                  $maxpageheight : $maxpagewidth);
  2895.             # PostScript code from the example 6 in section 6.3
  2896.             # of Adobe's PPD V4.3 specification
  2897.             # http://partners.adobe.com/asn/developer/pdfs/tn/5003.PPD_Spec_v4.3.pdf
  2898.             # If the page size is an option for the command line
  2899.             # of GhostScript, let the values which where put
  2900.             # on the stack being popped and inserta comment
  2901.             # to advise the filter
  2902.             
  2903.             my $pscode;
  2904.             my $foomaticstr = "";
  2905.             if ($arg->{'style'} eq 'G') {
  2906.                 $pscode = "pop pop pop
  2907. <</PageSize [ 5 -2 roll ] /ImagingBBox null>>setpagedevice";
  2908.             } else {
  2909.                 my $a = $arg->{'vals_byname'}{'Custom'};
  2910.                 my $header = sprintf
  2911.                 ("*FoomaticRIPOptionSetting %s=%s",
  2912.                  $name, $a->{'value'});
  2913.                 my $sprintfcmd = $cmd;
  2914.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  2915.                 my $cmdval =
  2916.                 sprintf($sprintfcmd,
  2917.                     (defined($a->{'driverval'})
  2918.                      ? $a->{'driverval'}
  2919.                      : $a->{'value'}));
  2920.                 $foomaticstr =
  2921.                 ripdirective($header, $cmdval) . "\n";
  2922.                 # Stuff to insert into command line/job is more
  2923.                 # than one line? Let an "*End" line follow
  2924.                 if ($foomaticstr =~ /\n.*\n/s) {
  2925.                 $foomaticstr .= "*End\n";
  2926.                 }
  2927.                 $pscode = "pop pop pop pop pop
  2928. %% FoomaticRIPOptionSetting: $name=Custom";
  2929.             }
  2930.             my ($left, $right, $top, $bottom) =
  2931.                 getmargins($dat, 0, 0, 'Custom');
  2932.             my $custompagesizeheader = 
  2933. "*HWMargins: $left $bottom $right $top
  2934. *VariablePaperSize: True
  2935. *MaxMediaWidth: $maxpaperdim
  2936. *MaxMediaHeight: $maxpaperdim
  2937. *NonUIOrderDependency: $order $section *CustomPageSize
  2938. *CustomPageSize True: \"$pscode\"
  2939. *End
  2940. ${foomaticstr}*ParamCustomPageSize Width: 1 points 36 $maxpagewidth
  2941. *ParamCustomPageSize Height: 2 points 36 $maxpageheight
  2942. *ParamCustomPageSize Orientation: 3 int 0 0
  2943. *ParamCustomPageSize WidthOffset: 4 points 0 0
  2944. *ParamCustomPageSize HeightOffset: 5 points 0 0
  2945.  
  2946. ";
  2947.             
  2948.             unshift (@optionblob, $custompagesizeheader);
  2949.             } else {
  2950.             unshift (@optionblob,
  2951.                  "*VariablePaperSize: False\n\n");
  2952.             }
  2953.         }
  2954.         } elsif (((1 == scalar(@{$arg->{'vals'}})) &&
  2955.               ($arg->{'style'} ne 'G')) ||
  2956.              ($arg->{'hidden'})) {
  2957.         # non-PostScript enumerated choice option with one single 
  2958.         # choice or hidden member option of forced composite
  2959.         # option
  2960.  
  2961.         # Insert line with option properties
  2962.         my $foomaticstrs = '';
  2963.         for my $v (@{$arg->{'vals'}}) {
  2964.             my $header = sprintf
  2965.             ("*FoomaticRIPOptionSetting %s=%s",
  2966.              $name, $v->{'value'});
  2967.             my $cmdval = '';
  2968.             # For the "From<Composite>" setting the command line
  2969.             # value is not made use of, so leave it blank then.
  2970.             if ($v->{'driverval'} ne "\x01") {
  2971.             my $sprintfcmd = $cmd;
  2972.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  2973.             $cmdval =
  2974.                 sprintf($sprintfcmd,
  2975.                     (defined($v->{'driverval'})
  2976.                      ? $v->{'driverval'}
  2977.                      : $v->{'value'}));
  2978.             }
  2979.             my $foomaticstr = ripdirective($header, $cmdval) . "\n";
  2980.             # Stuff to insert into command line/job is more
  2981.             # than one line? Let an "*End" line follow
  2982.             if ($foomaticstr =~ /\n.*\n/s) {
  2983.             $foomaticstr .= "*End\n";
  2984.             }
  2985.             $foomaticstrs .= $foomaticstr;
  2986.         }
  2987.         push(@optionblob, sprintf
  2988.              ("\n*FoomaticRIPOption %s: %s %s %s %s\n",
  2989.               $name, $type, $optstyle, $spot, $order),
  2990.              $stringextralines1, $foomaticstrs);
  2991.         }
  2992.     } elsif ($type eq 'bool') {
  2993.         my $name = $arg->{'name'};
  2994.         my $namef = $arg->{'name_false'};
  2995.         my $defstr = ($default ? 'True' : 'False');
  2996.         if (!defined($default)) { 
  2997.         $defstr = 'Unknown';
  2998.         }
  2999.         my $psstr = "";
  3000.         my $psstrf = "";
  3001.  
  3002.         push(@optionblob,
  3003.          sprintf("\n*OpenUI *%s/%s: Boolean\n", $name, 
  3004.              cutguiname($com, $shortgui)));
  3005.  
  3006.         if ($arg->{'style'} eq 'G') {
  3007.         # Ghostscript argument
  3008.         $psstr = $cmd;
  3009.         } else {
  3010.         # Option setting directive for Foomatic filter
  3011.         # 4 "%" because of the "sprintf" applied to it
  3012.         # In the end stay 2 "%" to have a PostScript comment
  3013.         my $header = sprintf
  3014.             ("%%%% FoomaticRIPOptionSetting: %s", $name);
  3015.         $psstr = "$header=True";
  3016.         $psstrf = "$header=False";
  3017.         $header = sprintf
  3018.             ("*FoomaticRIPOptionSetting %s", $name);
  3019.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3020.         # For non-PostScript options insert line with option
  3021.         # properties
  3022.         push(@optionblob, sprintf
  3023.              ("*FoomaticRIPOption %s: bool %s %s\n",
  3024.               $name, $optstyle, $spot).
  3025.              $foomaticstr,
  3026.              ($foomaticstr =~ /\n.*\n/s ? "*End\n" : ""));
  3027.         }
  3028.  
  3029.         push(@optionblob,
  3030.          sprintf("*OrderDependency: %s AnySetup *%s\n", 
  3031.              $order, $name),
  3032.          sprintf("*Default%s: $defstr\n", $name),
  3033.          sprintf("*%s True/%s: \"%s\"\n", $name, 
  3034.              cutguiname($name, $shortgui), $psstr),
  3035.          ($psstr =~ /\n/s ? "*End\n" : ""),
  3036.          sprintf("*%s False/%s: \"%s\"\n", $name,
  3037.              cutguiname($namef, $shortgui), $psstrf),
  3038.          ($psstrf =~ /\n/s ? "*End\n" : ""),
  3039.          sprintf("*CloseUI: *%s\n", $name));
  3040.         
  3041.     } elsif ($type eq 'int') {
  3042.  
  3043.         # Real numerical options do not exist in the Adobe
  3044.         # specification for PPD files. So we map the numerical
  3045.         # options to enumerated options offering the minimum, the
  3046.         # maximum, the default, and some values inbetween to the
  3047.         # user.
  3048.  
  3049.         my $min = $arg->{'min'};
  3050.         my $max = $arg->{'max'};
  3051.         my $second = $min + 1;
  3052.         my $stepsize = 1;
  3053.         if (($max - $min > 100) && ($name ne "Copies")) {
  3054.         # We don't want to have more than 100 values, but when the
  3055.         # difference between min and max is more than 100 we should
  3056.         # have at least 10 steps.
  3057.         my $mindesiredvalues = 10;
  3058.         my $maxdesiredvalues = 100;
  3059.         # Find the order of magnitude of the value range
  3060.         my $rangesize = $max - $min;
  3061.         my $log10 = log(10.0);
  3062.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  3063.         # Now find the step size
  3064.         my $trialstepsize = 10 ** $rangeom;
  3065.         my $numvalues = 0;
  3066.         while (($numvalues <= $mindesiredvalues) &&
  3067.                ($trialstepsize > 2)) {
  3068.             $trialstepsize /= 10;
  3069.             $numvalues = $rangesize/$trialstepsize;
  3070.         }
  3071.         # Try to find a finer stepping
  3072.         $stepsize = $trialstepsize;
  3073.         $trialstepsize = $stepsize / 2;
  3074.         $numvalues = $rangesize/$trialstepsize;
  3075.         if ($numvalues <= $maxdesiredvalues) {
  3076.             if ($stepsize > 20) { 
  3077.             $trialstepsize = $stepsize / 4;
  3078.             $numvalues = $rangesize/$trialstepsize;
  3079.             }
  3080.             if ($numvalues <= $maxdesiredvalues) {
  3081.             $trialstepsize = $stepsize / 5;
  3082.             $numvalues = $rangesize/$trialstepsize;
  3083.             }
  3084.             if ($numvalues <= $maxdesiredvalues) {
  3085.             $stepsize = $trialstepsize;
  3086.             } else {
  3087.             $stepsize /= 2;
  3088.             }
  3089.         }
  3090.         $numvalues = $rangesize/$stepsize;
  3091.         # We have the step size. Now we must find an appropriate
  3092.         # second value for the value list, so that it contains
  3093.         # the integer multiples of 10, 100, 1000, ...
  3094.         $second = $stepsize * POSIX::ceil($min / $stepsize);
  3095.         if ($second <= $min) {$second += $stepsize};
  3096.         }
  3097.         # Generate the choice list
  3098.         my @choicelist;
  3099.         push (@choicelist, $min);
  3100.         if (($default < $second) && ($default > $min)) {
  3101.         push (@choicelist, $default);
  3102.         }
  3103.         my $item = $second;
  3104.         while ($item < $max) {
  3105.         push (@choicelist, $item);
  3106.         if (($default < $item + $stepsize) && ($default > $item) &&
  3107.             ($default < $max)) {
  3108.             push (@choicelist, $default);
  3109.         }
  3110.         $item += $stepsize;
  3111.         }
  3112.         push (@choicelist, $max);
  3113.  
  3114.             # Add the option
  3115.  
  3116.         # Skip zero or one choice arguments
  3117.         if (1 < scalar(@choicelist)) {
  3118.         push(@optionblob,
  3119.              sprintf("\n*OpenUI *%s/%s: PickOne\n", $name,
  3120.                  cutguiname($com, $shortgui)));
  3121.  
  3122.         # Insert lines with the special properties of a
  3123.         # numerical option. Do this also for PostScript options
  3124.         # because numerical options are not supported by the PPD
  3125.         # file syntax. This way the info about this option being
  3126.         # a numerical one does not get lost
  3127.  
  3128.         push(@optionblob, sprintf
  3129.              ("*FoomaticRIPOption %s: int %s %s\n",
  3130.               $name, $optstyle, $spot));
  3131.  
  3132.         my $header = sprintf
  3133.             ("*FoomaticRIPOptionPrototype %s",
  3134.              $name);
  3135.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3136.         push(@optionblob, $foomaticstr);
  3137.         # Stuff to insert into command line/job is more than one
  3138.         # line? Let an "*End" line follow
  3139.         if ($foomaticstr =~ /\n.*\n/s) {
  3140.             push(@optionblob, "*End\n");
  3141.         }
  3142.  
  3143.         push(@optionblob, sprintf
  3144.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  3145.               $name, $arg->{'min'}, $arg->{'max'}));
  3146.  
  3147.         push(@optionblob,
  3148.              sprintf("*OrderDependency: %s AnySetup *%s\n", 
  3149.                  $order, $name),
  3150.              sprintf("*Default%s: %s\n", 
  3151.                  $name,
  3152.                  (defined($default) ? $default : 'Unknown')),
  3153.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  3154.                  $name,
  3155.                  (defined($default) ? $default : 'Unknown')));
  3156.         if (!defined($default)) {
  3157.             my $whr = sprintf("%s %s driver %s",
  3158.                       $dat->{'make'},
  3159.                       $dat->{'model'},
  3160.                       $dat->{'driver'});
  3161.             warn "undefined default for $idx/$name on a $whr\n";
  3162.         }
  3163.         
  3164.         for my $v (@choicelist) {
  3165.             my $psstr = "";
  3166.             
  3167.             if ($arg->{'style'} eq 'G') {
  3168.             # Ghostscript argument; offer up ps for insertion
  3169.             my $sprintfcmd = $cmd;
  3170.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  3171.             $psstr = sprintf($sprintfcmd, $v);
  3172.             } else {
  3173.             # Option setting directive for Foomatic filter
  3174.             # 4 "%" because of the "sprintf" applied to it
  3175.             # In the end stay 2 "%" to have a PostScript comment
  3176.             $psstr = sprintf
  3177.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  3178.                   $name, $v);
  3179.             }
  3180.             push(@optionblob,
  3181.              sprintf("*%s %s/%s: \"%s\"\n", 
  3182.                  $name, $v, 
  3183.                  cutguiname($v, $shortgui), $psstr));
  3184.             # PostScript code is more than one line? Let an "*End"
  3185.             # line follow
  3186.             if ($psstr =~ /\n/s) {
  3187.             push(@optionblob, "*End\n");
  3188.             }
  3189.         }
  3190.         
  3191.         push(@optionblob,
  3192.              sprintf("*CloseUI: *%s\n", $name));
  3193.         }
  3194.         
  3195.     } elsif ($type eq 'float') {
  3196.         
  3197.         # Real numerical options do not exist in the Adobe
  3198.         # specification for PPD files. So we map the numerical
  3199.         # options to enumerated options offering the minimum, the
  3200.         # maximum, the default, and some values inbetween to the
  3201.         # user.
  3202.  
  3203.         my $min = $arg->{'min'};
  3204.         my $max = $arg->{'max'};
  3205.         # We don't want to have more than 500 values or less than 50
  3206.         # values.
  3207.         my $mindesiredvalues = 10;
  3208.         my $maxdesiredvalues = 100;
  3209.         # Find the order of magnitude of the value range
  3210.         my $rangesize = $max - $min;
  3211.         my $log10 = log(10.0);
  3212.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  3213.         # Now find the step size
  3214.         my $trialstepsize = 10 ** $rangeom;
  3215.         my $stepom = $rangeom; # Order of magnitude of stepsize,
  3216.                                # needed for determining necessary number
  3217.                                # of digits
  3218.         my $numvalues = 0;
  3219.         while ($numvalues <= $mindesiredvalues) {
  3220.         $trialstepsize /= 10;
  3221.         $stepom -= 1;
  3222.         $numvalues = $rangesize/$trialstepsize;
  3223.         }
  3224.         # Try to find a finer stepping
  3225.         my $stepsize = $trialstepsize;
  3226.         my $stepsizeorig = $stepsize;
  3227.         $trialstepsize = $stepsizeorig / 2;
  3228.         $numvalues = $rangesize/$trialstepsize;
  3229.         if ($numvalues <= $maxdesiredvalues) {
  3230.         $stepsize = $trialstepsize;
  3231.         $trialstepsize = $stepsizeorig / 4;
  3232.         $numvalues = $rangesize/$trialstepsize;
  3233.         if ($numvalues <= $maxdesiredvalues) {
  3234.             $stepsize = $trialstepsize;
  3235.             $trialstepsize = $stepsizeorig / 5;
  3236.             $numvalues = $rangesize/$trialstepsize;
  3237.             if ($numvalues <= $maxdesiredvalues) {
  3238.             $stepsize = $trialstepsize;
  3239.             }
  3240.         }
  3241.         }
  3242.         $numvalues = $rangesize/$stepsize;
  3243.         if ($stepsize < $stepsizeorig * 0.9) {$stepom -= 1;}
  3244.         # Determine number of digits after the decimal point for
  3245.         # formatting the output values.
  3246.         my $digits = 0;
  3247.         if ($stepom < 0) {
  3248.         $digits = - $stepom;
  3249.         }
  3250.         # We have the step size. Now we must find an appropriate
  3251.         # second value for the value list, so that it contains
  3252.         # the integer multiples of 10, 100, 1000, ...
  3253.         my $second = $stepsize * POSIX::ceil($min / $stepsize);
  3254.         if ($second <= $min) {$second += $stepsize};
  3255.         # Generate the choice list
  3256.         my @choicelist;
  3257.         my $choicestr =  sprintf("%.${digits}f", $min);
  3258.         push (@choicelist, $choicestr);
  3259.         if (($default < $second) && ($default > $min)) {
  3260.         $choicestr =  sprintf("%.${digits}f", $default);
  3261.         # Prevent values from entering twice because of rounding
  3262.         # inacuracy
  3263.         if ($choicestr ne $choicelist[$#choicelist]) {
  3264.             push (@choicelist, $choicestr);
  3265.         }
  3266.         }
  3267.         my $item = $second;
  3268.         my $i = 0;
  3269.         while ($item < $max) {
  3270.         $choicestr =  sprintf("%.${digits}f", $item);
  3271.         # Prevent values from entering twice because of rounding
  3272.         # inacuracy
  3273.         if ($choicestr ne $choicelist[$#choicelist]) {
  3274.             push (@choicelist, $choicestr);
  3275.         }
  3276.         if (($default < $item + $stepsize) && ($default > $item) &&
  3277.             ($default < $max)) {
  3278.             $choicestr =  sprintf("%.${digits}f", $default);
  3279.             # Prevent values from entering twice because of rounding
  3280.             # inacuracy
  3281.             if ($choicestr ne $choicelist[$#choicelist]) {
  3282.             push (@choicelist, $choicestr);
  3283.             }
  3284.         }
  3285.         $i += 1;
  3286.         $item = $second + $i * $stepsize;
  3287.         }
  3288.         $choicestr =  sprintf("%.${digits}f", $max);
  3289.         # Prevent values from entering twice because of rounding
  3290.         # inacuracy
  3291.         if ($choicestr ne $choicelist[$#choicelist]) {
  3292.         push (@choicelist, $choicestr);
  3293.         }
  3294.  
  3295.             # Add the option
  3296.  
  3297.         # Skip zero or one choice arguments
  3298.         if (1 < scalar(@choicelist)) {
  3299.         push(@optionblob,
  3300.              sprintf("\n*OpenUI *%s/%s: PickOne\n", $name, 
  3301.                  cutguiname($com, $shortgui)));
  3302.  
  3303.         # Insert lines with the special properties of a
  3304.         # numerical option. Do this also for PostScript options
  3305.         # because numerical options are not supported by the PPD
  3306.         # file syntax. This way the info about this option being
  3307.         # a numerical one does not get lost
  3308.  
  3309.         push(@optionblob, sprintf
  3310.              ("*FoomaticRIPOption %s: float %s %s\n",
  3311.               $name, $optstyle, $spot));
  3312.  
  3313.         my $header = sprintf
  3314.             ("*FoomaticRIPOptionPrototype %s",
  3315.              $name);
  3316.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3317.         push(@optionblob, $foomaticstr);
  3318.         # Stuff to insert into command line/job is more than one
  3319.         # line? Let an "*End" line follow
  3320.         if ($foomaticstr =~ /\n.*\n/s) {
  3321.             push(@optionblob, "*End\n");
  3322.         }
  3323.  
  3324.         push(@optionblob, sprintf
  3325.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  3326.               $name, $arg->{'min'}, $arg->{'max'}));
  3327.  
  3328.         push(@optionblob,
  3329.              sprintf("*OrderDependency: %s AnySetup *%s\n", 
  3330.                  $order, $name),
  3331.              sprintf("*Default%s: %s\n", 
  3332.                  $name,
  3333.                  (defined($default) ? 
  3334.                   sprintf("%.${digits}f", $default) : 'Unknown')),
  3335.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  3336.                  $name,
  3337.                  (defined($default) ? 
  3338.                   sprintf("%.${digits}f", $default) : 'Unknown')));
  3339.         if (!defined($default)) {
  3340.             my $whr = sprintf("%s %s driver %s",
  3341.                       $dat->{'make'},
  3342.                       $dat->{'model'},
  3343.                       $dat->{'driver'});
  3344.             warn "undefined default for $idx/$name on a $whr\n";
  3345.         }
  3346.  
  3347.         for my $v (@choicelist) {
  3348.             my $psstr = "";
  3349.             if ($arg->{'style'} eq 'G') {
  3350.             # Ghostscript argument; offer up ps for insertion
  3351.             my $sprintfcmd = $cmd;
  3352.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  3353.             $psstr = sprintf($sprintfcmd, $v);
  3354.             } else {
  3355.             # Option setting directive for Foomatic filter
  3356.             # 4 "%" because of the "sprintf" applied to it
  3357.             # In the end stay 2 "%" to have a PostScript comment
  3358.             $psstr = sprintf
  3359.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  3360.                   $name, $v);
  3361.             }
  3362.             push(@optionblob,
  3363.              sprintf("*%s %s/%s: \"%s\"\n", 
  3364.                  $name, $v, 
  3365.                  cutguiname($v, $shortgui), $psstr));
  3366.             # PostScript code is more than one line? Let an "*End"
  3367.             # line follow
  3368.             if ($psstr =~ /\n/s) {
  3369.             push(@optionblob, "*End\n");
  3370.             }
  3371.         }
  3372.         
  3373.         push(@optionblob,
  3374.              sprintf("*CloseUI: *%s\n", $name));
  3375.         }
  3376.         }
  3377.     }
  3378.  
  3379.     # Close the option groups which are still open
  3380.     for (my $i = $#groupstack; $i >= 0; $i--) {
  3381.     push(@optionblob,
  3382.          sprintf("\n*Close%sGroup: %s\n",
  3383.              ($i > 0 ? "Sub" : ""), $groupstack[$i])
  3384.          );
  3385.     pop(@groupstack);
  3386.     }
  3387.  
  3388.     if (! $dat->{'args_byname'}{'PageSize'} ) {
  3389.     
  3390.     # This is a problem, since CUPS segfaults on PPD files without
  3391.     # a default PageSize set.  Indeed, the PPD spec requires a
  3392.     # PageSize clause.
  3393.     
  3394.     # GhostScript does not understand "/PageRegion[...]", therefore
  3395.     # we use "/PageSize[...]" in the "*PageRegion" option here, in
  3396.     # addition, for most modern PostScript interpreters "PageRegion"
  3397.     # is the same as "PageSize".
  3398.  
  3399.     push(@optionblob, <<EOFPGSZ);
  3400.  
  3401. *% This is fake. We have no information on how to
  3402. *% set the pagesize for this driver in the database. To
  3403. *% prevent PPD users from blowing up, we must provide a
  3404. *% default pagesize value.
  3405.  
  3406. *OpenUI *PageSize/Media Size: PickOne
  3407. *OrderDependency: 10 AnySetup *PageSize
  3408. *DefaultPageSize: Letter
  3409. *PageSize Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  3410. *PageSize Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  3411. *PageSize A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  3412. *CloseUI: *PageSize
  3413.  
  3414. *OpenUI *PageRegion: PickOne
  3415. *OrderDependency: 10 AnySetup *PageRegion
  3416. *DefaultPageRegion: Letter
  3417. *PageRegion Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  3418. *PageRegion Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  3419. *PageRegion A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  3420. *CloseUI: *PageRegion
  3421.  
  3422. *DefaultImageableArea: Letter
  3423. *ImageableArea Letter/Letter:    "0 0 612 792"
  3424. *ImageableArea Legal/Legal:    "0 0 612 1008"
  3425. *ImageableArea A4/A4:    "0 0 595 842"
  3426.  
  3427. *DefaultPaperDimension: Letter
  3428. *PaperDimension Letter/Letter:    "612 792"
  3429. *PaperDimension Legal/Legal:    "612 1008"
  3430. *PaperDimension A4/A4:    "595 842"
  3431.  
  3432. EOFPGSZ
  3433.     }
  3434.  
  3435.     my @others;
  3436.  
  3437.     my $headcomment =
  3438. "*% For information on using this, and to obtain the required backend
  3439. *% script, consult http://www.linuxprinting.org/
  3440. *%
  3441. *% This file is published under the GNU General Public License
  3442. *%
  3443. *% PPD-O-MATIC (3.0.0 or newer) generated this PPD file. It is for use with 
  3444. *% all programs and environments which use PPD files for dealing with
  3445. *% printer capability information. The printer must be configured with the
  3446. *% \"foomatic-rip\" backend filter script of Foomatic 3.0.0 or newer. This 
  3447. *% file and \"foomatic-rip\" work together to support PPD-controlled printer
  3448. *% driver option access with arbitrary free software printer drivers and
  3449. *% printing spoolers.
  3450. *%
  3451. *% To save this file on your disk, wait until the download has completed
  3452. *% (the animation of the browser logo must stop) and then use the
  3453. *% \"Save as...\" command in the \"File\" menu of your browser or in the 
  3454. *% pop-up manu when you click on this document with the right mouse button.
  3455. *% DO NOT cut and paste this file into an editor with your mouse. This can
  3456. *% introduce additional line breaks which lead to unexpected results.";
  3457.  
  3458.     my $postpipe = "";
  3459.     if ($dat->{'postpipe'}) {
  3460.     my $header = "*FoomaticRIPPostPipe";
  3461.     my $code = $dat->{'postpipe'};
  3462.     $postpipe = ripdirective($header, $code) . "\n";
  3463.     if ($postpipe =~ /\n.*\n/s) {
  3464.         $postpipe .= "*End\n";
  3465.     }
  3466.     }
  3467.     my $opts = join('',@optionblob);
  3468.     my $otherstuff = join('',@others);
  3469.     my $pcfilename;
  3470.     if (($dat->{'pcmodel'}) && ($dat->{'pcdriver'})) {
  3471.     $pcfilename = uc("$dat->{'pcmodel'}$dat->{'pcdriver'}");
  3472.     } else {
  3473.     my $driver = $dat->{'driver'};
  3474.     $driver =~ m!(^(.{1,8}))!;
  3475.     $pcfilename = uc($1);
  3476.     }
  3477.     $pcfilename = 'FOOMATIC' if !defined($pcfilename);
  3478.     my $model = $dat->{'model'};
  3479.     my $make = $dat->{'make'};
  3480.     my ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  3481.     $drivername,$nickname,$modelname) =
  3482.         getppdheaderdata($dat, $dat->{'driver'}, $dat->{'recdriver'});
  3483.     if ($ieee1284) {
  3484.     #$ieee1284 =~ s/;(.)/;\n  $1/gs;
  3485.     #$ieee1284 = "*1284DeviceID: \"\n  " . $ieee1284 . "\n\"\n*End";
  3486.     #$ieee1284 =~ s/;(.)/;\n  $1/gs;
  3487.     $ieee1284 = "*1284DeviceID: \"" . $ieee1284 . "\"";
  3488.     }
  3489.     # Do not use "," or "+" in the *ShortNickName to make the Windows
  3490.     # PostScript drivers happy
  3491.     my $shortnickname = "$make $model $drivername";
  3492.     if (length($shortnickname) > 31) {
  3493.     # ShortNickName too long? Shorten it.
  3494.     my %parts;
  3495.     $parts{'make'} = $make;
  3496.     $parts{'model'} = $model;
  3497.     $parts{'driver'} = $drivername;
  3498.     # Go through the three components, begin with model name, then
  3499.     # make and then driver
  3500.     for my $part (qw/model make driver/) {
  3501.         # Split the component into words, cutting always at the right edge
  3502.         # of the word. Cut also at a capital in the middle of the word
  3503.         # (ex: "S" in "PostScript").
  3504.         my @words = split(/(?<=[a-zA-Z])(?![a-zA-Z])|(?<=[a-z])(?=[A-Z])/,
  3505.                   $parts{$part});
  3506.         # Go through all words
  3507.         for (@words) {
  3508.         # Do not abbreviate words of less than 4 letters
  3509.         next if ($_ !~ /[a-zA-Z]{4,}$/);
  3510.         # How many letters did we chop off
  3511.         my $abbreviated = 0;
  3512.             while (1) {
  3513.             # Remove the last letter
  3514.             chop;
  3515.             $abbreviated ++;
  3516.             # Build the shortened component ...
  3517.             $parts{$part} = join('', @words);
  3518.             # ... and the ShortNickName
  3519.             $shortnickname =
  3520.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  3521.             # Stop if the ShostNickName has 30 characters or less
  3522.             # (we have still to add the abbreviation point), if there
  3523.             # is only one letter left, or if the manufacturer name
  3524.             # is reduced to three characters. Do not accept an
  3525.             # abbreviation of one character, as, taking the
  3526.             # abbreviation point into account, it does not save
  3527.             # a character.
  3528.             last if (((length($shortnickname) <= 30) &&
  3529.                   ($abbreviated != 1)) ||
  3530.                  ($_ !~ /[a-zA-Z]{2,}$/) ||
  3531.                  ((length($parts{'make'}) <= 3) &&
  3532.                   ($abbreviated != 1)));
  3533.         }
  3534.         #Abbreviation point
  3535.         if ($abbreviated) {
  3536.             $_ .= '.';
  3537.         }
  3538.         $parts{$part} = join('', @words);
  3539.         $shortnickname =
  3540.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  3541.         last if (length($shortnickname) <= 31);
  3542.         }
  3543.         last if (length($shortnickname) <= 31);
  3544.     }
  3545.     while ((length($shortnickname) > 31) &&
  3546.            (length($parts{'model'}) > 3)) {
  3547.         # ShortNickName too long? Remove last words from model name.
  3548.         $parts{'model'} =~
  3549.         s/(?<=[a-zA-Z0-9])[^a-zA-Z0-9]+[a-zA-Z0-9]*$//;
  3550.         $shortnickname =
  3551.         "$parts{'make'} $parts{'model'}, $parts{'driver'}";
  3552.     }
  3553.     if (length($shortnickname) > 31) {
  3554.         # If nothing else helps ...
  3555.         $shortnickname = substr($shortnickname, 0, 31);
  3556.     }
  3557.     }
  3558.  
  3559.     my $color;
  3560.     if ($dat->{'color'}) {
  3561.     $color = "*ColorDevice:    True\n*DefaultColorSpace: RGB";
  3562.     } else {
  3563.     $color = "*ColorDevice:    False\n*DefaultColorSpace: Gray";
  3564.     }
  3565.  
  3566.     # Clean up "<ppdentry>"s
  3567.     foreach my $type ('printerppdentry', 'driverppdentry', 'comboppdentry'){
  3568.     if (defined($dat->{$type})) {
  3569.         $dat->{$type} =~ s/^\s+//gm;
  3570.         $dat->{$type} =~ s/\s+$//gm;
  3571.         $dat->{$type} =~ s/^\n+//gs;
  3572.         $dat->{$type} =~ s/\n*$/\n/gs;
  3573.     } else {
  3574.         $dat->{$type} = '';
  3575.     }
  3576.     }
  3577.     my $extralines = $dat->{'printerppdentry'} .
  3578.                  $dat->{'driverppdentry'} .
  3579.              $dat->{'comboppdentry'};
  3580.  
  3581.     my $tmpl = get_tmpl();
  3582.     $tmpl =~ s!\@\@POSTPIPE\@\@!$postpipe!g;
  3583.     $tmpl =~ s!\@\@HEADCOMMENT\@\@!$headcomment!g;
  3584.     $tmpl =~ s!\@\@SAVETHISAS\@\@!$longname!g;
  3585.     $tmpl =~ s!\@\@PCFILENAME\@\@!$pcfilename!g;
  3586.     $tmpl =~ s!\@\@MANUFACTURER\@\@!$make!g;
  3587.     $tmpl =~ s!\@\@PNPMAKE\@\@!$pnpmake!g;
  3588.     $tmpl =~ s!\@\@PNPMODEL\@\@!$pnpmodel!g;
  3589.     $tmpl =~ s!\@\@MODEL\@\@!$modelname!g;
  3590.     $tmpl =~ s!\@\@NICKNAME\@\@!$nickname!g;
  3591.     $tmpl =~ s!\@\@SHORTNICKNAME\@\@!$shortnickname!g;
  3592.     $tmpl =~ s!\@\@COLOR\@\@!$color!g;
  3593.     $tmpl =~ s!\@\@IEEE1284\@\@!$ieee1284!g;
  3594.     $tmpl =~ s!\@\@OTHERSTUFF\@\@!$otherstuff!g;
  3595.     $tmpl =~ s!\@\@OPTIONS\@\@!$opts!g;
  3596.     $tmpl =~ s!\@\@EXTRALINES\@\@!$extralines!g;
  3597.     
  3598.     return ($tmpl);
  3599. }
  3600.  
  3601.  
  3602. # Utility function; returns content of a URL
  3603. sub getpage {
  3604.     my ($this, $url, $dontdie) = @_;
  3605.  
  3606.     my $failed = 0;
  3607.     my $page = undef;
  3608.     # Try it first to retrieve the page with the "wget" shell command
  3609.     if (-x $sysdeps->{'wget'}) {
  3610.     if (open PAGE, "$sysdeps->{'wget'} $url -O - 2>/dev/null |") {
  3611.         $page = join('', <PAGE>);
  3612.         close PAGE;
  3613.     } else {
  3614.         $failed = 1;
  3615.     }
  3616.     # Then try to retrieve the page with the "curl" shell command
  3617.     } elsif (-x $sysdeps->{'curl'}) {
  3618.     if (open PAGE, "$sysdeps->{'curl'} $url -o - 2>/dev/null |") {
  3619.         $page = join('', <PAGE>);
  3620.         close PAGE;
  3621.     } else {
  3622.         $failed = 1;
  3623.     }
  3624.     } else {
  3625.     warn("WARNING: No tool for downloading web content found, please install either\n\"wget\" or \"curl\"! The result you got may be incorrect!\n");
  3626.     }
  3627.  
  3628.     if ((!$page) || ($failed)) {
  3629.     if ($dontdie) {
  3630.         return undef;
  3631.     } else {
  3632.         die ("http error: " . $url . "\n");
  3633.     }
  3634.     }
  3635.  
  3636.     return $page;
  3637. }
  3638.  
  3639. # Determine the margins as needed by "*ImageableArea"
  3640. sub getmarginsformarginrecord {
  3641.     my ($margins, $width, $height, $pagesize) = @_;
  3642.     if (!defined($margins)) {
  3643.     # No margins defined? Return zero margins
  3644.     return (0, $width, $height, 0);
  3645.     }
  3646.     # Defaults
  3647.     my $unit = 'pt';
  3648.     my $absolute = 0;
  3649.     my ($left, $right, $top, $bottom) = (0, $width, $height, 0);
  3650.     # Check the general margins and then the particular paper size
  3651.     for my $i ('_general', $pagesize) {
  3652.     # Skip a section if it is not defined
  3653.     next if (!defined($margins->{$i}));
  3654.     # Determine the factor to calculate the margin in points (pt)
  3655.     $unit = (defined($margins->{$i}{'unit'}) ?
  3656.          $margins->{$i}{'unit'} : $unit);
  3657.     my $unitfactor = 1.0; # Default unit is points
  3658.     if ($unit =~ /^p/i) {
  3659.         $unitfactor = 1.0;
  3660.     } elsif ($unit =~ /^in/i) {
  3661.         $unitfactor = 72.0;
  3662.     } elsif ($unit =~ /^cm$/i) {
  3663.         $unitfactor = 72.0/2.54;
  3664.     } elsif ($unit =~ /^mm$/i) {
  3665.         $unitfactor = 72.0/25.4;
  3666.     } elsif ($unit =~ /^dots(\d+)dpi$/i) {
  3667.         $unitfactor = 72.0/$1;
  3668.     }
  3669.     # Convert the values to points
  3670.     ($left, $right, $top, $bottom) =
  3671.         ((defined($margins->{$i}{'left'}) ?
  3672.           $margins->{$i}{'left'} * $unitfactor : $left),
  3673.          (defined($margins->{$i}{'right'}) ?
  3674.           $margins->{$i}{'right'} * $unitfactor : $right),
  3675.          (defined($margins->{$i}{'top'}) ?
  3676.           $margins->{$i}{'top'} * $unitfactor : $top),
  3677.          (defined($margins->{$i}{'bottom'}) ?
  3678.           $margins->{$i}{'bottom'} * $unitfactor : $bottom));
  3679.     # Determine the absolute values
  3680.     $absolute = (defined($margins->{$i}{'absolute'}) ?
  3681.              $margins->{$i}{'absolute'} : $absolute);
  3682.     if (!$absolute){
  3683.         if (defined($margins->{$i}{'right'})) {
  3684.         $right = $width - $right;
  3685.         }
  3686.         if (defined($margins->{$i}{'top'})) {
  3687.         $top = $height - $top;
  3688.         }
  3689.     }
  3690.     }
  3691.     $left = sprintf("%.2f", $left) if $left =~ /\./;
  3692.     $right = sprintf("%.2f", $right) if $right =~ /\./;
  3693.     $top = sprintf("%.2f", $top) if $top =~ /\./;
  3694.     $bottom = sprintf("%.2f", $bottom) if $bottom =~ /\./;
  3695.     return ($left, $right, $top, $bottom);
  3696. }
  3697.  
  3698. sub getmargins {
  3699.     my ($dat, $width, $height, $pagesize) = @_;
  3700.     # Determine the unprintable margins
  3701.     # Zero margins when no margin info exists
  3702.     my ($left, $right, $top, $bottom) =
  3703.     (0, $width, $height, 0);
  3704.     # Margins from printer database entry
  3705.     my ($pleft, $pright, $ptop, $pbottom) =
  3706.     getmarginsformarginrecord($dat->{'printermargins'}, 
  3707.                   $width, $height, $pagesize);
  3708.     # Margins from driver database entry
  3709.     my ($dleft, $dright, $dtop, $dbottom) =
  3710.     getmarginsformarginrecord($dat->{'drivermargins'}, 
  3711.                   $width, $height, $pagesize);
  3712.     # Margins from printer/driver combo
  3713.     my ($cleft, $cright, $ctop, $cbottom) =
  3714.     getmarginsformarginrecord($dat->{'combomargins'}, 
  3715.                   $width, $height, $pagesize);
  3716.     # Left margin
  3717.     if ($pleft > $left) {$left = $pleft};
  3718.     if ($dleft > $left) {$left = $dleft};
  3719.     if ($cleft > $left) {$left = $cleft};
  3720.     # Right margin
  3721.     if ($pright < $right) {$right = $pright};
  3722.     if ($dright < $right) {$right = $dright};
  3723.     if ($cright < $right) {$right = $cright};
  3724.     # Top margin
  3725.     if ($ptop < $top) {$top = $ptop};
  3726.     if ($dtop < $top) {$top = $dtop};
  3727.     if ($ctop < $top) {$top = $ctop};
  3728.     # Bottom margin
  3729.     if ($pbottom > $bottom) {$bottom = $pbottom};
  3730.     if ($dbottom > $bottom) {$bottom = $dbottom};
  3731.     if ($cbottom > $bottom) {$bottom = $cbottom};
  3732.     # If we entered with $width == 0 and $height == 0, we mean
  3733.     # relative margins, so correct the signs
  3734.     if ($width == 0) {$right = -$right};
  3735.     if ($height == 0) {$top = -$top};
  3736.     # Clean up output
  3737.     $left =~ s/^\s*-0\s*$/0/;
  3738.     $right =~ s/^\s*-0\s*$/0/;
  3739.     $top =~ s/^\s*-0\s*$/0/;
  3740.     $bottom =~ s/^\s*-0\s*$/0/;
  3741.     # Return the results
  3742.     return ($left, $right, $top, $bottom);
  3743. }
  3744.  
  3745. # Generate a translation/longname from a shortname
  3746. sub longname {
  3747.     my $shortname = $_[0];
  3748.     # A space before every upper-case letter in the middle preceeded by
  3749.     # a lower-case one
  3750.     $shortname =~ s/([a-z])([A-Z])/$1 $2/g;
  3751.     # If there are three or more upper-case letters, assume the last as
  3752.     # the beginning of the next word, the others as an abbreviation
  3753.     $shortname =~ s/([A-Z][A-Z]+)([A-Z][a-z])/$1 $2/g;
  3754.     return $shortname;
  3755. }
  3756.  
  3757. # Prepare strings for being part of an HTML document by, converting
  3758. # "<" to "<", ">" to ">", "&" to "&", "\"" to """,
  3759. # and "'" to  "'"
  3760. sub htmlify {
  3761.     my $str = $_[0];
  3762.     $str =~ s!&!&!g;
  3763.     $str =~ s/\</\</g;
  3764.     $str =~ s/\>/\>/g;
  3765.     $str =~ s/\"/\"/g;
  3766.     $str =~ s/\'/\'/g;
  3767.     return $str;
  3768. }
  3769.  
  3770. # This splits RIP directives (PostScript comments which are
  3771. # foomatic-rip uses to build the RIP command line) into multiple lines
  3772. # of a fixed length, to avoid lines longer than 255 characters. The
  3773. # PPD specification does not allow such long lines.
  3774. sub ripdirective {
  3775.     my ($header, $content) = ($_[0], htmlify($_[1]));
  3776.     # If possible, make lines of this length
  3777.     my $maxlength = 72;
  3778.     # Header of continuation line
  3779.     my $continueheader = "";
  3780.     # Two subsequent ampersands are not possible in an htmlified string,
  3781.     # so we can use them at the line end to mark that the current line
  3782.     # continues on the next line. A newline without this is also a newline
  3783.     # in the decoded string
  3784.     my $continuelineend = "&&";
  3785.     # output string
  3786.     my $out;
  3787.     # The colon and the quote after the header must be on the line with
  3788.     # the header
  3789.     $header .= ": \"";
  3790.     # How much of the current line is left?
  3791.     my $freelength = $maxlength - length($header) -
  3792.     length($continuelineend);
  3793.     # Add the header
  3794.     if ($freelength < 0) {
  3795.     # header longer than $maxlength, don't break it
  3796.     $out = "$header$continuelineend\n$continueheader";
  3797.     $freelength = $maxlength - length($continueheader) -
  3798.         length($continuelineend);
  3799.     } else {
  3800.     $out = "$header";
  3801.     }
  3802.     $content .= "\"";
  3803.     # Go through every line of the $content
  3804.     for my $l (split ("\n", $content)) {
  3805.     while ($l) {
  3806.         # Take off $maxlength portions until the string is used up
  3807.         if (length($l) < $freelength) {
  3808.         $freelength = length($l);
  3809.         }
  3810.         my $line = substr($l, 0, $freelength, "");
  3811.         # Add the portion 
  3812.         $out .= $line;
  3813.         # Finish the line
  3814.         $freelength = $maxlength - length($continueheader) -
  3815.         length($continuelineend);
  3816.         if ($l) {
  3817.         # Line continues in next line
  3818.         $out .= "$continuelineend\n$continueheader";
  3819.         } else {
  3820.         # line ends
  3821.         $out .= "\n";
  3822.         last;
  3823.         }
  3824.     }
  3825.     }
  3826.     # Remove trailing newline
  3827.     $out = substr($out, 0, -1);
  3828.     return $out;
  3829. }
  3830.  
  3831.  
  3832. # PPD boilerplate template
  3833.  
  3834. sub get_tmpl_paperdimension {
  3835.     return <<ENDPDTEMPL;
  3836. *% Generic PaperDimension; evidently there was no normal PageSize argument
  3837.  
  3838. *DefaultPaperDimension: Letter
  3839. *PaperDimension Letter:    "612 792"
  3840. *PaperDimension Legal:    "612 1008"
  3841. *PaperDimension A4:    "595 842"
  3842. ENDPDTEMPL
  3843. }
  3844.  
  3845. sub get_tmpl {
  3846.     return <<ENDTMPL;
  3847. *PPD-Adobe: "4.3"
  3848. \@\@POSTPIPE\@\@*%
  3849. \@\@HEADCOMMENT\@\@
  3850. *%
  3851. *% You may save this file as '\@\@SAVETHISAS\@\@'
  3852. *%
  3853. *%
  3854. *FormatVersion:    "4.3"
  3855. *FileVersion:    "1.1"
  3856. *LanguageVersion: English 
  3857. *LanguageEncoding: ISOLatin1
  3858. *PCFileName:    "\@\@PCFILENAME\@\@.PPD"
  3859. *Manufacturer:    "\@\@MANUFACTURER\@\@"
  3860. *Product:    "(\@\@PNPMODEL\@\@)"
  3861. *cupsVersion:    1.0
  3862. *cupsManualCopies: True
  3863. *cupsModelNumber:  2
  3864. *cupsFilter:    "application/vnd.cups-postscript 0 foomatic-rip"
  3865. *%pprRIP:        foomatic-rip other
  3866. *ModelName:     "\@\@MODEL\@\@"
  3867. *ShortNickName: "\@\@SHORTNICKNAME\@\@"
  3868. *NickName:      "\@\@NICKNAME\@\@"
  3869. *PSVersion:    "(3010.000) 550"
  3870. *PSVersion:    "(3010.000) 651"
  3871. *PSVersion:    "(3010.000) 652"
  3872. *PSVersion:    "(3010.000) 653"
  3873. *PSVersion:    "(3010.000) 704"
  3874. *PSVersion:    "(3010.000) 705"
  3875. *PSVersion:    "(3010.000) 800"
  3876. *LanguageLevel:    "3"
  3877. \@\@COLOR\@\@
  3878. *FileSystem:    False
  3879. *Throughput:    "1"
  3880. *LandscapeOrientation: Plus90
  3881. *TTRasterizer:    Type42
  3882. \@\@IEEE1284\@\@
  3883. \@\@EXTRALINES\@\@
  3884. \@\@OTHERSTUFF\@\@
  3885.  
  3886. \@\@OPTIONS\@\@
  3887.  
  3888. *% Generic boilerplate PPD stuff as standard PostScript fonts and so on
  3889.  
  3890. *DefaultFont: Courier
  3891. *Font AvantGarde-Book: Standard "(001.006S)" Standard ROM
  3892. *Font AvantGarde-BookOblique: Standard "(001.006S)" Standard ROM
  3893. *Font AvantGarde-Demi: Standard "(001.007S)" Standard ROM
  3894. *Font AvantGarde-DemiOblique: Standard "(001.007S)" Standard ROM
  3895. *Font Bookman-Demi: Standard "(001.004S)" Standard ROM
  3896. *Font Bookman-DemiItalic: Standard "(001.004S)" Standard ROM
  3897. *Font Bookman-Light: Standard "(001.004S)" Standard ROM
  3898. *Font Bookman-LightItalic: Standard "(001.004S)" Standard ROM
  3899. *Font Courier: Standard "(002.004S)" Standard ROM
  3900. *Font Courier-Bold: Standard "(002.004S)" Standard ROM
  3901. *Font Courier-BoldOblique: Standard "(002.004S)" Standard ROM
  3902. *Font Courier-Oblique: Standard "(002.004S)" Standard ROM
  3903. *Font Helvetica: Standard "(001.006S)" Standard ROM
  3904. *Font Helvetica-Bold: Standard "(001.007S)" Standard ROM
  3905. *Font Helvetica-BoldOblique: Standard "(001.007S)" Standard ROM
  3906. *Font Helvetica-Narrow: Standard "(001.006S)" Standard ROM
  3907. *Font Helvetica-Narrow-Bold: Standard "(001.007S)" Standard ROM
  3908. *Font Helvetica-Narrow-BoldOblique: Standard "(001.007S)" Standard ROM
  3909. *Font Helvetica-Narrow-Oblique: Standard "(001.006S)" Standard ROM
  3910. *Font Helvetica-Oblique: Standard "(001.006S)" Standard ROM
  3911. *Font NewCenturySchlbk-Bold: Standard "(001.009S)" Standard ROM
  3912. *Font NewCenturySchlbk-BoldItalic: Standard "(001.007S)" Standard ROM
  3913. *Font NewCenturySchlbk-Italic: Standard "(001.006S)" Standard ROM
  3914. *Font NewCenturySchlbk-Roman: Standard "(001.007S)" Standard ROM
  3915. *Font Palatino-Bold: Standard "(001.005S)" Standard ROM
  3916. *Font Palatino-BoldItalic: Standard "(001.005S)" Standard ROM
  3917. *Font Palatino-Italic: Standard "(001.005S)" Standard ROM
  3918. *Font Palatino-Roman: Standard "(001.005S)" Standard ROM
  3919. *Font Symbol: Special "(001.007S)" Special ROM
  3920. *Font Times-Bold: Standard "(001.007S)" Standard ROM
  3921. *Font Times-BoldItalic: Standard "(001.009S)" Standard ROM
  3922. *Font Times-Italic: Standard "(001.007S)" Standard ROM
  3923. *Font Times-Roman: Standard "(001.007S)" Standard ROM
  3924. *Font ZapfChancery-MediumItalic: Standard "(001.007S)" Standard ROM
  3925. *Font ZapfDingbats: Special "(001.004S)" Standard ROM
  3926.  
  3927. ENDTMPL
  3928. }
  3929.  
  3930. # Determine the paper width and height in points from a given paper size
  3931. # name. Used for the "PaperDimension" and "ImageableArea" entries in PPD
  3932. # files.
  3933. #
  3934. # The paper sizes in the list are all sizes known to GhostScript, all
  3935. # of Gimp-Print, all sizes of HPIJS, and some others found in the data
  3936. # of printer drivers.
  3937.  
  3938. sub getpapersize {
  3939.     my $papersize = lc(join('', @_));
  3940.  
  3941.     my @sizetable = (
  3942.     ['germanlegalfanfold', '612 936'],
  3943.     ['halfletter',         '396 612'],
  3944.     ['letterwide',         '647 957'],
  3945.     ['lettersmall',        '612 792'],
  3946.     ['letter',             '612 792'],
  3947.     ['legal',              '612 1008'],
  3948.     ['postcard',           '283 416'],
  3949.     ['tabloid',            '792 1224'],
  3950.     ['ledger',             '1224 792'],
  3951.     ['tabloidextra',       '864 1296'],
  3952.     ['statement',          '396 612'],
  3953.     ['manual',             '396 612'],
  3954.     ['executive',          '522 756'],
  3955.     ['folio',              '612 936'],
  3956.     ['archa',              '648 864'],
  3957.     ['archb',              '864 1296'],
  3958.     ['archc',              '1296 1728'],
  3959.     ['archd',              '1728 2592'],
  3960.     ['arche',              '2592 3456'],
  3961.     ['usaarch',            '648 864'],
  3962.     ['usbarch',            '864 1296'],
  3963.     ['uscarch',            '1296 1728'],
  3964.     ['usdarch',            '1728 2592'],
  3965.     ['usearch',            '2592 3456'],
  3966.     ['a2.*invit.*',        '315 414'],
  3967.     ['b6-c4',              '354 918'],
  3968.     ['c7-6',               '229 459'],
  3969.     ['supera3-b',          '932 1369'],
  3970.     ['a3wide',             '936 1368'],
  3971.     ['a4wide',             '633 1008'],
  3972.     ['a4small',            '595 842'],
  3973.     ['sra4',               '637 907'],
  3974.     ['sra3',               '907 1275'],
  3975.     ['sra2',               '1275 1814'],
  3976.     ['sra1',               '1814 2551'],
  3977.     ['sra0',               '2551 3628'],
  3978.     ['ra4',                '609 864'],
  3979.     ['ra3',                '864 1218'],
  3980.     ['ra2',                '1218 1729'],
  3981.     ['ra1',                '1729 2437'],
  3982.     ['ra0',                '2437 3458'],
  3983.     ['a10',                '74 105'],
  3984.     ['a9',                 '105 148'],
  3985.     ['a8',                 '148 210'],
  3986.     ['a7',                 '210 297'],
  3987.     ['a6',                 '297 420'],
  3988.     ['a5',                 '420 595'],
  3989.     ['a4',                 '595 842'],
  3990.     ['a3',                 '842 1191'],
  3991.     ['a2',                 '1191 1684'],
  3992.     ['a1',                 '1684 2384'],
  3993.     ['a0',                 '2384 3370'],
  3994.     ['2a',                 '3370 4768'],
  3995.     ['4a',                 '4768 6749'],
  3996.     ['c10',                '79 113'],
  3997.     ['c9',                 '113 161'],
  3998.     ['c8',                 '161 229'],
  3999.     ['c7',                 '229 323'],
  4000.     ['c6',                 '323 459'],
  4001.     ['c5',                 '459 649'],
  4002.     ['c4',                 '649 918'],
  4003.     ['c3',                 '918 1298'],
  4004.     ['c2',                 '1298 1836'],
  4005.     ['c1',                 '1836 2599'],
  4006.     ['c0',                 '2599 3676'],
  4007.     ['b10.*jis',           '90 127'],
  4008.     ['b9.*jis',            '127 180'],
  4009.     ['b8.*jis',            '180 257'],
  4010.     ['b7.*jis',            '257 362'],
  4011.     ['b6.*jis',            '362 518'],
  4012.     ['b5.*jis',            '518 727'],
  4013.     ['b4.*jis',            '727 1029'],
  4014.     ['b3.*jis',            '1029 1459'],
  4015.     ['b2.*jis',            '1459 2063'],
  4016.     ['b1.*jis',            '2063 2919'],
  4017.     ['b0.*jis',            '2919 4127'],
  4018.     ['jis.*b10',           '90 127'],
  4019.     ['jis.*b9',            '127 180'],
  4020.     ['jis.*b8',            '180 257'],
  4021.     ['jis.*b7',            '257 362'],
  4022.     ['jis.*b6',            '362 518'],
  4023.     ['jis.*b5',            '518 727'],
  4024.     ['jis.*b4',            '727 1029'],
  4025.     ['jis.*b3',            '1029 1459'],
  4026.     ['jis.*b2',            '1459 2063'],
  4027.     ['jis.*b1',            '2063 2919'],
  4028.     ['jis.*b0',            '2919 4127'],
  4029.     ['b10.*iso',           '87 124'],
  4030.     ['b9.*iso',            '124 175'],
  4031.     ['b8.*iso',            '175 249'],
  4032.     ['b7.*iso',            '249 354'],
  4033.     ['b6.*iso',            '354 498'],
  4034.     ['b5.*iso',            '498 708'],
  4035.     ['b4.*iso',            '708 1000'],
  4036.     ['b3.*iso',            '1000 1417'],
  4037.     ['b2.*iso',            '1417 2004'],
  4038.     ['b1.*iso',            '2004 2834'],
  4039.     ['b0.*iso',            '2834 4008'],
  4040.     ['2b.*iso',            '4008 5669'],
  4041.     ['4b.*iso',            '5669 8016'],
  4042.     ['iso.*b10',           '87 124'],
  4043.     ['iso.*b9',            '124 175'],
  4044.     ['iso.*b8',            '175 249'],
  4045.     ['iso.*b7',            '249 354'],
  4046.     ['iso.*b6',            '354 498'],
  4047.     ['iso.*b5',            '498 708'],
  4048.     ['iso.*b4',            '708 1000'],
  4049.     ['iso.*b3',            '1000 1417'],
  4050.     ['iso.*b2',            '1417 2004'],
  4051.     ['iso.*b1',            '2004 2834'],
  4052.     ['iso.*b0',            '2834 4008'],
  4053.     ['iso.*2b',            '4008 5669'],
  4054.     ['iso.*4b',            '5669 8016'],
  4055.     ['b10envelope',        '87 124'],
  4056.     ['b9envelope',         '124 175'],
  4057.     ['b8envelope',         '175 249'],
  4058.     ['b7envelope',         '249 354'],
  4059.     ['b6envelope',         '354 498'],
  4060.     ['b5envelope',         '498 708'],
  4061.     ['b4envelope',         '708 1000'],
  4062.     ['b3envelope',         '1000 1417'],
  4063.     ['b2envelope',         '1417 2004'],
  4064.     ['b1envelope',         '2004 2834'],
  4065.     ['b0envelope',         '2834 4008'],
  4066.     ['b10',                '87 124'],
  4067.     ['b9',                 '124 175'],
  4068.     ['b8',                 '175 249'],
  4069.     ['b7',                 '249 354'],
  4070.     ['b6',                 '354 498'],
  4071.     ['b5',                 '498 708'],
  4072.     ['b4',                 '708 1000'],
  4073.     ['b3',                 '1000 1417'],
  4074.     ['b2',                 '1417 2004'],
  4075.     ['b1',                 '2004 2834'],
  4076.     ['b0',                 '2834 4008'],
  4077.     ['monarch',            '279 540'],
  4078.     ['dl',                 '311 623'],
  4079.     ['com10',              '297 684'],
  4080.     ['com.*10',            '297 684'],
  4081.     ['env10',              '297 684'],
  4082.     ['env.*10',            '297 684'],
  4083.     ['hagaki',             '283 420'],
  4084.     ['oufuku',             '420 567'],
  4085.     ['kaku',               '680 941'],
  4086.     ['long.*3',            '340 666'],
  4087.     ['long.*4',            '255 581'],
  4088.     ['foolscap',           '576 936'],
  4089.     ['flsa',               '612 936'],
  4090.     ['flse',               '648 936'],
  4091.     ['photo100x150',       '283 425'],
  4092.     ['photo200x300',       '567 850'],
  4093.     ['photofullbleed',     '298 440'],
  4094.     ['photo4x6',           '288 432'],
  4095.     ['photo',              '288 432'],
  4096.     ['wide',               '977 792'],
  4097.     ['card148',            '419 297'],
  4098.     ['envelope132x220',    '374 623'],
  4099.     ['envelope61/2',       '468 260'],
  4100.     ['supera',             '644 1008'],
  4101.     ['superb',             '936 1368'],
  4102.     ['fanfold5',           '612 792'],
  4103.     ['fanfold4',           '612 864'],
  4104.     ['fanfold3',           '684 792'],
  4105.     ['fanfold2',           '864 612'],
  4106.     ['fanfold1',           '1044 792'],
  4107.     ['fanfold',            '1071 792'],
  4108.     ['panoramic',          '595 1683'],
  4109.     ['plotter.*size.*a',   '612 792'],
  4110.     ['plotter.*size.*b',   '792 1124'],
  4111.     ['plotter.*size.*c',   '1124 1584'],
  4112.     ['plotter.*size.*d',   '1584 2448'],
  4113.     ['plotter.*size.*e',   '2448 3168'],
  4114.     ['plotter.*size.*f',   '3168 4896'],
  4115.     ['archlarge',          '162 540'],
  4116.     ['standardaddr',       '81 252'],
  4117.     ['largeaddr',          '101 252'],
  4118.     ['suspensionfile',     '36 144'],
  4119.     ['videospine',         '54 423'],
  4120.     ['badge',              '153 288'],
  4121.     ['archsmall',          '101 540'],
  4122.     ['videotop',           '130 223'],
  4123.     ['diskette',           '153 198'],
  4124.     ['76\.2mmroll',        '216 0'],
  4125.     ['69\.5mmroll',        '197 0'],
  4126.     ['roll',               '612 0'],
  4127.     ['custom',             '0 0']
  4128.     );
  4129.  
  4130.     # Remove prefixes which sometimes could appear
  4131.     $papersize =~ s/form_//;
  4132.  
  4133.     # Check whether the paper size name is in the list above
  4134.     for my $item (@sizetable) {
  4135.     if ($papersize =~ /@{$item}[0]/) {
  4136.         return @{$item}[1];
  4137.     }
  4138.     }
  4139.  
  4140.     # Check if we have a "<Width>x<Height>" format, assume the numbers are
  4141.     # given in inches
  4142.     if ($papersize =~ /(\d+)x(\d+)/) {
  4143.     my $w = $1 * 72;
  4144.     my $h = $2 * 72;
  4145.     return sprintf("%d %d", $w, $h);
  4146.     }
  4147.  
  4148.     # Check if we have a "w<Width>h<Height>" format, assume the numbers are
  4149.     # given in points
  4150.     if ($papersize =~ /w(\d+)h(\d+)/) {
  4151.     return "$1 $2";
  4152.     }
  4153.  
  4154.     # Check if we have a "w<Width>" format, assume roll paper with the given
  4155.     # width in points
  4156.     if ($papersize =~ /w(\d+)/) {
  4157.     return "$1 0";
  4158.     }
  4159.  
  4160.     # This paper size is absolutely unknown, issue a warning
  4161.     warn "WARNING: Unknown paper size: $papersize!";
  4162.     return "0 0";
  4163. }
  4164.  
  4165. # Get documentation for the printer/driver pair to print out. For
  4166. # "Execution Details" section of driver web pages on linuxprinting.org
  4167.  
  4168. sub getexecdocs {
  4169.  
  4170.     my ($this) = $_[0];
  4171.  
  4172.     my $dat = $this->{'dat'};
  4173.  
  4174.     my @docs;
  4175.     
  4176.     # Construct the proper command line.
  4177.     my $commandline = htmlify($dat->{'cmd'});
  4178.  
  4179.     if ($commandline eq "") {return ();}
  4180.  
  4181.     my @letters = qw/A B C D E F G H I J K L M Z/;
  4182.     
  4183.     for my $spot (@letters) {
  4184.     
  4185.     if($commandline =~ m!\%$spot!) {
  4186.  
  4187.         my $arg;
  4188.       argument:
  4189.         for $arg (@{$dat->{'args'}}) {
  4190. #        for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  4191. #              @{$dat->{'args'}}) {
  4192.         
  4193.         # Only do arguments that go in this spot
  4194.         next argument if ($arg->{'spot'} ne $spot);
  4195.         # PJL arguments are not inserted at a spot in the command
  4196.         # line
  4197.         next argument if ($arg->{'style'} eq 'J');
  4198.         # Composite options are not interesting here
  4199.         next argument if ($arg->{'style'} eq 'X');
  4200.         
  4201.         my $name = htmlify($arg->{'name'});
  4202.         my $varname = htmlify($arg->{'varname'});
  4203.         my $cmd = htmlify($arg->{'proto'});
  4204.         my $comment = htmlify($arg->{'comment'});
  4205.         my $placeholder = "</TT><I><$name></I><TT>";
  4206.         my $default = htmlify($arg->{'default'});
  4207.         my $type = $arg->{'type'};
  4208.         my $cmdvar = "";
  4209.         my $gsarg1 = "";
  4210.         my $gsarg2 = "";
  4211.         if ($arg->{'style'} eq 'G') {
  4212.             $gsarg1 = ' -c "';
  4213.             $gsarg2 = '"';
  4214.             $cmd =~ s/\"/\\\"/g;
  4215.         }
  4216.         #my $leftbr = ($arg->{'required'} ? "" : "[");
  4217.         #my $rightbr = ($arg->{'required'} ? "" : "]");
  4218.         my $leftbr = "";
  4219.         my $rightbr = "";
  4220.     
  4221.         if ($type eq 'bool') {
  4222.             $cmdvar = "$leftbr$gsarg1$cmd$gsarg2$rightbr";
  4223.         } elsif ($type eq 'int' or $type eq 'float') {
  4224.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",$placeholder);
  4225.         } elsif ($type eq 'enum') {
  4226.             my $val;
  4227.             if ($val=valbyname($arg,$default)) {
  4228.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",
  4229.                       $placeholder);
  4230.             }
  4231.         }
  4232.         
  4233.         # Insert the processed argument in the commandline
  4234.         # just before every occurance of the spot marker.
  4235.         $cmdvar =~ s!^\[\ !\ \[!;
  4236.         $commandline =~ s!\%$spot!$cmdvar\%$spot!g;
  4237.         }
  4238.         
  4239.         # Remove the letter markers from the commandline
  4240.         $commandline =~ s!\%$spot!!g;
  4241.         
  4242.     }
  4243.     
  4244.     }
  4245.  
  4246.     $dat->{'excommandline'} = $commandline;
  4247.  
  4248.     push(@docs, "<B>Command Line</B><P>");
  4249.     push(@docs, "<BLOCKQUOTE><TT>$commandline</TT></BLOCKQUOTE><P>");
  4250.  
  4251.     my ($arg, @doctmp);
  4252.     my @pjlcommands = ();
  4253.   argt:
  4254.     for $arg (@{$dat->{'args'}}) {
  4255. #    for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  4256. #          @{$dat->{'args'}}) {
  4257.  
  4258.     # Composite options are not interesting here
  4259.     next argt if ($arg->{'style'} eq 'X');
  4260.  
  4261.     # Make sure that the longname/translation exists
  4262.     if (!$arg->{'comment'}) {
  4263.         $arg->{'comment'} = longname($arg->{'name'});
  4264.     }
  4265.  
  4266.     my $name = htmlify($arg->{'name'});
  4267.     my $cmd = htmlify($arg->{'proto'});
  4268.     my $comment = htmlify($arg->{'comment'});
  4269.     my $placeholder = "</TT><I><$name></I><TT>";
  4270.     if ($arg->{'style'} eq 'J') {
  4271.         $cmd = "\@PJL $cmd";
  4272.         my $sprintfcmd = $cmd;
  4273.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4274.         push (@pjlcommands, sprintf($sprintfcmd, $placeholder));
  4275.     }
  4276.  
  4277.     my $default = htmlify($arg->{'default'});
  4278.     my $type = $arg->{'type'};
  4279.     
  4280.     my $required = ($arg->{'required'} ? " required" : "n optional");
  4281.     my $pjl = ($arg->{'style'} eq 'J' ? "PJL " : "");
  4282.  
  4283.     if ($type eq 'bool') {
  4284.         my $name_false = htmlify($arg->{'name_false'});
  4285.         push(@doctmp,
  4286.          "<DL><DT><I>$name</I></DT>",
  4287.          "<DD>A$required boolean ${pjl}argument meaning $name if present or $name_false if not.<BR>",
  4288.          "$comment<BR>",
  4289.          "Prototype: <TT>$cmd</TT><BR>",
  4290.          "Default: ", $default ? "True" : "False",
  4291.          "</DD></DL><P>"
  4292.          );
  4293.  
  4294.     } elsif ($type eq 'int' or $type eq 'float') {
  4295.         my $max = (defined($arg->{'max'}) ? $arg->{'max'} : "none");
  4296.         my $min = (defined($arg->{'min'}) ? $arg->{'min'} : "none");
  4297.         my $sprintfcmd = $cmd;
  4298.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4299.         push(@doctmp,
  4300.          "<DL><DT><I>$name</I></DT>",
  4301.          "<DD>A$required $type ${pjl}argument.<BR>",
  4302.          "$comment<BR>",
  4303.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  4304.          "</TT><BR>",
  4305.          "Default: <TT>$default</TT><BR>",
  4306.          "Range: <TT>$min <= $placeholder <= $max</TT>",
  4307.          "</DD></DL><P>"
  4308.          );
  4309.  
  4310.     } elsif ($type eq 'enum') {
  4311.         my ($val, $defstr);
  4312.         my (@choicelist) = ();
  4313.  
  4314.         for $val (@{$arg->{'vals'}}) {
  4315.  
  4316.         # Make sure that the longname/translation exists
  4317.         if (!$val->{'comment'}) {
  4318.             $val->{'comment'} = longname($val->{'value'});
  4319.         }
  4320.  
  4321.         my ($value, $comment, $driverval) = 
  4322.             (htmlify($val->{'value'}),
  4323.              htmlify($val->{'comment'}),
  4324.              htmlify($val->{'driverval'}));
  4325.  
  4326.         if (defined($driverval)) {
  4327.             if ($driverval eq "") {
  4328.             push(@choicelist,
  4329.                  "<LI>$value: $comment (<TT>$placeholder</TT> is left blank)</LI>");
  4330.             } else {
  4331.             my $widthheight = "";
  4332.             if (($name eq "PageSize") && ($value eq "Custom")) {
  4333.                 my $width = "</TT><I><Width></I><TT>";
  4334.                 my $height = "</TT><I><Height></I><TT>";
  4335.                 $driverval =~ s/\%0/$width/ or
  4336.                             $driverval =~ s/(\W)0(\W)/$1$width$2/ or
  4337.                             $driverval =~ s/^0(\W)/$width$1/m or
  4338.                             $driverval =~ s/(\W)0$/$1$width/m or
  4339.                             $driverval =~ s/^0$/$width/m;
  4340.                             $driverval =~ s/\%1/$height/ or
  4341.                             $driverval =~ s/(\W)0(\W)/$1$height$2/ or
  4342.                             $driverval =~ s/^0(\W)/$height$1/m or
  4343.                             $driverval =~ s/(\W)0$/$1$height/m or
  4344.                             $driverval =~ s/^0$/$height/m;
  4345.                 $widthheight = ", <I><Width></I> and <I><Height></I> are the page dimensions in points, 1/72 inches";
  4346.             }
  4347.             push(@choicelist,
  4348.                  "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$driverval</TT>'$widthheight)</LI>");
  4349.             }
  4350.         } else {
  4351.             push(@choicelist,
  4352.              "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$value</TT>')</LI>");
  4353.         }
  4354.         }
  4355.  
  4356.         my $sprintfcmd = $cmd;
  4357.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4358.         push(@doctmp,
  4359.          "<DL><DT><I>$name</I></DT>",
  4360.          "<DD>A$required enumerated choice ${pjl}argument.<BR>",
  4361.          "$comment<BR>",
  4362.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  4363.          "</TT><BR>",
  4364.          "Default: $default",
  4365.          "<UL>", 
  4366.          join("", @choicelist), 
  4367.          "</UL></DD></DL><P>"
  4368.          );
  4369.  
  4370.     }
  4371.     }
  4372.  
  4373.     # Instructions for PJL commands
  4374.     if (($#pjlcommands > -1) && (defined($dat->{'pjl'}))) {
  4375.     #if (($#pjlcommands > -1)) {
  4376.     my @pjltmp;
  4377.     push(@pjltmp,
  4378.          "PJL arguments are not put into the command line, they must be put into a PJL header which is prepended to the actual job data which is generated by the command line shown above and sent to the printer. After the job data one can reset the printer via PJL. So a complete job looks as follows:<BLOCKQUOTE>",
  4379.          "<I><ESC></I>",
  4380.          # The "JOB" PJL command is not supported by all printers
  4381.          "<TT>%-12345X\@PJL</TT><BR>");
  4382.          #"<TT>%-12345X\@PJL JOB NAME=\"</TT>",
  4383.          #"<I><A job name></I>",
  4384.          #"<TT>\"</TT><BR>");
  4385.     for my $command (@pjlcommands) {
  4386.         push(@pjltmp,
  4387.          "<TT>$command</TT><BR>");
  4388.     }
  4389.     push(@pjltmp,
  4390.          "<I><The job data></I><BR>",
  4391.          "<I><ESC></I>",
  4392.          # The "JOB" PJL command is not supported by all printers
  4393.          "<TT>%-12345X\@PJL RESET</TT></BLOCKQUOTE><P>",
  4394.          #"<TT>%-12345X\@PJL EOJ</TT></BLOCKQUOTE><P>",
  4395.          "<I><ESC></I>",
  4396.          ": This is the ",
  4397.          "<I>ESC</I>",
  4398.          " character, ASCII code 27.<P>",
  4399.          #"<I><A job name></I>",
  4400.          #": The job name can be chosen arbitrarily, some printers show it on their front panel displays.<P>",
  4401.          "It is not required to give the PJL arguments, you can leave out some of them or you can even send only the job data without PJL header and PJL end-of-job mark.<P>");
  4402.     push(@docs, "<B>PJL</B><P>");
  4403.     push(@docs, @pjltmp);
  4404.     } elsif ((defined($dat->{'drivernopjl'})) && 
  4405.          ($dat->{'drivernopjl'} == 1) && 
  4406.          (defined($dat->{'pjl'}))) {
  4407.     my @pjltmp;
  4408.     push(@pjltmp,
  4409.          "This driver produces a PJL header with PJL commands internally, so commands in a PJL header sent to the printer before the output of this driver would be ignored. Therefore there are no PJL options available when using this driver.<P>");
  4410.     push(@docs, "<B>PJL</B><P>");
  4411.     push(@docs, @pjltmp);
  4412.     }
  4413.  
  4414.     push(@docs, "<B>Options</B><P>");
  4415.  
  4416.     push(@docs, @doctmp);
  4417.  
  4418.     return @docs;
  4419.    
  4420. }
  4421.  
  4422. # Get a shorter summary documentation thing.
  4423. sub get_summarydocs {
  4424.     my ($this) = $_[0];
  4425.  
  4426.     my $dat = $this->{'dat'};
  4427.  
  4428.     my @docs;
  4429.  
  4430.     for my $arg (@{$dat->{'args'}}) {
  4431.  
  4432.     # Make sure that the longname/translation exists
  4433.     if (!$arg->{'comment'}) {
  4434.         $arg->{'comment'} = longname($arg->{'name'});
  4435.     }
  4436.  
  4437.     my ($name,
  4438.         $required,
  4439.         $type,
  4440.         $comment,
  4441.         $spot,
  4442.         $default) = ($arg->{'name'},
  4443.              $arg->{'required'},
  4444.              $arg->{'type'},
  4445.              $arg->{'comment'},
  4446.              $arg->{'spot'},
  4447.              $arg->{'default'});
  4448.     
  4449.     my $reqstr = ($required ? " required" : "n optional");
  4450.     push(@docs,
  4451.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  4452.  
  4453.     push(@docs,
  4454.          "  This option corresponds to a PJL command.\n") 
  4455.         if ($spot eq 'Y');
  4456.     
  4457.     if ($type eq 'bool') {
  4458.         if (defined($default)) {
  4459.         my $defstr = ($default ? "True" : "False");
  4460.         push(@docs, "  Default: $defstr\n");
  4461.         }
  4462.         push(@docs, "  Example (true): `$name'\n");
  4463.         push(@docs, "  Example (false): `no$name'\n");
  4464.     } elsif ($type eq 'enum') {
  4465.         push(@docs, "  Possible choices:\n");
  4466.         my $exarg;
  4467.         for (@{$arg->{'vals'}}) {
  4468.  
  4469.         # Make sure that the longname/translation exists
  4470.         if (!$_->{'comment'}) {
  4471.             $_->{'comment'} = longname($_->{'value'});
  4472.         }
  4473.  
  4474.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  4475.         push(@docs, "   * $choice: $comment\n");
  4476.         $exarg=$choice;
  4477.         }
  4478.         if (defined($default)) {
  4479.         push(@docs, "  Default: $default\n");
  4480.         }
  4481.         push(@docs, "  Example: `$name=$exarg'\n");
  4482.     } elsif ($type eq 'int' or $type eq 'float') {
  4483.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  4484.         my $exarg;
  4485.         if (defined($max)) {
  4486.         push(@docs, "  Range: $min <= x <= $max\n");
  4487.         $exarg=$max;
  4488.         }
  4489.         if (defined($default)) {
  4490.         push(@docs, "  Default: $default\n");
  4491.         $exarg=$default;
  4492.         }
  4493.         if (!$exarg) { $exarg=0; }
  4494.         push(@docs, "  Example: `$name=$exarg'\n");
  4495.     }
  4496.  
  4497.     push(@docs, "\n");
  4498.     }
  4499.  
  4500.     return @docs;
  4501.  
  4502. }
  4503.  
  4504. # About as obsolete as the other docs functions.  Why on earth are
  4505. # there three, anyway?!
  4506. sub getdocs {
  4507.     my ($this) = $_[0];
  4508.  
  4509.     my $dat = $this->{'dat'};
  4510.  
  4511.     my @docs;
  4512.  
  4513.     for my $arg (@{$dat->{'args'}}) {
  4514.  
  4515.     # Make sure that the longname/translation exists
  4516.     if (!$arg->{'comment'}) {
  4517.         $arg->{'comment'} = longname($arg->{'name'});
  4518.     }
  4519.  
  4520.     my ($name,
  4521.         $required,
  4522.         $type,
  4523.         $comment,
  4524.         $spot,
  4525.         $default) = ($arg->{'name'},
  4526.              $arg->{'required'},
  4527.              $arg->{'type'},
  4528.              $arg->{'comment'},
  4529.              $arg->{'spot'},
  4530.              $arg->{'default'});
  4531.     
  4532.     my $reqstr = ($required ? " required" : "n optional");
  4533.     push(@docs,
  4534.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  4535.  
  4536.     push(@docs,
  4537.          "  This option corresponds to a PJL command.\n") 
  4538.         if ($spot eq 'Y');
  4539.     
  4540.     if ($type eq 'bool') {
  4541.         if (defined($default)) {
  4542.         my $defstr = ($default ? "True" : "False");
  4543.         push(@docs, "  Default: $defstr\n");
  4544.         }
  4545.         push(@docs, "  Example (true): `$name'\n");
  4546.         push(@docs, "  Example (false): `no$name'\n");
  4547.     } elsif ($type eq 'enum') {
  4548.         push(@docs, "  Possible choices:\n");
  4549.         my $exarg;
  4550.         for (@{$arg->{'vals'}}) {
  4551.  
  4552.         # Make sure that the longname/translation exists
  4553.         if (!$_->{'comment'}) {
  4554.             $_->{'comment'} = longname($_->{'value'});
  4555.         }
  4556.  
  4557.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  4558.         push(@docs, "   * $choice: $comment\n");
  4559.         $exarg=$choice;
  4560.         }
  4561.         if (defined($default)) {
  4562.         push(@docs, "  Default: $default\n");
  4563.         }
  4564.         push(@docs, "  Example: `$name=$exarg'\n");
  4565.     } elsif ($type eq 'int' or $type eq 'float') {
  4566.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  4567.         my $exarg;
  4568.         if (defined($max)) {
  4569.         push(@docs, "  Range: $min <= x <= $max\n");
  4570.         $exarg=$max;
  4571.         }
  4572.         if (defined($default)) {
  4573.         push(@docs, "  Default: $default\n");
  4574.         $exarg=$default;
  4575.         }
  4576.         if (!$exarg) { $exarg=0; }
  4577.         push(@docs, "  Example: `$name=$exarg'\n");
  4578.     }
  4579.  
  4580.     push(@docs, "\n");
  4581.     }
  4582.  
  4583.     return @docs;
  4584.  
  4585. }
  4586.  
  4587. # Find a choice value hash by name.
  4588. # Operates on old dat structure...
  4589. sub valbyname {
  4590.     my ($arg,$name) = @_;
  4591.  
  4592.     my $val;
  4593.     for my $val (@{$arg->{'vals'}}) {
  4594.     return $val if (lc($name) eq lc($val->{'value'}));
  4595.     }
  4596.  
  4597.     return undef;
  4598. }
  4599.  
  4600. # replace numbers with fixed 6-digit number for ease of sorting
  4601. # ie: sort { normalizename($a) cmp normalizename($b) } @foo;
  4602. sub normalizename {
  4603.     my $n = $_[0];
  4604.  
  4605.     $n =~ s/[\d\.]+/sprintf("%013.6f", $&)/eg;
  4606.     return $n;
  4607. }
  4608.  
  4609.  
  4610. # Load an XML object from the library
  4611. # You specify the relative file path (to .../db/), less the .xml on the end.
  4612. sub _get_object_xml {
  4613.     my ($this, $file, $quiet) = @_;
  4614.  
  4615.     open XML, "$libdir/db/$file.xml"
  4616.     or do { warn "Cannot open file $libdir/db/$file.xml\n"
  4617.             if !$quiet;
  4618.         return undef; };
  4619.     my $xml = join('', (<XML>));
  4620.     close XML;
  4621.  
  4622.     return $xml;
  4623. }
  4624.  
  4625. # Write an XML object from the library
  4626. # You specify the relative file path (to .../db/), less the .xml on the end.
  4627. sub _set_object_xml {
  4628.     my ($this, $file, $stuff, $cache) = @_;
  4629.  
  4630.     my $dir = "$libdir/db";
  4631.     my $xfile = "$dir/$file.xml";
  4632.     umask 0002;
  4633.     open XML, ">$xfile.$$"
  4634.     or do { warn "Cannot write file $xfile.$$\n";
  4635.         return undef; };
  4636.     print XML $stuff;
  4637.     close XML;
  4638.     rename "$xfile.$$", $xfile
  4639.     or die "Cannot rename $xfile.$$ to $xfile\n";
  4640.  
  4641.     return 1;
  4642. }
  4643.  
  4644. # Get a list of XML filenames from a library directory.  These could then be
  4645. # read with _get_object_xml.
  4646. sub _get_xml_filelist {
  4647.     my ($this, $dir) = @_;
  4648.  
  4649.     if (!defined($this->{"names-$dir"})) {
  4650.     opendir DRV, "$libdir/db/$dir"
  4651.         or die 'Cannot find source db for $dir\n';
  4652.     my $driverfile;
  4653.     while($driverfile = readdir(DRV)) {
  4654.         next if ($driverfile !~ m!^(.+)\.xml$!);
  4655.         push(@{$this->{"names-$dir"}}, $1);
  4656.     }
  4657.     closedir(DRV);
  4658.     }
  4659.  
  4660.     return @{$this->{"names-$dir"}};
  4661. }
  4662.  
  4663.  
  4664. # Return a Perl structure in eval-able ascii format
  4665. sub getascii {
  4666.     my ($this) = $_[0];
  4667.     if (! $this->{'dat'}) {
  4668.     $this->getdat();
  4669.     }
  4670.     
  4671.     local $Data::Dumper::Purity=1;
  4672.     local $Data::Dumper::Indent=1;
  4673.  
  4674.     # Encase data for inclusion in PPD file
  4675.     return Dumper($this->{'dat'});
  4676. }
  4677.  
  4678. # Return list of printer makes
  4679. sub get_makes {
  4680.     my ($this) = @_;
  4681.  
  4682.     my @makes;
  4683.     my %seenmakes;
  4684.     my $p;
  4685.     for $p (@{$this->get_overview()}) {
  4686.     my $make = $p->{'make'};
  4687.     push (@makes, $make) 
  4688.         if ! $seenmakes{$make}++;
  4689.     }
  4690.     
  4691.     return @makes;
  4692.     
  4693. }
  4694.  
  4695. # get a list of model names from a make
  4696. sub get_models_by_make {
  4697.     my ($this, $wantmake) = @_;
  4698.  
  4699.     my $over = $this->get_overview();
  4700.  
  4701.     my @models;
  4702.     my $p;
  4703.     for $p (@{$over}) {
  4704.     push (@models, $p->{'model'}) 
  4705.         if ($wantmake eq $p->{'make'});
  4706.     }
  4707.  
  4708.     return @models;
  4709. }
  4710.  
  4711. # get a printer id from a make/model
  4712. sub get_printer_from_make_model {
  4713.     my ($this, $wantmake, $wantmodel) = @_;
  4714.  
  4715.     my $over = $this->get_overview();
  4716.     my $p;
  4717.     for $p (@{$over}) {
  4718.     return $p->{'id'} if ($p->{'make'} eq $wantmake
  4719.                   and $p->{'model'} eq $wantmodel);
  4720.     }
  4721.  
  4722.     return undef;
  4723. }
  4724.  
  4725. sub get_javascript2 {
  4726.  
  4727.     my ($this) = @_;
  4728.  
  4729.     my @swit;
  4730.     my $mak;
  4731.     my $else = "";
  4732.     for $mak ($this->get_makes()) {
  4733.     push (@swit,
  4734.           " $else if (make == \"$mak\") {\n");
  4735.  
  4736.     my $ct = 0;
  4737.     my $mod;
  4738.     for $mod (sort {normalizename($a) cmp normalizename($b) } 
  4739.           $this->get_models_by_make($mak)) {
  4740.         
  4741.         my $p;
  4742.         $p = $this->get_printer_from_make_model($mak, $mod);
  4743.         if (defined($p)) {
  4744.         push (@swit,
  4745.               "      o[i++]=new Option(\"$mod\", \"$p\");\n");
  4746.         $ct++;
  4747.         }
  4748.     }
  4749.  
  4750.     if (!$ct) {
  4751.         push(@swit,
  4752.          "      o[i++]=new Option(\"No Printers\", \"0\");\n");
  4753.     }
  4754.  
  4755.     push (@swit,
  4756.           "    }");
  4757.     $else = "else";
  4758.     }
  4759.  
  4760.     my $switch = join('',@swit);
  4761.  
  4762.     my $javascript = '
  4763.        function reflectMake(makeselector, modelselector) {
  4764.      //
  4765.      // This function is called when makeselector changes
  4766.      // by an onchange thingy on the makeselector.
  4767.      //
  4768.  
  4769.      // Get the value of the OPTION that just changed
  4770.      selected_value=makeselector.options[makeselector.selectedIndex].value;
  4771.      // Get the text of the OPTION that just changed
  4772.      make=makeselector.options[makeselector.selectedIndex].text;
  4773.  
  4774.      o = new Array;
  4775.      i=0;
  4776.  
  4777.      ' . $switch . '    if (i==0) {
  4778.        alert("Error: that dropdown should do something, but it doesnt");
  4779.      } else {
  4780.        modelselector.length=o.length;
  4781.        for (i=0; i < o.length; i++) {
  4782.          modelselector.options[i]=o[i];
  4783.        }
  4784.        modelselector.options[0].selected=true;
  4785.      }
  4786.  
  4787.        }
  4788.      ';
  4789.  
  4790.     return $javascript;
  4791. }
  4792.  
  4793.  
  4794.  
  4795.  
  4796. # Modify comments text to contain only what it should:
  4797. #
  4798. # <a>, <p>, <br> (<br> -> <p>)
  4799. #
  4800. sub comment_filter {
  4801.     my ($text) = @_;
  4802.  
  4803.     my $fake = ("INSERTFIXEDTHINGHERE" . sprintf("%06x", rand(1000000)));
  4804.     my %replacements;
  4805.     my $num = 1;
  4806.  
  4807.     # extract all the A href tags
  4808.     my $replace = "ANCHOR$fake$num";
  4809.     while ($text =~ 
  4810.        s!(<\s*a\s+href\s*=\s*['"]([^'"]+)['"]\s*>)!$replace!i) {
  4811.     $replacements{$replace} = $1;
  4812.     $num++;
  4813.     $replace = "ANCHOR$fake$num";
  4814.     }
  4815.  
  4816.     # extract all the A tail tags
  4817.     $replace = "ANCHORTAIL$fake$num";
  4818.     while ($text =~ 
  4819.        s!(<\s*/\s*a\s*>)!$replace!i) {
  4820.     $replacements{$replace} = $1;
  4821.     $num++;
  4822.     $replace = "ANCHOR$fake$num";
  4823.     }
  4824.  
  4825.     # extract all the P tags
  4826.     $replace = "PARA$fake$num";
  4827.     while ($text =~ 
  4828.        s!(<\s*p\s*>)!$replace!i) {
  4829.  
  4830.     $replacements{$replace} = $1;
  4831.     $num++;
  4832.     $replace = "PARA$fake$num";
  4833.     }
  4834.  
  4835.     # extract all the BR tags
  4836.     $replace = "PARA$fake$num";
  4837.     while ($text =~ 
  4838.        s!(<\s*br\s*>)!$replace!i) {
  4839.  
  4840.     $replacements{$replace} = $1;
  4841.     $num++;
  4842.     $replace = "PARA$fake$num";
  4843.     }
  4844.  
  4845.     # Now it's just clean text; remove all tags and &foo;s
  4846.     $text =~ s!<[^>]+>! !g;
  4847.     $text =~ s!&!&!g;
  4848.     $text =~ s!<!<!g;
  4849.     $text =~ s!>!>!g;
  4850.     $text =~ s!&[^;]+?;! !g;
  4851.  
  4852.     # Now rewrite into our teeny-html subset
  4853.     $text =~ s!&!&!g;
  4854.     $text =~ s!<!<!g;
  4855.     $text =~ s!>!>!g;
  4856.  
  4857.     # And reinsert the few things we wanted to preserve
  4858.     for (keys(%replacements)) {
  4859.     my ($k, $r) = ($_, $replacements{$_});
  4860.     $text =~ s!$k!$r!;
  4861.     }
  4862.  
  4863. #    print STDERR "$text";
  4864.  
  4865.     return $text;
  4866. }
  4867.  
  4868. 1;
  4869.