2014-12-01 05:42:47 -05:00
|
|
|
define [
|
|
|
|
"base"
|
|
|
|
], (App) ->
|
|
|
|
# App = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']
|
|
|
|
|
2014-12-08 09:46:59 -05:00
|
|
|
App.factory 'PDFRenderer', ['$q', '$timeout', 'pdfAnnotations', 'pdfTextLayer', 'pdfSpinner', ($q, $timeout, pdfAnnotations, pdfTextLayer, pdfSpinner) ->
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
class PDFRenderer
|
2014-12-05 07:14:40 -05:00
|
|
|
JOB_QUEUE_INTERVAL: 25
|
2015-01-13 11:38:40 -05:00
|
|
|
PAGE_LOAD_TIMEOUT: 60*1000
|
|
|
|
PAGE_RENDER_TIMEOUT: 60*1000
|
2016-07-11 10:01:24 -04:00
|
|
|
INDICATOR_DELAY1: 100 # time to delay before showing the indicator
|
|
|
|
INDICATOR_DELAY2: 250 # time until the indicator starts animating
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
constructor: (@url, @options) ->
|
2015-01-13 11:39:49 -05:00
|
|
|
# PDFJS.disableFontFace = true # avoids repaints, uses worker more
|
2016-06-10 11:06:02 -04:00
|
|
|
if @options.disableAutoFetch
|
|
|
|
PDFJS.disableAutoFetch = true # prevent loading whole file
|
2014-12-09 08:41:49 -05:00
|
|
|
# PDFJS.disableStream
|
|
|
|
# PDFJS.disableRange
|
2014-12-01 05:42:47 -05:00
|
|
|
@scale = @options.scale || 1
|
2016-05-10 10:53:37 -04:00
|
|
|
@pdfjs = PDFJS.getDocument {url: @url, rangeChunkSize: 2*65536}
|
2016-02-03 10:07:06 -05:00
|
|
|
@pdfjs.onProgress = @options.progressCallback
|
2014-12-09 08:46:44 -05:00
|
|
|
@document = $q.when(@pdfjs)
|
2014-12-01 05:42:47 -05:00
|
|
|
@navigateFn = @options.navigateFn
|
2014-12-08 09:46:59 -05:00
|
|
|
@spinner = new pdfSpinner
|
2014-12-01 05:42:47 -05:00
|
|
|
@resetState()
|
2015-01-13 11:28:24 -05:00
|
|
|
@document.then (pdfDocument) =>
|
|
|
|
pdfDocument.getDownloadInfo().then () =>
|
|
|
|
@options.loadedCallback()
|
2015-01-12 11:45:24 -05:00
|
|
|
@errorCallback = @options.errorCallback
|
2015-01-20 09:28:14 -05:00
|
|
|
@pageSizeChangeCallback = @options.pageSizeChangeCallback
|
2016-02-03 10:07:06 -05:00
|
|
|
@pdfjs.promise.catch (exception) =>
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'ERROR in get document', exception
|
|
|
|
@errorCallback(exception)
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
resetState: () ->
|
2016-07-11 10:01:58 -04:00
|
|
|
#console.log 'called reset state'
|
2014-12-01 05:42:47 -05:00
|
|
|
@renderQueue = []
|
2016-07-11 10:01:58 -04:00
|
|
|
clearTimeout @queueTimer if @queueTimer?
|
|
|
|
# clear any existing timers, render tasks
|
|
|
|
for timer in @spinTimer or []
|
|
|
|
clearTimeout timer
|
|
|
|
for page in @pageState or []
|
|
|
|
page?.loadTask?.cancel()
|
|
|
|
page?.renderTask?.cancel()
|
|
|
|
# initialise/reset the state
|
|
|
|
@pageState = []
|
|
|
|
@spinTimer = [] # timers for starting the spinners (to avoid jitter)
|
|
|
|
@spinTimerDone = [] # array of pages where the spinner has activated
|
2014-12-01 05:42:47 -05:00
|
|
|
@jobs = 0
|
|
|
|
|
|
|
|
getNumPages: () ->
|
|
|
|
@document.then (pdfDocument) ->
|
|
|
|
pdfDocument.numPages
|
|
|
|
|
|
|
|
getPage: (pageNum) ->
|
2015-01-12 11:45:24 -05:00
|
|
|
@document.then (pdfDocument) ->
|
|
|
|
# console.log 'got pdf document, now getting Page', pageNum
|
2014-12-01 05:42:47 -05:00
|
|
|
pdfDocument.getPage(pageNum)
|
|
|
|
|
|
|
|
getPdfViewport: (pageNum, scale) ->
|
|
|
|
scale ?= @scale
|
2015-01-26 08:05:21 -05:00
|
|
|
@document.then (pdfDocument) =>
|
2014-12-01 05:42:47 -05:00
|
|
|
pdfDocument.getPage(pageNum).then (page) ->
|
|
|
|
viewport = page.getViewport scale
|
2015-01-26 08:05:21 -05:00
|
|
|
, (error) =>
|
|
|
|
@errorCallback?(error)
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
getDestinations: () ->
|
|
|
|
@document.then (pdfDocument) ->
|
|
|
|
pdfDocument.getDestinations()
|
|
|
|
|
2014-12-09 08:43:25 -05:00
|
|
|
getDestination: (dest) ->
|
2015-03-31 09:54:36 -04:00
|
|
|
@document.then (pdfDocument) ->
|
|
|
|
pdfDocument.getDestination(dest)
|
2015-01-26 08:05:21 -05:00
|
|
|
, (error) =>
|
|
|
|
@errorCallback?(error)
|
2014-12-09 08:43:25 -05:00
|
|
|
|
2014-12-01 05:42:47 -05:00
|
|
|
getPageIndex: (ref) ->
|
2015-01-26 08:05:21 -05:00
|
|
|
@document.then (pdfDocument) =>
|
2014-12-01 05:42:47 -05:00
|
|
|
pdfDocument.getPageIndex(ref).then (idx) ->
|
|
|
|
idx
|
2015-01-26 08:05:21 -05:00
|
|
|
, (error) =>
|
|
|
|
@errorCallback?(error)
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
getScale: () ->
|
|
|
|
@scale
|
|
|
|
|
|
|
|
setScale: (@scale) ->
|
|
|
|
@resetState()
|
|
|
|
|
2014-12-05 07:14:40 -05:00
|
|
|
triggerRenderQueue: (interval = @JOB_QUEUE_INTERVAL) ->
|
2016-07-05 07:11:47 -04:00
|
|
|
if @queueTimer?
|
|
|
|
clearTimeout @queueTimer
|
2015-01-26 07:13:56 -05:00
|
|
|
@queueTimer = setTimeout () =>
|
|
|
|
@queueTimer = null
|
2014-12-01 05:42:47 -05:00
|
|
|
@processRenderQueue()
|
2014-12-05 07:14:40 -05:00
|
|
|
, interval
|
2014-12-01 05:42:47 -05:00
|
|
|
|
2016-07-11 10:01:58 -04:00
|
|
|
removeCompletedJob: (pagenum) ->
|
2014-12-01 05:42:47 -05:00
|
|
|
@jobs = @jobs - 1
|
2014-12-05 07:14:40 -05:00
|
|
|
@triggerRenderQueue(0)
|
2014-12-01 05:42:47 -05:00
|
|
|
|
2015-01-19 11:55:44 -05:00
|
|
|
renderPages: (pages) ->
|
|
|
|
return if @shuttingDown
|
|
|
|
@renderQueue = for page in pages
|
|
|
|
{
|
|
|
|
'element': page.elementChildren
|
|
|
|
'pagenum': page.pageNum
|
|
|
|
}
|
|
|
|
@triggerRenderQueue()
|
|
|
|
|
2015-01-20 16:33:49 -05:00
|
|
|
renderPage: (page) ->
|
|
|
|
return if @shuttingDown
|
|
|
|
current = {
|
|
|
|
'element': page.elementChildren
|
|
|
|
'pagenum': page.pageNum
|
|
|
|
}
|
|
|
|
@renderQueue.push current
|
|
|
|
@processRenderQueue()
|
|
|
|
|
2016-07-11 10:01:24 -04:00
|
|
|
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
|
|
|
|
@queuedPages = []
|
|
|
|
@queuedPages[page.pagenum] = true for page in @renderQueue
|
|
|
|
# clear any unfinished spinner timers on pages that aren't in the queue any more
|
|
|
|
for pagenum of @spinTimer when not @queuedPages[pagenum]
|
|
|
|
clearTimeout @spinTimer[pagenum]
|
|
|
|
delete @spinTimer[pagenum]
|
|
|
|
# add indicators for any new pages in the current queue
|
|
|
|
for page in @renderQueue when not @spinTimer[page.pagenum] and not @spinTimerDone[page.pagenum]
|
|
|
|
@startIndicator page
|
|
|
|
|
|
|
|
startIndicator: (page) ->
|
|
|
|
[canvas, pagenum] = @getPageDetails page
|
|
|
|
canvas.addClass('pdfng-loading')
|
|
|
|
@spinTimer[pagenum] = setTimeout () =>
|
|
|
|
for queuedPage in @renderQueue
|
|
|
|
if pagenum == queuedPage.pagenum
|
|
|
|
@spinner.add(canvas, {static:true})
|
|
|
|
@spinTimerDone[pagenum] = true
|
|
|
|
break
|
|
|
|
delete @spinTimer[pagenum]
|
|
|
|
, @INDICATOR_DELAY1
|
|
|
|
|
|
|
|
updateIndicator: (page) ->
|
|
|
|
[canvas, pagenum] = @getPageDetails page
|
|
|
|
# did the spinner insert itself already?
|
|
|
|
if @spinTimerDone[pagenum]
|
|
|
|
@spinTimer[pagenum] = setTimeout () =>
|
|
|
|
@spinner.start(canvas)
|
|
|
|
delete @spinTimer[pagenum]
|
|
|
|
, @INDICATOR_DELAY2
|
|
|
|
else
|
|
|
|
# stop the existing spin timer
|
|
|
|
clearTimeout @spinTimer[pagenum]
|
|
|
|
# start a new one which will also start spinning
|
|
|
|
@spinTimer[pagenum] = setTimeout () =>
|
|
|
|
@spinner.add(canvas, {static:true})
|
|
|
|
@spinTimerDone[pagenum] = true
|
|
|
|
@spinTimer[pagenum] = setTimeout () =>
|
|
|
|
@spinner.start(canvas)
|
|
|
|
delete @spinTimer[pagenum]
|
|
|
|
, @INDICATOR_DELAY2
|
|
|
|
, @INDICATOR_DELAY1
|
|
|
|
|
|
|
|
clearIndicator: (page) ->
|
|
|
|
[canvas, pagenum] = @getPageDetails page
|
|
|
|
@spinner.stop(canvas)
|
|
|
|
clearTimeout @spinTimer[pagenum]
|
|
|
|
delete @spinTimer[pagenum]
|
|
|
|
@spinTimerDone[pagenum] = true
|
|
|
|
|
|
|
|
# handle the queue of pages to be rendered
|
|
|
|
|
2014-12-01 05:42:47 -05:00
|
|
|
processRenderQueue: () ->
|
2015-01-12 11:45:24 -05:00
|
|
|
return if @shuttingDown
|
2016-07-11 10:01:24 -04:00
|
|
|
# mark all pages in the queue as loading
|
|
|
|
@startIndicators()
|
|
|
|
# bail out if there is already a render job running
|
2014-12-01 05:42:47 -05:00
|
|
|
return if @jobs > 0
|
2016-07-11 10:01:24 -04:00
|
|
|
# take the first page in the queue
|
|
|
|
page = @renderQueue.shift()
|
|
|
|
# check if it is in action already
|
|
|
|
while page? and @pageState[page.pagenum]?
|
|
|
|
page = @renderQueue.shift()
|
|
|
|
return unless page?
|
|
|
|
[element, pagenum] = [page.element, page.pagenum]
|
2014-12-01 05:42:47 -05:00
|
|
|
@jobs = @jobs + 1
|
|
|
|
|
2016-07-11 10:01:24 -04:00
|
|
|
# update the spinner to make it spinning (signifies loading has begun)
|
|
|
|
@updateIndicator page
|
2014-12-01 05:42:47 -05:00
|
|
|
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'started page load', pagenum
|
|
|
|
|
|
|
|
timedOut = false
|
|
|
|
timer = $timeout () =>
|
2016-07-11 10:01:58 -04:00
|
|
|
return if loadTask.cancelled # return from cancelled page load
|
2015-02-09 06:18:46 -05:00
|
|
|
Raven?.captureMessage?('pdfng page load timed out after ' + @PAGE_LOAD_TIMEOUT + 'ms (1% sample)') if Math.random() < 0.01
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'page load timed out', pagenum
|
|
|
|
timedOut = true
|
2016-07-11 10:01:24 -04:00
|
|
|
@clearIndicator page
|
2015-01-12 11:45:24 -05:00
|
|
|
# @jobs = @jobs - 1
|
|
|
|
# @triggerRenderQueue(0)
|
2015-01-26 08:05:21 -05:00
|
|
|
@errorCallback?('timeout')
|
2015-01-12 12:02:51 -05:00
|
|
|
, @PAGE_LOAD_TIMEOUT
|
2014-12-01 05:42:47 -05:00
|
|
|
|
2016-07-11 10:01:58 -04:00
|
|
|
loadTask = @getPage(pagenum)
|
|
|
|
|
|
|
|
loadTask.cancel = () ->
|
|
|
|
@cancelled = true
|
|
|
|
|
|
|
|
@pageState[pagenum] = pageState = { loadTask: loadTask }
|
2015-01-12 11:45:24 -05:00
|
|
|
|
2016-07-11 10:01:58 -04:00
|
|
|
loadTask.then (pageObject) =>
|
|
|
|
#console.log 'in page load success', pagenum
|
2015-01-12 11:45:24 -05:00
|
|
|
$timeout.cancel(timer)
|
2016-07-11 10:01:58 -04:00
|
|
|
return if loadTask.cancelled # return from cancelled page load
|
|
|
|
pageState.renderTask = @doRender element, pagenum, pageObject
|
|
|
|
pageState.renderTask.then () =>
|
|
|
|
#console.log 'render task success', pagenum
|
|
|
|
@clearIndicator page
|
|
|
|
pageState.complete = true
|
|
|
|
delete pageState.renderTask
|
|
|
|
@removeCompletedJob pagenum
|
2014-12-09 08:46:44 -05:00
|
|
|
, () =>
|
2016-07-11 10:01:58 -04:00
|
|
|
# display an error icon
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'render task failed', pagenum
|
2016-07-11 10:01:58 -04:00
|
|
|
pageState.complete = false
|
|
|
|
delete pageState.renderTask
|
2014-12-09 08:46:44 -05:00
|
|
|
# rejected
|
2016-07-11 10:01:58 -04:00
|
|
|
@removeCompletedJob pagenum
|
2015-01-12 11:45:24 -05:00
|
|
|
.catch (error) ->
|
|
|
|
# console.log 'in page load error', pagenum, 'timedOut=', timedOut
|
|
|
|
$timeout.cancel(timer)
|
2016-07-11 10:01:24 -04:00
|
|
|
@clearIndicator page
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'ERROR', error
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
doRender: (element, pagenum, page) ->
|
|
|
|
self = this
|
|
|
|
scale = @scale
|
|
|
|
|
|
|
|
if (not scale?)
|
2014-12-02 06:42:57 -05:00
|
|
|
# console.log 'scale is undefined, returning'
|
2014-12-01 05:42:47 -05:00
|
|
|
return
|
|
|
|
|
2014-12-08 09:46:59 -05:00
|
|
|
canvas = $('<canvas class="pdf-canvas pdfng-rendering"></canvas>')
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
viewport = page.getViewport (scale)
|
|
|
|
|
|
|
|
devicePixelRatio = window.devicePixelRatio || 1
|
|
|
|
|
|
|
|
ctx = canvas[0].getContext '2d'
|
|
|
|
backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
|
|
|
|
ctx.mozBackingStorePixelRatio ||
|
|
|
|
ctx.msBackingStorePixelRatio ||
|
|
|
|
ctx.oBackingStorePixelRatio ||
|
|
|
|
ctx.backingStorePixelRatio || 1
|
|
|
|
pixelRatio = devicePixelRatio / backingStoreRatio
|
|
|
|
|
|
|
|
scaledWidth = (Math.floor(viewport.width) * pixelRatio) | 0
|
|
|
|
scaledHeight = (Math.floor(viewport.height) * pixelRatio) | 0
|
|
|
|
|
|
|
|
newWidth = Math.floor(viewport.width)
|
|
|
|
newHeight = Math.floor(viewport.height)
|
|
|
|
|
|
|
|
canvas[0].height = scaledHeight
|
|
|
|
canvas[0].width = scaledWidth
|
|
|
|
|
|
|
|
canvas.height(newHeight + 'px')
|
|
|
|
canvas.width(newWidth + 'px')
|
|
|
|
|
2015-01-20 09:28:14 -05:00
|
|
|
oldHeight = element.canvas.height()
|
|
|
|
oldWidth = element.canvas.width()
|
|
|
|
if newHeight != oldHeight or newWidth != oldWidth
|
|
|
|
element.canvas.height(newHeight + 'px')
|
|
|
|
element.canvas.width(newWidth + 'px')
|
|
|
|
element.container.height(newHeight + 'px')
|
|
|
|
element.container.width(newWidth + 'px')
|
|
|
|
@pageSizeChangeCallback?(pagenum, newHeight - oldHeight)
|
2014-12-01 05:42:47 -05:00
|
|
|
|
|
|
|
textLayer = new pdfTextLayer({
|
|
|
|
textLayerDiv: element.text[0]
|
|
|
|
viewport: viewport
|
|
|
|
})
|
|
|
|
|
|
|
|
annotationsLayer = new pdfAnnotations({
|
|
|
|
annotations: element.annotations[0]
|
|
|
|
viewport: viewport
|
|
|
|
navigateFn: @navigateFn
|
|
|
|
})
|
2014-12-02 06:03:07 -05:00
|
|
|
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'staring page render', pagenum
|
|
|
|
|
2014-12-09 08:46:44 -05:00
|
|
|
result = page.render {
|
2014-12-01 05:42:47 -05:00
|
|
|
canvasContext: ctx
|
|
|
|
viewport: viewport
|
2016-02-03 10:07:06 -05:00
|
|
|
transform: [pixelRatio, 0, 0, pixelRatio, 0, 0]
|
2014-12-01 05:42:47 -05:00
|
|
|
}
|
2014-12-09 08:46:44 -05:00
|
|
|
|
2015-01-12 11:45:24 -05:00
|
|
|
timedOut = false
|
|
|
|
|
2015-01-12 12:02:51 -05:00
|
|
|
timer = $timeout () =>
|
2015-02-09 06:18:46 -05:00
|
|
|
Raven?.captureMessage?('pdfng page render timed out after ' + @PAGE_RENDER_TIMEOUT + 'ms (1% sample)') if Math.random() < 0.01
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'page render timed out', pagenum
|
|
|
|
timedOut = true
|
|
|
|
result.cancel()
|
2015-01-12 12:02:51 -05:00
|
|
|
, @PAGE_RENDER_TIMEOUT
|
2015-01-12 11:45:24 -05:00
|
|
|
|
2014-12-09 08:46:44 -05:00
|
|
|
result.then () ->
|
2015-01-12 11:45:24 -05:00
|
|
|
# console.log 'page rendered', pagenum
|
2016-07-05 09:31:55 -04:00
|
|
|
element.canvas.replaceWith(canvas)
|
2015-01-12 11:45:24 -05:00
|
|
|
$timeout.cancel(timer)
|
2014-12-08 09:46:59 -05:00
|
|
|
canvas.removeClass('pdfng-rendering')
|
2014-12-02 06:03:07 -05:00
|
|
|
page.getTextContent().then (textContent) ->
|
|
|
|
textLayer.setTextContent textContent
|
2015-01-12 11:45:24 -05:00
|
|
|
, (error) ->
|
2015-01-26 08:05:21 -05:00
|
|
|
self.errorCallback?(error)
|
2014-12-02 06:03:07 -05:00
|
|
|
page.getAnnotations().then (annotations) ->
|
|
|
|
annotationsLayer.setAnnotations annotations
|
2015-01-12 11:45:24 -05:00
|
|
|
, (error) ->
|
2015-01-26 08:05:21 -05:00
|
|
|
self.errorCallback?(error)
|
2015-01-12 11:45:24 -05:00
|
|
|
.catch (error) ->
|
|
|
|
# console.log 'page render failed', pagenum, error
|
|
|
|
$timeout.cancel(timer)
|
|
|
|
if timedOut
|
|
|
|
# console.log 'calling ERROR callback - was timeout'
|
|
|
|
self.errorCallback?('timeout')
|
|
|
|
else if error != 'cancelled'
|
|
|
|
# console.log 'calling ERROR callback'
|
|
|
|
self.errorCallback?(error)
|
2014-12-01 05:42:47 -05:00
|
|
|
|
2014-12-09 08:46:44 -05:00
|
|
|
return result
|
|
|
|
|
2016-07-11 10:01:58 -04:00
|
|
|
stop: () ->
|
|
|
|
|
2014-12-09 08:46:44 -05:00
|
|
|
destroy: () ->
|
2014-12-09 09:39:58 -05:00
|
|
|
# console.log 'in pdf renderer destroy', @renderQueue
|
2014-12-09 08:46:44 -05:00
|
|
|
@shuttingDown = true
|
2016-07-11 10:01:58 -04:00
|
|
|
@resetState()
|
2014-12-09 08:46:44 -05:00
|
|
|
@pdfjs.then (document) ->
|
|
|
|
document.cleanup()
|
|
|
|
document.destroy()
|
|
|
|
|
2014-12-01 05:42:47 -05:00
|
|
|
]
|