1use strict_num::ApproxEqUlps;
5use svgtypes::{Align, AspectRatio};
6pub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform};
7
8pub trait ApproxZeroUlps: ApproxEqUlps {
10 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
26pub(crate) trait IsValidLength {
28 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#[derive(Clone, Copy, Debug)]
48pub(crate) struct ViewBox {
49 pub rect: NonZeroRect,
51
52 pub aspect: AspectRatio,
54}
55
56impl ViewBox {
57 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#[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 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 #[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 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 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
179pub(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}