layout: Move sizing logic from geom.rs to sizing.rs (#38568)

Moves `Size`, `SizeConstraint`, `Sizes` and `LazySizeData`.

Testing: Not needed, no change in behavior.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-08-09 13:43:40 -07:00 committed by GitHub
parent d5d3ad6949
commit 7ff8724eaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 559 additions and 557 deletions

View file

@ -31,19 +31,17 @@ use crate::formatting_contexts::Baselines;
use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
};
use crate::geom::{AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes};
use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{
AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement,
};
use crate::sizing::{
ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, IntrinsicSizingMode,
LazySize, Size, SizeConstraint, Sizes,
};
use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock,
SizeConstraint,
};
use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock};
/// Layout parameters and intermediate results about a flex container,
/// grouped to avoid passing around many parameters

View file

@ -34,17 +34,17 @@ use crate::fragment_tree::{
Fragment, FragmentFlags,
};
use crate::geom::{
AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint,
PhysicalRect, PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock,
AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
PhysicalSides, ToLogical, ToLogicalWithContainingBlock,
};
use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
use crate::sizing::{self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{AspectRatio, ContentBoxSizesAndPBM, LayoutStyle, PaddingBorderMargin};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock,
SizeConstraint,
use crate::sizing::{
self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, Size,
SizeConstraint, Sizes,
};
use crate::style_ext::{AspectRatio, ContentBoxSizesAndPBM, LayoutStyle, PaddingBorderMargin};
use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock};
mod construct;
pub mod float;

View file

