fontconfig: Branch 'main' - 5 commits

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Thu May 22 05:54:32 UTC 2025


 fc-fontations/charset.rs                     |   90 +++++++++++++++++++++++++++
 fc-fontations/instance_enumerate.rs          |    1 
 fc-fontations/lang.rs                        |   67 ++++++++++++++++++++
 fc-fontations/mod.rs                         |   87 ++++++++++++++++++++++++--
 fc-fontations/pattern_bindings/fc_wrapper.rs |   17 ++++-
 fc-fontations/pattern_bindings/mod.rs        |   28 ++++++--
 test/test_fontations_ft_query.py             |    5 +
 7 files changed, 280 insertions(+), 15 deletions(-)

New commits:
commit 1c63ed3cffefb21defaafd5ba4e52af77c53b8ad
Merge: 8cf0ce7 5e8e9a0
Author: Akira TAGOH <akira at tagoh.org>
Date:   Thu May 22 05:54:29 2025 +0000

    Merge branch 'charset' into 'main'
    
    [Fontations] Add charset, langset, symbol, fontwrapper and file pattern elements
    
    See merge request fontconfig/fontconfig!404

commit 5e8e9a0f0cdd140eae4969d803a4d5a611f86bae
Author: Dominik Röttsches <drott at chromium.org>
Date:   Fri May 16 12:29:16 2025 +0000

    [Fontations] Add woff wrapper and filename if file is woff or woff2

diff --git a/fc-fontations/instance_enumerate.rs b/fc-fontations/instance_enumerate.rs
index 84deb08..89b26fe 100644
--- a/fc-fontations/instance_enumerate.rs
+++ b/fc-fontations/instance_enumerate.rs
@@ -29,7 +29,6 @@ use read_fonts::{
 };
 use skrifa::MetadataProvider;
 
-#[allow(unused)]
 pub fn fonts_and_indices(
     file_ref: Option<FileRef>,
 ) -> impl Iterator<Item = (FontRef<'_>, Option<i32>)> {
diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs
index bd6a064..4fba651 100644
--- a/fc-fontations/mod.rs
+++ b/fc-fontations/mod.rs
@@ -40,9 +40,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_OUTLINE_OBJECT, FC_SCALABLE_OBJECT, FC_SYMBOL_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_OUTLINE_OBJECT, FC_SCALABLE_OBJECT, FC_SYMBOL_OBJECT,
     },
     FcFontSet, FcFontSetAdd, FcPattern,
 };
@@ -79,14 +79,24 @@ pub unsafe extern "C" fn add_patterns_to_fontset(
     let fileref = FileRef::new(&bytes).ok();
 
     let fonts = fonts_and_indices(fileref);
+
+    let mut patterns_added: u32 = 0;
     for (font, ttc_index) in fonts {
         for pattern in build_patterns_for_font(&font, font_file, ttc_index) {
             if FcFontSetAdd(font_set, pattern) == 0 {
                 return 0;
             }
+            patterns_added += 1;
         }
     }
 
+    // Fontations does not natively understand WOFF/WOFF2 compressed file,
+    // if we are asked to scan one of those, only add wrapper information
+    // and filename.
+    if patterns_added == 0 {
+        try_append_woff_pattern(font_set, bytes.as_slice(), font_file);
+    }
+
     1
 }
 
@@ -102,6 +112,24 @@ enum InstanceMode {
     Variable,
 }
 
+fn try_append_woff_pattern(font_set: *mut FcFontSet, bytes: &[u8], font_file: *const libc::c_char) {
+    let wrapper: Option<CString> = match bytes.get(0..4) {
+        Some(b"wOFF") => CString::new("WOFF").ok(),
+        Some(b"wOF2") => CString::new("WOFF2").ok(),
+        _ => None,
+    };
+
+    if let Some(w) = wrapper {
+        let mut pattern = FcPatternBuilder::new();
+        pattern.append_element(PatternElement::new(FC_FONT_WRAPPER_OBJECT as i32, w.into()));
+        add_font_file_name(&mut pattern, font_file);
+
+        pattern
+            .create_fc_pattern()
+            .map(|p| unsafe { FcFontSetAdd(font_set, p.into_raw() as *mut FcPattern) });
+    }
+}
+
 fn has_one_of_tables<I>(font_ref: &FontRef, tags: I) -> bool
 where
     I: IntoIterator,
@@ -127,6 +155,17 @@ fn has_hint(font_ref: &FontRef) -> bool {
     false
 }
 
