Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/display/display_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1182,10 +1182,39 @@ class PagesMapper {
// Finally insert the moved pages.
pageNumberToId.set(mappedPagesToMove, adjustedTarget);

let hasChanged = false;
for (let i = 0, ii = pagesNumber; i < ii; i++) {
idToPageNumber[pageNumberToId[i] - 1] = i + 1;
const id = pageNumberToId[i];
hasChanged ||= id !== i + 1;
idToPageNumber[id - 1] = i + 1;
}
this.#updateListeners();

if (!hasChanged) {
// Reset.
this.pagesNumber = 0;
}
}

/**
* Checks if the page mappings have been altered from their initial state.
* @returns {boolean} True if the mappings have been altered, false otherwise.
*/
hasBeenAltered() {
return PagesMapper.#pageNumberToId !== null;
}

/**
* Gets the current page mapping suitable for saving.
* @returns {Object} An object containing the page indices.
*/
getPageMappingForSaving() {
// Saving is index-based.
return {
pageIndices: PagesMapper.#idToPageNumber
? PagesMapper.#idToPageNumber.map(x => x - 1)
: null,
};
}

getPrevPageNumber(pageNumber) {
Expand Down
62 changes: 62 additions & 0 deletions test/integration/reorganize_pages_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -568,4 +568,66 @@ describe("Reorganize Pages View", () => {
);
});
});

describe("Save a pdf", () => {
let pages;

beforeEach(async () => {
pages = await loadAndWait(
"page_with_number.pdf",
"#viewsManagerToggleButton",
"1",
null,
{ enableSplitMerge: true }
);
});

afterEach(async () => {
await closePages(pages);
});

it("should check that a save is triggered", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await waitForThumbnailVisible(page, 1);
await page.waitForSelector("#viewsManagerStatusActionButton", {
visible: true,
});
const rect1 = await getRect(page, getThumbnailSelector(1));
const rect2 = await getRect(page, getThumbnailSelector(2));

await dragAndDrop(
page,
getThumbnailSelector(1),
[[0, rect2.y - rect1.y + rect2.height / 2]],
10
);

const handleSaveAs = await createPromise(page, resolve => {
window.PDFViewerApplication.eventBus.on(
"savepageseditedpdf",
({ data }) => {
resolve(Array.from(data.pageIndices));
},
{
once: true,
}
);
});

await page.click("#viewsManagerStatusActionButton");
await page.waitForSelector("#viewsManagerStatusActionSaveAs", {
visible: true,
});
await page.click("#viewsManagerStatusActionSaveAs");
const pageIndices = await awaitPromise(handleSaveAs);
expect(pageIndices)
.withContext(`In ${browserName}`)
.toEqual([
1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
})
);
});
});
});
7 changes: 7 additions & 0 deletions test/integration/thumbnail_view_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ describe("PDF Thumbnail View", () => {
.withContext(`In ${browserName}`)
.toBe(true);

await kbFocusNext(page);
expect(
await isElementFocused(page, "#viewsManagerStatusActionButton")
)
.withContext(`In ${browserName}`)
.toBe(true);

await kbFocusNext(page);
expect(
await isElementFocused(
Expand Down
35 changes: 35 additions & 0 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ const PDFViewerApplication = {
abortSignal,
enableHWA,
enableSplitMerge: AppOptions.get("enableSplitMerge"),
manageMenu: appConfig.viewsManager.manageMenu,
});
renderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
}
Expand Down Expand Up @@ -2194,6 +2195,11 @@ const PDFViewerApplication = {
this.onBeforePagesEdited.bind(this),
opts
);
eventBus._on(
"savepageseditedpdf",
this.onSavePagesEditedPDF.bind(this),
opts
);
},

bindWindowEvents() {
Expand Down Expand Up @@ -2376,6 +2382,35 @@ const PDFViewerApplication = {
this.pdfViewer.onPagesEdited(data);
},

async onSavePagesEditedPDF({
data: { includePages, excludePages, pageIndices },
}) {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
return;
}
if (!this.pdfDocument) {
return;
}
const pageInfo = {
document: null, // For now, no merge.
includePages,
excludePages,
pageIndices,
};
const modifiedPdfBytes = await this.pdfDocument.extractPages([pageInfo]);
if (!modifiedPdfBytes) {
console.error(
"Something wrong happened when saving the edited PDF.\nPlease file a bug."
);
return;
}
this.downloadManager.download(
modifiedPdfBytes,
this._downloadUrl,
this._docFilename
);
},

_accumulateTicks(ticks, prop) {
// If the direction changed, reset the accumulated ticks.
if ((this[prop] > 0 && ticks < 0) || (this[prop] < 0 && ticks > 0)) {
Expand Down
34 changes: 30 additions & 4 deletions web/pdf_thumbnail_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
watchScroll,
} from "./ui_utils.js";
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
import { Menu } from "./menu.js";
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";

const SCROLL_OPTIONS = {
Expand Down Expand Up @@ -67,6 +68,8 @@ const SPACE_FOR_DRAG_MARKER_WHEN_NO_NEXT_ELEMENT = 15;
* rendering. The default value is `false`.
* @property {boolean} [enableSplitMerge] - Enables split and merge features.
* The default value is `false`.
* @property {Object} [manageMenu] - The menu elements to manage saving edited
* PDF.
*/

/**
Expand Down Expand Up @@ -109,6 +112,8 @@ class PDFThumbnailViewer {

#pagesMapper = PagesMapper.instance;

#manageSaveAsButton = null;

/**
* @param {PDFThumbnailViewerOptions} options
*/
Expand All @@ -123,6 +128,7 @@ class PDFThumbnailViewer {
abortSignal,
enableHWA,
enableSplitMerge,
manageMenu,
}) {
this.scrollableContainer = container.parentElement;
this.container = container;
Expand All @@ -135,6 +141,20 @@ class PDFThumbnailViewer {
this.enableHWA = enableHWA || false;
this.#enableSplitMerge = enableSplitMerge || false;

if (this.#enableSplitMerge && manageMenu) {
const { button, menu, copy, cut, delete: del, saveAs } = manageMenu;
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(),
});
});
} else {
manageMenu.button.hidden = true;
}

