diff --git a/fontc/src/change_detector.rs b/fontc/src/change_detector.rs index 33d7bedc4..2b25f735a 100644 --- a/fontc/src/change_detector.rs +++ b/fontc/src/change_detector.rs @@ -129,6 +129,15 @@ impl ChangeDetector { .is_file() } + pub fn kerning_ir_change(&self) -> bool { + self.final_static_metadata_ir_change() + || self.current_inputs.features != self.prev_inputs.features + || !self + .ir_paths + .target_file(&FeWorkIdentifier::Kerning) + .is_file() + } + pub fn avar_be_change(&self) -> bool { self.final_static_metadata_ir_change() || !self.be_paths.target_file(&BeWorkIdentifier::Avar).is_file() diff --git a/fontc/src/lib.rs b/fontc/src/lib.rs index 84c733524..9a6212728 100644 --- a/fontc/src/lib.rs +++ b/fontc/src/lib.rs @@ -256,6 +256,34 @@ fn add_feature_be_job( Ok(()) } +fn add_kerning_ir_job( + change_detector: &mut ChangeDetector, + workload: &mut Workload, +) -> Result<(), Error> { + if change_detector.kerning_ir_change() { + let mut dependencies = HashSet::new(); + dependencies.insert(FeWorkIdentifier::FinalizeStaticMetadata.into()); + + let id: AnyWorkId = FeWorkIdentifier::Kerning.into(); + let write_access = Access::one(id.clone()); + workload.insert( + id, + Job { + work: change_detector + .ir_source() + .create_kerning_ir_work(change_detector.current_inputs())? + .into(), + dependencies, + read_access: ReadAccess::Dependencies, + write_access, + }, + ); + } else { + workload.mark_success(FeWorkIdentifier::Kerning); + } + Ok(()) +} + fn add_glyph_ir_jobs( change_detector: &mut ChangeDetector, workload: &mut Workload, @@ -743,6 +771,7 @@ pub fn create_workload(change_detector: &mut ChangeDetector) -> Result>(), + vec![ + GlyphId::new(0), + GlyphId::new(1), + GlyphId::new(2), + GlyphId::new(5) + ], [0x20, 0x21, 0x2d, 0x3d] .iter() .map(|cp| font.cmap().unwrap().map_codepoint(*cp as u32).unwrap()) @@ -1908,4 +1944,104 @@ mod tests { ) ); } + + fn assert_simple_kerning(source: &str) { + let temp_dir = tempdir().unwrap(); + let build_dir = temp_dir.path(); + let result = compile(Args::for_test(build_dir, source)); + + let kerning = result.fe_context.get_kerning(); + + let mut groups: Vec<_> = kerning + .groups + .iter() + .map(|(name, entries)| { + let mut entries: Vec<_> = entries.iter().map(|e| e.as_str()).collect(); + entries.sort(); + (name.as_str(), entries) + }) + .collect(); + groups.sort(); + + let mut kerns: Vec<_> = kerning + .kerns + .iter() + .map(|((side1, side2), values)| { + ( + side1, + side2, + values + .iter() + .map(|(loc, val)| { + assert_eq!(loc.axis_names().count(), 1, "Should be only weight"); + let (axis, pos) = loc.iter().next().unwrap(); + (format!("{axis} {}", pos.to_f32()), val.0) + }) + .collect::>(), + ) + }) + .collect(); + kerns.sort_by_key(|(side1, side2, _)| (*side1, *side2)); + + assert_eq!( + (groups, kerns), + ( + vec![ + ("public.kern1.brackets", vec!["bracketleft", "bracketright"],), + ("public.kern2.brackets", vec!["bracketleft", "bracketright"],) + ], + vec![ + ( + &KernParticipant::Glyph("bracketleft".into()), + &KernParticipant::Glyph("bracketright".into()), + vec![ + ("Weight 0".to_string(), -300.0), + ("Weight 1".to_string(), -150.0) + ], + ), + ( + &KernParticipant::Glyph("exclam".into()), + &KernParticipant::Glyph("exclam".into()), + vec![ + ("Weight 0".to_string(), -360.0), + ("Weight 1".to_string(), -100.0) + ], + ), + ( + &KernParticipant::Glyph("exclam".into()), + &KernParticipant::Glyph("hyphen".into()), + vec![("Weight 0".to_string(), 20.0),], + ), + ( + &KernParticipant::Glyph("exclam".into()), + &KernParticipant::Group("public.kern2.brackets".into()), + vec![("Weight 0".to_string(), -160.0),], + ), + ( + &KernParticipant::Glyph("hyphen".into()), + &KernParticipant::Glyph("hyphen".into()), + vec![ + ("Weight 0".to_string(), -150.0), + ("Weight 1".to_string(), -50.0) + ], + ), + ( + &KernParticipant::Group("public.kern1.brackets".into()), + &KernParticipant::Glyph("exclam".into()), + vec![("Weight 0".to_string(), -165.0),], + ), + ], + ), + ); + } + + #[test] + fn kerning_from_glyphs() { + assert_simple_kerning("glyphs3/WghtVar.glyphs"); + } + + #[test] + fn kerning_from_ufo() { + assert_simple_kerning("designspace_from_glyphs/WghtVar.designspace"); + } } diff --git a/fontdrasil/src/types.rs b/fontdrasil/src/types.rs index 0e47246fa..8e9da6b82 100644 --- a/fontdrasil/src/types.rs +++ b/fontdrasil/src/types.rs @@ -49,3 +49,5 @@ impl Display for GlyphName { f.write_str(self.as_str()) } } + +pub type GroupName = GlyphName; diff --git a/fontir/src/error.rs b/fontir/src/error.rs index bc0ce4ca4..324d73fe6 100644 --- a/fontir/src/error.rs +++ b/fontir/src/error.rs @@ -130,6 +130,8 @@ pub enum WorkError { AxisMustMapMin(Tag), #[error("Axis '{0}' must map max if it maps anything")] AxisMustMapMax(Tag), + #[error("No kerning group or glyph for name {0:?}")] + InvalidKernSide(String), } /// An async work error, hence one that must be Send diff --git a/fontir/src/ir.rs b/fontir/src/ir.rs index 0ad8584e3..002e21787 100644 --- a/fontir/src/ir.rs +++ b/fontir/src/ir.rs @@ -3,20 +3,23 @@ use crate::{ coords::{CoordConverter, NormalizedCoord, NormalizedLocation, UserCoord, UserLocation}, error::{PathConversionError, VariationModelError, WorkError}, - serde::{GlobalMetricsSerdeRepr, GlyphSerdeRepr, MiscSerdeRepr, StaticMetadataSerdeRepr}, + serde::{ + GlobalMetricsSerdeRepr, GlyphSerdeRepr, KerningSerdeRepr, MiscSerdeRepr, + StaticMetadataSerdeRepr, + }, variations::VariationModel, }; use chrono::{DateTime, Utc}; use font_types::NameId; use font_types::Tag; -use fontdrasil::types::GlyphName; +use fontdrasil::types::{GlyphName, GroupName}; use indexmap::IndexSet; use kurbo::{Affine, BezPath, PathEl, Point}; use log::{trace, warn}; use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, fmt::Debug, path::{Path, PathBuf}, }; @@ -95,6 +98,37 @@ pub struct MiscMetadata { pub created: Option>, } +/// IR representation of kerning. +/// +/// In UFO terms, roughly [groups.plist](https://unifiedfontobject.org/versions/ufo3/groups.plist/) +/// and [kerning.plist](https://unifiedfontobject.org/versions/ufo3/kerning.plist/) combined. +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] +#[serde(from = "KerningSerdeRepr", into = "KerningSerdeRepr")] +pub struct Kerning { + pub groups: HashMap>, + /// An adjustment to the space *between* two glyphs in logical order. + /// + /// Maps (side1, side2) => a mapping location:adjustment. + /// + /// Used for both LTR and RTL. The BE application differs but the concept + /// is the same. + pub kerns: HashMap< + (KernParticipant, KernParticipant), + BTreeMap>, + >, +} + +/// A participant in kerning, one of the entries in a kerning pair. +/// +/// Concretely, a glyph or a group of glyphs. +/// +/// +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum KernParticipant { + Glyph(GlyphName), + Group(GroupName), +} + impl StaticMetadata { pub fn new( units_per_em: u16, diff --git a/fontir/src/orchestration.rs b/fontir/src/orchestration.rs index c93d219d1..dd203d6e3 100644 --- a/fontir/src/orchestration.rs +++ b/fontir/src/orchestration.rs @@ -106,6 +106,7 @@ pub enum WorkId { /// BE glyphs so the glyph order may change. FinalizeStaticMetadata, Features, + Kerning, } pub type IrWork = dyn Work + Send; @@ -133,6 +134,7 @@ pub struct Context { global_metrics: ContextItem, glyph_ir: Arc>>>, feature_ir: ContextItem, + kerning: ContextItem, } pub fn set_cached(lock: &Arc>>>, value: T) { @@ -152,6 +154,7 @@ impl Context { global_metrics: self.global_metrics.clone(), glyph_ir: self.glyph_ir.clone(), feature_ir: self.feature_ir.clone(), + kerning: self.kerning.clone(), } } @@ -166,6 +169,7 @@ impl Context { global_metrics: Arc::from(RwLock::new(None)), glyph_ir: Arc::from(RwLock::new(HashMap::new())), feature_ir: Arc::from(RwLock::new(None)), + kerning: Arc::from(RwLock::new(None)), } } @@ -234,6 +238,7 @@ impl Context { context_accessors! { get_final_static_metadata, set_final_static_metadata, has_final_static_metadata, final_static_metadata, ir::StaticMetadata, WorkId::FinalizeStaticMetadata, restore, nop } context_accessors! { get_global_metrics, set_global_metrics, has_global_metrics, global_metrics, ir::GlobalMetrics, WorkId::GlobalMetrics, restore, nop } context_accessors! { get_features, set_features, has_feature_ir, feature_ir, ir::Features, WorkId::Features, restore, nop } + context_accessors! { get_kerning, set_kerning, has_kerning, kerning, ir::Kerning, WorkId::Kerning, restore, nop } } fn nop(v: &T) -> &T { diff --git a/fontir/src/paths.rs b/fontir/src/paths.rs index d65231a3e..ef77e3bad 100644 --- a/fontir/src/paths.rs +++ b/fontir/src/paths.rs @@ -49,6 +49,7 @@ impl Paths { WorkId::Glyph(name) => self.glyph_ir_file(name.as_str()), WorkId::GlyphIrDelete => self.build_dir.join("delete.yml"), WorkId::Features => self.build_dir.join("features.yml"), + WorkId::Kerning => self.build_dir.join("kerning.yml"), } } } diff --git a/fontir/src/serde.rs b/fontir/src/serde.rs index 928f5f32a..f1b8d7926 100644 --- a/fontir/src/serde.rs +++ b/fontir/src/serde.rs @@ -13,8 +13,8 @@ use write_fonts::tables::os2::SelectionFlags; use crate::{ coords::{CoordConverter, DesignCoord, NormalizedLocation, UserCoord}, ir::{ - Axis, GlobalMetric, GlobalMetrics, Glyph, GlyphBuilder, GlyphInstance, MiscMetadata, - NameKey, NamedInstance, StaticMetadata, + Axis, GlobalMetric, GlobalMetrics, Glyph, GlyphBuilder, GlyphInstance, KernParticipant, + Kerning, MiscMetadata, NameKey, NamedInstance, StaticMetadata, }, stateset::{FileState, MemoryState, State, StateIdentifier, StateSet}, }; @@ -96,6 +96,88 @@ impl From for StaticMetadataSerdeRepr { } } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct KerningSerdeRepr { + pub groups: Vec, + pub kerns: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct KerningGroupSerdeRepr { + name: String, + glyphs: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct KernSerdeRepr { + side1: KernParticipant, + side2: KernParticipant, + values: Vec<(NormalizedLocation, f32)>, +} + +impl From for KerningSerdeRepr { + fn from(from: Kerning) -> Self { + KerningSerdeRepr { + groups: from + .groups + .into_iter() + .map(|(name, glyphs)| { + let name = name.to_string(); + let mut glyphs: Vec<_> = glyphs.iter().map(|g| g.to_string()).collect(); + glyphs.sort(); + KerningGroupSerdeRepr { name, glyphs } + }) + .collect(), + kerns: from + .kerns + .into_iter() + .map(|((side1, side2), values)| { + let mut values: Vec<_> = values + .into_iter() + .map(|(pos, adjustment)| (pos, adjustment.0)) + .collect(); + values.sort_by_key(|(pos, _)| pos.clone()); + KernSerdeRepr { + side1, + side2, + values, + } + }) + .collect(), + } + } +} + +impl From for Kerning { + fn from(from: KerningSerdeRepr) -> Self { + Kerning { + groups: from + .groups + .into_iter() + .map(|g| { + ( + g.name.into(), + g.glyphs.into_iter().map(|n| n.into()).collect(), + ) + }) + .collect(), + kerns: from + .kerns + .into_iter() + .map(|k| { + ( + (k.side1, k.side2), + k.values + .into_iter() + .map(|(pos, value)| (pos, value.into())) + .collect(), + ) + }) + .collect(), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct MiscSerdeRepr { pub selection_flags: u16, diff --git a/fontir/src/source.rs b/fontir/src/source.rs index 41985fa44..c2ad8158c 100644 --- a/fontir/src/source.rs +++ b/fontir/src/source.rs @@ -69,6 +69,11 @@ pub trait Source { /// /// When run work should update [Context] with [crate::ir::Features]. fn create_feature_ir_work(&self, input: &Input) -> Result, Error>; + + /// Create a function that could be called to generate or identify kerning. + /// + /// When run work should update [Context] with [crate::ir::Kerning]. + fn create_kerning_ir_work(&self, input: &Input) -> Result, Error>; } /// The files (in future non-file sources?) that drive various parts of IR diff --git a/glyphs-reader/src/font.rs b/glyphs-reader/src/font.rs index 65800c67c..851e00734 100644 --- a/glyphs-reader/src/font.rs +++ b/glyphs-reader/src/font.rs @@ -80,6 +80,8 @@ impl FeatureSnippet { pub struct Glyph { pub glyphname: String, pub layers: Vec, + pub left_kern: Option, + pub right_kern: Option, } #[derive(Debug, PartialEq, Hash)] @@ -166,6 +168,8 @@ pub struct Axis { pub struct RawGlyph { pub layers: Vec, pub glyphname: String, + pub kern_left: Option, + pub kern_right: Option, #[fromplist(rest)] pub other_stuff: BTreeMap, } @@ -975,6 +979,14 @@ impl RawFont { if let Some(kerning) = self.other_stuff.remove("kerning") { self.other_stuff.insert("kerningLTR".to_string(), kerning); }; + for glyph in self.glyphs.iter_mut() { + if let Some(Plist::String(group)) = glyph.other_stuff.remove("leftKerningGroup") { + glyph.kern_left = Some(group.clone()); + } + if let Some(Plist::String(group)) = glyph.other_stuff.remove("rightKerningGroup") { + glyph.kern_right = Some(group.clone()); + } + } Ok(()) } @@ -1340,6 +1352,8 @@ impl TryFrom for Glyph { Ok(Glyph { glyphname: from.glyphname, layers: instances, + left_kern: from.kern_left, + right_kern: from.kern_right, }) } } @@ -1953,7 +1967,14 @@ mod tests { fn glyph_order_default_is_file_order() { let font = Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap(); assert_eq!( - vec!["space", "exclam", "hyphen", "manual-component"], + vec![ + "space", + "exclam", + "hyphen", + "bracketleft", + "bracketright", + "manual-component" + ], font.glyph_order ); } @@ -2192,6 +2213,22 @@ mod tests { .collect::>() ); + let actual_groups: Vec<_> = font + .glyphs + .iter() + .filter_map(|(name, glyph)| { + if glyph.left_kern.is_some() || glyph.right_kern.is_some() { + Some(( + name.as_str(), + glyph.left_kern.as_deref(), + glyph.right_kern.as_deref(), + )) + } else { + None + } + }) + .collect(); + let actual_kerning = font .kerning_ltr .get("m01") @@ -2201,12 +2238,21 @@ mod tests { .collect::>(); assert_eq!( - vec![ - ("exclam", "exclam", -360), - ("exclam", "hyphen", 20), - ("hyphen", "hyphen", -150), - ], - actual_kerning, + ( + vec![ + ("bracketleft", Some("brackets"), Some("brackets")), + ("bracketright", Some("brackets"), Some("brackets")), + ], + vec![ + ("@MMK_L_brackets", "exclam", -165), + ("bracketleft", "bracketright", -300), + ("exclam", "@MMK_R_brackets", -160), + ("exclam", "exclam", -360), + ("exclam", "hyphen", 20), + ("hyphen", "hyphen", -150), + ], + ), + (actual_groups, actual_kerning), ); } } diff --git a/glyphs2fontir/src/source.rs b/glyphs2fontir/src/source.rs index 998fac0ad..bb6893bfa 100644 --- a/glyphs2fontir/src/source.rs +++ b/glyphs2fontir/src/source.rs @@ -1,12 +1,12 @@ use chrono::{TimeZone, Utc}; use font_types::{NameId, Tag}; use fontdrasil::orchestration::Work; -use fontdrasil::types::GlyphName; +use fontdrasil::types::{GlyphName, GroupName}; use fontir::coords::NormalizedCoord; use fontir::error::{Error, WorkError}; use fontir::ir::{ - self, GlobalMetric, GlobalMetrics, GlyphInstance, NameBuilder, NameKey, NamedInstance, - StaticMetadata, DEFAULT_VENDOR_ID, + self, GlobalMetric, GlobalMetrics, GlyphInstance, KernParticipant, Kerning, NameBuilder, + NameKey, NamedInstance, StaticMetadata, DEFAULT_VENDOR_ID, }; use fontir::orchestration::{Context, IrWork}; use fontir::source::{Input, Source}; @@ -223,6 +223,16 @@ impl Source for GlyphsIrSource { font_info: cache.font_info.clone(), })) } + + fn create_kerning_ir_work(&self, input: &Input) -> Result, Error> { + self.check_static_metadata(&input.static_metadata)?; + + let cache = self.cache.as_ref().unwrap(); + + Ok(Box::new(KerningWork { + font_info: cache.font_info.clone(), + })) + } } fn try_name_id(name: &str) -> Option { @@ -458,6 +468,138 @@ impl Work for FeatureWork { } } +/// What side of the kern is this, in logical order +enum KernSide { + L, + R, +} + +impl KernSide { + fn class_prefix(&self) -> &'static str { + match self { + KernSide::L => "@MMK_L_", + KernSide::R => "@MMK_R_", + } + } + + fn group_prefix(&self) -> &'static str { + match self { + KernSide::L => "public.kern1.", + KernSide::R => "public.kern2.", + } + } +} + +struct KerningWork { + font_info: Arc, +} + +/// See +fn kern_participant( + glyph_order: &IndexSet, + groups: &HashMap>, + side: KernSide, + raw_side: &str, +) -> Option { + if raw_side.starts_with(side.class_prefix()) { + let group_name = format!( + "{}{}", + side.group_prefix(), + raw_side.strip_prefix(side.class_prefix()).unwrap() + ); + let group = GroupName::from(&group_name); + if groups.contains_key(&group) { + Some(KernParticipant::Group(group)) + } else { + warn!("Invalid kern side: {raw_side}, no group {group_name}"); + None + } + } else { + let name = GlyphName::from(raw_side); + if glyph_order.contains(&name) { + Some(KernParticipant::Glyph(name)) + } else { + warn!("Invalid kern side: {raw_side}, no such glyph"); + None + } + } +} + +impl Work for KerningWork { + fn exec(&self, context: &Context) -> Result<(), WorkError> { + trace!("Generate IR for kerning"); + let static_metadata = context.get_final_static_metadata(); + let glyph_order = &static_metadata.glyph_order; + let font_info = self.font_info.as_ref(); + let font = &font_info.font; + + let master_positions: HashMap<_, _> = font + .masters + .iter() + .map(|m| (&m.id, font_info.locations.get(&m.axes_values).unwrap())) + .collect(); + + let mut kerning = Kerning::default(); + + // If glyph uses a group for either side it goes in that group + font.glyphs + .iter() + .flat_map(|(glyph_name, glyph)| { + glyph + .left_kern + .iter() + .map(|group| (KernSide::L, group)) + .chain(glyph.right_kern.iter().map(|group| (KernSide::R, group))) + .map(|(side, group_name)| { + ( + GroupName::from(format!("{}{}", side.group_prefix(), group_name)), + GlyphName::from(glyph_name.as_str()), + ) + }) + }) + .for_each(|(group_name, glyph_name)| { + kerning + .groups + .entry(group_name) + .or_default() + .insert(glyph_name); + }); + + font.kerning_ltr + .iter() + .filter_map(|(master_id, kerns)| match master_positions.get(master_id) { + Some(pos) => Some((pos, kerns)), + None => { + warn!("Kerning is present for non-existent master {master_id}"); + None + } + }) + .flat_map(|(master_pos, kerns)| { + kerns.iter().map(|((side1, side2), adjustment)| { + ((side1, side2), ((*master_pos).clone(), *adjustment)) + }) + }) + .filter_map(|((side1, side2), pos_adjust)| { + let side1 = kern_participant(glyph_order, &kerning.groups, KernSide::L, side1); + let side2 = kern_participant(glyph_order, &kerning.groups, KernSide::R, side2); + let (Some(side1), Some(side2)) = (side1, side2) else { + return None + }; + Some(((side1, side2), pos_adjust)) + }) + .for_each(|(participants, (pos, value))| { + kerning + .kerns + .entry(participants) + .or_default() + .insert(pos, (value as f32).into()); + }); + + context.set_kerning(kerning); + Ok(()) + } +} + struct GlyphIrWork { glyph_name: GlyphName, font_info: Arc, @@ -620,7 +762,14 @@ mod tests { #[test] fn find_glyphs() { assert_eq!( - HashSet::from(["space", "hyphen", "exclam", "manual-component"]), + HashSet::from([ + "space", + "hyphen", + "exclam", + "bracketleft", + "bracketright", + "manual-component" + ]), glyph_state_for_file(&glyphs3_dir(), "WghtVar.glyphs") .keys() .map(|k| k.as_str()) @@ -685,10 +834,17 @@ mod tests { .map(|a| a.tag) .collect::>() ); - let expected: IndexSet = vec!["space", "exclam", "hyphen", "manual-component"] - .iter() - .map(|s| (*s).into()) - .collect(); + let expected: IndexSet = vec![ + "space", + "exclam", + "hyphen", + "bracketleft", + "bracketright", + "manual-component", + ] + .iter() + .map(|s| (*s).into()) + .collect(); assert_eq!(expected, context.get_init_static_metadata().glyph_order); } diff --git a/resources/testdata/designspace_from_glyphs/README.md b/resources/testdata/designspace_from_glyphs/README.md new file mode 100644 index 000000000..1a86d1028 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/README.md @@ -0,0 +1,6 @@ +Created by converting .glyphs files using fontmake, e.g.: + +```shell +$ fontmake -o=ufo resources/testdata/glyphs3/WghtVar.glyphs +$ mv master_ufo/* resources/testdata/designspace_from_glyphs/ +``` \ No newline at end of file diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/fontinfo.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/fontinfo.plist new file mode 100644 index 000000000..3ad1eda79 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/fontinfo.plist @@ -0,0 +1,53 @@ + + + + + ascender + 800 + capHeight + 700 + copyright + Copy! + descender + -200 + familyName + WghtVar + italicAngle + 0 + openTypeHeadCreated + 2022/12/01 04:52:20 + openTypeNameDescription + The greatest weight var + openTypeNameLicenseURL + https://example.com/my/font/license + openTypeNameVersion + New Value + openTypeOS2Selection + + 7 + 8 + + openTypeOS2Type + + 3 + + postscriptUnderlinePosition + -100 + postscriptUnderlineThickness + 50 + styleMapFamilyName + WghtVar + styleMapStyleName + bold + styleName + Bold + unitsPerEm + 1000 + versionMajor + 42 + versionMinor + 42 + xHeight + 500 + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/bracketleft.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/bracketleft.glif new file mode 100644 index 000000000..58d3becdd --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/bracketleft.glif @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/bracketright.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/bracketright.glif new file mode 100644 index 000000000..4bd39ccd6 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/bracketright.glif @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/contents.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/contents.plist new file mode 100644 index 000000000..47862ecda --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/contents.plist @@ -0,0 +1,18 @@ + + + + + bracketleft + bracketleft.glif + bracketright + bracketright.glif + exclam + exclam.glif + hyphen + hyphen.glif + manual-component + manual-component.glif + space + space.glif + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/exclam.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/exclam.glif new file mode 100644 index 000000000..c16771227 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/exclam.glif @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/hyphen.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/hyphen.glif new file mode 100644 index 000000000..4bc2c7341 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/hyphen.glif @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/manual-component.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/manual-component.glif new file mode 100644 index 000000000..cd1d614cb --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/manual-component.glif @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/space.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/space.glif new file mode 100644 index 000000000..983348125 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/glyphs/space.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/groups.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/groups.plist new file mode 100644 index 000000000..15368f4da --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.brackets + + bracketleft + bracketright + + public.kern2.brackets + + bracketleft + bracketright + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/kerning.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/kerning.plist new file mode 100644 index 000000000..c49f61f57 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/kerning.plist @@ -0,0 +1,21 @@ + + + + + bracketleft + + bracketright + -150 + + exclam + + exclam + -100 + + hyphen + + hyphen + -50 + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/layercontents.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/layercontents.plist new file mode 100644 index 000000000..b9c1a4f27 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/lib.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/lib.plist new file mode 100644 index 000000000..5390b2cd8 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/lib.plist @@ -0,0 +1,55 @@ + + + + + com.github.googlei18n.ufo2ft.filters + + + name + eraseOpenCorners + namespace + glyphsLib.filters + pre + + + + com.schriftgestaltung.customParameter.GSFont.disablesAutomaticAlignment + + com.schriftgestaltung.customParameter.GSFont.useNiceNames + 1 + com.schriftgestaltung.customParameter.GSFontMaster.customValue + 0 + com.schriftgestaltung.customParameter.GSFontMaster.customValue1 + 0 + com.schriftgestaltung.customParameter.GSFontMaster.customValue2 + 0 + com.schriftgestaltung.customParameter.GSFontMaster.customValue3 + 0 + com.schriftgestaltung.customParameter.GSFontMaster.iconName + Bold + com.schriftgestaltung.customParameter.GSFontMaster.weightValue + 700 + com.schriftgestaltung.customParameter.GSFontMaster.widthValue + 100 + com.schriftgestaltung.fontMasterOrder + 1 + com.schriftgestaltung.weightValue + 700 + com.schriftgestaltung.widthValue + 100 + public.glyphOrder + + space + exclam + hyphen + bracketleft + bracketright + manual-component + + public.postscriptNames + + manual-component + manualcomponent + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/metainfo.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/metainfo.plist new file mode 100644 index 000000000..7b8b34ac6 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Bold.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/fontinfo.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/fontinfo.plist new file mode 100644 index 000000000..6fc0f394c --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/fontinfo.plist @@ -0,0 +1,65 @@ + + + + + ascender + 737 + capHeight + 702 + copyright + Copy! + descender + -42 + familyName + WghtVar + italicAngle + 0 + openTypeHeadCreated + 2022/12/01 04:52:20 + openTypeNameDescription + The greatest weight var + openTypeNameLicenseURL + https://example.com/my/font/license + openTypeNameVersion + New Value + openTypeOS2Selection + + 7 + 8 + + openTypeOS2Type + + 3 + + postscriptBlueValues + + -16 + 0 + 737 + 753 + + postscriptOtherBlues + + -58 + -42 + + postscriptUnderlinePosition + -100 + postscriptUnderlineThickness + 50 + styleMapFamilyName + WghtVar + styleMapStyleName + regular + styleName + Regular + unitsPerEm + 1000 + versionMajor + 42 + versionMinor + 42 + xHeight + 501 + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/bracketleft.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/bracketleft.glif new file mode 100644 index 000000000..b6bf45046 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/bracketleft.glif @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/bracketright.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/bracketright.glif new file mode 100644 index 000000000..6431af611 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/bracketright.glif @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/contents.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/contents.plist new file mode 100644 index 000000000..47862ecda --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/contents.plist @@ -0,0 +1,18 @@ + + + + + bracketleft + bracketleft.glif + bracketright + bracketright.glif + exclam + exclam.glif + hyphen + hyphen.glif + manual-component + manual-component.glif + space + space.glif + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/exclam.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/exclam.glif new file mode 100644 index 000000000..5bc6d8c77 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/exclam.glif @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/hyphen.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/hyphen.glif new file mode 100644 index 000000000..8b2d57e97 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/hyphen.glif @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/manual-component.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/manual-component.glif new file mode 100644 index 000000000..1da63bcd3 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/manual-component.glif @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/space.glif b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/space.glif new file mode 100644 index 000000000..c05cd73f7 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/glyphs/space.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/groups.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/groups.plist new file mode 100644 index 000000000..15368f4da --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/groups.plist @@ -0,0 +1,16 @@ + + + + + public.kern1.brackets + + bracketleft + bracketright + + public.kern2.brackets + + bracketleft + bracketright + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/kerning.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/kerning.plist new file mode 100644 index 000000000..716f79663 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/kerning.plist @@ -0,0 +1,30 @@ + + + + + bracketleft + + bracketright + -300 + + exclam + + exclam + -360 + hyphen + 20 + public.kern2.brackets + -160 + + hyphen + + hyphen + -150 + + public.kern1.brackets + + exclam + -165 + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/layercontents.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/layercontents.plist new file mode 100644 index 000000000..b9c1a4f27 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/lib.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/lib.plist new file mode 100644 index 000000000..f6d8314a3 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/lib.plist @@ -0,0 +1,55 @@ + + + + + com.github.googlei18n.ufo2ft.filters + + + name + eraseOpenCorners + namespace + glyphsLib.filters + pre + + + + com.schriftgestaltung.customParameter.GSFont.disablesAutomaticAlignment + + com.schriftgestaltung.customParameter.GSFont.useNiceNames + 1 + com.schriftgestaltung.customParameter.GSFontMaster.customValue + 0 + com.schriftgestaltung.customParameter.GSFontMaster.customValue1 + 0 + com.schriftgestaltung.customParameter.GSFontMaster.customValue2 + 0 + com.schriftgestaltung.customParameter.GSFontMaster.customValue3 + 0 + com.schriftgestaltung.customParameter.GSFontMaster.iconName + + com.schriftgestaltung.customParameter.GSFontMaster.weightValue + 400 + com.schriftgestaltung.customParameter.GSFontMaster.widthValue + 100 + com.schriftgestaltung.fontMasterOrder + 0 + com.schriftgestaltung.weightValue + 400 + com.schriftgestaltung.widthValue + 100 + public.glyphOrder + + space + exclam + hyphen + bracketleft + bracketright + manual-component + + public.postscriptNames + + manual-component + manualcomponent + + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/metainfo.plist b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/metainfo.plist new file mode 100644 index 000000000..7b8b34ac6 --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar-Regular.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/resources/testdata/designspace_from_glyphs/WghtVar.designspace b/resources/testdata/designspace_from_glyphs/WghtVar.designspace new file mode 100644 index 000000000..b955de11c --- /dev/null +++ b/resources/testdata/designspace_from_glyphs/WghtVar.designspace @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/testdata/glyphs2/WghtVar.glyphs b/resources/testdata/glyphs2/WghtVar.glyphs index 1e277cd7b..82a4ad937 100644 --- a/resources/testdata/glyphs2/WghtVar.glyphs +++ b/resources/testdata/glyphs2/WghtVar.glyphs @@ -1,8 +1,7 @@ { .appVersion = "3151"; DisplayStrings = ( -"-", -"!!--" +"![]!" ); copyright = "Copy!"; customParameters = ( @@ -84,7 +83,7 @@ unicode = 0020; }, { glyphname = exclam; -lastChange = "2023-06-05 23:22:51 +0000"; +lastChange = "2023-06-07 22:35:08 +0000"; layers = ( { layerId = m01; @@ -175,6 +174,100 @@ width = 600; unicode = 002D; }, { +glyphname = bracketleft; +lastChange = "2023-06-07 22:37:02 +0000"; +layers = ( +{ +layerId = m01; +paths = ( +{ +closed = 1; +nodes = ( +"324 637 LINE", +"324 51 LINE", +"454 51 LINE", +"454 -10 LINE", +"259 -10 LINE", +"259 696 LINE", +"454 696 LINE", +"454 637 LINE" +); +} +); +width = 600; +}, +{ +layerId = "E09E0C54-128D-4FEA-B209-1B70BEFE300B"; +paths = ( +{ +closed = 1; +nodes = ( +"324 629 LINE", +"324 58 LINE", +"454 58 LINE", +"454 -17 LINE", +"243 -17 LINE", +"243 704 LINE", +"454 704 LINE", +"454 629 LINE" +); +} +); +width = 600; +} +); +leftKerningGroup = brackets; +rightKerningGroup = brackets; +unicode = 005B; +}, +{ +glyphname = bracketright; +lastChange = "2023-06-07 22:35:47 +0000"; +layers = ( +{ +layerId = m01; +paths = ( +{ +closed = 1; +nodes = ( +"259 696 LINE", +"454 696 LINE", +"454 -10 LINE", +"259 -10 LINE", +"259 51 LINE", +"389 51 LINE", +"389 637 LINE", +"259 637 LINE" +); +} +); +width = 600; +}, +{ +layerId = "E09E0C54-128D-4FEA-B209-1B70BEFE300B"; +paths = ( +{ +closed = 1; +nodes = ( +"243 704 LINE", +"454 704 LINE", +"454 -17 LINE", +"243 -17 LINE", +"243 58 LINE", +"373 58 LINE", +"373 629 LINE", +"243 629 LINE" +); +} +); +width = 600; +} +); +leftKerningGroup = brackets; +rightKerningGroup = brackets; +unicode = 005D; +}, +{ glyphname = "manual-component"; lastChange = "2022-12-01 04:57:39 +0000"; layers = ( @@ -210,15 +303,25 @@ unicode = 003D; ); kerning = { m01 = { +bracketleft = { +bracketright = -300; +}; exclam = { +"@MMK_R_brackets" = -160; exclam = -360; hyphen = 20; }; hyphen = { hyphen = -150; }; +"@MMK_L_brackets" = { +exclam = -165; +}; }; "E09E0C54-128D-4FEA-B209-1B70BEFE300B" = { +bracketleft = { +bracketright = -150; +}; exclam = { exclam = -100; }; diff --git a/resources/testdata/glyphs3/WghtVar.glyphs b/resources/testdata/glyphs3/WghtVar.glyphs index 22ec2e3ec..b3f879b9f 100644 --- a/resources/testdata/glyphs3/WghtVar.glyphs +++ b/resources/testdata/glyphs3/WghtVar.glyphs @@ -2,8 +2,7 @@ .appVersion = "3151"; .formatVersion = 3; DisplayStrings = ( -"-", -"!!--" +"![]!" ); axes = ( { @@ -93,7 +92,7 @@ unicode = 32; }, { glyphname = exclam; -lastChange = "2023-06-05 23:22:51 +0000"; +lastChange = "2023-06-07 22:35:08 +0000"; layers = ( { layerId = m01; @@ -184,6 +183,100 @@ width = 600; unicode = 45; }, { +glyphname = bracketleft; +kernLeft = brackets; +kernRight = brackets; +lastChange = "2023-06-07 22:37:02 +0000"; +layers = ( +{ +layerId = m01; +shapes = ( +{ +closed = 1; +nodes = ( +(324,637,l), +(324,51,l), +(454,51,l), +(454,-10,l), +(259,-10,l), +(259,696,l), +(454,696,l), +(454,637,l) +); +} +); +width = 600; +}, +{ +layerId = "E09E0C54-128D-4FEA-B209-1B70BEFE300B"; +shapes = ( +{ +closed = 1; +nodes = ( +(324,629,l), +(324,58,l), +(454,58,l), +(454,-17,l), +(243,-17,l), +(243,704,l), +(454,704,l), +(454,629,l) +); +} +); +width = 600; +} +); +unicode = 91; +}, +{ +glyphname = bracketright; +kernLeft = brackets; +kernRight = brackets; +lastChange = "2023-06-07 22:35:47 +0000"; +layers = ( +{ +layerId = m01; +shapes = ( +{ +closed = 1; +nodes = ( +(259,696,l), +(454,696,l), +(454,-10,l), +(259,-10,l), +(259,51,l), +(389,51,l), +(389,637,l), +(259,637,l) +); +} +); +width = 600; +}, +{ +layerId = "E09E0C54-128D-4FEA-B209-1B70BEFE300B"; +shapes = ( +{ +closed = 1; +nodes = ( +(243,704,l), +(454,704,l), +(454,-17,l), +(243,-17,l), +(243,58,l), +(373,58,l), +(373,629,l), +(243,629,l) +); +} +); +width = 600; +} +); +unicode = 93; +}, +{ glyphname = "manual-component"; lastChange = "2022-12-01 04:57:39 +0000"; layers = ( @@ -220,15 +313,25 @@ unicode = 61; ); kerningLTR = { m01 = { +bracketleft = { +bracketright = -300; +}; exclam = { +"@MMK_R_brackets" = -160; exclam = -360; hyphen = 20; }; hyphen = { hyphen = -150; }; +"@MMK_L_brackets" = { +exclam = -165; +}; }; "E09E0C54-128D-4FEA-B209-1B70BEFE300B" = { +bracketleft = { +bracketright = -150; +}; exclam = { exclam = -100; }; diff --git a/ufo2fontir/src/source.rs b/ufo2fontir/src/source.rs index fab13781f..2adad62c6 100644 --- a/ufo2fontir/src/source.rs +++ b/ufo2fontir/src/source.rs @@ -7,13 +7,16 @@ use std::{ use chrono::{DateTime, TimeZone, Utc}; use font_types::{NameId, Tag}; -use fontdrasil::{orchestration::Work, types::GlyphName}; +use fontdrasil::{ + orchestration::Work, + types::{GlyphName, GroupName}, +}; use fontir::{ coords::{DesignLocation, NormalizedLocation, UserCoord}, error::{Error, WorkError}, ir::{ - Features, GlobalMetric, GlobalMetrics, NameBuilder, NameKey, NamedInstance, StaticMetadata, - DEFAULT_VENDOR_ID, + Features, GlobalMetric, GlobalMetrics, KernParticipant, Kerning, NameBuilder, NameKey, + NamedInstance, StaticMetadata, DEFAULT_VENDOR_ID, }, orchestration::{Context, IrWork}, source::{Input, Source}, @@ -346,6 +349,16 @@ impl Source for DesignSpaceIrSource { })) } + fn create_kerning_ir_work(&self, input: &Input) -> Result, Error> { + self.check_static_metadata(&input.static_metadata)?; + let cache = self.cache.as_ref().unwrap(); + + Ok(Box::new(KerningWork { + designspace_file: cache.designspace_file.clone(), + designspace: cache.designspace.clone(), + })) + } + fn create_glyph_ir_work( &self, glyph_names: &IndexSet, @@ -382,6 +395,11 @@ struct FeatureWork { fea_files: Arc>, } +struct KerningWork { + designspace_file: PathBuf, + designspace: Arc, +} + fn default_master(designspace: &DesignSpaceDocument) -> Option<(usize, &designspace::Source)> { let ds_axes = to_ir_axes(&designspace.axes).ok()?; let axes: HashMap<_, _> = ds_axes.iter().map(|a| (&a.name, a)).collect(); @@ -845,6 +863,79 @@ impl Work for FeatureWork { } } +impl Work for KerningWork { + fn exec(&self, context: &Context) -> Result<(), WorkError> { + debug!("Kerning for {:#?}", self.designspace_file); + + let designspace_dir = self.designspace_file.parent().unwrap(); + let static_metadata = context.get_final_static_metadata(); + let master_locations = master_locations(&static_metadata.axes, &self.designspace.sources); + + let mut kerning = Kerning::default(); + for source in self.designspace.sources.iter() { + let pos = master_locations.get(&source.name).unwrap(); + let ufo_dir = designspace_dir.join(&source.filename); + let data_request = norad::DataRequest::none().groups(true).kerning(true); + let font = norad::Font::load_requested_data(&ufo_dir, data_request) + .map_err(|e| WorkError::ParseError(ufo_dir, format!("{e}")))?; + + kerning.groups = font + .groups + .into_iter() + .map(|(group_name, entries)| { + ( + GroupName::from(group_name.as_str()), + entries + .into_iter() + .filter_map(|glyph_name| { + let glyph_name = GlyphName::from(glyph_name.as_str()); + if static_metadata.glyph_order.contains(&glyph_name) { + Some(glyph_name) + } else { + warn!("{} kerning group {} references non-existent glyph {}; ignoring", source.filename, group_name, glyph_name); + None + } + }) + .collect(), + ) + }) + .collect(); + + let resolve_participant = |name: &norad::Name| { + let group_name = GroupName::from(name.as_str()); + if kerning.groups.contains_key(&group_name) { + return Some(KernParticipant::Group(group_name)); + } + let glyph_name = GlyphName::from(name.as_str()); + if static_metadata.glyph_order.contains(&glyph_name) { + return Some(KernParticipant::Glyph(glyph_name)); + } + None + }; + + for (side1, side2, adjustment) in font.kerning.into_iter().flat_map(|(side1, kerns)| { + kerns + .into_iter() + .map(move |(side2, adjustment)| (side1.clone(), side2, adjustment)) + }) { + let (Some(side1), Some(side2)) = (resolve_participant(&side1), resolve_participant(&side2)) else { + warn!("{} kerning unable to resolve at least one of {}, {}; ignoring", source.name, side1.as_str(), side2.as_str()); + continue; + }; + + kerning + .kerns + .entry((side1, side2)) + .or_default() + .insert(pos.clone(), (adjustment as f32).into()); + } + } + + context.set_kerning(kerning); + Ok(()) + } +} + struct GlyphIrWork { glyph_name: GlyphName, glif_files: HashMap>,