1//! Style types for representing lengths / sizes
2use super::CompactLength;
3use crate::geometry::Rect;
4use crate::style_helpers::{
5 FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
6};
7#[cfg(feature = "parse")]
8use crate::util::parse::{from_str_from_css, parse_css_str_entirely, CssParseResult, FromCss, Parser, Token};
9
10/// A unit of linear measurement
11///
12/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
13#[derive(Copy, Clone, PartialEq, Debug)]
14#[cfg_attr(feature = "serde", derive(Serialize))]
15pub struct LengthPercentage(pub(crate) CompactLength);
16impl TaffyZero for LengthPercentage {
17 const ZERO: Self = Self(CompactLength::ZERO);
18}
19impl FromLength for LengthPercentage {
20 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
21 Self::length(value.into())
22 }
23}
24impl FromPercent for LengthPercentage {
25 fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
26 Self::percent(value.into())
27 }
28}
29
30#[cfg(feature = "parse")]
31impl FromCss for LengthPercentage {
32 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
33 match parser.next()?.clone() {
34 Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
35 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
36 token => Err(parser.new_unexpected_token_error(token))?,
37 }
38 }
39}
40#[cfg(feature = "parse")]
41from_str_from_css!(LengthPercentage);
42
43impl LengthPercentage {
44 /// An absolute length in some abstract units. Users of Taffy may define what they correspond
45 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
46 #[inline(always)]
47 pub const fn length(val: f32) -> Self {
48 Self(CompactLength::length(val))
49 }
50
51 /// A percentage length relative to the size of the containing block.
52 ///
53 /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
54 #[inline(always)]
55 pub const fn percent(val: f32) -> Self {
56 Self(CompactLength::percent(val))
57 }
58
59 /// A `calc()` value. The value passed here is treated as an opaque handle to
60 /// the actual calc representation and may be a pointer, index, etc.
61 ///
62 /// The low 3 bits are used as a tag value and will be returned as 0.
63 #[inline(always)]
64 #[cfg(feature = "calc")]
65 pub fn calc(ptr: *const ()) -> Self {
66 Self(CompactLength::calc(ptr))
67 }
68
69 /// Create a LengthPercentage from a raw `CompactLength`.
70 /// # Safety
71 /// CompactLength must represent a valid variant for LengthPercentage
72 #[allow(unsafe_code)]
73 pub const unsafe fn from_raw(val: CompactLength) -> Self {
74 Self(val)
75 }
76
77 /// Get the underlying `CompactLength` representation of the value
78 pub const fn into_raw(self) -> CompactLength {
79 self.0
80 }
81}
82
83#[cfg(feature = "serde")]
84impl<'de> serde::Deserialize<'de> for LengthPercentage {
85 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86 where
87 D: serde::Deserializer<'de>,
88 {
89 let inner = CompactLength::deserialize(deserializer)?;
90 // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
91 if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG) {
92 Ok(Self(inner))
93 } else {
94 Err(serde::de::Error::custom("Invalid tag"))
95 }
96 }
97}
98
99/// A unit of linear measurement
100///
101/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
102#[derive(Copy, Clone, PartialEq, Debug)]
103#[cfg_attr(feature = "serde", derive(Serialize))]
104pub struct LengthPercentageAuto(pub(crate) CompactLength);
105impl TaffyZero for LengthPercentageAuto {
106 const ZERO: Self = Self(CompactLength::ZERO);
107}
108impl TaffyAuto for LengthPercentageAuto {
109 const AUTO: Self = Self(CompactLength::AUTO);
110}
111impl FromLength for LengthPercentageAuto {
112 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
113 Self::length(value.into())
114 }
115}
116impl FromPercent for LengthPercentageAuto {
117 fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
118 Self::percent(value.into())
119 }
120}
121impl From<LengthPercentage> for LengthPercentageAuto {
122 fn from(input: LengthPercentage) -> Self {
123 Self(input.0)
124 }
125}
126
127#[cfg(feature = "parse")]
128impl FromCss for LengthPercentageAuto {
129 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
130 match parser.next()?.clone() {
131 Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
132 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
133 Token::Ident(ident) if ident == "auto" => Ok(Self::auto()),
134 token => Err(parser.new_unexpected_token_error(token))?,
135 }
136 }
137}
138#[cfg(feature = "parse")]
139from_str_from_css!(LengthPercentageAuto);
140
141impl LengthPercentageAuto {
142 /// An absolute length in some abstract units. Users of Taffy may define what they correspond
143 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
144 #[inline(always)]
145 pub const fn length(val: f32) -> Self {
146 Self(CompactLength::length(val))
147 }
148
149 /// A percentage length relative to the size of the containing block.
150 ///
151 /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
152 #[inline(always)]
153 pub const fn percent(val: f32) -> Self {
154 Self(CompactLength::percent(val))
155 }
156
157 /// The dimension should be automatically computed according to algorithm-specific rules
158 /// regarding the default size of boxes.
159 #[inline(always)]
160 pub const fn auto() -> Self {
161 Self(CompactLength::auto())
162 }
163
164 /// The `fit-content` keyword.
165 #[inline(always)]
166 pub const fn fit_content_keyword() -> Self {
167 Self(CompactLength::fit_content_keyword())
168 }
169
170 /// A `calc()` value. The value passed here is treated as an opaque handle to
171 /// the actual calc representation and may be a pointer, index, etc.
172 ///
173 /// The low 3 bits are used as a tag value and will be returned as 0.
174 #[inline]
175 #[cfg(feature = "calc")]
176 pub fn calc(ptr: *const ()) -> Self {
177 Self(CompactLength::calc(ptr))
178 }
179
180 /// Create a LengthPercentageAuto from a raw `CompactLength`.
181 /// # Safety
182 /// CompactLength must represent a valid variant for LengthPercentageAuto
183 #[allow(unsafe_code)]
184 pub const unsafe fn from_raw(val: CompactLength) -> Self {
185 Self(val)
186 }
187
188 /// Get the underlying `CompactLength` representation of the value
189 pub const fn into_raw(self) -> CompactLength {
190 self.0
191 }
192
193 /// Returns:
194 /// - Some(length) for Length variants
195 /// - Some(resolved) using the provided context for Percent variants
196 /// - None for Auto variants
197 #[inline(always)]
198 pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(*const (), f32) -> f32) -> Option<f32> {
199 match self.0.tag() {
200 CompactLength::LENGTH_TAG => Some(self.0.value()),
201 CompactLength::PERCENT_TAG => Some(context * self.0.value()),
202 CompactLength::AUTO_TAG => None,
203 #[cfg(feature = "calc")]
204 _ if self.0.is_calc() => Some(calc_resolver(self.0.calc_value(), context)),
205 _ => unreachable!("LengthPercentageAuto values cannot be constructed with other tags"),
206 }
207 }
208
209 /// Returns true if value is LengthPercentageAuto::Auto
210 #[inline(always)]
211 pub fn is_auto(self) -> bool {
212 self.0.is_auto()
213 }
214}
215
216#[cfg(feature = "serde")]
217impl<'de> serde::Deserialize<'de> for LengthPercentageAuto {
218 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219 where
220 D: serde::Deserializer<'de>,
221 {
222 let inner = CompactLength::deserialize(deserializer)?;
223 // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
224 if matches!(
225 inner.tag(),
226 CompactLength::LENGTH_TAG
227 | CompactLength::PERCENT_TAG
228 | CompactLength::AUTO_TAG
229 | CompactLength::MIN_CONTENT_TAG
230 | CompactLength::MAX_CONTENT_TAG
231 | CompactLength::FIT_CONTENT_PX_TAG
232 | CompactLength::FIT_CONTENT_PERCENT_TAG
233 | CompactLength::FIT_CONTENT_KEYWORD_TAG
234 ) {
235 Ok(Self(inner))
236 } else {
237 Err(serde::de::Error::custom("Invalid tag"))
238 }
239 }
240}
241
242/// A unit of linear measurement
243///
244/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
245#[derive(Copy, Clone, PartialEq, Debug)]
246#[cfg_attr(feature = "serde", derive(Serialize))]
247pub struct Dimension(pub(crate) CompactLength);
248impl TaffyZero for Dimension {
249 const ZERO: Self = Self(CompactLength::ZERO);
250}
251impl TaffyAuto for Dimension {
252 const AUTO: Self = Self(CompactLength::AUTO);
253}
254impl TaffyMinContent for Dimension {
255 const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
256}
257impl TaffyMaxContent for Dimension {
258 const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
259}
260impl TaffyFitContent for Dimension {
261 fn fit_content(argument: LengthPercentage) -> Self {
262 Self(CompactLength::fit_content(argument))
263 }
264}
265impl FromLength for Dimension {
266 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
267 Self::length(value.into())
268 }
269}
270impl FromPercent for Dimension {
271 fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
272 Self::percent(value.into())
273 }
274}
275impl From<LengthPercentage> for Dimension {
276 fn from(input: LengthPercentage) -> Self {
277 Self(input.0)
278 }
279}
280impl From<LengthPercentageAuto> for Dimension {
281 fn from(input: LengthPercentageAuto) -> Self {
282 Self(input.0)
283 }
284}
285
286#[cfg(feature = "parse")]
287impl FromCss for Dimension {
288 fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
289 match parser.next()?.clone() {
290 Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
291 Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
292 Token::Ident(ident) if ident == "auto" => Ok(Self::auto()),
293 token => Err(parser.new_unexpected_token_error(token))?,
294 }
295 }
296}
297#[cfg(feature = "parse")]
298from_str_from_css!(Dimension);
299
300impl Dimension {
301 /// An absolute length in some abstract units. Users of Taffy may define what they correspond
302 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
303 #[inline(always)]
304 pub const fn length(val: f32) -> Self {
305 Self(CompactLength::length(val))
306 }
307
308 /// A percentage length relative to the size of the containing block.
309 ///
310 /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
311 #[inline(always)]
312 pub const fn percent(val: f32) -> Self {
313 Self(CompactLength::percent(val))
314 }
315
316 /// The dimension should be automatically computed according to algorithm-specific rules
317 /// regarding the default size of boxes.
318 #[inline(always)]
319 pub const fn auto() -> Self {
320 Self(CompactLength::auto())
321 }
322
323 /// The `fit-content` keyword.
324 #[inline(always)]
325 pub const fn fit_content_keyword() -> Self {
326 Self(CompactLength::fit_content_keyword())
327 }
328
329 /// A `calc()` value. The value passed here is treated as an opaque handle to
330 /// the actual calc representation and may be a pointer, index, etc.
331 ///
332 /// The low 3 bits are used as a tag value and will be returned as 0.
333 #[inline]
334 #[cfg(feature = "calc")]
335 pub fn calc(ptr: *const ()) -> Self {
336 Self(CompactLength::calc(ptr))
337 }
338
339 /// Create a LengthPercentageAuto from a raw `CompactLength`.
340 /// # Safety
341 /// CompactLength must represent a valid variant for LengthPercentageAuto
342 #[allow(unsafe_code)]
343 pub const unsafe fn from_raw(val: CompactLength) -> Self {
344 Self(val)
345 }
346
347 /// Get the underlying `CompactLength` representation of the value
348 pub const fn into_raw(self) -> CompactLength {
349 self.0
350 }
351
352 /// Get Length value if value is Length variant
353 #[cfg(feature = "grid")]
354 pub fn into_option(self) -> Option<f32> {
355 match self.0.tag() {
356 CompactLength::LENGTH_TAG => Some(self.0.value()),
357 _ => None,
358 }
359 }
360 /// Returns true if value is Auto
361 #[inline(always)]
362 pub fn is_auto(self) -> bool {
363 self.0.is_auto()
364 }
365
366 /// Returns true if value is any intrinsic size keyword/function.
367 #[inline(always)]
368 pub fn is_intrinsic(self) -> bool {
369 self.0.is_intrinsic() && !self.is_auto()
370 }
371
372 /// Returns true if value is min-content.
373 #[inline(always)]
374 pub fn is_min_content(self) -> bool {
375 self.0.is_min_content()
376 }
377
378 /// Returns true if value is max-content.
379 #[inline(always)]
380 pub fn is_max_content(self) -> bool {
381 self.0.is_max_content()
382 }
383
384 /// Returns true if value is any fit-content keyword/function.
385 #[inline(always)]
386 pub fn is_fit_content(self) -> bool {
387 self.0.is_fit_content()
388 }
389
390 /// Returns true if value is the `fit-content` keyword.
391 #[inline(always)]
392 pub fn is_fit_content_keyword(self) -> bool {
393 self.0.is_fit_content_keyword()
394 }
395
396 /// Returns whether the value can be resolved using `Self::definite_value`.
397 #[inline(always)]
398 pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
399 match self.0.tag() {
400 CompactLength::LENGTH_TAG => true,
401 CompactLength::PERCENT_TAG => parent_size.is_some(),
402 #[cfg(feature = "calc")]
403 _ if self.0.is_calc() => parent_size.is_some(),
404 _ => false,
405 }
406 }
407
408 /// Resolve fixed point values directly and percentages against the parent size.
409 #[inline(always)]
410 pub fn definite_value(
411 self,
412 parent_size: Option<f32>,
413 calc_resolver: impl Fn(*const (), f32) -> f32,
414 ) -> Option<f32> {
415 match self.0.tag() {
416 CompactLength::LENGTH_TAG => Some(self.0.value()),
417 CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
418 #[cfg(feature = "calc")]
419 _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
420 _ => None,
421 }
422 }
423
424 /// Resolve the explicit limit used by `fit-content(<length-percentage>)`.
425 #[inline(always)]
426 pub fn definite_limit(
427 self,
428 parent_size: Option<f32>,
429 calc_resolver: impl Fn(*const (), f32) -> f32,
430 ) -> Option<f32> {
431 match self.0.tag() {
432 CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
433 CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
434 _ => self.definite_value(parent_size, calc_resolver),
435 }
436 }
437
438 /// Get the raw `CompactLength` tag
439 pub fn tag(self) -> usize {
440 self.0.tag()
441 }
442
443 /// Get the raw `CompactLength` value for non-calc variants that have a numeric parameter
444 pub fn value(self) -> f32 {
445 self.0.value()
446 }
447}
448
449#[cfg(feature = "serde")]
450impl<'de> serde::Deserialize<'de> for Dimension {
451 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
452 where
453 D: serde::Deserializer<'de>,
454 {
455 let inner = CompactLength::deserialize(deserializer)?;
456 // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
457 if matches!(
458 inner.tag(),
459 CompactLength::LENGTH_TAG
460 | CompactLength::PERCENT_TAG
461 | CompactLength::AUTO_TAG
462 | CompactLength::MIN_CONTENT_TAG
463 | CompactLength::MAX_CONTENT_TAG
464 | CompactLength::FIT_CONTENT_PX_TAG
465 | CompactLength::FIT_CONTENT_PERCENT_TAG
466 | CompactLength::FIT_CONTENT_KEYWORD_TAG
467 ) {
468 Ok(Self(inner))
469 } else {
470 Err(serde::de::Error::custom("Invalid tag"))
471 }
472 }
473}
474
475impl Rect<Dimension> {
476 /// Create a new Rect with length values
477 #[must_use]
478 pub const fn from_length(start: f32, end: f32, top: f32, bottom: f32) -> Self {
479 Rect {
480 left: Dimension(CompactLength::length(start)),
481 right: Dimension(CompactLength::length(end)),
482 top: Dimension(CompactLength::length(top)),
483 bottom: Dimension(CompactLength::length(bottom)),
484 }
485 }
486
487 /// Create a new Rect with percentage values
488 #[must_use]
489 pub const fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
490 Rect {
491 left: Dimension(CompactLength::percent(start)),
492 right: Dimension(CompactLength::percent(end)),
493 top: Dimension(CompactLength::percent(top)),
494 bottom: Dimension(CompactLength::percent(bottom)),
495 }
496 }
497}