diff --git a/examples/print-postscript-name.rs b/examples/print-postscript-name.rs new file mode 100644 index 0000000..c237f41 --- /dev/null +++ b/examples/print-postscript-name.rs @@ -0,0 +1,26 @@ +#[cfg(not(feature = "fs"))] +fn main() {} + +#[cfg(feature = "fs")] +fn main() { + let path = std::env::args().nth(1).expect("usage: print-postscript-name "); + + let mut db = fontdb::Database::new(); + let ids = db.load_font_file(&path).map(|_| ()).unwrap_or_else(|e| { + panic!("failed to load {}: {:?}", path, e); + }); + let _ = ids; + + for face in db.faces() { + let families: Vec<_> = face.families.iter().map(|(n, _)| n.as_str()).collect(); + println!( + "index={} post_script_name={:?} families={:?} style={:?} weight={} stretch={:?}", + face.index, + face.post_script_name, + families, + face.style, + face.weight.0, + face.stretch, + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 6a788de..1c9ae4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1088,11 +1088,70 @@ fn parse_names(raw_face: &ttf_parser::RawFace) -> Option<(Vec<(String, Language) .find(|name| { name.name_id == ttf_parser::name_id::POST_SCRIPT_NAME && name.is_supported_encoding() }) - .and_then(|name| name_to_unicode(&name))?; + .and_then(|name| name_to_unicode(&name)) + .or_else(|| { + let style = find_english_name(&name_table.names, ttf_parser::name_id::TYPOGRAPHIC_SUBFAMILY) + .or_else(|| find_english_name(&name_table.names, ttf_parser::name_id::SUBFAMILY)) + .unwrap_or_default(); + synthesize_postscript_name(&families[0].0, &style) + })?; Some((families, post_script_name)) } +fn find_english_name(names: &ttf_parser::name::Names, name_id: u16) -> Option { + let mut fallback: Option = None; + for name in names.into_iter() { + if name.name_id != name_id || !name.is_supported_encoding() { + continue; + } + if name.language() == Language::English_UnitedStates { + return name_to_unicode(&name); + } + fallback.get_or_insert(name); + } + fallback.and_then(|n| name_to_unicode(&n)) +} + +fn synthesize_postscript_name(family: &str, style: &str) -> Option { + const FORBIDDEN: &[char] = &['(', ')', '<', '>', '[', ']', '{', '}', '/', '%']; + const MAX: usize = 63; + const MIN_FAMILY: usize = 16; + const MAX_STYLE: usize = MAX - 1 - MIN_FAMILY; // 46 + + fn sanitize(s: &str) -> String { + s.chars() + .filter(|c| matches!(*c as u32, 0x21..=0x7E) && !FORBIDDEN.contains(c)) + .collect() + } + + let mut family = sanitize(family); + if family.is_empty() { + return None; + } + + let style = if style.is_empty() || style.eq_ignore_ascii_case("regular") { + String::new() + } else { + sanitize(style) + }; + + if style.is_empty() { + family.truncate(MAX); + return Some(family); + } + + if style.len() > MAX_STYLE { + return None; + } + + // Reserve room for "-Style", truncating the family if needed. + family.truncate(MAX - 1 - style.len()); + family.push('-'); + family.push_str(&style); + Some(family) +} + fn collect_families(name_id: u16, names: &ttf_parser::name::Names) -> Vec<(String, Language)> { let mut families = Vec::new(); for name in names.into_iter() {