Fix missing annotation parent in using the one from the Fields entry

Fixes #15096.
This commit is contained in:
Calixte Denizet 2024-10-03 21:48:58 +02:00
parent 7c1883a839
commit 3103deaa44
7 changed files with 67 additions and 11 deletions

View File

@ -112,6 +112,7 @@ class AnnotationFactory {
* @params {Object} annotationGlobals * @params {Object} annotationGlobals
* @param {Object} idFactory * @param {Object} idFactory
* @param {boolean} [collectFields] * @param {boolean} [collectFields]
* @param {Object} [orphanFields]
* @param {Object} [pageRef] * @param {Object} [pageRef]
* @returns {Promise} A promise that is resolved with an {Annotation} * @returns {Promise} A promise that is resolved with an {Annotation}
* instance. * instance.
@ -122,6 +123,7 @@ class AnnotationFactory {
annotationGlobals, annotationGlobals,
idFactory, idFactory,
collectFields, collectFields,
orphanFields,
pageRef pageRef
) { ) {
const pageIndex = collectFields const pageIndex = collectFields
@ -134,6 +136,7 @@ class AnnotationFactory {
annotationGlobals, annotationGlobals,
idFactory, idFactory,
collectFields, collectFields,
orphanFields,
pageIndex, pageIndex,
pageRef, pageRef,
]); ]);
@ -148,6 +151,7 @@ class AnnotationFactory {
annotationGlobals, annotationGlobals,
idFactory, idFactory,
collectFields = false, collectFields = false,
orphanFields = null,
pageIndex = null, pageIndex = null,
pageRef = null pageRef = null
) { ) {
@ -173,6 +177,7 @@ class AnnotationFactory {
id, id,
annotationGlobals, annotationGlobals,
collectFields, collectFields,
orphanFields,
needAppearances: needAppearances:
!collectFields && acroForm.get("NeedAppearances") === true, !collectFields && acroForm.get("NeedAppearances") === true,
pageIndex, pageIndex,
@ -623,7 +628,11 @@ function getTransformMatrix(rect, bbox, matrix) {
class Annotation { class Annotation {
constructor(params) { 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.setTitle(dict.get("T"));
this.setContents(dict.get("Contents")); 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("AS", name);
dict.set("M", `D:${getModificationDate()}`); dict.set("M", `D:${getModificationDate()}`);
if (flags !== undefined) { if (flags !== undefined) {

View File

@ -787,12 +787,16 @@ class Page {
if (annots.length === 0) { if (annots.length === 0) {
return annots; 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) { if (!annotationGlobals) {
return []; return [];
} }
const orphanFields = fieldObjects?.orphanFields;
const annotationPromises = []; const annotationPromises = [];
for (const annotationRef of annots) { for (const annotationRef of annots) {
annotationPromises.push( annotationPromises.push(
@ -802,6 +806,7 @@ class Page {
annotationGlobals, annotationGlobals,
this._localIdFactory, this._localIdFactory,
/* collectFields */ false, /* collectFields */ false,
orphanFields,
this.ref this.ref
).catch(function (reason) { ).catch(function (reason) {
warn(`_parsedAnnotations: "${reason}".`); warn(`_parsedAnnotations: "${reason}".`);
@ -1776,10 +1781,12 @@ class PDFDocument {
async #collectFieldObjects( async #collectFieldObjects(
name, name,
parentRef,
fieldRef, fieldRef,
promises, promises,
annotationGlobals, annotationGlobals,
visitedRefs visitedRefs,
orphanFields
) { ) {
const { xref } = this; const { xref } = this;
@ -1797,7 +1804,7 @@ class PDFDocument {
} else { } else {
let obj = field; let obj = field;
while (true) { while (true) {
obj = obj.getRaw("Parent"); obj = obj.getRaw("Parent") || parentRef;
if (obj instanceof Ref) { if (obj instanceof Ref) {
if (visitedRefs.has(obj)) { if (visitedRefs.has(obj)) {
break; 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)) { if (!promises.has(name)) {
promises.set(name, []); promises.set(name, []);
} }
@ -1825,6 +1841,7 @@ class PDFDocument {
annotationGlobals, annotationGlobals,
/* idFactory = */ null, /* idFactory = */ null,
/* collectFields */ true, /* collectFields */ true,
orphanFields,
/* pageRef */ null /* pageRef */ null
) )
.then(annotation => annotation?.getFieldObject()) .then(annotation => annotation?.getFieldObject())
@ -1842,10 +1859,12 @@ class PDFDocument {
for (const kid of kids) { for (const kid of kids) {
await this.#collectFieldObjects( await this.#collectFieldObjects(
name, name,
fieldRef,
kid, kid,
promises, promises,
annotationGlobals, annotationGlobals,
visitedRefs visitedRefs,
orphanFields
); );
} }
} }
@ -1867,13 +1886,16 @@ class PDFDocument {
const visitedRefs = new RefSet(); const visitedRefs = new RefSet();
const allFields = Object.create(null); const allFields = Object.create(null);
const fieldPromises = new Map(); const fieldPromises = new Map();
const orphanFields = new RefSetCache();
for (const fieldRef of await acroForm.getAsync("Fields")) { for (const fieldRef of await acroForm.getAsync("Fields")) {
await this.#collectFieldObjects( await this.#collectFieldObjects(
"", "",
null,
fieldRef, fieldRef,
fieldPromises, fieldPromises,
annotationGlobals, annotationGlobals,
visitedRefs visitedRefs,
orphanFields
); );
} }
@ -1890,7 +1912,7 @@ class PDFDocument {
} }
await Promise.all(allPromises); await Promise.all(allPromises);
return allFields; return { allFields, orphanFields };
}); });
return shadow(this, "fieldObjects", promise); return shadow(this, "fieldObjects", promise);
@ -1914,7 +1936,7 @@ class PDFDocument {
return true; return true;
} }
if (fieldObjects) { if (fieldObjects) {
return Object.values(fieldObjects).some(fieldObject => return Object.values(fieldObjects.allFields).some(fieldObject =>
fieldObject.some(object => object.actions !== null) fieldObject.some(object => object.actions !== null)
); );
} }

View File

@ -522,7 +522,9 @@ class WorkerMessageHandler {
}); });
handler.on("GetFieldObjects", function (data) { handler.on("GetFieldObjects", function (data) {
return pdfManager.ensureDoc("fieldObjects"); return pdfManager
.ensureDoc("fieldObjects")
.then(fieldObjects => fieldObjects?.allFields || null);
}); });
handler.on("HasJSActions", function (data) { handler.on("HasJSActions", function (data) {

View File

@ -673,3 +673,4 @@
!highlight_popup.pdf !highlight_popup.pdf
!issue18072.pdf !issue18072.pdf
!stamps.pdf !stamps.pdf
!issue15096.pdf

BIN
test/pdfs/issue15096.pdf Executable file

Binary file not shown.

View File

@ -10667,5 +10667,22 @@
"popupRef": "44R" "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
}
}
} }
] ]

View File

@ -250,7 +250,7 @@ describe("document", function () {
acroForm.set("Fields", [parentRef]); acroForm.set("Fields", [parentRef]);
pdfDocument = getDocument(acroForm, xref); pdfDocument = getDocument(acroForm, xref);
fields = await pdfDocument.fieldObjects; fields = (await pdfDocument.fieldObjects).allFields;
for (const [name, objs] of Object.entries(fields)) { for (const [name, objs] of Object.entries(fields)) {
fields[name] = objs.map(obj => obj.id); fields[name] = objs.map(obj => obj.id);