fontconfig: Branch 'main' - 2 commits

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Wed May 7 09:26:37 UTC 2025


 fc-fontations/attributes.rs           |  176 ++++++++++++++++++++++++++++++++++
 fc-fontations/foundries.rs            |    6 -
 fc-fontations/mod.rs                  |   19 +++
 fc-fontations/pattern_bindings/mod.rs |   12 +-
 test/test_fontations_ft_query.py      |    3 
 5 files changed, 209 insertions(+), 7 deletions(-)

New commits:
commit 752eb3d1c8423de634c3f61cbdd3ae13c7fdab78
Merge: 2fdcfb4 0ddd01e
Author: Akira TAGOH <akira at tagoh.org>
Date:   Wed May 7 09:26:34 2025 +0000

    Merge branch 'attributes' into 'main'
    
    [Fontations] Add attributes weight, width, slant to Pattern
    
    See merge request fontconfig/fontconfig!396

commit 0ddd01e33e18f9d597aa011e755981d5af9d35f4
Author: Dominik Röttsches <drott at chromium.org>
Date:   Mon Apr 28 12:37:53 2025 +0300

    [Fontations] Add attributes weight, width, slant to Pattern
    
    * Parse style attributes through Fontations Attributes
      and OS/2 table information.
    * Add an instance mode enum as preparation for handling
      variable fonts and named instances.
    * Enable comparison testing for "size", "weight", "width", "slant".

diff --git a/fc-fontations/attributes.rs b/fc-fontations/attributes.rs
new file mode 100644
index 0000000..e0ee28c
--- /dev/null
+++ b/fc-fontations/attributes.rs
@@ -0,0 +1,176 @@
+extern crate fc_fontations_bindgen;
+
+use fc_fontations_bindgen::{
+    fcint::{
+        FC_INDEX_OBJECT, FC_NAMED_INSTANCE_OBJECT, FC_SLANT_OBJECT, FC_VARIABLE_OBJECT,
+        FC_WEIGHT_OBJECT, FC_WIDTH_OBJECT,
+    },
+    FcWeightFromOpenTypeDouble, 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,
+};
+
+use crate::{
+    pattern_bindings::{FcPatternBuilder, PatternElement},
+    InstanceMode,
+};
+use read_fonts::TableProvider;
+use skrifa::{
+    attribute::{Attributes, Stretch, Style, Weight},
+    FontRef,
+};
+
+fn fc_weight(skrifa_weight: Weight) -> f64 {
+    (match skrifa_weight {
+        Weight::THIN => FC_WEIGHT_THIN,
+        Weight::EXTRA_LIGHT => FC_WEIGHT_EXTRALIGHT,
+        Weight::LIGHT => FC_WEIGHT_LIGHT,
+        Weight::NORMAL => FC_WEIGHT_NORMAL,
+        Weight::MEDIUM => FC_WEIGHT_MEDIUM,
+        Weight::SEMI_BOLD => FC_WEIGHT_SEMIBOLD,
+        Weight::BOLD => FC_WEIGHT_BOLD,
+        Weight::EXTRA_BOLD => FC_WEIGHT_EXTRABOLD,
+        Weight::BLACK => FC_WEIGHT_BLACK,
+        // See fcfreetype.c: When weight is not available, set to medium.
+        // This would mean a font did not have a parseable OS/2 table or
+        // a weight value could not be retrieved from it.
+        _ => FC_WEIGHT_MEDIUM,
+    }) as f64
+}
+
+fn fc_slant(skrifa_style: Style) -> u32 {
+    match skrifa_style {
+        Style::Italic => FC_SLANT_ITALIC,
+        Style::Oblique(_) => FC_SLANT_OBLIQUE,
+        _ => FC_SLANT_ROMAN,
+    }
+}
+
+fn fc_width(skrifa_stretch: Stretch) -> f64 {
+    (match skrifa_stretch {
+        Stretch::ULTRA_CONDENSED => FC_WIDTH_ULTRACONDENSED,
+        Stretch::EXTRA_CONDENSED => FC_WIDTH_EXTRACONDENSED,
+        Stretch::CONDENSED => FC_WIDTH_CONDENSED,
+        Stretch::SEMI_CONDENSED => FC_WIDTH_SEMICONDENSED,
+        Stretch::NORMAL => FC_WIDTH_NORMAL,
+        Stretch::SEMI_EXPANDED => FC_WIDTH_SEMIEXPANDED,
+        Stretch::EXPANDED => FC_WIDTH_EXPANDED,
+        Stretch::EXTRA_EXPANDED => FC_WIDTH_EXTRAEXPANDED,
+        Stretch::ULTRA_EXPANDED => FC_WIDTH_ULTRAEXPANDED,
+        _ => FC_WIDTH_NORMAL,
+    } as f64)
+}
+
+fn fc_weight_from_os2(font_ref: &FontRef) -> Option<f64> {
+    let us_weight = font_ref.os2().ok()?.us_weight_class() as f64;
+    unsafe {
+        let result = FcWeightFromOpenTypeDouble(us_weight);
+        if result == -1.0 {
+            None
+        } else {
+            Some(result)
+        }
+    }
+}
+
+fn fc_width_from_os2(font_ref: &FontRef) -> Option<f64> {
+    let us_width = font_ref.os2().ok()?.us_width_class();
+    let converted = match us_width {
+        1 => FC_WIDTH_ULTRACONDENSED,
+        2 => FC_WIDTH_EXTRACONDENSED,
+        3 => FC_WIDTH_CONDENSED,
+        4 => FC_WIDTH_SEMICONDENSED,
+        5 => FC_WIDTH_NORMAL,
+        6 => FC_WIDTH_SEMIEXPANDED,
+        7 => FC_WIDTH_EXPANDED,
+        8 => FC_WIDTH_EXTRAEXPANDED,
+        9 => FC_WIDTH_ULTRAEXPANDED,
+        _ => FC_WIDTH_NORMAL,
+    };
+    Some(converted as f64)
+}
+
+struct AttributesToPattern {
+    weight_from_os2: Option<f64>,
+    width_from_os2: Option<f64>,
+    attributes: Attributes,
+}
+
+impl AttributesToPattern {
+    fn new(font: &FontRef) -> Self {
+        Self {
+            weight_from_os2: fc_weight_from_os2(font),
+            width_from_os2: fc_width_from_os2(font),
+            attributes: Attributes::new(font),
+        }
+    }
+
+    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 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 static_slant(&self) -> PatternElement {
+        PatternElement::new(
+            FC_SLANT_OBJECT as i32,
+            (fc_slant(self.attributes.style) as i32).into(),
+        )
+    }
+}
+
+pub fn append_style_elements(
+    font: &FontRef,
+    instance_mode: InstanceMode,
+    ttc_index: Option<i32>,
+    pattern: &mut FcPatternBuilder,
+) {
+    // TODO: fcfreetype.c seems to prefer parsing information from the WWS name table entry,
+    // 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);
+
+    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());
+
+            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().into(),
+            ));
+
+            pattern.append_element(PatternElement::new(
+                FC_NAMED_INSTANCE_OBJECT as i32,
+                false.into(),
+            ));
+        }
+        _ => {
+            // TODO: Variable and named instances not implemented yet.
+            unreachable!()
+        }
+    }
+}
diff --git a/fc-fontations/foundries.rs b/fc-fontations/foundries.rs
index ad60647..536331a 100644
--- a/fc-fontations/foundries.rs
+++ b/fc-fontations/foundries.rs
@@ -63,7 +63,7 @@ pub fn make_foundry(font: &FontRef) -> Option<CString> {
         return CString::new(os2.ach_vend_id().to_be_bytes()).ok();
     }
 
