overleaf/services/web/public/coffee/ide/pdfng/directives/pdfTextLayer.coffee

211 lines
6.5 KiB
CoffeeScript
Raw Normal View History

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'
2014-11-25 11:00:21 -05:00
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
]