1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-04 07:57:52 +03:00
WeasyPrint/weasyprint/tests/test_css.py

411 lines
14 KiB
Python
Raw Normal View History

# coding: utf-8
"""
weasyprint.tests.test_css
-------------------------
2011-04-26 20:07:19 +04:00
Test the CSS parsing, cascade, inherited and computed values.
2014-01-10 18:27:02 +04:00
:copyright: Copyright 2011-2014 Simon Sapin and contributors, see AUTHORS.
:license: BSD, see LICENSE for details.
2011-08-24 13:46:16 +04:00
"""
from __future__ import division, unicode_literals
from pytest import raises
2011-04-26 20:07:19 +04:00
2017-03-25 02:33:36 +03:00
from .. import CSS, css, default_url_fetcher
from ..css import get_all_computed_styles
from ..css.computed_values import strut_layout
2012-07-27 19:52:35 +04:00
from ..urls import open_data_url, path2url
2017-03-25 02:33:36 +03:00
from .testing_utils import (
FakeHTML, assert_no_logs, capture_logs, resource_filename)
2011-04-26 20:07:19 +04:00
2012-03-21 19:07:49 +04:00
@assert_no_logs
def test_data_url():
"""Test URLs with the "data:" scheme."""
def parse(url, expected_content, expected_mime_type, expected_charset):
2012-07-13 14:24:55 +04:00
assert open_data_url(url) == dict(
string=expected_content,
mime_type=expected_mime_type,
encoding=expected_charset,
redirected_url=url)
2012-03-21 19:07:49 +04:00
parse('data:,foo', b'foo', 'text/plain', 'US-ASCII')
parse('data:,foo%22bar', b'foo"bar', 'text/plain', 'US-ASCII')
parse('data:text/plain,foo', b'foo', 'text/plain', None)
parse('data:text/html;charset=utf8,<body>', b'<body>', 'text/html', 'utf8')
parse('data:text/plain;base64,Zm9v', b'foo', 'text/plain', None)
parse('data:text/plain;base64,Zm9vbw==', b'fooo', 'text/plain', None)
parse('data:text/plain;base64,Zm9vb28=', b'foooo', 'text/plain', None)
parse('data:text/plain;base64,Zm9vb29v', b'fooooo', 'text/plain', None)
parse('data:text/plain;base64,Zm9vbw%3D%3D', b'fooo', 'text/plain', None)
parse('data:text/plain;base64,Zm9vb28%3D', b'foooo', 'text/plain', None)
# "From a theoretical point of view, the padding character is not needed,
# since the number of missing bytes can be calculated from the number
# of Base64 digits."
# https://en.wikipedia.org/wiki/Base64#Padding
# The Acid 2 test uses base64 URLs without padding.
# http://acid2.acidtests.org/
parse('data:text/plain;base64,Zm9vbw', b'fooo', 'text/plain', None)
parse('data:text/plain;base64,Zm9vb28', b'foooo', 'text/plain', None)
2012-04-02 16:45:44 +04:00
with raises(IOError):
open_data_url('data:foo')
2012-04-02 16:45:44 +04:00
2012-03-21 19:07:49 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-05-17 13:29:00 +04:00
def test_style_dict():
2011-08-24 13:46:16 +04:00
"""Test a style in a ``dict``."""
style = css.StyleDict({
'margin_left': 12,
'display': 'block'})
assert style.display == 'block'
assert style.margin_left == 12
2011-10-08 18:34:10 +04:00
with raises(KeyError):
2011-08-24 13:46:16 +04:00
style.position # pylint: disable=W0104
2011-04-26 20:07:19 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-04-26 20:07:19 +04:00
def test_find_stylesheets():
2011-08-24 13:46:16 +04:00
"""Test if the stylesheets are found in a HTML document."""
html = FakeHTML(resource_filename('doc1.html'))
2011-06-29 16:04:42 +04:00
sheets = list(css.find_stylesheets(
html.wrapper_element, 'print', default_url_fetcher, font_config=None,
2017-06-30 18:54:02 +03:00
page_rules=None))
2012-04-02 16:45:44 +04:00
assert len(sheets) == 2
2011-05-10 13:41:23 +04:00
# Also test that stylesheets are in tree order
assert [s.base_url.rsplit('/', 1)[-1].rsplit(',', 1)[-1] for s in sheets] \
2012-04-02 16:45:44 +04:00
== ['a%7Bcolor%3AcurrentColor%7D', 'doc1.html']
2011-04-26 20:07:19 +04:00
2017-06-30 18:54:02 +03:00
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)
2012-04-02 16:45:44 +04:00
assert len(rules) == 10
2017-06-30 18:54:02 +03:00
# TODO: test that the values are correct too
2011-04-26 20:07:19 +04:00
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_expand_shorthands():
2011-08-24 13:46:16 +04:00
"""Test the expand shorthands."""
2012-03-25 04:41:02 +04:00
sheet = CSS(resource_filename('sheet2.css'))
2017-06-30 18:54:02 +03:00
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
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_annotate_document():
2011-09-09 01:15:14 +04:00
"""Test a document with inline style."""
2011-08-24 13:46:16 +04:00
# Short names for variables are OK here
# pylint: disable=C0103
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 a lists of their children
_head, body = document.etree_element
2016-02-12 01:05:12 +03:00
h1, p, ul, div = body
li_0, _li_1 = ul
a, = li_0
2016-02-12 01:05:12 +03:00
span1, = div
span2, = span1
h1 = style_for(h1)
p = style_for(p)
ul = style_for(ul)
li_0 = style_for(li_0)
2016-02-12 01:05:12 +03:00
div = style_for(div)
after = style_for(a, 'after')
a = style_for(a)
2016-02-12 01:05:12 +03:00
span1 = style_for(span1)
span2 = style_for(span2)
assert h1.background_image == [
('url', path2url(resource_filename('logo_small.png')))]
2011-06-29 16:04:42 +04:00
assert h1.font_weight == 700
2013-04-04 06:39:25 +04:00
assert h1.font_size == 40 # 2em
2011-06-29 16:04:42 +04:00
2013-04-04 06:39:25 +04:00
# 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' # resolved at use-value time.
2011-06-29 16:04:42 +04:00
2013-04-04 06:39:25 +04:00
# 2em * 1.25ex = 2 * 20 * 1.25 * 0.8 = 40
# 2.5ex * 1.25ex = 2.5 * 0.8 * 20 * 1.25 * 0.8 = 40
assert ul.margin_top == (40, 'px')
assert ul.margin_right == (40, 'px')
assert ul.margin_bottom == (40, 'px')
assert ul.margin_left == (40, 'px')
2011-06-29 16:04:42 +04:00
assert ul.font_weight == 400
2011-05-06 18:23:29 +04:00
# 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
2011-06-29 16:04:42 +04:00
assert li_0.font_weight == 700
2012-04-02 16:45:44 +04:00
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
2011-06-29 16:04:42 +04:00
assert a.text_decoration == frozenset(['underline'])
assert a.font_weight == 900
2012-04-02 16:45:44 +04:00
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')
2012-04-05 18:31:32 +04:00
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'
2011-08-08 13:28:37 +04:00
2016-02-12 01:05:12 +03:00
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)
2012-04-05 18:31:32 +04:00
assert after.border_top_width == 42
assert after.border_bottom_width == 3
2011-05-12 18:06:47 +04:00
# TODO much more tests here: test that origin and selector precedence
# and inheritance are correct, ...
2011-08-24 13:46:16 +04:00
# pylint: enable=C0103
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-07-01 20:08:24 +04:00
def test_page():
2011-08-24 13:46:16 +04:00
"""Test the ``@page`` properties."""
document = FakeHTML(resource_filename('doc1.html'))
2013-04-11 14:08:53 +04:00
style_for = get_all_computed_styles(
document, user_stylesheets=[CSS(string='''
2012-01-27 14:02:22 +04:00
html {
color: red;
}
@page {
margin: 10px;
}
@page :right {
2012-01-27 14:02:22 +04:00
color: blue;
margin-bottom: 12pt;
font-size: 20px;
@top-left {
width: 10em;
}
2012-01-27 14:02:22 +04:00
@top-right {
font-size: 10px;
}
}
''')])
style = style_for('first_left_page')
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
2011-07-01 20:08:24 +04:00
style = style_for('first_right_page')
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
2011-07-01 20:08:24 +04:00
style = style_for('left_page')
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
2011-07-01 20:08:24 +04:00
style = style_for('right_page')
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
style = style_for('first_left_page', '@top-left')
assert style is None
style = style_for('first_right_page', '@top-left')
2012-01-27 14:02:22 +04:00
assert style.font_size == 20 # inherited from @page
assert style.width == (200, 'px')
style = style_for('first_right_page', '@top-right')
2012-01-27 14:02:22 +04:00
assert style.font_size == 10
2012-02-20 16:04:35 +04:00
@assert_no_logs
2011-12-16 15:19:10 +04:00
def test_warnings():
"""Check that appropriate warnings are logged."""
for source, messages in [
(':lipsum { margin: 2cm',
2012-04-25 21:05:42 +04:00
['WARNING: Invalid or unsupported selector']),
('::lipsum { margin: 2cm',
2012-04-25 21:05:42 +04:00
['WARNING: Invalid or unsupported selector']),
('@page foo { margin: 2cm',
2017-03-26 12:42:50 +03:00
['WARNING: Unsupported @page selector " foo "']),
('foo { margin-color: red',
2012-04-05 22:15:28 +04:00
['WARNING: Ignored', 'unknown property']),
('foo { margin-top: red',
2012-04-05 22:15:28 +04:00
['WARNING: Ignored', 'invalid value']),
('@import "relative-uri.css',
['WARNING: Relative URI reference without a base URI']),
('@import "invalid-protocol://absolute-URL',
['WARNING: Failed to load stylesheet at']),
]:
with capture_logs() as logs:
CSS(string=source)
2017-03-26 12:42:50 +03:00
assert len(logs) == 1, source
for message in messages:
assert message in logs[0]
html = '<link rel=stylesheet href=invalid-protocol://absolute>'
with capture_logs() as logs:
FakeHTML(string=html).render()
assert len(logs) == 1
assert 'WARNING: Failed to load stylesheet at' in logs[0]
2012-02-20 16:04:35 +04:00
@assert_no_logs
def test_error_recovery():
2011-12-16 14:19:56 +04:00
with capture_logs() as logs:
document = FakeHTML(string='''
<style> html { color red; color: blue; color
''')
2012-10-02 20:59:02 +04:00
page, = document.render().pages
html, = page._page_box.children
assert html.style.color == (0, 0, 1, 1) # blue
document = FakeHTML(string='''
<html style="color; color: blue; color red">
''')
2012-10-02 20:59:02 +04:00
page, = document.render().pages
html, = page._page_box.children
assert html.style.color == (0, 0, 1, 1) # blue
assert len(logs) == 4
2012-02-20 16:04:35 +04:00
@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>
''')
2012-10-02 20:59:02 +04:00
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
2012-04-02 16:45:44 +04:00
assert paragraph.style.vertical_align == 14 # 50% of 28px
@assert_no_logs
def test_important():
document = FakeHTML(string='''
2012-04-02 16:45:44 +04:00
<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 }
2012-04-02 16:45:44 +04:00
p:nth-child(4) { color: lime !important }
body p:nth-child(4) { color: red }
2012-10-02 20:59:02 +04:00
''')]).pages
html, = page._page_box.children
2012-04-02 16:45:44 +04:00
body, = html.children
for paragraph in body.children:
assert paragraph.style.color == (0, 1, 0, 1) # lime (light green)
2013-04-05 14:22:28 +04:00
@assert_no_logs
def test_units():
document = FakeHTML(string='''
2013-04-05 14:22:28 +04:00
<p style="margin-left: 96px"></p>
<p style="margin-left: 1in"></p>
<p style="margin-left: 72pt"></p>
<p style="margin-left: 6pc"></p>
<p style="margin-left: 2.54cm"></p>
<p style="margin-left: 25.4mm"></p>
2015-08-14 18:03:59 +03:00
<p style="margin-left: 101.6q"></p>
2013-04-05 14:22:28 +04:00
<p style="margin-left: 1.1em"></p>
<p style="margin-left: 1.1rem"></p>
2013-04-05 14:22:28 +04:00
<p style="margin-left: 1.1ch; font: 14px Ahem"></p>
<p style="margin-left: 1.5ex; font: 10px Ahem"></p>
<p style="margin-left: 1.1ch"></p>
''')
page, = document.render().pages
html, = page._page_box.children
body, = html.children
margins = [round(p.margin_left, 6) for p in body.children]
default_font_ch = margins.pop()
# Ahem: 1ex is 0.8em, 1ch is 1em
assert margins == [96, 96, 96, 96, 96, 96, 96, 17.6, 17.6, 15.4, 12]
2013-04-05 14:22:28 +04:00
assert 4 < default_font_ch < 12 # for 1em = 16px