usvg_tree/
geom.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use strict_num::ApproxEqUlps;
6pub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform};
7
8use crate::AspectRatio;
9
10/// Approximate zero equality comparisons.
11pub trait ApproxZeroUlps: ApproxEqUlps {
12    /// Checks if the number is approximately zero.
13    fn approx_zero_ulps(&self, ulps: <Self::Flt as strict_num::Ulps>::U) -> bool;
14}
15
16impl ApproxZeroUlps for f32 {
17    fn approx_zero_ulps(&self, ulps: i32) -> bool {
18        self.approx_eq_ulps(&0.0, ulps)
19    }
20}
21
22impl ApproxZeroUlps for f64 {
23    fn approx_zero_ulps(&self, ulps: i64) -> bool {
24        self.approx_eq_ulps(&0.0, ulps)
25    }
26}
27
28/// Checks that the current number is > 0.
29pub trait IsValidLength {
30    /// Checks that the current number is > 0.
31    fn is_valid_length(&self) -> bool;
32}
33
34impl IsValidLength for f32 {
35    #[inline]
36    fn is_valid_length(&self) -> bool {
37        *self > 0.0 && self.is_finite()
38    }
39}
40
41impl IsValidLength for f64 {
42    #[inline]
43    fn is_valid_length(&self) -> bool {
44        *self > 0.0 && self.is_finite()
45    }
46}
47
48/// View box.
49#[derive(Clone, Copy, Debug)]
50pub struct ViewBox {
51    /// Value of the `viewBox` attribute.
52    pub rect: NonZeroRect,
53
54    /// Value of the `preserveAspectRatio` attribute.
55    pub aspect: AspectRatio,
56}
57
58/// A bounding box calculator.
59#[derive(Clone, Copy, Debug)]
60pub struct BBox {
61    left: f32,
62    top: f32,
63    right: f32,
64    bottom: f32,
65}
66
67impl From<Rect> for BBox {
68    fn from(r: Rect) -> Self {
69        Self {
70            left: r.left(),
71            top: r.top(),
72            right: r.right(),
73            bottom: r.bottom(),
74        }
75    }
76}
77
78impl From<NonZeroRect> for BBox {
79    fn from(r: NonZeroRect) -> Self {
80        Self {
81            left: r.left(),
82            top: r.top(),
83            right: r.right(),
84            bottom: r.bottom(),
85        }
86    }
87}
88
89impl Default for BBox {
90    fn default() -> Self {
91        Self {
92            left: f32::MAX,
93            top: f32::MAX,
94            right: f32::MIN,
95            bottom: f32::MIN,
96        }
97    }
98}
99
100impl BBox {
101    /// Checks if the bounding box is default, i.e. invalid.
102    pub fn is_default(&self) -> bool {
103        self.left == f32::MAX
104            && self.top == f32::MAX
105            && self.right == f32::MIN
106            && self.bottom == f32::MIN
107    }
108
109    /// Expand the bounding box to the specified bounds.
110    #[must_use]
111    pub fn expand(&self, r: impl Into<Self>) -> Self {
112        self.expand_impl(r.into())
113    }
114
115    fn expand_impl(&self, r: Self) -> Self {
116        Self {
117            left: self.left.min(r.left),
118            top: self.top.min(r.top),
119            right: self.right.max(r.right),
120            bottom: self.bottom.max(r.bottom),
121        }
122    }
123
124    /// Transforms the bounding box.
125    pub fn transform(&self, ts: tiny_skia_path::Transform) -> Option<Self> {
126        self.to_rect()?.transform(ts).map(Self::from)
127    }
128
129    /// Converts a bounding box into [`Rect`].
130    pub fn to_rect(&self) -> Option<Rect> {
131        if !self.is_default() {
132            Rect::from_ltrb(self.left, self.top, self.right, self.bottom)
133        } else {
134            None
135        }
136    }
137
138    /// Converts a bounding box into [`NonZeroRect`].
139    pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {
140        if !self.is_default() {
141            NonZeroRect::from_ltrb(self.left, self.top, self.right, self.bottom)
142        } else {
143            None
144        }
145    }
146}
147
148/// Some useful utilities.
149pub mod utils {
150    use super::*;
151    use crate::Align;
152
153    /// Converts `viewBox` to `Transform`.
154    pub fn view_box_to_transform(
155        view_box: NonZeroRect,
156        aspect: AspectRatio,
157        img_size: Size,
158    ) -> Transform {
159        let vr = view_box;
160
161        let sx = img_size.width() / vr.width();
162        let sy = img_size.height() / vr.height();
163
164        let (sx, sy) = if aspect.align == Align::None {
165            (sx, sy)
166        } else {
167            let s = if aspect.slice {
168                if sx < sy {
169                    sy
170                } else {
171                    sx
172                }
173            } else {
174                if sx > sy {
175                    sy
176                } else {
177                    sx
178                }
179            };
180
181            (s, s)
182        };
183
184        let x = -vr.x() * sx;
185        let y = -vr.y() * sy;
186        let w = img_size.width() - vr.width() * sx;
187        let h = img_size.height() - vr.height() * sy;
188
189        let (tx, ty) = aligned_pos(aspect.align, x, y, w, h);
190        Transform::from_row(sx, 0.0, 0.0, sy, tx, ty)
191    }
192
193    /// Returns object aligned position.
194    pub fn aligned_pos(align: Align, x: f32, y: f32, w: f32, h: f32) -> (f32, f32) {
195        match align {
196            Align::None => (x, y),
197            Align::XMinYMin => (x, y),
198            Align::XMidYMin => (x + w / 2.0, y),
199            Align::XMaxYMin => (x + w, y),
200            Align::XMinYMid => (x, y + h / 2.0),
201            Align::XMidYMid => (x + w / 2.0, y + h / 2.0),
202            Align::XMaxYMid => (x + w, y + h / 2.0),
203            Align::XMinYMax => (x, y + h),
204            Align::XMidYMax => (x + w / 2.0, y + h),
205            Align::XMaxYMax => (x + w, y + h),
206        }
207    }
208}