#!@PERL@
# -*- perl -*-

# Get command line options
use Getopt::Long;
Getopt::Long::Configure("no_ignore_case", "pass_through");
GetOptions("t=s" => \$opt_t,         # Template directory
	   "f=s" => \$opt_f,         # Foomatic version
           "d=s" => \@opt_d,         # which Drivers?
	   "s"   => \$opt_s,         # Generate simplified data
	   "x"   => \$opt_x);	     # Generate printer list

my $simplified = 0;
my $simplified_subopt = "";
my $simplified_suffix = "";
if ($opt_s) {
    $simplified = 1;
    $simplified_subopt = "-s";
    $simplified_suffix = "-simplified";
}
    

my $templatedir;
$templatedir = $opt_t;

my @drivertypes = ("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 (e. g. "5.0.0-beta4")
# @@STPREL@@         - the version number (e. g. "5.0")
# @@REMAP@@          - option remappings (to shorten names)
# @@DRVNAME@@        - driver name (ie gutenprint)
# @@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 = { 'Model' => { 'OPTCONSTRAINTS' => \&build_model_cons,
			   'ENUMVALS' => \&build_model_ev },
	      'RenderResolution' => { '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;

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

my $groupname_prefix = "Gutenprint";

my @parameter_class_names = (
			     "Printer Features",
			     "Output Control"
			     );

my @parameter_level_names = (
			     "Common",
			     "Extra 1",
			     "Extra 2",
			     "Extra 3",
			     "Extra 4",
			     "Extra 5"
			     );

my $option_remap = "";
my $opt_index = 0;

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

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

%foomap = ();
%mapfoo = ();
$errors = 0;

open PIPE, "./printers|" or die "Cannot run printers: $!\n";
print STDERR "Loading printers from ./printers...";
while(<PIPE>) {
    #print $_;
    next if m!^#!;
    eval $_ or die "\nCan't parse `$_' $@\n";
}
close PIPE or die "Cannot run printers: $!\n";
die "\nCannot continue\n" if ($errors > 0);
print STDERR "done.\n";

if ($opt_x) {
    my (%printer_long_name) = reverse %printer_name;
    for (sort keys %printer_long_name) {
	my ($pn) = $printer_long_name{$_};
	next if $printer_family{$pn} eq "ps" || $printer_family{$pn} eq "raw";
	printf("%s	%s	%s\n", $_, $pn, $foomap{$pn});
    }
    exit;
}

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

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

#print Dumper(%ev_choices);

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 (sort keys(%{$printoutmode})) {
	for $vtmp (sort 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}";
	    }
	}
    }
}

# Make list of needed Foomatic entries for the numerical options. If
# for one and the same numerical option there are printers with
# different value ranges, there must be made an extra Foomatic entry
# for each value range. Therefore the filenames of numerical options
# are numbered (eg. Contrast-1.xml).
for $a (sort keys(%stp_float_values)) {
    for $otmp (sort keys %{$stp_float_values{$a}}) {
	for $vtmp (sort keys %{$stp_float_values{$a}{$otmp}}) {
	    my $min = $stp_float_values{$a}{$otmp}{'MINVAL'};
	    my $max = $stp_float_values{$a}{$otmp}{'MAXVAL'};
	    my $def = $stp_float_values{$a}{$otmp}{'DEFVAL'};
	    # Skip options with invalid values, the library contains such
	    # options in the case when only one constant value is allowed
	    next if (($min >= $max) || ($def < $min) || ($def > $max));
	    my $minmax = "${min}_${max}";
	    if (!$seen_fnumopt{$otmp}{$minmax}++) {
		if (!defined($fnums{$otmp})) {
		    $fnums{$otmp} = 0;
		}
		$fnums{$otmp} ++;
		push (@floatnumopts_list, "${otmp}-$fnums{$otmp}");
		$numopt_ranges{"${otmp}-$fnums{$otmp}"}{'MINVAL'} = $min;
		$numopt_ranges{"${otmp}-$fnums{$otmp}"}{'MAXVAL'} = $max;
	    }
	}
    }
}
for $a (sort keys(%stp_int_values)) {
    for $otmp (sort keys %{$stp_int_values{$a}}) {
	for $vtmp (sort keys %{$stp_int_values{$a}{$otmp}}) {
	    my $min = $stp_int_values{$a}{$otmp}{'MINVAL'};
	    my $max = $stp_int_values{$a}{$otmp}{'MAXVAL'};
	    my $def = $stp_int_values{$a}{$otmp}{'DEFVAL'};
	    # Skip options with invalid values, the library contains such
	    # options in the case when only one constant value is allowed
	    next if (($min >= $max) || ($def < $min) || ($def > $max));
	    my $minmax = "${min}_${max}";
	    if (!$seen_inumopt{$otmp}{$minmax}++) {
		if (!defined($inums{$otmp})) {
		    $inums{$otmp} = 0;
		}
		$inums{$otmp} ++;
		push (@intnumopts_list, "${otmp}-$inums{$otmp}");
		$numopt_ranges{"${otmp}-$inums{$otmp}"}{'MINVAL'} = $min;
		$numopt_ranges{"${otmp}-$inums{$otmp}"}{'MAXVAL'} = $max;
	    }
	}
    }
}

