repairPair function

RepairedPair repairPair(
  1. Lch face,
  2. Lch label, {
  3. ContrastPolicy policy = ContrastPolicy.standard,
})

Repairs a label/face pair to policy (research §4.3 repair order):

  1. move the label lightness (keeping hue and chroma);
  2. if the label had to go to pure black/white or lost most of its chroma, move the face lightness 0.04 away from the label and retry (up to 4 rounds);
  3. fall back to a neutral label, then pure #000 / #FFF; if even those fail (targets above 4.583:1 on mid-tone faces), keep moving the face away until they pass.

The returned pair always meets policy.minRatio (and the dark-face target where it applies). Throws only for targets above 21:1.

Implementation

RepairedPair repairPair(
  Lch face,
  Lch label, {
  ContrastPolicy policy = ContrastPolicy.standard,
}) {
  if (policy.minRatio > 21) {
    throw ArgumentError.value(policy.minRatio, 'minRatio', 'above 21:1');
  }
  RepairedPair pair(int f, int l, double fl, int rounds) => (
    face: f,
    label: l,
    faceLch: (l: fl, c: face.c, h: face.h),
    rounds: rounds,
  );

  // Steps 1–2: chromatic label, moving the face away when needed.
  final labelLighter = label.l >= face.l;
  RepairedPair? fallback;
  var faceL = face.l;
  for (var round = 0; round < 4; round++) {
    final faceArgb = oklchToArgb(faceL, face.c, face.h);
    final solved = solveLabel(faceArgb, label, policy: policy);
    if (solved != null) {
      final got = argbToOklch(solved);
      final extreme = got.l <= 0.0001 || got.l >= 0.9999;
      final stripped = label.c > 0.01 && got.c < label.c * 0.5;
      if (!extreme && !stripped) return pair(faceArgb, solved, faceL, round);
      fallback ??= pair(faceArgb, solved, faceL, round);
    }
    faceL = clamp01(faceL + (labelLighter ? -0.04 : 0.04));
  }
  if (fallback != null) return fallback;

  // Step 3: neutral label (then black/white), moving the original face
  // toward whichever end gives the label room, until the floor is met.
  faceL = face.l;
  for (var round = 4; round < 40; round++) {
    final faceArgb = oklchToArgb(faceL, face.c, face.h);
    final faceY = luminanceArgb(faceArgb);
    for (final t in {policy.targetFor(faceY), policy.minRatio}) {
      final neutral = solveLightness(
        l0: label.l,
        c: 0,
        hDeg: 0,
        againstArgb: faceArgb,
        target: t,
      );
      if (neutral != null) return pair(faceArgb, neutral, faceL, round);
    }
    faceL = clamp01(faceL + (faceY < yEqualContrast ? -0.04 : 0.04));
  }
  throw StateError('repairPair did not converge');
}