mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-05 08:27:22 +03:00
f6f9f144c2
Fix #1110.
229 lines
8.5 KiB
Python
229 lines
8.5 KiB
Python
"""
|
||
weasyprint.float
|
||
----------------
|
||
|
||
Layout for floating boxes.
|
||
|
||
"""
|
||
|
||
from ..formatting_structure import boxes
|
||
from .min_max import handle_min_max_width
|
||
from .percentages import resolve_percentages, resolve_position_percentages
|
||
from .preferred import shrink_to_fit
|
||
from .tables import table_wrapper_width
|
||
|
||
|
||
@handle_min_max_width
|
||
def float_width(box, context, containing_block):
|
||
# Check that box.width is auto even if the caller does it too, because
|
||
# the handle_min_max_width decorator can change the value
|
||
if box.width == 'auto':
|
||
box.width = shrink_to_fit(context, box, containing_block.width)
|
||
|
||
|
||
def float_layout(context, box, containing_block, absolute_boxes, fixed_boxes):
|
||
"""Set the width and position of floating ``box``."""
|
||
# Avoid circular imports
|
||
from .blocks import block_container_layout
|
||
from .flex import flex_layout
|
||
from .inlines import inline_replaced_box_width_height
|
||
|
||
cb_width, cb_height = (containing_block.width, containing_block.height)
|
||
resolve_percentages(box, (cb_width, cb_height))
|
||
|
||
# TODO: This is only handled later in blocks.block_container_layout
|
||
# http://www.w3.org/TR/CSS21/visudet.html#normal-block
|
||
if cb_height == 'auto':
|
||
cb_height = (
|
||
containing_block.position_y - containing_block.content_box_y())
|
||
|
||
resolve_position_percentages(box, (cb_width, cb_height))
|
||
|
||
if box.margin_left == 'auto':
|
||
box.margin_left = 0
|
||
if box.margin_right == 'auto':
|
||
box.margin_right = 0
|
||
if box.margin_top == 'auto':
|
||
box.margin_top = 0
|
||
if box.margin_bottom == 'auto':
|
||
box.margin_bottom = 0
|
||
|
||
clearance = get_clearance(context, box)
|
||
if clearance is not None:
|
||
box.position_y += clearance
|
||
|
||
if isinstance(box, boxes.BlockReplacedBox):
|
||
inline_replaced_box_width_height(box, containing_block)
|
||
elif box.width == 'auto':
|
||
float_width(box, context, containing_block)
|
||
|
||
if box.is_table_wrapper:
|
||
table_wrapper_width(context, box, (cb_width, cb_height))
|
||
|
||
if isinstance(box, boxes.BlockContainerBox):
|
||
context.create_block_formatting_context()
|
||
box, _, _, _, _ = block_container_layout(
|
||
context, box, max_position_y=float('inf'),
|
||
skip_stack=None, page_is_empty=False,
|
||
absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
|
||
adjoining_margins=None)
|
||
context.finish_block_formatting_context(box)
|
||
elif isinstance(box, boxes.FlexContainerBox):
|
||
box, _, _, _, _ = flex_layout(
|
||
context, box, max_position_y=float('inf'),
|
||
skip_stack=None, containing_block=containing_block,
|
||
page_is_empty=False, absolute_boxes=absolute_boxes,
|
||
fixed_boxes=fixed_boxes)
|
||
else:
|
||
assert isinstance(box, boxes.BlockReplacedBox)
|
||
|
||
box = find_float_position(context, box, containing_block)
|
||
|
||
context.excluded_shapes.append(box)
|
||
|
||
return box
|
||
|
||
|
||
def find_float_position(context, box, containing_block):
|
||
"""Get the right position of the float ``box``."""
|
||
# See http://www.w3.org/TR/CSS2/visuren.html#float-position
|
||
|
||
# Point 4 is already handled as box.position_y is set according to the
|
||
# containing box top position, with collapsing margins handled
|
||
|
||
# Points 5 and 6, box.position_y is set to the highest position_y possible
|
||
if context.excluded_shapes:
|
||
highest_y = context.excluded_shapes[-1].position_y
|
||
if box.position_y < highest_y:
|
||
box.translate(0, highest_y - box.position_y)
|
||
|
||
# Points 1 and 2
|
||
position_x, position_y, available_width = avoid_collisions(
|
||
context, box, containing_block)
|
||
|
||
# Point 9
|
||
# position_y is set now, let's define position_x
|
||
# for float: left elements, it's already done!
|
||
if box.style['float'] == 'right':
|
||
position_x += available_width - box.margin_width()
|
||
|
||
box.translate(position_x - box.position_x, position_y - box.position_y)
|
||
|
||
return box
|
||
|
||
|
||
def get_clearance(context, box, collapsed_margin=0):
|
||
"""Return None if there is no clearance, otherwise the clearance value."""
|
||
clearance = None
|
||
hypothetical_position = box.position_y + collapsed_margin
|
||
# Hypothetical position is the position of the top border edge
|
||
for excluded_shape in context.excluded_shapes:
|
||
if box.style['clear'] in (excluded_shape.style['float'], 'both'):
|
||
y, h = excluded_shape.position_y, excluded_shape.margin_height()
|
||
if hypothetical_position < y + h:
|
||
clearance = max(
|
||
(clearance or 0), y + h - hypothetical_position)
|
||
return clearance
|
||
|
||
|
||
def avoid_collisions(context, box, containing_block, outer=True):
|
||
excluded_shapes = context.excluded_shapes
|
||
position_y = box.position_y if outer else box.border_box_y()
|
||
|
||
box_width = box.margin_width() if outer else box.border_width()
|
||
box_height = box.margin_height() if outer else box.border_height()
|
||
|
||
if box.border_height() == 0 and box.is_floated():
|
||
return 0, 0, containing_block.width
|
||
|
||
while True:
|
||
colliding_shapes = []
|
||
for shape in excluded_shapes:
|
||
# Assign locals to avoid slow attribute lookups.
|
||
shape_position_y = shape.position_y
|
||
shape_margin_height = shape.margin_height()
|
||
if ((shape_position_y < position_y <
|
||
shape_position_y + shape_margin_height) or
|
||
(shape_position_y < position_y + box_height <
|
||
shape_position_y + shape_margin_height) or
|
||
(shape_position_y >= position_y and
|
||
shape_position_y + shape_margin_height <=
|
||
position_y + box_height)):
|
||
colliding_shapes.append(shape)
|
||
left_bounds = [
|
||
shape.position_x + shape.margin_width()
|
||
for shape in colliding_shapes
|
||
if shape.style['float'] == 'left']
|
||
right_bounds = [
|
||
shape.position_x
|
||
for shape in colliding_shapes
|
||
if shape.style['float'] == 'right']
|
||
|
||
# Set the default maximum bounds
|
||
max_left_bound = containing_block.content_box_x()
|
||
max_right_bound = \
|
||
containing_block.content_box_x() + containing_block.width
|
||
|
||
if not outer:
|
||
max_left_bound += box.margin_left
|
||
max_right_bound -= box.margin_right
|
||
|
||
# Set the real maximum bounds according to sibling float elements
|
||
if left_bounds or right_bounds:
|
||
if left_bounds:
|
||
max_left_bound = max(max(left_bounds), max_left_bound)
|
||
if right_bounds:
|
||
max_right_bound = min(min(right_bounds), max_right_bound)
|
||
|
||
# Points 3, 7 and 8
|
||
if box_width > max_right_bound - max_left_bound:
|
||
# The box does not fit here
|
||
new_positon_y = min(
|
||
shape.position_y + shape.margin_height()
|
||
for shape in colliding_shapes)
|
||
if new_positon_y > position_y:
|
||
# We can find a solution with a higher position_y
|
||
position_y = new_positon_y
|
||
continue
|
||
# No solution, we must put the box here
|
||
break
|
||
|
||
# See https://www.w3.org/TR/CSS21/visuren.html#floats
|
||
# Boxes that can’t collide with floats are:
|
||
# - floats
|
||
# - line boxes
|
||
# - table wrappers
|
||
# - block-level replaced box
|
||
# - element establishing new formatting contexts (not handled)
|
||
assert (
|
||
(box.style['float'] in ('right', 'left')) or
|
||
isinstance(box, boxes.LineBox) or
|
||
box.is_table_wrapper or
|
||
isinstance(box, boxes.BlockReplacedBox))
|
||
|
||
# The x-position of the box depends on its type.
|
||
position_x = max_left_bound
|
||
if box.style['float'] == 'none':
|
||
if containing_block.style['direction'] == 'rtl':
|
||
if isinstance(box, boxes.LineBox):
|
||
# The position of the line is the position of the cursor, at
|
||
# the right bound.
|
||
position_x = max_right_bound
|
||
elif box.is_table_wrapper:
|
||
# The position of the right border of the table is at the right
|
||
# bound.
|
||
position_x = max_right_bound - box_width
|
||
else:
|
||
# The position of the right border of the replaced box is at
|
||
# the right bound.
|
||
assert isinstance(box, boxes.BlockReplacedBox)
|
||
position_x = max_right_bound - box_width
|
||
|
||
available_width = max_right_bound - max_left_bound
|
||
|
||
if not outer:
|
||
position_x -= box.margin_left
|
||
position_y -= box.margin_top
|
||
|
||
return position_x, position_y, available_width
|