Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions src/core/editor/pdf_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,15 +616,20 @@ class PDFEditor {
if (newIndex !== -1) {
newPageIndex = newIndex++;
} else {
// Find the first available index in the newPages array.
// This is needed when the pageIndices option is used since the
// pages can be added in any order.
for (
newPageIndex = 0;
this.oldPages[newPageIndex] === undefined;
this.oldPages[newPageIndex] !== undefined;
newPageIndex++
) {
/* empty */
}
}
}
// Reserve the slot immediately because the page fetch is async.
this.oldPages[newPageIndex] = null;
promises.push(
document.getPage(i).then(page => {
this.oldPages[newPageIndex] = new PageData(page, documentData);
Expand Down Expand Up @@ -1147,6 +1152,17 @@ class PDFEditor {
*/
#findDuplicateNamedDestinations() {
const { namedDestinations } = this;
const getUniqueDestinationName = name => {
if (!namedDestinations.has(name)) {
return name;
}
for (let i = 1; ; i++) {
const dedupedName = `${name}_${i}`;
if (!namedDestinations.has(dedupedName)) {
return dedupedName;
}
}
};
for (let i = 0, ii = this.oldPages.length; i < ii; i++) {
const page = this.oldPages[i];
const {
Expand Down Expand Up @@ -1179,7 +1195,7 @@ class PDFEditor {
continue;
}
// Create a new unique named destination.
const newName = `${pointingDest}_p${i + 1}`;
const newName = getUniqueDestinationName(`${pointingDest}_p${i + 1}`);
dedupNamedDestinations.set(pointingDest, newName);
namedDestinations.set(newName, dest);
}
Expand Down Expand Up @@ -1460,6 +1476,7 @@ class PDFEditor {
this.currentDocument = null;
}
}
this.#setAcroFormCalculationOrder(allDocumentData);
}

#setAcroFormQ(allDocumentData) {
Expand Down Expand Up @@ -1495,7 +1512,6 @@ class PDFEditor {
#setAcroFormDefaultBasicValues(allDocumentData) {
let sigFlags = 0;
let needAppearances = false;
const calculationOrder = [];
for (const documentData of allDocumentData) {
if (!documentData.acroForm) {
continue;
Expand All @@ -1507,7 +1523,15 @@ class PDFEditor {
if (documentData.acroForm.get("NeedAppearances") === true) {
needAppearances = true;
}
const co = documentData.acroForm.get("CO") || null;
}
this.acroFormSigFlags = sigFlags;
this.acroFormNeedAppearances = needAppearances;
}

#setAcroFormCalculationOrder(allDocumentData) {
const calculationOrder = [];
for (const documentData of allDocumentData) {
const co = documentData.acroForm?.get("CO") || null;
if (!Array.isArray(co)) {
continue;
}
Expand All @@ -1519,8 +1543,6 @@ class PDFEditor {
}
}
}
this.acroFormSigFlags = sigFlags;
this.acroFormNeedAppearances = needAppearances;
this.acroFormCalculationOrder =
calculationOrder.length > 0 ? calculationOrder : null;
}
Expand Down
2 changes: 2 additions & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@
!issue14048.pdf
!issue11656.pdf
!annotation-fileattachment.pdf
!named_dest_collision_for_editor.pdf
!annotation-text-widget.pdf
!issue7454.pdf
!issue15443.pdf
Expand Down Expand Up @@ -886,3 +887,4 @@
!radial_gradients.pdf
!outlines_for_editor.pdf
!mesh_shading_empty.pdf
!acroform_calculation_order.pdf
Binary file added test/pdfs/acroform_calculation_order.pdf
Binary file not shown.
Binary file added test/pdfs/named_dest_collision_for_editor.pdf
Binary file not shown.
101 changes: 101 additions & 0 deletions test/unit/api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5800,6 +5800,41 @@ small scripts as well as for`);
});

describe("Named destinations", function () {
it("keeps colliding deduplicated destination names unique", async function () {
let loadingTask = getDocument(
buildGetDocumentParams("named_dest_collision_for_editor.pdf")
);
let pdfDoc = await loadingTask.promise;

let destinations = await pdfDoc.getDestinations();
expect(Object.keys(destinations).sort()).toEqual(["foo", "foo_p2"]);

const data = await pdfDoc.extractPages([
{ document: null },
{ document: null },
]);
await loadingTask.destroy();

loadingTask = getDocument(data);
pdfDoc = await loadingTask.promise;

destinations = await pdfDoc.getDestinations();
expect(Object.keys(destinations).sort()).toEqual([
"foo",
"foo_p2",
"foo_p2_1",
"foo_p2_p2",
]);

const secondPage = await pdfDoc.getPage(2);
const annots = await secondPage.getAnnotations();
expect(annots.length).toEqual(2);
expect(annots[0].dest).toEqual("foo_p2_1");
expect(annots[1].dest).toEqual("foo_p2_p2");

await loadingTask.destroy();
});

it("extract page and check destinations", async function () {
let loadingTask = getDocument(buildGetDocumentParams("issue6204.pdf"));
let pdfDoc = await loadingTask.promise;
Expand Down Expand Up @@ -6218,6 +6253,44 @@ small scripts as well as for`);

await loadingTask.destroy();
});

it("fills missing pageIndices with the first free slots", async function () {
let loadingTask = getDocument(
buildGetDocumentParams("tracemonkey.pdf")
);
let pdfDoc = await loadingTask.promise;
const data = await pdfDoc.extractPages([
{ document: null, includePages: [1, 3, 5], pageIndices: [1] },
]);
await loadingTask.destroy();

loadingTask = getDocument(data);
pdfDoc = await loadingTask.promise;

expect(pdfDoc.numPages).toEqual(3);

// Page 4 in the original document should occupy the first free slot.
let pdfPage = await pdfDoc.getPage(1);
let { items: textItems } = await pdfPage.getTextContent();
expect(mergeText(textItems).includes("3. Trace Trees")).toBeTrue();

// Page 2 in the original document keeps its explicit destination slot.
pdfPage = await pdfDoc.getPage(2);
({ items: textItems } = await pdfPage.getTextContent());
expect(
mergeText(textItems).includes("2. Overview: Example Tracing Run")
).toBeTrue();

// Page 6 in the original document should occupy the remaining free
// slot.
pdfPage = await pdfDoc.getPage(3);
({ items: textItems } = await pdfPage.getTextContent());
expect(
mergeText(textItems).includes("4. Nested Trace Tree Formation")
).toBeTrue();

await loadingTask.destroy();
});
});

describe("AcroForm", function () {
Expand Down Expand Up @@ -6325,6 +6398,34 @@ small scripts as well as for`);

await loadingTask.destroy();
});

it("preserves calculation order when it points to parent fields", async function () {
let loadingTask = getDocument(
buildGetDocumentParams("acroform_calculation_order.pdf")
);
let pdfDoc = await loadingTask.promise;

expect(await pdfDoc.getCalculationOrderIds()).toEqual(["6R"]);
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
"group",
]);

const data = await pdfDoc.extractPages([{ document: null }]);
await loadingTask.destroy();

loadingTask = getDocument(data);
pdfDoc = await loadingTask.promise;

const calculationOrder = await pdfDoc.getCalculationOrderIds();
expect(Array.isArray(calculationOrder)).toEqual(true);
expect(calculationOrder.length).toEqual(1);
expect(calculationOrder[0]).not.toEqual("6R");
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
"group",
]);

await loadingTask.destroy();
});
});

describe("Outlines", function () {
Expand Down
Loading