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,24 +98,13 @@ exports.anchor = (selectors) -> ...@@ -104,24 +98,13 @@ exports.anchor = (selectors) ->
else else
return range return range
if position? anchorByPosition = (page, anchor) ->
promise = promise.catch ->
findPage(position.start)
.then(({index, offset, textContent}) ->
page = getPage(index)
start = position.start - offset
end = position.end - offset
length = end - start
assertQuote(textContent.substr(start, length))
renderingState = page.renderingState renderingState = page.renderingState
renderingDone = page.textLayer?.renderingDone renderingDone = page.textLayer?.renderingDone
if renderingState is RenderingStates.FINISHED and renderingDone if renderingState is RenderingStates.FINISHED and renderingDone
root = page.textLayer.textLayerDiv root = page.textLayer.textLayerDiv
Promise.resolve(TextPositionAnchor.fromSelector({start, end}, {root})) selector = anchor.toSelector()
.then((a) -> Promise.resolve(a.toRange({root}))) return html.anchor([selector], {root})
else else
div = page.div ? page.el div = page.div ? page.el
placeholder = div.getElementsByClassName('annotator-placeholder')[0] placeholder = div.getElementsByClassName('annotator-placeholder')[0]
...@@ -134,7 +117,18 @@ exports.anchor = (selectors) -> ...@@ -134,7 +117,18 @@ exports.anchor = (selectors) ->
range.setStartBefore(placeholder) range.setStartBefore(placeholder)
range.setEndAfter(placeholder) range.setEndAfter(placeholder)
return range return range
)
if position?
promise = promise.catch ->
return findPage(position.start)
.then ({index, offset, textContent}) ->
page = getPage(index)
start = position.start - offset
end = position.end - offset
length = end - start
assertQuote(textContent.substr(start, length))
anchor = new TextPositionAnchor(start, end)
return anchorByPosition(page, anchor)
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