Skip to main content

taffy/style/
dimension.rs

1//! Style types for representing lengths / sizes
2use super::CompactLength;
3use crate::geometry::Rect;
4use crate::style_helpers::{FromLength, FromPercent, TaffyAuto, TaffyZero};
5#[cfg(feature = "parse")]
6use crate::util::parse::{from_str_from_css, CssParseResult, FromCss, Parser, Token};
7
8/// A unit of linear measurement
9///
10/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
11#[derive(Copy, Clone, PartialEq, Debug)]
12#[cfg_attr(feature = "serde", derive(Serialize))]
13pub struct LengthPercentage(pub(crate) CompactLength);
14impl TaffyZero for LengthPercentage {
15    const ZERO: Self = Self(CompactLength::ZERO);
16}
17impl FromLength for LengthPercentage {
18    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
19        Self::length(value.into() as f32)
20    }
21}
22impl FromPercent for LengthPercentage {
23    fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
24        Self::percent(value.into() as f32)
25    }
26}
27
28#[cfg(feature = "parse")]
29impl FromCss for LengthPercentage {
30    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
31        match parser.next()?.clone() {
32            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
33            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
34            token => Err(parser.new_unexpected_token_error(token))?,
35        }
36    }
37}
38#[cfg(feature = "parse")]
39from_str_from_css!(LengthPercentage);
40
41impl LengthPercentage {
42    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
43    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
44    #[inline(always)]
45    pub const fn length(val: f32) -> Self {
46        Self(CompactLength::length(val))
47    }
48
49    /// A percentage length relative to the size of the containing block.
50    ///
51    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
52    #[inline(always)]
53    pub const fn percent(val: f32) -> Self {
54        Self(CompactLength::percent(val))
55    }
56
57    /// A `calc()` value. The value passed here is treated as an opaque handle to
58    /// the actual calc representation and may be a pointer, index, etc.
59    ///
60    /// The low 3 bits are used as a tag value and will be returned as 0.
61    #[inline(always)]
62    #[cfg(feature = "calc")]
63    pub fn calc(ptr: *const ()) -> Self {
64        Self(CompactLength::calc(ptr))
65    }
66
67    /// Create a LengthPercentage from a raw `CompactLength`.
68    /// # Safety
69    /// CompactLength must represent a valid variant for LengthPercentage
70    #[allow(unsafe_code)]
71    pub const unsafe fn from_raw(val: CompactLength) -> Self {
72        Self(val)
73    }
74
75    /// Get the underlying `CompactLength` representation of the value
76    pub const fn into_raw(self) -> CompactLength {
77        self.0
78    }
79
80    /// Expand the compact representation into an [`ExpandedLengthPercentage`] enum.
81    ///
82    /// This is useful when integrating with other libraries (e.g. for style inspection or
83    /// serialization) as it allows the value to be pattern-matched without having to work
84    /// with the raw [`CompactLength`] tagged-pointer representation directly.
85    pub fn expand(self) -> ExpandedLengthPercentage {
86        match self.0.tag() {
87            CompactLength::LENGTH_TAG => ExpandedLengthPercentage::Length(self.0.value()),
88            CompactLength::PERCENT_TAG => ExpandedLengthPercentage::Percent(self.0.value()),
89            #[cfg(feature = "calc")]
90            _ if self.0.is_calc() => ExpandedLengthPercentage::Calc(self.0.calc_value()),
91            _ => unreachable!("LengthPercentage contains a value with an invalid tag"),
92        }
93    }
94}
95
96/// The expanded, non-compact representation of a [`LengthPercentage`].
97///
98/// Obtained via [`LengthPercentage::expand`]. Can be converted back into a [`LengthPercentage`]
99/// using the [`From`] implementation.
100#[derive(Copy, Clone, PartialEq, Debug)]
101pub enum ExpandedLengthPercentage {
102    /// An absolute length (see [`LengthPercentage::length`])
103    Length(f32),
104    /// A percentage length (see [`LengthPercentage::percent`])
105    Percent(f32),
106    /// A `calc()` value (see [`LengthPercentage::calc`]). The pointer is an opaque handle to the
107    /// calc representation, exactly as passed to the constructor.
108    #[cfg(feature = "calc")]
109    Calc(*const ()),
110}
111
112impl From<LengthPercentage> for ExpandedLengthPercentage {
113    fn from(value: LengthPercentage) -> Self {
114        value.expand()
115    }
116}
117
118impl From<ExpandedLengthPercentage> for LengthPercentage {
119    fn from(value: ExpandedLengthPercentage) -> Self {
120        match value {
121            ExpandedLengthPercentage::Length(val) => Self::length(val),
122            ExpandedLengthPercentage::Percent(val) => Self::percent(val),
123            #[cfg(feature = "calc")]
124            ExpandedLengthPercentage::Calc(ptr) => Self::calc(ptr),
125        }
126    }
127}
128
129#[cfg(feature = "serde")]
130impl<'de> serde::Deserialize<'de> for LengthPercentage {
131    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132    where
133        D: serde::Deserializer<'de>,
134    {
135        let inner = CompactLength::deserialize(deserializer)?;
136        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
137        if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG) {
138            Ok(Self(inner))
139        } else {
140            Err(serde::de::Error::custom("Invalid tag"))
141        }
142    }
143}
144
145/// A unit of linear measurement
146///
147/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
148#[derive(Copy, Clone, PartialEq, Debug)]
149#[cfg_attr(feature = "serde", derive(Serialize))]
150pub struct LengthPercentageAuto(pub(crate) CompactLength);
151impl TaffyZero for LengthPercentageAuto {
152    const ZERO: Self = Self(CompactLength::ZERO);
153}
154impl TaffyAuto for LengthPercentageAuto {
155    const AUTO: Self = Self(CompactLength::AUTO);
156}
157impl FromLength for LengthPercentageAuto {
158    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
159        Self::length(value.into() as f32)
160    }
161}
162impl FromPercent for LengthPercentageAuto {
163    fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
164        Self::percent(value.into() as f32)
165    }
166}
167impl From<LengthPercentage> for LengthPercentageAuto {
168    fn from(input: LengthPercentage) -> Self {
169        Self(input.0)
170    }
171}
172
173#[cfg(feature = "parse")]
174impl FromCss for LengthPercentageAuto {
175    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
176        match parser.next()?.clone() {
177            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
178            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
179            Token::Ident(ident) if ident == "auto" => Ok(Self::auto()),
180            token => Err(parser.new_unexpected_token_error(token))?,
181        }
182    }
183}
184#[cfg(feature = "parse")]
185from_str_from_css!(LengthPercentageAuto);
186
187impl LengthPercentageAuto {
188    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
189    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
190    #[inline(always)]
191    pub const fn length(val: f32) -> Self {
192        Self(CompactLength::length(val))
193    }
194
195    /// A percentage length relative to the size of the containing block.
196    ///
197    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
198    #[inline(always)]
199    pub const fn percent(val: f32) -> Self {
200        Self(CompactLength::percent(val))
201    }
202
203    /// The dimension should be automatically computed according to algorithm-specific rules
204    /// regarding the default size of boxes.
205    #[inline(always)]
206    pub const fn auto() -> Self {
207        Self(CompactLength::auto())
208    }
209
210    /// A `calc()` value. The value passed here is treated as an opaque handle to
211    /// the actual calc representation and may be a pointer, index, etc.
212    ///
213    /// The low 3 bits are used as a tag value and will be returned as 0.
214    #[inline]
215    #[cfg(feature = "calc")]
216    pub fn calc(ptr: *const ()) -> Self {
217        Self(CompactLength::calc(ptr))
218    }
219
220    /// Create a LengthPercentageAuto from a raw `CompactLength`.
221    /// # Safety
222    /// CompactLength must represent a valid variant for LengthPercentageAuto
223    #[allow(unsafe_code)]
224    pub const unsafe fn from_raw(val: CompactLength) -> Self {
225        Self(val)
226    }
227
228    /// Get the underlying `CompactLength` representation of the value
229    pub const fn into_raw(self) -> CompactLength {
230        self.0
231    }
232
233    /// Returns:
234    ///   - Some(length) for Length variants
235    ///   - Some(resolved) using the provided context for Percent variants
236    ///   - None for Auto variants
237    #[inline(always)]
238    pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(*const (), f32) -> f32) -> Option<f32> {
239        match self.0.tag() {
240            CompactLength::LENGTH_TAG => Some(self.0.value()),
241            CompactLength::PERCENT_TAG => Some(context * self.0.value()),
242            CompactLength::AUTO_TAG => None,
243            #[cfg(feature = "calc")]
244            _ if self.0.is_calc() => Some(calc_resolver(self.0.calc_value(), context)),
245            _ => unreachable!("LengthPercentageAuto values cannot be constructed with other tags"),
246        }
247    }
248
249    /// Returns true if value is LengthPercentageAuto::Auto
250    #[inline(always)]
251    pub fn is_auto(self) -> bool {
252        self.0.is_auto()
253    }
254
255    /// Expand the compact representation into an [`ExpandedLengthPercentageAuto`] enum.
256    ///
257    /// This is useful when integrating with other libraries (e.g. for style inspection or
258    /// serialization) as it allows the value to be pattern-matched without having to work
259    /// with the raw [`CompactLength`] tagged-pointer representation directly.
260    pub fn expand(self) -> ExpandedLengthPercentageAuto {
261        match self.0.tag() {
262            CompactLength::LENGTH_TAG => ExpandedLengthPercentageAuto::Length(self.0.value()),
263            CompactLength::PERCENT_TAG => ExpandedLengthPercentageAuto::Percent(self.0.value()),
264            CompactLength::AUTO_TAG => ExpandedLengthPercentageAuto::Auto,
265            #[cfg(feature = "calc")]
266            _ if self.0.is_calc() => ExpandedLengthPercentageAuto::Calc(self.0.calc_value()),
267            _ => unreachable!("LengthPercentageAuto contains a value with an invalid tag"),
268        }
269    }
270}
271
272/// The expanded, non-compact representation of a [`LengthPercentageAuto`].
273///
274/// Obtained via [`LengthPercentageAuto::expand`]. Can be converted back into a
275/// [`LengthPercentageAuto`] using the [`From`] implementation.
276#[derive(Copy, Clone, PartialEq, Debug)]
277pub enum ExpandedLengthPercentageAuto {
278    /// An absolute length (see [`LengthPercentageAuto::length`])
279    Length(f32),
280    /// A percentage length (see [`LengthPercentageAuto::percent`])
281    Percent(f32),
282    /// The automatic keyword (see [`LengthPercentageAuto::auto`])
283    Auto,
284    /// A `calc()` value (see [`LengthPercentageAuto::calc`]). The pointer is an opaque handle to
285    /// the calc representation, exactly as passed to the constructor.
286    #[cfg(feature = "calc")]
287    Calc(*const ()),
288}
289
290impl From<LengthPercentageAuto> for ExpandedLengthPercentageAuto {
291    fn from(value: LengthPercentageAuto) -> Self {
292        value.expand()
293    }
294}
295
296impl From<ExpandedLengthPercentageAuto> for LengthPercentageAuto {
297    fn from(value: ExpandedLengthPercentageAuto) -> Self {
298        match value {
299            ExpandedLengthPercentageAuto::Length(val) => Self::length(val),
300            ExpandedLengthPercentageAuto::Percent(val) => Self::percent(val),
301            ExpandedLengthPercentageAuto::Auto => Self::auto(),
302            #[cfg(feature = "calc")]
303            ExpandedLengthPercentageAuto::Calc(ptr) => Self::calc(ptr),
304        }
305    }
306}
307
308#[cfg(feature = "serde")]
309impl<'de> serde::Deserialize<'de> for LengthPercentageAuto {
310    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
311    where
312        D: serde::Deserializer<'de>,
313    {
314        let inner = CompactLength::deserialize(deserializer)?;
315        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
316        if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG | CompactLength::AUTO_TAG) {
317            Ok(Self(inner))
318        } else {
319            Err(serde::de::Error::custom("Invalid tag"))
320        }
321    }
322}
323
324/// A unit of linear measurement
325///
326/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
327#[derive(Copy, Clone, PartialEq, Debug)]
328#[cfg_attr(feature = "serde", derive(Serialize))]
329pub struct Dimension(pub(crate) CompactLength);
330impl TaffyZero for Dimension {
331    const ZERO: Self = Self(CompactLength::ZERO);
332}
333impl TaffyAuto for Dimension {
334    const AUTO: Self = Self(CompactLength::AUTO);
335}
336impl FromLength for Dimension {
337    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
338        Self::length(value.into() as f32)
339    }
340}
341impl FromPercent for Dimension {
342    fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
343        Self::percent(value.into() as f32)
344    }
345}
346impl From<LengthPercentage> for Dimension {
347    fn from(input: LengthPercentage) -> Self {
348        Self(input.0)
349    }
350}
351impl From<LengthPercentageAuto> for Dimension {
352    fn from(input: LengthPercentageAuto) -> Self {
353        Self(input.0)
354    }
355}
356
357#[cfg(feature = "parse")]
358impl FromCss for Dimension {
359    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
360        let token = parser.next()?.clone();
361        match token {
362            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
363            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
364            Token::Ident(ref ident) => match ident.as_ref() {
365                "auto" => Ok(Self::auto()),
366                "min-content" => Ok(Self::min_content()),
367                "max-content" => Ok(Self::max_content()),
368                "fit-content" => Ok(Self::fit_content()),
369                "stretch" => Ok(Self::stretch()),
370                "content" => Ok(Self::content()),
371                _ => Err(parser.new_unexpected_token_error(token))?,
372            },
373            Token::Function(ref name) if name.as_ref() == "fit-content" => parser.parse_nested_block(|parser| {
374                let token = parser.next()?.clone();
375                match token {
376                    Token::Percentage { unit_value, .. } => Ok(Self::fit_content_percent(unit_value)),
377                    Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::fit_content_px(value)),
378                    token => Err(parser.new_unexpected_token_error(token))?,
379                }
380            }),
381            token => Err(parser.new_unexpected_token_error(token))?,
382        }
383    }
384}
385#[cfg(feature = "parse")]
386from_str_from_css!(Dimension);
387
388impl Dimension {
389    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
390    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
391    #[inline(always)]
392    pub const fn length(val: f32) -> Self {
393        Self(CompactLength::length(val))
394    }
395
396    /// A percentage length relative to the size of the containing block.
397    ///
398    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
399    #[inline(always)]
400    pub const fn percent(val: f32) -> Self {
401        Self(CompactLength::percent(val))
402    }
403
404    /// The dimension should be automatically computed according to algorithm-specific rules
405    /// regarding the default size of boxes.
406    #[inline(always)]
407    pub const fn auto() -> Self {
408        Self(CompactLength::auto())
409    }
410
411    /// The size should be the "min-content" size.
412    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
413    #[inline(always)]
414    pub const fn min_content() -> Self {
415        Self(CompactLength::min_content())
416    }
417
418    /// The size should be the "max-content" size.
419    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
420    #[inline(always)]
421    pub const fn max_content() -> Self {
422        Self(CompactLength::max_content())
423    }
424
425    /// The size should be computed according to the "fit content" formula:
426    ///    `max(min_content, min(max_content, stretch))`
427    /// where `stretch` is the size the box would take if it filled the available space
428    #[inline(always)]
429    pub const fn fit_content() -> Self {
430        Self(CompactLength::fit_content_keyword())
431    }
432
433    /// The size should be computed according to the "fit content" formula:
434    ///    `max(min_content, min(max_content, limit))`
435    /// where `limit` is a LENGTH value
436    #[inline(always)]
437    pub const fn fit_content_px(limit: f32) -> Self {
438        Self(CompactLength::fit_content_px(limit))
439    }
440
441    /// The size should be the "stretch-fit" size: the size the box would take
442    /// if it filled the available space
443    /// (<https://www.w3.org/TR/css-sizing-4/#stretch-fit-sizing>)
444    #[inline(always)]
445    pub const fn stretch() -> Self {
446        Self(CompactLength::stretch())
447    }
448
449    /// The size should be an automatic size based on the box's content
450    /// (<https://www.w3.org/TR/css-flexbox-1/#valdef-flex-basis-content>)
451    ///
452    /// This keyword is only valid for `flex-basis`. In any other context it behaves as [`auto`](Self::auto).
453    #[inline(always)]
454    pub const fn content() -> Self {
455        Self(CompactLength::content())
456    }
457
458    /// The size should be computed according to the "fit content" formula:
459    ///    `max(min_content, min(max_content, limit))`
460    /// where `limit` is a PERCENTAGE value
461    #[inline(always)]
462    pub const fn fit_content_percent(limit: f32) -> Self {
463        Self(CompactLength::fit_content_percent(limit))
464    }
465
466    /// A `calc()` value. The value passed here is treated as an opaque handle to
467    /// the actual calc representation and may be a pointer, index, etc.
468    ///
469    /// The low 3 bits are used as a tag value and will be returned as 0.
470    #[inline]
471    #[cfg(feature = "calc")]
472    pub fn calc(ptr: *const ()) -> Self {
473        Self(CompactLength::calc(ptr))
474    }
475
476    /// Create a LengthPercentageAuto from a raw `CompactLength`.
477    /// # Safety
478    /// CompactLength must represent a valid variant for LengthPercentageAuto
479    #[allow(unsafe_code)]
480    pub const unsafe fn from_raw(val: CompactLength) -> Self {
481        Self(val)
482    }
483
484    /// Get the underlying `CompactLength` representation of the value
485    pub const fn into_raw(self) -> CompactLength {
486        self.0
487    }
488
489    /// Get Length value if value is Length variant
490    #[cfg(feature = "grid")]
491    pub fn into_option(self) -> Option<f32> {
492        match self.0.tag() {
493            CompactLength::LENGTH_TAG => Some(self.0.value()),
494            _ => None,
495        }
496    }
497    /// Returns true if value is Auto
498    #[inline(always)]
499    pub fn is_auto(self) -> bool {
500        self.0.is_auto()
501    }
502
503    /// Returns true if value is min-content, max-content, fit-content, fit-content(...), or stretch
504    #[inline(always)]
505    pub fn is_sizing_keyword(self) -> bool {
506        self.0.is_sizing_keyword()
507    }
508
509    /// Returns true if value is the stretch keyword
510    #[inline(always)]
511    pub fn is_stretch(self) -> bool {
512        self.0.tag() == CompactLength::STRETCH_TAG
513    }
514
515    /// Returns true if value is the content keyword
516    #[inline(always)]
517    pub fn is_content(self) -> bool {
518        self.0.is_content()
519    }
520
521    /// Get the raw `CompactLength` tag
522    pub fn tag(self) -> usize {
523        self.0.tag()
524    }
525
526    /// Get the raw `CompactLength` value for non-calc variants that have a numeric parameter
527    pub fn value(self) -> f32 {
528        self.0.value()
529    }
530
531    /// Expand the compact representation into an [`ExpandedDimension`] enum.
532    ///
533    /// This is useful when integrating with other libraries (e.g. for style inspection or
534    /// serialization) as it allows the value to be pattern-matched without having to work
535    /// with the raw [`CompactLength`] tagged-pointer representation directly.
536    pub fn expand(self) -> ExpandedDimension {
537        match self.0.tag() {
538            CompactLength::LENGTH_TAG => ExpandedDimension::Length(self.0.value()),
539            CompactLength::PERCENT_TAG => ExpandedDimension::Percent(self.0.value()),
540            CompactLength::AUTO_TAG => ExpandedDimension::Auto,
541            CompactLength::MIN_CONTENT_TAG => ExpandedDimension::MinContent,
542            CompactLength::MAX_CONTENT_TAG => ExpandedDimension::MaxContent,
543            CompactLength::FIT_CONTENT_PX_TAG => ExpandedDimension::FitContentPx(self.0.value()),
544            CompactLength::FIT_CONTENT_PERCENT_TAG => ExpandedDimension::FitContentPercent(self.0.value()),
545            CompactLength::FIT_CONTENT_KEYWORD_TAG => ExpandedDimension::FitContent,
546            CompactLength::STRETCH_TAG => ExpandedDimension::Stretch,
547            CompactLength::CONTENT_TAG => ExpandedDimension::Content,
548            #[cfg(feature = "calc")]
549            _ if self.0.is_calc() => ExpandedDimension::Calc(self.0.calc_value()),
550            _ => unreachable!("Dimension contains a value with an invalid tag"),
551        }
552    }
553}
554
555/// The expanded, non-compact representation of a [`Dimension`].
556///
557/// Obtained via [`Dimension::expand`]. Can be converted back into a [`Dimension`] using the
558/// [`From`] implementation.
559#[derive(Copy, Clone, PartialEq, Debug)]
560pub enum ExpandedDimension {
561    /// An absolute length (see [`Dimension::length`])
562    Length(f32),
563    /// A percentage length (see [`Dimension::percent`])
564    Percent(f32),
565    /// The automatic keyword (see [`Dimension::auto`])
566    Auto,
567    /// The `min-content` keyword (see [`Dimension::min_content`])
568    MinContent,
569    /// The `max-content` keyword (see [`Dimension::max_content`])
570    MaxContent,
571    /// A `fit-content(...)` value with a length limit (see [`Dimension::fit_content_px`])
572    FitContentPx(f32),
573    /// A `fit-content(...)` value with a percentage limit (see [`Dimension::fit_content_percent`])
574    FitContentPercent(f32),
575    /// The `fit-content` keyword with no limit (see [`Dimension::fit_content`])
576    FitContent,
577    /// The `stretch` keyword (see [`Dimension::stretch`])
578    Stretch,
579    /// The `content` keyword (see [`Dimension::content`])
580    Content,
581    /// A `calc()` value (see [`Dimension::calc`]). The pointer is an opaque handle to the calc
582    /// representation, exactly as passed to the constructor.
583    #[cfg(feature = "calc")]
584    Calc(*const ()),
585}
586
587impl From<Dimension> for ExpandedDimension {
588    fn from(value: Dimension) -> Self {
589        value.expand()
590    }
591}
592
593impl From<ExpandedDimension> for Dimension {
594    fn from(value: ExpandedDimension) -> Self {
595        match value {
596            ExpandedDimension::Length(val) => Self::length(val),
597            ExpandedDimension::Percent(val) => Self::percent(val),
598            ExpandedDimension::Auto => Self::auto(),
599            ExpandedDimension::MinContent => Self::min_content(),
600            ExpandedDimension::MaxContent => Self::max_content(),
601            ExpandedDimension::FitContentPx(val) => Self::fit_content_px(val),
602            ExpandedDimension::FitContentPercent(val) => Self::fit_content_percent(val),
603            ExpandedDimension::FitContent => Self::fit_content(),
604            ExpandedDimension::Stretch => Self::stretch(),
605            ExpandedDimension::Content => Self::content(),
606            #[cfg(feature = "calc")]
607            ExpandedDimension::Calc(ptr) => Self::calc(ptr),
608        }
609    }
610}
611
612#[cfg(feature = "serde")]
613impl<'de> serde::Deserialize<'de> for Dimension {
614    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
615    where
616        D: serde::Deserializer<'de>,
617    {
618        let inner = CompactLength::deserialize(deserializer)?;
619        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
620        if matches!(
621            inner.tag(),
622            CompactLength::LENGTH_TAG
623                | CompactLength::PERCENT_TAG
624                | CompactLength::AUTO_TAG
625                | CompactLength::MIN_CONTENT_TAG
626                | CompactLength::MAX_CONTENT_TAG
627                | CompactLength::FIT_CONTENT_KEYWORD_TAG
628                | CompactLength::FIT_CONTENT_PX_TAG
629                | CompactLength::FIT_CONTENT_PERCENT_TAG
630                | CompactLength::STRETCH_TAG
631                | CompactLength::CONTENT_TAG
632        ) {
633            Ok(Self(inner))
634        } else {
635            Err(serde::de::Error::custom("Invalid tag"))
636        }
637    }
638}
639
640impl Rect<Dimension> {
641    /// Create a new Rect with length values
642    #[must_use]
643    pub const fn from_length(start: f32, end: f32, top: f32, bottom: f32) -> Self {
644        Rect {
645            left: Dimension(CompactLength::length(start)),
646            right: Dimension(CompactLength::length(end)),
647            top: Dimension(CompactLength::length(top)),
648            bottom: Dimension(CompactLength::length(bottom)),
649        }
650    }
651
652    /// Create a new Rect with percentage values
653    #[must_use]
654    pub const fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
655        Rect {
656            left: Dimension(CompactLength::percent(start)),
657            right: Dimension(CompactLength::percent(end)),
658            top: Dimension(CompactLength::percent(top)),
659            bottom: Dimension(CompactLength::percent(bottom)),
660        }
661    }
662}
663
664#[cfg(test)]
665mod expand_tests {
666    use super::*;
667
668    /// A helper that produces a valid `calc()` handle for tests: a non-null pointer whose low
669    /// three bits are zero (as required by `CompactLength::calc`).
670    #[cfg(feature = "calc")]
671    fn calc_handle() -> *const () {
672        #[allow(dead_code)]
673        #[repr(align(8))]
674        struct Aligned(u64);
675        static HANDLE: Aligned = Aligned(0);
676        &HANDLE as *const Aligned as *const ()
677    }
678
679    #[test]
680    fn length_percentage_round_trips() {
681        let cases = [LengthPercentage::length(12.0), LengthPercentage::percent(0.5), LengthPercentage::ZERO];
682        for value in cases {
683            assert_eq!(LengthPercentage::from(value.expand()), value);
684            assert_eq!(ExpandedLengthPercentage::from(value), value.expand());
685        }
686        assert_eq!(LengthPercentage::length(3.0).expand(), ExpandedLengthPercentage::Length(3.0));
687        assert_eq!(LengthPercentage::percent(0.25).expand(), ExpandedLengthPercentage::Percent(0.25));
688    }
689
690    #[test]
691    fn length_percentage_auto_round_trips() {
692        let cases =
693            [LengthPercentageAuto::length(12.0), LengthPercentageAuto::percent(0.5), LengthPercentageAuto::auto()];
694        for value in cases {
695            assert_eq!(LengthPercentageAuto::from(value.expand()), value);
696        }
697        assert_eq!(LengthPercentageAuto::auto().expand(), ExpandedLengthPercentageAuto::Auto);
698    }
699
700    #[test]
701    fn dimension_round_trips_all_keywords() {
702        let cases = [
703            Dimension::length(12.0),
704            Dimension::percent(0.5),
705            Dimension::auto(),
706            Dimension::min_content(),
707            Dimension::max_content(),
708            Dimension::fit_content_px(30.0),
709            Dimension::fit_content_percent(0.75),
710            Dimension::fit_content(),
711            Dimension::stretch(),
712            Dimension::content(),
713        ];
714        for value in cases {
715            assert_eq!(Dimension::from(value.expand()), value);
716        }
717        assert_eq!(Dimension::fit_content_px(30.0).expand(), ExpandedDimension::FitContentPx(30.0));
718        assert_eq!(Dimension::content().expand(), ExpandedDimension::Content);
719    }
720
721    #[cfg(feature = "calc")]
722    #[test]
723    fn calc_round_trips() {
724        let handle = calc_handle();
725        assert_eq!(LengthPercentage::calc(handle).expand(), ExpandedLengthPercentage::Calc(handle));
726        assert_eq!(LengthPercentage::from(ExpandedLengthPercentage::Calc(handle)), LengthPercentage::calc(handle));
727        assert_eq!(Dimension::calc(handle).expand(), ExpandedDimension::Calc(handle));
728        assert_eq!(LengthPercentageAuto::calc(handle).expand(), ExpandedLengthPercentageAuto::Calc(handle));
729    }
730}