layout: Reduce the complexity of FlexLine::layout (#32810)

Instead of a complex combination of iterators, use a flatter iteration
design when laying out a flex line.

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-19 12:37:29 +02:00 committed by GitHub
parent 8b3c9b744a
commit 5eb77592ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 121 additions and 151 deletions

1
Cargo.lock generated
View file

@ -3406,6 +3406,7 @@ dependencies = [
"html5ever", "html5ever",
"icu_segmenter", "icu_segmenter",
"ipc-channel", "ipc-channel",
"itertools 0.13.0",
"lazy_static", "lazy_static",
"log", "log",
"net_traits", "net_traits",

View file

@ -27,6 +27,7 @@ fonts_traits = { workspace = true }
html5ever = { workspace = true } html5ever = { workspace = true }
icu_segmenter = { workspace = true } icu_segmenter = { workspace = true }
ipc-channel = { workspace = true } ipc-channel = { workspace = true }
itertools = { workspace = true }
log = { workspace = true } log = { workspace = true }
net_traits = { workspace = true } net_traits = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -7,6 +7,7 @@ use std::cmp::Ordering;
use app_units::Au; use app_units::Au;
use atomic_refcell::AtomicRefMut; use atomic_refcell::AtomicRefMut;
use itertools::izip;
use style::properties::longhands::align_content::computed_value::T as AlignContent; use style::properties::longhands::align_content::computed_value::T as AlignContent;
use style::properties::longhands::align_items::computed_value::T as AlignItems; use style::properties::longhands::align_items::computed_value::T as AlignItems;
use style::properties::longhands::align_self::computed_value::T as AlignSelf; use style::properties::longhands::align_self::computed_value::T as AlignSelf;
@ -804,7 +805,7 @@ impl FlexLine<'_> {
self.resolve_flexible_lengths(container_main_size); self.resolve_flexible_lengths(container_main_size);
// https://drafts.csswg.org/css-flexbox/#algo-cross-item // https://drafts.csswg.org/css-flexbox/#algo-cross-item
let item_layout_results = self let mut item_layout_results = self
.items .items
.iter_mut() .iter_mut()
.zip(&item_used_main_sizes) .zip(&item_used_main_sizes)
@ -828,33 +829,60 @@ 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_used_cross_sizes, item_results): (Vec<_>, Vec<_>) = self let item_count = self.items.len();
.items let mut max_baseline = None;
.iter_mut() let mut item_used_cross_sizes = Vec::with_capacity(item_count);
.zip(item_layout_results) let mut item_cross_margins = Vec::with_capacity(item_count);
.zip(&item_used_main_sizes) for (item, item_layout_result, used_main_size) in izip!(
.map(|((item, mut item_result), &used_main_size)| { self.items.iter_mut(),
let has_stretch = item.align_self.0.value() == AlignFlags::STRETCH; item_layout_results.iter_mut(),
let cross_size = if has_stretch && &item_used_main_sizes
item.content_box_size.cross.is_auto() && ) {
!(item.margin.cross_start.is_auto() || item.margin.cross_end.is_auto()) let has_stretch = item.align_self.0.value() == AlignFlags::STRETCH;
{ let used_cross_size = if has_stretch &&
(line_cross_size - item.pbm_auto_is_zero.cross).clamp_between_extremums( item.content_box_size.cross.is_auto() &&
item.content_min_size.cross, !(item.margin.cross_start.is_auto() || item.margin.cross_end.is_auto())
item.content_max_size.cross, {
) (line_cross_size - item.pbm_auto_is_zero.cross).clamp_between_extremums(
} else { item.content_min_size.cross,
item_result.hypothetical_cross_size item.content_max_size.cross,
}; )
if has_stretch { } else {
// “If the flex item has `align-self: stretch`, redo layout for its contents, item_layout_result.hypothetical_cross_size
// treating this used size as its definite cross size };
// so that percentage-sized children can be resolved.” item_used_cross_sizes.push(used_cross_size);
item_result = item.layout(used_main_size, flex_context, Some(cross_size));
} if has_stretch {
(cross_size, item_result) // “If the flex item has `align-self: stretch`, redo layout for its contents,
}) // treating this used size as its definite cross size
.unzip(); // so that percentage-sized children can be resolved.”
*item_layout_result =
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() {
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE => {
let baseline = item_layout_result
.baseline_relative_to_margin_box
.unwrap_or_else(|| {
item.synthesized_baseline_relative_to_margin_box(used_cross_size)
});
max_baseline = Some(max_baseline.unwrap_or(baseline).max(baseline));
Some(baseline)
},
_ => None,
};
// https://drafts.csswg.org/css-flexbox/#algo-cross-margins
item_cross_margins.push(item.resolve_auto_cross_margins(
flex_context,
line_cross_size,
used_cross_size,
));
}
// Layout of items is over. These should no longer be mutable.
let item_layout_results = item_layout_results;
// Distribute any remaining free space // Distribute any remaining free space
// https://drafts.csswg.org/css-flexbox/#algo-main-align // https://drafts.csswg.org/css-flexbox/#algo-main-align
@ -865,7 +893,6 @@ impl FlexLine<'_> {
} }
// Align the items along the main-axis per justify-content. // Align the items along the main-axis per justify-content.
let item_count = self.items.len();
let layout_is_flex_reversed = flex_context.flex_direction_is_reversed; let layout_is_flex_reversed = flex_context.flex_direction_is_reversed;
// Implement fallback alignment. // Implement fallback alignment.
@ -945,107 +972,73 @@ impl FlexLine<'_> {
_ => Au::zero(), _ => Au::zero(),
}; };
// https://drafts.csswg.org/css-flexbox/#algo-cross-margins let mut main_position_cursor = main_start_position;
let item_cross_margins = self.items.iter().zip(&item_used_cross_sizes).map( let item_fragments = izip!(
|(item, &item_cross_content_size)| { self.items.iter(),
item.resolve_auto_cross_margins( item_main_margins,
flex_context, item_cross_margins,
line_cross_size,
item_cross_content_size,
)
},
);
let item_margins = item_main_margins
.zip(item_cross_margins)
.map(
|((main_start, main_end), (cross_start, cross_end))| FlexRelativeSides {
main_start,
main_end,
cross_start,
cross_end,
},
)
.collect::<Vec<_>>();
// https://drafts.csswg.org/css-flexbox/#algo-main-align
let items_content_main_start_positions = self.align_along_main_axis(
&item_used_main_sizes, &item_used_main_sizes,
&item_margins, &item_used_cross_sizes,
main_start_position, item_layout_results.into_iter()
item_main_interval, )
); .map(
|(
item,
item_main_margins,
item_cross_margins,
item_used_main_size,
item_used_cross_size,
item_layout_result,
)| {
let item_margin = FlexRelativeSides {
main_start: item_main_margins.0,
main_end: item_main_margins.1,
cross_start: item_cross_margins.0,
cross_end: item_cross_margins.1,
};
// https://drafts.csswg.org/css-flexbox/#algo-cross-align // https://drafts.csswg.org/css-flexbox/#algo-main-align
let item_propagated_baselines = item_results // “Align the items along the main-axis”
.iter() main_position_cursor +=
.zip(&item_used_cross_sizes) item_margin.main_start + item.border.main_start + item.padding.main_start;
.zip(self.items.iter()) let item_content_main_start_position = main_position_cursor;
.map(|((layout_result, used_cross_size), item)| { main_position_cursor += *item_used_main_size +
if matches!( item.padding.main_end +
item.align_self.0.value(), item.border.main_end +
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE item_margin.main_end +
) { item_main_interval;
Some(
layout_result // https://drafts.csswg.org/css-flexbox/#algo-cross-align
.baseline_relative_to_margin_box let item_content_cross_start_position = item.align_along_cross_axis(
.unwrap_or_else(|| { &item_margin,
item.synthesized_baseline_relative_to_margin_box(*used_cross_size) item_used_cross_size,
}),
)
} else {
None
}
})
.collect::<Vec<_>>();
let max_propagated_baseline = item_propagated_baselines
.iter()
.copied()
.flatten()
.max()
.unwrap_or(Au::zero());
let item_content_cross_start_posititons = self
.items
.iter()
.zip(&item_margins)
.zip(&item_used_cross_sizes)
.zip(&item_propagated_baselines)
.map(|(((item, margin), size), propagated_baseline)| {
item.align_along_cross_axis(
margin,
size,
line_cross_size, line_cross_size,
propagated_baseline.unwrap_or_default(), item_layout_result
max_propagated_baseline, .baseline_relative_to_margin_box
) .unwrap_or_default(),
}); max_baseline.unwrap_or_default(),
);
let item_fragments = self let start_corner = FlexRelativeVec2 {
.items main: item_content_main_start_position,
.iter() cross: item_content_cross_start_position,
.zip(item_results) };
.zip( let size = FlexRelativeVec2 {
item_used_main_sizes main: *item_used_main_size,
.iter() cross: *item_used_cross_size,
.zip(&item_used_cross_sizes) };
.map(|(&main, &cross)| FlexRelativeVec2 { main, cross })
.zip( let content_rect = flex_context
items_content_main_start_positions .rect_to_flow_relative(line_size, FlexRelativeRect { start_corner, size });
.zip(item_content_cross_start_posititons) let margin = flex_context.sides_to_flow_relative(item_margin);
.map(|(main, cross)| FlexRelativeVec2 { main, cross }),
)
.map(|(size, start_corner)| FlexRelativeRect { size, start_corner }),
)
.zip(&item_margins)
.map(|(((item, item_result), content_rect), margin)| {
let content_rect = flex_context.rect_to_flow_relative(line_size, content_rect);
let margin = flex_context.sides_to_flow_relative(*margin);
let collapsed_margin = CollapsedBlockMargins::from_margin(&margin); let collapsed_margin = CollapsedBlockMargins::from_margin(&margin);
// TODO: We should likely propagate baselines from `display: flex`.
( (
// TODO: We should likely propagate baselines from `display: flex`.
BoxFragment::new( BoxFragment::new(
item.box_.base_fragment_info(), item.box_.base_fragment_info(),
item.box_.style().clone(), item.box_.style().clone(),
item_result.fragments, item_layout_result.fragments,
content_rect, content_rect,
flex_context.sides_to_flow_relative(item.padding), flex_context.sides_to_flow_relative(item.padding),
flex_context.sides_to_flow_relative(item.border), flex_context.sides_to_flow_relative(item.border),
@ -1053,10 +1046,12 @@ impl FlexLine<'_> {
None, /* clearance */ None, /* clearance */
collapsed_margin, collapsed_margin,
), ),
item_result.positioning_context, item_layout_result.positioning_context,
) )
}) },
.collect(); )
.collect();
FlexLineLayoutResult { FlexLineLayoutResult {
cross_size: line_cross_size, cross_size: line_cross_size,
item_fragments, item_fragments,
@ -1462,33 +1457,6 @@ impl<'items> FlexLine<'items> {
each_auto_margin > Au::zero(), each_auto_margin > Au::zero(),
) )
} }
/// Return the coordinate of the main-start side of the content area of each item
fn align_along_main_axis<'a>(
&'a self,
item_used_main_sizes: &'a [Au],
item_margins: &'a [FlexRelativeSides<Au>],
main_start_position: Au,
item_main_interval: Au,
) -> impl Iterator<Item = Au> + 'a {
// “Align the items along the main-axis”
let mut main_position_cursor = main_start_position;
self.items
.iter()
.zip(item_used_main_sizes)
.zip(item_margins)
.map(move |((item, &main_content_size), margin)| {
main_position_cursor +=
margin.main_start + item.border.main_start + item.padding.main_start;
let content_main_start_position = main_position_cursor;
main_position_cursor += main_content_size +
item.padding.main_end +
item.border.main_end +
margin.main_end +
item_main_interval;
content_main_start_position
})
}
} }
impl FlexItem<'_> { impl FlexItem<'_> {