Allow displaying pagelist data in header/footer

Fixes #2075554 [Feature request: Display of page-list element in the book viewer](https://bugs.launchpad.net/calibre/+bug/2075554)
This commit is contained in:
Kovid Goyal 2024-08-22 17:05:45 +05:30
parent 2d2d203862
commit 4985bb2e82
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 189 additions and 72 deletions

View File

@ -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(

View File

@ -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

View File

@ -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'[]'
for x in page_list:
items.push(create_item(x['pagenum'], action=def():
def goto(x):
overlay.view.goto_pagelist_item(x)
overlay.hide()
for x in page_list:
items.push(create_item(x.pagenum, action=goto.bind(None, x),
))
build_list(container, items)

View File

@ -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)

View File

@ -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:

View File

@ -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}

View File

@ -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]