Skip to main content

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#[allow(missing_docs)]
48#[derive(Clone, Copy, Debug)]
49pub 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 { sy } else { sx }
70            } else {
71                if sx > sy { sy } else { sx }
72            };
73
74            (s, s)
75        };
76
77        let x = -vr.x() * sx;
78        let y = -vr.y() * sy;
79        let w = img_size.width() - vr.width() * sx;
80        let h = img_size.height() - vr.height() * sy;
81
82        let (tx, ty) = aligned_pos(self.aspect.align, x, y, w, h);
83        Transform::from_row(sx, 0.0, 0.0, sy, tx, ty)
84    }
85}
86
87/// A bounding box calculator.
88#[derive(Clone, Copy, Debug)]
89pub(crate) struct BBox {
90    left: f32,
91    top: f32,
92    right: f32,
93    bottom: f32,
94}
95
96impl From<Rect> for BBox {
97    fn from(r: Rect) -> Self {
98        Self {
99            left: r.left(),
100            top: r.top(),
101            right: r.right(),
102            bottom: r.bottom(),
103        }
104    }
105}
106
107impl From<NonZeroRect> for BBox {
108    fn from(r: NonZeroRect) -> Self {
109        Self {
110            left: r.left(),
111            top: r.top(),
112            right: r.right(),
113            bottom: r.bottom(),
114        }
115    }
116}
117
118impl Default for BBox {
119    fn default() -> Self {
120        Self {
121            left: f32::MAX,
122            top: f32::MAX,
123            right: f32::MIN,
124            bottom: f32::MIN,
125        }
126    }
127}
128
129impl BBox {
130    /// Checks if the bounding box is default, i.e. invalid.
131    pub fn is_default(&self) -> bool {
132        self.left == f32::MAX
133            && self.top == f32::MAX
134            && self.right == f32::MIN
135            && self.bottom == f32::MIN
136    }
137
138    /// Expand the bounding box to the specified bounds.
139    #[must_use]
140    pub fn expand(&self, r: impl Into<Self>) -> Self {
141        self.expand_impl(r.into())
142    }
143
144    fn expand_impl(&self, r: Self) -> Self {
145        Self {
146            left: self.left.min(r.left),
147            top: self.top.min(r.top),
148            right: self.right.max(r.right),
149            bottom: self.bottom.max(r.bottom),
150        }
151    }
152
153    /// Converts a bounding box into [`Rect`].
154    pub fn to_rect(&self) -> Option<Rect> {
155        if !self.is_default() {
156            Rect::from_ltrb(self.left, self.top, self.right, self.bottom)
157        } else {
158            None
159        }
160    }
161
162    /// Converts a bounding box into [`NonZeroRect`].
163    pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {
164        if !self.is_default() {
165            NonZeroRect::from_ltrb(self.left, self.top, self.right, self.bottom)
166        } else {
167            None
168        }
169    }
170}
171
172/// Returns object aligned position.
173pub(crate) fn aligned_pos(align: Align, x: f32, y: f32, w: f32, h: f32) -> (f32, f32) {
174    match align {
175        Align::None => (x, y),
176        Align::XMinYMin => (x, y),
177        Align::XMidYMin => (x + w / 2.0, y),
178        Align::XMaxYMin => (x + w, y),
179        Align::XMinYMid => (x, y + h / 2.0),
180        Align::XMidYMid => (x + w / 2.0, y + h / 2.0),
181        Align::XMaxYMid => (x + w, y + h / 2.0),
182        Align::XMinYMax => (x, y + h),
183        Align::XMidYMax => (x + w / 2.0, y + h),
184        Align::XMaxYMax => (x + w, y + h),
185    }
186}