mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-25 16:37:22 +02:00
Merge pull request #21566 from calixteman/fix/pdf-editor-unicode-name-trees
Preserve Unicode destination and attachment names
This commit is contained in:
commit
b19cc7c746
@ -2652,7 +2652,15 @@ class PDFEditor {
|
||||
#makeNameNumTree(map, areNames) {
|
||||
const allEntries = map.sort(
|
||||
areNames
|
||||
? ([keyA], [keyB]) => keyA.localeCompare(keyB)
|
||||
? ([keyA], [keyB]) => {
|
||||
if (keyA < keyB) {
|
||||
return -1;
|
||||
}
|
||||
if (keyA > keyB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
: ([keyA], [keyB]) => keyA - keyB
|
||||
);
|
||||
const maxLeaves =
|
||||
@ -2729,7 +2737,7 @@ class PDFEditor {
|
||||
/* keepEscapeSequence = */ true
|
||||
);
|
||||
for (let i = 1; ; i++) {
|
||||
const deduped = `${displayName}_${i}`;
|
||||
const deduped = stringToAsciiOrUTF16BE(`${displayName}_${i}`);
|
||||
if (!embeddedFiles.has(deduped)) {
|
||||
name = deduped;
|
||||
break;
|
||||
@ -2775,7 +2783,10 @@ class PDFEditor {
|
||||
this.namesDict.set(
|
||||
"Dests",
|
||||
this.#makeNameNumTree(
|
||||
Array.from(namedDestinations.entries()),
|
||||
Array.from(namedDestinations, ([name, dest]) => [
|
||||
stringToAsciiOrUTF16BE(name),
|
||||
dest,
|
||||
]),
|
||||
/* areNames = */ true
|
||||
)
|
||||
);
|
||||
|
||||
@ -103,25 +103,7 @@ describe("api", function () {
|
||||
return count;
|
||||
}
|
||||
|
||||
function buildSharedImageResourcePdf() {
|
||||
const streamObject = (num, dict, data) =>
|
||||
`${num} 0 obj\n<< ${dict} /Length ${data.length} >>\n` +
|
||||
`stream\n${data}\nendstream\nendobj\n`;
|
||||
const objects = [
|
||||
"1 0 obj\n<< /Type /Catalog /Pages 2 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 10 10] " +
|
||||
"/Resources << /XObject << /Im0 4 0 R /Im1 4 0 R >> >> " +
|
||||
"/Contents 5 0 R >>\nendobj\n",
|
||||
streamObject(
|
||||
4,
|
||||
"/Type /XObject /Subtype /Image /Width 1 /Height 1 " +
|
||||
"/ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /ASCIIHexDecode",
|
||||
"FF0000>"
|
||||
),
|
||||
streamObject(5, "", "q 10 0 0 10 0 0 cm /Im0 Do Q"),
|
||||
];
|
||||
|
||||
function assemblePdf(objects) {
|
||||
let pdf = "%PDF-1.7\n";
|
||||
const offsets = [];
|
||||
for (const obj of objects) {
|
||||
@ -140,6 +122,26 @@ describe("api", function () {
|
||||
return stringToBytes(pdf);
|
||||
}
|
||||
|
||||
function buildSharedImageResourcePdf() {
|
||||
const streamObject = (num, dict, data) =>
|
||||
`${num} 0 obj\n<< ${dict} /Length ${data.length} >>\n` +
|
||||
`stream\n${data}\nendstream\nendobj\n`;
|
||||
return assemblePdf([
|
||||
"1 0 obj\n<< /Type /Catalog /Pages 2 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 10 10] " +
|
||||
"/Resources << /XObject << /Im0 4 0 R /Im1 4 0 R >> >> " +
|
||||
"/Contents 5 0 R >>\nendobj\n",
|
||||
streamObject(
|
||||
4,
|
||||
"/Type /XObject /Subtype /Image /Width 1 /Height 1 " +
|
||||
"/ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /ASCIIHexDecode",
|
||||
"FF0000>"
|
||||
),
|
||||
streamObject(5, "", "q 10 0 0 10 0 0 cm /Im0 Do Q"),
|
||||
]);
|
||||
}
|
||||
|
||||
function getNamedNodeInXML(node, path) {
|
||||
for (const component of path.split(".")) {
|
||||
if (!node.childNodes) {
|
||||
@ -6404,23 +6406,7 @@ small scripts as well as for`);
|
||||
"5 0 obj\n<< /Type /Annot /Subtype /Link /Rect [10 0 20 10] " +
|
||||
"/A << /S /GoToR /F (other.pdf) /D (target) >> >>\nendobj\n",
|
||||
];
|
||||
let pdfData = "%PDF-1.7\n";
|
||||
const offsets = [];
|
||||
for (const object of objects) {
|
||||
offsets.push(pdfData.length);
|
||||
pdfData += object;
|
||||
}
|
||||
const xrefOffset = pdfData.length;
|
||||
pdfData += `xref\n0 ${objects.length + 1}\n`;
|
||||
pdfData += "0000000000 65535 f \n";
|
||||
for (const offset of offsets) {
|
||||
pdfData += `${offset.toString().padStart(10, "0")} 00000 n \n`;
|
||||
}
|
||||
pdfData +=
|
||||
`trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\n` +
|
||||
`startxref\n${xrefOffset}\n%%EOF\n`;
|
||||
|
||||
let loadingTask = getDocument({ data: stringToBytes(pdfData) });
|
||||
let loadingTask = getDocument({ data: assemblePdf(objects) });
|
||||
let pdfDoc = await loadingTask.promise;
|
||||
const data = await pdfDoc.extractPages([{ document: null }]);
|
||||
await loadingTask.destroy();
|
||||
@ -6435,6 +6421,33 @@ small scripts as well as for`);
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
it("preserves Unicode destination names", async function () {
|
||||
const objects = [
|
||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R " +
|
||||
"/Names << /Dests 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] /Annots [5 0 R] >>\nendobj\n",
|
||||
"4 0 obj\n<< /Names [<FEFF540D> [3 0 R /Fit]] >>\nendobj\n",
|
||||
"5 0 obj\n<< /Type /Annot /Subtype /Link /Rect [0 0 10 10] " +
|
||||
"/Dest <FEFF540D> >>\nendobj\n",
|
||||
];
|
||||
let loadingTask = getDocument({ data: assemblePdf(objects) });
|
||||
let pdfDoc = await loadingTask.promise;
|
||||
expect(Object.keys(await pdfDoc.getDestinations()))
|
||||
.withContext("before extraction")
|
||||
.toEqual(["名"]);
|
||||
const data = await pdfDoc.extractPages([{ document: null }]);
|
||||
await loadingTask.destroy();
|
||||
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(Object.keys(await pdfDoc.getDestinations()))
|
||||
.withContext("after extraction")
|
||||
.toEqual(["名"]);
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
it("keeps colliding deduplicated destination names unique", async function () {
|
||||
let loadingTask = getDocument(
|
||||
buildGetDocumentParams("named_dest_collision_for_editor.pdf")
|
||||
@ -7248,6 +7261,31 @@ small scripts as well as for`);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
it("preserves Unicode EmbeddedFiles (attachments) names when deduplicating", async function () {
|
||||
const objects = [
|
||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R " +
|
||||
"/Names << /EmbeddedFiles 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<< /Names [<FEFF540D> 5 0 R] >>\nendobj\n",
|
||||
"5 0 obj\n<< /Type /Filespec /F (file) /UF <FEFF540D> >>\nendobj\n",
|
||||
];
|
||||
let loadingTask = getDocument({ data: assemblePdf(objects) });
|
||||
let pdfDoc = await loadingTask.promise;
|
||||
const data = await pdfDoc.extractPages([
|
||||
{ document: null },
|
||||
{ document: null },
|
||||
]);
|
||||
await loadingTask.destroy();
|
||||
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
const attachments = await pdfDoc.getAttachments();
|
||||
expect(Array.from(attachments.keys()).sort()).toEqual(["名", "名_1"]);
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("AcroForm", function () {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user