diff --git a/glyphs-reader/src/font.rs b/glyphs-reader/src/font.rs index a8e58c0b..52340d7f 100644 --- a/glyphs-reader/src/font.rs +++ b/glyphs-reader/src/font.rs @@ -621,6 +621,7 @@ struct RawFeature { name: Option, tag: Option, code: String, + labels: Vec, #[fromplist(ignore)] other_stuff: BTreeMap, @@ -1840,6 +1841,28 @@ fn get_glyph_category(name: &str, codepoints: &BTreeSet) -> Option<(Categor .map(|info| (info.category, info.subcategory)) } +// https://github.com/googlefonts/glyphsLib/blob/24b4d340e4c82948ba121dcfe563c1450a8e69c9/Lib/glyphsLib/builder/constants.py#L186 +#[rustfmt::skip] +static GLYPHS_TO_OPENTYPE_LANGUAGE_ID: &[(&str, i32)] = &[ + ("dflt", 0x0409), ("AFK", 0x0436), ("ARA", 0x0C01), ("ASM", 0x044D), ("AZE", 0x042C), + ("BEL", 0x0423), ("BEN", 0x0845), ("BGR", 0x0402), ("BRE", 0x047E), ("CAT", 0x0403), + ("CSY", 0x0405), ("DAN", 0x0406), ("DEU", 0x0407), ("ELL", 0x0408), ("ENG", 0x0409), + ("ESP", 0x0C0A), ("ETI", 0x0425), ("EUQ", 0x042D), ("FIN", 0x040B), ("FLE", 0x0813), + ("FOS", 0x0438), ("FRA", 0x040C), ("FRI", 0x0462), ("GRN", 0x046F), ("GUJ", 0x0447), + ("HAU", 0x0468), ("HIN", 0x0439), ("HRV", 0x041A), ("HUN", 0x040E), ("HVE", 0x042B), + ("IRI", 0x083C), ("ISL", 0x040F), ("ITA", 0x0410), ("IWR", 0x040D), ("JPN", 0x0411), + ("KAN", 0x044B), ("KAT", 0x0437), ("KAZ", 0x043F), ("KHM", 0x0453), ("KOK", 0x0457), + ("LAO", 0x0454), ("LSB", 0x082E), ("LTH", 0x0427), ("LVI", 0x0426), ("MAR", 0x044E), + ("MKD", 0x042F), ("MLR", 0x044C), ("MLY", 0x043E), ("MNG", 0x0352), ("MTS", 0x043A), + ("NEP", 0x0461), ("NLD", 0x0413), ("NOB", 0x0414), ("ORI", 0x0448), ("PAN", 0x0446), + ("PAS", 0x0463), ("PLK", 0x0415), ("PTG", 0x0816), ("PTG-BR", 0x0416), ("RMS", 0x0417), + ("ROM", 0x0418), ("RUS", 0x0419), ("SAN", 0x044F), ("SKY", 0x041B), ("SLV", 0x0424), + ("SQI", 0x041C), ("SRB", 0x081A), ("SVE", 0x041D), ("TAM", 0x0449), ("TAT", 0x0444), + ("TEL", 0x044A), ("THA", 0x041E), ("TIB", 0x0451), ("TRK", 0x041F), ("UKR", 0x0422), + ("URD", 0x0420), ("USB", 0x042E), ("UYG", 0x0480), ("UZB", 0x0443), ("VIT", 0x042A), + ("WEL", 0x0452), ("ZHH", 0x0C04), ("ZHS", 0x0804), ("ZHT", 0x0404), +]; + impl RawFeature { // https://github.com/googlefonts/glyphsLib/blob/24b4d340e4c82948ba121dcfe563c1450a8e69c9/Lib/glyphsLib/builder/features.py#L43 fn autostr(&self) -> String { @@ -1862,6 +1885,31 @@ impl RawFeature { fn disabled(&self) -> bool { self.disabled == Some(1) } + + // https://github.com/googlefonts/glyphsLib/blob/24b4d340e4c82948ba121dcfe563c1450a8e69c9/Lib/glyphsLib/builder/features.py#L134 + fn feature_names(&self) -> String { + if self.labels.is_empty() { + String::new() + } else { + let labels = self + .labels + .iter() + .map(|label| { + let language_id = GLYPHS_TO_OPENTYPE_LANGUAGE_ID + .iter() + .find(|entry| entry.0 == label.language) + .map(|entry| entry.1) + .unwrap_or_else(|| { + panic!("Unknown feature label language: {}", label.language); + }); + let name = label.value.replace("\\", "\\005c").replace("\"", "\\0022"); + format!(" name 3 1 0x{:04X} \"{}\";", language_id, name) + }) + .collect::>() + .join("\n"); + format!("featureNames {{\n{}\n}};\n", labels) + } + } } // https://github.com/googlefonts/glyphsLib/blob/24b4d340e4c82948ba121dcfe563c1450a8e69c9/Lib/glyphsLib/builder/features.py#L90 @@ -1891,9 +1939,10 @@ fn class_to_feature(feature: RawFeature) -> Result { fn raw_feature_to_feature(feature: RawFeature) -> Result { let name = feature.name()?; let code = format!( - "feature {0} {{\n{1}{2}\n}} {0};", + "feature {0} {{\n{1}{2}{3}\n}} {0};", name, feature.autostr(), + feature.feature_names(), feature.code ); Ok(FeatureSnippet::new(code, feature.disabled())) @@ -2091,6 +2140,7 @@ impl TryFrom for Font { let mut names = BTreeMap::new(); for name in from.properties { // TODO: we only support dflt, .glyphs l10n names are ignored + // TODO: use GLYPHS_TO_OPENTYPE_LANGUAGE_ID to support localized names name.value .or_else(|| { name.values @@ -2918,6 +2968,38 @@ mod tests { ) } + #[test] + fn fea_labels() { + let font = Font::load(&glyphs3_dir().join("Fea_Labels.glyphs")).unwrap(); + assert_eq!( + vec![ + concat!( + "feature ss01 {\n", + "# automatic\n", + "featureNames {\n", + " name 3 1 0x0409 \"Test 1\";\n", + " name 3 1 0x0C01 \"اختبار ١\";\n", + "};\n", + "sub a by a.ss01;\n", + "sub b by b.ss01;\n\n", + "} ss01;", + ), + concat!( + "feature ss02 {\n", + "featureNames {\n", + " name 3 1 0x0409 \"Test 2\";\n", + "};\n", + "sub c by c.alt;\n", + "} ss02;", + ), + ], + font.features + .iter() + .filter_map(|f| f.str_if_enabled()) + .collect::>() + ) + } + #[test] fn tags_make_excellent_names() { let raw = RawFeature { @@ -2926,6 +3008,7 @@ mod tests { automatic: None, disabled: None, code: "blah".to_string(), + labels: vec![], other_stuff: BTreeMap::new(), }; assert_eq!("aalt", raw.name().unwrap()); diff --git a/glyphs2fontir/src/toir.rs b/glyphs2fontir/src/toir.rs index acc2c715..67f19989 100644 --- a/glyphs2fontir/src/toir.rs +++ b/glyphs2fontir/src/toir.rs @@ -121,7 +121,7 @@ fn to_ir_path(glyph_name: GlyphName, src_path: &Path) -> Result Result { // Based on https://github.com/googlefonts/glyphsLib/blob/24b4d340e4c82948ba121dcfe563c1450a8e69c9/Lib/glyphsLib/builder/features.py#L74 // TODO: token expansion - // TODO: implement notes and labels + // TODO: implement notes let fea_snippets: Vec<_> = features.iter().filter_map(|f| f.str_if_enabled()).collect(); Ok(ir::FeaturesSource::Memory { fea_content: fea_snippets.join("\n\n"), diff --git a/resources/testdata/glyphs3/Fea_Labels.glyphs b/resources/testdata/glyphs3/Fea_Labels.glyphs new file mode 100644 index 00000000..a35727e8 --- /dev/null +++ b/resources/testdata/glyphs3/Fea_Labels.glyphs @@ -0,0 +1,175 @@ +{ +.appVersion = "3317"; +.formatVersion = 3; +DisplayStrings = ( +" " +); +date = "2024-09-11 20:25:35 +0000"; +familyName = "GlobalMetrics Font Test"; +features = ( +{ +automatic = 1; +code = "sub a by a.ss01; +sub b by b.ss01; +"; +labels = ( +{ +language = dflt; +value = "Test 1"; +}, +{ +language = ARA; +value = "اختبار ١"; +} +); +tag = ss01; +}, +{ +code = "sub c by c.alt;"; +labels = ( +{ +language = ENG; +value = "Test 2"; +} +); +tag = ss02; +} +); +fontMaster = ( +{ +iconName = Light; +id = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +metricValues = ( +{ +pos = 800; +}, +{ +pos = 660; +}, +{ +pos = 478; +}, +{ +}, +{ +pos = -200; +}, +{ +} +); +name = Regular; +} +); +glyphs = ( +{ +glyphname = a; +layers = ( +{ +layerId = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +width = 600; +} +); +unicode = 97; +}, +{ +glyphname = b; +layers = ( +{ +layerId = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +width = 600; +} +); +unicode = 98; +}, +{ +glyphname = c; +layers = ( +{ +layerId = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +width = 600; +} +); +unicode = 99; +}, +{ +glyphname = c.alt; +layers = ( +{ +layerId = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +width = 600; +} +); +}, +{ +glyphname = a.ss01; +layers = ( +{ +layerId = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +width = 600; +} +); +}, +{ +glyphname = b.ss01; +layers = ( +{ +layerId = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +width = 600; +} +); +}, +{ +glyphname = space; +lastChange = "2024-09-11 21:06:25 +0000"; +layers = ( +{ +layerId = "0CAB57D4-4FC4-49C3-B456-FB7666EAA712"; +width = 200; +} +); +unicode = 32; +} +); +instances = ( +{ +customParameters = ( +{ +name = "Axis Location"; +value = ( +{ +Axis = Weight; +Location = 400; +} +); +} +); +instanceInterpolations = { +"0CAB57D4-4FC4-49C3-B456-FB7666EAA712" = 1; +}; +name = Regular; +} +); +metrics = ( +{ +type = ascender; +}, +{ +type = "cap height"; +}, +{ +type = "x-height"; +}, +{ +type = baseline; +}, +{ +type = descender; +}, +{ +type = "italic angle"; +} +); +unitsPerEm = 1000; +versionMajor = 1; +versionMinor = 0; +}