mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-04 07:57:52 +03:00
81f73ead2e
Pango has a PANGO_SCALE value of 1024, making it really happy when fonts have a cadratin value of 1024. Unfortunately, AHEM uses 1000, leading to rounding errors. We now use weasyprint.otf everywhere, with 1024. It breaks the exact 20/80 ratio between descent and ascent values, so that we need to use approximate values for tests relying on an exact position of the baseline. All the tests should pass, or be explicitely marked as failing.
491 lines
17 KiB
Python
491 lines
17 KiB
Python
"""
|
|
weasyprint.tests.test_css
|
|
-------------------------
|
|
|
|
Test the CSS parsing, cascade, inherited and computed values.
|
|
"""
|
|
|
|
from math import isclose
|
|
|
|
import pytest
|
|
import tinycss2
|
|
from weasyprint import CSS, css, default_url_fetcher
|
|
from weasyprint.css import (
|
|
PageType, get_all_computed_styles, parse_page_selectors)
|
|
from weasyprint.css.computed_values import strut_layout
|
|
from weasyprint.layout.pages import set_page_type_computed_styles
|
|
from weasyprint.urls import path2url
|
|
|
|
from .testing_utils import (
|
|
BASE_URL, FakeHTML, assert_no_logs, capture_logs, resource_filename)
|
|
|
|
|
|
@assert_no_logs
|
|
def test_style_dict():
|
|
style = {'margin_left': 12, 'display': 'block'}
|
|
assert style['display'] == 'block'
|
|
assert style['margin_left'] == 12
|
|
with pytest.raises(KeyError):
|
|
style['position']
|
|
|
|
|
|
@assert_no_logs
|
|
def test_find_stylesheets():
|
|
html = FakeHTML(resource_filename('doc1.html'))
|
|
|
|
sheets = list(css.find_stylesheets(
|
|
html.wrapper_element, 'print', default_url_fetcher, html.base_url,
|
|
font_config=None, counter_style=None, page_rules=None))
|
|
assert len(sheets) == 2
|
|
# Also test that stylesheets are in tree order
|
|
assert [s.base_url.rsplit('/', 1)[-1].rsplit(',', 1)[-1] for s in sheets] \
|
|
== ['a%7Bcolor%3AcurrentColor%7D', 'doc1.html']
|
|
|
|
rules = []
|
|
for sheet in sheets:
|
|
for sheet_rules in sheet.matcher.lower_local_name_selectors.values():
|
|
for rule in sheet_rules:
|
|
rules.append(rule)
|
|
for rule in sheet.page_rules:
|
|
rules.append(rule)
|
|
assert len(rules) == 10
|
|
|
|
# TODO: test that the values are correct too
|
|
|
|
|
|
@assert_no_logs
|
|
def test_expand_shorthands():
|
|
sheet = CSS(resource_filename('sheet2.css'))
|
|
assert list(sheet.matcher.lower_local_name_selectors) == ['li']
|
|
|
|
rules = sheet.matcher.lower_local_name_selectors['li'][0][4]
|
|
assert rules[0][0] == 'margin_bottom'
|
|
assert rules[0][1] == (3, 'em')
|
|
assert rules[1][0] == 'margin_top'
|
|
assert rules[1][1] == (2, 'em')
|
|
assert rules[2][0] == 'margin_right'
|
|
assert rules[2][1] == (0, None)
|
|
assert rules[3][0] == 'margin_bottom'
|
|
assert rules[3][1] == (2, 'em')
|
|
assert rules[4][0] == 'margin_left'
|
|
assert rules[4][1] == (0, None)
|
|
assert rules[5][0] == 'margin_left'
|
|
assert rules[5][1] == (4, 'em')
|
|
|
|
# TODO: test that the values are correct too
|
|
|
|
|
|
@assert_no_logs
|
|
def test_annotate_document():
|
|
document = FakeHTML(resource_filename('doc1.html'))
|
|
document._ua_stylesheets = lambda: [CSS(resource_filename('mini_ua.css'))]
|
|
style_for = get_all_computed_styles(
|
|
document, user_stylesheets=[CSS(resource_filename('user.css'))])
|
|
|
|
# Element objects behave as lists of their children
|
|
_head, body = document.etree_element
|
|
h1, p, ul, div = body
|
|
li_0, _li_1 = ul
|
|
a, = li_0
|
|
span1, = div
|
|
span2, = span1
|
|
|
|
h1 = style_for(h1)
|
|
p = style_for(p)
|
|
ul = style_for(ul)
|
|
li_0 = style_for(li_0)
|
|
div = style_for(div)
|
|
after = style_for(a, 'after')
|
|
a = style_for(a)
|
|
span1 = style_for(span1)
|
|
span2 = style_for(span2)
|
|
|
|
assert h1['background_image'] == (
|
|
('url', path2url(resource_filename('logo_small.png'))),)
|
|
|
|
assert h1['font_weight'] == 700
|
|
assert h1['font_size'] == 40 # 2em
|
|
|
|
# x-large * initial = 3/2 * 16 = 24
|
|
assert p['margin_top'] == (24, 'px')
|
|
assert p['margin_right'] == (0, 'px')
|
|
assert p['margin_bottom'] == (24, 'px')
|
|
assert p['margin_left'] == (0, 'px')
|
|
assert p['background_color'] == 'currentColor'
|
|
|
|
# 2em * 1.25ex = 2 * 20 * 1.25 * 0.8 = 40
|
|
# 2.5ex * 1.25ex = 2.5 * 0.8 * 20 * 1.25 * 0.8 = 40
|
|
# TODO: ex unit doesn't work with @font-face fonts, see computed_values.py
|
|
# assert ul['margin_top'] == (40, 'px')
|
|
# assert ul['margin_right'] == (40, 'px')
|
|
# assert ul['margin_bottom'] == (40, 'px')
|
|
# assert ul['margin_left'] == (40, 'px')
|
|
|
|
assert ul['font_weight'] == 400
|
|
# thick = 5px, 0.25 inches = 96*.25 = 24px
|
|
assert ul['border_top_width'] == 0
|
|
assert ul['border_right_width'] == 5
|
|
assert ul['border_bottom_width'] == 0
|
|
assert ul['border_left_width'] == 24
|
|
|
|
assert li_0['font_weight'] == 700
|
|
assert li_0['font_size'] == 8 # 6pt
|
|
assert li_0['margin_top'] == (16, 'px') # 2em
|
|
assert li_0['margin_right'] == (0, 'px')
|
|
assert li_0['margin_bottom'] == (16, 'px')
|
|
assert li_0['margin_left'] == (32, 'px') # 4em
|
|
|
|
assert a['text_decoration_line'] == {'underline'}
|
|
assert a['font_weight'] == 900
|
|
assert a['font_size'] == 24 # 300% of 8px
|
|
assert a['padding_top'] == (1, 'px')
|
|
assert a['padding_right'] == (2, 'px')
|
|
assert a['padding_bottom'] == (3, 'px')
|
|
assert a['padding_left'] == (4, 'px')
|
|
assert a['border_top_width'] == 42
|
|
assert a['border_bottom_width'] == 42
|
|
|
|
assert a['color'] == (1, 0, 0, 1)
|
|
assert a['border_top_color'] == 'currentColor'
|
|
|
|
assert div['font_size'] == 40 # 2 * 20px
|
|
assert span1['width'] == (160, 'px') # 10 * 16px (root default is 16px)
|
|
assert span1['height'] == (400, 'px') # 10 * (2 * 20px)
|
|
assert span2['font_size'] == 32
|
|
|
|
# The href attr should be as in the source, not made absolute.
|
|
assert after['content'] == (
|
|
('string', ' ['), ('string', 'home.html'), ('string', ']'))
|
|
assert after['background_color'] == (1, 0, 0, 1)
|
|
assert after['border_top_width'] == 42
|
|
assert after['border_bottom_width'] == 3
|
|
|
|
# TODO: much more tests here: test that origin and selector precedence
|
|
# and inheritance are correct…
|
|
|
|
|
|
@assert_no_logs
|
|
def test_page():
|
|
document = FakeHTML(resource_filename('doc1.html'))
|
|
style_for = get_all_computed_styles(
|
|
document, user_stylesheets=[CSS(string='''
|
|
html { color: red }
|
|
@page { margin: 10px }
|
|
@page :right {
|
|
color: blue;
|
|
margin-bottom: 12pt;
|
|
font-size: 20px;
|
|
@top-left { width: 10em }
|
|
@top-right { font-size: 10px}
|
|
}
|
|
''')])
|
|
|
|
page_type = PageType(
|
|
side='left', first=True, blank=False, index=0, name='')
|
|
set_page_type_computed_styles(page_type, document, style_for)
|
|
style = style_for(page_type)
|
|
assert style['margin_top'] == (5, 'px')
|
|
assert style['margin_left'] == (10, 'px')
|
|
assert style['margin_bottom'] == (10, 'px')
|
|
assert style['color'] == (1, 0, 0, 1) # red, inherited from html
|
|
|
|
page_type = PageType(
|
|
side='right', first=True, blank=False, index=0, name='')
|
|
set_page_type_computed_styles(page_type, document, style_for)
|
|
style = style_for(page_type)
|
|
assert style['margin_top'] == (5, 'px')
|
|
assert style['margin_left'] == (10, 'px')
|
|
assert style['margin_bottom'] == (16, 'px')
|
|
assert style['color'] == (0, 0, 1, 1) # blue
|
|
|
|
page_type = PageType(
|
|
side='left', first=False, blank=False, index=1, name='')
|
|
set_page_type_computed_styles(page_type, document, style_for)
|
|
style = style_for(page_type)
|
|
assert style['margin_top'] == (10, 'px')
|
|
assert style['margin_left'] == (10, 'px')
|
|
assert style['margin_bottom'] == (10, 'px')
|
|
assert style['color'] == (1, 0, 0, 1) # red, inherited from html
|
|
|
|
page_type = PageType(
|
|
side='right', first=False, blank=False, index=1, name='')
|
|
set_page_type_computed_styles(page_type, document, style_for)
|
|
style = style_for(page_type)
|
|
assert style['margin_top'] == (10, 'px')
|
|
assert style['margin_left'] == (10, 'px')
|
|
assert style['margin_bottom'] == (16, 'px')
|
|
assert style['color'] == (0, 0, 1, 1) # blue
|
|
|
|
page_type = PageType(
|
|
side='left', first=True, blank=False, index=0, name='')
|
|
set_page_type_computed_styles(page_type, document, style_for)
|
|
style = style_for(page_type, '@top-left')
|
|
assert style is None
|
|
|
|
page_type = PageType(
|
|
side='right', first=True, blank=False, index=0, name='')
|
|
set_page_type_computed_styles(page_type, document, style_for)
|
|
style = style_for(page_type, '@top-left')
|
|
assert style['font_size'] == 20 # inherited from @page
|
|
assert style['width'] == (200, 'px')
|
|
|
|
page_type = PageType(
|
|
side='right', first=True, blank=False, index=0, name='')
|
|
set_page_type_computed_styles(page_type, document, style_for)
|
|
style = style_for(page_type, '@top-right')
|
|
assert style['font_size'] == 10
|
|
|
|
|
|
@assert_no_logs
|
|
@pytest.mark.parametrize('style, selectors', (
|
|
('@page {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': None,
|
|
'index': None, 'specificity': [0, 0, 0]}]),
|
|
('@page :left {}', [{
|
|
'side': 'left', 'blank': None, 'first': None, 'name': None,
|
|
'index': None, 'specificity': [0, 0, 1]}]),
|
|
('@page:first:left {}', [{
|
|
'side': 'left', 'blank': None, 'first': True, 'name': None,
|
|
'index': None, 'specificity': [0, 1, 1]}]),
|
|
('@page pagename {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': 'pagename',
|
|
'index': None, 'specificity': [1, 0, 0]}]),
|
|
('@page pagename:first:right:blank {}', [{
|
|
'side': 'right', 'blank': True, 'first': True, 'name': 'pagename',
|
|
'index': None, 'specificity': [1, 2, 1]}]),
|
|
('@page pagename, :first {}', [
|
|
{'side': None, 'blank': None, 'first': None, 'name': 'pagename',
|
|
'index': None, 'specificity': [1, 0, 0]},
|
|
{'side': None, 'blank': None, 'first': True, 'name': None,
|
|
'index': None, 'specificity': [0, 1, 0]}]),
|
|
('@page :first:first {}', [{
|
|
'side': None, 'blank': None, 'first': True, 'name': None,
|
|
'index': None, 'specificity': [0, 2, 0]}]),
|
|
('@page :left:left {}', [{
|
|
'side': 'left', 'blank': None, 'first': None, 'name': None,
|
|
'index': None, 'specificity': [0, 0, 2]}]),
|
|
('@page :nth(2) {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': None,
|
|
'index': (0, 2, None), 'specificity': [0, 1, 0]}]),
|
|
('@page :nth(2n + 4) {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': None,
|
|
'index': (2, 4, None), 'specificity': [0, 1, 0]}]),
|
|
('@page :nth(3n) {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': None,
|
|
'index': (3, 0, None), 'specificity': [0, 1, 0]}]),
|
|
('@page :nth( n+2 ) {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': None,
|
|
'index': (1, 2, None), 'specificity': [0, 1, 0]}]),
|
|
('@page :nth(even) {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': None,
|
|
'index': (2, 0, None), 'specificity': [0, 1, 0]}]),
|
|
('@page pagename:nth(2) {}', [{
|
|
'side': None, 'blank': None, 'first': None, 'name': 'pagename',
|
|
'index': (0, 2, None), 'specificity': [1, 1, 0]}]),
|
|
('@page page page {}', None),
|
|
('@page :left page {}', None),
|
|
('@page :left, {}', None),
|
|
('@page , {}', None),
|
|
('@page :left, test, {}', None),
|
|
('@page :wrong {}', None),
|
|
('@page :left:wrong {}', None),
|
|
('@page :left:right {}', None),
|
|
))
|
|
def test_page_selectors(style, selectors):
|
|
at_rule, = tinycss2.parse_stylesheet(style)
|
|
assert parse_page_selectors(at_rule) == selectors
|
|
|
|
|
|
@assert_no_logs
|
|
@pytest.mark.parametrize('source, messages', (
|
|
(':lipsum { margin: 2cm', ['WARNING: Invalid or unsupported selector']),
|
|
('::lipsum { margin: 2cm', ['WARNING: Invalid or unsupported selector']),
|
|
('foo { margin-color: red', ['WARNING: Ignored', 'unknown property']),
|
|
('foo { margin-top: red', ['WARNING: Ignored', 'invalid value']),
|
|
('@import "relative-uri.css"',
|
|
['ERROR: Relative URI reference without a base URI']),
|
|
('@import "invalid-protocol://absolute-URL"',
|
|
['ERROR: Failed to load stylesheet at']),
|
|
))
|
|
def test_warnings(source, messages):
|
|
"""Check that appropriate warnings are logged."""
|
|
with capture_logs() as logs:
|
|
CSS(string=source)
|
|
assert len(logs) == 1, source
|
|
for message in messages:
|
|
assert message in logs[0]
|
|
|
|
|
|
@assert_no_logs
|
|
def test_warnings_stylesheet():
|
|
html = '<link rel=stylesheet href=invalid-protocol://absolute>'
|
|
with capture_logs() as logs:
|
|
FakeHTML(string=html).render()
|
|
assert len(logs) == 1
|
|
assert 'ERROR: Failed to load stylesheet at' in logs[0]
|
|
|
|
|
|
@assert_no_logs
|
|
@pytest.mark.parametrize('style', (
|
|
'<style> html { color red; color: blue; color',
|
|
'<html style="color; color: blue; color red">',
|
|
))
|
|
def test_error_recovery(style):
|
|
with capture_logs() as logs:
|
|
document = FakeHTML(string=style)
|
|
page, = document.render().pages
|
|
html, = page._page_box.children
|
|
assert html.style['color'] == (0, 0, 1, 1) # blue
|
|
assert len(logs) == 2
|
|
|
|
|
|
@assert_no_logs
|
|
def test_line_height_inheritance():
|
|
document = FakeHTML(string='''
|
|
<style>
|
|
html { font-size: 10px; line-height: 140% }
|
|
section { font-size: 10px; line-height: 1.4 }
|
|
div, p { font-size: 20px; vertical-align: 50% }
|
|
</style>
|
|
<body><div><section><p></p></section></div></body>
|
|
''')
|
|
page, = document.render().pages
|
|
html, = page._page_box.children
|
|
body, = html.children
|
|
div, = body.children
|
|
section, = div.children
|
|
paragraph, = section.children
|
|
assert html.style['font_size'] == 10
|
|
assert div.style['font_size'] == 20
|
|
# 140% of 10px = 14px is inherited from html
|
|
assert strut_layout(div.style)[0] == 14
|
|
assert div.style['vertical_align'] == 7 # 50 % of 14px
|
|
|
|
assert paragraph.style['font_size'] == 20
|
|
# 1.4 is inherited from p, 1.4 * 20px on em = 28px
|
|
assert strut_layout(paragraph.style)[0] == 28
|
|
assert paragraph.style['vertical_align'] == 14 # 50% of 28px
|
|
|
|
|
|
@assert_no_logs
|
|
def test_important():
|
|
document = FakeHTML(string='''
|
|
<style>
|
|
p:nth-child(1) { color: lime }
|
|
body p:nth-child(2) { color: red }
|
|
|
|
p:nth-child(3) { color: lime !important }
|
|
body p:nth-child(3) { color: red }
|
|
|
|
body p:nth-child(5) { color: lime }
|
|
p:nth-child(5) { color: red }
|
|
|
|
p:nth-child(6) { color: red }
|
|
p:nth-child(6) { color: lime }
|
|
</style>
|
|
<p></p>
|
|
<p></p>
|
|
<p></p>
|
|
<p></p>
|
|
<p></p>
|
|
<p></p>
|
|
''')
|
|
page, = document.render(stylesheets=[CSS(string='''
|
|
body p:nth-child(1) { color: red }
|
|
p:nth-child(2) { color: lime !important }
|
|
|
|
p:nth-child(4) { color: lime !important }
|
|
body p:nth-child(4) { color: red }
|
|
''')]).pages
|
|
html, = page._page_box.children
|
|
body, = html.children
|
|
for paragraph in body.children:
|
|
assert paragraph.style['color'] == (0, 1, 0, 1) # lime (light green)
|
|
|
|
|
|
@assert_no_logs
|
|
def test_named_pages():
|
|
document = FakeHTML(string='''
|
|
<style>
|
|
@page NARRow { size: landscape }
|
|
div { page: AUTO }
|
|
p { page: NARRow }
|
|
</style>
|
|
<div><p><span>a</span></p></div>
|
|
''')
|
|
page, = document.render().pages
|
|
html, = page._page_box.children
|
|
body, = html.children
|
|
div, = body.children
|
|
p, = div.children
|
|
span, = p.children
|
|
assert html.style['page'] == ''
|
|
assert body.style['page'] == ''
|
|
assert div.style['page'] == ''
|
|
assert p.style['page'] == 'NARRow'
|
|
assert span.style['page'] == 'NARRow'
|
|
|
|
|
|
@assert_no_logs
|
|
@pytest.mark.parametrize('value, width', (
|
|
('96px', 96),
|
|
('1in', 96),
|
|
('72pt', 96),
|
|
('6pc', 96),
|
|
('2.54cm', 96),
|
|
('25.4mm', 96),
|
|
('101.6q', 96),
|
|
('1.1em', 11),
|
|
('1.1rem', 17.6),
|
|
# TODO: ch and ex units don't work with font-face, see computed_values.py
|
|
# ('1.1ch', 11),
|
|
# ('1.5ex', 12),
|
|
))
|
|
def test_units(value, width):
|
|
document = FakeHTML(base_url=BASE_URL, string='''
|
|
<style>@font-face {
|
|
src: url(weasyprint.otf); font-family: weasyprint
|
|
}</style>
|
|
<body style="font: 10px weasyprint">
|
|
<p style="margin-left: %s"></p>''' % value)
|
|
page, = document.render().pages
|
|
html, = page._page_box.children
|
|
body, = html.children
|
|
p, = body.children
|
|
assert p.margin_left == width
|
|
|
|
|
|
@assert_no_logs
|
|
@pytest.mark.parametrize('parent_css, parent_size, child_css, child_size', (
|
|
('10px', 10, '10px', 10),
|
|
('x-small', 12, 'xx-large', 32),
|
|
('x-large', 24, '2em', 48),
|
|
('1em', 16, '1em', 16),
|
|
('1em', 16, 'larger', 6 / 5 * 16),
|
|
('medium', 16, 'larger', 6 / 5 * 16),
|
|
('x-large', 24, 'larger', 32),
|
|
('xx-large', 32, 'larger', 1.2 * 32),
|
|
('1px', 1, 'larger', 3 / 5 * 16),
|
|
('28px', 28, 'larger', 32),
|
|
('100px', 100, 'larger', 120),
|
|
('xx-small', 3 / 5 * 16, 'larger', 12),
|
|
('1em', 16, 'smaller', 8 / 9 * 16),
|
|
('medium', 16, 'smaller', 8 / 9 * 16),
|
|
('x-large', 24, 'smaller', 6 / 5 * 16),
|
|
('xx-large', 32, 'smaller', 24),
|
|
('xx-small', 3 / 5 * 16, 'smaller', 0.8 * 3 / 5 * 16),
|
|
('1px', 1, 'smaller', 0.8),
|
|
('28px', 28, 'smaller', 24),
|
|
('100px', 100, 'smaller', 32),
|
|
))
|
|
def test_font_size(parent_css, parent_size, child_css, child_size):
|
|
document = FakeHTML(string='<p>a<span>b')
|
|
style_for = get_all_computed_styles(document, user_stylesheets=[CSS(
|
|
string='p{font-size:%s}span{font-size:%s}' % (parent_css, child_css))])
|
|
|
|
_head, body = document.etree_element
|
|
p, = body
|
|
span, = p
|
|
assert isclose(style_for(p)['font_size'], parent_size)
|
|
assert isclose(style_for(span)['font_size'], child_size)
|