Coverage Report

Created: 2025-12-31 07:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontations/write-fonts/src/tables/glyf/composite.rs
Line
Count
Source
1
//! Composite glyphs (containing other glyphs as components)
2
3
use crate::{
4
    from_obj::{FromObjRef, FromTableRef, ToOwnedTable},
5
    FontWrite,
6
};
7
8
use read_fonts::{tables::glyf::CompositeGlyphFlags, types::GlyphId16, FontRead};
9
10
use super::Bbox;
11
12
pub use read_fonts::tables::glyf::{Anchor, Transform};
13
14
/// A glyph consisting of multiple component sub-glyphs
15
#[derive(Clone, Debug, PartialEq, Eq)]
16
pub struct CompositeGlyph {
17
    pub bbox: Bbox,
18
    components: Vec<Component>,
19
    _instructions: Vec<u8>,
20
}
21
22
/// A single component glyph (part of a [`CompositeGlyph`]).
23
#[derive(Clone, Debug, PartialEq, Eq)]
24
pub struct Component {
25
    pub glyph: GlyphId16,
26
    pub anchor: Anchor,
27
    pub flags: ComponentFlags,
28
    pub transform: Transform,
29
}
30
31
/// Options that can be manually set for a given component.
32
///
33
/// This provides an easier interface for setting those flags that are not
34
/// calculated based on other properties of the glyph. For more information
35
/// on these flags, see [Component Glyph Flags](flags-spec) in the spec.
36
///
37
/// These eventually are combined with calculated flags into the
38
/// [`CompositeGlyphFlags`] bitset.
39
///
40
/// [flags-spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/glyf#compositeGlyphFlags
41
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
42
pub struct ComponentFlags {
43
    /// Round xy values to the nearest grid line
44
    pub round_xy_to_grid: bool,
45
    /// Use the advance/lsb/rsb values of this component for the whole
46
    /// composite glyph
47
    pub use_my_metrics: bool,
48
    /// The composite should have this component's offset scaled
49
    pub scaled_component_offset: bool,
50
    /// The composite should *not* have this component's offset scaled
51
    pub unscaled_component_offset: bool,
52
    /// If set, the components of the composite glyph overlap.
53
    pub overlap_compound: bool,
54
}
55
56
impl FromObjRef<read_fonts::tables::glyf::CompositeGlyph<'_>> for CompositeGlyph {
57
0
    fn from_obj_ref(
58
0
        from: &read_fonts::tables::glyf::CompositeGlyph,
59
0
        _data: read_fonts::FontData,
60
0
    ) -> Self {
61
0
        let bbox = Bbox {
62
0
            x_min: from.x_min(),
63
0
            y_min: from.y_min(),
64
0
            x_max: from.x_max(),
65
0
            y_max: from.y_max(),
66
0
        };
67
0
        let components = from
68
0
            .components()
69
0
            .map(|c| Component {
70
0
                glyph: c.glyph,
71
0
                anchor: c.anchor,
72
0
                flags: c.flags.into(),
73
0
                transform: c.transform,
74
0
            })
75
0
            .collect();
76
        Self {
77
0
            bbox,
78
0
            components,
79
0
            _instructions: from
80
0
                .instructions()
81
0
                .map(|v| v.to_owned())
82
0
                .unwrap_or_default(),
83
        }
84
0
    }
85
}
86
87
impl FromTableRef<read_fonts::tables::glyf::CompositeGlyph<'_>> for CompositeGlyph {}
88
89
impl<'a> FontRead<'a> for CompositeGlyph {
90
0
    fn read(data: read_fonts::FontData<'a>) -> Result<Self, read_fonts::ReadError> {
91
0
        read_fonts::tables::glyf::CompositeGlyph::read(data).map(|g| g.to_owned_table())
92
0
    }
93
}
94
95
impl Component {
96
    /// Create a new component.
97
0
    pub fn new(
98
0
        glyph: GlyphId16,
99
0
        anchor: Anchor,
100
0
        transform: Transform,
101
0
        flags: impl Into<ComponentFlags>,
102
0
    ) -> Self {
103
0
        Component {
104
0
            glyph,
105
0
            anchor,
106
0
            flags: flags.into(),
107
0
            transform,
108
0
        }
109
0
    }
110
    /// Compute the flags for this glyph, excepting `MORE_COMPONENTS` and
111
    /// `WE_HAVE_INSTRUCTIONS`, which must be set manually
112
0
    fn compute_flag(&self) -> CompositeGlyphFlags {
113
0
        self.anchor.compute_flags() | self.transform.compute_flags() | self.flags.into()
114
0
    }
115
116
    /// like `FontWrite` but lets us pass in the flags that must be determined
117
    /// externally (WE_HAVE_INSTRUCTIONS and MORE_COMPONENTS)
118
0
    fn write_into(&self, writer: &mut crate::TableWriter, extra_flags: CompositeGlyphFlags) {
119
0
        let flags = self.compute_flag() | extra_flags;
120
0
        flags.bits().write_into(writer);
121
0
        self.glyph.write_into(writer);
122
0
        self.anchor.write_into(writer);
123
0
        self.transform.write_into(writer);
124
0
    }
125
}
126
127
/// An error that occurs if a `CompositeGlyph` is constructed with no components.
128
#[derive(Clone, Copy, Debug)]
129
#[non_exhaustive]
130
pub struct NoComponents;
131
132
impl std::fmt::Display for NoComponents {
133
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134
0
        write!(f, "A composite glyph must contain at least one component")
135
0
    }
