/* eslint-disable node/handle-callback-err, max-len, new-cap, no-return-assign, no-unused-vars, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * DS103: Rewrite code to no longer use __guard__ * DS205: Consider reworking code to avoid use of IIFEs * DS206: Consider reworking classes to avoid initClass * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ import App from '../../../base' import PDFJS from './pdfJsLoader' import { captureMessage } from '../../../infrastructure/error-reporter' export default App.factory( 'PDFRenderer', function ($timeout, pdfAnnotations, pdfTextLayer, pdfSpinner) { let PDFRenderer return (PDFRenderer = (function () { PDFRenderer = class PDFRenderer { static initClass() { this.prototype.JOB_QUEUE_INTERVAL = 25 this.prototype.PAGE_LOAD_TIMEOUT = 60 * 1000 this.prototype.INDICATOR_DELAY1 = 100 // time to delay before showing the indicator this.prototype.INDICATOR_DELAY2 = 250 // time until the indicator starts animating this.prototype.TEXTLAYER_TIMEOUT = 100 } constructor(url, options) { // set up external character mappings - needed for Japanese etc this.url = url this.options = options this.scale = this.options.scale || 1 let disableFontFace if ( __guard__( window.location != null ? window.location.search : undefined, x => x.indexOf('disable-font-face=true') ) >= 0 ) { disableFontFace = true } else { disableFontFace = false } this.pdfjs = PDFJS.getDocument({ url: this.url, cMapUrl: window.pdfCMapsPath, cMapPacked: true, disableFontFace, // Enable fetching with Range headers to restrict individual // requests to 128kb. // To do this correctly we must: // a) disable auto-fetching of the whole file upfront // b) disable streaming (which in this context means streaming of // the response into memory). This isn't supported when using // Range headers, but shouldn't be a problem since we are already // limiting individual response size through chunked range // requests rangeChunkSize: 128 * 1024, disableAutoFetch: !!this.options.disableAutoFetch, disableStream: !!this.options.disableAutoFetch, }) this.pdfjs.onProgress = this.options.progressCallback this.document = this.pdfjs this.navigateFn = this.options.navigateFn this.spinner = new pdfSpinner() this.resetState() this.document.promise.then(pdfDocument => { return pdfDocument.getDownloadInfo().then(() => { return this.options.loadedCallback() }) }) this.errorCallback = this.options.errorCallback this.pageSizeChangeCallback = this.options.pageSizeChangeCallback this.pdfjs.promise.catch(exception => { // error getting document return this.errorCallback(exception) }) } resetState() { this.renderQueue = [] if (this.queueTimer != null) { clearTimeout(this.queueTimer) } // clear any existing timers, render tasks for (const timer of Array.from(this.spinTimer || [])) { clearTimeout(timer) } for (const page of Array.from(this.pageState || [])) { __guard__(page != null ? page.loadTask : undefined, x => x.cancel()) __guard__(page != null ? page.renderTask : undefined, x1 => x1.cancel() ) } // initialise/reset the state this.pageState = [] this.spinTimer = [] // timers for starting the spinners (to avoid jitter) this.spinTimerDone = [] // array of pages where the spinner has activated return (this.jobs = 0) } getNumPages() { return this.document.promise.then(pdfDocument => pdfDocument.numPages) } getPage(pageNum) { return this.document.promise.then(pdfDocument => pdfDocument.getPage(pageNum) ) } getPdfViewport(pageNum, scale) { if (scale == null) { ;({ scale } = this) } return this.document.promise.then(pdfDocument => { return pdfDocument.getPage(pageNum).then( function (page) { let viewport return (viewport = page.getViewport({ scale: scale })) }, error => { return typeof this.errorCallback === 'function' ? this.errorCallback(error) : undefined } ) }) } getDestinations() { return this.document.promise.then(pdfDocument => pdfDocument.getDestinations() ) } getDestination(dest) { return this.document.promise.then( pdfDocument => pdfDocument.getDestination(dest), error => { return typeof this.errorCallback === 'function' ? this.errorCallback(error) : undefined } ) } getPageIndex(ref) { return this.document.promise.then(pdfDocument => { return pdfDocument.getPageIndex(ref).then( idx => idx, error => { return typeof this.errorCallback === 'function' ? this.errorCallback(error) : undefined } ) }) } getScale() { return this.scale } setScale(scale) { this.scale = scale return this.resetState() } triggerRenderQueue(interval) { if (interval == null) { interval = this.JOB_QUEUE_INTERVAL } if (this.queueTimer != null) { clearTimeout(this.queueTimer) } return (this.queueTimer = setTimeout(() => { this.queueTimer = null return this.processRenderQueue() }, interval)) } removeCompletedJob(pagenum) { this.jobs = this.jobs - 1 return this.triggerRenderQueue(0) } renderPages(pages) { if (this.shuttingDown) { return } this.renderQueue = Array.from(pages).map(page => ({ element: page.elementChildren, pagenum: page.pageNum, })) return this.triggerRenderQueue() } renderPage(page) { if (this.shuttingDown) { return } const current = { element: page.elementChildren, pagenum: page.pageNum, } this.renderQueue.push(current) return this.processRenderQueue() } getPageDetails(page) { return [page.element.canvas, page.pagenum] } // handle the loading indicators for each page startIndicators() { // make an array of the pages in the queue this.queuedPages = [] for (var page of Array.from(this.renderQueue)) { this.queuedPages[page.pagenum] = true } // clear any unfinished spinner timers on pages that aren't in the queue any more for (const pagenum in this.spinTimer) { if (!this.queuedPages[pagenum]) { clearTimeout(this.spinTimer[pagenum]) delete this.spinTimer[pagenum] } } // add indicators for any new pages in the current queue return (() => { const result = [] for (page of Array.from(this.renderQueue)) { if ( !this.spinTimer[page.pagenum] && !this.spinTimerDone[page.pagenum] ) { result.push(this.startIndicator(page)) } } return result })() } startIndicator(page) { const [canvas, pagenum] = Array.from(this.getPageDetails(page)) canvas.addClass('pdfng-loading') return (this.spinTimer[pagenum] = setTimeout(() => { for (const queuedPage of Array.from(this.renderQueue)) { if (pagenum === queuedPage.pagenum) { this.spinner.add(canvas, { static: true }) this.spinTimerDone[pagenum] = true break } } return delete this.spinTimer[pagenum] }, this.INDICATOR_DELAY1)) } updateIndicator(page) { const [canvas, pagenum] = Array.from(this.getPageDetails(page)) // did the spinner insert itself already? if (this.spinTimerDone[pagenum]) { return (this.spinTimer[pagenum] = setTimeout(() => { this.spinner.start(canvas) return delete this.spinTimer[pagenum] }, this.INDICATOR_DELAY2)) } else { // stop the existing spin timer clearTimeout(this.spinTimer[pagenum]) // start a new one which will also start spinning return (this.spinTimer[pagenum] = setTimeout(() => { this.spinner.add(canvas, { static: true }) this.spinTimerDone[pagenum] = true return (this.spinTimer[pagenum] = setTimeout(() => { this.spinner.start(canvas) return delete this.spinTimer[pagenum] }, this.INDICATOR_DELAY2)) }, this.INDICATOR_DELAY1)) } } clearIndicator(page) { const [canvas, pagenum] = Array.from(this.getPageDetails(page)) this.spinner.stop(canvas) clearTimeout(this.spinTimer[pagenum]) delete this.spinTimer[pagenum] return (this.spinTimerDone[pagenum] = true) } // handle the queue of pages to be rendered processRenderQueue() { let pageState if (this.shuttingDown) { return } // mark all pages in the queue as loading this.startIndicators() // bail out if there is already a render job running if (this.jobs > 0) { return } // take the first page in the queue let page = this.renderQueue.shift() // check if it is in action already while (page != null && this.pageState[page.pagenum] != null) { page = this.renderQueue.shift() } if (page == null) { return } const [element, pagenum] = Array.from([page.element, page.pagenum]) this.jobs = this.jobs + 1 // update the spinner to make it spinning (signifies loading has begun) this.updateIndicator(page) let timedOut = false const timer = $timeout(() => { // page load timed out if (loadTask.cancelled) { return } // return from cancelled page load captureMessage( `pdfng page load timed out after ${this.PAGE_LOAD_TIMEOUT}ms` ) timedOut = true this.clearIndicator(page) // @jobs = @jobs - 1 // @triggerRenderQueue(0) return typeof this.errorCallback === 'function' ? this.errorCallback('timeout') : undefined }, this.PAGE_LOAD_TIMEOUT) var loadTask = this.getPage(pagenum) loadTask.cancel = function () { return (this.cancelled = true) } this.pageState[pagenum] = pageState = { loadTask } return loadTask .then(pageObject => { // page load success $timeout.cancel(timer) if (loadTask.cancelled) { return } // return from cancelled page load pageState.renderTask = this.doRender(element, pagenum, pageObject) return pageState.renderTask.promise.then( () => { // render task success this.clearIndicator(page) pageState.complete = true delete pageState.renderTask return this.removeCompletedJob(pagenum) }, () => { // render task failed // could display an error icon pageState.complete = false delete pageState.renderTask return this.removeCompletedJob(pagenum) } ) }) .catch(error => { // page load error $timeout.cancel(timer) return this.clearIndicator(page) }) } doRender(element, pagenum, page) { const self = this const { scale } = this if (scale == null) { // scale is undefined, returning return } const canvas = $( '' ) // In Windows+IE we must have the canvas in the DOM during // rendering to see the fonts defined in the DOM. If we try to // render 'offscreen' then all the text will be sans-serif. // Previously we rendered offscreen and added in the canvas // when rendering was complete. element.canvas.replaceWith(canvas) const viewport = page.getViewport({ scale: scale }) const devicePixelRatio = window.devicePixelRatio || 1 const ctx = canvas[0].getContext('2d') const backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1 const pixelRatio = devicePixelRatio / backingStoreRatio const scaledWidth = (Math.floor(viewport.width) * pixelRatio) | 0 const scaledHeight = (Math.floor(viewport.height) * pixelRatio) | 0 const newWidth = Math.floor(viewport.width) const newHeight = Math.floor(viewport.height) canvas[0].height = scaledHeight canvas[0].width = scaledWidth canvas.height(newHeight + 'px') canvas.width(newWidth + 'px') const oldHeight = element.canvas.height() const oldWidth = element.canvas.width() if (newHeight !== oldHeight || newWidth !== oldWidth) { element.canvas.height(newHeight + 'px') element.canvas.width(newWidth + 'px') element.container.height(newHeight + 'px') element.container.width(newWidth + 'px') if (typeof this.pageSizeChangeCallback === 'function') { this.pageSizeChangeCallback(pagenum, newHeight - oldHeight) } } const textLayer = new pdfTextLayer({ textLayerDiv: element.text[0], viewport, renderer: PDFJS.renderTextLayer, }) const annotationsLayer = new pdfAnnotations({ annotations: element.annotations[0], viewport, navigateFn: this.navigateFn, }) const result = page.render({ canvasContext: ctx, viewport, transform: [pixelRatio, 0, 0, pixelRatio, 0, 0], }) const textLayerTimeout = this.TEXTLAYER_TIMEOUT result.promise .then(function () { // page render success canvas.removeClass('pdfng-rendering') page.getTextContent({ normalizeWhitespace: true }).then( function (textContent) { textLayer.setTextContent(textContent) return textLayer.render(textLayerTimeout) }, error => typeof self.errorCallback === 'function' ? self.errorCallback(error) : undefined ) return page.getAnnotations().then( annotations => annotationsLayer.setAnnotations(annotations), error => typeof self.errorCallback === 'function' ? self.errorCallback(error) : undefined ) }) .catch(function (error) { // page render failed if (error.name === 'RenderingCancelledException') { // do nothing when cancelled } else { return typeof self.errorCallback === 'function' ? self.errorCallback(error) : undefined } }) return result } destroy() { this.shuttingDown = true this.resetState() return this.pdfjs.promise.then(function (document) { document.cleanup() return document.destroy() }) } } PDFRenderer.initClass() return PDFRenderer })()) } ) function __guard__(value, transform) { return typeof value !== 'undefined' && value !== null ? transform(value) : undefined } function __guardMethod__(obj, methodName, transform) { if ( typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function' ) { return transform(obj, methodName) } else { return undefined } }