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),
796 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
798 ("pt", SizeMetric::Pt),
799 ("in", SizeMetric::In),
800 ("mm", SizeMetric::Mm),
801 ("cm", SizeMetric::Cm),
802 ("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
805 ("vh", SizeMetric::Vh),
806 ("%", SizeMetric::Percent),
807 ],
808 )
809}
810
811pub fn parse_pixel_value_no_percent(
815 input: &str,
816) -> Result<PixelValueNoPercent, CssPixelValueParseError<'_>> {
817 Ok(PixelValueNoPercent {
818 inner: parse_pixel_value_inner(
819 input,
820 &[
821 ("px", SizeMetric::Px),
822 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
824 ("pt", SizeMetric::Pt),
825 ("in", SizeMetric::In),
826 ("mm", SizeMetric::Mm),
827 ("cm", SizeMetric::Cm),
828 ("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
831 ("vh", SizeMetric::Vh),
832 ],
833 )?,
834 })
835}
836
837#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
838pub enum PixelValueWithAuto {
839 None,
840 Initial,
841 Inherit,
842 Auto,
843 Exact(PixelValue),
844}
845
846pub fn parse_pixel_value_with_auto(
851 input: &str,
852) -> Result<PixelValueWithAuto, CssPixelValueParseError<'_>> {
853 let input = input.trim();
854 match input {
855 "none" => Ok(PixelValueWithAuto::None),
856 "initial" => Ok(PixelValueWithAuto::Initial),
857 "inherit" => Ok(PixelValueWithAuto::Inherit),
858 "auto" => Ok(PixelValueWithAuto::Auto),
859 e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
860 }
861}
862
863#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
872#[repr(C)]
873#[derive(Default)]
874pub enum SystemMetricRef {
875 #[default]
877 ButtonRadius,
878 ButtonPaddingHorizontal,
880 ButtonPaddingVertical,
882 ButtonBorderWidth,
884 TitlebarHeight,
886 TitlebarButtonWidth,
888 TitlebarPadding,
890 SafeAreaTop,
892 SafeAreaBottom,
894 SafeAreaLeft,
896 SafeAreaRight,
898}
899
900
901impl SystemMetricRef {
902 #[must_use] pub const fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
904 match self {
905 Self::ButtonRadius => metrics.corner_radius.as_option().copied(),
906 Self::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
907 Self::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
908 Self::ButtonBorderWidth => metrics.border_width.as_option().copied(),
909 Self::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
910 Self::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
911 Self::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
912 Self::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
913 Self::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
914 Self::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
915 Self::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
916 }
917 }
918
919 #[must_use] pub const fn as_css_str(&self) -> &'static str {
921 match self {
922 Self::ButtonRadius => "system:button-radius",
923 Self::ButtonPaddingHorizontal => "system:button-padding-horizontal",
924 Self::ButtonPaddingVertical => "system:button-padding-vertical",
925 Self::ButtonBorderWidth => "system:button-border-width",
926 Self::TitlebarHeight => "system:titlebar-height",
927 Self::TitlebarButtonWidth => "system:titlebar-button-width",
928 Self::TitlebarPadding => "system:titlebar-padding",
929 Self::SafeAreaTop => "system:safe-area-top",
930 Self::SafeAreaBottom => "system:safe-area-bottom",
931 Self::SafeAreaLeft => "system:safe-area-left",
932 Self::SafeAreaRight => "system:safe-area-right",
933 }
934 }
935
936 #[must_use] pub fn from_css_str(s: &str) -> Option<Self> {
938 match s {
939 "button-radius" => Some(Self::ButtonRadius),
940 "button-padding-horizontal" => Some(Self::ButtonPaddingHorizontal),
941 "button-padding-vertical" => Some(Self::ButtonPaddingVertical),
942 "button-border-width" => Some(Self::ButtonBorderWidth),
943 "titlebar-height" => Some(Self::TitlebarHeight),
944 "titlebar-button-width" => Some(Self::TitlebarButtonWidth),
945 "titlebar-padding" => Some(Self::TitlebarPadding),
946 "safe-area-top" => Some(Self::SafeAreaTop),
947 "safe-area-bottom" => Some(Self::SafeAreaBottom),
948 "safe-area-left" => Some(Self::SafeAreaLeft),
949 "safe-area-right" => Some(Self::SafeAreaRight),
950 _ => None,
951 }
952 }
953}
954
955impl fmt::Display for SystemMetricRef {
956 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
957 write!(f, "{}", self.as_css_str())
958 }
959}
960
961impl FormatAsCssValue for SystemMetricRef {
962 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963 write!(f, "{}", self.as_css_str())
964 }
965}
966
967#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
972#[repr(C, u8)]
973pub enum PixelValueOrSystem {
974 Value(PixelValue),
976 System(SystemMetricRef),
978}
979
980impl Default for PixelValueOrSystem {
981 fn default() -> Self {
982 Self::Value(PixelValue::zero())
983 }
984}
985
986impl From<PixelValue> for PixelValueOrSystem {
987 fn from(value: PixelValue) -> Self {
988 Self::Value(value)
989 }
990}
991
992impl PixelValueOrSystem {
993 #[must_use] pub const fn value(v: PixelValue) -> Self {
995 Self::Value(v)
996 }
997
998 #[must_use] pub const fn system(s: SystemMetricRef) -> Self {
1000 Self::System(s)
1001 }
1002
1003 #[must_use] pub fn resolve(&self, system_metrics: &crate::system::SystemMetrics, fallback: PixelValue) -> PixelValue {
1006 match self {
1007 Self::Value(v) => *v,
1008 Self::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
1009 }
1010 }
1011
1012}
1013
1014impl fmt::Display for PixelValueOrSystem {
1015 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1016 match self {
1017 Self::Value(v) => write!(f, "{v}"),
1018 Self::System(s) => write!(f, "{s}"),
1019 }
1020 }
1021}
1022
1023impl FormatAsCssValue for PixelValueOrSystem {
1024 fn format_as_css_value(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1025 match self {
1026 Self::Value(v) => v.format_as_css_value(f),
1027 Self::System(s) => s.format_as_css_value(f),
1028 }
1029 }
1030}
1031
1032#[cfg(feature = "parser")]
1036pub fn parse_pixel_value_or_system(
1040 input: &str,
1041) -> Result<PixelValueOrSystem, CssPixelValueParseError<'_>> {
1042 let input = input.trim();
1043
1044 if let Some(metric_name) = input.strip_prefix("system:") {
1046 if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
1047 return Ok(PixelValueOrSystem::System(metric_ref));
1048 }
1049 return Err(CssPixelValueParseError::InvalidPixelValue(input));
1050 }
1051
1052 Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
1054}
1055
1056#[cfg(all(test, feature = "parser"))]
1057mod tests {
1058 #![allow(clippy::float_cmp)]
1060 use super::*;
1061
1062 #[test]
1063 fn test_parse_pixel_value() {
1064 assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
1065 assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
1066 assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
1067 assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
1068 assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
1069 assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
1070 assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
1071 assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
1072 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1073 }
1074
1075 #[test]
1076 fn test_resolve_with_context_em() {
1077 let context = ResolutionContext {
1079 element_font_size: 32.0,
1080 parent_font_size: 16.0,
1081 ..Default::default()
1082 };
1083
1084 let margin = PixelValue::em(0.67);
1086 assert!(
1087 (margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
1088 );
1089
1090 let font_size = PixelValue::em(2.0);
1092 assert_eq!(
1093 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1094 32.0
1095 );
1096 }
1097
1098 #[test]
1099 fn test_resolve_with_context_rem() {
1100 let context = ResolutionContext {
1102 element_font_size: 32.0,
1103 parent_font_size: 16.0,
1104 root_font_size: 18.0,
1105 ..Default::default()
1106 };
1107
1108 let margin = PixelValue::rem(2.0);
1110 assert_eq!(
1111 margin.resolve_with_context(&context, PropertyContext::Margin),
1112 36.0
1113 );
1114
1115 let font_size = PixelValue::rem(1.5);
1116 assert_eq!(
1117 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1118 27.0
1119 );
1120 }
1121
1122 #[test]
1123 fn test_resolve_with_context_percent_margin() {
1124 let context = ResolutionContext {
1126 element_font_size: 16.0,
1127 parent_font_size: 16.0,
1128 root_font_size: 16.0,
1129 containing_block_size: PhysicalSize::new(800.0, 600.0),
1130 element_size: None,
1131 viewport_size: PhysicalSize::new(1920.0, 1080.0),
1132 };
1133
1134 let margin = PixelValue::percent(10.0); assert_eq!(
1136 margin.resolve_with_context(&context, PropertyContext::Margin),
1137 80.0
1138 ); }
1140
1141 #[test]
1142 fn test_parse_pixel_value_no_percent() {
1143 assert_eq!(
1144 parse_pixel_value_no_percent("10px").unwrap().inner,
1145 PixelValue::px(10.0)
1146 );
1147 assert!(parse_pixel_value_no_percent("50%").is_err());
1148 }
1149
1150 #[test]
1151 fn test_parse_pixel_value_with_auto() {
1152 assert_eq!(
1153 parse_pixel_value_with_auto("10px").unwrap(),
1154 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1155 );
1156 assert_eq!(
1157 parse_pixel_value_with_auto("auto").unwrap(),
1158 PixelValueWithAuto::Auto
1159 );
1160 assert_eq!(
1161 parse_pixel_value_with_auto("initial").unwrap(),
1162 PixelValueWithAuto::Initial
1163 );
1164 assert_eq!(
1165 parse_pixel_value_with_auto("inherit").unwrap(),
1166 PixelValueWithAuto::Inherit
1167 );
1168 assert_eq!(
1169 parse_pixel_value_with_auto("none").unwrap(),
1170 PixelValueWithAuto::None
1171 );
1172 }
1173
1174 #[test]
1175 fn test_parse_pixel_value_errors() {
1176 assert!(parse_pixel_value("").is_err());
1177 assert!(parse_pixel_value("10").is_ok()); assert!(parse_pixel_value("10 px").is_ok()); assert!(parse_pixel_value("px").is_err());
1182 assert!(parse_pixel_value("ten-px").is_err());
1183 }
1184}