Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/compute/flexbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
// line's visual position: for wrap-reverse containers the cross axis is flipped, so the
// startmost line is the last line in flex-line order rather than the first.
let first_line = if constants.is_wrap_reverse { flex_lines.last() } else { flex_lines.first() };
// Prefer the first item in the line participating in first-baseline alignment, then the first
// item participating in any baseline alignment, then the line's first item.
let first_vertical_baseline = first_line.and_then(|line| {
if constants.is_column {
// For column containers the baseline is generated from the startmost item in the line,
Expand All @@ -529,7 +531,8 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
line.items
.iter()
.find(|item| item.participates_in_baseline_alignment(constants.dir))
.or_else(|| line.items.iter().next())
.or_else(|| line.items.iter().find(|item| item.participates_in_last_baseline_alignment(constants.dir)))
.or_else(|| line.items.first())
.map(|child| child.baseline)
}
});
Expand All @@ -540,11 +543,17 @@ fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inp
let last_line = if constants.is_wrap_reverse { flex_lines.first() } else { flex_lines.last() };
let last_vertical_baseline = last_line.and_then(|line| {
if constants.is_column {
line.items.last().map(|child| child.last_baseline)
// For column containers the last baseline is generated from the endmost item in the
// line, which for reverse-direction containers is the first item in flex order.
let item = if constants.dir.is_reverse() { line.items.first() } else { line.items.last() };
item.map(|child| child.last_baseline)
} else {
// Prefer the first item in the line participating in last-baseline alignment, then the
// first item participating in any baseline alignment, then the line's last item.
line.items
.iter()
.find(|item| item.participates_in_last_baseline_alignment(constants.dir))
.or_else(|| line.items.iter().find(|item| item.participates_in_baseline_alignment(constants.dir)))
.or_else(|| line.items.last())
.map(|child| child.last_baseline)
}
Expand Down
68 changes: 37 additions & 31 deletions src/compute/grid/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ use placement::place_grid_items;
use track_sizing::{
determine_if_item_crosses_flexible_or_intrinsic_tracks, resolve_item_track_indexes, track_sizing_algorithm,
};
use types::{CellOccupancyMatrix, GridTrack, NamedLineResolver, TrackCounts};
use types::{CellOccupancyMatrix, GridItem, GridTrack, NamedLineResolver, TrackCounts};

#[cfg(feature = "detailed_layout_info")]
use types::{GridItem, GridTrackKind};
use types::GridTrackKind;

pub(crate) use types::{GridCoordinate, GridLine, OriginZeroLine, MAX_GRID_TRACKS, MAX_OZ_LINE, MIN_OZ_LINE};

Expand Down Expand Up @@ -345,7 +345,7 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
&mut columns,
&mut items,
|track: &GridTrack, _, _| Some(track.base_size),
false, // TODO: Support baseline alignment in the vertical axis
has_baseline_aligned_item,
);
let initial_row_sum = rows.iter().map(|track| track.base_size).sum::<f32>();
inner_node_size.height = inner_node_size.height.or_else(|| initial_row_sum.into());
Expand Down Expand Up @@ -531,7 +531,7 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
&mut columns,
&mut items,
|track: &GridTrack, _, _| Some(track.base_size),
false, // TODO: Support baseline alignment in the vertical axis
has_baseline_aligned_item,
);
}
}
Expand Down Expand Up @@ -630,6 +630,7 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
);
item.y_position = y_position;
item.height = height;
item.baseline = baselines.first;
item.last_baseline = baselines.last;

#[cfg(feature = "content_size")]
Expand Down Expand Up @@ -786,50 +787,55 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
return LayoutOutput::from_outer_size(container_border_box);
}

// "Grid order" key: row-major order of the grid areas items occupy, with ties resolved by
// document order (`items` is sorted by document order at this point, and `min_by_key` returns
// the first of equally-minimum elements, so ties fall back to document order for free)
// (column indexes are visually reversed for RTL, so negate them to recover logical column order)
let column_dir_factor: i32 = if direction.is_rtl() { -1 } else { 1 };
let column_order_key = |item: &&GridItem| column_dir_factor * item.column_indexes.start as i32;

