1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-05 08:27:22 +03:00

Merge branch 'master' into first

This commit is contained in:
Guillaume Ayoub 2017-01-02 12:14:49 +01:00
commit e222779f0a
8 changed files with 320 additions and 42 deletions

18
CHANGES
View File

@ -2,6 +2,24 @@ WeasyPrint changelog
====================
Version 0.34
------------
Released on 2016-12-21.
Bug fixes:
* `#398 <https://github.com/Kozea/WeasyPrint/issues/398>`_:
Honor the presentational_hints option for PDFs.
* `#399 <https://github.com/Kozea/WeasyPrint/pull/399>`_:
Avoid CairoSVG-2.0.0rc* on Python 2.
* `#396 <https://github.com/Kozea/WeasyPrint/issues/396>`_:
Correctly close files open by mkstemp.
* `#403 <https://github.com/Kozea/WeasyPrint/issues/403>`_:
Cast the number of columns into int.
* Fix multi-page multi-columns and add related tests.
Version 0.33
------------

View File

@ -33,12 +33,19 @@ Some elements need special treatment:
or in SVG with CairoSVG_. SVG images are not rasterized but rendered
as vectors in the PDF output.
HTML `presentational hints`_ are not supported by default, but can be supported:
HTML `presentational hints`_ are not supported by default, but most of them can
be supported:
* by using the ``--presentational-hints`` CLI parameter, or
* by setting the ``presentational_hints`` parameter of the ``HTML.render`` or
``HTML.write_*`` methods to ``True``.
Presentational hints include a wide array of attributes that direct styling in
HTML, including font ``color`` and ``size``, list attributes like ``type`` and
``start``, various table alignment attributes, and others. If the document
generated by WeasyPrint is missing some of the features you expect from the
HTML, try to enable this option.
.. _CairoSVG: http://cairosvg.org/
.. _GdkPixbuf: https://live.gnome.org/GdkPixbuf
.. _presentational hints: http://www.w3.org/TR/html5/rendering.html#presentational-hints

View File

@ -37,7 +37,7 @@ REQUIREMENTS = [
]
if sys.version_info < (3,):
REQUIREMENTS.append('CairoSVG >= 1.0.20, < 2')
REQUIREMENTS.append('CairoSVG >= 1.0.20, < 2.0.0')
else:
REQUIREMENTS.append('CairoSVG >= 1.0.20')

View File

