Skip to main content

topcoat_view/css/
length.rs

1use std::fmt::{self, Display};
2
3use topcoat_core::context::Cx;
4
5use crate::{AttributeValueViewParts, PartsWriter};
6
7/// A CSS length unit.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum LengthUnit {
10    // Absolute lengths.
11    /// CSS pixels (`px`), 1/96th of an inch.
12    Px,
13    /// Centimeters (`cm`).
14    Cm,
15    /// Millimeters (`mm`), 1/10th of a centimeter.
16    Mm,
17    /// Quarter-millimeters (`Q`), 1/40th of a centimeter.
18    Q,
19    /// Inches (`in`), 2.54 centimeters.
20    In,
21    /// Picas (`pc`), 1/6th of an inch.
22    Pc,
23    /// Points (`pt`), 1/72nd of an inch.
24    Pt,
25
26    // Font-relative lengths.
27    /// The element's font size (`em`).
28    Em,
29    /// The root element's font size (`rem`).
30    Rem,
31    /// The x-height of the element's font (`ex`).
32    Ex,
33    /// The x-height of the root element's font (`rex`).
34    Rex,
35    /// The cap height of the element's font (`cap`).
36    Cap,
37    /// The cap height of the root element's font (`rcap`).
38    Rcap,
39    /// The advance of the `0` glyph in the element's font (`ch`).
40    Ch,
41    /// The advance of the `0` glyph in the root element's font (`rch`).
42    Rch,
43    /// The advance of a fullwidth glyph in the element's font (`ic`).
44    Ic,
45    /// The advance of a fullwidth glyph in the root element's font (`ric`).
46    Ric,
47    /// The line height of the element (`lh`).
48    Lh,
49    /// The line height of the root element (`rlh`).
50    Rlh,
51
52    // Viewport-percentage lengths.
53    /// 1% of the viewport's width (`vw`).
54    Vw,
55    /// 1% of the viewport's height (`vh`).
56    Vh,
57    /// 1% of the viewport's inline-axis size (`vi`).
58    Vi,
59    /// 1% of the viewport's block-axis size (`vb`).
60    Vb,
61    /// 1% of the viewport's smaller dimension (`vmin`).
62    Vmin,
63    /// 1% of the viewport's larger dimension (`vmax`).
64    Vmax,
65
66    // Small-viewport-percentage lengths.
67    /// 1% of the small viewport's width (`svw`).
68    Svw,
69    /// 1% of the small viewport's height (`svh`).
70    Svh,
71    /// 1% of the small viewport's inline-axis size (`svi`).
72    Svi,
73    /// 1% of the small viewport's block-axis size (`svb`).
74    Svb,
75    /// 1% of the small viewport's smaller dimension (`svmin`).
76    Svmin,
77    /// 1% of the small viewport's larger dimension (`svmax`).
78    Svmax,
79
80    // Large-viewport-percentage lengths.
81    /// 1% of the large viewport's width (`lvw`).
82    Lvw,
83    /// 1% of the large viewport's height (`lvh`).
84    Lvh,
85    /// 1% of the large viewport's inline-axis size (`lvi`).
86    Lvi,
87    /// 1% of the large viewport's block-axis size (`lvb`).
88    Lvb,
89    /// 1% of the large viewport's smaller dimension (`lvmin`).
90    Lvmin,
91    /// 1% of the large viewport's larger dimension (`lvmax`).
92    Lvmax,
93
94    // Dynamic-viewport-percentage lengths.
95    /// 1% of the dynamic viewport's width (`dvw`).
96    Dvw,
97    /// 1% of the dynamic viewport's height (`dvh`).
98    Dvh,
99    /// 1% of the dynamic viewport's inline-axis size (`dvi`).
100    Dvi,
101    /// 1% of the dynamic viewport's block-axis size (`dvb`).
102    Dvb,
103    /// 1% of the dynamic viewport's smaller dimension (`dvmin`).
104    Dvmin,
105    /// 1% of the dynamic viewport's larger dimension (`dvmax`).
106    Dvmax,
107
108    // Container-query lengths.
109    /// 1% of the query container's width (`cqw`).
110    Cqw,
111    /// 1% of the query container's height (`cqh`).
112    Cqh,
113    /// 1% of the query container's inline size (`cqi`).
114    Cqi,
115    /// 1% of the query container's block size (`cqb`).
116    Cqb,
117    /// 1% of the query container's smaller dimension (`cqmin`).
118    Cqmin,
119    /// 1% of the query container's larger dimension (`cqmax`).
120    Cqmax,
121}
122
123impl LengthUnit {
124    /// Returns the CSS suffix for this unit, such as `"px"` or `"rem"`.
125    #[must_use]
126    pub const fn as_str(self) -> &'static str {
127        match self {
128            Self::Px => "px",
129            Self::Cm => "cm",
130            Self::Mm => "mm",
131            Self::Q => "Q",
132            Self::In => "in",
133            Self::Pc => "pc",
134            Self::Pt => "pt",
135            Self::Em => "em",
136            Self::Rem => "rem",
137            Self::Ex => "ex",
138            Self::Rex => "rex",
139            Self::Cap => "cap",
140            Self::Rcap => "rcap",
141            Self::Ch => "ch",
142            Self::Rch => "rch",
143            Self::Ic => "ic",
144            Self::Ric => "ric",
145            Self::Lh => "lh",
146            Self::Rlh => "rlh",
147            Self::Vw => "vw",
148            Self::Vh => "vh",
149            Self::Vi => "vi",
150            Self::Vb => "vb",
151            Self::Vmin => "vmin",
152            Self::Vmax => "vmax",
153            Self::Svw => "svw",
154            Self::Svh => "svh",
155            Self::Svi => "svi",
156            Self::Svb => "svb",
157            Self::Svmin => "svmin",
158            Self::Svmax => "svmax",
159            Self::Lvw => "lvw",
160            Self::Lvh => "lvh",
161            Self::Lvi => "lvi",
162            Self::Lvb => "lvb",
163            Self::Lvmin => "lvmin",
164            Self::Lvmax => "lvmax",
165            Self::Dvw => "dvw",
166            Self::Dvh => "dvh",
167            Self::Dvi => "dvi",
168            Self::Dvb => "dvb",
169            Self::Dvmin => "dvmin",
170            Self::Dvmax => "dvmax",
171            Self::Cqw => "cqw",
172            Self::Cqh => "cqh",
173            Self::Cqi => "cqi",
174            Self::Cqb => "cqb",
175            Self::Cqmin => "cqmin",
176            Self::Cqmax => "cqmax",
177        }
178    }
179}
180
181impl Display for LengthUnit {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        f.write_str(self.as_str())
184    }
185}
186
187/// A CSS length: a numeric value paired with a [`LengthUnit`].
188///
189/// Construct one with a per-unit helper like [`Length::px`] or [`Length::rem`],
190/// or from a value and unit with [`Length::new`]. Plain numbers convert to a
191/// pixel length, so a `#[into]` component parameter accepts `size: 24` as a
192/// 24-pixel length.
193#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct Length {
195    value: f32,
196    unit: LengthUnit,
197}
198
199impl Length {
200    /// Creates a length from a numeric value and a unit.
201    #[must_use]
202    pub const fn new(value: f32, unit: LengthUnit) -> Self {
203        Self { value, unit }
204    }
205
206    /// Returns the numeric value of this length.
207    #[must_use]
208    pub const fn value(self) -> f32 {
209        self.value
210    }
211
212    /// Returns the unit of this length.
213    #[must_use]
214    pub const fn unit(self) -> LengthUnit {
215        self.unit
216    }
217
218    /// A length in `px` units.
219    #[must_use]
220    pub const fn px(value: f32) -> Self {
221        Self::new(value, LengthUnit::Px)
222    }
223
224    /// A length in `cm` units.
225    #[must_use]
226    pub const fn cm(value: f32) -> Self {
227        Self::new(value, LengthUnit::Cm)
228    }
229
230    /// A length in `mm` units.
231    #[must_use]
232    pub const fn mm(value: f32) -> Self {
233        Self::new(value, LengthUnit::Mm)
234    }
235
236    /// A length in `Q` units.
237    #[must_use]
238    pub const fn q(value: f32) -> Self {
239        Self::new(value, LengthUnit::Q)
240    }
241
242    /// A length in `in` units.
243    #[must_use]
244    pub const fn r#in(value: f32) -> Self {
245        Self::new(value, LengthUnit::In)
246    }
247
248    /// A length in `pc` units.
249    #[must_use]
250    pub const fn pc(value: f32) -> Self {
251        Self::new(value, LengthUnit::Pc)
252    }
253
254    /// A length in `pt` units.
255    #[must_use]
256    pub const fn pt(value: f32) -> Self {
257        Self::new(value, LengthUnit::Pt)
258    }
259
260    /// A length in `em` units.
261    #[must_use]
262    pub const fn em(value: f32) -> Self {
263        Self::new(value, LengthUnit::Em)
264    }
265
266    /// A length in `rem` units.
267    #[must_use]
268    pub const fn rem(value: f32) -> Self {
269        Self::new(value, LengthUnit::Rem)
270    }
271
272    /// A length in `ex` units.
273    #[must_use]
274    pub const fn ex(value: f32) -> Self {
275        Self::new(value, LengthUnit::Ex)
276    }
277
278    /// A length in `rex` units.
279    #[must_use]
280    pub const fn rex(value: f32) -> Self {
281        Self::new(value, LengthUnit::Rex)
282    }
283
284    /// A length in `cap` units.
285    #[must_use]
286    pub const fn cap(value: f32) -> Self {
287        Self::new(value, LengthUnit::Cap)
288    }
289
290    /// A length in `rcap` units.
291    #[must_use]
292    pub const fn rcap(value: f32) -> Self {
293        Self::new(value, LengthUnit::Rcap)
294    }
295
296    /// A length in `ch` units.
297    #[must_use]
298    pub const fn ch(value: f32) -> Self {
299        Self::new(value, LengthUnit::Ch)
300    }
301
302    /// A length in `rch` units.
303    #[must_use]
304    pub const fn rch(value: f32) -> Self {
305        Self::new(value, LengthUnit::Rch)
306    }
307
308    /// A length in `ic` units.
309    #[must_use]
310    pub const fn ic(value: f32) -> Self {
311        Self::new(value, LengthUnit::Ic)
312    }
313
314    /// A length in `ric` units.
315    #[must_use]
316    pub const fn ric(value: f32) -> Self {
317        Self::new(value, LengthUnit::Ric)
318    }
319
320    /// A length in `lh` units.
321    #[must_use]
322    pub const fn lh(value: f32) -> Self {
323        Self::new(value, LengthUnit::Lh)
324    }
325
326    /// A length in `rlh` units.
327    #[must_use]
328    pub const fn rlh(value: f32) -> Self {
329        Self::new(value, LengthUnit::Rlh)
330    }
331
332    /// A length in `vw` units.
333    #[must_use]
334    pub const fn vw(value: f32) -> Self {
335        Self::new(value, LengthUnit::Vw)
336    }
337
338    /// A length in `vh` units.
339    #[must_use]
340    pub const fn vh(value: f32) -> Self {
341        Self::new(value, LengthUnit::Vh)
342    }
343
344    /// A length in `vi` units.
345    #[must_use]
346    pub const fn vi(value: f32) -> Self {
347        Self::new(value, LengthUnit::Vi)
348    }
349
350    /// A length in `vb` units.
351    #[must_use]
352    pub const fn vb(value: f32) -> Self {
353        Self::new(value, LengthUnit::Vb)
354    }
355
356    /// A length in `vmin` units.
357    #[must_use]
358    pub const fn vmin(value: f32) -> Self {
359        Self::new(value, LengthUnit::Vmin)
360    }
361
362    /// A length in `vmax` units.
363    #[must_use]
364    pub const fn vmax(value: f32) -> Self {
365        Self::new(value, LengthUnit::Vmax)
366    }
367
368    /// A length in `svw` units.
369    #[must_use]
370    pub const fn svw(value: f32) -> Self {
371        Self::new(value, LengthUnit::Svw)
372    }
373
374    /// A length in `svh` units.
375    #[must_use]
376    pub const fn svh(value: f32) -> Self {
377        Self::new(value, LengthUnit::Svh)
378    }
379
380    /// A length in `svi` units.
381    #[must_use]
382    pub const fn svi(value: f32) -> Self {
383        Self::new(value, LengthUnit::Svi)
384    }
385
386    /// A length in `svb` units.
387    #[must_use]
388    pub const fn svb(value: f32) -> Self {
389        Self::new(value, LengthUnit::Svb)
390    }
391
392    /// A length in `svmin` units.
393    #[must_use]
394    pub const fn svmin(value: f32) -> Self {
395        Self::new(value, LengthUnit::Svmin)
396    }
397
398    /// A length in `svmax` units.
399    #[must_use]
400    pub const fn svmax(value: f32) -> Self {
401        Self::new(value, LengthUnit::Svmax)
402    }
403
404    /// A length in `lvw` units.
405    #[must_use]
406    pub const fn lvw(value: f32) -> Self {
407        Self::new(value, LengthUnit::Lvw)
408    }
409
410    /// A length in `lvh` units.
411    #[must_use]
412    pub const fn lvh(value: f32) -> Self {
413        Self::new(value, LengthUnit::Lvh)
414    }
415
416    /// A length in `lvi` units.
417    #[must_use]
418    pub const fn lvi(value: f32) -> Self {
419        Self::new(value, LengthUnit::Lvi)
420    }
421
422    /// A length in `lvb` units.
423    #[must_use]
424    pub const fn lvb(value: f32) -> Self {
425        Self::new(value, LengthUnit::Lvb)
426    }
427
428    /// A length in `lvmin` units.
429    #[must_use]
430    pub const fn lvmin(value: f32) -> Self {
431        Self::new(value, LengthUnit::Lvmin)
432    }
433
434    /// A length in `lvmax` units.
435    #[must_use]
436    pub const fn lvmax(value: f32) -> Self {
437        Self::new(value, LengthUnit::Lvmax)
438    }
439
440    /// A length in `dvw` units.
441    #[must_use]
442    pub const fn dvw(value: f32) -> Self {
443        Self::new(value, LengthUnit::Dvw)
444    }
445
446    /// A length in `dvh` units.
447    #[must_use]
448    pub const fn dvh(value: f32) -> Self {
449        Self::new(value, LengthUnit::Dvh)
450    }
451
452    /// A length in `dvi` units.
453    #[must_use]
454    pub const fn dvi(value: f32) -> Self {
455        Self::new(value, LengthUnit::Dvi)
456    }
457
458    /// A length in `dvb` units.
459    #[must_use]
460    pub const fn dvb(value: f32) -> Self {
461        Self::new(value, LengthUnit::Dvb)
462    }
463
464    /// A length in `dvmin` units.
465    #[must_use]
466    pub const fn dvmin(value: f32) -> Self {
467        Self::new(value, LengthUnit::Dvmin)
468    }
469
470    /// A length in `dvmax` units.
471    #[must_use]
472    pub const fn dvmax(value: f32) -> Self {
473        Self::new(value, LengthUnit::Dvmax)
474    }
475
476    /// A length in `cqw` units.
477    #[must_use]
478    pub const fn cqw(value: f32) -> Self {
479        Self::new(value, LengthUnit::Cqw)
480    }
481
482    /// A length in `cqh` units.
483    #[must_use]
484    pub const fn cqh(value: f32) -> Self {
485        Self::new(value, LengthUnit::Cqh)
486    }
487
488    /// A length in `cqi` units.
489    #[must_use]
490    pub const fn cqi(value: f32) -> Self {
491        Self::new(value, LengthUnit::Cqi)
492    }
493
494    /// A length in `cqb` units.
495    #[must_use]
496    pub const fn cqb(value: f32) -> Self {
497        Self::new(value, LengthUnit::Cqb)
498    }
499
500    /// A length in `cqmin` units.
501    #[must_use]
502    pub const fn cqmin(value: f32) -> Self {
503        Self::new(value, LengthUnit::Cqmin)
504    }
505
506    /// A length in `cqmax` units.
507    #[must_use]
508    pub const fn cqmax(value: f32) -> Self {
509        Self::new(value, LengthUnit::Cqmax)
510    }
511}
512
513impl Display for Length {
514    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
515        write!(f, "{}", self.value)?;
516        f.write_str(self.unit.as_str())?;
517        Ok(())
518    }
519}
520
521impl From<u16> for Length {
522    fn from(value: u16) -> Self {
523        Self::px(f32::from(value))
524    }
525}
526
527impl From<f32> for Length {
528    fn from(value: f32) -> Self {
529        Self::px(value)
530    }
531}
532
533impl AttributeValueViewParts for Length {
534    fn attribute_present(&self) -> bool {
535        true
536    }
537
538    fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
539        parts.push_f32(self.value);
540        parts.push_static_str_unescaped(self.unit.as_str());
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547    use crate::internal::Builder;
548
549    /// Every unit constructor paired with its rendered form. The numeric value
550    /// is the same across cases so each assertion focuses on the unit suffix.
551    const UNITS: &[(Length, &str)] = &[
552        (Length::px(2.0), "2px"),
553        (Length::cm(2.0), "2cm"),
554        (Length::mm(2.0), "2mm"),
555        (Length::q(2.0), "2Q"),
556        (Length::r#in(2.0), "2in"),
557        (Length::pc(2.0), "2pc"),
558        (Length::pt(2.0), "2pt"),
559        (Length::em(2.0), "2em"),
560        (Length::rem(2.0), "2rem"),
561        (Length::ex(2.0), "2ex"),
562        (Length::rex(2.0), "2rex"),
563        (Length::cap(2.0), "2cap"),
564        (Length::rcap(2.0), "2rcap"),
565        (Length::ch(2.0), "2ch"),
566        (Length::rch(2.0), "2rch"),
567        (Length::ic(2.0), "2ic"),
568        (Length::ric(2.0), "2ric"),
569        (Length::lh(2.0), "2lh"),
570        (Length::rlh(2.0), "2rlh"),
571        (Length::vw(2.0), "2vw"),
572        (Length::vh(2.0), "2vh"),
573        (Length::vi(2.0), "2vi"),
574        (Length::vb(2.0), "2vb"),
575        (Length::vmin(2.0), "2vmin"),
576        (Length::vmax(2.0), "2vmax"),
577        (Length::svw(2.0), "2svw"),
578        (Length::svh(2.0), "2svh"),
579        (Length::svi(2.0), "2svi"),
580        (Length::svb(2.0), "2svb"),
581        (Length::svmin(2.0), "2svmin"),
582        (Length::svmax(2.0), "2svmax"),
583        (Length::lvw(2.0), "2lvw"),
584        (Length::lvh(2.0), "2lvh"),
585        (Length::lvi(2.0), "2lvi"),
586        (Length::lvb(2.0), "2lvb"),
587        (Length::lvmin(2.0), "2lvmin"),
588        (Length::lvmax(2.0), "2lvmax"),
589        (Length::dvw(2.0), "2dvw"),
590        (Length::dvh(2.0), "2dvh"),
591        (Length::dvi(2.0), "2dvi"),
592        (Length::dvb(2.0), "2dvb"),
593        (Length::dvmin(2.0), "2dvmin"),
594        (Length::dvmax(2.0), "2dvmax"),
595        (Length::cqw(2.0), "2cqw"),
596        (Length::cqh(2.0), "2cqh"),
597        (Length::cqi(2.0), "2cqi"),
598        (Length::cqb(2.0), "2cqb"),
599        (Length::cqmin(2.0), "2cqmin"),
600        (Length::cqmax(2.0), "2cqmax"),
601    ];
602
603    fn render(value: impl AttributeValueViewParts) -> String {
604        let cx = Cx::default();
605        Builder::build(&cx, |b| b.attribute_value(value)).render(&cx)
606    }
607
608    #[test]
609    fn displays_with_unit() {
610        for &(length, expected) in UNITS {
611            assert_eq!(length.to_string(), expected, "Display for {length:?}");
612        }
613    }
614
615    #[test]
616    fn renders_view_parts_with_unit() {
617        for &(length, expected) in UNITS {
618            assert_eq!(render(length), expected, "view parts for {length:?}");
619        }
620    }
621
622    #[test]
623    fn unit_reports_its_suffix() {
624        assert_eq!(LengthUnit::Px.as_str(), "px");
625        assert_eq!(LengthUnit::Q.as_str(), "Q");
626        assert_eq!(LengthUnit::In.as_str(), "in");
627        assert_eq!(LengthUnit::Cqmax.as_str(), "cqmax");
628        assert_eq!(LengthUnit::Rem.to_string(), "rem");
629    }
630
631    #[test]
632    fn getters_expose_value_and_unit() {
633        let length = Length::rem(1.5);
634        assert_eq!(length.unit(), LengthUnit::Rem);
635        // Rebuilding from the getters yields an equal length, which exercises
636        // `value` without comparing floats directly.
637        assert_eq!(Length::new(length.value(), length.unit()), length);
638    }
639
640    #[test]
641    fn formats_fractional_and_negative_values() {
642        assert_eq!(Length::px(1.5).to_string(), "1.5px");
643        assert_eq!(render(Length::px(1.5)), "1.5px");
644        assert_eq!(Length::em(-0.5).to_string(), "-0.5em");
645        assert_eq!(render(Length::em(-0.5)), "-0.5em");
646        assert_eq!(Length::rem(0.0).to_string(), "0rem");
647        assert_eq!(render(Length::rem(0.0)), "0rem");
648    }
649
650    #[test]
651    fn formats_whole_values_without_a_trailing_zero() {
652        assert_eq!(Length::px(16.0).to_string(), "16px");
653        assert_eq!(render(Length::px(16.0)), "16px");
654    }
655
656    #[test]
657    fn numbers_convert_to_pixels() {
658        assert_eq!(Length::from(24u16), Length::px(24.0));
659        assert_eq!(Length::from(1.5f32), Length::px(1.5));
660    }
661
662    #[test]
663    fn attribute_is_always_present() {
664        assert!(Length::px(0.0).attribute_present());
665        assert!(Length::cqmax(1.0).attribute_present());
666    }
667}