-    map_foundry_from_name_entry(&mut font.localized_strings(StringId::TRADEMARK)).or_else(
-        || map_foundry_from_name_entry(&mut font.localized_strings(StringId::MANUFACTURER)),
-    )
+    map_foundry_from_name_entry(&mut font.localized_strings(StringId::TRADEMARK)).or_else(|| {
+        map_foundry_from_name_entry(&mut font.localized_strings(StringId::MANUFACTURER))
+    })
 }
diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs
index 26754ff..1c4780d 100644
--- a/fc-fontations/mod.rs
+++ b/fc-fontations/mod.rs
@@ -22,10 +22,12 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
+mod attributes;
 mod foundries;
 mod names;
 mod pattern_bindings;
 
+use attributes::append_style_elements;
 use foundries::make_foundry;
 use names::add_names;
 
@@ -80,6 +82,18 @@ pub unsafe extern "C" fn add_patterns_to_fontset(
     1
 }
 
+/// Used for controlling FontConfig's behavior per font instance.
+///
+/// We add one pattern for the default instance, one for each named instance,
+/// and one for using the font as a variable font, with ranges of values where applicable.
+#[derive(Copy, Clone)]
+#[allow(dead_code)]
+enum InstanceMode {
+    Default,
+    Named(i32),
+    Variable,
+}
+
 fn fonts_and_indices(
     file_ref: Option<FileRef>,
 ) -> impl Iterator<Item = (FontRef<'_>, Option<i32>)> {
@@ -136,7 +150,7 @@ fn has_hint(font_ref: &FontRef) -> bool {
 fn build_patterns_for_font(
     font: &FontRef,
     _: *const libc::c_char,
-    _: Option<i32>,
+    ttc_index: Option<i32>,
 ) -> Vec<*mut FcPattern> {
     let mut pattern = FcPatternBuilder::new();
 
@@ -200,6 +214,9 @@ fn build_patterns_for_font(
         version.into(),
     ));
 
+    // TODO: Handle variable instance and named instances.
+    append_style_elements(font, InstanceMode::Default, ttc_index, &mut pattern);
+
     pattern
         .create_fc_pattern()
         .map(|p| p.into_raw() as *mut FcPattern)
diff --git a/fc-fontations/pattern_bindings/mod.rs b/fc-fontations/pattern_bindings/mod.rs
index 29359e9..24f7158 100644
--- a/fc-fontations/pattern_bindings/mod.rs
+++ b/fc-fontations/pattern_bindings/mod.rs
@@ -55,15 +55,21 @@ impl From<CString> for PatternValue {
     }
 }
 
+impl From<i32> for PatternValue {
+    fn from(item: i32) -> Self {
+        PatternValue::Integer(item)
+    }
+}
+
 impl From<bool> for PatternValue {
     fn from(item: bool) -> Self {
         PatternValue::Boolean(item)
     }
 }
 
-impl From<i32> for PatternValue {
-    fn from(item: i32) -> Self {
-        PatternValue::Integer(item)
+impl From<f64> for PatternValue {
+    fn from(item: f64) -> Self {
+        PatternValue::Double(item)
     }
 }
 
diff --git a/test/test_fontations_ft_query.py b/test/test_fontations_ft_query.py
index c08b16e..9a00e3e 100644
--- a/test/test_fontations_ft_query.py
+++ b/test/test_fontations_ft_query.py
@@ -62,6 +62,9 @@ def test_fontations_freetype_fcquery_equal(font_file):
         "fonthashint",
         "foundry",
         "version",
+        "weight",
+        "width",
+        "slant",
     ]
     format_string = ":".join(
         "%{" + entity + "}" for entity in supported_format_entitites


More information about the Fontconfig mailing list