From c8a10b3d3b3197ac1940b9f7498b290f3f93c528 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Sun, 6 Sep 2026 05:47:56 +0200 Subject: [PATCH] test: harden offline font raster evidence --- .../plan-27a-remediation-round-2.md | 22 ++++ .orchestration/plan-27a-remediation.md | 4 +- .../e2e/fixtures/offline-font-markers.test.ts | 44 +++++++ tests/e2e/fixtures/offline-font-markers.ts | 114 +++++++++++++++++ tests/e2e/fixtures/offline-fonts.ts | 5 +- tests/e2e/specs/offline-fonts.spec.ts | 115 +++++++++++------- 6 files changed, 252 insertions(+), 52 deletions(-) create mode 100644 .orchestration/plan-27a-remediation-round-2.md create mode 100644 tests/e2e/fixtures/offline-font-markers.test.ts create mode 100644 tests/e2e/fixtures/offline-font-markers.ts diff --git a/.orchestration/plan-27a-remediation-round-2.md b/.orchestration/plan-27a-remediation-round-2.md new file mode 100644 index 000000000..5fa66ff0c --- /dev/null +++ b/.orchestration/plan-27a-remediation-round-2.md @@ -0,0 +1,22 @@ +# Plan 27A remediation round 2 + +Date: 2026-09-06 +Base: `ae8e2f76f` + +## Focused fixes + +- Removed multilingual markers from the fixture headline. Each marker now exists only in its dedicated summary paragraph. +- Added pure marker-location helpers. Marker lookup joins PDF text items, supports markers split across items, rejects duplicate occurrences, and rejects non-whitespace neighbors that could contaminate a local crop. +- Raster measurement still scans with antialiasing padding but counts ink only inside the marker box, preventing neighboring glyphs from making blank or tofu-like evidence pass. +- Browser PDF download now separates download errors from post-download evidence errors. A received download with failed rasterization is reported as `unresolved-raster-evidence-error` and fails the opt-in test rather than passing as a generic download error. +- Added focused pure tests covering duplicate, split, neighboring, blank, and tofu-like cases. +- Removed trailing spaces from `plan-27a-remediation.md`. + +## Verification + +- `pnpm exec vitest run tests/e2e/fixtures/offline-font-markers.test.ts` — 5/5 passed. +- `pnpm exec biome check tests/e2e/specs/offline-fonts.spec.ts tests/e2e/fixtures/offline-fonts.ts tests/e2e/fixtures/offline-font-markers.ts tests/e2e/fixtures/offline-font-markers.test.ts` — passed. +- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list` — 4 tests collected. +- `git diff --check` — passed after remediation-document whitespace cleanup. + +Full diagnostic E2E remains opt-in and was not run in this focused round. Server outbound request capture and verifiable restart identity remain explicit external host-level blockers; no production resolver changes were made. diff --git a/.orchestration/plan-27a-remediation.md b/.orchestration/plan-27a-remediation.md index c96cd6ecb..f445acf14 100644 --- a/.orchestration/plan-27a-remediation.md +++ b/.orchestration/plan-27a-remediation.md @@ -1,7 +1,7 @@ # Plan 27A remediation -Date: 2026-09-06 -Base: `61b58ae9a` +Date: 2026-09-06 +Base: `61b58ae9a` Scope: concrete findings from `.orchestration/plan-27a-independent-review.md` only. ## Remediated findings diff --git a/tests/e2e/fixtures/offline-font-markers.test.ts b/tests/e2e/fixtures/offline-font-markers.test.ts new file mode 100644 index 000000000..3a0462dd5 --- /dev/null +++ b/tests/e2e/fixtures/offline-font-markers.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; +import { classifyRasterInk, locatePdfMarkerBoxes } from "./offline-font-markers"; + +const marker = { name: "cjk", marker: "简体中文" } as const; + +const item = (str: string, x = 10, y = 100, width = str.length * 5) => ({ + str, + x, + y, + width, + height: 12, +}); + +describe("locatePdfMarkerBoxes", () => { + it("rejects duplicate marker sources instead of selecting an arbitrary crop", () => { + const result = locatePdfMarkerBoxes([item(marker.marker), item(marker.marker, 10, 70)], [marker], 200); + + expect(result).toEqual([{ ...marker, box: null }]); + }); + + it("unions text items when one marker is split across PDF text items", () => { + const result = locatePdfMarkerBoxes([item("简体"), item("中文", 20)], [marker], 200); + + expect(result[0]?.box).toEqual({ x: 10, y: 88, width: 20, height: 12 }); + }); + + it("rejects text-item neighbors that could contaminate marker-local raster evidence", () => { + const result = locatePdfMarkerBoxes([item(`prefix${marker.marker}suffix`)], [marker], 200); + + expect(result).toEqual([{ ...marker, box: null }]); + }); +}); + +describe("classifyRasterInk", () => { + it("does not treat a blank crop as visible", () => { + expect(classifyRasterInk({ inkPixels: 0, interiorInk: 0, trimmedWidth: 0, trimmedHeight: 0 })).toBe("blank"); + }); + + it("does not treat tofu-like outline as visible", () => { + expect(classifyRasterInk({ inkPixels: 100, interiorInk: 0, trimmedWidth: 20, trimmedHeight: 20 })).toBe( + "tofu-like", + ); + }); +}); diff --git a/tests/e2e/fixtures/offline-font-markers.ts b/tests/e2e/fixtures/offline-font-markers.ts new file mode 100644 index 000000000..a1d6037e4 --- /dev/null +++ b/tests/e2e/fixtures/offline-font-markers.ts @@ -0,0 +1,114 @@ +export type PdfTextItem = { + str: string; + x: number; + y: number; + width: number; + height: number; +}; + +export type PdfMarker = { + name: string; + marker: string; +}; + +export type PdfMarkerBox = { + x: number; + y: number; + width: number; + height: number; +}; + +export type PdfMarkerLocation = TMarker & { + box: PdfMarkerBox | null; +}; + +export type RasterInkMeasurements = { + inkPixels: number; + interiorInk: number; + trimmedWidth: number; + trimmedHeight: number; +}; + +export type RasterGlyphStatus = "visible" | "blank" | "tofu-like"; + +type TextItemRange = PdfTextItem & { + start: number; + end: number; +}; + +const textItemRanges = (textItems: PdfTextItem[]): TextItemRange[] => { + let offset = 0; + return textItems.map((item) => { + const start = offset; + offset += item.str.length; + return { ...item, start, end: offset }; + }); +}; + +const findMarkerOccurrences = (text: string, marker: string): number[] => { + if (marker.length === 0) return []; + + const occurrences: number[] = []; + let start = 0; + while (start <= text.length - marker.length) { + const match = text.indexOf(marker, start); + if (match === -1) break; + occurrences.push(match); + start = match + marker.length; + } + return occurrences; +}; + +export const locatePdfMarkerBoxes = ( + textItems: PdfTextItem[], + markers: readonly TMarker[], + pageHeight: number, +): PdfMarkerLocation[] => { + const ranges = textItemRanges(textItems); + const text = textItems.map((item) => item.str).join(""); + + return markers.map((marker) => { + const [start] = findMarkerOccurrences(text, marker.marker); + const end = start === undefined ? undefined : start + marker.marker.length; + if (start === undefined || end === undefined || findMarkerOccurrences(text, marker.marker).length !== 1) { + return { ...marker, box: null }; + } + + const coveredRanges = ranges.filter((range) => range.start < end && range.end > start); + const firstRange = coveredRanges[0]; + const lastRange = coveredRanges.at(-1); + if (!firstRange || !lastRange) return { ...marker, box: null }; + + const firstLocalStart = start - firstRange.start; + const lastLocalEnd = end - lastRange.start; + const leadingText = firstRange.str.slice(0, firstLocalStart); + const trailingText = lastRange.str.slice(lastLocalEnd); + if (leadingText.trim() || trailingText.trim()) return { ...marker, box: null }; + + const left = Math.min(...coveredRanges.map((range) => range.x)); + const top = Math.min(...coveredRanges.map((range) => pageHeight - range.y - Math.max(range.height, 1))); + const right = Math.max(...coveredRanges.map((range) => range.x + Math.max(range.width, 1))); + const bottom = Math.max(...coveredRanges.map((range) => pageHeight - range.y)); + + return { + ...marker, + box: { + x: left, + y: top, + width: Math.max(right - left, 1), + height: Math.max(bottom - top, 1), + }, + }; + }); +}; + +export const classifyRasterInk = ({ + inkPixels, + interiorInk, + trimmedWidth, + trimmedHeight, +}: RasterInkMeasurements): RasterGlyphStatus => { + if (inkPixels === 0) return "blank"; + const interiorRatio = interiorInk / Math.max(inkPixels, 1); + return interiorRatio < 0.08 && trimmedWidth >= 8 && trimmedHeight >= 8 ? "tofu-like" : "visible"; +}; diff --git a/tests/e2e/fixtures/offline-fonts.ts b/tests/e2e/fixtures/offline-fonts.ts index 38275ed06..912a3ea94 100644 --- a/tests/e2e/fixtures/offline-fonts.ts +++ b/tests/e2e/fixtures/offline-fonts.ts @@ -10,7 +10,6 @@ export const offlineFontScriptSamples = [ { name: "emoji", marker: "Emoji 🚀" }, ] as const; -const offlineFontFixtureText = offlineFontScriptSamples.map(({ marker }) => marker).join(" | "); const offlineFontFixtureSummary = offlineFontScriptSamples.map(({ marker }) => `

