1247 lines
53 KiB
Python
1247 lines
53 KiB
Python
"""Layout for grid containers and grid-items."""
|
||
|
||
from itertools import count, cycle
|
||
from math import inf
|
||
|
||
from ..css.properties import Dimension
|
||
from ..formatting_structure import boxes
|
||
from ..logger import LOGGER
|
||
from .percent import percentage, resolve_percentages
|
||
from .preferred import max_content_width, min_content_width
|
||
from .table import find_in_flow_baseline
|
||
|
||
|
||
def _is_length(sizing):
|
||
return isinstance(sizing, Dimension) and sizing.unit != 'fr'
|
||
|
||
|
||
def _is_fr(sizing):
|
||
return isinstance(sizing, Dimension) and sizing.unit == 'fr'
|
||
|
||
|
||
def _intersect(position_1, size_1, position_2, size_2):
|
||
return (
|
||
position_1 < position_2 + size_2 and
|
||
position_2 < position_1 + size_1)
|
||
|
||
|
||
def _intersect_with_children(x, y, width, height, positions):
|
||
for full_x, full_y, full_width, full_height in positions:
|
||
x_intersect = _intersect(x, width, full_x, full_width)
|
||
y_intersect = _intersect(y, height, full_y, full_height)
|
||
if x_intersect and y_intersect:
|
||
return True
|
||
return False
|
||
|
||
|
||
def _get_line(line, lines, side):
|
||
span, number, ident = line
|
||
if ident and span is None and number is None:
|
||
for coord, line in enumerate(lines):
|
||
if f'{ident}-{side}' in line:
|
||
break
|
||
else:
|
||
number = 1
|
||
if number is not None and span is None:
|
||
if ident is None:
|
||
coord = number - 1
|
||
else:
|
||
step = 1 if number > 0 else -1
|
||
for coord, line in enumerate(lines[::step]):
|
||
if ident in line:
|
||
number -= step
|
||
break
|
||
if number == 0:
|
||
break
|
||
else:
|
||
coord += abs(number)
|
||
if step == -1:
|
||
coord = len(lines) - 1 - coord
|
||
if span is not None:
|
||
coord = None
|
||
return span, number, ident, coord
|
||
|
||
|
||
def _get_placement(start, end, lines):
|
||
# Input coordinates are 1-indexed, returned coordinates are 0-indexed.
|
||
if start == 'auto' or start[0] == 'span':
|
||
if end == 'auto' or end[0] == 'span':
|
||
return
|
||
if start != 'auto':
|
||
span, number, ident, coord = _get_line(start, lines, 'start')
|
||
if span is not None:
|
||
size = number or 1
|
||
span_ident = ident
|
||
else:
|
||
size = 1
|
||
span_ident = coord = None
|
||
if end != 'auto':
|
||
span, number, ident, coord_end = _get_line(end, lines, 'end')
|
||
if span is not None:
|
||
size = span_number = number or 1
|
||
span_ident = ident
|
||
if span_ident is not None:
|
||
for size, line in enumerate(lines[coord+1:], start=1):
|
||
if span_ident in line:
|
||
span_number -= 1
|
||
if span_number == 0:
|
||
break
|
||
else:
|
||
size += span_number
|
||
elif coord is not None:
|
||
size = coord_end - coord
|
||
if coord is None:
|
||
if span_ident is None:
|
||
coord = coord_end - size
|
||
else:
|
||
number = number or 1
|
||
if coord_end > 0:
|
||
iterable = enumerate(lines[coord_end-1::-1])
|
||
for coord, line in iterable:
|
||
if span_ident in line:
|
||
number -= 1
|
||
if number == 0:
|
||
coord = coord_end - 1 - coord
|
||
break
|
||
else:
|
||
coord = -number
|
||
else:
|
||
coord = -number
|
||
size = coord_end - coord
|
||
else:
|
||
size = 1
|
||
if size < 0:
|
||
size = -size
|
||
coord -= size
|
||
if size == 0:
|
||
size = 1
|
||
return (coord, size)
|
||
|
||
|
||
def _get_span(place):
|
||
# TODO: Handle lines.
|
||
span = 1
|
||
if place[0] == 'span':
|
||
span = place[1] or 1
|
||
return span
|
||
|
||
|
||
def _get_second_placement(first_placement, second_start, second_end,
|
||
second_tracks, children_positions, first_flow, dense):
|
||
occupied_tracks = set()
|
||
for x, y, width, height in children_positions.values():
|
||
# Test whether cells overlap.
|
||
if first_flow == 'row':
|
||
if _intersect(y, height, *first_placement):
|
||
for x in range(x, x + width):
|
||
occupied_tracks.add(x)
|
||
else:
|
||
if _intersect(x, width, *first_placement):
|
||
for y in range(y, y + height):
|
||
occupied_tracks.add(y)
|
||
if dense:
|
||
for track in count():
|
||
if track in occupied_tracks:
|
||
continue
|
||
if second_start == 'auto':
|
||
placement = _get_placement(
|
||
(None, track + 1, None), second_end, second_tracks)
|
||
else:
|
||
assert second_start[0] == 'span'
|
||
# If the placement contains two spans, remove the one
|
||
# contributed by the end grid-placement property.
|
||
# https://drafts.csswg.org/css-grid/#grid-placement-errors
|
||
assert second_start == 'auto' or second_start[0] == 'span'
|
||
span = _get_span(second_start)
|
||
placement = _get_placement(
|
||
second_start, (None, track + 1 + span, None), second_tracks)
|
||
tracks = range(placement[0], placement[0] + placement[1])
|
||
if not set(tracks) & occupied_tracks:
|
||
return placement
|
||
else:
|
||
track = max(occupied_tracks or [0]) + 1
|
||
if second_start == 'auto':
|
||
return _get_placement(
|
||
(None, track + 1, None), second_end, second_tracks)
|
||
else:
|
||
assert second_start[0] == 'span'
|
||
# If the placement contains two spans, remove the one contributed
|
||
# by the end grid-placement property.
|
||
# https://drafts.csswg.org/css-grid/#grid-placement-errors
|
||
assert second_start == 'auto' or second_start[0] == 'span'
|
||
for end_track in count(track + 1):
|
||
placement = _get_placement(
|
||
second_start, (None, end_track + 1, None), second_tracks)
|
||
if placement[0] >= track:
|
||
return placement
|
||
|
||
|
||
def _get_sizing_functions(size):
|
||
min_sizing = max_sizing = size
|
||
if size[0] == 'minmax()':
|
||
min_sizing, max_sizing = size[1:]
|
||
if min_sizing[0] == 'fit-content()':
|
||
min_sizing = 'auto'
|
||
elif _is_fr(min_sizing):
|
||
min_sizing = 'auto'
|
||
return (min_sizing, max_sizing)
|
||
|
||
|
||
def _get_template_tracks(tracks):
|
||
if tracks == 'none':
|
||
tracks = ((),)
|
||
if 'subgrid' in tracks:
|
||
# TODO: Support subgrids.
|
||
LOGGER.warning('Subgrids are unsupported')
|
||
return [[]]
|
||
tracks_list = []
|
||
for i, track in enumerate(tracks):
|
||
if i % 2:
|
||
# Track size.
|
||
if track[0] == 'repeat()':
|
||
repeat_number, repeat_track_list = track[1:]
|
||
if not isinstance(repeat_number, int):
|
||
# TODO: Respect auto-fit and auto-fill.
|
||
LOGGER.warning(
|
||
'"auto-fit" and "auto-fill" are unsupported in repeat()')
|
||
repeat_number = 1
|
||
for _ in range(repeat_number):
|
||
for j, repeat_track in enumerate(repeat_track_list):
|
||
if j % 2:
|
||
# Track size in repeat.
|
||
tracks_list.append(repeat_track)
|
||
else:
|
||
# Line names in repeat.
|
||
if len(tracks_list) % 2:
|
||
tracks_list[-1].extend(repeat_track)
|
||
else:
|
||
tracks_list.append(list(repeat_track))
|
||
else:
|
||
tracks_list.append(track)
|
||
else:
|
||
# Line names.
|
||
if len(tracks_list) % 2:
|
||
tracks_list[-1].extend(track)
|
||
else:
|
||
tracks_list.append(list(track))
|
||
return tracks_list
|
||
|
||
|
||
def _distribute_extra_space(affected_sizes, affected_tracks_types,
|
||
size_contribution, tracks_children,
|
||
sizing_functions, tracks_sizes, span, direction,
|
||
context, containing_block):
|
||
assert affected_sizes in ('min', 'max')
|
||
assert affected_tracks_types in (
|
||
'intrinsic', 'content-based', 'max-content')
|
||
assert size_contribution in ('mininum', 'min-content', 'max-content')
|
||
assert direction in 'xy'
|
||
|
||
# 1. Maintain separately for each affected track a planned increase.
|
||
planned_increases = [0] * len(tracks_sizes)
|
||
|
||
# 2. Distribute space.
|
||
affected_tracks = []
|
||
affected_size_index = 0 if affected_sizes == 'min' else 1
|
||
for functions in sizing_functions:
|
||
function = functions[affected_size_index]
|
||
if affected_tracks_types == 'intrinsic':
|
||
if (function in ('min-content', 'max-content', 'auto') or
|
||
function[0] == 'fit-content()'):
|
||
affected_tracks.append(True)
|
||
continue
|
||
elif affected_tracks_types == 'content-based':
|
||
if function in ('min-content', 'max-content'):
|
||
affected_tracks.append(True)
|
||
continue
|
||
elif affected_tracks_types == 'max-content':
|
||
if function in ('max-content', 'auto'):
|
||
affected_tracks.append(True)
|
||
continue
|
||
affected_tracks.append(False)
|
||
for i, children in enumerate(tracks_children):
|
||
if not children:
|
||
continue
|
||
for item in children:
|
||
# 2.1 Find the space distribution.
|
||
# TODO: Differenciate minimum and min-content values.
|
||
# TODO: Find a better way to get height.
|
||
if direction == 'x':
|
||
if size_contribution in ('minimum', 'min-content'):
|
||
space = min_content_width(context, item)
|
||
else:
|
||
space = max_content_width(context, item)
|
||
else:
|
||
from .block import block_level_layout
|
||
item = item.deepcopy()
|
||
item.position_x = 0
|
||
item.position_y = 0
|
||
item, _, _, _, _, _ = block_level_layout(
|
||
context, item, bottom_space=-inf, skip_stack=None,
|
||
containing_block=containing_block, page_is_empty=True,
|
||
absolute_boxes=[], fixed_boxes=[])
|
||
space = item.margin_height()
|
||
for sizes in tracks_sizes[i:i+span]:
|
||
space -= sizes[affected_size_index]
|
||
space = max(0, space)
|
||
# 2.2 Distribute space up to limits.
|
||
tracks_numbers = list(
|
||
enumerate(affected_tracks[i:i+span], start=i))
|
||
item_incurred_increases = [0] * len(sizing_functions)
|
||
affected_tracks_numbers = [
|
||
j for j, affected in tracks_numbers if affected]
|
||
distributed_space = space / (len(affected_tracks_numbers) or 1)
|
||
for track_number in affected_tracks_numbers:
|
||
base_size, growth_limit = tracks_sizes[track_number]
|
||
item_incurred_increase = distributed_space
|
||
affected_size = tracks_sizes[track_number][affected_size_index]
|
||
limit = tracks_sizes[track_number][1]
|
||
if affected_size + item_incurred_increase >= limit:
|
||
extra = (
|
||
item_incurred_increase + affected_size - limit)
|
||
item_incurred_increase -= extra
|
||
space -= item_incurred_increase
|
||
item_incurred_increases[track_number] = item_incurred_increase
|
||
# 2.3 Distribute space to non-affected tracks.
|
||
if space and affected_tracks_numbers:
|
||
unaffected_tracks_numbers = [
|
||
j for j, affected in tracks_numbers if not affected]
|
||
distributed_space = (
|
||
space / (len(unaffected_tracks_numbers) or 1))
|
||
for track_number in unaffected_tracks_numbers:
|
||
base_size, growth_limit = tracks_sizes[track_number]
|
||
item_incurred_increase = distributed_space
|
||
affected_size = (
|
||
tracks_sizes[track_number][affected_size_index])
|
||
limit = tracks_sizes[track_number][1]
|
||
if affected_size + item_incurred_increase >= limit:
|
||
extra = (
|
||
item_incurred_increase + affected_size - limit)
|
||
item_incurred_increase -= extra
|
||
space -= item_incurred_increase
|
||
item_incurred_increases[track_number] = (
|
||
item_incurred_increase)
|
||
# 2.4 Distribute space beyond limits.
|
||
if space:
|
||
# TODO: Distribute space beyond limits.
|
||
pass
|
||
# 2.5. Set the track’s planned increase.
|
||
for k, extra in enumerate(item_incurred_increases):
|
||
if extra > planned_increases[k]:
|
||
planned_increases[k] = extra
|
||
# 3. Update the tracks’ affected size.
|
||
for i, increase in enumerate(planned_increases):
|
||
if affected_sizes == 'max' and tracks_sizes[i][1] is inf:
|
||
tracks_sizes[i][1] = tracks_sizes[i][0] + increase
|
||
else:
|
||
tracks_sizes[i][affected_size_index] += increase
|
||
|
||
|
||
def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
|
||
implicit_start, direction, gap, context,
|
||
containing_block, orthogonal_sizes=None):
|
||
assert direction in 'xy'
|
||
tracks_sizes = []
|
||
# TODO: Check that auto box size is 0 for percentages.
|
||
percent_box_size = 0 if box_size == 'auto' else box_size
|
||
# 1.1 Initialize track sizes.
|
||
for min_function, max_function in sizing_functions:
|
||
base_size = None
|
||
if _is_length(min_function):
|
||
base_size = percentage(min_function, percent_box_size)
|
||
elif (min_function in ('min-content', 'max-content', 'auto') or
|
||
min_function[0] == 'fit-content()'):
|
||
base_size = 0
|
||
growth_limit = None
|
||
if _is_length(max_function):
|
||
growth_limit = percentage(max_function, percent_box_size)
|
||
elif (max_function in ('min-content', 'max-content', 'auto') or
|
||
max_function[0] == 'fit-content()' or _is_fr(max_function)):
|
||
growth_limit = inf
|
||
if None not in (base_size, growth_limit):
|
||
growth_limit = max(base_size, growth_limit)
|
||
tracks_sizes.append([base_size, growth_limit])
|
||
|
||
# 1.2 Resolve intrinsic track sizes.
|
||
# 1.2.1 Shim baseline-aligned items.
|
||
# TODO: Shim items.
|
||
# 1.2.2 Size tracks to fit non-spanning items.
|
||
tracks_children = [[] for _ in range(len(tracks_sizes))]
|
||
for child, (x, y, width, height) in children_positions.items():
|
||
coord, size = (x, width) if direction == 'x' else (y, height)
|
||
if size != 1:
|
||
continue
|
||
tracks_children[coord - implicit_start].append(child)
|
||
iterable = zip(tracks_children, sizing_functions, tracks_sizes)
|
||
for children, (min_function, max_function), sizes in iterable:
|
||
if not children:
|
||
continue
|
||
if direction == 'y':
|
||
# TODO: Find a better way to get height.
|
||
from .block import block_level_layout
|
||
height = 0
|
||
for child in children:
|
||
x, _, width, _ = children_positions[child]
|
||
width = sum(orthogonal_sizes[x:x+width])
|
||
child = child.deepcopy()
|
||
child.position_x = 0
|
||
child.position_y = 0
|
||
parent = boxes.BlockContainerBox.anonymous_from(
|
||
containing_block, ())
|
||
resolve_percentages(parent, containing_block)
|
||
parent.position_x = child.position_x
|
||
parent.position_y = child.position_y
|
||
parent.width = width
|
||
parent.height = height
|
||
bottom_space = -inf
|
||
child, _, _, _, _, _ = block_level_layout(
|
||
context, child, bottom_space, skip_stack=None,
|
||
containing_block=parent, page_is_empty=True,
|
||
absolute_boxes=[], fixed_boxes=[])
|
||
height = max(height, child.margin_height())
|
||
if min_function in ('min-content', 'max_content', 'auto'):
|
||
sizes[0] = height
|
||
if max_function in ('min-content', 'max_content'):
|
||
sizes[1] = height
|
||
if None not in sizes:
|
||
sizes[1] = max(sizes)
|
||
continue
|
||
if min_function == 'min-content':
|
||
sizes[0] = max(0, *(
|
||
min_content_width(context, child) for child in children))
|
||
elif min_function == 'max-content':
|
||
sizes[0] = max(0, *(
|
||
max_content_width(context, child) for child in children))
|
||
elif min_function == 'auto':
|
||
# TODO: Handle min-/max-content constrained parents.
|
||
# TODO: Use real "minimum contributions".
|
||
sizes[0] = max(0, *(
|
||
min_content_width(context, child) for child in children))
|
||
if max_function == 'min-content':
|
||
sizes[1] = max(
|
||
min_content_width(context, child) for child in children)
|
||
elif (max_function in ('auto', 'max-content') or
|
||
max_function[0] == 'fit_content()'):
|
||
sizes[1] = max(
|
||
max_content_width(context, child) for child in children)
|
||
if None not in sizes:
|
||
sizes[1] = max(sizes)
|
||
# 1.2.3 Increase sizes to accommodate items spanning content-sized tracks.
|
||
spans = sorted({
|
||
width if direction == 'x' else height
|
||
for (_, _, width, height) in children_positions.values()
|
||
if (width if direction == 'x' else height) >= 2})
|
||
for span in spans:
|
||
tracks_children = [[] for _ in range(len(sizing_functions))]
|
||
iterable = enumerate(children_positions.items())
|
||
for i, (child, (x, y, width, height)) in iterable:
|
||
coord, size = (x, width) if direction == 'x' else (y, height)
|
||
if size != span:
|
||
continue
|
||
for _, max_function in sizing_functions[i:i+span+1]:
|
||
if _is_fr(max_function):
|
||
break
|
||
else:
|
||
tracks_children[coord - implicit_start].append(child)
|
||
# 1.2.3.1 For intrinsic minimums.
|
||
# TODO: Respect min-/max-content constraint.
|
||
_distribute_extra_space(
|
||
'min', 'intrinsic', 'mininum', tracks_children,
|
||
sizing_functions, tracks_sizes, span, direction, context,
|
||
containing_block)
|
||
# 1.2.3.2 For content-based minimums.
|
||
_distribute_extra_space(
|
||
'min', 'content-based', 'min-content', tracks_children,
|
||
sizing_functions, tracks_sizes, span, direction, context,
|
||
containing_block)
|
||
# 1.2.3.3 For max-content minimums.
|
||
# TODO: Respect max-content constraint.
|
||
_distribute_extra_space(
|
||
'min', 'max-content', 'max-content', tracks_children,
|
||
sizing_functions, tracks_sizes, span, direction, context,
|
||
containing_block)
|
||
# 1.2.3.4 Increase growth limit.
|
||
for sizes in tracks_sizes:
|
||
if None not in sizes:
|
||
sizes[1] = max(sizes)
|
||
iterable = enumerate(children_positions.items())
|
||
for i, (child, (x, y, width, height)) in iterable:
|
||
coord, size = (x, width) if direction == 'x' else (y, height)
|
||
if size != span:
|
||
continue
|
||
for _, max_function in sizing_functions[i:i+span+1]:
|
||
if _is_fr(max_function):
|
||
break
|
||
else:
|
||
tracks_children[coord - implicit_start].append(child)
|
||
# 1.2.3.5 For intrinsic maximums.
|
||
_distribute_extra_space(
|
||
'max', 'intrinsic', 'min-content', tracks_children,
|
||
sizing_functions, tracks_sizes, span, direction, context,
|
||
containing_block)
|
||
# 1.2.3.6 For max-content maximums.
|
||
_distribute_extra_space(
|
||
'max', 'max-content', 'max-content', tracks_children,
|
||
sizing_functions, tracks_sizes, span, direction, context,
|
||
containing_block)
|
||
# 1.2.4 Increase sizes to accommodate items spanning flexible tracks.
|
||
# TODO: Support spans for flexible tracks.
|
||
# 1.2.5 Fix infinite growth limits.
|
||
for sizes in tracks_sizes:
|
||
if sizes[1] is inf:
|
||
sizes[1] = sizes[0]
|
||
# 1.3 Maximize tracks.
|
||
if box_size == 'auto':
|
||
free_space = None
|
||
else:
|
||
free_space = (
|
||
box_size -
|
||
sum(size[0] for size in tracks_sizes) -
|
||
(len(tracks_sizes) - 1) * gap)
|
||
if free_space is not None and free_space > 0:
|
||
distributed_free_space = free_space / len(tracks_sizes)
|
||
for i, sizes in enumerate(tracks_sizes):
|
||
base_size, growth_limit = sizes
|
||
if base_size + distributed_free_space > growth_limit:
|
||
sizes[0] = growth_limit
|
||
free_space -= growth_limit - base_size
|
||
else:
|
||
sizes[0] += distributed_free_space
|
||
free_space -= distributed_free_space
|
||
# TODO: Respect max-width/-height.
|
||
# 1.4 Expand flexible tracks.
|
||
inflexible_tracks = set()
|
||
if free_space is not None and free_space <= 0:
|
||
# TODO: Respect min-content constraint.
|
||
flex_fraction = 0
|
||
elif free_space is not None:
|
||
stop = False
|
||
while not stop:
|
||
leftover_space = free_space
|
||
flex_factor_sum = 0
|
||
iterable = enumerate(zip(tracks_sizes, sizing_functions))
|
||
for i, (sizes, (_, max_function)) in iterable:
|
||
if _is_fr(max_function):
|
||
leftover_space += sizes[0]
|
||
if i not in inflexible_tracks:
|
||
flex_factor_sum += max_function.value
|
||
flex_factor_sum = max(1, flex_factor_sum)
|
||
hypothetical_fr_size = leftover_space / flex_factor_sum
|
||
stop = True
|
||
iterable = enumerate(zip(tracks_sizes, sizing_functions))
|
||
for i, (sizes, (_, max_function)) in iterable:
|
||
if i not in inflexible_tracks and _is_fr(max_function):
|
||
if hypothetical_fr_size * max_function.value < sizes[0]:
|
||
inflexible_tracks.add(i)
|
||
free_space -= sizes[0]
|
||
stop = free_space > 0
|
||
flex_fraction = hypothetical_fr_size
|
||
else:
|
||
flex_fraction = 0
|
||
iterable = zip(tracks_sizes, sizing_functions)
|
||
for sizes, (_, max_function) in iterable:
|
||
if _is_fr(max_function):
|
||
if max_function.value > 1:
|
||
flex_fraction = max(
|
||
flex_fraction, max_function.value * sizes[0])
|
||
else:
|
||
flex_fraction = max(flex_fraction, sizes[0])
|
||
# TODO: Respect grid items max-content contribution.
|
||
# TODO: Respect min-* constraint.
|
||
iterable = enumerate(zip(tracks_sizes, sizing_functions))
|
||
for i, (sizes, (_, max_function)) in iterable:
|
||
if _is_fr(max_function) and i not in inflexible_tracks:
|
||
if flex_fraction * max_function.value > sizes[0]:
|
||
if free_space is not None:
|
||
free_space -= flex_fraction * max_function.value
|
||
sizes[0] = flex_fraction * max_function.value
|
||
# 1.5 Expand stretched auto tracks.
|
||
justify_content = containing_block.style['justify_content']
|
||
align_content = containing_block.style['align_content']
|
||
x_stretch = (
|
||
direction == 'x' and set(justify_content) & {'normal', 'stretch'})
|
||
y_stretch = (
|
||
direction == 'y' and set(align_content) & {'normal', 'stretch'})
|
||
if (x_stretch or y_stretch) and free_space is not None and free_space > 0:
|
||
auto_tracks_sizes = [
|
||
sizes for sizes, (min_function, _)
|
||
in zip(tracks_sizes, sizing_functions)
|
||
if min_function == 'auto']
|
||
if auto_tracks_sizes:
|
||
distributed_free_space = free_space / len(auto_tracks_sizes)
|
||
for sizes in auto_tracks_sizes:
|
||
sizes[0] += distributed_free_space
|
||
|
||
return tracks_sizes
|
||
|
||
|
||
def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
||
page_is_empty, absolute_boxes, fixed_boxes):
|
||
context.create_block_formatting_context()
|
||
|
||
# Define explicit grid
|
||
grid_areas = box.style['grid_template_areas']
|
||
flow = box.style['grid_auto_flow']
|
||
auto_rows = cycle(box.style['grid_auto_rows'])
|
||
auto_columns = cycle(box.style['grid_auto_columns'])
|
||
auto_rows_back = cycle(box.style['grid_auto_rows'][::-1])
|
||
auto_columns_back = cycle(box.style['grid_auto_columns'][::-1])
|
||
column_gap = box.style['column_gap']
|
||
if column_gap == 'normal':
|
||
column_gap = 0
|
||
else:
|
||
refer_to = containing_block.width if box.width == 'auto' else box.width
|
||
column_gap = percentage(column_gap, refer_to)
|
||
row_gap = box.style['row_gap']
|
||
if row_gap == 'normal':
|
||
row_gap = 0
|
||
else:
|
||
refer_to = 0 if box.height == 'auto' else box.height
|
||
row_gap = percentage(row_gap, refer_to)
|
||
|
||
if grid_areas == 'none':
|
||
grid_areas = ((None,),)
|
||
grid_areas = [list(row) for row in grid_areas]
|
||
|
||
rows = _get_template_tracks(box.style['grid_template_rows'])
|
||
columns = _get_template_tracks(box.style['grid_template_columns'])
|
||
|
||
# Adjust rows number
|
||
grid_areas_columns = len(grid_areas[0]) if grid_areas else 0
|
||
rows_diff = int((len(rows) - 1) / 2) - len(grid_areas)
|
||
if rows_diff > 0:
|
||
for _ in range(rows_diff):
|
||
grid_areas.append([None] * grid_areas_columns)
|
||
elif rows_diff < 0:
|
||
for _ in range(-rows_diff):
|
||
rows.append(next(auto_rows))
|
||
rows.append([])
|
||
|
||
# Adjust columns number
|
||
columns_diff = int((len(columns) - 1) / 2) - grid_areas_columns
|
||
if columns_diff > 0:
|
||
for row in grid_areas:
|
||
for _ in range(columns_diff):
|
||
row.append(None)
|
||
elif columns_diff < 0:
|
||
for _ in range(-columns_diff):
|
||
columns.append(next(auto_columns))
|
||
columns.append([])
|
||
|
||
# Add implicit line names
|
||
for y, row in enumerate(grid_areas):
|
||
for x, area_name in enumerate(row):
|
||
if area_name is None:
|
||
continue
|
||
start_name = f'{area_name}-start'
|
||
names = [name for row in rows[::2] for name in row]
|
||
if start_name not in names:
|
||
rows[2*y].append(start_name)
|
||
names = [name for column in columns[::2] for name in column]
|
||
if start_name not in names:
|
||
columns[2*x].append(start_name)
|
||
for y, row in enumerate(grid_areas[::-1]):
|
||
for x, area_name in enumerate(row[::-1]):
|
||
if area_name is None:
|
||
continue
|
||
end_name = f'{area_name}-end'
|
||
names = [name for row in rows[::2] for name in row]
|
||
if end_name not in names:
|
||
rows[-2*y-1].append(end_name)
|
||
names = [name for column in columns[::2] for name in column]
|
||
if end_name not in names:
|
||
columns[-2*x-1].append(end_name)
|
||
|
||
# 1. Run the grid placement algorithm.
|
||
|
||
first_flow = 'column' if 'column' in flow else 'row' # auto flow axis
|
||
second_flow = 'row' if 'column' in flow else 'column' # other axis
|
||
first_tracks = rows if first_flow == 'row' else columns
|
||
second_tracks = rows if second_flow == 'row' else columns
|
||
|
||
# 1.1 Position anything that’s not auto-positioned.
|
||
children_positions = {}
|
||
for child in box.children:
|
||
column_start = child.style['grid_column_start']
|
||
column_end = child.style['grid_column_end']
|
||
row_start = child.style['grid_row_start']
|
||
row_end = child.style['grid_row_end']
|
||
|
||
column_placement = _get_placement(
|
||
column_start, column_end, columns[::2])
|
||
row_placement = _get_placement(row_start, row_end, rows[::2])
|
||
|
||
if column_placement and row_placement:
|
||
x, width = column_placement
|
||
y, height = row_placement
|
||
children_positions[child] = (x, y, width, height)
|
||
|
||
# 1.2 Process the items locked to a given row (resp. column).
|
||
children = sorted(box.children, key=lambda item: item.style['order'])
|
||
for child in children:
|
||
if child in children_positions:
|
||
continue
|
||
first_start = child.style[f'grid_{first_flow}_start']
|
||
first_end = child.style[f'grid_{first_flow}_end']
|
||
first_placement = _get_placement(first_start, first_end, first_tracks[::2])
|
||
if not first_placement:
|
||
continue
|
||
second_start = child.style[f'grid_{second_flow}_start']
|
||
second_end = child.style[f'grid_{second_flow}_end']
|
||
second_placement = _get_second_placement(
|
||
first_placement, second_start, second_end, second_tracks,
|
||
children_positions, first_flow, 'dense' in flow)
|
||
if first_flow == 'row':
|
||
y, height = first_placement
|
||
x, width = second_placement
|
||
else:
|
||
x, width = first_placement
|
||
y, height = second_placement
|
||
children_positions[child] = (x, y, width, height)
|
||
|
||
# 1.3 Determine the columns (resp. rows) in the implicit grid.
|
||
# 1.3.1 Start with the columns (resp. rows) from the explicit grid.
|
||
implicit_second_1 = 0
|
||
if second_flow == 'column':
|
||
implicit_second_2 = len(grid_areas[0]) if grid_areas else 0
|
||
else:
|
||
implicit_second_2 = len(grid_areas)
|
||
# 1.3.2 Add columns (resp. rows) to the beginning and end of the implicit grid.
|
||
remaining_grid_items = []
|
||
for child in children:
|
||
if child in children_positions:
|
||
if second_flow == 'column':
|
||
i, _, size, _ = children_positions[child]
|
||
else:
|
||
_, i, _, size = children_positions[child]
|
||
else:
|
||
second_start = child.style[f'grid_{second_flow}_start']
|
||
second_end = child.style[f'grid_{second_flow}_end']
|
||
second_placement = _get_placement(
|
||
second_start, second_end, second_tracks[::2])
|
||
remaining_grid_items.append(child)
|
||
if second_placement:
|
||
i, size = second_placement
|
||
else:
|
||
continue
|
||
implicit_second_1 = min(i, implicit_second_1)
|
||
implicit_second_2 = max(i + size, implicit_second_2)
|
||
# 1.3.3 Add columns (resp. rows) to accommodate max track span.
|
||
for child in remaining_grid_items:
|
||
second_start = child.style[f'grid_{second_flow}_start']
|
||
second_end = child.style[f'grid_{second_flow}_end']
|
||
span = 1
|
||
if second_start != 'auto' and second_start[0] == 'span':
|
||
span = second_start[1]
|
||
elif second_end != 'auto' and second_end[0] == 'span':
|
||
span = second_end[1]
|
||
implicit_second_2 = max(implicit_second_1 + (span or 1), implicit_second_2)
|
||
|
||
# 1.4 Position the remaining grid items.
|
||
implicit_first_1 = 0
|
||
if first_flow == 'row':
|
||
implicit_first_2 = len(grid_areas)
|
||
else:
|
||
implicit_first_2 = len(grid_areas[0]) if grid_areas else 0
|
||
for position in children_positions.values():
|
||
if first_flow == 'row':
|
||
_, i, _, size = position
|
||
else:
|
||
i, _, size, _ = position
|
||
implicit_first_1 = min(i, implicit_first_1)
|
||
implicit_first_2 = max(i + size, implicit_first_2)
|
||
cursor_first, cursor_second = implicit_first_1, implicit_second_1
|
||
if 'dense' in flow:
|
||
for child in remaining_grid_items:
|
||
first_start = child.style[f'grid_{first_flow}_start']
|
||
first_end = child.style[f'grid_{first_flow}_end']
|
||
second_start = child.style[f'grid_{second_flow}_start']
|
||
second_end = child.style[f'grid_{second_flow}_end']
|
||
second_placement = _get_placement(
|
||
second_start, second_end, second_tracks[::2])
|
||
if second_placement:
|
||
# 1. Set the row (resp. column) position of the cursor.
|
||
cursor_first = implicit_first_1
|
||
second_i, second_size = second_placement
|
||
cursor_second = second_i
|
||
# 2. Increment the cursor’s row (resp. column) position.
|
||
for first_i in count(cursor_first):
|
||
if first_start == 'auto':
|
||
first_i, first_size = _get_placement(
|
||
(None, first_i + 1, None), first_end, first_tracks[::2])
|
||
else:
|
||
assert first_start[0] == 'span'
|
||
span = _get_span(first_start)
|
||
first_i, first_size = _get_placement(
|
||
first_start, (None, first_i + 1 + span, None),
|
||
first_tracks[::2])
|
||
if first_i < cursor_first:
|
||
continue
|
||
for _ in range(first_i, first_i + first_size):
|
||
if first_flow == 'row':
|
||
x, y = second_i, first_i
|
||
width, height = second_size, first_size
|
||
else:
|
||
x, y = first_i, second_i
|
||
width, height = first_size, second_size
|
||
intersect = _intersect_with_children(
|
||
x, y, width, height, children_positions.values())
|
||
if intersect:
|
||
# Child intersects with a positioned child on
|
||
# current row.
|
||
break
|
||
else:
|
||
# Child doesn’t intersect with any positioned child on
|
||
# any row.
|
||
break
|
||
first_diff = first_i + first_size - implicit_first_2
|
||
if first_diff > 0:
|
||
implicit_first_2 += first_diff
|
||
# 3. Set the item’s row-start line.
|
||
if first_flow == 'row':
|
||
x, y = second_i, first_i
|
||
width, height = second_size, first_size
|
||
else:
|
||
x, y = first_i, second_i
|
||
width, height = first_size, second_size
|
||
children_positions[child] = (x, y, width, height)
|
||
else:
|
||
# 1. Set the cursor’s row and column positions.
|
||
cursor_first, cursor_second = implicit_first_1, implicit_second_1
|
||
while True:
|
||
# 2. Increment the column (resp. row) position of the cursor.
|
||
first_i = cursor_first
|
||
for second_i in range(cursor_second, implicit_second_2):
|
||
if first_start == 'auto':
|
||
first_i, first_size = _get_placement(
|
||
(None, first_i + 1, None), first_end, first_tracks[::2])
|
||
else:
|
||
assert first_start[0] == 'span'
|
||
span = _get_span(first_start)
|
||
first_i, first_size = _get_placement(
|
||
first_start, (None, first_i + 1 + span, None),
|
||
first_tracks[::2])
|
||
if second_start == 'auto':
|
||
second_i, second_size = _get_placement(
|
||
(None, second_i + 1, None), second_end,
|
||
second_tracks[::2])
|
||
else:
|
||
span = _get_span(second_start)
|
||
second_i, second_size = _get_placement(
|
||
second_start, (None, second_i + 1 + span, None),
|
||
second_tracks[::2])
|
||
if first_flow == 'row':
|
||
x, y = second_i, first_i
|
||
width, height = second_size, first_size
|
||
else:
|
||
x, y = first_i, second_i
|
||
width, height = first_size, second_size
|
||
intersect = _intersect_with_children(
|
||
x, y, width, height, children_positions.values())
|
||
overflow = second_i + second_size > implicit_second_2
|
||
if intersect or overflow:
|
||
# Child intersects with a positioned child or overflows.
|
||
continue
|
||
else:
|
||
# Free place found.
|
||
# 3. Set the item’s row-/column-start lines.
|
||
children_positions[child] = (x, y, width, height)
|
||
first_diff = (
|
||
cursor_first + first_size - 1 - implicit_first_2)
|
||
if first_diff > 0:
|
||
implicit_first_2 += first_diff
|
||
break
|
||
else:
|
||
# No room found.
|
||
# 2. Return to the previous step.
|
||
cursor_first += 1
|
||
first_diff = cursor_first + 1 - implicit_first_2
|
||
if first_diff > 0:
|
||
implicit_first_2 += first_diff
|
||
cursor_second = implicit_second_1
|
||
continue
|
||
break
|
||
else:
|
||
for child in remaining_grid_items:
|
||
first_start = child.style[f'grid_{first_flow}_start']
|
||
first_end = child.style[f'grid_{first_flow}_end']
|
||
second_start = child.style[f'grid_{second_flow}_start']
|
||
second_end = child.style[f'grid_{second_flow}_end']
|
||
second_placement = _get_placement(
|
||
second_start, second_end, second_tracks[::2])
|
||
if second_placement:
|
||
# 1. Set the column (resp. row) position of the cursor.
|
||
second_i, second_size = second_placement
|
||
if second_i < cursor_second:
|
||
cursor_first += 1
|
||
cursor_second = second_i
|
||
# 2. Increment the cursor’s row (resp. column) position.
|
||
for cursor_first in count(cursor_first):
|
||
if first_start == 'auto':
|
||
first_i, first_size = _get_placement(
|
||
(None, cursor_first + 1, None), first_end,
|
||
first_tracks[::2])
|
||
else:
|
||
assert first_start[0] == 'span'
|
||
span = _get_span(first_start)
|
||
first_i, first_size = _get_placement(
|
||
first_start, (None, first_i + 1 + span, None),
|
||
first_tracks[::2])
|
||
if first_i < cursor_first:
|
||
continue
|
||
for row in range(first_i, first_i + first_size):
|
||
if first_flow == 'row':
|
||
x, y = second_i, first_i
|
||
width, height = second_size, first_size
|
||
else:
|
||
x, y = first_i, second_i
|
||
width, height = first_size, second_size
|
||
intersect = _intersect_with_children(
|
||
x, y, width, height, children_positions.values())
|
||
if intersect:
|
||
# Child intersects with a positioned child on
|
||
# current row.
|
||
break
|
||
else:
|
||
# Child doesn’t intersect with any positioned child on
|
||
# any row.
|
||
break
|
||
first_diff = first_i + first_size - implicit_first_2
|
||
if first_diff > 0:
|
||
implicit_first_2 += first_diff
|
||
# 3. Set the item’s row-start line.
|
||
children_positions[child] = (x, y, width, height)
|
||
else:
|
||
while True:
|
||
# 1. Increment the column position of the cursor.
|
||
first_i = cursor_first
|
||
for second_i in range(cursor_second, implicit_second_2):
|
||
if first_start == 'auto':
|
||
first_i, first_size = _get_placement(
|
||
(None, first_i + 1, None), first_end, first_tracks[::2])
|
||
else:
|
||
span = _get_span(first_start)
|
||
first_i, first_size = _get_placement(
|
||
first_start, (None, first_i + 1 + span, None),
|
||
first_tracks[::2])
|
||
if second_start == 'auto':
|
||
second_i, second_size = _get_placement(
|
||
(None, second_i + 1, None), second_end,
|
||
second_tracks[::2])
|
||
else:
|
||
span = _get_span(second_start)
|
||
second_i, second_size = _get_placement(
|
||
second_start, (None, second_i + 1 + span, None),
|
||
second_tracks[::2])
|
||
if first_flow == 'row':
|
||
x, y = second_i, first_i
|
||
width, height = second_size, first_size
|
||
else:
|
||
x, y = first_i, second_i
|
||
width, height = first_size, second_size
|
||
intersect = _intersect_with_children(
|
||
x, y, width, height, children_positions.values())
|
||
overflow = second_i + second_size > implicit_second_2
|
||
if intersect or overflow:
|
||
# Child intersects with a positioned child or overflows.
|
||
continue
|
||
else:
|
||
# Free place found.
|
||
# 2. Set the item’s row-/column-start lines.
|
||
children_positions[child] = (x, y, width, height)
|
||
break
|
||
else:
|
||
# No room found.
|
||
# 2. Return to the previous step.
|
||
cursor_first += 1
|
||
first_diff = cursor_first + 1 - implicit_first_2
|
||
if first_diff > 0:
|
||
implicit_first_2 += first_diff
|
||
cursor_second = implicit_second_1
|
||
continue
|
||
break
|
||
|
||
if first_flow == 'row':
|
||
implicit_x1, implicit_x2 = implicit_second_1, implicit_second_2
|
||
implicit_y1, implicit_y2 = implicit_first_1, implicit_first_2
|
||
else:
|
||
implicit_x1, implicit_x2 = implicit_first_1, implicit_first_2
|
||
implicit_y1, implicit_y2 = implicit_second_1, implicit_second_2
|
||
for _ in range(0 - implicit_x1):
|
||
columns.insert(0, next(auto_columns_back))
|
||
columns.insert(0, [])
|
||
for _ in range(len(grid_areas[0]) if grid_areas else 0, implicit_x2):
|
||
columns.append(next(auto_columns))
|
||
columns.append([])
|
||
for _ in range(0 - implicit_y1):
|
||
rows.insert(0, next(auto_rows_back))
|
||
rows.insert(0, [])
|
||
for _ in range(len(grid_areas), implicit_y2):
|
||
rows.append(next(auto_rows))
|
||
rows.append([])
|
||
|
||
# 2. Find the size of the grid container.
|
||
|
||
if isinstance(box, boxes.GridBox):
|
||
from .block import block_level_width
|
||
block_level_width(box, containing_block)
|
||
else:
|
||
assert isinstance(box, boxes.InlineGridBox)
|
||
from .inline import inline_block_width
|
||
inline_block_width(box, context, containing_block)
|
||
if box.width == 'auto':
|
||
# TODO: Calculate max-width.
|
||
box.width = containing_block.width
|
||
|
||
# 3. Run the grid sizing algorithm.
|
||
|
||
# 3.0 List min/max sizing functions.
|
||
row_sizing_functions = [_get_sizing_functions(row) for row in rows[1::2]]
|
||
column_sizing_functions = [
|
||
_get_sizing_functions(column) for column in columns[1::2]]
|
||
|
||
# 3.1 Resolve the sizes of the grid columns.
|
||
columns_sizes = _resolve_tracks_sizes(
|
||
column_sizing_functions, box.width, children_positions, implicit_second_1,
|
||
'x', column_gap, context, box)
|
||
|
||
# 3.2 Resolve the sizes of the grid rows.
|
||
rows_sizes = _resolve_tracks_sizes(
|
||
row_sizing_functions, box.height, children_positions, implicit_y1, 'y',
|
||
row_gap, context, box, [size for size, _ in columns_sizes])
|
||
|
||
# 3.3 Re-resolve the sizes of the grid columns with min-/max-content.
|
||
# TODO: Re-resolve.
|
||
|
||
# 3.4 Re-re-resolve the sizes of the grid columns with min-/max-content.
|
||
# TODO: Re-re-resolve.
|
||
|
||
# 3.5 Align the tracks within the grid container.
|
||
# TODO: Support safe/unsafe.
|
||
justify_content = set(box.style['justify_content'])
|
||
x = box.content_box_x()
|
||
free_width = max(0, box.width - sum(size for size, _ in columns_sizes))
|
||
columns_positions = []
|
||
columns_number = len(columns_sizes)
|
||
if justify_content & {'center'}:
|
||
x += free_width / 2
|
||
for size, _ in columns_sizes:
|
||
columns_positions.append(x)
|
||
x += size + column_gap
|
||
elif justify_content & {'right', 'end', 'flex-end'}:
|
||
x += free_width
|
||
for size, _ in columns_sizes:
|
||
columns_positions.append(x)
|
||
x += size + column_gap
|
||
elif justify_content & {'space-around'}:
|
||
x += free_width / 2 / columns_number
|
||
for size, _ in columns_sizes:
|
||
columns_positions.append(x)
|
||
x += size + free_width / columns_number + column_gap
|
||
elif justify_content & {'space-between'}:
|
||
for size, _ in columns_sizes:
|
||
columns_positions.append(x)
|
||
if columns_number >= 2:
|
||
x += size + free_width / (columns_number - 1) + column_gap
|
||
elif justify_content & {'space-evenly'}:
|
||
x += free_width / (columns_number + 1)
|
||
for size, _ in columns_sizes:
|
||
columns_positions.append(x)
|
||
x += size + free_width / (columns_number + 1) + column_gap
|
||
else:
|
||
for size, _ in columns_sizes:
|
||
columns_positions.append(x)
|
||
x += size + column_gap
|
||
|
||
align_content = set(box.style['align_content'])
|
||
y = box.content_box_y()
|
||
if box.height == 'auto':
|
||
free_height = 0
|
||
else:
|
||
free_height = (
|
||
box.height -
|
||
sum(size for size, _ in rows_sizes) -
|
||
(len(rows_sizes) - 1) * row_gap)
|
||
free_height = max(0, free_height)
|
||
rows_positions = []
|
||
rows_number = len(rows_sizes)
|
||
if align_content & {'center'}:
|
||
y += free_height / 2
|
||
for size, _ in rows_sizes:
|
||
rows_positions.append(y)
|
||
y += size + row_gap
|
||
elif align_content & {'right', 'end', 'flex-end'}:
|
||
y += free_height
|
||
for size, _ in rows_sizes:
|
||
rows_positions.append(y)
|
||
y += size + row_gap
|
||
elif align_content & {'space-around'}:
|
||
y += free_height / 2 / rows_number
|
||
for size, _ in rows_sizes:
|
||
rows_positions.append(y)
|
||
y += size + free_height / rows_number + row_gap
|
||
elif align_content & {'space-between'}:
|
||
for size, _ in rows_sizes:
|
||
rows_positions.append(y)
|
||
if rows_number >= 2:
|
||
y += size + free_height / (rows_number - 1) + row_gap
|
||
elif align_content & {'space-evenly'}:
|
||
y += free_height / (rows_number + 1)
|
||
for size, _ in rows_sizes:
|
||
rows_positions.append(y)
|
||
y += size + free_height / (rows_number + 1) + row_gap
|
||
else:
|
||
if align_content & {'baseline'}:
|
||
# TODO: Support baseline value.
|
||
LOGGER.warning('Baseline alignment is not supported for grid layout')
|
||
for size, _ in rows_sizes:
|
||
rows_positions.append(y)
|
||
y += size + row_gap
|
||
|
||
# 4. Lay out the grid items into their respective containing blocks.
|
||
# Find resume_at row.
|
||
this_page_children = []
|
||
resume_row = None
|
||
if skip_stack:
|
||
skip_row = next(iter(skip_stack))
|
||
skip_height = (
|
||
sum(size for size, _ in rows_sizes[:skip_row]) +
|
||
(len(rows_sizes[:skip_row]) - 1) * row_gap)
|
||
else:
|
||
skip_row = 0
|
||
skip_height = 0
|
||
resume_at = None
|
||
total_height = (
|
||
sum(size for size, _ in rows_sizes[skip_row:]) +
|
||
(len(rows_sizes[skip_row:]) - 1) * row_gap)
|
||
row_lines_positions = (
|
||
rows_positions[skip_row + 1:] + [box.content_box_y() + total_height])
|
||
for i, row_y in enumerate(row_lines_positions, start=skip_row + 1):
|
||
if context.overflows_page(bottom_space, row_y - skip_height):
|
||
if not page_is_empty:
|
||
if i == 1:
|
||
return None, None, {'break': 'any', 'page': None}, [], False
|
||
resume_row = i - 1
|
||
resume_at = {i-1: None}
|
||
for child in children:
|
||
_, y, _, _ = children_positions[child]
|
||
if skip_row <= y <= i-2:
|
||
this_page_children.append(child)
|
||
break
|
||
page_is_empty = False
|
||
else:
|
||
for child in children:
|
||
_, y, _, _ = children_positions[child]
|
||
if skip_row <= y:
|
||
this_page_children.append(child)
|
||
if box.height == 'auto':
|
||
box.height = (
|
||
sum(size for size, _ in rows_sizes[skip_row:resume_row]) +
|
||
(len(rows_sizes[skip_row:resume_row]) - 1) * row_gap)
|
||
# Lay out grid items.
|
||
justify_items = set(box.style['justify_items'])
|
||
align_items = set(box.style['align_items'])
|
||
new_children = []
|
||
baseline = None
|
||
next_page = {'break': 'any', 'page': None}
|
||
from .block import block_level_layout
|
||
for child in this_page_children:
|
||
x, y, width, height = children_positions[child]
|
||
index = box.children.index(child)
|
||
if skip_stack and skip_stack.get(y) and index in skip_stack[y]:
|
||
child_skip_stack = skip_stack[y][index]
|
||
else:
|
||
child_skip_stack = None
|
||
child = child.deepcopy()
|
||
child.position_x = columns_positions[x]
|
||
child.position_y = rows_positions[y] - skip_height
|
||
resolve_percentages(child, box)
|
||
width = (
|
||
sum(size for size, _ in columns_sizes[x:x+width]) +
|
||
(width - 1) * column_gap)
|
||
height = (
|
||
sum(size for size, _ in rows_sizes[y:y+height]) +
|
||
(height - 1) * row_gap)
|
||
|
||
# TODO: Apply auto margin.
|
||
if child.margin_top == 'auto':
|
||
child.margin_top = 0
|
||
if child.margin_right == 'auto':
|
||
child.margin_right = 0
|
||
if child.margin_bottom == 'auto':
|
||
child.margin_bottom = 0
|
||
if child.margin_left == 'auto':
|
||
child.margin_left = 0
|
||
|
||
child_width = width - (
|
||
child.margin_left + child.border_left_width + child.padding_left +
|
||
child.margin_right + child.border_right_width + child.padding_right)
|
||
child_height = height - (
|
||
child.margin_top + child.border_top_width + child.padding_top +
|
||
child.margin_bottom + child.border_bottom_width + child.padding_bottom)
|
||
|
||
justify_self = set(child.style['justify_self'])
|
||
if justify_self & {'auto'}:
|
||
justify_self = justify_items
|
||
if justify_self & {'normal', 'stretch'}:
|
||
if child.style['width'] == 'auto':
|
||
child.style['width'] = Dimension(child_width, 'px')
|
||
align_self = set(child.style['align_self'])
|
||
if align_self & {'auto'}:
|
||
align_self = align_items
|
||
if align_self & {'normal', 'stretch'}:
|
||
if child.style['height'] == 'auto':
|
||
child.style['height'] = Dimension(child_height, 'px')
|
||
|
||
# TODO: Find a better solution for the layout.
|
||
parent = boxes.BlockContainerBox.anonymous_from(box, ())
|
||
resolve_percentages(parent, containing_block)
|
||
parent.position_x = child.position_x
|
||
parent.position_y = child.position_y
|
||
parent.width = width
|
||
parent.height = height
|
||
new_child, child_resume_at, child_next_page = block_level_layout(
|
||
context, child, bottom_space, child_skip_stack, parent,
|
||
page_is_empty, absolute_boxes, fixed_boxes)[:3]
|
||
if new_child:
|
||
page_is_empty = False
|
||
# TODO: Support fragmentation in grid items.
|
||
else:
|
||
# TODO: Support fragmentation in grid rows.
|
||
continue
|
||
|
||
if justify_self & {'normal', 'stretch'}:
|
||
new_child.width = max(child_width, new_child.width)
|
||
else:
|
||
new_child.width = max_content_width(context, new_child)
|
||
diff = child_width - new_child.width
|
||
if justify_self & {'center'}:
|
||
new_child.translate(diff / 2, 0)
|
||
elif justify_self & {'right', 'end', 'flex-end', 'self-end'}:
|
||
new_child.translate(diff, 0)
|
||
|
||
# TODO: Apply auto margins.
|
||
if align_self & {'normal', 'stretch'}:
|
||
new_child.height = max(child_height, new_child.height)
|
||
else:
|
||
diff = child_height - new_child.height
|
||
if align_self & {'center'}:
|
||
new_child.translate(0, diff / 2)
|
||
elif align_self & {'end', 'flex-end', 'self-end'}:
|
||
new_child.translate(0, diff)
|
||
|
||
# TODO: Take care of page fragmentation.
|
||
new_children.append(new_child)
|
||
if baseline is None and y == implicit_y1:
|
||
baseline = find_in_flow_baseline(new_child)
|
||
|
||
box = box.copy_with_children(new_children)
|
||
if isinstance(box, boxes.InlineGridBox):
|
||
# TODO: Synthetize a real baseline value.
|
||
LOGGER.warning('Inline grids are not supported')
|
||
box.baseline = baseline or 0
|
||
|
||
context.finish_block_formatting_context(box)
|
||
|
||
return box, resume_at, next_page, [], False
|