for $a (sort keys(%stp_dimension_values)) {
    for $otmp (sort keys %{$stp_dimension_values{$a}}) {
	for $vtmp (sort keys %{$stp_dimension_values{$a}{$otmp}}) {
	    my $min = $stp_dimension_values{$a}{$otmp}{'MINVAL'};
	    my $max = $stp_dimension_values{$a}{$otmp}{'MAXVAL'};
	    my $def = $stp_dimension_values{$a}{$otmp}{'DEFVAL'};
	    # Skip options with invalid values, the library contains such
	    # options in the case when only one constant value is allowed
	    next if (($min >= $max) || ($def < $min) || ($def > $max));
	    my $minmax = "${min}_${max}";
	    if (!$seen_inumopt{$otmp}{$minmax}++) {
		if (!defined($inums{$otmp})) {
		    $inums{$otmp} = 0;
		}
		$inums{$otmp} ++;
		push (@dimensionnumopts_list, "${otmp}-$inums{$otmp}");
		$numopt_ranges{"${otmp}-$inums{$otmp}"}{'MINVAL'} = $min;
		$numopt_ranges{"${otmp}-$inums{$otmp}"}{'MAXVAL'} = $max;
	    }
	}
    }
}

#print join("\n", @floatnumopts_list);
#print join("\n", @intnumopts_list);
#print join("\n", @dimensionnumopts_list);

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

open PIPE, "./gutenprint-version -|" or die "Cannot run gutenprint-version: $!\n";
my $stprel = <PIPE>;
close PIPE or die "Cannot run gutenprint-version: $!\n";
chomp $stprel;

