use std::{
collections::{BTreeMap, HashMap},
convert::TryInto,
ops::Range,
};
use fontdrasil::coords::{DesignCoord, NormalizedCoord, NormalizedLocation, UserCoord};
use smol_str::SmolStr;
use write_fonts::{
tables::{
self,
gdef::GlyphClassDef,
gpos::{
builders::{
AnchorBuilder as Anchor, PreviouslyAssignedClass, ValueRecordBuilder as ValueRecord,
},
ValueFormat,
},
layout::{
builders::{CaretValueBuilder as CaretValue, DeviceOrDeltas, Metric},
ConditionFormat1, ConditionSet, FeatureVariations, LookupFlag,
},
variations::ivs_builder::{RemapVariationIndices, VariationStoreBuilder},
},
types::{F2Dot14, NameId, Tag},
};
use crate::{
common::{GlyphClass, GlyphId16, GlyphOrClass, GlyphSet, MarkClass},
parse::SourceMap,
token_tree::{
typed::{self, AstNode},
Token,
},
typed::ContextualRuleNode,
Diagnostic, GlyphIdent, GlyphMap, Kind, NodeOrToken, Opts,
};
use super::{
feature_writer::{FeatureBuilder, FeatureProvider, InsertionPoint},
features::{
AaltFeature, ActiveFeature, AllFeatures, ConditionSetMap, CvParams, SizeFeature,
SpecialVerticalFeatureState,
},
glyph_range,
language_system::{DefaultLanguageSystems, LanguageSystem},
lookups::{AllLookups, FilterSetId, LookupFlagInfo, LookupId, SomeLookup},
output::Compilation,
tables::{GlyphClassDefExt, ScriptRecord, Tables},
tags, VariationInfo,
};
pub struct CompilationCtx<'a, F: FeatureProvider, V: VariationInfo> {
glyph_map: &'a GlyphMap,
reverse_glyph_map: BTreeMap<GlyphId16, GlyphIdent>,
source_map: &'a SourceMap,
variation_info: Option<&'a V>,
feature_writer: Option<&'a F>,
opts: Opts,
pub errors: Vec<Diagnostic>,
tables: Tables,
features: AllFeatures,
default_lang_systems: DefaultLanguageSystems,
lookups: AllLookups,
lookup_flags: LookupFlagInfo,
active_feature: Option<ActiveFeature>,
vertical_feature: SpecialVerticalFeatureState,
script: Option<Tag>,
glyph_class_defs: HashMap<SmolStr, GlyphClass>,
mark_classes: HashMap<SmolStr, MarkClass>,
anchor_defs: HashMap<SmolStr, (Anchor, usize)>,
value_record_defs: HashMap<SmolStr, ValueRecord>,
conditionset_defs: ConditionSetMap,
mark_attach_class_id: HashMap<GlyphSet, u16>,
mark_filter_sets: HashMap<GlyphSet, FilterSetId>,
insert_markers: HashMap<Tag, InsertionPoint>,
}
impl<'a, F: FeatureProvider, V: VariationInfo> CompilationCtx<'a, F, V> {
pub(crate) fn new(
glyph_map: &'a GlyphMap,
source_map: &'a SourceMap,
variation_info: Option<&'a V>,
feature_writer: Option<&'a F>,
opts: Opts,
) -> Self {
CompilationCtx {
glyph_map,
reverse_glyph_map: glyph_map.reverse_map(),
source_map,
variation_info,
feature_writer,
errors: Vec::new(),
tables: Tables::default(),
default_lang_systems: Default::default(),
glyph_class_defs: Default::default(),
lookups: Default::default(),
features: Default::default(),
mark_classes: Default::default(),
anchor_defs: Default::default(),
value_record_defs: Default::default(),
conditionset_defs: Default::default(),
lookup_flags: Default::default(),
active_feature: Default::default(),
vertical_feature: Default::default(),
script: Default::default(),
mark_attach_class_id: Default::default(),
mark_filter_sets: Default::default(),
opts,
insert_markers: Default::default(),
}
}
pub(crate) fn compile(&mut self, node: &typed::Root) {
for item in node.statements() {
if let Some(language_system) = typed::LanguageSystem::cast(item) {
self.add_language_system(language_system);
} else if let Some(class_def) = typed::GlyphClassDef::cast(item) {
self.define_glyph_class(class_def);
} else if let Some(mark_def) = typed::MarkClassDef::cast(item) {
self.define_mark_class(mark_def);
} else if let Some(anchor_def) = typed::AnchorDef::cast(item) {
self.define_named_anchor(anchor_def);
} else if let Some(item) = typed::ValueRecordDef::cast(item) {
self.define_named_value_record(item);
} else if let Some(node) = typed::ConditionSet::cast(item) {
self.define_condition_set(node);
} else if let Some(feature) = typed::Feature::cast(item) {
self.add_feature(feature);
} else if let Some(node) = typed::FeatureVariation::cast(item) {
self.add_feature_variation(node);
} else if let Some(lookup) = typed::LookupBlock::cast(item) {
self.resolve_lookup_block(lookup);
} else if item.kind() == Kind::AnonBlockNode {
} else if let Some(table) = typed::Table::cast(item) {
self.resolve_table(table);
} else if !item.kind().is_trivia() {
let span = get_reasonable_length_span(item);
self.error(span, format!("unhandled top-level item: '{}'", item.kind()));
}
}
let lig_carets = self.run_feature_writer_if_present();
self.finalize_gdef_table(lig_carets);
self.features
.finalize_aalt(&mut self.lookups, &self.default_lang_systems);
self.features.dedupe_lookups();
}
pub(crate) fn build(&mut self) -> Result<(Compilation, Vec<Diagnostic>), Vec<Diagnostic>> {
if self.errors.iter().any(Diagnostic::is_error) {
return Err(self.errors.clone());
}
let mut name_builder = self.tables.name.clone();
let stat = self
.tables
.stat
.as_ref()
.map(|raw| raw.build(&mut name_builder));
let axis_count = self
.variation_info
.map(|info| info.axis_count())
.unwrap_or_default();
let mut ivs = VariationStoreBuilder::new(axis_count);
let (mut gsub, mut gpos) = self.lookups.build(&self.features, &mut ivs, &self.opts);
if !ivs.is_empty() {
self.tables
.gdef
.get_or_insert_with(Default::default)
.var_store = Some(ivs);
}
let (gdef, key_map) = match self.tables.gdef.as_ref().map(|raw| raw.build()) {
Some((gdef, key_map)) => (Some(gdef), key_map),
None => (None, None),
};
let feature_params = self.features.build_feature_params(&mut name_builder);
if let Some(gsub) = gsub.as_mut() {
if let Some(variations) = gsub.feature_variations.as_mut() {
sort_feature_variations(variations, |condset| {
self.conditionset_defs.sort_order(condset)
});
}
for record in gsub.feature_list.feature_records.iter_mut() {
if let Some(params) = feature_params.get(&record.feature_tag) {
record.feature.feature_params = params.clone().into();
}
}
}
if let Some(gpos) = gpos.as_mut() {
if let Some(key_map) = key_map {
gpos.remap_variation_indices(&key_map);
}
if let Some(variations) = gpos.feature_variations.as_mut() {
sort_feature_variations(variations, |condset| {
self.conditionset_defs.sort_order(condset)
});
}
for record in gpos.feature_list.feature_records.iter_mut() {
if let Some(params) = feature_params.get(&record.feature_tag) {
record.feature.feature_params = params.clone().into();
}
}
}
let gdef_classes = self.tables.gdef.as_ref().and_then(|gdef| {
(!gdef.glyph_classes_were_inferred).then(|| gdef.glyph_classes.clone())
});
Ok((
Compilation {
head: self.tables.head.as_ref().map(|raw| raw.build(None)),
hhea: self.tables.hhea.clone(),
vhea: self.tables.vhea.clone(),
os2: self.tables.os2.as_ref().map(|raw| raw.build()),
gdef,
base: self.tables.base.as_ref().map(|raw| raw.build()),
name: name_builder.build(),
stat,
gsub,
gpos,
opts: self.opts.clone(),
gdef_classes,
},
self.errors.clone(),
))
}
fn run_feature_writer_if_present(&mut self) -> BTreeMap<GlyphId16, Vec<CaretValue>> {
let Some(writer) = self.feature_writer else {
return Default::default();
};
let mut builder = FeatureBuilder::new(
&self.default_lang_systems,
&mut self.tables,
&mut self.mark_filter_sets,
);
writer.add_features(&mut builder);
let mut external_features = builder.finish();
if let Some(conditions) = external_features.feature_variations.as_ref() {
for conditionset in conditions.conditions.iter().map(|(cs, _)| cs) {
self.conditionset_defs.register_use(conditionset);
}
}
external_features.merge_into(&mut self.lookups, &mut self.features, &self.insert_markers);
external_features.lig_carets
}
fn finalize_gdef_table(&mut self, generated_lig_carets: BTreeMap<GlyphId16, Vec<CaretValue>>) {
let mut gdef = self.tables.gdef.take().unwrap_or_default();
if gdef.glyph_classes.is_empty() {
gdef.glyph_classes_were_inferred = true;
self.lookups.infer_glyph_classes(|glyph, class_id| {
gdef.glyph_classes.insert(glyph, class_id);
});
for glyph in self
.mark_classes
.values()
.flat_map(|class| class.members.iter().map(|(cls, _)| cls.iter()))
.flatten()
{
gdef.glyph_classes.insert(glyph, GlyphClassDef::Mark);
}
}
if !self.mark_attach_class_id.is_empty() {
gdef.mark_attach_class.extend(
self.mark_attach_class_id
.iter()
.flat_map(|(cls, id)| cls.iter().map(|gid| (gid, *id))),
);
}
if !self.mark_filter_sets.is_empty() {
let mut sorted = self
.mark_filter_sets
.iter()
.map(|(cls, id)| (*id, cls.clone()))
.collect::<Vec<_>>();
sorted.sort_unstable();
gdef.mark_glyph_sets = sorted.into_iter().map(|(_, cls)| cls).collect();
}
if gdef.ligature_pos.is_empty() {
gdef.ligature_pos = generated_lig_carets;
}
if !gdef.is_empty() {
self.tables.gdef = Some(gdef);
}
}
fn error(&mut self, range: Range<usize>, message: impl Into<String>) {
let (file, range) = self.source_map.resolve_range(range);
self.errors.push(Diagnostic::error(file, range, message));
}
fn warning(&mut self, range: Range<usize>, message: impl Into<String>) {
let (file, range) = self.source_map.resolve_range(range);
self.errors.push(Diagnostic::warning(file, range, message));
}
fn add_language_system(&mut self, language_system: typed::LanguageSystem) {
let script = language_system.script().to_raw();
let language = language_system.language().to_raw();
self.default_lang_systems
.insert(LanguageSystem { script, language });
}
fn start_feature(&mut self, feature_name: typed::Tag, conditions: Option<ConditionSet>) {
assert!(
!self.lookups.has_current(),
"no lookup should be active at start of feature"
);
let raw_tag = feature_name.to_raw();
self.active_feature = Some(ActiveFeature::new(
raw_tag,
self.default_lang_systems.clone(),
conditions,
));
self.vertical_feature.begin_feature(raw_tag);
self.lookup_flags.clear();
}
fn end_feature(&mut self) {
if let Some((id, _name)) = self.lookups.finish_current() {
assert!(
_name.is_none(),
"lookup blocks are finished before feature blocks"
);
self.add_lookup_to_current_feature_if_present(id);
}
let active = self.active_feature.take().expect("always present");
active.add_to_features(&mut self.features);
self.vertical_feature.end_feature();
self.lookup_flags.clear();
self.script = None;
}
fn start_lookup_block(&mut self, name: &Token) {
if let Some((id, _name)) = self.lookups.finish_current() {
assert!(_name.is_none(), "lookup blocks cannot be nested");
self.add_lookup_to_current_feature_if_present(id);
}
if self.active_feature.is_none() {
self.lookup_flags.clear();
}
self.vertical_feature.begin_lookup_block();
self.lookups.start_named(name.text.clone());
}
fn end_lookup_block(&mut self) {
let current = self.lookups.finish_current();
if self.active_feature.is_some() {
if let Some((id, _)) = current {
self.add_lookup_to_current_feature_if_present(id);
}
} else {
self.lookup_flags.clear();
}
self.vertical_feature.end_lookup_block();
}
fn set_language(&mut self, stmt: typed::Language) {
let language = stmt.tag().to_raw();
let script = self.script.unwrap_or(tags::SCRIPT_DFLT);
self.set_script_language(
script,
language,
stmt.exclude_dflt().is_some(),
stmt.required().is_some(),
);
}
fn set_script(&mut self, stmt: typed::Script) {
let script = stmt.tag().to_raw();
if self
.active_feature
.as_ref()
.unwrap()
.current_system()
.map(|langsys| (langsys.script, langsys.language))
== Some((script, tags::LANG_DFLT))
{
return;
}
self.script = Some(script);
self.lookup_flags.clear();
self.set_script_language(script, tags::LANG_DFLT, false, false);
}
fn set_script_language(
&mut self,
script: Tag,
language: Tag,
exclude_dflt: bool,
required: bool,
) {
let system = LanguageSystem { script, language };
if let Some((id, _name)) = self.lookups.finish_current() {
self.add_lookup_to_current_feature_if_present(id);
}
let key = self
.active_feature
.as_mut()
.unwrap()
.set_system(system, exclude_dflt);
if required {
self.features.add_required(key);
}
}
fn set_lookup_flag(&mut self, node: typed::LookupFlag) {
self.lookup_flags.clear();
if let Some(number) = node.number() {
let raw = number.parse_unsigned().unwrap() & 0xff;
self.lookup_flags.flags = LookupFlag::from_bits_truncate(raw);
return;
}
let mut flags = LookupFlag::empty();
let mut mark_filter_set = None;
let mut iter = node.values();
while let Some(next) = iter.next() {
match next.kind() {
Kind::RightToLeftKw => flags |= LookupFlag::RIGHT_TO_LEFT,
Kind::IgnoreBaseGlyphsKw => flags |= LookupFlag::IGNORE_BASE_GLYPHS,
Kind::IgnoreLigaturesKw => flags |= LookupFlag::IGNORE_LIGATURES,
Kind::IgnoreMarksKw => flags |= LookupFlag::IGNORE_MARKS,
Kind::MarkAttachmentTypeKw => {
let node = iter
.next()
.and_then(typed::GlyphClass::cast)
.expect("validated");
let mark_attach_set = self.resolve_mark_attach_class(&node);
flags.set_mark_attachment_class(mark_attach_set);
}
Kind::UseMarkFilteringSetKw => {
let node = iter
.next()
.and_then(typed::GlyphClass::cast)
.expect("validated");
let filter_set = self.resolve_mark_filter_set(&node);
flags |= LookupFlag::USE_MARK_FILTERING_SET;
mark_filter_set = Some(filter_set);
}
other => unreachable!("mark statements have been validated: '{:?}'", other),
}
}
self.lookup_flags = LookupFlagInfo::new(flags, mark_filter_set);
}
fn resolve_mark_attach_class(&mut self, glyphs: &typed::GlyphClass) -> u16 {
let glyphs = self.resolve_glyph_class(glyphs);
let mark_set = glyphs.to_glyph_set();
if let Some(id) = self.mark_attach_class_id.get(&mark_set) {
return *id;
}
let id = self.mark_attach_class_id.len() as u16 + 1;
self.mark_attach_class_id.insert(mark_set, id);
id
}
fn resolve_mark_filter_set(&mut self, glyphs: &typed::GlyphClass) -> u16 {
let glyphs = self.resolve_glyph_class(glyphs);
let set = glyphs.to_glyph_set();
let id = self.mark_filter_sets.len();
*self
.mark_filter_sets
.entry(set)
.or_insert_with(|| id.try_into().unwrap())
}
pub fn add_subtable_break(&mut self) {
if !self.lookups.add_subtable_break() {
}
}
fn ensure_current_lookup_type(&mut self, kind: Kind) -> &mut SomeLookup {
if !self.lookups.has_current_kind(kind) || !self.lookups.has_same_flags(self.lookup_flags) {
if let Some(lookup) = self.lookups.start_lookup(kind, self.lookup_flags) {
self.add_lookup_to_current_feature_if_present(lookup);
}
}
self.lookups.current_mut().expect("we just created it")
}
fn add_lookup_to_current_feature_if_present(&mut self, lookup: LookupId) {
if lookup != LookupId::Empty {
if let Some(active) = self.active_feature.as_mut() {
active.add_lookup(lookup);
}
}
}
fn add_gpos_statement(&mut self, node: typed::GposStatement) {
match node {
typed::GposStatement::Type1(rule) => self.add_single_pos(&rule),
typed::GposStatement::Type2(rule) => self.add_pair_pos(&rule),
typed::GposStatement::Type3(rule) => self.add_cursive_pos(&rule),
typed::GposStatement::Type4(rule) => self.add_mark_to_base(&rule),
typed::GposStatement::Type5(rule) => self.add_mark_to_lig(&rule),
typed::GposStatement::Type6(rule) => self.add_mark_to_mark(&rule),
typed::GposStatement::Type8(rule) => self.add_contextual_pos_rule(&rule),
typed::GposStatement::Ignore(rule) => self.add_contextual_pos_ignore(&rule),
}
}
fn add_gsub_statement(&mut self, node: typed::GsubStatement) {
match node {
typed::GsubStatement::Type1(rule) => self.add_single_sub(&rule),
typed::GsubStatement::Type2(rule) => self.add_multiple_sub(&rule),
typed::GsubStatement::Type3(rule) => self.add_alternate_sub(&rule),
typed::GsubStatement::Type4(rule) => self.add_ligature_sub(&rule),
typed::GsubStatement::Type6(rule) => self.add_contextual_sub(&rule),
typed::GsubStatement::Ignore(rule) => self.add_contextual_sub_ignore(&rule),
typed::GsubStatement::Type8(rule) => self.add_reverse_contextual_sub(&rule),
_ => self.warning(node.range(), "unimplemented rule type"),
}
}
fn add_single_sub(&mut self, node: &typed::Gsub1) {
let Some((target, replacement)) = self.resolve_single_sub_glyphs(node) else {
return;
};
if replacement.is_null() {
let lookup = self.ensure_current_lookup_type(Kind::GsubType2);
for target in target.iter() {
lookup.add_gsub_type_2(target, vec![]);
}
} else if self.lookups.has_current_kind(Kind::GsubType2)
&& self.lookups.has_same_flags(self.lookup_flags)
{
let lookup = self.ensure_current_lookup_type(Kind::GsubType2);
for (target, replacement) in target.iter().zip(replacement.into_iter_for_target()) {
lookup.add_gsub_type_2(target, vec![replacement]);
}
} else if self.lookups.has_current_kind(Kind::GsubType4)
&& self.lookups.has_same_flags(self.lookup_flags)
{
let lookup = self.ensure_current_lookup_type(Kind::GsubType4);
for (target, replacement) in target.iter().zip(replacement.into_iter_for_target()) {
if lookup.add_gsub_type_4(vec![target], replacement) {
self.error(node.range(), "Ligature substitution shadows existing rule");
return;
}
}
} else {
let lookup = self.ensure_current_lookup_type(Kind::GsubType1);
for (target, replacement) in target.iter().zip(replacement.into_iter_for_target()) {
lookup.add_gsub_type_1(target, replacement);
}
}
}
fn resolve_single_sub_glyphs(
&mut self,
node: &typed::Gsub1,
) -> Option<(GlyphOrClass, GlyphOrClass)> {
self.validate_single_sub_inputs(&node.target(), node.replacement().as_ref())
}
fn validate_single_sub_inputs(
&mut self,
target: &typed::GlyphOrClass,
replace: Option<&typed::GlyphOrClass>,
) -> Option<(GlyphOrClass, GlyphOrClass)> {
let target_ids = self.resolve_glyph_or_class(target);
let replace_ids = replace
.map(|r| self.resolve_glyph_or_class(r))
.unwrap_or(GlyphOrClass::Null);
match (target_ids, replace_ids) {
(GlyphOrClass::Null, _) => {
self.error(target.range(), "NULL is not a valid substitution target");
None
}
(GlyphOrClass::Glyph(_), GlyphOrClass::Class(_)) => {
self.error(replace.unwrap().range(), "cannot sub glyph by glyph class");
None
}
(GlyphOrClass::Class(c1), GlyphOrClass::Class(c2)) if c2.len() == 1 => {
let g2 = *c2.into_iter().next().unwrap();
Some((GlyphOrClass::Class(c1), GlyphOrClass::Glyph(g2)))
}
(GlyphOrClass::Class(c1), GlyphOrClass::Class(c2)) if c1.len() != c2.len() => {
self.error(
replace.unwrap().range(),
format!(
"class has different length ({}) than target ({})",
c1.len(),
c2.len()
),
);
None
}
other => Some(other),
}
}
fn add_multiple_sub(&mut self, node: &typed::Gsub2) {
let target = node.target();
let target_id = self.resolve_glyph(&target);
let replacement = node.replacement().map(|g| self.resolve_glyph(&g)).collect();
if self.lookups.has_same_flags(self.lookup_flags) {
self.lookups.promote_single_sub_to_multi_if_necessary();
}
let lookup = self.ensure_current_lookup_type(Kind::GsubType2);
lookup.add_gsub_type_2(target_id, replacement);
}
fn add_alternate_sub(&mut self, node: &typed::Gsub3) {
let target = self.resolve_glyph(&node.target());
let alts = self.resolve_glyph_class(&node.alternates());
let lookup = self.ensure_current_lookup_type(Kind::GsubType3);
lookup.add_gsub_type_3(target, alts.iter().collect());
}
fn add_ligature_sub(&mut self, node: &typed::Gsub4) {
let target = node
.target()
.map(|g| self.resolve_glyph_or_class(&g))
.collect::<Vec<_>>();
let replacement = self.resolve_glyph(&node.replacement());
if self.lookups.has_same_flags(self.lookup_flags) {
self.lookups.promote_single_sub_to_liga_if_necessary();
}
let lookup = self.ensure_current_lookup_type(Kind::GsubType4);
for target in sequence_enumerator(&target) {
if lookup.add_gsub_type_4(target, replacement) {
self.error(node.range(), "Ligature substitution shadows existing rule");
return;
}
}
}
fn add_contextual_sub(&mut self, node: &typed::Gsub6) {
let backtrack = self.resolve_backtrack_sequence(node.backtrack().items());
let lookahead = self.resolve_lookahead_sequence(node.lookahead().items());
let mut inline = node.inline_rule().and_then(|rule| {
let input = node.input();
if input.items().nth(1).is_some() {
let target = input
.items()
.map(|inp| self.resolve_glyph_or_class(&inp.target()))
.collect::<Vec<_>>();
let replacement = self.resolve_glyph(&rule.replacement_glyphs().next().unwrap());
let lookup = self.ensure_current_lookup_type(Kind::GsubType6);
let mut to_return = None;
for target in sequence_enumerator(&target) {
to_return = Some(
lookup
.as_gsub_contextual()
.add_anon_gsub_type_4(target, replacement),
);
}
to_return
} else {
let target = input.items().next().unwrap().target();
let arity = rule.replacements().count();
if arity == 1 && rule.null().is_none() {
let replacement = rule.replacements().next().unwrap();
if let Some((target, replacement)) =
self.validate_single_sub_inputs(&target, Some(&replacement))
{
let lookup = self.ensure_current_lookup_type(Kind::GsubType6);
Some(
lookup
.as_gsub_contextual()
.add_anon_gsub_type_1(target, replacement),
)
} else {
None
}
} else if target.is_class() && rule.null().is_some() {
let targets = self.resolve_glyph_or_class(&target);
let lookup = self.ensure_current_lookup_type(Kind::GsubType6);
let mut lookup_id = None;
for target in targets.iter() {
lookup_id = Some(
lookup
.as_gsub_contextual()
.add_anon_gsub_type_2(target, Vec::new()),
);
}
lookup_id
} else {
if target.is_class() {
self.error(
target.range(),
"Inline multiple substitution must have a single glyph target",
);
}
let replacements = if rule.null().is_some() {
Vec::new()
} else {
rule.replacement_glyphs()
.map(|g| self.resolve_glyph(&g))
.collect()
};
if let Some(target_id) = self.resolve_glyph_or_class(&target).iter().next() {
let lookup = self.ensure_current_lookup_type(Kind::GsubType6);
Some(
lookup
.as_gsub_contextual()
.add_anon_gsub_type_2(target_id, replacements),
)
} else {
None
}
}
}
});
let context = node
.input()
.items()
.map(|item| {
let glyphs = self.resolve_glyph_or_class(&item.target());
let mut lookups = Vec::new();
if let Some(inline) = inline.take() {
lookups.push(inline);
}
for lookup in item.lookups() {
let id = self.lookups.get_named(&lookup.label().text).unwrap(); if matches!(id, LookupId::Gpos(_)) {
self.error(
lookup.label().range(),
"Invalid lookup: expected GSUB, found GPOS",
);
}
lookups.push(id);
}
(glyphs, lookups)
})
.collect::<Vec<_>>();
let lookup = self.ensure_current_lookup_type(Kind::GsubType6);
lookup.add_contextual_rule(backtrack, context, lookahead);
}
fn add_contextual_sub_ignore(&mut self, node: &typed::GsubIgnore) {
for rule in node.rules() {
self.add_contextual_ignore_rule(&rule, Kind::GsubType6);
}
}
fn add_reverse_contextual_sub(&mut self, node: &typed::Gsub8) {
let backtrack = self.resolve_backtrack_sequence(node.backtrack().items());
let lookahead = self.resolve_lookahead_sequence(node.lookahead().items());
let input = node.input().items().next().unwrap();
let target = input.target();
let replacement = node.inline_rule().and_then(|r| r.replacements().next());
if let Some((target, replacement)) =
self.validate_single_sub_inputs(&target, replacement.as_ref())
{
let context = target
.iter()
.zip(replacement.into_iter_for_target())
.collect();
self.ensure_current_lookup_type(Kind::GsubType8)
.add_gsub_type_8(backtrack, context, lookahead);
}
}
fn add_single_pos(&mut self, node: &typed::Gpos1) {
let ids = self.resolve_glyph_or_class(&node.target());
let record = self.resolve_value_record(&node.value());
let lookup = self.ensure_current_lookup_type(Kind::GposType1);
for id in ids.iter() {
lookup.add_gpos_type_1(id, record.clone());
}
}
fn add_pair_pos(&mut self, node: &typed::Gpos2) {
let in_vert_feature = self.vertical_feature.in_eligible_vertical_feature();
let first_ids = self.resolve_glyph_or_class(&node.first_item());
let second_ids = self.resolve_glyph_or_class(&node.second_item());
let first_value = for_pair_pos(
self.resolve_value_record_raw(&node.first_value()),
in_vert_feature,
);
let second_value = for_pair_pos(
node.second_value()
.map(|val| self.resolve_value_record_raw(&val))
.unwrap_or_default(),
in_vert_feature,
);
let lookup = self.ensure_current_lookup_type(Kind::GposType2);
if (first_ids.is_class() || second_ids.is_class()) && node.enum_().is_none() {
lookup.add_gpos_type_2_class(
first_ids.to_class().unwrap().into(),
second_ids.to_class().unwrap().into(),
first_value,
second_value,
)
} else {
for first in first_ids.iter() {
for second in second_ids.iter() {
lookup.add_gpos_type_2_pair(
first,
second,
first_value.clone(),
second_value.clone(),
);
}
}
}
}
fn add_cursive_pos(&mut self, node: &typed::Gpos3) {
let ids = self.resolve_glyph_or_class(&node.target());
let entry = self.resolve_anchor(&node.entry());
let exit = self.resolve_anchor(&node.exit());
let lookup = self.ensure_current_lookup_type(Kind::GposType3);
for id in ids.iter() {
lookup.add_gpos_type_3(id, entry.clone(), exit.clone())
}
}
fn add_mark_to_base(&mut self, node: &typed::Gpos4) {
let base_ids = self.resolve_glyph_or_class(&node.base());
let _ = self.ensure_current_lookup_type(Kind::GposType4);
for mark in node.attachments() {
let base_anchor = self.resolve_anchor(&mark.anchor());
let mark_class_node = mark.mark_class_name().expect("checked in validation");
let class_name = mark_class_node.text().to_owned();
let mark_class = self.mark_classes.get(&class_name).unwrap();
let maybe_err = self
.lookups
.current_mut()
.unwrap()
.with_gpos_type_4(|subtable| {
for (glyphs, mark_anchor) in &mark_class.members {
let anchor = mark_anchor
.as_ref()
.expect("no null anchors in mark-to-base (check validation)");
for glyph in glyphs.iter() {
subtable.insert_mark(glyph, &class_name, anchor.clone())?;
}
}
for base in base_ids.iter() {
subtable.insert_base(
base,
&class_name,
base_anchor
.clone()
.expect("no null anchors in mark-to-base"),
)
}
Ok(())
});
self.maybe_report_mark_class_conflict(mark_class_node.range(), maybe_err.err())
}
}
fn add_mark_to_lig(&mut self, node: &typed::Gpos5) {
let base_ids = self.resolve_glyph_or_class(&node.base());
let mut components = Vec::new();
for component in node.ligature_components() {
let _lookup = self.ensure_current_lookup_type(Kind::GposType5);
let mut anchor_records = BTreeMap::new();
for attachment in component.attachments() {
let component_anchor = self.resolve_anchor(&attachment.anchor());
let mark_class_node = match attachment.mark_class_name() {
Some(node) => node,
None => {
assert!(component_anchor.is_none());
continue;
}
};
let component_anchor = component_anchor.unwrap();
let class_name = mark_class_node.text();
let mark_class = self.mark_classes.get(class_name).unwrap();
anchor_records.insert(class_name.to_string(), component_anchor);
let maybe_err = self
.lookups
.current_mut()
.unwrap()
.with_gpos_type_5(|subtable| {
for (glyphs, mark_anchor) in &mark_class.members {
let anchor = mark_anchor
.as_ref()
.expect("no null anchors on marks (check validation)");
for glyph in glyphs.iter() {
subtable.insert_mark(glyph, class_name, anchor.clone())?;
}
}
Ok(())
});
self.maybe_report_mark_class_conflict(mark_class_node.range(), maybe_err.err());
}
components.push(anchor_records);
}
self.lookups
.current_mut()
.unwrap()
.with_gpos_type_5(|subtable| {
for base in base_ids.iter() {
subtable.add_ligature_components_directly(base, components.clone());
}
})
}
fn add_mark_to_mark(&mut self, node: &typed::Gpos6) {
let base_ids = self.resolve_glyph_or_class(&node.base());
let _ = self.ensure_current_lookup_type(Kind::GposType6);
for mark in node.attachments() {
let base_anchor = self.resolve_anchor(&mark.anchor());
let mark_class_node = mark.mark_class_name().expect("checked in validation");
let class_name = mark_class_node.text();
let mark_class = self.mark_classes.get(mark_class_node.text()).unwrap();
let maybe_err = self
.lookups
.current_mut()
.unwrap()
.with_gpos_type_6(|subtable| {
for (glyphs, mark_anchor) in &mark_class.members {
let anchor = mark_anchor
.as_ref()
.expect("no null anchors in mark-to-mark (check validation)");
for glyph in glyphs.iter() {
subtable.insert_mark1(glyph, class_name, anchor.clone())?;
}
}
for base in base_ids.iter() {
subtable.insert_mark2(
base,
class_name,
base_anchor
.clone()
.expect("no null anchors in mark-to-mark"),
);
}
Ok(())
});
self.maybe_report_mark_class_conflict(mark_class_node.range(), maybe_err.err())
}
}
fn maybe_report_mark_class_conflict(
&mut self,
range: Range<usize>,
maybe_err: Option<PreviouslyAssignedClass>,
) {
if let Some(PreviouslyAssignedClass { class, .. }) = maybe_err {
self.error(
range,
format!("mark class includes glyph in class '{class}', already used in lookup.",),
);
};
}
fn add_contextual_pos_rule(&mut self, node: &typed::Gpos8) {
let backtrack = self.resolve_backtrack_sequence(node.backtrack().items());
let lookahead = self.resolve_lookahead_sequence(node.lookahead().items());
let trailing_value_record = node.trailing_value_record();
if trailing_value_record.is_some() {
debug_assert_eq!(node.input().items().count(), 1,);
}
let context = node
.input()
.items()
.map(|item| {
let glyphs = self.resolve_glyph_or_class(&item.target());
let mut lookups = Vec::new();
if let Some(value) = trailing_value_record.clone().or_else(|| item.valuerecord()) {
let value = self.resolve_value_record(&value);
let anon_id = self
.ensure_current_lookup_type(Kind::GposType8)
.as_gpos_contextual()
.add_anon_gpos_type_1(&glyphs, value);
lookups.push(anon_id);
}
for lookup in item.lookups() {
let id = self.lookups.get_named(&lookup.label().text).unwrap();
if matches!(id, LookupId::Gsub(_)) {
self.error(
lookup.label().range(),
"Invalid lookup type: expected GPOS, found GSUB",
);
}
lookups.push(id);
}
(glyphs, lookups)
})
.collect();
self.ensure_current_lookup_type(Kind::GposType8)
.add_contextual_rule(backtrack, context, lookahead);
}
fn add_contextual_pos_ignore(&mut self, node: &typed::GposIgnore) {
for rule in node.rules() {
self.add_contextual_ignore_rule(&rule, Kind::GposType8);
}
}
fn add_contextual_ignore_rule(&mut self, rule: &typed::IgnoreRule, kind: Kind) {
let backtrack = self.resolve_backtrack_sequence(rule.backtrack().items());
let lookahead = self.resolve_lookahead_sequence(rule.lookahead().items());
let context = rule
.input()
.items()
.map(|item| (self.resolve_glyph_or_class(&item.target()), Vec::new()))
.collect();
let lookup = self.ensure_current_lookup_type(kind);
lookup.add_contextual_rule(backtrack, context, lookahead);
}
fn resolve_value_record(&mut self, record: &typed::ValueRecord) -> ValueRecord {
self.resolve_value_record_raw(record).clear_zeros()
}
fn resolve_value_record_raw(&mut self, record: &typed::ValueRecord) -> ValueRecord {
if record.null().is_some() {
return ValueRecord::default();
}
if let Some(name) = record.named() {
return self
.value_record_defs
.get(name.as_str())
.cloned()
.expect("checked in validation");
}
if let Some(adv) = record.advance() {
let adv = self.resolve_metric(&adv);
if self.vertical_feature.in_eligible_vertical_feature() {
return ValueRecord::new()
.with_y_advance(adv.default)
.with_y_advance_device(adv.device_or_deltas);
} else {
return ValueRecord::new()
.with_x_advance(adv.default)
.with_x_advance_device(adv.device_or_deltas);
}
}
let Some([x_place, y_place, x_adv, y_adv]) = record.placement() else {
log::error!("failed to resolve value record. This indicates a bug.");
return Default::default();
};
let x_placement = self.resolve_metric(&x_place);
let y_placement = self.resolve_metric(&y_place);
let x_advance = self.resolve_metric(&x_adv);
let y_advance = self.resolve_metric(&y_adv);
let mut result = ValueRecord {
x_placement: Some(x_placement),
y_placement: Some(y_placement),
x_advance: Some(x_advance),
y_advance: Some(y_advance),
};
if let Some([x_place_dev, y_place_dev, x_adv_dev, y_adv_dev]) = record.device() {
debug_assert!(
!result.format().intersects(
ValueFormat::X_PLACEMENT_DEVICE
| ValueFormat::Y_PLACEMENT_DEVICE
| ValueFormat::X_ADVANCE_DEVICE
| ValueFormat::Y_ADVANCE_DEVICE
),
"checked during parsing"
);
result.x_advance.as_mut().unwrap().device_or_deltas = x_adv_dev.compile().into();
result.y_advance.as_mut().unwrap().device_or_deltas = y_adv_dev.compile().into();
result.x_placement.as_mut().unwrap().device_or_deltas = x_place_dev.compile().into();
result.y_placement.as_mut().unwrap().device_or_deltas = y_place_dev.compile().into();
}
result
}
fn resolve_metric(&mut self, metric: &typed::Metric) -> Metric {
match metric {
typed::Metric::Scalar(value) => value.parse_signed().into(),
typed::Metric::Variable(variable) => self.resolve_variable_metric(variable),
typed::Metric::GlyphsAppNumber(number_value) => {
self.resolve_glyphs_number_value(number_value)
}
}
}
fn resolve_variable_metric(&mut self, metric: &typed::VariableMetric) -> Metric {
let Some(var_info) = self.variation_info else {
self.error(
metric.range(),
"variable metric only valid when compiling variable font",
);
return Default::default();
};
let mut locations = HashMap::new();
for metric_loc in metric.location_values() {
let mut pos = NormalizedLocation::new();
for axis_value in metric_loc.location().items() {
let tag = axis_value.axis_tag().to_raw();
let (_, axis) = var_info.axis(tag).unwrap();
let coord = match axis_value.value().parse() {
super::AxisLocation::Normalized(value) => NormalizedCoord::new(value),
super::AxisLocation::User(value) => {
UserCoord::new(value).to_normalized(&axis.converter)
}
super::AxisLocation::Design(value) => {
DesignCoord::new(value).to_normalized(&axis.converter)
}
};
pos.insert(tag, coord);
}
locations.insert(pos, metric_loc.value().parse_signed());
}
match var_info.resolve_variable_metric(&locations) {
Ok((default, deltas)) => Metric {
default,
device_or_deltas: DeviceOrDeltas::Deltas(deltas),
},
Err(e) => {
self.error(metric.range(), format!("failed to compute deltas: '{e}'"));
Default::default()
}
}
}
fn resolve_glyphs_number_value(&mut self, number_value: &typed::GlyphsAppNumber) -> Metric {
let Some(var_info) = self.variation_info else {
self.error(
number_value.range(),
"glyphsapp number value only valid when compiling variable font",
);
return Default::default();
};
let locations = match number_value.value() {
typed::GlyphsAppNumberValue::Ident(ident) => var_info
.resolve_glyphs_number_value(ident.text())
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k, v.round() as i16))
.collect(),
typed::GlyphsAppNumberValue::Expr(expr) => {
match super::glyphsapp_syntax_ext::resolve_glyphs_app_expr(&expr, |name| {
var_info
.resolve_glyphs_number_value(name)
.unwrap_or_default()
}) {
super::glyphsapp_syntax_ext::ResolvedValue::Scalar(val) => return val.into(),
super::glyphsapp_syntax_ext::ResolvedValue::Variable(val) => val,
}
}
};
let locations = var_info.resolve_variable_metric(&locations);
match locations {
Ok((default, deltas)) => Metric {
default,
device_or_deltas: DeviceOrDeltas::Deltas(deltas),
},
Err(e) => {
self.error(
number_value.range(),
format!("failed to resolve number value: '{e}'"),
);
Default::default()
}
}
}
fn define_glyph_class(&mut self, class_decl: typed::GlyphClassDef) {
let name = class_decl.class_name();
let glyphs = if let Some(class) = class_decl.class_def() {
self.resolve_glyph_class_literal(&class)
} else if let Some(alias) = class_decl.class_alias() {
self.resolve_named_glyph_class(&alias)
} else {
panic!("write more code I guess");
};
self.glyph_class_defs.insert(name.text().clone(), glyphs);
}
fn define_mark_class(&mut self, class_decl: typed::MarkClassDef) {
let class_items = class_decl.glyph_class();
let class_items = self.resolve_glyph_or_class(&class_items).into();
let anchor = self.resolve_anchor(&class_decl.anchor());
let class_name = class_decl.mark_class_name();
self.mark_classes
.entry(class_name.text().clone())
.or_default()
.members
.push((class_items, anchor));
}
fn add_feature(&mut self, feature: typed::Feature) {
let tag = feature.tag();
let tag_raw = tag.to_raw();
self.start_feature(tag, None);
if tag_raw == tags::AALT {
self.resolve_aalt_feature(&feature);
} else if tag_raw == tags::SIZE {
self.resolve_size_feature(&feature);
} else if tags::is_stylistic_set(tag_raw) {
self.resolve_stylistic_set_feature(tag_raw, &feature);
} else if tags::is_character_variant(tag_raw) {
self.resolve_character_variant_feature(tag_raw, &feature);
} else {
for item in feature.statements() {
self.resolve_statement(item);
}
}
self.end_feature();
}
fn add_feature_variation(&mut self, node: typed::FeatureVariation) {
let tag = node.tag();
let conditions = self.resolve_condition_set(node.condition_set());
self.start_feature(tag, Some(conditions));
for item in node.statements() {
self.resolve_statement(item);
}
self.end_feature();
}
fn resolve_aalt_feature(&mut self, feature: &typed::Feature) {
let mut aalt = AaltFeature::default();
for item in feature.statements() {
if let Some(node) = typed::Gsub1::cast(item) {
let Some((target, replacement)) = self.resolve_single_sub_glyphs(&node) else {
continue;
};
aalt.extend(target.iter().zip(replacement.into_iter_for_target()))
} else if let Some(node) = typed::Gsub3::cast(item) {
let target = self.resolve_glyph(&node.target());
let alts = self.resolve_glyph_class(&node.alternates());
aalt.extend(std::iter::repeat(target).zip(alts.iter()));
} else if let Some(feature) = typed::FeatureRef::cast(item) {
aalt.add_feature_reference(feature.feature().to_raw());
}
}
self.features.aalt = Some(aalt);
}
fn resolve_stylistic_set_feature(&mut self, tag: Tag, feature: &typed::Feature) {
let mut names = BTreeMap::new();
for item in feature.statements() {
if let Some(feature_name) = typed::FeatureNames::cast(item) {
for name_spec in feature_name.statements() {
let resolved = self.resolve_name_spec(&name_spec);
let key = resolved.key();
names.insert(key, resolved);
}
continue;
}
self.resolve_statement(item);
}
if !names.is_empty() {
self.features
.stylistic_sets
.insert(tag, names.into_values().collect());
}
}
fn resolve_character_variant_feature(&mut self, tag: Tag, feature: &typed::Feature) {
let mut seen_cv_params = false;
for item in feature.statements() {
if let Some(cv_params) = typed::CvParameters::cast(item) {
if seen_cv_params {
self.warning(
cv_params.range(),
"Duplicate cvParameters block will be ignored. \
This is not disallowed by the spec, but is not currently supported.",
);
} else {
seen_cv_params = true;
let params = self.resolve_cv_params(&cv_params);
self.features.character_variants.insert(tag, params);
}
continue;
}
self.resolve_statement(item);
}
}
fn resolve_cv_params(&mut self, cv_params: &typed::CvParameters) -> CvParams {
let mut params = CvParams::default();
if let Some(node) = cv_params.feat_ui_label_name() {
params.feat_ui_label_name = node
.statements()
.map(|x| self.resolve_name_spec(&x))
.collect();
}
if let Some(node) = cv_params.feat_tooltip_text_name() {
params.feat_ui_tooltip_text_name = node
.statements()
.map(|x| self.resolve_name_spec(&x))
.collect();
}
if let Some(node) = cv_params.sample_text_name() {
params.sample_text_name = node
.statements()
.map(|x| self.resolve_name_spec(&x))
.collect();
}
if let Some(node) = cv_params.sample_text_name() {
params.sample_text_name = node
.statements()
.map(|x| self.resolve_name_spec(&x))
.collect();
}
for node in cv_params.param_ui_label_name() {
params.param_ui_label_names.push(
node.statements()
.map(|x| self.resolve_name_spec(&x))
.collect(),
);
}
for c in cv_params.characters() {
params.characters.push(c.value().parse_char().unwrap());
}
params
}
fn resolve_size_feature(&mut self, feature: &typed::Feature) {
fn resolve_decipoint(node: &typed::FloatLike) -> u16 {
match node {
typed::FloatLike::Number(n) => n.parse_unsigned(),
typed::FloatLike::Float(f) => {
let f = f.parse();
((f * 10.0).round() as i16).try_into().ok()
}
}
.expect("validated at parse time")
}
let mut size = SizeFeature::default();
for statement in feature.statements() {
if let Some(node) = typed::SizeMenuName::cast(statement) {
size.names.push(self.resolve_name_spec(&node.spec()));
} else if let Some(node) = typed::Parameters::cast(statement) {
size.design_size = resolve_decipoint(&node.design_size());
size.identifier = node.subfamily().parse_unsigned().unwrap();
if size.identifier != 0 {
size.range_start = resolve_decipoint(&node.range_start().unwrap());
size.range_end = resolve_decipoint(&node.range_end().unwrap());
}
}
}
for sys in self.default_lang_systems.iter() {
let key = sys.to_feature_key(tags::SIZE);
self.features.get_or_insert(key);
}
self.features.size = Some(size);
}
fn resolve_table(&mut self, table: typed::Table) {
match table {
typed::Table::Base(table) => self.resolve_base(&table),
typed::Table::Hhea(table) => self.resolve_hhea(&table),
typed::Table::Vhea(table) => self.resolve_vhea(&table),
typed::Table::Vmtx(table) => self.resolve_vmtx(&table),
typed::Table::Name(table) => self.resolve_name(&table),
typed::Table::Gdef(table) => self.resolve_gdef(&table),
typed::Table::Head(table) => self.resolve_head(&table),
typed::Table::Os2(table) => self.resolve_os2(&table),
typed::Table::Stat(table) => self.resolve_stat(&table),
_ => (),
}
}
fn resolve_base(&mut self, table: &typed::BaseTable) {
let mut base = super::tables::BaseBuilder::default();
if let Some(list) = table.horiz_base_tag_list() {
base.horiz_tag_list = list.tags().map(|t| t.to_raw()).collect();
}
if let Some(list) = table.horiz_base_script_record_list() {
base.horiz_script_list = list
.script_records()
.map(|record| ScriptRecord {
script: record.script().to_raw(),
default_baseline_tag: record.default_baseline().to_raw(),
values: record.values().map(|i| i.parse_signed()).collect(),
})
.collect();
base.horiz_script_list
.sort_unstable_by_key(|rec| rec.script);
}
if let Some(list) = table.vert_base_tag_list() {
base.vert_tag_list = list.tags().map(|t| t.to_raw()).collect();
}
if let Some(list) = table.vert_base_script_record_list() {
base.vert_script_list = list
.script_records()
.map(|record| ScriptRecord {
script: record.script().to_raw(),
default_baseline_tag: record.default_baseline().to_raw(),
values: record.values().map(|i| i.parse_signed()).collect(),
})
.collect();
base.vert_script_list.sort_unstable_by_key(|rec| rec.script);
}
self.tables.base = Some(base);
}
fn resolve_name(&mut self, table: &typed::NameTable) {
for record in table.statements() {
let name_id = NameId::new(record.name_id().parse().unwrap());
let spec = self.resolve_name_spec(&record.entry());
self.tables.name.add(name_id, spec);
}
}
fn resolve_os2(&mut self, table: &typed::Os2Table) {
let mut os2 = super::tables::Os2Builder::default();
for item in table.statements() {
match item {
typed::Os2TableItem::Number(val) => {
let value = val.number().parse_unsigned().unwrap();
match val.keyword().text.as_str() {
"WeightClass" => os2.us_weight_class = value,
"WidthClass" => os2.us_width_class = value,
"LowerOpSize" => os2.us_lower_optical_point_size = Some(value),
"UpperOpSize" => os2.us_upper_optical_point_size = Some(value),
"FSType" => os2.fs_type = value,
_ => unreachable!("checked at parse time"),
}
}
typed::Os2TableItem::Metric(val) => {
let value = val.metric().parse_simple().expect("checked in validation");
match val.keyword().kind {
Kind::TypoAscenderKw => os2.s_typo_ascender = value,
Kind::TypoDescenderKw => os2.s_typo_descender = value,
Kind::TypoLineGapKw => os2.s_typo_line_gap = value,
Kind::XHeightKw => os2.sx_height = value,
Kind::CapHeightKw => os2.s_cap_height = value,
Kind::WinAscentKw => os2.us_win_ascent = value as u16,
Kind::WinDescentKw => os2.us_win_descent = value as u16,
_ => unreachable!("checked at parse time"),
}
}
typed::Os2TableItem::NumberList(list) => match list.keyword().kind {
Kind::PanoseKw => {
for (i, val) in list.values().enumerate() {
os2.panose_10[i] = val.parse_signed() as u8;
}
}
Kind::UnicodeRangeKw => {
for val in list.values() {
os2.unicode_range.set_bit(val.parse_signed() as _);
}
}
Kind::CodePageRangeKw => {
for val in list.values() {
os2.code_page_range
.add_code_page(val.parse_unsigned().unwrap());
}
}
_ => unreachable!("checked at parse time"),
},
typed::Os2TableItem::Vendor(item) => {
os2.ach_vend_id = item.parse_tag().expect("validated");
}
typed::Os2TableItem::FamilyClass(item) => {
os2.s_family_class = item.value().parse().unwrap() as i16
}
}
}
self.tables.os2 = Some(os2);
}
fn resolve_stat(&mut self, table: &typed::StatTable) {
let mut stat = super::tables::StatBuilder {
name: super::tables::StatFallbackName::Id(u16::MAX.into()),
records: Vec::new(),
values: Vec::new(),
};
for item in table.statements() {
match item {
typed::StatTableItem::ElidedFallbackName(name) => {
if let Some(id) = name.elided_fallback_name_id() {
stat.name = super::tables::StatFallbackName::Id(
id.parse_unsigned().unwrap().into(),
);
} else {
let names = name.names().map(|n| self.resolve_name_spec(&n)).collect();
stat.name = super::tables::StatFallbackName::Record(names);
}
}
typed::StatTableItem::AxisValue(value) => {
stat.values.push(self.resolve_stat_axis_value(&value));
}
typed::StatTableItem::DesignAxis(value) => {
let tag = value.tag().to_raw();
let ordering = value.ordering().parse_unsigned().unwrap();
let name = value.names().map(|n| self.resolve_name_spec(&n)).collect();
stat.records.push(super::tables::AxisRecord {
tag,
ordering,
name,
});
}
}
}
self.tables.stat = Some(stat);
}
fn resolve_stat_axis_value(&mut self, node: &typed::StatAxisValue) -> super::tables::AxisValue {
use super::tables::AxisLocation;
let mut flags = 0;
let mut name = Vec::new();
let mut location = None;
for item in node.statements() {
match item {
typed::StatAxisValueItem::Flag(flag) => {
for bit in flag.bits() {
flags |= bit;
}
}
typed::StatAxisValueItem::NameRecord(record) => {
name.push(self.resolve_name_spec(&record));
}
typed::StatAxisValueItem::Location(loc) => {
let loc_tag = loc.tag().to_raw();
match loc.value() {
typed::StatLocationValue::Value(num) => {
let val = num.parse_fixed();
if let Some(AxisLocation::One { tag, value }) = location.as_ref() {
location = Some(AxisLocation::Four(vec![(*tag, *value)]));
}
if let Some(AxisLocation::Four(vals)) = location.as_mut() {
vals.push((loc_tag, val));
} else {
location = Some(AxisLocation::One {
tag: loc_tag,
value: val,
});
}
}
typed::StatLocationValue::MinMax { nominal, min, max } => {
location = Some(AxisLocation::Two {
tag: loc_tag,
nominal: nominal.parse_fixed(),
max: max.parse_fixed(),
min: min.parse_fixed(),
});
}
typed::StatLocationValue::Linked { value, linked } => {
location = Some(AxisLocation::Three {
tag: loc_tag,
value: value.parse_fixed(),
linked: linked.parse_fixed(),
});
}
}
}
}
}
super::tables::AxisValue {
flags,
name,
location: location.unwrap(),
}
}
fn resolve_hhea(&mut self, table: &typed::HheaTable) {
let mut hhea = tables::hhea::Hhea::default();
for record in table.metrics() {
let keyword = record.keyword();
let value = record
.metric()
.parse_simple()
.expect("checked during validation");
match keyword.kind {
Kind::CaretOffsetKw => hhea.caret_offset = value,
Kind::AscenderKw => hhea.ascender = value.into(),
Kind::DescenderKw => hhea.descender = value.into(),
Kind::LineGapKw => hhea.line_gap = value.into(),
other => panic!("bug in parser, unexpected token '{other}'"),
}
}
self.tables.hhea = Some(hhea);
}
fn resolve_vhea(&mut self, table: &typed::VheaTable) {
let mut vhea = tables::vhea::Vhea::default();
for record in table.metrics() {
let keyword = record.keyword();
let value = record
.metric()
.parse_simple()
.expect("checked during validation");
match keyword.kind {
Kind::VertTypoAscenderKw => vhea.ascender = value.into(),
Kind::VertTypoDescenderKw => vhea.descender = value.into(),
Kind::VertTypoLineGapKw => vhea.line_gap = value.into(),
other => panic!("bug in parser, unexpected token '{other}'"),
}
}
self.tables.vhea = Some(vhea);
}
fn resolve_vmtx(&mut self, table: &typed::VmtxTable) {
let mut vmtx = super::tables::VmtxBuilder::default();
for item in table.statements() {
let glyph = self.resolve_glyph(&item.glyph());
let value = item.value().parse_signed();
match item.keyword().kind {
Kind::VertAdvanceYKw => vmtx.advances_y.push((glyph, value)),
Kind::VertOriginYKw => vmtx.origins_y.push((glyph, value)),
_ => unreachable!(),
}
}
self.tables.vmtx = Some(vmtx);
}
fn resolve_gdef(&mut self, table: &typed::GdefTable) {
let mut gdef = super::tables::GdefBuilder::default();
for statement in table.statements() {
match statement {
typed::GdefTableItem::Attach(rule) => {
let glyphs = self.resolve_glyph_or_class(&rule.target());
let indices = rule
.indices()
.map(|n| n.parse_unsigned().unwrap())
.collect::<Vec<_>>();
assert!(!indices.is_empty(), "check this in validation");
for glyph in glyphs.iter() {
gdef.attach
.entry(glyph)
.or_default()
.extend(indices.iter().copied());
}
}
typed::GdefTableItem::LigatureCaret(rule) => {
let target = rule.target();
let glyphs = self.resolve_glyph_or_class(&target);
let carets: Vec<_> = match rule.values() {
typed::LigatureCaretValue::Pos(items) => items
.values()
.map(|n| CaretValue::Coordinate {
default: n.parse_signed(),
deltas: DeviceOrDeltas::None,
})
.collect(),
typed::LigatureCaretValue::Index(items) => items
.values()
.map(|n| CaretValue::PointIndex(n.parse_unsigned().unwrap()))
.collect(),
};
for glyph in glyphs.iter() {
gdef.ligature_pos
.entry(glyph)
.or_insert_with(|| carets.clone());
}
}
typed::GdefTableItem::ClassDef(rule) => {
for (class, id) in [
(rule.base_glyphs(), GlyphClassDef::Base),
(rule.ligature_glyphs(), GlyphClassDef::Ligature),
(rule.mark_glyphs(), GlyphClassDef::Mark),
(rule.component_glyphs(), GlyphClassDef::Component),
] {
let Some(class) = class else {
continue;
};
if let Err((bad_glyph, old_class)) =
gdef.add_glyph_class(self.resolve_glyph_class(&class), id)
{
let bad_name = self.reverse_glyph_map.get(&bad_glyph).unwrap();
let class_name = old_class.display();
self.error(class.range(), format!("class includes glyph '{bad_name}', already in class {class_name}"));
}
}
}
}
}
self.tables.gdef = Some(gdef);
}
fn resolve_head(&mut self, table: &typed::HeadTable) {
let mut head = super::tables::HeadBuilder::default();
let font_rev = table.statements().last().unwrap().value();
head.font_revision = font_rev.parse_fixed();
self.tables.head = Some(head);
}
fn resolve_name_spec(&mut self, node: &typed::NameSpec) -> super::tables::NameSpec {
const WIN_DEFAULT_IDS: (u16, u16) = (1, 0x0409);
const MAC_DEFAULT_IDS: (u16, u16) = (0, 0);
let platform_id = node
.platform_id()
.map(|n| n.parse().unwrap())
.unwrap_or(tags::WIN_PLATFORM_ID);
let (encoding_id, language_id) = match node.platform_and_language_ids() {
Some((platform, language)) => (platform.parse().unwrap(), language.parse().unwrap()),
None => match platform_id {
tags::MAC_PLATFORM_ID => MAC_DEFAULT_IDS,
tags::WIN_PLATFORM_ID => WIN_DEFAULT_IDS,
_ => panic!("missed validation"),
},
};
super::tables::NameSpec {
platform_id,
encoding_id,
language_id,
string: node.string().into(),
}
}
fn resolve_lookup_ref(&mut self, lookup: typed::LookupRef) {
let id = self
.lookups
.get_named(&lookup.label().text)
.expect("checked in validation pass");
self.add_lookup_to_current_feature_if_present(id);
}
fn resolve_lookup_block(&mut self, lookup: typed::LookupBlock) {
self.start_lookup_block(lookup.label());
for item in lookup.statements() {
self.resolve_statement(item);
}
self.end_lookup_block();
}
fn resolve_statement(&mut self, item: &NodeOrToken) {
if let Some(script) = typed::Script::cast(item) {
self.set_script(script);
} else if let Some(language) = typed::Language::cast(item) {
self.set_language(language);
} else if let Some(lookupflag) = typed::LookupFlag::cast(item) {
self.set_lookup_flag(lookupflag);
} else if let Some(glyph_def) = typed::GlyphClassDef::cast(item) {
self.define_glyph_class(glyph_def);
} else if let Some(glyph_def) = typed::MarkClassDef::cast(item) {
self.define_mark_class(glyph_def);
} else if item.kind() == Kind::SubtableNode {
self.add_subtable_break();
} else if let Some(lookup) = typed::LookupRef::cast(item) {
self.resolve_lookup_ref(lookup);
} else if let Some(lookup) = typed::LookupBlock::cast(item) {
self.resolve_lookup_block(lookup);
} else if let Some(rule) = typed::GsubStatement::cast(item) {
if self.opts.compile_gsub {
self.add_gsub_statement(rule);
}
} else if let Some(rule) = typed::GposStatement::cast(item) {
if self.opts.compile_gpos {
self.add_gpos_statement(rule)
}
} else if item.kind() == Kind::Semi {
} else if item.kind() == Kind::Comment {
assert!(item
.token_text()
.unwrap_or_default()
.contains("# Automatic Code"));
if self.active_feature.is_none() {
self.warning(
item.range(),
"Insertion marker outside feature block will be ignored",
);
return;
};
if let Some((id, _name)) = self.lookups.finish_current() {
if _name.is_some() {
self.error(
item.range(),
"insertion marker cannot be inside named lookup block",
);
}
self.add_lookup_to_current_feature_if_present(id);
}
let current_feature = self.active_feature.as_ref().unwrap().tag;
let priority = item.range().start;
self.insert_markers.insert(
current_feature,
InsertionPoint {
lookup_id: self.lookups.next_gpos_id(),
priority,
},
);
} else {
let span = match item {
NodeOrToken::Token(t) => t.range(),
NodeOrToken::Node(node) => {
let range = node.range();
let end = range.end.min(range.start + 16);
range.start..end
}
};
self.error(span, format!("unhandled statement: '{}'", item.kind()));
}
}
fn define_named_value_record(&mut self, item: typed::ValueRecordDef) {
let name = item.name();
let record = item.value_record();
let resolved = self.resolve_value_record(&record);
self.value_record_defs.insert(name.text.clone(), resolved);
}
fn define_named_anchor(&mut self, anchor_def: typed::AnchorDef) {
let anchor_block = anchor_def.anchor();
let name = anchor_def.name();
let anchor = match self.resolve_anchor(&anchor_block) {
Some(a) if !a.x.has_device_or_deltas() && !a.y.has_device_or_deltas() => a,
_ => {
return self.error(
anchor_block.range(),
"named anchor definition can only be in format A or B",
)
}
};
if let Some(_prev) = self
.anchor_defs
.insert(name.text.clone(), (anchor, anchor_def.range().start))
{
self.error(name.range(), "duplicate anchor definition");
}
}
fn resolve_anchor(&mut self, item: &typed::Anchor) -> Option<Anchor> {
if item.null().is_some() {
return None;
}
if let Some(name) = item.name() {
match self.anchor_defs.get(&name.text) {
Some((anchor, pos)) if *pos < item.range().start => return Some(anchor.clone()),
_ => {
self.error(name.range(), "anchor is not defined");
return None;
}
}
}
let Some((x, y)) = item.coords() else {
self.error(item.range(), "unexpected parse failure, please file a bug");
return None;
};
let x = self.resolve_metric(&x);
let y = self.resolve_metric(&y);
let mut anchor = Anchor {
x,
y,
contourpoint: None,
};
if let Some(point) = item.contourpoint() {
match point.parse_unsigned() {
Some(point) => anchor.contourpoint = Some(point),
None => panic!("negative contourpoint, go fix your parser"),
}
} else if let Some((x_dev, y_dev)) = item.devices() {
anchor.x.device_or_deltas = x_dev.compile().into();
anchor.y.device_or_deltas = y_dev.compile().into();
}
Some(anchor)
}
fn define_condition_set(&mut self, node: typed::ConditionSet) {
let Some(var_info) = self.variation_info else {
unreachable!("checked in validation pass");
};
let label = node.label();
let conditions = node
.conditions()
.map(|cond| {
let tag = cond.tag().to_raw();
let min = UserCoord::new(cond.min_value().parse_signed());
let max = UserCoord::new(cond.max_value().parse_signed());
let (axis_index, axis) = var_info.axis(tag).unwrap();
ConditionFormat1 {
axis_index: axis_index as u16,
filter_range_min_value: F2Dot14::from_f32(
min.to_normalized(&axis.converter).to_f64() as _,
),
filter_range_max_value: F2Dot14::from_f32(
max.to_normalized(&axis.converter).to_f64() as _,
),
}
.into()
})
.collect();
let conditionset = ConditionSet::new(conditions);
self.conditionset_defs
.insert(label.text.clone(), conditionset);
}
fn resolve_condition_set(&mut self, name: Option<&Token>) -> ConditionSet {
let condset = name
.as_ref()
.map(|name| self.conditionset_defs.get(&name.text).expect("validated"))
.cloned()
.unwrap_or_default();
self.conditionset_defs.register_use(&condset);
condset
}
fn resolve_glyph_or_class(&mut self, item: &typed::GlyphOrClass) -> GlyphOrClass {
match item {
typed::GlyphOrClass::Glyph(name) => GlyphOrClass::Glyph(self.resolve_glyph_name(name)),
typed::GlyphOrClass::Cid(cid) => GlyphOrClass::Glyph(self.resolve_cid(cid)),
typed::GlyphOrClass::Class(class) => {
GlyphOrClass::Class(self.resolve_glyph_class_literal(class))
}
typed::GlyphOrClass::NamedClass(name) => {
GlyphOrClass::Class(self.resolve_named_glyph_class(name))
}
typed::GlyphOrClass::Null(_) => GlyphOrClass::Null,
}
}
fn resolve_glyph(&mut self, item: &typed::Glyph) -> GlyphId16 {
match item {
typed::Glyph::Named(name) => self.resolve_glyph_name(name),
typed::Glyph::Cid(name) => self.resolve_cid(name),
typed::Glyph::Null(_) => GlyphId16::NOTDEF,
}
}
fn resolve_glyph_class(&mut self, item: &typed::GlyphClass) -> GlyphClass {
match item {
typed::GlyphClass::Named(name) => self.resolve_named_glyph_class(name),
typed::GlyphClass::Literal(lit) => self.resolve_glyph_class_literal(lit),
}
}
fn resolve_glyph_class_literal(&mut self, class: &typed::GlyphClassLiteral) -> GlyphClass {
let mut glyphs = Vec::new();
for item in class.items() {
if let Some(id) =
typed::GlyphName::cast(item).map(|name| self.resolve_glyph_name(&name))
{
glyphs.push(id);
} else if let Some(id) = typed::Cid::cast(item).map(|cid| self.resolve_cid(&cid)) {
glyphs.push(id);
} else if let Some(range) = typed::GlyphRange::cast(item) {
self.add_glyphs_from_range(&range, &mut glyphs);
} else if let Some(alias) = typed::GlyphClassName::cast(item) {
glyphs.extend(self.resolve_named_glyph_class(&alias).items());
} else {
panic!("unexptected kind in class literal: '{}'", item.kind());
}
}
glyphs.into()
}
fn resolve_named_glyph_class(&mut self, name: &typed::GlyphClassName) -> GlyphClass {
self.glyph_class_defs
.get(name.text())
.cloned()
.or_else(|| {
self.mark_classes.get(name.text()).map(|cls| {
cls.members
.iter()
.flat_map(|(glyphs, _)| glyphs.iter())
.collect()
})
})
.unwrap()
}
fn resolve_glyph_name(&mut self, name: &typed::GlyphName) -> GlyphId16 {
self.glyph_map.get(name.text()).unwrap()
}
fn resolve_lookahead_sequence(
&mut self,
seq: impl Iterator<Item = typed::GlyphOrClass>,
) -> Vec<GlyphOrClass> {
seq.map(|inp| self.resolve_glyph_or_class(&inp)).collect()
}
fn resolve_backtrack_sequence(
&mut self,
seq: impl Iterator<Item = typed::GlyphOrClass>,
) -> Vec<GlyphOrClass> {
let mut result = self.resolve_lookahead_sequence(seq);
result.reverse();
result
}
fn resolve_cid(&mut self, cid: &typed::Cid) -> GlyphId16 {
self.glyph_map.get(&cid.parse()).unwrap()
}
fn add_glyphs_from_range(&mut self, range: &typed::GlyphRange, out: &mut Vec<GlyphId16>) {
let start = range.start();
let end = range.end();
match (start.kind, end.kind) {
(Kind::Cid, Kind::Cid) => {
if let Err(err) = glyph_range::cid(start, end, |cid| {
match self.glyph_map.get(&cid) {
Some(id) => out.push(id),
None => {
self.error(
range.range(),
format!("Range member '{cid}' does not exist in font"),
);
}
}
}) {
self.error(range.range(), err);
}
}
(Kind::GlyphName, Kind::GlyphName) => {
if let Err(err) = glyph_range::named(start, end, |name| {
match self.glyph_map.get(name) {
Some(id) => out.push(id),
None => {
self.error(
range.range(),
format!("Range member '{name}' does not exist in font"),
);
}
}
}) {
self.error(range.range(), err);
}
}
(_, _) => self.error(range.range(), "Invalid types in glyph range"),
}
}
}
fn sequence_enumerator(sequence: &[GlyphOrClass]) -> Vec<Vec<GlyphId16>> {
assert!(sequence.len() >= 2);
let split = sequence.split_first();
let mut result = Vec::new();
let (left, right) = split.unwrap();
sequence_enumerator_impl(Vec::new(), left, right, &mut result);
result
}
fn sequence_enumerator_impl(
prefix: Vec<GlyphId16>,
left: &GlyphOrClass,
right: &[GlyphOrClass],
acc: &mut Vec<Vec<GlyphId16>>,
) {
for glyph in left.iter() {
let mut prefix = prefix.clone();
prefix.push(glyph);
match right.split_first() {
Some((head, tail)) => sequence_enumerator_impl(prefix, head, tail, acc),
None => acc.push(prefix),
}
}
}
fn sort_feature_variations(
variations: &mut FeatureVariations,
order_fn: impl Fn(&ConditionSet) -> usize,
) {
variations
.feature_variation_records
.sort_by_key(|record| match record.condition_set.as_ref() {
Some(condition) => order_fn(condition),
None => order_fn(&Default::default()),
})
}
fn get_reasonable_length_span(node: &NodeOrToken) -> Range<usize> {
const MAX_SPAN_LEN_FOR_NODES: usize = 32;
match node {
NodeOrToken::Token(t) => t.range(),
NodeOrToken::Node(node) => {
let range = node.range();
let end = range.end.min(range.start + MAX_SPAN_LEN_FOR_NODES);
range.start..end
}
}
}
fn for_pair_pos(record: ValueRecord, in_vert_feature: bool) -> ValueRecord {
if !record.is_all_zeros() {
return record.clear_zeros();
}
let mut out = record.clear_zeros();
if in_vert_feature {
out.y_advance = Some(0.into());
} else {
out.x_advance = Some(0.into());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn glyph_id_vec<const N: usize>(ids: [u16; N]) -> Vec<GlyphId16> {
ids.iter().copied().map(GlyphId16::new).collect()
}
#[test]
fn sequence_enumerator_smoke_test() {
let sequence = vec![
GlyphOrClass::Glyph(GlyphId16::new(1)),
GlyphOrClass::Class([2_u16, 3, 4].iter().copied().map(GlyphId16::new).collect()),
GlyphOrClass::Class([8, 9].iter().copied().map(GlyphId16::new).collect()),
];
assert_eq!(
sequence_enumerator(&sequence),
vec![
glyph_id_vec([1, 2, 8]),
glyph_id_vec([1, 2, 9]),
glyph_id_vec([1, 3, 8]),
glyph_id_vec([1, 3, 9]),
glyph_id_vec([1, 4, 8]),
glyph_id_vec([1, 4, 9]),
]
);
}
}