mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-14 09:14:04 +02:00
Merge pull request #20785 from calixteman/extract_pages
Add a way to extract some pages from a pdf (bug 2019682)
This commit is contained in:
commit
68cca32e20
@ -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([
|
||||
@ -1083,4 +1073,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
|
||||
@ -713,8 +725,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