usvg/tree/
geom.rs
1use strict_num::ApproxEqUlps;
6use svgtypes::{Align, AspectRatio};
7pub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform};
8
9pub trait ApproxZeroUlps: ApproxEqUlps {
11 fn approx_zero_ulps(&self, ulps: <Self::Flt as strict_num::Ulps>::U) -> bool;
13}
14
15impl ApproxZeroUlps for f32 {
16 fn approx_zero_ulps(&self, ulps: i32) -> bool {
17 self.approx_eq_ulps(&0.0, ulps)
18 }
19}
20
21impl ApproxZeroUlps for f64 {
22 fn approx_zero_ulps(&self, ulps: i64) -> bool {
23 self.approx_eq_ulps(&0.0, ulps)
24 }
25}
26
27pub(crate) trait IsValidLength {
29 fn is_valid_length(&self) -> bool;
31}
32
33impl IsValidLength for f32 {
34 #[inline]
35 fn is_valid_length(&self) -> bool {
36 *self > 0.0 && self.is_finite()
37 }
38}
39
40impl IsValidLength for f64 {
41 #[inline]
42 fn is_valid_length(&self) -> bool {
43 *self > 0.0 && self.is_finite()
44 }
45}
46
47#[derive(Clone, Copy, Debug)]
49pub(crate) 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 {
70 sy
71 } else {
72 sx
73 }
74 } else {
75 if sx > sy {
76 sy
77 } else {
78 sx
79 }
80 };
81
82 (s, s)
83 };
84
85 let x = -vr.x() * sx;
86 let y = -vr.y() * sy;
87 let w = img_size.width() - vr.width() * sx;
88 let h = img_size.height() - vr.height() * sy;
89
90 let (tx, ty) = aligned_pos(self.aspect.align, x, y, w, h);
91 Transform::from_row(sx, 0.0, 0.0, sy, tx, ty)
92 }
93}
94
95#[derive(Clone, Copy, Debug)]
97pub(crate) struct BBox {
98 left: f32,
99 top: f32,
100 right: f32,
101 bottom: f32,
102}
103
104impl From<Rect> for BBox {
105 fn from(r: Rect) -> Self {
106 Self {
107 left: r.left(),
108 top: r.top(),
109 right: r.right(),
110 bottom: r.bottom(),
111 }
112 }
113}
114
115impl From<NonZeroRect> for BBox {
116 fn from(r: NonZeroRect) -> Self {
117 Self {
118 left: r.left(),
119 top: r.top(),
120 right: r.right(),
121 bottom: r.bottom(),
122 }
123 }
124}
125
126impl Default for BBox {
127 fn default() -> Self {
128 Self {
129 left: f32::MAX,
130 top: f32::MAX,
131 right: f32::MIN,
132 bottom: f32::MIN,
133 }
134 }
135}
136
137impl BBox {
138 pub fn is_default(&self) -> bool {
140 self.left == f32::MAX
141 && self.top == f32::MAX
142 && self.right == f32::MIN
143 && self.bottom == f32::MIN
144 }
145
146 #[must_use]
148 pub fn expand(&self, r: impl Into<Self>) -> Self {
149 self.expand_impl(r.into())
150 }
151
152 fn expand_impl(&self, r: Self) -> Self {
153 Self {
154 left: self.left.min(r.left),
155 top: self.top.min(r.top),
156 right: self.right.max(r.right),
157 bottom: self.bottom.max(r.bottom),
158 }
159 }
160
161 pub fn to_rect(&self) -> Option<Rect> {
163 if !self.is_default() {
164 Rect::from_ltrb(self.left, self.top, self.right, self.bottom)
165 } else {
166 None
167 }
168 }
169
170 pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {
172 if !self.is_default() {
173 NonZeroRect::from_ltrb(self.left, self.top, self.right, self.bottom)
174 } else {
175 None
176 }
177 }
178}
179
180pub(crate) fn aligned_pos(align: Align, x: f32, y: f32, w: f32, h: f32) -> (f32, f32) {
182 match align {
183 Align::None => (x, y),
184 Align::XMinYMin => (x, y),
185 Align::XMidYMin => (x + w / 2.0, y),
186 Align::XMaxYMin => (x + w, y),
187 Align::XMinYMid => (x, y + h / 2.0),
188 Align::XMidYMid => (x + w / 2.0, y + h / 2.0),
189 Align::XMaxYMid => (x + w, y + h / 2.0),
190 Align::XMinYMax => (x, y + h),
191 Align::XMidYMax => (x + w / 2.0, y + h),
192 Align::XMaxYMax => (x + w, y + h),
193 }
194}