2013-09-20 16:25:41 +09:00
|
|
|
/* -*- 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.
|
|
|
|
*/
|
2014-09-13 11:27:45 +09:00
|
|
|
/* globals RenderingStates, PDFView, PDFJS, mozL10n, CustomStyle,
|
2014-09-16 02:37:03 +09:00
|
|
|
SCROLLBAR_PADDING, CSS_UNITS, UNKNOWN_SCALE, DEFAULT_SCALE,
|
|
|
|
getOutputScale, TextLayerBuilder, scrollIntoView, Stats,
|
|
|
|
PresentationModeState */
|
2013-09-20 16:25:41 +09:00
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
2014-09-13 11:27:45 +09:00
|
|
|
var PageView = function pageView(container, id, scale, defaultViewport,
|
2014-09-15 23:49:24 +09:00
|
|
|
linkService, renderingQueue, cache,
|
|
|
|
pageSource, viewer) {
|
2013-09-20 16:25:41 +09:00
|
|
|
this.id = id;
|
2014-09-16 01:18:28 +09:00
|
|
|
this.renderingId = 'page' + id;
|
2013-09-20 16:25:41 +09:00
|
|
|
|
|
|
|
this.rotation = 0;
|
|
|
|
this.scale = scale || 1.0;
|
|
|
|
this.viewport = defaultViewport;
|
2013-11-26 07:08:28 +09:00
|
|
|
this.pdfPageRotate = defaultViewport.rotation;
|
2014-05-24 10:43:43 +09:00
|
|
|
this.hasRestrictedScaling = false;
|
2013-09-20 16:25:41 +09:00
|
|
|
|
2014-09-13 11:27:45 +09:00
|
|
|
this.linkService = linkService;
|
|
|
|
this.renderingQueue = renderingQueue;
|
|
|
|
this.cache = cache;
|
2014-09-15 23:49:24 +09:00
|
|
|
this.pageSource = pageSource;
|
2014-09-13 11:27:45 +09:00
|
|
|
this.viewer = viewer;
|
|
|
|
|
2013-09-20 16:25:41 +09:00
|
|
|
this.renderingState = RenderingStates.INITIAL;
|
|
|
|
this.resume = null;
|
|
|
|
|
|
|
|
this.textLayer = null;
|
|
|
|
|
2013-10-03 01:05:46 +09:00
|
|
|
this.zoomLayer = null;
|
|
|
|
|
2013-09-20 16:25:41 +09:00
|
|
|
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;
|
2013-11-26 07:08:28 +09:00
|
|
|
var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
|
|
|
|
this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
|
2013-09-20 16:25:41 +09:00
|
|
|
this.stats = pdfPage.stats;
|
2013-10-03 01:05:46 +09:00
|
|
|
this.reset();
|
2013-09-20 16:25:41 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
this.destroy = function pageViewDestroy() {
|
2013-10-03 01:05:46 +09:00
|
|
|
this.zoomLayer = null;
|
|
|
|
this.reset();
|
2013-09-20 16:25:41 +09:00
|
|
|
if (this.pdfPage) {
|
|
|
|
this.pdfPage.destroy();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-04-01 20:45:00 +09:00
|
|
|
this.reset = function pageViewReset(keepAnnotations) {
|
2013-09-20 16:25:41 +09:00
|
|
|
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';
|
|
|
|
|
2013-10-03 01:05:46 +09:00
|
|
|
var childNodes = div.childNodes;
|
|
|
|
for (var i = div.childNodes.length - 1; i >= 0; i--) {
|
|
|
|
var node = childNodes[i];
|
2014-04-01 20:45:00 +09:00
|
|
|
if ((this.zoomLayer && this.zoomLayer === node) ||
|
|
|
|
(keepAnnotations && this.annotationLayer === node)) {
|
2013-10-03 01:05:46 +09:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
div.removeChild(node);
|
2013-09-20 16:28:03 +09:00
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
div.removeAttribute('data-loaded');
|
|
|
|
|
2014-04-01 20:45:00 +09:00
|
|
|
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;
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
2014-06-12 15:20:30 +09:00
|
|
|
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;
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
|
|
|
this.loadingIconDiv = document.createElement('div');
|
|
|
|
this.loadingIconDiv.className = 'loadingIcon';
|
|
|
|
div.appendChild(this.loadingIconDiv);
|
|
|
|
};
|
|
|
|
|
2013-10-03 01:05:46 +09:00
|
|
|
this.update = function pageViewUpdate(scale, rotation) {
|
|
|
|
this.scale = scale || this.scale;
|
|
|
|
|
|
|
|
if (typeof rotation !== 'undefined') {
|
|
|
|
this.rotation = rotation;
|
|
|
|
}
|
|
|
|
|
2013-11-26 07:08:28 +09:00
|
|
|
var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
|
|
|
|
this.viewport = this.viewport.clone({
|
|
|
|
scale: this.scale * CSS_UNITS,
|
|
|
|
rotation: totalRotation
|
|
|
|
});
|
2013-10-03 01:05:46 +09:00
|
|
|
|
2014-05-24 10:43:43 +09:00
|
|
|
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);
|
2013-10-03 01:05:46 +09:00
|
|
|
return;
|
|
|
|
} else if (this.canvas && !this.zoomLayer) {
|
|
|
|
this.zoomLayer = this.canvas.parentNode;
|
|
|
|
this.zoomLayer.style.position = 'absolute';
|
|
|
|
}
|
|
|
|
if (this.zoomLayer) {
|
2013-10-10 03:29:05 +09:00
|
|
|
this.cssTransform(this.zoomLayer.firstChild);
|
2013-10-03 01:05:46 +09:00
|
|
|
}
|
2014-04-01 20:45:00 +09:00
|
|
|
this.reset(true);
|
2013-10-03 01:05:46 +09:00
|
|
|
};
|
|
|
|
|
2014-05-24 10:43:43 +09:00
|
|
|
this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) {
|
2013-10-10 03:29:05 +09:00
|
|
|
// Scale canvas, canvas wrapper, and page container.
|
|
|
|
var width = this.viewport.width;
|
|
|
|
var height = this.viewport.height;
|
2013-10-03 01:05:46 +09:00
|
|
|
canvas.style.width = canvas.parentNode.style.width = div.style.width =
|
2013-10-10 03:29:05 +09:00
|
|
|
Math.floor(width) + 'px';
|
2013-10-03 01:05:46 +09:00
|
|
|
canvas.style.height = canvas.parentNode.style.height = div.style.height =
|
2013-10-10 03:29:05 +09:00
|
|
|
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);
|
|
|
|
|
2013-10-03 01:05:46 +09:00
|
|
|
if (this.textLayer) {
|
2013-10-10 03:29:05 +09:00
|
|
|
// 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.
|
2014-05-14 23:47:58 +09:00
|
|
|
var textLayerViewport = this.textLayer.viewport;
|
2013-10-10 03:29:05 +09:00
|
|
|
var textRelativeRotation = this.viewport.rotation -
|
2014-05-14 23:47:58 +09:00
|
|
|
textLayerViewport.rotation;
|
2013-10-10 03:29:05 +09:00
|
|
|
var textAbsRotation = Math.abs(textRelativeRotation);
|
2014-05-14 23:47:58 +09:00
|
|
|
var scale = width / textLayerViewport.width;
|
2013-10-10 03:29:05 +09:00
|
|
|
if (textAbsRotation === 90 || textAbsRotation === 270) {
|
2014-05-14 23:47:58 +09:00
|
|
|
scale = width / textLayerViewport.height;
|
2013-10-10 03:29:05 +09:00
|
|
|
}
|
2013-10-03 01:05:46 +09:00
|
|
|
var textLayerDiv = this.textLayer.textLayerDiv;
|
2013-10-10 03:29:05 +09:00
|
|
|
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 + ')');
|
2013-10-03 01:05:46 +09:00
|
|
|
CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
|
|
|
|
}
|
2014-01-13 07:07:56 +09:00
|
|
|
|
2014-05-24 10:43:43 +09:00
|
|
|
if (redrawAnnotations && this.annotationLayer) {
|
2014-01-13 07:07:56 +09:00
|
|
|
setupAnnotations(div, this.pdfPage, this.viewport);
|
|
|
|
}
|
2013-10-03 01:05:46 +09:00
|
|
|
};
|
|
|
|
|
2013-09-20 16:25:41 +09:00
|
|
|
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) {
|
2014-09-13 11:27:45 +09:00
|
|
|
link.href = linkService.getDestinationHash(dest);
|
2013-09-20 16:25:41 +09:00
|
|
|
link.onclick = function pageViewSetupLinksOnclick() {
|
2013-09-20 16:28:03 +09:00
|
|
|
if (dest) {
|
2014-09-13 11:27:45 +09:00
|
|
|
linkService.navigateTo(dest);
|
2013-09-20 16:28:03 +09:00
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
return false;
|
|
|
|
};
|
2013-10-02 23:17:38 +09:00
|
|
|
if (dest) {
|
|
|
|
link.className = 'internalLink';
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
function bindNamedAction(link, action) {
|
2014-09-13 11:27:45 +09:00
|
|
|
link.href = linkService.getAnchorUrl('');
|
2013-09-20 16:25:41 +09:00
|
|
|
link.onclick = function pageViewSetupNamedActionOnClick() {
|
2014-09-13 11:27:45 +09:00
|
|
|
linkService.executeNamedAction(action);
|
2013-09-20 16:25:41 +09:00
|
|
|
return false;
|
|
|
|
};
|
|
|
|
link.className = 'internalLink';
|
|
|
|
}
|
|
|
|
|
|
|
|
pdfPage.getAnnotations().then(function(annotationsData) {
|
|
|
|
viewport = viewport.clone({ dontFlip: true });
|
2014-04-01 20:45:00 +09:00
|
|
|
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);
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
}
|
2014-04-01 20:45:00 +09:00
|
|
|
// See this.reset()
|
|
|
|
self.annotationLayer.removeAttribute('hidden');
|
|
|
|
} else {
|
|
|
|
for (i = 0, ii = annotationsData.length; i < ii; i++) {
|
|
|
|
data = annotationsData[i];
|
2014-06-18 07:43:33 +09:00
|
|
|
if (!data || !data.hasHtml) {
|
2014-04-01 20:45:00 +09:00
|
|
|
continue;
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
2014-06-19 06:42:08 +09:00
|
|
|
element = PDFJS.AnnotationUtils.getHtmlElement(data,
|
|
|
|
pdfPage.commonObjs);
|
2014-04-01 20:45:00 +09:00
|
|
|
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);
|
|
|
|
}
|
2014-03-07 23:48:42 +09:00
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
}
|
|
|
|
|
2014-04-01 20:45:00 +09:00
|
|
|
if (!self.annotationLayer) {
|
|
|
|
var annotationLayerDiv = document.createElement('div');
|
|
|
|
annotationLayerDiv.className = 'annotationLayer';
|
|
|
|
pageDiv.appendChild(annotationLayerDiv);
|
|
|
|
self.annotationLayer = annotationLayerDiv;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.annotationLayer.appendChild(element);
|
2013-09-20 16:25:41 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.getPagePoint = function pageViewGetPagePoint(x, y) {
|
|
|
|
return this.viewport.convertToPdfPoint(x, y);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.scrollIntoView = function pageViewScrollIntoView(dest) {
|
2014-09-16 02:37:03 +09:00
|
|
|
if (this.viewer.presentationModeState ===
|
|
|
|
PresentationModeState.FULLSCREEN) {
|
2014-09-13 11:27:45 +09:00
|
|
|
if (this.linkService.page !== this.id) {
|
|
|
|
// Avoid breaking getVisiblePages in presentation mode.
|
|
|
|
this.linkService.page = this.id;
|
2014-02-10 21:43:26 +09:00
|
|
|
return;
|
|
|
|
}
|
2013-09-20 16:28:03 +09:00
|
|
|
dest = null;
|
2014-09-13 11:27:45 +09:00
|
|
|
this.viewer.setScale(this.viewer.currentScaleValue, true, true);
|
2013-09-20 16:28:03 +09:00
|
|
|
}
|
|
|
|
if (!dest) {
|
|
|
|
scrollIntoView(div);
|
|
|
|
return;
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
2013-09-20 16:28:03 +09:00
|
|
|
var x = 0, y = 0;
|
|
|
|
var width = 0, height = 0, widthScale, heightScale;
|
2014-04-08 03:55:28 +09:00
|
|
|
var changeOrientation = (this.rotation % 180 === 0 ? false : true);
|
2014-01-11 03:15:09 +09:00
|
|
|
var pageWidth = (changeOrientation ? this.height : this.width) /
|
|
|
|
this.scale / CSS_UNITS;
|
|
|
|
var pageHeight = (changeOrientation ? this.width : this.height) /
|
|
|
|
this.scale / CSS_UNITS;
|
2013-09-20 16:28:03 +09:00
|
|
|
var scale = 0;
|
|
|
|
switch (dest[1].name) {
|
|
|
|
case 'XYZ':
|
|
|
|
x = dest[2];
|
|
|
|
y = dest[3];
|
|
|
|
scale = dest[4];
|
|
|
|
// If x and/or y coordinates are not supplied, default to
|
|
|
|
// _top_ left of the page (not the obvious bottom left,
|
|
|
|
// since aligning the bottom of the intended page with the
|
|
|
|
// top of the window is rarely helpful).
|
|
|
|
x = x !== null ? x : 0;
|
2014-01-11 03:15:09 +09:00
|
|
|
y = y !== null ? y : pageHeight;
|
2013-09-20 16:28:03 +09:00
|
|
|
break;
|
|
|
|
case 'Fit':
|
|
|
|
case 'FitB':
|
|
|
|
scale = 'page-fit';
|
|
|
|
break;
|
|
|
|
case 'FitH':
|
|
|
|
case 'FitBH':
|
|
|
|
y = dest[2];
|
|
|
|
scale = 'page-width';
|
|
|
|
break;
|
|
|
|
case 'FitV':
|
|
|
|
case 'FitBV':
|
|
|
|
x = dest[2];
|
2014-01-11 03:15:09 +09:00
|
|
|
width = pageWidth;
|
|
|
|
height = pageHeight;
|
2013-09-20 16:28:03 +09:00
|
|
|
scale = 'page-height';
|
|
|
|
break;
|
|
|
|
case 'FitR':
|
|
|
|
x = dest[2];
|
|
|
|
y = dest[3];
|
|
|
|
width = dest[4] - x;
|
|
|
|
height = dest[5] - y;
|
2014-09-13 11:27:45 +09:00
|
|
|
var viewerContainer = this.viewer.container;
|
|
|
|
widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
|
2013-09-20 16:28:03 +09:00
|
|
|
width / CSS_UNITS;
|
2014-09-13 11:27:45 +09:00
|
|
|
heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
|
2013-09-20 16:28:03 +09:00
|
|
|
height / CSS_UNITS;
|
2013-11-24 02:27:03 +09:00
|
|
|
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
|
2013-09-20 16:28:03 +09:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
2014-09-13 11:27:45 +09:00
|
|
|
if (scale && scale !== this.viewer.currentScale) {
|
|
|
|
this.viewer.setScale(scale, true, true);
|
|
|
|
} else if (this.viewer.currentScale === UNKNOWN_SCALE) {
|
|
|
|
this.viewer.setScale(DEFAULT_SCALE, true, true);
|
2013-09-20 16:28:03 +09:00
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
2013-09-20 16:28:03 +09:00
|
|
|
if (scale === 'page-fit' && !dest[4]) {
|
|
|
|
scrollIntoView(div);
|
|
|
|
return;
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
2013-09-20 16:28:03 +09:00
|
|
|
var boundingRect = [
|
|
|
|
this.viewport.convertToViewportPoint(x, y),
|
|
|
|
this.viewport.convertToViewportPoint(x + width, y + height)
|
|
|
|
];
|
2014-01-11 20:57:33 +09:00
|
|
|
var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
|
|
|
|
var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
|
|
|
|
|
|
|
|
scrollIntoView(div, { left: left, top: top });
|
2013-09-20 16:25:41 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
this.draw = function pageviewDraw(callback) {
|
|
|
|
var pdfPage = this.pdfPage;
|
|
|
|
|
2014-01-08 01:59:54 +09:00
|
|
|
if (this.pagePdfPromise) {
|
|
|
|
return;
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
if (!pdfPage) {
|
2014-09-15 23:49:24 +09:00
|
|
|
var promise = this.pageSource.getPage();
|
2013-09-20 16:25:41 +09:00
|
|
|
promise.then(function(pdfPage) {
|
2014-01-08 01:59:54 +09:00
|
|
|
delete this.pagePdfPromise;
|
2013-09-20 16:25:41 +09:00
|
|
|
this.setPdfPage(pdfPage);
|
|
|
|
this.draw(callback);
|
|
|
|
}.bind(this));
|
2014-01-08 01:59:54 +09:00
|
|
|
this.pagePdfPromise = promise;
|
2013-09-20 16:25:41 +09:00
|
|
|
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);
|
2014-04-01 20:45:00 +09:00
|
|
|
if (this.annotationLayer) {
|
|
|
|
// annotationLayer needs to stay on top
|
|
|
|
div.insertBefore(canvasWrapper, this.annotationLayer);
|
|
|
|
} else {
|
|
|
|
div.appendChild(canvasWrapper);
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
this.canvas = canvas;
|
|
|
|
|
2014-04-12 04:07:36 +09:00
|
|
|
var ctx = canvas.getContext('2d');
|
2013-09-21 04:11:14 +09:00
|
|
|
var outputScale = getOutputScale(ctx);
|
|
|
|
|
2014-05-30 07:44:41 +09:00
|
|
|
if (PDFJS.useOnlyCssZoom) {
|
2013-10-09 12:31:49 +09:00
|
|
|
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;
|
2013-10-03 01:05:46 +09:00
|
|
|
outputScale.scaled = true;
|
|
|
|
}
|
|
|
|
|
2014-05-24 10:43:43 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-25 23:54:47 +09:00
|
|
|
canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
|
|
|
|
canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
|
2013-09-21 04:11:14 +09:00
|
|
|
canvas.style.width = Math.floor(viewport.width) + 'px';
|
|
|
|
canvas.style.height = Math.floor(viewport.height) + 'px';
|
2013-10-10 03:29:05 +09:00
|
|
|
// Add the viewport so it's known what it was originally drawn with.
|
|
|
|
canvas._viewport = viewport;
|
2013-09-20 16:25:41 +09:00
|
|
|
|
|
|
|
var textLayerDiv = null;
|
|
|
|
if (!PDFJS.disableTextLayer) {
|
|
|
|
textLayerDiv = document.createElement('div');
|
|
|
|
textLayerDiv.className = 'textLayer';
|
2014-04-10 08:44:07 +09:00
|
|
|
textLayerDiv.style.width = canvas.style.width;
|
|
|
|
textLayerDiv.style.height = canvas.style.height;
|
2014-04-01 20:45:00 +09:00
|
|
|
if (this.annotationLayer) {
|
|
|
|
// annotationLayer needs to stay on top
|
|
|
|
div.insertBefore(textLayerDiv, this.annotationLayer);
|
|
|
|
} else {
|
|
|
|
div.appendChild(textLayerDiv);
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
}
|
2014-09-16 02:37:03 +09:00
|
|
|
var isViewerInPresentationMode =
|
|
|
|
this.viewer.presentationModeState === PresentationModeState.FULLSCREEN;
|
2013-09-20 16:25:41 +09:00
|
|
|
var textLayer = this.textLayer =
|
|
|
|
textLayerDiv ? new TextLayerBuilder({
|
|
|
|
textLayerDiv: textLayerDiv,
|
|
|
|
pageIndex: this.id - 1,
|
2014-09-13 11:27:45 +09:00
|
|
|
lastScrollSource: this.linkService,
|
2013-09-20 16:25:41 +09:00
|
|
|
viewport: this.viewport,
|
2014-09-16 02:37:03 +09:00
|
|
|
isViewerInPresentationMode: isViewerInPresentationMode,
|
2014-08-07 00:09:27 +09:00
|
|
|
findController: PDFView.findController
|
2013-09-20 16:25:41 +09:00
|
|
|
}) : null;
|
|
|
|
// 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);
|
|
|
|
}
|
2013-09-21 04:11:14 +09:00
|
|
|
|
2013-09-20 16:25:41 +09:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2013-10-03 01:05:46 +09:00
|
|
|
if (self.zoomLayer) {
|
|
|
|
div.removeChild(self.zoomLayer);
|
|
|
|
self.zoomLayer = null;
|
|
|
|
}
|
|
|
|
|
2013-09-20 16:25:41 +09:00
|
|
|
//#if (FIREFOX || MOZCENTRAL)
|
|
|
|
// if (self.textLayer && self.textLayer.textDivs &&
|
|
|
|
// self.textLayer.textDivs.length > 0 &&
|
|
|
|
// !PDFView.supportsDocumentColors) {
|
|
|
|
// console.error(mozL10n.get('document_colors_disabled', null,
|
|
|
|
// 'PDF documents are not allowed to use their own colors: ' +
|
|
|
|
// '\'Allow pages to choose their own colors\' ' +
|
|
|
|
// 'is deactivated in the browser.'));
|
|
|
|
// PDFView.fallback();
|
|
|
|
// }
|
|
|
|
//#endif
|
|
|
|
if (error) {
|
|
|
|
PDFView.error(mozL10n.get('rendering_error', null,
|
|
|
|
'An error occurred while rendering the page.'), error);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.stats = pdfPage.stats;
|
|
|
|
self.updateStats();
|
2013-09-20 16:28:03 +09:00
|
|
|
if (self.onAfterDraw) {
|
2013-09-20 16:25:41 +09:00
|
|
|
self.onAfterDraw();
|
2013-09-20 16:28:03 +09:00
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
|
|
|
|
var event = document.createEvent('CustomEvent');
|
|
|
|
event.initCustomEvent('pagerender', true, true, {
|
|
|
|
pageNumber: pdfPage.pageNumber
|
|
|
|
});
|
|
|
|
div.dispatchEvent(event);
|
|
|
|
|
|
|
|
//#if (FIREFOX || MOZCENTRAL)
|
|
|
|
// FirefoxCom.request('reportTelemetry', JSON.stringify({
|
|
|
|
// type: 'pageInfo'
|
|
|
|
// }));
|
2014-06-16 23:52:04 +09:00
|
|
|
// // It is a good time to report stream and font types
|
|
|
|
// PDFView.pdfDocument.getStats().then(function (stats) {
|
|
|
|
// FirefoxCom.request('reportTelemetry', JSON.stringify({
|
|
|
|
// type: 'documentStats',
|
|
|
|
// stats: stats
|
|
|
|
// }));
|
|
|
|
// });
|
2013-09-20 16:25:41 +09:00
|
|
|
//#endif
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
|
|
|
|
var renderContext = {
|
|
|
|
canvasContext: ctx,
|
|
|
|
viewport: this.viewport,
|
2014-03-07 23:48:42 +09:00
|
|
|
// intent: 'default', // === 'display'
|
2013-09-20 16:25:41 +09:00
|
|
|
continueCallback: function pdfViewcContinueCallback(cont) {
|
2014-09-16 01:18:28 +09:00
|
|
|
if (!self.renderingQueue.isHighestPriority(self)) {
|
2013-09-20 16:25:41 +09:00
|
|
|
self.renderingState = RenderingStates.PAUSED;
|
|
|
|
self.resume = function resumeCallback() {
|
|
|
|
self.renderingState = RenderingStates.RUNNING;
|
|
|
|
cont();
|
|
|
|
};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
cont();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
var renderTask = this.renderTask = this.pdfPage.render(renderContext);
|
|
|
|
|
2014-01-04 09:17:05 +09:00
|
|
|
this.renderTask.promise.then(
|
2013-09-20 16:25:41 +09:00
|
|
|
function pdfPageRenderCallback() {
|
|
|
|
pageViewDrawCallback(null);
|
2014-04-10 08:44:07 +09:00
|
|
|
if (textLayer) {
|
2014-09-15 23:49:24 +09:00
|
|
|
self.pdfPage.getTextContent().then(
|
2014-04-10 08:44:07 +09:00
|
|
|
function textContentResolved(textContent) {
|
|
|
|
textLayer.setTextContent(textContent);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
},
|
|
|
|
function pdfPageRenderError(error) {
|
|
|
|
pageViewDrawCallback(error);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
setupAnnotations(div, pdfPage, this.viewport);
|
|
|
|
div.setAttribute('data-loaded', true);
|
2014-06-13 14:30:23 +09:00
|
|
|
|
|
|
|
// 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);
|
2013-09-20 16:25:41 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
2013-10-02 07:03:32 +09:00
|
|
|
var canvas = document.createElement('canvas');
|
2013-09-20 16:25:41 +09:00
|
|
|
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');
|
Add mozPrintCallback shim
This shim does the following:
1. Intercept window.print()
2. For a window.print() call (if allowed, ie. no previous print job):
1. Dispatch the beforeprint event.
2. Render a printg progress dialog.
3. For each canvas, call mozPrintCallback if existent (one at a time, async).
4. During each mozPrintCallback callback, update the progress dialog.
5. When all <canvas>es have been rendered, invoke the real window.print().
6. Dispatch the afterprint event.
The shim is not included in Firefox through the preprocessor.
Keyboard shortcuts (Ctrl/Cmd + P) are intercepted and default behavior
(i.e. printing) is prevented, and the steps for window.print() are run.
window.attachEvent is used, in order to cancel printing in IE10 and
earlier (courtesy of Stack Overflow - http://stackoverflow.com/a/15302847).
Unfortunately, this doesn't work in IE11 - if Ctrl + P is used, the
print dialog will be shown twice: Once because of Ctrl + P, and again
when all pages have finished rendering.
This logic of this polyfill is not specific to PDF.js, so it can also
be used in other projects.
There's one additional modification in PDF.js's viewer.js: The printed
<canvas> element is wrapped in a <div>. This is needed, because Chrome
would otherwise print one canvas on two pages.
2013-09-11 01:17:26 +09:00
|
|
|
var canvasWrapper = document.createElement('div');
|
|
|
|
canvasWrapper.style.width = viewport.width + 'pt';
|
|
|
|
canvasWrapper.style.height = viewport.height + 'pt';
|
|
|
|
canvasWrapper.appendChild(canvas);
|
|
|
|
printContainer.appendChild(canvasWrapper);
|
2013-09-20 16:25:41 +09:00
|
|
|
|
|
|
|
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,
|
2014-03-07 23:48:42 +09:00
|
|
|
viewport: viewport,
|
|
|
|
intent: 'print'
|
2013-09-20 16:25:41 +09:00
|
|
|
};
|
|
|
|
|
2014-01-04 09:17:05 +09:00
|
|
|
pdfPage.render(renderContext).promise.then(function() {
|
2013-09-20 16:25:41 +09:00
|
|
|
// 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.
|
2013-09-20 16:28:03 +09:00
|
|
|
if ('abort' in obj) {
|
2013-09-20 16:25:41 +09:00
|
|
|
obj.abort();
|
2013-09-20 16:28:03 +09:00
|
|
|
} else {
|
2013-09-20 16:25:41 +09:00
|
|
|
obj.done();
|
2013-09-20 16:28:03 +09:00
|
|
|
}
|
2013-09-20 16:25:41 +09:00
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
this.updateStats = function pageViewUpdateStats() {
|
|
|
|
if (!this.stats) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PDFJS.pdfBug && Stats.enabled) {
|
|
|
|
var stats = this.stats;
|
|
|
|
Stats.add(this.id, stats);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|