mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-09 23:04:02 +02:00
Add a way to extract some pages from a pdf (bug 2019682)
The user has to select some pages and then click on the "Save As" menu item in the Manage menu. If they modify the structure of the pdf (deleted, moved, copied pages), they have to use the usual save button.
This commit is contained in:
parent
973add845f
commit
a474e81b8a
@ -17,6 +17,7 @@ import {
|
||||
BaseException,
|
||||
DrawOPS,
|
||||
FeatureTest,
|
||||
makeArr,
|
||||
MathClamp,
|
||||
shadow,
|
||||
stripPath,
|
||||
@ -1360,9 +1361,7 @@ class PagesMapper {
|
||||
* Gets the current page mapping suitable for saving.
|
||||
* @returns {Object} An object containing the page indices.
|
||||
*/
|
||||
getPageMappingForSaving() {
|
||||
const idToPageNumber = this.#idToPageNumber;
|
||||
|
||||
getPageMappingForSaving(idToPageNumber = this.#idToPageNumber) {
|
||||
// idToPageNumber maps used 1-based IDs to 1-based page numbers.
|
||||
// For example if the final pdf contains page 3 twice and they are moved at
|
||||
// page 1 and 4, then it contains:
|
||||
@ -1413,6 +1412,19 @@ class PagesMapper {
|
||||
return extractParams;
|
||||
}
|
||||
|
||||
extractPages(extractedPageNumbers) {
|
||||
extractedPageNumbers = Array.from(extractedPageNumbers).sort(
|
||||
(a, b) => a - b
|
||||
);
|
||||
const usedIds = new Map();
|
||||
for (let i = 0, ii = extractedPageNumbers.length; i < ii; i++) {
|
||||
const id = this.getPageId(extractedPageNumbers[i]);
|
||||
const usedPageNumbers = usedIds.getOrInsertComputed(id, makeArr);
|
||||
usedPageNumbers.push(i + 1);
|
||||
}
|
||||
return this.getPageMappingForSaving(usedIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous page number for a given page number.
|
||||
* @param {number} pageNumber
|
||||
|
||||
@ -614,24 +614,14 @@ describe("Reorganize Pages View", () => {
|
||||
10
|
||||
);
|
||||
|
||||
const handleSaveAs = await createPromise(page, resolve => {
|
||||
window.PDFViewerApplication.eventBus.on(
|
||||
"savepageseditedpdf",
|
||||
({ data }) => {
|
||||
resolve(Array.from(data[0].pageIndices));
|
||||
},
|
||||
{
|
||||
once: true,
|
||||
}
|
||||
);
|
||||
const handleSave = await createPromise(page, resolve => {
|
||||
window.PDFViewerApplication.onSavePages = async ({ data }) => {
|
||||
resolve(Array.from(data[0].pageIndices));
|
||||
};
|
||||
});
|
||||
|
||||
await page.click("#viewsManagerStatusActionButton");
|
||||
await page.waitForSelector("#viewsManagerStatusActionSaveAs", {
|
||||
visible: true,
|
||||
});
|
||||
await page.click("#viewsManagerStatusActionSaveAs");
|
||||
const pageIndices = await awaitPromise(handleSaveAs);
|
||||
await waitAndClick(page, "#downloadButton");
|
||||
const pageIndices = await awaitPromise(handleSave);
|
||||
expect(pageIndices)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual([
|
||||
@ -1041,4 +1031,59 @@ describe("Reorganize Pages View", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Extract some pages from a pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"page_with_number.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
"page-fit",
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should check that the pages are correctly extracted", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
await waitAndClick(
|
||||
page,
|
||||
`.thumbnail:has(${getThumbnailSelector(1)}) input`
|
||||
);
|
||||
await waitAndClick(
|
||||
page,
|
||||
`.thumbnail:has(${getThumbnailSelector(3)}) input`
|
||||
);
|
||||
|
||||
const handleSaveAs = await createPromise(page, resolve => {
|
||||
window.PDFViewerApplication.eventBus.on(
|
||||
"saveextractedpages",
|
||||
({ data }) => {
|
||||
resolve(data);
|
||||
},
|
||||
{
|
||||
once: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await page.click("#viewsManagerStatusActionButton");
|
||||
await waitAndClick(page, "#viewsManagerStatusActionSaveAs");
|
||||
const pagesData = await awaitPromise(handleSaveAs);
|
||||
expect(pagesData)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual([
|
||||
{ document: null, pageIndices: [0, 1], includePages: [0, 2] },
|
||||
]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
59
web/app.js
59
web/app.js
@ -1072,9 +1072,9 @@ const PDFViewerApplication = {
|
||||
// Embedded PDF viewers should not be changing their parent page's title.
|
||||
return;
|
||||
}
|
||||
const editorIndicator =
|
||||
this._hasAnnotationEditors && !this.pdfRenderingQueue.printing;
|
||||
document.title = `${editorIndicator ? "* " : ""}${title}`;
|
||||
const hasChangesIndicator =
|
||||
this._hasChanges() && !this.pdfRenderingQueue.printing;
|
||||
document.title = `${hasChangesIndicator ? "* " : ""}${title}`;
|
||||
},
|
||||
|
||||
get _docFilename() {
|
||||
@ -1129,12 +1129,12 @@ const PDFViewerApplication = {
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("GENERIC && !TESTING")) &&
|
||||
this.pdfDocument?.annotationStorage.size > 0 &&
|
||||
this._hasChanges() &&
|
||||
this._annotationStorageModified
|
||||
) {
|
||||
try {
|
||||
// Trigger saving, to prevent data loss in forms; see issue 12257.
|
||||
await this.save();
|
||||
await this.downloadOrSave();
|
||||
} catch {
|
||||
// Ignoring errors, to ensure that document closing won't break.
|
||||
}
|
||||
@ -1315,9 +1315,15 @@ const PDFViewerApplication = {
|
||||
// a message and change PdfjsChild.sys.mjs to take it into account.
|
||||
const { classList } = this.appConfig.appContainer;
|
||||
classList.add("wait");
|
||||
await (this.pdfDocument?.annotationStorage.size > 0
|
||||
? this.save()
|
||||
: this.download());
|
||||
|
||||
const structuralChanges = this.pdfThumbnailViewer?.getStructuralChanges();
|
||||
if (structuralChanges) {
|
||||
await this.onSavePages({ data: structuralChanges });
|
||||
} else {
|
||||
await (this.pdfDocument?.annotationStorage.size > 0
|
||||
? this.save()
|
||||
: this.download());
|
||||
}
|
||||
classList.remove("wait");
|
||||
},
|
||||
|
||||
@ -1862,6 +1868,13 @@ const PDFViewerApplication = {
|
||||
}
|
||||
},
|
||||
|
||||
_hasChanges() {
|
||||
return (
|
||||
this.pdfDocument?.annotationStorage.size > 0 ||
|
||||
this.pdfThumbnailViewer?.hasStructuralChanges()
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -1872,15 +1885,11 @@ const PDFViewerApplication = {
|
||||
const { annotationStorage } = pdfDocument;
|
||||
|
||||
annotationStorage.onSetModified = () => {
|
||||
window.addEventListener("beforeunload", beforeUnload);
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
this._annotationStorageModified = true;
|
||||
}
|
||||
};
|
||||
annotationStorage.onResetModified = () => {
|
||||
window.removeEventListener("beforeunload", beforeUnload);
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
delete this._annotationStorageModified;
|
||||
}
|
||||
@ -2185,11 +2194,7 @@ const PDFViewerApplication = {
|
||||
);
|
||||
}
|
||||
eventBus._on("pagesedited", this.onPagesEdited.bind(this), opts);
|
||||
eventBus._on(
|
||||
"savepageseditedpdf",
|
||||
this.onSavePagesEditedPDF.bind(this),
|
||||
opts
|
||||
);
|
||||
eventBus._on("saveextractedpages", this.onSavePages.bind(this), opts);
|
||||
},
|
||||
|
||||
bindWindowEvents() {
|
||||
@ -2270,6 +2275,9 @@ const PDFViewerApplication = {
|
||||
},
|
||||
{ signal }
|
||||
);
|
||||
window.addEventListener("beforeunload", onBeforeUnload.bind(this), {
|
||||
signal,
|
||||
});
|
||||
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) &&
|
||||
@ -2368,7 +2376,7 @@ const PDFViewerApplication = {
|
||||
this.pdfViewer.onPagesEdited(data);
|
||||
},
|
||||
|
||||
async onSavePagesEditedPDF({ data: extractParams }) {
|
||||
async onSavePages({ data: extractParams }) {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||
return;
|
||||
}
|
||||
@ -2876,6 +2884,15 @@ function closeEditorUndoBar(evt) {
|
||||
}
|
||||
}
|
||||
|
||||
function onBeforeUnload(evt) {
|
||||
if (this._hasChanges()) {
|
||||
evt.preventDefault();
|
||||
evt.returnValue = "";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function onClick(evt) {
|
||||
closeSecondaryToolbar.call(this, evt);
|
||||
closeEditorUndoBar.call(this, evt);
|
||||
@ -3230,10 +3247,4 @@ function onKeyDown(evt) {
|
||||
}
|
||||
}
|
||||
|
||||
function beforeUnload(evt) {
|
||||
evt.preventDefault();
|
||||
evt.returnValue = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
export { PDFViewerApplication };
|
||||
|
||||
@ -175,12 +175,7 @@ class PDFThumbnailViewer {
|
||||
|
||||
this._manageMenu = new Menu(menu, button, [copy, cut, del, saveAs]);
|
||||
this.#manageSaveAsButton = saveAs;
|
||||
saveAs.addEventListener("click", () => {
|
||||
this.eventBus.dispatch("savepageseditedpdf", {
|
||||
source: this,
|
||||
data: this.#pagesMapper.getPageMappingForSaving(),
|
||||
});
|
||||
});
|
||||
saveAs.addEventListener("click", this.#saveExtractedPages.bind(this));
|
||||
this.#manageDeleteButton = del;
|
||||
del.addEventListener("click", this.#deletePages.bind(this));
|
||||
this.#manageCopyButton = copy;
|
||||
@ -432,6 +427,14 @@ class PDFThumbnailViewer {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasStructuralChanges() {
|
||||
return this.#pagesMapper?.hasBeenAltered() || false;
|
||||
}
|
||||
|
||||
getStructuralChanges() {
|
||||
return this.#pagesMapper?.getPageMappingForSaving() || null;
|
||||
}
|
||||
|
||||
static #getScaleFactor(image) {
|
||||
return (PDFThumbnailViewer.#draggingScaleFactor ||= parseFloat(
|
||||
getComputedStyle(image).getPropertyValue("--thumbnail-dragging-scale")
|
||||
@ -617,6 +620,15 @@ class PDFThumbnailViewer {
|
||||
this.#selectedPages.clear();
|
||||
}
|
||||
|
||||
#saveExtractedPages() {
|
||||
this.eventBus.dispatch("saveextractedpages", {
|
||||
source: this,
|
||||
data: this.#pagesMapper.extractPages(this.#selectedPages),
|
||||
});
|
||||
this.#clearSelection();
|
||||
this.#toggleMenuEntries(false);
|
||||
}
|
||||
|
||||
#copyPages(clearSelection = true) {
|
||||
const pageNumbersToCopy = (this.#copiedPageNumbers = Uint32Array.from(
|
||||
this.#selectedPages
|
||||
@ -710,8 +722,8 @@ class PDFThumbnailViewer {
|
||||
}
|
||||
|
||||
#updateMenuEntries() {
|
||||
this.#manageSaveAsButton.disabled = !this.#pagesMapper.hasBeenAltered();
|
||||
this.#manageDeleteButton.disabled =
|
||||
this.#manageSaveAsButton.disabled =
|
||||
this.#manageDeleteButton.disabled =
|
||||
this.#manageCopyButton.disabled =
|
||||
this.#manageCutButton.disabled =
|
||||
!this.#selectedPages?.size;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user