1use 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#[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 #[inline(always)]
45 pub const fn length(val: f32) -> Self {
46 Self(CompactLength::length(val))
47 }
48
49 #[inline(always)]
53 pub const fn percent(val: f32) -> Self {
54 Self(CompactLength::percent(val))
55 }
56
57 #[inline(always)]
62 #[cfg(feature = "calc")]
63 pub fn calc(ptr: *const ()) -> Self {
64 Self(CompactLength::calc(ptr))
65 }
66
67 #[allow(unsafe_code)]
71 pub const unsafe fn from_raw(val: CompactLength) -> Self {
72 Self(val)
73 }
74
75 pub const fn into_raw(self) -> CompactLength {
77 self.0
78 }
79
80 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#[derive(Copy, Clone, PartialEq, Debug)]
101pub enum ExpandedLengthPercentage {
102 Length(f32),
104 Percent(f32),
106 #[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 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#[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 #[inline(always)]
191 pub const fn length(val: f32) -> Self {
192 Self(CompactLength::length(val))
193 }
194
195 #[inline(always)]
199 pub const fn percent(val: f32) -> Self {
200 Self(CompactLength::percent(val))
201 }
202
203 #[inline(always)]
206 pub const fn auto() -> Self {
207 Self(CompactLength::auto())
208 }
209
210 #[inline]
215 #[cfg(feature = "calc")]
216 pub fn calc(ptr: *const ()) -> Self {
217 Self(CompactLength::calc(ptr))
218 }
219
220 #[allow(unsafe_code)]
224 pub const unsafe fn from_raw(val: CompactLength) -> Self {
225 Self(val)
226 }
227
228 pub const fn into_raw(self) -> CompactLength {
230 self.0
231 }
232
233 #[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 #[inline(always)]
251 pub fn is_auto(self) -> bool {
252 self.0.is_auto()
253 }
254
255 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#[derive(Copy, Clone, PartialEq, Debug)]
277pub enum ExpandedLengthPercentageAuto {
278 Length(f32),
280 Percent(f32),
282 Auto,
284 #[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 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#[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 #[inline(always)]
392 pub const fn length(val: f32) -> Self {
393 Self(CompactLength::length(val))
394 }
395
396 #[inline(always)]
400 pub const fn percent(val: f32) -> Self {
401 Self(CompactLength::percent(val))
402 }
403
404 #[inline(always)]
407 pub const fn auto() -> Self {
408 Self(CompactLength::auto())
409 }
410
411 #[inline(always)]
414 pub const fn min_content() -> Self {
415 Self(CompactLength::min_content())
416 }
417
418 #[inline(always)]
421 pub const fn max_content() -> Self {
422 Self(CompactLength::max_content())
423 }
424
425 #[inline(always)]
429 pub const fn fit_content() -> Self {
430 Self(CompactLength::fit_content_keyword())
431 }
432
433 #[inline(always)]
437 pub const fn fit_content_px(limit: f32) -> Self {
438 Self(CompactLength::fit_content_px(limit))
439 }
440
441 #[inline(always)]
445 pub const fn stretch() -> Self {
446 Self(CompactLength::stretch())
447 }
448
449 #[inline(always)]
454 pub const fn content() -> Self {
455 Self(CompactLength::content())
456 }
457
458 #[inline(always)]
462 pub const fn fit_content_percent(limit: f32) -> Self {
463 Self(CompactLength::fit_content_percent(limit))
464 }
465
466 #[inline]
471 #[cfg(feature = "calc")]
472 pub fn calc(ptr: *const ()) -> Self {
473 Self(CompactLength::calc(ptr))
474 }
475
476 #[allow(unsafe_code)]
480 pub const unsafe fn from_raw(val: CompactLength) -> Self {
481 Self(val)
482 }
483
484 pub const fn into_raw(self) -> CompactLength {
486 self.0
487 }
488
489 #[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 #[inline(always)]
499 pub fn is_auto(self) -> bool {
500 self.0.is_auto()
501 }
502
503 #[inline(always)]
505 pub fn is_sizing_keyword(self) -> bool {
506 self.0.is_sizing_keyword()
507 }
508
509 #[inline(always)]
511 pub fn is_stretch(self) -> bool {
512 self.0.tag() == CompactLength::STRETCH_TAG
513 }
514
515 #[inline(always)]
517 pub fn is_content(self) -> bool {
518 self.0.is_content()
519 }
520
521 pub fn tag(self) -> usize {
523 self.0.tag()
524 }
525
526 pub fn value(self) -> f32 {
528 self.0.value()
529 }
530
531 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#[derive(Copy, Clone, PartialEq, Debug)]
560pub enum ExpandedDimension {
561 Length(f32),
563 Percent(f32),
565 Auto,
567 MinContent,
569 MaxContent,
571 FitContentPx(f32),
573 FitContentPercent(f32),
575 FitContent,
577 Stretch,
579 Content,
581 #[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 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 #[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 #[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 #[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}