2012-02-16 16:41:00 +04:00
|
|
|
|
# coding: utf8
|
|
|
|
|
|
|
|
|
|
# WeasyPrint converts web documents (HTML, CSS, ...) to PDF.
|
2012-03-21 16:40:23 +04:00
|
|
|
|
# Copyright (C) 2011-2012 Simon Sapin and contributors.
|
|
|
|
|
# See AUTHORS for more details.
|
2012-02-16 16:41:00 +04:00
|
|
|
|
#
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
Test the public API.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2012-02-17 21:49:58 +04:00
|
|
|
|
from __future__ import division, unicode_literals
|
|
|
|
|
|
2012-02-16 21:28:18 +04:00
|
|
|
|
import sys
|
2012-02-16 16:41:00 +04:00
|
|
|
|
import os
|
2012-02-16 17:42:08 +04:00
|
|
|
|
import io
|
2012-02-16 16:41:00 +04:00
|
|
|
|
import contextlib
|
|
|
|
|
import threading
|
|
|
|
|
import shutil
|
2012-02-16 17:42:08 +04:00
|
|
|
|
import tempfile
|
2012-02-16 16:41:00 +04:00
|
|
|
|
|
2012-02-22 17:13:14 +04:00
|
|
|
|
import pystacia
|
2012-02-16 16:41:00 +04:00
|
|
|
|
|
2012-02-16 21:52:36 +04:00
|
|
|
|
from .testing_utils import (
|
|
|
|
|
resource_filename, assert_no_logs, TEST_UA_STYLESHEET)
|
2012-02-29 15:23:25 +04:00
|
|
|
|
from ..compat import urljoin
|
2012-02-22 18:52:49 +04:00
|
|
|
|
from .. import HTML, CSS
|
2012-02-16 21:52:36 +04:00
|
|
|
|
from .. import __main__
|
2012-02-16 16:41:00 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def temp_directory():
|
|
|
|
|
"""Context manager that gives the path to a new temporary directory.
|
|
|
|
|
|
|
|
|
|
Remove everything on exiting the context.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
directory = tempfile.mkdtemp()
|
|
|
|
|
try:
|
|
|
|
|
yield directory
|
|
|
|
|
finally:
|
|
|
|
|
shutil.rmtree(directory)
|
|
|
|
|
|
|
|
|
|
|
2012-02-16 17:42:08 +04:00
|
|
|
|
def read_file(filename):
|
|
|
|
|
"""Shortcut for reading a file."""
|
|
|
|
|
with open(filename, 'rb') as fd:
|
|
|
|
|
return fd.read()
|
|
|
|
|
|
|
|
|
|
|
2012-02-16 21:28:18 +04:00
|
|
|
|
def write_file(filename, content):
|
|
|
|
|
"""Shortcut for reading a file."""
|
|
|
|
|
with open(filename, 'wb') as fd:
|
|
|
|
|
fd.write(content)
|
|
|
|
|
|
|
|
|
|
|
2012-02-16 21:52:36 +04:00
|
|
|
|
class TestHTML(HTML):
|
|
|
|
|
"""Like HTML, but with the testing (smaller) UA stylesheet"""
|
|
|
|
|
def _ua_stylesheet(self):
|
|
|
|
|
return [TEST_UA_STYLESHEET]
|
|
|
|
|
|
|
|
|
|
|
2012-02-20 16:04:35 +04:00
|
|
|
|
def _test_resource(class_, basename, check, **kwargs):
|
2012-02-16 16:41:00 +04:00
|
|
|
|
"""Common code for testing the HTML and CSS classes."""
|
|
|
|
|
absolute_filename = resource_filename(basename)
|
|
|
|
|
check(class_(absolute_filename, **kwargs))
|
2012-02-27 18:07:41 +04:00
|
|
|
|
check(class_(guess=absolute_filename, **kwargs))
|
2012-02-16 16:41:00 +04:00
|
|
|
|
check(class_(filename=absolute_filename, **kwargs))
|
2012-02-27 18:07:41 +04:00
|
|
|
|
check(class_('file://' + absolute_filename, **kwargs))
|
|
|
|
|
check(class_(guess='file://' + absolute_filename, **kwargs))
|
2012-02-16 16:41:00 +04:00
|
|
|
|
check(class_(url='file://' + absolute_filename, **kwargs))
|
2012-02-27 18:07:41 +04:00
|
|
|
|
with open(absolute_filename, 'rb') as fd:
|
|
|
|
|
check(class_(fd, **kwargs))
|
|
|
|
|
with open(absolute_filename, 'rb') as fd:
|
|
|
|
|
check(class_(guess=fd, **kwargs))
|
2012-02-17 21:49:58 +04:00
|
|
|
|
with open(absolute_filename, 'rb') as fd:
|
2012-02-16 16:41:00 +04:00
|
|
|
|
check(class_(file_obj=fd, **kwargs))
|
2012-02-17 21:49:58 +04:00
|
|
|
|
with open(absolute_filename, 'rb') as fd:
|
2012-02-16 16:41:00 +04:00
|
|
|
|
content = fd.read()
|
|
|
|
|
with chdir(os.path.dirname(__file__)):
|
|
|
|
|
relative_filename = os.path.join('resources', basename)
|
|
|
|
|
check(class_(relative_filename, **kwargs))
|
|
|
|
|
check(class_(string=content, base_url=relative_filename, **kwargs))
|
|
|
|
|
encoding = kwargs.get('encoding') or 'utf8'
|
|
|
|
|
check(class_(string=content.decode(encoding), # unicode
|
|
|
|
|
base_url=relative_filename, **kwargs))
|
|
|
|
|
|
|
|
|
|
|
2012-02-20 16:04:35 +04:00
|
|
|
|
@assert_no_logs
|
2012-02-16 16:41:00 +04:00
|
|
|
|
def test_html_parsing():
|
|
|
|
|
"""Test the constructor for the HTML class."""
|
|
|
|
|
def check_doc1(html):
|
|
|
|
|
"""Check that a parsed HTML document looks like resources/doc1.html"""
|
|
|
|
|
assert html.root_element.tag == 'html'
|
|
|
|
|
assert [child.tag for child in html.root_element] == ['head', 'body']
|
|
|
|
|
_head, body = html.root_element
|
|
|
|
|
assert [child.tag for child in body] == ['h1', 'p', 'ul']
|
|
|
|
|
h1 = body[0]
|
2012-02-17 21:49:58 +04:00
|
|
|
|
assert h1.text == 'WeasyPrint test document (with Ünicōde)'
|
|
|
|
|
url = urljoin(h1.base_url, 'pattern.png')
|
2012-02-16 16:41:00 +04:00
|
|
|
|
assert url.startswith('file:')
|
|
|
|
|
assert url.endswith('weasyprint/tests/resources/pattern.png')
|
|
|
|
|
|
2012-02-20 16:04:35 +04:00
|
|
|
|
_test_resource(TestHTML, 'doc1.html', check_doc1)
|
|
|
|
|
_test_resource(TestHTML, 'doc1-utf32.html', check_doc1, encoding='utf32')
|
2012-02-16 16:41:00 +04:00
|
|
|
|
|
|
|
|
|
|
2012-02-20 16:04:35 +04:00
|
|
|
|
@assert_no_logs
|
2012-02-16 16:41:00 +04:00
|
|
|
|
def test_css_parsing():
|
|
|
|
|
"""Test the constructor for the CSS class."""
|
|
|
|
|
def check_css(css):
|
|
|
|
|
"""Check that a parsed stylsheet looks like resources/utf8-test.css"""
|
|
|
|
|
# Using 'encoding' adds a CSSCharsetRule
|
|
|
|
|
rule = css.stylesheet.cssRules[-1]
|
|
|
|
|
assert rule.selectorText == 'h1::before'
|
|
|
|
|
content, background = rule.style.getProperties(all=True)
|
|
|
|
|
|
|
|
|
|
assert content.name == 'content'
|
|
|
|
|
string, = content.propertyValue
|
2012-02-17 21:49:58 +04:00
|
|
|
|
assert string.value == 'I løvë Unicode'
|
2012-02-16 16:41:00 +04:00
|
|
|
|
|
|
|
|
|
assert background.name == 'background-image'
|
|
|
|
|
url, = background.propertyValue
|
|
|
|
|
url = url.absoluteUri
|
|
|
|
|
assert url.startswith('file:')
|
|
|
|
|
assert url.endswith('weasyprint/tests/resources/pattern.png')
|
|
|
|
|
|
2012-02-20 16:04:35 +04:00
|
|
|
|
_test_resource(CSS, 'utf8-test.css', check_css)
|
|
|
|
|
_test_resource(CSS, 'latin1-test.css', check_css, encoding='latin1')
|
2012-02-16 17:42:08 +04:00
|
|
|
|
|
|
|
|
|
|
2012-02-16 21:28:18 +04:00
|
|
|
|
def check_png_pattern(png_bytes):
|
2012-02-22 17:13:14 +04:00
|
|
|
|
with contextlib.closing(pystacia.read_blob(png_bytes)) as image:
|
|
|
|
|
assert image.size == (8, 8)
|
|
|
|
|
lines = image.get_raw('rgba')['raw']
|
2012-02-16 17:42:08 +04:00
|
|
|
|
from .test_draw import _, r, B, assert_pixels_equal
|
2012-02-22 17:13:14 +04:00
|
|
|
|
assert_pixels_equal('api_png', 8, 8, lines, b''.join([
|
2012-02-16 17:42:08 +04:00
|
|
|
|
_+_+_+_+_+_+_+_,
|
|
|
|
|
_+_+_+_+_+_+_+_,
|
|
|
|
|
_+_+r+B+B+B+_+_,
|
|
|
|
|
_+_+B+B+B+B+_+_,
|
|
|
|
|
_+_+B+B+B+B+_+_,
|
|
|
|
|
_+_+B+B+B+B+_+_,
|
|
|
|
|
_+_+_+_+_+_+_+_,
|
|
|
|
|
_+_+_+_+_+_+_+_,
|
2012-02-22 17:13:14 +04:00
|
|
|
|
]))
|
2012-02-16 21:28:18 +04:00
|
|
|
|
|
|
|
|
|
|
2012-02-20 16:04:35 +04:00
|
|
|
|
@assert_no_logs
|
2012-02-16 21:28:18 +04:00
|
|
|
|
def test_python_render():
|
|
|
|
|
"""Test rendering with the Python API."""
|
2012-02-16 21:52:36 +04:00
|
|
|
|
html = TestHTML(string='<body><img src=pattern.png>',
|
2012-02-16 21:28:18 +04:00
|
|
|
|
base_url=resource_filename('dummy.html'))
|
|
|
|
|
css = CSS(string='''
|
|
|
|
|
@page { margin: 2px; -weasy-size: 8px; background: #fff }
|
|
|
|
|
body { margin: 0; }
|
|
|
|
|
''')
|
|
|
|
|
|
|
|
|
|
png_bytes = html.write_png(stylesheets=[css])
|
|
|
|
|
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)
|
2012-02-16 17:42:08 +04:00
|
|
|
|
# TODO: check PDF content? How?
|
|
|
|
|
|
|
|
|
|
class fake_file(object):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.chunks = []
|
|
|
|
|
self.write = self.chunks.append
|
|
|
|
|
|
|
|
|
|
def getvalue(self):
|
|
|
|
|
return b''.join(self.chunks)
|
|
|
|
|
png_file = fake_file()
|
|
|
|
|
html.write_png(png_file, stylesheets=[css])
|
|
|
|
|
assert png_file.getvalue() == png_bytes
|
|
|
|
|
pdf_file = fake_file()
|
|
|
|
|
html.write_pdf(pdf_file, stylesheets=[css])
|
|
|
|
|
assert pdf_file.getvalue() == pdf_bytes
|
|
|
|
|
|
|
|
|
|
with temp_directory() as temp:
|
|
|
|
|
png_filename = os.path.join(temp, '1.png')
|
|
|
|
|
pdf_filename = os.path.join(temp, '1.pdf')
|
|
|
|
|
html.write_png(png_filename, stylesheets=[css])
|
|
|
|
|
html.write_pdf(pdf_filename, stylesheets=[css])
|
|
|
|
|
assert read_file(png_filename) == png_bytes
|
|
|
|
|
assert read_file(pdf_filename) == pdf_bytes
|
|
|
|
|
|
|
|
|
|
png_filename = os.path.join(temp, '2.png')
|
|
|
|
|
pdf_filename = os.path.join(temp, '2.pdf')
|
|
|
|
|
with open(png_filename, 'wb') as png_file:
|
|
|
|
|
html.write_png(png_file, stylesheets=[css])
|
|
|
|
|
with open(pdf_filename, 'wb') as pdf_file:
|
|
|
|
|
html.write_pdf(pdf_file, stylesheets=[css])
|
|
|
|
|
assert read_file(png_filename) == png_bytes
|
|
|
|
|
assert read_file(pdf_filename) == pdf_bytes
|
2012-02-16 21:28:18 +04:00
|
|
|
|
|
|
|
|
|
|
2012-02-20 16:04:35 +04:00
|
|
|
|
@assert_no_logs
|
2012-02-16 21:28:18 +04:00
|
|
|
|
def test_command_line_render():
|
|
|
|
|
"""Test rendering with the command-line API."""
|
|
|
|
|
css = b'''
|
|
|
|
|
@page { margin: 2px; -weasy-size: 8px; background: #fff }
|
|
|
|
|
body { margin: 0; }
|
|
|
|
|
'''
|
|
|
|
|
html = b'<body><img src=pattern.png>'
|
2012-02-17 21:49:58 +04:00
|
|
|
|
combined = b'<style>' + css + b'</style>' + html
|
2012-02-16 21:28:18 +04:00
|
|
|
|
linked = b'<link rel=stylesheet href=style.css>' + html
|
|
|
|
|
|
|
|
|
|
with chdir(resource_filename('')):
|
|
|
|
|
# Reference
|
2012-02-17 21:49:58 +04:00
|
|
|
|
png_bytes = TestHTML(string=combined, base_url='dummy.html').write_png()
|
|
|
|
|
pdf_bytes = TestHTML(string=combined, base_url='dummy.html').write_pdf()
|
2012-02-16 21:28:18 +04:00
|
|
|
|
check_png_pattern(png_bytes)
|
|
|
|
|
|
2012-02-27 17:51:10 +04:00
|
|
|
|
def run(args, stdin=b''):
|
|
|
|
|
stdin = io.BytesIO(stdin)
|
|
|
|
|
stdout = io.BytesIO()
|
2012-02-16 21:52:36 +04:00
|
|
|
|
try:
|
|
|
|
|
__main__.HTML = TestHTML
|
2012-02-27 17:51:10 +04:00
|
|
|
|
__main__.main(args.split(), stdin=stdin, stdout=stdout)
|
2012-02-16 21:52:36 +04:00
|
|
|
|
finally:
|
|
|
|
|
__main__.HTML = HTML
|
2012-02-27 17:51:10 +04:00
|
|
|
|
return stdout.getvalue()
|
2012-02-16 21:28:18 +04:00
|
|
|
|
|
|
|
|
|
with temp_directory() as temp:
|
|
|
|
|
with chdir(temp):
|
|
|
|
|
pattern_bytes = read_file(resource_filename('pattern.png'))
|
|
|
|
|
write_file('pattern.png', pattern_bytes)
|
|
|
|
|
write_file('no_css.html', html)
|
|
|
|
|
write_file('combined.html', combined)
|
|
|
|
|
write_file('combined-utf32.html',
|
|
|
|
|
combined.decode('ascii').encode('utf32'))
|
|
|
|
|
write_file('linked.html', linked)
|
|
|
|
|
write_file('style.css', css)
|
|
|
|
|
|
|
|
|
|
run('combined.html out1.png')
|
|
|
|
|
run('combined.html out2.pdf')
|
|
|
|
|
assert read_file('out1.png') == png_bytes
|
|
|
|
|
assert read_file('out2.pdf') == pdf_bytes
|
|
|
|
|
|
|
|
|
|
run('combined-utf32.html out3.png --encoding utf32')
|
|
|
|
|
assert read_file('out3.png') == png_bytes
|
|
|
|
|
|
|
|
|
|
combined_absolute = os.path.join(temp, 'combined.html')
|
|
|
|
|
run(combined_absolute + ' out4.png')
|
|
|
|
|
assert read_file('out4.png') == png_bytes
|
|
|
|
|
|
2012-02-21 17:13:34 +04:00
|
|
|
|
combined_url = 'file://{0}/{1}'.format(temp, 'combined.html')
|
2012-02-16 21:28:18 +04:00
|
|
|
|
run(combined_url + ' out5.png')
|
|
|
|
|
assert read_file('out5.png') == png_bytes
|
|
|
|
|
|
|
|
|
|
run('linked.html out6.png') # test relative URLs
|
|
|
|
|
assert read_file('out6.png') == png_bytes
|
|
|
|
|
|
|
|
|
|
run('combined.html out7 -f png')
|
|
|
|
|
run('combined.html out8 --format pdf')
|
|
|
|
|
assert read_file('out7') == png_bytes
|
|
|
|
|
assert read_file('out8') == pdf_bytes
|
|
|
|
|
|
|
|
|
|
run('no_css.html out9.png')
|
|
|
|
|
run('no_css.html out10.png -s style.css')
|
|
|
|
|
assert read_file('out9.png') != png_bytes
|
|
|
|
|
assert read_file('out10.png') == png_bytes
|
|
|
|
|
|
2012-02-27 17:51:10 +04:00
|
|
|
|
stdout = run('--format png combined.html -')
|
|
|
|
|
assert stdout == png_bytes
|
2012-02-16 21:28:18 +04:00
|
|
|
|
|
2012-02-27 17:51:10 +04:00
|
|
|
|
run('- out11.png', stdin=combined)
|
2012-02-17 21:49:58 +04:00
|
|
|
|
check_png_pattern(read_file('out11.png'))
|
2012-02-16 21:28:18 +04:00
|
|
|
|
assert read_file('out11.png') == png_bytes
|
|
|
|
|
|
2012-02-27 17:51:10 +04:00
|
|
|
|
stdout = run('--format png - -', stdin=combined)
|
|
|
|
|
assert stdout == png_bytes
|