diff --git a/Libraries/LibWeb/Rust/src/css/computed_value_types.rs b/Libraries/LibWeb/Rust/src/css/computed_value_types.rs index cfa7c9aa8abe8..d73e2d4208c4e 100644 --- a/Libraries/LibWeb/Rust/src/css/computed_value_types.rs +++ b/Libraries/LibWeb/Rust/src/css/computed_value_types.rs @@ -22,11 +22,13 @@ pub enum ComputedSizeKind { /// immutable Rust style-value identity. Fit-content retains only its /// argument, and keyword-only forms leave the handle empty. #[repr(C)] +#[derive(Debug)] pub struct ComputedStyleValueHandle { pub pointer: *const std::ffi::c_void, } #[repr(C)] +#[derive(Debug)] pub struct ComputedSize { pub kind: ComputedSizeKind, pub value: ComputedStyleValueHandle, @@ -367,3 +369,18 @@ pub struct SVGResetValues { pub vector_effect: u8, pub shape_rendering: u8, } + +// Registered indices of the style groups the computed-values view reads, +// pinned to the C++ StyleGroupIndex enum by static_asserts in +// LayoutRustBridge.cpp. +pub const STYLE_GROUP_INDEX_INHERITED_TABLE: usize = 0; +pub const STYLE_GROUP_INDEX_GRID: usize = 9; +pub const STYLE_GROUP_INDEX_INHERITED_TEXT: usize = 4; +pub const STYLE_GROUP_INDEX_INHERITED_BOX: usize = 5; +pub const STYLE_GROUP_INDEX_FONT: usize = 6; +pub const STYLE_GROUP_INDEX_SVG_RESET: usize = 8; +pub const STYLE_GROUP_INDEX_BORDER: usize = 17; +pub const STYLE_GROUP_INDEX_ALIGNMENT: usize = 18; +pub const STYLE_GROUP_INDEX_SIZING: usize = 20; +pub const STYLE_GROUP_INDEX_SURROUND: usize = 21; +pub const STYLE_GROUP_INDEX_BOX: usize = 22; diff --git a/Libraries/LibWeb/Rust/src/css/computed_value_views.rs b/Libraries/LibWeb/Rust/src/css/computed_value_views.rs new file mode 100644 index 0000000000000..4d05ea5fd1cbd --- /dev/null +++ b/Libraries/LibWeb/Rust/src/css/computed_value_views.rs @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +//! Lazy readers over the computed style group payload types, mirroring the +//! C++ `CSS::Size` / `CSS::LengthPercentage` method surface: every query +//! resolves on demand from the stored computed representation, so readers +//! need no intermediate decoded value. + +use crate::css::calc; +use crate::css::computed_value_types::{ + AlignmentValues, BorderLayoutFacts, BoxValues, ComputedAspectRatio, ComputedGap, ComputedLengthPercentageOrAuto, + ComputedSize, ComputedSizeKind, ComputedStyleValueHandle, FontLayoutFacts, GridValues, InheritedTextLayoutFacts, + STYLE_GROUP_INDEX_ALIGNMENT, STYLE_GROUP_INDEX_BORDER, STYLE_GROUP_INDEX_BOX, STYLE_GROUP_INDEX_FONT, + STYLE_GROUP_INDEX_GRID, STYLE_GROUP_INDEX_INHERITED_BOX, STYLE_GROUP_INDEX_INHERITED_TABLE, + STYLE_GROUP_INDEX_INHERITED_TEXT, STYLE_GROUP_INDEX_SIZING, STYLE_GROUP_INDEX_SURROUND, + STYLE_GROUP_INDEX_SVG_RESET, SVGResetValues, SizingValues, SurroundValues, +}; +use crate::css::computed_values::{InheritedBoxValues, InheritedTableValues}; +use crate::css::css_pixels::CssPixels; +use crate::css::display::FfiDisplay; +use crate::css::style_value::StyleValueData; +use std::ffi::c_void; + +/// The used-value truncation the C++ layout engine applies when resolving +/// percentages: truncate toward zero at 1/64 precision, collapse NaN to zero, +/// and saturate the raw value. +pub(crate) fn truncated_css_pixels(value: f64) -> CssPixels { + if value.is_nan() { + return CssPixels::default(); + } + let raw = (value * 64.0).trunc(); + CssPixels::from_raw(raw.clamp(i32::MIN as f64, i32::MAX as f64) as i32) +} + +pub(crate) fn px_calc_resolution_context(percentage_basis: CssPixels) -> calc::FfiCalcResolutionContext { + calc::FfiCalcResolutionContext { + basis_kind: 3, + basis_value: percentage_basis.to_double(), + basis_unit: crate::css::style_compute::px_length_unit(), + length_resolution_context: std::ptr::null(), + external_resolutions: std::ptr::null(), + external_resolution_count: 0, + } +} + +pub(crate) fn resolve_calc_to_px(calculated: *const c_void, percentage_basis: CssPixels) -> CssPixels { + assert!(!calculated.is_null()); + let context = px_calc_resolution_context(percentage_basis); + // SAFETY: The style value stays alive for the pass and the context + // carries no host callbacks. + let result = unsafe { calc::rust_calc_resolve(calculated, &raw const context, true) }; + assert!(result.resolved); + CssPixels::nearest_value_for(result.value) +} + +/// A borrowed computed ``: a retained length, percentage +/// or calculated style value read in place, the Rust twin of the C++ +/// `LengthPercentage::view` API. +#[derive(Clone, Copy)] +pub(crate) struct LengthPercentageRef<'a> { + value: &'a StyleValueData, +} + +impl LengthPercentageRef<'_> { + pub(crate) fn is_calculated(self) -> bool { + matches!(self.value, StyleValueData::Calculated { .. }) + } + + pub(crate) fn absolute_length_to_px(self) -> CssPixels { + let StyleValueData::Length { value, unit } = self.value else { + unreachable!("computed length-percentage read as a length holds another style value"); + }; + let ratio = crate::css::style_compute::LENGTH_UNIT_CANONICAL_PX_RATIOS[*unit as usize]; + assert!(ratio.is_finite(), "computed length is not absolute"); + CssPixels::nearest_value_for(value * ratio) + } + + /// Matches Percentage::as_fraction(); the multiplication order is + /// observable for some f64 inputs. + pub(crate) fn as_fraction(self) -> f64 { + let StyleValueData::Percentage { value } = self.value else { + unreachable!("computed length-percentage read as a percentage holds another style value"); + }; + value * 0.01 + } + + /// The retained calculated style value, for handing to calc resolution. + pub(crate) fn calculated_pointer(self) -> *const c_void { + assert!(self.is_calculated()); + std::ptr::from_ref(self.value).cast() + } + + pub(crate) fn contains_percentage(self) -> bool { + match self.value { + StyleValueData::Length { .. } => false, + StyleValueData::Percentage { .. } => true, + StyleValueData::Calculated { .. } => { + // SAFETY: The calculated style value outlives this borrowed + // view, and the root query takes no other state. + let root = unsafe { calc::rust_calc_root_from_calculated(self.calculated_pointer()) }; + assert!(!root.is_null()); + // SAFETY: The root borrows the same retained calculation. + unsafe { calc::rust_calc_node_contains_percentage(root) } + } + _ => unreachable!("computed length-percentage holds a non-length-percentage style value"), + } + } + + pub(crate) fn contains_anchor_function(self) -> bool { + // SAFETY: The calculated style value outlives this borrowed view. + self.is_calculated() && unsafe { calc::rust_calc_contains_anchor(self.calculated_pointer()) } + } + + pub(crate) fn to_px(self, reference: CssPixels) -> CssPixels { + match self.value { + StyleValueData::Length { .. } => self.absolute_length_to_px(), + StyleValueData::Percentage { .. } => truncated_css_pixels(reference.to_double() * self.as_fraction()), + StyleValueData::Calculated { .. } => resolve_calc_to_px(self.calculated_pointer(), reference), + _ => unreachable!("computed length-percentage holds a non-length-percentage style value"), + } + } +} + +impl ComputedStyleValueHandle { + /// The lifetime is the caller's to choose: the referenced style value is + /// retained by whatever owns the handle, not by the handle borrow itself. + pub(crate) fn length_percentage<'a>(&self) -> Option> { + if self.pointer.is_null() { + return None; + } + // SAFETY: A non-null handle points at the retained style value owned + // by the node's style group payload, which outlives every reader. + Some(LengthPercentageRef { + value: unsafe { &*self.pointer.cast::() }, + }) + } +} + +impl ComputedSize { + pub(crate) fn is_auto(&self) -> bool { + self.kind == ComputedSizeKind::Auto + } + + pub(crate) fn is_length(&self) -> bool { + self.kind == ComputedSizeKind::Length + } + + pub(crate) fn is_percentage(&self) -> bool { + self.kind == ComputedSizeKind::Percentage + } + + pub(crate) fn is_min_content(&self) -> bool { + self.kind == ComputedSizeKind::MinContent + } + + pub(crate) fn is_max_content(&self) -> bool { + self.kind == ComputedSizeKind::MaxContent + } + + pub(crate) fn is_fit_content(&self) -> bool { + self.kind == ComputedSizeKind::FitContent + } + + pub(crate) fn is_none(&self) -> bool { + self.kind == ComputedSizeKind::None + } + + pub(crate) fn is_length_percentage(&self) -> bool { + matches!( + self.kind, + ComputedSizeKind::Calculated | ComputedSizeKind::Length | ComputedSizeKind::Percentage + ) + } + + pub(crate) fn is_intrinsic_sizing_constraint(&self) -> bool { + matches!( + self.kind, + ComputedSizeKind::MinContent | ComputedSizeKind::MaxContent | ComputedSizeKind::FitContent + ) + } + + /// The length-percentage term of a Length, Percentage or Calculated size. + pub(crate) fn length_percentage(&self) -> LengthPercentageRef<'_> { + debug_assert!(self.is_length_percentage()); + self.value + .length_percentage() + .expect("computed length-percentage size lost its style value") + } + + /// The fit-content argument; None for the keyword-only form. + pub(crate) fn fit_content_available_space(&self) -> Option> { + debug_assert!(self.is_fit_content()); + self.value.length_percentage() + } + + pub(crate) fn to_px(&self, reference: CssPixels) -> CssPixels { + match self.kind { + ComputedSizeKind::Auto + | ComputedSizeKind::MinContent + | ComputedSizeKind::MaxContent + | ComputedSizeKind::None => CssPixels::default(), + ComputedSizeKind::Calculated | ComputedSizeKind::Length | ComputedSizeKind::Percentage => { + self.length_percentage().to_px(reference) + } + ComputedSizeKind::FitContent => self + .fit_content_available_space() + .map_or(CssPixels::default(), |available_space| available_space.to_px(reference)), + } + } + + pub(crate) fn contains_percentage(&self) -> bool { + match self.kind { + ComputedSizeKind::Auto + | ComputedSizeKind::MinContent + | ComputedSizeKind::MaxContent + | ComputedSizeKind::None => false, + ComputedSizeKind::Calculated | ComputedSizeKind::Length | ComputedSizeKind::Percentage => { + self.length_percentage().contains_percentage() + } + ComputedSizeKind::FitContent => self + .fit_content_available_space() + .is_some_and(|available_space| available_space.contains_percentage()), + } + } +} + +impl ComputedLengthPercentageOrAuto { + pub(crate) fn is_auto(&self) -> bool { + self.is_auto + } + + pub(crate) fn length_percentage(&self) -> Option> { + if self.is_auto { + return None; + } + Some( + self.value + .length_percentage() + .expect("non-auto computed length-percentage lost its style value"), + ) + } + + pub(crate) fn to_px(&self, reference: CssPixels) -> CssPixels { + self.length_percentage() + .map_or(CssPixels::default(), |value| value.to_px(reference)) + } + + pub(crate) fn contains_percentage(&self) -> bool { + self.length_percentage() + .is_some_and(|value| value.contains_percentage()) + } +} + +impl ComputedGap { + pub(crate) fn is_normal(&self) -> bool { + self.is_normal + } + + pub(crate) fn length_percentage(&self) -> Option> { + if self.is_normal { + return None; + } + Some( + self.value + .length_percentage() + .expect("non-normal computed gap lost its style value"), + ) + } + + pub(crate) fn to_px(&self, reference: CssPixels) -> CssPixels { + self.length_percentage() + .map_or(CssPixels::default(), |value| value.to_px(reference)) + } +} + +struct SyncComputedSize(ComputedSize); + +// SAFETY: The shared value's handle is null, so there is no pointee to race +// on. +unsafe impl Sync for SyncComputedSize {} + +static AUTO_COMPUTED_SIZE: SyncComputedSize = SyncComputedSize(ComputedSize { + kind: ComputedSizeKind::Auto, + value: ComputedStyleValueHandle { + pointer: std::ptr::null(), + }, +}); + +/// A shared computed `auto` size for readers that substitute auto for a +/// stored size. +pub(crate) fn auto_computed_size() -> &'static ComputedSize { + &AUTO_COMPUTED_SIZE.0 +} + +// https://drafts.csswg.org/css-contain-2/#containment-types +fn containment_applies_to_principal_box(display: FfiDisplay) -> bool { + if display.is_internal_table() && !display.is_table_cell() { + return false; + } + if display.is_inline_outside() && display.is_flow_inside() { + return false; + } + true +} + +/// The computed `aspect-ratio` term as a CSSPixels fraction. A zero +/// denominator means no usable ratio (none specified, degenerate, or collapsed +/// to zero by the fixed-point conversion). +fn decode_css_preferred_aspect_ratio(ratio: &ComputedAspectRatio) -> (CssPixels, CssPixels) { + let no_usable_ratio = (CssPixels::default(), CssPixels::default()); + if !ratio.has_preferred_ratio { + return no_usable_ratio; + } + let numerator = ratio.preferred_ratio_numerator; + let denominator = ratio.preferred_ratio_denominator; + let is_degenerate = !numerator.is_finite() || numerator == 0.0 || !denominator.is_finite() || denominator == 0.0; + if is_degenerate { + return no_usable_ratio; + } + let (numerator, denominator) = CssPixels::fraction_nearest_values_for(numerator, denominator); + if numerator.raw_value() == 0 { + return no_usable_ratio; + } + (numerator, denominator) +} + +/// A borrowed view over one node's computed style group payloads: the Rust +/// twin of the C++ ComputedValues accessor surface. Every read hands out +/// payload references or lazy views; the payloads must stay alive and +/// unchanged while the view or anything borrowed from it is in use. +#[derive(Clone, Copy)] +pub(crate) struct ComputedValuesView<'a> { + groups: &'a [*const c_void], +} + +macro_rules! scalar_accessors { + ($($group:ident: { $($name:ident: $ty:ty => $($field:ident).+,)+ })+) => { + impl ComputedValuesView<'_> { + $($( + #[inline] + pub(crate) fn $name(self) -> $ty { + self.$group().$($field).+ + } + )+)+ + } + }; +} + +scalar_accessors! { + box_values: { + display: FfiDisplay => display, + display_before_box_type_transformation: FfiDisplay => display_before_box_type_transformation, + position: u8 => position, + float_: u8 => float_, + clear: u8 => clear, + box_sizing: u8 => box_sizing, + overflow_x: u8 => overflow_x, + overflow_y: u8 => overflow_y, + text_overflow: u8 => text_overflow, + table_layout: u8 => table_layout, + unicode_bidi: u8 => unicode_bidi, + grid_auto_flow_row: bool => grid_auto_flow_row, + grid_auto_flow_dense: bool => grid_auto_flow_dense, + has_column_count: bool => column_count_has_value, + column_count: i32 => column_count, + has_size_containment: bool => size_containment, + is_size_container: bool => is_size_container, + aspect_ratio_uses_natural_when_available: bool => aspect_ratio.use_natural_aspect_ratio_if_available, + } + border_facts: { + border_top_width: CssPixels => border_top.width, + border_right_width: CssPixels => border_right.width, + border_bottom_width: CssPixels => border_bottom.width, + border_left_width: CssPixels => border_left.width, + border_top_style: u8 => border_top.line_style, + border_right_style: u8 => border_right.line_style, + border_bottom_style: u8 => border_bottom.line_style, + border_left_style: u8 => border_left.line_style, + border_top_color: u32 => border_top.color, + border_right_color: u32 => border_right.color, + border_bottom_color: u32 => border_bottom.color, + border_left_color: u32 => border_left.color, + } + inherited_box: { + writing_mode: u8 => writing_mode, + direction: u8 => direction, + visibility: u8 => visibility, + } + inherited_table: { + border_collapse: u8 => border_collapse, + caption_side: u8 => caption_side, + } + inherited_text_facts: { + text_align: u8 => text_align, + text_justify: u8 => text_justify, + white_space_collapse: u8 => white_space_collapse, + text_wrap_mode: u8 => text_wrap_mode, + word_break: u8 => word_break, + letter_spacing: CssPixels => letter_spacing, + word_spacing: CssPixels => word_spacing, + text_indent_each_line: bool => text_indent.each_line, + text_indent_hanging: bool => text_indent.hanging, + tab_size_is_number: bool => tab_size_is_number, + tab_size: CssPixels => tab_size_length, + tab_size_number: f64 => tab_size_number, + } + font_facts: { + font_variant_emoji: u8 => font_variant_emoji, + line_height: CssPixels => line_height_used, + font_size: CssPixels => font_size, + font_ascent: f32 => font_ascent, + font_descent: f32 => font_descent, + font_x_height: f32 => font_x_height, + } + alignment: { + flex_direction: u8 => flex_direction, + flex_wrap: u8 => flex_wrap, + flex_grow: f64 => flex_grow, + flex_shrink: f64 => flex_shrink, + order: i32 => order, + align_items: u8 => align_items, + align_self: u8 => align_self, + align_content: u8 => align_content, + justify_content: u8 => justify_content, + justify_items: u8 => justify_items, + justify_self: u8 => justify_self, + flex_basis_is_content: bool => flex_basis.is_content, + } +} + +macro_rules! reference_accessors { + ($($group:ident: { $($name:ident: $ty:ty => $($field:ident).+,)+ })+) => { + impl<'a> ComputedValuesView<'a> { + $($( + #[inline] + pub(crate) fn $name(self) -> &'a $ty { + &self.$group().$($field).+ + } + )+)+ + } + }; +} + +reference_accessors! { + sizing: { + width: ComputedSize => width, + height: ComputedSize => height, + min_width: ComputedSize => min_width, + min_height: ComputedSize => min_height, + max_width: ComputedSize => max_width, + max_height: ComputedSize => max_height, + } + box_values: { + column_width: ComputedSize => column_width, + } + surround: { + margin_top: ComputedLengthPercentageOrAuto => margin.top, + margin_right: ComputedLengthPercentageOrAuto => margin.right, + margin_bottom: ComputedLengthPercentageOrAuto => margin.bottom, + margin_left: ComputedLengthPercentageOrAuto => margin.left, + padding_top: ComputedLengthPercentageOrAuto => padding.top, + padding_right: ComputedLengthPercentageOrAuto => padding.right, + padding_bottom: ComputedLengthPercentageOrAuto => padding.bottom, + padding_left: ComputedLengthPercentageOrAuto => padding.left, + } + alignment: { + row_gap: ComputedGap => row_gap, + column_gap: ComputedGap => column_gap, + } +} + +impl<'a> ComputedValuesView<'a> { + #[inline] + pub(crate) fn new(groups: &'a [*const c_void]) -> Self { + Self { groups } + } + + #[inline] + fn native_group(self, group_index: usize) -> &'a T { + let payload = self.groups[group_index]; + debug_assert!(!payload.is_null()); + // SAFETY: The payload is the Rust-defined group struct itself; C++ + // derives its mirror from the cbindgen twin of the same type, and the + // node's ComputedValues keep it alive while readers exist. + unsafe { &*payload.cast::() } + } + + #[inline] + fn sizing(self) -> &'a SizingValues { + self.native_group(STYLE_GROUP_INDEX_SIZING) + } + + #[inline] + pub(crate) fn surround(self) -> &'a SurroundValues { + self.native_group(STYLE_GROUP_INDEX_SURROUND) + } + + #[inline] + fn alignment(self) -> &'a AlignmentValues { + self.native_group(STYLE_GROUP_INDEX_ALIGNMENT) + } + + #[inline] + fn svg_reset(self) -> &'a SVGResetValues { + self.native_group(STYLE_GROUP_INDEX_SVG_RESET) + } + + #[inline] + fn inherited_box(self) -> &'a InheritedBoxValues { + self.native_group(STYLE_GROUP_INDEX_INHERITED_BOX) + } + + #[inline] + fn inherited_table(self) -> &'a InheritedTableValues { + self.native_group(STYLE_GROUP_INDEX_INHERITED_TABLE) + } + + #[inline] + pub(crate) fn box_values(self) -> &'a BoxValues { + self.native_group(STYLE_GROUP_INDEX_BOX) + } + + #[inline] + pub(crate) fn grid_values(self) -> &'a GridValues { + self.native_group(STYLE_GROUP_INDEX_GRID) + } + + #[inline] + fn border_facts(self) -> &'a BorderLayoutFacts { + self.native_group(STYLE_GROUP_INDEX_BORDER) + } + + #[inline] + fn inherited_text_facts(self) -> &'a InheritedTextLayoutFacts { + self.native_group(STYLE_GROUP_INDEX_INHERITED_TEXT) + } + + #[inline] + fn font_facts(self) -> &'a FontLayoutFacts { + self.native_group(STYLE_GROUP_INDEX_FONT) + } + + pub(crate) fn is_floating(self) -> bool { + self.box_values().float_ != crate::css::css_enums::float::NONE + } + + pub(crate) fn is_absolutely_positioned(self) -> bool { + matches!( + self.box_values().position, + crate::css::css_enums::positioning::ABSOLUTE | crate::css::css_enums::positioning::FIXED + ) + } + + // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context + // The computed-style-only half of the block-formatting-context predicate; + // node_creates_block_formatting_context adds the terms that need the node + // kind, stamped DOM identity, the live IsFlexItem flag, or the parent's + // display. The float term is deliberately absent for the same reason: only + // non-flex-items establish one by floating. + pub(crate) fn own_style_establishes_block_formatting_context(self) -> bool { + let box_values = self.box_values(); + let display = box_values.display; + + if self.is_absolutely_positioned() { + return true; + } + + if display.is_inline_block() { + return true; + } + + if display.is_table_cell() || display.is_table_caption() { + return true; + } + + let overflow_establishes_context = |overflow: u8| { + overflow != crate::css::css_enums::overflow::VISIBLE && overflow != crate::css::css_enums::overflow::CLIP + }; + if overflow_establishes_context(box_values.overflow_x) || overflow_establishes_context(box_values.overflow_y) { + return true; + } + + if display.is_flow_root_inside() { + return true; + } + + // https://drafts.csswg.org/css-contain-2/#containment-types + // 1. The layout containment box establishes an independent formatting context. + // 4. The paint containment box establishes an independent formatting context. + let content_visibility_forces_containment = + self.inherited_box().content_visibility == crate::css::css_enums::content_visibility::AUTO; + if (box_values.layout_containment || box_values.paint_containment || content_visibility_forces_containment) + && containment_applies_to_principal_box(display) + { + return true; + } + + // https://drafts.csswg.org/css-conditional-5/#valdef-container-type-size + // Applies style containment and size containment to the principal box, and establishes an independent + // formatting context. + if box_values.is_size_container || box_values.is_inline_size_container { + return true; + } + + // https://drafts.csswg.org/css-multicol-2/#the-multi-column-model + // An element whose 'column-width', 'column-count', or 'column-height' property is not 'auto' establishes a + // multi-column container (or multicol container for short), and therefore acts as a container for + // multi-column layout. + if box_values.column_width.kind != ComputedSizeKind::Auto || box_values.column_count_has_value { + return true; + } + + false + } + + pub(crate) fn x(self) -> LengthPercentageRef<'a> { + self.svg_reset() + .x + .length_percentage() + .expect("computed x lost its style value") + } + + pub(crate) fn y(self) -> LengthPercentageRef<'a> { + self.svg_reset() + .y + .length_percentage() + .expect("computed y lost its style value") + } + + pub(crate) fn text_indent(self) -> LengthPercentageRef<'a> { + self.inherited_text_facts() + .text_indent + .length_percentage + .length_percentage() + .expect("computed text-indent lost its style value") + } + + pub(crate) fn vertical_align_value(self) -> LengthPercentageRef<'a> { + self.box_values() + .vertical_align + .value + .length_percentage() + .expect("computed vertical-align lost its style value") + } + + pub(crate) fn has_position_anchor(self) -> bool { + self.surround().position_anchor_name.raw() != 0 + } + + /// The raw fly-string representation of the computed position-anchor + /// name, borrowed from the surround payload for the duration of the pass. + pub(crate) fn position_anchor_name(self) -> usize { + self.surround().position_anchor_name.raw() + } + + pub(crate) fn first_available_font(self) -> *const c_void { + let font = self.font_facts().first_available_font; + debug_assert!( + !font.is_null(), + "layout read a font group that never received a font list" + ); + font + } + + pub(crate) fn font_cascade_list(self) -> *const c_void { + let list = self.font_facts().font_cascade_list; + debug_assert!( + !list.is_null(), + "layout read a font group that never received a font list" + ); + list + } + + pub(crate) fn box_sizing_for_aspect_ratio(self) -> u8 { + let values = self.box_values(); + if values.aspect_ratio.use_natural_aspect_ratio_if_available { + crate::css::css_enums::box_sizing::CONTENT_BOX + } else { + values.box_sizing + } + } + + pub(crate) fn css_preferred_aspect_ratio(self) -> (CssPixels, CssPixels) { + decode_css_preferred_aspect_ratio(&self.box_values().aspect_ratio) + } + + pub(crate) fn border_spacing_horizontal(self) -> CssPixels { + CssPixels::from_raw(self.inherited_table().border_spacing_horizontal) + } + + pub(crate) fn border_spacing_vertical(self) -> CssPixels { + CssPixels::from_raw(self.inherited_table().border_spacing_vertical) + } + + pub(crate) fn flex_basis(self) -> &'a ComputedSize { + let flex_basis = &self.alignment().flex_basis; + if flex_basis.is_content { + auto_computed_size() + } else { + &flex_basis.size + } + } +} diff --git a/Libraries/LibWeb/Rust/src/css/mod.rs b/Libraries/LibWeb/Rust/src/css/mod.rs index 23cf2a6084a9e..ce77fbb28ff71 100644 --- a/Libraries/LibWeb/Rust/src/css/mod.rs +++ b/Libraries/LibWeb/Rust/src/css/mod.rs @@ -10,6 +10,7 @@ pub mod cascaded_properties; pub(crate) mod color_conversion; pub mod color_interpolation; pub mod computed_value_types; +pub(crate) mod computed_value_views; pub mod computed_values; pub mod css_enums; pub mod css_pixels; diff --git a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs index 4a9e125ebbd57..d38ebacca45fa 100644 --- a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs +++ b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs @@ -573,14 +573,14 @@ impl AbsposEngine<'_> { fn resolve_anchor_value( &self, - value: FfiSizeValue, + value: InsetValue, positioned_box: Node, containing_block: Node, axis: AnchorValueAxis, resolution_state: &mut AnchorResolutionState, ) -> Option { - assert!(value.contains_anchor_function); - assert!(!value.calc.is_null()); + assert!(value.contains_anchor_function()); + let calculated = value.anchor_bearing_calculated(); let mut callback_context = AnchorCalcCallbackContext { engine: self, positioned_box, @@ -594,7 +594,7 @@ impl AbsposEngine<'_> { // all callback state remains live for this synchronous resolution. let result = unsafe { resolve_calc_with_external_resolutions( - value.calc, + calculated, axis.containing_block_extent, (&raw mut callback_context).cast(), Some(resolve_anchor_non_math_function), @@ -617,10 +617,10 @@ impl AbsposEngine<'_> { } let style = self.style(node); - let top_contains_anchor = style.inset_top().contains_anchor_function; - let right_contains_anchor = style.inset_right().contains_anchor_function; - let bottom_contains_anchor = style.inset_bottom().contains_anchor_function; - let left_contains_anchor = style.inset_left().contains_anchor_function; + let top_contains_anchor = style.inset_top().contains_anchor_function(); + let right_contains_anchor = style.inset_right().contains_anchor_function(); + let bottom_contains_anchor = style.inset_bottom().contains_anchor_function(); + let left_contains_anchor = style.inset_left().contains_anchor_function(); if !top_contains_anchor && !right_contains_anchor && !bottom_contains_anchor && !left_contains_anchor { return; } @@ -827,7 +827,11 @@ unsafe extern "C" fn resolve_anchor_non_math_function(context: *mut c_void, shel type AutoPx = Option; -fn resolve_or_auto(value: FfiSizeValue, basis: CssPixels) -> AutoPx { +fn resolve_or_auto(value: InsetValue, basis: CssPixels) -> AutoPx { + (!value.is_auto()).then(|| value.to_px(basis)) +} + +fn resolve_margin_or_auto(value: &ComputedLengthPercentageOrAuto, basis: CssPixels) -> AutoPx { (!value.is_auto()).then(|| value.to_px(basis)) } @@ -970,8 +974,8 @@ impl AbsposEngine<'_> { let computed_right = style.inset_right(); let mut left = style.inset_left().to_px(containing_block_inline_size); let mut right = style.inset_right().to_px(containing_block_inline_size); - let mut margin_left = resolve_or_auto(style.margin_left(), containing_block_inline_size); - let mut margin_right = resolve_or_auto(style.margin_right(), containing_block_inline_size); + let mut margin_left = resolve_margin_or_auto(style.margin_left(), containing_block_inline_size); + let mut margin_right = resolve_margin_or_auto(style.margin_right(), containing_block_inline_size); let mut inline_size = input_inline_size; let solve_for_left = |inline_size: AutoPx, margin_left: AutoPx, margin_right: AutoPx, right: CssPixels| { @@ -1179,8 +1183,8 @@ impl AbsposEngine<'_> { available, resolve_or_auto(style.inset_left(), containing_block_inline_size), resolve_or_auto(style.inset_right(), containing_block_inline_size), - resolve_or_auto(style.margin_left(), containing_block_inline_size), - resolve_or_auto(style.margin_right(), containing_block_inline_size), + resolve_margin_or_auto(style.margin_left(), containing_block_inline_size), + resolve_margin_or_auto(style.margin_right(), containing_block_inline_size), self.static_offset(node, static_position_rect).inline_offset, ReplacedAxisBehavior { clear_auto_margins_if_start_is_auto: true, @@ -1280,8 +1284,8 @@ impl AbsposEngine<'_> { let style = self.style(node); let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); let containing_block_block_size = available_space.block_size.to_px_or_zero(); - let mut margin_top = resolve_or_auto(style.margin_top(), containing_block_inline_size); - let mut margin_bottom = resolve_or_auto(style.margin_bottom(), containing_block_inline_size); + let mut margin_top = resolve_margin_or_auto(style.margin_top(), containing_block_inline_size); + let mut margin_bottom = resolve_margin_or_auto(style.margin_bottom(), containing_block_inline_size); let mut top = resolve_or_auto(style.inset_top(), containing_block_block_size); let mut bottom = resolve_or_auto(style.inset_bottom(), containing_block_block_size); let used = self.used(node); @@ -1578,8 +1582,8 @@ impl AbsposEngine<'_> { available, resolve_or_auto(style.inset_top(), containing_block_block_size), resolve_or_auto(style.inset_bottom(), containing_block_block_size), - resolve_or_auto(style.margin_top(), containing_block_block_size), - resolve_or_auto(style.margin_bottom(), containing_block_block_size), + resolve_margin_or_auto(style.margin_top(), containing_block_block_size), + resolve_margin_or_auto(style.margin_bottom(), containing_block_block_size), self.static_offset(node, static_position_rect).block_offset, ReplacedAxisBehavior { clear_auto_margins_if_start_is_auto: false, @@ -1857,10 +1861,10 @@ impl<'pass> AbsposEngine<'pass> { return; } let initial_style = self.style(node); - if initial_style.inset_top().contains_anchor_function - || initial_style.inset_right().contains_anchor_function - || initial_style.inset_bottom().contains_anchor_function - || initial_style.inset_left().contains_anchor_function + if initial_style.inset_top().contains_anchor_function() + || initial_style.inset_right().contains_anchor_function() + || initial_style.inset_bottom().contains_anchor_function() + || initial_style.inset_left().contains_anchor_function() { self.resolve_anchor_insets(node); } @@ -1869,7 +1873,7 @@ impl<'pass> AbsposEngine<'pass> { return; } - let resolve_opposing = |first: FfiSizeValue, second: FfiSizeValue, basis: CssPixels| { + let resolve_opposing = |first: InsetValue, second: InsetValue, basis: CssPixels| { let resolved_first = first.to_px(basis); let resolved_second = second.to_px(basis); if first.is_auto() && second.is_auto() { @@ -1886,8 +1890,8 @@ impl<'pass> AbsposEngine<'pass> { containing_block_size.inline_size, ); - let treat_percentage_as_auto = |value: FfiSizeValue| { - if !value.contains_percentage { + let treat_percentage_as_auto = |value: InsetValue<'pass>| -> InsetValue<'pass> { + if !value.contains_percentage() { return value; } let mut containing_block = self.callbacks.containing_block(node); @@ -1899,7 +1903,7 @@ impl<'pass> AbsposEngine<'pass> { containing_block = self.callbacks.containing_block(containing_block); } if !containing_block.is_invalid() && !self.used(containing_block).has_definite_block_size() { - FfiSizeValue::auto_value() + InsetValue::auto_value() } else { value } diff --git a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs index 8ab2bf79c45cc..1b05623a816a2 100644 --- a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs @@ -2189,7 +2189,7 @@ impl<'pass> BlockFormattingContext<'pass> { // (04) N := column-count return Some(style.column_count()); } - let column_gap = if style.column_gap().is_auto() { + let column_gap = if style.column_gap().is_normal() { style.font_size() } else { style.column_gap().to_px(used_inline_size) @@ -2217,7 +2217,7 @@ impl<'pass> BlockFormattingContext<'pass> { let root_inline_size = self.used(self.root).content_inline_size.get(); if let Some(column_count) = self.determine_used_value_for_column_count(root_inline_size) { let style = self.style(self.root); - let column_gap = if style.column_gap().is_auto() { + let column_gap = if style.column_gap().is_normal() { style.font_size() } else { style.column_gap().to_px(root_inline_size) diff --git a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs index 653331602db31..018315b9bbf74 100644 --- a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs @@ -31,11 +31,11 @@ struct DirectionAgnosticMargins { cross_after_is_auto: bool, } -#[derive(Clone, Copy, Debug)] -enum UsedFlexBasis { +#[derive(Clone, Copy)] +enum UsedFlexBasis<'pass> { Content, Size { - value: FfiSizeValue, + value: &'pass ComputedSize, property: SizingProperty, }, } @@ -43,7 +43,7 @@ enum UsedFlexBasis { struct FlexItem<'pass> { box_: Node, used_values: &'pass UsedValues, - used_flex_basis: UsedFlexBasis, + used_flex_basis: UsedFlexBasis<'pass>, used_flex_basis_is_definite: bool, main_size_was_resolved_from_aspect_ratio: bool, cross_size_was_resolved_from_aspect_ratio: bool, @@ -474,7 +474,7 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn computed_main_size(&self, node: Node) -> (FfiSizeValue, SizingProperty) { + fn computed_main_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); if self.main_axis_is_horizontal() { (style.width(), SizingProperty::Width) @@ -483,7 +483,7 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn computed_main_min_size(&self, node: Node) -> (FfiSizeValue, SizingProperty) { + fn computed_main_min_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); if self.main_axis_is_horizontal() { (style.min_width(), SizingProperty::MinWidth) @@ -492,7 +492,7 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn computed_main_max_size(&self, node: Node) -> (FfiSizeValue, SizingProperty) { + fn computed_main_max_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); if self.main_axis_is_horizontal() { (style.max_width(), SizingProperty::MaxWidth) @@ -501,7 +501,7 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn computed_cross_size(&self, node: Node) -> (FfiSizeValue, SizingProperty) { + fn computed_cross_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); if self.cross_axis_is_horizontal() { (style.width(), SizingProperty::Width) @@ -510,7 +510,7 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn computed_cross_min_size(&self, node: Node) -> (FfiSizeValue, SizingProperty) { + fn computed_cross_min_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); if self.cross_axis_is_horizontal() { (style.min_width(), SizingProperty::MinWidth) @@ -519,7 +519,7 @@ impl<'pass> FlexFormattingContext<'pass> { } } - fn computed_cross_max_size(&self, node: Node) -> (FfiSizeValue, SizingProperty) { + fn computed_cross_max_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); if self.cross_axis_is_horizontal() { (style.max_width(), SizingProperty::MaxWidth) @@ -568,7 +568,7 @@ impl<'pass> FlexFormattingContext<'pass> { ) } - fn resolve_inner_block_size(&self, index: usize, value: FfiSizeValue, property: SizingProperty) -> CssPixels { + fn resolve_inner_block_size(&self, index: usize, value: &ComputedSize, property: SizingProperty) -> CssPixels { // NOTE: When the main axis is horizontal, after we've determined the main size, we use that as the // available inline size for any intrinsic sizing layout needed to resolve the block size. let available_space = if self.main_axis_is_horizontal() && value.is_intrinsic_sizing_constraint() { @@ -594,7 +594,7 @@ impl<'pass> FlexFormattingContext<'pass> { &self, index: usize, axis_is_horizontal: bool, - value: FfiSizeValue, + value: &ComputedSize, property: SizingProperty, ) -> CssPixels { if axis_is_horizontal { @@ -813,7 +813,7 @@ impl<'pass> FlexFormattingContext<'pass> { } // https://drafts.csswg.org/css-flexbox-1/#propdef-flex-basis - fn used_flex_basis_for_item(&self, index: usize) -> UsedFlexBasis { + fn used_flex_basis_for_item(&self, index: usize) -> UsedFlexBasis<'pass> { let node = self.flex_items[index].box_; let style = self.style(node); if style.flex_basis_is_content() { @@ -845,8 +845,8 @@ impl<'pass> FlexFormattingContext<'pass> { &self, node: Node, mut main_size: CssPixels, - min_cross_size: FfiSizeValue, - max_cross_size: FfiSizeValue, + min_cross_size: &ComputedSize, + max_cross_size: &ComputedSize, ) -> CssPixels { let ratio = self.facts(node).preferred_aspect_ratio().unwrap(); let reference = self.inner_cross_size_used(self.container_used()); @@ -865,8 +865,8 @@ impl<'pass> FlexFormattingContext<'pass> { &self, node: Node, mut cross_size: CssPixels, - min_main_size: FfiSizeValue, - max_main_size: FfiSizeValue, + min_main_size: &ComputedSize, + max_main_size: &ComputedSize, ) -> CssPixels { let ratio = self.facts(node).preferred_aspect_ratio().unwrap(); let reference = self.inner_main_size_used(self.container_used()); @@ -913,7 +913,7 @@ impl<'pass> FlexFormattingContext<'pass> { // We can resolve percentage min/max-width if the available inline size is definite. let can_resolve_percentages = matches!(self.available_space_for_items.unwrap().space.inline_size, AvailableSize::Definite(_)); let min_inline_size = - if !style.min_width().is_auto() && (!style.min_width().contains_percentage || can_resolve_percentages) { + if !style.min_width().is_auto() && (!style.min_width().contains_percentage() || can_resolve_percentages) { self.resolve_inner_inline_size(index, SizingProperty::MinWidth) } else { CssPixels::default() @@ -922,7 +922,7 @@ impl<'pass> FlexFormattingContext<'pass> { node, SizingAxis::Inline, self.available_space_for_items.unwrap().space.inline_size, - ) && (!style.max_width().contains_percentage || can_resolve_percentages) + ) && (!style.max_width().contains_percentage() || can_resolve_percentages) { self.resolve_inner_inline_size(index, SizingProperty::MaxWidth) } else { @@ -1027,8 +1027,8 @@ impl<'pass> FlexFormattingContext<'pass> { false } else if value.is_length() { true - } else if value.kind() == FfiSizeKind::Calc { - !value.contains_percentage || self.has_definite_main_size_used(self.container_used()) + } else if value.kind == ComputedSizeKind::Calculated { + !value.contains_percentage() || self.has_definite_main_size_used(self.container_used()) } else { debug_assert!(value.is_percentage()); self.has_definite_main_size_used(self.container_used()) @@ -1047,7 +1047,7 @@ impl<'pass> FlexFormattingContext<'pass> { // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution _ if self.facts(node).is_replaced_box() && self.available_space_for_items.unwrap().main == AvailableSize::MinContent - && self.computed_main_size(node).0.contains_percentage => + && self.computed_main_size(node).0.contains_percentage() => { CssPixels::default() } @@ -2647,13 +2647,13 @@ impl<'pass> FlexFormattingContext<'pass> { } let min = self.computed_cross_min_size(node).0; let max = self.computed_cross_max_size(node).0; - let clamp_min = if !min.is_auto() && (resolve_percentage_min_max_sizes || !min.contains_percentage) { + let clamp_min = if !min.is_auto() && (resolve_percentage_min_max_sizes || !min.contains_percentage()) { self.specified_cross_min_size(index) } else { CssPixels::default() }; let clamp_max = if !self.should_treat_max_size_as_none(node, true) - && (resolve_percentage_min_max_sizes || !max.contains_percentage) + && (resolve_percentage_min_max_sizes || !max.contains_percentage()) { self.specified_cross_max_size(index) } else { @@ -2672,7 +2672,7 @@ impl<'pass> FlexFormattingContext<'pass> { if self.should_treat_max_size_as_none(node, false) { return CssPixels::from_raw(i32::MAX); } - if !max.contains_percentage { + if !max.contains_percentage() { return self.specified_main_max_size(index); } if available_size == AvailableSize::MinContent { @@ -2842,7 +2842,7 @@ impl<'pass> FlexFormattingContext<'pass> { }; let result = self.flex_items[index].flex_base_size + CssPixels::nearest_value_for(product); let min = self.computed_main_min_size(self.flex_items[index].box_).0; - let clamp_min = if !min.is_auto() && !min.contains_percentage { + let clamp_min = if !min.is_auto() && !min.contains_percentage() { self.specified_main_min_size(index) } else { self.automatic_minimum_size(index) diff --git a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs index 4a8701096e4b3..55b3098a9729e 100644 --- a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs @@ -951,13 +951,14 @@ impl FfiLayoutFcCallbacks { unsafe { &*std::ptr::from_ref(payloads) } } - pub(crate) fn style_reader_if_styled(&self, node: Node) -> Option> { + pub(crate) fn computed_values_view_if_styled(&self, node: Node) -> Option> { let payloads = self.arena().style_payloads(node)?; // SAFETY: The node's ComputedValues keep the style container alive // for the pass, and the container is only replaced between passes: // set_computed_values verifies no pass is running and no layout node // is created mid-pass. - Some(StyleReader::new(unsafe { &*std::ptr::from_ref(payloads) })) + let payloads: &'static FfiStylePayloads = unsafe { &*std::ptr::from_ref(payloads) }; + Some(ComputedValuesView::new(&payloads.groups)) } pub(crate) fn can_skip_is_anonymous_text_run(&self, node: Node) -> bool { @@ -1123,8 +1124,8 @@ impl std::ops::DerefMut for FormattingContextInstance<'_> { pub(crate) fn formatting_context_type_created_by_node_data( data: &NodeData, - style: Option>, - parent_style: Option>, + style: Option>, + parent_style: Option>, ) -> Option { if data.kind == crate::layout::node_data::NodeKind::SVGSVGBox { return Some(FfiFormattingContextType::Svg); @@ -1188,8 +1189,8 @@ pub(crate) fn formatting_context_type_created_by_node_data( pub(crate) fn formatting_context_type_created_by_box(facts: NodeFacts<'_>) -> Option { formatting_context_type_created_by_node_data( facts.data(), - facts.style_reader_if_styled(), - facts.parent_style_reader_if_styled(), + facts.computed_values_view_if_styled(), + facts.parent_computed_values_view_if_styled(), ) } @@ -1208,11 +1209,11 @@ pub extern "C" fn rust_layout_formatting_context_type_for_box(facts: FfiFormatti let arena = unsafe { LayoutNodeArena::from_handle(facts.arena) }; // SAFETY: The caller supplies the live box's arena slot. let data = unsafe { &*arena.data(facts.node) }; - let style = arena.style_payloads(facts.node).map(StyleReader::new); + let style = arena.style_payloads(facts.node).map(|payloads| ComputedValuesView::new(&payloads.groups)); let parent_style = (!data.parent.is_invalid()) .then(|| arena.style_payloads(data.parent)) .flatten() - .map(StyleReader::new); + .map(|payloads| ComputedValuesView::new(&payloads.groups)); formatting_context_type_created_by_node_data(data, style, parent_style) .map(|type_| type_ as u8) .unwrap_or(NO_FORMATTING_CONTEXT) diff --git a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs index b980495d96584..9faffe4b13443 100644 --- a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs @@ -214,50 +214,32 @@ pub(crate) fn align_item( } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(u8)] -pub(crate) enum GridTrackBreadthKind { +/// The pass-facing view of one stored track sizing function; sized breadths +/// borrow the computed size from the style group payload, which outlives the +/// pass. +#[derive(Clone, Copy, Debug)] +pub(crate) enum GridTrackBreadth { Auto, - LengthPercentage, - Flex, + LengthPercentage(&'static ComputedSize), + Flex(f64), MinContent, MaxContent, - FitContent, + FitContent(&'static ComputedSize), } -#[derive(Clone, Copy, Debug)] -pub(crate) struct GridTrackBreadth { - pub(crate) kind: u8, - pub(crate) value: FfiSizeValue, - pub(crate) flex_factor: f64, -} - -/// The pass-facing view of one stored track sizing function: the breadth kind -/// is derived from the flex flag and the computed size, and calc-bearing -/// values borrow their pointer from the style group payload, which outlives -/// the pass. -fn grid_track_breadth_view(breadth: &ComputedGridTrackBreadth) -> GridTrackBreadth { +fn grid_track_breadth_view(breadth: &'static ComputedGridTrackBreadth) -> GridTrackBreadth { if breadth.is_flex { - return GridTrackBreadth { - kind: GridTrackBreadthKind::Flex as u8, - value: FfiSizeValue::auto_value(), - flex_factor: breadth.flex_factor, - }; + return GridTrackBreadth::Flex(breadth.flex_factor); } - let kind = match breadth.size.kind { - ComputedSizeKind::Auto => GridTrackBreadthKind::Auto, + match breadth.size.kind { + ComputedSizeKind::Auto => GridTrackBreadth::Auto, ComputedSizeKind::Calculated | ComputedSizeKind::Length | ComputedSizeKind::Percentage => { - GridTrackBreadthKind::LengthPercentage + GridTrackBreadth::LengthPercentage(&breadth.size) } - ComputedSizeKind::MinContent => GridTrackBreadthKind::MinContent, - ComputedSizeKind::MaxContent => GridTrackBreadthKind::MaxContent, - ComputedSizeKind::FitContent => GridTrackBreadthKind::FitContent, + ComputedSizeKind::MinContent => GridTrackBreadth::MinContent, + ComputedSizeKind::MaxContent => GridTrackBreadth::MaxContent, + ComputedSizeKind::FitContent => GridTrackBreadth::FitContent(&breadth.size), ComputedSizeKind::None => unreachable!("grid track sizes cannot be none"), - }; - GridTrackBreadth { - kind: kind as u8, - value: decode_computed_size(&breadth.size), - flex_factor: 0.0, } } @@ -1180,7 +1162,7 @@ impl<'pass> GridFormattingContext<'pass> { /// outlives the pass because the node's ComputedValues keep it alive and /// style containers are only replaced between passes. fn grid_style(&self, node: Node) -> &'static GridValues { - StyleReader::new(self.callbacks.style_payloads(node)).grid_values() + ComputedValuesView::new(&self.callbacks.style_payloads(node).groups).grid_values() } fn sizing(&self) -> SizingContext<'_> { @@ -1237,7 +1219,7 @@ impl<'pass> GridFormattingContext<'pass> { } } - fn axis_gap_value(&self, axis: Axis) -> FfiSizeValue { + fn axis_gap_value(&self, axis: Axis) -> &'pass ComputedGap { let style = self.style(self.grid_container); if axis.is_column() { style.column_gap() @@ -1289,7 +1271,7 @@ impl<'pass> GridFormattingContext<'pass> { return CssPixels::default(); } let gap = self.axis_gap_value(axis); - if gap.is_auto() { + if gap.is_normal() { // https://drafts.csswg.org/css-grid-2/#subgrid-gaps // A value of normal indicates that the subgrid has the same size gutters // as its parent grid, i.e. the applied difference is zero. @@ -1450,7 +1432,7 @@ impl<'pass> GridFormattingContext<'pass> { fn automatic_repeat_count( &self, - source: TrackListSource<'_>, + source: TrackListSource, entry: &crate::layout::ComputedGridTrackEntry, axis: Axis, ) -> usize { @@ -1509,7 +1491,7 @@ impl<'pass> GridFormattingContext<'pass> { 1 } - fn expand_axis(&self, axis: Axis, grid_style: &GridValues) -> ExpandedTrackList { + fn expand_axis(&self, axis: Axis, grid_style: &'static GridValues) -> ExpandedTrackList { let list = if axis.is_column() { grid_style.template_columns } else { @@ -1543,7 +1525,7 @@ impl<'pass> GridFormattingContext<'pass> { }) } - fn initialize_lines(&mut self, grid_style: &GridValues) -> (ExpandedTrackList, ExpandedTrackList) { + fn initialize_lines(&mut self, grid_style: &'static GridValues) -> (ExpandedTrackList, ExpandedTrackList) { let mut columns = self.expand_axis(Axis::Column, grid_style); let mut rows = self.expand_axis(Axis::Row, grid_style); self.project_parent_grid_areas( @@ -1754,7 +1736,7 @@ impl<'pass> GridFormattingContext<'pass> { } } - fn expanded_auto_tracks(&self, grid_style: &GridValues, axis: Axis) -> Vec { + fn expanded_auto_tracks(&self, grid_style: &'static GridValues, axis: Axis) -> Vec { let list = if axis.is_column() { grid_style.auto_columns } else { @@ -1766,7 +1748,7 @@ impl<'pass> GridFormattingContext<'pass> { fn initialize_tracks_for_axis( &self, axis: Axis, - grid_style: &GridValues, + grid_style: &'static GridValues, explicit: &ExpandedTrackList, total_count: usize, explicit_start: usize, @@ -1896,7 +1878,7 @@ impl<'pass> GridFormattingContext<'pass> { } } - fn initialize_tracks(&mut self, grid_style: &GridValues, columns: &ExpandedTrackList, rows: &ExpandedTrackList) { + fn initialize_tracks(&mut self, grid_style: &'static GridValues, columns: &ExpandedTrackList, rows: &ExpandedTrackList) { self.columns = self.initialize_tracks_for_axis( Axis::Column, grid_style, @@ -2068,7 +2050,7 @@ impl<'pass> GridFormattingContext<'pass> { size + self.outer_edges(item, axis) } - fn preferred_size(&self, item: GridItem, axis: Axis) -> FfiSizeValue { + fn preferred_size(&self, item: GridItem, axis: Axis) -> &'pass ComputedSize { let style = self.style(item.box_); if axis.is_column() { style.width() @@ -2077,7 +2059,7 @@ impl<'pass> GridFormattingContext<'pass> { } } - fn minimum_size(&self, item: GridItem, axis: Axis) -> FfiSizeValue { + fn minimum_size(&self, item: GridItem, axis: Axis) -> &'pass ComputedSize { let style = self.style(item.box_); if axis.is_column() { style.min_width() @@ -2086,7 +2068,7 @@ impl<'pass> GridFormattingContext<'pass> { } } - fn maximum_size(&self, item: GridItem, axis: Axis) -> FfiSizeValue { + fn maximum_size(&self, item: GridItem, axis: Axis) -> &'pass ComputedSize { let style = self.style(item.box_); if axis.is_column() { style.max_width() @@ -2112,7 +2094,7 @@ impl<'pass> GridFormattingContext<'pass> { // sizing tracks in the same axis, the percentage is cyclic and behaves as // the property's initial value for intrinsic contribution calculations. behaves_as_auto - || (!self.facts(item.box_).is_replaced_box() && self.preferred_size(item, axis).contains_percentage) + || (!self.facts(item.box_).is_replaced_box() && self.preferred_size(item, axis).contains_percentage()) } fn min_content_size(&self, item: GridItem, axis: Axis) -> CssPixels { @@ -2130,7 +2112,7 @@ impl<'pass> GridFormattingContext<'pass> { fn min_content_contribution(&self, item: GridItem, axis: Axis) -> CssPixels { let max = self.maximum_size(item, axis); - let maximum = if max.is_length_percentage() && !max.contains_percentage { + let maximum = if max.is_length_percentage() && !max.contains_percentage() { max.to_px(CssPixels::default()) } else { CssPixels::from_raw(i32::MAX) @@ -2168,7 +2150,7 @@ impl<'pass> GridFormattingContext<'pass> { fn max_content_contribution(&self, item: GridItem, axis: Axis) -> CssPixels { let max = self.maximum_size(item, axis); - let maximum = if max.is_length_percentage() && !max.contains_percentage { + let maximum = if max.is_length_percentage() && !max.contains_percentage() { max.to_px(CssPixels::default()) } else { CssPixels::from_raw(i32::MAX) @@ -2225,7 +2207,7 @@ impl<'pass> GridFormattingContext<'pass> { // If the item’s preferred size in the relevant axis is definite, then the specified size suggestion is that size. // It is otherwise undefined. let preferred_size = self.preferred_size(item, axis); - if !self.facts(item.box_).is_replaced_box() && preferred_size.contains_percentage { + if !self.facts(item.box_).is_replaced_box() && preferred_size.contains_percentage() { return None; } @@ -2303,7 +2285,7 @@ impl<'pass> GridFormattingContext<'pass> { // In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if it’s definite. let maximum_size = self.maximum_size(item, axis); - if maximum_size.is_length_percentage() && !maximum_size.contains_percentage { + if maximum_size.is_length_percentage() && !maximum_size.contains_percentage() { result = result.min(maximum_size.to_px(CssPixels::default())); } @@ -2312,10 +2294,7 @@ impl<'pass> GridFormattingContext<'pass> { // against zero (and considered definite). // FIXME: "compressible replaced element" includes more elements than is_replaced_box(). let preferred_size = self.preferred_size(item, axis); - if self.facts(item.box_).is_replaced_box() - && (preferred_size.kind() == crate::layout::FfiSizeKind::Percentage - || maximum_size.kind() == crate::layout::FfiSizeKind::Percentage) - { + if self.facts(item.box_).is_replaced_box() && (preferred_size.is_percentage() || maximum_size.is_percentage()) { // NOTE: Implements "for this purpose, any indefinite percentages in these sizes are resolved // against zero (and considered definite)." part. result = CssPixels::default(); @@ -2369,7 +2348,7 @@ impl<'pass> GridFormattingContext<'pass> { return self.max_content_contribution(item, axis); } else { let mut available = self.item_available_space(item); - if axis.is_column() && self.facts(item.box_).is_table_wrapper() && minimum.contains_percentage { + if axis.is_column() && self.facts(item.box_).is_table_wrapper() && minimum.contains_percentage() { // Percentage minimum sizes on a table wrapper resolve against the same non-cyclic // inline size that the wrapper's own inline-size resolution uses. let containing = self.containing_block_size(item, Axis::Column); @@ -2416,7 +2395,7 @@ impl<'pass> GridFormattingContext<'pass> { for (index, track) in tracks.iter().enumerate().take(end).skip(start) { let max = track.max_sizing; if max.is_fixed(available) - || matches!(max, TrackSizingFunction::FitContent(value) if !value.contains_percentage || matches!(available, AvailableSize::Definite(_))) + || matches!(max, TrackSizingFunction::FitContent(value) if !value.contains_percentage() || matches!(available, AvailableSize::Definite(_))) { result += max.resolve(available); } else { @@ -2759,12 +2738,12 @@ impl<'pass> GridFormattingContext<'pass> { let table_box = self.sizing().table_box_inside_wrapper(item.box_); let table_style = self.style(table_box); let wrapper_style = self.style(item.box_); - if !wrapper_style.width().contains_percentage - && !wrapper_style.min_width().contains_percentage - && !wrapper_style.max_width().contains_percentage - && !table_style.width().contains_percentage - && !table_style.min_width().contains_percentage - && !table_style.max_width().contains_percentage + if !wrapper_style.width().contains_percentage() + && !wrapper_style.min_width().contains_percentage() + && !wrapper_style.max_width().contains_percentage() + && !table_style.width().contains_percentage() + && !table_style.min_width().contains_percentage() + && !table_style.max_width().contains_percentage() { return containing; } @@ -4461,14 +4440,14 @@ pub(crate) struct ExpandedTrackList { } #[derive(Clone, Copy)] -pub(crate) struct TrackListSource<'a> { - pub(crate) names: &'a [usize], - pub(crate) entries: &'a [ComputedGridTrackEntry], - pub(crate) name_indices: &'a [u32], +pub(crate) struct TrackListSource { + pub(crate) names: &'static [usize], + pub(crate) entries: &'static [ComputedGridTrackEntry], + pub(crate) name_indices: &'static [u32], } -impl<'a> TrackListSource<'a> { - fn from_grid_style(grid_style: &'a GridValues) -> Self { +impl TrackListSource { + fn from_grid_style(grid_style: &'static GridValues) -> Self { Self { names: grid_style.names.raws(), entries: grid_style.entries.as_slice(), @@ -4476,12 +4455,12 @@ impl<'a> TrackListSource<'a> { } } - fn entry(&self, index: u32) -> &'a ComputedGridTrackEntry { + fn entry(&self, index: u32) -> &'static ComputedGridTrackEntry { assert_ne!(index, GRID_NO_INDEX); &self.entries[index as usize] } - fn names(&self, entry: &ComputedGridTrackEntry) -> impl Iterator + 'a { + fn names(&self, entry: &ComputedGridTrackEntry) -> impl Iterator + 'static { let end = entry .name_index_start .checked_add(entry.name_index_count) @@ -4493,7 +4472,11 @@ impl<'a> TrackListSource<'a> { .map(move |name_index| LineName::explicit(name_index, name_raws[name_index as usize])) } - fn for_each_entry(&self, list: ComputedGridTrackList, mut callback: impl FnMut(u32, &'a ComputedGridTrackEntry)) { + fn for_each_entry( + &self, + list: ComputedGridTrackList, + mut callback: impl FnMut(u32, &'static ComputedGridTrackEntry), + ) { let mut index = list.first_entry; let mut visited = 0usize; while index != GRID_NO_INDEX { @@ -4506,15 +4489,7 @@ impl<'a> TrackListSource<'a> { } } -fn auto_breadth() -> GridTrackBreadth { - GridTrackBreadth { - kind: GridTrackBreadthKind::Auto as u8, - value: FfiSizeValue::auto_value(), - flex_factor: 0.0, - } -} - -fn definition_for(entry: &ComputedGridTrackEntry, auto_fit: bool, auto_repeat: bool) -> TrackDefinition { +fn definition_for(entry: &'static ComputedGridTrackEntry, auto_fit: bool, auto_repeat: bool) -> TrackDefinition { match entry.kind { kind if kind == ComputedGridTrackEntryKind::TrackSize as u8 => { let size = grid_track_breadth_view(&entry.size); @@ -4522,12 +4497,8 @@ fn definition_for(entry: &ComputedGridTrackEntry, auto_fit: bool, auto_repeat: b // min track sizing function: // If the track was sized with a minmax() function, this is the first argument to that function. // If the track was sized with a value or fit-content() function, auto. Otherwise, the track’s sizing function. - let min = if matches!( - size.kind, - kind if kind == GridTrackBreadthKind::Flex as u8 - || kind == GridTrackBreadthKind::FitContent as u8 - ) { - auto_breadth() + let min = if matches!(size, GridTrackBreadth::Flex(_) | GridTrackBreadth::FitContent(_)) { + GridTrackBreadth::Auto } else { size }; @@ -4550,7 +4521,7 @@ fn definition_for(entry: &ComputedGridTrackEntry, auto_fit: bool, auto_repeat: b #[allow(clippy::too_many_arguments)] fn expand_standalone_list( - source: TrackListSource<'_>, + source: TrackListSource, list: ComputedGridTrackList, lines: &mut Vec>, tracks: &mut Vec, @@ -4596,7 +4567,7 @@ fn expand_standalone_list( /// by placement. `auto_repeat_count` performs the container-size-dependent /// auto-fill/auto-fit calculation. pub(crate) fn expand_standalone( - source: TrackListSource<'_>, + source: TrackListSource, list: ComputedGridTrackList, mut auto_repeat_count: impl FnMut(u32, &ComputedGridTrackEntry) -> usize, ) -> ExpandedTrackList { @@ -4628,7 +4599,7 @@ pub(crate) fn expand_standalone( result } -pub(crate) fn count_subgrid_line_name_lists(source: TrackListSource<'_>, list: ComputedGridTrackList) -> usize { +pub(crate) fn count_subgrid_line_name_lists(source: TrackListSource, list: ComputedGridTrackList) -> usize { let mut count = 0usize; source.for_each_entry(list, |_index, entry| match entry.kind { kind if kind == ComputedGridTrackEntryKind::LineNames as u8 => count += 1, @@ -4645,12 +4616,12 @@ pub(crate) fn count_subgrid_line_name_lists(source: TrackListSource<'_>, list: C count } -pub(crate) fn automatic_subgrid_span(source: TrackListSource<'_>, list: ComputedGridTrackList) -> usize { +pub(crate) fn automatic_subgrid_span(source: TrackListSource, list: ComputedGridTrackList) -> usize { count_subgrid_line_name_lists(source, list).saturating_sub(1).max(1) } fn expand_subgrid_names( - source: TrackListSource<'_>, + source: TrackListSource, list: ComputedGridTrackList, lines: &mut [Vec], line_index: &mut usize, @@ -4713,7 +4684,7 @@ fn expand_subgrid_names( } pub(crate) fn expand_subgrid( - source: TrackListSource<'_>, + source: TrackListSource, list: ComputedGridTrackList, track_count: usize, inherited_lines: &[Vec], @@ -4819,36 +4790,42 @@ pub(crate) fn nth_named_line(lines: &[Vec], name_raw: usize, nth_line: #[derive(Clone, Copy, Debug)] pub(crate) enum TrackSizingFunction { Auto, - Fixed(FfiSizeValue), + Fixed(&'static ComputedSize), + /// A synthesized fixed track with no backing style value: collapsed + /// tracks, gap tracks, and fixed subgrid tracks carry a resolved px size. + FixedPx(CssPixels), Flex(f64), MinContent, MaxContent, - FitContent(FfiSizeValue), + FitContent(&'static ComputedSize), } impl TrackSizingFunction { pub(crate) fn from_breadth(value: GridTrackBreadth) -> Self { - match value.kind { - kind if kind == GridTrackBreadthKind::Auto as u8 => Self::Auto, - kind if kind == GridTrackBreadthKind::LengthPercentage as u8 => Self::Fixed(value.value), - kind if kind == GridTrackBreadthKind::Flex as u8 => Self::Flex(value.flex_factor), - kind if kind == GridTrackBreadthKind::MinContent as u8 => Self::MinContent, - kind if kind == GridTrackBreadthKind::MaxContent as u8 => Self::MaxContent, - kind if kind == GridTrackBreadthKind::FitContent as u8 => Self::FitContent(value.value), - _ => unreachable!("invalid grid track breadth"), + match value { + GridTrackBreadth::Auto => Self::Auto, + GridTrackBreadth::LengthPercentage(size) => Self::Fixed(size), + GridTrackBreadth::Flex(factor) => Self::Flex(factor), + GridTrackBreadth::MinContent => Self::MinContent, + GridTrackBreadth::MaxContent => Self::MaxContent, + GridTrackBreadth::FitContent(size) => Self::FitContent(size), } } pub(crate) fn is_auto(self, available: AvailableSize) -> bool { match self { Self::Auto => true, - Self::Fixed(value) => value.contains_percentage && !matches!(available, AvailableSize::Definite(_)), + Self::Fixed(value) => value.contains_percentage() && !matches!(available, AvailableSize::Definite(_)), _ => false, } } pub(crate) fn is_fixed(self, available: AvailableSize) -> bool { - matches!(self, Self::Fixed(value) if !value.contains_percentage || matches!(available, AvailableSize::Definite(_))) + match self { + Self::Fixed(value) => !value.contains_percentage() || matches!(available, AvailableSize::Definite(_)), + Self::FixedPx(_) => true, + _ => false, + } } pub(crate) fn is_intrinsic(self, available: AvailableSize) -> bool { @@ -4877,6 +4854,7 @@ impl TrackSizingFunction { pub(crate) fn resolve(self, available: AvailableSize) -> CssPixels { match self { Self::Fixed(value) | Self::FitContent(value) => value.to_px(available.to_px_or_zero()), + Self::FixedPx(px) => px, _ => CssPixels::default(), } } @@ -4904,10 +4882,9 @@ pub(crate) struct Track { impl Track { pub(crate) fn fixed(base_size: CssPixels) -> Self { - let value = fixed_size_value(base_size); Self { - min_sizing: TrackSizingFunction::Fixed(value), - max_sizing: TrackSizingFunction::Fixed(value), + min_sizing: TrackSizingFunction::FixedPx(base_size), + max_sizing: TrackSizingFunction::FixedPx(base_size), base_size, growth_limit: Some(base_size), flex_factor: None, @@ -4978,27 +4955,13 @@ impl Track { } pub(crate) fn collapse(&mut self) { - let zero = fixed_size_value(CssPixels::default()); - self.min_sizing = TrackSizingFunction::Fixed(zero); - self.max_sizing = TrackSizingFunction::Fixed(zero); + self.min_sizing = TrackSizingFunction::FixedPx(CssPixels::default()); + self.max_sizing = TrackSizingFunction::FixedPx(CssPixels::default()); self.flex_factor = None; self.is_collapsed = true; } } -fn fixed_size_value(value: CssPixels) -> FfiSizeValue { - use crate::layout::FfiSizeKind; - FfiSizeValue { - kind: FfiSizeKind::Px as u8, - px: value, - fraction: 0.0, - calc: std::ptr::null(), - contains_percentage: false, - contains_anchor_function: false, - fit_content_has_argument: false, - } -} - pub(crate) fn initialize_track_sizes(tracks: &mut [Track], available: AvailableSize) -> bool { // https://www.w3.org/TR/css-grid-2/#algo-init // 12.4. Initialize Track Sizes @@ -5015,7 +4978,7 @@ pub(crate) fn initialize_track_sizes(tracks: &mut [Track], available: AvailableS } if !matches!(available, AvailableSize::Definite(_)) - && matches!(track.max_sizing, TrackSizingFunction::FitContent(value) if value.contains_percentage) + && matches!(track.max_sizing, TrackSizingFunction::FitContent(value) if value.contains_percentage()) { // Normalize fit-content tracks with unresolvable percentage arguments to max-content, // since the percentage cannot be resolved against an indefinite available size. diff --git a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs index 89175b27aed80..ff6ff6405b276 100644 --- a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs @@ -873,7 +873,7 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { } else { sizing.calculate_max_content_inline_size(node, constraints) } - } else if style.width().contains_percentage && !matches!(available_space.inline_size, AvailableSize::Definite(_)) { + } else if style.width().contains_percentage() && !matches!(available_space.inline_size, AvailableSize::Definite(_)) { CssPixels::default() } else { sizing.calculate_inner_inline_size(node, available_space.inline_size, style.width(), constraints) diff --git a/Libraries/LibWeb/Rust/src/layout/layout_state.rs b/Libraries/LibWeb/Rust/src/layout/layout_state.rs index f1b16d2d74392..9c2e3be56d67c 100644 --- a/Libraries/LibWeb/Rust/src/layout/layout_state.rs +++ b/Libraries/LibWeb/Rust/src/layout/layout_state.rs @@ -344,16 +344,16 @@ impl<'pass> NodeFacts<'pass> { self.state.style_facts(self.callbacks, self.node) } - fn style_reader_if_styled(&self) -> Option> { - self.callbacks.style_reader_if_styled(self.node) + fn computed_values_view_if_styled(&self) -> Option> { + self.callbacks.computed_values_view_if_styled(self.node) } - fn parent_style_reader_if_styled(&self) -> Option> { + fn parent_computed_values_view_if_styled(&self) -> Option> { let parent = self.data().parent; if parent.is_invalid() { return None; } - self.callbacks.style_reader_if_styled(parent) + self.callbacks.computed_values_view_if_styled(parent) } fn replaced_content(&self) -> crate::layout::FfiReplacedContentFacts { @@ -390,18 +390,18 @@ impl<'pass> NodeFacts<'pass> { } pub(crate) fn is_floating(&self) -> bool { - self.style_reader_if_styled().is_some_and(|style| style.is_floating()) + self.computed_values_view_if_styled().is_some_and(|style| style.is_floating()) } pub(crate) fn is_absolutely_positioned(&self) -> bool { - self.style_reader_if_styled() + self.computed_values_view_if_styled() .is_some_and(|style| style.is_absolutely_positioned()) } pub(crate) fn is_inline(&self) -> bool { crate::layout::kind_is_text(self.data().kind) || self - .style_reader_if_styled() + .computed_values_view_if_styled() .is_some_and(|style| style.display().is_inline_outside()) } @@ -409,7 +409,7 @@ impl<'pass> NodeFacts<'pass> { let data = self.data(); crate::layout::has_flag(data, NodeFlag::IsReplacedElement) || data.kind == NodeKind::ListItemMarkerBox - || self.style_reader_if_styled().is_some_and(|style| { + || self.computed_values_view_if_styled().is_some_and(|style| { let display = style.display(); display.is_inline_outside() && !display.is_flow_inside() }) @@ -431,7 +431,7 @@ impl<'pass> NodeFacts<'pass> { let data = self.data(); data.kind == NodeKind::InlineNode || (data.kind == NodeKind::ListItemBox - && self.style_reader_if_styled().is_some_and(|style| { + && self.computed_values_view_if_styled().is_some_and(|style| { let display = style.display(); display.is_inline_outside() && display.is_flow_inside() })) @@ -445,14 +445,14 @@ impl<'pass> NodeFacts<'pass> { let Some(parent) = self.parent_data() else { return false; }; - let parent_is_inline_flow = self.parent_style_reader_if_styled().is_some_and(|style| { + let parent_is_inline_flow = self.parent_computed_values_view_if_styled().is_some_and(|style| { let display = style.display(); display.is_inline_outside() && display.is_flow_inside() }); if !parent_is_inline_flow { return false; } - let style = self.style_reader_if_styled(); + let style = self.computed_values_view_if_styled(); if style.is_some_and(|style| style.display().is_inline_outside()) || crate::layout::node_is_out_of_flow(data, style) { @@ -499,7 +499,7 @@ impl<'pass> NodeFacts<'pass> { } pub(crate) fn display_before_box_type_transformation_is_block_outside(&self) -> bool { - self.style_reader_if_styled() + self.computed_values_view_if_styled() .is_some_and(|style| style.display_before_box_type_transformation().is_block_outside()) } @@ -538,7 +538,7 @@ impl<'pass> NodeFacts<'pass> { pub(crate) fn has_replaced_element_table_display_adjustment(&self) -> bool { crate::layout::has_flag(self.data(), NodeFlag::IsReplacedElement) && self - .style_reader_if_styled() + .computed_values_view_if_styled() .is_some_and(|style| { let display = style.display_before_box_type_transformation(); display.is_table_inside() || display.is_internal_table() || display.is_table_caption() @@ -548,8 +548,8 @@ impl<'pass> NodeFacts<'pass> { pub(crate) fn creates_block_formatting_context(&self) -> bool { crate::layout::node_creates_block_formatting_context( self.data(), - self.style_reader_if_styled(), - self.parent_style_reader_if_styled(), + self.computed_values_view_if_styled(), + self.parent_computed_values_view_if_styled(), ) } @@ -589,7 +589,7 @@ impl<'pass> NodeFacts<'pass> { while !child.is_invalid() { let child_data = self.callbacks.node_data(child); if child_data.kind == NodeKind::LegendBox - && !crate::layout::node_is_out_of_flow(child_data, self.callbacks.style_reader_if_styled(child)) + && !crate::layout::node_is_out_of_flow(child_data, self.callbacks.computed_values_view_if_styled(child)) { return child; } @@ -931,7 +931,7 @@ impl LayoutState { Axis::Block => percentage_basis_block_size.is_some(), }; - let adjust_for_box_sizing = |unadjusted: crate::layout::CssPixels, computed_size: crate::layout::FfiSizeValue, axis: Axis| { + let adjust_for_box_sizing = |unadjusted: crate::layout::CssPixels, computed_size: &ComputedSize, axis: Axis| { // box-sizing: content-box and automatic sizes need no // adjustment. if style.box_sizing() == box_sizing::CONTENT_BOX || computed_size.is_auto() { @@ -961,7 +961,7 @@ impl LayoutState { let parent = callbacks.parent(node); let parent_facts = (!parent.is_invalid()).then(|| self.node_facts(callbacks, parent)); - let is_definite_size = |size: crate::layout::FfiSizeValue, axis: Axis| -> Option { + let is_definite_size = |size: &ComputedSize, axis: Axis| -> Option { // A definite size can be determined without performing // layout: a length, an initial-containing-block size, or a // percentage/formula resolved solely against definite sizes. @@ -998,10 +998,10 @@ impl LayoutState { if !size.is_length_percentage() { return None; } - if size.contains_percentage && !containing_block_has_definite_size(axis) { + if size.contains_percentage() && !containing_block_has_definite_size(axis) { return None; } - let basis = if size.contains_percentage { + let basis = if size.contains_percentage() { containing_block_size_for_axis(axis) } else { crate::layout::CssPixels::default() @@ -1140,7 +1140,7 @@ impl LayoutState { node: Node, ) -> StyleValues<'pass> { StyleValues::new( - StyleReader::new(callbacks.style_payloads(node)), + callbacks.style_payloads(node), &self.anchor_inset_store, callbacks.slot_index(node), ) @@ -1153,25 +1153,21 @@ impl LayoutState { resolved: crate::layout::FfiResolvedAnchorInsets, ) { let slot_index = callbacks.slot_index(node); - let replace = |field: SizeField, is_auto: bool, value: crate::layout::CssPixels| { - let value = if is_auto { - crate::layout::FfiSizeValue::auto_value() - } else { - crate::layout::FfiSizeValue::px_value(value) - }; - self.anchor_inset_store.set_override(slot_index, field, value); + let replace = |field: InsetField, is_auto: bool, px: crate::layout::CssPixels| { + self.anchor_inset_store + .set_override(slot_index, field, ResolvedInsetOverride { is_auto, px }); }; if resolved.resolves_top { - replace(SizeField::InsetTop, resolved.top_is_auto, resolved.top); + replace(InsetField::Top, resolved.top_is_auto, resolved.top); } if resolved.resolves_right { - replace(SizeField::InsetRight, resolved.right_is_auto, resolved.right); + replace(InsetField::Right, resolved.right_is_auto, resolved.right); } if resolved.resolves_bottom { - replace(SizeField::InsetBottom, resolved.bottom_is_auto, resolved.bottom); + replace(InsetField::Bottom, resolved.bottom_is_auto, resolved.bottom); } if resolved.resolves_left { - replace(SizeField::InsetLeft, resolved.left_is_auto, resolved.left); + replace(InsetField::Left, resolved.left_is_auto, resolved.left); } } diff --git a/Libraries/LibWeb/Rust/src/layout/mod.rs b/Libraries/LibWeb/Rust/src/layout/mod.rs index d23f362dc8696..33b6d05bc78eb 100644 --- a/Libraries/LibWeb/Rust/src/layout/mod.rs +++ b/Libraries/LibWeb/Rust/src/layout/mod.rs @@ -9,6 +9,7 @@ // make the css-module types they use resolve without per-file imports. pub(crate) use crate::abort_on_panic; pub(crate) use crate::css::computed_value_types::*; +pub(crate) use crate::css::computed_value_views::*; pub(crate) use crate::css::css_enums::*; pub(crate) use crate::css::css_pixels::*; pub(crate) use crate::css::display::*; diff --git a/Libraries/LibWeb/Rust/src/layout/node_facts.rs b/Libraries/LibWeb/Rust/src/layout/node_facts.rs index 99d03f4307371..7f82ca2e51bfa 100644 --- a/Libraries/LibWeb/Rust/src/layout/node_facts.rs +++ b/Libraries/LibWeb/Rust/src/layout/node_facts.rs @@ -65,7 +65,7 @@ pub(crate) fn node_may_have_list_item_facts(data: &NodeData) -> bool { matches!(data.kind, NodeKind::ListItemBox | NodeKind::ListItemMarkerBox) } -pub(crate) fn node_is_out_of_flow(data: &NodeData, style: Option>) -> bool { +pub(crate) fn node_is_out_of_flow(data: &NodeData, style: Option>) -> bool { let Some(style) = style else { return false; }; @@ -91,14 +91,14 @@ pub(crate) fn node_has_auto_content_box_size(data: &NodeData) -> bool { } // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context -// StyleReader::own_style_establishes_block_formatting_context covers the +// ComputedValuesView::own_style_establishes_block_formatting_context covers // computed-style-only terms; this composite adds the terms that need the node // kind, stamped DOM identity, the live IsFlexItem flag, or the parent's // display. pub(crate) fn node_creates_block_formatting_context( data: &NodeData, - style: Option>, - parent_style: Option>, + style: Option>, + parent_style: Option>, ) -> bool { if kind_is_replaced_box(data.kind) { return false; diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index c9b9e7d5354ac..912bec1d90c1d 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -186,7 +186,7 @@ impl<'pass> SizingContext<'pass> { } else { self.content_block_size_from_aspect_ratio(node, inline_size) }) - } else if style.min_width().is_length_percentage() && !style.min_width().contains_percentage { + } else if style.min_width().is_length_percentage() && !style.min_width().contains_percentage() { let inline_size = style.min_width().to_px(CssPixels::default()); Some(if is_inline_axis { inline_size @@ -202,7 +202,7 @@ impl<'pass> SizingContext<'pass> { } else { block_size }) - } else if style.min_height().is_length_percentage() && !style.min_height().contains_percentage { + } else if style.min_height().is_length_percentage() && !style.min_height().contains_percentage() { let block_size = style.min_height().to_px(CssPixels::default()); Some(if is_inline_axis { self.content_inline_size_from_aspect_ratio(node, block_size) @@ -239,7 +239,7 @@ impl<'pass> SizingContext<'pass> { } else { self.style(node).min_height() }; - if min_size.is_length_percentage() && !min_size.contains_percentage { + if min_size.is_length_percentage() && !min_size.contains_percentage() { return Some(min_size.to_px(CssPixels::default())); } // Otherwise, use 300px for the width and/or 150px for the height as needed. @@ -249,7 +249,7 @@ impl<'pass> SizingContext<'pass> { fn tentative_inline_size_for_replaced_element( &self, node: Node, - computed_inline_size: FfiSizeValue, + computed_inline_size: &ComputedSize, available_space: AvailableSpace, constraints: ContainingBlockConstraints, ) -> CssPixels { @@ -260,7 +260,7 @@ impl<'pass> SizingContext<'pass> { } let style = self.style(node); let computed_block_size = if self.should_treat_block_size_as_auto(node, available_space, constraints) { - FfiSizeValue::auto_value() + auto_computed_size() } else { style.height() }; @@ -310,7 +310,7 @@ impl<'pass> SizingContext<'pass> { } match cyclic_percentage_intrinsic_contribution( self.facts(node).is_replaced_box(), - style.width().contains_percentage, + style.width().contains_percentage(), available_space.inline_size, CyclicPercentageSizeProperty::PreferredOrMaxSize, ) { @@ -339,7 +339,7 @@ impl<'pass> SizingContext<'pass> { fn tentative_block_size_for_replaced_element( &self, node: Node, - computed_block_size: FfiSizeValue, + computed_block_size: &ComputedSize, available_space: AvailableSpace, constraints: ContainingBlockConstraints, ) -> CssPixels { @@ -489,12 +489,12 @@ impl<'pass> SizingContext<'pass> { // 10.3.2 Inline, replaced elements let style = self.style(node); let computed_inline = if self.should_treat_inline_size_as_auto(node, available_space) { - FfiSizeValue::auto_value() + auto_computed_size() } else { style.width() }; let computed_block = if self.should_treat_block_size_as_auto(node, available_space, constraints) { - FfiSizeValue::auto_value() + auto_computed_size() } else { style.height() }; @@ -551,12 +551,12 @@ impl<'pass> SizingContext<'pass> { // 10.6.10 'inline-block' replaced elements in normal flow let style = self.style(node); let computed_inline = if self.should_treat_inline_size_as_auto(node, available_space) { - FfiSizeValue::auto_value() + auto_computed_size() } else { style.width() }; let computed_block = if self.should_treat_block_size_as_auto(node, available_space, constraints) { - FfiSizeValue::auto_value() + auto_computed_size() } else { style.height() }; @@ -727,7 +727,7 @@ impl<'pass> SizingContext<'pass> { return true; } // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution - if size.contains_percentage { + if size.contains_percentage() { match cyclic_percentage_intrinsic_contribution( self.facts(node).is_replaced_box(), true, @@ -773,7 +773,7 @@ impl<'pass> SizingContext<'pass> { return true; } // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution - if size.contains_percentage { + if size.contains_percentage() { match cyclic_percentage_intrinsic_contribution( facts.is_replaced_box(), true, @@ -837,7 +837,7 @@ impl<'pass> SizingContext<'pass> { return true; } // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution - if size.contains_percentage { + if size.contains_percentage() { match cyclic_percentage_intrinsic_contribution( self.facts(node).is_replaced_box(), true, @@ -871,7 +871,7 @@ impl<'pass> SizingContext<'pass> { if size.is_none() { return true; } - if size.contains_percentage { + if size.contains_percentage() { if available == AvailableSize::MinContent { return false; } @@ -1080,7 +1080,7 @@ impl<'pass> SizingContext<'pass> { ) -> CssPixels { let facts = self.facts(node); let style = self.style(node); - if facts.is_replaced_box() && (style.width().contains_percentage || style.max_width().contains_percentage) { + if facts.is_replaced_box() && (style.width().contains_percentage() || style.max_width().contains_percentage()) { // https://www.w3.org/TR/css-sizing-3/#replaced-percentage-min-contribution // NOTE: If the box is replaced, a cyclic percentage in the value of any max size property or // preferred size property (width/max-width/height/max-height), is resolved against zero @@ -1211,13 +1211,13 @@ impl<'pass> SizingContext<'pass> { block_size: AvailableSize::Indefinite, }; let resolve_destination_inline_size = - |size: FfiSizeValue, property: CyclicPercentageSizeProperty| -> Option { + |size: &ComputedSize, property: CyclicPercentageSizeProperty| -> Option { if !size.is_length_percentage() { return None; } match cyclic_percentage_intrinsic_contribution( facts.is_replaced_box(), - size.contains_percentage, + size.contains_percentage(), max_content_available, property, ) { @@ -1228,7 +1228,7 @@ impl<'pass> SizingContext<'pass> { Some(self.calculate_inner_inline_size(node, max_content_available, size, zero_constraints)) } CyclicPercentageIntrinsicContribution::NotCyclic => { - if size.contains_percentage && constraints.percentage_basis_inline_size.is_none() { + if size.contains_percentage() && constraints.percentage_basis_inline_size.is_none() { None } else { Some(self.calculate_inner_inline_size(node, max_content_available, size, constraints)) @@ -1236,16 +1236,16 @@ impl<'pass> SizingContext<'pass> { } } }; - let resolve_block_size = |size: FfiSizeValue, property: CyclicPercentageSizeProperty| -> Option { + let resolve_block_size = |size: &ComputedSize, property: CyclicPercentageSizeProperty| -> Option { if !size.is_length_percentage() { return None; } - if !size.contains_percentage || constraints.percentage_basis_block_size.is_some() { + if !size.contains_percentage() || constraints.percentage_basis_block_size.is_some() { return Some(self.calculate_inner_block_size(node, intrinsic_available_space, size, constraints)); } match cyclic_percentage_intrinsic_contribution( facts.is_replaced_box(), - size.contains_percentage, + size.contains_percentage(), max_content_available, property, ) { @@ -1765,11 +1765,11 @@ impl<'pass> SizingContext<'pass> { &self, node: Node, available: AvailableSize, - preferred_size: FfiSizeValue, + preferred_size: &ComputedSize, constraints: ContainingBlockConstraints, ) -> CssPixels { assert!(!preferred_size.is_auto()); - let basis = if preferred_size.contains_percentage { + let basis = if preferred_size.contains_percentage() { if let Some(basis) = constraints.percentage_basis_inline_size { basis } else { @@ -1814,7 +1814,7 @@ impl<'pass> SizingContext<'pass> { &self, node: Node, available_space: AvailableSpace, - preferred_size: FfiSizeValue, + preferred_size: &ComputedSize, constraints: ContainingBlockConstraints, ) -> CssPixels { if preferred_size.is_auto() && self.facts(node).has_preferred_aspect_ratio() { @@ -1847,7 +1847,7 @@ impl<'pass> SizingContext<'pass> { // NOTE: We only do this when available space height is indefinite. If it's definite, // we trust that the caller has set it up correctly (e.g., grid/flex items get // their cell/area size as available space). - if preferred_size.contains_percentage && available_space.block_size == AvailableSize::Indefinite { + if preferred_size.contains_percentage() && available_space.block_size == AvailableSize::Indefinite { // https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk // NOTE: Flex/grid items resolve percentage heights against their container, not via quirk. let facts = self.facts(node); diff --git a/Libraries/LibWeb/Rust/src/layout/style_facts.rs b/Libraries/LibWeb/Rust/src/layout/style_facts.rs index fc078ac069b4c..adf6b391326e6 100644 --- a/Libraries/LibWeb/Rust/src/layout/style_facts.rs +++ b/Libraries/LibWeb/Rust/src/layout/style_facts.rs @@ -4,412 +4,34 @@ * SPDX-License-Identifier: BSD-2-Clause */ -// Registered indices of the style groups the layout engine reads, pinned to -// the C++ StyleGroupIndex enum by static_asserts in LayoutRustBridge.cpp. -pub const STYLE_GROUP_INDEX_INHERITED_TABLE: usize = 0; -pub const STYLE_GROUP_INDEX_GRID: usize = 9; -pub const STYLE_GROUP_INDEX_INHERITED_TEXT: usize = 4; -pub const STYLE_GROUP_INDEX_INHERITED_BOX: usize = 5; -pub const STYLE_GROUP_INDEX_FONT: usize = 6; -pub const STYLE_GROUP_INDEX_SVG_RESET: usize = 8; -pub const STYLE_GROUP_INDEX_BORDER: usize = 17; -pub const STYLE_GROUP_INDEX_ALIGNMENT: usize = 18; -pub const STYLE_GROUP_INDEX_SIZING: usize = 20; -pub const STYLE_GROUP_INDEX_SURROUND: usize = 21; -pub const STYLE_GROUP_INDEX_BOX: usize = 22; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(u8)] -pub enum FfiSizeKind { - Auto, - Px, - Percentage, - Calc, - MinContent, - MaxContent, - FitContent, - None_, -} - -/// A computed CSS size value with no Rust-owned allocation. -/// -/// `kind` is an `FfiSizeKind`. `fraction` is used for Percentage, `px` for Px, -/// and `calc` for Calc. FitContent uses the matching payload for its optional -/// inner length-percentage, and `fit_content_has_argument` distinguishes the -/// keyword-only form from an argument that resolves to zero. -/// -/// Values decoded in Rust from the node's style group payloads borrow their -/// calc pointer from those payloads, which outlive the synchronous layout -/// pass; anchor() inset values borrow theirs from the wrappers the -/// LayoutState's anchor-inset store owns. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct FfiSizeValue { - pub kind: u8, - pub px: CssPixels, - pub fraction: f64, - pub calc: *const c_void, - pub contains_percentage: bool, - pub contains_anchor_function: bool, - pub fit_content_has_argument: bool, -} - pub type FfiReleaseAnchorNameHandleCallback = unsafe extern "C" fn(usize); -impl FfiSizeValue { - pub(crate) fn auto_value() -> Self { - Self { - kind: FfiSizeKind::Auto as u8, - px: CssPixels::default(), - fraction: 0.0, - calc: std::ptr::null(), - contains_percentage: false, - contains_anchor_function: false, - fit_content_has_argument: false, - } - } - - pub(crate) fn px_value(px: CssPixels) -> Self { - Self { - kind: FfiSizeKind::Px as u8, - px, - fraction: 0.0, - calc: std::ptr::null(), - contains_percentage: false, - contains_anchor_function: false, - fit_content_has_argument: false, - } - } - - #[cfg(test)] - fn with_kind(kind: FfiSizeKind) -> Self { - Self { - kind: kind as u8, - px: CssPixels::default(), - fraction: 0.0, - calc: std::ptr::null(), - contains_percentage: false, - contains_anchor_function: false, - fit_content_has_argument: false, - } - } - - #[cfg(test)] - pub(crate) fn px(px: CssPixels) -> Self { - Self::px_value(px) - } - - #[cfg(test)] - pub(crate) fn percentage(fraction: f64) -> Self { - Self { - fraction, - ..Self::with_kind(FfiSizeKind::Percentage) - } - } - - pub(crate) fn kind(self) -> FfiSizeKind { - assert!(self.kind <= FfiSizeKind::None_ as u8); - // SAFETY: The range check above covers every repr(u8) variant. - unsafe { std::mem::transmute(self.kind) } - } - - pub(crate) fn is_auto(self) -> bool { - self.kind() == FfiSizeKind::Auto - } - - pub(crate) fn is_length(self) -> bool { - self.kind() == FfiSizeKind::Px - } - - pub(crate) fn is_percentage(self) -> bool { - self.kind() == FfiSizeKind::Percentage - } - - pub(crate) fn is_length_percentage(self) -> bool { - matches!( - self.kind(), - FfiSizeKind::Px | FfiSizeKind::Percentage | FfiSizeKind::Calc - ) - } - - pub(crate) fn is_min_content(self) -> bool { - self.kind() == FfiSizeKind::MinContent - } - - pub(crate) fn is_max_content(self) -> bool { - self.kind() == FfiSizeKind::MaxContent - } - - pub(crate) fn is_fit_content(self) -> bool { - self.kind() == FfiSizeKind::FitContent - } - - pub(crate) fn is_none(self) -> bool { - self.kind() == FfiSizeKind::None_ - } - - pub(crate) fn is_intrinsic_sizing_constraint(self) -> bool { - matches!( - self.kind(), - FfiSizeKind::MinContent | FfiSizeKind::MaxContent | FfiSizeKind::FitContent - ) - } - - pub(crate) fn to_px(self, reference: CssPixels) -> CssPixels { - match self.kind() { - FfiSizeKind::Px => self.px, - FfiSizeKind::Percentage => truncated_css_pixels(reference.to_double() * self.fraction), - FfiSizeKind::Calc => resolve_calc(self.calc, reference), - FfiSizeKind::FitContent if !self.calc.is_null() => resolve_calc(self.calc, reference), - FfiSizeKind::FitContent if self.contains_percentage => { - truncated_css_pixels(reference.to_double() * self.fraction) - } - FfiSizeKind::FitContent => self.px, - FfiSizeKind::Auto | FfiSizeKind::MinContent | FfiSizeKind::MaxContent | FfiSizeKind::None_ => { - CssPixels::default() - } - } - } -} - -fn truncated_css_pixels(value: f64) -> CssPixels { - if value.is_nan() { - return CssPixels::default(); - } - let raw = (value * 64.0).trunc(); - CssPixels::from_raw(raw.clamp(i32::MIN as f64, i32::MAX as f64) as i32) -} - -fn resolve_calc(calc: *const c_void, percentage_basis: CssPixels) -> CssPixels { - assert!(!calc.is_null()); - let context = px_calc_resolution_context(percentage_basis); - // SAFETY: The style value stays alive for the pass and the context - // carries no host callbacks. - let result = unsafe { crate::css::calc::rust_calc_resolve(calc, &raw const context, true) }; - assert!(result.resolved); - CssPixels::nearest_value_for(result.value) -} - -/// Every sizing-shaped value the layout engine reads, each decoding straight -/// from the node's typed group payloads. +/// The four inset properties; the discriminant indexes the anchor-inset +/// store fields. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(u8)] -pub(crate) enum SizeField { - Width, - Height, - MinWidth, - MinHeight, - MaxWidth, - MaxHeight, - MarginTop, - MarginRight, - MarginBottom, - MarginLeft, - PaddingTop, - PaddingRight, - PaddingBottom, - PaddingLeft, - InsetTop, - InsetRight, - InsetBottom, - InsetLeft, - FlexBasis, - RowGap, - ColumnGap, - ColumnWidth, - TextIndent, - X, - Y, - VerticalAlign, -} - -// https://drafts.csswg.org/css-contain-2/#containment-types -fn containment_applies_to_principal_box(display: crate::css::display::FfiDisplay) -> bool { - if display.is_internal_table() && !display.is_table_cell() { - return false; - } - if display.is_inline_outside() && display.is_flow_inside() { - return false; - } - true +pub(crate) enum InsetField { + Top = 0, + Right = 1, + Bottom = 2, + Left = 3, } +/// A resolved px-or-auto inset written back by anchor resolution. #[derive(Clone, Copy)] -pub(crate) struct StyleReader<'a> { - payloads: &'a FfiStylePayloads, -} - -impl<'a> StyleReader<'a> { - pub(crate) fn new(payloads: &'a FfiStylePayloads) -> Self { - Self { payloads } - } - - #[inline] - fn native_group(&self, group_index: usize) -> &'a T { - let payload = self.payloads.groups[group_index]; - debug_assert!(!payload.is_null()); - // SAFETY: The payload is the Rust-defined group struct itself; C++ - // derives its mirror from the cbindgen twin of the same type, and the - // node's ComputedValues keep it alive for the synchronous pass. - unsafe { &*payload.cast::() } - } - - #[inline] - fn sizing(&self) -> &'a crate::layout::SizingValues { - self.native_group(STYLE_GROUP_INDEX_SIZING) - } - - #[inline] - fn surround(&self) -> &'a crate::layout::SurroundValues { - self.native_group(STYLE_GROUP_INDEX_SURROUND) - } - - #[inline] - fn alignment(&self) -> &'a crate::layout::AlignmentValues { - self.native_group(STYLE_GROUP_INDEX_ALIGNMENT) - } - - #[inline] - fn svg_reset(&self) -> &'a crate::layout::SVGResetValues { - self.native_group(STYLE_GROUP_INDEX_SVG_RESET) - } - - #[inline] - fn inherited_box(&self) -> &'a crate::css::computed_values::InheritedBoxValues { - self.native_group(STYLE_GROUP_INDEX_INHERITED_BOX) - } - - #[inline] - fn inherited_table(&self) -> &'a crate::css::computed_values::InheritedTableValues { - self.native_group(STYLE_GROUP_INDEX_INHERITED_TABLE) - } - - #[inline] - fn box_values(&self) -> &'a crate::layout::BoxValues { - self.native_group(STYLE_GROUP_INDEX_BOX) - } - - #[inline] - pub(crate) fn grid_values(&self) -> &'a crate::layout::GridValues { - self.native_group(STYLE_GROUP_INDEX_GRID) - } - - #[inline] - fn border_facts(&self) -> &'a crate::layout::BorderLayoutFacts { - self.native_group(STYLE_GROUP_INDEX_BORDER) - } - - #[inline] - fn inherited_text_facts(&self) -> &'a crate::layout::InheritedTextLayoutFacts { - self.native_group(STYLE_GROUP_INDEX_INHERITED_TEXT) - } - - #[inline] - fn font_facts(&self) -> &'a crate::layout::FontLayoutFacts { - self.native_group(STYLE_GROUP_INDEX_FONT) - } - - pub(crate) fn display(&self) -> crate::css::display::FfiDisplay { - self.box_values().display - } - - pub(crate) fn display_before_box_type_transformation(&self) -> crate::css::display::FfiDisplay { - self.box_values().display_before_box_type_transformation - } - - pub(crate) fn is_floating(&self) -> bool { - self.box_values().float_ != crate::css::css_enums::float::NONE - } - - pub(crate) fn is_absolutely_positioned(&self) -> bool { - matches!( - self.box_values().position, - crate::css::css_enums::positioning::ABSOLUTE | crate::css::css_enums::positioning::FIXED - ) - } - - // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context - // The computed-style-only half of the block-formatting-context predicate; - // node_creates_block_formatting_context adds the terms that need the node - // kind, stamped DOM identity, the live IsFlexItem flag, or the parent's - // display. The float term is deliberately absent for the same reason: only - // non-flex-items establish one by floating. - pub(crate) fn own_style_establishes_block_formatting_context(&self) -> bool { - let box_values = self.box_values(); - let display = box_values.display; - - if self.is_absolutely_positioned() { - return true; - } - - if display.is_inline_block() { - return true; - } - - if display.is_table_cell() || display.is_table_caption() { - return true; - } - - let overflow_establishes_context = |overflow: u8| { - overflow != crate::css::css_enums::overflow::VISIBLE && overflow != crate::css::css_enums::overflow::CLIP - }; - if overflow_establishes_context(box_values.overflow_x) || overflow_establishes_context(box_values.overflow_y) { - return true; - } - - if display.is_flow_root_inside() { - return true; - } - - // https://drafts.csswg.org/css-contain-2/#containment-types - // 1. The layout containment box establishes an independent formatting context. - // 4. The paint containment box establishes an independent formatting context. - let content_visibility_forces_containment = self.inherited_box().content_visibility - == crate::css::css_enums::content_visibility::AUTO; - if (box_values.layout_containment || box_values.paint_containment || content_visibility_forces_containment) - && containment_applies_to_principal_box(display) - { - return true; - } - - // https://drafts.csswg.org/css-conditional-5/#valdef-container-type-size - // Applies style containment and size containment to the principal box, and establishes an independent - // formatting context. - if box_values.is_size_container || box_values.is_inline_size_container { - return true; - } - - // https://drafts.csswg.org/css-multicol-2/#the-multi-column-model - // An element whose 'column-width', 'column-count', or 'column-height' property is not 'auto' establishes a - // multi-column container (or multicol container for short), and therefore acts as a container for - // multi-column layout. - if box_values.column_width.kind != crate::layout::ComputedSizeKind::Auto || box_values.column_count_has_value { - return true; - } - - false - } -} - -fn anchor_inset_field_index(field: SizeField) -> usize { - match field { - SizeField::InsetTop => 0, - SizeField::InsetRight => 1, - SizeField::InsetBottom => 2, - SizeField::InsetLeft => 3, - _ => unreachable!(), - } +pub(crate) struct ResolvedInsetOverride { + pub(crate) is_auto: bool, + pub(crate) px: CssPixels, } #[derive(Default)] struct AnchorInsetField { - /// Resolved px/auto value written by replace_resolved_anchor_insets. It - /// takes precedence over every style decode and reports - /// contains_anchor_function == false; the abspos engine's early-out on - /// re-entry depends on both properties. Never carries a calc pointer. - resolved_override: Cell>, + /// Resolved value written by replace_resolved_anchor_insets. It takes + /// precedence over every style read and reports + /// contains_anchor_function() == false; the abspos engine's early-out on + /// re-entry depends on both properties. + resolved_override: Cell>, /// Memoized in-crate calculated wrapper for a bare anchor() inset: the - /// single owner of the Arc whose pointer the returned size values borrow. + /// single owner of the Arc that the returned inset values borrow. /// Written at most once and never replaced or dropped before the owning /// LayoutState drops. wrapper: std::cell::OnceCell>, @@ -438,556 +60,191 @@ impl AnchorInsetStore { .unwrap_or_else(|| self.slots.allocate(slot_index, AnchorInsetSlot::default())) } - fn override_for(&self, slot_index: u32, field: SizeField) -> Option { + fn override_for(&self, slot_index: u32, field: InsetField) -> Option { if !self.any_overrides.get() { return None; } - self.slots.get(slot_index)?.fields[anchor_inset_field_index(field)] - .resolved_override - .get() + self.slots.get(slot_index)?.fields[field as usize].resolved_override.get() } - fn memoized_anchor_inset_value( + fn memoized_bare_anchor_wrapper( &self, slot_index: u32, - field: SizeField, + field: InsetField, build_wrapper: impl FnOnce() -> std::sync::Arc, - ) -> FfiSizeValue { - let field = &self.slot(slot_index).fields[anchor_inset_field_index(field)]; - let calc = std::sync::Arc::as_ptr(field.wrapper.get_or_init(build_wrapper)).cast(); - FfiSizeValue { - kind: FfiSizeKind::Calc as u8, - px: CssPixels::default(), - fraction: 0.0, - calc, - contains_percentage: false, - contains_anchor_function: true, - fit_content_has_argument: false, - } + ) -> &crate::css::style_value::StyleValueData { + self.slot(slot_index).fields[field as usize] + .wrapper + .get_or_init(build_wrapper) } - pub(crate) fn set_override(&self, slot_index: u32, field: SizeField, value: FfiSizeValue) { - debug_assert!(value.calc.is_null()); - self.slot(slot_index).fields[anchor_inset_field_index(field)] - .resolved_override - .set(Some(value)); + pub(crate) fn set_override(&self, slot_index: u32, field: InsetField, value: ResolvedInsetOverride) { + self.slot(slot_index).fields[field as usize].resolved_override.set(Some(value)); self.any_overrides.set(true); } } -fn decode_length_percentage(handle: &crate::layout::ComputedStyleValueHandle) -> FfiSizeValue { - use crate::css::style_value::StyleValueData; +#[derive(Clone, Copy)] +pub(crate) enum InsetValue<'a> { + FromStyle(&'a ComputedLengthPercentageOrAuto), + BareAnchor(&'a crate::css::style_value::StyleValueData), + Resolved(ResolvedInsetOverride), +} - let pointer = handle.pointer; - assert!(!pointer.is_null()); - // SAFETY: The handle points at the retained style-value data held by the - // node's style group payload, which outlives the synchronous layout pass. - let data = unsafe { &*pointer.cast::() }; - match data { - StyleValueData::Length { value, unit } => { - let ratio = crate::css::style_compute::LENGTH_UNIT_CANONICAL_PX_RATIOS[*unit as usize]; - assert!(ratio.is_finite(), "computed length is not absolute"); - FfiSizeValue::px_value(CssPixels::nearest_value_for(value * ratio)) - } - StyleValueData::Percentage { value } => FfiSizeValue { - kind: FfiSizeKind::Percentage as u8, +impl InsetValue<'_> { + pub(crate) fn auto_value() -> Self { + Self::Resolved(ResolvedInsetOverride { + is_auto: true, px: CssPixels::default(), - // Match Percentage::as_fraction(); the multiplication order is - // observable for some f64 inputs. - fraction: value * 0.01, - calc: std::ptr::null(), - contains_percentage: true, - contains_anchor_function: false, - fit_content_has_argument: false, - }, - StyleValueData::Calculated { .. } => { - // SAFETY: The style value outlives the pass; the calc pointer - // below borrows it rather than retaining a second reference. - let root = unsafe { crate::css::calc::rust_calc_root_from_calculated(pointer) }; - assert!(!root.is_null()); - let contains_percentage = unsafe { crate::css::calc::rust_calc_node_contains_percentage(root) }; - let contains_anchor_function = unsafe { crate::css::calc::rust_calc_contains_anchor(pointer) }; - FfiSizeValue { - kind: FfiSizeKind::Calc as u8, - px: CssPixels::default(), - fraction: 0.0, - calc: pointer, - contains_percentage, - contains_anchor_function, - fit_content_has_argument: false, - } - } - _ => unreachable!("computed length-percentage holds a non-length-percentage style value"), + }) } -} - -fn decode_computed_size(value: &crate::layout::ComputedSize) -> FfiSizeValue { - use crate::layout::ComputedSizeKind; - match value.kind { - ComputedSizeKind::Auto => FfiSizeValue::auto_value(), - ComputedSizeKind::Calculated => decode_length_percentage(&value.value), - ComputedSizeKind::Length => { - let result = decode_length_percentage(&value.value); - assert_eq!(result.kind(), FfiSizeKind::Px); - result - } - ComputedSizeKind::Percentage => { - let result = decode_length_percentage(&value.value); - assert_eq!(result.kind(), FfiSizeKind::Percentage); - result - } - ComputedSizeKind::MinContent => FfiSizeValue { - kind: FfiSizeKind::MinContent as u8, - ..FfiSizeValue::auto_value() - }, - ComputedSizeKind::MaxContent => FfiSizeValue { - kind: FfiSizeKind::MaxContent as u8, - ..FfiSizeValue::auto_value() - }, - ComputedSizeKind::FitContent => { - if value.value.pointer.is_null() { - FfiSizeValue { - kind: FfiSizeKind::FitContent as u8, - ..FfiSizeValue::auto_value() - } - } else { - let mut result = decode_length_percentage(&value.value); - result.kind = FfiSizeKind::FitContent as u8; - result.fit_content_has_argument = true; - result - } + pub(crate) fn is_auto(self) -> bool { + match self { + Self::FromStyle(value) => value.is_auto(), + Self::BareAnchor(_) => false, + Self::Resolved(resolved) => resolved.is_auto, } - ComputedSizeKind::None => FfiSizeValue { - kind: FfiSizeKind::None_ as u8, - ..FfiSizeValue::auto_value() - }, } -} -fn decode_length_percentage_or_auto(value: &crate::layout::ComputedLengthPercentageOrAuto) -> FfiSizeValue { - if value.is_auto { - FfiSizeValue::auto_value() - } else { - decode_length_percentage(&value.value) + pub(crate) fn to_px(self, reference: CssPixels) -> CssPixels { + match self { + Self::FromStyle(value) => value.to_px(reference), + Self::BareAnchor(wrapper) => resolve_calc_to_px(std::ptr::from_ref(wrapper).cast(), reference), + Self::Resolved(resolved) if resolved.is_auto => CssPixels::default(), + Self::Resolved(resolved) => resolved.px, + } } -} -/// The computed `aspect-ratio` term as a CSSPixels fraction. A zero -/// denominator means no usable ratio (none specified, degenerate, or collapsed -/// to zero by the fixed-point conversion). -fn decode_css_preferred_aspect_ratio( - ratio: &crate::css::computed_value_types::ComputedAspectRatio, -) -> (CssPixels, CssPixels) { - let no_usable_ratio = (CssPixels::default(), CssPixels::default()); - if !ratio.has_preferred_ratio { - return no_usable_ratio; + pub(crate) fn contains_percentage(self) -> bool { + match self { + Self::FromStyle(value) => value.contains_percentage(), + Self::BareAnchor(_) | Self::Resolved(_) => false, + } } - let numerator = ratio.preferred_ratio_numerator; - let denominator = ratio.preferred_ratio_denominator; - let is_degenerate = !numerator.is_finite() || numerator == 0.0 || !denominator.is_finite() || denominator == 0.0; - if is_degenerate { - return no_usable_ratio; + + pub(crate) fn contains_anchor_function(self) -> bool { + match self { + Self::FromStyle(value) => value + .length_percentage() + .is_some_and(|length_percentage| length_percentage.contains_anchor_function()), + Self::BareAnchor(_) => true, + Self::Resolved(_) => false, + } } - let (numerator, denominator) = CssPixels::fraction_nearest_values_for(numerator, denominator); - if numerator.raw_value() == 0 { - return no_usable_ratio; + + /// The calculated style value carrying the anchor() function, for the + /// abspos engine's anchor-aware resolution. + pub(crate) fn anchor_bearing_calculated(self) -> *const c_void { + match self { + Self::FromStyle(value) => value + .length_percentage() + .expect("anchor-bearing inset must hold a style value") + .calculated_pointer(), + Self::BareAnchor(wrapper) => std::ptr::from_ref(wrapper).cast(), + Self::Resolved(_) => unreachable!("resolved inset overrides never carry anchor functions"), + } } - (numerator, denominator) } -/// A thin Rust-only view over a node's immutable computed-value group -/// payloads; every read decodes on demand from the typed group payloads. The -/// four inset fields additionally consult the per-LayoutState anchor-inset -/// store, the only style state a pass can change. +/// The layout pass view of a node's computed style: the pure payload view +/// plus the per-LayoutState anchor-inset store the four inset reads consult +/// and the line builder's vertical-align keyword substitution. Every other +/// read derefs to ComputedValuesView. #[derive(Clone, Copy)] pub(crate) struct StyleValues<'a> { - reader: StyleReader<'a>, + style: ComputedValuesView<'a>, anchor_insets: &'a AnchorInsetStore, slot_index: u32, vertical_align_override: u16, } -macro_rules! scalar_accessors { - ($($group:ident: { $($name:ident: $ty:ty => $($field:ident).+,)+ })+) => { - impl StyleValues<'_> { - $($( - #[inline] - pub(crate) fn $name(self) -> $ty { - self.reader.$group().$($field).+ - } - )+)+ - } - }; -} +impl<'a> std::ops::Deref for StyleValues<'a> { + type Target = ComputedValuesView<'a>; -scalar_accessors! { - box_values: { - display: FfiDisplay => display, - position: u8 => position, - float_: u8 => float_, - clear: u8 => clear, - box_sizing: u8 => box_sizing, - overflow_x: u8 => overflow_x, - overflow_y: u8 => overflow_y, - text_overflow: u8 => text_overflow, - table_layout: u8 => table_layout, - unicode_bidi: u8 => unicode_bidi, - grid_auto_flow_row: bool => grid_auto_flow_row, - grid_auto_flow_dense: bool => grid_auto_flow_dense, - } - border_facts: { - border_top_width: CssPixels => border_top.width, - border_right_width: CssPixels => border_right.width, - border_bottom_width: CssPixels => border_bottom.width, - border_left_width: CssPixels => border_left.width, - border_top_style: u8 => border_top.line_style, - border_right_style: u8 => border_right.line_style, - border_bottom_style: u8 => border_bottom.line_style, - border_left_style: u8 => border_left.line_style, - border_top_color: u32 => border_top.color, - border_right_color: u32 => border_right.color, - border_bottom_color: u32 => border_bottom.color, - border_left_color: u32 => border_left.color, - } - inherited_box: { - writing_mode: u8 => writing_mode, - direction: u8 => direction, - visibility: u8 => visibility, - } - inherited_table: { - border_collapse: u8 => border_collapse, - caption_side: u8 => caption_side, - } - inherited_text_facts: { - text_align: u8 => text_align, - text_justify: u8 => text_justify, - white_space_collapse: u8 => white_space_collapse, - text_wrap_mode: u8 => text_wrap_mode, - word_break: u8 => word_break, - letter_spacing: CssPixels => letter_spacing, - word_spacing: CssPixels => word_spacing, - } - font_facts: { - font_variant_emoji: u8 => font_variant_emoji, - line_height: CssPixels => line_height_used, - font_size: CssPixels => font_size, - } - alignment: { - flex_direction: u8 => flex_direction, - flex_wrap: u8 => flex_wrap, - flex_grow: f64 => flex_grow, - flex_shrink: f64 => flex_shrink, - order: i32 => order, - align_items: u8 => align_items, - align_self: u8 => align_self, - align_content: u8 => align_content, - justify_content: u8 => justify_content, - justify_items: u8 => justify_items, - justify_self: u8 => justify_self, + fn deref(&self) -> &ComputedValuesView<'a> { + &self.style } } impl<'a> StyleValues<'a> { #[inline] - pub(crate) fn new(reader: StyleReader<'a>, anchor_insets: &'a AnchorInsetStore, slot_index: u32) -> Self { + pub(crate) fn new(payloads: &'a FfiStylePayloads, anchor_insets: &'a AnchorInsetStore, slot_index: u32) -> Self { Self { - reader, + style: ComputedValuesView::new(&payloads.groups), anchor_insets, slot_index, vertical_align_override: u16::MAX, } } - fn anchor_inset_handle(self, field: SizeField) -> Option<&'a crate::layout::ComputedStyleValueHandle> { - let values = self.reader.surround(); + fn anchor_inset_handle(self, field: InsetField) -> Option<&'a ComputedStyleValueHandle> { + let values = self.style.surround(); let handle = match field { - SizeField::InsetTop => &values.top_anchor_inset, - SizeField::InsetRight => &values.right_anchor_inset, - SizeField::InsetBottom => &values.bottom_anchor_inset, - SizeField::InsetLeft => &values.left_anchor_inset, - _ => unreachable!(), + InsetField::Top => &values.top_anchor_inset, + InsetField::Right => &values.right_anchor_inset, + InsetField::Bottom => &values.bottom_anchor_inset, + InsetField::Left => &values.left_anchor_inset, }; (!handle.pointer.is_null()).then_some(handle) } - fn direct_size(self, field: SizeField) -> FfiSizeValue { - match field { - SizeField::Width - | SizeField::Height - | SizeField::MinWidth - | SizeField::MinHeight - | SizeField::MaxWidth - | SizeField::MaxHeight => { - let values = self.reader.sizing(); - decode_computed_size(match field { - SizeField::Width => &values.width, - SizeField::Height => &values.height, - SizeField::MinWidth => &values.min_width, - SizeField::MinHeight => &values.min_height, - SizeField::MaxWidth => &values.max_width, - SizeField::MaxHeight => &values.max_height, - _ => unreachable!(), - }) - } - SizeField::MarginTop - | SizeField::MarginRight - | SizeField::MarginBottom - | SizeField::MarginLeft - | SizeField::PaddingTop - | SizeField::PaddingRight - | SizeField::PaddingBottom - | SizeField::PaddingLeft - | SizeField::InsetTop - | SizeField::InsetRight - | SizeField::InsetBottom - | SizeField::InsetLeft => { - let values = self.reader.surround(); - decode_length_percentage_or_auto(match field { - SizeField::MarginTop => &values.margin.top, - SizeField::MarginRight => &values.margin.right, - SizeField::MarginBottom => &values.margin.bottom, - SizeField::MarginLeft => &values.margin.left, - SizeField::PaddingTop => &values.padding.top, - SizeField::PaddingRight => &values.padding.right, - SizeField::PaddingBottom => &values.padding.bottom, - SizeField::PaddingLeft => &values.padding.left, - SizeField::InsetTop => &values.inset.top, - SizeField::InsetRight => &values.inset.right, - SizeField::InsetBottom => &values.inset.bottom, - SizeField::InsetLeft => &values.inset.left, - _ => unreachable!(), - }) - } - SizeField::FlexBasis => { - let values = self.reader.alignment(); - if values.flex_basis.is_content { - FfiSizeValue::auto_value() - } else { - decode_computed_size(&values.flex_basis.size) - } - } - SizeField::RowGap | SizeField::ColumnGap => { - let values = self.reader.alignment(); - let gap = if field == SizeField::RowGap { - &values.row_gap - } else { - &values.column_gap - }; - if gap.is_normal { - FfiSizeValue::auto_value() - } else { - decode_length_percentage(&gap.value) - } - } - SizeField::X | SizeField::Y => { - let values = self.reader.svg_reset(); - decode_length_percentage(if field == SizeField::X { &values.x } else { &values.y }) - } - SizeField::VerticalAlign => decode_length_percentage(&self.reader.box_values().vertical_align.value), - SizeField::TextIndent => { - decode_length_percentage(&self.reader.inherited_text_facts().text_indent.length_percentage) - } - SizeField::ColumnWidth => decode_computed_size(&self.reader.box_values().column_width), + fn inset_value(self, field: InsetField) -> InsetValue<'a> { + if let Some(resolved) = self.anchor_insets.override_for(self.slot_index, field) { + return InsetValue::Resolved(resolved); } - } - - fn size_value(self, field: SizeField) -> FfiSizeValue { - if matches!( - field, - SizeField::InsetTop | SizeField::InsetRight | SizeField::InsetBottom | SizeField::InsetLeft - ) { - // The resolved override must mask BOTH anchor representations: a - // bare anchor() inset carries the surround anchor handle below, - // while a calc() containing anchor() has a null handle and - // decodes through direct_size with contains_anchor_function set. - if let Some(value) = self.anchor_insets.override_for(self.slot_index, field) { - return value; - } - if let Some(handle) = self.anchor_inset_handle(field) { - return self - .anchor_insets - .memoized_anchor_inset_value(self.slot_index, field, || { - // SAFETY: The handle is non-null, and the node's style - // group payload keeps the anchor value alive for the - // synchronous layout pass. - unsafe { crate::css::calc::create_anchor_inset_calculated(handle.pointer.cast()) } - }); - } - } - self.direct_size(field) - } - - pub(crate) fn with_vertical_align_keyword(mut self, keyword: u8) -> Self { - self.vertical_align_override = keyword as u16; - self - } - - pub(crate) fn vertical_align_is_keyword(self) -> bool { - self.vertical_align_override != u16::MAX || self.reader.box_values().vertical_align.is_keyword - } - - pub(crate) fn vertical_align_keyword(self) -> u8 { - if self.vertical_align_override != u16::MAX { - self.vertical_align_override as u8 - } else { - self.reader.box_values().vertical_align.keyword + if let Some(handle) = self.anchor_inset_handle(field) { + return InsetValue::BareAnchor(self.anchor_insets.memoized_bare_anchor_wrapper( + self.slot_index, + field, + || { + // SAFETY: The handle is non-null, and the node's style + // group payload keeps the anchor value alive for the + // synchronous layout pass. + unsafe { crate::css::calc::create_anchor_inset_calculated(handle.pointer.cast()) } + }, + )); } + let values = self.style.surround(); + InsetValue::FromStyle(match field { + InsetField::Top => &values.inset.top, + InsetField::Right => &values.inset.right, + InsetField::Bottom => &values.inset.bottom, + InsetField::Left => &values.inset.left, + }) } - pub(crate) fn vertical_align_value(self) -> FfiSizeValue { - self.size_value(SizeField::VerticalAlign) - } - - pub(crate) fn has_position_anchor(self) -> bool { - self.reader.surround().position_anchor_name.raw() != 0 + pub(crate) fn inset_top(self) -> InsetValue<'a> { + self.inset_value(InsetField::Top) } - /// The raw fly-string representation of the computed position-anchor - /// name, borrowed from the surround payload for the duration of the pass. - pub(crate) fn position_anchor_name(self) -> usize { - self.reader.surround().position_anchor_name.raw() + pub(crate) fn inset_right(self) -> InsetValue<'a> { + self.inset_value(InsetField::Right) } - pub(crate) fn first_available_font(self) -> *const c_void { - let font = self.reader.font_facts().first_available_font; - debug_assert!(!font.is_null(), "layout read a font group that never received a font list"); - font + pub(crate) fn inset_bottom(self) -> InsetValue<'a> { + self.inset_value(InsetField::Bottom) } - pub(crate) fn font_cascade_list(self) -> *const c_void { - let list = self.reader.font_facts().font_cascade_list; - debug_assert!(!list.is_null(), "layout read a font group that never received a font list"); - list + pub(crate) fn inset_left(self) -> InsetValue<'a> { + self.inset_value(InsetField::Left) } - pub(crate) fn font_ascent(self) -> f32 { - self.reader.font_facts().font_ascent - } - - pub(crate) fn font_descent(self) -> f32 { - self.reader.font_facts().font_descent + pub(crate) fn with_vertical_align_keyword(mut self, keyword: u8) -> Self { + self.vertical_align_override = keyword as u16; + self } - pub(crate) fn font_x_height(self) -> f32 { - self.reader.font_facts().font_x_height + pub(crate) fn vertical_align_is_keyword(self) -> bool { + self.vertical_align_override != u16::MAX || self.style.box_values().vertical_align.is_keyword } - pub(crate) fn box_sizing_for_aspect_ratio(self) -> u8 { - let values = self.reader.box_values(); - if values.aspect_ratio.use_natural_aspect_ratio_if_available { - crate::css::css_enums::box_sizing::CONTENT_BOX + pub(crate) fn vertical_align_keyword(self) -> u8 { + if self.vertical_align_override != u16::MAX { + self.vertical_align_override as u8 } else { - values.box_sizing - } - } - - pub(crate) fn css_preferred_aspect_ratio(self) -> (CssPixels, CssPixels) { - decode_css_preferred_aspect_ratio(&self.reader.box_values().aspect_ratio) - } - - pub(crate) fn border_spacing_horizontal(self) -> CssPixels { - CssPixels::from_raw(self.reader.inherited_table().border_spacing_horizontal) - } - - pub(crate) fn border_spacing_vertical(self) -> CssPixels { - CssPixels::from_raw(self.reader.inherited_table().border_spacing_vertical) - } - - pub(crate) fn aspect_ratio_uses_natural_when_available(self) -> bool { - self.reader.box_values().aspect_ratio.use_natural_aspect_ratio_if_available - } - - pub(crate) fn flex_basis_is_content(self) -> bool { - self.reader.alignment().flex_basis.is_content - } - - pub(crate) fn flex_basis(self) -> FfiSizeValue { - self.size_value(SizeField::FlexBasis) - } - - pub(crate) fn has_column_count(self) -> bool { - self.reader.box_values().column_count_has_value - } - - pub(crate) fn column_count(self) -> i32 { - self.reader.box_values().column_count - } - - pub(crate) fn has_size_containment(self) -> bool { - self.reader.box_values().size_containment - } - - pub(crate) fn is_size_container(self) -> bool { - self.reader.box_values().is_size_container - } - - pub(crate) fn text_indent_each_line(self) -> bool { - self.reader.inherited_text_facts().text_indent.each_line - } - - pub(crate) fn text_indent_hanging(self) -> bool { - self.reader.inherited_text_facts().text_indent.hanging - } - - pub(crate) fn tab_size_is_number(self) -> bool { - self.reader.inherited_text_facts().tab_size_is_number - } - - pub(crate) fn tab_size(self) -> CssPixels { - self.reader.inherited_text_facts().tab_size_length - } - - pub(crate) fn tab_size_number(self) -> f64 { - self.reader.inherited_text_facts().tab_size_number - } -} - -macro_rules! size_accessors { - ($($name:ident => $field:ident,)+) => { - impl StyleValues<'_> { - $(pub(crate) fn $name(self) -> FfiSizeValue { - self.size_value(SizeField::$field) - })+ + self.style.box_values().vertical_align.keyword } - }; -} - -size_accessors! { - width => Width, - height => Height, - min_width => MinWidth, - min_height => MinHeight, - max_width => MaxWidth, - max_height => MaxHeight, - margin_top => MarginTop, - margin_right => MarginRight, - margin_bottom => MarginBottom, - margin_left => MarginLeft, - padding_top => PaddingTop, - padding_right => PaddingRight, - padding_bottom => PaddingBottom, - padding_left => PaddingLeft, - inset_top => InsetTop, - inset_right => InsetRight, - inset_bottom => InsetBottom, - inset_left => InsetLeft, - row_gap => RowGap, - column_gap => ColumnGap, - column_width => ColumnWidth, - text_indent => TextIndent, - x => X, - y => Y, -} - -pub(crate) fn px_calc_resolution_context(percentage_basis: CssPixels) -> crate::css::calc::FfiCalcResolutionContext { - crate::css::calc::FfiCalcResolutionContext { - basis_kind: 3, - basis_value: percentage_basis.to_double(), - basis_unit: crate::css::style_compute::px_length_unit(), - length_resolution_context: std::ptr::null(), - external_resolutions: std::ptr::null(), - external_resolution_count: 0, } } diff --git a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs index 2fb004ce229dd..1dfefc9c4f6a5 100644 --- a/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/table_formatting_context.rs @@ -808,7 +808,7 @@ impl TableTree for TableFormattingContext<'_> { fn display(&self, node: Node) -> FfiDisplay { self.callbacks - .style_reader_if_styled(node) + .computed_values_view_if_styled(node) .map_or_else(FfiDisplay::block, |style| style.display()) } @@ -1379,12 +1379,12 @@ impl<'pass> TableFormattingContext<'pass> { TrackAxis::Column => (style.width(), style.max_width()), }; let maximum = if max_size.is_percentage() { - max_size.fraction * 100.0 + max_size.length_percentage().as_fraction() * 100.0 } else { f64::INFINITY }; let preferred = if size.is_percentage() { - size.fraction * 100.0 + size.length_percentage().as_fraction() * 100.0 } else { 0.0 }; @@ -1677,7 +1677,7 @@ impl<'pass> TableFormattingContext<'pass> { + style.margin_right().to_px(basis) }; let mut contribution = outer(self.calculate_min_content_inline_size(caption)); - if !style.width().is_auto() && !style.width().contains_percentage { + if !style.width().is_auto() && !style.width().contains_percentage() { let preferred = self.sizing().calculate_inner_inline_width( caption, AvailableSize::definite(basis), @@ -1692,7 +1692,7 @@ impl<'pass> TableFormattingContext<'pass> { fn resolve_inline_constraint( &mut self, - constraint: FfiSizeValue, + constraint: &ComputedSize, grid_min: CssPixels, grid_max: CssPixels, basis: CssPixels, @@ -1704,7 +1704,7 @@ impl<'pass> TableFormattingContext<'pass> { return grid_max; } if constraint.is_fit_content() { - let limit = if constraint.fit_content_has_argument { + let limit = if constraint.fit_content_available_space().is_some() { constraint.to_px(basis) } else { basis @@ -1757,7 +1757,7 @@ impl<'pass> TableFormattingContext<'pass> { used_min = used_min.max(self.resolve_inline_constraint(table_style.min_width(), grid_min, grid_max, basis)); } let width_is_auto_or_indefinite_percentage = table_style.width().is_auto() - || (table_style.width().contains_percentage + || (table_style.width().contains_percentage() && self.table_constraints.percentage_basis_inline_size.is_none()); let mut used = if width_is_auto_or_indefinite_percentage { // If the table-root has 'width: auto', the used inline size is the greater of @@ -1784,7 +1784,7 @@ impl<'pass> TableFormattingContext<'pass> { let cell_width = self.style_facts(cell.box_).width(); if cell_width.is_percentage() { let mut adjusted = spacing; - let percentage = cell_width.fraction * 100.0; + let percentage = cell_width.length_percentage().as_fraction() * 100.0; if percentage != 0.0 { adjusted += CssPixels::nearest_value_for( (100.0 / percentage * cell.outer_max_inline_size.to_double()).ceil(), diff --git a/Libraries/LibWeb/Rust/src/layout/tree_builder.rs b/Libraries/LibWeb/Rust/src/layout/tree_builder.rs index 3b69c929d7023..f21bb6f8a5165 100644 --- a/Libraries/LibWeb/Rust/src/layout/tree_builder.rs +++ b/Libraries/LibWeb/Rust/src/layout/tree_builder.rs @@ -7,7 +7,7 @@ use crate::abort_on_panic; use crate::layout::layout_node_arena::LayoutNodeArena; use crate::layout::node_data::{GENERATED_FOR_MARKER, NodeData, NodeFlag, NodeKind, NodeSlotId}; -use crate::layout::{FfiDisplay, StyleReader, kind_is_replaced_box, node_can_have_children}; +use crate::layout::{ComputedValuesView, FfiDisplay, kind_is_replaced_box, node_can_have_children}; use std::ffi::c_void; type LayoutNode = NodeSlotId; @@ -2154,11 +2154,11 @@ impl TreeBuilderHost<'_> { unsafe { &*(*self.arena).data(node) } } - fn style(&self, node: LayoutNode) -> Option> { + fn style(&self, node: LayoutNode) -> Option> { assert!(!node.is_invalid()); // SAFETY: Entry points guarantee that the arena remains live, and callers only retain the reader until the // next mutation callback. - unsafe { (*self.arena).style_payloads(node) }.map(StyleReader::new) + unsafe { (*self.arena).style_payloads(node) }.map(|payloads| ComputedValuesView::new(&payloads.groups)) } fn display(&self, node: LayoutNode) -> FfiDisplay {