1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-09-11 12:46:15 +03:00

Clean more and more tests

This commit is contained in:
Guillaume Ayoub 2018-03-18 22:23:39 +01:00
parent cffa1f35d4
commit 6a35d779a1
11 changed files with 3996 additions and 3684 deletions

2
.gitignore vendored
View File

@ -19,4 +19,4 @@
# Tests
/weasyprint/tests/.cache
/weasyprint/tests/results
/weasyprint/tests/test_draw/results

View File

@ -9,41 +9,27 @@
"""
import contextlib
import gzip
import io
import math
import os
import sys
import threading
import unicodedata
import zlib
from urllib.parse import urljoin, uses_relative
import cairocffi as cairo
import py
import pytest
from pdfrw import PdfReader
from .. import CSS, HTML, __main__, default_url_fetcher
from ..urls import path2url
from .test_draw import B, _, assert_pixels_equal, image_to_pixels, r
from .test_draw import assert_pixels_equal, image_to_pixels
from .test_draw.test_draw import B, _, r
from .testing_utils import (
FakeHTML, assert_no_logs, capture_logs, http_server, resource_filename)
CHDIR_LOCK = threading.Lock()
@contextlib.contextmanager
def chdir(path):
"""Change the current directory in a context manager."""
with CHDIR_LOCK:
old_dir = os.getcwd()
try:
os.chdir(path)
yield
finally:
os.chdir(old_dir)
def _test_resource(class_, basename, check, **kwargs):
"""Common code for testing the HTML and CSS classes."""
@ -63,7 +49,7 @@ def _test_resource(class_, basename, check, **kwargs):
check(class_(file_obj=fd, **kwargs))
with open(absolute_filename, 'rb') as fd:
content = fd.read()
with chdir(os.path.dirname(__file__)):
py.path.local(os.path.dirname(__file__)).chdir()
relative_filename = os.path.join('resources', basename)
check(class_(relative_filename, **kwargs))
check(class_(string=content, base_url=relative_filename, **kwargs))
@ -90,10 +76,7 @@ def _assert_equivalent_pdf(pdf_bytes1, pdf_bytes2):
assert page1.BleedBox == page2.BleedBox
@assert_no_logs
def test_html_parsing():
"""Test the constructor for the HTML class."""
def check_doc1(html, has_base_url=True):
def _check_doc1(html, has_base_url=True):
"""Check that a parsed HTML document looks like resources/doc1.html"""
root = html.etree_element
assert root.tag == 'html'
@ -109,19 +92,73 @@ def test_html_parsing():
else:
assert html.base_url is None
_test_resource(FakeHTML, 'doc1.html', check_doc1)
_test_resource(FakeHTML, 'doc1_UTF-16BE.html', check_doc1,
def _run(args, stdin=b''):
stdin = io.BytesIO(stdin)
stdout = io.BytesIO()
try:
__main__.HTML = FakeHTML
__main__.main(args.split(), stdin=stdin, stdout=stdout)
finally:
__main__.HTML = HTML
return stdout.getvalue()
class _fake_file(object):
def __init__(self):
self.chunks = []
def write(self, data):
self.chunks.append(bytes(data[:]))
def getvalue(self):
return b''.join(self.chunks)
def _png_size(result):
png_bytes, width, height = result
surface = cairo.ImageSurface.create_from_png(io.BytesIO(png_bytes))
assert (surface.get_width(), surface.get_height()) == (width, height)
return width, height
def _round_meta(pages):
"""Eliminate errors of floating point arithmetic for metadata.
(eg. 49.99999999999994 instead of 50)
"""
for page in pages:
anchors = page.anchors
for anchor_name, (pos_x, pos_y) in anchors.items():
anchors[anchor_name] = round(pos_x, 6), round(pos_y, 6)
links = page.links
for i, link in enumerate(links):
link_type, target, (pos_x, pos_y, width, height) = link
link = (
link_type, target, (round(pos_x, 6), round(pos_y, 6),
round(width, 6), round(height, 6)))
links[i] = link
bookmarks = page.bookmarks
for i, (level, label, (pos_x, pos_y)) in enumerate(bookmarks):
bookmarks[i] = level, label, (round(pos_x, 6), round(pos_y, 6))
@assert_no_logs
def test_html_parsing():
"""Test the constructor for the HTML class."""
_test_resource(FakeHTML, 'doc1.html', _check_doc1)
_test_resource(FakeHTML, 'doc1_UTF-16BE.html', _check_doc1,
encoding='UTF-16BE')
with chdir(os.path.dirname(__file__)):
py.path.local(os.path.dirname(__file__)).chdir()
filename = os.path.join('resources', 'doc1.html')
with open(filename) as fd:
string = fd.read()
check_doc1(FakeHTML(string=string, base_url=filename))
check_doc1(FakeHTML(string=string), has_base_url=False)
_check_doc1(FakeHTML(string=string, base_url=filename))
_check_doc1(FakeHTML(string=string), has_base_url=False)
string_with_meta = string.replace(
'<meta', '<base href="resources/"><meta')
check_doc1(FakeHTML(string=string_with_meta, base_url='.'))
_check_doc1(FakeHTML(string=string_with_meta, base_url='.'))
@assert_no_logs
@ -226,23 +263,13 @@ def test_python_render(tmpdir):
pdf_bytes = html.write_pdf(stylesheets=[css])
assert png_bytes.startswith(b'\211PNG\r\n\032\n')
assert pdf_bytes.startswith(b'%PDF')
check_png_pattern(png_bytes)
# TODO: check PDF content? How?
class fake_file(object):
def __init__(self):
self.chunks = []
def write(self, data):
self.chunks.append(bytes(data[:]))
def getvalue(self):
return b''.join(self.chunks)
png_file = fake_file()
png_file = _fake_file()
html.write_png(png_file, stylesheets=[css])
assert png_file.getvalue() == png_bytes
pdf_file = fake_file()
pdf_file = _fake_file()
html.write_pdf(pdf_file, stylesheets=[css])
_assert_equivalent_pdf(pdf_file.getvalue(), pdf_bytes)
@ -282,7 +309,6 @@ def test_python_render(tmpdir):
@assert_no_logs
def test_command_line_render(tmpdir):
"""Test rendering with the command-line API."""
css = b'''
@page { margin: 2px; size: 8px; background: #fff }
@media screen { img { transform: rotate(-90deg) } }
@ -292,7 +318,7 @@ def test_command_line_render(tmpdir):
combined = b'<style>' + css + b'</style>' + html
linked = b'<link rel=stylesheet href=style.css>' + html
with chdir(resource_filename('')):
py.path.local(resource_filename('')).chdir()
# Reference
html_obj = FakeHTML(string=combined, base_url='dummy.html')
pdf_bytes = html_obj.write_pdf()
@ -306,16 +332,6 @@ def test_command_line_render(tmpdir):
check_png_pattern(rotated_png_bytes, rotated=True)
check_png_pattern(empty_png_bytes, blank=True)
def run(args, stdin=b''):
stdin = io.BytesIO(stdin)
stdout = io.BytesIO()
try:
__main__.HTML = FakeHTML
__main__.main(args.split(), stdin=stdin, stdout=stdout)
finally:
__main__.HTML = HTML
return stdout.getvalue()
tmpdir.chdir()
with open(resource_filename('pattern.png'), 'rb') as pattern_fd:
pattern_bytes = pattern_fd.read()
@ -327,72 +343,72 @@ def test_command_line_render(tmpdir):
tmpdir.join('linked.html').write_binary(linked)
tmpdir.join('style.css').write_binary(css)
run('combined.html out1.png')
run('combined.html out2.pdf')
_run('combined.html out1.png')
_run('combined.html out2.pdf')
assert tmpdir.join('out1.png').read_binary() == png_bytes
_assert_equivalent_pdf(tmpdir.join('out2.pdf').read_binary(), pdf_bytes)
run('combined-UTF-16BE.html out3.png --encoding UTF-16BE')
_run('combined-UTF-16BE.html out3.png --encoding UTF-16BE')
assert tmpdir.join('out3.png').read_binary() == png_bytes
run(tmpdir.join('combined.html').strpath + ' out4.png')
_run(tmpdir.join('combined.html').strpath + ' out4.png')
assert tmpdir.join('out4.png').read_binary() == png_bytes
run(path2url(tmpdir.join('combined.html')) + ' out5.png')
_run(path2url(tmpdir.join('combined.html')) + ' out5.png')
assert tmpdir.join('out5.png').read_binary() == png_bytes
run('linked.html out6.png') # test relative URLs
_run('linked.html out6.png') # test relative URLs
assert tmpdir.join('out6.png').read_binary() == png_bytes
run('combined.html out7 -f png')
run('combined.html out8 --format pdf')
_run('combined.html out7 -f png')
_run('combined.html out8 --format pdf')
assert tmpdir.join('out7').read_binary() == png_bytes
_assert_equivalent_pdf(tmpdir.join('out8').read_binary(), pdf_bytes)
run('no_css.html out9.png')
run('no_css.html out10.png -s style.css')
_run('no_css.html out9.png')
_run('no_css.html out10.png -s style.css')
assert tmpdir.join('out9.png').read_binary() != png_bytes
assert tmpdir.join('out10.png').read_binary() == png_bytes
stdout = run('--format png combined.html -')
stdout = _run('--format png combined.html -')
assert stdout == png_bytes
run('- out11.png', stdin=combined)
_run('- out11.png', stdin=combined)
check_png_pattern(tmpdir.join('out11.png').read_binary())
assert tmpdir.join('out11.png').read_binary() == png_bytes
stdout = run('--format png - -', stdin=combined)
stdout = _run('--format png - -', stdin=combined)
assert stdout == png_bytes
run('combined.html out13.png --media-type screen')
run('combined.html out12.png -m screen')
run('linked.html out14.png -m screen')
_run('combined.html out13.png --media-type screen')
_run('combined.html out12.png -m screen')
_run('linked.html out14.png -m screen')
assert tmpdir.join('out12.png').read_binary() == rotated_png_bytes
assert tmpdir.join('out13.png').read_binary() == rotated_png_bytes
assert tmpdir.join('out14.png').read_binary() == rotated_png_bytes
stdout = run('-f pdf combined.html -')
stdout = _run('-f pdf combined.html -')
assert stdout.count(b'attachment') == 0
stdout = run('-f pdf -a pattern.png combined.html -')
stdout = _run('-f pdf -a pattern.png combined.html -')
assert stdout.count(b'attachment') == 1
stdout = run('-f pdf -a style.css -a pattern.png combined.html -')
stdout = _run('-f pdf -a style.css -a pattern.png combined.html -')
assert stdout.count(b'attachment') == 2
stdout = run('-f png -r 192 linked.html -')
stdout = _run('-f png -r 192 linked.html -')
assert stdout == x2_png_bytes
stdout = run('-f png --resolution 192 linked.html -')
assert run('linked.html - -f png --resolution 192') == x2_png_bytes
stdout = _run('-f png --resolution 192 linked.html -')
assert _run('linked.html - -f png --resolution 192') == x2_png_bytes
assert stdout == x2_png_bytes
os.mkdir('subdirectory')
os.chdir('subdirectory')
py.path.local('subdirectory').chdir()
with capture_logs() as logs:
stdout = run('--format png - -', stdin=combined)
stdout = _run('--format png - -', stdin=combined)
assert len(logs) == 1
assert logs[0].startswith('ERROR: Failed to load image')
assert stdout == empty_png_bytes
stdout = run('--format png --base-url .. - -', stdin=combined)
stdout = _run('--format png --base-url .. - -', stdin=combined)
assert stdout == png_bytes
@ -473,17 +489,11 @@ def test_low_level_api():
assert (width, height) == (16, 16)
check_png_pattern(png_bytes, x2=True)
def png_size(result):
png_bytes, width, height = result
surface = cairo.ImageSurface.create_from_png(io.BytesIO(png_bytes))
assert (surface.get_width(), surface.get_height()) == (width, height)
return width, height
document = html.render([css], enable_hinting=True)
page, = document.pages
assert (page.width, page.height) == (8, 8)
# A resolution that is not multiple of 96:
assert png_size(document.write_png(resolution=145.2)) == (13, 13)
assert _png_size(document.write_png(resolution=145.2)) == (13, 13)
document = FakeHTML(string='''
<style>
@ -499,31 +509,10 @@ def test_low_level_api():
result = document.write_png()
# (Max of both widths, Sum of both heights)
assert png_size(result) == (6, 14)
assert _png_size(result) == (6, 14)
assert document.copy([page_1, page_2]).write_png() == result
assert png_size(document.copy([page_1]).write_png()) == (5, 10)
assert png_size(document.copy([page_2]).write_png()) == (6, 4)
def round_meta(pages):
"""Eliminate errors of floating point arithmetic for metadata.
(eg. 49.99999999999994 instead of 50)
"""
for page in pages:
anchors = page.anchors
for anchor_name, (pos_x, pos_y) in anchors.items():
anchors[anchor_name] = round(pos_x, 6), round(pos_y, 6)
links = page.links
for i, link in enumerate(links):
link_type, target, (pos_x, pos_y, width, height) = link
link = (
link_type, target, (round(pos_x, 6), round(pos_y, 6),
round(width, 6), round(height, 6)))
links[i] = link
bookmarks = page.bookmarks
for i, (level, label, (pos_x, pos_y)) in enumerate(bookmarks):
bookmarks[i] = level, label, (round(pos_x, 6), round(pos_y, 6))
assert _png_size(document.copy([page_1]).write_png()) == (5, 10)
assert _png_size(document.copy([page_2]).write_png()) == (6, 4)
@assert_no_logs
@ -531,7 +520,7 @@ def test_bookmarks():
def assert_bookmarks(html, expected_by_page, expected_tree, round=False):
document = FakeHTML(string=html).render()
if round:
round_meta(document.pages)
_round_meta(document.pages)
assert [p.bookmarks for p in document.pages] == expected_by_page
assert document.make_bookmark_tree() == expected_tree
assert_bookmarks('''
@ -670,7 +659,7 @@ def test_links():
with capture_logs() as logs:
document = FakeHTML(string=html, base_url=base_url).render()
if round:
round_meta(document.pages)
_round_meta(document.pages)
resolved_links = list(document.resolve_links())
assert len(logs) == len(warnings)
for message, expected in zip(logs, warnings):

View File

@ -11,6 +11,8 @@
import functools
import pytest
from .. import images
from ..css import PageType, get_all_computed_styles
from ..formatting_structure import boxes, build, counters
@ -52,29 +54,6 @@ def serialize(box_list):
for box in box_list]
def unwrap_html_body(box):
"""Test that the box tree starts with a ``<html>`` and a ``<body>`` blocks.
Remove them to simplify further tests. These are always at the root
of HTML documents.
"""
assert box.element_tag == 'html'
assert isinstance(box, boxes.BlockBox)
assert len(box.children) == 1
box = box.children[0]
assert isinstance(box, boxes.BlockBox)
assert box.element_tag == 'body'
return box.children
def to_lists(box_tree):
"""Serialize and unwrap ``<html>`` and ``<body>``."""
return serialize(unwrap_html_body(box_tree))
def _parse_base(html_content, base_url=BASE_URL):
document = FakeHTML(string=html_content, base_url=base_url)
style_for, _, _ = get_all_computed_styles(document)
@ -93,7 +72,7 @@ def parse_all(html_content, base_url=BASE_URL):
"""Like parse() but also run all corrections on boxes."""
box = build.build_formatting_structure(*_parse_base(
html_content, base_url))
sanity_checks(box)
_sanity_checks(box)
return box
@ -113,10 +92,18 @@ def assert_tree(box, expected):
expected: a list of serialized <body> children as returned by to_lists().
"""
assert to_lists(box) == expected
assert box.element_tag == 'html'
assert isinstance(box, boxes.BlockBox)
assert len(box.children) == 1
box = box.children[0]
assert isinstance(box, boxes.BlockBox)
assert box.element_tag == 'body'
assert serialize(box.children) == expected
def sanity_checks(box):
def _sanity_checks(box):
"""Check that the rules regarding boxes are met.
This is not required and only helps debugging.
@ -143,20 +130,30 @@ def sanity_checks(box):
), (box, box.children)
for child in box.children:
sanity_checks(child)
_sanity_checks(child)
def _get_grid(html):
html = parse_all(html)
body, = html.children
table_wrapper, = body.children
table, = table_wrapper.children
return tuple(
[[(style, width, color) if width else None
for _score, (style, width, color) in column]
for column in grid]
for grid in table.collapsed_border_grid)
@assert_no_logs
def test_box_tree():
"""Test the creation of trees from HTML strings."""
assert_tree(parse('<p>'), [('p', 'Block', [])])
assert_tree(parse(
'''
assert_tree(parse('''
<style>
span { display: inline-block }
</style>
<p>Hello <em>World <img src="pattern.png"><span>L</span></em>!</p>'''),
[('p', 'Block', [
<p>Hello <em>World <img src="pattern.png"><span>L</span></em>!</p>'''), [
('p', 'Block', [
('p', 'Text', 'Hello '),
('em', 'Inline', [
('em', 'Text', 'World '),
@ -168,7 +165,6 @@ def test_box_tree():
@assert_no_logs
def test_html_entities():
"""Test the management of HTML entities."""
for quote in ['"', '&quot;', '&#x22;', '&#34;']:
assert_tree(parse('<p>{0}abc{1}'.format(quote, quote)), [
('p', 'Block', [
@ -176,8 +172,7 @@ def test_html_entities():
@assert_no_logs
def test_inline_in_block():
"""Test the management of inline boxes in block boxes."""
def test_inline_in_block_1():
source = '<div>Hello, <em>World</em>!\n<p>Lipsum.</p></div>'
expected = [
('div', 'Block', [
@ -194,6 +189,9 @@ def test_inline_in_block():
box = build.inline_in_block(box)
assert_tree(box, expected)
@assert_no_logs
def test_inline_in_block_2():
source = '<div><p>Lipsum.</p>Hello, <em>World</em>!\n</div>'
expected = [
('div', 'Block', [
@ -210,6 +208,9 @@ def test_inline_in_block():
box = build.inline_in_block(box)
assert_tree(box, expected)
@assert_no_logs
def test_inline_in_block_3():
# Absolutes are left in the lines to get their static position later.
source = '''<p>Hello <em style="position:absolute;
display: block">World</em>!</p>'''
@ -227,6 +228,9 @@ def test_inline_in_block():
box = build.block_in_inline(box)
assert_tree(box, expected)
@assert_no_logs
def test_inline_in_block_4():
# Floats are pull to the top of their containing blocks
source = '<p>Hello <em style="float: left">World</em>!</p>'
box = parse(source)
@ -244,14 +248,13 @@ def test_inline_in_block():
@assert_no_logs
def test_block_in_inline():
"""Test the management of block boxes in inline boxes."""
box = parse('''
<style>
<style>
p { display: inline-block; }
span, i { display: block; }
</style>
<p>Lorem <em>ipsum <strong>dolor <span>sit</span>
<span>amet,</span></strong><span><em>conse<i></i></em></span></em></p>''')
</style>
<p>Lorem <em>ipsum <strong>dolor <span>sit</span>
<span>amet,</span></strong><span><em>conse<i>''')
box = build.inline_in_block(box)
assert_tree(box, [
('body', 'Line', [
@ -320,7 +323,6 @@ def test_block_in_inline():
@assert_no_logs
def test_styles():
"""Test the application of CSS to HTML."""
box = parse('''
<style>
span { display: block; }
@ -345,7 +347,6 @@ def test_styles():
@assert_no_logs
def test_whitespace():
"""Test the management of white spaces."""
# TODO: test more cases
# http://www.w3.org/TR/CSS21/text.html#white-space-model
assert_tree(parse_all('''
@ -384,8 +385,14 @@ def test_whitespace():
@assert_no_logs
def test_page_style():
"""Test the management of page styles."""
@pytest.mark.parametrize('page_type, top, right, bottom, left', (
(PageType(side='left', first=True, blank=False, name=None), 20, 3, 3, 10),
(PageType(side='right', first=True, blank=False, name=None), 20, 10, 3, 3),
(PageType(side='left', first=False, blank=False, name=None), 10, 3, 3, 10),
(PageType(side='right', first=False, blank=False, name=None),
10, 10, 3, 3),
))
def test_page_style(page_type, top, right, bottom, left):
document = FakeHTML(string='''
<style>
@page { margin: 3px }
@ -397,14 +404,6 @@ def test_page_style():
style_for, cascaded_styles, computed_styles = get_all_computed_styles(
document)
def assert_page_margins(page_type, top, right, bottom, left):
"""Check the page margin values."""
style = style_for(page_type)
assert style['margin_top'] == (top, 'px')
assert style['margin_right'] == (right, 'px')
assert style['margin_bottom'] == (bottom, 'px')
assert style['margin_left'] == (left, 'px')
# Force the generation of the style for all possible page types as it's
# generally only done during the rendering for needed page types.
standard_page_type = PageType(
@ -412,23 +411,15 @@ def test_page_style():
set_page_type_computed_styles(
standard_page_type, cascaded_styles, computed_styles, document)
assert_page_margins(
PageType(side='left', first=True, blank=False, name=None),
top=20, right=3, bottom=3, left=10)
assert_page_margins(
PageType(side='right', first=True, blank=False, name=None),
top=20, right=10, bottom=3, left=3)
assert_page_margins(
PageType(side='left', first=False, blank=False, name=None),
top=10, right=3, bottom=3, left=10)
assert_page_margins(
PageType(side='right', first=False, blank=False, name=None),
top=10, right=10, bottom=3, left=3)
style = style_for(page_type)
assert style['margin_top'] == (top, 'px')
assert style['margin_right'] == (right, 'px')
assert style['margin_bottom'] == (bottom, 'px')
assert style['margin_left'] == (left, 'px')
@assert_no_logs
def test_images():
"""Test images that may or may not be available."""
def test_images_1():
with capture_logs() as logs:
result = parse_all('''
<p><img src=pattern.png
@ -447,6 +438,9 @@ def test_images():
('img', 'Inline', [
('img', 'Text', 'Inexistent src')])])])])
@assert_no_logs
def test_images_2():
with capture_logs() as logs:
result = parse_all('<p><img src=pattern.png alt="No base_url">',
base_url=None)
@ -460,8 +454,9 @@ def test_images():
@assert_no_logs
def test_tables():
def test_tables_1():
# Rules in http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
# Rule 1.3
# Also table model: http://www.w3.org/TR/CSS21/tables.html#model
assert_tree(parse_all('''
@ -508,6 +503,9 @@ def test_tables():
('x-tfoot', 'TableRowGroup', [])]),
('x-caption', 'TableCaption', [])])])
@assert_no_logs
def test_tables_2():
# Rules 1.4 and 3.1
assert_tree(parse_all('''
<span style="display: table-cell">foo</span>
@ -524,6 +522,9 @@ def test_tables():
('span', 'Line', [
('span', 'Text', 'bar')])])])])])])])
@assert_no_logs
def test_tables_3():
# http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
# Rules 1.1 and 1.2
# Rule XXX (not in the spec): column groups have at least one column child
@ -545,6 +546,9 @@ def test_tables():
('ins', 'TableColumnGroup', [
('ins', 'TableColumn', [])])])])])
@assert_no_logs
def test_tables_4():
# Rules 2.1 then 2.3
assert_tree(parse_all('<x-table>foo <div></div></x-table>'), [
('x-table', 'Block', [
@ -557,6 +561,9 @@ def test_tables():
('x-table', 'Text', 'foo ')])]),
('div', 'Block', [])])])])])])])
@assert_no_logs
def test_tables_5():
# Rule 2.2
assert_tree(parse_all('<x-thead style="display: table-header-group">'
'<div></div><x-td></x-td></x-thead>'), [
@ -568,6 +575,9 @@ def test_tables():
('div', 'Block', [])]),
('x-td', 'TableCell', [])])])])])])
@assert_no_logs
def test_tables_6():
# TODO: re-enable this once we support inline-table
# Rule 3.2
assert_tree(parse_all('<span><x-tr></x-tr></span>'), [
@ -578,6 +588,9 @@ def test_tables():
('span', 'TableRowGroup', [
('x-tr', 'TableRow', [])])])])])])])
@assert_no_logs
def test_tables_7():
# Rule 3.1
# Also, rule 1.3 does not apply: whitespace before and after is preserved
assert_tree(parse_all('''
@ -599,6 +612,9 @@ def test_tables():
('em', 'TableCell', [])])])])]),
('span', 'Text', ' ')])])])
@assert_no_logs
def test_tables_8():
# Rule 3.2
assert_tree(parse_all('<x-tr></x-tr>\t<x-tr></x-tr>'), [
('body', 'Block', [
@ -607,6 +623,9 @@ def test_tables():
('x-tr', 'TableRow', []),
('x-tr', 'TableRow', [])])])])])
@assert_no_logs
def test_tables_9():
assert_tree(parse_all('<x-col></x-col>\n<x-colgroup></x-colgroup>'), [
('body', 'Block', [
('body', 'Table', [
@ -671,23 +690,20 @@ def test_nested_grid_x():
@assert_no_logs
def test_colspan_rowspan():
"""
+---+---+---+
| A | B | C | #
+---+---+---+
| D | E | #
+---+---+ +---+
| F ...| | | <-- overlap
+---+---+---+ +
| H | # # | G |
+---+---+ + +
| I | J | # | |
+---+---+ +---+
def test_colspan_rowspan_1():
# +---+---+---+
# | A | B | C | X
# +---+---+---+
# | D | E | X
# +---+---+ +---+
# | F ...| | | <-- overlap
# +---+---+---+ +
# | H | X X | G |
# +---+---+ + +
# | I | J | X | |
# +---+---+ +---+
# empty cells
"""
# X: empty cells
html = parse_all('''
<table>
<tr>
@ -733,6 +749,9 @@ def test_colspan_rowspan():
[1, 1],
]
@assert_no_logs
def test_colspan_rowspan_2():
# A cell box cannot extend beyond the last row box of a table.
html = parse_all('''
<table>
@ -764,8 +783,7 @@ def test_colspan_rowspan():
@assert_no_logs
def test_before_after():
"""Test the ::before and ::after pseudo-elements."""
def test_before_after_1():
assert_tree(parse_all('''
<style>
p:before { content: normal }
@ -781,14 +799,15 @@ def test_before_after():
('div', 'Block', []),
('section', 'Block', [])])
@assert_no_logs
def test_before_after_2():
assert_tree(parse_all('''
<style>
p:before { content: 'a' 'b' }
p::after { content: 'd' 'e' }
</style>
<p>
c
</p>
<p> c </p>
'''), [
('p', 'Block', [
('p', 'Line', [
@ -798,6 +817,9 @@ def test_before_after():
('p::after', 'Inline', [
('p::after', 'Text', 'de')])])])])
@assert_no_logs
def test_before_after_3():
assert_tree(parse_all('''
<style>
a[href]:before { content: '[' attr(href) '] ' }
@ -811,6 +833,9 @@ def test_before_after():
('a::before', 'Text', '[some url] ')]),
('a', 'Text', 'some text')])])])])
@assert_no_logs
def test_before_after_4():
assert_tree(parse_all('''
<style>
body { quotes: '«' '»' '' '' }
@ -834,6 +859,10 @@ def test_before_after():
('q', 'Text', ' sit amet'),
('q::after', 'Inline', [
('q::after', 'Text', ' »')])])])])])
@assert_no_logs
def test_before_after_5():
with capture_logs() as logs:
assert_tree(parse_all('''
<style>
@ -860,8 +889,7 @@ def test_before_after():
@assert_no_logs
def test_counters():
"""Test counter-reset, counter-increment, content: counter() counters()"""
def test_counters_1():
assert_tree(parse_all('''
<style>
p { counter-increment: p 2 }
@ -887,6 +915,9 @@ def test_counters():
('p::before', 'Text', counter)])])])
for counter in '0 1 3 2 4 6 -11 -9 -7 44 46 48'.split()])
@assert_no_logs
def test_counters_2():
assert_tree(parse_all('''
<ol style="list-style-position: inside">
<li></li>
@ -927,6 +958,9 @@ def test_counters():
('li', 'Line', [
('li::marker', 'Text', '5.')])])])])
@assert_no_logs
def test_counters_3():
assert_tree(parse_all('''
<style>
p { display: list-item; list-style: inside decimal }
@ -951,6 +985,9 @@ def test_counters():
('p', 'Line', [
('p::marker', 'Text', '1.')])])])
@assert_no_logs
def test_counters_4():
assert_tree(parse_all('''
<style>
section:before { counter-reset: h; content: '' }
@ -994,6 +1031,9 @@ def test_counters():
('h1::before', 'Inline', [
('h1::before', 'Text', '3')])])])])])
@assert_no_logs
def test_counters_5():
assert_tree(parse_all('''
<style>
p:before { content: counter(c) }
@ -1014,6 +1054,9 @@ def test_counters():
('p::before', 'Inline', [
('p::before', 'Text', '0')])])])])
@assert_no_logs
def test_counters_6():
# counter-increment may interfere with display: list-item
assert_tree(parse_all('''
<p style="counter-increment: c;
@ -1024,8 +1067,7 @@ def test_counters():
@assert_no_logs
def test_counter_styles():
"""Test the various counter styles."""
def test_counter_styles_1():
assert_tree(parse_all('''
<style>
body { counter-reset: p -12 }
@ -1048,6 +1090,9 @@ def test_counter_styles():
('p::before', 'Text', counter)])])])
for counter in '-- • ◦ ▪ -7'.split()])
@assert_no_logs
def test_counter_styles_2():
assert_tree(parse_all('''
<style>
p { counter-increment: p }
@ -1079,8 +1124,10 @@ def test_counter_styles():
for counter in '''-1986 -1985 -11 -10 -09 -08 -01 00 01 02 09 10 11
99 100 101 4135 4136'''.split()])
# Same test as above, but short-circuit HTML and boxes
@assert_no_logs
def test_counter_styles_3():
# Same test as above, but short-circuit HTML and boxes
assert [counters.format(value, 'decimal-leading-zero') for value in [
-1986, -1985,
-11, -10, -9, -8,
@ -1093,9 +1140,12 @@ def test_counter_styles():
99 100 101 4135 4136
'''.split()
@assert_no_logs
def test_counter_styles_4():
# Now that were confident that they do the same, use the shorter form.
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-007.htm
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-007.htm
assert [counters.format(value, 'lower-roman') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
@ -1109,7 +1159,10 @@ def test_counter_styles():
mmmmcmxcix 5000 5001
'''.split()
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-008.htm
@assert_no_logs
def test_counter_styles_5():
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-008.htm
assert [counters.format(value, 'upper-roman') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
@ -1123,6 +1176,9 @@ def test_counter_styles():
MMMMCMXCIX 5000 5001
'''.split()
@assert_no_logs
def test_counter_styles_6():
assert [counters.format(value, 'lower-alpha') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4,
@ -1132,6 +1188,9 @@ def test_counter_styles():
-1986 -1985 -1 0 a b c d y z aa ab ac bxz bya
'''.split()
@assert_no_logs
def test_counter_styles_7():
assert [counters.format(value, 'upper-alpha') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4,
@ -1141,6 +1200,9 @@ def test_counter_styles():
-1986 -1985 -1 0 A B C D Y Z AA AB AC BXZ BYA
'''.split()
@assert_no_logs
def test_counter_styles_8():
assert [counters.format(value, 'lower-latin') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4,
@ -1150,6 +1212,9 @@ def test_counter_styles():
-1986 -1985 -1 0 a b c d y z aa ab ac bxz bya
'''.split()
@assert_no_logs
def test_counter_styles_9():
assert [counters.format(value, 'upper-latin') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4,
@ -1159,7 +1224,10 @@ def test_counter_styles():
-1986 -1985 -1 0 A B C D Y Z AA AB AC BXZ BYA
'''.split()
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-009.htm
@assert_no_logs
def test_counter_styles_10():
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-009.htm
assert [counters.format(value, 'georgian') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
@ -1176,7 +1244,10 @@ def test_counter_styles():
ჵჰშჟთ 20000 20001
'''.split()
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-010.htm
@assert_no_logs
def test_counter_styles_11():
# http://test.csswg.org/suites/css2.1/20110323/html4/content-counter-010.htm
assert [counters.format(value, 'armenian') for value in [
-1986, -1985,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
@ -1196,9 +1267,6 @@ def test_counter_styles():
@assert_no_logs
def test_margin_boxes():
"""
Test that the correct margin boxes are created.
"""
page_1, page_2 = render_pages('''
<style>
@page {
@ -1231,18 +1299,17 @@ def test_margin_boxes():
@assert_no_logs
def test_margin_box_string_set():
"""Test string-set / string() in margin boxes."""
def test_margin_box_string_set_1():
# Test that both pages get string in the `bottom-center` margin box
page_1, page_2 = render_pages('''
<style>
@page {
@bottom-center { content: string(text_header); }
@bottom-center { content: string(text_header) }
}
p{
p {
string-set: text_header content();
}
.page{
.page {
page-break-before: always;
}
</style>
@ -1260,13 +1327,16 @@ def test_margin_box_string_set():
text_box, = line_box.children
assert text_box.text == 'first assignment'
@assert_no_logs
def test_margin_box_string_set_2():
def simple_string_set_test(content_val, extra_style=""):
page_1, = render_pages('''
<style>
@page {
@top-center { content: string(text_header); }
@top-center { content: string(text_header) }
}
p{
p {
string-set: text_header content(%(content_val)s);
}
%(extra_style)s
@ -1290,13 +1360,16 @@ def test_margin_box_string_set():
else:
simple_string_set_test(value)
@assert_no_logs
def test_margin_box_string_set_3():
# Test `first` (default value) ie. use the first assignment on the page
page_1, = render_pages('''
<style>
@page {
@top-center { content: string(text_header, first); }
@top-center { content: string(text_header, first) }
}
p{
p {
string-set: text_header content();
}
</style>
@ -1309,11 +1382,14 @@ def test_margin_box_string_set():
text_box, = line_box.children
assert text_box.text == 'first assignment'
@assert_no_logs
def test_margin_box_string_set_4():
# test `first-except` ie. exclude from page on which value is assigned
page_1, page_2 = render_pages('''
<style>
@page {
@top-center { content: string(header_nofirst, first-except); }
@top-center { content: string(header_nofirst, first-except) }
}
p{
string-set: header_nofirst content();
@ -1333,13 +1409,16 @@ def test_margin_box_string_set():
text_box, = line_box.children
assert text_box.text == 'first_excepted'
@assert_no_logs
def test_margin_box_string_set_5():
# Test `last` ie. use the most-recent assignment
page_1, = render_pages('''
<style>
@page {
@top-center { content: string(header_last, last); }
@top-center { content: string(header_last, last) }
}
p{
p {
string-set: header_last content();
}
</style>
@ -1353,12 +1432,15 @@ def test_margin_box_string_set():
text_box, = line_box.children
assert text_box.text == 'Second assignment'
@assert_no_logs
def test_margin_box_string_set_6():
# Test multiple complex string-set values
page_1, = render_pages('''
<style>
@page {
@top-center { content: string(text_header, first); }
@bottom-center { content: string(text_footer, last); }
@top-center { content: string(text_header, first) }
@bottom-center { content: string(text_footer, last) }
}
html { counter-reset: a }
body { counter-increment: a }
@ -1416,8 +1498,20 @@ def test_page_counters():
assert text_box.text == 'Page {0} of 3.'.format(page_number)
black = (0, 0, 0, 1)
red = (1, 0, 0, 1)
green = (0, 1, 0, 1) # lime in CSS
blue = (0, 0, 1, 1)
yellow = (1, 1, 0, 1)
black_3 = ('solid', 3, black)
red_1 = ('solid', 1, red)
yellow_5 = ('solid', 5, yellow)
green_5 = ('solid', 5, green)
dashed_blue_5 = ('dashed', 5, blue)
@assert_no_logs
def test_border_collapse():
def test_border_collapse_1():
html = parse_all('<table></table>')
body, = html.children
table_wrapper, = body.children
@ -1425,35 +1519,19 @@ def test_border_collapse():
assert isinstance(table, boxes.TableBox)
assert not hasattr(table, 'collapsed_border_grid')
def get_grid(html):
html = parse_all(html)
body, = html.children
table_wrapper, = body.children
table, = table_wrapper.children
return tuple(
[[(style, width, color) if width else None
for _score, (style, width, color) in column]
for column in grid]
for grid in table.collapsed_border_grid)
grid = get_grid('<table style="border-collapse: collapse"></table>')
grid = _get_grid('<table style="border-collapse: collapse"></table>')
assert grid == ([], [])
black = (0, 0, 0, 1)
red = (1, 0, 0, 1)
green = (0, 1, 0, 1) # lime in CSS
blue = (0, 0, 1, 1)
yellow = (1, 1, 0, 1)
vertical_borders, horizontal_borders = get_grid('''
@assert_no_logs
def test_border_collapse_2():
vertical_borders, horizontal_borders = _get_grid('''
<style>td { border: 1px solid red }</style>
<table style="border-collapse: collapse; border: 3px solid black">
<tr> <td>A</td> <td>B</td> </tr>
<tr> <td>C</td> <td>D</td> </tr>
</table>
''')
black_3 = ('solid', 3, black)
red_1 = ('solid', 1, red)
assert vertical_borders == [
[black_3, red_1, black_3],
[black_3, red_1, black_3],
@ -1464,8 +1542,11 @@ def test_border_collapse():
[black_3, black_3],
]
@assert_no_logs
def test_border_collapse_3():
# hidden vs. none
vertical_borders, horizontal_borders = get_grid('''
vertical_borders, horizontal_borders = _get_grid('''
<style>table, td { border: 3px solid }</style>
<table style="border-collapse: collapse">
<tr> <td>A</td> <td style="border-style: hidden">B</td> </tr>
@ -1482,10 +1563,10 @@ def test_border_collapse():
[black_3, black_3],
]
yellow_5 = ('solid', 5, yellow)
green_5 = ('solid', 5, green)
dashed_blue_5 = ('dashed', 5, blue)
vertical_borders, horizontal_borders = get_grid('''
@assert_no_logs
def test_border_collapse_4():
vertical_borders, horizontal_borders = _get_grid('''
<style>td { border: 1px solid red }</style>
<table style="border-collapse: collapse; border: 5px solid yellow">
<col style="border: 3px solid black" />
@ -1510,8 +1591,11 @@ def test_border_collapse():
[yellow_5, yellow_5, yellow_5],
]
@assert_no_logs
def test_border_collapse_5():
# rowspan and colspan
vertical_borders, horizontal_borders = get_grid('''
vertical_borders, horizontal_borders = _get_grid('''
<style>col, tr { border: 3px solid }</style>
<table style="border-collapse: collapse">
<col /><col /><col />

View File

@ -16,8 +16,7 @@ from .testing_utils import assert_no_logs, capture_logs
@assert_no_logs
def test_font_face():
"""Test the ``font-face`` rule."""
def test_font_face_1():
stylesheet = tinycss2.parse_stylesheet(
'@font-face {'
' font-family: Gentium Hard;'
@ -32,6 +31,9 @@ def test_font_face():
assert src == (
'src', (('external', 'http://example.com/fonts/Gentium.woff'),))
@assert_no_logs
def test_font_face_2():
stylesheet = tinycss2.parse_stylesheet(
'@font-face {'
' font-family: "Fonty Smiley";'
@ -53,6 +55,9 @@ def test_font_face():
assert font_weight == ('font_weight', 200)
assert font_stretch == ('font_stretch', 'condensed')
@assert_no_logs
def test_font_face_3():
stylesheet = tinycss2.parse_stylesheet(
'@font-face {'
' font-family: Gentium Hard;'
@ -66,6 +71,9 @@ def test_font_face():
assert font_family == ('font_family', 'Gentium Hard')
assert src == ('src', (('local', None),))
@assert_no_logs
def test_font_face_4():
# See bug #487
stylesheet = tinycss2.parse_stylesheet(
'@font-face {'
@ -81,8 +89,7 @@ def test_font_face():
assert src == ('src', (('local', 'Gentium Hard'),))
def test_bad_font_face():
"""Test bad ``font-face`` rules."""
def test_font_face_bad_1():
stylesheet = tinycss2.parse_stylesheet(
'@font-face {'
' font-family: "Bad Font";'
@ -108,6 +115,8 @@ def test_bad_font_face():
'WARNING: Ignored `font-weight: bolder` at 1:111, invalid value.',
'WARNING: Ignored `font-stretch: wrong` at 1:133, invalid value.']
def test_font_face_bad_2():
stylesheet = tinycss2.parse_stylesheet('@font-face{}')
with capture_logs() as logs:
descriptors = []
@ -118,6 +127,8 @@ def test_bad_font_face():
assert logs == [
"WARNING: Missing src descriptor in '@font-face' rule at 1:1"]
def test_font_face_bad_3():
stylesheet = tinycss2.parse_stylesheet('@font-face{src: url(test.woff)}')
with capture_logs() as logs:
descriptors = []
@ -128,6 +139,8 @@ def test_bad_font_face():
assert logs == [
"WARNING: Missing font-family descriptor in '@font-face' rule at 1:1"]
def test_font_face_bad_4():
stylesheet = tinycss2.parse_stylesheet('@font-face{font-family: test}')
with capture_logs() as logs:
descriptors = []
@ -138,6 +151,8 @@ def test_bad_font_face():
assert logs == [
"WARNING: Missing src descriptor in '@font-face' rule at 1:1"]
def test_font_face_bad_5():
stylesheet = tinycss2.parse_stylesheet(
'@font-face { font-family: test; src: wrong }')
with capture_logs() as logs:
@ -150,6 +165,8 @@ def test_bad_font_face():
'WARNING: Ignored `src: wrong ` at 1:33, invalid value.',
"WARNING: Missing src descriptor in '@font-face' rule at 1:1"]
def test_font_face_bad_6():
stylesheet = tinycss2.parse_stylesheet(
'@font-face { font-family: good, bad; src: url(test.woff) }')
with capture_logs() as logs:
@ -162,6 +179,8 @@ def test_bad_font_face():
'WARNING: Ignored `font-family: good, bad` at 1:14, invalid value.',
"WARNING: Missing font-family descriptor in '@font-face' rule at 1:1"]
def test_font_face_bad_7():
stylesheet = tinycss2.parse_stylesheet(
'@font-face { font-family: good, bad; src: really bad }')
with capture_logs() as logs:

View File

@ -53,182 +53,224 @@ def test_not_print():
def test_function():
assert expand_to_dict('clip: rect(1px, 3em, auto, auto)') == {
'clip': ((1, 'px'), (3, 'em'), 'auto', 'auto')}
assert_invalid('clip: square(1px, 3em, auto, auto)')
assert_invalid('clip: rect(1px, 3em, auto auto)', 'invalid')
assert_invalid('clip: rect(1px, 3em, auto)')
assert_invalid('clip: rect(1px, 3em / auto)')
@assert_no_logs
def test_counters():
assert expand_to_dict('counter-reset: foo bar 2 baz') == {
'counter_reset': (('foo', 0), ('bar', 2), ('baz', 0))}
assert expand_to_dict('counter-increment: foo bar 2 baz') == {
'counter_increment': (('foo', 1), ('bar', 2), ('baz', 1))}
assert expand_to_dict('counter-reset: foo') == {
'counter_reset': (('foo', 0),)}
assert expand_to_dict('counter-reset: FoO') == {
'counter_reset': (('FoO', 0),)}
assert expand_to_dict('counter-increment: foo bAr 2 Bar') == {
'counter_increment': (('foo', 1), ('bAr', 2), ('Bar', 1))}
assert expand_to_dict('counter-reset: none') == {
'counter_reset': ()}
assert expand_to_dict(
'counter-reset: foo none', 'Invalid counter name') == {}
assert expand_to_dict(
'counter-reset: foo initial', 'Invalid counter name') == {}
assert_invalid('counter-reset: foo 3px')
assert_invalid('counter-reset: 3')
@pytest.mark.parametrize('rule', (
'clip: square(1px, 3em, auto, auto)',
'clip: rect(1px, 3em, auto auto)',
'clip: rect(1px, 3em, auto)',
'clip: rect(1px, 3em / auto)',
))
def test_function_invalid(rule):
assert_invalid(rule)
@assert_no_logs
def test_spacing():
assert expand_to_dict('letter-spacing: normal') == {
'letter_spacing': 'normal'}
assert expand_to_dict('letter-spacing: 3px') == {
'letter_spacing': (3, 'px')}
assert_invalid('letter-spacing: 3')
assert expand_to_dict(
'letter_spacing: normal', 'did you mean letter-spacing') == {}
@pytest.mark.parametrize('rule, result', (
('counter-reset: foo bar 2 baz', {
'counter_reset': (('foo', 0), ('bar', 2), ('baz', 0))}),
('counter-increment: foo bar 2 baz', {
'counter_increment': (('foo', 1), ('bar', 2), ('baz', 1))}),
('counter-reset: foo', {'counter_reset': (('foo', 0),)}),
('counter-reset: FoO', {'counter_reset': (('FoO', 0),)}),
('counter-increment: foo bAr 2 Bar', {
'counter_increment': (('foo', 1), ('bAr', 2), ('Bar', 1))}),
('counter-reset: none', {'counter_reset': ()}),
))
def test_counters(rule, result):
assert expand_to_dict(rule) == result
assert expand_to_dict('word-spacing: normal') == {
'word_spacing': 'normal'}
assert expand_to_dict('word-spacing: 3px') == {
'word_spacing': (3, 'px')}
assert_invalid('word-spacing: 3')
@pytest.mark.parametrize('rule, warning, result', (
('counter-reset: foo initial', 'Invalid counter name: initial.', {}),
('counter-reset: foo none', 'Invalid counter name: none.', {}),
))
def test_counters_warning(rule, warning, result):
assert expand_to_dict(rule, warning) == result
@assert_no_logs
def test_decoration():
assert expand_to_dict('text-decoration: none') == {
'text_decoration': 'none'}
assert expand_to_dict('text-decoration: overline') == {
'text_decoration': frozenset(['overline'])}
# blink is accepted but ignored
assert expand_to_dict('text-decoration: overline blink line-through') == {
'text_decoration': frozenset(['line-through', 'overline'])}
@pytest.mark.parametrize('rule', (
'counter-reset: foo 3px',
'counter-reset: 3',
))
def test_counters_invalid(rule):
assert_invalid(rule)
@assert_no_logs
def test_size():
assert expand_to_dict('size: 200px') == {
'size': ((200, 'px'), (200, 'px'))}
assert expand_to_dict('size: 200px 300pt') == {
'size': ((200, 'px'), (300, 'pt'))}
assert expand_to_dict('size: auto') == {
'size': ((210, 'mm'), (297, 'mm'))}
assert expand_to_dict('size: portrait') == {
'size': ((210, 'mm'), (297, 'mm'))}
assert expand_to_dict('size: landscape') == {
'size': ((297, 'mm'), (210, 'mm'))}
assert expand_to_dict('size: A3 portrait') == {
'size': ((297, 'mm'), (420, 'mm'))}
assert expand_to_dict('size: A3 landscape') == {
'size': ((420, 'mm'), (297, 'mm'))}
assert expand_to_dict('size: portrait A3') == {
'size': ((297, 'mm'), (420, 'mm'))}
assert expand_to_dict('size: landscape A3') == {
'size': ((420, 'mm'), (297, 'mm'))}
assert_invalid('size: A3 landscape A3')
assert_invalid('size: A9')
assert_invalid('size: foo')
assert_invalid('size: foo bar')
assert_invalid('size: 20%')
@pytest.mark.parametrize('rule, result', (
('letter-spacing: normal', {'letter_spacing': 'normal'}),
('letter-spacing: 3px', {'letter_spacing': (3, 'px')}),
('word-spacing: normal', {'word_spacing': 'normal'}),
('word-spacing: 3px', {'word_spacing': (3, 'px')}),
))
def test_spacing(rule, result):
assert expand_to_dict(rule) == result
@assert_no_logs
def test_transforms():
assert expand_to_dict('transform: none') == {
'transform': ()}
def test_spacing_warning():
assert expand_to_dict(
'transform: translate(6px) rotate(90deg)'
) == {'transform': (('translate', ((6, 'px'), (0, 'px'))),
('rotate', math.pi / 2))}
assert expand_to_dict(
'transform: translate(-4px, 0)'
) == {'transform': (('translate', ((-4, 'px'), (0, None))),)}
assert expand_to_dict(
'transform: translate(6px, 20%)'
) == {'transform': (('translate', ((6, 'px'), (20, '%'))),)}
assert expand_to_dict(
'transform: scale(2)'
) == {'transform': (('scale', (2, 2)),)}
assert_invalid('transform: translate(6px 20%)') # missing comma
assert_invalid('transform: lipsumize(6px)')
assert_invalid('transform: foo')
assert_invalid('transform: scale(2) foo')
assert_invalid('transform: 6px')
'letter_spacing: normal', 'did you mean letter-spacing?') == {}
@assert_no_logs
def test_expand_four_sides():
"""Test the 4-value properties."""
assert expand_to_dict('margin: inherit') == {
@pytest.mark.parametrize('rule', (
'letter-spacing: 3',
'word-spacing: 3',
))
def test_spacing_invalid(rule):
assert_invalid(rule)
@assert_no_logs
@pytest.mark.parametrize('rule, result', (
('text-decoration: none', {'text_decoration': 'none'}),
('text-decoration: overline', {
'text_decoration': frozenset(['overline'])}),
('text-decoration: overline blink line-through', {
'text_decoration': frozenset(['line-through', 'overline'])}),
))
def test_decoration(rule, result):
assert expand_to_dict(rule) == result
@assert_no_logs
@pytest.mark.parametrize('rule, result', (
('size: 200px', {'size': ((200, 'px'), (200, 'px'))}),
('size: 200px 300pt', {'size': ((200, 'px'), (300, 'pt'))}),
('size: auto', {'size': ((210, 'mm'), (297, 'mm'))}),
('size: portrait', {'size': ((210, 'mm'), (297, 'mm'))}),
('size: landscape', {'size': ((297, 'mm'), (210, 'mm'))}),
('size: A3 portrait', {'size': ((297, 'mm'), (420, 'mm'))}),
('size: A3 landscape', {'size': ((420, 'mm'), (297, 'mm'))}),
('size: portrait A3', {'size': ((297, 'mm'), (420, 'mm'))}),
('size: landscape A3', {'size': ((420, 'mm'), (297, 'mm'))}),
))
def test_size(rule, result):
assert expand_to_dict(rule) == result
@pytest.mark.parametrize('rule', (
'size: A3 landscape A3',
'size: A9',
'size: foo',
'size: foo bar',
'size: 20%',
))
def test_size_invalid(rule):
assert_invalid(rule)
@assert_no_logs
@pytest.mark.parametrize('rule, result', (
('transform: none', {'transform': ()}),
('transform: translate(6px) rotate(90deg)', {
'transform': (
('translate', ((6, 'px'), (0, 'px'))),
('rotate', math.pi / 2))}),
('transform: translate(-4px, 0)', {
'transform': (('translate', ((-4, 'px'), (0, None))),)}),
('transform: translate(6px, 20%)', {
'transform': (('translate', ((6, 'px'), (20, '%'))),)}),
('transform: scale(2)', {'transform': (('scale', (2, 2)),)}),
))
def test_transforms(rule, result):
assert expand_to_dict(rule) == result
@assert_no_logs
@pytest.mark.parametrize('rule', (
'transform: translate(6px 20%)', # missing comma
'transform: lipsumize(6px)',
'transform: foo',
'transform: scale(2) foo',
'transform: 6px',
))
def test_transforms_invalid(rule):
assert_invalid(rule)
@assert_no_logs
@pytest.mark.parametrize('rule, result', (
('margin: inherit', {
'margin_top': 'inherit',
'margin_right': 'inherit',
'margin_bottom': 'inherit',
'margin_left': 'inherit',
}
assert expand_to_dict('margin: 1em') == {
}),
('margin: 1em', {
'margin_top': (1, 'em'),
'margin_right': (1, 'em'),
'margin_bottom': (1, 'em'),
'margin_left': (1, 'em'),
}
assert expand_to_dict('margin: -1em auto 20%') == {
}),
('margin: -1em auto 20%', {
'margin_top': (-1, 'em'),
'margin_right': 'auto',
'margin_bottom': (20, '%'),
'margin_left': 'auto',
}
assert expand_to_dict('padding: 1em 0') == {
}),
('padding: 1em 0', {
'padding_top': (1, 'em'),
'padding_right': (0, None),
'padding_bottom': (1, 'em'),
'padding_left': (0, None),
}
assert expand_to_dict('padding: 1em 0 2%') == {
}),
('padding: 1em 0 2%', {
'padding_top': (1, 'em'),
'padding_right': (0, None),
'padding_bottom': (2, '%'),
'padding_left': (0, None),
}
assert expand_to_dict('padding: 1em 0 2em 5px') == {
}),
('padding: 1em 0 2em 5px', {
'padding_top': (1, 'em'),
'padding_right': (0, None),
'padding_bottom': (2, 'em'),
'padding_left': (5, 'px'),
}
assert expand_to_dict(
'padding: 1 2 3 4 5',
'Expected 1 to 4 token components got 5') == {}
assert_invalid('margin: rgb(0, 0, 0)')
assert_invalid('padding: auto')
assert_invalid('padding: -12px')
assert_invalid('border-width: -3em')
assert_invalid('border-width: 12%')
}),
))
def test_expand_four_sides(rule, result):
assert expand_to_dict(rule) == result
@assert_no_logs
def test_expand_borders():
"""Test the ``border`` property."""
assert expand_to_dict('border-top: 3px dotted red') == {
def test_expand_four_sides_warning():
assert expand_to_dict(
'padding: 1 2 3 4 5', 'Expected 1 to 4 token components got 5') == {}
@assert_no_logs
@pytest.mark.parametrize('rule', (
'margin: rgb(0, 0, 0)',
'padding: auto',
'padding: -12px',
'border-width: -3em',
'border-width: 12%',
))
def test_expand_four_sides_invalid(rule):
assert_invalid(rule)
@assert_no_logs
@pytest.mark.parametrize('rule, result', (
('border-top: 3px dotted red', {
'border_top_width': (3, 'px'),
'border_top_style': 'dotted',
'border_top_color': (1, 0, 0, 1), # red
}
assert expand_to_dict('border-top: 3px dotted') == {
}),
('border-top: 3px dotted', {
'border_top_width': (3, 'px'),
'border_top_style': 'dotted',
}
assert expand_to_dict('border-top: 3px red') == {
}),
('border-top: 3px red', {
'border_top_width': (3, 'px'),
'border_top_color': (1, 0, 0, 1), # red
}
assert expand_to_dict('border-top: solid') == {
'border_top_style': 'solid',
}
assert expand_to_dict('border: 6px dashed lime') == {
}),
('border-top: solid', {'border_top_style': 'solid'}),
('border: 6px dashed lime', {
'border_top_width': (6, 'px'),
'border_top_style': 'dashed',
'border_top_color': (0, 1, 0, 1), # lime
@ -244,44 +286,65 @@ def test_expand_borders():
'border_right_width': (6, 'px'),
'border_right_style': 'dashed',
'border_right_color': (0, 1, 0, 1), # lime
}
}),
))
def test_expand_borders(rule, result):
assert expand_to_dict(rule) == result
@assert_no_logs
def test_expand_borders_invalid():
assert_invalid('border: 6px dashed left')
@assert_no_logs
def test_expand_list_style():
"""Test the ``list_style`` property."""
assert expand_to_dict('list-style: inherit') == {
@pytest.mark.parametrize('rule, result', (
('list-style: inherit', {
'list_style_position': 'inherit',
'list_style_image': 'inherit',
'list_style_type': 'inherit',
}
assert expand_to_dict('list-style: url(../bar/lipsum.png)') == {
}),
('list-style: url(../bar/lipsum.png)', {
'list_style_image': ('url', 'http://weasyprint.org/bar/lipsum.png'),
}
assert expand_to_dict('list-style: square') == {
}),
('list-style: square', {
'list_style_type': 'square',
}
assert expand_to_dict('list-style: circle inside') == {
}),
('list-style: circle inside', {
'list_style_position': 'inside',
'list_style_type': 'circle',
}
assert expand_to_dict('list-style: none circle inside') == {
}),
('list-style: none circle inside', {
'list_style_position': 'inside',
'list_style_image': ('none', None),
'list_style_type': 'circle',
}
assert expand_to_dict('list-style: none inside none') == {
}),
('list-style: none inside none', {
'list_style_position': 'inside',
'list_style_image': ('none', None),
'list_style_type': 'none',
}
assert_invalid('list-style: none inside none none')
assert_invalid('list-style: red')
assert_invalid('list-style: circle disc',
}),
))
def test_expand_list_style(rule, result):
assert expand_to_dict(rule) == result
@assert_no_logs
def test_expand_list_style_warning():
assert_invalid(
'list-style: circle disc',
'got multiple type values in a list-style shorthand')
@assert_no_logs
@pytest.mark.parametrize('rule', (
'list-style: none inside none none',
'list-style: red',
))
def test_expand_list_style_invalid(rule):
assert_invalid(rule)
def assert_background(css, **expected):
"""Helper checking the background properties."""
expanded = expand_to_dict('background: ' + css)
@ -296,7 +359,6 @@ def assert_background(css, **expected):
@assert_no_logs
def test_expand_background():
"""Test the ``background`` property."""
assert_background('red', background_color=(1, 0, 0, 1))
assert_background(
'url(lipsum.png)',

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
"""
weasyprint.tests.test_draw
--------------------------
Test the final, drawn results and compare PNG images pixel per pixel.
:copyright: Copyright 2011-2018 Simon Sapin and contributors, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import sys
import cairocffi as cairo
from ..testing_utils import FakeHTML, resource_filename
# RGBA to native-endian ARGB
as_pixel = (
lambda x: x[:-1][::-1] + x[-1:]
if sys.byteorder == 'little' else
lambda x: x[-1:] + x[:-1])
_ = as_pixel(b'\xff\xff\xff\xff') # white
R = as_pixel(b'\xff\x00\x00\xff') # red
B = as_pixel(b'\x00\x00\xff\xff') # blue
G = as_pixel(b'\x00\xff\x00\xff') # lime green
V = as_pixel(b'\xBF\x00\x40\xff') # Average of 1*B and 3*r.
S = as_pixel(b'\xff\x3f\x3f\xff') # r above r above #fff
r = as_pixel(b'\xff\x00\x00\xff') # red
g = as_pixel(b'\x00\x80\x00\xff') # half green
b = as_pixel(b'\x00\x00\x80\xff') # half blue
v = as_pixel(b'\x80\x00\x80\xff') # Average of B and r.
a = as_pixel(b'\x00\x00\xfe\xff') # JPG is lossy...
p = as_pixel(b'\xc0\x00\x3f\xff') # r above r above B above #fff.
def assert_pixels(name, expected_width, expected_height, expected_pixels,
html):
"""Helper testing the size of the image and the pixels values."""
assert len(expected_pixels) == expected_height
assert len(expected_pixels[0]) == expected_width * 4
expected_raw = b''.join(expected_pixels)
_doc, pixels = html_to_pixels(name, expected_width, expected_height, html)
assert_pixels_equal(
name, expected_width, expected_height, pixels, expected_raw)
def assert_same_rendering(expected_width, expected_height, documents,
tolerance=0):
"""Render HTML documents to PNG and check that they render the same.
Each document is passed as a (name, html_source) tuple.
"""
pixels_list = []
for name, html in documents:
_doc, pixels = html_to_pixels(
name, expected_width, expected_height, html)
pixels_list.append((name, pixels))
_name, reference = pixels_list[0]
for name, pixels in pixels_list[1:]:
assert_pixels_equal(name, expected_width, expected_height,
reference, pixels, tolerance)
def assert_different_renderings(expected_width, expected_height, documents):
"""Render HTML documents to PNG and check that they don't render the same.
Each document is passed as a (name, html_source) tuple.
"""
pixels_list = []
for name, html in documents:
_doc, pixels = html_to_pixels(
name, expected_width, expected_height, html)
pixels_list.append((name, pixels))
for i, (name_1, pixels_1) in enumerate(pixels_list):
for name_2, pixels_2 in pixels_list[i + 1:]:
if pixels_1 == pixels_2: # pragma: no cover
write_png(name_1, pixels_1, expected_width, expected_height)
# Same as "assert pixels_1 != pixels_2" but the output of
# the assert hook would be gigantic and useless.
assert False, '%s and %s are the same' % (name_1, name_2)
def write_png(basename, pixels, width, height): # pragma: no cover
"""Take a pixel matrix and write a PNG file."""
directory = os.path.join(os.path.dirname(__file__), 'results')
if not os.path.isdir(directory):
os.mkdir(directory)
filename = os.path.join(directory, basename + '.png')
cairo.ImageSurface(
cairo.FORMAT_ARGB32, width, height,
data=bytearray(pixels), stride=width * 4
).write_to_png(filename)
def html_to_pixels(name, expected_width, expected_height, html):
"""Render an HTML document to PNG, checks its size and return pixel data.
Also return the document to aid debugging.
"""
document = FakeHTML(
string=html,
# Dummy filename, but in the right directory.
base_url=resource_filename('<test>'))
pixels = document_to_pixels(
document, name, expected_width, expected_height)
return document, pixels
def document_to_pixels(document, name, expected_width, expected_height):
"""Render an HTML document to PNG, check its size and return pixel data."""
surface = document.write_image_surface()
return image_to_pixels(surface, expected_width, expected_height)
def image_to_pixels(surface, width, height):
assert (surface.get_width(), surface.get_height()) == (width, height)
# RGB24 is actually the same as ARGB32, with A unused.
assert surface.get_format() in (cairo.FORMAT_ARGB32, cairo.FORMAT_RGB24)
pixels = surface.get_data()[:]
assert len(pixels) == width * height * 4
return pixels
def assert_pixels_equal(name, width, height, raw, expected_raw, tolerance=0):
"""Take 2 matrices of pixels and assert that they are the same."""
if raw != expected_raw: # pragma: no cover
for i, (value, expected) in enumerate(zip(raw, expected_raw)):
if abs(value - expected) > tolerance:
write_png(name, raw, width, height)
write_png(name + '.expected', expected_raw,
width, height)
pixel_n = i // 4
x = pixel_n // width
y = pixel_n % width
i % 4
pixel = tuple(list(raw[i:i + 4]))
expected_pixel = tuple(list(
expected_raw[i:i + 4]))
assert 0, (
'Pixel (%i, %i) in %s: expected rgba%s, got rgba%s'
% (x, y, name, expected_pixel, pixel))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,530 @@
"""
weasyprint.tests.test_draw.test_tables
--------------------------------------
Test the drawn tables.
:copyright: Copyright 2011-2014 Simon Sapin and contributors, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from . import B, R, S, _, as_pixel, assert_pixels, p
from ...html import HTML_HANDLERS
from ..testing_utils import assert_no_logs, requires
# rgba(255, 0, 0, 0.5) above #fff
r = as_pixel(b'\xff\x7f\x7f\xff')
# rgba(0, 255, 0, 0.5) above #fff
g = as_pixel(b'\x7f\xff\x7f\xff')
# r above B above #fff.
b = as_pixel(b'\x80\x00\x7f\xff')
# TODO: refactor colspan/rowspan into CSS:
# td, th { column-span: attr(colspan integer) }
HTML_HANDLERS['x-td'] = HTML_HANDLERS['td']
HTML_HANDLERS['x-th'] = HTML_HANDLERS['th']
tables_source = '''
<style>
@page { size: 28px; background: #fff }
x-table { margin: 1px; padding: 1px; border-spacing: 1px;
border: 1px solid transparent }
x-td { width: 2px; height: 2px; padding: 1px;
border: 1px solid transparent }
%(extra_css)s
</style>
<x-table>
<x-colgroup>
<x-col></x-col>
<x-col></x-col>
</x-colgroup>
<x-col></x-col>
<x-tbody>
<x-tr>
<x-td></x-td>
<x-td rowspan=2></x-td>
<x-td></x-td>
</x-tr>
<x-tr>
<x-td colspan=2></x-td>
<x-td></x-td>
</x-tr>
</x-tbody>
<x-tr>
<x-td></x-td>
<x-td></x-td>
</x-tr>
</x-table>
'''
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_1():
assert_pixels('table_borders', 28, 28, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+_+_+_+_+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+r+r+r+r+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+r+_+_+_+_+S+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+r+_+_+_+_+S+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+r+_+_+_+_+S+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+r+_+_+_+_+S+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+S+S+S+S+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border-color: #00f; table-layout: fixed }
x-td { border-color: rgba(255, 0, 0, 0.5) }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_2():
assert_pixels('table_collapsed_borders', 28, 28, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+r+r+r+r+r+_+_+_+_+r+r+r+r+r+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+r+r+r+r+r+r+r+r+r+r+r+r+r+r+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border: 2px solid #00f; table-layout: fixed;
border-collapse: collapse }
x-td { border-color: #ff7f7f }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_3():
assert_pixels('table_collapsed_borders_paged', 28, 52, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+r+r+r+r+r+_+_+_+_+r+r+r+r+r+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+r+r+r+r+r+r+r+r+r+r+r+r+r+r+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+_, # noqa
_+g+_+B+B+r+r+r+r+r+r+r+r+r+r+r+r+r+r+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+g+_, # noqa
_+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+g+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border: solid #00f; border-width: 8px 2px;
table-layout: fixed; border-collapse: collapse }
x-td { border-color: #ff7f7f }
@page { size: 28px 26px; margin: 1px;
border: 1px solid rgba(0, 255, 0, 0.5); }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_4():
assert_pixels('table_td_backgrounds', 28, 28, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+S+S+S+S+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+S+S+S+S+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+S+S+S+S+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+S+S+S+S+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+S+S+S+S+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+S+S+S+S+S+S+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border-color: #00f; table-layout: fixed }
x-td { background: rgba(255, 0, 0, 0.5) }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_5():
assert_pixels('table_row_backgrounds', 28, 28, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border-color: #00f; table-layout: fixed }
x-tbody { background: rgba(0, 0, 255, 1) }
x-tr { background: rgba(255, 0, 0, 0.5) }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_6():
assert_pixels('table_column_backgrounds', 28, 28, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border-color: #00f; table-layout: fixed }
x-colgroup { background: rgba(0, 0, 255, 1) }
x-col { background: rgba(255, 0, 0, 0.5) }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_7():
assert_pixels('table_borders_and_row_backgrounds', 28, 28, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+b+B+B+B+B+b+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+b+B+B+B+B+b+_+b+b+b+b+b+b+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+b+B+B+B+B+b+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+p+b+b+b+b+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+_+_+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+r+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border-color: #00f; table-layout: fixed }
x-tr:first-child { background: blue }
x-td { border-color: rgba(255, 0, 0, 0.5) }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_8():
assert_pixels('table_borders_and_column_backgrounds', 28, 28, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+r+_+_+_+_+r+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+b+b+b+b+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+B+B+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+B+B+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+B+B+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+B+B+b+B+B+B+B+p+_+r+_+_+_+_+r+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+b+p+p+p+p+p+p+_+r+r+r+r+r+r+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+B+B+B+B+b+_+r+_+_+_+_+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+b+b+b+b+b+b+_+r+r+r+r+r+r+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], tables_source % {'extra_css': '''
x-table { border-color: #00f; table-layout: fixed }
x-col:first-child { background: blue }
x-td { border-color: rgba(255, 0, 0, 0.5) }
'''})
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_9():
r = as_pixel(b'\xff\x00\x00\xff')
assert_pixels('collapsed_border_thead', 22, 36, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+_+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+r+_+_+_+_+r+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+_+_+_+_+_+r+_+_+_+_+r+_+_+_+_+_+r+_+_, # noqa
_+_+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+r+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], '''
<style>
@page { size: 22px 18px; margin: 1px; background: #fff }
td { border: 1px red solid; width: 4px; height: 3px; }
</style>
<table style="table-layout: fixed; border-collapse: collapse">
<thead style="border: blue solid; border-width: 2px 3px;
"><td></td><td></td><td></td></thead>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>''')
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_10():
assert_pixels('collapsed_border_tfoot', 22, 36, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+_+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+R+_+_+_+_+R+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+R+_+_+_+_+R+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+R+_+_+_+_+R+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+_+R+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+_+R+_+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+R+_+_+_+_+R+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+R+_+_+_+_+R+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+_+_+_+_+R+_+_+_+_+R+_+_+_+_+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], '''
<style>
@page { size: 22px 18px; margin: 1px; background: #fff }
td { border: 1px red solid; width: 4px; height: 3px; }
</style>
<table style="table-layout: fixed; margin-left: 1px;
border-collapse: collapse">
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tfoot style="border: blue solid; border-width: 2px 3px;
"><td></td><td></td><td></td></tfoot>''')
@assert_no_logs
@requires('cairo', (1, 12, 0))
def test_tables_11():
# Segression test for inline table with collapsed border and alignment
# rendering borders incorrectly
# https://github.com/Kozea/WeasyPrint/issues/82
assert_pixels('inline_text_align', 20, 10, [
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+R+R+R+R+R+R+R+R+R+R+R+_, # noqa
_+_+_+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+R+_, # noqa
_+_+_+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+R+_, # noqa
_+_+_+_+_+_+_+_+R+_+_+_+_+R+_+_+_+_+R+_, # noqa
_+_+_+_+_+_+_+_+R+R+R+R+R+R+R+R+R+R+R+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_+_, # noqa
], '''
<style>
@page { size: 20px 10px; margin: 1px; background: #fff }
body { text-align: right; font-size: 0 }
table { display: inline-table; width: 11px }
td { border: 1px red solid; width: 4px; height: 3px }
</style>
<table style="table-layout: fixed; border-collapse: collapse">
<tr><td></td><td></td></tr>''')

View File

@ -10,7 +10,7 @@
"""
from .test_boxes import render_pages
from .test_draw import requires
from .test_draw.test_draw import requires
from .testing_utils import assert_no_logs

View File

@ -12,7 +12,8 @@
import pytest
from ..test_boxes import render_pages
from ..test_draw import B, _, assert_pixels, r
from ..test_draw import assert_pixels
from ..test_draw.test_draw import B, _, r
from ..testing_utils import assert_no_logs, capture_logs, requires