diff --git a/src/core/editor/pdf_editor.js b/src/core/editor/pdf_editor.js index d8e964254..9aa0d7fbd 100644 --- a/src/core/editor/pdf_editor.js +++ b/src/core/editor/pdf_editor.js @@ -263,7 +263,7 @@ class PDFEditor { ) { if (obj instanceof Ref) { const { - currentDocument: { oldRefMapping }, + currentDocument: { fieldToParent, oldRefMapping }, } = this; const existingRef = oldRefMapping.get(obj); if (existingRef) { @@ -305,9 +305,17 @@ class PDFEditor { } } + let cloneSource = true; + if (fieldToParent.has(oldRef) && obj instanceof Dict) { + // Avoid following a widget's field hierarchy while cloning the page, + // without mutating the source dictionary cached by its XRef. + obj = this.cloneDict(obj); + obj.delete("Parent"); + cloneSource = false; + } this.xref[newRef.num] = await this.#collectDependencies( obj, - true, + cloneSource, xref, resourceStreamPath ); @@ -1229,11 +1237,8 @@ class PDFEditor { "Sig" ); const parentRef = annotationDict.getRaw("Parent") || null; - // We remove the parent to avoid visiting it when cloning the - // annotation. - // It'll be fixed later in #mergeAcroForms when merging the - // AcroForms. - annotationDict.delete("Parent"); + // The parent will be omitted from the annotation clone to avoid + // visiting it, then restored by #mergeAcroForms. fieldToParent.put(annotationRef, parentRef); } diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index a2bacec90..28551f2a2 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -7694,6 +7694,36 @@ small scripts as well as for`); return fontIndex < 0 ? null : operatorList.argsArray[fontIndex][0]; }; + it("does not mutate source widget parents", async function () { + const pdfData = assemblePdf([ + "1 0 obj\n<< /Type /Catalog /Pages 2 0 R " + + "/AcroForm 6 0 R >>\nendobj\n", + "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n", + "3 0 obj\n<< /Type /Page /Parent 2 0 R " + + "/MediaBox [0 0 100 100] /Annots [4 0 R] >>\nendobj\n", + "4 0 obj\n<< /Type /Annot /Subtype /Widget /Rect [0 0 20 10] " + + "/Parent 5 0 R >>\nendobj\n", + "5 0 obj\n<< /FT /Tx /T (group) /Kids [4 0 R] >>\nendobj\n", + "6 0 obj\n<< /Fields [] /DA (/Helv 10 Tf) >>\nendobj\n", + ]); + + let loadingTask = getDocument({ data: pdfData }); + let pdfDoc = await loadingTask.promise; + const data = await pdfDoc.extractPages([{ document: null }]); + + const sourceAnnotations = await ( + await pdfDoc.getPage(1) + ).getAnnotations(); + expect(sourceAnnotations[0].fieldName).toEqual("group"); + expect(sourceAnnotations[0].fieldType).toEqual("Tx"); + await loadingTask.destroy(); + + loadingTask = getDocument({ data }); + pdfDoc = await loadingTask.promise; + expect(pdfDoc.numPages).toEqual(1); + await loadingTask.destroy(); + }); + it("rebuilds a missing AcroForm Fields array", async function () { const data = assemblePdf([ "1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n",