foomatic-generator.in   [plain text]


#!@PERL@

# Get command line options
use Getopt::Long;
Getopt::Long::Configure("no_ignore_case", "pass_through");
GetOptions("m=s" => \$opt_m,         # Map file
           "t=s" => \$opt_t,         # Template directory
	   "f=s" => \$opt_f,         # Foomatic version
           "d=s" => \@opt_d);        # which Drivers?

# Wherever we put it...
my $mapfile;
# $mapfile = "../main/printers.xml";
$mapfile = $opt_m;

my $templatedir;
$templatedir = $opt_t;

my @drivertypes = ();
for my $d (@opt_d) {
    if (lc($d) eq "gs") {
	push (@drivertypes, "gs");
    }
    if (lc($d) eq "ijs") {
	push (@drivertypes, "ijs");
    }
    if (lc($d) eq "nogs") {
	@drivertypes = ("ijs");
    }
    if (lc($d) eq "noijs") {
	@drivertypes = ("gs");
    }
    if (lc($d) eq "all") {
	@drivertypes = ("gs", "ijs");
    }
    if (lc($d) eq "both") {
	@drivertypes = ("gs", "ijs");
    }
}

exit 0 if ($#drivertypes < 0); # Nothing to be done, exit silently

# Do we have Foomatic 2.9 or newer
my $foomatic3 = ((defined($opt_f)) && ($opt_f >= 2.9));

# The following substitutions happen to the template XML files:
# @@STPVER@@         - the version number (ie '4.1.5')
# @@DRVNAME@@        - driver name (ie gimp-print)
# @@STPRINTERS@@     - <printers>...</printers> clause for the driver
# @@OPTCONSTRAINTS@@ - <constraints>...</constraints> object for the option
# @@ENUMVALS@@       - <enum_vals>...</enum_vals> section for the enum option
# @@MINVAL@@         - minimum value for numeric setting
# @@MAXVAL@@         - maximum value for numeric setting
# @@DEFVAL@@         - default value for numeric setting

# For some things, there are option-specific root-around-n-writer
# functions.  So we have a dispatch table:
#
# function arguments are: ('StpDriverOptionName')

my $funcs = { 'InputSlot' => { 'OPTCONSTRAINTS' => \&build_cons,
			       'ENUMVALS' => \&build_ev, },
	      'InkType' => { 'OPTCONSTRAINTS' => \&build_cons,
			     'ENUMVALS' => \&build_ev },
	      'MediaType' => { 'OPTCONSTRAINTS' => \&build_cons,
			       'ENUMVALS' => \&build_ev },
	      'Model' => { 'OPTCONSTRAINTS' => \&build_model_cons,
			   'ENUMVALS' => \&build_model_ev },
	      'PageSize' => { 'OPTCONSTRAINTS' => \&build_cons,
			      'ENUMVALS' => \&build_ev },
	      'Quality' => { 'OPTCONSTRAINTS' => \&build_cons,
			     'ENUMVALS' => \&build_ev },
	      'Dither' => { 'OPTCONSTRAINTS' => \&build_cons,
			     'ENUMVALS' => \&build_ev },
	      'Color' => { 'OPTCONSTRAINTS' => \&build_cons,
			     'ENUMVALS' => \&build_ev },
	      'Resolution' => { 'OPTCONSTRAINTS' => \&build_resolution_cons,
				'ENUMVALS' => \&build_resolution_ev },
	      'PrintoutMode' => { 'OPTCONSTRAINTS' => 
				      \&build_printoutmode_cons,
				  'ENUMVALS' => 
				      \&build_printoutmode_ev },
	  };

my $drivervals = { 'PageSize' => \&optmap_pagesize,
		   'Color' => \&optmap_color
	       };

my $debug = 0;

# Foomatic name => Gimp-print name
%argnamemap = ('Quality' => 'Resolution');

$colormap = { 'gs' => { 'Grayscale' => '0',
			'Color' => '1',
			'BlackAndWhite' => '2' },
	      'ijs' => { 'Grayscale' => 'DeviceGray',
			 'Color' => 'DeviceRGB',
			 'BlackAndWhite' =>
			     'DeviceGray -dBitsPerSample=1',
			 'RawCMYK' => 'DeviceCMYK' }
	    };

use Data::Dumper;

open PIPE, "./printer_options|" or die "Cannot run printer_options: $!\n";
print STDERR "Loading options from ./printer_options...";
while(<PIPE>) {
    next if m!^#!;
    eval $_;
}
close PIPE or die "Cannot run printer_options: $!\n";
print STDERR "done.\n";

if ($foomatic3) {
    open PIPE, "./printer_margins|" or die "Cannot run printer_margins: $!\n";
    print STDERR "Loading margins from ./printer_margins...";
    $code = join('', <PIPE>);
    close PIPE or die "Cannot run printer_margins: $!\n";
    eval $code or die "Cannot run printer_margins: $!\n";
    print STDERR "done.\n";
}

open PIPE, "./stp_limits|" or die "Cannot run stp_limits: $!\n";
print STDERR "Loading options from ./stp_limits...";
while(<PIPE>) {
    next if m!^#!;
    eval $_;
}
close PIPE or die "Cannot run stp_limits: $!\n";
print STDERR "done.\n";

open PIPE, "./printers|" or die "Cannot run printers: $!\n";
print STDERR "Loading options from ./printers...";
while(<PIPE>) {
    next if m!^#!;
    eval $_;
}
close PIPE or die "Cannot run printers: $!\n";
print STDERR "done.\n";

# OK, now %stpdata is a big honking thing, and %defaults is in there, too.

# Invert, to build %bar{$optionname} = [ choice1, choice2 ];
my ($a, $otmp, $vtmp);
for $a (keys(%stpdata)) {
    for $otmp (keys %{$stpdata{$a}}) {
	for $vtmp (keys (%{$stpdata{$a}{$otmp}})) {
	    if (!$seen_evchoice{$otmp}{$vtmp}++) {
		push (@{$ev_choices{$otmp}}, [ $vtmp,
					       $stpdata{$a}{$otmp}{$vtmp}]);
	    }
	}
    }
}

if ($foomatic3) {
    # Generate data for "PrintoutMode" option, only needed for 
    # Foomatic 2.9.x or newer
    print STDERR "Generating data for \"PrintoutMode\" option...";
    ($printoutmode, $printoutmodechoices) = getprintoutmode();
    print STDERR "done.\n";
    # Foomatic >= 2.9: Make a list of all choice entries needed in the
    # "PrintoutMode" option XML file. Note: every choice ("Draft",
    # "Normal", ...) will appear several times, but with different
    # strings in "<ev_driverval>". Constraints will make only the
    # right choices getting into the PPD file. Assign a unique ID to
    # each entry.
    for $a (keys(%{$printoutmode})) {
	for $vtmp (keys %{$printoutmode->{$a}}) {
	    my $mode = $printoutmode->{$a}{$vtmp};
	    if (!$seen_modes{$vtmp}{$mode}++) {
		if (!defined($nums{$vtmp})) {
		    $nums{$vtmp} = 0;
		}
		$nums{$vtmp} ++;
		$modes{$vtmp}{$mode} = "$vtmp-$nums{$vtmp}";
	    }
	}
    }
}

# Step 1: construct a map from Model= values to Foomatic model id's
# this map is %mapstp.  The inverse from model to Model is %mapdb
#
# Foomatic is supposed to be a superset, so

open PRINTERS, $mapfile or die "Cannot open mapfile $mapfile: $!\n";
for (<PRINTERS>) {
    if (m!^#\s*gptofoo\s+([^\s]+)\s+([^\s]+)!) {
	push (@{$mapstp{$1}}, $2);
	$mapfoo{$2} = $1;	# do we need?
    }
}

$missing_drivers = 0;

# Are we missing any stp printers?
for (keys(%stpdata)) {
    if (!defined($mapstp{$_})) {
	$missing_drivers = 1;
	warn "No foomatic printer IDs for gimp-print printer $_.\n";
    }
}

for (keys(%mapstp)) {
    if (!defined($stpdata{$_})) {
	$missing_drivers = 1;
	warn "No gimp-print printer for foomatic ID $_.\n";
    }
}

if ($missing_drivers) {
   die "Cannot continue\n";
}

# Figure out version etc
open PIPE, "./gimp-print-version|" or die "Cannot run gimp-print-version: $!\n";
my $stpvers = <PIPE>;
close PIPE or die "Cannot run gimp-print-version: $!\n";
chomp $stpvers;

# Build <printers> clause...
my @printerlist = ();
push (@printerlist, " <printers>\n");
my $p1;
for $p1 (keys(%mapstp)) {
    push (@printerlist, "  <!-- gimp-print driver: $p1 -->\n");
    for my $id (@{$mapstp{$p1}}) {
	if ($foomatic3) {
	    # Add unprintable margins (only Foonmatic 2.9.x)
	    push(@printerlist, "  <printer>\n");
	    push(@printerlist, "    <id>$id</id>\n");
	    push(@printerlist, "    <margins>\n");
	    my ($cleft, $cright, $ctop, $cbottom) = 
		(undef, undef, undef, undef);
	    if (defined($imageableareas{$p1}{'Custom'})) {
		$cleft = $imageableareas{$p1}{'Custom'}{'left'};
		$cright = $imageableareas{$p1}{'Custom'}{'right'};
		$ctop = $imageableareas{$p1}{'Custom'}{'top'};
		$cbottom = $imageableareas{$p1}{'Custom'}{'bottom'};
		push(@printerlist, "      <general>\n");
		push(@printerlist, "        <relative />\n");
		push(@printerlist, "        <left>$cleft</left>\n");
		push(@printerlist, "        <right>$cright</right>\n");
		push(@printerlist, "        <top>$ctop</top>\n");
		push(@printerlist, "        <bottom>$cbottom</bottom>\n");
		push(@printerlist, "      </general>\n");
	    }
	    for my $ps (keys %{$imageableareas{$p1}}) {
		next if $ps eq 'Custom'; # We have done "Custom" already
		my ($left, $right, $top, $bottom, $width, $height);
		$left = $imageableareas{$p1}{$ps}{'left'};
		$right = $imageableareas{$p1}{$ps}{'right'};
		$top = $imageableareas{$p1}{$ps}{'top'};
		$bottom = $imageableareas{$p1}{$ps}{'bottom'};
		$width = $imageableareas{$p1}{$ps}{'width'};
		$height = $imageableareas{$p1}{$ps}{'height'};
		# If the <general> section serves for this paper size,
		# do not define an <exception>
		next if ((defined($cleft)) &&
			 ($left == $cleft) &&
			 ($right == $width - $cright) &&
			 ($top == $height - $ctop) &&
			 ($bottom == $cbottom));
		push(@printerlist, "      <exception PageSize=\"$ps\">\n");
		push(@printerlist, "        <absolute />\n");
		if ($left != $cleft) {
		    push(@printerlist, "        <left>$left</left>\n");
		}
		if ($right != $width - $cright) {
		    push(@printerlist, "        <right>$right</right>\n");
		}
		if ($top != $height - $ctop) {
		    push(@printerlist, "        <top>$top</top>\n");
		}
		if ($bottom != $cbottom) {
		    push(@printerlist, "        <bottom>$bottom" .
			 "</bottom>\n");
		}
		push(@printerlist, "      </exception>\n");
	    }
	    push(@printerlist, "    </margins>\n");
	    push(@printerlist, "  </printer>\n");
	} else {
	    # Printer IDs only
	    push(@printerlist, "  <printer><id>$id</id></printer>\n");
	}
    }
}
push (@printerlist, " </printers>\n");

$drivernameprefix = "gimp-print";
print STDERR "Using driver name prefix \"$drivernameprefix\"\n";

my $generalsubs = { 'STPVER' => $stpvers,
		    'DRVNAME' => $drivernameprefix,
		    'STPRINTERS' => join('', @printerlist) };

my $optiongroups = { 'PageSize' => 'General',
		     'InputSlot' => 'General',
		     'MediaType' => 'General',
		     'InkType' => 'General',
		     'PrintoutMode' => 'General',
		     'Resolution' => 'PrintoutMode',
		     'Quality' => 'PrintoutMode',
		     'Color' => 'PrintoutMode',
		     'ImageType' => 'PrintoutMode',
		     'Dither' => 'PrintoutMode',
		     'Gamma' => 'Adjustment',
		     'Density' => 'Adjustment',
		     'Brightness' => 'Adjustment',
		     'Contrast' => 'Adjustment',
		     'Saturation' => 'Adjustment',
		     'Cyan' => 'Adjustment',
		     'Magenta' => 'Adjustment',
		     'Yellow' => 'Adjustment' };

my @numericsubs = ('MINVAL', 'MAXVAL', 'DEFVAL');

# OK, make the db directory...
mkdir "foomatic-db", 0755 or
    die "Cannot create directory foomatic-db: $!\n"
     	unless -d "foomatic-db";

# Now do stuff, already.  Do the substitution into each file...
my $tmpl;
for $drivertype (@drivertypes) {
    $drivertypesuffix = "-$drivertype";
    $drivertypesuffix =~ s/-gs//;
    my $drvname = "$drivernameprefix$drivertypesuffix";
    $generalsubs->{'DRVNAME'} = $drvname;
    print "Generating Foomatic data for driver \"$drvname\"...\n";

    # OK, make the db heirarchy alongside the templates one...
    mkdir "foomatic-db/$drvname", 0755 or
	die "Cannot create directory foomatic-db/$drvname: $!\n"
     	    unless -d "foomatic-db/$drvname";
    mkdir "foomatic-db/$drvname/opt", 0755 or 
	die "Cannot create directory foomatic-db/$drvname/opt: $!\n"
	    unless -d "foomatic-db/$drvname/opt";
    mkdir "foomatic-db/$drvname/driver", 0755 or
	die "Cannot create directory foomatic-db/$drvname/driver: $!\n"
	    unless -d 'foomatic-db/$drvname/driver';

    opendir TDIR, "$templatedir-$drivertype" or
	die "Cannot open templates directory: $!\n";

    while ($tmpl=readdir(TDIR)) {

	# Only XML files
	next if ($tmpl !~ m!.+\.xml$!);
	# The "PrintoutMode" option is only supported by Foomatic 2.9.x or
	# newer
	next if ((!$foomatic3) && ($tmpl eq "PrintoutMode.xml"));

	my $fooopt = $tmpl;
	$fooopt =~ s!\.xml$!!;
	my $stpopt = $argnamemap{$fooopt};
	$stpopt = $fooopt if ! defined ($stpopt);

#	print STDERR "Argnamemap '$fooopt' => '$stpopt'\n";

	open TMPL, "$templatedir-$drivertype/$tmpl";
	my @datafile = <TMPL>;
	close TMPL;

	print STDERR "Processing $tmpl...";

	my $template = join('',@datafile);

	# First, do the generic substitutions.

	my ($substr);
	for $substr (keys(%$generalsubs)) {
	    my $substitution = $generalsubs->{$substr};
	    $template =~ s!\@\@$substr\@\@!$substitution!g;
	}

	# Put the options into PPD groups (Foomatic >= 2.9)

	if ($foomatic3) {
	    my $substitution = "\n   <arg_group>" . 
		$optiongroups->{$fooopt} . "</arg_group>";
	    $template =~ s!\@\@GROUP\@\@!$substitution!g;
	} else {
	    $template =~ s!\@\@GROUP\@\@!!g;
	}

	# Now do the numeric substitutions

	for $substr (@numericsubs) {
	    my $substitution = $stp_values{$substr}{$stpopt};
	    $template =~ s!\@\@$substr\@\@!$substitution!g;
	}

	# Now do special-purpose substitutions

	for $substr (keys(%{$funcs->{$fooopt}})) {
	    my $substitution = &{$funcs->{$fooopt}{$substr}}($stpopt);
	    if (defined($substitution)) {
		$template =~ s!\@\@$substr\@\@!$substitution!g;
	    }
	}

	# Any more?
	grep (m!\@\@([^\@]+)\@\@!g
	      && do { warn "  Unknown substitution $1 in $tmpl!\n"; },
	      split("\n",$template));

	# Finally, write out the new file

	# Options are under opt/
	my $dbfilename = lc("foomatic-db/$drvname/opt/$drvname-$tmpl");

	# Special case the actual driver file under driver/
	$dbfilename = "foomatic-db/$drvname/driver/$drvname.xml"
	    if ($tmpl eq 'gimp-print.xml');

	open NEWF, "> $dbfilename" or die "Cannot create $dbfilename: $!";
	print STDERR "writing $dbfilename...";
	print NEWF $template;
	print STDERR "done.\n";
	close NEWF;

    }

    closedir TDIR;

    # The paper size and resolution maps must be regenerated for the next
    # driver, because the "driverval"s are different for the different 
    # drivers. So delete the caches.
    undef $pagemap;
    undef %rescache;

}

sub get_ev_shortname {
    my ($val) = @_;
    $val =~ s/ //g;
    $val =~ s/\///g;
    $val =~ s/\://g;
    return $val;
}

sub get_ev_key {
    my ($val, $drv) = @_;
    return ("ev/$drv-" . get_ev_shortname($val));
}

sub build_ev {
    my ($stpopt) = @_;
    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @vals = ();

    # OK, now for each enum_val
    my $ev;
    for $ev (@{$ev_choices{$stpopt}}) {
	#  Put in the basic choice info: ev names, etc
	my $ev_longname = @$ev[1];
	my $ev_shortname = @$ev[0];

	# The GhostScript driver has no "RawCMYK" output type setting
	next if (($drivertype eq "gs") && ($stpopt eq "Color") &&
		 ($ev_shortname eq "RawCMYK"));

	my $ev_id = get_ev_key($ev_shortname, $drvname);
	my $ev_driverval;

	# Either call a per-option function to get the driverval, or
	# just use the string choice name.
	if (defined($drivervals->{$stpopt})) {
	    $ev_driverval = &{$drivervals->{$stpopt}}($ev_shortname);
	    die "Undefined driverval for option $stpopt value $ev_shortname!\n"
		if (! defined($ev));
	} else {
	    $ev_driverval = $ev_shortname;
	}
	# Remove "Roll" paper sizes, user has to use "Custom" instead.
	next if (($stpopt eq "PageSize") && ($ev_driverval eq ""));
	push (@vals,
	      "    <enum_val id='$ev_id'>\n",
	      "      <ev_longname><en>$ev_longname</en></ev_longname>\n",
	      "      <ev_shortname><en>$ev_shortname</en></ev_shortname>\n",
	      "      <ev_driverval>$ev_driverval</ev_driverval>\n",
	      "      <constraints>\n",
	      "        <!-- Assume the option doesn't apply... -->\n",
	      "        <constraint sense='false'>\n",
	      "          <driver>$drvname</driver>\n",
	      "        </constraint>\n");

	#   Build constraints for this particular choice
	my $stpprn;
	for $stpprn (keys(%stpdata)) {
	    my $fooprn;
	    for $fooprn (@{$mapstp{$stpprn}}) {
		if ($stpdata{$stpprn}{$stpopt}{$ev_shortname}) {
		    # OK, this choice applies to this printer
		    push (@vals,
			  "        <constraint sense='true'>\n",
			  "          <!-- $fooprn == $stpprn -->\n",
			  "          <driver>$drvname</driver>\n",
			  "          <printer>$fooprn</printer>\n",
			  "        </constraint>\n");
		}
	    }
	}

	push (@vals,
	      "      </constraints>\n",
	      "    </enum_val>\n");
    }

    return join('',
		"<enum_vals>\n",
		@vals,
		"  </enum_vals>\n");
}

sub build_cons {

    my ($stpopt) = @_;

    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @PNCONS = ();

    # For each stp printer...
    my $stpname;
    for $stpname (keys(%stpdata)) {

	if (0) {
	    print STDERR "    Processing gimp-print printer $stpname...\n";
	    print STDERR
		"      There are no foomatic printers mapped to $stpname!?\n"
		    if !$mapstp{$stpname};
	    print STDERR "      \%stpdata key is {$stpname}{$stpopt}\n";
	}

	# Add to this printer to argument constraints?
	if ($stpdata{$stpname}{$stpopt}) {

	    # What's the default value?
	    my $stpdef = $defaults{$stpname}{$stpopt};

	    # If there's no default, then this option doesn't apply to
	    # this printer.
	    if (defined($stpdef)) {

		my $foodefval = get_ev_key($stpdef, $drvname);

		if (0) {
		    print STDERR
			"      Default for $stpname/$stpopt is $stpdef aka $foodefval\n";
		}

		my $fooname;
		for $fooname (@{$mapstp{$stpname}}) {

		    if (0) {
			print STDERR
			    "      Printer $fooname takes option $stpopt.\n";
		    }

		    push (@PNCONS,
			  "    <constraint sense='true'>\n",
			  "      <driver>$drvname</driver>\n",
			  "      <printer>$fooname</printer><!-- gimp-print name: $stpname -->\n",
			  "      <arg_defval>$foodefval</arg_defval>\n",
			  "    </constraint>\n");
		}
	    }
	}

    }

    return join('',
		"<constraints>\n",
		@PNCONS,
		"  </constraints>\n");
}

sub optmap_pagesize {
    my ($value) = @_;

    if (!defined $pagemap) {
	open PUTIL, "./paper_sizes |" or die "Cannot run paper_sizes: $!\n";
	while (<PUTIL>) {
	    chomp;
	    $_ =~ m!^\s*(.+\S)\s+([0-9]+)\s+([0-9]+)\s*$!;
	    my ($name, $width, $height) = ($1, $2, $3);
	    if (($width > 0 and $height > 0) or
		($name eq "Custom")) {
		if ($drivertype eq "gs") {
		    $pagemap->{$name} = "$width $height";
		} else {
		    $pagemap->{$name} = "-dDEVICEWIDTHPOINTS=$width -dDEVICEHEIGHTPOINTS=$height";
		}
#		print STDERR "PageSize '$name' driverval '$width $height'\n";
	    }	
	}
	close PUTIL;
    }

    return $pagemap->{$value}
}

sub optmap_color {
    my ($value) = @_;
    if (defined $colormap->{$drivertype}{$value}) {
	return $colormap->{$drivertype}{$value};
    } else {
	die "Cannot map output type '$value'\n";
    }
}

sub build_model_cons {
    my ($stpopt) = @_;
    my $drvname = "$drivernameprefix$drivertypesuffix";


    # OK, this is funky.  For each stp model, we have a choice.  That
    # choice is valid for only the foo printers that correspond.  For
    # any given foo printer, there is *exactly one* available choice.
    # The defval is the one available choice.  Backends and
    # applications do not show options with only one choice; they just
    # select that choice.  So we don't bother to make pretty option
    # names or anything.
    #
    # See also build_model_ev()

    my @PNCONS = ();

    # For each stp printer...
    my $stpname;
    for $stpname (keys(%mapstp)) {

	# For each possible foo name
	my $fooname;
	for $fooname (@{$mapstp{$stpname}}) {

	    # What's the default value?
	    my $foodefval = get_ev_key($stpname, $drvname);

	    push (@PNCONS,
		  "    <constraint sense='true'>\n",
		  "      <driver>$drvname</driver>\n",
		  "      <printer>$fooname</printer>\n",
		  "      <arg_defval>$foodefval</arg_defval>\n",
		  "    </constraint>\n");
	}
    }

    return join('',
		"<constraints>\n",
		@PNCONS,
		"  </constraints>\n");


}

# See build_model_cons, above.
sub build_model_ev {
    my ($stpopt) = @_;
    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @vals = ();

    # OK, now for each enum_val
    my $ev;
    for $ev (keys(%mapstp)) {
	#  Put in the basic choice info: ev names, etc
	my $ev_shortname = $ev;
	my $ev_longname = $printer_name{$ev};
	my $ev_shortname = get_ev_shortname($ev);
	my $ev_id = get_ev_key($ev, $drvname);
	my $ev_driverval;
	if ($drivertype eq "gs") {
	    $ev_driverval = $ev;
	} else {
	    my $make;
	    if ($ev =~ /^escp2/) {
		$make = "EPSON";
	    } elsif ($ev =~ /^bjc/) {
		$make = "CANON";
	    } elsif ($ev =~ /^pcl/) {
		$make = "HEWLETT-PACKARD";
	    } elsif ($ev =~ /^lexmark/) {
		$make = "LEXMARK";
	    } else {
		die "Could not determine printer manufacturer from \"$ev\"!\n";
	    }
	    $ev_driverval = "-sDeviceManufacturer=$make -sDeviceModel=$ev";
	}
	push (@vals,
	      "    <enum_val id='$ev_id'>\n",
	      "      <ev_longname><en>$ev_longname</en></ev_longname>\n",
	      "      <ev_shortname><en>$ev_shortname</en></ev_shortname>\n",
	      "      <ev_driverval>$ev_driverval</ev_driverval>\n",
	      "      <constraints>\n",
	      "        <!-- Assume the option doesn't apply... -->\n",
	      "        <constraint sense='false'>\n",
	      "          <driver>$drvname</driver>\n",
	      "        </constraint>\n",
	      "        <!-- ...except to these: -->\n",
	      );

	# This stp Model value applies only to mapped foo printers
	my $fooprn;
	for $fooprn (@{$mapstp{$ev}}) {

	    # OK, this choice applies to this enum
	    push (@vals,
		  "        <constraint sense='true'>\n",
		  "          <!-- Model $ev for $fooprn -->\n",
		  "          <driver>$drvname</driver>\n",
		  "          <printer>$fooprn</printer>\n",
		  "        </constraint>\n");
	}

	push (@vals,
	      "      </constraints>\n",
	      "    </enum_val>\n");
    }

    return join('',
		"<enum_vals>\n",
		@vals,
		"  </enum_vals>\n");
}


# Stuff for Resolution.
#
# printer_options gives us Quality information.  We examine this to
# determine what to do for the gs resolution argument:
#
#  - Is this a 180/360/720 printer or a 150/300/600 printer?
#
#  - What are the legal resolutions?  Sort of parse and compute these
#    from the Resolution values.
#
# In the case of the GhostScript driver The driverval is "X Y", and gets
# passed in a /HWResolution ps clause, for the IJS driver it is "XxY" and
# gets passed via the "-r" command line option of GhostScript.

sub compute_resolutions {
    my ($stpname) = @_;

    my $drvname = "$drivernameprefix$drivertypesuffix";

    if (!defined($rescache{$stpname})) {

	my @reslist = ();
	my %hash;
	my $defval;

	my $qual;
	for $qual (keys(%{$stpdata{$stpname}{'Resolution'}})) {
	    $qual =~ m!(\d+)\s*(x\s*(\d+))?!;
	    my ($x, $y) = ($1, $3);
	    $y = $x if !defined($y);

	    my $r;
	    if ($drivertype eq "gs") {
		$r = {'x' => $x,
		      'y' => $y,
		      'driverval' => "$x $y",
		      'ev_key' => get_ev_key("res-$x-$y", $drvname)
		      };
	    } else {
		$r = {'x' => $x,
		      'y' => $y,
		      'driverval' => "${x}x${y}",
		      'ev_key' => get_ev_key("res-$x-$y", $drvname)
		      };
	    }
	    push (@reslist, $r);

	    # Default?
	    $defval = get_ev_key("res-$x-$y", $drvname)
		if ($qual eq $defaults{$stpname}{'Resolution'});

	    # Note that this resolution value exists
	    $resolutions{"$x $y"} = { 'x' => $x,
				      'y' => $y };

	    # Note that this printer takes this resolution
	    $hash{$x}{$y} = 1;

	}

	$rescache{$stpname}{'list'} = \@reslist;
	$rescache{$stpname}{'defval'} = $defval;
	$rescache{$stpname}{'takesit'} = \%hash;
    }

    return $rescache{$stpname};
}

sub do_all_res {
    my $n;
    for $n (keys(%mapstp)) {
	compute_resolutions($n);
    }
}

sub build_resolution_ev {
    my ($stpopt) = @_;
    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @vals = ();

    do_all_res();

    # OK, now for each possible resolution...
    my $ev;
    for $ev (keys(%resolutions)) {

	my ($x, $y) = ($resolutions{$ev}{'x'}, $resolutions{$ev}{'y'});

	#  Put in the basic choice info: ev names, etc
	my $ev_longname = "$x x $y DPI";
	my $ev_shortname = get_ev_shortname($ev_longname);
	my $ev_id = get_ev_key("res-$x-$y", $drvname);
	my $ev_driverval;
	if ($drivertype eq "gs") {
	    $ev_driverval = "$x $y";
	} else {
	    $ev_driverval = "${x}x${y}";
	}

	push (@vals,
	      "    <enum_val id='$ev_id'>\n",
	      "      <ev_longname><en>$ev_longname</en></ev_longname>\n",
	      "      <ev_shortname><en>$ev_shortname</en></ev_shortname>\n",
	      "      <ev_driverval>$ev_driverval</ev_driverval>\n",
	      "      <constraints>\n",
	      "        <!-- Assume the option doesn't apply... -->\n",
	      "        <constraint sense='false'>\n",
	      "          <driver>$drvname</driver>\n",
	      "        </constraint>\n",
	      "        <!-- ...except to these: -->\n",
	      );

	# Now, for each printer, put in a constraint if this
	# resolution makes sense or not...
	my $stpprn;
	for $stpprn (keys(%mapstp)) {

	    my $resobj = compute_resolutions($stpprn);
	    my $takesit = $resobj->{'takesit'}{$x}{$y};

	    if ($takesit) {
		my $fooprn;
		for $fooprn (@{$mapstp{$stpprn}}) {

#		    print STDERR "Printer $fooprn $stpprn uses ${x}x$y\n";

		    # OK, this choice applies to this enum
		    push (@vals,
			  "        <constraint sense='true'>\n",
			  "          <driver>$drvname</driver>\n",
			  "          <printer>$fooprn</printer><!-- stpname: $stpprn -->\n",
			  "        </constraint>\n");
		}
	    }

	}

	push (@vals,
	      "      </constraints>\n",
	      "    </enum_val>\n");
    }

    return join('',
		"<enum_vals>\n",
		@vals,
		"  </enum_vals>\n");
}

sub build_resolution_cons {
    my ($stpopt) = @_;
    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @PNCONS = ();

    # For each stp printer...
    my $stpname;
    for $stpname (keys(%mapstp)) {

	# Get some resolution info
	my $r = compute_resolutions($stpname);

	# For each possible foo name
	my $fooname;
	for $fooname (@{$mapstp{$stpname}}) {

	    # What's the default value?
	    my $foodefval = $r->{'defval'};

	    push (@PNCONS,
		  "    <constraint sense='true'>\n",
		  "      <driver>$drvname</driver>\n",
		  "      <printer>$fooname</printer><!-- stpname: $stpname -->\n",
		  "      <arg_defval>$foodefval</arg_defval>\n",
		  "    </constraint>\n");
	}
    }

    return join('',
		"<constraints>\n",
		@PNCONS,
		"  </constraints>\n");
}

sub build_printoutmode_ev {
    my ($stpopt) = @_;
    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @vals = ();

    # OK, now for each choice ("Draft", "Normal", ...) ...
    my $choice;
    for $choice (keys %modes) {
	# ... and each possible "<ev_driverval>" for it
	my $ev_driverval;
	for $ev_driverval (keys %{$modes{$choice}}) {
	    #  Put in the basic choice info: ev names, etc
	    my $ev_longname = $printoutmodechoices->{$choice};
	    my $ev_shortname = $choice;

	    my $ev_id = 
		get_ev_key($modes{$choice}{$ev_driverval}, $drvname);

	    push (@vals,
		  "    <enum_val id='$ev_id'>\n",
		  "      <ev_longname><en>$ev_longname</en></ev_longname>\n",
		  "      <ev_shortname><en>$ev_shortname</en></ev_shortname>\n",
		  "      <ev_driverval>$ev_driverval</ev_driverval>\n",
		  "      <constraints>\n",
		  "        <!-- Assume the option doesn't apply... -->\n",
		  "        <constraint sense='false'>\n",
		  "          <driver>$drvname</driver>\n",
		  "        </constraint>\n");

	    #   Build constraints for this particular ev_driverval
	    my $stpprn;
	    for $stpprn (keys(%stpdata)) {
		my $fooprn;
		for $fooprn (@{$mapstp{$stpprn}}) {
		    if ($printoutmode->{$stpprn}{$choice} eq 
			$ev_driverval) {
			# OK, this choice applies to this printer
			push (@vals,
			      "        <constraint sense='true'>\n",
			      "          <!-- $fooprn == $stpprn -->\n",
			      "          <driver>$drvname</driver>\n",
			      "          <printer>$fooprn</printer>\n",
			      "        </constraint>\n");
		    }
		}
	    }

	    push (@vals,
		  "      </constraints>\n",
		  "    </enum_val>\n");
	}
    }

    return join('',
		"<enum_vals>\n",
		@vals,
		"  </enum_vals>\n");
}

sub build_printoutmode_cons {
    my ($stpopt) = @_;
    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @PNCONS = ();

    # For each stp printer...
    my $stpname;
    for $stpname (keys(%mapstp)) {

	# For each possible foo name
	my $fooname;
	for $fooname (@{$mapstp{$stpname}}) {

	    # What's the default value (always the "Normal" mode)?
	    my $normalmode = $printoutmode->{$stpname}{'Normal'};
	    my $foodefval = get_ev_key($modes{'Normal'}{$normalmode}, 
				       $drvname);

	    push (@PNCONS,
		  "    <constraint sense='true'>\n",
		  "      <driver>$drvname</driver>\n",
		  "      <printer>$fooname</printer>\n",
		  "      <arg_defval>$foodefval</arg_defval>\n",
		  "    </constraint>\n");
	}
    }

    return join('',
		"<constraints>\n",
		@PNCONS,
		"  </constraints>\n");
}

sub qualityorder {
    # List of suffixes of the Quality choices
    my @suffixes = (
		    # General
		    "",
		    "dpi",
		    # Epson/Lexmark
		    "mw",
		    "mw2",
		    "sw",
		    "fol",
		    "fol2",
		    "fourp",
		    "uni",
		    "mwuni",
		    "mw2uni",
		    "swuni",
		    "foluni",
		    "fol2uni",
		    "fourpuni",
		    "hq",
		    "hquni",
		    "hq2",
		    "hq2uni",
		    # Canon
		    "dmt",
		    # HP
		    "mono",
		    );
    my ($a, $b) = @_;
    # Bring the suffixes to lower case
    my $first = lc($a);
    my $second = lc($b);
    # Check whether they are in the @suffixes list
    my $i;
    for ($i = 0; $i <= $#suffixes; $i++) {
	my $firstinlist = ($first eq $suffixes[$i]);
	my $secondinlist = ($second eq $suffixes[$i]);
	if (($firstinlist) && (!$secondinlist)) {return -1};
	if (($secondinlist) && (!$firstinlist)) {return 1};
	if (($firstinlist) && ($secondinlist)) {return 0};
    }

    # Equal quality
    die "The quality choice suffixes $a and $b are unknown!\n";
    return 0;

}

sub getprintoutmode {
    my $choicelongnames = {
	'Draft' => 'Draft (Economy)',
	'Draft.Gray' => 'Draft Grayscale (Economy)',
	'Normal' => 'Normal',
	'Normal.Gray' => 'Normal Grayscale',
	'High' => 'High Quality',
	'High.Gray' => 'High Quality Grayscale',
	'VeryHigh' => 'Very High Quality',
	'VeryHigh.Gray' => 'Very High Quality Grayscale',
	'Photo' => 'Photo',
	'Photo.Gray' => 'Photo Grayscale',
    };

    ### BASIC RULES

    # See mode-specific rules below

    # There must be always a "Normal" mode, this will be the default.

    # On black-and-white printers there are no modes with ".Gray"
    # specifier, the standard modes are already grayscale.

    # No "Photo" mode on laser printers.

    # If on a PCL printer "600mono" is the chose quality, it will be
    # replaced by "300dpi" in color mode (This can lead to a mode being
    # removed by the following two rules).

    # If "VeryHigh" has exactly the same settings as "High", "VeryHigh"
    # is left out.

    # If "High" has exactly the same settings as "Normal", "High"
    # is left out.

    # If nothing is found for a certain mode, this mode is left out.

    my $modes = {};
    # Treat all printers
    my $stpprn;
    for $stpprn (keys(%stpdata)) {
	my $modeinfo = {};
	my ($draftminres, $draftbestsymmetry, $draftlowestqualstr) = 
	    (99999999, 99999999, "xxx");
	my $normaldefaultqual = $defaults{$stpprn}{'Resolution'};
	my ($highmaxres, $highbestsymmetry, $highbestqualstr) = 
	    (0, 99999999, "");
	my ($veryhighmaxres, $veryhighbestsymmetry, $veryhighbestqualstr) = 
	    (0, 99999999, "");
	my ($photomaxres, $photobestsymmetry, $photobestqualstr) = 
	    (0, 99999999, "");
	# Go through all choices of the "Quality" option and find the
	# best values for the "PrintoutMode" option
	my $quality;
	for $quality (keys(%{$stpdata{$stpprn}{'Resolution'}})) {
	    my ($xres, $yres, $qualstr);
	    if ($quality =~ /^(\d+)x(\d+)(\D.*)$/) {
		$xres = $1;
		$yres = $2;
		$qualstr = $3;
	    } elsif ($quality =~ /^(\d+)(\D.*)$/) {
		$xres = $1;
		$yres = $1;
		$qualstr = $2;
	    } else {
		die "Invalid quality: $quality\n";
	    }
	    # Resolution in dots per square inch
	    my $respersquareinch = $xres * $yres;
	    # Symmetry: Shows how far from symmetric a resolution is,
	    # the smaller, the more symmetric, symmetric resolutions (as
	    # 300x300 dpi) give zero.
	    my $symmetry = abs(log($yres/$xres));

	    ### Mode: DRAFT

	    # Use always the lowest available resolution/quality, 
	    # preferrably symmetric resolutions,

	    # Do not use resolutions with less than 150 dpi in both
	    # demensions.

	    # VeryFast dithering, ImageType LineArt

	    if (($respersquareinch < $draftminres) ||
		(($respersquareinch == $draftminres) && 
		 ($symmetry < $draftbestsymmetry)) ||
		(($respersquareinch == $draftminres) &&
		 ($symmetry == $draftbestsymmetry) &&
		 (qualityorder($qualstr, $draftlowestqualstr) < 0))) {
		$draftbestsymmetry = $symmetry;
		$draftminres = $respersquareinch;
		$draftlowestqualstr = $qualstr;
		next if (($xres < 150) && # Resolution not lower than
			 ($yres < 150));  # 150x150, 360x120 allowed
		$modeinfo->{'Draft'} = {
		    'quality' => $quality,
		    'xres' => $xres,
		    'yres' => $yres,
		    'dither' => 'VeryFast',
		    'image' => 'LineArt'
		}
	    }

	    ### Mode: NORMAL

	    # Default resolution/quality of GIMP-Print, upgrade to 
	    # unidirectional if possible, use 600x600 dpi for
	    # Lexmark Z..

	    # Adaptive Hybrid dithering, ImageType Photographs

	    if ((($stpprn =~ /^lexmark\-z/) &&
		 ($xres == 600) && ($yres == 600) && ($qualstr eq "uni")) ||
		(($stpprn !~ /^lexmark\-z/) &&
		 (($quality eq "${normaldefaultqual}uni") ||
		  (($quality eq $normaldefaultqual) &&
		   (!defined($normal->{'quality'})))))) {
		$modeinfo->{'Normal'} = {
		    'quality' => $quality,
		    'xres' => $xres,
		    'yres' => $yres,
		    'dither' => 'Adaptive',
		    'image' => 'Photographs'
		}
	    }

	    ### Mode: HIGH

	    # High: The highest resolution which is not higher than
	    # 720x720 dpi (Lexmark Z..: 1200x1200 dpi),
	    # unidirectional if possible, 
	    # not "fol", "fourp", "hq", "hq2"

	    # Adaptive Hybrid dithering, ImageType Photographs

	    if (($respersquareinch > $highmaxres) ||
		(($respersquareinch == $highmaxres) && 
		 ($symmetry < $highbestsymmetry)) ||
		(($respersquareinch == $highmaxres) &&
		 ($symmetry == $highbestsymmetry) &&
		 (qualityorder($qualstr, $highbestqualstr) > 0))) {
		unless ((($stpprn !~ /^lexmark\-z/) && 
			 (($xres > 720) || # Resolution not higher than
			  ($yres > 720))) || # 720x720 for non Lexmark
			($xres > 1200) || # not bigger than 1200x1200
			($yres > 1200) || # in general
			($qualstr =~ /^(hq.*|fo.*)$/)) { # Not "hq", "hq2",
		                                         # "fol", "fourp"
		    $highbestsymmetry = $symmetry;
		    $highmaxres = $respersquareinch;
		    $highbestqualstr = $qualstr;
		    $modeinfo->{'High'} = {
			'quality' => $quality,
			'xres' => $xres,
			'yres' => $yres,
			'dither' => 'Adaptive',
			'image' => 'Photographs'
		    }
		}
	    }

	    ### Mode: VERY HIGH

	    # Use always the highest available resolution/quality, 
	    # preferrably symmetric resolutions,

	    # On Epsons: Maximum 1440x720, not "hq2".

	    # Adaptive Hybrid dithering, ImageType Photographs

	    if (($respersquareinch > $veryhighmaxres) ||
		(($respersquareinch == $veryhighmaxres) && 
		 ($symmetry < $veryhighbestsymmetry)) ||
		(($respersquareinch == $veryhighmaxres) &&
		 ($symmetry == $veryhighbestsymmetry) &&
		 (qualityorder($qualstr, $veryhighbestqualstr) > 0))) {
		unless (($stpprn =~ /^escp2/) && # Epson
			(($xres > 1440) || # Resolution not higher than
			 ($yres > 720) ||  # 1440x720
			 ($qualstr eq "hq2"))) {  # Not "hq2"
		    $veryhighbestsymmetry = $symmetry;
		    $veryhighmaxres = $respersquareinch;
		    $veryhighbestqualstr = $qualstr;
		    $modeinfo->{'VeryHigh'} = {
			'quality' => $quality,
			'xres' => $xres,
			'yres' => $yres,
			'dither' => 'Adaptive',
			'image' => 'Photographs'
		    }
		}
	    }

	    ### Mode: PHOTO

	    # Use always the highest available resolution/quality, 
	    # preferrably symmetric resolutions,

	    # On Epsons: Maximum 2880x720

	    # EvenTone dithering, ImageType Photographs

	    if (($respersquareinch > $photomaxres) ||
		(($respersquareinch == $photomaxres) && 
		 ($symmetry < $photobestsymmetry)) ||
		(($respersquareinch == $photomaxres) &&
		 ($symmetry == $photobestsymmetry) &&
		 (qualityorder($qualstr, $photobestqualstr) > 0))) {
		unless (($stpprn =~ /^escp2/) && # Epson
			(($xres > 2880) || # Resolution not higher than
			 ($yres > 720))) {  # 2880x720
		    $photobestsymmetry = $symmetry;
		    $photomaxres = $respersquareinch;
		    $photobestqualstr = $qualstr;
		    $modeinfo->{'Photo'} = {
			'quality' => $quality,
			'xres' => $xres,
			'yres' => $yres,
			'dither' => 'EvenTone',
			'image' => 'Photographs'
		    }
		}
	    }
	}

	# We must have a "Normal" mode for every printer.
	if (!defined($modeinfo->{'Normal'}{'quality'})) {
	    die "No 'Normal' mode for $stpprn!\n";
	}

	# Build the strings with the settings for the "PrintoutMode"
	# option
	for my $m (keys(%{$modeinfo})) {
	    # If we didn't find anything for a certain mode, skip this
	    # mode
	    next if (!defined($modeinfo->{$m}{'quality'}));
	    my $modestr =
		"GSResolution=$modeinfo->{$m}{'xres'}x" .
		"$modeinfo->{$m}{'yres'}DPI " .
		"Quality=$modeinfo->{$m}{'quality'} " .
		"Dither=$modeinfo->{$m}{'dither'} " .
		"ImageType=$modeinfo->{$m}{'image'}";
	    if (defined($stpdata{$stpprn}{'Color'}{'Color'})) { 
		# Color printer
		$modes->{$stpprn}{$m} = $modestr . " OutputType=Color";
		$modes->{$stpprn}{"$m.Gray"} = 
		    $modestr . " OutputType=Grayscale";
		# Some HP inkjets have a "600mono" quality mode which
		# is only available in Grayscale, replace this mode by
		# "300dpi" in the settings for color printing
		if ($modes->{$stpprn}{$m} =~ /600mono/) {
		    if(!defined($stpdata{$stpprn}{'Resolution'}{'300dpi'})){
			die "No '300dpi' mode for $stpprn!";
		    }
		    $modes->{$stpprn}{$m} =~ s/600x600DPI/300x300DPI/;
		    $modes->{$stpprn}{$m} =~ s/600mono/300dpi/;
		}
	    } else {
		# bw printer
		if ($stpprn =~ /^pcl\-[2-6][vls]?i?$/) { # Laser printer
		    # No 'Photo' mode on laser printers
		    next if ($m eq 'Photo');
		    # Always "VeryFast" dithering on laser printers
		    $modestr =~ s/(Dither=)\S+/$1VeryFast/;
		}
		$modes->{$stpprn}{$m} = $modestr . " OutputType=Grayscale";
	    }
	}
	# Remove 'VeryHigh' and 'High' if they are identical to lower
	# quality modes
	if ($modes->{$stpprn}{'VeryHigh'} eq 
	    $modes->{$stpprn}{'High'}) {
	    delete($modes->{$stpprn}{'VeryHigh'});
	}
	if ($modes->{$stpprn}{'High'} eq 
	    $modes->{$stpprn}{'Normal'}) {
	    delete($modes->{$stpprn}{'High'});
	}
	if (defined($stpdata{$stpprn}{'Color'}{'Color'})) { 
	    # Color printer
	    if ($modes->{$stpprn}{'VeryHigh.Gray'} eq 
		$modes->{$stpprn}{'High.Gray'}) {
		delete($modes->{$stpprn}{'VeryHigh.Gray'});
	    }
	    if ($modes->{$stpprn}{'High.Gray'} eq 
		$modes->{$stpprn}{'Normal.Gray'}) {
		delete($modes->{$stpprn}{'High.Gray'});
	    }
	}
    }

    return ($modes, $choicelongnames)
}

exit(0);