Fix page groups
This commit is contained in:
@@ -26,7 +26,7 @@ def test_page():
|
||||
}
|
||||
''')])
|
||||
|
||||
page_type = PageType(side='left', blank=False, name='', index=0, group_index=0)
|
||||
page_type = PageType(side='left', blank=False, name='', index=0, groups=())
|
||||
set_page_type_computed_styles(page_type, document, style_for)
|
||||
style = style_for(page_type)
|
||||
assert style['margin_top'] == (5, 'px')
|
||||
@@ -34,7 +34,7 @@ def test_page():
|
||||
assert style['margin_bottom'] == (10, 'px')
|
||||
assert style['color'] == (1, 0, 0, 1) # red, inherited from html
|
||||
|
||||
page_type = PageType(side='right', blank=False, name='', index=0, group_index=0)
|
||||
page_type = PageType(side='right', blank=False, name='', index=0, groups=())
|
||||
set_page_type_computed_styles(page_type, document, style_for)
|
||||
style = style_for(page_type)
|
||||
assert style['margin_top'] == (5, 'px')
|
||||
@@ -42,7 +42,7 @@ def test_page():
|
||||
assert style['margin_bottom'] == (16, 'px')
|
||||
assert style['color'] == (0, 0, 1, 1) # blue
|
||||
|
||||
page_type = PageType(side='left', blank=False, name='', index=1, group_index=1)
|
||||
page_type = PageType(side='left', blank=False, name='', index=1, groups=())
|
||||
set_page_type_computed_styles(page_type, document, style_for)
|
||||
style = style_for(page_type)
|
||||
assert style['margin_top'] == (10, 'px')
|
||||
@@ -50,7 +50,7 @@ def test_page():
|
||||
assert style['margin_bottom'] == (10, 'px')
|
||||
assert style['color'] == (1, 0, 0, 1) # red, inherited from html
|
||||
|
||||
page_type = PageType(side='right', blank=False, name='', index=1, group_index=1)
|
||||
page_type = PageType(side='right', blank=False, name='', index=1, groups=())
|
||||
set_page_type_computed_styles(page_type, document, style_for)
|
||||
style = style_for(page_type)
|
||||
assert style['margin_top'] == (10, 'px')
|
||||
@@ -58,18 +58,18 @@ def test_page():
|
||||
assert style['margin_bottom'] == (16, 'px')
|
||||
assert style['color'] == (0, 0, 1, 1) # blue
|
||||
|
||||
page_type = PageType(side='left', blank=False, name='', index=0, group_index=0)
|
||||
page_type = PageType(side='left', blank=False, name='', index=0, groups=())
|
||||
set_page_type_computed_styles(page_type, document, style_for)
|
||||
style = style_for(page_type, '@top-left')
|
||||
assert style is None
|
||||
|
||||
page_type = PageType(side='right', blank=False, name='', index=0, group_index=0)
|
||||
page_type = PageType(side='right', blank=False, name='', index=0, groups=())
|
||||
set_page_type_computed_styles(page_type, document, style_for)
|
||||
style = style_for(page_type, '@top-left')
|
||||
assert style['font_size'] == 20 # inherited from @page
|
||||
assert style['width'] == (200, 'px')
|
||||
|
||||
page_type = PageType(side='right', blank=False, name='', index=0, group_index=0)
|
||||
page_type = PageType(side='right', blank=False, name='', index=0, groups=())
|
||||
set_page_type_computed_styles(page_type, document, style_for)
|
||||
style = style_for(page_type, '@top-right')
|
||||
assert style['font_size'] == 10
|
||||
|
||||
@@ -891,6 +891,84 @@ def test_page_names_10():
|
||||
assert article.element_tag == 'article'
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_page_groups():
|
||||
pages = render_pages('''
|
||||
<style>
|
||||
@page { size: 200px 200px }
|
||||
@page small { size: 100px 100px }
|
||||
@page :nth(1 of small) { size: 50px 50px }
|
||||
section { page: small }
|
||||
div, div section { break-after: page }
|
||||
</style>
|
||||
<div></div>
|
||||
<article></article>
|
||||
<section>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</section>
|
||||
<section>
|
||||
</section>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<section>
|
||||
<div></div>
|
||||
</section>
|
||||
<div>
|
||||
<section></section>
|
||||
<section></section>
|
||||
</div>
|
||||
''')
|
||||
page1, page2, page3, page4, page5, page6, page7, page8, page9 = pages
|
||||
|
||||
assert (page1.width, page1.height) == (200, 200)
|
||||
div, = page1.children[0].children[0].children
|
||||
assert div.element_tag == 'div'
|
||||
|
||||
assert (page2.width, page2.height) == (200, 200)
|
||||
article, = page2.children[0].children[0].children
|
||||
assert article.element_tag == 'article'
|
||||
|
||||
assert (page3.width, page3.height) == (50, 50)
|
||||
section, = page3.children[0].children[0].children
|
||||
assert section.element_tag == 'section'
|
||||
div, = section.children
|
||||
assert div.element_tag == 'div'
|
||||
|
||||
assert (page4.width, page4.height) == (100, 100)
|
||||
section, = page4.children[0].children[0].children
|
||||
assert section.element_tag == 'section'
|
||||
div, = section.children
|
||||
assert div.element_tag == 'div'
|
||||
|
||||
assert (page5.width, page5.height) == (50, 50)
|
||||
section, div = page5.children[0].children[0].children
|
||||
assert section.element_tag == 'section'
|
||||
assert div.element_tag == 'div'
|
||||
|
||||
assert (page6.width, page6.height) == (200, 200)
|
||||
div, = page6.children[0].children[0].children
|
||||
assert div.element_tag == 'div'
|
||||
|
||||
assert (page7.width, page7.height) == (50, 50)
|
||||
section, = page7.children[0].children[0].children
|
||||
assert section.element_tag == 'section'
|
||||
div, = section.children
|
||||
assert div.element_tag == 'div'
|
||||
|
||||
assert (page8.width, page8.height) == (50, 50)
|
||||
div, = page8.children[0].children[0].children
|
||||
assert div.element_tag == 'div'
|
||||
section, = div.children
|
||||
assert section.element_tag == 'section'
|
||||
|
||||
assert (page9.width, page9.height) == (50, 50)
|
||||
div, = page9.children[0].children[0].children
|
||||
assert div.element_tag == 'div'
|
||||
section, = div.children
|
||||
assert section.element_tag == 'section'
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
@pytest.mark.parametrize('style, line_counts', (
|
||||
('orphans: 2; widows: 2', [4, 3]),
|
||||
|
||||
+8
-8
@@ -248,14 +248,14 @@ def test_whitespace():
|
||||
|
||||
@assert_no_logs
|
||||
@pytest.mark.parametrize('page_type, top, right, bottom, left', (
|
||||
(PageType('left', False, '', 0, 0), 20, 3, 3, 10),
|
||||
(PageType('right', False, '', 0, 0), 20, 10, 3, 3),
|
||||
(PageType('left', False, '', 1, 1), 10, 3, 3, 10),
|
||||
(PageType('right', False, '', 1, 1), 10, 10, 3, 3),
|
||||
(PageType('right', False, 'name', 1, 0), 5, 10, 3, 15),
|
||||
(PageType('right', False, 'name', 1, 1), 5, 10, 4, 15),
|
||||
(PageType('right', False, 'name', 2, 0), 5, 10, 1, 15),
|
||||
(PageType('right', False, 'name', 8, 8), 5, 10, 2, 15),
|
||||
(PageType('left', False, '', 0, ()), 20, 3, 3, 10),
|
||||
(PageType('right', False, '', 0, ()), 20, 10, 3, 3),
|
||||
(PageType('left', False, '', 1, ()), 10, 3, 3, 10),
|
||||
(PageType('right', False, '', 1, ()), 10, 10, 3, 3),
|
||||
(PageType('right', False, 'name', 1, (('name', 0),)), 5, 10, 3, 15),
|
||||
(PageType('right', False, 'name', 1, (('name', 1),)), 5, 10, 4, 15),
|
||||
(PageType('right', False, 'name', 2, (('name', 0),)), 5, 10, 1, 15),
|
||||
(PageType('right', False, 'name', 8, (('name', 8),)), 5, 10, 2, 15),
|
||||
))
|
||||
def test_page_style(page_type, top, right, bottom, left):
|
||||
document = FakeHTML(string='''
|
||||
|
||||
@@ -200,11 +200,19 @@ class StyleFor:
|
||||
return False
|
||||
if page_selector_type.index is not None:
|
||||
a, b, name = page_selector_type.index
|
||||
if name is not None and name != page_type.name:
|
||||
if name is None:
|
||||
index = page_type.index
|
||||
offset = index + 1 - b
|
||||
return offset == 0 if a == 0 else (offset / a >= 0 and not offset % a)
|
||||
if name != page_type.name:
|
||||
return False
|
||||
index = page_type.index if name is None else page_type.group_index
|
||||
offset = index + 1 - b
|
||||
return offset == 0 if a == 0 else (offset / a >= 0 and not offset % a)
|
||||
for group_name, index in page_type.groups:
|
||||
if name != group_name:
|
||||
continue
|
||||
offset = index + 1 - b
|
||||
if (offset == 0 if a == 0 else (offset / a >= 0 and not offset % a)):
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
+82
-27
@@ -14,7 +14,7 @@ from .min_max import handle_min_max_height, handle_min_max_width
|
||||
from .percent import resolve_percentages
|
||||
from .preferred import max_content_width, min_content_width
|
||||
|
||||
PageType = namedtuple('PageType', ['side', 'blank', 'name', 'index', 'group_index'])
|
||||
PageType = namedtuple('PageType', ['side', 'blank', 'name', 'index', 'groups'])
|
||||
|
||||
|
||||
class OrientedBox:
|
||||
@@ -800,7 +800,70 @@ def set_page_type_computed_styles(page_type, html, style_for):
|
||||
base_url=html.base_url)
|
||||
|
||||
|
||||
def remake_page(index, group_index, context, root_box, html):
|
||||
def _includes_resume_at(resume_at, page_group_resume_at):
|
||||
(page_child_index, page_child_resume_at), = page_group_resume_at.items()
|
||||
if resume_at is None or page_child_index not in resume_at:
|
||||
return False
|
||||
if page_child_resume_at is None:
|
||||
return True
|
||||
return _includes_resume_at(resume_at[page_child_index], page_child_resume_at)
|
||||
|
||||
|
||||
def _update_page_groups(page_groups, resume_at, next_page, root_box):
|
||||
# https://www.w3.org/TR/css-gcpm-3/#document-sequence-selectors
|
||||
|
||||
# Remove or increment page groups.
|
||||
page_groups_length = len(page_groups)
|
||||
for i, page_group in enumerate(page_groups[:]):
|
||||
if _includes_resume_at(resume_at, page_group[2]):
|
||||
page_group[1] += 1
|
||||
else:
|
||||
page_groups.pop(i - page_groups_length)
|
||||
|
||||
# Add page groups.
|
||||
if next_page['break'] == 'any' or not next_page['page']:
|
||||
# We don’t have a forced page break or a named page.
|
||||
return
|
||||
if page_groups and page_groups[-1][0] == next_page['page']:
|
||||
# We’re already in an element whose page name is the next page name.
|
||||
return
|
||||
|
||||
# Find the box that has the named page. It is a first in-flow child of the
|
||||
# element corresponding to resume_at.
|
||||
|
||||
# Find element corrensponding to resume_at.
|
||||
page_group_resume_at = copy.deepcopy(resume_at)
|
||||
current_resume_at = page_group_resume_at
|
||||
current_element = root_box
|
||||
while True:
|
||||
child_index, child_resume_at = tuple(current_resume_at.items())[-1]
|
||||
current_element = current_element.children[child_index]
|
||||
if child_resume_at is None:
|
||||
break
|
||||
current_resume_at = child_resume_at
|
||||
|
||||
# Find the descendant with named page.
|
||||
while True:
|
||||
if current_element.style['page'] == next_page['page']:
|
||||
page_groups.append([next_page['page'], 0, page_group_resume_at])
|
||||
return
|
||||
if not isinstance(current_element, boxes.ParentBox):
|
||||
# Shouldn’t happen.
|
||||
return
|
||||
for i, child in enumerate(current_element.children):
|
||||
if not child.is_in_normal_flow():
|
||||
continue
|
||||
current_resume_at[child_index] = {i: None}
|
||||
current_resume_at = current_resume_at[child_index]
|
||||
child_index = i
|
||||
current_element = child
|
||||
break
|
||||
else:
|
||||
# Shouldn’t happen.
|
||||
return
|
||||
|
||||
|
||||
def remake_page(index, page_groups, context, root_box, html):
|
||||
"""Return one laid out page without margin boxes.
|
||||
|
||||
Start with the initial values from ``context.page_maker[index]``.
|
||||
@@ -813,43 +876,37 @@ def remake_page(index, group_index, context, root_box, html):
|
||||
|
||||
"""
|
||||
page_maker = context.page_maker
|
||||
(initial_resume_at, initial_next_page, right_page, initial_page_state,
|
||||
remake_state) = page_maker[index]
|
||||
resume_at, next_page, right_page, page_state, _ = page_maker[index]
|
||||
|
||||
# PageType for current page, values for page_maker[index + 1].
|
||||
# Don't modify actual page_maker[index] values!
|
||||
# TODO: should we store (and reuse) page_type in the page_maker?
|
||||
page_state = copy.deepcopy(initial_page_state)
|
||||
if initial_next_page['break'] in ('left', 'right'):
|
||||
next_page_side = initial_next_page['break']
|
||||
elif initial_next_page['break'] in ('recto', 'verso'):
|
||||
page_state = copy.deepcopy(page_state)
|
||||
if next_page['break'] in ('left', 'right'):
|
||||
next_page_side = next_page['break']
|
||||
elif next_page['break'] in ('recto', 'verso'):
|
||||
direction_ltr = root_box.style['direction'] == 'ltr'
|
||||
break_verso = initial_next_page['break'] == 'verso'
|
||||
break_verso = next_page['break'] == 'verso'
|
||||
next_page_side = 'right' if direction_ltr ^ break_verso else 'left'
|
||||
else:
|
||||
next_page_side = None
|
||||
blank = bool(
|
||||
(next_page_side == 'left' and right_page) or
|
||||
(next_page_side == 'right' and not right_page) or
|
||||
(context.reported_footnotes and initial_resume_at is None))
|
||||
name = '' if blank else initial_next_page['page']
|
||||
if name not in group_index:
|
||||
group_index.clear()
|
||||
group_index[name] = 0
|
||||
(context.reported_footnotes and resume_at is None))
|
||||
name = '' if blank else next_page['page']
|
||||
side = 'right' if right_page else 'left'
|
||||
page_type = PageType(side, blank, name, index, group_index[name])
|
||||
_update_page_groups(page_groups, resume_at, next_page, root_box)
|
||||
groups = tuple((name, index) for name, index, _ in page_groups)
|
||||
page_type = PageType(side, blank, name, index, groups)
|
||||
set_page_type_computed_styles(page_type, html, context.style_for)
|
||||
group_index[name] += 1
|
||||
|
||||
context.forced_break = (
|
||||
initial_next_page['break'] != 'any' or initial_next_page['page'])
|
||||
context.forced_break = (next_page['break'] != 'any' or next_page['page'])
|
||||
context.margin_clearance = False
|
||||
|
||||
# make_page wants a page_number of index + 1
|
||||
page_number = index + 1
|
||||
page, resume_at, next_page = make_page(
|
||||
context, root_box, page_type, initial_resume_at, page_number,
|
||||
page_state)
|
||||
context, root_box, page_type, resume_at, page_number, page_state)
|
||||
assert next_page
|
||||
right_page = not right_page
|
||||
|
||||
@@ -859,9 +916,8 @@ def remake_page(index, group_index, context, root_box, html):
|
||||
page_maker_next_changed = True
|
||||
else:
|
||||
# Check whether something changed
|
||||
# TODO: Find what we need to compare. Is resume_at enough?
|
||||
(next_resume_at, next_next_page, next_right_page,
|
||||
next_page_state, _) = page_maker[index + 1]
|
||||
next_resume_at, next_next_page, next_right_page, next_page_state, _ = (
|
||||
page_maker[index + 1])
|
||||
page_maker_next_changed = (
|
||||
next_resume_at != resume_at or
|
||||
next_next_page != next_page or
|
||||
@@ -880,7 +936,6 @@ def remake_page(index, group_index, context, root_box, html):
|
||||
# If resume_at is None (last page) it must be False to prevent endless
|
||||
# loops and list index out of range (see #794).
|
||||
remake_state['content_changed'] = resume_at is not None
|
||||
# page_state is already a deepcopy
|
||||
item = resume_at, next_page, right_page, page_state, remake_state
|
||||
if index + 1 >= len(page_maker):
|
||||
page_maker.append(item)
|
||||
@@ -898,7 +953,7 @@ def make_all_pages(context, root_box, html, pages):
|
||||
"""
|
||||
i = 0
|
||||
reported_footnotes = None
|
||||
group_i = {'': 0}
|
||||
page_groups = []
|
||||
while True:
|
||||
remake_state = context.page_maker[i][-1]
|
||||
if (len(pages) == 0 or
|
||||
@@ -910,7 +965,7 @@ def make_all_pages(context, root_box, html, pages):
|
||||
remake_state['pages_wanted'] = False
|
||||
remake_state['anchors'] = []
|
||||
remake_state['content_lookups'] = []
|
||||
page, resume_at = remake_page(i, group_i, context, root_box, html)
|
||||
page, resume_at = remake_page(i, page_groups, context, root_box, html)
|
||||
reported_footnotes = context.reported_footnotes
|
||||
yield page
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user