validateTheme function
Every guardrail spec fails (empty = ships).
Implementation
List<GuardrailViolation> validateTheme(
ThemeSpec spec, {
GuardrailLimits? limits,
}) {
final lim = limits ?? GuardrailLimits.forSpec(spec);
final p = spec.palette;
final out = <GuardrailViolation>[];
final bgs = backgroundSamples(spec.background);
final backdrops = displayBackdrops(spec);
void ratio(GuardrailRule rule, double measured, double required) {
if (measured < required) {
out.add(GuardrailViolation(rule, measured, required));
}
}
void label(GuardrailRule rule, KeyColors k) => ratio(
rule,
_minContrast(k.label, faceSamples(k.face, bgs)),
lim.labelRatio,
);
label(GuardrailRule.digitLabel, p.digit);
label(GuardrailRule.operatorLabel, p.operator);
label(GuardrailRule.equalsLabel, p.equals);
label(GuardrailRule.functionLabel, p.function);
ratio(
GuardrailRule.displayText,
_minContrast(p.displayText, backdrops),
lim.displayRatio,
);
ratio(
GuardrailRule.errorText,
_minContrast(p.error, backdrops),
lim.displayRatio,
);
ratio(
GuardrailRule.previewText,
_minContrast(p.previewText, backdrops),
lim.previewRatio,
);
ratio(GuardrailRule.caret, _minContrast(p.caret, backdrops), lim.caretRatio);
ratio(
GuardrailRule.focusRing,
_minContrast(p.focusRing, bgs),
lim.focusRingRatio,
);
final digit = faceSamples(p.digit.face, bgs);
final op = faceSamples(p.operator.face, bgs);
final eq = faceSamples(p.equals.face, bgs);
if (spec.nonColourOperatorCue == OperatorCue.none) {
void distinct(GuardrailRule rule, List<int> face) {
final cr = _minFaceContrast(face, digit);
if (cr < lim.faceContrast) {
out.add(GuardrailViolation(rule, cr, lim.faceContrast, 'contrast'));
return;
}
final de = _minCvd(face, digit);
if (de < lim.cvdDeltaE) {
out.add(GuardrailViolation(rule, de, lim.cvdDeltaE, 'cvd'));
}
}
distinct(GuardrailRule.operatorVsDigit, op);
distinct(GuardrailRule.equalsVsDigit, eq);
}
final eqOp = _minCvd(eq, op);
if (eqOp < lim.equalsVsOperatorDeltaE) {
out.add(
GuardrailViolation(
GuardrailRule.equalsVsOperator,
eqOp,
lim.equalsVsOperatorDeltaE,
),
);
}
final ghost = p.lcdGhost;
if (ghost != null) {
var worst = 0.0;
for (final b in backdrops) {
final r = contrastRatio(compositeOver(ghost, b), b);
if (r > worst) worst = r;
}
if (worst > lcdGhostMaxRatio) {
out.add(
GuardrailViolation(
GuardrailRule.lcdGhost,
worst,
lcdGhostMaxRatio,
'maximum',
),
);
}
}
if (spec.keyStyle.kind == KeyStyleKind.soft) {
_validateSoft(spec, lim, bgs, out);
}
// Scoped by family (not by `KeyStyleKind.outlined`): Paper & ink draws
// outlined keys too, without the hard shadow. Neo-brutalism is the only
// family whose specs are outlined at engine v1–v3, so no output changes.
if (spec.family == ThemeFamily.neoBrutalism) {
_validateBrutal(spec, bgs, out);
}
if (spec.family == ThemeFamily.liquidGlass) {
out.addAll(validateGlass(spec, lim, bgs));
}
out.addAll(switch (spec.family) {
ThemeFamily.claymorphism => validateClaymorphism(spec, lim, bgs),
ThemeFamily.y2kChrome => validateY2kAero(spec, lim, bgs),
ThemeFamily.paperInk => validatePaperInk(spec, lim, bgs),
ThemeFamily.cyberpunk => validateCyberpunk(spec, lim, bgs),
ThemeFamily.vaporwave => validateVaporwave(spec, lim, bgs),
ThemeFamily.bauhaus => validateBauhaus(spec, lim, bgs),
_ => const <GuardrailViolation>[],
});
return out;
}