Skip to main content

topcoat_view/
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        let mut buffer = zmij::Buffer::new();
516        f.write_str(buffer.format(self.value))?;
517        f.write_str(self.unit.as_str())?;
518        Ok(())
519    }
520}
521
522impl From<u16> for Length {
523    fn from(value: u16) -> Self {
524        Self::px(f32::from(value))
525    }
526}
527
528impl From<f32> for Length {
529    fn from(value: f32) -> Self {
530        Self::px(value)
531    }
532}
533
534impl AttributeValueViewParts for Length {
535    fn attribute_present(&self) -> bool {
536        true
537    }
538
539    fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
540        parts.push_f32(self.value);
541        parts.push_str_unescaped(self.unit.as_str());
542    }
543}
544
545#[cfg(test)]
546mod tests {
547    use topcoat_core::context::Cx;
548
549    use super::*;
550    use crate::{HtmlContext, View, ViewParts};
551
552    /// Every unit constructor paired with its rendered form. The numeric value
553    /// is the same across cases so each assertion focuses on the unit suffix.
554    const UNITS: &[(Length, &str)] = &[
555        (Length::px(2.0), "2.0px"),
556        (Length::cm(2.0), "2.0cm"),
557        (Length::mm(2.0), "2.0mm"),
558        (Length::q(2.0), "2.0Q"),
559        (Length::r#in(2.0), "2.0in"),
560        (Length::pc(2.0), "2.0pc"),
561        (Length::pt(2.0), "2.0pt"),
562        (Length::em(2.0), "2.0em"),
563        (Length::rem(2.0), "2.0rem"),
564        (Length::ex(2.0), "2.0ex"),
565        (Length::rex(2.0), "2.0rex"),
566        (Length::cap(2.0), "2.0cap"),
567        (Length::rcap(2.0), "2.0rcap"),
568        (Length::ch(2.0), "2.0ch"),
569        (Length::rch(2.0), "2.0rch"),
570        (Length::ic(2.0), "2.0ic"),
571        (Length::ric(2.0), "2.0ric"),
572        (Length::lh(2.0), "2.0lh"),
573        (Length::rlh(2.0), "2.0rlh"),
574        (Length::vw(2.0), "2.0vw"),
575        (Length::vh(2.0), "2.0vh"),
576        (Length::vi(2.0), "2.0vi"),
577        (Length::vb(2.0), "2.0vb"),
578        (Length::vmin(2.0), "2.0vmin"),
579        (Length::vmax(2.0), "2.0vmax"),
580        (Length::svw(2.0), "2.0svw"),
581        (Length::svh(2.0), "2.0svh"),
582        (Length::svi(2.0), "2.0svi"),
583        (Length::svb(2.0), "2.0svb"),
584        (Length::svmin(2.0), "2.0svmin"),
585        (Length::svmax(2.0), "2.0svmax"),
586        (Length::lvw(2.0), "2.0lvw"),
587        (Length::lvh(2.0), "2.0lvh"),
588        (Length::lvi(2.0), "2.0lvi"),
589        (Length::lvb(2.0), "2.0lvb"),
590        (Length::lvmin(2.0), "2.0lvmin"),
591        (Length::lvmax(2.0), "2.0lvmax"),
592        (Length::dvw(2.0), "2.0dvw"),
593        (Length::dvh(2.0), "2.0dvh"),
594        (Length::dvi(2.0), "2.0dvi"),
595        (Length::dvb(2.0), "2.0dvb"),
596        (Length::dvmin(2.0), "2.0dvmin"),
597        (Length::dvmax(2.0), "2.0dvmax"),
598        (Length::cqw(2.0), "2.0cqw"),
599        (Length::cqh(2.0), "2.0cqh"),
600        (Length::cqi(2.0), "2.0cqi"),
601        (Length::cqb(2.0), "2.0cqb"),
602        (Length::cqmin(2.0), "2.0cqmin"),
603        (Length::cqmax(2.0), "2.0cqmax"),
604    ];
605
606    fn render(value: impl AttributeValueViewParts) -> String {
607        let mut parts = ViewParts::new();
608        value.into_view_parts(
609            &Cx::default(),
610            &mut PartsWriter::new(&mut parts, HtmlContext::AttributeValue),
611        );
612        View::new(parts).render(&Cx::default())
613    }
614
615    #[test]
616    fn displays_with_unit() {
617        for &(length, expected) in UNITS {
618            assert_eq!(length.to_string(), expected, "Display for {length:?}");
619        }
620    }
621
622    #[test]
623    fn renders_view_parts_with_unit() {
624        for &(length, expected) in UNITS {
625            assert_eq!(render(length), expected, "view parts for {length:?}");
626        }
627    }
628
629    #[test]
630    fn unit_reports_its_suffix() {
631        assert_eq!(LengthUnit::Px.as_str(), "px");
632        assert_eq!(LengthUnit::Q.as_str(), "Q");
633        assert_eq!(LengthUnit::In.as_str(), "in");
634        assert_eq!(LengthUnit::Cqmax.as_str(), "cqmax");
635        assert_eq!(LengthUnit::Rem.to_string(), "rem");
636    }
637
638    #[test]
639    fn getters_expose_value_and_unit() {
640        let length = Length::rem(1.5);
641        assert_eq!(length.unit(), LengthUnit::Rem);
642        // Rebuilding from the getters yields an equal length, which exercises
643        // `value` without comparing floats directly.
644        assert_eq!(Length::new(length.value(), length.unit()), length);
645    }
646
647    #[test]
648    fn formats_fractional_and_negative_values() {
649        assert_eq!(Length::px(1.5).to_string(), "1.5px");
650        assert_eq!(render(Length::px(1.5)), "1.5px");
651        assert_eq!(Length::em(-0.5).to_string(), "-0.5em");
652        assert_eq!(render(Length::em(-0.5)), "-0.5em");
653        assert_eq!(Length::rem(0.0).to_string(), "0.0rem");
654        assert_eq!(render(Length::rem(0.0)), "0.0rem");
655    }
656
657    #[test]
658    fn numbers_convert_to_pixels() {
659        assert_eq!(Length::from(24u16), Length::px(24.0));
660        assert_eq!(Length::from(1.5f32), Length::px(1.5));
661    }
662
663    #[test]
664    fn attribute_is_always_present() {
665        assert!(Length::px(0.0).attribute_present());
666        assert!(Length::cqmax(1.0).attribute_present());
667    }
668}