// Playwright. First read each text's rectangles (Range.getClientRects, clipped by any scrolling parent) and colour.
const tag = await page.addStyleTag({ content: ".fl-root * { color: transparent !important; transition: none !important; }" });
const png = (await page.screenshot({ type: "png" })).toString("base64");
await tag.evaluate((n) => n.remove());
// Then, in the page, draw the png into a 2D canvas and test each text against the pixels behind it.
const lin = (v) => { const u = v / 255; return u <= 0.04045 ? u / 12.92 : ((u + 0.055) / 1.055) ** 2.4; };
const lum = (r, g, b) => 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
function worstRatio(ctx, [x, y, w, h], [r, g, b, a]) {
const d = ctx.getImageData(x, y, w, h).data;
const ratios = [];
for (let i = 0; i < d.length; i += 4) {
// a translucent colour lands on the pixel behind it
const fg = lum(r * a + d[i] * (1 - a), g * a + d[i + 1] * (1 - a), b * a + d[i + 2] * (1 - a));
const bg = lum(d[i], d[i + 1], d[i + 2]);
ratios.push((Math.max(fg, bg) + 0.05) / (Math.min(fg, bg) + 0.05));
}
ratios.sort((p, q) => p - q);
return ratios[Math.floor(ratios.length * 0.01)] ?? 99; // the worst 1%
}