Commit 66db778f authored by Randall Leeds's avatar Randall Leeds

Fuzzy anchoring on unrendered PDF pages

Also change the PDF anchoring to leverage the HTML anchoring by
allowing the options to be passed into the module-level anchor
functions in the pdf and html modules so that the pdf module can
be only responsible for finding the appropriate page and then
delegating.
parent 0d49f282
...@@ -16,11 +16,7 @@ ...@@ -16,11 +16,7 @@
# :return: A Promise that resolves to a Range on success. # :return: A Promise that resolves to a Range on success.
# :rtype: Promise # :rtype: Promise
#### ####
exports.anchor = (selectors) -> exports.anchor = (selectors, options = {}) ->
options =
root: document.body
ignoreSelector: '[class^="annotator-"]'
# Selectors # Selectors
fragment = null fragment = null
position = null position = null
......
...@@ -3,10 +3,8 @@ seek = require('dom-seek') ...@@ -3,10 +3,8 @@ seek = require('dom-seek')
Annotator = require('annotator') Annotator = require('annotator')
xpathRange = Annotator.Range xpathRange = Annotator.Range
{ html = require('./html')
TextPositionAnchor {TextPositionAnchor, TextQuoteAnchor} = require('./types')
TextQuoteAnchor
} = require('./types')
getSiblingIndex = (node) -> getSiblingIndex = (node) ->
...@@ -77,11 +75,7 @@ findPage = (offset) -> ...@@ -77,11 +75,7 @@ findPage = (offset) ->
# :return: A Promise that resolves to a Range on success. # :return: A Promise that resolves to a Range on success.
# :rtype: Promise # :rtype: Promise
#### ####
exports.anchor = (selectors) -> exports.anchor = (selectors, options = {}) ->
options =
root: document.getElementById('viewer')
ignoreSelector: '[class^="annotator-"]'
# Selectors # Selectors
position = null position = null
quote = null quote = null
...@@ -104,37 +98,37 @@ exports.anchor = (selectors) -> ...@@ -104,37 +98,37 @@ exports.anchor = (selectors) ->
else else
return range return range
anchorByPosition = (page, anchor) ->
renderingState = page.renderingState
renderingDone = page.textLayer?.renderingDone
if renderingState is RenderingStates.FINISHED and renderingDone
root = page.textLayer.textLayerDiv
selector = anchor.toSelector()
return html.anchor([selector], {root})
else
div = page.div ? page.el
placeholder = div.getElementsByClassName('annotator-placeholder')[0]
unless placeholder?
placeholder = document.createElement('span')
placeholder.classList.add('annotator-placeholder')
placeholder.textContent = 'Loading annotations…'
div.appendChild(placeholder)
range = document.createRange()
range.setStartBefore(placeholder)
range.setEndAfter(placeholder)
return range
if position? if position?
promise = promise.catch -> promise = promise.catch ->
findPage(position.start) return findPage(position.start)
.then(({index, offset, textContent}) -> .then ({index, offset, textContent}) ->
page = getPage(index) page = getPage(index)
start = position.start - offset start = position.start - offset
end = position.end - offset end = position.end - offset
length = end - start length = end - start
assertQuote(textContent.substr(start, length)) assertQuote(textContent.substr(start, length))
anchor = new TextPositionAnchor(start, end)
renderingState = page.renderingState return anchorByPosition(page, anchor)
renderingDone = page.textLayer?.renderingDone
if renderingState is RenderingStates.FINISHED and renderingDone
root = page.textLayer.textLayerDiv
Promise.resolve(TextPositionAnchor.fromSelector({start, end}, {root}))
.then((a) -> Promise.resolve(a.toRange({root})))
else
div = page.div ? page.el
placeholder = div.getElementsByClassName('annotator-placeholder')[0]
unless placeholder?
placeholder = document.createElement('span')
placeholder.classList.add('annotator-placeholder')
placeholder.textContent = 'Loading annotations…'
div.appendChild(placeholder)
range = document.createRange()
range.setStartBefore(placeholder)
range.setEndAfter(placeholder)
return range
)
if quote? if quote?
promise = promise.catch -> promise = promise.catch ->
...@@ -142,30 +136,26 @@ exports.anchor = (selectors) -> ...@@ -142,30 +136,26 @@ exports.anchor = (selectors) ->
pageSearches = for pageIndex in [0...pagesCount] pageSearches = for pageIndex in [0...pagesCount]
page = getPage(pageIndex) page = getPage(pageIndex)
continue unless page.textLayer?.renderingDone
content = getPageTextContent(pageIndex) content = getPageTextContent(pageIndex)
offset = getPageOffset(pageIndex) offset = getPageOffset(pageIndex)
Promise.all([content, offset, page]).then (results) ->
Promise.all([content, offset, page]).then((results) ->
[content, offset, page] = results [content, offset, page] = results
quoteOptions = {root: page.textLayer.textLayerDiv} pageOptions = {root: {textContent: content}}
if position? if position?
# XXX: must be on one page # XXX: must be on one page
start = position.start - offset start = position.start - offset
end = position.end - offset end = position.end - offset
quoteOptions.position = {start, end} pageOptions.position = {start, end}
anchor = new TextQuoteAnchor.fromSelector(quote, pageOptions)
return TextQuoteAnchor return Promise.resolve(anchor)
.fromSelector(quote, quoteOptions) .then((a) -> return a.toPositionAnchor(pageOptions))
.toRange(quoteOptions) .then((a) -> return anchorByPosition(page, a))
).catch(-> null)
pageSearches = (p.catch(-> null) for p in pageSearches)
return Promise.all(pageSearches).then((results) -> return Promise.all(pageSearches).then (results) ->
for result in results when result? for result in results when result?
return result return result
throw new Error('quote not found') throw new Error('quote not found')
)
return promise return promise
......
...@@ -211,6 +211,18 @@ class TextQuoteAnchor extends Anchor ...@@ -211,6 +211,18 @@ class TextQuoteAnchor extends Anchor
return new TextQuoteAnchor(exact, prefix, suffix, start, end) return new TextQuoteAnchor(exact, prefix, suffix, start, end)
toRange: (options = {}) -> toRange: (options = {}) ->
return this.toPositionAnchor(options).toRange()
toSelector: ->
selector = {
type: 'TextQuoteSelector'
exact: @quote
}
if @prefix? then selector.prefix = @prefix
if @suffix? then selector.suffix = @suffix
return selector
toPositionAnchor: (options = {}) ->
root = options.root or document.body root = options.root or document.body
dmp = new DiffMatchPatch() dmp = new DiffMatchPatch()
...@@ -245,16 +257,7 @@ class TextQuoteAnchor extends Anchor ...@@ -245,16 +257,7 @@ class TextQuoteAnchor extends Anchor
dmp.Match_Distance = 64 dmp.Match_Distance = 64
{start, end} = slices.reduce(foldSlices, {start, end, loc}) {start, end} = slices.reduce(foldSlices, {start, end, loc})
return new TextPositionAnchor(start, end).toRange(options) return new TextPositionAnchor(start, end)
toSelector: ->
selector = {
type: 'TextQuoteSelector'
exact: @quote
}
if @prefix? then selector.prefix = @prefix
if @suffix? then selector.suffix = @suffix
return selector
exports.Anchor = Anchor exports.Anchor = Anchor
exports.FragmentAnchor = FragmentAnchor exports.FragmentAnchor = FragmentAnchor
......
...@@ -155,8 +155,9 @@ module.exports = class Guest extends Annotator ...@@ -155,8 +155,9 @@ module.exports = class Guest extends Annotator
deadHighlights = [] deadHighlights = []
_anchor = (target) -> _anchor = (target) ->
options = {ignoreSelector: '[class^="annotator-"]'}
return new Promise(raf) return new Promise(raf)
.then(-> anchoring.anchor(target.selector)) .then(-> anchoring.anchor(target.selector, options))
.then((range) -> {annotation, target, range}) .then((range) -> {annotation, target, range})
.catch(-> {annotation, target}) .catch(-> {annotation, target})
......
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