add pdfng module and use it by default

This commit is contained in:
Brian Gough 2014-11-25 16:00:21 +00:00
parent 81b8594944
commit 643bda6095
12 changed files with 1430 additions and 2 deletions

View file

@ -11,6 +11,7 @@ define [
"underscore"
"ngSanitize"
"ipCookie"
"pdfViewerApp"
])
return App
return App

View file

@ -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"

View 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

View file

@ -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')
]

View file

@ -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()

View file

@ -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
]

View file

@ -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 = []
]

View 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>
"""
}
]

View file

@ -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()
}
]

View file

@ -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')
]

View file

@ -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
]

View 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
}
]