rustybuzz/hb/
paint_extents.rs

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}