@ -16,13 +16,14 @@ use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::flexbox::FlexContainer;
use crate::flow::BlockFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags};
use crate::geom::LazySize;
use crate::layout_box_base::{
CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase,
};
use crate::positioned::PositioningContext;
use crate::replaced::ReplacedContents;
use crate::sizing::{self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::sizing::{
self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize,
};
use crate::style_ext::{AspectRatio, DisplayInside, LayoutStyle};
use crate::table::Table;
use crate::taffy::TaffyContainer;

View file

@ -2,24 +2,20 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{LazyCell, OnceCell};
use std::convert::From;
use std::fmt;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use app_units::{Au, MAX_AU};
use app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use style::Zero;
use style::logical_geometry::{BlockFlowDirection, Direction, InlineBaseDirection, WritingMode};
use style::values::computed::{
CSSPixelLength, LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize,
};
use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection, WritingMode};
use style::values::computed::{CSSPixelLength, LengthPercentage};
use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr;
use style_traits::CSSPixel;
use crate::ContainingBlock;
use crate::sizing::ContentSizes;
use crate::style_ext::Clamp;
use crate::sizing::Size;
pub type PhysicalPoint<U> = euclid::Point2D<U, CSSPixel>;
pub type PhysicalSize<U> = euclid::Size2D<U, CSSPixel>;
@ -686,517 +682,3 @@ impl ToLogicalWithContainingBlock<LogicalRect<Au>> for PhysicalRect<Au> {
}
}
}
/// The possible values accepted by the sizing properties.
/// <https://drafts.csswg.org/css-sizing/#sizing-properties>
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Size<T> {
/// Represents an `auto` value for the preferred and minimum size properties,
/// or `none` for the maximum size properties.
/// <https://drafts.csswg.org/css-sizing/#valdef-width-auto>
/// <https://drafts.csswg.org/css-sizing/#valdef-max-width-none>
Initial,
/// <https://drafts.csswg.org/css-sizing/#valdef-width-min-content>
MinContent,
/// <https://drafts.csswg.org/css-sizing/#valdef-width-max-content>
MaxContent,
/// <https://drafts.csswg.org/css-sizing-4/#valdef-width-fit-content>
FitContent,
/// <https://drafts.csswg.org/css-sizing-3/#funcdef-width-fit-content>
FitContentFunction(T),
/// <https://drafts.csswg.org/css-sizing-4/#valdef-width-stretch>
Stretch,
/// Represents a numeric `<length-percentage>`, but resolved as a `T`.
/// <https://drafts.csswg.org/css-sizing/#valdef-width-length-percentage-0>
Numeric(T),
}
impl<T: Copy> Copy for Size<T> {}
impl<T> Default for Size<T> {
#[inline]
fn default() -> Self {
Self::Initial
}
}
impl<T> Size<T> {
#[inline]
pub(crate) fn is_initial(&self) -> bool {
matches!(self, Self::Initial)
}
}
impl<T: Clone> Size<T> {
#[inline]
pub(crate) fn to_numeric(&self) -> Option<T> {
match self {
Self::Numeric(numeric) => Some(numeric).cloned(),
_ => None,
}
}
#[inline]
pub(crate) fn map<U>(&self, f: impl FnOnce(T) -> U) -> Size<U> {
match self {
Size::Initial => Size::Initial,
Size::MinContent => Size::MinContent,
Size::MaxContent => Size::MaxContent,
Size::FitContent => Size::FitContent,
Size::FitContentFunction(size) => Size::FitContentFunction(f(size.clone())),
Size::Stretch => Size::Stretch,
Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())),
}
}
}
impl From<StyleSize> for Size<LengthPercentage> {
fn from(size: StyleSize) -> Self {
match size {
StyleSize::LengthPercentage(lp) => Size::Numeric(lp.0),
StyleSize::Auto => Size::Initial,
StyleSize::MinContent => Size::MinContent,
StyleSize::MaxContent => Size::MaxContent,
StyleSize::FitContent => Size::FitContent,
StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
StyleSize::Stretch => Size::Stretch,
StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
}
impl From<StyleMaxSize> for Size<LengthPercentage> {
fn from(max_size: StyleMaxSize) -> Self {
match max_size {
StyleMaxSize::LengthPercentage(lp) => Size::Numeric(lp.0),
StyleMaxSize::None => Size::Initial,
StyleMaxSize::MinContent => Size::MinContent,
StyleMaxSize::MaxContent => Size::MaxContent,
StyleMaxSize::FitContent => Size::FitContent,
StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
StyleMaxSize::Stretch => Size::Stretch,
StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
}
impl Size<LengthPercentage> {
#[inline]
pub(crate) fn to_percentage(&self) -> Option<Percentage> {
self.to_numeric()
.and_then(|length_percentage| length_percentage.to_percentage())
}
/// Resolves percentages in a preferred size, against the provided basis.
/// If the basis is missing, percentages are considered cyclic.
/// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
/// <https://www.w3.org/TR/css-sizing-3/#cyclic-percentage-size>
#[inline]
pub(crate) fn resolve_percentages_for_preferred(&self, basis: Option<Au>) -> Size<Au> {
match self {
Size::Numeric(numeric) => numeric
.maybe_to_used_value(basis)
.map_or(Size::Initial, Size::Numeric),
Size::FitContentFunction(numeric) => {
// Under discussion in https://github.com/w3c/csswg-drafts/issues/11805
numeric
.maybe_to_used_value(basis)
.map_or(Size::FitContent, Size::FitContentFunction)
},
_ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
}
}
/// Resolves percentages in a maximum size, against the provided basis.
/// If the basis is missing, percentages are considered cyclic.
/// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
/// <https://www.w3.org/TR/css-sizing-3/#cyclic-percentage-size>
#[inline]
pub(crate) fn resolve_percentages_for_max(&self, basis: Option<Au>) -> Size<Au> {
match self {
Size::Numeric(numeric) => numeric
.maybe_to_used_value(basis)
.map_or(Size::Initial, Size::Numeric),
Size::FitContentFunction(numeric) => {
// Under discussion in https://github.com/w3c/csswg-drafts/issues/11805
numeric
.maybe_to_used_value(basis)
.map_or(Size::MaxContent, Size::FitContentFunction)
},
_ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
}
}
}
impl LogicalVec2<Size<LengthPercentage>> {
pub(crate) fn percentages_relative_to_basis(
&self,
basis: &LogicalVec2<Au>,
) -> LogicalVec2<Size<Au>> {
LogicalVec2 {
inline: self.inline.map(|value| value.to_used_value(basis.inline)),
block: self.block.map(|value| value.to_used_value(basis.block)),
}
}
}
impl Size<Au> {
/// Resolves a preferred size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
#[inline]
pub(crate) fn resolve_for_preferred<F: FnOnce() -> ContentSizes>(
&self,
automatic_size: Size<Au>,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
) -> Au {
match self {
Self::Initial => {
assert!(!automatic_size.is_initial());
automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size)
},
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
Self::FitContent => {
content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content))
},
Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content),
Self::Numeric(numeric) => *numeric,
}
}
/// Resolves a minimum size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#min-size-properties>
#[inline]
pub(crate) fn resolve_for_min<F: FnOnce() -> ContentSizes>(
&self,
get_automatic_minimum_size: impl FnOnce() -> Au,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
is_table: bool,
) -> Au {
let result = match self {
Self::Initial => get_automatic_minimum_size(),
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()),
Self::Stretch => stretch_size.unwrap_or_default(),
Self::Numeric(numeric) => *numeric,
};
if is_table {
// In addition to the specified minimum, the inline size of a table is forced to be
// at least as big as its min-content size.
//
// Note that if there are collapsed columns, only the inline size of the table grid will
// shrink, while the size of the table wrapper (being computed here) won't be affected.
// However, collapsed rows should typically affect the block size of the table wrapper,
// so it might be wrong to use this function for that case.
// This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408
result.max(content_size.min_content)
} else {
result
}
}
/// Resolves a maximum size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#max-size-properties>
#[inline]
pub(crate) fn resolve_for_max<F: FnOnce() -> ContentSizes>(
&self,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
) -> Option<Au> {
Some(match self {
Self::Initial => return None,
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)),
Self::Stretch => return stretch_size,
Self::Numeric(numeric) => *numeric,
})
}
/// Tries to resolve an extrinsic size into a numerical value.
/// Extrinsic sizes are those based on the context of an element, without regard for its contents.
/// <https://drafts.csswg.org/css-sizing-3/#extrinsic>
///
/// Returns `None` if either:
/// - The size is intrinsic.
/// - The size is the initial one.
/// TODO: should we allow it to behave as `stretch` instead of assuming it's intrinsic?
/// - The provided `stretch_size` is `None` but we need its value.
#[inline]
pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option<Au>) -> Option<Au> {
match self {
Self::Initial |
Self::MinContent |
Self::MaxContent |
Self::FitContent |
Self::FitContentFunction(_) => None,
Self::Stretch => stretch_size,
Self::Numeric(numeric) => Some(*numeric),
}
}
}
/// Represents the sizing constraint that the preferred, min and max sizing properties
/// impose on one axis.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
pub(crate) enum SizeConstraint {
/// Represents a definite preferred size, clamped by minimum and maximum sizes (if any).
Definite(Au),
/// Represents an indefinite preferred size that allows a range of values between
/// the first argument (minimum size) and the second one (maximum size).
MinMax(Au, Option<Au>),
}
impl Default for SizeConstraint {
#[inline]
fn default() -> Self {
Self::MinMax(Au::default(), None)
}
}
impl SizeConstraint {
#[inline]
pub(crate) fn new(preferred_size: Option<Au>, min_size: Au, max_size: Option<Au>) -> Self {
preferred_size.map_or_else(
|| Self::MinMax(min_size, max_size),
|size| Self::Definite(size.clamp_between_extremums(min_size, max_size)),
)
}
#[inline]
pub(crate) fn is_definite(self) -> bool {
matches!(self, Self::Definite(_))
}
#[inline]
pub(crate) fn to_definite(self) -> Option<Au> {
match self {
Self::Definite(size) => Some(size),
_ => None,
}
}
}
impl From<Option<Au>> for SizeConstraint {
fn from(size: Option<Au>) -> Self {
size.map(SizeConstraint::Definite).unwrap_or_default()
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Sizes {
/// <https://drafts.csswg.org/css-sizing-3/#preferred-size-properties>
pub preferred: Size<Au>,
/// <https://drafts.csswg.org/css-sizing-3/#min-size-properties>
pub min: Size<Au>,
/// <https://drafts.csswg.org/css-sizing-3/#max-size-properties>
pub max: Size<Au>,
}
impl Sizes {
#[inline]
pub(crate) fn new(preferred: Size<Au>, min: Size<Au>, max: Size<Au>) -> Self {
Self {
preferred,
min,
max,
}
}
/// Resolves the three sizes into a single numerical value.
#[inline]
pub(crate) fn resolve(
&self,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: impl FnOnce() -> Au,
stretch_size: Option<Au>,
get_content_size: impl FnOnce() -> ContentSizes,
is_table: bool,
) -> Au {
if is_table && axis == Direction::Block {
// The intrinsic block size of a table already takes sizing properties into account,
// but it can be a smaller amount if there are collapsed rows.
// Therefore, disregard sizing properties and just defer to the intrinsic size.
// This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408
return get_content_size().max_content;
}
let (preferred, min, max) = self.resolve_each(
automatic_size,
get_automatic_minimum_size,
stretch_size,
get_content_size,
is_table,
);
preferred.clamp_between_extremums(min, max)
}
/// Resolves each of the three sizes into a numerical value, separately.
/// - The 1st returned value is the resolved preferred size.
/// - The 2nd returned value is the resolved minimum size.
/// - The 3rd returned value is the resolved maximum size. `None` means no maximum.
#[inline]
pub(crate) fn resolve_each(
&self,
automatic_size: Size<Au>,
get_automatic_minimum_size: impl FnOnce() -> Au,
stretch_size: Option<Au>,
get_content_size: impl FnOnce() -> ContentSizes,
is_table: bool,
) -> (Au, Au, Option<Au>) {
// The provided `get_content_size` is a FnOnce but we may need its result multiple times.
// A LazyCell will only invoke it once if needed, and then reuse the result.
let content_size = LazyCell::new(get_content_size);
(
self.preferred
.resolve_for_preferred(automatic_size, stretch_size, &content_size),
self.min.resolve_for_min(
get_automatic_minimum_size,
stretch_size,
&content_size,
is_table,
),
self.max.resolve_for_max(stretch_size, &content_size),
)
}
/// Tries to extrinsically resolve the three sizes into a single [`SizeConstraint`].
/// Values that are intrinsic or need `stretch_size` when it's `None` are handled as such:
/// - On the preferred size, they make the returned value be an indefinite [`SizeConstraint::MinMax`].
/// - On the min size, they are treated as `auto`, enforcing the automatic minimum size.
/// - On the max size, they are treated as `none`, enforcing no maximum.
#[inline]
pub(crate) fn resolve_extrinsic(
&self,
automatic_size: Size<Au>,
automatic_minimum_size: Au,
stretch_size: Option<Au>,
) -> SizeConstraint {
let (preferred, min, max) =
self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size);
SizeConstraint::new(preferred, min, max)
}
/// Tries to extrinsically resolve each of the three sizes into a numerical value, separately.
/// This can't resolve values that are intrinsic or need `stretch_size` but it's `None`.
/// - The 1st returned value is the resolved preferred size. If it can't be resolved then
/// the returned value is `None`. Note that this is different than treating it as `auto`.
/// TODO: This needs to be discussed in <https://github.com/w3c/csswg-drafts/issues/11387>.
/// - The 2nd returned value is the resolved minimum size. If it can't be resolved then we
/// treat it as the initial `auto`, returning the automatic minimum size.
/// - The 3rd returned value is the resolved maximum size. If it can't be resolved then we
/// treat it as the initial `none`, returning `None`.
#[inline]
pub(crate) fn resolve_each_extrinsic(
&self,
automatic_size: Size<Au>,
automatic_minimum_size: Au,
stretch_size: Option<Au>,
) -> (Option<Au>, Au, Option<Au>) {
(
if self.preferred.is_initial() {
automatic_size.maybe_resolve_extrinsic(stretch_size)
} else {
self.preferred.maybe_resolve_extrinsic(stretch_size)
},
self.min
.maybe_resolve_extrinsic(stretch_size)
.unwrap_or(automatic_minimum_size),
self.max.maybe_resolve_extrinsic(stretch_size),
)
}
}
struct LazySizeData<'a> {
sizes: &'a Sizes,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: fn() -> Au,
stretch_size: Option<Au>,
is_table: bool,
}
/// Represents a size that can't be fully resolved until the intrinsic size
/// is known. This is useful in the block axis, since the intrinsic size
/// depends on layout, but the other inputs are known beforehand.
pub(crate) struct LazySize<'a> {
result: OnceCell<Au>,
data: Option<LazySizeData<'a>>,
}
impl<'a> LazySize<'a> {
pub(crate) fn new(
sizes: &'a Sizes,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: fn() -> Au,
stretch_size: Option<Au>,
is_table: bool,
) -> Self {
Self {
result: OnceCell::new(),
data: Some(LazySizeData {
sizes,
axis,
automatic_size,
get_automatic_minimum_size,
stretch_size,
is_table,
}),
}
}
/// Creates a [`LazySize`] that will resolve to the intrinsic size.
/// Should be equivalent to [`LazySize::new()`] with default parameters,
/// but avoiding the trouble of getting a reference to a [`Sizes::default()`]
/// which lives long enough.
///
/// TODO: It's not clear what this should do if/when [`LazySize::resolve()`]
/// is changed to accept a [`ContentSizes`] as the intrinsic size.
pub(crate) fn intrinsic() -> Self {
Self {
result: OnceCell::new(),
data: None,
}
}
/// Resolves the [`LazySize`] into [`Au`], caching the result.
/// The argument is a callback that computes the intrinsic size lazily.
///
/// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`].
pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au {
*self.result.get_or_init(|| {
let Some(ref data) = self.data else {
return get_content_size();
};
data.sizes.resolve(
data.axis,
data.automatic_size,
data.get_automatic_minimum_size,
data.stretch_size,
|| get_content_size().into(),
data.is_table,
)
})
}
}
impl From<Au> for LazySize<'_> {
/// Creates a [`LazySize`] that will resolve to the given [`Au`],
/// ignoring the intrinsic size.
fn from(value: Au) -> Self {
let result = OnceCell::new();
result.set(value).unwrap();
LazySize { result, data: None }
}
}

