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#[allow(missing_docs)]
48#[derive(Clone, Copy, Debug)]
49pub struct ViewBox {
50 pub rect: NonZeroRect,
52
53 pub aspect: AspectRatio,
55}
56
57impl ViewBox {
58 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#[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 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 #[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 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 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
172pub(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}