usvg/tree/
geom.rs

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