flex: handle ‘align-self: [ first | last ]? && baseline’ (#32787)

* flex: handle ‘align-self: baseline’

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* FIXME: css/css-flexbox/align-items-baseline-overflow-non-visible.html

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* Fix baseline selection

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* Fix baseline calculation with padding/border/margin

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* Fix compile errors and warnings

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* Implement ‘align-self: last baseline’

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* Fix bug where non-baseline-aligned items affected max baseline

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* Update expectations

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* Rename method

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

---------

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Delan Azabani 2024-07-18 15:03:29 +08:00 committed by GitHub
parent 34eed29037
commit 1b1f79305e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 118 additions and 75 deletions

View file

@ -100,6 +100,9 @@ struct FlexItemLayoutResult {
hypothetical_cross_size: Au,
fragments: Vec<Fragment>,
positioning_context: PositioningContext,
// Either the first or the last baseline, depending on align-self.
baseline_relative_to_margin_box: Option<Au>,
}
/// Return type of `FlexLine::layout`
@ -480,6 +483,7 @@ impl FlexContainer {
fragments,
content_block_size,
content_inline_size_for_table: None,
// TODO: compute baseline for flex container
baselines: Baselines::default(),
}
}
@ -972,13 +976,47 @@ impl FlexLine<'_> {
);
// https://drafts.csswg.org/css-flexbox/#algo-cross-align
let item_propagated_baselines = item_results
.iter()
.zip(&item_used_cross_sizes)
.zip(self.items.iter())
.map(|((layout_result, used_cross_size), item)| {
if matches!(
item.align_self.0.value(),
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
) {
Some(
layout_result
.baseline_relative_to_margin_box
.unwrap_or_else(|| {
item.synthesized_baseline_relative_to_margin_box(*used_cross_size)
}),
)
} else {
None
}
})
.collect::<Vec<_>>();
let max_propagated_baseline = item_propagated_baselines
.iter()
.copied()
.filter_map(|baseline| baseline)
.max()
.unwrap_or(Au::zero());
let item_content_cross_start_posititons = self
.items
.iter()
.zip(&item_margins)
.zip(&item_used_cross_sizes)
.map(|((item, margin), size)| {
item.align_along_cross_axis(margin, size, line_cross_size)
.zip(&item_propagated_baselines)
.map(|(((item, margin), size), propagated_baseline)| {
item.align_along_cross_axis(
margin,
size,
line_cross_size,
propagated_baseline.unwrap_or_default(),
max_propagated_baseline,
)
});
let item_fragments = self
@ -1243,10 +1281,16 @@ impl<'a> FlexItem<'a> {
);
let cross_size = flex_context.vec2_to_flex_relative(size).cross;
let fragments = replaced.contents.make_fragments(&replaced.style, size);
FlexItemLayoutResult {
hypothetical_cross_size: cross_size,
fragments,
positioning_context,
// We will need to synthesize the baseline, but since the used cross
// size can differ from the hypothetical cross size, we should defer
// synthesizing until needed.
baseline_relative_to_margin_box: None,
}
},
IndependentFormattingContext::NonReplaced(non_replaced) => {
@ -1263,6 +1307,7 @@ impl<'a> FlexItem<'a> {
let IndependentLayout {
fragments,
content_block_size,
baselines: content_box_baselines,
..
} = non_replaced.layout(
flex_context.layout_context,
@ -1271,6 +1316,16 @@ impl<'a> FlexItem<'a> {
flex_context.containing_block,
);
let baselines_relative_to_margin_box =
self.layout_baselines_relative_to_margin_box(&content_box_baselines);
let baseline_relative_to_margin_box = match self.align_self.0.value() {
// baseline computes to first baseline.
AlignFlags::BASELINE => baselines_relative_to_margin_box.first,
AlignFlags::LAST_BASELINE => baselines_relative_to_margin_box.last,
_ => None,
};
let hypothetical_cross_size = self
.content_box_size
.cross
@ -1284,6 +1339,7 @@ impl<'a> FlexItem<'a> {
hypothetical_cross_size,
fragments,
positioning_context,
baseline_relative_to_margin_box,
}
},
}
@ -1295,6 +1351,29 @@ impl<'a> FlexItem<'a> {
},
}
}
fn layout_baselines_relative_to_margin_box(
&self,
baselines_relative_to_content_box: &Baselines,
) -> Baselines {
baselines_relative_to_content_box.offset(
self.margin.cross_start.auto_is(Au::zero) +
self.padding.cross_start +
self.border.cross_start,
)
}
fn synthesized_baseline_relative_to_margin_box(&self, content_size: Au) -> Au {
// If the item does not have a baseline in the necessary axis,
// then one is synthesized from the flex items border box.
// https://drafts.csswg.org/css-flexbox/#valdef-align-items-baseline
content_size +
self.margin.cross_start.auto_is(Au::zero) +
self.padding.cross_start +
self.border.cross_start +
self.border.cross_end +
self.padding.cross_end
}
}
impl<'items> FlexLine<'items> {
@ -1309,17 +1388,37 @@ impl<'items> FlexLine<'items> {
return size;
}
}
let outer_hypothetical_cross_sizes =
item_layout_results
.iter()
.zip(&*self.items)
.map(|(item_result, item)| {
item_result.hypothetical_cross_size + item.pbm_auto_is_zero.cross
let mut max_ascent = Au::zero();
let mut max_descent = Au::zero();
let mut max_outer_hypothetical_cross_size = Au::zero();
for (item_result, item) in item_layout_results.iter().zip(&*self.items) {
// TODO: check inline-axis is parallel to main axis, check no auto cross margins
if matches!(
item.align_self.0.value(),
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
) {
let baseline = item_result
.baseline_relative_to_margin_box
.unwrap_or_else(|| {
item.synthesized_baseline_relative_to_margin_box(
item_result.hypothetical_cross_size,
)
});
let hypothetical_margin_box_cross_size =
item_result.hypothetical_cross_size + item.pbm_auto_is_zero.cross;
max_ascent = max_ascent.max(baseline);
max_descent = max_descent.max(hypothetical_margin_box_cross_size - baseline);
} else {
max_outer_hypothetical_cross_size = max_outer_hypothetical_cross_size
.max(item_result.hypothetical_cross_size + item.pbm_auto_is_zero.cross);
}
}
// FIXME: add support for `align-self: baseline`
// and computing the baseline of flex items.
// https://drafts.csswg.org/css-flexbox/#baseline-participation
let largest = outer_hypothetical_cross_sizes.fold(Au::zero(), Au::max);
let largest = max_outer_hypothetical_cross_size.max(max_ascent + max_descent);
if flex_context.container_is_single_line {
largest.clamp_between_extremums(
flex_context.container_min_cross_size,
@ -1452,8 +1551,10 @@ impl FlexItem<'_> {
fn align_along_cross_axis(
&self,
margin: &FlexRelativeSides<Au>,
content_size: &Au,
used_cross_size: &Au,
line_cross_size: Au,
propagated_baseline: Au,
max_propagated_baseline: Au,
) -> Au {
let outer_cross_start =
if self.margin.cross_start.is_auto() || self.margin.cross_end.is_auto() {
@ -1462,14 +1563,16 @@ impl FlexItem<'_> {
match self.align_self.0.value() {
AlignFlags::STRETCH | AlignFlags::FLEX_START => Au::zero(),
AlignFlags::FLEX_END => {
let margin_box_cross = *content_size + self.pbm_auto_is_zero.cross;
let margin_box_cross = *used_cross_size + self.pbm_auto_is_zero.cross;
line_cross_size - margin_box_cross
},
AlignFlags::CENTER => {
let margin_box_cross = *content_size + self.pbm_auto_is_zero.cross;
let margin_box_cross = *used_cross_size + self.pbm_auto_is_zero.cross;
(line_cross_size - margin_box_cross) / 2
},
// FIXME: handle other alignments (note: not all AlignFlags values are valid for self alignment)
AlignFlags::BASELINE | AlignFlags::LAST_BASELINE => {
max_propagated_baseline - propagated_baseline
},
_ => Au::zero(),
}
};

View file

@ -2082,6 +2082,7 @@ impl IndependentFormattingContext {
}
/// Picks either the first or the last baseline, depending on `baseline-source`.
/// TODO: clarify that this is not to be used for box alignment in flex/grid
/// <https://drafts.csswg.org/css-inline/#baseline-source>
fn pick_baseline(&self, baselines: &Baselines) -> Option<Au> {
match self.style().clone_baseline_source() {

View file

@ -1,6 +1,3 @@
[flex-align-baseline-005.html]
[#target > div 1]
expected: FAIL
[#target > div 3]
expected: FAIL

View file

@ -1,6 +1,3 @@
[flex-align-baseline-006.html]
[#target > div 1]
expected: FAIL
[#target > div 3]
expected: FAIL

View file

@ -1,6 +1,3 @@
[flex-align-baseline-007.html]
[#target > div 1]
expected: FAIL
[#target > div 3]
expected: FAIL

View file

@ -11,8 +11,5 @@
[.target > * 7]
expected: FAIL
[.target > * 9]
expected: FAIL
[.target > * 11]
expected: FAIL

View file

@ -1,36 +1,12 @@
[flex-align-baseline-line-clamp-001.tentative.html]
[.target > * 1]
expected: FAIL
[.target > * 3]
expected: FAIL
[.target > * 5]
expected: FAIL
[.target > * 7]
expected: FAIL
[.target > * 9]
expected: FAIL
[.target > * 11]
expected: FAIL
[.target > * 13]
expected: FAIL
[.target > * 15]
expected: FAIL
[.target > * 17]
expected: FAIL
[.target > * 19]
expected: FAIL
[.target > * 21]
expected: FAIL
[.target > * 23]
expected: FAIL

View file

@ -1,18 +1,9 @@
[flex-align-baseline-multicol-001.html]
[.target > * 1]
expected: FAIL
[.target > * 3]
expected: FAIL
[.target > * 5]
expected: FAIL
[.target > * 7]
expected: FAIL
[.target > * 9]
expected: FAIL
[.target > * 11]
expected: FAIL

View file

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

View file

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

View file

@ -1,2 +0,0 @@
[flexbox-align-self-baseline-horiz-001a.xhtml]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox_align-items-baseline.html]
expected: FAIL