# Build <printers> clause...
my @printerlist = ();
push (@printerlist, " <printers>\n");
my $p1;
for $p1 (sort keys(%mapstp)) {
    push (@printerlist, "  <!-- gutenprint driver: $p1 -->\n");
    for my $id (@{$mapstp{$p1}}) {
	if ($foomatic3) {
	    # Add unprintable margins (only Foomatic 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 (sort 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 = "gutenprint";
print STDERR "Using driver name prefix \"$drivernameprefix\"\n";

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

my $optiongroups = { 'PageSize' => 'General',
		     'STP_InputSlot' => 'General',
		     'STP_MediaType' => 'General',
		     'PrintoutMode' => 'General',
		     'STP_RenderResolution' => 'General',
		     'STP_Quality' => 'General',
		     'Color' => 'General',
		     'STP_ImageType' => 'General'};

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

my $specialoutputfilenames = { 'Resolution' => 'PrinterResolution',
			       'RenderResolution' => 'Resolution' };

# 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//;
    $drivertypesuffix .= "$simplified_suffix.$stprel";
    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';

    my $order = 1000;
    my %numoptionorders;

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

    for $fooopt (sort(keys(%ev_choices)),
		 sort(@floatnumopts_list, @dimensionnumopts_list,
		      @intnumopts_list), 
                 "PrintoutMode", "Model",
		 "RenderResolution", "gutenprint") {

	# The "PrintoutMode" option is only supported by Foomatic 2.9.x or
	# newer (preliminary skipped always, do not remove the
	# infrastructure for "PrintoutMode", it will be re-activated later
	# on)
	next if ((!$foomatic3) && ($fooopt eq "PrintoutMode"));

	# "x_resolution" and "y_resolution" appear in %ev_choices but are
	# not option names, so skip them
	next if $fooopt =~ /^[xy]_resolution$/;

	my ($num_opt, $type, $tmpl);
	if (member($fooopt, @floatnumopts_list)) {
	    $num_opt = 1;
	    $type = "float";
	    $tmpl = "NumericalOptions.xml";
	} elsif (member($fooopt, @intnumopts_list)) {
	    $num_opt = 1;
	    $type = "int";
	    $tmpl = "NumericalOptions.xml";
	} elsif (member($fooopt, @dimensionnumopts_list)) {
	    $num_opt = 1;
	    $type = "dimension";
	    $tmpl = "NumericalOptions.xml";
	} else {
	    $num_opt = 0;
	    $type = "enum";
	    $tmpl = "${fooopt}.xml";
	    $tmpl = "OtherOptions.xml"
		if ! -r "$templatedir-$drivertype/$tmpl";
	}

	# Remove number appended to the end of the file name of
	# numerical options
	my $shortname = $fooopt;
	$shortname =~ s!-\d+$!!;

	#my $stpopt = $argnamemap{$shortname};
	#$stpopt = $shortname if ! defined ($stpopt);
	my $stpopt = $shortname;

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

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

	print STDERR "Processing \"$fooopt\"...";

	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 $group;
	    if (defined($optiongroups->{$shortname})) {
		# Group names given by table (esp. "General" for special
		# Options)
		$group = $optiongroups->{$shortname};
	    } elsif (defined($param_classes{$shortname}) &&
		     defined($param_levels{$shortname}) &&
		     $parameter_class_names[$param_classes{$shortname}] &&
		     $parameter_level_names[$param_levels{$shortname}]) {
		# Group names given by libgutenprint
		$group = $groupname_prefix . " " .
		    $parameter_class_names[$param_classes{$shortname}] . " " .
		    $parameter_level_names[$param_levels{$shortname}];
		# Remove the spaces (the name with spaces Foomatic will
		# generate automatically)
		$group =~ s! !!g;
	    } else {
		# All the above did not assign a group name? Use
		# "Miscellaneous" (Should usually not happen)
		$group = "Miscellaneous";
	    }
	    my $substitution = "\n   <arg_group>" .
		$group . "</arg_group>";
	    $template =~ s!\@\@GROUP\@\@!$substitution!g;
	} else {
	    $template =~ s!\@\@GROUP\@\@!!g;
	}

	# Now do the numeric substitutions

	$template =~ s!\@\@TYPE\@\@!$type!g;
	for $substr (@numericsubs) {
	    my $substitution;
	    my $substitution = $numopt_ranges{$fooopt}{$substr};
	    $template =~ s!\@\@$substr\@\@!$substitution!g;
	}

	# Substitutions for generic template files

	my $lowercaseshortname = lc($fooopt);
	my $subopt = $stpopt;

	# The maximum length of the short name of an option is 31
	# characters to be accepted in a PPD. The maximum length of a
	# main keyword in a PPD is 40 characters, with
	# "FoomaticRIPDefault" having 18, a short name of a numerical
	# option (only those have "FoomaticRIPDefault" entries) is 22.

	# We do not touch options whose names are short enough.

	# As this problem did not occur with Gimp Print 4.2.7, none of
	# the option names modified here already existed in
	# Gimp-Print, all options concerned are new Gutenprint
	# options. We also do not modify the parameters to be inserted
	# in the GhostScript command line, so the Gutenprint library
	# or the IJS server do not need to be modified.

	my $maxlen = ($type eq "enum" ? 31 : 22);
	my $shortopt = $subopt;
	if (length($subopt) > $maxlen) {
	    my $nostpopt = $stpopt;
	    $nostpopt =~ s/^STP_//;
	    my $stropt = sprintf("%03d", $opt_index);
	    $shortopt = "STP_OPT_$stropt";
	    $opt_index++;
	}
	if ($tmpl !~ /^$fooopt/) {
	    $template =~ s!\@\@LOWERCASESHORTNAME\@\@!$lowercaseshortname!g;
	    $template =~ s!\@\@LONGNAME\@\@!$longnames{$shortname}!g;
	    $template =~ s!\@\@SHORTNAME\@\@!$shortopt!g;
	    if ($shortname =~ /^STP_Enable(.*)$/) {
		$numoptionorders{$1} = $order;
		$template =~ s!\@\@ORDER\@\@!$order!g;
		$order += 10;
	    } else {
		$shortname =~ /^STP_(.*)$/;
		my $basename = $1;
		if (my $enableorder = $numoptionorders{$basename}) {
		    $enableorder ++;
		    $template =~ s!\@\@ORDER\@\@!$enableorder!g;
		} else {
		    $template =~ s!\@\@ORDER\@\@!$order!g;
		    $order += 10;
		}
	    }
	    $template =~ s!\@\@SPOT\@\@!B!g;
	    $template =~ s!\@\@PROTO\@\@!$subopt=\%s,!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;
	    }
	}

	if ($template =~ m!\@\@OPTCONSTRAINTS\@\@!) {
	    my $substitution = ($num_opt ? build_num_cons($fooopt) :
				build_cons($stpopt));
	    # Skip this option if there are no constraints (no printers
	    # using this option)
	    if ($substitution !~ m!</constraint>!s) {
		print STDERR "No printers using $fooopt\n";
		next;
	    }
	    if (defined($substitution)) {
		$template =~ s!\@\@OPTCONSTRAINTS\@\@!$substitution!g;
	    }
	}
	if ($template =~ m!\@\@ENUMVALS\@\@!) {
	    my $substitution = build_ev($stpopt);
	    # Skip this option if there are no choices (an enum option
	    # without choices does not make sense)
	    if ($substitution !~ m!</ev_shortname>!s) {
		print STDERR "No choices for $fooopt\n";
		next;
	    }
	    if (defined($substitution)) {
		$template =~ s!\@\@ENUMVALS\@\@!$substitution!g;
	    }
	}

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

	# File name for the option XML file

	$tmpl = "${fooopt}.xml" if $tmpl !~ /^$fooopt\.xml$/;

	if (my $f = $specialoutputfilenames->{$fooopt}) {
	    $tmpl = "${f}.xml";
	}

	# 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 'gutenprint.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;

}


# member( $a, @b ) returns 1 if $a is in @b, 0 otherwise.
sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 };

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];

	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 "STP_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 (sort 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 (sort keys(%stpdata)) {

	if (0) {
	    print STDERR "    Processing gutenprint 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 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><!-- gutenprint name: $stpname -->\n",
			  "      <arg_defval>$foodefval</arg_defval>\n",
			  "    </constraint>\n");
		}
	    }
	}

    }

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

