Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example .glyphs kern and ability to read it #330

Merged
merged 2 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions glyphs-reader/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ pub struct Font {
pub version_major: i32,
pub version_minor: u32,
pub date: Option<String>,

// master id => { (name or class, name or class) => adjustment }
pub kerning_ltr: BTreeMap<String, BTreeMap<(String, String), i32>>,
}

#[derive(Debug, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -968,13 +971,21 @@ impl RawFont {
Ok(())
}

fn v2_to_v3_kerning(&mut self) -> Result<(), Error> {
if let Some(kerning) = self.other_stuff.remove("kerning") {
self.other_stuff.insert("kerningLTR".to_string(), kerning);
Comment on lines +974 to +976
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amusingly in glyphsLib we ended up actually doing the opposite i.e. down-convert the kerningLTR/kerningRTL split dictionaries into a single kerning dict, like in was in glyphs v2 and in UFO.. the history of this is complicated as everything in font-land..
You may start from googlefonts/glyphsLib#865

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in any case, assuming like you do here that all v2 "kerning" is equivalent to v3 "kerningLTR" is incorrect, only holds for with LTR-only fonts such as Oswald which you're working with.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assuming like you do here that all v2 "kerning" is equivalent to v3 "kerningLTR" is incorrect

sorry, correction: the naive approach here (v2 kerning ==> v3 kerningLTR, no swapping) is the correct one, if we want to match fontmake's single kerning dict approach; it's only when starting from a v3 source which has both kerningLTR and kerningRTL that we need to take care of reversing the order of first/second.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explicitly didn't handle the kerningRTL set yet. My intent wasthat this is valid for both v2 and v3 LTR kerning. I think you are saying it is? Perhaps I should file a bug for kerningRTL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};
Ok(())
}

/// `<See https://github.com/schriftgestalt/GlyphsSDK/blob/Glyphs3/GlyphsFileFormat/GlyphsFileFormatv3.md#differences-between-version-2>`
fn v2_to_v3(&mut self) -> Result<(), Error> {
self.v2_to_v3_weight()?;
self.v2_to_v3_axes()?;
self.v2_to_v3_metrics()?;
self.v2_to_v3_names()?;
self.v2_to_v3_instances()?;
self.v2_to_v3_kerning()?;
Ok(())
}
}
Expand Down Expand Up @@ -1027,6 +1038,31 @@ fn parse_codepoints(raw_font: &mut RawFont, radix: u32) -> BTreeMap<String, BTre
name_to_cp
}

fn parse_kerning(kerning: Option<&Plist>) -> BTreeMap<String, BTreeMap<(String, String), i32>> {
let mut result = BTreeMap::new();
let Some(Plist::Dictionary(kerning)) = kerning else {
return result;
};
for (master_id, kerning) in kerning {
let mut master_kerns = BTreeMap::new();
if let Plist::Dictionary(kerning) = kerning {
for (kern_from, kern_tos) in kerning {
let Plist::Dictionary(kern_tos) = kern_tos else {
continue;
};
for (kern_to, adjustment) in kern_tos {
let Some(adjustment) = adjustment.as_i64() else {
continue;
};
master_kerns.insert((kern_from.clone(), kern_to.clone()), adjustment as i32);
}
}
}
result.insert(master_id.clone(), master_kerns);
}
result
}

/// <https://github.com/googlefonts/glyphsLib/blob/6f243c1f732ea1092717918d0328f3b5303ffe56/Lib/glyphsLib/builder/axes.py#L578>
fn default_master_idx(raw_font: &RawFont) -> usize {
// Prefer an explicit origin
Expand Down Expand Up @@ -1584,6 +1620,7 @@ impl TryFrom<RawFont> for Font {
version_major: from.versionMajor.unwrap_or_default() as i32,
version_minor: from.versionMinor.unwrap_or_default() as u32,
date: from.date,
kerning_ltr: parse_kerning(from.other_stuff.get("kerningLTR")),
})
}
}
Expand Down Expand Up @@ -1662,7 +1699,7 @@ mod tests {
Font, FromPlist, Node, Plist, Shape,
};
use std::{
collections::{BTreeMap, BTreeSet},
collections::{BTreeMap, BTreeSet, HashSet},
path::{Path, PathBuf},
};

