From 511458fbbc523a76610f3fb4927d308425939c47 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 7 Oct 2021 14:04:41 +0200 Subject: [PATCH 1/2] Add a new Page scrolling mode (issue 2638, 8952, 10907) This implements a new Page scrolling mode, essentially bringing (and extending) the functionality from `PDFSinglePageViewer` into the regular `PDFViewer`-class. Compared to `PDFSinglePageViewer`, which as its name suggests will only display one page at a time, in the `PDFViewer`-implementation this new Page scrolling mode also support spreadModes properly (somewhat similar to e.g. Adobe Reader). Given the size and scope of these changes, I've tried to focus on implementing the basic functionality. Hence there's room for further clean-up and/or improvements, including e.g. simplifying the CSS/JS related to PresentationMode and implementing easier page-switching with the mouse-wheel/arrow-keys. --- l10n/en-US/viewer.properties | 2 + l10n/sv-SE/viewer.properties | 2 + web/app.js | 14 +- web/base_viewer.js | 256 ++++++++++++++---- .../secondaryToolbarButton-scrollPage.svg | 1 + web/pdf_presentation_mode.js | 25 +- web/pdf_scripting_manager.js | 8 +- web/pdf_single_page_viewer.js | 109 +------- web/pdf_viewer.js | 59 +--- web/secondary_toolbar.js | 10 + web/ui_utils.js | 30 +- web/viewer.css | 6 + web/viewer.html | 17 +- web/viewer.js | 1 + 14 files changed, 296 insertions(+), 244 deletions(-) create mode 100644 web/images/secondaryToolbarButton-scrollPage.svg diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index 5fe094b76..dc54ed39d 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -61,6 +61,8 @@ cursor_text_select_tool_label=Text Selection Tool cursor_hand_tool.title=Enable 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_label=Vertical Scrolling scroll_horizontal.title=Use Horizontal Scrolling diff --git a/l10n/sv-SE/viewer.properties b/l10n/sv-SE/viewer.properties index 75f675eab..a1d6fe1bc 100644 --- a/l10n/sv-SE/viewer.properties +++ b/l10n/sv-SE/viewer.properties @@ -61,6 +61,8 @@ cursor_text_select_tool_label=Textmarkeringsverktyg cursor_hand_tool.title=Aktivera 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_label=Vertikal rullning scroll_horizontal.title=Använd horisontell rullning diff --git a/web/app.js b/web/app.js index 9ee0edf6f..a804bb04a 100644 --- a/web/app.js +++ b/web/app.js @@ -16,7 +16,7 @@ import { animationStarted, - apiPageLayoutToSpreadMode, + apiPageLayoutToViewerModes, apiPageModeToSidebarView, AutomationEventBus, AutoPrintRegExp, @@ -1300,8 +1300,16 @@ const PDFViewerApplication = { if (pageMode && sidebarView === SidebarView.UNKNOWN) { sidebarView = apiPageModeToSidebarView(pageMode); } - if (pageLayout && spreadMode === SpreadMode.UNKNOWN) { - spreadMode = apiPageLayoutToSpreadMode(pageLayout); + if ( + 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, { diff --git a/web/base_viewer.js b/web/base_viewer.js index 206ac4482..fbacf361c 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -445,14 +445,6 @@ class BaseViewer { 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 */ @@ -538,6 +530,10 @@ class BaseViewer { this._firstPageCapability.resolve(firstPdfPage); this._optionalContentConfigPromise = optionalContentConfigPromise; + const viewerElement = + this._scrollMode === ScrollMode.PAGE + ? this._scrollModePageState.shadowViewer + : this.viewer; const scale = this.currentScale; const viewport = firstPdfPage.getViewport({ scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS, @@ -552,7 +548,7 @@ class BaseViewer { for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) { const pageView = new PDFPageView({ - container: this._viewerElement, + container: viewerElement, eventBus: this.eventBus, id: pageNum, scale, @@ -582,7 +578,12 @@ class BaseViewer { firstPageView.setPdfPage(firstPdfPage); 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(); } @@ -684,8 +685,16 @@ class BaseViewer { this._onePageRenderedCapability = createPromiseCapability(); this._pagesCapability = createPromiseCapability(); this._scrollMode = ScrollMode.VERTICAL; + this._previousScrollMode = ScrollMode.UNKNOWN; this._spreadMode = SpreadMode.NONE; + this._scrollModePageState = { + shadowViewer: document.createDocumentFragment(), + previousPageNumber: 1, + scrollDown: true, + pages: [], + }; + if (this._onBeforeDraw) { this.eventBus._off("pagerender", this._onBeforeDraw); this._onBeforeDraw = null; @@ -700,6 +709,71 @@ class BaseViewer { 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() { if (this.pagesCount === 0) { return; @@ -708,6 +782,29 @@ class BaseViewer { } _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); } @@ -772,8 +869,7 @@ class BaseViewer { get _pageWidthScaleFactor() { if ( this._spreadMode !== SpreadMode.NONE && - this._scrollMode !== ScrollMode.HORIZONTAL && - !this.isInPresentationMode + this._scrollMode !== ScrollMode.HORIZONTAL ) { return 2; } @@ -794,7 +890,7 @@ class BaseViewer { let hPadding = noPadding ? 0 : SCROLLBAR_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. } const pageWidthScale = @@ -1047,10 +1143,6 @@ class BaseViewer { }; } - _updateHelper(visiblePages) { - throw new Error("Not implemented: _updateHelper"); - } - update() { const visible = this._getVisiblePages(); const visiblePages = visible.views, @@ -1064,7 +1156,28 @@ class BaseViewer { 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.eventBus.dispatch("updateviewarea", { @@ -1081,14 +1194,6 @@ class BaseViewer { 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() { return getComputedStyle(this.container).direction === "rtl"; } @@ -1115,9 +1220,8 @@ class BaseViewer { /** * 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: - * - `PDFSinglePageViewer`. - * - `PDFViewer` with Presentation Mode active. + * the viewer can only display a single page at a time, for example: + * - When PresentationMode is active. */ _getCurrentVisiblePage() { if (!this.pagesCount) { @@ -1138,12 +1242,24 @@ class BaseViewer { } _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({ scrollEl: this.container, - views: this._pages, + views, sortByVisibility: true, - horizontal: this._isScrollModeHorizontal, - rtl: this._isScrollModeHorizontal && this._isContainerRtl, + horizontal, + rtl, }); } @@ -1245,15 +1361,25 @@ class BaseViewer { 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) { const visiblePages = currentlyVisiblePages || this._getVisiblePages(); - const scrollAhead = this._isScrollModeHorizontal - ? this.scroll.right - : this.scroll.down; + const scrollAhead = this._scrollAhead; const preRenderExtra = - this._scrollMode === ScrollMode.VERTICAL && this._spreadMode !== SpreadMode.NONE && - !this.isInPresentationMode; + this._scrollMode !== ScrollMode.HORIZONTAL; const pageView = this.renderingQueue.getHighestPriority( visiblePages, @@ -1492,6 +1618,8 @@ class BaseViewer { if (!isValidScrollMode(mode)) { throw new Error(`Invalid scroll mode: ${mode}`); } + this._previousScrollMode = this._scrollMode; + this._scrollMode = mode; this.eventBus.dispatch("scrollmodechanged", { source: this, mode }); @@ -1511,6 +1639,14 @@ class BaseViewer { if (!this.pdfDocument || !pageNumber) { 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. // Call this before re-scrolling to the current page, to ensure that any // changes in scale don't move the current page. @@ -1552,32 +1688,40 @@ class BaseViewer { } const viewer = this.viewer, pages = this._pages; - // Temporarily remove all the pages from the DOM. - viewer.textContent = ""; - if (this._spreadMode === SpreadMode.NONE) { - for (let i = 0, iMax = pages.length; i < iMax; ++i) { - viewer.appendChild(pages[i].div); - } + if (this._scrollMode === ScrollMode.PAGE) { + this._ensurePageViewVisible(); } else { - const parity = this._spreadMode - 1; - let spread = null; - for (let i = 0, iMax = pages.length; i < iMax; ++i) { - 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); + // Temporarily remove all the pages from the DOM. + viewer.textContent = ""; + + if (this._spreadMode === SpreadMode.NONE) { + for (let i = 0, ii = pages.length; i < ii; ++i) { + viewer.appendChild(pages[i].div); + } + } else { + const parity = this._spreadMode - 1; + let spread = null; + for (let i = 0, ii = pages.length; i < ii; ++i) { + 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); + } + spread.appendChild(pages[i].div); } - spread.appendChild(pages[i].div); } } if (!pageNumber) { 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)) { this._setScale(this._currentScaleValue, true); } @@ -1589,9 +1733,6 @@ class BaseViewer { * @private */ _getPageAdvance(currentPageNumber, previous = false) { - if (this.isInPresentationMode) { - return 1; - } switch (this._scrollMode) { case ScrollMode.WRAPPED: { const { views } = this._getVisiblePages(), @@ -1655,6 +1796,7 @@ class BaseViewer { case ScrollMode.HORIZONTAL: { break; } + case ScrollMode.PAGE: case ScrollMode.VERTICAL: { if (this._spreadMode === SpreadMode.NONE) { break; // Normal vertical scrolling. diff --git a/web/images/secondaryToolbarButton-scrollPage.svg b/web/images/secondaryToolbarButton-scrollPage.svg new file mode 100644 index 000000000..bea2f0d67 --- /dev/null +++ b/web/images/secondaryToolbarButton-scrollPage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/pdf_presentation_mode.js b/web/pdf_presentation_mode.js index 6fb788e14..fc393694d 100644 --- a/web/pdf_presentation_mode.js +++ b/web/pdf_presentation_mode.js @@ -13,7 +13,12 @@ * 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_HIDING_CONTROLS = 3000; // in ms @@ -84,8 +89,10 @@ class PDFPresentationMode { } this.args = { - page: this.pdfViewer.currentPageNumber, - previousScale: this.pdfViewer.currentScaleValue, + pageNumber: this.pdfViewer.currentPageNumber, + scaleValue: this.pdfViewer.currentScaleValue, + scrollMode: this.pdfViewer.scrollMode, + spreadMode: this.pdfViewer.spreadMode, }; return true; @@ -203,7 +210,9 @@ class PDFPresentationMode { // Ensure that the correct page is scrolled into view when entering // Presentation Mode, by waiting until fullscreen mode in enabled. 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"; }, 0); @@ -221,7 +230,7 @@ class PDFPresentationMode { * @private */ _exit() { - const page = this.pdfViewer.currentPageNumber; + const pageNumber = this.pdfViewer.currentPageNumber; this.container.classList.remove(ACTIVE_SELECTOR); // Ensure that the correct page is scrolled into view when exiting @@ -231,8 +240,10 @@ class PDFPresentationMode { this._removeFullscreenChangeListeners(); this._notifyStateChange(); - this.pdfViewer.currentScaleValue = this.args.previousScale; - this.pdfViewer.currentPageNumber = page; + this.pdfViewer.scrollMode = this.args.scrollMode; + this.pdfViewer.spreadMode = this.args.spreadMode; + this.pdfViewer.currentScaleValue = this.args.scaleValue; + this.pdfViewer.currentPageNumber = pageNumber; this.args = null; }, 0); diff --git a/web/pdf_scripting_manager.js b/web/pdf_scripting_manager.js index f2af2386f..7895404db 100644 --- a/web/pdf_scripting_manager.js +++ b/web/pdf_scripting_manager.js @@ -14,7 +14,7 @@ */ 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"; /** @@ -287,7 +287,11 @@ class PDFScriptingManager { console.error(value); break; case "layout": - this._pdfViewer.spreadMode = apiPageLayoutToSpreadMode(value); + if (isInPresentationMode) { + return; + } + const modes = apiPageLayoutToViewerModes(value); + this._pdfViewer.spreadMode = modes.spreadMode; break; case "page-num": this._pdfViewer.currentPageNumber = value + 1; diff --git a/web/pdf_single_page_viewer.js b/web/pdf_single_page_viewer.js index a3bee655d..4ba497441 100644 --- a/web/pdf_single_page_viewer.js +++ b/web/pdf_single_page_viewer.js @@ -13,118 +13,25 @@ * limitations under the License. */ +import { ScrollMode, SpreadMode } from "./ui_utils.js"; 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; + this._scrollMode = ScrollMode.PAGE; + this._spreadMode = SpreadMode.NONE; } - _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); - } + // eslint-disable-next-line accessor-pairs + set scrollMode(mode) {} _updateScrollMode() {} - _updateSpreadMode() {} + // eslint-disable-next-line accessor-pairs + set spreadMode(mode) {} - _getPageAdvance() { - return 1; - } + _updateSpreadMode() {} } export { PDFSinglePageViewer }; diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 2a70a1de0..3830e052e 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -13,65 +13,8 @@ * limitations under the License. */ -import { ScrollMode, SpreadMode } from "./ui_utils.js"; import { BaseViewer } from "./base_viewer.js"; -import { shadow } from "pdfjs-lib"; -class PDFViewer extends BaseViewer { - get _viewerElement() { - return shadow(this, "_viewerElement", this.viewer); - } - - _scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null }) { - if (!pageSpot && !this.isInPresentationMode) { - const left = pageDiv.offsetLeft + pageDiv.clientLeft; - const right = left + pageDiv.clientWidth; - const { scrollLeft, clientWidth } = this.container; - if ( - this._isScrollModeHorizontal || - left < scrollLeft || - right > scrollLeft + clientWidth - ) { - pageSpot = { left: 0, top: 0 }; - } - } - super._scrollIntoView({ pageDiv, pageSpot, pageNumber }); - } - - _getVisiblePages() { - 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); - } -} +class PDFViewer extends BaseViewer {} export { PDFViewer }; diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 92cbf728b..fdaaef3ad 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -93,6 +93,12 @@ class SecondaryToolbar { eventDetails: { tool: CursorTool.HAND }, close: true, }, + { + element: options.scrollPageButton, + eventName: "switchscrollmode", + eventDetails: { mode: ScrollMode.PAGE }, + close: true, + }, { element: options.scrollVerticalButton, eventName: "switchscrollmode", @@ -247,6 +253,10 @@ class SecondaryToolbar { _bindScrollModeListener(buttons) { function scrollModeChanged({ mode }) { + buttons.scrollPageButton.classList.toggle( + "toggled", + mode === ScrollMode.PAGE + ); buttons.scrollVerticalButton.classList.toggle( "toggled", mode === ScrollMode.VERTICAL diff --git a/web/ui_utils.js b/web/ui_utils.js index 3c947613a..e8f61436b 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -57,6 +57,7 @@ const ScrollMode = { VERTICAL: 0, // Default value. HORIZONTAL: 1, WRAPPED: 2, + PAGE: 3, }; const SpreadMode = { @@ -952,21 +953,32 @@ function getActiveOrFocusedElement() { * necessary Scroll/Spread modes (since SinglePage, TwoPageLeft, * and TwoPageRight all suggests using non-continuous scrolling). * @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) { case "SinglePage": + scrollMode = ScrollMode.PAGE; + break; case "OneColumn": - return SpreadMode.NONE; - case "TwoColumnLeft": + break; case "TwoPageLeft": - return SpreadMode.ODD; - case "TwoColumnRight": + scrollMode = ScrollMode.PAGE; + /* falls through */ + case "TwoColumnLeft": + spreadMode = SpreadMode.ODD; + break; 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 { animationStarted, - apiPageLayoutToSpreadMode, + apiPageLayoutToViewerModes, apiPageModeToSidebarView, approximateFraction, AutomationEventBus, diff --git a/web/viewer.css b/web/viewer.css index ccf203847..0ed926dd3 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -94,6 +94,7 @@ --secondaryToolbarButton-rotateCw-icon: url(images/secondaryToolbarButton-rotateCw.svg); --secondaryToolbarButton-selectTool-icon: url(images/secondaryToolbarButton-selectTool.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-scrollHorizontal-icon: url(images/secondaryToolbarButton-scrollHorizontal.svg); --secondaryToolbarButton-scrollWrapped-icon: url(images/secondaryToolbarButton-scrollWrapped.svg); @@ -1196,6 +1197,11 @@ html[dir="rtl"] .secondaryToolbarButton > span { 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 { -webkit-mask-image: var(--secondaryToolbarButton-scrollVertical-icon); mask-image: var(--secondaryToolbarButton-scrollVertical-icon); diff --git a/web/viewer.html b/web/viewer.html index e5368a4a2..c49aee352 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -196,31 +196,34 @@ See https://github.com/adobe-type-tools/cmap-resources
- + - -
- - -
- diff --git a/web/viewer.js b/web/viewer.js index 1cc45837f..53fe840ac 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -114,6 +114,7 @@ function getViewerConfiguration() { pageRotateCcwButton: document.getElementById("pageRotateCcw"), cursorSelectToolButton: document.getElementById("cursorSelectTool"), cursorHandToolButton: document.getElementById("cursorHandTool"), + scrollPageButton: document.getElementById("scrollPage"), scrollVerticalButton: document.getElementById("scrollVertical"), scrollHorizontalButton: document.getElementById("scrollHorizontal"), scrollWrappedButton: document.getElementById("scrollWrapped"), From e1a2e916e89d7937eb4ff25f629aad1f51dafae3 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 9 Oct 2021 15:37:54 +0200 Subject: [PATCH 2/2] Move `PDFSinglePageViewer` into the `web/pdf_viewer.js` file With the previous commit, both of the `PDFViewer` and `PDFSinglePageViewer` clases are now small/simple enough that it no longer seems necessary to keep them in separate files. --- web/pdf_single_page_viewer.js | 37 ----------------------------------- web/pdf_viewer.component.js | 3 +-- web/pdf_viewer.js | 21 +++++++++++++++++++- web/secondary_toolbar.js | 2 +- 4 files changed, 22 insertions(+), 41 deletions(-) delete mode 100644 web/pdf_single_page_viewer.js diff --git a/web/pdf_single_page_viewer.js b/web/pdf_single_page_viewer.js deleted file mode 100644 index 4ba497441..000000000 --- a/web/pdf_single_page_viewer.js +++ /dev/null @@ -1,37 +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 { ScrollMode, SpreadMode } from "./ui_utils.js"; -import { BaseViewer } from "./base_viewer.js"; - -class PDFSinglePageViewer extends BaseViewer { - _resetView() { - super._resetView(); - this._scrollMode = ScrollMode.PAGE; - this._spreadMode = SpreadMode.NONE; - } - - // eslint-disable-next-line accessor-pairs - set scrollMode(mode) {} - - _updateScrollMode() {} - - // eslint-disable-next-line accessor-pairs - set spreadMode(mode) {} - - _updateSpreadMode() {} -} - -export { PDFSinglePageViewer }; diff --git a/web/pdf_viewer.component.js b/web/pdf_viewer.component.js index 38d21ed4f..a424c29e7 100644 --- a/web/pdf_viewer.component.js +++ b/web/pdf_viewer.component.js @@ -31,6 +31,7 @@ import { } from "./xfa_layer_builder.js"; import { EventBus, ProgressBar } from "./ui_utils.js"; import { PDFLinkService, SimpleLinkService } from "./pdf_link_service.js"; +import { PDFSinglePageViewer, PDFViewer } from "./pdf_viewer.js"; import { DownloadManager } from "./download_manager.js"; import { GenericL10n } from "./genericl10n.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 { PDFPageView } from "./pdf_page_view.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 const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION"); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 3830e052e..7ca01ec04 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -13,8 +13,27 @@ * limitations under the License. */ +import { ScrollMode, SpreadMode } from "./ui_utils.js"; import { BaseViewer } from "./base_viewer.js"; class PDFViewer extends BaseViewer {} -export { PDFViewer }; +class PDFSinglePageViewer extends BaseViewer { + _resetView() { + super._resetView(); + this._scrollMode = ScrollMode.PAGE; + this._spreadMode = SpreadMode.NONE; + } + + // eslint-disable-next-line accessor-pairs + set scrollMode(mode) {} + + _updateScrollMode() {} + + // eslint-disable-next-line accessor-pairs + set spreadMode(mode) {} + + _updateSpreadMode() {} +} + +export { PDFSinglePageViewer, PDFViewer }; diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index fdaaef3ad..468dd69c9 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -15,7 +15,7 @@ import { SCROLLBAR_PADDING, ScrollMode, SpreadMode } from "./ui_utils.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