mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Port the paged layout code to RapydScript
This commit is contained in:
parent
2fe618b4c1
commit
3f34835bac
@ -8,6 +8,7 @@ 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.settings import apply_settings
|
from read_book.settings import apply_settings
|
||||||
from utils import debounce
|
from utils import debounce
|
||||||
|
|
||||||
@ -75,10 +76,14 @@ class Boss:
|
|||||||
window.addEventListener('keydown', self.onkeydown)
|
window.addEventListener('keydown', self.onkeydown)
|
||||||
if current_layout_mode() is 'flow':
|
if current_layout_mode() is 'flow':
|
||||||
flow_layout()
|
flow_layout()
|
||||||
|
else:
|
||||||
|
paged_layout()
|
||||||
csi = current_spine_item()
|
csi = current_spine_item()
|
||||||
if csi.initial_scroll_fraction is not None:
|
if csi.initial_scroll_fraction is not None:
|
||||||
if current_layout_mode() is 'flow':
|
if current_layout_mode() is 'flow':
|
||||||
flow_to_scroll_fraction(csi.initial_scroll_fraction)
|
flow_to_scroll_fraction(csi.initial_scroll_fraction)
|
||||||
|
else:
|
||||||
|
paged_scroll_to_fraction(csi.initial_scroll_fraction)
|
||||||
|
|
||||||
def update_cfi(self):
|
def update_cfi(self):
|
||||||
pass # TODO: Update CFI
|
pass # TODO: Update CFI
|
||||||
|
456
src/pyj/read_book/paged_mode.pyj
Normal file
456
src/pyj/read_book/paged_mode.pyj
Normal file
@ -0,0 +1,456 @@
|
|||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
from __python__ import hash_literals
|
||||||
|
|
||||||
|
from dom import set_css
|
||||||
|
from elementmaker import E
|
||||||
|
from keycodes import get_key
|
||||||
|
from read_book.cfi import scroll_to as cfi_scroll_to, at_point as cfi_at_point, at_current as cfi_at_current
|
||||||
|
from read_book.settings import opts
|
||||||
|
import traceback
|
||||||
|
from utils import get_elem_data, set_elem_data, viewport_to_document
|
||||||
|
|
||||||
|
def first_child(parent):
|
||||||
|
c = parent.firstChild
|
||||||
|
count = 0
|
||||||
|
while c?.nodeType is not Node.ELEMENT_NODE and count < 20:
|
||||||
|
c = c?.nextSibling
|
||||||
|
count += 1
|
||||||
|
if c?.nodeType is Node.ELEMENT_NODE:
|
||||||
|
return c
|
||||||
|
|
||||||
|
def has_start_text(elem):
|
||||||
|
# Returns true if elem has some non-whitespace text before its first child
|
||||||
|
# element
|
||||||
|
for c in elem.childNodes:
|
||||||
|
if c.nodeType is not Node.TEXT_NODE:
|
||||||
|
break
|
||||||
|
if c.nodeType is Node.TEXT_NODE and c.nodeValue and /\S/.test(c.nodeValue):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def handle_rtl_body(body_style):
|
||||||
|
# Make the body and root nodes have direction ltr so that column layout
|
||||||
|
# works as expected
|
||||||
|
if body_style.direction is "rtl":
|
||||||
|
for node in document.body.childNodes:
|
||||||
|
if node.nodeType is Node.ELEMENT_NODE and window.getComputedStyle(node).direction is "rtl":
|
||||||
|
node.style.setProperty("direction", "rtl")
|
||||||
|
document.body.style.direction = "ltr"
|
||||||
|
document.documentElement.style.direction = 'ltr'
|
||||||
|
|
||||||
|
def create_page_div(elem):
|
||||||
|
div = E('blank-page-div', ' \n ')
|
||||||
|
document.body.appendChild(div)
|
||||||
|
set_css(div, break_before='always', display='block', white_space='pre', background_color='transparent',
|
||||||
|
background_image='none', border_width='0', float='none', position='static')
|
||||||
|
|
||||||
|
_in_paged_mode = False
|
||||||
|
def in_paged_mode():
|
||||||
|
return _in_paged_mode
|
||||||
|
col_width = page_width = screen_width = side_margin = page_height = margin_top = margin_bottom = 0
|
||||||
|
is_full_screen_layout = False
|
||||||
|
|
||||||
|
def column_at(xpos):
|
||||||
|
# Return the number of the column that contains xpos
|
||||||
|
return xpos // page_width
|
||||||
|
|
||||||
|
def fit_images():
|
||||||
|
# Ensure no images are wider than the available width in a column. Note
|
||||||
|
# that this method use getBoundingClientRect() which means it will
|
||||||
|
# force a relayout if the render tree is dirty.
|
||||||
|
images = []
|
||||||
|
vimages = []
|
||||||
|
maxh = page_height
|
||||||
|
for img in document.getElementsByTagName('img'):
|
||||||
|
previously_limited = get_elem_data(img, 'width-limited', False)
|
||||||
|
data = get_elem_data(img, 'img-data', None)
|
||||||
|
br = img.getBoundingClientRect()
|
||||||
|
if data is None:
|
||||||
|
data = {'left':br.left, 'right':br.right, 'height':br.height, 'display': img.style.display}
|
||||||
|
set_elem_data(img, 'img-data', data)
|
||||||
|
left = viewport_to_document(br.left, 0, img.ownerDocument)[0]
|
||||||
|
col = column_at(left) * page_width
|
||||||
|
rleft = left - col - side_margin
|
||||||
|
width = br.right - br.left
|
||||||
|
rright = rleft + width
|
||||||
|
col_width = page_width - 2*side_margin
|
||||||
|
if previously_limited or rright > col_width:
|
||||||
|
images.push([img, col_width - rleft])
|
||||||
|
previously_limited = get_elem_data(img, 'height-limited', False)
|
||||||
|
if previously_limited or br.height > maxh:
|
||||||
|
vimages.push(img)
|
||||||
|
if previously_limited:
|
||||||
|
set_css(img, break_before='auto', display=data.display)
|
||||||
|
set_css(img, break_inside='avoid')
|
||||||
|
|
||||||
|
for img, max_width in images:
|
||||||
|
img.style.setProperty('max-width', max_width+'px')
|
||||||
|
set_elem_data(img, 'width-limited', True)
|
||||||
|
|
||||||
|
for img in vimages:
|
||||||
|
data = get_elem_data(img, 'img-data', None)
|
||||||
|
set_css(img, break_before='always', max_height=maxh+'px')
|
||||||
|
if data.height > maxh:
|
||||||
|
# This is needed to force the image onto a new page, without
|
||||||
|
# it, the webkit algorithm may still decide to split the image
|
||||||
|
# by keeping it part of its parent block
|
||||||
|
img.style.setProperty('display', 'block')
|
||||||
|
set_elem_data(img, 'height-limited', True)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
body_style = window.getComputedStyle(document.body)
|
||||||
|
first_layout = False
|
||||||
|
if not _in_paged_mode:
|
||||||
|
handle_rtl_body(body_style)
|
||||||
|
# Check if the current document is a full screen layout like
|
||||||
|
# cover, if so we treat it specially.
|
||||||
|
single_screen = (document.body.scrollHeight < window.innerHeight + 75)
|
||||||
|
first_layout = True
|
||||||
|
if not single_screen and opts.cols_per_screen > 1:
|
||||||
|
num = opts.cols_per_screen - 1
|
||||||
|
while num > 0:
|
||||||
|
num -= 1
|
||||||
|
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 = opts.cols_per_screen
|
||||||
|
adjust = ww - (ww // n) * n
|
||||||
|
# Ensure that the margins are large enough that the adjustment does not
|
||||||
|
# cause them to become negative semidefinite
|
||||||
|
sm = max(2*adjust, opts.margin_side)
|
||||||
|
# Minimum column width, for the cases when the window is too
|
||||||
|
# narrow
|
||||||
|
col_width = max(100, ((ww - adjust)/n) - 2*sm)
|
||||||
|
if opts.max_col_width > 0 and col_width > opts.max_col_width:
|
||||||
|
# Increase the side margin to ensure that col_width is no larger
|
||||||
|
# than opts.max_col_width
|
||||||
|
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 * opts.cols_per_screen
|
||||||
|
|
||||||
|
set_css(document.body, column_gap=2*sm + 'px', column_width=col_width + 'px', column_rule='0px inset blue',
|
||||||
|
min_width='0', max_width='none', min_height='0', max_height='none',
|
||||||
|
margin='0', border_width='0', padding='0 ' + sm + 'px',
|
||||||
|
width=(window.innerWidth - 2*sm) + 'px', height=window.innerHeight + 'px'
|
||||||
|
)
|
||||||
|
# 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
|
||||||
|
# page margins (the margin collapse algorithm)
|
||||||
|
document.body.style.setProperty('-webkit-margin-collapse', 'separate')
|
||||||
|
# Remove any webkit specified default margin from the first child of body
|
||||||
|
# Otherwise, you could end up with an effective negative margin, I dont
|
||||||
|
# understand exactly why, but see:
|
||||||
|
# https://bugs.launchpad.net/calibre/+bug/1082640 for an example
|
||||||
|
c = first_child(document.body)
|
||||||
|
if c:
|
||||||
|
c.style.setProperty('-webkit-margin-before', '0')
|
||||||
|
# Remove page breaks on the first few elements to prevent blank pages
|
||||||
|
# at the start of a chapter
|
||||||
|
set_css(c, break_before='avoid')
|
||||||
|
if c.tagName.toLowerCase() is 'div':
|
||||||
|
c2 = first_child(c)
|
||||||
|
if c2 and not has_start_text(c):
|
||||||
|
# Common pattern of all content being enclosed in a wrapper
|
||||||
|
# <div>, see for example: https://bugs.launchpad.net/bugs/1366074
|
||||||
|
# In this case, we also modify the first child of the div
|
||||||
|
# as long as there was no text before it.
|
||||||
|
set_css(c2, break_before='avoid')
|
||||||
|
|
||||||
|
if first_layout:
|
||||||
|
# Because of a bug in webkit column mode, svg elements defined with
|
||||||
|
# width 100% are wider than body and lead to a blank page after the
|
||||||
|
# current page (when cols_per_screen == 1). Similarly img elements
|
||||||
|
# with height=100% overflow the first column
|
||||||
|
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
|
||||||
|
# We only set full_screen_layout if scrollWidth is in (body_width,
|
||||||
|
# 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 and document.body.scrollWidth < 2 * body_width
|
||||||
|
if is_single_page:
|
||||||
|
is_full_screen_layout = True
|
||||||
|
|
||||||
|
# Prevent the TAB key from shifting focus as it causes partial scrolling
|
||||||
|
document.documentElement.addEventListener('keydown', def (evt):
|
||||||
|
if get_key(evt) is 'tab':
|
||||||
|
evt.preventDefault()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure the body width is an exact multiple of the column widths so that
|
||||||
|
# the browser does not adjust the column widths
|
||||||
|
# body_width = ncols * col_width + (ncols-1) * 2 * sm
|
||||||
|
ncols = document.body.scrollWidth / page_width
|
||||||
|
if ncols is not Math.floor(ncols) and not is_full_screen_layout:
|
||||||
|
set_css(document.body, width=Math.floor(ncols) * page_width - 2 * sm)
|
||||||
|
|
||||||
|
_in_paged_mode = True
|
||||||
|
fit_images()
|
||||||
|
return sm
|
||||||
|
|
||||||
|
def scroll_to_fraction(frac):
|
||||||
|
# Scroll to the position represented by frac (number between 0 and 1)
|
||||||
|
xpos = Math.floor(document.body.scrollWidth * frac)
|
||||||
|
scroll_to_xpos(xpos)
|
||||||
|
|
||||||
|
def scroll_to_xpos(xpos, animated=False, notify=False, duration=1000):
|
||||||
|
# Scroll so that the column containing xpos is the left most column in
|
||||||
|
# the viewport
|
||||||
|
if type(xpos) is not 'number':
|
||||||
|
print(xpos, 'is not a number, cannot scroll to it!')
|
||||||
|
return
|
||||||
|
if is_full_screen_layout:
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
return
|
||||||
|
pos = Math.floor(xpos/page_width) * page_width
|
||||||
|
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):
|
||||||
|
scroll_to_xpos(number * page_width + 10)
|
||||||
|
|
||||||
|
current_scroll_animation = None
|
||||||
|
|
||||||
|
def animated_scroll(pos, duration=1000, notify=True):
|
||||||
|
# Scroll the window to X-position pos in an animated fashion over
|
||||||
|
# duration milliseconds.
|
||||||
|
nonlocal current_scroll_animation
|
||||||
|
delta = pos - window.pageXOffset
|
||||||
|
interval = 50
|
||||||
|
steps = Math.floor(duration/interval)
|
||||||
|
step_size = Math.floor(delta/steps)
|
||||||
|
current_scroll_animation = {'target':pos, 'step_size':step_size, 'interval':interval, 'notify':notify, 'fn': def():
|
||||||
|
a = current_scroll_animation
|
||||||
|
npos = window.pageXOffset + a.step_size
|
||||||
|
completed = False
|
||||||
|
if abs(npos - a.target) < abs(a.step_size):
|
||||||
|
completed = True
|
||||||
|
npos = a.target
|
||||||
|
window.scrollTo(npos, 0)
|
||||||
|
if not completed:
|
||||||
|
setTimeout(a.fn, a.interval)
|
||||||
|
}
|
||||||
|
current_scroll_animation.fn()
|
||||||
|
|
||||||
|
def column_location(elem):
|
||||||
|
# Return the location of elem relative to its containing column.
|
||||||
|
# WARNING: This method may cause the viewport to scroll (to workaround
|
||||||
|
# a bug in WebKit).
|
||||||
|
br = elem.getBoundingClientRect()
|
||||||
|
# Because of a bug in WebKit's getBoundingClientRect() in column
|
||||||
|
# mode, this position can be inaccurate, see
|
||||||
|
# https://bugs.launchpad.net/calibre/+bug/1202390 for a test case.
|
||||||
|
# The usual symptom of the inaccuracy is br.top is highly negative.
|
||||||
|
if br.top < -100:
|
||||||
|
# We have to actually scroll the element into view to get its
|
||||||
|
# position
|
||||||
|
elem.scrollIntoView()
|
||||||
|
left, top = viewport_to_document(elem.scrollLeft, elem.scrollTop, elem.ownerDocument)
|
||||||
|
else:
|
||||||
|
left, top = viewport_to_document(br.left, br.top, elem.ownerDocument)
|
||||||
|
c = column_at(left)
|
||||||
|
width = min(br.right, (c+1)*page_width) - br.left
|
||||||
|
if br.bottom < br.top:
|
||||||
|
br.bottom = window.innerHeight
|
||||||
|
height = min(br.bottom, window.innerHeight) - br.top
|
||||||
|
left -= c*page_width
|
||||||
|
return {'column':c, 'left':left, 'top':top, 'width':width, 'height':height}
|
||||||
|
|
||||||
|
def column_boundaries():
|
||||||
|
# Return the column numbers at the left edge and after the right edge
|
||||||
|
# of the viewport
|
||||||
|
l = column_at(window.pageXOffset + 10)
|
||||||
|
return l, l + opts.cols_per_screen
|
||||||
|
|
||||||
|
def current_pos(frac):
|
||||||
|
# The current scroll position as a fraction between 0 and 1
|
||||||
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
|
if limit <= 0:
|
||||||
|
return 0.0
|
||||||
|
return window.pageXOffset / limit
|
||||||
|
|
||||||
|
def current_column_location():
|
||||||
|
# The location of the left edge of the left most column currently
|
||||||
|
# visible in the viewport
|
||||||
|
if is_full_screen_layout:
|
||||||
|
return 0
|
||||||
|
x = window.pageXOffset + max(10, side_margin)
|
||||||
|
return Math.floor(x/page_width) * page_width
|
||||||
|
|
||||||
|
def next_screen_location():
|
||||||
|
# The position to scroll to for the next screen (which could contain
|
||||||
|
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||||
|
if is_full_screen_layout:
|
||||||
|
return -1
|
||||||
|
cc = current_column_location()
|
||||||
|
ans = cc + screen_width
|
||||||
|
if opts.cols_per_screen > 1:
|
||||||
|
width_left = document.body.scrollWidth - (window.pageXOffset + window.innerWidth)
|
||||||
|
pages_left = width_left / page_width
|
||||||
|
if Math.ceil(pages_left) < opts.cols_per_screen:
|
||||||
|
return -1 # Only blank, dummy pages left
|
||||||
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
|
if ans > limit:
|
||||||
|
ans = limit if window.pageXOffset < limit else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def previous_screen_location():
|
||||||
|
# The position to scroll to for the previous screen (which could contain
|
||||||
|
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||||
|
if is_full_screen_layout:
|
||||||
|
return -1
|
||||||
|
cc = current_column_location()
|
||||||
|
ans = cc - screen_width
|
||||||
|
if ans < 0:
|
||||||
|
# We ignore small scrolls (less than 15px) when going to previous
|
||||||
|
# screen
|
||||||
|
ans = 0 if window.pageXOffset > 15 else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def next_col_location():
|
||||||
|
# The position to scroll to for the next column (same as
|
||||||
|
# next_screen_location() if columns per screen == 1). Returns -1 if no
|
||||||
|
# further scrolling is possible.
|
||||||
|
if is_full_screen_layout:
|
||||||
|
return -1
|
||||||
|
cc = current_column_location()
|
||||||
|
ans = cc + page_width
|
||||||
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
|
if ans > limit:
|
||||||
|
ans = limit if window.pageXOffset < limit else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def previous_col_location():
|
||||||
|
# The position to scroll to for the previous column (same as
|
||||||
|
# previous_screen_location() if columns per screen == 1). Returns -1 if
|
||||||
|
# no further scrolling is possible.
|
||||||
|
if is_full_screen_layout:
|
||||||
|
return -1
|
||||||
|
cc = current_column_location()
|
||||||
|
ans = cc - page_width
|
||||||
|
if ans < 0:
|
||||||
|
ans = 0 if window.pageXOffset > 0 else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def jump_to_anchor(name):
|
||||||
|
# Jump to the element identified by anchor name. Ensures that the left
|
||||||
|
# most column in the viewport is the column containing the start of the
|
||||||
|
# element and that the scroll position is at the start of the column.
|
||||||
|
elem = document.getElementById(name)
|
||||||
|
if not elem:
|
||||||
|
elems = document.getElementsByName(name)
|
||||||
|
if elems:
|
||||||
|
elem = elems[0]
|
||||||
|
if not elem:
|
||||||
|
return
|
||||||
|
# TODO: Re-enable this once you have added mathjax support
|
||||||
|
# if window.mathjax?.math_present
|
||||||
|
# # MathJax links to children of SVG tags and scrollIntoView doesn't
|
||||||
|
# # work properly for them, so if this link points to something
|
||||||
|
# # inside an <svg> tag we instead scroll the parent of the svg tag
|
||||||
|
# # into view.
|
||||||
|
# parent = elem
|
||||||
|
# while parent and parent?.tagName?.toLowerCase() != 'svg'
|
||||||
|
# parent = parent.parentNode
|
||||||
|
# if parent?.tagName?.toLowerCase() == 'svg'
|
||||||
|
# elem = parent.parentNode
|
||||||
|
elem.scrollIntoView()
|
||||||
|
if in_paged_mode:
|
||||||
|
# Ensure we are scrolled to the column containing elem
|
||||||
|
|
||||||
|
# Because of a bug in WebKit's getBoundingClientRect() in column
|
||||||
|
# mode, this position can be inaccurate, see
|
||||||
|
# https://bugs.launchpad.net/calibre/+bug/1132641 for a test case.
|
||||||
|
# The usual symptom of the inaccuracy is br.top is highly negative.
|
||||||
|
br = elem.getBoundingClientRect()
|
||||||
|
if br.top < -100:
|
||||||
|
# This only works because of the preceding call to
|
||||||
|
# elem.scrollIntoView(). However, in some cases it gives
|
||||||
|
# inaccurate results, so we prefer the bounding client rect,
|
||||||
|
# when possible.
|
||||||
|
left = elem.scrollLeft
|
||||||
|
else:
|
||||||
|
left = br.left
|
||||||
|
scroll_to_xpos(viewport_to_document(
|
||||||
|
left+side_margin, elem.scrollTop, elem.ownerDocument)[0])
|
||||||
|
|
||||||
|
def snap_to_selection():
|
||||||
|
# Ensure that the viewport is positioned at the start of the column
|
||||||
|
# containing the start of the current selection
|
||||||
|
if in_paged_mode:
|
||||||
|
sel = window.getSelection()
|
||||||
|
r = sel.getRangeAt(0).getBoundingClientRect()
|
||||||
|
node = sel.anchorNode
|
||||||
|
left = viewport_to_document(r.left, r.top, doc=node.ownerDocument)[0]
|
||||||
|
|
||||||
|
# Ensure we are scrolled to the column containing the start of the
|
||||||
|
# selection
|
||||||
|
scroll_to_xpos(left+5)
|
||||||
|
|
||||||
|
def jump_to_cfi(cfi, job_id=-1):
|
||||||
|
# Jump to the position indicated by the specified conformal fragment
|
||||||
|
# indicator (requires the cfi.coffee library). When in paged mode, the
|
||||||
|
# scroll is performed so that the column containing the position
|
||||||
|
# pointed to by the cfi is the left most column in the viewport
|
||||||
|
cfi_scroll_to(cfi, def(x, y):
|
||||||
|
if in_paged_mode:
|
||||||
|
scroll_to_xpos(x)
|
||||||
|
else:
|
||||||
|
window.scrollTo(0, y)
|
||||||
|
# if window.py_bridge
|
||||||
|
# window.py_bridge.jump_to_cfi_finished(job_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
def current_cfi():
|
||||||
|
# The Conformal Fragment Identifier at the current position, returns
|
||||||
|
# null if it could not be calculated.
|
||||||
|
ans = None
|
||||||
|
# TODO: uncomment after mathjax is implemented
|
||||||
|
# if window.mathjax?.math_present and not window.mathjax?.math_loaded:
|
||||||
|
# # If MathJax is loading, it is changing the DOM, so we cannot
|
||||||
|
# # reliably generate a CFI
|
||||||
|
# return ans
|
||||||
|
if in_paged_mode:
|
||||||
|
c = current_column_location()
|
||||||
|
for x in c, c-page_width, c+page_width:
|
||||||
|
# Try the current column, the previous column and the next
|
||||||
|
# column. Each column is tried from top to bottom.
|
||||||
|
left, right = x, x + page_width
|
||||||
|
if left < 0 or right > document.body.scrollWidth:
|
||||||
|
continue
|
||||||
|
deltax = page_width // 25
|
||||||
|
deltay = window.innerHeight // 25
|
||||||
|
cury = 0
|
||||||
|
while cury < window.innerHeight - this.effective_margin_bottom:
|
||||||
|
curx = left + side_margin
|
||||||
|
while curx < right - side_margin:
|
||||||
|
cfi = cfi_at_point(curx-window.pageXOffset, cury-window.pageYOffset)
|
||||||
|
if cfi:
|
||||||
|
print('Viewport cfi:', cfi)
|
||||||
|
return cfi
|
||||||
|
curx += deltax
|
||||||
|
cury += deltay
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ans = cfi_at_current() or None
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
if ans:
|
||||||
|
print('Viewport cfi:', ans)
|
||||||
|
return ans
|
@ -2,6 +2,8 @@
|
|||||||
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
from __python__ import hash_literals
|
from __python__ import hash_literals
|
||||||
|
|
||||||
|
from encodings import hexlify
|
||||||
|
|
||||||
def debounce(func, wait, immediate=False):
|
def debounce(func, wait, immediate=False):
|
||||||
# Returns a function, that, as long as it continues to be invoked, will not
|
# Returns a function, that, as long as it continues to be invoked, will not
|
||||||
# be triggered. The function will be called after it stops being called for
|
# be triggered. The function will be called after it stops being called for
|
||||||
@ -89,6 +91,43 @@ def document_width():
|
|||||||
html = document.documentElement
|
html = document.documentElement
|
||||||
return max(document.body.scrollWidth, document.body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth)
|
return max(document.body.scrollWidth, document.body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth)
|
||||||
|
|
||||||
|
_data_ns = None
|
||||||
|
|
||||||
|
def data_ns(name):
|
||||||
|
nonlocal _data_ns
|
||||||
|
if _data_ns is None:
|
||||||
|
rand = Uint8Array(12)
|
||||||
|
window.crypto.getRandomValues(rand)
|
||||||
|
_data_ns = 'data-' + hexlify(rand) + '-'
|
||||||
|
return _data_ns + name
|
||||||
|
|
||||||
|
def get_elem_data(elem, name, defval):
|
||||||
|
ans = elem.getAttribute(data_ns(name))
|
||||||
|
if ans is None:
|
||||||
|
return defval ? None
|
||||||
|
return JSON.parse(ans)
|
||||||
|
|
||||||
|
def set_elem_data(elem, name, val):
|
||||||
|
elem.setAttribute(data_ns(name), JSON.stringify(val))
|
||||||
|
|
||||||
|
def viewport_to_document(x, y, doc):
|
||||||
|
# Convert x, y from the viewport (window) co-ordinate system to the
|
||||||
|
# document (body) co-ordinate system
|
||||||
|
doc = doc or window.document
|
||||||
|
topdoc = window.top.document
|
||||||
|
while doc is not topdoc:
|
||||||
|
# We are in a frame
|
||||||
|
frame = doc.defaultView.frameElement
|
||||||
|
rect = frame.getBoundingClientRect()
|
||||||
|
x += rect.left
|
||||||
|
y += rect.top
|
||||||
|
doc = frame.ownerDocument
|
||||||
|
win = doc.defaultView
|
||||||
|
wx, wy = win.pageXOffset, win.pageYOffset
|
||||||
|
x += wx
|
||||||
|
y += wy
|
||||||
|
return x, y
|
||||||
|
|
||||||
if __name__ is '__main__':
|
if __name__ is '__main__':
|
||||||
print(fmt_sidx(10), fmt_sidx(1.2))
|
print(fmt_sidx(10), fmt_sidx(1.2))
|
||||||
print(list(map(human_readable, [1, 1024.0, 1025, 1024*1024*2.3])))
|
print(list(map(human_readable, [1, 1024.0, 1025, 1024*1024*2.3])))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user