mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-09-29 16:24:22 +10:00
test: harden offline font raster evidence
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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 extends PdfMarker = PdfMarker> = 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 = <TMarker extends PdfMarker>(
|
||||
textItems: PdfTextItem[],
|
||||
markers: readonly TMarker[],
|
||||
pageHeight: number,
|
||||
): PdfMarkerLocation<TMarker>[] => {
|
||||
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";
|
||||
};
|
||||
@@ -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 }) => `<p>${marker}</p>`).join("");
|
||||
|
||||
export type OfflineFontResumeFixture = {
|
||||
@@ -31,8 +30,8 @@ export async function seedOfflineFontResume(page: Page): Promise<OfflineFontResu
|
||||
const patches: Array<{ path: string[]; value: unknown }> = [
|
||||
{ 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"] },
|
||||
|
||||
@@ -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<HTMLCanvasElement>('[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<string, unknown>) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user