1use core::fmt;
2use std::num::ParseFloatError;
3
4use crate::props::{
5 basic::{FloatValue, SizeMetric},
6 formatter::FormatAsCssValue,
7};
8
9pub 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 #[inline]
205 pub const fn for_fonts(
206 element_font_size: f32,
207 parent_font_size: f32,
208 root_font_size: f32,
209 ) -> Self {
210 Self {
211 element_font_size,
212 parent_font_size,
213 root_font_size,
214 containing_block_size: PhysicalSize {
215 width: 0.0,
216 height: 0.0,
217 },
218 element_size: None,
219 viewport_size: PhysicalSize {
220 width: 0.0,
221 height: 0.0,
222 },
223 }
224 }
225
226 #[inline]
228 pub const fn with_containing_block(mut self, containing_block_size: PhysicalSize) -> Self {
229 self.containing_block_size = containing_block_size;
230 self
231 }
232
233 #[inline]
235 pub const fn with_element_size(mut self, element_size: PhysicalSize) -> Self {
236 self.element_size = Some(element_size);
237 self
238 }
239
240 #[inline]
242 pub const fn with_viewport_size(mut self, viewport_size: PhysicalSize) -> Self {
243 self.viewport_size = viewport_size;
244 self
245 }
246}
247
248#[derive(Debug, Copy, Clone, PartialEq, Eq)]
250pub enum PropertyContext {
251 FontSize,
253 Margin,
255 Padding,
257 Width,
259 Height,
261 BorderWidth,
263 BorderRadius,
265 Transform,
267 Other,
269}
270
271#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
272#[repr(C)]
273pub struct PixelValue {
274 pub metric: SizeMetric,
275 pub number: FloatValue,
276}
277
278impl PixelValue {
279 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
280 self.number = FloatValue::new(self.number.get() * scale_factor);
281 }
282}
283
284impl FormatAsCssValue for PixelValue {
285 fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
286 write!(f, "{}{}", self.number, self.metric)
287 }
288}
289
290impl crate::css::PrintAsCssValue for PixelValue {
291 fn print_as_css_value(&self) -> String {
292 format!("{}{}", self.number, self.metric)
293 }
294}
295
296impl crate::format_rust_code::FormatAsRustCode for PixelValue {
297 fn format_as_rust_code(&self, _tabs: usize) -> String {
298 format!(
299 "PixelValue {{ metric: {:?}, number: FloatValue::new({}) }}",
300 self.metric,
301 self.number.get()
302 )
303 }
304}
305
306impl fmt::Debug for PixelValue {
307 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
308 write!(f, "{}{}", self.number, self.metric)
309 }
310}
311
312impl fmt::Display for PixelValue {
314 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
315 write!(f, "{}{}", self.number, self.metric)
316 }
317}
318
319impl PixelValue {
320 #[inline]
321 pub const fn zero() -> Self {
322 const ZERO_PX: PixelValue = PixelValue::const_px(0);
323 ZERO_PX
324 }
325
326 #[inline]
329 pub const fn const_px(value: isize) -> Self {
330 Self::const_from_metric(SizeMetric::Px, value)
331 }
332
333 #[inline]
336 pub const fn const_em(value: isize) -> Self {
337 Self::const_from_metric(SizeMetric::Em, value)
338 }
339
340 #[inline]
353 pub const fn const_em_fractional(pre_comma: isize, post_comma: isize) -> Self {
354 Self::const_from_metric_fractional(SizeMetric::Em, pre_comma, post_comma)
355 }
356
357 #[inline]
360 pub const fn const_pt(value: isize) -> Self {
361 Self::const_from_metric(SizeMetric::Pt, value)
362 }
363
364 #[inline]
366 pub const fn const_pt_fractional(pre_comma: isize, post_comma: isize) -> Self {
367 Self::const_from_metric_fractional(SizeMetric::Pt, pre_comma, post_comma)
368 }
369
370 #[inline]
373 pub const fn const_percent(value: isize) -> Self {
374 Self::const_from_metric(SizeMetric::Percent, value)
375 }
376
377 #[inline]
380 pub const fn const_in(value: isize) -> Self {
381 Self::const_from_metric(SizeMetric::In, value)
382 }
383
384 #[inline]
387 pub const fn const_cm(value: isize) -> Self {
388 Self::const_from_metric(SizeMetric::Cm, value)
389 }
390
391 #[inline]
394 pub const fn const_mm(value: isize) -> Self {
395 Self::const_from_metric(SizeMetric::Mm, value)
396 }
397
398 #[inline]
399 pub const fn const_from_metric(metric: SizeMetric, value: isize) -> Self {
400 Self {
401 metric,
402 number: FloatValue::const_new(value),
403 }
404 }
405
406 #[inline]
413 pub const fn const_from_metric_fractional(
414 metric: SizeMetric,
415 pre_comma: isize,
416 post_comma: isize,
417 ) -> Self {
418 Self {
419 metric,
420 number: FloatValue::const_new_fractional(pre_comma, post_comma),
421 }
422 }
423
424 #[inline]
425 pub fn px(value: f32) -> Self {
426 Self::from_metric(SizeMetric::Px, value)
427 }
428
429 #[inline]
430 pub fn em(value: f32) -> Self {
431 Self::from_metric(SizeMetric::Em, value)
432 }
433
434 #[inline]
435 pub fn inch(value: f32) -> Self {
436 Self::from_metric(SizeMetric::In, value)
437 }
438
439 #[inline]
440 pub fn cm(value: f32) -> Self {
441 Self::from_metric(SizeMetric::Cm, value)
442 }
443
444 #[inline]
445 pub fn mm(value: f32) -> Self {
446 Self::from_metric(SizeMetric::Mm, value)
447 }
448
449 #[inline]
450 pub fn pt(value: f32) -> Self {
451 Self::from_metric(SizeMetric::Pt, value)
452 }
453
454 #[inline]
455 pub fn percent(value: f32) -> Self {
456 Self::from_metric(SizeMetric::Percent, value)
457 }
458
459 #[inline]
460 pub fn rem(value: f32) -> Self {
461 Self::from_metric(SizeMetric::Rem, value)
462 }
463
464 #[inline]
465 pub fn from_metric(metric: SizeMetric, value: f32) -> Self {
466 Self {
467 metric,
468 number: FloatValue::new(value),
469 }
470 }
471
472 #[inline]
473 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
474 if self.metric == other.metric {
475 Self {
476 metric: self.metric,
477 number: self.number.interpolate(&other.number, t),
478 }
479 } else {
480 let self_px_interp = self.to_pixels_internal(0.0, DEFAULT_FONT_SIZE);
483 let other_px_interp = other.to_pixels_internal(0.0, DEFAULT_FONT_SIZE);
484 Self::from_metric(
485 SizeMetric::Px,
486 self_px_interp + (other_px_interp - self_px_interp) * t,
487 )
488 }
489 }
490
491 #[inline]
497 pub fn to_percent(&self) -> Option<NormalizedPercentage> {
498 match self.metric {
499 SizeMetric::Percent => Some(NormalizedPercentage::from_unnormalized(self.number.get())),
500 _ => None,
501 }
502 }
503
504 #[doc(hidden)]
510 #[inline]
511 pub fn to_pixels_internal(&self, percent_resolve: f32, em_resolve: f32) -> f32 {
512 match self.metric {
513 SizeMetric::Px => self.number.get(),
514 SizeMetric::Pt => self.number.get() * PT_TO_PX,
515 SizeMetric::In => self.number.get() * 96.0,
516 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
517 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
518 SizeMetric::Em => self.number.get() * em_resolve,
519 SizeMetric::Rem => self.number.get() * em_resolve,
520 SizeMetric::Percent => {
521 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(percent_resolve)
522 }
523 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => 0.0,
526 }
527 }
528
529 #[inline]
542 pub fn resolve_with_context(
543 &self,
544 context: &ResolutionContext,
545 property_context: PropertyContext,
546 ) -> f32 {
547 match self.metric {
548 SizeMetric::Px => self.number.get(),
550 SizeMetric::Pt => self.number.get() * PT_TO_PX,
551 SizeMetric::In => self.number.get() * 96.0,
552 SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
553 SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
554
555 SizeMetric::Em => {
557 let reference_font_size = if property_context == PropertyContext::FontSize {
558 context.parent_font_size
560 } else {
561 context.element_font_size
563 };
564 self.number.get() * reference_font_size
565 }
566
567 SizeMetric::Rem => self.number.get() * context.root_font_size,
569
570 SizeMetric::Vw => self.number.get() * context.viewport_size.width / 100.0,
573 SizeMetric::Vh => self.number.get() * context.viewport_size.height / 100.0,
574 SizeMetric::Vmin => {
576 let min_dimension = context
577 .viewport_size
578 .width
579 .min(context.viewport_size.height);
580 self.number.get() * min_dimension / 100.0
581 }
582 SizeMetric::Vmax => {
584 let max_dimension = context
585 .viewport_size
586 .width
587 .max(context.viewport_size.height);
588 self.number.get() * max_dimension / 100.0
589 }
590
591 SizeMetric::Percent => {
593 let reference = match property_context {
594 PropertyContext::FontSize => context.parent_font_size,
596
597 PropertyContext::Width => context.containing_block_size.width,
599
600 PropertyContext::Height => context.containing_block_size.height,
602
603 PropertyContext::Margin | PropertyContext::Padding => {
606 context.containing_block_size.width
607 }
608
609 PropertyContext::BorderWidth => 0.0,
612
613 PropertyContext::BorderRadius => {
617 context.element_size.map(|s| s.width).unwrap_or(0.0)
618 }
619
620 PropertyContext::Transform => {
622 context.element_size.map(|s| s.width).unwrap_or(0.0)
623 }
624
625 PropertyContext::Other => context.containing_block_size.width,
627 };
628
629 NormalizedPercentage::from_unnormalized(self.number.get()).resolve(reference)
630 }
631 }
632 }
633}
634
635pub const THIN_BORDER_THICKNESS: PixelValue = PixelValue {
641 metric: SizeMetric::Px,
642 number: FloatValue { number: 1000 },
643};
644
645pub const MEDIUM_BORDER_THICKNESS: PixelValue = PixelValue {
647 metric: SizeMetric::Px,
648 number: FloatValue { number: 3000 },
649};
650
651pub const THICK_BORDER_THICKNESS: PixelValue = PixelValue {
653 metric: SizeMetric::Px,
654 number: FloatValue { number: 5000 },
655};
656
657#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
659#[repr(C)]
660pub struct PixelValueNoPercent {
661 pub inner: PixelValue,
662}
663
664impl PixelValueNoPercent {
665 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
666 self.inner.scale_for_dpi(scale_factor);
667 }
668}
669
670impl_option!(
671 PixelValueNoPercent,
672 OptionPixelValueNoPercent,
673 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
674);
675
676impl_option!(
677 PixelValue,
678 OptionPixelValue,
679 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
680);
681
682impl fmt::Display for PixelValueNoPercent {
683 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
684 write!(f, "{}", self.inner)
685 }
686}
687
688impl ::core::fmt::Debug for PixelValueNoPercent {
689 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
690 write!(f, "{}", self)
691 }
692}
693
694impl PixelValueNoPercent {
695 #[doc(hidden)]
701 #[inline]
702 pub fn to_pixels_internal(&self, em_resolve: f32) -> f32 {
703 self.inner.to_pixels_internal(0.0, em_resolve)
704 }
705
706 #[inline]
707 pub const fn zero() -> Self {
708 const ZERO_PXNP: PixelValueNoPercent = PixelValueNoPercent {
709 inner: PixelValue::zero(),
710 };
711 ZERO_PXNP
712 }
713}
714impl From<PixelValue> for PixelValueNoPercent {
715 fn from(e: PixelValue) -> Self {
716 Self { inner: e }
717 }
718}
719
720#[derive(Clone, PartialEq)]
721pub enum CssPixelValueParseError<'a> {
722 EmptyString,
723 NoValueGiven(&'a str, SizeMetric),
724 ValueParseErr(ParseFloatError, &'a str),
725 InvalidPixelValue(&'a str),
726}
727
728impl_debug_as_display!(CssPixelValueParseError<'a>);
729
730impl_display! { CssPixelValueParseError<'a>, {
731 EmptyString => format!("Missing [px / pt / em / %] value"),
732 NoValueGiven(input, metric) => format!("Expected floating-point pixel value, got: \"{}{}\"", input, metric),
733 ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
734 InvalidPixelValue(s) => format!("Invalid pixel value: \"{}\"", s),
735}}
736
737#[derive(Debug, Clone, PartialEq)]
739pub enum CssPixelValueParseErrorOwned {
740 EmptyString,
741 NoValueGiven(String, SizeMetric),
742 ValueParseErr(ParseFloatError, String),
743 InvalidPixelValue(String),
744}
745
746impl<'a> CssPixelValueParseError<'a> {
747 pub fn to_contained(&self) -> CssPixelValueParseErrorOwned {
748 match self {
749 CssPixelValueParseError::EmptyString => CssPixelValueParseErrorOwned::EmptyString,
750 CssPixelValueParseError::NoValueGiven(s, metric) => {
751 CssPixelValueParseErrorOwned::NoValueGiven(s.to_string(), *metric)
752 }
753 CssPixelValueParseError::ValueParseErr(err, s) => {
754 CssPixelValueParseErrorOwned::ValueParseErr(err.clone(), s.to_string())
755 }
756 CssPixelValueParseError::InvalidPixelValue(s) => {
757 CssPixelValueParseErrorOwned::InvalidPixelValue(s.to_string())
758 }
759 }
760 }
761}
762
763impl CssPixelValueParseErrorOwned {
764 pub fn to_shared<'a>(&'a self) -> CssPixelValueParseError<'a> {
765 match self {
766 CssPixelValueParseErrorOwned::EmptyString => CssPixelValueParseError::EmptyString,
767 CssPixelValueParseErrorOwned::NoValueGiven(s, metric) => {
768 CssPixelValueParseError::NoValueGiven(s.as_str(), *metric)
769 }
770 CssPixelValueParseErrorOwned::ValueParseErr(err, s) => {
771 CssPixelValueParseError::ValueParseErr(err.clone(), s.as_str())
772 }
773 CssPixelValueParseErrorOwned::InvalidPixelValue(s) => {
774 CssPixelValueParseError::InvalidPixelValue(s.as_str())
775 }
776 }
777 }
778}
779
780fn parse_pixel_value_inner<'a>(
782 input: &'a str,
783 match_values: &[(&'static str, SizeMetric)],
784) -> Result<PixelValue, CssPixelValueParseError<'a>> {
785 let input = input.trim();
786
787 if input.is_empty() {
788 return Err(CssPixelValueParseError::EmptyString);
789 }
790
791 for (match_val, metric) in match_values {
792 if input.ends_with(match_val) {
793 let value = &input[..input.len() - match_val.len()];
794 let value = value.trim();
795 if value.is_empty() {
796 return Err(CssPixelValueParseError::NoValueGiven(input, *metric));
797 }
798 match value.parse::<f32>() {
799 Ok(o) => {
800 return Ok(PixelValue::from_metric(*metric, o));
801 }
802 Err(e) => {
803 return Err(CssPixelValueParseError::ValueParseErr(e, value));
804 }
805 }
806 }
807 }
808
809 match input.trim().parse::<f32>() {
810 Ok(o) => Ok(PixelValue::px(o)),
811 Err(_) => Err(CssPixelValueParseError::InvalidPixelValue(input)),
812 }
813}
814
815pub fn parse_pixel_value<'a>(input: &'a str) -> Result<PixelValue, CssPixelValueParseError<'a>> {
816 parse_pixel_value_inner(
817 input,
818 &[
819 ("px", SizeMetric::Px),
820 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
822 ("pt", SizeMetric::Pt),
823 ("in", SizeMetric::In),
824 ("mm", SizeMetric::Mm),
825 ("cm", SizeMetric::Cm),
826 ("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
829 ("vh", SizeMetric::Vh),
830 ("%", SizeMetric::Percent),
831 ],
832 )
833}
834
835pub fn parse_pixel_value_no_percent<'a>(
836 input: &'a str,
837) -> Result<PixelValueNoPercent, CssPixelValueParseError<'a>> {
838 Ok(PixelValueNoPercent {
839 inner: parse_pixel_value_inner(
840 input,
841 &[
842 ("px", SizeMetric::Px),
843 ("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
845 ("pt", SizeMetric::Pt),
846 ("in", SizeMetric::In),
847 ("mm", SizeMetric::Mm),
848 ("cm", SizeMetric::Cm),
849 ("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
852 ("vh", SizeMetric::Vh),
853 ],
854 )?,
855 })
856}
857
858#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
859pub enum PixelValueWithAuto {
860 None,
861 Initial,
862 Inherit,
863 Auto,
864 Exact(PixelValue),
865}
866
867pub fn parse_pixel_value_with_auto<'a>(
869 input: &'a str,
870) -> Result<PixelValueWithAuto, CssPixelValueParseError<'a>> {
871 let input = input.trim();
872 match input {
873 "none" => Ok(PixelValueWithAuto::None),
874 "initial" => Ok(PixelValueWithAuto::Initial),
875 "inherit" => Ok(PixelValueWithAuto::Inherit),
876 "auto" => Ok(PixelValueWithAuto::Auto),
877 e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
878 }
879}
880
881#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
890#[repr(C)]
891pub enum SystemMetricRef {
892 ButtonRadius,
894 ButtonPaddingHorizontal,
896 ButtonPaddingVertical,
898 ButtonBorderWidth,
900 TitlebarHeight,
902 TitlebarButtonWidth,
904 TitlebarPadding,
906 SafeAreaTop,
908 SafeAreaBottom,
910 SafeAreaLeft,
912 SafeAreaRight,
914}
915
916impl Default for SystemMetricRef {
917 fn default() -> Self {
918 SystemMetricRef::ButtonRadius
919 }
920}
921
922impl SystemMetricRef {
923 pub fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
925 match self {
926 SystemMetricRef::ButtonRadius => metrics.corner_radius.as_option().copied(),
927 SystemMetricRef::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
928 SystemMetricRef::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
929 SystemMetricRef::ButtonBorderWidth => metrics.border_width.as_option().copied(),
930 SystemMetricRef::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
931 SystemMetricRef::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
932 SystemMetricRef::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
933 SystemMetricRef::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
934 SystemMetricRef::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
935 SystemMetricRef::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
936 SystemMetricRef::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
937 }
938 }
939
940 pub fn as_css_str(&self) -> &'static str {
942 match self {
943 SystemMetricRef::ButtonRadius => "system:button-radius",
944 SystemMetricRef::ButtonPaddingHorizontal => "system:button-padding-horizontal",
945 SystemMetricRef::ButtonPaddingVertical => "system:button-padding-vertical",
946 SystemMetricRef::ButtonBorderWidth => "system:button-border-width",
947 SystemMetricRef::TitlebarHeight => "system:titlebar-height",
948 SystemMetricRef::TitlebarButtonWidth => "system:titlebar-button-width",
949 SystemMetricRef::TitlebarPadding => "system:titlebar-padding",
950 SystemMetricRef::SafeAreaTop => "system:safe-area-top",
951 SystemMetricRef::SafeAreaBottom => "system:safe-area-bottom",
952 SystemMetricRef::SafeAreaLeft => "system:safe-area-left",
953 SystemMetricRef::SafeAreaRight => "system:safe-area-right",
954 }
955 }
956
957 pub fn from_css_str(s: &str) -> Option<Self> {
959 match s {
960 "button-radius" => Some(SystemMetricRef::ButtonRadius),
961 "button-padding-horizontal" => Some(SystemMetricRef::ButtonPaddingHorizontal),
962 "button-padding-vertical" => Some(SystemMetricRef::ButtonPaddingVertical),
963 "button-border-width" => Some(SystemMetricRef::ButtonBorderWidth),
964 "titlebar-height" => Some(SystemMetricRef::TitlebarHeight),
965 "titlebar-button-width" => Some(SystemMetricRef::TitlebarButtonWidth),
966 "titlebar-padding" => Some(SystemMetricRef::TitlebarPadding),
967 "safe-area-top" => Some(SystemMetricRef::SafeAreaTop),
968 "safe-area-bottom" => Some(SystemMetricRef::SafeAreaBottom),
969 "safe-area-left" => Some(SystemMetricRef::SafeAreaLeft),
970 "safe-area-right" => Some(SystemMetricRef::SafeAreaRight),
971 _ => None,
972 }
973 }
974}
975
976impl fmt::Display for SystemMetricRef {
977 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
978 write!(f, "{}", self.as_css_str())
979 }
980}
981
982impl FormatAsCssValue for SystemMetricRef {
983 fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
984 write!(f, "{}", self.as_css_str())
985 }
986}
987
988#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
993#[repr(C, u8)]
994pub enum PixelValueOrSystem {
995 Value(PixelValue),
997 System(SystemMetricRef),
999}
1000
1001impl Default for PixelValueOrSystem {
1002 fn default() -> Self {
1003 PixelValueOrSystem::Value(PixelValue::zero())
1004 }
1005}
1006
1007impl From<PixelValue> for PixelValueOrSystem {
1008 fn from(value: PixelValue) -> Self {
1009 PixelValueOrSystem::Value(value)
1010 }
1011}
1012
1013impl PixelValueOrSystem {
1014 pub const fn value(v: PixelValue) -> Self {
1016 PixelValueOrSystem::Value(v)
1017 }
1018
1019 pub const fn system(s: SystemMetricRef) -> Self {
1021 PixelValueOrSystem::System(s)
1022 }
1023
1024 pub fn resolve(&self, system_metrics: &crate::system::SystemMetrics, fallback: PixelValue) -> PixelValue {
1027 match self {
1028 PixelValueOrSystem::Value(v) => *v,
1029 PixelValueOrSystem::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
1030 }
1031 }
1032
1033 pub fn to_pixel_value_with_fallback(&self, fallback: PixelValue) -> PixelValue {
1035 match self {
1036 PixelValueOrSystem::Value(v) => *v,
1037 PixelValueOrSystem::System(_) => fallback,
1038 }
1039 }
1040
1041 pub fn to_pixel_value_default(&self) -> PixelValue {
1043 self.to_pixel_value_with_fallback(PixelValue::zero())
1044 }
1045}
1046
1047impl fmt::Display for PixelValueOrSystem {
1048 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1049 match self {
1050 PixelValueOrSystem::Value(v) => write!(f, "{}", v),
1051 PixelValueOrSystem::System(s) => write!(f, "{}", s),
1052 }
1053 }
1054}
1055
1056impl FormatAsCssValue for PixelValueOrSystem {
1057 fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
1058 match self {
1059 PixelValueOrSystem::Value(v) => v.format_as_css_value(f),
1060 PixelValueOrSystem::System(s) => s.format_as_css_value(f),
1061 }
1062 }
1063}
1064
1065#[cfg(feature = "parser")]
1069pub fn parse_pixel_value_or_system<'a>(
1070 input: &'a str,
1071) -> Result<PixelValueOrSystem, CssPixelValueParseError<'a>> {
1072 let input = input.trim();
1073
1074 if let Some(metric_name) = input.strip_prefix("system:") {
1076 if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
1077 return Ok(PixelValueOrSystem::System(metric_ref));
1078 } else {
1079 return Err(CssPixelValueParseError::InvalidPixelValue(input));
1080 }
1081 }
1082
1083 Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
1085}
1086
1087#[cfg(all(test, feature = "parser"))]
1088mod tests {
1089 use super::*;
1090
1091 #[test]
1092 fn test_parse_pixel_value() {
1093 assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
1094 assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
1095 assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
1096 assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
1097 assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
1098 assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
1099 assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
1100 assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
1101 assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
1102 }
1103
1104 #[test]
1105 fn test_resolve_with_context_em() {
1106 let context = ResolutionContext {
1108 element_font_size: 32.0,
1109 parent_font_size: 16.0,
1110 ..Default::default()
1111 };
1112
1113 let margin = PixelValue::em(0.67);
1115 assert!(
1116 (margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
1117 );
1118
1119 let font_size = PixelValue::em(2.0);
1121 assert_eq!(
1122 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1123 32.0
1124 );
1125 }
1126
1127 #[test]
1128 fn test_resolve_with_context_rem() {
1129 let context = ResolutionContext {
1131 element_font_size: 32.0,
1132 parent_font_size: 16.0,
1133 root_font_size: 18.0,
1134 ..Default::default()
1135 };
1136
1137 let margin = PixelValue::rem(2.0);
1139 assert_eq!(
1140 margin.resolve_with_context(&context, PropertyContext::Margin),
1141 36.0
1142 );
1143
1144 let font_size = PixelValue::rem(1.5);
1145 assert_eq!(
1146 font_size.resolve_with_context(&context, PropertyContext::FontSize),
1147 27.0
1148 );
1149 }
1150
1151 #[test]
1152 fn test_resolve_with_context_percent_margin() {
1153 let context = ResolutionContext {
1155 element_font_size: 16.0,
1156 parent_font_size: 16.0,
1157 root_font_size: 16.0,
1158 containing_block_size: PhysicalSize::new(800.0, 600.0),
1159 element_size: None,
1160 viewport_size: PhysicalSize::new(1920.0, 1080.0),
1161 };
1162
1163 let margin = PixelValue::percent(10.0); assert_eq!(
1165 margin.resolve_with_context(&context, PropertyContext::Margin),
1166 80.0
1167 ); }
1169
1170 #[test]
1171 fn test_parse_pixel_value_no_percent() {
1172 assert_eq!(
1173 parse_pixel_value_no_percent("10px").unwrap().inner,
1174 PixelValue::px(10.0)
1175 );
1176 assert!(parse_pixel_value_no_percent("50%").is_err());
1177 }
1178
1179 #[test]
1180 fn test_parse_pixel_value_with_auto() {
1181 assert_eq!(
1182 parse_pixel_value_with_auto("10px").unwrap(),
1183 PixelValueWithAuto::Exact(PixelValue::px(10.0))
1184 );
1185 assert_eq!(
1186 parse_pixel_value_with_auto("auto").unwrap(),
1187 PixelValueWithAuto::Auto
1188 );
1189 assert_eq!(
1190 parse_pixel_value_with_auto("initial").unwrap(),
1191 PixelValueWithAuto::Initial
1192 );
1193 assert_eq!(
1194 parse_pixel_value_with_auto("inherit").unwrap(),
1195 PixelValueWithAuto::Inherit
1196 );
1197 assert_eq!(
1198 parse_pixel_value_with_auto("none").unwrap(),
1199 PixelValueWithAuto::None
1200 );
1201 }
1202
1203 #[test]
1204 fn test_parse_pixel_value_errors() {
1205 assert!(parse_pixel_value("").is_err());
1206 assert!(parse_pixel_value("10").is_ok()); assert!(parse_pixel_value("10 px").is_ok()); assert!(parse_pixel_value("px").is_err());
1211 assert!(parse_pixel_value("ten-px").is_err());
1212 }
1213}