mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-02 04:17:24 +02:00
Place new annotations on the correct page when extracting pages (bug 2027682)
This commit is contained in:
parent
cd1b5f57c7
commit
4c62a49483
@ -125,6 +125,8 @@ class PDFEditor {
|
|||||||
|
|
||||||
#newAnnotationsParams = null;
|
#newAnnotationsParams = null;
|
||||||
|
|
||||||
|
#primaryDocument = null;
|
||||||
|
|
||||||
currentDocument = null;
|
currentDocument = null;
|
||||||
|
|
||||||
oldPages = [];
|
oldPages = [];
|
||||||
@ -774,13 +776,22 @@ class PDFEditor {
|
|||||||
* @param {Array<PageInfo>} pageInfos
|
* @param {Array<PageInfo>} pageInfos
|
||||||
* @param {Object} annotationStorage - The annotation storage containing the
|
* @param {Object} annotationStorage - The annotation storage containing the
|
||||||
* annotations to be merged into the new document.
|
* annotations to be merged into the new document.
|
||||||
|
* @param {PDFDocument} primaryDocument - The document the annotation storage
|
||||||
|
* belongs to.
|
||||||
* @param {MessageHandler} handler - The message handler to use for processing
|
* @param {MessageHandler} handler - The message handler to use for processing
|
||||||
* the annotations.
|
* the annotations.
|
||||||
* @param {WorkerTask} task - The worker task to use for reporting progress
|
* @param {WorkerTask} task - The worker task to use for reporting progress
|
||||||
* and cancellation.
|
* and cancellation.
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async extractPages(pageInfos, annotationStorage, handler, task) {
|
async extractPages(
|
||||||
|
pageInfos,
|
||||||
|
annotationStorage,
|
||||||
|
primaryDocument,
|
||||||
|
handler,
|
||||||
|
task
|
||||||
|
) {
|
||||||
|
this.#primaryDocument = primaryDocument;
|
||||||
if (pageInfos.some(info => info.insertAfter !== undefined)) {
|
if (pageInfos.some(info => info.insertAfter !== undefined)) {
|
||||||
pageInfos = this.#resolveInsertAfterIndices(pageInfos);
|
pageInfos = this.#resolveInsertAfterIndices(pageInfos);
|
||||||
}
|
}
|
||||||
@ -2170,7 +2181,9 @@ class PDFEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newAnnotations =
|
const newAnnotations =
|
||||||
this.#newAnnotationsParams?.newAnnotationsByPage?.get(pageIndex);
|
documentData.document === this.#primaryDocument
|
||||||
|
? this.#newAnnotationsParams?.newAnnotationsByPage?.get(page.pageIndex)
|
||||||
|
: null;
|
||||||
if (newAnnotations) {
|
if (newAnnotations) {
|
||||||
const { handler, task, imagesPromises } = this.#newAnnotationsParams;
|
const { handler, task, imagesPromises } = this.#newAnnotationsParams;
|
||||||
const changes = new RefSetCache();
|
const changes = new RefSetCache();
|
||||||
|
|||||||
@ -647,6 +647,7 @@ class WorkerMessageHandler {
|
|||||||
const buffer = await pdfEditor.extractPages(
|
const buffer = await pdfEditor.extractPages(
|
||||||
pageInfos,
|
pageInfos,
|
||||||
annotationStorage,
|
annotationStorage,
|
||||||
|
pdfManager.pdfDocument,
|
||||||
handler,
|
handler,
|
||||||
task
|
task
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2916,9 +2916,32 @@ class WorkerTransport {
|
|||||||
};
|
};
|
||||||
let transfer;
|
let transfer;
|
||||||
if (this.annotationStorage.size > 0) {
|
if (this.annotationStorage.size > 0) {
|
||||||
const { map, transfer: t } = this.annotationStorage.serializable;
|
const serialized = this.annotationStorage.serializable;
|
||||||
|
let { map } = serialized;
|
||||||
|
transfer = serialized.transfer;
|
||||||
|
// Annotation pageIndex tracks the editor's current viewer position; the
|
||||||
|
// worker keys lookups by source index. Remap UI -> source via pagesMapper
|
||||||
|
// so reorganized pages still receive their annotations after extraction.
|
||||||
|
const mapping = this.pagesMapper.getMapping();
|
||||||
|
if (mapping) {
|
||||||
|
const remapped = new Map();
|
||||||
|
for (const [k, v] of map) {
|
||||||
|
if (
|
||||||
|
v?.pageIndex !== undefined &&
|
||||||
|
v.pageIndex >= 0 &&
|
||||||
|
v.pageIndex < mapping.length
|
||||||
|
) {
|
||||||
|
const sourceIdx = mapping[v.pageIndex] - 1;
|
||||||
|
if (sourceIdx !== v.pageIndex) {
|
||||||
|
remapped.set(k, { ...v, pageIndex: sourceIdx });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remapped.set(k, v);
|
||||||
|
}
|
||||||
|
map = remapped;
|
||||||
|
}
|
||||||
params.annotationStorage = map;
|
params.annotationStorage = map;
|
||||||
transfer = t;
|
|
||||||
}
|
}
|
||||||
return this.messageHandler
|
return this.messageHandler
|
||||||
.sendWithPromise("ExtractPages", params, transfer)
|
.sendWithPromise("ExtractPages", params, transfer)
|
||||||
|
|||||||
@ -6354,6 +6354,146 @@ small scripts as well as for`);
|
|||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("places new annotations on the correct page after reordering (bug 2027682)", async function () {
|
||||||
|
let loadingTask = getDocument(
|
||||||
|
buildGetDocumentParams("doc_1_3_pages.pdf")
|
||||||
|
);
|
||||||
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
|
||||||
|
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_0", {
|
||||||
|
annotationType: AnnotationEditorType.FREETEXT,
|
||||||
|
rect: [12, 34, 56, 78],
|
||||||
|
rotation: 0,
|
||||||
|
fontSize: 10,
|
||||||
|
color: [0, 0, 0],
|
||||||
|
value: "on source page 2",
|
||||||
|
pageIndex: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await pdfDoc.extractPages([
|
||||||
|
{ document: null, includePages: [0, 1, 2], pageIndices: [2, 0, 1] },
|
||||||
|
]);
|
||||||
|
await loadingTask.destroy();
|
||||||
|
|
||||||
|
loadingTask = getDocument(data);
|
||||||
|
pdfDoc = await loadingTask.promise;
|
||||||
|
expect(pdfDoc.numPages).toEqual(3);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
const pdfPage = await pdfDoc.getPage(i);
|
||||||
|
const annotations = await pdfPage.getAnnotations();
|
||||||
|
const hasNew = annotations.some(
|
||||||
|
a => a.contentsObj?.str === "on source page 2"
|
||||||
|
);
|
||||||
|
if (i === 1) {
|
||||||
|
expect(hasNew).withContext(`Page ${i}`).toBeTrue();
|
||||||
|
} else {
|
||||||
|
expect(hasNew).withContext(`Page ${i}`).toBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadingTask.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not bleed new annotations across documents when merging (bug 2027682)", async function () {
|
||||||
|
let loadingTask = getDocument(
|
||||||
|
buildGetDocumentParams("doc_1_3_pages.pdf")
|
||||||
|
);
|
||||||
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
const pdfData2 = await DefaultFileReaderFactory.fetch({
|
||||||
|
path: TEST_PDFS_PATH + "doc_2_3_pages.pdf",
|
||||||
|
});
|
||||||
|
|
||||||
|
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_0", {
|
||||||
|
annotationType: AnnotationEditorType.FREETEXT,
|
||||||
|
rect: [12, 34, 56, 78],
|
||||||
|
rotation: 0,
|
||||||
|
fontSize: 10,
|
||||||
|
color: [0, 0, 0],
|
||||||
|
value: "primary page 1",
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await pdfDoc.extractPages([
|
||||||
|
{ document: pdfData2 },
|
||||||
|
{ document: null },
|
||||||
|
]);
|
||||||
|
await loadingTask.destroy();
|
||||||
|
|
||||||
|
loadingTask = getDocument(data);
|
||||||
|
pdfDoc = await loadingTask.promise;
|
||||||
|
expect(pdfDoc.numPages).toEqual(6);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
const pdfPage = await pdfDoc.getPage(i);
|
||||||
|
const annotations = await pdfPage.getAnnotations();
|
||||||
|
const hasNew = annotations.some(
|
||||||
|
a => a.contentsObj?.str === "primary page 1"
|
||||||
|
);
|
||||||
|
if (i === 4) {
|
||||||
|
expect(hasNew).withContext(`Page ${i}`).toBeTrue();
|
||||||
|
} else {
|
||||||
|
expect(hasNew).withContext(`Page ${i}`).toBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadingTask.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("places annotations correctly after the user reorganizes pages in the viewer (bug 2027682)", async function () {
|
||||||
|
const loadingTask = getDocument(
|
||||||
|
buildGetDocumentParams("page_with_number.pdf")
|
||||||
|
);
|
||||||
|
const pdfDoc = await loadingTask.promise;
|
||||||
|
|
||||||
|
// Simulate the user moving source page 1 (1-based) to UI position 5,
|
||||||
|
// mirroring what the viewer's pagesMapper records on a drag-drop. The
|
||||||
|
// annotation editor is then rebound to that new UI position, so its
|
||||||
|
// pageIndex (=4 in 0-based) reflects where the editor *currently* is,
|
||||||
|
// not where its source page is.
|
||||||
|
pdfDoc.pagesMapper.movePages(new Set([1]), [1], 5);
|
||||||
|
|
||||||
|
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_0", {
|
||||||
|
annotationType: AnnotationEditorType.FREETEXT,
|
||||||
|
rect: [12, 34, 56, 78],
|
||||||
|
rotation: 0,
|
||||||
|
fontSize: 10,
|
||||||
|
color: [0, 0, 0],
|
||||||
|
value: "moved annotation",
|
||||||
|
pageIndex: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export-all reflecting the post-reorganize layout: source 0 ends at
|
||||||
|
// output 4, the others shift down by one.
|
||||||
|
const data = await pdfDoc.extractPages([
|
||||||
|
{
|
||||||
|
document: null,
|
||||||
|
includePages: [0, 1, 2, 3, 4],
|
||||||
|
pageIndices: [4, 0, 1, 2, 3],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await loadingTask.destroy();
|
||||||
|
|
||||||
|
const newLoadingTask = getDocument(data);
|
||||||
|
const newPdfDoc = await newLoadingTask.promise;
|
||||||
|
expect(newPdfDoc.numPages).toEqual(5);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
const pdfPage = await newPdfDoc.getPage(i);
|
||||||
|
const annotations = await pdfPage.getAnnotations();
|
||||||
|
const hasNew = annotations.some(
|
||||||
|
a => a.contentsObj?.str === "moved annotation"
|
||||||
|
);
|
||||||
|
if (i === 5) {
|
||||||
|
expect(hasNew).withContext(`Page ${i}`).toBeTrue();
|
||||||
|
} else {
|
||||||
|
expect(hasNew).withContext(`Page ${i}`).toBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await newLoadingTask.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it("fills missing pageIndices with the first free slots", async function () {
|
it("fills missing pageIndices with the first free slots", async function () {
|
||||||
let loadingTask = getDocument(
|
let loadingTask = getDocument(
|
||||||
buildGetDocumentParams("tracemonkey.pdf")
|
buildGetDocumentParams("tracemonkey.pdf")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user