1use crate::corety::{AzString, OptionF32};
14use core::fmt;
15use std::num::ParseFloatError;
16
17use crate::props::{
18 basic::{error::ParseFloatErrorWithInput, FloatValue, SizeMetric},
19 formatter::FormatAsCssValue,
20};
21
22pub const DEFAULT_FONT_SIZE: f32 = 16.0;
25
26pub const PT_TO_PX: f32 = 96.0 / 72.0;
28
29#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
35#[repr(transparent)]
36pub struct NormalizedPercentage(f32);
37
38impl NormalizedPercentage {
39 #[inline]
44 #[must_use]
45 pub const fn new(value: f32) -> Self {
46 Self(value)
47 }
48
49 #[inline]
54 #[must_use]
55 pub fn from_unnormalized(value: f32) -> Self {
56 Self(value / 100.0)
57 }
58
59 #[inline]
61 #[must_use]
62 pub const fn get(self) -> f32 {
63 self.0
64 }
65
66 #[inline]
71 #[must_use]
72 pub fn resolve(self, containing_block_size: f32) -> f32 {
73 self.0 * containing_block_size
74 }
75}
76
77impl fmt::Display for NormalizedPercentage {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}%", self.0 * 100.0)
80 }
81}
82
83#[derive(Debug, Copy, Clone, PartialEq)]
85#[repr(C)]
86pub struct CssLogicalSize {
87 pub inline_size: f32,
89 pub block_size: f32,
91}
92
93impl CssLogicalSize {
94 #[inline]
95 #[must_use]
96 pub const fn new(inline_size: f32, block_size: f32) -> Self {
97 Self {
98 inline_size,
99 block_size,
100 }
101 }
102
103 #[inline]
105 #[must_use]
106 pub const fn to_physical(self) -> PhysicalSize {
107 PhysicalSize {
108 width: self.inline_size,
109 height: self.block_size,
110 }
111 }
112}
113
114#[derive(Debug, Copy, Clone, PartialEq)]
116#[repr(C)]
117pub struct PhysicalSize {
118 pub width: f32,
119 pub height: f32,
120}
121
122impl PhysicalSize {
123 #[inline]
124 #[must_use]
125 pub const fn new(width: f32, height: f32) -> Self {
126 Self { width, height }
127 }
128
129 #[inline]
131 #[must_use]
132 pub const fn to_logical(self) -> CssLogicalSize {
133 CssLogicalSize {
134 inline_size: self.width,
135 block_size: self.height,
136 }
137 }
138}
139
140#[derive(Debug, Copy, Clone)]
156pub struct ResolutionContext {
157 pub element_font_size: f32,
159
160 pub parent_font_size: f32,
162
163 pub root_font_size: f32,
165
166 pub containing_block_size: PhysicalSize,
168
169 pub element_size: Option<PhysicalSize>,
172
173 pub vertical_writing_mode: bool,
178
179 pub viewport_size: PhysicalSize,
182}
183
184impl Default for ResolutionContext {
185 fn default() -> Self {
186 Self {
187 element_font_size: 16.0,
188 parent_font_size: 16.0,
189 root_font_size: 16.0,
190 containing_block_size: PhysicalSize::new(0.0, 0.0),
191 element_size: None,
192 viewport_size: PhysicalSize::new(0.0, 0.0),
193 vertical_writing_mode: false,
194 }
195 }
196}
197
198impl ResolutionContext {
199 #[inline]
201 #[must_use]
202 pub const fn default_const() -> Self {
203 Self {
204 element_font_size: 16.0,
205 parent_font_size: 16.0,
206 root_font_size: 16.0,
207 containing_block_size: PhysicalSize {
208 width: 0.0,
209 height: 0.0,
210 },
211 element_size: None,
212 viewport_size: PhysicalSize {
213 width: 0.0,
214 height: 0.0,
215 },
216 vertical_writing_mode: false,
217 }
218 }
219}
220
221#[derive(Debug, Copy, Clone, PartialEq, Eq)]
223pub enum PropertyContext {
224 FontSize,
226 Margin,
228 Padding,
230 Width,
232 Height,
234 BorderWidth,
236 BorderRadius,
238 Transform,
240 Other,
242}
243
244#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
246#[repr(C)]
247pub struct PixelValue {
248 pub metric: SizeMetric,
249 pub number: FloatValue,
250}
251
252impl PixelValue {
253 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
254 self.number = FloatValue::new(self.number.get() * scale_factor);
255 }
256
257 #[must_use]
263 pub const fn is_absolute(&self) -> bool {
264 matches!(
265 self.metric,
266 SizeMetric::Px | SizeMetric::Pt | SizeMetric::In | SizeMetric::Cm | SizeMetric::Mm
267 )
268 }
269
270 #[must_use]
285 pub fn to_pixels_absolute(&self) -> OptionF32 {
286 if self.is_absolute() {
287 OptionF32::Some(self.to_pixels_internal(0.0, 0.0, 0.0))
290 } else {
291 OptionF32::None
292 }
293 }
294
295 #[must_use]
312 pub fn to_pixels(&self, percent_resolve: f32, em_resolve: f32, rem_resolve: f32) -> f32 {
313 self.to_pixels_internal(percent_resolve, em_resolve, rem_resolve)
314 }
315}
316
317impl FormatAsCssValue for PixelValue {
318 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 write!(f, "{}{}", self.number, self.metric)
320 }
321}
322
323impl crate::css::PrintAsCssValue for PixelValue {
324 fn print_as_css_value(&self) -> String {
325 format!("{}{}", self.number, self.metric)
326 }
327}
328
329impl crate::codegen::format::FormatAsRustCode for PixelValue {
330 fn format_as_rust_code(&self, _tabs: usize) -> String {
331 format!(
332 "PixelValue {{ metric: {:?}, number: FloatValue::new({}) }}",
333 self.metric,
334 self.number.get()
335 )
336 }
337}
338
339impl fmt::Debug for PixelValue {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 write!(f, "{}{}", self.number, self.metric)
343 }
344}
345
346impl fmt::Display for PixelValue {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 write!(f, "{}{}", self.number, self.metric)
349 }
350}
351
352impl PixelValue {
353 #[inline]
354 #[must_use]
355 pub const fn zero() -> Self {
356 const ZERO_PX: PixelValue = PixelValue::const_px(0);
357 ZERO_PX
358 }
359
360 #[inline]
363 #[must_use]
364 pub const fn const_px(value: isize) -> Self {
365 Self::const_from_metric(SizeMetric::Px, value)
366 }
367
368 #[inline]
371 #[must_use]
372 pub const fn const_em(value: isize) -> Self {
373 Self::const_from_metric(SizeMetric::Em, value)
374 }
375
376 #[inline]
389 #[must_use]
390 pub const fn const_em_fractional(pre_comma: isize, post_comma: isize) -> Self {
391 Self::const_from_metric_fractional(SizeMetric::Em, pre_comma, post_comma)
392 }
393
394 #[inline]
397 #[must_use]
398 pub const fn const_pt(value: isize) -> Self {
399 Self::const_from_metric(SizeMetric::Pt, value)
400 }
401
402 #[inline]
404 #[must_use]
405 pub const fn const_pt_fractional(pre_comma: isize, post_comma: isize) -> Self {
406 Self::const_from_metric_fractional(SizeMetric::Pt, pre_comma, post_comma)
407 }
408
409 #[inline]
412 #[must_use]
413 pub const fn const_percent(value: isize) -> Self {
414 Self::const_from_metric(SizeMetric::Percent, value)
415 }
416
417 #[inline]
420 #[must_use]
421 pub const fn const_in(value: isize) -> Self {
422 Self::const_from_metric(SizeMetric::In, value)
423 }
424
425 #[inline]
428 #[must_use]
429 pub const fn const_cm(value: isize) -> Self {
430 Self::const_from_metric(SizeMetric::Cm, value)
431 }
432
433 #[inline]
436 #[must_use]
437 pub const fn const_mm(value: isize) -> Self {
438 Self::const_from_metric(SizeMetric::Mm, value)
439 }
440
441 #[inline]
442 #[must_use]
443 pub const fn const_from_metric(metric: SizeMetric, value: isize) -> Self {
444 Self {
445 metric,
446 number: FloatValue::const_new(value),
447 }
448 }
449
450 #[inline]
457 #[must_use]
458 pub const fn const_from_metric_fractional(
459 metric: SizeMetric,
460 pre_comma: isize,
461 post_comma: isize,
462 ) -> Self {
463 Self {
464 metric,
465 number: FloatValue::const_new_fractional(pre_comma, post_comma),
466 }
467 }
468
469 #[inline]
470 #[must_use]
471 pub fn px(value: f32) -> Self {
472 Self::from_metric(SizeMetric::Px, value)
473 }
474
475 #[inline]
476 #[must_use]
477 pub fn em(value: f32) -> Self {
478 Self::from_metric(SizeMetric::Em, value)
479 }
480
481 #[inline]
482 #[must_use]
483 pub fn inch(value: f32) -> Self {
484 Self::from_metric(SizeMetric::In, value)
485 }
486
487 #[inline]
488 #[must_use]
489 pub fn cm(value: f32) -> Self {
490 Self::from_metric(SizeMetric::Cm, value)
491 }
492
493 #[inline]
494 #[must_use]
495 pub fn mm(value: f32) -> Self {
496 Self::from_metric(SizeMetric::Mm, value)
497 }
498
499 #[inline]
500 #[must_use]
501 pub fn pt(value: f32) -> Self {
502 Self::from_metric(SizeMetric::Pt, value)
503 }
504
505 #[inline]
506 #[must_use]
507 pub fn percent(value: f32) -> Self {
508 Self::from_metric(SizeMetric::Percent, value)
509 }
510
511 #[inline]
512 #[must_use]
513 pub fn rem(value: f32) -> Self {
514 Self::from_metric(SizeMetric::Rem, value)
515 }
516
517 #[inline]
518 #[must_use]
519 pub fn from_metric(metric: SizeMetric, value: f32) -> Self {
520 Self {
521 metric,
522 number: FloatValue::new(value),
523 }
524 }
525
526 #[inline]
527 #[allow(clippy::suboptimal_flops)] #[must_use]
529 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
530 if self.metric == other.metric {
531 Self {
532 metric: self.metric,
533 number: self.number.interpolate(&other.number, t),
534 }
535 } else {
536 let self_px_interp = self.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
539 let other_px_interp =
540 other.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
541 Self::from_metric(
542 SizeMetric::Px,
543 self_px_interp + (other_px_interp - self_px_interp) * t,
544 )
545 }
546 }
547
548 #[inline]
554 #[must_use]
555 pub fn to_percent(&self) -> Option<NormalizedPercentage> {
556 match self.metric {
557 SizeMetric::Percent => Some(NormalizedPercentage::from_unnormalized(self.number.get())),
558 _ => None,
559 }
560 }
561
562 #[doc(hidden)]
568 #[inline]
569 #[must_use]
570 pub fn to_pixels_internal(
571 &self,
572 percent_resolve: f32,
573 em_resolve: f32,
574 rem_resolve: f32,
575 ) -> f32 {
576 match self.metric {
577 SizeMetric::Px => self.number.get(),
578 SizeMetric::Pt => self.number.get() * PT_TO_PX,
579 SizeMetric::In => self.number.get() * 96.0,
580 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
581 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
582 SizeMetric::Em => self.number.get() * em_resolve,
583 SizeMetric::Rem => self.number.get() * rem_resolve,
584 SizeMetric::Percent => {
585 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(percent_resolve)
586 }
587 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => 0.0,
590 }
591 }
592
593 #[inline]
606 #[must_use]
607 pub fn resolve_with_context(
608 &self,
609 context: &ResolutionContext,
610 property_context: PropertyContext,
611 ) -> f32 {
612 match self.metric {
613 SizeMetric::Px => self.number.get(),
615 SizeMetric::Pt => self.number.get() * PT_TO_PX,
616 SizeMetric::In => self.number.get() * 96.0,
617 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
618 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
619
620 SizeMetric::Em => {
622 let reference_font_size = if property_context == PropertyContext::FontSize {
623 context.parent_font_size
625 } else {
626 context.element_font_size
628 };
629 self.number.get() * reference_font_size
630 }
631
632 SizeMetric::Rem => self.number.get() * context.root_font_size,
634
635 SizeMetric::Vw => self.number.get() * context.viewport_size.width / 100.0,
638 SizeMetric::Vh => self.number.get() * context.viewport_size.height / 100.0,
639 SizeMetric::Vmin => {
641 let min_dimension = context
642 .viewport_size
643 .width
644 .min(context.viewport_size.height);
645 self.number.get() * min_dimension / 100.0
646 }
647 SizeMetric::Vmax => {
649 let max_dimension = context
650 .viewport_size
651 .width
652 .max(context.viewport_size.height);
653 self.number.get() * max_dimension / 100.0
654 }
655
656 SizeMetric::Percent => {
658 #[allow(clippy::match_same_arms)]
661 let reference = match property_context {
662 PropertyContext::FontSize => context.parent_font_size,
664
665 PropertyContext::Width => context.containing_block_size.width,
667
668 PropertyContext::Height => context.containing_block_size.height,
670
671 PropertyContext::Margin | PropertyContext::Padding => {
677 if context.vertical_writing_mode {
681 context.containing_block_size.height
682 } else {
683 context.containing_block_size.width
684 }
685 }
686
687 PropertyContext::BorderWidth => 0.0,
690
691 PropertyContext::BorderRadius => context.element_size.map_or(0.0, |s| s.width),
695
696 PropertyContext::Transform => context.element_size.map_or(0.0, |s| s.width),
698
699 PropertyContext::Other => context.containing_block_size.width,
701 };
702
703 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(reference)
704 }
705 }
706 }
707}
708
709pub const THIN_BORDER_THICKNESS: PixelValue = PixelValue {
715 metric: SizeMetric::Px,
716 number: FloatValue { number: 1000 },
717};
718
719pub const MEDIUM_BORDER_THICKNESS: PixelValue = PixelValue {
721 metric: SizeMetric::Px,
722 number: FloatValue { number: 3000 },
723};
724
725pub const THICK_BORDER_THICKNESS: PixelValue = PixelValue {
727 metric: SizeMetric::Px,
728 number: FloatValue { number: 5000 },
729};
730
731#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
733#[repr(C)]
734pub struct PixelValueNoPercent {
735 pub inner: PixelValue,
736}
737
738impl PixelValueNoPercent {
739 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
740 self.inner.scale_for_dpi(scale_factor);
741 }
742}
743
744impl_option!(
745 PixelValueNoPercent,
746 OptionPixelValueNoPercent,
747 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
748);
749
750impl_option!(
751 PixelValue,
752 OptionPixelValue,
753 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
754);
755
756impl fmt::Display for PixelValueNoPercent {
757 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
758 write!(f, "{}", self.inner)
759 }
760}
761
762impl ::core::fmt::Debug for PixelValueNoPercent {
763 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
764 write!(f, "{self}")
765 }
766}
767
768impl PixelValueNoPercent {
769 #[doc(hidden)]
775 #[inline]
776 #[must_use]
777 pub fn to_pixels_internal(&self, em_resolve: f32, rem_resolve: f32) -> f32 {
778 self.inner.to_pixels_internal(0.0, em_resolve, rem_resolve)
779 }
780
781 #[inline]
782 #[must_use]
783 pub const fn zero() -> Self {
784 const ZERO_PXNP: PixelValueNoPercent = PixelValueNoPercent {
785 inner: PixelValue::zero(),
786 };
787 ZERO_PXNP
788 }
789}
790impl From<PixelValue> for PixelValueNoPercent {
791 fn from(e: PixelValue) -> Self {
792 Self { inner: e }
793 }
794}
795
796#[derive(Clone, PartialEq, Eq)]
797pub enum CssPixelValueParseError<'a> {
798 EmptyString,
799 NoValueGiven(&'a str, SizeMetric),
800 ValueParseErr(ParseFloatError, &'a str),
801 InvalidPixelValue(&'a str),
802}
803
804impl_debug_as_display!(CssPixelValueParseError<'a>);
805
806impl_display! { CssPixelValueParseError<'a>, {
807 EmptyString => format!("Missing [px / pt / em / %] value"),
808 NoValueGiven(input, metric) => format!("Expected floating-point pixel value, got: \"{}{}\"", input, metric),
809 ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
810 InvalidPixelValue(s) => format!("Invalid pixel value: \"{}\"", s),
811}}
812
813#[derive(Debug, Clone, PartialEq, Eq)]
815#[repr(C)]
816pub struct PixelNoValueGivenError {
817 pub value: AzString,
818 pub metric: SizeMetric,
819}
820
821#[derive(Debug, Clone, PartialEq, Eq)]
823#[repr(C, u8)]
824pub enum CssPixelValueParseErrorOwned {
825 EmptyString,
826 NoValueGiven(PixelNoValueGivenError),
827 ValueParseErr(ParseFloatErrorWithInput),
828 InvalidPixelValue(AzString),
829}
830
831impl CssPixelValueParseError<'_> {
832 #[must_use]
833 pub fn to_contained(&self) -> CssPixelValueParseErrorOwned {
834 match self {
835 CssPixelValueParseError::EmptyString => CssPixelValueParseErrorOwned::EmptyString,
836 CssPixelValueParseError::NoValueGiven(s, metric) => {
837 CssPixelValueParseErrorOwned::NoValueGiven(PixelNoValueGivenError {
838 value: (*s).to_string().into(),
839 metric: *metric,
840 })
841 }
842 CssPixelValueParseError::ValueParseErr(err, s) => {
843 CssPixelValueParseErrorOwned::ValueParseErr(ParseFloatErrorWithInput {
844 error: err.clone().into(),
845 input: (*s).to_string().into(),
846 })
847 }
848 CssPixelValueParseError::InvalidPixelValue(s) => {
849 CssPixelValueParseErrorOwned::InvalidPixelValue((*s).to_string().into())
850 }
851 }
852 }
853}
854
855impl CssPixelValueParseErrorOwned {
856 #[must_use]
857 pub fn to_shared(&self) -> CssPixelValueParseError<'_> {
858 match self {
859 Self::EmptyString => CssPixelValueParseError::EmptyString,
860 Self::NoValueGiven(e) => {
861 CssPixelValueParseError::NoValueGiven(e.value.as_str(), e.metric)
862 }
863 Self::ValueParseErr(e) => {
864 CssPixelValueParseError::ValueParseErr(e.error.to_std(), e.input.as_str())
865 }
866 Self::InvalidPixelValue(s) => CssPixelValueParseError::InvalidPixelValue(s.as_str()),
867 }
868 }
869}
870
871fn parse_pixel_value_inner<'a>(
873 input: &'a str,
874 match_values: &[(&'static str, SizeMetric)],
875) -> Result<PixelValue, CssPixelValueParseError<'a>> {
876 let input = input.trim();
877
878 if input.is_empty() {
879 return Err(CssPixelValueParseError::EmptyString);
880 }
881
882 for (match_val, metric) in match_values {
883 if let Some(value) = input.strip_suffix(match_val) {
884 let value = value.trim();
885 if value.is_empty() {
886 return Err(CssPixelValueParseError::NoValueGiven(input, *metric));
887 }
888 match value.parse::<f32>() {
889 Ok(o) => {
890 return Ok(PixelValue::from_metric(*metric, o));
891 }
892 Err(e) => {
893 return Err(CssPixelValueParseError::ValueParseErr(e, value));
894 }
895 }
896 }
897 }
898
899 input.trim().parse::<f32>().map_or_else(
900 |_| Err(CssPixelValueParseError::InvalidPixelValue(input)),
901 |o| Ok(PixelValue::px(o)),
902 )
903}
904
905pub fn parse_pixel_value(input: &str) -> Result<PixelValue, CssPixelValueParseError<'_>> {
909 parse_pixel_value_inner(
910 input,
911 &[
912 ("px", SizeMetric::Px),
915 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
917 ("pt", SizeMetric::Pt),
918 ("vmax", SizeMetric::Vmax),
919 ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
921 ("vh", SizeMetric::Vh),
922 ("in", SizeMetric::In),
923 ("mm", SizeMetric::Mm),
924 ("cm", SizeMetric::Cm),
925 ("%", SizeMetric::Percent),
926 ],
927 )
928}
929
930pub fn parse_pixel_value_no_percent(
934 input: &str,
935) -> Result<PixelValueNoPercent, CssPixelValueParseError<'_>> {
936 Ok(PixelValueNoPercent {
937 inner: parse_pixel_value_inner(
938 input,
939 &[
940 ("px", SizeMetric::Px),
942 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
944 ("pt", SizeMetric::Pt),
945 ("vmax", SizeMetric::Vmax),
946 ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
948 ("vh", SizeMetric::Vh),
949 ("in", SizeMetric::In),
950 ("mm", SizeMetric::Mm),
951 ("cm", SizeMetric::Cm),
952 ],
953 )?,
954 })
955}
956
957#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
958pub enum PixelValueWithAuto {
959 None,
960 Initial,
961 Inherit,
962 Auto,
963 Exact(PixelValue),
964}
965
966pub fn parse_pixel_value_with_auto(
971 input: &str,
972) -> Result<PixelValueWithAuto, CssPixelValueParseError<'_>> {
973 let input = input.trim();
974 match input {
975 "none" => Ok(PixelValueWithAuto::None),
976 "initial" => Ok(PixelValueWithAuto::Initial),
977 "inherit" => Ok(PixelValueWithAuto::Inherit),
978 "auto" => Ok(PixelValueWithAuto::Auto),
979 e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
980 }
981}
982
983#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
992#[repr(C)]
993#[derive(Default)]
994pub enum SystemMetricRef {
995 #[default]
997 ButtonRadius,
998 ButtonPaddingHorizontal,
1000 ButtonPaddingVertical,
1002 ButtonBorderWidth,
1004 TitlebarHeight,
1006 TitlebarButtonWidth,
1008 TitlebarPadding,
1010 SafeAreaTop,
1012 SafeAreaBottom,
1014 SafeAreaLeft,
1016 SafeAreaRight,
1018}
1019
1020impl SystemMetricRef {
1021 #[must_use]
1023 pub const fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
1024 match self {
1025 Self::ButtonRadius => metrics.corner_radius.as_option().copied(),
1026 Self::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
1027 Self::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
1028 Self::ButtonBorderWidth => metrics.border_width.as_option().copied(),
1029 Self::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
1030 Self::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
1031 Self::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
1032 Self::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
1033 Self::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
1034 Self::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
1035 Self::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
1036 }
1037 }
1038
1039 #[must_use]
1041 pub const fn as_css_str(&self) -> &'static str {
1042 match self {
1043 Self::ButtonRadius => "system:button-radius",
1044 Self::ButtonPaddingHorizontal => "system:button-padding-horizontal",
1045 Self::ButtonPaddingVertical => "system:button-padding-vertical",
1046 Self::ButtonBorderWidth => "system:button-border-width",
1047 Self::TitlebarHeight => "system:titlebar-height",
1048 Self::TitlebarButtonWidth => "system:titlebar-button-width",
1049 Self::TitlebarPadding => "system:titlebar-padding",
1050 Self::SafeAreaTop => "system:safe-area-top",
1051 Self::SafeAreaBottom => "system:safe-area-bottom",
1052 Self::SafeAreaLeft => "system:safe-area-left",
1053 Self::SafeAreaRight => "system:safe-area-right",
1054 }
1055 }
1056
1057 #[must_use]
1059 pub fn from_css_str(s: &str) -> Option<Self> {
1060 match s {
1061 "button-radius" => Some(Self::ButtonRadius),
1062 "button-padding-horizontal" => Some(Self::ButtonPaddingHorizontal),
1063 "button-padding-vertical" => Some(Self::ButtonPaddingVertical),
1064 "button-border-width" => Some(Self::ButtonBorderWidth),
1065 "titlebar-height" => Some(Self::TitlebarHeight),
1066 "titlebar-button-width" => Some(Self::TitlebarButtonWidth),
1067 "titlebar-padding" => Some(Self::TitlebarPadding),
1068 "safe-area-top" => Some(Self::SafeAreaTop),
1069 "safe-area-bottom" => Some(Self::SafeAreaBottom),
1070 "safe-area-left" => Some(Self::SafeAreaLeft),
1071 "safe-area-right" => Some(Self::SafeAreaRight),
1072 _ => None,
1073 }
1074 }
1075}
1076
1077impl fmt::Display for SystemMetricRef {
1078 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1079 write!(f, "{}", self.as_css_str())
1080 }
1081}
1082
1083impl FormatAsCssValue for SystemMetricRef {
1084 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1085 write!(f, "{}", self.as_css_str())
1086 }
1087}
1088
1089#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
1094#[repr(C, u8)]
1095pub enum PixelValueOrSystem {
1096 Value(PixelValue),
1098 System(SystemMetricRef),
1100}
1101
1102impl Default for PixelValueOrSystem {
1103 fn default() -> Self {
1104 Self::Value(PixelValue::zero())
1105 }
1106}
1107
1108impl From<PixelValue> for PixelValueOrSystem {
1109 fn from(value: PixelValue) -> Self {
1110 Self::Value(value)
1111 }
1112}
1113
1114impl PixelValueOrSystem {
1115 #[must_use]
1117 pub const fn value(v: PixelValue) -> Self {
1118 Self::Value(v)
1119 }
1120
1121 #[must_use]
1123 pub const fn system(s: SystemMetricRef) -> Self {
1124 Self::System(s)
1125 }
1126
1127 #[must_use]
1130 pub fn resolve(
1131 &self,
1132 system_metrics: &crate::system::SystemMetrics,
1133 fallback: PixelValue,
1134 ) -> PixelValue {
1135 match self {
1136 Self::Value(v) => *v,
1137 Self::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
1138 }
1139 }
1140}
1141
1142impl fmt::Display for PixelValueOrSystem {
1143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1144 match self {
1145 Self::Value(v) => write!(f, "{v}"),
1146 Self::System(s) => write!(f, "{s}"),
1147 }
1148 }
1149}
1150
1151impl FormatAsCssValue for PixelValueOrSystem {
1152 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1153 match self {
1154 Self::Value(v) => v.format_as_css_value(f),
1155 Self::System(s) => s.format_as_css_value(f),
1156 }
1157 }
1158}
1159
1160#[cfg(feature = "parser")]
1164pub fn parse_pixel_value_or_system(
1168 input: &str,
1169) -> Result<PixelValueOrSystem, CssPixelValueParseError<'_>> {
1170 let input = input.trim();
1171
1172 if let Some(metric_name) = input.strip_prefix("system:") {
1174 if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
1175 return Ok(PixelValueOrSystem::System(metric_ref));
1176 }
1177 return Err(CssPixelValueParseError::InvalidPixelValue(input));
1178 }
1179
1180 Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
1182}
1183
1184#[cfg(all(test, feature = "parser"))]
1185mod tests {
1186 #![allow(clippy::float_cmp)]
1188 use super::*;
1189
1190 #[test]
1191 fn test_parse_pixel_value() {
1192 assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
1193 assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
1194 assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
1195 assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
1196 assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
1197 assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
1198 assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
1199 assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
1200 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1201 }
1202
1203 #[test]
1204 fn test_resolve_with_context_em() {
1205 let context = ResolutionContext {
1207 vertical_writing_mode: false,
1208 element_font_size: 32.0,
1209 parent_font_size: 16.0,
1210 ..Default::default()
1211 };
1212
1213 let margin = PixelValue::em(0.67);
1215 assert!(
1216 (margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
1217 );
1218
1219 let font_size = PixelValue::em(2.0);
1221 assert_eq!(
1222 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1223 32.0
1224 );
1225 }
1226
1227 #[test]
1228 fn test_resolve_with_context_rem() {
1229 let context = ResolutionContext {
1231 vertical_writing_mode: false,
1232 element_font_size: 32.0,
1233 parent_font_size: 16.0,
1234 root_font_size: 18.0,
1235 ..Default::default()
1236 };
1237
1238 let margin = PixelValue::rem(2.0);
1240 assert_eq!(
1241 margin.resolve_with_context(&context, PropertyContext::Margin),
1242 36.0
1243 );
1244
1245 let font_size = PixelValue::rem(1.5);
1246 assert_eq!(
1247 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1248 27.0
1249 );
1250 }
1251
1252 #[test]
1253 fn test_resolve_with_context_percent_margin() {
1254 let context = ResolutionContext {
1256 vertical_writing_mode: false,
1257 element_font_size: 16.0,
1258 parent_font_size: 16.0,
1259 root_font_size: 16.0,
1260 containing_block_size: PhysicalSize::new(800.0, 600.0),
1261 element_size: None,
1262 viewport_size: PhysicalSize::new(1920.0, 1080.0),
1263 };
1264
1265 let margin = PixelValue::percent(10.0); assert_eq!(
1267 margin.resolve_with_context(&context, PropertyContext::Margin),
1268 80.0
1269 ); }
1271
1272 #[test]
1273 fn test_parse_pixel_value_no_percent() {
1274 assert_eq!(
1275 parse_pixel_value_no_percent("10px").unwrap().inner,
1276 PixelValue::px(10.0)
1277 );
1278 assert!(parse_pixel_value_no_percent("50%").is_err());
1279 }
1280
1281 #[test]
1282 fn test_parse_pixel_value_with_auto() {
1283 assert_eq!(
1284 parse_pixel_value_with_auto("10px").unwrap(),
1285 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1286 );
1287 assert_eq!(
1288 parse_pixel_value_with_auto("auto").unwrap(),
1289 PixelValueWithAuto::Auto
1290 );
1291 assert_eq!(
1292 parse_pixel_value_with_auto("initial").unwrap(),
1293 PixelValueWithAuto::Initial
1294 );
1295 assert_eq!(
1296 parse_pixel_value_with_auto("inherit").unwrap(),
1297 PixelValueWithAuto::Inherit
1298 );
1299 assert_eq!(
1300 parse_pixel_value_with_auto("none").unwrap(),
1301 PixelValueWithAuto::None
1302 );
1303 }
1304
1305 #[test]
1306 fn test_parse_pixel_value_errors() {
1307 assert!(parse_pixel_value("").is_err());
1308 assert!(parse_pixel_value("10").is_ok()); assert!(parse_pixel_value("10 px").is_ok()); assert!(parse_pixel_value("px").is_err());
1313 assert!(parse_pixel_value("ten-px").is_err());
1314 }
1315}
1316
1317#[cfg(test)]
1318#[allow(
1319 clippy::float_cmp,
1320 clippy::unreadable_literal,
1321 clippy::cast_precision_loss,
1322 clippy::too_many_lines,
1323 clippy::excessive_precision
1324)]
1325mod autotest_generated {
1326 use std::{
1327 collections::hash_map::DefaultHasher,
1328 hash::{Hash, Hasher},
1329 };
1330
1331 use super::*;
1332 use crate::{
1333 codegen::format::FormatAsRustCode,
1334 css::PrintAsCssValue,
1335 props::{
1336 basic::length::{FloatValue, SizeMetric},
1337 formatter::FormatAsCssValue,
1338 },
1339 system::{SafeAreaInsets, SystemMetrics, TitlebarMetrics},
1340 };
1341
1342 const MULT: f32 = 1000.0;
1345
1346 const MAX_SAFE_CONST: isize = isize::MAX / 1000;
1350 const MIN_SAFE_CONST: isize = isize::MIN / 1000;
1351
1352 const ALL_METRICS: [SizeMetric; 12] = [
1353 SizeMetric::Px,
1354 SizeMetric::Pt,
1355 SizeMetric::Em,
1356 SizeMetric::Rem,
1357 SizeMetric::In,
1358 SizeMetric::Cm,
1359 SizeMetric::Mm,
1360 SizeMetric::Percent,
1361 SizeMetric::Vw,
1362 SizeMetric::Vh,
1363 SizeMetric::Vmin,
1364 SizeMetric::Vmax,
1365 ];
1366
1367 const ALL_PROPERTY_CONTEXTS: [PropertyContext; 9] = [
1368 PropertyContext::FontSize,
1369 PropertyContext::Margin,
1370 PropertyContext::Padding,
1371 PropertyContext::Width,
1372 PropertyContext::Height,
1373 PropertyContext::BorderWidth,
1374 PropertyContext::BorderRadius,
1375 PropertyContext::Transform,
1376 PropertyContext::Other,
1377 ];
1378
1379 const ALL_SYSTEM_REFS: [SystemMetricRef; 11] = [
1380 SystemMetricRef::ButtonRadius,
1381 SystemMetricRef::ButtonPaddingHorizontal,
1382 SystemMetricRef::ButtonPaddingVertical,
1383 SystemMetricRef::ButtonBorderWidth,
1384 SystemMetricRef::TitlebarHeight,
1385 SystemMetricRef::TitlebarButtonWidth,
1386 SystemMetricRef::TitlebarPadding,
1387 SystemMetricRef::SafeAreaTop,
1388 SystemMetricRef::SafeAreaBottom,
1389 SystemMetricRef::SafeAreaLeft,
1390 SystemMetricRef::SafeAreaRight,
1391 ];
1392
1393 const EXTREME_F32: [f32; 13] = [
1395 0.0,
1396 -0.0,
1397 1.0,
1398 -1.0,
1399 f32::MIN_POSITIVE,
1400 -f32::MIN_POSITIVE,
1401 1e30,
1402 -1e30,
1403 f32::MAX,
1404 f32::MIN,
1405 f32::INFINITY,
1406 f32::NEG_INFINITY,
1407 f32::NAN,
1408 ];
1409
1410 fn approx(a: f32, b: f32) -> bool {
1411 (a - b).abs() < 0.001
1412 }
1413
1414 fn hash_of<T: Hash>(v: &T) -> u64 {
1415 let mut h = DefaultHasher::new();
1416 v.hash(&mut h);
1417 h.finish()
1418 }
1419
1420 struct CssVal<T>(T);
1423
1424 impl<T: FormatAsCssValue> fmt::Display for CssVal<T> {
1425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1426 self.0.format_as_css_value(f)
1427 }
1428 }
1429
1430 fn as_css_value<T: FormatAsCssValue>(v: T) -> String {
1431 CssVal(v).to_string()
1432 }
1433
1434 fn distinct_context() -> ResolutionContext {
1437 ResolutionContext {
1438 vertical_writing_mode: false,
1439 element_font_size: 32.0,
1440 parent_font_size: 8.0,
1441 root_font_size: 4.0,
1442 containing_block_size: PhysicalSize::new(800.0, 600.0),
1443 element_size: Some(PhysicalSize::new(200.0, 100.0)),
1444 viewport_size: PhysicalSize::new(1000.0, 500.0),
1445 }
1446 }
1447
1448 fn populated_metrics() -> SystemMetrics {
1449 SystemMetrics {
1450 corner_radius: OptionPixelValue::Some(PixelValue::px(1.0)),
1451 border_width: OptionPixelValue::Some(PixelValue::px(2.0)),
1452 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(3.0)),
1453 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
1454 titlebar: TitlebarMetrics {
1455 height: OptionPixelValue::Some(PixelValue::px(5.0)),
1456 button_area_width: OptionPixelValue::Some(PixelValue::px(6.0)),
1457 padding_horizontal: OptionPixelValue::Some(PixelValue::px(7.0)),
1458 safe_area: SafeAreaInsets {
1459 top: OptionPixelValue::Some(PixelValue::px(8.0)),
1460 bottom: OptionPixelValue::Some(PixelValue::px(9.0)),
1461 left: OptionPixelValue::Some(PixelValue::px(10.0)),
1462 right: OptionPixelValue::Some(PixelValue::px(11.0)),
1463 keyboard: OptionPixelValue::None,
1466 },
1467 ..TitlebarMetrics::default()
1468 },
1469 }
1470 }
1471
1472 #[test]
1475 fn parse_pixel_value_rejects_empty_and_whitespace_only() {
1476 assert_eq!(
1477 parse_pixel_value("").unwrap_err(),
1478 CssPixelValueParseError::EmptyString
1479 );
1480 for ws in [" ", "\t\n", "\r\n\t ", "\n"] {
1481 assert_eq!(
1482 parse_pixel_value(ws).unwrap_err(),
1483 CssPixelValueParseError::EmptyString,
1484 "whitespace-only input {ws:?} must trim down to EmptyString"
1485 );
1486 }
1487 }
1488
1489 #[test]
1490 fn parse_pixel_value_rejects_a_bare_unit_with_no_number() {
1491 for unit in [
1495 "px", "rem", "em", "pt", "in", "mm", "cm", "vmax", "vw", "vh", "%",
1496 ] {
1497 let err = parse_pixel_value(unit).unwrap_err();
1498 assert!(
1499 matches!(err, CssPixelValueParseError::NoValueGiven(input, _) if input == unit),
1500 "bare unit {unit:?} should be NoValueGiven, got {err:?}"
1501 );
1502 }
1503 assert!(matches!(
1505 parse_pixel_value(" px").unwrap_err(),
1506 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px)
1507 ));
1508 }
1509
1510 #[test]
1511 fn parse_pixel_value_vmin_is_shadowed_by_the_in_suffix() {
1512 assert_eq!(
1517 parse_pixel_value("5vmin").unwrap(),
1518 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1519 );
1520 assert!(matches!(
1523 parse_pixel_value("vmin").unwrap_err(),
1524 CssPixelValueParseError::NoValueGiven(..)
1525 ));
1526
1527 assert_eq!(
1529 parse_pixel_value("5vmax").unwrap(),
1530 PixelValue::from_metric(SizeMetric::Vmax, 5.0)
1531 );
1532 assert_eq!(
1533 parse_pixel_value("5vw").unwrap(),
1534 PixelValue::from_metric(SizeMetric::Vw, 5.0)
1535 );
1536 assert_eq!(
1537 parse_pixel_value("5vh").unwrap(),
1538 PixelValue::from_metric(SizeMetric::Vh, 5.0)
1539 );
1540 }
1541
1542 #[test]
1543 fn parse_pixel_value_inner_proves_the_vmin_bug_is_pure_suffix_ordering() {
1544 let in_first: [(&'static str, SizeMetric); 2] =
1547 [("in", SizeMetric::In), ("vmin", SizeMetric::Vmin)];
1548 let vmin_first: [(&'static str, SizeMetric); 2] =
1549 [("vmin", SizeMetric::Vmin), ("in", SizeMetric::In)];
1550
1551 assert!(parse_pixel_value_inner("5vmin", &in_first).is_err());
1552 assert_eq!(
1553 parse_pixel_value_inner("5vmin", &vmin_first).unwrap(),
1554 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1555 );
1556 assert_eq!(
1558 parse_pixel_value_inner("5in", &vmin_first).unwrap(),
1559 PixelValue::inch(5.0)
1560 );
1561 }
1562
1563 #[test]
1564 fn parse_pixel_value_inner_with_an_empty_table_falls_back_to_unitless_px() {
1565 let empty: [(&'static str, SizeMetric); 0] = [];
1566
1567 assert_eq!(
1569 parse_pixel_value_inner("10", &empty).unwrap(),
1570 PixelValue::px(10.0)
1571 );
1572 assert!(matches!(
1573 parse_pixel_value_inner("10px", &empty).unwrap_err(),
1574 CssPixelValueParseError::InvalidPixelValue("10px")
1575 ));
1576 assert_eq!(
1577 parse_pixel_value_inner("", &empty).unwrap_err(),
1578 CssPixelValueParseError::EmptyString
1579 );
1580 }
1581
1582 #[test]
1583 fn parse_pixel_value_accepts_every_unit_it_advertises() {
1584 let cases: [(&str, PixelValue); 12] = [
1586 ("10px", PixelValue::px(10.0)),
1587 ("1.5em", PixelValue::em(1.5)),
1588 ("2rem", PixelValue::rem(2.0)),
1589 ("-20pt", PixelValue::pt(-20.0)),
1590 ("50%", PixelValue::percent(50.0)),
1591 ("1in", PixelValue::inch(1.0)),
1592 ("2.54cm", PixelValue::cm(2.54)),
1593 ("10mm", PixelValue::mm(10.0)),
1594 ("+7px", PixelValue::px(7.0)),
1595 (".5px", PixelValue::px(0.5)),
1596 ("5.px", PixelValue::px(5.0)),
1597 ("1e2px", PixelValue::px(100.0)),
1598 ];
1599 for (input, expected) in cases {
1600 assert_eq!(
1601 parse_pixel_value(input).unwrap(),
1602 expected,
1603 "parsing {input:?}"
1604 );
1605 }
1606
1607 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1609 assert_eq!(parse_pixel_value("10 px").unwrap(), PixelValue::px(10.0));
1610 assert_eq!(parse_pixel_value("\t10px\n").unwrap(), PixelValue::px(10.0));
1611 }
1612
1613 #[test]
1614 fn parse_pixel_value_boundary_numbers_saturate_instead_of_overflowing() {
1615 assert_eq!(parse_pixel_value("-0").unwrap(), PixelValue::px(0.0));
1617 assert_eq!(parse_pixel_value("-0").unwrap(), PixelValue::zero());
1618
1619 assert_eq!(parse_pixel_value("0.0004px").unwrap(), PixelValue::px(0.0));
1621 assert_eq!(parse_pixel_value("-0.0009px").unwrap(), PixelValue::px(0.0));
1622 assert_eq!(parse_pixel_value("1e-40px").unwrap(), PixelValue::px(0.0));
1623
1624 for huge in ["9223372036854775807", "1e40px", "3.5e38"] {
1626 let v = parse_pixel_value(huge).unwrap();
1627 assert!(
1628 v.number.get().is_finite(),
1629 "{huge:?} leaked a non-finite value: {}",
1630 v.number.get()
1631 );
1632 assert!(v.number.get() > 0.0, "{huge:?} lost its sign");
1633 }
1634 let neg = parse_pixel_value("-1e40px").unwrap();
1635 assert!(neg.number.get().is_finite() && neg.number.get() < 0.0);
1636 }
1637
1638 #[test]
1639 fn parse_pixel_value_inherits_rusts_float_keywords() {
1640 assert_eq!(parse_pixel_value("NaN").unwrap(), PixelValue::zero());
1645
1646 let inf = parse_pixel_value("infinity").unwrap();
1647 assert_eq!(inf, PixelValue::px(f32::INFINITY));
1648 assert!(inf.number.get().is_finite() && inf.number.get() > 0.0);
1649
1650 let neg_inf = parse_pixel_value("-infinity").unwrap();
1651 assert_eq!(neg_inf, PixelValue::px(f32::NEG_INFINITY));
1652 assert!(neg_inf.number.get().is_finite() && neg_inf.number.get() < 0.0);
1653
1654 let inf_short = parse_pixel_value("inf").unwrap();
1658 assert_eq!(inf_short, PixelValue::px(f32::INFINITY));
1659 assert!(inf_short.number.get().is_finite() && inf_short.number.get() > 0.0);
1660 }
1661
1662 #[test]
1663 fn parse_pixel_value_is_case_sensitive_about_units() {
1664 for input in ["10PX", "10Px", "10EM", "10REM", "10VMAX"] {
1668 let err = parse_pixel_value(input).unwrap_err();
1669 assert!(
1670 matches!(err, CssPixelValueParseError::InvalidPixelValue(s) if s == input),
1671 "uppercase unit {input:?} should be InvalidPixelValue, got {err:?}"
1672 );
1673 }
1674 }
1675
1676 #[test]
1677 fn parse_pixel_value_rejects_garbage_and_trailing_junk() {
1678 for input in [
1679 "ten-px",
1680 "px10",
1681 "10px;garbage",
1682 "10;",
1683 "--",
1684 "1%%",
1685 "10 20px",
1686 "#",
1687 "10px 10px",
1688 "e",
1689 "0x10px",
1690 ] {
1691 assert!(
1692 parse_pixel_value(input).is_err(),
1693 "{input:?} must not parse, got {:?}",
1694 parse_pixel_value(input)
1695 );
1696 }
1697 }
1698
1699 #[test]
1700 fn parse_pixel_value_survives_unicode() {
1701 for input in [
1703 "\u{1F600}", "10px\u{1F600}", "10px\u{0301}", "\u{200B}10px", "\u{0661}\u{0660}px", "10\u{0440}\u{0445}", "\u{202E}10px", ] {
1711 let got = parse_pixel_value(input);
1712 assert!(got.is_err(), "{input:?} must be rejected, got {got:?}");
1713 }
1714
1715 assert!(matches!(
1718 parse_pixel_value("\u{200B}10px").unwrap_err(),
1719 CssPixelValueParseError::ValueParseErr(_, "\u{200B}10")
1720 ));
1721 }
1722
1723 #[test]
1724 fn parse_pixel_value_handles_extremely_long_and_deeply_nested_input() {
1725 let long_number = format!("{}px", "9".repeat(100_000));
1727 let parsed = parse_pixel_value(&long_number).unwrap();
1728 assert!(parsed.number.get().is_finite());
1729 assert_eq!(parsed.metric, SizeMetric::Px);
1730
1731 let long_junk = "x".repeat(100_000);
1733 assert!(parse_pixel_value(&long_junk).is_err());
1734
1735 let nested = "(".repeat(10_000);
1738 assert!(matches!(
1739 parse_pixel_value(&nested).unwrap_err(),
1740 CssPixelValueParseError::InvalidPixelValue(_)
1741 ));
1742 }
1743
1744 #[test]
1745 fn parse_pixel_value_no_percent_rejects_percentages_but_keeps_the_rest() {
1746 assert_eq!(
1747 parse_pixel_value_no_percent("10px").unwrap().inner,
1748 PixelValue::px(10.0)
1749 );
1750 assert_eq!(
1751 parse_pixel_value_no_percent("5vmax").unwrap().inner,
1752 PixelValue::from_metric(SizeMetric::Vmax, 5.0)
1753 );
1754
1755 assert!(matches!(
1757 parse_pixel_value_no_percent("50%").unwrap_err(),
1758 CssPixelValueParseError::InvalidPixelValue("50%")
1759 ));
1760 assert!(matches!(
1761 parse_pixel_value_no_percent("%").unwrap_err(),
1762 CssPixelValueParseError::InvalidPixelValue("%")
1763 ));
1764
1765 assert_eq!(
1766 parse_pixel_value_no_percent("").unwrap_err(),
1767 CssPixelValueParseError::EmptyString
1768 );
1769 assert_eq!(
1770 parse_pixel_value_no_percent(" ").unwrap_err(),
1771 CssPixelValueParseError::EmptyString
1772 );
1773 assert!(parse_pixel_value_no_percent("\u{1F600}").is_err());
1774 assert_eq!(
1776 parse_pixel_value_no_percent("5vmin").unwrap().inner,
1777 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1778 );
1779 }
1780
1781 #[test]
1782 fn parse_pixel_value_with_auto_keywords_and_fallthrough() {
1783 assert_eq!(
1784 parse_pixel_value_with_auto("auto").unwrap(),
1785 PixelValueWithAuto::Auto
1786 );
1787 assert_eq!(
1788 parse_pixel_value_with_auto(" initial ").unwrap(),
1789 PixelValueWithAuto::Initial
1790 );
1791 assert_eq!(
1792 parse_pixel_value_with_auto("\tinherit\n").unwrap(),
1793 PixelValueWithAuto::Inherit
1794 );
1795 assert_eq!(
1796 parse_pixel_value_with_auto("none").unwrap(),
1797 PixelValueWithAuto::None
1798 );
1799 assert_eq!(
1800 parse_pixel_value_with_auto("10px").unwrap(),
1801 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1802 );
1803
1804 for input in ["AUTO", "Auto", "INHERIT", "None"] {
1806 assert!(
1807 parse_pixel_value_with_auto(input).is_err(),
1808 "{input:?} unexpectedly matched a keyword"
1809 );
1810 }
1811
1812 assert_eq!(
1814 parse_pixel_value_with_auto("").unwrap_err(),
1815 CssPixelValueParseError::EmptyString
1816 );
1817 assert_eq!(
1818 parse_pixel_value_with_auto(" \t ").unwrap_err(),
1819 CssPixelValueParseError::EmptyString
1820 );
1821 assert!(parse_pixel_value_with_auto("auto;garbage").is_err());
1822 assert!(parse_pixel_value_with_auto("\u{1F600}").is_err());
1823 assert!(parse_pixel_value_with_auto(&"(".repeat(10_000)).is_err());
1824 }
1825
1826 #[cfg(feature = "parser")]
1827 #[test]
1828 fn parse_pixel_value_or_system_accepts_every_system_ref() {
1829 for r in ALL_SYSTEM_REFS {
1830 let css = r.as_css_str(); assert_eq!(
1832 parse_pixel_value_or_system(css).unwrap(),
1833 PixelValueOrSystem::System(r),
1834 "round-tripping {css:?}"
1835 );
1836 assert_eq!(
1838 parse_pixel_value_or_system(&format!(" {css} ")).unwrap(),
1839 PixelValueOrSystem::System(r)
1840 );
1841 }
1842
1843 assert_eq!(
1845 parse_pixel_value_or_system("10px").unwrap(),
1846 PixelValueOrSystem::Value(PixelValue::px(10.0))
1847 );
1848 assert_eq!(
1849 parse_pixel_value_or_system("1.5em").unwrap(),
1850 PixelValueOrSystem::Value(PixelValue::em(1.5))
1851 );
1852 }
1853
1854 #[cfg(feature = "parser")]
1855 #[test]
1856 fn parse_pixel_value_or_system_rejects_malformed_system_refs() {
1857 assert!(matches!(
1862 parse_pixel_value_or_system("system:button-padding").unwrap_err(),
1863 CssPixelValueParseError::InvalidPixelValue("system:button-padding")
1864 ));
1865
1866 for input in [
1867 "system:", "system:unknown", "system: button-radius", "system:BUTTON-RADIUS", "system:button-radius;x", "system:\u{1F600}", ] {
1874 let err = parse_pixel_value_or_system(input).unwrap_err();
1875 assert!(
1876 matches!(err, CssPixelValueParseError::InvalidPixelValue(s) if s == input),
1877 "{input:?} should be InvalidPixelValue, got {err:?}"
1878 );
1879 }
1880
1881 assert!(matches!(
1884 parse_pixel_value_or_system("SYSTEM:button-radius").unwrap_err(),
1885 CssPixelValueParseError::InvalidPixelValue("SYSTEM:button-radius")
1886 ));
1887 assert_eq!(
1888 parse_pixel_value_or_system("").unwrap_err(),
1889 CssPixelValueParseError::EmptyString
1890 );
1891 assert_eq!(
1892 parse_pixel_value_or_system(" ").unwrap_err(),
1893 CssPixelValueParseError::EmptyString
1894 );
1895
1896 let long = format!("system:{}", "a".repeat(100_000));
1898 assert!(parse_pixel_value_or_system(&long).is_err());
1899 }
1900
1901 #[test]
1904 fn parse_errors_survive_the_owned_round_trip() {
1905 let float_err = "x".parse::<f32>().unwrap_err();
1906 let errors = [
1907 CssPixelValueParseError::EmptyString,
1908 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px),
1909 CssPixelValueParseError::ValueParseErr(float_err, "abc"),
1910 CssPixelValueParseError::InvalidPixelValue("ten-px"),
1911 ];
1912
1913 for err in errors {
1914 let owned = err.to_contained();
1915 let shared = owned.to_shared();
1916 assert_eq!(shared, err, "to_contained -> to_shared must be lossless");
1917 assert!(!err.to_string().is_empty());
1919 assert_eq!(shared.to_string(), err.to_string());
1920 }
1921 }
1922
1923 #[test]
1924 fn parse_errors_round_trip_from_real_parse_failures() {
1925 for input in ["", "px", "\u{200B}10px", "ten-px", "%"] {
1928 let err = parse_pixel_value(input).unwrap_err();
1929 let owned = err.to_contained();
1930 assert_eq!(owned.to_shared(), err, "round-trip failed for {input:?}");
1931 }
1932 }
1933
1934 #[test]
1937 fn float_constructors_never_leak_a_non_finite_value() {
1938 type Ctor = (fn(f32) -> PixelValue, SizeMetric);
1941 let ctors: [Ctor; 8] = [
1942 (PixelValue::px, SizeMetric::Px),
1943 (PixelValue::em, SizeMetric::Em),
1944 (PixelValue::pt, SizeMetric::Pt),
1945 (PixelValue::inch, SizeMetric::In),
1946 (PixelValue::cm, SizeMetric::Cm),
1947 (PixelValue::mm, SizeMetric::Mm),
1948 (PixelValue::percent, SizeMetric::Percent),
1949 (PixelValue::rem, SizeMetric::Rem),
1950 ];
1951
1952 for (ctor, metric) in ctors {
1953 for v in EXTREME_F32 {
1954 let px = ctor(v);
1955 assert_eq!(px.metric, metric, "constructor lost its metric for {v}");
1956 assert!(
1957 px.number.get().is_finite(),
1958 "{metric:?} constructor leaked a non-finite value for input {v}"
1959 );
1960 }
1961 assert_eq!(ctor(f32::NAN).number.get(), 0.0, "NaN must sanitize to 0");
1962 assert!(ctor(f32::INFINITY).number.get() > 0.0);
1963 assert!(ctor(f32::NEG_INFINITY).number.get() < 0.0);
1964 }
1965
1966 for metric in ALL_METRICS {
1968 for v in EXTREME_F32 {
1969 let px = PixelValue::from_metric(metric, v);
1970 assert_eq!(px.metric, metric);
1971 assert!(px.number.get().is_finite());
1972 }
1973 assert_eq!(
1974 PixelValue::from_metric(metric, 12.0),
1975 PixelValue {
1976 metric,
1977 number: FloatValue::new(12.0)
1978 }
1979 );
1980 }
1981 }
1982
1983 #[test]
1984 fn float_constructors_quantize_to_one_thousandth() {
1985 assert_eq!(PixelValue::px(0.0004).number.get(), 0.0);
1987 assert_eq!(PixelValue::px(-0.0009).number.get(), 0.0);
1988 assert_eq!(PixelValue::px(1.0005).number.get(), 1.0);
1990
1991 assert_eq!(PixelValue::px(-0.0), PixelValue::px(0.0));
1994 assert_eq!(
1995 hash_of(&PixelValue::px(-0.0)),
1996 hash_of(&PixelValue::px(0.0))
1997 );
1998 assert_eq!(PixelValue::px(f32::NAN), PixelValue::px(f32::NAN));
2000 assert_eq!(PixelValue::px(f32::NAN), PixelValue::zero());
2001 }
2002
2003 #[test]
2004 fn const_constructors_agree_with_their_float_twins() {
2005 assert_eq!(PixelValue::const_px(5), PixelValue::px(5.0));
2006 assert_eq!(PixelValue::const_em(5), PixelValue::em(5.0));
2007 assert_eq!(PixelValue::const_pt(5), PixelValue::pt(5.0));
2008 assert_eq!(PixelValue::const_percent(5), PixelValue::percent(5.0));
2009 assert_eq!(PixelValue::const_in(5), PixelValue::inch(5.0));
2010 assert_eq!(PixelValue::const_cm(5), PixelValue::cm(5.0));
2011 assert_eq!(PixelValue::const_mm(5), PixelValue::mm(5.0));
2012
2013 assert_eq!(PixelValue::const_px(0), PixelValue::zero());
2014 assert_eq!(PixelValue::const_px(-7), PixelValue::px(-7.0));
2015
2016 for metric in ALL_METRICS {
2017 assert_eq!(
2018 PixelValue::const_from_metric(metric, 7),
2019 PixelValue::from_metric(metric, 7.0),
2020 "const_from_metric disagrees with from_metric for {metric:?}"
2021 );
2022 assert_eq!(
2023 PixelValue::const_from_metric(metric, -7),
2024 PixelValue::from_metric(metric, -7.0)
2025 );
2026 }
2027 }
2028
2029 #[test]
2030 fn const_constructors_are_usable_up_to_the_documented_isize_bound() {
2031 for v in [0, 1, -1, MAX_SAFE_CONST, MIN_SAFE_CONST] {
2036 let px = PixelValue::const_px(v);
2037 assert!(
2038 px.number.get().is_finite(),
2039 "const_px({v}) leaked a non-finite value"
2040 );
2041 }
2042 assert!(PixelValue::const_px(MAX_SAFE_CONST).number.get() > 0.0);
2043 assert!(PixelValue::const_px(MIN_SAFE_CONST).number.get() < 0.0);
2044 assert_eq!(
2045 PixelValue::const_px(MAX_SAFE_CONST).number.number(),
2046 MAX_SAFE_CONST * 1000
2047 );
2048 }
2049
2050 #[test]
2051 fn const_fractional_constructors_match_their_documented_examples() {
2052 assert!(approx(
2054 PixelValue::const_em_fractional(1, 5).number.get(),
2055 1.5
2056 ));
2057 assert!(approx(
2058 PixelValue::const_em_fractional(0, 83).number.get(),
2059 0.83
2060 ));
2061 assert!(approx(
2062 PixelValue::const_em_fractional(1, 17).number.get(),
2063 1.17
2064 ));
2065 assert_eq!(PixelValue::const_em_fractional(1, 5).metric, SizeMetric::Em);
2066 assert_eq!(PixelValue::const_pt_fractional(1, 5).metric, SizeMetric::Pt);
2067 assert!(approx(
2068 PixelValue::const_pt_fractional(2, 25).number.get(),
2069 2.25
2070 ));
2071
2072 assert_eq!(
2075 PixelValue::const_from_metric_fractional(SizeMetric::Px, 0, 0),
2076 PixelValue::zero()
2077 );
2078 assert!(approx(
2079 PixelValue::const_from_metric_fractional(SizeMetric::Px, -1, 5)
2080 .number
2081 .get(),
2082 -1.5
2083 ));
2084
2085 assert!(approx(
2088 PixelValue::const_from_metric_fractional(SizeMetric::Px, 1, 5234)
2089 .number
2090 .get(),
2091 1.523
2092 ));
2093
2094 let extreme = PixelValue::const_from_metric_fractional(SizeMetric::Px, 0, isize::MAX);
2097 assert!(extreme.number.get().is_finite());
2098 }
2099
2100 #[test]
2101 fn scale_for_dpi_is_defined_for_every_scale_factor() {
2102 let mut doubled = PixelValue::px(10.0);
2103 doubled.scale_for_dpi(2.0);
2104 assert_eq!(doubled, PixelValue::px(20.0));
2105
2106 doubled.scale_for_dpi(2.0);
2109 assert_eq!(doubled, PixelValue::px(40.0));
2110
2111 let mut zeroed = PixelValue::em(3.0);
2112 zeroed.scale_for_dpi(0.0);
2113 assert_eq!(zeroed, PixelValue::em(0.0));
2114 assert_eq!(zeroed.metric, SizeMetric::Em, "metric must be preserved");
2115
2116 let mut flipped = PixelValue::px(10.0);
2117 flipped.scale_for_dpi(-1.5);
2118 assert_eq!(flipped, PixelValue::px(-15.0));
2119
2120 let mut nan_scaled = PixelValue::px(10.0);
2122 nan_scaled.scale_for_dpi(f32::NAN);
2123 assert_eq!(nan_scaled.number.get(), 0.0);
2124
2125 let mut inf_scaled = PixelValue::px(10.0);
2126 inf_scaled.scale_for_dpi(f32::INFINITY);
2127 assert!(inf_scaled.number.get().is_finite() && inf_scaled.number.get() > 0.0);
2128
2129 let mut max_scaled = PixelValue::px(f32::MAX);
2130 max_scaled.scale_for_dpi(f32::MAX);
2131 assert!(max_scaled.number.get().is_finite());
2132
2133 let mut wrapped = PixelValueNoPercent::from(PixelValue::px(10.0));
2135 wrapped.scale_for_dpi(2.5);
2136 assert_eq!(wrapped.inner, PixelValue::px(25.0));
2137
2138 let mut wrapped_nan = PixelValueNoPercent::from(PixelValue::px(10.0));
2139 wrapped_nan.scale_for_dpi(f32::NAN);
2140 assert_eq!(wrapped_nan.inner.number.get(), 0.0);
2141 }
2142
2143 #[test]
2144 fn interpolate_within_one_metric_keeps_that_metric() {
2145 let a = PixelValue::em(1.0);
2146 let b = PixelValue::em(3.0);
2147
2148 assert_eq!(a.interpolate(&b, 0.0), a);
2149 assert_eq!(a.interpolate(&b, 1.0), b);
2150 assert_eq!(a.interpolate(&b, 0.5), PixelValue::em(2.0));
2151
2152 assert_eq!(a.interpolate(&b, 2.0), PixelValue::em(5.0));
2154 assert_eq!(a.interpolate(&b, -1.0), PixelValue::em(-1.0));
2155
2156 let p = PixelValue::percent(0.0).interpolate(&PixelValue::percent(100.0), 0.5);
2158 assert_eq!(p, PixelValue::percent(50.0));
2159 assert_eq!(p.metric, SizeMetric::Percent);
2160
2161 let nan_t = a.interpolate(&b, f32::NAN);
2163 assert_eq!(nan_t.number.get(), 0.0);
2164 assert_eq!(nan_t.metric, SizeMetric::Em);
2165 assert!(a.interpolate(&b, f32::INFINITY).number.get().is_finite());
2166 }
2167
2168 #[test]
2169 fn interpolate_across_metrics_falls_back_to_px() {
2170 let from_px = PixelValue::px(0.0);
2172 let to_em = PixelValue::em(1.0); let mid = from_px.interpolate(&to_em, 0.5);
2174 assert_eq!(mid.metric, SizeMetric::Px);
2175 assert!(approx(mid.number.get(), DEFAULT_FONT_SIZE / 2.0));
2176
2177 assert!(approx(
2178 PixelValue::px(0.0)
2179 .interpolate(&PixelValue::pt(72.0), 1.0)
2180 .number
2181 .get(),
2182 96.0
2183 ));
2184
2185 for metric in [
2189 SizeMetric::Percent,
2190 SizeMetric::Vw,
2191 SizeMetric::Vh,
2192 SizeMetric::Vmin,
2193 SizeMetric::Vmax,
2194 ] {
2195 let other = PixelValue::from_metric(metric, 50.0);
2196 let done = PixelValue::px(100.0).interpolate(&other, 1.0);
2197 assert_eq!(
2198 done,
2199 PixelValue::px(0.0),
2200 "{metric:?} should collapse to 0px on the cross-metric path"
2201 );
2202 }
2203
2204 let nan_t = PixelValue::px(0.0).interpolate(&PixelValue::em(1.0), f32::NAN);
2205 assert_eq!(nan_t.number.get(), 0.0);
2206 }
2207
2208 #[test]
2214 fn to_pixels_absolute_resolves_absolute_units_and_refuses_relative_ones() {
2215 for (v, expected) in [
2216 (PixelValue::px(24.0), 24.0),
2217 (PixelValue::pt(12.0), 12.0 * PT_TO_PX),
2218 (PixelValue::from_metric(SizeMetric::In, 1.0), 96.0),
2219 (PixelValue::from_metric(SizeMetric::Cm, 2.54), 96.0),
2220 (PixelValue::from_metric(SizeMetric::Mm, 25.4), 96.0),
2221 ] {
2222 match v.to_pixels_absolute() {
2223 OptionF32::Some(px) => assert!(
2224 (px - expected).abs() < 0.001,
2225 "{v:?} resolved to {px}, expected {expected}"
2226 ),
2227 OptionF32::None => panic!("{v:?} is absolute and must resolve"),
2228 }
2229 }
2230 }
2231
2232 #[test]
2236 fn to_pixels_absolute_returns_none_for_every_context_dependent_unit() {
2237 for v in [
2238 PixelValue::em(2.0),
2239 PixelValue::rem(2.0),
2240 PixelValue::percent(50.0),
2241 PixelValue::from_metric(SizeMetric::Vw, 50.0),
2242 PixelValue::from_metric(SizeMetric::Vh, 50.0),
2243 PixelValue::from_metric(SizeMetric::Vmin, 50.0),
2244 PixelValue::from_metric(SizeMetric::Vmax, 50.0),
2245 ] {
2246 assert_eq!(
2247 v.to_pixels_absolute(),
2248 OptionF32::None,
2249 "{v:?} needs a context and must not answer with a number"
2250 );
2251 }
2252 }
2253
2254 #[test]
2258 fn is_absolute_agrees_with_to_pixels_absolute() {
2259 for v in [
2260 PixelValue::px(1.0),
2261 PixelValue::pt(1.0),
2262 PixelValue::from_metric(SizeMetric::In, 1.0),
2263 PixelValue::from_metric(SizeMetric::Cm, 1.0),
2264 PixelValue::from_metric(SizeMetric::Mm, 1.0),
2265 PixelValue::em(1.0),
2266 PixelValue::rem(1.0),
2267 PixelValue::percent(1.0),
2268 PixelValue::from_metric(SizeMetric::Vw, 1.0),
2269 PixelValue::from_metric(SizeMetric::Vh, 1.0),
2270 PixelValue::from_metric(SizeMetric::Vmin, 1.0),
2271 PixelValue::from_metric(SizeMetric::Vmax, 1.0),
2272 ] {
2273 assert_eq!(
2274 v.is_absolute(),
2275 v.to_pixels_absolute() != OptionF32::None,
2276 "{v:?}: is_absolute() and to_pixels_absolute() disagree"
2277 );
2278 }
2279 }
2280
2281 #[test]
2284 fn to_pixels_matches_the_internal_resolver() {
2285 for v in [
2286 PixelValue::px(3.0),
2287 PixelValue::em(3.0),
2288 PixelValue::rem(3.0),
2289 PixelValue::percent(50.0),
2290 ] {
2291 assert_eq!(
2292 v.to_pixels(200.0, 16.0, 10.0),
2293 v.to_pixels_internal(200.0, 16.0, 10.0),
2294 "{v:?} diverged from the internal resolver"
2295 );
2296 }
2297 }
2298
2299 #[test]
2300 fn to_pixels_internal_converts_every_absolute_and_relative_unit() {
2301 assert_eq!(
2302 PixelValue::px(10.0).to_pixels_internal(0.0, 16.0, 16.0),
2303 10.0
2304 );
2305 assert!(approx(
2306 PixelValue::pt(72.0).to_pixels_internal(0.0, 16.0, 16.0),
2307 96.0
2308 ));
2309 assert!(approx(
2310 PixelValue::inch(1.0).to_pixels_internal(0.0, 16.0, 16.0),
2311 96.0
2312 ));
2313 assert!(approx(
2314 PixelValue::cm(2.54).to_pixels_internal(0.0, 16.0, 16.0),
2315 96.0
2316 ));
2317 assert!(approx(
2318 PixelValue::mm(25.4).to_pixels_internal(0.0, 16.0, 16.0),
2319 96.0
2320 ));
2321 assert_eq!(PT_TO_PX, 96.0 / 72.0);
2322
2323 assert_eq!(
2326 PixelValue::em(2.0).to_pixels_internal(0.0, 10.0, 100.0),
2327 20.0
2328 );
2329 assert_eq!(
2330 PixelValue::rem(2.0).to_pixels_internal(0.0, 10.0, 100.0),
2331 200.0
2332 );
2333
2334 assert_eq!(
2337 PixelValue::percent(50.0).to_pixels_internal(800.0, 16.0, 16.0),
2338 400.0
2339 );
2340 assert_eq!(
2341 PixelValue::percent(0.0).to_pixels_internal(800.0, 16.0, 16.0),
2342 0.0
2343 );
2344 assert_eq!(
2345 PixelValue::percent(-50.0).to_pixels_internal(800.0, 16.0, 16.0),
2346 -400.0
2347 );
2348
2349 for metric in [
2352 SizeMetric::Vw,
2353 SizeMetric::Vh,
2354 SizeMetric::Vmin,
2355 SizeMetric::Vmax,
2356 ] {
2357 assert_eq!(
2358 PixelValue::from_metric(metric, 50.0).to_pixels_internal(800.0, 16.0, 16.0),
2359 0.0,
2360 "{metric:?} must resolve to 0 on the legacy path"
2361 );
2362 }
2363 }
2364
2365 #[test]
2366 fn to_pixels_internal_non_finite_resolves_are_defined_not_panics() {
2367 assert!(PixelValue::em(1.0)
2370 .to_pixels_internal(0.0, f32::NAN, 0.0)
2371 .is_nan());
2372 assert!(PixelValue::rem(1.0)
2373 .to_pixels_internal(0.0, 0.0, f32::INFINITY)
2374 .is_infinite());
2375 assert!(PixelValue::percent(50.0)
2376 .to_pixels_internal(f32::INFINITY, 16.0, 16.0)
2377 .is_infinite());
2378 assert!(PixelValue::percent(50.0)
2379 .to_pixels_internal(f32::NAN, 16.0, 16.0)
2380 .is_nan());
2381 assert!(PixelValue::percent(0.0)
2383 .to_pixels_internal(f32::INFINITY, 16.0, 16.0)
2384 .is_nan());
2385
2386 assert!(PixelValue::em(f32::MAX)
2388 .to_pixels_internal(0.0, f32::MAX, 0.0)
2389 .is_infinite());
2390
2391 for v in EXTREME_F32 {
2393 assert!(PixelValue::px(v)
2394 .to_pixels_internal(f32::NAN, f32::NAN, f32::NAN)
2395 .is_finite());
2396 }
2397 }
2398
2399 #[test]
2400 fn pixel_value_no_percent_to_pixels_internal_zeroes_out_percentages() {
2401 assert_eq!(
2402 PixelValueNoPercent::from(PixelValue::px(10.0)).to_pixels_internal(16.0, 16.0),
2403 10.0
2404 );
2405 assert_eq!(
2406 PixelValueNoPercent::from(PixelValue::em(2.0)).to_pixels_internal(10.0, 100.0),
2407 20.0
2408 );
2409 assert_eq!(
2410 PixelValueNoPercent::from(PixelValue::rem(2.0)).to_pixels_internal(10.0, 100.0),
2411 200.0
2412 );
2413
2414 assert_eq!(
2417 PixelValueNoPercent::from(PixelValue::percent(50.0)).to_pixels_internal(16.0, 16.0),
2418 0.0
2419 );
2420 assert_eq!(
2421 PixelValueNoPercent::zero().to_pixels_internal(16.0, 16.0),
2422 0.0
2423 );
2424 assert_eq!(PixelValueNoPercent::zero().inner, PixelValue::zero());
2425 assert_eq!(PixelValueNoPercent::default().inner, PixelValue::zero());
2426 }
2427
2428 #[test]
2431 fn to_percent_is_some_only_for_the_percent_metric() {
2432 for metric in ALL_METRICS {
2433 let v = PixelValue::from_metric(metric, 50.0);
2434 if metric == SizeMetric::Percent {
2435 assert_eq!(
2436 v.to_percent().unwrap().get(),
2437 0.5,
2438 "50% must normalize to 0.5"
2439 );
2440 } else {
2441 assert!(
2442 v.to_percent().is_none(),
2443 "{metric:?} must not masquerade as a percentage"
2444 );
2445 }
2446 }
2447
2448 assert_eq!(
2451 PixelValue::percent(50.0)
2452 .to_percent()
2453 .unwrap()
2454 .resolve(640.0),
2455 320.0
2456 );
2457 assert_eq!(PixelValue::percent(-50.0).to_percent().unwrap().get(), -0.5);
2458 assert_eq!(PixelValue::percent(0.0).to_percent().unwrap().get(), 0.0);
2459 assert!(PixelValue::percent(f32::MAX)
2461 .to_percent()
2462 .unwrap()
2463 .get()
2464 .is_finite());
2465 assert_eq!(
2466 PixelValue::percent(f32::NAN).to_percent().unwrap().get(),
2467 0.0
2468 );
2469 }
2470
2471 #[test]
2472 fn normalized_percentage_new_and_from_unnormalized_disagree_by_100x() {
2473 assert_eq!(NormalizedPercentage::new(0.5).get(), 0.5);
2476 assert_eq!(NormalizedPercentage::from_unnormalized(50.0).get(), 0.5);
2477 assert_eq!(NormalizedPercentage::from_unnormalized(0.0).get(), 0.0);
2478 assert_eq!(NormalizedPercentage::from_unnormalized(100.0).get(), 1.0);
2479 assert_eq!(NormalizedPercentage::from_unnormalized(-25.0).get(), -0.25);
2480
2481 assert_eq!(NormalizedPercentage::new(0.5).resolve(640.0), 320.0);
2482 assert_eq!(NormalizedPercentage::new(0.0).resolve(640.0), 0.0);
2483 assert_eq!(NormalizedPercentage::new(1.0).resolve(f32::MAX), f32::MAX);
2484 assert_eq!(NormalizedPercentage::new(-1.0).resolve(100.0), -100.0);
2485
2486 assert!(NormalizedPercentage::new(f32::NAN).get().is_nan());
2489 assert!(NormalizedPercentage::new(f32::NAN).resolve(100.0).is_nan());
2490 assert!(NormalizedPercentage::from_unnormalized(f32::INFINITY)
2491 .get()
2492 .is_infinite());
2493 assert!(NormalizedPercentage::new(1.0)
2494 .resolve(f32::INFINITY)
2495 .is_infinite());
2496 assert!(NormalizedPercentage::new(0.0)
2498 .resolve(f32::INFINITY)
2499 .is_nan());
2500
2501 assert_eq!(NormalizedPercentage::new(0.5).to_string(), "50%");
2503 assert_eq!(NormalizedPercentage::new(0.0).to_string(), "0%");
2504 assert!(!NormalizedPercentage::new(f32::NAN).to_string().is_empty());
2505 assert!(!NormalizedPercentage::new(f32::INFINITY)
2506 .to_string()
2507 .is_empty());
2508 }
2509
2510 #[test]
2511 fn resolve_with_context_reads_the_right_reference_for_each_property() {
2512 let ctx = distinct_context(); assert_eq!(
2518 PixelValue::em(2.0).resolve_with_context(&ctx, PropertyContext::Margin),
2519 64.0
2520 );
2521 assert_eq!(
2522 PixelValue::em(2.0).resolve_with_context(&ctx, PropertyContext::FontSize),
2523 16.0
2524 );
2525
2526 for pc in ALL_PROPERTY_CONTEXTS {
2528 assert_eq!(
2529 PixelValue::rem(2.0).resolve_with_context(&ctx, pc),
2530 8.0,
2531 "rem must ignore the property context ({pc:?})"
2532 );
2533 }
2534
2535 let pct = PixelValue::percent(50.0);
2537 assert_eq!(
2538 pct.resolve_with_context(&ctx, PropertyContext::Width),
2539 400.0,
2540 "width % -> containing block WIDTH"
2541 );
2542 assert_eq!(
2543 pct.resolve_with_context(&ctx, PropertyContext::Height),
2544 300.0,
2545 "height % -> containing block HEIGHT"
2546 );
2547 assert_eq!(
2548 pct.resolve_with_context(&ctx, PropertyContext::Margin),
2549 400.0,
2550 "margin % -> containing block WIDTH, even vertically (CSS 2.1 §8.3)"
2551 );
2552 assert_eq!(
2553 pct.resolve_with_context(&ctx, PropertyContext::Padding),
2554 400.0,
2555 "padding % -> containing block WIDTH, even vertically (CSS 2.1 §8.4)"
2556 );
2557 assert_eq!(
2558 pct.resolve_with_context(&ctx, PropertyContext::Other),
2559 400.0
2560 );
2561 assert_eq!(
2562 pct.resolve_with_context(&ctx, PropertyContext::FontSize),
2563 4.0,
2564 "font-size % -> PARENT font size"
2565 );
2566 assert_eq!(
2567 pct.resolve_with_context(&ctx, PropertyContext::BorderRadius),
2568 100.0,
2569 "border-radius % -> the element's own box"
2570 );
2571 assert_eq!(
2572 pct.resolve_with_context(&ctx, PropertyContext::Transform),
2573 100.0
2574 );
2575 assert_eq!(
2576 pct.resolve_with_context(&ctx, PropertyContext::BorderWidth),
2577 0.0,
2578 "% is invalid on border-width (CSS Backgrounds 3 §4.1) -> 0"
2579 );
2580 }
2581
2582 #[test]
2583 fn resolve_with_context_percent_without_an_element_size_is_zero() {
2584 let ctx = ResolutionContext {
2587 vertical_writing_mode: false,
2588 element_size: None,
2589 ..distinct_context()
2590 };
2591 assert_eq!(
2592 PixelValue::percent(50.0).resolve_with_context(&ctx, PropertyContext::BorderRadius),
2593 0.0
2594 );
2595 assert_eq!(
2596 PixelValue::percent(50.0).resolve_with_context(&ctx, PropertyContext::Transform),
2597 0.0
2598 );
2599 }
2600
2601 #[test]
2602 fn resolve_with_context_absolute_units_ignore_the_context_entirely() {
2603 let sane = distinct_context();
2604 let poisoned = ResolutionContext {
2605 vertical_writing_mode: false,
2606 element_font_size: f32::NAN,
2607 parent_font_size: f32::INFINITY,
2608 root_font_size: f32::NEG_INFINITY,
2609 containing_block_size: PhysicalSize::new(f32::NAN, f32::NAN),
2610 element_size: Some(PhysicalSize::new(f32::INFINITY, f32::NAN)),
2611 viewport_size: PhysicalSize::new(f32::NAN, f32::INFINITY),
2612 };
2613
2614 let absolutes = [
2615 PixelValue::px(10.0),
2616 PixelValue::pt(10.0),
2617 PixelValue::inch(10.0),
2618 PixelValue::cm(10.0),
2619 PixelValue::mm(10.0),
2620 ];
2621 for v in absolutes {
2622 for pc in ALL_PROPERTY_CONTEXTS {
2623 let a = v.resolve_with_context(&sane, pc);
2624 let b = v.resolve_with_context(&poisoned, pc);
2625 assert_eq!(a, b, "{:?} must not read the context ({pc:?})", v.metric);
2626 assert!(a.is_finite());
2627 }
2628 }
2629
2630 assert_eq!(
2632 PixelValue::px(10.0).resolve_with_context(&sane, PropertyContext::Width),
2633 10.0
2634 );
2635 assert!(approx(
2636 PixelValue::inch(1.0).resolve_with_context(&sane, PropertyContext::Width),
2637 96.0
2638 ));
2639 assert!(approx(
2640 PixelValue::pt(72.0).resolve_with_context(&sane, PropertyContext::Width),
2641 96.0
2642 ));
2643 assert!(approx(
2644 PixelValue::cm(2.54).resolve_with_context(&sane, PropertyContext::Width),
2645 96.0
2646 ));
2647 assert!(approx(
2648 PixelValue::mm(25.4).resolve_with_context(&sane, PropertyContext::Width),
2649 96.0
2650 ));
2651 }
2652
2653 #[test]
2654 fn resolve_with_context_viewport_units_use_the_viewport() {
2655 let ctx = distinct_context(); assert_eq!(
2658 PixelValue::from_metric(SizeMetric::Vw, 10.0)
2659 .resolve_with_context(&ctx, PropertyContext::Width),
2660 100.0
2661 );
2662 assert_eq!(
2663 PixelValue::from_metric(SizeMetric::Vh, 10.0)
2664 .resolve_with_context(&ctx, PropertyContext::Width),
2665 50.0
2666 );
2667 assert_eq!(
2668 PixelValue::from_metric(SizeMetric::Vmin, 10.0)
2669 .resolve_with_context(&ctx, PropertyContext::Width),
2670 50.0,
2671 "vmin must take the SMALLER viewport dimension"
2672 );
2673 assert_eq!(
2674 PixelValue::from_metric(SizeMetric::Vmax, 10.0)
2675 .resolve_with_context(&ctx, PropertyContext::Width),
2676 100.0,
2677 "vmax must take the LARGER viewport dimension"
2678 );
2679
2680 let zero_vp = ResolutionContext::default_const();
2683 for metric in [
2684 SizeMetric::Vw,
2685 SizeMetric::Vh,
2686 SizeMetric::Vmin,
2687 SizeMetric::Vmax,
2688 ] {
2689 assert_eq!(
2690 PixelValue::from_metric(metric, 100.0)
2691 .resolve_with_context(&zero_vp, PropertyContext::Width),
2692 0.0,
2693 "{metric:?} against a 0x0 viewport must be 0"
2694 );
2695 }
2696
2697 let nan_vp = ResolutionContext {
2699 vertical_writing_mode: false,
2700 viewport_size: PhysicalSize::new(f32::NAN, f32::NAN),
2701 ..distinct_context()
2702 };
2703 assert!(PixelValue::from_metric(SizeMetric::Vw, 10.0)
2704 .resolve_with_context(&nan_vp, PropertyContext::Width)
2705 .is_nan());
2706 let half_nan_vp = ResolutionContext {
2709 vertical_writing_mode: false,
2710 viewport_size: PhysicalSize::new(f32::NAN, 500.0),
2711 ..distinct_context()
2712 };
2713 assert_eq!(
2714 PixelValue::from_metric(SizeMetric::Vmin, 10.0)
2715 .resolve_with_context(&half_nan_vp, PropertyContext::Width),
2716 50.0
2717 );
2718 }
2719
2720 #[test]
2721 fn resolve_with_context_never_panics_on_extreme_values() {
2722 let ctx = distinct_context();
2723 for metric in ALL_METRICS {
2724 for v in EXTREME_F32 {
2725 for pc in ALL_PROPERTY_CONTEXTS {
2726 let _ = PixelValue::from_metric(metric, v).resolve_with_context(&ctx, pc);
2728 }
2729 }
2730 }
2731 }
2732
2733 #[test]
2734 fn resolution_context_default_matches_default_const() {
2735 let a = ResolutionContext::default();
2737 let b = ResolutionContext::default_const();
2738
2739 assert_eq!(a.element_font_size, b.element_font_size);
2740 assert_eq!(a.parent_font_size, b.parent_font_size);
2741 assert_eq!(a.root_font_size, b.root_font_size);
2742 assert_eq!(a.containing_block_size, b.containing_block_size);
2743 assert_eq!(a.element_size, b.element_size);
2744 assert_eq!(a.viewport_size, b.viewport_size);
2745
2746 assert_eq!(a.element_font_size, DEFAULT_FONT_SIZE);
2748 assert!(a.element_size.is_none());
2749 }
2750
2751 #[test]
2752 fn logical_and_physical_sizes_round_trip() {
2753 let logical = CssLogicalSize::new(800.0, 600.0);
2754 assert_eq!(logical.to_physical(), PhysicalSize::new(800.0, 600.0));
2755 assert_eq!(logical.to_physical().to_logical(), logical);
2756
2757 let physical = PhysicalSize::new(1920.0, 1080.0);
2758 assert_eq!(physical.to_logical(), CssLogicalSize::new(1920.0, 1080.0));
2759 assert_eq!(physical.to_logical().to_physical(), physical);
2760
2761 assert_eq!(CssLogicalSize::new(800.0, 600.0).to_physical().width, 800.0);
2764 assert_eq!(
2765 PhysicalSize::new(800.0, 600.0).to_logical().block_size,
2766 600.0
2767 );
2768
2769 let nan = PhysicalSize::new(f32::NAN, f32::INFINITY);
2771 assert!(nan.to_logical().inline_size.is_nan());
2772 assert!(nan.to_logical().block_size.is_infinite());
2773 }
2774
2775 #[test]
2778 fn every_rendering_of_a_pixel_value_agrees() {
2779 for metric in ALL_METRICS {
2782 let v = PixelValue::from_metric(metric, 1.5);
2783 let display = v.to_string();
2784 assert_eq!(format!("{v:?}"), display, "Debug != Display for {metric:?}");
2785 assert_eq!(v.print_as_css_value(), display);
2786 assert_eq!(as_css_value(v), display);
2787 assert!(display.starts_with("1.5"), "{display} lost its number");
2788 assert!(display.len() > 3, "{display} lost its unit");
2789 }
2790
2791 assert_eq!(PixelValue::px(10.0).to_string(), "10px");
2792 assert_eq!(PixelValue::percent(50.0).to_string(), "50%");
2793 assert_eq!(PixelValue::zero().to_string(), "0px");
2794 assert_eq!(
2795 PixelValue::from_metric(SizeMetric::Vmin, 12.0).to_string(),
2796 "12vmin"
2797 );
2798
2799 let np = PixelValueNoPercent::from(PixelValue::px(10.0));
2801 assert_eq!(np.to_string(), "10px");
2802 assert_eq!(format!("{np:?}"), "10px");
2803 assert_eq!(PixelValueNoPercent::zero().to_string(), "0px");
2804 }
2805
2806 #[test]
2807 fn display_never_leaks_nan_or_infinity_into_css() {
2808 for metric in ALL_METRICS {
2812 for v in EXTREME_F32 {
2813 let s = PixelValue::from_metric(metric, v).to_string();
2814 assert!(
2815 !s.contains("NaN") && !s.contains("inf"),
2816 "{metric:?} with input {v} serialized to {s:?}"
2817 );
2818 assert!(!s.is_empty());
2819 }
2820 }
2821 assert_eq!(PixelValue::px(f32::NAN).to_string(), "0px");
2822 }
2823
2824 #[test]
2825 fn pixel_values_round_trip_through_css_for_every_metric_but_vmin() {
2826 for metric in ALL_METRICS {
2828 if metric == SizeMetric::Vmin {
2829 continue; }
2831 for number in [0.0_f32, 1.0, 1.5, -20.0, 0.001, 12345.0] {
2832 let original = PixelValue::from_metric(metric, number);
2833 let css = original.print_as_css_value();
2834 let reparsed = parse_pixel_value(&css).unwrap_or_else(|e| {
2835 panic!("{css:?} (from {metric:?} {number}) failed to re-parse: {e:?}")
2836 });
2837 assert_eq!(reparsed, original, "round-trip broke for {css:?}");
2838 assert_eq!(reparsed.print_as_css_value(), css);
2840 }
2841 }
2842
2843 for metric in ALL_METRICS {
2845 if metric == SizeMetric::Vmin || metric == SizeMetric::Percent {
2846 continue;
2847 }
2848 let original = PixelValueNoPercent::from(PixelValue::from_metric(metric, 7.0));
2849 let css = original.to_string();
2850 assert_eq!(
2851 parse_pixel_value_no_percent(&css).unwrap(),
2852 original,
2853 "no-percent round-trip broke for {css:?}"
2854 );
2855 }
2856
2857 for (css, expected) in [
2859 ("auto", PixelValueWithAuto::Auto),
2860 ("none", PixelValueWithAuto::None),
2861 ("initial", PixelValueWithAuto::Initial),
2862 ("inherit", PixelValueWithAuto::Inherit),
2863 ] {
2864 assert_eq!(parse_pixel_value_with_auto(css).unwrap(), expected);
2865 }
2866 let exact = PixelValue::em(1.5);
2867 assert_eq!(
2868 parse_pixel_value_with_auto(&exact.print_as_css_value()).unwrap(),
2869 PixelValueWithAuto::Exact(exact)
2870 );
2871 }
2872
2873 #[test]
2874 fn format_as_rust_code_emits_a_reconstructible_literal() {
2875 assert_eq!(
2876 PixelValue::px(10.0).format_as_rust_code(0),
2877 "PixelValue { metric: Px, number: FloatValue::new(10) }"
2878 );
2879 assert_eq!(
2880 PixelValue::percent(-1.5).format_as_rust_code(4),
2881 "PixelValue { metric: Percent, number: FloatValue::new(-1.5) }"
2882 );
2883 let nan = PixelValue::from_metric(SizeMetric::Vmax, f32::NAN).format_as_rust_code(0);
2885 assert_eq!(
2886 nan,
2887 "PixelValue { metric: Vmax, number: FloatValue::new(0) }"
2888 );
2889 assert!(!PixelValue::px(f32::INFINITY)
2890 .format_as_rust_code(0)
2891 .contains("inf"));
2892 }
2893
2894 #[test]
2895 fn border_thickness_constants_match_the_css_keywords() {
2896 assert_eq!(THIN_BORDER_THICKNESS, PixelValue::px(1.0));
2899 assert_eq!(MEDIUM_BORDER_THICKNESS, PixelValue::px(3.0));
2900 assert_eq!(THICK_BORDER_THICKNESS, PixelValue::px(5.0));
2901
2902 assert_eq!(THIN_BORDER_THICKNESS.number.get(), 1.0);
2903 assert_eq!(MEDIUM_BORDER_THICKNESS.number.get(), 3.0);
2904 assert_eq!(THICK_BORDER_THICKNESS.number.get(), 5.0);
2905 assert_eq!(THIN_BORDER_THICKNESS.number.number() as f32, MULT);
2906
2907 assert!(THIN_BORDER_THICKNESS < MEDIUM_BORDER_THICKNESS);
2908 assert!(MEDIUM_BORDER_THICKNESS < THICK_BORDER_THICKNESS);
2909 assert_eq!(THIN_BORDER_THICKNESS.to_string(), "1px");
2910 }
2911
2912 #[test]
2913 fn ord_is_lexicographic_by_metric_then_number_not_by_resolved_size() {
2914 assert!(PixelValue::px(100.0) < PixelValue::em(1.0));
2918 assert!(PixelValue::px(1.0) < PixelValue::px(2.0));
2919 assert!(PixelValue::percent(1.0) > PixelValue::mm(9999.0));
2920
2921 let a = PixelValue::px(1.5);
2923 let b = PixelValue::px(1.5);
2924 assert_eq!(a, b);
2925 assert_eq!(hash_of(&a), hash_of(&b));
2926 assert_ne!(hash_of(&PixelValue::px(1.0)), hash_of(&PixelValue::em(1.0)));
2927
2928 assert_eq!(PixelValue::px(1.0001), PixelValue::px(1.0002));
2931 assert_eq!(
2932 hash_of(&PixelValue::px(1.0001)),
2933 hash_of(&PixelValue::px(1.0002))
2934 );
2935 }
2936
2937 #[test]
2940 fn system_metric_ref_css_strings_round_trip() {
2941 for r in ALL_SYSTEM_REFS {
2942 let css = r.as_css_str();
2943 assert!(
2944 css.starts_with("system:"),
2945 "{css:?} is missing the system: prefix"
2946 );
2947 assert_eq!(r.to_string(), css, "Display must match as_css_str");
2948 assert_eq!(as_css_value(r), css);
2949
2950 let name = css.strip_prefix("system:").unwrap();
2952 assert_eq!(
2953 SystemMetricRef::from_css_str(name),
2954 Some(r),
2955 "{name:?} must parse back to {r:?}"
2956 );
2957 assert_eq!(
2959 SystemMetricRef::from_css_str(name).unwrap().as_css_str(),
2960 css
2961 );
2962
2963 assert_eq!(SystemMetricRef::from_css_str(css), None);
2966 }
2967
2968 assert_eq!(SystemMetricRef::default(), SystemMetricRef::ButtonRadius);
2969 }
2970
2971 #[test]
2972 fn system_metric_ref_from_css_str_rejects_everything_else() {
2973 for input in [
2974 "",
2975 " ",
2976 "\t\n",
2977 " button-radius ", "Button-Radius", "button_radius", "button-padding", "button-radius;x",
2982 "\u{1F600}",
2983 "b\u{0301}utton-radius",
2984 ] {
2985 assert_eq!(
2986 SystemMetricRef::from_css_str(input),
2987 None,
2988 "{input:?} must not resolve to a system metric"
2989 );
2990 }
2991
2992 assert_eq!(SystemMetricRef::from_css_str(&"a".repeat(100_000)), None);
2994 assert_eq!(SystemMetricRef::from_css_str(&"(".repeat(10_000)), None);
2995 }
2996
2997 #[test]
2998 fn system_metric_ref_resolve_maps_each_variant_to_its_own_field() {
2999 let metrics = populated_metrics();
3001 let expected = [
3002 (SystemMetricRef::ButtonRadius, 1.0),
3003 (SystemMetricRef::ButtonBorderWidth, 2.0),
3004 (SystemMetricRef::ButtonPaddingHorizontal, 3.0),
3005 (SystemMetricRef::ButtonPaddingVertical, 4.0),
3006 (SystemMetricRef::TitlebarHeight, 5.0),
3007 (SystemMetricRef::TitlebarButtonWidth, 6.0),
3008 (SystemMetricRef::TitlebarPadding, 7.0),
3009 (SystemMetricRef::SafeAreaTop, 8.0),
3010 (SystemMetricRef::SafeAreaBottom, 9.0),
3011 (SystemMetricRef::SafeAreaLeft, 10.0),
3012 (SystemMetricRef::SafeAreaRight, 11.0),
3013 ];
3014 for (r, px) in expected {
3015 assert_eq!(
3016 r.resolve(&metrics),
3017 Some(PixelValue::px(px)),
3018 "{r:?} resolved to the wrong field"
3019 );
3020 }
3021
3022 let empty = SystemMetrics::default();
3024 for r in ALL_SYSTEM_REFS {
3025 assert_eq!(r.resolve(&empty), None, "{r:?} must be None when unset");
3026 }
3027 }
3028
3029 #[test]
3030 fn pixel_value_or_system_resolves_and_falls_back() {
3031 let metrics = populated_metrics();
3032 let empty = SystemMetrics::default();
3033 let fallback = PixelValue::px(99.0);
3034
3035 let concrete = PixelValueOrSystem::value(PixelValue::px(10.0));
3037 assert_eq!(concrete.resolve(&metrics, fallback), PixelValue::px(10.0));
3038 assert_eq!(concrete.resolve(&empty, fallback), PixelValue::px(10.0));
3039
3040 let sys = PixelValueOrSystem::system(SystemMetricRef::ButtonRadius);
3042 assert_eq!(sys.resolve(&metrics, fallback), PixelValue::px(1.0));
3043 for r in ALL_SYSTEM_REFS {
3045 assert_eq!(
3046 PixelValueOrSystem::system(r).resolve(&empty, fallback),
3047 fallback,
3048 "{r:?} must fall back when the metric is unset"
3049 );
3050 }
3051
3052 let nan_fallback = PixelValue::px(f32::NAN);
3054 assert_eq!(sys.resolve(&empty, nan_fallback).number.get(), 0.0);
3055
3056 assert_eq!(
3058 PixelValueOrSystem::default(),
3059 PixelValueOrSystem::Value(PixelValue::zero())
3060 );
3061 assert_eq!(
3062 PixelValueOrSystem::from(PixelValue::em(2.0)),
3063 PixelValueOrSystem::Value(PixelValue::em(2.0))
3064 );
3065 assert_eq!(
3066 PixelValueOrSystem::default().resolve(&metrics, fallback),
3067 PixelValue::zero()
3068 );
3069 }
3070
3071 #[test]
3072 fn pixel_value_or_system_renders_both_arms() {
3073 let concrete = PixelValueOrSystem::value(PixelValue::px(10.0));
3074 assert_eq!(concrete.to_string(), "10px");
3075 assert_eq!(as_css_value(concrete), "10px");
3076
3077 let sys = PixelValueOrSystem::system(SystemMetricRef::TitlebarHeight);
3078 assert_eq!(sys.to_string(), "system:titlebar-height");
3079 assert_eq!(as_css_value(sys), "system:titlebar-height");
3080
3081 assert_eq!(PixelValueOrSystem::default().to_string(), "0px");
3082
3083 for v in EXTREME_F32 {
3085 let s = PixelValueOrSystem::value(PixelValue::px(v)).to_string();
3086 assert!(!s.contains("NaN") && !s.contains("inf"), "leaked {s:?}");
3087 }
3088 }
3089}