cerfaparse 0.1.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.
@@ -0,0 +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 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"]}
@@ -0,0 +1,134 @@
1
+ interface TransformMatrix {
2
+ a: number;
3
+ b: number;
4
+ c: number;
5
+ d: number;
6
+ e: number;
7
+ f: number;
8
+ }
9
+ type BoxType = 'cell' | 'checkbox';
10
+ /** Formly-compatible field types */
11
+ type FieldType = 'input' | 'checkbox';
12
+ interface SvgBox {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ type: BoxType;
18
+ }
19
+ interface PdfRect {
20
+ x: number;
21
+ y: number;
22
+ width: number;
23
+ height: number;
24
+ }
25
+ interface BoxRow {
26
+ boxes: SvgBox[];
27
+ y: number;
28
+ type: BoxType;
29
+ page: number;
30
+ }
31
+ interface Label {
32
+ text: string;
33
+ xMin: number;
34
+ yMin: number;
35
+ xMax: number;
36
+ yMax: number;
37
+ page: number;
38
+ }
39
+ /** Formly-compatible field definition with spatial metadata */
40
+ interface Field {
41
+ key: string;
42
+ type: FieldType;
43
+ props: {
44
+ label: string;
45
+ maxLength?: number;
46
+ page: number;
47
+ pdfRect: PdfRect;
48
+ };
49
+ }
50
+ interface PageData {
51
+ pageNumber: number;
52
+ boxes: SvgBox[];
53
+ transform: TransformMatrix;
54
+ labels: Label[];
55
+ fields: Field[];
56
+ }
57
+ interface FieldOutput {
58
+ pages: {
59
+ pageNumber: number;
60
+ fields: Field[];
61
+ }[];
62
+ }
63
+
64
+ declare function convert(inputPath: string, outputPath?: string): Promise<{
65
+ pdfOut: string;
66
+ jsonOut: string;
67
+ fields: Field[];
68
+ }>;
69
+
70
+ interface ExtractResult {
71
+ boxes: SvgBox[];
72
+ transform: TransformMatrix | null;
73
+ pageHeight: number;
74
+ }
75
+ declare function extractBoxes(svgXml: string): ExtractResult;
76
+
77
+ /**
78
+ * Parse pdftotext -bbox-layout HTML output and extract labels per page.
79
+ * Returns labels with bounding boxes in PDF coordinate space.
80
+ */
81
+ declare function extractLabels(bboxHtml: string): Map<number, Label[]>;
82
+
83
+ interface FieldGroup {
84
+ boxes: SvgBox[];
85
+ row: BoxRow;
86
+ /** Number of character cells (= maxLength for comb fields) */
87
+ boxCount: number;
88
+ }
89
+ /**
90
+ * Group boxes by Y-proximity into rows, then split each row
91
+ * into fields by X-gap. Returns FieldGroups sorted top-to-bottom,
92
+ * left-to-right.
93
+ */
94
+ declare function groupBoxesIntoFields(boxes: SvgBox[], page: number): FieldGroup[];
95
+
96
+ /**
97
+ * Map labels to field groups and produce Field definitions.
98
+ * Labels are in pdftotext coords (viewport); boxes are in SVG content coords.
99
+ * Both are converted to PDF coords (bottom-left, Y-up) before matching.
100
+ */
101
+ declare function mapLabelsToFields(fieldGroups: FieldGroup[], labels: Label[], transform: TransformMatrix, page: number, pageHeight: number): Field[];
102
+
103
+ /**
104
+ * Apply SVG affine transform matrix to a point.
105
+ * Output is in SVG viewport coordinates (origin top-left, Y increases downward).
106
+ */
107
+ declare function svgPointToViewport(svgX: number, svgY: number, matrix: TransformMatrix): {
108
+ x: number;
109
+ y: number;
110
+ };
111
+ /**
112
+ * Transform an SVG box to a PDF rect.
113
+ *
114
+ * The SVG matrix converts content coords → SVG viewport coords (top-left origin, Y-down).
115
+ * PDF uses bottom-left origin with Y-up. So after applying the matrix, we flip Y
116
+ * using the page height: pdf_y = pageHeight - viewport_y.
117
+ *
118
+ * Returns (x, y) as the bottom-left corner with positive width/height (PDF convention).
119
+ */
120
+ declare function svgBoxToPdfRect(box: SvgBox, matrix: TransformMatrix, pageHeight: number): PdfRect;
121
+ declare const svgPointToPdf: typeof svgPointToViewport;
122
+
123
+ /**
124
+ * Load the original PDF, inject AcroForm fields at computed positions,
125
+ * and return the modified PDF bytes.
126
+ */
127
+ declare function injectFields(pdfBytes: Uint8Array, fields: Field[]): Promise<Uint8Array>;
128
+
129
+ declare function checkPoppler(): Promise<void>;
130
+ declare function getPageCount(pdfPath: string): Promise<number>;
131
+ declare function extractSvg(pdfPath: string, page: number): Promise<string>;
132
+ declare function extractBbox(pdfPath: string): Promise<string>;
133
+
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 };