// Determine the grid container's first baseline, generated from the first row containing items.
// Layout containment suppresses the box's baseline for baseline-alignment purposes
let grid_container_baseline: Option<f32> = if contain.suppresses_baseline() {
None
} else {
// Sort items by row start position so that we can iterate items in groups which are in the same row
items.sort_by_key(|item| item.row_indexes.start);

// Get the row index of the first row containing items
let first_row = items[0].row_indexes.start;
let first_row = items.iter().map(|item| item.row_indexes.start).min().unwrap();
let first_row_items = items.iter().filter(|item| item.row_indexes.start == first_row);

// Create a slice of all of the items start in this row (taking advantage of the fact that we have just sorted the array)
let first_row_items = &items[0..].split(|item| item.row_indexes.start != first_row).next().unwrap();

// Prefer the first item in *this row* which participates in first-baseline alignment
// (items with an auto block-axis margin do not participate: https://www.w3.org/TR/css-align-3/#baseline-align-self),
// falling back to the row's first item
// Prefer the first item (in grid order) in *this row* which participates in
// first-baseline alignment (items with an auto block-axis margin do not participate:
// https://www.w3.org/TR/css-align-3/#baseline-align-self), falling back to the row's first item
let item = first_row_items
.iter()
.find(|item| item.participates_in_baseline_group(AlignItemsKeyword::Baseline))
.unwrap_or(&first_row_items[0]);
.clone()
.filter(|item| item.participates_in_baseline_group(AlignItemsKeyword::Baseline))
.min_by_key(column_order_key)
.or_else(|| first_row_items.min_by_key(column_order_key))
.unwrap();

Some(item.y_position + item.baseline.unwrap_or(item.height))
};

// Determine the grid container's last baseline, generated from the last row containing items
// Determine the grid container's last baseline, generated from the last row containing items.
// Items participate in the last row's baseline-sharing group if their grid area *ends* in that
// row, so the last row is the one with the maximum row end index and membership is determined
// by row end rather than row start.
let grid_container_last_baseline: Option<f32> = if contain.suppresses_baseline() {
None
} else {
// Sort items by row start position so that we can iterate items in groups which are in the same row
items.sort_by_key(|item| item.row_indexes.start);
// Get the row end index of the last row containing items
let last_row_end = items.iter().map(|item| item.row_indexes.end).max().unwrap();
let last_row_items = items.iter().filter(|item| item.row_indexes.end == last_row_end);

// Get the row index of the last row containing items
let last_row = items[items.len() - 1].row_indexes.start;

// Create a slice of all of the items that start in this row (taking advantage of the fact that the array is sorted)
let last_row_items = &items[0..].rsplit(|item| item.row_indexes.start != last_row).next().unwrap();

// Prefer the first item in *this row* which participates in last-baseline alignment,
// falling back to the row's first item
// Prefer the first item (in grid order) ending in this row which participates in
// last-baseline alignment, falling back to the first item ending in this row
let item = last_row_items
.iter()
.find(|item| item.align_self.keyword == AlignItemsKeyword::LastBaseline)
.unwrap_or(&last_row_items[0]);
.clone()
.filter(|item| item.participates_in_baseline_group(AlignItemsKeyword::LastBaseline))
.min_by_key(column_order_key)
.or_else(|| last_row_items.min_by_key(column_order_key))
.unwrap();

Some(item.y_position + item.last_baseline.unwrap_or(item.height))
};

