''')
html, = page_1.children
body, = html.children
img_1, div = body.children
assert img_1.position_y == 0
assert img_1.height == 25
assert div.position_y == 25
assert div.height == 100
html, = page_2.children
body, = html.children
img_2, = body.children
assert img_2.position_y == 0
assert img_2.height == 25
@assert_no_logs
def test_page_breaks_complex_3():
# Adding a few page-break-*: avoid, the only legal break is
# before the
page_1, page_2 = render_pages('''
''')
html, = page_1.children
body, = html.children
img_1, = body.children
assert img_1.position_y == 0
assert img_1.height == 25
html, = page_2.children
body, = html.children
div, img_2 = body.children
assert div.position_y == 0
assert div.height == 100
assert img_2.position_y == 100
assert img_2.height == 25
@assert_no_logs
def test_page_breaks_complex_4():
page_1, page_2 = render_pages('''
''')
html, = page_1.children
body, = html.children
img_1, = body.children
assert img_1.position_y == 0
assert img_1.height == 25
html, = page_2.children
body, = html.children
outer_div, = body.children
inner_div, img_2 = outer_div.children
assert inner_div.position_y == 0
assert inner_div.height == 100
assert img_2.position_y == 100
assert img_2.height == 25
@assert_no_logs
def test_page_breaks_complex_5():
# Reference for the next test
page_1, page_2, page_3 = render_pages('''
''')
html, = page_1.children
body, = html.children
div, = body.children
assert div.height == 100
html, = page_2.children
body, = html.children
div, img_4 = body.children
assert div.height == 60
assert img_4.height == 30
html, = page_3.children
body, = html.children
img_5, = body.children
assert img_5.height == 30
@assert_no_logs
def test_page_breaks_complex_6():
page_1, page_2, page_3 = render_pages('''
''')
html, = page_1.children
body, = html.children
div, = body.children
assert div.height == 100
html, = page_2.children
body, = html.children
div, = body.children
section, = div.children
img_2, = section.children
assert img_2.height == 30
# TODO: currently this is 60: we do not increase the used height of blocks
# to make them fill the blank space at the end of the age when we remove
# children from them for some break-*: avoid.
# See TODOs in blocks.block_container_layout
# assert div.height == 100
html, = page_3.children
body, = html.children
div, img_4, img_5, = body.children
assert div.height == 30
assert img_4.height == 30
assert img_5.height == 30
@assert_no_logs
def test_page_breaks_complex_7():
page_1, page_2, page_3 = render_pages('''
foo
bar
''')
assert len(page_1.children) == 2 # content and @bottom-center
assert len(page_2.children) == 1 # content only
assert len(page_3.children) == 2 # content and @bottom-center
@assert_no_logs
def test_page_breaks_complex_8():
page_1, page_2 = render_pages('''
''')
html, = page_1.children
body, _div = html.children
div_1, section = body.children
div_2, = section.children
assert div_1.position_y == 0
assert div_2.position_y == 20
assert div_1.height == 20
assert div_2.height == 20
html, = page_2.children
body, = html.children
section, div_4 = body.children
div_3, = section.children
absolute, fixed = div_3.children
assert div_3.position_y == 0
assert div_4.position_y == 20
assert div_3.height == 20
assert div_4.height == 20
@assert_no_logs
@pytest.mark.parametrize('break_after, margin_break, margin_top', (
('page', 'auto', 5),
('auto', 'auto', 0),
('page', 'keep', 5),
('auto', 'keep', 5),
('page', 'discard', 0),
('auto', 'discard', 0),
))
def test_margin_break(break_after, margin_break, margin_top):
page_1, page_2 = render_pages('''
''' % (break_after, margin_break))
html, = page_1.children
body, = html.children
section, = body.children
div, = section.children
assert div.margin_top == 5
html, = page_2.children
body, = html.children
section, = body.children
div, = section.children
assert div.margin_top == margin_top
@pytest.mark.xfail
@assert_no_logs
def test_margin_break_clearance():
page_1, page_2 = render_pages('''
''')
html, = page_1.children
body, = html.children
section, = body.children
div, = section.children
assert div.margin_top == 5
html, = page_2.children
body, = html.children
section, = body.children
div_1, = section.children
assert div_1.margin_top == 0
div_2, = div_1.children
assert div_2.margin_top == 5
assert div_2.content_box_y() == 5
@assert_no_logs
@pytest.mark.parametrize('direction, page_break, pages_number', (
('ltr', 'recto', 3),
('ltr', 'verso', 2),
('rtl', 'recto', 3),
('rtl', 'verso', 2),
('ltr', 'right', 3),
('ltr', 'left', 2),
('rtl', 'right', 2),
('rtl', 'left', 3),
))
def test_recto_verso_break(direction, page_break, pages_number):
pages = render_pages('''
abc
def
''' % (direction, page_break))
assert len(pages) == pages_number
@assert_no_logs
def test_page_names_1():
pages = render_pages('''
''')
page1, = pages
assert (page1.width, page1.height) == (100, 100)
@assert_no_logs
def test_page_names_2():
pages = render_pages('''
''')
page1, = pages
assert (page1.width, page1.height) == (100, 100)
@assert_no_logs
def test_page_names_3():
pages = render_pages('''
''')
page1, page2 = pages
assert (page1.width, page1.height) == (200, 100)
html, = page1.children
body, = html.children
div, = body.children
section1, section2 = div.children
assert section1.element_tag == section2.element_tag == 'section'
assert (page2.width, page2.height) == (100, 200)
html, = page2.children
body, = html.children
div, = body.children
p, = div.children
assert p.element_tag == 'p'
@assert_no_logs
def test_page_names_4():
pages = render_pages('''
small
''')
page1, page2 = pages
assert (page1.width, page1.height) == (200, 200)
html, = page1.children
body, = html.children
section1, section2 = body.children
assert section1.element_tag == section2.element_tag == 'section'
assert (page2.width, page2.height) == (100, 100)
html, = page2.children
body, = html.children
p, section = body.children
assert p.element_tag == 'p'
assert section.element_tag == 'section'
@assert_no_logs
def test_page_names_5():
pages = render_pages('''
''')
page1, page2 = pages
assert (page1.width, page1.height) == (200, 200)
html, = page1.children
body, = html.children
section1, section2 = body.children
assert section1.element_tag == section2.element_tag == 'section'
p, line = section1.children
line, = section2.children
assert (page2.width, page2.height) == (100, 100)
html, = page2.children
body, = html.children
section2, = body.children
div, = section2.children
@assert_no_logs
def test_page_names_6():
pages = render_pages('''
''')
page1, page2, page3 = pages
assert (page1.width, page1.height) == (200, 200)
html, = page1.children
body, = html.children
section1, section2 = body.children
assert section1.element_tag == section2.element_tag == 'section'
line1, p, line2 = section1.children
line, = section2.children
assert (page2.width, page2.height) == (100, 100)
html, = page2.children
body, = html.children
section2, = body.children
div, = section2.children
assert (page3.width, page3.height) == (200, 200)
html, = page3.children
body, = html.children
section2, = body.children
line, = section2.children
@assert_no_logs
def test_page_names_7():
pages = render_pages('''
small
''')
page1, page2, page3 = pages
assert (page1.width, page1.height) == (200, 200)
html, = page1.children
body, = html.children
section1, section2 = body.children
assert section1.element_tag == section2.element_tag == 'section'
assert (page2.width, page2.height) == (200, 200)
html, = page2.children
assert not html.children
assert (page3.width, page3.height) == (100, 100)
html, = page3.children
body, = html.children
p, section = body.children
assert p.element_tag == 'p'
assert section.element_tag == 'section'
@assert_no_logs
def test_page_names_8():
pages = render_pages('''
''')
page1, page2 = pages
assert (page1.width, page1.height) == (100, 100)
html, = page1.children
body, = html.children
section, = body.children
p, = section.children
assert section.element_tag == 'section'
assert p.element_tag == 'p'
assert (page2.width, page2.height) == (100, 100)
html, = page2.children
body, = html.children
section, = body.children
p, = section.children
assert section.element_tag == 'section'
assert p.element_tag == 'p'
@assert_no_logs
def test_page_names_9():
pages = render_pages('''
small
small
''')
page1, page2, = pages
assert (page1.width, page1.height) == (100, 100)
html, = page1.children
body, = html.children
section, = body.children
assert section.element_tag == 'section'
assert (page2.width, page2.height) == (100, 100)
html, = page2.children
body, = html.children
article, = body.children
assert article.element_tag == 'article'
@assert_no_logs
@pytest.mark.parametrize('style, line_counts', (
('orphans: 2; widows: 2', [4, 3]),
('orphans: 5; widows: 2', [0, 7]),
('orphans: 2; widows: 4', [3, 4]),
('orphans: 4; widows: 4', [0, 7]),
('orphans: 2; widows: 2; page-break-inside: avoid', [0, 7]),
))
def test_orphans_widows_avoid(style, line_counts):
pages = render_pages('''
Tasty test
one two three four five six seven
''' % style)
for i, page in enumerate(pages):
html, = page.children
body, = html.children
body_children = body.children if i else body.children[1:] # skip h1
count = len(body_children[0].children) if body_children else 0
assert line_counts.pop(0) == count
assert not line_counts
@assert_no_logs
def test_page_and_linebox_breaking():
# Empty
tests a corner case in skip_first_whitespace()
pages = render_pages('''
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
''')
texts = []
for page in pages:
html, = page.children
body, = html.children
div, = body.children
lines = div.children
for line in lines:
line_texts = []
for child in line.descendants():
if isinstance(child, boxes.TextBox):
line_texts.append(child.text)
texts.append(''.join(line_texts))
assert len(pages) == 4
assert ''.join(texts) == '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
@assert_no_logs
def test_margin_boxes_fixed_dimension_1():
# Corner boxes
page, = render_pages('''
''')
html, top_left, top_right, bottom_left, bottom_right = page.children
for margin_box, text in zip(
[top_left, top_right, bottom_left, bottom_right],
['top_left', 'top_right', 'bottom_left', 'bottom_right']):
line, = margin_box.children
text, = line.children
assert text == text
# Check positioning and Rule 1 for fixed dimensions
assert top_left.position_x == 0
assert top_left.position_y == 0
assert top_left.margin_width() == 200 # margin-left
assert top_left.margin_height() == 100 # margin-top
assert top_right.position_x == 700 # size-x - margin-right
assert top_right.position_y == 0
assert top_right.margin_width() == 300 # margin-right
assert top_right.margin_height() == 100 # margin-top
assert bottom_left.position_x == 0
assert bottom_left.position_y == 600 # size-y - margin-bottom
assert bottom_left.margin_width() == 200 # margin-left
assert bottom_left.margin_height() == 400 # margin-bottom
assert bottom_right.position_x == 700 # size-x - margin-right
assert bottom_right.position_y == 600 # size-y - margin-bottom
assert bottom_right.margin_width() == 300 # margin-right
assert bottom_right.margin_height() == 400 # margin-bottom
@assert_no_logs
def test_margin_boxes_fixed_dimension_2():
# Test rules 2 and 3
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_width() == 200
assert margin_box.margin_left == 60
assert margin_box.margin_right == 60
assert margin_box.width == 80 # 200 - 60 - 60
assert margin_box.margin_height() == 100
# total was too big, the outside margin was ignored:
assert margin_box.margin_top == 60
assert margin_box.margin_bottom == 40 # Not 60
assert margin_box.height == 0 # But not negative
@assert_no_logs
def test_margin_boxes_fixed_dimension_3():
# Test rule 3 with a non-auto inner dimension
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_width() == 100
assert margin_box.margin_left == -40 # Not 10px
assert margin_box.margin_right == 10
assert margin_box.width == 130 # As specified
@assert_no_logs
def test_margin_boxes_fixed_dimension_4():
# Test rule 4
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_width() == 100
assert margin_box.margin_left == 10 # 10px this time, no over-constrain
assert margin_box.margin_right == 20
assert margin_box.width == 70 # As specified
@assert_no_logs
def test_margin_boxes_fixed_dimension_5():
# Test rules 2, 3 and 4
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_width() == 100
assert margin_box.margin_left == 0 # rule 2
assert margin_box.margin_right == -30 # rule 3, after rule 2
assert margin_box.width == 130 # As specified
@assert_no_logs
def test_margin_boxes_fixed_dimension_6():
# Test rule 5
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_height() == 100
assert margin_box.margin_top == 10
assert margin_box.margin_bottom == 0
assert margin_box.height == 90
@assert_no_logs
def test_margin_boxes_fixed_dimension_7():
# Test rule 5
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_height() == 100
assert margin_box.margin_top == 0
assert margin_box.margin_bottom == 0
assert margin_box.height == 100
@assert_no_logs
def test_margin_boxes_fixed_dimension_8():
# Test rule 6
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_height() == 100
assert margin_box.margin_top == 15
assert margin_box.margin_bottom == 15
assert margin_box.height == 70
@assert_no_logs
def test_margin_boxes_fixed_dimension_9():
# Rule 2 inhibits rule 6
page, = render_pages('''
''')
html, margin_box = page.children
assert margin_box.margin_height() == 100
assert margin_box.margin_top == 0
assert margin_box.margin_bottom == -50 # outside
assert margin_box.height == 150
def images(*widths):
return ' '.join(
'url(\'data:image/svg+xml,
\')'
% width for width in widths)
@assert_no_logs
@pytest.mark.parametrize('css, widths', (
('''@top-left { content: %s }
@top-center { content: %s }
@top-right { content: %s }
''' % (images(50, 50), images(50, 50), images(50, 50)),
[100, 100, 100]), # Use preferred widths if they fit
('''@top-left { content: %s; margin: auto }
@top-center { content: %s }
@top-right { content: %s }
''' % (images(50, 50), images(50, 50), images(50, 50)),
[100, 100, 100]), # 'auto' margins are set to 0
('''@top-left { content: %s }
@top-center { content: %s }
@top-right { content: 'foo'; width: 200px }
''' % (images(100, 50), images(300, 150)),
[150, 300, 200]), # Use at least minimum widths, even if boxes overlap
('''@top-left { content: %s }
@top-center { content: %s }
@top-right { content: %s }
''' % (images(150, 150), images(150, 150), images(150, 150)),
[200, 200, 200]), # Distribute remaining space proportionally
('''@top-left { content: %s }
@top-center { content: %s }
@top-right { content: %s }
''' % (images(100, 100, 100), images(100, 100), images(10)),
[220, 160, 10]),
('''@top-left { content: %s; width: 205px }
@top-center { content: %s }
@top-right { content: %s }
''' % (images(100, 100, 100), images(100, 100), images(10)),
[205, 190, 10]),
('''@top-left { width: 1000px; margin: 1000px; padding: 1000px;
border: 1000px solid }
@top-center { content: %s }
@top-right { content: %s }
''' % (images(100, 100), images(10)),
[200, 10]), # 'width' and other have no effect without 'content'
('''@top-left { content: ''; width: 200px }
@top-center { content: ''; width: 300px }
@top-right { content: %s }
''' % images(50, 50), # This leaves 150px for @top-right’s shrink-to-fit
[200, 300, 100]),
('''@top-left { content: ''; width: 200px }
@top-center { content: ''; width: 300px }
@top-right { content: %s }
''' % images(100, 100, 100),
[200, 300, 150]),
('''@top-left { content: ''; width: 200px }
@top-center { content: ''; width: 300px }
@top-right { content: %s }
''' % images(170, 175),
[200, 300, 175]),
('''@top-left { content: ''; width: 200px }
@top-center { content: ''; width: 300px }
@top-right { content: %s }
''' % images(170, 175),
[200, 300, 175]),
('''@top-left { content: ''; width: 200px }
@top-right { content: ''; width: 500px }
''',
[200, 500]),
('''@top-left { content: ''; width: 200px }
@top-right { content: %s }
''' % images(150, 50, 150),
[200, 350]),
('''@top-left { content: ''; width: 200px }
@top-right { content: %s }
''' % images(150, 50, 150, 200),
[200, 400]),
('''@top-left { content: %s }
@top-right { content: ''; width: 200px }
''' % images(150, 50, 450),
[450, 200]),
('''@top-left { content: %s }
@top-right { content: %s }
''' % (images(150, 100), images(10, 120)),
[250, 130]),
('''@top-left { content: %s }
@top-right { content: %s }
''' % (images(550, 100), images(10, 120)),
[550, 120]),
('''@top-left { content: %s }
@top-right { content: %s }
''' % (images(250, 60), images(250, 180)),
[275, 325]), # 250 + (100 * 1 / 4), 250 + (100 * 3 / 4)
))
def test_page_style(css, widths):
expected_at_keywords = [
at_keyword for at_keyword in [
'@top-left', '@top-center', '@top-right']
if at_keyword + ' { content: ' in css]
page, = render_pages('''
''' % css)
assert page.children[0].element_tag == 'html'
margin_boxes = page.children[1:]
assert [box.at_keyword for box in margin_boxes] == expected_at_keywords
offsets = {'@top-left': 0, '@top-center': 0.5, '@top-right': 1}
for box in margin_boxes:
assert box.position_x == 100 + offsets[box.at_keyword] * (
600 - box.margin_width())
assert [box.margin_width() for box in margin_boxes] == widths
@assert_no_logs
def test_margin_boxes_vertical_align():
# 3 px -> +-----+
# | 1 |
# +-----+
#
# 43 px -> +-----+
# 53 px -> | 2 |
# +-----+
#
# 83 px -> +-----+
# | 3 |
# 103px -> +-----+
page, = render_pages('''
''')
html, top_left, top_center, top_right = page.children
line_1, = top_left.children
line_2, = top_center.children
line_3, = top_right.children
assert line_1.position_y == 3
assert line_2.position_y == 43
assert line_3.position_y == 83
@assert_no_logs
def test_margin_boxes_element():
pages = render_pages('''
test1
test2
test3
test4
test5
test6
''')
footer1_text = ''.join(
getattr(node, 'text', '')
for node in pages[0].children[1].descendants())
assert footer1_text == '1 of 3'
footer2_text = ''.join(
getattr(node, 'text', '')
for node in pages[1].children[1].descendants())
assert footer2_text == '2 of 3'
footer3_text = ''.join(
getattr(node, 'text', '')
for node in pages[2].children[1].descendants())
assert footer3_text == 'Static'
@assert_no_logs
@pytest.mark.parametrize('argument, texts', (
# TODO: start doesn’t work because running elements are removed from the
# original tree, and the current implentation in
# layout.get_running_element_for uses the tree to know if it’s at the
# beginning of the page
# ('start', ('', '2-first', '2-last', '3-last', '5')),
('first', ('', '2-first', '3-first', '3-last', '5')),
('last', ('', '2-last', '3-last', '3-last', '5')),
('first-except', ('', '', '', '3-last', '')),
))
def test_running_elements(argument, texts):
pages = render_pages('''
1
2-first
2-last
3
3-first
3-last
5
''' % argument)
assert len(pages) == 5
for page, text in zip(pages, texts):
html, margin = page.children
if margin.children:
h1, = margin.children
line, = h1.children
textbox, = line.children
assert textbox.text == text
else:
assert not text
@assert_no_logs
def test_running_elements_display():
page, = render_pages('''
text
block
inline
''')
html, left, center, right = page.children
assert ''.join(
getattr(node, 'text', '') for node in left.descendants()) == 'inline'
assert ''.join(
getattr(node, 'text', '') for node in center.descendants()) == 'block'
assert ''.join(
getattr(node, 'text', '') for node in right.descendants()) == 'table'