commit 2cb60fac4cd05d426e8dc8caa9bbce182ff91195
parent ee22a4cf3c80c1e80f92b1ed210d58e6cfe83aca
Author: Updatebot <updatebot@mozilla.com>
Date: Mon, 10 Nov 2025 15:10:04 +0000
Bug 1999003 - Update PDF.js to 57334bd20544a713c400476e7bf966491dd1c823 r=calixte
Differential Revision: https://phabricator.services.mozilla.com/D271816
Diffstat:
6 files changed, 766 insertions(+), 63 deletions(-)
diff --git a/toolkit/components/pdfjs/content/build/pdf.mjs b/toolkit/components/pdfjs/content/build/pdf.mjs
@@ -21,8 +21,8 @@
*/
/**
- * pdfjsVersion = 5.4.396
- * pdfjsBuild = 0a2680bca
+ * pdfjsVersion = 5.4.402
+ * pdfjsBuild = 57334bd20
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
@@ -13083,7 +13083,7 @@ function getDocument(src = {}) {
}
const docParams = {
docId,
- apiVersion: "5.4.396",
+ apiVersion: "5.4.402",
data,
password,
disableAutoFetch,
@@ -13329,6 +13329,9 @@ class PDFDocumentProxy {
saveDocument() {
return this._transport.saveDocument();
}
+ extractPages(pageInfos) {
+ return this._transport.extractPages(pageInfos);
+ }
getDownloadInfo() {
return this._transport.downloadInfoCapability.promise;
}
@@ -14333,6 +14336,11 @@ class WorkerTransport {
this.annotationStorage.resetModified();
});
}
+ extractPages(pageInfos) {
+ return this.messageHandler.sendWithPromise("ExtractPages", {
+ pageInfos
+ });
+ }
getPage(pageNumber) {
if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) {
return Promise.reject(new Error("Invalid page request."));
@@ -14663,8 +14671,8 @@ class InternalRenderTask {
}
}
}
-const version = "5.4.396";
-const build = "0a2680bca";
+const version = "5.4.402";
+const build = "57334bd20";
;// ./src/display/editor/color_picker.js
diff --git a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs
@@ -21,8 +21,8 @@
*/
/**
- * pdfjsVersion = 5.4.396
- * pdfjsBuild = 0a2680bca
+ * pdfjsVersion = 5.4.402
+ * pdfjsBuild = 57334bd20
*/
var __webpack_exports__ = {};
diff --git a/toolkit/components/pdfjs/content/build/pdf.worker.mjs b/toolkit/components/pdfjs/content/build/pdf.worker.mjs
@@ -21,8 +21,8 @@
*/
/**
- * pdfjsVersion = 5.4.396
- * pdfjsBuild = 0a2680bca
+ * pdfjsVersion = 5.4.402
+ * pdfjsBuild = 57334bd20
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
@@ -1024,6 +1024,9 @@ class Dict {
getRawValues() {
return [...this._map.values()];
}
+ getRawEntries() {
+ return this._map.entries();
+ }
set(key, value) {
this._map.set(key, value);
}
@@ -1054,6 +1057,11 @@ class Dict {
this.set(key, value);
}
}
+ setIfDict(key, value) {
+ if (value instanceof Dict) {
+ this.set(key, value);
+ }
+ }
has(key) {
return this._map.has(key);
}
@@ -2893,6 +2901,9 @@ class Stream extends BaseStream {
makeSubStream(start, length, dict = null) {
return new Stream(this.bytes.buffer, start, length, dict);
}
+ clone() {
+ return new Stream(this.bytes.buffer, this.start, this.end - this.start, this.dict.clone());
+ }
}
class StringStream extends Stream {
constructor(str) {
@@ -3910,6 +3921,12 @@ class DecodeStream extends BaseStream {
getBaseStreams() {
return this.stream ? this.stream.getBaseStreams() : null;
}
+ clone() {
+ while (!this.eof) {
+ this.readBlock();
+ }
+ return new Stream(this.buffer, this.start, this.end - this.start, this.dict.clone());
+ }
}
class StreamsSequenceStream extends DecodeStream {
constructor(streams, onError = null) {
@@ -37072,6 +37089,7 @@ class StructTreeRoot {
this.roleMap = new Map();
this.structParentIds = null;
this.kidRefToPosition = undefined;
+ this.parentTree = null;
}
getKidPosition(kidRef) {
if (this.kidRefToPosition === undefined) {
@@ -37096,6 +37114,11 @@ class StructTreeRoot {
}
init() {
this.readRoleMap();
+ const parentTree = this.dict.get("ParentTree");
+ if (!parentTree) {
+ return;
+ }
+ this.parentTree = new NumberTree(parentTree, this.xref);
}
#addIdToPage(pageRef, id, type) {
if (!(pageRef instanceof Ref) || id < 0) {
@@ -37718,7 +37741,9 @@ class StructTreePage {
if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) {
return;
}
- const parentTree = this.rootDict.get("ParentTree");
+ const {
+ parentTree
+ } = this.root;
if (!parentTree) {
return;
}
@@ -37728,9 +37753,8 @@ class StructTreePage {
return;
}
const map = new Map();
- const numberTree = new NumberTree(parentTree, this.xref);
if (Number.isInteger(id)) {
- const parentArray = numberTree.get(id);
+ const parentArray = parentTree.get(id);
if (Array.isArray(parentArray)) {
for (const ref of parentArray) {
if (ref instanceof Ref) {
@@ -37743,7 +37767,7 @@ class StructTreePage {
return;
}
for (const [elemId, type] of ids) {
- const obj = numberTree.get(elemId);
+ const obj = parentTree.get(elemId);
if (obj) {
const elem = this.addNode(this.xref.fetchIfRef(obj), map);
if (elem?.kids?.length === 1 && elem.kids[0].type === StructElementType.OBJECT) {
@@ -38472,6 +38496,14 @@ class Catalog {
}
return rawDests;
}
+ get rawPageLabels() {
+ const obj = this.#catDict.getRaw("PageLabels");
+ if (!obj) {
+ return null;
+ }
+ const numberTree = new NumberTree(obj, this.xref);
+ return numberTree.getAll();
+ }
get pageLabels() {
let obj = null;
try {
@@ -38485,15 +38517,13 @@ class Catalog {
return shadow(this, "pageLabels", obj);
}
#readPageLabels() {
- const obj = this.#catDict.getRaw("PageLabels");
- if (!obj) {
+ const nums = this.rawPageLabels;
+ if (!nums) {
return null;
}
const pageLabels = new Array(this.numPages);
let style = null,
prefix = "";
- const numberTree = new NumberTree(obj, this.xref);
- const nums = numberTree.getAll();
let currentLabel = "",
currentIndex = 1;
for (let i = 0, ii = this.numPages; i < ii; i++) {
@@ -54333,6 +54363,9 @@ class DecryptStream extends DecodeStream {
buffer.set(chunk, bufferLength);
this.bufferLength = newLength;
}
+ getOriginalStream() {
+ return this;
+ }
}
;// ./src/core/crypto.js
@@ -55966,7 +55999,7 @@ class Page {
const resources = this.#getInheritableProperty("Resources");
return shadow(this, "resources", resources instanceof Dict ? resources : Dict.empty);
}
- #getBoundingBox(name) {
+ getBoundingBox(name) {
if (this.xfaData) {
return this.xfaData.bbox;
}
@@ -55980,10 +56013,10 @@ class Page {
return null;
}
get mediaBox() {
- return shadow(this, "mediaBox", this.#getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX);
+ return shadow(this, "mediaBox", this.getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX);
}
get cropBox() {
- return shadow(this, "cropBox", this.#getBoundingBox("CropBox") || this.mediaBox);
+ return shadow(this, "cropBox", this.getBoundingBox("CropBox") || this.mediaBox);
}
get userUnit() {
const obj = this.pageDict.get("UserUnit");
@@ -57840,28 +57873,25 @@ class MessageHandler {
async function writeObject(ref, obj, buffer, {
- encrypt = null
+ encrypt = null,
+ encryptRef = null
}) {
- const transform = encrypt?.createCipherTransform(ref.num, ref.gen);
+ const transform = encrypt && encryptRef !== ref ? encrypt.createCipherTransform(ref.num, ref.gen) : null;
buffer.push(`${ref.num} ${ref.gen} obj\n`);
- if (obj instanceof Dict) {
- await writeDict(obj, buffer, transform);
- } else if (obj instanceof BaseStream) {
- await writeStream(obj, buffer, transform);
- } else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) {
- await writeArray(obj, buffer, transform);
- }
+ await writeValue(obj, buffer, transform);
buffer.push("\nendobj\n");
}
async function writeDict(dict, buffer, transform) {
buffer.push("<<");
- for (const key of dict.getKeys()) {
+ for (const [key, rawObj] of dict.getRawEntries()) {
buffer.push(` /${escapePDFName(key)} `);
- await writeValue(dict.getRaw(key), buffer, transform);
+ await writeValue(rawObj, buffer, transform);
}
buffer.push(">>");
}
async function writeStream(stream, buffer, transform) {
+ stream = stream.getOriginalStream();
+ stream.reset();
let bytes = stream.getBytes();
const {
dict
@@ -57870,7 +57900,7 @@ async function writeStream(stream, buffer, transform) {
const filterZero = Array.isArray(filter) ? await dict.xref.fetchIfRefAsync(filter[0]) : filter;
const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode");
const MIN_LENGTH_FOR_COMPRESSING = 256;
- if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode) {
+ if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING && !isFilterZeroFlateDecode) {
try {
const cs = new CompressionStream("deflate");
const writer = cs.writable.getWriter();
@@ -57910,14 +57940,11 @@ async function writeStream(stream, buffer, transform) {
}
async function writeArray(array, buffer, transform) {
buffer.push("[");
- let first = true;
- for (const val of array) {
- if (!first) {
+ for (let i = 0, ii = array.length; i < ii; i++) {
+ await writeValue(array[i], buffer, transform);
+ if (i < ii - 1) {
buffer.push(" ");
- } else {
- first = false;
}
- await writeValue(val, buffer, transform);
}
buffer.push("]");
}
@@ -57934,7 +57961,7 @@ async function writeValue(value, buffer, transform) {
}
buffer.push(`(${escapeString(value)})`);
} else if (typeof value === "number") {
- buffer.push(numberToString(value));
+ buffer.push(value.toString());
} else if (typeof value === "boolean") {
buffer.push(value.toString());
} else if (value instanceof Dict) {
@@ -58073,7 +58100,7 @@ async function getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer) {
}
computeIDs(baseOffset, xrefInfo, newXref);
buffer.push("trailer\n");
- await writeDict(newXref, buffer);
+ await writeDict(newXref, buffer, null);
buffer.push("\nstartxref\n", baseOffset.toString(), "\n%%EOF\n");
}
function getIndexes(newRefs) {
@@ -58095,11 +58122,16 @@ async function getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer
let maxGen = 0;
for (const {
ref,
- data
+ data,
+ objStreamRef,
+ index
} of newRefs) {
let gen;
maxOffset = Math.max(maxOffset, baseOffset);
- if (data !== null) {
+ if (objStreamRef) {
+ gen = index;
+ xrefTableData.push([2, objStreamRef.num, gen]);
+ } else if (data !== null) {
gen = Math.min(ref.gen, 0xffff);
xrefTableData.push([1, baseOffset, gen]);
baseOffset += data.length;
@@ -58131,12 +58163,12 @@ async function getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer
function computeIDs(baseOffset, xrefInfo, newXref) {
if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) {
const md5 = computeMD5(baseOffset, xrefInfo);
- newXref.set("ID", [xrefInfo.fileIds[0], md5]);
+ newXref.set("ID", [xrefInfo.fileIds[0] || md5, md5]);
}
}
function getTrailerDict(xrefInfo, changes, useXrefStream) {
const newXref = new Dict(null);
- newXref.set("Prev", xrefInfo.startXRef);
+ newXref.setIfDefined("Prev", xrefInfo?.startXRef);
const refForXrefTable = xrefInfo.newRef;
if (useXrefStream) {
changes.put(refForXrefTable, {
@@ -58147,22 +58179,27 @@ function getTrailerDict(xrefInfo, changes, useXrefStream) {
} else {
newXref.set("Size", refForXrefTable.num);
}
- if (xrefInfo.rootRef !== null) {
- newXref.set("Root", xrefInfo.rootRef);
- }
- if (xrefInfo.infoRef !== null) {
- newXref.set("Info", xrefInfo.infoRef);
- }
- if (xrefInfo.encryptRef !== null) {
- newXref.set("Encrypt", xrefInfo.encryptRef);
- }
+ newXref.setIfDefined("Root", xrefInfo?.rootRef);
+ newXref.setIfDefined("Info", xrefInfo?.infoRef);
+ newXref.setIfDefined("Encrypt", xrefInfo?.encryptRef);
return newXref;
}
async function writeChanges(changes, xref, buffer = []) {
const newRefs = [];
for (const [ref, {
- data
+ data,
+ objStreamRef,
+ index
}] of changes.items()) {
+ if (objStreamRef) {
+ newRefs.push({
+ ref,
+ data,
+ objStreamRef,
+ index
+ });
+ continue;
+ }
if (data === null || typeof data === "string") {
newRefs.push({
ref,
@@ -58238,6 +58275,577 @@ async function incrementalUpdate({
return array;
}
+;// ./src/core/editor/pdf_editor.js
+
+
+
+
+
+
+const MAX_LEAVES_PER_PAGES_NODE = 16;
+const MAX_IN_NAME_TREE_NODE = 64;
+class PageData {
+ constructor(page, documentData) {
+ this.page = page;
+ this.documentData = documentData;
+ this.annotations = null;
+ documentData.pagesMap.put(page.ref, this);
+ }
+}
+class DocumentData {
+ constructor(document) {
+ this.document = document;
+ this.pageLabels = null;
+ this.pagesMap = new RefSetCache();
+ this.oldRefMapping = new RefSetCache();
+ }
+}
+class PDFEditor {
+ constructor({
+ useObjectStreams = true,
+ title = "",
+ author = ""
+ } = {}) {
+ this.hasSingleFile = false;
+ this.currentDocument = null;
+ this.oldPages = [];
+ this.newPages = [];
+ this.xref = [null];
+ this.newRefCount = 1;
+ [this.rootRef, this.rootDict] = this.newDict;
+ [this.infoRef, this.infoDict] = this.newDict;
+ [this.pagesRef, this.pagesDict] = this.newDict;
+ this.namesDict = null;
+ this.useObjectStreams = useObjectStreams;
+ this.objStreamRefs = useObjectStreams ? new Set() : null;
+ this.version = "1.7";
+ this.title = title;
+ this.author = author;
+ this.pageLabels = null;
+ }
+ get newRef() {
+ const ref = Ref.get(this.newRefCount++, 0);
+ return ref;
+ }
+ get newDict() {
+ const ref = this.newRef;
+ const dict = this.xref[ref.num] = new Dict();
+ return [ref, dict];
+ }
+ async #cloneObject(obj, xref) {
+ const ref = this.newRef;
+ this.xref[ref.num] = await this.#collectDependencies(obj, true, xref);
+ return ref;
+ }
+ async #collectDependencies(obj, mustClone, xref) {
+ if (obj instanceof Ref) {
+ const {
+ currentDocument: {
+ oldRefMapping
+ }
+ } = this;
+ let newRef = oldRefMapping.get(obj);
+ if (newRef) {
+ return newRef;
+ }
+ newRef = this.newRef;
+ oldRefMapping.put(obj, newRef);
+ obj = await xref.fetchAsync(obj);
+ this.xref[newRef.num] = await this.#collectDependencies(obj, true, xref);
+ return newRef;
+ }
+ const promises = [];
+ if (Array.isArray(obj)) {
+ if (mustClone) {
+ obj = obj.slice();
+ }
+ for (let i = 0, ii = obj.length; i < ii; i++) {
+ promises.push(this.#collectDependencies(obj[i], true, xref).then(newObj => obj[i] = newObj));
+ }
+ await Promise.all(promises);
+ return obj;
+ }
+ let dict;
+ if (obj instanceof BaseStream) {
+ ({
+ dict
+ } = obj = obj.getOriginalStream().clone());
+ } else if (obj instanceof Dict) {
+ if (mustClone) {
+ obj = obj.clone();
+ }
+ dict = obj;
+ }
+ if (dict) {
+ for (const [key, rawObj] of dict.getRawEntries()) {
+ promises.push(this.#collectDependencies(rawObj, true, xref).then(newObj => dict.set(key, newObj)));
+ }
+ await Promise.all(promises);
+ }
+ return obj;
+ }
+ async extractPages(pageInfos) {
+ const promises = [];
+ let newIndex = 0;
+ this.hasSingleFile = pageInfos.length === 1;
+ for (const {
+ document,
+ includePages,
+ excludePages
+ } of pageInfos) {
+ if (!document) {
+ continue;
+ }
+ const documentData = new DocumentData(document);
+ promises.push(this.#collectDocumentData(documentData));
+ let keptIndices, keptRanges, deletedIndices, deletedRanges;
+ for (const page of includePages || []) {
+ if (Array.isArray(page)) {
+ (keptRanges ||= []).push(page);
+ } else {
+ (keptIndices ||= new Set()).add(page);
+ }
+ }
+ for (const page of excludePages || []) {
+ if (Array.isArray(page)) {
+ (deletedRanges ||= []).push(page);
+ } else {
+ (deletedIndices ||= new Set()).add(page);
+ }
+ }
+ for (let i = 0, ii = document.numPages; i < ii; i++) {
+ if (deletedIndices?.has(i)) {
+ continue;
+ }
+ if (deletedRanges) {
+ let isDeleted = false;
+ for (const [start, end] of deletedRanges) {
+ if (i >= start && i <= end) {
+ isDeleted = true;
+ break;
+ }
+ }
+ if (isDeleted) {
+ continue;
+ }
+ }
+ let takePage = false;
+ if (keptIndices) {
+ takePage = keptIndices.has(i);
+ }
+ if (!takePage && keptRanges) {
+ for (const [start, end] of keptRanges) {
+ if (i >= start && i <= end) {
+ takePage = true;
+ break;
+ }
+ }
+ }
+ if (!takePage && !keptIndices && !keptRanges) {
+ takePage = true;
+ }
+ if (!takePage) {
+ continue;
+ }
+ const newPageIndex = newIndex++;
+ promises.push(document.getPage(i).then(page => {
+ this.oldPages[newPageIndex] = new PageData(page, documentData);
+ }));
+ }
+ }
+ await Promise.all(promises);
+ promises.length = 0;
+ this.#collectPageLabels();
+ for (const page of this.oldPages) {
+ promises.push(this.#postCollectPageData(page));
+ }
+ await Promise.all(promises);
+ for (let i = 0, ii = this.oldPages.length; i < ii; i++) {
+ this.newPages[i] = await this.#makePageCopy(i, null);
+ }
+ return this.writePDF();
+ }
+ async #collectDocumentData(documentData) {
+ const {
+ document
+ } = documentData;
+ await document.pdfManager.ensureCatalog("rawPageLabels").then(pageLabels => documentData.pageLabels = pageLabels);
+ }
+ async #postCollectPageData(pageData) {
+ const {
+ page: {
+ xref,
+ annotations
+ }
+ } = pageData;
+ if (!annotations) {
+ return;
+ }
+ const promises = [];
+ let newAnnotations = [];
+ let newIndex = 0;
+ for (const annotationRef of annotations) {
+ const newAnnotationIndex = newIndex++;
+ promises.push(xref.fetchIfRefAsync(annotationRef).then(async annotationDict => {
+ if (!isName(annotationDict.get("Subtype"), "Link")) {
+ newAnnotations[newAnnotationIndex] = annotationRef;
+ }
+ }));
+ }
+ await Promise.all(promises);
+ newAnnotations = newAnnotations.filter(annot => !!annot);
+ pageData.annotations = newAnnotations.length > 0 ? newAnnotations : null;
+ }
+ async #collectPageLabels() {
+ if (!this.hasSingleFile) {
+ return;
+ }
+ const {
+ documentData: {
+ document,
+ pageLabels
+ }
+ } = this.oldPages[0];
+ if (!pageLabels) {
+ return;
+ }
+ const numPages = document.numPages;
+ const oldPageLabels = [];
+ const oldPageIndices = new Set(this.oldPages.map(({
+ page: {
+ pageIndex
+ }
+ }) => pageIndex));
+ let currentLabel = null;
+ let stFirstIndex = -1;
+ for (let i = 0; i < numPages; i++) {
+ const newLabel = pageLabels.get(i);
+ if (newLabel) {
+ currentLabel = newLabel;
+ stFirstIndex = currentLabel.has("St") ? i : -1;
+ }
+ if (!oldPageIndices.has(i)) {
+ continue;
+ }
+ if (stFirstIndex !== -1) {
+ const st = currentLabel.get("St");
+ currentLabel = currentLabel.clone();
+ currentLabel.set("St", st + (i - stFirstIndex));
+ stFirstIndex = -1;
+ }
+ oldPageLabels.push(currentLabel);
+ }
+ currentLabel = oldPageLabels[0];
+ let currentIndex = 0;
+ const newPageLabels = this.pageLabels = [[0, currentLabel]];
+ for (let i = 0, ii = oldPageLabels.length; i < ii; i++) {
+ const label = oldPageLabels[i];
+ if (label === currentLabel) {
+ continue;
+ }
+ currentIndex = i;
+ currentLabel = label;
+ newPageLabels.push([currentIndex, currentLabel]);
+ }
+ }
+ async #makePageCopy(pageIndex) {
+ const {
+ page,
+ documentData,
+ annotations
+ } = this.oldPages[pageIndex];
+ this.currentDocument = documentData;
+ const {
+ oldRefMapping
+ } = documentData;
+ const {
+ xref,
+ rotate,
+ mediaBox,
+ resources,
+ ref: oldPageRef
+ } = page;
+ const pageRef = this.newRef;
+ const pageDict = this.xref[pageRef.num] = page.pageDict.clone();
+ oldRefMapping.put(oldPageRef, pageRef);
+ for (const key of ["Rotate", "MediaBox", "CropBox", "BleedBox", "TrimBox", "ArtBox", "Resources", "Annots", "Parent", "UserUnit"]) {
+ pageDict.delete(key);
+ }
+ const lastRef = this.newRefCount;
+ await this.#collectDependencies(pageDict, false, xref);
+ pageDict.set("Rotate", rotate);
+ pageDict.set("MediaBox", mediaBox);
+ for (const boxName of ["CropBox", "BleedBox", "TrimBox", "ArtBox"]) {
+ const box = page.getBoundingBox(boxName);
+ if (box?.some((value, index) => value !== mediaBox[index])) {
+ pageDict.set(boxName, box);
+ }
+ }
+ const userUnit = page.userUnit;
+ if (userUnit !== 1) {
+ pageDict.set("UserUnit", userUnit);
+ }
+ pageDict.setIfDict("Resources", await this.#collectDependencies(resources, true, xref));
+ pageDict.setIfArray("Annots", await this.#collectDependencies(annotations, true, xref));
+ if (this.useObjectStreams) {
+ const newLastRef = this.newRefCount;
+ const pageObjectRefs = [];
+ for (let i = lastRef; i < newLastRef; i++) {
+ const obj = this.xref[i];
+ if (obj instanceof BaseStream) {
+ continue;
+ }
+ pageObjectRefs.push(Ref.get(i, 0));
+ }
+ for (let i = 0; i < pageObjectRefs.length; i += 0xffff) {
+ const objStreamRef = this.newRef;
+ this.objStreamRefs.add(objStreamRef.num);
+ this.xref[objStreamRef.num] = pageObjectRefs.slice(i, i + 0xffff);
+ }
+ }
+ this.currentDocument = null;
+ return pageRef;
+ }
+ #makePageTree() {
+ const {
+ newPages: pages,
+ rootDict,
+ pagesRef,
+ pagesDict
+ } = this;
+ rootDict.set("Pages", pagesRef);
+ pagesDict.setIfName("Type", "Pages");
+ pagesDict.set("Count", pages.length);
+ const maxLeaves = MAX_LEAVES_PER_PAGES_NODE <= 1 ? pages.length : MAX_LEAVES_PER_PAGES_NODE;
+ const stack = [{
+ dict: pagesDict,
+ kids: pages,
+ parentRef: pagesRef
+ }];
+ while (stack.length > 0) {
+ const {
+ dict,
+ kids,
+ parentRef
+ } = stack.pop();
+ if (kids.length <= maxLeaves) {
+ dict.set("Kids", kids);
+ for (const ref of kids) {
+ this.xref[ref.num].set("Parent", parentRef);
+ }
+ continue;
+ }
+ const chunkSize = Math.max(maxLeaves, Math.ceil(kids.length / maxLeaves));
+ const kidsChunks = [];
+ for (let i = 0; i < kids.length; i += chunkSize) {
+ kidsChunks.push(kids.slice(i, i + chunkSize));
+ }
+ const kidsRefs = [];
+ dict.set("Kids", kidsRefs);
+ for (const chunk of kidsChunks) {
+ const [kidRef, kidDict] = this.newDict;
+ kidsRefs.push(kidRef);
+ kidDict.setIfName("Type", "Pages");
+ kidDict.set("Parent", parentRef);
+ kidDict.set("Count", chunk.length);
+ stack.push({
+ dict: kidDict,
+ kids: chunk,
+ parentRef: kidRef
+ });
+ }
+ }
+ }
+ #makeNameNumTree(map, areNames) {
+ const allEntries = map.sort(areNames ? ([keyA], [keyB]) => keyA.localeCompare(keyB) : ([keyA], [keyB]) => keyA - keyB);
+ const maxLeaves = MAX_IN_NAME_TREE_NODE <= 1 ? allEntries.length : MAX_IN_NAME_TREE_NODE;
+ const [treeRef, treeDict] = this.newDict;
+ const stack = [{
+ dict: treeDict,
+ entries: allEntries
+ }];
+ const valueType = areNames ? "Names" : "Nums";
+ while (stack.length > 0) {
+ const {
+ dict,
+ entries
+ } = stack.pop();
+ if (entries.length <= maxLeaves) {
+ dict.set("Limits", [entries[0][0], entries.at(-1)[0]]);
+ dict.set(valueType, entries.flat());
+ continue;
+ }
+ const entriesChunks = [];
+ const chunkSize = Math.max(maxLeaves, Math.ceil(entries.length / maxLeaves));
+ for (let i = 0; i < entries.length; i += chunkSize) {
+ entriesChunks.push(entries.slice(i, i + chunkSize));
+ }
+ const entriesRefs = [];
+ dict.set("Kids", entriesRefs);
+ for (const chunk of entriesChunks) {
+ const [entriesRef, entriesDict] = this.newDict;
+ entriesRefs.push(entriesRef);
+ entriesDict.set("Limits", [chunk[0][0], chunk.at(-1)[0]]);
+ stack.push({
+ dict: entriesDict,
+ entries: chunk
+ });
+ }
+ }
+ return treeRef;
+ }
+ #makePageLabelsTree() {
+ const {
+ pageLabels
+ } = this;
+ if (!pageLabels || pageLabels.length === 0) {
+ return;
+ }
+ const {
+ rootDict
+ } = this;
+ const pageLabelsRef = this.#makeNameNumTree(this.pageLabels, false);
+ rootDict.set("PageLabels", pageLabelsRef);
+ }
+ async #makeRoot() {
+ const {
+ rootDict
+ } = this;
+ rootDict.setIfName("Type", "Catalog");
+ rootDict.set("Version", this.version);
+ this.#makePageTree();
+ this.#makePageLabelsTree();
+ }
+ #makeInfo() {
+ const infoMap = new Map();
+ if (this.hasSingleFile) {
+ const {
+ xref: {
+ trailer
+ }
+ } = this.oldPages[0].documentData.document;
+ const oldInfoDict = trailer.get("Info");
+ for (const [key, value] of oldInfoDict || []) {
+ if (typeof value === "string") {
+ infoMap.set(key, stringToPDFString(value));
+ }
+ }
+ }
+ infoMap.delete("ModDate");
+ infoMap.set("CreationDate", getModificationDate());
+ infoMap.set("Creator", "PDF.js");
+ infoMap.set("Producer", "Firefox");
+ if (this.author) {
+ infoMap.set("Author", this.author);
+ }
+ if (this.title) {
+ infoMap.set("Title", this.title);
+ }
+ for (const [key, value] of infoMap) {
+ this.infoDict.set(key, stringToAsciiOrUTF16BE(value));
+ }
+ return infoMap;
+ }
+ async #makeEncrypt() {
+ if (!this.hasSingleFile) {
+ return [null, null, null];
+ }
+ const {
+ documentData
+ } = this.oldPages[0];
+ const {
+ document: {
+ xref: {
+ trailer,
+ encrypt
+ }
+ }
+ } = documentData;
+ if (!trailer.has("Encrypt")) {
+ return [null, null, null];
+ }
+ const encryptDict = trailer.get("Encrypt");
+ if (!(encryptDict instanceof Dict)) {
+ return [null, null, null];
+ }
+ this.currentDocument = documentData;
+ const result = [await this.#cloneObject(encryptDict, trailer.xref), encrypt, trailer.get("ID")];
+ this.currentDocument = null;
+ return result;
+ }
+ async #createChanges() {
+ const changes = new RefSetCache();
+ changes.put(Ref.get(0, 0xffff), {
+ data: null
+ });
+ for (let i = 1, ii = this.xref.length; i < ii; i++) {
+ if (this.objStreamRefs?.has(i)) {
+ await this.#createObjectStream(Ref.get(i, 0), this.xref[i], changes);
+ } else {
+ changes.put(Ref.get(i, 0), {
+ data: this.xref[i]
+ });
+ }
+ }
+ return [changes, this.newRef];
+ }
+ async #createObjectStream(objStreamRef, objRefs, changes) {
+ const streamBuffer = [""];
+ const objOffsets = [];
+ let offset = 0;
+ const buffer = [];
+ for (let i = 0, ii = objRefs.length; i < ii; i++) {
+ const objRef = objRefs[i];
+ changes.put(objRef, {
+ data: null,
+ objStreamRef,
+ index: i
+ });
+ objOffsets.push(`${objRef.num} ${offset}`);
+ const data = this.xref[objRef.num];
+ await writeValue(data, buffer, null);
+ const obj = buffer.join("");
+ buffer.length = 0;
+ streamBuffer.push(obj);
+ offset += obj.length + 1;
+ }
+ streamBuffer[0] = objOffsets.join("\n");
+ const objStream = new StringStream(streamBuffer.join("\n"));
+ const objStreamDict = objStream.dict = new Dict();
+ objStreamDict.setIfName("Type", "ObjStm");
+ objStreamDict.set("N", objRefs.length);
+ objStreamDict.set("First", streamBuffer[0].length + 1);
+ changes.put(objStreamRef, {
+ data: objStream
+ });
+ }
+ async writePDF() {
+ await this.#makeRoot();
+ const infoMap = this.#makeInfo();
+ const [encryptRef, encrypt, fileIds] = await this.#makeEncrypt();
+ const [changes, xrefTableRef] = await this.#createChanges();
+ const header = [...`%PDF-${this.version}\n%`.split("").map(c => c.charCodeAt(0)), 0xfa, 0xde, 0xfa, 0xce];
+ return incrementalUpdate({
+ originalData: new Uint8Array(header),
+ changes,
+ xrefInfo: {
+ startXRef: null,
+ rootRef: this.rootRef,
+ infoRef: this.infoRef,
+ encryptRef,
+ newRef: xrefTableRef,
+ fileIds: fileIds || [null, null],
+ infoMap
+ },
+ useXrefStream: this.useObjectStreams,
+ xref: {
+ encrypt,
+ encryptRef
+ }
+ });
+ }
+}
+
;// ./src/core/worker_stream.js
class PDFWorkerStream {
@@ -58356,6 +58964,7 @@ class PDFWorkerStreamRangeReader {
+
class WorkerTask {
constructor(name) {
this.name = name;
@@ -58407,7 +59016,7 @@ class WorkerMessageHandler {
docId,
apiVersion
} = docParams;
- const workerVersion = "5.4.396";
+ const workerVersion = "5.4.402";
if (apiVersion !== workerVersion) {
throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
}
@@ -58726,6 +59335,92 @@ class WorkerMessageHandler {
handler.on("GetCalculationOrderIds", function (data) {
return pdfManager.ensureDoc("calculationOrderIds");
});
+ handler.on("ExtractPages", async function ({
+ pageInfos
+ }) {
+ if (!pageInfos) {
+ warn("extractPages: nothing to extract.");
+ return null;
+ }
+ if (!Array.isArray(pageInfos)) {
+ pageInfos = [pageInfos];
+ }
+ let newDocumentId = 0;
+ for (const pageInfo of pageInfos) {
+ if (pageInfo.document === null) {
+ pageInfo.document = pdfManager.pdfDocument;
+ } else if (ArrayBuffer.isView(pageInfo.document)) {
+ const manager = new LocalPdfManager({
+ source: pageInfo.document,
+ docId: `${docId}_extractPages_${newDocumentId++}`,
+ handler,
+ password: pageInfo.password ?? null,
+ evaluatorOptions: Object.assign({}, pdfManager.evaluatorOptions)
+ });
+ let recoveryMode = false;
+ let isValid = true;
+ while (true) {
+ try {
+ await manager.requestLoadedStream();
+ await manager.ensureDoc("checkHeader");
+ await manager.ensureDoc("parseStartXRef");
+ await manager.ensureDoc("parse", [recoveryMode]);
+ break;
+ } catch (e) {
+ if (e instanceof XRefParseException) {
+ if (recoveryMode === false) {
+ recoveryMode = true;
+ continue;
+ } else {
+ isValid = false;
+ warn("extractPages: XRefParseException.");
+ }
+ } else if (e instanceof PasswordException) {
+ const task = new WorkerTask(`PasswordException: response ${e.code}`);
+ startWorkerTask(task);
+ try {
+ const {
+ password
+ } = await handler.sendWithPromise("PasswordRequest", e);
+ manager.updatePassword(password);
+ } catch {
+ isValid = false;
+ warn("extractPages: invalid password.");
+ } finally {
+ finishWorkerTask(task);
+ }
+ } else {
+ isValid = false;
+ warn("extractPages: invalid document.");
+ }
+ if (!isValid) {
+ break;
+ }
+ }
+ }
+ if (!isValid) {
+ pageInfo.document = null;
+ }
+ const isPureXfa = await manager.ensureDoc("isPureXfa");
+ if (isPureXfa) {
+ pageInfo.document = null;
+ warn("extractPages does not support pure XFA documents.");
+ } else {
+ pageInfo.document = manager.pdfDocument;
+ }
+ } else {
+ warn("extractPages: invalid document.");
+ }
+ }
+ try {
+ const pdfEditor = new PDFEditor();
+ const buffer = await pdfEditor.extractPages(pageInfos);
+ return buffer;
+ } catch (reason) {
+ console.error(reason);
+ return null;
+ }
+ });
handler.on("SaveDocument", async function ({
isPureXfa,
numPages,
diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs
@@ -21,8 +21,8 @@
*/
/**
- * pdfjsVersion = 5.4.396
- * pdfjsBuild = 0a2680bca
+ * pdfjsVersion = 5.4.402
+ * pdfjsBuild = 57334bd20
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
@@ -8300,7 +8300,7 @@ class PDFViewer {
#textLayerMode = TextLayerMode.ENABLE;
#viewerAlert = null;
constructor(options) {
- const viewerVersion = "5.4.396";
+ const viewerVersion = "5.4.402";
if (version !== viewerVersion) {
throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
}
diff --git a/toolkit/components/pdfjs/content/web/viewer.mjs b/toolkit/components/pdfjs/content/web/viewer.mjs
@@ -21,8 +21,8 @@
*/
/**
- * pdfjsVersion = 5.4.396
- * pdfjsBuild = 0a2680bca
+ * pdfjsVersion = 5.4.402
+ * pdfjsBuild = 57334bd20
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
@@ -11485,7 +11485,7 @@ class PDFViewer {
#textLayerMode = TextLayerMode.ENABLE;
#viewerAlert = null;
constructor(options) {
- const viewerVersion = "5.4.396";
+ const viewerVersion = "5.4.402";
if (version !== viewerVersion) {
throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
}
diff --git a/toolkit/components/pdfjs/moz.yaml b/toolkit/components/pdfjs/moz.yaml
@@ -20,8 +20,8 @@ origin:
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
- release: 0a2680bca627243d157e18a52189cc19b635993e (2025-11-02T12:14:45Z).
- revision: 0a2680bca627243d157e18a52189cc19b635993e
+ release: 57334bd20544a713c400476e7bf966491dd1c823 (2025-11-07T16:21:38Z).
+ revision: 57334bd20544a713c400476e7bf966491dd1c823
# The package's license, where possible using the mnemonic from
# https://spdx.org/licenses/