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