* Extract detailed form field info (name, type, page, bounding box, label) * from a PDF. Bounding boxes are converted to model coordinates (top-left origin).
( pdfDoc: PDFDocumentProxy, )
| 1077 | * from a PDF. Bounding boxes are converted to model coordinates (top-left origin). |
| 1078 | */ |
| 1079 | async function extractFormFieldInfo( |
| 1080 | pdfDoc: PDFDocumentProxy, |
| 1081 | ): Promise<FormFieldInfo[]> { |
| 1082 | const fields: FormFieldInfo[] = []; |
| 1083 | for (let i = 1; i <= pdfDoc.numPages; i++) { |
| 1084 | const page = await pdfDoc.getPage(i); |
| 1085 | const pageHeight = page.getViewport({ scale: 1.0 }).height; |
| 1086 | const annotations = await page.getAnnotations(); |
| 1087 | for (const ann of annotations) { |
| 1088 | // Only include form widgets (annotationType 20) |
| 1089 | if (ann.annotationType !== 20) continue; |
| 1090 | if (!ann.rect) continue; |
| 1091 | |
| 1092 | const fieldName = ann.fieldName || ""; |
| 1093 | const fieldType = ann.fieldType || "unknown"; |
| 1094 | |
| 1095 | // PDF rect is [x1, y1, x2, y2] in bottom-left origin |
| 1096 | const x1 = Math.min(ann.rect[0], ann.rect[2]); |
| 1097 | const y1 = Math.min(ann.rect[1], ann.rect[3]); |
| 1098 | const x2 = Math.max(ann.rect[0], ann.rect[2]); |
| 1099 | const y2 = Math.max(ann.rect[1], ann.rect[3]); |
| 1100 | const width = x2 - x1; |
| 1101 | const height = y2 - y1; |
| 1102 | |
| 1103 | // Convert to model coords (top-left origin): modelY = pageHeight - pdfY - height |
| 1104 | const modelY = pageHeight - y2; |
| 1105 | |
| 1106 | // Choice widgets (combo/listbox) carry `options` as |
| 1107 | // [{exportValue, displayValue}]. Expose export values — that's |
| 1108 | // what fill_form needs. |
| 1109 | let options: string[] | undefined; |
| 1110 | if (Array.isArray(ann.options) && ann.options.length > 0) { |
| 1111 | options = ann.options |
| 1112 | .map((o: { exportValue?: string }) => o?.exportValue) |
| 1113 | .filter((v: unknown): v is string => typeof v === "string"); |
| 1114 | } |
| 1115 | |
| 1116 | fields.push({ |
| 1117 | name: fieldName, |
| 1118 | type: fieldType, |
| 1119 | page: i, |
| 1120 | x: Math.round(x1), |
| 1121 | y: Math.round(modelY), |
| 1122 | width: Math.round(width), |
| 1123 | height: Math.round(height), |
| 1124 | ...(ann.alternativeText ? { label: ann.alternativeText } : undefined), |
| 1125 | // Radio: buttonValue is the per-widget export value — the only |
| 1126 | // thing distinguishing three `size [Btn]` lines from each other. |
| 1127 | ...(ann.radioButton && ann.buttonValue != null |
| 1128 | ? { exportValue: String(ann.buttonValue) } |
| 1129 | : undefined), |
| 1130 | ...(options?.length ? { options } : undefined), |
| 1131 | }); |
| 1132 | } |
| 1133 | } |
| 1134 | |
| 1135 | return fields; |
| 1136 | } |
no outgoing calls
no test coverage detected
searching dependent graphs…