Merge pull request #14112 from Snuffleupagus/ScrollMode-PAGE

Add a new Page scrolling mode (issue 2638, 8952, 10907)
This commit is contained in:
Tim van der Meij 2021-10-15 21:40:51 +02:00 committed by GitHub
commit e504e81cda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 303 additions and 270 deletions

View File

@ -61,6 +61,8 @@ cursor_text_select_tool_label=Text Selection Tool
cursor_hand_tool.title=Enable Hand Tool cursor_hand_tool.title=Enable Hand Tool
cursor_hand_tool_label=Hand Tool cursor_hand_tool_label=Hand Tool
scroll_page.title=Use Page Scrolling
scroll_page_label=Page Scrolling
scroll_vertical.title=Use Vertical Scrolling scroll_vertical.title=Use Vertical Scrolling
scroll_vertical_label=Vertical Scrolling scroll_vertical_label=Vertical Scrolling
scroll_horizontal.title=Use Horizontal Scrolling scroll_horizontal.title=Use Horizontal Scrolling

View File

@ -61,6 +61,8 @@ cursor_text_select_tool_label=Textmarkeringsverktyg
cursor_hand_tool.title=Aktivera handverktyg cursor_hand_tool.title=Aktivera handverktyg
cursor_hand_tool_label=Handverktyg cursor_hand_tool_label=Handverktyg
scroll_page.title=Använd sidrullning
scroll_page_label=Sidrullning
scroll_vertical.title=Använd vertikal rullning scroll_vertical.title=Använd vertikal rullning
scroll_vertical_label=Vertikal rullning scroll_vertical_label=Vertikal rullning
scroll_horizontal.title=Använd horisontell rullning scroll_horizontal.title=Använd horisontell rullning

View File

