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_plain_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_plain_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_plain_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 #[inline]
481 #[cfg(feature = "calc")]
482 pub fn fit_content_calc(ptr: *const ()) -> Self {
483 Self(CompactLength::fit_content_calc(ptr))
484 }
485
486 #[inline(always)]
488 pub fn is_fit_content_calc(self) -> bool {
489 self.0.is_fit_content_calc()
490 }
491
492 #[inline(always)]
494 #[cfg(feature = "calc")]
495 pub fn calc_value(self) -> *const () {
496 self.0.calc_value()
497 }
498
499 #[allow(unsafe_code)]
503 pub const unsafe fn from_raw(val: CompactLength) -> Self {
504 Self(val)
505 }
506
507 pub const fn into_raw(self) -> CompactLength {
509 self.0
510 }
511
512 #[cfg(feature = "grid")]
514 pub fn into_option(self) -> Option<f32> {
515 match self.0.tag() {
516 CompactLength::LENGTH_TAG => Some(self.0.value()),
517 _ => None,
518 }
519 }
520 #[inline(always)]
522 pub fn is_auto(self) -> bool {
523 self.0.is_auto()
524 }
525
526 #[inline(always)]
528 pub fn is_sizing_keyword(self) -> bool {
529 self.0.is_sizing_keyword()
530 }
531
532 #[inline(always)]
534 pub fn is_stretch(self) -> bool {
535 self.0.tag() == CompactLength::STRETCH_TAG
536 }
537
538 #[inline(always)]
540 pub fn is_content(self) -> bool {
541 self.0.is_content()
542 }
543
544 pub fn tag(self) -> usize {
546 self.0.tag()
547 }
548
549 pub fn value(self) -> f32 {
551 self.0.value()
552 }
553
554 pub fn expand(self) -> ExpandedDimension {
560 match self.0.tag() {
561 CompactLength::LENGTH_TAG => ExpandedDimension::Length(self.0.value()),
562 CompactLength::PERCENT_TAG => ExpandedDimension::Percent(self.0.value()),
563 CompactLength::AUTO_TAG => ExpandedDimension::Auto,
564 CompactLength::MIN_CONTENT_TAG => ExpandedDimension::MinContent,
565 CompactLength::MAX_CONTENT_TAG => ExpandedDimension::MaxContent,
566 CompactLength::FIT_CONTENT_PX_TAG => ExpandedDimension::FitContentPx(self.0.value()),
567 CompactLength::FIT_CONTENT_PERCENT_TAG => ExpandedDimension::FitContentPercent(self.0.value()),
568 CompactLength::FIT_CONTENT_KEYWORD_TAG => ExpandedDimension::FitContent,
569 #[cfg(feature = "calc")]
570 _ if self.0.is_fit_content_calc() => ExpandedDimension::FitContentCalc(self.0.calc_value()),
571 CompactLength::STRETCH_TAG => ExpandedDimension::Stretch,
572 CompactLength::CONTENT_TAG => ExpandedDimension::Content,
573 #[cfg(feature = "calc")]
574 _ if self.0.is_plain_calc() => ExpandedDimension::Calc(self.0.calc_value()),
575 _ => unreachable!("Dimension contains a value with an invalid tag"),
576 }
577 }
578}
579
580#[derive(Copy, Clone, PartialEq, Debug)]
585pub enum ExpandedDimension {
586 Length(f32),
588 Percent(f32),
590 Auto,
592 MinContent,
594 MaxContent,
596 FitContentPx(f32),
598 FitContentPercent(f32),
600 #[cfg(feature = "calc")]
602 FitContentCalc(*const ()),
603 FitContent,
605 Stretch,
607 Content,
609 #[cfg(feature = "calc")]
612 Calc(*const ()),
613}
614
615impl From<Dimension> for ExpandedDimension {
616 fn from(value: Dimension) -> Self {
617 value.expand()
618 }
619}
620
621impl From<ExpandedDimension> for Dimension {
622 fn from(value: ExpandedDimension) -> Self {
623 match value {
624 ExpandedDimension::Length(val) => Self::length(val),
625 ExpandedDimension::Percent(val) => Self::percent(val),
626 ExpandedDimension::Auto => Self::auto(),
627 ExpandedDimension::MinContent => Self::min_content(),
628 ExpandedDimension::MaxContent => Self::max_content(),
629 ExpandedDimension::FitContentPx(val) => Self::fit_content_px(val),
630 ExpandedDimension::FitContentPercent(val) => Self::fit_content_percent(val),
631 ExpandedDimension::FitContent => Self::fit_content(),
632 #[cfg(feature = "calc")]
633 ExpandedDimension::FitContentCalc(ptr) => Self::fit_content_calc(ptr),
634 ExpandedDimension::Stretch => Self::stretch(),
635 ExpandedDimension::Content => Self::content(),
636 #[cfg(feature = "calc")]
637 ExpandedDimension::Calc(ptr) => Self::calc(ptr),
638 }
639 }
640}
641
642#[cfg(feature = "serde")]
643impl<'de> serde::Deserialize<'de> for Dimension {
644 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
645 where
646 D: serde::Deserializer<'de>,
647 {
648 let inner = CompactLength::deserialize(deserializer)?;
649 if matches!(
651 inner.tag(),
652 CompactLength::LENGTH_TAG
653 | CompactLength::PERCENT_TAG
654 | CompactLength::AUTO_TAG
655 | CompactLength::MIN_CONTENT_TAG
656 | CompactLength::MAX_CONTENT_TAG
657 | CompactLength::FIT_CONTENT_KEYWORD_TAG
658 | CompactLength::FIT_CONTENT_PX_TAG
659 | CompactLength::FIT_CONTENT_PERCENT_TAG
660 | CompactLength::STRETCH_TAG
661 | CompactLength::CONTENT_TAG
662 ) {
663 Ok(Self(inner))
664 } else {
665 Err(serde::de::Error::custom("Invalid tag"))
666 }
667 }
668}
669
670impl Rect<Dimension> {
671 #[must_use]
673 pub const fn from_length(start: f32, end: f32, top: f32, bottom: f32) -> Self {
674 Rect {
675 left: Dimension(CompactLength::length(start)),
676 right: Dimension(CompactLength::length(end)),
677 top: Dimension(CompactLength::length(top)),
678 bottom: Dimension(CompactLength::length(bottom)),
679 }
680 }
681
682 #[must_use]
684 pub const fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
685 Rect {
686 left: Dimension(CompactLength::percent(start)),
687 right: Dimension(CompactLength::percent(end)),
688 top: Dimension(CompactLength::percent(top)),
689 bottom: Dimension(CompactLength::percent(bottom)),
690 }
691 }
692}
693
694#[cfg(test)]
695mod expand_tests {
696 use super::*;
697
698 #[cfg(feature = "calc")]
701 fn calc_handle() -> *const () {
702 #[allow(dead_code)]
703 #[repr(align(8))]
704 struct Aligned(u64);
705 static HANDLE: Aligned = Aligned(0);
706 &HANDLE as *const Aligned as *const ()
707 }
708
709 #[test]
710 fn length_percentage_round_trips() {
711 let cases = [LengthPercentage::length(12.0), LengthPercentage::percent(0.5), LengthPercentage::ZERO];
712 for value in cases {
713 assert_eq!(LengthPercentage::from(value.expand()), value);
714 assert_eq!(ExpandedLengthPercentage::from(value), value.expand());
715 }
716 assert_eq!(LengthPercentage::length(3.0).expand(), ExpandedLengthPercentage::Length(3.0));
717 assert_eq!(LengthPercentage::percent(0.25).expand(), ExpandedLengthPercentage::Percent(0.25));
718 }
719
720 #[test]
721 fn length_percentage_auto_round_trips() {
722 let cases =
723 [LengthPercentageAuto::length(12.0), LengthPercentageAuto::percent(0.5), LengthPercentageAuto::auto()];
724 for value in cases {
725 assert_eq!(LengthPercentageAuto::from(value.expand()), value);
726 }
727 assert_eq!(LengthPercentageAuto::auto().expand(), ExpandedLengthPercentageAuto::Auto);
728 }
729
730 #[test]
731 fn dimension_round_trips_all_keywords() {
732 let cases = [
733 Dimension::length(12.0),
734 Dimension::percent(0.5),
735 Dimension::auto(),
736 Dimension::min_content(),
737 Dimension::max_content(),
738 Dimension::fit_content_px(30.0),
739 Dimension::fit_content_percent(0.75),
740 Dimension::fit_content(),
741 Dimension::stretch(),
742 Dimension::content(),
743 ];
744 for value in cases {
745 assert_eq!(Dimension::from(value.expand()), value);
746 }
747 assert_eq!(Dimension::fit_content_px(30.0).expand(), ExpandedDimension::FitContentPx(30.0));
748 assert_eq!(Dimension::content().expand(), ExpandedDimension::Content);
749 }
750
751 #[cfg(feature = "calc")]
752 #[test]
753 fn calc_round_trips() {
754 let handle = calc_handle();
755 assert_eq!(LengthPercentage::calc(handle).expand(), ExpandedLengthPercentage::Calc(handle));
756 assert_eq!(LengthPercentage::from(ExpandedLengthPercentage::Calc(handle)), LengthPercentage::calc(handle));
757 assert_eq!(Dimension::calc(handle).expand(), ExpandedDimension::Calc(handle));
758 assert_eq!(Dimension::fit_content_calc(handle).expand(), ExpandedDimension::FitContentCalc(handle));
759 assert_eq!(Dimension::from(ExpandedDimension::FitContentCalc(handle)), Dimension::fit_content_calc(handle));
760 assert_eq!(LengthPercentageAuto::calc(handle).expand(), ExpandedLengthPercentageAuto::Calc(handle));
761 }
762}