+fn add_font_file_name(pattern: &mut FcPatternBuilder, font_file: *const libc::c_char) {
+    if !font_file.is_null() {
+        let filename = unsafe { std::ffi::CStr::from_ptr(font_file) };
+
+        pattern.append_element(PatternElement::new(
+            FC_FILE_OBJECT as i32,
+            filename.to_owned().into(),
+        ));
+    }
+}
+
 fn build_patterns_for_font(
     font: &FontRef,
     font_file: *const libc::c_char,
@@ -220,14 +259,7 @@ fn build_patterns_for_font(
         .unwrap_or_default()
         .to_bits();
 
-    if !font_file.is_null() {
-        let filename = unsafe { std::ffi::CStr::from_ptr(font_file) };
-
-        pattern.append_element(PatternElement::new(
-            FC_FILE_OBJECT as i32,
-            filename.to_owned().into(),
-        ));
-    }
+    add_font_file_name(&mut pattern, font_file);
 
     pattern.append_element(PatternElement::new(
         FC_FONT_WRAPPER_OBJECT as i32,
diff --git a/fc-fontations/pattern_bindings/mod.rs b/fc-fontations/pattern_bindings/mod.rs
index d3e0f37..990d5e1 100644
--- a/fc-fontations/pattern_bindings/mod.rs
+++ b/fc-fontations/pattern_bindings/mod.rs
@@ -32,7 +32,7 @@ use std::fmt::Debug;
 use fc_fontations_bindgen::fcint::{
     FcPattern, FcPatternObjectAddBool, FcPatternObjectAddCharSet, FcPatternObjectAddDouble,
     FcPatternObjectAddInteger, FcPatternObjectAddLangSet, FcPatternObjectAddRange,
-    FcPatternObjectAddString, FC_FAMILY_OBJECT,
+    FcPatternObjectAddString, FC_FAMILY_OBJECT, FC_FILE_OBJECT,
 };
 
 use fc_wrapper::{FcCharSetWrapper, FcLangSetWrapper, FcPatternWrapper, FcRangeWrapper};
@@ -165,23 +165,25 @@ impl FcPatternBuilder {
     pub fn create_fc_pattern(&mut self) -> Option<FcPatternWrapper> {
         let pattern = FcPatternWrapper::new()?;
 
-        let mut family_name_encountered = false;
+        let mut family_name_or_file_name_encountered = false;
 
         const FAMILY_ID: i32 = FC_FAMILY_OBJECT as i32;
+        const FILE_ID: i32 = FC_FILE_OBJECT as i32;
+
         for element in self.elements.drain(0..) {
             if let PatternElement {
-                object_id: FAMILY_ID,
-                value: PatternValue::String(ref fam_name),
+                object_id: FAMILY_ID | FILE_ID,
+                value: PatternValue::String(ref file_fam_name),
             } = element
             {
-                if !fam_name.is_empty() {
-                    family_name_encountered = true;
+                if !file_fam_name.is_empty() {
+                    family_name_or_file_name_encountered = true;
                 }
             }
             element.append_to_fc_pattern(pattern.as_ptr()).ok()?;
         }
 
-        if !family_name_encountered {
+        if !family_name_or_file_name_encountered {
             return None;
         }
 
commit 9fa2ba1d092b3f22153cc6ce1d8470d14226199c
Author: Dominik Röttsches <drott at chromium.org>
Date:   Wed May 14 12:35:47 2025 +0300

    [Fontations] Add fontwrapper, filename and symbol elements to pattern

diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs
index 2758679..bd6a064 100644
--- a/fc-fontations/mod.rs
+++ b/fc-fontations/mod.rs
@@ -40,14 +40,16 @@ use names::add_names;
 use fc_fontations_bindgen::{
     fcint::{
         FcFreeTypeLangSet, FC_CAPABILITY_OBJECT, FC_CHARSET_OBJECT, FC_COLOR_OBJECT,
-        FC_DECORATIVE_OBJECT, FC_FONTFORMAT_OBJECT, FC_FONTVERSION_OBJECT, FC_FONT_HAS_HINT_OBJECT,
-        FC_FOUNDRY_OBJECT, FC_LANG_OBJECT, FC_OUTLINE_OBJECT, FC_SCALABLE_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_OUTLINE_OBJECT, FC_SCALABLE_OBJECT, FC_SYMBOL_OBJECT,
     },
     FcFontSet, FcFontSetAdd, FcPattern,
 };
 
 use font_types::Tag;
 use pattern_bindings::{fc_wrapper::FcLangSetWrapper, FcPatternBuilder, PatternElement};
+use skrifa::MetadataProvider;
 use std::str::FromStr;
 
 use read_fonts::{FileRef, FontRef, TableProvider};
@@ -127,7 +129,7 @@ fn has_hint(font_ref: &FontRef) -> bool {
 
 fn build_patterns_for_font(
     font: &FontRef,
-    _: *const libc::c_char,
+    font_file: *const libc::c_char,
     ttc_index: Option<i32>,
 ) -> Vec<*mut FcPattern> {
     let mut pattern = FcPatternBuilder::new();
@@ -178,6 +180,11 @@ fn build_patterns_for_font(
         foundry_string.into(),
     ));
 
+    pattern.append_element(PatternElement::new(
+        FC_SYMBOL_OBJECT as i32,
+        font.charmap().is_symbol().into(),
+    ));
+
     if let Some(capabilities) = make_capabilities(font) {
         pattern.append_element(PatternElement::new(
             FC_CAPABILITY_OBJECT as i32,
@@ -213,6 +220,20 @@ fn build_patterns_for_font(
         .unwrap_or_default()
         .to_bits();
 
+    if !font_file.is_null() {
+        let filename = unsafe { std::ffi::CStr::from_ptr(font_file) };
+
+        pattern.append_element(PatternElement::new(
+            FC_FILE_OBJECT as i32,
+            filename.to_owned().into(),
+        ));
+    }
+
+    pattern.append_element(PatternElement::new(
+        FC_FONT_WRAPPER_OBJECT as i32,
+        CString::new("SFNT").unwrap().into(),
+    ));
+
     pattern.append_element(PatternElement::new(
         FC_FONTVERSION_OBJECT as i32,
         version.into(),
diff --git a/test/test_fontations_ft_query.py b/test/test_fontations_ft_query.py
index 04fe4da..643f2e9 100644
--- a/test/test_fontations_ft_query.py
+++ b/test/test_fontations_ft_query.py
@@ -68,6 +68,9 @@ def test_fontations_freetype_fcquery_equal(font_file):
         "capability",
         "charset",
         "langset",
+        "symbol",
+        "fontwrapper",
+        "file",
     ]
     format_string = ":".join(
         "%{" + entity + "}" for entity in supported_format_entitites
commit a82ab0f3cf76525cb87b2082ea706b50c3b62543
Author: Dominik Röttsches <drott at chromium.org>
Date:   Wed May 14 11:48:47 2025 +0300

    [Fontations] Add langset pattern element
    
    Process charset into a langset, taking into account
    the special processing that FcFreeTypeLangSet does to
    not extend advertised langset support beyond what the font
    advertises in OS/2.

diff --git a/fc-fontations/lang.rs b/fc-fontations/lang.rs
new file mode 100644
index 0000000..b1f7529
--- /dev/null
+++ b/fc-fontations/lang.rs
@@ -0,0 +1,67 @@
+/*
+ * fontconfig/fc-fontations/lang.rs
+ *
+ * Copyright 2025 Google LLC.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the author(s) not be used in
+ * advertising or publicity pertaining to distribution of the software without
+ * specific, written prior permission.  The authors make no
+ * representations about the suitability of this software for any purpose.  It
+ * is provided "as is" without express or implied warranty.
+ *
+ * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+use read_fonts::TableProvider;
+use skrifa::FontRef;
+use std::ffi::CString;
+
+// See logic and comment in FcFreeTypeLangSet, check for Han charsets to make fonts
+// which advertise support for a single language not support other Han languages.
+fn exclusive_lang_in_codepage_range(codepage_ranges: u64) -> Option<String> {
+    // u32 is the position of the bit in the OS/2 codepage range.
+    const LANG_MAP: [(u32, &str); 4] = [(17, "ja"), (18, "zh-cn"), (19, "ko"), (20, "zh-tw")];
+
+    let mut found_languages = LANG_MAP
+        .iter()
+        .filter(move |bit_lang_pair| codepage_ranges & (1 << bit_lang_pair.0) != 0);
+
+    match (found_languages.next(), found_languages.next()) {
+        (Some(bit_lang_pair), None) => Some(bit_lang_pair.1.to_string()),
+        _ => None,
+    }
+}
+
+// Returns the language identifier string if according to OS/2 codepage ranges
+// the font advertises support for only one Han font, otherwise none.
+pub fn exclusive_lang(font: &FontRef) -> Option<CString> {
+    let os2 = font.os2().ok()?;
+    let codepage_ranges: u64 =
+        ((os2.ul_code_page_range_2()? as u64) << 32) + os2.ul_code_page_range_1()? as u64;
+
+    exclusive_lang_in_codepage_range(codepage_ranges).and_then(|string| CString::new(string).ok())
+}
+
+#[cfg(test)]
+mod test {
+    use super::exclusive_lang_in_codepage_range;
+    #[test]
+    fn exclusive_lang() {
+        let non_exclusive = 1 << 17 + 1 << 20;
+        assert!(exclusive_lang_in_codepage_range(non_exclusive).is_none());
+        let exclusive = 1 << 17;
+        assert_eq!(exclusive_lang_in_codepage_range(exclusive).unwrap(), "ja");
+        let unmatched_non_han = 1 << 4 + 1 << 21;
+        assert!(exclusive_lang_in_codepage_range(unmatched_non_han).is_none());
+    }
+}
diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs
index 1568084..2758679 100644
--- a/fc-fontations/mod.rs
+++ b/fc-fontations/mod.rs
@@ -27,25 +27,27 @@ mod capabilities;
 mod charset;
 mod foundries;
 mod instance_enumerate;
+mod lang;
 mod names;
 mod pattern_bindings;
 
 use attributes::append_style_elements;
 use capabilities::make_capabilities;
 use foundries::make_foundry;
+use lang::exclusive_lang;
 use names::add_names;
 
 use fc_fontations_bindgen::{
     fcint::{
-        FC_CAPABILITY_OBJECT, FC_CHARSET_OBJECT, FC_COLOR_OBJECT, FC_DECORATIVE_OBJECT,
-        FC_FONTFORMAT_OBJECT, FC_FONTVERSION_OBJECT, FC_FONT_HAS_HINT_OBJECT, FC_FOUNDRY_OBJECT,
-        FC_OUTLINE_OBJECT, FC_SCALABLE_OBJECT,
+        FcFreeTypeLangSet, FC_CAPABILITY_OBJECT, FC_CHARSET_OBJECT, FC_COLOR_OBJECT,
+        FC_DECORATIVE_OBJECT, FC_FONTFORMAT_OBJECT, FC_FONTVERSION_OBJECT, FC_FONT_HAS_HINT_OBJECT,
+        FC_FOUNDRY_OBJECT, FC_LANG_OBJECT, FC_OUTLINE_OBJECT, FC_SCALABLE_OBJECT,
     },
     FcFontSet, FcFontSetAdd, FcPattern,
 };
 
 use font_types::Tag;
-use pattern_bindings::{FcPatternBuilder, PatternElement};
+use pattern_bindings::{fc_wrapper::FcLangSetWrapper, FcPatternBuilder, PatternElement};
 use std::str::FromStr;
 
 use read_fonts::{FileRef, FontRef, TableProvider};
@@ -183,11 +185,25 @@ fn build_patterns_for_font(
         ));
     };
 
+    // CharSet and Langset.
     if let Some(charset) = charset::make_charset(font) {
-        pattern.append_element(PatternElement::new(
-            FC_CHARSET_OBJECT as i32,
-            charset.into(),
-        ));
+        let exclusive_lang =
+            exclusive_lang(font).map_or(std::ptr::null(), |lang| lang.as_bytes_with_nul().as_ptr());
+
+        unsafe {
+            let langset =
+                FcLangSetWrapper::from_raw(FcFreeTypeLangSet(charset.as_ptr(), exclusive_lang));
+
+            pattern.append_element(PatternElement::new(
+                FC_CHARSET_OBJECT as i32,
+                charset.into(),
+            ));
+
+            // TODO: Move FcFreeTypeLangSet to a different name, as the function does not actually depend on FreeType.
+            if !langset.is_null() {
+                pattern.append_element(PatternElement::new(FC_LANG_OBJECT as i32, langset.into()));
+            }
+        }
     };
 
     let version = font
diff --git a/fc-fontations/pattern_bindings/fc_wrapper.rs b/fc-fontations/pattern_bindings/fc_wrapper.rs
index a8b90f4..0fa727e 100644
--- a/fc-fontations/pattern_bindings/fc_wrapper.rs
+++ b/fc-fontations/pattern_bindings/fc_wrapper.rs
@@ -181,4 +181,8 @@ impl FcLangSetWrapper {
             })
         }
     }
+
+    pub fn is_null(&self) -> bool {
+        self.inner.is_null()
+    }
 }
diff --git a/fc-fontations/pattern_bindings/mod.rs b/fc-fontations/pattern_bindings/mod.rs
index 5f8c8a4..d3e0f37 100644
--- a/fc-fontations/pattern_bindings/mod.rs
+++ b/fc-fontations/pattern_bindings/mod.rs
@@ -85,6 +85,12 @@ impl From<FcCharSetWrapper> for PatternValue {
     }
 }
 
+impl From<FcLangSetWrapper> for PatternValue {
+    fn from(value: FcLangSetWrapper) -> Self {
+        PatternValue::LangSet(value)
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct PatternElement {
     object_id: i32,
diff --git a/test/test_fontations_ft_query.py b/test/test_fontations_ft_query.py
index eaf7a15..04fe4da 100644
--- a/test/test_fontations_ft_query.py
+++ b/test/test_fontations_ft_query.py
@@ -67,6 +67,7 @@ def test_fontations_freetype_fcquery_equal(font_file):
         "slant",
         "capability",
         "charset",
+        "langset",
     ]
     format_string = ":".join(
         "%{" + entity + "}" for entity in supported_format_entitites
commit 614275b0d8b29ea350929053deea576c9fca1af0
Author: Dominik Röttsches <drott at chromium.org>
Date:   Wed May 14 10:41:41 2025 +0300

    [Fontations] Add charset pattern element
    
    Generate charset from cmap mappings, check for ink in control
    characters, same as in the FreeType-backed fccharset.c.

diff --git a/fc-fontations/charset.rs b/fc-fontations/charset.rs
new file mode 100644
index 0000000..7affb09
--- /dev/null
+++ b/fc-fontations/charset.rs
@@ -0,0 +1,90 @@
+/*
+ * fontconfig/fc-fontations/charset.rs
+ *
+ * Copyright 2025 Google LLC.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the author(s) not be used in
+ * advertising or publicity pertaining to distribution of the software without
+ * specific, written prior permission.  The authors make no
+ * representations about the suitability of this software for any purpose.  It
+ * is provided "as is" without express or implied warranty.
+ *
+ * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+use font_types::GlyphId;
+
+use skrifa::outline::DrawSettings;
+use skrifa::prelude::{LocationRef, Size};
+use skrifa::{outline::pen::OutlinePen, FontRef, MetadataProvider};
+
+use crate::pattern_bindings::fc_wrapper::FcCharSetWrapper;
+
+struct HasAnyContourPen {
+    has_ink: bool,
+}
+
+impl HasAnyContourPen {
+    pub fn new() -> Self {
+        Self { has_ink: false }
+    }
+}
+
+impl OutlinePen for HasAnyContourPen {
+    fn move_to(&mut self, _x: f32, _y: f32) {}
+    fn line_to(&mut self, _x: f32, _y: f32) {}
+    fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {}
+    fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {}
+    fn close(&mut self) {
+        self.has_ink = true;
+    }
+}
+
+const CONTROL_CHAR_MAX: u32 = 0x1F;
+
+// For control characters, check for whether they have ink/any-contour since
+// these might be mapped to space. Simulates fcfreetype.c behavior.
+// Specifically, FreeType checks for face->glyph->outline.n_contours - so,
+// number of contours, not for actually pixel coverage / contour area.
+fn control_char_without_ink(font: &FontRef, char: u32, glyph: GlyphId) -> bool {
+    let mut pen = HasAnyContourPen::new();
+    if char <= CONTROL_CHAR_MAX {
+        font.outline_glyphs().get(glyph).and_then(|outline| {
+            outline
+                .draw(
+                    DrawSettings::unhinted(Size::new(12.0), LocationRef::default()),
+                    &mut pen,
+                )
+                .ok()
+        });
+        if !pen.has_ink {
+            return true;
+        }
+    }
+    false
+}
+
+pub fn make_charset(font: &FontRef) -> Option<FcCharSetWrapper> {
+    let mut charset = FcCharSetWrapper::new()?;
+    let mappings = font.charmap().mappings();
+    for mapping in mappings {
+        if control_char_without_ink(font, mapping.0, mapping.1) {
+            continue;
+        }
+
+        if charset.add_char(mapping.0).is_err() {
+            return None;
+        }
+    }
+    Some(charset)
+}
diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs
index 72f5ac7..1568084 100644
--- a/fc-fontations/mod.rs
+++ b/fc-fontations/mod.rs
@@ -24,6 +24,7 @@
 
 mod attributes;
 mod capabilities;
+mod charset;
 mod foundries;
 mod instance_enumerate;
 mod names;
@@ -36,9 +37,9 @@ use names::add_names;
 
 use fc_fontations_bindgen::{
     fcint::{
-        FC_CAPABILITY_OBJECT, FC_COLOR_OBJECT, FC_DECORATIVE_OBJECT, FC_FONTFORMAT_OBJECT,
-        FC_FONTVERSION_OBJECT, FC_FONT_HAS_HINT_OBJECT, FC_FOUNDRY_OBJECT, FC_OUTLINE_OBJECT,
-        FC_SCALABLE_OBJECT,
+        FC_CAPABILITY_OBJECT, FC_CHARSET_OBJECT, FC_COLOR_OBJECT, FC_DECORATIVE_OBJECT,
+        FC_FONTFORMAT_OBJECT, FC_FONTVERSION_OBJECT, FC_FONT_HAS_HINT_OBJECT, FC_FOUNDRY_OBJECT,
+        FC_OUTLINE_OBJECT, FC_SCALABLE_OBJECT,
     },
     FcFontSet, FcFontSetAdd, FcPattern,
 };
@@ -182,6 +183,13 @@ fn build_patterns_for_font(
         ));
     };
 
+    if let Some(charset) = charset::make_charset(font) {
+        pattern.append_element(PatternElement::new(
+            FC_CHARSET_OBJECT as i32,
+            charset.into(),
+        ));
+    };
+
     let version = font
         .head()
         .ok()
diff --git a/fc-fontations/pattern_bindings/fc_wrapper.rs b/fc-fontations/pattern_bindings/fc_wrapper.rs
index ccc3269..a8b90f4 100644
--- a/fc-fontations/pattern_bindings/fc_wrapper.rs
+++ b/fc-fontations/pattern_bindings/fc_wrapper.rs
@@ -28,7 +28,7 @@ use fc_fontations_bindgen::{
         FcPatternCreate, FcPatternDestroy, FcRange, FcRangeCopy, FcRangeCreateDouble,
         FcRangeDestroy,
     },
-    FcLangSetCopy, FcLangSetCreate, FcLangSetDestroy,
+    FcCharSetAddChar, FcLangSetCopy, FcLangSetCreate, FcLangSetDestroy,
 };
 
 macro_rules! wrap_fc_object {
@@ -134,7 +134,6 @@ wrap_fc_object! {
 }
 
 impl FcCharSetWrapper {
-    #[allow(unused)]
     pub fn new() -> Option<Self> {
         let created_charset: *mut FcCharSet;
         unsafe {
@@ -148,6 +147,16 @@ impl FcCharSetWrapper {
             })
         }
     }
+
+    pub fn add_char(&mut self, char: u32) -> Result<(), ()> {
+        unsafe {
+            if FcCharSetAddChar(self.as_ptr(), char) == 1 {
+                Ok(())
+            } else {
+                Err(())
+            }
+        }
+    }
 }
 
 wrap_fc_object! {
diff --git a/fc-fontations/pattern_bindings/mod.rs b/fc-fontations/pattern_bindings/mod.rs
index 32f604a..5f8c8a4 100644
--- a/fc-fontations/pattern_bindings/mod.rs
+++ b/fc-fontations/pattern_bindings/mod.rs
@@ -79,6 +79,12 @@ impl From<FcRangeWrapper> for PatternValue {
     }
 }
 
+impl From<FcCharSetWrapper> for PatternValue {
+    fn from(value: FcCharSetWrapper) -> Self {
+        PatternValue::CharSet(value)
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct PatternElement {
     object_id: i32,
diff --git a/test/test_fontations_ft_query.py b/test/test_fontations_ft_query.py
index b7baf0b..eaf7a15 100644
--- a/test/test_fontations_ft_query.py
+++ b/test/test_fontations_ft_query.py
@@ -66,6 +66,7 @@ def test_fontations_freetype_fcquery_equal(font_file):
         "width",
         "slant",
         "capability",
+        "charset",
     ]
     format_string = ":".join(
         "%{" + entity + "}" for entity in supported_format_entitites


More information about the Fontconfig mailing list