@ -19,7 +19,7 @@ import contextlib # noqa
import html5lib # noqa
VERSION = '0.33'
VERSION = '0.34'
__version__ = VERSION
# Used for 'User-Agent' in HTTP and 'Creator' in PDF
@ -175,8 +175,10 @@ class HTML(object):
:obj:`target`.)
"""
return self.render(stylesheets, presentational_hints).write_pdf(
target, zoom, attachments)
return self.render(
stylesheets, enable_hinting=False,
presentational_hints=presentational_hints).write_pdf(
target, zoom, attachments)
def write_image_surface(self, stylesheets=None, resolution=96,
presentational_hints=False):

View File

@ -225,9 +225,9 @@ else:
**font_features).items():
features_string += '<string>%s %s</string>' % (
key, value)
_, filename = tempfile.mkstemp()
with open(filename, 'wb') as fd:
fd.write(font)
fd, filename = tempfile.mkstemp()
os.write(fd, font)
os.close(fd)
self._filenames.append(filename)
xml = '''<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
@ -266,10 +266,10 @@ else:
FONTCONFIG_STRETCH_CONSTANTS[
rule_descriptors.get('font_stretch', 'normal')],
filename, features_string)
_, conf_filename = tempfile.mkstemp()
with open(conf_filename, 'wb') as fd:
# TODO: coding is OK for <test> but what about <edit>?
fd.write(xml.encode(FILESYSTEM_ENCODING))
fd, conf_filename = tempfile.mkstemp()
# TODO: coding is OK for <test> but what about <edit>?
os.write(fd, xml.encode(FILESYSTEM_ENCODING))
os.close(fd)
self._filenames.append(conf_filename)
fontconfig.FcConfigParseAndLoad(
config, conf_filename.encode(FILESYSTEM_ENCODING),

View File

@ -22,7 +22,6 @@ from .markers import list_marker_layout
from .min_max import handle_min_max_width
from .tables import table_layout, table_wrapper_width
from .percentages import resolve_percentages, resolve_position_percentages
from .preferred import shrink_to_fit
from ..formatting_structure import boxes
from ..compat import xrange, izip
@ -121,36 +120,28 @@ def columns_layout(context, box, max_position_y, skip_stack, containing_block,
# New containing block, use a new absolute list
absolute_boxes = []
box = box.copy_with_children(box.children)
# TODO: the available width can be unknown if the containing block needs
# the size of this block to know its own size.
block_level_width(box, containing_block)
available_width = box.width
if available_width == 'auto':
if style.column_count == 'auto':
count = 1
width = box.width
elif style.column_width != 'auto':
count = style.column_count
width = style.column_width
else:
# TODO: replace with real shrink-to-fit
available_width = shrink_to_fit(context, box, float('inf'))
if count is None:
if style.column_width == 'auto' and style.column_count != 'auto':
count = style.column_count
width = max(
0, available_width - (count - 1) * style.column_gap) / count
elif style.column_width != 'auto' and style.column_count == 'auto':
count = max(1, floor(
count = max(1, int(floor(
(available_width + style.column_gap) /
(style.column_width + style.column_gap)))
(style.column_width + style.column_gap))))
width = (
(available_width + style.column_gap) / count -
style.column_gap)
else:
count = min(style.column_count, floor(
count = min(style.column_count, int(floor(
(available_width + style.column_gap) /
(style.column_width + style.column_gap)))
(style.column_width + style.column_gap))))
width = (
(available_width + style.column_gap) / count -
style.column_gap)
@ -191,7 +182,7 @@ def columns_layout(context, box, max_position_y, skip_stack, containing_block,
# Find the total height of the content
original_max_position_y = max_position_y
column_box = create_column_box()
new_child, new_skip_stack, _, _, _ = block_box_layout(
new_child, _, _, _, _ = block_box_layout(
context, column_box, float('inf'), skip_stack, containing_block,
device_size, page_is_empty, [], [], [])
height = new_child.margin_height()
@ -221,19 +212,24 @@ def columns_layout(context, box, max_position_y, skip_stack, containing_block,
# Replace the current box children with columns
children = []
for i in range(count):
if i == count - 1:
max_position_y = original_max_position_y
column_box = create_column_box()
column_box.position_x += i * (width + style.column_gap)
new_child, skip_stack, next_page, _, _ = block_box_layout(
context, column_box, max_position_y, skip_stack, containing_block,
device_size, page_is_empty, absolute_boxes, fixed_boxes, None)
if new_child is None:
break
children.append(new_child)
if skip_stack is None:
break
if box.children:
for i in range(count):
if i == count - 1:
max_position_y = original_max_position_y
column_box = create_column_box()
column_box.position_x += i * (width + style.column_gap)
new_child, skip_stack, next_page, _, _ = block_box_layout(
context, column_box, max_position_y, skip_stack,
containing_block, device_size, page_is_empty, absolute_boxes,
fixed_boxes, None)
if new_child is None:
break
children.append(new_child)
if skip_stack is None:
break
else:
next_page = 'any'
skip_stack = None
# Set the height of box and the columns
box.children = children
@ -254,7 +250,7 @@ def columns_layout(context, box, max_position_y, skip_stack, containing_block,
for absolute_box in absolute_boxes:
absolute_layout(context, absolute_box, box, fixed_boxes)
return box, new_skip_stack, next_page, [0], False
return box, skip_stack, next_page, [0], False
@handle_min_max_width

View File

@ -2883,3 +2883,53 @@ def test_radial_gradients():
assert (pixel(3, 9) not in (B, r)) ^ thin
assert (pixel(7, 5) not in (B, r)) ^ thin
assert (pixel(7, 9) not in (B, r)) ^ thin
def test_column_rule():
"""Test standard cases for column-rule-*."""
# JPG is lossy...
b = as_pixel(b'\x00\x00\xfe\xff')
assert_pixels('solid', 5, 3, [
b+_+r+_+b,
b+_+r+_+b,
_+_+_+_+_,
], '''
<style>
img { display: inline-block; width: 1px; height: 1px }
div { columns: 2; column-rule-style: solid;
column-rule-width: 1px; column-gap: 3px;
column-rule-color: red }
body { margin: 0; font-size: 0; background: white}
@page { margin: 0; size: 5px 3px }
</style>
<div>
<img src=blue.jpg>
<img src=blue.jpg>
<img src=blue.jpg>
<img src=blue.jpg>
</div>
''')
assert_pixels('dotted', 5, 3, [
b+_+r+_+b,
b+_+_+_+b,
b+_+r+_+b,
], '''
<style>
img { display: inline-block; width: 1px; height: 1px }
div { columns: 2; column-rule-style: dotted;
column-rule-width: 1px; column-gap: 3px;
column-rule-color: red }
body { margin: 0; font-size: 0; background: white}
@page { margin: 0; size: 5px 3px }
</style>
<div>
<img src=blue.jpg>
<img src=blue.jpg>
<img src=blue.jpg>
<img src=blue.jpg>
<img src=blue.jpg>
<img src=blue.jpg>
</div>
''')

View File

@ -3535,3 +3535,208 @@ def test_shrink_to_fit_floating_point_error():
break
else:
letters += 1
@assert_no_logs
def test_columns():
"""Test standard cases for column-width, column-count and columns."""
for css in (
'columns: 4',
'columns: 100px',
'columns: 4 100px',
'columns: 100px 4',
'column-width: 100px',
'column-count: 4'):
page, = parse('''
<style>
div { %s; column-gap: 0 }
body { margin: 0; font-family: "ahem" }
@page { margin: 0; size: 400px 1000px }
</style>
<div>
Ipsum dolor sit amet,
consectetur adipiscing elit.
Sed sollicitudin nibh
et turpis molestie tristique.
</div>
''' % css)
html, = page.children
body, = html.children
div, = body.children
columns = div.children
assert len(columns) == 4
assert [column.width for column in columns] == [100, 100, 100, 100]
assert [column.position_x for column in columns] == [0, 100, 200, 300]
assert [column.position_y for column in columns] == [0, 0, 0, 0]
def test_column_gap():
"""Test standard cases for column-gap."""
for value, width in {
'normal': 16, # "normal" is 1em = 16px
'unknown': 16, # default value is normal
'15px': 15,
'40%': 16, # percentages are not allowed
'-1em': 16, # negative values are not allowed
}.items():
page, = parse('''
<style>
div { columns: 3; column-gap: %s }
body { margin: 0; font-family: "ahem" }
@page { margin: 0; size: 300px 1000px }
</style>
<div>
Ipsum dolor sit amet,
consectetur adipiscing elit.
Sed sollicitudin nibh
et turpis molestie tristique.
</div>
''' % value)
html, = page.children
body, = html.children
div, = body.children
columns = div.children
assert len(columns) == 3
assert [column.width for column in columns] == (
3 * [100 - 2 * width / 3])
assert [column.position_x for column in columns] == (
[0, 100 + width / 3, 200 + 2 * width / 3])
assert [column.position_y for column in columns] == [0, 0, 0]
@assert_no_logs
def test_columns_multipage():
"""Test columns split among multiple pages."""
page1, page2 = parse('''
<style>
div { columns: 2; column-gap: 1px }
body { margin: 0; font-family: "ahem";
font-size: 1px; line-height: 1px }
@page { margin: 0; size: 3px 2px }
</style>
<div>a b c d e f g</div>
''')
html, = page1.children
body, = html.children
div, = body.children
columns = div.children
assert len(columns) == 2
assert len(columns[0].children) == 2
assert len(columns[1].children) == 2
columns[0].children[0].children[0].text == 'a'
columns[0].children[1].children[0].text == 'b'
columns[1].children[0].children[0].text == 'c'
columns[1].children[1].children[0].text == 'd'
html, = page2.children
body, = html.children
div, = body.children
columns = div.children
assert len(columns) == 2
assert len(columns[0].children) == 2
assert len(columns[1].children) == 1
columns[0].children[0].children[0].text == 'e'
columns[0].children[1].children[0].text == 'f'
columns[1].children[0].children[0].text == 'g'
@assert_no_logs
def test_columns_not_enough_content():
"""Test when there are too many columns."""
page, = parse('''
<style>
div { columns: 5; column-gap: 0 }
body { margin: 0; font-family: "ahem" }
@page { margin: 0; size: 5px; font-size: 1px }
</style>
<div>a b c</div>
''')
html, = page.children
body, = html.children
div, = body.children
assert div.width == 5
columns = div.children
assert len(columns) == 3
assert [column.width for column in columns] == [1, 1, 1]
assert [column.position_x for column in columns] == [0, 1, 2]
assert [column.position_y for column in columns] == [0, 0, 0]
@assert_no_logs
def test_columns_empty():
"""Test when there's no content in columns."""
page, = parse('''
<style>
div { columns: 3 }
body { margin: 0; font-family: "ahem" }
@page { margin: 0; size: 3px; font-size: 1px }
</style>
<div></div>
''')
html, = page.children
body, = html.children
div, = body.children
assert div.width == 3
assert div.height == 0
columns = div.children
assert len(columns) == 0
@assert_no_logs
def test_columns_fixed_height():
"""Test columns with fixed height."""
# TODO: we should test when the height is too small
for prop in ('height', 'min-height'):
page, = parse('''
<style>
div { columns: 4; column-gap: 0; %s: 10px }
body { margin: 0; font-family: "ahem"; line-height: 1px }
@page { margin: 0; size: 4px 50px; font-size: 1px }
</style>
<div>a b c</div>
''' % prop)
html, = page.children
body, = html.children
div, = body.children
assert div.width == 4
columns = div.children
assert len(columns) == 3
assert [column.width for column in columns] == [1, 1, 1]
assert [column.height for column in columns] == [10, 10, 10]
assert [column.position_x for column in columns] == [0, 1, 2]
assert [column.position_y for column in columns] == [0, 0, 0]
@assert_no_logs
def test_columns_relative():
"""Test columns with position: relative."""
page, = parse('''
<style>
article { position: absolute; top: 3px }
div { columns: 4; column-gap: 0; position: relative;
top: 1px; left: 2px }
body { margin: 0; font-family: "ahem"; line-height: 1px }
@page { margin: 0; size: 4px 50px; font-size: 1px }
</style>
<div>a b c<article>d</article></div>
''')
html, = page.children
body, = html.children
div, = body.children
assert div.width == 4
column1, column2, column3, absolute_column = div.children
columns = column1, column2, column3
assert [column.width for column in columns] == [1, 1, 1]
assert [column.position_x for column in columns] == [2, 3, 4]
assert [column.position_y for column in columns] == [1, 1, 1]
absolute_line, = absolute_column.children
span, = absolute_line.children
assert span.position_x == 5 # Default position of the 4th column
assert span.position_y == 4 # div's 1px + span's 3px