mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-27 02:25:47 +02:00
Merge pull request #20587 from calixteman/reorg_simplify_mapping
Refactor a bit page mapping stuff in order to be able to support delete/copy pages
This commit is contained in:
commit
07a4aab246
@ -464,6 +464,8 @@ class Page {
|
|||||||
task,
|
task,
|
||||||
intent,
|
intent,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
|
pageId = this.pageIndex,
|
||||||
|
pageIndex = this.pageIndex,
|
||||||
annotationStorage = null,
|
annotationStorage = null,
|
||||||
modifiedIds = null,
|
modifiedIds = null,
|
||||||
}) {
|
}) {
|
||||||
@ -549,13 +551,12 @@ class Page {
|
|||||||
RESOURCES_KEYS_OPERATOR_LIST
|
RESOURCES_KEYS_OPERATOR_LIST
|
||||||
);
|
);
|
||||||
const opList = new OperatorList(intent, sink);
|
const opList = new OperatorList(intent, sink);
|
||||||
|
|
||||||
handler.send("StartRenderPage", {
|
handler.send("StartRenderPage", {
|
||||||
transparency: partialEvaluator.hasBlendModes(
|
transparency: partialEvaluator.hasBlendModes(
|
||||||
resources,
|
resources,
|
||||||
this.nonBlendModesSet
|
this.nonBlendModesSet
|
||||||
),
|
),
|
||||||
pageIndex: this.pageIndex,
|
pageIndex,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -853,8 +853,8 @@ class WorkerMessageHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
handler.on("GetOperatorList", function (data, sink) {
|
handler.on("GetOperatorList", function (data, sink) {
|
||||||
const pageIndex = data.pageIndex;
|
const { pageId, pageIndex } = data;
|
||||||
pdfManager.getPage(pageIndex).then(function (page) {
|
pdfManager.getPage(pageId).then(function (page) {
|
||||||
const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`);
|
const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`);
|
||||||
startWorkerTask(task);
|
startWorkerTask(task);
|
||||||
|
|
||||||
@ -871,6 +871,7 @@ class WorkerMessageHandler {
|
|||||||
cacheKey: data.cacheKey,
|
cacheKey: data.cacheKey,
|
||||||
annotationStorage: data.annotationStorage,
|
annotationStorage: data.annotationStorage,
|
||||||
modifiedIds: data.modifiedIds,
|
modifiedIds: data.modifiedIds,
|
||||||
|
pageIndex,
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
function (operatorListInfo) {
|
function (operatorListInfo) {
|
||||||
@ -899,9 +900,10 @@ class WorkerMessageHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
handler.on("GetTextContent", function (data, sink) {
|
handler.on("GetTextContent", function (data, sink) {
|
||||||
const { pageIndex, includeMarkedContent, disableNormalization } = data;
|
const { pageId, pageIndex, includeMarkedContent, disableNormalization } =
|
||||||
|
data;
|
||||||
|
|
||||||
pdfManager.getPage(pageIndex).then(function (page) {
|
pdfManager.getPage(pageId).then(function (page) {
|
||||||
const task = new WorkerTask("GetTextContent: page " + pageIndex);
|
const task = new WorkerTask("GetTextContent: page " + pageIndex);
|
||||||
startWorkerTask(task);
|
startWorkerTask(task);
|
||||||
|
|
||||||
|
|||||||
@ -293,7 +293,7 @@ class AnnotationElement {
|
|||||||
this.annotationStorage.setValue(`${AnnotationEditorPrefix}${data.id}`, {
|
this.annotationStorage.setValue(`${AnnotationEditorPrefix}${data.id}`, {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
annotationType: data.annotationType,
|
annotationType: data.annotationType,
|
||||||
pageIndex: this.parent.page._pageIndex,
|
page: this.parent.page,
|
||||||
popup,
|
popup,
|
||||||
popupRef: data.popupRef,
|
popupRef: data.popupRef,
|
||||||
modificationDate: new Date(),
|
modificationDate: new Date(),
|
||||||
|
|||||||
@ -196,6 +196,10 @@ class AnnotationStorage {
|
|||||||
val instanceof AnnotationEditor
|
val instanceof AnnotationEditor
|
||||||
? val.serialize(/* isForCopying = */ false, context)
|
? val.serialize(/* isForCopying = */ false, context)
|
||||||
: val;
|
: val;
|
||||||
|
if (val.page) {
|
||||||
|
val.pageIndex = val.page._pageIndex;
|
||||||
|
delete val.page;
|
||||||
|
}
|
||||||
if (serialized) {
|
if (serialized) {
|
||||||
map.set(key, serialized);
|
map.set(key, serialized);
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import {
|
|||||||
deprecated,
|
deprecated,
|
||||||
isDataScheme,
|
isDataScheme,
|
||||||
isValidFetchUrl,
|
isValidFetchUrl,
|
||||||
|
PagesMapper,
|
||||||
PageViewport,
|
PageViewport,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
StatTimer,
|
StatTimer,
|
||||||
@ -1328,6 +1329,8 @@ class PDFDocumentProxy {
|
|||||||
class PDFPageProxy {
|
class PDFPageProxy {
|
||||||
#pendingCleanup = false;
|
#pendingCleanup = false;
|
||||||
|
|
||||||
|
#pagesMapper = PagesMapper.instance;
|
||||||
|
|
||||||
constructor(pageIndex, pageInfo, transport, pdfBug = false) {
|
constructor(pageIndex, pageInfo, transport, pdfBug = false) {
|
||||||
this._pageIndex = pageIndex;
|
this._pageIndex = pageIndex;
|
||||||
this._pageInfo = pageInfo;
|
this._pageInfo = pageInfo;
|
||||||
@ -1350,6 +1353,13 @@ class PDFPageProxy {
|
|||||||
return this._pageIndex + 1;
|
return this._pageIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} value - The page number to set. First page is 1.
|
||||||
|
*/
|
||||||
|
set pageNumber(value) {
|
||||||
|
this._pageIndex = value - 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {number} The number of degrees the page is rotated clockwise.
|
* @type {number} The number of degrees the page is rotated clockwise.
|
||||||
*/
|
*/
|
||||||
@ -1699,6 +1709,7 @@ class PDFPageProxy {
|
|||||||
return this._transport.messageHandler.sendWithStream(
|
return this._transport.messageHandler.sendWithStream(
|
||||||
"GetTextContent",
|
"GetTextContent",
|
||||||
{
|
{
|
||||||
|
pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1,
|
||||||
pageIndex: this._pageIndex,
|
pageIndex: this._pageIndex,
|
||||||
includeMarkedContent: includeMarkedContent === true,
|
includeMarkedContent: includeMarkedContent === true,
|
||||||
disableNormalization: disableNormalization === true,
|
disableNormalization: disableNormalization === true,
|
||||||
@ -1884,6 +1895,7 @@ class PDFPageProxy {
|
|||||||
const readableStream = this._transport.messageHandler.sendWithStream(
|
const readableStream = this._transport.messageHandler.sendWithStream(
|
||||||
"GetOperatorList",
|
"GetOperatorList",
|
||||||
{
|
{
|
||||||
|
pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1,
|
||||||
pageIndex: this._pageIndex,
|
pageIndex: this._pageIndex,
|
||||||
intent: renderingIntent,
|
intent: renderingIntent,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
@ -2389,6 +2401,8 @@ class WorkerTransport {
|
|||||||
|
|
||||||
#passwordCapability = null;
|
#passwordCapability = null;
|
||||||
|
|
||||||
|
#pagesMapper = PagesMapper.instance;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
messageHandler,
|
messageHandler,
|
||||||
loadingTask,
|
loadingTask,
|
||||||
@ -2424,6 +2438,8 @@ class WorkerTransport {
|
|||||||
|
|
||||||
this.setupMessageHandler();
|
this.setupMessageHandler();
|
||||||
|
|
||||||
|
this.#pagesMapper.addListener(this.#updateCaches.bind(this));
|
||||||
|
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||||
// For testing purposes.
|
// For testing purposes.
|
||||||
Object.defineProperty(this, "getNetworkStreamName", {
|
Object.defineProperty(this, "getNetworkStreamName", {
|
||||||
@ -2448,6 +2464,24 @@ 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;
|
||||||
|
const page = this.#pageCache.get(prevPageIndex);
|
||||||
|
if (page) {
|
||||||
|
newPageCache.set(i, page);
|
||||||
|
}
|
||||||
|
const promise = this.#pagePromises.get(prevPageIndex);
|
||||||
|
if (promise) {
|
||||||
|
newPromiseCache.set(i, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.#pageCache = newPageCache;
|
||||||
|
this.#pagePromises = newPromiseCache;
|
||||||
|
}
|
||||||
|
|
||||||
#cacheSimpleMethod(name, data = null) {
|
#cacheSimpleMethod(name, data = null) {
|
||||||
const cachedPromise = this.#methodPromises.get(name);
|
const cachedPromise = this.#methodPromises.get(name);
|
||||||
if (cachedPromise) {
|
if (cachedPromise) {
|
||||||
@ -2710,6 +2744,7 @@ class WorkerTransport {
|
|||||||
});
|
});
|
||||||
|
|
||||||
messageHandler.on("GetDoc", ({ pdfInfo }) => {
|
messageHandler.on("GetDoc", ({ pdfInfo }) => {
|
||||||
|
this.#pagesMapper.pagesNumber = pdfInfo.numPages;
|
||||||
this._numPages = pdfInfo.numPages;
|
this._numPages = pdfInfo.numPages;
|
||||||
this._htmlForXfa = pdfInfo.htmlForXfa;
|
this._htmlForXfa = pdfInfo.htmlForXfa;
|
||||||
delete pdfInfo.htmlForXfa;
|
delete pdfInfo.htmlForXfa;
|
||||||
@ -2932,26 +2967,27 @@ class WorkerTransport {
|
|||||||
if (
|
if (
|
||||||
!Number.isInteger(pageNumber) ||
|
!Number.isInteger(pageNumber) ||
|
||||||
pageNumber <= 0 ||
|
pageNumber <= 0 ||
|
||||||
pageNumber > this._numPages
|
pageNumber > this.#pagesMapper.pagesNumber
|
||||||
) {
|
) {
|
||||||
return Promise.reject(new Error("Invalid page request."));
|
return Promise.reject(new Error("Invalid page request."));
|
||||||
}
|
}
|
||||||
|
const pageIndex = pageNumber - 1;
|
||||||
|
const newPageIndex = this.#pagesMapper.getPageId(pageNumber) - 1;
|
||||||
|
|
||||||
const pageIndex = pageNumber - 1,
|
const cachedPromise = this.#pagePromises.get(pageIndex);
|
||||||
cachedPromise = this.#pagePromises.get(pageIndex);
|
|
||||||
if (cachedPromise) {
|
if (cachedPromise) {
|
||||||
return cachedPromise;
|
return cachedPromise;
|
||||||
}
|
}
|
||||||
const promise = this.messageHandler
|
const promise = this.messageHandler
|
||||||
.sendWithPromise("GetPage", {
|
.sendWithPromise("GetPage", {
|
||||||
pageIndex,
|
pageIndex: newPageIndex,
|
||||||
})
|
})
|
||||||
.then(pageInfo => {
|
.then(pageInfo => {
|
||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
throw new Error("Transport destroyed");
|
throw new Error("Transport destroyed");
|
||||||
}
|
}
|
||||||
if (pageInfo.refStr) {
|
if (pageInfo.refStr) {
|
||||||
this.#pageRefCache.set(pageInfo.refStr, pageNumber);
|
this.#pageRefCache.set(pageInfo.refStr, newPageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = new PDFPageProxy(
|
const page = new PDFPageProxy(
|
||||||
@ -2967,19 +3003,20 @@ class WorkerTransport {
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageIndex(ref) {
|
async getPageIndex(ref) {
|
||||||
if (!isRefProxy(ref)) {
|
if (!isRefProxy(ref)) {
|
||||||
return Promise.reject(new Error("Invalid pageIndex request."));
|
throw new Error("Invalid pageIndex request.");
|
||||||
}
|
}
|
||||||
return this.messageHandler.sendWithPromise("GetPageIndex", {
|
const index = await this.messageHandler.sendWithPromise("GetPageIndex", {
|
||||||
num: ref.num,
|
num: ref.num,
|
||||||
gen: ref.gen,
|
gen: ref.gen,
|
||||||
});
|
});
|
||||||
|
return this.#pagesMapper.getPageNumber(index + 1) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAnnotations(pageIndex, intent) {
|
getAnnotations(pageIndex, intent) {
|
||||||
return this.messageHandler.sendWithPromise("GetAnnotations", {
|
return this.messageHandler.sendWithPromise("GetAnnotations", {
|
||||||
pageIndex,
|
pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||||
intent,
|
intent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -3046,13 +3083,13 @@ class WorkerTransport {
|
|||||||
|
|
||||||
getPageJSActions(pageIndex) {
|
getPageJSActions(pageIndex) {
|
||||||
return this.messageHandler.sendWithPromise("GetPageJSActions", {
|
return this.messageHandler.sendWithPromise("GetPageJSActions", {
|
||||||
pageIndex,
|
pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getStructTree(pageIndex) {
|
getStructTree(pageIndex) {
|
||||||
return this.messageHandler.sendWithPromise("GetStructTree", {
|
return this.messageHandler.sendWithPromise("GetStructTree", {
|
||||||
pageIndex,
|
pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3122,7 +3159,10 @@ class WorkerTransport {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
|
const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
|
||||||
return this.#pageRefCache.get(refStr) ?? null;
|
const pageIndex = this.#pageRefCache.get(refStr);
|
||||||
|
return pageIndex >= 0
|
||||||
|
? this.#pagesMapper.getPageNumber(pageIndex + 1)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3130,7 +3170,7 @@ class WorkerTransport {
|
|||||||
* Allows controlling of the rendering tasks.
|
* Allows controlling of the rendering tasks.
|
||||||
*/
|
*/
|
||||||
class RenderTask {
|
class RenderTask {
|
||||||
#internalRenderTask = null;
|
_internalRenderTask = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for incremental rendering -- a function that will be called
|
* Callback for incremental rendering -- a function that will be called
|
||||||
@ -3151,12 +3191,12 @@ class RenderTask {
|
|||||||
onError = null;
|
onError = null;
|
||||||
|
|
||||||
constructor(internalRenderTask) {
|
constructor(internalRenderTask) {
|
||||||
this.#internalRenderTask = internalRenderTask;
|
this._internalRenderTask = internalRenderTask;
|
||||||
|
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||||
// For testing purposes.
|
// For testing purposes.
|
||||||
Object.defineProperty(this, "getOperatorList", {
|
Object.defineProperty(this, "getOperatorList", {
|
||||||
value: () => this.#internalRenderTask.operatorList,
|
value: () => this._internalRenderTask.operatorList,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3166,7 +3206,7 @@ class RenderTask {
|
|||||||
* @type {Promise<void>}
|
* @type {Promise<void>}
|
||||||
*/
|
*/
|
||||||
get promise() {
|
get promise() {
|
||||||
return this.#internalRenderTask.capability.promise;
|
return this._internalRenderTask.capability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3177,7 +3217,7 @@ class RenderTask {
|
|||||||
* @param {number} [extraDelay]
|
* @param {number} [extraDelay]
|
||||||
*/
|
*/
|
||||||
cancel(extraDelay = 0) {
|
cancel(extraDelay = 0) {
|
||||||
this.#internalRenderTask.cancel(/* error = */ null, extraDelay);
|
this._internalRenderTask.cancel(/* error = */ null, extraDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3185,11 +3225,11 @@ class RenderTask {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get separateAnnots() {
|
get separateAnnots() {
|
||||||
const { separateAnnots } = this.#internalRenderTask.operatorList;
|
const { separateAnnots } = this._internalRenderTask.operatorList;
|
||||||
if (!separateAnnots) {
|
if (!separateAnnots) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { annotationCanvasMap } = this.#internalRenderTask;
|
const { annotationCanvasMap } = this._internalRenderTask;
|
||||||
return (
|
return (
|
||||||
separateAnnots.form ||
|
separateAnnots.form ||
|
||||||
(separateAnnots.canvas && annotationCanvasMap?.size > 0)
|
(separateAnnots.canvas && annotationCanvasMap?.size > 0)
|
||||||
@ -3389,7 +3429,6 @@ class InternalRenderTask {
|
|||||||
if (this.operatorList.lastChunk) {
|
if (this.operatorList.lastChunk) {
|
||||||
this.gfx.endDrawing();
|
this.gfx.endDrawing();
|
||||||
InternalRenderTask.#canvasInUse.delete(this._canvas);
|
InternalRenderTask.#canvasInUse.delete(this._canvas);
|
||||||
|
|
||||||
this.callback();
|
this.callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
BaseException,
|
BaseException,
|
||||||
DrawOPS,
|
DrawOPS,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
|
MathClamp,
|
||||||
shadow,
|
shadow,
|
||||||
Util,
|
Util,
|
||||||
warn,
|
warn,
|
||||||
@ -1034,6 +1035,197 @@ function makePathFromDrawOPS(data) {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps between page IDs and page numbers, allowing bidirectional conversion
|
||||||
|
* between the two representations. This is useful when the page numbering
|
||||||
|
* in the PDF document doesn't match the default sequential ordering.
|
||||||
|
*/
|
||||||
|
class PagesMapper {
|
||||||
|
/**
|
||||||
|
* Maps page IDs to their corresponding page numbers.
|
||||||
|
* @type {Uint32Array|null}
|
||||||
|
*/
|
||||||
|
static #idToPageNumber = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps page numbers to their corresponding page IDs.
|
||||||
|
* @type {Uint32Array|null}
|
||||||
|
*/
|
||||||
|
static #pageNumberToId = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous mapping of page IDs to page numbers.
|
||||||
|
* @type {Uint32Array|null}
|
||||||
|
*/
|
||||||
|
static #prevIdToPageNumber = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of pages.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
static #pagesNumber = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listeners for page changes.
|
||||||
|
* @type {Array<function>}
|
||||||
|
*/
|
||||||
|
static #listeners = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the total number of pages.
|
||||||
|
* @returns {number} The number of pages.
|
||||||
|
*/
|
||||||
|
get pagesNumber() {
|
||||||
|
return PagesMapper.#pagesNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the total number of pages and initializes default mappings
|
||||||
|
* where page IDs equal page numbers (1-indexed).
|
||||||
|
* @param {number} n - The total number of pages.
|
||||||
|
*/
|
||||||
|
set pagesNumber(n) {
|
||||||
|
if (PagesMapper.#pagesNumber === n) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PagesMapper.#pagesNumber = n;
|
||||||
|
if (n === 0) {
|
||||||
|
PagesMapper.#pageNumberToId = null;
|
||||||
|
PagesMapper.#idToPageNumber = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener(listener) {
|
||||||
|
PagesMapper.#listeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener(listener) {
|
||||||
|
const index = PagesMapper.#listeners.indexOf(listener);
|
||||||
|
if (index >= 0) {
|
||||||
|
PagesMapper.#listeners.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateListeners() {
|
||||||
|
for (const listener of PagesMapper.#listeners) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#init(mustInit) {
|
||||||
|
if (PagesMapper.#pageNumberToId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const n = PagesMapper.#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
|
||||||
|
));
|
||||||
|
if (mustInit) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
pageNumberToId[i] = idToPageNumber[i] = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PagesMapper.#prevIdToPageNumber = array.subarray(2 * n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a set of pages to a new position while keeping ID→number mappings in
|
||||||
|
* sync.
|
||||||
|
*
|
||||||
|
* @param {Set<number>} selectedPages - Page numbers being moved (1-indexed).
|
||||||
|
* @param {number[]} pagesToMove - Ordered list of page numbers to move.
|
||||||
|
* @param {number} index - Zero-based insertion index in the page-number list.
|
||||||
|
*/
|
||||||
|
movePages(selectedPages, pagesToMove, index) {
|
||||||
|
this.#init(true);
|
||||||
|
const pageNumberToId = PagesMapper.#pageNumberToId;
|
||||||
|
const idToPageNumber = PagesMapper.#idToPageNumber;
|
||||||
|
PagesMapper.#prevIdToPageNumber.set(idToPageNumber);
|
||||||
|
const movedCount = pagesToMove.length;
|
||||||
|
const mappedPagesToMove = new Uint32Array(movedCount);
|
||||||
|
let removedBeforeTarget = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < movedCount; i++) {
|
||||||
|
const pageIndex = pagesToMove[i] - 1;
|
||||||
|
mappedPagesToMove[i] = pageNumberToId[pageIndex];
|
||||||
|
if (pageIndex < index) {
|
||||||
|
removedBeforeTarget += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pagesNumber = PagesMapper.#pagesNumber;
|
||||||
|
// target index after removing elements that were before it
|
||||||
|
let adjustedTarget = index - removedBeforeTarget;
|
||||||
|
const remainingLen = pagesNumber - movedCount;
|
||||||
|
adjustedTarget = MathClamp(adjustedTarget, 0, remainingLen);
|
||||||
|
|
||||||
|
// Create the new mapping.
|
||||||
|
// First copy over the pages that are not being moved.
|
||||||
|
// Then insert the moved pages at the target position.
|
||||||
|
for (let i = 0, r = 0; i < pagesNumber; i++) {
|
||||||
|
if (!selectedPages.has(i + 1)) {
|
||||||
|
pageNumberToId[r++] = pageNumberToId[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift the pages after the target position.
|
||||||
|
pageNumberToId.copyWithin(
|
||||||
|
adjustedTarget + movedCount,
|
||||||
|
adjustedTarget,
|
||||||
|
remainingLen
|
||||||
|
);
|
||||||
|
// Finally insert the moved pages.
|
||||||
|
pageNumberToId.set(mappedPagesToMove, adjustedTarget);
|
||||||
|
|
||||||
|
for (let i = 0, ii = pagesNumber; i < ii; i++) {
|
||||||
|
idToPageNumber[pageNumberToId[i] - 1] = i + 1;
|
||||||
|
}
|
||||||
|
this.#updateListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrevPageNumber(pageNumber) {
|
||||||
|
return PagesMapper.#prevIdToPageNumber[
|
||||||
|
PagesMapper.#pageNumberToId[pageNumber - 1] - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the page number for a given page ID.
|
||||||
|
* @param {number} id - The page ID (1-indexed).
|
||||||
|
* @returns {number} The page number, or the ID itself if no mapping exists.
|
||||||
|
*/
|
||||||
|
getPageNumber(id) {
|
||||||
|
return PagesMapper.#idToPageNumber?.[id - 1] ?? id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the page ID for a given page number.
|
||||||
|
* @param {number} pageNumber - The page number (1-indexed).
|
||||||
|
* @returns {number} The page ID, or the page number itself if no mapping
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
getMapping() {
|
||||||
|
return PagesMapper.#pageNumberToId.subarray(0, this.pagesNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
@ -1054,6 +1246,7 @@ export {
|
|||||||
makePathFromDrawOPS,
|
makePathFromDrawOPS,
|
||||||
noContextMenu,
|
noContextMenu,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PageViewport,
|
PageViewport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
|
|||||||
@ -30,10 +30,6 @@ class DrawLayer {
|
|||||||
|
|
||||||
static #id = 0;
|
static #id = 0;
|
||||||
|
|
||||||
constructor({ pageIndex }) {
|
|
||||||
this.pageIndex = pageIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
setParent(parent) {
|
setParent(parent) {
|
||||||
if (!this.#parent) {
|
if (!this.#parent) {
|
||||||
this.#parent = parent;
|
this.#parent = parent;
|
||||||
@ -103,7 +99,7 @@ class DrawLayer {
|
|||||||
root.append(defs);
|
root.append(defs);
|
||||||
const path = DrawLayer._svgFactory.createElement("path");
|
const path = DrawLayer._svgFactory.createElement("path");
|
||||||
defs.append(path);
|
defs.append(path);
|
||||||
const pathId = `path_p${this.pageIndex}_${id}`;
|
const pathId = `path_${id}`;
|
||||||
path.setAttribute("id", pathId);
|
path.setAttribute("id", pathId);
|
||||||
path.setAttribute("vector-effect", "non-scaling-stroke");
|
path.setAttribute("vector-effect", "non-scaling-stroke");
|
||||||
|
|
||||||
@ -135,7 +131,7 @@ class DrawLayer {
|
|||||||
root.append(defs);
|
root.append(defs);
|
||||||
const path = DrawLayer._svgFactory.createElement("path");
|
const path = DrawLayer._svgFactory.createElement("path");
|
||||||
defs.append(path);
|
defs.append(path);
|
||||||
const pathId = `path_p${this.pageIndex}_${id}`;
|
const pathId = `path_${id}`;
|
||||||
path.setAttribute("id", pathId);
|
path.setAttribute("id", pathId);
|
||||||
path.setAttribute("vector-effect", "non-scaling-stroke");
|
path.setAttribute("vector-effect", "non-scaling-stroke");
|
||||||
|
|
||||||
@ -143,7 +139,7 @@ class DrawLayer {
|
|||||||
if (mustRemoveSelfIntersections) {
|
if (mustRemoveSelfIntersections) {
|
||||||
const mask = DrawLayer._svgFactory.createElement("mask");
|
const mask = DrawLayer._svgFactory.createElement("mask");
|
||||||
defs.append(mask);
|
defs.append(mask);
|
||||||
maskId = `mask_p${this.pageIndex}_${id}`;
|
maskId = `mask_${id}`;
|
||||||
mask.setAttribute("id", maskId);
|
mask.setAttribute("id", maskId);
|
||||||
mask.setAttribute("maskUnits", "objectBoundingBox");
|
mask.setAttribute("maskUnits", "objectBoundingBox");
|
||||||
const rect = DrawLayer._svgFactory.createElement("rect");
|
const rect = DrawLayer._svgFactory.createElement("rect");
|
||||||
|
|||||||
@ -144,6 +144,10 @@ class AnnotationEditorLayer {
|
|||||||
this.#uiManager.addLayer(this);
|
this.#uiManager.addLayer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePageIndex(newPageIndex) {
|
||||||
|
this.pageIndex = newPageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
get isEmpty() {
|
get isEmpty() {
|
||||||
return this.#editors.size === 0;
|
return this.#editors.size === 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -209,6 +209,10 @@ class AnnotationEditor {
|
|||||||
this.deleted = false;
|
this.deleted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePageIndex(newPageIndex) {
|
||||||
|
this.pageIndex = newPageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
get editorType() {
|
get editorType() {
|
||||||
return Object.getPrototypeOf(this).constructor._type;
|
return Object.getPrototypeOf(this).constructor._type;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -948,6 +948,7 @@ class AnnotationEditorUIManager {
|
|||||||
evt => this.updateParams(evt.type, evt.value),
|
evt => this.updateParams(evt.type, evt.value),
|
||||||
{ signal }
|
{ signal }
|
||||||
);
|
);
|
||||||
|
eventBus._on("pagesedited", this.onPagesEdited.bind(this), { signal });
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"pointerdown",
|
"pointerdown",
|
||||||
() => {
|
() => {
|
||||||
@ -1259,6 +1260,26 @@ class AnnotationEditorUIManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPagesEdited({ pagesMapper }) {
|
||||||
|
for (const editor of this.#allEditors.values()) {
|
||||||
|
editor.updatePageIndex(
|
||||||
|
pagesMapper.getPrevPageNumber(editor.pageIndex + 1) - 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const allLayers = this.#allLayers;
|
||||||
|
const newAllLayers = (this.#allLayers = new Map());
|
||||||
|
for (const [pageIndex, layer] of allLayers) {
|
||||||
|
const prevPageIndex = pagesMapper.getPrevPageNumber(pageIndex + 1) - 1;
|
||||||
|
if (prevPageIndex === -1) {
|
||||||
|
// TODO: handle the case where the deletion of the page has been undone.
|
||||||
|
layer.destroy();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
newAllLayers.set(prevPageIndex, layer);
|
||||||
|
layer.updatePageIndex(prevPageIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPageChanging({ pageNumber }) {
|
onPageChanging({ pageNumber }) {
|
||||||
this.#currentPageIndex = pageNumber - 1;
|
this.#currentPageIndex = pageNumber - 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,7 @@ import {
|
|||||||
isPdfFile,
|
isPdfFile,
|
||||||
noContextMenu,
|
noContextMenu,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
@ -128,6 +129,7 @@ globalThis.pdfjsLib = {
|
|||||||
normalizeUnicode,
|
normalizeUnicode,
|
||||||
OPS,
|
OPS,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PasswordResponses,
|
PasswordResponses,
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
@ -187,6 +189,7 @@ export {
|
|||||||
normalizeUnicode,
|
normalizeUnicode,
|
||||||
OPS,
|
OPS,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PasswordResponses,
|
PasswordResponses,
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
|
|||||||
@ -59,20 +59,40 @@ function waitForPagesEdited(page) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForHavingContents(page, expected) {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
// Make sure all the pages will be visible.
|
||||||
|
window.PDFViewerApplication.pdfViewer.scrollMode = 2 /* = ScrollMode.WRAPPED = */;
|
||||||
|
window.PDFViewerApplication.pdfViewer.updateScale({
|
||||||
|
drawingDelay: 0,
|
||||||
|
scaleFactor: 0.01,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return page.waitForFunction(
|
||||||
|
ex => {
|
||||||
|
const buffer = [];
|
||||||
|
for (const textLayer of document.querySelectorAll(".textLayer")) {
|
||||||
|
buffer.push(parseInt(textLayer.textContent.trim(), 10));
|
||||||
|
}
|
||||||
|
return ex.length === buffer.length && ex.every((v, i) => v === buffer[i]);
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getSearchResults(page) {
|
function getSearchResults(page) {
|
||||||
return page.evaluate(() => {
|
return page.evaluate(() => {
|
||||||
const pages = document.querySelectorAll(".page");
|
const pages = document.querySelectorAll(".page");
|
||||||
const results = [];
|
const results = [];
|
||||||
for (let i = 0; i < pages.length; i++) {
|
for (let i = 0; i < pages.length; i++) {
|
||||||
const domPage = pages[i];
|
const domPage = pages[i];
|
||||||
const pageNumber = parseInt(domPage.getAttribute("data-page-number"), 10);
|
|
||||||
const highlights = domPage.querySelectorAll("span.highlight");
|
const highlights = domPage.querySelectorAll("span.highlight");
|
||||||
if (highlights.length === 0) {
|
if (highlights.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
results.push([
|
results.push([
|
||||||
i + 1,
|
i + 1,
|
||||||
pageNumber,
|
|
||||||
Array.from(highlights).map(span => span.textContent),
|
Array.from(highlights).map(span => span.textContent),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -184,11 +204,13 @@ describe("Reorganize Pages View", () => {
|
|||||||
10
|
10
|
||||||
);
|
);
|
||||||
const pagesMapping = await awaitPromise(handlePagesEdited);
|
const pagesMapping = await awaitPromise(handlePagesEdited);
|
||||||
|
const expected = [
|
||||||
|
2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||||
|
];
|
||||||
expect(pagesMapping)
|
expect(pagesMapping)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.toEqual(expected);
|
||||||
2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
await waitForHavingContents(page, expected);
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -208,11 +230,13 @@ describe("Reorganize Pages View", () => {
|
|||||||
10
|
10
|
||||||
);
|
);
|
||||||
const pagesMapping = await awaitPromise(handlePagesEdited);
|
const pagesMapping = await awaitPromise(handlePagesEdited);
|
||||||
|
const expected = [
|
||||||
|
2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||||
|
];
|
||||||
expect(pagesMapping)
|
expect(pagesMapping)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.toEqual(expected);
|
||||||
2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
await waitForHavingContents(page, expected);
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -233,11 +257,13 @@ describe("Reorganize Pages View", () => {
|
|||||||
10
|
10
|
||||||
);
|
);
|
||||||
const pagesMapping = await awaitPromise(handlePagesEdited);
|
const pagesMapping = await awaitPromise(handlePagesEdited);
|
||||||
|
const expected = [
|
||||||
|
3, 4, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||||
|
];
|
||||||
expect(pagesMapping)
|
expect(pagesMapping)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.toEqual(expected);
|
||||||
3, 4, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
await waitForHavingContents(page, expected);
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -266,11 +292,13 @@ describe("Reorganize Pages View", () => {
|
|||||||
10
|
10
|
||||||
);
|
);
|
||||||
const pagesMapping = await awaitPromise(handlePagesEdited);
|
const pagesMapping = await awaitPromise(handlePagesEdited);
|
||||||
|
const expected = [
|
||||||
|
2, 1, 14, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
|
||||||
|
];
|
||||||
expect(pagesMapping)
|
expect(pagesMapping)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.toEqual(expected);
|
||||||
2, 1, 14, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
|
await waitForHavingContents(page, expected);
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -296,10 +324,10 @@ describe("Reorganize Pages View", () => {
|
|||||||
);
|
);
|
||||||
await awaitPromise(handlePagesEdited);
|
await awaitPromise(handlePagesEdited);
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
`${getThumbnailSelector(2)}[aria-current="false"]`
|
`${getThumbnailSelector(2)}[aria-current="page"]`
|
||||||
);
|
);
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
`${getThumbnailSelector(1)}[aria-current="page"]`
|
`${getThumbnailSelector(1)}[aria-current="false"]`
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -344,16 +372,16 @@ describe("Reorganize Pages View", () => {
|
|||||||
expect(results)
|
expect(results)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.toEqual([
|
||||||
// Page number, Id, [matches]
|
// Page number, [matches]
|
||||||
[1, 1, ["1"]],
|
[1, ["1"]],
|
||||||
[10, 10, ["1"]],
|
[10, ["1"]],
|
||||||
[11, 11, ["1", "1"]],
|
[11, ["1", "1"]],
|
||||||
[12, 12, ["1"]],
|
[12, ["1"]],
|
||||||
[13, 13, ["1"]],
|
[13, ["1"]],
|
||||||
[14, 14, ["1"]],
|
[14, ["1"]],
|
||||||
[15, 15, ["1"]],
|
[15, ["1"]],
|
||||||
[16, 16, ["1"]],
|
[16, ["1"]],
|
||||||
[17, 17, ["1"]],
|
[17, ["1"]],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await movePages(page, [11, 2], 3);
|
await movePages(page, [11, 2], 3);
|
||||||
@ -373,16 +401,16 @@ describe("Reorganize Pages View", () => {
|
|||||||
expect(results)
|
expect(results)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.toEqual([
|
||||||
// Page number, Id, [matches]
|
// Page number, [matches]
|
||||||
[1, 1, ["1"]],
|
[1, ["1"]],
|
||||||
[4, 11, ["1", "1"]],
|
[4, ["1", "1"]],
|
||||||
[11, 10, ["1"]],
|
[11, ["1"]],
|
||||||
[12, 12, ["1"]],
|
[12, ["1"]],
|
||||||
[13, 13, ["1"]],
|
[13, ["1"]],
|
||||||
[14, 14, ["1"]],
|
[14, ["1"]],
|
||||||
[15, 15, ["1"]],
|
[15, ["1"]],
|
||||||
[16, 16, ["1"]],
|
[16, ["1"]],
|
||||||
[17, 17, ["1"]],
|
[17, ["1"]],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await movePages(page, [13], 0);
|
await movePages(page, [13], 0);
|
||||||
@ -402,16 +430,16 @@ describe("Reorganize Pages View", () => {
|
|||||||
expect(results)
|
expect(results)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.toEqual([
|
||||||
// Page number, Id, [matches]
|
// Page number, [matches]
|
||||||
[1, 13, ["1"]],
|
[1, ["1"]],
|
||||||
[2, 1, ["1"]],
|
[2, ["1"]],
|
||||||
[5, 11, ["1", "1"]],
|
[5, ["1", "1"]],
|
||||||
[12, 10, ["1"]],
|
[12, ["1"]],
|
||||||
[13, 12, ["1"]],
|
[13, ["1"]],
|
||||||
[14, 14, ["1"]],
|
[14, ["1"]],
|
||||||
[15, 15, ["1"]],
|
[15, ["1"]],
|
||||||
[16, 16, ["1"]],
|
[16, ["1"]],
|
||||||
[17, 17, ["1"]],
|
[17, ["1"]],
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -442,13 +470,6 @@ describe("Reorganize Pages View", () => {
|
|||||||
await movePages(page, [2], 10);
|
await movePages(page, [2], 10);
|
||||||
await scrollIntoView(page, getAnnotationSelector("107R"));
|
await scrollIntoView(page, getAnnotationSelector("107R"));
|
||||||
await page.click(getAnnotationSelector("107R"));
|
await page.click(getAnnotationSelector("107R"));
|
||||||
await page.waitForSelector(
|
|
||||||
".page[data-page-number='10'] + .page[data-page-number='2']",
|
|
||||||
{
|
|
||||||
visible: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentPage = await page.$eval(
|
const currentPage = await page.$eval(
|
||||||
"#pageNumber",
|
"#pageNumber",
|
||||||
el => el.valueAsNumber
|
el => el.valueAsNumber
|
||||||
@ -469,12 +490,6 @@ describe("Reorganize Pages View", () => {
|
|||||||
await page.waitForSelector("#outlinesView", { visible: true });
|
await page.waitForSelector("#outlinesView", { visible: true });
|
||||||
|
|
||||||
await page.click("#outlinesView .treeItem:nth-child(2)");
|
await page.click("#outlinesView .treeItem:nth-child(2)");
|
||||||
await page.waitForSelector(
|
|
||||||
".page[data-page-number='10'] + .page[data-page-number='2']",
|
|
||||||
{
|
|
||||||
visible: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentPage = await page.$eval(
|
const currentPage = await page.$eval(
|
||||||
"#pageNumber",
|
"#pageNumber",
|
||||||
|
|||||||
@ -177,7 +177,7 @@ describe("Signature Editor", () => {
|
|||||||
const editorSelector = getEditorSelector(0);
|
const editorSelector = getEditorSelector(0);
|
||||||
await page.waitForSelector(editorSelector, { visible: true });
|
await page.waitForSelector(editorSelector, { visible: true });
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
`.canvasWrapper > svg use[href="#path_p1_0"]`,
|
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||||
{ visible: true }
|
{ visible: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ describe("Signature Editor", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
".canvasWrapper > svg use[href='#path_p1_0']"
|
".canvasWrapper > svg use[href='#path_0']"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -340,7 +340,7 @@ describe("Signature Editor", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
".canvasWrapper > svg use[href='#path_p1_0']"
|
".canvasWrapper > svg use[href='#path_0']"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -427,7 +427,7 @@ describe("Signature Editor", () => {
|
|||||||
const editorSelector = getEditorSelector(0);
|
const editorSelector = getEditorSelector(0);
|
||||||
await page.waitForSelector(editorSelector, { visible: true });
|
await page.waitForSelector(editorSelector, { visible: true });
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
`.canvasWrapper > svg use[href="#path_p1_0"]`,
|
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||||
{ visible: true }
|
{ visible: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -527,13 +527,13 @@ describe("Signature Editor", () => {
|
|||||||
const editorSelector = getEditorSelector(0);
|
const editorSelector = getEditorSelector(0);
|
||||||
await page.waitForSelector(editorSelector, { visible: true });
|
await page.waitForSelector(editorSelector, { visible: true });
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
`.canvasWrapper > svg use[href="#path_p1_0"]`,
|
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||||
{ visible: true }
|
{ visible: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = await page.evaluate(() => {
|
const color = await page.evaluate(() => {
|
||||||
const use = document.querySelector(
|
const use = document.querySelector(
|
||||||
`.canvasWrapper > svg use[href="#path_p1_0"]`
|
`.canvasWrapper > svg use[href="#path_0"]`
|
||||||
);
|
);
|
||||||
return use.parentNode.getAttribute("fill");
|
return use.parentNode.getAttribute("fill");
|
||||||
});
|
});
|
||||||
@ -583,13 +583,13 @@ describe("Signature Editor", () => {
|
|||||||
const editorSelector = getEditorSelector(0);
|
const editorSelector = getEditorSelector(0);
|
||||||
await page.waitForSelector(editorSelector, { visible: true });
|
await page.waitForSelector(editorSelector, { visible: true });
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
`.canvasWrapper > svg use[href="#path_p1_0"]`,
|
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||||
{ visible: true }
|
{ visible: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = await page.evaluate(() => {
|
const color = await page.evaluate(() => {
|
||||||
const use = document.querySelector(
|
const use = document.querySelector(
|
||||||
`.canvasWrapper > svg use[href="#path_p1_0"]`
|
`.canvasWrapper > svg use[href="#path_0"]`
|
||||||
);
|
);
|
||||||
return use.parentNode.getAttribute("fill");
|
return use.parentNode.getAttribute("fill");
|
||||||
});
|
});
|
||||||
@ -672,7 +672,7 @@ describe("Signature Editor", () => {
|
|||||||
});
|
});
|
||||||
const { width, height } = await getRect(
|
const { width, height } = await getRect(
|
||||||
page,
|
page,
|
||||||
".canvasWrapper > svg use[href='#path_p1_0']"
|
".canvasWrapper > svg use[href='#path_0']"
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Math.abs(contentWidth / width - contentHeight / height))
|
expect(Math.abs(contentWidth / width - contentHeight / height))
|
||||||
|
|||||||
@ -48,6 +48,7 @@ import {
|
|||||||
isPdfFile,
|
isPdfFile,
|
||||||
noContextMenu,
|
noContextMenu,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
@ -112,6 +113,7 @@ const expectedAPI = Object.freeze({
|
|||||||
normalizeUnicode,
|
normalizeUnicode,
|
||||||
OPS,
|
OPS,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PasswordResponses,
|
PasswordResponses,
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
|
|||||||
@ -15,11 +15,6 @@
|
|||||||
|
|
||||||
import { DrawLayer } from "pdfjs-lib";
|
import { DrawLayer } from "pdfjs-lib";
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} DrawLayerBuilderOptions
|
|
||||||
* @property {number} pageIndex
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} DrawLayerBuilderRenderOptions
|
* @typedef {Object} DrawLayerBuilderRenderOptions
|
||||||
* @property {string} [intent] - The default value is "display".
|
* @property {string} [intent] - The default value is "display".
|
||||||
@ -28,13 +23,6 @@ import { DrawLayer } from "pdfjs-lib";
|
|||||||
class DrawLayerBuilder {
|
class DrawLayerBuilder {
|
||||||
#drawLayer = null;
|
#drawLayer = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {DrawLayerBuilderOptions} options
|
|
||||||
*/
|
|
||||||
constructor(options) {
|
|
||||||
this.pageIndex = options.pageIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DrawLayerBuilderRenderOptions} options
|
* @param {DrawLayerBuilderRenderOptions} options
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
@ -43,9 +31,7 @@ class DrawLayerBuilder {
|
|||||||
if (intent !== "display" || this.#drawLayer || this._cancelled) {
|
if (intent !== "display" || this.#drawLayer || this._cancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#drawLayer = new DrawLayer({
|
this.#drawLayer = new DrawLayer();
|
||||||
pageIndex: this.pageIndex,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
|
|||||||
@ -17,11 +17,7 @@
|
|||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||||
|
|
||||||
import {
|
import { binarySearchFirstItem, scrollIntoView } from "./ui_utils.js";
|
||||||
binarySearchFirstItem,
|
|
||||||
PagesMapper,
|
|
||||||
scrollIntoView,
|
|
||||||
} from "./ui_utils.js";
|
|
||||||
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
||||||
|
|
||||||
const FindState = {
|
const FindState = {
|
||||||
@ -426,8 +422,6 @@ class PDFFindController {
|
|||||||
|
|
||||||
#visitedPagesCount = 0;
|
#visitedPagesCount = 0;
|
||||||
|
|
||||||
#pagesMapper = PagesMapper.instance;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PDFFindControllerOptions} options
|
* @param {PDFFindControllerOptions} options
|
||||||
*/
|
*/
|
||||||
@ -801,13 +795,12 @@ class PDFFindController {
|
|||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
return; // Do nothing: the matches should be wiped out already.
|
return; // Do nothing: the matches should be wiped out already.
|
||||||
}
|
}
|
||||||
const pageId = this.getPageId(pageIndex);
|
const pageContent = this._pageContents[pageIndex];
|
||||||
const pageContent = this._pageContents[pageId];
|
|
||||||
const matcherResult = this.match(query, pageContent, pageIndex);
|
const matcherResult = this.match(query, pageContent, pageIndex);
|
||||||
|
|
||||||
const matches = (this._pageMatches[pageIndex] = []);
|
const matches = (this._pageMatches[pageIndex] = []);
|
||||||
const matchesLength = (this._pageMatchesLength[pageIndex] = []);
|
const matchesLength = (this._pageMatchesLength[pageIndex] = []);
|
||||||
const diffs = this._pageDiffs[pageId];
|
const diffs = this._pageDiffs[pageIndex];
|
||||||
|
|
||||||
matcherResult?.forEach(({ index, length }) => {
|
matcherResult?.forEach(({ index, length }) => {
|
||||||
const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
|
const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
|
||||||
@ -856,7 +849,7 @@ class PDFFindController {
|
|||||||
* page.
|
* page.
|
||||||
*/
|
*/
|
||||||
match(query, pageContent, pageIndex) {
|
match(query, pageContent, pageIndex) {
|
||||||
const hasDiacritics = this._hasDiacritics[this.getPageId(pageIndex)];
|
const hasDiacritics = this._hasDiacritics[pageIndex];
|
||||||
|
|
||||||
let isUnicode = false;
|
let isUnicode = false;
|
||||||
if (typeof query === "string") {
|
if (typeof query === "string") {
|
||||||
@ -957,14 +950,6 @@ class PDFFindController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageNumber(idx) {
|
|
||||||
return this.#pagesMapper.getPageNumber(idx + 1) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPageId(pageNumber) {
|
|
||||||
return this.#pagesMapper.getPageId(pageNumber + 1) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#updatePage(index) {
|
#updatePage(index) {
|
||||||
if (this._scrollMatches && this._selected.pageIdx === index) {
|
if (this._scrollMatches && this._selected.pageIdx === index) {
|
||||||
// If the page is selected, scroll the page into view, which triggers
|
// If the page is selected, scroll the page into view, which triggers
|
||||||
@ -976,7 +961,6 @@ class PDFFindController {
|
|||||||
this._eventBus.dispatch("updatetextlayermatches", {
|
this._eventBus.dispatch("updatetextlayermatches", {
|
||||||
source: this,
|
source: this,
|
||||||
pageIndex: index,
|
pageIndex: index,
|
||||||
pageId: this.getPageId(index),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -984,7 +968,6 @@ class PDFFindController {
|
|||||||
this._eventBus.dispatch("updatetextlayermatches", {
|
this._eventBus.dispatch("updatetextlayermatches", {
|
||||||
source: this,
|
source: this,
|
||||||
pageIndex: -1,
|
pageIndex: -1,
|
||||||
pageId: -1,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1016,7 +999,7 @@ class PDFFindController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this._pendingFindMatches.add(i);
|
this._pendingFindMatches.add(i);
|
||||||
this._extractTextPromises[this.getPageId(i)].then(() => {
|
this._extractTextPromises[i].then(() => {
|
||||||
this._pendingFindMatches.delete(i);
|
this._pendingFindMatches.delete(i);
|
||||||
this.#calculateMatch(i);
|
this.#calculateMatch(i);
|
||||||
});
|
});
|
||||||
@ -1144,12 +1127,23 @@ class PDFFindController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#onPagesEdited() {
|
#onPagesEdited({ pagesMapper }) {
|
||||||
if (this._extractTextPromises.length === 0) {
|
if (this._extractTextPromises.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#onFindBarClose();
|
this.#onFindBarClose();
|
||||||
this._dirtyMatch = true;
|
this._dirtyMatch = true;
|
||||||
|
const prevTextPromises = this._extractTextPromises;
|
||||||
|
const extractTextPromises = (this._extractTextPromises.length = []);
|
||||||
|
for (let i = 0, ii = pagesMapper.length; i < ii; i++) {
|
||||||
|
const prevPageIndex = pagesMapper.getPrevPageNumber(i + 1) - 1;
|
||||||
|
if (prevPageIndex === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
extractTextPromises.push(
|
||||||
|
prevTextPromises[prevPageIndex] || Promise.resolve()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#onFindBarClose(evt) {
|
#onFindBarClose(evt) {
|
||||||
|
|||||||
@ -16,8 +16,8 @@
|
|||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||||
|
|
||||||
import { PagesMapper, parseQueryString } from "./ui_utils.js";
|
|
||||||
import { isValidExplicitDest } from "pdfjs-lib";
|
import { isValidExplicitDest } from "pdfjs-lib";
|
||||||
|
import { parseQueryString } from "./ui_utils.js";
|
||||||
|
|
||||||
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
|
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
|
||||||
|
|
||||||
@ -50,8 +50,6 @@ const LinkTarget = {
|
|||||||
class PDFLinkService {
|
class PDFLinkService {
|
||||||
externalLinkEnabled = true;
|
externalLinkEnabled = true;
|
||||||
|
|
||||||
#pagesMapper = PagesMapper.instance;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PDFLinkServiceOptions} options
|
* @param {PDFLinkServiceOptions} options
|
||||||
*/
|
*/
|
||||||
@ -140,7 +138,7 @@ class PDFLinkService {
|
|||||||
if (!this.pdfDocument) {
|
if (!this.pdfDocument) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let namedDest, explicitDest, pageId;
|
let namedDest, explicitDest, pageNumber;
|
||||||
if (typeof dest === "string") {
|
if (typeof dest === "string") {
|
||||||
namedDest = dest;
|
namedDest = dest;
|
||||||
explicitDest = await this.pdfDocument.getDestination(dest);
|
explicitDest = await this.pdfDocument.getDestination(dest);
|
||||||
@ -158,13 +156,13 @@ class PDFLinkService {
|
|||||||
const [destRef] = explicitDest;
|
const [destRef] = explicitDest;
|
||||||
|
|
||||||
if (destRef && typeof destRef === "object") {
|
if (destRef && typeof destRef === "object") {
|
||||||
pageId = this.pdfDocument.cachedPageNumber(destRef);
|
pageNumber = this.pdfDocument.cachedPageNumber(destRef);
|
||||||
|
|
||||||
if (!pageId) {
|
if (!pageNumber) {
|
||||||
// Fetch the page reference if it's not yet available. This could
|
// Fetch the page reference if it's not yet available. This could
|
||||||
// only occur during loading, before all pages have been resolved.
|
// only occur during loading, before all pages have been resolved.
|
||||||
try {
|
try {
|
||||||
pageId = (await this.pdfDocument.getPageIndex(destRef)) + 1;
|
pageNumber = (await this.pdfDocument.getPageIndex(destRef)) + 1;
|
||||||
} catch {
|
} catch {
|
||||||
console.error(
|
console.error(
|
||||||
`goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`
|
`goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`
|
||||||
@ -173,25 +171,20 @@ class PDFLinkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (Number.isInteger(destRef)) {
|
} else if (Number.isInteger(destRef)) {
|
||||||
pageId = destRef + 1;
|
pageNumber = destRef + 1;
|
||||||
}
|
}
|
||||||
if (!pageId || pageId < 1 || pageId > this.pagesCount) {
|
if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
|
||||||
console.error(
|
console.error(
|
||||||
`goToDestination: "${pageId}" is not a valid page number, for dest="${dest}".`
|
`goToDestination: "${pageNumber}" is not a valid page number, for dest="${dest}".`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageNumber = this.#pagesMapper.getPageNumber(pageId);
|
|
||||||
if (pageNumber === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pdfHistory) {
|
if (this.pdfHistory) {
|
||||||
// Update the browser history before scrolling the new destination into
|
// Update the browser history before scrolling the new destination into
|
||||||
// view, to be able to accurately capture the current document position.
|
// view, to be able to accurately capture the current document position.
|
||||||
this.pdfHistory.pushCurrentPosition();
|
this.pdfHistory.pushCurrentPosition();
|
||||||
this.pdfHistory.push({ namedDest, explicitDest, pageNumber: pageId });
|
this.pdfHistory.push({ namedDest, explicitDest, pageNumber });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pdfViewer.scrollPageIntoView({
|
this.pdfViewer.scrollPageIntoView({
|
||||||
@ -204,7 +197,7 @@ class PDFLinkService {
|
|||||||
this.eventBus._on(
|
this.eventBus._on(
|
||||||
"textlayerrendered",
|
"textlayerrendered",
|
||||||
evt => {
|
evt => {
|
||||||
if (evt.pageNumber === pageId) {
|
if (evt.pageNumber === pageNumber) {
|
||||||
evt.source.textLayer.div.focus();
|
evt.source.textLayer.div.focus();
|
||||||
ac.abort();
|
ac.abort();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -319,6 +319,25 @@ class PDFPageView extends BasePDFPageView {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePageNumber(newPageNumber) {
|
||||||
|
if (this.id === newPageNumber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.id = newPageNumber;
|
||||||
|
this.renderingId = `page${newPageNumber}`;
|
||||||
|
if (this.pdfPage) {
|
||||||
|
this.pdfPage.pageNumber = newPageNumber;
|
||||||
|
}
|
||||||
|
// TODO: do we set the page label ?
|
||||||
|
this.setPageLabel(this.pageLabel);
|
||||||
|
const { div } = this;
|
||||||
|
div.setAttribute("data-page-number", newPageNumber);
|
||||||
|
div.setAttribute("data-l10n-args", JSON.stringify({ page: newPageNumber }));
|
||||||
|
this._textHighlighter.pageIdx = newPageNumber - 1;
|
||||||
|
// Don't update the page index for the draw layer, since it's just used as
|
||||||
|
// an identifier.
|
||||||
|
}
|
||||||
|
|
||||||
setPdfPage(pdfPage) {
|
setPdfPage(pdfPage) {
|
||||||
if (
|
if (
|
||||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
|
||||||
@ -1116,9 +1135,7 @@ class PDFPageView extends BasePDFPageView {
|
|||||||
if (!annotationEditorUIManager) {
|
if (!annotationEditorUIManager) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.drawLayer ||= new DrawLayerBuilder({
|
this.drawLayer ||= new DrawLayerBuilder();
|
||||||
pageIndex: this.id,
|
|
||||||
});
|
|
||||||
await this.#renderDrawLayer();
|
await this.#renderDrawLayer();
|
||||||
this.drawLayer.setParent(canvasWrapper);
|
this.drawLayer.setParent(canvasWrapper);
|
||||||
|
|
||||||
|
|||||||
@ -100,7 +100,7 @@ class PDFThumbnailView {
|
|||||||
enableSplitMerge = false,
|
enableSplitMerge = false,
|
||||||
}) {
|
}) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.renderingId = "thumbnail" + id;
|
this.renderingId = `thumbnail${id}`;
|
||||||
this.pageLabel = null;
|
this.pageLabel = null;
|
||||||
|
|
||||||
this.pdfPage = null;
|
this.pdfPage = null;
|
||||||
@ -144,6 +144,14 @@ class PDFThumbnailView {
|
|||||||
container.append(imageContainer);
|
container.append(imageContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateId(newId) {
|
||||||
|
this.id = newId;
|
||||||
|
this.renderingId = `thumbnail${newId}`;
|
||||||
|
this.div.setAttribute("page-number", newId);
|
||||||
|
// TODO: do we set the page label ?
|
||||||
|
this.setPageLabel(this.pageLabel);
|
||||||
|
}
|
||||||
|
|
||||||
#updateDims() {
|
#updateDims() {
|
||||||
const { width, height } = this.viewport;
|
const { width, height } = this.viewport;
|
||||||
const ratio = width / height;
|
const ratio = width / height;
|
||||||
|
|||||||
@ -24,11 +24,10 @@ import {
|
|||||||
binarySearchFirstItem,
|
binarySearchFirstItem,
|
||||||
getVisibleElements,
|
getVisibleElements,
|
||||||
isValidRotation,
|
isValidRotation,
|
||||||
PagesMapper,
|
|
||||||
RenderingStates,
|
RenderingStates,
|
||||||
watchScroll,
|
watchScroll,
|
||||||
} from "./ui_utils.js";
|
} from "./ui_utils.js";
|
||||||
import { MathClamp, noContextMenu, stopEvent } from "pdfjs-lib";
|
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
|
||||||
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
||||||
|
|
||||||
const SCROLL_OPTIONS = {
|
const SCROLL_OPTIONS = {
|
||||||
@ -110,8 +109,6 @@ class PDFThumbnailViewer {
|
|||||||
|
|
||||||
#pagesMapper = PagesMapper.instance;
|
#pagesMapper = PagesMapper.instance;
|
||||||
|
|
||||||
#originalThumbnails = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PDFThumbnailViewerOptions} options
|
* @param {PDFThumbnailViewerOptions} options
|
||||||
*/
|
*/
|
||||||
@ -385,6 +382,26 @@ class PDFThumbnailViewer {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#updateThumbnails() {
|
||||||
|
const pagesMapper = this.#pagesMapper;
|
||||||
|
this.container.replaceChildren();
|
||||||
|
const prevThumbnails = this._thumbnails;
|
||||||
|
const newThumbnails = (this._thumbnails = []);
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) {
|
||||||
|
const prevPageIndex = pagesMapper.getPrevPageNumber(i + 1) - 1;
|
||||||
|
if (prevPageIndex === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const newThumbnail = prevThumbnails[prevPageIndex];
|
||||||
|
newThumbnails.push(newThumbnail);
|
||||||
|
newThumbnail.updateId(i + 1);
|
||||||
|
newThumbnail.checkbox.checked = false;
|
||||||
|
fragment.append(newThumbnail.div);
|
||||||
|
}
|
||||||
|
this.container.append(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
#onStartDragging(draggedThumbnail) {
|
#onStartDragging(draggedThumbnail) {
|
||||||
this.#currentScrollTop = this.scrollableContainer.scrollTop;
|
this.#currentScrollTop = this.scrollableContainer.scrollTop;
|
||||||
this.#currentScrollBottom =
|
this.#currentScrollBottom =
|
||||||
@ -449,8 +466,6 @@ class PDFThumbnailViewer {
|
|||||||
this.#dragAC.abort();
|
this.#dragAC.abort();
|
||||||
this.#dragAC = null;
|
this.#dragAC = null;
|
||||||
|
|
||||||
this.#originalThumbnails ||= this._thumbnails;
|
|
||||||
|
|
||||||
this.container.classList.remove("isDragging");
|
this.container.classList.remove("isDragging");
|
||||||
for (const selected of this.#selectedPages) {
|
for (const selected of this.#selectedPages) {
|
||||||
const thumbnail = this._thumbnails[selected - 1];
|
const thumbnail = this._thumbnails[selected - 1];
|
||||||
@ -481,11 +496,7 @@ class PDFThumbnailViewer {
|
|||||||
) {
|
) {
|
||||||
const newIndex = lastDraggedOverIndex + 1;
|
const newIndex = lastDraggedOverIndex + 1;
|
||||||
const pagesToMove = Array.from(selectedPages).sort((a, b) => a - b);
|
const pagesToMove = Array.from(selectedPages).sort((a, b) => a - b);
|
||||||
const movedCount = pagesToMove.length;
|
|
||||||
const thumbnails = this._thumbnails;
|
|
||||||
const pagesMapper = this.#pagesMapper;
|
const pagesMapper = this.#pagesMapper;
|
||||||
const N = thumbnails.length;
|
|
||||||
pagesMapper.pagesNumber = N;
|
|
||||||
const currentPageId = pagesMapper.getPageId(this._currentPageNumber);
|
const currentPageId = pagesMapper.getPageId(this._currentPageNumber);
|
||||||
const newCurrentPageId = pagesMapper.getPageId(
|
const newCurrentPageId = pagesMapper.getPageId(
|
||||||
isNaN(this.#pageNumberToRemove)
|
isNaN(this.#pageNumberToRemove)
|
||||||
@ -493,37 +504,14 @@ class PDFThumbnailViewer {
|
|||||||
: this.#pageNumberToRemove
|
: this.#pageNumberToRemove
|
||||||
);
|
);
|
||||||
|
|
||||||
// Move the thumbnails in the DOM.
|
|
||||||
let thumbnail = thumbnails[pagesToMove[0] - 1];
|
|
||||||
thumbnail.checkbox.checked = false;
|
|
||||||
if (newIndex === 0) {
|
|
||||||
thumbnails[0].div.before(thumbnail.div);
|
|
||||||
} else {
|
|
||||||
thumbnails[newIndex - 1].div.after(thumbnail.div);
|
|
||||||
}
|
|
||||||
for (let i = 1; i < movedCount; i++) {
|
|
||||||
const newThumbnail = thumbnails[pagesToMove[i] - 1];
|
|
||||||
newThumbnail.checkbox.checked = false;
|
|
||||||
thumbnail.div.after(newThumbnail.div);
|
|
||||||
thumbnail = newThumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.eventBus.dispatch("beforepagesedited", {
|
this.eventBus.dispatch("beforepagesedited", {
|
||||||
source: this,
|
source: this,
|
||||||
pagesMapper,
|
pagesMapper,
|
||||||
index: newIndex,
|
|
||||||
pagesToMove,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pagesMapper.movePages(selectedPages, pagesToMove, newIndex);
|
pagesMapper.movePages(selectedPages, pagesToMove, newIndex);
|
||||||
|
|
||||||
const newThumbnails = (this._thumbnails = new Array(N));
|
this.#updateThumbnails();
|
||||||
const originalThumbnails = this.#originalThumbnails;
|
|
||||||
for (let i = 0; i < N; i++) {
|
|
||||||
const newThumbnail = (newThumbnails[i] =
|
|
||||||
originalThumbnails[pagesMapper.getPageId(i + 1) - 1]);
|
|
||||||
newThumbnail.div.setAttribute("page-number", i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._currentPageNumber = pagesMapper.getPageNumber(currentPageId);
|
this._currentPageNumber = pagesMapper.getPageNumber(currentPageId);
|
||||||
this.#computeThumbnailsPosition();
|
this.#computeThumbnailsPosition();
|
||||||
@ -534,8 +522,6 @@ class PDFThumbnailViewer {
|
|||||||
this.eventBus.dispatch("pagesedited", {
|
this.eventBus.dispatch("pagesedited", {
|
||||||
source: this,
|
source: this,
|
||||||
pagesMapper,
|
pagesMapper,
|
||||||
index: newIndex,
|
|
||||||
pagesToMove,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const newCurrentPageNumber = pagesMapper.getPageNumber(newCurrentPageId);
|
const newCurrentPageNumber = pagesMapper.getPageNumber(newCurrentPageId);
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import {
|
|||||||
AnnotationEditorUIManager,
|
AnnotationEditorUIManager,
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
MathClamp,
|
MathClamp,
|
||||||
|
PagesMapper,
|
||||||
PermissionFlag,
|
PermissionFlag,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
shadow,
|
shadow,
|
||||||
@ -52,7 +53,6 @@ import {
|
|||||||
MAX_AUTO_SCALE,
|
MAX_AUTO_SCALE,
|
||||||
MAX_SCALE,
|
MAX_SCALE,
|
||||||
MIN_SCALE,
|
MIN_SCALE,
|
||||||
PagesMapper,
|
|
||||||
PresentationModeState,
|
PresentationModeState,
|
||||||
removeNullCharacters,
|
removeNullCharacters,
|
||||||
RenderingStates,
|
RenderingStates,
|
||||||
@ -289,8 +289,6 @@ class PDFViewer {
|
|||||||
|
|
||||||
#viewerAlert = null;
|
#viewerAlert = null;
|
||||||
|
|
||||||
#originalPages = null;
|
|
||||||
|
|
||||||
#pagesMapper = PagesMapper.instance;
|
#pagesMapper = PagesMapper.instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -885,6 +883,7 @@ class PDFViewer {
|
|||||||
this.#annotationEditorMode = AnnotationEditorType.NONE;
|
this.#annotationEditorMode = AnnotationEditorType.NONE;
|
||||||
|
|
||||||
this.#printingAllowed = true;
|
this.#printingAllowed = true;
|
||||||
|
this.#pagesMapper.pagesNumber = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pdfDocument = pdfDocument;
|
this.pdfDocument = pdfDocument;
|
||||||
@ -1180,37 +1179,40 @@ class PDFViewer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforePagesEdited() {
|
async onBeforePagesEdited({ pagesMapper }) {
|
||||||
this._currentPageId = this.#pagesMapper.getPageId(this._currentPageNumber);
|
await this._pagesCapability.promise;
|
||||||
|
this._currentPageId = pagesMapper.getPageId(this._currentPageNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPagesEdited({ index, pagesToMove }) {
|
onPagesEdited({ pagesMapper }) {
|
||||||
const pagesMapper = this.#pagesMapper;
|
|
||||||
this._currentPageNumber = pagesMapper.getPageNumber(this._currentPageId);
|
this._currentPageNumber = pagesMapper.getPageNumber(this._currentPageId);
|
||||||
|
const prevPages = this._pages;
|
||||||
|
const newPages = (this._pages = []);
|
||||||
|
for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) {
|
||||||
|
const prevPageNumber = pagesMapper.getPrevPageNumber(i + 1) - 1;
|
||||||
|
if (prevPageNumber === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const page = prevPages[prevPageNumber];
|
||||||
|
newPages[i] = page;
|
||||||
|
page.updatePageNumber(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
const viewerElement =
|
const viewerElement =
|
||||||
this._scrollMode === ScrollMode.PAGE ? null : this.viewer;
|
this._scrollMode === ScrollMode.PAGE ? null : this.viewer;
|
||||||
if (viewerElement) {
|
if (viewerElement) {
|
||||||
const pages = this._pages;
|
viewerElement.replaceChildren();
|
||||||
let page = pages[pagesToMove[0] - 1].div;
|
const fragment = document.createDocumentFragment();
|
||||||
if (index === 0) {
|
for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) {
|
||||||
pages[0].div.before(page);
|
const { div } = newPages[i];
|
||||||
} else {
|
div.setAttribute("data-page-number", i + 1);
|
||||||
pages[index - 1].div.after(page);
|
fragment.append(div);
|
||||||
}
|
|
||||||
for (let i = 1, ii = pagesToMove.length; i < ii; i++) {
|
|
||||||
const newPage = pages[pagesToMove[i] - 1].div;
|
|
||||||
page.after(newPage);
|
|
||||||
page = newPage;
|
|
||||||
}
|
}
|
||||||
|
viewerElement.append(fragment);
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
this.#originalPages ||= this._pages;
|
this.forceRendering();
|
||||||
const newPages = (this._pages = []);
|
});
|
||||||
for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) {
|
|
||||||
const pageView = this.#originalPages[pagesMapper.getPageId(i + 1) - 1];
|
|
||||||
newPages.push(pageView);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1357,12 +1359,11 @@ class PDFViewer {
|
|||||||
|
|
||||||
#scrollIntoView(pageView, pageSpot = null) {
|
#scrollIntoView(pageView, pageSpot = null) {
|
||||||
const { div, id } = pageView;
|
const { div, id } = pageView;
|
||||||
const pageNumber = this.#pagesMapper.getPageNumber(id);
|
|
||||||
|
|
||||||
// Ensure that `this._currentPageNumber` is correct, when `#scrollIntoView`
|
// Ensure that `this._currentPageNumber` is correct, when `#scrollIntoView`
|
||||||
// is called directly (and not from `#resetCurrentPageView`).
|
// is called directly (and not from `#resetCurrentPageView`).
|
||||||
if (this._currentPageNumber !== pageNumber) {
|
if (this._currentPageNumber !== id) {
|
||||||
this._setCurrentPageNumber(pageNumber);
|
this._setCurrentPageNumber(id);
|
||||||
}
|
}
|
||||||
if (this._scrollMode === ScrollMode.PAGE) {
|
if (this._scrollMode === ScrollMode.PAGE) {
|
||||||
this.#ensurePageViewVisible();
|
this.#ensurePageViewVisible();
|
||||||
@ -1823,22 +1824,20 @@ class PDFViewer {
|
|||||||
this._spreadMode === SpreadMode.NONE &&
|
this._spreadMode === SpreadMode.NONE &&
|
||||||
(this._scrollMode === ScrollMode.PAGE ||
|
(this._scrollMode === ScrollMode.PAGE ||
|
||||||
this._scrollMode === ScrollMode.VERTICAL);
|
this._scrollMode === ScrollMode.VERTICAL);
|
||||||
const currentId = this.#pagesMapper.getPageId(this._currentPageNumber);
|
const currentPageNumber = this._currentPageNumber;
|
||||||
let stillFullyVisible = false;
|
let stillFullyVisible = false;
|
||||||
|
|
||||||
for (const page of visiblePages) {
|
for (const page of visiblePages) {
|
||||||
if (page.percent < 100) {
|
if (page.percent < 100) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (page.id === currentId && isSimpleLayout) {
|
if (page.id === currentPageNumber && isSimpleLayout) {
|
||||||
stillFullyVisible = true;
|
stillFullyVisible = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._setCurrentPageNumber(
|
this._setCurrentPageNumber(
|
||||||
stillFullyVisible
|
stillFullyVisible ? this._currentPageNumber : visiblePages[0].id
|
||||||
? this._currentPageNumber
|
|
||||||
: this.#pagesMapper.getPageNumber(visiblePages[0].id)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this._updateLocation(visible.first);
|
this._updateLocation(visible.first);
|
||||||
|
|||||||
@ -49,6 +49,7 @@ const {
|
|||||||
normalizeUnicode,
|
normalizeUnicode,
|
||||||
OPS,
|
OPS,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PasswordResponses,
|
PasswordResponses,
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
@ -108,6 +109,7 @@ export {
|
|||||||
normalizeUnicode,
|
normalizeUnicode,
|
||||||
OPS,
|
OPS,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
|
PagesMapper,
|
||||||
PasswordResponses,
|
PasswordResponses,
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
|
|||||||
@ -77,7 +77,7 @@ class TextHighlighter {
|
|||||||
this.eventBus._on(
|
this.eventBus._on(
|
||||||
"updatetextlayermatches",
|
"updatetextlayermatches",
|
||||||
evt => {
|
evt => {
|
||||||
if (evt.pageId === this.pageIdx || evt.pageId === -1) {
|
if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
|
||||||
this._updateMatches();
|
this._updateMatches();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -159,8 +159,7 @@ class TextHighlighter {
|
|||||||
const { findController, pageIdx } = this;
|
const { findController, pageIdx } = this;
|
||||||
const { textContentItemsStr, textDivs } = this;
|
const { textContentItemsStr, textDivs } = this;
|
||||||
|
|
||||||
const isSelectedPage =
|
const isSelectedPage = pageIdx === findController.selected.pageIdx;
|
||||||
findController.getPageNumber(pageIdx) === findController.selected.pageIdx;
|
|
||||||
const selectedMatchIdx = findController.selected.matchIdx;
|
const selectedMatchIdx = findController.selected.matchIdx;
|
||||||
const highlightAll = findController.state.highlightAll;
|
const highlightAll = findController.state.highlightAll;
|
||||||
let prevEnd = null;
|
let prevEnd = null;
|
||||||
@ -274,7 +273,7 @@ class TextHighlighter {
|
|||||||
findController.scrollMatchIntoView({
|
findController.scrollMatchIntoView({
|
||||||
element: textDivs[begin.divIdx],
|
element: textDivs[begin.divIdx],
|
||||||
selectedLeft,
|
selectedLeft,
|
||||||
pageIndex: findController.getPageNumber(pageIdx),
|
pageIndex: pageIdx,
|
||||||
matchIndex: selectedMatchIdx,
|
matchIndex: selectedMatchIdx,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -309,10 +308,8 @@ class TextHighlighter {
|
|||||||
}
|
}
|
||||||
// Convert the matches on the `findController` into the match format
|
// Convert the matches on the `findController` into the match format
|
||||||
// used for the textLayer.
|
// used for the textLayer.
|
||||||
const pageNumber = findController.getPageNumber(pageIdx);
|
const pageMatches = findController.pageMatches[pageIdx] || null;
|
||||||
const pageMatches = findController.pageMatches[pageNumber] || null;
|
const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
|
||||||
const pageMatchesLength =
|
|
||||||
findController.pageMatchesLength[pageNumber] || null;
|
|
||||||
|
|
||||||
this.matches = this._convertMatches(pageMatches, pageMatchesLength);
|
this.matches = this._convertMatches(pageMatches, pageMatchesLength);
|
||||||
this._renderMatches(this.matches);
|
this._renderMatches(this.matches);
|
||||||
|
|||||||
139
web/ui_utils.js
139
web/ui_utils.js
@ -13,7 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MathClamp, shadow } from "pdfjs-lib";
|
import { MathClamp } from "pdfjs-lib";
|
||||||
|
|
||||||
const DEFAULT_SCALE_VALUE = "auto";
|
const DEFAULT_SCALE_VALUE = "auto";
|
||||||
const DEFAULT_SCALE = 1.0;
|
const DEFAULT_SCALE = 1.0;
|
||||||
@ -883,142 +883,6 @@ const calcRound =
|
|||||||
return e.style.width === "calc(1320px)" ? Math.fround : x => x;
|
return e.style.width === "calc(1320px)" ? Math.fround : x => x;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps between page IDs and page numbers, allowing bidirectional conversion
|
|
||||||
* between the two representations. This is useful when the page numbering
|
|
||||||
* in the PDF document doesn't match the default sequential ordering.
|
|
||||||
*/
|
|
||||||
class PagesMapper {
|
|
||||||
/**
|
|
||||||
* Maps page IDs to their corresponding page numbers.
|
|
||||||
* @type {Uint32Array|null}
|
|
||||||
*/
|
|
||||||
static #idToPageNumber = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps page numbers to their corresponding page IDs.
|
|
||||||
* @type {Uint32Array|null}
|
|
||||||
*/
|
|
||||||
static #pageNumberToId = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The total number of pages.
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
static #pagesNumber = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the total number of pages.
|
|
||||||
* @returns {number} The number of pages.
|
|
||||||
*/
|
|
||||||
get pagesNumber() {
|
|
||||||
return PagesMapper.#pagesNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the total number of pages and initializes default mappings
|
|
||||||
* where page IDs equal page numbers (1-indexed).
|
|
||||||
* @param {number} n - The total number of pages.
|
|
||||||
*/
|
|
||||||
set pagesNumber(n) {
|
|
||||||
if (PagesMapper.#pagesNumber === n) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PagesMapper.#pagesNumber = n;
|
|
||||||
const pageNumberToId = (PagesMapper.#pageNumberToId = new Uint32Array(
|
|
||||||
2 * n
|
|
||||||
));
|
|
||||||
const idToPageNumber = (PagesMapper.#idToPageNumber =
|
|
||||||
pageNumberToId.subarray(n));
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
pageNumberToId[i] = idToPageNumber[i] = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move a set of pages to a new position while keeping ID→number mappings in
|
|
||||||
* sync.
|
|
||||||
*
|
|
||||||
* @param {Set<number>} selectedPages - Page numbers being moved (1-indexed).
|
|
||||||
* @param {number[]} pagesToMove - Ordered list of page numbers to move.
|
|
||||||
* @param {number} index - Zero-based insertion index in the page-number list.
|
|
||||||
*/
|
|
||||||
movePages(selectedPages, pagesToMove, index) {
|
|
||||||
const pageNumberToId = PagesMapper.#pageNumberToId;
|
|
||||||
const idToPageNumber = PagesMapper.#idToPageNumber;
|
|
||||||
const movedCount = pagesToMove.length;
|
|
||||||
const mappedPagesToMove = new Uint32Array(movedCount);
|
|
||||||
let removedBeforeTarget = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < movedCount; i++) {
|
|
||||||
const pageIndex = pagesToMove[i] - 1;
|
|
||||||
mappedPagesToMove[i] = pageNumberToId[pageIndex];
|
|
||||||
if (pageIndex < index) {
|
|
||||||
removedBeforeTarget += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pagesNumber = PagesMapper.#pagesNumber;
|
|
||||||
// target index after removing elements that were before it
|
|
||||||
let adjustedTarget = index - removedBeforeTarget;
|
|
||||||
const remainingLen = pagesNumber - movedCount;
|
|
||||||
adjustedTarget = MathClamp(adjustedTarget, 0, remainingLen);
|
|
||||||
|
|
||||||
// Create the new mapping.
|
|
||||||
// First copy over the pages that are not being moved.
|
|
||||||
// Then insert the moved pages at the target position.
|
|
||||||
for (let i = 0, r = 0; i < pagesNumber; i++) {
|
|
||||||
if (!selectedPages.has(i + 1)) {
|
|
||||||
pageNumberToId[r++] = pageNumberToId[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift the pages after the target position.
|
|
||||||
pageNumberToId.copyWithin(
|
|
||||||
adjustedTarget + movedCount,
|
|
||||||
adjustedTarget,
|
|
||||||
remainingLen
|
|
||||||
);
|
|
||||||
// Finally insert the moved pages.
|
|
||||||
pageNumberToId.set(mappedPagesToMove, adjustedTarget);
|
|
||||||
|
|
||||||
for (let i = 0, ii = pagesNumber; i < ii; i++) {
|
|
||||||
idToPageNumber[pageNumberToId[i] - 1] = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the page number for a given page ID.
|
|
||||||
* @param {number} id - The page ID (1-indexed).
|
|
||||||
* @returns {number} The page number, or the ID itself if no mapping exists.
|
|
||||||
*/
|
|
||||||
getPageNumber(id) {
|
|
||||||
return PagesMapper.#idToPageNumber?.[id - 1] ?? id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the page ID for a given page number.
|
|
||||||
* @param {number} pageNumber - The page number (1-indexed).
|
|
||||||
* @returns {number} The page ID, or the page number itself if no mapping
|
|
||||||
* 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
getMapping() {
|
|
||||||
return PagesMapper.#pageNumberToId.subarray(0, this.pagesNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
animationStarted,
|
animationStarted,
|
||||||
apiPageLayoutToViewerModes,
|
apiPageLayoutToViewerModes,
|
||||||
@ -1046,7 +910,6 @@ export {
|
|||||||
MIN_SCALE,
|
MIN_SCALE,
|
||||||
normalizeWheelEventDelta,
|
normalizeWheelEventDelta,
|
||||||
normalizeWheelEventDirection,
|
normalizeWheelEventDirection,
|
||||||
PagesMapper,
|
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
PresentationModeState,
|
PresentationModeState,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user