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 pub const fn new(value: f32) -> Self {
45 Self(value)
46 }
47
48 #[inline]
53 pub fn from_unnormalized(value: f32) -> Self {
54 Self(value / 100.0)
55 }
56
57 #[inline]
59 pub const fn get(self) -> f32 {
60 self.0
61 }
62
63 #[inline]
68 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 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 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 pub const fn new(width: f32, height: f32) -> Self {
119 Self { width, height }
120 }
121
122 #[inline]
124 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 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::format_rust_code::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 pub const fn zero() -> Self {
280 const ZERO_PX: PixelValue = PixelValue::const_px(0);
281 ZERO_PX
282 }
283
284 #[inline]
287 pub const fn const_px(value: isize) -> Self {
288 Self::const_from_metric(SizeMetric::Px, value)
289 }
290
291 #[inline]
294 pub const fn const_em(value: isize) -> Self {
295 Self::const_from_metric(SizeMetric::Em, value)
296 }
297
298 #[inline]
311 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 pub const fn const_pt(value: isize) -> Self {
319 Self::const_from_metric(SizeMetric::Pt, value)
320 }
321
322 #[inline]
324 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 pub const fn const_percent(value: isize) -> Self {
332 Self::const_from_metric(SizeMetric::Percent, value)
333 }
334
335 #[inline]
338 pub const fn const_in(value: isize) -> Self {
339 Self::const_from_metric(SizeMetric::In, value)
340 }
341
342 #[inline]
345 pub const fn const_cm(value: isize) -> Self {
346 Self::const_from_metric(SizeMetric::Cm, value)
347 }
348
349 #[inline]
352 pub const fn const_mm(value: isize) -> Self {
353 Self::const_from_metric(SizeMetric::Mm, value)
354 }
355
356 #[inline]
357 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 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 pub fn px(value: f32) -> Self {
384 Self::from_metric(SizeMetric::Px, value)
385 }
386
387 #[inline]
388 pub fn em(value: f32) -> Self {
389 Self::from_metric(SizeMetric::Em, value)
390 }
391
392 #[inline]
393 pub fn inch(value: f32) -> Self {
394 Self::from_metric(SizeMetric::In, value)
395 }
396
397 #[inline]
398 pub fn cm(value: f32) -> Self {
399 Self::from_metric(SizeMetric::Cm, value)
400 }
401
402 #[inline]
403 pub fn mm(value: f32) -> Self {
404 Self::from_metric(SizeMetric::Mm, value)
405 }
406
407 #[inline]
408 pub fn pt(value: f32) -> Self {
409 Self::from_metric(SizeMetric::Pt, value)
410 }
411
412 #[inline]
413 pub fn percent(value: f32) -> Self {
414 Self::from_metric(SizeMetric::Percent, value)
415 }
416
417 #[inline]
418 pub fn rem(value: f32) -> Self {
419 Self::from_metric(SizeMetric::Rem, value)
420 }
421
422 #[inline]
423 pub fn from_metric(metric: SizeMetric, value: f32) -> Self {
424 Self {
425 metric,
426 number: FloatValue::new(value),
427 }
428 }
429
430 #[inline]
431 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
432 if self.metric == other.metric {
433 Self {
434 metric: self.metric,
435 number: self.number.interpolate(&other.number, t),
436 }
437 } else {
438 let self_px_interp = self.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
441 let other_px_interp = other.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
442 Self::from_metric(
443 SizeMetric::Px,
444 self_px_interp + (other_px_interp - self_px_interp) * t,
445 )
446 }
447 }
448
449 #[inline]
455 pub fn to_percent(&self) -> Option<NormalizedPercentage> {
456 match self.metric {
457 SizeMetric::Percent => Some(NormalizedPercentage::from_unnormalized(self.number.get())),
458 _ => None,
459 }
460 }
461
462 #[doc(hidden)]
468 #[inline]
469 pub fn to_pixels_internal(&self, percent_resolve: f32, em_resolve: f32, rem_resolve: f32) -> f32 {
470 match self.metric {
471 SizeMetric::Px => self.number.get(),
472 SizeMetric::Pt => self.number.get() * PT_TO_PX,
473 SizeMetric::In => self.number.get() * 96.0,
474 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
475 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
476 SizeMetric::Em => self.number.get() * em_resolve,
477 SizeMetric::Rem => self.number.get() * rem_resolve,
478 SizeMetric::Percent => {
479 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(percent_resolve)
480 }
481 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => 0.0,
484 }
485 }
486
487 #[inline]
500 pub fn resolve_with_context(
501 &self,
502 context: &ResolutionContext,
503 property_context: PropertyContext,
504 ) -> f32 {
505 match self.metric {
506 SizeMetric::Px => self.number.get(),
508 SizeMetric::Pt => self.number.get() * PT_TO_PX,
509 SizeMetric::In => self.number.get() * 96.0,
510 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
511 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
512
513 SizeMetric::Em => {
515 let reference_font_size = if property_context == PropertyContext::FontSize {
516 context.parent_font_size
518 } else {
519 context.element_font_size
521 };
522 self.number.get() * reference_font_size
523 }
524
525 SizeMetric::Rem => self.number.get() * context.root_font_size,
527
528 SizeMetric::Vw => self.number.get() * context.viewport_size.width / 100.0,
531 SizeMetric::Vh => self.number.get() * context.viewport_size.height / 100.0,
532 SizeMetric::Vmin => {
534 let min_dimension = context
535 .viewport_size
536 .width
537 .min(context.viewport_size.height);
538 self.number.get() * min_dimension / 100.0
539 }
540 SizeMetric::Vmax => {
542 let max_dimension = context
543 .viewport_size
544 .width
545 .max(context.viewport_size.height);
546 self.number.get() * max_dimension / 100.0
547 }
548
549 SizeMetric::Percent => {
551 let reference = match property_context {
552 PropertyContext::FontSize => context.parent_font_size,
554
555 PropertyContext::Width => context.containing_block_size.width,
557
558 PropertyContext::Height => context.containing_block_size.height,
560
561 PropertyContext::Margin | PropertyContext::Padding => {
567 context.containing_block_size.width
568 }
569
570 PropertyContext::BorderWidth => 0.0,
573
574 PropertyContext::BorderRadius => {
578 context.element_size.map(|s| s.width).unwrap_or(0.0)
579 }
580
581 PropertyContext::Transform => {
583 context.element_size.map(|s| s.width).unwrap_or(0.0)
584 }
585
586 PropertyContext::Other => context.containing_block_size.width,
588 };
589
590 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(reference)
591 }
592 }
593 }
594}
595
596pub const THIN_BORDER_THICKNESS: PixelValue = PixelValue {
602 metric: SizeMetric::Px,
603 number: FloatValue { number: 1000 },
604};
605
606pub const MEDIUM_BORDER_THICKNESS: PixelValue = PixelValue {
608 metric: SizeMetric::Px,
609 number: FloatValue { number: 3000 },
610};
611
612pub const THICK_BORDER_THICKNESS: PixelValue = PixelValue {
614 metric: SizeMetric::Px,
615 number: FloatValue { number: 5000 },
616};
617
618#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
620#[repr(C)]
621pub struct PixelValueNoPercent {
622 pub inner: PixelValue,
623}
624
625impl PixelValueNoPercent {
626 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
627 self.inner.scale_for_dpi(scale_factor);
628 }
629}
630
631impl_option!(
632 PixelValueNoPercent,
633 OptionPixelValueNoPercent,
634 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
635);
636
637impl_option!(
638 PixelValue,
639 OptionPixelValue,
640 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
641);
642
643impl fmt::Display for PixelValueNoPercent {
644 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
645 write!(f, "{}", self.inner)
646 }
647}
648
649impl ::core::fmt::Debug for PixelValueNoPercent {
650 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
651 write!(f, "{}", self)
652 }
653}
654
655impl PixelValueNoPercent {
656 #[doc(hidden)]
662 #[inline]
663 pub fn to_pixels_internal(&self, em_resolve: f32, rem_resolve: f32) -> f32 {
664 self.inner.to_pixels_internal(0.0, em_resolve, rem_resolve)
665 }
666
667 #[inline]
668 pub const fn zero() -> Self {
669 const ZERO_PXNP: PixelValueNoPercent = PixelValueNoPercent {
670 inner: PixelValue::zero(),
671 };
672 ZERO_PXNP
673 }
674}
675impl From<PixelValue> for PixelValueNoPercent {
676 fn from(e: PixelValue) -> Self {
677 Self { inner: e }
678 }
679}
680
681#[derive(Clone, PartialEq)]
682pub enum CssPixelValueParseError<'a> {
683 EmptyString,
684 NoValueGiven(&'a str, SizeMetric),
685 ValueParseErr(ParseFloatError, &'a str),
686 InvalidPixelValue(&'a str),
687}
688
689impl_debug_as_display!(CssPixelValueParseError<'a>);
690
691impl_display! { CssPixelValueParseError<'a>, {
692 EmptyString => format!("Missing [px / pt / em / %] value"),
693 NoValueGiven(input, metric) => format!("Expected floating-point pixel value, got: \"{}{}\"", input, metric),
694 ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
695 InvalidPixelValue(s) => format!("Invalid pixel value: \"{}\"", s),
696}}
697
698#[derive(Debug, Clone, PartialEq)]
700#[repr(C)]
701pub struct PixelNoValueGivenError {
702 pub value: AzString,
703 pub metric: SizeMetric,
704}
705
706#[derive(Debug, Clone, PartialEq)]
708#[repr(C, u8)]
709pub enum CssPixelValueParseErrorOwned {
710 EmptyString,
711 NoValueGiven(PixelNoValueGivenError),
712 ValueParseErr(ParseFloatErrorWithInput),
713 InvalidPixelValue(AzString),
714}
715
716impl<'a> CssPixelValueParseError<'a> {
717 pub fn to_contained(&self) -> CssPixelValueParseErrorOwned {
718 match self {
719 CssPixelValueParseError::EmptyString => CssPixelValueParseErrorOwned::EmptyString,
720 CssPixelValueParseError::NoValueGiven(s, metric) => {
721 CssPixelValueParseErrorOwned::NoValueGiven(PixelNoValueGivenError { value: s.to_string().into(), metric: *metric })
722 }
723 CssPixelValueParseError::ValueParseErr(err, s) => {
724 CssPixelValueParseErrorOwned::ValueParseErr(ParseFloatErrorWithInput { error: err.clone().into(), input: s.to_string().into() })
725 }
726 CssPixelValueParseError::InvalidPixelValue(s) => {
727 CssPixelValueParseErrorOwned::InvalidPixelValue(s.to_string().into())
728 }
729 }
730 }
731}
732
733impl CssPixelValueParseErrorOwned {
734 pub fn to_shared<'a>(&'a self) -> CssPixelValueParseError<'a> {
735 match self {
736 CssPixelValueParseErrorOwned::EmptyString => CssPixelValueParseError::EmptyString,
737 CssPixelValueParseErrorOwned::NoValueGiven(e) => {
738 CssPixelValueParseError::NoValueGiven(e.value.as_str(), e.metric)
739 }
740 CssPixelValueParseErrorOwned::ValueParseErr(e) => {
741 CssPixelValueParseError::ValueParseErr(e.error.to_std(), e.input.as_str())
742 }
743 CssPixelValueParseErrorOwned::InvalidPixelValue(s) => {
744 CssPixelValueParseError::InvalidPixelValue(s.as_str())
745 }
746 }
747 }
748}
749
750fn parse_pixel_value_inner<'a>(
752 input: &'a str,
753 match_values: &[(&'static str, SizeMetric)],
754) -> Result<PixelValue, CssPixelValueParseError<'a>> {
755 let input = input.trim();
756
757 if input.is_empty() {
758 return Err(CssPixelValueParseError::EmptyString);
759 }
760
761 for (match_val, metric) in match_values {
762 if input.ends_with(match_val) {
763 let value = &input[..input.len() - match_val.len()];
764 let value = value.trim();
765 if value.is_empty() {
766 return Err(CssPixelValueParseError::NoValueGiven(input, *metric));
767 }
768 match value.parse::<f32>() {
769 Ok(o) => {
770 return Ok(PixelValue::from_metric(*metric, o));
771 }
772 Err(e) => {
773 return Err(CssPixelValueParseError::ValueParseErr(e, value));
774 }
775 }
776 }
777 }
778
779 match input.trim().parse::<f32>() {
780 Ok(o) => Ok(PixelValue::px(o)),
781 Err(_) => Err(CssPixelValueParseError::InvalidPixelValue(input)),
782 }
783}
784
785pub fn parse_pixel_value<'a>(input: &'a str) -> Result<PixelValue, CssPixelValueParseError<'a>> {
786 parse_pixel_value_inner(
787 input,
788 &[
789 ("px", SizeMetric::Px),
790 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
792 ("pt", SizeMetric::Pt),
793 ("in", SizeMetric::In),
794 ("mm", SizeMetric::Mm),
795 ("cm", SizeMetric::Cm),
796 ("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
799 ("vh", SizeMetric::Vh),
800 ("%", SizeMetric::Percent),
801 ],
802 )
803}
804
805pub fn parse_pixel_value_no_percent<'a>(
806 input: &'a str,
807) -> Result<PixelValueNoPercent, CssPixelValueParseError<'a>> {
808 Ok(PixelValueNoPercent {
809 inner: parse_pixel_value_inner(
810 input,
811 &[
812 ("px", SizeMetric::Px),
813 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
815 ("pt", SizeMetric::Pt),
816 ("in", SizeMetric::In),
817 ("mm", SizeMetric::Mm),
818 ("cm", SizeMetric::Cm),
819 ("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
822 ("vh", SizeMetric::Vh),
823 ],
824 )?,
825 })
826}
827
828#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
829pub enum PixelValueWithAuto {
830 None,
831 Initial,
832 Inherit,
833 Auto,
834 Exact(PixelValue),
835}
836
837pub fn parse_pixel_value_with_auto<'a>(
839 input: &'a str,
840) -> Result<PixelValueWithAuto, CssPixelValueParseError<'a>> {
841 let input = input.trim();
842 match input {
843 "none" => Ok(PixelValueWithAuto::None),
844 "initial" => Ok(PixelValueWithAuto::Initial),
845 "inherit" => Ok(PixelValueWithAuto::Inherit),
846 "auto" => Ok(PixelValueWithAuto::Auto),
847 e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
848 }
849}
850
851#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
860#[repr(C)]
861#[derive(Default)]
862pub enum SystemMetricRef {
863 #[default]
865 ButtonRadius,
866 ButtonPaddingHorizontal,
868 ButtonPaddingVertical,
870 ButtonBorderWidth,
872 TitlebarHeight,
874 TitlebarButtonWidth,
876 TitlebarPadding,
878 SafeAreaTop,
880 SafeAreaBottom,
882 SafeAreaLeft,
884 SafeAreaRight,
886}
887
888
889impl SystemMetricRef {
890 pub fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
892 match self {
893 SystemMetricRef::ButtonRadius => metrics.corner_radius.as_option().copied(),
894 SystemMetricRef::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
895 SystemMetricRef::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
896 SystemMetricRef::ButtonBorderWidth => metrics.border_width.as_option().copied(),
897 SystemMetricRef::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
898 SystemMetricRef::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
899 SystemMetricRef::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
900 SystemMetricRef::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
901 SystemMetricRef::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
902 SystemMetricRef::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
903 SystemMetricRef::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
904 }
905 }
906
907 pub fn as_css_str(&self) -> &'static str {
909 match self {
910 SystemMetricRef::ButtonRadius => "system:button-radius",
911 SystemMetricRef::ButtonPaddingHorizontal => "system:button-padding-horizontal",
912 SystemMetricRef::ButtonPaddingVertical => "system:button-padding-vertical",
913 SystemMetricRef::ButtonBorderWidth => "system:button-border-width",
914 SystemMetricRef::TitlebarHeight => "system:titlebar-height",
915 SystemMetricRef::TitlebarButtonWidth => "system:titlebar-button-width",
916 SystemMetricRef::TitlebarPadding => "system:titlebar-padding",
917 SystemMetricRef::SafeAreaTop => "system:safe-area-top",
918 SystemMetricRef::SafeAreaBottom => "system:safe-area-bottom",
919 SystemMetricRef::SafeAreaLeft => "system:safe-area-left",
920 SystemMetricRef::SafeAreaRight => "system:safe-area-right",
921 }
922 }
923
924 pub fn from_css_str(s: &str) -> Option<Self> {
926 match s {
927 "button-radius" => Some(SystemMetricRef::ButtonRadius),
928 "button-padding-horizontal" => Some(SystemMetricRef::ButtonPaddingHorizontal),
929 "button-padding-vertical" => Some(SystemMetricRef::ButtonPaddingVertical),
930 "button-border-width" => Some(SystemMetricRef::ButtonBorderWidth),
931 "titlebar-height" => Some(SystemMetricRef::TitlebarHeight),
932 "titlebar-button-width" => Some(SystemMetricRef::TitlebarButtonWidth),
933 "titlebar-padding" => Some(SystemMetricRef::TitlebarPadding),
934 "safe-area-top" => Some(SystemMetricRef::SafeAreaTop),
935 "safe-area-bottom" => Some(SystemMetricRef::SafeAreaBottom),
936 "safe-area-left" => Some(SystemMetricRef::SafeAreaLeft),
937 "safe-area-right" => Some(SystemMetricRef::SafeAreaRight),
938 _ => None,
939 }
940 }
941}
942
943impl fmt::Display for SystemMetricRef {
944 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
945 write!(f, "{}", self.as_css_str())
946 }
947}
948
949impl FormatAsCssValue for SystemMetricRef {
950 fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
951 write!(f, "{}", self.as_css_str())
952 }
953}
954
955#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
960#[repr(C, u8)]
961pub enum PixelValueOrSystem {
962 Value(PixelValue),
964 System(SystemMetricRef),
966}
967
968impl Default for PixelValueOrSystem {
969 fn default() -> Self {
970 PixelValueOrSystem::Value(PixelValue::zero())
971 }
972}
973
974impl From<PixelValue> for PixelValueOrSystem {
975 fn from(value: PixelValue) -> Self {
976 PixelValueOrSystem::Value(value)
977 }
978}
979
980impl PixelValueOrSystem {
981 pub const fn value(v: PixelValue) -> Self {
983 PixelValueOrSystem::Value(v)
984 }
985
986 pub const fn system(s: SystemMetricRef) -> Self {
988 PixelValueOrSystem::System(s)
989 }
990
991 pub fn resolve(&self, system_metrics: &crate::system::SystemMetrics, fallback: PixelValue) -> PixelValue {
994 match self {
995 PixelValueOrSystem::Value(v) => *v,
996 PixelValueOrSystem::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
997 }
998 }
999
1000}
1001
1002impl fmt::Display for PixelValueOrSystem {
1003 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1004 match self {
1005 PixelValueOrSystem::Value(v) => write!(f, "{}", v),
1006 PixelValueOrSystem::System(s) => write!(f, "{}", s),
1007 }
1008 }
1009}
1010
1011impl FormatAsCssValue for PixelValueOrSystem {
1012 fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
1013 match self {
1014 PixelValueOrSystem::Value(v) => v.format_as_css_value(f),
1015 PixelValueOrSystem::System(s) => s.format_as_css_value(f),
1016 }
1017 }
1018}
1019
1020#[cfg(feature = "parser")]
1024pub fn parse_pixel_value_or_system<'a>(
1025 input: &'a str,
1026) -> Result<PixelValueOrSystem, CssPixelValueParseError<'a>> {
1027 let input = input.trim();
1028
1029 if let Some(metric_name) = input.strip_prefix("system:") {
1031 if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
1032 return Ok(PixelValueOrSystem::System(metric_ref));
1033 } else {
1034 return Err(CssPixelValueParseError::InvalidPixelValue(input));
1035 }
1036 }
1037
1038 Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
1040}
1041
1042#[cfg(all(test, feature = "parser"))]
1043mod tests {
1044 use super::*;
1045
1046 #[test]
1047 fn test_parse_pixel_value() {
1048 assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
1049 assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
1050 assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
1051 assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
1052 assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
1053 assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
1054 assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
1055 assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
1056 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1057 }
1058
1059 #[test]
1060 fn test_resolve_with_context_em() {
1061 let context = ResolutionContext {
1063 element_font_size: 32.0,
1064 parent_font_size: 16.0,
1065 ..Default::default()
1066 };
1067
1068 let margin = PixelValue::em(0.67);
1070 assert!(
1071 (margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
1072 );
1073
1074 let font_size = PixelValue::em(2.0);
1076 assert_eq!(
1077 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1078 32.0
1079 );
1080 }
1081
1082 #[test]
1083 fn test_resolve_with_context_rem() {
1084 let context = ResolutionContext {
1086 element_font_size: 32.0,
1087 parent_font_size: 16.0,
1088 root_font_size: 18.0,
1089 ..Default::default()
1090 };
1091
1092 let margin = PixelValue::rem(2.0);
1094 assert_eq!(
1095 margin.resolve_with_context(&context, PropertyContext::Margin),
1096 36.0
1097 );
1098
1099 let font_size = PixelValue::rem(1.5);
1100 assert_eq!(
1101 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1102 27.0
1103 );
1104 }
1105
1106 #[test]
1107 fn test_resolve_with_context_percent_margin() {
1108 let context = ResolutionContext {
1110 element_font_size: 16.0,
1111 parent_font_size: 16.0,
1112 root_font_size: 16.0,
1113 containing_block_size: PhysicalSize::new(800.0, 600.0),
1114 element_size: None,
1115 viewport_size: PhysicalSize::new(1920.0, 1080.0),
1116 };
1117
1118 let margin = PixelValue::percent(10.0); assert_eq!(
1120 margin.resolve_with_context(&context, PropertyContext::Margin),
1121 80.0
1122 ); }
1124
1125 #[test]
1126 fn test_parse_pixel_value_no_percent() {
1127 assert_eq!(
1128 parse_pixel_value_no_percent("10px").unwrap().inner,
1129 PixelValue::px(10.0)
1130 );
1131 assert!(parse_pixel_value_no_percent("50%").is_err());
1132 }
1133
1134 #[test]
1135 fn test_parse_pixel_value_with_auto() {
1136 assert_eq!(
1137 parse_pixel_value_with_auto("10px").unwrap(),
1138 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1139 );
1140 assert_eq!(
1141 parse_pixel_value_with_auto("auto").unwrap(),
1142 PixelValueWithAuto::Auto
1143 );
1144 assert_eq!(
1145 parse_pixel_value_with_auto("initial").unwrap(),
1146 PixelValueWithAuto::Initial
1147 );
1148 assert_eq!(
1149 parse_pixel_value_with_auto("inherit").unwrap(),
1150 PixelValueWithAuto::Inherit
1151 );
1152 assert_eq!(
1153 parse_pixel_value_with_auto("none").unwrap(),
1154 PixelValueWithAuto::None
1155 );
1156 }
1157
1158 #[test]
1159 fn test_parse_pixel_value_errors() {
1160 assert!(parse_pixel_value("").is_err());
1161 assert!(parse_pixel_value("10").is_ok()); assert!(parse_pixel_value("10 px").is_ok()); assert!(parse_pixel_value("px").is_err());
1166 assert!(parse_pixel_value("ten-px").is_err());
1167 }
1168}