mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-05 08:27:22 +03:00
5f7a104c62
Fix #831.
888 lines
38 KiB
Python
888 lines
38 KiB
Python
"""
|
|
weasyprint.layout.flex
|
|
------------------------
|
|
|
|
Layout for flex containers and flex-items.
|
|
|
|
:copyright: Copyright 2011-2019 Simon Sapin and contributors, see AUTHORS.
|
|
:license: BSD, see LICENSE for details.
|
|
|
|
"""
|
|
|
|
import sys
|
|
from math import log10
|
|
|
|
from ..css.properties import Dimension
|
|
from ..formatting_structure import boxes
|
|
from .percentages import resolve_one_percentage, resolve_percentages
|
|
from .preferred import max_content_width, min_content_width
|
|
from .tables import find_in_flow_baseline
|
|
|
|
|
|
class FlexLine(list):
|
|
pass
|
|
|
|
|
|
def flex_layout(context, box, max_position_y, skip_stack, containing_block,
|
|
page_is_empty, absolute_boxes, fixed_boxes):
|
|
# Avoid a circular import
|
|
from . import blocks, preferred
|
|
|
|
context.create_block_formatting_context()
|
|
resume_at = None
|
|
|
|
# Step 1 is done in formatting_structure.boxes
|
|
# Step 2
|
|
if box.style['flex_direction'].startswith('row'):
|
|
axis, cross = 'width', 'height'
|
|
else:
|
|
axis, cross = 'height', 'width'
|
|
|
|
margin_left = 0 if box.margin_left == 'auto' else box.margin_left
|
|
margin_right = 0 if box.margin_right == 'auto' else box.margin_right
|
|
margin_top = 0 if box.margin_top == 'auto' else box.margin_top
|
|
margin_bottom = 0 if box.margin_bottom == 'auto' else box.margin_bottom
|
|
|
|
if getattr(box, axis) != 'auto':
|
|
available_main_space = getattr(box, axis)
|
|
else:
|
|
if axis == 'width':
|
|
available_main_space = (
|
|
containing_block.width -
|
|
margin_left - margin_right -
|
|
box.padding_left - box.padding_right -
|
|
box.border_left_width - box.border_right_width)
|
|
else:
|
|
main_space = max_position_y - box.position_y
|
|
if containing_block.height != 'auto':
|
|
if hasattr(containing_block.height, 'unit'):
|
|
assert containing_block.height.unit == 'px'
|
|
main_space = min(main_space, containing_block.height.value)
|
|
else:
|
|
main_space = min(main_space, containing_block.height)
|
|
available_main_space = (
|
|
main_space -
|
|
margin_top - margin_bottom -
|
|
box.padding_top - box.padding_bottom -
|
|
box.border_top_width - box.border_bottom_width)
|
|
|
|
if getattr(box, cross) != 'auto':
|
|
available_cross_space = getattr(box, cross)
|
|
else:
|
|
if cross == 'height':
|
|
main_space = max_position_y - box.content_box_y()
|
|
if containing_block.height != 'auto':
|
|
if hasattr(containing_block.height, 'unit'):
|
|
assert containing_block.height.unit == 'px'
|
|
main_space = min(main_space, containing_block.height.value)
|
|
else:
|
|
main_space = min(main_space, containing_block.height)
|
|
available_cross_space = (
|
|
main_space -
|
|
margin_top - margin_bottom -
|
|
box.padding_top - box.padding_bottom -
|
|
box.border_top_width - box.border_bottom_width)
|
|
else:
|
|
available_cross_space = (
|
|
containing_block.width -
|
|
margin_left - margin_right -
|
|
box.padding_left - box.padding_right -
|
|
box.border_left_width - box.border_right_width)
|
|
|
|
# Step 3
|
|
children = box.children
|
|
parent_box = box.copy_with_children(children)
|
|
resolve_percentages(parent_box, containing_block)
|
|
# TODO: removing auto margins is OK for this step, but margins should be
|
|
# calculated later.
|
|
if parent_box.margin_top == 'auto':
|
|
box.margin_top = parent_box.margin_top = 0
|
|
if parent_box.margin_bottom == 'auto':
|
|
box.margin_bottom = parent_box.margin_bottom = 0
|
|
if parent_box.margin_left == 'auto':
|
|
box.margin_left = parent_box.margin_left = 0
|
|
if parent_box.margin_right == 'auto':
|
|
box.margin_right = parent_box.margin_right = 0
|
|
if isinstance(parent_box, boxes.FlexBox):
|
|
blocks.block_level_width(parent_box, containing_block)
|
|
else:
|
|
parent_box.width = preferred.flex_max_content_width(
|
|
context, parent_box)
|
|
original_skip_stack = skip_stack
|
|
if skip_stack is not None:
|
|
if box.style['flex_direction'].endswith('-reverse'):
|
|
children = children[:skip_stack[0] + 1]
|
|
else:
|
|
children = children[skip_stack[0]:]
|
|
skip_stack = skip_stack[1]
|
|
else:
|
|
skip_stack = None
|
|
child_skip_stack = skip_stack
|
|
for child in children:
|
|
if not child.is_flex_item:
|
|
continue
|
|
|
|
# See https://www.w3.org/TR/css-flexbox-1/#min-size-auto
|
|
if child.style['overflow'] == 'visible':
|
|
main_flex_direction = axis
|
|
else:
|
|
main_flex_direction = None
|
|
resolve_percentages(child, containing_block, main_flex_direction)
|
|
child.position_x = parent_box.content_box_x()
|
|
child.position_y = parent_box.content_box_y()
|
|
if child.min_width == 'auto':
|
|
specified_size = (
|
|
child.width if child.width != 'auto' else float('inf'))
|
|
if isinstance(child, boxes.ParentBox):
|
|
new_child = child.copy_with_children(child.children)
|
|
else:
|
|
new_child = child.copy()
|
|
new_child.style = child.style.copy()
|
|
new_child.style['width'] = 'auto'
|
|
new_child.style['min_width'] = Dimension(0, 'px')
|
|
new_child.style['max_width'] = Dimension(float('inf'), 'px')
|
|
content_size = min_content_width(context, new_child, outer=False)
|
|
child.min_width = min(specified_size, content_size)
|
|
elif child.min_height == 'auto':
|
|
# TODO: find a way to get min-content-height
|
|
specified_size = (
|
|
child.height if child.height != 'auto' else float('inf'))
|
|
if isinstance(child, boxes.ParentBox):
|
|
new_child = child.copy_with_children(child.children)
|
|
else:
|
|
new_child = child.copy()
|
|
new_child.style = child.style.copy()
|
|
new_child.style['height'] = 'auto'
|
|
new_child.style['min_height'] = Dimension(0, 'px')
|
|
new_child.style['max_height'] = Dimension(float('inf'), 'px')
|
|
new_child = blocks.block_level_layout(
|
|
context, new_child, float('inf'), child_skip_stack,
|
|
parent_box, page_is_empty, [], [], [])[0]
|
|
content_size = new_child.height
|
|
child.min_height = min(specified_size, content_size)
|
|
|
|
child.style = child.style.copy()
|
|
|
|
if child.style['flex_basis'] == 'content':
|
|
flex_basis = child.flex_basis = 'content'
|
|
else:
|
|
resolve_one_percentage(child, 'flex_basis', available_main_space)
|
|
flex_basis = child.flex_basis
|
|
|
|
# "If a value would resolve to auto for width, it instead resolves
|
|
# to content for flex-basis." Let's do this for height too.
|
|
# See https://www.w3.org/TR/css-flexbox-1/#propdef-flex-basis
|
|
resolve_one_percentage(child, axis, available_main_space)
|
|
if flex_basis == 'auto':
|
|
if child.style[axis] == 'auto':
|
|
flex_basis = 'content'
|
|
else:
|
|
if axis == 'width':
|
|
flex_basis = child.border_width()
|
|
if child.margin_left != 'auto':
|
|
flex_basis += child.margin_left
|
|
if child.margin_right != 'auto':
|
|
flex_basis += child.margin_right
|
|
else:
|
|
flex_basis = child.border_height()
|
|
if child.margin_top != 'auto':
|
|
flex_basis += child.margin_top
|
|
if child.margin_bottom != 'auto':
|
|
flex_basis += child.margin_bottom
|
|
|
|
# Step 3.A
|
|
if flex_basis != 'content':
|
|
child.flex_base_size = flex_basis
|
|
|
|
# TODO: Step 3.B
|
|
# TODO: Step 3.C
|
|
|
|
# Step 3.D is useless, as we never have infinite sizes on paged media
|
|
|
|
# Step 3.E
|
|
else:
|
|
child.style[axis] = 'max-content'
|
|
|
|
# TODO: don't set style value, support *-content values instead
|
|
if child.style[axis] == 'max-content':
|
|
child.style[axis] = 'auto'
|
|
if axis == 'width':
|
|
child.flex_base_size = max_content_width(context, child)
|
|
else:
|
|
if isinstance(child, boxes.ParentBox):
|
|
new_child = child.copy_with_children(child.children)
|
|
else:
|
|
new_child = child.copy()
|
|
new_child.width = float('inf')
|
|
new_child = blocks.block_level_layout(
|
|
context, new_child, float('inf'), child_skip_stack,
|
|
parent_box, page_is_empty, absolute_boxes, fixed_boxes,
|
|
adjoining_margins=[])[0]
|
|
child.flex_base_size = new_child.margin_height()
|
|
elif child.style[axis] == 'min-content':
|
|
child.style[axis] = 'auto'
|
|
if axis == 'width':
|
|
child.flex_base_size = min_content_width(context, child)
|
|
else:
|
|
if isinstance(child, boxes.ParentBox):
|
|
new_child = child.copy_with_children(child.children)
|
|
else:
|
|
new_child = child.copy()
|
|
new_child.width = 0
|
|
new_child = blocks.block_level_layout(
|
|
context, new_child, float('inf'), child_skip_stack,
|
|
parent_box, page_is_empty, absolute_boxes, fixed_boxes,
|
|
adjoining_margins=[])[0]
|
|
child.flex_base_size = new_child.margin_height()
|
|
else:
|
|
assert child.style[axis].unit == 'px'
|
|
# TODO: should we add padding, borders and margins?
|
|
child.flex_base_size = child.style[axis].value
|
|
|
|
child.hypothetical_main_size = max(
|
|
getattr(child, 'min_%s' % axis), min(
|
|
child.flex_base_size, getattr(child, 'max_%s' % axis)))
|
|
|
|
# Skip stack is only for the first child
|
|
child_skip_stack = None
|
|
|
|
# Step 4
|
|
# TODO: the whole step has to be fixed
|
|
if axis == 'width':
|
|
blocks.block_level_width(box, containing_block)
|
|
else:
|
|
if box.style['height'] != 'auto':
|
|
box.height = box.style['height'].value
|
|
else:
|
|
box.height = 0
|
|
for i, child in enumerate(children):
|
|
if not child.is_flex_item:
|
|
continue
|
|
child_height = (
|
|
child.hypothetical_main_size +
|
|
child.border_top_width + child.border_bottom_width +
|
|
child.padding_top + child.padding_bottom)
|
|
if getattr(box, axis) == 'auto' and (
|
|
child_height + box.height > available_main_space):
|
|
resume_at = (i, None)
|
|
children = children[:i + 1]
|
|
break
|
|
box.height += child_height
|
|
|
|
# Step 5
|
|
flex_lines = []
|
|
|
|
line = []
|
|
line_size = 0
|
|
axis_size = getattr(box, axis)
|
|
for i, child in enumerate(
|
|
sorted(children, key=lambda item: item.style['order'])):
|
|
if not child.is_flex_item:
|
|
continue
|
|
line_size += child.hypothetical_main_size
|
|
if box.style['flex_wrap'] != 'nowrap' and line_size > axis_size:
|
|
if line:
|
|
flex_lines.append(FlexLine(line))
|
|
line = [(i, child)]
|
|
line_size = child.hypothetical_main_size
|
|
else:
|
|
line.append((i, child))
|
|
flex_lines.append(FlexLine(line))
|
|
line = []
|
|
line_size = 0
|
|
else:
|
|
line.append((i, child))
|
|
if line:
|
|
flex_lines.append(FlexLine(line))
|
|
|
|
# TODO: handle *-reverse using the terminology from the specification
|
|
if box.style['flex_wrap'] == 'wrap-reverse':
|
|
flex_lines.reverse()
|
|
if box.style['flex_direction'].endswith('-reverse'):
|
|
for line in flex_lines:
|
|
line.reverse()
|
|
|
|
# Step 6
|
|
# See https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths
|
|
for line in flex_lines:
|
|
# Step 6 - 9.7.1
|
|
hypothetical_main_size = sum(
|
|
child.hypothetical_main_size for i, child in line)
|
|
if hypothetical_main_size < available_main_space:
|
|
flex_factor_type = 'grow'
|
|
else:
|
|
flex_factor_type = 'shrink'
|
|
|
|
# Step 6 - 9.7.2
|
|
for i, child in line:
|
|
if flex_factor_type == 'grow':
|
|
child.flex_factor = child.style['flex_grow']
|
|
else:
|
|
child.flex_factor = child.style['flex_shrink']
|
|
if (child.flex_factor == 0 or
|
|
(flex_factor_type == 'grow' and
|
|
child.flex_base_size > child.hypothetical_main_size) or
|
|
(flex_factor_type == 'shrink' and
|
|
child.flex_base_size < child.hypothetical_main_size)):
|
|
child.target_main_size = child.hypothetical_main_size
|
|
child.frozen = True
|
|
else:
|
|
child.frozen = False
|
|
|
|
# Step 6 - 9.7.3
|
|
initial_free_space = available_main_space
|
|
for i, child in line:
|
|
if child.frozen:
|
|
initial_free_space -= child.target_main_size
|
|
else:
|
|
initial_free_space -= child.flex_base_size
|
|
|
|
# Step 6 - 9.7.4
|
|
while not all(child.frozen for i, child in line):
|
|
unfrozen_factor_sum = 0
|
|
remaining_free_space = available_main_space
|
|
|
|
# Step 6 - 9.7.4.b
|
|
for i, child in line:
|
|
if child.frozen:
|
|
remaining_free_space -= child.target_main_size
|
|
else:
|
|
remaining_free_space -= child.flex_base_size
|
|
unfrozen_factor_sum += child.flex_factor
|
|
|
|
if unfrozen_factor_sum < 1:
|
|
initial_free_space *= unfrozen_factor_sum
|
|
|
|
if initial_free_space == float('inf'):
|
|
initial_free_space = sys.maxsize
|
|
if remaining_free_space == float('inf'):
|
|
remaining_free_space = sys.maxsize
|
|
|
|
initial_magnitude = (
|
|
int(log10(initial_free_space)) if initial_free_space > 0
|
|
else -float('inf'))
|
|
remaining_magnitude = (
|
|
int(log10(remaining_free_space)) if remaining_free_space > 0
|
|
else -float('inf'))
|
|
if initial_magnitude < remaining_magnitude:
|
|
remaining_free_space = initial_free_space
|
|
|
|
# Step 6 - 9.7.4.c
|
|
if remaining_free_space == 0:
|
|
# "Do nothing", but we at least set the flex_base_size as
|
|
# target_main_size for next step.
|
|
for i, child in line:
|
|
if not child.frozen:
|
|
child.target_main_size = child.flex_base_size
|
|
else:
|
|
scaled_flex_shrink_factors_sum = 0
|
|
flex_grow_factors_sum = 0
|
|
for i, child in line:
|
|
if not child.frozen:
|
|
child.scaled_flex_shrink_factor = (
|
|
child.flex_base_size * child.style['flex_shrink'])
|
|
scaled_flex_shrink_factors_sum += (
|
|
child.scaled_flex_shrink_factor)
|
|
flex_grow_factors_sum += child.style['flex_grow']
|
|
for i, child in line:
|
|
if not child.frozen:
|
|
if flex_factor_type == 'grow':
|
|
ratio = (
|
|
child.style['flex_grow'] /
|
|
flex_grow_factors_sum)
|
|
child.target_main_size = (
|
|
child.flex_base_size +
|
|
remaining_free_space * ratio)
|
|
elif flex_factor_type == 'shrink':
|
|
if scaled_flex_shrink_factors_sum == 0:
|
|
child.target_main_size = child.flex_base_size
|
|
else:
|
|
ratio = (
|
|
child.scaled_flex_shrink_factor /
|
|
scaled_flex_shrink_factors_sum)
|
|
child.target_main_size = (
|
|
child.flex_base_size +
|
|
remaining_free_space * ratio)
|
|
|
|
# Step 6 - 9.7.4.d
|
|
# TODO: First part of this step is useless until 3.E is correct
|
|
for i, child in line:
|
|
child.adjustment = 0
|
|
if not child.frozen and child.target_main_size < 0:
|
|
child.adjustment = -child.target_main_size
|
|
child.target_main_size = 0
|
|
|
|
# Step 6 - 9.7.4.e
|
|
adjustments = sum(child.adjustment for i, child in line)
|
|
for i, child in line:
|
|
if adjustments == 0:
|
|
child.frozen = True
|
|
elif adjustments > 0 and child.adjustment > 0:
|
|
child.frozen = True
|
|
elif adjustments < 0 and child.adjustment < 0:
|
|
child.frozen = True
|
|
|
|
# Step 6 - 9.7.5
|
|
for i, child in line:
|
|
if axis == 'width':
|
|
child.width = (
|
|
child.target_main_size -
|
|
child.padding_left - child.padding_right -
|
|
child.border_left_width - child.border_right_width)
|
|
if child.margin_left != 'auto':
|
|
child.width -= child.margin_left
|
|
if child.margin_right != 'auto':
|
|
child.width -= child.margin_right
|
|
else:
|
|
child.height = (
|
|
child.target_main_size -
|
|
child.padding_top - child.padding_bottom -
|
|
child.border_top_width - child.border_top_width)
|
|
if child.margin_left != 'auto':
|
|
child.height -= child.margin_left
|
|
if child.margin_right != 'auto':
|
|
child.height -= child.margin_right
|
|
|
|
# Step 7
|
|
# TODO: Fix TODO in build.flex_children
|
|
# TODO: Handle breaks
|
|
new_flex_lines = []
|
|
child_skip_stack = skip_stack
|
|
for line in flex_lines:
|
|
new_flex_line = FlexLine()
|
|
for i, child in line:
|
|
# TODO: Find another way than calling block_level_layout_switch to
|
|
# get baseline and child.height
|
|
if child.margin_top == 'auto':
|
|
child.margin_top = 0
|
|
if child.margin_bottom == 'auto':
|
|
child.margin_bottom = 0
|
|
if isinstance(child, boxes.ParentBox):
|
|
child_copy = child.copy_with_children(child.children)
|
|
else:
|
|
child_copy = child.copy()
|
|
blocks.block_level_width(child_copy, parent_box)
|
|
new_child, _, _, adjoining_margins, _ = (
|
|
blocks.block_level_layout_switch(
|
|
context, child_copy, float('inf'), child_skip_stack,
|
|
parent_box, page_is_empty, absolute_boxes, fixed_boxes,
|
|
adjoining_margins=[]))
|
|
|
|
child._baseline = find_in_flow_baseline(new_child) or 0
|
|
if cross == 'height':
|
|
child.height = new_child.height
|
|
# As flex items margins never collapse (with other flex items
|
|
# or with the flex container), we can add the adjoining margins
|
|
# to the child bottom margin.
|
|
child.margin_bottom += blocks.collapse_margin(
|
|
adjoining_margins)
|
|
else:
|
|
child.width = min_content_width(context, child, outer=False)
|
|
|
|
new_flex_line.append((i, child))
|
|
|
|
# Skip stack is only for the first child
|
|
child_skip_stack = None
|
|
|
|
if new_flex_line:
|
|
new_flex_lines.append(new_flex_line)
|
|
flex_lines = new_flex_lines
|
|
|
|
# Step 8
|
|
cross_size = getattr(box, cross)
|
|
if len(flex_lines) == 1 and cross_size != 'auto':
|
|
flex_lines[0].cross_size = cross_size
|
|
else:
|
|
for line in flex_lines:
|
|
collected_items = []
|
|
not_collected_items = []
|
|
for i, child in line:
|
|
align_self = child.style['align_self']
|
|
if (box.style['flex_direction'].startswith('row') and
|
|
align_self == 'baseline' and
|
|
child.margin_top != 'auto' and
|
|
child.margin_bottom != 'auto'):
|
|
collected_items.append(child)
|
|
else:
|
|
not_collected_items.append(child)
|
|
cross_start_distance = 0
|
|
cross_end_distance = 0
|
|
for child in collected_items:
|
|
baseline = child._baseline - child.position_y
|
|
cross_start_distance = max(cross_start_distance, baseline)
|
|
cross_end_distance = max(
|
|
cross_end_distance, child.margin_height() - baseline)
|
|
collected_cross_size = cross_start_distance + cross_end_distance
|
|
non_collected_cross_size = 0
|
|
if not_collected_items:
|
|
non_collected_cross_size = float('-inf')
|
|
for child in not_collected_items:
|
|
if cross == 'height':
|
|
child_cross_size = child.border_height()
|
|
if child.margin_top != 'auto':
|
|
child_cross_size += child.margin_top
|
|
if child.margin_bottom != 'auto':
|
|
child_cross_size += child.margin_bottom
|
|
else:
|
|
child_cross_size = child.border_width()
|
|
if child.margin_left != 'auto':
|
|
child_cross_size += child.margin_left
|
|
if child.margin_right != 'auto':
|
|
child_cross_size += child.margin_right
|
|
non_collected_cross_size = max(
|
|
child_cross_size, non_collected_cross_size)
|
|
line.cross_size = max(
|
|
collected_cross_size, non_collected_cross_size)
|
|
|
|
if len(flex_lines) == 1:
|
|
line, = flex_lines
|
|
min_cross_size = getattr(box, 'min_%s' % cross)
|
|
if min_cross_size == 'auto':
|
|
min_cross_size = float('-inf')
|
|
max_cross_size = getattr(box, 'max_%s' % cross)
|
|
if max_cross_size == 'auto':
|
|
max_cross_size = float('inf')
|
|
line.cross_size = max(
|
|
min_cross_size, min(line.cross_size, max_cross_size))
|
|
|
|
# Step 9
|
|
if box.style['align_content'] == 'stretch':
|
|
definite_cross_size = None
|
|
if cross == 'height' and box.style['height'] != 'auto':
|
|
definite_cross_size = box.style['height'].value
|
|
elif cross == 'width':
|
|
if isinstance(box, boxes.FlexBox):
|
|
if box.style['width'] == 'auto':
|
|
definite_cross_size = available_cross_space
|
|
else:
|
|
definite_cross_size = box.style['width'].value
|
|
if definite_cross_size is not None:
|
|
extra_cross_size = definite_cross_size - sum(
|
|
line.cross_size for line in flex_lines)
|
|
if extra_cross_size:
|
|
for line in flex_lines:
|
|
line.cross_size += extra_cross_size / len(flex_lines)
|
|
|
|
# TODO: Step 10
|
|
|
|
# Step 11
|
|
for line in flex_lines:
|
|
for i, child in line:
|
|
align_self = child.style['align_self']
|
|
if align_self == 'auto':
|
|
align_self = box.style['align_items']
|
|
if align_self == 'stretch' and child.style[cross] == 'auto':
|
|
cross_margins = (
|
|
(child.margin_top, child.margin_bottom)
|
|
if cross == 'height'
|
|
else (child.margin_left, child.margin_right))
|
|
if child.style[cross] == 'auto':
|
|
if 'auto' not in cross_margins:
|
|
cross_size = line.cross_size
|
|
if cross == 'height':
|
|
cross_size -= (
|
|
child.margin_top + child.margin_bottom +
|
|
child.padding_top + child.padding_bottom +
|
|
child.border_top_width +
|
|
child.border_bottom_width)
|
|
else:
|
|
cross_size -= (
|
|
child.margin_left + child.margin_right +
|
|
child.padding_left + child.padding_right +
|
|
child.border_left_width +
|
|
child.border_right_width)
|
|
setattr(child, cross, cross_size)
|
|
# TODO: redo layout?
|
|
# else: Cross size has been set by step 7
|
|
|
|
# Step 12
|
|
# TODO: handle rtl
|
|
original_position_axis = (
|
|
box.content_box_x() if axis == 'width'
|
|
else box.content_box_y())
|
|
justify_content = box.style['justify_content']
|
|
if box.style['flex_direction'].endswith('-reverse'):
|
|
if justify_content == 'flex-start':
|
|
justify_content = 'flex-end'
|
|
elif justify_content == 'flex-end':
|
|
justify_content = 'flex-start'
|
|
|
|
for line in flex_lines:
|
|
position_axis = original_position_axis
|
|
if axis == 'width':
|
|
free_space = box.width
|
|
for i, child in line:
|
|
free_space -= child.border_width()
|
|
if child.margin_left != 'auto':
|
|
free_space -= child.margin_left
|
|
if child.margin_right != 'auto':
|
|
free_space -= child.margin_right
|
|
else:
|
|
free_space = box.height
|
|
for i, child in line:
|
|
free_space -= child.border_height()
|
|
if child.margin_top != 'auto':
|
|
free_space -= child.margin_top
|
|
if child.margin_bottom != 'auto':
|
|
free_space -= child.margin_bottom
|
|
|
|
margins = 0
|
|
for i, child in line:
|
|
if axis == 'width':
|
|
if child.margin_left == 'auto':
|
|
margins += 1
|
|
if child.margin_right == 'auto':
|
|
margins += 1
|
|
else:
|
|
if child.margin_top == 'auto':
|
|
margins += 1
|
|
if child.margin_bottom == 'auto':
|
|
margins += 1
|
|
if margins:
|
|
free_space /= margins
|
|
for i, child in line:
|
|
if axis == 'width':
|
|
if child.margin_left == 'auto':
|
|
child.margin_left = free_space
|
|
if child.margin_right == 'auto':
|
|
child.margin_right = free_space
|
|
else:
|
|
if child.margin_top == 'auto':
|
|
child.margin_top = free_space
|
|
if child.margin_bottom == 'auto':
|
|
child.margin_bottom = free_space
|
|
free_space = 0
|
|
|
|
if justify_content == 'flex-end':
|
|
position_axis += free_space
|
|
elif justify_content == 'center':
|
|
position_axis += free_space / 2
|
|
elif justify_content == 'space-around':
|
|
position_axis += free_space / len(line) / 2
|
|
elif justify_content == 'space-evenly':
|
|
position_axis += free_space / (len(line) + 1)
|
|
|
|
for i, child in line:
|
|
if axis == 'width':
|
|
child.position_x = position_axis
|
|
if justify_content == 'stretch':
|
|
child.width += free_space / len(line)
|
|
else:
|
|
child.position_y = position_axis
|
|
position_axis += (
|
|
child.margin_width() if axis == 'width'
|
|
else child.margin_height())
|
|
if justify_content == 'space-around':
|
|
position_axis += free_space / len(line)
|
|
elif justify_content == 'space-between':
|
|
if len(line) > 1:
|
|
position_axis += free_space / (len(line) - 1)
|
|
elif justify_content == 'space-evenly':
|
|
position_axis += free_space / (len(line) + 1)
|
|
|
|
# Step 13
|
|
position_cross = (
|
|
box.content_box_y() if cross == 'height'
|
|
else box.content_box_x())
|
|
for line in flex_lines:
|
|
line.lower_baseline = 0
|
|
# TODO: don't duplicate this loop
|
|
for i, child in line:
|
|
align_self = child.style['align_self']
|
|
if align_self == 'auto':
|
|
align_self = box.style['align_items']
|
|
if align_self == 'baseline' and axis == 'width':
|
|
# TODO: handle vertical text
|
|
child.baseline = child._baseline - position_cross
|
|
line.lower_baseline = max(line.lower_baseline, child.baseline)
|
|
for i, child in line:
|
|
cross_margins = (
|
|
(child.margin_top, child.margin_bottom) if cross == 'height'
|
|
else (child.margin_left, child.margin_right))
|
|
auto_margins = sum([margin == 'auto' for margin in cross_margins])
|
|
if auto_margins:
|
|
extra_cross = line.cross_size
|
|
if cross == 'height':
|
|
extra_cross -= child.border_height()
|
|
if child.margin_top != 'auto':
|
|
extra_cross -= child.margin_top
|
|
if child.margin_bottom != 'auto':
|
|
extra_cross -= child.margin_bottom
|
|
else:
|
|
extra_cross -= child.border_width()
|
|
if child.margin_left != 'auto':
|
|
extra_cross -= child.margin_left
|
|
if child.margin_right != 'auto':
|
|
extra_cross -= child.margin_right
|
|
if extra_cross > 0:
|
|
extra_cross /= auto_margins
|
|
if cross == 'height':
|
|
if child.margin_top == 'auto':
|
|
child.margin_top = extra_cross
|
|
if child.margin_bottom == 'auto':
|
|
child.margin_bottom = extra_cross
|
|
else:
|
|
if child.margin_left == 'auto':
|
|
child.margin_left = extra_cross
|
|
if child.margin_right == 'auto':
|
|
child.margin_right = extra_cross
|
|
else:
|
|
if cross == 'height':
|
|
if child.margin_top == 'auto':
|
|
child.margin_top = 0
|
|
child.margin_bottom = extra_cross
|
|
else:
|
|
if child.margin_left == 'auto':
|
|
child.margin_left = 0
|
|
child.margin_right = extra_cross
|
|
else:
|
|
# Step 14
|
|
align_self = child.style['align_self']
|
|
if align_self == 'auto':
|
|
align_self = box.style['align_items']
|
|
position = 'position_y' if cross == 'height' else 'position_x'
|
|
setattr(child, position, position_cross)
|
|
if align_self == 'flex-end':
|
|
if cross == 'height':
|
|
child.position_y += (
|
|
line.cross_size - child.margin_height())
|
|
else:
|
|
child.position_x += (
|
|
line.cross_size - child.margin_width())
|
|
elif align_self == 'center':
|
|
if cross == 'height':
|
|
child.position_y += (
|
|
line.cross_size - child.margin_height()) / 2
|
|
else:
|
|
child.position_x += (
|
|
line.cross_size - child.margin_width()) / 2
|
|
elif align_self == 'baseline':
|
|
if cross == 'height':
|
|
child.position_y += (
|
|
line.lower_baseline - child.baseline)
|
|
else:
|
|
# Handle vertical text
|
|
pass
|
|
elif align_self == 'stretch':
|
|
if child.style[cross] == 'auto':
|
|
if cross == 'height':
|
|
margins = child.margin_top + child.margin_bottom
|
|
else:
|
|
margins = child.margin_left + child.margin_right
|
|
if child.style['box_sizing'] == 'content-box':
|
|
if cross == 'height':
|
|
margins += (
|
|
child.border_top_width +
|
|
child.border_bottom_width +
|
|
child.padding_top + child.padding_bottom)
|
|
else:
|
|
margins += (
|
|
child.border_left_width +
|
|
child.border_right_width +
|
|
child.padding_left + child.padding_right)
|
|
# TODO: don't set style width, find a way to avoid
|
|
# width re-calculation after Step 16
|
|
child.style[cross] = Dimension(
|
|
line.cross_size - margins, 'px')
|
|
position_cross += line.cross_size
|
|
|
|
# Step 15
|
|
if box.style[cross] == 'auto':
|
|
# TODO: handle min-max
|
|
setattr(box, cross, sum(line.cross_size for line in flex_lines))
|
|
|
|
# Step 16
|
|
elif len(flex_lines) > 1:
|
|
extra_cross_size = getattr(box, cross) - sum(
|
|
line.cross_size for line in flex_lines)
|
|
direction = 'position_y' if cross == 'height' else 'position_x'
|
|
if extra_cross_size > 0:
|
|
cross_translate = 0
|
|
for line in flex_lines:
|
|
for i, child in line:
|
|
if child.is_flex_item:
|
|
current_value = getattr(child, direction)
|
|
current_value += cross_translate
|
|
setattr(child, direction, current_value)
|
|
if box.style['align_content'] == 'flex-end':
|
|
setattr(
|
|
child, direction,
|
|
current_value + extra_cross_size)
|
|
elif box.style['align_content'] == 'center':
|
|
setattr(
|
|
child, direction,
|
|
current_value + extra_cross_size / 2)
|
|
elif box.style['align_content'] == 'space-around':
|
|
setattr(
|
|
child, direction,
|
|
current_value + extra_cross_size /
|
|
len(flex_lines) / 2)
|
|
elif box.style['align_content'] == 'space-evenly':
|
|
setattr(
|
|
child, direction,
|
|
current_value + extra_cross_size /
|
|
(len(flex_lines) + 1))
|
|
if box.style['align_content'] == 'space-between':
|
|
cross_translate += extra_cross_size / (len(flex_lines) - 1)
|
|
elif box.style['align_content'] == 'space-around':
|
|
cross_translate += extra_cross_size / len(flex_lines)
|
|
elif box.style['align_content'] == 'space-evenly':
|
|
cross_translate += extra_cross_size / (len(flex_lines) + 1)
|
|
|
|
# TODO: don't use block_box_layout, see TODOs in Step 14 and
|
|
# build.flex_children.
|
|
box = box.copy()
|
|
box.children = []
|
|
child_skip_stack = skip_stack
|
|
for line in flex_lines:
|
|
for i, child in line:
|
|
if child.is_flex_item:
|
|
new_child, child_resume_at = blocks.block_level_layout_switch(
|
|
context, child, max_position_y, child_skip_stack, box,
|
|
page_is_empty, absolute_boxes, fixed_boxes,
|
|
adjoining_margins=[])[:2]
|
|
if new_child is None:
|
|
if resume_at and resume_at[0]:
|
|
resume_at = (resume_at[0] + i - 1, None)
|
|
else:
|
|
box.children.append(new_child)
|
|
if child_resume_at is not None:
|
|
if original_skip_stack:
|
|
first_level_skip = original_skip_stack[0]
|
|
else:
|
|
first_level_skip = 0
|
|
if resume_at:
|
|
first_level_skip += resume_at[0]
|
|
resume_at = (first_level_skip + i, child_resume_at)
|
|
if resume_at:
|
|
break
|
|
|
|
# Skip stack is only for the first child
|
|
child_skip_stack = None
|
|
if resume_at:
|
|
break
|
|
|
|
# Set box height
|
|
# TODO: this is probably useless because of step #15
|
|
if axis == 'width' and box.height == 'auto':
|
|
if flex_lines:
|
|
box.height = sum(line.cross_size for line in flex_lines)
|
|
else:
|
|
box.height = 0
|
|
|
|
# Set baseline
|
|
# See https://www.w3.org/TR/css-flexbox-1/#flex-baselines
|
|
# TODO: use the real algorithm
|
|
if isinstance(box, boxes.InlineFlexBox):
|
|
if axis == 'width': # and main text direction is horizontal
|
|
box.baseline = flex_lines[0].lower_baseline if flex_lines else 0
|
|
else:
|
|
box.baseline = ((
|
|
find_in_flow_baseline(box.children[0])
|
|
if box.children else 0) or 0)
|
|
|
|
context.finish_block_formatting_context(box)
|
|
|
|
# TODO: check these returned values
|
|
return box, resume_at, {'break': 'any', 'page': None}, [], False
|