diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index 6a0173efd..2291be92f 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -58,6 +58,24 @@ const lang = await page.evaluate(() => document.documentElement.lang || 'en') // the bug it is, and no other assertion here would notice. const RTL = new Set(['ar', 'he', 'fa', 'ur', 'ps', 'sd', 'yi']) const dir = await page.evaluate(() => document.documentElement.dir || 'ltr') +// SCRIPT FONT. Some languages need a face the system stack does not carry — Hindi +// renders as tofu boxes in Georgia/Palatino/serif. When the packet declares one, +// it must actually LOAD; a declared-but-failed @font-face is silent, and the page +// then prints as empty rectangles with every other assertion still green. +// +// ⚠ This asserts the FONT LOADED, not that glyphs are correct. A general tofu +// detector was attempted and abandoned: comparing shaped width against a missing +// font false-positives on every system-covered script (Cyrillic, CJK, Arabic all +// legitimately measure the same in both stacks), and an ink-profile heuristic +// scored CORRECTLY-RENDERED Devanagari the same as tofu, because its baseline bar +// gives a uniform column profile. A detector that blocks working packets is worse +// than none. Read the pixels for that; this covers the mechanical half. +const fontStatus = await page.evaluate(async () => { + await document.fonts.ready + const faces = [...document.fonts] + return { declared: faces.length, loaded: faces.filter((f) => f.status === 'loaded').length, + names: faces.map((f) => `${f.family}:${f.status}`) } +}) // The catalog sits beside the generator that produced this packet, so derive it // from the packet's own path rather than hardcoding a machine-specific location. const catalogPath = process.env.PACKET_STRINGS @@ -145,6 +163,10 @@ if (r.rhythms !== r.rhythmsTotal) fails.push(`only ${r.rhythms}/${r.rhythmsTotal if (!r.hasMark5) fails.push('Mark 5:1-20 is NOT printed in full (Driver requirement for point 4)') if (r.metacamp !== 2) fails.push(`${r.metacamp} metacamp.org attributions, expected exactly 2`) if (r.mapFaults.length) fails.push(`map: ${r.mapFaults.join('; ')}`) +if (fontStatus.declared && fontStatus.loaded !== fontStatus.declared) { + fails.push(`a declared @font-face did not load (${fontStatus.names.join(', ')}) — ` + + 'text in that script will print as empty boxes') +} if (RTL.has(lang) && dir !== 'rtl') fails.push(`lang="${lang}" is right-to-left but dir="${dir}"`) if (!RTL.has(lang) && dir === 'rtl') fails.push(`lang="${lang}" is not RTL but dir="rtl"`) if (translated === 0) { @@ -162,6 +184,7 @@ console.log(` Mark 5 in full : ${r.hasMark5}`) console.log(` metacamp.org : ${r.metacamp}`) console.log(` map : ${r.mapFaults.length ? r.mapFaults.join('; ') : 'palette resolves, not black'}`) console.log(` page size : ${boxes[0] ? boxes[0].join(' x ') : '?'} pt`) +console.log(` script font : ${fontStatus.declared ? fontStatus.names.join(', ') : 'system stack'}`) console.log(` language : ${lang} (dir=${dir}) (${r.rhythms}/${r.rhythmsTotal} rhythms in ${lang}` + `${translated === null ? '' : `, ${translated} strings translated`})`)