2014-09-13 11:27:45 +09:00
|
|
|
/* 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.
|
|
|
|
*/
|
|
|
|
|
2017-03-28 08:07:27 +09:00
|
|
|
import {
|
2018-09-21 03:51:50 +09:00
|
|
|
CSS_UNITS, DEFAULT_SCALE, DEFAULT_SCALE_VALUE, getGlobalEventBus,
|
|
|
|
isPortraitOrientation, isValidRotation, MAX_AUTO_SCALE, moveToEndOfArray,
|
|
|
|
NullL10n, PresentationModeState, RendererType, SCROLLBAR_PADDING,
|
|
|
|
TextLayerMode, UNKNOWN_SCALE, VERTICAL_PADDING, watchScroll
|
2017-04-15 00:32:36 +09:00
|
|
|
} from './ui_utils';
|
2017-04-15 19:57:54 +09:00
|
|
|
import { PDFRenderingQueue, RenderingStates } from './pdf_rendering_queue';
|
2017-04-15 00:32:36 +09:00
|
|
|
import { AnnotationLayerBuilder } from './annotation_layer_builder';
|
2018-02-18 06:08:45 +09:00
|
|
|
import { createPromiseCapability } from 'pdfjs-lib';
|
2017-04-15 00:32:36 +09:00
|
|
|
import { PDFPageView } from './pdf_page_view';
|
|
|
|
import { SimpleLinkService } from './pdf_link_service';
|
|
|
|
import { TextLayerBuilder } from './text_layer_builder';
|
2016-04-09 02:34:27 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
const DEFAULT_CACHE_SIZE = 10;
|
2014-09-16 05:46:01 +09:00
|
|
|
|
2018-05-15 12:10:32 +09:00
|
|
|
const ScrollMode = {
|
|
|
|
VERTICAL: 0, // The default value.
|
|
|
|
HORIZONTAL: 1,
|
|
|
|
WRAPPED: 2,
|
|
|
|
};
|
|
|
|
|
2018-05-15 12:10:32 +09:00
|
|
|
const SpreadMode = {
|
|
|
|
NONE: 0, // The default value.
|
|
|
|
ODD: 1,
|
|
|
|
EVEN: 2,
|
|
|
|
};
|
|
|
|
|
2014-09-21 02:21:49 +09:00
|
|
|
/**
|
|
|
|
* @typedef {Object} PDFViewerOptions
|
|
|
|
* @property {HTMLDivElement} container - The container for the viewer element.
|
|
|
|
* @property {HTMLDivElement} viewer - (optional) The viewer element.
|
2016-04-26 07:57:15 +09:00
|
|
|
* @property {EventBus} eventBus - The application event bus.
|
2014-09-21 02:21:49 +09:00
|
|
|
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
2016-02-15 04:44:00 +09:00
|
|
|
* @property {DownloadManager} downloadManager - (optional) The download
|
|
|
|
* manager component.
|
2018-09-23 02:44:13 +09:00
|
|
|
* @property {PDFFindController} findController - (optional) The find
|
|
|
|
* controller component.
|
2014-09-21 02:21:49 +09:00
|
|
|
* @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
|
|
|
|
* queue object.
|
2015-03-12 04:36:01 +09:00
|
|
|
* @property {boolean} removePageBorders - (optional) Removes the border shadow
|
2018-02-13 23:01:55 +09:00
|
|
|
* around the pages. The default value is `false`.
|
|
|
|
* @property {number} textLayerMode - (optional) Controls if the text layer used
|
|
|
|
* for selection and searching is created, and if the improved text selection
|
|
|
|
* behaviour is enabled. The constants from {TextLayerMode} should be used.
|
|
|
|
* The default value is `TextLayerMode.ENABLE`.
|
2018-02-13 21:17:11 +09:00
|
|
|
* @property {string} imageResourcesPath - (optional) Path for image resources,
|
|
|
|
* mainly for annotation icons. Include trailing slash.
|
2016-09-18 02:44:25 +09:00
|
|
|
* @property {boolean} renderInteractiveForms - (optional) Enables rendering of
|
|
|
|
* interactive form elements. The default is `false`.
|
2017-02-08 08:15:09 +09:00
|
|
|
* @property {boolean} enablePrintAutoRotate - (optional) Enables automatic
|
|
|
|
* rotation of pages whose orientation differ from the first page upon
|
|
|
|
* printing. The default is `false`.
|
2016-11-19 04:03:49 +09:00
|
|
|
* @property {string} renderer - 'canvas' or 'svg'. The default is 'canvas'.
|
2018-02-13 22:16:10 +09:00
|
|
|
* @property {boolean} enableWebGL - (optional) Enables WebGL accelerated
|
|
|
|
* rendering for some operations. The default value is `false`.
|
2018-02-13 20:52:42 +09:00
|
|
|
* @property {boolean} useOnlyCssZoom - (optional) Enables CSS only zooming.
|
|
|
|
* The default value is `false`.
|
2018-02-13 21:02:15 +09:00
|
|
|
* @property {number} maxCanvasPixels - (optional) The maximum supported canvas
|
|
|
|
* size in total pixels, i.e. width * height. Use -1 for no limit.
|
|
|
|
* The default value is 4096 * 4096 (16 mega-pixels).
|
2017-05-04 10:05:53 +09:00
|
|
|
* @property {IL10n} l10n - Localization service.
|
2014-09-21 02:21:49 +09:00
|
|
|
*/
|
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
function PDFPageViewBuffer(size) {
|
|
|
|
let data = [];
|
2017-08-01 21:11:28 +09:00
|
|
|
this.push = function(view) {
|
2017-07-09 20:07:06 +09:00
|
|
|
let i = data.indexOf(view);
|
|
|
|
if (i >= 0) {
|
|
|
|
data.splice(i, 1);
|
2014-10-30 21:04:01 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
data.push(view);
|
|
|
|
if (data.length > size) {
|
|
|
|
data.shift().destroy();
|
2014-10-30 21:04:01 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
};
|
2018-05-15 12:10:32 +09:00
|
|
|
/**
|
|
|
|
* After calling resize, the size of the buffer will be newSize. The optional
|
|
|
|
* parameter pagesToKeep is, if present, an array of pages to push to the back
|
|
|
|
* of the buffer, delaying their destruction. The size of pagesToKeep has no
|
|
|
|
* impact on the final size of the buffer; if pagesToKeep has length larger
|
|
|
|
* than newSize, some of those pages will be destroyed anyway.
|
|
|
|
*/
|
|
|
|
this.resize = function(newSize, pagesToKeep) {
|
2017-07-09 20:07:06 +09:00
|
|
|
size = newSize;
|
2018-05-15 12:10:32 +09:00
|
|
|
if (pagesToKeep) {
|
|
|
|
const pageIdsToKeep = new Set();
|
|
|
|
for (let i = 0, iMax = pagesToKeep.length; i < iMax; ++i) {
|
|
|
|
pageIdsToKeep.add(pagesToKeep[i].id);
|
|
|
|
}
|
|
|
|
moveToEndOfArray(data, function(page) {
|
|
|
|
return pageIdsToKeep.has(page.id);
|
|
|
|
});
|
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
while (data.length > size) {
|
|
|
|
data.shift().destroy();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2014-10-30 21:04:01 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
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;
|
2017-02-08 08:15:09 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
return false;
|
|
|
|
}
|
2017-02-08 08:15:09 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* Simple viewer control to display PDF content/pages.
|
|
|
|
* @implements {IRenderableView}
|
|
|
|
*/
|
2017-08-01 21:11:28 +09:00
|
|
|
class BaseViewer {
|
2014-09-21 02:21:49 +09:00
|
|
|
/**
|
|
|
|
* @param {PDFViewerOptions} options
|
|
|
|
*/
|
2017-07-09 20:07:06 +09:00
|
|
|
constructor(options) {
|
2017-08-01 21:11:28 +09:00
|
|
|
if (this.constructor === BaseViewer) {
|
|
|
|
throw new Error('Cannot initialize BaseViewer.');
|
|
|
|
}
|
|
|
|
this._name = this.constructor.name;
|
|
|
|
|
2014-09-13 11:27:45 +09:00
|
|
|
this.container = options.container;
|
2014-09-16 05:46:01 +09:00
|
|
|
this.viewer = options.viewer || options.container.firstElementChild;
|
2017-04-17 07:48:45 +09:00
|
|
|
this.eventBus = options.eventBus || getGlobalEventBus();
|
Simplify the SimpleLinkService and use it to pass in a linkService instance in DefaultAnnotationsLayerFactory
Considering that most methods of `SimpleLinkService` are complete stubs, or practically "useless" considering what they return, we can actually simplify it even more.
*Note:* This depends on the previous patch, that did a small amount of refactoring of `PDFViewer_scrollPageIntoView`, since `PDFViewer.linkService.page` is no longer accessed.
----------
Currently the `pageviewer` components example doesn't work correctly (an error is printed in the console), since no `linkService` is present when the `AnnotationsLayerBuilder` is created.
*Note:* Given that this uses the `SimpleLinkService`, clicking on e.g. internal links won't actually do anything. However, given that internal links (and similar features) are pretty much useless when only *one* page is loaded the `pageviewer` example, I don't think that really matters.
Also, using the complete `PDFLinkService` would require a `PDFViewer` instance. That would significantly complicate the example, thus making it both less clear and less self contained.
2015-06-12 05:20:04 +09:00
|
|
|
this.linkService = options.linkService || new SimpleLinkService();
|
2016-02-15 04:44:00 +09:00
|
|
|
this.downloadManager = options.downloadManager || null;
|
2018-09-23 02:44:13 +09:00
|
|
|
this.findController = options.findController || null;
|
2015-03-12 04:36:01 +09:00
|
|
|
this.removePageBorders = options.removePageBorders || false;
|
2018-02-13 23:01:55 +09:00
|
|
|
this.textLayerMode = Number.isInteger(options.textLayerMode) ?
|
|
|
|
options.textLayerMode : TextLayerMode.ENABLE;
|
2018-02-13 21:17:11 +09:00
|
|
|
this.imageResourcesPath = options.imageResourcesPath || '';
|
2016-09-18 02:44:25 +09:00
|
|
|
this.renderInteractiveForms = options.renderInteractiveForms || false;
|
2017-02-08 08:15:09 +09:00
|
|
|
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
|
2016-11-19 04:03:49 +09:00
|
|
|
this.renderer = options.renderer || RendererType.CANVAS;
|
2018-02-13 22:16:10 +09:00
|
|
|
this.enableWebGL = options.enableWebGL || false;
|
2018-02-13 20:52:42 +09:00
|
|
|
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
2018-02-13 21:02:15 +09:00
|
|
|
this.maxCanvasPixels = options.maxCanvasPixels;
|
2017-05-04 10:05:53 +09:00
|
|
|
this.l10n = options.l10n || NullL10n;
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2014-09-16 05:46:01 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-09-13 11:27:45 +09:00
|
|
|
this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
|
2014-09-16 02:37:03 +09:00
|
|
|
this.presentationModeState = PresentationModeState.UNKNOWN;
|
2014-09-21 02:15:18 +09:00
|
|
|
this._resetView();
|
2015-03-12 04:36:01 +09:00
|
|
|
|
|
|
|
if (this.removePageBorders) {
|
|
|
|
this.viewer.classList.add('removePageBorders');
|
|
|
|
}
|
2018-07-08 17:56:01 +09:00
|
|
|
// Defer the dispatching of this event, to give other viewer components
|
|
|
|
// time to initialize *and* register 'baseviewerinit' event listeners.
|
|
|
|
Promise.resolve().then(() => {
|
|
|
|
this.eventBus.dispatch('baseviewerinit', { source: this, });
|
|
|
|
});
|
2014-09-13 11:27:45 +09:00
|
|
|
}
|
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
get pagesCount() {
|
|
|
|
return this._pages.length;
|
|
|
|
}
|
Remove the `previousPageNumber` parameter from the `pagechanging`/pagechange` events, and stop dispatching the events if the input is out of bounds
This patch attempts to cleanup a couple of things:
- Remove the `previousPageNumber` paramater. Prior to PR 7289, when the events were dispatched even when the active page didn't change, it made sense to be able to detect that in an event listener. However, now that's no longer the case, and furthermore other similar events (e.g. `scalechanging`/`scalechange`) don't include information about the previous state.
- Don't dispatch the events when the value passed to `set currentPageNumber` is out of bounds. Given that the active page doesn't change in this case, again similar to PR 7289, I don't think that the events should actually be dispatched in this case.
- Ensure that the value passed to `set currentPageNumber` is actually an integer, to avoid any issues (note how e.g. `set currentScale` has similar validation code).
Given that these changes could possibly affect the PDF.js `mochitest` integration tests in mozilla-central, in particular https://dxr.mozilla.org/mozilla-central/source/browser/extensions/pdfjs/test/browser_pdfjs_navigation.js, I ran the tests locally with this patch applied to ensure that they still pass.
2016-07-22 22:32:14 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
getPageView(index) {
|
|
|
|
return this._pages[index];
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @returns {boolean} true if all {PDFPageView} objects are initialized.
|
|
|
|
*/
|
|
|
|
get pageViewsReady() {
|
|
|
|
return this._pageViewsReady;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
get currentPageNumber() {
|
|
|
|
return this._currentPageNumber;
|
|
|
|
}
|
2016-07-14 19:04:16 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param {number} val - The page number.
|
|
|
|
*/
|
|
|
|
set currentPageNumber(val) {
|
2017-09-03 20:08:50 +09:00
|
|
|
if (!Number.isInteger(val)) {
|
2017-07-09 20:07:06 +09:00
|
|
|
throw new Error('Invalid page number.');
|
|
|
|
}
|
|
|
|
if (!this.pdfDocument) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// The intent can be to just reset a scroll position and/or scale.
|
|
|
|
this._setCurrentPageNumber(val, /* resetCurrentPageView = */ true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_setCurrentPageNumber(val, resetCurrentPageView = false) {
|
|
|
|
if (this._currentPageNumber === val) {
|
2016-07-14 19:04:16 +09:00
|
|
|
if (resetCurrentPageView) {
|
|
|
|
this._resetCurrentPageView();
|
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
return;
|
|
|
|
}
|
2017-06-21 18:23:17 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
if (!(0 < val && val <= this.pagesCount)) {
|
|
|
|
console.error(
|
2017-08-01 21:11:28 +09:00
|
|
|
`${this._name}._setCurrentPageNumber: "${val}" is out of bounds.`);
|
2017-07-09 20:07:06 +09:00
|
|
|
return;
|
|
|
|
}
|
2018-09-21 03:51:50 +09:00
|
|
|
this._currentPageNumber = val;
|
2014-09-21 02:15:18 +09:00
|
|
|
|
2018-09-21 03:51:50 +09:00
|
|
|
this.eventBus.dispatch('pagechanging', {
|
2017-07-09 20:07:06 +09:00
|
|
|
source: this,
|
|
|
|
pageNumber: val,
|
|
|
|
pageLabel: this._pageLabels && this._pageLabels[val - 1],
|
2018-09-21 03:51:50 +09:00
|
|
|
});
|
2015-07-01 04:49:32 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
if (resetCurrentPageView) {
|
|
|
|
this._resetCurrentPageView();
|
|
|
|
}
|
|
|
|
}
|
2014-09-15 23:49:24 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @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];
|
|
|
|
}
|
2014-09-15 23:49:24 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param {string} val - The page label.
|
|
|
|
*/
|
|
|
|
set currentPageLabel(val) {
|
|
|
|
let pageNumber = val | 0; // Fallback page number.
|
|
|
|
if (this._pageLabels) {
|
|
|
|
let i = this._pageLabels.indexOf(val);
|
|
|
|
if (i >= 0) {
|
|
|
|
pageNumber = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.currentPageNumber = pageNumber;
|
|
|
|
}
|
2017-04-15 19:57:54 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
get currentScale() {
|
|
|
|
return this._currentScale !== UNKNOWN_SCALE ? this._currentScale :
|
|
|
|
DEFAULT_SCALE;
|
|
|
|
}
|
2014-09-15 23:49:24 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param {number} val - Scale of the pages in percents.
|
|
|
|
*/
|
|
|
|
set currentScale(val) {
|
|
|
|
if (isNaN(val)) {
|
|
|
|
throw new Error('Invalid numeric scale');
|
|
|
|
}
|
|
|
|
if (!this.pdfDocument) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._setScale(val, false);
|
|
|
|
}
|
2014-09-15 23:49:24 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
get currentScaleValue() {
|
|
|
|
return this._currentScaleValue;
|
|
|
|
}
|
2014-09-15 23:49:24 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param val - The scale of the pages (in percent or predefined value).
|
|
|
|
*/
|
|
|
|
set currentScaleValue(val) {
|
|
|
|
if (!this.pdfDocument) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._setScale(val, false);
|
|
|
|
}
|
2014-09-16 05:46:01 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
get pagesRotation() {
|
|
|
|
return this._pagesRotation;
|
|
|
|
}
|
2014-10-01 02:31:58 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
|
|
|
|
*/
|
|
|
|
set pagesRotation(rotation) {
|
2017-08-19 21:23:40 +09:00
|
|
|
if (!isValidRotation(rotation)) {
|
2017-07-09 20:07:06 +09:00
|
|
|
throw new Error('Invalid pages rotation angle.');
|
|
|
|
}
|
|
|
|
if (!this.pdfDocument) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-19 21:23:40 +09:00
|
|
|
if (this._pagesRotation === rotation) {
|
|
|
|
return; // The rotation didn't change.
|
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
this._pagesRotation = rotation;
|
2015-02-11 21:05:04 +09:00
|
|
|
|
2017-08-19 21:23:40 +09:00
|
|
|
let pageNumber = this._currentPageNumber;
|
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
for (let i = 0, ii = this._pages.length; i < ii; i++) {
|
|
|
|
let pageView = this._pages[i];
|
|
|
|
pageView.update(pageView.scale, rotation);
|
|
|
|
}
|
2017-08-19 21:23:40 +09:00
|
|
|
// Prevent errors in case the rotation changes *before* the scale has been
|
|
|
|
// set to a non-default value.
|
|
|
|
if (this._currentScaleValue) {
|
|
|
|
this._setScale(this._currentScaleValue, true);
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-08-19 21:23:40 +09:00
|
|
|
this.eventBus.dispatch('rotationchanging', {
|
|
|
|
source: this,
|
|
|
|
pagesRotation: rotation,
|
|
|
|
pageNumber,
|
|
|
|
});
|
2016-01-23 21:56:56 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
if (this.defaultRenderingQueue) {
|
2014-09-13 11:27:45 +09:00
|
|
|
this.update();
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-08-01 21:11:28 +09:00
|
|
|
get _setDocumentViewerElement() {
|
|
|
|
throw new Error('Not implemented: _setDocumentViewerElement');
|
|
|
|
}
|
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param pdfDocument {PDFDocument}
|
|
|
|
*/
|
|
|
|
setDocument(pdfDocument) {
|
|
|
|
if (this.pdfDocument) {
|
|
|
|
this._cancelRendering();
|
|
|
|
this._resetView();
|
Make `PDFFindController` less confusing to use, by allowing searching to start when `setDocument` is called
*This patch is based on something that I noticed while working on PR 10126.*
The recent re-factoring of `PDFFindController` brought many improvements, among those the fact that access to `BaseViewer` is no longer required. However, with these changes there's one thing which now strikes me as not particularly user-friendly[1]: The fact that in order for searching to actually work, `PDFFindController.setDocument` must be called *and* a 'pagesinit' event must be dispatched (from somewhere).
For all other viewer components, calling the `setDocument` method[2] is enough in order for the component to actually be usable.
The `PDFFindController` thus stands out quite a bit, and it also becomes difficult to work with in any sort of custom implementation. For example: Imagine someone trying to use `PDFFindController` separately from the viewer[3], which *should* now be relatively simple given the re-factoring, and thus having to (somehow) figure out that they'll also need to manually dispatch a 'pagesinit' event for searching to work.
Note that the above even affects the unit-tests, where an out-of-place 'pagesinit' event is being used.
To attempt to address these problems, I'm thus suggesting that *only* `setDocument` should be used to indicate that searching may start. For the default viewer and/or the viewer components, `BaseViewer.setDocument` will now call `PDFFindController.setDocument` when the document is ready, thus requiring no outside configuration anymore[4]. For custom implementation, and the unit-tests, it's now as simple as just calling `PDFFindController.setDocument` to allow searching to start.
---
[1] I should have caught this during review of PR 10099, but unfortunately it's sometimes not until you actually work with the code in question that things like these become clear.
[2] Assuming, obviously, that the viewer component in question actually implements such a method :-)
[3] There's even a very recent issue, filed by someone trying to do just that.
[4] Short of providing a `PDFFindController` instance when creating a `BaseViewer` instance, of course.
2018-10-03 19:42:41 +09:00
|
|
|
|
|
|
|
if (this.findController) {
|
|
|
|
this.findController.setDocument(null);
|
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
this.pdfDocument = pdfDocument;
|
|
|
|
if (!pdfDocument) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let pagesCount = pdfDocument.numPages;
|
2015-02-20 01:30:47 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
let pagesCapability = createPromiseCapability();
|
|
|
|
this.pagesPromise = pagesCapability.promise;
|
2014-10-30 21:04:01 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
pagesCapability.promise.then(() => {
|
|
|
|
this._pageViewsReady = true;
|
|
|
|
this.eventBus.dispatch('pagesloaded', {
|
|
|
|
source: this,
|
|
|
|
pagesCount,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
let isOnePageRenderedResolved = false;
|
|
|
|
let onePageRenderedCapability = createPromiseCapability();
|
|
|
|
this.onePageRendered = onePageRenderedCapability.promise;
|
|
|
|
|
|
|
|
let bindOnAfterAndBeforeDraw = (pageView) => {
|
|
|
|
pageView.onBeforeDraw = () => {
|
|
|
|
// 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.
|
|
|
|
this._buffer.push(pageView);
|
|
|
|
};
|
|
|
|
pageView.onAfterDraw = () => {
|
|
|
|
if (!isOnePageRenderedResolved) {
|
|
|
|
isOnePageRenderedResolved = true;
|
|
|
|
onePageRenderedCapability.resolve();
|
2015-02-20 01:30:47 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
};
|
|
|
|
};
|
2015-02-20 01:30:47 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
let 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
|
2017-08-04 07:24:19 +09:00
|
|
|
firstPagePromise.then((pdfPage) => {
|
2017-07-09 20:07:06 +09:00
|
|
|
let scale = this.currentScale;
|
|
|
|
let viewport = pdfPage.getViewport(scale * CSS_UNITS);
|
|
|
|
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
|
|
|
|
let textLayerFactory = null;
|
2018-02-13 23:01:55 +09:00
|
|
|
if (this.textLayerMode !== TextLayerMode.DISABLE) {
|
2017-07-09 20:07:06 +09:00
|
|
|
textLayerFactory = this;
|
2014-09-13 11:27:45 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
let pageView = new PDFPageView({
|
2017-08-01 21:11:28 +09:00
|
|
|
container: this._setDocumentViewerElement,
|
2017-07-09 20:07:06 +09:00
|
|
|
eventBus: this.eventBus,
|
|
|
|
id: pageNum,
|
|
|
|
scale,
|
|
|
|
defaultViewport: viewport.clone(),
|
|
|
|
renderingQueue: this.renderingQueue,
|
|
|
|
textLayerFactory,
|
2018-02-13 23:01:55 +09:00
|
|
|
textLayerMode: this.textLayerMode,
|
2017-07-09 20:07:06 +09:00
|
|
|
annotationLayerFactory: this,
|
2018-02-13 21:17:11 +09:00
|
|
|
imageResourcesPath: this.imageResourcesPath,
|
2017-07-09 20:07:06 +09:00
|
|
|
renderInteractiveForms: this.renderInteractiveForms,
|
|
|
|
renderer: this.renderer,
|
2018-02-13 22:16:10 +09:00
|
|
|
enableWebGL: this.enableWebGL,
|
2018-02-13 20:52:42 +09:00
|
|
|
useOnlyCssZoom: this.useOnlyCssZoom,
|
2018-02-13 21:02:15 +09:00
|
|
|
maxCanvasPixels: this.maxCanvasPixels,
|
2017-07-09 20:07:06 +09:00
|
|
|
l10n: this.l10n,
|
Prevent destinations with bad left/top values from scrolling the wrong page into view (bug 874482)
There are PDF generators which create destinations with e.g. too large top values, which cause the wrong page to be scrolled into view because the offset becomes negative.
By ignoring negative offsets, we can prevent this issue, and get a similar behaviour as in Adobe Reader.
However, since we're also using `PDFViewer_scrollPageIntoView` in more cases than just when links (in the document/outline) are clicked, the patch adds a way to allow the caller to opt-out of this behaviour.
In e.g. the following situations, I think that we still want to be able to allow negative offsets: when restoring a position from the `ViewHistory`, when the `viewBookmark` button is used to obtain a link to the current position, or when maintaining the current position on zooming.
Rather than adding another parameter to `PDFViewer_scrollPageIntoView`, I've changed the signature to take an parameter object instead. To maintain backwards compatibility, I've added fallback code enclosed in a `GENERIC` preprocessor tag.
Fixes https://bugzilla.mozilla.org/show_bug.cgi?id=874482.
2016-06-20 19:30:05 +09:00
|
|
|
});
|
2017-07-09 20:07:06 +09:00
|
|
|
bindOnAfterAndBeforeDraw(pageView);
|
|
|
|
this._pages.push(pageView);
|
2014-09-13 11:27:45 +09:00
|
|
|
}
|
2018-06-29 21:14:11 +09:00
|
|
|
if (this._spreadMode !== SpreadMode.NONE) {
|
2018-06-29 22:28:38 +09:00
|
|
|
this._updateSpreadMode();
|
2018-05-15 12:10:32 +09:00
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
// 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.
|
|
|
|
onePageRenderedCapability.promise.then(() => {
|
2018-02-18 06:08:45 +09:00
|
|
|
if (pdfDocument.loadingParams['disableAutoFetch']) {
|
2017-07-09 20:07:06 +09:00
|
|
|
// XXX: Printing is semi-broken with auto fetch disabled.
|
|
|
|
pagesCapability.resolve();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let getPagesLeft = pagesCount;
|
|
|
|
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
|
|
|
|
pdfDocument.getPage(pageNum).then((pdfPage) => {
|
|
|
|
let pageView = this._pages[pageNum - 1];
|
|
|
|
if (!pageView.pdfPage) {
|
|
|
|
pageView.setPdfPage(pdfPage);
|
|
|
|
}
|
|
|
|
this.linkService.cachePageRef(pageNum, pdfPage.ref);
|
|
|
|
if (--getPagesLeft === 0) {
|
|
|
|
pagesCapability.resolve();
|
|
|
|
}
|
2017-08-04 07:24:19 +09:00
|
|
|
}, (reason) => {
|
|
|
|
console.error(`Unable to get page ${pageNum} to initialize viewer`,
|
|
|
|
reason);
|
|
|
|
if (--getPagesLeft === 0) {
|
|
|
|
pagesCapability.resolve();
|
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.eventBus.dispatch('pagesinit', { source: this, });
|
2015-07-01 04:49:32 +09:00
|
|
|
|
Make `PDFFindController` less confusing to use, by allowing searching to start when `setDocument` is called
*This patch is based on something that I noticed while working on PR 10126.*
The recent re-factoring of `PDFFindController` brought many improvements, among those the fact that access to `BaseViewer` is no longer required. However, with these changes there's one thing which now strikes me as not particularly user-friendly[1]: The fact that in order for searching to actually work, `PDFFindController.setDocument` must be called *and* a 'pagesinit' event must be dispatched (from somewhere).
For all other viewer components, calling the `setDocument` method[2] is enough in order for the component to actually be usable.
The `PDFFindController` thus stands out quite a bit, and it also becomes difficult to work with in any sort of custom implementation. For example: Imagine someone trying to use `PDFFindController` separately from the viewer[3], which *should* now be relatively simple given the re-factoring, and thus having to (somehow) figure out that they'll also need to manually dispatch a 'pagesinit' event for searching to work.
Note that the above even affects the unit-tests, where an out-of-place 'pagesinit' event is being used.
To attempt to address these problems, I'm thus suggesting that *only* `setDocument` should be used to indicate that searching may start. For the default viewer and/or the viewer components, `BaseViewer.setDocument` will now call `PDFFindController.setDocument` when the document is ready, thus requiring no outside configuration anymore[4]. For custom implementation, and the unit-tests, it's now as simple as just calling `PDFFindController.setDocument` to allow searching to start.
---
[1] I should have caught this during review of PR 10099, but unfortunately it's sometimes not until you actually work with the code in question that things like these become clear.
[2] Assuming, obviously, that the viewer component in question actually implements such a method :-)
[3] There's even a very recent issue, filed by someone trying to do just that.
[4] Short of providing a `PDFFindController` instance when creating a `BaseViewer` instance, of course.
2018-10-03 19:42:41 +09:00
|
|
|
if (this.findController) {
|
|
|
|
this.findController.setDocument(pdfDocument); // Enable searching.
|
|
|
|
}
|
2015-07-01 04:49:32 +09:00
|
|
|
if (this.defaultRenderingQueue) {
|
|
|
|
this.update();
|
|
|
|
}
|
2017-08-04 07:24:19 +09:00
|
|
|
}).catch((reason) => {
|
|
|
|
console.error('Unable to initialize viewer', reason);
|
2017-07-09 20:07:06 +09:00
|
|
|
});
|
|
|
|
}
|
2016-05-05 01:06:07 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param {Array|null} labels
|
|
|
|
*/
|
|
|
|
setPageLabels(labels) {
|
|
|
|
if (!this.pdfDocument) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!labels) {
|
|
|
|
this._pageLabels = null;
|
2018-07-09 20:11:35 +09:00
|
|
|
} else if (!(Array.isArray(labels) &&
|
2017-07-09 20:07:06 +09:00
|
|
|
this.pdfDocument.numPages === labels.length)) {
|
|
|
|
this._pageLabels = null;
|
2017-08-01 21:11:28 +09:00
|
|
|
console.error(`${this._name}.setPageLabels: Invalid page labels.`);
|
2017-07-09 20:07:06 +09:00
|
|
|
} else {
|
|
|
|
this._pageLabels = labels;
|
|
|
|
}
|
|
|
|
// Update all the `PDFPageView` instances.
|
|
|
|
for (let i = 0, ii = this._pages.length; i < ii; i++) {
|
|
|
|
let pageView = this._pages[i];
|
|
|
|
let label = this._pageLabels && this._pageLabels[i];
|
|
|
|
pageView.setPageLabel(label);
|
|
|
|
}
|
|
|
|
}
|
2015-11-24 00:57:43 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
_resetView() {
|
|
|
|
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;
|
2018-06-29 22:48:45 +09:00
|
|
|
this._scrollMode = ScrollMode.VERTICAL;
|
|
|
|
this._spreadMode = SpreadMode.NONE;
|
2017-07-09 20:07:06 +09:00
|
|
|
|
2018-06-29 22:48:45 +09:00
|
|
|
// Remove the pages from the DOM...
|
2017-07-09 20:07:06 +09:00
|
|
|
this.viewer.textContent = '';
|
2018-06-29 22:48:45 +09:00
|
|
|
// ... and reset the Scroll mode CSS class(es) afterwards.
|
|
|
|
this._updateScrollMode();
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
_scrollUpdate() {
|
|
|
|
if (this.pagesCount === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
|
2017-08-01 21:11:28 +09:00
|
|
|
_scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null, }) {
|
|
|
|
throw new Error('Not implemented: _scrollIntoView');
|
|
|
|
}
|
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
_setScaleDispatchEvent(newScale, newValue, preset = false) {
|
2018-09-21 03:51:50 +09:00
|
|
|
this.eventBus.dispatch('scalechanging', {
|
2017-07-09 20:07:06 +09:00
|
|
|
source: this,
|
|
|
|
scale: newScale,
|
|
|
|
presetValue: preset ? newValue : undefined,
|
2018-09-21 03:51:50 +09:00
|
|
|
});
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
_setScaleUpdatePages(newScale, newValue, noScroll = false, preset = false) {
|
|
|
|
this._currentScaleValue = newValue.toString();
|
|
|
|
|
|
|
|
if (isSameScale(this._currentScale, newScale)) {
|
|
|
|
if (preset) {
|
|
|
|
this._setScaleDispatchEvent(newScale, newValue, true);
|
2014-09-30 21:13:46 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0, ii = this._pages.length; i < ii; i++) {
|
|
|
|
this._pages[i].update(newScale);
|
|
|
|
}
|
|
|
|
this._currentScale = newScale;
|
|
|
|
|
|
|
|
if (!noScroll) {
|
|
|
|
let page = this._currentPageNumber, dest;
|
Remove the `ignoreCurrentPositionOnZoom` viewer option
The only reason for adding this parameter in the first place, all the way back in PR 4074, was that the "maintain document position on zooming" feature was landed and backed out a couple of times before it finally stuck.
Hence it seemed, at the time, like a good idea to have a simple way to disable that behaviour. However, that was almost four years ago, and it's just not likely that we'd want/need to ever disable it now.
Furthermore I really cannot imagine why anyone would actually *want* to reset the position whenever zooming occurs, since it results in a quite annoying UX.
*So, to summarize:* Based on the above, I think that we should try to remove this parameter now. On the off chance that anyone complains, re-adding it shouldn't be difficult.
2017-11-13 01:30:53 +09:00
|
|
|
if (this._location &&
|
2017-07-09 20:07:06 +09:00
|
|
|
!(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,
|
|
|
|
});
|
|
|
|
}
|
2014-09-30 21:13:46 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
this._setScaleDispatchEvent(newScale, newValue, preset);
|
|
|
|
|
|
|
|
if (this.defaultRenderingQueue) {
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_setScale(value, noScroll = false) {
|
|
|
|
let scale = parseFloat(value);
|
|
|
|
|
|
|
|
if (scale > 0) {
|
|
|
|
this._setScaleUpdatePages(scale, value, noScroll, /* preset = */ false);
|
|
|
|
} else {
|
|
|
|
let currentPage = this._pages[this._currentPageNumber - 1];
|
|
|
|
if (!currentPage) {
|
Prevent destinations with bad left/top values from scrolling the wrong page into view (bug 874482)
There are PDF generators which create destinations with e.g. too large top values, which cause the wrong page to be scrolled into view because the offset becomes negative.
By ignoring negative offsets, we can prevent this issue, and get a similar behaviour as in Adobe Reader.
However, since we're also using `PDFViewer_scrollPageIntoView` in more cases than just when links (in the document/outline) are clicked, the patch adds a way to allow the caller to opt-out of this behaviour.
In e.g. the following situations, I think that we still want to be able to allow negative offsets: when restoring a position from the `ViewHistory`, when the `viewBookmark` button is used to obtain a link to the current position, or when maintaining the current position on zooming.
Rather than adding another parameter to `PDFViewer_scrollPageIntoView`, I've changed the signature to take an parameter object instead. To maintain backwards compatibility, I've added fallback code enclosed in a `GENERIC` preprocessor tag.
Fixes https://bugzilla.mozilla.org/show_bug.cgi?id=874482.
2016-06-20 19:30:05 +09:00
|
|
|
return;
|
|
|
|
}
|
2018-06-22 04:45:03 +09:00
|
|
|
const noPadding = (this.isInPresentationMode || this.removePageBorders);
|
|
|
|
let hPadding = noPadding ? 0 : SCROLLBAR_PADDING;
|
|
|
|
let vPadding = noPadding ? 0 : VERTICAL_PADDING;
|
|
|
|
|
|
|
|
if (!noPadding && this._isScrollModeHorizontal) {
|
2018-08-20 21:18:16 +09:00
|
|
|
[hPadding, vPadding] = [vPadding, hPadding]; // Swap the padding values.
|
2018-05-15 12:10:32 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
let pageWidthScale = (this.container.clientWidth - hPadding) /
|
|
|
|
currentPage.width * currentPage.scale;
|
|
|
|
let pageHeightScale = (this.container.clientHeight - vPadding) /
|
|
|
|
currentPage.height * currentPage.scale;
|
|
|
|
switch (value) {
|
|
|
|
case 'page-actual':
|
|
|
|
scale = 1;
|
2014-09-30 21:13:46 +09:00
|
|
|
break;
|
2017-07-09 20:07:06 +09:00
|
|
|
case 'page-width':
|
|
|
|
scale = pageWidthScale;
|
2014-09-30 21:13:46 +09:00
|
|
|
break;
|
2017-07-09 20:07:06 +09:00
|
|
|
case 'page-height':
|
|
|
|
scale = pageHeightScale;
|
2014-09-30 21:13:46 +09:00
|
|
|
break;
|
2017-07-09 20:07:06 +09:00
|
|
|
case 'page-fit':
|
|
|
|
scale = Math.min(pageWidthScale, pageHeightScale);
|
2014-09-30 21:13:46 +09:00
|
|
|
break;
|
2017-07-09 20:07:06 +09:00
|
|
|
case 'auto':
|
|
|
|
// For pages in landscape mode, fit the page height to the viewer
|
|
|
|
// *unless* the page would thus become too wide to fit horizontally.
|
2018-03-20 21:25:13 +09:00
|
|
|
let horizontalScale = isPortraitOrientation(currentPage) ?
|
|
|
|
pageWidthScale : Math.min(pageHeightScale, pageWidthScale);
|
2017-07-09 20:07:06 +09:00
|
|
|
scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
|
2014-09-30 21:13:46 +09:00
|
|
|
break;
|
|
|
|
default:
|
2017-07-09 20:07:06 +09:00
|
|
|
console.error(
|
2017-08-01 21:11:28 +09:00
|
|
|
`${this._name}._setScale: "${value}" is an unknown zoom value.`);
|
2014-09-30 21:13:46 +09:00
|
|
|
return;
|
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
this._setScaleUpdatePages(scale, value, noScroll, /* preset = */ true);
|
|
|
|
}
|
|
|
|
}
|
2014-09-30 21:13:46 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* Refreshes page view: scrolls to the current page and updates the scale.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_resetCurrentPageView() {
|
|
|
|
if (this.isInPresentationMode) {
|
|
|
|
// Fixes the case when PDF has different page sizes.
|
|
|
|
this._setScale(this._currentScaleValue, true);
|
|
|
|
}
|
2014-09-30 21:13:46 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
let pageView = this._pages[this._currentPageNumber - 1];
|
2017-08-01 21:11:28 +09:00
|
|
|
this._scrollIntoView({ pageDiv: pageView.div, });
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
2014-09-30 21:13:46 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @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(params) {
|
2017-08-22 19:07:25 +09:00
|
|
|
if (!this.pdfDocument) {
|
|
|
|
return;
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
let pageNumber = params.pageNumber || 0;
|
|
|
|
let dest = params.destArray || null;
|
|
|
|
let allowNegativeOffset = params.allowNegativeOffset || false;
|
|
|
|
|
|
|
|
if (this.isInPresentationMode || !dest) {
|
|
|
|
this._setCurrentPageNumber(pageNumber, /* resetCurrentPageView = */ true);
|
|
|
|
return;
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
let pageView = this._pages[pageNumber - 1];
|
|
|
|
if (!pageView) {
|
|
|
|
console.error(
|
2017-08-01 21:11:28 +09:00
|
|
|
`${this._name}.scrollPageIntoView: Invalid "pageNumber" parameter.`);
|
2017-07-09 20:07:06 +09:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
let x = 0, y = 0;
|
|
|
|
let width = 0, height = 0, widthScale, heightScale;
|
|
|
|
let changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
|
|
|
|
let pageWidth = (changeOrientation ? pageView.height : pageView.width) /
|
|
|
|
pageView.scale / CSS_UNITS;
|
|
|
|
let pageHeight = (changeOrientation ? pageView.width : pageView.height) /
|
|
|
|
pageView.scale / CSS_UNITS;
|
|
|
|
let 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;
|
|
|
|
let hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
|
|
|
|
let 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:
|
2017-08-01 21:11:28 +09:00
|
|
|
console.error(`${this._name}.scrollPageIntoView: "${dest[1].name}" ` +
|
2017-07-09 20:07:06 +09:00
|
|
|
'is not a valid destination type.');
|
2014-09-13 11:27:45 +09:00
|
|
|
return;
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
if (scale && scale !== this._currentScale) {
|
|
|
|
this.currentScaleValue = scale;
|
|
|
|
} else if (this._currentScale === UNKNOWN_SCALE) {
|
|
|
|
this.currentScaleValue = DEFAULT_SCALE_VALUE;
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
if (scale === 'page-fit' && !dest[4]) {
|
2017-08-01 21:11:28 +09:00
|
|
|
this._scrollIntoView({
|
|
|
|
pageDiv: pageView.div,
|
|
|
|
pageNumber,
|
|
|
|
});
|
2017-07-09 20:07:06 +09:00
|
|
|
return;
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
let boundingRect = [
|
|
|
|
pageView.viewport.convertToViewportPoint(x, y),
|
|
|
|
pageView.viewport.convertToViewportPoint(x + width, y + height)
|
|
|
|
];
|
|
|
|
let left = Math.min(boundingRect[0][0], boundingRect[1][0]);
|
|
|
|
let 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);
|
|
|
|
}
|
2017-08-01 21:11:28 +09:00
|
|
|
this._scrollIntoView({
|
|
|
|
pageDiv: pageView.div,
|
|
|
|
pageSpot: { left, top, },
|
|
|
|
pageNumber,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-15 12:10:32 +09:00
|
|
|
/**
|
|
|
|
* visiblePages is optional; if present, it should be an array of pages and in
|
|
|
|
* practice its length is going to be numVisiblePages, but this is not
|
|
|
|
* required. The new size of the buffer depends only on numVisiblePages.
|
|
|
|
*/
|
|
|
|
_resizeBuffer(numVisiblePages, visiblePages) {
|
2017-08-01 21:11:28 +09:00
|
|
|
let suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
|
|
|
|
2 * numVisiblePages + 1);
|
2018-05-15 12:10:32 +09:00
|
|
|
this._buffer.resize(suggestedCacheSize, visiblePages);
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
_updateLocation(firstPage) {
|
|
|
|
let currentScale = this._currentScale;
|
|
|
|
let currentScaleValue = this._currentScaleValue;
|
|
|
|
let normalizedScaleValue =
|
|
|
|
parseFloat(currentScaleValue) === currentScale ?
|
|
|
|
Math.round(currentScale * 10000) / 100 : currentScaleValue;
|
|
|
|
|
|
|
|
let pageNumber = firstPage.id;
|
|
|
|
let pdfOpenParams = '#page=' + pageNumber;
|
|
|
|
pdfOpenParams += '&zoom=' + normalizedScaleValue;
|
|
|
|
let currentPageView = this._pages[pageNumber - 1];
|
|
|
|
let container = this.container;
|
|
|
|
let topLeft = currentPageView.getPagePoint(
|
|
|
|
(container.scrollLeft - firstPage.x),
|
|
|
|
(container.scrollTop - firstPage.y));
|
|
|
|
let intLeft = Math.round(topLeft[0]);
|
|
|
|
let intTop = Math.round(topLeft[1]);
|
|
|
|
pdfOpenParams += ',' + intLeft + ',' + intTop;
|
|
|
|
|
|
|
|
this._location = {
|
|
|
|
pageNumber,
|
|
|
|
scale: normalizedScaleValue,
|
|
|
|
top: intTop,
|
|
|
|
left: intLeft,
|
2017-08-21 18:22:07 +09:00
|
|
|
rotation: this._pagesRotation,
|
2017-07-09 20:07:06 +09:00
|
|
|
pdfOpenParams,
|
|
|
|
};
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
update() {
|
2017-08-01 21:11:28 +09:00
|
|
|
throw new Error('Not implemented: update');
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
containsElement(element) {
|
|
|
|
return this.container.contains(element);
|
|
|
|
}
|
2015-01-29 21:37:49 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
focus() {
|
|
|
|
this.container.focus();
|
|
|
|
}
|
2015-01-29 21:37:49 +09:00
|
|
|
|
2018-06-22 04:45:03 +09:00
|
|
|
get _isScrollModeHorizontal() {
|
|
|
|
throw new Error('Not implemented: _isScrollModeHorizontal');
|
|
|
|
}
|
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
get isInPresentationMode() {
|
|
|
|
return this.presentationModeState === PresentationModeState.FULLSCREEN;
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
get isChangingPresentationMode() {
|
|
|
|
return this.presentationModeState === PresentationModeState.CHANGING;
|
|
|
|
}
|
|
|
|
|
|
|
|
get isHorizontalScrollbarEnabled() {
|
|
|
|
return (this.isInPresentationMode ?
|
|
|
|
false : (this.container.scrollWidth > this.container.clientWidth));
|
|
|
|
}
|
|
|
|
|
2018-05-15 12:10:32 +09:00
|
|
|
get isVerticalScrollbarEnabled() {
|
|
|
|
return (this.isInPresentationMode ?
|
|
|
|
false : (this.container.scrollHeight > this.container.clientHeight));
|
|
|
|
}
|
|
|
|
|
2018-10-24 19:29:56 +09:00
|
|
|
/**
|
|
|
|
* Helper method for `this._getVisiblePages`. Should only ever be used when
|
|
|
|
* the viewer can only display a single page at a time, for example in:
|
|
|
|
* - `PDFSinglePageViewer`.
|
|
|
|
* - `PDFViewer` with Presentation Mode active.
|
|
|
|
*/
|
|
|
|
_getCurrentVisiblePage() {
|
|
|
|
if (!this.pagesCount) {
|
|
|
|
return { views: [], };
|
|
|
|
}
|
|
|
|
const pageView = this._pages[this._currentPageNumber - 1];
|
|
|
|
// NOTE: Compute the `x` and `y` properties of the current view,
|
|
|
|
// since `this._updateLocation` depends of them being available.
|
|
|
|
const element = pageView.div;
|
|
|
|
|
|
|
|
const view = {
|
|
|
|
id: pageView.id,
|
|
|
|
x: element.offsetLeft + element.clientLeft,
|
|
|
|
y: element.offsetTop + element.clientTop,
|
|
|
|
view: pageView,
|
|
|
|
};
|
|
|
|
return { first: view, last: view, views: [view], };
|
|
|
|
}
|
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
_getVisiblePages() {
|
2017-08-01 21:11:28 +09:00
|
|
|
throw new Error('Not implemented: _getVisiblePages');
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
for (let i = 0, ii = this._pages.length; i < ii; i++) {
|
|
|
|
if (this._pages[i] &&
|
|
|
|
this._pages[i].renderingState !== RenderingStates.FINISHED) {
|
|
|
|
this._pages[i].reset();
|
2014-09-29 22:11:46 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_cancelRendering() {
|
|
|
|
for (let i = 0, ii = this._pages.length; i < ii; i++) {
|
|
|
|
if (this._pages[i]) {
|
|
|
|
this._pages[i].cancelRendering();
|
2014-09-13 11:27:45 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
}
|
2014-09-15 23:49:24 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* @param {PDFPageView} pageView
|
|
|
|
* @returns {Promise} Returns a promise containing a {PDFPageProxy} object.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_ensurePdfPageLoaded(pageView) {
|
|
|
|
if (pageView.pdfPage) {
|
|
|
|
return Promise.resolve(pageView.pdfPage);
|
|
|
|
}
|
|
|
|
let pageNumber = pageView.id;
|
|
|
|
if (this._pagesRequests[pageNumber]) {
|
|
|
|
return this._pagesRequests[pageNumber];
|
|
|
|
}
|
|
|
|
let promise = this.pdfDocument.getPage(pageNumber).then((pdfPage) => {
|
|
|
|
if (!pageView.pdfPage) {
|
|
|
|
pageView.setPdfPage(pdfPage);
|
|
|
|
}
|
|
|
|
this._pagesRequests[pageNumber] = null;
|
|
|
|
return pdfPage;
|
2017-08-04 07:24:19 +09:00
|
|
|
}).catch((reason) => {
|
|
|
|
console.error('Unable to get page for page view', reason);
|
|
|
|
// Page error -- there is nothing can be done.
|
|
|
|
this._pagesRequests[pageNumber] = null;
|
2017-07-09 20:07:06 +09:00
|
|
|
});
|
|
|
|
this._pagesRequests[pageNumber] = promise;
|
|
|
|
return promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
forceRendering(currentlyVisiblePages) {
|
|
|
|
let visiblePages = currentlyVisiblePages || this._getVisiblePages();
|
2018-06-22 04:45:03 +09:00
|
|
|
let scrollAhead = (this._isScrollModeHorizontal ?
|
|
|
|
this.scroll.right : this.scroll.down);
|
2017-07-09 20:07:06 +09:00
|
|
|
let pageView = this.renderingQueue.getHighestPriority(visiblePages,
|
|
|
|
this._pages,
|
2018-05-15 12:10:32 +09:00
|
|
|
scrollAhead);
|
2017-07-09 20:07:06 +09:00
|
|
|
if (pageView) {
|
|
|
|
this._ensurePdfPageLoaded(pageView).then(() => {
|
|
|
|
this.renderingQueue.renderView(pageView);
|
2014-09-30 01:05:28 +09:00
|
|
|
});
|
2017-07-09 19:43:57 +09:00
|
|
|
return true;
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {HTMLDivElement} textLayerDiv
|
|
|
|
* @param {number} pageIndex
|
|
|
|
* @param {PageViewport} viewport
|
|
|
|
* @returns {TextLayerBuilder}
|
|
|
|
*/
|
|
|
|
createTextLayerBuilder(textLayerDiv, pageIndex, viewport,
|
|
|
|
enhanceTextSelection = false) {
|
|
|
|
return new TextLayerBuilder({
|
|
|
|
textLayerDiv,
|
|
|
|
eventBus: this.eventBus,
|
|
|
|
pageIndex,
|
|
|
|
viewport,
|
|
|
|
findController: this.isInPresentationMode ? null : this.findController,
|
|
|
|
enhanceTextSelection: this.isInPresentationMode ? false :
|
|
|
|
enhanceTextSelection,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {HTMLDivElement} pageDiv
|
|
|
|
* @param {PDFPage} pdfPage
|
2018-02-13 21:17:11 +09:00
|
|
|
* @param {string} imageResourcesPath - (optional) Path for image resources,
|
|
|
|
* mainly for annotation icons. Include trailing slash.
|
2017-07-09 20:07:06 +09:00
|
|
|
* @param {boolean} renderInteractiveForms
|
|
|
|
* @param {IL10n} l10n
|
|
|
|
* @returns {AnnotationLayerBuilder}
|
|
|
|
*/
|
2018-02-13 21:17:11 +09:00
|
|
|
createAnnotationLayerBuilder(pageDiv, pdfPage, imageResourcesPath = '',
|
|
|
|
renderInteractiveForms = false,
|
2017-07-09 20:07:06 +09:00
|
|
|
l10n = NullL10n) {
|
|
|
|
return new AnnotationLayerBuilder({
|
|
|
|
pageDiv,
|
|
|
|
pdfPage,
|
2018-02-13 21:17:11 +09:00
|
|
|
imageResourcesPath,
|
2017-07-09 20:07:06 +09:00
|
|
|
renderInteractiveForms,
|
|
|
|
linkService: this.linkService,
|
|
|
|
downloadManager: this.downloadManager,
|
|
|
|
l10n,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @returns {boolean} Whether all pages of the PDF document have identical
|
|
|
|
* widths and heights.
|
|
|
|
*/
|
|
|
|
get hasEqualPageSizes() {
|
|
|
|
let firstPageView = this._pages[0];
|
|
|
|
for (let i = 1, ii = this._pages.length; i < ii; ++i) {
|
|
|
|
let pageView = this._pages[i];
|
|
|
|
if (pageView.width !== firstPageView.width ||
|
|
|
|
pageView.height !== firstPageView.height) {
|
|
|
|
return false;
|
2017-02-08 08:15:09 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2014-09-13 11:27:45 +09:00
|
|
|
|
2017-07-09 20:07:06 +09:00
|
|
|
/**
|
|
|
|
* Returns sizes of the pages.
|
|
|
|
* @returns {Array} Array of objects with width/height/rotation fields.
|
|
|
|
*/
|
|
|
|
getPagesOverview() {
|
|
|
|
let pagesOverview = this._pages.map(function(pageView) {
|
|
|
|
let viewport = pageView.pdfPage.getViewport(1);
|
|
|
|
return {
|
|
|
|
width: viewport.width,
|
|
|
|
height: viewport.height,
|
|
|
|
rotation: viewport.rotation,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
if (!this.enablePrintAutoRotate) {
|
|
|
|
return pagesOverview;
|
|
|
|
}
|
|
|
|
let isFirstPagePortrait = isPortraitOrientation(pagesOverview[0]);
|
|
|
|
return pagesOverview.map(function (size) {
|
|
|
|
if (isFirstPagePortrait === isPortraitOrientation(size)) {
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
width: size.height,
|
|
|
|
height: size.width,
|
|
|
|
rotation: (size.rotation + 90) % 360,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
2018-05-15 12:10:32 +09:00
|
|
|
|
2018-06-29 22:07:42 +09:00
|
|
|
/**
|
|
|
|
* @return {number} One of the values in {ScrollMode}.
|
|
|
|
*/
|
|
|
|
get scrollMode() {
|
|
|
|
return this._scrollMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} mode - The direction in which the document pages should be
|
|
|
|
* laid out within the scrolling container.
|
|
|
|
* The constants from {ScrollMode} should be used.
|
|
|
|
*/
|
|
|
|
set scrollMode(mode) {
|
|
|
|
if (this._scrollMode === mode) {
|
|
|
|
return; // The Scroll mode didn't change.
|
|
|
|
}
|
2018-06-22 02:53:13 +09:00
|
|
|
if (!Number.isInteger(mode) || !Object.values(ScrollMode).includes(mode)) {
|
|
|
|
throw new Error(`Invalid scroll mode: ${mode}`);
|
2018-05-15 12:10:32 +09:00
|
|
|
}
|
2018-06-29 21:14:11 +09:00
|
|
|
this._scrollMode = mode;
|
2018-06-29 22:07:42 +09:00
|
|
|
this.eventBus.dispatch('scrollmodechanged', { source: this, mode, });
|
|
|
|
|
2018-06-29 22:28:38 +09:00
|
|
|
this._updateScrollMode(/* pageNumber = */ this._currentPageNumber);
|
|
|
|
}
|
2018-06-29 22:07:42 +09:00
|
|
|
|
2018-06-29 22:28:38 +09:00
|
|
|
_updateScrollMode(pageNumber = null) {
|
|
|
|
const scrollMode = this._scrollMode, viewer = this.viewer;
|
|
|
|
|
2018-10-12 22:29:45 +09:00
|
|
|
viewer.classList.toggle('scrollHorizontal',
|
|
|
|
scrollMode === ScrollMode.HORIZONTAL);
|
|
|
|
viewer.classList.toggle('scrollWrapped',
|
|
|
|
scrollMode === ScrollMode.WRAPPED);
|
2018-06-29 22:28:38 +09:00
|
|
|
|
|
|
|
if (!this.pdfDocument || !pageNumber) {
|
2018-06-29 22:07:42 +09:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Non-numeric scale values can be sensitive to the scroll orientation.
|
|
|
|
// Call this before re-scrolling to the current page, to ensure that any
|
|
|
|
// changes in scale don't move the current page.
|
2018-06-29 22:28:38 +09:00
|
|
|
if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
|
2018-06-29 22:07:42 +09:00
|
|
|
this._setScale(this._currentScaleValue, true);
|
|
|
|
}
|
|
|
|
this.scrollPageIntoView({ pageNumber, });
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {number} One of the values in {SpreadMode}.
|
|
|
|
*/
|
|
|
|
get spreadMode() {
|
|
|
|
return this._spreadMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} mode - Group the pages in spreads, starting with odd- or
|
|
|
|
* even-number pages (unless `SpreadMode.NONE` is used).
|
|
|
|
* The constants from {SpreadMode} should be used.
|
|
|
|
*/
|
|
|
|
set spreadMode(mode) {
|
|
|
|
if (this._spreadMode === mode) {
|
|
|
|
return; // The Spread mode didn't change.
|
|
|
|
}
|
2018-06-22 02:53:13 +09:00
|
|
|
if (!Number.isInteger(mode) || !Object.values(SpreadMode).includes(mode)) {
|
|
|
|
throw new Error(`Invalid spread mode: ${mode}`);
|
2018-05-15 12:10:32 +09:00
|
|
|
}
|
2018-06-29 21:14:11 +09:00
|
|
|
this._spreadMode = mode;
|
2018-06-29 22:07:42 +09:00
|
|
|
this.eventBus.dispatch('spreadmodechanged', { source: this, mode, });
|
|
|
|
|
2018-06-29 22:28:38 +09:00
|
|
|
this._updateSpreadMode(/* pageNumber = */ this._currentPageNumber);
|
2018-06-29 22:07:42 +09:00
|
|
|
}
|
|
|
|
|
2018-06-29 22:28:38 +09:00
|
|
|
_updateSpreadMode(pageNumber = null) {
|
|
|
|
if (!this.pdfDocument) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const viewer = this.viewer, pages = this._pages;
|
|
|
|
// Temporarily remove all the pages from the DOM.
|
|
|
|
viewer.textContent = '';
|
|
|
|
|
|
|
|
if (this._spreadMode === SpreadMode.NONE) {
|
|
|
|
for (let i = 0, iMax = pages.length; i < iMax; ++i) {
|
|
|
|
viewer.appendChild(pages[i].div);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const parity = this._spreadMode - 1;
|
|
|
|
let spread = null;
|
|
|
|
for (let i = 0, iMax = pages.length; i < iMax; ++i) {
|
|
|
|
if (spread === null) {
|
|
|
|
spread = document.createElement('div');
|
|
|
|
spread.className = 'spread';
|
|
|
|
viewer.appendChild(spread);
|
|
|
|
} else if (i % 2 === parity) {
|
|
|
|
spread = spread.cloneNode(false);
|
|
|
|
viewer.appendChild(spread);
|
|
|
|
}
|
|
|
|
spread.appendChild(pages[i].div);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pageNumber) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.scrollPageIntoView({ pageNumber, });
|
|
|
|
this.update();
|
2018-05-15 12:10:32 +09:00
|
|
|
}
|
2017-07-09 20:07:06 +09:00
|
|
|
}
|
2014-09-15 23:49:24 +09:00
|
|
|
|
2017-03-28 08:07:27 +09:00
|
|
|
export {
|
2017-08-01 21:11:28 +09:00
|
|
|
BaseViewer,
|
2018-05-15 12:10:32 +09:00
|
|
|
ScrollMode,
|
2018-05-15 12:10:32 +09:00
|
|
|
SpreadMode,
|
2017-03-28 08:07:27 +09:00
|
|
|
};
|