1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! The result of a compilation

use write_fonts::{
    dump_table,
    read::TopLevelTable,
    tables::{self as wtables, maxp::Maxp},
    validate::Validate,
    FontBuilder, FontWrite,
};

use super::{error::BinaryCompilationError, Opts};

use crate::{Diagnostic, GlyphMap};

/// The tables generated by this compilation.
///
/// All tables are optional, and the set of tables that are present depends
/// on the input file.
///
/// Each table is a type defined in the [`write-fonts`][] crate. The caller
/// may either interact with these directly, or else they may use the [`to_binary`]
/// method to generate a binary font.
///
/// [`to_binary`]: Compilation::to_binary
pub struct Compilation {
    /// Any warnings encountered during parsing or compilation
    pub warnings: Vec<Diagnostic>,
    /// The `head` table, if one was generated
    pub head: Option<wtables::head::Head>,
    /// The `hhea` table, if one was generated
    pub hhea: Option<wtables::hhea::Hhea>,
    /// The `vhea` table, if one was generated
    pub vhea: Option<wtables::vhea::Vhea>,
    /// The `OS/2` table, if one was generated
    pub os2: Option<wtables::os2::Os2>,
    /// The `GDEF` table, if one was generated
    pub gdef: Option<wtables::gdef::Gdef>,
    /// The `BASE` table, if one was generated
    pub base: Option<wtables::base::Base>,
    /// The `name` table, if one was generated
    pub name: Option<wtables::name::Name>,
    /// The `STAT` table, if one was generated
    pub stat: Option<wtables::stat::Stat>,
    /// The `GSUB` table, if one was generated
    pub gsub: Option<wtables::gsub::Gsub>,
    /// The `GPOS` table, if one was generated
    pub gpos: Option<wtables::gpos::Gpos>,
}

impl Compilation {
    /// Assemble the output tables into a `FontBuilder`.
    ///
    /// This is a convenience method. To compile a binary font you can use
    /// [`to_binary`] instead, and for more fine-grained control you can inspect
    /// and manipulate the raw tables directly.
    ///
    /// [`to_binary`]: Compilation::to_binary
    pub fn to_font_builder(&self) -> Result<FontBuilder, BinaryCompilationError> {
        let mut builder = FontBuilder::default();
        builder.add(self.head.as_ref())?;
        builder.add(self.hhea.as_ref())?;
        builder.add(self.vhea.as_ref())?;
        builder.add(self.os2.as_ref())?;
        builder.add(self.gdef.as_ref())?;
        builder.add(self.base.as_ref())?;
        builder.add(self.name.as_ref())?;
        builder.add(self.stat.as_ref())?;
        builder.add(self.gsub.as_ref())?;
        builder.add(self.gpos.as_ref())?;
        Ok(builder)
    }

    /// Compile the output tables into a font.
    ///
    /// This is a convenience method used for things like testing; if you are
    /// building a font compiler you will probably prefer to manipulate the
    /// generated tables directly.
    pub fn to_binary(
        &self,
        glyph_map: &GlyphMap,
        opts: Opts,
    ) -> Result<Vec<u8>, BinaryCompilationError> {
        // because we often inspect our output with ttx, and ttx fails if maxp is
        // missing, we create a maxp table.
        let mut builder = self.to_font_builder()?;
        let maxp = Maxp::new(glyph_map.len().try_into().unwrap());
        builder.add(Some(&maxp))?;
        if opts.make_post_table {
            let post = glyph_map.make_post_table();
            builder.add(Some(&post))?;
        }

        Ok(builder.build())
    }
}

// a little helper trait for adding raw tables to a FontBuilder
trait BuilderHelper {
    fn add<T: FontWrite + Validate + TopLevelTable>(
        &mut self,
        table: Option<&T>,
    ) -> Result<(), BinaryCompilationError>;
}

impl BuilderHelper for FontBuilder<'_> {
    fn add<T: FontWrite + Validate + TopLevelTable>(
        &mut self,
        table: Option<&T>,
    ) -> Result<(), BinaryCompilationError> {
        let Some(table) = table else { return Ok(()) };
        dump_table(table).map(|bytes| self.add_table(T::TAG, bytes))?;
        Ok(())
    }
}