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@xxxxxxxxx> 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@xxxxxxxxxxxx> 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. + */ + +@SYNOPSIS@ +#include <fontconfig.h> +#include <fcfontations.h> +@RET@ unsigned int +@FUNC@ FcFontationsQueryAll +@TYPE1@ const FcChar8 * @ARG1@ file +@TYPE2@ int% @ARG2@ id +@TYPE3@ FcBlanks * @ARG3@ blanks +@TYPE4@ int * @ARG4@ count +@TYPE5@ FcFontSet * @ARG5@ set +@PURPOSE@ compute all patterns from font file (and index) +@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. +@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 + + +@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}"