From 383fe346dd9102020b3b44a4954672af3207e589 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2012 17:34:13 +0530 Subject: [PATCH] EPUB CFI: Handle identifiers pointing to content inside a scrollable element --- src/calibre/ebooks/oeb/display/cfi.coffee | 76 +++++++++++++++---- .../ebooks/oeb/display/test/cfi-test.coffee | 31 ++++---- .../ebooks/oeb/display/test/index.html | 56 +++++++++++++- 3 files changed, 130 insertions(+), 33 deletions(-) diff --git a/src/calibre/ebooks/oeb/display/cfi.coffee b/src/calibre/ebooks/oeb/display/cfi.coffee index 3bc2828582..2a15f03b0d 100644 --- a/src/calibre/ebooks/oeb/display/cfi.coffee +++ b/src/calibre/ebooks/oeb/display/cfi.coffee @@ -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? diff --git a/src/calibre/ebooks/oeb/display/test/cfi-test.coffee b/src/calibre/ebooks/oeb/display/test/cfi-test.coffee index 5c3bb6ee16..427a74c6e9 100644 --- a/src/calibre/ebooks/oeb/display/test/cfi-test.coffee +++ b/src/calibre/ebooks/oeb/display/test/cfi-test.coffee @@ -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 - diff --git a/src/calibre/ebooks/oeb/display/test/index.html b/src/calibre/ebooks/oeb/display/test/index.html index 4ab7dca502..f43848c5e7 100644 --- a/src/calibre/ebooks/oeb/display/test/index.html +++ b/src/calibre/ebooks/oeb/display/test/index.html @@ -5,11 +5,61 @@ + -

Testing CFI functionality

- -

0123

+
+

Testing EPUB CFI

+
Current CFI: None
+

A div with scrollbars

+
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 loves 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 +
+
+