1use std::fmt::{self, Display};
2
3use topcoat_core::context::Cx;
4
5use crate::{AttributeValueViewParts, PartsWriter};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum LengthUnit {
10 Px,
13 Cm,
15 Mm,
17 Q,
19 In,
21 Pc,
23 Pt,
25
26 Em,
29 Rem,
31 Ex,
33 Rex,
35 Cap,
37 Rcap,
39 Ch,
41 Rch,
43 Ic,
45 Ric,
47 Lh,
49 Rlh,
51
52 Vw,
55 Vh,
57 Vi,
59 Vb,
61 Vmin,
63 Vmax,
65
66 Svw,
69 Svh,
71 Svi,
73 Svb,
75 Svmin,
77 Svmax,
79
80 Lvw,
83 Lvh,
85 Lvi,
87 Lvb,
89 Lvmin,
91 Lvmax,
93
94 Dvw,
97 Dvh,
99 Dvi,
101 Dvb,
103 Dvmin,
105 Dvmax,
107
108 Cqw,
111 Cqh,
113 Cqi,
115 Cqb,
117 Cqmin,
119 Cqmax,
121}
122
123impl LengthUnit {
124 #[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#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct Length {
195 value: f32,
196 unit: LengthUnit,
197}
198
199impl Length {
200 #[must_use]
202 pub const fn new(value: f32, unit: LengthUnit) -> Self {
203 Self { value, unit }
204 }
205
206 #[must_use]
208 pub const fn value(self) -> f32 {
209 self.value
210 }
211
212 #[must_use]
214 pub const fn unit(self) -> LengthUnit {
215 self.unit
216 }
217
218 #[must_use]
220 pub const fn px(value: f32) -> Self {
221 Self::new(value, LengthUnit::Px)
222 }
223
224 #[must_use]
226 pub const fn cm(value: f32) -> Self {
227 Self::new(value, LengthUnit::Cm)
228 }
229
230 #[must_use]
232 pub const fn mm(value: f32) -> Self {
233 Self::new(value, LengthUnit::Mm)
234 }
235
236 #[must_use]
238 pub const fn q(value: f32) -> Self {
239 Self::new(value, LengthUnit::Q)
240 }
241
242 #[must_use]
244 pub const fn r#in(value: f32) -> Self {
245 Self::new(value, LengthUnit::In)
246 }
247
248 #[must_use]
250 pub const fn pc(value: f32) -> Self {
251 Self::new(value, LengthUnit::Pc)
252 }
253
254 #[must_use]
256 pub const fn pt(value: f32) -> Self {
257 Self::new(value, LengthUnit::Pt)
258 }
259
260 #[must_use]
262 pub const fn em(value: f32) -> Self {
263 Self::new(value, LengthUnit::Em)
264 }
265
266 #[must_use]
268 pub const fn rem(value: f32) -> Self {
269 Self::new(value, LengthUnit::Rem)
270 }
271
272 #[must_use]
274 pub const fn ex(value: f32) -> Self {
275 Self::new(value, LengthUnit::Ex)
276 }
277
278 #[must_use]
280 pub const fn rex(value: f32) -> Self {
281 Self::new(value, LengthUnit::Rex)
282 }
283
284 #[must_use]
286 pub const fn cap(value: f32) -> Self {
287 Self::new(value, LengthUnit::Cap)
288 }
289
290 #[must_use]
292 pub const fn rcap(value: f32) -> Self {
293 Self::new(value, LengthUnit::Rcap)
294 }
295
296 #[must_use]
298 pub const fn ch(value: f32) -> Self {
299 Self::new(value, LengthUnit::Ch)
300 }
301
302 #[must_use]
304 pub const fn rch(value: f32) -> Self {
305 Self::new(value, LengthUnit::Rch)
306 }
307
308 #[must_use]
310 pub const fn ic(value: f32) -> Self {
311 Self::new(value, LengthUnit::Ic)
312 }
313
314 #[must_use]
316 pub const fn ric(value: f32) -> Self {
317 Self::new(value, LengthUnit::Ric)
318 }
319
320 #[must_use]
322 pub const fn lh(value: f32) -> Self {
323 Self::new(value, LengthUnit::Lh)
324 }
325
326 #[must_use]
328 pub const fn rlh(value: f32) -> Self {
329 Self::new(value, LengthUnit::Rlh)
330 }
331
332 #[must_use]
334 pub const fn vw(value: f32) -> Self {
335 Self::new(value, LengthUnit::Vw)
336 }
337
338 #[must_use]
340 pub const fn vh(value: f32) -> Self {
341 Self::new(value, LengthUnit::Vh)
342 }
343
344 #[must_use]
346 pub const fn vi(value: f32) -> Self {
347 Self::new(value, LengthUnit::Vi)
348 }
349
350 #[must_use]
352 pub const fn vb(value: f32) -> Self {
353 Self::new(value, LengthUnit::Vb)
354 }
355
356 #[must_use]
358 pub const fn vmin(value: f32) -> Self {
359 Self::new(value, LengthUnit::Vmin)
360 }
361
362 #[must_use]
364 pub const fn vmax(value: f32) -> Self {
365 Self::new(value, LengthUnit::Vmax)
366 }
367
368 #[must_use]
370 pub const fn svw(value: f32) -> Self {
371 Self::new(value, LengthUnit::Svw)
372 }
373
374 #[must_use]
376 pub const fn svh(value: f32) -> Self {
377 Self::new(value, LengthUnit::Svh)
378 }
379
380 #[must_use]
382 pub const fn svi(value: f32) -> Self {
383 Self::new(value, LengthUnit::Svi)
384 }
385
386 #[must_use]
388 pub const fn svb(value: f32) -> Self {
389 Self::new(value, LengthUnit::Svb)
390 }
391
392 #[must_use]
394 pub const fn svmin(value: f32) -> Self {
395 Self::new(value, LengthUnit::Svmin)
396 }
397
398 #[must_use]
400 pub const fn svmax(value: f32) -> Self {
401 Self::new(value, LengthUnit::Svmax)
402 }
403
404 #[must_use]
406 pub const fn lvw(value: f32) -> Self {
407 Self::new(value, LengthUnit::Lvw)
408 }
409
410 #[must_use]
412 pub const fn lvh(value: f32) -> Self {
413 Self::new(value, LengthUnit::Lvh)
414 }
415
416 #[must_use]
418 pub const fn lvi(value: f32) -> Self {
419 Self::new(value, LengthUnit::Lvi)
420 }
421
422 #[must_use]
424 pub const fn lvb(value: f32) -> Self {
425 Self::new(value, LengthUnit::Lvb)
426 }
427
428 #[must_use]
430 pub const fn lvmin(value: f32) -> Self {
431 Self::new(value, LengthUnit::Lvmin)
432 }
433
434 #[must_use]
436 pub const fn lvmax(value: f32) -> Self {
437 Self::new(value, LengthUnit::Lvmax)
438 }
439
440 #[must_use]
442 pub const fn dvw(value: f32) -> Self {
443 Self::new(value, LengthUnit::Dvw)
444 }
445
446 #[must_use]
448 pub const fn dvh(value: f32) -> Self {
449 Self::new(value, LengthUnit::Dvh)
450 }
451
452 #[must_use]
454 pub const fn dvi(value: f32) -> Self {
455 Self::new(value, LengthUnit::Dvi)
456 }
457
458 #[must_use]
460 pub const fn dvb(value: f32) -> Self {
461 Self::new(value, LengthUnit::Dvb)
462 }
463
464 #[must_use]
466 pub const fn dvmin(value: f32) -> Self {
467 Self::new(value, LengthUnit::Dvmin)
468 }
469
470 #[must_use]
472 pub const fn dvmax(value: f32) -> Self {
473 Self::new(value, LengthUnit::Dvmax)
474 }
475
476 #[must_use]
478 pub const fn cqw(value: f32) -> Self {
479 Self::new(value, LengthUnit::Cqw)
480 }
481
482 #[must_use]
484 pub const fn cqh(value: f32) -> Self {
485 Self::new(value, LengthUnit::Cqh)
486 }
487
488 #[must_use]
490 pub const fn cqi(value: f32) -> Self {
491 Self::new(value, LengthUnit::Cqi)
492 }
493
494 #[must_use]
496 pub const fn cqb(value: f32) -> Self {
497 Self::new(value, LengthUnit::Cqb)
498 }
499
500 #[must_use]
502 pub const fn cqmin(value: f32) -> Self {
503 Self::new(value, LengthUnit::Cqmin)
504 }
505
506 #[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 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 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}