mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-14 01:04:04 +02:00
parent
2b95a8eb38
commit
4b4ab10c54
@ -233,6 +233,9 @@ const RENDERING_CANCELLED_TIMEOUT = 100; // ms
|
||||
* The default value is {DOMFilterFactory}.
|
||||
* @property {boolean} [enableHWA] - Enables hardware acceleration for
|
||||
* rendering. The default value is `false`.
|
||||
* @property {Object} [pagesMapper] - The pages mapper that will be used to map
|
||||
* page ids and page numbers. It's used when the page order is changed or some
|
||||
* pages are removed, cloned, etc.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -342,6 +345,7 @@ function getDocument(src = {}) {
|
||||
: DOMFilterFactory);
|
||||
const enableHWA = src.enableHWA === true;
|
||||
const useWasm = src.useWasm !== false;
|
||||
const pagesMapper = src.pagesMapper || new PagesMapper();
|
||||
|
||||
// Parameters whose default values depend on other parameters.
|
||||
const length = rangeTransport ? rangeTransport.length : (src.length ?? NaN);
|
||||
@ -511,7 +515,8 @@ function getDocument(src = {}) {
|
||||
task,
|
||||
networkStream,
|
||||
transportParams,
|
||||
transportFactory
|
||||
transportFactory,
|
||||
pagesMapper
|
||||
);
|
||||
task._transport = transport;
|
||||
messageHandler.send("Ready", null);
|
||||
@ -761,6 +766,13 @@ class PDFDocumentProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {PagesMapper} The pages mapper instance.
|
||||
*/
|
||||
get pagesMapper() {
|
||||
return this._transport.pagesMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {AnnotationStorage} Storage for annotation data in forms.
|
||||
*/
|
||||
@ -1324,9 +1336,9 @@ class PDFDocumentProxy {
|
||||
class PDFPageProxy {
|
||||
#pendingCleanup = false;
|
||||
|
||||
#pagesMapper = PagesMapper.instance;
|
||||
#pagesMapper = null;
|
||||
|
||||
constructor(pageIndex, pageInfo, transport, pdfBug = false) {
|
||||
constructor(pageIndex, pageInfo, transport, pagesMapper, pdfBug = false) {
|
||||
this._pageIndex = pageIndex;
|
||||
this._pageInfo = pageInfo;
|
||||
this._transport = transport;
|
||||
@ -1339,6 +1351,7 @@ class PDFPageProxy {
|
||||
this._intentStates = new Map();
|
||||
this.destroyed = false;
|
||||
this.recordedBBoxes = null;
|
||||
this.#pagesMapper = pagesMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2402,9 +2415,14 @@ class WorkerTransport {
|
||||
|
||||
#passwordCapability = null;
|
||||
|
||||
#pagesMapper = PagesMapper.instance;
|
||||
|
||||
constructor(messageHandler, loadingTask, networkStream, params, factory) {
|
||||
constructor(
|
||||
messageHandler,
|
||||
loadingTask,
|
||||
networkStream,
|
||||
params,
|
||||
factory,
|
||||
pagesMapper
|
||||
) {
|
||||
this.messageHandler = messageHandler;
|
||||
this.loadingTask = loadingTask;
|
||||
this.#networkStream = networkStream;
|
||||
@ -2429,7 +2447,8 @@ class WorkerTransport {
|
||||
|
||||
this.setupMessageHandler();
|
||||
|
||||
this.#pagesMapper.addListener(this.#updateCaches.bind(this));
|
||||
this.pagesMapper = pagesMapper;
|
||||
this.pagesMapper.addListener(this.#updateCaches.bind(this));
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||
// For testing purposes.
|
||||
@ -2458,8 +2477,8 @@ class WorkerTransport {
|
||||
#updateCaches() {
|
||||
const newPageCache = new Map();
|
||||
const newPromiseCache = new Map();
|
||||
for (let i = 0, ii = this.#pagesMapper.pagesNumber; i < ii; i++) {
|
||||
const prevPageIndex = this.#pagesMapper.getPrevPageNumber(i + 1) - 1;
|
||||
for (let i = 0, ii = this.pagesMapper.pagesNumber; i < ii; i++) {
|
||||
const prevPageIndex = this.pagesMapper.getPrevPageNumber(i + 1) - 1;
|
||||
const page = this.#pageCache.get(prevPageIndex);
|
||||
if (page) {
|
||||
newPageCache.set(i, page);
|
||||
@ -2730,7 +2749,7 @@ class WorkerTransport {
|
||||
});
|
||||
|
||||
messageHandler.on("GetDoc", ({ pdfInfo }) => {
|
||||
this.#pagesMapper.pagesNumber = pdfInfo.numPages;
|
||||
this.pagesMapper.pagesNumber = pdfInfo.numPages;
|
||||
this._numPages = pdfInfo.numPages;
|
||||
this._htmlForXfa = pdfInfo.htmlForXfa;
|
||||
delete pdfInfo.htmlForXfa;
|
||||
@ -2947,12 +2966,12 @@ class WorkerTransport {
|
||||
if (
|
||||
!Number.isInteger(pageNumber) ||
|
||||
pageNumber <= 0 ||
|
||||
pageNumber > this.#pagesMapper.pagesNumber
|
||||
pageNumber > this.pagesMapper.pagesNumber
|
||||
) {
|
||||
return Promise.reject(new Error("Invalid page request."));
|
||||
}
|
||||
const pageIndex = pageNumber - 1;
|
||||
const newPageIndex = this.#pagesMapper.getPageId(pageNumber) - 1;
|
||||
const newPageIndex = this.pagesMapper.getPageId(pageNumber) - 1;
|
||||
|
||||
const cachedPromise = this.#pagePromises.get(pageIndex);
|
||||
if (cachedPromise) {
|
||||
@ -2974,6 +2993,7 @@ class WorkerTransport {
|
||||
pageIndex,
|
||||
pageInfo,
|
||||
this,
|
||||
this.pagesMapper,
|
||||
this._params.pdfBug
|
||||
);
|
||||
this.#pageCache.set(pageIndex, page);
|
||||
@ -2991,12 +3011,12 @@ class WorkerTransport {
|
||||
num: ref.num,
|
||||
gen: ref.gen,
|
||||
});
|
||||
return this.#pagesMapper.getPageNumber(index + 1) - 1;
|
||||
return this.pagesMapper.getPageNumber(index + 1) - 1;
|
||||
}
|
||||
|
||||
getAnnotations(pageIndex, intent) {
|
||||
return this.messageHandler.sendWithPromise("GetAnnotations", {
|
||||
pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||
pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||
intent,
|
||||
});
|
||||
}
|
||||
@ -3063,13 +3083,13 @@ class WorkerTransport {
|
||||
|
||||
getPageJSActions(pageIndex) {
|
||||
return this.messageHandler.sendWithPromise("GetPageJSActions", {
|
||||
pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||
pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||
});
|
||||
}
|
||||
|
||||
getStructTree(pageIndex) {
|
||||
return this.messageHandler.sendWithPromise("GetStructTree", {
|
||||
pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||
pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||
});
|
||||
}
|
||||
|
||||
@ -3141,7 +3161,7 @@ class WorkerTransport {
|
||||
const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
|
||||
const pageIndex = this.#pageRefCache.get(refStr);
|
||||
return pageIndex >= 0
|
||||
? this.#pagesMapper.getPageNumber(pageIndex + 1)
|
||||
? this.pagesMapper.getPageNumber(pageIndex + 1)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1045,38 +1045,38 @@ class PagesMapper {
|
||||
* Maps page IDs to their corresponding page numbers.
|
||||
* @type {Uint32Array|null}
|
||||
*/
|
||||
static #idToPageNumber = null;
|
||||
#idToPageNumber = null;
|
||||
|
||||
/**
|
||||
* Maps page numbers to their corresponding page IDs.
|
||||
* @type {Uint32Array|null}
|
||||
*/
|
||||
static #pageNumberToId = null;
|
||||
#pageNumberToId = null;
|
||||
|
||||
/**
|
||||
* Previous mapping of page IDs to page numbers.
|
||||
* @type {Uint32Array|null}
|
||||
*/
|
||||
static #prevIdToPageNumber = null;
|
||||
#prevIdToPageNumber = null;
|
||||
|
||||
/**
|
||||
* The total number of pages.
|
||||
* @type {number}
|
||||
*/
|
||||
static #pagesNumber = 0;
|
||||
#pagesNumber = 0;
|
||||
|
||||
/**
|
||||
* Listeners for page changes.
|
||||
* @type {Array<function>}
|
||||
*/
|
||||
static #listeners = [];
|
||||
#listeners = [];
|
||||
|
||||
/**
|
||||
* Gets the total number of pages.
|
||||
* @returns {number} The number of pages.
|
||||
*/
|
||||
get pagesNumber() {
|
||||
return PagesMapper.#pagesNumber;
|
||||
return this.#pagesNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1085,52 +1085,49 @@ class PagesMapper {
|
||||
* @param {number} n - The total number of pages.
|
||||
*/
|
||||
set pagesNumber(n) {
|
||||
if (PagesMapper.#pagesNumber === n) {
|
||||
if (this.#pagesNumber === n) {
|
||||
return;
|
||||
}
|
||||
PagesMapper.#pagesNumber = n;
|
||||
this.#pagesNumber = n;
|
||||
if (n === 0) {
|
||||
PagesMapper.#pageNumberToId = null;
|
||||
PagesMapper.#idToPageNumber = null;
|
||||
this.#pageNumberToId = null;
|
||||
this.#idToPageNumber = null;
|
||||
}
|
||||
}
|
||||
|
||||
addListener(listener) {
|
||||
PagesMapper.#listeners.push(listener);
|
||||
this.#listeners.push(listener);
|
||||
}
|
||||
|
||||
removeListener(listener) {
|
||||
const index = PagesMapper.#listeners.indexOf(listener);
|
||||
const index = this.#listeners.indexOf(listener);
|
||||
if (index >= 0) {
|
||||
PagesMapper.#listeners.splice(index, 1);
|
||||
this.#listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#updateListeners() {
|
||||
for (const listener of PagesMapper.#listeners) {
|
||||
for (const listener of this.#listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
#init(mustInit) {
|
||||
if (PagesMapper.#pageNumberToId) {
|
||||
if (this.#pageNumberToId) {
|
||||
return;
|
||||
}
|
||||
const n = PagesMapper.#pagesNumber;
|
||||
const n = this.#pagesNumber;
|
||||
|
||||
// Allocate a single array for better memory locality.
|
||||
const array = new Uint32Array(3 * n);
|
||||
const pageNumberToId = (PagesMapper.#pageNumberToId = array.subarray(0, n));
|
||||
const idToPageNumber = (PagesMapper.#idToPageNumber = array.subarray(
|
||||
n,
|
||||
2 * n
|
||||
));
|
||||
const pageNumberToId = (this.#pageNumberToId = array.subarray(0, n));
|
||||
const idToPageNumber = (this.#idToPageNumber = array.subarray(n, 2 * n));
|
||||
if (mustInit) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
pageNumberToId[i] = idToPageNumber[i] = i + 1;
|
||||
}
|
||||
}
|
||||
PagesMapper.#prevIdToPageNumber = array.subarray(2 * n);
|
||||
this.#prevIdToPageNumber = array.subarray(2 * n);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1143,9 +1140,9 @@ class PagesMapper {
|
||||
*/
|
||||
movePages(selectedPages, pagesToMove, index) {
|
||||
this.#init(true);
|
||||
const pageNumberToId = PagesMapper.#pageNumberToId;
|
||||
const idToPageNumber = PagesMapper.#idToPageNumber;
|
||||
PagesMapper.#prevIdToPageNumber.set(idToPageNumber);
|
||||
const pageNumberToId = this.#pageNumberToId;
|
||||
const idToPageNumber = this.#idToPageNumber;
|
||||
this.#prevIdToPageNumber.set(idToPageNumber);
|
||||
const movedCount = pagesToMove.length;
|
||||
const mappedPagesToMove = new Uint32Array(movedCount);
|
||||
let removedBeforeTarget = 0;
|
||||
@ -1158,7 +1155,7 @@ class PagesMapper {
|
||||
}
|
||||
}
|
||||
|
||||
const pagesNumber = PagesMapper.#pagesNumber;
|
||||
const pagesNumber = this.#pagesNumber;
|
||||
// target index after removing elements that were before it
|
||||
let adjustedTarget = index - removedBeforeTarget;
|
||||
const remainingLen = pagesNumber - movedCount;
|
||||
@ -1201,7 +1198,7 @@ class PagesMapper {
|
||||
* @returns {boolean} True if the mappings have been altered, false otherwise.
|
||||
*/
|
||||
hasBeenAltered() {
|
||||
return PagesMapper.#pageNumberToId !== null;
|
||||
return this.#pageNumberToId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1211,16 +1208,14 @@ class PagesMapper {
|
||||
getPageMappingForSaving() {
|
||||
// Saving is index-based.
|
||||
return {
|
||||
pageIndices: PagesMapper.#idToPageNumber
|
||||
? PagesMapper.#idToPageNumber.map(x => x - 1)
|
||||
pageIndices: this.#idToPageNumber
|
||||
? this.#idToPageNumber.map(x => x - 1)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
getPrevPageNumber(pageNumber) {
|
||||
return PagesMapper.#prevIdToPageNumber[
|
||||
PagesMapper.#pageNumberToId[pageNumber - 1] - 1
|
||||
];
|
||||
return this.#prevIdToPageNumber[this.#pageNumberToId[pageNumber - 1] - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1229,7 +1224,7 @@ class PagesMapper {
|
||||
* @returns {number} The page number, or the ID itself if no mapping exists.
|
||||
*/
|
||||
getPageNumber(id) {
|
||||
return PagesMapper.#idToPageNumber?.[id - 1] ?? id;
|
||||
return this.#idToPageNumber?.[id - 1] ?? id;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1239,19 +1234,11 @@ class PagesMapper {
|
||||
* exists.
|
||||
*/
|
||||
getPageId(pageNumber) {
|
||||
return PagesMapper.#pageNumberToId?.[pageNumber - 1] ?? pageNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a singleton instance of PagesMapper.
|
||||
* @returns {PagesMapper} The singleton instance.
|
||||
*/
|
||||
static get instance() {
|
||||
return shadow(this, "instance", new PagesMapper());
|
||||
return this.#pageNumberToId?.[pageNumber - 1] ?? pageNumber;
|
||||
}
|
||||
|
||||
getMapping() {
|
||||
return PagesMapper.#pageNumberToId.subarray(0, this.pagesNumber);
|
||||
return this.#pageNumberToId.subarray(0, this.pagesNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,6 @@ import {
|
||||
isPdfFile,
|
||||
noContextMenu,
|
||||
OutputScale,
|
||||
PagesMapper,
|
||||
PDFDateString,
|
||||
PixelsPerInch,
|
||||
RenderingCancelledException,
|
||||
@ -129,7 +128,6 @@ globalThis.pdfjsLib = {
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PagesMapper,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
@ -189,7 +187,6 @@ export {
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PagesMapper,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
|
||||
@ -104,12 +104,12 @@ function movePages(page, selectedPages, atIndex) {
|
||||
return page.evaluate(
|
||||
(selected, index) => {
|
||||
const viewer = window.PDFViewerApplication.pdfViewer;
|
||||
const pagesMapper = viewer.pdfDocument.pagesMapper;
|
||||
const pagesToMove = Array.from(selected).sort((a, b) => a - b);
|
||||
viewer.pagesMapper.pagesNumber =
|
||||
document.querySelectorAll(".page").length;
|
||||
viewer.pagesMapper.movePages(new Set(pagesToMove), pagesToMove, index);
|
||||
pagesMapper.pagesNumber = document.querySelectorAll(".page").length;
|
||||
pagesMapper.movePages(new Set(pagesToMove), pagesToMove, index);
|
||||
window.PDFViewerApplication.eventBus.dispatch("pagesedited", {
|
||||
pagesMapper: viewer.pagesMapper,
|
||||
pagesMapper,
|
||||
index,
|
||||
pagesToMove,
|
||||
});
|
||||
|
||||
@ -5403,6 +5403,36 @@ small scripts as well as for`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Multiple documents and pages mapper", function () {
|
||||
it("should load multiple documents in parallel", async function () {
|
||||
const loadingTask1 = getDocument(buildGetDocumentParams("pdkids.pdf"));
|
||||
const loadingTask2 = getDocument(
|
||||
buildGetDocumentParams("page_with_number.pdf")
|
||||
);
|
||||
const loadingTask3 = getDocument(buildGetDocumentParams("empty.pdf"));
|
||||
|
||||
const [pdfDoc1, pdfDoc2, pdfDoc3] = await Promise.all([
|
||||
loadingTask1.promise,
|
||||
loadingTask2.promise,
|
||||
loadingTask3.promise,
|
||||
]);
|
||||
// Each document has its own pages mapper, so the number of pages
|
||||
// should be correct for each document.
|
||||
expect(pdfDoc1.numPages).toEqual(55);
|
||||
expect(pdfDoc1.pagesMapper.pagesNumber).toEqual(55);
|
||||
expect(pdfDoc2.numPages).toEqual(17);
|
||||
expect(pdfDoc2.pagesMapper.pagesNumber).toEqual(17);
|
||||
expect(pdfDoc3.numPages).toEqual(1);
|
||||
expect(pdfDoc3.pagesMapper.pagesNumber).toEqual(1);
|
||||
|
||||
await Promise.all([
|
||||
loadingTask1.destroy(),
|
||||
loadingTask2.destroy(),
|
||||
loadingTask3.destroy(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PDF page editing", function () {
|
||||
const getPageRefs = async pdfDoc => {
|
||||
const refs = [];
|
||||
|
||||
@ -48,7 +48,6 @@ import {
|
||||
isPdfFile,
|
||||
noContextMenu,
|
||||
OutputScale,
|
||||
PagesMapper,
|
||||
PDFDateString,
|
||||
PixelsPerInch,
|
||||
RenderingCancelledException,
|
||||
@ -113,7 +112,6 @@ const expectedAPI = Object.freeze({
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PagesMapper,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
isValidRotation,
|
||||
watchScroll,
|
||||
} from "./ui_utils.js";
|
||||
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
|
||||
import { MathClamp, noContextMenu, stopEvent } from "pdfjs-lib";
|
||||
import { Menu } from "./menu.js";
|
||||
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
||||
import { RenderingStates } from "./renderable_view.js";
|
||||
@ -109,7 +109,7 @@ class PDFThumbnailViewer {
|
||||
|
||||
#currentScrollTop = 0;
|
||||
|
||||
#pagesMapper = PagesMapper.instance;
|
||||
#pagesMapper = null;
|
||||
|
||||
#manageSaveAsButton = null;
|
||||
|
||||
@ -275,6 +275,7 @@ class PDFThumbnailViewer {
|
||||
if (!pdfDocument) {
|
||||
return;
|
||||
}
|
||||
this.#pagesMapper = pdfDocument.pagesMapper;
|
||||
const firstPagePromise = pdfDocument.getPage(1);
|
||||
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
|
||||
intent: "display",
|
||||
|
||||
@ -30,7 +30,6 @@ import {
|
||||
AnnotationEditorUIManager,
|
||||
AnnotationMode,
|
||||
MathClamp,
|
||||
PagesMapper,
|
||||
PermissionFlag,
|
||||
PixelsPerInch,
|
||||
shadow,
|
||||
@ -286,8 +285,6 @@ class PDFViewer {
|
||||
|
||||
#viewerAlert = null;
|
||||
|
||||
#pagesMapper = PagesMapper.instance;
|
||||
|
||||
/**
|
||||
* @param {PDFViewerOptions} options
|
||||
*/
|
||||
@ -299,9 +296,6 @@ class PDFViewer {
|
||||
`The API version "${version}" does not match the Viewer version "${viewerVersion}".`
|
||||
);
|
||||
}
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||
this.pagesMapper = PagesMapper.instance;
|
||||
}
|
||||
|
||||
this.container = options.container;
|
||||
this.viewer = options.viewer || options.container.firstElementChild;
|
||||
@ -880,7 +874,6 @@ class PDFViewer {
|
||||
this.#annotationEditorMode = AnnotationEditorType.NONE;
|
||||
|
||||
this.#printingAllowed = true;
|
||||
this.#pagesMapper.pagesNumber = 0;
|
||||
}
|
||||
|
||||
this.pdfDocument = pdfDocument;
|
||||
|
||||
@ -49,7 +49,6 @@ const {
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PagesMapper,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
@ -109,7 +108,6 @@ export {
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PagesMapper,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user