1use core::fmt;
14use std::num::ParseFloatError;
15use crate::corety::AzString;
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] pub const fn new(value: f32) -> Self {
45 Self(value)
46 }
47
48 #[inline]
53 #[must_use] pub fn from_unnormalized(value: f32) -> Self {
54 Self(value / 100.0)
55 }
56
57 #[inline]
59 #[must_use] pub const fn get(self) -> f32 {
60 self.0
61 }
62
63 #[inline]
68 #[must_use] pub fn resolve(self, containing_block_size: f32) -> f32 {
69 self.0 * containing_block_size
70 }
71}
72
73impl fmt::Display for NormalizedPercentage {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(f, "{}%", self.0 * 100.0)
76 }
77}
78
79#[derive(Debug, Copy, Clone, PartialEq)]
81#[repr(C)]
82pub struct CssLogicalSize {
83 pub inline_size: f32,
85 pub block_size: f32,
87}
88
89impl CssLogicalSize {
90 #[inline]
91 #[must_use] pub const fn new(inline_size: f32, block_size: f32) -> Self {
92 Self {
93 inline_size,
94 block_size,
95 }
96 }
97
98 #[inline]
100 #[must_use] pub const fn to_physical(self) -> PhysicalSize {
101 PhysicalSize {
102 width: self.inline_size,
103 height: self.block_size,
104 }
105 }
106}
107
108#[derive(Debug, Copy, Clone, PartialEq)]
110#[repr(C)]
111pub struct PhysicalSize {
112 pub width: f32,
113 pub height: f32,
114}
115
116impl PhysicalSize {
117 #[inline]
118 #[must_use] pub const fn new(width: f32, height: f32) -> Self {
119 Self { width, height }
120 }
121
122 #[inline]
124 #[must_use] pub const fn to_logical(self) -> CssLogicalSize {
125 CssLogicalSize {
126 inline_size: self.width,
127 block_size: self.height,
128 }
129 }
130}
131
132#[derive(Debug, Copy, Clone)]
148pub struct ResolutionContext {
149 pub element_font_size: f32,
151
152 pub parent_font_size: f32,
154
155 pub root_font_size: f32,
157
158 pub containing_block_size: PhysicalSize,
160
161 pub element_size: Option<PhysicalSize>,
164
165 pub vertical_writing_mode: bool,
170
171 pub viewport_size: PhysicalSize,
174}
175
176impl Default for ResolutionContext {
177 fn default() -> Self {
178 Self {
179 element_font_size: 16.0,
180 parent_font_size: 16.0,
181 root_font_size: 16.0,
182 containing_block_size: PhysicalSize::new(0.0, 0.0),
183 element_size: None,
184 viewport_size: PhysicalSize::new(0.0, 0.0),
185 vertical_writing_mode: false,
186 }
187 }
188}
189
190impl ResolutionContext {
191 #[inline]
193 #[must_use] pub const fn default_const() -> Self {
194 Self {
195 element_font_size: 16.0,
196 parent_font_size: 16.0,
197 root_font_size: 16.0,
198 containing_block_size: PhysicalSize {
199 width: 0.0,
200 height: 0.0,
201 },
202 element_size: None,
203 viewport_size: PhysicalSize {
204 width: 0.0,
205 height: 0.0,
206 },
207 vertical_writing_mode: false,
208 }
209 }
210
211}
212
213#[derive(Debug, Copy, Clone, PartialEq, Eq)]
215pub enum PropertyContext {
216 FontSize,
218 Margin,
220 Padding,
222 Width,
224 Height,
226 BorderWidth,
228 BorderRadius,
230 Transform,
232 Other,
234}
235
236#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
238#[repr(C)]
239pub struct PixelValue {
240 pub metric: SizeMetric,
241 pub number: FloatValue,
242}
243
244impl PixelValue {
245 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
246 self.number = FloatValue::new(self.number.get() * scale_factor);
247 }
248}
249
250impl FormatAsCssValue for PixelValue {
251 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 write!(f, "{}{}", self.number, self.metric)
253 }
254}
255
256impl crate::css::PrintAsCssValue for PixelValue {
257 fn print_as_css_value(&self) -> String {
258 format!("{}{}", self.number, self.metric)
259 }
260}
261
262impl crate::codegen::format::FormatAsRustCode for PixelValue {
263 fn format_as_rust_code(&self, _tabs: usize) -> String {
264 format!(
265 "PixelValue {{ metric: {:?}, number: FloatValue::new({}) }}",
266 self.metric,
267 self.number.get()
268 )
269 }
270}
271
272impl fmt::Debug for PixelValue {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 write!(f, "{}{}", self.number, self.metric)
276 }
277}
278
279impl fmt::Display for PixelValue {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 write!(f, "{}{}", self.number, self.metric)
282 }
283}
284
285impl PixelValue {
286 #[inline]
287 #[must_use] pub const fn zero() -> Self {
288 const ZERO_PX: PixelValue = PixelValue::const_px(0);
289 ZERO_PX
290 }
291
292 #[inline]
295 #[must_use] pub const fn const_px(value: isize) -> Self {
296 Self::const_from_metric(SizeMetric::Px, value)
297 }
298
299 #[inline]
302 #[must_use] pub const fn const_em(value: isize) -> Self {
303 Self::const_from_metric(SizeMetric::Em, value)
304 }
305
306 #[inline]
319 #[must_use] pub const fn const_em_fractional(pre_comma: isize, post_comma: isize) -> Self {
320 Self::const_from_metric_fractional(SizeMetric::Em, pre_comma, post_comma)
321 }
322
323 #[inline]
326 #[must_use] pub const fn const_pt(value: isize) -> Self {
327 Self::const_from_metric(SizeMetric::Pt, value)
328 }
329
330 #[inline]
332 #[must_use] pub const fn const_pt_fractional(pre_comma: isize, post_comma: isize) -> Self {
333 Self::const_from_metric_fractional(SizeMetric::Pt, pre_comma, post_comma)
334 }
335
336 #[inline]
339 #[must_use] pub const fn const_percent(value: isize) -> Self {
340 Self::const_from_metric(SizeMetric::Percent, value)
341 }
342
343 #[inline]
346 #[must_use] pub const fn const_in(value: isize) -> Self {
347 Self::const_from_metric(SizeMetric::In, value)
348 }
349
350 #[inline]
353 #[must_use] pub const fn const_cm(value: isize) -> Self {
354 Self::const_from_metric(SizeMetric::Cm, value)
355 }
356
357 #[inline]
360 #[must_use] pub const fn const_mm(value: isize) -> Self {
361 Self::const_from_metric(SizeMetric::Mm, value)
362 }
363
364 #[inline]
365 #[must_use] pub const fn const_from_metric(metric: SizeMetric, value: isize) -> Self {
366 Self {
367 metric,
368 number: FloatValue::const_new(value),
369 }
370 }
371
372 #[inline]
379 #[must_use] pub const fn const_from_metric_fractional(
380 metric: SizeMetric,
381 pre_comma: isize,
382 post_comma: isize,
383 ) -> Self {
384 Self {
385 metric,
386 number: FloatValue::const_new_fractional(pre_comma, post_comma),
387 }
388 }
389
390 #[inline]
391 #[must_use] pub fn px(value: f32) -> Self {
392 Self::from_metric(SizeMetric::Px, value)
393 }
394
395 #[inline]
396 #[must_use] pub fn em(value: f32) -> Self {
397 Self::from_metric(SizeMetric::Em, value)
398 }
399
400 #[inline]
401 #[must_use] pub fn inch(value: f32) -> Self {
402 Self::from_metric(SizeMetric::In, value)
403 }
404
405 #[inline]
406 #[must_use] pub fn cm(value: f32) -> Self {
407 Self::from_metric(SizeMetric::Cm, value)
408 }
409
410 #[inline]
411 #[must_use] pub fn mm(value: f32) -> Self {
412 Self::from_metric(SizeMetric::Mm, value)
413 }
414
415 #[inline]
416 #[must_use] pub fn pt(value: f32) -> Self {
417 Self::from_metric(SizeMetric::Pt, value)
418 }
419
420 #[inline]
421 #[must_use] pub fn percent(value: f32) -> Self {
422 Self::from_metric(SizeMetric::Percent, value)
423 }
424
425 #[inline]
426 #[must_use] pub fn rem(value: f32) -> Self {
427 Self::from_metric(SizeMetric::Rem, value)
428 }
429
430 #[inline]
431 #[must_use] pub fn from_metric(metric: SizeMetric, value: f32) -> Self {
432 Self {
433 metric,
434 number: FloatValue::new(value),
435 }
436 }
437
438 #[inline]
439 #[allow(clippy::suboptimal_flops)] #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
441 if self.metric == other.metric {
442 Self {
443 metric: self.metric,
444 number: self.number.interpolate(&other.number, t),
445 }
446 } else {
447 let self_px_interp = self.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
450 let other_px_interp = other.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
451 Self::from_metric(
452 SizeMetric::Px,
453 self_px_interp + (other_px_interp - self_px_interp) * t,
454 )
455 }
456 }
457
458 #[inline]
464 #[must_use] pub fn to_percent(&self) -> Option<NormalizedPercentage> {
465 match self.metric {
466 SizeMetric::Percent => Some(NormalizedPercentage::from_unnormalized(self.number.get())),
467 _ => None,
468 }
469 }
470
471 #[doc(hidden)]
477 #[inline]
478 #[must_use] pub fn to_pixels_internal(&self, percent_resolve: f32, em_resolve: f32, rem_resolve: f32) -> f32 {
479 match self.metric {
480 SizeMetric::Px => self.number.get(),
481 SizeMetric::Pt => self.number.get() * PT_TO_PX,
482 SizeMetric::In => self.number.get() * 96.0,
483 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
484 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
485 SizeMetric::Em => self.number.get() * em_resolve,
486 SizeMetric::Rem => self.number.get() * rem_resolve,
487 SizeMetric::Percent => {
488 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(percent_resolve)
489 }
490 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => 0.0,
493 }
494 }
495
496 #[inline]
509 #[must_use] pub fn resolve_with_context(
510 &self,
511 context: &ResolutionContext,
512 property_context: PropertyContext,
513 ) -> f32 {
514 match self.metric {
515 SizeMetric::Px => self.number.get(),
517 SizeMetric::Pt => self.number.get() * PT_TO_PX,
518 SizeMetric::In => self.number.get() * 96.0,
519 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
520 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
521
522 SizeMetric::Em => {
524 let reference_font_size = if property_context == PropertyContext::FontSize {
525 context.parent_font_size
527 } else {
528 context.element_font_size
530 };
531 self.number.get() * reference_font_size
532 }
533
534 SizeMetric::Rem => self.number.get() * context.root_font_size,
536
537 SizeMetric::Vw => self.number.get() * context.viewport_size.width / 100.0,
540 SizeMetric::Vh => self.number.get() * context.viewport_size.height / 100.0,
541 SizeMetric::Vmin => {
543 let min_dimension = context
544 .viewport_size
545 .width
546 .min(context.viewport_size.height);
547 self.number.get() * min_dimension / 100.0
548 }
549 SizeMetric::Vmax => {
551 let max_dimension = context
552 .viewport_size
553 .width
554 .max(context.viewport_size.height);
555 self.number.get() * max_dimension / 100.0
556 }
557
558 SizeMetric::Percent => {
560 #[allow(clippy::match_same_arms)]
563 let reference = match property_context {
564 PropertyContext::FontSize => context.parent_font_size,
566
567 PropertyContext::Width => context.containing_block_size.width,
569
570 PropertyContext::Height => context.containing_block_size.height,
572
573 PropertyContext::Margin | PropertyContext::Padding => {
579 if context.vertical_writing_mode {
583 context.containing_block_size.height
584 } else {
585 context.containing_block_size.width
586 }
587 }
588
589 PropertyContext::BorderWidth => 0.0,
592
593 PropertyContext::BorderRadius => {
597 context.element_size.map_or(0.0, |s| s.width)
598 }
599
600 PropertyContext::Transform => {
602 context.element_size.map_or(0.0, |s| s.width)
603 }
604
605 PropertyContext::Other => context.containing_block_size.width,
607 };
608
609 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(reference)
610 }
611 }
612 }
613}
614
615pub const THIN_BORDER_THICKNESS: PixelValue = PixelValue {
621 metric: SizeMetric::Px,
622 number: FloatValue { number: 1000 },
623};
624
625pub const MEDIUM_BORDER_THICKNESS: PixelValue = PixelValue {
627 metric: SizeMetric::Px,
628 number: FloatValue { number: 3000 },
629};
630
631pub const THICK_BORDER_THICKNESS: PixelValue = PixelValue {
633 metric: SizeMetric::Px,
634 number: FloatValue { number: 5000 },
635};
636
637#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
639#[repr(C)]
640pub struct PixelValueNoPercent {
641 pub inner: PixelValue,
642}
643
644impl PixelValueNoPercent {
645 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
646 self.inner.scale_for_dpi(scale_factor);
647 }
648}
649
650impl_option!(
651 PixelValueNoPercent,
652 OptionPixelValueNoPercent,
653 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
654);
655
656impl_option!(
657 PixelValue,
658 OptionPixelValue,
659 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
660);
661
662impl fmt::Display for PixelValueNoPercent {
663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664 write!(f, "{}", self.inner)
665 }
666}
667
668impl ::core::fmt::Debug for PixelValueNoPercent {
669 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
670 write!(f, "{self}")
671 }
672}
673
674impl PixelValueNoPercent {
675 #[doc(hidden)]
681 #[inline]
682 #[must_use] pub fn to_pixels_internal(&self, em_resolve: f32, rem_resolve: f32) -> f32 {
683 self.inner.to_pixels_internal(0.0, em_resolve, rem_resolve)
684 }
685
686 #[inline]
687 #[must_use] pub const fn zero() -> Self {
688 const ZERO_PXNP: PixelValueNoPercent = PixelValueNoPercent {
689 inner: PixelValue::zero(),
690 };
691 ZERO_PXNP
692 }
693}
694impl From<PixelValue> for PixelValueNoPercent {
695 fn from(e: PixelValue) -> Self {
696 Self { inner: e }
697 }
698}
699
700#[derive(Clone, PartialEq, Eq)]
701pub enum CssPixelValueParseError<'a> {
702 EmptyString,
703 NoValueGiven(&'a str, SizeMetric),
704 ValueParseErr(ParseFloatError, &'a str),
705 InvalidPixelValue(&'a str),
706}
707
708impl_debug_as_display!(CssPixelValueParseError<'a>);
709
710impl_display! { CssPixelValueParseError<'a>, {
711 EmptyString => format!("Missing [px / pt / em / %] value"),
712 NoValueGiven(input, metric) => format!("Expected floating-point pixel value, got: \"{}{}\"", input, metric),
713 ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
714 InvalidPixelValue(s) => format!("Invalid pixel value: \"{}\"", s),
715}}
716
717#[derive(Debug, Clone, PartialEq, Eq)]
719#[repr(C)]
720pub struct PixelNoValueGivenError {
721 pub value: AzString,
722 pub metric: SizeMetric,
723}
724
725#[derive(Debug, Clone, PartialEq, Eq)]
727#[repr(C, u8)]
728pub enum CssPixelValueParseErrorOwned {
729 EmptyString,
730 NoValueGiven(PixelNoValueGivenError),
731 ValueParseErr(ParseFloatErrorWithInput),
732 InvalidPixelValue(AzString),
733}
734
735impl CssPixelValueParseError<'_> {
736 #[must_use] pub fn to_contained(&self) -> CssPixelValueParseErrorOwned {
737 match self {
738 CssPixelValueParseError::EmptyString => CssPixelValueParseErrorOwned::EmptyString,
739 CssPixelValueParseError::NoValueGiven(s, metric) => {
740 CssPixelValueParseErrorOwned::NoValueGiven(PixelNoValueGivenError { value: (*s).to_string().into(), metric: *metric })
741 }
742 CssPixelValueParseError::ValueParseErr(err, s) => {
743 CssPixelValueParseErrorOwned::ValueParseErr(ParseFloatErrorWithInput { error: err.clone().into(), input: (*s).to_string().into() })
744 }
745 CssPixelValueParseError::InvalidPixelValue(s) => {
746 CssPixelValueParseErrorOwned::InvalidPixelValue((*s).to_string().into())
747 }
748 }
749 }
750}
751
752impl CssPixelValueParseErrorOwned {
753 #[must_use] pub fn to_shared(&self) -> CssPixelValueParseError<'_> {
754 match self {
755 Self::EmptyString => CssPixelValueParseError::EmptyString,
756 Self::NoValueGiven(e) => {
757 CssPixelValueParseError::NoValueGiven(e.value.as_str(), e.metric)
758 }
759 Self::ValueParseErr(e) => {
760 CssPixelValueParseError::ValueParseErr(e.error.to_std(), e.input.as_str())
761 }
762 Self::InvalidPixelValue(s) => {
763 CssPixelValueParseError::InvalidPixelValue(s.as_str())
764 }
765 }
766 }
767}
768
769fn parse_pixel_value_inner<'a>(
771 input: &'a str,
772 match_values: &[(&'static str, SizeMetric)],
773) -> Result<PixelValue, CssPixelValueParseError<'a>> {
774 let input = input.trim();
775
776 if input.is_empty() {
777 return Err(CssPixelValueParseError::EmptyString);
778 }
779
780 for (match_val, metric) in match_values {
781 if let Some(value) = input.strip_suffix(match_val) {
782 let value = value.trim();
783 if value.is_empty() {
784 return Err(CssPixelValueParseError::NoValueGiven(input, *metric));
785 }
786 match value.parse::<f32>() {
787 Ok(o) => {
788 return Ok(PixelValue::from_metric(*metric, o));
789 }
790 Err(e) => {
791 return Err(CssPixelValueParseError::ValueParseErr(e, value));
792 }
793 }
794 }
795 }
796
797 input.trim().parse::<f32>().map_or_else(
798 |_| Err(CssPixelValueParseError::InvalidPixelValue(input)),
799 |o| Ok(PixelValue::px(o)),
800 )
801}
802
803pub fn parse_pixel_value(input: &str) -> Result<PixelValue, CssPixelValueParseError<'_>> {
807 parse_pixel_value_inner(
808 input,
809 &[
810 ("px", SizeMetric::Px),
813 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
815 ("pt", SizeMetric::Pt),
816 ("vmax", SizeMetric::Vmax),
817 ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
819 ("vh", SizeMetric::Vh),
820 ("in", SizeMetric::In),
821 ("mm", SizeMetric::Mm),
822 ("cm", SizeMetric::Cm),
823 ("%", SizeMetric::Percent),
824 ],
825 )
826}
827
828pub fn parse_pixel_value_no_percent(
832 input: &str,
833) -> Result<PixelValueNoPercent, CssPixelValueParseError<'_>> {
834 Ok(PixelValueNoPercent {
835 inner: parse_pixel_value_inner(
836 input,
837 &[
838 ("px", SizeMetric::Px),
840 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
842 ("pt", SizeMetric::Pt),
843 ("vmax", SizeMetric::Vmax),
844 ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
846 ("vh", SizeMetric::Vh),
847 ("in", SizeMetric::In),
848 ("mm", SizeMetric::Mm),
849 ("cm", SizeMetric::Cm),
850 ],
851 )?,
852 })
853}
854
855#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
856pub enum PixelValueWithAuto {
857 None,
858 Initial,
859 Inherit,
860 Auto,
861 Exact(PixelValue),
862}
863
864pub fn parse_pixel_value_with_auto(
869 input: &str,
870) -> Result<PixelValueWithAuto, CssPixelValueParseError<'_>> {
871 let input = input.trim();
872 match input {
873 "none" => Ok(PixelValueWithAuto::None),
874 "initial" => Ok(PixelValueWithAuto::Initial),
875 "inherit" => Ok(PixelValueWithAuto::Inherit),
876 "auto" => Ok(PixelValueWithAuto::Auto),
877 e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
878 }
879}
880
881#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
890#[repr(C)]
891#[derive(Default)]
892pub enum SystemMetricRef {
893 #[default]
895 ButtonRadius,
896 ButtonPaddingHorizontal,
898 ButtonPaddingVertical,
900 ButtonBorderWidth,
902 TitlebarHeight,
904 TitlebarButtonWidth,
906 TitlebarPadding,
908 SafeAreaTop,
910 SafeAreaBottom,
912 SafeAreaLeft,
914 SafeAreaRight,
916}
917
918
919impl SystemMetricRef {
920 #[must_use] pub const fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
922 match self {
923 Self::ButtonRadius => metrics.corner_radius.as_option().copied(),
924 Self::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
925 Self::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
926 Self::ButtonBorderWidth => metrics.border_width.as_option().copied(),
927 Self::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
928 Self::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
929 Self::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
930 Self::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
931 Self::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
932 Self::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
933 Self::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
934 }
935 }
936
937 #[must_use] pub const fn as_css_str(&self) -> &'static str {
939 match self {
940 Self::ButtonRadius => "system:button-radius",
941 Self::ButtonPaddingHorizontal => "system:button-padding-horizontal",
942 Self::ButtonPaddingVertical => "system:button-padding-vertical",
943 Self::ButtonBorderWidth => "system:button-border-width",
944 Self::TitlebarHeight => "system:titlebar-height",
945 Self::TitlebarButtonWidth => "system:titlebar-button-width",
946 Self::TitlebarPadding => "system:titlebar-padding",
947 Self::SafeAreaTop => "system:safe-area-top",
948 Self::SafeAreaBottom => "system:safe-area-bottom",
949 Self::SafeAreaLeft => "system:safe-area-left",
950 Self::SafeAreaRight => "system:safe-area-right",
951 }
952 }
953
954 #[must_use] pub fn from_css_str(s: &str) -> Option<Self> {
956 match s {
957 "button-radius" => Some(Self::ButtonRadius),
958 "button-padding-horizontal" => Some(Self::ButtonPaddingHorizontal),
959 "button-padding-vertical" => Some(Self::ButtonPaddingVertical),
960 "button-border-width" => Some(Self::ButtonBorderWidth),
961 "titlebar-height" => Some(Self::TitlebarHeight),
962 "titlebar-button-width" => Some(Self::TitlebarButtonWidth),
963 "titlebar-padding" => Some(Self::TitlebarPadding),
964 "safe-area-top" => Some(Self::SafeAreaTop),
965 "safe-area-bottom" => Some(Self::SafeAreaBottom),
966 "safe-area-left" => Some(Self::SafeAreaLeft),
967 "safe-area-right" => Some(Self::SafeAreaRight),
968 _ => None,
969 }
970 }
971}
972
973impl fmt::Display for SystemMetricRef {
974 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
975 write!(f, "{}", self.as_css_str())
976 }
977}
978
979impl FormatAsCssValue for SystemMetricRef {
980 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
981 write!(f, "{}", self.as_css_str())
982 }
983}
984
985#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
990#[repr(C, u8)]
991pub enum PixelValueOrSystem {
992 Value(PixelValue),
994 System(SystemMetricRef),
996}
997
998impl Default for PixelValueOrSystem {
999 fn default() -> Self {
1000 Self::Value(PixelValue::zero())
1001 }
1002}
1003
1004impl From<PixelValue> for PixelValueOrSystem {
1005 fn from(value: PixelValue) -> Self {
1006 Self::Value(value)
1007 }
1008}
1009
1010impl PixelValueOrSystem {
1011 #[must_use] pub const fn value(v: PixelValue) -> Self {
1013 Self::Value(v)
1014 }
1015
1016 #[must_use] pub const fn system(s: SystemMetricRef) -> Self {
1018 Self::System(s)
1019 }
1020
1021 #[must_use] pub fn resolve(&self, system_metrics: &crate::system::SystemMetrics, fallback: PixelValue) -> PixelValue {
1024 match self {
1025 Self::Value(v) => *v,
1026 Self::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
1027 }
1028 }
1029
1030}
1031
1032impl fmt::Display for PixelValueOrSystem {
1033 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1034 match self {
1035 Self::Value(v) => write!(f, "{v}"),
1036 Self::System(s) => write!(f, "{s}"),
1037 }
1038 }
1039}
1040
1041impl FormatAsCssValue for PixelValueOrSystem {
1042 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1043 match self {
1044 Self::Value(v) => v.format_as_css_value(f),
1045 Self::System(s) => s.format_as_css_value(f),
1046 }
1047 }
1048}
1049
1050#[cfg(feature = "parser")]
1054pub fn parse_pixel_value_or_system(
1058 input: &str,
1059) -> Result<PixelValueOrSystem, CssPixelValueParseError<'_>> {
1060 let input = input.trim();
1061
1062 if let Some(metric_name) = input.strip_prefix("system:") {
1064 if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
1065 return Ok(PixelValueOrSystem::System(metric_ref));
1066 }
1067 return Err(CssPixelValueParseError::InvalidPixelValue(input));
1068 }
1069
1070 Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
1072}
1073
1074#[cfg(all(test, feature = "parser"))]
1075mod tests {
1076 #![allow(clippy::float_cmp)]
1078 use super::*;
1079
1080 #[test]
1081 fn test_parse_pixel_value() {
1082 assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
1083 assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
1084 assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
1085 assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
1086 assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
1087 assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
1088 assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
1089 assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
1090 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1091 }
1092
1093 #[test]
1094 fn test_resolve_with_context_em() {
1095 let context = ResolutionContext {
1097 vertical_writing_mode: false,
1098 element_font_size: 32.0,
1099 parent_font_size: 16.0,
1100 ..Default::default()
1101 };
1102
1103 let margin = PixelValue::em(0.67);
1105 assert!(
1106 (margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
1107 );
1108
1109 let font_size = PixelValue::em(2.0);
1111 assert_eq!(
1112 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1113 32.0
1114 );
1115 }
1116
1117 #[test]
1118 fn test_resolve_with_context_rem() {
1119 let context = ResolutionContext {
1121 vertical_writing_mode: false,
1122 element_font_size: 32.0,
1123 parent_font_size: 16.0,
1124 root_font_size: 18.0,
1125 ..Default::default()
1126 };
1127
1128 let margin = PixelValue::rem(2.0);
1130 assert_eq!(
1131 margin.resolve_with_context(&context, PropertyContext::Margin),
1132 36.0
1133 );
1134
1135 let font_size = PixelValue::rem(1.5);
1136 assert_eq!(
1137 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1138 27.0
1139 );
1140 }
1141
1142 #[test]
1143 fn test_resolve_with_context_percent_margin() {
1144 let context = ResolutionContext {
1146 vertical_writing_mode: false,
1147 element_font_size: 16.0,
1148 parent_font_size: 16.0,
1149 root_font_size: 16.0,
1150 containing_block_size: PhysicalSize::new(800.0, 600.0),
1151 element_size: None,
1152 viewport_size: PhysicalSize::new(1920.0, 1080.0),
1153 };
1154
1155 let margin = PixelValue::percent(10.0); assert_eq!(
1157 margin.resolve_with_context(&context, PropertyContext::Margin),
1158 80.0
1159 ); }
1161
1162 #[test]
1163 fn test_parse_pixel_value_no_percent() {
1164 assert_eq!(
1165 parse_pixel_value_no_percent("10px").unwrap().inner,
1166 PixelValue::px(10.0)
1167 );
1168 assert!(parse_pixel_value_no_percent("50%").is_err());
1169 }
1170
1171 #[test]
1172 fn test_parse_pixel_value_with_auto() {
1173 assert_eq!(
1174 parse_pixel_value_with_auto("10px").unwrap(),
1175 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1176 );
1177 assert_eq!(
1178 parse_pixel_value_with_auto("auto").unwrap(),
1179 PixelValueWithAuto::Auto
1180 );
1181 assert_eq!(
1182 parse_pixel_value_with_auto("initial").unwrap(),
1183 PixelValueWithAuto::Initial
1184 );
1185 assert_eq!(
1186 parse_pixel_value_with_auto("inherit").unwrap(),
1187 PixelValueWithAuto::Inherit
1188 );
1189 assert_eq!(
1190 parse_pixel_value_with_auto("none").unwrap(),
1191 PixelValueWithAuto::None
1192 );
1193 }
1194
1195 #[test]
1196 fn test_parse_pixel_value_errors() {
1197 assert!(parse_pixel_value("").is_err());
1198 assert!(parse_pixel_value("10").is_ok()); assert!(parse_pixel_value("10 px").is_ok()); assert!(parse_pixel_value("px").is_err());
1203 assert!(parse_pixel_value("ten-px").is_err());
1204 }
1205}
1206
1207#[cfg(test)]
1208#[allow(
1209 clippy::float_cmp,
1210 clippy::unreadable_literal,
1211 clippy::cast_precision_loss,
1212 clippy::too_many_lines,
1213 clippy::excessive_precision
1214)]
1215mod autotest_generated {
1216 use std::{
1217 collections::hash_map::DefaultHasher,
1218 hash::{Hash, Hasher},
1219 };
1220
1221 use super::*;
1222 use crate::{
1223 codegen::format::FormatAsRustCode,
1224 css::PrintAsCssValue,
1225 props::{
1226 basic::length::{FloatValue, SizeMetric},
1227 formatter::FormatAsCssValue,
1228 },
1229 system::{SafeAreaInsets, SystemMetrics, TitlebarMetrics},
1230 };
1231
1232 const MULT: f32 = 1000.0;
1235
1236 const MAX_SAFE_CONST: isize = isize::MAX / 1000;
1240 const MIN_SAFE_CONST: isize = isize::MIN / 1000;
1241
1242 const ALL_METRICS: [SizeMetric; 12] = [
1243 SizeMetric::Px,
1244 SizeMetric::Pt,
1245 SizeMetric::Em,
1246 SizeMetric::Rem,
1247 SizeMetric::In,
1248 SizeMetric::Cm,
1249 SizeMetric::Mm,
1250 SizeMetric::Percent,
1251 SizeMetric::Vw,
1252 SizeMetric::Vh,
1253 SizeMetric::Vmin,
1254 SizeMetric::Vmax,
1255 ];
1256
1257 const ALL_PROPERTY_CONTEXTS: [PropertyContext; 9] = [
1258 PropertyContext::FontSize,
1259 PropertyContext::Margin,
1260 PropertyContext::Padding,
1261 PropertyContext::Width,
1262 PropertyContext::Height,
1263 PropertyContext::BorderWidth,
1264 PropertyContext::BorderRadius,
1265 PropertyContext::Transform,
1266 PropertyContext::Other,
1267 ];
1268
1269 const ALL_SYSTEM_REFS: [SystemMetricRef; 11] = [
1270 SystemMetricRef::ButtonRadius,
1271 SystemMetricRef::ButtonPaddingHorizontal,
1272 SystemMetricRef::ButtonPaddingVertical,
1273 SystemMetricRef::ButtonBorderWidth,
1274 SystemMetricRef::TitlebarHeight,
1275 SystemMetricRef::TitlebarButtonWidth,
1276 SystemMetricRef::TitlebarPadding,
1277 SystemMetricRef::SafeAreaTop,
1278 SystemMetricRef::SafeAreaBottom,
1279 SystemMetricRef::SafeAreaLeft,
1280 SystemMetricRef::SafeAreaRight,
1281 ];
1282
1283 const EXTREME_F32: [f32; 13] = [
1285 0.0,
1286 -0.0,
1287 1.0,
1288 -1.0,
1289 f32::MIN_POSITIVE,
1290 -f32::MIN_POSITIVE,
1291 1e30,
1292 -1e30,
1293 f32::MAX,
1294 f32::MIN,
1295 f32::INFINITY,
1296 f32::NEG_INFINITY,
1297 f32::NAN,
1298 ];
1299
1300 fn approx(a: f32, b: f32) -> bool {
1301 (a - b).abs() < 0.001
1302 }
1303
1304 fn hash_of<T: Hash>(v: &T) -> u64 {
1305 let mut h = DefaultHasher::new();
1306 v.hash(&mut h);
1307 h.finish()
1308 }
1309
1310 struct CssVal<T>(T);
1313
1314 impl<T: FormatAsCssValue> fmt::Display for CssVal<T> {
1315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1316 self.0.format_as_css_value(f)
1317 }
1318 }
1319
1320 fn as_css_value<T: FormatAsCssValue>(v: T) -> String {
1321 CssVal(v).to_string()
1322 }
1323
1324 fn distinct_context() -> ResolutionContext {
1327 ResolutionContext {
1328 vertical_writing_mode: false,
1329 element_font_size: 32.0,
1330 parent_font_size: 8.0,
1331 root_font_size: 4.0,
1332 containing_block_size: PhysicalSize::new(800.0, 600.0),
1333 element_size: Some(PhysicalSize::new(200.0, 100.0)),
1334 viewport_size: PhysicalSize::new(1000.0, 500.0),
1335 }
1336 }
1337
1338 fn populated_metrics() -> SystemMetrics {
1339 SystemMetrics {
1340 corner_radius: OptionPixelValue::Some(PixelValue::px(1.0)),
1341 border_width: OptionPixelValue::Some(PixelValue::px(2.0)),
1342 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(3.0)),
1343 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
1344 titlebar: TitlebarMetrics {
1345 height: OptionPixelValue::Some(PixelValue::px(5.0)),
1346 button_area_width: OptionPixelValue::Some(PixelValue::px(6.0)),
1347 padding_horizontal: OptionPixelValue::Some(PixelValue::px(7.0)),
1348 safe_area: SafeAreaInsets {
1349 top: OptionPixelValue::Some(PixelValue::px(8.0)),
1350 bottom: OptionPixelValue::Some(PixelValue::px(9.0)),
1351 left: OptionPixelValue::Some(PixelValue::px(10.0)),
1352 right: OptionPixelValue::Some(PixelValue::px(11.0)),
1353 },
1354 ..TitlebarMetrics::default()
1355 },
1356 }
1357 }
1358
1359 #[test]
1362 fn parse_pixel_value_rejects_empty_and_whitespace_only() {
1363 assert_eq!(
1364 parse_pixel_value("").unwrap_err(),
1365 CssPixelValueParseError::EmptyString
1366 );
1367 for ws in [" ", "\t\n", "\r\n\t ", "\n"] {
1368 assert_eq!(
1369 parse_pixel_value(ws).unwrap_err(),
1370 CssPixelValueParseError::EmptyString,
1371 "whitespace-only input {ws:?} must trim down to EmptyString"
1372 );
1373 }
1374 }
1375
1376 #[test]
1377 fn parse_pixel_value_rejects_a_bare_unit_with_no_number() {
1378 for unit in [
1382 "px", "rem", "em", "pt", "in", "mm", "cm", "vmax", "vw", "vh", "%",
1383 ] {
1384 let err = parse_pixel_value(unit).unwrap_err();
1385 assert!(
1386 matches!(err, CssPixelValueParseError::NoValueGiven(input, _) if input == unit),
1387 "bare unit {unit:?} should be NoValueGiven, got {err:?}"
1388 );
1389 }
1390 assert!(matches!(
1392 parse_pixel_value(" px").unwrap_err(),
1393 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px)
1394 ));
1395 }
1396
1397 #[test]
1398 fn parse_pixel_value_vmin_is_shadowed_by_the_in_suffix() {
1399 assert_eq!(
1404 parse_pixel_value("5vmin").unwrap(),
1405 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1406 );
1407 assert!(matches!(
1410 parse_pixel_value("vmin").unwrap_err(),
1411 CssPixelValueParseError::NoValueGiven(..)
1412 ));
1413
1414 assert_eq!(
1416 parse_pixel_value("5vmax").unwrap(),
1417 PixelValue::from_metric(SizeMetric::Vmax, 5.0)
1418 );
1419 assert_eq!(
1420 parse_pixel_value("5vw").unwrap(),
1421 PixelValue::from_metric(SizeMetric::Vw, 5.0)
1422 );
1423 assert_eq!(
1424 parse_pixel_value("5vh").unwrap(),
1425 PixelValue::from_metric(SizeMetric::Vh, 5.0)
1426 );
1427 }
1428
1429 #[test]
1430 fn parse_pixel_value_inner_proves_the_vmin_bug_is_pure_suffix_ordering() {
1431 let in_first: [(&'static str, SizeMetric); 2] =
1434 [("in", SizeMetric::In), ("vmin", SizeMetric::Vmin)];
1435 let vmin_first: [(&'static str, SizeMetric); 2] =
1436 [("vmin", SizeMetric::Vmin), ("in", SizeMetric::In)];
1437
1438 assert!(parse_pixel_value_inner("5vmin", &in_first).is_err());
1439 assert_eq!(
1440 parse_pixel_value_inner("5vmin", &vmin_first).unwrap(),
1441 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1442 );
1443 assert_eq!(
1445 parse_pixel_value_inner("5in", &vmin_first).unwrap(),
1446 PixelValue::inch(5.0)
1447 );
1448 }
1449
1450 #[test]
1451 fn parse_pixel_value_inner_with_an_empty_table_falls_back_to_unitless_px() {
1452 let empty: [(&'static str, SizeMetric); 0] = [];
1453
1454 assert_eq!(
1456 parse_pixel_value_inner("10", &empty).unwrap(),
1457 PixelValue::px(10.0)
1458 );
1459 assert!(matches!(
1460 parse_pixel_value_inner("10px", &empty).unwrap_err(),
1461 CssPixelValueParseError::InvalidPixelValue("10px")
1462 ));
1463 assert_eq!(
1464 parse_pixel_value_inner("", &empty).unwrap_err(),
1465 CssPixelValueParseError::EmptyString
1466 );
1467 }
1468
1469 #[test]
1470 fn parse_pixel_value_accepts_every_unit_it_advertises() {
1471 let cases: [(&str, PixelValue); 12] = [
1473 ("10px", PixelValue::px(10.0)),
1474 ("1.5em", PixelValue::em(1.5)),
1475 ("2rem", PixelValue::rem(2.0)),
1476 ("-20pt", PixelValue::pt(-20.0)),
1477 ("50%", PixelValue::percent(50.0)),
1478 ("1in", PixelValue::inch(1.0)),
1479 ("2.54cm", PixelValue::cm(2.54)),
1480 ("10mm", PixelValue::mm(10.0)),
1481 ("+7px", PixelValue::px(7.0)),
1482 (".5px", PixelValue::px(0.5)),
1483 ("5.px", PixelValue::px(5.0)),
1484 ("1e2px", PixelValue::px(100.0)),
1485 ];
1486 for (input, expected) in cases {
1487 assert_eq!(
1488 parse_pixel_value(input).unwrap(),
1489 expected,
1490 "parsing {input:?}"
1491 );
1492 }
1493
1494 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1496 assert_eq!(parse_pixel_value("10 px").unwrap(), PixelValue::px(10.0));
1497 assert_eq!(parse_pixel_value("\t10px\n").unwrap(), PixelValue::px(10.0));
1498 }
1499
1500 #[test]
1501 fn parse_pixel_value_boundary_numbers_saturate_instead_of_overflowing() {
1502 assert_eq!(parse_pixel_value("-0").unwrap(), PixelValue::px(0.0));
1504 assert_eq!(parse_pixel_value("-0").unwrap(), PixelValue::zero());
1505
1506 assert_eq!(parse_pixel_value("0.0004px").unwrap(), PixelValue::px(0.0));
1508 assert_eq!(parse_pixel_value("-0.0009px").unwrap(), PixelValue::px(0.0));
1509 assert_eq!(parse_pixel_value("1e-40px").unwrap(), PixelValue::px(0.0));
1510
1511 for huge in ["9223372036854775807", "1e40px", "3.5e38"] {
1513 let v = parse_pixel_value(huge).unwrap();
1514 assert!(
1515 v.number.get().is_finite(),
1516 "{huge:?} leaked a non-finite value: {}",
1517 v.number.get()
1518 );
1519 assert!(v.number.get() > 0.0, "{huge:?} lost its sign");
1520 }
1521 let neg = parse_pixel_value("-1e40px").unwrap();
1522 assert!(neg.number.get().is_finite() && neg.number.get() < 0.0);
1523 }
1524
1525 #[test]
1526 fn parse_pixel_value_inherits_rusts_float_keywords() {
1527 assert_eq!(parse_pixel_value("NaN").unwrap(), PixelValue::zero());
1532
1533 let inf = parse_pixel_value("infinity").unwrap();
1534 assert_eq!(inf, PixelValue::px(f32::INFINITY));
1535 assert!(inf.number.get().is_finite() && inf.number.get() > 0.0);
1536
1537 let neg_inf = parse_pixel_value("-infinity").unwrap();
1538 assert_eq!(neg_inf, PixelValue::px(f32::NEG_INFINITY));
1539 assert!(neg_inf.number.get().is_finite() && neg_inf.number.get() < 0.0);
1540
1541 let inf_short = parse_pixel_value("inf").unwrap();
1545 assert_eq!(inf_short, PixelValue::px(f32::INFINITY));
1546 assert!(inf_short.number.get().is_finite() && inf_short.number.get() > 0.0);
1547 }
1548
1549 #[test]
1550 fn parse_pixel_value_is_case_sensitive_about_units() {
1551 for input in ["10PX", "10Px", "10EM", "10REM", "10VMAX"] {
1555 let err = parse_pixel_value(input).unwrap_err();
1556 assert!(
1557 matches!(err, CssPixelValueParseError::InvalidPixelValue(s) if s == input),
1558 "uppercase unit {input:?} should be InvalidPixelValue, got {err:?}"
1559 );
1560 }
1561 }
1562
1563 #[test]
1564 fn parse_pixel_value_rejects_garbage_and_trailing_junk() {
1565 for input in [
1566 "ten-px",
1567 "px10",
1568 "10px;garbage",
1569 "10;",
1570 "--",
1571 "1%%",
1572 "10 20px",
1573 "#",
1574 "10px 10px",
1575 "e",
1576 "0x10px",
1577 ] {
1578 assert!(
1579 parse_pixel_value(input).is_err(),
1580 "{input:?} must not parse, got {:?}",
1581 parse_pixel_value(input)
1582 );
1583 }
1584 }
1585
1586 #[test]
1587 fn parse_pixel_value_survives_unicode() {
1588 for input in [
1590 "\u{1F600}", "10px\u{1F600}", "10px\u{0301}", "\u{200B}10px", "\u{0661}\u{0660}px", "10\u{0440}\u{0445}", "\u{202E}10px", ] {
1598 let got = parse_pixel_value(input);
1599 assert!(got.is_err(), "{input:?} must be rejected, got {got:?}");
1600 }
1601
1602 assert!(matches!(
1605 parse_pixel_value("\u{200B}10px").unwrap_err(),
1606 CssPixelValueParseError::ValueParseErr(_, "\u{200B}10")
1607 ));
1608 }
1609
1610 #[test]
1611 fn parse_pixel_value_handles_extremely_long_and_deeply_nested_input() {
1612 let long_number = format!("{}px", "9".repeat(100_000));
1614 let parsed = parse_pixel_value(&long_number).unwrap();
1615 assert!(parsed.number.get().is_finite());
1616 assert_eq!(parsed.metric, SizeMetric::Px);
1617
1618 let long_junk = "x".repeat(100_000);
1620 assert!(parse_pixel_value(&long_junk).is_err());
1621
1622 let nested = "(".repeat(10_000);
1625 assert!(matches!(
1626 parse_pixel_value(&nested).unwrap_err(),
1627 CssPixelValueParseError::InvalidPixelValue(_)
1628 ));
1629 }
1630
1631 #[test]
1632 fn parse_pixel_value_no_percent_rejects_percentages_but_keeps_the_rest() {
1633 assert_eq!(
1634 parse_pixel_value_no_percent("10px").unwrap().inner,
1635 PixelValue::px(10.0)
1636 );
1637 assert_eq!(
1638 parse_pixel_value_no_percent("5vmax").unwrap().inner,
1639 PixelValue::from_metric(SizeMetric::Vmax, 5.0)
1640 );
1641
1642 assert!(matches!(
1644 parse_pixel_value_no_percent("50%").unwrap_err(),
1645 CssPixelValueParseError::InvalidPixelValue("50%")
1646 ));
1647 assert!(matches!(
1648 parse_pixel_value_no_percent("%").unwrap_err(),
1649 CssPixelValueParseError::InvalidPixelValue("%")
1650 ));
1651
1652 assert_eq!(
1653 parse_pixel_value_no_percent("").unwrap_err(),
1654 CssPixelValueParseError::EmptyString
1655 );
1656 assert_eq!(
1657 parse_pixel_value_no_percent(" ").unwrap_err(),
1658 CssPixelValueParseError::EmptyString
1659 );
1660 assert!(parse_pixel_value_no_percent("\u{1F600}").is_err());
1661 assert_eq!(
1663 parse_pixel_value_no_percent("5vmin").unwrap().inner,
1664 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1665 );
1666 }
1667
1668 #[test]
1669 fn parse_pixel_value_with_auto_keywords_and_fallthrough() {
1670 assert_eq!(
1671 parse_pixel_value_with_auto("auto").unwrap(),
1672 PixelValueWithAuto::Auto
1673 );
1674 assert_eq!(
1675 parse_pixel_value_with_auto(" initial ").unwrap(),
1676 PixelValueWithAuto::Initial
1677 );
1678 assert_eq!(
1679 parse_pixel_value_with_auto("\tinherit\n").unwrap(),
1680 PixelValueWithAuto::Inherit
1681 );
1682 assert_eq!(
1683 parse_pixel_value_with_auto("none").unwrap(),
1684 PixelValueWithAuto::None
1685 );
1686 assert_eq!(
1687 parse_pixel_value_with_auto("10px").unwrap(),
1688 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1689 );
1690
1691 for input in ["AUTO", "Auto", "INHERIT", "None"] {
1693 assert!(
1694 parse_pixel_value_with_auto(input).is_err(),
1695 "{input:?} unexpectedly matched a keyword"
1696 );
1697 }
1698
1699 assert_eq!(
1701 parse_pixel_value_with_auto("").unwrap_err(),
1702 CssPixelValueParseError::EmptyString
1703 );
1704 assert_eq!(
1705 parse_pixel_value_with_auto(" \t ").unwrap_err(),
1706 CssPixelValueParseError::EmptyString
1707 );
1708 assert!(parse_pixel_value_with_auto("auto;garbage").is_err());
1709 assert!(parse_pixel_value_with_auto("\u{1F600}").is_err());
1710 assert!(parse_pixel_value_with_auto(&"(".repeat(10_000)).is_err());
1711 }
1712
1713 #[cfg(feature = "parser")]
1714 #[test]
1715 fn parse_pixel_value_or_system_accepts_every_system_ref() {
1716 for r in ALL_SYSTEM_REFS {
1717 let css = r.as_css_str(); assert_eq!(
1719 parse_pixel_value_or_system(css).unwrap(),
1720 PixelValueOrSystem::System(r),
1721 "round-tripping {css:?}"
1722 );
1723 assert_eq!(
1725 parse_pixel_value_or_system(&format!(" {css} ")).unwrap(),
1726 PixelValueOrSystem::System(r)
1727 );
1728 }
1729
1730 assert_eq!(
1732 parse_pixel_value_or_system("10px").unwrap(),
1733 PixelValueOrSystem::Value(PixelValue::px(10.0))
1734 );
1735 assert_eq!(
1736 parse_pixel_value_or_system("1.5em").unwrap(),
1737 PixelValueOrSystem::Value(PixelValue::em(1.5))
1738 );
1739 }
1740
1741 #[cfg(feature = "parser")]
1742 #[test]
1743 fn parse_pixel_value_or_system_rejects_malformed_system_refs() {
1744 assert!(matches!(
1749 parse_pixel_value_or_system("system:button-padding").unwrap_err(),
1750 CssPixelValueParseError::InvalidPixelValue("system:button-padding")
1751 ));
1752
1753 for input in [
1754 "system:", "system:unknown", "system: button-radius", "system:BUTTON-RADIUS", "system:button-radius;x", "system:\u{1F600}", ] {
1761 let err = parse_pixel_value_or_system(input).unwrap_err();
1762 assert!(
1763 matches!(err, CssPixelValueParseError::InvalidPixelValue(s) if s == input),
1764 "{input:?} should be InvalidPixelValue, got {err:?}"
1765 );
1766 }
1767
1768 assert!(matches!(
1771 parse_pixel_value_or_system("SYSTEM:button-radius").unwrap_err(),
1772 CssPixelValueParseError::InvalidPixelValue("SYSTEM:button-radius")
1773 ));
1774 assert_eq!(
1775 parse_pixel_value_or_system("").unwrap_err(),
1776 CssPixelValueParseError::EmptyString
1777 );
1778 assert_eq!(
1779 parse_pixel_value_or_system(" ").unwrap_err(),
1780 CssPixelValueParseError::EmptyString
1781 );
1782
1783 let long = format!("system:{}", "a".repeat(100_000));
1785 assert!(parse_pixel_value_or_system(&long).is_err());
1786 }
1787
1788 #[test]
1791 fn parse_errors_survive_the_owned_round_trip() {
1792 let float_err = "x".parse::<f32>().unwrap_err();
1793 let errors = [
1794 CssPixelValueParseError::EmptyString,
1795 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px),
1796 CssPixelValueParseError::ValueParseErr(float_err, "abc"),
1797 CssPixelValueParseError::InvalidPixelValue("ten-px"),
1798 ];
1799
1800 for err in errors {
1801 let owned = err.to_contained();
1802 let shared = owned.to_shared();
1803 assert_eq!(shared, err, "to_contained -> to_shared must be lossless");
1804 assert!(!err.to_string().is_empty());
1806 assert_eq!(shared.to_string(), err.to_string());
1807 }
1808 }
1809
1810 #[test]
1811 fn parse_errors_round_trip_from_real_parse_failures() {
1812 for input in ["", "px", "\u{200B}10px", "ten-px", "%"] {
1815 let err = parse_pixel_value(input).unwrap_err();
1816 let owned = err.to_contained();
1817 assert_eq!(owned.to_shared(), err, "round-trip failed for {input:?}");
1818 }
1819 }
1820
1821 #[test]
1824 fn float_constructors_never_leak_a_non_finite_value() {
1825 type Ctor = (fn(f32) -> PixelValue, SizeMetric);
1828 let ctors: [Ctor; 8] = [
1829 (PixelValue::px, SizeMetric::Px),
1830 (PixelValue::em, SizeMetric::Em),
1831 (PixelValue::pt, SizeMetric::Pt),
1832 (PixelValue::inch, SizeMetric::In),
1833 (PixelValue::cm, SizeMetric::Cm),
1834 (PixelValue::mm, SizeMetric::Mm),
1835 (PixelValue::percent, SizeMetric::Percent),
1836 (PixelValue::rem, SizeMetric::Rem),
1837 ];
1838
1839 for (ctor, metric) in ctors {
1840 for v in EXTREME_F32 {
1841 let px = ctor(v);
1842 assert_eq!(px.metric, metric, "constructor lost its metric for {v}");
1843 assert!(
1844 px.number.get().is_finite(),
1845 "{metric:?} constructor leaked a non-finite value for input {v}"
1846 );
1847 }
1848 assert_eq!(ctor(f32::NAN).number.get(), 0.0, "NaN must sanitize to 0");
1849 assert!(ctor(f32::INFINITY).number.get() > 0.0);
1850 assert!(ctor(f32::NEG_INFINITY).number.get() < 0.0);
1851 }
1852
1853 for metric in ALL_METRICS {
1855 for v in EXTREME_F32 {
1856 let px = PixelValue::from_metric(metric, v);
1857 assert_eq!(px.metric, metric);
1858 assert!(px.number.get().is_finite());
1859 }
1860 assert_eq!(
1861 PixelValue::from_metric(metric, 12.0),
1862 PixelValue {
1863 metric,
1864 number: FloatValue::new(12.0)
1865 }
1866 );
1867 }
1868 }
1869
1870 #[test]
1871 fn float_constructors_quantize_to_one_thousandth() {
1872 assert_eq!(PixelValue::px(0.0004).number.get(), 0.0);
1874 assert_eq!(PixelValue::px(-0.0009).number.get(), 0.0);
1875 assert_eq!(PixelValue::px(1.0005).number.get(), 1.0);
1877
1878 assert_eq!(PixelValue::px(-0.0), PixelValue::px(0.0));
1881 assert_eq!(
1882 hash_of(&PixelValue::px(-0.0)),
1883 hash_of(&PixelValue::px(0.0))
1884 );
1885 assert_eq!(PixelValue::px(f32::NAN), PixelValue::px(f32::NAN));
1887 assert_eq!(PixelValue::px(f32::NAN), PixelValue::zero());
1888 }
1889
1890 #[test]
1891 fn const_constructors_agree_with_their_float_twins() {
1892 assert_eq!(PixelValue::const_px(5), PixelValue::px(5.0));
1893 assert_eq!(PixelValue::const_em(5), PixelValue::em(5.0));
1894 assert_eq!(PixelValue::const_pt(5), PixelValue::pt(5.0));
1895 assert_eq!(PixelValue::const_percent(5), PixelValue::percent(5.0));
1896 assert_eq!(PixelValue::const_in(5), PixelValue::inch(5.0));
1897 assert_eq!(PixelValue::const_cm(5), PixelValue::cm(5.0));
1898 assert_eq!(PixelValue::const_mm(5), PixelValue::mm(5.0));
1899
1900 assert_eq!(PixelValue::const_px(0), PixelValue::zero());
1901 assert_eq!(PixelValue::const_px(-7), PixelValue::px(-7.0));
1902
1903 for metric in ALL_METRICS {
1904 assert_eq!(
1905 PixelValue::const_from_metric(metric, 7),
1906 PixelValue::from_metric(metric, 7.0),
1907 "const_from_metric disagrees with from_metric for {metric:?}"
1908 );
1909 assert_eq!(
1910 PixelValue::const_from_metric(metric, -7),
1911 PixelValue::from_metric(metric, -7.0)
1912 );
1913 }
1914 }
1915
1916 #[test]
1917 fn const_constructors_are_usable_up_to_the_documented_isize_bound() {
1918 for v in [0, 1, -1, MAX_SAFE_CONST, MIN_SAFE_CONST] {
1923 let px = PixelValue::const_px(v);
1924 assert!(
1925 px.number.get().is_finite(),
1926 "const_px({v}) leaked a non-finite value"
1927 );
1928 }
1929 assert!(PixelValue::const_px(MAX_SAFE_CONST).number.get() > 0.0);
1930 assert!(PixelValue::const_px(MIN_SAFE_CONST).number.get() < 0.0);
1931 assert_eq!(
1932 PixelValue::const_px(MAX_SAFE_CONST).number.number(),
1933 MAX_SAFE_CONST * 1000
1934 );
1935 }
1936
1937 #[test]
1938 fn const_fractional_constructors_match_their_documented_examples() {
1939 assert!(approx(PixelValue::const_em_fractional(1, 5).number.get(), 1.5));
1941 assert!(approx(
1942 PixelValue::const_em_fractional(0, 83).number.get(),
1943 0.83
1944 ));
1945 assert!(approx(
1946 PixelValue::const_em_fractional(1, 17).number.get(),
1947 1.17
1948 ));
1949 assert_eq!(PixelValue::const_em_fractional(1, 5).metric, SizeMetric::Em);
1950 assert_eq!(PixelValue::const_pt_fractional(1, 5).metric, SizeMetric::Pt);
1951 assert!(approx(PixelValue::const_pt_fractional(2, 25).number.get(), 2.25));
1952
1953 assert_eq!(
1956 PixelValue::const_from_metric_fractional(SizeMetric::Px, 0, 0),
1957 PixelValue::zero()
1958 );
1959 assert!(approx(
1960 PixelValue::const_from_metric_fractional(SizeMetric::Px, -1, 5)
1961 .number
1962 .get(),
1963 -1.5
1964 ));
1965
1966 assert!(approx(
1969 PixelValue::const_from_metric_fractional(SizeMetric::Px, 1, 5234)
1970 .number
1971 .get(),
1972 1.523
1973 ));
1974
1975 let extreme =
1978 PixelValue::const_from_metric_fractional(SizeMetric::Px, 0, isize::MAX);
1979 assert!(extreme.number.get().is_finite());
1980 }
1981
1982 #[test]
1983 fn scale_for_dpi_is_defined_for_every_scale_factor() {
1984 let mut doubled = PixelValue::px(10.0);
1985 doubled.scale_for_dpi(2.0);
1986 assert_eq!(doubled, PixelValue::px(20.0));
1987
1988 doubled.scale_for_dpi(2.0);
1991 assert_eq!(doubled, PixelValue::px(40.0));
1992
1993 let mut zeroed = PixelValue::em(3.0);
1994 zeroed.scale_for_dpi(0.0);
1995 assert_eq!(zeroed, PixelValue::em(0.0));
1996 assert_eq!(zeroed.metric, SizeMetric::Em, "metric must be preserved");
1997
1998 let mut flipped = PixelValue::px(10.0);
1999 flipped.scale_for_dpi(-1.5);
2000 assert_eq!(flipped, PixelValue::px(-15.0));
2001
2002 let mut nan_scaled = PixelValue::px(10.0);
2004 nan_scaled.scale_for_dpi(f32::NAN);
2005 assert_eq!(nan_scaled.number.get(), 0.0);
2006
2007 let mut inf_scaled = PixelValue::px(10.0);
2008 inf_scaled.scale_for_dpi(f32::INFINITY);
2009 assert!(inf_scaled.number.get().is_finite() && inf_scaled.number.get() > 0.0);
2010
2011 let mut max_scaled = PixelValue::px(f32::MAX);
2012 max_scaled.scale_for_dpi(f32::MAX);
2013 assert!(max_scaled.number.get().is_finite());
2014
2015 let mut wrapped = PixelValueNoPercent::from(PixelValue::px(10.0));
2017 wrapped.scale_for_dpi(2.5);
2018 assert_eq!(wrapped.inner, PixelValue::px(25.0));
2019
2020 let mut wrapped_nan = PixelValueNoPercent::from(PixelValue::px(10.0));
2021 wrapped_nan.scale_for_dpi(f32::NAN);
2022 assert_eq!(wrapped_nan.inner.number.get(), 0.0);
2023 }
2024
2025 #[test]
2026 fn interpolate_within_one_metric_keeps_that_metric() {
2027 let a = PixelValue::em(1.0);
2028 let b = PixelValue::em(3.0);
2029
2030 assert_eq!(a.interpolate(&b, 0.0), a);
2031 assert_eq!(a.interpolate(&b, 1.0), b);
2032 assert_eq!(a.interpolate(&b, 0.5), PixelValue::em(2.0));
2033
2034 assert_eq!(a.interpolate(&b, 2.0), PixelValue::em(5.0));
2036 assert_eq!(a.interpolate(&b, -1.0), PixelValue::em(-1.0));
2037
2038 let p = PixelValue::percent(0.0).interpolate(&PixelValue::percent(100.0), 0.5);
2040 assert_eq!(p, PixelValue::percent(50.0));
2041 assert_eq!(p.metric, SizeMetric::Percent);
2042
2043 let nan_t = a.interpolate(&b, f32::NAN);
2045 assert_eq!(nan_t.number.get(), 0.0);
2046 assert_eq!(nan_t.metric, SizeMetric::Em);
2047 assert!(a.interpolate(&b, f32::INFINITY).number.get().is_finite());
2048 }
2049
2050 #[test]
2051 fn interpolate_across_metrics_falls_back_to_px() {
2052 let from_px = PixelValue::px(0.0);
2054 let to_em = PixelValue::em(1.0); let mid = from_px.interpolate(&to_em, 0.5);
2056 assert_eq!(mid.metric, SizeMetric::Px);
2057 assert!(approx(mid.number.get(), DEFAULT_FONT_SIZE / 2.0));
2058
2059 assert!(approx(
2060 PixelValue::px(0.0)
2061 .interpolate(&PixelValue::pt(72.0), 1.0)
2062 .number
2063 .get(),
2064 96.0
2065 ));
2066
2067 for metric in [
2071 SizeMetric::Percent,
2072 SizeMetric::Vw,
2073 SizeMetric::Vh,
2074 SizeMetric::Vmin,
2075 SizeMetric::Vmax,
2076 ] {
2077 let other = PixelValue::from_metric(metric, 50.0);
2078 let done = PixelValue::px(100.0).interpolate(&other, 1.0);
2079 assert_eq!(
2080 done,
2081 PixelValue::px(0.0),
2082 "{metric:?} should collapse to 0px on the cross-metric path"
2083 );
2084 }
2085
2086 let nan_t = PixelValue::px(0.0).interpolate(&PixelValue::em(1.0), f32::NAN);
2087 assert_eq!(nan_t.number.get(), 0.0);
2088 }
2089
2090 #[test]
2091 fn to_pixels_internal_converts_every_absolute_and_relative_unit() {
2092 assert_eq!(PixelValue::px(10.0).to_pixels_internal(0.0, 16.0, 16.0), 10.0);
2093 assert!(approx(
2094 PixelValue::pt(72.0).to_pixels_internal(0.0, 16.0, 16.0),
2095 96.0
2096 ));
2097 assert!(approx(
2098 PixelValue::inch(1.0).to_pixels_internal(0.0, 16.0, 16.0),
2099 96.0
2100 ));
2101 assert!(approx(
2102 PixelValue::cm(2.54).to_pixels_internal(0.0, 16.0, 16.0),
2103 96.0
2104 ));
2105 assert!(approx(
2106 PixelValue::mm(25.4).to_pixels_internal(0.0, 16.0, 16.0),
2107 96.0
2108 ));
2109 assert_eq!(PT_TO_PX, 96.0 / 72.0);
2110
2111 assert_eq!(PixelValue::em(2.0).to_pixels_internal(0.0, 10.0, 100.0), 20.0);
2114 assert_eq!(
2115 PixelValue::rem(2.0).to_pixels_internal(0.0, 10.0, 100.0),
2116 200.0
2117 );
2118
2119 assert_eq!(
2122 PixelValue::percent(50.0).to_pixels_internal(800.0, 16.0, 16.0),
2123 400.0
2124 );
2125 assert_eq!(
2126 PixelValue::percent(0.0).to_pixels_internal(800.0, 16.0, 16.0),
2127 0.0
2128 );
2129 assert_eq!(
2130 PixelValue::percent(-50.0).to_pixels_internal(800.0, 16.0, 16.0),
2131 -400.0
2132 );
2133
2134 for metric in [
2137 SizeMetric::Vw,
2138 SizeMetric::Vh,
2139 SizeMetric::Vmin,
2140 SizeMetric::Vmax,
2141 ] {
2142 assert_eq!(
2143 PixelValue::from_metric(metric, 50.0).to_pixels_internal(800.0, 16.0, 16.0),
2144 0.0,
2145 "{metric:?} must resolve to 0 on the legacy path"
2146 );
2147 }
2148 }
2149
2150 #[test]
2151 fn to_pixels_internal_non_finite_resolves_are_defined_not_panics() {
2152 assert!(PixelValue::em(1.0)
2155 .to_pixels_internal(0.0, f32::NAN, 0.0)
2156 .is_nan());
2157 assert!(PixelValue::rem(1.0)
2158 .to_pixels_internal(0.0, 0.0, f32::INFINITY)
2159 .is_infinite());
2160 assert!(PixelValue::percent(50.0)
2161 .to_pixels_internal(f32::INFINITY, 16.0, 16.0)
2162 .is_infinite());
2163 assert!(PixelValue::percent(50.0)
2164 .to_pixels_internal(f32::NAN, 16.0, 16.0)
2165 .is_nan());
2166 assert!(PixelValue::percent(0.0)
2168 .to_pixels_internal(f32::INFINITY, 16.0, 16.0)
2169 .is_nan());
2170
2171 assert!(PixelValue::em(f32::MAX)
2173 .to_pixels_internal(0.0, f32::MAX, 0.0)
2174 .is_infinite());
2175
2176 for v in EXTREME_F32 {
2178 assert!(PixelValue::px(v)
2179 .to_pixels_internal(f32::NAN, f32::NAN, f32::NAN)
2180 .is_finite());
2181 }
2182 }
2183
2184 #[test]
2185 fn pixel_value_no_percent_to_pixels_internal_zeroes_out_percentages() {
2186 assert_eq!(
2187 PixelValueNoPercent::from(PixelValue::px(10.0)).to_pixels_internal(16.0, 16.0),
2188 10.0
2189 );
2190 assert_eq!(
2191 PixelValueNoPercent::from(PixelValue::em(2.0)).to_pixels_internal(10.0, 100.0),
2192 20.0
2193 );
2194 assert_eq!(
2195 PixelValueNoPercent::from(PixelValue::rem(2.0)).to_pixels_internal(10.0, 100.0),
2196 200.0
2197 );
2198
2199 assert_eq!(
2202 PixelValueNoPercent::from(PixelValue::percent(50.0)).to_pixels_internal(16.0, 16.0),
2203 0.0
2204 );
2205 assert_eq!(PixelValueNoPercent::zero().to_pixels_internal(16.0, 16.0), 0.0);
2206 assert_eq!(PixelValueNoPercent::zero().inner, PixelValue::zero());
2207 assert_eq!(PixelValueNoPercent::default().inner, PixelValue::zero());
2208 }
2209
2210 #[test]
2213 fn to_percent_is_some_only_for_the_percent_metric() {
2214 for metric in ALL_METRICS {
2215 let v = PixelValue::from_metric(metric, 50.0);
2216 if metric == SizeMetric::Percent {
2217 assert_eq!(v.to_percent().unwrap().get(), 0.5, "50% must normalize to 0.5");
2218 } else {
2219 assert!(
2220 v.to_percent().is_none(),
2221 "{metric:?} must not masquerade as a percentage"
2222 );
2223 }
2224 }
2225
2226 assert_eq!(
2229 PixelValue::percent(50.0)
2230 .to_percent()
2231 .unwrap()
2232 .resolve(640.0),
2233 320.0
2234 );
2235 assert_eq!(
2236 PixelValue::percent(-50.0).to_percent().unwrap().get(),
2237 -0.5
2238 );
2239 assert_eq!(PixelValue::percent(0.0).to_percent().unwrap().get(), 0.0);
2240 assert!(PixelValue::percent(f32::MAX)
2242 .to_percent()
2243 .unwrap()
2244 .get()
2245 .is_finite());
2246 assert_eq!(
2247 PixelValue::percent(f32::NAN).to_percent().unwrap().get(),
2248 0.0
2249 );
2250 }
2251
2252 #[test]
2253 fn normalized_percentage_new_and_from_unnormalized_disagree_by_100x() {
2254 assert_eq!(NormalizedPercentage::new(0.5).get(), 0.5);
2257 assert_eq!(NormalizedPercentage::from_unnormalized(50.0).get(), 0.5);
2258 assert_eq!(NormalizedPercentage::from_unnormalized(0.0).get(), 0.0);
2259 assert_eq!(NormalizedPercentage::from_unnormalized(100.0).get(), 1.0);
2260 assert_eq!(NormalizedPercentage::from_unnormalized(-25.0).get(), -0.25);
2261
2262 assert_eq!(NormalizedPercentage::new(0.5).resolve(640.0), 320.0);
2263 assert_eq!(NormalizedPercentage::new(0.0).resolve(640.0), 0.0);
2264 assert_eq!(NormalizedPercentage::new(1.0).resolve(f32::MAX), f32::MAX);
2265 assert_eq!(NormalizedPercentage::new(-1.0).resolve(100.0), -100.0);
2266
2267 assert!(NormalizedPercentage::new(f32::NAN).get().is_nan());
2270 assert!(NormalizedPercentage::new(f32::NAN).resolve(100.0).is_nan());
2271 assert!(NormalizedPercentage::from_unnormalized(f32::INFINITY)
2272 .get()
2273 .is_infinite());
2274 assert!(NormalizedPercentage::new(1.0)
2275 .resolve(f32::INFINITY)
2276 .is_infinite());
2277 assert!(NormalizedPercentage::new(0.0)
2279 .resolve(f32::INFINITY)
2280 .is_nan());
2281
2282 assert_eq!(NormalizedPercentage::new(0.5).to_string(), "50%");
2284 assert_eq!(NormalizedPercentage::new(0.0).to_string(), "0%");
2285 assert!(!NormalizedPercentage::new(f32::NAN).to_string().is_empty());
2286 assert!(!NormalizedPercentage::new(f32::INFINITY)
2287 .to_string()
2288 .is_empty());
2289 }
2290
2291 #[test]
2292 fn resolve_with_context_reads_the_right_reference_for_each_property() {
2293 let ctx = distinct_context(); assert_eq!(
2299 PixelValue::em(2.0).resolve_with_context(&ctx, PropertyContext::Margin),
2300 64.0
2301 );
2302 assert_eq!(
2303 PixelValue::em(2.0).resolve_with_context(&ctx, PropertyContext::FontSize),
2304 16.0
2305 );
2306
2307 for pc in ALL_PROPERTY_CONTEXTS {
2309 assert_eq!(
2310 PixelValue::rem(2.0).resolve_with_context(&ctx, pc),
2311 8.0,
2312 "rem must ignore the property context ({pc:?})"
2313 );
2314 }
2315
2316 let pct = PixelValue::percent(50.0);
2318 assert_eq!(
2319 pct.resolve_with_context(&ctx, PropertyContext::Width),
2320 400.0,
2321 "width % -> containing block WIDTH"
2322 );
2323 assert_eq!(
2324 pct.resolve_with_context(&ctx, PropertyContext::Height),
2325 300.0,
2326 "height % -> containing block HEIGHT"
2327 );
2328 assert_eq!(
2329 pct.resolve_with_context(&ctx, PropertyContext::Margin),
2330 400.0,
2331 "margin % -> containing block WIDTH, even vertically (CSS 2.1 §8.3)"
2332 );
2333 assert_eq!(
2334 pct.resolve_with_context(&ctx, PropertyContext::Padding),
2335 400.0,
2336 "padding % -> containing block WIDTH, even vertically (CSS 2.1 §8.4)"
2337 );
2338 assert_eq!(
2339 pct.resolve_with_context(&ctx, PropertyContext::Other),
2340 400.0
2341 );
2342 assert_eq!(
2343 pct.resolve_with_context(&ctx, PropertyContext::FontSize),
2344 4.0,
2345 "font-size % -> PARENT font size"
2346 );
2347 assert_eq!(
2348 pct.resolve_with_context(&ctx, PropertyContext::BorderRadius),
2349 100.0,
2350 "border-radius % -> the element's own box"
2351 );
2352 assert_eq!(
2353 pct.resolve_with_context(&ctx, PropertyContext::Transform),
2354 100.0
2355 );
2356 assert_eq!(
2357 pct.resolve_with_context(&ctx, PropertyContext::BorderWidth),
2358 0.0,
2359 "% is invalid on border-width (CSS Backgrounds 3 §4.1) -> 0"
2360 );
2361 }
2362
2363 #[test]
2364 fn resolve_with_context_percent_without_an_element_size_is_zero() {
2365 let ctx = ResolutionContext {
2368 vertical_writing_mode: false,
2369 element_size: None,
2370 ..distinct_context()
2371 };
2372 assert_eq!(
2373 PixelValue::percent(50.0)
2374 .resolve_with_context(&ctx, PropertyContext::BorderRadius),
2375 0.0
2376 );
2377 assert_eq!(
2378 PixelValue::percent(50.0).resolve_with_context(&ctx, PropertyContext::Transform),
2379 0.0
2380 );
2381 }
2382
2383 #[test]
2384 fn resolve_with_context_absolute_units_ignore_the_context_entirely() {
2385 let sane = distinct_context();
2386 let poisoned = ResolutionContext {
2387 vertical_writing_mode: false,
2388 element_font_size: f32::NAN,
2389 parent_font_size: f32::INFINITY,
2390 root_font_size: f32::NEG_INFINITY,
2391 containing_block_size: PhysicalSize::new(f32::NAN, f32::NAN),
2392 element_size: Some(PhysicalSize::new(f32::INFINITY, f32::NAN)),
2393 viewport_size: PhysicalSize::new(f32::NAN, f32::INFINITY),
2394 };
2395
2396 let absolutes = [
2397 PixelValue::px(10.0),
2398 PixelValue::pt(10.0),
2399 PixelValue::inch(10.0),
2400 PixelValue::cm(10.0),
2401 PixelValue::mm(10.0),
2402 ];
2403 for v in absolutes {
2404 for pc in ALL_PROPERTY_CONTEXTS {
2405 let a = v.resolve_with_context(&sane, pc);
2406 let b = v.resolve_with_context(&poisoned, pc);
2407 assert_eq!(a, b, "{:?} must not read the context ({pc:?})", v.metric);
2408 assert!(a.is_finite());
2409 }
2410 }
2411
2412 assert_eq!(
2414 PixelValue::px(10.0).resolve_with_context(&sane, PropertyContext::Width),
2415 10.0
2416 );
2417 assert!(approx(
2418 PixelValue::inch(1.0).resolve_with_context(&sane, PropertyContext::Width),
2419 96.0
2420 ));
2421 assert!(approx(
2422 PixelValue::pt(72.0).resolve_with_context(&sane, PropertyContext::Width),
2423 96.0
2424 ));
2425 assert!(approx(
2426 PixelValue::cm(2.54).resolve_with_context(&sane, PropertyContext::Width),
2427 96.0
2428 ));
2429 assert!(approx(
2430 PixelValue::mm(25.4).resolve_with_context(&sane, PropertyContext::Width),
2431 96.0
2432 ));
2433 }
2434
2435 #[test]
2436 fn resolve_with_context_viewport_units_use_the_viewport() {
2437 let ctx = distinct_context(); assert_eq!(
2440 PixelValue::from_metric(SizeMetric::Vw, 10.0)
2441 .resolve_with_context(&ctx, PropertyContext::Width),
2442 100.0
2443 );
2444 assert_eq!(
2445 PixelValue::from_metric(SizeMetric::Vh, 10.0)
2446 .resolve_with_context(&ctx, PropertyContext::Width),
2447 50.0
2448 );
2449 assert_eq!(
2450 PixelValue::from_metric(SizeMetric::Vmin, 10.0)
2451 .resolve_with_context(&ctx, PropertyContext::Width),
2452 50.0,
2453 "vmin must take the SMALLER viewport dimension"
2454 );
2455 assert_eq!(
2456 PixelValue::from_metric(SizeMetric::Vmax, 10.0)
2457 .resolve_with_context(&ctx, PropertyContext::Width),
2458 100.0,
2459 "vmax must take the LARGER viewport dimension"
2460 );
2461
2462 let zero_vp = ResolutionContext::default_const();
2465 for metric in [
2466 SizeMetric::Vw,
2467 SizeMetric::Vh,
2468 SizeMetric::Vmin,
2469 SizeMetric::Vmax,
2470 ] {
2471 assert_eq!(
2472 PixelValue::from_metric(metric, 100.0)
2473 .resolve_with_context(&zero_vp, PropertyContext::Width),
2474 0.0,
2475 "{metric:?} against a 0x0 viewport must be 0"
2476 );
2477 }
2478
2479 let nan_vp = ResolutionContext {
2481 vertical_writing_mode: false,
2482 viewport_size: PhysicalSize::new(f32::NAN, f32::NAN),
2483 ..distinct_context()
2484 };
2485 assert!(PixelValue::from_metric(SizeMetric::Vw, 10.0)
2486 .resolve_with_context(&nan_vp, PropertyContext::Width)
2487 .is_nan());
2488 let half_nan_vp = ResolutionContext {
2491 vertical_writing_mode: false,
2492 viewport_size: PhysicalSize::new(f32::NAN, 500.0),
2493 ..distinct_context()
2494 };
2495 assert_eq!(
2496 PixelValue::from_metric(SizeMetric::Vmin, 10.0)
2497 .resolve_with_context(&half_nan_vp, PropertyContext::Width),
2498 50.0
2499 );
2500 }
2501
2502 #[test]
2503 fn resolve_with_context_never_panics_on_extreme_values() {
2504 let ctx = distinct_context();
2505 for metric in ALL_METRICS {
2506 for v in EXTREME_F32 {
2507 for pc in ALL_PROPERTY_CONTEXTS {
2508 let _ = PixelValue::from_metric(metric, v).resolve_with_context(&ctx, pc);
2510 }
2511 }
2512 }
2513 }
2514
2515 #[test]
2516 fn resolution_context_default_matches_default_const() {
2517 let a = ResolutionContext::default();
2519 let b = ResolutionContext::default_const();
2520
2521 assert_eq!(a.element_font_size, b.element_font_size);
2522 assert_eq!(a.parent_font_size, b.parent_font_size);
2523 assert_eq!(a.root_font_size, b.root_font_size);
2524 assert_eq!(a.containing_block_size, b.containing_block_size);
2525 assert_eq!(a.element_size, b.element_size);
2526 assert_eq!(a.viewport_size, b.viewport_size);
2527
2528 assert_eq!(a.element_font_size, DEFAULT_FONT_SIZE);
2530 assert!(a.element_size.is_none());
2531 }
2532
2533 #[test]
2534 fn logical_and_physical_sizes_round_trip() {
2535 let logical = CssLogicalSize::new(800.0, 600.0);
2536 assert_eq!(logical.to_physical(), PhysicalSize::new(800.0, 600.0));
2537 assert_eq!(logical.to_physical().to_logical(), logical);
2538
2539 let physical = PhysicalSize::new(1920.0, 1080.0);
2540 assert_eq!(physical.to_logical(), CssLogicalSize::new(1920.0, 1080.0));
2541 assert_eq!(physical.to_logical().to_physical(), physical);
2542
2543 assert_eq!(CssLogicalSize::new(800.0, 600.0).to_physical().width, 800.0);
2546 assert_eq!(PhysicalSize::new(800.0, 600.0).to_logical().block_size, 600.0);
2547
2548 let nan = PhysicalSize::new(f32::NAN, f32::INFINITY);
2550 assert!(nan.to_logical().inline_size.is_nan());
2551 assert!(nan.to_logical().block_size.is_infinite());
2552 }
2553
2554 #[test]
2557 fn every_rendering_of_a_pixel_value_agrees() {
2558 for metric in ALL_METRICS {
2561 let v = PixelValue::from_metric(metric, 1.5);
2562 let display = v.to_string();
2563 assert_eq!(format!("{v:?}"), display, "Debug != Display for {metric:?}");
2564 assert_eq!(v.print_as_css_value(), display);
2565 assert_eq!(as_css_value(v), display);
2566 assert!(display.starts_with("1.5"), "{display} lost its number");
2567 assert!(display.len() > 3, "{display} lost its unit");
2568 }
2569
2570 assert_eq!(PixelValue::px(10.0).to_string(), "10px");
2571 assert_eq!(PixelValue::percent(50.0).to_string(), "50%");
2572 assert_eq!(PixelValue::zero().to_string(), "0px");
2573 assert_eq!(
2574 PixelValue::from_metric(SizeMetric::Vmin, 12.0).to_string(),
2575 "12vmin"
2576 );
2577
2578 let np = PixelValueNoPercent::from(PixelValue::px(10.0));
2580 assert_eq!(np.to_string(), "10px");
2581 assert_eq!(format!("{np:?}"), "10px");
2582 assert_eq!(PixelValueNoPercent::zero().to_string(), "0px");
2583 }
2584
2585 #[test]
2586 fn display_never_leaks_nan_or_infinity_into_css() {
2587 for metric in ALL_METRICS {
2591 for v in EXTREME_F32 {
2592 let s = PixelValue::from_metric(metric, v).to_string();
2593 assert!(
2594 !s.contains("NaN") && !s.contains("inf"),
2595 "{metric:?} with input {v} serialized to {s:?}"
2596 );
2597 assert!(!s.is_empty());
2598 }
2599 }
2600 assert_eq!(PixelValue::px(f32::NAN).to_string(), "0px");
2601 }
2602
2603 #[test]
2604 fn pixel_values_round_trip_through_css_for_every_metric_but_vmin() {
2605 for metric in ALL_METRICS {
2607 if metric == SizeMetric::Vmin {
2608 continue; }
2610 for number in [0.0_f32, 1.0, 1.5, -20.0, 0.001, 12345.0] {
2611 let original = PixelValue::from_metric(metric, number);
2612 let css = original.print_as_css_value();
2613 let reparsed = parse_pixel_value(&css).unwrap_or_else(|e| {
2614 panic!("{css:?} (from {metric:?} {number}) failed to re-parse: {e:?}")
2615 });
2616 assert_eq!(reparsed, original, "round-trip broke for {css:?}");
2617 assert_eq!(reparsed.print_as_css_value(), css);
2619 }
2620 }
2621
2622 for metric in ALL_METRICS {
2624 if metric == SizeMetric::Vmin || metric == SizeMetric::Percent {
2625 continue;
2626 }
2627 let original = PixelValueNoPercent::from(PixelValue::from_metric(metric, 7.0));
2628 let css = original.to_string();
2629 assert_eq!(
2630 parse_pixel_value_no_percent(&css).unwrap(),
2631 original,
2632 "no-percent round-trip broke for {css:?}"
2633 );
2634 }
2635
2636 for (css, expected) in [
2638 ("auto", PixelValueWithAuto::Auto),
2639 ("none", PixelValueWithAuto::None),
2640 ("initial", PixelValueWithAuto::Initial),
2641 ("inherit", PixelValueWithAuto::Inherit),
2642 ] {
2643 assert_eq!(parse_pixel_value_with_auto(css).unwrap(), expected);
2644 }
2645 let exact = PixelValue::em(1.5);
2646 assert_eq!(
2647 parse_pixel_value_with_auto(&exact.print_as_css_value()).unwrap(),
2648 PixelValueWithAuto::Exact(exact)
2649 );
2650 }
2651
2652 #[test]
2653 fn format_as_rust_code_emits_a_reconstructible_literal() {
2654 assert_eq!(
2655 PixelValue::px(10.0).format_as_rust_code(0),
2656 "PixelValue { metric: Px, number: FloatValue::new(10) }"
2657 );
2658 assert_eq!(
2659 PixelValue::percent(-1.5).format_as_rust_code(4),
2660 "PixelValue { metric: Percent, number: FloatValue::new(-1.5) }"
2661 );
2662 let nan = PixelValue::from_metric(SizeMetric::Vmax, f32::NAN).format_as_rust_code(0);
2664 assert_eq!(nan, "PixelValue { metric: Vmax, number: FloatValue::new(0) }");
2665 assert!(!PixelValue::px(f32::INFINITY)
2666 .format_as_rust_code(0)
2667 .contains("inf"));
2668 }
2669
2670 #[test]
2671 fn border_thickness_constants_match_the_css_keywords() {
2672 assert_eq!(THIN_BORDER_THICKNESS, PixelValue::px(1.0));
2675 assert_eq!(MEDIUM_BORDER_THICKNESS, PixelValue::px(3.0));
2676 assert_eq!(THICK_BORDER_THICKNESS, PixelValue::px(5.0));
2677
2678 assert_eq!(THIN_BORDER_THICKNESS.number.get(), 1.0);
2679 assert_eq!(MEDIUM_BORDER_THICKNESS.number.get(), 3.0);
2680 assert_eq!(THICK_BORDER_THICKNESS.number.get(), 5.0);
2681 assert_eq!(THIN_BORDER_THICKNESS.number.number() as f32, MULT);
2682
2683 assert!(THIN_BORDER_THICKNESS < MEDIUM_BORDER_THICKNESS);
2684 assert!(MEDIUM_BORDER_THICKNESS < THICK_BORDER_THICKNESS);
2685 assert_eq!(THIN_BORDER_THICKNESS.to_string(), "1px");
2686 }
2687
2688 #[test]
2689 fn ord_is_lexicographic_by_metric_then_number_not_by_resolved_size() {
2690 assert!(PixelValue::px(100.0) < PixelValue::em(1.0));
2694 assert!(PixelValue::px(1.0) < PixelValue::px(2.0));
2695 assert!(PixelValue::percent(1.0) > PixelValue::mm(9999.0));
2696
2697 let a = PixelValue::px(1.5);
2699 let b = PixelValue::px(1.5);
2700 assert_eq!(a, b);
2701 assert_eq!(hash_of(&a), hash_of(&b));
2702 assert_ne!(hash_of(&PixelValue::px(1.0)), hash_of(&PixelValue::em(1.0)));
2703
2704 assert_eq!(PixelValue::px(1.0001), PixelValue::px(1.0002));
2707 assert_eq!(
2708 hash_of(&PixelValue::px(1.0001)),
2709 hash_of(&PixelValue::px(1.0002))
2710 );
2711 }
2712
2713 #[test]
2716 fn system_metric_ref_css_strings_round_trip() {
2717 for r in ALL_SYSTEM_REFS {
2718 let css = r.as_css_str();
2719 assert!(
2720 css.starts_with("system:"),
2721 "{css:?} is missing the system: prefix"
2722 );
2723 assert_eq!(r.to_string(), css, "Display must match as_css_str");
2724 assert_eq!(as_css_value(r), css);
2725
2726 let name = css.strip_prefix("system:").unwrap();
2728 assert_eq!(
2729 SystemMetricRef::from_css_str(name),
2730 Some(r),
2731 "{name:?} must parse back to {r:?}"
2732 );
2733 assert_eq!(SystemMetricRef::from_css_str(name).unwrap().as_css_str(), css);
2735
2736 assert_eq!(SystemMetricRef::from_css_str(css), None);
2739 }
2740
2741 assert_eq!(SystemMetricRef::default(), SystemMetricRef::ButtonRadius);
2742 }
2743
2744 #[test]
2745 fn system_metric_ref_from_css_str_rejects_everything_else() {
2746 for input in [
2747 "",
2748 " ",
2749 "\t\n",
2750 " button-radius ", "Button-Radius", "button_radius", "button-padding", "button-radius;x",
2755 "\u{1F600}",
2756 "b\u{0301}utton-radius",
2757 ] {
2758 assert_eq!(
2759 SystemMetricRef::from_css_str(input),
2760 None,
2761 "{input:?} must not resolve to a system metric"
2762 );
2763 }
2764
2765 assert_eq!(
2767 SystemMetricRef::from_css_str(&"a".repeat(100_000)),
2768 None
2769 );
2770 assert_eq!(SystemMetricRef::from_css_str(&"(".repeat(10_000)), None);
2771 }
2772
2773 #[test]
2774 fn system_metric_ref_resolve_maps_each_variant_to_its_own_field() {
2775 let metrics = populated_metrics();
2777 let expected = [
2778 (SystemMetricRef::ButtonRadius, 1.0),
2779 (SystemMetricRef::ButtonBorderWidth, 2.0),
2780 (SystemMetricRef::ButtonPaddingHorizontal, 3.0),
2781 (SystemMetricRef::ButtonPaddingVertical, 4.0),
2782 (SystemMetricRef::TitlebarHeight, 5.0),
2783 (SystemMetricRef::TitlebarButtonWidth, 6.0),
2784 (SystemMetricRef::TitlebarPadding, 7.0),
2785 (SystemMetricRef::SafeAreaTop, 8.0),
2786 (SystemMetricRef::SafeAreaBottom, 9.0),
2787 (SystemMetricRef::SafeAreaLeft, 10.0),
2788 (SystemMetricRef::SafeAreaRight, 11.0),
2789 ];
2790 for (r, px) in expected {
2791 assert_eq!(
2792 r.resolve(&metrics),
2793 Some(PixelValue::px(px)),
2794 "{r:?} resolved to the wrong field"
2795 );
2796 }
2797
2798 let empty = SystemMetrics::default();
2800 for r in ALL_SYSTEM_REFS {
2801 assert_eq!(r.resolve(&empty), None, "{r:?} must be None when unset");
2802 }
2803 }
2804
2805 #[test]
2806 fn pixel_value_or_system_resolves_and_falls_back() {
2807 let metrics = populated_metrics();
2808 let empty = SystemMetrics::default();
2809 let fallback = PixelValue::px(99.0);
2810
2811 let concrete = PixelValueOrSystem::value(PixelValue::px(10.0));
2813 assert_eq!(concrete.resolve(&metrics, fallback), PixelValue::px(10.0));
2814 assert_eq!(concrete.resolve(&empty, fallback), PixelValue::px(10.0));
2815
2816 let sys = PixelValueOrSystem::system(SystemMetricRef::ButtonRadius);
2818 assert_eq!(sys.resolve(&metrics, fallback), PixelValue::px(1.0));
2819 for r in ALL_SYSTEM_REFS {
2821 assert_eq!(
2822 PixelValueOrSystem::system(r).resolve(&empty, fallback),
2823 fallback,
2824 "{r:?} must fall back when the metric is unset"
2825 );
2826 }
2827
2828 let nan_fallback = PixelValue::px(f32::NAN);
2830 assert_eq!(
2831 sys.resolve(&empty, nan_fallback).number.get(),
2832 0.0
2833 );
2834
2835 assert_eq!(
2837 PixelValueOrSystem::default(),
2838 PixelValueOrSystem::Value(PixelValue::zero())
2839 );
2840 assert_eq!(
2841 PixelValueOrSystem::from(PixelValue::em(2.0)),
2842 PixelValueOrSystem::Value(PixelValue::em(2.0))
2843 );
2844 assert_eq!(
2845 PixelValueOrSystem::default().resolve(&metrics, fallback),
2846 PixelValue::zero()
2847 );
2848 }
2849
2850 #[test]
2851 fn pixel_value_or_system_renders_both_arms() {
2852 let concrete = PixelValueOrSystem::value(PixelValue::px(10.0));
2853 assert_eq!(concrete.to_string(), "10px");
2854 assert_eq!(as_css_value(concrete), "10px");
2855
2856 let sys = PixelValueOrSystem::system(SystemMetricRef::TitlebarHeight);
2857 assert_eq!(sys.to_string(), "system:titlebar-height");
2858 assert_eq!(as_css_value(sys), "system:titlebar-height");
2859
2860 assert_eq!(PixelValueOrSystem::default().to_string(), "0px");
2861
2862 for v in EXTREME_F32 {
2864 let s = PixelValueOrSystem::value(PixelValue::px(v)).to_string();
2865 assert!(!s.contains("NaN") && !s.contains("inf"), "leaked {s:?}");
2866 }
2867 }
2868}