Delta semántico · 31
src/style/compact_length.rs
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index edeafe252..f5d61da83 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -2,8 +2,8 @@
use crate::compute::common::alignment::compute_alignment_offset;
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{
- AlignContent, AlignItems, AlignSelf, AvailableSpace, FlexWrap, JustifyContent, LengthPercentageAuto, Overflow,
- Position,
+ AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, FlexWrap, JustifyContent, LengthPercentageAuto,
+ Overflow, Position,
};
use crate::style::{CoreStyle, FlexDirection, FlexboxContainerStyle, FlexboxItemStyle};
use crate::style_helpers::{TaffyMaxContent, TaffyMinContent};
@@ -29,6 +29,8 @@ struct FlexItem {
/// The base size of this item
size: Size<Option<f32>>,
+ /// The raw specified size of this item
+ raw_size: Size<Dimension>,
/// The minimum allowable size of this item
min_size: Size<Option<f32>>,
/// The maximum allowable size of this item
@@ -60,6 +62,8 @@ struct FlexItem {
/// The border of this item
border: Rect<f32>,
+ /// The raw flex-basis style of this item
+ raw_flex_basis: Dimension,
/// The default size of this item
flex_basis: f32,
/// The default size of this item, minus padding and border
@@ -530,6 +534,7 @@ fn generate_anonymous_flex_items(
.maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
+ raw_size: child_style.size(),
min_size: child_style
.min_size()
.maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis))
@@ -559,6 +564,7 @@ fn generate_anonymous_flex_items(
scrollbar_width: child_style.scrollbar_width(),
flex_grow: child_style.flex_grow(),
flex_shrink: child_style.flex_shrink(),
+ raw_flex_basis: child_style.flex_basis(),
flex_basis: 0.0,
inner_flex_basis: 0.0,
violation: 0.0,
@@ -645,6 +651,34 @@ fn determine_available_space(
/// Furthermore, the sizing calculations that floor the content box size at zero when applying box-sizing are also ignored.
/// (For example, an item with a specified size of zero, positive padding, and box-sizing: border-box will have an outer flex base size of zero—and hence a negative inner flex base size.)
#[inline]
+fn flex_intrinsic_available_space(
+ dimension: Dimension,
+ parent_size: Option<f32>,
+ available_space: AvailableSpace,
+ calc_resolver: impl Fn(*const (), f32) -> f32,
+) -> Option<AvailableSpace> {
+ if dimension.is_min_content() {
+ Some(AvailableSpace::MinContent)
+ } else if dimension.is_max_content() {
+ Some(AvailableSpace::MaxContent)
+ } else if dimension.is_fit_content_keyword() {
+ Some(match available_space {
+ AvailableSpace::Definite(limit) => AvailableSpace::Definite(limit),
+ AvailableSpace::MinContent => AvailableSpace::MinContent,
+ AvailableSpace::MaxContent => AvailableSpace::MaxContent,
+ })
+ } else if dimension.is_fit_content() {
+ Some(
+ dimension
+ .definite_limit(parent_size, calc_resolver)
+ .map(AvailableSpace::Definite)
+ .unwrap_or(AvailableSpace::MaxContent),
+ )
+ } else {
+ None
+ }
+}
+
fn determine_flex_base_size(
tree: &mut impl LayoutFlexboxContainer,
constants: &AlgoConstants,
@@ -709,9 +743,31 @@ fn determine_flex_base_size(
.flex_basis()
.maybe_resolve(container_width, |val, basis| tree.calc(val, basis))
.maybe_add(box_sizing_adjustment);
+ let raw_flex_basis = child.raw_flex_basis;
drop(child_style);
+ let intrinsic_main_size = flex_intrinsic_available_space(
+ child.raw_size.main(dir),
+ constants.node_inner_size.main(dir),
+ available_space.main(dir),
+ |val, basis| tree.calc(val, basis),
+ )
+ .map(|main_available_space| {
+ let child_available_space =
+ Size::MAX_CONTENT.with_main(dir, main_available_space).with_cross(dir, cross_axis_available_space);
+
+ tree.measure_child_size(
+ child.node,
+ child_known_dimensions.with_main(dir, None),
+ child_parent_size,
+ child_available_space,
+ SizingMode::InherentSize,
+ dir.main_axis(),
+ Line::FALSE,
+ )
+ });
+
child.flex_basis = 'flex_basis: {
// A. If the item has a definite used flex basis, that’s the flex base size.
@@ -722,11 +778,31 @@ fn determine_flex_base_size(
// Note: `child.size` has already been resolved against aspect_ratio in generate_anonymous_flex_items
// So B will just work here by using main_size without special handling for aspect_ratio
- let main_size = child.size.main(dir);
+ let main_size = intrinsic_main_size.or(child.size.main(dir));
if let Some(flex_basis) = flex_basis.or(main_size) {
break 'flex_basis flex_basis;
};
+ if let Some(main_available_space) = flex_intrinsic_available_space(
+ raw_flex_basis,
+ container_width,
+ available_space.main(dir),
+ |val, basis| tree.calc(val, basis),
+ ) {
+ let child_available_space =
+ Size::MAX_CONTENT.with_main(dir, main_available_space).with_cross(dir, cross_axis_available_space);
+
+ break 'flex_basis tree.measure_child_size(
+ child.node,
+ child_known_dimensions.with_main(dir, None),
+ child_parent_size,
+ child_available_space,
+ SizingMode::ContentSize,
+ dir.main_axis(),
+ Line::FALSE,
+ );
+ }
+
// C. If the used flex basis is content or depends on its available space,
// and the flex container is being sized under a min-content or max-content
// constraint (e.g. when performing automatic table layout [CSS21]),
diff --git a/src/style/compact_length.rs b/src/style/compact_length.rs
index 93c85c981..173ccf090 100644
--- a/src/style/compact_length.rs
+++ b/src/style/compact_length.rs
@@ -227,6 +227,8 @@ impl CompactLength {
pub const MAX_CONTENT_TAG: usize = 0b00001111;
/// The tag indicating a fit-content value with px limit
pub const FIT_CONTENT_PX_TAG: usize = 0b00010111;
+ /// The tag indicating the `fit-content` keyword
+ pub const FIT_CONTENT_KEYWORD_TAG: usize = 0b00100111;
/// The tag indicating a fit-content value with percent limit
pub const FIT_CONTENT_PERCENT_TAG: usize = 0b00011111;
}
@@ -302,6 +304,13 @@ impl CompactLength {
Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PX_TAG))
}
+ /// The size should be computed according to the `fit-content` keyword formula:
+ /// stretch-fit clamped between the min-content and max-content sizes.
+ #[inline(always)]
+ pub const fn fit_content_keyword() -> Self {
+ Self(CompactLengthInner::from_tag(Self::FIT_CONTENT_KEYWORD_TAG))
+ }
+
/// The size should be computed according to the "fit content" formula:
/// `max(min_content, min(max_content, limit))`
/// where:
@@ -376,13 +385,25 @@ impl CompactLength {
/// Returns true if the value is a fit-content(...) value
#[inline(always)]
pub fn is_fit_content(self) -> bool {
- matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
+ matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG | Self::FIT_CONTENT_KEYWORD_TAG)
+ }
+
+ /// Returns true if the value is the `fit-content` keyword
+ #[inline(always)]
+ pub fn is_fit_content_keyword(self) -> bool {
+ self.tag() == Self::FIT_CONTENT_KEYWORD_TAG
}
/// Returns true if the value is max-content or a fit-content(...) value
#[inline(always)]
pub fn is_max_or_fit_content(self) -> bool {
- matches!(self.tag(), Self::MAX_CONTENT_TAG | Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
+ matches!(
+ self.tag(),
+ Self::MAX_CONTENT_TAG
+ | Self::FIT_CONTENT_PX_TAG
+ | Self::FIT_CONTENT_PERCENT_TAG
+ | Self::FIT_CONTENT_KEYWORD_TAG
+ )
}
/// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
@@ -394,6 +415,7 @@ impl CompactLength {
self.tag(),
CompactLength::AUTO_TAG
| CompactLength::MAX_CONTENT_TAG
+ | CompactLength::FIT_CONTENT_KEYWORD_TAG
| CompactLength::FIT_CONTENT_PX_TAG
| CompactLength::FIT_CONTENT_PERCENT_TAG
)
@@ -412,6 +434,7 @@ impl CompactLength {
self.tag(),
Self::AUTO_TAG
| Self::MIN_CONTENT_TAG
+ | Self::FIT_CONTENT_KEYWORD_TAG
| Self::MAX_CONTENT_TAG
| Self::FIT_CONTENT_PX_TAG
| Self::FIT_CONTENT_PERCENT_TAG
@@ -528,6 +551,7 @@ impl<'de> serde::Deserialize<'de> for CompactLength {
| CompactLength::AUTO_TAG
| CompactLength::MIN_CONTENT_TAG
| CompactLength::MAX_CONTENT_TAG
+ | CompactLength::FIT_CONTENT_KEYWORD_TAG
| CompactLength::FIT_CONTENT_PX_TAG
| CompactLength::FIT_CONTENT_PERCENT_TAG
| CompactLength::FR_TAG
diff --git a/src/style/dimension.rs b/src/style/dimension.rs
index 1307b194f..23781ff90 100644
--- a/src/style/dimension.rs
+++ b/src/style/dimension.rs
@@ -1,7 +1,9 @@
//! Style types for representing lengths / sizes
use super::CompactLength;
use crate::geometry::Rect;
-use crate::style_helpers::{FromLength, FromPercent, TaffyAuto, TaffyZero};
+use crate::style_helpers::{
+ FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
+};
#[cfg(feature = "parse")]
use crate::util::parse::{from_str_from_css, parse_css_str_entirely, CssParseResult, FromCss, Parser, Token};
@@ -159,6 +161,12 @@ impl LengthPercentageAuto {
Self(CompactLength::auto())
}
+ /// The `fit-content` keyword.
+ #[inline(always)]
+ pub const fn fit_content_keyword() -> Self {
+ Self(CompactLength::fit_content_keyword())
+ }
+
/// A `calc()` value. The value passed here is treated as an opaque handle to
/// the actual calc representation and may be a pointer, index, etc.
///
@@ -213,7 +221,17 @@ impl<'de> serde::Deserialize<'de> for LengthPercentageAuto {
{
let inner = CompactLength::deserialize(deserializer)?;
// Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
- if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG | CompactLength::AUTO_TAG) {
+ if matches!(
+ inner.tag(),
+ CompactLength::LENGTH_TAG
+ | CompactLength::PERCENT_TAG
+ | CompactLength::AUTO_TAG
+ | CompactLength::MIN_CONTENT_TAG
+ | CompactLength::MAX_CONTENT_TAG
+ | CompactLength::FIT_CONTENT_PX_TAG
+ | CompactLength::FIT_CONTENT_PERCENT_TAG
+ | CompactLength::FIT_CONTENT_KEYWORD_TAG
+ ) {
Ok(Self(inner))
} else {
Err(serde::de::Error::custom("Invalid tag"))
@@ -233,6 +251,17 @@ impl TaffyZero for Dimension {
impl TaffyAuto for Dimension {
const AUTO: Self = Self(CompactLength::AUTO);
}
+impl TaffyMinContent for Dimension {
+ const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
+}
+impl TaffyMaxContent for Dimension {
+ const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
+}
+impl TaffyFitContent for Dimension {
+ fn fit_content(argument: LengthPercentage) -> Self {
+ Self(CompactLength::fit_content(argument))
+ }
+}
impl FromLength for Dimension {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::length(value.into())
@@ -291,6 +320,12 @@ impl Dimension {
Self(CompactLength::auto())
}
+ /// The `fit-content` keyword.
+ #[inline(always)]
+ pub const fn fit_content_keyword() -> Self {
+ Self(CompactLength::fit_content_keyword())
+ }
+
/// A `calc()` value. The value passed here is treated as an opaque handle to
/// the actual calc representation and may be a pointer, index, etc.
///
@@ -328,6 +363,78 @@ impl Dimension {
self.0.is_auto()
}
+ /// Returns true if value is any intrinsic size keyword/function.
+ #[inline(always)]
+ pub fn is_intrinsic(self) -> bool {
+ self.0.is_intrinsic() && !self.is_auto()
+ }
+
+ /// Returns true if value is min-content.
+ #[inline(always)]
+ pub fn is_min_content(self) -> bool {
+ self.0.is_min_content()
+ }
+
+ /// Returns true if value is max-content.
+ #[inline(always)]
+ pub fn is_max_content(self) -> bool {
+ self.0.is_max_content()
+ }
+
+ /// Returns true if value is any fit-content keyword/function.
+ #[inline(always)]
+ pub fn is_fit_content(self) -> bool {
+ self.0.is_fit_content()
+ }
+
+ /// Returns true if value is the `fit-content` keyword.
+ #[inline(always)]
+ pub fn is_fit_content_keyword(self) -> bool {
+ self.0.is_fit_content_keyword()
+ }
+
+ /// Returns whether the value can be resolved using `Self::definite_value`.
+ #[inline(always)]
+ pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
+ match self.0.tag() {
+ CompactLength::LENGTH_TAG => true,
+ CompactLength::PERCENT_TAG => parent_size.is_some(),
+ #[cfg(feature = "calc")]
+ _ if self.0.is_calc() => parent_size.is_some(),
+ _ => false,
+ }
+ }
+
+ /// Resolve fixed point values directly and percentages against the parent size.
+ #[inline(always)]
+ pub fn definite_value(
+ self,
+ parent_size: Option<f32>,
+ calc_resolver: impl Fn(*const (), f32) -> f32,
+ ) -> Option<f32> {
+ match self.0.tag() {
+ CompactLength::LENGTH_TAG => Some(self.0.value()),
+ CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
+ #[cfg(feature = "calc")]
+ _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
+ _ => None,
+ }
+ }
+
+ /// Resolve the explicit limit used by `fit-content(<length-percentage>)`.
+ #[inline(always)]
+ pub fn definite_limit(
+ self,
+ parent_size: Option<f32>,
+ calc_resolver: impl Fn(*const (), f32) -> f32,
+ ) -> Option<f32> {
+ match self.0.tag() {
+ CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
+ CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
+ _ => self.definite_value(parent_size, calc_resolver),
+ }
+ }
+
/// Get the raw `CompactLength` tag
pub fn tag(self) -> usize {
self.0.tag()
@@ -347,7 +454,17 @@ impl<'de> serde::Deserialize<'de> for Dimension {
{
let inner = CompactLength::deserialize(deserializer)?;
// Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
- if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG | CompactLength::AUTO_TAG) {
+ if matches!(
+ inner.tag(),
+ CompactLength::LENGTH_TAG
+ | CompactLength::PERCENT_TAG
+ | CompactLength::AUTO_TAG
+ | CompactLength::MIN_CONTENT_TAG
+ | CompactLength::MAX_CONTENT_TAG
+ | CompactLength::FIT_CONTENT_PX_TAG
+ | CompactLength::FIT_CONTENT_PERCENT_TAG
+ | CompactLength::FIT_CONTENT_KEYWORD_TAG
+ ) {
Ok(Self(inner))
} else {
Err(serde::de::Error::custom("Invalid tag"))
diff --git a/src/util/resolve.rs b/src/util/resolve.rs
index c40fec300..9f9906a7f 100644
--- a/src/util/resolve.rs
+++ b/src/util/resolve.rs
@@ -46,10 +46,16 @@ impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentageAuto {
match self.0.tag() {
CompactLength::AUTO_TAG => None,
CompactLength::LENGTH_TAG => Some(self.0.value()),
- CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()),
+ CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG => {
+ context.map(|dim| dim * self.0.value())
+ }
+ CompactLength::MIN_CONTENT_TAG
+ | CompactLength::MAX_CONTENT_TAG
+ | CompactLength::FIT_CONTENT_PX_TAG
+ | CompactLength::FIT_CONTENT_KEYWORD_TAG => None,
#[cfg(feature = "calc")]
_ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)),
- _ => unreachable!(),
+ _ => None,
}
}
}
@@ -62,10 +68,16 @@ impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
match self.0.tag() {
CompactLength::AUTO_TAG => None,
CompactLength::LENGTH_TAG => Some(self.0.value()),
- CompactLength::PERCENT_TAG => context.map(|dim| dim * self.0.value()),
+ CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG => {
+ context.map(|dim| dim * self.0.value())
+ }
+ CompactLength::MIN_CONTENT_TAG
+ | CompactLength::MAX_CONTENT_TAG
+ | CompactLength::FIT_CONTENT_PX_TAG
+ | CompactLength::FIT_CONTENT_KEYWORD_TAG => None,
#[cfg(feature = "calc")]
_ if self.0.is_calc() => context.map(|dim| calc(self.0.calc_value(), dim)),
- _ => unreachable!(),
+ _ => None,
}
}
}
@@ -213,6 +225,26 @@ mod tests {
mr_case(Dimension::from_percent(1.0), Some(-5.0), Some(-5.0));
mr_case(Dimension::from_percent(1.0), Some(50.0), Some(50.0));
}
+
+ /// Intrinsic size keywords/functions should remain unresolved in the generic numeric resolver.
+ #[test]
+ fn intrinsic_sizes_do_not_resolve_to_numeric_values() {
+ mr_case(Dimension::MIN_CONTENT, None, None);
+ mr_case(Dimension::MIN_CONTENT, Some(50.0), None);
+ mr_case(Dimension::MAX_CONTENT, None, None);
+ mr_case(Dimension::MAX_CONTENT, Some(50.0), None);
+ mr_case(Dimension::fit_content_keyword(), None, None);
+ mr_case(Dimension::fit_content_keyword(), Some(50.0), None);
+ mr_case(fit_content::<Dimension>(length(12.0)), None, None);
+ mr_case(fit_content::<Dimension>(length(12.0)), Some(50.0), None);
+ }
+
+ /// `fit-content(percentage)` carries the percentage limit until the algorithm consumes it.
+ #[test]
+ fn fit_content_percent_resolves_its_limit() {
+ mr_case(fit_content::<Dimension>(percent(0.5)), None, None);
+ mr_case(fit_content::<Dimension>(percent(0.5)), Some(80.0), Some(40.0));
+ }
}
mod maybe_resolve_size_dimension {