mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-04 16:07:57 +03:00
Fix shrink-to-fit in PNG: have the same hinting as in layout.
This commit is contained in:
parent
972045c63f
commit
3cd540eaa9
@ -119,7 +119,7 @@ def absolute_block(document, box, containing_block):
|
||||
box.margin_right = 0
|
||||
available_width = cb_width - (
|
||||
padding_plus_borders_x + box.margin_left + box.margin_right)
|
||||
box.width = shrink_to_fit(box, available_width)
|
||||
box.width = shrink_to_fit(document, box, available_width)
|
||||
elif left != 'auto' and right != 'auto' and width != 'auto':
|
||||
width_for_margins = cb_width - (
|
||||
right + left + padding_plus_borders_x)
|
||||
@ -143,13 +143,14 @@ def absolute_block(document, box, containing_block):
|
||||
box.margin_right = 0
|
||||
spacing = padding_plus_borders_x + box.margin_left + box.margin_right
|
||||
if left == width == 'auto':
|
||||
box.width = shrink_to_fit(box, cb_width - spacing - right)
|
||||
box.width = shrink_to_fit(
|
||||
document, box, cb_width - spacing - right)
|
||||
translate_x = cb_width - right - spacing + default_translate_x
|
||||
translate_box_width = True
|
||||
elif left == right == 'auto':
|
||||
pass # Keep the static position
|
||||
elif width == right == 'auto':
|
||||
box.width = shrink_to_fit(box, cb_width - spacing - left)
|
||||
box.width = shrink_to_fit(document, box, cb_width - spacing - left)
|
||||
translate_x = left + default_translate_x
|
||||
elif left == 'auto':
|
||||
translate_x = (
|
||||
|
@ -57,8 +57,9 @@ def block_box_layout(document, box, max_position_y, skip_stack,
|
||||
"""Lay out the block ``box``."""
|
||||
resolve_percentages(box, containing_block)
|
||||
if box.is_table_wrapper:
|
||||
table_wrapper_width(box, (containing_block.width,
|
||||
containing_block.height), absolute_boxes)
|
||||
table_wrapper_width(
|
||||
document, box, (containing_block.width, containing_block.height),
|
||||
absolute_boxes)
|
||||
block_level_width(box, containing_block)
|
||||
new_box, resume_at, next_page, adjoining_margins, collapsing_through = \
|
||||
block_container_layout(
|
||||
|
@ -407,8 +407,10 @@ def atomic_box(document, box, position_x, skip_stack, containing_block,
|
||||
box.baseline = box.margin_height()
|
||||
elif isinstance(box, boxes.InlineBlockBox):
|
||||
if box.is_table_wrapper:
|
||||
table_wrapper_width(box, (containing_block.width,
|
||||
containing_block.height), absolute_boxes)
|
||||
table_wrapper_width(
|
||||
document, box,
|
||||
(containing_block.width, containing_block.height),
|
||||
absolute_boxes)
|
||||
box = inline_block_box_layout(
|
||||
document, box, position_x, skip_stack, containing_block,
|
||||
device_size, absolute_boxes)
|
||||
@ -430,7 +432,7 @@ def inline_block_box_layout(document, box, position_x, skip_stack,
|
||||
if box.margin_right == 'auto':
|
||||
box.margin_right = 0
|
||||
|
||||
inline_block_width(box, containing_block)
|
||||
inline_block_width(box, document, containing_block)
|
||||
|
||||
box.position_x = position_x
|
||||
box.position_y = 0
|
||||
@ -458,9 +460,9 @@ def inline_block_baseline(box):
|
||||
|
||||
|
||||
@handle_min_max_width
|
||||
def inline_block_width(box, containing_block):
|
||||
def inline_block_width(box, document, containing_block):
|
||||
if box.width == 'auto':
|
||||
box.width = shrink_to_fit(box, containing_block.width)
|
||||
box.width = shrink_to_fit(document, box, containing_block.width)
|
||||
|
||||
|
||||
def split_inline_level(document, box, position_x, max_x, skip_stack,
|
||||
|
@ -22,7 +22,8 @@ from .variable_margin_dimension import with_rule_2
|
||||
|
||||
|
||||
class VerticalBox(object):
|
||||
def __init__(self, box):
|
||||
def __init__(self, document, box):
|
||||
self.document = document
|
||||
self.box = box
|
||||
# Inner dimension: that of the content area, as opposed to the
|
||||
# outer dimension: that of the margin area.
|
||||
@ -50,7 +51,8 @@ class VerticalBox(object):
|
||||
|
||||
|
||||
class HorizontalBox(object):
|
||||
def __init__(self, box):
|
||||
def __init__(self, document, box):
|
||||
self.document = document
|
||||
self.box = box
|
||||
self.inner = box.width
|
||||
self.margin_a = box.margin_left
|
||||
@ -71,17 +73,18 @@ class HorizontalBox(object):
|
||||
def minimum(self):
|
||||
if self._minimum is None:
|
||||
self._minimum = inline_preferred_minimum_width(
|
||||
self.box, outer=False)
|
||||
self.document, self.box, outer=False)
|
||||
return self._minimum
|
||||
|
||||
@property
|
||||
def preferred(self):
|
||||
if self._preferred is None:
|
||||
self._preferred = inline_preferred_width(self.box, outer=False)
|
||||
self._preferred = inline_preferred_width(
|
||||
self.document, self.box, outer=False)
|
||||
return self._preferred
|
||||
|
||||
|
||||
def compute_fixed_dimension(box, outer, vertical, top_or_left):
|
||||
def compute_fixed_dimension(document, box, outer, vertical, top_or_left):
|
||||
"""
|
||||
Compute and set a margin box fixed dimension on ``box``, as described in:
|
||||
http://dev.w3.org/csswg/css3-page/#margin-constraints
|
||||
@ -99,7 +102,7 @@ def compute_fixed_dimension(box, outer, vertical, top_or_left):
|
||||
This determines which margin should be 'auto' if the values are
|
||||
over-constrained. (Rule 3 of the algorithm.)
|
||||
"""
|
||||
box = (VerticalBox if vertical else HorizontalBox)(box)
|
||||
box = (VerticalBox if vertical else HorizontalBox)(document, box)
|
||||
|
||||
# Rule 2
|
||||
total = box.padding_plus_border + sum(
|
||||
@ -172,7 +175,7 @@ def compute_variable_dimension(document, side_boxes, vertical, outer_sum):
|
||||
|
||||
"""
|
||||
box_class = VerticalBox if vertical else HorizontalBox
|
||||
side_boxes = [box_class(box) for box in side_boxes]
|
||||
side_boxes = [box_class(document, box) for box in side_boxes]
|
||||
box_a, box_b, box_c = side_boxes
|
||||
|
||||
num_auto_margins = sum(
|
||||
@ -378,7 +381,8 @@ def make_margin_boxes(document, page, counter_values):
|
||||
if not box.exists:
|
||||
continue
|
||||
compute_fixed_dimension(
|
||||
box, fixed_outer, not vertical, prefix in ['top', 'left'])
|
||||
document, box, fixed_outer, not vertical,
|
||||
prefix in ['top', 'left'])
|
||||
box.position_x = position_x
|
||||
box.position_y = position_y
|
||||
if vertical:
|
||||
@ -404,8 +408,10 @@ def make_margin_boxes(document, page, counter_values):
|
||||
continue
|
||||
box.position_x = position_x
|
||||
box.position_y = position_y
|
||||
compute_fixed_dimension(box, cb_height, True, 'top' in at_keyword)
|
||||
compute_fixed_dimension(box, cb_width, False, 'left' in at_keyword)
|
||||
compute_fixed_dimension(
|
||||
document, box, cb_height, True, 'top' in at_keyword)
|
||||
compute_fixed_dimension(
|
||||
document, box, cb_width, False, 'left' in at_keyword)
|
||||
generated_boxes.append(box)
|
||||
|
||||
generated_boxes.extend(delayed_boxes)
|
||||
|
@ -18,7 +18,7 @@ from ..formatting_structure import boxes
|
||||
from ..text import TextFragment
|
||||
|
||||
|
||||
def shrink_to_fit(box, available_width):
|
||||
def shrink_to_fit(document, box, available_width):
|
||||
"""Return the shrink-to-fit width of ``box``.
|
||||
|
||||
*Warning:* both available_outer_width and the return value are
|
||||
@ -28,11 +28,13 @@ def shrink_to_fit(box, available_width):
|
||||
|
||||
"""
|
||||
return min(
|
||||
max(preferred_minimum_width(box, outer=False), available_width),
|
||||
preferred_width(box, outer=False))
|
||||
max(
|
||||
preferred_minimum_width(document, box, outer=False),
|
||||
available_width),
|
||||
preferred_width(document, box, outer=False))
|
||||
|
||||
|
||||
def preferred_minimum_width(box, outer=True):
|
||||
def preferred_minimum_width(document, box, outer=True):
|
||||
"""Return the preferred minimum width for ``box``.
|
||||
|
||||
This is the width by breaking at every line-break opportunity.
|
||||
@ -40,11 +42,11 @@ def preferred_minimum_width(box, outer=True):
|
||||
"""
|
||||
if isinstance(box, boxes.BlockContainerBox):
|
||||
if box.is_table_wrapper:
|
||||
return table_preferred_minimum_width(box, outer)
|
||||
return table_preferred_minimum_width(document, box, outer)
|
||||
else:
|
||||
return block_preferred_minimum_width(box, outer)
|
||||
return block_preferred_minimum_width(document, box, outer)
|
||||
elif isinstance(box, (boxes.InlineBox, boxes.LineBox)):
|
||||
return inline_preferred_minimum_width(box, outer)
|
||||
return inline_preferred_minimum_width(document, box, outer)
|
||||
elif isinstance(box, boxes.ReplacedBox):
|
||||
return replaced_preferred_width(box, outer)
|
||||
else:
|
||||
@ -53,7 +55,7 @@ def preferred_minimum_width(box, outer=True):
|
||||
type(box).__name__)
|
||||
|
||||
|
||||
def preferred_width(box, outer=True):
|
||||
def preferred_width(document, box, outer=True):
|
||||
"""Return the preferred width for ``box``.
|
||||
|
||||
This is the width by only breaking at forced line breaks.
|
||||
@ -61,11 +63,11 @@ def preferred_width(box, outer=True):
|
||||
"""
|
||||
if isinstance(box, boxes.BlockContainerBox):
|
||||
if box.is_table_wrapper:
|
||||
return table_preferred_width(box, outer)
|
||||
return table_preferred_width(document, box, outer)
|
||||
else:
|
||||
return block_preferred_width(box, outer)
|
||||
return block_preferred_width(document, box, outer)
|
||||
elif isinstance(box, (boxes.InlineBox, boxes.LineBox)):
|
||||
return inline_preferred_width(box, outer)
|
||||
return inline_preferred_width(document, box, outer)
|
||||
elif isinstance(box, boxes.ReplacedBox):
|
||||
return replaced_preferred_width(box, outer)
|
||||
else:
|
||||
@ -73,7 +75,7 @@ def preferred_width(box, outer=True):
|
||||
'Preferred width for %s not handled yet' % type(box).__name__)
|
||||
|
||||
|
||||
def _block_preferred_width(box, function, outer):
|
||||
def _block_preferred_width(document, box, function, outer):
|
||||
"""Helper to create ``block_preferred_*_width.``"""
|
||||
width = box.style.width
|
||||
if width == 'auto' or width.unit == '%':
|
||||
@ -81,7 +83,7 @@ def _block_preferred_width(box, function, outer):
|
||||
# though they were the following: width: auto"
|
||||
# http://dbaron.org/css/intrinsic/#outer-intrinsic
|
||||
children_widths = [
|
||||
function(child, outer=True) for child in box.children
|
||||
function(document, child, outer=True) for child in box.children
|
||||
if child.is_in_normal_flow()]
|
||||
width = max(children_widths) if children_widths else 0
|
||||
else:
|
||||
@ -129,17 +131,18 @@ def adjust(box, outer, width):
|
||||
return 0
|
||||
|
||||
|
||||
def block_preferred_minimum_width(box, outer=True):
|
||||
def block_preferred_minimum_width(document, box, outer=True):
|
||||
"""Return the preferred minimum width for a ``BlockBox``."""
|
||||
return _block_preferred_width(box, preferred_minimum_width, outer)
|
||||
return _block_preferred_width(
|
||||
document, box, preferred_minimum_width, outer)
|
||||
|
||||
|
||||
def block_preferred_width(box, outer=True):
|
||||
def block_preferred_width(document, box, outer=True):
|
||||
"""Return the preferred width for a ``BlockBox``."""
|
||||
return _block_preferred_width(box, preferred_width, outer)
|
||||
return _block_preferred_width(document, box, preferred_width, outer)
|
||||
|
||||
|
||||
def inline_preferred_minimum_width(box, outer=True):
|
||||
def inline_preferred_minimum_width(document, box, outer=True):
|
||||
"""Return the preferred minimum width for an ``InlineBox``."""
|
||||
widest_line = 0
|
||||
for child in box.children:
|
||||
@ -151,20 +154,20 @@ def inline_preferred_minimum_width(box, outer=True):
|
||||
current_line = replaced_preferred_width(child)
|
||||
elif isinstance(child, boxes.InlineBlockBox):
|
||||
if child.is_table_wrapper:
|
||||
current_line = table_preferred_minimum_width(child)
|
||||
current_line = table_preferred_minimum_width(document, child)
|
||||
else:
|
||||
current_line = block_preferred_minimum_width(child)
|
||||
current_line = block_preferred_minimum_width(document, child)
|
||||
elif isinstance(child, boxes.InlineBox):
|
||||
# TODO: handle forced line breaks
|
||||
current_line = inline_preferred_minimum_width(child)
|
||||
current_line = inline_preferred_minimum_width(document, child)
|
||||
else:
|
||||
assert isinstance(child, boxes.TextBox)
|
||||
current_line = max(text_lines_width(child, width=0))
|
||||
current_line = max(text_lines_width(document, child, width=0))
|
||||
widest_line = max(widest_line, current_line)
|
||||
return adjust(box, outer, widest_line)
|
||||
|
||||
|
||||
def inline_preferred_width(box, outer=True):
|
||||
def inline_preferred_width(document, box, outer=True):
|
||||
"""Return the preferred width for an ``InlineBox``."""
|
||||
widest_line = 0
|
||||
current_line = 0
|
||||
@ -177,15 +180,15 @@ def inline_preferred_width(box, outer=True):
|
||||
current_line += replaced_preferred_width(child)
|
||||
elif isinstance(child, boxes.InlineBlockBox):
|
||||
if child.is_table_wrapper:
|
||||
current_line += table_preferred_width(child)
|
||||
current_line += table_preferred_width(document, child)
|
||||
else:
|
||||
current_line += block_preferred_width(child)
|
||||
current_line += block_preferred_width(document, child)
|
||||
elif isinstance(child, boxes.InlineBox):
|
||||
# TODO: handle forced line breaks
|
||||
current_line += inline_preferred_width(child)
|
||||
current_line += inline_preferred_width(document, child)
|
||||
else:
|
||||
assert isinstance(child, boxes.TextBox)
|
||||
lines = list(text_lines_width(child, width=None))
|
||||
lines = list(text_lines_width(document, child, width=None))
|
||||
assert lines
|
||||
# The first text line goes on the current line
|
||||
current_line += lines[0]
|
||||
@ -200,7 +203,7 @@ def inline_preferred_width(box, outer=True):
|
||||
return adjust(box, outer, widest_line)
|
||||
|
||||
|
||||
def table_and_columns_preferred_widths(box, outer=True,
|
||||
def table_and_columns_preferred_widths(document, box, outer=True,
|
||||
resolved_table_width=False):
|
||||
"""Return preferred widths for the table and its columns.
|
||||
|
||||
@ -251,9 +254,9 @@ def table_and_columns_preferred_widths(box, outer=True,
|
||||
if cell:
|
||||
# TODO: when border-collapse: collapse; set outer=False
|
||||
column_preferred_widths[j][i] = \
|
||||
preferred_width(cell)
|
||||
preferred_width(document, cell)
|
||||
column_preferred_minimum_widths[j][i] = \
|
||||
preferred_minimum_width(cell)
|
||||
preferred_minimum_width(document, cell)
|
||||
|
||||
column_preferred_widths = [
|
||||
max(widths) if widths else 0
|
||||
@ -288,7 +291,7 @@ def table_and_columns_preferred_widths(box, outer=True,
|
||||
|
||||
# TODO: when border-collapse: collapse; set outer=False
|
||||
cell_width = (
|
||||
preferred_width(cell) -
|
||||
preferred_width(document, cell) -
|
||||
table.style.border_spacing[0] * (cell.colspan - 1))
|
||||
columns_width = sum(column_preferred_widths[column_slice])
|
||||
if cell_width > columns_width:
|
||||
@ -298,7 +301,7 @@ def table_and_columns_preferred_widths(box, outer=True,
|
||||
|
||||
# TODO: when border-collapse: collapse; set outer=False
|
||||
cell_minimum_width = (
|
||||
preferred_minimum_width(cell) -
|
||||
preferred_minimum_width(document, cell) -
|
||||
table.style.border_spacing[0] * (cell.colspan - 1))
|
||||
columns_minimum_width = sum(
|
||||
column_preferred_minimum_widths[column_slice])
|
||||
@ -345,7 +348,7 @@ def table_and_columns_preferred_widths(box, outer=True,
|
||||
|
||||
if captions:
|
||||
caption_width = max(
|
||||
preferred_minimum_width(caption) for caption in captions)
|
||||
preferred_minimum_width(document, caption) for caption in captions)
|
||||
else:
|
||||
caption_width = 0
|
||||
|
||||
@ -370,22 +373,21 @@ def table_and_columns_preferred_widths(box, outer=True,
|
||||
column_preferred_minimum_widths, column_preferred_widths)
|
||||
|
||||
|
||||
def table_preferred_minimum_width(box, outer=True):
|
||||
def table_preferred_minimum_width(document, box, outer=True):
|
||||
"""Return the preferred minimum width for a ``TableBox``. wrapper"""
|
||||
minimum_width, _, _, _ = table_and_columns_preferred_widths(box)
|
||||
minimum_width, _, _, _ = table_and_columns_preferred_widths(document, box)
|
||||
return adjust(box, outer, minimum_width)
|
||||
|
||||
|
||||
def table_preferred_width(box, outer=True):
|
||||
def table_preferred_width(document, box, outer=True):
|
||||
"""Return the preferred width for a ``TableBox`` wrapper."""
|
||||
_, width, _, _ = table_and_columns_preferred_widths(box)
|
||||
_, width, _, _ = table_and_columns_preferred_widths(document, box)
|
||||
return adjust(box, outer, width)
|
||||
|
||||
|
||||
def text_lines_width(box, width):
|
||||
def text_lines_width(document, box, width):
|
||||
"""Return the list of line widths for a ``TextBox``."""
|
||||
# TODO: find the real surface, to have correct hinting
|
||||
context = cairo.Context(cairo.PDFSurface(None, 1, 1))
|
||||
context = cairo.Context(document.surface)
|
||||
fragment = TextFragment(box.text, box.style, context, width=width)
|
||||
return fragment.line_widths()
|
||||
|
||||
|
@ -447,7 +447,7 @@ def fixed_table_layout(box, absolute_boxes):
|
||||
table.column_widths = column_widths
|
||||
|
||||
|
||||
def auto_table_layout(box, containing_block, absolute_boxes):
|
||||
def auto_table_layout(document, box, containing_block, absolute_boxes):
|
||||
"""Run the auto table layout and return a list of column widths.
|
||||
|
||||
http://www.w3.org/TR/CSS21/tables.html#auto-table-layout
|
||||
@ -456,7 +456,8 @@ def auto_table_layout(box, containing_block, absolute_boxes):
|
||||
table = box.get_wrapped_table()
|
||||
(table_preferred_minimum_width, table_preferred_width,
|
||||
column_preferred_minimum_widths, column_preferred_widths) = \
|
||||
table_and_columns_preferred_widths(box, resolved_table_width=True)
|
||||
table_and_columns_preferred_widths(
|
||||
document, box, resolved_table_width=True)
|
||||
|
||||
all_border_spacing = (
|
||||
table.style.border_spacing[0] * (len(column_preferred_widths) + 1))
|
||||
@ -503,7 +504,7 @@ def auto_table_layout(box, containing_block, absolute_boxes):
|
||||
for column_width in table.column_widths]
|
||||
|
||||
|
||||
def table_wrapper_width(wrapper, containing_block, absolute_boxes):
|
||||
def table_wrapper_width(document, wrapper, containing_block, absolute_boxes):
|
||||
"""Find the width of each column and derive the wrapper width."""
|
||||
table = wrapper.get_wrapped_table()
|
||||
resolve_percentages(table, containing_block)
|
||||
@ -511,7 +512,7 @@ def table_wrapper_width(wrapper, containing_block, absolute_boxes):
|
||||
if table.style.table_layout == 'fixed' and table.width != 'auto':
|
||||
fixed_table_layout(wrapper, absolute_boxes)
|
||||
else:
|
||||
auto_table_layout(wrapper, containing_block, absolute_boxes)
|
||||
auto_table_layout(document, wrapper, containing_block, absolute_boxes)
|
||||
|
||||
wrapper.width = table.border_width()
|
||||
wrapper.style.width = Dimension(wrapper.width, 'px')
|
||||
|
@ -500,6 +500,21 @@ def test_inline_block_sizes():
|
||||
assert div_8.width == 185
|
||||
assert div_9.width == 10
|
||||
|
||||
# Previously, the hinting for in shrink-to-fit did not match that
|
||||
# of the layout, which often resulted in a line break just before
|
||||
# the last word.
|
||||
page, = parse('''
|
||||
<p style="display: inline-block">Lorem ipsum dolor sit amet …</p>
|
||||
''')
|
||||
html, = page.children
|
||||
body, = html.children
|
||||
outer_line, = body.children
|
||||
paragraph, = outer_line.children
|
||||
inner_lines = paragraph.children
|
||||
assert len(inner_lines) == 1
|
||||
text_box, = inner_lines[0].children
|
||||
assert text_box.text == 'Lorem ipsum dolor sit amet …'
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_inline_table():
|
||||
@ -2974,8 +2989,8 @@ def test_preferred_widths():
|
||||
text, = line.children
|
||||
assert text.text == '\nLorem ipsum dolor sit amet,\nconsectetur elit\n'
|
||||
|
||||
minimum = inline_preferred_minimum_width(line)
|
||||
preferred = inline_preferred_width(line)
|
||||
minimum = inline_preferred_minimum_width(document, line)
|
||||
preferred = inline_preferred_width(document, line)
|
||||
# Not exact, depends on the installed fonts
|
||||
assert 120 < minimum < 140
|
||||
assert 220 < preferred < 240
|
||||
|
Loading…
Reference in New Issue
Block a user