diff --git a/web/interfaces.js b/web/interfaces.js index 1cf9ee8d7..b99338c49 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -73,17 +73,6 @@ IRenderableView.prototype = { resume: function () {}, }; -/** - * @interface - */ -function ILastScrollSource() {} -ILastScrollSource.prototype = { - /** - * @returns {number} - */ - get lastScroll() {}, -}; - /** * @interface */ diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js index c54fc8ade..0d3d1d5ae 100644 --- a/web/pdf_find_controller.js +++ b/web/pdf_find_controller.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals PDFJS, FirefoxCom, Promise */ +/* globals PDFJS, FirefoxCom, Promise, scrollIntoView */ 'use strict'; @@ -24,6 +24,9 @@ var FindStates = { FIND_PENDING: 3 }; +var FIND_SCROLL_OFFSET_TOP = -50; +var FIND_SCROLL_OFFSET_LEFT = -400; + /** * Provides "search" or "find" functionality for the PDF. * This object actually performs the search for a given string. @@ -308,6 +311,26 @@ var PDFFindController = (function PDFFindControllerClosure() { } }, + /** + * The method is called back from the text layer when match presentation + * is updated. + * @param {number} pageIndex - page index. + * @param {number} index - match index. + * @param {Array} elements - text layer div elements array. + * @param {number} beginIdx - start index of the div array for the match. + * @param {number} endIdx - end index of the div array for the match. + */ + updateMatchPosition: function PDFFindController_updateMatchPosition( + pageIndex, index, elements, beginIdx, endIdx) { + if (this.selected.matchIdx === index && + this.selected.pageIdx === pageIndex) { + scrollIntoView(elements[beginIdx], { + top: FIND_SCROLL_OFFSET_TOP, + left: FIND_SCROLL_OFFSET_LEFT + }); + } + }, + nextPageMatch: function PDFFindController_nextPageMatch() { if (this.resumePageIdx !== null) { console.error('There can only be one pending page.'); diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 6c8ea8c29..50d3c2b9b 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -18,6 +18,8 @@ 'use strict'; +var TEXT_LAYER_RENDER_DELAY = 200; // ms + /** * @typedef {Object} PDFPageViewOptions * @property {HTMLDivElement} container - The viewer element. @@ -194,6 +196,15 @@ var PDFPageView = (function PDFPageViewClosure() { this.reset(true); }, + /** + * Called when moved in the parent's container. + */ + updatePosition: function PDFPageView_updatePosition() { + if (this.textLayer) { + this.textLayer.render(TEXT_LAYER_RENDER_DELAY); + } + }, + cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) { // Scale canvas, canvas wrapper, and page container. var width = this.viewport.width; @@ -429,6 +440,7 @@ var PDFPageView = (function PDFPageViewClosure() { self.pdfPage.getTextContent().then( function textContentResolved(textContent) { textLayer.setTextContent(textContent); + textLayer.render(TEXT_LAYER_RENDER_DELAY); } ); } diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 1ad1c0c7e..e68130ce5 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -49,7 +49,6 @@ var DEFAULT_CACHE_SIZE = 10; /** * Simple viewer control to display PDF content/pages. * @class - * @implements {ILastScrollSource} * @implements {IRenderableView} */ var PDFViewer = (function pdfViewer() { @@ -92,7 +91,6 @@ var PDFViewer = (function pdfViewer() { } this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this)); - this.lastScroll = 0; this.updateInProgress = false; this.presentationModeState = PresentationModeState.UNKNOWN; this._resetView(); @@ -333,12 +331,13 @@ var PDFViewer = (function pdfViewer() { }, _scrollUpdate: function () { - this.lastScroll = Date.now(); - if (this.pagesCount === 0) { return; } this.update(); + for (var i = 0, ii = this.pages.length; i < ii; i++) { + this.pages[i].updatePosition(); + } }, _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages( @@ -696,9 +695,7 @@ var PDFViewer = (function pdfViewer() { textLayerDiv: textLayerDiv, pageIndex: pageIndex, viewport: viewport, - lastScrollSource: this, - isViewerInPresentationMode: isViewerInPresentationMode, - findController: this.findController + findController: isViewerInPresentationMode ? null : this.findController }); }, diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index 5fac1c493..d300f6354 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -13,14 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals CustomStyle, scrollIntoView, PDFJS */ +/* globals CustomStyle, PDFJS */ 'use strict'; -var FIND_SCROLL_OFFSET_TOP = -50; -var FIND_SCROLL_OFFSET_LEFT = -400; var MAX_TEXT_DIVS_TO_RENDER = 100000; -var RENDER_DELAY = 200; // ms var NonWhitespaceRegexp = /\S/; @@ -33,9 +30,6 @@ function isAllWhitespace(str) { * @property {HTMLDivElement} textLayerDiv - The text layer container. * @property {number} pageIndex - The page index. * @property {PageViewport} viewport - The viewport of the text layer. - * @property {ILastScrollSource} lastScrollSource - The object that records when - * last time scroll happened. - * @property {boolean} isViewerInPresentationMode * @property {PDFFindController} findController */ @@ -49,13 +43,11 @@ function isAllWhitespace(str) { var TextLayerBuilder = (function TextLayerBuilderClosure() { function TextLayerBuilder(options) { this.textLayerDiv = options.textLayerDiv; - this.layoutDone = false; + this.renderingDone = false; this.divContentDone = false; this.pageIdx = options.pageIndex; this.matches = []; - this.lastScrollSource = options.lastScrollSource || null; this.viewport = options.viewport; - this.isViewerInPresentationMode = options.isViewerInPresentationMode; this.textDivs = []; this.findController = options.findController || null; } @@ -71,6 +63,7 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { // No point in rendering many divs as it would make the browser // unusable even after the divs are rendered. if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) { + this.renderingDone = true; return; } @@ -118,23 +111,29 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { this.updateMatches(); }, - setupRenderLayoutTimer: - function TextLayerBuilder_setupRenderLayoutTimer() { - // Schedule renderLayout() if the user has been scrolling, - // otherwise run it right away. - var self = this; - var lastScroll = (this.lastScrollSource === null ? - 0 : this.lastScrollSource.lastScroll); + /** + * Renders the text layer. + * @param {number} timeout (optional) if specified, the rendering waits + * for specified amount of ms. + */ + render: function TextLayerBuilder_render(timeout) { + if (!this.divContentDone || this.renderingDone) { + return; + } - if (Date.now() - lastScroll > RENDER_DELAY) { // Render right away + if (this.renderTimer) { + clearTimeout(this.renderTimer); + this.renderTimer = null; + } + + if (!timeout) { // Render right away this.renderLayer(); } else { // Schedule - if (this.renderTimer) { - clearTimeout(this.renderTimer); - } + var self = this; this.renderTimer = setTimeout(function() { - self.setupRenderLayoutTimer(); - }, RENDER_DELAY); + self.renderLayer(); + self.renderTimer = null; + }, timeout); } }, @@ -204,7 +203,6 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { this.appendText(textItems[i], textContent.styles); } this.divContentDone = true; - this.setupRenderLayoutTimer(); }, convertMatches: function TextLayerBuilder_convertMatches(matches) { @@ -266,8 +264,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { var bidiTexts = this.textContent.items; var textDivs = this.textDivs; var prevEnd = null; + var pageIdx = this.pageIdx; var isSelectedPage = (this.findController === null ? - false : (this.pageIdx === this.findController.selected.pageIdx)); + false : (pageIdx === this.findController.selected.pageIdx)); var selectedMatchIdx = (this.findController === null ? -1 : this.findController.selected.matchIdx); var highlightAll = (this.findController === null ? @@ -313,10 +312,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { var isSelected = (isSelectedPage && i === selectedMatchIdx); var highlightSuffix = (isSelected ? ' selected' : ''); - if (isSelected && !this.isViewerInPresentationMode) { - scrollIntoView(textDivs[begin.divIdx], - { top: FIND_SCROLL_OFFSET_TOP, - left: FIND_SCROLL_OFFSET_LEFT }); + if (this.findController) { + this.findController.updateMatchPosition(pageIdx, i, textDivs, + begin.divIdx, end.divIdx); } // Match inside new div.