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