mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-11 17:45:49 +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,
|
BaseException,
|
||||||
DrawOPS,
|
DrawOPS,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
|
makeArr,
|
||||||
MathClamp,
|
MathClamp,
|
||||||
shadow,
|
shadow,
|
||||||
stripPath,
|
stripPath,
|
||||||
@ -1360,9 +1361,7 @@ class PagesMapper {
|
|||||||
* Gets the current page mapping suitable for saving.
|
* Gets the current page mapping suitable for saving.
|
||||||
* @returns {Object} An object containing the page indices.
|
* @returns {Object} An object containing the page indices.
|
||||||
*/
|
*/
|
||||||
getPageMappingForSaving() {
|
getPageMappingForSaving(idToPageNumber = this.#idToPageNumber) {
|
||||||
const idToPageNumber = this.#idToPageNumber;
|
|
||||||
|
|
||||||
// idToPageNumber maps used 1-based IDs to 1-based page numbers.
|
// 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
|
// For example if the final pdf contains page 3 twice and they are moved at
|
||||||
// page 1 and 4, then it contains:
|
// page 1 and 4, then it contains:
|
||||||
@ -1413,6 +1412,19 @@ class PagesMapper {
|
|||||||
return extractParams;
|
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.
|
* Gets the previous page number for a given page number.
|
||||||
* @param {number} pageNumber
|
* @param {number} pageNumber
|
||||||
|
|||||||
@ -614,24 +614,14 @@ describe("Reorganize Pages View", () => {
|
|||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSaveAs = await createPromise(page, resolve => {
|
const handleSave = await createPromise(page, resolve => {
|
||||||
window.PDFViewerApplication.eventBus.on(
|
window.PDFViewerApplication.onSavePages = async ({ data }) => {
|
||||||
"savepageseditedpdf",
|
resolve(Array.from(data[0].pageIndices));
|
||||||
({ data }) => {
|
};
|
||||||
resolve(Array.from(data[0].pageIndices));
|
|
||||||
},
|
|
||||||
{
|
|
||||||
once: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.click("#viewsManagerStatusActionButton");
|
await waitAndClick(page, "#downloadButton");
|
||||||
await page.waitForSelector("#viewsManagerStatusActionSaveAs", {
|
const pageIndices = await awaitPromise(handleSave);
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
await page.click("#viewsManagerStatusActionSaveAs");
|
|
||||||
const pageIndices = await awaitPromise(handleSaveAs);
|
|
||||||
expect(pageIndices)
|
expect(pageIndices)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual([
|
.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.
|
// Embedded PDF viewers should not be changing their parent page's title.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const editorIndicator =
|
const hasChangesIndicator =
|
||||||
this._hasAnnotationEditors && !this.pdfRenderingQueue.printing;
|
this._hasChanges() && !this.pdfRenderingQueue.printing;
|
||||||
document.title = `${editorIndicator ? "* " : ""}${title}`;
|
document.title = `${hasChangesIndicator ? "* " : ""}${title}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
get _docFilename() {
|
get _docFilename() {
|
||||||
@ -1129,12 +1129,12 @@ const PDFViewerApplication = {
|
|||||||
if (
|
if (
|
||||||
(typeof PDFJSDev === "undefined" ||
|
(typeof PDFJSDev === "undefined" ||
|
||||||
PDFJSDev.test("GENERIC && !TESTING")) &&
|
PDFJSDev.test("GENERIC && !TESTING")) &&
|
||||||
this.pdfDocument?.annotationStorage.size > 0 &&
|
this._hasChanges() &&
|
||||||
this._annotationStorageModified
|
this._annotationStorageModified
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// Trigger saving, to prevent data loss in forms; see issue 12257.
|
// Trigger saving, to prevent data loss in forms; see issue 12257.
|
||||||
await this.save();
|
await this.downloadOrSave();
|
||||||
} catch {
|
} catch {
|
||||||
// Ignoring errors, to ensure that document closing won't break.
|
// 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.
|
// a message and change PdfjsChild.sys.mjs to take it into account.
|
||||||
const { classList } = this.appConfig.appContainer;
|
const { classList } = this.appConfig.appContainer;
|
||||||
classList.add("wait");
|
classList.add("wait");
|
||||||
await (this.pdfDocument?.annotationStorage.size > 0
|
|
||||||
? this.save()
|
const structuralChanges = this.pdfThumbnailViewer?.getStructuralChanges();
|
||||||
: this.download());
|
if (structuralChanges) {
|
||||||
|
await this.onSavePages({ data: structuralChanges });
|
||||||
|
} else {
|
||||||
|
await (this.pdfDocument?.annotationStorage.size > 0
|
||||||
|
? this.save()
|
||||||
|
: this.download());
|
||||||
|
}
|
||||||
classList.remove("wait");
|
classList.remove("wait");
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1862,6 +1868,13 @@ const PDFViewerApplication = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_hasChanges() {
|
||||||
|
return (
|
||||||
|
this.pdfDocument?.annotationStorage.size > 0 ||
|
||||||
|
this.pdfThumbnailViewer?.hasStructuralChanges()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -1872,15 +1885,11 @@ const PDFViewerApplication = {
|
|||||||
const { annotationStorage } = pdfDocument;
|
const { annotationStorage } = pdfDocument;
|
||||||
|
|
||||||
annotationStorage.onSetModified = () => {
|
annotationStorage.onSetModified = () => {
|
||||||
window.addEventListener("beforeunload", beforeUnload);
|
|
||||||
|
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||||
this._annotationStorageModified = true;
|
this._annotationStorageModified = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
annotationStorage.onResetModified = () => {
|
annotationStorage.onResetModified = () => {
|
||||||
window.removeEventListener("beforeunload", beforeUnload);
|
|
||||||
|
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||||
delete this._annotationStorageModified;
|
delete this._annotationStorageModified;
|
||||||
}
|
}
|
||||||
@ -2185,11 +2194,7 @@ const PDFViewerApplication = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
eventBus._on("pagesedited", this.onPagesEdited.bind(this), opts);
|
eventBus._on("pagesedited", this.onPagesEdited.bind(this), opts);
|
||||||
eventBus._on(
|
eventBus._on("saveextractedpages", this.onSavePages.bind(this), opts);
|
||||||
"savepageseditedpdf",
|
|
||||||
this.onSavePagesEditedPDF.bind(this),
|
|
||||||
opts
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
bindWindowEvents() {
|
bindWindowEvents() {
|
||||||
@ -2270,6 +2275,9 @@ const PDFViewerApplication = {
|
|||||||
},
|
},
|
||||||
{ signal }
|
{ signal }
|
||||||
);
|
);
|
||||||
|
window.addEventListener("beforeunload", onBeforeUnload.bind(this), {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) &&
|
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) &&
|
||||||
@ -2368,7 +2376,7 @@ const PDFViewerApplication = {
|
|||||||
this.pdfViewer.onPagesEdited(data);
|
this.pdfViewer.onPagesEdited(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
async onSavePagesEditedPDF({ data: extractParams }) {
|
async onSavePages({ data: extractParams }) {
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||||
return;
|
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) {
|
function onClick(evt) {
|
||||||
closeSecondaryToolbar.call(this, evt);
|
closeSecondaryToolbar.call(this, evt);
|
||||||
closeEditorUndoBar.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 };
|
export { PDFViewerApplication };
|
||||||
|
|||||||
@ -175,12 +175,7 @@ class PDFThumbnailViewer {
|
|||||||
|
|
||||||
this._manageMenu = new Menu(menu, button, [copy, cut, del, saveAs]);
|
this._manageMenu = new Menu(menu, button, [copy, cut, del, saveAs]);
|
||||||
this.#manageSaveAsButton = saveAs;
|
this.#manageSaveAsButton = saveAs;
|
||||||
saveAs.addEventListener("click", () => {
|
saveAs.addEventListener("click", this.#saveExtractedPages.bind(this));
|
||||||
this.eventBus.dispatch("savepageseditedpdf", {
|
|
||||||
source: this,
|
|
||||||
data: this.#pagesMapper.getPageMappingForSaving(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.#manageDeleteButton = del;
|
this.#manageDeleteButton = del;
|
||||||
del.addEventListener("click", this.#deletePages.bind(this));
|
del.addEventListener("click", this.#deletePages.bind(this));
|
||||||
this.#manageCopyButton = copy;
|
this.#manageCopyButton = copy;
|
||||||
@ -432,6 +427,14 @@ class PDFThumbnailViewer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasStructuralChanges() {
|
||||||
|
return this.#pagesMapper?.hasBeenAltered() || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStructuralChanges() {
|
||||||
|
return this.#pagesMapper?.getPageMappingForSaving() || null;
|
||||||
|
}
|
||||||
|
|
||||||
static #getScaleFactor(image) {
|
static #getScaleFactor(image) {
|
||||||
return (PDFThumbnailViewer.#draggingScaleFactor ||= parseFloat(
|
return (PDFThumbnailViewer.#draggingScaleFactor ||= parseFloat(
|
||||||
getComputedStyle(image).getPropertyValue("--thumbnail-dragging-scale")
|
getComputedStyle(image).getPropertyValue("--thumbnail-dragging-scale")
|
||||||
@ -617,6 +620,15 @@ class PDFThumbnailViewer {
|
|||||||
this.#selectedPages.clear();
|
this.#selectedPages.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#saveExtractedPages() {
|
||||||
|
this.eventBus.dispatch("saveextractedpages", {
|
||||||
|
source: this,
|
||||||
|
data: this.#pagesMapper.extractPages(this.#selectedPages),
|
||||||
|
});
|
||||||
|
this.#clearSelection();
|
||||||
|
this.#toggleMenuEntries(false);
|
||||||
|
}
|
||||||
|
|
||||||
#copyPages(clearSelection = true) {
|
#copyPages(clearSelection = true) {
|
||||||
const pageNumbersToCopy = (this.#copiedPageNumbers = Uint32Array.from(
|
const pageNumbersToCopy = (this.#copiedPageNumbers = Uint32Array.from(
|
||||||
this.#selectedPages
|
this.#selectedPages
|
||||||
@ -713,8 +725,8 @@ class PDFThumbnailViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#updateMenuEntries() {
|
#updateMenuEntries() {
|
||||||
this.#manageSaveAsButton.disabled = !this.#pagesMapper.hasBeenAltered();
|
this.#manageSaveAsButton.disabled =
|
||||||
this.#manageDeleteButton.disabled =
|
this.#manageDeleteButton.disabled =
|
||||||
this.#manageCopyButton.disabled =
|
this.#manageCopyButton.disabled =
|
||||||
this.#manageCutButton.disabled =
|
this.#manageCutButton.disabled =
|
||||||
!this.#selectedPages?.size;
|
!this.#selectedPages?.size;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user