Start work on implementing the EPUB 3 CFI standard

This commit is contained in:
Kovid Goyal 2011-12-14 16:38:10 +05:30
parent dc594ba4c8
commit 23bc88c681
8 changed files with 329 additions and 6 deletions

View File

@ -2,6 +2,7 @@
.check-cache.pickle
src/calibre/plugins
resources/images.qrc
src/calibre/ebooks/oeb/display/test/*.js
resources/display/*.js
src/calibre/manual/.build/
src/calibre/manual/cli/

View File

@ -1,5 +1,5 @@
" Project wide builtins
let g:pyflakes_builtins = ["_", "dynamic_property", "__", "P", "I", "lopen", "icu_lower", "icu_upper", "icu_title", "ngettext"]
let $PYFLAKES_BUILTINS = "_,dynamic_property,__,P,I,lopen,icu_lower,icu_upper,icu_title,ngettext"
python << EOFPY
import os, sys

View File

@ -31,18 +31,29 @@ class Coffee(Command): # {{{
def add_options(self, parser):
parser.add_option('--watch', '-w', action='store_true', default=False,
help='Autocompile when .coffee files are changed')
parser.add_option('--show-js', action='store_true', default=False,
help='Display the generated javascript')
def run(self, opts):
self.do_coffee_compile()
self.do_coffee_compile(opts)
if opts.watch:
try:
while True:
time.sleep(1)
self.do_coffee_compile(timestamp=True)
time.sleep(0.5)
self.do_coffee_compile(opts, timestamp=True,
ignore_errors=True)
except KeyboardInterrupt:
pass
def do_coffee_compile(self, timestamp=False):
def show_js(self, jsfile):
from pygments.lexers import JavascriptLexer
from pygments.formatters import TerminalFormatter
from pygments import highlight
with open(jsfile, 'rb') as f:
raw = f.read()
print highlight(raw, JavascriptLexer(), TerminalFormatter())
def do_coffee_compile(self, opts, timestamp=False, ignore_errors=False):
for toplevel, dest in self.COFFEE_DIRS.iteritems():
dest = self.j(self.RESOURCES, dest)
for x in glob.glob(self.j(self.SRC, __appname__, toplevel, '*.coffee')):
@ -50,7 +61,20 @@ class Coffee(Command): # {{{
if self.newer(js, x):
print ('\t%sCompiling %s'%(time.strftime('[%H:%M:%S] ') if
timestamp else '', os.path.basename(x)))
try:
subprocess.check_call(['coffee', '-c', '-o', dest, x])
except:
print ('\n\tCompilation of %s failed'%os.path.basename(x))
if ignore_errors:
with open(js, 'wb') as f:
f.write('# Compilation from coffeescript failed')
else:
raise SystemExit(1)
else:
if opts.show_js:
self.show_js(js)
print ('#'*80)
print ('#'*80)
def clean(self):
for toplevel, dest in self.COFFEE_DIRS.iteritems():

View File

@ -0,0 +1,136 @@
#!/usr/bin/env coffee
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
###
Copyright 2011, Kovid Goyal <kovid@kovidgoyal.net>
Released under the GPLv3 License
###
log = (error) ->
if error and window?.console?.log
window.console.log(error)
fstr = (d) -> # {{{
# Convert a timestamp floating point number to a string
ans = ""
if ( d < 0 )
ans = "-"
d = -d
n = Math.floor(d)
ans += n
n = Math.round((d-n)*100)
if( n != 0 )
ans += "."
ans += if (n % 10 == 0) then (n/10) else n
ans
# }}}
class CanonicalFragmentIdentifier
# This class is a namespace to expose CFI functions via the window.cfi
# object
constructor: () ->
encode: (doc, node, offset, tail) -> # {{{
cfi = tail or ""
# Handle the offset, if any
switch node.nodeType
when 1 # Element node
if typeoff(offset) == 'number'
node = node.childNodes.item(offset)
when 3, 4, 5, 6 # Text/entity/CDATA node
offset or= 0
while true
p = node.previousSibling
if (p?.nodeType not in [3, 4, 5, 6])
break
offset += p.nodeValue.length
node = p
cfi = ":" + offset + cfi
else # Not handled
log("Offsets for nodes of type #{ node.nodeType } are not handled")
# Construct the path to node from root
until node == doc
p = node.parentNode
if not p
if node.nodeType == 9 # Document node (iframe)
win = node.defaultView
if win.frameElement
node = win.frameElement
cfi = "!" + cfi
continue
break
# Increase index by the length of all previous sibling text nodes
index = 0
child = p.firstChild
while true
index |= 1
if child.nodeType in [1, 7]
index++
if child == node
break
child = child.nextSibling
# Add id assertions for robustness where possible
id = node.getAttribute?('id')
idspec = if id then "[#{ id }]" else ''
cfi = '/' + index + idspec + cfi
node = p
cfi
# }}}
at: (x, y, doc=window.document) -> # {{{
cdoc = doc
target = null
cwin = cdoc.defaultView
tail = ''
offset = null
name = null
# Drill down into iframes, etc.
while true
target = cdoc.elementFromPoint x, y
if not target or target.localName == 'html'
log("No element at (#{ x }, #{ y })")
return null
name = target.localName
if name not in ['iframe', 'embed', 'object']
break
cd = target.contentDocument
if not cd
break
x = x + cwin.pageXOffset - target.offsetLeft
y = y + cwin.pageYOffset - target.offsetTop
cdoc = cd
cwin = cdoc.defaultView
target.normalize()
if name in ['audio', 'video']
tail = "~" + fstr target.currentTime
else if name in ['img']
px = ((x + cwin.scrollX - target.offsetLeft)*100)/target.offsetWidth
py = ((y + cwin.scrollY - target.offsetTop)*100)/target.offsetHeight
tail = "#{ tail }@#{ fstr px },#{ fstr py }"
else
if cdoc.caretRangeFromPoint # WebKit
range = cdoc.caretRangeFromPoint(x, y)
if range
target = range.startContainer
offset = range.startOffset
else
# TODO: implement a span bisection algorithm for UAs
# without caretRangeFromPoint (Gecko, IE)
this.encode(doc, target, offset, tail)
# }}}
window.cfi = new CanonicalFragmentIdentifier()

View File

@ -0,0 +1,25 @@
#!/usr/bin/env coffee
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
###
Copyright 2011, Kovid Goyal <kovid@kovidgoyal.net>
Released under the GPLv3 License
###
viewport_top = (node) ->
node.offsetTop - window.pageYOffset
viewport_left = (node) ->
node.offsetLeft - window.pageXOffset
window.onload = ->
h1 = document.getElementsByTagName('h1')[0]
x = h1.scrollLeft + 150
y = viewport_top(h1) + h1.offsetHeight/2
e = document.elementFromPoint x, y
if e.getAttribute('id') != 'first-h1'
alert 'Failed to find top h1'
return
alert window.cfi.at x, y

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>Testing CFI functionality</title>
<script type="text/javascript" src="cfi.js"></script>
<script type="text/javascript" src="cfi-test.js"></script>
</head>
<body>
<h1 id="first-h1" style="border: solid 1px red">Testing CFI functionality</h1>
</body>
</html>

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
try:
from calibre.utils.coffeescript import serve
except ImportError:
import init_calibre
if False: init_calibre, serve
from calibre.utils.coffeescript import serve
def run_devel_server():
os.chdir(os.path.dirname(__file__))
serve(['../cfi.coffee', 'cfi-test.coffee'])
if __name__ == '__main__':
run_devel_server()

View File

@ -0,0 +1,98 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
'''
Utilities to help with developing coffeescript based apps
'''
import time, SimpleHTTPServer, SocketServer, threading, os, subprocess
class Server(threading.Thread):
def __init__(self, port=8000):
threading.Thread.__init__(self)
self.port = port
self.daemon = True
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
self.httpd = SocketServer.TCPServer(("localhost", port), Handler)
def run(self):
print('serving at localhost:%d'%self.port)
self.httpd.serve_forever()
def end(self):
self.httpd.shutdown()
self.join()
class Compiler(threading.Thread):
def __init__(self, coffee_files):
threading.Thread.__init__(self)
self.daemon = True
if not isinstance(coffee_files, dict):
coffee_files = {x:os.path.splitext(os.path.basename(x))[0]+'.js'
for x in coffee_files}
a = os.path.abspath
self.src_map = {a(x):a(y) for x, y in coffee_files.iteritems()}
self.keep_going = True
def run(self):
while self.keep_going:
for src, dest in self.src_map.iteritems():
if self.newer(src, dest):
self.compile(src, dest)
time.sleep(0.1)
def newer(self, src, dest):
try:
sstat = os.stat(src)
except:
time.sleep(0.01)
sstat = os.stat(src)
return (not os.access(dest, os.R_OK) or sstat.st_mtime >
os.stat(dest).st_mtime)
def compile(self, src, dest):
with open(dest, 'wb') as f:
try:
subprocess.check_call(['coffee', '-c', '-p', src], stdout=f)
except:
print('Compilation of %s failed'%src)
f.seek(0)
f.truncate()
f.write('// Compilation of cofeescript failed')
def end(self):
self.keep_going = False
self.join()
for x in self.src_map.itervalues():
try:
os.remove(x)
except:
pass
def serve(coffee_files, port=8000):
ws = Server(port=port)
comp = Compiler(coffee_files)
comp.start()
ws.start()
try:
while True:
time.sleep(1)
if not comp.is_alive() or not ws.is_alive():
print ('Worker failed')
raise SystemExit(1)
except KeyboardInterrupt:
pass
finally:
try:
comp.end()
except:
pass
ws.end()