mirror of
https://github.com/servo/servo.git
synced 2025-08-14 01:45:33 +01:00
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:
parent
d5d3ad6949
commit
7ff8724eaf
12 changed files with 559 additions and 557 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue