fontconfig: Branch 'main' - 2 commits

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon Apr 28 14:49:31 UTC 2025


 doc/Makefile.am                  |    1 
 doc/fcfontations.fncs            |   47 +++++++++++
 doc/fontconfig-devel.sgml        |    9 ++
 doc/meson.build                  |    1 
 fc-fontations/meson.build        |   10 +-
 fc-fontations/mod.rs             |  165 +++++++++++++++++++++++++++++++++++----
 fc-fontations/names.rs           |   96 ++++++++++++++++++++++
 fc-query/fc-query.c              |   13 ++-
 fontconfig/fcfontations.h        |   43 ++++++++++
 meson.build                      |   13 ++-
 src/fcdir.c                      |    8 +
 src/fcfontations.c               |   46 ++++++++++
 src/fcint.h                      |    4 
 src/meson.build                  |    6 -
 test/meson.build                 |    2 
 test/test_fontations_ft_query.py |   83 +++++++++++++++++++
 16 files changed, 521 insertions(+), 26 deletions(-)

New commits:
commit 1d5113bc3ec0c6411ed03fb0e79a5c9403f7d638
Merge: 2e4e665 9708015
Author: Akira TAGOH <akira at tagoh.org>
Date:   Mon Apr 28 14:49:28 2025 +0000

    Merge branch 'buildFcQuery' into 'main'
    
    [Fontations] Enable fc-query indexing through Fontations
    
    See merge request fontconfig/fontconfig!393

commit 97080159225f8a43638b587190042135daf3859d
Author: Dominik Röttsches <drott at chromium.org>
Date:   Wed Apr 16 13:23:28 2025 +0300

    [Fontations] Enable fc-query indexing through Fontations
    
    * Expose FcFontationsQueryAll as a Fontations equivalent to the
      FreeType-based indexing function.
    * Depending on environment variable FC_FONTATIONS= being set, and
      Fontations being compiled in, execute indexing through
      Fontations. This is the first step towards compatible indexing, but
      does not support all pattern properties yet.
    * Add a pytest set of tests that compares fc-query output between
      FreeType and Fontations indexing.
    * Minor modifciation to test/meson.build to pass buildroot correctly
    
    Changelog: added

diff --git a/doc/Makefile.am b/doc/Makefile.am
index 335f984..389c64d 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -90,6 +90,7 @@ DOC_FUNCS_FNCS =		\
 	fcfontset.fncs		\
 	fcformat.fncs		\
 	fcfreetype.fncs		\
+	fcfontations.fncs	\
 	fcinit.fncs		\
 	fclangset.fncs		\
 	fcmatrix.fncs		\
diff --git a/doc/fcfontations.fncs b/doc/fcfontations.fncs
new file mode 100644
index 0000000..9b3b5a5
--- /dev/null
+++ b/doc/fcfontations.fncs
@@ -0,0 +1,47 @@
+/*
+ * fontconfig/doc/fcfontations.fncs
+ *
+ * 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.
+ */
+
+ at SYNOPSIS@
+#include <fontconfig.h>
+#include <fcfontations.h>
+ at RET@           unsigned int
+ at FUNC@          FcFontationsQueryAll
+ at TYPE1@         const FcChar8 *                 @ARG1@          file
+ at TYPE2@         int%                            @ARG2@          id
+ at TYPE3@         FcBlanks *                      @ARG3@          blanks
+ at TYPE4@         int *                           @ARG4@          count
+ at TYPE5@         FcFontSet *                     @ARG5@          set
+ at PURPOSE@       compute all patterns from font file (and index)
+ at DESC@
+UNSTABLE feature. Constructs patterns found in 'file'. Currently pattern is incomplete
+compared to FcFreeTypeQueryAll().
+Not supported yet, but will follow: If id is -1, then all patterns found
+in 'file' are added to 'set'.
+Otherwise, this function works exactly like FcFreeTypeQueryAll()/FcFreeTypeQuery().
+The number of faces in 'file' is returned in 'count'.
+The number of patterns added to 'set' is returned.
+FcBlanks is deprecated, <parameter>blanks</parameter> is ignored and
+accepted only for compatibility with older code.
+ at SINCE@         2.17.0
+@@
diff --git a/doc/fontconfig-devel.sgml b/doc/fontconfig-devel.sgml
index a3d41fb..897d48e 100644
--- a/doc/fontconfig-devel.sgml
+++ b/doc/fontconfig-devel.sgml
@@ -7,6 +7,7 @@
 <!ENTITY fcconstant SYSTEM "fcconstant.sgml">
 <!ENTITY fcdircache SYSTEM "fcdircache.sgml">
 <!ENTITY fcfile SYSTEM "fcfile.sgml">
