diff --git a/ppd/PPD.pm b/ppd/PPD.pm
index 4f4556197087601538e242e649212f8e66884354..4f983140d61769933e8cebf1624ee44bc7f5d9fd 100644
--- a/ppd/PPD.pm
+++ b/ppd/PPD.pm
@@ -211,24 +211,104 @@ sub int_option($) {
 	option($o);
 }
 
+sub is_false($) {
+	return $_[0] =~ m{^(None|False)$};
+}
+
 sub constrain($$$$) {
 	my ($name, $k1, $k2, $condition) = @_;
 	$name //= "$k1 vs. $k2";
+	$k1 ne $k2 or die "Want to create constrait between a keyword $k1 and itself\n";
 	my $kw1 = $keywords{$k1} or die "Want to create constraint for unknown keyword $k1\n";
 	my $kw2 = $keywords{$k2} or die "Want to create constraint for unknown keyword $k2\n";
 	$kw1->{Type} eq 'o' && $kw2->{Type} eq 'o' or die "Want to create constraint between non-option keywords $k1 and $k2\n";
+
+	# Evaluate which combinations are permitted
+	my @vals1 = map { $_->{Key} } @{$kw1->{Values}};
+	my @vals2 = map { $_->{Key} } @{$kw2->{Values}};
+	my %allow = ();
+	for my $v1 (@vals1) {
+		for my $v2 (@vals2) {
+			$allow{$v1}{$v2} = $condition->($v1, $v2) ? 1 : 0;
+		}
+	}
+
 	my @c = ();
-	for my $vv1 (@{$kw1->{Values}}) {
-		for my $vv2 (@{$kw2->{Values}}) {
-			my $v1 = $vv1->{Key};
-			my $v2 = $vv2->{Key};
-			if (! &{$condition}($v1, $v2)) {
-				push @c, { K1 => $k1, V1 => $v1, K2 => $k2, V2 => $v2 };
-				push @c, { K1 => $k2, V1 => $v2, K2 => $k1, V2 => $v1 };
+
+	my $case1 = sub {
+		# Special case 1: Forbid True & True
+		for my $v1 (@vals1) {
+			for my $v2 (@vals2) {
+				if (is_false($v1) || is_false($v2)) {
+					$allow{$v1}{$v2} or return;
+				} else {
+					!$allow{$v1}{$v2} or return;
+				}
 			}
 		}
-	}
-	push @constraints, { Name => $name, Pairs => [@c] } if @c;
+		push @c, { K1 => $k1, K2 => $k2 };
+		return 1;
+	};
+
+	my $case2 = sub {
+		# Special case 2: Forbid True & subset
+		my %res = ();
+		for my $v1 (@vals1) {
+			for my $v2 (@vals2) {
+				if (is_false($v1)) {
+					$allow{$v1}{$v2} or return;
+				} else {
+					my $a = $allow{$v1}{$v2};
+					$res{$v2} //= $a;
+					$res{$v2} == $a or return;
+				}
+			}
+		}
+		for my $v2 (@vals2) {
+			!$res{$v2} and push @c, { K1 => $k1, K2 => $k2, V2 => $v2 };
+		}
+		return 1;
+	};
+
+	my $case3 = sub {
+		# Special case 3: Forbid subset & True
+		my %res = ();
+		for my $v1 (@vals1) {
+			for my $v2 (@vals2) {
+				if (is_false($v2)) {
+					$allow{$v1}{$v2} or return;
+				} else {
+					my $a = $allow{$v1}{$v2};
+					$res{$v1} //= $a;
+					$res{$v1} == $a or return;
+				}
+			}
+		}
+		for my $v1 (@vals1) {
+			!$res{$v1} and push @c, { K1 => $k1, K2 => $k2, V1 => $v1 };
+		}
+		return 1;
+	};
+
+	my $case4 = sub {
+		# General case
+		for my $v1 (@vals1) {
+			for my $v2 (@vals2) {
+				$allow{$v1}{$v2} or push @c, { K1 => $k1, V1 => $v1, K2 => $k2, V2 => $v2 };
+			}
+		}
+	};
+
+	$case1->() || $case2->() || $case3->() || $case4->() || return;
+	@c or return;
+
+	push @constraints, {
+		Name => $name,
+		Pairs => [
+			@c,
+			map(+{ K1 => $_->{K2}, K2 => $_->{K1}, V1 => $_->{V2}, V2 => $_->{V1} }, @c),
+		],
+	};
 }
 
 ### Auxiliary functions ###
@@ -447,12 +527,14 @@ sub emit_constraints() {
 	for my $cc (@constraints) {
 		heading($cc->{Name});
 		for my $c (@{$cc->{Pairs}}) {
-			printf "*%sUIConstraints: *%s %s *%s %s\n",
+			my $c1 = $c->{K1};
+			$c1 .= ' ' . $c->{V1} if defined $c->{V1};
+			my $c2 = $c->{K2};
+			$c2 .= ' ' . $c->{V2} if defined $c->{V2};
+			printf "*%sUIConstraints: *%s *%s\n",
 				((!$keywords{$c->{K1}}->{Choice} || !$keywords{$c->{K2}}->{Choice})) ? "Non" : "",
-				$c->{K1},
-				$c->{V1},
-				$c->{K2},
-				$c->{V2};
+				$c1,
+				$c2;
 		}
 	}
 }
diff --git a/ppd/gen-nessie-xcpt b/ppd/gen-nessie-xcpt
index 21f361d15d45166ed78c333c5f365c148a2f56ce..eed2ab9dd8aa67ac29a9e8d8f7f8704eaeb4ebb2 100755
--- a/ppd/gen-nessie-xcpt
+++ b/ppd/gen-nessie-xcpt
@@ -1,8 +1,5 @@
 #!/usr/bin/perl
 
-# FIXME: cupsIPPFinishings
-# FIXME: cupsSingleFile?
-# FIXME: Constraints
 # FIXME: Path to XCPT filter
 
 use strict;
@@ -274,18 +271,6 @@ option({
 	],
 });
 
-# FIXME: Update
-#constrain(undef, 'InputSlot', 'MediaType', sub {
-#	my ($is, $mt) = @_;
-#	return !(($is eq 'Tray2' || $is eq 'Tray3') &&
-#		 ($mt eq 'Labels' || $mt eq 'Envelope'));
-#});
-
-#constrain(undef, 'Duplex', 'MediaType', sub {
-#	my ($dp, $mt) = @_;
-#	return !($dp ne 'None' && $mt =~ /^(Labels|Transparency|Bond)$/);
-#});
-
 define_ui_group({ Key => 'Quality', Name => 'Print Quality' });
 
 option({
@@ -584,4 +569,36 @@ ZapfChancery-MediumItalic: Standard "(003.000)" Standard ROM
 ZapfDingbats: Special "(002.000)" Special ROM
 AMEN
 
+# Various constraints
+
+constrain(undef, 'PageSize', 'MediaType', sub {
+	my ($ps, $mt) = @_;
+	return !($ps =~ m{^Env} && $mt ne 'Envelopes' && $mt ne 'AutoSelect');
+});
+
+constrain(undef, 'Duplex', 'PageSize', sub {
+	my ($dp, $ps) = @_;
+	return !($dp ne 'None' && $ps !~ m{^(A[345]|ISOB[345]|Letter|Legal)$});
+});
+
+constrain(undef, 'Duplex', 'MediaType', sub {
+	my ($dp, $mt) = @_;
+	return !($dp ne 'None' && $mt =~ /^(Labels|Transparency|Bond|Envelopes)$/);
+});
+
+constrain(undef, 'StapleLocation', 'PageSize', sub {
+	my ($sl, $ps) = @_;
+	return !($sl ne 'None' && $ps !~ m{^(A[345]|ISOB[345]|Letter|Legal)$});
+});
+
+constrain(undef, 'XRFold', 'StapleLocation', sub {
+	my ($f, $s) = @_;
+	return !($f ne 'None' && $s ne 'None');
+});
+
+constrain(undef, 'XRFold', 'XRPunch', sub {
+	my ($f, $s) = @_;
+	return !($f ne 'None' && $s ne 'None');
+});
+
 generate();