From 253ce6e323b53d212925038f26f9bb8d80f3a2c9 Mon Sep 17 00:00:00 2001 From: calixteman Date: Sun, 8 Mar 2026 12:27:51 +0100 Subject: [PATCH] Handle outline with Structure Element (SE) destination --- src/core/catalog.js | 128 +++++++++++++++++++++++++++++++ test/integration/viewer_spec.mjs | 41 ++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/outlines_se.pdf | Bin 0 -> 30444 bytes 4 files changed, 170 insertions(+) create mode 100644 test/pdfs/outlines_se.pdf diff --git a/src/core/catalog.js b/src/core/catalog.js index 9ab10f281a0f5..fcb5964987288 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -1542,6 +1542,105 @@ class Catalog { * exist in most PDF documents). */ + /** + * Derive a destination array from a Structure Element reference. + * Walks the SE dict to find its page (Pg) and optional bounding box (A.BBox), + * then returns an XYZ destination array that can be used for navigation. + * @param {XRef} xref + * @param {Ref} seRef + * @returns {Array|null} + */ + static #getDestFromStructElement(xref, seRef) { + const seDict = xref.fetchIfRef(seRef); + if (!(seDict instanceof Dict)) { + return null; + } + + // Try to find the page reference for this structure element. + // Search order: the element itself, its descendants down to leaf nodes, + // then ancestor elements via the P entry (up). + let pageRef = null; + + // Check the element directly. + const directPg = seDict.getRaw("Pg"); + if (directPg instanceof Ref) { + pageRef = directPg; + } + + // Walk down into descendants (BFS) until a Pg is found or leaves are + // reached (e.g. integer MCIDs or MCR/OBJR dicts without further K). + if (!pageRef) { + const queue = [seDict]; + while (queue.length > 0 && !pageRef) { + const node = queue.shift(); + const kids = node.get("K"); + let kidsArr; + if (Array.isArray(kids)) { + kidsArr = kids; + } else if (kids) { + kidsArr = [kids]; + } else { + kidsArr = []; + } + for (const kid of kidsArr) { + const kidObj = xref.fetchIfRef(kid); + if (!(kidObj instanceof Dict)) { + continue; // integer MCID – leaf node, no Pg here + } + const pg = kidObj.getRaw("Pg"); + if (pg instanceof Ref) { + pageRef = pg; + break; + } + queue.push(kidObj); + } + } + } + + // Walk up the parent chain if still not found. + if (!pageRef) { + const MAX_DEPTH = 40; + let current = seDict; + for (let depth = 0; depth < MAX_DEPTH; depth++) { + const parentRaw = current.getRaw("P"); + if (!(parentRaw instanceof Ref)) { + break; + } + const parentDict = xref.fetchIfRef(parentRaw); + if (!(parentDict instanceof Dict)) { + break; + } + if (isName(parentDict.get("Type"), "StructTreeRoot")) { + break; + } + const pg = parentDict.getRaw("Pg"); + if (pg instanceof Ref) { + pageRef = pg; + break; + } + current = parentDict; + } + } + + if (!pageRef) { + return null; + } + + // Try to obtain precise coordinates from the element's attribute BBox. + let x = null, + 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 + } + } + + return [pageRef, { name: "XYZ" }, x, y, null]; + } + /** * Helper function used to parse the contents of destination dictionaries. * @param {ParseDestDictionaryParameters} params @@ -1773,6 +1872,35 @@ class Catalog { resultObj.dest = dest; } } + + // Handle SE (Structure Element) entry: when no other destination has been + // found, derive one from the structure element's page and optional bbox. + if ( + !resultObj.dest && + !resultObj.url && + !resultObj.action && + !resultObj.attachment && + !resultObj.setOCGState && + !resultObj.resetForm + ) { + const seRef = destDict.getRaw("SE"); + if (seRef instanceof Ref) { + try { + const seDest = Catalog.#getDestFromStructElement( + destDict.xref, + seRef + ); + if (seDest) { + resultObj.dest = seDest; + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + info("SE parsing failed."); + } + } + } } } diff --git a/test/integration/viewer_spec.mjs b/test/integration/viewer_spec.mjs index 9d05102ccd337..d65e81ee91a74 100644 --- a/test/integration/viewer_spec.mjs +++ b/test/integration/viewer_spec.mjs @@ -1646,4 +1646,45 @@ describe("PDF viewer", () => { ); }); }); + + describe("Outline with SE (Structure Element) entries", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "outlines_se.pdf", + `.page[data-page-number="1"] .endOfContent` + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("should navigate to the correct page when clicking an outline item with an SE entry", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + // Open the sidebar. + await showViewsManager(page); + + // Switch to the outline view. + await page.click("#viewsManagerSelectorButton"); + await page.waitForSelector("#outlinesViewMenu", { visible: true }); + await page.click("#outlinesViewMenu"); + + for (let i = 2; i >= 1; i--) { + await waitAndClick( + page, + `#outlinesView .treeItem .treeItem:nth-child(${i}) a` + ); + await page.waitForFunction( + pageNum => window.PDFViewerApplication.page === pageNum, + {}, + i + ); + } + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 01472be4974a2..771e71c68339b 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -879,3 +879,4 @@ !sci-notation.pdf !nested_outline.pdf !form_two_pages.pdf +!outlines_se.pdf diff --git a/test/pdfs/outlines_se.pdf b/test/pdfs/outlines_se.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bb6725142bb87d143801f568b4e00397dc29b1d6 GIT binary patch literal 30444 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 literal 0 HcmV?d00001