home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / share / perl5 / Foomatic / DB.pm next >
Encoding:
Perl POD Document  |  2007-03-03  |  163.4 KB  |  5,479 lines

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