Expand All @@ -1673,7 +1710,11 @@ mod tests {
use kurbo::Affine;

fn testdata_dir() -> PathBuf {
let dir = Path::new("../resources/testdata");
// working dir varies CLI vs VSCode
let mut dir = Path::new("../resources/testdata");
if !dir.is_dir() {
dir = Path::new("./resources/testdata");
}
assert!(dir.is_dir());
dir.to_path_buf()
}
Expand Down Expand Up @@ -2139,4 +2180,33 @@ mod tests {
let font = Font::load(&glyphs2_dir().join("WghtVar_OS2.glyphs")).unwrap();
assert_eq!((None, None), (font.use_typo_metrics, font.has_wws_names));
}

#[test]
fn read_simple_kerning() {
let font = Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap();
assert_eq!(
HashSet::from(["m01", "E09E0C54-128D-4FEA-B209-1B70BEFE300B",]),
font.kerning_ltr
.keys()
.map(|k| k.as_str())
.collect::<HashSet<_>>()
);

let actual_kerning = font
.kerning_ltr
.get("m01")
.unwrap()
.iter()
.map(|((n1, n2), value)| (n1.as_str(), n2.as_str(), *value))
.collect::<Vec<_>>();

assert_eq!(
vec![
("exclam", "exclam", -360),
("exclam", "hyphen", 20),
("hyphen", "hyphen", -150),
],
actual_kerning,
);
}
}
2 changes: 2 additions & 0 deletions glyphs2fontir/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ impl GlyphsIrSource {
version_major: Default::default(),
version_minor: Default::default(),
date: None,
kerning_ltr: Default::default(),
};
state.track_memory("/font_master".to_string(), &font)?;
Ok(state)
Expand Down Expand Up @@ -117,6 +118,7 @@ impl GlyphsIrSource {
version_major: Default::default(),
version_minor: Default::default(),
date: None,
kerning_ltr: font.kerning_ltr.clone(),
};
state.track_memory("/font_master".to_string(), &font)?;
Ok(state)
Expand Down
25 changes: 22 additions & 3 deletions resources/testdata/glyphs2/WghtVar.glyphs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.appVersion = "3151";
DisplayStrings = (
"-",
"!"
"!!--"
);
copyright = "Copy!";
customParameters = (
Expand Down Expand Up @@ -84,7 +84,7 @@ unicode = 0020;
},
{
glyphname = exclam;
lastChange = "2022-12-01 05:10:49 +0000";
lastChange = "2023-06-05 23:22:51 +0000";
layers = (
{
layerId = m01;
Expand Down Expand Up @@ -139,7 +139,7 @@ unicode = 0021;
},
{
glyphname = hyphen;
lastChange = "2022-12-01 04:57:39 +0000";
lastChange = "2023-06-05 23:23:03 +0000";
layers = (
{
layerId = m01;
Expand Down Expand Up @@ -208,6 +208,25 @@ width = 600;
unicode = 003D;
}
);
kerning = {
m01 = {
exclam = {
exclam = -360;
hyphen = 20;
};
hyphen = {
hyphen = -150;
};
};
"E09E0C54-128D-4FEA-B209-1B70BEFE300B" = {
exclam = {
exclam = -100;
};
hyphen = {
hyphen = -50;
};
};
};
unitsPerEm = 1000;
versionMajor = 42;
versionMinor = 42;
Expand Down
25 changes: 22 additions & 3 deletions resources/testdata/glyphs3/WghtVar.glyphs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
.formatVersion = 3;
DisplayStrings = (
"-",
"!"
"!!--"
);
axes = (
{
Expand Down Expand Up @@ -93,7 +93,7 @@ unicode = 32;
},
{
glyphname = exclam;
lastChange = "2022-12-01 05:10:49 +0000";
lastChange = "2023-06-05 23:22:51 +0000";
layers = (
{
layerId = m01;
Expand Down Expand Up @@ -148,7 +148,7 @@ unicode = 33;
},
{
glyphname = hyphen;
lastChange = "2022-12-01 04:57:39 +0000";
lastChange = "2023-06-05 23:23:03 +0000";
layers = (
{
layerId = m01;
Expand Down Expand Up @@ -218,6 +218,25 @@ width = 600;
unicode = 61;
}
);
kerningLTR = {
m01 = {
exclam = {
exclam = -360;
hyphen = 20;
};
hyphen = {
hyphen = -150;
};
};
"E09E0C54-128D-4FEA-B209-1B70BEFE300B" = {
exclam = {
exclam = -100;
};
hyphen = {
hyphen = -50;
};
};
};
metrics = (
{
type = ascender;
Expand Down