// SPDX-License-Identifier: MIT // Synthetic test documents only. Requires pdf-lib and Poppler. import { PDFDocument, StandardFonts, rgb } from "pdf-lib"; import { execFileSync } from "node:child_process"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { createHash } from "node:crypto"; const directory = resolve(process.env.PDF_STUDY_DIR || "public/research/scanned-pdf-fillers/2026-09-29"); await mkdir(directory, { recursive: true }); const pdf = await PDFDocument.create(); pdf.setTitle("Synthetic PDF filling benchmark form"); pdf.setAuthor("Rust Solutions Ltd — synthetic research fixture"); pdf.setCreationDate(new Date("2026-09-29T00:00:00Z")); pdf.setModificationDate(new Date("2026-09-29T00:00:00Z")); const font = await pdf.embedFont(StandardFonts.Helvetica); const page = pdf.addPage([600, 480]); const ink = rgb(.12, .18, .16); for (const [text, x, y, size] of [ ["PDF filling test — fictional answers only", 40, 425, 20], ["This is not an application. There are no interactive form fields.", 40, 392, 12], ["Name", 40, 310, 16], ["Date", 40, 240, 16], ["STUDY-DOCUMENT-MARKER-20260929", 40, 65, 11], ["Same page artwork in the flat and image-only test documents.", 40, 40, 11] ]) page.drawText(text, { x, y, size, font, color: ink }); for (const y of [304, 234]) page.drawLine({ start: { x: 145, y }, end: { x: 552, y }, thickness: 1, color: ink }); const flatPath = resolve(directory, "flat-form.pdf"); await writeFile(flatPath, await pdf.save({ useObjectStreams: false })); execFileSync("pdftoppm", ["-singlefile", "-scale-to", "1500", "-png", flatPath, resolve(directory, "fixture-artwork")], { timeout: 15000 }); const scan = await PDFDocument.create(); scan.setTitle("Image-only PDF filling benchmark form"); scan.setCreationDate(new Date("2026-09-29T00:00:00Z")); scan.setModificationDate(new Date("2026-09-29T00:00:00Z")); const image = await scan.embedPng(await readFile(resolve(directory, "fixture-artwork.png"))); scan.addPage([600, 480]).drawImage(image, { x: 0, y: 0, width: 600, height: 480 }); const scannedPath = resolve(directory, "image-only-form.pdf"); await writeFile(scannedPath, await scan.save({ useObjectStreams: false })); if (execFileSync("pdftotext", [scannedPath, "-"], { encoding: "utf8" }).trim()) throw new Error("The image-only fixture must have no text layer"); const manifest = []; for (const [id, path] of [["flat", flatPath], ["scan", scannedPath]]) { const bytes = await readFile(path); const loaded = await PDFDocument.load(bytes); if (loaded.getForm().getFields().length) throw new Error("Fixtures must have no interactive fields"); manifest.push({ id, file: path.split("/").at(-1), sha256: createHash("sha256").update(bytes).digest("hex"), bytes: bytes.length, pages: loaded.getPageCount(), pageSize: [600, 480], extractableSourceText: id === "flat", answer: "Alex Example" }); } await writeFile(resolve(directory, "fixtures.json"), JSON.stringify(manifest, null, 2) + "\n"); console.log(JSON.stringify({ directory, manifest }, null, 2));