Commit 471cb769 authored by Randall Leeds's avatar Randall Leeds

PDF TextPosition and TextQuote starting to work

TextPositionAnchor can be generated and re-anchored within a PDF so
long as the selection doesn't span a page boundary.

TextQuoteAnchor is generated, but not re-anchored.
parent 7a09ea0e
......@@ -16,7 +16,11 @@
# :return: A Promise that resolves to a Range on success.
# :rtype: Promise
####
exports.anchor = (selectors, options) ->
exports.anchor = (selectors) ->
options =
root: document.body
ignoreSelector: '[class^="annotator-"]'
# Selectors
fragment = null
position = null
......@@ -48,34 +52,38 @@ exports.anchor = (selectors, options) ->
if fragment?
promise = promise.catch =>
Promise.resolve(FragmentAnchor.fromSelector(fragment, options))
.then((a) -> Promise.resolve(a.toRange(options)))
.then((a) -> a.toRange(options))
.then(assertQuote)
if range?
promise = promise.catch =>
Promise.resolve(RangeAnchor.fromSelector(range, options))
.then((a) -> Promise.resolve(a.toRange(options)))
.then((a) -> a.toRange(options))
.then(assertQuote)
if position?
promise = promise.catch =>
Promise.resolve(TextPositionAnchor.fromSelector(position, options))
.then((a) -> Promise.resolve(a.toRange(options)))
.then((a) -> a.toRange(options))
.then(assertQuote)
if quote?
promise = promise.catch =>
Promise.resolve(TextQuoteAnchor.fromSelector(quote, options))
.then((a) -> Promise.resolve(a.toRange(options)))
.then((a) -> a.toRange(options))
return promise
exports.describe = (range, options) ->
exports.describe = (range) ->
options =
root: document.body
ignoreSelector: '[class^="annotator-"]'
maybeDescribeWith = (type) ->
return Promise.resolve(type)
.then((t) -> Promise.resolve(t.fromRange(range, options)))
.then((a) -> Promise.resolve(a.toSelector(options)))
.then((t) -> t.fromRange(range, options))
.then((a) -> a.toSelector(options))
.catch(-> null)
selectors = (maybeDescribeWith(type) for type in [
......
if (window.PDFViewerApplication)
module.exports = require('./pdf')
else
module.exports = require('./html')
seek = require('dom-seek')
Annotator = require('annotator')
xpathRange = Annotator.Range
{
TextPositionAnchor
TextQuoteAnchor
} = require('./types')
getSiblingIndex = (node) ->
siblings = Array.prototype.slice.call(node.parentNode.childNodes)
return siblings.indexOf(node)
getNodeTextLayer = (node) ->
until node.classList?.contains('page')
node = node.parentNode
return node.getElementsByClassName('textLayer')[0]
getPageTextLayer = (pageIndex) ->
return PDFViewerApplication.pdfViewer.pages[pageIndex].textLayer
getPageTextContent = (pageIndex) ->
return PDFViewerApplication.pdfViewer.getPageTextContent(pageIndex)
getPageOffset = (pageIndex) ->
index = -1
next = (offset) ->
if ++index is pageIndex
return Promise.resolve(offset)
return getPageTextContent(index)
.then(getTextContentLength)
.then((length) -> next(offset + length))
return next(0)
getTextContentLength = (textContent) ->
sum = 0
for item in textContent.items
sum += item.str.length
return sum
findPage = (offset) ->
index = 0
total = 0
next = (length) ->
if total + length >= offset
offset = total
return Promise.resolve({index, offset})
else
index++
total += length
return count()
count = ->
return getPageTextContent(index)
.then(getTextContentLength)
.then(next)
return count()
###*
# Anchor a set of selectors.
#
# This function converts a set of selectors into a document range using.
# It encapsulates the core anchoring algorithm, using the selectors alone or
# in combination to establish the best anchor within the document.
#
# :param Array selectors: The selectors to try.
# :return: A Promise that resolves to a Range on success.
# :rtype: Promise
####
exports.anchor = (selectors) ->
options =
root: document.getElementById('viewer')
ignoreSelector: '[class^="annotator-"]'
# Selectors
position = null
quote = null
# Collect all the selectors
for selector in selectors ? []
switch selector.type
when 'TextPositionSelector'
position = selector
when 'TextQuoteSelector'
quote = selector
# Until we successfully anchor, we fail.
promise = Promise.reject('unable to anchor')
# Assert the quote matches the stored quote, if applicable
assertQuote = (range) ->
if quote?.exact? and range.toString() != quote.exact
throw new Error('quote mismatch')
else
return range
if position?
promise = promise.catch =>
findPage(position.start)
.then(({index, offset}) ->
textLayer = getPageTextLayer(index)
start = position.start - offset
end = position.end - offset
root = textLayer.textLayerDiv
Promise.resolve(TextPositionAnchor.fromSelector({start, end}, {root}))
.then((a) -> Promise.resolve(a.toRange({root})))
)
.then(assertQuote)
return promise
exports.describe = (range) ->
range = new xpathRange.BrowserRange(range).normalize()
startTextLayer = getNodeTextLayer(range.start)
endTextLayer = getNodeTextLayer(range.end)
# XXX: range covers only one page
if startTextLayer isnt endTextLayer
throw new Error('selecting across page breaks is not supported')
startRange = range.limit(startTextLayer)
endRange = range.limit(endTextLayer)
startPageIndex = getSiblingIndex(startTextLayer.parentNode)
endPageIndex = getSiblingIndex(endTextLayer.parentNode)
iter = seek.createTextIterator(startTextLayer)
return iter.seek(range.start).then (start) ->
iter.seek(range.end).then (length) ->
end = start + length + range.end.textContent.length
getPageOffset(startPageIndex).then (offset) ->
# XXX: range covers only one page
start += offset
end += offset
position = new TextPositionAnchor(start, end).toSelector()
options = {root: startTextLayer}
r = document.createRange()
r.setStartBefore(startRange.start)
r.setEndAfter(endRange.end)
quote = Promise.resolve(TextQuoteAnchor.fromRange(r, options))
.then((a) -> a.toSelector())
return Promise.all([position, quote])
......@@ -3,7 +3,7 @@ Promise = global.Promise or require('es6-promise').Promise
Annotator = require('annotator')
$ = Annotator.$
anchoring = require('./anchoring/html')
anchoring = require('./anchoring/main')
highlighter = require('./highlighter')
......@@ -145,19 +145,17 @@ module.exports = class Guest extends Annotator
this.removeEvents()
setupAnnotation: (annotation) ->
{anchored, unanchored, plugins} = this
root = @element[0]
ignoreSelector = '[class^="annotator-"]'
{anchored, unanchored, element, plugins} = this
maybeAnchor = (target) ->
anchoring.anchor(target.selector, {root, ignoreSelector})
anchoring.anchor(target.selector)
.then(highlightRange)
.then((highlights) -> {annotation, target, highlights})
.catch((reason) -> {annotation, target, reason})
highlightRange = (range) ->
return new Promise(raf).then ->
normedRange = Annotator.Range.sniff(range).normalize(root)
normedRange = Annotator.Range.sniff(range).normalize(element[0])
return highlighter.highlightRange(normedRange)
storeAndSync = (results) ->
......@@ -179,16 +177,12 @@ module.exports = class Guest extends Annotator
annotation
createAnnotation: (annotation = {}) ->
root = @element[0]
ignoreSelector = '[class^="annotator-"]'
options = {root, ignoreSelector}
ranges = @selectedRanges
@selectedRanges = null
info = this.getDocumentInfo()
Promise.all(ranges.map((r) -> anchoring.describe(r, options)))
Promise.all(ranges.map(anchoring.describe))
.then((targets) ->
info.then((info) ->
if info.metadata?
......
raf = require('raf')
Promise = require('es6-promise').Promise
Annotator = require('annotator')
......@@ -44,10 +45,21 @@ class PDF extends Annotator.Plugin
return {title, link}
onpagerendered: =>
unanchored = @annotator.unanchored.splice(0, @annotator.unanchored.length)
for obj in unanchored
@annotator.setupAnnotation(obj.annotation)
onpagerendered: (event) =>
annotator = @annotator
unanchored = @annotator.unanchored
page = PDFViewerApplication.pdfViewer.pages[event.detail.pageNumber - 1]
waitForTextLayer = ->
unless (page.textLayer.renderingDone)
return new Promise(raf).then(waitForTextLayer)
reanchor = ->
unanchored = unanchored.splice(0, unanchored.length)
for obj in unanchored
annotator.setupAnnotation(obj.annotation)
waitForTextLayer().then(reanchor)
Annotator.Plugin.PDF = PDF
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment