1use alloc::vec;
2use ttf_parser::colr::{ClipBox, CompositeMode, Paint};
3use ttf_parser::{GlyphId, RectF, Transform};
4
5/*
6 * This file implements bounds-extraction as well as boundedness
7 * computation of COLRv1 fonts as described in:
8 *
9 * https://learn.microsoft.com/en-us/typography/opentype/spec/colr#glyph-metrics-and-boundedness
10 */
11
12#[derive(Copy, Clone)]
13pub(crate) struct hb_extents_t {
14 pub x_min: f32,
15 pub y_min: f32,
16 pub x_max: f32,
17 pub y_max: f32,
18}
19
20impl hb_extents_t {
21 pub fn is_empty(&self) -> bool {
22 self.x_min >= self.x_max || self.y_min >= self.y_max
23 }
24 pub fn is_void(&self) -> bool {
25 self.x_min > self.x_max
26 }
27
28 pub fn union_(&mut self, o: &hb_extents_t) {
29 self.x_min = o.x_min.min(o.x_min);
30 self.y_min = o.y_min.min(o.y_min);
31 self.x_max = o.x_max.max(o.x_max);
32 self.y_max = o.y_max.max(o.y_max);
33 }
34
35 pub fn intersect(&mut self, o: &hb_extents_t) {
36 self.x_min = o.x_min.max(o.x_min);
37 self.y_min = o.y_min.max(o.y_min);
38 self.x_max = o.x_max.min(o.x_max);
39 self.y_max = o.y_max.min(o.y_max);
40 }
41}
42
43impl From<RectF> for hb_extents_t {
44 fn from(val: RectF) -> Self {
45 Self {
46 x_min: val.x_min,
47 y_min: val.y_min,
48 x_max: val.x_max,
49 y_max: val.y_max,
50 }
51 }
52}
53
54#[derive(PartialEq, Eq, Clone, Copy)]
55enum status_t {
56 EMPTY,
57 BOUNDED,
58 UNBOUNDED,
59}
60
61#[derive(Clone, Copy)]
62pub(crate) struct hb_bounds_t {
63 status: status_t,
64 extents: hb_extents_t,
65}
66
67impl hb_bounds_t {
68 fn from_extents(extents: &hb_extents_t) -> Self {
69 let status = if extents.is_empty() {
70 status_t::EMPTY
71 } else {
72 status_t::BOUNDED
73 };
74
75 hb_bounds_t {
76 extents: *extents,
77 status,
78 }
79 }
80
81 fn from_status(status: status_t) -> Self {
82 hb_bounds_t {
83 status,
84 ..hb_bounds_t::default()
85 }
86 }
87
88 fn union(&mut self, o: &hb_bounds_t) {
89 if o.status == status_t::UNBOUNDED {
90 self.status = status_t::UNBOUNDED;
91 } else if o.status == status_t::BOUNDED {
92 if self.status == status_t::EMPTY {
93 *self = *o;
94 } else if self.status == status_t::BOUNDED {
95 self.extents.union_(&o.extents);
96 }
97 }
98 }
99
100 fn intersect(&mut self, o: &hb_bounds_t) {
101 if o.status == status_t::EMPTY {
102 self.status = status_t::EMPTY;
103 } else if o.status == status_t::BOUNDED {
104 if self.status == status_t::UNBOUNDED {
105 *self = *o;
106 } else if self.status == status_t::BOUNDED {
107 self.extents.intersect(&o.extents);
108
109 if self.extents.is_empty() {
110 self.status = status_t::EMPTY;
111 }
112 }
113 }
114 }
115}
116
117impl Default for hb_bounds_t {
118 fn default() -> Self {
119 Self::from_extents(&hb_extents_t {
120 x_min: 0.0,
121 x_max: 0.0,
122 y_min: 0.0,
123 y_max: 0.0,
124 })
125 }
126}
127
128pub(crate) struct hb_paint_extents_context_t<'a> {
129 clips: vec::Vec<hb_bounds_t>,
130 groups: vec::Vec<hb_bounds_t>,
131 transforms: vec::Vec<Transform>,
132 // Doesn't exist in harfbuzz. The reason we need it is that in harfbuzz, composite modes
133 // are passed as part of `pop`, while ttf-parser passes it as part of `push`, so we need to
134 // store it in the meanwhile.
135 composite_modes: vec::Vec<CompositeMode>,
136 face: &'a ttf_parser::Face<'a>,
137 current_glyph: GlyphId,
138}
139
140impl<'a> hb_paint_extents_context_t<'a> {
141 pub(crate) fn new(face: &'a ttf_parser::Face<'a>) -> Self {
142 Self {
143 clips: vec![hb_bounds_t::from_status(status_t::UNBOUNDED)],
144 groups: vec![hb_bounds_t::from_status(status_t::EMPTY)],
145 transforms: vec![Transform::default()],
146 composite_modes: vec![CompositeMode::SourceOver],
147 face,
148 current_glyph: Default::default(),
149 }
150 }
151
152 pub(crate) fn get_extents(&self) -> hb_extents_t {
153 // harfbuzz doesn't have the unwrap_or_default part, but in a valid font
154 // this should always be valid anyway.
155 self.groups.last().copied().unwrap_or_default().extents
156 }
157
158 fn push_transform(&mut self, trans: &Transform) {
159 let t = self
160 .transforms
161 .last()
162 .copied()
163 .unwrap_or(Transform::default());
164 let new = Transform::combine(t, *trans);
165 self.transforms.push(new);
166 }
167
168 fn pop_transform(&mut self) {
169 self.transforms.pop();
170 }
171
172 fn push_clip(&mut self, mut extents: hb_extents_t) {
173 if let Some(r) = self.transforms.last_mut() {
174 r.transform_extents(&mut extents);
175 }
176
177 let b = hb_bounds_t::from_extents(&extents);
178 self.clips.push(b);
179 }
180
181 fn pop_clip(&mut self) {
182 self.clips.pop();
183 }
184
185 fn push_group(&mut self) {
186 self.groups.push(hb_bounds_t::default());
187 }
188
189 fn pop_group(&mut self) {
190 if let Some(mode) = self.composite_modes.pop() {
191 if let Some(src_bounds) = self.groups.pop() {
192 if let Some(backdrop_bounds) = self.groups.last_mut() {
193 match mode {
194 CompositeMode::Clear => backdrop_bounds.status = status_t::EMPTY,
195 CompositeMode::Source | CompositeMode::SourceOut => {
196 *backdrop_bounds = src_bounds
197 }
198 CompositeMode::Destination | CompositeMode::DestinationOut => {}
199 CompositeMode::SourceIn | CompositeMode::DestinationIn => {
200 backdrop_bounds.intersect(&src_bounds)
201 }
202 _ => backdrop_bounds.union(&src_bounds),
203 }
204 }
205 }
206 }
207 }
208
209 fn paint(&mut self) {
210 if let (Some(clip), Some(group)) = (self.clips.last(), self.groups.last_mut()) {
211 group.union(clip);
212 }
213 }
214}
215
216impl ttf_parser::colr::Painter<'_> for hb_paint_extents_context_t<'_> {
217 fn outline_glyph(&mut self, glyph_id: GlyphId) {
218 self.current_glyph = glyph_id;
219 }
220
221 fn paint(&mut self, _: Paint<'_>) {
222 self.paint();
223 }
224
225 fn push_clip(&mut self) {
226 if let Some(glyph_bbox) = self.face.glyph_bounding_box(self.current_glyph) {
227 self.push_clip(hb_extents_t {
228 x_min: glyph_bbox.x_min as f32,
229 y_min: glyph_bbox.y_min as f32,
230 x_max: glyph_bbox.x_max as f32,
231 y_max: glyph_bbox.y_max as f32,
232 });
233 }
234 }
235
236 fn push_clip_box(&mut self, clipbox: ClipBox) {
237 self.push_clip(clipbox.into());
238 }
239
240 fn pop_clip(&mut self) {
241 self.pop_clip();
242 }
243
244 fn push_layer(&mut self, mode: CompositeMode) {
245 self.composite_modes.push(mode);
246 self.push_group();
247 }
248
249 fn pop_layer(&mut self) {
250 self.pop_group();
251 }
252
253 fn push_transform(&mut self, transform: Transform) {
254 self.push_transform(&transform);
255 }
256
257 fn pop_transform(&mut self) {
258 self.pop_transform();
259 }
260}
261
262trait TransformExt {
263 fn transform_distance(&self, dx: &mut f32, dy: &mut f32);
264 fn transform_point(&self, x: &mut f32, y: &mut f32);
265 fn transform_extents(&self, extents: &mut hb_extents_t);
266}
267
268impl TransformExt for Transform {
269 fn transform_distance(&self, dx: &mut f32, dy: &mut f32) {
270 let new_x = self.a * (*dx) + self.c * (*dy);
271 let new_y = self.b * (*dx) + self.d * (*dy);
272 *dx = new_x;
273 *dy = new_y;
274 }
275
276 fn transform_point(&self, x: &mut f32, y: &mut f32) {
277 self.transform_distance(x, y);
278 *x += self.e;
279 *y += self.f;
280 }
281
282 fn transform_extents(&self, extents: &mut hb_extents_t) {
283 let mut quad_x = [0.0f32; 4];
284 let mut quad_y = [0.0f32; 4];
285
286 quad_x[0] = extents.x_min;
287 quad_y[0] = extents.y_min;
288 quad_x[1] = extents.x_min;
289 quad_y[1] = extents.y_max;
290 quad_x[2] = extents.x_max;
291 quad_y[2] = extents.y_min;
292 quad_x[3] = extents.x_max;
293 quad_y[3] = extents.y_max;
294
295 for i in 0..4 {
296 self.transform_point(&mut quad_x[i], &mut quad_y[i])
297 }
298
299 extents.x_max = quad_x[0];
300 extents.x_min = extents.x_max;
301 extents.y_max = quad_y[0];
302 extents.y_min = extents.y_max;
303
304 for i in 1..4 {
305 extents.x_min = extents.x_min.min(quad_x[i]);
306 extents.y_min = extents.y_min.min(quad_y[i]);
307 extents.x_max = extents.x_max.max(quad_x[i]);
308 extents.y_max = extents.y_max.max(quad_y[i]);
309 }
310 }
311}
312