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