diff --git a/services/web/.gitignore b/services/web/.gitignore
index f9bcc5cd9b..86ff84610b 100644
--- a/services/web/.gitignore
+++ b/services/web/.gitignore
@@ -46,10 +46,10 @@ TpdsWorker.js
BackgroundJobsWorker.js
public/js/history/versiondetail.js
-!public/js/libs/*
+!public/js/libs/
public/js/*
!public/js/ace/*
-!public/js/libs/*
+!public/js/libs/
public/js/editor.js
public/js/home.js
public/js/forms.js
diff --git a/services/web/Gruntfile.coffee b/services/web/Gruntfile.coffee
index de6807ba38..01af8c73d9 100644
--- a/services/web/Gruntfile.coffee
+++ b/services/web/Gruntfile.coffee
@@ -98,7 +98,7 @@ module.exports = (grunt) ->
exclude: ["libs"]
}, {
name: "ide",
- exclude: ["libs"]
+ exclude: ["libs", "libs/pdf"]
}, {
name: "libs"
},{
diff --git a/services/web/app/coffee/Features/Compile/CompileController.coffee b/services/web/app/coffee/Features/Compile/CompileController.coffee
index 054c6f5aaf..663210b092 100755
--- a/services/web/app/coffee/Features/Compile/CompileController.coffee
+++ b/services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -78,7 +78,12 @@ module.exports = CompileController =
url = "#{compilerUrl}#{url}"
logger.log url: url, "proxying to CLSI"
oneMinute = 60 * 1000
- proxy = request(url: url, method: req.method, timeout: oneMinute)
+ # pass through If-* and Range headers for byte serving pdfs
+ # do not send any others, potential proxying loop if Host: is passed!
+ newHeaders = {}
+ for h, v of req.headers
+ newHeaders[h] = req.headers[h] if h.match /^(If-|Range)/i
+ proxy = request(url: url, method: req.method, timeout: oneMinute, headers: newHeaders)
proxy.pipe(res)
proxy.on "error", (error) ->
logger.warn err: error, url: url, "CLSI proxy error"
diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee
index cb3e7837aa..b85ba6850c 100644
--- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee
+++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee
@@ -18,14 +18,18 @@ jsPath =
logger.log "Generating file fingerprints..."
for path in [
- "#{jsPath}libs/require.js",
- "#{jsPath}ide.js",
- "#{jsPath}main.js",
+ "#{jsPath}libs/require.js",
+ "#{jsPath}ide.js",
+ "#{jsPath}main.js",
"#{jsPath}libs.js",
"#{jsPath}ace/ace.js",
- "#{jsPath}libs/pdf.js",
- "#{jsPath}libs/pdf.worker.js",
- "/stylesheets/style.css"
+ "#{jsPath}libs/pdf.js",
+ "#{jsPath}libs/pdf.worker.js",
+ "#{jsPath}libs/compatibility.js",
+ "#{jsPath}libs/pdfjs-1.0.712/pdf.js",
+ "#{jsPath}libs/pdfjs-1.0.712/pdf.worker.js",
+ "#{jsPath}libs/pdfjs-1.0.712/compatibility.js",
+ "/stylesheets/style.css"
]
filePath = Path.join __dirname, "../../../", "public#{path}"
exists = fs.existsSync filePath
diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee
index 55e222d16e..783389cb6a 100644
--- a/services/web/app/coffee/models/User.coffee
+++ b/services/web/app/coffee/models/User.coffee
@@ -35,7 +35,9 @@ UserSchema = new Schema
compileTimeout: { type:Number, default: Settings.defaultFeatures.compileTimeout }
compileGroup: { type:String, default: Settings.defaultFeatures.compileGroup }
}
- featureSwitches : {}
+ featureSwitches : {
+ pdfng: { type: Boolean }
+ }
referal_id : {type:String, default:() -> uuid.v4().split("-")[0]}
refered_users: [ type:ObjectId, ref:'User' ]
refered_user_count: { type:Number, default: 0 }
diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade
index 48be8f0dbc..d6f99e24f2 100644
--- a/services/web/app/views/project/editor.jade
+++ b/services/web/app/views/project/editor.jade
@@ -112,11 +112,21 @@ block content
- locals.suppressDefaultJs = true
- - var fingerprintedPath = fingerprint(jsPath+'libs/pdf.worker.js')
- - var pdfJsWorkerPath = jsPath+'libs/pdf.worker.js?fingerprint='+fingerprintedPath
+ - // user.featureSwitches = {} // override
+ - var usePdfNG = user.featureSwitches && user.featureSwitches.pdfng;
+ - var pdfPath = 'libs/pdf.worker.js';
+ - if (usePdfNG) { pdfPath = 'libs/pdfjs-1.0.712/pdf.worker.js'; }
+ - var fingerprintedPath = fingerprint(jsPath+pdfPath)
+ - var pdfJsWorkerPath = jsPath+pdfPath+'?fingerprint='+fingerprintedPath
script(type='text/javascript').
window.pdfJsWorkerPath = "#{pdfJsWorkerPath}";
+ // when using pdfng we need a different pdfjs version
+ if usePdfNG
+ script(type='text/javascript').
+ window.requirejs.paths['libs/pdf'] = 'libs/pdfjs-1.0.712/pdf'
+ window.requirejs.paths['libs/compatibility'] = 'libs/pdfjs-1.0.712/compatibility'
+
script(
data-main=jsPath+"ide.js",
baseurl=jsPath,
diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade
index 1abcb75ae4..cbfeece827 100644
--- a/services/web/app/views/project/editor/pdf.jade
+++ b/services/web/app/views/project/editor/pdf.jade
@@ -58,16 +58,31 @@ div.full-size.pdf(ng-controller="PdfController")
i.split-screen
.pdf-viewer(ng-show="pdf.url && pdf.view == 'pdf' && !pdf.failure && !pdf.timeout && !pdf.error")
- div(
- pdfjs
- ng-if="settings.pdfViewer == 'pdfjs'"
- pdf-src="pdf.url"
- key="{{ project_id }}"
- resize-on="layout:main:resize,layout:pdf:resize"
- highlights="pdf.highlights"
- position="pdf.position"
- dbl-click-callback="syncToCode"
- )
+ - // user.featureSwitches = {} // override
+ - var usePdfNG = user.featureSwitches && user.featureSwitches.pdfng;
+ if !usePdfNG
+ div(
+ pdfjs
+ ng-if="settings.pdfViewer == 'pdfjs'"
+ pdf-src="pdf.url"
+ key="{{ project_id }}"
+ resize-on="layout:main:resize,layout:pdf:resize"
+ highlights="pdf.highlights"
+ position="pdf.position"
+ dbl-click-callback="syncToCode"
+ )
+ else
+ div(
+ pdfng
+ ng-if="settings.pdfViewer == 'pdfjs'"
+ pdf-src="pdf.url"
+ key="{{ project_id }}"
+ resize-on="layout:main:resize,layout:pdf:resize"
+ highlights="pdf.highlights"
+ position="pdf.position"
+ dbl-click-callback="syncToCode"
+ )
+
iframe(
ng-src="{{ pdf.url }}"
ng-if="settings.pdfViewer == 'native'"
diff --git a/services/web/public/coffee/base.coffee b/services/web/public/coffee/base.coffee
index c79f8e6d45..b050b45802 100644
--- a/services/web/public/coffee/base.coffee
+++ b/services/web/public/coffee/base.coffee
@@ -13,4 +13,4 @@ define [
"ipCookie"
])
- return App
\ No newline at end of file
+ return App
diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee
index e5abde12cf..2bd1ae236f 100644
--- a/services/web/public/coffee/ide.coffee
+++ b/services/web/public/coffee/ide.coffee
@@ -106,4 +106,4 @@ define [
else
$scope.darkTheme = false
- angular.bootstrap(document.body, ["SharelatexApp"])
\ No newline at end of file
+ angular.bootstrap(document.body, ["SharelatexApp"])
diff --git a/services/web/public/coffee/ide/pdf/PdfManager.coffee b/services/web/public/coffee/ide/pdf/PdfManager.coffee
index f53d649e97..b9e3fe5e88 100644
--- a/services/web/public/coffee/ide/pdf/PdfManager.coffee
+++ b/services/web/public/coffee/ide/pdf/PdfManager.coffee
@@ -2,6 +2,7 @@ define [
"ide/pdf/controllers/PdfController"
"ide/pdf/controllers/PdfViewToggleController"
"ide/pdf/directives/pdfJs"
+ "ide/pdfng/directives/pdfJs" # alternative incremental viewer
], () ->
class PdfManager
constructor: (@ide, @$scope) ->
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfAnnotations.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfAnnotations.coffee
new file mode 100644
index 0000000000..e9943859b8
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfAnnotations.coffee
@@ -0,0 +1,45 @@
+define [
+ "base"
+], (App) ->
+ # App = angular.module 'pdfAnnotations', []
+ App.factory 'pdfAnnotations', [ () ->
+ class pdfAnnotations
+
+ @EXTERNAL_LINK_TARGET = "_blank";
+
+ constructor: (options) ->
+ @annotationsLayerDiv = options.annotations;
+ @viewport = options.viewport
+ @navigateFn = options.navigateFn
+
+ setAnnotations: (annotations) ->
+ for annotation in annotations
+ switch annotation.subtype
+ when 'Link' then @addLink(annotation);
+ when 'Text' then continue
+
+ addLink: (link) ->
+ element = @buildLinkElementFromRect(link.rect);
+ @setLinkTarget(element, link);
+ @annotationsLayerDiv.appendChild(element);
+
+ buildLinkElementFromRect: (rect) ->
+ rect = @viewport.convertToViewportRectangle(rect);
+ rect = PDFJS.Util.normalizeRect(rect);
+ element = document.createElement("a");
+ element.style.left = Math.floor(rect[0]) + 'px';
+ element.style.top = Math.floor(rect[1]) + 'px';
+ element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+ element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
+ element
+
+ setLinkTarget: (element, link) ->
+ if link.url
+ element.href = link.url;
+ element.target = @EXTERNAL_LINK_TARGET;
+ else if (link.dest)
+ element.href = "#" + link.dest;
+ element.onclick = (e) =>
+ @navigateFn link
+ return false
+ ]
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee
new file mode 100644
index 0000000000..fc2b72bd79
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee
@@ -0,0 +1,29 @@
+define [
+ "base"
+], (App) ->
+ #app = angular.module 'pdfHighlights', []
+
+ App.factory 'pdfHighlights', [ () ->
+ class pdfHighlights
+
+ constructor: (options) ->
+ @highlightsLayerDiv = options.highlights[0]
+ @highlightElements = []
+
+ addHighlight: (viewport, left, top, width, height) ->
+ rect = viewport.convertToViewportRectangle([left, top, left + width, top + height])
+ rect = PDFJS.Util.normalizeRect(rect)
+ element = document.createElement("div")
+ element.style.left = Math.floor(rect[0]) + 'px'
+ element.style.top = Math.floor(rect[1]) + 'px'
+ element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'
+ element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'
+ @highlightElements.push(element)
+ @highlightsLayerDiv.appendChild(element)
+ element
+
+ clearHighlights: () ->
+ for h in @highlightElements
+ h.remove()
+ @highlightElements = []
+ ]
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
new file mode 100644
index 0000000000..aba1bd9578
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
@@ -0,0 +1,208 @@
+define [
+ "base"
+ "ide/pdfng/directives/pdfViewer"
+ "text!libs/pdfListView/TextLayer.css"
+ "text!libs/pdfListView/AnnotationsLayer.css"
+ "text!libs/pdfListView/HighlightsLayer.css"
+], (
+ App
+ pdfViewer
+ textLayerCss
+ annotationsLayerCss
+ highlightsLayerCss
+) ->
+ if PDFJS?
+ PDFJS.workerSrc = window.pdfJsWorkerPath
+
+ style = $("")
+ style.text(textLayerCss + "\n" + annotationsLayerCss + "\n" + highlightsLayerCss)
+ $("body").append(style)
+
+ App.directive "pdfng", ["$timeout", ($timeout) ->
+ return {
+ scope: {
+ "pdfSrc": "="
+ "highlights": "="
+ "position": "="
+ "dblClickCallback": "="
+ }
+ 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.pleaseJumpTo = null
+ scope.scale = null
+ initializedPosition = false
+ initializePosition = () ->
+ return if initializedPosition
+ initializedPosition = true
+
+ if (scale = $.localStorage("pdf.scale"))?
+ scope.scale = { scaleMode: scale.scaleMode, scale: +scale.scale}
+ else
+ scope.scale = { scaleMode: 'scale_mode_fit_width' }
+
+ if (position = $.localStorage("pdf.position.#{attrs.key}"))
+ scope.position = { page: +position.page, offset: { "top": +position.offset.top, "left": +position.offset.left } }
+
+ #scope.position = pdfListView.getPdfPosition(true)
+
+ scope.$on "$destroy", () =>
+ $.localStorage "pdf.scale", scope.scale
+ $.localStorage "pdf.position.#{attrs.key}", scope.position
+
+ $(window).unload () =>
+ $.localStorage "pdf.scale", scope.scale
+ $.localStorage "pdf.position.#{attrs.key}", scope.position
+
+ flashControls = () ->
+ scope.flashControls = true
+ $timeout () ->
+ scope.flashControls = false
+ , 1000
+
+ scope.$on 'pdfDoubleClick', (event, e) ->
+ scope.dblClickCallback?(page: e.page - 1, offset: { top: e.y, left: e.x })
+
+ scope.$on 'flash-controls', () ->
+ flashControls()
+
+ scope.$watch "pdfSrc", (url) ->
+ if url
+ scope.loading = true
+ # console.log 'pdfSrc =', url
+ initializePosition()
+ flashControls()
+ scope.$broadcast 'layout-ready'
+ # pdfListView
+ # .loadPdf(url, onProgress)
+ # .then () ->
+ # scope.$apply () ->
+ # scope.loading = false
+ # delete scope.progress
+ # initializePosition()
+ # flashControls()
+
+ scope.$on "loaded", () ->
+ # console.log "LOADED"
+ scope.loading = false
+
+ #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 = () ->
+ scale = angular.copy (scope.scale)
+ scale.scaleMode = 'scale_mode_fit_height'
+ scope.scale = scale
+
+ scope.fitToWidth = () ->
+ scale = angular.copy (scope.scale)
+ scale.scaleMode = 'scale_mode_fit_width'
+ scope.scale = scale
+
+ scope.zoomIn = () ->
+ scale = angular.copy (scope.scale)
+ scale.scaleMode = 'scale_mode_value'
+ scale.scale = scale.scale * 1.2
+ scope.scale = scale
+
+ scope.zoomOut = () ->
+ scale = angular.copy (scope.scale)
+ scale.scaleMode = 'scale_mode_value'
+ scale.scale = scale.scale / 1.2
+ scope.scale = scale
+
+ if attrs.resizeOn?
+ for event in attrs.resizeOn.split(",")
+ scope.$on event, (e) ->
+ #console.log 'got a resize event', event, e
+ #
+
+ scope.$on 'progress', (event, progress) ->
+ scope.$apply () ->
+ #console.log 'progress', progress.loaded, progress.total, progress
+ scope.progress = Math.floor(progress.loaded/progress.total*100)
+
+ template: """
+
+
+
+ """
+ }
+ ]
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfPage.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfPage.coffee
new file mode 100644
index 0000000000..c440b6fc12
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfPage.coffee
@@ -0,0 +1,129 @@
+define [
+ "base"
+], (App) ->
+ # App = angular.module 'pdfPage', ['pdfHighlights']
+
+ App.directive 'pdfPage', ['$timeout', 'pdfHighlights', ($timeout, pdfHighlights) ->
+ {
+ require: '^pdfViewer',
+ template: '''
+
+ '''
+ link: (scope, element, attrs, ctrl) ->
+ canvasElement = $(element).find('.pdf-canvas')
+ textElement = $(element).find('.text-layer')
+ annotationsElement = $(element).find('.annotations-layer')
+ highlightsElement = $(element).find('.highlights-layer')
+
+ updatePageSize = (size) ->
+ h = Math.floor(size[0])
+ w = Math.floor(size[1])
+ element.height(h)
+ element.width(w)
+ canvasElement.height(h)
+ canvasElement.width(w)
+ scope.page.sized = true
+
+ isVisible = (containerSize) ->
+ elemTop = element.offset().top - containerSize[2]
+ elemBottom = elemTop + element.innerHeight()
+ visible = (elemTop < containerSize[1] and elemBottom > 0)
+ scope.page.visible = visible
+ scope.page.elemTop = elemTop
+ scope.page.elemBottom = elemBottom
+ return visible
+
+ renderPage = () ->
+ scope.document.renderPage {
+ canvas: canvasElement,
+ text: textElement
+ annotations: annotationsElement
+ highlights: highlightsElement
+ }, scope.page.pageNum
+
+ pausePage = () ->
+ scope.document.pause {
+ canvas: canvasElement,
+ text: textElement
+ }, scope.page.pageNum
+
+ # keep track of our page element, so we can access it in the
+ # parent with scope.pages[i].element
+ scope.page.element = element
+
+ if (!scope.page.sized && scope.defaultPageSize)
+ updatePageSize scope.defaultPageSize
+
+ if scope.page.current
+ # console.log 'we must scroll to this page', scope.page.pageNum, 'at position', scope.page.position
+ renderPage()
+ # this is the current page, we want to scroll it into view
+ ctrl.setPdfPosition(scope.page, scope.page.position)
+
+ scope.$watch 'defaultPageSize', (defaultPageSize) ->
+ return unless defaultPageSize?
+ updatePageSize defaultPageSize
+
+ watchHandle = scope.$watch 'containerSize', (containerSize, oldVal) ->
+ return unless containerSize?
+ return unless scope.page.sized
+ oldVisible = scope.page.visible
+ newVisible = isVisible containerSize
+ scope.page.visible = newVisible
+ if newVisible && !oldVisible
+ renderPage()
+ # TODO deregister this listener after the page is rendered
+ #watchHandle()
+ else if !newVisible && oldVisible
+ pausePage()
+
+ element.on 'dblclick', (e) ->
+ offset = $(element).find('.pdf-canvas').offset()
+ dx = e.pageX - offset.left
+ dy = e.pageY - offset.top
+ scope.document.getPdfViewport(scope.page.pageNum).then (viewport) ->
+ pdfPoint = viewport.convertToPdfPoint(dx, dy);
+ event = {
+ page: scope.page.pageNum
+ x: pdfPoint[0],
+ y: viewport.viewBox[3] - pdfPoint[1]
+ }
+ scope.$emit 'pdfDoubleClick', event
+
+ highlightsLayer = new pdfHighlights({
+ highlights: highlightsElement
+ })
+
+ scope.$watch 'highlights', (highlights, oldVal) ->
+ return unless highlights?
+ return unless highlights.length > 0
+ if scope.timeoutHandler
+ $timeout.cancel(scope.timeoutHandler)
+ highlightsLayer.clearHighlights()
+ scope.timeoutHandler
+
+ # console.log 'got highlight watch in pdfPage', scope.page
+ pageHighlights = (h for h in highlights when h.page == scope.page.pageNum)
+ return unless pageHighlights.length
+ scope.document.getPdfViewport(scope.page.pageNum).then (viewport) ->
+ for hl in pageHighlights
+ # console.log 'adding highlight', h, viewport
+ top = viewport.viewBox[3] - hl.v
+ highlightsLayer.addHighlight viewport, hl.h, top, hl.width, hl.height
+ scope.timeoutHandler = $timeout () ->
+ highlightsLayer.clearHighlights()
+ scope.timeoutHandler = null
+ , 1000
+
+ scope.$on "$destroy", () ->
+ if scope.timeoutHandler?
+ $timeout.cancel(scope.timeoutHandler)
+ highlightsLayer.clearHighlights()
+
+ }
+ ]
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
new file mode 100644
index 0000000000..15a45c8908
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
@@ -0,0 +1,210 @@
+define [
+ "base"
+], (App) ->
+ # App = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']
+
+ App.factory 'PDFRenderer', ['$q', '$timeout', 'pdfAnnotations', 'pdfTextLayer', 'pdfSpinner', ($q, $timeout, pdfAnnotations, pdfTextLayer, pdfSpinner) ->
+
+ class PDFRenderer
+ JOB_QUEUE_INTERVAL: 25
+
+ constructor: (@url, @options) ->
+ PDFJS.disableFontFace = true # avoids repaints, uses worker more
+ # PDFJS.disableAutoFetch = true # enable this to prevent loading whole file
+ # PDFJS.disableStream
+ # PDFJS.disableRange
+ @scale = @options.scale || 1
+ @pdfjs = PDFJS.getDocument @url, null, null, @options.progressCallback
+ @document = $q.when(@pdfjs)
+ @navigateFn = @options.navigateFn
+ @spinner = new pdfSpinner
+ @resetState()
+
+ resetState: () ->
+ @page = []
+ @complete = []
+ @timeout = []
+ @pageLoad = []
+ @renderTask = []
+ @renderQueue = []
+ @jobs = 0
+
+ getNumPages: () ->
+ @document.then (pdfDocument) ->
+ pdfDocument.numPages
+
+ getPage: (pageNum) ->
+ # with promise caching
+ return @page[pageNum] if @page[pageNum]?
+ @page[pageNum] = @document.then (pdfDocument) ->
+ pdfDocument.getPage(pageNum)
+
+ getPdfViewport: (pageNum, scale) ->
+ scale ?= @scale
+ @document.then (pdfDocument) ->
+ pdfDocument.getPage(pageNum).then (page) ->
+ viewport = page.getViewport scale
+
+ getDestinations: () ->
+ @document.then (pdfDocument) ->
+ pdfDocument.getDestinations()
+
+# Not available in pdf.js-1.0.712, in later versions there is a direct
+# call for this - we should use it as soon as it is available in a
+# stable version
+ getDestination: (dest) ->
+ @destinations = @document.then (pdfDocument) ->
+ pdfDocument.getDestinations()
+ return @destinations.then (all) ->
+ all[dest]
+
+ @document.then (pdfDocument) ->
+ pdfDocument.getDestination(dest)
+
+
+ getPageIndex: (ref) ->
+ @document.then (pdfDocument) ->
+ pdfDocument.getPageIndex(ref).then (idx) ->
+ idx
+
+ getScale: () ->
+ @scale
+
+ setScale: (@scale) ->
+ @resetState()
+
+ pause: (element, pagenum) ->
+ return if @complete[pagenum]
+ return if @shuttingDown
+ @renderQueue = @renderQueue.filter (q) ->
+ q.pagenum != pagenum
+ @spinner.stop(element.canvas)
+
+ triggerRenderQueue: (interval = @JOB_QUEUE_INTERVAL) ->
+ $timeout () =>
+ @processRenderQueue()
+ , interval
+
+ removeCompletedJob: (taskRef, pagenum) ->
+ # may need to clean up deferred object here
+ delete taskRef[pagenum]
+ @jobs = @jobs - 1
+ @triggerRenderQueue(0)
+
+ renderPage: (element, pagenum) ->
+ return if @shuttingDown
+ current = {
+ 'element': element
+ 'pagenum': pagenum
+ }
+ @renderQueue.push(current)
+ @triggerRenderQueue()
+
+ processRenderQueue: () ->
+ return if @jobs > 0
+ current = @renderQueue.shift()
+ return unless current?
+ [element, pagenum] = [current.element, current.pagenum]
+ # if task is underway or complete, go to the next entry in the
+ # render queue
+ # console.log 'processing renderq', pagenum, @renderTask[pagenum], @complete[pagenum]
+ if @pageLoad[pagenum] or @renderTask[pagenum] or @complete[pagenum]
+ @processRenderQueue()
+ return
+ @jobs = @jobs + 1
+
+ element.canvas.addClass('pdfng-loading')
+ @spinner.add(element.canvas)
+
+ completeRef = @complete
+ renderTaskRef = @renderTask
+
+ @pageLoad[pagenum] = @getPage(pagenum)
+ @pageLoad[pagenum].then (pageObject) =>
+ @renderTask[pagenum] = @doRender element, pagenum, pageObject
+ @renderTask[pagenum].then () =>
+ # complete
+ completeRef[pagenum] = true
+ @removeCompletedJob renderTaskRef, pagenum
+ , () =>
+ # rejected
+ @removeCompletedJob renderTaskRef, pagenum
+
+ doRender: (element, pagenum, page) ->
+ self = this
+ scale = @scale
+
+ if (not scale?)
+ # console.log 'scale is undefined, returning'
+ return
+
+ canvas = $('')
+
+ 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')
+
+ element.canvas.height(newHeight)
+ element.canvas.width(newWidth)
+
+ if pixelRatio != 1
+ ctx.scale(pixelRatio, pixelRatio)
+
+ textLayer = new pdfTextLayer({
+ textLayerDiv: element.text[0]
+ viewport: viewport
+ })
+
+ annotationsLayer = new pdfAnnotations({
+ annotations: element.annotations[0]
+ viewport: viewport
+ navigateFn: @navigateFn
+ })
+
+ element.canvas.replaceWith(canvas)
+
+ result = page.render {
+ canvasContext: ctx
+ viewport: viewport
+ }
+
+ result.then () ->
+ canvas.removeClass('pdfng-rendering')
+ page.getTextContent().then (textContent) ->
+ textLayer.setTextContent textContent
+ page.getAnnotations().then (annotations) ->
+ annotationsLayer.setAnnotations annotations
+
+ return result
+
+ destroy: () ->
+ # console.log 'in pdf renderer destroy', @renderQueue
+ @shuttingDown = true
+ @renderQueue = []
+ for task in @renderTask
+ task.cancel() if task?
+ @pdfjs.then (document) ->
+ document.cleanup()
+ document.destroy()
+
+ ]
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfSpinner.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfSpinner.coffee
new file mode 100644
index 0000000000..4f5c96f3ab
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfSpinner.coffee
@@ -0,0 +1,25 @@
+define [
+ "base"
+], (App) ->
+ App.factory 'pdfSpinner', [() ->
+
+ class pdfSpinner
+
+ constructor: () ->
+ # handler for spinners
+
+ add: (element) ->
+ h = element.height()
+ w = element.width()
+ size = 64
+ spinner = $('
')
+ spinner.css({'font-size' : size + 'px'})
+ element.append(spinner)
+
+ stop: (element) ->
+ element.find('.fa-spin').removeClass('fa-spin')
+
+ remove: (element) ->
+ element.find('.fa-spin').remove()
+
+ ]
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfTextLayer.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfTextLayer.coffee
new file mode 100644
index 0000000000..3ad9ec6b5e
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfTextLayer.coffee
@@ -0,0 +1,217 @@
+define [
+ "base"
+], (App) ->
+ # App = angular.module 'pdfTextLayer', []
+
+ App.factory 'pdfTextLayer', [ () ->
+
+ # TRANSLATED FROM pdf.js-1.0.712
+ # pdf.js-1.0.712/web/ui_utils.js
+ # pdf.js-1.0.712/web/text_layer_builder.js
+
+ # -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+
+ # Copyright 2012 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.
+ #
+
+ # globals CustomStyle, scrollIntoView, PDFJS
+ # ms
+
+ # optimised CSS custom property getter/setter
+
+ CustomStyle = (CustomStyleClosure = ->
+
+ # As noted on: http://www.zachstronaut.com/posts/2009/02/17/
+ # animate-css-transforms-firefox-webkit.html
+ # in some versions of IE9 it is critical that ms appear in this list
+ # before Moz
+ CustomStyle = ->
+ prefixes = [
+ 'ms'
+ 'Moz'
+ 'Webkit'
+ 'O'
+ ]
+ _cache = {}
+ CustomStyle.getProp = get = (propName, element) ->
+
+ # check cache only when no element is given
+ return _cache[propName] if arguments.length is 1 and typeof _cache[propName] is 'string'
+ element = element or document.documentElement
+ style = element.style
+ prefixed = undefined
+ uPropName = undefined
+
+ # test standard property first
+ return (_cache[propName] = propName) if typeof style[propName] is 'string'
+
+ # capitalize
+ uPropName = propName.charAt(0).toUpperCase() + propName.slice(1)
+
+ # test vendor specific properties
+ i = 0
+ l = prefixes.length
+
+ while i < l
+ prefixed = prefixes[i] + uPropName
+ return (_cache[propName] = prefixed) if typeof style[prefixed] is 'string'
+ i++
+
+ #if all fails then set to undefined
+ _cache[propName] = 'undefined'
+
+ CustomStyle.setProp = set = (propName, element, str) ->
+ prop = @getProp(propName)
+ element.style[prop] = str if prop isnt 'undefined'
+ return
+
+ CustomStyle
+ )()
+
+ #################################
+
+ isAllWhitespace = (str) ->
+ not NonWhitespaceRegexp.test(str)
+ 'use strict'
+ FIND_SCROLL_OFFSET_TOP = -50
+ FIND_SCROLL_OFFSET_LEFT = -400
+ MAX_TEXT_DIVS_TO_RENDER = 100000
+ RENDER_DELAY = 200
+ NonWhitespaceRegexp = /\S/
+
+ ###*
+ TextLayerBuilder provides text-selection functionality for the PDF.
+ It does this by creating overlay divs over the PDF text. These divs
+ contain text that matches the PDF text they are overlaying. This object
+ also provides a way to highlight text that is being searched for.
+ ###
+
+ class pdfTextLayer
+
+ constructor: (options) ->
+ @textLayerDiv = options.textLayerDiv
+ @layoutDone = false
+ @divContentDone = false
+ @pageIdx = options.pageIndex
+ @matches = []
+ @lastScrollSource = options.lastScrollSource or null
+ @viewport = options.viewport
+ @isViewerInPresentationMode = options.isViewerInPresentationMode
+ @textDivs = []
+ @findController = options.findController or null
+
+ renderLayer: () ->
+ textLayerFrag = document.createDocumentFragment()
+ textDivs = @textDivs
+ textDivsLength = textDivs.length
+ canvas = document.createElement('canvas')
+ ctx = canvas.getContext('2d')
+
+ # No point in rendering many divs as it would make the browser
+ # unusable even after the divs are rendered.
+ return if textDivsLength > MAX_TEXT_DIVS_TO_RENDER
+ lastFontSize = undefined
+ lastFontFamily = undefined
+ i = 0
+ while i < textDivsLength
+ textDiv = textDivs[i]
+ if textDiv.dataset.isWhitespace
+ i++
+ continue
+ fontSize = textDiv.style.fontSize
+ fontFamily = textDiv.style.fontFamily
+
+ # Only build font string and set to context if different from last.
+ if fontSize isnt lastFontSize or fontFamily isnt lastFontFamily
+ ctx.font = fontSize + ' ' + fontFamily
+ lastFontSize = fontSize
+ lastFontFamily = fontFamily
+ width = ctx.measureText(textDiv.textContent).width
+ if width > 0
+ textLayerFrag.appendChild textDiv
+
+ # Dataset values come of type string.
+ textScale = textDiv.dataset.canvasWidth / width
+ rotation = textDiv.dataset.angle
+ transform = 'scale(' + textScale + ', 1)'
+ transform = 'rotate(' + rotation + 'deg) ' + transform if rotation
+ CustomStyle.setProp 'transform', textDiv, transform
+ CustomStyle.setProp 'transformOrigin', textDiv, '0% 0%'
+ i++
+ @textLayerDiv.appendChild textLayerFrag
+ return
+
+ appendText: (geom, styles) ->
+ style = styles[geom.fontName]
+ textDiv = document.createElement('div')
+ @textDivs.push textDiv
+ if isAllWhitespace(geom.str)
+ textDiv.dataset.isWhitespace = true
+ return
+ tx = PDFJS.Util.transform(@viewport.transform, geom.transform)
+ angle = Math.atan2(tx[1], tx[0])
+ angle += Math.PI / 2 if style.vertical
+ fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]))
+ fontAscent = fontHeight
+ if style.ascent
+ fontAscent = style.ascent * fontAscent
+ else fontAscent = (1 + style.descent) * fontAscent if style.descent
+ left = undefined
+ top = undefined
+ if angle is 0
+ left = tx[4]
+ top = tx[5] - fontAscent
+ else
+ left = tx[4] + (fontAscent * Math.sin(angle))
+ top = tx[5] - (fontAscent * Math.cos(angle))
+ textDiv.style.left = left + 'px'
+ textDiv.style.top = top + 'px'
+ textDiv.style.fontSize = fontHeight + 'px'
+ textDiv.style.fontFamily = style.fontFamily
+ textDiv.textContent = geom.str
+
+ textDiv.ondblclick = (e) ->
+ if (window.getSelection)
+ window.getSelection().removeAllRanges();
+ else if (document.selection)
+ document.selection.empty();
+
+ # |fontName| is only used by the Font Inspector. This test will succeed
+ # when e.g. the Font Inspector is off but the Stepper is on, but it's
+ # not worth the effort to do a more accurate test.
+ textDiv.dataset.fontName = geom.fontName if PDFJS.pdfBug
+
+ # Storing into dataset will convert number into string.
+ textDiv.dataset.angle = angle * (180 / Math.PI) if angle isnt 0
+ if style.vertical
+ textDiv.dataset.canvasWidth = geom.height * @viewport.scale
+ else
+ textDiv.dataset.canvasWidth = geom.width * @viewport.scale
+ return
+
+ setTextContent: (textContent) ->
+ @textContent = textContent
+ textItems = textContent.items
+ i = 0
+ len = textItems.length
+
+ while i < len
+ @appendText textItems[i], textContent.styles
+ i++
+ @divContentDone = true
+ @renderLayer()
+ return
+
+ ]
diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
new file mode 100644
index 0000000000..c9c696d087
--- /dev/null
+++ b/services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -0,0 +1,424 @@
+define [
+ "base"
+ "ide/pdfng/directives/pdfTextLayer"
+ "ide/pdfng/directives/pdfAnnotations"
+ "ide/pdfng/directives/pdfHighlights"
+ "ide/pdfng/directives/pdfRenderer"
+ "ide/pdfng/directives/pdfPage"
+ "ide/pdfng/directives/pdfSpinner"
+ "libs/pdf" # needs pdfjs-1.0.712, override the path in require.js to get it
+], (
+ App
+ pdfTextLayer
+ pdfAnnotations
+ pdfHighlights
+ pdfRenderer
+ pdfPage
+ pdfSpinner
+ pdf
+) ->
+
+ # App = angular.module 'pdfViewerApp', ['pdfPage', 'PDFRenderer', 'pdfHighlights']
+
+ App.controller 'pdfViewerController', ['$scope', '$q', '$timeout', 'PDFRenderer', '$element', 'pdfHighlights', 'pdfSpinner', ($scope, $q, $timeout, PDFRenderer, $element, pdfHighlights, pdfSpinner) ->
+ @load = () ->
+ # $scope.pages = []
+
+ $scope.document.destroy() if $scope.document?
+
+ $scope.document = new PDFRenderer($scope.pdfSrc, {
+ scale: 1,
+ navigateFn: (ref) ->
+ # this function captures clicks on the annotation links
+ $scope.navigateTo = ref
+ $scope.$apply()
+ progressCallback: (progress) ->
+ $scope.$emit 'progress', progress
+ })
+
+ # we will have all the main information needed to start display
+ # after the following promise is resolved
+ $scope.loaded = $q.all({
+ numPages: $scope.document.getNumPages()
+ # get size of first page as default @ scale 1
+ pdfViewport: $scope.document.getPdfViewport 1, 1
+ }).then (result) ->
+ $scope.pdfViewport = result.pdfViewport
+ $scope.pdfPageSize = [
+ result.pdfViewport.height,
+ result.pdfViewport.width
+ ]
+ # console.log 'resolved q.all, page size is', result
+ $scope.numPages = result.numPages
+ $scope.$emit "loaded"
+
+ @setScale = (scale, containerHeight, containerWidth) ->
+ $scope.loaded.then () ->
+ scale = {} if not scale?
+ if containerHeight == 0 or containerWidth == 0
+ numScale = 1
+ else if scale.scaleMode == 'scale_mode_fit_width'
+ # TODO make this dynamic
+ numScale = (containerWidth - 40) / ($scope.pdfPageSize[1])
+ else if scale.scaleMode == 'scale_mode_fit_height'
+ # TODO magic numbers for jquery ui layout
+ numScale = (containerHeight - 20) / ($scope.pdfPageSize[0])
+ else if scale.scaleMode == 'scale_mode_value'
+ numScale = scale.scale
+ else if scale.scaleMode == 'scale_mode_auto'
+ # TODO
+ else
+ scale.scaleMode = 'scale_mode_fit_width'
+ numScale = (containerWidth - 40) / ($scope.pdfPageSize[1])
+ # TODO
+ $scope.scale.scale = numScale
+ $scope.document.setScale(numScale)
+ $scope.defaultPageSize = [
+ numScale * $scope.pdfPageSize[0],
+ numScale * $scope.pdfPageSize[1]
+ ]
+ # console.log 'in setScale result', $scope.scale.scale, $scope.defaultPageSize
+
+ @redraw = (position) ->
+ # console.log 'in redraw'
+ # console.log 'reseting pages array for', $scope.numPages
+ $scope.pages = ({
+ pageNum: i + 1
+ } for i in [0 .. $scope.numPages-1])
+ if position? && position.page?
+ # console.log 'position is', position.page, position.offset
+ # console.log 'setting current page', position.page
+ pagenum = position.page
+ $scope.pages[pagenum].current = true
+ $scope.pages[pagenum].position = position
+
+ @zoomIn = () ->
+ # console.log 'zoom in'
+ newScale = $scope.scale.scale * 1.2
+ $scope.forceScale = { scaleMode: 'scale_mode_value', scale: newScale }
+
+ @zoomOut = () ->
+ # console.log 'zoom out'
+ newScale = $scope.scale.scale / 1.2
+ $scope.forceScale = { scaleMode: 'scale_mode_value', scale: newScale }
+
+ @fitWidth = () ->
+ # console.log 'fit width'
+ $scope.forceScale = { scaleMode: 'scale_mode_fit_width' }
+
+ @fitHeight = () ->
+ # console.log 'fit height'
+ $scope.forceScale = { scaleMode: 'scale_mode_fit_height' }
+
+ @checkPosition = () ->
+ # console.log 'check position'
+ $scope.forceCheck = ($scope.forceCheck || 0) + 1
+
+ @showRandomHighlights = () ->
+ # console.log 'show highlights'
+ $scope.highlights = [
+ {
+ page: 3
+ h: 100
+ v: 100
+ height: 30
+ width: 200
+ }
+ ]
+
+ # we work with (pagenumber, % of height down page from top)
+ # pdfListView works with (pagenumber, vertical position up page from
+ # bottom measured in pts)
+
+ @getPdfPosition = () ->
+ # console.log 'in getPdfPosition'
+ topPageIdx = 0
+ topPage = $scope.pages[0]
+ # find first visible page
+ visible = $scope.pages.some (page, i) ->
+ [topPageIdx, topPage] = [i, page] if page.visible
+ if visible
+ # console.log 'found it', topPageIdx
+ else
+ # console.log 'CANNOT FIND TOP PAGE'
+ return
+
+ # console.log 'top page is', topPage.pageNum, topPage.elemTop, topPage.elemBottom, topPage
+ top = topPage.elemTop
+ bottom = topPage.elemBottom
+ viewportTop = 0
+ viewportHeight = $element.height()
+ topVisible = (top >= viewportTop && top < viewportTop + viewportHeight)
+ someContentVisible = (top < viewportTop && bottom > viewportTop)
+ # console.log 'in PdfListView', top, topVisible, someContentVisible, viewportTop
+ if topVisible
+ canvasOffset = 0
+ else if someContentVisible
+ canvasOffset = viewportTop - top
+ else
+ canvasOffset = null
+ # console.log 'pdfListview position = ', canvasOffset
+ # instead of using promise, check if size is known and revert to
+ # default otherwise
+ # console.log 'looking up viewport', topPage.viewport, $scope.pdfViewport
+ if topPage.viewport
+ viewport = topPage.viewport
+ pdfOffset = viewport.convertToPdfPoint(0, canvasOffset);
+ else
+ # console.log 'WARNING: had to default to global page size'
+ viewport = $scope.pdfViewport
+ scaledOffset = canvasOffset / $scope.scale.scale
+ pdfOffset = viewport.convertToPdfPoint(0, scaledOffset);
+ # console.log 'converted to offset = ', pdfOffset
+ newPosition = {
+ "page": topPageIdx,
+ "offset" : { "top" : pdfOffset[1], "left": 0 }
+ }
+ return newPosition
+
+ @computeOffset = (page, position) ->
+ # console.log 'computing offset for', page, position
+ element = page.element
+ #console.log 'element =', $(element), 'parent =', $(element).parent()
+ t1 = $(element).offset()?.top
+ t2 = $(element).parent().offset()?.top
+ if not (t1? and t2?)
+ return $q((resolve, reject) -> reject('elements destroyed'))
+ pageTop = $(element).offset().top - $(element).parent().offset().top
+ # console.log('top of page scroll is', pageTop, 'vs', page.elemTop)
+ # console.log('inner height is', $(element).innerHeight())
+ currentScroll = $(element).parent().scrollTop()
+ offset = position.offset
+ # convert offset to pixels
+ return $scope.document.getPdfViewport(page.pageNum).then (viewport) ->
+ page.viewport = viewport
+ pageOffset = viewport.convertToViewportPoint(offset.left, offset.top)
+ # console.log 'addition offset =', pageOffset
+ # console.log 'total', pageTop + pageOffset[1]
+ Math.round(pageTop + pageOffset[1] + currentScroll) ## 10 is margin
+
+ @setPdfPosition = (page, position) ->
+ # console.log 'required pdf Position is', position
+ @computeOffset(page, position).then (offset) ->
+ $scope.pleaseScrollTo = offset
+ $scope.position = position
+
+ return this
+
+ ]
+
+ App.directive 'pdfViewer', ['$q', '$timeout', 'pdfSpinner', ($q, $timeout, pdfSpinner) ->
+ {
+ controller: 'pdfViewerController'
+ controllerAs: 'ctrl'
+ scope: {
+ "pdfSrc": "="
+ "highlights": "="
+ "position": "="
+ "scale": "="
+ "pleaseJumpTo": "="
+ }
+ template: """
+
+ """
+ link: (scope, element, attrs, ctrl) ->
+ # console.log 'in pdfViewer element is', element
+ # console.log 'attrs', attrs
+ spinner = new pdfSpinner
+ layoutReady = $q.defer()
+ layoutReady.notify 'waiting for layout'
+ layoutReady.promise.then () ->
+ # console.log 'layoutReady was resolved'
+
+ # TODO can we combine this with scope.parentSize, need to finalize boxes
+ updateContainer = () ->
+ scope.containerSize = [
+ element.innerWidth()
+ element.innerHeight()
+ element.offset().top
+ ]
+
+ doRescale = (scale) ->
+ # console.log 'doRescale', scale
+ origposition = angular.copy scope.position
+ # console.log 'origposition', origposition
+ layoutReady.promise.then () ->
+ [h, w] = [element.innerHeight(), element.width()]
+ # console.log 'in promise', h, w
+ ctrl.setScale(scale, h, w).then () ->
+ spinner.remove(element)
+ ctrl.redraw(origposition)
+
+ checkElementReady = () ->
+ # if element is zero-sized keep checking until it is ready
+ if element.height() == 0 or element.width() == 0
+ $timeout () ->
+ checkElementReady()
+ , 250
+ else
+ scope.$broadcast 'layout-ready' if !scope.parentSize?
+
+ checkElementReady()
+
+ scope.$on 'layout-ready', () ->
+ # console.log 'GOT LAYOUT READY EVENT'
+ # console.log 'calling refresh'
+ updateContainer()
+ spinner.add(element)
+ layoutReady.resolve 'layout is ready'
+ scope.parentSize = [
+ element.innerHeight(),
+ element.innerWidth()
+ ]
+ scope.$emit 'flash-controls'
+ #scope.$apply()
+
+ scope.$on 'layout:main:resize', () ->
+ # console.log 'GOT LAYOUT-MAIN-RESIZE EVENT'
+ scope.parentSize = [
+ element.innerHeight(),
+ element.innerWidth()
+ ]
+ scope.$apply()
+
+
+ scope.$on 'layout:pdf:resize', () ->
+ # console.log 'GOT LAYOUT-PDF-RESIZE EVENT'
+ scope.parentSize = [
+ element.innerHeight(),
+ element.innerWidth()
+ ]
+ #scope.$apply()
+
+ element.on 'scroll', () ->
+ #console.log 'scroll event', element.scrollTop(), 'adjusting?', scope.adjustingScroll
+ if scope.adjustingScroll
+ updateContainer()
+ scope.$apply()
+ scope.adjustingScroll = false
+ return
+ scope.scrolled = true
+ if scope.scrollHandlerTimeout
+ $timeout.cancel(scope.scrollHandlerTimeout)
+ scope.scrollHandlerTimeout = $timeout scrollHandler, 100
+
+ scrollHandler = () ->
+ scope.scrollHandlerTimeout = null
+ updateContainer()
+ scope.$apply()
+ newPosition = ctrl.getPdfPosition()
+ if newPosition?
+ scope.position = newPosition
+ scope.$apply()
+
+ scope.$watch 'pdfSrc', (newVal, oldVal) ->
+ # console.log 'loading pdf', newVal, oldVal
+ return unless newVal?
+ ctrl.load()
+ # trigger a redraw
+ scope.scale = angular.copy (scope.scale)
+
+ scope.$watch 'scale', (newVal, oldVal) ->
+ # no need to set scale when initialising, done in pdfSrc
+ return if newVal == oldVal
+ # console.log 'XXX calling Setscale in scale watch'
+ doRescale newVal
+
+ scope.$watch 'forceScale', (newVal, oldVal) ->
+ # console.log 'got change in numscale watcher', newVal, oldVal
+ return unless newVal?
+ doRescale newVal
+
+# scope.$watch 'position', (newVal, oldVal) ->
+# console.log 'got change in position watcher', newVal, oldVal
+
+ scope.$watch 'forceCheck', (newVal, oldVal) ->
+ # console.log 'forceCheck', newVal, oldVal
+ return unless newVal?
+ scope.adjustingScroll = true # temporarily disable scroll
+ doRescale scope.scale
+
+ scope.$watch('parentSize', (newVal, oldVal) ->
+ # console.log 'XXX in parentSize watch', newVal, oldVal
+ # if newVal == oldVal
+ # console.log 'returning because old and new are the same'
+ # return
+ return unless oldVal?
+ # console.log 'XXX calling setScale in parentSize watcher'
+ doRescale scope.scale
+ , true)
+
+ # scope.$watch 'elementWidth', (newVal, oldVal) ->
+ # console.log '*** watch INTERVAL element width is', newVal, oldVal
+
+ scope.$watch 'pleaseScrollTo', (newVal, oldVal) ->
+ # console.log 'got request to ScrollTo', newVal, 'oldVal', oldVal
+ return unless newVal?
+ scope.adjustingScroll = true # temporarily disable scroll
+ # handler while we reposition
+ $(element).scrollTop(newVal)
+ scope.pleaseScrollTo = undefined
+
+ scope.$watch 'pleaseJumpTo', (newPosition, oldPosition) ->
+ # console.log 'in pleaseJumpTo', newPosition, oldPosition
+ return unless newPosition?
+ ctrl.setPdfPosition scope.pages[newPosition.page-1], newPosition
+
+ scope.$watch 'navigateTo', (newVal, oldVal) ->
+ return unless newVal?
+ # console.log 'got request to navigate to', newVal, 'oldVal', oldVal
+ scope.navigateTo = undefined
+ # console.log 'navigate to', newVal
+ # console.log 'look up page num'
+ scope.document.getDestination(newVal.dest).then (r) ->
+ # console.log 'need to go to', r
+ # console.log 'page ref is', r[0]
+ scope.document.getPageIndex(r[0]).then (pidx) ->
+ # console.log 'page num is', pidx
+ page = scope.pages[pidx]
+ scope.document.getPdfViewport(page.pageNum).then (viewport) ->
+ #console.log 'got viewport', viewport
+ coords = viewport.convertToViewportPoint r[2], r[3]
+ #console.log 'viewport position', coords
+ #console.log 'r is', r, 'r[1]', r[1], 'r[1].name', r[1].name
+ if r[1].name == 'XYZ'
+ #console.log 'XYZ:', r[2], r[3]
+ newPosition = {page: pidx, offset: {top: r[3], left: r[2]}}
+ ctrl.setPdfPosition scope.pages[pidx], newPosition # XXX?
+
+ scope.$watch "highlights", (areas) ->
+ # console.log 'got HIGHLIGHTS in pdfViewer', areas
+ return if !areas?
+ #console.log 'areas are', areas
+ highlights = for area in areas or []
+ {
+ page: area.page - 1
+ highlight:
+ left: area.h
+ top: area.v
+ height: area.height
+ width: area.width
+ }
+ #console.log 'highlights', highlights
+
+ return if !highlights.length
+
+ first = highlights[0]
+
+ pageNum = scope.pages[first.page].pageNum
+
+ scope.document.getPdfViewport(pageNum).then (viewport) ->
+ position = {
+ page: first.page
+ offset:
+ left: first.highlight.left
+ top: viewport.viewBox[3] - first.highlight.top + first.highlight.height + 72
+ }
+ ctrl.setPdfPosition(scope.pages[first.page], position)
+
+ scope.$watch '$destroy', () ->
+ #console.log 'handle pdfng directive destroy'
+
+
+ }
+ ]
diff --git a/services/web/public/js/libs/pdfjs-1.0.712/compatibility.js b/services/web/public/js/libs/pdfjs-1.0.712/compatibility.js
new file mode 100644
index 0000000000..2c77766104
--- /dev/null
+++ b/services/web/public/js/libs/pdfjs-1.0.712/compatibility.js
@@ -0,0 +1,563 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2012 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.
+ */
+/* globals VBArray, PDFJS */
+
+'use strict';
+
+// Initializing PDFJS global object here, it case if we need to change/disable
+// some PDF.js features, e.g. range requests
+if (typeof PDFJS === 'undefined') {
+ (typeof window !== 'undefined' ? window : this).PDFJS = {};
+}
+
+// Checking if the typed arrays are supported
+// Support: iOS<6.0 (subarray), IE<10, Android<4.0
+(function checkTypedArrayCompatibility() {
+ if (typeof Uint8Array !== 'undefined') {
+ // Support: iOS<6.0
+ if (typeof Uint8Array.prototype.subarray === 'undefined') {
+ Uint8Array.prototype.subarray = function subarray(start, end) {
+ return new Uint8Array(this.slice(start, end));
+ };
+ Float32Array.prototype.subarray = function subarray(start, end) {
+ return new Float32Array(this.slice(start, end));
+ };
+ }
+
+ // Support: Android<4.1
+ if (typeof Float64Array === 'undefined') {
+ window.Float64Array = Float32Array;
+ }
+ return;
+ }
+
+ function subarray(start, end) {
+ return new TypedArray(this.slice(start, end));
+ }
+
+ function setArrayOffset(array, offset) {
+ if (arguments.length < 2) {
+ offset = 0;
+ }
+ for (var i = 0, n = array.length; i < n; ++i, ++offset) {
+ this[offset] = array[i] & 0xFF;
+ }
+ }
+
+ function TypedArray(arg1) {
+ var result, i, n;
+ if (typeof arg1 === 'number') {
+ result = [];
+ for (i = 0; i < arg1; ++i) {
+ result[i] = 0;
+ }
+ } else if ('slice' in arg1) {
+ result = arg1.slice(0);
+ } else {
+ result = [];
+ for (i = 0, n = arg1.length; i < n; ++i) {
+ result[i] = arg1[i];
+ }
+ }
+
+ result.subarray = subarray;
+ result.buffer = result;
+ result.byteLength = result.length;
+ result.set = setArrayOffset;
+
+ if (typeof arg1 === 'object' && arg1.buffer) {
+ result.buffer = arg1.buffer;
+ }
+ return result;
+ }
+
+ window.Uint8Array = TypedArray;
+ window.Int8Array = TypedArray;
+
+ // we don't need support for set, byteLength for 32-bit array
+ // so we can use the TypedArray as well
+ window.Uint32Array = TypedArray;
+ window.Int32Array = TypedArray;
+ window.Uint16Array = TypedArray;
+ window.Float32Array = TypedArray;
+ window.Float64Array = TypedArray;
+})();
+
+// URL = URL || webkitURL
+// Support: Safari<7, Android 4.2+
+(function normalizeURLObject() {
+ if (!window.URL) {
+ window.URL = window.webkitURL;
+ }
+})();
+
+// Object.defineProperty()?
+// Support: Android<4.0, Safari<5.1
+(function checkObjectDefinePropertyCompatibility() {
+ if (typeof Object.defineProperty !== 'undefined') {
+ var definePropertyPossible = true;
+ try {
+ // some browsers (e.g. safari) cannot use defineProperty() on DOM objects
+ // and thus the native version is not sufficient
+ Object.defineProperty(new Image(), 'id', { value: 'test' });
+ // ... another test for android gb browser for non-DOM objects
+ var Test = function Test() {};
+ Test.prototype = { get id() { } };
+ Object.defineProperty(new Test(), 'id',
+ { value: '', configurable: true, enumerable: true, writable: false });
+ } catch (e) {
+ definePropertyPossible = false;
+ }
+ if (definePropertyPossible) {
+ return;
+ }
+ }
+
+ Object.defineProperty = function objectDefineProperty(obj, name, def) {
+ delete obj[name];
+ if ('get' in def) {
+ obj.__defineGetter__(name, def['get']);
+ }
+ if ('set' in def) {
+ obj.__defineSetter__(name, def['set']);
+ }
+ if ('value' in def) {
+ obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
+ this.__defineGetter__(name, function objectDefinePropertyGetter() {
+ return value;
+ });
+ return value;
+ });
+ obj[name] = def.value;
+ }
+ };
+})();
+
+
+// No XMLHttpRequest#response?
+// Support: IE<11, Android <4.0
+(function checkXMLHttpRequestResponseCompatibility() {
+ var xhrPrototype = XMLHttpRequest.prototype;
+ var xhr = new XMLHttpRequest();
+ if (!('overrideMimeType' in xhr)) {
+ // IE10 might have response, but not overrideMimeType
+ // Support: IE10
+ Object.defineProperty(xhrPrototype, 'overrideMimeType', {
+ value: function xmlHttpRequestOverrideMimeType(mimeType) {}
+ });
+ }
+ if ('responseType' in xhr) {
+ return;
+ }
+
+ // The worker will be using XHR, so we can save time and disable worker.
+ PDFJS.disableWorker = true;
+
+ // Support: IE9
+ if (typeof VBArray !== 'undefined') {
+ Object.defineProperty(xhrPrototype, 'response', {
+ get: function xmlHttpRequestResponseGet() {
+ if (this.responseType === 'arraybuffer') {
+ return new Uint8Array(new VBArray(this.responseBody).toArray());
+ } else {
+ return this.responseText;
+ }
+ }
+ });
+ return;
+ }
+
+ // other browsers
+ function responseTypeSetter() {
+ // will be only called to set "arraybuffer"
+ this.overrideMimeType('text/plain; charset=x-user-defined');
+ }
+ if (typeof xhr.overrideMimeType === 'function') {
+ Object.defineProperty(xhrPrototype, 'responseType',
+ { set: responseTypeSetter });
+ }
+ function responseGetter() {
+ var text = this.responseText;
+ var i, n = text.length;
+ var result = new Uint8Array(n);
+ for (i = 0; i < n; ++i) {
+ result[i] = text.charCodeAt(i) & 0xFF;
+ }
+ return result.buffer;
+ }
+ Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
+})();
+
+// window.btoa (base64 encode function) ?
+// Support: IE<10
+(function checkWindowBtoaCompatibility() {
+ if ('btoa' in window) {
+ return;
+ }
+
+ var digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+ window.btoa = function windowBtoa(chars) {
+ var buffer = '';
+ var i, n;
+ for (i = 0, n = chars.length; i < n; i += 3) {
+ var b1 = chars.charCodeAt(i) & 0xFF;
+ var b2 = chars.charCodeAt(i + 1) & 0xFF;
+ var b3 = chars.charCodeAt(i + 2) & 0xFF;
+ var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+ var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+ var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
+ buffer += (digits.charAt(d1) + digits.charAt(d2) +
+ digits.charAt(d3) + digits.charAt(d4));
+ }
+ return buffer;
+ };
+})();
+
+// window.atob (base64 encode function)?
+// Support: IE<10
+(function checkWindowAtobCompatibility() {
+ if ('atob' in window) {
+ return;
+ }
+
+ // https://github.com/davidchambers/Base64.js
+ var digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+ window.atob = function (input) {
+ input = input.replace(/=+$/, '');
+ if (input.length % 4 === 1) {
+ throw new Error('bad atob input');
+ }
+ for (
+ // initialize result and counters
+ var bc = 0, bs, buffer, idx = 0, output = '';
+ // get next character
+ buffer = input.charAt(idx++);
+ // character found in table?
+ // initialize bit storage and add its ascii value
+ ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
+ // and if not first of each 4 characters,
+ // convert the first 8 bits to one ascii character
+ bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
+ ) {
+ // try to find character in table (0-63, not found => -1)
+ buffer = digits.indexOf(buffer);
+ }
+ return output;
+ };
+})();
+
+// Function.prototype.bind?
+// Support: Android<4.0, iOS<6.0
+(function checkFunctionPrototypeBindCompatibility() {
+ if (typeof Function.prototype.bind !== 'undefined') {
+ return;
+ }
+
+ Function.prototype.bind = function functionPrototypeBind(obj) {
+ var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
+ var bound = function functionPrototypeBindBound() {
+ var args = headArgs.concat(Array.prototype.slice.call(arguments));
+ return fn.apply(obj, args);
+ };
+ return bound;
+ };
+})();
+
+// HTMLElement dataset property
+// Support: IE<11, Safari<5.1, Android<4.0
+(function checkDatasetProperty() {
+ var div = document.createElement('div');
+ if ('dataset' in div) {
+ return; // dataset property exists
+ }
+
+ Object.defineProperty(HTMLElement.prototype, 'dataset', {
+ get: function() {
+ if (this._dataset) {
+ return this._dataset;
+ }
+
+ var dataset = {};
+ for (var j = 0, jj = this.attributes.length; j < jj; j++) {
+ var attribute = this.attributes[j];
+ if (attribute.name.substring(0, 5) !== 'data-') {
+ continue;
+ }
+ var key = attribute.name.substring(5).replace(/\-([a-z])/g,
+ function(all, ch) {
+ return ch.toUpperCase();
+ });
+ dataset[key] = attribute.value;
+ }
+
+ Object.defineProperty(this, '_dataset', {
+ value: dataset,
+ writable: false,
+ enumerable: false
+ });
+ return dataset;
+ },
+ enumerable: true
+ });
+})();
+
+// HTMLElement classList property
+// Support: IE<10, Android<4.0, iOS<5.0
+(function checkClassListProperty() {
+ var div = document.createElement('div');
+ if ('classList' in div) {
+ return; // classList property exists
+ }
+
+ function changeList(element, itemName, add, remove) {
+ var s = element.className || '';
+ var list = s.split(/\s+/g);
+ if (list[0] === '') {
+ list.shift();
+ }
+ var index = list.indexOf(itemName);
+ if (index < 0 && add) {
+ list.push(itemName);
+ }
+ if (index >= 0 && remove) {
+ list.splice(index, 1);
+ }
+ element.className = list.join(' ');
+ return (index >= 0);
+ }
+
+ var classListPrototype = {
+ add: function(name) {
+ changeList(this.element, name, true, false);
+ },
+ contains: function(name) {
+ return changeList(this.element, name, false, false);
+ },
+ remove: function(name) {
+ changeList(this.element, name, false, true);
+ },
+ toggle: function(name) {
+ changeList(this.element, name, true, true);
+ }
+ };
+
+ Object.defineProperty(HTMLElement.prototype, 'classList', {
+ get: function() {
+ if (this._classList) {
+ return this._classList;
+ }
+
+ var classList = Object.create(classListPrototype, {
+ element: {
+ value: this,
+ writable: false,
+ enumerable: true
+ }
+ });
+ Object.defineProperty(this, '_classList', {
+ value: classList,
+ writable: false,
+ enumerable: false
+ });
+ return classList;
+ },
+ enumerable: true
+ });
+})();
+
+// Check console compatibility
+// In older IE versions the console object is not available
+// unless console is open.
+// Support: IE<10
+(function checkConsoleCompatibility() {
+ if (!('console' in window)) {
+ window.console = {
+ log: function() {},
+ error: function() {},
+ warn: function() {}
+ };
+ } else if (!('bind' in console.log)) {
+ // native functions in IE9 might not have bind
+ console.log = (function(fn) {
+ return function(msg) { return fn(msg); };
+ })(console.log);
+ console.error = (function(fn) {
+ return function(msg) { return fn(msg); };
+ })(console.error);
+ console.warn = (function(fn) {
+ return function(msg) { return fn(msg); };
+ })(console.warn);
+ }
+})();
+
+// Check onclick compatibility in Opera
+// Support: Opera<15
+(function checkOnClickCompatibility() {
+ // workaround for reported Opera bug DSK-354448:
+ // onclick fires on disabled buttons with opaque content
+ function ignoreIfTargetDisabled(event) {
+ if (isDisabled(event.target)) {
+ event.stopPropagation();
+ }
+ }
+ function isDisabled(node) {
+ return node.disabled || (node.parentNode && isDisabled(node.parentNode));
+ }
+ if (navigator.userAgent.indexOf('Opera') !== -1) {
+ // use browser detection since we cannot feature-check this bug
+ document.addEventListener('click', ignoreIfTargetDisabled, true);
+ }
+})();
+
+// Checks if possible to use URL.createObjectURL()
+// Support: IE
+(function checkOnBlobSupport() {
+ // sometimes IE loosing the data created with createObjectURL(), see #3977
+ if (navigator.userAgent.indexOf('Trident') >= 0) {
+ PDFJS.disableCreateObjectURL = true;
+ }
+})();
+
+// Checks if navigator.language is supported
+(function checkNavigatorLanguage() {
+ if ('language' in navigator &&
+ /^[a-z]+(-[A-Z]+)?$/.test(navigator.language)) {
+ return;
+ }
+ function formatLocale(locale) {
+ var split = locale.split(/[-_]/);
+ split[0] = split[0].toLowerCase();
+ if (split.length > 1) {
+ split[1] = split[1].toUpperCase();
+ }
+ return split.join('-');
+ }
+ var language = navigator.language || navigator.userLanguage || 'en-US';
+ PDFJS.locale = formatLocale(language);
+})();
+
+(function checkRangeRequests() {
+ // Safari has issues with cached range requests see:
+ // https://github.com/mozilla/pdf.js/issues/3260
+ // Last tested with version 6.0.4.
+ // Support: Safari 6.0+
+ var isSafari = Object.prototype.toString.call(
+ window.HTMLElement).indexOf('Constructor') > 0;
+
+ // Older versions of Android (pre 3.0) has issues with range requests, see:
+ // https://github.com/mozilla/pdf.js/issues/3381.
+ // Make sure that we only match webkit-based Android browsers,
+ // since Firefox/Fennec works as expected.
+ // Support: Android<3.0
+ var regex = /Android\s[0-2][^\d]/;
+ var isOldAndroid = regex.test(navigator.userAgent);
+
+ if (isSafari || isOldAndroid) {
+ PDFJS.disableRange = true;
+ }
+})();
+
+// Check if the browser supports manipulation of the history.
+// Support: IE<10, Android<4.2
+(function checkHistoryManipulation() {
+ // Android 2.x has so buggy pushState support that it was removed in
+ // Android 3.0 and restored as late as in Android 4.2.
+ // Support: Android 2.x
+ if (!history.pushState || navigator.userAgent.indexOf('Android 2.') >= 0) {
+ PDFJS.disableHistory = true;
+ }
+})();
+
+// Support: IE<11, Chrome<21, Android<4.4, Safari<6
+(function checkSetPresenceInImageData() {
+ // IE < 11 will use window.CanvasPixelArray which lacks set function.
+ if (window.CanvasPixelArray) {
+ if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
+ window.CanvasPixelArray.prototype.set = function(arr) {
+ for (var i = 0, ii = this.length; i < ii; i++) {
+ this[i] = arr[i];
+ }
+ };
+ }
+ } else {
+ // Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
+ // Because we cannot feature detect it, we rely on user agent parsing.
+ var polyfill = false, versionMatch;
+ if (navigator.userAgent.indexOf('Chrom') >= 0) {
+ versionMatch = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
+ // Chrome < 21 lacks the set function.
+ polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
+ } else if (navigator.userAgent.indexOf('Android') >= 0) {
+ // Android < 4.4 lacks the set function.
+ // Android >= 4.4 will contain Chrome in the user agent,
+ // thus pass the Chrome check above and not reach this block.
+ polyfill = /Android\s[0-4][^\d]/g.test(navigator.userAgent);
+ } else if (navigator.userAgent.indexOf('Safari') >= 0) {
+ versionMatch = navigator.userAgent.
+ match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
+ // Safari < 6 lacks the set function.
+ polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
+ }
+
+ if (polyfill) {
+ var contextPrototype = window.CanvasRenderingContext2D.prototype;
+ contextPrototype._createImageData = contextPrototype.createImageData;
+ contextPrototype.createImageData = function(w, h) {
+ var imageData = this._createImageData(w, h);
+ imageData.data.set = function(arr) {
+ for (var i = 0, ii = this.length; i < ii; i++) {
+ this[i] = arr[i];
+ }
+ };
+ return imageData;
+ };
+ }
+ }
+})();
+
+// Support: IE<10, Android<4.0, iOS
+(function checkRequestAnimationFrame() {
+ function fakeRequestAnimationFrame(callback) {
+ window.setTimeout(callback, 20);
+ }
+
+ var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
+ if (isIOS) {
+ // requestAnimationFrame on iOS is broken, replacing with fake one.
+ window.requestAnimationFrame = fakeRequestAnimationFrame;
+ return;
+ }
+ if ('requestAnimationFrame' in window) {
+ return;
+ }
+ window.requestAnimationFrame =
+ window.mozRequestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ fakeRequestAnimationFrame;
+})();
+
+(function checkCanvasSizeLimitation() {
+ var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
+ var isAndroid = /Android/g.test(navigator.userAgent);
+ if (isIOS || isAndroid) {
+ // 5MP
+ PDFJS.maxCanvasPixels = 5242880;
+ }
+})();
diff --git a/services/web/public/js/libs/pdfjs-1.0.712/pdf.js b/services/web/public/js/libs/pdfjs-1.0.712/pdf.js
new file mode 100644
index 0000000000..cb69b433b8
--- /dev/null
+++ b/services/web/public/js/libs/pdfjs-1.0.712/pdf.js
@@ -0,0 +1,7681 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2012 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.
+ */
+/*jshint globalstrict: false */
+/* globals PDFJS */
+
+// Initializing PDFJS global object (if still undefined)
+if (typeof PDFJS === 'undefined') {
+ (typeof window !== 'undefined' ? window : this).PDFJS = {};
+}
+
+PDFJS.version = '1.0.712';
+PDFJS.build = '6969ed4';
+
+(function pdfjsWrapper() {
+ // Use strict in our context only - users might not want it
+ 'use strict';
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2012 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.
+ */
+/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL,
+ Promise */
+
+'use strict';
+
+var globalScope = (typeof window === 'undefined') ? this : window;
+
+var isWorker = (typeof window === 'undefined');
+
+var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
+
+var TextRenderingMode = {
+ FILL: 0,
+ STROKE: 1,
+ FILL_STROKE: 2,
+ INVISIBLE: 3,
+ FILL_ADD_TO_PATH: 4,
+ STROKE_ADD_TO_PATH: 5,
+ FILL_STROKE_ADD_TO_PATH: 6,
+ ADD_TO_PATH: 7,
+ FILL_STROKE_MASK: 3,
+ ADD_TO_PATH_FLAG: 4
+};
+
+var ImageKind = {
+ GRAYSCALE_1BPP: 1,
+ RGB_24BPP: 2,
+ RGBA_32BPP: 3
+};
+
+var AnnotationType = {
+ WIDGET: 1,
+ TEXT: 2,
+ LINK: 3
+};
+
+var StreamType = {
+ UNKNOWN: 0,
+ FLATE: 1,
+ LZW: 2,
+ DCT: 3,
+ JPX: 4,
+ JBIG: 5,
+ A85: 6,
+ AHX: 7,
+ CCF: 8,
+ RL: 9
+};
+
+var FontType = {
+ UNKNOWN: 0,
+ TYPE1: 1,
+ TYPE1C: 2,
+ CIDFONTTYPE0: 3,
+ CIDFONTTYPE0C: 4,
+ TRUETYPE: 5,
+ CIDFONTTYPE2: 6,
+ TYPE3: 7,
+ OPENTYPE: 8,
+ TYPE0: 9,
+ MMTYPE1: 10
+};
+
+// The global PDFJS object exposes the API
+// In production, it will be declared outside a global wrapper
+// In development, it will be declared here
+if (!globalScope.PDFJS) {
+ globalScope.PDFJS = {};
+}
+
+globalScope.PDFJS.pdfBug = false;
+
+PDFJS.VERBOSITY_LEVELS = {
+ errors: 0,
+ warnings: 1,
+ infos: 5
+};
+
+// All the possible operations for an operator list.
+var OPS = PDFJS.OPS = {
+ // Intentionally start from 1 so it is easy to spot bad operators that will be
+ // 0's.
+ dependency: 1,
+ setLineWidth: 2,
+ setLineCap: 3,
+ setLineJoin: 4,
+ setMiterLimit: 5,
+ setDash: 6,
+ setRenderingIntent: 7,
+ setFlatness: 8,
+ setGState: 9,
+ save: 10,
+ restore: 11,
+ transform: 12,
+ moveTo: 13,
+ lineTo: 14,
+ curveTo: 15,
+ curveTo2: 16,
+ curveTo3: 17,
+ closePath: 18,
+ rectangle: 19,
+ stroke: 20,
+ closeStroke: 21,
+ fill: 22,
+ eoFill: 23,
+ fillStroke: 24,
+ eoFillStroke: 25,
+ closeFillStroke: 26,
+ closeEOFillStroke: 27,
+ endPath: 28,
+ clip: 29,
+ eoClip: 30,
+ beginText: 31,
+ endText: 32,
+ setCharSpacing: 33,
+ setWordSpacing: 34,
+ setHScale: 35,
+ setLeading: 36,
+ setFont: 37,
+ setTextRenderingMode: 38,
+ setTextRise: 39,
+ moveText: 40,
+ setLeadingMoveText: 41,
+ setTextMatrix: 42,
+ nextLine: 43,
+ showText: 44,
+ showSpacedText: 45,
+ nextLineShowText: 46,
+ nextLineSetSpacingShowText: 47,
+ setCharWidth: 48,
+ setCharWidthAndBounds: 49,
+ setStrokeColorSpace: 50,
+ setFillColorSpace: 51,
+ setStrokeColor: 52,
+ setStrokeColorN: 53,
+ setFillColor: 54,
+ setFillColorN: 55,
+ setStrokeGray: 56,
+ setFillGray: 57,
+ setStrokeRGBColor: 58,
+ setFillRGBColor: 59,
+ setStrokeCMYKColor: 60,
+ setFillCMYKColor: 61,
+ shadingFill: 62,
+ beginInlineImage: 63,
+ beginImageData: 64,
+ endInlineImage: 65,
+ paintXObject: 66,
+ markPoint: 67,
+ markPointProps: 68,
+ beginMarkedContent: 69,
+ beginMarkedContentProps: 70,
+ endMarkedContent: 71,
+ beginCompat: 72,
+ endCompat: 73,
+ paintFormXObjectBegin: 74,
+ paintFormXObjectEnd: 75,
+ beginGroup: 76,
+ endGroup: 77,
+ beginAnnotations: 78,
+ endAnnotations: 79,
+ beginAnnotation: 80,
+ endAnnotation: 81,
+ paintJpegXObject: 82,
+ paintImageMaskXObject: 83,
+ paintImageMaskXObjectGroup: 84,
+ paintImageXObject: 85,
+ paintInlineImageXObject: 86,
+ paintInlineImageXObjectGroup: 87,
+ paintImageXObjectRepeat: 88,
+ paintImageMaskXObjectRepeat: 89,
+ paintSolidColorImageMask: 90,
+ constructPath: 91
+};
+
+// A notice for devs. These are good for things that are helpful to devs, such
+// as warning that Workers were disabled, which is important to devs but not
+// end users.
+function info(msg) {
+ if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) {
+ console.log('Info: ' + msg);
+ }
+}
+
+// Non-fatal warnings.
+function warn(msg) {
+ if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) {
+ console.log('Warning: ' + msg);
+ }
+}
+
+// Fatal errors that should trigger the fallback UI and halt execution by
+// throwing an exception.
+function error(msg) {
+ // If multiple arguments were passed, pass them all to the log function.
+ if (arguments.length > 1) {
+ var logArguments = ['Error:'];
+ logArguments.push.apply(logArguments, arguments);
+ console.log.apply(console, logArguments);
+ // Join the arguments into a single string for the lines below.
+ msg = [].join.call(arguments, ' ');
+ } else {
+ console.log('Error: ' + msg);
+ }
+ console.log(backtrace());
+ UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown);
+ throw new Error(msg);
+}
+
+function backtrace() {
+ try {
+ throw new Error();
+ } catch (e) {
+ return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+ }
+}
+
+function assert(cond, msg) {
+ if (!cond) {
+ error(msg);
+ }
+}
+
+var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
+ unknown: 'unknown',
+ forms: 'forms',
+ javaScript: 'javaScript',
+ smask: 'smask',
+ shadingPattern: 'shadingPattern',
+ font: 'font'
+};
+
+var UnsupportedManager = PDFJS.UnsupportedManager =
+ (function UnsupportedManagerClosure() {
+ var listeners = [];
+ return {
+ listen: function (cb) {
+ listeners.push(cb);
+ },
+ notify: function (featureId) {
+ warn('Unsupported feature "' + featureId + '"');
+ for (var i = 0, ii = listeners.length; i < ii; i++) {
+ listeners[i](featureId);
+ }
+ }
+ };
+})();
+
+// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
+// absolute URL, it will be returned as is.
+function combineUrl(baseUrl, url) {
+ if (!url) {
+ return baseUrl;
+ }
+ if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) {
+ return url;
+ }
+ var i;
+ if (url.charAt(0) === '/') {
+ // absolute path
+ i = baseUrl.indexOf('://');
+ if (url.charAt(1) === '/') {
+ ++i;
+ } else {
+ i = baseUrl.indexOf('/', i + 3);
+ }
+ return baseUrl.substring(0, i) + url;
+ } else {
+ // relative path
+ var pathLength = baseUrl.length;
+ i = baseUrl.lastIndexOf('#');
+ pathLength = i >= 0 ? i : pathLength;
+ i = baseUrl.lastIndexOf('?', pathLength);
+ pathLength = i >= 0 ? i : pathLength;
+ var prefixLength = baseUrl.lastIndexOf('/', pathLength);
+ return baseUrl.substring(0, prefixLength + 1) + url;
+ }
+}
+
+// Validates if URL is safe and allowed, e.g. to avoid XSS.
+function isValidUrl(url, allowRelative) {
+ if (!url) {
+ return false;
+ }
+ // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
+ // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url);
+ if (!protocol) {
+ return allowRelative;
+ }
+ protocol = protocol[0].toLowerCase();
+ switch (protocol) {
+ case 'http':
+ case 'https':
+ case 'ftp':
+ case 'mailto':
+ return true;
+ default:
+ return false;
+ }
+}
+PDFJS.isValidUrl = isValidUrl;
+
+function shadow(obj, prop, value) {
+ Object.defineProperty(obj, prop, { value: value,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return value;
+}
+
+var PasswordResponses = PDFJS.PasswordResponses = {
+ NEED_PASSWORD: 1,
+ INCORRECT_PASSWORD: 2
+};
+
+var PasswordException = (function PasswordExceptionClosure() {
+ function PasswordException(msg, code) {
+ this.name = 'PasswordException';
+ this.message = msg;
+ this.code = code;
+ }
+
+ PasswordException.prototype = new Error();
+ PasswordException.constructor = PasswordException;
+
+ return PasswordException;
+})();
+
+var UnknownErrorException = (function UnknownErrorExceptionClosure() {
+ function UnknownErrorException(msg, details) {
+ this.name = 'UnknownErrorException';
+ this.message = msg;
+ this.details = details;
+ }
+
+ UnknownErrorException.prototype = new Error();
+ UnknownErrorException.constructor = UnknownErrorException;
+
+ return UnknownErrorException;
+})();
+
+var InvalidPDFException = (function InvalidPDFExceptionClosure() {
+ function InvalidPDFException(msg) {
+ this.name = 'InvalidPDFException';
+ this.message = msg;
+ }
+
+ InvalidPDFException.prototype = new Error();
+ InvalidPDFException.constructor = InvalidPDFException;
+
+ return InvalidPDFException;
+})();
+
+var MissingPDFException = (function MissingPDFExceptionClosure() {
+ function MissingPDFException(msg) {
+ this.name = 'MissingPDFException';
+ this.message = msg;
+ }
+
+ MissingPDFException.prototype = new Error();
+ MissingPDFException.constructor = MissingPDFException;
+
+ return MissingPDFException;
+})();
+
+var NotImplementedException = (function NotImplementedExceptionClosure() {
+ function NotImplementedException(msg) {
+ this.message = msg;
+ }
+
+ NotImplementedException.prototype = new Error();
+ NotImplementedException.prototype.name = 'NotImplementedException';
+ NotImplementedException.constructor = NotImplementedException;
+
+ return NotImplementedException;
+})();
+
+var MissingDataException = (function MissingDataExceptionClosure() {
+ function MissingDataException(begin, end) {
+ this.begin = begin;
+ this.end = end;
+ this.message = 'Missing data [' + begin + ', ' + end + ')';
+ }
+
+ MissingDataException.prototype = new Error();
+ MissingDataException.prototype.name = 'MissingDataException';
+ MissingDataException.constructor = MissingDataException;
+
+ return MissingDataException;
+})();
+
+var XRefParseException = (function XRefParseExceptionClosure() {
+ function XRefParseException(msg) {
+ this.message = msg;
+ }
+
+ XRefParseException.prototype = new Error();
+ XRefParseException.prototype.name = 'XRefParseException';
+ XRefParseException.constructor = XRefParseException;
+
+ return XRefParseException;
+})();
+
+
+function bytesToString(bytes) {
+ var length = bytes.length;
+ var MAX_ARGUMENT_COUNT = 8192;
+ if (length < MAX_ARGUMENT_COUNT) {
+ return String.fromCharCode.apply(null, bytes);
+ }
+ var strBuf = [];
+ for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
+ var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
+ var chunk = bytes.subarray(i, chunkEnd);
+ strBuf.push(String.fromCharCode.apply(null, chunk));
+ }
+ return strBuf.join('');
+}
+
+function stringToBytes(str) {
+ var length = str.length;
+ var bytes = new Uint8Array(length);
+ for (var i = 0; i < length; ++i) {
+ bytes[i] = str.charCodeAt(i) & 0xFF;
+ }
+ return bytes;
+}
+
+function string32(value) {
+ return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff,
+ (value >> 8) & 0xff, value & 0xff);
+}
+
+function log2(x) {
+ var n = 1, i = 0;
+ while (x > n) {
+ n <<= 1;
+ i++;
+ }
+ return i;
+}
+
+function readInt8(data, start) {
+ return (data[start] << 24) >> 24;
+}
+
+function readUint16(data, offset) {
+ return (data[offset] << 8) | data[offset + 1];
+}
+
+function readUint32(data, offset) {
+ return ((data[offset] << 24) | (data[offset + 1] << 16) |
+ (data[offset + 2] << 8) | data[offset + 3]) >>> 0;
+}
+
+// Lazy test the endianness of the platform
+// NOTE: This will be 'true' for simulated TypedArrays
+function isLittleEndian() {
+ var buffer8 = new Uint8Array(2);
+ buffer8[0] = 1;
+ var buffer16 = new Uint16Array(buffer8.buffer);
+ return (buffer16[0] === 1);
+}
+
+Object.defineProperty(PDFJS, 'isLittleEndian', {
+ configurable: true,
+ get: function PDFJS_isLittleEndian() {
+ return shadow(PDFJS, 'isLittleEndian', isLittleEndian());
+ }
+});
+
+ // Lazy test if the userAgant support CanvasTypedArrays
+function hasCanvasTypedArrays() {
+ var canvas = document.createElement('canvas');
+ canvas.width = canvas.height = 1;
+ var ctx = canvas.getContext('2d');
+ var imageData = ctx.createImageData(1, 1);
+ return (typeof imageData.data.buffer !== 'undefined');
+}
+
+Object.defineProperty(PDFJS, 'hasCanvasTypedArrays', {
+ configurable: true,
+ get: function PDFJS_hasCanvasTypedArrays() {
+ return shadow(PDFJS, 'hasCanvasTypedArrays', hasCanvasTypedArrays());
+ }
+});
+
+var Uint32ArrayView = (function Uint32ArrayViewClosure() {
+
+ function Uint32ArrayView(buffer, length) {
+ this.buffer = buffer;
+ this.byteLength = buffer.length;
+ this.length = length === undefined ? (this.byteLength >> 2) : length;
+ ensureUint32ArrayViewProps(this.length);
+ }
+ Uint32ArrayView.prototype = Object.create(null);
+
+ var uint32ArrayViewSetters = 0;
+ function createUint32ArrayProp(index) {
+ return {
+ get: function () {
+ var buffer = this.buffer, offset = index << 2;
+ return (buffer[offset] | (buffer[offset + 1] << 8) |
+ (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24)) >>> 0;
+ },
+ set: function (value) {
+ var buffer = this.buffer, offset = index << 2;
+ buffer[offset] = value & 255;
+ buffer[offset + 1] = (value >> 8) & 255;
+ buffer[offset + 2] = (value >> 16) & 255;
+ buffer[offset + 3] = (value >>> 24) & 255;
+ }
+ };
+ }
+
+ function ensureUint32ArrayViewProps(length) {
+ while (uint32ArrayViewSetters < length) {
+ Object.defineProperty(Uint32ArrayView.prototype,
+ uint32ArrayViewSetters,
+ createUint32ArrayProp(uint32ArrayViewSetters));
+ uint32ArrayViewSetters++;
+ }
+ }
+
+ return Uint32ArrayView;
+})();
+
+var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
+
+var Util = PDFJS.Util = (function UtilClosure() {
+ function Util() {}
+
+ var rgbBuf = ['rgb(', 0, ',', 0, ',', 0, ')'];
+
+ // makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids
+ // creating many intermediate strings.
+ Util.makeCssRgb = function Util_makeCssRgb(rgb) {
+ rgbBuf[1] = rgb[0];
+ rgbBuf[3] = rgb[1];
+ rgbBuf[5] = rgb[2];
+ return rgbBuf.join('');
+ };
+
+ // Concatenates two transformation matrices together and returns the result.
+ Util.transform = function Util_transform(m1, m2) {
+ return [
+ m1[0] * m2[0] + m1[2] * m2[1],
+ m1[1] * m2[0] + m1[3] * m2[1],
+ m1[0] * m2[2] + m1[2] * m2[3],
+ m1[1] * m2[2] + m1[3] * m2[3],
+ m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
+ m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
+ ];
+ };
+
+ // For 2d affine transforms
+ Util.applyTransform = function Util_applyTransform(p, m) {
+ var xt = p[0] * m[0] + p[1] * m[2] + m[4];
+ var yt = p[0] * m[1] + p[1] * m[3] + m[5];
+ return [xt, yt];
+ };
+
+ Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+ var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+ return [xt, yt];
+ };
+
+ // Applies the transform to the rectangle and finds the minimum axially
+ // aligned bounding box.
+ Util.getAxialAlignedBoundingBox =
+ function Util_getAxialAlignedBoundingBox(r, m) {
+
+ var p1 = Util.applyTransform(r, m);
+ var p2 = Util.applyTransform(r.slice(2, 4), m);
+ var p3 = Util.applyTransform([r[0], r[3]], m);
+ var p4 = Util.applyTransform([r[2], r[1]], m);
+ return [
+ Math.min(p1[0], p2[0], p3[0], p4[0]),
+ Math.min(p1[1], p2[1], p3[1], p4[1]),
+ Math.max(p1[0], p2[0], p3[0], p4[0]),
+ Math.max(p1[1], p2[1], p3[1], p4[1])
+ ];
+ };
+
+ Util.inverseTransform = function Util_inverseTransform(m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
+ (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
+ };
+
+ // Apply a generic 3d matrix M on a 3-vector v:
+ // | a b c | | X |
+ // | d e f | x | Y |
+ // | g h i | | Z |
+ // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
+ // with v as [X,Y,Z]
+ Util.apply3dTransform = function Util_apply3dTransform(m, v) {
+ return [
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
+ ];
+ };
+
+ // This calculation uses Singular Value Decomposition.
+ // The SVD can be represented with formula A = USV. We are interested in the
+ // matrix S here because it represents the scale values.
+ Util.singularValueDecompose2dScale =
+ function Util_singularValueDecompose2dScale(m) {
+
+ var transpose = [m[0], m[2], m[1], m[3]];
+
+ // Multiply matrix m with its transpose.
+ var a = m[0] * transpose[0] + m[1] * transpose[2];
+ var b = m[0] * transpose[1] + m[1] * transpose[3];
+ var c = m[2] * transpose[0] + m[3] * transpose[2];
+ var d = m[2] * transpose[1] + m[3] * transpose[3];
+
+ // Solve the second degree polynomial to get roots.
+ var first = (a + d) / 2;
+ var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
+ var sx = first + second || 1;
+ var sy = first - second || 1;
+
+ // Scale values are the square roots of the eigenvalues.
+ return [Math.sqrt(sx), Math.sqrt(sy)];
+ };
+
+ // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
+ // For coordinate systems whose origin lies in the bottom-left, this
+ // means normalization to (BL,TR) ordering. For systems with origin in the
+ // top-left, this means (TL,BR) ordering.
+ Util.normalizeRect = function Util_normalizeRect(rect) {
+ var r = rect.slice(0); // clone rect
+ if (rect[0] > rect[2]) {
+ r[0] = rect[2];
+ r[2] = rect[0];
+ }
+ if (rect[1] > rect[3]) {
+ r[1] = rect[3];
+ r[3] = rect[1];
+ }
+ return r;
+ };
+
+ // Returns a rectangle [x1, y1, x2, y2] corresponding to the
+ // intersection of rect1 and rect2. If no intersection, returns 'false'
+ // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
+ Util.intersect = function Util_intersect(rect1, rect2) {
+ function compare(a, b) {
+ return a - b;
+ }
+
+ // Order points along the axes
+ var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
+ orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
+ result = [];
+
+ rect1 = Util.normalizeRect(rect1);
+ rect2 = Util.normalizeRect(rect2);
+
+ // X: first and second points belong to different rectangles?
+ if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) ||
+ (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) {
+ // Intersection must be between second and third points
+ result[0] = orderedX[1];
+ result[2] = orderedX[2];
+ } else {
+ return false;
+ }
+
+ // Y: first and second points belong to different rectangles?
+ if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) ||
+ (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) {
+ // Intersection must be between second and third points
+ result[1] = orderedY[1];
+ result[3] = orderedY[2];
+ } else {
+ return false;
+ }
+
+ return result;
+ };
+
+ Util.sign = function Util_sign(num) {
+ return num < 0 ? -1 : 1;
+ };
+
+ Util.appendToArray = function Util_appendToArray(arr1, arr2) {
+ Array.prototype.push.apply(arr1, arr2);
+ };
+
+ Util.prependToArray = function Util_prependToArray(arr1, arr2) {
+ Array.prototype.unshift.apply(arr1, arr2);
+ };
+
+ Util.extendObj = function extendObj(obj1, obj2) {
+ for (var key in obj2) {
+ obj1[key] = obj2[key];
+ }
+ };
+
+ Util.getInheritableProperty = function Util_getInheritableProperty(dict,
+ name) {
+ while (dict && !dict.has(name)) {
+ dict = dict.get('Parent');
+ }
+ if (!dict) {
+ return null;
+ }
+ return dict.get(name);
+ };
+
+ Util.inherit = function Util_inherit(sub, base, prototype) {
+ sub.prototype = Object.create(base.prototype);
+ sub.prototype.constructor = sub;
+ for (var prop in prototype) {
+ sub.prototype[prop] = prototype[prop];
+ }
+ };
+
+ Util.loadScript = function Util_loadScript(src, callback) {
+ var script = document.createElement('script');
+ var loaded = false;
+ script.setAttribute('src', src);
+ if (callback) {
+ script.onload = function() {
+ if (!loaded) {
+ callback();
+ }
+ loaded = true;
+ };
+ }
+ document.getElementsByTagName('head')[0].appendChild(script);
+ };
+
+ return Util;
+})();
+
+/**
+ * PDF page viewport created based on scale, rotation and offset.
+ * @class
+ * @alias PDFJS.PageViewport
+ */
+var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
+ /**
+ * @constructor
+ * @private
+ * @param viewBox {Array} xMin, yMin, xMax and yMax coordinates.
+ * @param scale {number} scale of the viewport.
+ * @param rotation {number} rotations of the viewport in degrees.
+ * @param offsetX {number} offset X
+ * @param offsetY {number} offset Y
+ * @param dontFlip {boolean} if true, axis Y will not be flipped.
+ */
+ function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
+ this.viewBox = viewBox;
+ this.scale = scale;
+ this.rotation = rotation;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+
+ // creating transform to convert pdf coordinate system to the normal
+ // canvas like coordinates taking in account scale and rotation
+ var centerX = (viewBox[2] + viewBox[0]) / 2;
+ var centerY = (viewBox[3] + viewBox[1]) / 2;
+ var rotateA, rotateB, rotateC, rotateD;
+ rotation = rotation % 360;
+ rotation = rotation < 0 ? rotation + 360 : rotation;
+ switch (rotation) {
+ case 180:
+ rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
+ break;
+ case 90:
+ rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
+ break;
+ case 270:
+ rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
+ break;
+ //case 0:
+ default:
+ rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
+ break;
+ }
+
+ if (dontFlip) {
+ rotateC = -rotateC; rotateD = -rotateD;
+ }
+
+ var offsetCanvasX, offsetCanvasY;
+ var width, height;
+ if (rotateA === 0) {
+ offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+ width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ } else {
+ offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+ width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ }
+ // creating transform for the following operations:
+ // translate(-centerX, -centerY), rotate and flip vertically,
+ // scale, and translate(offsetCanvasX, offsetCanvasY)
+ this.transform = [
+ rotateA * scale,
+ rotateB * scale,
+ rotateC * scale,
+ rotateD * scale,
+ offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+ offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+ ];
+
+ this.width = width;
+ this.height = height;
+ this.fontScale = scale;
+ }
+ PageViewport.prototype = /** @lends PDFJS.PageViewport.prototype */ {
+ /**
+ * Clones viewport with additional properties.
+ * @param args {Object} (optional) If specified, may contain the 'scale' or
+ * 'rotation' properties to override the corresponding properties in
+ * the cloned viewport.
+ * @returns {PDFJS.PageViewport} Cloned viewport.
+ */
+ clone: function PageViewPort_clone(args) {
+ args = args || {};
+ var scale = 'scale' in args ? args.scale : this.scale;
+ var rotation = 'rotation' in args ? args.rotation : this.rotation;
+ return new PageViewport(this.viewBox.slice(), scale, rotation,
+ this.offsetX, this.offsetY, args.dontFlip);
+ },
+ /**
+ * Converts PDF point to the viewport coordinates. For examples, useful for
+ * converting PDF location into canvas pixel coordinates.
+ * @param x {number} X coordinate.
+ * @param y {number} Y coordinate.
+ * @returns {Object} Object that contains 'x' and 'y' properties of the
+ * point in the viewport coordinate space.
+ * @see {@link convertToPdfPoint}
+ * @see {@link convertToViewportRectangle}
+ */
+ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+ return Util.applyTransform([x, y], this.transform);
+ },
+ /**
+ * Converts PDF rectangle to the viewport coordinates.
+ * @param rect {Array} xMin, yMin, xMax and yMax coordinates.
+ * @returns {Array} Contains corresponding coordinates of the rectangle
+ * in the viewport coordinate space.
+ * @see {@link convertToViewportPoint}
+ */
+ convertToViewportRectangle:
+ function PageViewport_convertToViewportRectangle(rect) {
+ var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
+ var br = Util.applyTransform([rect[2], rect[3]], this.transform);
+ return [tl[0], tl[1], br[0], br[1]];
+ },
+ /**
+ * Converts viewport coordinates to the PDF location. For examples, useful
+ * for converting canvas pixel location into PDF one.
+ * @param x {number} X coordinate.
+ * @param y {number} Y coordinate.
+ * @returns {Object} Object that contains 'x' and 'y' properties of the
+ * point in the PDF coordinate space.
+ * @see {@link convertToViewportPoint}
+ */
+ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+ return Util.applyInverseTransform([x, y], this.transform);
+ }
+ };
+ return PageViewport;
+})();
+
+var PDFStringTranslateTable = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014,
+ 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C,
+ 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160,
+ 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
+];
+
+function stringToPDFString(str) {
+ var i, n = str.length, strBuf = [];
+ if (str[0] === '\xFE' && str[1] === '\xFF') {
+ // UTF16BE BOM
+ for (i = 2; i < n; i += 2) {
+ strBuf.push(String.fromCharCode(
+ (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)));
+ }
+ } else {
+ for (i = 0; i < n; ++i) {
+ var code = PDFStringTranslateTable[str.charCodeAt(i)];
+ strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
+ }
+ }
+ return strBuf.join('');
+}
+
+function stringToUTF8String(str) {
+ return decodeURIComponent(escape(str));
+}
+
+function isEmptyObj(obj) {
+ for (var key in obj) {
+ return false;
+ }
+ return true;
+}
+
+function isBool(v) {
+ return typeof v === 'boolean';
+}
+
+function isInt(v) {
+ return typeof v === 'number' && ((v | 0) === v);
+}
+
+function isNum(v) {
+ return typeof v === 'number';
+}
+
+function isString(v) {
+ return typeof v === 'string';
+}
+
+function isNull(v) {
+ return v === null;
+}
+
+function isName(v) {
+ return v instanceof Name;
+}
+
+function isCmd(v, cmd) {
+ return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
+}
+
+function isDict(v, type) {
+ if (!(v instanceof Dict)) {
+ return false;
+ }
+ if (!type) {
+ return true;
+ }
+ var dictType = v.get('Type');
+ return isName(dictType) && dictType.name === type;
+}
+
+function isArray(v) {
+ return v instanceof Array;
+}
+
+function isStream(v) {
+ return typeof v === 'object' && v !== null && v.getBytes !== undefined;
+}
+
+function isArrayBuffer(v) {
+ return typeof v === 'object' && v !== null && v.byteLength !== undefined;
+}
+
+function isRef(v) {
+ return v instanceof Ref;
+}
+
+/**
+ * Promise Capability object.
+ *
+ * @typedef {Object} PromiseCapability
+ * @property {Promise} promise - A promise object.
+ * @property {function} resolve - Fullfills the promise.
+ * @property {function} reject - Rejects the promise.
+ */
+
+/**
+ * Creates a promise capability object.
+ * @alias PDFJS.createPromiseCapability
+ *
+ * @return {PromiseCapability} A capability object contains:
+ * - a Promise, resolve and reject methods.
+ */
+function createPromiseCapability() {
+ var capability = {};
+ capability.promise = new Promise(function (resolve, reject) {
+ capability.resolve = resolve;
+ capability.reject = reject;
+ });
+ return capability;
+}
+
+PDFJS.createPromiseCapability = createPromiseCapability;
+
+/**
+ * Polyfill for Promises:
+ * The following promise implementation tries to generally implement the
+ * Promise/A+ spec. Some notable differences from other promise libaries are:
+ * - There currently isn't a seperate deferred and promise object.
+ * - Unhandled rejections eventually show an error if they aren't handled.
+ *
+ * Based off of the work in:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
+ */
+(function PromiseClosure() {
+ if (globalScope.Promise) {
+ // Promises existing in the DOM/Worker, checking presence of all/resolve
+ if (typeof globalScope.Promise.all !== 'function') {
+ globalScope.Promise.all = function (iterable) {
+ var count = 0, results = [], resolve, reject;
+ var promise = new globalScope.Promise(function (resolve_, reject_) {
+ resolve = resolve_;
+ reject = reject_;
+ });
+ iterable.forEach(function (p, i) {
+ count++;
+ p.then(function (result) {
+ results[i] = result;
+ count--;
+ if (count === 0) {
+ resolve(results);
+ }
+ }, reject);
+ });
+ if (count === 0) {
+ resolve(results);
+ }
+ return promise;
+ };
+ }
+ if (typeof globalScope.Promise.resolve !== 'function') {
+ globalScope.Promise.resolve = function (value) {
+ return new globalScope.Promise(function (resolve) { resolve(value); });
+ };
+ }
+ if (typeof globalScope.Promise.reject !== 'function') {
+ globalScope.Promise.reject = function (reason) {
+ return new globalScope.Promise(function (resolve, reject) {
+ reject(reason);
+ });
+ };
+ }
+ if (typeof globalScope.Promise.prototype.catch !== 'function') {
+ globalScope.Promise.prototype.catch = function (onReject) {
+ return globalScope.Promise.prototype.then(undefined, onReject);
+ };
+ }
+ return;
+ }
+ var STATUS_PENDING = 0;
+ var STATUS_RESOLVED = 1;
+ var STATUS_REJECTED = 2;
+
+ // In an attempt to avoid silent exceptions, unhandled rejections are
+ // tracked and if they aren't handled in a certain amount of time an
+ // error is logged.
+ var REJECTION_TIMEOUT = 500;
+
+ var HandlerManager = {
+ handlers: [],
+ running: false,
+ unhandledRejections: [],
+ pendingRejectionCheck: false,
+
+ scheduleHandlers: function scheduleHandlers(promise) {
+ if (promise._status === STATUS_PENDING) {
+ return;
+ }
+
+ this.handlers = this.handlers.concat(promise._handlers);
+ promise._handlers = [];
+
+ if (this.running) {
+ return;
+ }
+ this.running = true;
+
+ setTimeout(this.runHandlers.bind(this), 0);
+ },
+
+ runHandlers: function runHandlers() {
+ var RUN_TIMEOUT = 1; // ms
+ var timeoutAt = Date.now() + RUN_TIMEOUT;
+ while (this.handlers.length > 0) {
+ var handler = this.handlers.shift();
+
+ var nextStatus = handler.thisPromise._status;
+ var nextValue = handler.thisPromise._value;
+
+ try {
+ if (nextStatus === STATUS_RESOLVED) {
+ if (typeof handler.onResolve === 'function') {
+ nextValue = handler.onResolve(nextValue);
+ }
+ } else if (typeof handler.onReject === 'function') {
+ nextValue = handler.onReject(nextValue);
+ nextStatus = STATUS_RESOLVED;
+
+ if (handler.thisPromise._unhandledRejection) {
+ this.removeUnhandeledRejection(handler.thisPromise);
+ }
+ }
+ } catch (ex) {
+ nextStatus = STATUS_REJECTED;
+ nextValue = ex;
+ }
+
+ handler.nextPromise._updateStatus(nextStatus, nextValue);
+ if (Date.now() >= timeoutAt) {
+ break;
+ }
+ }
+
+ if (this.handlers.length > 0) {
+ setTimeout(this.runHandlers.bind(this), 0);
+ return;
+ }
+
+ this.running = false;
+ },
+
+ addUnhandledRejection: function addUnhandledRejection(promise) {
+ this.unhandledRejections.push({
+ promise: promise,
+ time: Date.now()
+ });
+ this.scheduleRejectionCheck();
+ },
+
+ removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
+ promise._unhandledRejection = false;
+ for (var i = 0; i < this.unhandledRejections.length; i++) {
+ if (this.unhandledRejections[i].promise === promise) {
+ this.unhandledRejections.splice(i);
+ i--;
+ }
+ }
+ },
+
+ scheduleRejectionCheck: function scheduleRejectionCheck() {
+ if (this.pendingRejectionCheck) {
+ return;
+ }
+ this.pendingRejectionCheck = true;
+ setTimeout(function rejectionCheck() {
+ this.pendingRejectionCheck = false;
+ var now = Date.now();
+ for (var i = 0; i < this.unhandledRejections.length; i++) {
+ if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
+ var unhandled = this.unhandledRejections[i].promise._value;
+ var msg = 'Unhandled rejection: ' + unhandled;
+ if (unhandled.stack) {
+ msg += '\n' + unhandled.stack;
+ }
+ warn(msg);
+ this.unhandledRejections.splice(i);
+ i--;
+ }
+ }
+ if (this.unhandledRejections.length) {
+ this.scheduleRejectionCheck();
+ }
+ }.bind(this), REJECTION_TIMEOUT);
+ }
+ };
+
+ function Promise(resolver) {
+ this._status = STATUS_PENDING;
+ this._handlers = [];
+ try {
+ resolver.call(this, this._resolve.bind(this), this._reject.bind(this));
+ } catch (e) {
+ this._reject(e);
+ }
+ }
+ /**
+ * Builds a promise that is resolved when all the passed in promises are
+ * resolved.
+ * @param {array} array of data and/or promises to wait for.
+ * @return {Promise} New dependant promise.
+ */
+ Promise.all = function Promise_all(promises) {
+ var resolveAll, rejectAll;
+ var deferred = new Promise(function (resolve, reject) {
+ resolveAll = resolve;
+ rejectAll = reject;
+ });
+ var unresolved = promises.length;
+ var results = [];
+ if (unresolved === 0) {
+ resolveAll(results);
+ return deferred;
+ }
+ function reject(reason) {
+ if (deferred._status === STATUS_REJECTED) {
+ return;
+ }
+ results = [];
+ rejectAll(reason);
+ }
+ for (var i = 0, ii = promises.length; i < ii; ++i) {
+ var promise = promises[i];
+ var resolve = (function(i) {
+ return function(value) {
+ if (deferred._status === STATUS_REJECTED) {
+ return;
+ }
+ results[i] = value;
+ unresolved--;
+ if (unresolved === 0) {
+ resolveAll(results);
+ }
+ };
+ })(i);
+ if (Promise.isPromise(promise)) {
+ promise.then(resolve, reject);
+ } else {
+ resolve(promise);
+ }
+ }
+ return deferred;
+ };
+
+ /**
+ * Checks if the value is likely a promise (has a 'then' function).
+ * @return {boolean} true if value is thenable
+ */
+ Promise.isPromise = function Promise_isPromise(value) {
+ return value && typeof value.then === 'function';
+ };
+
+ /**
+ * Creates resolved promise
+ * @param value resolve value
+ * @returns {Promise}
+ */
+ Promise.resolve = function Promise_resolve(value) {
+ return new Promise(function (resolve) { resolve(value); });
+ };
+
+ /**
+ * Creates rejected promise
+ * @param reason rejection value
+ * @returns {Promise}
+ */
+ Promise.reject = function Promise_reject(reason) {
+ return new Promise(function (resolve, reject) { reject(reason); });
+ };
+
+ Promise.prototype = {
+ _status: null,
+ _value: null,
+ _handlers: null,
+ _unhandledRejection: null,
+
+ _updateStatus: function Promise__updateStatus(status, value) {
+ if (this._status === STATUS_RESOLVED ||
+ this._status === STATUS_REJECTED) {
+ return;
+ }
+
+ if (status === STATUS_RESOLVED &&
+ Promise.isPromise(value)) {
+ value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
+ this._updateStatus.bind(this, STATUS_REJECTED));
+ return;
+ }
+
+ this._status = status;
+ this._value = value;
+
+ if (status === STATUS_REJECTED && this._handlers.length === 0) {
+ this._unhandledRejection = true;
+ HandlerManager.addUnhandledRejection(this);
+ }
+
+ HandlerManager.scheduleHandlers(this);
+ },
+
+ _resolve: function Promise_resolve(value) {
+ this._updateStatus(STATUS_RESOLVED, value);
+ },
+
+ _reject: function Promise_reject(reason) {
+ this._updateStatus(STATUS_REJECTED, reason);
+ },
+
+ then: function Promise_then(onResolve, onReject) {
+ var nextPromise = new Promise(function (resolve, reject) {
+ this.resolve = resolve;
+ this.reject = reject;
+ });
+ this._handlers.push({
+ thisPromise: this,
+ onResolve: onResolve,
+ onReject: onReject,
+ nextPromise: nextPromise
+ });
+ HandlerManager.scheduleHandlers(this);
+ return nextPromise;
+ },
+
+ catch: function Promise_catch(onReject) {
+ return this.then(undefined, onReject);
+ }
+ };
+
+ globalScope.Promise = Promise;
+})();
+
+var StatTimer = (function StatTimerClosure() {
+ function rpad(str, pad, length) {
+ while (str.length < length) {
+ str += pad;
+ }
+ return str;
+ }
+ function StatTimer() {
+ this.started = {};
+ this.times = [];
+ this.enabled = true;
+ }
+ StatTimer.prototype = {
+ time: function StatTimer_time(name) {
+ if (!this.enabled) {
+ return;
+ }
+ if (name in this.started) {
+ warn('Timer is already running for ' + name);
+ }
+ this.started[name] = Date.now();
+ },
+ timeEnd: function StatTimer_timeEnd(name) {
+ if (!this.enabled) {
+ return;
+ }
+ if (!(name in this.started)) {
+ warn('Timer has not been started for ' + name);
+ }
+ this.times.push({
+ 'name': name,
+ 'start': this.started[name],
+ 'end': Date.now()
+ });
+ // Remove timer from started so it can be called again.
+ delete this.started[name];
+ },
+ toString: function StatTimer_toString() {
+ var i, ii;
+ var times = this.times;
+ var out = '';
+ // Find the longest name for padding purposes.
+ var longest = 0;
+ for (i = 0, ii = times.length; i < ii; ++i) {
+ var name = times[i]['name'];
+ if (name.length > longest) {
+ longest = name.length;
+ }
+ }
+ for (i = 0, ii = times.length; i < ii; ++i) {
+ var span = times[i];
+ var duration = span.end - span.start;
+ out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
+ }
+ return out;
+ }
+ };
+ return StatTimer;
+})();
+
+PDFJS.createBlob = function createBlob(data, contentType) {
+ if (typeof Blob !== 'undefined') {
+ return new Blob([data], { type: contentType });
+ }
+ // Blob builder is deprecated in FF14 and removed in FF18.
+ var bb = new MozBlobBuilder();
+ bb.append(data);
+ return bb.getBlob(contentType);
+};
+
+PDFJS.createObjectURL = (function createObjectURLClosure() {
+ // Blob/createObjectURL is not available, falling back to data schema.
+ var digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+ return function createObjectURL(data, contentType) {
+ if (!PDFJS.disableCreateObjectURL &&
+ typeof URL !== 'undefined' && URL.createObjectURL) {
+ var blob = PDFJS.createBlob(data, contentType);
+ return URL.createObjectURL(blob);
+ }
+
+ var buffer = 'data:' + contentType + ';base64,';
+ for (var i = 0, ii = data.length; i < ii; i += 3) {
+ var b1 = data[i] & 0xFF;
+ var b2 = data[i + 1] & 0xFF;
+ var b3 = data[i + 2] & 0xFF;
+ var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+ var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+ var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
+ buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
+ }
+ return buffer;
+ };
+})();
+
+function MessageHandler(name, comObj) {
+ this.name = name;
+ this.comObj = comObj;
+ this.callbackIndex = 1;
+ this.postMessageTransfers = true;
+ var callbacksCapabilities = this.callbacksCapabilities = {};
+ var ah = this.actionHandler = {};
+
+ ah['console_log'] = [function ahConsoleLog(data) {
+ console.log.apply(console, data);
+ }];
+ ah['console_error'] = [function ahConsoleError(data) {
+ console.error.apply(console, data);
+ }];
+ ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) {
+ UnsupportedManager.notify(data);
+ }];
+
+ comObj.onmessage = function messageHandlerComObjOnMessage(event) {
+ var data = event.data;
+ if (data.isReply) {
+ var callbackId = data.callbackId;
+ if (data.callbackId in callbacksCapabilities) {
+ var callback = callbacksCapabilities[callbackId];
+ delete callbacksCapabilities[callbackId];
+ if ('error' in data) {
+ callback.reject(data.error);
+ } else {
+ callback.resolve(data.data);
+ }
+ } else {
+ error('Cannot resolve callback ' + callbackId);
+ }
+ } else if (data.action in ah) {
+ var action = ah[data.action];
+ if (data.callbackId) {
+ Promise.resolve().then(function () {
+ return action[0].call(action[1], data.data);
+ }).then(function (result) {
+ comObj.postMessage({
+ isReply: true,
+ callbackId: data.callbackId,
+ data: result
+ });
+ }, function (reason) {
+ comObj.postMessage({
+ isReply: true,
+ callbackId: data.callbackId,
+ error: reason
+ });
+ });
+ } else {
+ action[0].call(action[1], data.data);
+ }
+ } else {
+ error('Unknown action from worker: ' + data.action);
+ }
+ };
+}
+
+MessageHandler.prototype = {
+ on: function messageHandlerOn(actionName, handler, scope) {
+ var ah = this.actionHandler;
+ if (ah[actionName]) {
+ error('There is already an actionName called "' + actionName + '"');
+ }
+ ah[actionName] = [handler, scope];
+ },
+ /**
+ * Sends a message to the comObj to invoke the action with the supplied data.
+ * @param {String} actionName Action to call.
+ * @param {JSON} data JSON data to send.
+ * @param {Array} [transfers] Optional list of transfers/ArrayBuffers
+ */
+ send: function messageHandlerSend(actionName, data, transfers) {
+ var message = {
+ action: actionName,
+ data: data
+ };
+ this.postMessage(message, transfers);
+ },
+ /**
+ * Sends a message to the comObj to invoke the action with the supplied data.
+ * Expects that other side will callback with the response.
+ * @param {String} actionName Action to call.
+ * @param {JSON} data JSON data to send.
+ * @param {Array} [transfers] Optional list of transfers/ArrayBuffers.
+ * @returns {Promise} Promise to be resolved with response data.
+ */
+ sendWithPromise:
+ function messageHandlerSendWithPromise(actionName, data, transfers) {
+ var callbackId = this.callbackIndex++;
+ var message = {
+ action: actionName,
+ data: data,
+ callbackId: callbackId
+ };
+ var capability = createPromiseCapability();
+ this.callbacksCapabilities[callbackId] = capability;
+ try {
+ this.postMessage(message, transfers);
+ } catch (e) {
+ capability.reject(e);
+ }
+ return capability.promise;
+ },
+ /**
+ * Sends raw message to the comObj.
+ * @private
+ * @param message {Object} Raw message.
+ * @param transfers List of transfers/ArrayBuffers, or undefined.
+ */
+ postMessage: function (message, transfers) {
+ if (transfers && this.postMessageTransfers) {
+ this.comObj.postMessage(message, transfers);
+ } else {
+ this.comObj.postMessage(message);
+ }
+ }
+};
+
+function loadJpegStream(id, imageUrl, objs) {
+ var img = new Image();
+ img.onload = (function loadJpegStream_onloadClosure() {
+ objs.resolve(id, img);
+ });
+ img.onerror = (function loadJpegStream_onerrorClosure() {
+ objs.resolve(id, null);
+ warn('Error during JPEG image loading');
+ });
+ img.src = imageUrl;
+}
+
+
+/**
+ * The maximum allowed image size in total pixels e.g. width * height. Images
+ * above this value will not be drawn. Use -1 for no limit.
+ * @var {number}
+ */
+PDFJS.maxImageSize = (PDFJS.maxImageSize === undefined ?
+ -1 : PDFJS.maxImageSize);
+
+/**
+ * The url of where the predefined Adobe CMaps are located. Include trailing
+ * slash.
+ * @var {string}
+ */
+PDFJS.cMapUrl = (PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl);
+
+/**
+ * Specifies if CMaps are binary packed.
+ * @var {boolean}
+ */
+PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked;
+
+/*
+ * By default fonts are converted to OpenType fonts and loaded via font face
+ * rules. If disabled, the font will be rendered using a built in font renderer
+ * that constructs the glyphs with primitive path commands.
+ * @var {boolean}
+ */
+PDFJS.disableFontFace = (PDFJS.disableFontFace === undefined ?
+ false : PDFJS.disableFontFace);
+
+/**
+ * Path for image resources, mainly for annotation icons. Include trailing
+ * slash.
+ * @var {string}
+ */
+PDFJS.imageResourcesPath = (PDFJS.imageResourcesPath === undefined ?
+ '' : PDFJS.imageResourcesPath);
+
+/**
+ * Disable the web worker and run all code on the main thread. This will happen
+ * automatically if the browser doesn't support workers or sending typed arrays
+ * to workers.
+ * @var {boolean}
+ */
+PDFJS.disableWorker = (PDFJS.disableWorker === undefined ?
+ false : PDFJS.disableWorker);
+
+/**
+ * Path and filename of the worker file. Required when the worker is enabled in
+ * development mode. If unspecified in the production build, the worker will be
+ * loaded based on the location of the pdf.js file.
+ * @var {string}
+ */
+PDFJS.workerSrc = (PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc);
+
+/**
+ * Disable range request loading of PDF files. When enabled and if the server
+ * supports partial content requests then the PDF will be fetched in chunks.
+ * Enabled (false) by default.
+ * @var {boolean}
+ */
+PDFJS.disableRange = (PDFJS.disableRange === undefined ?
+ false : PDFJS.disableRange);
+
+/**
+ * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
+ * will automatically keep fetching more data even if it isn't needed to display
+ * the current page. This default behavior can be disabled.
+ * @var {boolean}
+ */
+PDFJS.disableAutoFetch = (PDFJS.disableAutoFetch === undefined ?
+ false : PDFJS.disableAutoFetch);
+
+/**
+ * Enables special hooks for debugging PDF.js.
+ * @var {boolean}
+ */
+PDFJS.pdfBug = (PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug);
+
+/**
+ * Enables transfer usage in postMessage for ArrayBuffers.
+ * @var {boolean}
+ */
+PDFJS.postMessageTransfers = (PDFJS.postMessageTransfers === undefined ?
+ true : PDFJS.postMessageTransfers);
+
+/**
+ * Disables URL.createObjectURL usage.
+ * @var {boolean}
+ */
+PDFJS.disableCreateObjectURL = (PDFJS.disableCreateObjectURL === undefined ?
+ false : PDFJS.disableCreateObjectURL);
+
+/**
+ * Disables WebGL usage.
+ * @var {boolean}
+ */
+PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ?
+ true : PDFJS.disableWebGL);
+
+/**
+ * Enables CSS only zooming.
+ * @var {boolean}
+ */
+PDFJS.useOnlyCssZoom = (PDFJS.useOnlyCssZoom === undefined ?
+ false : PDFJS.useOnlyCssZoom);
+
+/**
+ * Controls the logging level.
+ * The constants from PDFJS.VERBOSITY_LEVELS should be used:
+ * - errors
+ * - warnings [default]
+ * - infos
+ * @var {number}
+ */
+PDFJS.verbosity = (PDFJS.verbosity === undefined ?
+ PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity);
+
+/**
+ * The maximum supported canvas size in total pixels e.g. width * height.
+ * The default value is 4096 * 4096. Use -1 for no limit.
+ * @var {number}
+ */
+PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ?
+ 16777216 : PDFJS.maxCanvasPixels);
+
+/**
+ * Document initialization / loading parameters object.
+ *
+ * @typedef {Object} DocumentInitParameters
+ * @property {string} url - The URL of the PDF.
+ * @property {TypedArray} data - A typed array with PDF data.
+ * @property {Object} httpHeaders - Basic authentication headers.
+ * @property {boolean} withCredentials - Indicates whether or not cross-site
+ * Access-Control requests should be made using credentials such as cookies
+ * or authorization headers. The default is false.
+ * @property {string} password - For decrypting password-protected PDFs.
+ * @property {TypedArray} initialData - A typed array with the first portion or
+ * all of the pdf data. Used by the extension since some data is already
+ * loaded before the switch to range requests.
+ */
+
+/**
+ * @typedef {Object} PDFDocumentStats
+ * @property {Array} streamTypes - Used stream types in the document (an item
+ * is set to true if specific stream ID was used in the document).
+ * @property {Array} fontTypes - Used font type in the document (an item is set
+ * to true if specific font ID was used in the document).
+ */
+
+/**
+ * This is the main entry point for loading a PDF and interacting with it.
+ * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
+ * is used, which means it must follow the same origin rules that any XHR does
+ * e.g. No cross domain requests without CORS.
+ *
+ * @param {string|TypedArray|DocumentInitParameters} source Can be a url to
+ * where a PDF is located, a typed array (Uint8Array) already populated with
+ * data or parameter object.
+ *
+ * @param {Object} pdfDataRangeTransport is optional. It is used if you want
+ * to manually serve range requests for data in the PDF. See viewer.js for
+ * an example of pdfDataRangeTransport's interface.
+ *
+ * @param {function} passwordCallback is optional. It is used to request a
+ * password if wrong or no password was provided. The callback receives two
+ * parameters: function that needs to be called with new password and reason
+ * (see {PasswordResponses}).
+ *
+ * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy}
+ * object.
+ */
+PDFJS.getDocument = function getDocument(source,
+ pdfDataRangeTransport,
+ passwordCallback,
+ progressCallback) {
+ var workerInitializedCapability, workerReadyCapability, transport;
+
+ if (typeof source === 'string') {
+ source = { url: source };
+ } else if (isArrayBuffer(source)) {
+ source = { data: source };
+ } else if (typeof source !== 'object') {
+ error('Invalid parameter in getDocument, need either Uint8Array, ' +
+ 'string or a parameter object');
+ }
+
+ if (!source.url && !source.data) {
+ error('Invalid parameter array, need either .data or .url');
+ }
+
+ // copy/use all keys as is except 'url' -- full path is required
+ var params = {};
+ for (var key in source) {
+ if (key === 'url' && typeof window !== 'undefined') {
+ params[key] = combineUrl(window.location.href, source[key]);
+ continue;
+ }
+ params[key] = source[key];
+ }
+
+ workerInitializedCapability = createPromiseCapability();
+ workerReadyCapability = createPromiseCapability();
+ transport = new WorkerTransport(workerInitializedCapability,
+ workerReadyCapability, pdfDataRangeTransport,
+ progressCallback);
+ workerInitializedCapability.promise.then(function transportInitialized() {
+ transport.passwordCallback = passwordCallback;
+ transport.fetchDocument(params);
+ });
+ return workerReadyCapability.promise;
+};
+
+/**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ * @class
+ */
+var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
+ function PDFDocumentProxy(pdfInfo, transport) {
+ this.pdfInfo = pdfInfo;
+ this.transport = transport;
+ }
+ PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ {
+ /**
+ * @return {number} Total number of pages the PDF contains.
+ */
+ get numPages() {
+ return this.pdfInfo.numPages;
+ },
+ /**
+ * @return {string} A unique ID to identify a PDF. Not guaranteed to be
+ * unique.
+ */
+ get fingerprint() {
+ return this.pdfInfo.fingerprint;
+ },
+ /**
+ * @param {number} pageNumber The page number to get. The first page is 1.
+ * @return {Promise} A promise that is resolved with a {@link PDFPageProxy}
+ * object.
+ */
+ getPage: function PDFDocumentProxy_getPage(pageNumber) {
+ return this.transport.getPage(pageNumber);
+ },
+ /**
+ * @param {{num: number, gen: number}} ref The page reference. Must have
+ * the 'num' and 'gen' properties.
+ * @return {Promise} A promise that is resolved with the page index that is
+ * associated with the reference.
+ */
+ getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
+ return this.transport.getPageIndex(ref);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named destinations to reference numbers.
+ */
+ getDestinations: function PDFDocumentProxy_getDestinations() {
+ return this.transport.getDestinations();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named attachments to their content.
+ */
+ getAttachments: function PDFDocumentProxy_getAttachments() {
+ return this.transport.getAttachments();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an array of all the
+ * JavaScript strings in the name tree.
+ */
+ getJavaScript: function PDFDocumentProxy_getJavaScript() {
+ return this.transport.getJavaScript();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {Array} that is a
+ * tree outline (if it has one) of the PDF. The tree is in the format of:
+ * [
+ * {
+ * title: string,
+ * bold: boolean,
+ * italic: boolean,
+ * color: rgb array,
+ * dest: dest obj,
+ * items: array of more items like this
+ * },
+ * ...
+ * ].
+ */
+ getOutline: function PDFDocumentProxy_getOutline() {
+ return this.transport.getOutline();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {Object} that has
+ * info and metadata properties. Info is an {Object} filled with anything
+ * available in the information dictionary and similarly metadata is a
+ * {Metadata} object with information from the metadata section of the PDF.
+ */
+ getMetadata: function PDFDocumentProxy_getMetadata() {
+ return this.transport.getMetadata();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a TypedArray that has
+ * the raw data from the PDF.
+ */
+ getData: function PDFDocumentProxy_getData() {
+ return this.transport.getData();
+ },
+ /**
+ * @return {Promise} A promise that is resolved when the document's data
+ * is loaded. It is resolved with an {Object} that contains the length
+ * property that indicates size of the PDF data in bytes.
+ */
+ getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() {
+ return this.transport.downloadInfoCapability.promise;
+ },
+ /**
+ * @returns {Promise} A promise this is resolved with current stats about
+ * document structures (see {@link PDFDocumentStats}).
+ */
+ getStats: function PDFDocumentProxy_getStats() {
+ return this.transport.getStats();
+ },
+ /**
+ * Cleans up resources allocated by the document, e.g. created @font-face.
+ */
+ cleanup: function PDFDocumentProxy_cleanup() {
+ this.transport.startCleanup();
+ },
+ /**
+ * Destroys current document instance and terminates worker.
+ */
+ destroy: function PDFDocumentProxy_destroy() {
+ this.transport.destroy();
+ }
+ };
+ return PDFDocumentProxy;
+})();
+
+/**
+ * Page text content.
+ *
+ * @typedef {Object} TextContent
+ * @property {array} items - array of {@link TextItem}
+ * @property {Object} styles - {@link TextStyles} objects, indexed by font
+ * name.
+ */
+
+/**
+ * Page text content part.
+ *
+ * @typedef {Object} TextItem
+ * @property {string} str - text content.
+ * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'.
+ * @property {array} transform - transformation matrix.
+ * @property {number} width - width in device space.
+ * @property {number} height - height in device space.
+ * @property {string} fontName - font name used by pdf.js for converted font.
+ */
+
+/**
+ * Text style.
+ *
+ * @typedef {Object} TextStyle
+ * @property {number} ascent - font ascent.
+ * @property {number} descent - font descent.
+ * @property {boolean} vertical - text is in vertical mode.
+ * @property {string} fontFamily - possible font family
+ */
+
+/**
+ * Page render parameters.
+ *
+ * @typedef {Object} RenderParameters
+ * @property {Object} canvasContext - A 2D context of a DOM Canvas object.
+ * @property {PDFJS.PageViewport} viewport - Rendering viewport obtained by
+ * calling of PDFPage.getViewport method.
+ * @property {string} intent - Rendering intent, can be 'display' or 'print'
+ * (default value is 'display').
+ * @property {Object} imageLayer - (optional) An object that has beginLayout,
+ * endLayout and appendImage functions.
+ * @property {function} continueCallback - (optional) A function that will be
+ * called each time the rendering is paused. To continue
+ * rendering call the function that is the first argument
+ * to the callback.
+ */
+
+/**
+ * PDF page operator list.
+ *
+ * @typedef {Object} PDFOperatorList
+ * @property {Array} fnArray - Array containing the operator functions.
+ * @property {Array} argsArray - Array containing the arguments of the
+ * functions.
+ */
+
+/**
+ * Proxy to a PDFPage in the worker thread.
+ * @class
+ */
+var PDFPageProxy = (function PDFPageProxyClosure() {
+ function PDFPageProxy(pageIndex, pageInfo, transport) {
+ this.pageIndex = pageIndex;
+ this.pageInfo = pageInfo;
+ this.transport = transport;
+ this.stats = new StatTimer();
+ this.stats.enabled = !!globalScope.PDFJS.enableStats;
+ this.commonObjs = transport.commonObjs;
+ this.objs = new PDFObjects();
+ this.cleanupAfterRender = false;
+ this.pendingDestroy = false;
+ this.intentStates = {};
+ }
+ PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ {
+ /**
+ * @return {number} Page number of the page. First page is 1.
+ */
+ get pageNumber() {
+ return this.pageIndex + 1;
+ },
+ /**
+ * @return {number} The number of degrees the page is rotated clockwise.
+ */
+ get rotate() {
+ return this.pageInfo.rotate;
+ },
+ /**
+ * @return {Object} The reference that points to this page. It has 'num' and
+ * 'gen' properties.
+ */
+ get ref() {
+ return this.pageInfo.ref;
+ },
+ /**
+ * @return {Array} An array of the visible portion of the PDF page in the
+ * user space units - [x1, y1, x2, y2].
+ */
+ get view() {
+ return this.pageInfo.view;
+ },
+ /**
+ * @param {number} scale The desired scale of the viewport.
+ * @param {number} rotate Degrees to rotate the viewport. If omitted this
+ * defaults to the page rotation.
+ * @return {PDFJS.PageViewport} Contains 'width' and 'height' properties
+ * along with transforms required for rendering.
+ */
+ getViewport: function PDFPageProxy_getViewport(scale, rotate) {
+ if (arguments.length < 2) {
+ rotate = this.rotate;
+ }
+ return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {Array} of the
+ * annotation objects.
+ */
+ getAnnotations: function PDFPageProxy_getAnnotations() {
+ if (this.annotationsPromise) {
+ return this.annotationsPromise;
+ }
+
+ var promise = this.transport.getAnnotations(this.pageIndex);
+ this.annotationsPromise = promise;
+ return promise;
+ },
+ /**
+ * Begins the process of rendering a page to the desired context.
+ * @param {RenderParameters} params Page render parameters.
+ * @return {RenderTask} An object that contains the promise, which
+ * is resolved when the page finishes rendering.
+ */
+ render: function PDFPageProxy_render(params) {
+ var stats = this.stats;
+ stats.time('Overall');
+
+ // If there was a pending destroy cancel it so no cleanup happens during
+ // this call to render.
+ this.pendingDestroy = false;
+
+ var renderingIntent = (params.intent === 'print' ? 'print' : 'display');
+
+ if (!this.intentStates[renderingIntent]) {
+ this.intentStates[renderingIntent] = {};
+ }
+ var intentState = this.intentStates[renderingIntent];
+
+ // If there's no displayReadyCapability yet, then the operatorList
+ // was never requested before. Make the request and create the promise.
+ if (!intentState.displayReadyCapability) {
+ intentState.receivingOperatorList = true;
+ intentState.displayReadyCapability = createPromiseCapability();
+ intentState.operatorList = {
+ fnArray: [],
+ argsArray: [],
+ lastChunk: false
+ };
+
+ this.stats.time('Page Request');
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageNumber - 1,
+ intent: renderingIntent
+ });
+ }
+
+ var internalRenderTask = new InternalRenderTask(complete, params,
+ this.objs,
+ this.commonObjs,
+ intentState.operatorList,
+ this.pageNumber);
+ if (!intentState.renderTasks) {
+ intentState.renderTasks = [];
+ }
+ intentState.renderTasks.push(internalRenderTask);
+ var renderTask = new RenderTask(internalRenderTask);
+
+ var self = this;
+ intentState.displayReadyCapability.promise.then(
+ function pageDisplayReadyPromise(transparency) {
+ if (self.pendingDestroy) {
+ complete();
+ return;
+ }
+ stats.time('Rendering');
+ internalRenderTask.initalizeGraphics(transparency);
+ internalRenderTask.operatorListChanged();
+ },
+ function pageDisplayReadPromiseError(reason) {
+ complete(reason);
+ }
+ );
+
+ function complete(error) {
+ var i = intentState.renderTasks.indexOf(internalRenderTask);
+ if (i >= 0) {
+ intentState.renderTasks.splice(i, 1);
+ }
+
+ if (self.cleanupAfterRender) {
+ self.pendingDestroy = true;
+ }
+ self._tryDestroy();
+
+ if (error) {
+ internalRenderTask.capability.reject(error);
+ } else {
+ internalRenderTask.capability.resolve();
+ }
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
+ }
+
+ return renderTask;
+ },
+
+ /**
+ * @return {Promise} A promise resolved with an {@link PDFOperatorList}
+ * object that represents page's operator list.
+ */
+ getOperatorList: function PDFPageProxy_getOperatorList() {
+ function operatorListChanged() {
+ if (intentState.operatorList.lastChunk) {
+ intentState.opListReadCapability.resolve(intentState.operatorList);
+ }
+ }
+
+ var renderingIntent = 'oplist';
+ if (!this.intentStates[renderingIntent]) {
+ this.intentStates[renderingIntent] = {};
+ }
+ var intentState = this.intentStates[renderingIntent];
+
+ if (!intentState.opListReadCapability) {
+ var opListTask = {};
+ opListTask.operatorListChanged = operatorListChanged;
+ intentState.receivingOperatorList = true;
+ intentState.opListReadCapability = createPromiseCapability();
+ intentState.renderTasks = [];
+ intentState.renderTasks.push(opListTask);
+ intentState.operatorList = {
+ fnArray: [],
+ argsArray: [],
+ lastChunk: false
+ };
+
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageIndex,
+ intent: renderingIntent
+ });
+ }
+ return intentState.opListReadCapability.promise;
+ },
+
+ /**
+ * @return {Promise} That is resolved a {@link TextContent}
+ * object that represent the page text content.
+ */
+ getTextContent: function PDFPageProxy_getTextContent() {
+ return this.transport.messageHandler.sendWithPromise('GetTextContent', {
+ pageIndex: this.pageNumber - 1
+ });
+ },
+ /**
+ * Destroys resources allocated by the page.
+ */
+ destroy: function PDFPageProxy_destroy() {
+ this.pendingDestroy = true;
+ this._tryDestroy();
+ },
+ /**
+ * For internal use only. Attempts to clean up if rendering is in a state
+ * where that's possible.
+ * @ignore
+ */
+ _tryDestroy: function PDFPageProxy__destroy() {
+ if (!this.pendingDestroy ||
+ Object.keys(this.intentStates).some(function(intent) {
+ var intentState = this.intentStates[intent];
+ return (intentState.renderTasks.length !== 0 ||
+ intentState.receivingOperatorList);
+ }, this)) {
+ return;
+ }
+
+ Object.keys(this.intentStates).forEach(function(intent) {
+ delete this.intentStates[intent];
+ }, this);
+ this.objs.clear();
+ this.annotationsPromise = null;
+ this.pendingDestroy = false;
+ },
+ /**
+ * For internal use only.
+ * @ignore
+ */
+ _startRenderPage: function PDFPageProxy_startRenderPage(transparency,
+ intent) {
+ var intentState = this.intentStates[intent];
+ // TODO Refactor RenderPageRequest to separate rendering
+ // and operator list logic
+ if (intentState.displayReadyCapability) {
+ intentState.displayReadyCapability.resolve(transparency);
+ }
+ },
+ /**
+ * For internal use only.
+ * @ignore
+ */
+ _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk,
+ intent) {
+ var intentState = this.intentStates[intent];
+ var i, ii;
+ // Add the new chunk to the current operator list.
+ for (i = 0, ii = operatorListChunk.length; i < ii; i++) {
+ intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
+ intentState.operatorList.argsArray.push(
+ operatorListChunk.argsArray[i]);
+ }
+ intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
+
+ // Notify all the rendering tasks there are more operators to be consumed.
+ for (i = 0; i < intentState.renderTasks.length; i++) {
+ intentState.renderTasks[i].operatorListChanged();
+ }
+
+ if (operatorListChunk.lastChunk) {
+ intentState.receivingOperatorList = false;
+ this._tryDestroy();
+ }
+ }
+ };
+ return PDFPageProxy;
+})();
+
+/**
+ * For internal use only.
+ * @ignore
+ */
+var WorkerTransport = (function WorkerTransportClosure() {
+ function WorkerTransport(workerInitializedCapability, workerReadyCapability,
+ pdfDataRangeTransport, progressCallback) {
+ this.pdfDataRangeTransport = pdfDataRangeTransport;
+
+ this.workerInitializedCapability = workerInitializedCapability;
+ this.workerReadyCapability = workerReadyCapability;
+ this.progressCallback = progressCallback;
+ this.commonObjs = new PDFObjects();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ this.downloadInfoCapability = createPromiseCapability();
+ this.passwordCallback = null;
+
+ // If worker support isn't disabled explicit and the browser has worker
+ // support, create a new web worker and test if it/the browser fullfills
+ // all requirements to run parts of pdf.js in a web worker.
+ // Right now, the requirement is, that an Uint8Array is still an Uint8Array
+ // as it arrives on the worker. Chrome added this with version 15.
+ if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
+ var workerSrc = PDFJS.workerSrc;
+ if (!workerSrc) {
+ error('No PDFJS.workerSrc specified');
+ }
+
+ try {
+ // Some versions of FF can't create a worker on localhost, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+ var worker = new Worker(workerSrc);
+ var messageHandler = new MessageHandler('main', worker);
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('test', function transportTest(data) {
+ var supportTypedArray = data && data.supportTypedArray;
+ if (supportTypedArray) {
+ this.worker = worker;
+ if (!data.supportTransfers) {
+ PDFJS.postMessageTransfers = false;
+ }
+ this.setupMessageHandler(messageHandler);
+ workerInitializedCapability.resolve();
+ } else {
+ this.setupFakeWorker();
+ }
+ }.bind(this));
+
+ var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]);
+ // Some versions of Opera throw a DATA_CLONE_ERR on serializing the
+ // typed array. Also, checking if we can use transfers.
+ try {
+ messageHandler.send('test', testObj, [testObj.buffer]);
+ } catch (ex) {
+ info('Cannot use postMessage transfers');
+ testObj[0] = 0;
+ messageHandler.send('test', testObj);
+ }
+ return;
+ } catch (e) {
+ info('The worker has been disabled.');
+ }
+ }
+ // Either workers are disabled, not supported or have thrown an exception.
+ // Thus, we fallback to a faked worker.
+ this.setupFakeWorker();
+ }
+ WorkerTransport.prototype = {
+ destroy: function WorkerTransport_destroy() {
+ this.pageCache = [];
+ this.pagePromises = [];
+ var self = this;
+ this.messageHandler.sendWithPromise('Terminate', null).then(function () {
+ FontLoader.clear();
+ if (self.worker) {
+ self.worker.terminate();
+ }
+ });
+ },
+
+ setupFakeWorker: function WorkerTransport_setupFakeWorker() {
+ globalScope.PDFJS.disableWorker = true;
+
+ if (!PDFJS.fakeWorkerFilesLoadedCapability) {
+ PDFJS.fakeWorkerFilesLoadedCapability = createPromiseCapability();
+ // In the developer build load worker_loader which in turn loads all the
+ // other files and resolves the promise. In production only the
+ // pdf.worker.js file is needed.
+ Util.loadScript(PDFJS.workerSrc, function() {
+ PDFJS.fakeWorkerFilesLoadedCapability.resolve();
+ });
+ }
+ PDFJS.fakeWorkerFilesLoadedCapability.promise.then(function () {
+ warn('Setting up fake worker.');
+ // If we don't use a worker, just post/sendMessage to the main thread.
+ var fakeWorker = {
+ postMessage: function WorkerTransport_postMessage(obj) {
+ fakeWorker.onmessage({data: obj});
+ },
+ terminate: function WorkerTransport_terminate() {}
+ };
+
+ var messageHandler = new MessageHandler('main', fakeWorker);
+ this.setupMessageHandler(messageHandler);
+
+ // If the main thread is our worker, setup the handling for the messages
+ // the main thread sends to it self.
+ PDFJS.WorkerMessageHandler.setup(messageHandler);
+
+ this.workerInitializedCapability.resolve();
+ }.bind(this));
+ },
+
+ setupMessageHandler:
+ function WorkerTransport_setupMessageHandler(messageHandler) {
+ this.messageHandler = messageHandler;
+
+ function updatePassword(password) {
+ messageHandler.send('UpdatePassword', password);
+ }
+
+ var pdfDataRangeTransport = this.pdfDataRangeTransport;
+ if (pdfDataRangeTransport) {
+ pdfDataRangeTransport.addRangeListener(function(begin, chunk) {
+ messageHandler.send('OnDataRange', {
+ begin: begin,
+ chunk: chunk
+ });
+ });
+
+ pdfDataRangeTransport.addProgressListener(function(loaded) {
+ messageHandler.send('OnDataProgress', {
+ loaded: loaded
+ });
+ });
+
+ messageHandler.on('RequestDataRange',
+ function transportDataRange(data) {
+ pdfDataRangeTransport.requestDataRange(data.begin, data.end);
+ }, this);
+ }
+
+ messageHandler.on('GetDoc', function transportDoc(data) {
+ var pdfInfo = data.pdfInfo;
+ this.numPages = data.pdfInfo.numPages;
+ var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
+ this.pdfDocument = pdfDocument;
+ this.workerReadyCapability.resolve(pdfDocument);
+ }, this);
+
+ messageHandler.on('NeedPassword', function transportPassword(data) {
+ if (this.passwordCallback) {
+ return this.passwordCallback(updatePassword,
+ PasswordResponses.NEED_PASSWORD);
+ }
+ this.workerReadyCapability.reject(data.exception.message,
+ data.exception);
+ }, this);
+
+ messageHandler.on('IncorrectPassword', function transportBadPass(data) {
+ if (this.passwordCallback) {
+ return this.passwordCallback(updatePassword,
+ PasswordResponses.INCORRECT_PASSWORD);
+ }
+ this.workerReadyCapability.reject(data.exception.message,
+ data.exception);
+ }, this);
+
+ messageHandler.on('InvalidPDF', function transportInvalidPDF(data) {
+ this.workerReadyCapability.reject(data.exception.name, data.exception);
+ }, this);
+
+ messageHandler.on('MissingPDF', function transportMissingPDF(data) {
+ this.workerReadyCapability.reject(data.exception.message,
+ data.exception);
+ }, this);
+
+ messageHandler.on('UnknownError', function transportUnknownError(data) {
+ this.workerReadyCapability.reject(data.exception.message,
+ data.exception);
+ }, this);
+
+ messageHandler.on('DataLoaded', function transportPage(data) {
+ this.downloadInfoCapability.resolve(data);
+ }, this);
+
+ messageHandler.on('StartRenderPage', function transportRender(data) {
+ var page = this.pageCache[data.pageIndex];
+
+ page.stats.timeEnd('Page Request');
+ page._startRenderPage(data.transparency, data.intent);
+ }, this);
+
+ messageHandler.on('RenderPageChunk', function transportRender(data) {
+ var page = this.pageCache[data.pageIndex];
+
+ page._renderPageChunk(data.operatorList, data.intent);
+ }, this);
+
+ messageHandler.on('commonobj', function transportObj(data) {
+ var id = data[0];
+ var type = data[1];
+ if (this.commonObjs.hasData(id)) {
+ return;
+ }
+
+ switch (type) {
+ case 'Font':
+ var exportedData = data[2];
+
+ var font;
+ if ('error' in exportedData) {
+ var error = exportedData.error;
+ warn('Error during font loading: ' + error);
+ this.commonObjs.resolve(id, error);
+ break;
+ } else {
+ font = new FontFace(exportedData);
+ }
+
+ FontLoader.bind(
+ [font],
+ function fontReady(fontObjs) {
+ this.commonObjs.resolve(id, font);
+ }.bind(this)
+ );
+ break;
+ case 'FontPath':
+ this.commonObjs.resolve(id, data[2]);
+ break;
+ default:
+ error('Got unknown common object type ' + type);
+ }
+ }, this);
+
+ messageHandler.on('obj', function transportObj(data) {
+ var id = data[0];
+ var pageIndex = data[1];
+ var type = data[2];
+ var pageProxy = this.pageCache[pageIndex];
+ var imageData;
+ if (pageProxy.objs.hasData(id)) {
+ return;
+ }
+
+ switch (type) {
+ case 'JpegStream':
+ imageData = data[3];
+ loadJpegStream(id, imageData, pageProxy.objs);
+ break;
+ case 'Image':
+ imageData = data[3];
+ pageProxy.objs.resolve(id, imageData);
+
+ // heuristics that will allow not to store large data
+ var MAX_IMAGE_SIZE_TO_STORE = 8000000;
+ if (imageData && 'data' in imageData &&
+ imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) {
+ pageProxy.cleanupAfterRender = true;
+ }
+ break;
+ default:
+ error('Got unknown object type ' + type);
+ }
+ }, this);
+
+ messageHandler.on('DocProgress', function transportDocProgress(data) {
+ if (this.progressCallback) {
+ this.progressCallback({
+ loaded: data.loaded,
+ total: data.total
+ });
+ }
+ }, this);
+
+ messageHandler.on('DocError', function transportDocError(data) {
+ this.workerReadyCapability.reject(data);
+ }, this);
+
+ messageHandler.on('PageError', function transportError(data) {
+ var page = this.pageCache[data.pageNum - 1];
+ var intentState = page.intentStates[data.intent];
+ if (intentState.displayReadyCapability) {
+ intentState.displayReadyCapability.reject(data.error);
+ } else {
+ error(data.error);
+ }
+ }, this);
+
+ messageHandler.on('JpegDecode', function(data) {
+ var imageUrl = data[0];
+ var components = data[1];
+ if (components !== 3 && components !== 1) {
+ return Promise.reject(
+ new Error('Only 3 components or 1 component can be returned'));
+ }
+
+ return new Promise(function (resolve, reject) {
+ var img = new Image();
+ img.onload = function () {
+ var width = img.width;
+ var height = img.height;
+ var size = width * height;
+ var rgbaLength = size * 4;
+ var buf = new Uint8Array(size * components);
+ var tmpCanvas = createScratchCanvas(width, height);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ tmpCtx.drawImage(img, 0, 0);
+ var data = tmpCtx.getImageData(0, 0, width, height).data;
+ var i, j;
+
+ if (components === 3) {
+ for (i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
+ buf[j] = data[i];
+ buf[j + 1] = data[i + 1];
+ buf[j + 2] = data[i + 2];
+ }
+ } else if (components === 1) {
+ for (i = 0, j = 0; i < rgbaLength; i += 4, j++) {
+ buf[j] = data[i];
+ }
+ }
+ resolve({ data: buf, width: width, height: height});
+ };
+ img.onerror = function () {
+ reject(new Error('JpegDecode failed to load image'));
+ };
+ img.src = imageUrl;
+ });
+ });
+ },
+
+ fetchDocument: function WorkerTransport_fetchDocument(source) {
+ source.disableAutoFetch = PDFJS.disableAutoFetch;
+ source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
+ this.messageHandler.send('GetDocRequest', {
+ source: source,
+ disableRange: PDFJS.disableRange,
+ maxImageSize: PDFJS.maxImageSize,
+ cMapUrl: PDFJS.cMapUrl,
+ cMapPacked: PDFJS.cMapPacked,
+ disableFontFace: PDFJS.disableFontFace,
+ disableCreateObjectURL: PDFJS.disableCreateObjectURL,
+ verbosity: PDFJS.verbosity
+ });
+ },
+
+ getData: function WorkerTransport_getData() {
+ return this.messageHandler.sendWithPromise('GetData', null);
+ },
+
+ getPage: function WorkerTransport_getPage(pageNumber, capability) {
+ if (pageNumber <= 0 || pageNumber > this.numPages ||
+ (pageNumber|0) !== pageNumber) {
+ return Promise.reject(new Error('Invalid page request'));
+ }
+
+ var pageIndex = pageNumber - 1;
+ if (pageIndex in this.pagePromises) {
+ return this.pagePromises[pageIndex];
+ }
+ var promise = this.messageHandler.sendWithPromise('GetPage', {
+ pageIndex: pageIndex
+ }).then(function (pageInfo) {
+ var page = new PDFPageProxy(pageIndex, pageInfo, this);
+ this.pageCache[pageIndex] = page;
+ return page;
+ }.bind(this));
+ this.pagePromises[pageIndex] = promise;
+ return promise;
+ },
+
+ getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
+ return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref });
+ },
+
+ getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
+ return this.messageHandler.sendWithPromise('GetAnnotations',
+ { pageIndex: pageIndex });
+ },
+
+ getDestinations: function WorkerTransport_getDestinations() {
+ return this.messageHandler.sendWithPromise('GetDestinations', null);
+ },
+
+ getAttachments: function WorkerTransport_getAttachments() {
+ return this.messageHandler.sendWithPromise('GetAttachments', null);
+ },
+
+ getJavaScript: function WorkerTransport_getJavaScript() {
+ return this.messageHandler.sendWithPromise('GetJavaScript', null);
+ },
+
+ getOutline: function WorkerTransport_getOutline() {
+ return this.messageHandler.sendWithPromise('GetOutline', null);
+ },
+
+ getMetadata: function WorkerTransport_getMetadata() {
+ return this.messageHandler.sendWithPromise('GetMetadata', null).
+ then(function transportMetadata(results) {
+ return {
+ info: results[0],
+ metadata: (results[1] ? new PDFJS.Metadata(results[1]) : null)
+ };
+ });
+ },
+
+ getStats: function WorkerTransport_getStats() {
+ return this.messageHandler.sendWithPromise('GetStats', null);
+ },
+
+ startCleanup: function WorkerTransport_startCleanup() {
+ this.messageHandler.sendWithPromise('Cleanup', null).
+ then(function endCleanup() {
+ for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
+ var page = this.pageCache[i];
+ if (page) {
+ page.destroy();
+ }
+ }
+ this.commonObjs.clear();
+ FontLoader.clear();
+ }.bind(this));
+ }
+ };
+ return WorkerTransport;
+
+})();
+
+/**
+ * A PDF document and page is built of many objects. E.g. there are objects
+ * for fonts, images, rendering code and such. These objects might get processed
+ * inside of a worker. The `PDFObjects` implements some basic functions to
+ * manage these objects.
+ * @ignore
+ */
+var PDFObjects = (function PDFObjectsClosure() {
+ function PDFObjects() {
+ this.objs = {};
+ }
+
+ PDFObjects.prototype = {
+ /**
+ * Internal function.
+ * Ensures there is an object defined for `objId`.
+ */
+ ensureObj: function PDFObjects_ensureObj(objId) {
+ if (this.objs[objId]) {
+ return this.objs[objId];
+ }
+
+ var obj = {
+ capability: createPromiseCapability(),
+ data: null,
+ resolved: false
+ };
+ this.objs[objId] = obj;
+
+ return obj;
+ },
+
+ /**
+ * If called *without* callback, this returns the data of `objId` but the
+ * object needs to be resolved. If it isn't, this function throws.
+ *
+ * If called *with* a callback, the callback is called with the data of the
+ * object once the object is resolved. That means, if you call this
+ * function and the object is already resolved, the callback gets called
+ * right away.
+ */
+ get: function PDFObjects_get(objId, callback) {
+ // If there is a callback, then the get can be async and the object is
+ // not required to be resolved right now
+ if (callback) {
+ this.ensureObj(objId).capability.promise.then(callback);
+ return null;
+ }
+
+ // If there isn't a callback, the user expects to get the resolved data
+ // directly.
+ var obj = this.objs[objId];
+
+ // If there isn't an object yet or the object isn't resolved, then the
+ // data isn't ready yet!
+ if (!obj || !obj.resolved) {
+ error('Requesting object that isn\'t resolved yet ' + objId);
+ }
+
+ return obj.data;
+ },
+
+ /**
+ * Resolves the object `objId` with optional `data`.
+ */
+ resolve: function PDFObjects_resolve(objId, data) {
+ var obj = this.ensureObj(objId);
+
+ obj.resolved = true;
+ obj.data = data;
+ obj.capability.resolve(data);
+ },
+
+ isResolved: function PDFObjects_isResolved(objId) {
+ var objs = this.objs;
+
+ if (!objs[objId]) {
+ return false;
+ } else {
+ return objs[objId].resolved;
+ }
+ },
+
+ hasData: function PDFObjects_hasData(objId) {
+ return this.isResolved(objId);
+ },
+
+ /**
+ * Returns the data of `objId` if object exists, null otherwise.
+ */
+ getData: function PDFObjects_getData(objId) {
+ var objs = this.objs;
+ if (!objs[objId] || !objs[objId].resolved) {
+ return null;
+ } else {
+ return objs[objId].data;
+ }
+ },
+
+ clear: function PDFObjects_clear() {
+ this.objs = {};
+ }
+ };
+ return PDFObjects;
+})();
+
+/**
+ * Allows controlling of the rendering tasks.
+ * @class
+ */
+var RenderTask = (function RenderTaskClosure() {
+ function RenderTask(internalRenderTask) {
+ this.internalRenderTask = internalRenderTask;
+ /**
+ * Promise for rendering task completion.
+ * @type {Promise}
+ */
+ this.promise = this.internalRenderTask.capability.promise;
+ }
+
+ RenderTask.prototype = /** @lends RenderTask.prototype */ {
+ /**
+ * Cancels the rendering task. If the task is currently rendering it will
+ * not be cancelled until graphics pauses with a timeout. The promise that
+ * this object extends will resolved when cancelled.
+ */
+ cancel: function RenderTask_cancel() {
+ this.internalRenderTask.cancel();
+ },
+
+ /**
+ * Registers callback to indicate the rendering task completion.
+ *
+ * @param {function} onFulfilled The callback for the rendering completion.
+ * @param {function} onRejected The callback for the rendering failure.
+ * @return {Promise} A promise that is resolved after the onFulfilled or
+ * onRejected callback.
+ */
+ then: function RenderTask_then(onFulfilled, onRejected) {
+ return this.promise.then(onFulfilled, onRejected);
+ }
+ };
+
+ return RenderTask;
+})();
+
+/**
+ * For internal use only.
+ * @ignore
+ */
+var InternalRenderTask = (function InternalRenderTaskClosure() {
+
+ function InternalRenderTask(callback, params, objs, commonObjs, operatorList,
+ pageNumber) {
+ this.callback = callback;
+ this.params = params;
+ this.objs = objs;
+ this.commonObjs = commonObjs;
+ this.operatorListIdx = null;
+ this.operatorList = operatorList;
+ this.pageNumber = pageNumber;
+ this.running = false;
+ this.graphicsReadyCallback = null;
+ this.graphicsReady = false;
+ this.cancelled = false;
+ this.capability = createPromiseCapability();
+ // caching this-bound methods
+ this._continueBound = this._continue.bind(this);
+ this._scheduleNextBound = this._scheduleNext.bind(this);
+ this._nextBound = this._next.bind(this);
+ }
+
+ InternalRenderTask.prototype = {
+
+ initalizeGraphics:
+ function InternalRenderTask_initalizeGraphics(transparency) {
+
+ if (this.cancelled) {
+ return;
+ }
+ if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
+ globalScope.StepperManager.enabled) {
+ this.stepper = globalScope.StepperManager.create(this.pageNumber - 1);
+ this.stepper.init(this.operatorList);
+ this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
+ }
+
+ var params = this.params;
+ this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
+ this.objs, params.imageLayer);
+
+ this.gfx.beginDrawing(params.viewport, transparency);
+ this.operatorListIdx = 0;
+ this.graphicsReady = true;
+ if (this.graphicsReadyCallback) {
+ this.graphicsReadyCallback();
+ }
+ },
+
+ cancel: function InternalRenderTask_cancel() {
+ this.running = false;
+ this.cancelled = true;
+ this.callback('cancelled');
+ },
+
+ operatorListChanged: function InternalRenderTask_operatorListChanged() {
+ if (!this.graphicsReady) {
+ if (!this.graphicsReadyCallback) {
+ this.graphicsReadyCallback = this._continueBound;
+ }
+ return;
+ }
+
+ if (this.stepper) {
+ this.stepper.updateOperatorList(this.operatorList);
+ }
+
+ if (this.running) {
+ return;
+ }
+ this._continue();
+ },
+
+ _continue: function InternalRenderTask__continue() {
+ this.running = true;
+ if (this.cancelled) {
+ return;
+ }
+ if (this.params.continueCallback) {
+ this.params.continueCallback(this._scheduleNextBound);
+ } else {
+ this._scheduleNext();
+ }
+ },
+
+ _scheduleNext: function InternalRenderTask__scheduleNext() {
+ window.requestAnimationFrame(this._nextBound);
+ },
+
+ _next: function InternalRenderTask__next() {
+ if (this.cancelled) {
+ return;
+ }
+ this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList,
+ this.operatorListIdx,
+ this._continueBound,
+ this.stepper);
+ if (this.operatorListIdx === this.operatorList.argsArray.length) {
+ this.running = false;
+ if (this.operatorList.lastChunk) {
+ this.gfx.endDrawing();
+ this.callback();
+ }
+ }
+ }
+
+ };
+
+ return InternalRenderTask;
+})();
+
+
+var Metadata = PDFJS.Metadata = (function MetadataClosure() {
+ function fixMetadata(meta) {
+ return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) {
+ var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g,
+ function(code, d1, d2, d3) {
+ return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
+ });
+ var chars = '';
+ for (var i = 0; i < bytes.length; i += 2) {
+ var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
+ chars += code >= 32 && code < 127 && code !== 60 && code !== 62 &&
+ code !== 38 && false ? String.fromCharCode(code) :
+ '' + (0x10000 + code).toString(16).substring(1) + ';';
+ }
+ return '>' + chars;
+ });
+ }
+
+ function Metadata(meta) {
+ if (typeof meta === 'string') {
+ // Ghostscript produces invalid metadata
+ meta = fixMetadata(meta);
+
+ var parser = new DOMParser();
+ meta = parser.parseFromString(meta, 'application/xml');
+ } else if (!(meta instanceof Document)) {
+ error('Metadata: Invalid metadata object');
+ }
+
+ this.metaDocument = meta;
+ this.metadata = {};
+ this.parse();
+ }
+
+ Metadata.prototype = {
+ parse: function Metadata_parse() {
+ var doc = this.metaDocument;
+ var rdf = doc.documentElement;
+
+ if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in
+ rdf = rdf.firstChild;
+ while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
+ rdf = rdf.nextSibling;
+ }
+ }
+
+ var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null;
+ if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) {
+ return;
+ }
+
+ var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength;
+ for (i = 0, length = children.length; i < length; i++) {
+ desc = children[i];
+ if (desc.nodeName.toLowerCase() !== 'rdf:description') {
+ continue;
+ }
+
+ for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) {
+ if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') {
+ entry = desc.childNodes[ii];
+ name = entry.nodeName.toLowerCase();
+ this.metadata[name] = entry.textContent.trim();
+ }
+ }
+ }
+ },
+
+ get: function Metadata_get(name) {
+ return this.metadata[name] || null;
+ },
+
+ has: function Metadata_has(name) {
+ return typeof this.metadata[name] !== 'undefined';
+ }
+ };
+
+ return Metadata;
+})();
+
+
+//