From 61b58ae9a3ad40deb6a0818fc6574db91daf4e12 Mon Sep 17 00:00:00 2001
From: Amruth Pillai
Date: Sun, 6 Sep 2026 05:15:24 +0200
Subject: [PATCH 1/9] test: capture offline font network paths
---
.orchestration/plan-27a-diagnostic.md | 157 +++++++++++++++++++
tests/e2e/fixtures/offline-fonts.ts | 67 ++++++++
tests/e2e/specs/offline-fonts.spec.ts | 213 ++++++++++++++++++++++++++
turbo.json | 4 +-
4 files changed, 440 insertions(+), 1 deletion(-)
create mode 100644 .orchestration/plan-27a-diagnostic.md
create mode 100644 tests/e2e/fixtures/offline-fonts.ts
create mode 100644 tests/e2e/specs/offline-fonts.spec.ts
diff --git a/.orchestration/plan-27a-diagnostic.md b/.orchestration/plan-27a-diagnostic.md
new file mode 100644
index 000000000..b3b9e869c
--- /dev/null
+++ b/.orchestration/plan-27a-diagnostic.md
@@ -0,0 +1,157 @@
+# Plan 27 Phase A diagnostic evidence
+
+Date: 2026-09-06
+Issue: [#3377](https://github.com/amruthpillai/reactive-resume/issues/3377)
+Revision: `2a4a1583b` (`origin/main` at run start)
+Scope: Phase A, steps 1–2 only. No resolver, runtime behavior, or remote-source behavior changed.
+
+## Drift and authority
+
+- Worktree started clean and `HEAD` matched `origin/main`; `git diff origin/main...HEAD` was empty.
+- Current catalog is `packages/fonts/src/webfontlist.json`. Its web font records point at both Google Fonts static assets and jsDelivr assets; “Google blocked” is not an offline proof.
+- Browser preview is `apps/web/src/components/typography/font-display.tsx` and calls `FontFace.load()` against each catalog preview URL.
+- Browser PDF preview/download is `apps/web/src/features/resume/export/pdf-document.tsx` → `@reactive-resume/pdf/browser`; registration is `packages/pdf/src/hooks/use-register-fonts.ts`.
+- Server PDF is `apps/server/src/http/resume-pdf.ts` → `createResumePdfDownload`; Playwright browser routing cannot observe that process’s outbound font fetches.
+- The issue is open and unmodified. PR #3455 is the approved planning PR; its plan/decision log grants execution of this bounded diagnostic and manifest evidence.
+
+## Deterministic fixture
+
+`tests/e2e/fixtures/offline-fonts.ts` seeds one disposable resume after sample creation. It writes the same text into basics and summary, hides the picture, selects IBM Plex Serif 400/700 for body and heading, and marks the row public for the server-PDF surface.
+
+The exact markers are versioned as `offline-font-scripts-v1`:
+
+| Marker | Script or coverage |
+| --- | --- |
+| `Latin punctuation • — “quotes” €` | Latin plus General Punctuation and currency |
+| `简体中文` | Han / Simplified Chinese |
+| `العربية` | Arabic |
+| `עברית` | Hebrew |
+| `ไทย` | Thai |
+| `Emoji 🚀` | Emoji |
+
+`tests/e2e/specs/offline-fonts.spec.ts` is opt-in (`OFFLINE_FONT_DIAGNOSTIC=1`) so the normal PR E2E suite does not become network-dependent. Each surface creates a new browser context with persisted auth state, disabled service workers, and no prior browser cache. Every non-same-origin request is aborted and recorded as `{ hostname, path }`; query strings, fragments, headers, bodies, tokens, and full URLs never enter diagnostic output. Reports are attached as JSON and emitted with the same sanitized shape.
+
+The four surfaces are separate tests:
+
+1. Font picker preview opens Typography → Font Family and waits for lazy `FontFace` preview loads.
+2. Builder PDF preview navigates to the builder and records whether a PDF canvas appears.
+3. Browser PDF download uses the Export dialog and extracts marker presence from the downloaded PDF when generation succeeds.
+4. Server PDF calls the public PDF endpoint and extracts marker presence from its response when generation succeeds.
+
+PDF marker results classify missing text as a glyph result (`true`/`false` per marker), while blocked browser font requests classify the browser surface as `network-error`. The server report deliberately says `server-outbound-requests-unobservable-from-playwright`; it does not claim zero server requests.
+
+## Run protocol and cold-cache boundary
+
+Build and database setup follow `tests/e2e/README.md`. Run each surface in a separately restarted production server process so module-level PDF font registration state cannot leak between controls:
+
+```text
+OFFLINE_FONT_DIAGNOSTIC=1 OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED=1 \
+ pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --grep "picker preview"
+```
+
+Stop and restart the production server before repeating the command with `builder PDF`, `browser PDF`, and `server PDF` grep patterns. The environment used for this change had no built `apps/server/dist` or `apps/web/dist`, no running PostgreSQL instance, and no production server to restart, so the cold E2E matrix was not run. This is an explicit infrastructure blocker, not a pass claim. The test still preserves the restart confirmation in its report (`serverRestartConfirmed`) for a later run.
+
+The current Playwright route guard cannot impose host-level egress denial on Node.js running the server. A genuinely cold server test therefore needs a separately restarted server plus host-level egress capture/deny (for example, a controlled network namespace or an approved outbound proxy). Do not infer server network behavior from an empty browser request list.
+
+## Administrator-hosted manifest proposal
+
+This is a proposal, not an asset download. It intentionally contains only the primary family and glyph fallbacks required by the fixture and current PDF fallback map, not the full catalog.
+
+```json
+{
+ "schemaVersion": "offline-fonts-v1",
+ "mode": "local-only",
+ "assetRoot": "/fonts/offline/v1",
+ "families": {
+ "IBM Plex Serif": {
+ "normal": { "400": "ibm-plex-serif/400.ttf", "700": "ibm-plex-serif/700.ttf" },
+ "italic": { "400": "ibm-plex-serif/400-italic.ttf", "700": "ibm-plex-serif/700-italic.ttf" },
+ "preview": "ibm-plex-serif/preview.ttf"
+ },
+ "IBM Plex Sans": {
+ "normal": { "400": "ibm-plex-sans/400.ttf", "700": "ibm-plex-sans/700.ttf" },
+ "italic": { "400": "ibm-plex-sans/400-italic.ttf", "700": "ibm-plex-sans/700-italic.ttf" },
+ "preview": "ibm-plex-sans/preview.ttf"
+ },
+ "Noto Serif": {
+ "normal": { "400": "noto-serif/400.ttf", "700": "noto-serif/700.ttf" },
+ "italic": { "400": "noto-serif/400-italic.ttf", "700": "noto-serif/700-italic.ttf" },
+ "preview": "noto-serif/preview.ttf"
+ },
+ "Noto Sans": {
+ "normal": { "400": "noto-sans/400.ttf", "700": "noto-sans/700.ttf" },
+ "italic": { "400": "noto-sans/400-italic.ttf", "700": "noto-sans/700-italic.ttf" },
+ "preview": "noto-sans/preview.ttf"
+ },
+ "Noto Sans SC": { "normal": { "400": "noto-sans-sc/400.ttf", "700": "noto-sans-sc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-sc/preview.ttf" },
+ "Noto Serif SC": { "normal": { "400": "noto-serif-sc/400.ttf", "700": "noto-serif-sc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-sc/preview.ttf" },
+ "Noto Sans TC": { "normal": { "400": "noto-sans-tc/400.ttf", "700": "noto-sans-tc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-tc/preview.ttf" },
+ "Noto Serif TC": { "normal": { "400": "noto-serif-tc/400.ttf", "700": "noto-serif-tc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-tc/preview.ttf" },
+ "Noto Sans JP": { "normal": { "400": "noto-sans-jp/400.ttf", "700": "noto-sans-jp/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-jp/preview.ttf" },
+ "Noto Serif JP": { "normal": { "400": "noto-serif-jp/400.ttf", "700": "noto-serif-jp/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-jp/preview.ttf" },
+ "Noto Sans KR": { "normal": { "400": "noto-sans-kr/400.ttf", "700": "noto-sans-kr/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-kr/preview.ttf" },
+ "Noto Serif KR": { "normal": { "400": "noto-serif-kr/400.ttf", "700": "noto-serif-kr/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-kr/preview.ttf" },
+ "Noto Sans Arabic": { "normal": { "400": "noto-sans-arabic/400.ttf", "700": "noto-sans-arabic/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-arabic/preview.ttf" },
+ "Noto Naskh Arabic": { "normal": { "400": "noto-naskh-arabic/400.ttf", "700": "noto-naskh-arabic/700.ttf" }, "italic": "reuse-normal", "preview": "noto-naskh-arabic/preview.ttf" },
+ "Noto Sans Hebrew": { "normal": { "400": "noto-sans-hebrew/400.ttf", "700": "noto-sans-hebrew/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-hebrew/preview.ttf" },
+ "Noto Sans Thai": { "normal": { "400": "noto-sans-thai/400.ttf", "700": "noto-sans-thai/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-thai/preview.ttf" },
+ "Noto Emoji": { "normal": { "400": "noto-emoji/400.ttf", "700": "noto-emoji/700.ttf" }, "italic": "reuse-normal", "preview": "noto-emoji/preview.ttf" }
+ }
+}
+```
+
+### Candidate source, license, script, and size evidence
+
+Sizes are `Content-Length` bytes from a HEAD request to the exact current catalog assets on 2026-09-06. Responses reported `Content-Encoding: gzip`; these are compressed transfer-size estimates, not a claim about the eventual on-disk representation. Preview paths are aliases to the selected 400 face and add no extra bytes when stored once. Primary sources: [IBM Plex LICENSE.txt](https://github.com/IBM/plex/blob/master/LICENSE.txt), [Noto core LICENSE](https://github.com/notofonts/noto-fonts/blob/main/LICENSE), [Noto CJK Sans LICENSE](https://github.com/notofonts/noto-cjk/blob/main/Sans/LICENSE), and [Noto Emoji font LICENSE](https://github.com/googlefonts/noto-emoji/blob/main/fonts/LICENSE).
+
+| Family | Style/weights in proposal | Current catalog source version | License | Script/fallback role | Gzip bytes (selected files) | Build owner; runtime owners |
+| --- | --- | --- | --- | --- | ---: | --- |
+| IBM Plex Serif | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/ibmplexserif/v20` | OFL 1.1, Reserved Font Name `Plex` | Primary serif; Latin and punctuation stack | 294,717 | `packages/fonts`; `apps/web` FontDisplay; `packages/pdf` registration |
+| IBM Plex Sans | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/ibmplexsans/v23` | OFL 1.1, Reserved Font Name `Plex` | Primary sans | 435,469 | `packages/fonts`; `apps/web` FontDisplay; `packages/pdf` registration |
+| Noto Serif | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/notoserif/v33` | OFL 1.1 | Serif punctuation fallback | 1,055,120 | `packages/fonts`; `packages/pdf` fallback registration |
+| Noto Sans | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/notosans/v42` | OFL 1.1 | Sans punctuation fallback | 1,236,259 | `packages/fonts`; `packages/pdf` fallback registration |
+| Noto Sans SC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanssc/v40` | OFL 1.1 (Noto CJK) | Simplified Han; CJK fallback | 12,766,416 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif SC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoserifsc/v35` | OFL 1.1 (Noto CJK) | Simplified Han serif fallback | 17,350,185 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans TC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanstc/v39` | OFL 1.1 (Noto CJK) | Traditional Han fallback | 8,628,278 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif TC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoseriftc/v36` | OFL 1.1 (Noto CJK) | Traditional Han serif fallback | 11,804,923 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans JP | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansjp/v56` | OFL 1.1 (Noto CJK) | Kana and Japanese Han fallback | 6,383,035 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif JP | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoserifjp/v33` | OFL 1.1 (Noto CJK) | Kana and Japanese Han serif fallback | 8,685,862 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans KR | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanskr/v39` | OFL 1.1 (Noto CJK) | Hangul and Korean Han fallback | 6,102,888 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif KR | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoserifkr/v31` | OFL 1.1 (Noto CJK) | Hangul and Korean Han serif fallback | 11,113,442 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans Arabic | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansarabic/v33` | OFL 1.1 | Arabic sans fallback | 178,455 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Naskh Arabic | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notonaskharabic/v44` | OFL 1.1 | Arabic serif fallback | 190,924 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Sans Hebrew | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanshebrew/v50` | OFL 1.1 | Hebrew fallback for both serif/sans slots | 55,707 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Sans Thai | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansthai/v29` | OFL 1.1 | Thai fallback for both serif/sans slots | 55,173 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Emoji | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoemoji/v62` | OFL 1.1 for font files; assets/tools have separate licenses | Emoji outline fallback; verify renderer support | 1,153,847 | `packages/fonts`; `packages/pdf` script fallback |
+
+Estimated transfer size for all rows and listed styles: **87,490,700 bytes (~83.45 MiB)**. This confirms why a full-catalog bundle is out of scope. A later implementation should subset by declared glyph requirements or make the administrator choose fallback families; it must not silently fetch another CDN.
+
+### Source and license obligations
+
+- Pin an upstream release/commit and retain source attribution plus the complete applicable license with hosted assets. Do not use mutable `@latest` URLs as runtime sources.
+- IBM Plex’s license has Reserved Font Name `Plex`; modified/subset outputs must follow OFL naming requirements.
+- Noto core, Noto CJK, and Noto Emoji font files are OFL 1.1, but Noto Emoji documents separate Apache/public-domain treatment for tools and flag image assets. Bundle only font files unless those other assets are intentionally needed and separately attributed.
+- License checks are build-owner responsibility (`packages/fonts`/tooling); runtime owners (`apps/web` and `packages/pdf`) consume only the validated manifest.
+
+### Missing-family and missing-asset behavior
+
+Local mode must resolve only same-origin administrator-hosted manifest paths. If imported resume data names an unavailable family, show an actionable missing-family error naming the family and required local asset; apply a configured local fallback only when the administrator explicitly supplied one. If a required weight/style/fallback asset is absent, fail the affected preview/export with an actionable diagnostic containing family/style/weight and local path. Never retry Google Fonts, jsDelivr, or any other remote URL in local mode.
+
+Standard PDF families (Helvetica, Courier, Times-Roman) remain file-free. They do not prove that a document containing punctuation, CJK, Arabic, Hebrew, Thai, or emoji is network-free; the script fallback rows remain required.
+
+## Verification record
+
+Completed read-only checks before handoff:
+
+- CodeGraph exploration of font catalog, picker preview, browser PDF, server PDF, and existing fallback tests.
+- `pnpm dlx @tanstack/intent@latest list`: no matching local intent skill for this work.
+- `pnpm exec biome check tests/e2e/specs/offline-fonts.spec.ts tests/e2e/fixtures/offline-fonts.ts turbo.json`: passed.
+- `git diff --check`: passed.
+- `pnpm --filter @reactive-resume/fonts test`: passed (55 tests).
+- `pnpm --filter @reactive-resume/pdf exec vitest run src/hooks/use-register-fonts.test.ts`: passed (35 tests).
+- Web typography/regression suite: passed (940 tests across 135 files); web and server package typechecks passed.
+- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list`: passed (4 diagnostic tests collected).
+- E2E diagnostic execution: blocked by missing build outputs and unavailable PostgreSQL/server; no success claim made.
+- `pnpm exec turbo boundaries`: unresolved repository checker failure (418 parser-panicked `.js` files, including existing PDF/web source mirrors); no boundary violation was reported for the new TypeScript imports.
+
+The implementation intentionally stops at diagnostic fixtures and manifest evidence. Shared source resolution, asset hosting, local-mode configuration, and production behavior remain Phase A step 3+ work.
diff --git a/tests/e2e/fixtures/offline-fonts.ts b/tests/e2e/fixtures/offline-fonts.ts
new file mode 100644
index 000000000..6ecc6978b
--- /dev/null
+++ b/tests/e2e/fixtures/offline-fonts.ts
@@ -0,0 +1,67 @@
+import type { Page } from "@playwright/test";
+import { Pool } from "pg";
+
+export const offlineFontScriptSamples = [
+ { name: "latin-punctuation", marker: "Latin punctuation • — “quotes” €" },
+ { name: "cjk", marker: "简体中文" },
+ { name: "arabic", marker: "العربية" },
+ { name: "hebrew", marker: "עברית" },
+ { name: "thai", marker: "ไทย" },
+ { name: "emoji", marker: "Emoji 🚀" },
+] as const;
+
+const offlineFontFixtureText = offlineFontScriptSamples.map(({ marker }) => marker).join(" | ");
+
+export type OfflineFontResumeFixture = {
+ builderURL: string;
+ slug: string;
+ username: string;
+};
+
+/** Seeds one stable multilingual document so each diagnostic surface uses identical glyph input. */
+export async function seedOfflineFontResume(page: Page): Promise {
+ const builderURL = page.url();
+ const resumeId = new URL(builderURL).pathname.split("/").at(-1);
+ const databaseURL = process.env.DATABASE_URL;
+ if (!resumeId || !databaseURL) throw new Error("Offline font fixture requires builder URL and DATABASE_URL.");
+
+ const pool = new Pool({ connectionString: databaseURL });
+ try {
+ 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 },
+ { path: ["summary", "content"], value: `${offlineFontFixtureText}
` },
+ { path: ["metadata", "typography", "body", "fontFamily"], value: "IBM Plex Serif" },
+ { path: ["metadata", "typography", "body", "fontWeights"], value: ["400", "700"] },
+ { path: ["metadata", "typography", "heading", "fontFamily"], value: "IBM Plex Serif" },
+ { path: ["metadata", "typography", "heading", "fontWeights"], value: ["400", "700"] },
+ ];
+
+ for (const patch of patches) {
+ await pool.query(
+ `update "resume"
+ set data = jsonb_set(data, $2::text[], $3::jsonb, true),
+ updated_at = now()
+ where id = $1`,
+ [resumeId, patch.path, JSON.stringify(patch.value)],
+ );
+ }
+
+ await pool.query('update "resume" set is_public = true, updated_at = now() where id = $1', [resumeId]);
+ const result = await pool.query<{ slug: string; user_id: string }>(
+ 'select slug, user_id from "resume" where id = $1',
+ [resumeId],
+ );
+ const row = result.rows[0];
+ if (!row) throw new Error("Offline font fixture resume was not found after seeding.");
+
+ const owner = await pool.query<{ username: string }>('select username from "user" where id = $1', [row.user_id]);
+ const ownerUsername = owner.rows[0]?.username;
+ if (!ownerUsername) throw new Error("Offline font fixture owner was not found after seeding.");
+
+ return { builderURL, slug: row.slug, username: ownerUsername };
+ } finally {
+ await pool.end();
+ }
+}
diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts
new file mode 100644
index 000000000..eb35cbbd0
--- /dev/null
+++ b/tests/e2e/specs/offline-fonts.spec.ts
@@ -0,0 +1,213 @@
+import type { Browser, BrowserContext, Page, TestInfo } from "@playwright/test";
+import { readFile, writeFile } from "node:fs/promises";
+import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
+import { offlineFontScriptSamples, seedOfflineFontResume } from "../fixtures/offline-fonts";
+import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
+import { expect, test } from "../fixtures/test";
+
+const diagnosticEnabled = process.env.OFFLINE_FONT_DIAGNOSTIC === "1";
+const serverRestartConfirmed = process.env.OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED === "1";
+
+type BlockedRequest = {
+ hostname: string;
+ path: string;
+};
+
+type ColdContext = {
+ context: BrowserContext;
+ blockedRequests: BlockedRequest[];
+};
+
+type PdfMarkers = Record<(typeof offlineFontScriptSamples)[number]["name"], boolean>;
+
+test.describe("offline font diagnostic", () => {
+ test.describe.configure({ mode: "serial" });
+ test.skip(!diagnosticEnabled, "Set OFFLINE_FONT_DIAGNOSTIC=1 to run network diagnostics.");
+ test.setTimeout(120_000);
+
+ async function createColdContext(browser: Browser, page: Page, testInfo: TestInfo): Promise {
+ const baseURL = String(testInfo.project.use.baseURL ?? "http://localhost:3000");
+ const allowedOrigin = new URL(baseURL).origin;
+ const context = await browser.newContext({
+ baseURL,
+ serviceWorkers: "block",
+ storageState: await page.context().storageState(),
+ });
+ const blockedRequests: BlockedRequest[] = [];
+
+ await context.route("**/*", async (route) => {
+ const requestURL = new URL(route.request().url());
+ if (requestURL.origin === allowedOrigin || requestURL.protocol === "data:" || requestURL.protocol === "blob:") {
+ await route.continue();
+ return;
+ }
+
+ blockedRequests.push({ hostname: requestURL.hostname, path: requestURL.pathname });
+ await route.abort("blockedbyclient");
+ });
+
+ return { context, blockedRequests };
+ }
+
+ function markerResult(text: string): PdfMarkers {
+ return Object.fromEntries(
+ offlineFontScriptSamples.map((sample) => [sample.name, text.includes(sample.marker)]),
+ ) as PdfMarkers;
+ }
+
+ function networkStatus(blockedRequests: BlockedRequest[]) {
+ return blockedRequests.length > 0 ? "network-error" : "no-browser-network-error";
+ }
+
+ async function readPdfText(bytes: Uint8Array) {
+ const loadingTask = getDocument({ data: bytes, useSystemFonts: false });
+ try {
+ const document = await loadingTask.promise;
+ 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] : [])));
+ }
+ return pages.join(" ");
+ } finally {
+ await loadingTask.destroy();
+ }
+ }
+
+ async function report(testInfo: TestInfo, name: string, reportData: Record) {
+ const body = JSON.stringify({
+ version: 1,
+ fixture: "offline-font-scripts-v1",
+ surface: name,
+ ...reportData,
+ });
+ expect(body).not.toMatch(/https?:\/\/|[?&](token|secret|password|auth)=/i);
+ await testInfo.attach(`${name}.json`, { body, contentType: "application/json" });
+ console.log(`[offline-fonts] ${body}`);
+ }
+
+ test("captures cold font picker preview requests", async ({ browser, authPage: seedPage }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ try {
+ await page.goto(fixture.builderURL);
+ await openSidebarSection(page, "Typography");
+ await page.getByRole("combobox").first().click();
+ await expect(page.getByRole("option").first()).toBeVisible();
+ await page.waitForTimeout(1_000);
+ await report(testInfo, "picker-preview", {
+ cache: "new-browser-context",
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: networkStatus(cold.blockedRequests),
+ glyphStatus: "not-applicable-picker-only",
+ });
+ } finally {
+ await cold.context.close();
+ }
+ });
+
+ test("captures cold builder PDF preview and classifies glyph/network results", async ({
+ browser,
+ authPage: seedPage,
+ }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ try {
+ await page.goto(fixture.builderURL);
+ await page.waitForTimeout(5_000);
+ const canvasVisible = await page
+ .locator('[aria-hidden="false"] canvas')
+ .first()
+ .isVisible()
+ .catch(() => false);
+ await report(testInfo, "builder-preview", {
+ cache: "new-browser-context",
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: networkStatus(cold.blockedRequests),
+ canvasVisible,
+ glyphStatus: "not-measured-preview-canvas",
+ });
+ } finally {
+ await cold.context.close();
+ }
+ });
+
+ test("captures cold browser PDF download and classifies extracted glyphs", async ({
+ browser,
+ authPage: seedPage,
+ }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ let markerResultValue: PdfMarkers | null = null;
+ let downloadStatus = "not-started";
+ try {
+ await page.goto(fixture.builderURL);
+ await openSidebarSection(page, "Export");
+ await page.getByRole("button", { name: /Choose PDF, DOCX, Markdown, or JSON/ }).click();
+ const downloadPromise = page.waitForEvent("download", { timeout: 20_000 });
+ await page.getByRole("button", { name: "Download PDF", exact: true }).click();
+ const download = await downloadPromise;
+ downloadStatus = "received";
+ const path = testInfo.outputPath("offline-font-browser-download.pdf");
+ await download.saveAs(path);
+ markerResultValue = markerResult(await readPdfText(new Uint8Array(await readFile(path))));
+ } catch {
+ downloadStatus = "renderer-or-network-error";
+ } finally {
+ await report(testInfo, "browser-download", {
+ cache: "new-browser-context",
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: networkStatus(cold.blockedRequests),
+ downloadStatus,
+ glyphStatus: markerResultValue ?? "not-extracted",
+ });
+ await cold.context.close();
+ }
+ });
+
+ test("exercises restarted-server PDF and records server observability boundary", async ({
+ browser,
+ authPage: seedPage,
+ }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ let responseStatus = "not-requested";
+ let markerResultValue: PdfMarkers | null = null;
+ try {
+ await page.goto(fixture.builderURL);
+ if (!serverRestartConfirmed) {
+ responseStatus = "blocked-before-request";
+ } else {
+ const response = await page.request.get(
+ `/api/resumes/${encodeURIComponent(fixture.username)}/${encodeURIComponent(fixture.slug)}/pdf`,
+ );
+ responseStatus = String(response.status());
+ if (response.ok()) {
+ const bytes = await response.body();
+ await writeFile(testInfo.outputPath("offline-font-server.pdf"), bytes);
+ markerResultValue = markerResult(await readPdfText(new Uint8Array(bytes)));
+ }
+ }
+ } finally {
+ await report(testInfo, "server-pdf", {
+ cache: "new-browser-context; server-process-state-is-external",
+ serverRestartConfirmed,
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: "server-outbound-requests-unobservable-from-playwright",
+ responseStatus,
+ glyphStatus: markerResultValue ?? "not-extracted",
+ limitation:
+ "Playwright route interception sees browser requests only; server fetches need a restarted process plus host-level egress capture.",
+ });
+ await cold.context.close();
+ }
+ });
+});
diff --git a/turbo.json b/turbo.json
index ee5384e87..d95fde1ec 100644
--- a/turbo.json
+++ b/turbo.json
@@ -89,7 +89,9 @@
"FLAG_SHOW_SPONSORS",
"FLAG_ALLOW_UNSAFE_OAUTH_REDIRECT_URI",
"FLAG_ALLOW_UNSAFE_AI_BASE_URL",
- "AI_TEST_TIMEOUT_MS"
+ "AI_TEST_TIMEOUT_MS",
+ "OFFLINE_FONT_DIAGNOSTIC",
+ "OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED"
],
"tasks": {
"transit": {
From ae8e2f76f1f6d49129f0f01050e39be0d4959fad Mon Sep 17 00:00:00 2001
From: Amruth Pillai
Date: Sun, 6 Sep 2026 05:30:40 +0200
Subject: [PATCH 2/9] test: remediate offline font diagnostic review
---
.orchestration/plan-27a-diagnostic.md | 14 +-
.orchestration/plan-27a-remediation.md | 24 +++
tests/e2e/fixtures/offline-fonts.ts | 4 +-
tests/e2e/specs/offline-fonts.spec.ts | 232 +++++++++++++++++++++++--
4 files changed, 251 insertions(+), 23 deletions(-)
create mode 100644 .orchestration/plan-27a-remediation.md
diff --git a/.orchestration/plan-27a-diagnostic.md b/.orchestration/plan-27a-diagnostic.md
index b3b9e869c..0a3ddccbb 100644
--- a/.orchestration/plan-27a-diagnostic.md
+++ b/.orchestration/plan-27a-diagnostic.md
@@ -34,11 +34,11 @@ The exact markers are versioned as `offline-font-scripts-v1`:
The four surfaces are separate tests:
1. Font picker preview opens Typography → Font Family and waits for lazy `FontFace` preview loads.
-2. Builder PDF preview navigates to the builder and records whether a PDF canvas appears.
-3. Browser PDF download uses the Export dialog and extracts marker presence from the downloaded PDF when generation succeeds.
-4. Server PDF calls the public PDF endpoint and extracts marker presence from its response when generation succeeds.
+2. Builder PDF preview navigates to the builder, captures the active PDF canvas, and measures marker-local raster crops.
+3. Browser PDF download uses the Export dialog, rasterizes the downloaded PDF, and measures marker-local crops when generation succeeds.
+4. Server PDF calls the public PDF endpoint and records text-layer marker presence when generation succeeds.
-PDF marker results classify missing text as a glyph result (`true`/`false` per marker), while blocked browser font requests classify the browser surface as `network-error`. The server report deliberately says `server-outbound-requests-unobservable-from-playwright`; it does not claim zero server requests.
+Builder/browser-PDF reports keep PDF text extraction as a separate `textLayerMarkers` signal; it does not prove visible glyph outlines. Raster evidence attaches a rendered PNG and per-marker crop metrics, failing for blank or tofu-like visible crops. Blocked browser font requests classify browser surfaces as `network-error`. The server report deliberately says `server-outbound-requests-unobservable-from-playwright`; its cold-network gate remains unresolved because server outbound capture and verifiable restart identity require external host-level controls.
## Run protocol and cold-cache boundary
@@ -49,7 +49,7 @@ OFFLINE_FONT_DIAGNOSTIC=1 OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED=1 \
pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --grep "picker preview"
```
-Stop and restart the production server before repeating the command with `builder PDF`, `browser PDF`, and `server PDF` grep patterns. The environment used for this change had no built `apps/server/dist` or `apps/web/dist`, no running PostgreSQL instance, and no production server to restart, so the cold E2E matrix was not run. This is an explicit infrastructure blocker, not a pass claim. The test still preserves the restart confirmation in its report (`serverRestartConfirmed`) for a later run.
+Stop and restart the production server before repeating the command with `builder PDF`, `browser PDF`, and `server PDF` grep patterns. The environment used for this change had no built `apps/server/dist` or `apps/web/dist`, no running PostgreSQL instance, and no production server to restart, so the cold E2E matrix was not run. This is an explicit infrastructure blocker, not a pass claim. The test records `serverRestartFlag` only as caller input and labels it non-proof; it does not claim a completed cold-network gate.
The current Playwright route guard cannot impose host-level egress denial on Node.js running the server. A genuinely cold server test therefore needs a separately restarted server plus host-level egress capture/deny (for example, a controlled network namespace or an approved outbound proxy). Do not infer server network behavior from an empty browser request list.
@@ -124,7 +124,7 @@ Sizes are `Content-Length` bytes from a HEAD request to the exact current catalo
| Noto Sans Thai | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansthai/v29` | OFL 1.1 | Thai fallback for both serif/sans slots | 55,173 | `packages/fonts`; `packages/pdf` script fallback |
| Noto Emoji | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoemoji/v62` | OFL 1.1 for font files; assets/tools have separate licenses | Emoji outline fallback; verify renderer support | 1,153,847 | `packages/fonts`; `packages/pdf` script fallback |
-Estimated transfer size for all rows and listed styles: **87,490,700 bytes (~83.45 MiB)**. This confirms why a full-catalog bundle is out of scope. A later implementation should subset by declared glyph requirements or make the administrator choose fallback families; it must not silently fetch another CDN.
+Estimated transfer size for all rows and listed styles: **87,490,700 bytes (~83.44 MiB)**. This confirms why a full-catalog bundle is out of scope. A later implementation should subset by declared glyph requirements or make the administrator choose fallback families; it must not silently fetch another CDN.
### Source and license obligations
@@ -152,6 +152,6 @@ Completed read-only checks before handoff:
- Web typography/regression suite: passed (940 tests across 135 files); web and server package typechecks passed.
- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list`: passed (4 diagnostic tests collected).
- E2E diagnostic execution: blocked by missing build outputs and unavailable PostgreSQL/server; no success claim made.
-- `pnpm exec turbo boundaries`: unresolved repository checker failure (418 parser-panicked `.js` files, including existing PDF/web source mirrors); no boundary violation was reported for the new TypeScript imports.
+- `pnpm exec turbo boundaries`: passed on fresh rerun (Turbo 2.10.12, 1108 files, no issues).
The implementation intentionally stops at diagnostic fixtures and manifest evidence. Shared source resolution, asset hosting, local-mode configuration, and production behavior remain Phase A step 3+ work.
diff --git a/.orchestration/plan-27a-remediation.md b/.orchestration/plan-27a-remediation.md
new file mode 100644
index 000000000..c96cd6ecb
--- /dev/null
+++ b/.orchestration/plan-27a-remediation.md
@@ -0,0 +1,24 @@
+# Plan 27A remediation
+
+Date: 2026-09-06
+Base: `61b58ae9a`
+Scope: concrete findings from `.orchestration/plan-27a-independent-review.md` only.
+
+## Remediated findings
+
+- Builder PDF preview and browser PDF download now produce raster evidence. The fixture stores each multilingual marker in its own summary paragraph, allowing the diagnostic to locate marker-local PDF text boxes and measure only those raster crops. Reports attach a rendered PNG plus per-marker `inkPixels`, trimmed dimensions, and status. Blank and tofu-like crops fail assertions; no whole-page snapshot is used.
+- PDF text extraction is reported separately as `textLayerMarkers`. It is not described or asserted as proof of visible glyph outlines.
+- Server PDF output remains text-extraction-only and is explicitly classified as `serverGateStatus: unresolved-external-host-level-blocker`. `serverRestartFlag` is caller input, not restart proof. Browser Playwright routing is not used to infer server egress, and no production resolver or instrumentation behavior was added.
+- `.orchestration/plan-27a-diagnostic.md` now records a fresh boundaries pass and the corrected `87,490,700 bytes (~83.44 MiB)` arithmetic.
+- Diagnostic remains opt-in through `OFFLINE_FONT_DIAGNOSTIC=1`; normal CI behavior remains unchanged. Request logs stay sanitized to hostname and pathname.
+
+## Verification
+
+- `pnpm exec biome check tests/e2e/specs/offline-fonts.spec.ts tests/e2e/fixtures/offline-fonts.ts` — passed.
+- `git diff --check` — passed.
+- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list` — 4 tests collected.
+- `pnpm --filter @reactive-resume/fonts test` — 55/55 passed.
+- `pnpm --filter @reactive-resume/pdf exec vitest run src/hooks/use-register-fonts.test.ts` — 35/35 passed.
+- `pnpm exec turbo boundaries` — passed on fresh rerun (Turbo 2.10.12, 1108 files, no issues).
+
+Full diagnostic E2E remains unrun because this environment lacks production build output, PostgreSQL, and a production server. Server cold-network capture and verifiable restart identity remain external host-level blockers by design; this remediation does not claim that gate is complete.
diff --git a/tests/e2e/fixtures/offline-fonts.ts b/tests/e2e/fixtures/offline-fonts.ts
index 6ecc6978b..38275ed06 100644
--- a/tests/e2e/fixtures/offline-fonts.ts
+++ b/tests/e2e/fixtures/offline-fonts.ts
@@ -11,6 +11,7 @@ export const offlineFontScriptSamples = [
] as const;
const offlineFontFixtureText = offlineFontScriptSamples.map(({ marker }) => marker).join(" | ");
+const offlineFontFixtureSummary = offlineFontScriptSamples.map(({ marker }) => `${marker}
`).join("");
export type OfflineFontResumeFixture = {
builderURL: string;
@@ -31,7 +32,8 @@ export async function seedOfflineFontResume(page: Page): Promise${offlineFontFixtureText}
` },
+ // Keep markers in separate blocks 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"] },
{ path: ["metadata", "typography", "heading", "fontFamily"], value: "IBM Plex Serif" },
diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts
index eb35cbbd0..f1bda9261 100644
--- a/tests/e2e/specs/offline-fonts.spec.ts
+++ b/tests/e2e/specs/offline-fonts.spec.ts
@@ -1,12 +1,13 @@
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 { offlineFontScriptSamples, seedOfflineFontResume } from "../fixtures/offline-fonts";
import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
const diagnosticEnabled = process.env.OFFLINE_FONT_DIAGNOSTIC === "1";
-const serverRestartConfirmed = process.env.OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED === "1";
+const serverRestartFlag = process.env.OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED === "1";
type BlockedRequest = {
hostname: string;
@@ -20,6 +21,21 @@ type ColdContext = {
type PdfMarkers = Record<(typeof offlineFontScriptSamples)[number]["name"], boolean>;
+type RasterGlyphEvidence = {
+ name: (typeof offlineFontScriptSamples)[number]["name"];
+ status: "visible" | "blank" | "tofu-like" | "not-located";
+ inkPixels: number;
+ trimmedWidth: number;
+ trimmedHeight: number;
+};
+
+type PdfRasterEvidence = {
+ rasterDataUrl: string;
+ textLayerMarkers: PdfMarkers;
+ referenceGlyphs: RasterGlyphEvidence[];
+ previewGlyphs: RasterGlyphEvidence[];
+};
+
test.describe("offline font diagnostic", () => {
test.describe.configure({ mode: "serial" });
test.skip(!diagnosticEnabled, "Set OFFLINE_FONT_DIAGNOSTIC=1 to run network diagnostics.");
@@ -49,7 +65,7 @@ test.describe("offline font diagnostic", () => {
return { context, blockedRequests };
}
- function markerResult(text: string): PdfMarkers {
+ function extractedMarkerResult(text: string): PdfMarkers {
return Object.fromEntries(
offlineFontScriptSamples.map((sample) => [sample.name, text.includes(sample.marker)]),
) as PdfMarkers;
@@ -74,6 +90,165 @@ test.describe("offline font diagnostic", () => {
}
}
+ async function capturePdfBytes(page: Page) {
+ await page.addInitScript(() => {
+ const read = Blob.prototype.arrayBuffer;
+ Blob.prototype.arrayBuffer = async function () {
+ const buffer = await read.call(this);
+ const bytes = new Uint8Array(buffer);
+ if (String.fromCharCode(...bytes.subarray(0, 5)) === "%PDF-") {
+ (window as Window & { offlineFontPdfBytes?: number[] }).offlineFontPdfBytes = Array.from(bytes);
+ }
+ return buffer;
+ };
+ });
+ }
+
+ async function renderPdfRasterEvidence(page: Page, bytes: Uint8Array): Promise {
+ const require = createRequire(`${process.cwd()}/package.json`);
+ await page.route("**/__offline_font_pdfjs/*", async (route) => {
+ const worker = new URL(route.request().url()).pathname.endsWith("worker.mjs");
+ await route.fulfill({
+ contentType: "text/javascript",
+ path: require.resolve(`pdfjs-dist/legacy/build/${worker ? "pdf.worker.mjs" : "pdf.mjs"}`),
+ });
+ });
+
+ return page.evaluate(
+ async ({ bytes, markers }) => {
+ 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 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(" ");
+ const baseViewport = pdfPage.getViewport({ scale: 1 });
+ const rasterScale = 4;
+ const viewport = pdfPage.getViewport({ scale: rasterScale });
+ const raster = globalThis.document.createElement("canvas");
+ raster.width = Math.ceil(viewport.width);
+ raster.height = Math.ceil(viewport.height);
+ const rasterContext = raster.getContext("2d");
+ if (!rasterContext) throw new Error("Missing PDF raster context.");
+ await pdfPage.render({
+ canvas: raster,
+ canvasContext: rasterContext,
+ viewport,
+ annotationMode: pdfjs.AnnotationMode.DISABLE,
+ 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 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));
+ const right = Math.min(canvas.width, Math.ceil((box.x + box.width) * scale + padding));
+ const bottom = Math.min(canvas.height, Math.ceil((box.y + box.height) * scale + padding));
+ let inkPixels = 0;
+ let minX = right;
+ let minY = bottom;
+ let maxX = left;
+ let maxY = top;
+ for (let y = top; y < bottom; y += 1) {
+ for (let x = left; x < right; x += 1) {
+ const index = (y * pixels.width + x) * 4;
+ const red = pixels.data[index] ?? 255;
+ const green = pixels.data[index + 1] ?? 255;
+ const blue = pixels.data[index + 2] ?? 255;
+ if (red >= 245 && green >= 245 && blue >= 245) continue;
+ inkPixels += 1;
+ minX = Math.min(minX, x);
+ minY = Math.min(minY, y);
+ maxX = Math.max(maxX, x);
+ maxY = Math.max(maxY, y);
+ }
+ }
+ if (inkPixels === 0) {
+ return { name, status: "blank", inkPixels, trimmedWidth: 0, trimmedHeight: 0 };
+ }
+ let interiorInk = 0;
+ for (
+ let y = minY + Math.floor((maxY - minY + 1) * 0.2);
+ y < maxY - Math.floor((maxY - minY + 1) * 0.2);
+ y += 1
+ ) {
+ for (
+ let x = minX + Math.floor((maxX - minX + 1) * 0.2);
+ x < maxX - Math.floor((maxX - minX + 1) * 0.2);
+ x += 1
+ ) {
+ const index = (y * pixels.width + x) * 4;
+ if (
+ (pixels.data[index] ?? 255) < 245 ||
+ (pixels.data[index + 1] ?? 255) < 245 ||
+ (pixels.data[index + 2] ?? 255) < 245
+ )
+ interiorInk += 1;
+ }
+ }
+ const trimmedWidth = maxX - minX + 1;
+ const trimmedHeight = maxY - minY + 1;
+ const interiorRatio = interiorInk / Math.max(inkPixels, 1);
+ const status = interiorRatio < 0.08 && trimmedWidth >= 8 && trimmedHeight >= 8 ? "tofu-like" : "visible";
+ return { name, status, inkPixels, trimmedWidth, trimmedHeight };
+ });
+ }
+
+ 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) : [],
+ };
+ } finally {
+ await loadingTask.destroy();
+ }
+ },
+ { bytes: Array.from(bytes), markers: offlineFontScriptSamples },
+ );
+ }
+
async function report(testInfo: TestInfo, name: string, reportData: Record) {
const body = JSON.stringify({
version: 1,
@@ -116,20 +291,33 @@ test.describe("offline font diagnostic", () => {
const fixture = await seedOfflineFontResume(seedPage);
const cold = await createColdContext(browser, seedPage, testInfo);
const page = await cold.context.newPage();
+ await capturePdfBytes(page);
try {
await page.goto(fixture.builderURL);
await page.waitForTimeout(5_000);
- const canvasVisible = await page
- .locator('[aria-hidden="false"] canvas')
- .first()
- .isVisible()
- .catch(() => false);
+ const previewCanvas = page.locator('[aria-hidden="false"] canvas').first();
+ const canvasVisible = await previewCanvas.isVisible().catch(() => false);
+ const pdfBytes = await page.evaluate(
+ () => (window as Window & { offlineFontPdfBytes?: number[] }).offlineFontPdfBytes,
+ );
+ expect(pdfBytes).toBeDefined();
+ const rasterEvidence = pdfBytes ? await renderPdfRasterEvidence(page, Uint8Array.from(pdfBytes)) : null;
+ if (rasterEvidence) {
+ await testInfo.attach("builder-preview-raster.png", {
+ body: Buffer.from(rasterEvidence.rasterDataUrl.split(",")[1] ?? "", "base64"),
+ contentType: "image/png",
+ });
+ expect(rasterEvidence.previewGlyphs).toHaveLength(offlineFontScriptSamples.length);
+ expect(rasterEvidence.previewGlyphs.every(({ status }) => status === "visible")).toBe(true);
+ }
+ expect(canvasVisible).toBe(true);
await report(testInfo, "builder-preview", {
cache: "new-browser-context",
blockedExternalFontRequests: cold.blockedRequests,
networkStatus: networkStatus(cold.blockedRequests),
canvasVisible,
- glyphStatus: "not-measured-preview-canvas",
+ textLayerMarkers: rasterEvidence?.textLayerMarkers ?? "not-extracted",
+ glyphStatus: rasterEvidence?.previewGlyphs ?? "not-rasterized",
});
} finally {
await cold.context.close();
@@ -145,6 +333,7 @@ test.describe("offline font diagnostic", () => {
const cold = await createColdContext(browser, seedPage, testInfo);
const page = await cold.context.newPage();
let markerResultValue: PdfMarkers | null = null;
+ let rasterEvidence: PdfRasterEvidence | null = null;
let downloadStatus = "not-started";
try {
await page.goto(fixture.builderURL);
@@ -156,7 +345,13 @@ test.describe("offline font diagnostic", () => {
downloadStatus = "received";
const path = testInfo.outputPath("offline-font-browser-download.pdf");
await download.saveAs(path);
- markerResultValue = markerResult(await readPdfText(new Uint8Array(await readFile(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",
+ });
} catch {
downloadStatus = "renderer-or-network-error";
} finally {
@@ -165,10 +360,15 @@ test.describe("offline font diagnostic", () => {
blockedExternalFontRequests: cold.blockedRequests,
networkStatus: networkStatus(cold.blockedRequests),
downloadStatus,
- glyphStatus: markerResultValue ?? "not-extracted",
+ textLayerMarkers: markerResultValue ?? "not-extracted",
+ glyphStatus: rasterEvidence?.referenceGlyphs ?? "not-rasterized",
});
await cold.context.close();
}
+ if (rasterEvidence) {
+ expect(rasterEvidence.referenceGlyphs).toHaveLength(offlineFontScriptSamples.length);
+ expect(rasterEvidence.referenceGlyphs.every(({ status }) => status === "visible")).toBe(true);
+ }
});
test("exercises restarted-server PDF and records server observability boundary", async ({
@@ -183,7 +383,7 @@ test.describe("offline font diagnostic", () => {
let markerResultValue: PdfMarkers | null = null;
try {
await page.goto(fixture.builderURL);
- if (!serverRestartConfirmed) {
+ if (!serverRestartFlag) {
responseStatus = "blocked-before-request";
} else {
const response = await page.request.get(
@@ -193,19 +393,21 @@ test.describe("offline font diagnostic", () => {
if (response.ok()) {
const bytes = await response.body();
await writeFile(testInfo.outputPath("offline-font-server.pdf"), bytes);
- markerResultValue = markerResult(await readPdfText(new Uint8Array(bytes)));
+ markerResultValue = extractedMarkerResult(await readPdfText(new Uint8Array(bytes)));
}
}
} finally {
await report(testInfo, "server-pdf", {
cache: "new-browser-context; server-process-state-is-external",
- serverRestartConfirmed,
+ serverRestartFlag,
+ serverGateStatus: "unresolved-external-host-level-blocker",
blockedExternalFontRequests: cold.blockedRequests,
networkStatus: "server-outbound-requests-unobservable-from-playwright",
responseStatus,
- glyphStatus: markerResultValue ?? "not-extracted",
+ textLayerMarkers: markerResultValue ?? "not-extracted",
+ glyphStatus: "not-rasterized-server-surface",
limitation:
- "Playwright route interception sees browser requests only; server fetches need a restarted process plus host-level egress capture.",
+ "Server outbound request capture and verifiable restart identity remain unresolved external host-level blockers; the caller flag is not restart proof. Playwright route interception sees browser requests only; do not treat this run as a cold-network gate.",
});
await cold.context.close();
}
From f97d1b736e2be599c33535398e922c904b6107ce Mon Sep 17 00:00:00 2001
From: Amruth Pillai
Date: Sun, 6 Sep 2026 05:46:38 +0200
Subject: [PATCH 3/9] test(pdf): characterize date layout issues 3155 2841
---
.../src/templates/shared/date-layout.test.tsx | 566 +++++
.../pdf/test-artifacts/date-layout/README.md | 35 +
.../date-layout/all-templates.json | 2200 +++++++++++++++++
.../date-layout/chikorita-ltr-page-1.png | Bin 0 -> 97072 bytes
.../date-layout/chikorita-ltr-page-2.png | Bin 0 -> 85770 bytes
.../date-layout/chikorita-ltr.json | 158 ++
.../date-layout/chikorita-ltr.pdf | Bin 0 -> 6391 bytes
.../date-layout/chikorita-rtl-page-1.png | Bin 0 -> 87638 bytes
.../date-layout/chikorita-rtl-page-2.png | Bin 0 -> 89983 bytes
.../date-layout/chikorita-rtl.json | 158 ++
.../date-layout/chikorita-rtl.pdf | Bin 0 -> 8834 bytes
.../date-layout/ditto-ltr-page-1.png | Bin 0 -> 94004 bytes
.../date-layout/ditto-ltr-page-2.png | Bin 0 -> 90742 bytes
.../test-artifacts/date-layout/ditto-ltr.json | 158 ++
.../test-artifacts/date-layout/ditto-ltr.pdf | Bin 0 -> 6096 bytes
.../date-layout/ditto-rtl-page-1.png | Bin 0 -> 87226 bytes
.../date-layout/ditto-rtl-page-2.png | Bin 0 -> 93059 bytes
.../test-artifacts/date-layout/ditto-rtl.json | 158 ++
.../test-artifacts/date-layout/ditto-rtl.pdf | Bin 0 -> 8598 bytes
19 files changed, 3433 insertions(+)
create mode 100644 packages/pdf/src/templates/shared/date-layout.test.tsx
create mode 100644 packages/pdf/test-artifacts/date-layout/README.md
create mode 100644 packages/pdf/test-artifacts/date-layout/all-templates.json
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-ltr-page-1.png
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-ltr-page-2.png
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-ltr.json
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-ltr.pdf
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-rtl-page-1.png
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-rtl-page-2.png
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-rtl.json
create mode 100644 packages/pdf/test-artifacts/date-layout/chikorita-rtl.pdf
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-ltr-page-1.png
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-ltr-page-2.png
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-ltr.json
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-ltr.pdf
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-rtl-page-1.png
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-rtl-page-2.png
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-rtl.json
create mode 100644 packages/pdf/test-artifacts/date-layout/ditto-rtl.pdf
diff --git a/packages/pdf/src/templates/shared/date-layout.test.tsx b/packages/pdf/src/templates/shared/date-layout.test.tsx
new file mode 100644
index 000000000..20d0004ce
--- /dev/null
+++ b/packages/pdf/src/templates/shared/date-layout.test.tsx
@@ -0,0 +1,566 @@
+import type { SemanticNode } from "@reactive-resume/resume/stylesheet";
+import type { ResumeData } from "@reactive-resume/schema/resume/data";
+import type { Template } from "@reactive-resume/schema/templates";
+import type { TextItem } from "pdfjs-dist/types/src/display/api";
+import type { RasterizedPdfPage } from "../../semantic/test/rasterize-pdf";
+import { createHash } from "node:crypto";
+import { mkdirSync, writeFileSync } from "node:fs";
+import { join } from "node:path";
+import { describe, expect, it } from "vitest";
+import { pdf, renderToBuffer } from "@react-pdf/renderer";
+import { encode } from "fast-png";
+import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
+import { act, createElement } from "react";
+import { defaultResumeData } from "@reactive-resume/schema/resume/default";
+import { resolveLevelDisplaySizes } from "@reactive-resume/schema/resume/level-display-sizes";
+import { ResumeDocument } from "../../document";
+import { resolveResumeRuntime } from "../../semantic/resolve";
+import { rasterizePdf } from "../../semantic/test/rasterize-pdf";
+
+const templates = [
+ "azurill",
+ "bronzor",
+ "chikorita",
+ "ditgar",
+ "ditto",
+ "gengar",
+ "glalie",
+ "kakuna",
+ "lapras",
+ "leafish",
+ "meowth",
+ "onyx",
+ "pikachu",
+ "rhyhorn",
+ "scizor",
+] as const satisfies readonly Template[];
+
+const dateMarkers = [
+ "EXP_LONG",
+ "EXP_NO_LOCATION",
+ "ROLE_ONE",
+ "ROLE_TWO",
+ "EXP_NO_PERIOD",
+ "EDU_DATE",
+ "PROJECT_DATE",
+ "AWARD_DATE",
+ "CERT_DATE",
+ "VOLUNTEER_DATE",
+ "CUSTOM_EXP_DATE",
+ "CUSTOM_ROLE_DATE",
+ "CUSTOM_EDU_DATE",
+ "CUSTOM_PROJECT_DATE",
+ "CUSTOM_AWARD_DATE",
+ "CUSTOM_CERT_DATE",
+ "CUSTOM_VOLUNTEER_DATE",
+] as const;
+
+type DateMarker = (typeof dateMarkers)[number];
+
+type PdfPageText = {
+ pageNumber: number;
+ items: TextItem[];
+};
+
+type RenderedFixture = {
+ bytes: Uint8Array;
+ pages: PdfPageText[];
+ raster: readonly RasterizedPdfPage[];
+};
+
+const emptyWebsite = () => ({ url: "", label: "", inlineLink: false });
+
+const required = (value: T | undefined): T => {
+ if (value === undefined) throw new Error("Missing fixture value");
+ return value;
+};
+
+const richText = (text: string) => `${text}
`;
+
+const dateFixture = (locale: "en-US" | "ar-SA" = "en-US"): ResumeData => {
+ const data = structuredClone(defaultResumeData);
+ data.picture.hidden = true;
+ data.basics = {
+ name: locale === "ar-SA" ? "RTL Date Matrix" : "LTR Date Matrix",
+ headline: "Date layout characterization",
+ email: "dates@example.com",
+ phone: "",
+ location: locale === "ar-SA" ? "دبي" : "Berlin",
+ website: { url: "", label: "" },
+ customFields: [],
+ };
+ data.metadata.page.locale = locale;
+ data.metadata.page.marginX = 28;
+ data.metadata.page.marginY = 20;
+ data.metadata.typography.body.fontFamily = "Helvetica";
+ data.metadata.typography.heading.fontFamily = "Helvetica";
+ data.metadata.layout.pages = [
+ {
+ fullWidth: true,
+ main: [
+ "experience",
+ "education",
+ "projects",
+ "awards",
+ "certifications",
+ "publications",
+ "volunteer",
+ "custom-experience",
+ "custom-education",
+ "custom-projects",
+ "custom-awards",
+ "custom-certifications",
+ "custom-publications",
+ "custom-volunteer",
+ ],
+ sidebar: [],
+ },
+ ];
+
+ data.sections.experience = {
+ ...data.sections.experience,
+ title: "Experience matrix",
+ items: [
+ {
+ id: "experience-dated",
+ hidden: false,
+ company: "Experience Parent",
+ position: "Lead Engineer",
+ location: "Oslo LOCATION_ORDER",
+ period: "März 2018 – Dezember 2024 EXP_LONG",
+ website: emptyWebsite(),
+ description: richText("Experience dated details"),
+ roles: [
+ {
+ id: "role-one",
+ position: "Role One",
+ period: "2020 ROLE_ONE",
+ description: richText("First role details"),
+ },
+ {
+ id: "role-two",
+ position: "Role Two",
+ period: "2022 ROLE_TWO",
+ description: richText("Second role details"),
+ },
+ ],
+ },
+ {
+ id: "experience-no-location",
+ hidden: false,
+ company: "Experience No Location",
+ position: "Solo Role",
+ location: "",
+ period: "2016 EXP_NO_LOCATION",
+ website: emptyWebsite(),
+ description: "",
+ roles: [],
+ },
+ {
+ id: "experience-no-period",
+ hidden: false,
+ company: "Experience No Period",
+ position: "No Date Role",
+ location: "Oslo EXP_NO_PERIOD",
+ period: "",
+ website: emptyWebsite(),
+ description: "",
+ roles: [],
+ },
+ ],
+ };
+ data.sections.education = {
+ ...data.sections.education,
+ title: "Education matrix",
+ items: [
+ {
+ id: "education-dated",
+ hidden: false,
+ school: "Education School",
+ degree: "Degree",
+ area: "Area",
+ grade: "Grade",
+ location: "Education City",
+ period: "2019 EDU_DATE",
+ website: emptyWebsite(),
+ description: richText("Education details"),
+ },
+ {
+ id: "education-empty-period",
+ hidden: false,
+ school: "Education Empty Period",
+ degree: "",
+ area: "",
+ grade: "",
+ location: "",
+ period: "",
+ website: emptyWebsite(),
+ description: "",
+ },
+ ],
+ };
+ data.sections.projects = {
+ ...data.sections.projects,
+ title: "Projects matrix",
+ items: [
+ {
+ id: "project-dated",
+ hidden: false,
+ name: "Project Entry",
+ period: "2020 PROJECT_DATE",
+ website: emptyWebsite(),
+ description: richText("Project details"),
+ },
+ ],
+ };
+ data.sections.awards = {
+ ...data.sections.awards,
+ title: "Awards matrix",
+ items: [
+ {
+ id: "award-dated",
+ hidden: false,
+ title: "Award Entry",
+ awarder: "Award Society",
+ date: "2021 AWARD_DATE",
+ website: emptyWebsite(),
+ description: richText("Award details"),
+ },
+ ],
+ };
+ data.sections.certifications = {
+ ...data.sections.certifications,
+ title: "Certifications matrix",
+ items: [
+ {
+ id: "certification-dated",
+ hidden: false,
+ title: "Certification Entry",
+ issuer: "Certification Issuer",
+ date: "2022 CERT_DATE",
+ website: emptyWebsite(),
+ description: richText("Certification details"),
+ },
+ ],
+ };
+ data.sections.publications = {
+ ...data.sections.publications,
+ title: "Publications matrix",
+ items: [
+ {
+ id: "publication-empty-date",
+ hidden: false,
+ title: "Publication Empty Date",
+ publisher: "Publication Publisher",
+ date: "",
+ website: emptyWebsite(),
+ description: richText("Publication details"),
+ },
+ ],
+ };
+ data.sections.volunteer = {
+ ...data.sections.volunteer,
+ title: "Volunteer matrix",
+ items: [
+ {
+ id: "volunteer-dated",
+ hidden: false,
+ organization: "Volunteer Organization",
+ location: "Volunteer City",
+ period: "2023 VOLUNTEER_DATE",
+ website: emptyWebsite(),
+ description: richText("Volunteer details"),
+ },
+ ],
+ };
+
+ data.customSections = [
+ {
+ ...data.sections.experience,
+ id: "custom-experience",
+ type: "experience",
+ title: "Custom Experience matrix",
+ items: [
+ {
+ ...required(data.sections.experience.items[0]),
+ id: "custom-experience-item",
+ company: "Custom Experience",
+ location: "Custom Oslo",
+ period: "2024 CUSTOM_EXP_DATE",
+ roles: [
+ {
+ id: "custom-role",
+ position: "Custom Role",
+ period: "2024 CUSTOM_ROLE_DATE",
+ description: "",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ ...data.sections.education,
+ id: "custom-education",
+ type: "education",
+ title: "Custom Education matrix",
+ items: [
+ { ...required(data.sections.education.items[0]), id: "custom-education-item", period: "2024 CUSTOM_EDU_DATE" },
+ ],
+ },
+ {
+ ...data.sections.projects,
+ id: "custom-projects",
+ type: "projects",
+ title: "Custom Projects matrix",
+ items: [
+ { ...required(data.sections.projects.items[0]), id: "custom-project-item", period: "2024 CUSTOM_PROJECT_DATE" },
+ ],
+ },
+ {
+ ...data.sections.awards,
+ id: "custom-awards",
+ type: "awards",
+ title: "Custom Awards matrix",
+ items: [{ ...required(data.sections.awards.items[0]), id: "custom-award-item", date: "2024 CUSTOM_AWARD_DATE" }],
+ },
+ {
+ ...data.sections.certifications,
+ id: "custom-certifications",
+ type: "certifications",
+ title: "Custom Certifications matrix",
+ items: [
+ {
+ ...required(data.sections.certifications.items[0]),
+ id: "custom-certification-item",
+ date: "2024 CUSTOM_CERT_DATE",
+ },
+ ],
+ },
+ {
+ ...data.sections.publications,
+ id: "custom-publications",
+ type: "publications",
+ title: "Custom Publications matrix",
+ items: [{ ...required(data.sections.publications.items[0]), id: "custom-publication-item", date: "" }],
+ },
+ {
+ ...data.sections.volunteer,
+ id: "custom-volunteer",
+ type: "volunteer",
+ title: "Custom Volunteer matrix",
+ items: [
+ {
+ ...required(data.sections.volunteer.items[0]),
+ id: "custom-volunteer-item",
+ period: "2024 CUSTOM_VOLUNTEER_DATE",
+ },
+ ],
+ },
+ ];
+
+ return data;
+};
+
+const extractPages = async (bytes: Uint8Array): Promise => {
+ const loadingTask = getDocument({ data: bytes, useSystemFonts: true });
+ try {
+ const document = await loadingTask.promise;
+ const pages: PdfPageText[] = [];
+ for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) {
+ const page = await document.getPage(pageNumber);
+ const content = await page.getTextContent();
+ pages.push({
+ pageNumber,
+ items: content.items.filter(
+ (item): item is TextItem => "str" in item && typeof item.str === "string" && item.str.trim().length > 0,
+ ),
+ });
+ }
+ return pages;
+ } finally {
+ await loadingTask.destroy();
+ }
+};
+
+const renderFixture = async (data: ResumeData, template: Template): Promise => {
+ const element = createElement(ResumeDocument, { data, template }) as unknown as Parameters[0];
+ let bytes = new Uint8Array();
+ await act(async () => {
+ bytes = new Uint8Array(await renderToBuffer(element));
+ });
+ const pages = await extractPages(bytes.slice());
+ const raster = await rasterizePdf(bytes.slice());
+ return { bytes, pages, raster };
+};
+
+const allItems = (result: RenderedFixture) => result.pages.flatMap((page) => page.items);
+
+const textFor = (result: RenderedFixture) =>
+ allItems(result)
+ .map((item) => item.str)
+ .join(" ");
+
+const markerItems = (result: RenderedFixture, marker: DateMarker) =>
+ allItems(result).filter((item) => new RegExp(`(?:^|\\s)${marker}(?=\\s|$)`).test(item.str));
+
+const textItems = (result: RenderedFixture, text: string) => allItems(result).filter((item) => item.str.includes(text));
+
+const coordinatesFor = (result: RenderedFixture, markers: readonly DateMarker[]) =>
+ Object.fromEntries(
+ markers.map((marker) => [
+ marker,
+ markerItems(result, marker).map((item) => ({
+ x: Number(item.transform[4].toFixed(2)),
+ y: Number(item.transform[5].toFixed(2)),
+ width: Number(item.width.toFixed(2)),
+ text: item.str,
+ })),
+ ]),
+ );
+
+const requiredMarkers = (result: RenderedFixture) => {
+ const text = textFor(result);
+ for (const marker of dateMarkers) expect(text).toContain(marker);
+ return coordinatesFor(result, dateMarkers);
+};
+
+const writeArtifacts = (name: string, result: RenderedFixture, coordinates: Record): void => {
+ const output = process.env.DATE_LAYOUT_ARTIFACT_DIR;
+ if (!output) return;
+ mkdirSync(output, { recursive: true });
+ writeFileSync(join(output, `${name}.pdf`), result.bytes);
+ writeFileSync(
+ join(output, `${name}.json`),
+ JSON.stringify(
+ {
+ pages: result.pages.map((page) => ({ pageNumber: page.pageNumber, itemCount: page.items.length })),
+ coordinates,
+ },
+ null,
+ 2,
+ ),
+ );
+ for (const [index, page] of result.raster.entries()) {
+ writeFileSync(join(output, `${name}-page-${index + 1}.png`), encode(page));
+ }
+};
+
+type HostNode = {
+ type: string;
+ style?: unknown;
+ value?: string;
+ children?: HostNode[];
+};
+
+const nodeText = (node: HostNode): string => node.value ?? (node.children ?? []).map(nodeText).join("");
+
+const findLink = (node: HostNode, target: string): HostNode | undefined => {
+ if (node.type === "LINK" && nodeText(node) === target) return node;
+ for (const child of node.children ?? []) {
+ const match = findLink(child, target);
+ if (match) return match;
+ }
+};
+
+const linkDecoration = async (hideLinkUnderline: boolean) => {
+ const data = structuredClone(defaultResumeData);
+ data.picture.hidden = true;
+ data.basics.name = "Link control";
+ data.basics.email = "dates@example.com";
+ data.metadata.page.hideLinkUnderline = hideLinkUnderline;
+ data.metadata.layout.pages = [{ fullWidth: true, main: [], sidebar: [] }];
+ const instance = pdf(createElement(ResumeDocument, { data, template: "chikorita" }) as never);
+ await expect.poll(() => instance.container.document).not.toBeNull();
+ const link = findLink(instance.container.document as HostNode, data.basics.email);
+ const styles = Array.isArray(link?.style) ? link.style : link?.style ? [link.style] : [];
+ return Object.assign({}, ...styles).textDecoration;
+};
+
+const flatten = (node: SemanticNode): SemanticNode[] => [node, ...node.children.flatMap(flatten)];
+
+describe("date layout characterization (#3155, #2841)", () => {
+ it.each([
+ ["chikorita", "ltr"],
+ ["ditto", "ltr"],
+ ["chikorita", "rtl"],
+ ["ditto", "rtl"],
+ ] as const)("records Q7 date matrix coordinates and raster for %s %s", async (template, direction) => {
+ const data = dateFixture(direction === "rtl" ? "ar-SA" : "en-US");
+ const result = await renderFixture(data, template);
+ const coordinates = requiredMarkers(result);
+ const text = textFor(result);
+ expect(text).toContain("Publication Empty Date");
+ expect(text).toContain("Custom Publications matrix");
+ const location = textItems(result, "Oslo LOCATION_ORDER");
+ expect(
+ location,
+ location.map((item) => `${item.str}@${item.transform[4]},${item.transform[5]}`).join(" | "),
+ ).toHaveLength(1);
+ coordinates.LOCATION_ORDER = location.map((item) => ({
+ x: Number(item.transform[4].toFixed(2)),
+ y: Number(item.transform[5].toFixed(2)),
+ width: Number(item.width.toFixed(2)),
+ text: item.str,
+ }));
+ if (template === "chikorita" && direction === "ltr") {
+ const date = required(markerItems(result, "EXP_LONG")[0]);
+ expect(location[0]?.transform[5]).toBeGreaterThan(date.transform[5]);
+ }
+ for (const marker of dateMarkers) expect(markerItems(result, marker), marker).toHaveLength(1);
+ writeArtifacts(`${template}-${direction}`, result, coordinates);
+ });
+
+ it("records default date evidence for every template without claiming parity geometry", async () => {
+ const evidence: Record = {};
+ for (const template of templates) {
+ const result = await renderFixture(dateFixture(), template);
+ const coordinates = coordinatesFor(result, dateMarkers);
+ evidence[template] = {
+ pageCount: result.pages.length,
+ textItemCount: allItems(result).length,
+ missingMarkers: dateMarkers.filter((marker) => markerItems(result, marker).length === 0),
+ rasterSha256: result.raster.map((page) => createHash("sha256").update(page.data).digest("hex")),
+ coordinates,
+ };
+ }
+ const output = process.env.DATE_LAYOUT_ARTIFACT_DIR;
+ if (output) {
+ mkdirSync(output, { recursive: true });
+ writeFileSync(join(output, "all-templates.json"), JSON.stringify(evidence, null, 2));
+ }
+ });
+
+ it("verifies #2841 link underline toggle independently of date layout", async () => {
+ expect(await linkDecoration(false)).toBe("underline");
+ expect(await linkDecoration(true)).toBe("none");
+ });
+
+ it("verifies #2841 level design variants and explicit size control independently", () => {
+ const data = dateFixture();
+ data.sections.skills.items = [
+ {
+ id: "skill-level-control",
+ hidden: false,
+ icon: "star",
+ iconColor: "",
+ name: "Level control",
+ proficiency: "",
+ level: 3,
+ keywords: [],
+ },
+ ];
+ data.metadata.layout.pages = [{ fullWidth: true, main: ["skills"], sidebar: [] }];
+ for (const type of ["hidden", "circle", "square", "rectangle", "rectangle-full", "progress-bar", "icon"] as const) {
+ data.metadata.design.level = { type, icon: "star" };
+ const runtime = resolveResumeRuntime({ data, template: "chikorita", mode: "legacy" });
+ const level = flatten(runtime.sourceTree).find((node) => node.kind === "level");
+ if (type === "hidden") expect(level).toBeUndefined();
+ else expect(level?.children.every((node) => node.attributes.type === type)).toBe(true);
+ }
+ expect(resolveLevelDisplaySizes({ bodyFontSize: 12 })).toEqual({ decorationSize: 10, levelIconExplicitSize: 14 });
+ expect(resolveLevelDisplaySizes({ bodyFontSize: 12, levelFontSize: 7 })).toEqual({
+ decorationSize: 7,
+ levelIconExplicitSize: 7,
+ });
+ });
+});
diff --git a/packages/pdf/test-artifacts/date-layout/README.md b/packages/pdf/test-artifacts/date-layout/README.md
new file mode 100644
index 000000000..a3fee1d48
--- /dev/null
+++ b/packages/pdf/test-artifacts/date-layout/README.md
@@ -0,0 +1,35 @@
+# Plan 24 Step 1 — date layout characterization
+
+Revision under test: `66c25efe1` (`origin/main`), branch `codex/issue-3155-date-layout-characterization`.
+
+The durable fixture is [`date-layout.test.tsx`](../../src/templates/shared/date-layout.test.tsx). It renders the same unchanged resume data through Chikorita and Ditto in LTR and RTL modes, extracts PDF text coordinates, rasterizes each PDF page, and records default evidence for all 15 templates.
+
+## Matrix coverage
+
+- Standard date-bearing sections: Awards (`date`), Certifications (`date`), Education (`period`), Experience (`period` and two nested role periods), Projects (`period`), Publications (`date`), and Volunteer (`period`).
+- Custom equivalents: all seven supported types, including a custom experience role and a custom publication with an empty date.
+- Edge values: long localized German date, blank location, blank period, mixed dated/undated entries, and RTL locale (`ar-SA`).
+- #2841 controls: `hideLinkUnderline` resolves independently to `underline`/`none`; all seven level design types are represented in the semantic tree; default and explicit level display sizes resolve independently.
+
+## Current output evidence
+
+The Chikorita LTR JSON records current default coordinates such as:
+
+- Long Experience date: `x=381.24`, `width=186.04`.
+- Chikorita Experience location: `x=452.35`, `y=706.39`; its date is `y=690.64`, so current PDF coordinates place location before date in the existing split header.
+- Parent role dates: `x=488.00` and `x=486.54`.
+- Education period combined with location: `x=416.99`, `width=150.29`.
+- Project period: `x=465.16`.
+- Award date: `x=475.41`.
+- Certification date: `x=484.71`.
+- Volunteer period: `x=450.91`.
+
+These are observations of current disabled/default output, not proposed geometry. All-template JSON records page counts, text-item counts, raster SHA-256 values, coordinates, and any missing marker. Meowth omits the location-only marker in the blank-period Experience case; this is recorded as default behavior rather than normalized in the fixture.
+
+## Issue disposition and gate
+
+**#3155:** Q4, Q5, Q7, and Q8 are reflected in the fixture contract: optional dedicated date column, logical-start placement, all free-text date sections/custom equivalents, and all templates with default layout preserved. Current output confirms dates are trailing/inline in the existing layout; it does not establish the future dedicated-column geometry.
+
+**#2841:** Date/location ordering remains a separate layout concern. Existing `hideLinkUnderline` and level design/size controls are present and independently verified; no duplicate schema/UI/runtime controls were added. Historical Chikorita v4 pixel parity remains unverified because no reproducible v4 reference was available.
+
+**STOP — exact missing visual-geometry gate:** Q6 approves user-controlled per-section width and wrapping but does not specify width units, minimum/maximum bounds, or initial/default width geometry. Narrow-width behavior can be characterized, but implementation must wait for those numeric/geometry inputs; this step intentionally adds no date-column schema, UI, or renderer behavior.
diff --git a/packages/pdf/test-artifacts/date-layout/all-templates.json b/packages/pdf/test-artifacts/date-layout/all-templates.json
new file mode 100644
index 000000000..19dfc3937
--- /dev/null
+++ b/packages/pdf/test-artifacts/date-layout/all-templates.json
@@ -0,0 +1,2200 @@
+{
+ "azurill": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "94a64232003bc0aa5cabbf2a8b2348dda09afea8b9b849c066f06a3448351451",
+ "dad92a02d4650191c0fa329efaecbb900e1da985a1928c4915b39471382ce0c6"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 693.74,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 609.74,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 677.99,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 646.49,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 572.99,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 485.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 388.74,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 316.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 229.49,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 54.49,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 741.14,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 725.39,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 653.64,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 581.89,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 510.14,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 422.64,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 247.64,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "bronzor": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "70a1993501f630837235ee0442264ba9351c95392fffeaa43c5deee80c7ad247",
+ "e9a016fad7cdb59cf09ec7c988261a222b1058bb512f3b176afdda9b25eebeef"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 710.74,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 626.74,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 694.99,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 663.49,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 589.99,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 519.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 443.74,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 388.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 318.49,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 177.49,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 91.24,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 75.49,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 812.89,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 758.14,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 703.39,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 632.89,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 491.89,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "chikorita": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "d40eb546a701a9a37fb4a97aaf5b7c7eafa726304e583f9d70ef70948b92e29e",
+ "244ed00fb13974f3c18bbed7cfafa217a2f7cb025113a612126d729d3799340d"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 690.64,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 606.64,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 674.89,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 643.39,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 569.89,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 480.04,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 384.94,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 310.84,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 220.99,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 41.29,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 723.04,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 707.29,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 633.19,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 559.09,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 484.99,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 395.14,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 215.44,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "ditgar": {
+ "pageCount": 2,
+ "textItemCount": 88,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "e918144fb978bcc538d6edf9c10adce410b2693c83ca3dc36277285e3ca9e405",
+ "52c479c3e58efb87b3853df81300c3e3e4c9bb760e5e0203a6f99189c522eac4"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 774.99,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 686.49,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 757.74,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 724.74,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 648.99,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 560.34,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 463.44,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 389.04,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 299.64,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 120.84,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 812.89,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 795.64,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 721.99,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 647.59,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 573.19,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 483.79,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 304.99,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "ditto": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "20dcd5d72b3a9dae4fc9eacc235d14309ec108504bc4aa50a355e93f2ff51cd3",
+ "0d1c3194791fe018870e070f04b1e39b0cfd32fcaca090e7de1f9ec9233f2899"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 655.24,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 571.24,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 639.49,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 607.99,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 534.49,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 445.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 351.24,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 277.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 188.99,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 812.89,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 708.14,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 692.39,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 619.14,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 545.89,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 472.64,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 383.64,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 205.64,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "gengar": {
+ "pageCount": 2,
+ "textItemCount": 88,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "5f0bde730468f4eb2642d861197e1fa400b4ac3d7f7392cb40de220e666ab19f",
+ "6bdec54da543be1f2099d5753746cd6dcb7560cc71f805a13325a7dac27ee893"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 774.99,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 690.99,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 759.24,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 727.74,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 654.24,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 565.59,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 471.69,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 398.79,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 310.14,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 132.84,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 28.44,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 12.69,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 794.99,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 722.09,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 649.19,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 560.54,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 383.24,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "glalie": {
+ "pageCount": 2,
+ "textItemCount": 88,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "9fbcffc4e0c3cb8d160371f94a4dede9b84224a4d8009142913bd65aa45f08f4",
+ "9c5bd9f9b31e5d6ef1f73ef73e72a1dcc792651344734068ffb311ab9f77ba4d"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 773.64,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 689.64,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 757.89,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 726.39,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 652.89,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 562.89,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 467.64,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 393.39,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 303.39,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 123.39,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 812.89,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 797.14,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 722.89,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 648.64,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 574.39,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 484.39,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 304.39,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "kakuna": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "b93a725b79f200ab91a1914a96bd07c259f7b0060e3dd2c765c58021270b0986",
+ "58c0d98acb45cdfb8bdd7b75a74d07cbe17beea473f4f6b08e7052f7aff0b380"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 690.49,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 606.49,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 674.74,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 643.24,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 569.74,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 478.99,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 382.99,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 307.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 217.24,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 35.74,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 722.14,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 706.39,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 631.39,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 556.39,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 481.39,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 390.64,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 209.14,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "lapras": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "56eea42ffab93d25e038b1fd411e708cc07c377f350a67cb55bcb6423d50ff49",
+ "68d02d2741097a226889dd396ebbc08c30d03554a667da7dbc08453def126470"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 376.24,
+ "y": 708.24,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 441.21,
+ "y": 624.24,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 483,
+ "y": 692.49,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 481.54,
+ "y": 660.99,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 454.47,
+ "y": 587.49,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 411.99,
+ "y": 519.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 460.16,
+ "y": 446.24,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 470.41,
+ "y": 393.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 479.71,
+ "y": 325.99,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 445.91,
+ "y": 189.99,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 438.13,
+ "y": 106.24,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 431.11,
+ "y": 90.49,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 363.5,
+ "y": 38.24,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 411.67,
+ "y": 776.39,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 421.92,
+ "y": 724.14,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 431.22,
+ "y": 656.14,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 397.42,
+ "y": 520.14,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "leafish": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "b68582f685ce63345cb57a64cd20d15cd8c4be76fc3313990d81ecd1dda4d668",
+ "7340fa42411c6033e8e9494094d3d108361d43c776c00350802f1f42d1354354"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 634.24,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 550.24,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 618.49,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 586.99,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 513.49,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 423.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 328.24,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 253.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 163.99,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 789.39,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 683.64,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 667.89,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 593.64,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 519.39,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 445.14,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 355.14,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 175.14,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "meowth": {
+ "pageCount": 2,
+ "textItemCount": 85,
+ "missingMarkers": ["EXP_NO_PERIOD"],
+ "rasterSha256": [
+ "c2810a7899ebddb79df314102c149930fd440b9f41a5685cd201197dd117efb2",
+ "9393a6fa7b5d1a8881a7b5499ab5f8b4fb2b085e0197310e09c6b5e022553946"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 706.24,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 607.24,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 675.49,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 643.99,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [],
+ "EDU_DATE": [
+ {
+ "x": 490.52,
+ "y": 526.99,
+ "width": 76.76,
+ "text": "2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 415.24,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 340.24,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 249.49,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 67.99,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 788.64,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 772.89,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 442.03,
+ "y": 713.64,
+ "width": 125.25,
+ "text": "2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 622.89,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 547.89,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 457.14,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 275.64,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "onyx": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "572629027ad7541108e91f20c4861b52a6a9a6cc319c0753c0794206cb7cbb11",
+ "6c653ccf5325ddb9aa048850a732a3f6fba3b76e37edd3a266b26272c7b543b3"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 671.24,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 587.24,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 655.49,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 623.99,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 550.49,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 461.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 367.24,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 293.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 204.99,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 26.99,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 723.89,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 708.14,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 634.89,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 561.64,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 488.39,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 399.39,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 221.39,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "pikachu": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "3e982b77e57fd1d9bf57e3acb2c9a3975ac73124311d599fd67a7260589919fb",
+ "7340fa42411c6033e8e9494094d3d108361d43c776c00350802f1f42d1354354"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 647.24,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 563.24,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 631.49,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 599.99,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 526.49,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 436.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 341.24,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 266.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 176.99,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 789.39,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 683.64,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 667.89,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 593.64,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 519.39,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 445.14,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 355.14,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 175.14,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "rhyhorn": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "e71b95a3b60166447788aae981a92dddf0b34d7f1d70bed788294f75492db6f6",
+ "59f28b8c74354e1f206f1f63088e168d9772fbf20e661bf8adeed3e544f4301b"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 691.24,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 607.24,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 675.49,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 643.99,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 570.49,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 480.49,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 385.24,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 310.99,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 220.99,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 40.99,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 722.89,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 707.14,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 632.89,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 558.64,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 484.39,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 394.39,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 214.39,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ },
+ "scizor": {
+ "pageCount": 2,
+ "textItemCount": 87,
+ "missingMarkers": [],
+ "rasterSha256": [
+ "b5785640ab77cb1b69c085d8ab838efccb721315a3a25b8913b05e55a0ea3e58",
+ "cc10d4fa1ce380f546e0ed3deef45af526de957f0c026cff668f299b8626d989"
+ ],
+ "coordinates": {
+ "EXP_LONG": [
+ {
+ "x": 381.24,
+ "y": 673.27,
+ "width": 186.04,
+ "text": "März 2018 – Dezember 2024 EXP_LONG"
+ }
+ ],
+ "EXP_NO_LOCATION": [
+ {
+ "x": 446.21,
+ "y": 589.27,
+ "width": 121.07,
+ "text": "2016 EXP_NO_LOCATION"
+ }
+ ],
+ "ROLE_ONE": [
+ {
+ "x": 488,
+ "y": 657.52,
+ "width": 79.28,
+ "text": "2020 ROLE_ONE"
+ }
+ ],
+ "ROLE_TWO": [
+ {
+ "x": 486.54,
+ "y": 626.02,
+ "width": 80.74,
+ "text": "2022 ROLE_TWO"
+ }
+ ],
+ "EXP_NO_PERIOD": [
+ {
+ "x": 459.47,
+ "y": 552.52,
+ "width": 107.81,
+ "text": "Oslo EXP_NO_PERIOD"
+ }
+ ],
+ "EDU_DATE": [
+ {
+ "x": 416.99,
+ "y": 460.72,
+ "width": 150.29,
+ "text": "Education City • 2019 EDU_DATE"
+ }
+ ],
+ "PROJECT_DATE": [
+ {
+ "x": 465.16,
+ "y": 363.67,
+ "width": 102.12,
+ "text": "2020 PROJECT_DATE"
+ }
+ ],
+ "AWARD_DATE": [
+ {
+ "x": 475.41,
+ "y": 287.62,
+ "width": 91.87,
+ "text": "2021 AWARD_DATE"
+ }
+ ],
+ "CERT_DATE": [
+ {
+ "x": 484.71,
+ "y": 195.82,
+ "width": 82.57,
+ "text": "2022 CERT_DATE"
+ }
+ ],
+ "VOLUNTEER_DATE": [
+ {
+ "x": 450.91,
+ "y": 810.19,
+ "width": 116.37,
+ "text": "2023 VOLUNTEER_DATE"
+ }
+ ],
+ "CUSTOM_EXP_DATE": [
+ {
+ "x": 443.13,
+ "y": 702.64,
+ "width": 124.15,
+ "text": "2024 CUSTOM_EXP_DATE"
+ }
+ ],
+ "CUSTOM_ROLE_DATE": [
+ {
+ "x": 436.11,
+ "y": 686.89,
+ "width": 131.17,
+ "text": "2024 CUSTOM_ROLE_DATE"
+ }
+ ],
+ "CUSTOM_EDU_DATE": [
+ {
+ "x": 368.5,
+ "y": 610.84,
+ "width": 198.78,
+ "text": "Education City • 2024 CUSTOM_EDU_DATE"
+ }
+ ],
+ "CUSTOM_PROJECT_DATE": [
+ {
+ "x": 416.67,
+ "y": 534.79,
+ "width": 150.61,
+ "text": "2024 CUSTOM_PROJECT_DATE"
+ }
+ ],
+ "CUSTOM_AWARD_DATE": [
+ {
+ "x": 426.92,
+ "y": 458.74,
+ "width": 140.36,
+ "text": "2024 CUSTOM_AWARD_DATE"
+ }
+ ],
+ "CUSTOM_CERT_DATE": [
+ {
+ "x": 436.22,
+ "y": 366.94,
+ "width": 131.06,
+ "text": "2024 CUSTOM_CERT_DATE"
+ }
+ ],
+ "CUSTOM_VOLUNTEER_DATE": [
+ {
+ "x": 402.42,
+ "y": 183.34,
+ "width": 164.86,
+ "text": "2024 CUSTOM_VOLUNTEER_DATE"
+ }
+ ]
+ }
+ }
+}
diff --git a/packages/pdf/test-artifacts/date-layout/chikorita-ltr-page-1.png b/packages/pdf/test-artifacts/date-layout/chikorita-ltr-page-1.png
new file mode 100644
index 0000000000000000000000000000000000000000..c98284b6d6fc9a8672579068f68cbb07d14fd66d
GIT binary patch
literal 97072
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