From d185db2b7046d013ceaa16b3553dfb119bbae082 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 30 Aug 2023 20:00:05 +0200 Subject: [PATCH] Add tagged annotations in the structure tree (bug 1850797) --- src/core/annotation.js | 43 +++++++++--- src/core/document.js | 11 ++- src/core/struct_tree.js | 92 ++++++++++++++++++------- src/display/annotation_layer.js | 2 +- src/display/display_utils.js | 3 - src/shared/util.js | 3 + test/integration/accessibility_spec.js | 31 +++++++++ test/pdfs/.gitignore | 1 + test/pdfs/tagged_stamp.pdf | Bin 0 -> 21629 bytes web/struct_tree_layer_builder.js | 13 ++-- 10 files changed, 152 insertions(+), 47 deletions(-) create mode 100755 test/pdfs/tagged_stamp.pdf diff --git a/src/core/annotation.js b/src/core/annotation.js index fc0bb208c..2e8d35807 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -77,10 +77,11 @@ class AnnotationFactory { * @param {PDFManager} pdfManager * @param {Object} idFactory * @param {boolean} collectFields + * @param {Object} [pageRef] * @returns {Promise} A promise that is resolved with an {Annotation} * instance. */ - static create(xref, ref, pdfManager, idFactory, collectFields) { + static create(xref, ref, pdfManager, idFactory, collectFields, pageRef) { return Promise.all([ pdfManager.ensureCatalog("acroForm"), // Only necessary to prevent the `pdfManager.docBaseUrl`-getter, used @@ -91,18 +92,29 @@ class AnnotationFactory { pdfManager.ensureCatalog("attachments"), pdfManager.ensureDoc("xfaDatasets"), collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1, - ]).then(([acroForm, baseUrl, attachments, xfaDatasets, pageIndex]) => - pdfManager.ensure(this, "_create", [ - xref, - ref, - pdfManager, - idFactory, + pageRef ? pdfManager.ensureCatalog("structTreeRoot") : null, + ]).then( + ([ acroForm, + baseUrl, attachments, xfaDatasets, - collectFields, pageIndex, - ]) + structTreeRoot, + ]) => + pdfManager.ensure(this, "_create", [ + xref, + ref, + pdfManager, + idFactory, + acroForm, + attachments, + xfaDatasets, + collectFields, + pageIndex, + structTreeRoot, + pageRef, + ]) ); } @@ -118,7 +130,9 @@ class AnnotationFactory { attachments = null, xfaDatasets, collectFields, - pageIndex = -1 + pageIndex = -1, + structTreeRoot = null, + pageRef = null ) { const dict = xref.fetchIfRef(ref); if (!(dict instanceof Dict)) { @@ -150,6 +164,8 @@ class AnnotationFactory { !collectFields && acroFormDict.get("NeedAppearances") === true, pageIndex, evaluatorOptions: pdfManager.evaluatorOptions, + structTreeRoot, + pageRef, }; switch (subtype) { @@ -594,6 +610,13 @@ class Annotation { const isLocked = !!(this.flags & AnnotationFlag.LOCKED); const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS); + if (params.structTreeRoot) { + let structParent = dict.get("StructParent"); + structParent = + Number.isInteger(structParent) && structParent >= 0 ? structParent : -1; + params.structTreeRoot.addAnnotationIdToPage(params.pageRef, structParent); + } + // Expose public properties using a data object. this.data = { annotationFlags: this.flags, diff --git a/src/core/document.js b/src/core/document.js index 66197ea13..741494023 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -651,6 +651,9 @@ class Page { if (!structTreeRoot) { return null; } + // Ensure that the structTree will contain the page's annotations. + await this._parsedAnnotations; + const structTree = await this.pdfManager.ensure(this, "_parseStructTree", [ structTreeRoot, ]); @@ -662,7 +665,7 @@ class Page { */ _parseStructTree(structTreeRoot) { const tree = new StructTreePage(structTreeRoot, this.pageDict); - tree.parse(); + tree.parse(this.ref); return tree; } @@ -740,7 +743,8 @@ class Page { annotationRef, this.pdfManager, this._localIdFactory, - /* collectFields */ false + /* collectFields */ false, + this.ref ).catch(function (reason) { warn(`_parsedAnnotations: "${reason}".`); return null; @@ -1719,7 +1723,8 @@ class PDFDocument { fieldRef, this.pdfManager, this._localIdFactory, - /* collectFields */ true + /* collectFields */ true, + /* pageRef */ null ) .then(annotation => annotation?.getFieldObject()) .catch(function (reason) { diff --git a/src/core/struct_tree.js b/src/core/struct_tree.js index 0f576dbcf..eeebd1194 100644 --- a/src/core/struct_tree.js +++ b/src/core/struct_tree.js @@ -13,29 +13,48 @@ * limitations under the License. */ -import { Dict, isName, Name, Ref } from "./primitives.js"; -import { stringToPDFString, warn } from "../shared/util.js"; +import { AnnotationPrefix, stringToPDFString, warn } from "../shared/util.js"; +import { Dict, isName, Name, Ref, RefSetCache } from "./primitives.js"; import { NumberTree } from "./name_number_tree.js"; const MAX_DEPTH = 40; const StructElementType = { - PAGE_CONTENT: "PAGE_CONTENT", - STREAM_CONTENT: "STREAM_CONTENT", - OBJECT: "OBJECT", - ELEMENT: "ELEMENT", + PAGE_CONTENT: 1, + STREAM_CONTENT: 2, + OBJECT: 3, + ANNOTATION: 4, + ELEMENT: 5, }; class StructTreeRoot { constructor(rootDict) { this.dict = rootDict; this.roleMap = new Map(); + this.structParentIds = null; } init() { this.readRoleMap(); } + #addIdToPage(pageRef, id, type) { + if (!(pageRef instanceof Ref) || id < 0) { + return; + } + this.structParentIds ||= new RefSetCache(); + let ids = this.structParentIds.get(pageRef); + if (!ids) { + ids = []; + this.structParentIds.put(pageRef, ids); + } + ids.push([id, type]); + } + + addAnnotationIdToPage(pageRef, id) { + this.#addIdToPage(pageRef, id, StructElementType.ANNOTATION); + } + readRoleMap() { const roleMapDict = this.dict.get("RoleMap"); if (!(roleMapDict instanceof Dict)) { @@ -129,12 +148,10 @@ class StructElementNode { if (this.tree.pageDict.objId !== pageObjId) { return null; } + const kidRef = kidDict.getRaw("Stm"); return new StructElement({ type: StructElementType.STREAM_CONTENT, - refObjId: - kidDict.getRaw("Stm") instanceof Ref - ? kidDict.getRaw("Stm").toString() - : null, + refObjId: kidRef instanceof Ref ? kidRef.toString() : null, pageObjId, mcid: kidDict.get("MCID"), }); @@ -144,12 +161,10 @@ class StructElementNode { if (this.tree.pageDict.objId !== pageObjId) { return null; } + const kidRef = kidDict.getRaw("Obj"); return new StructElement({ type: StructElementType.OBJECT, - refObjId: - kidDict.getRaw("Obj") instanceof Ref - ? kidDict.getRaw("Obj").toString() - : null, + refObjId: kidRef instanceof Ref ? kidRef.toString() : null, pageObjId, }); } @@ -186,7 +201,7 @@ class StructTreePage { this.nodes = []; } - parse() { + parse(pageRef) { if (!this.root || !this.rootDict) { return; } @@ -196,18 +211,42 @@ class StructTreePage { return; } const id = this.pageDict.get("StructParents"); - if (!Number.isInteger(id)) { - return; - } - const numberTree = new NumberTree(parentTree, this.rootDict.xref); - const parentArray = numberTree.get(id); - if (!Array.isArray(parentArray)) { + const ids = + pageRef instanceof Ref && this.root.structParentIds?.get(pageRef); + if (!Number.isInteger(id) && !ids) { return; } + const map = new Map(); - for (const ref of parentArray) { - if (ref instanceof Ref) { - this.addNode(this.rootDict.xref.fetch(ref), map); + const numberTree = new NumberTree(parentTree, this.rootDict.xref); + + if (Number.isInteger(id)) { + const parentArray = numberTree.get(id); + if (Array.isArray(parentArray)) { + for (const ref of parentArray) { + if (ref instanceof Ref) { + this.addNode(this.rootDict.xref.fetch(ref), map); + } + } + } + } + + if (!ids) { + return; + } + for (const [elemId, type] of ids) { + const obj = numberTree.get(elemId); + if (obj) { + const elem = this.addNode(this.rootDict.xref.fetchIfRef(obj), map); + if ( + elem?.kids?.length === 1 && + elem.kids[0].type === StructElementType.OBJECT + ) { + // The node in the struct tree is wrapping an object (annotation + // or xobject), so we need to update the type of the node to match + // the type of the object. + elem.kids[0].type = type; + } } } } @@ -322,6 +361,11 @@ class StructTreePage { type: "object", id: kid.refObjId, }); + } else if (kid.type === StructElementType.ANNOTATION) { + obj.children.push({ + type: "annotation", + id: `${AnnotationPrefix}${kid.refObjId}`, + }); } } } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index ba9cfb997..90cf2edf4 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -21,6 +21,7 @@ import { AnnotationBorderStyleType, AnnotationEditorType, + AnnotationPrefix, AnnotationType, FeatureTest, LINE_FACTOR, @@ -30,7 +31,6 @@ import { warn, } from "../shared/util.js"; import { - AnnotationPrefix, DOMSVGFactory, getFilenameFromUrl, PDFDateString, diff --git a/src/display/display_utils.js b/src/display/display_utils.js index bac8faf0a..e77f94a0c 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -31,8 +31,6 @@ import { const SVG_NS = "http://www.w3.org/2000/svg"; -const AnnotationPrefix = "pdfjs_internal_id_"; - class PixelsPerInch { static CSS = 96.0; @@ -1005,7 +1003,6 @@ function setLayerDimensions( } export { - AnnotationPrefix, deprecated, DOMCanvasFactory, DOMCMapReaderFactory, diff --git a/src/shared/util.js b/src/shared/util.js index adce62c1d..9bd726e9a 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -1047,6 +1047,8 @@ function getUuid() { return bytesToString(buf); } +const AnnotationPrefix = "pdfjs_internal_id_"; + export { AbortException, AnnotationActionEventType, @@ -1057,6 +1059,7 @@ export { AnnotationFieldFlag, AnnotationFlag, AnnotationMode, + AnnotationPrefix, AnnotationReplyType, AnnotationType, assert, diff --git a/test/integration/accessibility_spec.js b/test/integration/accessibility_spec.js index 0d25586b1..c161e5dd8 100644 --- a/test/integration/accessibility_spec.js +++ b/test/integration/accessibility_spec.js @@ -139,4 +139,35 @@ describe("accessibility", () => { ); }); }); + + describe("Stamp annotation accessibility", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("tagged_stamp.pdf", ".annotationLayer"); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that the stamp annotation is linked to the struct tree", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForSelector(".structTree"); + + const isLinkedToStampAnnotation = await page.$eval( + ".structTree [role='figure']", + el => + document + .getElementById(el.getAttribute("aria-owns")) + .classList.contains("stampAnnotation") + ); + expect(isLinkedToStampAnnotation) + .withContext(`In ${browserName}`) + .toEqual(true); + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 266b80b83..43c66b754 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -610,3 +610,4 @@ !annotation_hidden_noview.pdf !widget_hidden_print.pdf !empty_protected.pdf +!tagged_stamp.pdf diff --git a/test/pdfs/tagged_stamp.pdf b/test/pdfs/tagged_stamp.pdf new file mode 100755 index 0000000000000000000000000000000000000000..918fd55977bcf18af80530f29e1cfbf9866b80b7 GIT binary patch literal 21629 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