zng_unit/
px_dip.rs

1use std::{cmp, fmt, ops};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{CornerRadius2D, Factor, side_offsets::SideOffsets2D};
6
7/// Same value used in `60`.
8const DIP_TO_PX: i32 = 60;
9
10/// Device pixel.
11///
12/// Represents an actual device pixel, not descaled by the pixel scale factor.
13#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, bytemuck::Zeroable, bytemuck::Pod)]
14#[repr(transparent)]
15#[serde(transparent)]
16pub struct Px(pub i32);
17impl Px {
18    /// See [`DipToPx`].
19    pub fn from_dip(dip: Dip, scale_factor: Factor) -> Px {
20        Px((dip.0 as f32 / DIP_TO_PX as f32 * scale_factor.0).round() as i32)
21    }
22
23    /// Compares and returns the maximum of two pixel values.
24    pub fn max(self, other: Px) -> Px {
25        Px(self.0.max(other.0))
26    }
27
28    /// Compares and returns the minimum of two pixel values.
29    pub fn min(self, other: Px) -> Px {
30        Px(self.0.min(other.0))
31    }
32
33    /// Computes the saturating absolute value of `self`.
34    pub fn abs(self) -> Px {
35        Px(self.0.saturating_abs())
36    }
37
38    /// [`i32::MAX`].
39    pub const MAX: Px = Px(i32::MAX);
40
41    /// [`i32::MIN`].
42    pub const MIN: Px = Px(i32::MIN);
43}
44impl fmt::Debug for Px {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        write!(f, "{}px", self.0)
47    }
48}
49impl fmt::Display for Px {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(f, "{}px", self.0)
52    }
53}
54impl num_traits::ToPrimitive for Px {
55    fn to_i32(&self) -> Option<i32> {
56        Some(self.0)
57    }
58    fn to_i64(&self) -> Option<i64> {
59        Some(self.0 as i64)
60    }
61
62    fn to_u64(&self) -> Option<u64> {
63        Some(self.0 as u64)
64    }
65}
66impl num_traits::NumCast for Px {
67    fn from<T: num_traits::ToPrimitive>(n: T) -> Option<Self> {
68        if let Some(p) = n.to_i32() {
69            Some(Px(p))
70        } else {
71            n.to_f32().map(|p| Px(p as i32))
72        }
73    }
74}
75impl num_traits::Zero for Px {
76    fn zero() -> Self {
77        Px(0)
78    }
79
80    fn is_zero(&self) -> bool {
81        self.0 == 0
82    }
83}
84impl num_traits::One for Px {
85    fn one() -> Self {
86        Px(1)
87    }
88}
89impl euclid::num::Round for Px {
90    fn round(self) -> Self {
91        self
92    }
93}
94impl euclid::num::Ceil for Px {
95    fn ceil(self) -> Self {
96        self
97    }
98}
99impl euclid::num::Floor for Px {
100    fn floor(self) -> Self {
101        self
102    }
103}
104impl num_traits::Num for Px {
105    type FromStrRadixErr = <i32 as num_traits::Num>::FromStrRadixErr;
106
107    fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
108        num_traits::Num::from_str_radix(str, radix).map(Px)
109    }
110}
111impl num_traits::Signed for Px {
112    fn abs(&self) -> Self {
113        Px(self.0.abs())
114    }
115
116    fn abs_sub(&self, other: &Self) -> Self {
117        Px(num_traits::Signed::abs_sub(&self.0, &other.0))
118    }
119
120    fn signum(&self) -> Self {
121        Px(num_traits::Signed::signum(&self.0))
122    }
123
124    fn is_positive(&self) -> bool {
125        self.0 > 0
126    }
127
128    fn is_negative(&self) -> bool {
129        self.0 < 0
130    }
131}
132impl From<i32> for Px {
133    fn from(px: i32) -> Self {
134        Px(px)
135    }
136}
137impl ops::Add for Px {
138    type Output = Self;
139
140    fn add(self, rhs: Self) -> Self::Output {
141        Px(self.0.saturating_add(rhs.0))
142    }
143}
144impl ops::AddAssign for Px {
145    fn add_assign(&mut self, rhs: Self) {
146        *self = *self + rhs;
147    }
148}
149impl ops::Sub for Px {
150    type Output = Self;
151
152    fn sub(self, rhs: Self) -> Self::Output {
153        Px(self.0.saturating_sub(rhs.0))
154    }
155}
156impl ops::SubAssign for Px {
157    fn sub_assign(&mut self, rhs: Self) {
158        *self = *self - rhs;
159    }
160}
161impl ops::Mul<f32> for Px {
162    type Output = Px;
163
164    fn mul(self, rhs: f32) -> Self::Output {
165        Px((self.0 as f32 * rhs).round() as i32)
166    }
167}
168impl ops::MulAssign<f32> for Px {
169    fn mul_assign(&mut self, rhs: f32) {
170        *self = *self * rhs;
171    }
172}
173impl ops::Mul<i32> for Px {
174    type Output = Px;
175
176    fn mul(self, rhs: i32) -> Self::Output {
177        Px(self.0 * rhs)
178    }
179}
180impl ops::MulAssign<i32> for Px {
181    fn mul_assign(&mut self, rhs: i32) {
182        *self = *self * rhs;
183    }
184}
185impl ops::Mul<Px> for Px {
186    type Output = Px;
187
188    fn mul(self, rhs: Px) -> Self::Output {
189        Px(self.0.saturating_mul(rhs.0))
190    }
191}
192impl ops::MulAssign<Px> for Px {
193    fn mul_assign(&mut self, rhs: Px) {
194        *self = *self * rhs;
195    }
196}
197impl ops::Div<f32> for Px {
198    type Output = Px;
199
200    fn div(self, rhs: f32) -> Self::Output {
201        Px((self.0 as f32 / rhs).round() as i32)
202    }
203}
204impl ops::Div<i32> for Px {
205    type Output = Px;
206
207    fn div(self, rhs: i32) -> Self::Output {
208        Px(self.0 / rhs)
209    }
210}
211impl ops::Div<Px> for Px {
212    type Output = Px;
213
214    fn div(self, rhs: Px) -> Self::Output {
215        Px(self.0 / rhs.0)
216    }
217}
218impl ops::DivAssign<f32> for Px {
219    fn div_assign(&mut self, rhs: f32) {
220        *self = *self / rhs;
221    }
222}
223impl ops::DivAssign<i32> for Px {
224    fn div_assign(&mut self, rhs: i32) {
225        *self = *self / rhs;
226    }
227}
228impl ops::DivAssign<Px> for Px {
229    fn div_assign(&mut self, rhs: Px) {
230        *self = *self / rhs;
231    }
232}
233impl ops::Neg for Px {
234    type Output = Self;
235
236    fn neg(self) -> Self::Output {
237        Px(self.0.saturating_neg())
238    }
239}
240impl ops::Rem for Px {
241    type Output = Self;
242
243    fn rem(self, rhs: Self) -> Self::Output {
244        Px(self.0 % rhs.0)
245    }
246}
247impl std::iter::Sum for Px {
248    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
249        iter.fold(Px(0), |a, b| a + b)
250    }
251}
252impl PartialEq<i32> for Px {
253    fn eq(&self, other: &i32) -> bool {
254        *self == Px(*other)
255    }
256}
257impl PartialOrd<i32> for Px {
258    fn partial_cmp(&self, other: &i32) -> Option<cmp::Ordering> {
259        self.partial_cmp(&Px(*other))
260    }
261}
262
263/// Device independent pixel.
264///
265/// Represent a device pixel descaled by the pixel scale factor.
266///
267/// Internally this is an `i32` that represents 1/60th of a pixel.
268#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, bytemuck::Zeroable, bytemuck::Pod)]
269#[serde(from = "f32")]
270#[serde(into = "f32")]
271#[repr(transparent)]
272pub struct Dip(i32);
273impl Dip {
274    /// New from round integer value.
275    pub const fn new(dip: i32) -> Self {
276        Dip(dip * DIP_TO_PX)
277    }
278
279    /// new from floating point.
280    pub fn new_f32(dip: f32) -> Self {
281        Dip((dip * DIP_TO_PX as f32).round() as i32)
282    }
283
284    /// See [`PxToDip`].
285    pub fn from_px(px: Px, scale_factor: Factor) -> Dip {
286        Dip((px.0 as f32 / scale_factor.0 * DIP_TO_PX as f32).round() as i32)
287    }
288
289    /// Returns `self` as [`f32`].
290    pub fn to_f32(self) -> f32 {
291        self.0 as f32 / DIP_TO_PX as f32
292    }
293
294    /// Returns `self` as [`i32`].
295    pub fn to_i32(self) -> i32 {
296        self.0 / DIP_TO_PX
297    }
298
299    /// Compares and returns the maximum of two pixel values.
300    pub fn max(self, other: Dip) -> Dip {
301        Dip(self.0.max(other.0))
302    }
303
304    /// Compares and returns the minimum of two pixel values.
305    pub fn min(self, other: Dip) -> Dip {
306        Dip(self.0.min(other.0))
307    }
308
309    /// Computes the saturating absolute value of `self`.
310    pub fn abs(self) -> Dip {
311        Dip(self.0.saturating_abs())
312    }
313
314    /// Maximum DIP value.
315    pub const MAX: Dip = Dip(i32::MAX / DIP_TO_PX);
316    /// Minimum DIP value.
317    pub const MIN: Dip = Dip(i32::MIN / DIP_TO_PX);
318}
319impl fmt::Debug for Dip {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        fmt::Display::fmt(self, f)
322    }
323}
324impl fmt::Display for Dip {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        fmt::Display::fmt(&self.to_f32(), f)?;
327        write!(f, "dip")
328    }
329}
330impl From<i32> for Dip {
331    fn from(dip: i32) -> Self {
332        Dip::new(dip)
333    }
334}
335impl From<f32> for Dip {
336    fn from(dip: f32) -> Self {
337        Dip::new_f32(dip)
338    }
339}
340impl From<Dip> for f32 {
341    fn from(value: Dip) -> Self {
342        value.to_f32()
343    }
344}
345impl ops::Add for Dip {
346    type Output = Self;
347
348    fn add(self, rhs: Self) -> Self::Output {
349        Dip(self.0.saturating_add(rhs.0))
350    }
351}
352impl ops::AddAssign for Dip {
353    fn add_assign(&mut self, rhs: Self) {
354        *self = *self + rhs;
355    }
356}
357impl ops::Sub for Dip {
358    type Output = Self;
359
360    fn sub(self, rhs: Self) -> Self::Output {
361        Dip(self.0.saturating_sub(rhs.0))
362    }
363}
364impl ops::SubAssign for Dip {
365    fn sub_assign(&mut self, rhs: Self) {
366        *self = *self - rhs;
367    }
368}
369impl ops::Neg for Dip {
370    type Output = Self;
371
372    fn neg(self) -> Self::Output {
373        Dip(self.0.saturating_neg())
374    }
375}
376impl ops::Mul<f32> for Dip {
377    type Output = Dip;
378
379    fn mul(self, rhs: f32) -> Self::Output {
380        Dip((self.0 as f32 * rhs).round() as i32)
381    }
382}
383impl ops::MulAssign<f32> for Dip {
384    fn mul_assign(&mut self, rhs: f32) {
385        *self = *self * rhs;
386    }
387}
388impl ops::Mul<Dip> for Dip {
389    type Output = Dip;
390
391    fn mul(self, rhs: Dip) -> Self::Output {
392        Dip(self.0.saturating_mul(rhs.to_i32()))
393    }
394}
395impl ops::MulAssign<Dip> for Dip {
396    fn mul_assign(&mut self, rhs: Dip) {
397        *self = *self * rhs;
398    }
399}
400impl ops::Div<f32> for Dip {
401    type Output = Dip;
402
403    fn div(self, rhs: f32) -> Self::Output {
404        Dip((self.0 as f32 / rhs).round() as i32)
405    }
406}
407impl ops::DivAssign<f32> for Dip {
408    fn div_assign(&mut self, rhs: f32) {
409        *self = *self / rhs;
410    }
411}
412impl ops::Div<Dip> for Dip {
413    type Output = Dip;
414
415    fn div(self, rhs: Dip) -> Self::Output {
416        Dip::new(self.0 / rhs.0)
417    }
418}
419impl ops::DivAssign<Dip> for Dip {
420    fn div_assign(&mut self, rhs: Dip) {
421        *self = *self / rhs;
422    }
423}
424impl ops::Rem for Dip {
425    type Output = Self;
426
427    fn rem(self, rhs: Self) -> Self::Output {
428        Dip(self.0 % rhs.0)
429    }
430}
431impl ops::RemAssign for Dip {
432    fn rem_assign(&mut self, rhs: Self) {
433        *self = *self % rhs;
434    }
435}
436impl num_traits::ToPrimitive for Dip {
437    fn to_i64(&self) -> Option<i64> {
438        Some(Dip::to_i32(*self) as i64)
439    }
440
441    fn to_u64(&self) -> Option<u64> {
442        if self.0 >= 0 { Some(Dip::to_i32(*self) as u64) } else { None }
443    }
444
445    fn to_f32(&self) -> Option<f32> {
446        Some(Dip::to_f32(*self))
447    }
448
449    fn to_f64(&self) -> Option<f64> {
450        Some(Dip::to_f32(*self) as f64)
451    }
452}
453impl num_traits::NumCast for Dip {
454    fn from<T: num_traits::ToPrimitive>(n: T) -> Option<Self> {
455        #[expect(clippy::manual_map)]
456        if let Some(n) = n.to_f32() {
457            Some(Dip::new_f32(n))
458        } else if let Some(n) = n.to_i32() {
459            Some(Dip::new(n))
460        } else {
461            None
462        }
463    }
464}
465impl num_traits::Zero for Dip {
466    fn zero() -> Self {
467        Dip(0)
468    }
469
470    fn is_zero(&self) -> bool {
471        self.0 == 0
472    }
473}
474impl num_traits::One for Dip {
475    fn one() -> Self {
476        Dip::new(1)
477    }
478}
479impl euclid::num::Round for Dip {
480    fn round(self) -> Self {
481        Dip::new_f32(self.to_f32().round())
482    }
483}
484impl euclid::num::Ceil for Dip {
485    fn ceil(self) -> Self {
486        Dip::new_f32(self.to_f32().ceil())
487    }
488}
489impl euclid::num::Floor for Dip {
490    fn floor(self) -> Self {
491        Dip::new_f32(self.to_f32().floor())
492    }
493}
494impl num_traits::Signed for Dip {
495    fn abs(&self) -> Self {
496        Dip(self.0.abs())
497    }
498
499    fn abs_sub(&self, other: &Self) -> Self {
500        Dip(num_traits::Signed::abs_sub(&self.0, &other.0))
501    }
502
503    fn signum(&self) -> Self {
504        match self.0.cmp(&0) {
505            cmp::Ordering::Less => Dip::new(-1),
506            cmp::Ordering::Equal => Dip(0),
507            cmp::Ordering::Greater => Dip::new(1),
508        }
509    }
510
511    fn is_positive(&self) -> bool {
512        self.0 > 0
513    }
514
515    fn is_negative(&self) -> bool {
516        self.0 < 0
517    }
518}
519impl num_traits::Num for Dip {
520    type FromStrRadixErr = <i32 as num_traits::Num>::FromStrRadixErr;
521
522    fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
523        num_traits::Num::from_str_radix(str, radix).map(Dip::new)
524    }
525}
526impl PartialEq<i32> for Dip {
527    fn eq(&self, other: &i32) -> bool {
528        *self == Dip::new(*other)
529    }
530}
531impl PartialOrd<i32> for Dip {
532    fn partial_cmp(&self, other: &i32) -> Option<cmp::Ordering> {
533        self.partial_cmp(&Dip::new(*other))
534    }
535}
536impl PartialEq<f32> for Dip {
537    fn eq(&self, other: &f32) -> bool {
538        *self == Dip::new_f32(*other)
539    }
540}
541impl PartialOrd<f32> for Dip {
542    fn partial_cmp(&self, other: &f32) -> Option<cmp::Ordering> {
543        self.partial_cmp(&Dip::new_f32(*other))
544    }
545}
546
547/// A point in device pixels.
548pub type PxPoint = euclid::Point2D<Px, Px>;
549
550/// A point in device independent pixels.
551pub type DipPoint = euclid::Point2D<Dip, Dip>;
552
553/// A vector in device pixels.
554pub type PxVector = euclid::Vector2D<Px, Px>;
555
556/// A vector in device independent pixels.
557pub type DipVector = euclid::Vector2D<Dip, Dip>;
558
559/// A size in device pixels.
560pub type PxSize = euclid::Size2D<Px, Px>;
561
562/// A size in device pixels.
563pub type DipSize = euclid::Size2D<Dip, Dip>;
564
565/// A rectangle in device pixels.
566pub type PxRect = euclid::Rect<Px, Px>;
567
568/// A rectangle box in device pixels.
569pub type PxBox = euclid::Box2D<Px, Px>;
570
571/// A rectangle in device independent pixels.
572pub type DipRect = euclid::Rect<Dip, Dip>;
573
574/// A rectangle box in device independent pixels.
575pub type DipBox = euclid::Box2D<Dip, Dip>;
576
577/// Side-offsets in device pixels.
578pub type PxSideOffsets = SideOffsets2D<Px, Px>;
579/// Side-offsets in device independent pixels.
580pub type DipSideOffsets = SideOffsets2D<Dip, Dip>;
581
582/// Corner-radius in device pixels.
583pub type PxCornerRadius = CornerRadius2D<Px, Px>;
584
585/// Corner-radius in device independent pixels.
586pub type DipCornerRadius = CornerRadius2D<Dip, Dip>;
587
588/// Conversion from [`Px`] to [`Dip`] units.
589pub trait PxToDip {
590    /// `Self` equivalent in [`Dip`] units.
591    type AsDip;
592
593    /// Divide the [`Px`] self by the scale.
594    fn to_dip(self, scale_factor: Factor) -> Self::AsDip;
595}
596
597/// Conversion from [`Dip`] to [`Px`] units.
598pub trait DipToPx {
599    /// `Self` equivalent in [`Px`] units.
600    type AsPx;
601
602    /// Multiply the [`Dip`] self by the scale.
603    fn to_px(self, scale_factor: Factor) -> Self::AsPx;
604}
605
606impl PxToDip for Px {
607    type AsDip = Dip;
608
609    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
610        Dip::from_px(self, scale_factor)
611    }
612}
613
614impl DipToPx for Dip {
615    type AsPx = Px;
616
617    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
618        Px::from_dip(self, scale_factor)
619    }
620}
621
622impl PxToDip for PxPoint {
623    type AsDip = DipPoint;
624
625    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
626        DipPoint::new(self.x.to_dip(scale_factor), self.y.to_dip(scale_factor))
627    }
628}
629
630impl DipToPx for DipPoint {
631    type AsPx = PxPoint;
632
633    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
634        PxPoint::new(self.x.to_px(scale_factor), self.y.to_px(scale_factor))
635    }
636}
637impl DipToPx for euclid::Point2D<f32, Dip> {
638    type AsPx = euclid::Point2D<f32, Px>;
639
640    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
641        euclid::point2(self.x * scale_factor.0, self.y * scale_factor.0)
642    }
643}
644
645impl PxToDip for PxSize {
646    type AsDip = DipSize;
647
648    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
649        DipSize::new(self.width.to_dip(scale_factor), self.height.to_dip(scale_factor))
650    }
651}
652
653impl DipToPx for DipSize {
654    type AsPx = PxSize;
655
656    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
657        PxSize::new(self.width.to_px(scale_factor), self.height.to_px(scale_factor))
658    }
659}
660
661impl DipToPx for DipVector {
662    type AsPx = PxVector;
663
664    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
665        PxVector::new(self.x.to_px(scale_factor), self.y.to_px(scale_factor))
666    }
667}
668impl PxToDip for PxVector {
669    type AsDip = DipVector;
670
671    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
672        DipVector::new(self.x.to_dip(scale_factor), self.y.to_dip(scale_factor))
673    }
674}
675
676impl PxToDip for PxRect {
677    type AsDip = DipRect;
678
679    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
680        DipRect::new(self.origin.to_dip(scale_factor), self.size.to_dip(scale_factor))
681    }
682}
683
684impl DipToPx for DipRect {
685    type AsPx = PxRect;
686
687    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
688        PxRect::new(self.origin.to_px(scale_factor), self.size.to_px(scale_factor))
689    }
690}
691
692impl PxToDip for PxBox {
693    type AsDip = DipBox;
694
695    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
696        DipBox::new(self.min.to_dip(scale_factor), self.max.to_dip(scale_factor))
697    }
698}
699
700impl DipToPx for DipBox {
701    type AsPx = PxBox;
702
703    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
704        PxBox::new(self.min.to_px(scale_factor), self.max.to_px(scale_factor))
705    }
706}
707
708impl DipToPx for DipSideOffsets {
709    type AsPx = PxSideOffsets;
710
711    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
712        PxSideOffsets::new(
713            self.top.to_px(scale_factor),
714            self.right.to_px(scale_factor),
715            self.bottom.to_px(scale_factor),
716            self.left.to_px(scale_factor),
717        )
718    }
719}
720impl PxToDip for PxSideOffsets {
721    type AsDip = DipSideOffsets;
722
723    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
724        DipSideOffsets::new(
725            self.top.to_dip(scale_factor),
726            self.right.to_dip(scale_factor),
727            self.bottom.to_dip(scale_factor),
728            self.left.to_dip(scale_factor),
729        )
730    }
731}
732
733impl DipToPx for DipCornerRadius {
734    type AsPx = PxCornerRadius;
735
736    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
737        PxCornerRadius::new(
738            self.top_left.to_px(scale_factor),
739            self.top_right.to_px(scale_factor),
740            self.bottom_left.to_px(scale_factor),
741            self.bottom_right.to_px(scale_factor),
742        )
743    }
744}
745impl PxToDip for PxCornerRadius {
746    type AsDip = DipCornerRadius;
747
748    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
749        DipCornerRadius::new(
750            self.top_left.to_dip(scale_factor),
751            self.top_right.to_dip(scale_factor),
752            self.bottom_left.to_dip(scale_factor),
753            self.bottom_right.to_dip(scale_factor),
754        )
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761
762    #[test]
763    fn dip_px_1_1_conversion() {
764        let px = Dip::new(100).to_px(Factor(1.0));
765        assert_eq!(px, Px(100));
766    }
767
768    #[test]
769    fn px_dip_1_1_conversion() {
770        let dip = Px(100).to_dip(Factor(1.0));
771        assert_eq!(dip, Dip::new(100));
772    }
773
774    #[test]
775    fn dip_px_1_15_conversion() {
776        let px = Dip::new(100).to_px(Factor(1.5));
777        assert_eq!(px, Px(150));
778    }
779
780    #[test]
781    fn px_dip_1_15_conversion() {
782        let dip = Px(150).to_dip(Factor(1.5));
783        assert_eq!(dip, Dip::new(100));
784    }
785}