Skip to main content

whisker_css/data_type/
length.rs

1//! `<length>` — a distance.
2//!
3//! Lynx reference: <https://lynxjs.org/api/css/data-type/length.html>
4//!
5//! Lynx supports a subset of CSS length units:
6//!
7//! | Variant | CSS | Reference |
8//! |---|---|---|
9//! | [`Length::Px`]  | `px`  | iOS points / Android dp |
10//! | [`Length::Rpx`] | `rpx` | Lynx-specific: `750rpx` = device width |
11//! | [`Length::Ppx`] | `ppx` | Physical pixels (device resolution) |
12//! | [`Length::Em`]  | `em`  | The element's computed `font-size` |
13//! | [`Length::Rem`] | `rem` | The root element's computed `font-size` |
14//! | [`Length::Vh`]  | `vh`  | 1% of viewport height |
15//! | [`Length::Vw`]  | `vw`  | 1% of viewport width |
16//! | [`Length::Zero`]| `0`   | The unitless zero — only zero is allowed without a unit |
17//!
18//! The web units `cm`, `mm`, `in`, `pt`, `pc`, `ch`, `ex`, `lh`,
19//! `rlh` are **not** part of Lynx and are intentionally absent from
20//! this enum.
21
22use core::fmt;
23
24use crate::to_css::{write_number, ToCss};
25
26/// A CSS `<length>` value.
27#[derive(Copy, Clone, Debug, PartialEq)]
28pub enum Length {
29    /// Logical pixels (`px`). Maps to iOS points and Android dp.
30    Px(f32),
31    /// Lynx-specific responsive pixel (`rpx`). `750rpx` equals the
32    /// device's screen width regardless of pixel density.
33    Rpx(f32),
34    /// Physical pixels (`ppx`). One device pixel.
35    Ppx(f32),
36    /// Em (`em`) — relative to the element's computed `font-size`.
37    Em(f32),
38    /// Root em (`rem`) — relative to the root element's
39    /// computed `font-size`.
40    Rem(f32),
41    /// Viewport height (`vh`) — 1% of the viewport's height.
42    Vh(f32),
43    /// Viewport width (`vw`) — 1% of the viewport's width.
44    Vw(f32),
45    /// The unitless zero. CSS allows `0` (and only `0`) without a
46    /// unit; this variant covers that case so a non-zero unit-less
47    /// length is unrepresentable.
48    Zero,
49}
50
51impl Length {
52    /// Returns `true` when the length is exactly zero, regardless of
53    /// the unit chosen at construction.
54    pub fn is_zero(self) -> bool {
55        match self {
56            Length::Zero => true,
57            Length::Px(v)
58            | Length::Rpx(v)
59            | Length::Ppx(v)
60            | Length::Em(v)
61            | Length::Rem(v)
62            | Length::Vh(v)
63            | Length::Vw(v) => v == 0.0,
64        }
65    }
66}
67
68impl ToCss for Length {
69    fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
70        let (v, unit) = match *self {
71            Length::Zero => return dest.write_char('0'),
72            Length::Px(v) => (v, "px"),
73            Length::Rpx(v) => (v, "rpx"),
74            Length::Ppx(v) => (v, "ppx"),
75            Length::Em(v) => (v, "em"),
76            Length::Rem(v) => (v, "rem"),
77            Length::Vh(v) => (v, "vh"),
78            Length::Vw(v) => (v, "vw"),
79        };
80        write_number(dest, v)?;
81        dest.write_str(unit)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn each_unit_serializes() {
91        assert_eq!(Length::Px(8.0).to_css_string(), "8px");
92        assert_eq!(Length::Rpx(750.0).to_css_string(), "750rpx");
93        assert_eq!(Length::Ppx(2.0).to_css_string(), "2ppx");
94        assert_eq!(Length::Em(1.5).to_css_string(), "1.5em");
95        assert_eq!(Length::Rem(1.0).to_css_string(), "1rem");
96        assert_eq!(Length::Vh(50.0).to_css_string(), "50vh");
97        assert_eq!(Length::Vw(100.0).to_css_string(), "100vw");
98    }
99
100    #[test]
101    fn zero_serializes_unitless() {
102        assert_eq!(Length::Zero.to_css_string(), "0");
103    }
104
105    #[test]
106    fn fractional_values_keep_decimal() {
107        assert_eq!(Length::Px(0.5).to_css_string(), "0.5px");
108        assert_eq!(Length::Px(-1.25).to_css_string(), "-1.25px");
109    }
110
111    #[test]
112    fn is_zero_detects_all_variants() {
113        assert!(Length::Zero.is_zero());
114        assert!(Length::Px(0.0).is_zero());
115        assert!(Length::Vh(0.0).is_zero());
116        assert!(!Length::Px(0.1).is_zero());
117        assert!(!Length::Rpx(1.0).is_zero());
118    }
119}