+<!ENTITY fcfontations SYSTEM "fcfontations.sgml">
 <!ENTITY fcfontset SYSTEM "fcfontset.sgml">
 <!ENTITY fcformat SYSTEM "fcformat.sgml">
 <!ENTITY fcfreetype SYSTEM "fcfreetype.sgml">
@@ -498,6 +499,14 @@ functions.
     </para>
     &fcfreetype;
   </sect2>
+  <sect2><title>Fontations specific functions</title>
+    <para>
+For font indexing, an alternative backend based on the Fontations set
+of font libraries is in development. The indexing functions are exposed
+as Fontations specific functions.
+    </para>
+    &fcfontations;
+  </sect2>
   <sect2><title>FcValue</title>
     <para>
 FcValue is a structure containing a type tag and a union of all possible
diff --git a/doc/meson.build b/doc/meson.build
index 548df12..f2d46c7 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -29,6 +29,7 @@ doc_funcs_fncs = [
   'fcdircache',
   'fcfile',
   'fcfontset',
+  'fcfontations',
   'fcformat',
   'fcfreetype',
   'fcinit',
diff --git a/fc-fontations/meson.build b/fc-fontations/meson.build
index 567dc24..34d4e5b 100644
--- a/fc-fontations/meson.build
+++ b/fc-fontations/meson.build
@@ -38,16 +38,22 @@ if (fontations.enabled())
     rust_abi : 'rust',
   )
 
