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:
parent
16c015a974
commit
26937a315b
@ -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 children’s 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)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user