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