diff --git a/src/calibre/srv/render_book.py b/src/calibre/srv/render_book.py index d2d1a14d26..be212b905d 100644 --- a/src/calibre/srv/render_book.py +++ b/src/calibre/srv/render_book.py @@ -201,6 +201,7 @@ class Container(ContainerBase): 'spine_length': 0, 'toc_anchor_map': toc_anchor_map(toc), 'landmarks': landmarks, + 'link_to_map': {}, } # Mark the spine as dirty since we have to ensure it is normalized for name in data['spine']: @@ -328,6 +329,8 @@ class Container(ContainerBase): changed.add(base) return url + ltm = self.book_render_data['link_to_map'] + for name, mt in self.mime_map.iteritems(): mt = mt.lower() if mt in OEB_STYLES: @@ -350,7 +353,9 @@ class Container(ContainerBase): if href.startswith(link_uid): a.set('href', 'javascript:void(0)') parts = decode_url(href.split('|')[1]) - a.set('data-' + link_uid, json.dumps({'name':parts[0], 'frag':parts[1]}, ensure_ascii=False)) + lname, lfrag = parts[0], parts[1] + ltm.setdefault(lname, {}).setdefault(lfrag or '', []).append(name) + a.set('data-' + link_uid, json.dumps({'name':lname, 'frag':lfrag}, ensure_ascii=False)) else: a.set('target', '_blank') a.set('rel', 'noopener noreferrer') @@ -382,7 +387,7 @@ boolean_attributes = frozenset('allowfullscreen,async,autofocus,autoplay,checked EPUB_TYPE_MAP = {k:'doc-' + k for k in ( 'abstract acknowledgements afterword appendix biblioentry bibliography biblioref chapter colophon conclusion cover credit' - ' credits dedication epigraph epilogue errata footnote footnotes forward glossary glossref index introduction noteref notice' + ' credits dedication epigraph epilogue errata footnote footnotes forward glossary glossref index introduction link noteref notice' ' pagebreak pagelist part preface prologue pullquote qna locator subtitle title toc').split(' ')} for k in 'figure term definition directory list list-item table row cell'.split(' '): EPUB_TYPE_MAP[k] = k diff --git a/src/pyj/read_book/footnotes.pyj b/src/pyj/read_book/footnotes.pyj new file mode 100644 index 0000000000..d3b47f3c41 --- /dev/null +++ b/src/pyj/read_book/footnotes.pyj @@ -0,0 +1,70 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2017, Kovid Goyal +from __python__ import bound_methods, hash_literals + + +def elem_roles(elem): + return {k.toLowerCase(): True for k in (elem.getAttribute('role') or '').split(' ')} + + +def get_containing_block(node): + while node and node.tagName and get_containing_block.block_names[node.tagName.toLowerCase()] is not True: + node = node.parentNode + return node +get_containing_block.block_names = dict.fromkeys(v"['p', 'div', 'li', 'td', 'h1', 'h2', 'h2', 'h3', 'h4', 'h5', 'h6', 'body']", True).as_object() + + +def is_footnote_link(a, dest_name, dest_frag, src_name, link_to_map): + roles = elem_roles(a) + if roles['doc-noteref']: + return True + if roles['doc-link']: + return False + + # Check if node or any of its first few parents have vertical-align set + x, num = a, 3 + while x and num > 0: + style = window.getComputedStyle(x) + if not is_footnote_link.inline_displays[style.display]: + break + if is_footnote_link.vert_aligns[style.verticalAlign]: + return True + x = x.parentNode + num -= 1 + + # Check if node has a single child with the appropriate css + children = [x for x in a.childNodes if x.nodeType is Node.ELEMENT_NODE] + if children.length == 1: + style = window.getComputedStyle(children[0]) + if is_footnote_link.inline_displays[style.display] and is_footnote_link.vert_aligns[style.verticalAlign]: + text_children = [x for x in a.childNodes if x.nodeType is Node.TEXT_NODE and x.nodeValue and /\S+/.test(x.nodeValue)] + if not text_children.length: + return True + + eid = a.getAttribute('id') or a.getAttribute('name') + files_linking_to_self = link_to_map[src_name] + if eid and files_linking_to_self: + files_linking_to_anchor = files_linking_to_self[eid] + if files_linking_to_anchor.length > 1 or (files_linking_to_anchor.length == 1 and files_linking_to_anchor[0] is not src_name): + # An link that is linked back from some other + # file in the spine, most likely an endnote. We exclude links that are + # the only content of their parent block tag, as these are not likely + # to be endnotes. + cb = get_containing_block(a) + if not cb or cb.tagName.toLowerCase() == 'body': + return False + ltext = a.textContent + if not ltext: + return False + ctext = cb.textContent + if not ctext: + return False + if ctext.strip() is ltext.strip(): + return False + return True + + return False + + +is_footnote_link.inline_displays = {'inline': True, 'inline-block': True} +is_footnote_link.vert_aligns = {'sub': True, 'super': True, 'top': True, 'bottom': True} diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index d7e27a2ae9..de49bed260 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -12,6 +12,7 @@ from read_book.flow_mode import ( flow_to_scroll_fraction, handle_gesture as flow_handle_gesture, layout as flow_layout, scroll_by_page as flow_scroll_by_page ) +from read_book.footnotes import is_footnote_link from read_book.globals import ( current_book, current_layout_mode, current_spine_item, set_boss, set_current_spine_item, set_layout_mode @@ -383,6 +384,14 @@ class IframeBoss: name, frag = data.name, data.frag if not name: name = current_spine_item().name + try: + is_popup = is_footnote_link(evt.currentTarget, name, frag, current_spine_item().name, self.book.manifest.link_to_map or {}) + except: + import traceback + traceback.print_exc() + is_popup = False + if is_popup: + pass if name is current_spine_item().name: self.replace_history_on_next_cfi_update = False self.scroll_to_anchor(frag)