View file

@ -13,9 +13,8 @@ use style::properties::ComputedValues;
use crate::context::LayoutContext;
use crate::formatting_contexts::Baselines;
use crate::fragment_tree::{BaseFragmentInfo, CollapsedBlockMargins, Fragment, SpecificLayoutInfo};
use crate::geom::SizeConstraint;
use crate::positioned::PositioningContext;
use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult};
use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult, SizeConstraint};
use crate::{ConstraintSpace, ContainingBlockSize};
/// A box tree node that handles containing information about style and the original DOM

View file

@ -42,7 +42,8 @@ use servo_arc::Arc as ServoArc;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use crate::geom::{LogicalVec2, SizeConstraint};
use crate::geom::LogicalVec2;
use crate::sizing::SizeConstraint;
use crate::style_ext::AspectRatio;
/// At times, a style is "owned" by more than one layout object. For example, text

View file

@ -20,15 +20,16 @@ use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::{BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment};
use crate::geom::{
AuOrAuto, LazySize, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D,
LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size,
Sizes, ToLogical, ToLogicalWithContainingBlock,
AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2,
PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, ToLogical,
ToLogicalWithContainingBlock,
};
use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
use crate::sizing::{LazySize, Size, SizeConstraint, Sizes};
use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
PropagatedBoxTreeData, SizeConstraint,
PropagatedBoxTreeData,
};
#[derive(Debug, MallocSizeOf)]