136
}
137
138
impl std::error::Error for NoComponents {}
139
140
impl CompositeGlyph {
141
    /// Create a new composite glyph, with the provided component.
142
    ///
143
    /// The 'bbox' argument is the bounding box of the glyph after the transform
144
    /// has been applied.
145
    ///
146
    /// Additional components can be added with [`add_component`][Self::add_component]
147
0
    pub fn new(component: Component, bbox: impl Into<Bbox>) -> Self {
148
0
        Self {
149
0
            bbox: bbox.into(),
150
0
            components: vec![component],
151
0
            _instructions: Default::default(),
152
0
        }
153
0
    }
154
155
    /// Add a new component to this glyph
156
    ///
157
    /// The 'bbox' argument is the bounding box of the glyph after the transform
158
    /// has been applied.
159
0
    pub fn add_component(&mut self, component: Component, bbox: impl Into<Bbox>) {
160
0
        self.components.push(component);
161
0
        self.bbox = self.bbox.union(bbox.into());
162
0
    }
163
164
    /// Construct a `CompositeGlyph` from an iterator of `Component` and `Bbox`es.
165
    ///
166
    /// This returns an error if the iterator is empty; a CompositeGlyph must always
167
    /// contain at least one component.
168
0
    pub fn try_from_iter(
169
0
        source: impl IntoIterator<Item = (Component, Bbox)>,
170
0
    ) -> Result<Self, NoComponents> {
171
0
        let mut components = Vec::new();
172
0
        let mut union_box: Option<Bbox> = None;
173
174
0
        for (component, bbox) in source {
175
0
            components.push(component);
176
0
            union_box.get_or_insert(bbox).union(bbox);
177
0
        }
178
179
0
        if components.is_empty() {
180
0
            Err(NoComponents)
181
        } else {
182
0
            Ok(CompositeGlyph {
183
0
                bbox: union_box.unwrap(),
184
0
                components,
185
0
                _instructions: Default::default(),
186
0
            })
187
        }
188
0
    }
189
190
0
    pub fn components(&self) -> &[Component] {
191
0
        &self.components
192
0
    }
193
}
194
195
impl FontWrite for CompositeGlyph {
196
0
    fn write_into(&self, writer: &mut crate::TableWriter) {
197
        const N_CONTOURS: i16 = -1;
198
0
        N_CONTOURS.write_into(writer);
199
0
        self.bbox.write_into(writer);
200
0
        let (last, rest) = self
201
0
            .components
202
0
            .split_last()
203
0
            .expect("empty composites checked in validation");
204
0
        for comp in rest {
205
0
            comp.write_into(writer, CompositeGlyphFlags::MORE_COMPONENTS);
206
0
        }
207
0
        let last_flags = if self._instructions.is_empty() {
208
0
            CompositeGlyphFlags::empty()
209
        } else {
210
0
            CompositeGlyphFlags::WE_HAVE_INSTRUCTIONS
211
        };
212
0
        last.write_into(writer, last_flags);
213
214
0
        if !self._instructions.is_empty() {
215
0
            (self._instructions.len() as u16).write_into(writer);
216
0
            self._instructions.write_into(writer);
217
0
        }
218
0
        writer.pad_to_2byte_aligned();
219
0
    }
220
}
221
222
impl crate::validate::Validate for CompositeGlyph {
223
0
    fn validate_impl(&self, ctx: &mut crate::codegen_prelude::ValidationCtx) {
224
0
        if self.components.is_empty() {
225
0
            ctx.report("composite glyph must have components");
226
0
        }
227
0
        if self._instructions.len() > u16::MAX as usize {
228
0
            ctx.report("instructions len overflows");
229
0
        }
230
0
    }
231
}
232
233
impl FontWrite for Anchor {
234
0
    fn write_into(&self, writer: &mut crate::TableWriter) {
235
0
        let two_bytes = self
236
0
            .compute_flags()
237
0
            .contains(CompositeGlyphFlags::ARG_1_AND_2_ARE_WORDS);
238
0
        match self {
239
0
            Anchor::Offset { x, y } if !two_bytes => [*x as i8, *y as i8].write_into(writer),
240
0
            Anchor::Offset { x, y } => [*x, *y].write_into(writer),
241
0
            Anchor::Point { base, component } if !two_bytes => {
242
0
                [*base as u8, *component as u8].write_into(writer)
243
            }
244
0
            Anchor::Point { base, component } => [*base, *component].write_into(writer),
245
        }
246
0
    }
247
}
248
249
impl FontWrite for Transform {
250
0
    fn write_into(&self, writer: &mut crate::TableWriter) {
251
0
        let flags = self.compute_flags();
252
0
        if flags.contains(CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO) {
253
0
            [self.xx, self.yx, self.xy, self.yy].write_into(writer);
254
0
        } else if flags.contains(CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE) {
255
0
            [self.xx, self.yy].write_into(writer);
256
0
        } else if flags.contains(CompositeGlyphFlags::WE_HAVE_A_SCALE) {
257
0
            self.xx.write_into(writer)
258
0
        }
259
0
    }
260
}
261
262
impl From<CompositeGlyphFlags> for ComponentFlags {
263
0
    fn from(src: CompositeGlyphFlags) -> ComponentFlags {
264
0
        ComponentFlags {
265
0
            round_xy_to_grid: src.contains(CompositeGlyphFlags::ROUND_XY_TO_GRID),
266
0
            use_my_metrics: src.contains(CompositeGlyphFlags::USE_MY_METRICS),
267
0
            scaled_component_offset: src.contains(CompositeGlyphFlags::SCALED_COMPONENT_OFFSET),
268
0
            unscaled_component_offset: src.contains(CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET),
269
0
            overlap_compound: src.contains(CompositeGlyphFlags::OVERLAP_COMPOUND),
270
0
        }
271
0
    }
272
}
273
274
impl From<ComponentFlags> for CompositeGlyphFlags {
275
0
    fn from(value: ComponentFlags) -> Self {
276
0
        (if value.round_xy_to_grid {
277
0
            CompositeGlyphFlags::ROUND_XY_TO_GRID
278
        } else {
279
0
            Default::default()
280
0
        }) | if value.use_my_metrics {
281
0
            CompositeGlyphFlags::USE_MY_METRICS
282
        } else {
283
0
            Default::default()
284
0
        } | if value.scaled_component_offset {
285
0
            CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
286
        } else {
287
0
            Default::default()
288
0
        } | if value.unscaled_component_offset {
289
0
            CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET
290
        } else {
291
0
            Default::default()
292
0
        } | if value.overlap_compound {
293
0
            CompositeGlyphFlags::OVERLAP_COMPOUND
294
        } else {
295
0
            Default::default()
296
        }
297
0
    }
298
}
299
300
#[cfg(test)]
301
mod tests {
302
303
    use read_fonts::{
304
        tables::glyf as read_glyf, types::GlyphId, FontData, FontRead, FontRef, TableProvider,
305
    };
306
307
    use super::*;
308
309
    #[test]
310
    fn roundtrip_composite() {
311
        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
312
        let loca = font.loca(None).unwrap();
313
        let glyf = font.glyf().unwrap();
314
        let read_glyf::Glyph::Composite(orig) =
315
            loca.get_glyf(GlyphId::new(2), &glyf).unwrap().unwrap()
316
        else {
317
            panic!("not a composite glyph")
318
        };
319
320
        let bbox = Bbox {
321
            x_min: orig.x_min(),
322
            y_min: orig.y_min(),
323
            x_max: orig.x_max(),
324
            y_max: orig.y_max(),
325
        };
326
        let mut iter = orig
327
            .components()
328
            .map(|comp| Component::new(comp.glyph, comp.anchor, comp.transform, comp.flags));
329
        let mut composite = CompositeGlyph::new(iter.next().unwrap(), bbox);
330
        composite.add_component(iter.next().unwrap(), bbox);
331
        composite._instructions = orig.instructions().unwrap_or_default().to_vec();
332
        assert!(iter.next().is_none());
333
        let bytes = crate::dump_table(&composite).unwrap();
334
        let ours = read_fonts::tables::glyf::CompositeGlyph::read(FontData::new(&bytes)).unwrap();
335
336
        let our_comps = ours.components().collect::<Vec<_>>();
337
        let orig_comps = orig.components().collect::<Vec<_>>();
338
        assert_eq!(our_comps.len(), orig_comps.len());
339
        assert_eq!(our_comps.len(), 2);
340
        assert_eq!(&our_comps[0], &orig_comps[0]);
341
        assert_eq!(&our_comps[1], &orig_comps[1]);
342
        assert_eq!(ours.instructions(), orig.instructions());
343
        assert_eq!(orig.offset_data().len(), bytes.len());
344
345
        assert_eq!(orig.offset_data().as_ref(), bytes);
346
    }
347
}