ajout fichiers manquant

This commit is contained in:
Ambulance Clerc
2022-03-04 18:47:24 +01:00
parent 2e92cb8cf4
commit aa5b7f9254
170 changed files with 554027 additions and 1 deletions

View File

@@ -0,0 +1,975 @@
"""
weasyprint.layout.tables
------------------------
Layout for tables and internal table boxes.
"""
from ..formatting_structure import boxes
from ..logger import LOGGER
from .percent import resolve_one_percentage, resolve_percentages
from .preferred import max_content_width, table_and_columns_preferred_widths
def table_layout(context, table, bottom_space, skip_stack, containing_block,
page_is_empty, absolute_boxes, fixed_boxes):
"""Layout for a table box."""
from .block import (
block_container_layout, block_level_page_break,
find_earlier_page_break)
table.remove_decoration(start=skip_stack is not None, end=False)
column_widths = table.column_widths
if table.style['border_collapse'] == 'separate':
border_spacing_x, border_spacing_y = table.style['border_spacing']
else:
border_spacing_x = 0
border_spacing_y = 0
column_positions = table.column_positions = []
rows_left_x = table.content_box_x() + border_spacing_x
if table.style['direction'] == 'ltr':
position_x = table.content_box_x()
rows_x = position_x + border_spacing_x
for width in column_widths:
position_x += border_spacing_x
column_positions.append(position_x)
position_x += width
rows_width = position_x - rows_x
else:
position_x = table.content_box_x() + table.width
rows_x = position_x - border_spacing_x
for width in column_widths:
position_x -= border_spacing_x
position_x -= width
column_positions.append(position_x)
rows_width = rows_x - position_x
if table.style['border_collapse'] == 'collapse':
table.skip_cell_border_top = False
table.skip_cell_border_bottom = False
split_cells = False
if skip_stack:
(skipped_groups, group_skip_stack), = skip_stack.items()
if group_skip_stack:
(skipped_rows, cells_skip_stack), = group_skip_stack.items()
if cells_skip_stack:
split_cells = True
else:
skipped_rows = 0
for group in table.children[:skipped_groups]:
skipped_rows += len(group.children)
else:
skipped_rows = 0
if not split_cells:
_, horizontal_borders = table.collapsed_border_grid
if horizontal_borders:
table.border_top_width = max(
width for _, (_, width, _)
in horizontal_borders[skipped_rows]) / 2
# Make this a sub-function so that many local variables like rows_x
# don't need to be passed as parameters.
def group_layout(group, position_y, bottom_space, page_is_empty,
skip_stack):
resume_at = None
next_page = {'break': 'any', 'page': None}
original_page_is_empty = page_is_empty
resolve_percentages(group, containing_block=table)
group.position_x = rows_left_x
group.position_y = position_y
group.width = rows_width
new_group_children = []
# For each rows, cells for which this is the last row (with rowspan)
ending_cells_by_row = [[] for row in group.children]
is_group_start = skip_stack is None
if is_group_start:
skip = 0
else:
(skip, skip_stack), = skip_stack.items()
for index_row, row in enumerate(group.children[skip:], start=skip):
row.index = index_row
if new_group_children:
page_break = block_level_page_break(
new_group_children[-1], row)
if page_break in ('page', 'recto', 'verso', 'left', 'right'):
next_page['break'] = page_break
resume_at = {index_row: None}
break
resolve_percentages(row, containing_block=table)
row.position_x = rows_left_x
row.position_y = position_y
row.width = rows_width
# Place cells at the top of the row and layout their content
new_row_children = []
for index_cell, cell in enumerate(row.children):
spanned_widths = column_widths[cell.grid_x:][:cell.colspan]
# In the fixed layout the grid width is set by cells in
# the first row and column elements.
# This may be less than the previous value of cell.colspan
# if that would bring the cell beyond the grid width.
cell.colspan = len(spanned_widths)
if cell.colspan == 0:
# The cell is entierly beyond the grid width, remove it
# entierly. Subsequent cells in the same row have greater
# grid_x, so they are beyond too.
cell_index = row.children.index(cell)
ignored_cells = row.children[cell_index:]
LOGGER.warning(
'This table row has more columns than the table, '
f'ignored {len(ignored_cells)} cells: {ignored_cells}')
break
resolve_percentages(cell, containing_block=table)
if table.style['direction'] == 'ltr':
cell.position_x = column_positions[cell.grid_x]
else:
cell.position_x = column_positions[
cell.grid_x + cell.colspan - 1]
cell.position_y = row.position_y
cell.margin_top = 0
cell.margin_left = 0
cell.width = 0
borders_plus_padding = cell.border_width() # with width==0
# TODO: we should remove the number of columns with no
# originating cells to cell.colspan, see
# test_layout_table_auto_49
cell.width = (
sum(spanned_widths) +
border_spacing_x * (cell.colspan - 1) -
borders_plus_padding)
# The computed height is a minimum
cell.computed_height = cell.height
cell.height = 'auto'
if skip_stack:
if index_cell in skip_stack:
cell_skip_stack = skip_stack[index_cell]
else:
cell_skip_stack = {len(cell.children): None}
else:
cell_skip_stack = None
if cell_skip_stack:
cell.remove_decoration(start=True, end=False)
if table.style['border_collapse'] == 'collapse':
table.skip_cell_border_top = True
# First try to render content as if there was already something
# on the page to avoid hitting block_level_layouts TODO. Then
# force to render something if the page is actually empty, or
# just draw an empty cell otherwise. See
# test_table_break_children_margin.
new_cell, cell_resume_at, _, _, _ = block_container_layout(
context, cell, bottom_space, cell_skip_stack,
page_is_empty=page_is_empty, absolute_boxes=absolute_boxes,
fixed_boxes=fixed_boxes, adjoining_margins=None,
discard=False)
if new_cell is None:
cell = cell.copy_with_children([])
cell, _, _, _, _ = block_container_layout(
context, cell, bottom_space, cell_skip_stack,
page_is_empty=True, absolute_boxes=[],
fixed_boxes=[], adjoining_margins=None,
discard=False)
cell_resume_at = {0: None}
else:
cell = new_cell
cell.remove_decoration(
start=cell_skip_stack is not None,
end=cell_resume_at is not None)
if cell_resume_at:
if resume_at is None:
resume_at = {index_row: {}}
resume_at[index_row][index_cell] = cell_resume_at
cell.empty = not any(
child.is_floated() or child.is_in_normal_flow()
for child in cell.children)
cell.content_height = cell.height
if cell.computed_height != 'auto':
cell.height = max(cell.height, cell.computed_height)
new_row_children.append(cell)
if resume_at and not page_is_empty:
if row.style['break_inside'] in ('avoid', 'avoid-page'):
resume_at = {index_row: {}}
break
row = row.copy_with_children(new_row_children)
# Table height algorithm
# http://www.w3.org/TR/CSS21/tables.html#height-layout
# cells with vertical-align: baseline
baseline_cells = []
for cell in row.children:
vertical_align = cell.style['vertical_align']
if vertical_align in ('top', 'middle', 'bottom'):
cell.vertical_align = vertical_align
else:
# Assume 'baseline' for any other value
cell.vertical_align = 'baseline'
cell.baseline = cell_baseline(cell)
baseline_cells.append(cell)
if baseline_cells:
row.baseline = max(cell.baseline for cell in baseline_cells)
for cell in baseline_cells:
extra = row.baseline - cell.baseline
if cell.baseline != row.baseline and extra:
add_top_padding(cell, extra)
# row height
for cell in row.children:
ending_cells_by_row[cell.rowspan - 1].append(cell)
ending_cells = ending_cells_by_row.pop(0)
if ending_cells: # in this row
if row.height == 'auto':
row_bottom_y = max(
cell.position_y + cell.border_height()
for cell in ending_cells)
row.height = max(row_bottom_y - row.position_y, 0)
else:
row.height = max(row.height, max(
row_cell.height for row_cell in ending_cells))
row_bottom_y = row.position_y + row.height
else:
row_bottom_y = row.position_y
row.height = 0
if not baseline_cells:
row.baseline = row_bottom_y
# Add extra padding to make the cells the same height as the row
# and honor vertical-align
for cell in ending_cells:
cell_bottom_y = cell.position_y + cell.border_height()
extra = row_bottom_y - cell_bottom_y
if extra:
if cell.vertical_align == 'bottom':
add_top_padding(cell, extra)
elif cell.vertical_align == 'middle':
extra /= 2.
add_top_padding(cell, extra)
cell.padding_bottom += extra
else:
cell.padding_bottom += extra
if cell.computed_height != 'auto':
vertical_align_shift = 0
if cell.vertical_align == 'middle':
vertical_align_shift = (
cell.computed_height - cell.content_height) / 2
elif cell.vertical_align == 'bottom':
vertical_align_shift = (
cell.computed_height - cell.content_height)
if vertical_align_shift > 0:
for child in cell.children:
child.translate(dy=vertical_align_shift)
next_position_y = row.position_y + row.height
if resume_at is None:
next_position_y += border_spacing_y
# Break if one cell was broken
break_cell = False
if resume_at:
values, = list(resume_at.values())
if len(row.children) == len(values):
for cell_resume_at in values.values():
if cell_resume_at != {0: None}:
break_cell = True
break
else:
# No cell was displayed, give up row
next_position_y = float('inf')
page_is_empty = False
resume_at = None
else:
break_cell = True
# Break if this row overflows the page, unless there is no
# other content on the page.
if not page_is_empty and (
next_position_y > context.page_bottom - bottom_space):
if new_group_children:
previous_row = new_group_children[-1]
page_break = block_level_page_break(previous_row, row)
if page_break == 'avoid':
earlier_page_break = find_earlier_page_break(
new_group_children, absolute_boxes, fixed_boxes)
if earlier_page_break:
new_group_children, resume_at = earlier_page_break
break
else:
resume_at = {index_row: None}
break
if original_page_is_empty:
resume_at = {index_row: None}
else:
return None, None, next_page
break
new_group_children.append(row)
position_y = next_position_y
page_is_empty = False
skip_stack = None
if break_cell and table.style['border_collapse'] == 'collapse':
table.skip_cell_border_bottom = True
if break_cell or resume_at:
break
# Do not keep the row group if we made a page break
# before any of its rows or with 'avoid'
if resume_at and not original_page_is_empty and (
group.style['break_inside'] in ('avoid', 'avoid-page') or
not new_group_children):
return None, None, next_page
group = group.copy_with_children(new_group_children)
group.remove_decoration(
start=not is_group_start, end=resume_at is not None)
# Set missing baselines in a second loop because of rowspan
for row in group.children:
if row.baseline is None:
if row.children:
# lowest bottom content edge
row.baseline = max(
cell.content_box_y() + cell.height
for cell in row.children) - row.position_y
else:
row.baseline = 0
group.height = position_y - group.position_y
if group.children:
# The last border spacing is outside of the group.
group.height -= border_spacing_y
return group, resume_at, next_page
def body_groups_layout(skip_stack, position_y, bottom_space,
page_is_empty):
if skip_stack is None:
skip = 0
else:
(skip, skip_stack), = skip_stack.items()
new_table_children = []
resume_at = None
next_page = {'break': 'any', 'page': None}
for i, group in enumerate(table.children[skip:]):
if group.is_header or group.is_footer:
continue
# Index is useless for headers and footers, as we never want to
# break pages after the header or before the footer.
index_group = i + skip
group.index = index_group
if new_table_children:
page_break = block_level_page_break(
new_table_children[-1], group)
if page_break in ('page', 'recto', 'verso', 'left', 'right'):
next_page['break'] = page_break
resume_at = {index_group: None}
break
new_group, resume_at, next_page = group_layout(
group, position_y, bottom_space, page_is_empty, skip_stack)
skip_stack = None
if new_group is None:
if new_table_children:
previous_group = new_table_children[-1]
page_break = block_level_page_break(previous_group, group)
if page_break == 'avoid':
earlier_page_break = find_earlier_page_break(
new_table_children, absolute_boxes, fixed_boxes)
if earlier_page_break is not None:
new_table_children, resume_at = earlier_page_break
break
resume_at = {index_group: None}
else:
return None, None, next_page, position_y
break
new_table_children.append(new_group)
position_y += new_group.height + border_spacing_y
page_is_empty = False
if resume_at:
resume_at = {index_group: resume_at}
break
return new_table_children, resume_at, next_page, position_y
# Layout for row groups, rows and cells
position_y = table.content_box_y()
if skip_stack is None:
position_y += border_spacing_y
initial_position_y = position_y
table_rows = [
child for child in table.children
if not child.is_header and not child.is_footer]
def all_groups_layout():
# If the page is not empty, we try to render the header and the footer
# on it. If the table does not fit on the page, we try to render it on
# the next page.
# If the page is empty and the header and footer are too big, there
# are not rendered. If no row can be rendered because of the header and
# the footer, the header and/or the footer are not rendered.
if page_is_empty:
header_footer_bottom_space = bottom_space
else:
header_footer_bottom_space = -float('inf')
if table.children and table.children[0].is_header:
header = table.children[0]
header, resume_at, next_page = group_layout(
header, position_y, header_footer_bottom_space,
skip_stack=None, page_is_empty=False)
if header and not resume_at:
header_height = header.height + border_spacing_y
else: # Header too big for the page
header = None
else:
header = None
if table.children and table.children[-1].is_footer:
footer = table.children[-1]
footer, resume_at, next_page = group_layout(
footer, position_y, header_footer_bottom_space,
skip_stack=None, page_is_empty=False)
if footer and not resume_at:
footer_height = footer.height + border_spacing_y
else: # Footer too big for the page
footer = None
else:
footer = None
# Don't remove headers and footers if breaks are avoided in line groups
if skip_stack:
skip, = skip_stack
else:
skip = 0
avoid_breaks = False
for group in table.children[skip:]:
if not group.is_header and not group.is_footer:
avoid_breaks = (
group.style['break_inside'] in ('avoid', 'avoid-page'))
break
if header and footer:
# Try with both the header and footer
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack, position_y + header_height,
bottom_space + footer_height, page_is_empty=avoid_breaks))
if new_table_children or not table_rows or not page_is_empty:
footer.translate(dy=end_position_y - footer.position_y)
end_position_y += footer_height
return (
header, new_table_children, footer, end_position_y,
resume_at, next_page)
else:
# We could not fit any content, drop the footer
footer = None
if header and not footer:
# Try with just the header
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack, position_y + header_height, bottom_space,
page_is_empty=avoid_breaks))
if new_table_children or not table_rows or not page_is_empty:
return (
header, new_table_children, footer, end_position_y,
resume_at, next_page)
else:
# We could not fit any content, drop the header
header = None
if footer and not header:
# Try with just the footer
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack, position_y, bottom_space + footer_height,
page_is_empty=avoid_breaks))
if new_table_children or not table_rows or not page_is_empty:
footer.translate(dy=end_position_y - footer.position_y)
end_position_y += footer_height
return (
header, new_table_children, footer, end_position_y,
resume_at, next_page)
else:
# We could not fit any content, drop the footer
footer = None
assert not (header or footer)
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack, position_y, bottom_space, page_is_empty))
return (
header, new_table_children, footer, end_position_y, resume_at,
next_page)
def get_column_cells(table, column):
"""Closure getting the column cells."""
return lambda: [
cell
for row_group in table.children
for row in row_group.children
for cell in row.children
if cell.grid_x == column.grid_x]
header, new_table_children, footer, position_y, resume_at, next_page = (
all_groups_layout())
if new_table_children is None:
assert resume_at is None
table = None
adjoining_margins = []
collapsing_through = False
return (
table, resume_at, next_page, adjoining_margins, collapsing_through)
table = table.copy_with_children(
([header] if header is not None else []) +
new_table_children +
([footer] if footer is not None else []))
table.remove_decoration(
start=skip_stack is not None, end=resume_at is not None)
if table.style['border_collapse'] == 'collapse':
table.skipped_rows = skipped_rows
# If the height property has a bigger value, just add blank space
# below the last row group.
table.height = max(
table.height if table.height != 'auto' else 0,
position_y - table.content_box_y())
# Layout for column groups and columns
columns_height = position_y - initial_position_y
if table.children:
# The last border spacing is below the columns.
columns_height -= border_spacing_y
for group in table.column_groups:
for column in group.children:
resolve_percentages(column, containing_block=table)
if column.grid_x < len(column_positions):
column.position_x = column_positions[column.grid_x]
column.position_y = initial_position_y
column.width = column_widths[column.grid_x]
column.height = columns_height
else:
# Ignore extra empty columns
column.position_x = 0
column.position_y = 0
column.width = 0
column.height = 0
resolve_percentages(group, containing_block=table)
column.get_cells = get_column_cells(table, column)
first = group.children[0]
last = group.children[-1]
group.position_x = first.position_x
group.position_y = initial_position_y
group.width = last.position_x + last.width - first.position_x
group.height = columns_height
if resume_at and not page_is_empty and (
table.style['break_inside'] in ('avoid', 'avoid-page')):
table = None
resume_at = None
adjoining_margins = []
collapsing_through = False
return table, resume_at, next_page, adjoining_margins, collapsing_through
def add_top_padding(box, extra_padding):
"""Increase the top padding of a box.
This also translates the children.
"""
box.padding_top += extra_padding
for child in box.children:
child.translate(dy=extra_padding)
def fixed_table_layout(box):
"""Run the fixed table layout and return a list of column widths.
http://www.w3.org/TR/CSS21/tables.html#fixed-table-layout
"""
table = box.get_wrapped_table()
assert table.width != 'auto'
all_columns = [column for column_group in table.column_groups
for column in column_group.children]
if table.children and table.children[0].children:
first_rowgroup = table.children[0]
first_row_cells = first_rowgroup.children[0].children
else:
first_row_cells = []
num_columns = max(
len(all_columns),
sum(cell.colspan for cell in first_row_cells)
)
# ``None`` means not know yet.
column_widths = [None] * num_columns
# `width` on column boxes
for i, column in enumerate(all_columns):
resolve_one_percentage(column, 'width', table.width)
if column.width != 'auto':
column_widths[i] = column.width
if table.style['border_collapse'] == 'separate':
border_spacing_x, _ = table.style['border_spacing']
else:
border_spacing_x = 0
# `width` on cells of the first row.
i = 0
for cell in first_row_cells:
resolve_percentages(cell, table)
if cell.width != 'auto':
width = cell.border_width()
width -= border_spacing_x * (cell.colspan - 1)
# In the general case, this width affects several columns (through
# colspan) some of which already have a width. Subtract these
# known widths and divide among remaining columns.
columns_without_width = [] # and occupied by this cell
for j in range(i, i + cell.colspan):
if column_widths[j] is None:
columns_without_width.append(j)
else:
width -= column_widths[j]
if columns_without_width:
width_per_column = width / len(columns_without_width)
for j in columns_without_width:
column_widths[j] = width_per_column
del width
i += cell.colspan
del i
# Distribute the remaining space equally on columns that do not have
# a width yet.
all_border_spacing = border_spacing_x * (num_columns + 1)
min_table_width = (sum(w for w in column_widths if w is not None) +
all_border_spacing)
columns_without_width = [i for i, w in enumerate(column_widths)
if w is None]
if columns_without_width and table.width >= min_table_width:
remaining_width = table.width - min_table_width
width_per_column = remaining_width / len(columns_without_width)
for i in columns_without_width:
column_widths[i] = width_per_column
else:
# XXX this is bad, but we were given a broken table to work with...
for i in columns_without_width:
column_widths[i] = 0
# If the sum is less than the table width,
# distribute the remaining space equally
extra_width = table.width - sum(column_widths) - all_border_spacing
if extra_width <= 0:
# substract a negative: widen the table
table.width -= extra_width
elif num_columns:
extra_per_column = extra_width / num_columns
column_widths = [w + extra_per_column for w in column_widths]
# Now we have table.width == sum(column_widths) + all_border_spacing
# with possible floating point rounding errors.
# (unless there is zero column)
table.column_widths = column_widths
def auto_table_layout(context, box, containing_block):
"""Run the auto table layout and return a list of column widths.
http://www.w3.org/TR/CSS21/tables.html#auto-table-layout
"""
table = box.get_wrapped_table()
(table_min_content_width, table_max_content_width,
column_min_content_widths, column_max_content_widths,
column_intrinsic_percentages, constrainedness,
total_horizontal_border_spacing, grid) = \
table_and_columns_preferred_widths(context, box, outer=False)
margins = 0
if box.margin_left != 'auto':
margins += box.margin_left
if box.margin_right != 'auto':
margins += box.margin_right
paddings = table.padding_left + table.padding_right
borders = table.border_left_width + table.border_right_width
cb_width, _ = containing_block
available_width = cb_width - margins - paddings - borders
if table.width == 'auto':
if available_width <= table_min_content_width:
table.width = table_min_content_width
elif available_width < table_max_content_width:
table.width = available_width
else:
table.width = table_max_content_width
else:
if table.width < table_min_content_width:
table.width = table_min_content_width
if not grid:
table.column_widths = []
return
assignable_width = table.width - total_horizontal_border_spacing
min_content_guess = column_min_content_widths[:]
min_content_percentage_guess = column_min_content_widths[:]
min_content_specified_guess = column_min_content_widths[:]
max_content_guess = column_max_content_widths[:]
guesses = (
min_content_guess, min_content_percentage_guess,
min_content_specified_guess, max_content_guess)
for i in range(len(grid)):
if column_intrinsic_percentages[i]:
min_content_percentage_guess[i] = max(
column_intrinsic_percentages[i] / 100 * assignable_width,
column_min_content_widths[i])
min_content_specified_guess[i] = min_content_percentage_guess[i]
max_content_guess[i] = min_content_percentage_guess[i]
elif constrainedness[i]:
min_content_specified_guess[i] = column_min_content_widths[i]
if assignable_width <= sum(max_content_guess):
# Default values shouldn't be used, but we never know.
# See https://github.com/Kozea/WeasyPrint/issues/770
lower_guess = guesses[0]
upper_guess = guesses[-1]
# We have to work around floating point rounding errors here.
# The 1e-9 value comes from PEP 485.
for guess in guesses:
if sum(guess) <= assignable_width * (1 + 1e-9):
lower_guess = guess
else:
break
for guess in guesses[::-1]:
if sum(guess) >= assignable_width * (1 - 1e-9):
upper_guess = guess
else:
break
if upper_guess == lower_guess:
# TODO: Uncomment the assert when bugs #770 and #628 are closed
# Equivalent to "assert assignable_width == sum(upper_guess)"
# assert abs(assignable_width - sum(upper_guess)) <= (
# assignable_width * 1e-9)
table.column_widths = upper_guess
else:
added_widths = [
upper_guess[i] - lower_guess[i] for i in range(len(grid))]
available_ratio = (
(assignable_width - sum(lower_guess)) / sum(added_widths))
table.column_widths = [
lower_guess[i] + added_widths[i] * available_ratio
for i in range(len(grid))]
else:
table.column_widths = max_content_guess
excess_width = assignable_width - sum(max_content_guess)
excess_width = distribute_excess_width(
context, grid, excess_width, table.column_widths, constrainedness,
column_intrinsic_percentages, column_max_content_widths)
if excess_width:
if table_min_content_width < table.width - excess_width:
# Reduce the width of the size from the excess width that has
# not been distributed.
table.width -= excess_width
else:
# Break rules
columns = [i for i, column in enumerate(grid) if any(column)]
for i in columns:
table.column_widths[i] += excess_width / len(columns)
def table_wrapper_width(context, wrapper, containing_block):
"""Find the width of each column and derive the wrapper width."""
table = wrapper.get_wrapped_table()
resolve_percentages(table, containing_block)
if table.style['table_layout'] == 'fixed' and table.width != 'auto':
fixed_table_layout(wrapper)
else:
auto_table_layout(context, wrapper, containing_block)
wrapper.width = table.border_width()
def cell_baseline(cell):
"""Return the y position of a cell baseline from the top of its border box.
See http://www.w3.org/TR/CSS21/tables.html#height-layout
"""
result = find_in_flow_baseline(
cell, baseline_types=(boxes.LineBox, boxes.TableRowBox))
if result is not None:
return result - cell.position_y
else:
# Default to the bottom of the content area.
return cell.border_top_width + cell.padding_top + cell.height
def find_in_flow_baseline(box, last=False, baseline_types=(boxes.LineBox,)):
"""Return the absolute y position for the first (or last) in-flow baseline.
If theres no in-flow baseline, return None.
"""
# TODO: synthetize baseline when needed
# See https://www.w3.org/TR/css-align-3/#synthesize-baseline
if isinstance(box, baseline_types):
return box.position_y + box.baseline
if isinstance(box, boxes.ParentBox) and not isinstance(
box, boxes.TableCaptionBox):
children = reversed(box.children) if last else box.children
for child in children:
if child.is_in_normal_flow():
result = find_in_flow_baseline(child, last, baseline_types)
if result is not None:
return result
def distribute_excess_width(context, grid, excess_width, column_widths,
constrainedness, column_intrinsic_percentages,
column_max_content_widths,
column_slice=slice(0, None)):
"""Distribute available width to columns.
Return excess width left when it's impossible without breaking rules.
See http://dbaron.org/css/intrinsic/#distributetocols
"""
# First group
columns = [
(i + column_slice.start, column)
for i, column in enumerate(grid[column_slice])
if not constrainedness[i + column_slice.start] and
column_intrinsic_percentages[i + column_slice.start] == 0 and
column_max_content_widths[i + column_slice.start] > 0]
if columns:
current_widths = [column_widths[i] for i, column in columns]
differences = [
max(0, width[0] - width[1])
for width in zip(column_max_content_widths, current_widths)]
if sum(differences) > excess_width:
differences = [
difference / sum(differences) * excess_width
for difference in differences]
excess_width -= sum(differences)
for i, difference in enumerate(differences):
column_widths[columns[i][0]] += difference
if excess_width <= 0:
return
# Second group
columns = [
i + column_slice.start for i, column in enumerate(grid[column_slice])
if not constrainedness[i + column_slice.start] and
column_intrinsic_percentages[i + column_slice.start] == 0]
if columns:
for i in columns:
column_widths[i] += excess_width / len(columns)
return
# Third group
columns = [
(i + column_slice.start, column)
for i, column in enumerate(grid[column_slice])
if constrainedness[i + column_slice.start] and
column_intrinsic_percentages[i + column_slice.start] == 0 and
column_max_content_widths[i + column_slice.start] > 0]
if columns:
current_widths = [column_widths[i] for i, column in columns]
differences = [
max(0, width[0] - width[1])
for width in zip(column_max_content_widths, current_widths)]
if sum(differences) > excess_width:
differences = [
difference / sum(differences) * excess_width
for difference in differences]
excess_width -= sum(differences)
for i, difference in enumerate(differences):
column_widths[columns[i][0]] += difference
if excess_width <= 0:
return
# Fourth group
columns = [
(i + column_slice.start, column)
for i, column in enumerate(grid[column_slice])
if column_intrinsic_percentages[i + column_slice.start] > 0]
if columns:
fixed_width = sum(
column_widths[j] for j in range(len(grid))
if j not in [i for i, column in columns])
percentage_width = sum(
column_intrinsic_percentages[i]
for i, column in columns)
if fixed_width and percentage_width >= 100:
# Sum of the percentages are greater than 100%
ratio = excess_width
elif fixed_width == 0:
# No fixed width, let's take the whole excess width
ratio = excess_width
else:
ratio = fixed_width / (100 - percentage_width)
widths = [
column_intrinsic_percentages[i] * ratio for i, column in columns]
current_widths = [column_widths[i] for i, column in columns]
# Allow to reduce the size of the columns to respect the percentage
differences = [
width[0] - width[1]
for width in zip(widths, current_widths)]
if sum(differences) > excess_width:
differences = [
difference / sum(differences) * excess_width
for difference in differences]
excess_width -= sum(differences)
for i, difference in enumerate(differences):
column_widths[columns[i][0]] += difference
if excess_width <= 0:
return
# Bonus: we've tried our best to distribute the extra size, but we
# failed. Instead of blindly distributing the size among all the colums
# and breaking all the rules (as said in the draft), let's try to
# change the columns with no constraint at all, then resize the table,
# and at least break the rules to make the columns fill the table.
# Fifth group, part 1
columns = [
i + column_slice.start for i, column in enumerate(grid[column_slice])
if any(column) and
column_intrinsic_percentages[i + column_slice.start] == 0 and
not any(
max_content_width(context, cell)
for cell in column if cell)]
if columns:
for i in columns:
column_widths[i] += excess_width / len(columns)
return
# Fifth group, part 2, aka abort
return excess_width