View file

@ -29,11 +29,13 @@ use crate::dom::NodeExt;
use crate::fragment_tree::{
BaseFragmentInfo, CollapsedBlockMargins, Fragment, IFrameFragment, ImageFragment,
};
use crate::geom::{LazySize, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize};
use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize};
use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult};
use crate::sizing::{
ComputeInlineContentSizes, InlineContentSizesResult, LazySize, SizeConstraint,
};
use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, LayoutStyle};
use crate::{ConstraintSpace, ContainingBlock, SizeConstraint};
use crate::{ConstraintSpace, ContainingBlock};
#[derive(Debug, MallocSizeOf)]
pub(crate) struct ReplacedContents {

View file

@ -4,17 +4,18 @@
//! <https://drafts.csswg.org/css-sizing/>
use std::cell::LazyCell;
use std::cell::{LazyCell, OnceCell};
use std::ops::{Add, AddAssign};
use app_units::Au;
use app_units::{Au, MAX_AU};
use malloc_size_of_derive::MallocSizeOf;
use style::Zero;
use style::logical_geometry::Direction;
use style::values::computed::LengthPercentage;
use style::values::computed::{
LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize,
};
use crate::context::LayoutContext;
use crate::geom::{Size, SizeConstraint};
use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle};
use crate::{ConstraintSpace, IndefiniteContainingBlock, LogicalVec2};
@ -308,3 +309,517 @@ pub(crate) trait ComputeInlineContentSizes {
result
}
}
/// The possible values accepted by the sizing properties.
/// <https://drafts.csswg.org/css-sizing/#sizing-properties>
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Size<T> {
/// Represents an `auto` value for the preferred and minimum size properties,
/// or `none` for the maximum size properties.
/// <https://drafts.csswg.org/css-sizing/#valdef-width-auto>
/// <https://drafts.csswg.org/css-sizing/#valdef-max-width-none>
Initial,
/// <https://drafts.csswg.org/css-sizing/#valdef-width-min-content>
MinContent,
/// <https://drafts.csswg.org/css-sizing/#valdef-width-max-content>
MaxContent,
/// <https://drafts.csswg.org/css-sizing-4/#valdef-width-fit-content>
FitContent,
/// <https://drafts.csswg.org/css-sizing-3/#funcdef-width-fit-content>
FitContentFunction(T),
/// <https://drafts.csswg.org/css-sizing-4/#valdef-width-stretch>
Stretch,
/// Represents a numeric `<length-percentage>`, but resolved as a `T`.
/// <https://drafts.csswg.org/css-sizing/#valdef-width-length-percentage-0>
Numeric(T),
}
impl<T: Copy> Copy for Size<T> {}
impl<T> Default for Size<T> {
#[inline]
fn default() -> Self {
Self::Initial
}
}
impl<T> Size<T> {
#[inline]
pub(crate) fn is_initial(&self) -> bool {
matches!(self, Self::Initial)
}
}
impl<T: Clone> Size<T> {
#[inline]
pub(crate) fn to_numeric(&self) -> Option<T> {
match self {
Self::Numeric(numeric) => Some(numeric).cloned(),
_ => None,
}
}
#[inline]
pub(crate) fn map<U>(&self, f: impl FnOnce(T) -> U) -> Size<U> {
match self {
Size::Initial => Size::Initial,
Size::MinContent => Size::MinContent,
Size::MaxContent => Size::MaxContent,
Size::FitContent => Size::FitContent,
Size::FitContentFunction(size) => Size::FitContentFunction(f(size.clone())),
Size::Stretch => Size::Stretch,
Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())),
}
}
}
impl From<StyleSize> for Size<LengthPercentage> {
fn from(size: StyleSize) -> Self {
match size {
StyleSize::LengthPercentage(lp) => Size::Numeric(lp.0),
StyleSize::Auto => Size::Initial,
StyleSize::MinContent => Size::MinContent,
StyleSize::MaxContent => Size::MaxContent,
StyleSize::FitContent => Size::FitContent,
StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
StyleSize::Stretch => Size::Stretch,
StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
}
impl From<StyleMaxSize> for Size<LengthPercentage> {
fn from(max_size: StyleMaxSize) -> Self {
match max_size {
StyleMaxSize::LengthPercentage(lp) => Size::Numeric(lp.0),
StyleMaxSize::None => Size::Initial,
StyleMaxSize::MinContent => Size::MinContent,
StyleMaxSize::MaxContent => Size::MaxContent,
StyleMaxSize::FitContent => Size::FitContent,
StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
StyleMaxSize::Stretch => Size::Stretch,
StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => {
unreachable!("anchor-size() should be disabled")
},
}
}
}
impl Size<LengthPercentage> {
#[inline]
pub(crate) fn to_percentage(&self) -> Option<Percentage> {
self.to_numeric()
.and_then(|length_percentage| length_percentage.to_percentage())
}
/// Resolves percentages in a preferred size, against the provided basis.
/// If the basis is missing, percentages are considered cyclic.
/// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
/// <https://www.w3.org/TR/css-sizing-3/#cyclic-percentage-size>
#[inline]
pub(crate) fn resolve_percentages_for_preferred(&self, basis: Option<Au>) -> Size<Au> {
match self {
Size::Numeric(numeric) => numeric
.maybe_to_used_value(basis)
.map_or(Size::Initial, Size::Numeric),
Size::FitContentFunction(numeric) => {
// Under discussion in https://github.com/w3c/csswg-drafts/issues/11805
numeric
.maybe_to_used_value(basis)
.map_or(Size::FitContent, Size::FitContentFunction)
},
_ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
}
}
/// Resolves percentages in a maximum size, against the provided basis.
/// If the basis is missing, percentages are considered cyclic.
/// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
/// <https://www.w3.org/TR/css-sizing-3/#cyclic-percentage-size>
#[inline]
pub(crate) fn resolve_percentages_for_max(&self, basis: Option<Au>) -> Size<Au> {
match self {
Size::Numeric(numeric) => numeric
.maybe_to_used_value(basis)
.map_or(Size::Initial, Size::Numeric),
Size::FitContentFunction(numeric) => {
// Under discussion in https://github.com/w3c/csswg-drafts/issues/11805
numeric
.maybe_to_used_value(basis)
.map_or(Size::MaxContent, Size::FitContentFunction)
},
_ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
}
}
}
impl LogicalVec2<Size<LengthPercentage>> {
pub(crate) fn percentages_relative_to_basis(
&self,
basis: &LogicalVec2<Au>,
) -> LogicalVec2<Size<Au>> {
LogicalVec2 {
inline: self.inline.map(|value| value.to_used_value(basis.inline)),
block: self.block.map(|value| value.to_used_value(basis.block)),
}
}
}
impl Size<Au> {
/// Resolves a preferred size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
#[inline]
pub(crate) fn resolve_for_preferred<F: FnOnce() -> ContentSizes>(
&self,
automatic_size: Size<Au>,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
) -> Au {
match self {
Self::Initial => {
assert!(!automatic_size.is_initial());
automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size)
},
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
Self::FitContent => {
content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content))
},
Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content),
Self::Numeric(numeric) => *numeric,
}
}
/// Resolves a minimum size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#min-size-properties>
#[inline]
pub(crate) fn resolve_for_min<F: FnOnce() -> ContentSizes>(
&self,
get_automatic_minimum_size: impl FnOnce() -> Au,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
is_table: bool,
) -> Au {
let result = match self {
Self::Initial => get_automatic_minimum_size(),
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()),
Self::Stretch => stretch_size.unwrap_or_default(),
Self::Numeric(numeric) => *numeric,
};
if is_table {
// In addition to the specified minimum, the inline size of a table is forced to be
// at least as big as its min-content size.
//
// Note that if there are collapsed columns, only the inline size of the table grid will
// shrink, while the size of the table wrapper (being computed here) won't be affected.
// However, collapsed rows should typically affect the block size of the table wrapper,
// so it might be wrong to use this function for that case.
// This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408
result.max(content_size.min_content)
} else {
result
}
}
/// Resolves a maximum size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#max-size-properties>
#[inline]
pub(crate) fn resolve_for_max<F: FnOnce() -> ContentSizes>(
&self,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
) -> Option<Au> {
Some(match self {
Self::Initial => return None,
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)),
Self::Stretch => return stretch_size,
Self::Numeric(numeric) => *numeric,
})
}
/// Tries to resolve an extrinsic size into a numerical value.
/// Extrinsic sizes are those based on the context of an element, without regard for its contents.
/// <https://drafts.csswg.org/css-sizing-3/#extrinsic>
///
/// Returns `None` if either:
/// - The size is intrinsic.
/// - The size is the initial one.
/// TODO: should we allow it to behave as `stretch` instead of assuming it's intrinsic?
/// - The provided `stretch_size` is `None` but we need its value.
#[inline]
pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option<Au>) -> Option<Au> {
match self {
Self::Initial |
Self::MinContent |
Self::MaxContent |
Self::FitContent |
Self::FitContentFunction(_) => None,
Self::Stretch => stretch_size,
Self::Numeric(numeric) => Some(*numeric),
}
}
}
/// Represents the sizing constraint that the preferred, min and max sizing properties
/// impose on one axis.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
pub(crate) enum SizeConstraint {
/// Represents a definite preferred size, clamped by minimum and maximum sizes (if any).
Definite(Au),
/// Represents an indefinite preferred size that allows a range of values between
/// the first argument (minimum size) and the second one (maximum size).
MinMax(Au, Option<Au>),
}
impl Default for SizeConstraint {
#[inline]
fn default() -> Self {
Self::MinMax(Au::default(), None)
}
}
impl SizeConstraint {
#[inline]
pub(crate) fn new(preferred_size: Option<Au>, min_size: Au, max_size: Option<Au>) -> Self {
preferred_size.map_or_else(
|| Self::MinMax(min_size, max_size),
|size| Self::Definite(size.clamp_between_extremums(min_size, max_size)),
)
}
#[inline]
pub(crate) fn is_definite(self) -> bool {
matches!(self, Self::Definite(_))
}
#[inline]
pub(crate) fn to_definite(self) -> Option<Au> {
match self {
Self::Definite(size) => Some(size),
_ => None,
}
}
}
impl From<Option<Au>> for SizeConstraint {
fn from(size: Option<Au>) -> Self {
size.map(SizeConstraint::Definite).unwrap_or_default()
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Sizes {
/// <https://drafts.csswg.org/css-sizing-3/#preferred-size-properties>
pub preferred: Size<Au>,
/// <https://drafts.csswg.org/css-sizing-3/#min-size-properties>
pub min: Size<Au>,
/// <https://drafts.csswg.org/css-sizing-3/#max-size-properties>
pub max: Size<Au>,
}
impl Sizes {
#[inline]
pub(crate) fn new(preferred: Size<Au>, min: Size<Au>, max: Size<Au>) -> Self {
Self {
preferred,
min,
max,
}
}
/// Resolves the three sizes into a single numerical value.
#[inline]
pub(crate) fn resolve(
&self,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: impl FnOnce() -> Au,
stretch_size: Option<Au>,
get_content_size: impl FnOnce() -> ContentSizes,
is_table: bool,
) -> Au {
if is_table && axis == Direction::Block {
// The intrinsic block size of a table already takes sizing properties into account,
// but it can be a smaller amount if there are collapsed rows.
// Therefore, disregard sizing properties and just defer to the intrinsic size.
// This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408
return get_content_size().max_content;
}
let (preferred, min, max) = self.resolve_each(
automatic_size,
get_automatic_minimum_size,
stretch_size,
get_content_size,
is_table,
);
preferred.clamp_between_extremums(min, max)
}
/// Resolves each of the three sizes into a numerical value, separately.
/// - The 1st returned value is the resolved preferred size.
/// - The 2nd returned value is the resolved minimum size.
/// - The 3rd returned value is the resolved maximum size. `None` means no maximum.
#[inline]
pub(crate) fn resolve_each(
&self,
automatic_size: Size<Au>,
get_automatic_minimum_size: impl FnOnce() -> Au,
stretch_size: Option<Au>,
get_content_size: impl FnOnce() -> ContentSizes,
is_table: bool,
) -> (Au, Au, Option<Au>) {
// The provided `get_content_size` is a FnOnce but we may need its result multiple times.
// A LazyCell will only invoke it once if needed, and then reuse the result.
let content_size = LazyCell::new(get_content_size);
(
self.preferred
.resolve_for_preferred(automatic_size, stretch_size, &content_size),
self.min.resolve_for_min(
get_automatic_minimum_size,
stretch_size,
&content_size,
is_table,
),
self.max.resolve_for_max(stretch_size, &content_size),
)
}
/// Tries to extrinsically resolve the three sizes into a single [`SizeConstraint`].
/// Values that are intrinsic or need `stretch_size` when it's `None` are handled as such:
/// - On the preferred size, they make the returned value be an indefinite [`SizeConstraint::MinMax`].
/// - On the min size, they are treated as `auto`, enforcing the automatic minimum size.
/// - On the max size, they are treated as `none`, enforcing no maximum.
#[inline]
pub(crate) fn resolve_extrinsic(
&self,
automatic_size: Size<Au>,
automatic_minimum_size: Au,
stretch_size: Option<Au>,
) -> SizeConstraint {
let (preferred, min, max) =
self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size);
SizeConstraint::new(preferred, min, max)
}
/// Tries to extrinsically resolve each of the three sizes into a numerical value, separately.
/// This can't resolve values that are intrinsic or need `stretch_size` but it's `None`.
/// - The 1st returned value is the resolved preferred size. If it can't be resolved then
/// the returned value is `None`. Note that this is different than treating it as `auto`.
/// TODO: This needs to be discussed in <https://github.com/w3c/csswg-drafts/issues/11387>.
/// - The 2nd returned value is the resolved minimum size. If it can't be resolved then we
/// treat it as the initial `auto`, returning the automatic minimum size.
/// - The 3rd returned value is the resolved maximum size. If it can't be resolved then we
/// treat it as the initial `none`, returning `None`.
#[inline]
pub(crate) fn resolve_each_extrinsic(
&self,
automatic_size: Size<Au>,
automatic_minimum_size: Au,
stretch_size: Option<Au>,
) -> (Option<Au>, Au, Option<Au>) {
(
if self.preferred.is_initial() {
automatic_size.maybe_resolve_extrinsic(stretch_size)
} else {
self.preferred.maybe_resolve_extrinsic(stretch_size)
},
self.min
.maybe_resolve_extrinsic(stretch_size)
.unwrap_or(automatic_minimum_size),
self.max.maybe_resolve_extrinsic(stretch_size),
)
}
}
struct LazySizeData<'a> {
sizes: &'a Sizes,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: fn() -> Au,
stretch_size: Option<Au>,
is_table: bool,
}
/// Represents a size that can't be fully resolved until the intrinsic size
/// is known. This is useful in the block axis, since the intrinsic size
/// depends on layout, but the other inputs are known beforehand.
pub(crate) struct LazySize<'a> {
result: OnceCell<Au>,
data: Option<LazySizeData<'a>>,
}
impl<'a> LazySize<'a> {
pub(crate) fn new(
sizes: &'a Sizes,
axis: Direction,
automatic_size: Size<Au>,
get_automatic_minimum_size: fn() -> Au,
stretch_size: Option<Au>,
is_table: bool,
) -> Self {
Self {
result: OnceCell::new(),
data: Some(LazySizeData {
sizes,
axis,
automatic_size,
get_automatic_minimum_size,
stretch_size,
is_table,
}),
}
}
/// Creates a [`LazySize`] that will resolve to the intrinsic size.
/// Should be equivalent to [`LazySize::new()`] with default parameters,
/// but avoiding the trouble of getting a reference to a [`Sizes::default()`]
/// which lives long enough.
///
/// TODO: It's not clear what this should do if/when [`LazySize::resolve()`]
/// is changed to accept a [`ContentSizes`] as the intrinsic size.
pub(crate) fn intrinsic() -> Self {
Self {
result: OnceCell::new(),
data: None,
}
}
/// Resolves the [`LazySize`] into [`Au`], caching the result.
/// The argument is a callback that computes the intrinsic size lazily.
///
/// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`].
pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au {
*self.result.get_or_init(|| {
let Some(ref data) = self.data else {
return get_content_size();
};
data.sizes.resolve(
data.axis,
data.automatic_size,
data.get_automatic_minimum_size,
data.stretch_size,
|| get_content_size().into(),
data.is_table,
)
})
}
}
impl From<Au> for LazySize<'_> {
/// Creates a [`LazySize`] that will resolve to the given [`Au`],
/// ignoring the intrinsic size.
fn from(value: Au) -> Self {
let result = OnceCell::new();
result.set(value).unwrap();
LazySize { result, data: None }
}
}

View file

@ -36,8 +36,9 @@ use crate::dom_traversal::{Contents, NonReplacedContents};
use crate::fragment_tree::FragmentFlags;
use crate::geom::{
AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides,
PhysicalSize, Size, Sizes,
PhysicalSize,
};
use crate::sizing::{Size, Sizes};
use crate::table::TableLayoutStyle;
use crate::{ContainingBlock, IndefiniteContainingBlock};

View file

@ -37,11 +37,13 @@ use crate::fragment_tree::{
};
use crate::geom::{
LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
PhysicalSides, PhysicalVec, Size, SizeConstraint, ToLogical, ToLogicalWithContainingBlock,
PhysicalSides, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{PositioningContext, PositioningContextLength, relative_adjustement};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::sizing::{
ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, Size, SizeConstraint,
};
use crate::style_ext::{
BorderStyleColor, Clamp, ComputedValuesExt, LayoutStyle, PaddingBorderMargin,
};

View file

@ -20,12 +20,12 @@ use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
};
use crate::geom::{
LazySize, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, SizeConstraint,
};
use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::sizing::{
ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, SizeConstraint,
};
use crate::style_ext::LayoutStyle;
use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize};