f461fd64aa
This patch implements the page label functionality in a similar way as Adobe Reader. For documents with page labels, if a non-existent page label is entered we'll try to fallback to the page number instead. The patch also includes a preference (`disablePageLabels`), to make it easy to opt-out of using page labels if the user/implementor so wishes. The way that `get/set currentPageLabel` is implemented in `PDFViewer`, is as wrappers for the corresponding `get/set currentPageNumber` functions, since that seemed like the cleanest solution. The page labels are purposely *only* added to the page controls in the viewer UI, and not stored in e.g. the `ViewHistory`. Since doing so would mean adding unnecessary code complexity, without any real added value, and would also mean delaying the inital loading of PDF documents. Note that this patch will ignore page labels if they are identical to standard page numbering, since in this case displaying the page labels adds no value (but only UI noise). The reason for handling this case specially, is that in practice a surprising number of PDF files include "pointless" page labels.
959 lines
31 KiB
JavaScript
959 lines
31 KiB
JavaScript
/* 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';
|
|
|
|
(function (root, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define('pdfjs-web/pdf_viewer', ['exports', 'pdfjs-web/ui_utils',
|
|
'pdfjs-web/pdf_page_view', 'pdfjs-web/pdf_rendering_queue',
|
|
'pdfjs-web/text_layer_builder', 'pdfjs-web/annotation_layer_builder',
|
|
'pdfjs-web/pdf_link_service', 'pdfjs-web/dom_events', 'pdfjs-web/pdfjs'],
|
|
factory);
|
|
} else if (typeof exports !== 'undefined') {
|
|
factory(exports, require('./ui_utils.js'), require('./pdf_page_view.js'),
|
|
require('./pdf_rendering_queue.js'), require('./text_layer_builder.js'),
|
|
require('./annotation_layer_builder.js'),
|
|
require('./pdf_link_service.js'), require('./dom_events.js'),
|
|
require('./pdfjs.js'));
|
|
} else {
|
|
factory((root.pdfjsWebPDFViewer = {}), root.pdfjsWebUIUtils,
|
|
root.pdfjsWebPDFPageView, root.pdfjsWebPDFRenderingQueue,
|
|
root.pdfjsWebTextLayerBuilder, root.pdfjsWebAnnotationLayerBuilder,
|
|
root.pdfjsWebPDFLinkService, root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS);
|
|
}
|
|
}(this, function (exports, uiUtils, pdfPageView, pdfRenderingQueue,
|
|
textLayerBuilder, annotationLayerBuilder, pdfLinkService,
|
|
domEvents, pdfjsLib) {
|
|
|
|
var UNKNOWN_SCALE = uiUtils.UNKNOWN_SCALE;
|
|
var SCROLLBAR_PADDING = uiUtils.SCROLLBAR_PADDING;
|
|
var VERTICAL_PADDING = uiUtils.VERTICAL_PADDING;
|
|
var MAX_AUTO_SCALE = uiUtils.MAX_AUTO_SCALE;
|
|
var CSS_UNITS = uiUtils.CSS_UNITS;
|
|
var DEFAULT_SCALE = uiUtils.DEFAULT_SCALE;
|
|
var DEFAULT_SCALE_VALUE = uiUtils.DEFAULT_SCALE_VALUE;
|
|
var scrollIntoView = uiUtils.scrollIntoView;
|
|
var watchScroll = uiUtils.watchScroll;
|
|
var getVisibleElements = uiUtils.getVisibleElements;
|
|
var PDFPageView = pdfPageView.PDFPageView;
|
|
var RenderingStates = pdfRenderingQueue.RenderingStates;
|
|
var PDFRenderingQueue = pdfRenderingQueue.PDFRenderingQueue;
|
|
var TextLayerBuilder = textLayerBuilder.TextLayerBuilder;
|
|
var AnnotationLayerBuilder = annotationLayerBuilder.AnnotationLayerBuilder;
|
|
var SimpleLinkService = pdfLinkService.SimpleLinkService;
|
|
|
|
var PresentationModeState = {
|
|
UNKNOWN: 0,
|
|
NORMAL: 1,
|
|
CHANGING: 2,
|
|
FULLSCREEN: 3,
|
|
};
|
|
|
|
var DEFAULT_CACHE_SIZE = 10;
|
|
|
|
/**
|
|
* @typedef {Object} PDFViewerOptions
|
|
* @property {HTMLDivElement} container - The container for the viewer element.
|
|
* @property {HTMLDivElement} viewer - (optional) The viewer element.
|
|
* @property {EventBus} eventBus - The application event bus.
|
|
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
|
* @property {DownloadManager} downloadManager - (optional) The download
|
|
* manager component.
|
|
* @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
|
|
* queue object.
|
|
* @property {boolean} removePageBorders - (optional) Removes the border shadow
|
|
* around the pages. The default is false.
|
|
* @property {boolean} enhanceTextSelection - (optional) Enables the improved
|
|
* text selection behaviour. The default is `false`.
|
|
* @property {boolean} renderInteractiveForms - (optional) Enables rendering of
|
|
* interactive form elements. The default is `false`.
|
|
*/
|
|
|
|
/**
|
|
* Simple viewer control to display PDF content/pages.
|
|
* @class
|
|
* @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();
|
|
}
|
|
};
|
|
}
|
|
|
|
function isSameScale(oldScale, newScale) {
|
|
if (newScale === oldScale) {
|
|
return true;
|
|
}
|
|
if (Math.abs(newScale - oldScale) < 1e-15) {
|
|
// Prevent unnecessary re-rendering of all pages when the scale
|
|
// changes only because of limited numerical precision.
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @constructs PDFViewer
|
|
* @param {PDFViewerOptions} options
|
|
*/
|
|
function PDFViewer(options) {
|
|
this.container = options.container;
|
|
this.viewer = options.viewer || options.container.firstElementChild;
|
|
this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
|
|
this.linkService = options.linkService || new SimpleLinkService();
|
|
this.downloadManager = options.downloadManager || null;
|
|
this.removePageBorders = options.removePageBorders || false;
|
|
this.enhanceTextSelection = options.enhanceTextSelection || false;
|
|
this.renderInteractiveForms = options.renderInteractiveForms || false;
|
|
|
|
this.defaultRenderingQueue = !options.renderingQueue;
|
|
if (this.defaultRenderingQueue) {
|
|
// Custom rendering queue is not specified, using default one
|
|
this.renderingQueue = new PDFRenderingQueue();
|
|
this.renderingQueue.setViewer(this);
|
|
} else {
|
|
this.renderingQueue = options.renderingQueue;
|
|
}
|
|
|
|
this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
|
|
this.presentationModeState = PresentationModeState.UNKNOWN;
|
|
this._resetView();
|
|
|
|
if (this.removePageBorders) {
|
|
this.viewer.classList.add('removePageBorders');
|
|
}
|
|
}
|
|
|
|
PDFViewer.prototype = /** @lends PDFViewer.prototype */{
|
|
get pagesCount() {
|
|
return this._pages.length;
|
|
},
|
|
|
|
getPageView: function (index) {
|
|
return this._pages[index];
|
|
},
|
|
|
|
/**
|
|
* @returns {boolean} true if all {PDFPageView} objects are initialized.
|
|
*/
|
|
get pageViewsReady() {
|
|
return this._pageViewsReady;
|
|
},
|
|
|
|
/**
|
|
* @returns {number}
|
|
*/
|
|
get currentPageNumber() {
|
|
return this._currentPageNumber;
|
|
},
|
|
|
|
/**
|
|
* @param {number} val - The page number.
|
|
*/
|
|
set currentPageNumber(val) {
|
|
if ((val | 0) !== val) { // Ensure that `val` is an integer.
|
|
throw new Error('Invalid page number.');
|
|
}
|
|
if (!this.pdfDocument) {
|
|
this._currentPageNumber = val;
|
|
return;
|
|
}
|
|
// The intent can be to just reset a scroll position and/or scale.
|
|
this._setCurrentPageNumber(val, /* resetCurrentPageView = */ true);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_setCurrentPageNumber:
|
|
function PDFViewer_setCurrentPageNumber(val, resetCurrentPageView) {
|
|
if (this._currentPageNumber === val) {
|
|
if (resetCurrentPageView) {
|
|
this._resetCurrentPageView();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!(0 < val && val <= this.pagesCount)) {
|
|
console.error('PDFViewer_setCurrentPageNumber: "' + val +
|
|
'" is out of bounds.');
|
|
return;
|
|
}
|
|
|
|
var arg = {
|
|
source: this,
|
|
pageNumber: val,
|
|
pageLabel: this._pageLabels && this._pageLabels[val - 1],
|
|
};
|
|
this._currentPageNumber = val;
|
|
this.eventBus.dispatch('pagechanging', arg);
|
|
this.eventBus.dispatch('pagechange', arg);
|
|
|
|
if (resetCurrentPageView) {
|
|
this._resetCurrentPageView();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @returns {string|null} Returns the current page label,
|
|
* or `null` if no page labels exist.
|
|
*/
|
|
get currentPageLabel() {
|
|
return this._pageLabels && this._pageLabels[this._currentPageNumber - 1];
|
|
},
|
|
|
|
/**
|
|
* @param {string} val - The page label.
|
|
*/
|
|
set currentPageLabel(val) {
|
|
var pageNumber = val | 0; // Fallback page number.
|
|
if (this._pageLabels) {
|
|
var i = this._pageLabels.indexOf(val);
|
|
if (i >= 0) {
|
|
pageNumber = i + 1;
|
|
}
|
|
}
|
|
this.currentPageNumber = pageNumber;
|
|
},
|
|
|
|
/**
|
|
* @returns {number}
|
|
*/
|
|
get currentScale() {
|
|
return this._currentScale !== UNKNOWN_SCALE ? this._currentScale :
|
|
DEFAULT_SCALE;
|
|
},
|
|
|
|
/**
|
|
* @param {number} val - Scale of the pages in percents.
|
|
*/
|
|
set currentScale(val) {
|
|
if (isNaN(val)) {
|
|
throw new Error('Invalid numeric scale');
|
|
}
|
|
if (!this.pdfDocument) {
|
|
this._currentScale = val;
|
|
this._currentScaleValue = val !== UNKNOWN_SCALE ? val.toString() : null;
|
|
return;
|
|
}
|
|
this._setScale(val, false);
|
|
},
|
|
|
|
/**
|
|
* @returns {string}
|
|
*/
|
|
get currentScaleValue() {
|
|
return this._currentScaleValue;
|
|
},
|
|
|
|
/**
|
|
* @param val - The scale of the pages (in percent or predefined value).
|
|
*/
|
|
set currentScaleValue(val) {
|
|
if (!this.pdfDocument) {
|
|
this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
|
|
this._currentScaleValue = val.toString();
|
|
return;
|
|
}
|
|
this._setScale(val, false);
|
|
},
|
|
|
|
/**
|
|
* @returns {number}
|
|
*/
|
|
get pagesRotation() {
|
|
return this._pagesRotation;
|
|
},
|
|
|
|
/**
|
|
* @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
|
|
*/
|
|
set pagesRotation(rotation) {
|
|
if (!(typeof rotation === 'number' && rotation % 90 === 0)) {
|
|
throw new Error('Invalid pages rotation angle.');
|
|
}
|
|
this._pagesRotation = rotation;
|
|
|
|
if (!this.pdfDocument) {
|
|
return;
|
|
}
|
|
for (var i = 0, l = this._pages.length; i < l; i++) {
|
|
var pageView = this._pages[i];
|
|
pageView.update(pageView.scale, rotation);
|
|
}
|
|
|
|
this._setScale(this._currentScaleValue, true);
|
|
|
|
if (this.defaultRenderingQueue) {
|
|
this.update();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param pdfDocument {PDFDocument}
|
|
*/
|
|
setDocument: function (pdfDocument) {
|
|
if (this.pdfDocument) {
|
|
this._cancelRendering();
|
|
this._resetView();
|
|
}
|
|
|
|
this.pdfDocument = pdfDocument;
|
|
if (!pdfDocument) {
|
|
return;
|
|
}
|
|
|
|
var pagesCount = pdfDocument.numPages;
|
|
var self = this;
|
|
|
|
var resolvePagesPromise;
|
|
var pagesPromise = new Promise(function (resolve) {
|
|
resolvePagesPromise = resolve;
|
|
});
|
|
this.pagesPromise = pagesPromise;
|
|
pagesPromise.then(function () {
|
|
self._pageViewsReady = true;
|
|
self.eventBus.dispatch('pagesloaded', {
|
|
source: self,
|
|
pagesCount: pagesCount
|
|
});
|
|
});
|
|
|
|
var isOnePageRenderedResolved = false;
|
|
var resolveOnePageRendered = null;
|
|
var onePageRendered = new Promise(function (resolve) {
|
|
resolveOnePageRendered = resolve;
|
|
});
|
|
this.onePageRendered = onePageRendered;
|
|
|
|
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);
|
|
};
|
|
pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
|
|
if (!isOnePageRenderedResolved) {
|
|
isOnePageRenderedResolved = true;
|
|
resolveOnePageRendered();
|
|
}
|
|
};
|
|
};
|
|
|
|
var firstPagePromise = pdfDocument.getPage(1);
|
|
this.firstPagePromise = firstPagePromise;
|
|
|
|
// Fetch a single page so we can get a viewport that will be the default
|
|
// viewport for all pages
|
|
return firstPagePromise.then(function(pdfPage) {
|
|
var scale = this.currentScale;
|
|
var viewport = pdfPage.getViewport(scale * CSS_UNITS);
|
|
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
|
|
var textLayerFactory = null;
|
|
if (!pdfjsLib.PDFJS.disableTextLayer) {
|
|
textLayerFactory = this;
|
|
}
|
|
var pageView = new PDFPageView({
|
|
container: this.viewer,
|
|
eventBus: this.eventBus,
|
|
id: pageNum,
|
|
scale: scale,
|
|
defaultViewport: viewport.clone(),
|
|
renderingQueue: this.renderingQueue,
|
|
textLayerFactory: textLayerFactory,
|
|
annotationLayerFactory: this,
|
|
enhanceTextSelection: this.enhanceTextSelection,
|
|
renderInteractiveForms: this.renderInteractiveForms,
|
|
});
|
|
bindOnAfterAndBeforeDraw(pageView);
|
|
this._pages.push(pageView);
|
|
}
|
|
|
|
var linkService = this.linkService;
|
|
|
|
// Fetch all the pages since the viewport is needed before printing
|
|
// starts to create the correct size canvas. Wait until one page is
|
|
// rendered so we don't tie up too many resources early on.
|
|
onePageRendered.then(function () {
|
|
if (!pdfjsLib.PDFJS.disableAutoFetch) {
|
|
var getPagesLeft = pagesCount;
|
|
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
|
|
pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
|
|
var pageView = self._pages[pageNum - 1];
|
|
if (!pageView.pdfPage) {
|
|
pageView.setPdfPage(pdfPage);
|
|
}
|
|
linkService.cachePageRef(pageNum, pdfPage.ref);
|
|
getPagesLeft--;
|
|
if (!getPagesLeft) {
|
|
resolvePagesPromise();
|
|
}
|
|
}.bind(null, pageNum));
|
|
}
|
|
} else {
|
|
// XXX: Printing is semi-broken with auto fetch disabled.
|
|
resolvePagesPromise();
|
|
}
|
|
});
|
|
|
|
self.eventBus.dispatch('pagesinit', {source: self});
|
|
|
|
if (this.defaultRenderingQueue) {
|
|
this.update();
|
|
}
|
|
|
|
if (this.findController) {
|
|
this.findController.resolveFirstPage();
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* @param {Array|null} labels
|
|
*/
|
|
setPageLabels: function PDFViewer_setPageLabels(labels) {
|
|
if (!this.pdfDocument) {
|
|
return;
|
|
}
|
|
if (!labels) {
|
|
this._pageLabels = null;
|
|
} else if (!(labels instanceof Array &&
|
|
this.pdfDocument.numPages === labels.length)) {
|
|
this._pageLabels = null;
|
|
console.error('PDFViewer_setPageLabels: Invalid page labels.');
|
|
} else {
|
|
this._pageLabels = labels;
|
|
}
|
|
},
|
|
|
|
_resetView: function () {
|
|
this._pages = [];
|
|
this._currentPageNumber = 1;
|
|
this._currentScale = UNKNOWN_SCALE;
|
|
this._currentScaleValue = null;
|
|
this._pageLabels = null;
|
|
this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
|
|
this._location = null;
|
|
this._pagesRotation = 0;
|
|
this._pagesRequests = [];
|
|
this._pageViewsReady = false;
|
|
|
|
// Remove the pages from the DOM.
|
|
this.viewer.textContent = '';
|
|
},
|
|
|
|
_scrollUpdate: function PDFViewer_scrollUpdate() {
|
|
if (this.pagesCount === 0) {
|
|
return;
|
|
}
|
|
this.update();
|
|
for (var i = 0, ii = this._pages.length; i < ii; i++) {
|
|
this._pages[i].updatePosition();
|
|
}
|
|
},
|
|
|
|
_setScaleDispatchEvent: function pdfViewer_setScaleDispatchEvent(
|
|
newScale, newValue, preset) {
|
|
var arg = {
|
|
source: this,
|
|
scale: newScale,
|
|
presetValue: preset ? newValue : undefined
|
|
};
|
|
this.eventBus.dispatch('scalechanging', arg);
|
|
this.eventBus.dispatch('scalechange', arg);
|
|
},
|
|
|
|
_setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
|
|
newScale, newValue, noScroll, preset) {
|
|
this._currentScaleValue = newValue.toString();
|
|
|
|
if (isSameScale(this._currentScale, newScale)) {
|
|
if (preset) {
|
|
this._setScaleDispatchEvent(newScale, newValue, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (var i = 0, ii = this._pages.length; i < ii; i++) {
|
|
this._pages[i].update(newScale);
|
|
}
|
|
this._currentScale = newScale;
|
|
|
|
if (!noScroll) {
|
|
var page = this._currentPageNumber, dest;
|
|
if (this._location && !pdfjsLib.PDFJS.ignoreCurrentPositionOnZoom &&
|
|
!(this.isInPresentationMode || this.isChangingPresentationMode)) {
|
|
page = this._location.pageNumber;
|
|
dest = [null, { name: 'XYZ' }, this._location.left,
|
|
this._location.top, null];
|
|
}
|
|
this.scrollPageIntoView({
|
|
pageNumber: page,
|
|
destArray: dest,
|
|
allowNegativeOffset: true,
|
|
});
|
|
}
|
|
|
|
this._setScaleDispatchEvent(newScale, newValue, preset);
|
|
|
|
if (this.defaultRenderingQueue) {
|
|
this.update();
|
|
}
|
|
},
|
|
|
|
_setScale: function PDFViewer_setScale(value, noScroll) {
|
|
var scale = parseFloat(value);
|
|
|
|
if (scale > 0) {
|
|
this._setScaleUpdatePages(scale, value, noScroll, false);
|
|
} else {
|
|
var currentPage = this._pages[this._currentPageNumber - 1];
|
|
if (!currentPage) {
|
|
return;
|
|
}
|
|
var hPadding = (this.isInPresentationMode || this.removePageBorders) ?
|
|
0 : SCROLLBAR_PADDING;
|
|
var vPadding = (this.isInPresentationMode || this.removePageBorders) ?
|
|
0 : VERTICAL_PADDING;
|
|
var pageWidthScale = (this.container.clientWidth - hPadding) /
|
|
currentPage.width * currentPage.scale;
|
|
var pageHeightScale = (this.container.clientHeight - vPadding) /
|
|
currentPage.height * currentPage.scale;
|
|
switch (value) {
|
|
case 'page-actual':
|
|
scale = 1;
|
|
break;
|
|
case 'page-width':
|
|
scale = pageWidthScale;
|
|
break;
|
|
case 'page-height':
|
|
scale = pageHeightScale;
|
|
break;
|
|
case 'page-fit':
|
|
scale = Math.min(pageWidthScale, pageHeightScale);
|
|
break;
|
|
case 'auto':
|
|
var isLandscape = (currentPage.width > currentPage.height);
|
|
// For pages in landscape mode, fit the page height to the viewer
|
|
// *unless* the page would thus become too wide to fit horizontally.
|
|
var horizontalScale = isLandscape ?
|
|
Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
|
|
scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
|
|
break;
|
|
default:
|
|
console.error('PDFViewer_setScale: "' + value +
|
|
'" is an unknown zoom value.');
|
|
return;
|
|
}
|
|
this._setScaleUpdatePages(scale, value, noScroll, true);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Refreshes page view: scrolls to the current page and updates the scale.
|
|
* @private
|
|
*/
|
|
_resetCurrentPageView: function () {
|
|
if (this.isInPresentationMode) {
|
|
// Fixes the case when PDF has different page sizes.
|
|
this._setScale(this._currentScaleValue, true);
|
|
}
|
|
|
|
var pageView = this._pages[this._currentPageNumber - 1];
|
|
scrollIntoView(pageView.div);
|
|
},
|
|
|
|
/**
|
|
* @typedef ScrollPageIntoViewParameters
|
|
* @property {number} pageNumber - The page number.
|
|
* @property {Array} destArray - (optional) The original PDF destination
|
|
* array, in the format: <page-ref> </XYZ|/FitXXX> <args..>
|
|
* @property {boolean} allowNegativeOffset - (optional) Allow negative page
|
|
* offsets. The default value is `false`.
|
|
*/
|
|
|
|
/**
|
|
* Scrolls page into view.
|
|
* @param {ScrollPageIntoViewParameters} params
|
|
*/
|
|
scrollPageIntoView: function PDFViewer_scrollPageIntoView(params) {
|
|
if (!this.pdfDocument) {
|
|
return;
|
|
}
|
|
if ((typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) &&
|
|
(arguments.length > 1 || typeof params === 'number')) {
|
|
console.warn('Call of scrollPageIntoView() with obsolete signature.');
|
|
var paramObj = {};
|
|
if (typeof params === 'number') {
|
|
paramObj.pageNumber = params; // pageNumber argument was found.
|
|
}
|
|
if (arguments[1] instanceof Array) {
|
|
paramObj.destArray = arguments[1]; // destArray argument was found.
|
|
}
|
|
params = paramObj;
|
|
}
|
|
var pageNumber = params.pageNumber || 0;
|
|
var dest = params.destArray || null;
|
|
var allowNegativeOffset = params.allowNegativeOffset || false;
|
|
|
|
if (this.isInPresentationMode || !dest) {
|
|
this._setCurrentPageNumber(pageNumber, /* resetCurrentPageView */ true);
|
|
return;
|
|
}
|
|
|
|
var pageView = this._pages[pageNumber - 1];
|
|
if (!pageView) {
|
|
console.error('PDFViewer_scrollPageIntoView: ' +
|
|
'Invalid "pageNumber" parameter.');
|
|
return;
|
|
}
|
|
var x = 0, y = 0;
|
|
var width = 0, height = 0, widthScale, heightScale;
|
|
var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
|
|
var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
|
|
pageView.scale / CSS_UNITS;
|
|
var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
|
|
pageView.scale / CSS_UNITS;
|
|
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;
|
|
y = y !== null ? y : pageHeight;
|
|
break;
|
|
case 'Fit':
|
|
case 'FitB':
|
|
scale = 'page-fit';
|
|
break;
|
|
case 'FitH':
|
|
case 'FitBH':
|
|
y = dest[2];
|
|
scale = 'page-width';
|
|
// According to the PDF spec, section 12.3.2.2, a `null` value in the
|
|
// parameter should maintain the position relative to the new page.
|
|
if (y === null && this._location) {
|
|
x = this._location.left;
|
|
y = this._location.top;
|
|
}
|
|
break;
|
|
case 'FitV':
|
|
case 'FitBV':
|
|
x = dest[2];
|
|
width = pageWidth;
|
|
height = pageHeight;
|
|
scale = 'page-height';
|
|
break;
|
|
case 'FitR':
|
|
x = dest[2];
|
|
y = dest[3];
|
|
width = dest[4] - x;
|
|
height = dest[5] - y;
|
|
var hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
|
|
var vPadding = this.removePageBorders ? 0 : VERTICAL_PADDING;
|
|
|
|
widthScale = (this.container.clientWidth - hPadding) /
|
|
width / CSS_UNITS;
|
|
heightScale = (this.container.clientHeight - vPadding) /
|
|
height / CSS_UNITS;
|
|
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
|
|
break;
|
|
default:
|
|
console.error('PDFViewer_scrollPageIntoView: \'' + dest[1].name +
|
|
'\' is not a valid destination type.');
|
|
return;
|
|
}
|
|
|
|
if (scale && scale !== this._currentScale) {
|
|
this.currentScaleValue = scale;
|
|
} else if (this._currentScale === UNKNOWN_SCALE) {
|
|
this.currentScaleValue = DEFAULT_SCALE_VALUE;
|
|
}
|
|
|
|
if (scale === 'page-fit' && !dest[4]) {
|
|
scrollIntoView(pageView.div);
|
|
return;
|
|
}
|
|
|
|
var boundingRect = [
|
|
pageView.viewport.convertToViewportPoint(x, y),
|
|
pageView.viewport.convertToViewportPoint(x + width, y + height)
|
|
];
|
|
var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
|
|
var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
|
|
|
|
if (!allowNegativeOffset) {
|
|
// Some bad PDF generators will create destinations with e.g. top values
|
|
// that exceeds the page height. Ensure that offsets are not negative,
|
|
// to prevent a previous page from becoming visible (fixes bug 874482).
|
|
left = Math.max(left, 0);
|
|
top = Math.max(top, 0);
|
|
}
|
|
scrollIntoView(pageView.div, { left: left, top: top });
|
|
},
|
|
|
|
_updateLocation: function (firstPage) {
|
|
var currentScale = this._currentScale;
|
|
var currentScaleValue = this._currentScaleValue;
|
|
var normalizedScaleValue =
|
|
parseFloat(currentScaleValue) === currentScale ?
|
|
Math.round(currentScale * 10000) / 100 : currentScaleValue;
|
|
|
|
var pageNumber = firstPage.id;
|
|
var pdfOpenParams = '#page=' + pageNumber;
|
|
pdfOpenParams += '&zoom=' + normalizedScaleValue;
|
|
var currentPageView = this._pages[pageNumber - 1];
|
|
var container = this.container;
|
|
var topLeft = currentPageView.getPagePoint(
|
|
(container.scrollLeft - firstPage.x),
|
|
(container.scrollTop - firstPage.y));
|
|
var intLeft = Math.round(topLeft[0]);
|
|
var intTop = Math.round(topLeft[1]);
|
|
pdfOpenParams += ',' + intLeft + ',' + intTop;
|
|
|
|
this._location = {
|
|
pageNumber: pageNumber,
|
|
scale: normalizedScaleValue,
|
|
top: intTop,
|
|
left: intLeft,
|
|
pdfOpenParams: pdfOpenParams
|
|
};
|
|
},
|
|
|
|
update: function PDFViewer_update() {
|
|
var visible = this._getVisiblePages();
|
|
var visiblePages = visible.views;
|
|
if (visiblePages.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
|
|
2 * visiblePages.length + 1);
|
|
this._buffer.resize(suggestedCacheSize);
|
|
|
|
this.renderingQueue.renderHighestPriority(visible);
|
|
|
|
var currentId = this._currentPageNumber;
|
|
var firstPage = visible.first;
|
|
|
|
for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
|
|
i < ii; ++i) {
|
|
var page = visiblePages[i];
|
|
|
|
if (page.percent < 100) {
|
|
break;
|
|
}
|
|
if (page.id === currentId) {
|
|
stillFullyVisible = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!stillFullyVisible) {
|
|
currentId = visiblePages[0].id;
|
|
}
|
|
|
|
if (!this.isInPresentationMode) {
|
|
this._setCurrentPageNumber(currentId);
|
|
}
|
|
|
|
this._updateLocation(firstPage);
|
|
|
|
this.eventBus.dispatch('updateviewarea', {
|
|
source: this,
|
|
location: this._location
|
|
});
|
|
},
|
|
|
|
containsElement: function (element) {
|
|
return this.container.contains(element);
|
|
},
|
|
|
|
focus: function () {
|
|
this.container.focus();
|
|
},
|
|
|
|
get isInPresentationMode() {
|
|
return this.presentationModeState === PresentationModeState.FULLSCREEN;
|
|
},
|
|
|
|
get isChangingPresentationMode() {
|
|
return this.presentationModeState === PresentationModeState.CHANGING;
|
|
},
|
|
|
|
get isHorizontalScrollbarEnabled() {
|
|
return (this.isInPresentationMode ?
|
|
false : (this.container.scrollWidth > this.container.clientWidth));
|
|
},
|
|
|
|
_getVisiblePages: function () {
|
|
if (!this.isInPresentationMode) {
|
|
return getVisibleElements(this.container, this._pages, true);
|
|
} else {
|
|
// The algorithm in getVisibleElements doesn't work in all browsers and
|
|
// configurations when presentation mode is active.
|
|
var visible = [];
|
|
var currentPage = this._pages[this._currentPageNumber - 1];
|
|
visible.push({ id: currentPage.id, view: currentPage });
|
|
return { first: currentPage, last: currentPage, views: visible };
|
|
}
|
|
},
|
|
|
|
cleanup: function () {
|
|
for (var i = 0, ii = this._pages.length; i < ii; i++) {
|
|
if (this._pages[i] &&
|
|
this._pages[i].renderingState !== RenderingStates.FINISHED) {
|
|
this._pages[i].reset();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_cancelRendering: function PDFViewer_cancelRendering() {
|
|
for (var i = 0, ii = this._pages.length; i < ii; i++) {
|
|
if (this._pages[i]) {
|
|
this._pages[i].cancelRendering();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @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._ensurePdfPageLoaded(pageView).then(function () {
|
|
this.renderingQueue.renderView(pageView);
|
|
}.bind(this));
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
getPageTextContent: function (pageIndex) {
|
|
return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
|
|
return page.getTextContent({
|
|
normalizeWhitespace: true,
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param {HTMLDivElement} textLayerDiv
|
|
* @param {number} pageIndex
|
|
* @param {PageViewport} viewport
|
|
* @returns {TextLayerBuilder}
|
|
*/
|
|
createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport,
|
|
enhanceTextSelection) {
|
|
return new TextLayerBuilder({
|
|
textLayerDiv: textLayerDiv,
|
|
eventBus: this.eventBus,
|
|
pageIndex: pageIndex,
|
|
viewport: viewport,
|
|
findController: this.isInPresentationMode ? null : this.findController,
|
|
enhanceTextSelection: this.isInPresentationMode ? false :
|
|
enhanceTextSelection,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param {HTMLDivElement} pageDiv
|
|
* @param {PDFPage} pdfPage
|
|
* @param {boolean} renderInteractiveForms
|
|
* @returns {AnnotationLayerBuilder}
|
|
*/
|
|
createAnnotationLayerBuilder: function (pageDiv, pdfPage,
|
|
renderInteractiveForms) {
|
|
return new AnnotationLayerBuilder({
|
|
pageDiv: pageDiv,
|
|
pdfPage: pdfPage,
|
|
renderInteractiveForms: renderInteractiveForms,
|
|
linkService: this.linkService,
|
|
downloadManager: this.downloadManager
|
|
});
|
|
},
|
|
|
|
setFindController: function (findController) {
|
|
this.findController = findController;
|
|
},
|
|
|
|
/**
|
|
* Returns sizes of the pages.
|
|
* @returns {Array} Array of objects with width/height fields.
|
|
*/
|
|
getPagesOverview: function () {
|
|
return this._pages.map(function (pageView) {
|
|
var viewport = pageView.pdfPage.getViewport(1);
|
|
return {width: viewport.width, height: viewport.height};
|
|
});
|
|
},
|
|
};
|
|
|
|
return PDFViewer;
|
|
})();
|
|
|
|
exports.PresentationModeState = PresentationModeState;
|
|
exports.PDFViewer = PDFViewer;
|
|
}));
|