Expand Down
111 changes: 70 additions & 41 deletions src/compute/grid/track_sizing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use super::types::{GridItem, GridTrack, TrackCounts};
use crate::geometry::{AbstractAxis, Line, Size};
use crate::style::{AlignContent, AlignContentKeyword, AlignItemsKeyword, AvailableSpace};
use crate::style_helpers::TaffyMinContent;
use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, SizingMode};
use crate::util::sys::{f32_max, f32_min, Vec};
use crate::util::{MaybeMath, ResolveOrZero};
Expand Down Expand Up @@ -291,8 +290,10 @@ pub(super) fn track_sizing_algorithm<Tree: LayoutPartialTree>(
initialize_track_sizes(tree, axis_tracks, percentage_basis);

// 11.5.1 Shim item baselines
if has_baseline_aligned_item {
resolve_item_baselines(tree, axis, items, inner_node_size);
// Baseline shims apply in the block axis, and are resolved once the inline axis (column) tracks
// have been sized so that percentage-sized and aspect-ratio-dependent items measure correctly.
if has_baseline_aligned_item && axis == AbstractAxis::Block {
resolve_item_baselines(tree, items, inner_node_size, other_axis_tracks);
}

// If all tracks have a fixed min track sizing function and base_size = growth_limit,
Expand Down Expand Up @@ -457,30 +458,61 @@ fn initialize_track_sizes(
/// 11.5.1 Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment.
fn resolve_item_baselines(
tree: &mut impl LayoutPartialTree,
axis: AbstractAxis,
items: &mut [GridItem],
inner_node_size: Size<Option<f32>>,
columns: &[GridTrack],
) {
// Sort items by track in the other axis (row) start position so that we can iterate items in groups which
// are in the same track in the other axis (row)
let other_axis = axis.other();
items.sort_by_key(|item| item.placement(other_axis).start);
// First-baseline aligned items participate in the baseline-sharing group of their start-most
// row, while last-baseline aligned items participate in that of their end-most row.
// See https://www.w3.org/TR/css-align-3/#baseline-sharing-group
resolve_item_baselines_for_group(tree, items, inner_node_size, columns, AlignItemsKeyword::Baseline);
resolve_item_baselines_for_group(tree, items, inner_node_size, columns, AlignItemsKeyword::LastBaseline);
}

/// Resolve baselines and shims for either the first-baseline or last-baseline alignment group.
fn resolve_item_baselines_for_group(
tree: &mut impl LayoutPartialTree,
items: &mut [GridItem],
inner_node_size: Size<Option<f32>>,
columns: &[GridTrack],
group_keyword: AlignItemsKeyword,
) {
// If fewer than two items participate in this kind of baseline alignment then no row can
// contain a multi-item baseline-sharing group, so skip sorting and grouping entirely
if items.iter().filter(|item| item.align_self.keyword == group_keyword).count() <= 1 {
return;
}

let is_first_baseline = group_keyword == AlignItemsKeyword::Baseline;

// The grid row line at which an item participates in baseline alignment: the start of its
// start-most row for first-baseline alignment, the end of its end-most row for last-baseline
let participation_line = |item: &GridItem| {
let placement = item.placement(AbstractAxis::Block);
if is_first_baseline {
placement.start
} else {
placement.end
}
};

// Sort items by their participation line so that we can iterate items in groups which share a row edge
items.sort_by_key(participation_line);

// Iterate over grid rows
let mut remaining_items = &mut items[0..];
while !remaining_items.is_empty() {
// Get the row index of the current row
let current_row = remaining_items[0].placement(other_axis).start;
// Get the row line of the current group
let current_line = participation_line(&remaining_items[0]);

// Find the item index of the first item that is in a different row (or None if we've reached the end of the list)
let next_row_first_item =
remaining_items.iter().position(|item| item.placement(other_axis).start != current_row);
// Find the item index of the first item that is in a different group (or None if we've reached the end of the list)
let next_group_first_item = remaining_items.iter().position(|item| participation_line(item) != current_line);

// Use this index to split the `remaining_items` slice in two slices:
// - A `row_items` slice containing the items (that start) in the current row
// - A `row_items` slice containing the items that participate at the current row line
// - A new `remaining_items` consisting of the remainder of the `remaining_items` slice
// that hasn't been split off into `row_items
let row_items = if let Some(index) = next_row_first_item {
// that hasn't been split off into `row_items`
let row_items = if let Some(index) = next_group_first_item {
let (row_items, tail) = remaining_items.split_at_mut(index);
remaining_items = tail;
row_items
Expand All @@ -490,35 +522,34 @@ fn resolve_item_baselines(
row_items
};

// Count how many items in *this row* participate in each baseline alignment group
// Count how many items in *this row* participate in the baseline alignment group
// (items with an auto block-axis margin do not participate).
// If a group has one or zero items then baseline alignment is a no-op for those items
// and we skip further computations for that group
let row_first_baseline_item_count =
row_items.iter().filter(|item| item.participates_in_baseline_group(AlignItemsKeyword::Baseline)).count();
let row_last_baseline_item_count = row_items
.iter()
.filter(|item| item.participates_in_baseline_group(AlignItemsKeyword::LastBaseline))
.count();
if row_first_baseline_item_count <= 1 && row_last_baseline_item_count <= 1 {
// If the group has one or zero items then baseline alignment is a no-op for those items
// and we skip further computations for the group
let baseline_item_count =
row_items.iter().filter(|item| item.participates_in_baseline_group(group_keyword)).count();
if baseline_item_count <= 1 {
continue;
}

// Compute the baselines of all items in the row participating in baseline alignment
// Compute the baselines of all items in the group
for item in row_items.iter_mut() {
let is_first_baseline = item.participates_in_baseline_group(AlignItemsKeyword::Baseline);
let is_last_baseline = item.participates_in_baseline_group(AlignItemsKeyword::LastBaseline);
let should_measure = (is_first_baseline && row_first_baseline_item_count > 1)
|| (is_last_baseline && row_last_baseline_item_count > 1);
if !should_measure {
if !item.participates_in_baseline_group(group_keyword) {
continue;
}

// Measure the item using its grid area width (including any spanned gutters) as the
// containing block width so that percentage sizes and aspect ratios resolve correctly
let grid_area_width: f32 =
item.track_range_excluding_lines(AbstractAxis::Inline).map(|index| columns[index].base_size).sum();
let grid_area_size = Size { width: Some(grid_area_width), height: None };
let known_dimensions = item.known_dimensions(tree, grid_area_size);

let measured_size_and_baselines = tree.perform_child_layout(
item.node,
Size::NONE,
inner_node_size,
Size::MIN_CONTENT,
known_dimensions,
grid_area_size,
Size { width: AvailableSpace::Definite(grid_area_width), height: AvailableSpace::MinContent },
SizingMode::InherentSize,
Line::FALSE,
);
Expand Down Expand Up @@ -553,8 +584,8 @@ fn resolve_item_baselines(
}
}

// Compute the max first-baseline ascent and shim each item in the first-baseline group
if row_first_baseline_item_count > 1 {
if is_first_baseline {
// Compute the max first-baseline ascent and shim each item in the first-baseline group
let row_max_baseline = row_items
.iter()
.filter(|item| item.participates_in_baseline_group(AlignItemsKeyword::Baseline))
Expand All @@ -566,10 +597,8 @@ fn resolve_item_baselines(
{
item.baseline_shims.start = row_max_baseline - item.baseline.unwrap_or(0.0);
}
}

// Compute the max last-baseline descent and shim each item in the last-baseline group
if row_last_baseline_item_count > 1 {
} else {
// Compute the max last-baseline descent and shim each item in the last-baseline group
let row_max_descent = row_items
.iter()
.filter(|item| item.participates_in_baseline_group(AlignItemsKeyword::LastBaseline))
Expand Down
2 changes: 1 addition & 1 deletion src/compute/grid/types/grid_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ impl GridItem {
/// Compute the known_dimensions to be passed to the child sizing functions
/// The key thing that is being done here is applying stretch alignment, which is necessary to
/// allow percentage sizes further down the tree to resolve properly in some cases
fn known_dimensions(
pub(in crate::compute::grid) fn known_dimensions(
&self,
tree: &mut impl LayoutPartialTree,
grid_area_size: Size<Option<f32>>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="../../scripts/gentest/test_helper.js"></script>
<link rel="stylesheet" type="text/css" href="../../scripts/gentest/test_base_style.css">
<title>
Test description
</title>
</head>
<body>

<div id="test-root" style="align-items: baseline; width: 200px;">
<div style="width: 20px; height: 45px;"></div>
<div style="flex-wrap: wrap; width: 140px; gap: 10px;">
<div style="width: 40px; height: 10px;"></div>
<div style="width: 40px; height: 30px; margin: 10px 0; align-self: baseline;"></div>
<div style="width: 40px; height: 50px;"></div>
<div style="width: 40px; height: 60px;"></div>
<div style="width: 40px; height: 40px; margin: 10px 0; align-self: baseline;"></div>
<div style="width: 40px; height: 20px;"></div>
</div>
</div>
</body>
</html>
Loading