7706cfa9fe
With PR 7502 we no longer dispatch an event when the `val` is out of bounds, so to better communicate why nothing happens this patch logs an error in that case (similar to the logging of errors when trying to set an invalid scale). The way that the default viewer is currently implemented, means that e.g. keyboard short-cuts could trigger the new error. Hence this patch also adds the necessary validation code, both to `app.js` and `pdf_link_service.js` to prevent unnecessary errors.
441 lines
13 KiB
JavaScript
441 lines
13 KiB
JavaScript
/* Copyright 2015 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_link_service', ['exports', 'pdfjs-web/ui_utils',
|
|
'pdfjs-web/dom_events'], factory);
|
|
} else if (typeof exports !== 'undefined') {
|
|
factory(exports, require('./ui_utils.js'), require('./dom_events.js'));
|
|
} else {
|
|
factory((root.pdfjsWebPDFLinkService = {}), root.pdfjsWebUIUtils,
|
|
root.pdfjsWebDOMEvents);
|
|
}
|
|
}(this, function (exports, uiUtils, domEvents) {
|
|
|
|
var parseQueryString = uiUtils.parseQueryString;
|
|
|
|
var PageNumberRegExp = /^\d+$/;
|
|
function isPageNumber(str) {
|
|
return PageNumberRegExp.test(str);
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} PDFLinkServiceOptions
|
|
* @property {EventBus} eventBus - The application event bus.
|
|
*/
|
|
|
|
/**
|
|
* Performs navigation functions inside PDF, such as opening specified page,
|
|
* or destination.
|
|
* @class
|
|
* @implements {IPDFLinkService}
|
|
*/
|
|
var PDFLinkService = (function PDFLinkServiceClosure() {
|
|
/**
|
|
* @constructs PDFLinkService
|
|
* @param {PDFLinkServiceOptions} options
|
|
*/
|
|
function PDFLinkService(options) {
|
|
options = options || {};
|
|
this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
|
|
this.baseUrl = null;
|
|
this.pdfDocument = null;
|
|
this.pdfViewer = null;
|
|
this.pdfHistory = null;
|
|
|
|
this._pagesRefCache = null;
|
|
}
|
|
|
|
PDFLinkService.prototype = {
|
|
setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
|
|
this.baseUrl = baseUrl;
|
|
this.pdfDocument = pdfDocument;
|
|
this._pagesRefCache = Object.create(null);
|
|
},
|
|
|
|
setViewer: function PDFLinkService_setViewer(pdfViewer) {
|
|
this.pdfViewer = pdfViewer;
|
|
},
|
|
|
|
setHistory: function PDFLinkService_setHistory(pdfHistory) {
|
|
this.pdfHistory = pdfHistory;
|
|
},
|
|
|
|
/**
|
|
* @returns {number}
|
|
*/
|
|
get pagesCount() {
|
|
return this.pdfDocument ? this.pdfDocument.numPages : 0;
|
|
},
|
|
|
|
/**
|
|
* @returns {number}
|
|
*/
|
|
get page() {
|
|
return this.pdfViewer.currentPageNumber;
|
|
},
|
|
|
|
/**
|
|
* @param {number} value
|
|
*/
|
|
set page(value) {
|
|
this.pdfViewer.currentPageNumber = value;
|
|
},
|
|
|
|
/**
|
|
* @param dest - The PDF destination object.
|
|
*/
|
|
navigateTo: function PDFLinkService_navigateTo(dest) {
|
|
var destString = '';
|
|
var self = this;
|
|
|
|
var goToDestination = function(destRef) {
|
|
// dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
|
|
var pageNumber = destRef instanceof Object ?
|
|
self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
|
|
(destRef + 1);
|
|
if (pageNumber) {
|
|
if (pageNumber > self.pagesCount) {
|
|
console.error('PDFLinkService_navigateTo: ' +
|
|
'Trying to navigate to a non-existent page.');
|
|
return;
|
|
}
|
|
self.pdfViewer.scrollPageIntoView(pageNumber, dest);
|
|
|
|
if (self.pdfHistory) {
|
|
// Update the browsing history.
|
|
self.pdfHistory.push({
|
|
dest: dest,
|
|
hash: destString,
|
|
page: pageNumber
|
|
});
|
|
}
|
|
} else {
|
|
self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
|
|
var pageNum = pageIndex + 1;
|
|
var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
|
|
self._pagesRefCache[cacheKey] = pageNum;
|
|
goToDestination(destRef);
|
|
});
|
|
}
|
|
};
|
|
|
|
var destinationPromise;
|
|
if (typeof dest === 'string') {
|
|
destString = dest;
|
|
destinationPromise = this.pdfDocument.getDestination(dest);
|
|
} else {
|
|
destinationPromise = Promise.resolve(dest);
|
|
}
|
|
destinationPromise.then(function(destination) {
|
|
dest = destination;
|
|
if (!(destination instanceof Array)) {
|
|
return; // invalid destination
|
|
}
|
|
goToDestination(destination[0]);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param dest - The PDF destination object.
|
|
* @returns {string} The hyperlink to the PDF object.
|
|
*/
|
|
getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
|
|
if (typeof dest === 'string') {
|
|
// In practice, a named destination may contain only a number.
|
|
// If that happens, use the '#nameddest=' form to avoid the link
|
|
// redirecting to a page, instead of the correct destination.
|
|
return this.getAnchorUrl(
|
|
'#' + (isPageNumber(dest) ? 'nameddest=' : '') + escape(dest));
|
|
}
|
|
if (dest instanceof Array) {
|
|
var str = JSON.stringify(dest);
|
|
return this.getAnchorUrl('#' + escape(str));
|
|
}
|
|
return this.getAnchorUrl('');
|
|
},
|
|
|
|
/**
|
|
* Prefix the full url on anchor links to make sure that links are resolved
|
|
* relative to the current URL instead of the one defined in <base href>.
|
|
* @param {String} anchor The anchor hash, including the #.
|
|
* @returns {string} The hyperlink to the PDF object.
|
|
*/
|
|
getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
|
|
return (this.baseUrl || '') + anchor;
|
|
},
|
|
|
|
/**
|
|
* @param {string} hash
|
|
*/
|
|
setHash: function PDFLinkService_setHash(hash) {
|
|
var pageNumber, dest;
|
|
if (hash.indexOf('=') >= 0) {
|
|
var params = parseQueryString(hash);
|
|
if ('search' in params) {
|
|
this.eventBus.dispatch('findfromurlhash', {
|
|
source: this,
|
|
query: params['search'].replace(/"/g, ''),
|
|
phraseSearch: (params['phrase'] === 'true')
|
|
});
|
|
}
|
|
// borrowing syntax from "Parameters for Opening PDF Files"
|
|
if ('nameddest' in params) {
|
|
if (this.pdfHistory) {
|
|
this.pdfHistory.updateNextHashParam(params.nameddest);
|
|
}
|
|
this.navigateTo(params.nameddest);
|
|
return;
|
|
}
|
|
if ('page' in params) {
|
|
pageNumber = (params.page | 0) || 1;
|
|
}
|
|
if ('zoom' in params) {
|
|
// Build the destination array.
|
|
var zoomArgs = params.zoom.split(','); // scale,left,top
|
|
var zoomArg = zoomArgs[0];
|
|
var zoomArgNumber = parseFloat(zoomArg);
|
|
|
|
if (zoomArg.indexOf('Fit') === -1) {
|
|
// If the zoomArg is a number, it has to get divided by 100. If it's
|
|
// a string, it should stay as it is.
|
|
dest = [null, { name: 'XYZ' },
|
|
zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
|
|
zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
|
|
(zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
|
|
} else {
|
|
if (zoomArg === 'Fit' || zoomArg === 'FitB') {
|
|
dest = [null, { name: zoomArg }];
|
|
} else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
|
|
(zoomArg === 'FitV' || zoomArg === 'FitBV')) {
|
|
dest = [null, { name: zoomArg },
|
|
zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
|
|
} else if (zoomArg === 'FitR') {
|
|
if (zoomArgs.length !== 5) {
|
|
console.error('PDFLinkService_setHash: ' +
|
|
'Not enough parameters for \'FitR\'.');
|
|
} else {
|
|
dest = [null, { name: zoomArg },
|
|
(zoomArgs[1] | 0), (zoomArgs[2] | 0),
|
|
(zoomArgs[3] | 0), (zoomArgs[4] | 0)];
|
|
}
|
|
} else {
|
|
console.error('PDFLinkService_setHash: \'' + zoomArg +
|
|
'\' is not a valid zoom value.');
|
|
}
|
|
}
|
|
}
|
|
if (dest) {
|
|
this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
|
|
} else if (pageNumber) {
|
|
this.page = pageNumber; // simple page
|
|
}
|
|
if ('pagemode' in params) {
|
|
this.eventBus.dispatch('pagemode', {
|
|
source: this,
|
|
mode: params.pagemode
|
|
});
|
|
}
|
|
} else if (isPageNumber(hash)) { // Page number.
|
|
this.page = hash | 0;
|
|
} else { // Named (or explicit) destination.
|
|
dest = unescape(hash);
|
|
try {
|
|
dest = JSON.parse(dest);
|
|
} catch (ex) {}
|
|
|
|
if (typeof dest === 'string' || isValidExplicitDestination(dest)) {
|
|
if (this.pdfHistory) {
|
|
this.pdfHistory.updateNextHashParam(dest);
|
|
}
|
|
this.navigateTo(dest);
|
|
return;
|
|
}
|
|
console.error('PDFLinkService_setHash: \'' + unescape(hash) +
|
|
'\' is not a valid destination.');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {string} action
|
|
*/
|
|
executeNamedAction: function PDFLinkService_executeNamedAction(action) {
|
|
// See PDF reference, table 8.45 - Named action
|
|
switch (action) {
|
|
case 'GoBack':
|
|
if (this.pdfHistory) {
|
|
this.pdfHistory.back();
|
|
}
|
|
break;
|
|
|
|
case 'GoForward':
|
|
if (this.pdfHistory) {
|
|
this.pdfHistory.forward();
|
|
}
|
|
break;
|
|
|
|
case 'NextPage':
|
|
if (this.page < this.pagesCount) {
|
|
this.page++;
|
|
}
|
|
break;
|
|
|
|
case 'PrevPage':
|
|
if (this.page > 1) {
|
|
this.page--;
|
|
}
|
|
break;
|
|
|
|
case 'LastPage':
|
|
this.page = this.pagesCount;
|
|
break;
|
|
|
|
case 'FirstPage':
|
|
this.page = 1;
|
|
break;
|
|
|
|
default:
|
|
break; // No action according to spec
|
|
}
|
|
|
|
this.eventBus.dispatch('namedaction', {
|
|
source: this,
|
|
action: action
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param {number} pageNum - page number.
|
|
* @param {Object} pageRef - reference to the page.
|
|
*/
|
|
cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
|
|
var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
|
|
this._pagesRefCache[refStr] = pageNum;
|
|
}
|
|
};
|
|
|
|
function isValidExplicitDestination(dest) {
|
|
if (!(dest instanceof Array)) {
|
|
return false;
|
|
}
|
|
var destLength = dest.length, allowNull = true;
|
|
if (destLength < 2) {
|
|
return false;
|
|
}
|
|
var page = dest[0];
|
|
if (!(typeof page === 'object' &&
|
|
typeof page.num === 'number' && (page.num | 0) === page.num &&
|
|
typeof page.gen === 'number' && (page.gen | 0) === page.gen) &&
|
|
!(typeof page === 'number' && (page | 0) === page && page >= 0)) {
|
|
return false;
|
|
}
|
|
var zoom = dest[1];
|
|
if (!(typeof zoom === 'object' && typeof zoom.name === 'string')) {
|
|
return false;
|
|
}
|
|
switch (zoom.name) {
|
|
case 'XYZ':
|
|
if (destLength !== 5) {
|
|
return false;
|
|
}
|
|
break;
|
|
case 'Fit':
|
|
case 'FitB':
|
|
return destLength === 2;
|
|
case 'FitH':
|
|
case 'FitBH':
|
|
case 'FitV':
|
|
case 'FitBV':
|
|
if (destLength !== 3) {
|
|
return false;
|
|
}
|
|
break;
|
|
case 'FitR':
|
|
if (destLength !== 6) {
|
|
return false;
|
|
}
|
|
allowNull = false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
for (var i = 2; i < destLength; i++) {
|
|
var param = dest[i];
|
|
if (!(typeof param === 'number' || (allowNull && param === null))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return PDFLinkService;
|
|
})();
|
|
|
|
var SimpleLinkService = (function SimpleLinkServiceClosure() {
|
|
function SimpleLinkService() {}
|
|
|
|
SimpleLinkService.prototype = {
|
|
/**
|
|
* @returns {number}
|
|
*/
|
|
get page() {
|
|
return 0;
|
|
},
|
|
/**
|
|
* @param {number} value
|
|
*/
|
|
set page(value) {},
|
|
/**
|
|
* @param dest - The PDF destination object.
|
|
*/
|
|
navigateTo: function (dest) {},
|
|
/**
|
|
* @param dest - The PDF destination object.
|
|
* @returns {string} The hyperlink to the PDF object.
|
|
*/
|
|
getDestinationHash: function (dest) {
|
|
return '#';
|
|
},
|
|
/**
|
|
* @param hash - The PDF parameters/hash.
|
|
* @returns {string} The hyperlink to the PDF object.
|
|
*/
|
|
getAnchorUrl: function (hash) {
|
|
return '#';
|
|
},
|
|
/**
|
|
* @param {string} hash
|
|
*/
|
|
setHash: function (hash) {},
|
|
/**
|
|
* @param {string} action
|
|
*/
|
|
executeNamedAction: function (action) {},
|
|
/**
|
|
* @param {number} pageNum - page number.
|
|
* @param {Object} pageRef - reference to the page.
|
|
*/
|
|
cachePageRef: function (pageNum, pageRef) {}
|
|
};
|
|
return SimpleLinkService;
|
|
})();
|
|
|
|
exports.PDFLinkService = PDFLinkService;
|
|
exports.SimpleLinkService = SimpleLinkService;
|
|
}));
|