EPUB CFI: Handle identifiers pointing to content inside a scrollable element

This commit is contained in:
Kovid Goyal 2012-01-05 17:34:13 +05:30
parent 645d7e95c7
commit 383fe346dd
3 changed files with 130 additions and 33 deletions

View File

@ -63,6 +63,18 @@ get_current_time = (target) -> # {{{
fstr(ans)
# }}}
viewport_to_document = (x, y, doc) -> # {{{
win = doc.defaultView
x += win.scrollX
y += win.scrollY
if doc != window.document
# We are in a frame
node = win.frameElement
rect = node.getBoundingClientRect()
return viewport_to_document(rect.left, rect.top, node.ownerDocument)
return [x + win.scrollX, y + win.scrollY]
# }}}
# Equivalent for caretRangeFromPoint for non WebKit browsers {{{
range_has_point = (range, x, y) ->
for rect in range.getClientRects()
@ -297,7 +309,9 @@ class CanonicalFragmentIdentifier
next = false
while true
nn = node.nextSibling
if nn.nodeType in [3, 4, 5, 6] # Text node, entity, cdata
if not nn
break
if nn.nodeType in [3, 4, 5, 6] and nn.nodeValue?.length # Text node, entity, cdata
next = nn
break
if not next
@ -387,6 +401,7 @@ class CanonicalFragmentIdentifier
nwin = ndoc.defaultView
x = null
y = null
range = null
if typeof(r.offset) == "number"
# Character offset
@ -421,29 +436,62 @@ class CanonicalFragmentIdentifier
if rects?.length
break
if not rects?.length
log("Could not find caret position: rects: #{ rects } offset: #{ r.offset }")
return null
rect = rects[0]
x = (a*rect.left + (1-a)*rect.right)
y = (rect.top + rect.bottom)/2
else
x = node.offsetLeft - nwin.scrollX
y = node.offsetTop - nwin.scrollY
if typeof(r.x) == "number" and node.offsetWidth
x += (r.x*node.offsetWidth)/100
y += (r.y*node.offsetHeight)/100
[x, y] = [r.x, r.y]
until ndoc == doc
node = nwin.frameElement
{x:x, y:y, node:r.node, time:r.time, range:range, a:a}
# }}}
scroll_to: (cfi, callback=false, doc=window?.document) -> # {{{
point = this.point(cfi, doc)
if not point
log("No point found for cfi: #{ cfi }")
return
if typeof point.time == 'number'
this.set_current_time(point.node, point.time)
if point.range != null
r = point.range
node = r.startContainer
ndoc = node.ownerDocument
nwin = ndoc.defaultView
x += node.offsetLeft - nwin.scrollX
y += node.offsetTop - nwin.scrollY
span = ndoc.createElement('span')
span.setAttribute('style', 'border-width: 0; padding: 0; margin: 0')
r.surroundContents(span)
span.scrollIntoView()
fn = ->
rect = span.getBoundingClientRect()
x = (point.a*rect.left + (1-point.a)*rect.right)
y = (rect.top + rect.bottom)/2
[x, y] = viewport_to_document(x, y, ndoc)
span.outerHTML = span.innerHTML
if callback
callback(x, y)
else
node = point.node
nwin = node.ownerDocument.defaultView
node.scrollIntoView()
{x:x, y:y, node:r.node, time:r.time}
fn = ->
rect = node.getBoundingClientRect()
[x, y] = viewport_to_document(rect.left, rect.top, node.ownerDocument)
if typeof(point.x) == 'number' and node.offsetWidth
x += (r.x*node.offsetWidth)/100
if typeof(point.y) == 'number' and node.offsetHeight
y += (r.y*node.offsetHeight)/100
scrollTo(x, y)
if callback
callback(x, y)
setTimeout(fn, 10)
null
# }}}
if window?

View File

