From 33cba30bdbbc4d768bf3280c55f5aee424d20f72 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 12 Feb 2025 13:47:13 +0100 Subject: [PATCH] Search for destinations in both /Names and /Dests dictionaries (issue 19474) Currently we only use either one of them, preferring the NameTree when it's available. --- src/core/catalog.js | 68 ++++++++++++++++++++------------------- test/pdfs/.gitignore | 1 + test/pdfs/issue19474.pdf | Bin 0 -> 2361 bytes test/unit/api_spec.js | 29 +++++++++++++++++ 4 files changed, 65 insertions(+), 33 deletions(-) create mode 100644 test/pdfs/issue19474.pdf diff --git a/src/core/catalog.js b/src/core/catalog.js index b2f0d8711..1274f6f5c 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -707,20 +707,23 @@ class Catalog { } get destinations() { - const obj = this._readDests(), + const rawDests = this.#readDests(), dests = Object.create(null); - if (obj instanceof NameTree) { - for (const [key, value] of obj.getAll()) { - const dest = fetchDest(value); - if (dest) { - dests[stringToPDFString(key)] = dest; + for (const obj of rawDests) { + if (obj instanceof NameTree) { + for (const [key, value] of obj.getAll()) { + const dest = fetchDest(value); + if (dest) { + dests[stringToPDFString(key)] = dest; + } } - } - } else if (obj instanceof Dict) { - for (const [key, value] of obj) { - const dest = fetchDest(value); - if (dest) { - dests[key] = dest; + } else if (obj instanceof Dict) { + for (const [key, value] of obj) { + const dest = fetchDest(value); + if (dest) { + // Always let the NameTree take precedence. + dests[key] ||= dest; + } } } } @@ -728,40 +731,39 @@ class Catalog { } getDestination(id) { - const obj = this._readDests(); - if (obj instanceof NameTree) { - const dest = fetchDest(obj.get(id)); - if (dest) { - return dest; + const rawDests = this.#readDests(); + for (const obj of rawDests) { + if (obj instanceof NameTree || obj instanceof Dict) { + const dest = fetchDest(obj.get(id)); + if (dest) { + return dest; + } } + } + + if (rawDests[0] instanceof NameTree) { // Fallback to checking the *entire* NameTree, in an attempt to handle // corrupt PDF documents with out-of-order NameTrees (fixes issue 10272). - const allDest = this.destinations[id]; - if (allDest) { - warn(`Found "${id}" at an incorrect position in the NameTree.`); - return allDest; - } - } else if (obj instanceof Dict) { - const dest = fetchDest(obj.get(id)); + const dest = this.destinations[id]; if (dest) { + warn(`Found "${id}" at an incorrect position in the NameTree.`); return dest; } } return null; } - /** - * @private - */ - _readDests() { + #readDests() { const obj = this._catDict.get("Names"); + const rawDests = []; if (obj?.has("Dests")) { - return new NameTree(obj.getRaw("Dests"), this.xref); - } else if (this._catDict.has("Dests")) { - // Simple destination dictionary. - return this._catDict.get("Dests"); + rawDests.push(new NameTree(obj.getRaw("Dests"), this.xref)); } - return undefined; + if (this._catDict.has("Dests")) { + // Simple destination dictionary. + rawDests.push(this._catDict.get("Dests")); + } + return rawDests; } get pageLabels() { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index efe327f76..71bd89464 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -533,6 +533,7 @@ !transparent.pdf !issue19326.pdf !issue13931.pdf +!issue19474.pdf !xobject-image.pdf !issue15441.pdf !issue6605.pdf diff --git a/test/pdfs/issue19474.pdf b/test/pdfs/issue19474.pdf new file mode 100644 index 0000000000000000000000000000000000000000..33e9963936a2ef749735bc5bd24999bda34dde08 GIT binary patch literal 2361 zcmb_e&u<$=6kc(FvT85jNFZ^TOR5Bsotgb@lv?sSwGuUL%_gWqjuB&f;%;zu%kCtk zhy#BBTb8;>+5*;{ymP{ahwReb2Jq4wjIUqeh9VD%~B+zY_@M1==9%dlJ@`6e~|=B&2%Q-C=oST~vqp5pG53BJKc$rzaA@kHE} z#Q+_dNI5&yPriKg`Y!2h-npm$@}ad$?C{do4zYjxn!IrOapTj=jW;e|`TNH!YmFfM z+=q7Qg18Jc+U>R5D zO+G#5A{sG#J%;L{c!KXlaXZeU;$G>w)Ie2XJvQO^CLc#ENKSIGI8Da@J^6M{dC0Tm zC>`+(L@Dv)8%0!R2x4lraF3-t7D%ZJcfbQ~Sszi^f^d*)Sl(s-L!?v(Z&K>vppuyh zh9a{_O!>+`|7sl#X?rW$u@-3zjxqc_)Hs|HZKxffN!xwo*}BLDMPBed_em3kUI%jKqFq4N}CRZIGDXHXFWSudvu3mIN4+{OIVT<=H9z9} zb(`naY9p=*N}kV;I99C=V%c>+^Bg~VPCO!4^*jc+)i9_@KrDryNLe)Hsr*-EC;Es( z+i;j90;#o!w>J9g)O9&=Op`LooT23o$!N$d#~pFYY+9b#da{T7e#it%h0+G~iK8zs JU%%be{{wc5b~gY3 literal 0 HcmV?d00001 diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index df515c253..bc0e8bb94 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -1261,6 +1261,19 @@ describe("api", function () { await loadingTask.destroy(); }); + it("gets destinations, from /Names (NameTree) respectively /Dests dictionary", async function () { + const loadingTask = getDocument(buildGetDocumentParams("issue19474.pdf")); + const pdfDoc = await loadingTask.promise; + const destinations = await pdfDoc.getDestinations(); + expect(destinations).toEqual({ + A: [{ num: 1, gen: 0 }, { name: "Fit" }], + B: [{ num: 4, gen: 0 }, { name: "Fit" }], + C: [{ num: 5, gen: 0 }, { name: "Fit" }], + }); + + await loadingTask.destroy(); + }); + it("gets a destination, from /Names (NameTree) dictionary", async function () { const loadingTask = getDocument(buildGetDocumentParams("issue6204.pdf")); const pdfDoc = await loadingTask.promise; @@ -1320,6 +1333,22 @@ describe("api", function () { await loadingTask.destroy(); }); + it("gets a destination, from /Names (NameTree) respectively /Dests dictionary", async function () { + const loadingTask = getDocument(buildGetDocumentParams("issue19474.pdf")); + const pdfDoc = await loadingTask.promise; + + const destA = await pdfDoc.getDestination("A"); + expect(destA).toEqual([{ num: 1, gen: 0 }, { name: "Fit" }]); + + const destB = await pdfDoc.getDestination("B"); + expect(destB).toEqual([{ num: 4, gen: 0 }, { name: "Fit" }]); + + const destC = await pdfDoc.getDestination("C"); + expect(destC).toEqual([{ num: 5, gen: 0 }, { name: "Fit" }]); + + await loadingTask.destroy(); + }); + it("gets non-string destination", async function () { let numberPromise = pdfDocument.getDestination(4.3); let booleanPromise = pdfDocument.getDestination(true);