mod closure;
mod error;
mod fix;
pub mod license;
mod names;
mod overlaps;
pub mod stat;
mod utils;
pub use fix::{FixFvarTable, IncludeSourceFixes, Interactive, fix_font};
use google_fonts_languages::LANGUAGES;
pub use overlaps::remove_overlaps;
use skrifa::raw::TableProvider;
use skrifa::string::StringId;
use skrifa::{FontRef, MetadataProvider};
use std::path::Path;
use unicode_script::UnicodeScript as _;
pub use error::GftoolsError;
pub use names::{AxisLimits, AxisTriple, update_name_table};
pub use stat::{
AxisLocation, AxisRecord, AxisValue, StatBuilder, StatConfig, VarFont, gen_stat_tables,
gen_stat_tables_from_config,
};
pub use utils::{
PROD_FAMILY_DOWNLOAD, download_family_from_google_fonts, font_is_italic, is_google_fonts_repo,
strip_json_guard,
};
pub use gf_metadata::{AxisProto, DesignerInfoProto, FamilyProto};
pub fn parse_pb<T>(path: &Path) -> Result<T, GftoolsError>
where
T: protobuf::MessageFull,
{
let contents = std::fs::read(path)?;
let utf8_contents = std::str::from_utf8(&contents)
.map_err(|_| GftoolsError::Misc("METADATA.pb is not valid UTF-8".to_string()))?;
let data = protobuf::text_format::parse_from_str::<T>(utf8_contents)
.map_err(GftoolsError::ProtobufParse)?;
Ok(data)
}
pub fn write_family_metadata(data: &FamilyProto, comments: bool) -> Result<String, GftoolsError> {
let s = protobuf::text_format::print_to_string_pretty(data);
if !comments {
return Ok(s);
}
let mut rewritten = String::new();
for line in s.lines() {
rewritten += line;
if line.starts_with("languages: ")
&& let Some(lang) = line.split('"').nth(1)
&& let Some(lang) = LANGUAGES.get(lang)
{
rewritten += &format!(" # {}", lang.name());
}
rewritten.push('\n');
}
Ok(rewritten)
}
pub fn font_version(f: &FontRef) -> String {
if let Some(version) = f
.localized_strings(StringId::VERSION_STRING)
.english_or_first()
.map(|name| name.chars().collect())
{
version
} else {
f.head()
.map(|head| head.font_revision().to_string())
.unwrap_or_else(|_| "0.0".to_string())
}
}
pub fn primary_script(fontref: &FontRef, ignore_latin: bool) -> Option<String> {
let classification = closure::classify_glyphs(
|cp| {
let Some(c) = char::from_u32(cp) else {
return vec![];
};
let mut scripts = vec![c.script().short_name().to_string()];
scripts.extend(
c.script_extension()
.iter()
.map(|s| s.short_name().to_string()),
);
scripts
},
&fontref.charmap(),
fontref.gsub().ok().as_ref(),
)
.ok()?;
let mut badkeys = vec!["Zinh", "Zyyy", "Zzzz"];
if ignore_latin {
badkeys.push("Latn");
}
let mut script_counts = classification
.iter()
.filter(|(script, _)| !badkeys.contains(&script.as_str()))
.map(|(script, glyphs)| (script.clone(), glyphs.len()))
.collect::<Vec<(String, usize)>>();
script_counts.sort_by_key(|b| std::cmp::Reverse(b.1));
if script_counts.len() > 2 && script_counts[0].1 < 2 * script_counts[1].1 {
return None;
}
script_counts.first().map(|(script, _)| script.clone())
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_roundtrip_proto() {
let pushster_string =
std::fs::read_to_string("resources/test/pushster/METADATA.pb").unwrap();
let pushster =
parse_pb::<FamilyProto>(Path::new("resources/test/pushster/METADATA.pb")).unwrap();
let serialized = write_family_metadata(&pushster, true).unwrap();
assert_eq!(pushster_string, serialized);
}
}