diff --git a/src/calibre/srv/render_book.py b/src/calibre/srv/render_book.py index 7a22165903..68961f707f 100644 --- a/src/calibre/srv/render_book.py +++ b/src/calibre/srv/render_book.py @@ -196,11 +196,12 @@ def toc_anchor_map(toc): def pagelist_anchor_map(page_list): ans = defaultdict(list) seen_map = defaultdict(set) - for x in page_list: + for i, x in enumerate(page_list): + x['id'] = i name = x['dest'] frag = x['frag'] if name and frag not in seen_map[name]: - ans[name].append({'frag':frag}) + ans[name].append({'id': i, 'pagenum': x['pagenum'], 'frag':frag}) seen_map[name].add(frag) return dict(ans) @@ -761,7 +762,7 @@ def process_exploded_book( 'link_to_map': {}, 'page_progression_direction': page_progression_direction, 'page_list': page_list, - 'pagelist_anchor_map': pagelist_anchor_map(page_list), + 'page_list_anchor_map': pagelist_anchor_map(page_list), } names = sorted( diff --git a/src/pyj/read_book/anchor_visibility.pyj b/src/pyj/read_book/anchor_visibility.pyj index 540eee13be..5c30ec1003 100644 --- a/src/pyj/read_book/anchor_visibility.pyj +++ b/src/pyj/read_book/anchor_visibility.pyj @@ -21,7 +21,15 @@ def ensure_anchor_cache_valid(): invalidate_anchor_position_cache() -def position_for_anchor(anchor): +def ensure_page_list_target_is_displayed(elem): + # The stupid EPUB 3 examples have page list links pointing to + # display:none divs. Sigh. + if window.getComputedStyle(elem).display is 'none': + elem.textContent = '' + elem.setAttribute('style', 'all: revert') + + +def position_for_anchor(anchor, is_page_list_anchor): ensure_anchor_cache_valid() cache = anchor_position_cache.positions val = cache[anchor] @@ -29,6 +37,8 @@ def position_for_anchor(anchor): return val anchor_funcs = get_boss().anchor_funcs elem = document.getElementById(anchor) + if is_page_list_anchor: + ensure_page_list_target_is_displayed(elem) val = anchor_funcs.pos_for_elem(elem) if elem else anchor_funcs.pos_for_elem() cache[anchor] = val return val diff --git a/src/pyj/read_book/goto.pyj b/src/pyj/read_book/goto.pyj index f69e70832d..e89d007634 100644 --- a/src/pyj/read_book/goto.pyj +++ b/src/pyj/read_book/goto.pyj @@ -65,10 +65,13 @@ def create_goto_panel(current_position_data, book, container, onclick): def create_page_list_overlay(book, overlay, container): page_list = book.manifest.page_list or v'[]' items = v'[]' + + def goto(x): + overlay.view.goto_pagelist_item(x) + overlay.hide() + for x in page_list: - items.push(create_item(x['pagenum'], action=def(): - overlay.view.goto_pagelist_item(x) - overlay.hide() + items.push(create_item(x.pagenum, action=goto.bind(None, x), )) build_list(container, items) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index a04156a097..aa67d4a430 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -3,73 +3,97 @@ from __python__ import bound_methods, hash_literals import traceback +from select import first_visible_word, is_start_closer_to_point, move_end_of_selection, selection_extents, word_at_point + from fs_images import fix_fullscreen_svg_images from iframe_comm import IframeClient from range_utils import ( - all_annots_in_selection, highlight_associated_with_selection, last_span_for_crw, - reset_highlight_counter, select_crw, unwrap_all_crw, unwrap_crw, wrap_text_in_range + all_annots_in_selection, + highlight_associated_with_selection, + last_span_for_crw, + reset_highlight_counter, + select_crw, + unwrap_all_crw, + unwrap_crw, + wrap_text_in_range ) -from read_book.anchor_visibility import invalidate_anchor_position_cache +from read_book.anchor_visibility import ensure_page_list_target_is_displayed, invalidate_anchor_position_cache from read_book.cfi import cfi_for_selection, range_from_cfi from read_book.extract import get_elements -from read_book.find import ( - reset_find_caches, select_search_result, select_tts_mark, tts_data -) -from read_book.flow_mode import ( - anchor_funcs as flow_anchor_funcs, auto_scroll_action as flow_auto_scroll_action, - cancel_drag_scroll as cancel_drag_scroll_flow, - ensure_selection_boundary_visible as ensure_selection_boundary_visible_flow, - ensure_selection_visible, flow_onwheel, flow_to_scroll_fraction, - handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut, - jump_to_cfi as flow_jump_to_cfi, layout as flow_layout, - scroll_by_page as flow_scroll_by_page, - scroll_to_extend_annotation as flow_annotation_scroll, - start_drag_scroll as start_drag_scroll_flow -) +from read_book.find import reset_find_caches, select_search_result, select_tts_mark, tts_data +from read_book.flow_mode import anchor_funcs as flow_anchor_funcs +from read_book.flow_mode import auto_scroll_action as flow_auto_scroll_action +from read_book.flow_mode import cancel_drag_scroll as cancel_drag_scroll_flow +from read_book.flow_mode import ensure_selection_boundary_visible as ensure_selection_boundary_visible_flow +from read_book.flow_mode import ensure_selection_visible, flow_onwheel, flow_to_scroll_fraction +from read_book.flow_mode import handle_gesture as flow_handle_gesture +from read_book.flow_mode import handle_shortcut as flow_handle_shortcut +from read_book.flow_mode import jump_to_cfi as flow_jump_to_cfi +from read_book.flow_mode import layout as flow_layout +from read_book.flow_mode import scroll_by_page as flow_scroll_by_page +from read_book.flow_mode import scroll_to_extend_annotation as flow_annotation_scroll +from read_book.flow_mode import start_drag_scroll as start_drag_scroll_flow from read_book.footnotes import is_footnote_link from read_book.gestures import action_for_gesture from read_book.globals import ( - annot_id_uuid_map, clear_annot_id_uuid_map, current_book, current_layout_mode, - current_spine_item, runtime, set_boss, set_current_spine_item, set_layout_mode, + annot_id_uuid_map, + clear_annot_id_uuid_map, + current_book, + current_layout_mode, + current_spine_item, + runtime, + set_boss, + set_current_spine_item, + set_layout_mode, set_toc_anchor_map ) from read_book.highlights import highlight_style_as_css from read_book.hints import apply_prefix_to_hints, hint_visible_links, unhint_links from read_book.mathjax import apply_mathjax +from read_book.paged_mode import anchor_funcs as paged_anchor_funcs +from read_book.paged_mode import auto_scroll_action as paged_auto_scroll_action from read_book.paged_mode import ( - anchor_funcs as paged_anchor_funcs, auto_scroll_action as paged_auto_scroll_action, - calc_columns_per_screen, cancel_drag_scroll as cancel_drag_scroll_paged, + calc_columns_per_screen, current_cfi, - ensure_selection_boundary_visible as ensure_selection_boundary_visible_paged, - get_columns_per_screen_data, handle_gesture as paged_handle_gesture, - handle_shortcut as paged_handle_shortcut, jump_to_cfi as paged_jump_to_cfi, - layout as paged_layout, onwheel as paged_onwheel, page_counts, - prepare_for_resize as paged_prepare_for_resize, progress_frac, - reset_paged_mode_globals, resize_done as paged_resize_done, - scroll_by_page as paged_scroll_by_page, scroll_to_elem, - scroll_to_extend_annotation as paged_annotation_scroll, - scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection, - start_drag_scroll as start_drag_scroll_paged, will_columns_per_screen_change + get_columns_per_screen_data, + page_counts, + progress_frac, + reset_paged_mode_globals, + scroll_to_elem, + snap_to_selection, + will_columns_per_screen_change ) +from read_book.paged_mode import cancel_drag_scroll as cancel_drag_scroll_paged +from read_book.paged_mode import ensure_selection_boundary_visible as ensure_selection_boundary_visible_paged +from read_book.paged_mode import handle_gesture as paged_handle_gesture +from read_book.paged_mode import handle_shortcut as paged_handle_shortcut +from read_book.paged_mode import jump_to_cfi as paged_jump_to_cfi +from read_book.paged_mode import layout as paged_layout +from read_book.paged_mode import onwheel as paged_onwheel +from read_book.paged_mode import prepare_for_resize as paged_prepare_for_resize +from read_book.paged_mode import resize_done as paged_resize_done +from read_book.paged_mode import scroll_by_page as paged_scroll_by_page +from read_book.paged_mode import scroll_to_extend_annotation as paged_annotation_scroll +from read_book.paged_mode import scroll_to_fraction as paged_scroll_to_fraction +from read_book.paged_mode import start_drag_scroll as start_drag_scroll_paged from read_book.referencing import elem_for_ref, end_reference_mode, start_reference_mode from read_book.resources import finalize_resources, unserialize_html from read_book.settings import ( - apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts, - set_color_scheme_class, set_selection_style, update_settings + apply_colors, + apply_font_size, + apply_settings, + apply_stylesheet, + opts, + set_color_scheme_class, + set_selection_style, + update_settings ) -from read_book.shortcuts import ( - create_shortcut_map, keyevent_as_shortcut, shortcut_for_key_event -) -from read_book.smil import flatten_smil_map, smil_element_at, mark_smil_element +from read_book.shortcuts import create_shortcut_map, keyevent_as_shortcut, shortcut_for_key_event +from read_book.smil import flatten_smil_map, mark_smil_element, smil_element_at from read_book.toc import find_anchor_before_range, update_visible_toc_anchors -from read_book.touch import ( - create_handlers as create_touch_handlers, reset_handlers as reset_touch_handlers -) +from read_book.touch import create_handlers as create_touch_handlers +from read_book.touch import reset_handlers as reset_touch_handlers from read_book.viewport import scroll_viewport -from select import ( - first_visible_word, is_start_closer_to_point, move_end_of_selection, - selection_extents, word_at_point -) from utils import debounce, is_ios FORCE_FLOW_MODE = False @@ -560,7 +584,7 @@ class IframeBoss: 'update_progress_frac', progress_frac=pf, file_progress_frac=fpf, page_counts=page_counts()) def update_toc_position(self, recalculate_toc_anchor_positions): - visible_anchors = update_visible_toc_anchors(self.book.manifest.toc_anchor_map, bool(recalculate_toc_anchor_positions)) + visible_anchors = update_visible_toc_anchors(self.book.manifest.toc_anchor_map, self.book.manifest.page_list_anchor_map, bool(recalculate_toc_anchor_positions)) self.send_message('update_toc_position', visible_anchors=visible_anchors) def onscroll(self): @@ -767,12 +791,7 @@ class IframeBoss: if c and c.length: elem = c[0] if elem: - # The stupid EPUB 3 examples have page list links pointing to - # display:none divs. Sigh. - if window.getComputedStyle(elem).display is 'none': - elem.textContent = '' - elem.setAttribute('style', 'all: revert') - elem.style.display = 'block' + ensure_page_list_target_is_displayed(elem) scroll_to_elem(elem) def scroll_to_ref(self, refnum): @@ -841,7 +860,7 @@ class IframeBoss: sel = window.getSelection() if not sel.rangeCount: return - anchor_before = find_anchor_before_range(sel.getRangeAt(0), self.book.manifest.toc_anchor_map) + anchor_before = find_anchor_before_range(sel.getRangeAt(0), self.book.manifest.toc_anchor_map, self.book.manifest.page_list_anchor_map) text = sel.toString() bounds = cfi_for_selection() style = highlight_style_as_css(style, opts.is_dark_theme, opts.color_scheme.foreground) diff --git a/src/pyj/read_book/prefs/head_foot.pyj b/src/pyj/read_book/prefs/head_foot.pyj index 40bbdd5388..dc1bd8ced1 100644 --- a/src/pyj/read_book/prefs/head_foot.pyj +++ b/src/pyj/read_book/prefs/head_foot.pyj @@ -48,6 +48,7 @@ def create_item(region, label, style): opt(_('Position in book'), 'pos-book'), opt(_('Position in chapter'), 'pos-chapter'), opt(_('Pages in chapter'), 'pages-progress'), + opt(_('Pages from paper edition'), 'page-list'), )) ) @@ -183,7 +184,7 @@ def format_pos(progress_frac, length): return f'{pos:.1f} / {pages}' -def render_head_foot(div, which, region, metadata, current_toc_node, current_toc_toplevel_node, book_time, chapter_time, pos, override, view_mode): +def render_head_foot(div, which, region, metadata, current_toc_node, current_toc_toplevel_node, page_list, book_time, chapter_time, pos, override, view_mode): template = get_session_data().get(which) or {} field = template[region] or 'empty' interface_data = get_interface_data() @@ -244,6 +245,12 @@ def render_head_foot(div, which, region, metadata, current_toc_node, current_toc text = f'{pos.page_counts.current + 1} / {pos.page_counts.total}' else: text = _('Unknown') + elif field is 'page-list': + if page_list and page_list.length > 0: + if page_list.length is 1: + text = page_list[0].pagenum + else: + text = f'{page_list[0].pagenum} - {page_list[-1].pagenum}' if not text: text = '\xa0' if text is not div.textContent: diff --git a/src/pyj/read_book/toc.pyj b/src/pyj/read_book/toc.pyj index f930014de7..fb93e679bb 100644 --- a/src/pyj/read_book/toc.pyj +++ b/src/pyj/read_book/toc.pyj @@ -91,6 +91,52 @@ def family_for_toc_node(toc_node_id, parent_map, id_map): return family +def get_page_list_id_map(page_list): + return {x.id: x for x in page_list} + + +def get_page_list_before(page_list, id_map): + # Find the page list entries that point to the closest files on either side of the spine item + csi = csi or current_spine_item() + spine = current_book().manifest.spine + spine_before, spine_after = {}, {} + which = spine_before + before = prev = None + for name in spine: + if name is csi: + which = spine_after + else: + which[name] = True + for item in page_list: + if item.dest: + if spine_before[item.dest]: + prev = item + elif spine_after[item.dest]: + if not before: + before = prev + break + return before + + +def get_current_pagelist_items(): + ans = {} + page_list = current_book().manifest.page_list + data = update_visible_toc_nodes.data?.page_list + if not data or not page_list: + return ans + id_map = get_page_list_id_map(page_list) + if data.has_visible: + ans = data.visible_anchors + else: + if data.before?: + ans[data.before] = True + else: + before = get_page_list_before(page_list, id_map) + if before?: + ans[before.id] = True + return [id_map[x] for x in Object.keys(ans)] + + def get_current_toc_nodes(): toc = current_book().manifest.toc parent_map, id_map = get_toc_maps(toc) @@ -203,7 +249,7 @@ def create_toc_panel(book, container, onclick): toc_panel.style.flexShrink = '1' toc_panel.style.overflow = 'auto' -def recalculate_toc_anchor_positions(tam): +def recalculate_toc_anchor_positions(tam, plam): name = current_spine_item().name am = {} anchors = v'[]' @@ -217,25 +263,39 @@ def recalculate_toc_anchor_positions(tam): anchors.sort(def (a, b): return anchor_funcs.cmp(am[a], am[b]) or (pos_map[a] - pos_map[b]);) current_map = {'layout_mode': current_layout_mode(), 'width': scroll_viewport.width(), 'height': scroll_viewport.height(), 'pos_map': am, 'sorted_anchors':anchors} + am = {} + anchors = v'[]' + pos_map = {} + if plam: + for i, anchor in enumerate(plam[name] or v'[]'): + am[anchor.id] = position_for_anchor(anchor.frag, True) + anchors.push(anchor.id) + pos_map[anchor.id] = i + # stable sort by position in document + anchors.sort(def (a, b): return anchor_funcs.cmp(am[a], am[b]) or (pos_map[a] - pos_map[b]);) + current_map.page_list_pos_map = am + current_map.page_list_sorted_anchors = anchors + set_toc_anchor_map(current_map) return current_map -def current_toc_anchor_map(tam): +def current_toc_anchor_map(tam, plam): current_map = toc_anchor_map() if not (current_map and current_map.layout_mode is current_layout_mode() and current_map.width is scroll_viewport.width() and current_map.height is scroll_viewport.height()): - current_map = recalculate_toc_anchor_positions(tam) + current_map = recalculate_toc_anchor_positions(tam, plam) return current_map -def update_visible_toc_anchors(toc_anchor_map, recalculate): +def update_visible_toc_anchors(toc_anchor_map, page_list_anchor_map, recalculate): if recalculate: - recalculate_toc_anchor_positions(toc_anchor_map) - tam = current_toc_anchor_map(toc_anchor_map) + recalculate_toc_anchor_positions(toc_anchor_map, page_list_anchor_map) + anchor_funcs = get_boss().anchor_funcs + + tam = current_toc_anchor_map(toc_anchor_map, page_list_anchor_map) before = after = None visible_anchors = {} has_visible = False - anchor_funcs = get_boss().anchor_funcs for anchor_id in tam.sorted_anchors: pos = tam.pos_map[anchor_id] @@ -248,14 +308,30 @@ def update_visible_toc_anchors(toc_anchor_map, recalculate): elif visibility > 0: after = anchor_id break + ans = {'visible_anchors':visible_anchors, 'has_visible':has_visible, 'before':before, 'after':after, 'sorted_anchors':tam.sorted_anchors} - return {'visible_anchors':visible_anchors, 'has_visible':has_visible, 'before':before, 'after':after, 'sorted_anchors':tam.sorted_anchors} + before = after = None + visible_anchors = {} + has_visible = False + for anchor_id in tam.page_list_sorted_anchors: + pos = tam.page_list_pos_map[anchor_id] + visibility = anchor_funcs.visibility(pos) + if visibility < 0: + before = anchor_id + elif visibility is 0: + has_visible = True + visible_anchors[anchor_id] = True + elif visibility > 0: + after = anchor_id + break + ans.page_list = {'visible_anchors':visible_anchors, 'has_visible':has_visible, 'before':before, 'after':after, 'sorted_anchors':tam.page_list_sorted_anchors} + return ans -def find_anchor_before_range(r, toc_anchor_map): +def find_anchor_before_range(r, toc_anchor_map, page_list_anchor_map): name = current_spine_item().name prev_anchor = None - tam = current_toc_anchor_map(toc_anchor_map) + tam = current_toc_anchor_map(toc_anchor_map, page_list_anchor_map) anchors = toc_anchor_map[name] if anchors: amap = {x.id: x for x in anchors} diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 0e8a38278d..a203d63e15 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -43,7 +43,7 @@ from read_book.selection_bar import SelectionBar from read_book.shortcuts import create_shortcut_map from read_book.smil import get_smil_audio_map from read_book.timers import Timers -from read_book.toc import get_current_toc_nodes, update_visible_toc_nodes +from read_book.toc import get_current_toc_nodes, update_visible_toc_nodes, get_current_pagelist_items from read_book.touch import set_left_margin_handler, set_right_margin_handler from session import get_device_uuid, get_interface_data from utils import ( @@ -1323,7 +1323,7 @@ class View: view_mode = sd.get('read_mode') def render(div, name, which, override): - return render_head_foot(div, name, which, mi, self.current_toc_node, self.current_toc_toplevel_node, book_time, chapter_time, pos, override, view_mode) + return render_head_foot(div, name, which, mi, self.current_toc_node, self.current_toc_toplevel_node, self.current_pagelist_items, book_time, chapter_time, pos, override, view_mode) return render @@ -1365,6 +1365,7 @@ class View: def on_update_toc_position(self, data): update_visible_toc_nodes(data.visible_anchors) self.current_toc_families = get_current_toc_nodes() + self.current_pagelist_items = get_current_pagelist_items() if self.current_toc_families.length: first = self.current_toc_families[0] self.current_toc_node = first[-1]