sub build_num_cons {

    my ($foooptfile) = @_;

    my $drvname = "$drivernameprefix$drivertypesuffix";

    my @PNCONS = ();

    # Do we have a float or an int option?
    my $type;
    if (member($foooptfile, @floatnumopts_list)) {
       $type = "float";
    } elsif (member($foooptfile, @dimensionnumopts_list)) {
       $type = "dimension";
    } else {
       $type = "int";
    }

    # Name of the actual option
    my $stpopt = $foooptfile;
    $stpopt =~ s!-\d+$!!;

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

	if (0) {
	    print STDERR "    Processing gutenprint printer $stpname...\n";
	    print STDERR
		"      There are no foomatic printers mapped to $stpname!?\n"
		    if !$mapstp{$stpname};
	}

	# Add this printer to argument constraints? The printer must provide
	# this option and a default value for it.
	my $stpdef;
	if ((defined($stpdef =
		     $stp_float_values{$stpname}{$stpopt}{"DEFVAL"})) ||
	    (defined($stpdef =
		     $stp_int_values{$stpname}{$stpopt}{"DEFVAL"})) ||
	    (defined($stpdef =
		     $stp_dimension_values{$stpname}{$stpopt}{"DEFVAL"}))) {
	    # Find minimum and maximum
	    my ($min, $max);
	    if ($type eq "float") {
		$min = $stp_float_values{$stpname}{$stpopt}{"MINVAL"};
		$max = $stp_float_values{$stpname}{$stpopt}{"MAXVAL"};
	    } elsif ($type eq "dimension") {
		$min = $stp_dimension_values{$stpname}{$stpopt}{"MINVAL"};
		$max = $stp_dimension_values{$stpname}{$stpopt}{"MAXVAL"};
	    } else {
		$min = $stp_int_values{$stpname}{$stpopt}{"MINVAL"};
		$max = $stp_int_values{$stpname}{$stpopt}{"MAXVAL"};
	    }
	    # Does the range of this option with this printer match
	    # the Foomatic option entry we are building currently?
	    if (defined($numopt_ranges{$foooptfile}{'MINVAL'}) &&
		defined($numopt_ranges{$foooptfile}{'MAXVAL'}) &&
		($min == $numopt_ranges{$foooptfile}{'MINVAL'}) &&
		($max == $numopt_ranges{$foooptfile}{'MAXVAL'})) {

		if (0) {
		    print STDERR
			"      Default for $stpname/$stpopt is $stpdef\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><!-- gutenprint name: $stpname -->\n",
			  "      <arg_defval>$stpdef</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")) {
		$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 (sort 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 (sort keys(%mapstp)) {
	#  Put in the basic choice info: ev names, etc
	my $ev_shortname = $ev;
	my $ev_longname = $printer_name{$ev};
	my $make = $printer_make{$ev};
	my $ev_shortname = get_ev_shortname($ev);
	my $ev_id = get_ev_key($ev, $drvname);
	my $ev_driverval;
	$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.

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

    my $drvname = "$drivernameprefix$drivertypesuffix";

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

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

	my $qual;
	for $qual (sort keys(%{$stpdata{$stpname}{'STP_Resolution'}})) {
	    my ($x) = $stpdata{$stpname}{'x_resolution'}{$qual};
	    my ($y) = $stpdata{$stpname}{'y_resolution'}{$qual};

	    my $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}{'STP_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 (sort 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 = "${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 (sort 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><!-- gutenprint name: $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 (sort 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><!-- gutenprint name: $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 (sort keys %modes) {
	# ... and each possible "<ev_driverval>" for it
	my $ev_driverval;
	for $ev_driverval (sort 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 (sort 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 (sort 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 = (
		    # HP
		    "mono",
		    # General
		    "",
		    "dpi",
		    # Epson/Lexmark
		    "mw",
		    "mw2",
		    "sw",
		    "fol",
		    "fol2",
		    "fourp",
		    "uni",
		    "mwuni",
		    "mw2uni",
		    "swuni",
		    "foluni",
		    "fol2uni",
		    "fourpuni",
		    "hq",
		    "hquni",
		    "hq2",
		    "hq2uni",
		    "ov",
		    "oov",
		    # Canon
		    "dmt",
		    "dpi_high",
		    "dpi_high2",
		    "dpi_high3",
		    "dpi_high4",
		    "dpi_high5",
		    "dpi_highmono",
		    "dpi_highmono2",
		    "dpi_highmono3",
		    "dpi_highmono4",
		    "dpi_highmono5",
		    "dpi_mono",
		    "dpi_mono2",
		    "dpi_mono3",
		    "dpi_mono4",
		    "dpi_mono5",
		    "dpi_draft",
		    "dpi_draft2",
		    "dpi_draft3",
		    "dpi_draft4",
		    "dpi_draft5",
                    "dpi_std2",
                    "dpi_std3",
                    "dpi_std4",
                    "dpi_std5",
		    "dpi_draftmono",
		    "dpi_draftmono2",
		    "dpi_draftmono3",
		    "dpi_draftmono4",
		    "dpi_draftmono5",
		    "dpi_photohigh",
		    "dpi_photohigh2",
		    "dpi_photohigh3",
		    "dpi_photohigh4",
		    "dpi_photohigh5",
	            "dpi_tshirt",
		    "dpi_photomed",
		    "dpi_photomed2",
		    "dpi_photo",
		    "dpi_photo2",
		    "dpi_photo3",
		    "dpi_photo4",
		    "dpi_photo5",
		    "dpi_photodraft",
		    "dpi_photodraft2",
		    "dpi_photodraft3",
		    "dpi_photodraft4",
		    "dpi_photodraft5",
		    "dpi_photomonohigh",
		    "dpi_photomonohigh2",
		    "dpi_photomonomed",
		    "dpi_photomono",
		    "dpi_photomono2",
		    "dpi_photomonodraft",
		    "dpi_photomonodraft2",
                    "dpi_ohphigh",
                    "dpi_ohp",
                    "dpi_ohpdraft",
		    );
    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};
    }

    # Unknown qualities
    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 (sort keys(%stpdata)) {
	my $modeinfo = {};
	my ($draftminres, $draftbestsymmetry, $draftlowestqualstr) =
	    (99999999, 99999999, "xxx");
	my ($normalminres, $normalbestsymmetry, $normaluni,
	    $normallowestqualstr) =
	    (99999999, 99999999, 0, "xxx");
	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 (sort keys(%{$stpdata{$stpprn}{'STP_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 {
		$xres = $stpdata{$stpprn}{'x_resolution'};
		$yres = $stpdata{$stpprn}{'y_resolution'};
		$qualstr = $quality;
	    }
	    # 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.

	    # ImageType Text

	    my $qualitysetting =
		(defined($stpdata{$stpprn}{'STP_Quality'}) ?
		 (defined($stpdata{$stpprn}{'STP_Quality'}{'Draft'}) ?
		  'Draft' :
		  (defined($stpdata{$stpprn}{'STP_Quality'}{'Economy'}) ?
		   'Economy' :
		   (defined($stpdata{$stpprn}{'STP_Quality'}{'FastEconomy'}) ?
		    'FastEconomy' :
		    (defined($stpdata{$stpprn}{'STP_Quality'}{'Standard'}) ?
		     'Standard' : 'ERROR' )))) : '');
	    die ("\n\n'STP_Quality' option without 'Draft', 'Economy', " .
		 "'FastEconomy', and 'Standard' choices for the '$stpprn' " .
		 "device class!\n")
		if $qualitysetting eq 'ERROR';

	    my $imagesetting =
		(defined($stpdata{$stpprn}{'STP_ImageType'}) ?
		 (defined($stpdata{$stpprn}{'STP_ImageType'}{'TextGraphics'}) ?
		  'TextGraphics' :
		  (defined($stpdata{$stpprn}{'STP_ImageType'}{'TextGraphics'}) ?
		   'TextGraphics' :
		   (defined($stpdata{$stpprn}{'STP_ImageType'}{'LineArt'}) ?
		    'LineArt' :
		    (defined($stpdata{$stpprn}{'STP_ImageType'}{'Graphics'}) ?
		     'Graphics' : 'ERROR' )))) : '');
	    die ("\n\n'STP_ImageType' option without 'TextGraphics', " .
		 "'LineArt', and 'Graphics' choices for the '$stpprn' " .
		 "device class!\n")
		if $imagesetting eq 'ERROR';

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

	    ### Mode: NORMAL

	    # Lowest resolution which is at least 300x300 dpi (600x600 dpi
	    # for Lexmark Z..), low quality level, unidirectional if
	    # possible,

	    # ImageType TextGraphics

	    $qualitysetting =
		(defined($stpdata{$stpprn}{'STP_Quality'}) ?
		 (defined($stpdata{$stpprn}{'STP_Quality'}{'Standard'}) ?
		  'Standard' :
		  (defined($stpdata{$stpprn}{'STP_Quality'}{'High'}) ?
		   'High' :
		   (defined($stpdata{$stpprn}{'STP_Quality'}{'Draft'}) ?
		    'Draft' : 'ERROR' ))) : '');
	    die ("\n\n'STP_Quality' option without 'Draft', 'Standard', and " .
		 "'High' choices for the '$stpprn' device class!\n")
		if $qualitysetting eq 'ERROR';

	    $imagesetting =
		(defined($stpdata{$stpprn}{'STP_ImageType'}) ?
		 (defined($stpdata{$stpprn}{'STP_ImageType'}{'TextGraphics'}) ?
		  'TextGraphics' :
		  (defined($stpdata{$stpprn}{'STP_ImageType'}{'Graphics'}) ?
		   'Graphics' :
		   (defined($stpdata{$stpprn}{'STP_ImageType'}{'LineArt'}) ?
		    'LineArt' :
		    (defined($stpdata{$stpprn}{'STP_ImageType'}{'Photo'}) ?
		     'Photo' : 'ERROR' )))) : '');
	    die ("\n\n'STP_ImageType' option without 'TextGraphics', 'Graphics', " .
		 "'LineArt', and 'Photo' choices for the '$stpprn' " .
		 "device class!\n")
		if $imagesetting eq 'ERROR';

	    if (($respersquareinch < $normalminres) ||
		(($respersquareinch == $normalminres) &&
		 ($symmetry < $normalbestsymmetry)) ||
		(($respersquareinch == $normalminres) &&
		 ($symmetry == $normalbestsymmetry) &&
		 (($qualstr =~ /uni/) > $normaluni)) ||
		(($respersquareinch == $normalminres) &&
		 ($symmetry == $normalbestsymmetry) &&
		 (($qualstr =~ /uni/) == $normaluni) &&
		 (qualityorder($qualstr, $normallowestqualstr) < 0))) {
		unless ((($xres < 300) ||  # Resolution not lower than
			 ($yres < 300)) || # 300x300, (600x600 on Lexmark)
			(($stpprn =~ /^lexmark\-z/) &&
			 (($xres < 600) ||
			  ($yres < 600)))) {
		    $normalbestsymmetry = $symmetry;
		    $normalminres = $respersquareinch;
		    $normaluni = ($qualstr =~ /uni/);
		    $normallowestqualstr = $qualstr;
		    $modeinfo->{'Normal'} = {
			'stpres' => $quality,
			'quality' => $qualitysetting,
			'image' => $imagesetting,
			'xres' => $xres,
			'yres' => $yres
		    }
		}
	    }

	    ### 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"

	    # ImageType TextGraphics

	    $qualitysetting =
		(defined($stpdata{$stpprn}{'STP_Quality'}) ?
		 (defined($stpdata{$stpprn}{'STP_Quality'}{'High'}) ?
		  'High' :
		  (defined($stpdata{$stpprn}{'STP_Quality'}{'Best'}) ?
		   'Best' :
		   (defined($stpdata{$stpprn}{'STP_Quality'}{'Photo'}) ?
		    'Photo' :
		    (defined($stpdata{$stpprn}{'STP_Quality'}{'Standard'}) ?
		     'Standard' : 'ERROR' )))) : '');
	    die ("\n\n'STP_Quality' option without 'High', 'Best', " .
		 "'Photo', and 'Standard' choices for the '$stpprn' " .
		 "device class!\n")
		if $qualitysetting eq 'ERROR';

	    $imagesetting =
		(defined($stpdata{$stpprn}{'STP_ImageType'}) ?
		 (defined($stpdata{$stpprn}{'STP_ImageType'}{'TextGraphics'}) ?
		  'TextGraphics' :
		  (defined($stpdata{$stpprn}{'STP_ImageType'}{'Graphics'}) ?
		   'Graphics' :
		   (defined($stpdata{$stpprn}{'STP_ImageType'}{'Photo'}) ?
		    'Photo' :
		    (defined($stpdata{$stpprn}{'STP_ImageType'}{'LineArt'}) ?
		     'LineArt' : 'ERROR' )))) : '');
	    die ("\n\n'STP_ImageType' option without 'TextGraphics', 'Graphics', " .
		 "'LineArt', and 'Photo' choices for the '$stpprn' " .
		 "device class!\n")
		if $imagesetting eq 'ERROR';

	    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'} = {
			'stpres' => $quality,
			'quality' => $qualitysetting,
			'image' => $imagesetting,
			'xres' => $xres,
			'yres' => $yres
		    }
		}
	    }

	    ### Mode: VERY HIGH

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

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

	    # ImageType TextGraphics

	    $qualitysetting =
		(defined($stpdata{$stpprn}{'STP_Quality'}) ?
		 (defined($stpdata{$stpprn}{'STP_Quality'}{'Best'}) ?
		  'Best' :
		  (defined($stpdata{$stpprn}{'STP_Quality'}{'High'}) ?
		   'High' :
		   (defined($stpdata{$stpprn}{'STP_Quality'}{'Photo'}) ?
		    'Photo' :
		    (defined($stpdata{$stpprn}{'STP_Quality'}{'Standard'}) ?
		     'Standard' : 'ERROR' )))) : '');
	    die ("\n\n'STP_Quality' option without 'High', 'Best', " .
		 "'Photo', and 'Standard' choices for the '$stpprn' " .
		 "device class!\n")
		if $qualitysetting eq 'ERROR';

	    $imagesetting =
		(defined($stpdata{$stpprn}{'STP_ImageType'}) ?
		 (defined($stpdata{$stpprn}{'STP_ImageType'}{'TextGraphics'}) ?
		  'TextGraphics' :
		  (defined($stpdata{$stpprn}{'STP_ImageType'}{'Graphics'}) ?
		   'Graphics' :
		   (defined($stpdata{$stpprn}{'STP_ImageType'}{'Photo'}) ?
		    'Photo' :
		    (defined($stpdata{$stpprn}{'STP_ImageType'}{'LineArt'}) ?
		     'LineArt' : 'ERROR' )))) : '');
	    die ("\n\n'STP_ImageType' option without 'TextGraphics', 'Graphics', " .
		 "'LineArt', and 'Photo' choices for the '$stpprn' " .
		 "device class!\n")
		if $imagesetting eq 'ERROR';

	    if (($respersquareinch > $veryhighmaxres) ||
		(($respersquareinch == $veryhighmaxres) &&
		 ($symmetry < $veryhighbestsymmetry)) ||
		(($respersquareinch == $veryhighmaxres) &&
		 ($symmetry == $veryhighbestsymmetry) &&
		 (qualityorder($qualstr, $veryhighbestqualstr) > 0))) {
		unless (($xres > 1440) || # Resolution not higher than
			($yres > 720) ||  # 1440x720
			($qualstr eq "hq2")) { # Not "hq2"
		    $veryhighbestsymmetry = $symmetry;
		    $veryhighmaxres = $respersquareinch;
		    $veryhighbestqualstr = $qualstr;
		    $modeinfo->{'VeryHigh'} = {
			'stpres' => $quality,
			'quality' => $qualitysetting,
			'image' => $imagesetting,
			'xres' => $xres,
			'yres' => $yres
		    }
		}
	    }

	    ### Mode: PHOTO

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

	    # ImageType Photographs

	    $qualitysetting =
		(defined($stpdata{$stpprn}{'STP_Quality'}) ?
		 (defined($stpdata{$stpprn}{'STP_Quality'}{'HighPhoto'}) ?
		  'HighPhoto' :
		  (defined($stpdata{$stpprn}{'STP_Quality'}{'Photo'}) ?
		   'Photo' :
		   (defined($stpdata{$stpprn}{'STP_Quality'}{'UltraPhoto'}) ?
		    'UltraPhoto' :
		    (defined($stpdata{$stpprn}{'STP_Quality'}{'Best'}) ?
		     'Best' :
		     (defined($stpdata{$stpprn}{'STP_Quality'}{'High'}) ?
		      'High' :
		      (defined($stpdata{$stpprn}{'STP_Quality'}{'Standard'}) ?
		       'Standard' : 'ERROR' )))))) : '');
	    die ("\n\n'STP_Quality' option without 'HighPhoto', 'Photo', " .
		 "'UltraPhoto', 'Best', 'High', and 'Standard' choices " .
		 "for the '$stpprn' device class!\n")
		if $qualitysetting eq 'ERROR';

	    $imagesetting =
		(defined($stpdata{$stpprn}{'STP_ImageType'}) ?
		 (defined($stpdata{$stpprn}{'STP_ImageType'}{'Photo'}) ?
		  'Photo' :
		  (defined($stpdata{$stpprn}{'STP_ImageType'}{'Graphics'}) ?
		   'Graphics' :
		   (defined($stpdata{$stpprn}{'STP_ImageType'}{'TextGraphics'}) ?
		    'TextGraphics' :
		    (defined($stpdata{$stpprn}{'STP_ImageType'}{'LineArt'}) ?
		     'LineArt' : 'ERROR' )))) : '');
	    die ("\n\n'STP_ImageType' option without 'TextGraphics', 'Graphics', " .
		 "'LineArt', and 'Photo' choices for the '$stpprn' " .
		 "device class!\n")
		if $imagesetting eq 'ERROR';

	    if (($respersquareinch > $photomaxres) ||
		(($respersquareinch == $photomaxres) &&
		 ($symmetry < $photobestsymmetry)) ||
		(($respersquareinch == $photomaxres) &&
		 ($symmetry == $photobestsymmetry) &&
		 (qualityorder($qualstr, $photobestqualstr) > 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 =~ /^(hq2)$/)) { # Not "hq2"
		    $photobestsymmetry = $symmetry;
		    $photomaxres = $respersquareinch;
		    $photobestqualstr = $qualstr;
		    $modeinfo->{'Photo'} = {
			'stpres' => $quality,
			'quality' => $qualitysetting,
			'image' => $imagesetting,
			'xres' => $xres,
			'yres' => $yres
		    }
		}
	    }
	}

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

	# Build the strings with the settings for the "PrintoutMode"
	# option
	for my $m (sort keys(%{$modeinfo})) {
	    # If we didn't find anything for a certain mode, skip this
	    # mode
	    next if (!defined($modeinfo->{$m}{'stpres'}));
	    my $modestr =
		(defined($stpdata{$stpprn}{'STP_ImageType'}) ?
		 "STP_ImageType=$modeinfo->{$m}{'image'} " : "") .
		(defined($stpdata{$stpprn}{'STP_Quality'}) ?
		 "STP_Quality=$modeinfo->{$m}{'quality'} " : 
		 "STP_Resolution=$modeinfo->{$m}{'stpres'} ") .
		"Resolution=$modeinfo->{$m}{'xres'}x" .
		"$modeinfo->{$m}{'yres'}dpi";
	    if (defined($stpdata{$stpprn}{'Color'}{'Color'})) {
		# Color printer
		$modes->{$stpprn}{$m} = $modestr . " OutputType=Color";
		if (defined($stpdata{$stpprn}{'Color'}{'Grayscale'})) {
		    $modes->{$stpprn}{"$m.Gray"} =
			$modestr . " OutputType=Grayscale";
		} elsif (defined($stpdata{$stpprn}{'Color'}{'BlackAndWhite'})) {
		    $modes->{$stpprn}{"$m.Gray"} =
			$modestr . " OutputType=BlackAndWhite";
		}
		# 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}{'STP_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);
