Skip to content

Commit

Permalink
Starting to play with modelling kerning and passing through to IR
Browse files Browse the repository at this point in the history
  • Loading branch information
rsheeter committed Jun 7, 2023
1 parent 96d77c1 commit 464f7f6
Show file tree
Hide file tree
Showing 42 changed files with 1,397 additions and 31 deletions.
9 changes: 9 additions & 0 deletions fontc/src/change_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
140 changes: 138 additions & 2 deletions fontc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,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,
Expand Down Expand Up @@ -717,6 +745,7 @@ pub fn create_workload(change_detector: &mut ChangeDetector) -> Result<Workload,
add_init_static_metadata_ir_job(change_detector, &mut workload)?;
add_global_metric_ir_job(change_detector, &mut workload)?;
add_feature_ir_job(change_detector, &mut workload)?;
add_kerning_ir_job(change_detector, &mut workload)?;
add_glyph_ir_jobs(change_detector, &mut workload)?;
add_finalize_static_metadata_ir_job(change_detector, &mut workload)?;

Expand Down Expand Up @@ -757,7 +786,7 @@ mod tests {
};
use fontdrasil::types::GlyphName;
use fontir::{
ir,
ir::{self, KernParticipant},
orchestration::{Context as FeContext, WorkId as FeWorkIdentifier},
};
use indexmap::IndexSet;
Expand Down Expand Up @@ -876,6 +905,7 @@ mod tests {
add_finalize_static_metadata_ir_job(&mut change_detector, &mut workload).unwrap();
add_glyph_ir_jobs(&mut change_detector, &mut workload).unwrap();
add_feature_ir_job(&mut change_detector, &mut workload).unwrap();
add_kerning_ir_job(&mut change_detector, &mut workload).unwrap();
add_feature_be_job(&mut change_detector, &mut workload).unwrap();

add_glyf_loca_be_job(&mut change_detector, &mut workload).unwrap();
Expand Down Expand Up @@ -932,6 +962,7 @@ mod tests {
FeWorkIdentifier::Glyph("plus".into()).into(),
FeWorkIdentifier::FinalizeStaticMetadata.into(),
FeWorkIdentifier::Features.into(),
FeWorkIdentifier::Kerning.into(),
BeWorkIdentifier::Features.into(),
BeWorkIdentifier::Avar.into(),
BeWorkIdentifier::Cmap.into(),
Expand Down Expand Up @@ -1562,7 +1593,12 @@ mod tests {
);

assert_eq!(
(0..4).map(GlyphId::new).collect::<Vec<_>>(),
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())
Expand Down Expand Up @@ -1850,4 +1886,104 @@ mod tests {
fn captures_created_from_designspace() {
assert_created_set("wght_var.designspace");
}

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::<Vec<_>>(),
)
})
.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");
}
}
2 changes: 2 additions & 0 deletions fontdrasil/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ impl Display for GlyphName {
f.write_str(self.as_str())
}
}

pub type GroupName = GlyphName;
2 changes: 2 additions & 0 deletions fontir/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 37 additions & 3 deletions fontir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -95,6 +98,37 @@ pub struct MiscMetadata {
pub created: Option<DateTime<Utc>>,
}

/// 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<GroupName, HashSet<GlyphName>>,
/// 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<NormalizedLocation, OrderedFloat<f32>>,
>,
}

/// A participant in kerning, one of the entries in a kerning pair.
///
/// Concretely, a glyph or a group of glyphs.
///
/// <https://unifiedfontobject.org/versions/ufo3/kerning.plist/#kerning-pair-types>
#[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,
Expand Down
5 changes: 5 additions & 0 deletions fontir/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pub enum WorkId {
/// BE glyphs so the glyph order may change.
FinalizeStaticMetadata,
Features,
Kerning,
}

pub type IrWork = dyn Work<Context, WorkError> + Send;
Expand Down Expand Up @@ -133,6 +134,7 @@ pub struct Context {
global_metrics: ContextItem<ir::GlobalMetrics>,
glyph_ir: Arc<RwLock<HashMap<GlyphName, Arc<ir::Glyph>>>>,
feature_ir: ContextItem<ir::Features>,
kerning: ContextItem<ir::Kerning>,
}

pub fn set_cached<T>(lock: &Arc<RwLock<Option<Arc<T>>>>, value: T) {
Expand All @@ -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(),
}
}

Expand All @@ -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)),
}
}

Expand Down Expand Up @@ -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<T>(v: &T) -> &T {
Expand Down
1 change: 1 addition & 0 deletions fontir/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
}
}
Loading

0 comments on commit 464f7f6

Please sign in to comment.