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::{Align, 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(crate) 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
58impl 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)]
98pub(crate) struct BBox {
99 left: f32,
100 top: f32,
101 right: f32,
102 bottom: f32,
103}
104
105impl 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
116impl 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
127impl 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
138impl 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.
182pub 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