1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-05 08:27:22 +03:00
WeasyPrint/weasyprint/tests/test_layout.py

2876 lines
90 KiB
Python
Raw Normal View History

# coding: utf8
"""
weasyprint.tests.layout
-----------------------
Tests for layout, ie. positioning and dimensioning of boxes,
line breaks, page breaks.
:copyright: Copyright 2011-2012 Simon Sapin and contributors, see AUTHORS.
:license: BSD, see LICENSE for details.
2011-08-24 14:28:25 +04:00
"""
from __future__ import division, unicode_literals
2011-12-16 14:19:56 +04:00
from .testing_utils import (
2011-12-16 15:19:10 +04:00
TestPNGDocument, resource_filename, FONTS, assert_no_logs, capture_logs)
from .test_boxes import monkeypatch_validation
2011-12-16 14:19:56 +04:00
from ..formatting_structure import boxes
from ..layout.inlines import split_inline_box
from ..layout.percentages import resolve_percentages
from ..layout.preferred import (inline_preferred_width,
inline_preferred_minimum_width)
2011-08-25 14:50:23 +04:00
def body_children(page):
2011-09-09 01:15:14 +04:00
"""Take a ``page`` and return its <body>s children."""
html, = page.children
assert html.element_tag == 'html'
2011-08-25 14:50:23 +04:00
body, = html.children
assert body.element_tag == 'body'
2011-08-25 14:50:23 +04:00
return body.children
2011-09-02 14:03:37 +04:00
2011-08-25 14:33:32 +04:00
def parse_without_layout(html_content):
2011-09-09 01:15:14 +04:00
"""Parse some HTML, apply stylesheets, transform to boxes."""
return TestPNGDocument(html_content)
2011-08-25 14:33:32 +04:00
2011-09-09 01:15:14 +04:00
def validate_absolute_and_float(
real_non_shorthand, base_url, name, values, required=False):
2011-08-24 14:28:25 +04:00
"""Fake validator for ``absolute`` and ``float``."""
value = values[0].value
if (name == 'position' and value == 'absolute'
) or (name == 'float' and value == 'left'):
return [(name, value)]
return real_non_shorthand(base_url, name, values, required)
def parse(html_content, return_document=False):
2011-08-24 14:28:25 +04:00
"""Parse some HTML, apply stylesheets, transform to boxes and lay out."""
# TODO: remove this patching when asbolute and floats are validated
with monkeypatch_validation(validate_absolute_and_float):
document = TestPNGDocument(html_content,
base_url=resource_filename('<inline HTML>'))
if return_document:
return document
else:
return document.pages
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_page():
2011-08-24 14:28:25 +04:00
"""Test the layout for ``@page`` properties."""
pages = parse('<p>')
page = pages[0]
assert isinstance(page, boxes.PageBox)
assert int(page.outer_width) == 793 # A4: 210 mm in pixels
assert int(page.outer_height) == 1122 # A4: 297 mm in pixels
page, = parse('''<style>@page { -weasy-size: 2in 10in; }</style>''')
2011-07-11 17:03:06 +04:00
assert page.outer_width == 192
assert page.outer_height == 960
page, = parse('''<style>@page { -weasy-size: 242px; }</style>''')
2011-07-11 17:03:06 +04:00
assert page.outer_width == 242
assert page.outer_height == 242
page, = parse('''<style>@page { -weasy-size: letter; }</style>''')
2011-07-11 17:03:06 +04:00
assert page.outer_width == 816 # 8.5in
assert page.outer_height == 1056 # 11in
page, = parse('''<style>@page { -weasy-size: letter portrait; }</style>''')
2011-07-11 17:03:06 +04:00
assert page.outer_width == 816 # 8.5in
assert page.outer_height == 1056 # 11in
page, = parse('''<style>@page { -weasy-size: letter landscape; }</style>''')
2011-07-11 17:03:06 +04:00
assert page.outer_width == 1056 # 11in
assert page.outer_height == 816 # 8.5in
page, = parse('''<style>@page { -weasy-size: portrait; }</style>''')
2011-07-11 17:03:06 +04:00
assert int(page.outer_width) == 793 # A4: 210 mm
assert int(page.outer_height) == 1122 # A4: 297 mm
page, = parse('''<style>@page { -weasy-size: landscape; }</style>''')
2011-07-11 17:03:06 +04:00
assert int(page.outer_width) == 1122 # A4: 297 mm
assert int(page.outer_height) == 793 # A4: 210 mm
page, = parse('''
<style>@page { -weasy-size: 200px 300px; margin: 10px 10% 20% 1in }
body { margin: 8px }
</style>
2011-07-12 19:09:41 +04:00
<p style="margin: 0">
2011-07-11 17:03:06 +04:00
''')
assert page.outer_width == 200
assert page.outer_height == 300
2011-07-12 19:09:41 +04:00
assert page.position_x == 0
assert page.position_y == 0
2011-08-24 14:28:25 +04:00
assert page.width == 84 # 200px - 10% - 1 inch
assert page.height == 230 # 300px - 10px - 20%
html, = page.children
assert html.element_tag == 'html'
2011-08-24 14:28:25 +04:00
assert html.position_x == 96 # 1in
assert html.position_y == 10 # root elements margins do not collapse
assert html.width == 84
2011-08-25 14:50:23 +04:00
body, = html.children
assert body.element_tag == 'body'
2011-08-24 14:28:25 +04:00
assert body.position_x == 96 # 1in
2011-07-12 19:09:41 +04:00
assert body.position_y == 10
# body has margins in the UA stylesheet
assert body.margin_left == 8
assert body.margin_right == 8
2011-07-12 19:09:41 +04:00
assert body.margin_top == 8
assert body.margin_bottom == 8
assert body.width == 68
2011-08-25 14:50:23 +04:00
paragraph, = body.children
assert paragraph.element_tag == 'p'
2011-08-24 14:28:25 +04:00
assert paragraph.position_x == 104 # 1in + 8px
assert paragraph.position_y == 18 # 10px + 8px
2011-07-12 19:09:41 +04:00
assert paragraph.width == 68
page, = parse('''
<style>
@page { -weasy-size: 100px; margin: 1px 2px; padding: 4px 8px;
border-width: 16px 32px; border-style: solid }
</style>
<body>
''')
2011-08-24 14:28:25 +04:00
assert page.width == 16 # 100 - 2 * 42
assert page.height == 58 # 100 - 2 * 21
html, = page.children
assert html.element_tag == 'html'
2011-08-24 14:28:25 +04:00
assert html.position_x == 42 # 2 + 8 + 32
assert html.position_y == 21 # 1 + 4 + 16
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-07-12 15:50:54 +04:00
def test_block_widths():
2011-08-24 14:28:25 +04:00
"""Test the blocks widths."""
2011-08-22 20:14:37 +04:00
page, = parse('''
<style>
@page { margin: 0; -weasy-size: 120px 2000px }
body { margin: 0 }
2011-07-08 20:08:11 +04:00
div { margin: 10px }
p { padding: 2px; border-width: 1px; border-style: solid }
</style>
<div>
<p></p>
<p style="width: 50px"></p>
</div>
<div style="direction: rtl">
<p style="width: 50px; direction: rtl"></p>
</div>
<div>
<p style="margin: 0 10px 0 20px"></p>
<p style="width: 50px; margin-left: 20px; margin-right: auto"></p>
<p style="width: 50px; margin-left: auto; margin-right: 20px"></p>
<p style="width: 50px; margin: auto"></p>
<p style="margin-left: 20px; margin-right: auto"></p>
<p style="margin-left: auto; margin-right: 20px"></p>
<p style="margin: auto"></p>
<p style="width: 200px; margin: auto"></p>
<p style="min-width: 200px; margin: auto"></p>
<p style="max-width: 50px; margin: auto"></p>
<p style="min-width: 50px; margin: auto"></p>
<p style="width: 70%"></p>
</div>
2011-07-11 17:03:06 +04:00
''')
html, = page.children
assert html.element_tag == 'html'
2011-08-25 14:50:23 +04:00
body, = html.children
assert body.element_tag == 'body'
2011-07-08 20:08:11 +04:00
assert body.width == 120
divs = body.children
paragraphs = []
for div in divs:
assert isinstance(div, boxes.BlockBox)
assert div.element_tag == 'div'
assert div.width == 100
for paragraph in div.children:
assert isinstance(paragraph, boxes.BlockBox)
assert paragraph.element_tag == 'p'
assert paragraph.padding_left == 2
assert paragraph.padding_right == 2
assert paragraph.border_left_width == 1
assert paragraph.border_right_width == 1
paragraphs.append(paragraph)
assert len(paragraphs) == 15
# width is 'auto'
assert paragraphs[0].width == 94
assert paragraphs[0].margin_left == 0
assert paragraphs[0].margin_right == 0
# No 'auto', over-constrained equation with ltr, the initial
# 'margin-right: 0' was ignored.
assert paragraphs[1].width == 50
assert paragraphs[1].margin_left == 0
assert paragraphs[1].margin_right == 44
# No 'auto', over-constrained equation with ltr, the initial
# 'margin-right: 0' was ignored.
assert paragraphs[2].width == 50
assert paragraphs[2].margin_left == 44
assert paragraphs[2].margin_right == 0
# width is 'auto'
assert paragraphs[3].width == 64
assert paragraphs[3].margin_left == 20
assert paragraphs[3].margin_right == 10
# margin-right is 'auto'
assert paragraphs[4].width == 50
assert paragraphs[4].margin_left == 20
assert paragraphs[4].margin_right == 24
# margin-left is 'auto'
assert paragraphs[5].width == 50
assert paragraphs[5].margin_left == 24
assert paragraphs[5].margin_right == 20
# Both margins are 'auto', remaining space is split in half
assert paragraphs[6].width == 50
assert paragraphs[6].margin_left == 22
assert paragraphs[6].margin_right == 22
# width is 'auto', other 'auto' are set to 0
assert paragraphs[7].width == 74
assert paragraphs[7].margin_left == 20
assert paragraphs[7].margin_right == 0
# width is 'auto', other 'auto' are set to 0
assert paragraphs[8].width == 74
assert paragraphs[8].margin_left == 0
assert paragraphs[8].margin_right == 20
# width is 'auto', other 'auto' are set to 0
assert paragraphs[9].width == 94
assert paragraphs[9].margin_left == 0
assert paragraphs[9].margin_right == 0
# sum of non-auto initially is too wide, set auto values to 0
assert paragraphs[10].width == 200
assert paragraphs[10].margin_left == 0
assert paragraphs[10].margin_right == -106
2011-07-12 15:50:54 +04:00
# Constrained by min-width, same as above
assert paragraphs[11].width == 200
assert paragraphs[11].margin_left == 0
assert paragraphs[11].margin_right == -106
# Constrained by max-width, same as paragraphs[6]
assert paragraphs[12].width == 50
assert paragraphs[12].margin_left == 22
assert paragraphs[12].margin_right == 22
# NOT constrained by min-width
assert paragraphs[13].width == 94
assert paragraphs[13].margin_left == 0
assert paragraphs[13].margin_right == 0
# 70%
assert paragraphs[14].width == 70
assert paragraphs[14].margin_left == 0
assert paragraphs[14].margin_right == 24
2011-07-12 15:50:54 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-07-12 15:50:54 +04:00
def test_block_heights():
2011-08-24 14:28:25 +04:00
"""Test the blocks heights."""
2011-07-12 15:50:54 +04:00
page, = parse('''
<style>
@page { margin: 0; -weasy-size: 100px 20000px }
2011-07-12 15:50:54 +04:00
html, body { margin: 0 }
div { margin: 4px; border-width: 2px; border-style: solid;
padding: 4px }
/* Only use top margins so that margin collapsing does not change
the result: */
p { margin: 16px 0 0; border-width: 4px; border-style: solid;
2011-07-12 15:50:54 +04:00
padding: 8px; height: 50px }
</style>
<div>
<p></p>
<!-- These two are not in normal flow: the do not contribute to
the parents height. -->
<p style="position: absolute"></p>
<p style="float: left"></p>
</div>
<div>
<p></p>
<p></p>
2011-07-12 15:50:54 +04:00
<p></p>
</div>
<div style="height: 20px">
<p></p>
</div>
<div style="height: 120px">
2011-07-12 15:50:54 +04:00
<p></p>
</div>
<div style="max-height: 20px">
<p></p>
</div>
<div style="min-height: 120px">
<p></p>
</div>
<div style="min-height: 20px">
<p></p>
</div>
<div style="max-height: 120px">
2011-07-12 15:50:54 +04:00
<p></p>
</div>
''')
heights = [div.height for div in body_children(page)]
assert heights == [90, 90 * 3, 20, 120, 20, 120, 90, 90]
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-08-10 12:28:33 +04:00
def test_block_percentage_heights():
2011-08-24 14:28:25 +04:00
"""Test the blocks heights set in percents."""
page, = parse('''
<style>
html, body { margin: 0 }
body { height: 50% }
</style>
<body>
''')
html, = page.children
assert html.element_tag == 'html'
2011-08-25 14:50:23 +04:00
body, = html.children
assert body.element_tag == 'body'
# Since htmls height depend on bodys, bodys 50% means 'auto'
assert body.height == 0
page, = parse('''
<style>
html, body { margin: 0 }
html { height: 300px }
body { height: 50% }
</style>
<body>
''')
html, = page.children
assert html.element_tag == 'html'
2011-08-25 14:50:23 +04:00
body, = html.children
assert body.element_tag == 'body'
# This time the percentage makes sense
assert body.height == 150
@assert_no_logs
def test_inline_block_sizes():
"""Test the inline-block elements sizes."""
page, = parse('''
<style>
@page { margin: 0; -weasy-size: 200px 2000px }
body { margin: 0 }
div { display: inline-block; }
</style>
<div> </div>
<div>a</div>
<div style="margin: 10px; height: 100px"></div>
<div style="margin-left: 10px; margin-top: -50px;
padding-right: 20px;"></div>
<div>
Ipsum dolor sit amet,
consectetur adipiscing elit.
Sed sollicitudin nibh
et turpis molestie tristique.
</div>
<div style="width: 100px; height: 100px;
padding-left: 10px; margin-right: 10px;
margin-top: -10px; margin-bottom: 50px"></div>
<div style="font-size: 0">
<div style="min-width: 10px; height: 10px"></div>
<div style="width: 10%">
<div style="width: 10px; height: 10px"></div>
</div>
</div>
<div style="min-width: 185px">foo</div>
<div style="max-width: 10px">Supercalifragilisticexpialidocious</div>
''')
html, = page.children
assert html.element_tag == 'html'
body, = html.children
assert body.element_tag == 'body'
assert body.width == 200
line_1, line_2, line_3, line_4 = body.children
# First line:
# White space in-between divs ends up preserved in TextBoxes
div_1, _, div_2, _, div_3, _, div_4, _ = line_1.children
# First div, one ignored space collapsing with next space
assert div_1.element_tag == 'div'
assert div_1.width == 0
# Second div, one letter
assert div_2.element_tag == 'div'
assert 0 < div_2.width < 20
# Third div, empty with margin
assert div_3.element_tag == 'div'
assert div_3.width == 0
assert div_3.margin_width() == 20
assert div_3.height == 100
# Fourth div, empty with margin and padding
assert div_4.element_tag == 'div'
assert div_4.width == 0
assert div_4.margin_width() == 30
# Second line:
div_5, = line_2.children
# Fifth div, long text, full-width div
assert div_5.element_tag == 'div'
assert len(div_5.children) > 1
assert div_5.width == 200
# Third line:
div_6, _, div_7, _ = line_3.children
# Sixth div, empty div with fixed width and height
assert div_6.element_tag == 'div'
assert div_6.width == 100
assert div_6.margin_width() == 120
assert div_6.height == 100
assert div_6.margin_height() == 140
# Seventh div
assert div_7.element_tag == 'div'
assert div_7.width == 20
child_line, = div_7.children
# Spaces have font-size: 0, they get removed
child_div_1, child_div_2 = child_line.children
assert child_div_1.element_tag == 'div'
assert child_div_1.width == 10
assert child_div_2.element_tag == 'div'
assert child_div_2.width == 2
grandchild, = child_div_2.children
assert grandchild.element_tag == 'div'
assert grandchild.width == 10
div_8, _, div_9 = line_4.children
assert div_8.width == 185
assert div_9.width == 10
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_lists():
2011-08-24 14:28:25 +04:00
"""Test the lists."""
page, = parse('''
<style>
body { margin: 0 }
ul { margin-left: 50px; list-style: inside circle }
</style>
<ul>
<li>abc</li>
</ul>
''')
2011-08-25 14:50:23 +04:00
unordered_list, = body_children(page)
list_item, = unordered_list.children
line, = list_item.children
marker, content = line.children
assert marker.text == ''
assert marker.margin_left == 0
assert marker.margin_right == 8
assert content.text == 'abc'
page, = parse('''
<style>
body { margin: 0 }
ul { margin-left: 50px; }
</style>
<ul>
<li>abc</li>
</ul>
''')
2011-08-25 14:50:23 +04:00
unordered_list, = body_children(page)
list_item, = unordered_list.children
marker = list_item.outside_list_marker
font_size = marker.style.font_size
assert marker.margin_right == 0.5 * font_size # 0.5em
2011-08-24 14:28:25 +04:00
assert marker.position_x == (
list_item.padding_box_x() - marker.width - marker.margin_right)
assert marker.position_y == list_item.position_y
assert marker.text == ''
line, = list_item.children
content, = line.children
assert content.text == 'abc'
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-08-24 14:28:25 +04:00
def test_empty_linebox():
"""Test lineboxes with no content other than space-like characters."""
2012-04-02 16:45:44 +04:00
page, = parse('<p> </p>')
paragraph, = body_children(page)
assert len(paragraph.children) == 0
assert paragraph.height == 0
# Whitespace removed at the beginning of the line => empty line => no line
page, = parse('''
<style>
2012-04-02 16:45:44 +04:00
p { width: 1px }
</style>
2012-04-02 16:45:44 +04:00
<p><br> </p>
''')
paragraph, = body_children(page)
2012-04-02 16:45:44 +04:00
assert len(paragraph.children) == 1
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-10-03 18:37:02 +04:00
def test_breaking_linebox():
"""Test lineboxes breaks with a lot of text and deep nesting."""
page, = parse('''
<style>
p { font-size: 13px;
width: 300px;
font-family: %(fonts)s;
background-color: #393939;
color: #FFFFFF;
line-height: 1;
text-decoration: underline overline line-through;}
</style>
<p><em>Lorem<strong> Ipsum <span>is very</span>simply</strong><em>
dummy</em>text of the printing and. naaaa </em> naaaa naaaa naaaa
naaaa naaaa naaaa naaaa naaaa</p>
''' % {'fonts': FONTS})
html, = page.children
body, = html.children
paragraph, = body.children
2011-10-03 18:37:02 +04:00
assert len(list(paragraph.children)) == 3
lines = paragraph.children
for line in lines:
assert line.style.font_size == 13
assert line.element_tag == 'p'
2011-10-03 18:37:02 +04:00
for child in line.children:
assert child.element_tag in ('em', 'p')
assert child.style.font_size == 13
2011-10-03 18:37:02 +04:00
if isinstance(child, boxes.ParentBox):
for child_child in child.children:
assert child.element_tag in ('em', 'strong', 'span')
assert child.style.font_size == 13
2011-07-28 20:39:35 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_linebox_text():
2011-08-24 14:28:25 +04:00
"""Test the creation of line boxes."""
page, = parse('''
<style>
p { width: 165px; font-family:%(fonts)s;}
</style>
<p><em>Lorem Ipsum</em>is very <strong>coool</strong></p>
''' % {'fonts': FONTS})
paragraph, = body_children(page)
lines = list(paragraph.children)
assert len(lines) == 2
2011-11-08 16:09:19 +04:00
text = ' '.join(
(''.join(box.text for box in line.descendants()
if isinstance(box, boxes.TextBox)))
for line in lines)
2011-11-08 16:09:19 +04:00
assert text == 'Lorem Ipsumis very coool'
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_linebox_positions():
2011-08-24 14:28:25 +04:00
"""Test the position of line boxes."""
for width, expected_lines in [(165, 2), (1, 5), (0, 5)]:
page = '''
<style>
p { width:%(width)spx; font-family:%(fonts)s;
line-height: 20px }
</style>
<p>this is test for <strong>Weasyprint</strong></p>'''
page, = parse(page % {'fonts': FONTS, 'width': width})
paragraph, = body_children(page)
lines = list(paragraph.children)
assert len(lines) == expected_lines
ref_position_y = lines[0].position_y
ref_position_x = lines[0].position_x
for line in lines:
assert ref_position_y == line.position_y
assert ref_position_x == line.position_x
for box in line.children:
assert ref_position_x == box.position_x
ref_position_x += box.width
assert ref_position_y == box.position_y
assert ref_position_x - line.position_x <= line.width
ref_position_x = line.position_x
ref_position_y += line.height
2011-08-22 20:14:37 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-08-25 14:50:23 +04:00
def test_forced_line_breaks():
"""Test <pre> and <br>."""
# These lines should be small enough to fit on the default A4 page
# with the default 12pt font-size.
2011-09-09 01:15:14 +04:00
page, = parse('''
2011-10-19 17:14:43 +04:00
<style> pre { line-height: 42px }</style>
2011-08-25 14:50:23 +04:00
<pre>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
2011-10-19 17:14:43 +04:00
2011-08-25 14:50:23 +04:00
Sed sollicitudin nibh
2011-10-19 17:14:43 +04:00
2011-08-25 14:50:23 +04:00
et turpis molestie tristique.</pre>
2011-09-09 01:15:14 +04:00
''')
2011-08-25 14:50:23 +04:00
pre, = body_children(page)
assert pre.element_tag == 'pre'
2011-08-25 14:50:23 +04:00
lines = pre.children
assert all(isinstance(line, boxes.LineBox) for line in lines)
2011-10-19 17:14:43 +04:00
assert len(lines) == 7
assert [line.height for line in lines] == [42] * 7
2011-08-25 14:50:23 +04:00
2011-09-09 01:15:14 +04:00
page, = parse('''
2011-10-19 17:14:43 +04:00
<style> p { line-height: 42px }</style>
2011-08-25 14:50:23 +04:00
<p>Lorem ipsum dolor sit amet,<br>
2011-10-19 17:14:43 +04:00
consectetur adipiscing elit.<br><br><br>
2011-08-25 14:50:23 +04:00
Sed sollicitudin nibh<br>
2011-10-19 17:14:43 +04:00
<br>
2011-08-25 14:50:23 +04:00
et turpis molestie tristique.</p>
2011-09-09 01:15:14 +04:00
''')
2011-08-25 14:50:23 +04:00
pre, = body_children(page)
assert pre.element_tag == 'p'
2011-08-25 14:50:23 +04:00
lines = pre.children
assert all(isinstance(line, boxes.LineBox) for line in lines)
2011-10-19 17:14:43 +04:00
assert len(lines) == 7
assert [line.height for line in lines] == [42] * 7
2011-08-25 14:50:23 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-08-22 20:14:37 +04:00
def test_page_breaks():
2011-08-24 14:28:25 +04:00
"""Test the page breaks."""
2011-08-22 20:14:37 +04:00
pages = parse('''
<style>
@page { -weasy-size: 100px; margin: 10px }
2011-08-22 20:14:37 +04:00
body { margin: 0 }
div { height: 30px; font-size: 20px; }
2011-08-22 20:14:37 +04:00
</style>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
2011-08-22 20:14:37 +04:00
''')
page_divs = []
for page in pages:
2011-08-25 14:50:23 +04:00
divs = body_children(page)
assert all([div.element_tag == 'div' for div in divs])
2011-08-22 20:14:37 +04:00
assert all([div.position_x == 10 for div in divs])
page_divs.append(divs)
2011-08-24 14:28:25 +04:00
positions_y = [[div.position_y for div in divs] for divs in page_divs]
2011-08-22 20:14:37 +04:00
assert positions_y == [[10, 40], [10, 40], [10]]
2011-08-25 14:33:32 +04:00
# Same as above, but no content inside each <div>.
# TODO: This currently gives no page break. Should it?
# pages = parse('''
# <style>
# @page { -weasy-size: 100px; margin: 10px }
# body { margin: 0 }
# div { height: 30px }
# </style>
# <div/><div/><div/><div/><div/>
# ''')
# page_divs = []
# for page in pages:
# divs = body_children(page)
# assert all([div.element_tag == 'div' for div in divs])
# assert all([div.position_x == 10 for div in divs])
# page_divs.append(divs)
# positions_y = [[div.position_y for div in divs] for divs in page_divs]
# assert positions_y == [[10, 40], [10, 40], [10]]
pages = parse('''
<style>
@page { -weasy-size: 100px; margin: 10px }
img { height: 30px; display: block }
</style>
<body>
<img src=pattern.png>
<img src=pattern.png>
<img src=pattern.png>
<img src=pattern.png>
<img src=pattern.png>
''')
page_images = []
for page in pages:
images = body_children(page)
assert all([img.element_tag == 'img' for img in images])
assert all([img.position_x == 10 for img in images])
page_images.append(images)
positions_y = [[img.position_y for img in images]
for images in page_images]
assert positions_y == [[10, 40], [10, 40], [10]]
page_1, page_2, page_3, page_4 = parse('''
<style>
@page { margin: 10px }
@page :left { margin-left: 50px }
@page :right { margin-right: 50px }
html { page-break-before: left }
div { page-break-after: left }
ul { page-break-before: always }
</style>
<div>1</div>
<p>2</p>
<p>3</p>
<article>
<section>
<ul><li>4</li></ul>
</section>
</article>
''')
# The first page is a right page on rtl, but not here because of
# page-break-before on the root element.
assert page_1.margin_left == 50 # left page
assert page_1.margin_right == 10
html, = page_1.children
body, = html.children
div, = body.children
line, = div.children
text, = line.children
assert div.element_tag == 'div'
assert text.text == '1'
assert page_2.margin_left == 10
assert page_2.margin_right == 50 # right page
assert not page_2.children # empty page to get to a left page
assert page_3.margin_left == 50 # left page
assert page_3.margin_right == 10
html, = page_3.children
body, = html.children
p_1, p_2 = body.children
assert p_1.element_tag == 'p'
assert p_2.element_tag == 'p'
assert page_4.margin_left == 10
assert page_4.margin_right == 50 # right page
html, = page_4.children
body, = html.children
article, = body.children
section, = article.children
ulist, = section.children
assert ulist.element_tag == 'ul'
2011-08-25 14:33:32 +04:00
2012-03-14 22:33:24 +04:00
@assert_no_logs
2012-03-16 19:45:31 +04:00
def test_orphans_widows_avoid():
2012-03-14 22:33:24 +04:00
"""Test orphans and widows control."""
2012-03-16 19:45:31 +04:00
def line_distribution(css):
2012-03-14 22:33:24 +04:00
pages = parse('''
<style>
@page { -weasy-size: 200px }
h1 { height: 120px }
p { line-height: 20px;
width: 1px; /* line break at each word */
2012-03-16 19:45:31 +04:00
%s }
2012-03-14 22:33:24 +04:00
</style>
<h1>Tasty test</h1>
<!-- There is room for 4 lines after h1 on the fist page -->
<p>
one
two
three
four
five
six
seven
</p>
2012-03-16 19:45:31 +04:00
''' % css)
2012-03-14 22:33:24 +04:00
line_counts = []
for i, page in enumerate(pages):
html, = page.children
body, = html.children
if i == 0:
body_children = body.children[1:] # skip h1
else:
body_children = body.children
if body_children:
paragraph, = body_children
line_counts.append(len(paragraph.children))
else:
line_counts.append(0)
return line_counts
2012-03-16 19:45:31 +04:00
assert line_distribution('orphans: 2; widows: 2') == [4, 3]
assert line_distribution('orphans: 5; widows: 2') == [0, 7]
assert line_distribution('orphans: 2; widows: 4') == [3, 4]
assert line_distribution('orphans: 4; widows: 4') == [0, 7]
assert line_distribution(
'orphans: 2; widows: 2; page-break-inside: avoid') == [0, 7]
2012-03-14 22:33:24 +04:00
@assert_no_logs
def test_table_page_breaks():
"""Test the page breaks inside tables."""
def run(html):
pages = parse(html)
rows_per_page = []
rows_position_y = []
for i, page in enumerate(pages):
html, = page.children
body, = html.children
if i == 0:
body_children = body.children[1:] # skip h1
else:
body_children = body.children
if not body_children:
rows_per_page.append(0)
continue
table_wrapper, = body_children
table, = table_wrapper.children
rows_in_this_page = 0
for group in table.children:
assert group.children, 'found an empty table group'
for row in group.children:
rows_in_this_page += 1
rows_position_y.append(row.position_y)
cell, = row.children
line, = cell.children
text, = line.children
assert text.text == 'row %i' % len(rows_position_y)
rows_per_page.append(rows_in_this_page)
return rows_per_page, rows_position_y
rows_per_page, rows_position_y = run('''
<style>
@page { -weasy-size: 120px }
2012-03-23 22:31:54 +04:00
table { table-layout: fixed }
h1 { height: 30px }
td { height: 40px }
</style>
<h1>Dummy title</h1>
<table>
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
<tr><td>row 4</td></tr>
<tr><td>row 5</td></tr>
<tr><td style="height: 300px"> <!-- overflow the page -->
row 6</td></tr>
<tr><td>row 7</td></tr>
<tr><td>row 8</td></tr>
</table>
''')
assert rows_per_page == [2, 3, 1, 2]
assert rows_position_y == [30, 70, 0, 40, 80, 0, 0, 40]
rows_per_page, rows_position_y = run('''
<style>
@page { -weasy-size: 120px }
h1 { height: 30px}
td { height: 40px }
2012-03-23 22:31:54 +04:00
table { page-break-inside: avoid; table-layout: fixed }
</style>
<h1>Dummy title</h1>
<table>
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
<tr><td>row 4</td></tr>
</table>
''')
assert rows_per_page == [0, 3, 1]
assert rows_position_y == [0, 40, 80, 0]
rows_per_page, rows_position_y = run('''
<style>
@page { -weasy-size: 120px }
h1 { height: 30px}
td { height: 40px }
2012-03-23 22:31:54 +04:00
table { page-break-inside: avoid; table-layout: fixed }
</style>
<h1>Dummy title</h1>
<table>
<tbody>
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
</tbody>
<tr><td>row 4</td></tr>
</table>
''')
assert rows_per_page == [0, 3, 1]
assert rows_position_y == [0, 40, 80, 0]
rows_per_page, rows_position_y = run('''
<style>
@page { -weasy-size: 120px }
h1 { height: 30px}
td { height: 40px }
2012-03-23 22:31:54 +04:00
table { table-layout: fixed }
</style>
<h1>Dummy title</h1>
<table>
<tr><td>row 1</td></tr>
<tbody style="page-break-inside: avoid">
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
</tbody>
</table>
''')
assert rows_per_page == [1, 2]
assert rows_position_y == [30, 0, 40]
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-08-25 14:33:32 +04:00
def test_inlinebox_spliting():
2011-08-25 19:27:00 +04:00
"""Test the inline boxes spliting."""
2011-08-25 14:33:32 +04:00
def get_inlinebox(content):
"""Helper returning a inlinebox with customizable style."""
page = '<style>p { font-family:%(fonts)s;}</style>'
2011-08-25 14:33:32 +04:00
page = '%s <p>%s</p>' % (page, content)
document = parse_without_layout(page % {'fonts': FONTS})
html = document.formatting_structure
body, = html.children
paragraph, = body.children
line, = paragraph.children
inline, = line.children
paragraph.width = 200
paragraph.height = 'auto'
return document, inline, paragraph
2011-08-25 14:33:32 +04:00
def get_parts(document, inlinebox, width, parent):
2011-09-09 01:02:17 +04:00
"""Yield the parts of the splitted ``inlinebox`` of given ``width``."""
skip = None
while 1:
box, skip, _ = split_inline_box(
document, inlinebox, 0, width, skip, parent, None)
yield box
if skip is None:
break
2011-08-25 14:33:32 +04:00
def get_joined_text(parts):
2011-09-09 01:02:17 +04:00
"""Get the joined text from ``parts``."""
2011-11-08 16:09:19 +04:00
return ''.join(part.children[0].text for part in parts)
2011-08-25 14:33:32 +04:00
def test_inlinebox_spacing(inlinebox, value, side):
2011-09-09 01:02:17 +04:00
"""Test the margin, padding and border-width of ``inlinebox``."""
2011-10-19 17:14:43 +04:00
if side in ['left', 'right']:
# Vertical margins on inlines are irrelevant.
assert getattr(inlinebox, 'margin_%s' % side) == value
2011-08-25 14:33:32 +04:00
assert getattr(inlinebox, 'padding_%s' % side) == value
assert getattr(inlinebox, 'border_%s_width' % side) == value
2011-08-25 14:33:32 +04:00
2011-09-09 01:02:17 +04:00
content = '''<strong>WeasyPrint is a free software visual rendering engine
for HTML and CSS</strong>'''
2011-08-25 14:33:32 +04:00
document, inlinebox, parent = get_inlinebox(content)
resolve_percentages(inlinebox, parent)
2011-11-08 16:09:19 +04:00
original_text = inlinebox.children[0].text
2011-08-25 14:33:32 +04:00
# test with width = 1000
parts = list(get_parts(document, inlinebox, 1000, parent))
2011-08-25 14:33:32 +04:00
assert len(parts) == 1
assert original_text == get_joined_text(parts)
document, inlinebox, parent = get_inlinebox(content)
resolve_percentages(inlinebox, parent)
2011-11-08 16:09:19 +04:00
original_text = inlinebox.children[0].text
2011-08-25 19:27:00 +04:00
2011-08-25 14:33:32 +04:00
# test with width = 100
parts = list(get_parts(document, inlinebox, 100, parent))
assert len(parts) > 1
2011-08-25 14:33:32 +04:00
assert original_text == get_joined_text(parts)
2011-08-25 19:27:00 +04:00
document, inlinebox, parent = get_inlinebox(content)
resolve_percentages(inlinebox, parent)
2011-11-08 16:09:19 +04:00
original_text = inlinebox.children[0].text
2011-08-25 14:33:32 +04:00
# test with width = 10
parts = list(get_parts(document, inlinebox, 10, parent))
assert len(parts) > 1
2011-08-25 14:33:32 +04:00
assert original_text == get_joined_text(parts)
# test with width = 0
parts = list(get_parts(document, inlinebox, 0, parent))
assert len(parts) > 1
assert original_text == get_joined_text(parts)
2011-08-25 14:33:32 +04:00
# with margin-border-padding
2011-09-09 01:02:17 +04:00
content = '''<strong style="border:10px solid; margin:10px; padding:10px">
2011-08-25 14:33:32 +04:00
WeasyPrint is a free software visual rendering engine
2011-09-09 01:02:17 +04:00
for HTML and CSS</strong>'''
2011-08-25 14:33:32 +04:00
document, inlinebox, parent = get_inlinebox(content)
resolve_percentages(inlinebox, parent)
2011-11-08 16:09:19 +04:00
original_text = inlinebox.children[0].text
2011-08-25 14:33:32 +04:00
# test with width = 1000
parts = list(get_parts(document, inlinebox, 1000, parent))
2011-08-25 14:33:32 +04:00
assert len(parts) == 1
assert original_text == get_joined_text(parts)
for side in ('left', 'top', 'bottom', 'right'):
test_inlinebox_spacing(parts[0], 10, side)
document, inlinebox, parent = get_inlinebox(content)
resolve_percentages(inlinebox, parent)
2011-11-08 16:09:19 +04:00
original_text = inlinebox.children[0].text
2011-08-25 19:27:00 +04:00
2011-08-25 14:33:32 +04:00
# test with width = 1000
parts = list(get_parts(document, inlinebox, 100, parent))
2011-08-25 14:33:32 +04:00
assert len(parts) != 1
assert original_text == get_joined_text(parts)
first_inline_box = parts.pop(0)
test_inlinebox_spacing(first_inline_box, 10, 'left')
test_inlinebox_spacing(first_inline_box, 0, 'right')
last_inline_box = parts.pop()
test_inlinebox_spacing(last_inline_box, 10, 'right')
test_inlinebox_spacing(last_inline_box, 0, 'left')
for part in parts:
test_inlinebox_spacing(part, 0, 'right')
test_inlinebox_spacing(part, 0, 'left')
2011-08-25 19:27:00 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-08-25 19:27:00 +04:00
def test_inlinebox_text_after_spliting():
"""Test the inlinebox text after spliting."""
document = parse_without_layout('''
<style>p { width: 200px; font-family:%(fonts)s;}</style>
<p><strong><em><em><em>
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
</em></em></em></strong></p>
''' % {'fonts': FONTS})
html = document.formatting_structure
body, = html.children
paragraph, = body.children
line, = paragraph.children
inlinebox, = line.children
paragraph.width = 200
paragraph.height = 'auto'
resolve_percentages(inlinebox, paragraph)
2011-08-25 19:27:00 +04:00
2011-11-08 16:09:19 +04:00
original_text = ''.join(
part.text for part in inlinebox.descendants()
if isinstance(part, boxes.TextBox))
2011-08-25 19:27:00 +04:00
# test with width = 10
parts = []
skip = None
while 1:
box, skip, _ = split_inline_box(
document, inlinebox, 0, 100, skip, paragraph, None)
parts.append(box)
if skip is None:
break
2011-08-25 19:27:00 +04:00
assert len(parts) > 2
2011-11-08 16:09:19 +04:00
assert ''.join(
child.text
for part in parts
for child in part.descendants()
if isinstance(child, boxes.TextBox)
) == original_text
2011-08-25 19:27:00 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-08-25 19:27:00 +04:00
def test_page_and_linebox_breaking():
"""Test the linebox text after spliting linebox and page."""
# The empty <span/> tests a corner case
# in skip_first_whitespace()
pages = parse('''
<style>
div { font-family:%(fonts)s; font-size:22px}
@page { -weasy-size: 100px; margin:2px; border:1px solid }
body { margin: 0 }
</style>
<div><span/>1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15</div>
''' % {'fonts': FONTS})
texts = []
for page in pages:
html, = page.children
body, = html.children
div, = body.children
lines = div.children
2011-08-25 19:27:00 +04:00
for line in lines:
line_texts = []
2011-08-25 19:27:00 +04:00
for child in line.descendants():
if isinstance(child, boxes.TextBox):
2011-11-08 16:09:19 +04:00
line_texts.append(child.text)
texts.append(''.join(line_texts))
2011-08-25 19:27:00 +04:00
assert len(pages) == 2
2011-11-08 16:09:19 +04:00
assert ' '.join(texts) == \
'1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15'
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_whitespace_processing():
2011-11-05 02:55:26 +04:00
"""Test various spaces and tabulations processing."""
for source in ['a', ' a ', ' \n \ta', ' a\t ']:
page, = parse('<p><em>%s</em></p>' % source)
html, = page.children
body, = html.children
p, = body.children
line, = p.children
em, = line.children
text, = em.children
2011-11-08 16:09:19 +04:00
assert text.text == 'a', 'source was %r' % (source,)
page, = parse('<p style="white-space: pre-line">\n\n<em>%s</em></pre>'
% source.replace('\n', ' '))
html, = page.children
body, = html.children
p, = body.children
_line1, _line2, line3 = p.children
em, = line3.children
text, = em.children
2011-11-08 16:09:19 +04:00
assert text.text == 'a', 'source was %r' % (source,)
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_images():
2011-11-05 02:55:26 +04:00
"""Test that width, height and ratio of images are respected."""
def get_img(html):
page, = parse(html)
html, = page.children
body, = html.children
line, = body.children
img, = line.children
return body, img
2011-12-09 13:15:55 +04:00
# Try a few image formats
2012-02-29 20:38:30 +04:00
for html in [
'<img src="%s">' % url for url in [
'pattern.png', 'pattern.gif', 'blue.jpg', 'pattern.svg',
"data:image/svg+xml,<svg width='4' height='4'></svg>",
"data:image/svg+xml,<svg width='4px' height='4px'></svg>",
]
] + [
'<embed src=pattern.png>',
'<embed src=pattern.svg>',
'<embed src=really-a-png.svg type=image/png>',
'<embed src=really-a-svg.png type=image/svg+xml>',
'<object data=pattern.png>',
'<object data=pattern.svg>',
'<object data=really-a-png.svg type=image/png>',
'<object data=really-a-svg.png type=image/svg+xml>',
]:
body, img = get_img(html)
2011-12-09 13:15:55 +04:00
assert img.width == 4
assert img.height == 4
2011-12-08 21:25:19 +04:00
2012-01-13 21:16:27 +04:00
# With physical units
url = "data:image/svg+xml,<svg width='2.54cm' height='0.5in'></svg>"
body, img = get_img('<img src="%s">' % url)
2012-01-13 21:16:27 +04:00
assert img.width == 96
assert img.height == 48
2011-12-14 20:57:05 +04:00
# Invalid images
for url in [
'inexistant.png',
'unknownprotocol://weasyprint.org/foo.png',
'data:image/unknowntype,Not an image',
# zero-byte images
'data:image/png,',
'data:image/jpeg,',
'data:image/svg+xml,',
# Incorrect format
'data:image/png,Not a PNG',
'data:image/jpeg,Not a JPEG',
'data:image/svg+xml,<svg>invalid xml',
2012-02-29 20:38:30 +04:00
'really-a-svg.png',
2011-12-14 20:57:05 +04:00
]:
2011-12-16 15:19:10 +04:00
with capture_logs() as logs:
body, img = get_img("<img src='%s' alt='invalid image'>" % url)
2011-12-16 15:19:10 +04:00
assert len(logs) == 1
assert 'WARNING: Error for image' in logs[0]
assert isinstance(img, boxes.InlineBox) # not a replaced box
2011-12-14 20:57:05 +04:00
text, = img.children
assert text.text == 'invalid image', url
2011-12-08 19:31:03 +04:00
# Layout rules try to preserve the ratio, so the height should be 40px too:
body, img = get_img('<img src="pattern.png" style="width: 40px">')
assert body.height == 40
assert img.position_y == 0
2011-12-08 19:31:03 +04:00
assert img.width == 40
assert img.height == 40
# Same with percentages
body, img = get_img('''<p style="width: 200px">
<img src="pattern.png" style="width: 20%">''')
assert body.height == 40
assert img.position_y == 0
assert img.width == 40
assert img.height == 40
body, img = get_img('<img src="pattern.png" style="min-width: 40px">')
assert body.height == 40
assert img.position_y == 0
assert img.width == 40
assert img.height == 40
body, img = get_img('<img src="pattern.png" style="max-width: 2px">')
assert img.width == 2
assert img.height == 2
# display: table-cell is ignored
page, = parse('''
<img src="pattern.png" style="width: 40px">
<img src="pattern.png" style="width: 60px; display: table-cell">
''')
html, = page.children
body, = html.children
line, = body.children
img_1, img_2 = line.children
assert body.height == 60
assert img_1.width == 40
assert img_1.height == 40
assert img_2.width == 60
assert img_2.height == 60
assert img_1.position_y == 20
assert img_2.position_y == 0
# Block-level image:
page, = parse('''
<style>
@page { -weasy-size: 100px }
img { width: 40px; margin: 10px auto; display: block }
</style>
<body>
<img src="pattern.png">
''')
html, = page.children
body, = html.children
img, = body.children
assert img.element_tag == 'img'
assert img.position_x == 0
assert img.position_y == 0
assert img.content_box_x() == 30 # (100 - 40) / 2 == 30px for margin-left
assert img.content_box_y() == 10
page, = parse('''
<style>
@page { -weasy-size: 100px }
img { min-width: 40%; margin: 10px auto; display: block }
</style>
<body>
<img src="pattern.png">
''')
html, = page.children
body, = html.children
img, = body.children
assert img.element_tag == 'img'
assert img.position_x == 0
assert img.position_y == 0
assert img.content_box_x() == 30 # (100 - 40) / 2 == 30px for margin-left
assert img.content_box_y() == 10
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_vertical_align():
"""Test various values of vertical-align."""
"""
+-------+ <- position_y = 0
+-----+ |
40px | | | 60px
| | |
+-----+-------+ <- baseline
"""
page, = parse('''
<span>
<img src="pattern.png" style="width: 40px">
<img src="pattern.png" style="width: 60px">
</span>
''')
html, = page.children
body, = html.children
line, = body.children
span, = line.children
img_1, img_2 = span.children
assert img_1.height == 40
assert img_2.height == 60
assert img_1.position_y == 20
assert img_2.position_y == 0
# 60px + the descent of the font below the baseline
assert 60 < line.height < 70
assert body.height == line.height
"""
+-------+ <- position_y = 0
35px | |
+-----+ | 60px
40px | | |
| +-------+ <- baseline
+-----+ 15px
"""
page, = parse('''
<span>
<img src="pattern.png" style="width: 40px; vertical-align: -15px">
<img src="pattern.png" style="width: 60px">
</span>
''')
html, = page.children
body, = html.children
line, = body.children
span, = line.children
img_1, img_2 = span.children
assert img_1.height == 40
assert img_2.height == 60
assert img_1.position_y == 35
assert img_2.position_y == 0
assert line.height == 75
assert body.height == line.height
# Same as previously, but with percentages
page, = parse('''
<span style="line-height: 10px">
<img src="pattern.png" style="width: 40px; vertical-align: -150%">
<img src="pattern.png" style="width: 60px">
</span>
''')
html, = page.children
body, = html.children
line, = body.children
span, = line.children
img_1, img_2 = span.children
assert img_1.height == 40
assert img_2.height == 60
assert img_1.position_y == 35
assert img_2.position_y == 0
assert line.height == 75
assert body.height == line.height
# Same again, but have the vertical-align on an inline box.
page, = parse('''
<span style="line-height: 10px">
<span style="line-height: 10px; vertical-align: -15px">
<img src="pattern.png" style="width: 40px">
</span>
<img src="pattern.png" style="width: 60px">
</span>
''')
html, = page.children
body, = html.children
line, = body.children
span_1, = line.children
span_2, _whitespace, img_1 = span_1.children
img_1, = span_2.children
assert img_1.height == 40
assert img_2.height == 60
assert img_1.position_y == 35
assert img_2.position_y == 0
assert line.height == 75
assert body.height == line.height
2011-11-23 21:23:33 +04:00
# Same as previously, but with percentages
page, = parse('''
<span style="line-height: 12px; font-size: 12px">
<img src="pattern.png" style="width: 40px; vertical-align: middle">
<img src="pattern.png" style="width: 60px">
</span>
''')
html, = page.children
2011-11-23 21:23:33 +04:00
body, = html.children
line, = body.children
span, = line.children
img_1, img_2 = span.children
assert img_1.height == 40
assert img_2.height == 60
# middle of the image (position_y + 20) is at half the ex-height above
# the baseline of the parent. Currently the ex-height is 0.5em
# TODO: update this when we actually get ex form the font metrics
assert img_1.position_y == 37 # 60 - 0.5 * 0.5 * font-size - 40/2
assert img_2.position_y == 0
assert line.height == 77
assert body.height == line.height
# sup and sub currently mean +/- 0.5 em
# With the initial 16px font-size, thats 8px.
page, = parse('''
<span style="line-height: 10px">
<img src="pattern.png" style="width: 60px">
<img src="pattern.png" style="width: 40px; vertical-align: super">
<img src="pattern.png" style="width: 40px; vertical-align: sub">
</span>
''')
html, = page.children
body, = html.children
line, = body.children
span, = line.children
img_1, img_2, img_3 = span.children
assert img_1.height == 60
assert img_2.height == 40
assert img_3.height == 40
assert img_1.position_y == 0
assert img_2.position_y == 12 # 20 - 16 * 0.5
assert img_3.position_y == 28 # 20 + 16 * 0.5
assert line.height == 68
assert body.height == line.height
# Pango gives a height of 19px for font-size of 16px
page, = parse('''
<span style="line-height: 10px">
<img src="pattern.png" style="vertical-align: text-top">
<img src="pattern.png" style="vertical-align: text-bottom">
</span>
''')
html, = page.children
body, = html.children
line, = body.children
span, = line.children
img_1, img_2 = span.children
assert img_1.height == 4
assert img_2.height == 4
assert img_1.position_y == 0
assert img_2.position_y == 15 # 19 - 4
assert line.height == 19
assert body.height == line.height
2011-11-23 21:23:33 +04:00
# This case used to cause an exception:
# The second span has no children but should count for line heights
# since it has padding.
page, = parse('<span><span style="padding: 1px"></span></span>')
html, = page.children
body, = html.children
line, = body.children
span_1, = line.children
span_2, = span_1.children
assert span_1.height == 19
assert span_2.height == 19
page, = parse('''
<span>
<img src="pattern.png" style="width: 40px; vertical-align: -15px">
<img src="pattern.png" style="width: 60px">
</span>
2012-04-10 17:07:10 +04:00
<div style="display: inline-block; vertical-align: 3px">
<div>
<div style="height: 100px">foo</div>
<div>
<img src="pattern.png" style="
width: 40px; vertical-align: -15px">
<img src="pattern.png" style="width: 60px">
</div>
</div>
</div>
''')
html, = page.children
body, = html.children
line, = body.children
2012-04-10 17:07:10 +04:00
span, _, div_1 = line.children # _ is white space
assert line.height == 178
assert body.height == line.height
# Same as earlier
img_1, img_2 = span.children
assert img_1.height == 40
assert img_2.height == 60
2012-04-10 17:07:10 +04:00
assert img_1.position_y == 138
assert img_2.position_y == 103
2012-04-10 17:07:10 +04:00
div_2, = div_1.children
div_3, div_4 = div_2.children
div_line, = div_4.children
div_img_1, div_img_2 = div_line.children
2012-04-10 17:07:10 +04:00
assert div_1.position_y == 0
assert div_1.height == 175
assert div_3.height == 100
assert div_line.height == 75
assert div_img_1.height == 40
assert div_img_2.height == 60
2012-04-10 17:07:10 +04:00
assert div_img_1.position_y == 135
assert div_img_2.position_y == 100
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_text_align_left():
2011-11-05 02:55:26 +04:00
"""Test the left text alignment."""
"""
<--------------------> page, body
+-----+
+---+ |
| | |
+---+-----+
^ ^ ^ ^
x=0 x=40 x=100 x=200
"""
page, = parse('''
<style>
@page { -weasy-size: 200px }
</style>
<body>
<img src="pattern.png" style="width: 40px">
<img src="pattern.png" style="width: 60px">
''')
html, = page.children
body, = html.children
line, = body.children
img_1, img_2 = line.children
# initial value for text-align: left (in ltr text)
assert img_1.position_x == 0
assert img_2.position_x == 40
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_text_align_right():
2011-11-05 02:55:26 +04:00
"""Test the right text alignment."""
"""
<--------------------> page, body
+-----+
+---+ |
| | |
+---+-----+
^ ^ ^ ^
x=0 x=100 x=200
x=140
"""
page, = parse('''
<style>
@page { -weasy-size: 200px }
body { text-align: right }
</style>
<body>
<img src="pattern.png" style="width: 40px">
<img src="pattern.png" style="width: 60px">
''')
html, = page.children
body, = html.children
line, = body.children
img_1, img_2 = line.children
assert img_1.position_x == 100 # 200 - 60 - 40
assert img_2.position_x == 140 # 200 - 60
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_text_align_center():
2011-11-05 02:55:26 +04:00
"""Test the center text alignment."""
"""
<--------------------> page, body
+-----+
+---+ |
| | |
+---+-----+
^ ^ ^ ^
x= x=50 x=150
x=90
"""
page, = parse('''
<style>
@page { -weasy-size: 200px }
body { text-align: center }
</style>
<body>
<img src="pattern.png" style="width: 40px">
<img src="pattern.png" style="width: 60px">
''')
html, = page.children
body, = html.children
line, = body.children
img_1, img_2 = line.children
assert img_1.position_x == 50
assert img_2.position_x == 90
2012-02-20 16:04:35 +04:00
@assert_no_logs
2012-02-01 19:13:47 +04:00
def test_text_align_justify():
"""Test justified text."""
page, = parse('''
<style>
@page { -weasy-size: 300px 1000px }
body { text-align: justify }
</style>
<p><img src="pattern.png" style="width: 40px"> &#20;
<strong>
<img src="pattern.png" style="width: 60px"> &#20;
<img src="pattern.png" style="width: 10px"> &#20;
<img src="pattern.png" style="width: 100px">
</strong><img src="pattern.png" style="width: 290px">
<!-- Last image will be on its own line. -->
''')
html, = page.children
body, = html.children
paragraph, = body.children
line_1, line_2 = paragraph.children
image_1, space_1, strong = line_1.children
image_2, space_2, image_3, space_3, image_4 = strong.children
image_5, = line_2.children
assert space_1.text == ' '
assert space_2.text == ' '
assert space_3.text == ' '
assert image_1.position_x == 0
assert space_1.position_x == 40
assert strong.position_x == 70
assert image_2.position_x == 70
assert space_2.position_x == 130
assert image_3.position_x == 160
assert space_3.position_x == 170
assert image_4.position_x == 200
assert strong.width == 230
assert image_5.position_x == 0
2012-04-02 16:45:44 +04:00
# single-word line (zero spaces)
page, = parse('''
<style>
body { text-align: justify; width: 50px }
</style>
<p>Supercalifragilisticexpialidocious bar</p>
''')
html, = page.children
body, = html.children
paragraph, = body.children
line_1, line_2 = paragraph.children
text, = line_1.children
assert text.position_x == 0
2012-02-01 19:13:47 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_word_spacing():
"""Test word-spacing."""
# keep the empty <style> as a regression test: element.text is None
# (Not a string.)
page, = parse('''
<style></style>
<body><strong>Lorem ipsum dolor<em>sit amet</em></strong>
''')
html, = page.children
body, = html.children
line, = body.children
strong_1, = line.children
assert 200 <= strong_1.width <= 250
# TODO: Pango gives only half of word-spacing to a space at the end
# of a TextBox. Is this what we want?
page, = parse('''
<style>strong { word-spacing: 11px }</style>
<body><strong>Lorem ipsum dolor<em>sit amet</em></strong>
''')
html, = page.children
body, = html.children
line, = body.children
strong_2, = line.children
assert strong_2.width - strong_1.width == 33
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_letter_spacing():
"""Test letter-spacing."""
page, = parse('''
2012-04-02 16:45:44 +04:00
<body><strong>Supercalifragilisticexpialidocious</strong>
''')
html, = page.children
body, = html.children
line, = body.children
strong_1, = line.children
2012-04-02 16:45:44 +04:00
assert 250 <= strong_1.width <= 300
page, = parse('''
<style>strong { letter-spacing: 11px }</style>
2012-04-02 16:45:44 +04:00
<body><strong>Supercalifragilisticexpialidocious</strong>
''')
html, = page.children
body, = html.children
line, = body.children
strong_2, = line.children
2012-04-02 16:45:44 +04:00
assert strong_2.width - strong_1.width == 33 * 11
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_text_indent():
2011-11-05 02:55:26 +04:00
"""Test the text-indent property."""
for indent in ['12px', '6%']: # 6% of 200px is 12px
page, = parse('''
<style>
@page { -weasy-size: 220px }
body { margin: 10px; text-indent: %(indent)s }
</style>
<p>Some text that is long enough that it take at least three line,
but maybe more.
''' % {'indent': indent})
html, = page.children
body, = html.children
paragraph, = body.children
lines = paragraph.children
text_1, = lines[0].children
text_2, = lines[1].children
text_3, = lines[2].children
assert text_1.position_x == 22 # 10px margin-left + 12px indent
assert text_2.position_x == 10 # No indent
assert text_3.position_x == 10 # No indent
2011-11-05 02:55:26 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_inline_replaced_auto_margins():
"""Test that auto margins are ignored for inline replaced boxes."""
2011-11-05 02:55:26 +04:00
page, = parse('''
<style>
@page { -weasy-size: 200px }
2011-11-05 02:55:26 +04:00
img { display: inline; margin: auto; width: 50px }
</style>
<body>
<img src="pattern.png" />
''')
html, = page.children
2011-11-05 02:55:26 +04:00
body, = html.children
line, = body.children
img, = line.children
assert img.margin_top == 0
assert img.margin_right == 0
assert img.margin_bottom == 0
assert img.margin_left == 0
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_empty_inline_auto_margins():
"""Test that horizontal auto margins are ignored for empty inline boxes."""
page, = parse('''
<style>
@page { -weasy-size: 200px }
span { margin: auto }
</style>
<body><span></span>
''')
html, = page.children
body, = html.children
block, = body.children
span, = block.children
assert span.margin_top != 0
assert span.margin_right == 0
assert span.margin_bottom != 0
assert span.margin_left == 0
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_box_sizing():
"""Test the box-sizing property.
http://www.w3.org/TR/css3-ui/#box-sizing
"""
page, = parse('''
<style>
@page { -weasy-size: 100000px }
body { width: 10000px; margin: 0 }
div { width: 10%; height: 1000px;
margin: 100px; padding: 10px; border: 1px solid }
2012-04-06 17:56:36 +04:00
div:nth-child(2) { box-sizing: content-box }
div:nth-child(3) { box-sizing: padding-box }
div:nth-child(4) { box-sizing: border-box }
</style>
2012-04-06 17:56:36 +04:00
<div></div>
<div></div>
<div></div>
<div></div>
''')
html, = page.children
body, = html.children
2012-04-06 17:56:36 +04:00
div_1, div_2, div_3, div_4 = body.children
for div in div_1, div_2:
assert div.style.box_sizing == 'content-box'
assert div.width == 1000
assert div.height == 1000
assert div.padding_width() == 1020
assert div.padding_height() == 1020
assert div.border_width() == 1022
assert div.border_height() == 1022
assert div.margin_height() == 1222
# margin_width() is the width of the containing block
# padding-box
assert div_3.style.box_sizing == 'padding-box'
assert div_3.width == 980 # 1000 - 20
assert div_3.height == 980
assert div_3.padding_width() == 1000
assert div_3.padding_height() == 1000
assert div_3.border_width() == 1002
assert div_3.border_height() == 1002
assert div_3.margin_height() == 1202
# border-box
assert div_4.style.box_sizing == 'border-box'
assert div_4.width == 978 # 1000 - 20 - 2
assert div_4.height == 978
assert div_4.padding_width() == 998
assert div_4.padding_height() == 998
assert div_4.border_width() == 1000
assert div_4.border_height() == 1000
assert div_4.margin_height() == 1200
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_table_column_width():
2011-12-16 15:19:10 +04:00
source = '''
<style>
body { width: 20000px; margin: 0 }
2012-03-23 22:31:54 +04:00
table {
width: 10000px; margin: 0 auto; border-spacing: 100px 0;
table-layout: fixed
}
td { border: 10px solid; padding: 1px }
</style>
<table>
<col style="width: 10%">
<tr>
<td style="width: 30%" colspan=3>
<td>
</tr>
<tr>
<td>
<td>
<td>
<td>
</tr>
<tr>
<td>
<td colspan=12>This cell will be truncated to grid width
<td>This cell will be removed as it is beyond the grid width
</tr>
</table>
2011-12-16 15:19:10 +04:00
'''
with capture_logs() as logs:
page, = parse(source)
assert len(logs) == 1
assert logs[0] == ('WARNING: This table row has more columns than '
2012-03-23 22:31:54 +04:00
'the table, ignored 1 cells: (<TableCellBox td 25>,)')
html, = page.children
body, = html.children
wrapper, = body.children
table, = wrapper.children
row_group, = table.children
first_row, second_row, third_row = row_group.children
cells = [first_row.children, second_row.children, third_row.children]
assert len(first_row.children) == 2
assert len(second_row.children) == 4
# Third cell here is completly removed
assert len(third_row.children) == 2
assert body.position_x == 0
assert wrapper.position_x == 0
assert wrapper.margin_left == 5000
assert wrapper.content_box_x() == 5000 # auto margin-left
assert wrapper.width == 10000
assert table.position_x == 5000
assert table.width == 10000
assert row_group.position_x == 5100 # 5000 + border_spacing
assert row_group.width == 9800 # 10000 - 2*border-spacing
assert first_row.position_x == row_group.position_x
assert first_row.width == row_group.width
# This cell has colspan=3
assert cells[0][0].position_x == 5100 # 5000 + border-spacing
# `width` on a cell sets the content width
assert cells[0][0].width == 3000 # 30% of 10000px
assert cells[0][0].border_width() == 3022 # 3000 + borders + padding
# Second cell of the first line, but on the fourth and last column
assert cells[0][1].position_x == 8222 # 5100 + 3022 + border-spacing
assert cells[0][1].border_width() == 6678 # 10000 - 3022 - 3*100
assert cells[0][1].width == 6656 # 6678 - borders - padding
assert cells[1][0].position_x == 5100 # 5000 + border-spacing
# `width` on a column sets the border width of cells
assert cells[1][0].border_width() == 1000 # 10% of 10000px
assert cells[1][0].width == 978 # 1000 - borders - padding
assert cells[1][1].position_x == 6200 # 5100 + 1000 + border-spacing
assert cells[1][1].border_width() == 911 # (3022 - 1000 - 2*100) / 2
assert cells[1][1].width == 889 # 911 - borders - padding
assert cells[1][2].position_x == 7211 # 6200 + 911 + border-spacing
assert cells[1][2].border_width() == 911 # (3022 - 1000 - 2*100) / 2
assert cells[1][2].width == 889 # 911 - borders - padding
# Same as cells[0][1]
assert cells[1][3].position_x == 8222 # Also 7211 + 911 + border-spacing
assert cells[1][3].border_width() == 6678
assert cells[1][3].width == 6656
# Same as cells[1][0]
assert cells[2][0].position_x == 5100
assert cells[2][0].border_width() == 1000
assert cells[2][0].width == 978
assert cells[2][1].position_x == 6200 # Same as cells[1][1]
assert cells[2][1].border_width() == 8700 # 1000 - 1000 - 3*border-spacing
assert cells[2][1].width == 8678 # 8700 - borders - padding
assert cells[2][1].colspan == 3 # truncated to grid width
page, = parse('''
<style>
2012-03-23 22:31:54 +04:00
table { width: 1000px; border-spacing: 100px; table-layout: fixed }
</style>
<table>
<tr>
<td style="width: 50%">
<td style="width: 60%">
<td>
</tr>
</table>
''')
html, = page.children
body, = html.children
wrapper, = body.children
table, = wrapper.children
row_group, = table.children
row, = row_group.children
assert row.children[0].width == 500
assert row.children[1].width == 600
assert row.children[2].width == 0
assert table.width == 1500 # 500 + 600 + 4 * border-spacing
2011-11-24 20:56:05 +04:00
# Sum of columns width larger that the table width:
# increase the table width
for body_width, table_width in [('auto', '1000px'), ('1000px', 'auto')]:
page, = parse('''
<style>
body { width: %(body_width)s }
2012-03-23 22:31:54 +04:00
table {
width: %(table_width)s; border-spacing: 100px;
table-layout: fixed
}
td { width: %(td_width)s }
</style>
<table>
<tr>
<td>
<td>
</tr>
</table>
''' % {'body_width': body_width, 'table_width': table_width,
'td_width': '60%'})
html, = page.children
body, = html.children
wrapper, = body.children
table, = wrapper.children
row_group, = table.children
row, = row_group.children
cell_1, cell_2 = row.children
assert cell_1.width == 600 # 60% of 1000px
assert cell_2.width == 600
assert table.width == 1500 # 600 + 600 + 3*border-spacing
assert wrapper.width == table.width
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-11-24 20:56:05 +04:00
def test_table_row_height():
page, = parse('''
2011-12-16 16:44:25 +04:00
<table style="width: 1000px; border-spacing: 0 100px;
2012-03-23 22:31:54 +04:00
font: 20px/1em serif; margin: 3px; table-layout: fixed">
2011-11-24 20:56:05 +04:00
<tr>
2011-11-25 21:13:21 +04:00
<td rowspan=0 style="height: 420px; vertical-align: top">
2011-11-24 20:56:05 +04:00
<td>X<br>X<br>X
2012-04-10 17:46:36 +04:00
<td><table style="margin-top: 20px;
border-spacing: 0">X</table>
2011-11-24 20:56:05 +04:00
<td style="vertical-align: top">X
<td style="vertical-align: middle">X
<td style="vertical-align: bottom">X
</tr>
<tr>
<!-- cells with no text (no line boxes) is a corner case
in cell baselines -->
<td style="padding: 15px"></td>
<td><div style="height: 10px"></div></td>
2011-11-24 20:56:05 +04:00
</tr>
2011-11-25 21:13:21 +04:00
<tr></tr>
2011-11-24 20:56:05 +04:00
<tr>
2011-11-25 21:13:21 +04:00
<td style="vertical-align: bottom">
2011-11-24 20:56:05 +04:00
</tr>
</table>
''')
html, = page.children
2011-11-24 20:56:05 +04:00
body, = html.children
wrapper, = body.children
table, = wrapper.children
row_group, = table.children
assert wrapper.position_y == 0
assert table.position_y == 3 # 0 + margin-top
assert table.height == 620 # sum of row heigths + 5*border-spacing
assert wrapper.height == table.height
assert row_group.position_y == 103 # 3 + border-spacing
assert row_group.height == 420 # 620 - 2*border-spacing
assert [row.height for row in row_group.children] == [
80, 30, 0, 10]
assert [row.position_y for row in row_group.children] == [
# cumulative sum of previous row heights and border-spacings
103, 283, 413, 513]
2011-11-24 20:56:05 +04:00
assert [[cell.height for cell in row.children]
for row in row_group.children] == [
2011-11-25 21:13:21 +04:00
[420, 60, 40, 20, 20, 20],
[0, 10],
2011-11-25 21:13:21 +04:00
[],
2011-11-24 20:56:05 +04:00
[0]
]
assert [[cell.border_height() for cell in row.children]
for row in row_group.children] == [
2011-11-25 21:13:21 +04:00
[420, 80, 80, 80, 80, 80],
[30, 30],
2011-11-25 21:13:21 +04:00
[],
2011-11-24 20:56:05 +04:00
[10]
]
# The baseline of the first row is at 40px because of the third column.
# The second column thus gets a top padding of 20px pushes the bottom
# to 80px.The middle is at 40px.
assert [[cell.padding_top for cell in row.children]
for row in row_group.children] == [
[0, 20, 0, 0, 30, 60],
[15, 5],
2011-11-25 21:13:21 +04:00
[],
[10]
2011-11-24 20:56:05 +04:00
]
assert [[cell.padding_bottom for cell in row.children]
for row in row_group.children] == [
[0, 0, 40, 60, 30, 0],
[15, 15],
2011-11-25 21:13:21 +04:00
[],
[0]
2011-11-24 20:56:05 +04:00
]
assert [[cell.position_y for cell in row.children]
for row in row_group.children] == [
[103, 103, 103, 103, 103, 103],
[283, 283],
2011-11-25 21:13:21 +04:00
[],
[513]
2011-11-24 20:56:05 +04:00
]
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_table_wrapper():
page, = parse('''
<style>
@page { -weasy-size: 1000px }
2012-03-23 22:31:54 +04:00
table { /* width: auto; */ height: 500px; table-layout: fixed;
padding: 1px; border: 10px solid; margin: 100px; }
</style>
<table></table>
''')
html, = page.children
body, = html.children
wrapper, = body.children
table, = wrapper.children
assert body.width == 1000
assert wrapper.margin_width() == 1000
assert wrapper.width == 800 # 1000 - 2*100, no borders or padding
assert table.margin_width() == 800
assert table.width == 778 # 800 - 2*10 - 2*1, no margin
# box-sizing in the UA stylesheet makes `height: 500px` set this
assert table.border_height() == 500
assert table.height == 478 # 500 - 2*10 - 2*1
assert table.margin_height() == 500 # no margin
assert wrapper.height == 500
assert wrapper.margin_height() == 700 # 500 + 2*100
# Non-regression test: this used to cause an exception
page, = parse('<html style="display: table">')
2011-12-28 21:20:55 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_margin_boxes_fixed_dimension():
2011-12-28 21:20:55 +04:00
# Corner boxes
page, = parse('''
<style>
@page {
@top-left-corner {
content: 'top_left';
padding: 10px;
}
@top-right-corner {
content: 'top_right';
padding: 10px;
}
@bottom-left-corner {
content: 'bottom_left';
padding: 10px;
}
@bottom-right-corner {
content: 'bottom_right';
padding: 10px;
}
-weasy-size: 1000px;
margin-top: 10%;
margin-bottom: 40%;
margin-left: 20%;
margin-right: 30%;
2011-12-28 21:20:55 +04:00
}
</style>
''')
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
2011-12-28 21:20:55 +04:00
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
# Test rules 2 and 3
page, = parse('''
<style>
@page {
margin: 100px 200px;
@bottom-left-corner {
content: "";
margin: 60px
}
}
</style>
''')
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
# Test rule 3 with a non-auto inner dimension
page, = parse('''
<style>
@page {
margin: 100px;
@left-middle {
content: "";
margin: 10px;
width: 130px;
}
}
</style>
''')
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
# Test rule 4
page, = parse('''
<style>
@page {
margin: 100px;
@left-bottom {
content: "";
margin-left: 10px;
margin-right: auto;
width: 70px;
}
}
</style>
''')
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
# Test rules 2, 3 and 4
page, = parse('''
<style>
@page {
margin: 100px;
@right-top {
content: "";
margin-right: 10px;
margin-left: auto;
width: 130px;
}
}
</style>
''')
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
# Test rule 5
page, = parse('''
<style>
@page {
margin: 100px;
@top-left {
content: "";
margin-top: 10px;
margin-bottom: auto;
}
}
</style>
''')
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
# Test rule 5
page, = parse('''
<style>
@page {
margin: 100px;
@top-center {
content: "";
margin: auto 0;
}
}
</style>
''')
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
# Test rule 6
page, = parse('''
<style>
@page {
margin: 100px;
@bottom-right {
content: "";
margin: auto;
height: 70px;
}
}
</style>
''')
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
# Rule 2 inhibits rule 6
page, = parse('''
<style>
@page {
margin: 100px;
@bottom-center {
content: "";
margin: auto 0;
height: 150px;
}
}
</style>
''')
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
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_preferred_widths():
"""Unit tests for preferred widths."""
document = parse('''
<p style="white-space: pre-line">
Lorem ipsum dolor sit amet,
consectetur elit
</p>
<!-- ^ No-break space here -->
''', return_document=True)
# Non-laid-out boxes:
body, = document.formatting_structure.children
paragraph, = body.children
line, = paragraph.children
text, = line.children
assert text.text == '\nLorem ipsum dolor sit amet,\nconsectetur elit\n'
minimum = inline_preferred_minimum_width(line)
preferred = inline_preferred_width(line)
# Not exact, depends on the installed fonts
assert 120 < minimum < 140
assert 220 < preferred < 240
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_margin_boxes_variable_dimension():
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
padding: 42px;
border: 7px solid;
@top-left {
content: "";
}
@top-center {
content: "";
}
@top-right {
content: "";
}
}
</style>
''')
html, top_left, top_right, top_center = page.children
assert top_left.at_keyword == '@top-left'
assert top_center.at_keyword == '@top-center'
assert top_right.at_keyword == '@top-right'
assert top_left.margin_width() == 200
assert top_center.margin_width() == 200
assert top_right.margin_width() == 200
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
padding: 42px;
border: 7px solid;
@top-left {
content: "HeyHey";
}
@top-center {
content: "Hey";
}
@top-right {
content: "";
}
}
</style>
''')
html, top_left, top_right, top_center = page.children
assert top_left.at_keyword == '@top-left'
assert top_center.at_keyword == '@top-center'
assert top_right.at_keyword == '@top-right'
assert top_left.margin_width() == 240
assert top_center.margin_width() == 120
assert top_right.margin_width() == 240
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
padding: 42px;
border: 7px solid;
@top-left {
content: "Lorem";
}
@top-right {
content: "Lorem";
}
}
</style>
''')
html, top_left, top_right = page.children
assert top_left.at_keyword == '@top-left'
assert top_right.at_keyword == '@top-right'
assert top_left.margin_width() == 300
assert top_right.margin_width() == 300
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
padding: 42px;
border: 7px solid;
@top-left {
content: "HelloHello";
}
@top-right {
content: "Hello";
}
}
</style>
''')
html, top_left, top_right = page.children
assert top_left.margin_width() == 400
assert top_right.margin_width() == 200
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
padding: 42px;
border: 7px solid;
@top-left {
content: url('data:image/svg+xml, \
<svg width="10" height="10"></svg>');
}
@top-right {
content: url('data:image/svg+xml, \
<svg width="30" height="10"></svg>');
}
}
</style>
''')
html, top_left, top_right = page.children
assert top_left.margin_width() == 150
assert top_right.margin_width() == 450
2012-01-12 22:10:53 +04:00
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
@top-left {
width: 150px;
}
@top-right {
width: 250px;
}
}
</style>
''')
html, top_left, top_right = page.children
assert top_left.margin_width() == 150
assert top_right.margin_width() == 250
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
@top-left {
margin: auto;
width: 100px;
}
@top-center {
margin-left: auto;
}
@top-right {
width: 200px;
}
}
</style>
''')
html, top_left, top_right = page.children
# 300 pixels evenly distributed over the 3 margins
assert top_left.margin_left == 100
assert top_left.margin_right == 100
assert top_left.width == 100
assert top_right.margin_left == 0
assert top_right.margin_right == 0
assert top_right.width == 200
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
@top-left {
margin: auto;
width: 500px;
}
@top-center {
margin-left: auto;
}
@top-right {
width: 400px;
}
}
</style>
''')
html, top_left, top_right = page.children
# -300 pixels evenly distributed over the 3 margins
assert top_left.margin_left == -100
assert top_left.margin_right == -100
assert top_left.width == 500
assert top_right.margin_left == 0
assert top_right.margin_right == 0
assert top_right.width == 400
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
padding: 42px;
border: 7px solid;
@top-left {
content: url('data:image/svg+xml, \
<svg width="450" height="10"></svg>');
}
@top-right {
content: url('data:image/svg+xml, \
<svg width="350" height="10"></svg>');
}
}
</style>
''')
html, top_left, top_right = page.children
# -200 pixels evenly distributed over the 2 added margins
assert top_left.margin_left == 0
assert top_left.margin_right == -100
assert top_left.width == 450
assert top_right.margin_left == -100
assert top_right.margin_right == 0
assert top_right.width == 350
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 100px;
padding: 42px;
border: 7px solid;
@top-left {
content: url('data:image/svg+xml, \
<svg width="100" height="10"></svg>');
border-right: 50px solid;
margin: auto;
}
@top-right {
content: url('data:image/svg+xml, \
<svg width="200" height="10"></svg>');
margin-left: 30px;
}
}
</style>
''')
html, top_left, top_right = page.children
assert top_left.margin_left == 110
assert top_left.margin_right == 110
assert top_left.width == 100
assert top_left.margin_width() == 370
assert top_right.margin_left == 30
assert top_right.margin_right == 0
assert top_right.width == 200
assert top_right.margin_width() == 230
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_margin_boxes_vertical_align():
"""
3 px -> +-----+
| 1 |
+-----+
43 px -> +-----+
53 px -> | 2 |
+-----+
83 px -> +-----+
| 3 |
103px -> +-----+
"""
page, = parse('''
<style>
@page {
-weasy-size: 800px;
margin: 106px; /* margin boxes content height is 100px */
@top-left {
content: "foo"; line-height: 20px; border: 3px solid;
vertical-align: top;
}
@top-center {
content: "foo"; line-height: 20px; border: 3px solid;
vertical-align: middle;
}
@top-right {
content: "foo"; line-height: 20px; border: 3px solid;
vertical-align: bottom;
}
}
</style>
''')
html, top_left, top_right, top_center = 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_collapsing():
"""
The vertical space between to sibling blocks is the max of their margins,
not the sum. But thats only the simplest case...
"""
2012-02-28 19:33:54 +04:00
def assert_collapsing(vertical_space):
assert vertical_space('10px', '15px') == 15 # not 25
# "The maximum of the absolute values of the negative adjoining margins
# is deducted from the maximum of the positive adjoining margins"
assert vertical_space('-10px', '15px') == 5
assert vertical_space('10px', '-15px') == -5
assert vertical_space('-10px', '-15px') == -15
assert vertical_space('10px', 'auto') == 10 # 'auto' is 0
return vertical_space
def assert_NOT_collapsing(vertical_space):
assert vertical_space('10px', '15px') == 25
assert vertical_space('-10px', '15px') == 5
assert vertical_space('10px', '-15px') == -5
assert vertical_space('-10px', '-15px') == -25
assert vertical_space('10px', 'auto') == 10 # 'auto' is 0
return vertical_space
# Siblings
@assert_collapsing
2012-02-23 15:51:19 +04:00
def vertical_space_1(p1_margin_bottom, p2_margin_top):
page, = parse('''
<style>
p { font: 20px/1 serif } /* block height == 20px */
#p1 { margin-bottom: %s }
#p2 { margin-top: %s }
</style>
<p id=p1>Lorem ipsum
<p id=p2>dolor sit amet
''' % (p1_margin_bottom, p2_margin_top))
html, = page.children
body, = html.children
p1, p2 = body.children
p1_bottom = p1.content_box_y() + p1.height
p2_top = p2.content_box_y()
return p2_top - p1_bottom
2012-02-28 19:33:54 +04:00
# Not siblings, first is nested
@assert_collapsing
2012-02-23 15:51:19 +04:00
def vertical_space_2(p1_margin_bottom, p2_margin_top):
page, = parse('''
<style>
p { font: 20px/1 serif } /* block height == 20px */
#p1 { margin-bottom: %s }
#p2 { margin-top: %s }
</style>
<div>
<p id=p1>Lorem ipsum
</div>
<p id=p2>dolor sit amet
''' % (p1_margin_bottom, p2_margin_top))
html, = page.children
body, = html.children
div, p2 = body.children
p1, = div.children
p1_bottom = p1.content_box_y() + p1.height
p2_top = p2.content_box_y()
return p2_top - p1_bottom
2012-02-28 19:33:54 +04:00
# Not siblings, second is nested
@assert_collapsing
def vertical_space_3(p1_margin_bottom, p2_margin_top):
page, = parse('''
<style>
p { font: 20px/1 serif } /* block height == 20px */
#p1 { margin-bottom: %s }
#p2 { margin-top: %s }
</style>
<p id=p1>Lorem ipsum
<div>
<p id=p2>dolor sit amet
</div>
''' % (p1_margin_bottom, p2_margin_top))
html, = page.children
body, = html.children
p1, div = body.children
p2, = div.children
p1_bottom = p1.content_box_y() + p1.height
p2_top = p2.content_box_y()
return p2_top - p1_bottom
# Not siblings, second is doubly nested
@assert_collapsing
def vertical_space_4(p1_margin_bottom, p2_margin_top):
page, = parse('''
<style>
p { font: 20px/1 serif } /* block height == 20px */
#p1 { margin-bottom: %s }
#p2 { margin-top: %s }
</style>
<p id=p1>Lorem ipsum
<div>
<div>
<p id=p2>dolor sit amet
</div>
</div>
''' % (p1_margin_bottom, p2_margin_top))
html, = page.children
body, = html.children
p1, div1 = body.children
div2, = div1.children
p2, = div2.children
p1_bottom = p1.content_box_y() + p1.height
p2_top = p2.content_box_y()
return p2_top - p1_bottom
# Collapsing with children
@assert_collapsing
def vertical_space_5(margin_1, margin_2):
page, = parse('''
<style>
p { font: 20px/1 serif } /* block height == 20px */
#div1 { margin-top: %s }
#div2 { margin-top: %s }
</style>
<p>Lorem ipsum
<div id=div1>
<div id=div2>
<p id=p2>dolor sit amet
</div>
</div>
''' % (margin_1, margin_2))
html, = page.children
body, = html.children
p1, div1 = body.children
div2, = div1.children
p2, = div2.children
p1_bottom = p1.content_box_y() + p1.height
p2_top = p2.content_box_y()
# Parent and element edge are the same:
assert div1.border_box_y() == p2.border_box_y()
assert div2.border_box_y() == p2.border_box_y()
return p2_top - p1_bottom
# Block formatting context: Not collapsing with children
@assert_NOT_collapsing
def vertical_space_6(margin_1, margin_2):
page, = parse('''
<style>
p { font: 20px/1 serif } /* block height == 20px */
#div1 { margin-top: %s; overflow: hidden }
#div2 { margin-top: %s }
</style>
<p>Lorem ipsum
<div id=div1>
<div id=div2>
<p id=p2>dolor sit amet
</div>
</div>
''' % (margin_1, margin_2))
html, = page.children
body, = html.children
p1, div1 = body.children
div2, = div1.children
p2, = div2.children
p1_bottom = p1.content_box_y() + p1.height
p2_top = p2.content_box_y()
return p2_top - p1_bottom
# Collapsing through an empty div
@assert_collapsing
def vertical_space_7(p1_margin_bottom, p2_margin_top):
page, = parse('''
<style>
p { font: 20px/1 serif } /* block height == 20px */
#p1 { margin-bottom: %s }
#p2 { margin-top: %s }
div { margin-bottom: %s; margin-top: %s }
</style>
<p id=p1>Lorem ipsum
<div></div>
<p id=p2>dolor sit amet
''' % (2 * (p1_margin_bottom, p2_margin_top)))
html, = page.children
body, = html.children
p1, div, p2 = body.children
p1_bottom = p1.content_box_y() + p1.height
p2_top = p2.content_box_y()
return p2_top - p1_bottom
# The root elememt does not collapse
@assert_NOT_collapsing
def vertical_space_8(margin_1, margin_2):
page, = parse('''
<html>
<style>
html { margin-top: %s }
body { margin-top: %s }
</style>
<body>
<p>Lorem ipsum
''' % (margin_1, margin_2))
html, = page.children
body, = html.children
p1, = body.children
p1_top = p1.content_box_y()
# Vertical space from y=0
return p1_top
# <body> DOES collapse
@assert_collapsing
def vertical_space_9(margin_1, margin_2):
page, = parse('''
<html>
<style>
body { margin-top: %s }
div { margin-top: %s }
</style>
<body>
<div>
<p>Lorem ipsum
''' % (margin_1, margin_2))
html, = page.children
body, = html.children
div, = body.children
p1, = div.children
p1_top = p1.content_box_y()
# Vertical space from y=0
return p1_top