diff --git a/apps/deno/tests/test1.ts b/apps/deno/tests/test1.ts index 3627f3701..fe71ed5c7 100644 --- a/apps/deno/tests/test1.ts +++ b/apps/deno/tests/test1.ts @@ -623,6 +623,29 @@ export default async (assets: Assets) => { font: ubuntuFont, }); + page5.drawText('There should be no remnant of a field\nbelow this text!!', { + y: size - fMax * 5 - fPadding * 0 - fHeight * 3, + x: fPadding, + size: 18, + font: indieFlowerFont, + lineHeight: 18, + }); + const textField = form.createTextField('a.new.text.field'); + textField.setText('This Should Not Be Visible'); + textField.addToPage(page5, { + x: fPadding, + y: size - fMax * 5 - fPadding * 3.5 - fHeight * 3, + width: fWidth * 2.5, + height: fHeight * 1, + borderWidth: 4, + backgroundColor: pastels.pinkish, + borderColor: pastels.blue, + textColor: pastels.darkBlue, + font: ubuntuFont, + }); + + form.removeField(textField); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/apps/node/tests/test1.ts b/apps/node/tests/test1.ts index 62c11892d..1fc9f74db 100644 --- a/apps/node/tests/test1.ts +++ b/apps/node/tests/test1.ts @@ -621,6 +621,29 @@ export default async (assets: Assets) => { font: ubuntuFont, }); + page5.drawText('There should be no remnant of a field\nbelow this text!!', { + y: size - fMax * 5 - fPadding * 0 - fHeight * 3, + x: fPadding, + size: 18, + font: indieFlowerFont, + lineHeight: 18, + }); + const textField = form.createTextField('a.new.text.field'); + textField.setText('This Should Not Be Visible'); + textField.addToPage(page5, { + x: fPadding, + y: size - fMax * 5 - fPadding * 3.5 - fHeight * 3, + width: fWidth * 2.5, + height: fHeight * 1, + borderWidth: 4, + backgroundColor: pastels.pinkish, + borderColor: pastels.blue, + textColor: pastels.darkBlue, + font: ubuntuFont, + }); + + form.removeField(textField); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/apps/rn/src/tests/test1.js b/apps/rn/src/tests/test1.js index 73faeaab0..e7ebd36b3 100644 --- a/apps/rn/src/tests/test1.js +++ b/apps/rn/src/tests/test1.js @@ -656,6 +656,29 @@ export default async () => { font: ubuntuFont, }); + page5.drawText('There should be no remnant of a field\nbelow this text!!', { + y: size - fMax * 5 - fPadding * 0 - fHeight * 3, + x: fPadding, + size: 18, + font: indieFlowerFont, + lineHeight: 18, + }); + const textField = form.createTextField('a.new.text.field'); + textField.setText('This Should Not Be Visible'); + textField.addToPage(page5, { + x: fPadding, + y: size - fMax * 5 - fPadding * 3.5 - fHeight * 3, + width: fWidth * 2.5, + height: fHeight * 1, + borderWidth: 4, + backgroundColor: pastels.pinkish, + borderColor: pastels.blue, + textColor: pastels.darkBlue, + font: ubuntuFont, + }); + + form.removeField(textField); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/apps/web/test1.html b/apps/web/test1.html index 1dbc7d81b..5b881326d 100644 --- a/apps/web/test1.html +++ b/apps/web/test1.html @@ -733,6 +733,32 @@ font: ubuntuFont, }); + page5.drawText( + 'There should be no remnant of a field\nbelow this text!!', + { + y: size - fMax * 5 - fPadding * 0 - fHeight * 3, + x: fPadding, + size: 18, + font: indieFlowerFont, + lineHeight: 18, + }, + ); + const textField = form.createTextField('a.new.text.field'); + textField.setText('This Should Not Be Visible'); + textField.addToPage(page5, { + x: fPadding, + y: size - fMax * 5 - fPadding * 3.5 - fHeight * 3, + width: fWidth * 2.5, + height: fHeight * 1, + borderWidth: 4, + backgroundColor: pastels.pinkish, + borderColor: pastels.blue, + textColor: pastels.darkBlue, + font: ubuntuFont, + }); + + form.removeField(textField); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/src/api/form/PDFForm.ts b/src/api/form/PDFForm.ts index 74dbde470..dfb7d161c 100644 --- a/src/api/form/PDFForm.ts +++ b/src/api/form/PDFForm.ts @@ -596,6 +596,14 @@ export default class PDFForm { pages.forEach((page) => page.node.removeAnnot(field.ref)); this.acroForm.removeField(field.acroField); + const fieldKids = field.acroField.normalizedEntries().Kids; + const kidsCount = fieldKids.size(); + for (let childIndex = 0; childIndex < kidsCount; childIndex++) { + const child = fieldKids.get(childIndex); + if (child instanceof PDFRef) { + this.doc.context.delete(child); + } + } this.doc.context.delete(field.ref); } diff --git a/tests/api/form/PDFForm.spec.ts b/tests/api/form/PDFForm.spec.ts index 8475dd76f..63c8edbcc 100644 --- a/tests/api/form/PDFForm.spec.ts +++ b/tests/api/form/PDFForm.spec.ts @@ -328,6 +328,43 @@ describe(`PDFForm`, () => { rgWidgetRefs.forEach((ref) => expect(refs2).not.toContain(ref)); }); + it(`it cleans references of removed fields and their widgets when created with pdf-lib`, async () => { + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage(); + const form = pdfDoc.getForm(); + + const cb = form.createCheckBox('a.new.check.box'); + const tf = form.createTextField('a.new.text.field'); + + cb.addToPage(page); + tf.addToPage(page); + + const refs1 = getRefs(pdfDoc); + + const cbWidgetRefs = cb.acroField.normalizedEntries().Kids.asArray(); + const tfWidgetRefs = cb.acroField.normalizedEntries().Kids.asArray(); + + expect(cbWidgetRefs.length).toBeGreaterThan(0); + expect(tfWidgetRefs.length).toBeGreaterThan(0); + + // Assert that refs are present before their fields have been removed + expect(refs1.includes(cb.ref)).toBe(true); + expect(refs1.includes(tf.ref)).toBe(true); + cbWidgetRefs.forEach((ref) => expect(refs1).toContain(ref)); + tfWidgetRefs.forEach((ref) => expect(refs1).toContain(ref)); + + form.removeField(cb); + form.removeField(tf); + + const refs2 = getRefs(pdfDoc); + + // Assert that refs are not present after their fields have been removed + expect(refs2.includes(cb.ref)).toBe(false); + expect(refs2.includes(tf.ref)).toBe(false); + cbWidgetRefs.forEach((ref) => expect(refs2).not.toContain(ref)); + tfWidgetRefs.forEach((ref) => expect(refs2).not.toContain(ref)); + }); + // TODO: Add method to remove APs and use `NeedsAppearances`? How would this // work with RadioGroups? Just set the APs to `null`but keep the keys? });