read_fonts/model/glyph/color/
bounds.rs1use super::{Brush, CompositeMode, PaintSink};
9use crate::{
10 model::glyph::outline::OutlineGlyphs,
11 types::{BoundingBox, GlyphId, Matrix},
12};
13use smallvec::SmallVec;
14
15#[derive(Clone, Copy)]
17enum Bounds {
18 Unbounded,
20 Bounded(BoundingBox<f32>),
22 Empty,
24}
25
26impl Bounds {
27 fn of(bounds: BoundingBox<f32>) -> Self {
31 if bounds.x_min < bounds.x_max && bounds.y_min < bounds.y_max {
32 Bounds::Bounded(bounds)
33 } else {
34 Bounds::Empty
35 }
36 }
37
38 fn union(&mut self, other: Bounds) {
40 match (*self, other) {
41 (_, Bounds::Unbounded) => *self = Bounds::Unbounded,
42 (Bounds::Empty, Bounds::Bounded(_)) => *self = other,
43 (Bounds::Bounded(a), Bounds::Bounded(b)) => {
44 *self = Bounds::Bounded(BoundingBox {
45 x_min: a.x_min.min(b.x_min),
46 y_min: a.y_min.min(b.y_min),
47 x_max: a.x_max.max(b.x_max),
48 y_max: a.y_max.max(b.y_max),
49 })
50 }
51 _ => {}
52 }
53 }
54
55 fn intersect(&mut self, other: Bounds) {
57 match (*self, other) {
58 (_, Bounds::Empty) => *self = Bounds::Empty,
59 (Bounds::Unbounded, Bounds::Bounded(_)) => *self = other,
60 (Bounds::Bounded(a), Bounds::Bounded(b)) => {
61 *self = Bounds::of(BoundingBox {
62 x_min: a.x_min.max(b.x_min),
63 y_min: a.y_min.max(b.y_min),
64 x_max: a.x_max.min(b.x_max),
65 y_max: a.y_max.min(b.y_max),
66 });
67 }
68 _ => {}
69 }
70 }
71}
72
73fn transform_bounds(transform: &Matrix<f32>, bounds: BoundingBox<f32>) -> BoundingBox<f32> {
76 let corners = [
77 transform.transform(bounds.x_min, bounds.y_min),
78 transform.transform(bounds.x_max, bounds.y_min),
79 transform.transform(bounds.x_min, bounds.y_max),
80 transform.transform(bounds.x_max, bounds.y_max),
81 ];
82 let (first, rest) = corners.split_first().unwrap();
83 rest.iter().fold(
84 BoundingBox {
85 x_min: first.0,
86 y_min: first.1,
87 x_max: first.0,
88 y_max: first.1,
89 },
90 |bounds, (x, y)| BoundingBox {
91 x_min: bounds.x_min.min(*x),
92 y_min: bounds.y_min.min(*y),
93 x_max: bounds.x_max.max(*x),
94 y_max: bounds.y_max.max(*y),
95 },
96 )
97}
98
99pub(super) struct BoundsSink<'a> {
101 outlines: OutlineGlyphs<'a>,
102 transforms: SmallVec<[Matrix<f32>; 8]>,
104 clips: SmallVec<[Bounds; 8]>,
106 groups: SmallVec<[Bounds; 8]>,
108}
109
110impl<'a> BoundsSink<'a> {
111 pub(super) fn new(outlines: OutlineGlyphs<'a>) -> Self {
112 let mut sink = Self {
113 outlines,
114 transforms: SmallVec::new(),
115 clips: SmallVec::new(),
116 groups: SmallVec::new(),
117 };
118 sink.transforms.push(Matrix::IDENTITY);
119 sink.clips.push(Bounds::Unbounded);
120 sink.groups.push(Bounds::Empty);
121 sink
122 }
123
124 pub(super) fn bounds(&self) -> BoundingBox<f32> {
127 match self.groups.last() {
128 Some(Bounds::Bounded(bounds)) => *bounds,
129 _ => BoundingBox::default(),
130 }
131 }
132
133 fn current_transform(&self) -> &Matrix<f32> {
134 self.transforms.last().unwrap_or(&Matrix::IDENTITY)
135 }
136
137 fn current_clip(&self) -> Bounds {
138 self.clips.last().copied().unwrap_or(Bounds::Unbounded)
139 }
140
141 fn push_clip(&mut self, bounds: Bounds) {
143 let mut clip = self.current_clip();
144 clip.intersect(bounds);
145 self.clips.push(clip);
146 }
147
148 fn paint(&mut self) {
150 let clip = self.current_clip();
151 if let Some(group) = self.groups.last_mut() {
152 group.union(clip);
153 }
154 }
155}
156
157impl PaintSink for BoundsSink<'_> {
158 fn push_transform(&mut self, transform: Matrix<f32>) {
159 let transform = *self.current_transform() * transform;
160 self.transforms.push(transform);
161 }
162
163 fn pop_transform(&mut self) {
164 if self.transforms.len() > 1 {
165 self.transforms.pop();
166 }
167 }
168
169 fn push_clip_glyph(&mut self, glyph: GlyphId) {
170 let bounds = match self
172 .outlines
173 .get(glyph)
174 .and_then(|glyph| glyph.control_box().ok())
175 {
176 Some(Some(bounds)) => Bounds::of(transform_bounds(self.current_transform(), bounds)),
177 _ => Bounds::Empty,
178 };
179 self.push_clip(bounds);
180 }
181
182 fn push_clip_box(&mut self, clip_box: BoundingBox<f32>) {
183 let bounds = Bounds::of(transform_bounds(self.current_transform(), clip_box));
184 self.push_clip(bounds);
185 }
186
187 fn pop_clip(&mut self) {
188 if self.clips.len() > 1 {
189 self.clips.pop();
190 }
191 }
192
193 fn fill(&mut self, _brush: Brush<'_>) {
194 self.paint();
195 }
196
197 fn push_layer(&mut self, _composite_mode: CompositeMode) {
198 self.groups.push(Bounds::Empty);
199 }
200
201 fn pop_layer_with_mode(&mut self, composite_mode: CompositeMode) {
202 if self.groups.len() < 2 {
203 return;
204 }
205 let source = self.groups.pop().unwrap_or(Bounds::Empty);
206 let Some(backdrop) = self.groups.last_mut() else {
207 return;
208 };
209 match composite_mode {
210 CompositeMode::Clear => *backdrop = Bounds::Empty,
211 CompositeMode::Src | CompositeMode::SrcOut => *backdrop = source,
212 CompositeMode::Dest | CompositeMode::DestOut => {}
213 CompositeMode::SrcIn | CompositeMode::DestIn => backdrop.intersect(source),
214 _ => backdrop.union(source),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 fn boxed(x_min: f32, y_min: f32, x_max: f32, y_max: f32) -> Bounds {
224 Bounds::Bounded(BoundingBox {
225 x_min,
226 y_min,
227 x_max,
228 y_max,
229 })
230 }
231
232 #[test]
233 fn union_and_intersection() {
234 let mut a = boxed(0.0, 0.0, 10.0, 10.0);
235 a.union(boxed(5.0, -5.0, 20.0, 5.0));
236 assert!(
237 matches!(a, Bounds::Bounded(b) if b == BoundingBox { x_min: 0.0, y_min: -5.0, x_max: 20.0, y_max: 10.0 })
238 );
239 a.intersect(boxed(15.0, 0.0, 30.0, 30.0));
240 assert!(
241 matches!(a, Bounds::Bounded(b) if b == BoundingBox { x_min: 15.0, y_min: 0.0, x_max: 20.0, y_max: 10.0 })
242 );
243 a.intersect(boxed(100.0, 100.0, 200.0, 200.0));
244 assert!(matches!(a, Bounds::Empty));
245 let mut unbounded = Bounds::Unbounded;
246 unbounded.intersect(boxed(1.0, 2.0, 3.0, 4.0));
247 assert!(matches!(unbounded, Bounds::Bounded(_)));
248 let mut empty = Bounds::Empty;
249 empty.union(Bounds::Unbounded);
250 assert!(matches!(empty, Bounds::Unbounded));
251 }
252
253 #[test]
255 fn a_box_without_area_is_empty() {
256 let line = BoundingBox {
257 x_min: 5.0,
258 y_min: 0.0,
259 x_max: 5.0,
260 y_max: 10.0,
261 };
262 assert!(matches!(Bounds::of(line), Bounds::Empty));
263 let mut a = boxed(0.0, 0.0, 5.0, 10.0);
265 a.intersect(boxed(5.0, 0.0, 10.0, 10.0));
266 assert!(matches!(a, Bounds::Empty));
267 let font = crate::model::Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
269 let mut sink = BoundsSink::new(font.outline_glyphs());
270 sink.push_clip_box(line);
271 sink.fill(Brush::Solid {
272 palette_index: 0,
273 alpha: 1.0,
274 });
275 sink.pop_clip();
276 assert_eq!(sink.bounds(), BoundingBox::default());
277 }
278
279 #[test]
280 fn a_transformed_box_is_the_box_around_its_corners() {
281 let rotate = Matrix {
283 xx: 0.0,
284 yx: 1.0,
285 xy: -1.0,
286 yy: 0.0,
287 dx: 0.0,
288 dy: 0.0,
289 };
290 let bounds = transform_bounds(
291 &rotate,
292 BoundingBox {
293 x_min: 1.0,
294 y_min: 2.0,
295 x_max: 3.0,
296 y_max: 4.0,
297 },
298 );
299 assert_eq!(
300 bounds,
301 BoundingBox {
302 x_min: -4.0,
303 y_min: 1.0,
304 x_max: -2.0,
305 y_max: 3.0,
306 }
307 );
308 }
309}