@ -21,30 +21,29 @@ viewport_left = (node) ->
show_cfi = (dont_seek) ->
if window.current_cfi
pos = window.cfi.point(window.current_cfi)
if pos
fn = (x, y) ->
ms = $("#marker")
ms.offset({left:pos.x-1, top:pos.y-30})
ms.css('visibility', 'visible')
if not dont_seek
if typeof pos.time == "number"
window.cfi.set_current_time(pos.node, pos.time)
scrollTo(0, pos.y - 30)
# This strange sequence is needed to get it to work in Chrome
# when called from the onload handler
ms.offset({left:x-1, top:y-30})
ms.offset()
ms.offset({left:x-1, top:y-30})
window.cfi.scroll_to(window.current_cfi, fn)
null
# Set this to true to have the browser reload the page with the current cfi
RELOAD = false
mark_and_reload = (evt) ->
window.current_cfi = window.cfi.at(evt.clientX, evt.clientY)
if not RELOAD
show_cfi(true)
if window.current_cfi
fn = () ->
newloc = window.location.href.replace(/#.*$/, '') + "#epubcfi(#{ window.current_cfi })"
epubcfi = "#epubcfi(#{ window.current_cfi })"
newloc = window.location.href.replace(/#.*$/, '') + epubcfi
window.location.replace(newloc)
if RELOAD
window.location.reload()
document.getElementById('current-cfi').innerHTML = window.current_cfi
window.location.reload()
setTimeout(fn, 1)
null
@ -58,7 +57,7 @@ window.onload = ->
r = location.hash.match(/#epubcfi\((.+)\)$/)
if r
window.current_cfi = r[1]
setTimeout(show_cfi, 1)
document.getElementById('current-cfi').innerHTML = window.current_cfi
setTimeout(show_cfi, 100)
null

View File

@ -5,11 +5,61 @@
<script type="text/javascript" src="cfi.coffee"></script>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="cfi-test.coffee"></script>
<style type="text/css">
body { font-family: sans-serif }
h2 {
border-top: solid 2px black;
margin-top: 4ex;
}
#container {
max-width: 30em;
margin-right: auto;
margin-left: 2em;
position:relative;
}
#current-cfi {
font-family: monospace;
border: solid 1px blue;
padding: 1em;
}
#overflow {
max-height: 100px;
overflow: scroll;
border: solid 1px black;
}
</style>
</head>
<body>
<h1 id="first-h1" style="border: solid 1px red">Testing CFI functionality</h1>
<img id="marker" style="position: absolute; visibility: hidden;" src="marker.png" alt="Marker" />
<p>0123</p>
<div id="container">
<h1 id="first-h1">Testing EPUB CFI</h1>
<div>Current CFI:&nbsp;<span id="current-cfi">None</span></div>
<h2>A div with scrollbars</h2>
<div id="overflow"> But I must explain to you how all this mistaken
idea of denouncing pleasure and praising pain was born and I
will give you a complete account of the system, and expound the
actual teachings of the great explorer of the truth, the
master-builder of human happiness. No one rejects, dislikes, or
avoids pleasure itself, because it is pleasure, but because
those who do not know how to pursue pleasure rationally
encounter consequences that are extremely painful. Nor again is
there anyone who <b>loves</b> or pursues or desires to obtain pain of
itself, because it is pain, but because occasionally
circumstances occur in which toil and pain can procure him some
great pleasure. To take a trivial example, which of us ever
undertakes laborious physical exercise, except to obtain some
advantage from it? But who has any right to find fault with a
man who chooses to enjoy a pleasure that has no annoying
consequences, or one who avoids a pain that produces no
resultant pleasure? On the other hand, we denounce with
righteous indignation and dislike men who are so beguiled and
demoralized by the charms of pleasure of the moment, so blinded
by desire, that they cannot foresee
</div>
</div>
<img id="marker" style="position: absolute; visibility: hidden; z-index:10" src="marker.png" alt="Marker" />
</body>
</html>