cerfaparse 0.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +116 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +23 -2
- package/dist/index.js +120 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -433,16 +433,16 @@ function labelToPdfCoords(label, pageHeight) {
|
|
|
433
433
|
// top edge in PDF
|
|
434
434
|
};
|
|
435
435
|
}
|
|
436
|
-
function mapLabelsToFields(fieldGroups, labels, transform, page, pageHeight) {
|
|
436
|
+
function mapLabelsToFields(fieldGroups, labels, transform, page, pageHeight, usedNames) {
|
|
437
437
|
const fields = [];
|
|
438
|
-
const
|
|
438
|
+
const names = usedNames ?? /* @__PURE__ */ new Set();
|
|
439
439
|
for (const group of fieldGroups) {
|
|
440
440
|
const pdfRect = computeGroupPdfRect(group, transform, pageHeight);
|
|
441
441
|
const bestLabel = findBestLabel(pdfRect, labels, pageHeight);
|
|
442
442
|
const labelText = bestLabel?.text ?? "";
|
|
443
443
|
const baseName = generateFieldName(labelText, group.row.type, page);
|
|
444
|
-
const name = deduplicateName(baseName,
|
|
445
|
-
|
|
444
|
+
const name = deduplicateName(baseName, names);
|
|
445
|
+
names.add(name);
|
|
446
446
|
const field = {
|
|
447
447
|
key: name,
|
|
448
448
|
type: toFieldType(group.row.type),
|
|
@@ -539,6 +539,9 @@ async function injectFields(pdfBytes, fields) {
|
|
|
539
539
|
textField.setMaxLength(field.props.maxLength);
|
|
540
540
|
textField.enableCombing();
|
|
541
541
|
}
|
|
542
|
+
if (field.props.multiline) {
|
|
543
|
+
textField.enableMultiline();
|
|
544
|
+
}
|
|
542
545
|
textField.setFontSize(0);
|
|
543
546
|
clearWidgetAppearance(textField.acroField);
|
|
544
547
|
textField.updateAppearances(font);
|
|
@@ -552,6 +555,94 @@ async function injectFields(pdfBytes, fields) {
|
|
|
552
555
|
return pdfDoc.save();
|
|
553
556
|
}
|
|
554
557
|
|
|
558
|
+
// src/extract-dot-lines.ts
|
|
559
|
+
var MIN_DOT_COUNT = 10;
|
|
560
|
+
var DOT_GROUP_Y_GAP = 20;
|
|
561
|
+
var LABEL_Y_MAX_DISTANCE2 = 30;
|
|
562
|
+
function isDotLine(text) {
|
|
563
|
+
if (!/^[.\s\u2026]+$/.test(text)) return false;
|
|
564
|
+
const dotCount = (text.match(/\./g) ?? []).length;
|
|
565
|
+
return dotCount >= MIN_DOT_COUNT;
|
|
566
|
+
}
|
|
567
|
+
function extractDotLineFields(labels, page, pageHeight, usedNames) {
|
|
568
|
+
const names = usedNames ?? /* @__PURE__ */ new Set();
|
|
569
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
570
|
+
const fields = [];
|
|
571
|
+
const dotIndices = [];
|
|
572
|
+
for (let i = 0; i < labels.length; i++) {
|
|
573
|
+
if (isDotLine(labels[i].text)) {
|
|
574
|
+
dotIndices.push(i);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (dotIndices.length === 0) return { fields: [], consumedLabelIndices: consumed };
|
|
578
|
+
const groups = [[dotIndices[0]]];
|
|
579
|
+
for (let i = 1; i < dotIndices.length; i++) {
|
|
580
|
+
const prevIdx = dotIndices[i - 1];
|
|
581
|
+
const currIdx = dotIndices[i];
|
|
582
|
+
const prevLabel = labels[prevIdx];
|
|
583
|
+
const currLabel = labels[currIdx];
|
|
584
|
+
if (Math.abs(currLabel.yMin - prevLabel.yMin) <= DOT_GROUP_Y_GAP) {
|
|
585
|
+
groups[groups.length - 1].push(currIdx);
|
|
586
|
+
} else {
|
|
587
|
+
groups.push([currIdx]);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
for (const group of groups) {
|
|
591
|
+
for (const idx of group) consumed.add(idx);
|
|
592
|
+
let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
|
|
593
|
+
for (const idx of group) {
|
|
594
|
+
const lbl = labels[idx];
|
|
595
|
+
if (lbl.xMin < xMin) xMin = lbl.xMin;
|
|
596
|
+
if (lbl.yMin < yMin) yMin = lbl.yMin;
|
|
597
|
+
if (lbl.xMax > xMax) xMax = lbl.xMax;
|
|
598
|
+
if (lbl.yMax > yMax) yMax = lbl.yMax;
|
|
599
|
+
}
|
|
600
|
+
const pdfRect = {
|
|
601
|
+
x: xMin,
|
|
602
|
+
y: pageHeight - yMax,
|
|
603
|
+
width: xMax - xMin,
|
|
604
|
+
height: yMax - yMin
|
|
605
|
+
};
|
|
606
|
+
const groupTopY = yMin;
|
|
607
|
+
const bestLabel = findTextLabelAbove(labels, groupTopY, xMin, xMax, consumed);
|
|
608
|
+
const labelText = bestLabel?.text ?? "";
|
|
609
|
+
const isMultiline = group.length > 1;
|
|
610
|
+
const baseName = generateFieldName(labelText, "cell", page);
|
|
611
|
+
const name = deduplicateName(baseName, names);
|
|
612
|
+
names.add(name);
|
|
613
|
+
fields.push({
|
|
614
|
+
key: name,
|
|
615
|
+
type: "input",
|
|
616
|
+
props: {
|
|
617
|
+
label: labelText,
|
|
618
|
+
page,
|
|
619
|
+
pdfRect,
|
|
620
|
+
...isMultiline ? { multiline: true } : {}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return { fields, consumedLabelIndices: consumed };
|
|
625
|
+
}
|
|
626
|
+
function findTextLabelAbove(labels, groupTopY, groupXMin, groupXMax, consumed) {
|
|
627
|
+
let best = null;
|
|
628
|
+
let bestDistance = Infinity;
|
|
629
|
+
for (let i = 0; i < labels.length; i++) {
|
|
630
|
+
if (consumed.has(i)) continue;
|
|
631
|
+
const lbl = labels[i];
|
|
632
|
+
const yDistance = groupTopY - lbl.yMax;
|
|
633
|
+
if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE2) continue;
|
|
634
|
+
const hasOverlap = lbl.xMin < groupXMax && lbl.xMax > groupXMin;
|
|
635
|
+
const isLeftOf = lbl.xMax <= groupXMin && groupXMin - lbl.xMax < 50;
|
|
636
|
+
if (!hasOverlap && !isLeftOf) continue;
|
|
637
|
+
const score = yDistance + (hasOverlap ? 0 : 20);
|
|
638
|
+
if (score < bestDistance) {
|
|
639
|
+
bestDistance = score;
|
|
640
|
+
best = lbl;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return best;
|
|
644
|
+
}
|
|
645
|
+
|
|
555
646
|
// src/cli.ts
|
|
556
647
|
async function convert(inputPath, outputPath) {
|
|
557
648
|
await checkPoppler();
|
|
@@ -568,17 +659,33 @@ async function convert(inputPath, outputPath) {
|
|
|
568
659
|
console.log(` Page ${page}: extracting geometry...`);
|
|
569
660
|
const svgXml = await extractSvg(inputPath, page);
|
|
570
661
|
const { boxes, transform, pageHeight } = extractBoxes(svgXml);
|
|
662
|
+
const labels = labelsByPage.get(page) ?? [];
|
|
663
|
+
const usedNames = new Set(allFields.map((f) => f.key));
|
|
664
|
+
const { fields: dotFields, consumedLabelIndices } = extractDotLineFields(
|
|
665
|
+
labels,
|
|
666
|
+
page,
|
|
667
|
+
pageHeight,
|
|
668
|
+
usedNames
|
|
669
|
+
);
|
|
670
|
+
const remainingLabels = labels.filter((_, i) => !consumedLabelIndices.has(i));
|
|
571
671
|
if (!transform || boxes.length === 0) {
|
|
572
|
-
|
|
672
|
+
if (dotFields.length === 0) {
|
|
673
|
+
console.log(` Page ${page}: no input boxes found, skipping`);
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const freeTextCount2 = dotFields.length;
|
|
677
|
+
console.log(` Page ${page}: ${freeTextCount2} free-text field(s)`);
|
|
678
|
+
allFields.push(...dotFields);
|
|
573
679
|
continue;
|
|
574
680
|
}
|
|
575
681
|
const fieldGroups = groupBoxesIntoFields(boxes, page);
|
|
576
|
-
const
|
|
577
|
-
const fields = mapLabelsToFields(fieldGroups,
|
|
682
|
+
for (const f of dotFields) usedNames.add(f.key);
|
|
683
|
+
const fields = mapLabelsToFields(fieldGroups, remainingLabels, transform, page, pageHeight, usedNames);
|
|
578
684
|
const cellCount = fields.filter((f) => f.type === "input").length;
|
|
579
685
|
const checkboxCount = fields.filter((f) => f.type === "checkbox").length;
|
|
580
|
-
|
|
581
|
-
|
|
686
|
+
const freeTextCount = dotFields.length;
|
|
687
|
+
console.log(` Page ${page}: ${cellCount} text fields, ${checkboxCount} checkboxes${freeTextCount ? `, ${freeTextCount} free-text field(s)` : ""}`);
|
|
688
|
+
allFields.push(...fields, ...dotFields);
|
|
582
689
|
}
|
|
583
690
|
console.log("Injecting AcroForm fields...");
|
|
584
691
|
const pdfBytes = new Uint8Array(await readFile2(inputPath));
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/poppler.ts","../src/extract-boxes.ts","../src/extract-labels.ts","../src/group-rows.ts","../src/transform.ts","../src/map-labels.ts","../src/inject-fields.ts"],"sourcesContent":["import { program } from 'commander';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { checkPoppler, extractBbox, extractSvg, getPageCount } from './poppler.js';\nimport { extractBoxes } from './extract-boxes.js';\nimport { extractLabels } from './extract-labels.js';\nimport { groupBoxesIntoFields } from './group-rows.js';\nimport { mapLabelsToFields } from './map-labels.js';\nimport { injectFields } from './inject-fields.js';\nimport type { Field, FieldOutput } from './types.js';\n\nexport async function convert(inputPath: string, outputPath?: string): Promise<{\n pdfOut: string;\n jsonOut: string;\n fields: Field[];\n}> {\n // 1. Check dependencies\n await checkPoppler();\n\n // 2. Resolve output paths\n const dir = dirname(inputPath);\n const base = basename(inputPath, '.pdf');\n const pdfOut = outputPath ?? join(dir, `${base}-fillable.pdf`);\n const jsonOut = join(dirname(pdfOut), `${basename(pdfOut, '.pdf')}.fields.json`);\n\n // 3. Get page count\n const pageCount = await getPageCount(inputPath);\n console.log(`Processing ${pageCount} page(s)...`);\n\n // 4. Extract bbox labels (all pages at once)\n const bboxHtml = await extractBbox(inputPath);\n const labelsByPage = extractLabels(bboxHtml);\n\n // 5. Process each page: SVG → boxes → groups → fields\n const allFields: Field[] = [];\n\n for (let page = 1; page <= pageCount; page++) {\n console.log(` Page ${page}: extracting geometry...`);\n\n const svgXml = await extractSvg(inputPath, page);\n const { boxes, transform, pageHeight } = extractBoxes(svgXml);\n\n if (!transform || boxes.length === 0) {\n console.log(` Page ${page}: no input boxes found, skipping`);\n continue;\n }\n\n const fieldGroups = groupBoxesIntoFields(boxes, page);\n const labels = labelsByPage.get(page) ?? [];\n const fields = mapLabelsToFields(fieldGroups, labels, transform, page, pageHeight);\n\n const cellCount = fields.filter((f) => f.type === 'input').length;\n const checkboxCount = fields.filter((f) => f.type === 'checkbox').length;\n console.log(` Page ${page}: ${cellCount} text fields, ${checkboxCount} checkboxes`);\n\n allFields.push(...fields);\n }\n\n // 6. Inject AcroForm fields into PDF\n console.log('Injecting AcroForm fields...');\n const pdfBytes = new Uint8Array(await readFile(inputPath));\n const filledPdf = await injectFields(pdfBytes, allFields);\n await writeFile(pdfOut, filledPdf);\n\n // 7. Write JSON field definitions\n const output: FieldOutput = {\n pages: Array.from({ length: pageCount }, (_, i) => ({\n pageNumber: i + 1,\n fields: allFields.filter((f) => f.props.page === i + 1),\n })),\n };\n await writeFile(jsonOut, JSON.stringify(output, null, 2));\n\n console.log(`\\nDone!`);\n console.log(` PDF: ${pdfOut}`);\n console.log(` JSON: ${jsonOut}`);\n console.log(` Total fields: ${allFields.length}`);\n\n return { pdfOut, jsonOut, fields: allFields };\n}\n\nfunction main() {\n program\n .name('cerfaparse')\n .description('Convert flat CERFA PDFs into fillable AcroForm PDFs with field definitions')\n .version('0.1.0');\n\n program\n .command('convert')\n .description('Convert a flat CERFA PDF to a fillable AcroForm PDF + JSON field definitions')\n .argument('<input>', 'Path to the input CERFA PDF')\n .option('-o, --output <path>', 'Output PDF path')\n .action(async (input: string, opts: { output?: string }) => {\n try {\n await convert(input, opts.output);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n });\n\n if (process.argv.length <= 2) {\n program.help();\n }\n\n program.parse();\n}\n\n// Only run CLI when executed directly (not imported as library)\nconst isMainModule = process.argv[1] && (\n import.meta.url.endsWith(process.argv[1]) ||\n import.meta.url === `file://${process.argv[1]}`\n);\nif (isMainModule) {\n main();\n}\n","import { execa } from 'execa';\nimport { access, mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nexport async function checkPoppler(): Promise<void> {\n const tools = ['pdftocairo', 'pdftotext', 'pdfinfo'];\n const missing: string[] = [];\n\n for (const tool of tools) {\n try {\n await execa(tool, ['-v']);\n } catch {\n missing.push(tool);\n }\n }\n\n if (missing.length > 0) {\n throw new Error(\n `Poppler tools not found: ${missing.join(', ')}. Install with:\\n` +\n ' macOS: brew install poppler\\n' +\n ' Linux: apt install poppler-utils',\n );\n }\n}\n\nexport async function getPageCount(pdfPath: string): Promise<number> {\n await validateFileExists(pdfPath);\n const { stdout } = await execa('pdfinfo', [pdfPath]);\n const match = stdout.match(/^Pages:\\s+(\\d+)/m);\n if (!match) throw new Error('Could not determine page count from pdfinfo');\n return parseInt(match[1], 10);\n}\n\nexport async function extractSvg(\n pdfPath: string,\n page: number,\n): Promise<string> {\n const tmpDir = await mkdtemp(join(tmpdir(), 'cerfaparse-'));\n const outPath = join(tmpDir, `page-${page}.svg`);\n try {\n await execa('pdftocairo', [\n '-svg',\n '-f',\n String(page),\n '-l',\n String(page),\n pdfPath,\n outPath,\n ]);\n return await readFile(outPath, 'utf-8');\n } finally {\n try {\n await rm(tmpDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors — don't mask the original error\n }\n }\n}\n\nexport async function extractBbox(pdfPath: string): Promise<string> {\n const { stdout } = await execa('pdftotext', [\n '-bbox-layout',\n pdfPath,\n '-',\n ]);\n return stdout;\n}\n\nasync function validateFileExists(path: string): Promise<void> {\n try {\n await access(path);\n } catch {\n throw new Error(`File not found: ${path}`);\n }\n}\n","import * as cheerio from 'cheerio';\nimport type { SvgBox, TransformMatrix } from './types.js';\n\nconst WHITE_THRESHOLD = 0.95; // RGB components above 95% → white\nconst DARK_STROKE_THRESHOLD = 0.2; // RGB components below 20% → dark\n\n/** Size thresholds to filter out large background rects and thin slivers */\nconst MAX_BOX_WIDTH = 50;\nconst MAX_BOX_HEIGHT = 50;\nconst MIN_BOX_SIZE = 3;\n\nexport interface ExtractResult {\n boxes: SvgBox[];\n transform: TransformMatrix | null;\n pageHeight: number;\n}\n\nexport function extractBoxes(svgXml: string): ExtractResult {\n const $ = cheerio.load(svgXml, { xml: true });\n\n // Extract page height from SVG viewBox or height attribute\n let pageHeight = 0;\n const viewBox = $('svg').attr('viewBox') ?? '';\n const vbParts = viewBox.split(/\\s+/);\n if (vbParts.length >= 4) {\n pageHeight = parseFloat(vbParts[3]);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n // Fallback: parse height attribute (e.g. \"572pt\" → 572)\n const heightAttr = $('svg').attr('height') ?? '';\n pageHeight = parseFloat(heightAttr);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n throw new Error('Could not extract page height from SVG viewBox or height attribute');\n }\n\n let transform: TransformMatrix | null = null;\n const boxes: SvgBox[] = [];\n\n $('path').each((_, el) => {\n const path = $(el);\n const d = path.attr('d');\n const fill = path.attr('fill') ?? '';\n const stroke = path.attr('stroke') ?? '';\n const strokeWidth = parseFloat(path.attr('stroke-width') ?? '0');\n\n if (!d || !isWhiteColor(fill)) return;\n\n const rect = parseRectPath(d);\n if (!rect) return;\n if (rect.width > MAX_BOX_WIDTH || rect.height > MAX_BOX_HEIGHT) return;\n if (rect.width < MIN_BOX_SIZE || rect.height < MIN_BOX_SIZE) return;\n\n const type = classifyBox(stroke, strokeWidth);\n if (!type) return;\n\n // Extract and validate transform from each matching element\n const pathTransform = parseTransformFromAttr(path.attr('transform') ?? '');\n if (!pathTransform) return;\n\n // Compose with ancestor transforms (<g> parents, or <use> for paths in <defs>)\n const ancestorTransform = collectAncestorTransform($, el);\n const effectiveTransform = ancestorTransform\n ? composeTransforms(ancestorTransform, pathTransform)\n : pathTransform;\n\n if (!transform) {\n transform = effectiveTransform;\n } else if (!matricesEqual(transform, effectiveTransform)) {\n console.warn(\n 'Warning: found input box with different transform matrix — skipping. ' +\n 'This may indicate rotated sections.',\n );\n return;\n }\n\n boxes.push({ ...rect, type });\n });\n\n return { boxes, transform, pageHeight };\n}\n\n/**\n * Collect the composed ancestor transform for a path element.\n *\n * Walks up from the path to <svg>, collecting <g> transforms along the way.\n * If the path is inside <defs> (rendered indirectly via <use>), finds the\n * root <use> element's transform instead — in pdftocairo output, intermediate\n * group transforms within the <defs>/<use> chain cancel out, leaving only\n * the outermost <use> transform as the net effect.\n */\nfunction collectAncestorTransform(\n $: cheerio.CheerioAPI,\n el: any,\n): TransformMatrix | null {\n const transforms: TransformMatrix[] = [];\n let insideDefs = false;\n let current = (el as any).parent;\n\n while (current) {\n const tag = current.tagName ?? current.name ?? '';\n if (tag === 'svg') break;\n\n if (tag === 'defs') {\n insideDefs = true;\n break;\n }\n\n if (tag === 'g') {\n const t = $(current).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) transforms.unshift(parsed); // outermost first\n }\n\n current = current.parent;\n }\n\n if (insideDefs) {\n // Path lives in <defs> — rendered via <use> reference chain.\n // Find the outermost <use> (direct child of <svg>) with a transform.\n // Intermediate transforms within the defs/use chain cancel in pdftocairo output.\n const svgChildren = $('svg').children().toArray();\n for (const child of svgChildren) {\n const tag = (child as any).tagName ?? '';\n if (tag !== 'use') continue;\n const t = $(child).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) return parsed;\n }\n return null;\n }\n\n if (transforms.length === 0) return null;\n return transforms.reduce(composeTransforms);\n}\n\n/**\n * Parse a transform attribute — supports both matrix() and translate() syntax.\n */\nfunction parseTransformAttr(attr: string): TransformMatrix | null {\n // Try matrix() first\n const matrixMatch = attr.match(MATRIX_RE);\n if (matrixMatch) {\n const m: TransformMatrix = {\n a: parseFloat(matrixMatch[1]),\n b: parseFloat(matrixMatch[2]),\n c: parseFloat(matrixMatch[3]),\n d: parseFloat(matrixMatch[4]),\n e: parseFloat(matrixMatch[5]),\n f: parseFloat(matrixMatch[6]),\n };\n if (Object.values(m).some((v) => !Number.isFinite(v))) return null;\n return m;\n }\n\n // Try translate()\n const translateMatch = attr.match(\n /translate\\(\\s*([-\\d.]+)(?:\\s*,\\s*([-\\d.]+))?\\s*\\)/,\n );\n if (translateMatch) {\n const tx = parseFloat(translateMatch[1]);\n const ty = parseFloat(translateMatch[2] ?? '0');\n if (!Number.isFinite(tx) || !Number.isFinite(ty)) return null;\n return { a: 1, b: 0, c: 0, d: 1, e: tx, f: ty };\n }\n\n return null;\n}\n\n/**\n * Compose two 2D affine transforms: result = m1 * m2\n * Applies m2 first, then m1 (standard matrix multiplication order).\n */\nfunction composeTransforms(\n m1: TransformMatrix,\n m2: TransformMatrix,\n): TransformMatrix {\n return {\n a: m1.a * m2.a + m1.c * m2.b,\n b: m1.b * m2.a + m1.d * m2.b,\n c: m1.a * m2.c + m1.c * m2.d,\n d: m1.b * m2.c + m1.d * m2.d,\n e: m1.a * m2.e + m1.c * m2.f + m1.e,\n f: m1.b * m2.e + m1.d * m2.f + m1.f,\n };\n}\n\nconst MATRIX_RE =\n /matrix\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)/;\n\nfunction parseTransformFromAttr(attr: string): TransformMatrix | null {\n return parseTransformAttr(attr);\n}\n\nfunction matricesEqual(a: TransformMatrix, b: TransformMatrix): boolean {\n const tol = 0.001;\n return (\n Math.abs(a.a - b.a) < tol &&\n Math.abs(a.b - b.b) < tol &&\n Math.abs(a.c - b.c) < tol &&\n Math.abs(a.d - b.d) < tol &&\n Math.abs(a.e - b.e) < tol &&\n Math.abs(a.f - b.f) < tol\n );\n}\n\n/**\n * Parse a rectangular path from 4 corner points, regardless of winding order.\n * Computes bounding box from all points.\n */\nfunction parseRectPath(\n d: string,\n): { x: number; y: number; width: number; height: number } | null {\n const commands = d.trim().split(/\\s*([MLHVCSQTAZ])\\s*/i).filter(Boolean);\n\n const points: [number, number][] = [];\n let i = 0;\n while (i < commands.length) {\n const cmd = commands[i];\n if (cmd === 'M' || cmd === 'L') {\n const coords = commands[i + 1]?.trim().split(/\\s+/);\n if (coords && coords.length >= 2) {\n const x = parseFloat(coords[0]);\n const y = parseFloat(coords[1]);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n points.push([x, y]);\n }\n i += 2;\n } else if (cmd === 'Z') {\n i += 1;\n } else {\n return null;\n }\n }\n\n if (points.length < 4) return null;\n\n // Use first 4 points to compute bounding box (handles any winding order)\n const xs = points.slice(0, 4).map((p) => p[0]);\n const ys = points.slice(0, 4).map((p) => p[1]);\n const minX = Math.min(...xs);\n const maxX = Math.max(...xs);\n const minY = Math.min(...ys);\n const maxY = Math.max(...ys);\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n if (width < 1 || height < 1) return null;\n\n return { x: minX, y: minY, width, height };\n}\n\n/** Parse an rgb(r%, g%, b%) color string and return [0-1] components, or null */\nfunction parseRgbPercent(color: string): [number, number, number] | null {\n const match = color.match(\n /rgb\\(\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*\\)/,\n );\n if (!match) return null;\n return [\n parseFloat(match[1]) / 100,\n parseFloat(match[2]) / 100,\n parseFloat(match[3]) / 100,\n ];\n}\n\nfunction isWhiteColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] >= WHITE_THRESHOLD && rgb[1] >= WHITE_THRESHOLD && rgb[2] >= WHITE_THRESHOLD;\n}\n\nfunction isDarkColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] < DARK_STROKE_THRESHOLD && rgb[1] < DARK_STROKE_THRESHOLD && rgb[2] < DARK_STROKE_THRESHOLD;\n}\n\nfunction classifyBox(\n stroke: string,\n strokeWidth: number,\n): 'cell' | 'checkbox' | null {\n if (isWhiteColor(stroke) && strokeWidth >= 0.9) {\n return 'cell';\n }\n\n if (isDarkColor(stroke)) {\n return 'checkbox';\n }\n\n return null;\n}\n","import * as cheerio from 'cheerio';\nimport type { Label } from './types.js';\n\n/** Max X-gap between words on same line to be joined (PDF points) */\nconst WORD_JOIN_GAP = 10;\n/** Y tolerance for considering words on the same line (PDF points) */\nconst LINE_Y_TOLERANCE = 2;\n\n/**\n * Parse pdftotext -bbox-layout HTML output and extract labels per page.\n * Returns labels with bounding boxes in PDF coordinate space.\n */\nexport function extractLabels(bboxHtml: string): Map<number, Label[]> {\n const $ = cheerio.load(bboxHtml, { xml: false });\n const pageLabels = new Map<number, Label[]>();\n\n $('page').each((pageIdx, pageEl) => {\n const pageNum = pageIdx + 1;\n const words: {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n }[] = [];\n\n $(pageEl)\n .find('word')\n .each((_, wordEl) => {\n const $w = $(wordEl);\n words.push({\n text: $w.text().trim(),\n xMin: parseFloat($w.attr('xmin') ?? '0'),\n yMin: parseFloat($w.attr('ymin') ?? '0'),\n xMax: parseFloat($w.attr('xmax') ?? '0'),\n yMax: parseFloat($w.attr('ymax') ?? '0'),\n });\n });\n\n const labels = assembleLabels(words, pageNum);\n pageLabels.set(pageNum, labels);\n });\n\n return pageLabels;\n}\n\ninterface Word {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\nfunction assembleLabels(words: Word[], page: number): Label[] {\n if (words.length === 0) return [];\n\n // Sort by Y then X\n const sorted = [...words].sort((a, b) => {\n const yDiff = a.yMin - b.yMin;\n if (Math.abs(yDiff) > LINE_Y_TOLERANCE) return yDiff;\n return a.xMin - b.xMin;\n });\n\n // Group words into lines by Y-proximity\n const lines: Word[][] = [[sorted[0]]];\n for (let i = 1; i < sorted.length; i++) {\n const prev = lines[lines.length - 1];\n const lastWord = prev[prev.length - 1];\n const curr = sorted[i];\n\n if (Math.abs(curr.yMin - lastWord.yMin) <= LINE_Y_TOLERANCE) {\n prev.push(curr);\n } else {\n lines.push([curr]);\n }\n }\n\n // Within each line, join adjacent words into label spans\n const labels: Label[] = [];\n for (const line of lines) {\n const sortedLine = line.sort((a, b) => a.xMin - b.xMin);\n let current: Word[] = [sortedLine[0]];\n\n for (let i = 1; i < sortedLine.length; i++) {\n const prev = current[current.length - 1];\n const curr = sortedLine[i];\n const gap = curr.xMin - prev.xMax;\n\n if (gap <= WORD_JOIN_GAP) {\n current.push(curr);\n } else {\n labels.push(wordsToLabel(current, page));\n current = [curr];\n }\n }\n labels.push(wordsToLabel(current, page));\n }\n\n return labels;\n}\n\nfunction wordsToLabel(words: Word[], page: number): Label {\n const text = words.map((w) => w.text).join(' ');\n let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;\n for (const w of words) {\n if (w.xMin < xMin) xMin = w.xMin;\n if (w.yMin < yMin) yMin = w.yMin;\n if (w.xMax > xMax) xMax = w.xMax;\n if (w.yMax > yMax) yMax = w.yMax;\n }\n return { text, xMin, yMin, xMax, yMax, page };\n}\n","import type { BoxRow, SvgBox } from './types.js';\n\n/** Tolerance for grouping boxes into the same Y-row (SVG units) */\nconst Y_TOLERANCE = 2;\n\n/** X-gap threshold: gaps larger than this split into separate fields.\n * Typical inter-cell gap within a field is ~3 SVG units; cross-field gaps are ≥15. */\nconst FIELD_GAP_THRESHOLD = 10;\n\nexport interface FieldGroup {\n boxes: SvgBox[];\n row: BoxRow;\n /** Number of character cells (= maxLength for comb fields) */\n boxCount: number;\n}\n\n/**\n * Group boxes by Y-proximity into rows, then split each row\n * into fields by X-gap. Returns FieldGroups sorted top-to-bottom,\n * left-to-right.\n */\nexport function groupBoxesIntoFields(\n boxes: SvgBox[],\n page: number,\n): FieldGroup[] {\n const rows = groupByY(boxes, page);\n const fields: FieldGroup[] = [];\n\n for (const row of rows) {\n const sorted = [...row.boxes].sort((a, b) => a.x - b.x);\n const splits = splitByXGap(sorted, row);\n fields.push(...splits);\n }\n\n // Sort by Y (top-to-bottom in SVG = ascending Y), then X\n fields.sort((a, b) => {\n const yDiff = a.boxes[0].y - b.boxes[0].y;\n if (Math.abs(yDiff) > Y_TOLERANCE) return yDiff;\n return a.boxes[0].x - b.boxes[0].x;\n });\n\n return fields;\n}\n\nfunction groupByY(boxes: SvgBox[], page: number): BoxRow[] {\n const sorted = [...boxes].sort((a, b) => a.y - b.y);\n const rows: BoxRow[] = [];\n\n for (const box of sorted) {\n const existingRow = rows.find(\n (r) => Math.abs(r.y - box.y) <= Y_TOLERANCE && r.type === box.type,\n );\n if (existingRow) {\n existingRow.boxes.push(box);\n // Update row Y to running average for stable clustering\n const avg =\n existingRow.boxes.reduce((sum, b) => sum + b.y, 0) /\n existingRow.boxes.length;\n existingRow.y = avg;\n } else {\n rows.push({ boxes: [box], y: box.y, type: box.type, page });\n }\n }\n\n return rows;\n}\n\nfunction splitByXGap(\n sortedBoxes: SvgBox[],\n row: BoxRow,\n): FieldGroup[] {\n if (sortedBoxes.length === 0) return [];\n\n const groups: SvgBox[][] = [[sortedBoxes[0]]];\n\n for (let i = 1; i < sortedBoxes.length; i++) {\n const prev = sortedBoxes[i - 1];\n const curr = sortedBoxes[i];\n const gap = curr.x - (prev.x + prev.width);\n\n if (gap > FIELD_GAP_THRESHOLD) {\n groups.push([curr]);\n } else {\n groups[groups.length - 1].push(curr);\n }\n }\n\n return groups.map((boxes) => ({\n boxes,\n row: { ...row, boxes },\n boxCount: boxes.length,\n }));\n}\n","import type { PdfRect, SvgBox, TransformMatrix } from './types.js';\n\n/**\n * Apply SVG affine transform matrix to a point.\n * Output is in SVG viewport coordinates (origin top-left, Y increases downward).\n */\nexport function svgPointToViewport(\n svgX: number,\n svgY: number,\n matrix: TransformMatrix,\n): { x: number; y: number } {\n return {\n x: matrix.a * svgX + matrix.c * svgY + matrix.e,\n y: matrix.b * svgX + matrix.d * svgY + matrix.f,\n };\n}\n\n/**\n * Transform an SVG box to a PDF rect.\n *\n * The SVG matrix converts content coords → SVG viewport coords (top-left origin, Y-down).\n * PDF uses bottom-left origin with Y-up. So after applying the matrix, we flip Y\n * using the page height: pdf_y = pageHeight - viewport_y.\n *\n * Returns (x, y) as the bottom-left corner with positive width/height (PDF convention).\n */\nexport function svgBoxToPdfRect(\n box: SvgBox,\n matrix: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const corner1 = svgPointToViewport(box.x, box.y, matrix);\n const corner2 = svgPointToViewport(\n box.x + box.width,\n box.y + box.height,\n matrix,\n );\n\n // In viewport coords, compute bounding box\n const vpLeft = Math.min(corner1.x, corner2.x);\n const vpTop = Math.min(corner1.y, corner2.y);\n const vpRight = Math.max(corner1.x, corner2.x);\n const vpBottom = Math.max(corner1.y, corner2.y);\n const width = vpRight - vpLeft;\n const height = vpBottom - vpTop;\n\n // Convert from viewport (top-left, Y-down) to PDF (bottom-left, Y-up)\n const pdfX = vpLeft;\n const pdfY = pageHeight - vpBottom; // bottom edge in PDF coords\n\n return { x: pdfX, y: pdfY, width, height };\n}\n\n// Keep backward-compatible alias\nexport const svgPointToPdf = svgPointToViewport;\n","import type { Field, FieldType, Label, PdfRect } from './types.js';\nimport type { FieldGroup } from './group-rows.js';\nimport { svgBoxToPdfRect } from './transform.js';\nimport type { TransformMatrix } from './types.js';\n\n/** Map internal box type to formly-compatible field type */\nfunction toFieldType(boxType: 'cell' | 'checkbox'): FieldType {\n return boxType === 'cell' ? 'input' : 'checkbox';\n}\n\n/** Max Y-distance (PDF pts) for a label to be considered \"above\" a field row */\nconst LABEL_Y_MAX_DISTANCE = 25;\n\n/** Convert pdftotext label coords (top-left origin, Y-down) to PDF coords (bottom-left, Y-up) */\nfunction labelToPdfCoords(label: Label, pageHeight: number): {\n xMin: number; xMax: number; yBottom: number; yTop: number;\n} {\n return {\n xMin: label.xMin,\n xMax: label.xMax,\n yBottom: pageHeight - label.yMax, // bottom edge in PDF\n yTop: pageHeight - label.yMin, // top edge in PDF\n };\n}\n\n/**\n * Map labels to field groups and produce Field definitions.\n * Labels are in pdftotext coords (viewport); boxes are in SVG content coords.\n * Both are converted to PDF coords (bottom-left, Y-up) before matching.\n */\nexport function mapLabelsToFields(\n fieldGroups: FieldGroup[],\n labels: Label[],\n transform: TransformMatrix,\n page: number,\n pageHeight: number,\n): Field[] {\n const fields: Field[] = [];\n const usedNames = new Set<string>();\n\n for (const group of fieldGroups) {\n const pdfRect = computeGroupPdfRect(group, transform, pageHeight);\n const bestLabel = findBestLabel(pdfRect, labels, pageHeight);\n const labelText = bestLabel?.text ?? '';\n\n const baseName = generateFieldName(labelText, group.row.type, page);\n const name = deduplicateName(baseName, usedNames);\n usedNames.add(name);\n\n const field: Field = {\n key: name,\n type: toFieldType(group.row.type),\n props: {\n label: labelText,\n page,\n pdfRect,\n ...(group.row.type === 'cell' ? { maxLength: group.boxCount } : {}),\n },\n };\n\n fields.push(field);\n }\n\n return fields;\n}\n\nfunction computeGroupPdfRect(\n group: FieldGroup,\n transform: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const rects = group.boxes.map((box) => svgBoxToPdfRect(box, transform, pageHeight));\n let x = Infinity, y = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const r of rects) {\n if (r.x < x) x = r.x;\n if (r.y < y) y = r.y;\n const rx = r.x + r.width;\n const ry = r.y + r.height;\n if (rx > maxX) maxX = rx;\n if (ry > maxY) maxY = ry;\n }\n return { x, y, width: maxX - x, height: maxY - y };\n}\n\nfunction findBestLabel(fieldRect: PdfRect, labels: Label[], pageHeight: number): Label | null {\n const fieldTop = fieldRect.y + fieldRect.height;\n const fieldLeft = fieldRect.x;\n const fieldRight = fieldRect.x + fieldRect.width;\n\n let bestLabel: Label | null = null;\n let bestScore = Infinity;\n\n for (const label of labels) {\n const pdfLabel = labelToPdfCoords(label, pageHeight);\n\n // Label should be above the field: label bottom > field top\n // yDistance > 0 means label is above the field (correct position)\n const yDistance = pdfLabel.yBottom - fieldTop;\n\n if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE) continue;\n\n // Check horizontal overlap or proximity\n const hasOverlap = pdfLabel.xMin < fieldRight && pdfLabel.xMax > fieldLeft;\n const isLeftOf =\n pdfLabel.xMax <= fieldLeft && fieldLeft - pdfLabel.xMax < 50;\n\n if (!hasOverlap && !isLeftOf) continue;\n\n // Score: prefer labels that are close vertically and overlap horizontally\n const score = yDistance + (hasOverlap ? 0 : 20);\n if (score < bestScore) {\n bestScore = score;\n bestLabel = label;\n }\n }\n\n return bestLabel;\n}\n\nfunction generateFieldName(\n labelText: string,\n _type: 'cell' | 'checkbox',\n page: number,\n): string {\n if (!labelText) {\n return `p${page}_field`;\n }\n\n const cleaned = labelText.replace(/\\s*:\\s*$/, '').trim();\n\n const camel = cleaned\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-zA-Z0-9\\s]/g, '')\n .trim()\n .split(/\\s+/)\n .map((word, i) =>\n i === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join('');\n\n if (!camel) {\n return `p${page}_field`;\n }\n\n return `p${page}_${camel}`;\n}\n\nfunction deduplicateName(baseName: string, used: Set<string>): string {\n if (!used.has(baseName)) return baseName;\n let counter = 2;\n while (used.has(`${baseName}_${counter}`)) counter++;\n return `${baseName}_${counter}`;\n}\n","import { PDFDocument, PDFName, StandardFonts } from 'pdf-lib';\nimport type { Field } from './types.js';\n\n/**\n * Remove background color and border from a form field's widget annotations\n * so the field overlay is fully transparent against the printed form.\n */\nfunction clearWidgetAppearance(acroField: { getWidgets(): { dict: any }[] }): void {\n for (const widget of acroField.getWidgets()) {\n const mk = widget.dict.lookup(PDFName.of('MK'));\n if (mk && typeof mk.delete === 'function') {\n mk.delete(PDFName.of('BG')); // background color\n mk.delete(PDFName.of('BC')); // border color\n }\n }\n}\n\n/**\n * Load the original PDF, inject AcroForm fields at computed positions,\n * and return the modified PDF bytes.\n */\nexport async function injectFields(\n pdfBytes: Uint8Array,\n fields: Field[],\n): Promise<Uint8Array> {\n const pdfDoc = await PDFDocument.load(pdfBytes);\n const form = pdfDoc.getForm();\n const font = await pdfDoc.embedFont(StandardFonts.Helvetica);\n\n const pages = pdfDoc.getPages();\n\n for (const field of fields) {\n const pageIndex = field.props.page - 1;\n if (pageIndex < 0 || pageIndex >= pages.length) continue;\n const page = pages[pageIndex];\n const { x, y, width, height } = field.props.pdfRect;\n\n if (field.type === 'input') {\n const textField = form.createTextField(field.key);\n textField.addToPage(page, { x, y, width, height, font, borderWidth: 0 });\n if (field.props.maxLength) {\n textField.setMaxLength(field.props.maxLength);\n textField.enableCombing();\n }\n textField.setFontSize(0); // auto-size\n clearWidgetAppearance(textField.acroField);\n textField.updateAppearances(font);\n } else {\n const checkbox = form.createCheckBox(field.key);\n checkbox.addToPage(page, { x, y, width, height, borderWidth: 0 });\n clearWidgetAppearance(checkbox.acroField);\n checkbox.updateAppearances();\n }\n }\n\n return pdfDoc.save();\n}\n"],"mappings":"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","names":["readFile","join","cheerio","join","readFile"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/poppler.ts","../src/extract-boxes.ts","../src/extract-labels.ts","../src/group-rows.ts","../src/transform.ts","../src/map-labels.ts","../src/inject-fields.ts","../src/extract-dot-lines.ts"],"sourcesContent":["import { program } from 'commander';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { checkPoppler, extractBbox, extractSvg, getPageCount } from './poppler.js';\nimport { extractBoxes } from './extract-boxes.js';\nimport { extractLabels } from './extract-labels.js';\nimport { groupBoxesIntoFields } from './group-rows.js';\nimport { mapLabelsToFields, generateFieldName, deduplicateName } from './map-labels.js';\nimport { injectFields } from './inject-fields.js';\nimport { extractDotLineFields } from './extract-dot-lines.js';\nimport type { Field, FieldOutput } from './types.js';\n\nexport async function convert(inputPath: string, outputPath?: string): Promise<{\n pdfOut: string;\n jsonOut: string;\n fields: Field[];\n}> {\n // 1. Check dependencies\n await checkPoppler();\n\n // 2. Resolve output paths\n const dir = dirname(inputPath);\n const base = basename(inputPath, '.pdf');\n const pdfOut = outputPath ?? join(dir, `${base}-fillable.pdf`);\n const jsonOut = join(dirname(pdfOut), `${basename(pdfOut, '.pdf')}.fields.json`);\n\n // 3. Get page count\n const pageCount = await getPageCount(inputPath);\n console.log(`Processing ${pageCount} page(s)...`);\n\n // 4. Extract bbox labels (all pages at once)\n const bboxHtml = await extractBbox(inputPath);\n const labelsByPage = extractLabels(bboxHtml);\n\n // 5. Process each page: SVG → boxes → groups → fields\n const allFields: Field[] = [];\n\n for (let page = 1; page <= pageCount; page++) {\n console.log(` Page ${page}: extracting geometry...`);\n\n const svgXml = await extractSvg(inputPath, page);\n const { boxes, transform, pageHeight } = extractBoxes(svgXml);\n\n const labels = labelsByPage.get(page) ?? [];\n\n // Detect dot-line free-text fields\n const usedNames = new Set<string>(allFields.map((f) => f.key));\n const { fields: dotFields, consumedLabelIndices } = extractDotLineFields(\n labels, page, pageHeight, usedNames,\n );\n\n // Filter consumed dot labels before passing to SVG-based pipeline\n const remainingLabels = labels.filter((_, i) => !consumedLabelIndices.has(i));\n\n if (!transform || boxes.length === 0) {\n if (dotFields.length === 0) {\n console.log(` Page ${page}: no input boxes found, skipping`);\n continue;\n }\n // Page has dot-line fields but no SVG boxes\n const freeTextCount = dotFields.length;\n console.log(` Page ${page}: ${freeTextCount} free-text field(s)`);\n allFields.push(...dotFields);\n continue;\n }\n\n const fieldGroups = groupBoxesIntoFields(boxes, page);\n // Add dot-line names to usedNames so SVG fields don't collide\n for (const f of dotFields) usedNames.add(f.key);\n const fields = mapLabelsToFields(fieldGroups, remainingLabels, transform, page, pageHeight, usedNames);\n\n const cellCount = fields.filter((f) => f.type === 'input').length;\n const checkboxCount = fields.filter((f) => f.type === 'checkbox').length;\n const freeTextCount = dotFields.length;\n console.log(` Page ${page}: ${cellCount} text fields, ${checkboxCount} checkboxes${freeTextCount ? `, ${freeTextCount} free-text field(s)` : ''}`);\n\n allFields.push(...fields, ...dotFields);\n }\n\n // 6. Inject AcroForm fields into PDF\n console.log('Injecting AcroForm fields...');\n const pdfBytes = new Uint8Array(await readFile(inputPath));\n const filledPdf = await injectFields(pdfBytes, allFields);\n await writeFile(pdfOut, filledPdf);\n\n // 7. Write JSON field definitions\n const output: FieldOutput = {\n pages: Array.from({ length: pageCount }, (_, i) => ({\n pageNumber: i + 1,\n fields: allFields.filter((f) => f.props.page === i + 1),\n })),\n };\n await writeFile(jsonOut, JSON.stringify(output, null, 2));\n\n console.log(`\\nDone!`);\n console.log(` PDF: ${pdfOut}`);\n console.log(` JSON: ${jsonOut}`);\n console.log(` Total fields: ${allFields.length}`);\n\n return { pdfOut, jsonOut, fields: allFields };\n}\n\nfunction main() {\n program\n .name('cerfaparse')\n .description('Convert flat CERFA PDFs into fillable AcroForm PDFs with field definitions')\n .version('0.1.0');\n\n program\n .command('convert')\n .description('Convert a flat CERFA PDF to a fillable AcroForm PDF + JSON field definitions')\n .argument('<input>', 'Path to the input CERFA PDF')\n .option('-o, --output <path>', 'Output PDF path')\n .action(async (input: string, opts: { output?: string }) => {\n try {\n await convert(input, opts.output);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n });\n\n if (process.argv.length <= 2) {\n program.help();\n }\n\n program.parse();\n}\n\n// Only run CLI when executed directly (not imported as library)\nconst isMainModule = process.argv[1] && (\n import.meta.url.endsWith(process.argv[1]) ||\n import.meta.url === `file://${process.argv[1]}`\n);\nif (isMainModule) {\n main();\n}\n","import { execa } from 'execa';\nimport { access, mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nexport async function checkPoppler(): Promise<void> {\n const tools = ['pdftocairo', 'pdftotext', 'pdfinfo'];\n const missing: string[] = [];\n\n for (const tool of tools) {\n try {\n await execa(tool, ['-v']);\n } catch {\n missing.push(tool);\n }\n }\n\n if (missing.length > 0) {\n throw new Error(\n `Poppler tools not found: ${missing.join(', ')}. Install with:\\n` +\n ' macOS: brew install poppler\\n' +\n ' Linux: apt install poppler-utils',\n );\n }\n}\n\nexport async function getPageCount(pdfPath: string): Promise<number> {\n await validateFileExists(pdfPath);\n const { stdout } = await execa('pdfinfo', [pdfPath]);\n const match = stdout.match(/^Pages:\\s+(\\d+)/m);\n if (!match) throw new Error('Could not determine page count from pdfinfo');\n return parseInt(match[1], 10);\n}\n\nexport async function extractSvg(\n pdfPath: string,\n page: number,\n): Promise<string> {\n const tmpDir = await mkdtemp(join(tmpdir(), 'cerfaparse-'));\n const outPath = join(tmpDir, `page-${page}.svg`);\n try {\n await execa('pdftocairo', [\n '-svg',\n '-f',\n String(page),\n '-l',\n String(page),\n pdfPath,\n outPath,\n ]);\n return await readFile(outPath, 'utf-8');\n } finally {\n try {\n await rm(tmpDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors — don't mask the original error\n }\n }\n}\n\nexport async function extractBbox(pdfPath: string): Promise<string> {\n const { stdout } = await execa('pdftotext', [\n '-bbox-layout',\n pdfPath,\n '-',\n ]);\n return stdout;\n}\n\nasync function validateFileExists(path: string): Promise<void> {\n try {\n await access(path);\n } catch {\n throw new Error(`File not found: ${path}`);\n }\n}\n","import * as cheerio from 'cheerio';\nimport type { SvgBox, TransformMatrix } from './types.js';\n\nconst WHITE_THRESHOLD = 0.95; // RGB components above 95% → white\nconst DARK_STROKE_THRESHOLD = 0.2; // RGB components below 20% → dark\n\n/** Size thresholds to filter out large background rects and thin slivers */\nconst MAX_BOX_WIDTH = 50;\nconst MAX_BOX_HEIGHT = 50;\nconst MIN_BOX_SIZE = 3;\n\nexport interface ExtractResult {\n boxes: SvgBox[];\n transform: TransformMatrix | null;\n pageHeight: number;\n}\n\nexport function extractBoxes(svgXml: string): ExtractResult {\n const $ = cheerio.load(svgXml, { xml: true });\n\n // Extract page height from SVG viewBox or height attribute\n let pageHeight = 0;\n const viewBox = $('svg').attr('viewBox') ?? '';\n const vbParts = viewBox.split(/\\s+/);\n if (vbParts.length >= 4) {\n pageHeight = parseFloat(vbParts[3]);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n // Fallback: parse height attribute (e.g. \"572pt\" → 572)\n const heightAttr = $('svg').attr('height') ?? '';\n pageHeight = parseFloat(heightAttr);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n throw new Error('Could not extract page height from SVG viewBox or height attribute');\n }\n\n let transform: TransformMatrix | null = null;\n const boxes: SvgBox[] = [];\n\n $('path').each((_, el) => {\n const path = $(el);\n const d = path.attr('d');\n const fill = path.attr('fill') ?? '';\n const stroke = path.attr('stroke') ?? '';\n const strokeWidth = parseFloat(path.attr('stroke-width') ?? '0');\n\n if (!d || !isWhiteColor(fill)) return;\n\n const rect = parseRectPath(d);\n if (!rect) return;\n if (rect.width > MAX_BOX_WIDTH || rect.height > MAX_BOX_HEIGHT) return;\n if (rect.width < MIN_BOX_SIZE || rect.height < MIN_BOX_SIZE) return;\n\n const type = classifyBox(stroke, strokeWidth);\n if (!type) return;\n\n // Extract and validate transform from each matching element\n const pathTransform = parseTransformFromAttr(path.attr('transform') ?? '');\n if (!pathTransform) return;\n\n // Compose with ancestor transforms (<g> parents, or <use> for paths in <defs>)\n const ancestorTransform = collectAncestorTransform($, el);\n const effectiveTransform = ancestorTransform\n ? composeTransforms(ancestorTransform, pathTransform)\n : pathTransform;\n\n if (!transform) {\n transform = effectiveTransform;\n } else if (!matricesEqual(transform, effectiveTransform)) {\n console.warn(\n 'Warning: found input box with different transform matrix — skipping. ' +\n 'This may indicate rotated sections.',\n );\n return;\n }\n\n boxes.push({ ...rect, type });\n });\n\n return { boxes, transform, pageHeight };\n}\n\n/**\n * Collect the composed ancestor transform for a path element.\n *\n * Walks up from the path to <svg>, collecting <g> transforms along the way.\n * If the path is inside <defs> (rendered indirectly via <use>), finds the\n * root <use> element's transform instead — in pdftocairo output, intermediate\n * group transforms within the <defs>/<use> chain cancel out, leaving only\n * the outermost <use> transform as the net effect.\n */\nfunction collectAncestorTransform(\n $: cheerio.CheerioAPI,\n el: any,\n): TransformMatrix | null {\n const transforms: TransformMatrix[] = [];\n let insideDefs = false;\n let current = (el as any).parent;\n\n while (current) {\n const tag = current.tagName ?? current.name ?? '';\n if (tag === 'svg') break;\n\n if (tag === 'defs') {\n insideDefs = true;\n break;\n }\n\n if (tag === 'g') {\n const t = $(current).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) transforms.unshift(parsed); // outermost first\n }\n\n current = current.parent;\n }\n\n if (insideDefs) {\n // Path lives in <defs> — rendered via <use> reference chain.\n // Find the outermost <use> (direct child of <svg>) with a transform.\n // Intermediate transforms within the defs/use chain cancel in pdftocairo output.\n const svgChildren = $('svg').children().toArray();\n for (const child of svgChildren) {\n const tag = (child as any).tagName ?? '';\n if (tag !== 'use') continue;\n const t = $(child).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) return parsed;\n }\n return null;\n }\n\n if (transforms.length === 0) return null;\n return transforms.reduce(composeTransforms);\n}\n\n/**\n * Parse a transform attribute — supports both matrix() and translate() syntax.\n */\nfunction parseTransformAttr(attr: string): TransformMatrix | null {\n // Try matrix() first\n const matrixMatch = attr.match(MATRIX_RE);\n if (matrixMatch) {\n const m: TransformMatrix = {\n a: parseFloat(matrixMatch[1]),\n b: parseFloat(matrixMatch[2]),\n c: parseFloat(matrixMatch[3]),\n d: parseFloat(matrixMatch[4]),\n e: parseFloat(matrixMatch[5]),\n f: parseFloat(matrixMatch[6]),\n };\n if (Object.values(m).some((v) => !Number.isFinite(v))) return null;\n return m;\n }\n\n // Try translate()\n const translateMatch = attr.match(\n /translate\\(\\s*([-\\d.]+)(?:\\s*,\\s*([-\\d.]+))?\\s*\\)/,\n );\n if (translateMatch) {\n const tx = parseFloat(translateMatch[1]);\n const ty = parseFloat(translateMatch[2] ?? '0');\n if (!Number.isFinite(tx) || !Number.isFinite(ty)) return null;\n return { a: 1, b: 0, c: 0, d: 1, e: tx, f: ty };\n }\n\n return null;\n}\n\n/**\n * Compose two 2D affine transforms: result = m1 * m2\n * Applies m2 first, then m1 (standard matrix multiplication order).\n */\nfunction composeTransforms(\n m1: TransformMatrix,\n m2: TransformMatrix,\n): TransformMatrix {\n return {\n a: m1.a * m2.a + m1.c * m2.b,\n b: m1.b * m2.a + m1.d * m2.b,\n c: m1.a * m2.c + m1.c * m2.d,\n d: m1.b * m2.c + m1.d * m2.d,\n e: m1.a * m2.e + m1.c * m2.f + m1.e,\n f: m1.b * m2.e + m1.d * m2.f + m1.f,\n };\n}\n\nconst MATRIX_RE =\n /matrix\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)/;\n\nfunction parseTransformFromAttr(attr: string): TransformMatrix | null {\n return parseTransformAttr(attr);\n}\n\nfunction matricesEqual(a: TransformMatrix, b: TransformMatrix): boolean {\n const tol = 0.001;\n return (\n Math.abs(a.a - b.a) < tol &&\n Math.abs(a.b - b.b) < tol &&\n Math.abs(a.c - b.c) < tol &&\n Math.abs(a.d - b.d) < tol &&\n Math.abs(a.e - b.e) < tol &&\n Math.abs(a.f - b.f) < tol\n );\n}\n\n/**\n * Parse a rectangular path from 4 corner points, regardless of winding order.\n * Computes bounding box from all points.\n */\nfunction parseRectPath(\n d: string,\n): { x: number; y: number; width: number; height: number } | null {\n const commands = d.trim().split(/\\s*([MLHVCSQTAZ])\\s*/i).filter(Boolean);\n\n const points: [number, number][] = [];\n let i = 0;\n while (i < commands.length) {\n const cmd = commands[i];\n if (cmd === 'M' || cmd === 'L') {\n const coords = commands[i + 1]?.trim().split(/\\s+/);\n if (coords && coords.length >= 2) {\n const x = parseFloat(coords[0]);\n const y = parseFloat(coords[1]);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n points.push([x, y]);\n }\n i += 2;\n } else if (cmd === 'Z') {\n i += 1;\n } else {\n return null;\n }\n }\n\n if (points.length < 4) return null;\n\n // Use first 4 points to compute bounding box (handles any winding order)\n const xs = points.slice(0, 4).map((p) => p[0]);\n const ys = points.slice(0, 4).map((p) => p[1]);\n const minX = Math.min(...xs);\n const maxX = Math.max(...xs);\n const minY = Math.min(...ys);\n const maxY = Math.max(...ys);\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n if (width < 1 || height < 1) return null;\n\n return { x: minX, y: minY, width, height };\n}\n\n/** Parse an rgb(r%, g%, b%) color string and return [0-1] components, or null */\nfunction parseRgbPercent(color: string): [number, number, number] | null {\n const match = color.match(\n /rgb\\(\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*\\)/,\n );\n if (!match) return null;\n return [\n parseFloat(match[1]) / 100,\n parseFloat(match[2]) / 100,\n parseFloat(match[3]) / 100,\n ];\n}\n\nfunction isWhiteColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] >= WHITE_THRESHOLD && rgb[1] >= WHITE_THRESHOLD && rgb[2] >= WHITE_THRESHOLD;\n}\n\nfunction isDarkColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] < DARK_STROKE_THRESHOLD && rgb[1] < DARK_STROKE_THRESHOLD && rgb[2] < DARK_STROKE_THRESHOLD;\n}\n\nfunction classifyBox(\n stroke: string,\n strokeWidth: number,\n): 'cell' | 'checkbox' | null {\n if (isWhiteColor(stroke) && strokeWidth >= 0.9) {\n return 'cell';\n }\n\n if (isDarkColor(stroke)) {\n return 'checkbox';\n }\n\n return null;\n}\n","import * as cheerio from 'cheerio';\nimport type { Label } from './types.js';\n\n/** Max X-gap between words on same line to be joined (PDF points) */\nconst WORD_JOIN_GAP = 10;\n/** Y tolerance for considering words on the same line (PDF points) */\nconst LINE_Y_TOLERANCE = 2;\n\n/**\n * Parse pdftotext -bbox-layout HTML output and extract labels per page.\n * Returns labels with bounding boxes in PDF coordinate space.\n */\nexport function extractLabels(bboxHtml: string): Map<number, Label[]> {\n const $ = cheerio.load(bboxHtml, { xml: false });\n const pageLabels = new Map<number, Label[]>();\n\n $('page').each((pageIdx, pageEl) => {\n const pageNum = pageIdx + 1;\n const words: {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n }[] = [];\n\n $(pageEl)\n .find('word')\n .each((_, wordEl) => {\n const $w = $(wordEl);\n words.push({\n text: $w.text().trim(),\n xMin: parseFloat($w.attr('xmin') ?? '0'),\n yMin: parseFloat($w.attr('ymin') ?? '0'),\n xMax: parseFloat($w.attr('xmax') ?? '0'),\n yMax: parseFloat($w.attr('ymax') ?? '0'),\n });\n });\n\n const labels = assembleLabels(words, pageNum);\n pageLabels.set(pageNum, labels);\n });\n\n return pageLabels;\n}\n\ninterface Word {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\nfunction assembleLabels(words: Word[], page: number): Label[] {\n if (words.length === 0) return [];\n\n // Sort by Y then X\n const sorted = [...words].sort((a, b) => {\n const yDiff = a.yMin - b.yMin;\n if (Math.abs(yDiff) > LINE_Y_TOLERANCE) return yDiff;\n return a.xMin - b.xMin;\n });\n\n // Group words into lines by Y-proximity\n const lines: Word[][] = [[sorted[0]]];\n for (let i = 1; i < sorted.length; i++) {\n const prev = lines[lines.length - 1];\n const lastWord = prev[prev.length - 1];\n const curr = sorted[i];\n\n if (Math.abs(curr.yMin - lastWord.yMin) <= LINE_Y_TOLERANCE) {\n prev.push(curr);\n } else {\n lines.push([curr]);\n }\n }\n\n // Within each line, join adjacent words into label spans\n const labels: Label[] = [];\n for (const line of lines) {\n const sortedLine = line.sort((a, b) => a.xMin - b.xMin);\n let current: Word[] = [sortedLine[0]];\n\n for (let i = 1; i < sortedLine.length; i++) {\n const prev = current[current.length - 1];\n const curr = sortedLine[i];\n const gap = curr.xMin - prev.xMax;\n\n if (gap <= WORD_JOIN_GAP) {\n current.push(curr);\n } else {\n labels.push(wordsToLabel(current, page));\n current = [curr];\n }\n }\n labels.push(wordsToLabel(current, page));\n }\n\n return labels;\n}\n\nfunction wordsToLabel(words: Word[], page: number): Label {\n const text = words.map((w) => w.text).join(' ');\n let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;\n for (const w of words) {\n if (w.xMin < xMin) xMin = w.xMin;\n if (w.yMin < yMin) yMin = w.yMin;\n if (w.xMax > xMax) xMax = w.xMax;\n if (w.yMax > yMax) yMax = w.yMax;\n }\n return { text, xMin, yMin, xMax, yMax, page };\n}\n","import type { BoxRow, SvgBox } from './types.js';\n\n/** Tolerance for grouping boxes into the same Y-row (SVG units) */\nconst Y_TOLERANCE = 2;\n\n/** X-gap threshold: gaps larger than this split into separate fields.\n * Typical inter-cell gap within a field is ~3 SVG units; cross-field gaps are ≥15. */\nconst FIELD_GAP_THRESHOLD = 10;\n\nexport interface FieldGroup {\n boxes: SvgBox[];\n row: BoxRow;\n /** Number of character cells (= maxLength for comb fields) */\n boxCount: number;\n}\n\n/**\n * Group boxes by Y-proximity into rows, then split each row\n * into fields by X-gap. Returns FieldGroups sorted top-to-bottom,\n * left-to-right.\n */\nexport function groupBoxesIntoFields(\n boxes: SvgBox[],\n page: number,\n): FieldGroup[] {\n const rows = groupByY(boxes, page);\n const fields: FieldGroup[] = [];\n\n for (const row of rows) {\n const sorted = [...row.boxes].sort((a, b) => a.x - b.x);\n const splits = splitByXGap(sorted, row);\n fields.push(...splits);\n }\n\n // Sort by Y (top-to-bottom in SVG = ascending Y), then X\n fields.sort((a, b) => {\n const yDiff = a.boxes[0].y - b.boxes[0].y;\n if (Math.abs(yDiff) > Y_TOLERANCE) return yDiff;\n return a.boxes[0].x - b.boxes[0].x;\n });\n\n return fields;\n}\n\nfunction groupByY(boxes: SvgBox[], page: number): BoxRow[] {\n const sorted = [...boxes].sort((a, b) => a.y - b.y);\n const rows: BoxRow[] = [];\n\n for (const box of sorted) {\n const existingRow = rows.find(\n (r) => Math.abs(r.y - box.y) <= Y_TOLERANCE && r.type === box.type,\n );\n if (existingRow) {\n existingRow.boxes.push(box);\n // Update row Y to running average for stable clustering\n const avg =\n existingRow.boxes.reduce((sum, b) => sum + b.y, 0) /\n existingRow.boxes.length;\n existingRow.y = avg;\n } else {\n rows.push({ boxes: [box], y: box.y, type: box.type, page });\n }\n }\n\n return rows;\n}\n\nfunction splitByXGap(\n sortedBoxes: SvgBox[],\n row: BoxRow,\n): FieldGroup[] {\n if (sortedBoxes.length === 0) return [];\n\n const groups: SvgBox[][] = [[sortedBoxes[0]]];\n\n for (let i = 1; i < sortedBoxes.length; i++) {\n const prev = sortedBoxes[i - 1];\n const curr = sortedBoxes[i];\n const gap = curr.x - (prev.x + prev.width);\n\n if (gap > FIELD_GAP_THRESHOLD) {\n groups.push([curr]);\n } else {\n groups[groups.length - 1].push(curr);\n }\n }\n\n return groups.map((boxes) => ({\n boxes,\n row: { ...row, boxes },\n boxCount: boxes.length,\n }));\n}\n","import type { PdfRect, SvgBox, TransformMatrix } from './types.js';\n\n/**\n * Apply SVG affine transform matrix to a point.\n * Output is in SVG viewport coordinates (origin top-left, Y increases downward).\n */\nexport function svgPointToViewport(\n svgX: number,\n svgY: number,\n matrix: TransformMatrix,\n): { x: number; y: number } {\n return {\n x: matrix.a * svgX + matrix.c * svgY + matrix.e,\n y: matrix.b * svgX + matrix.d * svgY + matrix.f,\n };\n}\n\n/**\n * Transform an SVG box to a PDF rect.\n *\n * The SVG matrix converts content coords → SVG viewport coords (top-left origin, Y-down).\n * PDF uses bottom-left origin with Y-up. So after applying the matrix, we flip Y\n * using the page height: pdf_y = pageHeight - viewport_y.\n *\n * Returns (x, y) as the bottom-left corner with positive width/height (PDF convention).\n */\nexport function svgBoxToPdfRect(\n box: SvgBox,\n matrix: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const corner1 = svgPointToViewport(box.x, box.y, matrix);\n const corner2 = svgPointToViewport(\n box.x + box.width,\n box.y + box.height,\n matrix,\n );\n\n // In viewport coords, compute bounding box\n const vpLeft = Math.min(corner1.x, corner2.x);\n const vpTop = Math.min(corner1.y, corner2.y);\n const vpRight = Math.max(corner1.x, corner2.x);\n const vpBottom = Math.max(corner1.y, corner2.y);\n const width = vpRight - vpLeft;\n const height = vpBottom - vpTop;\n\n // Convert from viewport (top-left, Y-down) to PDF (bottom-left, Y-up)\n const pdfX = vpLeft;\n const pdfY = pageHeight - vpBottom; // bottom edge in PDF coords\n\n return { x: pdfX, y: pdfY, width, height };\n}\n\n// Keep backward-compatible alias\nexport const svgPointToPdf = svgPointToViewport;\n","import type { Field, FieldType, Label, PdfRect } from './types.js';\nimport type { FieldGroup } from './group-rows.js';\nimport { svgBoxToPdfRect } from './transform.js';\nimport type { TransformMatrix } from './types.js';\n\n/** Map internal box type to formly-compatible field type */\nfunction toFieldType(boxType: 'cell' | 'checkbox'): FieldType {\n return boxType === 'cell' ? 'input' : 'checkbox';\n}\n\n/** Max Y-distance (PDF pts) for a label to be considered \"above\" a field row */\nconst LABEL_Y_MAX_DISTANCE = 25;\n\n/** Convert pdftotext label coords (top-left origin, Y-down) to PDF coords (bottom-left, Y-up) */\nfunction labelToPdfCoords(label: Label, pageHeight: number): {\n xMin: number; xMax: number; yBottom: number; yTop: number;\n} {\n return {\n xMin: label.xMin,\n xMax: label.xMax,\n yBottom: pageHeight - label.yMax, // bottom edge in PDF\n yTop: pageHeight - label.yMin, // top edge in PDF\n };\n}\n\n/**\n * Map labels to field groups and produce Field definitions.\n * Labels are in pdftotext coords (viewport); boxes are in SVG content coords.\n * Both are converted to PDF coords (bottom-left, Y-up) before matching.\n */\nexport function mapLabelsToFields(\n fieldGroups: FieldGroup[],\n labels: Label[],\n transform: TransformMatrix,\n page: number,\n pageHeight: number,\n usedNames?: Set<string>,\n): Field[] {\n const fields: Field[] = [];\n const names = usedNames ?? new Set<string>();\n\n for (const group of fieldGroups) {\n const pdfRect = computeGroupPdfRect(group, transform, pageHeight);\n const bestLabel = findBestLabel(pdfRect, labels, pageHeight);\n const labelText = bestLabel?.text ?? '';\n\n const baseName = generateFieldName(labelText, group.row.type, page);\n const name = deduplicateName(baseName, names);\n names.add(name);\n\n const field: Field = {\n key: name,\n type: toFieldType(group.row.type),\n props: {\n label: labelText,\n page,\n pdfRect,\n ...(group.row.type === 'cell' ? { maxLength: group.boxCount } : {}),\n },\n };\n\n fields.push(field);\n }\n\n return fields;\n}\n\nfunction computeGroupPdfRect(\n group: FieldGroup,\n transform: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const rects = group.boxes.map((box) => svgBoxToPdfRect(box, transform, pageHeight));\n let x = Infinity, y = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const r of rects) {\n if (r.x < x) x = r.x;\n if (r.y < y) y = r.y;\n const rx = r.x + r.width;\n const ry = r.y + r.height;\n if (rx > maxX) maxX = rx;\n if (ry > maxY) maxY = ry;\n }\n return { x, y, width: maxX - x, height: maxY - y };\n}\n\nfunction findBestLabel(fieldRect: PdfRect, labels: Label[], pageHeight: number): Label | null {\n const fieldTop = fieldRect.y + fieldRect.height;\n const fieldLeft = fieldRect.x;\n const fieldRight = fieldRect.x + fieldRect.width;\n\n let bestLabel: Label | null = null;\n let bestScore = Infinity;\n\n for (const label of labels) {\n const pdfLabel = labelToPdfCoords(label, pageHeight);\n\n // Label should be above the field: label bottom > field top\n // yDistance > 0 means label is above the field (correct position)\n const yDistance = pdfLabel.yBottom - fieldTop;\n\n if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE) continue;\n\n // Check horizontal overlap or proximity\n const hasOverlap = pdfLabel.xMin < fieldRight && pdfLabel.xMax > fieldLeft;\n const isLeftOf =\n pdfLabel.xMax <= fieldLeft && fieldLeft - pdfLabel.xMax < 50;\n\n if (!hasOverlap && !isLeftOf) continue;\n\n // Score: prefer labels that are close vertically and overlap horizontally\n const score = yDistance + (hasOverlap ? 0 : 20);\n if (score < bestScore) {\n bestScore = score;\n bestLabel = label;\n }\n }\n\n return bestLabel;\n}\n\nexport function generateFieldName(\n labelText: string,\n _type: 'cell' | 'checkbox',\n page: number,\n): string {\n if (!labelText) {\n return `p${page}_field`;\n }\n\n const cleaned = labelText.replace(/\\s*:\\s*$/, '').trim();\n\n const camel = cleaned\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-zA-Z0-9\\s]/g, '')\n .trim()\n .split(/\\s+/)\n .map((word, i) =>\n i === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join('');\n\n if (!camel) {\n return `p${page}_field`;\n }\n\n return `p${page}_${camel}`;\n}\n\nexport function deduplicateName(baseName: string, used: Set<string>): string {\n if (!used.has(baseName)) return baseName;\n let counter = 2;\n while (used.has(`${baseName}_${counter}`)) counter++;\n return `${baseName}_${counter}`;\n}\n","import { PDFDocument, PDFName, StandardFonts } from 'pdf-lib';\nimport type { Field } from './types.js';\n\n/**\n * Remove background color and border from a form field's widget annotations\n * so the field overlay is fully transparent against the printed form.\n */\nfunction clearWidgetAppearance(acroField: { getWidgets(): { dict: any }[] }): void {\n for (const widget of acroField.getWidgets()) {\n const mk = widget.dict.lookup(PDFName.of('MK'));\n if (mk && typeof mk.delete === 'function') {\n mk.delete(PDFName.of('BG')); // background color\n mk.delete(PDFName.of('BC')); // border color\n }\n }\n}\n\n/**\n * Load the original PDF, inject AcroForm fields at computed positions,\n * and return the modified PDF bytes.\n */\nexport async function injectFields(\n pdfBytes: Uint8Array,\n fields: Field[],\n): Promise<Uint8Array> {\n const pdfDoc = await PDFDocument.load(pdfBytes);\n const form = pdfDoc.getForm();\n const font = await pdfDoc.embedFont(StandardFonts.Helvetica);\n\n const pages = pdfDoc.getPages();\n\n for (const field of fields) {\n const pageIndex = field.props.page - 1;\n if (pageIndex < 0 || pageIndex >= pages.length) continue;\n const page = pages[pageIndex];\n const { x, y, width, height } = field.props.pdfRect;\n\n if (field.type === 'input') {\n const textField = form.createTextField(field.key);\n textField.addToPage(page, { x, y, width, height, font, borderWidth: 0 });\n if (field.props.maxLength) {\n textField.setMaxLength(field.props.maxLength);\n textField.enableCombing();\n }\n if (field.props.multiline) {\n textField.enableMultiline();\n }\n textField.setFontSize(0); // auto-size\n clearWidgetAppearance(textField.acroField);\n textField.updateAppearances(font);\n } else {\n const checkbox = form.createCheckBox(field.key);\n checkbox.addToPage(page, { x, y, width, height, borderWidth: 0 });\n clearWidgetAppearance(checkbox.acroField);\n checkbox.updateAppearances();\n }\n }\n\n return pdfDoc.save();\n}\n","import type { Field, Label, PdfRect } from './types.js';\nimport { generateFieldName, deduplicateName } from './map-labels.js';\n\n/** Minimum number of period characters for a label to be considered a dot line */\nconst MIN_DOT_COUNT = 10;\n\n/** Max Y-gap (pdftotext coords, Y-down) between consecutive dot lines to group them */\nconst DOT_GROUP_Y_GAP = 20;\n\n/** Max Y-distance (pdftotext coords) for a text label above a dot group */\nconst LABEL_Y_MAX_DISTANCE = 30;\n\nexport interface DotLineResult {\n fields: Field[];\n consumedLabelIndices: Set<number>;\n}\n\n/**\n * Returns true if the label text consists only of dots (periods) and whitespace,\n * with at least MIN_DOT_COUNT periods. Excludes short dot sequences like \"...\" or\n * labels mixing text with dots.\n */\nexport function isDotLine(text: string): boolean {\n if (!/^[.\\s\\u2026]+$/.test(text)) return false;\n const dotCount = (text.match(/\\./g) ?? []).length;\n return dotCount >= MIN_DOT_COUNT;\n}\n\n/**\n * Detect free-text dot-line fields from pdftotext labels.\n *\n * Labels are in pdftotext coords (top-left origin, Y-down).\n * Output PdfRects are in PDF coords (bottom-left origin, Y-up).\n */\nexport function extractDotLineFields(\n labels: Label[],\n page: number,\n pageHeight: number,\n usedNames?: Set<string>,\n): DotLineResult {\n const names = usedNames ?? new Set<string>();\n const consumed = new Set<number>();\n const fields: Field[] = [];\n\n // 1. Identify dot-line label indices\n const dotIndices: number[] = [];\n for (let i = 0; i < labels.length; i++) {\n if (isDotLine(labels[i].text)) {\n dotIndices.push(i);\n }\n }\n\n if (dotIndices.length === 0) return { fields: [], consumedLabelIndices: consumed };\n\n // 2. Group consecutive dot lines by Y-proximity\n const groups: number[][] = [[dotIndices[0]]];\n for (let i = 1; i < dotIndices.length; i++) {\n const prevIdx = dotIndices[i - 1];\n const currIdx = dotIndices[i];\n const prevLabel = labels[prevIdx];\n const currLabel = labels[currIdx];\n\n if (Math.abs(currLabel.yMin - prevLabel.yMin) <= DOT_GROUP_Y_GAP) {\n groups[groups.length - 1].push(currIdx);\n } else {\n groups.push([currIdx]);\n }\n }\n\n // 3. For each group, compute bounding rect and find text label\n for (const group of groups) {\n // Mark consumed\n for (const idx of group) consumed.add(idx);\n\n // Compute bounding box in pdftotext coords (Y-down)\n let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;\n for (const idx of group) {\n const lbl = labels[idx];\n if (lbl.xMin < xMin) xMin = lbl.xMin;\n if (lbl.yMin < yMin) yMin = lbl.yMin;\n if (lbl.xMax > xMax) xMax = lbl.xMax;\n if (lbl.yMax > yMax) yMax = lbl.yMax;\n }\n\n // Convert to PDF coords (bottom-left, Y-up)\n const pdfRect: PdfRect = {\n x: xMin,\n y: pageHeight - yMax,\n width: xMax - xMin,\n height: yMax - yMin,\n };\n\n // Find best text label above the dot group (in pdftotext coords, \"above\" = smaller Y)\n const groupTopY = yMin; // top of group in pdftotext Y-down coords\n const bestLabel = findTextLabelAbove(labels, groupTopY, xMin, xMax, consumed);\n\n const labelText = bestLabel?.text ?? '';\n const isMultiline = group.length > 1;\n\n const baseName = generateFieldName(labelText, 'cell', page);\n const name = deduplicateName(baseName, names);\n names.add(name);\n\n fields.push({\n key: name,\n type: 'input',\n props: {\n label: labelText,\n page,\n pdfRect,\n ...(isMultiline ? { multiline: true } : {}),\n },\n });\n }\n\n return { fields, consumedLabelIndices: consumed };\n}\n\n/**\n * Find the closest text label above a dot group in pdftotext coords (Y-down).\n * \"Above\" means smaller Y value. Skips consumed (dot-line) labels.\n */\nfunction findTextLabelAbove(\n labels: Label[],\n groupTopY: number,\n groupXMin: number,\n groupXMax: number,\n consumed: Set<number>,\n): Label | null {\n let best: Label | null = null;\n let bestDistance = Infinity;\n\n for (let i = 0; i < labels.length; i++) {\n if (consumed.has(i)) continue;\n const lbl = labels[i];\n\n // Label must be above (smaller yMax in Y-down coords)\n const yDistance = groupTopY - lbl.yMax;\n if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE) continue;\n\n // Check horizontal overlap or proximity\n const hasOverlap = lbl.xMin < groupXMax && lbl.xMax > groupXMin;\n const isLeftOf = lbl.xMax <= groupXMin && groupXMin - lbl.xMax < 50;\n if (!hasOverlap && !isLeftOf) continue;\n\n const score = yDistance + (hasOverlap ? 0 : 20);\n if (score < bestDistance) {\n bestDistance = score;\n best = lbl;\n }\n }\n\n return best;\n}\n"],"mappings":"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","names":["readFile","join","cheerio","LABEL_Y_MAX_DISTANCE","join","freeTextCount","readFile"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ interface Field {
|
|
|
43
43
|
props: {
|
|
44
44
|
label: string;
|
|
45
45
|
maxLength?: number;
|
|
46
|
+
multiline?: boolean;
|
|
46
47
|
page: number;
|
|
47
48
|
pdfRect: PdfRect;
|
|
48
49
|
};
|
|
@@ -98,7 +99,27 @@ declare function groupBoxesIntoFields(boxes: SvgBox[], page: number): FieldGroup
|
|
|
98
99
|
* Labels are in pdftotext coords (viewport); boxes are in SVG content coords.
|
|
99
100
|
* Both are converted to PDF coords (bottom-left, Y-up) before matching.
|
|
100
101
|
*/
|
|
101
|
-
declare function mapLabelsToFields(fieldGroups: FieldGroup[], labels: Label[], transform: TransformMatrix, page: number, pageHeight: number): Field[];
|
|
102
|
+
declare function mapLabelsToFields(fieldGroups: FieldGroup[], labels: Label[], transform: TransformMatrix, page: number, pageHeight: number, usedNames?: Set<string>): Field[];
|
|
103
|
+
declare function generateFieldName(labelText: string, _type: 'cell' | 'checkbox', page: number): string;
|
|
104
|
+
declare function deduplicateName(baseName: string, used: Set<string>): string;
|
|
105
|
+
|
|
106
|
+
interface DotLineResult {
|
|
107
|
+
fields: Field[];
|
|
108
|
+
consumedLabelIndices: Set<number>;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns true if the label text consists only of dots (periods) and whitespace,
|
|
112
|
+
* with at least MIN_DOT_COUNT periods. Excludes short dot sequences like "..." or
|
|
113
|
+
* labels mixing text with dots.
|
|
114
|
+
*/
|
|
115
|
+
declare function isDotLine(text: string): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Detect free-text dot-line fields from pdftotext labels.
|
|
118
|
+
*
|
|
119
|
+
* Labels are in pdftotext coords (top-left origin, Y-down).
|
|
120
|
+
* Output PdfRects are in PDF coords (bottom-left origin, Y-up).
|
|
121
|
+
*/
|
|
122
|
+
declare function extractDotLineFields(labels: Label[], page: number, pageHeight: number, usedNames?: Set<string>): DotLineResult;
|
|
102
123
|
|
|
103
124
|
/**
|
|
104
125
|
* Apply SVG affine transform matrix to a point.
|
|
@@ -131,4 +152,4 @@ declare function getPageCount(pdfPath: string): Promise<number>;
|
|
|
131
152
|
declare function extractSvg(pdfPath: string, page: number): Promise<string>;
|
|
132
153
|
declare function extractBbox(pdfPath: string): Promise<string>;
|
|
133
154
|
|
|
134
|
-
export { type BoxRow, type BoxType, type ExtractResult, type Field, type FieldGroup, type FieldOutput, type FieldType, type Label, type PageData, type PdfRect, type SvgBox, type TransformMatrix, checkPoppler, convert, extractBbox, extractBoxes, extractLabels, extractSvg, getPageCount, groupBoxesIntoFields, injectFields, mapLabelsToFields, svgBoxToPdfRect, svgPointToPdf };
|
|
155
|
+
export { type BoxRow, type BoxType, type DotLineResult, type ExtractResult, type Field, type FieldGroup, type FieldOutput, type FieldType, type Label, type PageData, type PdfRect, type SvgBox, type TransformMatrix, checkPoppler, convert, deduplicateName, extractBbox, extractBoxes, extractDotLineFields, extractLabels, extractSvg, generateFieldName, getPageCount, groupBoxesIntoFields, injectFields, isDotLine, mapLabelsToFields, svgBoxToPdfRect, svgPointToPdf };
|
package/dist/index.js
CHANGED
|
@@ -432,16 +432,16 @@ function labelToPdfCoords(label, pageHeight) {
|
|
|
432
432
|
// top edge in PDF
|
|
433
433
|
};
|
|
434
434
|
}
|
|
435
|
-
function mapLabelsToFields(fieldGroups, labels, transform, page, pageHeight) {
|
|
435
|
+
function mapLabelsToFields(fieldGroups, labels, transform, page, pageHeight, usedNames) {
|
|
436
436
|
const fields = [];
|
|
437
|
-
const
|
|
437
|
+
const names = usedNames ?? /* @__PURE__ */ new Set();
|
|
438
438
|
for (const group of fieldGroups) {
|
|
439
439
|
const pdfRect = computeGroupPdfRect(group, transform, pageHeight);
|
|
440
440
|
const bestLabel = findBestLabel(pdfRect, labels, pageHeight);
|
|
441
441
|
const labelText = bestLabel?.text ?? "";
|
|
442
442
|
const baseName = generateFieldName(labelText, group.row.type, page);
|
|
443
|
-
const name = deduplicateName(baseName,
|
|
444
|
-
|
|
443
|
+
const name = deduplicateName(baseName, names);
|
|
444
|
+
names.add(name);
|
|
445
445
|
const field = {
|
|
446
446
|
key: name,
|
|
447
447
|
type: toFieldType(group.row.type),
|
|
@@ -538,6 +538,9 @@ async function injectFields(pdfBytes, fields) {
|
|
|
538
538
|
textField.setMaxLength(field.props.maxLength);
|
|
539
539
|
textField.enableCombing();
|
|
540
540
|
}
|
|
541
|
+
if (field.props.multiline) {
|
|
542
|
+
textField.enableMultiline();
|
|
543
|
+
}
|
|
541
544
|
textField.setFontSize(0);
|
|
542
545
|
clearWidgetAppearance(textField.acroField);
|
|
543
546
|
textField.updateAppearances(font);
|
|
@@ -551,6 +554,94 @@ async function injectFields(pdfBytes, fields) {
|
|
|
551
554
|
return pdfDoc.save();
|
|
552
555
|
}
|
|
553
556
|
|
|
557
|
+
// src/extract-dot-lines.ts
|
|
558
|
+
var MIN_DOT_COUNT = 10;
|
|
559
|
+
var DOT_GROUP_Y_GAP = 20;
|
|
560
|
+
var LABEL_Y_MAX_DISTANCE2 = 30;
|
|
561
|
+
function isDotLine(text) {
|
|
562
|
+
if (!/^[.\s\u2026]+$/.test(text)) return false;
|
|
563
|
+
const dotCount = (text.match(/\./g) ?? []).length;
|
|
564
|
+
return dotCount >= MIN_DOT_COUNT;
|
|
565
|
+
}
|
|
566
|
+
function extractDotLineFields(labels, page, pageHeight, usedNames) {
|
|
567
|
+
const names = usedNames ?? /* @__PURE__ */ new Set();
|
|
568
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
569
|
+
const fields = [];
|
|
570
|
+
const dotIndices = [];
|
|
571
|
+
for (let i = 0; i < labels.length; i++) {
|
|
572
|
+
if (isDotLine(labels[i].text)) {
|
|
573
|
+
dotIndices.push(i);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (dotIndices.length === 0) return { fields: [], consumedLabelIndices: consumed };
|
|
577
|
+
const groups = [[dotIndices[0]]];
|
|
578
|
+
for (let i = 1; i < dotIndices.length; i++) {
|
|
579
|
+
const prevIdx = dotIndices[i - 1];
|
|
580
|
+
const currIdx = dotIndices[i];
|
|
581
|
+
const prevLabel = labels[prevIdx];
|
|
582
|
+
const currLabel = labels[currIdx];
|
|
583
|
+
if (Math.abs(currLabel.yMin - prevLabel.yMin) <= DOT_GROUP_Y_GAP) {
|
|
584
|
+
groups[groups.length - 1].push(currIdx);
|
|
585
|
+
} else {
|
|
586
|
+
groups.push([currIdx]);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
for (const group of groups) {
|
|
590
|
+
for (const idx of group) consumed.add(idx);
|
|
591
|
+
let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
|
|
592
|
+
for (const idx of group) {
|
|
593
|
+
const lbl = labels[idx];
|
|
594
|
+
if (lbl.xMin < xMin) xMin = lbl.xMin;
|
|
595
|
+
if (lbl.yMin < yMin) yMin = lbl.yMin;
|
|
596
|
+
if (lbl.xMax > xMax) xMax = lbl.xMax;
|
|
597
|
+
if (lbl.yMax > yMax) yMax = lbl.yMax;
|
|
598
|
+
}
|
|
599
|
+
const pdfRect = {
|
|
600
|
+
x: xMin,
|
|
601
|
+
y: pageHeight - yMax,
|
|
602
|
+
width: xMax - xMin,
|
|
603
|
+
height: yMax - yMin
|
|
604
|
+
};
|
|
605
|
+
const groupTopY = yMin;
|
|
606
|
+
const bestLabel = findTextLabelAbove(labels, groupTopY, xMin, xMax, consumed);
|
|
607
|
+
const labelText = bestLabel?.text ?? "";
|
|
608
|
+
const isMultiline = group.length > 1;
|
|
609
|
+
const baseName = generateFieldName(labelText, "cell", page);
|
|
610
|
+
const name = deduplicateName(baseName, names);
|
|
611
|
+
names.add(name);
|
|
612
|
+
fields.push({
|
|
613
|
+
key: name,
|
|
614
|
+
type: "input",
|
|
615
|
+
props: {
|
|
616
|
+
label: labelText,
|
|
617
|
+
page,
|
|
618
|
+
pdfRect,
|
|
619
|
+
...isMultiline ? { multiline: true } : {}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
return { fields, consumedLabelIndices: consumed };
|
|
624
|
+
}
|
|
625
|
+
function findTextLabelAbove(labels, groupTopY, groupXMin, groupXMax, consumed) {
|
|
626
|
+
let best = null;
|
|
627
|
+
let bestDistance = Infinity;
|
|
628
|
+
for (let i = 0; i < labels.length; i++) {
|
|
629
|
+
if (consumed.has(i)) continue;
|
|
630
|
+
const lbl = labels[i];
|
|
631
|
+
const yDistance = groupTopY - lbl.yMax;
|
|
632
|
+
if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE2) continue;
|
|
633
|
+
const hasOverlap = lbl.xMin < groupXMax && lbl.xMax > groupXMin;
|
|
634
|
+
const isLeftOf = lbl.xMax <= groupXMin && groupXMin - lbl.xMax < 50;
|
|
635
|
+
if (!hasOverlap && !isLeftOf) continue;
|
|
636
|
+
const score = yDistance + (hasOverlap ? 0 : 20);
|
|
637
|
+
if (score < bestDistance) {
|
|
638
|
+
bestDistance = score;
|
|
639
|
+
best = lbl;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return best;
|
|
643
|
+
}
|
|
644
|
+
|
|
554
645
|
// src/cli.ts
|
|
555
646
|
async function convert(inputPath, outputPath) {
|
|
556
647
|
await checkPoppler();
|
|
@@ -567,17 +658,33 @@ async function convert(inputPath, outputPath) {
|
|
|
567
658
|
console.log(` Page ${page}: extracting geometry...`);
|
|
568
659
|
const svgXml = await extractSvg(inputPath, page);
|
|
569
660
|
const { boxes, transform, pageHeight } = extractBoxes(svgXml);
|
|
661
|
+
const labels = labelsByPage.get(page) ?? [];
|
|
662
|
+
const usedNames = new Set(allFields.map((f) => f.key));
|
|
663
|
+
const { fields: dotFields, consumedLabelIndices } = extractDotLineFields(
|
|
664
|
+
labels,
|
|
665
|
+
page,
|
|
666
|
+
pageHeight,
|
|
667
|
+
usedNames
|
|
668
|
+
);
|
|
669
|
+
const remainingLabels = labels.filter((_, i) => !consumedLabelIndices.has(i));
|
|
570
670
|
if (!transform || boxes.length === 0) {
|
|
571
|
-
|
|
671
|
+
if (dotFields.length === 0) {
|
|
672
|
+
console.log(` Page ${page}: no input boxes found, skipping`);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const freeTextCount2 = dotFields.length;
|
|
676
|
+
console.log(` Page ${page}: ${freeTextCount2} free-text field(s)`);
|
|
677
|
+
allFields.push(...dotFields);
|
|
572
678
|
continue;
|
|
573
679
|
}
|
|
574
680
|
const fieldGroups = groupBoxesIntoFields(boxes, page);
|
|
575
|
-
const
|
|
576
|
-
const fields = mapLabelsToFields(fieldGroups,
|
|
681
|
+
for (const f of dotFields) usedNames.add(f.key);
|
|
682
|
+
const fields = mapLabelsToFields(fieldGroups, remainingLabels, transform, page, pageHeight, usedNames);
|
|
577
683
|
const cellCount = fields.filter((f) => f.type === "input").length;
|
|
578
684
|
const checkboxCount = fields.filter((f) => f.type === "checkbox").length;
|
|
579
|
-
|
|
580
|
-
|
|
685
|
+
const freeTextCount = dotFields.length;
|
|
686
|
+
console.log(` Page ${page}: ${cellCount} text fields, ${checkboxCount} checkboxes${freeTextCount ? `, ${freeTextCount} free-text field(s)` : ""}`);
|
|
687
|
+
allFields.push(...fields, ...dotFields);
|
|
581
688
|
}
|
|
582
689
|
console.log("Injecting AcroForm fields...");
|
|
583
690
|
const pdfBytes = new Uint8Array(await readFile2(inputPath));
|
|
@@ -619,13 +726,17 @@ if (isMainModule) {
|
|
|
619
726
|
export {
|
|
620
727
|
checkPoppler,
|
|
621
728
|
convert,
|
|
729
|
+
deduplicateName,
|
|
622
730
|
extractBbox,
|
|
623
731
|
extractBoxes,
|
|
732
|
+
extractDotLineFields,
|
|
624
733
|
extractLabels,
|
|
625
734
|
extractSvg,
|
|
735
|
+
generateFieldName,
|
|
626
736
|
getPageCount,
|
|
627
737
|
groupBoxesIntoFields,
|
|
628
738
|
injectFields,
|
|
739
|
+
isDotLine,
|
|
629
740
|
mapLabelsToFields,
|
|
630
741
|
svgBoxToPdfRect,
|
|
631
742
|
svgPointToPdf
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/poppler.ts","../src/extract-boxes.ts","../src/extract-labels.ts","../src/group-rows.ts","../src/transform.ts","../src/map-labels.ts","../src/inject-fields.ts"],"sourcesContent":["import { program } from 'commander';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { checkPoppler, extractBbox, extractSvg, getPageCount } from './poppler.js';\nimport { extractBoxes } from './extract-boxes.js';\nimport { extractLabels } from './extract-labels.js';\nimport { groupBoxesIntoFields } from './group-rows.js';\nimport { mapLabelsToFields } from './map-labels.js';\nimport { injectFields } from './inject-fields.js';\nimport type { Field, FieldOutput } from './types.js';\n\nexport async function convert(inputPath: string, outputPath?: string): Promise<{\n pdfOut: string;\n jsonOut: string;\n fields: Field[];\n}> {\n // 1. Check dependencies\n await checkPoppler();\n\n // 2. Resolve output paths\n const dir = dirname(inputPath);\n const base = basename(inputPath, '.pdf');\n const pdfOut = outputPath ?? join(dir, `${base}-fillable.pdf`);\n const jsonOut = join(dirname(pdfOut), `${basename(pdfOut, '.pdf')}.fields.json`);\n\n // 3. Get page count\n const pageCount = await getPageCount(inputPath);\n console.log(`Processing ${pageCount} page(s)...`);\n\n // 4. Extract bbox labels (all pages at once)\n const bboxHtml = await extractBbox(inputPath);\n const labelsByPage = extractLabels(bboxHtml);\n\n // 5. Process each page: SVG → boxes → groups → fields\n const allFields: Field[] = [];\n\n for (let page = 1; page <= pageCount; page++) {\n console.log(` Page ${page}: extracting geometry...`);\n\n const svgXml = await extractSvg(inputPath, page);\n const { boxes, transform, pageHeight } = extractBoxes(svgXml);\n\n if (!transform || boxes.length === 0) {\n console.log(` Page ${page}: no input boxes found, skipping`);\n continue;\n }\n\n const fieldGroups = groupBoxesIntoFields(boxes, page);\n const labels = labelsByPage.get(page) ?? [];\n const fields = mapLabelsToFields(fieldGroups, labels, transform, page, pageHeight);\n\n const cellCount = fields.filter((f) => f.type === 'input').length;\n const checkboxCount = fields.filter((f) => f.type === 'checkbox').length;\n console.log(` Page ${page}: ${cellCount} text fields, ${checkboxCount} checkboxes`);\n\n allFields.push(...fields);\n }\n\n // 6. Inject AcroForm fields into PDF\n console.log('Injecting AcroForm fields...');\n const pdfBytes = new Uint8Array(await readFile(inputPath));\n const filledPdf = await injectFields(pdfBytes, allFields);\n await writeFile(pdfOut, filledPdf);\n\n // 7. Write JSON field definitions\n const output: FieldOutput = {\n pages: Array.from({ length: pageCount }, (_, i) => ({\n pageNumber: i + 1,\n fields: allFields.filter((f) => f.props.page === i + 1),\n })),\n };\n await writeFile(jsonOut, JSON.stringify(output, null, 2));\n\n console.log(`\\nDone!`);\n console.log(` PDF: ${pdfOut}`);\n console.log(` JSON: ${jsonOut}`);\n console.log(` Total fields: ${allFields.length}`);\n\n return { pdfOut, jsonOut, fields: allFields };\n}\n\nfunction main() {\n program\n .name('cerfaparse')\n .description('Convert flat CERFA PDFs into fillable AcroForm PDFs with field definitions')\n .version('0.1.0');\n\n program\n .command('convert')\n .description('Convert a flat CERFA PDF to a fillable AcroForm PDF + JSON field definitions')\n .argument('<input>', 'Path to the input CERFA PDF')\n .option('-o, --output <path>', 'Output PDF path')\n .action(async (input: string, opts: { output?: string }) => {\n try {\n await convert(input, opts.output);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n });\n\n if (process.argv.length <= 2) {\n program.help();\n }\n\n program.parse();\n}\n\n// Only run CLI when executed directly (not imported as library)\nconst isMainModule = process.argv[1] && (\n import.meta.url.endsWith(process.argv[1]) ||\n import.meta.url === `file://${process.argv[1]}`\n);\nif (isMainModule) {\n main();\n}\n","import { execa } from 'execa';\nimport { access, mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nexport async function checkPoppler(): Promise<void> {\n const tools = ['pdftocairo', 'pdftotext', 'pdfinfo'];\n const missing: string[] = [];\n\n for (const tool of tools) {\n try {\n await execa(tool, ['-v']);\n } catch {\n missing.push(tool);\n }\n }\n\n if (missing.length > 0) {\n throw new Error(\n `Poppler tools not found: ${missing.join(', ')}. Install with:\\n` +\n ' macOS: brew install poppler\\n' +\n ' Linux: apt install poppler-utils',\n );\n }\n}\n\nexport async function getPageCount(pdfPath: string): Promise<number> {\n await validateFileExists(pdfPath);\n const { stdout } = await execa('pdfinfo', [pdfPath]);\n const match = stdout.match(/^Pages:\\s+(\\d+)/m);\n if (!match) throw new Error('Could not determine page count from pdfinfo');\n return parseInt(match[1], 10);\n}\n\nexport async function extractSvg(\n pdfPath: string,\n page: number,\n): Promise<string> {\n const tmpDir = await mkdtemp(join(tmpdir(), 'cerfaparse-'));\n const outPath = join(tmpDir, `page-${page}.svg`);\n try {\n await execa('pdftocairo', [\n '-svg',\n '-f',\n String(page),\n '-l',\n String(page),\n pdfPath,\n outPath,\n ]);\n return await readFile(outPath, 'utf-8');\n } finally {\n try {\n await rm(tmpDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors — don't mask the original error\n }\n }\n}\n\nexport async function extractBbox(pdfPath: string): Promise<string> {\n const { stdout } = await execa('pdftotext', [\n '-bbox-layout',\n pdfPath,\n '-',\n ]);\n return stdout;\n}\n\nasync function validateFileExists(path: string): Promise<void> {\n try {\n await access(path);\n } catch {\n throw new Error(`File not found: ${path}`);\n }\n}\n","import * as cheerio from 'cheerio';\nimport type { SvgBox, TransformMatrix } from './types.js';\n\nconst WHITE_THRESHOLD = 0.95; // RGB components above 95% → white\nconst DARK_STROKE_THRESHOLD = 0.2; // RGB components below 20% → dark\n\n/** Size thresholds to filter out large background rects and thin slivers */\nconst MAX_BOX_WIDTH = 50;\nconst MAX_BOX_HEIGHT = 50;\nconst MIN_BOX_SIZE = 3;\n\nexport interface ExtractResult {\n boxes: SvgBox[];\n transform: TransformMatrix | null;\n pageHeight: number;\n}\n\nexport function extractBoxes(svgXml: string): ExtractResult {\n const $ = cheerio.load(svgXml, { xml: true });\n\n // Extract page height from SVG viewBox or height attribute\n let pageHeight = 0;\n const viewBox = $('svg').attr('viewBox') ?? '';\n const vbParts = viewBox.split(/\\s+/);\n if (vbParts.length >= 4) {\n pageHeight = parseFloat(vbParts[3]);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n // Fallback: parse height attribute (e.g. \"572pt\" → 572)\n const heightAttr = $('svg').attr('height') ?? '';\n pageHeight = parseFloat(heightAttr);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n throw new Error('Could not extract page height from SVG viewBox or height attribute');\n }\n\n let transform: TransformMatrix | null = null;\n const boxes: SvgBox[] = [];\n\n $('path').each((_, el) => {\n const path = $(el);\n const d = path.attr('d');\n const fill = path.attr('fill') ?? '';\n const stroke = path.attr('stroke') ?? '';\n const strokeWidth = parseFloat(path.attr('stroke-width') ?? '0');\n\n if (!d || !isWhiteColor(fill)) return;\n\n const rect = parseRectPath(d);\n if (!rect) return;\n if (rect.width > MAX_BOX_WIDTH || rect.height > MAX_BOX_HEIGHT) return;\n if (rect.width < MIN_BOX_SIZE || rect.height < MIN_BOX_SIZE) return;\n\n const type = classifyBox(stroke, strokeWidth);\n if (!type) return;\n\n // Extract and validate transform from each matching element\n const pathTransform = parseTransformFromAttr(path.attr('transform') ?? '');\n if (!pathTransform) return;\n\n // Compose with ancestor transforms (<g> parents, or <use> for paths in <defs>)\n const ancestorTransform = collectAncestorTransform($, el);\n const effectiveTransform = ancestorTransform\n ? composeTransforms(ancestorTransform, pathTransform)\n : pathTransform;\n\n if (!transform) {\n transform = effectiveTransform;\n } else if (!matricesEqual(transform, effectiveTransform)) {\n console.warn(\n 'Warning: found input box with different transform matrix — skipping. ' +\n 'This may indicate rotated sections.',\n );\n return;\n }\n\n boxes.push({ ...rect, type });\n });\n\n return { boxes, transform, pageHeight };\n}\n\n/**\n * Collect the composed ancestor transform for a path element.\n *\n * Walks up from the path to <svg>, collecting <g> transforms along the way.\n * If the path is inside <defs> (rendered indirectly via <use>), finds the\n * root <use> element's transform instead — in pdftocairo output, intermediate\n * group transforms within the <defs>/<use> chain cancel out, leaving only\n * the outermost <use> transform as the net effect.\n */\nfunction collectAncestorTransform(\n $: cheerio.CheerioAPI,\n el: any,\n): TransformMatrix | null {\n const transforms: TransformMatrix[] = [];\n let insideDefs = false;\n let current = (el as any).parent;\n\n while (current) {\n const tag = current.tagName ?? current.name ?? '';\n if (tag === 'svg') break;\n\n if (tag === 'defs') {\n insideDefs = true;\n break;\n }\n\n if (tag === 'g') {\n const t = $(current).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) transforms.unshift(parsed); // outermost first\n }\n\n current = current.parent;\n }\n\n if (insideDefs) {\n // Path lives in <defs> — rendered via <use> reference chain.\n // Find the outermost <use> (direct child of <svg>) with a transform.\n // Intermediate transforms within the defs/use chain cancel in pdftocairo output.\n const svgChildren = $('svg').children().toArray();\n for (const child of svgChildren) {\n const tag = (child as any).tagName ?? '';\n if (tag !== 'use') continue;\n const t = $(child).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) return parsed;\n }\n return null;\n }\n\n if (transforms.length === 0) return null;\n return transforms.reduce(composeTransforms);\n}\n\n/**\n * Parse a transform attribute — supports both matrix() and translate() syntax.\n */\nfunction parseTransformAttr(attr: string): TransformMatrix | null {\n // Try matrix() first\n const matrixMatch = attr.match(MATRIX_RE);\n if (matrixMatch) {\n const m: TransformMatrix = {\n a: parseFloat(matrixMatch[1]),\n b: parseFloat(matrixMatch[2]),\n c: parseFloat(matrixMatch[3]),\n d: parseFloat(matrixMatch[4]),\n e: parseFloat(matrixMatch[5]),\n f: parseFloat(matrixMatch[6]),\n };\n if (Object.values(m).some((v) => !Number.isFinite(v))) return null;\n return m;\n }\n\n // Try translate()\n const translateMatch = attr.match(\n /translate\\(\\s*([-\\d.]+)(?:\\s*,\\s*([-\\d.]+))?\\s*\\)/,\n );\n if (translateMatch) {\n const tx = parseFloat(translateMatch[1]);\n const ty = parseFloat(translateMatch[2] ?? '0');\n if (!Number.isFinite(tx) || !Number.isFinite(ty)) return null;\n return { a: 1, b: 0, c: 0, d: 1, e: tx, f: ty };\n }\n\n return null;\n}\n\n/**\n * Compose two 2D affine transforms: result = m1 * m2\n * Applies m2 first, then m1 (standard matrix multiplication order).\n */\nfunction composeTransforms(\n m1: TransformMatrix,\n m2: TransformMatrix,\n): TransformMatrix {\n return {\n a: m1.a * m2.a + m1.c * m2.b,\n b: m1.b * m2.a + m1.d * m2.b,\n c: m1.a * m2.c + m1.c * m2.d,\n d: m1.b * m2.c + m1.d * m2.d,\n e: m1.a * m2.e + m1.c * m2.f + m1.e,\n f: m1.b * m2.e + m1.d * m2.f + m1.f,\n };\n}\n\nconst MATRIX_RE =\n /matrix\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)/;\n\nfunction parseTransformFromAttr(attr: string): TransformMatrix | null {\n return parseTransformAttr(attr);\n}\n\nfunction matricesEqual(a: TransformMatrix, b: TransformMatrix): boolean {\n const tol = 0.001;\n return (\n Math.abs(a.a - b.a) < tol &&\n Math.abs(a.b - b.b) < tol &&\n Math.abs(a.c - b.c) < tol &&\n Math.abs(a.d - b.d) < tol &&\n Math.abs(a.e - b.e) < tol &&\n Math.abs(a.f - b.f) < tol\n );\n}\n\n/**\n * Parse a rectangular path from 4 corner points, regardless of winding order.\n * Computes bounding box from all points.\n */\nfunction parseRectPath(\n d: string,\n): { x: number; y: number; width: number; height: number } | null {\n const commands = d.trim().split(/\\s*([MLHVCSQTAZ])\\s*/i).filter(Boolean);\n\n const points: [number, number][] = [];\n let i = 0;\n while (i < commands.length) {\n const cmd = commands[i];\n if (cmd === 'M' || cmd === 'L') {\n const coords = commands[i + 1]?.trim().split(/\\s+/);\n if (coords && coords.length >= 2) {\n const x = parseFloat(coords[0]);\n const y = parseFloat(coords[1]);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n points.push([x, y]);\n }\n i += 2;\n } else if (cmd === 'Z') {\n i += 1;\n } else {\n return null;\n }\n }\n\n if (points.length < 4) return null;\n\n // Use first 4 points to compute bounding box (handles any winding order)\n const xs = points.slice(0, 4).map((p) => p[0]);\n const ys = points.slice(0, 4).map((p) => p[1]);\n const minX = Math.min(...xs);\n const maxX = Math.max(...xs);\n const minY = Math.min(...ys);\n const maxY = Math.max(...ys);\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n if (width < 1 || height < 1) return null;\n\n return { x: minX, y: minY, width, height };\n}\n\n/** Parse an rgb(r%, g%, b%) color string and return [0-1] components, or null */\nfunction parseRgbPercent(color: string): [number, number, number] | null {\n const match = color.match(\n /rgb\\(\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*\\)/,\n );\n if (!match) return null;\n return [\n parseFloat(match[1]) / 100,\n parseFloat(match[2]) / 100,\n parseFloat(match[3]) / 100,\n ];\n}\n\nfunction isWhiteColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] >= WHITE_THRESHOLD && rgb[1] >= WHITE_THRESHOLD && rgb[2] >= WHITE_THRESHOLD;\n}\n\nfunction isDarkColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] < DARK_STROKE_THRESHOLD && rgb[1] < DARK_STROKE_THRESHOLD && rgb[2] < DARK_STROKE_THRESHOLD;\n}\n\nfunction classifyBox(\n stroke: string,\n strokeWidth: number,\n): 'cell' | 'checkbox' | null {\n if (isWhiteColor(stroke) && strokeWidth >= 0.9) {\n return 'cell';\n }\n\n if (isDarkColor(stroke)) {\n return 'checkbox';\n }\n\n return null;\n}\n","import * as cheerio from 'cheerio';\nimport type { Label } from './types.js';\n\n/** Max X-gap between words on same line to be joined (PDF points) */\nconst WORD_JOIN_GAP = 10;\n/** Y tolerance for considering words on the same line (PDF points) */\nconst LINE_Y_TOLERANCE = 2;\n\n/**\n * Parse pdftotext -bbox-layout HTML output and extract labels per page.\n * Returns labels with bounding boxes in PDF coordinate space.\n */\nexport function extractLabels(bboxHtml: string): Map<number, Label[]> {\n const $ = cheerio.load(bboxHtml, { xml: false });\n const pageLabels = new Map<number, Label[]>();\n\n $('page').each((pageIdx, pageEl) => {\n const pageNum = pageIdx + 1;\n const words: {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n }[] = [];\n\n $(pageEl)\n .find('word')\n .each((_, wordEl) => {\n const $w = $(wordEl);\n words.push({\n text: $w.text().trim(),\n xMin: parseFloat($w.attr('xmin') ?? '0'),\n yMin: parseFloat($w.attr('ymin') ?? '0'),\n xMax: parseFloat($w.attr('xmax') ?? '0'),\n yMax: parseFloat($w.attr('ymax') ?? '0'),\n });\n });\n\n const labels = assembleLabels(words, pageNum);\n pageLabels.set(pageNum, labels);\n });\n\n return pageLabels;\n}\n\ninterface Word {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\nfunction assembleLabels(words: Word[], page: number): Label[] {\n if (words.length === 0) return [];\n\n // Sort by Y then X\n const sorted = [...words].sort((a, b) => {\n const yDiff = a.yMin - b.yMin;\n if (Math.abs(yDiff) > LINE_Y_TOLERANCE) return yDiff;\n return a.xMin - b.xMin;\n });\n\n // Group words into lines by Y-proximity\n const lines: Word[][] = [[sorted[0]]];\n for (let i = 1; i < sorted.length; i++) {\n const prev = lines[lines.length - 1];\n const lastWord = prev[prev.length - 1];\n const curr = sorted[i];\n\n if (Math.abs(curr.yMin - lastWord.yMin) <= LINE_Y_TOLERANCE) {\n prev.push(curr);\n } else {\n lines.push([curr]);\n }\n }\n\n // Within each line, join adjacent words into label spans\n const labels: Label[] = [];\n for (const line of lines) {\n const sortedLine = line.sort((a, b) => a.xMin - b.xMin);\n let current: Word[] = [sortedLine[0]];\n\n for (let i = 1; i < sortedLine.length; i++) {\n const prev = current[current.length - 1];\n const curr = sortedLine[i];\n const gap = curr.xMin - prev.xMax;\n\n if (gap <= WORD_JOIN_GAP) {\n current.push(curr);\n } else {\n labels.push(wordsToLabel(current, page));\n current = [curr];\n }\n }\n labels.push(wordsToLabel(current, page));\n }\n\n return labels;\n}\n\nfunction wordsToLabel(words: Word[], page: number): Label {\n const text = words.map((w) => w.text).join(' ');\n let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;\n for (const w of words) {\n if (w.xMin < xMin) xMin = w.xMin;\n if (w.yMin < yMin) yMin = w.yMin;\n if (w.xMax > xMax) xMax = w.xMax;\n if (w.yMax > yMax) yMax = w.yMax;\n }\n return { text, xMin, yMin, xMax, yMax, page };\n}\n","import type { BoxRow, SvgBox } from './types.js';\n\n/** Tolerance for grouping boxes into the same Y-row (SVG units) */\nconst Y_TOLERANCE = 2;\n\n/** X-gap threshold: gaps larger than this split into separate fields.\n * Typical inter-cell gap within a field is ~3 SVG units; cross-field gaps are ≥15. */\nconst FIELD_GAP_THRESHOLD = 10;\n\nexport interface FieldGroup {\n boxes: SvgBox[];\n row: BoxRow;\n /** Number of character cells (= maxLength for comb fields) */\n boxCount: number;\n}\n\n/**\n * Group boxes by Y-proximity into rows, then split each row\n * into fields by X-gap. Returns FieldGroups sorted top-to-bottom,\n * left-to-right.\n */\nexport function groupBoxesIntoFields(\n boxes: SvgBox[],\n page: number,\n): FieldGroup[] {\n const rows = groupByY(boxes, page);\n const fields: FieldGroup[] = [];\n\n for (const row of rows) {\n const sorted = [...row.boxes].sort((a, b) => a.x - b.x);\n const splits = splitByXGap(sorted, row);\n fields.push(...splits);\n }\n\n // Sort by Y (top-to-bottom in SVG = ascending Y), then X\n fields.sort((a, b) => {\n const yDiff = a.boxes[0].y - b.boxes[0].y;\n if (Math.abs(yDiff) > Y_TOLERANCE) return yDiff;\n return a.boxes[0].x - b.boxes[0].x;\n });\n\n return fields;\n}\n\nfunction groupByY(boxes: SvgBox[], page: number): BoxRow[] {\n const sorted = [...boxes].sort((a, b) => a.y - b.y);\n const rows: BoxRow[] = [];\n\n for (const box of sorted) {\n const existingRow = rows.find(\n (r) => Math.abs(r.y - box.y) <= Y_TOLERANCE && r.type === box.type,\n );\n if (existingRow) {\n existingRow.boxes.push(box);\n // Update row Y to running average for stable clustering\n const avg =\n existingRow.boxes.reduce((sum, b) => sum + b.y, 0) /\n existingRow.boxes.length;\n existingRow.y = avg;\n } else {\n rows.push({ boxes: [box], y: box.y, type: box.type, page });\n }\n }\n\n return rows;\n}\n\nfunction splitByXGap(\n sortedBoxes: SvgBox[],\n row: BoxRow,\n): FieldGroup[] {\n if (sortedBoxes.length === 0) return [];\n\n const groups: SvgBox[][] = [[sortedBoxes[0]]];\n\n for (let i = 1; i < sortedBoxes.length; i++) {\n const prev = sortedBoxes[i - 1];\n const curr = sortedBoxes[i];\n const gap = curr.x - (prev.x + prev.width);\n\n if (gap > FIELD_GAP_THRESHOLD) {\n groups.push([curr]);\n } else {\n groups[groups.length - 1].push(curr);\n }\n }\n\n return groups.map((boxes) => ({\n boxes,\n row: { ...row, boxes },\n boxCount: boxes.length,\n }));\n}\n","import type { PdfRect, SvgBox, TransformMatrix } from './types.js';\n\n/**\n * Apply SVG affine transform matrix to a point.\n * Output is in SVG viewport coordinates (origin top-left, Y increases downward).\n */\nexport function svgPointToViewport(\n svgX: number,\n svgY: number,\n matrix: TransformMatrix,\n): { x: number; y: number } {\n return {\n x: matrix.a * svgX + matrix.c * svgY + matrix.e,\n y: matrix.b * svgX + matrix.d * svgY + matrix.f,\n };\n}\n\n/**\n * Transform an SVG box to a PDF rect.\n *\n * The SVG matrix converts content coords → SVG viewport coords (top-left origin, Y-down).\n * PDF uses bottom-left origin with Y-up. So after applying the matrix, we flip Y\n * using the page height: pdf_y = pageHeight - viewport_y.\n *\n * Returns (x, y) as the bottom-left corner with positive width/height (PDF convention).\n */\nexport function svgBoxToPdfRect(\n box: SvgBox,\n matrix: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const corner1 = svgPointToViewport(box.x, box.y, matrix);\n const corner2 = svgPointToViewport(\n box.x + box.width,\n box.y + box.height,\n matrix,\n );\n\n // In viewport coords, compute bounding box\n const vpLeft = Math.min(corner1.x, corner2.x);\n const vpTop = Math.min(corner1.y, corner2.y);\n const vpRight = Math.max(corner1.x, corner2.x);\n const vpBottom = Math.max(corner1.y, corner2.y);\n const width = vpRight - vpLeft;\n const height = vpBottom - vpTop;\n\n // Convert from viewport (top-left, Y-down) to PDF (bottom-left, Y-up)\n const pdfX = vpLeft;\n const pdfY = pageHeight - vpBottom; // bottom edge in PDF coords\n\n return { x: pdfX, y: pdfY, width, height };\n}\n\n// Keep backward-compatible alias\nexport const svgPointToPdf = svgPointToViewport;\n","import type { Field, FieldType, Label, PdfRect } from './types.js';\nimport type { FieldGroup } from './group-rows.js';\nimport { svgBoxToPdfRect } from './transform.js';\nimport type { TransformMatrix } from './types.js';\n\n/** Map internal box type to formly-compatible field type */\nfunction toFieldType(boxType: 'cell' | 'checkbox'): FieldType {\n return boxType === 'cell' ? 'input' : 'checkbox';\n}\n\n/** Max Y-distance (PDF pts) for a label to be considered \"above\" a field row */\nconst LABEL_Y_MAX_DISTANCE = 25;\n\n/** Convert pdftotext label coords (top-left origin, Y-down) to PDF coords (bottom-left, Y-up) */\nfunction labelToPdfCoords(label: Label, pageHeight: number): {\n xMin: number; xMax: number; yBottom: number; yTop: number;\n} {\n return {\n xMin: label.xMin,\n xMax: label.xMax,\n yBottom: pageHeight - label.yMax, // bottom edge in PDF\n yTop: pageHeight - label.yMin, // top edge in PDF\n };\n}\n\n/**\n * Map labels to field groups and produce Field definitions.\n * Labels are in pdftotext coords (viewport); boxes are in SVG content coords.\n * Both are converted to PDF coords (bottom-left, Y-up) before matching.\n */\nexport function mapLabelsToFields(\n fieldGroups: FieldGroup[],\n labels: Label[],\n transform: TransformMatrix,\n page: number,\n pageHeight: number,\n): Field[] {\n const fields: Field[] = [];\n const usedNames = new Set<string>();\n\n for (const group of fieldGroups) {\n const pdfRect = computeGroupPdfRect(group, transform, pageHeight);\n const bestLabel = findBestLabel(pdfRect, labels, pageHeight);\n const labelText = bestLabel?.text ?? '';\n\n const baseName = generateFieldName(labelText, group.row.type, page);\n const name = deduplicateName(baseName, usedNames);\n usedNames.add(name);\n\n const field: Field = {\n key: name,\n type: toFieldType(group.row.type),\n props: {\n label: labelText,\n page,\n pdfRect,\n ...(group.row.type === 'cell' ? { maxLength: group.boxCount } : {}),\n },\n };\n\n fields.push(field);\n }\n\n return fields;\n}\n\nfunction computeGroupPdfRect(\n group: FieldGroup,\n transform: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const rects = group.boxes.map((box) => svgBoxToPdfRect(box, transform, pageHeight));\n let x = Infinity, y = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const r of rects) {\n if (r.x < x) x = r.x;\n if (r.y < y) y = r.y;\n const rx = r.x + r.width;\n const ry = r.y + r.height;\n if (rx > maxX) maxX = rx;\n if (ry > maxY) maxY = ry;\n }\n return { x, y, width: maxX - x, height: maxY - y };\n}\n\nfunction findBestLabel(fieldRect: PdfRect, labels: Label[], pageHeight: number): Label | null {\n const fieldTop = fieldRect.y + fieldRect.height;\n const fieldLeft = fieldRect.x;\n const fieldRight = fieldRect.x + fieldRect.width;\n\n let bestLabel: Label | null = null;\n let bestScore = Infinity;\n\n for (const label of labels) {\n const pdfLabel = labelToPdfCoords(label, pageHeight);\n\n // Label should be above the field: label bottom > field top\n // yDistance > 0 means label is above the field (correct position)\n const yDistance = pdfLabel.yBottom - fieldTop;\n\n if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE) continue;\n\n // Check horizontal overlap or proximity\n const hasOverlap = pdfLabel.xMin < fieldRight && pdfLabel.xMax > fieldLeft;\n const isLeftOf =\n pdfLabel.xMax <= fieldLeft && fieldLeft - pdfLabel.xMax < 50;\n\n if (!hasOverlap && !isLeftOf) continue;\n\n // Score: prefer labels that are close vertically and overlap horizontally\n const score = yDistance + (hasOverlap ? 0 : 20);\n if (score < bestScore) {\n bestScore = score;\n bestLabel = label;\n }\n }\n\n return bestLabel;\n}\n\nfunction generateFieldName(\n labelText: string,\n _type: 'cell' | 'checkbox',\n page: number,\n): string {\n if (!labelText) {\n return `p${page}_field`;\n }\n\n const cleaned = labelText.replace(/\\s*:\\s*$/, '').trim();\n\n const camel = cleaned\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-zA-Z0-9\\s]/g, '')\n .trim()\n .split(/\\s+/)\n .map((word, i) =>\n i === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join('');\n\n if (!camel) {\n return `p${page}_field`;\n }\n\n return `p${page}_${camel}`;\n}\n\nfunction deduplicateName(baseName: string, used: Set<string>): string {\n if (!used.has(baseName)) return baseName;\n let counter = 2;\n while (used.has(`${baseName}_${counter}`)) counter++;\n return `${baseName}_${counter}`;\n}\n","import { PDFDocument, PDFName, StandardFonts } from 'pdf-lib';\nimport type { Field } from './types.js';\n\n/**\n * Remove background color and border from a form field's widget annotations\n * so the field overlay is fully transparent against the printed form.\n */\nfunction clearWidgetAppearance(acroField: { getWidgets(): { dict: any }[] }): void {\n for (const widget of acroField.getWidgets()) {\n const mk = widget.dict.lookup(PDFName.of('MK'));\n if (mk && typeof mk.delete === 'function') {\n mk.delete(PDFName.of('BG')); // background color\n mk.delete(PDFName.of('BC')); // border color\n }\n }\n}\n\n/**\n * Load the original PDF, inject AcroForm fields at computed positions,\n * and return the modified PDF bytes.\n */\nexport async function injectFields(\n pdfBytes: Uint8Array,\n fields: Field[],\n): Promise<Uint8Array> {\n const pdfDoc = await PDFDocument.load(pdfBytes);\n const form = pdfDoc.getForm();\n const font = await pdfDoc.embedFont(StandardFonts.Helvetica);\n\n const pages = pdfDoc.getPages();\n\n for (const field of fields) {\n const pageIndex = field.props.page - 1;\n if (pageIndex < 0 || pageIndex >= pages.length) continue;\n const page = pages[pageIndex];\n const { x, y, width, height } = field.props.pdfRect;\n\n if (field.type === 'input') {\n const textField = form.createTextField(field.key);\n textField.addToPage(page, { x, y, width, height, font, borderWidth: 0 });\n if (field.props.maxLength) {\n textField.setMaxLength(field.props.maxLength);\n textField.enableCombing();\n }\n textField.setFontSize(0); // auto-size\n clearWidgetAppearance(textField.acroField);\n textField.updateAppearances(font);\n } else {\n const checkbox = form.createCheckBox(field.key);\n checkbox.addToPage(page, { x, y, width, height, borderWidth: 0 });\n clearWidgetAppearance(checkbox.acroField);\n checkbox.updateAppearances();\n }\n }\n\n return pdfDoc.save();\n}\n"],"mappings":"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","names":["readFile","join","cheerio","join","readFile"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/poppler.ts","../src/extract-boxes.ts","../src/extract-labels.ts","../src/group-rows.ts","../src/transform.ts","../src/map-labels.ts","../src/inject-fields.ts","../src/extract-dot-lines.ts"],"sourcesContent":["import { program } from 'commander';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { checkPoppler, extractBbox, extractSvg, getPageCount } from './poppler.js';\nimport { extractBoxes } from './extract-boxes.js';\nimport { extractLabels } from './extract-labels.js';\nimport { groupBoxesIntoFields } from './group-rows.js';\nimport { mapLabelsToFields, generateFieldName, deduplicateName } from './map-labels.js';\nimport { injectFields } from './inject-fields.js';\nimport { extractDotLineFields } from './extract-dot-lines.js';\nimport type { Field, FieldOutput } from './types.js';\n\nexport async function convert(inputPath: string, outputPath?: string): Promise<{\n pdfOut: string;\n jsonOut: string;\n fields: Field[];\n}> {\n // 1. Check dependencies\n await checkPoppler();\n\n // 2. Resolve output paths\n const dir = dirname(inputPath);\n const base = basename(inputPath, '.pdf');\n const pdfOut = outputPath ?? join(dir, `${base}-fillable.pdf`);\n const jsonOut = join(dirname(pdfOut), `${basename(pdfOut, '.pdf')}.fields.json`);\n\n // 3. Get page count\n const pageCount = await getPageCount(inputPath);\n console.log(`Processing ${pageCount} page(s)...`);\n\n // 4. Extract bbox labels (all pages at once)\n const bboxHtml = await extractBbox(inputPath);\n const labelsByPage = extractLabels(bboxHtml);\n\n // 5. Process each page: SVG → boxes → groups → fields\n const allFields: Field[] = [];\n\n for (let page = 1; page <= pageCount; page++) {\n console.log(` Page ${page}: extracting geometry...`);\n\n const svgXml = await extractSvg(inputPath, page);\n const { boxes, transform, pageHeight } = extractBoxes(svgXml);\n\n const labels = labelsByPage.get(page) ?? [];\n\n // Detect dot-line free-text fields\n const usedNames = new Set<string>(allFields.map((f) => f.key));\n const { fields: dotFields, consumedLabelIndices } = extractDotLineFields(\n labels, page, pageHeight, usedNames,\n );\n\n // Filter consumed dot labels before passing to SVG-based pipeline\n const remainingLabels = labels.filter((_, i) => !consumedLabelIndices.has(i));\n\n if (!transform || boxes.length === 0) {\n if (dotFields.length === 0) {\n console.log(` Page ${page}: no input boxes found, skipping`);\n continue;\n }\n // Page has dot-line fields but no SVG boxes\n const freeTextCount = dotFields.length;\n console.log(` Page ${page}: ${freeTextCount} free-text field(s)`);\n allFields.push(...dotFields);\n continue;\n }\n\n const fieldGroups = groupBoxesIntoFields(boxes, page);\n // Add dot-line names to usedNames so SVG fields don't collide\n for (const f of dotFields) usedNames.add(f.key);\n const fields = mapLabelsToFields(fieldGroups, remainingLabels, transform, page, pageHeight, usedNames);\n\n const cellCount = fields.filter((f) => f.type === 'input').length;\n const checkboxCount = fields.filter((f) => f.type === 'checkbox').length;\n const freeTextCount = dotFields.length;\n console.log(` Page ${page}: ${cellCount} text fields, ${checkboxCount} checkboxes${freeTextCount ? `, ${freeTextCount} free-text field(s)` : ''}`);\n\n allFields.push(...fields, ...dotFields);\n }\n\n // 6. Inject AcroForm fields into PDF\n console.log('Injecting AcroForm fields...');\n const pdfBytes = new Uint8Array(await readFile(inputPath));\n const filledPdf = await injectFields(pdfBytes, allFields);\n await writeFile(pdfOut, filledPdf);\n\n // 7. Write JSON field definitions\n const output: FieldOutput = {\n pages: Array.from({ length: pageCount }, (_, i) => ({\n pageNumber: i + 1,\n fields: allFields.filter((f) => f.props.page === i + 1),\n })),\n };\n await writeFile(jsonOut, JSON.stringify(output, null, 2));\n\n console.log(`\\nDone!`);\n console.log(` PDF: ${pdfOut}`);\n console.log(` JSON: ${jsonOut}`);\n console.log(` Total fields: ${allFields.length}`);\n\n return { pdfOut, jsonOut, fields: allFields };\n}\n\nfunction main() {\n program\n .name('cerfaparse')\n .description('Convert flat CERFA PDFs into fillable AcroForm PDFs with field definitions')\n .version('0.1.0');\n\n program\n .command('convert')\n .description('Convert a flat CERFA PDF to a fillable AcroForm PDF + JSON field definitions')\n .argument('<input>', 'Path to the input CERFA PDF')\n .option('-o, --output <path>', 'Output PDF path')\n .action(async (input: string, opts: { output?: string }) => {\n try {\n await convert(input, opts.output);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n });\n\n if (process.argv.length <= 2) {\n program.help();\n }\n\n program.parse();\n}\n\n// Only run CLI when executed directly (not imported as library)\nconst isMainModule = process.argv[1] && (\n import.meta.url.endsWith(process.argv[1]) ||\n import.meta.url === `file://${process.argv[1]}`\n);\nif (isMainModule) {\n main();\n}\n","import { execa } from 'execa';\nimport { access, mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nexport async function checkPoppler(): Promise<void> {\n const tools = ['pdftocairo', 'pdftotext', 'pdfinfo'];\n const missing: string[] = [];\n\n for (const tool of tools) {\n try {\n await execa(tool, ['-v']);\n } catch {\n missing.push(tool);\n }\n }\n\n if (missing.length > 0) {\n throw new Error(\n `Poppler tools not found: ${missing.join(', ')}. Install with:\\n` +\n ' macOS: brew install poppler\\n' +\n ' Linux: apt install poppler-utils',\n );\n }\n}\n\nexport async function getPageCount(pdfPath: string): Promise<number> {\n await validateFileExists(pdfPath);\n const { stdout } = await execa('pdfinfo', [pdfPath]);\n const match = stdout.match(/^Pages:\\s+(\\d+)/m);\n if (!match) throw new Error('Could not determine page count from pdfinfo');\n return parseInt(match[1], 10);\n}\n\nexport async function extractSvg(\n pdfPath: string,\n page: number,\n): Promise<string> {\n const tmpDir = await mkdtemp(join(tmpdir(), 'cerfaparse-'));\n const outPath = join(tmpDir, `page-${page}.svg`);\n try {\n await execa('pdftocairo', [\n '-svg',\n '-f',\n String(page),\n '-l',\n String(page),\n pdfPath,\n outPath,\n ]);\n return await readFile(outPath, 'utf-8');\n } finally {\n try {\n await rm(tmpDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors — don't mask the original error\n }\n }\n}\n\nexport async function extractBbox(pdfPath: string): Promise<string> {\n const { stdout } = await execa('pdftotext', [\n '-bbox-layout',\n pdfPath,\n '-',\n ]);\n return stdout;\n}\n\nasync function validateFileExists(path: string): Promise<void> {\n try {\n await access(path);\n } catch {\n throw new Error(`File not found: ${path}`);\n }\n}\n","import * as cheerio from 'cheerio';\nimport type { SvgBox, TransformMatrix } from './types.js';\n\nconst WHITE_THRESHOLD = 0.95; // RGB components above 95% → white\nconst DARK_STROKE_THRESHOLD = 0.2; // RGB components below 20% → dark\n\n/** Size thresholds to filter out large background rects and thin slivers */\nconst MAX_BOX_WIDTH = 50;\nconst MAX_BOX_HEIGHT = 50;\nconst MIN_BOX_SIZE = 3;\n\nexport interface ExtractResult {\n boxes: SvgBox[];\n transform: TransformMatrix | null;\n pageHeight: number;\n}\n\nexport function extractBoxes(svgXml: string): ExtractResult {\n const $ = cheerio.load(svgXml, { xml: true });\n\n // Extract page height from SVG viewBox or height attribute\n let pageHeight = 0;\n const viewBox = $('svg').attr('viewBox') ?? '';\n const vbParts = viewBox.split(/\\s+/);\n if (vbParts.length >= 4) {\n pageHeight = parseFloat(vbParts[3]);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n // Fallback: parse height attribute (e.g. \"572pt\" → 572)\n const heightAttr = $('svg').attr('height') ?? '';\n pageHeight = parseFloat(heightAttr);\n }\n if (!Number.isFinite(pageHeight) || pageHeight <= 0) {\n throw new Error('Could not extract page height from SVG viewBox or height attribute');\n }\n\n let transform: TransformMatrix | null = null;\n const boxes: SvgBox[] = [];\n\n $('path').each((_, el) => {\n const path = $(el);\n const d = path.attr('d');\n const fill = path.attr('fill') ?? '';\n const stroke = path.attr('stroke') ?? '';\n const strokeWidth = parseFloat(path.attr('stroke-width') ?? '0');\n\n if (!d || !isWhiteColor(fill)) return;\n\n const rect = parseRectPath(d);\n if (!rect) return;\n if (rect.width > MAX_BOX_WIDTH || rect.height > MAX_BOX_HEIGHT) return;\n if (rect.width < MIN_BOX_SIZE || rect.height < MIN_BOX_SIZE) return;\n\n const type = classifyBox(stroke, strokeWidth);\n if (!type) return;\n\n // Extract and validate transform from each matching element\n const pathTransform = parseTransformFromAttr(path.attr('transform') ?? '');\n if (!pathTransform) return;\n\n // Compose with ancestor transforms (<g> parents, or <use> for paths in <defs>)\n const ancestorTransform = collectAncestorTransform($, el);\n const effectiveTransform = ancestorTransform\n ? composeTransforms(ancestorTransform, pathTransform)\n : pathTransform;\n\n if (!transform) {\n transform = effectiveTransform;\n } else if (!matricesEqual(transform, effectiveTransform)) {\n console.warn(\n 'Warning: found input box with different transform matrix — skipping. ' +\n 'This may indicate rotated sections.',\n );\n return;\n }\n\n boxes.push({ ...rect, type });\n });\n\n return { boxes, transform, pageHeight };\n}\n\n/**\n * Collect the composed ancestor transform for a path element.\n *\n * Walks up from the path to <svg>, collecting <g> transforms along the way.\n * If the path is inside <defs> (rendered indirectly via <use>), finds the\n * root <use> element's transform instead — in pdftocairo output, intermediate\n * group transforms within the <defs>/<use> chain cancel out, leaving only\n * the outermost <use> transform as the net effect.\n */\nfunction collectAncestorTransform(\n $: cheerio.CheerioAPI,\n el: any,\n): TransformMatrix | null {\n const transforms: TransformMatrix[] = [];\n let insideDefs = false;\n let current = (el as any).parent;\n\n while (current) {\n const tag = current.tagName ?? current.name ?? '';\n if (tag === 'svg') break;\n\n if (tag === 'defs') {\n insideDefs = true;\n break;\n }\n\n if (tag === 'g') {\n const t = $(current).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) transforms.unshift(parsed); // outermost first\n }\n\n current = current.parent;\n }\n\n if (insideDefs) {\n // Path lives in <defs> — rendered via <use> reference chain.\n // Find the outermost <use> (direct child of <svg>) with a transform.\n // Intermediate transforms within the defs/use chain cancel in pdftocairo output.\n const svgChildren = $('svg').children().toArray();\n for (const child of svgChildren) {\n const tag = (child as any).tagName ?? '';\n if (tag !== 'use') continue;\n const t = $(child).attr('transform') ?? '';\n const parsed = parseTransformAttr(t);\n if (parsed) return parsed;\n }\n return null;\n }\n\n if (transforms.length === 0) return null;\n return transforms.reduce(composeTransforms);\n}\n\n/**\n * Parse a transform attribute — supports both matrix() and translate() syntax.\n */\nfunction parseTransformAttr(attr: string): TransformMatrix | null {\n // Try matrix() first\n const matrixMatch = attr.match(MATRIX_RE);\n if (matrixMatch) {\n const m: TransformMatrix = {\n a: parseFloat(matrixMatch[1]),\n b: parseFloat(matrixMatch[2]),\n c: parseFloat(matrixMatch[3]),\n d: parseFloat(matrixMatch[4]),\n e: parseFloat(matrixMatch[5]),\n f: parseFloat(matrixMatch[6]),\n };\n if (Object.values(m).some((v) => !Number.isFinite(v))) return null;\n return m;\n }\n\n // Try translate()\n const translateMatch = attr.match(\n /translate\\(\\s*([-\\d.]+)(?:\\s*,\\s*([-\\d.]+))?\\s*\\)/,\n );\n if (translateMatch) {\n const tx = parseFloat(translateMatch[1]);\n const ty = parseFloat(translateMatch[2] ?? '0');\n if (!Number.isFinite(tx) || !Number.isFinite(ty)) return null;\n return { a: 1, b: 0, c: 0, d: 1, e: tx, f: ty };\n }\n\n return null;\n}\n\n/**\n * Compose two 2D affine transforms: result = m1 * m2\n * Applies m2 first, then m1 (standard matrix multiplication order).\n */\nfunction composeTransforms(\n m1: TransformMatrix,\n m2: TransformMatrix,\n): TransformMatrix {\n return {\n a: m1.a * m2.a + m1.c * m2.b,\n b: m1.b * m2.a + m1.d * m2.b,\n c: m1.a * m2.c + m1.c * m2.d,\n d: m1.b * m2.c + m1.d * m2.d,\n e: m1.a * m2.e + m1.c * m2.f + m1.e,\n f: m1.b * m2.e + m1.d * m2.f + m1.f,\n };\n}\n\nconst MATRIX_RE =\n /matrix\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)/;\n\nfunction parseTransformFromAttr(attr: string): TransformMatrix | null {\n return parseTransformAttr(attr);\n}\n\nfunction matricesEqual(a: TransformMatrix, b: TransformMatrix): boolean {\n const tol = 0.001;\n return (\n Math.abs(a.a - b.a) < tol &&\n Math.abs(a.b - b.b) < tol &&\n Math.abs(a.c - b.c) < tol &&\n Math.abs(a.d - b.d) < tol &&\n Math.abs(a.e - b.e) < tol &&\n Math.abs(a.f - b.f) < tol\n );\n}\n\n/**\n * Parse a rectangular path from 4 corner points, regardless of winding order.\n * Computes bounding box from all points.\n */\nfunction parseRectPath(\n d: string,\n): { x: number; y: number; width: number; height: number } | null {\n const commands = d.trim().split(/\\s*([MLHVCSQTAZ])\\s*/i).filter(Boolean);\n\n const points: [number, number][] = [];\n let i = 0;\n while (i < commands.length) {\n const cmd = commands[i];\n if (cmd === 'M' || cmd === 'L') {\n const coords = commands[i + 1]?.trim().split(/\\s+/);\n if (coords && coords.length >= 2) {\n const x = parseFloat(coords[0]);\n const y = parseFloat(coords[1]);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n points.push([x, y]);\n }\n i += 2;\n } else if (cmd === 'Z') {\n i += 1;\n } else {\n return null;\n }\n }\n\n if (points.length < 4) return null;\n\n // Use first 4 points to compute bounding box (handles any winding order)\n const xs = points.slice(0, 4).map((p) => p[0]);\n const ys = points.slice(0, 4).map((p) => p[1]);\n const minX = Math.min(...xs);\n const maxX = Math.max(...xs);\n const minY = Math.min(...ys);\n const maxY = Math.max(...ys);\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n if (width < 1 || height < 1) return null;\n\n return { x: minX, y: minY, width, height };\n}\n\n/** Parse an rgb(r%, g%, b%) color string and return [0-1] components, or null */\nfunction parseRgbPercent(color: string): [number, number, number] | null {\n const match = color.match(\n /rgb\\(\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*\\)/,\n );\n if (!match) return null;\n return [\n parseFloat(match[1]) / 100,\n parseFloat(match[2]) / 100,\n parseFloat(match[3]) / 100,\n ];\n}\n\nfunction isWhiteColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] >= WHITE_THRESHOLD && rgb[1] >= WHITE_THRESHOLD && rgb[2] >= WHITE_THRESHOLD;\n}\n\nfunction isDarkColor(color: string): boolean {\n const rgb = parseRgbPercent(color);\n if (!rgb) return false;\n return rgb[0] < DARK_STROKE_THRESHOLD && rgb[1] < DARK_STROKE_THRESHOLD && rgb[2] < DARK_STROKE_THRESHOLD;\n}\n\nfunction classifyBox(\n stroke: string,\n strokeWidth: number,\n): 'cell' | 'checkbox' | null {\n if (isWhiteColor(stroke) && strokeWidth >= 0.9) {\n return 'cell';\n }\n\n if (isDarkColor(stroke)) {\n return 'checkbox';\n }\n\n return null;\n}\n","import * as cheerio from 'cheerio';\nimport type { Label } from './types.js';\n\n/** Max X-gap between words on same line to be joined (PDF points) */\nconst WORD_JOIN_GAP = 10;\n/** Y tolerance for considering words on the same line (PDF points) */\nconst LINE_Y_TOLERANCE = 2;\n\n/**\n * Parse pdftotext -bbox-layout HTML output and extract labels per page.\n * Returns labels with bounding boxes in PDF coordinate space.\n */\nexport function extractLabels(bboxHtml: string): Map<number, Label[]> {\n const $ = cheerio.load(bboxHtml, { xml: false });\n const pageLabels = new Map<number, Label[]>();\n\n $('page').each((pageIdx, pageEl) => {\n const pageNum = pageIdx + 1;\n const words: {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n }[] = [];\n\n $(pageEl)\n .find('word')\n .each((_, wordEl) => {\n const $w = $(wordEl);\n words.push({\n text: $w.text().trim(),\n xMin: parseFloat($w.attr('xmin') ?? '0'),\n yMin: parseFloat($w.attr('ymin') ?? '0'),\n xMax: parseFloat($w.attr('xmax') ?? '0'),\n yMax: parseFloat($w.attr('ymax') ?? '0'),\n });\n });\n\n const labels = assembleLabels(words, pageNum);\n pageLabels.set(pageNum, labels);\n });\n\n return pageLabels;\n}\n\ninterface Word {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\nfunction assembleLabels(words: Word[], page: number): Label[] {\n if (words.length === 0) return [];\n\n // Sort by Y then X\n const sorted = [...words].sort((a, b) => {\n const yDiff = a.yMin - b.yMin;\n if (Math.abs(yDiff) > LINE_Y_TOLERANCE) return yDiff;\n return a.xMin - b.xMin;\n });\n\n // Group words into lines by Y-proximity\n const lines: Word[][] = [[sorted[0]]];\n for (let i = 1; i < sorted.length; i++) {\n const prev = lines[lines.length - 1];\n const lastWord = prev[prev.length - 1];\n const curr = sorted[i];\n\n if (Math.abs(curr.yMin - lastWord.yMin) <= LINE_Y_TOLERANCE) {\n prev.push(curr);\n } else {\n lines.push([curr]);\n }\n }\n\n // Within each line, join adjacent words into label spans\n const labels: Label[] = [];\n for (const line of lines) {\n const sortedLine = line.sort((a, b) => a.xMin - b.xMin);\n let current: Word[] = [sortedLine[0]];\n\n for (let i = 1; i < sortedLine.length; i++) {\n const prev = current[current.length - 1];\n const curr = sortedLine[i];\n const gap = curr.xMin - prev.xMax;\n\n if (gap <= WORD_JOIN_GAP) {\n current.push(curr);\n } else {\n labels.push(wordsToLabel(current, page));\n current = [curr];\n }\n }\n labels.push(wordsToLabel(current, page));\n }\n\n return labels;\n}\n\nfunction wordsToLabel(words: Word[], page: number): Label {\n const text = words.map((w) => w.text).join(' ');\n let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;\n for (const w of words) {\n if (w.xMin < xMin) xMin = w.xMin;\n if (w.yMin < yMin) yMin = w.yMin;\n if (w.xMax > xMax) xMax = w.xMax;\n if (w.yMax > yMax) yMax = w.yMax;\n }\n return { text, xMin, yMin, xMax, yMax, page };\n}\n","import type { BoxRow, SvgBox } from './types.js';\n\n/** Tolerance for grouping boxes into the same Y-row (SVG units) */\nconst Y_TOLERANCE = 2;\n\n/** X-gap threshold: gaps larger than this split into separate fields.\n * Typical inter-cell gap within a field is ~3 SVG units; cross-field gaps are ≥15. */\nconst FIELD_GAP_THRESHOLD = 10;\n\nexport interface FieldGroup {\n boxes: SvgBox[];\n row: BoxRow;\n /** Number of character cells (= maxLength for comb fields) */\n boxCount: number;\n}\n\n/**\n * Group boxes by Y-proximity into rows, then split each row\n * into fields by X-gap. Returns FieldGroups sorted top-to-bottom,\n * left-to-right.\n */\nexport function groupBoxesIntoFields(\n boxes: SvgBox[],\n page: number,\n): FieldGroup[] {\n const rows = groupByY(boxes, page);\n const fields: FieldGroup[] = [];\n\n for (const row of rows) {\n const sorted = [...row.boxes].sort((a, b) => a.x - b.x);\n const splits = splitByXGap(sorted, row);\n fields.push(...splits);\n }\n\n // Sort by Y (top-to-bottom in SVG = ascending Y), then X\n fields.sort((a, b) => {\n const yDiff = a.boxes[0].y - b.boxes[0].y;\n if (Math.abs(yDiff) > Y_TOLERANCE) return yDiff;\n return a.boxes[0].x - b.boxes[0].x;\n });\n\n return fields;\n}\n\nfunction groupByY(boxes: SvgBox[], page: number): BoxRow[] {\n const sorted = [...boxes].sort((a, b) => a.y - b.y);\n const rows: BoxRow[] = [];\n\n for (const box of sorted) {\n const existingRow = rows.find(\n (r) => Math.abs(r.y - box.y) <= Y_TOLERANCE && r.type === box.type,\n );\n if (existingRow) {\n existingRow.boxes.push(box);\n // Update row Y to running average for stable clustering\n const avg =\n existingRow.boxes.reduce((sum, b) => sum + b.y, 0) /\n existingRow.boxes.length;\n existingRow.y = avg;\n } else {\n rows.push({ boxes: [box], y: box.y, type: box.type, page });\n }\n }\n\n return rows;\n}\n\nfunction splitByXGap(\n sortedBoxes: SvgBox[],\n row: BoxRow,\n): FieldGroup[] {\n if (sortedBoxes.length === 0) return [];\n\n const groups: SvgBox[][] = [[sortedBoxes[0]]];\n\n for (let i = 1; i < sortedBoxes.length; i++) {\n const prev = sortedBoxes[i - 1];\n const curr = sortedBoxes[i];\n const gap = curr.x - (prev.x + prev.width);\n\n if (gap > FIELD_GAP_THRESHOLD) {\n groups.push([curr]);\n } else {\n groups[groups.length - 1].push(curr);\n }\n }\n\n return groups.map((boxes) => ({\n boxes,\n row: { ...row, boxes },\n boxCount: boxes.length,\n }));\n}\n","import type { PdfRect, SvgBox, TransformMatrix } from './types.js';\n\n/**\n * Apply SVG affine transform matrix to a point.\n * Output is in SVG viewport coordinates (origin top-left, Y increases downward).\n */\nexport function svgPointToViewport(\n svgX: number,\n svgY: number,\n matrix: TransformMatrix,\n): { x: number; y: number } {\n return {\n x: matrix.a * svgX + matrix.c * svgY + matrix.e,\n y: matrix.b * svgX + matrix.d * svgY + matrix.f,\n };\n}\n\n/**\n * Transform an SVG box to a PDF rect.\n *\n * The SVG matrix converts content coords → SVG viewport coords (top-left origin, Y-down).\n * PDF uses bottom-left origin with Y-up. So after applying the matrix, we flip Y\n * using the page height: pdf_y = pageHeight - viewport_y.\n *\n * Returns (x, y) as the bottom-left corner with positive width/height (PDF convention).\n */\nexport function svgBoxToPdfRect(\n box: SvgBox,\n matrix: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const corner1 = svgPointToViewport(box.x, box.y, matrix);\n const corner2 = svgPointToViewport(\n box.x + box.width,\n box.y + box.height,\n matrix,\n );\n\n // In viewport coords, compute bounding box\n const vpLeft = Math.min(corner1.x, corner2.x);\n const vpTop = Math.min(corner1.y, corner2.y);\n const vpRight = Math.max(corner1.x, corner2.x);\n const vpBottom = Math.max(corner1.y, corner2.y);\n const width = vpRight - vpLeft;\n const height = vpBottom - vpTop;\n\n // Convert from viewport (top-left, Y-down) to PDF (bottom-left, Y-up)\n const pdfX = vpLeft;\n const pdfY = pageHeight - vpBottom; // bottom edge in PDF coords\n\n return { x: pdfX, y: pdfY, width, height };\n}\n\n// Keep backward-compatible alias\nexport const svgPointToPdf = svgPointToViewport;\n","import type { Field, FieldType, Label, PdfRect } from './types.js';\nimport type { FieldGroup } from './group-rows.js';\nimport { svgBoxToPdfRect } from './transform.js';\nimport type { TransformMatrix } from './types.js';\n\n/** Map internal box type to formly-compatible field type */\nfunction toFieldType(boxType: 'cell' | 'checkbox'): FieldType {\n return boxType === 'cell' ? 'input' : 'checkbox';\n}\n\n/** Max Y-distance (PDF pts) for a label to be considered \"above\" a field row */\nconst LABEL_Y_MAX_DISTANCE = 25;\n\n/** Convert pdftotext label coords (top-left origin, Y-down) to PDF coords (bottom-left, Y-up) */\nfunction labelToPdfCoords(label: Label, pageHeight: number): {\n xMin: number; xMax: number; yBottom: number; yTop: number;\n} {\n return {\n xMin: label.xMin,\n xMax: label.xMax,\n yBottom: pageHeight - label.yMax, // bottom edge in PDF\n yTop: pageHeight - label.yMin, // top edge in PDF\n };\n}\n\n/**\n * Map labels to field groups and produce Field definitions.\n * Labels are in pdftotext coords (viewport); boxes are in SVG content coords.\n * Both are converted to PDF coords (bottom-left, Y-up) before matching.\n */\nexport function mapLabelsToFields(\n fieldGroups: FieldGroup[],\n labels: Label[],\n transform: TransformMatrix,\n page: number,\n pageHeight: number,\n usedNames?: Set<string>,\n): Field[] {\n const fields: Field[] = [];\n const names = usedNames ?? new Set<string>();\n\n for (const group of fieldGroups) {\n const pdfRect = computeGroupPdfRect(group, transform, pageHeight);\n const bestLabel = findBestLabel(pdfRect, labels, pageHeight);\n const labelText = bestLabel?.text ?? '';\n\n const baseName = generateFieldName(labelText, group.row.type, page);\n const name = deduplicateName(baseName, names);\n names.add(name);\n\n const field: Field = {\n key: name,\n type: toFieldType(group.row.type),\n props: {\n label: labelText,\n page,\n pdfRect,\n ...(group.row.type === 'cell' ? { maxLength: group.boxCount } : {}),\n },\n };\n\n fields.push(field);\n }\n\n return fields;\n}\n\nfunction computeGroupPdfRect(\n group: FieldGroup,\n transform: TransformMatrix,\n pageHeight: number,\n): PdfRect {\n const rects = group.boxes.map((box) => svgBoxToPdfRect(box, transform, pageHeight));\n let x = Infinity, y = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const r of rects) {\n if (r.x < x) x = r.x;\n if (r.y < y) y = r.y;\n const rx = r.x + r.width;\n const ry = r.y + r.height;\n if (rx > maxX) maxX = rx;\n if (ry > maxY) maxY = ry;\n }\n return { x, y, width: maxX - x, height: maxY - y };\n}\n\nfunction findBestLabel(fieldRect: PdfRect, labels: Label[], pageHeight: number): Label | null {\n const fieldTop = fieldRect.y + fieldRect.height;\n const fieldLeft = fieldRect.x;\n const fieldRight = fieldRect.x + fieldRect.width;\n\n let bestLabel: Label | null = null;\n let bestScore = Infinity;\n\n for (const label of labels) {\n const pdfLabel = labelToPdfCoords(label, pageHeight);\n\n // Label should be above the field: label bottom > field top\n // yDistance > 0 means label is above the field (correct position)\n const yDistance = pdfLabel.yBottom - fieldTop;\n\n if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE) continue;\n\n // Check horizontal overlap or proximity\n const hasOverlap = pdfLabel.xMin < fieldRight && pdfLabel.xMax > fieldLeft;\n const isLeftOf =\n pdfLabel.xMax <= fieldLeft && fieldLeft - pdfLabel.xMax < 50;\n\n if (!hasOverlap && !isLeftOf) continue;\n\n // Score: prefer labels that are close vertically and overlap horizontally\n const score = yDistance + (hasOverlap ? 0 : 20);\n if (score < bestScore) {\n bestScore = score;\n bestLabel = label;\n }\n }\n\n return bestLabel;\n}\n\nexport function generateFieldName(\n labelText: string,\n _type: 'cell' | 'checkbox',\n page: number,\n): string {\n if (!labelText) {\n return `p${page}_field`;\n }\n\n const cleaned = labelText.replace(/\\s*:\\s*$/, '').trim();\n\n const camel = cleaned\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-zA-Z0-9\\s]/g, '')\n .trim()\n .split(/\\s+/)\n .map((word, i) =>\n i === 0\n ? word.toLowerCase()\n : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join('');\n\n if (!camel) {\n return `p${page}_field`;\n }\n\n return `p${page}_${camel}`;\n}\n\nexport function deduplicateName(baseName: string, used: Set<string>): string {\n if (!used.has(baseName)) return baseName;\n let counter = 2;\n while (used.has(`${baseName}_${counter}`)) counter++;\n return `${baseName}_${counter}`;\n}\n","import { PDFDocument, PDFName, StandardFonts } from 'pdf-lib';\nimport type { Field } from './types.js';\n\n/**\n * Remove background color and border from a form field's widget annotations\n * so the field overlay is fully transparent against the printed form.\n */\nfunction clearWidgetAppearance(acroField: { getWidgets(): { dict: any }[] }): void {\n for (const widget of acroField.getWidgets()) {\n const mk = widget.dict.lookup(PDFName.of('MK'));\n if (mk && typeof mk.delete === 'function') {\n mk.delete(PDFName.of('BG')); // background color\n mk.delete(PDFName.of('BC')); // border color\n }\n }\n}\n\n/**\n * Load the original PDF, inject AcroForm fields at computed positions,\n * and return the modified PDF bytes.\n */\nexport async function injectFields(\n pdfBytes: Uint8Array,\n fields: Field[],\n): Promise<Uint8Array> {\n const pdfDoc = await PDFDocument.load(pdfBytes);\n const form = pdfDoc.getForm();\n const font = await pdfDoc.embedFont(StandardFonts.Helvetica);\n\n const pages = pdfDoc.getPages();\n\n for (const field of fields) {\n const pageIndex = field.props.page - 1;\n if (pageIndex < 0 || pageIndex >= pages.length) continue;\n const page = pages[pageIndex];\n const { x, y, width, height } = field.props.pdfRect;\n\n if (field.type === 'input') {\n const textField = form.createTextField(field.key);\n textField.addToPage(page, { x, y, width, height, font, borderWidth: 0 });\n if (field.props.maxLength) {\n textField.setMaxLength(field.props.maxLength);\n textField.enableCombing();\n }\n if (field.props.multiline) {\n textField.enableMultiline();\n }\n textField.setFontSize(0); // auto-size\n clearWidgetAppearance(textField.acroField);\n textField.updateAppearances(font);\n } else {\n const checkbox = form.createCheckBox(field.key);\n checkbox.addToPage(page, { x, y, width, height, borderWidth: 0 });\n clearWidgetAppearance(checkbox.acroField);\n checkbox.updateAppearances();\n }\n }\n\n return pdfDoc.save();\n}\n","import type { Field, Label, PdfRect } from './types.js';\nimport { generateFieldName, deduplicateName } from './map-labels.js';\n\n/** Minimum number of period characters for a label to be considered a dot line */\nconst MIN_DOT_COUNT = 10;\n\n/** Max Y-gap (pdftotext coords, Y-down) between consecutive dot lines to group them */\nconst DOT_GROUP_Y_GAP = 20;\n\n/** Max Y-distance (pdftotext coords) for a text label above a dot group */\nconst LABEL_Y_MAX_DISTANCE = 30;\n\nexport interface DotLineResult {\n fields: Field[];\n consumedLabelIndices: Set<number>;\n}\n\n/**\n * Returns true if the label text consists only of dots (periods) and whitespace,\n * with at least MIN_DOT_COUNT periods. Excludes short dot sequences like \"...\" or\n * labels mixing text with dots.\n */\nexport function isDotLine(text: string): boolean {\n if (!/^[.\\s\\u2026]+$/.test(text)) return false;\n const dotCount = (text.match(/\\./g) ?? []).length;\n return dotCount >= MIN_DOT_COUNT;\n}\n\n/**\n * Detect free-text dot-line fields from pdftotext labels.\n *\n * Labels are in pdftotext coords (top-left origin, Y-down).\n * Output PdfRects are in PDF coords (bottom-left origin, Y-up).\n */\nexport function extractDotLineFields(\n labels: Label[],\n page: number,\n pageHeight: number,\n usedNames?: Set<string>,\n): DotLineResult {\n const names = usedNames ?? new Set<string>();\n const consumed = new Set<number>();\n const fields: Field[] = [];\n\n // 1. Identify dot-line label indices\n const dotIndices: number[] = [];\n for (let i = 0; i < labels.length; i++) {\n if (isDotLine(labels[i].text)) {\n dotIndices.push(i);\n }\n }\n\n if (dotIndices.length === 0) return { fields: [], consumedLabelIndices: consumed };\n\n // 2. Group consecutive dot lines by Y-proximity\n const groups: number[][] = [[dotIndices[0]]];\n for (let i = 1; i < dotIndices.length; i++) {\n const prevIdx = dotIndices[i - 1];\n const currIdx = dotIndices[i];\n const prevLabel = labels[prevIdx];\n const currLabel = labels[currIdx];\n\n if (Math.abs(currLabel.yMin - prevLabel.yMin) <= DOT_GROUP_Y_GAP) {\n groups[groups.length - 1].push(currIdx);\n } else {\n groups.push([currIdx]);\n }\n }\n\n // 3. For each group, compute bounding rect and find text label\n for (const group of groups) {\n // Mark consumed\n for (const idx of group) consumed.add(idx);\n\n // Compute bounding box in pdftotext coords (Y-down)\n let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;\n for (const idx of group) {\n const lbl = labels[idx];\n if (lbl.xMin < xMin) xMin = lbl.xMin;\n if (lbl.yMin < yMin) yMin = lbl.yMin;\n if (lbl.xMax > xMax) xMax = lbl.xMax;\n if (lbl.yMax > yMax) yMax = lbl.yMax;\n }\n\n // Convert to PDF coords (bottom-left, Y-up)\n const pdfRect: PdfRect = {\n x: xMin,\n y: pageHeight - yMax,\n width: xMax - xMin,\n height: yMax - yMin,\n };\n\n // Find best text label above the dot group (in pdftotext coords, \"above\" = smaller Y)\n const groupTopY = yMin; // top of group in pdftotext Y-down coords\n const bestLabel = findTextLabelAbove(labels, groupTopY, xMin, xMax, consumed);\n\n const labelText = bestLabel?.text ?? '';\n const isMultiline = group.length > 1;\n\n const baseName = generateFieldName(labelText, 'cell', page);\n const name = deduplicateName(baseName, names);\n names.add(name);\n\n fields.push({\n key: name,\n type: 'input',\n props: {\n label: labelText,\n page,\n pdfRect,\n ...(isMultiline ? { multiline: true } : {}),\n },\n });\n }\n\n return { fields, consumedLabelIndices: consumed };\n}\n\n/**\n * Find the closest text label above a dot group in pdftotext coords (Y-down).\n * \"Above\" means smaller Y value. Skips consumed (dot-line) labels.\n */\nfunction findTextLabelAbove(\n labels: Label[],\n groupTopY: number,\n groupXMin: number,\n groupXMax: number,\n consumed: Set<number>,\n): Label | null {\n let best: Label | null = null;\n let bestDistance = Infinity;\n\n for (let i = 0; i < labels.length; i++) {\n if (consumed.has(i)) continue;\n const lbl = labels[i];\n\n // Label must be above (smaller yMax in Y-down coords)\n const yDistance = groupTopY - lbl.yMax;\n if (yDistance < 0 || yDistance > LABEL_Y_MAX_DISTANCE) continue;\n\n // Check horizontal overlap or proximity\n const hasOverlap = lbl.xMin < groupXMax && lbl.xMax > groupXMin;\n const isLeftOf = lbl.xMax <= groupXMin && groupXMin - lbl.xMax < 50;\n if (!hasOverlap && !isLeftOf) continue;\n\n const score = yDistance + (hasOverlap ? 0 : 20);\n if (score < bestDistance) {\n bestDistance = score;\n best = lbl;\n }\n }\n\n return best;\n}\n"],"mappings":"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","names":["readFile","join","cheerio","LABEL_Y_MAX_DISTANCE","join","freeTextCount","readFile"]}
|