mirror of
https://github.com/overleaf/overleaf.git
synced 2025-01-24 14:31:20 +00:00
add pdfng module and use it by default
This commit is contained in:
parent
81b8594944
commit
643bda6095
12 changed files with 1430 additions and 2 deletions
|
@ -11,6 +11,7 @@ define [
|
|||
"underscore"
|
||||
"ngSanitize"
|
||||
"ipCookie"
|
||||
"pdfViewerApp"
|
||||
])
|
||||
|
||||
return App
|
||||
return App
|
||||
|
|
|
@ -6,7 +6,7 @@ define [
|
|||
"ide/online-users/OnlineUsersManager"
|
||||
"ide/track-changes/TrackChangesManager"
|
||||
"ide/permissions/PermissionsManager"
|
||||
"ide/pdf/PdfManager"
|
||||
"ide/pdfng/PdfManager"
|
||||
"ide/binary-files/BinaryFilesManager"
|
||||
"ide/settings/index"
|
||||
"ide/share/index"
|
||||
|
|
21
services/web/public/coffee/ide/pdfng/PdfManager.coffee
Normal file
21
services/web/public/coffee/ide/pdfng/PdfManager.coffee
Normal file
|
@ -0,0 +1,21 @@
|
|||
define [
|
||||
"ide/pdfng/controllers/PdfController"
|
||||
"ide/pdfng/controllers/PdfViewToggleController"
|
||||
"ide/pdfng/directives/pdfJs"
|
||||
], () ->
|
||||
class PdfManager
|
||||
constructor: (@ide, @$scope) ->
|
||||
@$scope.pdf =
|
||||
url: null # Pdf Url
|
||||
error: false # Server error
|
||||
timeout: false # Server timed out
|
||||
failure: false # PDF failed to compile
|
||||
compiling: false
|
||||
uncompiled: true
|
||||
logEntries: []
|
||||
logEntryAnnotations: {}
|
||||
rawLog: ""
|
||||
view: null # 'pdf' 'logs'
|
||||
showRawLog: false
|
||||
highlights: []
|
||||
position: null
|
|
@ -0,0 +1,293 @@
|
|||
define [
|
||||
"base"
|
||||
"libs/latex-log-parser"
|
||||
], (App, LogParser) ->
|
||||
App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking) ->
|
||||
autoCompile = true
|
||||
$scope.$on "project:joined", () ->
|
||||
return if !autoCompile
|
||||
autoCompile = false
|
||||
$scope.recompile(isAutoCompile: true)
|
||||
$scope.hasPremiumCompile = $scope.project.features.compileGroup == "priority"
|
||||
|
||||
sendCompileRequest = (options = {}) ->
|
||||
url = "/project/#{$scope.project_id}/compile"
|
||||
if options.isAutoCompile
|
||||
url += "?auto_compile=true"
|
||||
return $http.post url, {
|
||||
settingsOverride:
|
||||
rootDoc_id: options.rootDocOverride_id or null
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
parseCompileResponse = (response) ->
|
||||
# Reset everything
|
||||
$scope.pdf.error = false
|
||||
$scope.pdf.timedout = false
|
||||
$scope.pdf.failure = false
|
||||
$scope.pdf.uncompiled = false
|
||||
$scope.pdf.url = null
|
||||
|
||||
if response.status == "timedout"
|
||||
$scope.pdf.timedout = true
|
||||
else if response.status == "autocompile-backoff"
|
||||
$scope.pdf.uncompiled = true
|
||||
else if response.status == "failure"
|
||||
$scope.pdf.failure = true
|
||||
fetchLogs()
|
||||
else if response.status == "success"
|
||||
$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}"
|
||||
fetchLogs()
|
||||
|
||||
IGNORE_FILES = ["output.fls", "output.fdb_latexmk"]
|
||||
$scope.pdf.outputFiles = []
|
||||
for file in response.outputFiles
|
||||
if IGNORE_FILES.indexOf(file.path) == -1
|
||||
# Turn 'output.blg' into 'blg file'.
|
||||
if file.path.match(/^output\./)
|
||||
file.name = "#{file.path.replace(/^output\./, "")} file"
|
||||
else
|
||||
file.name = file.path
|
||||
$scope.pdf.outputFiles.push file
|
||||
|
||||
fetchLogs = () ->
|
||||
$http.get "/project/#{$scope.project_id}/output/output.log"
|
||||
.success (log) ->
|
||||
$scope.pdf.rawLog = log
|
||||
logEntries = LogParser.parse(log, ignoreDuplicates: true)
|
||||
$scope.pdf.logEntries = logEntries
|
||||
$scope.pdf.logEntries.all = logEntries.errors.concat(logEntries.warnings).concat(logEntries.typesetting)
|
||||
|
||||
$scope.pdf.logEntryAnnotations = {}
|
||||
for entry in logEntries.all
|
||||
entry.file = normalizeFilePath(entry.file)
|
||||
|
||||
entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
||||
if entity?
|
||||
$scope.pdf.logEntryAnnotations[entity.id] ||= []
|
||||
$scope.pdf.logEntryAnnotations[entity.id].push {
|
||||
row: entry.line - 1
|
||||
type: if entry.level == "error" then "error" else "warning"
|
||||
text: entry.message
|
||||
}
|
||||
|
||||
.error () ->
|
||||
$scope.pdf.logEntries = []
|
||||
$scope.pdf.rawLog = ""
|
||||
|
||||
getRootDocOverride_id = () ->
|
||||
doc = ide.editorManager.getCurrentDocValue()
|
||||
return null if !doc?
|
||||
for line in doc.split("\n")
|
||||
match = line.match /(.*)\\documentclass/
|
||||
if match and !match[1].match /%/
|
||||
return ide.editorManager.getCurrentDocId()
|
||||
return null
|
||||
|
||||
normalizeFilePath = (path) ->
|
||||
path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "")
|
||||
path = path.replace(/^\/compile\//, "")
|
||||
|
||||
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
||||
if rootDocDirname?
|
||||
path = path.replace(/^\.\//, rootDocDirname + "/")
|
||||
|
||||
return path
|
||||
|
||||
compileCount = 0
|
||||
$scope.recompile = (options = {}) ->
|
||||
return if $scope.pdf.compiling
|
||||
$scope.pdf.compiling = true
|
||||
|
||||
if !options.isAutoCompile
|
||||
compileCount++
|
||||
if compileCount == 1
|
||||
event_tracking.send('editor-interaction', 'single-compile')
|
||||
else if compileCount == 3
|
||||
event_tracking.send('editor-interaction', 'multi-compile')
|
||||
|
||||
options.rootDocOverride_id = getRootDocOverride_id()
|
||||
|
||||
sendCompileRequest(options)
|
||||
.success (data) ->
|
||||
$scope.pdf.view = "pdf"
|
||||
$scope.pdf.compiling = false
|
||||
parseCompileResponse(data)
|
||||
.error () ->
|
||||
$scope.pdf.compiling = false
|
||||
$scope.pdf.error = true
|
||||
|
||||
# This needs to be public.
|
||||
ide.$scope.recompile = $scope.recompile
|
||||
|
||||
$scope.clearCache = () ->
|
||||
$http {
|
||||
url: "/project/#{$scope.project_id}/output"
|
||||
method: "DELETE"
|
||||
headers:
|
||||
"X-Csrf-Token": window.csrfToken
|
||||
}
|
||||
|
||||
$scope.toggleLogs = () ->
|
||||
if !$scope.pdf.view? or $scope.pdf.view == "pdf"
|
||||
$scope.pdf.view = "logs"
|
||||
else
|
||||
$scope.pdf.view = "pdf"
|
||||
|
||||
$scope.showPdf = () ->
|
||||
$scope.pdf.view = "pdf"
|
||||
|
||||
$scope.toggleRawLog = () ->
|
||||
$scope.pdf.showRawLog = !$scope.pdf.showRawLog
|
||||
|
||||
$scope.openOutputFile = (file) ->
|
||||
window.open("/project/#{$scope.project_id}/output/#{file.path}")
|
||||
|
||||
$scope.openClearCacheModal = () ->
|
||||
modalInstance = $modal.open(
|
||||
templateUrl: "clearCacheModalTemplate"
|
||||
controller: "ClearCacheModalController"
|
||||
scope: $scope
|
||||
)
|
||||
|
||||
$scope.syncToCode = (position) ->
|
||||
synctex
|
||||
.syncToCode(position)
|
||||
.then (data) ->
|
||||
{doc, line} = data
|
||||
ide.editorManager.openDoc(doc, gotoLine: line)
|
||||
|
||||
$scope.switchToFlatLayout = () ->
|
||||
$scope.ui.pdfLayout = 'flat'
|
||||
$scope.ui.view = 'pdf'
|
||||
$.localStorage "pdf.layout", "flat"
|
||||
|
||||
$scope.switchToSideBySideLayout = () ->
|
||||
$scope.ui.pdfLayout = 'sideBySide'
|
||||
$scope.ui.view = 'editor'
|
||||
$.localStorage "pdf.layout", "split"
|
||||
|
||||
if pdfLayout = $.localStorage("pdf.layout")
|
||||
$scope.switchToSideBySideLayout() if pdfLayout == "split"
|
||||
$scope.switchToFlatLayout() if pdfLayout == "flat"
|
||||
else
|
||||
$scope.switchToSideBySideLayout()
|
||||
|
||||
App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
|
||||
synctex =
|
||||
syncToPdf: (cursorPosition) ->
|
||||
deferred = $q.defer()
|
||||
|
||||
doc_id = ide.editorManager.getCurrentDocId()
|
||||
if !doc_id?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
doc = ide.fileTreeManager.findEntityById(doc_id)
|
||||
if !doc?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
path = ide.fileTreeManager.getEntityPath(doc)
|
||||
if !path?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
|
||||
# If the root file is folder/main.tex, then synctex sees the
|
||||
# path as folder/./main.tex
|
||||
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
||||
if rootDocDirname? and rootDocDirname != ""
|
||||
path = path.replace(RegExp("^#{rootDocDirname}"), "#{rootDocDirname}/.")
|
||||
|
||||
{row, column} = cursorPosition
|
||||
|
||||
$http({
|
||||
url: "/project/#{ide.project_id}/sync/code",
|
||||
method: "GET",
|
||||
params: {
|
||||
file: path
|
||||
line: row + 1
|
||||
column: column
|
||||
}
|
||||
})
|
||||
.success (data) ->
|
||||
deferred.resolve(data.pdf or [])
|
||||
.error (error) ->
|
||||
deferred.reject(error)
|
||||
|
||||
return deferred.promise
|
||||
|
||||
syncToCode: (position, options = {}) ->
|
||||
deferred = $q.defer()
|
||||
if !position?
|
||||
deferred.reject()
|
||||
return deferred.promise
|
||||
|
||||
# It's not clear exactly where we should sync to if it wasn't directly
|
||||
# clicked on, but a little bit down from the very top seems best.
|
||||
if options.includeVisualOffset
|
||||
position.offset.top = position.offset.top + 80
|
||||
|
||||
$http({
|
||||
url: "/project/#{ide.project_id}/sync/pdf",
|
||||
method: "GET",
|
||||
params: {
|
||||
page: position.page + 1
|
||||
h: position.offset.left.toFixed(2)
|
||||
v: position.offset.top.toFixed(2)
|
||||
}
|
||||
})
|
||||
.success (data) ->
|
||||
if data.code? and data.code.length > 0
|
||||
doc = ide.fileTreeManager.findEntityByPath(data.code[0].file)
|
||||
return if !doc?
|
||||
deferred.resolve({doc: doc, line: data.code[0].line})
|
||||
.error (error) ->
|
||||
deferred.reject(error)
|
||||
|
||||
return deferred.promise
|
||||
|
||||
return synctex
|
||||
]
|
||||
|
||||
App.controller "PdfSynctexController", ["$scope", "synctex", "ide", ($scope, synctex, ide) ->
|
||||
@cursorPosition = null
|
||||
ide.$scope.$on "cursor:editor:update", (event, @cursorPosition) =>
|
||||
|
||||
$scope.syncToPdf = () =>
|
||||
return if !@cursorPosition?
|
||||
synctex
|
||||
.syncToPdf(@cursorPosition)
|
||||
.then (highlights) ->
|
||||
$scope.pdf.highlights = highlights
|
||||
|
||||
$scope.syncToCode = () ->
|
||||
synctex
|
||||
.syncToCode($scope.pdf.position, includeVisualOffset: true)
|
||||
.then (data) ->
|
||||
{doc, line} = data
|
||||
ide.editorManager.openDoc(doc, gotoLine: line)
|
||||
]
|
||||
|
||||
App.controller "PdfLogEntryController", ["$scope", "ide", ($scope, ide) ->
|
||||
$scope.openInEditor = (entry) ->
|
||||
entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
||||
return if !entity? or entity.type != "doc"
|
||||
if entry.line?
|
||||
line = entry.line
|
||||
ide.editorManager.openDoc(entity, gotoLine: line)
|
||||
]
|
||||
|
||||
App.controller 'ClearCacheModalController', ["$scope", "$modalInstance", ($scope, $modalInstance) ->
|
||||
$scope.state =
|
||||
inflight: false
|
||||
|
||||
$scope.clear = () ->
|
||||
$scope.state.inflight = true
|
||||
$scope
|
||||
.clearCache()
|
||||
.then () ->
|
||||
$scope.state.inflight = false
|
||||
$modalInstance.close()
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
]
|
|
@ -0,0 +1,17 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "PdfViewToggleController", ($scope) ->
|
||||
$scope.togglePdfView = () ->
|
||||
if $scope.ui.view == "pdf"
|
||||
$scope.ui.view = "editor"
|
||||
else
|
||||
$scope.ui.view = "pdf"
|
||||
|
||||
$scope.fileTreeClosed = false
|
||||
$scope.$on "layout:main:resize", (e, state) ->
|
||||
if state.west.initClosed
|
||||
$scope.fileTreeClosed = true
|
||||
else
|
||||
$scope.fileTreeClosed = false
|
||||
$scope.$apply()
|
|
@ -0,0 +1,42 @@
|
|||
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
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
app = angular.module 'pdfHighlights', []
|
||||
|
||||
app.factory 'pdfHighlights', [ () ->
|
||||
class pdfHighlights
|
||||
|
||||
constructor: (options) ->
|
||||
@highlightsLayerDiv = options.highlights
|
||||
@viewport = options.viewport
|
||||
@highlightElements = []
|
||||
|
||||
addHighlight: (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 = []
|
||||
]
|
198
services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
Normal file
198
services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
Normal file
|
@ -0,0 +1,198 @@
|
|||
define [
|
||||
"base"
|
||||
"ide/pdfng/directives/pdfViewer"
|
||||
"ide/pdfng/directives/pdfPage"
|
||||
"ide/pdfng/directives/pdfRenderer"
|
||||
"ide/pdfng/directives/pdfTextLayer"
|
||||
"ide/pdfng/directives/pdfAnnotations"
|
||||
"ide/pdfng/directives/pdfHighlights"
|
||||
"libs/pdf"
|
||||
"text!libs/pdfListView/TextLayer.css"
|
||||
"text!libs/pdfListView/AnnotationsLayer.css"
|
||||
"text!libs/pdfListView/HighlightsLayer.css"
|
||||
], (
|
||||
App
|
||||
pdfViewerApp
|
||||
pdfPage
|
||||
pdfRenderer
|
||||
pdfTextLayer
|
||||
pdfAnnotations
|
||||
pdfHighlights
|
||||
pdf
|
||||
textLayerCss
|
||||
annotationsLayerCss
|
||||
highlightsLayerCss
|
||||
) ->
|
||||
if PDFJS?
|
||||
PDFJS.workerSrc = window.pdfJsWorkerPath
|
||||
PDFJS.disableAutoFetch = true
|
||||
|
||||
style = $("<style/>")
|
||||
style.text(textLayerCss + "\n" + annotationsLayerCss + "\n" + highlightsLayerCss)
|
||||
$("body").append(style)
|
||||
|
||||
App.directive "pdfjs", ["$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.scale = {}
|
||||
initializedPosition = false
|
||||
initializePosition = () ->
|
||||
return if initializedPosition
|
||||
initializedPosition = true
|
||||
|
||||
if (scale = $.localStorage("pdf.scale"))?
|
||||
#pdfListView.setScaleMode(scale.scaleMode, scale.scale)
|
||||
else
|
||||
scope.scale = { scaleMode: 'scale_mode_fit_width' }
|
||||
|
||||
if (position = $.localStorage("pdf.position.#{attrs.key}"))
|
||||
1
|
||||
#pdfListView.setPdfPosition(position)
|
||||
|
||||
#scope.position = pdfListView.getPdfPosition(true)
|
||||
|
||||
$(window).unload () =>
|
||||
$.localStorage "pdf.scale", {
|
||||
# scaleMode: pdfListView.getScaleMode()
|
||||
# scale: pdfListView.getScale()
|
||||
}
|
||||
# $.localStorage "pdf.position.#{attrs.key}", pdfListView.getPdfPosition()
|
||||
|
||||
flashControls = () ->
|
||||
scope.flashControls = true
|
||||
$timeout () ->
|
||||
scope.flashControls = false
|
||||
, 1000
|
||||
|
||||
element.find(".pdfjs-viewer").scroll () ->
|
||||
# scope.position = pdfListView.getPdfPosition(true)
|
||||
|
||||
onDoubleClick = (e) ->
|
||||
scope.dblClickCallback?(page: e.page, offset: { top: e.y, left: e.x })
|
||||
|
||||
scope.$watch "pdfSrc", (url) ->
|
||||
if url
|
||||
scope.loading = true
|
||||
scope.progress = 0
|
||||
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.$watch "highlights", (areas) ->
|
||||
return if !areas?
|
||||
highlights = for area in areas or []
|
||||
{
|
||||
page: area.page - 1
|
||||
highlight:
|
||||
left: area.h
|
||||
top: area.v
|
||||
height: area.height
|
||||
width: area.width
|
||||
}
|
||||
|
||||
if highlights.length > 0
|
||||
first = highlights[0]
|
||||
# pdfListView.setPdfPosition({
|
||||
# page: first.page
|
||||
# offset:
|
||||
# left: first.highlight.left
|
||||
# top: first.highlight.top - 80
|
||||
# }, true)
|
||||
|
||||
# pdfListView.clearHighlights()
|
||||
# pdfListView.setHighlights(highlights, true)
|
||||
|
||||
# setTimeout () =>
|
||||
# pdfListView.clearHighlights()
|
||||
# , 1000
|
||||
|
||||
scope.fitToHeight = () ->
|
||||
# pdfListView.setToFitHeight()
|
||||
|
||||
scope.fitToWidth = () ->
|
||||
# pdfListView.setToFitWidth()
|
||||
|
||||
scope.zoomIn = () ->
|
||||
# scale = pdfListView.getScale()
|
||||
# pdfListView.setScale(scale * 1.2)
|
||||
|
||||
scope.zoomOut = () ->
|
||||
# scale = pdfListView.getScale()
|
||||
# pdfListView.setScale(scale / 1.2)
|
||||
|
||||
if attrs.resizeOn?
|
||||
for event in attrs.resizeOn.split(",")
|
||||
scope.$on event, (e) ->
|
||||
console.log 'got a resize event', event, e
|
||||
# pdfListView.onResize()
|
||||
|
||||
template: """
|
||||
<div data-pdf-viewer class="pdfjs-viewer" pdf-src='pdfSrc' position='position' scale='scale'></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>
|
||||
"""
|
||||
}
|
||||
]
|
|
@ -0,0 +1,80 @@
|
|||
app = angular.module 'pdfPage', []
|
||||
|
||||
app.directive 'pdfPage', ['$timeout', ($timeout) ->
|
||||
{
|
||||
require: '^pdfViewer',
|
||||
template: '''
|
||||
<div class="pdf-canvas"></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>
|
||||
'''
|
||||
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) ->
|
||||
element.height(Math.floor(size[0]))
|
||||
element.width(Math.floor(size[1]))
|
||||
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
|
||||
scope.document.getPdfViewport(scope.page.pageNum).then (viewport) ->
|
||||
scope.page.viewport = viewport
|
||||
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()
|
||||
|
||||
}
|
||||
]
|
|
@ -0,0 +1,185 @@
|
|||
app = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']
|
||||
|
||||
console.log 'hello from Renderer'
|
||||
|
||||
app.factory 'PDFRenderer', ['$q', '$timeout', 'pdfAnnotations', 'pdfTextLayer', ($q, $timeout, pdfAnnotations, pdfTextLayer) ->
|
||||
|
||||
class PDFRenderer
|
||||
@JOB_QUEUE_INTERVAL: 100
|
||||
|
||||
constructor: (@url, @options) ->
|
||||
@scale = @options.scale || 1
|
||||
@document = $q.when(PDFJS.getDocument @url)
|
||||
@navigateFn = @options.navigateFn
|
||||
@resetState()
|
||||
|
||||
resetState: () ->
|
||||
console.log 'reseting renderer state'
|
||||
@page = []
|
||||
@complete = []
|
||||
@timeout = []
|
||||
@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()
|
||||
|
||||
getPageIndex: (ref) ->
|
||||
@document.then (pdfDocument) ->
|
||||
pdfDocument.getPageIndex(ref).then (idx) ->
|
||||
idx
|
||||
|
||||
getScale: () ->
|
||||
@scale
|
||||
|
||||
setScale: (@scale) ->
|
||||
@resetState()
|
||||
|
||||
pause: (element, pagenum) ->
|
||||
return if @complete[pagenum]
|
||||
@renderQueue = @renderQueue.filter (q) ->
|
||||
q.pagenum != pagenum
|
||||
@stopSpinner (element.canvas)
|
||||
|
||||
triggerRenderQueue: () ->
|
||||
$timeout () =>
|
||||
@processRenderQueue()
|
||||
, @JOB_QUEUE_INTERVAL
|
||||
|
||||
removeCompletedJob: (pagenum) ->
|
||||
# may need to clean up deferred object here
|
||||
delete @renderTask[pagenum]
|
||||
@jobs = @jobs - 1
|
||||
@triggerRenderQueue()
|
||||
|
||||
renderPage: (element, pagenum) ->
|
||||
viewport = $q.defer()
|
||||
current = {
|
||||
'element': element
|
||||
'pagenum': pagenum
|
||||
}
|
||||
@renderQueue.push(current)
|
||||
@triggerRenderQueue()
|
||||
|
||||
processRenderQueue: () ->
|
||||
return if @jobs > 0
|
||||
current = @renderQueue.pop()
|
||||
return unless current?
|
||||
[element, pagenum] = [current.element, current.pagenum]
|
||||
return if @complete[pagenum]
|
||||
return if @renderTask[pagenum]
|
||||
@jobs = @jobs + 1
|
||||
|
||||
@addSpinner(element.canvas)
|
||||
|
||||
pageLoad = @getPage(pagenum)
|
||||
|
||||
@renderTask[pagenum] = pageLoad.then (pageObject) =>
|
||||
@doRender element, pagenum, pageObject
|
||||
|
||||
@renderTask[pagenum].then () =>
|
||||
# complete
|
||||
@complete[pagenum] = true
|
||||
@removeCompletedJob pagenum
|
||||
, () =>
|
||||
# rejected
|
||||
@removeCompletedJob pagenum
|
||||
|
||||
doRender: (element, pagenum, page) ->
|
||||
self = this
|
||||
scale = @scale
|
||||
|
||||
if (not scale?)
|
||||
console.log 'scale is undefined, returning'
|
||||
return
|
||||
|
||||
canvas = $('<canvas class="pdf-canvas-new"></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[0].height = newHeight
|
||||
element.canvas[0].width = newWidth
|
||||
|
||||
if pixelRatio != 1
|
||||
ctx.scale(pixelRatio, pixelRatio)
|
||||
|
||||
textLayer = new pdfTextLayer({
|
||||
textLayerDiv: element.text[0]
|
||||
viewport: viewport
|
||||
})
|
||||
page.getTextContent().then (textContent) ->
|
||||
console.log 'text content is', textContent
|
||||
window.RENDER_DELAY = 0
|
||||
textLayer.setTextContent textContent
|
||||
|
||||
annotationsLayer = new pdfAnnotations({
|
||||
annotations: element.annotations[0]
|
||||
viewport: viewport
|
||||
navigateFn: @navigateFn
|
||||
})
|
||||
page.getAnnotations().then (annotations) ->
|
||||
console.log 'annotations are', annotations
|
||||
window.RENDER_DELAY = 0
|
||||
annotationsLayer.setAnnotations annotations
|
||||
|
||||
return @renderTask = page.render {
|
||||
canvasContext: ctx
|
||||
viewport: viewport
|
||||
}
|
||||
.then () ->
|
||||
element.canvas.replaceWith(canvas)
|
||||
canvas.removeClass('pdf-canvas-new')
|
||||
|
||||
addSpinner: (element) ->
|
||||
element.css({position: 'relative'})
|
||||
h = element.parent().height()
|
||||
w = element.parent().width()
|
||||
size = Math.floor(0.5 * Math.min(h, w))
|
||||
spinner = $('<div 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)
|
||||
|
||||
stopSpinner: (element) ->
|
||||
element.find('.fa-spin').removeClass('fa-spin')
|
||||
|
||||
]
|
|
@ -0,0 +1,207 @@
|
|||
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]
|
||||
continue if textDiv.dataset.isWhitespace
|
||||
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
|
||||
|
||||
# |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
|
||||
|
||||
]
|
357
services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
Normal file
357
services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
Normal file
|
@ -0,0 +1,357 @@
|
|||
app = angular.module 'pdfViewerApp', ['pdfPage', 'PDFRenderer', 'pdfHighlights']
|
||||
|
||||
app.config [ "$logProvider", ($logProvider) ->
|
||||
$logProvider.debugEnabled true
|
||||
]
|
||||
|
||||
console.log "HELLO"
|
||||
|
||||
app.controller 'pdfViewerController', ['$scope', '$q', 'PDFRenderer', '$element', 'pdfHighlights', ($scope, $q, PDFRenderer, $element, pdfHighlights) ->
|
||||
@load = () ->
|
||||
$scope.document = new PDFRenderer($scope.pdfSrc, {
|
||||
scale: 1,
|
||||
navigateFn: (ref) ->
|
||||
# this function captures clicks on the annotation links
|
||||
$scope.navigateTo = ref
|
||||
$scope.$apply()
|
||||
})
|
||||
|
||||
# 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()
|
||||
destinations: $scope.document.getDestinations()
|
||||
# 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
|
||||
]
|
||||
$scope.destinations = result.destinations
|
||||
console.log 'resolved q.all, page size is', result
|
||||
$scope.numPages = result.numPages
|
||||
|
||||
@setScale = (scale, containerHeight, containerWidth) ->
|
||||
$scope.loaded.then () ->
|
||||
scale = {} if not scale?
|
||||
if scale.scaleMode == 'scale_mode_fit_width'
|
||||
# TODO make this dynamic
|
||||
numScale = (containerWidth - 15) / ($scope.pdfPageSize[1])
|
||||
else if scale.scaleMode == 'scale_mode_fit_height'
|
||||
# TODO magic numbers for jquery ui layout
|
||||
numScale = (containerHeight) / ($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 - 15) / ($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
|
||||
console.log 'position is', position
|
||||
$scope.pages = ({
|
||||
pageNum: i
|
||||
} for i in [1 .. $scope.numPages])
|
||||
if position? && position.page?
|
||||
console.log 'setting current page', position.page
|
||||
pagenum = position.page
|
||||
$scope.pages[pagenum - 1].current = true
|
||||
$scope.pages[pagenum - 1].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'
|
||||
visiblePages = $scope.pages.filter (page) ->
|
||||
page.visible
|
||||
if visiblePages.length
|
||||
topPage = visiblePages[0]
|
||||
else
|
||||
console.log 'CANNOT FIND TOP PAGE'
|
||||
topPage = $scope.pages[0]
|
||||
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": topPage.pageNum,
|
||||
"offset" : { "top" : pdfOffset[1], "left": 0 }
|
||||
}
|
||||
return newPosition
|
||||
|
||||
@computeOffset = (page, position) ->
|
||||
element = page.element
|
||||
pageTop = $(element).offset().top - $(element).parent().offset().top
|
||||
console.log('top of page scroll is', pageTop)
|
||||
console.log('inner height is', $(element).innerHeight())
|
||||
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] + 10) ## 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', ($q, $timeout) ->
|
||||
{
|
||||
controller: 'pdfViewerController'
|
||||
controllerAs: 'ctrl'
|
||||
scope: {
|
||||
"pdfSrc": "="
|
||||
"highlights": "="
|
||||
"position": "="
|
||||
"scale": "="
|
||||
"dblClickCallback": "="
|
||||
}
|
||||
template: """
|
||||
<div data-pdf-page class='pdf-page-container plv-page-view page-view' ng-repeat='page in pages'></div>
|
||||
"""
|
||||
link: (scope, element, attrs, ctrl) ->
|
||||
console.log 'in pdfViewer element is', element
|
||||
console.log 'attrs', attrs
|
||||
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 () ->
|
||||
ctrl.redraw(origposition)
|
||||
|
||||
scope.$on 'layout-ready', () ->
|
||||
console.log 'GOT LAYOUT READY EVENT'
|
||||
console.log 'calling refresh'
|
||||
updateContainer()
|
||||
layoutReady.resolve 'layout is ready'
|
||||
scope.parentSize = [
|
||||
element.innerHeight(),
|
||||
element.innerWidth()
|
||||
]
|
||||
#scope.$apply()
|
||||
|
||||
scope.$on 'layout:pdf:resize', () ->
|
||||
console.log 'GOT LAYOUT-RESIZE EVENT'
|
||||
scope.parentSize = [
|
||||
element.innerHeight(),
|
||||
element.innerWidth()
|
||||
]
|
||||
#scope.$apply()
|
||||
|
||||
element.on 'scroll', () ->
|
||||
console.log 'scroll detected', scope.adjustingScroll
|
||||
updateContainer()
|
||||
scope.$apply()
|
||||
#console.log 'pdfposition', element.parent().scrollTop()
|
||||
if scope.adjustingScroll
|
||||
scope.adjustingScroll = false
|
||||
return
|
||||
#console.log 'not from auto scroll'
|
||||
scope.position = ctrl.getPdfPosition()
|
||||
console.log 'position is', scope.position
|
||||
scope.$apply()
|
||||
|
||||
scope.$watch 'pdfSrc', (newVal, oldVal) ->
|
||||
console.log 'loading pdf', newVal, oldVal
|
||||
return unless newVal?
|
||||
ctrl.load()
|
||||
doRescale 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 '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.loaded.then () ->
|
||||
console.log 'destinations are', scope.destinations
|
||||
r = scope.destinations[newVal.dest]
|
||||
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
|
||||
scope.document.getPdfViewport(pidx).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 + 1, offset: {top: r[3], left: r[2]}}
|
||||
ctrl.setPdfPosition scope.pages[pidx], newPosition
|
||||
|
||||
scope.$watch "highlights", (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
|
||||
|
||||
if highlights.length > 0
|
||||
first = highlights[0]
|
||||
ctrl.setPdfPosition({
|
||||
page: first.page
|
||||
offset:
|
||||
left: first.highlight.left
|
||||
top: first.highlight.top - 80
|
||||
}, true)
|
||||
|
||||
# iterate over pages
|
||||
# highlightsElement = $(element).find('.highlights-layer')
|
||||
# highlightsLayer = new pdfHighlights({
|
||||
# highlights: element.highlights[0]
|
||||
# viewport: viewport
|
||||
# })
|
||||
#pdfListView.clearHighlights()
|
||||
#ctrl.setHighlights(highlights, true)
|
||||
|
||||
#setTimeout () =>
|
||||
# pdfListView.clearHighlights()
|
||||
#, 1000
|
||||
|
||||
}
|
||||
]
|
Loading…
Reference in a new issue