From 863d583ae1e5234382c0d13c2c9f8cb88f864d06 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Sat, 27 Sep 2014 11:58:42 -0500 Subject: [PATCH 01/10] Renames page_view.js file. --- web/{page_view.js => pdf_page_view.js} | 0 web/pdf_viewer.js | 2 +- web/viewer.html | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename web/{page_view.js => pdf_page_view.js} (100%) diff --git a/web/page_view.js b/web/pdf_page_view.js similarity index 100% rename from web/page_view.js rename to web/pdf_page_view.js diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 47e109fab..190c37ed4 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -31,7 +31,7 @@ var PresentationModeState = { var IGNORE_CURRENT_POSITION_ON_ZOOM = false; //#include pdf_rendering_queue.js -//#include page_view.js +//#include pdf_page_view.js //#include text_layer_builder.js /** diff --git a/web/viewer.html b/web/viewer.html index f562dbd66..c2d3dff69 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -69,7 +69,7 @@ http://sourceforge.net/adobe/cmap/wiki/License/ - + From f68678086dbbc0214eed883905a830f6fc14c1ff Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Sat, 27 Sep 2014 13:03:28 -0500 Subject: [PATCH 02/10] Simple restructuring PageView into PDFPageView --- web/pdf_find_controller.js | 3 +- web/pdf_page_view.js | 1111 ++++++++++++++++++------------------ web/pdf_viewer.js | 24 +- web/viewer.js | 3 +- 4 files changed, 585 insertions(+), 556 deletions(-) diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js index 1aa912109..c54fc8ade 100644 --- a/web/pdf_find_controller.js +++ b/web/pdf_find_controller.js @@ -197,8 +197,6 @@ var PDFFindController = (function PDFFindControllerClosure() { }, updatePage: function PDFFindController_updatePage(index) { - var page = this.pdfViewer.getPageView(index); - if (this.selected.pageIdx === index) { // If the page is selected, scroll the page into view, which triggers // rendering the page, which adds the textLayer. Once the textLayer is @@ -206,6 +204,7 @@ var PDFFindController = (function PDFFindControllerClosure() { this.pdfViewer.scrollPageIntoView(index + 1); } + var page = this.pdfViewer.getPageView(index); if (page.textLayer) { page.textLayer.updateMatches(); } diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 92ed39b7d..7e6aa17b3 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -20,591 +20,616 @@ 'use strict'; /** - * @constructor - * @param {HTMLDivElement} container - The viewer element. - * @param {number} id - The page unique ID (normally its number). - * @param {number} scale - The page scale display. - * @param {PageViewport} defaultViewport - The page viewport. - * @param {IPDFLinkService} linkService - The navigation/linking service. - * @param {PDFRenderingQueue} renderingQueue - The rendering queue object. - * @param {Cache} cache - The page cache. - * @param {PDFPageSource} pageSource - * @param {PDFViewer} viewer - * + * @typedef {Object} PDFPageViewOptions + * @property {HTMLDivElement} container - The viewer element. + * @property {number} id - The page unique ID (normally its number). + * @property {number} scale - The page scale display. + * @property {PageViewport} defaultViewport - The page viewport. + * @property {IPDFLinkService} linkService - The navigation/linking service. + * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. + * @property {Cache} cache - The page cache. + * @property {PDFPageSource} pageSource + * @property {PDFViewer} viewer + */ + +/** + * @class * @implements {IRenderableView} */ -var PageView = function pageView(container, id, scale, defaultViewport, - linkService, renderingQueue, cache, - pageSource, viewer) { - this.id = id; - this.renderingId = 'page' + id; +var PDFPageView = (function PDFPageViewClosure() { + /** + * @constructs PDFPageView + * @param {PDFPageViewOptions} options + */ + function PDFPageView(options) { + var container = options.container; + var id = options.id; + var scale = options.scale; + var defaultViewport = options.defaultViewport; + var linkService = options.linkService; + var renderingQueue = options.renderingQueue; + var cache = options.cache; + var pageSource = options.pageSource; + var viewer = options.viewer; - this.rotation = 0; - this.scale = scale || 1.0; - this.viewport = defaultViewport; - this.pdfPageRotate = defaultViewport.rotation; - this.hasRestrictedScaling = false; + this.id = id; + this.renderingId = 'page' + id; - this.linkService = linkService; - this.renderingQueue = renderingQueue; - this.cache = cache; - this.pageSource = pageSource; - this.viewer = viewer; + this.rotation = 0; + this.scale = scale || 1.0; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotation; + this.hasRestrictedScaling = false; - this.renderingState = RenderingStates.INITIAL; - this.resume = null; + this.linkService = linkService; + this.renderingQueue = renderingQueue; + this.cache = cache; + this.pageSource = pageSource; + this.viewer = viewer; - this.textLayer = null; - - this.zoomLayer = null; - - this.annotationLayer = null; - - var anchor = document.createElement('a'); - anchor.name = '' + this.id; - - var div = this.el = document.createElement('div'); - div.id = 'pageContainer' + this.id; - div.className = 'page'; - div.style.width = Math.floor(this.viewport.width) + 'px'; - div.style.height = Math.floor(this.viewport.height) + 'px'; - - container.appendChild(anchor); - container.appendChild(div); - - this.setPdfPage = function pageViewSetPdfPage(pdfPage) { - this.pdfPage = pdfPage; - this.pdfPageRotate = pdfPage.rotate; - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation); - this.stats = pdfPage.stats; - this.reset(); - }; - - this.destroy = function pageViewDestroy() { - this.zoomLayer = null; - this.reset(); - if (this.pdfPage) { - this.pdfPage.destroy(); - } - }; - - this.reset = function pageViewReset(keepAnnotations) { - if (this.renderTask) { - this.renderTask.cancel(); - } - this.resume = null; this.renderingState = RenderingStates.INITIAL; + this.resume = null; + this.textLayer = null; + + this.zoomLayer = null; + + this.annotationLayer = null; + + var anchor = document.createElement('a'); + anchor.name = '' + this.id; + + var div = document.createElement('div'); + div.id = 'pageContainer' + this.id; + div.className = 'page'; div.style.width = Math.floor(this.viewport.width) + 'px'; div.style.height = Math.floor(this.viewport.height) + 'px'; + this.el = div; // TODO replace 'el' property usage + this.div = div; - var childNodes = div.childNodes; - for (var i = div.childNodes.length - 1; i >= 0; i--) { - var node = childNodes[i]; - if ((this.zoomLayer && this.zoomLayer === node) || - (keepAnnotations && this.annotationLayer === node)) { - continue; - } - div.removeChild(node); - } - div.removeAttribute('data-loaded'); - - if (keepAnnotations) { - if (this.annotationLayer) { - // Hide annotationLayer until all elements are resized - // so they are not displayed on the already-resized page - this.annotationLayer.setAttribute('hidden', 'true'); - } - } else { - this.annotationLayer = null; - } - - if (this.canvas) { - // Zeroing the width and height causes Firefox to release graphics - // resources immediately, which can greatly reduce memory consumption. - this.canvas.width = 0; - this.canvas.height = 0; - delete this.canvas; - } - - this.loadingIconDiv = document.createElement('div'); - this.loadingIconDiv.className = 'loadingIcon'; - div.appendChild(this.loadingIconDiv); - }; - - this.update = function pageViewUpdate(scale, rotation) { - this.scale = scale || this.scale; - - if (typeof rotation !== 'undefined') { - this.rotation = rotation; - } - - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = this.viewport.clone({ - scale: this.scale * CSS_UNITS, - rotation: totalRotation - }); - - var isScalingRestricted = false; - if (this.canvas && PDFJS.maxCanvasPixels > 0) { - var ctx = this.canvas.getContext('2d'); - var outputScale = getOutputScale(ctx); - var pixelsInViewport = this.viewport.width * this.viewport.height; - var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); - if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) * - ((Math.floor(this.viewport.height) * outputScale.sy) | 0) > - PDFJS.maxCanvasPixels) { - isScalingRestricted = true; - } - } - - if (this.canvas && - (PDFJS.useOnlyCssZoom || - (this.hasRestrictedScaling && isScalingRestricted))) { - this.cssTransform(this.canvas, true); - return; - } else if (this.canvas && !this.zoomLayer) { - this.zoomLayer = this.canvas.parentNode; - this.zoomLayer.style.position = 'absolute'; - } - if (this.zoomLayer) { - this.cssTransform(this.zoomLayer.firstChild); - } - this.reset(true); - }; - - this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) { - // Scale canvas, canvas wrapper, and page container. - var width = this.viewport.width; - var height = this.viewport.height; - canvas.style.width = canvas.parentNode.style.width = div.style.width = - Math.floor(width) + 'px'; - canvas.style.height = canvas.parentNode.style.height = div.style.height = - Math.floor(height) + 'px'; - // The canvas may have been originally rotated, so rotate relative to that. - var relativeRotation = this.viewport.rotation - canvas._viewport.rotation; - var absRotation = Math.abs(relativeRotation); - var scaleX = 1, scaleY = 1; - if (absRotation === 90 || absRotation === 270) { - // Scale x and y because of the rotation. - scaleX = height / width; - scaleY = width / height; - } - var cssTransform = 'rotate(' + relativeRotation + 'deg) ' + - 'scale(' + scaleX + ',' + scaleY + ')'; - CustomStyle.setProp('transform', canvas, cssTransform); - - if (this.textLayer) { - // Rotating the text layer is more complicated since the divs inside the - // the text layer are rotated. - // TODO: This could probably be simplified by drawing the text layer in - // one orientation then rotating overall. - var textLayerViewport = this.textLayer.viewport; - var textRelativeRotation = this.viewport.rotation - - textLayerViewport.rotation; - var textAbsRotation = Math.abs(textRelativeRotation); - var scale = width / textLayerViewport.width; - if (textAbsRotation === 90 || textAbsRotation === 270) { - scale = width / textLayerViewport.height; - } - var textLayerDiv = this.textLayer.textLayerDiv; - var transX, transY; - switch (textAbsRotation) { - case 0: - transX = transY = 0; - break; - case 90: - transX = 0; - transY = '-' + textLayerDiv.style.height; - break; - case 180: - transX = '-' + textLayerDiv.style.width; - transY = '-' + textLayerDiv.style.height; - break; - case 270: - transX = '-' + textLayerDiv.style.width; - transY = 0; - break; - default: - console.error('Bad rotation value.'); - break; - } - CustomStyle.setProp('transform', textLayerDiv, - 'rotate(' + textAbsRotation + 'deg) ' + - 'scale(' + scale + ', ' + scale + ') ' + - 'translate(' + transX + ', ' + transY + ')'); - CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%'); - } - - if (redrawAnnotations && this.annotationLayer) { - setupAnnotations(div, this.pdfPage, this.viewport); - } - }; - - Object.defineProperty(this, 'width', { - get: function PageView_getWidth() { - return this.viewport.width; - }, - enumerable: true - }); - - Object.defineProperty(this, 'height', { - get: function PageView_getHeight() { - return this.viewport.height; - }, - enumerable: true - }); - - var self = this; - - function setupAnnotations(pageDiv, pdfPage, viewport) { - - function bindLink(link, dest) { - link.href = linkService.getDestinationHash(dest); - link.onclick = function pageViewSetupLinksOnclick() { - if (dest) { - linkService.navigateTo(dest); - } - return false; - }; - if (dest) { - link.className = 'internalLink'; - } - } - - function bindNamedAction(link, action) { - link.href = linkService.getAnchorUrl(''); - link.onclick = function pageViewSetupNamedActionOnClick() { - linkService.executeNamedAction(action); - return false; - }; - link.className = 'internalLink'; - } - - pdfPage.getAnnotations().then(function(annotationsData) { - viewport = viewport.clone({ dontFlip: true }); - var transform = viewport.transform; - var transformStr = 'matrix(' + transform.join(',') + ')'; - var data, element, i, ii; - - if (self.annotationLayer) { - // If an annotationLayer already exists, refresh its children's - // transformation matrices - for (i = 0, ii = annotationsData.length; i < ii; i++) { - data = annotationsData[i]; - element = self.annotationLayer.querySelector( - '[data-annotation-id="' + data.id + '"]'); - if (element) { - CustomStyle.setProp('transform', element, transformStr); - } - } - // See this.reset() - self.annotationLayer.removeAttribute('hidden'); - } else { - for (i = 0, ii = annotationsData.length; i < ii; i++) { - data = annotationsData[i]; - if (!data || !data.hasHtml) { - continue; - } - - element = PDFJS.AnnotationUtils.getHtmlElement(data, - pdfPage.commonObjs); - element.setAttribute('data-annotation-id', data.id); - mozL10n.translate(element); - - var rect = data.rect; - var view = pdfPage.view; - rect = PDFJS.Util.normalizeRect([ - rect[0], - view[3] - rect[1] + view[1], - rect[2], - view[3] - rect[3] + view[1] - ]); - element.style.left = rect[0] + 'px'; - element.style.top = rect[1] + 'px'; - element.style.position = 'absolute'; - - CustomStyle.setProp('transform', element, transformStr); - var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; - CustomStyle.setProp('transformOrigin', element, transformOriginStr); - - if (data.subtype === 'Link' && !data.url) { - var link = element.getElementsByTagName('a')[0]; - if (link) { - if (data.action) { - bindNamedAction(link, data.action); - } else { - bindLink(link, ('dest' in data) ? data.dest : null); - } - } - } - - if (!self.annotationLayer) { - var annotationLayerDiv = document.createElement('div'); - annotationLayerDiv.className = 'annotationLayer'; - pageDiv.appendChild(annotationLayerDiv); - self.annotationLayer = annotationLayerDiv; - } - - self.annotationLayer.appendChild(element); - } - } - }); + container.appendChild(anchor); + container.appendChild(div); } - this.getPagePoint = function pageViewGetPagePoint(x, y) { - return this.viewport.convertToPdfPoint(x, y); - }; + PDFPageView.prototype = { + setPdfPage: function PDFPageView_setPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, + totalRotation); + this.stats = pdfPage.stats; + this.reset(); + }, - this.draw = function pageviewDraw(callback) { - var pdfPage = this.pdfPage; + destroy: function PDFPageView_destroy() { + this.zoomLayer = null; + this.reset(); + if (this.pdfPage) { + this.pdfPage.destroy(); + } + }, - if (this.pagePdfPromise) { - return; - } - if (!pdfPage) { - var promise = this.pageSource.getPage(); - promise.then(function(pdfPage) { - delete this.pagePdfPromise; - this.setPdfPage(pdfPage); - this.draw(callback); - }.bind(this)); - this.pagePdfPromise = promise; - return; - } + reset: function PDFPageView_reset(keepAnnotations) { + if (this.renderTask) { + this.renderTask.cancel(); + } + this.resume = null; + this.renderingState = RenderingStates.INITIAL; - if (this.renderingState !== RenderingStates.INITIAL) { - console.error('Must be in new state before drawing'); - } + var div = this.div; + div.style.width = Math.floor(this.viewport.width) + 'px'; + div.style.height = Math.floor(this.viewport.height) + 'px'; - this.renderingState = RenderingStates.RUNNING; + var childNodes = div.childNodes; + for (var i = div.childNodes.length - 1; i >= 0; i--) { + var node = childNodes[i]; + if ((this.zoomLayer && this.zoomLayer === node) || + (keepAnnotations && this.annotationLayer === node)) { + continue; + } + div.removeChild(node); + } + div.removeAttribute('data-loaded'); - var viewport = this.viewport; - // Wrap the canvas so if it has a css transform for highdpi the overflow - // will be hidden in FF. - var canvasWrapper = document.createElement('div'); - canvasWrapper.style.width = div.style.width; - canvasWrapper.style.height = div.style.height; - canvasWrapper.classList.add('canvasWrapper'); - - var canvas = document.createElement('canvas'); - canvas.id = 'page' + this.id; - canvasWrapper.appendChild(canvas); - if (this.annotationLayer) { - // annotationLayer needs to stay on top - div.insertBefore(canvasWrapper, this.annotationLayer); - } else { - div.appendChild(canvasWrapper); - } - this.canvas = canvas; - - var ctx = canvas.getContext('2d'); - var outputScale = getOutputScale(ctx); - - if (PDFJS.useOnlyCssZoom) { - var actualSizeViewport = viewport.clone({ scale: CSS_UNITS }); - // Use a scale that will make the canvas be the original intended size - // of the page. - outputScale.sx *= actualSizeViewport.width / viewport.width; - outputScale.sy *= actualSizeViewport.height / viewport.height; - outputScale.scaled = true; - } - - if (PDFJS.maxCanvasPixels > 0) { - var pixelsInViewport = viewport.width * viewport.height; - var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); - if (outputScale.sx > maxScale || outputScale.sy > maxScale) { - outputScale.sx = maxScale; - outputScale.sy = maxScale; - outputScale.scaled = true; - this.hasRestrictedScaling = true; + if (keepAnnotations) { + if (this.annotationLayer) { + // Hide annotationLayer until all elements are resized + // so they are not displayed on the already-resized page + this.annotationLayer.setAttribute('hidden', 'true'); + } } else { - this.hasRestrictedScaling = false; - } - } - - canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; - canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; - canvas.style.width = Math.floor(viewport.width) + 'px'; - canvas.style.height = Math.floor(viewport.height) + 'px'; - // Add the viewport so it's known what it was originally drawn with. - canvas._viewport = viewport; - - var textLayerDiv = null; - var textLayer = null; - if (!PDFJS.disableTextLayer) { - textLayerDiv = document.createElement('div'); - textLayerDiv.className = 'textLayer'; - textLayerDiv.style.width = canvas.style.width; - textLayerDiv.style.height = canvas.style.height; - if (this.annotationLayer) { - // annotationLayer needs to stay on top - div.insertBefore(textLayerDiv, this.annotationLayer); - } else { - div.appendChild(textLayerDiv); + this.annotationLayer = null; } - textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1, - this.viewport); - } - this.textLayer = textLayer; - - // TODO(mack): use data attributes to store these - ctx._scaleX = outputScale.sx; - ctx._scaleY = outputScale.sy; - if (outputScale.scaled) { - ctx.scale(outputScale.sx, outputScale.sy); - } - - // Rendering area - - var self = this; - function pageViewDrawCallback(error) { - // The renderTask may have been replaced by a new one, so only remove the - // reference to the renderTask if it matches the one that is triggering - // this callback. - if (renderTask === self.renderTask) { - self.renderTask = null; + if (this.canvas) { + // Zeroing the width and height causes Firefox to release graphics + // resources immediately, which can greatly reduce memory consumption. + this.canvas.width = 0; + this.canvas.height = 0; + delete this.canvas; } - if (error === 'cancelled') { + this.loadingIconDiv = document.createElement('div'); + this.loadingIconDiv.className = 'loadingIcon'; + div.appendChild(this.loadingIconDiv); + }, + + update: function PDFPageView_update(scale, rotation) { + this.scale = scale || this.scale; + + if (typeof rotation !== 'undefined') { + this.rotation = rotation; + } + + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: this.scale * CSS_UNITS, + rotation: totalRotation + }); + + var isScalingRestricted = false; + if (this.canvas && PDFJS.maxCanvasPixels > 0) { + var ctx = this.canvas.getContext('2d'); + var outputScale = getOutputScale(ctx); + var pixelsInViewport = this.viewport.width * this.viewport.height; + var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); + if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) * + ((Math.floor(this.viewport.height) * outputScale.sy) | 0) > + PDFJS.maxCanvasPixels) { + isScalingRestricted = true; + } + } + + if (this.canvas && + (PDFJS.useOnlyCssZoom || + (this.hasRestrictedScaling && isScalingRestricted))) { + this.cssTransform(this.canvas, true); + return; + } else if (this.canvas && !this.zoomLayer) { + this.zoomLayer = this.canvas.parentNode; + this.zoomLayer.style.position = 'absolute'; + } + if (this.zoomLayer) { + this.cssTransform(this.zoomLayer.firstChild); + } + this.reset(true); + }, + + cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) { + // Scale canvas, canvas wrapper, and page container. + var width = this.viewport.width; + var height = this.viewport.height; + var div = this.div; + canvas.style.width = canvas.parentNode.style.width = div.style.width = + Math.floor(width) + 'px'; + canvas.style.height = canvas.parentNode.style.height = div.style.height = + Math.floor(height) + 'px'; + // The canvas may have been originally rotated, rotate relative to that. + var relativeRotation = this.viewport.rotation - canvas._viewport.rotation; + var absRotation = Math.abs(relativeRotation); + var scaleX = 1, scaleY = 1; + if (absRotation === 90 || absRotation === 270) { + // Scale x and y because of the rotation. + scaleX = height / width; + scaleY = width / height; + } + var cssTransform = 'rotate(' + relativeRotation + 'deg) ' + + 'scale(' + scaleX + ',' + scaleY + ')'; + CustomStyle.setProp('transform', canvas, cssTransform); + + if (this.textLayer) { + // Rotating the text layer is more complicated since the divs inside the + // the text layer are rotated. + // TODO: This could probably be simplified by drawing the text layer in + // one orientation then rotating overall. + var textLayerViewport = this.textLayer.viewport; + var textRelativeRotation = this.viewport.rotation - + textLayerViewport.rotation; + var textAbsRotation = Math.abs(textRelativeRotation); + var scale = width / textLayerViewport.width; + if (textAbsRotation === 90 || textAbsRotation === 270) { + scale = width / textLayerViewport.height; + } + var textLayerDiv = this.textLayer.textLayerDiv; + var transX, transY; + switch (textAbsRotation) { + case 0: + transX = transY = 0; + break; + case 90: + transX = 0; + transY = '-' + textLayerDiv.style.height; + break; + case 180: + transX = '-' + textLayerDiv.style.width; + transY = '-' + textLayerDiv.style.height; + break; + case 270: + transX = '-' + textLayerDiv.style.width; + transY = 0; + break; + default: + console.error('Bad rotation value.'); + break; + } + CustomStyle.setProp('transform', textLayerDiv, + 'rotate(' + textAbsRotation + 'deg) ' + + 'scale(' + scale + ', ' + scale + ') ' + + 'translate(' + transX + ', ' + transY + ')'); + CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%'); + } + + if (redrawAnnotations && this.annotationLayer) { + this.setupAnnotations(); + } + }, + + get width() { + return this.viewport.width; + }, + + get height() { + return this.viewport.height; + }, + + setupAnnotations: function PDFPageView_setupAnnotations() { + function bindLink(link, dest) { + link.href = linkService.getDestinationHash(dest); + link.onclick = function pageViewSetupLinksOnclick() { + if (dest) { + linkService.navigateTo(dest); + } + return false; + }; + if (dest) { + link.className = 'internalLink'; + } + } + + function bindNamedAction(link, action) { + link.href = linkService.getAnchorUrl(''); + link.onclick = function pageViewSetupNamedActionOnClick() { + linkService.executeNamedAction(action); + return false; + }; + link.className = 'internalLink'; + } + + var linkService = this.linkService; + var pageDiv = this.div; + var pdfPage = this.pdfPage; + var viewport = this.viewport; + var self = this; + + pdfPage.getAnnotations().then(function(annotationsData) { + viewport = viewport.clone({ dontFlip: true }); + var transform = viewport.transform; + var transformStr = 'matrix(' + transform.join(',') + ')'; + var data, element, i, ii; + + if (self.annotationLayer) { + // If an annotationLayer already exists, refresh its children's + // transformation matrices + for (i = 0, ii = annotationsData.length; i < ii; i++) { + data = annotationsData[i]; + element = self.annotationLayer.querySelector( + '[data-annotation-id="' + data.id + '"]'); + if (element) { + CustomStyle.setProp('transform', element, transformStr); + } + } + // See this.reset() + self.annotationLayer.removeAttribute('hidden'); + } else { + for (i = 0, ii = annotationsData.length; i < ii; i++) { + data = annotationsData[i]; + if (!data || !data.hasHtml) { + continue; + } + + element = PDFJS.AnnotationUtils.getHtmlElement(data, + pdfPage.commonObjs); + element.setAttribute('data-annotation-id', data.id); + mozL10n.translate(element); + + var rect = data.rect; + var view = pdfPage.view; + rect = PDFJS.Util.normalizeRect([ + rect[0], + view[3] - rect[1] + view[1], + rect[2], + view[3] - rect[3] + view[1] + ]); + element.style.left = rect[0] + 'px'; + element.style.top = rect[1] + 'px'; + element.style.position = 'absolute'; + + CustomStyle.setProp('transform', element, transformStr); + var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; + CustomStyle.setProp('transformOrigin', element, transformOriginStr); + + if (data.subtype === 'Link' && !data.url) { + var link = element.getElementsByTagName('a')[0]; + if (link) { + if (data.action) { + bindNamedAction(link, data.action); + } else { + bindLink(link, ('dest' in data) ? data.dest : null); + } + } + } + + if (!self.annotationLayer) { + var annotationLayerDiv = document.createElement('div'); + annotationLayerDiv.className = 'annotationLayer'; + pageDiv.appendChild(annotationLayerDiv); + self.annotationLayer = annotationLayerDiv; + } + + self.annotationLayer.appendChild(element); + } + } + }); + }, + + getPagePoint: function PDFPageView_getPagePoint(x, y) { + return this.viewport.convertToPdfPoint(x, y); + }, + + draw: function PDFPageView_draw(callback) { + var pdfPage = this.pdfPage; + + if (this.pagePdfPromise) { + return; + } + if (!pdfPage) { + var promise = this.pageSource.getPage(); + promise.then(function(pdfPage) { + delete this.pagePdfPromise; + this.setPdfPage(pdfPage); + this.draw(callback); + }.bind(this)); + this.pagePdfPromise = promise; return; } - self.renderingState = RenderingStates.FINISHED; - - if (self.loadingIconDiv) { - div.removeChild(self.loadingIconDiv); - delete self.loadingIconDiv; + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); } - if (self.zoomLayer) { - div.removeChild(self.zoomLayer); - self.zoomLayer = null; + this.renderingState = RenderingStates.RUNNING; + + var viewport = this.viewport; + var div = this.div; + // Wrap the canvas so if it has a css transform for highdpi the overflow + // will be hidden in FF. + var canvasWrapper = document.createElement('div'); + canvasWrapper.style.width = div.style.width; + canvasWrapper.style.height = div.style.height; + canvasWrapper.classList.add('canvasWrapper'); + + var canvas = document.createElement('canvas'); + canvas.id = 'page' + this.id; + canvasWrapper.appendChild(canvas); + if (this.annotationLayer) { + // annotationLayer needs to stay on top + div.insertBefore(canvasWrapper, this.annotationLayer); + } else { + div.appendChild(canvasWrapper); + } + this.canvas = canvas; + + var ctx = canvas.getContext('2d'); + var outputScale = getOutputScale(ctx); + + if (PDFJS.useOnlyCssZoom) { + var actualSizeViewport = viewport.clone({ scale: CSS_UNITS }); + // Use a scale that will make the canvas be the original intended size + // of the page. + outputScale.sx *= actualSizeViewport.width / viewport.width; + outputScale.sy *= actualSizeViewport.height / viewport.height; + outputScale.scaled = true; } - self.error = error; - self.stats = pdfPage.stats; - self.updateStats(); - if (self.onAfterDraw) { - self.onAfterDraw(); + if (PDFJS.maxCanvasPixels > 0) { + var pixelsInViewport = viewport.width * viewport.height; + var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); + if (outputScale.sx > maxScale || outputScale.sy > maxScale) { + outputScale.sx = maxScale; + outputScale.sy = maxScale; + outputScale.scaled = true; + this.hasRestrictedScaling = true; + } else { + this.hasRestrictedScaling = false; + } } - var event = document.createEvent('CustomEvent'); - event.initCustomEvent('pagerender', true, true, { - pageNumber: pdfPage.pageNumber - }); - div.dispatchEvent(event); + canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; + canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; + canvas.style.width = Math.floor(viewport.width) + 'px'; + canvas.style.height = Math.floor(viewport.height) + 'px'; + // Add the viewport so it's known what it was originally drawn with. + canvas._viewport = viewport; - callback(); - } + var textLayerDiv = null; + var textLayer = null; + if (!PDFJS.disableTextLayer) { + textLayerDiv = document.createElement('div'); + textLayerDiv.className = 'textLayer'; + textLayerDiv.style.width = canvas.style.width; + textLayerDiv.style.height = canvas.style.height; + if (this.annotationLayer) { + // annotationLayer needs to stay on top + div.insertBefore(textLayerDiv, this.annotationLayer); + } else { + div.appendChild(textLayerDiv); + } - var renderContext = { - canvasContext: ctx, - viewport: this.viewport, - // intent: 'default', // === 'display' - continueCallback: function pdfViewcContinueCallback(cont) { - if (!self.renderingQueue.isHighestPriority(self)) { - self.renderingState = RenderingStates.PAUSED; - self.resume = function resumeCallback() { - self.renderingState = RenderingStates.RUNNING; - cont(); - }; + textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, + this.id - 1, + this.viewport); + } + this.textLayer = textLayer; + + // TODO(mack): use data attributes to store these + ctx._scaleX = outputScale.sx; + ctx._scaleY = outputScale.sy; + if (outputScale.scaled) { + ctx.scale(outputScale.sx, outputScale.sy); + } + + // Rendering area + + var self = this; + function pageViewDrawCallback(error) { + // The renderTask may have been replaced by a new one, so only remove + // the reference to the renderTask if it matches the one that is + // triggering this callback. + if (renderTask === self.renderTask) { + self.renderTask = null; + } + + if (error === 'cancelled') { return; } - cont(); - } - }; - var renderTask = this.renderTask = this.pdfPage.render(renderContext); - this.renderTask.promise.then( - function pdfPageRenderCallback() { - pageViewDrawCallback(null); - if (textLayer) { - self.pdfPage.getTextContent().then( - function textContentResolved(textContent) { - textLayer.setTextContent(textContent); - } - ); + self.renderingState = RenderingStates.FINISHED; + + if (self.loadingIconDiv) { + div.removeChild(self.loadingIconDiv); + delete self.loadingIconDiv; } - }, - function pdfPageRenderError(error) { - pageViewDrawCallback(error); + + if (self.zoomLayer) { + div.removeChild(self.zoomLayer); + self.zoomLayer = null; + } + + self.error = error; + self.stats = pdfPage.stats; + self.updateStats(); + if (self.onAfterDraw) { + self.onAfterDraw(); + } + + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('pagerender', true, true, { + pageNumber: pdfPage.pageNumber + }); + div.dispatchEvent(event); + + callback(); } - ); - - setupAnnotations(div, pdfPage, this.viewport); - div.setAttribute('data-loaded', true); - - // Add the page to the cache at the start of drawing. That way it can be - // evicted from the cache and destroyed even if we pause its rendering. - cache.push(this); - }; - - this.beforePrint = function pageViewBeforePrint() { - var pdfPage = this.pdfPage; - - var viewport = pdfPage.getViewport(1); - // Use the same hack we use for high dpi displays for printing to get better - // output until bug 811002 is fixed in FF. - var PRINT_OUTPUT_SCALE = 2; - var canvas = document.createElement('canvas'); - canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; - canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; - canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt'; - canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt'; - var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' + - (1 / PRINT_OUTPUT_SCALE) + ')'; - CustomStyle.setProp('transform' , canvas, cssScale); - CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); - - var printContainer = document.getElementById('printContainer'); - var canvasWrapper = document.createElement('div'); - canvasWrapper.style.width = viewport.width + 'pt'; - canvasWrapper.style.height = viewport.height + 'pt'; - canvasWrapper.appendChild(canvas); - printContainer.appendChild(canvasWrapper); - - canvas.mozPrintCallback = function(obj) { - var ctx = obj.context; - - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE); var renderContext = { canvasContext: ctx, - viewport: viewport, - intent: 'print' - }; - - pdfPage.render(renderContext).promise.then(function() { - // Tell the printEngine that rendering this canvas/page has finished. - obj.done(); - }, function(error) { - console.error(error); - // Tell the printEngine that rendering this canvas/page has failed. - // This will make the print proces stop. - if ('abort' in obj) { - obj.abort(); - } else { - obj.done(); + viewport: this.viewport, + // intent: 'default', // === 'display' + continueCallback: function pdfViewcContinueCallback(cont) { + if (!self.renderingQueue.isHighestPriority(self)) { + self.renderingState = RenderingStates.PAUSED; + self.resume = function resumeCallback() { + self.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); } - }); - }; + }; + var renderTask = this.renderTask = this.pdfPage.render(renderContext); + + this.renderTask.promise.then( + function pdfPageRenderCallback() { + pageViewDrawCallback(null); + if (textLayer) { + self.pdfPage.getTextContent().then( + function textContentResolved(textContent) { + textLayer.setTextContent(textContent); + } + ); + } + }, + function pdfPageRenderError(error) { + pageViewDrawCallback(error); + } + ); + + this.setupAnnotations(); + div.setAttribute('data-loaded', true); + + // Add the page to the cache at the start of drawing. That way it can be + // evicted from the cache and destroyed even if we pause its rendering. + this.cache.push(this); + }, + + beforePrint: function PDFPageView_beforePrint() { + var pdfPage = this.pdfPage; + + var viewport = pdfPage.getViewport(1); + // Use the same hack we use for high dpi displays for printing to get + // better output until bug 811002 is fixed in FF. + var PRINT_OUTPUT_SCALE = 2; + var canvas = document.createElement('canvas'); + canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; + canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; + canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt'; + canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt'; + var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' + + (1 / PRINT_OUTPUT_SCALE) + ')'; + CustomStyle.setProp('transform' , canvas, cssScale); + CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); + + var printContainer = document.getElementById('printContainer'); + var canvasWrapper = document.createElement('div'); + canvasWrapper.style.width = viewport.width + 'pt'; + canvasWrapper.style.height = viewport.height + 'pt'; + canvasWrapper.appendChild(canvas); + printContainer.appendChild(canvasWrapper); + + canvas.mozPrintCallback = function(obj) { + var ctx = obj.context; + + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE); + + var renderContext = { + canvasContext: ctx, + viewport: viewport, + intent: 'print' + }; + + pdfPage.render(renderContext).promise.then(function() { + // Tell the printEngine that rendering this canvas/page has finished. + obj.done(); + }, function(error) { + console.error(error); + // Tell the printEngine that rendering this canvas/page has failed. + // This will make the print proces stop. + if ('abort' in obj) { + obj.abort(); + } else { + obj.done(); + } + }); + }; + }, + + updateStats: function PDFPageView_updateStats() { + if (!this.stats) { + return; + } + + if (PDFJS.pdfBug && Stats.enabled) { + var stats = this.stats; + Stats.add(this.id, stats); + } + }, }; - this.updateStats = function pageViewUpdateStats() { - if (!this.stats) { - return; - } - - if (PDFJS.pdfBug && Stats.enabled) { - var stats = this.stats; - Stats.add(this.id, stats); - } - }; -}; + return PDFPageView; +})(); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 190c37ed4..bd87a941a 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PageView, UNKNOWN_SCALE, + /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PDFPageView, UNKNOWN_SCALE, SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS, DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates, PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue */ @@ -236,10 +236,17 @@ var PDFViewer = (function pdfViewer() { var viewport = pdfPage.getViewport(scale * CSS_UNITS); for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { var pageSource = new PDFPageSource(pdfDocument, pageNum); - var pageView = new PageView(this.viewer, pageNum, scale, - viewport.clone(), this.linkService, - this.renderingQueue, this.cache, - pageSource, this); + var pageView = new PDFPageView({ + container: this.viewer, + id: pageNum, + scale: scale, + defaultViewport: viewport.clone(), + linkService: this.linkService, + renderingQueue: this.renderingQueue, + cache: this.cache, + pageSource: pageSource, + viewer: this + }); bindOnAfterDraw(pageView); this.pages.push(pageView); } @@ -398,7 +405,6 @@ var PDFViewer = (function pdfViewer() { scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber, dest) { var pageView = this.pages[pageNumber - 1]; - var pageViewDiv = pageView.el; if (this.presentationModeState === PresentationModeState.FULLSCREEN) { @@ -412,7 +418,7 @@ var PDFViewer = (function pdfViewer() { this._setScale(this.currentScaleValue, true); } if (!dest) { - scrollIntoView(pageViewDiv); + scrollIntoView(pageView.div); return; } @@ -475,7 +481,7 @@ var PDFViewer = (function pdfViewer() { } if (scale === 'page-fit' && !dest[4]) { - scrollIntoView(pageViewDiv); + scrollIntoView(pageView.div); return; } @@ -486,7 +492,7 @@ var PDFViewer = (function pdfViewer() { var left = Math.min(boundingRect[0][0], boundingRect[1][0]); var top = Math.min(boundingRect[0][1], boundingRect[1][1]); - scrollIntoView(pageViewDiv, { left: left, top: top }); + scrollIntoView(pageView.div, { left: left, top: top }); }, _updateLocation: function (firstPage) { diff --git a/web/viewer.js b/web/viewer.js index 439fb0106..5528dd36c 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -16,7 +16,7 @@ */ /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar, DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL, - PDFHistory, Preferences, SidebarView, ViewHistory, PageView, + PDFHistory, Preferences, SidebarView, ViewHistory, PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar, PasswordPrompt, PresentationMode, HandTool, Promise, DocumentProperties, DocumentOutlineView, DocumentAttachmentsView, @@ -1353,7 +1353,6 @@ var PDFViewerApplication = { rotatePages: function pdfViewRotatePages(delta) { var pageNumber = this.page; - this.pageRotation = (this.pageRotation + 360 + delta) % 360; this.pdfViewer.pagesRotation = this.pageRotation; this.pdfThumbnailViewer.pagesRotation = this.pageRotation; From 7663942ee5ac344326b4b32a1e6e333209ab1e34 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Sun, 28 Sep 2014 09:35:33 -0500 Subject: [PATCH 03/10] Creates IPDFTextLayerFactory interface --- web/interfaces.js | 14 ++++++++++++++ web/pdf_page_view.js | 14 +++++++------- web/pdf_viewer.js | 12 ++++++++---- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/web/interfaces.js b/web/interfaces.js index 5f0ad04c6..91ac65d9b 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -83,3 +83,17 @@ ILastScrollSource.prototype = { */ get lastScroll() {}, }; + +/** + * @interface + */ +function IPDFTextLayerFactory() {} +IPDFTextLayerFactory.prototype = { + /** + * @param {HTMLDivElement} textLayerDiv + * @param {number} pageIndex + * @param {PageViewport} viewport + * @returns {TextLayerBuilder} + */ + createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {} +}; diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 7e6aa17b3..d0a081827 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -29,7 +29,7 @@ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. * @property {Cache} cache - The page cache. * @property {PDFPageSource} pageSource - * @property {PDFViewer} viewer + * @property {IPDFTextLayerFactory} textLayerFactory */ /** @@ -50,7 +50,7 @@ var PDFPageView = (function PDFPageViewClosure() { var renderingQueue = options.renderingQueue; var cache = options.cache; var pageSource = options.pageSource; - var viewer = options.viewer; + var textLayerFactory = options.textLayerFactory; this.id = id; this.renderingId = 'page' + id; @@ -65,7 +65,7 @@ var PDFPageView = (function PDFPageViewClosure() { this.renderingQueue = renderingQueue; this.cache = cache; this.pageSource = pageSource; - this.viewer = viewer; + this.textLayerFactory = textLayerFactory; this.renderingState = RenderingStates.INITIAL; this.resume = null; @@ -454,7 +454,7 @@ var PDFPageView = (function PDFPageViewClosure() { var textLayerDiv = null; var textLayer = null; - if (!PDFJS.disableTextLayer) { + if (this.textLayerFactory) { textLayerDiv = document.createElement('div'); textLayerDiv.className = 'textLayer'; textLayerDiv.style.width = canvas.style.width; @@ -466,9 +466,9 @@ var PDFPageView = (function PDFPageViewClosure() { div.appendChild(textLayerDiv); } - textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, - this.id - 1, - this.viewport); + textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv, + this.id - 1, + this.viewport); } this.textLayer = textLayer; diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index bd87a941a..292d765cd 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -236,6 +236,10 @@ var PDFViewer = (function pdfViewer() { var viewport = pdfPage.getViewport(scale * CSS_UNITS); for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { var pageSource = new PDFPageSource(pdfDocument, pageNum); + var textLayerFactory = null; + if (!PDFJS.disableTextLayer) { + textLayerFactory = this; + } var pageView = new PDFPageView({ container: this.viewer, id: pageNum, @@ -245,7 +249,7 @@ var PDFViewer = (function pdfViewer() { renderingQueue: this.renderingQueue, cache: this.cache, pageSource: pageSource, - viewer: this + textLayerFactory: textLayerFactory }); bindOnAfterDraw(pageView); this.pages.push(pageView); @@ -629,9 +633,9 @@ var PDFViewer = (function pdfViewer() { }, /** - * @param textLayerDiv {HTMLDivElement} - * @param pageIndex {number} - * @param viewport {PageViewport} + * @param {HTMLDivElement} textLayerDiv + * @param {number} pageIndex + * @param {PageViewport} viewport * @returns {TextLayerBuilder} */ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) { From fe4ac8678198937aca49c02fb7d2e83de183cd0e Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Mon, 29 Sep 2014 08:11:46 -0500 Subject: [PATCH 04/10] Removes PDFPageSource --- web/pdf_page_view.js | 20 +------------- web/pdf_viewer.js | 58 +++++++++++++++++++--------------------- web/thumbnail_view.js | 61 +++++++++++++++++++++++++------------------ web/viewer.js | 2 +- 4 files changed, 64 insertions(+), 77 deletions(-) diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index d0a081827..38556efc6 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -28,7 +28,6 @@ * @property {IPDFLinkService} linkService - The navigation/linking service. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. * @property {Cache} cache - The page cache. - * @property {PDFPageSource} pageSource * @property {IPDFTextLayerFactory} textLayerFactory */ @@ -49,7 +48,6 @@ var PDFPageView = (function PDFPageViewClosure() { var linkService = options.linkService; var renderingQueue = options.renderingQueue; var cache = options.cache; - var pageSource = options.pageSource; var textLayerFactory = options.textLayerFactory; this.id = id; @@ -64,7 +62,6 @@ var PDFPageView = (function PDFPageViewClosure() { this.linkService = linkService; this.renderingQueue = renderingQueue; this.cache = cache; - this.pageSource = pageSource; this.textLayerFactory = textLayerFactory; this.renderingState = RenderingStates.INITIAL; @@ -378,28 +375,13 @@ var PDFPageView = (function PDFPageViewClosure() { }, draw: function PDFPageView_draw(callback) { - var pdfPage = this.pdfPage; - - if (this.pagePdfPromise) { - return; - } - if (!pdfPage) { - var promise = this.pageSource.getPage(); - promise.then(function(pdfPage) { - delete this.pagePdfPromise; - this.setPdfPage(pdfPage); - this.draw(callback); - }.bind(this)); - this.pagePdfPromise = promise; - return; - } - if (this.renderingState !== RenderingStates.INITIAL) { console.error('Must be in new state before drawing'); } this.renderingState = RenderingStates.RUNNING; + var pdfPage = this.pdfPage; var viewport = this.viewport; var div = this.div; // Wrap the canvas so if it has a css transform for highdpi the overflow diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 292d765cd..6441eae9e 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -235,7 +235,6 @@ var PDFViewer = (function pdfViewer() { var scale = this._currentScale || 1.0; var viewport = pdfPage.getViewport(scale * CSS_UNITS); for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { - var pageSource = new PDFPageSource(pdfDocument, pageNum); var textLayerFactory = null; if (!PDFJS.disableTextLayer) { textLayerFactory = this; @@ -248,7 +247,6 @@ var PDFViewer = (function pdfViewer() { linkService: this.linkService, renderingQueue: this.renderingQueue, cache: this.cache, - pageSource: pageSource, textLayerFactory: textLayerFactory }); bindOnAfterDraw(pageView); @@ -299,6 +297,7 @@ var PDFViewer = (function pdfViewer() { this._currentScaleValue = null; this.location = null; this._pagesRotation = 0; + this._pagesRequests = []; var container = this.viewer; while (container.hasChildNodes()) { @@ -614,13 +613,38 @@ var PDFViewer = (function pdfViewer() { } }, + /** + * @param {PDFPageView} pageView + * @returns {PDFPage} + * @private + */ + _ensurePdfPageLoaded: function (pageView) { + if (pageView.pdfPage) { + return Promise.resolve(pageView.pdfPage); + } + var pageNumber = pageView.id; + if (this._pagesRequests[pageNumber]) { + return this._pagesRequests[pageNumber]; + } + var promise = this.pdfDocument.getPage(pageNumber).then( + function (pdfPage) { + pageView.setPdfPage(pdfPage); + this._pagesRequests[pageNumber] = null; + return pdfPage; + }.bind(this)); + this._pagesRequests[pageNumber] = promise; + return promise; + }, + forceRendering: function (currentlyVisiblePages) { var visiblePages = currentlyVisiblePages || this._getVisiblePages(); var pageView = this.renderingQueue.getHighestPriority(visiblePages, this.pages, this.scroll.down); if (pageView) { - this.renderingQueue.renderView(pageView); + this._ensurePdfPageLoaded(pageView).then(function () { + this.renderingQueue.renderView(pageView); + }.bind(this)); return true; } return false; @@ -705,31 +729,3 @@ var SimpleLinkService = (function SimpleLinkServiceClosure() { }; return SimpleLinkService; })(); - -/** - * PDFPage object source. - * @class - */ -var PDFPageSource = (function PDFPageSourceClosure() { - /** - * @constructs - * @param {PDFDocument} pdfDocument - * @param {number} pageNumber - * @constructor - */ - function PDFPageSource(pdfDocument, pageNumber) { - this.pdfDocument = pdfDocument; - this.pageNumber = pageNumber; - } - - PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ { - /** - * @returns {Promise} - */ - getPage: function () { - return this.pdfDocument.getPage(this.pageNumber); - } - }; - - return PDFPageSource; -})(); diff --git a/web/thumbnail_view.js b/web/thumbnail_view.js index 1e16a7de4..f8c207ee9 100644 --- a/web/thumbnail_view.js +++ b/web/thumbnail_view.js @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals mozL10n, RenderingStates, Promise, scrollIntoView, PDFPageSource, +/* globals mozL10n, RenderingStates, Promise, scrollIntoView, watchScroll, getVisibleElements */ 'use strict'; @@ -28,13 +28,11 @@ var THUMBNAIL_SCROLL_MARGIN = -19; * @param defaultViewport * @param linkService * @param renderingQueue - * @param pageSource * * @implements {IRenderableView} */ var ThumbnailView = function thumbnailView(container, id, defaultViewport, - linkService, renderingQueue, - pageSource) { + linkService, renderingQueue) { var anchor = document.createElement('a'); anchor.href = linkService.getAnchorUrl('#page=' + id); anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); @@ -80,7 +78,6 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport, this.hasImage = false; this.renderingState = RenderingStates.INITIAL; this.renderingQueue = renderingQueue; - this.pageSource = pageSource; this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) { this.pdfPage = pdfPage; @@ -143,15 +140,6 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport, }; this.draw = function thumbnailViewDraw(callback) { - if (!this.pdfPage) { - var promise = this.pageSource.getPage(this.id); - promise.then(function(pdfPage) { - this.setPdfPage(pdfPage); - this.draw(callback); - }.bind(this)); - return; - } - if (this.renderingState !== RenderingStates.INITIAL) { console.error('Must be in new state before drawing'); } @@ -204,18 +192,14 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport, return tempCanvas; } - this.setImage = function thumbnailViewSetImage(img) { - if (!this.pdfPage) { - var promise = this.pageSource.getPage(); - promise.then(function(pdfPage) { - this.setPdfPage(pdfPage); - this.setImage(img); - }.bind(this)); - return; - } + this.setImage = function thumbnailViewSetImage(pageView) { + var img = pageView.canvas; if (this.hasImage || !img) { return; } + if (this.pdfPage) { + this.setPdfPage(pageView.pdfPage); + } this.renderingState = RenderingStates.FINISHED; var ctx = this.getPageDrawContext(); @@ -330,6 +314,7 @@ var PDFThumbnailViewer = (function pdfThumbnailViewer() { _resetView: function () { this.thumbnails = []; this._pagesRotation = 0; + this._pagesRequests = []; }, setDocument: function (pdfDocument) { @@ -351,15 +336,37 @@ var PDFThumbnailViewer = (function pdfThumbnailViewer() { var pagesCount = pdfDocument.numPages; var viewport = firstPage.getViewport(1.0); for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { - var pageSource = new PDFPageSource(pdfDocument, pageNum); var thumbnail = new ThumbnailView(this.container, pageNum, viewport.clone(), this.linkService, - this.renderingQueue, pageSource); + this.renderingQueue); this.thumbnails.push(thumbnail); } }.bind(this)); }, + /** + * @param {PDFPageView} pageView + * @returns {PDFPage} + * @private + */ + _ensurePdfPageLoaded: function (thumbView) { + if (thumbView.pdfPage) { + return Promise.resolve(thumbView.pdfPage); + } + var pageNumber = thumbView.id; + if (this._pagesRequests[pageNumber]) { + return this._pagesRequests[pageNumber]; + } + var promise = this.pdfDocument.getPage(pageNumber).then( + function (pdfPage) { + thumbView.setPdfPage(pdfPage); + this._pagesRequests[pageNumber] = null; + return pdfPage; + }.bind(this)); + this._pagesRequests[pageNumber] = promise; + return promise; + }, + ensureThumbnailVisible: function PDFThumbnailViewer_ensureThumbnailVisible(page) { // Ensure that the thumbnail of the current page is visible @@ -373,7 +380,9 @@ var PDFThumbnailViewer = (function pdfThumbnailViewer() { this.thumbnails, this.scroll.down); if (thumbView) { - this.renderingQueue.renderView(thumbView); + this._ensurePdfPageLoaded(thumbView).then(function () { + this.renderingQueue.renderView(thumbView); + }.bind(this)); return true; } return false; diff --git a/web/viewer.js b/web/viewer.js index 5528dd36c..802d554dd 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1734,7 +1734,7 @@ document.addEventListener('pagerendered', function (e) { var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex); var thumbnailView = PDFViewerApplication.pdfThumbnailViewer. getThumbnail(pageIndex); - thumbnailView.setImage(pageView.canvas); + thumbnailView.setImage(pageView); //#if (FIREFOX || MOZCENTRAL) //if (pageView.textLayer && pageView.textLayer.textDivs && From 9f384bbb41f780b258d832d1c7170b570aedac95 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Mon, 29 Sep 2014 11:05:28 -0500 Subject: [PATCH 05/10] Creates AnnotationsLayerBuilder. --- web/annotations_layer_builder.js | 156 +++++++++++++++++++++++++++++++ web/interfaces.js | 13 +++ web/pdf_page_view.js | 132 ++++---------------------- web/pdf_viewer.js | 21 ++++- web/viewer.html | 1 + 5 files changed, 208 insertions(+), 115 deletions(-) create mode 100644 web/annotations_layer_builder.js diff --git a/web/annotations_layer_builder.js b/web/annotations_layer_builder.js new file mode 100644 index 000000000..309f7c033 --- /dev/null +++ b/web/annotations_layer_builder.js @@ -0,0 +1,156 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* Copyright 2014 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. + */ +/*globals PDFJS, CustomStyle, mozL10n */ + +'use strict'; + +/** + * @typedef {Object} AnnotationsLayerBuilderOptions + * @property {HTMLDivElement} pageDiv + * @property {PDFPage} pdfPage + * @property {IPDFLinkService} linkService + */ + +/** + * @class + */ +var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() { + /** + * @param {AnnotationsLayerBuilderOptions} options + * @constructs AnnotationsLayerBuilder + */ + function AnnotationsLayerBuilder(options) { + this.pageDiv = options.pageDiv; + this.pdfPage = options.pdfPage; + this.linkService = options.linkService; + + this.div = null; + } + AnnotationsLayerBuilder.prototype = + /** @lends AnnotationsLayerBuilder.prototype */ { + + /** + * @param {PageViewport} viewport + */ + setupAnnotations: + function AnnotationsLayerBuilder_setupAnnotations(viewport) { + function bindLink(link, dest) { + link.href = linkService.getDestinationHash(dest); + link.onclick = function annotationsLayerBuilderLinksOnclick() { + if (dest) { + linkService.navigateTo(dest); + } + return false; + }; + if (dest) { + link.className = 'internalLink'; + } + } + + function bindNamedAction(link, action) { + link.href = linkService.getAnchorUrl(''); + link.onclick = function annotationsLayerBuilderNamedActionOnClick() { + linkService.executeNamedAction(action); + return false; + }; + link.className = 'internalLink'; + } + + var linkService = this.linkService; + var pdfPage = this.pdfPage; + var self = this; + + pdfPage.getAnnotations().then(function (annotationsData) { + viewport = viewport.clone({ dontFlip: true }); + var transform = viewport.transform; + var transformStr = 'matrix(' + transform.join(',') + ')'; + var data, element, i, ii; + + if (self.div) { + // If an annotationLayer already exists, refresh its children's + // transformation matrices + for (i = 0, ii = annotationsData.length; i < ii; i++) { + data = annotationsData[i]; + element = self.div.querySelector( + '[data-annotation-id="' + data.id + '"]'); + if (element) { + CustomStyle.setProp('transform', element, transformStr); + } + } + // See PDFPageView.reset() + self.div.removeAttribute('hidden'); + } else { + for (i = 0, ii = annotationsData.length; i < ii; i++) { + data = annotationsData[i]; + if (!data || !data.hasHtml) { + continue; + } + + element = PDFJS.AnnotationUtils.getHtmlElement(data, + pdfPage.commonObjs); + element.setAttribute('data-annotation-id', data.id); + mozL10n.translate(element); + + var rect = data.rect; + var view = pdfPage.view; + rect = PDFJS.Util.normalizeRect([ + rect[0], + view[3] - rect[1] + view[1], + rect[2], + view[3] - rect[3] + view[1] + ]); + element.style.left = rect[0] + 'px'; + element.style.top = rect[1] + 'px'; + element.style.position = 'absolute'; + + CustomStyle.setProp('transform', element, transformStr); + var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; + CustomStyle.setProp('transformOrigin', element, transformOriginStr); + + if (data.subtype === 'Link' && !data.url) { + var link = element.getElementsByTagName('a')[0]; + if (link) { + if (data.action) { + bindNamedAction(link, data.action); + } else { + bindLink(link, ('dest' in data) ? data.dest : null); + } + } + } + + if (!self.div) { + var annotationLayerDiv = document.createElement('div'); + annotationLayerDiv.className = 'annotationLayer'; + self.pageDiv.appendChild(annotationLayerDiv); + self.div = annotationLayerDiv; + } + + self.div.appendChild(element); + } + } + }); + }, + + hide: function () { + if (!this.div) { + return; + } + this.div.setAttribute('hidden', 'true'); + } + }; + return AnnotationsLayerBuilder; +})(); diff --git a/web/interfaces.js b/web/interfaces.js index 91ac65d9b..1cf9ee8d7 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -97,3 +97,16 @@ IPDFTextLayerFactory.prototype = { */ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {} }; + +/** + * @interface + */ +function IPDFAnnotationsLayerFactory() {} +IPDFAnnotationsLayerFactory.prototype = { + /** + * @param {HTMLDivElement} pageDiv + * @param {PDFPage} pdfPage + * @returns {AnnotationsLayerBuilder} + */ + createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {} +}; diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 38556efc6..87228c3e5 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -14,8 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals RenderingStates, PDFJS, mozL10n, CustomStyle, getOutputScale, Stats, - CSS_UNITS */ +/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale, + Stats */ 'use strict'; @@ -25,10 +25,10 @@ * @property {number} id - The page unique ID (normally its number). * @property {number} scale - The page scale display. * @property {PageViewport} defaultViewport - The page viewport. - * @property {IPDFLinkService} linkService - The navigation/linking service. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. * @property {Cache} cache - The page cache. * @property {IPDFTextLayerFactory} textLayerFactory + * @property {IPDFAnnotationsLayerFactory} annotationsLayerFactory */ /** @@ -45,10 +45,10 @@ var PDFPageView = (function PDFPageViewClosure() { var id = options.id; var scale = options.scale; var defaultViewport = options.defaultViewport; - var linkService = options.linkService; var renderingQueue = options.renderingQueue; var cache = options.cache; var textLayerFactory = options.textLayerFactory; + var annotationsLayerFactory = options.annotationsLayerFactory; this.id = id; this.renderingId = 'page' + id; @@ -59,10 +59,10 @@ var PDFPageView = (function PDFPageViewClosure() { this.pdfPageRotate = defaultViewport.rotation; this.hasRestrictedScaling = false; - this.linkService = linkService; this.renderingQueue = renderingQueue; this.cache = cache; this.textLayerFactory = textLayerFactory; + this.annotationsLayerFactory = annotationsLayerFactory; this.renderingState = RenderingStates.INITIAL; this.resume = null; @@ -119,10 +119,12 @@ var PDFPageView = (function PDFPageViewClosure() { div.style.height = Math.floor(this.viewport.height) + 'px'; var childNodes = div.childNodes; + var currentZoomLayer = this.zoomLayer || null; + var currentAnnotationNode = (keepAnnotations && this.annotationLayer && + this.annotationLayer.div) || null; for (var i = div.childNodes.length - 1; i >= 0; i--) { var node = childNodes[i]; - if ((this.zoomLayer && this.zoomLayer === node) || - (keepAnnotations && this.annotationLayer === node)) { + if (currentZoomLayer === node || currentAnnotationNode === node) { continue; } div.removeChild(node); @@ -133,7 +135,7 @@ var PDFPageView = (function PDFPageViewClosure() { if (this.annotationLayer) { // Hide annotationLayer until all elements are resized // so they are not displayed on the already-resized page - this.annotationLayer.setAttribute('hidden', 'true'); + this.annotationLayer.hide(); } } else { this.annotationLayer = null; @@ -258,7 +260,7 @@ var PDFPageView = (function PDFPageViewClosure() { } if (redrawAnnotations && this.annotationLayer) { - this.setupAnnotations(); + this.annotationLayer.setupAnnotations(this.viewport); } }, @@ -270,106 +272,6 @@ var PDFPageView = (function PDFPageViewClosure() { return this.viewport.height; }, - setupAnnotations: function PDFPageView_setupAnnotations() { - function bindLink(link, dest) { - link.href = linkService.getDestinationHash(dest); - link.onclick = function pageViewSetupLinksOnclick() { - if (dest) { - linkService.navigateTo(dest); - } - return false; - }; - if (dest) { - link.className = 'internalLink'; - } - } - - function bindNamedAction(link, action) { - link.href = linkService.getAnchorUrl(''); - link.onclick = function pageViewSetupNamedActionOnClick() { - linkService.executeNamedAction(action); - return false; - }; - link.className = 'internalLink'; - } - - var linkService = this.linkService; - var pageDiv = this.div; - var pdfPage = this.pdfPage; - var viewport = this.viewport; - var self = this; - - pdfPage.getAnnotations().then(function(annotationsData) { - viewport = viewport.clone({ dontFlip: true }); - var transform = viewport.transform; - var transformStr = 'matrix(' + transform.join(',') + ')'; - var data, element, i, ii; - - if (self.annotationLayer) { - // If an annotationLayer already exists, refresh its children's - // transformation matrices - for (i = 0, ii = annotationsData.length; i < ii; i++) { - data = annotationsData[i]; - element = self.annotationLayer.querySelector( - '[data-annotation-id="' + data.id + '"]'); - if (element) { - CustomStyle.setProp('transform', element, transformStr); - } - } - // See this.reset() - self.annotationLayer.removeAttribute('hidden'); - } else { - for (i = 0, ii = annotationsData.length; i < ii; i++) { - data = annotationsData[i]; - if (!data || !data.hasHtml) { - continue; - } - - element = PDFJS.AnnotationUtils.getHtmlElement(data, - pdfPage.commonObjs); - element.setAttribute('data-annotation-id', data.id); - mozL10n.translate(element); - - var rect = data.rect; - var view = pdfPage.view; - rect = PDFJS.Util.normalizeRect([ - rect[0], - view[3] - rect[1] + view[1], - rect[2], - view[3] - rect[3] + view[1] - ]); - element.style.left = rect[0] + 'px'; - element.style.top = rect[1] + 'px'; - element.style.position = 'absolute'; - - CustomStyle.setProp('transform', element, transformStr); - var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; - CustomStyle.setProp('transformOrigin', element, transformOriginStr); - - if (data.subtype === 'Link' && !data.url) { - var link = element.getElementsByTagName('a')[0]; - if (link) { - if (data.action) { - bindNamedAction(link, data.action); - } else { - bindLink(link, ('dest' in data) ? data.dest : null); - } - } - } - - if (!self.annotationLayer) { - var annotationLayerDiv = document.createElement('div'); - annotationLayerDiv.className = 'annotationLayer'; - pageDiv.appendChild(annotationLayerDiv); - self.annotationLayer = annotationLayerDiv; - } - - self.annotationLayer.appendChild(element); - } - } - }); - }, - getPagePoint: function PDFPageView_getPagePoint(x, y) { return this.viewport.convertToPdfPoint(x, y); }, @@ -396,7 +298,7 @@ var PDFPageView = (function PDFPageViewClosure() { canvasWrapper.appendChild(canvas); if (this.annotationLayer) { // annotationLayer needs to stay on top - div.insertBefore(canvasWrapper, this.annotationLayer); + div.insertBefore(canvasWrapper, this.annotationLayer.div); } else { div.appendChild(canvasWrapper); } @@ -443,7 +345,7 @@ var PDFPageView = (function PDFPageViewClosure() { textLayerDiv.style.height = canvas.style.height; if (this.annotationLayer) { // annotationLayer needs to stay on top - div.insertBefore(textLayerDiv, this.annotationLayer); + div.insertBefore(textLayerDiv, this.annotationLayer.div); } else { div.appendChild(textLayerDiv); } @@ -538,7 +440,13 @@ var PDFPageView = (function PDFPageViewClosure() { } ); - this.setupAnnotations(); + if (this.annotationsLayerFactory) { + if (!this.annotationLayer) { + this.annotationLayer = this.annotationsLayerFactory. + createAnnotationsLayerBuilder(div, this.pdfPage); + } + this.annotationLayer.setupAnnotations(this.viewport); + } div.setAttribute('data-loaded', true); // Add the page to the cache at the start of drawing. That way it can be diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 6441eae9e..84483224c 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -17,7 +17,8 @@ /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PDFPageView, UNKNOWN_SCALE, SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS, DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates, - PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue */ + PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue, + AnnotationsLayerBuilder */ 'use strict'; @@ -33,6 +34,7 @@ var IGNORE_CURRENT_POSITION_ON_ZOOM = false; //#include pdf_rendering_queue.js //#include pdf_page_view.js //#include text_layer_builder.js +//#include annotations_layer_builder.js /** * @typedef {Object} PDFViewerOptions @@ -244,10 +246,10 @@ var PDFViewer = (function pdfViewer() { id: pageNum, scale: scale, defaultViewport: viewport.clone(), - linkService: this.linkService, renderingQueue: this.renderingQueue, cache: this.cache, - textLayerFactory: textLayerFactory + textLayerFactory: textLayerFactory, + annotationsLayerFactory: this }); bindOnAfterDraw(pageView); this.pages.push(pageView); @@ -675,6 +677,19 @@ var PDFViewer = (function pdfViewer() { }); }, + /** + * @param {HTMLDivElement} pageDiv + * @param {PDFPage} pdfPage + * @returns {AnnotationsLayerBuilder} + */ + createAnnotationsLayerBuilder: function (pageDiv, pdfPage) { + return new AnnotationsLayerBuilder({ + pageDiv: pageDiv, + pdfPage: pdfPage, + linkService: this.linkService + }); + }, + setFindController: function (findController) { this.findController = findController; }, diff --git a/web/viewer.html b/web/viewer.html index c2d3dff69..d6588d940 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -71,6 +71,7 @@ http://sourceforge.net/adobe/cmap/wiki/License/ + From 22c62685b0f7e6d2009f3a6d0419b51817851c99 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Mon, 29 Sep 2014 11:15:06 -0500 Subject: [PATCH 06/10] Removes Stats dependency from PDFPageView. --- web/pdf_page_view.js | 15 +-------------- web/pdf_viewer.js | 1 - web/viewer.js | 19 ++++++++++++++++--- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 87228c3e5..3ebe41427 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -14,8 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale, - Stats */ +/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale */ 'use strict'; @@ -392,7 +391,6 @@ var PDFPageView = (function PDFPageViewClosure() { self.error = error; self.stats = pdfPage.stats; - self.updateStats(); if (self.onAfterDraw) { self.onAfterDraw(); } @@ -508,17 +506,6 @@ var PDFPageView = (function PDFPageViewClosure() { }); }; }, - - updateStats: function PDFPageView_updateStats() { - if (!this.stats) { - return; - } - - if (PDFJS.pdfBug && Stats.enabled) { - var stats = this.stats; - Stats.add(this.id, stats); - } - }, }; return PDFPageView; diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 84483224c..42e9efaf2 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -107,7 +107,6 @@ var PDFViewer = (function pdfViewer() { return; } - this.pages[val - 1].updateStats(); event.previousPageNumber = this._currentPageNumber; this._currentPageNumber = val; event.pageNumber = val; diff --git a/web/viewer.js b/web/viewer.js index 802d554dd..c00a43bcd 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -16,7 +16,7 @@ */ /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar, DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL, - PDFHistory, Preferences, SidebarView, ViewHistory, + PDFHistory, Preferences, SidebarView, ViewHistory, Stats, PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar, PasswordPrompt, PresentationMode, HandTool, Promise, DocumentProperties, DocumentOutlineView, DocumentAttachmentsView, @@ -1730,12 +1730,17 @@ function webViewerInitialized() { document.addEventListener('DOMContentLoaded', webViewerLoad, true); document.addEventListener('pagerendered', function (e) { - var pageIndex = e.detail.pageNumber - 1; + var pageNumber = e.detail.pageNumber; + var pageIndex = pageNumber - 1; var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex); var thumbnailView = PDFViewerApplication.pdfThumbnailViewer. getThumbnail(pageIndex); thumbnailView.setImage(pageView); + if (PDFJS.pdfBug && Stats.enabled && pageView.stats) { + Stats.add(pageNumber, pageView.stats); + } + //#if (FIREFOX || MOZCENTRAL) //if (pageView.textLayer && pageView.textLayer.textDivs && // pageView.textLayer.textDivs.length > 0 && @@ -1768,7 +1773,7 @@ document.addEventListener('pagerendered', function (e) { // If the page is still visible when it has finished rendering, // ensure that the page number input loading indicator is hidden. - if ((pageIndex + 1) === PDFViewerApplication.page) { + if (pageNumber === PDFViewerApplication.page) { var pageNumberInput = document.getElementById('pageNumber'); pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR); } @@ -1965,6 +1970,14 @@ window.addEventListener('pagechange', function pagechange(evt) { document.getElementById('firstPage').disabled = (page <= 1); document.getElementById('lastPage').disabled = (page >= numPages); + // we need to update stats + if (PDFJS.pdfBug && Stats.enabled) { + var pageView = PDFViewerApplication.pdfViewer.getPageView(page - 1); + if (pageView.stats) { + Stats.add(page, pageView.stats); + } + } + // checking if the this.page was called from the updateViewarea function if (evt.updateInProgress) { return; From b930228788c64414d6040bca6941cc734158df31 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Mon, 29 Sep 2014 11:32:45 -0500 Subject: [PATCH 07/10] Refactors Cache into PDFPageViewBuffer --- web/pdf_page_view.js | 12 ++++++------ web/pdf_viewer.js | 38 ++++++++++++++++++++++++++++++++------ web/ui_utils.js | 21 --------------------- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 3ebe41427..6c8ea8c29 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -25,7 +25,6 @@ * @property {number} scale - The page scale display. * @property {PageViewport} defaultViewport - The page viewport. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. - * @property {Cache} cache - The page cache. * @property {IPDFTextLayerFactory} textLayerFactory * @property {IPDFAnnotationsLayerFactory} annotationsLayerFactory */ @@ -45,7 +44,6 @@ var PDFPageView = (function PDFPageViewClosure() { var scale = options.scale; var defaultViewport = options.defaultViewport; var renderingQueue = options.renderingQueue; - var cache = options.cache; var textLayerFactory = options.textLayerFactory; var annotationsLayerFactory = options.annotationsLayerFactory; @@ -59,13 +57,15 @@ var PDFPageView = (function PDFPageViewClosure() { this.hasRestrictedScaling = false; this.renderingQueue = renderingQueue; - this.cache = cache; this.textLayerFactory = textLayerFactory; this.annotationsLayerFactory = annotationsLayerFactory; this.renderingState = RenderingStates.INITIAL; this.resume = null; + this.onBeforeDraw = null; + this.onAfterDraw = null; + this.textLayer = null; this.zoomLayer = null; @@ -447,9 +447,9 @@ var PDFPageView = (function PDFPageViewClosure() { } div.setAttribute('data-loaded', true); - // Add the page to the cache at the start of drawing. That way it can be - // evicted from the cache and destroyed even if we pause its rendering. - this.cache.push(this); + if (self.onBeforeDraw) { + self.onBeforeDraw(); + } }, beforePrint: function PDFPageView_beforePrint() { diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 42e9efaf2..1ad1c0c7e 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PDFPageView, UNKNOWN_SCALE, + /*globals watchScroll, PDFPageView, UNKNOWN_SCALE, SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS, DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates, PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue, @@ -30,6 +30,7 @@ var PresentationModeState = { }; var IGNORE_CURRENT_POSITION_ON_ZOOM = false; +var DEFAULT_CACHE_SIZE = 10; //#include pdf_rendering_queue.js //#include pdf_page_view.js @@ -52,6 +53,26 @@ var IGNORE_CURRENT_POSITION_ON_ZOOM = false; * @implements {IRenderableView} */ var PDFViewer = (function pdfViewer() { + function PDFPageViewBuffer(size) { + var data = []; + this.push = function cachePush(view) { + var i = data.indexOf(view); + if (i >= 0) { + data.splice(i, 1); + } + data.push(view); + if (data.length > size) { + data.shift().destroy(); + } + }; + this.resize = function (newSize) { + size = newSize; + while (data.length > size) { + data.shift().destroy(); + } + }; + } + /** * @constructs PDFViewer * @param {PDFViewerOptions} options @@ -212,7 +233,13 @@ var PDFViewer = (function pdfViewer() { }); this.onePageRendered = onePageRendered; - var bindOnAfterDraw = function (pageView) { + var bindOnAfterAndBeforeDraw = function (pageView) { + pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() { + // Add the page to the buffer at the start of drawing. That way it can + // be evicted from the buffer and destroyed even if we pause its + // rendering. + self._buffer.push(this); + }; // when page is painted, using the image as thumbnail base pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { if (!isOnePageRenderedResolved) { @@ -246,11 +273,10 @@ var PDFViewer = (function pdfViewer() { scale: scale, defaultViewport: viewport.clone(), renderingQueue: this.renderingQueue, - cache: this.cache, textLayerFactory: textLayerFactory, annotationsLayerFactory: this }); - bindOnAfterDraw(pageView); + bindOnAfterAndBeforeDraw(pageView); this.pages.push(pageView); } @@ -291,11 +317,11 @@ var PDFViewer = (function pdfViewer() { }, _resetView: function () { - this.cache = new Cache(DEFAULT_CACHE_SIZE); this.pages = []; this._currentPageNumber = 1; this._currentScale = UNKNOWN_SCALE; this._currentScaleValue = null; + this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE); this.location = null; this._pagesRotation = 0; this._pagesRequests = []; @@ -538,7 +564,7 @@ var PDFViewer = (function pdfViewer() { var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * visiblePages.length + 1); - this.cache.resize(suggestedCacheSize); + this._buffer.resize(suggestedCacheSize); this.renderingQueue.renderHighestPriority(visible); diff --git a/web/ui_utils.js b/web/ui_utils.js index b4f2d7365..3c32c5939 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -22,7 +22,6 @@ var UNKNOWN_SCALE = 0; var MAX_AUTO_SCALE = 1.25; var SCROLLBAR_PADDING = 40; var VERTICAL_PADDING = 5; -var DEFAULT_CACHE_SIZE = 10; // optimised CSS custom property getter/setter var CustomStyle = (function CustomStyleClosure() { @@ -349,23 +348,3 @@ var ProgressBar = (function ProgressBarClosure() { return ProgressBar; })(); - -var Cache = function cacheCache(size) { - var data = []; - this.push = function cachePush(view) { - var i = data.indexOf(view); - if (i >= 0) { - data.splice(i, 1); - } - data.push(view); - if (data.length > size) { - data.shift().destroy(); - } - }; - this.resize = function (newSize) { - size = newSize; - while (data.length > size) { - data.shift().destroy(); - } - }; -}; From 2ac7ac4678519d8fcfa0f5e703531ba71bf0859d Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Wed, 17 Dec 2014 14:12:51 -0600 Subject: [PATCH 08/10] Removes lastScrollSource and isViewerInPresentationMode from TextLayerBuilderOptions --- web/interfaces.js | 11 -------- web/pdf_find_controller.js | 25 ++++++++++++++++- web/pdf_page_view.js | 12 ++++++++ web/pdf_viewer.js | 11 +++----- web/text_layer_builder.js | 56 ++++++++++++++++++-------------------- 5 files changed, 67 insertions(+), 48 deletions(-) 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. From 2565e627a31dccd562133d2e0f91413f09f302a5 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Wed, 17 Dec 2014 14:47:14 -0600 Subject: [PATCH 09/10] Refactors draw method in PDFPageView; makes optional some PDFPageViewOptions options --- web/annotations_layer_builder.js | 19 +++++++++++++++++ web/interfaces.js | 4 ++-- web/pdf_page_view.js | 36 ++++++++++++++++++++++++-------- web/pdf_rendering_queue.js | 5 ++++- web/text_layer_builder.js | 21 +++++++++++++++++++ web/thumbnail_view.js | 16 +++++++++----- 6 files changed, 84 insertions(+), 17 deletions(-) diff --git a/web/annotations_layer_builder.js b/web/annotations_layer_builder.js index 309f7c033..494ceffb0 100644 --- a/web/annotations_layer_builder.js +++ b/web/annotations_layer_builder.js @@ -154,3 +154,22 @@ var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() { }; return AnnotationsLayerBuilder; })(); + +/** + * @constructor + * @implements IPDFAnnotationsLayerFactory + */ +function DefaultAnnotationsLayerFactory() {} +DefaultAnnotationsLayerFactory.prototype = { + /** + * @param {HTMLDivElement} pageDiv + * @param {PDFPage} pdfPage + * @returns {AnnotationsLayerBuilder} + */ + createAnnotationsLayerBuilder: function (pageDiv, pdfPage) { + return new AnnotationsLayerBuilder({ + pageDiv: pageDiv, + pdfPage: pdfPage + }); + } +}; diff --git a/web/interfaces.js b/web/interfaces.js index b99338c49..37a7b19d0 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -67,9 +67,9 @@ IRenderableView.prototype = { */ get renderingState() {}, /** - * @param {function} callback - The draw completion callback. + * @returns {Promise} Resolved on draw completion. */ - draw: function (callback) {}, + draw: function () {}, resume: function () {}, }; diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 50d3c2b9b..cb0725a3c 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale */ +/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale, + TextLayerBuilder, AnnotationsLayerBuilder, Promise */ 'use strict'; @@ -286,7 +287,7 @@ var PDFPageView = (function PDFPageViewClosure() { return this.viewport.convertToPdfPoint(x, y); }, - draw: function PDFPageView_draw(callback) { + draw: function PDFPageView_draw() { if (this.renderingState !== RenderingStates.INITIAL) { console.error('Must be in new state before drawing'); } @@ -373,6 +374,12 @@ var PDFPageView = (function PDFPageViewClosure() { ctx.scale(outputScale.sx, outputScale.sy); } + var resolveRenderPromise, rejectRenderPromise; + var promise = new Promise(function (resolve, reject) { + resolveRenderPromise = resolve; + rejectRenderPromise = reject; + }); + // Rendering area var self = this; @@ -385,6 +392,7 @@ var PDFPageView = (function PDFPageViewClosure() { } if (error === 'cancelled') { + rejectRenderPromise(error); return; } @@ -412,14 +420,16 @@ var PDFPageView = (function PDFPageViewClosure() { }); div.dispatchEvent(event); - callback(); + if (!error) { + resolveRenderPromise(undefined); + } else { + rejectRenderPromise(error); + } } - var renderContext = { - canvasContext: ctx, - viewport: this.viewport, - // intent: 'default', // === 'display' - continueCallback: function pdfViewcContinueCallback(cont) { + var renderContinueCallback = null; + if (this.renderingQueue) { + renderContinueCallback = function renderContinueCallback(cont) { if (!self.renderingQueue.isHighestPriority(self)) { self.renderingState = RenderingStates.PAUSED; self.resume = function resumeCallback() { @@ -429,7 +439,14 @@ var PDFPageView = (function PDFPageViewClosure() { return; } cont(); - } + }; + } + + var renderContext = { + canvasContext: ctx, + viewport: this.viewport, + // intent: 'default', // === 'display' + continueCallback: renderContinueCallback }; var renderTask = this.renderTask = this.pdfPage.render(renderContext); @@ -462,6 +479,7 @@ var PDFPageView = (function PDFPageViewClosure() { if (self.onBeforeDraw) { self.onBeforeDraw(); } + return promise; }, beforePrint: function PDFPageView_beforePrint() { diff --git a/web/pdf_rendering_queue.js b/web/pdf_rendering_queue.js index c7b1b3aba..b17a1ee58 100644 --- a/web/pdf_rendering_queue.js +++ b/web/pdf_rendering_queue.js @@ -165,7 +165,10 @@ var PDFRenderingQueue = (function PDFRenderingQueueClosure() { break; case RenderingStates.INITIAL: this.highestPriorityPage = view.renderingId; - view.draw(this.renderHighestPriority.bind(this)); + var continueRendering = function () { + this.renderHighestPriority(); + }.bind(this); + view.draw().then(continueRendering, continueRendering); break; } return true; diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index d300f6354..020dfdf1c 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -385,3 +385,24 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { }; return TextLayerBuilder; })(); + +/** + * @constructor + * @implements IPDFTextLayerFactory + */ +function DefaultTextLayerFactory() {} +DefaultTextLayerFactory.prototype = { + /** + * @param {HTMLDivElement} textLayerDiv + * @param {number} pageIndex + * @param {PageViewport} viewport + * @returns {TextLayerBuilder} + */ + createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) { + return new TextLayerBuilder({ + textLayerDiv: textLayerDiv, + pageIndex: pageIndex, + viewport: viewport + }); + } +}; diff --git a/web/thumbnail_view.js b/web/thumbnail_view.js index f8c207ee9..e07ab3ab9 100644 --- a/web/thumbnail_view.js +++ b/web/thumbnail_view.js @@ -139,17 +139,22 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport, return !this.hasImage; }; - this.draw = function thumbnailViewDraw(callback) { + this.draw = function thumbnailViewDraw() { if (this.renderingState !== RenderingStates.INITIAL) { console.error('Must be in new state before drawing'); } this.renderingState = RenderingStates.RUNNING; if (this.hasImage) { - callback(); - return; + return Promise.resolve(undefined); } + var resolveRenderPromise, rejectRenderPromise; + var promise = new Promise(function (resolve, reject) { + resolveRenderPromise = resolve; + rejectRenderPromise = reject; + }); + var self = this; var ctx = this.getPageDrawContext(); var drawViewport = this.viewport.clone({ scale: this.scale }); @@ -171,14 +176,15 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport, this.pdfPage.render(renderContext).promise.then( function pdfPageRenderCallback() { self.renderingState = RenderingStates.FINISHED; - callback(); + resolveRenderPromise(undefined); }, function pdfPageRenderError(error) { self.renderingState = RenderingStates.FINISHED; - callback(); + rejectRenderPromise(error); } ); this.hasImage = true; + return promise; }; function getTempCanvas(width, height) { From 513a3d8c91c2948fcc95d2f1f297d787f45fa416 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Fri, 19 Dec 2014 18:10:57 -0600 Subject: [PATCH 10/10] Replaces text selection example --- examples/components/pageviewer.html | 46 +++++++++ examples/components/pageviewer.js | 58 +++++++++++ examples/text-selection/css/minimal.css | 43 -------- examples/text-selection/index.html | 26 ----- examples/text-selection/js/minimal.js | 97 ------------------- examples/text-selection/pdf/TestDocument.pdf | Bin 35153 -> 0 bytes web/annotations_layer_builder.js | 4 +- web/pdf_viewer.component.js | 9 +- web/text_layer_builder.css | 4 + web/viewer.css | 7 -- 10 files changed, 119 insertions(+), 175 deletions(-) create mode 100644 examples/components/pageviewer.html create mode 100644 examples/components/pageviewer.js delete mode 100644 examples/text-selection/css/minimal.css delete mode 100644 examples/text-selection/index.html delete mode 100644 examples/text-selection/js/minimal.js delete mode 100644 examples/text-selection/pdf/TestDocument.pdf diff --git a/examples/components/pageviewer.html b/examples/components/pageviewer.html new file mode 100644 index 000000000..ac49d273c --- /dev/null +++ b/examples/components/pageviewer.html @@ -0,0 +1,46 @@ + + + + + + + + PDF.js page viewer using built components + + + + + + + + + + + + +
+ + + + + diff --git a/examples/components/pageviewer.js b/examples/components/pageviewer.js new file mode 100644 index 000000000..41c1b35da --- /dev/null +++ b/examples/components/pageviewer.js @@ -0,0 +1,58 @@ +/* Copyright 2014 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. + */ + +'use strict'; + +if (!PDFJS.PDFViewer || !PDFJS.getDocument) { + alert('Please build the library and components using\n' + + ' `node make generic components`'); +} + +// In cases when the pdf.worker.js is located at the different folder than the +// pdf.js's one, or the pdf.js is executed via eval(), the workerSrc property +// shall be specified. +// +// PDFJS.workerSrc = '../../build/pdf.worker.js'; + +// Some PDFs need external cmaps. +// +// PDFJS.cMapUrl = '../../external/bcmaps/'; +// PDFJS.cMapPacked = true; + +var DEFAULT_URL = '../../web/compressed.tracemonkey-pldi-09.pdf'; +var PAGE_TO_VIEW = 1; +var SCALE = 1.0; + +var container = document.getElementById('pageContainer'); + +// Loading document. +PDFJS.getDocument(DEFAULT_URL).then(function (pdfDocument) { + // Document loaded, retrieving the page. + return pdfDocument.getPage(PAGE_TO_VIEW).then(function (pdfPage) { + // Creating the page view with default parameters. + var pdfPageView = new PDFJS.PDFPageView({ + container: container, + id: PAGE_TO_VIEW, + scale: SCALE, + defaultViewport: pdfPage.getViewport(SCALE), + // We can enable text/annotations layers, if needed + textLayerFactory: new PDFJS.DefaultTextLayerFactory(), + annotationsLayerFactory: new PDFJS.DefaultAnnotationsLayerFactory() + }); + // Associates the actual page with the view, and drawing it + pdfPageView.setPdfPage(pdfPage); + return pdfPageView.draw(); + }); +}); diff --git a/examples/text-selection/css/minimal.css b/examples/text-selection/css/minimal.css deleted file mode 100644 index 6a1124484..000000000 --- a/examples/text-selection/css/minimal.css +++ /dev/null @@ -1,43 +0,0 @@ -body { - font-family: arial, verdana, sans-serif; -} - -/* Allow absolute positioning of the canvas and textLayer in the page. They - will be the same size and will be placed on top of each other. */ -.pdfPage { - position: relative; - overflow: visible; - border: 1px solid #000000; -} - -.pdfPage > canvas { - position: absolute; - top: 0; - left: 0; -} - -/* CSS classes used by TextLayerBuilder to style the text layer divs */ - -/* This stuff is important! Otherwise when you select the text, - the text in the divs will show up! */ -::selection { background:rgba(0,0,255,0.3); } -::-moz-selection { background:rgba(0,0,255,0.3); } - -.textLayer { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - color: #000; - font-family: sans-serif; - overflow: hidden; -} - -.textLayer > div { - color: transparent; - position: absolute; - line-height: 1; - white-space: pre; - cursor: text; -} diff --git a/examples/text-selection/index.html b/examples/text-selection/index.html deleted file mode 100644 index fc1e43ba1..000000000 --- a/examples/text-selection/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - Minimal pdf.js text-selection demo - - - - - - - - - - - - - - - This is a minimal pdf.js text-selection demo. The existing minimal-example shows you how to render a PDF, but not - how to enable text-selection. This example shows you how to do both.

-
-
- - diff --git a/examples/text-selection/js/minimal.js b/examples/text-selection/js/minimal.js deleted file mode 100644 index e87bb837a..000000000 --- a/examples/text-selection/js/minimal.js +++ /dev/null @@ -1,97 +0,0 @@ -// Minimal PDF rendering and text-selection example using PDF.js by Vivin Suresh Paliath (http://vivin.net) -// This example uses a built version of PDF.js that contains all modules that it requires. -// -// The problem with understanding text selection was that the text selection code has heavily intertwined -// with viewer.html and viewer.js. I have extracted the parts I need out of viewer.js into a separate file -// which contains the bare minimum required to implement text selection. The key component is TextLayerBuilder, -// which is the object that handles the creation of text-selection divs. I have added this code as an external -// resource. -// -// This demo uses a PDF that only has one page. You can render other pages if you wish, but the focus here is -// just to show you how you can render a PDF with text selection. Hence the code only loads up one page. -// -// The CSS used here is also very important since it sets up the CSS for the text layer divs overlays that -// you actually end up selecting. -// -// NOTE: The original example was changed to remove jQuery usage, re-structure and add more comments. - -window.onload = function () { - if (typeof PDFJS === 'undefined') { - alert('Built version of pdf.js is not found\nPlease run `node make generic`'); - return; - } - - var scale = 1.5; //Set this to whatever you want. This is basically the "zoom" factor for the PDF. - PDFJS.workerSrc = '../../build/generic/build/pdf.worker.js'; - - function loadPdf(pdfPath) { - var pdf = PDFJS.getDocument(pdfPath); - return pdf.then(renderPdf); - } - - function renderPdf(pdf) { - return pdf.getPage(1).then(renderPage); - } - - function renderPage(page) { - var viewport = page.getViewport(scale); - - // Create and append the 'pdf-page' div to the pdf container. - var pdfPage = document.createElement('div'); - pdfPage.className = 'pdfPage'; - var pdfContainer = document.getElementById('pdfContainer'); - pdfContainer.appendChild(pdfPage); - - // Set the canvas height and width to the height and width of the viewport. - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - - // The following few lines of code set up scaling on the context, if we are - // on a HiDPI display. - var outputScale = getOutputScale(context); - canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; - canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; - context._scaleX = outputScale.sx; - context._scaleY = outputScale.sy; - if (outputScale.scaled) { - context.scale(outputScale.sx, outputScale.sy); - } - - // The page, canvas and text layer elements will have the same size. - canvas.style.width = Math.floor(viewport.width) + 'px'; - canvas.style.height = Math.floor(viewport.height) + 'px'; - - pdfPage.style.width = canvas.style.width; - pdfPage.style.height = canvas.style.height; - pdfPage.appendChild(canvas); - - var textLayerDiv = document.createElement('div'); - textLayerDiv.className = 'textLayer'; - textLayerDiv.style.width = canvas.style.width; - textLayerDiv.style.height = canvas.style.height; - pdfPage.appendChild(textLayerDiv); - - // Painting the canvas... - var renderContext = { - canvasContext: context, - viewport: viewport - }; - var renderTask = page.render(renderContext); - - // ... and at the same time, getting the text and creating the text layer. - var textLayerPromise = page.getTextContent().then(function (textContent) { - var textLayerBuilder = new TextLayerBuilder({ - textLayerDiv: textLayerDiv, - viewport: viewport, - pageIndex: 0 - }); - textLayerBuilder.setTextContent(textContent); - }); - - // We might be interested when rendering complete and text layer is built. - return Promise.all([renderTask.promise, textLayerPromise]); - } - - loadPdf('pdf/TestDocument.pdf'); -}; - diff --git a/examples/text-selection/pdf/TestDocument.pdf b/examples/text-selection/pdf/TestDocument.pdf deleted file mode 100644 index 843fd9d2bda87f634a58449d017d7624fe17c02b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35153 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 I@akv)b#CuGw#f-4^-;NmzM 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 div { @@ -57,3 +58,6 @@ .textLayer .highlight.selected { background-color: rgb(0, 100, 0); } + +.textLayer ::selection { background: rgb(0,0,255); } +.textLayer ::-moz-selection { background: rgb(0,0,255); } diff --git a/web/viewer.css b/web/viewer.css index 066b12aeb..5fcb3f0fd 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -1258,19 +1258,12 @@ html[dir='rtl'] .attachmentsItem > button { cursor: default; } - /* TODO: file FF bug to support ::-moz-selection:window-inactive so we can override the opaque grey background when the window is inactive; see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */ ::selection { background: rgba(0,0,255,0.3); } ::-moz-selection { background: rgba(0,0,255,0.3); } -.textLayer ::selection { background: rgb(0,0,255); } -.textLayer ::-moz-selection { background: rgb(0,0,255); } -.textLayer { - opacity: 0.2; -} - #errorWrapper { background: none repeat scroll 0 0 #FF5555; color: white;