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 viewport_size: PhysicalSize,
168}
169
170impl Default for ResolutionContext {
171 fn default() -> Self {
172 Self {
173 element_font_size: 16.0,
174 parent_font_size: 16.0,
175 root_font_size: 16.0,
176 containing_block_size: PhysicalSize::new(0.0, 0.0),
177 element_size: None,
178 viewport_size: PhysicalSize::new(0.0, 0.0),
179 }
180 }
181}
182
183impl ResolutionContext {
184 #[inline]
186 #[must_use] pub const fn default_const() -> Self {
187 Self {
188 element_font_size: 16.0,
189 parent_font_size: 16.0,
190 root_font_size: 16.0,
191 containing_block_size: PhysicalSize {
192 width: 0.0,
193 height: 0.0,
194 },
195 element_size: None,
196 viewport_size: PhysicalSize {
197 width: 0.0,
198 height: 0.0,
199 },
200 }
201 }
202
203}
204
205#[derive(Debug, Copy, Clone, PartialEq, Eq)]
207pub enum PropertyContext {
208 FontSize,
210 Margin,
212 Padding,
214 Width,
216 Height,
218 BorderWidth,
220 BorderRadius,
222 Transform,
224 Other,
226}
227
228#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
230#[repr(C)]
231pub struct PixelValue {
232 pub metric: SizeMetric,
233 pub number: FloatValue,
234}
235
236impl PixelValue {
237 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
238 self.number = FloatValue::new(self.number.get() * scale_factor);
239 }
240}
241
242impl FormatAsCssValue for PixelValue {
243 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 write!(f, "{}{}", self.number, self.metric)
245 }
246}
247
248impl crate::css::PrintAsCssValue for PixelValue {
249 fn print_as_css_value(&self) -> String {
250 format!("{}{}", self.number, self.metric)
251 }
252}
253
254impl crate::codegen::format::FormatAsRustCode for PixelValue {
255 fn format_as_rust_code(&self, _tabs: usize) -> String {
256 format!(
257 "PixelValue {{ metric: {:?}, number: FloatValue::new({}) }}",
258 self.metric,
259 self.number.get()
260 )
261 }
262}
263
264impl fmt::Debug for PixelValue {
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 write!(f, "{}{}", self.number, self.metric)
268 }
269}
270
271impl fmt::Display for PixelValue {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 write!(f, "{}{}", self.number, self.metric)
274 }
275}
276
277impl PixelValue {
278 #[inline]
279 #[must_use] pub const fn zero() -> Self {
280 const ZERO_PX: PixelValue = PixelValue::const_px(0);
281 ZERO_PX
282 }
283
284 #[inline]
287 #[must_use] pub const fn const_px(value: isize) -> Self {
288 Self::const_from_metric(SizeMetric::Px, value)
289 }
290
291 #[inline]
294 #[must_use] pub const fn const_em(value: isize) -> Self {
295 Self::const_from_metric(SizeMetric::Em, value)
296 }
297
298 #[inline]
311 #[must_use] pub const fn const_em_fractional(pre_comma: isize, post_comma: isize) -> Self {
312 Self::const_from_metric_fractional(SizeMetric::Em, pre_comma, post_comma)
313 }
314
315 #[inline]
318 #[must_use] pub const fn const_pt(value: isize) -> Self {
319 Self::const_from_metric(SizeMetric::Pt, value)
320 }
321
322 #[inline]
324 #[must_use] pub const fn const_pt_fractional(pre_comma: isize, post_comma: isize) -> Self {
325 Self::const_from_metric_fractional(SizeMetric::Pt, pre_comma, post_comma)
326 }
327
328 #[inline]
331 #[must_use] pub const fn const_percent(value: isize) -> Self {
332 Self::const_from_metric(SizeMetric::Percent, value)
333 }
334
335 #[inline]
338 #[must_use] pub const fn const_in(value: isize) -> Self {
339 Self::const_from_metric(SizeMetric::In, value)
340 }
341
342 #[inline]
345 #[must_use] pub const fn const_cm(value: isize) -> Self {
346 Self::const_from_metric(SizeMetric::Cm, value)
347 }
348
349 #[inline]
352 #[must_use] pub const fn const_mm(value: isize) -> Self {
353 Self::const_from_metric(SizeMetric::Mm, value)
354 }
355
356 #[inline]
357 #[must_use] pub const fn const_from_metric(metric: SizeMetric, value: isize) -> Self {
358 Self {
359 metric,
360 number: FloatValue::const_new(value),
361 }
362 }
363
364 #[inline]
371 #[must_use] pub const fn const_from_metric_fractional(
372 metric: SizeMetric,
373 pre_comma: isize,
374 post_comma: isize,
375 ) -> Self {
376 Self {
377 metric,
378 number: FloatValue::const_new_fractional(pre_comma, post_comma),
379 }
380 }
381
382 #[inline]
383 #[must_use] pub fn px(value: f32) -> Self {
384 Self::from_metric(SizeMetric::Px, value)
385 }
386
387 #[inline]
388 #[must_use] pub fn em(value: f32) -> Self {
389 Self::from_metric(SizeMetric::Em, value)
390 }
391
392 #[inline]
393 #[must_use] pub fn inch(value: f32) -> Self {
394 Self::from_metric(SizeMetric::In, value)
395 }
396
397 #[inline]
398 #[must_use] pub fn cm(value: f32) -> Self {
399 Self::from_metric(SizeMetric::Cm, value)
400 }
401
402 #[inline]
403 #[must_use] pub fn mm(value: f32) -> Self {
404 Self::from_metric(SizeMetric::Mm, value)
405 }
406
407 #[inline]
408 #[must_use] pub fn pt(value: f32) -> Self {
409 Self::from_metric(SizeMetric::Pt, value)
410 }
411
412 #[inline]
413 #[must_use] pub fn percent(value: f32) -> Self {
414 Self::from_metric(SizeMetric::Percent, value)
415 }
416
417 #[inline]
418 #[must_use] pub fn rem(value: f32) -> Self {
419 Self::from_metric(SizeMetric::Rem, value)
420 }
421
422 #[inline]
423 #[must_use] pub fn from_metric(metric: SizeMetric, value: f32) -> Self {
424 Self {
425 metric,
426 number: FloatValue::new(value),
427 }
428 }
429
430 #[inline]
431 #[allow(clippy::suboptimal_flops)] #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
433 if self.metric == other.metric {
434 Self {
435 metric: self.metric,
436 number: self.number.interpolate(&other.number, t),
437 }
438 } else {
439 let self_px_interp = self.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
442 let other_px_interp = other.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
443 Self::from_metric(
444 SizeMetric::Px,
445 self_px_interp + (other_px_interp - self_px_interp) * t,
446 )
447 }
448 }
449
450 #[inline]
456 #[must_use] pub fn to_percent(&self) -> Option<NormalizedPercentage> {
457 match self.metric {
458 SizeMetric::Percent => Some(NormalizedPercentage::from_unnormalized(self.number.get())),
459 _ => None,
460 }
461 }
462
463 #[doc(hidden)]
469 #[inline]
470 #[must_use] pub fn to_pixels_internal(&self, percent_resolve: f32, em_resolve: f32, rem_resolve: f32) -> f32 {
471 match self.metric {
472 SizeMetric::Px => self.number.get(),
473 SizeMetric::Pt => self.number.get() * PT_TO_PX,
474 SizeMetric::In => self.number.get() * 96.0,
475 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
476 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
477 SizeMetric::Em => self.number.get() * em_resolve,
478 SizeMetric::Rem => self.number.get() * rem_resolve,
479 SizeMetric::Percent => {
480 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(percent_resolve)
481 }
482 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => 0.0,
485 }
486 }
487
488 #[inline]
501 #[must_use] pub fn resolve_with_context(
502 &self,
503 context: &ResolutionContext,
504 property_context: PropertyContext,
505 ) -> f32 {
506 match self.metric {
507 SizeMetric::Px => self.number.get(),
509 SizeMetric::Pt => self.number.get() * PT_TO_PX,
510 SizeMetric::In => self.number.get() * 96.0,
511 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
512 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
513
514 SizeMetric::Em => {
516 let reference_font_size = if property_context == PropertyContext::FontSize {
517 context.parent_font_size
519 } else {
520 context.element_font_size
522 };
523 self.number.get() * reference_font_size
524 }
525
526 SizeMetric::Rem => self.number.get() * context.root_font_size,
528
529 SizeMetric::Vw => self.number.get() * context.viewport_size.width / 100.0,
532 SizeMetric::Vh => self.number.get() * context.viewport_size.height / 100.0,
533 SizeMetric::Vmin => {
535 let min_dimension = context
536 .viewport_size
537 .width
538 .min(context.viewport_size.height);
539 self.number.get() * min_dimension / 100.0
540 }
541 SizeMetric::Vmax => {
543 let max_dimension = context
544 .viewport_size
545 .width
546 .max(context.viewport_size.height);
547 self.number.get() * max_dimension / 100.0
548 }
549
550 SizeMetric::Percent => {
552 #[allow(clippy::match_same_arms)]
555 let reference = match property_context {
556 PropertyContext::FontSize => context.parent_font_size,
558
559 PropertyContext::Width => context.containing_block_size.width,
561
562 PropertyContext::Height => context.containing_block_size.height,
564
565 PropertyContext::Margin | PropertyContext::Padding => {
571 context.containing_block_size.width
572 }
573
574 PropertyContext::BorderWidth => 0.0,
577
578 PropertyContext::BorderRadius => {
582 context.element_size.map_or(0.0, |s| s.width)
583 }
584
585 PropertyContext::Transform => {
587 context.element_size.map_or(0.0, |s| s.width)
588 }
589
590 PropertyContext::Other => context.containing_block_size.width,
592 };
593
594 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(reference)
595 }
596 }
597 }
598}
599
600pub const THIN_BORDER_THICKNESS: PixelValue = PixelValue {
606 metric: SizeMetric::Px,
607 number: FloatValue { number: 1000 },
608};
609
610pub const MEDIUM_BORDER_THICKNESS: PixelValue = PixelValue {
612 metric: SizeMetric::Px,
613 number: FloatValue { number: 3000 },
614};
615
616pub const THICK_BORDER_THICKNESS: PixelValue = PixelValue {
618 metric: SizeMetric::Px,
619 number: FloatValue { number: 5000 },
620};
621
622#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
624#[repr(C)]
625pub struct PixelValueNoPercent {
626 pub inner: PixelValue,
627}
628
629impl PixelValueNoPercent {
630 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
631 self.inner.scale_for_dpi(scale_factor);
632 }
633}
634
635impl_option!(
636 PixelValueNoPercent,
637 OptionPixelValueNoPercent,
638 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
639);
640
641impl_option!(
642 PixelValue,
643 OptionPixelValue,
644 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
645);
646
647impl fmt::Display for PixelValueNoPercent {
648 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649 write!(f, "{}", self.inner)
650 }
651}
652
653impl ::core::fmt::Debug for PixelValueNoPercent {
654 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
655 write!(f, "{self}")
656 }
657}
658
659impl PixelValueNoPercent {
660 #[doc(hidden)]
666 #[inline]
667 #[must_use] pub fn to_pixels_internal(&self, em_resolve: f32, rem_resolve: f32) -> f32 {
668 self.inner.to_pixels_internal(0.0, em_resolve, rem_resolve)
669 }
670
671 #[inline]
672 #[must_use] pub const fn zero() -> Self {
673 const ZERO_PXNP: PixelValueNoPercent = PixelValueNoPercent {
674 inner: PixelValue::zero(),
675 };
676 ZERO_PXNP
677 }
678}
679impl From<PixelValue> for PixelValueNoPercent {
680 fn from(e: PixelValue) -> Self {
681 Self { inner: e }
682 }
683}
684
685#[derive(Clone, PartialEq, Eq)]
686pub enum CssPixelValueParseError<'a> {
687 EmptyString,
688 NoValueGiven(&'a str, SizeMetric),
689 ValueParseErr(ParseFloatError, &'a str),
690 InvalidPixelValue(&'a str),
691}
692
693impl_debug_as_display!(CssPixelValueParseError<'a>);
694
695impl_display! { CssPixelValueParseError<'a>, {
696 EmptyString => format!("Missing [px / pt / em / %] value"),
697 NoValueGiven(input, metric) => format!("Expected floating-point pixel value, got: \"{}{}\"", input, metric),
698 ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
699 InvalidPixelValue(s) => format!("Invalid pixel value: \"{}\"", s),
700}}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
704#[repr(C)]
705pub struct PixelNoValueGivenError {
706 pub value: AzString,
707 pub metric: SizeMetric,
708}
709
710#[derive(Debug, Clone, PartialEq, Eq)]
712#[repr(C, u8)]
713pub enum CssPixelValueParseErrorOwned {
714 EmptyString,
715 NoValueGiven(PixelNoValueGivenError),
716 ValueParseErr(ParseFloatErrorWithInput),
717 InvalidPixelValue(AzString),
718}
719
720impl CssPixelValueParseError<'_> {
721 #[must_use] pub fn to_contained(&self) -> CssPixelValueParseErrorOwned {
722 match self {
723 CssPixelValueParseError::EmptyString => CssPixelValueParseErrorOwned::EmptyString,
724 CssPixelValueParseError::NoValueGiven(s, metric) => {
725 CssPixelValueParseErrorOwned::NoValueGiven(PixelNoValueGivenError { value: (*s).to_string().into(), metric: *metric })
726 }
727 CssPixelValueParseError::ValueParseErr(err, s) => {
728 CssPixelValueParseErrorOwned::ValueParseErr(ParseFloatErrorWithInput { error: err.clone().into(), input: (*s).to_string().into() })
729 }
730 CssPixelValueParseError::InvalidPixelValue(s) => {
731 CssPixelValueParseErrorOwned::InvalidPixelValue((*s).to_string().into())
732 }
733 }
734 }
735}
736
737impl CssPixelValueParseErrorOwned {
738 #[must_use] pub fn to_shared(&self) -> CssPixelValueParseError<'_> {
739 match self {
740 Self::EmptyString => CssPixelValueParseError::EmptyString,
741 Self::NoValueGiven(e) => {
742 CssPixelValueParseError::NoValueGiven(e.value.as_str(), e.metric)
743 }
744 Self::ValueParseErr(e) => {
745 CssPixelValueParseError::ValueParseErr(e.error.to_std(), e.input.as_str())
746 }
747 Self::InvalidPixelValue(s) => {
748 CssPixelValueParseError::InvalidPixelValue(s.as_str())
749 }
750 }
751 }
752}
753
754fn parse_pixel_value_inner<'a>(
756 input: &'a str,
757 match_values: &[(&'static str, SizeMetric)],
758) -> Result<PixelValue, CssPixelValueParseError<'a>> {
759 let input = input.trim();
760
761 if input.is_empty() {
762 return Err(CssPixelValueParseError::EmptyString);
763 }
764
765 for (match_val, metric) in match_values {
766 if let Some(value) = input.strip_suffix(match_val) {
767 let value = value.trim();
768 if value.is_empty() {
769 return Err(CssPixelValueParseError::NoValueGiven(input, *metric));
770 }
771 match value.parse::<f32>() {
772 Ok(o) => {
773 return Ok(PixelValue::from_metric(*metric, o));
774 }
775 Err(e) => {
776 return Err(CssPixelValueParseError::ValueParseErr(e, value));
777 }
778 }
779 }
780 }
781
782 input.trim().parse::<f32>().map_or_else(
783 |_| Err(CssPixelValueParseError::InvalidPixelValue(input)),
784 |o| Ok(PixelValue::px(o)),
785 )
786}
787
788pub fn parse_pixel_value(input: &str) -> Result<PixelValue, CssPixelValueParseError<'_>> {
792 parse_pixel_value_inner(
793 input,
794 &[
795 ("px", SizeMetric::Px),
798 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
800 ("pt", SizeMetric::Pt),
801 ("vmax", SizeMetric::Vmax),
802 ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
804 ("vh", SizeMetric::Vh),
805 ("in", SizeMetric::In),
806 ("mm", SizeMetric::Mm),
807 ("cm", SizeMetric::Cm),
808 ("%", SizeMetric::Percent),
809 ],
810 )
811}
812
813pub fn parse_pixel_value_no_percent(
817 input: &str,
818) -> Result<PixelValueNoPercent, CssPixelValueParseError<'_>> {
819 Ok(PixelValueNoPercent {
820 inner: parse_pixel_value_inner(
821 input,
822 &[
823 ("px", SizeMetric::Px),
825 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
827 ("pt", SizeMetric::Pt),
828 ("vmax", SizeMetric::Vmax),
829 ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
831 ("vh", SizeMetric::Vh),
832 ("in", SizeMetric::In),
833 ("mm", SizeMetric::Mm),
834 ("cm", SizeMetric::Cm),
835 ],
836 )?,
837 })
838}
839
840#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
841pub enum PixelValueWithAuto {
842 None,
843 Initial,
844 Inherit,
845 Auto,
846 Exact(PixelValue),
847}
848
849pub fn parse_pixel_value_with_auto(
854 input: &str,
855) -> Result<PixelValueWithAuto, CssPixelValueParseError<'_>> {
856 let input = input.trim();
857 match input {
858 "none" => Ok(PixelValueWithAuto::None),
859 "initial" => Ok(PixelValueWithAuto::Initial),
860 "inherit" => Ok(PixelValueWithAuto::Inherit),
861 "auto" => Ok(PixelValueWithAuto::Auto),
862 e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
863 }
864}
865
866#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
875#[repr(C)]
876#[derive(Default)]
877pub enum SystemMetricRef {
878 #[default]
880 ButtonRadius,
881 ButtonPaddingHorizontal,
883 ButtonPaddingVertical,
885 ButtonBorderWidth,
887 TitlebarHeight,
889 TitlebarButtonWidth,
891 TitlebarPadding,
893 SafeAreaTop,
895 SafeAreaBottom,
897 SafeAreaLeft,
899 SafeAreaRight,
901}
902
903
904impl SystemMetricRef {
905 #[must_use] pub const fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
907 match self {
908 Self::ButtonRadius => metrics.corner_radius.as_option().copied(),
909 Self::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
910 Self::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
911 Self::ButtonBorderWidth => metrics.border_width.as_option().copied(),
912 Self::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
913 Self::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
914 Self::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
915 Self::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
916 Self::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
917 Self::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
918 Self::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
919 }
920 }
921
922 #[must_use] pub const fn as_css_str(&self) -> &'static str {
924 match self {
925 Self::ButtonRadius => "system:button-radius",
926 Self::ButtonPaddingHorizontal => "system:button-padding-horizontal",
927 Self::ButtonPaddingVertical => "system:button-padding-vertical",
928 Self::ButtonBorderWidth => "system:button-border-width",
929 Self::TitlebarHeight => "system:titlebar-height",
930 Self::TitlebarButtonWidth => "system:titlebar-button-width",
931 Self::TitlebarPadding => "system:titlebar-padding",
932 Self::SafeAreaTop => "system:safe-area-top",
933 Self::SafeAreaBottom => "system:safe-area-bottom",
934 Self::SafeAreaLeft => "system:safe-area-left",
935 Self::SafeAreaRight => "system:safe-area-right",
936 }
937 }
938
939 #[must_use] pub fn from_css_str(s: &str) -> Option<Self> {
941 match s {
942 "button-radius" => Some(Self::ButtonRadius),
943 "button-padding-horizontal" => Some(Self::ButtonPaddingHorizontal),
944 "button-padding-vertical" => Some(Self::ButtonPaddingVertical),
945 "button-border-width" => Some(Self::ButtonBorderWidth),
946 "titlebar-height" => Some(Self::TitlebarHeight),
947 "titlebar-button-width" => Some(Self::TitlebarButtonWidth),
948 "titlebar-padding" => Some(Self::TitlebarPadding),
949 "safe-area-top" => Some(Self::SafeAreaTop),
950 "safe-area-bottom" => Some(Self::SafeAreaBottom),
951 "safe-area-left" => Some(Self::SafeAreaLeft),
952 "safe-area-right" => Some(Self::SafeAreaRight),
953 _ => None,
954 }
955 }
956}
957
958impl fmt::Display for SystemMetricRef {
959 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
960 write!(f, "{}", self.as_css_str())
961 }
962}
963
964impl FormatAsCssValue for SystemMetricRef {
965 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
966 write!(f, "{}", self.as_css_str())
967 }
968}
969
970#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
975#[repr(C, u8)]
976pub enum PixelValueOrSystem {
977 Value(PixelValue),
979 System(SystemMetricRef),
981}
982
983impl Default for PixelValueOrSystem {
984 fn default() -> Self {
985 Self::Value(PixelValue::zero())
986 }
987}
988
989impl From<PixelValue> for PixelValueOrSystem {
990 fn from(value: PixelValue) -> Self {
991 Self::Value(value)
992 }
993}
994
995impl PixelValueOrSystem {
996 #[must_use] pub const fn value(v: PixelValue) -> Self {
998 Self::Value(v)
999 }
1000
1001 #[must_use] pub const fn system(s: SystemMetricRef) -> Self {
1003 Self::System(s)
1004 }
1005
1006 #[must_use] pub fn resolve(&self, system_metrics: &crate::system::SystemMetrics, fallback: PixelValue) -> PixelValue {
1009 match self {
1010 Self::Value(v) => *v,
1011 Self::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
1012 }
1013 }
1014
1015}
1016
1017impl fmt::Display for PixelValueOrSystem {
1018 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1019 match self {
1020 Self::Value(v) => write!(f, "{v}"),
1021 Self::System(s) => write!(f, "{s}"),
1022 }
1023 }
1024}
1025
1026impl FormatAsCssValue for PixelValueOrSystem {
1027 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1028 match self {
1029 Self::Value(v) => v.format_as_css_value(f),
1030 Self::System(s) => s.format_as_css_value(f),
1031 }
1032 }
1033}
1034
1035#[cfg(feature = "parser")]
1039pub fn parse_pixel_value_or_system(
1043 input: &str,
1044) -> Result<PixelValueOrSystem, CssPixelValueParseError<'_>> {
1045 let input = input.trim();
1046
1047 if let Some(metric_name) = input.strip_prefix("system:") {
1049 if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
1050 return Ok(PixelValueOrSystem::System(metric_ref));
1051 }
1052 return Err(CssPixelValueParseError::InvalidPixelValue(input));
1053 }
1054
1055 Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
1057}
1058
1059#[cfg(all(test, feature = "parser"))]
1060mod tests {
1061 #![allow(clippy::float_cmp)]
1063 use super::*;
1064
1065 #[test]
1066 fn test_parse_pixel_value() {
1067 assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
1068 assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
1069 assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
1070 assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
1071 assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
1072 assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
1073 assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
1074 assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
1075 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1076 }
1077
1078 #[test]
1079 fn test_resolve_with_context_em() {
1080 let context = ResolutionContext {
1082 element_font_size: 32.0,
1083 parent_font_size: 16.0,
1084 ..Default::default()
1085 };
1086
1087 let margin = PixelValue::em(0.67);
1089 assert!(
1090 (margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
1091 );
1092
1093 let font_size = PixelValue::em(2.0);
1095 assert_eq!(
1096 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1097 32.0
1098 );
1099 }
1100
1101 #[test]
1102 fn test_resolve_with_context_rem() {
1103 let context = ResolutionContext {
1105 element_font_size: 32.0,
1106 parent_font_size: 16.0,
1107 root_font_size: 18.0,
1108 ..Default::default()
1109 };
1110
1111 let margin = PixelValue::rem(2.0);
1113 assert_eq!(
1114 margin.resolve_with_context(&context, PropertyContext::Margin),
1115 36.0
1116 );
1117
1118 let font_size = PixelValue::rem(1.5);
1119 assert_eq!(
1120 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1121 27.0
1122 );
1123 }
1124
1125 #[test]
1126 fn test_resolve_with_context_percent_margin() {
1127 let context = ResolutionContext {
1129 element_font_size: 16.0,
1130 parent_font_size: 16.0,
1131 root_font_size: 16.0,
1132 containing_block_size: PhysicalSize::new(800.0, 600.0),
1133 element_size: None,
1134 viewport_size: PhysicalSize::new(1920.0, 1080.0),
1135 };
1136
1137 let margin = PixelValue::percent(10.0); assert_eq!(
1139 margin.resolve_with_context(&context, PropertyContext::Margin),
1140 80.0
1141 ); }
1143
1144 #[test]
1145 fn test_parse_pixel_value_no_percent() {
1146 assert_eq!(
1147 parse_pixel_value_no_percent("10px").unwrap().inner,
1148 PixelValue::px(10.0)
1149 );
1150 assert!(parse_pixel_value_no_percent("50%").is_err());
1151 }
1152
1153 #[test]
1154 fn test_parse_pixel_value_with_auto() {
1155 assert_eq!(
1156 parse_pixel_value_with_auto("10px").unwrap(),
1157 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1158 );
1159 assert_eq!(
1160 parse_pixel_value_with_auto("auto").unwrap(),
1161 PixelValueWithAuto::Auto
1162 );
1163 assert_eq!(
1164 parse_pixel_value_with_auto("initial").unwrap(),
1165 PixelValueWithAuto::Initial
1166 );
1167 assert_eq!(
1168 parse_pixel_value_with_auto("inherit").unwrap(),
1169 PixelValueWithAuto::Inherit
1170 );
1171 assert_eq!(
1172 parse_pixel_value_with_auto("none").unwrap(),
1173 PixelValueWithAuto::None
1174 );
1175 }
1176
1177 #[test]
1178 fn test_parse_pixel_value_errors() {
1179 assert!(parse_pixel_value("").is_err());
1180 assert!(parse_pixel_value("10").is_ok()); assert!(parse_pixel_value("10 px").is_ok()); assert!(parse_pixel_value("px").is_err());
1185 assert!(parse_pixel_value("ten-px").is_err());
1186 }
1187}
1188
1189#[cfg(test)]
1190#[allow(
1191 clippy::float_cmp,
1192 clippy::unreadable_literal,
1193 clippy::cast_precision_loss,
1194 clippy::too_many_lines,
1195 clippy::excessive_precision
1196)]
1197mod autotest_generated {
1198 use std::{
1199 collections::hash_map::DefaultHasher,
1200 hash::{Hash, Hasher},
1201 };
1202
1203 use super::*;
1204 use crate::{
1205 codegen::format::FormatAsRustCode,
1206 css::PrintAsCssValue,
1207 props::{
1208 basic::length::{FloatValue, SizeMetric},
1209 formatter::FormatAsCssValue,
1210 },
1211 system::{SafeAreaInsets, SystemMetrics, TitlebarMetrics},
1212 };
1213
1214 const MULT: f32 = 1000.0;
1217
1218 const MAX_SAFE_CONST: isize = isize::MAX / 1000;
1222 const MIN_SAFE_CONST: isize = isize::MIN / 1000;
1223
1224 const ALL_METRICS: [SizeMetric; 12] = [
1225 SizeMetric::Px,
1226 SizeMetric::Pt,
1227 SizeMetric::Em,
1228 SizeMetric::Rem,
1229 SizeMetric::In,
1230 SizeMetric::Cm,
1231 SizeMetric::Mm,
1232 SizeMetric::Percent,
1233 SizeMetric::Vw,
1234 SizeMetric::Vh,
1235 SizeMetric::Vmin,
1236 SizeMetric::Vmax,
1237 ];
1238
1239 const ALL_PROPERTY_CONTEXTS: [PropertyContext; 9] = [
1240 PropertyContext::FontSize,
1241 PropertyContext::Margin,
1242 PropertyContext::Padding,
1243 PropertyContext::Width,
1244 PropertyContext::Height,
1245 PropertyContext::BorderWidth,
1246 PropertyContext::BorderRadius,
1247 PropertyContext::Transform,
1248 PropertyContext::Other,
1249 ];
1250
1251 const ALL_SYSTEM_REFS: [SystemMetricRef; 11] = [
1252 SystemMetricRef::ButtonRadius,
1253 SystemMetricRef::ButtonPaddingHorizontal,
1254 SystemMetricRef::ButtonPaddingVertical,
1255 SystemMetricRef::ButtonBorderWidth,
1256 SystemMetricRef::TitlebarHeight,
1257 SystemMetricRef::TitlebarButtonWidth,
1258 SystemMetricRef::TitlebarPadding,
1259 SystemMetricRef::SafeAreaTop,
1260 SystemMetricRef::SafeAreaBottom,
1261 SystemMetricRef::SafeAreaLeft,
1262 SystemMetricRef::SafeAreaRight,
1263 ];
1264
1265 const EXTREME_F32: [f32; 13] = [
1267 0.0,
1268 -0.0,
1269 1.0,
1270 -1.0,
1271 f32::MIN_POSITIVE,
1272 -f32::MIN_POSITIVE,
1273 1e30,
1274 -1e30,
1275 f32::MAX,
1276 f32::MIN,
1277 f32::INFINITY,
1278 f32::NEG_INFINITY,
1279 f32::NAN,
1280 ];
1281
1282 fn approx(a: f32, b: f32) -> bool {
1283 (a - b).abs() < 0.001
1284 }
1285
1286 fn hash_of<T: Hash>(v: &T) -> u64 {
1287 let mut h = DefaultHasher::new();
1288 v.hash(&mut h);
1289 h.finish()
1290 }
1291
1292 struct CssVal<T>(T);
1295
1296 impl<T: FormatAsCssValue> fmt::Display for CssVal<T> {
1297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1298 self.0.format_as_css_value(f)
1299 }
1300 }
1301
1302 fn as_css_value<T: FormatAsCssValue>(v: T) -> String {
1303 CssVal(v).to_string()
1304 }
1305
1306 fn distinct_context() -> ResolutionContext {
1309 ResolutionContext {
1310 element_font_size: 32.0,
1311 parent_font_size: 8.0,
1312 root_font_size: 4.0,
1313 containing_block_size: PhysicalSize::new(800.0, 600.0),
1314 element_size: Some(PhysicalSize::new(200.0, 100.0)),
1315 viewport_size: PhysicalSize::new(1000.0, 500.0),
1316 }
1317 }
1318
1319 fn populated_metrics() -> SystemMetrics {
1320 SystemMetrics {
1321 corner_radius: OptionPixelValue::Some(PixelValue::px(1.0)),
1322 border_width: OptionPixelValue::Some(PixelValue::px(2.0)),
1323 button_padding_horizontal: OptionPixelValue::Some(PixelValue::px(3.0)),
1324 button_padding_vertical: OptionPixelValue::Some(PixelValue::px(4.0)),
1325 titlebar: TitlebarMetrics {
1326 height: OptionPixelValue::Some(PixelValue::px(5.0)),
1327 button_area_width: OptionPixelValue::Some(PixelValue::px(6.0)),
1328 padding_horizontal: OptionPixelValue::Some(PixelValue::px(7.0)),
1329 safe_area: SafeAreaInsets {
1330 top: OptionPixelValue::Some(PixelValue::px(8.0)),
1331 bottom: OptionPixelValue::Some(PixelValue::px(9.0)),
1332 left: OptionPixelValue::Some(PixelValue::px(10.0)),
1333 right: OptionPixelValue::Some(PixelValue::px(11.0)),
1334 },
1335 ..TitlebarMetrics::default()
1336 },
1337 }
1338 }
1339
1340 #[test]
1343 fn parse_pixel_value_rejects_empty_and_whitespace_only() {
1344 assert_eq!(
1345 parse_pixel_value("").unwrap_err(),
1346 CssPixelValueParseError::EmptyString
1347 );
1348 for ws in [" ", "\t\n", "\r\n\t ", "\n"] {
1349 assert_eq!(
1350 parse_pixel_value(ws).unwrap_err(),
1351 CssPixelValueParseError::EmptyString,
1352 "whitespace-only input {ws:?} must trim down to EmptyString"
1353 );
1354 }
1355 }
1356
1357 #[test]
1358 fn parse_pixel_value_rejects_a_bare_unit_with_no_number() {
1359 for unit in [
1363 "px", "rem", "em", "pt", "in", "mm", "cm", "vmax", "vw", "vh", "%",
1364 ] {
1365 let err = parse_pixel_value(unit).unwrap_err();
1366 assert!(
1367 matches!(err, CssPixelValueParseError::NoValueGiven(input, _) if input == unit),
1368 "bare unit {unit:?} should be NoValueGiven, got {err:?}"
1369 );
1370 }
1371 assert!(matches!(
1373 parse_pixel_value(" px").unwrap_err(),
1374 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px)
1375 ));
1376 }
1377
1378 #[test]
1379 fn parse_pixel_value_vmin_is_shadowed_by_the_in_suffix() {
1380 assert_eq!(
1385 parse_pixel_value("5vmin").unwrap(),
1386 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1387 );
1388 assert!(matches!(
1391 parse_pixel_value("vmin").unwrap_err(),
1392 CssPixelValueParseError::NoValueGiven(..)
1393 ));
1394
1395 assert_eq!(
1397 parse_pixel_value("5vmax").unwrap(),
1398 PixelValue::from_metric(SizeMetric::Vmax, 5.0)
1399 );
1400 assert_eq!(
1401 parse_pixel_value("5vw").unwrap(),
1402 PixelValue::from_metric(SizeMetric::Vw, 5.0)
1403 );
1404 assert_eq!(
1405 parse_pixel_value("5vh").unwrap(),
1406 PixelValue::from_metric(SizeMetric::Vh, 5.0)
1407 );
1408 }
1409
1410 #[test]
1411 fn parse_pixel_value_inner_proves_the_vmin_bug_is_pure_suffix_ordering() {
1412 let in_first: [(&'static str, SizeMetric); 2] =
1415 [("in", SizeMetric::In), ("vmin", SizeMetric::Vmin)];
1416 let vmin_first: [(&'static str, SizeMetric); 2] =
1417 [("vmin", SizeMetric::Vmin), ("in", SizeMetric::In)];
1418
1419 assert!(parse_pixel_value_inner("5vmin", &in_first).is_err());
1420 assert_eq!(
1421 parse_pixel_value_inner("5vmin", &vmin_first).unwrap(),
1422 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1423 );
1424 assert_eq!(
1426 parse_pixel_value_inner("5in", &vmin_first).unwrap(),
1427 PixelValue::inch(5.0)
1428 );
1429 }
1430
1431 #[test]
1432 fn parse_pixel_value_inner_with_an_empty_table_falls_back_to_unitless_px() {
1433 let empty: [(&'static str, SizeMetric); 0] = [];
1434
1435 assert_eq!(
1437 parse_pixel_value_inner("10", &empty).unwrap(),
1438 PixelValue::px(10.0)
1439 );
1440 assert!(matches!(
1441 parse_pixel_value_inner("10px", &empty).unwrap_err(),
1442 CssPixelValueParseError::InvalidPixelValue("10px")
1443 ));
1444 assert_eq!(
1445 parse_pixel_value_inner("", &empty).unwrap_err(),
1446 CssPixelValueParseError::EmptyString
1447 );
1448 }
1449
1450 #[test]
1451 fn parse_pixel_value_accepts_every_unit_it_advertises() {
1452 let cases: [(&str, PixelValue); 12] = [
1454 ("10px", PixelValue::px(10.0)),
1455 ("1.5em", PixelValue::em(1.5)),
1456 ("2rem", PixelValue::rem(2.0)),
1457 ("-20pt", PixelValue::pt(-20.0)),
1458 ("50%", PixelValue::percent(50.0)),
1459 ("1in", PixelValue::inch(1.0)),
1460 ("2.54cm", PixelValue::cm(2.54)),
1461 ("10mm", PixelValue::mm(10.0)),
1462 ("+7px", PixelValue::px(7.0)),
1463 (".5px", PixelValue::px(0.5)),
1464 ("5.px", PixelValue::px(5.0)),
1465 ("1e2px", PixelValue::px(100.0)),
1466 ];
1467 for (input, expected) in cases {
1468 assert_eq!(
1469 parse_pixel_value(input).unwrap(),
1470 expected,
1471 "parsing {input:?}"
1472 );
1473 }
1474
1475 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1477 assert_eq!(parse_pixel_value("10 px").unwrap(), PixelValue::px(10.0));
1478 assert_eq!(parse_pixel_value("\t10px\n").unwrap(), PixelValue::px(10.0));
1479 }
1480
1481 #[test]
1482 fn parse_pixel_value_boundary_numbers_saturate_instead_of_overflowing() {
1483 assert_eq!(parse_pixel_value("-0").unwrap(), PixelValue::px(0.0));
1485 assert_eq!(parse_pixel_value("-0").unwrap(), PixelValue::zero());
1486
1487 assert_eq!(parse_pixel_value("0.0004px").unwrap(), PixelValue::px(0.0));
1489 assert_eq!(parse_pixel_value("-0.0009px").unwrap(), PixelValue::px(0.0));
1490 assert_eq!(parse_pixel_value("1e-40px").unwrap(), PixelValue::px(0.0));
1491
1492 for huge in ["9223372036854775807", "1e40px", "3.5e38"] {
1494 let v = parse_pixel_value(huge).unwrap();
1495 assert!(
1496 v.number.get().is_finite(),
1497 "{huge:?} leaked a non-finite value: {}",
1498 v.number.get()
1499 );
1500 assert!(v.number.get() > 0.0, "{huge:?} lost its sign");
1501 }
1502 let neg = parse_pixel_value("-1e40px").unwrap();
1503 assert!(neg.number.get().is_finite() && neg.number.get() < 0.0);
1504 }
1505
1506 #[test]
1507 fn parse_pixel_value_inherits_rusts_float_keywords() {
1508 assert_eq!(parse_pixel_value("NaN").unwrap(), PixelValue::zero());
1513
1514 let inf = parse_pixel_value("infinity").unwrap();
1515 assert_eq!(inf, PixelValue::px(f32::INFINITY));
1516 assert!(inf.number.get().is_finite() && inf.number.get() > 0.0);
1517
1518 let neg_inf = parse_pixel_value("-infinity").unwrap();
1519 assert_eq!(neg_inf, PixelValue::px(f32::NEG_INFINITY));
1520 assert!(neg_inf.number.get().is_finite() && neg_inf.number.get() < 0.0);
1521
1522 let inf_short = parse_pixel_value("inf").unwrap();
1526 assert_eq!(inf_short, PixelValue::px(f32::INFINITY));
1527 assert!(inf_short.number.get().is_finite() && inf_short.number.get() > 0.0);
1528 }
1529
1530 #[test]
1531 fn parse_pixel_value_is_case_sensitive_about_units() {
1532 for input in ["10PX", "10Px", "10EM", "10REM", "10VMAX"] {
1536 let err = parse_pixel_value(input).unwrap_err();
1537 assert!(
1538 matches!(err, CssPixelValueParseError::InvalidPixelValue(s) if s == input),
1539 "uppercase unit {input:?} should be InvalidPixelValue, got {err:?}"
1540 );
1541 }
1542 }
1543
1544 #[test]
1545 fn parse_pixel_value_rejects_garbage_and_trailing_junk() {
1546 for input in [
1547 "ten-px",
1548 "px10",
1549 "10px;garbage",
1550 "10;",
1551 "--",
1552 "1%%",
1553 "10 20px",
1554 "#",
1555 "10px 10px",
1556 "e",
1557 "0x10px",
1558 ] {
1559 assert!(
1560 parse_pixel_value(input).is_err(),
1561 "{input:?} must not parse, got {:?}",
1562 parse_pixel_value(input)
1563 );
1564 }
1565 }
1566
1567 #[test]
1568 fn parse_pixel_value_survives_unicode() {
1569 for input in [
1571 "\u{1F600}", "10px\u{1F600}", "10px\u{0301}", "\u{200B}10px", "\u{0661}\u{0660}px", "10\u{0440}\u{0445}", "\u{202E}10px", ] {
1579 let got = parse_pixel_value(input);
1580 assert!(got.is_err(), "{input:?} must be rejected, got {got:?}");
1581 }
1582
1583 assert!(matches!(
1586 parse_pixel_value("\u{200B}10px").unwrap_err(),
1587 CssPixelValueParseError::ValueParseErr(_, "\u{200B}10")
1588 ));
1589 }
1590
1591 #[test]
1592 fn parse_pixel_value_handles_extremely_long_and_deeply_nested_input() {
1593 let long_number = format!("{}px", "9".repeat(100_000));
1595 let parsed = parse_pixel_value(&long_number).unwrap();
1596 assert!(parsed.number.get().is_finite());
1597 assert_eq!(parsed.metric, SizeMetric::Px);
1598
1599 let long_junk = "x".repeat(100_000);
1601 assert!(parse_pixel_value(&long_junk).is_err());
1602
1603 let nested = "(".repeat(10_000);
1606 assert!(matches!(
1607 parse_pixel_value(&nested).unwrap_err(),
1608 CssPixelValueParseError::InvalidPixelValue(_)
1609 ));
1610 }
1611
1612 #[test]
1613 fn parse_pixel_value_no_percent_rejects_percentages_but_keeps_the_rest() {
1614 assert_eq!(
1615 parse_pixel_value_no_percent("10px").unwrap().inner,
1616 PixelValue::px(10.0)
1617 );
1618 assert_eq!(
1619 parse_pixel_value_no_percent("5vmax").unwrap().inner,
1620 PixelValue::from_metric(SizeMetric::Vmax, 5.0)
1621 );
1622
1623 assert!(matches!(
1625 parse_pixel_value_no_percent("50%").unwrap_err(),
1626 CssPixelValueParseError::InvalidPixelValue("50%")
1627 ));
1628 assert!(matches!(
1629 parse_pixel_value_no_percent("%").unwrap_err(),
1630 CssPixelValueParseError::InvalidPixelValue("%")
1631 ));
1632
1633 assert_eq!(
1634 parse_pixel_value_no_percent("").unwrap_err(),
1635 CssPixelValueParseError::EmptyString
1636 );
1637 assert_eq!(
1638 parse_pixel_value_no_percent(" ").unwrap_err(),
1639 CssPixelValueParseError::EmptyString
1640 );
1641 assert!(parse_pixel_value_no_percent("\u{1F600}").is_err());
1642 assert_eq!(
1644 parse_pixel_value_no_percent("5vmin").unwrap().inner,
1645 PixelValue::from_metric(SizeMetric::Vmin, 5.0)
1646 );
1647 }
1648
1649 #[test]
1650 fn parse_pixel_value_with_auto_keywords_and_fallthrough() {
1651 assert_eq!(
1652 parse_pixel_value_with_auto("auto").unwrap(),
1653 PixelValueWithAuto::Auto
1654 );
1655 assert_eq!(
1656 parse_pixel_value_with_auto(" initial ").unwrap(),
1657 PixelValueWithAuto::Initial
1658 );
1659 assert_eq!(
1660 parse_pixel_value_with_auto("\tinherit\n").unwrap(),
1661 PixelValueWithAuto::Inherit
1662 );
1663 assert_eq!(
1664 parse_pixel_value_with_auto("none").unwrap(),
1665 PixelValueWithAuto::None
1666 );
1667 assert_eq!(
1668 parse_pixel_value_with_auto("10px").unwrap(),
1669 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1670 );
1671
1672 for input in ["AUTO", "Auto", "INHERIT", "None"] {
1674 assert!(
1675 parse_pixel_value_with_auto(input).is_err(),
1676 "{input:?} unexpectedly matched a keyword"
1677 );
1678 }
1679
1680 assert_eq!(
1682 parse_pixel_value_with_auto("").unwrap_err(),
1683 CssPixelValueParseError::EmptyString
1684 );
1685 assert_eq!(
1686 parse_pixel_value_with_auto(" \t ").unwrap_err(),
1687 CssPixelValueParseError::EmptyString
1688 );
1689 assert!(parse_pixel_value_with_auto("auto;garbage").is_err());
1690 assert!(parse_pixel_value_with_auto("\u{1F600}").is_err());
1691 assert!(parse_pixel_value_with_auto(&"(".repeat(10_000)).is_err());
1692 }
1693
1694 #[cfg(feature = "parser")]
1695 #[test]
1696 fn parse_pixel_value_or_system_accepts_every_system_ref() {
1697 for r in ALL_SYSTEM_REFS {
1698 let css = r.as_css_str(); assert_eq!(
1700 parse_pixel_value_or_system(css).unwrap(),
1701 PixelValueOrSystem::System(r),
1702 "round-tripping {css:?}"
1703 );
1704 assert_eq!(
1706 parse_pixel_value_or_system(&format!(" {css} ")).unwrap(),
1707 PixelValueOrSystem::System(r)
1708 );
1709 }
1710
1711 assert_eq!(
1713 parse_pixel_value_or_system("10px").unwrap(),
1714 PixelValueOrSystem::Value(PixelValue::px(10.0))
1715 );
1716 assert_eq!(
1717 parse_pixel_value_or_system("1.5em").unwrap(),
1718 PixelValueOrSystem::Value(PixelValue::em(1.5))
1719 );
1720 }
1721
1722 #[cfg(feature = "parser")]
1723 #[test]
1724 fn parse_pixel_value_or_system_rejects_malformed_system_refs() {
1725 assert!(matches!(
1730 parse_pixel_value_or_system("system:button-padding").unwrap_err(),
1731 CssPixelValueParseError::InvalidPixelValue("system:button-padding")
1732 ));
1733
1734 for input in [
1735 "system:", "system:unknown", "system: button-radius", "system:BUTTON-RADIUS", "system:button-radius;x", "system:\u{1F600}", ] {
1742 let err = parse_pixel_value_or_system(input).unwrap_err();
1743 assert!(
1744 matches!(err, CssPixelValueParseError::InvalidPixelValue(s) if s == input),
1745 "{input:?} should be InvalidPixelValue, got {err:?}"
1746 );
1747 }
1748
1749 assert!(matches!(
1752 parse_pixel_value_or_system("SYSTEM:button-radius").unwrap_err(),
1753 CssPixelValueParseError::InvalidPixelValue("SYSTEM:button-radius")
1754 ));
1755 assert_eq!(
1756 parse_pixel_value_or_system("").unwrap_err(),
1757 CssPixelValueParseError::EmptyString
1758 );
1759 assert_eq!(
1760 parse_pixel_value_or_system(" ").unwrap_err(),
1761 CssPixelValueParseError::EmptyString
1762 );
1763
1764 let long = format!("system:{}", "a".repeat(100_000));
1766 assert!(parse_pixel_value_or_system(&long).is_err());
1767 }
1768
1769 #[test]
1772 fn parse_errors_survive_the_owned_round_trip() {
1773 let float_err = "x".parse::<f32>().unwrap_err();
1774 let errors = [
1775 CssPixelValueParseError::EmptyString,
1776 CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px),
1777 CssPixelValueParseError::ValueParseErr(float_err, "abc"),
1778 CssPixelValueParseError::InvalidPixelValue("ten-px"),
1779 ];
1780
1781 for err in errors {
1782 let owned = err.to_contained();
1783 let shared = owned.to_shared();
1784 assert_eq!(shared, err, "to_contained -> to_shared must be lossless");
1785 assert!(!err.to_string().is_empty());
1787 assert_eq!(shared.to_string(), err.to_string());
1788 }
1789 }
1790
1791 #[test]
1792 fn parse_errors_round_trip_from_real_parse_failures() {
1793 for input in ["", "px", "\u{200B}10px", "ten-px", "%"] {
1796 let err = parse_pixel_value(input).unwrap_err();
1797 let owned = err.to_contained();
1798 assert_eq!(owned.to_shared(), err, "round-trip failed for {input:?}");
1799 }
1800 }
1801
1802 #[test]
1805 fn float_constructors_never_leak_a_non_finite_value() {
1806 type Ctor = (fn(f32) -> PixelValue, SizeMetric);
1809 let ctors: [Ctor; 8] = [
1810 (PixelValue::px, SizeMetric::Px),
1811 (PixelValue::em, SizeMetric::Em),
1812 (PixelValue::pt, SizeMetric::Pt),
1813 (PixelValue::inch, SizeMetric::In),
1814 (PixelValue::cm, SizeMetric::Cm),
1815 (PixelValue::mm, SizeMetric::Mm),
1816 (PixelValue::percent, SizeMetric::Percent),
1817 (PixelValue::rem, SizeMetric::Rem),
1818 ];
1819
1820 for (ctor, metric) in ctors {
1821 for v in EXTREME_F32 {
1822 let px = ctor(v);
1823 assert_eq!(px.metric, metric, "constructor lost its metric for {v}");
1824 assert!(
1825 px.number.get().is_finite(),
1826 "{metric:?} constructor leaked a non-finite value for input {v}"
1827 );
1828 }
1829 assert_eq!(ctor(f32::NAN).number.get(), 0.0, "NaN must sanitize to 0");
1830 assert!(ctor(f32::INFINITY).number.get() > 0.0);
1831 assert!(ctor(f32::NEG_INFINITY).number.get() < 0.0);
1832 }
1833
1834 for metric in ALL_METRICS {
1836 for v in EXTREME_F32 {
1837 let px = PixelValue::from_metric(metric, v);
1838 assert_eq!(px.metric, metric);
1839 assert!(px.number.get().is_finite());
1840 }
1841 assert_eq!(
1842 PixelValue::from_metric(metric, 12.0),
1843 PixelValue {
1844 metric,
1845 number: FloatValue::new(12.0)
1846 }
1847 );
1848 }
1849 }
1850
1851 #[test]
1852 fn float_constructors_quantize_to_one_thousandth() {
1853 assert_eq!(PixelValue::px(0.0004).number.get(), 0.0);
1855 assert_eq!(PixelValue::px(-0.0009).number.get(), 0.0);
1856 assert_eq!(PixelValue::px(1.0005).number.get(), 1.0);
1858
1859 assert_eq!(PixelValue::px(-0.0), PixelValue::px(0.0));
1862 assert_eq!(
1863 hash_of(&PixelValue::px(-0.0)),
1864 hash_of(&PixelValue::px(0.0))
1865 );
1866 assert_eq!(PixelValue::px(f32::NAN), PixelValue::px(f32::NAN));
1868 assert_eq!(PixelValue::px(f32::NAN), PixelValue::zero());
1869 }
1870
1871 #[test]
1872 fn const_constructors_agree_with_their_float_twins() {
1873 assert_eq!(PixelValue::const_px(5), PixelValue::px(5.0));
1874 assert_eq!(PixelValue::const_em(5), PixelValue::em(5.0));
1875 assert_eq!(PixelValue::const_pt(5), PixelValue::pt(5.0));
1876 assert_eq!(PixelValue::const_percent(5), PixelValue::percent(5.0));
1877 assert_eq!(PixelValue::const_in(5), PixelValue::inch(5.0));
1878 assert_eq!(PixelValue::const_cm(5), PixelValue::cm(5.0));
1879 assert_eq!(PixelValue::const_mm(5), PixelValue::mm(5.0));
1880
1881 assert_eq!(PixelValue::const_px(0), PixelValue::zero());
1882 assert_eq!(PixelValue::const_px(-7), PixelValue::px(-7.0));
1883
1884 for metric in ALL_METRICS {
1885 assert_eq!(
1886 PixelValue::const_from_metric(metric, 7),
1887 PixelValue::from_metric(metric, 7.0),
1888 "const_from_metric disagrees with from_metric for {metric:?}"
1889 );
1890 assert_eq!(
1891 PixelValue::const_from_metric(metric, -7),
1892 PixelValue::from_metric(metric, -7.0)
1893 );
1894 }
1895 }
1896
1897 #[test]
1898 fn const_constructors_are_usable_up_to_the_documented_isize_bound() {
1899 for v in [0, 1, -1, MAX_SAFE_CONST, MIN_SAFE_CONST] {
1904 let px = PixelValue::const_px(v);
1905 assert!(
1906 px.number.get().is_finite(),
1907 "const_px({v}) leaked a non-finite value"
1908 );
1909 }
1910 assert!(PixelValue::const_px(MAX_SAFE_CONST).number.get() > 0.0);
1911 assert!(PixelValue::const_px(MIN_SAFE_CONST).number.get() < 0.0);
1912 assert_eq!(
1913 PixelValue::const_px(MAX_SAFE_CONST).number.number(),
1914 MAX_SAFE_CONST * 1000
1915 );
1916 }
1917
1918 #[test]
1919 fn const_fractional_constructors_match_their_documented_examples() {
1920 assert!(approx(PixelValue::const_em_fractional(1, 5).number.get(), 1.5));
1922 assert!(approx(
1923 PixelValue::const_em_fractional(0, 83).number.get(),
1924 0.83
1925 ));
1926 assert!(approx(
1927 PixelValue::const_em_fractional(1, 17).number.get(),
1928 1.17
1929 ));
1930 assert_eq!(PixelValue::const_em_fractional(1, 5).metric, SizeMetric::Em);
1931 assert_eq!(PixelValue::const_pt_fractional(1, 5).metric, SizeMetric::Pt);
1932 assert!(approx(PixelValue::const_pt_fractional(2, 25).number.get(), 2.25));
1933
1934 assert_eq!(
1937 PixelValue::const_from_metric_fractional(SizeMetric::Px, 0, 0),
1938 PixelValue::zero()
1939 );
1940 assert!(approx(
1941 PixelValue::const_from_metric_fractional(SizeMetric::Px, -1, 5)
1942 .number
1943 .get(),
1944 -1.5
1945 ));
1946
1947 assert!(approx(
1950 PixelValue::const_from_metric_fractional(SizeMetric::Px, 1, 5234)
1951 .number
1952 .get(),
1953 1.523
1954 ));
1955
1956 let extreme =
1959 PixelValue::const_from_metric_fractional(SizeMetric::Px, 0, isize::MAX);
1960 assert!(extreme.number.get().is_finite());
1961 }
1962
1963 #[test]
1964 fn scale_for_dpi_is_defined_for_every_scale_factor() {
1965 let mut doubled = PixelValue::px(10.0);
1966 doubled.scale_for_dpi(2.0);
1967 assert_eq!(doubled, PixelValue::px(20.0));
1968
1969 doubled.scale_for_dpi(2.0);
1972 assert_eq!(doubled, PixelValue::px(40.0));
1973
1974 let mut zeroed = PixelValue::em(3.0);
1975 zeroed.scale_for_dpi(0.0);
1976 assert_eq!(zeroed, PixelValue::em(0.0));
1977 assert_eq!(zeroed.metric, SizeMetric::Em, "metric must be preserved");
1978
1979 let mut flipped = PixelValue::px(10.0);
1980 flipped.scale_for_dpi(-1.5);
1981 assert_eq!(flipped, PixelValue::px(-15.0));
1982
1983 let mut nan_scaled = PixelValue::px(10.0);
1985 nan_scaled.scale_for_dpi(f32::NAN);
1986 assert_eq!(nan_scaled.number.get(), 0.0);
1987
1988 let mut inf_scaled = PixelValue::px(10.0);
1989 inf_scaled.scale_for_dpi(f32::INFINITY);
1990 assert!(inf_scaled.number.get().is_finite() && inf_scaled.number.get() > 0.0);
1991
1992 let mut max_scaled = PixelValue::px(f32::MAX);
1993 max_scaled.scale_for_dpi(f32::MAX);
1994 assert!(max_scaled.number.get().is_finite());
1995
1996 let mut wrapped = PixelValueNoPercent::from(PixelValue::px(10.0));
1998 wrapped.scale_for_dpi(2.5);
1999 assert_eq!(wrapped.inner, PixelValue::px(25.0));
2000
2001 let mut wrapped_nan = PixelValueNoPercent::from(PixelValue::px(10.0));
2002 wrapped_nan.scale_for_dpi(f32::NAN);
2003 assert_eq!(wrapped_nan.inner.number.get(), 0.0);
2004 }
2005
2006 #[test]
2007 fn interpolate_within_one_metric_keeps_that_metric() {
2008 let a = PixelValue::em(1.0);
2009 let b = PixelValue::em(3.0);
2010
2011 assert_eq!(a.interpolate(&b, 0.0), a);
2012 assert_eq!(a.interpolate(&b, 1.0), b);
2013 assert_eq!(a.interpolate(&b, 0.5), PixelValue::em(2.0));
2014
2015 assert_eq!(a.interpolate(&b, 2.0), PixelValue::em(5.0));
2017 assert_eq!(a.interpolate(&b, -1.0), PixelValue::em(-1.0));
2018
2019 let p = PixelValue::percent(0.0).interpolate(&PixelValue::percent(100.0), 0.5);
2021 assert_eq!(p, PixelValue::percent(50.0));
2022 assert_eq!(p.metric, SizeMetric::Percent);
2023
2024 let nan_t = a.interpolate(&b, f32::NAN);
2026 assert_eq!(nan_t.number.get(), 0.0);
2027 assert_eq!(nan_t.metric, SizeMetric::Em);
2028 assert!(a.interpolate(&b, f32::INFINITY).number.get().is_finite());
2029 }
2030
2031 #[test]
2032 fn interpolate_across_metrics_falls_back_to_px() {
2033 let from_px = PixelValue::px(0.0);
2035 let to_em = PixelValue::em(1.0); let mid = from_px.interpolate(&to_em, 0.5);
2037 assert_eq!(mid.metric, SizeMetric::Px);
2038 assert!(approx(mid.number.get(), DEFAULT_FONT_SIZE / 2.0));
2039
2040 assert!(approx(
2041 PixelValue::px(0.0)
2042 .interpolate(&PixelValue::pt(72.0), 1.0)
2043 .number
2044 .get(),
2045 96.0
2046 ));
2047
2048 for metric in [
2052 SizeMetric::Percent,
2053 SizeMetric::Vw,
2054 SizeMetric::Vh,
2055 SizeMetric::Vmin,
2056 SizeMetric::Vmax,
2057 ] {
2058 let other = PixelValue::from_metric(metric, 50.0);
2059 let done = PixelValue::px(100.0).interpolate(&other, 1.0);
2060 assert_eq!(
2061 done,
2062 PixelValue::px(0.0),
2063 "{metric:?} should collapse to 0px on the cross-metric path"
2064 );
2065 }
2066
2067 let nan_t = PixelValue::px(0.0).interpolate(&PixelValue::em(1.0), f32::NAN);
2068 assert_eq!(nan_t.number.get(), 0.0);
2069 }
2070
2071 #[test]
2072 fn to_pixels_internal_converts_every_absolute_and_relative_unit() {
2073 assert_eq!(PixelValue::px(10.0).to_pixels_internal(0.0, 16.0, 16.0), 10.0);
2074 assert!(approx(
2075 PixelValue::pt(72.0).to_pixels_internal(0.0, 16.0, 16.0),
2076 96.0
2077 ));
2078 assert!(approx(
2079 PixelValue::inch(1.0).to_pixels_internal(0.0, 16.0, 16.0),
2080 96.0
2081 ));
2082 assert!(approx(
2083 PixelValue::cm(2.54).to_pixels_internal(0.0, 16.0, 16.0),
2084 96.0
2085 ));
2086 assert!(approx(
2087 PixelValue::mm(25.4).to_pixels_internal(0.0, 16.0, 16.0),
2088 96.0
2089 ));
2090 assert_eq!(PT_TO_PX, 96.0 / 72.0);
2091
2092 assert_eq!(PixelValue::em(2.0).to_pixels_internal(0.0, 10.0, 100.0), 20.0);
2095 assert_eq!(
2096 PixelValue::rem(2.0).to_pixels_internal(0.0, 10.0, 100.0),
2097 200.0
2098 );
2099
2100 assert_eq!(
2103 PixelValue::percent(50.0).to_pixels_internal(800.0, 16.0, 16.0),
2104 400.0
2105 );
2106 assert_eq!(
2107 PixelValue::percent(0.0).to_pixels_internal(800.0, 16.0, 16.0),
2108 0.0
2109 );
2110 assert_eq!(
2111 PixelValue::percent(-50.0).to_pixels_internal(800.0, 16.0, 16.0),
2112 -400.0
2113 );
2114
2115 for metric in [
2118 SizeMetric::Vw,
2119 SizeMetric::Vh,
2120 SizeMetric::Vmin,
2121 SizeMetric::Vmax,
2122 ] {
2123 assert_eq!(
2124 PixelValue::from_metric(metric, 50.0).to_pixels_internal(800.0, 16.0, 16.0),
2125 0.0,
2126 "{metric:?} must resolve to 0 on the legacy path"
2127 );
2128 }
2129 }
2130
2131 #[test]
2132 fn to_pixels_internal_non_finite_resolves_are_defined_not_panics() {
2133 assert!(PixelValue::em(1.0)
2136 .to_pixels_internal(0.0, f32::NAN, 0.0)
2137 .is_nan());
2138 assert!(PixelValue::rem(1.0)
2139 .to_pixels_internal(0.0, 0.0, f32::INFINITY)
2140 .is_infinite());
2141 assert!(PixelValue::percent(50.0)
2142 .to_pixels_internal(f32::INFINITY, 16.0, 16.0)
2143 .is_infinite());
2144 assert!(PixelValue::percent(50.0)
2145 .to_pixels_internal(f32::NAN, 16.0, 16.0)
2146 .is_nan());
2147 assert!(PixelValue::percent(0.0)
2149 .to_pixels_internal(f32::INFINITY, 16.0, 16.0)
2150 .is_nan());
2151
2152 assert!(PixelValue::em(f32::MAX)
2154 .to_pixels_internal(0.0, f32::MAX, 0.0)
2155 .is_infinite());
2156
2157 for v in EXTREME_F32 {
2159 assert!(PixelValue::px(v)
2160 .to_pixels_internal(f32::NAN, f32::NAN, f32::NAN)
2161 .is_finite());
2162 }
2163 }
2164
2165 #[test]
2166 fn pixel_value_no_percent_to_pixels_internal_zeroes_out_percentages() {
2167 assert_eq!(
2168 PixelValueNoPercent::from(PixelValue::px(10.0)).to_pixels_internal(16.0, 16.0),
2169 10.0
2170 );
2171 assert_eq!(
2172 PixelValueNoPercent::from(PixelValue::em(2.0)).to_pixels_internal(10.0, 100.0),
2173 20.0
2174 );
2175 assert_eq!(
2176 PixelValueNoPercent::from(PixelValue::rem(2.0)).to_pixels_internal(10.0, 100.0),
2177 200.0
2178 );
2179
2180 assert_eq!(
2183 PixelValueNoPercent::from(PixelValue::percent(50.0)).to_pixels_internal(16.0, 16.0),
2184 0.0
2185 );
2186 assert_eq!(PixelValueNoPercent::zero().to_pixels_internal(16.0, 16.0), 0.0);
2187 assert_eq!(PixelValueNoPercent::zero().inner, PixelValue::zero());
2188 assert_eq!(PixelValueNoPercent::default().inner, PixelValue::zero());
2189 }
2190
2191 #[test]
2194 fn to_percent_is_some_only_for_the_percent_metric() {
2195 for metric in ALL_METRICS {
2196 let v = PixelValue::from_metric(metric, 50.0);
2197 if metric == SizeMetric::Percent {
2198 assert_eq!(v.to_percent().unwrap().get(), 0.5, "50% must normalize to 0.5");
2199 } else {
2200 assert!(
2201 v.to_percent().is_none(),
2202 "{metric:?} must not masquerade as a percentage"
2203 );
2204 }
2205 }
2206
2207 assert_eq!(
2210 PixelValue::percent(50.0)
2211 .to_percent()
2212 .unwrap()
2213 .resolve(640.0),
2214 320.0
2215 );
2216 assert_eq!(
2217 PixelValue::percent(-50.0).to_percent().unwrap().get(),
2218 -0.5
2219 );
2220 assert_eq!(PixelValue::percent(0.0).to_percent().unwrap().get(), 0.0);
2221 assert!(PixelValue::percent(f32::MAX)
2223 .to_percent()
2224 .unwrap()
2225 .get()
2226 .is_finite());
2227 assert_eq!(
2228 PixelValue::percent(f32::NAN).to_percent().unwrap().get(),
2229 0.0
2230 );
2231 }
2232
2233 #[test]
2234 fn normalized_percentage_new_and_from_unnormalized_disagree_by_100x() {
2235 assert_eq!(NormalizedPercentage::new(0.5).get(), 0.5);
2238 assert_eq!(NormalizedPercentage::from_unnormalized(50.0).get(), 0.5);
2239 assert_eq!(NormalizedPercentage::from_unnormalized(0.0).get(), 0.0);
2240 assert_eq!(NormalizedPercentage::from_unnormalized(100.0).get(), 1.0);
2241 assert_eq!(NormalizedPercentage::from_unnormalized(-25.0).get(), -0.25);
2242
2243 assert_eq!(NormalizedPercentage::new(0.5).resolve(640.0), 320.0);
2244 assert_eq!(NormalizedPercentage::new(0.0).resolve(640.0), 0.0);
2245 assert_eq!(NormalizedPercentage::new(1.0).resolve(f32::MAX), f32::MAX);
2246 assert_eq!(NormalizedPercentage::new(-1.0).resolve(100.0), -100.0);
2247
2248 assert!(NormalizedPercentage::new(f32::NAN).get().is_nan());
2251 assert!(NormalizedPercentage::new(f32::NAN).resolve(100.0).is_nan());
2252 assert!(NormalizedPercentage::from_unnormalized(f32::INFINITY)
2253 .get()
2254 .is_infinite());
2255 assert!(NormalizedPercentage::new(1.0)
2256 .resolve(f32::INFINITY)
2257 .is_infinite());
2258 assert!(NormalizedPercentage::new(0.0)
2260 .resolve(f32::INFINITY)
2261 .is_nan());
2262
2263 assert_eq!(NormalizedPercentage::new(0.5).to_string(), "50%");
2265 assert_eq!(NormalizedPercentage::new(0.0).to_string(), "0%");
2266 assert!(!NormalizedPercentage::new(f32::NAN).to_string().is_empty());
2267 assert!(!NormalizedPercentage::new(f32::INFINITY)
2268 .to_string()
2269 .is_empty());
2270 }
2271
2272 #[test]
2273 fn resolve_with_context_reads_the_right_reference_for_each_property() {
2274 let ctx = distinct_context(); assert_eq!(
2280 PixelValue::em(2.0).resolve_with_context(&ctx, PropertyContext::Margin),
2281 64.0
2282 );
2283 assert_eq!(
2284 PixelValue::em(2.0).resolve_with_context(&ctx, PropertyContext::FontSize),
2285 16.0
2286 );
2287
2288 for pc in ALL_PROPERTY_CONTEXTS {
2290 assert_eq!(
2291 PixelValue::rem(2.0).resolve_with_context(&ctx, pc),
2292 8.0,
2293 "rem must ignore the property context ({pc:?})"
2294 );
2295 }
2296
2297 let pct = PixelValue::percent(50.0);
2299 assert_eq!(
2300 pct.resolve_with_context(&ctx, PropertyContext::Width),
2301 400.0,
2302 "width % -> containing block WIDTH"
2303 );
2304 assert_eq!(
2305 pct.resolve_with_context(&ctx, PropertyContext::Height),
2306 300.0,
2307 "height % -> containing block HEIGHT"
2308 );
2309 assert_eq!(
2310 pct.resolve_with_context(&ctx, PropertyContext::Margin),
2311 400.0,
2312 "margin % -> containing block WIDTH, even vertically (CSS 2.1 §8.3)"
2313 );
2314 assert_eq!(
2315 pct.resolve_with_context(&ctx, PropertyContext::Padding),
2316 400.0,
2317 "padding % -> containing block WIDTH, even vertically (CSS 2.1 §8.4)"
2318 );
2319 assert_eq!(
2320 pct.resolve_with_context(&ctx, PropertyContext::Other),
2321 400.0
2322 );
2323 assert_eq!(
2324 pct.resolve_with_context(&ctx, PropertyContext::FontSize),
2325 4.0,
2326 "font-size % -> PARENT font size"
2327 );
2328 assert_eq!(
2329 pct.resolve_with_context(&ctx, PropertyContext::BorderRadius),
2330 100.0,
2331 "border-radius % -> the element's own box"
2332 );
2333 assert_eq!(
2334 pct.resolve_with_context(&ctx, PropertyContext::Transform),
2335 100.0
2336 );
2337 assert_eq!(
2338 pct.resolve_with_context(&ctx, PropertyContext::BorderWidth),
2339 0.0,
2340 "% is invalid on border-width (CSS Backgrounds 3 §4.1) -> 0"
2341 );
2342 }
2343
2344 #[test]
2345 fn resolve_with_context_percent_without_an_element_size_is_zero() {
2346 let ctx = ResolutionContext {
2349 element_size: None,
2350 ..distinct_context()
2351 };
2352 assert_eq!(
2353 PixelValue::percent(50.0)
2354 .resolve_with_context(&ctx, PropertyContext::BorderRadius),
2355 0.0
2356 );
2357 assert_eq!(
2358 PixelValue::percent(50.0).resolve_with_context(&ctx, PropertyContext::Transform),
2359 0.0
2360 );
2361 }
2362
2363 #[test]
2364 fn resolve_with_context_absolute_units_ignore_the_context_entirely() {
2365 let sane = distinct_context();
2366 let poisoned = ResolutionContext {
2367 element_font_size: f32::NAN,
2368 parent_font_size: f32::INFINITY,
2369 root_font_size: f32::NEG_INFINITY,
2370 containing_block_size: PhysicalSize::new(f32::NAN, f32::NAN),
2371 element_size: Some(PhysicalSize::new(f32::INFINITY, f32::NAN)),
2372 viewport_size: PhysicalSize::new(f32::NAN, f32::INFINITY),
2373 };
2374
2375 let absolutes = [
2376 PixelValue::px(10.0),
2377 PixelValue::pt(10.0),
2378 PixelValue::inch(10.0),
2379 PixelValue::cm(10.0),
2380 PixelValue::mm(10.0),
2381 ];
2382 for v in absolutes {
2383 for pc in ALL_PROPERTY_CONTEXTS {
2384 let a = v.resolve_with_context(&sane, pc);
2385 let b = v.resolve_with_context(&poisoned, pc);
2386 assert_eq!(a, b, "{:?} must not read the context ({pc:?})", v.metric);
2387 assert!(a.is_finite());
2388 }
2389 }
2390
2391 assert_eq!(
2393 PixelValue::px(10.0).resolve_with_context(&sane, PropertyContext::Width),
2394 10.0
2395 );
2396 assert!(approx(
2397 PixelValue::inch(1.0).resolve_with_context(&sane, PropertyContext::Width),
2398 96.0
2399 ));
2400 assert!(approx(
2401 PixelValue::pt(72.0).resolve_with_context(&sane, PropertyContext::Width),
2402 96.0
2403 ));
2404 assert!(approx(
2405 PixelValue::cm(2.54).resolve_with_context(&sane, PropertyContext::Width),
2406 96.0
2407 ));
2408 assert!(approx(
2409 PixelValue::mm(25.4).resolve_with_context(&sane, PropertyContext::Width),
2410 96.0
2411 ));
2412 }
2413
2414 #[test]
2415 fn resolve_with_context_viewport_units_use_the_viewport() {
2416 let ctx = distinct_context(); assert_eq!(
2419 PixelValue::from_metric(SizeMetric::Vw, 10.0)
2420 .resolve_with_context(&ctx, PropertyContext::Width),
2421 100.0
2422 );
2423 assert_eq!(
2424 PixelValue::from_metric(SizeMetric::Vh, 10.0)
2425 .resolve_with_context(&ctx, PropertyContext::Width),
2426 50.0
2427 );
2428 assert_eq!(
2429 PixelValue::from_metric(SizeMetric::Vmin, 10.0)
2430 .resolve_with_context(&ctx, PropertyContext::Width),
2431 50.0,
2432 "vmin must take the SMALLER viewport dimension"
2433 );
2434 assert_eq!(
2435 PixelValue::from_metric(SizeMetric::Vmax, 10.0)
2436 .resolve_with_context(&ctx, PropertyContext::Width),
2437 100.0,
2438 "vmax must take the LARGER viewport dimension"
2439 );
2440
2441 let zero_vp = ResolutionContext::default_const();
2444 for metric in [
2445 SizeMetric::Vw,
2446 SizeMetric::Vh,
2447 SizeMetric::Vmin,
2448 SizeMetric::Vmax,
2449 ] {
2450 assert_eq!(
2451 PixelValue::from_metric(metric, 100.0)
2452 .resolve_with_context(&zero_vp, PropertyContext::Width),
2453 0.0,
2454 "{metric:?} against a 0x0 viewport must be 0"
2455 );
2456 }
2457
2458 let nan_vp = ResolutionContext {
2460 viewport_size: PhysicalSize::new(f32::NAN, f32::NAN),
2461 ..distinct_context()
2462 };
2463 assert!(PixelValue::from_metric(SizeMetric::Vw, 10.0)
2464 .resolve_with_context(&nan_vp, PropertyContext::Width)
2465 .is_nan());
2466 let half_nan_vp = ResolutionContext {
2469 viewport_size: PhysicalSize::new(f32::NAN, 500.0),
2470 ..distinct_context()
2471 };
2472 assert_eq!(
2473 PixelValue::from_metric(SizeMetric::Vmin, 10.0)
2474 .resolve_with_context(&half_nan_vp, PropertyContext::Width),
2475 50.0
2476 );
2477 }
2478
2479 #[test]
2480 fn resolve_with_context_never_panics_on_extreme_values() {
2481 let ctx = distinct_context();
2482 for metric in ALL_METRICS {
2483 for v in EXTREME_F32 {
2484 for pc in ALL_PROPERTY_CONTEXTS {
2485 let _ = PixelValue::from_metric(metric, v).resolve_with_context(&ctx, pc);
2487 }
2488 }
2489 }
2490 }
2491
2492 #[test]
2493 fn resolution_context_default_matches_default_const() {
2494 let a = ResolutionContext::default();
2496 let b = ResolutionContext::default_const();
2497
2498 assert_eq!(a.element_font_size, b.element_font_size);
2499 assert_eq!(a.parent_font_size, b.parent_font_size);
2500 assert_eq!(a.root_font_size, b.root_font_size);
2501 assert_eq!(a.containing_block_size, b.containing_block_size);
2502 assert_eq!(a.element_size, b.element_size);
2503 assert_eq!(a.viewport_size, b.viewport_size);
2504
2505 assert_eq!(a.element_font_size, DEFAULT_FONT_SIZE);
2507 assert!(a.element_size.is_none());
2508 }
2509
2510 #[test]
2511 fn logical_and_physical_sizes_round_trip() {
2512 let logical = CssLogicalSize::new(800.0, 600.0);
2513 assert_eq!(logical.to_physical(), PhysicalSize::new(800.0, 600.0));
2514 assert_eq!(logical.to_physical().to_logical(), logical);
2515
2516 let physical = PhysicalSize::new(1920.0, 1080.0);
2517 assert_eq!(physical.to_logical(), CssLogicalSize::new(1920.0, 1080.0));
2518 assert_eq!(physical.to_logical().to_physical(), physical);
2519
2520 assert_eq!(CssLogicalSize::new(800.0, 600.0).to_physical().width, 800.0);
2523 assert_eq!(PhysicalSize::new(800.0, 600.0).to_logical().block_size, 600.0);
2524
2525 let nan = PhysicalSize::new(f32::NAN, f32::INFINITY);
2527 assert!(nan.to_logical().inline_size.is_nan());
2528 assert!(nan.to_logical().block_size.is_infinite());
2529 }
2530
2531 #[test]
2534 fn every_rendering_of_a_pixel_value_agrees() {
2535 for metric in ALL_METRICS {
2538 let v = PixelValue::from_metric(metric, 1.5);
2539 let display = v.to_string();
2540 assert_eq!(format!("{v:?}"), display, "Debug != Display for {metric:?}");
2541 assert_eq!(v.print_as_css_value(), display);
2542 assert_eq!(as_css_value(v), display);
2543 assert!(display.starts_with("1.5"), "{display} lost its number");
2544 assert!(display.len() > 3, "{display} lost its unit");
2545 }
2546
2547 assert_eq!(PixelValue::px(10.0).to_string(), "10px");
2548 assert_eq!(PixelValue::percent(50.0).to_string(), "50%");
2549 assert_eq!(PixelValue::zero().to_string(), "0px");
2550 assert_eq!(
2551 PixelValue::from_metric(SizeMetric::Vmin, 12.0).to_string(),
2552 "12vmin"
2553 );
2554
2555 let np = PixelValueNoPercent::from(PixelValue::px(10.0));
2557 assert_eq!(np.to_string(), "10px");
2558 assert_eq!(format!("{np:?}"), "10px");
2559 assert_eq!(PixelValueNoPercent::zero().to_string(), "0px");
2560 }
2561
2562 #[test]
2563 fn display_never_leaks_nan_or_infinity_into_css() {
2564 for metric in ALL_METRICS {
2568 for v in EXTREME_F32 {
2569 let s = PixelValue::from_metric(metric, v).to_string();
2570 assert!(
2571 !s.contains("NaN") && !s.contains("inf"),
2572 "{metric:?} with input {v} serialized to {s:?}"
2573 );
2574 assert!(!s.is_empty());
2575 }
2576 }
2577 assert_eq!(PixelValue::px(f32::NAN).to_string(), "0px");
2578 }
2579
2580 #[test]
2581 fn pixel_values_round_trip_through_css_for_every_metric_but_vmin() {
2582 for metric in ALL_METRICS {
2584 if metric == SizeMetric::Vmin {
2585 continue; }
2587 for number in [0.0_f32, 1.0, 1.5, -20.0, 0.001, 12345.0] {
2588 let original = PixelValue::from_metric(metric, number);
2589 let css = original.print_as_css_value();
2590 let reparsed = parse_pixel_value(&css).unwrap_or_else(|e| {
2591 panic!("{css:?} (from {metric:?} {number}) failed to re-parse: {e:?}")
2592 });
2593 assert_eq!(reparsed, original, "round-trip broke for {css:?}");
2594 assert_eq!(reparsed.print_as_css_value(), css);
2596 }
2597 }
2598
2599 for metric in ALL_METRICS {
2601 if metric == SizeMetric::Vmin || metric == SizeMetric::Percent {
2602 continue;
2603 }
2604 let original = PixelValueNoPercent::from(PixelValue::from_metric(metric, 7.0));
2605 let css = original.to_string();
2606 assert_eq!(
2607 parse_pixel_value_no_percent(&css).unwrap(),
2608 original,
2609 "no-percent round-trip broke for {css:?}"
2610 );
2611 }
2612
2613 for (css, expected) in [
2615 ("auto", PixelValueWithAuto::Auto),
2616 ("none", PixelValueWithAuto::None),
2617 ("initial", PixelValueWithAuto::Initial),
2618 ("inherit", PixelValueWithAuto::Inherit),
2619 ] {
2620 assert_eq!(parse_pixel_value_with_auto(css).unwrap(), expected);
2621 }
2622 let exact = PixelValue::em(1.5);
2623 assert_eq!(
2624 parse_pixel_value_with_auto(&exact.print_as_css_value()).unwrap(),
2625 PixelValueWithAuto::Exact(exact)
2626 );
2627 }
2628
2629 #[test]
2630 fn format_as_rust_code_emits_a_reconstructible_literal() {
2631 assert_eq!(
2632 PixelValue::px(10.0).format_as_rust_code(0),
2633 "PixelValue { metric: Px, number: FloatValue::new(10) }"
2634 );
2635 assert_eq!(
2636 PixelValue::percent(-1.5).format_as_rust_code(4),
2637 "PixelValue { metric: Percent, number: FloatValue::new(-1.5) }"
2638 );
2639 let nan = PixelValue::from_metric(SizeMetric::Vmax, f32::NAN).format_as_rust_code(0);
2641 assert_eq!(nan, "PixelValue { metric: Vmax, number: FloatValue::new(0) }");
2642 assert!(!PixelValue::px(f32::INFINITY)
2643 .format_as_rust_code(0)
2644 .contains("inf"));
2645 }
2646
2647 #[test]
2648 fn border_thickness_constants_match_the_css_keywords() {
2649 assert_eq!(THIN_BORDER_THICKNESS, PixelValue::px(1.0));
2652 assert_eq!(MEDIUM_BORDER_THICKNESS, PixelValue::px(3.0));
2653 assert_eq!(THICK_BORDER_THICKNESS, PixelValue::px(5.0));
2654
2655 assert_eq!(THIN_BORDER_THICKNESS.number.get(), 1.0);
2656 assert_eq!(MEDIUM_BORDER_THICKNESS.number.get(), 3.0);
2657 assert_eq!(THICK_BORDER_THICKNESS.number.get(), 5.0);
2658 assert_eq!(THIN_BORDER_THICKNESS.number.number() as f32, MULT);
2659
2660 assert!(THIN_BORDER_THICKNESS < MEDIUM_BORDER_THICKNESS);
2661 assert!(MEDIUM_BORDER_THICKNESS < THICK_BORDER_THICKNESS);
2662 assert_eq!(THIN_BORDER_THICKNESS.to_string(), "1px");
2663 }
2664
2665 #[test]
2666 fn ord_is_lexicographic_by_metric_then_number_not_by_resolved_size() {
2667 assert!(PixelValue::px(100.0) < PixelValue::em(1.0));
2671 assert!(PixelValue::px(1.0) < PixelValue::px(2.0));
2672 assert!(PixelValue::percent(1.0) > PixelValue::mm(9999.0));
2673
2674 let a = PixelValue::px(1.5);
2676 let b = PixelValue::px(1.5);
2677 assert_eq!(a, b);
2678 assert_eq!(hash_of(&a), hash_of(&b));
2679 assert_ne!(hash_of(&PixelValue::px(1.0)), hash_of(&PixelValue::em(1.0)));
2680
2681 assert_eq!(PixelValue::px(1.0001), PixelValue::px(1.0002));
2684 assert_eq!(
2685 hash_of(&PixelValue::px(1.0001)),
2686 hash_of(&PixelValue::px(1.0002))
2687 );
2688 }
2689
2690 #[test]
2693 fn system_metric_ref_css_strings_round_trip() {
2694 for r in ALL_SYSTEM_REFS {
2695 let css = r.as_css_str();
2696 assert!(
2697 css.starts_with("system:"),
2698 "{css:?} is missing the system: prefix"
2699 );
2700 assert_eq!(r.to_string(), css, "Display must match as_css_str");
2701 assert_eq!(as_css_value(r), css);
2702
2703 let name = css.strip_prefix("system:").unwrap();
2705 assert_eq!(
2706 SystemMetricRef::from_css_str(name),
2707 Some(r),
2708 "{name:?} must parse back to {r:?}"
2709 );
2710 assert_eq!(SystemMetricRef::from_css_str(name).unwrap().as_css_str(), css);
2712
2713 assert_eq!(SystemMetricRef::from_css_str(css), None);
2716 }
2717
2718 assert_eq!(SystemMetricRef::default(), SystemMetricRef::ButtonRadius);
2719 }
2720
2721 #[test]
2722 fn system_metric_ref_from_css_str_rejects_everything_else() {
2723 for input in [
2724 "",
2725 " ",
2726 "\t\n",
2727 " button-radius ", "Button-Radius", "button_radius", "button-padding", "button-radius;x",
2732 "\u{1F600}",
2733 "b\u{0301}utton-radius",
2734 ] {
2735 assert_eq!(
2736 SystemMetricRef::from_css_str(input),
2737 None,
2738 "{input:?} must not resolve to a system metric"
2739 );
2740 }
2741
2742 assert_eq!(
2744 SystemMetricRef::from_css_str(&"a".repeat(100_000)),
2745 None
2746 );
2747 assert_eq!(SystemMetricRef::from_css_str(&"(".repeat(10_000)), None);
2748 }
2749
2750 #[test]
2751 fn system_metric_ref_resolve_maps_each_variant_to_its_own_field() {
2752 let metrics = populated_metrics();
2754 let expected = [
2755 (SystemMetricRef::ButtonRadius, 1.0),
2756 (SystemMetricRef::ButtonBorderWidth, 2.0),
2757 (SystemMetricRef::ButtonPaddingHorizontal, 3.0),
2758 (SystemMetricRef::ButtonPaddingVertical, 4.0),
2759 (SystemMetricRef::TitlebarHeight, 5.0),
2760 (SystemMetricRef::TitlebarButtonWidth, 6.0),
2761 (SystemMetricRef::TitlebarPadding, 7.0),
2762 (SystemMetricRef::SafeAreaTop, 8.0),
2763 (SystemMetricRef::SafeAreaBottom, 9.0),
2764 (SystemMetricRef::SafeAreaLeft, 10.0),
2765 (SystemMetricRef::SafeAreaRight, 11.0),
2766 ];
2767 for (r, px) in expected {
2768 assert_eq!(
2769 r.resolve(&metrics),
2770 Some(PixelValue::px(px)),
2771 "{r:?} resolved to the wrong field"
2772 );
2773 }
2774
2775 let empty = SystemMetrics::default();
2777 for r in ALL_SYSTEM_REFS {
2778 assert_eq!(r.resolve(&empty), None, "{r:?} must be None when unset");
2779 }
2780 }
2781
2782 #[test]
2783 fn pixel_value_or_system_resolves_and_falls_back() {
2784 let metrics = populated_metrics();
2785 let empty = SystemMetrics::default();
2786 let fallback = PixelValue::px(99.0);
2787
2788 let concrete = PixelValueOrSystem::value(PixelValue::px(10.0));
2790 assert_eq!(concrete.resolve(&metrics, fallback), PixelValue::px(10.0));
2791 assert_eq!(concrete.resolve(&empty, fallback), PixelValue::px(10.0));
2792
2793 let sys = PixelValueOrSystem::system(SystemMetricRef::ButtonRadius);
2795 assert_eq!(sys.resolve(&metrics, fallback), PixelValue::px(1.0));
2796 for r in ALL_SYSTEM_REFS {
2798 assert_eq!(
2799 PixelValueOrSystem::system(r).resolve(&empty, fallback),
2800 fallback,
2801 "{r:?} must fall back when the metric is unset"
2802 );
2803 }
2804
2805 let nan_fallback = PixelValue::px(f32::NAN);
2807 assert_eq!(
2808 sys.resolve(&empty, nan_fallback).number.get(),
2809 0.0
2810 );
2811
2812 assert_eq!(
2814 PixelValueOrSystem::default(),
2815 PixelValueOrSystem::Value(PixelValue::zero())
2816 );
2817 assert_eq!(
2818 PixelValueOrSystem::from(PixelValue::em(2.0)),
2819 PixelValueOrSystem::Value(PixelValue::em(2.0))
2820 );
2821 assert_eq!(
2822 PixelValueOrSystem::default().resolve(&metrics, fallback),
2823 PixelValue::zero()
2824 );
2825 }
2826
2827 #[test]
2828 fn pixel_value_or_system_renders_both_arms() {
2829 let concrete = PixelValueOrSystem::value(PixelValue::px(10.0));
2830 assert_eq!(concrete.to_string(), "10px");
2831 assert_eq!(as_css_value(concrete), "10px");
2832
2833 let sys = PixelValueOrSystem::system(SystemMetricRef::TitlebarHeight);
2834 assert_eq!(sys.to_string(), "system:titlebar-height");
2835 assert_eq!(as_css_value(sys), "system:titlebar-height");
2836
2837 assert_eq!(PixelValueOrSystem::default().to_string(), "0px");
2838
2839 for v in EXTREME_F32 {
2841 let s = PixelValueOrSystem::value(PixelValue::px(v)).to_string();
2842 assert!(!s.contains("NaN") && !s.contains("inf"), "leaked {s:?}");
2843 }
2844 }
2845}