fc-fontations/attributes.rs | 331 ++++++++++++++++++++++++++++------ fc-fontations/bitmap.rs | 8 fc-fontations/capabilities.rs | 10 - fc-fontations/foundries.rs | 17 + fc-fontations/mod.rs | 32 --- fc-fontations/name_records.rs | 4 fc-fontations/names.rs | 68 ++++-- fc-fontations/pattern_bindings/mod.rs | 14 + 8 files changed, 364 insertions(+), 120 deletions(-) New commits: commit 41f2ae7df48d192b938ab78217bece1fab09dd1b Merge: bccd175 031eeae Author: Akira TAGOH <akira@xxxxxxxxx> Date: Fri Jun 13 17:04:44 2025 +0000 Merge branch 'landUbuntuMatch' into 'main' [Fontations] Assorted fixes to match FreeType indexing See merge request fontconfig/fontconfig!428 commit 031eeae68e3e631ab526651c3d5019819e476d8c Author: Dominik Röttsches <drott@xxxxxxxxxxxx> Date: Tue Jun 10 14:17:12 2025 +0300 [Fontations] Assorted fixes to match FreeType indexing With these changes, only 48 fonts out of 4801 fonts that include all extracted fonts from Ubuntu fonts-.* packages have minor differences. The remaining differences are mostly explained by #480 and a set of several fonts (~3) which have a broken OS/2 table for which Skrifa fails to retrieve foundry information. * Fix language order, trim string ends. * Attributes from style string * Foundry retrieval fixes * Lowercase language * Clippy fixes * No bitmap strikes for EBDT * PostScript name assembly from subfamily, using family or prefix * PostScript name sanitizing, * OS/2 min/max size * Correct sequence joining when graphite support is present diff --git a/fc-fontations/attributes.rs b/fc-fontations/attributes.rs index 092ccfe..5fd6d7a 100644 --- a/fc-fontations/attributes.rs +++ b/fc-fontations/attributes.rs @@ -24,19 +24,24 @@ use fc_fontations_bindgen::{ fcint::{ - FC_INDEX_OBJECT, FC_NAMED_INSTANCE_OBJECT, FC_SIZE_OBJECT, FC_SLANT_OBJECT, - FC_SPACING_OBJECT, FC_VARIABLE_OBJECT, FC_WEIGHT_OBJECT, FC_WIDTH_OBJECT, + FC_DECORATIVE_OBJECT, FC_INDEX_OBJECT, FC_NAMED_INSTANCE_OBJECT, FC_SIZE_OBJECT, + FC_SLANT_OBJECT, FC_SPACING_OBJECT, FC_STYLE_OBJECT, FC_VARIABLE_OBJECT, FC_WEIGHT_OBJECT, + FC_WIDTH_OBJECT, }, FcWeightFromOpenTypeDouble, FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_OBLIQUE, - FC_SLANT_ROMAN, FC_WEIGHT_BLACK, FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABOLD, FC_WEIGHT_EXTRALIGHT, - FC_WEIGHT_LIGHT, FC_WEIGHT_MEDIUM, FC_WEIGHT_NORMAL, FC_WEIGHT_SEMIBOLD, FC_WEIGHT_THIN, - FC_WIDTH_CONDENSED, FC_WIDTH_EXPANDED, FC_WIDTH_EXTRACONDENSED, FC_WIDTH_EXTRAEXPANDED, - FC_WIDTH_NORMAL, FC_WIDTH_SEMICONDENSED, FC_WIDTH_SEMIEXPANDED, FC_WIDTH_ULTRACONDENSED, - FC_WIDTH_ULTRAEXPANDED, + FC_SLANT_ROMAN, FC_WEIGHT_BLACK, FC_WEIGHT_BOLD, FC_WEIGHT_BOOK, FC_WEIGHT_DEMIBOLD, + FC_WEIGHT_DEMILIGHT, FC_WEIGHT_EXTRABLACK, FC_WEIGHT_EXTRABOLD, FC_WEIGHT_EXTRALIGHT, + FC_WEIGHT_HEAVY, FC_WEIGHT_LIGHT, FC_WEIGHT_MEDIUM, FC_WEIGHT_NORMAL, FC_WEIGHT_REGULAR, + FC_WEIGHT_SEMIBOLD, FC_WEIGHT_SEMILIGHT, FC_WEIGHT_THIN, FC_WEIGHT_ULTRABLACK, + FC_WEIGHT_ULTRABOLD, FC_WEIGHT_ULTRALIGHT, FC_WIDTH_CONDENSED, FC_WIDTH_EXPANDED, + FC_WIDTH_EXTRACONDENSED, FC_WIDTH_EXTRAEXPANDED, FC_WIDTH_NORMAL, FC_WIDTH_SEMICONDENSED, + FC_WIDTH_SEMIEXPANDED, FC_WIDTH_ULTRACONDENSED, FC_WIDTH_ULTRAEXPANDED, }; use crate::{ - pattern_bindings::{fc_wrapper::FcRangeWrapper, FcPatternBuilder, PatternElement}, + pattern_bindings::{ + fc_wrapper::FcRangeWrapper, FcPatternBuilder, PatternElement, PatternValue, + }, InstanceMode, }; use read_fonts::TableProvider; @@ -47,6 +52,7 @@ use skrifa::{ prelude::{LocationRef, Size}, AxisCollection, FontRef, MetadataProvider, NamedInstance, Tag, }; +use std::ffi::CString; fn fc_weight(skrifa_weight: Weight) -> f64 { (match skrifa_weight { @@ -118,6 +124,15 @@ fn fc_width_from_os2(font_ref: &FontRef) -> Option<f64> { Some(converted as f64) } +fn fc_size_from_os2(font_ref: &FontRef) -> Option<(f64, f64)> { + font_ref.os2().ok().and_then(|os2| { + Some(( + os2.us_lower_optical_point_size()? as f64 / 20.0, + os2.us_upper_optical_point_size()? as f64 / 20.0, + )) + }) +} + struct AttributesToPattern<'a> { weight_from_os2: Option<f64>, width_from_os2: Option<f64>, @@ -153,30 +168,30 @@ impl<'a> AttributesToPattern<'a> { Some(axis_coords.find(|item| item.0 == tag)?.1 as f64) } - fn static_weight(&self) -> PatternElement { - self.weight_from_os2.map_or_else( - || { - PatternElement::new( - FC_WEIGHT_OBJECT as i32, - fc_weight(self.attributes.weight).into(), - ) - }, - |os2_weight| PatternElement::new(FC_WEIGHT_OBJECT as i32, os2_weight.into()), + fn flags_weight(&self) -> PatternElement { + PatternElement::new( + FC_WEIGHT_OBJECT as i32, + fc_weight(self.attributes.weight).into(), ) } - fn static_width(&self) -> PatternElement { - self.width_from_os2.map_or_else( - || { - PatternElement::new( - FC_WIDTH_OBJECT as i32, - fc_width(self.attributes.stretch).into(), - ) - }, - |os2_width| PatternElement::new(FC_WIDTH_OBJECT as i32, os2_width.into()), + fn os2_weight(&self) -> Option<PatternElement> { + self.weight_from_os2 + .map(|weight| PatternElement::new(FC_WEIGHT_OBJECT as i32, weight.into())) + } + + fn flags_width(&self) -> PatternElement { + PatternElement::new( + FC_WIDTH_OBJECT as i32, + fc_width(self.attributes.stretch).into(), ) } + fn os2_width(&self) -> Option<PatternElement> { + self.width_from_os2 + .map(|width| PatternElement::new(FC_WIDTH_OBJECT as i32, width.into())) + } + fn static_slant(&self) -> PatternElement { PatternElement::new( FC_SLANT_OBJECT as i32, @@ -227,7 +242,7 @@ impl<'a> AttributesToPattern<'a> { )) } - fn default_size(&self) -> Option<PatternElement> { + fn default_axis_size(&self) -> Option<PatternElement> { self.axes.get_by_tag(Tag::new(b"opsz")).map(|opsz_axis| { PatternElement::new( FC_SIZE_OBJECT as i32, @@ -236,6 +251,17 @@ impl<'a> AttributesToPattern<'a> { }) } + fn os2_size(&self) -> Option<PatternElement> { + fc_size_from_os2(&self.font_ref).and_then(|(lower, higher)| { + if lower != higher { + let range = FcRangeWrapper::new(lower, higher)?; + Some(PatternElement::new(FC_SIZE_OBJECT as i32, range.into())) + } else { + Some(PatternElement::new(FC_SIZE_OBJECT as i32, lower.into())) + } + }) + } + fn variable_weight(&self) -> Option<PatternElement> { let weight_axis = self.axes.get_by_tag(Tag::new(b"wght"))?; unsafe { @@ -334,18 +360,164 @@ impl<'a> AttributesToPattern<'a> { .font_ref .charmap() .mappings() - .map(|(_codepoint, gid)| { + .filter_map(|(_codepoint, gid)| { glyph_metrics .advance_width(gid) .and_then(|adv| if adv > 0.0 { Some(adv) } else { None }) - }) - .flatten(); + }); Self::spacing_from_advances(advances) .map(|spacing| PatternElement::new(FC_SPACING_OBJECT as i32, spacing.into())) } } +#[derive(Default)] +struct AttributesFromStyleString { + weight: Option<PatternElement>, + width: Option<PatternElement>, + slant: Option<PatternElement>, + decorative: Option<PatternElement>, +} + +fn contains_weight(style_name: &CString) -> Option<PatternElement> { + const WEIGHT_MAP: [(&str, f64); 23] = [ + ("thin", FC_WEIGHT_THIN as f64), + ("extralight", FC_WEIGHT_EXTRALIGHT as f64), + ("ultralight", FC_WEIGHT_ULTRALIGHT as f64), + ("demilight", FC_WEIGHT_DEMILIGHT as f64), + ("semilight", FC_WEIGHT_SEMILIGHT as f64), + ("light", FC_WEIGHT_LIGHT as f64), + ("book", FC_WEIGHT_BOOK as f64), + ("regular", FC_WEIGHT_REGULAR as f64), + ("normal", FC_WEIGHT_NORMAL as f64), + ("medium", FC_WEIGHT_MEDIUM as f64), + ("demibold", FC_WEIGHT_DEMIBOLD as f64), + ("demi", FC_WEIGHT_DEMIBOLD as f64), + ("semibold", FC_WEIGHT_SEMIBOLD as f64), + ("extrabold", FC_WEIGHT_EXTRABOLD as f64), + ("superbold", FC_WEIGHT_EXTRABOLD as f64), + ("ultrabold", FC_WEIGHT_ULTRABOLD as f64), + ("bold", FC_WEIGHT_BOLD as f64), + ("ultrablack", FC_WEIGHT_ULTRABLACK as f64), + ("superblack", FC_WEIGHT_EXTRABLACK as f64), + ("extrablack", FC_WEIGHT_EXTRABLACK as f64), + ("ultra", FC_WEIGHT_ULTRABOLD as f64), + ("black", FC_WEIGHT_BLACK as f64), + ("heavy", FC_WEIGHT_HEAVY as f64), + ]; + + for weight_mapping in WEIGHT_MAP { + if style_name + .to_string_lossy() + .to_lowercase() + .contains(weight_mapping.0) + { + return Some(PatternElement::new( + FC_WEIGHT_OBJECT as i32, + weight_mapping.1.into(), + )); + } + } + None +} + +fn contains_slant(style_name: &CString) -> Option<PatternElement> { + const SLANT_MAP: [(&str, i32); 3] = [ + ("italic", FC_SLANT_ITALIC as i32), + ("kursiv", FC_SLANT_ITALIC as i32), + ("oblique", FC_SLANT_OBLIQUE as i32), + ]; + + for mapping in SLANT_MAP { + if style_name + .to_string_lossy() + .to_lowercase() + .contains(mapping.0) + { + return Some(PatternElement::new( + FC_SLANT_OBJECT as i32, + mapping.1.into(), + )); + } + } + None +} + +fn contains_width(style_name: &CString) -> Option<PatternElement> { + const WIDTH_MAP: [(&str, f64); 10] = [ + ("ultracondensed", FC_WIDTH_ULTRACONDENSED as f64), + ("extracondensed", FC_WIDTH_EXTRACONDENSED as f64), + ("semicondensed", FC_WIDTH_SEMICONDENSED as f64), + ("condensed", FC_WIDTH_CONDENSED as f64), + ("normal", FC_WIDTH_NORMAL as f64), + ("semiexpanded", FC_WIDTH_SEMIEXPANDED as f64), + ("extraexpanded", FC_WIDTH_EXTRAEXPANDED as f64), + ("ultraexpanded", FC_WIDTH_ULTRAEXPANDED as f64), + ("expanded", FC_WIDTH_EXPANDED as f64), + ("extended", FC_WIDTH_EXPANDED as f64), + ]; + for mapping in WIDTH_MAP { + if style_name + .to_string_lossy() + .to_lowercase() + .contains(mapping.0) + { + return Some(PatternElement::new( + FC_WIDTH_OBJECT as i32, + mapping.1.into(), + )); + } + } + None +} + +fn contains_decorative(style_name: &CString) -> Option<PatternElement> { + let had_decorative = style_name + .to_string_lossy() + .to_lowercase() + .contains("decorative"); + + Some(PatternElement::new( + FC_DECORATIVE_OBJECT as i32, + had_decorative.into(), + )) +} + +impl AttributesFromStyleString { + fn new(pattern: &FcPatternBuilder) -> Self { + let style_string = pattern + .into_iter() + .find(|element| element.object_id == FC_STYLE_OBJECT as i32) + .and_then(|element| match &element.value { + PatternValue::String(style) => Some(style), + _ => None, + }); + + if let Some(style) = style_string { + Self { + weight: contains_weight(style), + width: contains_width(style), + slant: contains_slant(style), + decorative: contains_decorative(style), + } + } else { + Self { + weight: None, + width: None, + slant: None, + decorative: Some(PatternElement::new( + FC_DECORATIVE_OBJECT as i32, + false.into(), + )), + } + } + } +} + +/// Appends style pattern elements such as weight, width, slant, decorative to the pattern. +/// Requires a textual style element to be already added to the pattern, so it's good +/// to run this after names have been added. This is because this method performs certain +/// string matches on the font name to determine style attributes. pub fn append_style_elements( font: &FontRef, instance_mode: InstanceMode, @@ -356,17 +528,37 @@ pub fn append_style_elements( // but falls back to flags if those are not found. So far, I haven't identified test fonts // for which the WWS code path would trigger. - let attributes_converter = AttributesToPattern::new(font, &instance_mode); + let attributes_text = AttributesFromStyleString::new(pattern); - if let Some(spacing) = attributes_converter.spacing() { + let skrifa_attributes = AttributesToPattern::new(font, &instance_mode); + + if let Some(spacing) = skrifa_attributes.spacing() { pattern.append_element(spacing); } match instance_mode { InstanceMode::Default => { - pattern.append_element(attributes_converter.static_weight()); - pattern.append_element(attributes_converter.static_width()); - pattern.append_element(attributes_converter.static_slant()); + let pattern_weight = skrifa_attributes + .os2_weight() + .or(attributes_text.weight) + .unwrap_or(skrifa_attributes.flags_weight()); + pattern.append_element(pattern_weight); + + let width = skrifa_attributes + .os2_width() + .or(attributes_text.width) + .unwrap_or(skrifa_attributes.flags_width()); + pattern.append_element(width); + + pattern.append_element( + attributes_text + .slant + .unwrap_or(skrifa_attributes.static_slant()), + ); + + if let Some(element) = attributes_text.decorative { + pattern.append_element(element) + } pattern.append_element(PatternElement::new(FC_VARIABLE_OBJECT as i32, false.into())); pattern.append_element(PatternElement::new( @@ -374,7 +566,10 @@ pub fn append_style_elements( ttc_index.unwrap_or_default().into(), )); - if let Some(size) = attributes_converter.default_size() { + if let Some(size) = skrifa_attributes + .default_axis_size() + .or(skrifa_attributes.os2_size()) + { pattern.append_element(size); } @@ -384,21 +579,28 @@ pub fn append_style_elements( )); } InstanceMode::Variable => { - if let Some(weight_to_add) = attributes_converter + let weight = skrifa_attributes .variable_weight() - .or(Some(attributes_converter.static_weight())) - { - pattern.append_element(weight_to_add); - } - if let Some(width_to_add) = attributes_converter + .or(skrifa_attributes.os2_weight()) + .or(attributes_text.weight) + .unwrap_or(skrifa_attributes.flags_weight()); + pattern.append_element(weight); + + let width = skrifa_attributes .variable_width() - .or(Some(attributes_converter.static_width())) - { - pattern.append_element(width_to_add); + .or(skrifa_attributes.os2_width()) + .or(attributes_text.width) + .unwrap_or(skrifa_attributes.flags_width()); + pattern.append_element(width); + + if let Some(element) = attributes_text.decorative { + pattern.append_element(element) } - if let Some(size) = attributes_converter.variable_opsz() { + + if let Some(size) = skrifa_attributes.variable_opsz() { pattern.append_element(size); } + pattern.append_element(PatternElement::new(FC_VARIABLE_OBJECT as i32, true.into())); // TODO: Check if this should have a zero ttc index if not part of a collection. @@ -410,29 +612,44 @@ pub fn append_style_elements( FC_NAMED_INSTANCE_OBJECT as i32, false.into(), )); - pattern.append_element(attributes_converter.static_slant()); + pattern.append_element(skrifa_attributes.static_slant()); } InstanceMode::Named(index) => { - if let Some(weight) = attributes_converter.instance_weight() { - pattern.append_element(weight); - } - if let Some(width) = attributes_converter.instance_width() { - pattern.append_element(width); + let weight = skrifa_attributes + .instance_weight() + .or(attributes_text.weight) + .unwrap_or(skrifa_attributes.flags_weight()); + pattern.append_element(weight); + + let width = skrifa_attributes + .instance_width() + .or(attributes_text.width) + .unwrap_or(skrifa_attributes.flags_width()); + pattern.append_element(width); + + pattern.append_element( + skrifa_attributes + .instance_slant() + .or(attributes_text.slant) + .unwrap_or(skrifa_attributes.static_slant()), + ); + + if let Some(element) = attributes_text.decorative { + pattern.append_element(element) } + pattern.append_element(PatternElement::new(FC_VARIABLE_OBJECT as i32, false.into())); pattern.append_element(PatternElement::new( FC_INDEX_OBJECT as i32, (ttc_index.unwrap_or_default() + ((index + 1) << 16)).into(), )); - if let Some(size_element) = attributes_converter + if let Some(size_element) = skrifa_attributes .instance_size() - .or(attributes_converter.default_size()) + .or(skrifa_attributes.default_axis_size()) { pattern.append_element(size_element); }; - if let Some(slant_element) = attributes_converter.instance_slant() { - pattern.append_element(slant_element); - } + pattern.append_element(PatternElement::new( FC_NAMED_INSTANCE_OBJECT as i32, true.into(), diff --git a/fc-fontations/bitmap.rs b/fc-fontations/bitmap.rs index 149c07f..14cc03c 100644 --- a/fc-fontations/bitmap.rs +++ b/fc-fontations/bitmap.rs @@ -22,7 +22,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -use skrifa::FontRef; +use skrifa::{bitmap::BitmapFormat, FontRef}; use skrifa::bitmap::BitmapStrikes; @@ -32,7 +32,11 @@ use fc_fontations_bindgen::fcint::{FC_ANTIALIAS_OBJECT, FC_PIXEL_SIZE_OBJECT}; pub fn add_pixel_size(pattern: &mut FcPatternBuilder, font: &FontRef) { let strikes = BitmapStrikes::new(font); - let has_strikes = strikes.len() > 0; + if let Some(BitmapFormat::Ebdt) = strikes.format() { + return; + } + + let has_strikes = !strikes.is_empty(); for strike in strikes.iter() { pattern.append_element(PatternElement::new( diff --git a/fc-fontations/capabilities.rs b/fc-fontations/capabilities.rs index a19345d..232857b 100644 --- a/fc-fontations/capabilities.rs +++ b/fc-fontations/capabilities.rs @@ -29,20 +29,20 @@ use skrifa::FontRef; use std::ffi::CString; // Mimicking issue in FreeType indexer inserting two delimiting spaces. -const SILF_CAPABILITIES_PREFIX: &str = "ttable:Silf "; +const SILF_CAPABILITIES_PREFIX: &str = "ttable:Silf"; const SILF_TAG: Tag = Tag::new(b"Silf"); fn capabilities_string<T: IntoIterator<Item = Tag>>(tags: T, has_silf: bool) -> Option<CString> { let mut deduplicated_tags: Vec<Tag> = tags.into_iter().collect::<Vec<_>>(); deduplicated_tags.sort(); deduplicated_tags.dedup(); - let mut capabilities = deduplicated_tags + let mut capabilities_set = deduplicated_tags .into_iter() .map(|tag| format!("otlayout:{}", tag)) - .collect::<Vec<_>>() - .join(" "); + .collect::<Vec<_>>(); + has_silf.then(|| capabilities_set.insert(0, SILF_CAPABILITIES_PREFIX.to_string())); - has_silf.then(|| capabilities.insert_str(0, SILF_CAPABILITIES_PREFIX)); + let capabilities = capabilities_set.join(" "); if capabilities.is_empty() { return None; } diff --git a/fc-fontations/foundries.rs b/fc-fontations/foundries.rs index 536331a..ba10356 100644 --- a/fc-fontations/foundries.rs +++ b/fc-fontations/foundries.rs @@ -27,7 +27,7 @@ use skrifa::{ string::{LocalizedStrings, StringId}, FontRef, MetadataProvider, }; -use std::ffi::CString; +use std::ffi::{CStr, CString}; fn foundry_name_to_taglike(foundry: &str) -> Option<&'static str> { match foundry { @@ -60,7 +60,20 @@ fn map_foundry_from_name_entry(localized_strings: &mut LocalizedStrings) -> Opti pub fn make_foundry(font: &FontRef) -> Option<CString> { if let Ok(os2) = font.os2() { - return CString::new(os2.ach_vend_id().to_be_bytes()).ok(); + let vend_bytes = os2.ach_vend_id().to_be_bytes(); + let foundry = if vend_bytes.contains(&0) { + CStr::from_bytes_until_nul(&vend_bytes) + .ok() + .map(|cstr| cstr.to_owned()) + } else { + CString::new(vend_bytes).ok() + }; + + if let Some(foundry) = foundry { + if !foundry.is_empty() { + return Some(foundry); + } + } } map_foundry_from_name_entry(&mut font.localized_strings(StringId::TRADEMARK)).or_else(|| { diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs index 88d23e7..8ed2ce3 100644 --- a/fc-fontations/mod.rs +++ b/fc-fontations/mod.rs @@ -43,9 +43,9 @@ use names::add_names; use fc_fontations_bindgen::{ fcint::{ FcFreeTypeLangSet, FC_CAPABILITY_OBJECT, FC_CHARSET_OBJECT, FC_COLOR_OBJECT, - FC_DECORATIVE_OBJECT, FC_FILE_OBJECT, FC_FONTFORMAT_OBJECT, FC_FONTVERSION_OBJECT, - FC_FONT_HAS_HINT_OBJECT, FC_FONT_WRAPPER_OBJECT, FC_FOUNDRY_OBJECT, FC_LANG_OBJECT, - FC_ORDER_OBJECT, FC_OUTLINE_OBJECT, FC_SCALABLE_OBJECT, FC_SYMBOL_OBJECT, + FC_FILE_OBJECT, FC_FONTFORMAT_OBJECT, FC_FONTVERSION_OBJECT, FC_FONT_HAS_HINT_OBJECT, + FC_FONT_WRAPPER_OBJECT, FC_FOUNDRY_OBJECT, FC_LANG_OBJECT, FC_ORDER_OBJECT, + FC_OUTLINE_OBJECT, FC_SCALABLE_OBJECT, FC_SYMBOL_OBJECT, }, FcFontSet, FcFontSetAdd, FcPattern, }; @@ -288,33 +288,15 @@ fn build_patterns_for_font( .flat_map(move |instance_mode| { let mut instance_pattern = pattern.clone(); + // Family, full name, postscript name, etc. + // Includes adding style name to the pattern, which is then used by append_style_elements. + add_names(font, instance_mode, &mut instance_pattern); + // Style names: fcfreetype adds TT_NAME_ID_WWS_SUBFAMILY, TT_NAME_ID_TYPOGRAPHIC_SUBFAMILY, // TT_NAME_ID_FONT_SUBFAMILY as FC_STYLE_OBJECT, FC_STYLE_OBJECT_LANG unless a named instance // is added,then the instance's name id is used as FC_STYLE_OBJECT. - append_style_elements(font, instance_mode, ttc_index, &mut instance_pattern); - // For variable fonts: - // Names (mainly postscript name and style), weight, width and opsz (font-size?) are affected. - // * Add the variable font itself, with ranges for weight, width, opsz. - // * Add an entry for each named instance - // * With instance name turning into FC_STYLE_OBJECT. - // * Fixed width, wgth, opsz - // * Add the default instance with fixed values. - let mut had_decoratve = false; - // Family and full name. - add_names( - font, - instance_mode, - &mut instance_pattern, - &mut had_decoratve, - ); - - instance_pattern.append_element(PatternElement::new( - FC_DECORATIVE_OBJECT as i32, - had_decoratve.into(), - )); - instance_pattern .create_fc_pattern() .map(|wrapper| wrapper.into_raw() as *mut FcPattern) diff --git a/fc-fontations/name_records.rs b/fc-fontations/name_records.rs index 08cce97..631ccdc 100644 --- a/fc-fontations/name_records.rs +++ b/fc-fontations/name_records.rs @@ -91,7 +91,7 @@ impl<'a> FcSortedNameRecords<'a> { } (MS_ENGLISH_US, _) | (MAC_ENGLISH, _) => std::cmp::Ordering::Greater, (_, MS_ENGLISH_US) | (_, MAC_ENGLISH) => std::cmp::Ordering::Less, - _ => a.cmp(&b), + _ => a.cmp(&b).reverse(), } } @@ -125,7 +125,7 @@ impl<'a> FcSortedNameRecords<'a> { } } -impl<'a> Iterator for FcSortedNameRecords<'a> { +impl Iterator for FcSortedNameRecords<'_> { type Item = NameRecord; fn next(&mut self) -> Option<Self::Item> { diff --git a/fc-fontations/names.rs b/fc-fontations/names.rs index ae7fded..0344db5 100644 --- a/fc-fontations/names.rs +++ b/fc-fontations/names.rs @@ -41,7 +41,9 @@ fn object_ids_for_name_id(string_id: StringId) -> Option<(i32, i32)> { StringId::FAMILY_NAME | StringId::WWS_FAMILY_NAME | StringId::TYPOGRAPHIC_FAMILY_NAME => { Some((FC_FAMILY_OBJECT as i32, FC_FAMILYLANG_OBJECT as i32)) } - StringId::FULL_NAME => Some((FC_FULLNAME_OBJECT as i32, FC_FULLNAMELANG_OBJECT as i32)), + StringId::FULL_NAME | StringId::COMPATIBLE_FULL_NAME => { + Some((FC_FULLNAME_OBJECT as i32, FC_FULLNAMELANG_OBJECT as i32)) + } StringId::POSTSCRIPT_NAME => { Some((FC_POSTSCRIPT_NAME_OBJECT as i32, FC_INVALID_OBJECT as i32)) } @@ -69,13 +71,37 @@ fn mangle_postscript_name_for_named_instance( let instance_ps_name_id = font .named_instances() .get(named_instance_id as usize)? - .postscript_name_id()?; - let ps_name = font - .localized_strings(instance_ps_name_id) - .english_or_first()? - .clone() - .to_string(); - CString::new(ps_name).ok() + .postscript_name_id(); + + if let Some(ps_name_id) = instance_ps_name_id { + let ps_name = font + .localized_strings(ps_name_id) + .english_or_first()? + .clone() + .to_string(); + CString::new(ps_name).ok() + } else { + let instance_subfamily_name_id = font + .named_instances() + .get(named_instance_id as usize)? + .subfamily_name_id(); + let prefix = font + .localized_strings(StringId::VARIATIONS_POSTSCRIPT_NAME_PREFIX) + .english_or_first() + .or(font + .localized_strings(StringId::FAMILY_NAME) + .english_or_first())? + .to_string() + + "-"; + let subfam = font + .localized_strings(instance_subfamily_name_id) + .english_or_first()? + .to_string(); + + let assembled = prefix + &subfam; + let assembled = assembled.replace(" ", ""); + CString::new(assembled).ok() + } } fn mangle_subfamily_name_for_named_instance( @@ -112,19 +138,7 @@ fn mangle_full_name_for_named_instance(font: &FontRef, named_instance_id: i32) - CString::new(full_name + &subfam).ok() } -fn determine_decorative(object_id: i32, name: &Option<CString>) -> bool { - object_id == FC_STYLE_OBJECT as i32 - && name - .as_ref() - .is_some_and(|name| name.to_string_lossy().to_lowercase().contains("decorative")) -} - -pub fn add_names( - font: &FontRef, - instance_mode: InstanceMode, - pattern: &mut FcPatternBuilder, - had_decorative: &mut bool, -) { +pub fn add_names(font: &FontRef, instance_mode: InstanceMode, pattern: &mut FcPatternBuilder) { let mut already_encountered_names: HashSet<(i32, String)> = HashSet::new(); let name_table = font.name(); if name_table.is_err() { @@ -140,13 +154,19 @@ pub fn add_names( let name = if localized.to_string().is_empty() { None } else { - CString::new(localized.to_string()).ok() + let mut name_trimmed = localized.to_string().trim().to_owned(); + // PostScript name sanitization. + if object_ids.0 == FC_POSTSCRIPT_NAME_OBJECT as i32 { + name_trimmed = name_trimmed.replace(" ", ""); + } + CString::new(name_trimmed).ok() }; let language = localized.language().or(Some("und")).and_then(|lang| { + let lang = lang.to_lowercase(); let lang = if lang.starts_with("zh") { lang } else { - lang.split('-').next().unwrap_or(lang) + lang.split('-').next().unwrap_or(&lang).to_string() }; CString::new(lang).ok() }); @@ -173,8 +193,6 @@ pub fn add_names( _ => name, }; - *had_decorative = determine_decorative(object_ids.0, &name); - if let (Some(name), Some(language)) = (name, language) { let normalized_name = normalize_name(&name); if already_encountered_names.contains(&(object_ids.0, normalized_name.clone())) { diff --git a/fc-fontations/pattern_bindings/mod.rs b/fc-fontations/pattern_bindings/mod.rs index 990d5e1..826f34b 100644 --- a/fc-fontations/pattern_bindings/mod.rs +++ b/fc-fontations/pattern_bindings/mod.rs @@ -93,8 +93,8 @@ impl From<FcLangSetWrapper> for PatternValue { #[derive(Debug, Clone)] pub struct PatternElement { - object_id: i32, - value: PatternValue, + pub object_id: i32, + pub value: PatternValue, } impl PatternElement { @@ -191,6 +191,16 @@ impl FcPatternBuilder { } } +/// Mainly needed for finding the style PatternElement in attributes.rs. +impl<'a> IntoIterator for &'a FcPatternBuilder { + type Item = &'a PatternElement; + type IntoIter = std::slice::Iter<'a, PatternElement>; + + fn into_iter(self) -> Self::IntoIter { + self.elements.iter() + } +} + #[cfg(test)] mod test { use std::ffi::CString;