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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! The result of a compilation
use std::collections::HashMap;
use write_fonts::{
BuilderError, FontBuilder,
tables::{
self as wtables, gdef::GlyphClassDef, layout::FeatureParams, maxp::Maxp, stat::AxisValue,
},
types::{GlyphId16, NameId},
};
use super::Opts;
use crate::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
/// [`write-fonts`]: https://docs.rs/write-fonts/latest/write_fonts/
pub struct Compilation {
/// The options passed in for this compilation.
pub(crate) opts: Opts,
/// 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>,
/// Any *explicit* gdef classes declared in the FEA.
///
/// This is provided so that the user can reference them if they are going
/// to manually generate kerning or markpos lookups.
pub gdef_classes: Option<HashMap<GlyphId16, GlyphClassDef>>,
}
impl Compilation {
/// Returns `true` if the FEA generated tables other than GSUB, GPOS & GDEF.
pub fn has_non_layout_tables(&self) -> bool {
self.head.is_some()
|| self.hhea.is_some()
|| self.vhea.is_some()
|| self.os2.is_some()
|| self.base.is_some()
|| self.name.is_some()
|| self.stat.is_some()
}
/// Remap any `NameId`s in the name table and anywhere they are referenced.
///
/// This is used for merging the results of our compilation with other
/// compilation operations which may have occured elsewhere, and which may
/// have used the same `NameId`s as us for different strings.
///
/// We will take all the name ids we have declared that are >= 256 and offset
/// them to start at `first_avail_id`.
pub fn remap_name_ids(&mut self, first_avail_id: u16) {
let id_offset = first_avail_id.saturating_sub(NameId::LAST_RESERVED_NAME_ID.to_u16() + 1);
log::info!("remapping FEA name ideas with delta {id_offset}");
if id_offset == 0 {
return;
}
let adjust_id = |id: NameId| {
if !id.is_reserved() {
// in the case of a degenerate name table we'll just reuse the last id?
// entries will be pruned later.
id.checked_add(id_offset)
.unwrap_or(NameId::LAST_ALLOWED_NAME_ID)
} else {
id
}
};
if let Some(name) = self.name.as_mut() {
let records = std::mem::take(&mut name.name_record);
name.name_record = records
.into_iter()
.map(|mut rec| {
rec.name_id = adjust_id(rec.name_id);
rec
})
.collect();
}
if let Some(gsub) = self.gsub.as_mut() {
gsub.feature_list
.as_mut()
.feature_records
.iter_mut()
.for_each(|rec| match rec.feature.as_mut().feature_params.as_mut() {
Some(FeatureParams::StylisticSet(params)) => {
params.ui_name_id = adjust_id(params.ui_name_id);
}
Some(FeatureParams::CharacterVariant(params)) => {
params.feat_ui_label_name_id = adjust_id(params.feat_ui_label_name_id);
params.feat_ui_tooltip_text_name_id =
adjust_id(params.feat_ui_tooltip_text_name_id);
params.sample_text_name_id = adjust_id(params.sample_text_name_id);
params.first_param_ui_label_name_id =
adjust_id(params.first_param_ui_label_name_id);
}
_ => (),
});
}
if let Some(stat) = self.stat.as_mut() {
stat.elided_fallback_name_id = stat
.elided_fallback_name_id
.map(|id| id.to_u16().saturating_add(id_offset).into());
stat.design_axes.iter_mut().for_each(|axe| {
axe.axis_name_id = adjust_id(axe.axis_name_id);
});
if let Some(blah) = stat.offset_to_axis_values.as_mut() {
blah.iter_mut().for_each(|val| match val.as_mut() {
AxisValue::Format1(val) => val.value_name_id = adjust_id(val.value_name_id),
AxisValue::Format2(val) => val.value_name_id = adjust_id(val.value_name_id),
AxisValue::Format3(val) => val.value_name_id = adjust_id(val.value_name_id),
AxisValue::Format4(val) => val.value_name_id = adjust_id(val.value_name_id),
});
}
}
}
/// 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<'_>, BuilderError> {
let mut builder = FontBuilder::default();
macro_rules! add_if_some {
($table:expr_2021) => {
if let Some(table) = $table.as_ref() {
builder.add_table(table)?;
}
};
}
add_if_some!(self.head);
add_if_some!(self.hhea);
add_if_some!(self.vhea);
add_if_some!(self.os2);
add_if_some!(self.gdef);
add_if_some!(self.base);
add_if_some!(self.name);
add_if_some!(self.stat);
add_if_some!(self.gsub);
add_if_some!(self.gpos);
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) -> Result<Vec<u8>, BuilderError> {
// 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_table(&maxp)?;
if self.opts.make_post_table {
let post = glyph_map.make_post_table();
builder.add_table(&post)?;
}
Ok(builder.build())
}
}