write_fonts/tables/glyf/
glyf_loca_builder.rs

1//! A builder for the 'glyf' and 'loca' tables
2
3use crate::{
4    error::Error,
5    tables::loca::{Loca, LocaFormat},
6    validate::Validate,
7    FontWrite, TableWriter,
8};
9
10use super::{CompositeGlyph, Glyf, Glyph, SimpleGlyph};
11
12/// A builder for constructing the 'glyf' & 'loca' tables.
13///
14/// These two tables are tightly coupled, and are necessarily constructed
15/// together.
16///
17/// # Example
18///
19/// ```
20/// use write_fonts::tables::glyf::{Glyph, GlyfLocaBuilder};
21/// # fn get_glyphs() -> Vec<(String, Glyph)> { Vec::new() }
22///
23/// let names_and_glyphs: Vec<(String, Glyph)> = get_glyphs();
24/// let mut builder = GlyfLocaBuilder::new();
25///
26/// for (name, glyph) in names_and_glyphs {
27///     // your error handling goes here
28///     if let Err(e) = builder.add_glyph(&glyph) {
29///         panic!("error compiling glyph '{name}': '{e}'");
30///     }
31/// }
32///
33/// let (_glyf, _loca, _loca_format) = builder.build();
34/// // store the results somewhere
35/// ```
36pub struct GlyfLocaBuilder {
37    glyph_writer: TableWriter,
38    raw_loca: Vec<u32>,
39}
40
41/// A trait encompassing [`Glyph`], [`SimpleGlyph`] and [`CompositeGlyph`]
42///
43/// This is a marker trait to ensure that only glyphs are passed to [`GlyfLocaBuilder`].
44pub trait SomeGlyph: Validate + FontWrite {}
45
46impl GlyfLocaBuilder {
47    /// Construct a new builder for the 'glyf' and 'loca' tables.
48    pub fn new() -> Self {
49        Self {
50            glyph_writer: TableWriter::default(),
51            raw_loca: vec![0],
52        }
53    }
54
55    /// Add a glyph to the table.
56    ///
57    /// The argument can be any of [`Glyph`], [`SimpleGlyph`] or [`CompositeGlyph`].
58    ///
59    /// The glyph is validated and compiled immediately, so that the caller can
60    /// associate any errors with a particular glyph.
61    pub fn add_glyph(&mut self, glyph: &impl SomeGlyph) -> Result<&mut Self, Error> {
62        glyph.validate()?;
63        glyph.write_into(&mut self.glyph_writer);
64        let pos = self.glyph_writer.current_data().bytes.len();
65        self.raw_loca.push(pos as u32);
66        Ok(self)
67    }
68
69    /// Construct the final glyf and loca tables.
70    ///
71    /// This method also returns the loca format; the caller is responsible for
72    /// setting this field in the ['head'] table.
73    ///
74    /// [`head`]: crate::tables::head::Head::index_to_loc_format
75    #[must_use]
76    pub fn build(self) -> (Glyf, Loca, LocaFormat) {
77        let glyph_data = self.glyph_writer.into_data().bytes;
78        let loca = Loca::new(self.raw_loca);
79        let format = loca.format();
80        (Glyf(glyph_data), loca, format)
81    }
82}
83
84impl SomeGlyph for SimpleGlyph {}
85
86impl SomeGlyph for CompositeGlyph {}
87
88impl SomeGlyph for Glyph {}
89
90impl Default for GlyfLocaBuilder {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::super::{Anchor, Component, ComponentFlags, Transform};
99    use crate::from_obj::FromTableRef;
100    use font_types::GlyphId;
101    use kurbo::{BezPath, Shape};
102    use read_fonts::FontRead;
103
104    use super::*;
105
106    #[test]
107    fn build_some_glyphs() {
108        fn make_triangle() -> BezPath {
109            let mut path = BezPath::new();
110            path.move_to((0., 0.));
111            path.line_to((0., 40.));
112            path.line_to((20., 40.));
113            path.line_to((0., 0.));
114            path
115        }
116        let square = kurbo::Rect::from_points((5., 5.), (100., 100.)).into_path(0.1);
117        let triangle = make_triangle();
118
119        let glyph0 = Glyph::Empty;
120        let glyph1 = SimpleGlyph::from_bezpath(&square).unwrap();
121        let glyph2 = SimpleGlyph::from_bezpath(&triangle).unwrap();
122        let gid1 = GlyphId::new(1);
123        let gid2 = GlyphId::new(2);
124
125        let mut glyph3 = CompositeGlyph::new(
126            Component::new(
127                gid1.try_into().unwrap(),
128                Anchor::Offset { x: 0, y: 0 },
129                Transform::default(),
130                ComponentFlags::default(),
131            ),
132            square.bounding_box(),
133        );
134        glyph3.add_component(
135            Component::new(
136                gid2.try_into().unwrap(),
137                Anchor::Offset { x: 0, y: 0 },
138                Transform::default(),
139                ComponentFlags::default(),
140            ),
141            triangle.bounding_box(),
142        );
143
144        let len1 = crate::dump_table(&glyph1).unwrap().len() as u32;
145        let len2 = crate::dump_table(&glyph2).unwrap().len() as u32;
146        let len3 = crate::dump_table(&glyph3).unwrap().len() as u32;
147
148        let mut builder = GlyfLocaBuilder::new();
149        builder
150            .add_glyph(&glyph0)
151            .unwrap()
152            .add_glyph(&glyph1)
153            .unwrap()
154            .add_glyph(&glyph2)
155            .unwrap()
156            .add_glyph(&glyph3)
157            .unwrap();
158
159        let (glyf, loca, format) = builder.build();
160        assert_eq!(loca.offsets.len(), 5);
161        assert_eq!(loca.offsets, &[0, 0, len1, len1 + len2, len1 + len2 + len3]);
162
163        let rglyf = read_fonts::tables::glyf::Glyf::read(glyf.0.as_slice().into()).unwrap();
164        let loca_bytes = crate::dump_table(&loca).unwrap();
165        let rloca = read_fonts::tables::loca::Loca::read(
166            loca_bytes.as_slice().into(),
167            format == LocaFormat::Long,
168        )
169        .unwrap();
170
171        let rglyph1 = Glyph::from_table_ref(&rloca.get_glyf(gid1, &rglyf).unwrap().unwrap());
172        let rglyph2 = Glyph::from_table_ref(&rloca.get_glyf(gid2, &rglyf).unwrap().unwrap());
173        let rglyph3 =
174            Glyph::from_table_ref(&rloca.get_glyf(GlyphId::new(3), &rglyf).unwrap().unwrap());
175        assert_eq!(rglyph1, glyph1.into());
176        assert_eq!(rglyph2, glyph2.into());
177        assert_eq!(rglyph3, glyph3.into());
178    }
179}