fix(cli): consolidate layout and contrast audit correctness (#2401)

* fix(cli): respect transparent image pixels in occlusion audit

* test(cli): cover contained image letterboxing

* fix(cli): account for text strokes in contrast checks

* fix(cli): honor text overflow opt-outs

* fix(cli): skip contrast on transparent backdrops

* chore(skills): refresh generated manifest
This commit is contained in:
Miguel Ángel
2026-07-14 01:44:58 -04:00
committed by GitHub
parent 6fc92308d6
commit b98463ae3a
7 changed files with 369 additions and 11 deletions
@@ -220,6 +220,9 @@ window.__contrastAuditPrepare = function () {
var isSvgText = isSvgTextElement(el);
var fg = isSvgText ? tryParseSolidColor(cs.fill) || parseColor(cs.color) : parseColor(cs.color);
if (fg[3] <= 0.01) continue;
var strokeWidth = parseFloat(cs.webkitTextStrokeWidth || "0");
var stroke = strokeWidth > 0 ? tryParseSolidColor(cs.webkitTextStrokeColor || "") : null;
if (stroke && stroke[3] <= 0.01) stroke = null;
var fontSize = parseFloat(cs.fontSize);
var fontWeight = Number(cs.fontWeight) || 400;
@@ -251,6 +254,13 @@ window.__contrastAuditPrepare = function () {
origFillPriority = el.style.getPropertyPriority("fill");
el.style.setProperty("fill", "transparent", "important");
}
var origStrokeColor = null,
origStrokeColorPriority = null;
if (stroke) {
origStrokeColor = el.style.getPropertyValue("-webkit-text-stroke-color");
origStrokeColorPriority = el.style.getPropertyPriority("-webkit-text-stroke-color");
el.style.setProperty("-webkit-text-stroke-color", "transparent", "important");
}
restores.push({
el: el,
origTransition: origTransition,
@@ -259,6 +269,9 @@ window.__contrastAuditPrepare = function () {
origColorPriority: origColorPriority,
origFill: origFill,
origFillPriority: origFillPriority,
origStrokeColor: origStrokeColor,
origStrokeColorPriority: origStrokeColorPriority,
hasStroke: !!stroke,
isSvgText: isSvgText,
});
@@ -266,6 +279,7 @@ window.__contrastAuditPrepare = function () {
selector: selectorOf(el),
text: (el.textContent || "").trim().slice(0, 50),
fg: fg,
stroke: stroke,
fontSize: fontSize,
fontWeight: fontWeight,
large: large,
@@ -287,6 +301,15 @@ function __contrastAuditRestoreAll() {
if (r.origFill) r.el.style.setProperty("fill", r.origFill, r.origFillPriority);
else r.el.style.removeProperty("fill");
}
if (r.hasStroke) {
if (r.origStrokeColor)
r.el.style.setProperty(
"-webkit-text-stroke-color",
r.origStrokeColor,
r.origStrokeColorPriority,
);
else r.el.style.removeProperty("-webkit-text-stroke-color");
}
if (r.origTransition)
r.el.style.setProperty("transition", r.origTransition, r.origTransitionPriority);
else r.el.style.removeProperty("transition");
@@ -371,17 +394,25 @@ window.__contrastAuditFinish = async function (imgBase64, time, candidates) {
var stepY = Math.max(1, Math.floor((y1 - y0) / 6));
var rr = [],
gg = [],
bb = [];
bb = [],
aa = [];
for (var y = y0; y <= y1; y += stepY) {
for (var x = x0; x <= x1; x += stepX) {
var idx = (y * w + x) * 4;
rr.push(px[idx]);
gg.push(px[idx + 1]);
bb.push(px[idx + 2]);
aa.push(px[idx + 3]);
}
}
if (rr.length === 0) continue;
// A transparent sampled backdrop is supplied by the downstream editor,
// not this composition. WCAG contrast cannot be inferred until that live
// video/image is known, so do not invent an opaque browser default and
// hard-fail an otherwise valid alpha overlay.
if (median(aa) < 255) continue;
var bgR = median(rr),
bgG = median(gg),
bgB = median(bb);
@@ -394,6 +425,23 @@ window.__contrastAuditFinish = async function (imgBase64, time, candidates) {
var ratio = +wcagRatio(compR, compG, compB, bgR, bgG, bgB).toFixed(2);
// A solid text stroke is part of the visible glyph paint. Use whichever
// of fill or stroke provides the stronger edge contrast, rather than
// failing readable outlined captions based on fill alone.
if (c.stroke) {
var stroke = c.stroke;
var strokeR = Math.round(stroke[0] * stroke[3] + bgR * (1 - stroke[3]));
var strokeG = Math.round(stroke[1] * stroke[3] + bgG * (1 - stroke[3]));
var strokeB = Math.round(stroke[2] * stroke[3] + bgB * (1 - stroke[3]));
var strokeRatio = +wcagRatio(strokeR, strokeG, strokeB, bgR, bgG, bgB).toFixed(2);
if (strokeRatio > ratio) {
compR = strokeR;
compG = strokeG;
compB = strokeB;
ratio = strokeRatio;
}
}
out.push({
time: time,
selector: c.selector,