diff --git a/src/core/annotation.js b/src/core/annotation.js index 8f415e9c050a5..825e9247a9a72 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -112,6 +112,7 @@ class AnnotationFactory { * @params {Object} annotationGlobals * @param {Object} idFactory * @param {boolean} [collectFields] + * @param {Object} [orphanFields] * @param {Object} [pageRef] * @returns {Promise} A promise that is resolved with an {Annotation} * instance. @@ -122,6 +123,7 @@ class AnnotationFactory { annotationGlobals, idFactory, collectFields, + orphanFields, pageRef ) { const pageIndex = collectFields @@ -134,6 +136,7 @@ class AnnotationFactory { annotationGlobals, idFactory, collectFields, + orphanFields, pageIndex, pageRef, ]); @@ -148,6 +151,7 @@ class AnnotationFactory { annotationGlobals, idFactory, collectFields = false, + orphanFields = null, pageIndex = null, pageRef = null ) { @@ -173,6 +177,7 @@ class AnnotationFactory { id, annotationGlobals, collectFields, + orphanFields, needAppearances: !collectFields && acroForm.get("NeedAppearances") === true, pageIndex, @@ -623,7 +628,11 @@ function getTransformMatrix(rect, bbox, matrix) { class Annotation { constructor(params) { - const { dict, xref, annotationGlobals } = params; + const { dict, xref, annotationGlobals, ref, orphanFields } = params; + const parentRef = orphanFields?.get(ref); + if (parentRef) { + dict.set("Parent", parentRef); + } this.setTitle(dict.get("T")); this.setContents(dict.get("Contents")); @@ -3172,6 +3181,11 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } } + if (!this.parent) { + // If there is no parent then we must set the value in the field. + dict.set("V", name); + } + dict.set("AS", name); dict.set("M", `D:${getModificationDate()}`); if (flags !== undefined) { diff --git a/src/core/document.js b/src/core/document.js index c484bae1d5df4..bff44ce1e4b89 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -787,12 +787,16 @@ class Page { if (annots.length === 0) { return annots; } - const annotationGlobals = - await this.pdfManager.ensureDoc("annotationGlobals"); + + const [annotationGlobals, fieldObjects] = await Promise.all([ + this.pdfManager.ensureDoc("annotationGlobals"), + this.pdfManager.ensureDoc("fieldObjects"), + ]); if (!annotationGlobals) { return []; } + const orphanFields = fieldObjects?.orphanFields; const annotationPromises = []; for (const annotationRef of annots) { annotationPromises.push( @@ -802,6 +806,7 @@ class Page { annotationGlobals, this._localIdFactory, /* collectFields */ false, + orphanFields, this.ref ).catch(function (reason) { warn(`_parsedAnnotations: "${reason}".`); @@ -1776,10 +1781,12 @@ class PDFDocument { async #collectFieldObjects( name, + parentRef, fieldRef, promises, annotationGlobals, - visitedRefs + visitedRefs, + orphanFields ) { const { xref } = this; @@ -1797,7 +1804,7 @@ class PDFDocument { } else { let obj = field; while (true) { - obj = obj.getRaw("Parent"); + obj = obj.getRaw("Parent") || parentRef; if (obj instanceof Ref) { if (visitedRefs.has(obj)) { break; @@ -1815,6 +1822,15 @@ class PDFDocument { } } + if ( + parentRef && + !field.has("Parent") && + isName(field.get("Subtype"), "Widget") + ) { + // We've a parent from the Fields array, but the field hasn't. + orphanFields.put(fieldRef, parentRef); + } + if (!promises.has(name)) { promises.set(name, []); } @@ -1825,6 +1841,7 @@ class PDFDocument { annotationGlobals, /* idFactory = */ null, /* collectFields */ true, + orphanFields, /* pageRef */ null ) .then(annotation => annotation?.getFieldObject()) @@ -1842,10 +1859,12 @@ class PDFDocument { for (const kid of kids) { await this.#collectFieldObjects( name, + fieldRef, kid, promises, annotationGlobals, - visitedRefs + visitedRefs, + orphanFields ); } } @@ -1867,13 +1886,16 @@ class PDFDocument { const visitedRefs = new RefSet(); const allFields = Object.create(null); const fieldPromises = new Map(); + const orphanFields = new RefSetCache(); for (const fieldRef of await acroForm.getAsync("Fields")) { await this.#collectFieldObjects( "", + null, fieldRef, fieldPromises, annotationGlobals, - visitedRefs + visitedRefs, + orphanFields ); } @@ -1890,7 +1912,7 @@ class PDFDocument { } await Promise.all(allPromises); - return allFields; + return { allFields, orphanFields }; }); return shadow(this, "fieldObjects", promise); @@ -1914,7 +1936,7 @@ class PDFDocument { return true; } if (fieldObjects) { - return Object.values(fieldObjects).some(fieldObject => + return Object.values(fieldObjects.allFields).some(fieldObject => fieldObject.some(object => object.actions !== null) ); } diff --git a/src/core/worker.js b/src/core/worker.js index 3764b12edcda6..a8e0c42306233 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -522,7 +522,9 @@ class WorkerMessageHandler { }); handler.on("GetFieldObjects", function (data) { - return pdfManager.ensureDoc("fieldObjects"); + return pdfManager + .ensureDoc("fieldObjects") + .then(fieldObjects => fieldObjects?.allFields || null); }); handler.on("HasJSActions", function (data) { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 001000de0b98c..d74a670c356d7 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -673,3 +673,4 @@ !highlight_popup.pdf !issue18072.pdf !stamps.pdf +!issue15096.pdf diff --git a/test/pdfs/issue15096.pdf b/test/pdfs/issue15096.pdf new file mode 100755 index 0000000000000..35ac6288ff459 Binary files /dev/null and b/test/pdfs/issue15096.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 85e2b1058f9d4..044fb9825838b 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -10667,5 +10667,22 @@ "popupRef": "44R" } } + }, + { + "id": "issue15096", + "file": "pdfs/issue15096.pdf", + "md5": "5c3515177acd6e146d177adac802277d", + "rounds": 1, + "type": "eq", + "save": true, + "annotations": true, + "annotationStorage": { + "62R": { + "value": true + }, + "66R": { + "value": false + } + } } ] diff --git a/test/unit/document_spec.js b/test/unit/document_spec.js index 94de57dfbd22c..b0224e8821d59 100644 --- a/test/unit/document_spec.js +++ b/test/unit/document_spec.js @@ -250,7 +250,7 @@ describe("document", function () { acroForm.set("Fields", [parentRef]); pdfDocument = getDocument(acroForm, xref); - fields = await pdfDocument.fieldObjects; + fields = (await pdfDocument.fieldObjects).allFields; for (const [name, objs] of Object.entries(fields)) { fields[name] = objs.map(obj => obj.id);