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