1use std::fmt;
10
11#[derive(Debug, Copy, Clone, PartialEq)]
16pub enum LengthUnit {
17 Absolute(i32),
19 Em(f64),
21 Ex(f64),
23}
24
25#[derive(Debug, Copy, Clone, PartialEq, Eq)]
30pub struct FontContext {
31 pub font_size: Length,
33 pub x_height: Length,
35}
36
37impl FontContext {
38 #[must_use = "this returns a new value without modifying anything"]
43 pub fn new(font_size: Length) -> Self {
44 Self {
45 font_size,
46 x_height: font_size / 2,
47 }
48 }
49
50 #[must_use = "this returns a new value without modifying anything"]
52 pub fn with_x_height(font_size: Length, x_height: Length) -> Self {
53 Self {
54 font_size,
55 x_height,
56 }
57 }
58}
59
60#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
72pub struct Length {
73 millipoints: i32,
74}
75
76impl Length {
77 pub const ZERO: Self = Self { millipoints: 0 };
79
80 const PT_TO_MILLI: f64 = 1000.0;
82
83 const IN_TO_MILLI: f64 = 72000.0;
85
86 const MM_TO_MILLI: f64 = 2834.645669;
88
89 const CM_TO_MILLI: f64 = 28346.45669;
91
92 #[inline]
94 #[must_use = "this returns a new value without modifying anything"]
95 pub fn from_pt(pt: f64) -> Self {
96 Self {
97 millipoints: (pt * Self::PT_TO_MILLI).round() as i32,
98 }
99 }
100
101 #[inline]
103 #[must_use = "this returns a new value without modifying anything"]
104 pub fn from_inch(inch: f64) -> Self {
105 Self {
106 millipoints: (inch * Self::IN_TO_MILLI).round() as i32,
107 }
108 }
109
110 #[inline]
112 #[must_use = "this returns a new value without modifying anything"]
113 pub fn from_mm(mm: f64) -> Self {
114 Self {
115 millipoints: (mm * Self::MM_TO_MILLI).round() as i32,
116 }
117 }
118
119 #[inline]
121 #[must_use = "this returns a new value without modifying anything"]
122 pub fn from_cm(cm: f64) -> Self {
123 Self {
124 millipoints: (cm * Self::CM_TO_MILLI).round() as i32,
125 }
126 }
127
128 #[inline]
130 #[must_use = "this returns a new value without modifying anything"]
131 pub const fn from_millipoints(millipoints: i32) -> Self {
132 Self { millipoints }
133 }
134
135 #[inline]
151 #[must_use = "this returns a new value without modifying anything"]
152 pub fn from_em(em: f64) -> LengthUnit {
153 LengthUnit::Em(em)
154 }
155
156 #[inline]
174 #[must_use = "this returns a new value without modifying anything"]
175 pub fn from_ex(ex: f64) -> LengthUnit {
176 LengthUnit::Ex(ex)
177 }
178
179 #[must_use = "computed value is not stored automatically"]
205 pub fn resolve_unit(unit: &LengthUnit, context: &FontContext) -> Self {
206 match unit {
207 LengthUnit::Absolute(millipoints) => Self::from_millipoints(*millipoints),
208 LengthUnit::Em(em) => {
209 let font_size_milli = context.font_size.millipoints() as f64;
210 Self::from_millipoints((em * font_size_milli).round() as i32)
211 }
212 LengthUnit::Ex(ex) => {
213 let x_height_milli = context.x_height.millipoints() as f64;
214 Self::from_millipoints((ex * x_height_milli).round() as i32)
215 }
216 }
217 }
218
219 #[inline]
221 #[must_use = "the result should be used"]
222 pub fn to_pt(self) -> f64 {
223 self.millipoints as f64 / Self::PT_TO_MILLI
224 }
225
226 #[inline]
228 #[must_use = "the result should be used"]
229 pub fn to_inch(self) -> f64 {
230 self.millipoints as f64 / Self::IN_TO_MILLI
231 }
232
233 #[inline]
235 #[must_use = "the result should be used"]
236 pub fn to_mm(self) -> f64 {
237 self.millipoints as f64 / Self::MM_TO_MILLI
238 }
239
240 #[inline]
242 #[must_use = "the result should be used"]
243 pub fn to_cm(self) -> f64 {
244 self.millipoints as f64 / Self::CM_TO_MILLI
245 }
246
247 #[inline]
249 #[must_use = "the result should be used"]
250 pub const fn millipoints(self) -> i32 {
251 self.millipoints
252 }
253
254 #[inline]
256 #[must_use = "this returns a new value without modifying the original"]
257 pub fn abs(self) -> Self {
258 Self {
259 millipoints: self.millipoints.abs(),
260 }
261 }
262}
263
264impl std::ops::Neg for Length {
265 type Output = Self;
266
267 #[inline]
268 fn neg(self) -> Self {
269 Self {
270 millipoints: -self.millipoints,
271 }
272 }
273}
274
275impl fmt::Debug for Length {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 write!(f, "Length({}pt)", self.to_pt())
278 }
279}
280
281impl fmt::Display for Length {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 write!(f, "{}pt", self.to_pt())
284 }
285}
286
287impl std::ops::Add for Length {
288 type Output = Self;
289
290 #[inline]
291 fn add(self, other: Self) -> Self {
292 Self {
293 millipoints: self.millipoints + other.millipoints,
294 }
295 }
296}
297
298impl std::ops::Sub for Length {
299 type Output = Self;
300
301 #[inline]
302 fn sub(self, other: Self) -> Self {
303 Self {
304 millipoints: self.millipoints - other.millipoints,
305 }
306 }
307}
308
309impl std::ops::Mul<i32> for Length {
310 type Output = Self;
311
312 #[inline]
313 fn mul(self, scalar: i32) -> Self {
314 Self {
315 millipoints: self.millipoints * scalar,
316 }
317 }
318}
319
320impl std::ops::Div<i32> for Length {
321 type Output = Self;
322
323 #[inline]
324 fn div(self, scalar: i32) -> Self {
325 Self {
326 millipoints: self.millipoints / scalar,
327 }
328 }
329}
330
331impl std::ops::AddAssign for Length {
332 #[inline]
333 fn add_assign(&mut self, other: Self) {
334 self.millipoints += other.millipoints;
335 }
336}
337
338impl std::ops::SubAssign for Length {
339 #[inline]
340 fn sub_assign(&mut self, other: Self) {
341 self.millipoints -= other.millipoints;
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn test_zero() {
351 assert_eq!(Length::ZERO.millipoints(), 0);
352 assert_eq!(Length::ZERO.to_pt(), 0.0);
353 }
354
355 #[test]
356 fn test_point_conversion() {
357 let len = Length::from_pt(72.0);
358 assert_eq!(len.millipoints(), 72000);
359 assert!((len.to_pt() - 72.0).abs() < 0.001);
360 }
361
362 #[test]
363 fn test_inch_conversion() {
364 let len = Length::from_inch(1.0);
365 assert_eq!(len.millipoints(), 72000);
366 assert!((len.to_inch() - 1.0).abs() < 0.001);
367 assert!((len.to_pt() - 72.0).abs() < 0.001);
368 }
369
370 #[test]
371 fn test_mm_conversion() {
372 let len = Length::from_mm(25.4);
373 assert!((len.to_inch() - 1.0).abs() < 0.001);
375 assert!((len.to_pt() - 72.0).abs() < 0.1);
376 }
377
378 #[test]
379 fn test_cm_conversion() {
380 let len = Length::from_cm(2.54);
381 assert!((len.to_inch() - 1.0).abs() < 0.001);
383 assert!((len.to_pt() - 72.0).abs() < 0.1);
384 }
385
386 #[test]
387 fn test_arithmetic() {
388 let a = Length::from_pt(10.0);
389 let b = Length::from_pt(5.0);
390
391 let sum = a + b;
392 assert!((sum.to_pt() - 15.0).abs() < 0.001);
393
394 let diff = a - b;
395 assert!((diff.to_pt() - 5.0).abs() < 0.001);
396
397 let prod = a * 2;
398 assert!((prod.to_pt() - 20.0).abs() < 0.001);
399
400 let quot = a / 2;
401 assert!((quot.to_pt() - 5.0).abs() < 0.001);
402 }
403
404 #[test]
405 fn test_abs_neg() {
406 let len = Length::from_pt(-10.0);
407 assert_eq!(len.abs(), Length::from_pt(10.0));
408 assert_eq!(-len, Length::from_pt(10.0));
409 }
410
411 #[test]
412 fn test_ordering() {
413 let a = Length::from_pt(10.0);
414 let b = Length::from_pt(20.0);
415 assert!(a < b);
416 assert!(b > a);
417 assert_eq!(a, a);
418 }
419
420 #[test]
421 fn test_display() {
422 let len = Length::from_pt(12.0);
423 assert_eq!(format!("{}", len), "12pt");
424
425 let zero = Length::ZERO;
426 assert_eq!(format!("{}", zero), "0pt");
427
428 let negative = Length::from_pt(-5.5);
429 assert_eq!(format!("{}", negative), "-5.5pt");
430
431 let fractional = Length::from_pt(12.345);
432 assert_eq!(format!("{}", fractional), "12.345pt");
433 }
434
435 #[test]
436 fn test_display_units() {
437 let from_inch = Length::from_inch(1.0);
439 assert_eq!(format!("{}", from_inch), "72pt");
440
441 let from_mm = Length::from_mm(25.4);
442 assert!(format!("{}", from_mm).contains("pt"));
444
445 let from_cm = Length::from_cm(2.54);
446 assert!(format!("{}", from_cm).contains("pt"));
448 }
449
450 #[test]
452 fn test_font_context_new() {
453 let context = FontContext::new(Length::from_pt(12.0));
454 assert_eq!(context.font_size, Length::from_pt(12.0));
455 assert_eq!(context.x_height, Length::from_pt(6.0));
457 }
458
459 #[test]
460 fn test_font_context_with_x_height() {
461 let context = FontContext::with_x_height(Length::from_pt(12.0), Length::from_pt(5.0));
462 assert_eq!(context.font_size, Length::from_pt(12.0));
463 assert_eq!(context.x_height, Length::from_pt(5.0));
464 }
465
466 #[test]
467 fn test_em_unit_creation() {
468 let em = Length::from_em(1.5);
469 assert_eq!(em, LengthUnit::Em(1.5));
470 }
471
472 #[test]
473 fn test_ex_unit_creation() {
474 let ex = Length::from_ex(2.0);
475 assert_eq!(ex, LengthUnit::Ex(2.0));
476 }
477
478 #[test]
479 fn test_resolve_absolute_unit() {
480 let context = FontContext::new(Length::from_pt(12.0));
481 let unit = LengthUnit::Absolute(12000);
482 let resolved = Length::resolve_unit(&unit, &context);
483 assert_eq!(resolved, Length::from_pt(12.0));
484 }
485
486 #[test]
487 fn test_resolve_em_unit_one_em() {
488 let context = FontContext::new(Length::from_pt(12.0));
489 let unit = Length::from_em(1.0);
490 let resolved = Length::resolve_unit(&unit, &context);
491 assert_eq!(resolved, Length::from_pt(12.0));
492 }
493
494 #[test]
495 fn test_resolve_em_unit_one_and_half_em() {
496 let context = FontContext::new(Length::from_pt(12.0));
497 let unit = Length::from_em(1.5);
498 let resolved = Length::resolve_unit(&unit, &context);
499 assert_eq!(resolved, Length::from_pt(18.0));
500 }
501
502 #[test]
503 fn test_resolve_em_unit_fractional() {
504 let context = FontContext::new(Length::from_pt(10.0));
505 let unit = Length::from_em(0.8);
506 let resolved = Length::resolve_unit(&unit, &context);
507 assert_eq!(resolved, Length::from_pt(8.0));
508 }
509
510 #[test]
511 fn test_resolve_ex_unit_one_ex() {
512 let context = FontContext::new(Length::from_pt(12.0));
513 let unit = Length::from_ex(1.0);
514 let resolved = Length::resolve_unit(&unit, &context);
515 assert_eq!(resolved, Length::from_pt(6.0));
517 }
518
519 #[test]
520 fn test_resolve_ex_unit_two_ex() {
521 let context = FontContext::new(Length::from_pt(12.0));
522 let unit = Length::from_ex(2.0);
523 let resolved = Length::resolve_unit(&unit, &context);
524 assert_eq!(resolved, Length::from_pt(12.0));
526 }
527
528 #[test]
529 fn test_resolve_ex_unit_with_custom_x_height() {
530 let context = FontContext::with_x_height(Length::from_pt(12.0), Length::from_pt(5.0));
531 let unit = Length::from_ex(2.0);
532 let resolved = Length::resolve_unit(&unit, &context);
533 assert_eq!(resolved, Length::from_pt(10.0));
534 }
535
536 #[test]
537 fn test_em_with_different_font_sizes() {
538 let context16 = FontContext::new(Length::from_pt(16.0));
540 let unit = Length::from_em(1.0);
541 assert_eq!(
542 Length::resolve_unit(&unit, &context16),
543 Length::from_pt(16.0)
544 );
545
546 let context24 = FontContext::new(Length::from_pt(24.0));
548 assert_eq!(
549 Length::resolve_unit(&unit, &context24),
550 Length::from_pt(24.0)
551 );
552 }
553
554 #[test]
555 fn test_ex_with_different_font_sizes() {
556 let context16 = FontContext::new(Length::from_pt(16.0));
558 let unit = Length::from_ex(1.0);
559 assert_eq!(
560 Length::resolve_unit(&unit, &context16),
561 Length::from_pt(8.0)
562 );
563
564 let context20 = FontContext::new(Length::from_pt(20.0));
566 assert_eq!(
567 Length::resolve_unit(&unit, &context20),
568 Length::from_pt(10.0)
569 );
570 }
571
572 #[test]
573 fn test_em_negative_values() {
574 let context = FontContext::new(Length::from_pt(12.0));
575 let unit = Length::from_em(-1.0);
576 let resolved = Length::resolve_unit(&unit, &context);
577 assert_eq!(resolved, Length::from_pt(-12.0));
578 }
579
580 #[test]
581 fn test_ex_negative_values() {
582 let context = FontContext::new(Length::from_pt(12.0));
583 let unit = Length::from_ex(-2.0);
584 let resolved = Length::resolve_unit(&unit, &context);
585 assert_eq!(resolved, Length::from_pt(-12.0));
586 }
587
588 #[test]
589 fn test_em_zero() {
590 let context = FontContext::new(Length::from_pt(12.0));
591 let unit = Length::from_em(0.0);
592 let resolved = Length::resolve_unit(&unit, &context);
593 assert_eq!(resolved, Length::ZERO);
594 }
595
596 #[test]
597 fn test_ex_zero() {
598 let context = FontContext::new(Length::from_pt(12.0));
599 let unit = Length::from_ex(0.0);
600 let resolved = Length::resolve_unit(&unit, &context);
601 assert_eq!(resolved, Length::ZERO);
602 }
603
604 #[test]
605 fn test_length_unit_equality() {
606 assert_eq!(LengthUnit::Em(1.5), LengthUnit::Em(1.5));
607 assert_eq!(LengthUnit::Ex(2.0), LengthUnit::Ex(2.0));
608 assert_eq!(LengthUnit::Absolute(12000), LengthUnit::Absolute(12000));
609
610 assert_ne!(LengthUnit::Em(1.5), LengthUnit::Em(2.0));
611 assert_ne!(LengthUnit::Ex(1.0), LengthUnit::Ex(2.0));
612 assert_ne!(LengthUnit::Em(1.0), LengthUnit::Ex(1.0));
613 }
614}
615
616#[cfg(test)]
617mod length_extra_tests {
618 use super::*;
619
620 const TOLERANCE: f64 = 0.01;
621
622 fn approx(a: f64, b: f64) -> bool {
623 (a - b).abs() < TOLERANCE
624 }
625
626 #[test]
629 fn test_mm_roundtrip() {
630 let mm = Length::from_mm(42.0);
631 assert!(approx(mm.to_mm(), 42.0));
632 }
633
634 #[test]
635 fn test_cm_roundtrip() {
636 let cm = Length::from_cm(5.5);
637 assert!(approx(cm.to_cm(), 5.5));
638 }
639
640 #[test]
641 fn test_inch_roundtrip() {
642 let inch = Length::from_inch(3.0);
643 assert!(approx(inch.to_inch(), 3.0));
644 }
645
646 #[test]
647 fn test_pt_roundtrip() {
648 let pt = Length::from_pt(144.0);
649 assert!(approx(pt.to_pt(), 144.0));
650 }
651
652 #[test]
655 fn test_25_4mm_equals_1inch() {
656 let mm = Length::from_mm(25.4);
658 let inch = Length::from_inch(1.0);
659 assert!(approx(mm.to_pt(), inch.to_pt()));
660 }
661
662 #[test]
663 fn test_2_54cm_equals_1inch() {
664 let cm = Length::from_cm(2.54);
665 let inch = Length::from_inch(1.0);
666 assert!(approx(cm.to_pt(), inch.to_pt()));
667 }
668
669 #[test]
670 fn test_1cm_equals_10mm() {
671 let cm = Length::from_cm(1.0);
672 let mm = Length::from_mm(10.0);
673 assert!(approx(cm.to_pt(), mm.to_pt()));
674 }
675
676 #[test]
677 fn test_1inch_equals_72pt() {
678 let inch = Length::from_inch(1.0);
679 assert!(approx(inch.to_pt(), 72.0));
680 }
681
682 #[test]
683 fn test_a4_width_mm_to_pt() {
684 let w = Length::from_mm(210.0);
686 assert!(approx(w.to_pt(), 595.276));
687 }
688
689 #[test]
690 fn test_a4_height_mm_to_pt() {
691 let h = Length::from_mm(297.0);
693 assert!(approx(h.to_pt(), 841.890));
694 }
695
696 #[test]
697 fn test_letter_width_in_to_pt() {
698 let w = Length::from_inch(8.5);
700 assert!(approx(w.to_pt(), 612.0));
701 }
702
703 #[test]
704 fn test_letter_height_in_to_pt() {
705 let h = Length::from_inch(11.0);
706 assert!(approx(h.to_pt(), 792.0));
707 }
708
709 #[test]
712 fn test_max_via_ord() {
713 let a = Length::from_mm(5.0);
714 let b = Length::from_mm(10.0);
715 assert_eq!(a.max(b), b);
716 assert_eq!(b.max(a), b);
717 }
718
719 #[test]
720 fn test_min_via_ord() {
721 let a = Length::from_mm(5.0);
722 let b = Length::from_mm(10.0);
723 assert_eq!(a.min(b), a);
724 assert_eq!(b.min(a), a);
725 }
726
727 #[test]
728 fn test_clamp_via_ord() {
729 let val = Length::from_mm(15.0);
730 let lo = Length::from_mm(0.0);
731 let hi = Length::from_mm(10.0);
732 assert_eq!(val.clamp(lo, hi), hi);
733
734 let val2 = Length::from_mm(-5.0);
735 assert_eq!(val2.clamp(lo, hi), lo);
736
737 let val3 = Length::from_mm(7.0);
738 assert_eq!(val3.clamp(lo, hi), val3);
739 }
740
741 #[test]
744 fn test_add_assign() {
745 let mut a = Length::from_pt(10.0);
746 a += Length::from_pt(5.0);
747 assert!(approx(a.to_pt(), 15.0));
748 }
749
750 #[test]
751 fn test_sub_assign() {
752 let mut a = Length::from_pt(10.0);
753 a -= Length::from_pt(3.0);
754 assert!(approx(a.to_pt(), 7.0));
755 }
756
757 #[test]
758 fn test_mul_i32() {
759 let a = Length::from_pt(7.0);
760 assert!(approx((a * 3).to_pt(), 21.0));
761 }
762
763 #[test]
764 fn test_div_i32() {
765 let a = Length::from_pt(15.0);
766 assert!(approx((a / 5).to_pt(), 3.0));
767 }
768
769 #[test]
770 fn test_neg_operator() {
771 let a = Length::from_pt(8.0);
772 assert!(approx((-a).to_pt(), -8.0));
773 }
774
775 #[test]
776 fn test_abs_positive() {
777 let a = Length::from_pt(5.0);
778 assert_eq!(a.abs(), a);
779 }
780
781 #[test]
782 fn test_abs_negative() {
783 let a = Length::from_pt(-5.0);
784 assert!(approx(a.abs().to_pt(), 5.0));
785 }
786
787 #[test]
790 fn test_from_millipoints_and_back() {
791 let mp = 72000_i32;
792 let len = Length::from_millipoints(mp);
793 assert_eq!(len.millipoints(), mp);
794 assert!(approx(len.to_pt(), 72.0));
795 }
796
797 #[test]
798 fn test_zero_millipoints() {
799 assert_eq!(Length::ZERO.millipoints(), 0);
800 }
801
802 #[test]
803 fn test_negative_millipoints() {
804 let neg = Length::from_pt(-10.0);
805 assert!(neg.millipoints() < 0);
806 }
807
808 #[test]
811 fn test_font_context_x_height_is_half_font_size() {
812 let ctx = FontContext::new(Length::from_pt(20.0));
813 assert!(approx(ctx.x_height.to_pt(), 10.0));
814 }
815
816 #[test]
817 fn test_font_context_with_x_height_explicit() {
818 let ctx = FontContext::with_x_height(Length::from_pt(16.0), Length::from_pt(7.0));
819 assert!(approx(ctx.x_height.to_pt(), 7.0));
820 assert!(approx(ctx.font_size.to_pt(), 16.0));
821 }
822
823 #[test]
824 fn test_em_resolution_large_font() {
825 let ctx = FontContext::new(Length::from_pt(24.0));
827 let unit = Length::from_em(2.5);
828 let resolved = Length::resolve_unit(&unit, &ctx);
829 assert!(approx(resolved.to_pt(), 60.0));
830 }
831
832 #[test]
833 fn test_ex_resolution_custom_x_height() {
834 let ctx = FontContext::with_x_height(Length::from_pt(12.0), Length::from_pt(4.0));
836 let unit = Length::from_ex(3.0);
837 let resolved = Length::resolve_unit(&unit, &ctx);
838 assert!(approx(resolved.to_pt(), 12.0));
839 }
840
841 #[test]
842 fn test_resolve_absolute_unit_large() {
843 let ctx = FontContext::new(Length::from_pt(12.0));
844 let unit = LengthUnit::Absolute(595276); let resolved = Length::resolve_unit(&unit, &ctx);
846 assert_eq!(resolved.millipoints(), 595276);
847 }
848
849 #[test]
852 fn test_debug_format() {
853 let len = Length::from_pt(36.0);
854 let s = format!("{:?}", len);
855 assert!(s.contains("36pt"));
856 }
857
858 #[test]
859 fn test_display_zero() {
860 assert_eq!(format!("{}", Length::ZERO), "0pt");
861 }
862
863 #[test]
864 fn test_display_fractional() {
865 let len = Length::from_pt(1.5);
866 assert_eq!(format!("{}", len), "1.5pt");
867 }
868
869 #[test]
872 fn test_length_unit_debug_em() {
873 let unit = LengthUnit::Em(1.5);
874 let s = format!("{:?}", unit);
875 assert!(s.contains("Em"));
876 assert!(s.contains("1.5"));
877 }
878
879 #[test]
880 fn test_length_unit_debug_ex() {
881 let unit = LengthUnit::Ex(2.0);
882 let s = format!("{:?}", unit);
883 assert!(s.contains("Ex"));
884 }
885
886 #[test]
887 fn test_length_unit_debug_absolute() {
888 let unit = LengthUnit::Absolute(12000);
889 let s = format!("{:?}", unit);
890 assert!(s.contains("Absolute"));
891 }
892}
893
894#[cfg(test)]
895mod length_unit_conversion_tests {
896 use super::*;
897
898 const TOL: f64 = 0.001;
899
900 fn approx_eq(a: f64, b: f64) -> bool {
901 (a - b).abs() < TOL
902 }
903
904 #[test]
907 fn test_1mm_to_pt_approx_2_8346() {
908 let l = Length::from_mm(1.0);
909 let pts = l.to_pt();
910 assert!(
911 (pts - 2.8346).abs() < 0.01,
912 "1mm should be ~2.83pt, got {}",
913 pts
914 );
915 }
916
917 #[test]
920 fn test_1cm_equals_10mm_pt_value() {
921 let cm = Length::from_cm(1.0);
922 let mm = Length::from_mm(10.0);
923 assert!(approx_eq(cm.to_pt(), mm.to_pt()), "1cm != 10mm in pt");
924 }
925
926 #[test]
929 fn test_1in_equals_72pt() {
930 let inch = Length::from_inch(1.0);
931 assert!(
932 approx_eq(inch.to_pt(), 72.0),
933 "1in should be 72pt, got {}",
934 inch.to_pt()
935 );
936 }
937
938 #[test]
941 fn test_pica_to_pt() {
942 let one_pica_via_pt = Length::from_pt(12.0);
945 let one_pica_via_inch = Length::from_inch(1.0 / 6.0);
946 assert!(approx_eq(
947 one_pica_via_pt.to_pt(),
948 one_pica_via_inch.to_pt()
949 ));
950 }
951
952 #[test]
955 fn test_css_px_to_pt() {
956 let one_px = Length::from_inch(1.0 / 96.0);
958 assert!(
959 (one_px.to_pt() - 0.75).abs() < 0.01,
960 "1px should be 0.75pt, got {}",
961 one_px.to_pt()
962 );
963 }
964
965 #[test]
966 fn test_96px_equals_72pt() {
967 let ninety_six_px = Length::from_inch(1.0);
969 assert!(
970 approx_eq(ninety_six_px.to_pt(), 72.0),
971 "96px (=1in) should be 72pt"
972 );
973 }
974
975 #[test]
978 fn test_millipoint_precision_1pt() {
979 let l = Length::from_pt(1.0);
980 assert_eq!(l.millipoints(), 1000);
981 }
982
983 #[test]
984 fn test_millipoint_precision_1inch() {
985 let l = Length::from_inch(1.0);
986 assert_eq!(l.millipoints(), 72_000);
987 }
988
989 #[test]
992 fn test_pt_millipoints_roundtrip_fractional() {
993 let l = Length::from_pt(3.5);
994 assert!(approx_eq(l.to_pt(), 3.5));
995 }
996
997 #[test]
1000 fn test_zero_mm_to_pt() {
1001 let l = Length::from_mm(0.0);
1002 assert_eq!(l.to_pt(), 0.0);
1003 }
1004
1005 #[test]
1006 fn test_zero_cm_to_pt() {
1007 assert_eq!(Length::from_cm(0.0).to_pt(), 0.0);
1008 }
1009
1010 #[test]
1011 fn test_zero_inch_to_pt() {
1012 assert_eq!(Length::from_inch(0.0).to_pt(), 0.0);
1013 }
1014
1015 #[test]
1018 fn test_negative_mm() {
1019 let l = Length::from_mm(-5.0);
1020 assert!(l.millipoints() < 0);
1021 assert!(l < Length::ZERO);
1022 }
1023
1024 #[test]
1025 fn test_negative_inch() {
1026 let l = Length::from_inch(-1.0);
1027 assert!((l.to_pt() - (-72.0)).abs() < 0.1, "got {}pt", l.to_pt());
1028 }
1029
1030 #[test]
1033 fn test_mul_by_i32_scalar() {
1034 let l = Length::from_pt(12.0);
1035 let result = l * 3;
1036 assert!(approx_eq(result.to_pt(), 36.0));
1037 }
1038
1039 #[test]
1040 fn test_div_by_i32_scalar() {
1041 let l = Length::from_pt(30.0);
1042 let result = l / 5;
1043 assert!(approx_eq(result.to_pt(), 6.0));
1044 }
1045
1046 #[test]
1049 fn test_max_of_two_lengths() {
1050 let a = Length::from_mm(5.0);
1051 let b = Length::from_mm(10.0);
1052 assert_eq!(a.max(b), b);
1053 }
1054
1055 #[test]
1056 fn test_min_of_two_lengths() {
1057 let a = Length::from_mm(5.0);
1058 let b = Length::from_mm(10.0);
1059 assert_eq!(a.min(b), a);
1060 }
1061
1062 #[test]
1065 fn test_ordering_less_than() {
1066 let a = Length::from_mm(5.0);
1067 let b = Length::from_mm(10.0);
1068 assert!(a < b);
1069 }
1070
1071 #[test]
1072 fn test_ordering_greater_than() {
1073 let a = Length::from_mm(10.0);
1074 let b = Length::from_mm(5.0);
1075 assert!(a > b);
1076 }
1077
1078 #[test]
1079 fn test_ordering_equal() {
1080 let a = Length::from_pt(12.0);
1081 let b = Length::from_pt(12.0);
1082 assert!(a <= b && b <= a);
1083 }
1084
1085 #[test]
1088 fn test_add_mm_and_pt() {
1089 let mm = Length::from_mm(25.4);
1091 let pt = Length::from_pt(0.0);
1092 let result = mm + pt;
1093 assert!((result.to_pt() - 72.0).abs() < 0.1);
1094 }
1095
1096 #[test]
1097 fn test_sub_pt_from_inch() {
1098 let inch = Length::from_inch(1.0);
1100 let half = Length::from_pt(36.0);
1101 let result = inch - half;
1102 assert!((result.to_pt() - 36.0).abs() < 0.1);
1103 }
1104
1105 #[test]
1108 fn test_abs_of_negative_mm() {
1109 let l = Length::from_mm(-7.5);
1110 let abs = l.abs();
1111 assert!(approx_eq(abs.to_mm(), 7.5));
1112 }
1113
1114 #[test]
1117 fn test_neg_of_positive_length() {
1118 let l = Length::from_pt(10.0);
1119 let neg = -l;
1120 assert!(approx_eq(neg.to_pt(), -10.0));
1121 }
1122
1123 #[test]
1124 fn test_neg_of_negative_length() {
1125 let l = Length::from_pt(-5.0);
1126 let pos = -l;
1127 assert!(approx_eq(pos.to_pt(), 5.0));
1128 }
1129
1130 #[test]
1133 fn test_clamp_length() {
1134 let val = Length::from_pt(150.0);
1135 let lo = Length::from_pt(0.0);
1136 let hi = Length::from_pt(100.0);
1137 assert_eq!(val.clamp(lo, hi), hi);
1138 }
1139
1140 #[test]
1143 fn test_add_assign_mm() {
1144 let mut a = Length::from_mm(5.0);
1145 a += Length::from_mm(3.0);
1146 assert!(approx_eq(a.to_mm(), 8.0));
1147 }
1148
1149 #[test]
1150 fn test_sub_assign_pt() {
1151 let mut a = Length::from_pt(20.0);
1152 a -= Length::from_pt(8.0);
1153 assert!(approx_eq(a.to_pt(), 12.0));
1154 }
1155}