1use std::collections::HashMap;
4
5use write_fonts::{
6 tables::{
7 self as wtables, gdef::GlyphClassDef, layout::FeatureParams, maxp::Maxp, stat::AxisValue,
8 },
9 types::{GlyphId16, NameId},
10 BuilderError, FontBuilder,
11};
12
13use super::Opts;
14
15use crate::GlyphMap;
16
17pub struct Compilation {
28 pub(crate) opts: Opts,
30 pub head: Option<wtables::head::Head>,
32 pub hhea: Option<wtables::hhea::Hhea>,
34 pub vhea: Option<wtables::vhea::Vhea>,
36 pub os2: Option<wtables::os2::Os2>,
38 pub gdef: Option<wtables::gdef::Gdef>,
40 pub base: Option<wtables::base::Base>,
42 pub name: Option<wtables::name::Name>,
44 pub stat: Option<wtables::stat::Stat>,
46 pub gsub: Option<wtables::gsub::Gsub>,
48 pub gpos: Option<wtables::gpos::Gpos>,
50 pub gdef_classes: Option<HashMap<GlyphId16, GlyphClassDef>>,
55}
56
57impl Compilation {
58 pub fn has_non_layout_tables(&self) -> bool {
60 self.head.is_some()
61 || self.hhea.is_some()
62 || self.vhea.is_some()
63 || self.os2.is_some()
64 || self.base.is_some()
65 || self.name.is_some()
66 || self.stat.is_some()
67 }
68
69 pub fn remap_name_ids(&mut self, first_avail_id: u16) {
78 let id_offset = first_avail_id.saturating_sub(NameId::LAST_RESERVED_NAME_ID.to_u16() + 1);
79 log::info!("remapping FEA name ideas with delta {id_offset}");
80 if id_offset == 0 {
81 return;
82 }
83
84 let adjust_id = |id: NameId| {
85 if !id.is_reserved() {
86 id.checked_add(id_offset)
89 .unwrap_or(NameId::LAST_ALLOWED_NAME_ID)
90 } else {
91 id
92 }
93 };
94
95 if let Some(name) = self.name.as_mut() {
96 let records = std::mem::take(&mut name.name_record);
97 name.name_record = records
98 .into_iter()
99 .map(|mut rec| {
100 rec.name_id = adjust_id(rec.name_id);
101 rec
102 })
103 .collect();
104 }
105
106 if let Some(gsub) = self.gsub.as_mut() {
107 gsub.feature_list
108 .as_mut()
109 .feature_records
110 .iter_mut()
111 .for_each(|rec| match rec.feature.as_mut().feature_params.as_mut() {
112 Some(FeatureParams::StylisticSet(params)) => {
113 params.ui_name_id = adjust_id(params.ui_name_id);
114 }
115 Some(FeatureParams::CharacterVariant(params)) => {
116 params.feat_ui_label_name_id = adjust_id(params.feat_ui_label_name_id);
117 params.feat_ui_tooltip_text_name_id =
118 adjust_id(params.feat_ui_tooltip_text_name_id);
119 params.sample_text_name_id = adjust_id(params.sample_text_name_id);
120 params.first_param_ui_label_name_id =
121 adjust_id(params.first_param_ui_label_name_id);
122 }
123 _ => (),
124 });
125 }
126 if let Some(stat) = self.stat.as_mut() {
127 stat.elided_fallback_name_id = stat
128 .elided_fallback_name_id
129 .map(|id| id.to_u16().saturating_add(id_offset).into());
130 stat.design_axes.iter_mut().for_each(|axe| {
131 axe.axis_name_id = adjust_id(axe.axis_name_id);
132 });
133 if let Some(blah) = stat.offset_to_axis_values.as_mut() {
134 blah.iter_mut().for_each(|val| match val.as_mut() {
135 AxisValue::Format1(val) => val.value_name_id = adjust_id(val.value_name_id),
136 AxisValue::Format2(val) => val.value_name_id = adjust_id(val.value_name_id),
137 AxisValue::Format3(val) => val.value_name_id = adjust_id(val.value_name_id),
138 AxisValue::Format4(val) => val.value_name_id = adjust_id(val.value_name_id),
139 });
140 }
141 }
142 }
143
144 pub fn to_font_builder(&self) -> Result<FontBuilder, BuilderError> {
152 let mut builder = FontBuilder::default();
153 macro_rules! add_if_some {
154 ($table:expr) => {
155 if let Some(table) = $table.as_ref() {
156 builder.add_table(table)?;
157 }
158 };
159 }
160 add_if_some!(self.head);
161 add_if_some!(self.hhea);
162 add_if_some!(self.vhea);
163 add_if_some!(self.os2);
164 add_if_some!(self.gdef);
165 add_if_some!(self.base);
166 add_if_some!(self.name);
167 add_if_some!(self.stat);
168 add_if_some!(self.gsub);
169 add_if_some!(self.gpos);
170 Ok(builder)
171 }
172
173 pub fn to_binary(&self, glyph_map: &GlyphMap) -> Result<Vec<u8>, BuilderError> {
179 let mut builder = self.to_font_builder()?;
182 let maxp = Maxp::new(glyph_map.len().try_into().unwrap());
183 builder.add_table(&maxp)?;
184 if self.opts.make_post_table {
185 let post = glyph_map.make_post_table();
186 builder.add_table(&post)?;
187 }
188
189 Ok(builder.build())
190 }
191}