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 000000000..33e996393 Binary files /dev/null and b/test/pdfs/issue19474.pdf differ 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);