1 | // Copyright 2018 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | use strict_num::ApproxEqUlps; |
5 | use svgtypes::{Align, AspectRatio}; |
6 | pub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform}; |
7 | |
8 | /// Approximate zero equality comparisons. |
9 | pub trait ApproxZeroUlps: ApproxEqUlps { |
10 | /// Checks if the number is approximately zero. |
11 | fn approx_zero_ulps(&self, ulps: <Self::Flt as strict_num::Ulps>::U) -> bool; |
12 | } |
13 | |
14 | impl ApproxZeroUlps for f32 { |
15 | fn approx_zero_ulps(&self, ulps: i32) -> bool { |
16 | self.approx_eq_ulps(&0.0, ulps) |
17 | } |
18 | } |
19 | |
20 | impl ApproxZeroUlps for f64 { |
21 | fn approx_zero_ulps(&self, ulps: i64) -> bool { |
22 | self.approx_eq_ulps(&0.0, ulps) |
23 | } |
24 | } |
25 | |
26 | /// Checks that the current number is > 0. |
27 | pub(crate) trait IsValidLength { |
28 | /// Checks that the current number is > 0. |
29 | fn is_valid_length(&self) -> bool; |
30 | } |
31 | |
32 | impl IsValidLength for f32 { |
33 | #[inline ] |
34 | fn is_valid_length(&self) -> bool { |
35 | *self > 0.0 && self.is_finite() |
36 | } |
37 | } |
38 | |
39 | impl IsValidLength for f64 { |
40 | #[inline ] |
41 | fn is_valid_length(&self) -> bool { |
42 | *self > 0.0 && self.is_finite() |
43 | } |
44 | } |
45 | |
46 | /// View box. |
47 | #[derive (Clone, Copy, Debug)] |
48 | pub(crate) struct ViewBox { |
49 | /// Value of the `viewBox` attribute. |
50 | pub rect: NonZeroRect, |
51 | |
52 | /// Value of the `preserveAspectRatio` attribute. |
53 | pub aspect: AspectRatio, |
54 | } |
55 | |
56 | impl ViewBox { |
57 | /// Converts `viewBox` into `Transform`. |
58 | 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 | /// A bounding box calculator. |
95 | #[derive (Clone, Copy, Debug)] |
96 | pub(crate) struct BBox { |
97 | left: f32, |
98 | top: f32, |
99 | right: f32, |
100 | bottom: f32, |
101 | } |
102 | |
103 | impl 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 | |
114 | impl 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 | |
125 | impl 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 | |
136 | impl BBox { |
137 | /// Checks if the bounding box is default, i.e. invalid. |
138 | 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 | /// Expand the bounding box to the specified bounds. |
146 | #[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 | /// Converts a bounding box into [`Rect`]. |
161 | 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 | /// Converts a bounding box into [`NonZeroRect`]. |
170 | 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 | |
179 | /// Returns object aligned position. |
180 | pub(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 | } |
194 | |