1
1
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:
Simon Sapin 2012-06-02 08:28:41 +02:00
parent 972045c63f
commit 3cd540eaa9
7 changed files with 94 additions and 66 deletions

View File

@ -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 = (

View File

@ -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(

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -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')

View File

@ -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