layout: Add support for propagating baselines from flexbox (#32841)

Some tests are still broken due to missing preferred widths calculation
for flexbox and also for missing column layout.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Co-authored-by: Delan Azabani <dazabani@igalia.com>
This commit is contained in:
Martin Robinson 2024-07-24 16:38:03 +02:00 committed by GitHub
parent 569fd5d8b5
commit 1906741704
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 56 deletions

View file

@ -106,10 +106,27 @@ struct FlexItemLayoutResult {
baseline_relative_to_margin_box: Option<Au>, baseline_relative_to_margin_box: Option<Au>,
} }
impl FlexItemLayoutResult {
fn get_or_synthesize_baseline_with_block_size(&self, block_size: Au, item: &FlexItem) -> Au {
self.baseline_relative_to_margin_box
.unwrap_or_else(|| item.synthesized_baseline_relative_to_margin_box(block_size))
}
}
/// Return type of `FlexLine::layout` /// Return type of `FlexLine::layout`
struct FlexLineLayoutResult { struct FlexLineLayoutResult {
cross_size: Au, cross_size: Au,
item_fragments: Vec<(BoxFragment, PositioningContext)>, // One per flex item, in the given order /// The [`BoxFragment`]s and [`PositioningContext`]s of all flex items,
/// one per flex item in "order-modified document order."
item_fragments: Vec<(BoxFragment, PositioningContext)>,
/// The 'shared alignment baseline' of this flex line. This is the baseline used for
/// baseline-aligned items if there are any, otherwise `None`.
shared_alignment_baseline: Option<Au>,
/// This is the baseline of the first and last items with compatible writing mode, regardless of
/// whether they particpate in baseline alignement. This is used as a fallback baseline for the
/// container, if there are no items participating in baseline alignment in the first or last
/// flex lines.
all_baselines: Baselines,
} }
impl FlexContext<'_> { impl FlexContext<'_> {
@ -420,10 +437,12 @@ impl FlexContainer {
}, },
}; };
let mut flex_item_fragments = flex_lines let mut baseline_alignment_participating_baselines = Baselines::default();
.into_iter() let mut all_baselines = Baselines::default();
.zip(line_cross_start_positions) let num_lines = flex_lines.len();
.flat_map(move |(mut line, line_cross_start_position)| { let mut flex_item_fragments = izip!(flex_lines.into_iter(), line_cross_start_positions)
.enumerate()
.flat_map(|(index, (mut line, line_cross_start_position))| {
let flow_relative_line_position = match (flex_axis, flex_wrap_reverse) { let flow_relative_line_position = match (flex_axis, flex_wrap_reverse) {
(FlexAxis::Row, false) => LogicalVec2 { (FlexAxis::Row, false) => LogicalVec2 {
block: line_cross_start_position, block: line_cross_start_position,
@ -442,6 +461,23 @@ impl FlexContainer {
inline: container_cross_size - line_cross_start_position - line.cross_size, inline: container_cross_size - line_cross_start_position - line.cross_size,
}, },
}; };
let line_shared_alignment_baseline = line
.shared_alignment_baseline
.map(|baseline| baseline + flow_relative_line_position.block);
let line_all_baselines =
line.all_baselines.offset(flow_relative_line_position.block);
if index == 0 {
baseline_alignment_participating_baselines.first =
line_shared_alignment_baseline;
all_baselines.first = line_all_baselines.first;
}
if index == num_lines - 1 {
baseline_alignment_participating_baselines.last =
line_shared_alignment_baseline;
all_baselines.last = line_all_baselines.last;
}
for (fragment, _) in &mut line.item_fragments { for (fragment, _) in &mut line.item_fragments {
fragment.content_rect.start_corner += flow_relative_line_position fragment.content_rect.start_corner += flow_relative_line_position
} }
@ -480,12 +516,20 @@ impl FlexContainer {
// There should be no more flex items // There should be no more flex items
assert!(flex_item_fragments.next().is_none()); assert!(flex_item_fragments.next().is_none());
let baselines = Baselines {
first: baseline_alignment_participating_baselines
.first
.or(all_baselines.first),
last: baseline_alignment_participating_baselines
.last
.or(all_baselines.last),
};
IndependentLayout { IndependentLayout {
fragments, fragments,
content_block_size, content_block_size,
content_inline_size_for_table: None, content_inline_size_for_table: None,
// TODO: compute baseline for flex container baselines,
baselines: Baselines::default(),
} }
} }
@ -743,12 +787,12 @@ fn flex_base_size(
// “Collect flex items into flex lines” // “Collect flex items into flex lines”
// https://drafts.csswg.org/css-flexbox/#algo-line-break // https://drafts.csswg.org/css-flexbox/#algo-line-break
fn collect_flex_lines<'items, LineResult>( fn collect_flex_lines<'items>(
flex_context: &mut FlexContext, flex_context: &mut FlexContext,
container_main_size: Au, container_main_size: Au,
mut items: &'items mut [FlexItem<'items>], mut items: &'items mut [FlexItem<'items>],
mut each: impl FnMut(&mut FlexContext, FlexLine<'items>) -> LineResult, mut each: impl FnMut(&mut FlexContext, FlexLine<'items>) -> FlexLineLayoutResult,
) -> Vec<LineResult> { ) -> Vec<FlexLineLayoutResult> {
if flex_context.container_is_single_line { if flex_context.container_is_single_line {
let line = FlexLine { let line = FlexLine {
outer_hypothetical_main_sizes_sum: items outer_hypothetical_main_sizes_sum: items
@ -830,7 +874,7 @@ impl FlexLine<'_> {
// Determine the used cross size of each flex item // Determine the used cross size of each flex item
// https://drafts.csswg.org/css-flexbox/#algo-stretch // https://drafts.csswg.org/css-flexbox/#algo-stretch
let item_count = self.items.len(); let item_count = self.items.len();
let mut max_baseline = None; let mut shared_alignment_baseline = None;
let mut item_used_cross_sizes = Vec::with_capacity(item_count); let mut item_used_cross_sizes = Vec::with_capacity(item_count);
let mut item_cross_margins = Vec::with_capacity(item_count); let mut item_cross_margins = Vec::with_capacity(item_count);
for (item, item_layout_result, used_main_size) in izip!( for (item, item_layout_result, used_main_size) in izip!(
@ -860,18 +904,17 @@ impl FlexLine<'_> {
item.layout(*used_main_size, flex_context, Some(used_cross_size)); item.layout(*used_main_size, flex_context, Some(used_cross_size));
} }
item_layout_result.baseline_relative_to_margin_box = match item.align_self.0.value() { // TODO: This also needs to check whether we have a compatible writing mode.
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE => { let baseline = item_layout_result
let baseline = item_layout_result .get_or_synthesize_baseline_with_block_size(used_cross_size, item);
.baseline_relative_to_margin_box if matches!(
.unwrap_or_else(|| { item.align_self.0.value(),
item.synthesized_baseline_relative_to_margin_box(used_cross_size) AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
}); ) {
max_baseline = Some(max_baseline.unwrap_or(baseline).max(baseline)); shared_alignment_baseline =
Some(baseline) Some(shared_alignment_baseline.unwrap_or(baseline).max(baseline));
}, }
_ => None, item_layout_result.baseline_relative_to_margin_box = Some(baseline);
};
// https://drafts.csswg.org/css-flexbox/#algo-cross-margins // https://drafts.csswg.org/css-flexbox/#algo-cross-margins
item_cross_margins.push(item.resolve_auto_cross_margins( item_cross_margins.push(item.resolve_auto_cross_margins(
@ -972,6 +1015,7 @@ impl FlexLine<'_> {
_ => Au::zero(), _ => Au::zero(),
}; };
let mut all_baselines = Baselines::default();
let mut main_position_cursor = main_start_position; let mut main_position_cursor = main_start_position;
let item_fragments = izip!( let item_fragments = izip!(
self.items.iter(), self.items.iter(),
@ -1016,7 +1060,7 @@ impl FlexLine<'_> {
item_layout_result item_layout_result
.baseline_relative_to_margin_box .baseline_relative_to_margin_box
.unwrap_or_default(), .unwrap_or_default(),
max_baseline.unwrap_or_default(), shared_alignment_baseline.unwrap_or_default(),
); );
let start_corner = FlexRelativeVec2 { let start_corner = FlexRelativeVec2 {
@ -1028,12 +1072,23 @@ impl FlexLine<'_> {
cross: *item_used_cross_size, cross: *item_used_cross_size,
}; };
// Need to collect both baselines from baseline participation and other baselines.
let content_rect = flex_context let content_rect = flex_context
.rect_to_flow_relative(line_size, FlexRelativeRect { start_corner, size }); .rect_to_flow_relative(line_size, FlexRelativeRect { start_corner, size });
let margin = flex_context.sides_to_flow_relative(item_margin); let margin = flex_context.sides_to_flow_relative(item_margin);
let collapsed_margin = CollapsedBlockMargins::from_margin(&margin); let collapsed_margin = CollapsedBlockMargins::from_margin(&margin);
// TODO: We should likely propagate baselines from `display: flex`. if let Some(item_baseline) =
item_layout_result.baseline_relative_to_margin_box.as_ref()
{
let item_baseline = *item_baseline + item_content_cross_start_position -
item.border.cross_start -
item.padding.cross_start -
item_margin.cross_start;
all_baselines.first.get_or_insert(item_baseline);
all_baselines.last = Some(item_baseline);
}
( (
BoxFragment::new( BoxFragment::new(
item.box_.base_fragment_info(), item.box_.base_fragment_info(),
@ -1055,6 +1110,8 @@ impl FlexLine<'_> {
FlexLineLayoutResult { FlexLineLayoutResult {
cross_size: line_cross_size, cross_size: line_cross_size,
item_fragments, item_fragments,
all_baselines,
shared_alignment_baseline,
} }
} }
@ -1393,13 +1450,10 @@ impl<'items> FlexLine<'items> {
item.align_self.0.value(), item.align_self.0.value(),
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
) { ) {
let baseline = item_result let baseline = item_result.get_or_synthesize_baseline_with_block_size(
.baseline_relative_to_margin_box item_result.hypothetical_cross_size,
.unwrap_or_else(|| { item,
item.synthesized_baseline_relative_to_margin_box( );
item_result.hypothetical_cross_size,
)
});
let hypothetical_margin_box_cross_size = let hypothetical_margin_box_cross_size =
item_result.hypothetical_cross_size + item.pbm_auto_is_zero.cross; item_result.hypothetical_cross_size + item.pbm_auto_is_zero.cross;
max_ascent = max_ascent.max(baseline); max_ascent = max_ascent.max(baseline);

View file

@ -2,12 +2,6 @@
[.target > * 1] [.target > * 1]
expected: FAIL expected: FAIL
[.target > * 3]
expected: FAIL
[.target > * 5]
expected: FAIL
[.target > * 7] [.target > * 7]
expected: FAIL expected: FAIL
@ -17,27 +11,15 @@
[.target > * 11] [.target > * 11]
expected: FAIL expected: FAIL
[.target > * 13]
expected: FAIL
[.target > * 15] [.target > * 15]
expected: FAIL expected: FAIL
[.target > * 17] [.target > * 17]
expected: FAIL expected: FAIL
[.target > * 19]
expected: FAIL
[.target > * 21]
expected: FAIL
[.target > * 23] [.target > * 23]
expected: FAIL expected: FAIL
[.target > * 25]
expected: FAIL
[.target > * 27] [.target > * 27]
expected: FAIL expected: FAIL
@ -70,3 +52,9 @@
[.target > * 47] [.target > * 47]
expected: FAIL expected: FAIL
[.target > * 44]
expected: FAIL
[.target > * 48]
expected: FAIL

View file

@ -1,10 +1,4 @@
[flex-align-baseline-flex-003.html] [flex-align-baseline-flex-003.html]
[.target > * 1]
expected: FAIL
[.target > * 3]
expected: FAIL
[.target > * 5] [.target > * 5]
expected: FAIL expected: FAIL