1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-04 16:07:57 +03:00

Fix all the bugs for absolute positioning (maybe)

When an absolutly-positioned box is taken out of the flow,
leave a "placeholder" instead.

This should fix the bugs related to .translate() with absolutes.
This commit is contained in:
Simon Sapin 2012-05-25 12:03:48 +02:00
parent 16c015a974
commit 26937a315b
5 changed files with 105 additions and 83 deletions

View File

@ -17,11 +17,39 @@ from .tables import table_wrapper_width
from ..formatting_structure import boxes
def absolute_layout(document, box, containing_block):
class AbsolutePlaceholder(object):
"""Left where an absolutely-positioned box was taken out of the flow."""
def __init__(self, box):
# Work around the overloaded __setattr__
object.__setattr__(self, '_box', box)
object.__setattr__(self, '_layout_done', False)
def set_laid_out_box(self, new_box):
object.__setattr__(self, '_box', new_box)
object.__setattr__(self, '_layout_done', True)
def translate(self, dx=0, dy=0):
if self._layout_done:
self._box.translate(dx, dy)
else:
# Descendants do not have a position yet.
self._box.position_x += dx
self._box.position_y += dy
# Pretend to be the box itself
def __getattr__(self, name):
return getattr(self._box, name)
def __setattr__(self, name, value):
setattr(self._box, name, value)
def absolute_layout(document, placeholder, containing_block):
"""Set the width of absolute positioned ``box``."""
# TODO: avoid this (circular import)
from .blocks import block_container_layout
box = placeholder._box
resolve_percentages(box, containing_block)
resolve_position_percentages(box, containing_block)
@ -156,7 +184,7 @@ def absolute_layout(document, box, containing_block):
# TODO: handle absolute tables
assert isinstance(box, boxes.BlockBox)
# New containing block, use a new absolute list
# This box is the containing block for absolute descendants.
absolute_boxes = []
if box.is_table_wrapper:
@ -167,37 +195,16 @@ def absolute_layout(document, box, containing_block):
document, box, max_position_y=float('inf'), skip_stack=None,
device_size=None, page_is_empty=False,
absolute_boxes=absolute_boxes, adjoining_margins=None)
box.__dict__ = new_box.__dict__
list_marker_layout(document, box)
list_marker_layout(document, new_box)
for absolute_box in absolute_boxes:
absolute_layout(document, absolute_box, new_box)
for child_placeholder in absolute_boxes:
absolute_layout(document, child_placeholder, new_box)
if translate_box_width:
translate_x -= new_box.width
if translate_box_height:
translate_y -= new_box.height
translate_except_absolute(new_box, translate_x, translate_y)
new_box.translate(translate_x, translate_y)
def translate_except_absolute(box, dx=0, dy=0):
"""Change the position of the box, except its absolute descandants.
Also update the childrens positions accordingly.
"""
box.position_x += dx
box.position_y += dy
marker = getattr(box, 'outside_list_marker', None)
if marker:
translate_except_absolute(marker, dx, dy)
if box.style.position in ('static', 'relative'):
if isinstance(box, boxes.ParentBox):
for child in box.children:
translate_except_absolute(child, dx, dy)
if isinstance(box, boxes.TableBox):
for child in box.column_groups:
translate_except_absolute(child, dx, dy)
placeholder.set_laid_out_box(new_box)

View File

