diff --git a/web/interfaces.js b/web/interfaces.js index 87aff84f9..69cd55d5f 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -57,6 +57,11 @@ class IPDFLinkService { */ executeNamedAction(action) {} + /** + * @param {Object} params + */ + onFileAttachmentAnnotation({ id, filename, content, }) {} + /** * @param {number} pageNum - page number. * @param {Object} pageRef - reference to the page. diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js index 984a75314..cb592cc9b 100644 --- a/web/pdf_link_service.js +++ b/web/pdf_link_service.js @@ -24,17 +24,14 @@ import { parseQueryString } from './ui_utils'; /** * Performs navigation functions inside PDF, such as opening specified page, * or destination. - * @class * @implements {IPDFLinkService} */ -var PDFLinkService = (function PDFLinkServiceClosure() { +class PDFLinkService { /** - * @constructs PDFLinkService * @param {PDFLinkServiceOptions} options */ - function PDFLinkService(options) { - options = options || {}; - this.eventBus = options.eventBus || getGlobalEventBus(); + constructor({ eventBus, } = {}) { + this.eventBus = eventBus || getGlobalEventBus(); this.baseUrl = null; this.pdfDocument = null; this.pdfViewer = null; @@ -43,432 +40,423 @@ var PDFLinkService = (function PDFLinkServiceClosure() { 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 {string|Array} dest - The named, or explicit, PDF destination. - */ - navigateTo(dest) { - let goToDestination = ({ namedDest, explicitDest, }) => { - // Dest array looks like that: - let destRef = explicitDest[0], pageNumber; - - if (destRef instanceof Object) { - pageNumber = this._cachedPageNumber(destRef); - - if (pageNumber === null) { - // Fetch the page reference if it's not yet available. This could - // only occur during loading, before all pages have been resolved. - this.pdfDocument.getPageIndex(destRef).then((pageIndex) => { - this.cachePageRef(pageIndex + 1, destRef); - goToDestination({ namedDest, explicitDest, }); - }).catch(() => { - console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` + - `a valid page reference, for dest="${dest}".`); - }); - return; - } - } else if ((destRef | 0) === destRef) { // Integer - pageNumber = destRef + 1; - } else { - console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` + - `a valid destination reference, for dest="${dest}".`); - return; - } - if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) { - console.error(`PDFLinkService.navigateTo: "${pageNumber}" is not ` + - `a valid page number, for dest="${dest}".`); - return; - } - - this.pdfViewer.scrollPageIntoView({ - pageNumber, - destArray: explicitDest, - }); - - if (this.pdfHistory) { // Update the browsing history, if enabled. - this.pdfHistory.push({ - dest: explicitDest, - hash: namedDest, - page: pageNumber, - }); - } - }; - - new Promise((resolve, reject) => { - if (typeof dest === 'string') { - this.pdfDocument.getDestination(dest).then((destArray) => { - resolve({ - namedDest: dest, - explicitDest: destArray, - }); - }); - return; - } - resolve({ - namedDest: '', - explicitDest: dest, - }); - }).then((data) => { - if (!(data.explicitDest instanceof Array)) { - console.error(`PDFLinkService.navigateTo: "${data.explicitDest}" is` + - ` not a valid destination array, for dest="${dest}".`); - return; - } - goToDestination(data); - }); - }, - - /** - * @param {string|Array} dest - The PDF destination object. - * @returns {string} The hyperlink to the PDF object. - */ - getDestinationHash(dest) { - if (typeof dest === 'string') { - return this.getAnchorUrl('#' + escape(dest)); - } - if (dest instanceof Array) { - let 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 . - * @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: pageNumber || this.page, - destArray: dest, - allowNegativeOffset: true, - }); - } else if (pageNumber) { - this.page = pageNumber; // simple page - } - if ('pagemode' in params) { - this.eventBus.dispatch('pagemode', { - source: this, - mode: params.pagemode, - }); - } - } else { // Named (or explicit) destination. - if ((typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) && - /^\d+$/.test(hash) && hash <= this.pagesCount) { - console.warn('PDFLinkService_setHash: specifying a page number ' + - 'directly after the hash symbol (#) is deprecated, ' + - 'please use the "#page=' + hash + '" form instead.'); - this.page = hash | 0; - } - - dest = unescape(hash); - try { - dest = JSON.parse(dest); - - if (!(dest instanceof Array)) { - // Avoid incorrectly rejecting a valid named destination, such as - // e.g. "4.3" or "true", because `JSON.parse` converted its type. - dest = dest.toString(); - } - } 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, - }); - }, - - /** - * @param {Object} params - */ - onFileAttachmentAnnotation(params = {}) { - this.eventBus.dispatch('fileattachmentannotation', { - source: this, - id: params.id, - filename: params.filename, - content: params.content, - }); - }, - - /** - * @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; - }, - - _cachedPageNumber: function PDFLinkService_cachedPageNumber(pageRef) { - var refStr = pageRef.num + ' ' + pageRef.gen + ' R'; - return (this._pagesRefCache && this._pagesRefCache[refStr]) || null; - }, - }; - - 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; + setDocument(pdfDocument, baseUrl) { + this.baseUrl = baseUrl; + this.pdfDocument = pdfDocument; + this._pagesRefCache = Object.create(null); } - return PDFLinkService; -})(); + setViewer(pdfViewer) { + this.pdfViewer = pdfViewer; + } -var SimpleLinkService = (function SimpleLinkServiceClosure() { - function SimpleLinkService() {} + setHistory(pdfHistory) { + this.pdfHistory = pdfHistory; + } - SimpleLinkService.prototype = { - /** - * @returns {number} - */ - get page() { - return 0; - }, - /** - * @param {number} value - */ - set page(value) {}, - /** - * @param dest - The PDF destination object. - */ - navigateTo(dest) {}, - /** - * @param dest - The PDF destination object. - * @returns {string} The hyperlink to the PDF object. - */ - getDestinationHash(dest) { - return '#'; - }, - /** - * @param hash - The PDF parameters/hash. - * @returns {string} The hyperlink to the PDF object. - */ - getAnchorUrl(hash) { - return '#'; - }, - /** - * @param {string} hash - */ - setHash(hash) {}, - /** - * @param {string} action - */ - executeNamedAction(action) {}, - /** - * @param {Object} params - */ - onFileAttachmentAnnotation(params) {}, - /** - * @param {number} pageNum - page number. - * @param {Object} pageRef - reference to the page. - */ - cachePageRef(pageNum, pageRef) {}, - }; - return SimpleLinkService; -})(); + /** + * @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 {string|Array} dest - The named, or explicit, PDF destination. + */ + navigateTo(dest) { + let goToDestination = ({ namedDest, explicitDest, }) => { + // Dest array looks like that: + let destRef = explicitDest[0], pageNumber; + + if (destRef instanceof Object) { + pageNumber = this._cachedPageNumber(destRef); + + if (pageNumber === null) { + // Fetch the page reference if it's not yet available. This could + // only occur during loading, before all pages have been resolved. + this.pdfDocument.getPageIndex(destRef).then((pageIndex) => { + this.cachePageRef(pageIndex + 1, destRef); + goToDestination({ namedDest, explicitDest, }); + }).catch(() => { + console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` + + `a valid page reference, for dest="${dest}".`); + }); + return; + } + } else if ((destRef | 0) === destRef) { // Integer + pageNumber = destRef + 1; + } else { + console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` + + `a valid destination reference, for dest="${dest}".`); + return; + } + if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) { + console.error(`PDFLinkService.navigateTo: "${pageNumber}" is not ` + + `a valid page number, for dest="${dest}".`); + return; + } + + this.pdfViewer.scrollPageIntoView({ + pageNumber, + destArray: explicitDest, + }); + + if (this.pdfHistory) { // Update the browsing history, if enabled. + this.pdfHistory.push({ + dest: explicitDest, + hash: namedDest, + page: pageNumber, + }); + } + }; + + new Promise((resolve, reject) => { + if (typeof dest === 'string') { + this.pdfDocument.getDestination(dest).then((destArray) => { + resolve({ + namedDest: dest, + explicitDest: destArray, + }); + }); + return; + } + resolve({ + namedDest: '', + explicitDest: dest, + }); + }).then((data) => { + if (!(data.explicitDest instanceof Array)) { + console.error(`PDFLinkService.navigateTo: "${data.explicitDest}" is` + + ` not a valid destination array, for dest="${dest}".`); + return; + } + goToDestination(data); + }); + } + + /** + * @param {string|Array} dest - The PDF destination object. + * @returns {string} The hyperlink to the PDF object. + */ + getDestinationHash(dest) { + if (typeof dest === 'string') { + return this.getAnchorUrl('#' + escape(dest)); + } + if (dest instanceof Array) { + let 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 . + * @param {String} anchor The anchor hash, including the #. + * @returns {string} The hyperlink to the PDF object. + */ + getAnchorUrl(anchor) { + return (this.baseUrl || '') + anchor; + } + + /** + * @param {string} hash + */ + setHash(hash) { + let pageNumber, dest; + if (hash.indexOf('=') >= 0) { + let 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. + let zoomArgs = params.zoom.split(','); // scale,left,top + let zoomArg = zoomArgs[0]; + let 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: pageNumber || this.page, + destArray: dest, + allowNegativeOffset: true, + }); + } else if (pageNumber) { + this.page = pageNumber; // simple page + } + if ('pagemode' in params) { + this.eventBus.dispatch('pagemode', { + source: this, + mode: params.pagemode, + }); + } + } else { // Named (or explicit) destination. + if ((typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) && + /^\d+$/.test(hash) && hash <= this.pagesCount) { + console.warn('PDFLinkService_setHash: specifying a page number ' + + 'directly after the hash symbol (#) is deprecated, ' + + `please use the "#page=${hash}" form instead.`); + this.page = hash | 0; + } + + dest = unescape(hash); + try { + dest = JSON.parse(dest); + + if (!(dest instanceof Array)) { + // Avoid incorrectly rejecting a valid named destination, such as + // e.g. "4.3" or "true", because `JSON.parse` converted its type. + dest = dest.toString(); + } + } 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(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, + }); + } + + /** + * @param {Object} params + */ + onFileAttachmentAnnotation({ id, filename, content, }) { + this.eventBus.dispatch('fileattachmentannotation', { + source: this, + id, + filename, + content, + }); + } + + /** + * @param {number} pageNum - page number. + * @param {Object} pageRef - reference to the page. + */ + cachePageRef(pageNum, pageRef) { + let refStr = pageRef.num + ' ' + pageRef.gen + ' R'; + this._pagesRefCache[refStr] = pageNum; + } + + _cachedPageNumber(pageRef) { + let refStr = pageRef.num + ' ' + pageRef.gen + ' R'; + return (this._pagesRefCache && this._pagesRefCache[refStr]) || null; + } +} + +function isValidExplicitDestination(dest) { + if (!(dest instanceof Array)) { + return false; + } + let destLength = dest.length, allowNull = true; + if (destLength < 2) { + return false; + } + let 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; + } + let 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 (let i = 2; i < destLength; i++) { + let param = dest[i]; + if (!(typeof param === 'number' || (allowNull && param === null))) { + return false; + } + } + return true; +} + +class SimpleLinkService { + /** + * @returns {number} + */ + get page() { + return 0; + } + /** + * @param {number} value + */ + set page(value) {} + /** + * @param dest - The PDF destination object. + */ + navigateTo(dest) {} + /** + * @param dest - The PDF destination object. + * @returns {string} The hyperlink to the PDF object. + */ + getDestinationHash(dest) { + return '#'; + } + /** + * @param hash - The PDF parameters/hash. + * @returns {string} The hyperlink to the PDF object. + */ + getAnchorUrl(hash) { + return '#'; + } + /** + * @param {string} hash + */ + setHash(hash) {} + /** + * @param {string} action + */ + executeNamedAction(action) {} + /** + * @param {Object} params + */ + onFileAttachmentAnnotation({ id, filename, content, }) {} + /** + * @param {number} pageNum - page number. + * @param {Object} pageRef - reference to the page. + */ + cachePageRef(pageNum, pageRef) {} +} export { PDFLinkService,