1use strict_num::ApproxEqUlps;
6pub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform};
7
8use crate::AspectRatio;
9
10pub trait ApproxZeroUlps: ApproxEqUlps {
12 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
28pub trait IsValidLength {
30 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#[derive(Clone, Copy, Debug)]
50pub struct ViewBox {
51 pub rect: NonZeroRect,
53
54 pub aspect: AspectRatio,
56}
57
58#[derive(Clone, Copy, Debug)]
60pub struct BBox {
61 left: f32,
62 top: f32,
63 right: f32,
64 bottom: f32,
65}
66
67impl From<Rect> for BBox {
68 fn from(r: Rect) -> Self {
69 Self {
70 left: r.left(),
71 top: r.top(),
72 right: r.right(),
73 bottom: r.bottom(),
74 }
75 }
76}
77
78impl From<NonZeroRect> for BBox {
79 fn from(r: NonZeroRect) -> Self {
80 Self {
81 left: r.left(),
82 top: r.top(),
83 right: r.right(),
84 bottom: r.bottom(),
85 }
86 }
87}
88
89impl Default for BBox {
90 fn default() -> Self {
91 Self {
92 left: f32::MAX,
93 top: f32::MAX,
94 right: f32::MIN,
95 bottom: f32::MIN,
96 }
97 }
98}
99
100impl BBox {
101 pub fn is_default(&self) -> bool {
103 self.left == f32::MAX
104 && self.top == f32::MAX
105 && self.right == f32::MIN
106 && self.bottom == f32::MIN
107 }
108
109 #[must_use]
111 pub fn expand(&self, r: impl Into<Self>) -> Self {
112 self.expand_impl(r.into())
113 }
114
115 fn expand_impl(&self, r: Self) -> Self {
116 Self {
117 left: self.left.min(r.left),
118 top: self.top.min(r.top),
119 right: self.right.max(r.right),
120 bottom: self.bottom.max(r.bottom),
121 }
122 }
123
124 pub fn transform(&self, ts: tiny_skia_path::Transform) -> Option<Self> {
126 self.to_rect()?.transform(ts).map(Self::from)
127 }
128
129 pub fn to_rect(&self) -> Option<Rect> {
131 if !self.is_default() {
132 Rect::from_ltrb(self.left, self.top, self.right, self.bottom)
133 } else {
134 None
135 }
136 }
137
138 pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {
140 if !self.is_default() {
141 NonZeroRect::from_ltrb(self.left, self.top, self.right, self.bottom)
142 } else {
143 None
144 }
145 }
146}
147
148pub mod utils {
150 use super::*;
151 use crate::Align;
152
153 pub fn view_box_to_transform(
155 view_box: NonZeroRect,
156 aspect: AspectRatio,
157 img_size: Size,
158 ) -> Transform {
159 let vr = view_box;
160
161 let sx = img_size.width() / vr.width();
162 let sy = img_size.height() / vr.height();
163
164 let (sx, sy) = if aspect.align == Align::None {
165 (sx, sy)
166 } else {
167 let s = if aspect.slice {
168 if sx < sy {
169 sy
170 } else {
171 sx
172 }
173 } else {
174 if sx > sy {
175 sy
176 } else {
177 sx
178 }
179 };
180
181 (s, s)
182 };
183
184 let x = -vr.x() * sx;
185 let y = -vr.y() * sy;
186 let w = img_size.width() - vr.width() * sx;
187 let h = img_size.height() - vr.height() * sy;
188
189 let (tx, ty) = aligned_pos(aspect.align, x, y, w, h);
190 Transform::from_row(sx, 0.0, 0.0, sy, tx, ty)
191 }
192
193 pub fn aligned_pos(align: Align, x: f32, y: f32, w: f32, h: f32) -> (f32, f32) {
195 match align {
196 Align::None => (x, y),
197 Align::XMinYMin => (x, y),
198 Align::XMidYMin => (x + w / 2.0, y),
199 Align::XMaxYMin => (x + w, y),
200 Align::XMinYMid => (x, y + h / 2.0),
201 Align::XMidYMid => (x + w / 2.0, y + h / 2.0),
202 Align::XMaxYMid => (x + w, y + h / 2.0),
203 Align::XMinYMax => (x, y + h),
204 Align::XMidYMax => (x + w / 2.0, y + h),
205 Align::XMaxYMax => (x + w, y + h),
206 }
207 }
208}