fix(cli): allow-occlusion on the covering element silences text_occluded (#6741)

* fix(cli): allow-occlusion on the covering element silences text_occluded

* fix(cli): allow-occlusion also works on graded video and does not excuse a card's own text
This commit is contained in:
Miguel Ángel
2026-10-29 12:00:55 +00:00
committed by GitHub
parent e1a8c7f173
commit 1a80b5407d
2 changed files with 56 additions and 1 deletions
@@ -1362,6 +1362,12 @@
return !!ctx && ctx === preserve3dContext(b);
}
// A covering element marked to cover text, or inside a marked wrapper that does not also hold the text.
function allowsOcclusionOf(hit, element) {
const allowed = authoredElement(hit).closest("[data-layout-allow-occlusion]");
return !!allowed && !allowed.contains(element);
}
// The opaque element painted over (x, y), or null when the topmost element
// there is related to the text, non-opaque, sharing a 3D context with it, or
// part of a transient crossfade overlap.
@@ -1377,7 +1383,7 @@
for (const hit of stack) {
if (!isForeignElement(element, hit)) return null;
// Pair-specific exemptions excuse this hit only; keep walking for deeper occluders.
if (hasIgnoreFlag(hit)) continue;
if (hasIgnoreFlag(hit) || allowsOcclusionOf(hit, element)) continue;
if (sharedPreserve3d(element, hit)) continue;
if (isCrossSceneTransitionOverlap(element, hit)) continue;
const matte = seenMattes && matteFrameFor(hit);
@@ -3262,6 +3262,55 @@ describe("layout-audit.browser occlusion", () => {
);
});
it("lets data-layout-allow-occlusion on the covering element silence text_occluded", () => {
const issues = (overlayAttrs?: string) =>
auditOcclusionScene({
overlayAttrs,
overlayStyle: { backgroundColor: "rgb(10, 10, 10)" },
topmostId: "overlay",
});
expect(issues().some((issue) => issue.code === "text_occluded")).toBe(true);
expect(
issues("data-layout-allow-occlusion").some((issue) => issue.code === "text_occluded"),
).toBe(false);
});
it("honours the flag on a graded video whose topmost element is its canvas stand-in", () => {
document.body.innerHTML = `
<div id="root" data-composition-id="main" data-width="1920" data-height="1080">
<div id="headline">Headline copy</div>
<video id="cutout" data-layout-allow-occlusion></video>
<canvas id="overlay" data-hf-color-grading-canvas></canvas>
</div>
`;
installOcclusionGeometry({
styleOverrides: { overlay: { backgroundColor: "rgb(10, 10, 10)" } },
headlineTextRect: rect({ left: 200, top: 500, width: 600, height: 80 }),
topmostId: "overlay",
});
installAuditScript();
expect(runAudit().some((issue) => issue.code === "text_occluded")).toBe(false);
});
it("still flags a cover inside a flagged wrapper that also holds the text", () => {
document.body.innerHTML = `
<div id="root" data-composition-id="main" data-width="1920" data-height="1080">
<div id="card" data-layout-allow-occlusion>
<div id="headline">Headline copy</div>
<div id="overlay"></div>
</div>
</div>
`;
installOcclusionGeometry({
styleOverrides: { overlay: { backgroundColor: "rgb(10, 10, 10)" } },
headlineTextRect: rect({ left: 200, top: 500, width: 600, height: 80 }),
topmostId: "overlay",
});
installAuditScript();
expect(runAudit().some((issue) => issue.code === "text_occluded")).toBe(true);
});
it("respects the data-layout-allow-occlusion opt-out", () => {
const issues = auditOcclusionScene({
headlineAttrs: "data-layout-allow-occlusion",