@ -12,7 +12,7 @@
from __future__ import division, unicode_literals
from .absolute import absolute_layout, translate_except_absolute
from .absolute import absolute_layout, AbsolutePlaceholder
from .inlines import (iter_line_boxes, replaced_box_width, replaced_box_height,
handle_min_max_width, min_max_replaced_height,
min_max_auto_replaced)
@ -174,7 +174,7 @@ def relative_positioning(box, containing_block):
else:
translate_y = 0
translate_except_absolute(box, translate_x, translate_y)
box.translate(translate_x, translate_y)
if isinstance(box, (boxes.InlineBox, boxes.LineBox)):
for child in box.children:
@ -232,13 +232,14 @@ def block_container_layout(document, box, max_position_y, skip_stack,
child.position_y = position_y
if not child.is_in_normal_flow():
if child.style.position == 'absolute':
if child.style.position in ('absolute', 'fixed'):
child.position_y += collapse_margin(adjoining_margins)
absolute_boxes.append(child)
new_children.append(child)
elif child.style.position == 'fixed':
child.position_y += collapse_margin(adjoining_margins)
document.fixed_boxes.append(child)
placeholder = AbsolutePlaceholder(child)
if child.style.position == 'absolute':
absolute_boxes.append(placeholder)
new_children.append(placeholder)
else:
document.fixed_boxes.append(placeholder)
else:
# TODO: Floats
new_children.append(child)
@ -327,7 +328,7 @@ def block_container_layout(document, box, max_position_y, skip_stack,
adjoining_margins.append(new_child.margin_top)
offset_y = (collapse_margin(adjoining_margins)
- new_child.margin_top)
translate_except_absolute(new_child, 0, offset_y)
new_child.translate(0, offset_y)
adjoining_margins = []
#else: blocks handle that themselves.

View File

@ -15,7 +15,7 @@ import functools
import cairo
from .absolute import absolute_layout, translate_except_absolute
from .absolute import absolute_layout, AbsolutePlaceholder
from .markers import image_marker_layout
from .percentages import resolve_percentages, resolve_one_percentage
from .preferred import shrink_to_fit
@ -71,12 +71,12 @@ def get_next_linebox(document, linebox, position_y, skip_stack,
if skip_stack == 'continue':
return None, None
new_absolute_boxes = []
line_placeholders = []
resolve_percentages(linebox, containing_block)
line, resume_at, preserved_line_break = split_inline_box(
document, linebox, position_x, max_x, skip_stack, containing_block,
device_size, new_absolute_boxes)
device_size, absolute_boxes, line_placeholders)
remove_last_whitespace(document, line)
@ -104,12 +104,10 @@ def get_next_linebox(document, linebox, position_y, skip_stack,
line.margin_bottom = 0
if offset_x != 0 or offset_y != 0:
# This also translates children
translate_except_absolute(line, offset_x, offset_y)
line.translate(offset_x, offset_y)
for absolute_box in new_absolute_boxes:
absolute_box.position_y = position_y
absolute_boxes.extend(new_absolute_boxes)
for placeholder in line_placeholders:
placeholder.position_y = position_y
return line, resume_at
@ -456,7 +454,8 @@ def inline_block_width(box, containing_block):
def split_inline_level(document, box, position_x, max_x, skip_stack,
containing_block, device_size, absolute_boxes):
containing_block, device_size, absolute_boxes,
line_placeholders):
"""Fit as much content as possible from an inline-level box in a width.
Return ``(new_box, resume_at)``. ``resume_at`` is ``None`` if all of the
@ -492,7 +491,7 @@ def split_inline_level(document, box, position_x, max_x, skip_stack,
box.margin_right = 0
new_box, resume_at, preserved_line_break = split_inline_box(
document, box, position_x, max_x, skip_stack, containing_block,
device_size, absolute_boxes)
device_size, absolute_boxes, line_placeholders)
elif isinstance(box, boxes.AtomicInlineLevelBox):
new_box = atomic_box(
document, box, position_x, skip_stack, containing_block,
@ -505,7 +504,8 @@ def split_inline_level(document, box, position_x, max_x, skip_stack,
def split_inline_box(document, box, position_x, max_x, skip_stack,
containing_block, device_size, absolute_boxes):
containing_block, device_size, absolute_boxes,
line_placeholders):
"""Same behavior as split_inline_level."""
initial_position_x = position_x
assert isinstance(box, (boxes.LineBox, boxes.InlineBox))
@ -527,27 +527,28 @@ def split_inline_box(document, box, position_x, max_x, skip_stack,
if box.style.position == 'relative':
absolute_boxes = []
line_placeholders = []
for index, child in box.enumerate_skip(skip):
child.position_y = box.position_y
if not child.is_in_normal_flow():
if child.style.position == 'absolute':
if child.style.position in ('absolute', 'fixed'):
child.position_x = position_x
child.position_y = 0
absolute_boxes.append(child)
children.append(child)
elif child.style.position == 'fixed':
child.position_x = position_x
child.position_y = 0
document.fixed_boxes.append(child)
placeholder = AbsolutePlaceholder(child)
line_placeholders.append(placeholder)
if child.style.position == 'absolute':
absolute_boxes.append(placeholder)
children.append(placeholder)
else:
document.fixed_boxes.append(placeholder)
else:
# TODO: Floats
children.append(child)
continue
child.position_y = box.position_y
new_child, resume_at, preserved = split_inline_level(
document, child, position_x, max_x, skip_stack,
containing_block, device_size, absolute_boxes)
containing_block, device_size, absolute_boxes, line_placeholders)
skip_stack = None
if preserved:
preserved_line_break = True

View File

@ -74,7 +74,8 @@ def _block_preferred_width(box, function, outer):
# though they were the following: width: auto"
# http://dbaron.org/css/intrinsic/#outer-intrinsic
if box.children:
width = max(function(child, outer=True) for child in box.children)
width = max(function(child, outer=True) for child in box.children
if child.is_in_normal_flow())
else:
width = 0
else:
@ -136,6 +137,9 @@ def inline_preferred_minimum_width(box, outer=True):
"""Return the preferred minimum width for an ``InlineBox``."""
widest_line = 0
for child in box.children:
if not child.is_in_normal_flow():
continue # Skip
if isinstance(child, boxes.InlineReplacedBox):
# Images are on their own line
current_line = replaced_preferred_width(child)
@ -156,6 +160,9 @@ def inline_preferred_width(box, outer=True):
widest_line = 0
current_line = 0
for child in box.children:
if not child.is_in_normal_flow():
continue # Skip
if isinstance(child, boxes.InlineReplacedBox):
# No line break around images
current_line += replaced_preferred_width(child)
@ -313,7 +320,8 @@ def table_and_columns_preferred_widths(box, outer=True,
sum(column_preferred_minimum_widths) + total_border_spacing)
table_preferred_width = sum(column_preferred_widths) + total_border_spacing
captions = [child for child in box.children if child != table]
captions = [child for child in box.children
if child is not table and child.is_in_normal_flow()]
if captions:
caption_width = max(

View File

@ -39,7 +39,7 @@ def parse_without_layout(html_content):
def parse(html_content, return_document=False):
"""Parse some HTML, apply stylesheets, transform to boxes and lay out."""
# TODO: remove this patching when asbolute and floats are validated
# TODO: remove this patching when floats are validated
with monkeypatch_validation(validate_float):
document = TestPNGDocument(html_content,
base_url=resource_filename('<inline HTML>'))
@ -1503,7 +1503,7 @@ def test_inlinebox_spliting():
while 1:
inlinebox.position_y = 0
box, skip, _ = split_inline_box(
document, inlinebox, 0, width, skip, parent, None, [])
document, inlinebox, 0, width, skip, parent, None, [], [])
yield box
if skip is None:
break
@ -1617,7 +1617,7 @@ def test_inlinebox_text_after_spliting():
while 1:
inlinebox.position_y = 0
box, skip, _ = split_inline_box(
document, inlinebox, 0, 100, skip, paragraph, None, [])
document, inlinebox, 0, 100, skip, paragraph, None, [], [])
parts.append(box)
if skip is None:
break
@ -3559,6 +3559,7 @@ def test_absolute_positioning():
page, = parse('''
<style>
@page { -weasy-size: 1000px 2000px }
html { font-size: 0 }
p { height: 20px }
</style>
@ -3567,7 +3568,8 @@ def test_absolute_positioning():
<p>2</p>
<p style="position: absolute; top: -5px; left: 5px">3</p>
<p style="margin: 3px">4</p>
<p style="position: relative; bottom: 5px; left: 5px;
<p style="position: absolute; bottom: 5px; right: 15px;
width: 50px; height: 10%;
padding: 3px; margin: 7px">5
<span>
<img src="pattern.png">
@ -3576,7 +3578,7 @@ def test_absolute_positioning():
width: 20px; height: 15px"></span>
</span>
</p>
<p>6</p>
<p style="margin-top: 8px">6</p>
</div>
<p>7</p>
''')
@ -3592,22 +3594,25 @@ def test_absolute_positioning():
assert (p2.position_x, p2.position_y) == (0, 20)
assert (p3.position_x, p3.position_y) == (5, -5)
assert (p4.position_x, p4.position_y) == (0, 40)
# p5 y = p4 y + p4 margin height - p5 bottom - margin collapsing
# = 40 + 26 - 5 - 3
# = 58
assert (p5.position_x, p5.position_y) == (5, 58)
assert (img.position_x, img.position_y) == (15, 68)
assert (span2.position_x, span2.position_y) == (19, 68)
# span3 x = p5 right - p5 right margin - span width - span right
# = 105 - 7 - 20 - 5
# = 73
# p5 x = page width - right - margin/padding/border - width
# = 1000 - 15 - 2 * 10 - 50
# = 915
# p5 y = page height - bottom - margin/padding/border - height
# = 2000 - 5 - 2 * 10 - 200
# = 1775
assert (p5.position_x, p5.position_y) == (915, 1775)
assert (img.position_x, img.position_y) == (925, 1785)
assert (span2.position_x, span2.position_y) == (929, 1785)
# span3 x = p5 right - p5 margin - span width - span right
# = 985 - 7 - 20 - 5
# = 953
# span3 y = p5 y + p5 margin top + span top
# = 58 + 7 + -10
# = 55
assert (span3.position_x, span3.position_y) == (73, 55)
# p6 y = p4 y + p4 margin height + p5 margin height - margin collapsing
# = 40 + 26 + 40 - 3
# = 103
assert (p6.position_x, p6.position_y) == (0, 103)
assert (p7.position_x, p7.position_y) == (0, 123)
assert div.height == 103
# = 1775 + 7 + -10
# = 1772
assert (span3.position_x, span3.position_y) == (953, 1772)
# p6 y = p4 y + p4 margin height - margin collapsing
# = 40 + 26 - 3
# = 63
assert (p6.position_x, p6.position_y) == (0, 63)
assert div.height == 71 # 20*3 + 2*3 + 8 - 3
assert (p7.position_x, p7.position_y) == (0, 91)