${marker}

`).join(""); export type OfflineFontResumeFixture = { @@ -31,8 +30,8 @@ export async function seedOfflineFontResume(page: Page): Promise = [ { path: ["picture", "hidden"], value: true }, { path: ["basics", "name"], value: "Offline Font Diagnostic Fixture" }, - { path: ["basics", "headline"], value: offlineFontFixtureText }, - // Keep markers in separate blocks so raster evidence can map each script to a local PDF crop. + { path: ["basics", "headline"], value: "Offline font diagnostic" }, + // Keep each marker in one dedicated block so raster evidence can map each script to a local PDF crop. { path: ["summary", "content"], value: offlineFontFixtureSummary }, { path: ["metadata", "typography", "body", "fontFamily"], value: "IBM Plex Serif" }, { path: ["metadata", "typography", "body", "fontWeights"], value: ["400", "700"] }, diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts index f1bda9261..faec317e5 100644 --- a/tests/e2e/specs/offline-fonts.spec.ts +++ b/tests/e2e/specs/offline-fonts.spec.ts @@ -2,6 +2,7 @@ import type { Browser, BrowserContext, Page, TestInfo } from "@playwright/test"; import { readFile, writeFile } from "node:fs/promises"; import { createRequire } from "node:module"; import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs"; +import { locatePdfMarkerBoxes } from "../fixtures/offline-font-markers"; import { offlineFontScriptSamples, seedOfflineFontResume } from "../fixtures/offline-fonts"; import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; @@ -82,9 +83,11 @@ test.describe("offline font diagnostic", () => { const pages: string[] = []; for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) { const pdfPage = await document.getPage(pageNumber); - pages.push(...(await pdfPage.getTextContent()).items.flatMap((item) => ("str" in item ? [item.str] : []))); + pages.push( + (await pdfPage.getTextContent()).items.flatMap((item) => ("str" in item ? [item.str] : [])).join(""), + ); } - return pages.join(" "); + return pages.join("\n"); } finally { await loadingTask.destroy(); } @@ -114,8 +117,8 @@ test.describe("offline font diagnostic", () => { }); }); - return page.evaluate( - async ({ bytes, markers }) => { + const textEvidence = await page.evaluate( + async ({ bytes }) => { const moduleUrl = `${location.origin}/__offline_font_pdfjs/pdf.mjs`; const pdfjs: typeof import("pdfjs-dist/legacy/build/pdf.mjs") = await import(moduleUrl); pdfjs.GlobalWorkerOptions.workerSrc = `${location.origin}/__offline_font_pdfjs/worker.mjs`; @@ -124,20 +127,40 @@ test.describe("offline font diagnostic", () => { const pdfDocument = await loadingTask.promise; const pdfPage = await pdfDocument.getPage(1); const textContent = await pdfPage.getTextContent(); - const textItems = textContent.items.flatMap((item) => - "str" in item - ? [ - { - str: item.str, - x: item.transform[4] ?? 0, - y: item.transform[5] ?? 0, - width: item.width, - height: Math.max(item.height, Math.abs(item.transform[3] ?? 0), 1), - }, - ] - : [], - ); - const textLayer = textItems.map(({ str }) => str).join(" "); + return { + pageHeight: pdfPage.getViewport({ scale: 1 }).height, + textItems: textContent.items.flatMap((item) => + "str" in item + ? [ + { + str: item.str, + x: item.transform[4] ?? 0, + y: item.transform[5] ?? 0, + width: item.width, + height: Math.max(item.height, Math.abs(item.transform[3] ?? 0), 1), + }, + ] + : [], + ), + }; + } finally { + await loadingTask.destroy(); + } + }, + { bytes: Array.from(bytes) }, + ); + const boxes = locatePdfMarkerBoxes(textEvidence.textItems, offlineFontScriptSamples, textEvidence.pageHeight); + const textLayerMarkers = extractedMarkerResult(textEvidence.textItems.map(({ str }) => str).join("")); + + const rasterEvidence = await page.evaluate( + async ({ bytes, boxes }) => { + const moduleUrl = `${location.origin}/__offline_font_pdfjs/pdf.mjs`; + const pdfjs: typeof import("pdfjs-dist/legacy/build/pdf.mjs") = await import(moduleUrl); + pdfjs.GlobalWorkerOptions.workerSrc = `${location.origin}/__offline_font_pdfjs/worker.mjs`; + const loadingTask = pdfjs.getDocument({ data: Uint8Array.from(bytes), useSystemFonts: false }); + try { + const pdfDocument = await loadingTask.promise; + const pdfPage = await pdfDocument.getPage(1); const baseViewport = pdfPage.getViewport({ scale: 1 }); const rasterScale = 4; const viewport = pdfPage.getViewport({ scale: rasterScale }); @@ -154,29 +177,16 @@ test.describe("offline font diagnostic", () => { background: "white", }).promise; - const boxes = markers.map((sample) => { - const item = textItems.find(({ str }) => str.includes(sample.marker)); - if (!item) return { name: sample.name, marker: sample.marker, box: null }; - const height = Math.max(item.height, 1); - const baseline = baseViewport.height - item.y; - return { - name: sample.name, - marker: sample.marker, - box: { - x: item.x, - y: baseline - height, - width: Math.max(item.width, height), - height, - }, - }; - }); - function measure(canvas: HTMLCanvasElement, scale: number): RasterGlyphEvidence[] { const context = canvas.getContext("2d"); if (!context) throw new Error("Missing PDF preview raster context."); const pixels = context.getImageData(0, 0, canvas.width, canvas.height); return boxes.map(({ name, box }) => { if (!box) return { name, status: "not-located", inkPixels: 0, trimmedWidth: 0, trimmedHeight: 0 }; + const markerLeft = box.x * scale; + const markerTop = box.y * scale; + const markerRight = (box.x + box.width) * scale; + const markerBottom = (box.y + box.height) * scale; const padding = 2 * scale; const left = Math.max(0, Math.floor(box.x * scale - padding)); const top = Math.max(0, Math.floor(box.y * scale - padding)); @@ -189,6 +199,9 @@ test.describe("offline font diagnostic", () => { let maxY = top; for (let y = top; y < bottom; y += 1) { for (let x = left; x < right; x += 1) { + // Scan with padding for antialiasing, but count ink only inside marker box. + // Neighboring glyphs must never make blank/tofu marker evidence pass. + if (x < markerLeft || x >= markerRight || y < markerTop || y >= markerBottom) continue; const index = (y * pixels.width + x) * 4; const red = pixels.data[index] ?? 255; const green = pixels.data[index + 1] ?? 255; @@ -235,9 +248,6 @@ test.describe("offline font diagnostic", () => { const preview = document.querySelector('[aria-hidden="false"] canvas'); return { rasterDataUrl: raster.toDataURL(), - textLayerMarkers: Object.fromEntries( - markers.map((sample) => [sample.name, textLayer.includes(sample.marker)]), - ), referenceGlyphs: measure(raster, rasterScale), previewGlyphs: preview ? measure(preview, preview.width / baseViewport.width) : [], }; @@ -245,8 +255,9 @@ test.describe("offline font diagnostic", () => { await loadingTask.destroy(); } }, - { bytes: Array.from(bytes), markers: offlineFontScriptSamples }, + { bytes: Array.from(bytes), boxes }, ); + return { ...rasterEvidence, textLayerMarkers }; } async function report(testInfo: TestInfo, name: string, reportData: Record) { @@ -335,6 +346,7 @@ test.describe("offline font diagnostic", () => { let markerResultValue: PdfMarkers | null = null; let rasterEvidence: PdfRasterEvidence | null = null; let downloadStatus = "not-started"; + let rasterEvidenceStatus = "not-attempted"; try { await page.goto(fixture.builderURL); await openSidebarSection(page, "Export"); @@ -346,26 +358,35 @@ test.describe("offline font diagnostic", () => { const path = testInfo.outputPath("offline-font-browser-download.pdf"); await download.saveAs(path); const bytes = new Uint8Array(await readFile(path)); - markerResultValue = extractedMarkerResult(await readPdfText(bytes)); - rasterEvidence = await renderPdfRasterEvidence(page, bytes); - await testInfo.attach("browser-download-raster.png", { - body: Buffer.from(rasterEvidence.rasterDataUrl.split(",")[1] ?? "", "base64"), - contentType: "image/png", - }); + try { + markerResultValue = extractedMarkerResult(await readPdfText(bytes)); + rasterEvidence = await renderPdfRasterEvidence(page, bytes); + await testInfo.attach("browser-download-raster.png", { + body: Buffer.from(rasterEvidence.rasterDataUrl.split(",")[1] ?? "", "base64"), + contentType: "image/png", + }); + rasterEvidenceStatus = "received"; + } catch { + rasterEvidenceStatus = "unresolved-raster-evidence-error"; + } } catch { - downloadStatus = "renderer-or-network-error"; + downloadStatus = "download-error"; } finally { await report(testInfo, "browser-download", { cache: "new-browser-context", blockedExternalFontRequests: cold.blockedRequests, networkStatus: networkStatus(cold.blockedRequests), downloadStatus, + rasterEvidenceStatus, textLayerMarkers: markerResultValue ?? "not-extracted", glyphStatus: rasterEvidence?.referenceGlyphs ?? "not-rasterized", }); await cold.context.close(); } - if (rasterEvidence) { + if (downloadStatus === "received") { + expect(rasterEvidenceStatus).toBe("received"); + expect(rasterEvidence).not.toBeNull(); + if (!rasterEvidence) return; expect(rasterEvidence.referenceGlyphs).toHaveLength(offlineFontScriptSamples.length); expect(rasterEvidence.referenceGlyphs.every(({ status }) => status === "visible")).toBe(true); }