diff --git a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs index f309fe606c029..5fa16b968628c 100644 --- a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs @@ -66,6 +66,7 @@ struct FlexItem<'pass> { padding: DirectionAgnosticMargins, is_min_violation: bool, is_max_violation: bool, + content_baselines: DerivedBaselines, } impl<'pass> FlexItem<'pass> { @@ -94,6 +95,7 @@ impl<'pass> FlexItem<'pass> { padding: DirectionAgnosticMargins::default(), is_min_violation: false, is_max_violation: false, + content_baselines: DerivedBaselines::default(), } } @@ -2295,8 +2297,16 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn box_baseline(&self, node: Node) -> CssPixels { - crate::layout::box_baseline(self.state, &self.callbacks, node, crate::layout::BaselineSet::First) + fn item_box_baseline(&self, index: usize) -> CssPixels { + let item = &self.flex_items[index]; + crate::layout::box_baseline_with_content_baselines( + self.state, + &self.callbacks, + item.box_, + item.used_values, + crate::layout::BaselineSet::First, + item.content_baselines, + ) } // https://drafts.csswg.org/css-flexbox-1/#valdef-align-items-baseline @@ -2319,13 +2329,13 @@ impl<'pass> FlexFormattingContext<'pass> { let mut max_baseline = CssPixels::default(); for index in self.flex_lines[line_index].items.iter().copied() { if participates(self, index) { - max_baseline = max_baseline.max(self.box_baseline(self.flex_items[index].box_)); + max_baseline = max_baseline.max(self.item_box_baseline(index)); } } for item_position in 0..self.flex_lines[line_index].items.len() { let index = self.flex_lines[line_index].items[item_position]; if participates(self, index) { - let baseline = self.box_baseline(self.flex_items[index].box_); + let baseline = self.item_box_baseline(index); self.flex_items[index].cross_offset += max_baseline - baseline; } } @@ -2387,11 +2397,15 @@ impl<'pass> FlexFormattingContext<'pass> { input.sizing.forced_min_border_box_block_size = Some(intrinsic_size + extra); } - match crate::layout::layout_inside_child(run, None, None, node, LayoutMode::Normal, input, false) { - crate::layout::ChildLayoutOutcome::Created(_) => {} - crate::layout::ChildLayoutOutcome::ReenterCurrent => self.run(run, input), - crate::layout::ChildLayoutOutcome::Skipped => {} - } + self.flex_items[index].content_baselines = + match crate::layout::layout_inside_child(run, None, None, node, LayoutMode::Normal, input, false) { + crate::layout::ChildLayoutOutcome::Created(result) => result.baselines, + crate::layout::ChildLayoutOutcome::ReenterCurrent => { + self.run(run, input); + self.item_used(index).content_baselines_from_cells() + } + crate::layout::ChildLayoutOutcome::Skipped => self.item_used(index).content_baselines_from_cells(), + }; let container_inline_size = self.container_used().content_inline_size.get(); let container_block_size = self.container_used().content_block_size.get(); diff --git a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs index 8a3e6b97b1079..9eb61a380d2a4 100644 --- a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs @@ -484,12 +484,22 @@ pub(crate) fn box_baseline( state: &LayoutState, callbacks: &FfiLayoutFcCallbacks, box_: Node, + used: &UsedValues, + baseline_set: BaselineSet, +) -> CssPixels { + box_baseline_with_content_baselines(state, callbacks, box_, used, baseline_set, used.content_baselines_from_cells()) +} + +pub(crate) fn box_baseline_with_content_baselines( + state: &LayoutState, + callbacks: &FfiLayoutFcCallbacks, + box_: Node, + used: &UsedValues, mut baseline_set: BaselineSet, + content_baselines: DerivedBaselines, ) -> CssPixels { let facts = state.node_facts(callbacks, box_); let style = state.style_facts(callbacks, box_); - let used_pointer = state.used_values(callbacks, box_); - let used = used_pointer; let collapsed = used.uses_collapsing_borders_model.get(); // https://drafts.csswg.org/css2/#propdef-vertical-align @@ -559,9 +569,8 @@ pub(crate) fn box_baseline( let input_derives_from_children = facts.is_html_input_element() && !facts.children_are_inline(); let content_baseline = match baseline_set { - BaselineSet::First if used.has_first_baseline.get() => Some(used.first_baseline.get()), - BaselineSet::Last if used.has_last_baseline.get() => Some(used.last_baseline.get()), - _ => None, + BaselineSet::First => content_baselines.first, + BaselineSet::Last => content_baselines.last, }; if let Some(content_baseline) = content_baseline && (derive_baseline_from_content || input_derives_from_children) @@ -630,7 +639,7 @@ pub(crate) fn derive_baselines( let block_child_state = state.used_values(callbacks, fragment_node); let child_offset_from_margin_edge = block_child_state.content_offset.get().y - block_child_state.margin_box_top(block_child_state.uses_collapsing_borders_model.get()); - child_offset_from_margin_edge + box_baseline(state, callbacks, fragment_node, baseline_set) + child_offset_from_margin_edge + box_baseline(state, callbacks, fragment_node, block_child_state, baseline_set) }; let mut first_line_index = 0; @@ -706,7 +715,7 @@ pub(crate) fn derive_baselines( } let child_offset_from_margin_edge = child_state.content_offset.get().y - child_state.margin_box_top(child_state.uses_collapsing_borders_model.get()); - return Some(child_offset_from_margin_edge + box_baseline(state, callbacks, child, baseline_set)); + return Some(child_offset_from_margin_edge + box_baseline(state, callbacks, child, child_state, baseline_set)); } None }; @@ -762,6 +771,7 @@ pub struct FfiBordersData { pub(crate) struct ChildLayoutResult { pub automatic_content_inline_size: CssPixels, pub automatic_content_block_size: CssPixels, + pub baselines: DerivedBaselines, } pub(crate) enum ChildLayoutOutcome { @@ -1489,9 +1499,10 @@ fn run_formatting_context<'pass>( None }; let mut implementation = None; - let result = if let Some(cached_block_size) = cached_atomic_block_size { + let result = if let Some((cached_block_size, cached_baselines)) = cached_atomic_block_size { ChildLayoutResult { automatic_content_block_size: cached_block_size, + baselines: cached_baselines, ..ChildLayoutResult::default() } } else { @@ -1499,47 +1510,42 @@ fn run_formatting_context<'pass>( let result = match &mut context_implementation { FormattingContextImplementation::Block(context) => { context.run(run, body_input); - let result = ChildLayoutResult { + let baselines = context.derived_baselines_of_root_box(); + store_derived_baselines(run.state.used_values(&run.callbacks, run.box_), baselines); + ChildLayoutResult { automatic_content_inline_size: context.automatic_content_inline_size(), automatic_content_block_size: context.automatic_content_block_size(), - }; - store_derived_baselines( - run.state.used_values(&run.callbacks, run.box_), - context.derived_baselines_of_root_box(), - ); - result + baselines, + } } FormattingContextImplementation::Flex(context) => { context.run(run, body_input); - store_derived_baselines( - run.state.used_values(&run.callbacks, run.box_), - context.derived_baselines_of_root_box(), - ); + let baselines = context.derived_baselines_of_root_box(); + store_derived_baselines(run.state.used_values(&run.callbacks, run.box_), baselines); ChildLayoutResult { automatic_content_inline_size: context.automatic_content_inline_size(), automatic_content_block_size: context.automatic_content_block_size(), + baselines, } } FormattingContextImplementation::Grid(context) => { context.run(run, body_input); - store_derived_baselines( - run.state.used_values(&run.callbacks, run.box_), - context.derived_baselines_of_root_box(), - ); + let baselines = context.derived_baselines_of_root_box(); + store_derived_baselines(run.state.used_values(&run.callbacks, run.box_), baselines); ChildLayoutResult { automatic_content_inline_size: context.automatic_content_inline_size(), automatic_content_block_size: context.automatic_content_block_size(), + baselines, } } FormattingContextImplementation::Table(context) => { context.run(run, body_input); - store_derived_baselines( - run.state.used_values(&run.callbacks, run.box_), - context.derived_baselines_of_root_box(), - ); + let baselines = context.derived_baselines_of_root_box(); + store_derived_baselines(run.state.used_values(&run.callbacks, run.box_), baselines); ChildLayoutResult { automatic_content_inline_size: context.automatic_content_inline_size(), automatic_content_block_size: context.automatic_content_block_size, + baselines, } } FormattingContextImplementation::Svg(context) => { @@ -1572,7 +1578,7 @@ fn run_formatting_context<'pass>( finalize_atomic_root_block_size( run, &input, - cached_atomic_block_size, + cached_atomic_block_size.map(|(block_size, _)| block_size), automatic_content_block_size_of_completed_body_run, parent_block, ); diff --git a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs index e0e89c35bede1..771e20febc961 100644 --- a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs @@ -886,26 +886,28 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { next.map(|next| next - containing_block_offset_in_root) } - fn layout_inside(&mut self, node: Node, available_space: AvailableSpace) { + fn layout_inside(&mut self, node: Node, available_space: AvailableSpace) -> DerivedBaselines { let input = LayoutInput::new( available_space, self.input.containing_block_constraints, ParticipationInParentFormattingContext::AtomicInline, ); - if let crate::layout::ChildLayoutOutcome::ReenterCurrent = crate::layout::layout_inside_child( - self.run, - Some(self.parent), - None, - node, - self.layout_mode, - input, - false, - ) { - self.parent.run(self.run, input); + let content_baselines_from_cells = |used: &UsedValues| DerivedBaselines { + first: used.has_first_baseline.get().then(|| used.first_baseline.get()), + last: used.has_last_baseline.get().then(|| used.last_baseline.get()), + }; + match crate::layout::layout_inside_child(self.run, Some(self.parent), None, node, self.layout_mode, input, false) + { + crate::layout::ChildLayoutOutcome::Created(result) => result.baselines, + crate::layout::ChildLayoutOutcome::ReenterCurrent => { + self.parent.run(self.run, input); + content_baselines_from_cells(self.used(node)) + } + crate::layout::ChildLayoutOutcome::Skipped => content_baselines_from_cells(self.used(node)), } } - pub(crate) fn dimension_box_on_line(&mut self, node: Node) { + pub(crate) fn dimension_box_on_line(&mut self, node: Node) -> DerivedBaselines { let available_space = self.input.available_space; let facts = self.facts(node); // Any fragmented inline box should have generated line box fragments already. @@ -918,15 +920,16 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { self.callbacks.shell(node), ); } - return; + return DerivedBaselines::default(); } - self.layout_inside(node, available_space); + let content_baselines = self.layout_inside(node, available_space); debug_assert!( self.used(node).has_definite_inline_size.get() || self.used(node).inline_size_constraint.get() != SizeConstraint::None, "atomic inline-level run left its root's inline size unresolved" ); + content_baselines } fn clear_floating_boxes(&self, node: Node) -> bool { @@ -1021,6 +1024,7 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { item.padding_end + item.border_end, item.margin_start, item.margin_end, + item.content_baselines, ); } ItemType::BlockLevelBox => { diff --git a/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs b/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs index 6f11ac9417e9a..9510277e1da39 100644 --- a/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs +++ b/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs @@ -31,6 +31,7 @@ pub(crate) struct Item { pub(crate) is_collapsible_whitespace: bool, pub(crate) can_break_before: bool, pub(crate) preceded_by_unattached_inline_start_edges: bool, + pub(crate) content_baselines: DerivedBaselines, } impl Item { @@ -51,6 +52,7 @@ impl Item { is_collapsible_whitespace: false, can_break_before: false, preceded_by_unattached_inline_start_edges: false, + content_baselines: DerivedBaselines::default(), } } @@ -552,8 +554,9 @@ impl<'iterator, 'context, 'pass> InlineLevelIteratorGenerator<'iterator, 'contex self.context() .create_used_values(node, self.context().input.containing_block_constraints) }; - self.context_mut().dimension_box_on_line(node); + let content_baselines = self.context_mut().dimension_box_on_line(node); let mut item = Item::new(ItemType::Element, node); + item.content_baselines = content_baselines; item.inline_size = used.content_inline_size.get(); item.padding_start = used.padding_left.get(); item.padding_end = used.padding_right.get(); diff --git a/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs b/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs index 35eda738026a0..49fe70aea0630 100644 --- a/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs +++ b/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs @@ -49,6 +49,7 @@ pub(crate) struct LineBoxFragmentData { pub(crate) first_available_font: *const c_void, pub(crate) text_utf16: *const u16, pub(crate) text_length_in_code_units: usize, + pub(crate) content_baselines: Option, } #[derive(Clone, Copy)] @@ -103,6 +104,7 @@ impl LineBoxFragmentData { first_available_font: facts.first_available_font, text_utf16: facts.text_utf16, text_length_in_code_units: facts.text_length_in_code_units, + content_baselines: None, }; if let Some(glyphs) = &fragment.glyphs { fragment.current_insert_direction = fragment.resolve_glyph_run_direction(glyphs.text_type); diff --git a/Libraries/LibWeb/Rust/src/layout/line_builder.rs b/Libraries/LibWeb/Rust/src/layout/line_builder.rs index b6a617d4c025e..f6b22ad95e660 100644 --- a/Libraries/LibWeb/Rust/src/layout/line_builder.rs +++ b/Libraries/LibWeb/Rust/src/layout/line_builder.rs @@ -216,6 +216,7 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { trailing_size: CssPixels, leading_margin: CssPixels, trailing_margin: CssPixels, + content_baselines: DerivedBaselines, ) { self.prepare_to_append_inline_content(); let used = self.context().used(node); @@ -244,6 +245,7 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { fragment_facts, text_align_is_justify, ); + self.line_mut(line_index).fragments[fragment_index].content_baselines = Some(content_baselines); self.max_block_size_on_current_line = self.max_block_size_on_current_line.max(margin_block_size); let used = self.context().used_mut(node); used.has_containing_line_box_fragment.set(false); @@ -557,18 +559,28 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { let mut line_box_baseline = strut_baseline; let fragment_count = self.line(line_index).fragments.len(); for fragment_index in 0..fragment_count { - let (node, style_source) = { + let (node, style_source, content_baselines) = { let fragment = &self.line(line_index).fragments[fragment_index]; - (fragment.layout_node, fragment.style_source) + (fragment.layout_node, fragment.style_source, fragment.content_baselines) }; let style = self.context().style(style_source); let fragment_baseline = if self.context().facts(node).is_text_node() { Self::baseline_for_style(style, style.line_height()) + } else if let Some(content_baselines) = content_baselines { + crate::layout::box_baseline_with_content_baselines( + self.context().state, + &self.context().callbacks, + node, + self.context().used(node), + crate::layout::BaselineSet::Last, + content_baselines, + ) } else { crate::layout::box_baseline( self.context().state, &self.context().callbacks, node, + self.context().used(node), crate::layout::BaselineSet::Last, ) }; diff --git a/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs index 0b3e086c7b33a..30500b98562d6 100644 --- a/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/replaced_with_children_formatting_context.rs @@ -69,5 +69,6 @@ fn layout_replaced_with_children(run: &FormattingContextRun, layout_input: Layou ChildLayoutResult { automatic_content_inline_size: content_inline_size, automatic_content_block_size: wrapper_layout.automatic_content_block_size, + baselines: DerivedBaselines::default(), } } diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index a047b416f2b8b..03de4e200133c 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -1275,7 +1275,7 @@ impl<'pass> SizingContext<'pass> { available_inline_size: AvailableSize, available_block_size: AvailableSize, constraints: ContainingBlockConstraints, - ) -> Option { + ) -> Option<(CssPixels, DerivedBaselines)> { // OPTIMIZATION: Calculating an intrinsic inline size already performs a complete measurement layout. // A later equivalent intrinsic line build only consumes the atomic box's measured dimensions and // baselines, so retain that summary instead of formatting the same descendants again. Commit layout @@ -1304,7 +1304,11 @@ impl<'pass> SizingContext<'pass> { used.first_baseline.set(measurement.first_baseline); used.has_last_baseline.set(measurement.has_last_baseline); used.last_baseline.set(measurement.last_baseline); - Some(measurement.automatic_content_block_size) + let baselines = DerivedBaselines { + first: measurement.has_first_baseline.then_some(measurement.first_baseline), + last: measurement.has_last_baseline.then_some(measurement.last_baseline), + }; + Some((measurement.automatic_content_block_size, baselines)) } fn calculate_transferred_inline_size_for_replaced_element( diff --git a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs index 1d59dae74ea1e..ecce05bba8973 100644 --- a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs @@ -1924,7 +1924,7 @@ impl<'pass> TableFormattingContext<'pass> { input: AvailableSpace, adopt_automatic_content_block_size: bool, intrinsic_block_padding: Option<(CssPixels, CssPixels)>, - ) { + ) -> DerivedBaselines { let layout_input = LayoutInput { available_space: input, containing_block_constraints: ContainingBlockConstraints::default(), @@ -1936,15 +1936,38 @@ impl<'pass> TableFormattingContext<'pass> { }, participation: ParticipationInParentFormattingContext::Item, }; - if let crate::layout::ChildLayoutOutcome::ReenterCurrent = - crate::layout::layout_inside_child(run, None, None, cell.box_, self.layout_mode, layout_input, false) - { - self.run(run, layout_input); + match crate::layout::layout_inside_child(run, None, None, cell.box_, self.layout_mode, layout_input, false) { + crate::layout::ChildLayoutOutcome::Created(result) => result.baselines, + crate::layout::ChildLayoutOutcome::ReenterCurrent => { + self.run(run, layout_input); + self.used_values(cell.box_).content_baselines_from_cells() + } + crate::layout::ChildLayoutOutcome::Skipped => self.used_values(cell.box_).content_baselines_from_cells(), } } + fn cell_box_baseline(&self, cell_box: Node, committing_run_baselines: Option) -> CssPixels { + let Some(content_baselines) = committing_run_baselines else { + return self.box_baseline(cell_box); + }; + crate::layout::box_baseline_with_content_baselines( + self.state, + &self.callbacks, + cell_box, + self.used_values(cell_box), + crate::layout::BaselineSet::First, + content_baselines, + ) + } + fn box_baseline(&self, node: Node) -> CssPixels { - crate::layout::box_baseline(self.state, &self.callbacks, node, crate::layout::BaselineSet::First) + crate::layout::box_baseline( + self.state, + &self.callbacks, + node, + self.used_values(node), + crate::layout::BaselineSet::First, + ) } fn measure_cell( @@ -1997,13 +2020,16 @@ impl<'pass> TableFormattingContext<'pass> { participation: ParticipationInParentFormattingContext::Item, }, ); + let measured_cell_used = measurement.rust_state().used_values(measurement.callbacks(), cell.box_); Some(MeasuredCellContent { content_block_size: result.automatic_content_block_size, - first_baseline: crate::layout::box_baseline( + first_baseline: crate::layout::box_baseline_with_content_baselines( measurement.rust_state(), measurement.callbacks(), cell.box_, + measured_cell_used, crate::layout::BaselineSet::First, + result.baselines, ), }) } @@ -2072,6 +2098,7 @@ impl<'pass> TableFormattingContext<'pass> { || self.anonymous_cell_wraps_flex_or_grid(cell); self.deferred_cell_inside_layouts[cell_index] = defer_inside_layout; let mut measured_baseline = None; + let mut committing_run_baselines = None; if defer_inside_layout { // This cell's final inside layout happens once row heights are final; measure its // content in a throwaway state instead of laying out the committing state twice. @@ -2080,7 +2107,7 @@ impl<'pass> TableFormattingContext<'pass> { measured_baseline = Some(measured.first_baseline); } } else { - self.layout_inside_cell(run, cell, inner, true, None); + committing_run_baselines = Some(self.layout_inside_cell(run, cell, inner, true, None)); } if self.needs_fixed_mode_row_measurement { let min_size = style.min_height().to_px(participant_block_basis); @@ -2092,7 +2119,8 @@ impl<'pass> TableFormattingContext<'pass> { // https://drafts.csswg.org/css2/#height-layout // The baseline of a cell is the baseline of the first in-flow line box in the cell, or the first in-flow // table-row in the cell, whichever comes first. - let baseline = measured_baseline.unwrap_or_else(|| self.box_baseline(cell.box_)); + let baseline = + measured_baseline.unwrap_or_else(|| self.cell_box_baseline(cell.box_, committing_run_baselines)); self.cells[cell_index].baseline = baseline; // Implements https://www.w3.org/TR/css-tables-3/#computing-the-table-height diff --git a/Libraries/LibWeb/Rust/src/layout/used_values.rs b/Libraries/LibWeb/Rust/src/layout/used_values.rs index 95bbcb8376dd2..3fa0c10b3763c 100644 --- a/Libraries/LibWeb/Rust/src/layout/used_values.rs +++ b/Libraries/LibWeb/Rust/src/layout/used_values.rs @@ -217,6 +217,13 @@ impl Default for UsedValues { } impl UsedValues { + pub(crate) fn content_baselines_from_cells(&self) -> crate::layout::DerivedBaselines { + crate::layout::DerivedBaselines { + first: self.has_first_baseline.get().then(|| self.first_baseline.get()), + last: self.has_last_baseline.get().then(|| self.last_baseline.get()), + } + } + /// Seals every field that commit emits as part of FfiCommittedBoxMetrics. /// Called when the box is placed: after placement, none of these may /// change again.