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.
This commit is contained in:
Jonas Jenwald 2025-02-12 13:47:13 +01:00
parent c69282a64f
commit 33cba30bdb
4 changed files with 65 additions and 33 deletions

View File

@ -707,8 +707,9 @@ class Catalog {
} }
get destinations() { get destinations() {
const obj = this._readDests(), const rawDests = this.#readDests(),
dests = Object.create(null); dests = Object.create(null);
for (const obj of rawDests) {
if (obj instanceof NameTree) { if (obj instanceof NameTree) {
for (const [key, value] of obj.getAll()) { for (const [key, value] of obj.getAll()) {
const dest = fetchDest(value); const dest = fetchDest(value);
@ -720,7 +721,9 @@ class Catalog {
for (const [key, value] of obj) { for (const [key, value] of obj) {
const dest = fetchDest(value); const dest = fetchDest(value);
if (dest) { if (dest) {
dests[key] = dest; // Always let the NameTree take precedence.
dests[key] ||= dest;
}
} }
} }
} }
@ -728,40 +731,39 @@ class Catalog {
} }
getDestination(id) { getDestination(id) {
const obj = this._readDests(); const rawDests = this.#readDests();
if (obj instanceof NameTree) { for (const obj of rawDests) {
if (obj instanceof NameTree || obj instanceof Dict) {
const dest = fetchDest(obj.get(id)); const dest = fetchDest(obj.get(id));
if (dest) { if (dest) {
return dest; return dest;
} }
}
}
if (rawDests[0] instanceof NameTree) {
// Fallback to checking the *entire* NameTree, in an attempt to handle // Fallback to checking the *entire* NameTree, in an attempt to handle
// corrupt PDF documents with out-of-order NameTrees (fixes issue 10272). // corrupt PDF documents with out-of-order NameTrees (fixes issue 10272).
const allDest = this.destinations[id]; const dest = 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));
if (dest) { if (dest) {
warn(`Found "${id}" at an incorrect position in the NameTree.`);
return dest; return dest;
} }
} }
return null; return null;
} }
/** #readDests() {
* @private
*/
_readDests() {
const obj = this._catDict.get("Names"); const obj = this._catDict.get("Names");
const rawDests = [];
if (obj?.has("Dests")) { if (obj?.has("Dests")) {
return new NameTree(obj.getRaw("Dests"), this.xref); rawDests.push(new NameTree(obj.getRaw("Dests"), this.xref));
} else if (this._catDict.has("Dests")) {
// Simple destination dictionary.
return this._catDict.get("Dests");
} }
return undefined; if (this._catDict.has("Dests")) {
// Simple destination dictionary.
rawDests.push(this._catDict.get("Dests"));
}
return rawDests;
} }
get pageLabels() { get pageLabels() {

View File

@ -533,6 +533,7 @@
!transparent.pdf !transparent.pdf
!issue19326.pdf !issue19326.pdf
!issue13931.pdf !issue13931.pdf
!issue19474.pdf
!xobject-image.pdf !xobject-image.pdf
!issue15441.pdf !issue15441.pdf
!issue6605.pdf !issue6605.pdf

BIN
test/pdfs/issue19474.pdf Normal file

Binary file not shown.

View File

@ -1261,6 +1261,19 @@ describe("api", function () {
await loadingTask.destroy(); 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 () { it("gets a destination, from /Names (NameTree) dictionary", async function () {
const loadingTask = getDocument(buildGetDocumentParams("issue6204.pdf")); const loadingTask = getDocument(buildGetDocumentParams("issue6204.pdf"));
const pdfDoc = await loadingTask.promise; const pdfDoc = await loadingTask.promise;
@ -1320,6 +1333,22 @@ describe("api", function () {
await loadingTask.destroy(); 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 () { it("gets non-string destination", async function () {
let numberPromise = pdfDocument.getDestination(4.3); let numberPromise = pdfDocument.getDestination(4.3);
let booleanPromise = pdfDocument.getDestination(true); let booleanPromise = pdfDocument.getDestination(true);