@ -16,7 +16,7 @@
import { import {
animationStarted, animationStarted,
apiPageLayoutToSpreadMode, apiPageLayoutToViewerModes,
apiPageModeToSidebarView, apiPageModeToSidebarView,
AutomationEventBus, AutomationEventBus,
AutoPrintRegExp, AutoPrintRegExp,
@ -1300,8 +1300,16 @@ const PDFViewerApplication = {
if (pageMode && sidebarView === SidebarView.UNKNOWN) { if (pageMode && sidebarView === SidebarView.UNKNOWN) {
sidebarView = apiPageModeToSidebarView(pageMode); sidebarView = apiPageModeToSidebarView(pageMode);
} }
if (pageLayout && spreadMode === SpreadMode.UNKNOWN) { if (
spreadMode = apiPageLayoutToSpreadMode(pageLayout); pageLayout &&
scrollMode === ScrollMode.UNKNOWN &&
spreadMode === SpreadMode.UNKNOWN
) {
const modes = apiPageLayoutToViewerModes(pageLayout);
// TODO: Try to improve page-switching when using the mouse-wheel
// and/or arrow-keys before allowing the document to control this.
// scrollMode = modes.scrollMode;
spreadMode = modes.spreadMode;
} }
this.setInitialView(hash, { this.setInitialView(hash, {

View File

@ -445,14 +445,6 @@ class BaseViewer {
return this.pdfDocument ? this._pagesCapability.promise : null; return this.pdfDocument ? this._pagesCapability.promise : null;
} }
/**
* @private
*/
get _viewerElement() {
// In most viewers, e.g. `PDFViewer`, this should return `this.viewer`.
throw new Error("Not implemented: _viewerElement");
}
/** /**
* @private * @private
*/ */
@ -538,6 +530,10 @@ class BaseViewer {
this._firstPageCapability.resolve(firstPdfPage); this._firstPageCapability.resolve(firstPdfPage);
this._optionalContentConfigPromise = optionalContentConfigPromise; this._optionalContentConfigPromise = optionalContentConfigPromise;
const viewerElement =
this._scrollMode === ScrollMode.PAGE
? this._scrollModePageState.shadowViewer
: this.viewer;
const scale = this.currentScale; const scale = this.currentScale;
const viewport = firstPdfPage.getViewport({ const viewport = firstPdfPage.getViewport({
scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS, scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS,
@ -552,7 +548,7 @@ class BaseViewer {
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) { for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
const pageView = new PDFPageView({ const pageView = new PDFPageView({
container: this._viewerElement, container: viewerElement,
eventBus: this.eventBus, eventBus: this.eventBus,
id: pageNum, id: pageNum,
scale, scale,
@ -582,7 +578,12 @@ class BaseViewer {
firstPageView.setPdfPage(firstPdfPage); firstPageView.setPdfPage(firstPdfPage);
this.linkService.cachePageRef(1, firstPdfPage.ref); this.linkService.cachePageRef(1, firstPdfPage.ref);
} }
if (this._spreadMode !== SpreadMode.NONE) {
if (this._scrollMode === ScrollMode.PAGE) {
// Since the pages are placed in a `DocumentFragment`, ensure that
// the current page becomes visible upon loading of the document.
this._ensurePageViewVisible();
} else if (this._spreadMode !== SpreadMode.NONE) {
this._updateSpreadMode(); this._updateSpreadMode();
} }
@ -684,8 +685,16 @@ class BaseViewer {
this._onePageRenderedCapability = createPromiseCapability(); this._onePageRenderedCapability = createPromiseCapability();
this._pagesCapability = createPromiseCapability(); this._pagesCapability = createPromiseCapability();
this._scrollMode = ScrollMode.VERTICAL; this._scrollMode = ScrollMode.VERTICAL;
this._previousScrollMode = ScrollMode.UNKNOWN;
this._spreadMode = SpreadMode.NONE; this._spreadMode = SpreadMode.NONE;
this._scrollModePageState = {
shadowViewer: document.createDocumentFragment(),
previousPageNumber: 1,
scrollDown: true,
pages: [],
};
if (this._onBeforeDraw) { if (this._onBeforeDraw) {
this.eventBus._off("pagerender", this._onBeforeDraw); this.eventBus._off("pagerender", this._onBeforeDraw);
this._onBeforeDraw = null; this._onBeforeDraw = null;
@ -700,6 +709,71 @@ class BaseViewer {
this._updateScrollMode(); this._updateScrollMode();
} }
_ensurePageViewVisible() {
if (this._scrollMode !== ScrollMode.PAGE) {
throw new Error("_ensurePageViewVisible: Invalid scrollMode value.");
}
const pageNumber = this._currentPageNumber,
state = this._scrollModePageState,
viewer = this.viewer;
// Remove the currently active pages, if any, from the viewer.
if (viewer.hasChildNodes()) {
// Temporarily remove all the pages from the DOM.
viewer.textContent = "";
for (const pageView of this._pages) {
state.shadowViewer.appendChild(pageView.div);
}
}
state.pages.length = 0;
if (this._spreadMode === SpreadMode.NONE) {
// Finally, append the new page to the viewer.
const pageView = this._pages[pageNumber - 1];
viewer.appendChild(pageView.div);
state.pages.push(pageView);
} else {
const pageIndexSet = new Set(),
parity = this._spreadMode - 1;
// Determine the pageIndices in the new spread.
if (pageNumber % 2 !== parity) {
// Left-hand side page.
pageIndexSet.add(pageNumber - 1);
pageIndexSet.add(pageNumber);
} else {
// Right-hand side page.
pageIndexSet.add(pageNumber - 2);
pageIndexSet.add(pageNumber - 1);
}
// Finally, append the new pages to the viewer and apply the spreadMode.
let spread = null;
for (let i = 0, ii = this._pages.length; i < ii; ++i) {
if (!pageIndexSet.has(i)) {
continue;
}
if (spread === null) {
spread = document.createElement("div");
spread.className = "spread";
viewer.appendChild(spread);
} else if (i % 2 === parity) {
spread = spread.cloneNode(false);
viewer.appendChild(spread);
}
const pageView = this._pages[i];
spread.appendChild(pageView.div);
state.pages.push(pageView);
}
}
state.scrollDown = pageNumber >= state.previousPageNumber;
state.previousPageNumber = pageNumber;
}
_scrollUpdate() { _scrollUpdate() {
if (this.pagesCount === 0) { if (this.pagesCount === 0) {
return; return;
@ -708,6 +782,29 @@ class BaseViewer {
} }
_scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null }) { _scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null }) {
if (this._scrollMode === ScrollMode.PAGE) {
if (pageNumber) {
// Ensure that `this._currentPageNumber` is correct.
this._setCurrentPageNumber(pageNumber);
}
this._ensurePageViewVisible();
// Ensure that rendering always occurs, to avoid showing a blank page,
// even if the current position doesn't change when the page is scrolled.
this.update();
}
if (!pageSpot && !this.isInPresentationMode) {
const left = pageDiv.offsetLeft + pageDiv.clientLeft;
const right = left + pageDiv.clientWidth;
const { scrollLeft, clientWidth } = this.container;
if (
this._scrollMode === ScrollMode.HORIZONTAL ||
left < scrollLeft ||
right > scrollLeft + clientWidth
) {
pageSpot = { left: 0, top: 0 };
}
}
scrollIntoView(pageDiv, pageSpot); scrollIntoView(pageDiv, pageSpot);
} }
@ -772,8 +869,7 @@ class BaseViewer {
get _pageWidthScaleFactor() { get _pageWidthScaleFactor() {
if ( if (
this._spreadMode !== SpreadMode.NONE && this._spreadMode !== SpreadMode.NONE &&
this._scrollMode !== ScrollMode.HORIZONTAL && this._scrollMode !== ScrollMode.HORIZONTAL
!this.isInPresentationMode
) { ) {
return 2; return 2;
} }
@ -794,7 +890,7 @@ class BaseViewer {
let hPadding = noPadding ? 0 : SCROLLBAR_PADDING; let hPadding = noPadding ? 0 : SCROLLBAR_PADDING;
let vPadding = noPadding ? 0 : VERTICAL_PADDING; let vPadding = noPadding ? 0 : VERTICAL_PADDING;
if (!noPadding && this._isScrollModeHorizontal) { if (!noPadding && this._scrollMode === ScrollMode.HORIZONTAL) {
[hPadding, vPadding] = [vPadding, hPadding]; // Swap the padding values. [hPadding, vPadding] = [vPadding, hPadding]; // Swap the padding values.
} }
const pageWidthScale = const pageWidthScale =
@ -1047,10 +1143,6 @@ class BaseViewer {
}; };
} }
_updateHelper(visiblePages) {
throw new Error("Not implemented: _updateHelper");
}
update() { update() {
const visible = this._getVisiblePages(); const visible = this._getVisiblePages();
const visiblePages = visible.views, const visiblePages = visible.views,
@ -1064,7 +1156,28 @@ class BaseViewer {
this.renderingQueue.renderHighestPriority(visible); this.renderingQueue.renderHighestPriority(visible);
this._updateHelper(visiblePages); // Run any class-specific update code. if (!this.isInPresentationMode) {
const isSimpleLayout =
this._spreadMode === SpreadMode.NONE &&
(this._scrollMode === ScrollMode.PAGE ||
this._scrollMode === ScrollMode.VERTICAL);
let currentId = this._currentPageNumber;
let stillFullyVisible = false;
for (const page of visiblePages) {
if (page.percent < 100) {
break;
}
if (page.id === currentId && isSimpleLayout) {
stillFullyVisible = true;
break;
}
}
if (!stillFullyVisible) {
currentId = visiblePages[0].id;
}
this._setCurrentPageNumber(currentId);
}
this._updateLocation(visible.first); this._updateLocation(visible.first);
this.eventBus.dispatch("updateviewarea", { this.eventBus.dispatch("updateviewarea", {
@ -1081,14 +1194,6 @@ class BaseViewer {
this.container.focus(); this.container.focus();
} }
get _isScrollModeHorizontal() {
// Used to ensure that pre-rendering of the next/previous page works
// correctly, since Scroll/Spread modes are ignored in Presentation Mode.
return this.isInPresentationMode
? false
: this._scrollMode === ScrollMode.HORIZONTAL;
}
get _isContainerRtl() { get _isContainerRtl() {
return getComputedStyle(this.container).direction === "rtl"; return getComputedStyle(this.container).direction === "rtl";
} }
@ -1115,9 +1220,8 @@ class BaseViewer {
/** /**
* Helper method for `this._getVisiblePages`. Should only ever be used when * Helper method for `this._getVisiblePages`. Should only ever be used when
* the viewer can only display a single page at a time, for example in: * the viewer can only display a single page at a time, for example:
* - `PDFSinglePageViewer`. * - When PresentationMode is active.
* - `PDFViewer` with Presentation Mode active.
*/ */
_getCurrentVisiblePage() { _getCurrentVisiblePage() {
if (!this.pagesCount) { if (!this.pagesCount) {
@ -1138,12 +1242,24 @@ class BaseViewer {
} }
_getVisiblePages() { _getVisiblePages() {
if (this.isInPresentationMode) {
// The algorithm in `getVisibleElements` doesn't work in all browsers and
// configurations (e.g. Chrome) when PresentationMode is active.
return this._getCurrentVisiblePage();
}
const views =
this._scrollMode === ScrollMode.PAGE
? this._scrollModePageState.pages
: this._pages,
horizontal = this._scrollMode === ScrollMode.HORIZONTAL,
rtl = horizontal && this._isContainerRtl;
return getVisibleElements({ return getVisibleElements({
scrollEl: this.container, scrollEl: this.container,
views: this._pages, views,
sortByVisibility: true, sortByVisibility: true,
horizontal: this._isScrollModeHorizontal, horizontal,
rtl: this._isScrollModeHorizontal && this._isContainerRtl, rtl,
}); });
} }
@ -1245,15 +1361,25 @@ class BaseViewer {
return promise; return promise;
} }
/**
* @private
*/
get _scrollAhead() {
switch (this._scrollMode) {
case ScrollMode.PAGE:
return this._scrollModePageState.scrollDown;
case ScrollMode.HORIZONTAL:
return this.scroll.right;
}
return this.scroll.down;
}
forceRendering(currentlyVisiblePages) { forceRendering(currentlyVisiblePages) {
const visiblePages = currentlyVisiblePages || this._getVisiblePages(); const visiblePages = currentlyVisiblePages || this._getVisiblePages();
const scrollAhead = this._isScrollModeHorizontal const scrollAhead = this._scrollAhead;
? this.scroll.right
: this.scroll.down;
const preRenderExtra = const preRenderExtra =
this._scrollMode === ScrollMode.VERTICAL &&
this._spreadMode !== SpreadMode.NONE && this._spreadMode !== SpreadMode.NONE &&
!this.isInPresentationMode; this._scrollMode !== ScrollMode.HORIZONTAL;
const pageView = this.renderingQueue.getHighestPriority( const pageView = this.renderingQueue.getHighestPriority(
visiblePages, visiblePages,
@ -1492,6 +1618,8 @@ class BaseViewer {
if (!isValidScrollMode(mode)) { if (!isValidScrollMode(mode)) {
throw new Error(`Invalid scroll mode: ${mode}`); throw new Error(`Invalid scroll mode: ${mode}`);
} }
this._previousScrollMode = this._scrollMode;
this._scrollMode = mode; this._scrollMode = mode;
this.eventBus.dispatch("scrollmodechanged", { source: this, mode }); this.eventBus.dispatch("scrollmodechanged", { source: this, mode });
@ -1511,6 +1639,14 @@ class BaseViewer {
if (!this.pdfDocument || !pageNumber) { if (!this.pdfDocument || !pageNumber) {
return; return;
} }
if (scrollMode === ScrollMode.PAGE) {
this._ensurePageViewVisible();
} else if (this._previousScrollMode === ScrollMode.PAGE) {
// Ensure that the current spreadMode is still applied correctly when
// the *previous* scrollMode was `ScrollMode.PAGE`.
this._updateSpreadMode();
}
// Non-numeric scale values can be sensitive to the scroll orientation. // Non-numeric scale values can be sensitive to the scroll orientation.
// Call this before re-scrolling to the current page, to ensure that any // Call this before re-scrolling to the current page, to ensure that any
// changes in scale don't move the current page. // changes in scale don't move the current page.
@ -1552,17 +1688,21 @@ class BaseViewer {
} }
const viewer = this.viewer, const viewer = this.viewer,
pages = this._pages; pages = this._pages;
if (this._scrollMode === ScrollMode.PAGE) {
this._ensurePageViewVisible();
} else {
// Temporarily remove all the pages from the DOM. // Temporarily remove all the pages from the DOM.
viewer.textContent = ""; viewer.textContent = "";
if (this._spreadMode === SpreadMode.NONE) { if (this._spreadMode === SpreadMode.NONE) {
for (let i = 0, iMax = pages.length; i < iMax; ++i) { for (let i = 0, ii = pages.length; i < ii; ++i) {
viewer.appendChild(pages[i].div); viewer.appendChild(pages[i].div);
} }
} else { } else {
const parity = this._spreadMode - 1; const parity = this._spreadMode - 1;
let spread = null; let spread = null;
for (let i = 0, iMax = pages.length; i < iMax; ++i) { for (let i = 0, ii = pages.length; i < ii; ++i) {
if (spread === null) { if (spread === null) {
spread = document.createElement("div"); spread = document.createElement("div");
spread.className = "spread"; spread.className = "spread";
@ -1574,10 +1714,14 @@ class BaseViewer {
spread.appendChild(pages[i].div); spread.appendChild(pages[i].div);
} }
} }
}
if (!pageNumber) { if (!pageNumber) {
return; return;
} }
// Non-numeric scale values can be sensitive to the scroll orientation.
// Call this before re-scrolling to the current page, to ensure that any
// changes in scale don't move the current page.
if (this._currentScaleValue && isNaN(this._currentScaleValue)) { if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
this._setScale(this._currentScaleValue, true); this._setScale(this._currentScaleValue, true);
} }
@ -1589,9 +1733,6 @@ class BaseViewer {
* @private * @private
*/ */
_getPageAdvance(currentPageNumber, previous = false) { _getPageAdvance(currentPageNumber, previous = false) {
if (this.isInPresentationMode) {
return 1;
}
switch (this._scrollMode) { switch (this._scrollMode) {
case ScrollMode.WRAPPED: { case ScrollMode.WRAPPED: {
const { views } = this._getVisiblePages(), const { views } = this._getVisiblePages(),
@ -1655,6 +1796,7 @@ class BaseViewer {
case ScrollMode.HORIZONTAL: { case ScrollMode.HORIZONTAL: {
break; break;
} }
case ScrollMode.PAGE:
case ScrollMode.VERTICAL: { case ScrollMode.VERTICAL: {
if (this._spreadMode === SpreadMode.NONE) { if (this._spreadMode === SpreadMode.NONE) {
break; // Normal vertical scrolling. break; // Normal vertical scrolling.

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M9.5 4c1 0 1.5.5 1.5 1.5v5c0 1-.5 1.5-1.5 1.5h-3c-1 0-1.5-.5-1.5-1.5v-5C5 4.5 5.5 4 6.5 4z"/></svg>

After

Width:  |  Height:  |  Size: 171 B

View File

@ -13,7 +13,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { normalizeWheelEventDelta, PresentationModeState } from "./ui_utils.js"; import {
normalizeWheelEventDelta,
PresentationModeState,
ScrollMode,
SpreadMode,
} from "./ui_utils.js";
const DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1500; // in ms const DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1500; // in ms
const DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms const DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
@ -84,8 +89,10 @@ class PDFPresentationMode {
} }
this.args = { this.args = {
page: this.pdfViewer.currentPageNumber, pageNumber: this.pdfViewer.currentPageNumber,
previousScale: this.pdfViewer.currentScaleValue, scaleValue: this.pdfViewer.currentScaleValue,
scrollMode: this.pdfViewer.scrollMode,
spreadMode: this.pdfViewer.spreadMode,
}; };
return true; return true;
@ -203,7 +210,9 @@ class PDFPresentationMode {
// Ensure that the correct page is scrolled into view when entering // Ensure that the correct page is scrolled into view when entering
// Presentation Mode, by waiting until fullscreen mode in enabled. // Presentation Mode, by waiting until fullscreen mode in enabled.
setTimeout(() => { setTimeout(() => {
this.pdfViewer.currentPageNumber = this.args.page; this.pdfViewer.scrollMode = ScrollMode.PAGE;
this.pdfViewer.spreadMode = SpreadMode.NONE;
this.pdfViewer.currentPageNumber = this.args.pageNumber;
this.pdfViewer.currentScaleValue = "page-fit"; this.pdfViewer.currentScaleValue = "page-fit";
}, 0); }, 0);
@ -221,7 +230,7 @@ class PDFPresentationMode {
* @private * @private
*/ */
_exit() { _exit() {
const page = this.pdfViewer.currentPageNumber; const pageNumber = this.pdfViewer.currentPageNumber;
this.container.classList.remove(ACTIVE_SELECTOR); this.container.classList.remove(ACTIVE_SELECTOR);
// Ensure that the correct page is scrolled into view when exiting // Ensure that the correct page is scrolled into view when exiting
@ -231,8 +240,10 @@ class PDFPresentationMode {
this._removeFullscreenChangeListeners(); this._removeFullscreenChangeListeners();
this._notifyStateChange(); this._notifyStateChange();
this.pdfViewer.currentScaleValue = this.args.previousScale; this.pdfViewer.scrollMode = this.args.scrollMode;
this.pdfViewer.currentPageNumber = page; this.pdfViewer.spreadMode = this.args.spreadMode;
this.pdfViewer.currentScaleValue = this.args.scaleValue;
this.pdfViewer.currentPageNumber = pageNumber;
this.args = null; this.args = null;
}, 0); }, 0);

View File

@ -14,7 +14,7 @@
*/ */
import { createPromiseCapability, shadow } from "pdfjs-lib"; import { createPromiseCapability, shadow } from "pdfjs-lib";
import { apiPageLayoutToSpreadMode } from "./ui_utils.js"; import { apiPageLayoutToViewerModes } from "./ui_utils.js";
import { RenderingStates } from "./pdf_rendering_queue.js"; import { RenderingStates } from "./pdf_rendering_queue.js";
/** /**
@ -287,7 +287,11 @@ class PDFScriptingManager {
console.error(value); console.error(value);
break; break;
case "layout": case "layout":
this._pdfViewer.spreadMode = apiPageLayoutToSpreadMode(value); if (isInPresentationMode) {
return;
}
const modes = apiPageLayoutToViewerModes(value);
this._pdfViewer.spreadMode = modes.spreadMode;
break; break;
case "page-num": case "page-num":
this._pdfViewer.currentPageNumber = value + 1; this._pdfViewer.currentPageNumber = value + 1;

View File

@ -1,130 +0,0 @@
/* Copyright 2017 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseViewer } from "./base_viewer.js";
import { shadow } from "pdfjs-lib";
class PDFSinglePageViewer extends BaseViewer {
constructor(options) {
super(options);
this.eventBus._on("pagesinit", evt => {
// Since the pages are placed in a `DocumentFragment`, make sure that
// the current page becomes visible upon loading of the document.
this._ensurePageViewVisible();
});
}
get _viewerElement() {
// Since we only want to display *one* page at a time when using the
// `PDFSinglePageViewer`, we cannot append them to the `viewer` DOM element.
// Instead, they are placed in a `DocumentFragment`, and only the current
// page is displayed in the viewer (refer to `this._ensurePageViewVisible`).
return shadow(this, "_viewerElement", this._shadowViewer);
}
get _pageWidthScaleFactor() {
return 1;
}
_resetView() {
super._resetView();
this._previousPageNumber = 1;
this._shadowViewer = document.createDocumentFragment();
this._updateScrollDown = null;
}
_ensurePageViewVisible() {
const pageView = this._pages[this._currentPageNumber - 1];
const previousPageView = this._pages[this._previousPageNumber - 1];
const viewerNodes = this.viewer.childNodes;
switch (viewerNodes.length) {
case 0: // Should *only* occur on initial loading.
this.viewer.appendChild(pageView.div);
break;
case 1: // The normal page-switching case.
if (viewerNodes[0] !== previousPageView.div) {
throw new Error(
"_ensurePageViewVisible: Unexpected previously visible page."
);
}
if (pageView === previousPageView) {
break; // The correct page is already visible.
}
// Switch visible pages, and reset the viewerContainer scroll position.
this._shadowViewer.appendChild(previousPageView.div);
this.viewer.appendChild(pageView.div);
this.container.scrollTop = 0;
break;
default:
throw new Error(
"_ensurePageViewVisible: Only one page should be visible at a time."
);
}
this._previousPageNumber = this._currentPageNumber;
}
_scrollUpdate() {
if (this._updateScrollDown) {
this._updateScrollDown();
}
super._scrollUpdate();
}
_scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null }) {
if (pageNumber) {
// Ensure that `this._currentPageNumber` is correct.
this._setCurrentPageNumber(pageNumber);
}
const scrolledDown = this._currentPageNumber >= this._previousPageNumber;
this._ensurePageViewVisible();
// Ensure that rendering always occurs, to avoid showing a blank page,
// even if the current position doesn't change when the page is scrolled.
this.update();
super._scrollIntoView({ pageDiv, pageSpot, pageNumber });
// Since scrolling is tracked using `requestAnimationFrame`, update the
// scroll direction during the next `this._scrollUpdate` invocation.
this._updateScrollDown = () => {
this.scroll.down = scrolledDown;
this._updateScrollDown = null;
};
}
_getVisiblePages() {
return this._getCurrentVisiblePage();
}
_updateHelper(visiblePages) {}
get _isScrollModeHorizontal() {
// The Scroll/Spread modes are never used in `PDFSinglePageViewer`.
return shadow(this, "_isScrollModeHorizontal", false);
}
_updateScrollMode() {}
_updateSpreadMode() {}
_getPageAdvance() {
return 1;
}
}
export { PDFSinglePageViewer };

View File

@ -31,6 +31,7 @@ import {
} from "./xfa_layer_builder.js"; } from "./xfa_layer_builder.js";
import { EventBus, ProgressBar } from "./ui_utils.js"; import { EventBus, ProgressBar } from "./ui_utils.js";
import { PDFLinkService, SimpleLinkService } from "./pdf_link_service.js"; import { PDFLinkService, SimpleLinkService } from "./pdf_link_service.js";
import { PDFSinglePageViewer, PDFViewer } from "./pdf_viewer.js";
import { DownloadManager } from "./download_manager.js"; import { DownloadManager } from "./download_manager.js";
import { GenericL10n } from "./genericl10n.js"; import { GenericL10n } from "./genericl10n.js";
import { NullL10n } from "./l10n_utils.js"; import { NullL10n } from "./l10n_utils.js";
@ -38,8 +39,6 @@ import { PDFFindController } from "./pdf_find_controller.js";
import { PDFHistory } from "./pdf_history.js"; import { PDFHistory } from "./pdf_history.js";
import { PDFPageView } from "./pdf_page_view.js"; import { PDFPageView } from "./pdf_page_view.js";
import { PDFScriptingManager } from "./pdf_scripting_manager.js"; import { PDFScriptingManager } from "./pdf_scripting_manager.js";
import { PDFSinglePageViewer } from "./pdf_single_page_viewer.js";
import { PDFViewer } from "./pdf_viewer.js";
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION"); const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION");

View File

@ -15,63 +15,25 @@
import { ScrollMode, SpreadMode } from "./ui_utils.js"; import { ScrollMode, SpreadMode } from "./ui_utils.js";
import { BaseViewer } from "./base_viewer.js"; import { BaseViewer } from "./base_viewer.js";
import { shadow } from "pdfjs-lib";
class PDFViewer extends BaseViewer { class PDFViewer extends BaseViewer {}
get _viewerElement() {
return shadow(this, "_viewerElement", this.viewer); class PDFSinglePageViewer extends BaseViewer {
_resetView() {
super._resetView();
this._scrollMode = ScrollMode.PAGE;
this._spreadMode = SpreadMode.NONE;
} }
_scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null }) { // eslint-disable-next-line accessor-pairs
if (!pageSpot && !this.isInPresentationMode) { set scrollMode(mode) {}
const left = pageDiv.offsetLeft + pageDiv.clientLeft;
const right = left + pageDiv.clientWidth; _updateScrollMode() {}
const { scrollLeft, clientWidth } = this.container;
if ( // eslint-disable-next-line accessor-pairs
this._isScrollModeHorizontal || set spreadMode(mode) {}
left < scrollLeft ||
right > scrollLeft + clientWidth _updateSpreadMode() {}
) {
pageSpot = { left: 0, top: 0 };
}
}
super._scrollIntoView({ pageDiv, pageSpot, pageNumber });
} }
_getVisiblePages() { export { PDFSinglePageViewer, PDFViewer };
if (this.isInPresentationMode) {
// The algorithm in `getVisibleElements` doesn't work in all browsers and
// configurations (e.g. Chrome) when Presentation Mode is active.
return this._getCurrentVisiblePage();
}
return super._getVisiblePages();
}
_updateHelper(visiblePages) {
if (this.isInPresentationMode) {
return;
}
let currentId = this._currentPageNumber;
let stillFullyVisible = false;
for (const page of visiblePages) {
if (page.percent < 100) {
break;
}
if (
page.id === currentId &&
this._scrollMode === ScrollMode.VERTICAL &&
this._spreadMode === SpreadMode.NONE
) {
stillFullyVisible = true;
break;
}
}
if (!stillFullyVisible) {
currentId = visiblePages[0].id;
}
this._setCurrentPageNumber(currentId);
}
}
export { PDFViewer };

View File

@ -15,7 +15,7 @@
import { SCROLLBAR_PADDING, ScrollMode, SpreadMode } from "./ui_utils.js"; import { SCROLLBAR_PADDING, ScrollMode, SpreadMode } from "./ui_utils.js";
import { CursorTool } from "./pdf_cursor_tools.js"; import { CursorTool } from "./pdf_cursor_tools.js";
import { PDFSinglePageViewer } from "./pdf_single_page_viewer.js"; import { PDFSinglePageViewer } from "./pdf_viewer.js";
/** /**
* @typedef {Object} SecondaryToolbarOptions * @typedef {Object} SecondaryToolbarOptions
@ -93,6 +93,12 @@ class SecondaryToolbar {
eventDetails: { tool: CursorTool.HAND }, eventDetails: { tool: CursorTool.HAND },
close: true, close: true,
}, },
{
element: options.scrollPageButton,
eventName: "switchscrollmode",
eventDetails: { mode: ScrollMode.PAGE },
close: true,
},
{ {
element: options.scrollVerticalButton, element: options.scrollVerticalButton,
eventName: "switchscrollmode", eventName: "switchscrollmode",
@ -247,6 +253,10 @@ class SecondaryToolbar {
_bindScrollModeListener(buttons) { _bindScrollModeListener(buttons) {
function scrollModeChanged({ mode }) { function scrollModeChanged({ mode }) {
buttons.scrollPageButton.classList.toggle(
"toggled",
mode === ScrollMode.PAGE
);
buttons.scrollVerticalButton.classList.toggle( buttons.scrollVerticalButton.classList.toggle(
"toggled", "toggled",
mode === ScrollMode.VERTICAL mode === ScrollMode.VERTICAL

View File

@ -57,6 +57,7 @@ const ScrollMode = {
VERTICAL: 0, // Default value. VERTICAL: 0, // Default value.
HORIZONTAL: 1, HORIZONTAL: 1,
WRAPPED: 2, WRAPPED: 2,
PAGE: 3,
}; };
const SpreadMode = { const SpreadMode = {
@ -952,21 +953,32 @@ function getActiveOrFocusedElement() {
* necessary Scroll/Spread modes (since SinglePage, TwoPageLeft, * necessary Scroll/Spread modes (since SinglePage, TwoPageLeft,
* and TwoPageRight all suggests using non-continuous scrolling). * and TwoPageRight all suggests using non-continuous scrolling).
* @param {string} mode - The API PageLayout value. * @param {string} mode - The API PageLayout value.
* @returns {number} A value from {SpreadMode}. * @returns {Object}
*/ */
function apiPageLayoutToSpreadMode(layout) { function apiPageLayoutToViewerModes(layout) {
let scrollMode = ScrollMode.VERTICAL,
spreadMode = SpreadMode.NONE;
switch (layout) { switch (layout) {
case "SinglePage": case "SinglePage":
scrollMode = ScrollMode.PAGE;
break;
case "OneColumn": case "OneColumn":
return SpreadMode.NONE; break;
case "TwoColumnLeft":
case "TwoPageLeft": case "TwoPageLeft":
return SpreadMode.ODD; scrollMode = ScrollMode.PAGE;
case "TwoColumnRight": /* falls through */
case "TwoColumnLeft":
spreadMode = SpreadMode.ODD;
break;
case "TwoPageRight": case "TwoPageRight":
return SpreadMode.EVEN; scrollMode = ScrollMode.PAGE;
/* falls through */
case "TwoColumnRight":
spreadMode = SpreadMode.EVEN;
break;
} }
return SpreadMode.NONE; // Default value. return { scrollMode, spreadMode };
} }
/** /**
@ -995,7 +1007,7 @@ function apiPageModeToSidebarView(mode) {
export { export {
animationStarted, animationStarted,
apiPageLayoutToSpreadMode, apiPageLayoutToViewerModes,
apiPageModeToSidebarView, apiPageModeToSidebarView,
approximateFraction, approximateFraction,
AutomationEventBus, AutomationEventBus,

View File

@ -94,6 +94,7 @@
--secondaryToolbarButton-rotateCw-icon: url(images/secondaryToolbarButton-rotateCw.svg); --secondaryToolbarButton-rotateCw-icon: url(images/secondaryToolbarButton-rotateCw.svg);
--secondaryToolbarButton-selectTool-icon: url(images/secondaryToolbarButton-selectTool.svg); --secondaryToolbarButton-selectTool-icon: url(images/secondaryToolbarButton-selectTool.svg);
--secondaryToolbarButton-handTool-icon: url(images/secondaryToolbarButton-handTool.svg); --secondaryToolbarButton-handTool-icon: url(images/secondaryToolbarButton-handTool.svg);
--secondaryToolbarButton-scrollPage-icon: url(images/secondaryToolbarButton-scrollPage.svg);
--secondaryToolbarButton-scrollVertical-icon: url(images/secondaryToolbarButton-scrollVertical.svg); --secondaryToolbarButton-scrollVertical-icon: url(images/secondaryToolbarButton-scrollVertical.svg);
--secondaryToolbarButton-scrollHorizontal-icon: url(images/secondaryToolbarButton-scrollHorizontal.svg); --secondaryToolbarButton-scrollHorizontal-icon: url(images/secondaryToolbarButton-scrollHorizontal.svg);
--secondaryToolbarButton-scrollWrapped-icon: url(images/secondaryToolbarButton-scrollWrapped.svg); --secondaryToolbarButton-scrollWrapped-icon: url(images/secondaryToolbarButton-scrollWrapped.svg);
@ -1196,6 +1197,11 @@ html[dir="rtl"] .secondaryToolbarButton > span {
mask-image: var(--secondaryToolbarButton-handTool-icon); mask-image: var(--secondaryToolbarButton-handTool-icon);
} }
.secondaryToolbarButton.scrollPage::before {
-webkit-mask-image: var(--secondaryToolbarButton-scrollPage-icon);
mask-image: var(--secondaryToolbarButton-scrollPage-icon);
}
.secondaryToolbarButton.scrollVertical::before { .secondaryToolbarButton.scrollVertical::before {
-webkit-mask-image: var(--secondaryToolbarButton-scrollVertical-icon); -webkit-mask-image: var(--secondaryToolbarButton-scrollVertical-icon);
mask-image: var(--secondaryToolbarButton-scrollVertical-icon); mask-image: var(--secondaryToolbarButton-scrollVertical-icon);

View File

@ -196,31 +196,34 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="horizontalToolbarSeparator"></div> <div class="horizontalToolbarSeparator"></div>
<button id="scrollVertical" class="secondaryToolbarButton scrollModeButtons scrollVertical toggled" title="Use Vertical Scrolling" tabindex="62" data-l10n-id="scroll_vertical"> <button id="scrollPage" class="secondaryToolbarButton scrollModeButtons scrollPage" title="Use Page Scrolling" tabindex="62" data-l10n-id="scroll_page">
<span data-l10n-id="scroll_page_label">Page Scrolling</span>
</button>
<button id="scrollVertical" class="secondaryToolbarButton scrollModeButtons scrollVertical toggled" title="Use Vertical Scrolling" tabindex="63" data-l10n-id="scroll_vertical">
<span data-l10n-id="scroll_vertical_label">Vertical Scrolling</span> <span data-l10n-id="scroll_vertical_label">Vertical Scrolling</span>
</button> </button>
<button id="scrollHorizontal" class="secondaryToolbarButton scrollModeButtons scrollHorizontal" title="Use Horizontal Scrolling" tabindex="63" data-l10n-id="scroll_horizontal"> <button id="scrollHorizontal" class="secondaryToolbarButton scrollModeButtons scrollHorizontal" title="Use Horizontal Scrolling" tabindex="64" data-l10n-id="scroll_horizontal">
<span data-l10n-id="scroll_horizontal_label">Horizontal Scrolling</span> <span data-l10n-id="scroll_horizontal_label">Horizontal Scrolling</span>
</button> </button>
<button id="scrollWrapped" class="secondaryToolbarButton scrollModeButtons scrollWrapped" title="Use Wrapped Scrolling" tabindex="64" data-l10n-id="scroll_wrapped"> <button id="scrollWrapped" class="secondaryToolbarButton scrollModeButtons scrollWrapped" title="Use Wrapped Scrolling" tabindex="65" data-l10n-id="scroll_wrapped">
<span data-l10n-id="scroll_wrapped_label">Wrapped Scrolling</span> <span data-l10n-id="scroll_wrapped_label">Wrapped Scrolling</span>
</button> </button>
<div class="horizontalToolbarSeparator scrollModeButtons"></div> <div class="horizontalToolbarSeparator scrollModeButtons"></div>
<button id="spreadNone" class="secondaryToolbarButton spreadModeButtons spreadNone toggled" title="Do not join page spreads" tabindex="65" data-l10n-id="spread_none"> <button id="spreadNone" class="secondaryToolbarButton spreadModeButtons spreadNone toggled" title="Do not join page spreads" tabindex="66" data-l10n-id="spread_none">
<span data-l10n-id="spread_none_label">No Spreads</span> <span data-l10n-id="spread_none_label">No Spreads</span>
</button> </button>
<button id="spreadOdd" class="secondaryToolbarButton spreadModeButtons spreadOdd" title="Join page spreads starting with odd-numbered pages" tabindex="66" data-l10n-id="spread_odd"> <button id="spreadOdd" class="secondaryToolbarButton spreadModeButtons spreadOdd" title="Join page spreads starting with odd-numbered pages" tabindex="67" data-l10n-id="spread_odd">
<span data-l10n-id="spread_odd_label">Odd Spreads</span> <span data-l10n-id="spread_odd_label">Odd Spreads</span>
</button> </button>
<button id="spreadEven" class="secondaryToolbarButton spreadModeButtons spreadEven" title="Join page spreads starting with even-numbered pages" tabindex="67" data-l10n-id="spread_even"> <button id="spreadEven" class="secondaryToolbarButton spreadModeButtons spreadEven" title="Join page spreads starting with even-numbered pages" tabindex="68" data-l10n-id="spread_even">
<span data-l10n-id="spread_even_label">Even Spreads</span> <span data-l10n-id="spread_even_label">Even Spreads</span>
</button> </button>
<div class="horizontalToolbarSeparator spreadModeButtons"></div> <div class="horizontalToolbarSeparator spreadModeButtons"></div>
<button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="68" data-l10n-id="document_properties"> <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="69" data-l10n-id="document_properties">
<span data-l10n-id="document_properties_label">Document Properties…</span> <span data-l10n-id="document_properties_label">Document Properties…</span>
</button> </button>
</div> </div>

View File

@ -114,6 +114,7 @@ function getViewerConfiguration() {
pageRotateCcwButton: document.getElementById("pageRotateCcw"), pageRotateCcwButton: document.getElementById("pageRotateCcw"),
cursorSelectToolButton: document.getElementById("cursorSelectTool"), cursorSelectToolButton: document.getElementById("cursorSelectTool"),
cursorHandToolButton: document.getElementById("cursorHandTool"), cursorHandToolButton: document.getElementById("cursorHandTool"),
scrollPageButton: document.getElementById("scrollPage"),
scrollVerticalButton: document.getElementById("scrollVertical"), scrollVerticalButton: document.getElementById("scrollVertical"),
scrollHorizontalButton: document.getElementById("scrollHorizontal"), scrollHorizontalButton: document.getElementById("scrollHorizontal"),
scrollWrappedButton: document.getElementById("scrollWrapped"), scrollWrappedButton: document.getElementById("scrollWrapped"),