+  fontations_query_lib = static_library(
+    'fc_fontations_query',
+    include_directories : [ '../', '../fontconfig' ],
+    sources: ['../src/fcfontations.c', fcstdint_h, alias_headers],
+  )
+
   fc_fontations = static_library(
     'fc_fontations',
     sources: ['mod.rs'],
-    link_with: [bindgen_lib, pattern_lib],
+    link_with: [bindgen_lib, pattern_lib, fontations_query_lib],
     rust_abi: 'c',
     dependencies: [
       dependency('skrifa-0.30-rs'),
       dependency('read-fonts-0.28-rs'),
       dependency('font-types-0.8-rs'),
-      dependency('libc-0.2-rs')
+      dependency('libc-0.2-rs'),
     ],
     install: true,
 
diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs
index 3929924..f219790 100644
--- a/fc-fontations/mod.rs
+++ b/fc-fontations/mod.rs
@@ -23,12 +23,36 @@
  */
 
 extern crate fc_fontations_bindgen;
+extern crate font_types;
+extern crate read_fonts;
+extern crate skrifa;
 
+mod names;
 mod pattern_bindings;
 
+use names::add_names;
+
 use fc_fontations_bindgen::{
-    fcint::{FcPatternCreate, FcPatternObjectAddBool, FC_COLOR_OBJECT},
-    FcFontSet, FcFontSetAdd,
+    fcint::{
+        FC_COLOR_OBJECT, FC_FONTFORMAT_OBJECT, FC_FONT_HAS_HINT_OBJECT, FC_OUTLINE_OBJECT,
+        FC_SCALABLE_OBJECT,
+    },
+    FcFontSet, FcFontSetAdd, FcPattern,
+};
+
+use font_types::Tag;
+use pattern_bindings::{FcPatternBuilder, PatternElement};
+use std::str::FromStr;
+
+use read_fonts::{
+    FileRef::{self, Collection, Font},
+    FontRef, TableProvider,
+};
+
+use std::{
+    ffi::{CStr, CString, OsStr},
+    iter::IntoIterator,
+    os::unix::ffi::OsStrExt,
 };
 
 #[no_mangle]
@@ -40,33 +64,144 @@ use fc_fontations_bindgen::{
 /// * In this initial sanity check mock call, only one empty pattern
 ///   is added to the FontSet, which is null checked, which is sound.
 pub unsafe extern "C" fn add_patterns_to_fontset(
-    _: *const libc::c_char,
+    font_file: *const libc::c_char,
     font_set: *mut FcFontSet,
 ) -> libc::c_int {
-    let empty_pattern = FcPatternCreate();
-    // Test call to ensure that an FcPrivate API function FcPatternObjectAddBool
-    // is accessible and can be linked to.
-    FcPatternObjectAddBool(empty_pattern, FC_COLOR_OBJECT as i32, 0_i32);
-    if !font_set.is_null() {
-        FcFontSetAdd(
-            font_set,
-            empty_pattern as *mut fc_fontations_bindgen::FcPattern,
-        )
-    } else {
-        0
+    let font_path = unsafe { OsStr::from_bytes(CStr::from_ptr(font_file).to_bytes()) };
+    let bytes = std::fs::read(font_path).ok().unwrap_or_default();
+    let fileref = FileRef::new(&bytes).ok();
+
+    let fonts = fonts_and_indices(fileref);
+    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;
+            }
+        }
+    }
+
+    1
+}
+
+fn fonts_and_indices(
+    file_ref: Option<FileRef>,
+) -> impl Iterator<Item = (FontRef<'_>, Option<i32>)> {
+    let (iter_one, iter_two) = match file_ref {
+        Some(Font(font)) => (Some((Ok(font.clone()), None)), None),
+        Some(Collection(collection)) => (
+            None,
+            Some(
+                collection
+                    .iter()
+                    .enumerate()
+                    .map(|entry| (entry.1, Some(entry.0 as i32))),
+            ),
+        ),
+        None => (None, None),
+    };
+    iter_two
+        .into_iter()
+        .flatten()
+        .chain(iter_one)
+        .filter_map(|(font_result, index)| {
+            if let Ok(font) = font_result {
+                return Some((font, index));
+            }
+            None
+        })
+}
+
+fn has_one_of_tables<I>(font_ref: &FontRef, tags: I) -> bool
+where
+    I: IntoIterator,
+    I::Item: ToString,
+{
+    tags.into_iter().fold(false, |has_tag, tag| {
+        let tag = Tag::from_str(tag.to_string().as_str());
+        if let Ok(tag_converted) = tag {
+            return has_tag | font_ref.data_for_tag(tag_converted).is_some();
+        }
+        has_tag
+    })
+}
+
+fn has_hint(font_ref: &FontRef) -> bool {
+    if has_one_of_tables(font_ref, ["fpgm", "cvt "]) {
+        return true;
+    }
+
+    if let Some(prep_table) = font_ref.data_for_tag(Tag::new(b"prep")) {
+        return prep_table.len() > 7;
+    }
+    false
+}
+
+fn build_patterns_for_font(
+    font: &FontRef,
+    _: *const libc::c_char,
+    _: Option<i32>,
+) -> Vec<*mut FcPattern> {
+    let mut pattern = FcPatternBuilder::new();
+
+    add_names(font, &mut pattern);
+
+    let has_glyf = has_one_of_tables(font, ["glyf"]);
+    let has_cff = has_one_of_tables(font, ["CFF ", "CFF2"]);
+    let has_color = has_one_of_tables(font, ["COLR", "SVG ", "CBLC", "SBIX"]);
+
+    // Color and Outlines
+    let has_outlines = has_glyf | has_cff;
+
+    pattern.append_element(PatternElement::new(
+        FC_OUTLINE_OBJECT as i32,
+        has_outlines.into(),
+    ));
+    pattern.append_element(PatternElement::new(
+        FC_COLOR_OBJECT as i32,
+        has_color.into(),
+    ));
+    pattern.append_element(PatternElement::new(
+        FC_SCALABLE_OBJECT as i32,
+        (has_outlines || has_color).into(),
+    ));
+    pattern.append_element(PatternElement::new(
+        FC_FONT_HAS_HINT_OBJECT as i32,
+        has_hint(font).into(),
+    ));
+
+    match (has_glyf, has_cff) {
+        (_, true) => {
+            pattern.append_element(PatternElement::new(
+                FC_FONTFORMAT_OBJECT as i32,
+                CString::new("CFF").unwrap().into(),
+            ));
+        }
+        _ => {
+            pattern.append_element(PatternElement::new(
+                FC_FONTFORMAT_OBJECT as i32,
+                CString::new("TrueType").unwrap().into(),
+            ));
+        }
     }
+
+    pattern
+        .create_fc_pattern()
+        .map(|p| p.into_raw() as *mut FcPattern)
+        .into_iter()
+        .collect()
 }
 
 #[cfg(test)]
 mod test {
     use crate::add_patterns_to_fontset;
     use fc_fontations_bindgen::{FcFontSetCreate, FcFontSetDestroy};
+    use CString;
 
     #[test]
     fn basic_pattern_construction() {
         unsafe {
             let font_set = FcFontSetCreate();
-            assert!(add_patterns_to_fontset(std::ptr::null(), font_set) == 1);
+            assert!(add_patterns_to_fontset(CString::new("").unwrap().into_raw(), font_set) == 1);
             FcFontSetDestroy(font_set);
         }
     }
diff --git a/fc-fontations/names.rs b/fc-fontations/names.rs
new file mode 100644
index 0000000..1682e73
--- /dev/null
+++ b/fc-fontations/names.rs
@@ -0,0 +1,96 @@
+/*
+ * fontconfig/fc-fontations/mod.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 skrifa::{string::StringId, MetadataProvider};
+
+use fc_fontations_bindgen::fcint::{
+    FC_FAMILYLANG_OBJECT, FC_FAMILY_OBJECT, FC_INVALID_OBJECT, FC_POSTSCRIPT_NAME_OBJECT,
+};
+
+use CString;
+use FcPatternBuilder;
+use FontRef;
+use PatternElement;
+
+use std::collections::HashSet;
+
+fn objects_for_id(string_id: StringId) -> (i32, i32) {
+    match string_id {
+        StringId::FAMILY_NAME | StringId::WWS_FAMILY_NAME | StringId::TYPOGRAPHIC_FAMILY_NAME => {
+            (FC_FAMILY_OBJECT as i32, FC_FAMILYLANG_OBJECT as i32)
+        }
+        StringId::POSTSCRIPT_NAME => (FC_POSTSCRIPT_NAME_OBJECT as i32, FC_INVALID_OBJECT as i32),
+        _ => panic!("No equivalent FontConfig objects found for StringId."),
+    }
+}
+
+fn normalize_name(name: &CString) -> String {
+    name.clone()
+        .into_string()
+        .unwrap_or_default()
+        .to_lowercase()
+        .replace(' ', "")
+}
+
+pub fn add_names(font: &FontRef, pattern: &mut FcPatternBuilder) {
+    // Order of these is important for matching FreeType. Or we might need to sort these descending to achieve the same result.
+    let string_ids = &[
+        StringId::TYPOGRAPHIC_FAMILY_NAME,
+        StringId::FAMILY_NAME,
+        StringId::POSTSCRIPT_NAME,
+    ];
+
+    let mut already_encountered_names: HashSet<(i32, String)> = HashSet::new();
+    for string_id in string_ids.iter() {
+        let object_ids = objects_for_id(*string_id);
+        for string in font.localized_strings(*string_id) {
+            let name = if string.to_string().is_empty() {
+                None
+            } else {
+                CString::new(string.to_string()).ok()
+            };
+            let language = string.language().or(Some("und")).and_then(|lang| {
+                let lang = if lang.starts_with("zh") {
+                    lang
+                } else {
+                    lang.split('-').next().unwrap_or(lang)
+                };
+                CString::new(lang).ok()
+            });
+
+            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())) {
+                    continue;
+                }
+                already_encountered_names.insert((object_ids.0, normalized_name));
+                pattern.append_element(PatternElement::new(object_ids.0, name.into()));
+                // Postscriptname for example does not attach a language.
+                if object_ids.1 != FC_INVALID_OBJECT as i32 {
+                    pattern.append_element(PatternElement::new(object_ids.1, language.into()));
+                }
+            }
+        }
+    }
+}
diff --git a/fc-query/fc-query.c b/fc-query/fc-query.c
index b8d0a1c..debf4d3 100644
--- a/fc-query/fc-query.c
+++ b/fc-query/fc-query.c
@@ -36,6 +36,10 @@
 #include <fontconfig/fontconfig.h>
 #include <fontconfig/fcfreetype.h>
 
+#if ENABLE_FONTATIONS
+#  include <fontconfig/fcfontations.h>
+#endif
+
 #include <locale.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -157,8 +161,15 @@ main (int argc, char **argv)
 
     fs = FcFontSetCreate();
 
+    unsigned int (*query_function) (const FcChar8 *, unsigned int, FcBlanks *, int *, FcFontSet *) = FcFreeTypeQueryAll;
+#if ENABLE_FONTATIONS
+    if (getenv ("FC_FONTATIONS") != NULL) {
+	query_function = FcFontationsQueryAll;
+    }
+#endif
+
     for (; i < argc; i++) {
-	if (!FcFreeTypeQueryAll ((FcChar8 *)argv[i], id, NULL, NULL, fs)) {
+	if (!query_function ((FcChar8 *)argv[i], id, NULL, NULL, fs)) {
 	    fprintf (stderr, _("Can't query face %u of font file %s\n"), id, argv[i]);
 	    err = 1;
 	}
diff --git a/fontconfig/fcfontations.h b/fontconfig/fcfontations.h
new file mode 100644
index 0000000..28f1556
--- /dev/null
+++ b/fontconfig/fcfontations.h
@@ -0,0 +1,43 @@
+/*
+ * 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 copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef _FCFONTATIONSINT_H_
+#define _FCFONTATIONSINT_H_
+
+#ifndef FcPublic
+#  define FcPublic
+#endif
+
+#include <fontconfig/fontconfig.h>
+
+_FCFUNCPROTOBEGIN
+
+FcPublic unsigned int
+FcFontationsQueryAll (const FcChar8 *file,
+                      unsigned int   id,
+                      FcBlanks      *blanks,
+                      int           *count,
+                      FcFontSet     *set);
+
+_FCFUNCPROTOEND
+
+#endif
\ No newline at end of file
diff --git a/meson.build b/meson.build
index b795948..93e4347 100644
--- a/meson.build
+++ b/meson.build
@@ -479,9 +479,17 @@ fcstdint_h = fs.copyfile('src/fcstdint.h.in', 'fcstdint.h')
 
 makealias = files('src/makealias.py')[0]
 
+
+
+
+alias_input_headers = ['fontconfig/fontconfig.h', 'src/fcdeprecate.h', 'fontconfig/fcprivate.h']
+if get_option('fontations').enabled()
+  alias_input_headers += ['fontconfig/fcfontations.h']
+endif
+
 alias_headers = custom_target('alias_headers',
                               output: ['fcalias.h', 'fcaliastail.h'],
-                              input: ['fontconfig/fontconfig.h', 'src/fcdeprecate.h', 'fontconfig/fcprivate.h'],
+                              input: alias_input_headers,
                               command: [python3, makealias, join_paths(meson.current_source_dir(), 'src'), '@OUTPUT@', '@INPUT@'],
                              )
 
@@ -501,7 +509,8 @@ subdir('src')
 
 if get_option('fontations').enabled()
   subdir('fc-fontations')
-  lib_fontconfig_link_with_libs += [fc_fontations]
+  lib_fontconfig_kwargs = lib_fontconfig_kwargs + {
+    'link_with' : lib_fontconfig_kwargs['link_with'] + [fc_fontations, fontations_query_lib]}
 endif
 
 libfontconfig = library('fontconfig',
diff --git a/src/fcdir.c b/src/fcdir.c
index 9b93286..54be3b8 100644
--- a/src/fcdir.c
+++ b/src/fcdir.c
@@ -77,7 +77,13 @@ FcFileScanFontConfig (FcFontSet     *set,
 	fflush (stdout);
     }
 
-    if (!FcFreeTypeQueryAll (file, -1, NULL, NULL, set))
+    unsigned int (*query_function) (const FcChar8 *, unsigned int, FcBlanks *, int *, FcFontSet *) = FcFreeTypeQueryAll;
+#if ENABLE_FONTATIONS
+    if (getenv ("FC_FONTATIONS") != NULL) {
+	query_function = FcFontationsQueryAll;
+    }
+#endif
+    if (!query_function (file, -1, NULL, NULL, set))
 	return FcFalse;
 
     if (FcDebug() & FC_DBG_SCAN)
diff --git a/src/fcfontations.c b/src/fcfontations.c
new file mode 100644
index 0000000..a4ecec9
--- /dev/null
+++ b/src/fcfontations.c
@@ -0,0 +1,46 @@
+/*
+ * fontconfig/src/fcfontations.c
+ *
+ * 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.
+ */
+
+#include "fcint.h"
+
+#if ENABLE_FONTATIONS
+
+extern int add_patterns_to_fontset (const FcChar8 *file, FcFontSet *set);
+
+unsigned int
+FcFontationsQueryAll (const FcChar8 *file,
+                      unsigned int   id,
+                      FcBlanks      *blanks,
+                      int           *count,
+                      FcFontSet     *set)
+{
+    // TODO(#163): For exposing this as API this should handle the passed id.
+    return add_patterns_to_fontset (file, set);
+}
+
+#  define __fcfontations__
+#  include "fcaliastail.h"
+#  undef __fcfontations__
+
+#endif
diff --git a/src/fcint.h b/src/fcint.h
index e422c72..d3c2c5a 100644
--- a/src/fcint.h
+++ b/src/fcint.h
@@ -46,6 +46,10 @@
 #include <fontconfig/fontconfig.h>
 #include <fontconfig/fcprivate.h>
 
+#if ENABLE_FONTATIONS
+#  include <fontconfig/fcfontations.h>
+#endif
+
 #include <stddef.h>
 #include <sys/stat.h>
 #include <sys/types.h>
diff --git a/src/meson.build b/src/meson.build
index ee884f1..1fa7fe1 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -48,6 +48,7 @@ fc_sources = files([
 ])
 
 
+
 fcobjshash_h = cc.preprocess('fcobjshash.gperf.h', include_directories: incbase)
 fcobjshash_gperf = custom_target(
   input: fcobjshash_h,
@@ -62,13 +63,10 @@ fcobjshash_h = custom_target('fcobjshash.h',
   command: [gperf, '--pic', '-m', '100', '@INPUT@', '--output-file', '@OUTPUT@']
 )
 
-lib_fontconfig_link_with_libs =  [pattern_lib]
-
-
 lib_fontconfig_sources = [fc_sources, alias_headers, ft_alias_headers, fclang_h, fccase_h, fcobjshash_h, fcstdint_h]
 lib_fontconfig_kwargs = {
   'include_directories': incbase,
   'dependencies': [deps, math_dep],
-  'link_with': lib_fontconfig_link_with_libs,
+  'link_with': [pattern_lib],
 }
 
diff --git a/test/meson.build b/test/meson.build
index 895dbab..769d088 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -73,7 +73,7 @@ endforeach
 
 if get_option('fontations').enabled()
   rust = import('rust')
-  rust.test('fc_fontations_rust_tests', fc_fontations, link_with: [fc_fontations, libfontconfig_internal], depends: fetch_test_fonts)
+  rust.test('fc_fontations_rust_tests', fc_fontations, link_with: [libfontconfig_internal], depends: fetch_test_fonts)
 endif
 
 fs = import('fs')
diff --git a/test/test_fontations_ft_query.py b/test/test_fontations_ft_query.py
new file mode 100644
index 0000000..6a678aa
--- /dev/null
+++ b/test/test_fontations_ft_query.py
@@ -0,0 +1,83 @@
+#! /usr/bin/env python3
+# Copyright (C) 2025 Google LLC.
+# SPDX-License-Identifier: HPND
+
+import os
+from pathlib import Path
+import pytest
+import re
+import requests
+import subprocess
+
+
+def builddir():
+    return Path(os.environ.get("builddir", Path(__file__).parent.parent))
+
+
+def list_test_fonts():
+    font_files = []
+    for root, _, files in os.walk(builddir() / "testfonts"):
+        for file in files:
+            # Variable .ttc not supported yet.
+            if file.endswith(".ttf") and not "RobotoFlex" in file:
+                font_files.append(os.path.join(root, file))
+    return font_files
+
+
+def run_fc_query(font_file, format_string, with_fontations=False):
+    fc_query_path = builddir() / "fc-query" / "fc-query"
+
+    env = os.environ.copy()
+    if with_fontations:
+        env["FC_FONTATIONS"] = ""
+
+    result = subprocess.run(
+        [fc_query_path, "-f", format_string, font_file],
+        stdout=subprocess.PIPE,
+        env=env,
+        stderr=subprocess.PIPE,
+        text=True,
+        check=False,
+    )
+
+    assert (
+        result.returncode == 0
+    ), f"fc-query failed for {font_file} with error: {result.stderr}"
+    assert result.stdout, f"fc-query produced no output for {font_file}"
+
+    return result
+
+
+ at pytest.mark.parametrize("font_file", list_test_fonts())
+def test_fontations_freetype_fcquery_equal(font_file):
+    print(f"Testing with: {font_file}")  # Example usage
+
+    supported_format_entitites = [
+        "family[0]",
+        "familylang[0]",
+        "outline",
+        "scalable",
+        "fontformat",
+        "color",
+        "fonthashint",
+    ]
+    format_string = ":".join(
+        "%{" + entity + "}" for entity in supported_format_entitites
+    )
+    print(format_string)
+
+    font_path = Path(font_file)
+
+    if not font_path.exists():
+        pytest.skip(f"Font file not found: {font_file}")  # Skip if file missing
+
+    result_freetype = run_fc_query(font_file, format_string).stdout.strip().splitlines()
+    result_fontations = (
+        run_fc_query(font_file, format_string, with_fontations=True)
+        .stdout.strip()
+        .splitlines()
+    )
+
+    assert (
+        result_freetype == result_fontations
+    ), f"FreeType and Fontations fc-query result must match. Fontations: {result_fontations}, FreeType: {result_freetype}"


More information about the Fontconfig mailing list