Merge pull request #277 from sharelatex/prerender

Improve rendering of pdf pages
This commit is contained in:
Brian Gough 2016-07-12 09:02:46 +01:00 committed by GitHub
commit ef00aa646d
5 changed files with 139 additions and 126 deletions

View file

@ -27,15 +27,6 @@ define [
"dblClickCallback": "=" "dblClickCallback": "="
} }
link: (scope, element, attrs) -> link: (scope, element, attrs) ->
# pdfListView = new PDFListView element.find(".pdfjs-viewer")[0],
# textLayerBuilder: TextLayerBuilder
# annotationsLayerBuilder: AnnotationsLayerBuilder
# highlightsLayerBuilder: HighlightsLayerBuilder
# ondblclick: (e) -> onDoubleClick(e)
# # logLevel: PDFListView.Logger.DEBUG
# pdfListView.listView.pageWidthOffset = 20
# pdfListView.listView.pageHeightOffset = 20
scope.loading = false scope.loading = false
scope.pleaseJumpTo = null scope.pleaseJumpTo = null
scope.scale = null scope.scale = null
@ -56,8 +47,6 @@ define [
"top": +position.offset.top "top": +position.offset.top
"left": +position.offset.left "left": +position.offset.left
#scope.position = pdfListView.getPdfPosition(true)
scope.$on "$destroy", () => scope.$on "$destroy", () =>
localStorage "pdf.scale", scope.scale localStorage "pdf.scale", scope.scale
localStorage "pdf.position.#{attrs.key}", scope.position localStorage "pdf.position.#{attrs.key}", scope.position
@ -82,56 +71,18 @@ define [
scope.$watch "pdfSrc", (url) -> scope.$watch "pdfSrc", (url) ->
if url if url
scope.loading = true scope.loading = true
# console.log 'pdfSrc =', url scope.loaded = false
scope.progress = 1
initializePosition() initializePosition()
flashControls() flashControls()
# pdfListView
# .loadPdf(url, onProgress)
# .then () ->
# scope.$apply () ->
# scope.loading = false
# delete scope.progress
# initializePosition()
# flashControls()
scope.$on "loaded", () -> scope.$on "loaded", () ->
scope.loaded = true
scope.progress = 100 scope.progress = 100
scope.$apply()
$timeout () -> $timeout () ->
scope.loading = false scope.loading = false
delete scope.progress delete scope.progress
, 250 , 500
#scope.$watch "highlights", (areas) ->
# console.log 'got HIGHLIGHTS in pdfJS', areas
# return if !areas?
# highlights = for area in areas or []
# {
# page: area.page
# highlight:
# left: area.h
# top: area.v
# height: area.height
# width: area.width
# }
# if highlights.length > 0
# first = highlights[0]
# position = {
# page: first.page
# offset:
# left: first.highlight.left
# top: first.highlight.top - 80
# }
# console.log 'position is', position, 'in highlights'
# scope.pleaseJumpTo = position
# pdfListView.clearHighlights()
# pdfListView.setHighlights(highlights, true)
# setTimeout () =>
# pdfListView.clearHighlights()
# , 1000
scope.fitToHeight = () -> scope.fitToHeight = () ->
scale = angular.copy (scope.scale) scale = angular.copy (scope.scale)
@ -159,10 +110,10 @@ define [
for event in attrs.resizeOn.split(",") for event in attrs.resizeOn.split(",")
scope.$on event, (e) -> scope.$on event, (e) ->
#console.log 'got a resize event', event, e #console.log 'got a resize event', event, e
#
scope.$on 'progress', (event, progress) -> scope.$on 'progress', (event, progress) ->
scope.$apply () -> scope.$apply () ->
return if scope.loaded
scope.progress = Math.floor(progress.loaded/progress.total*100) scope.progress = Math.floor(progress.loaded/progress.total*100)
scope.progress = 100 if scope.progress > 100 scope.progress = 100 if scope.progress > 100
scope.progress = 0 if scope.progress < 0 scope.progress = 0 if scope.progress < 0

View file

@ -3,7 +3,7 @@ define [
], (App) -> ], (App) ->
# App = angular.module 'pdfPage', ['pdfHighlights'] # App = angular.module 'pdfPage', ['pdfHighlights']
App.directive 'pdfPage', ['$timeout', 'pdfHighlights', ($timeout, pdfHighlights) -> App.directive 'pdfPage', ['$timeout', 'pdfHighlights', 'pdfSpinner', ($timeout, pdfHighlights, pdfSpinner) ->
{ {
require: '^pdfViewer', require: '^pdfViewer',
template: ''' template: '''
@ -82,7 +82,7 @@ define [
if scope.timeoutHandler if scope.timeoutHandler
$timeout.cancel(scope.timeoutHandler) $timeout.cancel(scope.timeoutHandler)
highlightsLayer.clearHighlights() highlightsLayer.clearHighlights()
scope.timeoutHandler scope.timeoutHandler = null
# console.log 'got highlight watch in pdfPage', scope.page # console.log 'got highlight watch in pdfPage', scope.page
pageHighlights = (h for h in highlights when h.page == scope.page.pageNum) pageHighlights = (h for h in highlights when h.page == scope.page.pageNum)

View file

@ -9,6 +9,8 @@ define [
JOB_QUEUE_INTERVAL: 25 JOB_QUEUE_INTERVAL: 25
PAGE_LOAD_TIMEOUT: 60*1000 PAGE_LOAD_TIMEOUT: 60*1000
PAGE_RENDER_TIMEOUT: 60*1000 PAGE_RENDER_TIMEOUT: 60*1000
INDICATOR_DELAY1: 100 # time to delay before showing the indicator
INDICATOR_DELAY2: 250 # time until the indicator starts animating
constructor: (@url, @options) -> constructor: (@url, @options) ->
# PDFJS.disableFontFace = true # avoids repaints, uses worker more # PDFJS.disableFontFace = true # avoids repaints, uses worker more
@ -29,15 +31,22 @@ define [
@errorCallback = @options.errorCallback @errorCallback = @options.errorCallback
@pageSizeChangeCallback = @options.pageSizeChangeCallback @pageSizeChangeCallback = @options.pageSizeChangeCallback
@pdfjs.promise.catch (exception) => @pdfjs.promise.catch (exception) =>
# console.log 'ERROR in get document', exception # error getting document
@errorCallback(exception) @errorCallback(exception)
resetState: () -> resetState: () ->
@complete = []
@timeout = []
@pageLoad = []
@renderTask = []
@renderQueue = [] @renderQueue = []
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
@jobs = 0 @jobs = 0
getNumPages: () -> getNumPages: () ->
@ -46,7 +55,6 @@ define [
getPage: (pageNum) -> getPage: (pageNum) ->
@document.then (pdfDocument) -> @document.then (pdfDocument) ->
# console.log 'got pdf document, now getting Page', pageNum
pdfDocument.getPage(pageNum) pdfDocument.getPage(pageNum)
getPdfViewport: (pageNum, scale) -> getPdfViewport: (pageNum, scale) ->
@ -81,14 +89,14 @@ define [
@resetState() @resetState()
triggerRenderQueue: (interval = @JOB_QUEUE_INTERVAL) -> triggerRenderQueue: (interval = @JOB_QUEUE_INTERVAL) ->
if @queueTimer?
clearTimeout @queueTimer
@queueTimer = setTimeout () => @queueTimer = setTimeout () =>
@queueTimer = null @queueTimer = null
@processRenderQueue() @processRenderQueue()
, interval , interval
removeCompletedJob: (taskRef, pagenum) -> removeCompletedJob: (pagenum) ->
# may need to clean up deferred object here
delete taskRef[pagenum]
@jobs = @jobs - 1 @jobs = @jobs - 1
@triggerRenderQueue(0) @triggerRenderQueue(0)
@ -110,69 +118,129 @@ define [
@renderQueue.push current @renderQueue.push current
@processRenderQueue() @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
@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
processRenderQueue: () -> processRenderQueue: () ->
return if @shuttingDown return if @shuttingDown
# mark all pages in the queue as loading
@startIndicators()
# bail out if there is already a render job running
return if @jobs > 0 return if @jobs > 0
current = @renderQueue.shift() # take the first page in the queue
return unless current? page = @renderQueue.shift()
[element, pagenum] = [current.element, current.pagenum] # check if it is in action already
# if task is underway or complete, go to the next entry in the while page? and @pageState[page.pagenum]?
# render queue page = @renderQueue.shift()
# console.log 'processing renderq', pagenum, @renderTask[pagenum], @complete[pagenum] return unless page?
if @pageLoad[pagenum] or @renderTask[pagenum] or @complete[pagenum] [element, pagenum] = [page.element, page.pagenum]
@processRenderQueue()
return
@jobs = @jobs + 1 @jobs = @jobs + 1
element.canvas.addClass('pdfng-loading') # update the spinner to make it spinning (signifies loading has begun)
spinTimer = setTimeout () => @updateIndicator page
@spinner.add(element.canvas)
, 100
completeRef = @complete
renderTaskRef = @renderTask
# console.log 'started page load', pagenum
timedOut = false timedOut = false
timer = $timeout () => timer = $timeout () => # page load timed out
return if loadTask.cancelled # return from cancelled page load
Raven?.captureMessage?('pdfng page load timed out after ' + @PAGE_LOAD_TIMEOUT + 'ms (1% sample)') if Math.random() < 0.01 Raven?.captureMessage?('pdfng page load timed out after ' + @PAGE_LOAD_TIMEOUT + 'ms (1% sample)') if Math.random() < 0.01
# console.log 'page load timed out', pagenum
timedOut = true timedOut = true
clearTimeout(spinTimer) @clearIndicator page
@spinner.stop(element.canvas)
# @jobs = @jobs - 1 # @jobs = @jobs - 1
# @triggerRenderQueue(0) # @triggerRenderQueue(0)
@errorCallback?('timeout') @errorCallback?('timeout')
, @PAGE_LOAD_TIMEOUT , @PAGE_LOAD_TIMEOUT
@pageLoad[pagenum] = @getPage(pagenum) loadTask = @getPage(pagenum)
@pageLoad[pagenum].then (pageObject) => loadTask.cancel = () ->
# console.log 'in page load success', pagenum @cancelled = true
@pageState[pagenum] = pageState = { loadTask: loadTask }
loadTask.then (pageObject) =>
# page load success
$timeout.cancel(timer) $timeout.cancel(timer)
clearTimeout(spinTimer) return if loadTask.cancelled # return from cancelled page load
@renderTask[pagenum] = @doRender element, pagenum, pageObject pageState.renderTask = @doRender element, pagenum, pageObject
@renderTask[pagenum].then () => pageState.renderTask.then () =>
# complete # render task success
# console.log 'render task success', pagenum @clearIndicator page
completeRef[pagenum] = true pageState.complete = true
@removeCompletedJob renderTaskRef, pagenum delete pageState.renderTask
@removeCompletedJob pagenum
, () => , () =>
# console.log 'render task failed', pagenum # render task failed
# rejected # could display an error icon
@removeCompletedJob renderTaskRef, pagenum pageState.complete = false
delete pageState.renderTask
@removeCompletedJob pagenum
.catch (error) -> .catch (error) ->
# console.log 'in page load error', pagenum, 'timedOut=', timedOut # page load error
$timeout.cancel(timer) $timeout.cancel(timer)
clearTimeout(spinTimer) @clearIndicator page
# console.log 'ERROR', error
doRender: (element, pagenum, page) -> doRender: (element, pagenum, page) ->
self = this self = this
scale = @scale scale = @scale
if (not scale?) if (not scale?)
# console.log 'scale is undefined, returning' # scale is undefined, returning
return return
canvas = $('<canvas class="pdf-canvas pdfng-rendering"></canvas>') canvas = $('<canvas class="pdf-canvas pdfng-rendering"></canvas>')
@ -221,10 +289,6 @@ define [
navigateFn: @navigateFn navigateFn: @navigateFn
}) })
element.canvas.replaceWith(canvas)
# console.log 'staring page render', pagenum
result = page.render { result = page.render {
canvasContext: ctx canvasContext: ctx
viewport: viewport viewport: viewport
@ -234,14 +298,15 @@ define [
timedOut = false timedOut = false
timer = $timeout () => timer = $timeout () =>
# page render timed out
Raven?.captureMessage?('pdfng page render timed out after ' + @PAGE_RENDER_TIMEOUT + 'ms (1% sample)') if Math.random() < 0.01 Raven?.captureMessage?('pdfng page render timed out after ' + @PAGE_RENDER_TIMEOUT + 'ms (1% sample)') if Math.random() < 0.01
# console.log 'page render timed out', pagenum
timedOut = true timedOut = true
result.cancel() result.cancel()
, @PAGE_RENDER_TIMEOUT , @PAGE_RENDER_TIMEOUT
result.then () -> result.then () ->
# console.log 'page rendered', pagenum # page render success
element.canvas.replaceWith(canvas)
$timeout.cancel(timer) $timeout.cancel(timer)
canvas.removeClass('pdfng-rendering') canvas.removeClass('pdfng-rendering')
page.getTextContent().then (textContent) -> page.getTextContent().then (textContent) ->
@ -253,24 +318,20 @@ define [
, (error) -> , (error) ->
self.errorCallback?(error) self.errorCallback?(error)
.catch (error) -> .catch (error) ->
# console.log 'page render failed', pagenum, error # page render failed
$timeout.cancel(timer) $timeout.cancel(timer)
if timedOut if timedOut
# console.log 'calling ERROR callback - was timeout'
self.errorCallback?('timeout') self.errorCallback?('timeout')
else if error != 'cancelled' else if error is 'cancelled'
# console.log 'calling ERROR callback' return # do nothing when cancelled
else
self.errorCallback?(error) self.errorCallback?(error)
return result return result
destroy: () -> destroy: () ->
# console.log 'in pdf renderer destroy', @renderQueue
@shuttingDown = true @shuttingDown = true
clearTimeout @queueTimer if @queueTimer? @resetState()
@renderQueue = []
for task in @renderTask
task.cancel() if task?
@pdfjs.then (document) -> @pdfjs.then (document) ->
document.cleanup() document.cleanup()
document.destroy() document.destroy()

View file

@ -8,14 +8,15 @@ define [
constructor: () -> constructor: () ->
# handler for spinners # handler for spinners
add: (element) -> add: (element, options) ->
h = element.height()
w = element.width()
size = 64 size = 64
spinner = $('<div class="pdfng-spinner" style="position: absolute; top: 50%; left:50%; transform: translateX(-50%) translateY(-50%);"><i class="fa fa-spinner fa-spin" style="color: #999"></i></div>') spinner = $('<div class="pdfng-spinner" style="position: absolute; top: 50%; left:50%; transform: translateX(-50%) translateY(-50%);"><i class="fa fa-spinner' + (if options?.static then '' else ' fa-spin') + '" style="color: #999"></i></div>')
spinner.css({'font-size' : size + 'px'}) spinner.css({'font-size' : size + 'px'})
element.append(spinner) element.append(spinner)
start: (element) ->
element.find('.fa-spinner').addClass('fa-spin')
stop: (element) -> stop: (element) ->
element.find('.fa-spinner').removeClass('fa-spin') element.find('.fa-spinner').removeClass('fa-spin')

View file

@ -41,7 +41,6 @@ define [
$scope.navigateTo = ref $scope.navigateTo = ref
$scope.$apply() $scope.$apply()
progressCallback: (progress) -> progressCallback: (progress) ->
return if onDemandLoading is true # don't show progress for on-demand page loading
$scope.$emit 'progress', progress $scope.$emit 'progress', progress
loadedCallback: () -> loadedCallback: () ->
$scope.$emit 'loaded' $scope.$emit 'loaded'
@ -65,6 +64,7 @@ define [
result.pdfViewport.width result.pdfViewport.width
] ]
# console.log 'resolved q.all, page size is', result # console.log 'resolved q.all, page size is', result
$scope.$emit 'loaded'
$scope.numPages = result.numPages $scope.numPages = result.numPages
.catch (error) -> .catch (error) ->
$scope.$emit 'pdf:error', error $scope.$emit 'pdf:error', error
@ -257,8 +257,8 @@ define [
# console.log 'layoutReady was resolved' # console.log 'layoutReady was resolved'
renderVisiblePages = () -> renderVisiblePages = () ->
pages = getVisiblePages() visiblePages = getVisiblePages()
# pages = getExtraPages visiblePages pages = getExtraPages visiblePages
scope.document.renderPages(pages) scope.document.renderPages(pages)
getVisiblePages = () -> getVisiblePages = () ->