Add the possibility to save added annotations when reorganizing a pdf (bug 2023086)

This commit is contained in:
Calixte Denizet 2026-03-17 16:28:47 +01:00
parent ff1af5a058
commit 04272de41d
No known key found for this signature in database
GPG Key ID: 0C5442631EE0691F
16 changed files with 444 additions and 164 deletions

View File

@ -354,12 +354,12 @@ class AnnotationFactory {
static async saveNewAnnotations(
evaluator,
xref,
task,
annotations,
imagePromises,
changes
) {
const xref = evaluator.xref;
let baseFontRef;
const promises = [];
const { isOffscreenCanvasSupported } = evaluator.options;

View File

@ -151,6 +151,10 @@ class Page {
});
}
createAnnotationEvaluator(handler) {
return this.#createPartialEvaluator(handler);
}
#getInheritableProperty(key, getArray = false) {
const value = getInheritableProperty({
dict: this.pageDict,
@ -386,6 +390,7 @@ class Page {
);
const newData = await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
this.xref,
task,
annotations,
imagePromises,

View File

@ -16,16 +16,21 @@
/** @typedef {import("../document.js").PDFDocument} PDFDocument */
/** @typedef {import("../document.js").Page} Page */
/** @typedef {import("../xref.js").XRef} XRef */
/** @typedef {import("../worker.js").WorkerTask} WorkerTask */
// eslint-disable-next-line max-len
/** @typedef {import("../../shared/message_handler.js").MessageHandler} MessageHandler */
import {
deepCompare,
getInheritableProperty,
getNewAnnotationsMap,
stringToAsciiOrUTF16BE,
} from "../core_utils.js";
import { Dict, isName, Name, Ref, RefSet, RefSetCache } from "../primitives.js";
import { getModificationDate, stringToPDFString } from "../../shared/util.js";
import { incrementalUpdate, writeValue } from "../writer.js";
import { NameTree, NumberTree } from "../name_number_tree.js";
import { AnnotationFactory } from "../annotation.js";
import { BaseStream } from "../base_stream.js";
import { StringStream } from "../stream.js";
@ -75,8 +80,9 @@ class DocumentData {
}
class XRefWrapper {
constructor(entries) {
constructor(entries, getNewRef) {
this.entries = entries;
this._getNewRef = getNewRef;
}
fetch(ref) {
@ -94,11 +100,17 @@ class XRefWrapper {
fetchAsync(ref) {
return Promise.resolve(this.fetch(ref));
}
getNewTemporaryRef() {
return this._getNewRef();
}
}
class PDFEditor {
hasSingleFile = false;
#newAnnotationsParams = null;
currentDocument = null;
oldPages = [];
@ -107,7 +119,7 @@ class PDFEditor {
xref = [null];
xrefWrapper = new XRefWrapper(this.xref);
xrefWrapper = new XRefWrapper(this.xref, () => this.newRef);
newRefCount = 1;
@ -535,13 +547,33 @@ class PDFEditor {
/**
* Extract pages from the given documents.
* @param {Array<PageInfo>} pageInfos
* @param {Object} annotationStorage - The annotation storage containing the
* annotations to be merged into the new document.
* @param {MessageHandler} handler - The message handler to use for processing
* the annotations.
* @param {WorkerTask} task - The worker task to use for reporting progress
* and cancellation.
* @return {Promise<void>}
*/
async extractPages(pageInfos) {
async extractPages(pageInfos, annotationStorage, handler, task) {
const promises = [];
let newIndex = 0;
this.hasSingleFile = pageInfos.length === 1;
const allDocumentData = [];
if (annotationStorage) {
this.#newAnnotationsParams = {
handler,
task,
newAnnotationsByPage: getNewAnnotationsMap(annotationStorage),
imagesPromises: AnnotationFactory.generateImages(
annotationStorage.values(),
this.xrefWrapper,
true
),
};
}
for (const {
document,
includePages,
@ -1930,6 +1962,8 @@ class PDFEditor {
await this.#collectDependencies(resources, true, xref)
);
let newAnnots = null;
if (annotations) {
const newAnnotations = await this.#collectDependencies(
annotations,
@ -1937,9 +1971,35 @@ class PDFEditor {
xref
);
this.#fixNamedDestinations(newAnnotations, dedupNamedDestinations);
pageDict.setIfArray("Annots", newAnnotations);
if (Array.isArray(newAnnotations) && newAnnotations.length > 0) {
newAnnots = newAnnotations;
}
}
const newAnnotations =
this.#newAnnotationsParams?.newAnnotationsByPage.get(pageIndex);
if (newAnnotations) {
const { handler, task, imagesPromises } = this.#newAnnotationsParams;
const changes = new RefSetCache();
const newData = await AnnotationFactory.saveNewAnnotations(
page.createAnnotationEvaluator(handler),
this.xrefWrapper,
task,
newAnnotations,
imagesPromises,
changes
);
for (const [ref, { data }] of changes.items()) {
this.xref[ref.num] = data;
}
newAnnots ||= [];
for (const { ref } of newData.annotations) {
newAnnots.push(ref);
}
}
pageDict.setIfArray("Annots", newAnnots);
if (this.useObjectStreams) {
const newLastRef = this.newRefCount;
const pageObjectRefs = [];

View File

@ -551,96 +551,111 @@ class WorkerMessageHandler {
return pdfManager.ensureDoc("calculationOrderIds");
});
handler.on("ExtractPages", async function ({ pageInfos }) {
if (!pageInfos) {
warn("extractPages: nothing to extract.");
return null;
}
if (!Array.isArray(pageInfos)) {
pageInfos = [pageInfos];
}
let newDocumentId = 0;
for (const pageInfo of pageInfos) {
if (pageInfo.document === null) {
pageInfo.document = pdfManager.pdfDocument;
} else if (ArrayBuffer.isView(pageInfo.document)) {
const manager = new LocalPdfManager({
source: pageInfo.document,
docId: `${docId}_extractPages_${newDocumentId++}`,
handler,
password: pageInfo.password ?? null,
evaluatorOptions: Object.assign({}, pdfManager.evaluatorOptions),
});
let recoveryMode = false;
let isValid = true;
while (true) {
try {
await manager.requestLoadedStream();
await manager.ensureDoc("checkHeader");
await manager.ensureDoc("parseStartXRef");
await manager.ensureDoc("parse", [recoveryMode]);
break;
} catch (e) {
if (e instanceof XRefParseException) {
if (recoveryMode === false) {
recoveryMode = true;
continue;
handler.on(
"ExtractPages",
async function ({ pageInfos, annotationStorage }) {
if (!pageInfos) {
warn("extractPages: nothing to extract.");
return null;
}
if (!Array.isArray(pageInfos)) {
pageInfos = [pageInfos];
}
let newDocumentId = 0;
for (const pageInfo of pageInfos) {
if (pageInfo.document === null) {
pageInfo.document = pdfManager.pdfDocument;
} else if (ArrayBuffer.isView(pageInfo.document)) {
const manager = new LocalPdfManager({
source: pageInfo.document,
docId: `${docId}_extractPages_${newDocumentId++}`,
handler,
password: pageInfo.password ?? null,
evaluatorOptions: Object.assign({}, pdfManager.evaluatorOptions),
});
let recoveryMode = false;
let isValid = true;
while (true) {
try {
await manager.requestLoadedStream();
await manager.ensureDoc("checkHeader");
await manager.ensureDoc("parseStartXRef");
await manager.ensureDoc("parse", [recoveryMode]);
break;
} catch (e) {
if (e instanceof XRefParseException) {
if (recoveryMode === false) {
recoveryMode = true;
continue;
} else {
isValid = false;
warn("extractPages: XRefParseException.");
}
} else if (e instanceof PasswordException) {
const task = new WorkerTask(
`PasswordException: response ${e.code}`
);
startWorkerTask(task);
try {
const { password } = await handler.sendWithPromise(
"PasswordRequest",
e
);
manager.updatePassword(password);
} catch {
isValid = false;
warn("extractPages: invalid password.");
} finally {
finishWorkerTask(task);
}
} else {
isValid = false;
warn("extractPages: XRefParseException.");
warn("extractPages: invalid document.");
}
} else if (e instanceof PasswordException) {
const task = new WorkerTask(
`PasswordException: response ${e.code}`
);
startWorkerTask(task);
try {
const { password } = await handler.sendWithPromise(
"PasswordRequest",
e
);
manager.updatePassword(password);
} catch {
isValid = false;
warn("extractPages: invalid password.");
} finally {
finishWorkerTask(task);
if (!isValid) {
break;
}
} else {
isValid = false;
warn("extractPages: invalid document.");
}
if (!isValid) {
break;
}
}
}
if (!isValid) {
pageInfo.document = null;
}
const isPureXfa = await manager.ensureDoc("isPureXfa");
if (isPureXfa) {
pageInfo.document = null;
warn("extractPages does not support pure XFA documents.");
if (!isValid) {
pageInfo.document = null;
}
const isPureXfa = await manager.ensureDoc("isPureXfa");
if (isPureXfa) {
pageInfo.document = null;
warn("extractPages does not support pure XFA documents.");
} else {
pageInfo.document = manager.pdfDocument;
}
} else {
pageInfo.document = manager.pdfDocument;
warn("extractPages: invalid document.");
}
}
let task;
try {
const pdfEditor = new PDFEditor();
task = new WorkerTask(`ExtractPages: ${pageInfos.length} page(s)`);
startWorkerTask(task);
const buffer = await pdfEditor.extractPages(
pageInfos,
annotationStorage,
handler,
task
);
return buffer;
} catch (reason) {
// eslint-disable-next-line no-console
console.error(reason);
return null;
} finally {
if (task) {
finishWorkerTask(task);
}
} else {
warn("extractPages: invalid document.");
}
}
try {
const pdfEditor = new PDFEditor();
const buffer = await pdfEditor.extractPages(pageInfos);
return buffer;
} catch (reason) {
// eslint-disable-next-line no-console
console.error(reason);
return null;
}
});
);
handler.on(
"SaveDocument",

View File

@ -2973,7 +2973,20 @@ class WorkerTransport {
}
extractPages(pageInfos) {
return this.messageHandler.sendWithPromise("ExtractPages", { pageInfos });
const params = {
pageInfos,
};
let transfer;
if (this.annotationStorage.size > 0) {
const { map, transfer: t } = this.annotationStorage.serializable;
params.annotationStorage = map;
transfer = t;
}
return this.messageHandler
.sendWithPromise("ExtractPages", params, transfer)
.finally(() => {
this.annotationStorage.resetModified();
});
}
getPage(pageNumber) {

View File

@ -144,47 +144,6 @@ class AnnotationEditorLayer {
this.#uiManager.addLayer(this);
}
updatePageIndex(newPageIndex) {
for (const editor of this.#allEditorsIterator) {
editor.updatePageIndex(newPageIndex);
}
this.pageIndex = newPageIndex;
this.#uiManager.addLayer(this);
}
/**
* Clones all annotation editors from another layer into this layer.
* This is typically used when duplicating a page - the editors from the
* source page are serialized and then deserialized into the new page's layer.
*
* @param {AnnotationEditorLayer} clonedFrom - The source annotation editor
* layer to clone editors from. If null or undefined, no action is taken.
* @returns {Promise<void>} A promise that resolves when all editors have been
* cloned and added to this layer.
*/
async setClonedFrom(clonedFrom) {
if (!clonedFrom) {
return;
}
const promises = [];
for (const editor of clonedFrom.#allEditorsIterator) {
const serialized = editor.serialize(/* isForCopying = */ true);
if (!serialized) {
continue;
}
serialized.isCopy = false;
promises.push(
this.deserialize(serialized).then(deserialized => {
if (deserialized) {
this.addOrRebuild(deserialized);
}
})
);
}
await Promise.all(promises);
}
get isEmpty() {
return this.#editors.size === 0;
}
@ -713,14 +672,6 @@ class AnnotationEditorLayer {
return null;
}
/**
* Get an id for an editor.
* @returns {string}
*/
getNextId() {
return this.#uiManager.getId();
}
get #currentEditorType() {
return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode());
}
@ -753,7 +704,7 @@ class AnnotationEditorLayer {
await this.#uiManager.updateMode(options.mode);
const { offsetX, offsetY } = this.#getCenterPoint();
const id = this.getNextId();
const id = this.#uiManager.getId();
const editor = this.#createNewEditor({
parent: this,
id,
@ -789,7 +740,7 @@ class AnnotationEditorLayer {
* @returns {AnnotationEditor}
*/
createAndAddNewEditor(event, isCentered, data = {}) {
const id = this.getNextId();
const id = this.#uiManager.getId();
const editor = this.#createNewEditor({
parent: this,
id,
@ -1073,13 +1024,17 @@ class AnnotationEditorLayer {
* Render the main editor.
* @param {RenderEditorLayerOptions} parameters
*/
render({ viewport }) {
async render({ viewport }) {
this.viewport = viewport;
setLayerDimensions(this.div, viewport);
for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
this.add(editor);
editor.rebuild();
}
await this.#uiManager.findClonesForPage(this);
this.div.hidden = this.isEmpty;
// We're maybe rendering a layer which was invisible when we started to edit
// so we must set the different callbacks for it.
this.updateMode();

View File

@ -235,7 +235,7 @@ class AnnotationEditor {
static deleteAnnotationElement(editor) {
const fakeEditor = new FakeEditor({
id: editor.parent.getNextId(),
id: editor._uiManager.getId(),
parent: editor.parent,
uiManager: editor._uiManager,
});
@ -480,6 +480,10 @@ class AnnotationEditor {
}
_moveAfterPaste(baseX, baseY) {
if (this.isClone) {
delete this.isClone;
return;
}
const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt(
baseX * parentWidth,
@ -1862,7 +1866,7 @@ class AnnotationEditor {
static async deserialize(data, parent, uiManager) {
const editor = new this.prototype.constructor({
parent,
id: parent.getNextId(),
id: uiManager.getId(),
uiManager,
annotationElementId: data.annotationElementId,
creationDate: data.creationDate,

View File

@ -959,7 +959,7 @@ class HighlightEditor extends AnnotationEditor {
};
}
const { color, quadPoints, inkLists, opacity } = data;
const { color, quadPoints, inkLists, outlines, opacity } = data;
const editor = await super.deserialize(data, parent, uiManager);
editor.color = Util.makeHexColor(...color);
@ -988,9 +988,9 @@ class HighlightEditor extends AnnotationEditor {
editor.#createOutlines();
editor.#addToDrawLayer();
editor.rotate(editor.rotation);
} else if (inkLists) {
} else if (inkLists || outlines) {
editor.#isFreeHighlight = true;
const points = inkLists[0];
const points = (inkLists || outlines.points)[0];
const point = {
x: points[0] - pageX,
y: pageHeight - (points[1] - pageY),

View File

@ -675,6 +675,8 @@ class AnnotationEditorUIManager {
#allLayers = new Map();
#savedAllLayers = null;
#altTextManager = null;
#annotationStorage = null;
@ -1841,6 +1843,66 @@ class AnnotationEditorUIManager {
}
}
updatePageIndex(oldPageIndex, newPageIndex) {
for (const editor of this.getEditors(oldPageIndex)) {
editor.pageIndex = newPageIndex;
}
const layer = this.#savedAllLayers.get(oldPageIndex);
if (layer) {
layer.pageIndex = newPageIndex;
this.#allLayers.set(newPageIndex, layer);
if (this.#isEnabled) {
layer.enable();
} else {
layer.disable();
}
}
}
startUpdatePages() {
this.#savedAllLayers = new Map(this.#allLayers);
this.#allLayers.clear();
}
endUpdatePages() {
this.#savedAllLayers = null;
}
clonePage(pageIndex, newPageIndex) {
for (const editor of this.getEditors(pageIndex)) {
const serialized = editor.serialize(
editor.mode !== AnnotationEditorType.HIGHLIGHT
);
if (!serialized) {
continue;
}
serialized.pageIndex = newPageIndex;
serialized.id = this.getId();
serialized.isClone = true;
delete serialized.popupRef;
this.#annotationStorage.setValue(serialized.id, serialized);
}
}
findClonesForPage(layer) {
const promises = [];
const { pageIndex } = layer;
for (const [id, editor] of this.#annotationStorage) {
if (editor.pageIndex === pageIndex && editor.isClone) {
this.#annotationStorage.remove(id);
promises.push(
layer.deserialize(editor).then(deserializedEditor => {
if (deserializedEditor) {
deserializedEditor.isClone = true;
layer.addOrRebuild(deserializedEditor);
}
})
);
}
}
return Promise.all(promises);
}
/**
* Update the different possible states of this manager, e.g. is there
* something to undo, redo, ...

View File

@ -31,9 +31,13 @@ import {
PDI,
scrollIntoView,
showViewsManager,
switchToEditor,
waitAndClick,
waitForBrowserTrip,
waitForDOMMutation,
waitForPointerUp,
waitForSerialized,
waitForStorageEntries,
waitForTextToBe,
waitForTooltipToBe,
} from "./test_utils.mjs";
@ -2563,4 +2567,87 @@ describe("Reorganize Pages View", () => {
);
});
});
describe("Copy page with an ink annotation and paste it", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"page_with_number.pdf",
".annotationEditorLayer",
"50",
null,
{ enableSplitMerge: true }
);
});
afterEach(async () => {
await closePages(pages);
});
it("should check that the pasted page has an ink annotation in the DOM", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
// Enable ink editor mode and draw a line on page 1.
await switchToEditor("Ink", page);
const rect = await getRect(
page,
".page[data-page-number='1'] .annotationEditorLayer"
);
const x = rect.x + rect.width * 0.3;
const y = rect.y + rect.height * 0.3;
const clickHandle = await waitForPointerUp(page);
await page.mouse.move(x, y);
await page.mouse.down();
await page.mouse.move(x + 50, y + 50);
await page.mouse.up();
await awaitPromise(clickHandle);
// Commit the drawing and wait for it to be serialized.
await page.keyboard.press("Escape");
await waitForSerialized(page, 1);
await waitForThumbnailVisible(page, 1);
// Select page 1 and copy it.
await page.waitForSelector("#viewsManagerStatusActionButton", {
visible: true,
});
await waitAndClick(
page,
`.thumbnail:has(${getThumbnailSelector(1)}) input`
);
let handlePagesEdited = await waitForPagesEdited(page);
await waitAndClick(page, "#viewsManagerStatusActionButton");
await waitAndClick(page, "#viewsManagerStatusActionCopy");
await awaitPromise(handlePagesEdited);
// Paste after page 2 so the copy lands at position 3.
handlePagesEdited = await waitForPagesEdited(page);
await waitAndClick(page, `${getThumbnailSelector(2)}+button`);
await awaitPromise(handlePagesEdited);
// Both the original and the cloned annotation must now be in storage.
await waitForStorageEntries(page, 2);
// Close the reorganize view and navigate to page 3 (the pasted copy)
// to trigger rendering of its annotation editor layer.
await page.click("#viewsManagerToggleButton");
await page.waitForSelector("#viewsManager", { hidden: true });
await page.evaluate(() => {
window.PDFViewerApplication.pdfViewer.currentPageNumber = 3;
});
// The cloned ink annotation must appear in the DOM of page 3.
await page.waitForSelector(`.page[data-page-number="3"] .inkEditor`, {
visible: true,
});
const inkEditors = await page.$$(
`.page[data-page-number="3"] .inkEditor`
);
expect(inkEditors.length).withContext(`In ${browserName}`).toBe(1);
})
);
});
});
});

View File

@ -4203,6 +4203,7 @@ describe("annotation", function () {
const changes = new RefSetCache();
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -4320,6 +4321,7 @@ describe("annotation", function () {
const task = new WorkerTask("test FreeText update");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -4435,6 +4437,7 @@ describe("annotation", function () {
const task = new WorkerTask("test Ink creation");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -4532,6 +4535,7 @@ describe("annotation", function () {
const task = new WorkerTask("test Ink creation");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -4764,6 +4768,7 @@ describe("annotation", function () {
const task = new WorkerTask("test Highlight creation");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -4857,6 +4862,7 @@ describe("annotation", function () {
const task = new WorkerTask("test free Highlight creation");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -4988,6 +4994,7 @@ describe("annotation", function () {
const task = new WorkerTask("test Highlight update");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -5051,6 +5058,7 @@ describe("annotation", function () {
const task = new WorkerTask("test Highlight update");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{
@ -5215,6 +5223,7 @@ describe("annotation", function () {
const task = new WorkerTask("test Stamp creation");
await AnnotationFactory.saveNewAnnotations(
partialEvaluator,
xref,
task,
[
{

View File

@ -6254,6 +6254,79 @@ small scripts as well as for`);
await loadingTask.destroy();
});
it("save an ink annotation on a cloned page", async function () {
let loadingTask = getDocument(buildGetDocumentParams("empty.pdf"));
let pdfDoc = await loadingTask.promise;
// Simulate what clonePage() puts in annotationStorage when a page is
// copied: the original annotation stays on pageIndex 0 and the clone
// is placed on pageIndex 1 (the new position of the pasted copy).
const inkAnnotation = {
annotationType: AnnotationEditorType.INK,
rect: [50, 50, 200, 200],
rotation: 0,
structTreeParentId: null,
popupRef: "",
color: [0, 0, 255],
opacity: 1,
thickness: 2,
paths: {
lines: [
new Float32Array([
0,
0,
0,
0,
50,
200,
NaN,
NaN,
NaN,
NaN,
200,
50,
]),
],
points: [[50, 200, 100, 100, 200, 50]],
},
isCopy: true,
};
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_0", {
...inkAnnotation,
pageIndex: 0,
});
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_1", {
...inkAnnotation,
pageIndex: 1,
});
// Extract page 0 twice: once at output position 0 (original) and once
// at output position 1 (clone), mirroring copy+paste in the UI.
const data = await pdfDoc.extractPages([
{ document: null, includePages: [0], pageIndices: [0] },
{ document: null, includePages: [0], pageIndices: [1] },
]);
await loadingTask.destroy();
loadingTask = getDocument(data);
pdfDoc = await loadingTask.promise;
expect(pdfDoc.numPages).toEqual(2);
// Both pages should carry the ink annotation.
for (let i = 1; i <= 2; i++) {
const pdfPage = await pdfDoc.getPage(i);
const annotations = await pdfPage.getAnnotations();
expect(annotations.length).withContext(`Page ${i}`).toEqual(1);
expect(annotations[0].annotationType)
.withContext(`Page ${i}`)
.toEqual(AnnotationType.INK);
}
await loadingTask.destroy();
});
it("fills missing pageIndices with the first free slots", async function () {
let loadingTask = getDocument(
buildGetDocumentParams("tracemonkey.pdf")

View File

@ -39,7 +39,6 @@ import { GenericL10n } from "web-null_l10n";
* @property {TextLayer} [textLayer]
* @property {DrawLayer} [drawLayer]
* @property {function} [onAppend]
* @property {AnnotationEditorLayer} [clonedFrom]
*/
/**
@ -61,8 +60,6 @@ class AnnotationEditorLayerBuilder {
#uiManager;
#clonedFrom = null;
/**
* @param {AnnotationEditorLayerBuilderOptions} options
*/
@ -82,7 +79,6 @@ class AnnotationEditorLayerBuilder {
this.#drawLayer = options.drawLayer || null;
this.#onAppend = options.onAppend || null;
this.#structTreeLayer = options.structTreeLayer || null;
this.#clonedFrom = options.clonedFrom || null;
}
updatePageIndex(newPageIndex) {
@ -130,11 +126,6 @@ class AnnotationEditorLayerBuilder {
drawLayer: this.#drawLayer,
});
this.annotationEditorLayer.setClonedFrom(
this.#clonedFrom?.annotationEditorLayer
);
this.#clonedFrom = null;
const parameters = {
viewport: clonedViewport,
div,
@ -142,7 +133,7 @@ class AnnotationEditorLayerBuilder {
intent,
};
this.annotationEditorLayer.render(parameters);
await this.annotationEditorLayer.render(parameters);
this.show();
}

View File

@ -107,7 +107,6 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
* @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
* text that look like URLs. The default value is `true`.
* @property {CommentManager} [commentManager] - The comment manager instance.
* @property {PDFPageView} [clonedFrom] - The page view that is cloned
* to.
*/
@ -172,8 +171,6 @@ class PDFPageView extends BasePDFPageView {
#layers = [null, null, null, null];
#clonedFrom = null;
/**
* @param {PDFPageViewOptions} options
*/
@ -205,7 +202,6 @@ class PDFPageView extends BasePDFPageView {
options.capCanvasAreaFactor ?? AppOptions.get("capCanvasAreaFactor");
this.#enableAutoLinking = options.enableAutoLinking !== false;
this.#commentManager = options.commentManager || null;
this.#clonedFrom = options.clonedFrom || null;
this.l10n = options.l10n;
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
@ -301,7 +297,6 @@ class PDFPageView extends BasePDFPageView {
enableAutoLinking: this.#enableAutoLinking,
commentManager: this.#commentManager,
l10n: this.l10n,
clonedFrom: this,
});
clone.setPdfPage(this.pdfPage.clone(id - 1));
return clone;
@ -355,6 +350,7 @@ class PDFPageView extends BasePDFPageView {
if (this.id === newPageNumber) {
return;
}
const oldPageNumber = this.id;
this.id = newPageNumber;
this.renderingId = `page${newPageNumber}`;
if (this.pdfPage) {
@ -368,7 +364,11 @@ class PDFPageView extends BasePDFPageView {
this._textHighlighter.pageIdx = newPageNumber - 1;
// Don't update the page index for the draw layer, since it's just used as
// an identifier.
this.annotationEditorLayer?.updatePageIndex(newPageNumber - 1);
this.#layerProperties.annotationEditorUIManager?.updatePageIndex(
oldPageNumber - 1,
newPageNumber - 1
);
}
setPdfPage(pdfPage) {
@ -1207,12 +1207,10 @@ class PDFPageView extends BasePDFPageView {
annotationLayer: this.annotationLayer?.annotationLayer,
textLayer: this.textLayer,
drawLayer: this.drawLayer.getDrawLayer(),
clonedFrom: this.#clonedFrom?.annotationEditorLayer,
onAppend: annotationEditorLayerDiv => {
this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
},
});
this.#clonedFrom = null;
this.#renderAnnotationEditorLayer();
}
});

View File

@ -80,8 +80,6 @@ class TempImageFactory {
class PDFThumbnailView extends RenderableView {
#renderingState = RenderingStates.INITIAL;
static foo = 0;
/**
* @param {PDFThumbnailViewOptions} options
*/
@ -99,7 +97,6 @@ class PDFThumbnailView extends RenderableView {
enableSplitMerge = false,
}) {
super();
this.foo = PDFThumbnailView.foo++;
this.id = id;
this.renderingId = `thumbnail${id}`;
this.pageLabel = null;

View File

@ -1209,6 +1209,7 @@ class PDFViewer {
const viewerElement =
this._scrollMode === ScrollMode.PAGE ? null : this.viewer;
if (viewerElement) {
this.#annotationEditorUIManager?.startUpdatePages();
const fragment = document.createDocumentFragment();
for (let i = 0, ii = this.#savedPageViews.length; i < ii; i++) {
const page = this.#savedPageViews[i];
@ -1216,6 +1217,7 @@ class PDFViewer {
fragment.append(page.div);
}
viewerElement.replaceChildren(fragment);
this.#annotationEditorUIManager?.endUpdatePages();
}
this._pages = this.#savedPageViews;
this.#savedPageViews = null;
@ -1238,6 +1240,9 @@ class PDFViewer {
this._currentPageNumber = 0;
const prevPages = this._pages;
const newPages = (this._pages = []);
this.#annotationEditorUIManager?.startUpdatePages();
for (let i = 1, ii = pagesMapper.pagesNumber; i <= ii; i++) {
const prevPageNumber = pagesMapper.getPrevPageNumber(i);
if (prevPageNumber < 0) {
@ -1245,6 +1250,10 @@ class PDFViewer {
if (hasBeenCut) {
page.updatePageNumber(i);
} else {
this.#annotationEditorUIManager?.clonePage(
-prevPageNumber - 1,
i - 1
);
page = page.clone(i);
}
newPages.push(page);
@ -1255,6 +1264,8 @@ class PDFViewer {
page.updatePageNumber(i);
}
this.#annotationEditorUIManager?.endUpdatePages();
if (type === "paste") {
this.#copiedPageViews = null;
}