# coding: utf8 """ weasyprint.tests.test_draw -------------------------- Test the final, drawn results and compare PNG images pixel per pixel. :copyright: Copyright 2011-2013 Simon Sapin and contributors, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import division, unicode_literals import sys import os.path import tempfile import shutil import itertools import functools import cairocffi as cairo import pytest from ..compat import xrange, izip, ints_from_bytes from ..urls import ensure_url from .. import HTML from .testing_utils import ( resource_filename, TestHTML, FONTS, assert_no_logs, capture_logs) # 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]) # Short variable names are OK here # pylint: disable=C0103 _ = 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 def save_pixels_to_png(pixels, width, height, filename): """Save raw pixels to a PNG file.""" cairo.ImageSurface( cairo.FORMAT_ARGB32, width, height, data=bytearray(pixels), stride=width * 4 ).write_to_png(filename) def requires_cairo_1_12(test): @functools.wraps(test) def decorated_test(): if cairo.cairo_version() < 11200: print('Running cairo %s but this test requires 1.12+' % cairo.cairo_version_string()) pytest.xfail() test() return decorated_test 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, pixel-per-pixel. 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 no two documents 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__), 'test_results') if not os.path.isdir(directory): os.mkdir(directory) filename = os.path.join(directory, basename + '.png') save_pixels_to_png(pixels, width, height, 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 = TestHTML( string=html, # Dummy filename, but in the right directory. base_url=resource_filename('')) 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, checks 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()[:] stride = surface.get_stride() row_bytes = width * 4 if stride != row_bytes: assert stride > row_bytes pixels = b''.join(pixels[i:i + row_bytes] for i in xrange(0, height * stride, stride)) assert len(pixels) == width * height * 4 return pixels def assert_pixels_equal(name, width, height, raw, expected_raw, tolerance=0): """ Take 2 matrices of height by width pixels and assert that they are the same. """ if raw != expected_raw: # pragma: no cover for i, (value, expected) in enumerate(izip( ints_from_bytes(raw), ints_from_bytes(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(ints_from_bytes(raw[i:i + 4])) expected_pixel = tuple(ints_from_bytes( expected_raw[i:i + 4])) assert 0, ( 'Pixel (%i, %i) in %s: expected rgba%s, got rgba%s' % (x, y, name, expected_pixel, pixel)) @assert_no_logs def test_canvas_background(): """Test the background applied on ```` and/or ```` tags.""" assert_pixels('all_blue', 10, 10, (10 * [10 * B]), ''' ''') assert_pixels('blocks', 10, 10, [ r+r+r+r+r+r+r+r+r+r, r+r+r+r+r+r+r+r+r+r, r+r+B+B+B+B+B+B+r+r, r+r+B+B+B+B+B+B+r+r, r+r+B+B+B+B+B+B+r+r, r+r+B+B+B+B+B+B+r+r, r+r+B+B+B+B+B+B+r+r, r+r+r+r+r+r+r+r+r+r, r+r+r+r+r+r+r+r+r+r, r+r+r+r+r+r+r+r+r+r, ], ''' ''') @assert_no_logs def test_background_image(): """Test background images.""" # pattern.png looks like this: # r+B+B+B, # B+B+B+B, # B+B+B+B, # B+B+B+B, for name, css, pixels in [ ('repeat', 'url(pattern.png)', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+r+B+B+B+r+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+B+B+B+B+B+_+_, _+_+r+B+B+B+r+B+B+B+r+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+B+B+B+B+B+_+_, _+_+r+B+B+B+r+B+B+B+r+B+_+_, _+_+B+B+B+B+B+B+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('repeat_x', 'url(pattern.png) repeat-x', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+r+B+B+B+r+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+B+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('repeat_y', 'url(pattern.png) repeat-y', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('left_top', 'url(pattern.png) no-repeat 0 0%', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('center_top', 'url(pattern.png) no-repeat 50% 0px', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+r+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('right_top', 'url(pattern.png) no-repeat 6px top', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+r+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('bottom_6_right_0', 'url(pattern.png) no-repeat bottom 6px right 0', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+r+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('left_center', 'url(pattern.png) no-repeat left center', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('center_left', 'url(pattern.png) no-repeat center left', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('center_center', 'url(pattern.png) no-repeat 3px 3px', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+r+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('right_center', 'url(pattern.png) no-repeat 100% 50%', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+r+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('left_bottom', 'url(pattern.png) no-repeat 0% bottom', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('center_bottom', 'url(pattern.png) no-repeat center 6px', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+r+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('bottom_center', 'url(pattern.png) no-repeat bottom center', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+r+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('right_bottom', 'url(pattern.png) no-repeat 6px 100%', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+r+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('repeat_x_1px_2px', 'url(pattern.png) repeat-x 1px 2px', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+B+r+B+B+B+r+B+B+B+r+_+_, _+_+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+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('repeat_y_local_2px_1px', 'url(pattern.png) repeat-y local 2px 1px', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+r+B+B+B+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+r+B+B+B+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+r+B+B+B+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('fixed', 'url(pattern.png) no-repeat fixed', [ # The image is actually here: ####### _+_+_+_+_+_+_+_+_+_+_+_+_+_, # _+_+_+_+_+_+_+_+_+_+_+_+_+_, # _+_+B+B+_+_+_+_+_+_+_+_+_+_, # _+_+B+B+_+_+_+_+_+_+_+_+_+_, # _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('fixed_right', 'url(pattern.png) no-repeat fixed right 3px', [ ####### _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+r+B+_+_, # _+_+_+_+_+_+_+_+_+_+B+B+_+_, # _+_+_+_+_+_+_+_+_+_+B+B+_+_, # _+_+_+_+_+_+_+_+_+_+B+B+_+_, # _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('fixed_center_center', 'url(pattern.png)no-repeat fixed 50%center', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+r+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('multi_under', 'url(pattern.png) no-repeat, ' 'url(pattern.png) no-repeat 2px 1px', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+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+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ('multi_over', 'url(pattern.png) no-repeat 2px 1px, ' 'url(pattern.png) no-repeat', [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_+_+_, _+_+B+B+r+B+B+B+_+_+_+_+_+_, _+_+B+B+B+B+B+B+_+_+_+_+_+_, _+_+B+B+B+B+B+B+_+_+_+_+_+_, _+_+_+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ]), ]: assert_pixels('background_' + name, 14, 16, pixels, '''

  ''' % (css,)) @assert_no_logs def test_background_origin(): """Test the background-origin property.""" def test_value(value, pixels, css=None): assert_pixels('background_origin_' + value, 12, 12, pixels, ''' ''' % (css or value,)) test_value('border-box', [ _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+r+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_+_+_, ]) test_value('padding-box', [ _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+r+B+B+B+_+_, _+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, ]) test_value('content-box', [ _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+r+B+B+B+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_, _+_+_+_+_+B+B+B+B+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, ]) test_value('border-box_clip', [ _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+r+B+_+_+_, _+_+_+_+_+_+_+B+B+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, ], css='border-box; background-clip: content-box') @assert_no_logs def test_background_repeat_space(): """Test for background-repeat: space""" assert_pixels('background_repeat_space', 12, 16, [ _+_+_+_+_+_+_+_+_+_+_+_, _+r+B+B+B+_+_+r+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+B+_, _+_+_+_+_+_+_+_+_+_+_+_, _+r+B+B+B+_+_+r+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+B+_, _+_+_+_+_+_+_+_+_+_+_+_, _+r+B+B+B+_+_+r+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+B+_, _+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_repeat_space', 12, 14, [ _+_+_+_+_+_+_+_+_+_+_+_, _+r+B+B+B+_+_+r+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+B+_, _+r+B+B+B+_+_+r+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+B+_, _+r+B+B+B+_+_+r+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+B+_, _+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_repeat_space', 12, 13, [ _+_+_+_+_+_+_+_+_+_+_+_, _+r+B+B+B+r+B+B+B+r+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+B+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+r+B+B+B+r+B+B+B+r+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+B+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') @assert_no_logs def test_background_repeat_round(): """Test for background-repeat: round""" assert_pixels('background_repeat_round', 10, 14, [ _+_+_+_+_+_+_+_+_+_, _+r+r+B+B+B+B+B+B+_, _+r+r+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+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+B+B+_, _+r+r+B+B+B+B+B+B+_, _+r+r+B+B+B+B+B+B+_, _+B+B+B+B+B+B+B+B+_, _+B+B+B+B+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_repeat_round', 10, 18, [ _+_+_+_+_+_+_+_+_+_, _+r+r+B+B+B+B+B+B+_, _+r+r+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+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+B+B+_, _+r+r+B+B+B+B+B+B+_, _+r+r+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+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+B+B+_, _+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_repeat_round', 10, 14, [ _+_+_+_+_+_+_+_+_+_, _+r+r+B+B+B+B+B+B+_, _+r+r+B+B+B+B+B+B+_, _+r+r+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+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+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+_, _+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_repeat_round', 10, 14, [ _+_+_+_+_+_+_+_+_+_, _+r+B+B+B+r+B+B+B+_, _+r+B+B+B+r+B+B+B+_, _+r+B+B+B+r+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+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+B+B+B+B+B+B+B+_, _+B+B+B+B+B+B+B+B+_, _+B+B+B+B+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_, ], ''' ''') @assert_no_logs def test_background_clip(): """Test the background-clip property.""" def test_value(value, pixels): assert_pixels('background_clip_' + value, 8, 8, pixels, ''' ''' % (value,)) test_value('#00f border-box', [ _+_+_+_+_+_+_+_, _+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+B+B+B+B+_, _+B+B+B+B+B+B+_, _+_+_+_+_+_+_+_, ]) test_value('#00f padding-box', [ _+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_, _+_+B+B+B+B+_+_, _+_+B+B+B+B+_+_, _+_+B+B+B+B+_+_, _+_+B+B+B+B+_+_, _+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_, ]) test_value('#00f content-box', [ _+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_, _+_+_+B+B+_+_+_, _+_+_+B+B+_+_+_, _+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_, ]) G = as_pixel(b'\x00\xff\x00\xff') # lime green test_value('url(pattern.png) padding-box, #0f0', [ _+_+_+_+_+_+_+_, _+G+G+G+G+G+G+_, _+G+r+B+B+B+G+_, _+G+B+B+B+B+G+_, _+G+B+B+B+B+G+_, _+G+B+B+B+B+G+_, _+G+G+G+G+G+G+_, _+_+_+_+_+_+_+_, ]) @assert_no_logs def test_background_size(): """Test the background-size property.""" assert_pixels('background_size', 12, 12, [ _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+r+r+B+B+B+B+B+B+_, _+_+_+r+r+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+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+B+B+_, _+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_size_auto', 12, 12, [ _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+r+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_size_contain', 14, 10, [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+r+r+B+B+B+B+B+B+_+_+_+_+_, _+r+r+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+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+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_size_mixed', 14, 10, [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+r+r+B+B+B+B+B+B+_+_+_+_+_, _+r+r+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+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+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_size_double', 14, 10, [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+r+r+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+B+B+B+B+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') assert_pixels('background_size_cover', 14, 10, [ _+_+_+_+_+_+_+_+_+_+_+_+_+_, _+r+r+r+B+B+B+B+B+B+B+B+B+_, _+r+r+r+B+B+B+B+B+B+B+B+B+_, _+r+r+r+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+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+B+B+B+B+B+_, _+B+B+B+B+B+B+B+B+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_+_+_+_+_, ], ''' ''') @assert_no_logs def test_list_style_image(): """Test images as list markers.""" for position, pixels in [ ('outside', [ # ++++++++++++++ ++++

  • horizontal margins: 7px 2px # ######
  • width: 12 - 7 - 2 = 3px # -- list marker margin: 0.5em = 2px # ******** list marker image is 4px wide _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+r+B+B+B+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_, _+_+B+B+B+B+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, ]), ('inside', [ # ++++++++++++++ ++++
  • horizontal margins: 7px 2px # ######
  • width: 12 - 7 - 2 = 3px # ******** list marker image is 4px wide: overflow _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+r+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+B+B+B+B+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_+_+_, ]) ]: assert_pixels('list_style_image_' + position, 12, 10, pixels, ''' ''' % (FONTS, position)) assert_pixels('list_style_none', 10, 10, [ _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, _+_+_+_+_+_+_+_+_+_, ], '''