+
+
+
+ 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 843fd9d2b..000000000
Binary files a/examples/text-selection/pdf/TestDocument.pdf and /dev/null differ
diff --git a/web/annotations_layer_builder.js b/web/annotations_layer_builder.js
new file mode 100644
index 000000000..b5ef814ec
--- /dev/null
+++ b/web/annotations_layer_builder.js
@@ -0,0 +1,177 @@
+/* -*- 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);
+ if (typeof mozL10n !== 'undefined') {
+ 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;
+})();
+
+/**
+ * @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 5f0ad04c6..37a7b19d0 100644
--- a/web/interfaces.js
+++ b/web/interfaces.js
@@ -67,19 +67,35 @@ IRenderableView.prototype = {
*/
get renderingState() {},
/**
- * @param {function} callback - The draw completion callback.
+ * @returns {Promise} Resolved on draw completion.
*/
- draw: function (callback) {},
+ draw: function () {},
resume: function () {},
};
/**
* @interface
*/
-function ILastScrollSource() {}
-ILastScrollSource.prototype = {
+function IPDFTextLayerFactory() {}
+IPDFTextLayerFactory.prototype = {
/**
- * @returns {number}
+ * @param {HTMLDivElement} textLayerDiv
+ * @param {number} pageIndex
+ * @param {PageViewport} viewport
+ * @returns {TextLayerBuilder}
*/
- get lastScroll() {},
+ 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/page_view.js b/web/page_view.js
deleted file mode 100644
index 92ed39b7d..000000000
--- a/web/page_view.js
+++ /dev/null
@@ -1,610 +0,0 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-/* Copyright 2012 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 RenderingStates, PDFJS, mozL10n, CustomStyle, getOutputScale, Stats,
- CSS_UNITS */
-
-'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
- *
- * @implements {IRenderableView}
- */
-var PageView = function pageView(container, id, scale, defaultViewport,
- linkService, renderingQueue, cache,
- pageSource, viewer) {
- this.id = id;
- this.renderingId = 'page' + id;
-
- this.rotation = 0;
- this.scale = scale || 1.0;
- this.viewport = defaultViewport;
- this.pdfPageRotate = defaultViewport.rotation;
- this.hasRestrictedScaling = false;
-
- this.linkService = linkService;
- this.renderingQueue = renderingQueue;
- this.cache = cache;
- this.pageSource = pageSource;
- this.viewer = viewer;
-
- 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 = 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;
-
- div.style.width = Math.floor(this.viewport.width) + 'px';
- div.style.height = Math.floor(this.viewport.height) + 'px';
-
- 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);
- }
- }
- });
- }
-
- this.getPagePoint = function pageViewGetPagePoint(x, y) {
- return this.viewport.convertToPdfPoint(x, y);
- };
-
- this.draw = function pageviewDraw(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 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;
- } 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);
- }
-
- 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;
- }
-
- self.renderingState = RenderingStates.FINISHED;
-
- if (self.loadingIconDiv) {
- div.removeChild(self.loadingIconDiv);
- delete self.loadingIconDiv;
- }
-
- 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();
- }
-
- 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();
- };
- 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);
- }
- );
-
- 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();
- }
- });
- };
- };
-
- this.updateStats = function pageViewUpdateStats() {
- if (!this.stats) {
- return;
- }
-
- if (PDFJS.pdfBug && Stats.enabled) {
- var stats = this.stats;
- Stats.add(this.id, stats);
- }
- };
-};
diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js
index 1aa912109..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.
@@ -197,8 +200,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 +207,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.pdfViewer.scrollPageIntoView(index + 1);
}
+ var page = this.pdfViewer.getPageView(index);
if (page.textLayer) {
page.textLayer.updateMatches();
}
@@ -309,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
new file mode 100644
index 000000000..cb0725a3c
--- /dev/null
+++ b/web/pdf_page_view.js
@@ -0,0 +1,542 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2012 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 RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale,
+ TextLayerBuilder, AnnotationsLayerBuilder, Promise */
+
+'use strict';
+
+var TEXT_LAYER_RENDER_DELAY = 200; // ms
+
+/**
+ * @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 {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @property {IPDFTextLayerFactory} textLayerFactory
+ * @property {IPDFAnnotationsLayerFactory} annotationsLayerFactory
+ */
+
+/**
+ * @class
+ * @implements {IRenderableView}
+ */
+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 renderingQueue = options.renderingQueue;
+ var textLayerFactory = options.textLayerFactory;
+ var annotationsLayerFactory = options.annotationsLayerFactory;
+
+ this.id = id;
+ this.renderingId = 'page' + id;
+
+ this.rotation = 0;
+ this.scale = scale || 1.0;
+ this.viewport = defaultViewport;
+ this.pdfPageRotate = defaultViewport.rotation;
+ this.hasRestrictedScaling = false;
+
+ this.renderingQueue = renderingQueue;
+ 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;
+
+ 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;
+
+ container.appendChild(anchor);
+ container.appendChild(div);
+ }
+
+ 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();
+ },
+
+ destroy: function PDFPageView_destroy() {
+ this.zoomLayer = null;
+ this.reset();
+ if (this.pdfPage) {
+ this.pdfPage.destroy();
+ }
+ },
+
+ reset: function PDFPageView_reset(keepAnnotations) {
+ if (this.renderTask) {
+ this.renderTask.cancel();
+ }
+ this.resume = null;
+ this.renderingState = RenderingStates.INITIAL;
+
+ var div = this.div;
+ div.style.width = Math.floor(this.viewport.width) + 'px';
+ 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 (currentZoomLayer === node || currentAnnotationNode === 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.hide();
+ }
+ } 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);
+ },
+
+ 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);
+ },
+
+ /**
+ * 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;
+ 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.annotationLayer.setupAnnotations(this.viewport);
+ }
+ },
+
+ get width() {
+ return this.viewport.width;
+ },
+
+ get height() {
+ return this.viewport.height;
+ },
+
+ getPagePoint: function PDFPageView_getPagePoint(x, y) {
+ return this.viewport.convertToPdfPoint(x, y);
+ },
+
+ draw: function PDFPageView_draw() {
+ 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
+ // 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.div);
+ } 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;
+ } 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 (this.textLayerFactory) {
+ 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.div);
+ } else {
+ div.appendChild(textLayerDiv);
+ }
+
+ textLayer = this.textLayerFactory.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);
+ }
+
+ var resolveRenderPromise, rejectRenderPromise;
+ var promise = new Promise(function (resolve, reject) {
+ resolveRenderPromise = resolve;
+ rejectRenderPromise = reject;
+ });
+
+ // 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') {
+ rejectRenderPromise(error);
+ return;
+ }
+
+ self.renderingState = RenderingStates.FINISHED;
+
+ if (self.loadingIconDiv) {
+ div.removeChild(self.loadingIconDiv);
+ delete self.loadingIconDiv;
+ }
+
+ if (self.zoomLayer) {
+ div.removeChild(self.zoomLayer);
+ self.zoomLayer = null;
+ }
+
+ self.error = error;
+ self.stats = pdfPage.stats;
+ if (self.onAfterDraw) {
+ self.onAfterDraw();
+ }
+
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagerender', true, true, {
+ pageNumber: pdfPage.pageNumber
+ });
+ div.dispatchEvent(event);
+
+ if (!error) {
+ resolveRenderPromise(undefined);
+ } else {
+ rejectRenderPromise(error);
+ }
+ }
+
+ var renderContinueCallback = null;
+ if (this.renderingQueue) {
+ renderContinueCallback = function renderContinueCallback(cont) {
+ if (!self.renderingQueue.isHighestPriority(self)) {
+ self.renderingState = RenderingStates.PAUSED;
+ self.resume = function resumeCallback() {
+ self.renderingState = RenderingStates.RUNNING;
+ cont();
+ };
+ return;
+ }
+ cont();
+ };
+ }
+
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: this.viewport,
+ // intent: 'default', // === 'display'
+ continueCallback: renderContinueCallback
+ };
+ 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);
+ textLayer.render(TEXT_LAYER_RENDER_DELAY);
+ }
+ );
+ }
+ },
+ function pdfPageRenderError(error) {
+ pageViewDrawCallback(error);
+ }
+ );
+
+ if (this.annotationsLayerFactory) {
+ if (!this.annotationLayer) {
+ this.annotationLayer = this.annotationsLayerFactory.
+ createAnnotationsLayerBuilder(div, this.pdfPage);
+ }
+ this.annotationLayer.setupAnnotations(this.viewport);
+ }
+ div.setAttribute('data-loaded', true);
+
+ if (self.onBeforeDraw) {
+ self.onBeforeDraw();
+ }
+ return promise;
+ },
+
+ 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();
+ }
+ });
+ };
+ },
+ };
+
+ return PDFPageView;
+})();
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/pdf_viewer.component.js b/web/pdf_viewer.component.js
index 22c483854..8c1223ab7 100644
--- a/web/pdf_viewer.component.js
+++ b/web/pdf_viewer.component.js
@@ -15,7 +15,9 @@
* limitations under the License.
*/
/*jshint globalstrict: false */
-/* globals PDFJS, PDFViewer */
+/* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder,
+ DefaultTextLayerFactory, AnnotationsLayerBuilder,
+ DefaultAnnotationsLayerFactory */
// Initializing PDFJS global object (if still undefined)
if (typeof PDFJS === 'undefined') {
@@ -29,4 +31,9 @@ if (typeof PDFJS === 'undefined') {
//#include pdf_viewer.js
PDFJS.PDFViewer = PDFViewer;
+ PDFJS.PDFPageView = PDFPageView;
+ PDFJS.TextLayerBuilder = TextLayerBuilder;
+ PDFJS.DefaultTextLayerFactory = DefaultTextLayerFactory;
+ PDFJS.AnnotationsLayerBuilder = AnnotationsLayerBuilder;
+ PDFJS.DefaultAnnotationsLayerFactory = DefaultAnnotationsLayerFactory;
}).call((typeof window === 'undefined') ? this : window);
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 47e109fab..e68130ce5 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -14,10 +14,11 @@
* 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, 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';
@@ -29,10 +30,12 @@ var PresentationModeState = {
};
var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
+var DEFAULT_CACHE_SIZE = 10;
//#include pdf_rendering_queue.js
-//#include page_view.js
+//#include pdf_page_view.js
//#include text_layer_builder.js
+//#include annotations_layer_builder.js
/**
* @typedef {Object} PDFViewerOptions
@@ -46,10 +49,29 @@ var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
/**
* Simple viewer control to display PDF content/pages.
* @class
- * @implements {ILastScrollSource}
* @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
@@ -69,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();
@@ -105,7 +126,6 @@ var PDFViewer = (function pdfViewer() {
return;
}
- this.pages[val - 1].updateStats();
event.previousPageNumber = this._currentPageNumber;
this._currentPageNumber = val;
event.pageNumber = val;
@@ -211,7 +231,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) {
@@ -235,12 +261,20 @@ 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 pageView = new PageView(this.viewer, pageNum, scale,
- viewport.clone(), this.linkService,
- this.renderingQueue, this.cache,
- pageSource, this);
- bindOnAfterDraw(pageView);
+ var textLayerFactory = null;
+ if (!PDFJS.disableTextLayer) {
+ textLayerFactory = this;
+ }
+ var pageView = new PDFPageView({
+ container: this.viewer,
+ id: pageNum,
+ scale: scale,
+ defaultViewport: viewport.clone(),
+ renderingQueue: this.renderingQueue,
+ textLayerFactory: textLayerFactory,
+ annotationsLayerFactory: this
+ });
+ bindOnAfterAndBeforeDraw(pageView);
this.pages.push(pageView);
}
@@ -281,13 +315,14 @@ 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 = [];
var container = this.viewer;
while (container.hasChildNodes()) {
@@ -296,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(
@@ -398,7 +434,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 +447,7 @@ var PDFViewer = (function pdfViewer() {
this._setScale(this.currentScaleValue, true);
}
if (!dest) {
- scrollIntoView(pageViewDiv);
+ scrollIntoView(pageView.div);
return;
}
@@ -475,7 +510,7 @@ var PDFViewer = (function pdfViewer() {
}
if (scale === 'page-fit' && !dest[4]) {
- scrollIntoView(pageViewDiv);
+ scrollIntoView(pageView.div);
return;
}
@@ -486,7 +521,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) {
@@ -528,7 +563,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);
@@ -604,13 +639,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;
@@ -623,9 +683,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) {
@@ -635,9 +695,20 @@ var PDFViewer = (function pdfViewer() {
textLayerDiv: textLayerDiv,
pageIndex: pageIndex,
viewport: viewport,
- lastScrollSource: this,
- isViewerInPresentationMode: isViewerInPresentationMode,
- findController: this.findController
+ findController: isViewerInPresentationMode ? null : this.findController
+ });
+ },
+
+ /**
+ * @param {HTMLDivElement} pageDiv
+ * @param {PDFPage} pdfPage
+ * @returns {AnnotationsLayerBuilder}
+ */
+ createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {
+ return new AnnotationsLayerBuilder({
+ pageDiv: pageDiv,
+ pdfPage: pdfPage,
+ linkService: this.linkService
});
},
@@ -695,31 +766,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/text_layer_builder.css b/web/text_layer_builder.css
index 32f7c497b..f5a55df10 100644
--- a/web/text_layer_builder.css
+++ b/web/text_layer_builder.css
@@ -20,6 +20,7 @@
right: 0;
bottom: 0;
overflow: hidden;
+ opacity: 0.2;
}
.textLayer > 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/text_layer_builder.js b/web/text_layer_builder.js
index 5fac1c493..020dfdf1c 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.
@@ -387,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 1e16a7de4..e07ab3ab9 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;
@@ -142,26 +139,22 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport,
return !this.hasImage;
};
- 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;
- }
-
+ 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 });
@@ -183,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) {
@@ -204,18 +198,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 +320,7 @@ var PDFThumbnailViewer = (function pdfThumbnailViewer() {
_resetView: function () {
this.thumbnails = [];
this._pagesRotation = 0;
+ this._pagesRequests = [];
},
setDocument: function (pdfDocument) {
@@ -351,15 +342,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 +386,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/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();
- }
- };
-};
diff --git a/web/viewer.css b/web/viewer.css
index 91e64eae9..1dd872a06 100644
--- a/web/viewer.css
+++ b/web/viewer.css
@@ -1256,19 +1256,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;
diff --git a/web/viewer.html b/web/viewer.html
index f562dbd66..d6588d940 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -69,8 +69,9 @@ http://sourceforge.net/adobe/cmap/wiki/License/
-
+
+
diff --git a/web/viewer.js b/web/viewer.js
index 439fb0106..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, PageView,
+ PDFHistory, Preferences, SidebarView, ViewHistory, Stats,
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;
@@ -1731,11 +1730,16 @@ 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.canvas);
+ thumbnailView.setImage(pageView);
+
+ if (PDFJS.pdfBug && Stats.enabled && pageView.stats) {
+ Stats.add(pageNumber, pageView.stats);
+ }
//#if (FIREFOX || MOZCENTRAL)
//if (pageView.textLayer && pageView.textLayer.textDivs &&
@@ -1769,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);
}
@@ -1966,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;