1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-04 07:57:52 +03:00
WeasyPrint/tests/test_css.py
Guillaume Ayoub 81f73ead2e Use the weasyprint.otf font everywhere
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.
2020-12-11 00:22:33 +01:00

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)