mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 22:23:42 +01:00
Merge pull request #123 from sharelatex/pdfng
new pdf viewer using angularjs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -98,7 +98,7 @@ module.exports = (grunt) ->
|
||||
exclude: ["libs"]
|
||||
}, {
|
||||
name: "ide",
|
||||
exclude: ["libs"]
|
||||
exclude: ["libs", "libs/pdf"]
|
||||
}, {
|
||||
name: "libs"
|
||||
},{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'"
|
||||
|
||||
@@ -13,4 +13,4 @@ define [
|
||||
"ipCookie"
|
||||
])
|
||||
|
||||
return App
|
||||
return App
|
||||
|
||||
@@ -106,4 +106,4 @@ define [
|
||||
else
|
||||
$scope.darkTheme = false
|
||||
|
||||
angular.bootstrap(document.body, ["SharelatexApp"])
|
||||
angular.bootstrap(document.body, ["SharelatexApp"])
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
]
|
||||
@@ -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 = []
|
||||
]
|
||||
@@ -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/>")
|
||||
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: """
|
||||
<div data-pdf-viewer class="pdfjs-viewer" pdf-src='pdfSrc' position='position' scale='scale' highlights='highlights' please-jump-to='pleaseJumpTo'></div>
|
||||
<div class="pdfjs-controls" ng-class="{'flash': flashControls }">
|
||||
<div class="btn-group">
|
||||
<a href
|
||||
class="btn btn-info btn-lg"
|
||||
ng-click="fitToWidth()"
|
||||
tooltip="Fit to Width"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
>
|
||||
<i class="fa fa-fw fa-arrows-h"></i>
|
||||
</a>
|
||||
<a href
|
||||
class="btn btn-info btn-lg"
|
||||
ng-click="fitToHeight()"
|
||||
tooltip="Fit to Height"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
>
|
||||
<i class="fa fa-fw fa-arrows-v"></i>
|
||||
</a>
|
||||
<a href
|
||||
class="btn btn-info btn-lg"
|
||||
ng-click="zoomIn()"
|
||||
tooltip="Zoom In"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
>
|
||||
<i class="fa fa-fw fa-search-plus"></i>
|
||||
</a>
|
||||
<a href
|
||||
class="btn btn-info btn-lg"
|
||||
ng-click="zoomOut()"
|
||||
tooltip="Zoom Out"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
>
|
||||
<i class="fa fa-fw fa-search-minus"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-thin" ng-show="loading">
|
||||
<div class="progress-bar" ng-style="{ 'width': progress + '%' }"></div>
|
||||
</div>
|
||||
"""
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,129 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
# App = angular.module 'pdfPage', ['pdfHighlights']
|
||||
|
||||
App.directive 'pdfPage', ['$timeout', 'pdfHighlights', ($timeout, pdfHighlights) ->
|
||||
{
|
||||
require: '^pdfViewer',
|
||||
template: '''
|
||||
<div class="plv-page-view page-view">
|
||||
<div class="pdf-canvas pdfng-empty"></div>
|
||||
<div class="plv-text-layer text-layer"></div>
|
||||
<div class="plv-annotations-layer annotations-layer"></div>
|
||||
<div class="plv-highlights-layer highlights-layer"></div>
|
||||
</div>
|
||||
'''
|
||||
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()
|
||||
|
||||
}
|
||||
]
|
||||
@@ -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 = $('<canvas class="pdf-canvas pdfng-rendering"></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()
|
||||
|
||||
]
|
||||
@@ -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 = $('<div class="pdfng-spinner" style="position: absolute; top: 50%; left:50%; transform: translateX(-50%) translateY(-50%);"><i class="fa fa-spinner fa-spin" style="color: #999"></i></div>')
|
||||
spinner.css({'font-size' : size + 'px'})
|
||||
element.append(spinner)
|
||||
|
||||
stop: (element) ->
|
||||
element.find('.fa-spin').removeClass('fa-spin')
|
||||
|
||||
remove: (element) ->
|
||||
element.find('.fa-spin').remove()
|
||||
|
||||
]
|
||||
@@ -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
|
||||
|
||||
]
|
||||
@@ -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: """
|
||||
<div data-pdf-page class='pdf-page-container page-container' ng-repeat='page in pages'></div>
|
||||
"""
|
||||
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'
|
||||
|
||||
|
||||
}
|
||||
]
|
||||
@@ -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;
|
||||
}
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
+38151
File diff suppressed because it is too large
Load Diff
@@ -23,10 +23,16 @@
|
||||
.full-size;
|
||||
background-color: @gray-lighter;
|
||||
overflow: scroll;
|
||||
canvas {
|
||||
canvas, div.pdf-canvas {
|
||||
background: white;
|
||||
box-shadow: black 0px 0px 10px;
|
||||
}
|
||||
div.pdf-canvas.pdfng-empty {
|
||||
background-color: white;
|
||||
}
|
||||
div.pdf-canvas.pdfng-loading {
|
||||
background-color: white;
|
||||
}
|
||||
.page-container {
|
||||
margin: 10px auto;
|
||||
padding: 0 10px;
|
||||
|
||||
@@ -137,7 +137,12 @@ describe "CompileController", ->
|
||||
statusCode: 204
|
||||
headers: { "mock": "header" }
|
||||
@req.method = "mock-method"
|
||||
|
||||
@req.headers = {
|
||||
'Mock': 'Headers',
|
||||
'Range': 'should be passed - Range'
|
||||
'If-Range': 'should be passed - If-Range'
|
||||
'If-Modified-Since': 'should be passed - If-Modified-Since'
|
||||
}
|
||||
|
||||
describe "user with standard priority", ->
|
||||
|
||||
@@ -152,6 +157,11 @@ describe "CompileController", ->
|
||||
method: @req.method
|
||||
url: "#{@settings.apis.clsi.url}#{@url}",
|
||||
timeout: 60 * 1000
|
||||
headers: {
|
||||
'Range': 'should be passed - Range'
|
||||
'If-Range': 'should be passed - If-Range'
|
||||
'If-Modified-Since': 'should be passed - If-Modified-Since'
|
||||
}
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
@@ -175,6 +185,11 @@ describe "CompileController", ->
|
||||
method: @req.method
|
||||
url: "#{@settings.apis.clsi_priority.url}#{@url}",
|
||||
timeout: 60 * 1000
|
||||
headers: {
|
||||
'Range': 'should be passed - Range'
|
||||
'If-Range': 'should be passed - If-Range'
|
||||
'If-Modified-Since': 'should be passed - If-Modified-Since'
|
||||
}
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user