From 65743d85627ca910ac57dffb61a389b402fa7d85 Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 28 May 2025 09:09:41 -0600 Subject: [PATCH] One day I'll figure this shit out --- cli/TerminalLayout.ts | 1 - cli/selectMenu.ts | 3 +- deno.json | 2 +- testing/test.pdf | Bin 1915504 -> 1920544 bytes tools/fieldRename.ts | 643 +++++++++++++++++++++++++++++++++--------- util/logfile.ts | 2 +- 6 files changed, 512 insertions(+), 139 deletions(-) diff --git a/cli/TerminalLayout.ts b/cli/TerminalLayout.ts index 3c34a4c..5be729d 100644 --- a/cli/TerminalLayout.ts +++ b/cli/TerminalLayout.ts @@ -1,5 +1,4 @@ import { Cursor } from "./cursor.ts"; -import { InputManager } from "./InputManager.ts"; export class TerminalLayout { private static ALT_BUFFER_ENABLE = "\x1b[?1049h"; diff --git a/cli/selectMenu.ts b/cli/selectMenu.ts index d3740d8..5f23e32 100644 --- a/cli/selectMenu.ts +++ b/cli/selectMenu.ts @@ -1,5 +1,6 @@ import type { callback } from "../types.ts"; import { type CLICharEvent, InputManager } from "./InputManager.ts"; +import { cliLog } from "./prompts.ts"; import { colorize } from "./style.ts"; import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts"; @@ -299,7 +300,7 @@ if (import.meta.main) { "yuzu", "zucchini", ], { terminalBlock: block }); - // console.log(val); + cliLog(val || "No value"); // Deno.stdout.writeSync(new TextEncoder().encode("\x07")); } diff --git a/deno.json b/deno.json index c36933f..66b6252 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@bearmetal/pdf-tools", - "version": "1.0.8-h", + "version": "1.0.8-l", "license": "GPL 3.0", "tasks": { "dev": "deno run -A --env-file=.env main.ts", diff --git a/testing/test.pdf b/testing/test.pdf index 7ace2e1b7cbf2fc8e4cd9b61ff9d2f84e43c1256..1ef7c5788a707030895b1111bbc20c6f30b4ae78 100644 GIT binary patch delta 1706 zcmZux3s4kg9On@RcOGCy;R9VY3*@o8x4U<@aC5`yx$-y^jw5w$F1g1CoZRi=-kwT6 zPV%#05=YcR0}Cq=EuWCh*w{!@iH>QRXfsZNp`|gEV7@5FcJEHmiP@R&`~Khmd;I>t z{e8b%e+w)IJqOKDlj9+60?U z=M{Kh^C;TyM=y$qQJZmfCW&R@TNle~MxypUc|!P0ga%+ZZBwPw4s}^Nslq|JO^vJ4 zF||rXYm^#?8aTF2R-~p?Im^=BE<2K3AqZ98OcX752^BtjhJ*8WlNG%3^GMWS2%Mo0On9IcZ@)VRoNR%<7=9OqczE-Q#p@j%Z zh$$0VXyXAZAUL54geKq+EFujV7_Af!P-iF!5+OCAn4~k5euA_PbF|RqEC&K=POjqI zE=Tne*5|PUJ|)PTo@F^fr&A1rlx}>QaBP@U*{Efap&b?~fEJ-h#4n;oDCyscT>vyf zse_Rw*wKMbg#|iIaET_Eql1$;jt7!K-b~gJh)-yOak&M+qm0`o00VGvP7shuoTw9= zB!_`F&L5xDF#1oCt`60k?{a!AkFOgcJpf#!j}s~YUnp_t0$vMdO?i?wY0=Zi1ZuKM zwDT^Hjjzu2vtAz$oKJ0&Dh_QMr4+`+dj&*EJ(fO65$JqTB>}}RurDnZJi{f*Dz1EL zD&T&>;vc4ZEBP@0o7o#SS41q0YpdnkWNoM4n&dwsTefX`$-SPxG_fVSe|Df#&YfA+ zH_&9u)|*OKM{HT&GA$>(Q;^-J7hn8Lm4v+<-!Y@%K>a>X_Sl+N)p0|&vwvv9Vy|xe zpo_T<1{VlT9XU~We_dAh&B~^|tZqqK*ZiihlUtmR_7v5oU9i_2oVP#ExVJxX>uame zrNp&e^Ec<_T^9_mtk`ztLF{e|x1-9g1Y+PGnVAp4Zy*0Q?vr~Z%P*p!=-$-&#wpe2ZtgpSI_oc*fH(K=GDD-qHMqN zXO8>68k_{SXz`dsQ}pj2Y@Jov9e!Zboj-byU55V@F|`MTt8l{dQ`F#^#y6&3-rbZl zZq2;=$i|)Jcl{rp>CDT&`}>cZ_sEY<+}YLd9m)sq4hXyAzei_2`n*LPf-BPHCP&LnxyG|twE(B zsJVKEnN1LL=8}4vq;Q6z^~`JoHJfA%IxCvXI>AZ{j))&6HY6rBN<_3A0!L^mU&cwZ zl0${OSw%}IL|3D6l2$5F6UPbC9%)Ij>vbSLN=#C(UvzF0C_@*D#}f!{eNx4CyS?;= zy>tkzywhA$H!y(bGA&_|ksJR#d7|)cV_nh3hY68|a9K>P6N_kPk)k_Tl98BQw|7_OofvY9jIE?^Y0{{V5MSNZ?| delta 67 zcmV~$yA?wK006*;BoaZs65mBkrI226V<-%f!4w8?x6HqF8z+HK pattern.test(s)); - let cField: PDFAcroField | undefined = field.acroField; - while (cField) { - if ( - cField.getPartialName() && - matchingSegments.includes(cField.getPartialName()!) - ) { - const mName = cField.getPartialName()?.replace(pattern, change); - if (mName) { - cField.dict.set(PDFName.of("T"), PDFString.of(mName)); - // console.log(cField.getPartialName()) - } - } - cField = cField.getParent(); - // console.log(cField?.getPartialName()) + const maybeKids = field.acroField.dict.get(PDFName.of("Kids")); + if (!maybeKids || !(maybeKids instanceof PDFArray)) return; + const kids = maybeKids; + if (!kids) return; + + const widgetRef = getWidgetRef(widget, doc); + if (!widgetRef) return; + + const updatedKids = kids.asArray().filter((ref) => { + const dict = doc.context.lookup(ref); + return dict !== widget.dict; + }); + + if (updatedKids.length === 0) { + // Field is now empty, remove it from the AcroForm + const acroForm = doc.catalog.lookup(PDFName.of("AcroForm"), PDFDict); + const fields = acroForm.lookup(PDFName.of("Fields"), PDFArray); + const fieldRef = field.acroField.ref; + const newFields = fields.asArray().filter((ref) => ref !== fieldRef); + acroForm.set(PDFName.of("Fields"), doc.context.obj(newFields)); + } else { + field.acroField.dict.set(PDFName.of("Kids"), doc.context.obj(updatedKids)); } } -// function applyWidgetRename( -// doc: PDFDocument, -// field: PDFField, -// widget: PDFWidgetAnnotation, -// name: string, -// pattern: RegExp, -// change: string, -// ) { -// if (field.acroField.getWidgets().length > 1) { -// const widgets = field.acroField.getWidgets(); -// const widgetIndex = widgets.indexOf(widget); -// widgets.splice(widgetIndex, 1); +function moveWidgetToFlatField( + doc: PDFDocument, + field: PDFField, + widget: PDFWidgetAnnotation, + newName: string, +) { + const form = doc.getForm(); + const page = findPageForWidget(doc, widget); + if (!page) throw new Error("Widget's page not found"); -// const pdfDocContext = doc.context; + const rect = widget.getRectangle(); + if (!rect) throw new Error("Widget has no rectangle"); -// const originalRef = field.acroField.ref; -// const originalFieldDict = pdfDocContext.lookup(originalRef); -// if (!originalFieldDict) return; + const fieldType = detectFieldType(field); + const widgetRef = getWidgetRef(widget, doc); + if (!widgetRef) throw new Error("Widget ref not found"); -// const newFieldDict = pdfDocContext.obj({ -// ...originalFieldDict, -// T: PDFString.of(name.replace(pattern, change)), -// Kids: [getWidgetRef(widget, doc.getPages())], -// }); -// const newField = pdfDocContext.register(newFieldDict); + // 🔒 Extract value + style before any destructive ops + let value: string | undefined; + try { + if (fieldType === "/Tx" && field instanceof PDFTextField) { + value = field.getText(); + } + } catch (_) { + log("Failed to extract value from field"); + } + + const sourceFieldDict = field.acroField.dict; + const sourceWidgetDict = widget.dict; + + // 🔥 Remove widget from page + field + removeWidgetFromPage(widget, doc); + removeWidgetCompletely(doc, widget, field); + + // 🔥 Carefully remove field + parents + try { + fullyDeleteFieldHierarchy(doc, field); + } catch (_) { + // fallback + log("Failed to remove field hierarchy"); + removeFieldIfEmpty(doc, field); + } + + sanitizeFieldsTree(doc); + removeDanglingParents(doc); + removeEmptyAncestors(doc, field); + + // 🔁 Create replacement field + let newField: PDFField; + + switch (fieldType) { + case "/Tx": { + const tf = form.createTextField(newName); + if (value) tf.setText(value); + tf.addToPage(page, rect); + newField = tf; + break; + } + + case "/Btn": { + const isRadio = getFlag(field, 15); + if (isRadio) { + const rg = form.createRadioGroup(newName); + rg.addOptionToPage(newName, page, rect); + return; + } else { + const cb = form.createCheckBox(newName); + cb.addToPage(page, rect); + if (field instanceof PDFCheckBox && field.isChecked()) { + cb.check(); + } + return; + } + } + + case "/Ch": { + const ff = sourceFieldDict.get(PDFName.of("Ff")); + const isCombo = ff instanceof PDFNumber && + ((ff.asNumber() & (1 << 17)) !== 0); + const opts = sourceFieldDict.lookupMaybe(PDFName.of("Opt"), PDFArray); + const values = + opts?.asArray().map((opt) => + opt instanceof PDFString || opt instanceof PDFHexString + ? opt.decodeText() + : "" + ) ?? []; + + if (isCombo) { + const dd = form.createDropdown(newName); + dd.addOptions(values); + dd.addToPage(page, rect); + newField = dd; + } else { + const ol = form.createOptionList(newName); + ol.addOptions(values); + ol.addToPage(page, rect); + newField = ol; + } + break; + } + + default: + throw new Error(`Unsupported field type: ${fieldType}`); + } + + // 🔧 Apply styles *after creation* + const targetWidgetDict = newField.acroField.getWidgets()[0].dict; + copyFieldAndWidgetStyles( + sourceFieldDict, + sourceWidgetDict, + newField.acroField.dict, + targetWidgetDict, + ); +} + +function removeDanglingParents(doc: PDFDocument) { + const context = doc.context; + const acroForm = doc.catalog.lookup(PDFName.of("AcroForm"), PDFDict); + const fields = acroForm.lookupMaybe(PDFName.of("Fields"), PDFArray); + if (!(fields instanceof PDFArray)) return; + + function fixFieldDict(dict: PDFDict) { + const parentRef = dict.get(PDFName.of("Parent")); + if (!parentRef || !(parentRef instanceof PDFRef)) return; + + try { + const parentDict = context.lookup(parentRef, PDFDict); + if (!parentDict) throw new Error("Missing parent"); + } catch { + // Parent is broken — remove reference + dict.delete(PDFName.of("Parent")); + log("Broken parent reference removed"); + } + } + + const visited = new Set(); + + function recurseKids(dict: PDFDict) { + const kids = dict.lookupMaybe(PDFName.of("Kids"), PDFArray); + if (!(kids instanceof PDFArray)) return; + + for (const kidRef of kids.asArray()) { + if (!(kidRef instanceof PDFRef)) continue; + const key = kidRef.toString(); + if (visited.has(key)) continue; + visited.add(key); + + try { + const kidDict = context.lookup(kidRef, PDFDict); + fixFieldDict(kidDict); + recurseKids(kidDict); + } catch (e) { + context.delete(kidRef); // nuke broken reference + log("Broken kid reference removed"); + log(e); + } + } + } + + for (const ref of fields.asArray()) { + if (!(ref instanceof PDFRef)) continue; + try { + const dict = context.lookup(ref, PDFDict); + fixFieldDict(dict); + recurseKids(dict); + } catch { + context.delete(ref); // broken root + log("Broken root reference removed"); + } + } +} + +function removeFieldByName(doc: PDFDocument, fieldName: string) { + const form = doc.getForm(); + const acroForm = doc.catalog.lookup(PDFName.of("AcroForm"), PDFDict); + const fields = acroForm.lookup(PDFName.of("Fields"), PDFArray); + const context = doc.context; + + const remainingFields = fields.asArray().filter((ref) => { + const dict = context.lookup(ref, PDFDict); + const name = dict?.get(PDFName.of("T")); + + if (name && (name.decodeText?.() === fieldName)) { + context.delete(ref as PDFRef); + return false; + } + + return true; + }); + + acroForm.set(PDFName.of("Fields"), context.obj(remainingFields)); +} + +function sanitizeFieldsTree(doc: PDFDocument) { + const context = doc.context; + const acroForm = doc.catalog.lookup(PDFName.of("AcroForm"), PDFDict); + const fields = acroForm.lookupMaybe(PDFName.of("Fields"), PDFArray); + if (!(fields instanceof PDFArray)) return; + + function pruneInvalidKids(dict: PDFDict, context: PDFContext) { + const kids = dict.lookupMaybe(PDFName.of("Kids"), PDFArray); + if (!(kids instanceof PDFArray)) return; + + const validKids: PDFRef[] = []; + + for (const ref of kids.asArray()) { + // 💥 Defensive: skip anything that's not a real PDFRef + if (!ref || !(ref instanceof PDFRef)) continue; + + let child: PDFDict | undefined; + try { + child = context.lookup(ref, PDFDict); + } catch (e) { + context.delete(ref); + log("Broken kid reference removed"); + log(e); + continue; + } + + if (!child) { + context.delete(ref); + continue; + } + + const t = child.get(PDFName.of("T")); + if (!(t instanceof PDFString || t instanceof PDFHexString)) { + context.delete(ref); + continue; + } + + // Recurse, but protect inner layers too + pruneInvalidKids(child, context); + validKids.push(ref); + } + + if (validKids.length > 0) { + dict.set(PDFName.of("Kids"), context.obj(validKids)); + } else { + dict.delete(PDFName.of("Kids")); + } + } + + const validFields: PDFRef[] = []; + + for (const ref of fields.asArray()) { + if (!ref || !(ref instanceof PDFRef)) continue; + + let dict: PDFDict | undefined; + try { + dict = context.lookup(ref, PDFDict); + } catch { + context.delete(ref); + log("Broken field reference removed"); + continue; + } + + if (!dict) { + context.delete(ref); + continue; + } + + const t = dict.get(PDFName.of("T")); + if (!(t instanceof PDFString || t instanceof PDFHexString)) { + context.delete(ref); + continue; + } + + pruneInvalidKids(dict, context); + validFields.push(ref); + } + + acroForm.set(PDFName.of("Fields"), context.obj(validFields)); +} + +function fullyDeleteFieldHierarchy(doc: PDFDocument, rootField: PDFField) { + const context = doc.context; + const acroForm = doc.catalog.lookup(PDFName.of("AcroForm"), PDFDict); + const fields = acroForm.lookup(PDFName.of("Fields"), PDFArray); + + function recurseDelete(dict: PDFDict, ref: PDFRef) { + const kids = dict.lookupMaybe(PDFName.of("Kids"), PDFArray); + + if (kids instanceof PDFArray) { + for (const kidRef of kids.asArray()) { + const kidDict = context.lookup(kidRef, PDFDict); + if (kidDict) { + recurseDelete(kidDict, kidRef as PDFRef); + } + } + } + + context.delete(ref); + } + + recurseDelete(rootField.acroField.dict, rootField.acroField.ref); + + // Remove root from AcroForm.Fields + const newFields = fields + .asArray() + .filter((ref) => ref !== rootField.acroField.ref); + + acroForm.set(PDFName.of("Fields"), context.obj(newFields)); +} + +function removeEmptyAncestors(doc: PDFDocument, field: PDFField) { + let current: PDFAcroField | undefined = field.acroField; + const context = doc.context; + + while (current) { + const parent = current.getParent(); + + const kids = parent?.dict.lookupMaybe(PDFName.of("Kids"), PDFArray); + if (kids instanceof PDFArray) { + const remaining = kids.asArray().filter((ref) => { + try { + const kidDict = context.lookup(ref, PDFDict); + return kidDict !== current?.dict; + } catch (e) { + log("Broken kid reference removed"); + log(e); + return false; + } + }); + + if (remaining.length > 0) { + parent.dict.set(PDFName.of("Kids"), context.obj(remaining)); + break; + } else { + parent.dict.delete(PDFName.of("Kids")); + } + } + + context.delete(current.ref); + current = parent; + } +} + +function removeWidgetCompletely( + doc: PDFDocument, + widget: PDFWidgetAnnotation, + field: PDFField, +) { + const widgetRef = getWidgetRef(widget, doc); + if (!widgetRef) return; + + // 1. Remove from field's /Kids array + const kidsRaw = field.acroField.dict.get(PDFName.of("Kids")); + if (kidsRaw instanceof PDFArray) { + const updatedKids = kidsRaw.asArray().filter((ref) => { + const dict = doc.context.lookup(ref); + return dict !== widget.dict; + }); + + if (updatedKids.length > 0) { + field.acroField.dict.set( + PDFName.of("Kids"), + doc.context.obj(updatedKids), + ); + } else { + field.acroField.dict.delete(PDFName.of("Kids")); + } + } + + // 2. Remove from page /Annots + for (const page of doc.getPages()) { + const annotsRaw = page.node.Annots()?.asArray(); + if (!annotsRaw) continue; + + const remainingAnnots = annotsRaw.filter((ref) => { + const dict = doc.context.lookup(ref); + return dict !== widget.dict; + }); + + page.node.set(PDFName.of("Annots"), doc.context.obj(remainingAnnots)); + } + + // Optional: delete the widget from the context + doc.context.delete(widgetRef); +} +function removeFieldIfEmpty(doc: PDFDocument, field: PDFField) { + const kids = field.acroField.getWidgets(); + if (kids.length > 0) return; + + const acroForm = doc.catalog.lookup(PDFName.of("AcroForm"), PDFDict); + const fieldsArray = acroForm.lookup(PDFName.of("Fields"), PDFArray); + const ref = field.acroField.ref; + + const updatedFields = fieldsArray.asArray().filter((f) => f !== ref); + acroForm.set(PDFName.of("Fields"), doc.context.obj(updatedFields)); + + // Optional: remove field object entirely + doc.context.delete(ref); +} + +function copyFieldAndWidgetStyles( + sourceFieldDict: PDFDict, + sourceWidgetDict: PDFDict, + targetFieldDict: PDFDict, + targetWidgetDict: PDFDict, +) { + const fieldKeys = ["DA", "DR", "Q"]; + const widgetKeys = ["MK", "BS", "Border"]; + + // Copy from field dict → field dict + for (const key of fieldKeys) { + const val = sourceFieldDict.get(PDFName.of(key)); + if (val) { + targetFieldDict.set(PDFName.of(key), val); + } + } + + // Copy from widget dict → widget dict + for (const key of widgetKeys) { + const val = sourceWidgetDict.get(PDFName.of(key)); + if (val) { + targetWidgetDict.set(PDFName.of(key), val); + } + } +} -// const acroForm = doc.catalog.lookup(PDFName.of("AcroForm"), PDFDict); -// const fields = acroForm.lookup(PDFName.of("Fields"), PDFArray); -// fields.push(newField); -// } -// } function findPageForWidget( doc: PDFDocument, widget: PDFWidgetAnnotation, @@ -134,19 +534,22 @@ function applyWidgetRename( try { const form = doc.getForm(); const widgets = field.acroField.getWidgets(); - - if (widgets.length <= 1) return; const widgetDict = widget.dict; const widgetIndex = widgets.findIndex((w) => w.dict === widgetDict); if (widgetIndex === -1) return; + const widgetRef = getWidgetRef(widget, doc); + if (!widgetRef) return; + + // Remove widget from internal widgets list widgets.splice(widgetIndex, 1); - const kids = field.acroField.dict.lookup(PDFName.of("Kids"), PDFArray); - if (kids) { - const updatedKids = kids.asArray().filter((ref) => { + // Remove from /Kids + const maybeKids = field.acroField.dict.get(PDFName.of("Kids")); + if (maybeKids instanceof PDFArray) { + const updatedKids = maybeKids.asArray().filter((ref) => { const maybeDict = doc.context.lookup(ref); - return maybeDict !== widget.dict; + return maybeDict !== widgetDict; }); field.acroField.dict.set( PDFName.of("Kids"), @@ -155,48 +558,41 @@ function applyWidgetRename( } const page = findPageForWidget(doc, widget); - if (!page) throw new Error("Widget page not found"); + if (!page) throw new Error("Widget's page not found"); const rect = widget.getRectangle(); if (!rect) throw new Error("Widget has no rectangle"); const finalName = newName.replace(pattern, change); + const fieldType = detectFieldType(field); - // Try to get existing field with the new name + // Attempt to find an existing field with the new name let targetField: PDFField | undefined; - try { targetField = form.getField(finalName); } catch { - // Field doesn't exist — that's fine + // + log("Failed to find existing field"); } - // Compare field types if field exists if (targetField) { const sourceType = detectFieldType(field); const targetType = detectFieldType(targetField); - if (sourceType !== targetType) { throw new Error( `Field "${finalName}" already exists with a different type (${targetType} vs ${sourceType})`, ); } - // ✅ Same type — attach widget to the existing field - // const targetFieldWidgets = targetField.acroField.getWidgets(); - const targetKidsArray = targetField.acroField.dict.lookup( + // Add widget to existing field + widget.dict.set(PDFName.of("Parent"), targetField.acroField.ref); + + const kids = targetField.acroField.dict.lookup( PDFName.of("Kids"), PDFArray, ); - - // Set /Parent on the widget to point to the existing field - widget.dict.set(PDFName.of("Parent"), targetField.acroField.ref); - - // Add the widget to the field's /Kids array - const widgetRef = getWidgetRef(widget, doc); - if (!widgetRef) throw new Error("Widget ref not found"); - if (targetKidsArray) { - targetKidsArray.push(widgetRef); + if (kids) { + kids.push(widgetRef); } else { targetField.acroField.dict.set( PDFName.of("Kids"), @@ -204,22 +600,23 @@ function applyWidgetRename( ); } - // Also ensure widget is attached to a page - const page = findPageForWidget(doc, widget); - if (!page) throw new Error("Widget's page not found"); - - const pageAnnots = page.node.Annots(); - const refs = pageAnnots?.asArray() ?? []; - if (!refs.includes(widgetRef)) { - refs.push(widgetRef); - page.node.set(PDFName.of("Annots"), doc.context.obj(refs)); + const annots = page.node.Annots()?.asArray() ?? []; + if (!annots.includes(widgetRef)) { + annots.push(widgetRef); + page.node.set(PDFName.of("Annots"), doc.context.obj(annots)); } - return; // Done - } - removeWidgetFromPage(widget, doc); + removeWidgetFromPage(widget, doc); + removeWidgetCompletely(doc, widget, field); + removeFieldIfEmpty(doc, field); - const fieldType = detectFieldType(field); + return; + } + + // No existing field — create new one and move widget + removeWidgetFromPage(widget, doc); + removeWidgetCompletely(doc, widget, field); + removeFieldIfEmpty(doc, field); let newField: PDFField; @@ -230,6 +627,12 @@ function applyWidgetRename( const val = field.getText(); if (val) tf.setText(val); } + tf.addToPage(page, { + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + }); newField = tf; break; } @@ -237,8 +640,8 @@ function applyWidgetRename( case "/Btn": { const isRadio = getFlag(field, 15); if (isRadio) { - const rf = form.createRadioGroup(finalName); - rf.addOptionToPage(finalName, page, { + const radio = form.createRadioGroup(finalName); + radio.addOptionToPage(finalName, page, { x: rect.x, y: rect.y, width: rect.width, @@ -246,7 +649,7 @@ function applyWidgetRename( }); if (field instanceof PDFRadioGroup) { const selected = field.getSelected(); - if (selected) rf.select(selected); + if (selected) radio.select(selected); } return; } else { @@ -268,20 +671,15 @@ function applyWidgetRename( throw new Error(`Unsupported field type: ${fieldType}`); } - // Attach the new field to the page if necessary - if ( - newField instanceof PDFTextField || - newField instanceof PDFCheckBox - ) { - newField.addToPage(page, { - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - }); - } - } catch { - // log(e); + // Apply styles from old field/widget after creation + copyFieldAndWidgetStyles( + field.acroField.dict, + widget.dict, + newField.acroField.dict, + newField.acroField.getWidgets()[0].dict, + ); + } catch (e) { + log("applyWidgetRename error:", e); } } @@ -304,36 +702,6 @@ function removeWidgetFromPage(widget: PDFWidgetAnnotation, doc: PDFDocument) { } } -// function getWidgetRef( -// widget: PDFWidgetAnnotation, -// pages: PDFPage[], -// ): PDFRef | undefined { -// const widgetRect = (widget?.dict?.get(PDFName.of("Rect")) as PDFArray) -// ?.asArray(); -// const widgetFT = (widget?.dict?.get(PDFName.of("FT")) as PDFString) -// ?.["value"]; - -// for (const page of pages) { -// const annotsArray = page.node.Annots()?.asArray(); -// if (!annotsArray) continue; - -// for (const annotRef of annotsArray) { -// const annotDict = page.doc.context.lookup(annotRef); -// if (!annotDict) continue; -// if (!(annotDict instanceof PDFDict)) continue; -// const rect = (annotDict.get(PDFName.of("Rect")) as PDFArray)?.asArray(); -// const ft = (annotDict.get(PDFName.of("FT")) as PDFString)?.["value"]; - -// // rudimentary match (you can add more checks like /T, /Subtype, etc.) -// if (rect?.toString() === widgetRect?.toString() && ft === widgetFT) { -// return annotRef as PDFRef; -// } -// } -// } - -// return undefined; -// } - /*** * Evaluates the change string with the match array * @@ -501,7 +869,12 @@ class RenameFields implements ITool { new RegExp(patternRegex), toChange, ) - : applyRename(field, name, patternRegex, toChange); + : moveWidgetToFlatField( + pdf, + field, + field.acroField.getWidgets()[0], + preview, + ); changesMade = true; }, ]; @@ -527,7 +900,7 @@ class RenameFields implements ITool { try { await savePdf(pdf, path || pdfPath); } catch { - // log(e); + log(e); } } else { cliLog("No changes made, skipping", this.block); diff --git a/util/logfile.ts b/util/logfile.ts index a101562..2e90588 100644 --- a/util/logfile.ts +++ b/util/logfile.ts @@ -7,7 +7,7 @@ const logFile = Deno.openSync("./log.txt", { logFile.truncateSync(0); -export function log(message: any) { +export function log(...message: any) { if (typeof message === "object") { message = Deno.inspect(message); }