From d1f15fe35252a0d265c78f656c93847faf92773e Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 29 Mar 2026 13:52:58 +0200 Subject: [PATCH] Add a unit-test for the `Catalog.#getDestFromStructElement` method This code already has an integration-test, however also having a unit-test shouldn't hurt since those are often easier to run and debug (and it nicely complements the existing `outline` unit-tests). The patch also makes the following smaller changes to the method itself: - Avoid creating and parsing an empty Array, when doing the `pageRef` search. - Use `XRef.prototype.fetch` directly, when walking the parent chain, since the check just above ensures that the value is a Reference. - Use the `lookupRect` helper when parsing the /BBox entry. --- src/core/catalog.js | 13 +++++----- test/unit/api_spec.js | 59 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/core/catalog.js b/src/core/catalog.js index 5ec4947f4..8b6e30f33 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -29,6 +29,7 @@ import { import { collectActions, isNumberArray, + lookupRect, MissingDataException, PDF_VERSION_REGEXP, recoverJsURL, @@ -1597,7 +1598,7 @@ class Catalog { } else if (kids) { kidsArr = [kids]; } else { - kidsArr = []; + continue; } for (const kid of kidsArr) { const kidObj = xref.fetchIfRef(kid); @@ -1623,7 +1624,7 @@ class Catalog { if (!(parentRaw instanceof Ref)) { break; } - const parentDict = xref.fetchIfRef(parentRaw); + const parentDict = xref.fetch(parentRaw); if (!(parentDict instanceof Dict)) { break; } @@ -1648,10 +1649,10 @@ class Catalog { y = null; const attrs = seDict.get("A"); if (attrs instanceof Dict) { - const bboxArr = attrs.getArray("BBox"); - if (isNumberArray(bboxArr, 4)) { - x = bboxArr[0]; - y = bboxArr[3]; // top of the bbox in PDF page coordinates + const bbox = lookupRect(attrs.getArray("BBox"), null); + if (bbox) { + x = bbox[0]; + y = bbox[3]; // top of the bbox in PDF page coordinates } } diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 02710e456..f5148781b 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -2167,6 +2167,65 @@ describe("api", function () { await loadingTask.destroy(); }); + it("gets outline, with SE (Structure Element) entries", async function () { + const loadingTask = getDocument( + buildGetDocumentParams("outlines_se.pdf") + ); + const pdfDoc = await loadingTask.promise; + const outline = await pdfDoc.getOutline(); + + expect(outline).toEqual([ + { + action: null, + attachment: undefined, + dest: null, + url: null, + unsafeUrl: undefined, + newWindow: undefined, + setOCGState: undefined, + title: "P tags", + color: new Uint8ClampedArray([0, 0, 0]), + count: 2, + bold: false, + italic: false, + items: [ + { + action: null, + attachment: undefined, + dest: [{ num: 37, gen: 0 }, { name: "XYZ" }, null, null, null], + url: null, + unsafeUrl: undefined, + newWindow: undefined, + setOCGState: undefined, + title: "Hello ", + color: new Uint8ClampedArray([0, 0, 0]), + count: undefined, + bold: false, + italic: false, + items: [], + }, + { + action: null, + attachment: undefined, + dest: [{ num: 36, gen: 0 }, { name: "XYZ" }, null, null, null], + url: null, + unsafeUrl: undefined, + newWindow: undefined, + setOCGState: undefined, + title: "World ", + color: new Uint8ClampedArray([0, 0, 0]), + count: undefined, + bold: false, + italic: false, + items: [], + }, + ], + }, + ]); + + await loadingTask.destroy(); + }); + it("gets non-existent permissions", async function () { const permissions = await pdfDocument.getPermissions(); expect(permissions).toEqual(null);