mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-30 02:47:22 +02:00
Handle indirect and dangling structure tree root kids
Preserve indirect root kids and marked content from removed link annotations. Ignore dangling kid references instead of throwing.
This commit is contained in:
parent
b19cc7c746
commit
dae773d74a
@ -530,6 +530,14 @@ class PDFEditor {
|
|||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #resolveStructKids(rawKids, xref) {
|
||||||
|
if (rawKids instanceof Ref) {
|
||||||
|
const fetched = await xref.fetchAsync(rawKids);
|
||||||
|
return Array.isArray(fetched) ? fetched : [rawKids];
|
||||||
|
}
|
||||||
|
return Array.isArray(rawKids) ? rawKids : [rawKids];
|
||||||
|
}
|
||||||
|
|
||||||
async #cloneStructTreeNode(
|
async #cloneStructTreeNode(
|
||||||
parentStructRef,
|
parentStructRef,
|
||||||
node,
|
node,
|
||||||
@ -547,19 +555,11 @@ class PDFEditor {
|
|||||||
if (pg instanceof Ref && !pagesMap.has(pg)) {
|
if (pg instanceof Ref && !pagesMap.has(pg)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let kids;
|
const k = node.getRaw("K");
|
||||||
const k = (kids = node.getRaw("K"));
|
if (k instanceof Ref && visited.has(k)) {
|
||||||
if (k instanceof Ref) {
|
return null;
|
||||||
// We're only interested by ref referencing nodes and not an array.
|
|
||||||
if (visited.has(k)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
kids = await xref.fetchAsync(k);
|
|
||||||
if (!Array.isArray(kids)) {
|
|
||||||
kids = [k];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
kids = Array.isArray(kids) ? kids : [kids];
|
const kids = await this.#resolveStructKids(k, xref);
|
||||||
const newKids = [];
|
const newKids = [];
|
||||||
const structElemIndices = [];
|
const structElemIndices = [];
|
||||||
for (let kid of kids) {
|
for (let kid of kids) {
|
||||||
@ -1523,17 +1523,24 @@ class PDFEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the kids.
|
// Get the kids.
|
||||||
let kids = structTreeRoot.dict.get("K");
|
const rawKids = structTreeRoot.dict.getRaw("K");
|
||||||
if (!kids) {
|
if (!rawKids) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
kids = Array.isArray(kids) ? kids : [kids];
|
const kids = await this.#resolveStructKids(rawKids, xref);
|
||||||
for (let kid of kids) {
|
for (let kid of kids) {
|
||||||
const kidRef = kid instanceof Ref ? kid : null;
|
const kidRef = kid instanceof Ref ? kid : null;
|
||||||
if (kidRef && removedStructElements.has(kidRef)) {
|
kid = await xref.fetchIfRefAsync(kid);
|
||||||
|
if (!(kid instanceof Dict)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
kid = await xref.fetchIfRefAsync(kid);
|
let setAsSpan = false;
|
||||||
|
if (kidRef && removedStructElements.has(kidRef)) {
|
||||||
|
if (!isName(kid.get("S"), "Link")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
setAsSpan = true;
|
||||||
|
}
|
||||||
const newKidRef = await this.#cloneStructTreeNode(
|
const newKidRef = await this.#cloneStructTreeNode(
|
||||||
kidRef,
|
kidRef,
|
||||||
kid,
|
kid,
|
||||||
@ -1545,6 +1552,12 @@ class PDFEditor {
|
|||||||
);
|
);
|
||||||
if (newKidRef) {
|
if (newKidRef) {
|
||||||
structTreeKids.push(newKidRef);
|
structTreeKids.push(newKidRef);
|
||||||
|
if (kidRef) {
|
||||||
|
oldRefMapping.put(kidRef, newKidRef);
|
||||||
|
}
|
||||||
|
if (setAsSpan) {
|
||||||
|
this.xref[newKidRef.num].setIfName("S", "Span");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6622,6 +6622,102 @@ small scripts as well as for`);
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Struct trees", function () {
|
describe("Struct trees", function () {
|
||||||
|
it("preserves an indirect StructTreeRoot kid", async function () {
|
||||||
|
const objects = [
|
||||||
|
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R " +
|
||||||
|
"/StructTreeRoot 4 0 R >>\nendobj\n",
|
||||||
|
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
|
||||||
|
"3 0 obj\n<< /Type /Page /Parent 2 0 R /StructParents 0 " +
|
||||||
|
"/MediaBox [0 0 100 100] >>\nendobj\n",
|
||||||
|
"4 0 obj\n<< /Type /StructTreeRoot /K 5 0 R " +
|
||||||
|
"/ParentTree 6 0 R /ParentTreeNextKey 1 >>\nendobj\n",
|
||||||
|
"5 0 obj\n<< /Type /StructElem /S /P /P 4 0 R /Pg 3 0 R " +
|
||||||
|
"/K 0 >>\nendobj\n",
|
||||||
|
"6 0 obj\n<< /Nums [0 [5 0 R]] >>\nendobj\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
let loadingTask = getDocument({ data: assemblePdf(objects) });
|
||||||
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
let page = await pdfDoc.getPage(1);
|
||||||
|
expect((await page.getStructTree()).children[0].role).toEqual("P");
|
||||||
|
const data = await pdfDoc.extractPages([{ document: null }]);
|
||||||
|
await loadingTask.destroy();
|
||||||
|
|
||||||
|
loadingTask = getDocument({ data });
|
||||||
|
pdfDoc = await loadingTask.promise;
|
||||||
|
page = await pdfDoc.getPage(1);
|
||||||
|
const tree = await page.getStructTree();
|
||||||
|
expect(tree.children.length).toEqual(1);
|
||||||
|
expect(tree.children[0].role).toEqual("P");
|
||||||
|
expect(tree.children[0].children[0].type).toEqual("content");
|
||||||
|
await loadingTask.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("extracts pages when the StructTreeRoot /K is dangling", async function () {
|
||||||
|
const objects = [
|
||||||
|
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R " +
|
||||||
|
"/StructTreeRoot 4 0 R >>\nendobj\n",
|
||||||
|
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
|
||||||
|
"3 0 obj\n<< /Type /Page /Parent 2 0 R " +
|
||||||
|
"/MediaBox [0 0 100 100] >>\nendobj\n",
|
||||||
|
"4 0 obj\n<< /Type /StructTreeRoot /K 99 0 R >>\nendobj\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
let loadingTask = getDocument({ data: assemblePdf(objects) });
|
||||||
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
const data = await pdfDoc.extractPages([{ document: null }]);
|
||||||
|
expect(data).not.toBeNull();
|
||||||
|
await loadingTask.destroy();
|
||||||
|
|
||||||
|
loadingTask = getDocument({ data });
|
||||||
|
pdfDoc = await loadingTask.promise;
|
||||||
|
expect(pdfDoc.numPages).toEqual(1);
|
||||||
|
const page = await pdfDoc.getPage(1);
|
||||||
|
expect((await page.getStructTree())?.children.length ?? 0).toEqual(0);
|
||||||
|
await loadingTask.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves a root Link when its annotation is removed", async function () {
|
||||||
|
const objects = [
|
||||||
|
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R " +
|
||||||
|
"/StructTreeRoot 6 0 R >>\nendobj\n",
|
||||||
|
"2 0 obj\n<< /Type /Pages /Kids [3 0 R 4 0 R] " +
|
||||||
|
"/Count 2 >>\nendobj\n",
|
||||||
|
"3 0 obj\n<< /Type /Page /Parent 2 0 R /StructParents 0 " +
|
||||||
|
"/Annots [5 0 R] /MediaBox [0 0 100 100] >>\nendobj\n",
|
||||||
|
"4 0 obj\n<< /Type /Page /Parent 2 0 R " +
|
||||||
|
"/MediaBox [0 0 100 100] >>\nendobj\n",
|
||||||
|
"5 0 obj\n<< /Type /Annot /Subtype /Link /Rect [0 0 10 10] " +
|
||||||
|
"/StructParent 1 /Dest [4 0 R /Fit] >>\nendobj\n",
|
||||||
|
"6 0 obj\n<< /Type /StructTreeRoot /K 7 0 R " +
|
||||||
|
"/ParentTree 8 0 R /ParentTreeNextKey 2 >>\nendobj\n",
|
||||||
|
"7 0 obj\n<< /Type /StructElem /S /Link /P 6 0 R /Pg 3 0 R " +
|
||||||
|
"/K 0 >>\nendobj\n",
|
||||||
|
"8 0 obj\n<< /Nums [0 [7 0 R] 1 7 0 R] >>\nendobj\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
let loadingTask = getDocument({ data: assemblePdf(objects) });
|
||||||
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
let page = await pdfDoc.getPage(1);
|
||||||
|
expect((await page.getStructTree()).children[0].role).toEqual("Link");
|
||||||
|
|
||||||
|
const data = await pdfDoc.extractPages([
|
||||||
|
{ document: null, excludePages: [1] },
|
||||||
|
]);
|
||||||
|
await loadingTask.destroy();
|
||||||
|
|
||||||
|
loadingTask = getDocument({ data });
|
||||||
|
pdfDoc = await loadingTask.promise;
|
||||||
|
expect(pdfDoc.numPages).toEqual(1);
|
||||||
|
page = await pdfDoc.getPage(1);
|
||||||
|
expect((await page.getAnnotations()).length).toEqual(0);
|
||||||
|
const tree = await page.getStructTree();
|
||||||
|
expect(tree.children.length).toEqual(1);
|
||||||
|
expect(tree.children[0].role).toEqual("Span");
|
||||||
|
expect(tree.children[0].children[0].type).toEqual("content");
|
||||||
|
await loadingTask.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it("extract pages and merge struct trees", async function () {
|
it("extract pages and merge struct trees", async function () {
|
||||||
let loadingTask = getDocument(
|
let loadingTask = getDocument(
|
||||||
buildGetDocumentParams("two_paragraphs.pdf")
|
buildGetDocumentParams("two_paragraphs.pdf")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user