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 deleted file mode 100644 index a3bee655d..000000000 --- a/web/pdf_single_page_viewer.js +++ /dev/null @@ -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 }; 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 2a70a1de0..7ca01ec04 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -15,63 +15,25 @@ 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); +class PDFViewer extends BaseViewer {} + +class PDFSinglePageViewer extends BaseViewer { + _resetView() { + super._resetView(); + this._scrollMode = ScrollMode.PAGE; + this._spreadMode = SpreadMode.NONE; } - _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 }); - } + // eslint-disable-next-line accessor-pairs + set scrollMode(mode) {} - _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(); - } + _updateScrollMode() {} - _updateHelper(visiblePages) { - if (this.isInPresentationMode) { - return; - } - let currentId = this._currentPageNumber; - let stillFullyVisible = false; + // eslint-disable-next-line accessor-pairs + set spreadMode(mode) {} - 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); - } + _updateSpreadMode() {} } -export { PDFViewer }; +export { PDFSinglePageViewer, PDFViewer }; diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 92cbf728b..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 @@ -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
-