Skip to main content

write_fonts/tables/glyf/
composite.rs

1//! Composite glyphs (containing other glyphs as components)
2
3use crate::{
4    from_obj::{FromObjRef, FromTableRef, ToOwnedTable},
5    FontWrite,
6};
7
8use read_fonts::{
9    tables::glyf::CompositeGlyphFlags,
10    types::{GlyphId, Uint24},
11    FontRead, ReadArgs,
12};
13
14use super::Bbox;
15
16pub use read_fonts::tables::glyf::{Anchor, Transform};
17
18/// A glyph consisting of multiple component sub-glyphs
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct CompositeGlyph {
21    pub bbox: Bbox,
22    components: Vec<Component>,
23    instructions: Vec<u8>,
24}
25
26/// A single component glyph (part of a [`CompositeGlyph`]).
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Component {
29    pub glyph: GlyphId,
30    pub anchor: Anchor,
31    pub flags: ComponentFlags,
32    pub transform: Transform,
33}
34
35/// Options that can be manually set for a given component.
36///
37/// This provides an easier interface for setting those flags that are not
38/// calculated based on other properties of the glyph. For more information
39/// on these flags, see [Component Glyph Flags](flags-spec) in the spec.
40///
41/// These eventually are combined with calculated flags into the
42/// [`CompositeGlyphFlags`] bitset.
43///
44/// [flags-spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/glyf#compositeGlyphFlags
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
46pub struct ComponentFlags {
47    /// Round xy values to the nearest grid line
48    pub round_xy_to_grid: bool,
49    /// Use the advance/lsb/rsb values of this component for the whole
50    /// composite glyph
51    pub use_my_metrics: bool,
52    /// The composite should have this component's offset scaled
53    pub scaled_component_offset: bool,
54    /// The composite should *not* have this component's offset scaled
55    pub unscaled_component_offset: bool,
56    /// If set, the components of the composite glyph overlap.
57    pub overlap_compound: bool,
58}
59
60impl FromObjRef<read_fonts::tables::glyf::CompositeGlyph<'_>> for CompositeGlyph {
61    fn from_obj_ref(
62        from: &read_fonts::tables::glyf::CompositeGlyph,
63        _data: read_fonts::FontData,
64    ) -> Self {
65        let bbox = Bbox {
66            x_min: from.x_min(),
67            y_min: from.y_min(),
68            x_max: from.x_max(),
69            y_max: from.y_max(),
70        };
71        let components = from
72            .components()
73            .map(|c| Component {
74                glyph: c.glyph,
75                anchor: c.anchor,
76                flags: c.flags.into(),
77                transform: c.transform,
78            })
79            .collect();
80        Self {
81            bbox,
82            components,
83            instructions: from
84                .instructions()
85                .map(|v| v.to_owned())
86                .unwrap_or_default(),
87        }
88    }
89}
90
91impl FromTableRef<read_fonts::tables::glyf::CompositeGlyph<'_>> for CompositeGlyph {}
92
93impl ReadArgs for CompositeGlyph {
94    type Args = ();
95}
96
97impl<'a> FontRead<'a> for CompositeGlyph {
98    fn read_with_args(
99        data: read_fonts::FontData<'a>,
100        _: (),
101    ) -> Result<Self, read_fonts::ReadError> {
102        read_fonts::tables::glyf::CompositeGlyph::read(data).map(|g| g.to_owned_table())
103    }
104}
105
106impl Component {
107    /// Create a new component.
108    pub fn new(
109        glyph: GlyphId,
110        anchor: Anchor,
111        transform: Transform,
112        flags: impl Into<ComponentFlags>,
113    ) -> Self {
114        Component {
115            glyph,
116            anchor,
117            flags: flags.into(),
118            transform,
119        }
120    }
121    /// Compute the flags for this glyph, excepting `MORE_COMPONENTS` and
122    /// `WE_HAVE_INSTRUCTIONS`, which must be set manually
123    fn compute_flag(&self) -> CompositeGlyphFlags {
124        let glyph_flag = if self.glyph.to_u32() > u16::MAX as u32 {
125            CompositeGlyphFlags::GID_IS_24_BIT
126        } else {
127            CompositeGlyphFlags::empty()
128        };
129        self.anchor.compute_flags()
130            | self.transform.compute_flags()
131            | self.flags.into()
132            | glyph_flag
133    }
134
135    /// like `FontWrite` but lets us pass in the flags that must be determined
136    /// externally (WE_HAVE_INSTRUCTIONS and MORE_COMPONENTS)
137    fn write_into(&self, writer: &mut crate::TableWriter, extra_flags: CompositeGlyphFlags) {
138        let flags = self.compute_flag() | extra_flags;
139        flags.bits().write_into(writer);
140        if flags.contains(CompositeGlyphFlags::GID_IS_24_BIT) {
141            Uint24::new(self.glyph.to_u32()).write_into(writer);
142        } else {
143            (self.glyph.to_u32() as u16).write_into(writer);
144        }
145        self.anchor.write_into(writer);
146        self.transform.write_into(writer);
147    }
148}
149
150/// An error that occurs if a `CompositeGlyph` is constructed with no components.
151#[derive(Clone, Copy, Debug)]
152#[non_exhaustive]
153pub struct NoComponents;
154
155impl std::fmt::Display for NoComponents {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        write!(f, "A composite glyph must contain at least one component")
158    }
159}
160
161impl std::error::Error for NoComponents {}
162
163impl CompositeGlyph {
164    /// Create a new composite glyph, with the provided component.
165    ///
166    /// The 'bbox' argument is the bounding box of the glyph after the transform
167    /// has been applied.
168    ///
169    /// Additional components can be added with [`add_component`][Self::add_component]
170    pub fn new(component: Component, bbox: impl Into<Bbox>) -> Self {
171        Self {
172            bbox: bbox.into(),
173            components: vec![component],
174            instructions: Default::default(),
175        }
176    }
177
178    /// Add a new component to this glyph
179    ///
180    /// The 'bbox' argument is the bounding box of the glyph after the transform
181    /// has been applied.
182    pub fn add_component(&mut self, component: Component, bbox: impl Into<Bbox>) {
183        self.components.push(component);
184        self.bbox = self.bbox.union(bbox.into());
185    }
186
187    /// Construct a `CompositeGlyph` from an iterator of `Component` and `Bbox`es.
188    ///
189    /// This returns an error if the iterator is empty; a CompositeGlyph must always
190    /// contain at least one component.
191    pub fn try_from_iter(
192        source: impl IntoIterator<Item = (Component, Bbox)>,
193    ) -> Result<Self, NoComponents> {
194        let mut components = Vec::new();
195        let mut union_box: Option<Bbox> = None;
196
197        for (component, bbox) in source {
198            components.push(component);
199            union_box.get_or_insert(bbox).union(bbox);
200        }
201
202        if components.is_empty() {
203            Err(NoComponents)
204        } else {
205            Ok(CompositeGlyph {
206                bbox: union_box.unwrap(),
207                components,
208                instructions: Default::default(),
209            })
210        }
211    }
212
213    pub fn components(&self) -> &[Component] {
214        &self.components
215    }
216
217    pub fn components_mut(&mut self) -> &mut [Component] {
218        &mut self.components
219    }
220
221    pub fn set_instructions(&mut self, instructions: &[u8]) {
222        self.instructions = instructions.to_vec();
223    }
224
225    pub fn instructions(&self) -> &[u8] {
226        &self.instructions
227    }
228}
229
230impl FontWrite for CompositeGlyph {
231    fn write_into(&self, writer: &mut crate::TableWriter) {
232        const N_CONTOURS: i16 = -1;
233        N_CONTOURS.write_into(writer);
234        self.bbox.write_into(writer);
235        let (last, rest) = self
236            .components
237            .split_last()
238            .expect("empty composites checked in validation");
239        for comp in rest {
240            comp.write_into(writer, CompositeGlyphFlags::MORE_COMPONENTS);
241        }
242        let last_flags = if self.instructions.is_empty() {
243            CompositeGlyphFlags::empty()
244        } else {
245            CompositeGlyphFlags::WE_HAVE_INSTRUCTIONS
246        };
247        last.write_into(writer, last_flags);
248
249        if !self.instructions.is_empty() {
250            (self.instructions.len() as u16).write_into(writer);
251            self.instructions.write_into(writer);
252        }
253        writer.pad_to_2byte_aligned();
254    }
255}
256
257impl crate::validate::Validate for CompositeGlyph {
258    fn validate_impl(&self, ctx: &mut crate::codegen_prelude::ValidationCtx) {
259        if self.components.is_empty() {
260            ctx.report("composite glyph must have components");
261        }
262        if self.instructions.len() > u16::MAX as usize {
263            ctx.report("instructions len overflows");
264        }
265        if self
266            .components
267            .iter()
268            .any(|component| component.glyph.to_u32() > 0xffffff)
269        {
270            ctx.report("component glyph identifier overflows 24 bits");
271        }
272    }
273}
274
275impl FontWrite for Anchor {
276    fn write_into(&self, writer: &mut crate::TableWriter) {
277        let two_bytes = self
278            .compute_flags()
279            .contains(CompositeGlyphFlags::ARG_1_AND_2_ARE_WORDS);
280        match self {
281            Anchor::Offset { x, y } if !two_bytes => [*x as i8, *y as i8].write_into(writer),
282            Anchor::Offset { x, y } => [*x, *y].write_into(writer),
283            Anchor::Point { base, component } if !two_bytes => {
284                [*base as u8, *component as u8].write_into(writer)
285            }
286            Anchor::Point { base, component } => [*base, *component].write_into(writer),
287        }
288    }
289}
290
291impl FontWrite for Transform {
292    fn write_into(&self, writer: &mut crate::TableWriter) {
293        let flags = self.compute_flags();
294        if flags.contains(CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO) {
295            [self.xx, self.yx, self.xy, self.yy].write_into(writer);
296        } else if flags.contains(CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE) {
297            [self.xx, self.yy].write_into(writer);
298        } else if flags.contains(CompositeGlyphFlags::WE_HAVE_A_SCALE) {
299            self.xx.write_into(writer)
300        }
301    }
302}
303
304impl From<CompositeGlyphFlags> for ComponentFlags {
305    fn from(src: CompositeGlyphFlags) -> ComponentFlags {
306        ComponentFlags {
307            round_xy_to_grid: src.contains(CompositeGlyphFlags::ROUND_XY_TO_GRID),
308            use_my_metrics: src.contains(CompositeGlyphFlags::USE_MY_METRICS),
309            scaled_component_offset: src.contains(CompositeGlyphFlags::SCALED_COMPONENT_OFFSET),
310            unscaled_component_offset: src.contains(CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET),
311            overlap_compound: src.contains(CompositeGlyphFlags::OVERLAP_COMPOUND),
312        }
313    }
314}
315
316impl From<ComponentFlags> for CompositeGlyphFlags {
317    fn from(value: ComponentFlags) -> Self {
318        (if value.round_xy_to_grid {
319            CompositeGlyphFlags::ROUND_XY_TO_GRID
320        } else {
321            Default::default()
322        }) | if value.use_my_metrics {
323            CompositeGlyphFlags::USE_MY_METRICS
324        } else {
325            Default::default()
326        } | if value.scaled_component_offset {
327            CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
328        } else {
329            Default::default()
330        } | if value.unscaled_component_offset {
331            CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET
332        } else {
333            Default::default()
334        } | if value.overlap_compound {
335            CompositeGlyphFlags::OVERLAP_COMPOUND
336        } else {
337            Default::default()
338        }
339    }
340}
341
342#[cfg(test)]
343mod tests {
344
345    use read_fonts::{
346        tables::glyf as read_glyf, types::GlyphId, FontData, FontRead, FontRef, TableProvider,
347    };
348
349    use super::*;
350
351    #[test]
352    fn roundtrip_extended_composite_components() {
353        let half = read_fonts::types::F2Dot14::from_f32(0.5);
354        let bbox = Bbox {
355            x_min: 0,
356            y_min: 0,
357            x_max: 80,
358            y_max: 80,
359        };
360        for gid in [65535, 65536, 0xffffff] {
361            for anchor in [
362                Anchor::Offset { x: 1, y: -2 },
363                Anchor::Point {
364                    base: 256,
365                    component: 512,
366                },
367            ] {
368                for transform in [
369                    Transform::default(),
370                    Transform {
371                        xx: half,
372                        yy: half,
373                        ..Default::default()
374                    },
375                    Transform {
376                        xx: half,
377                        ..Default::default()
378                    },
379                    Transform {
380                        xy: half,
381                        ..Default::default()
382                    },
383                ] {
384                    let mut glyph = CompositeGlyph::new(
385                        Component::new(
386                            GlyphId::new(gid),
387                            anchor,
388                            transform,
389                            ComponentFlags::default(),
390                        ),
391                        bbox,
392                    );
393                    glyph.add_component(
394                        Component::new(
395                            GlyphId::new(7),
396                            Anchor::Offset { x: 3, y: 4 },
397                            Transform::default(),
398                            ComponentFlags::default(),
399                        ),
400                        bbox,
401                    );
402                    glyph.set_instructions(&[0xb0, 0, 0x21]);
403                    let bytes = crate::dump_table(&glyph).unwrap();
404                    let read = read_glyf::CompositeGlyph::read(bytes.as_slice().into()).unwrap();
405                    let components: Vec<_> = read.components().collect();
406                    let ids: Vec<_> = read.component_glyphs_and_flags().map(|c| c.0).collect();
407                    assert_eq!(ids, [GlyphId::new(gid), GlyphId::new(7)]);
408                    assert_eq!(components[0].glyph, ids[0]);
409                    assert_eq!(components[0].anchor, anchor);
410                    assert_eq!(components[0].transform, transform);
411                    assert_eq!(
412                        components[0]
413                            .flags
414                            .contains(CompositeGlyphFlags::GID_IS_24_BIT),
415                        gid > 65535
416                    );
417                    assert!(!components[1]
418                        .flags
419                        .contains(CompositeGlyphFlags::GID_IS_24_BIT));
420                    assert_eq!(
421                        read.count_and_instructions(),
422                        (2, Some(&[0xb0, 0, 0x21][..]))
423                    );
424                    let owned: CompositeGlyph = read.to_owned_table();
425                    assert_eq!(owned, glyph);
426                    assert_eq!(crate::dump_table(&owned).unwrap(), bytes);
427                }
428            }
429        }
430        let glyph = CompositeGlyph::new(
431            Component::new(
432                GlyphId::new(0x1000000),
433                Anchor::Offset { x: 0, y: 0 },
434                Transform::default(),
435                ComponentFlags::default(),
436            ),
437            bbox,
438        );
439        assert!(crate::dump_table(&glyph).is_err());
440    }
441
442    #[test]
443    fn roundtrip_composite() {
444        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
445        let loca = font.loca(None).unwrap();
446        let glyf = font.glyf().unwrap();
447        let read_glyf::Glyph::Composite(orig) = loca
448            .get(GlyphId::new(2), &glyf)
449            .and_then(|g| g.into_glyph())
450            .unwrap()
451        else {
452            panic!("not a composite glyph")
453        };
454
455        let bbox = Bbox {
456            x_min: orig.x_min(),
457            y_min: orig.y_min(),
458            x_max: orig.x_max(),
459            y_max: orig.y_max(),
460        };
461        let mut iter = orig
462            .components()
463            .map(|comp| Component::new(comp.glyph, comp.anchor, comp.transform, comp.flags));
464        let mut composite = CompositeGlyph::new(iter.next().unwrap(), bbox);
465        composite.add_component(iter.next().unwrap(), bbox);
466        composite.instructions = orig.instructions().unwrap_or_default().to_vec();
467        assert!(iter.next().is_none());
468        let bytes = crate::dump_table(&composite).unwrap();
469        let ours = read_fonts::tables::glyf::CompositeGlyph::read(FontData::new(&bytes)).unwrap();
470
471        let our_comps = ours.components().collect::<Vec<_>>();
472        let orig_comps = orig.components().collect::<Vec<_>>();
473        assert_eq!(our_comps.len(), orig_comps.len());
474        assert_eq!(our_comps.len(), 2);
475        assert_eq!(&our_comps[0], &orig_comps[0]);
476        assert_eq!(&our_comps[1], &orig_comps[1]);
477        assert_eq!(ours.instructions(), orig.instructions());
478        assert_eq!(orig.offset_data().len(), bytes.len());
479
480        assert_eq!(orig.offset_data().as_ref(), bytes);
481    }
482}