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:
commit
e222779f0a
18
CHANGES
18
CHANGES
@ -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
|
||||
------------
|
||||
|
||||
|
@ -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
|
||||
|
2
setup.py
2
setup.py
@ -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')
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
''')
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user