diff --git a/src/core/editor/pdf_editor.js b/src/core/editor/pdf_editor.js index 51f5b5faf..5c2288920 100644 --- a/src/core/editor/pdf_editor.js +++ b/src/core/editor/pdf_editor.js @@ -1452,9 +1452,12 @@ class PDFEditor { result.push({ ...item, // When the item's own destination is invalid (but it has surviving - // children), clear the destination so the output item is a plain - // container rather than a broken link. + // children), clear the destination and rawDict so the output item is + // a plain container rather than a broken link. Clearing rawDict + // prevents #setOutlineItemDest from cloning a GoTo action that + // references a deleted page via its D array. dest: hasValidOwnDest ? item.dest : null, + rawDict: hasValidOwnDest ? item.rawDict : null, items: filteredChildren, _documentData: documentData, }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 1bd14c54d..d591829ed 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -886,6 +886,7 @@ !outlines_se.pdf !radial_gradients.pdf !outlines_for_editor.pdf +!outline_goto_action.pdf !mesh_shading_empty.pdf !acroform_calculation_order.pdf !extractPages_null_in_array.pdf diff --git a/test/pdfs/outline_goto_action.pdf b/test/pdfs/outline_goto_action.pdf new file mode 100644 index 000000000..6cbc020c6 Binary files /dev/null and b/test/pdfs/outline_goto_action.pdf differ diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 1be2c3037..98b6f82be 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -6971,6 +6971,41 @@ small scripts as well as for`); await newLoadingTask.destroy(); }); + + it("should handle parent items whose dest was cleared but rawDict still holds a GoTo action (bug 2033603)", async function () { + // outline_goto_action.pdf has 2 pages and this outline: + // "Parent" /A << /S /GoTo /D [page1Ref /FitH 842] >> + // "Child" /Dest [page2Ref /FitH 0] + const loadingTask = getDocument( + buildGetDocumentParams("outline_goto_action.pdf") + ); + const pdfDoc = await loadingTask.promise; + const data = await pdfDoc.extractPages([ + { document: null, includePages: [1] }, + ]); + await loadingTask.destroy(); + + const newLoadingTask = getDocument(data); + const newPdfDoc = await newLoadingTask.promise; + expect(newPdfDoc.numPages).toEqual(1); + + const outline = await newPdfDoc.getOutline(); + expect(Array.isArray(outline)).toEqual(true); + expect(outline.length).toEqual(1); + + // "Parent" is kept as a plain container (dest cleared, no action). + const parent = outline[0]; + expect(parent.title).toEqual("Parent"); + expect(parent.dest).toEqual(null); + expect(parent.items.length).toEqual(1); + + // "Child" keeps its explicit dest pointing to the (only) kept page. + const child = parent.items[0]; + expect(child.title).toEqual("Child"); + expect(Array.isArray(child.dest)).toEqual(true); + + await newLoadingTask.destroy(); + }); }); describe("extract pages with null values in arrays", function () {