mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
More work on paged mode
This commit is contained in:
parent
d6624cdc33
commit
01d2572a9f
@ -8,10 +8,12 @@ from gettext import install
|
|||||||
from read_book.globals import set_boss, set_current_spine_item, current_layout_mode, current_spine_item, set_layout_mode
|
from read_book.globals import set_boss, set_current_spine_item, current_layout_mode, current_spine_item, set_layout_mode
|
||||||
from read_book.resources import finalize_resources, unserialize_html
|
from read_book.resources import finalize_resources, unserialize_html
|
||||||
from read_book.flow_mode import flow_to_scroll_fraction, flow_onwheel, flow_onkeydown, layout as flow_layout
|
from read_book.flow_mode import flow_to_scroll_fraction, flow_onwheel, flow_onkeydown, layout as flow_layout
|
||||||
from read_book.paged_mode import layout as paged_layout, scroll_to_fraction as paged_scroll_to_fraction
|
from read_book.paged_mode import layout as paged_layout, scroll_to_fraction as paged_scroll_to_fraction, onwheel as paged_onwheel, onkeydown as paged_onkeydown
|
||||||
from read_book.settings import apply_settings
|
from read_book.settings import apply_settings
|
||||||
from utils import debounce
|
from utils import debounce
|
||||||
|
|
||||||
|
FORCE_FLOW_MODE = False
|
||||||
|
|
||||||
class Boss:
|
class Boss:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -62,7 +64,7 @@ class Boss:
|
|||||||
self.book = data.book
|
self.book = data.book
|
||||||
spine = self.book.manifest.spine
|
spine = self.book.manifest.spine
|
||||||
index = spine.indexOf(data.name)
|
index = spine.indexOf(data.name)
|
||||||
set_layout_mode('flow' or data.settings.read_mode) # TODO: Change this
|
set_layout_mode('flow' if FORCE_FLOW_MODE else data.settings.read_mode)
|
||||||
apply_settings(data.settings)
|
apply_settings(data.settings)
|
||||||
set_current_spine_item({'name':data.name, 'is_first':index is 0, 'is_last':index is spine.length - 1, 'initial_scroll_fraction':data.initial_scroll_fraction})
|
set_current_spine_item({'name':data.name, 'is_first':index is 0, 'is_last':index is spine.length - 1, 'initial_scroll_fraction':data.initial_scroll_fraction})
|
||||||
root_data = finalize_resources(self.book, data.name, data.resource_data)
|
root_data = finalize_resources(self.book, data.name, data.resource_data)
|
||||||
@ -95,10 +97,14 @@ class Boss:
|
|||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
if current_layout_mode() is 'flow':
|
if current_layout_mode() is 'flow':
|
||||||
flow_onwheel(evt)
|
flow_onwheel(evt)
|
||||||
|
else:
|
||||||
|
paged_onwheel(evt)
|
||||||
|
|
||||||
def onkeydown(self, evt):
|
def onkeydown(self, evt):
|
||||||
if current_layout_mode() is 'flow':
|
if current_layout_mode() is 'flow':
|
||||||
flow_onkeydown(evt)
|
flow_onkeydown(evt)
|
||||||
|
else:
|
||||||
|
paged_onkeydown(evt)
|
||||||
|
|
||||||
def send_message(self, action, **data):
|
def send_message(self, action, **data):
|
||||||
data.action = action
|
data.action = action
|
||||||
|
@ -49,12 +49,15 @@ def create_page_div(elem):
|
|||||||
_in_paged_mode = False
|
_in_paged_mode = False
|
||||||
def in_paged_mode():
|
def in_paged_mode():
|
||||||
return _in_paged_mode
|
return _in_paged_mode
|
||||||
col_width = page_width = screen_width = side_margin = page_height = margin_top = margin_bottom = cols_per_screen = 0
|
col_width = screen_width = screen_height = cols_per_screen = gap = col_and_gap = 0
|
||||||
is_full_screen_layout = False
|
is_full_screen_layout = False
|
||||||
|
|
||||||
def column_at(xpos):
|
def column_at(xpos):
|
||||||
# Return the number of the column that contains xpos
|
# Return the (zero-based) number of the column that contains xpos
|
||||||
return xpos // page_width
|
sw = document.body.scrollWidth
|
||||||
|
if xpos >= sw - col_and_gap:
|
||||||
|
xpos = sw - col_width + 10
|
||||||
|
return (xpos + gap) // col_and_gap
|
||||||
|
|
||||||
def fit_images():
|
def fit_images():
|
||||||
# Ensure no images are wider than the available width in a column. Note
|
# Ensure no images are wider than the available width in a column. Note
|
||||||
@ -62,7 +65,7 @@ def fit_images():
|
|||||||
# force a relayout if the render tree is dirty.
|
# force a relayout if the render tree is dirty.
|
||||||
images = []
|
images = []
|
||||||
vimages = []
|
vimages = []
|
||||||
maxh = page_height
|
maxh = screen_height
|
||||||
for img in document.getElementsByTagName('img'):
|
for img in document.getElementsByTagName('img'):
|
||||||
previously_limited = get_elem_data(img, 'width-limited', False)
|
previously_limited = get_elem_data(img, 'width-limited', False)
|
||||||
data = get_elem_data(img, 'img-data', None)
|
data = get_elem_data(img, 'img-data', None)
|
||||||
@ -71,11 +74,10 @@ def fit_images():
|
|||||||
data = {'left':br.left, 'right':br.right, 'height':br.height, 'display': img.style.display}
|
data = {'left':br.left, 'right':br.right, 'height':br.height, 'display': img.style.display}
|
||||||
set_elem_data(img, 'img-data', data)
|
set_elem_data(img, 'img-data', data)
|
||||||
left = viewport_to_document(br.left, 0, img.ownerDocument)[0]
|
left = viewport_to_document(br.left, 0, img.ownerDocument)[0]
|
||||||
col = column_at(left) * page_width
|
col = column_at(left) * col_and_gap
|
||||||
rleft = left - col - side_margin
|
rleft = left - col
|
||||||
width = br.right - br.left
|
width = br.right - br.left
|
||||||
rright = rleft + width
|
rright = rleft + width
|
||||||
col_width = page_width - 2*side_margin
|
|
||||||
if previously_limited or rright > col_width:
|
if previously_limited or rright > col_width:
|
||||||
images.push([img, col_width - rleft])
|
images.push([img, col_width - rleft])
|
||||||
previously_limited = get_elem_data(img, 'height-limited', False)
|
previously_limited = get_elem_data(img, 'height-limited', False)
|
||||||
@ -101,7 +103,7 @@ def fit_images():
|
|||||||
|
|
||||||
|
|
||||||
def layout(is_single_page):
|
def layout(is_single_page):
|
||||||
nonlocal _in_paged_mode, col_width, page_width, page_height, side_margin, screen_width, margin_top, margin_bottom, is_full_screen_layout, cols_per_screen
|
nonlocal _in_paged_mode, col_width, col_and_gap, screen_height, gap, screen_width, is_full_screen_layout, cols_per_screen
|
||||||
body_style = window.getComputedStyle(document.body)
|
body_style = window.getComputedStyle(document.body)
|
||||||
first_layout = not _in_paged_mode
|
first_layout = not _in_paged_mode
|
||||||
if first_layout:
|
if first_layout:
|
||||||
@ -116,35 +118,25 @@ def layout(is_single_page):
|
|||||||
num -= 1
|
num -= 1
|
||||||
create_page_div()
|
create_page_div()
|
||||||
|
|
||||||
ww = window.innerWidth
|
|
||||||
|
|
||||||
# Calculate the column width so that cols_per_screen columns fit in the
|
|
||||||
# window in such a way the right margin of the last column is <=
|
|
||||||
# side_margin (it may be less if the window width is not a
|
|
||||||
# multiple of n*(col_width+2*side_margin).
|
|
||||||
|
|
||||||
n = cols_per_screen = opts.cols_per_screen
|
n = cols_per_screen = opts.cols_per_screen
|
||||||
adjust = ww - (ww // n) * n
|
# Calculate the column width so that cols_per_screen columns fit exactly in
|
||||||
# Ensure that the margins are large enough that the adjustment does not
|
# the window width, with their separator margins
|
||||||
# cause them to become negative semidefinite
|
ww = col_width = screen_width = window.innerWidth
|
||||||
sm = max(2*adjust, opts.margin_side)
|
gap = 0
|
||||||
# Minimum column width, for the cases when the window is too
|
if n > 1:
|
||||||
# narrow
|
# Adjust the side margin so that the window width satisfies
|
||||||
col_width = max(100, ((ww - adjust)/n) - 2*sm)
|
# col_width * n + (n-1) * 2 * side_margin = window_width
|
||||||
if opts.max_col_width > 0 and col_width > opts.max_col_width:
|
sm = opts.margin_side or 0
|
||||||
# Increase the side margin to ensure that col_width is no larger
|
gap = 2*sm + ((ww + 2*sm) % n) # Ensure ww + gap is a multiple of n
|
||||||
# than opts.max_col_width
|
col_width = ((ww + gap) // n) - gap
|
||||||
sm += Math.ceil( (col_width - opts.max_col_width) / 2*n )
|
|
||||||
col_width = max(100, ((ww - adjust)/n) - 2*sm)
|
|
||||||
page_width = col_width + 2*sm
|
|
||||||
side_margin = sm
|
|
||||||
screen_width = page_width * n
|
|
||||||
page_height = window.innerHeight
|
|
||||||
|
|
||||||
set_css(document.body, column_gap=2*sm + 'px', column_width=col_width + 'px', column_rule='0px inset blue',
|
screen_height = window.innerHeight
|
||||||
|
col_and_gap = col_width + gap
|
||||||
|
|
||||||
|
set_css(document.body, column_gap=gap + 'px', column_width=col_width + 'px', column_rule='0px inset blue',
|
||||||
min_width='0', max_width='none', min_height='0', max_height='none',
|
min_width='0', max_width='none', min_height='0', max_height='none',
|
||||||
margin='0', border_width='0', padding='0 ' + sm + 'px',
|
margin='0', border_width='0', padding='0',
|
||||||
width=(window.innerWidth - 2*sm) + 'px', height=window.innerHeight + 'px'
|
width=screen_width + 'px', height=screen_height + 'px'
|
||||||
)
|
)
|
||||||
# Without this, webkit bleeds the margin of the first block(s) of body
|
# Without this, webkit bleeds the margin of the first block(s) of body
|
||||||
# above the columns, which causes them to effectively be added to the
|
# above the columns, which causes them to effectively be added to the
|
||||||
@ -176,12 +168,7 @@ def layout(is_single_page):
|
|||||||
# with height=100% overflow the first column
|
# with height=100% overflow the first column
|
||||||
has_svg = document.getElementsByTagName('svg').length > 0
|
has_svg = document.getElementsByTagName('svg').length > 0
|
||||||
only_img = document.getElementsByTagName('img').length is 1 and document.getElementsByTagName('div').length < 3 and document.getElementsByTagName('p').length < 2
|
only_img = document.getElementsByTagName('img').length is 1 and document.getElementsByTagName('div').length < 3 and document.getElementsByTagName('p').length < 2
|
||||||
# We only set full_screen_layout if scrollWidth is in (body_width,
|
is_full_screen_layout = (only_img or has_svg) and single_screen and (document.body.scrollWidth < 2*ww + 10)
|
||||||
# 2*body_width) as if it is <= body_width scrolling will work
|
|
||||||
# anyway and if it is >= 2*body_width it can't be a full screen
|
|
||||||
# layout
|
|
||||||
body_width = document.body.offsetWidth + 2 * sm
|
|
||||||
is_full_screen_layout = (only_img or has_svg) and single_screen and (document.body.scrollWidth <= body_width or (body_width < document.body.scrollWidth < 2 * body_width))
|
|
||||||
if is_single_page:
|
if is_single_page:
|
||||||
is_full_screen_layout = True
|
is_full_screen_layout = True
|
||||||
|
|
||||||
@ -191,21 +178,24 @@ def layout(is_single_page):
|
|||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure the body width is an exact multiple of the column widths so that
|
ncols = (document.body.scrollWidth + gap) / col_and_gap
|
||||||
# the browser does not adjust the column widths
|
if ncols is not Math.floor(ncols):
|
||||||
# body_width = ncols * col_width + (ncols-1) * 2 * sm
|
n = Math.floor(ncols)
|
||||||
ncols = document.body.scrollWidth / page_width
|
dw = (n*col_width + (n-1)*gap)
|
||||||
if ncols is not Math.floor(ncols) and not is_full_screen_layout:
|
document.body.width = dw + 'px'
|
||||||
set_css(document.body, width=Math.floor(ncols) * page_width - 2 * sm)
|
print('WARNING: column layout broken', {'col_with':col_width, 'gap':gap, 'scrollWidth':document.body.scrollWidth, 'ncols':ncols, 'delta':document.body.scrollWidth - dw})
|
||||||
|
|
||||||
_in_paged_mode = True
|
_in_paged_mode = True
|
||||||
fit_images()
|
fit_images()
|
||||||
return sm
|
return gap
|
||||||
|
|
||||||
def scroll_to_fraction(frac):
|
def scroll_to_column(number, animated=False, notify=False, duration=1000):
|
||||||
# Scroll to the position represented by frac (number between 0 and 1)
|
pos = number * col_and_gap
|
||||||
xpos = Math.floor(document.body.scrollWidth * frac)
|
limit = document.body.scrollWidth - screen_width
|
||||||
scroll_to_xpos(xpos)
|
pos = min(pos, limit)
|
||||||
|
if animated:
|
||||||
|
animated_scroll(pos, duration=1000, notify=notify)
|
||||||
|
else:
|
||||||
|
window.scrollTo(pos, 0)
|
||||||
|
|
||||||
def scroll_to_xpos(xpos, animated=False, notify=False, duration=1000):
|
def scroll_to_xpos(xpos, animated=False, notify=False, duration=1000):
|
||||||
# Scroll so that the column containing xpos is the left most column in
|
# Scroll so that the column containing xpos is the left most column in
|
||||||
@ -216,16 +206,12 @@ def scroll_to_xpos(xpos, animated=False, notify=False, duration=1000):
|
|||||||
if is_full_screen_layout:
|
if is_full_screen_layout:
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
return
|
return
|
||||||
pos = Math.floor(xpos/page_width) * page_width
|
scroll_to_column(column_at(xpos), animated=animated, notify=notify, duration=duration)
|
||||||
limit = document.body.scrollWidth - screen_width
|
|
||||||
pos = min(pos, limit)
|
|
||||||
if animated:
|
|
||||||
animated_scroll(pos, duration=1000, notify=notify)
|
|
||||||
else:
|
|
||||||
window.scrollTo(pos, 0)
|
|
||||||
|
|
||||||
def scroll_to_column(number):
|
def scroll_to_fraction(frac):
|
||||||
scroll_to_xpos(number * page_width + 10)
|
# Scroll to the position represented by frac (number between 0 and 1)
|
||||||
|
xpos = Math.floor(document.body.scrollWidth * frac)
|
||||||
|
scroll_to_xpos(xpos)
|
||||||
|
|
||||||
current_scroll_animation = None
|
current_scroll_animation = None
|
||||||
|
|
||||||
@ -267,11 +253,11 @@ def column_location(elem):
|
|||||||
else:
|
else:
|
||||||
left, top = viewport_to_document(br.left, br.top, elem.ownerDocument)
|
left, top = viewport_to_document(br.left, br.top, elem.ownerDocument)
|
||||||
c = column_at(left)
|
c = column_at(left)
|
||||||
width = min(br.right, (c+1)*page_width) - br.left
|
width = min(br.right, (c+1)*col_and_gap) - br.left
|
||||||
if br.bottom < br.top:
|
if br.bottom < br.top:
|
||||||
br.bottom = window.innerHeight
|
br.bottom = window.innerHeight
|
||||||
height = min(br.bottom, window.innerHeight) - br.top
|
height = min(br.bottom, window.innerHeight) - br.top
|
||||||
left -= c*page_width
|
left -= c * col_and_gap
|
||||||
return {'column':c, 'left':left, 'top':top, 'width':width, 'height':height}
|
return {'column':c, 'left':left, 'top':top, 'width':width, 'height':height}
|
||||||
|
|
||||||
def column_boundaries():
|
def column_boundaries():
|
||||||
@ -292,8 +278,8 @@ def current_column_location():
|
|||||||
# visible in the viewport
|
# visible in the viewport
|
||||||
if is_full_screen_layout:
|
if is_full_screen_layout:
|
||||||
return 0
|
return 0
|
||||||
x = window.pageXOffset + max(10, side_margin)
|
c = column_at(window.pageXOffset + 10)
|
||||||
return Math.floor(x/page_width) * page_width
|
return c * col_and_gap
|
||||||
|
|
||||||
def next_screen_location():
|
def next_screen_location():
|
||||||
# The position to scroll to for the next screen (which could contain
|
# The position to scroll to for the next screen (which could contain
|
||||||
@ -303,9 +289,10 @@ def next_screen_location():
|
|||||||
cc = current_column_location()
|
cc = current_column_location()
|
||||||
ans = cc + screen_width
|
ans = cc + screen_width
|
||||||
if cols_per_screen > 1:
|
if cols_per_screen > 1:
|
||||||
width_left = document.body.scrollWidth - (window.pageXOffset + window.innerWidth)
|
current_col = column_at(window.pageXOffset + 10)
|
||||||
pages_left = width_left / page_width
|
ncols = (document.body.scrollWidth + gap) // col_and_gap
|
||||||
if Math.ceil(pages_left) < cols_per_screen:
|
cols_left = ncols - (current_col + cols_per_screen)
|
||||||
|
if cols_left < cols_per_screen:
|
||||||
return -1 # Only blank, dummy pages left
|
return -1 # Only blank, dummy pages left
|
||||||
limit = document.body.scrollWidth - window.innerWidth
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
if ans > limit:
|
if ans > limit:
|
||||||
@ -332,7 +319,7 @@ def next_col_location():
|
|||||||
if is_full_screen_layout:
|
if is_full_screen_layout:
|
||||||
return -1
|
return -1
|
||||||
cc = current_column_location()
|
cc = current_column_location()
|
||||||
ans = cc + page_width
|
ans = cc + col_and_gap
|
||||||
limit = document.body.scrollWidth - window.innerWidth
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
if ans > limit:
|
if ans > limit:
|
||||||
ans = limit if window.pageXOffset < limit else -1
|
ans = limit if window.pageXOffset < limit else -1
|
||||||
@ -345,7 +332,7 @@ def previous_col_location():
|
|||||||
if is_full_screen_layout:
|
if is_full_screen_layout:
|
||||||
return -1
|
return -1
|
||||||
cc = current_column_location()
|
cc = current_column_location()
|
||||||
ans = cc - page_width
|
ans = cc - col_and_gap
|
||||||
if ans < 0:
|
if ans < 0:
|
||||||
ans = 0 if window.pageXOffset > 0 else -1
|
ans = 0 if window.pageXOffset > 0 else -1
|
||||||
return ans
|
return ans
|
||||||
@ -390,7 +377,7 @@ def jump_to_anchor(name):
|
|||||||
else:
|
else:
|
||||||
left = br.left
|
left = br.left
|
||||||
scroll_to_xpos(viewport_to_document(
|
scroll_to_xpos(viewport_to_document(
|
||||||
left+side_margin, elem.scrollTop, elem.ownerDocument)[0])
|
left+2, elem.scrollTop, elem.ownerDocument)[0])
|
||||||
|
|
||||||
def snap_to_selection():
|
def snap_to_selection():
|
||||||
# Ensure that the viewport is positioned at the start of the column
|
# Ensure that the viewport is positioned at the start of the column
|
||||||
@ -430,18 +417,18 @@ def current_cfi():
|
|||||||
# return ans
|
# return ans
|
||||||
if in_paged_mode:
|
if in_paged_mode:
|
||||||
c = current_column_location()
|
c = current_column_location()
|
||||||
for x in c, c-page_width, c+page_width:
|
for x in c, c - col_and_gap, c + col_and_gap:
|
||||||
# Try the current column, the previous column and the next
|
# Try the current column, the previous column and the next
|
||||||
# column. Each column is tried from top to bottom.
|
# column. Each column is tried from top to bottom.
|
||||||
left, right = x, x + page_width
|
left, right = x, x + col_and_gap
|
||||||
if left < 0 or right > document.body.scrollWidth:
|
if left < 0 or right > document.body.scrollWidth:
|
||||||
continue
|
continue
|
||||||
deltax = page_width // 25
|
deltax = col_and_gap // 25
|
||||||
deltay = window.innerHeight // 25
|
deltay = window.innerHeight // 25
|
||||||
cury = 0
|
cury = 0
|
||||||
while cury < window.innerHeight:
|
while cury < window.innerHeight:
|
||||||
curx = left + side_margin
|
curx = left
|
||||||
while curx < right - side_margin:
|
while curx < right - gap:
|
||||||
cfi = cfi_at_point(curx-window.pageXOffset, cury-window.pageYOffset)
|
cfi = cfi_at_point(curx-window.pageXOffset, cury-window.pageYOffset)
|
||||||
if cfi:
|
if cfi:
|
||||||
# print('Viewport cfi:', cfi)
|
# print('Viewport cfi:', cfi)
|
||||||
@ -465,3 +452,33 @@ def onwheel(evt):
|
|||||||
get_boss().send_message('next_spine_item', previous=backward)
|
get_boss().send_message('next_spine_item', previous=backward)
|
||||||
else:
|
else:
|
||||||
scroll_to_xpos(x)
|
scroll_to_xpos(x)
|
||||||
|
|
||||||
|
def onkeydown(evt):
|
||||||
|
handled = False
|
||||||
|
key = get_key(evt)
|
||||||
|
if key is 'up' or key is 'down':
|
||||||
|
handled = True
|
||||||
|
if evt.ctrlKey:
|
||||||
|
goto_boundary(-1 if key is 'up' else 1)
|
||||||
|
else:
|
||||||
|
smooth_y_scroll(key is 'up')
|
||||||
|
elif key is 'left' or key is 'right':
|
||||||
|
handled = True
|
||||||
|
if evt.ctrlKey:
|
||||||
|
window.scrollTo(0 if key is 'left' else document_width(), window.pageYOffset)
|
||||||
|
else:
|
||||||
|
window.scrollBy(-15 if key is 'left' else 15, 0)
|
||||||
|
elif key is 'home' or key is 'end':
|
||||||
|
handled = True
|
||||||
|
if evt.ctrlKey:
|
||||||
|
get_boss().send_message('goto_doc_boundary', start=key is 'home')
|
||||||
|
else:
|
||||||
|
if key is 'home':
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
else:
|
||||||
|
window.scrollTo(document.body.scrollWidth, 0)
|
||||||
|
elif key is 'pageup' or key is 'pagedown' or key is 'space':
|
||||||
|
handled = True
|
||||||
|
scroll_by_page(-1 if key is 'pageup' else 1)
|
||||||
|
if handled:
|
||||||
|
evt.preventDefault()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user