this.scroll = watchScroll(
this.scrollableContainer,
this.#scrollUpdated.bind(this),
Expand Down Expand Up @@ -519,10 +539,16 @@ class PDFThumbnailViewer {
selectedPages.clear();
this.#pageNumberToRemove = NaN;

this.eventBus.dispatch("pagesedited", {
source: this,
pagesMapper,
});
const isIdentity = (this.#manageSaveAsButton.disabled =
!this.#pagesMapper.hasBeenAltered());
if (!isIdentity) {
this.eventBus.dispatch("pagesedited", {
source: this,
pagesMapper,
index: newIndex,
pagesToMove,
});
}

const newCurrentPageNumber = pagesMapper.getPageNumber(newCurrentPageId);
setTimeout(() => {
Expand Down
10 changes: 5 additions & 5 deletions web/viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
</button>
</div>
<div id="viewsManagerStatus">
<div id="viewsManagerStatusAction" class="hidden">
<div id="viewsManagerStatusAction">
<span
id="viewsManagerStatusActionLabel"
class="viewsManagerStatusLabel"
Expand All @@ -207,22 +207,22 @@
</button>
<menu id="viewsManagerStatusActionOptions" class="popupMenu">
<li>
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0">
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
<span data-l10n-id="pdfjs-views-manager-pages-status-copy-button-label"></span>
</button>
</li>
<li>
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="0">
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
<span data-l10n-id="pdfjs-views-manager-pages-status-cut-button-label"></span>
</button>
</li>
<li>
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="0">
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
<span data-l10n-id="pdfjs-views-manager-pages-status-delete-button-label"></span>
</button>
</li>
<li>
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="0">
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
<span data-l10n-id="pdfjs-views-manager-pages-status-save-as-button-label"></span>
</button>
</li>
Expand Down
8 changes: 8 additions & 0 deletions web/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ function getViewerConfiguration() {
viewsManagerHeaderLabel: document.getElementById(
"viewsManagerHeaderLabel"
),
manageMenu: {
button: document.getElementById("viewsManagerStatusActionButton"),
menu: document.getElementById("viewsManagerStatusActionOptions"),
copy: document.getElementById("viewsManagerStatusActionCopy"),
cut: document.getElementById("viewsManagerStatusActionCut"),
delete: document.getElementById("viewsManagerStatusActionDelete"),
saveAs: document.getElementById("viewsManagerStatusActionSaveAs"),
},
},
findBar: {
bar: document.getElementById("findbar"),
Expand Down