fontconfig: Branch 'main' - 3 commits
GitLab Mirror
gitlab-mirror at kemper.freedesktop.org
Thu May 8 09:40:39 UTC 2025
doc/fcfontations.fncs | 2
fc-fontations-bindgen/build.rs | 3
fc-fontations/attributes.rs | 173 +++++++++++++++++++++++++--
fc-fontations/instance_enumerate.rs | 164 +++++++++++++++++++++++++
fc-fontations/mod.rs | 92 +++++++-------
fc-fontations/names.rs | 97 ++++++++++++++-
fc-fontations/pattern_bindings/fc_wrapper.rs | 13 +-
fc-fontations/pattern_bindings/mod.rs | 16 +-
test/meson.build | 2
test/test_fontations_ft_query.py | 6
10 files changed, 494 insertions(+), 74 deletions(-)
New commits:
commit 3454d29ef0761978897de5f28e388e5b0a3489f4
Merge: 4e6cdf7 bd37dd6
Author: Akira TAGOH <akira at tagoh.org>
Date: Thu May 8 09:40:03 2025 +0000
Merge branch 'instances' into 'main'
[Fontations] Process variable instances
See merge request fontconfig/fontconfig!399
commit bd37dd6150b157329c240bcee07ef88242ebe29e
Author: Dominik Röttsches <drott at chromium.org>
Date: Wed Apr 30 12:54:51 2025 +0300
[Fontations] Iterate over TrueType collections and named instances
* Create an iterator for generating instance modes for variable fonts
* Refactor attributes assignment to take into account variable axes
* Refactor names assignment to take into account instance names
* Enable Roboto Flex pattern comparison test
diff --git a/doc/fcfontations.fncs b/doc/fcfontations.fncs
index 9b3b5a5..12328cd 100644
--- a/doc/fcfontations.fncs
+++ b/doc/fcfontations.fncs
@@ -35,7 +35,7 @@
@PURPOSE@ compute all patterns from font file (and index)
@DESC@
UNSTABLE feature. Constructs patterns found in 'file'. Currently pattern is incomplete
-compared to FcFreeTypeQueryAll().
+compared to FcFreeTypeQueryAll() and may deviate from what FreeType indexing produces.
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().
diff --git a/fc-fontations/attributes.rs b/fc-fontations/attributes.rs
index e0ee28c..41c853b 100644
--- a/fc-fontations/attributes.rs
+++ b/fc-fontations/attributes.rs
@@ -2,8 +2,8 @@ extern crate fc_fontations_bindgen;
use fc_fontations_bindgen::{
fcint::{
- FC_INDEX_OBJECT, FC_NAMED_INSTANCE_OBJECT, FC_SLANT_OBJECT, FC_VARIABLE_OBJECT,
- FC_WEIGHT_OBJECT, FC_WIDTH_OBJECT,
+ FC_INDEX_OBJECT, FC_NAMED_INSTANCE_OBJECT, FC_SIZE_OBJECT, FC_SLANT_OBJECT,
+ FC_VARIABLE_OBJECT, FC_WEIGHT_OBJECT, FC_WIDTH_OBJECT,
},
FcWeightFromOpenTypeDouble, FC_SLANT_ITALIC, FC_SLANT_OBLIQUE, FC_SLANT_ROMAN, FC_WEIGHT_BLACK,
FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABOLD, FC_WEIGHT_EXTRALIGHT, FC_WEIGHT_LIGHT, FC_WEIGHT_MEDIUM,
@@ -13,13 +13,13 @@ use fc_fontations_bindgen::{
};
use crate::{
- pattern_bindings::{FcPatternBuilder, PatternElement},
+ pattern_bindings::{fc_wrapper::FcRangeWrapper, FcPatternBuilder, PatternElement},
InstanceMode,
};
use read_fonts::TableProvider;
use skrifa::{
attribute::{Attributes, Stretch, Style, Weight},
- FontRef,
+ AxisCollection, FontRef, MetadataProvider, NamedInstance, Tag,
};
fn fc_weight(skrifa_weight: Weight) -> f64 {
@@ -92,21 +92,38 @@ fn fc_width_from_os2(font_ref: &FontRef) -> Option<f64> {
Some(converted as f64)
}
-struct AttributesToPattern {
+struct AttributesToPattern<'a> {
weight_from_os2: Option<f64>,
width_from_os2: Option<f64>,
attributes: Attributes,
+ axes: AxisCollection<'a>,
+ named_instance: Option<NamedInstance<'a>>,
}
-impl AttributesToPattern {
- fn new(font: &FontRef) -> Self {
+impl<'a> AttributesToPattern<'a> {
+ fn new(font: &'a FontRef, instance_mode: &InstanceMode) -> Self {
+ let named_instance = match instance_mode {
+ InstanceMode::Named(index) => font.named_instances().get(*index as usize),
+ _ => None,
+ };
Self {
weight_from_os2: fc_weight_from_os2(font),
width_from_os2: fc_width_from_os2(font),
attributes: Attributes::new(font),
+ axes: font.axes(),
+ named_instance,
}
}
+ fn user_coord_for_tag(&self, tag: Tag) -> Option<f64> {
+ let mut axis_coords = self
+ .axes
+ .iter()
+ .map(|axis| axis.tag())
+ .zip(self.named_instance.clone()?.user_coords());
+ Some(axis_coords.find(|item| item.0 == tag)?.1 as f64)
+ }
+
fn static_weight(&self) -> PatternElement {
self.weight_from_os2.map_or_else(
|| {
@@ -137,6 +154,89 @@ impl AttributesToPattern {
(fc_slant(self.attributes.style) as i32).into(),
)
}
+
+ fn instance_weight(&self) -> Option<PatternElement> {
+ let named_instance_weight = self.user_coord_for_tag(Tag::new(b"wght"))?;
+ unsafe {
+ Some(PatternElement::new(
+ FC_WEIGHT_OBJECT as i32,
+ FcWeightFromOpenTypeDouble(named_instance_weight).into(),
+ ))
+ }
+ }
+
+ fn instance_width(&self) -> Option<PatternElement> {
+ let named_instance_weight = self.user_coord_for_tag(Tag::new(b"wdth"))?;
+
+ Some(PatternElement::new(
+ FC_WIDTH_OBJECT as i32,
+ named_instance_weight.into(),
+ ))
+ }
+
+ fn instance_slant(&self) -> Option<PatternElement> {
+ let named_instance_slant = self.user_coord_for_tag(Tag::new(b"slnt"))?;
+ if named_instance_slant < 0.0 {
+ Some(PatternElement::new(
+ FC_SLANT_OBJECT as i32,
+ (FC_SLANT_ITALIC as i32).into(),
+ ))
+ } else {
+ Some(PatternElement::new(
+ FC_SLANT_OBJECT as i32,
+ (FC_SLANT_ROMAN as i32).into(),
+ ))
+ }
+ }
+
+ fn instance_size(&self) -> Option<PatternElement> {
+ let named_instance_size = self.user_coord_for_tag(Tag::new(b"opsz"))?;
+
+ Some(PatternElement::new(
+ FC_SIZE_OBJECT as i32,
+ named_instance_size.into(),
+ ))
+ }
+
+ fn default_size(&self) -> Option<PatternElement> {
+ self.axes.get_by_tag(Tag::new(b"opsz")).map(|opsz_axis| {
+ PatternElement::new(
+ FC_SIZE_OBJECT as i32,
+ (opsz_axis.default_value() as f64).into(),
+ )
+ })
+ }
+
+ fn variable_weight(&self) -> Option<PatternElement> {
+ let weight_axis = self.axes.get_by_tag(Tag::new(b"wght"))?;
+ unsafe {
+ Some(PatternElement::new(
+ FC_WEIGHT_OBJECT as i32,
+ FcRangeWrapper::new(
+ FcWeightFromOpenTypeDouble(weight_axis.min_value() as f64),
+ FcWeightFromOpenTypeDouble(weight_axis.max_value() as f64),
+ )?
+ .into(),
+ ))
+ }
+ }
+
+ fn variable_width(&self) -> Option<PatternElement> {
+ let width_axis = self.axes.get_by_tag(Tag::new(b"wdth"))?;
+ Some(PatternElement::new(
+ FC_WIDTH_OBJECT as i32,
+ FcRangeWrapper::new(width_axis.min_value() as f64, width_axis.max_value() as f64)?
+ .into(),
+ ))
+ }
+
+ fn variable_opsz(&self) -> Option<PatternElement> {
+ let opsz_axis = self.axes.get_by_tag(Tag::new(b"opsz"))?;
+ Some(PatternElement::new(
+ FC_SIZE_OBJECT as i32,
+ FcRangeWrapper::new(opsz_axis.min_value() as f64, opsz_axis.max_value() as f64)?.into(),
+ ))
+ }
}
pub fn append_style_elements(
@@ -149,7 +249,7 @@ pub fn append_style_elements(
// but falls back to flags if those are not found. So far, I haven't identified test fonts
// for which the WWS code path would trigger.
- let attributes_converter = AttributesToPattern::new(font);
+ let attributes_converter = AttributesToPattern::new(font, &instance_mode);
match instance_mode {
InstanceMode::Default => {
@@ -168,9 +268,60 @@ pub fn append_style_elements(
false.into(),
));
}
- _ => {
- // TODO: Variable and named instances not implemented yet.
- unreachable!()
+ InstanceMode::Variable => {
+ if let Some(weight_to_add) = attributes_converter
+ .variable_weight()
+ .or(Some(attributes_converter.static_weight()))
+ {
+ pattern.append_element(weight_to_add);
+ }
+ if let Some(width_to_add) = attributes_converter
+ .variable_width()
+ .or(Some(attributes_converter.static_width()))
+ {
+ pattern.append_element(width_to_add);
+ }
+ if let Some(size) = attributes_converter.variable_opsz() {
+ pattern.append_element(size);
+ }
+ pattern.append_element(PatternElement::new(FC_VARIABLE_OBJECT as i32, true.into()));
+
+ // TODO: Check if this should have a zero ttc index if not part of a collection.
+ pattern.append_element(PatternElement::new(
+ FC_INDEX_OBJECT as i32,
+ ttc_index.unwrap_or_default().into(),
+ ));
+ pattern.append_element(PatternElement::new(
+ FC_NAMED_INSTANCE_OBJECT as i32,
+ false.into(),
+ ));
+ pattern.append_element(attributes_converter.static_slant());
+ }
+ InstanceMode::Named(index) => {
+ if let Some(weight) = attributes_converter.instance_weight() {
+ pattern.append_element(weight);
+ }
+ if let Some(width) = attributes_converter.instance_width() {
+ pattern.append_element(width);
+ }
+ pattern.append_element(PatternElement::new(FC_VARIABLE_OBJECT as i32, false.into()));
+ pattern.append_element(PatternElement::new(
+ FC_INDEX_OBJECT as i32,
+ (ttc_index.unwrap_or_default() + ((index + 1) << 16)).into(),
+ ));
+ if let Some(size_element) = attributes_converter
+ .instance_size()
+ .or(attributes_converter.default_size())
+ {
+ pattern.append_element(size_element);
+ };
+ if let Some(slant_element) = attributes_converter.instance_slant() {
+ pattern.append_element(slant_element);
+ }
+ pattern.append_element(PatternElement::new(
+ FC_NAMED_INSTANCE_OBJECT as i32,
+ true.into(),
+ ));
}
}
}
diff --git a/fc-fontations/instance_enumerate.rs b/fc-fontations/instance_enumerate.rs
new file mode 100644
index 0000000..6f13bf9
--- /dev/null
+++ b/fc-fontations/instance_enumerate.rs
@@ -0,0 +1,164 @@
+/*
+ * 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 crate::InstanceMode::{self, Named};
+use read_fonts::{
+ FileRef::{self, Collection, Font},
+ FontRef,
+};
+use skrifa::MetadataProvider;
+
+#[allow(unused)]
+pub 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
+ })
+}
+
+// Produces an iterator over the named instances of the font.
+#[allow(unused)]
+pub fn enumerate_named_instances<'a>(f: &'a FontRef) -> impl Iterator<Item = InstanceMode> + 'a {
+ let default_coords = f.axes().iter().map(|axis| axis.default_value());
+ f.named_instances()
+ .iter()
+ .enumerate()
+ .filter_map(move |(i, instance)| {
+ let user_coords = instance.user_coords();
+ if user_coords.eq(default_coords.clone()) {
+ None
+ } else {
+ Some(Named(i as i32))
+ }
+ })
+}
+
+/// Used for producing the expected set of patterns out of variable fonts.
+/// Produces an iterator over
+/// * the default instance,
+/// * the named instances, and the
+/// * variable instance.
+#[allow(unused)]
+pub fn all_instances<'a>(f: &'a FontRef) -> Box<dyn Iterator<Item = InstanceMode> + 'a> {
+ if f.axes().is_empty() {
+ Box::new(std::iter::once(InstanceMode::Default))
+ } else {
+ Box::new(
+ std::iter::once(InstanceMode::Default)
+ .chain(enumerate_named_instances(f))
+ .chain(std::iter::once(InstanceMode::Variable)),
+ )
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{
+ instance_enumerate::{all_instances, fonts_and_indices},
+ InstanceMode,
+ };
+
+ use read_fonts::FileRef;
+ use std::path::PathBuf;
+
+ fn testfontdir() -> PathBuf {
+ let default_build_dir = std::env::current_dir().unwrap().join("build");
+ let build_dir = std::env::var("builddir").unwrap_or_else(|_| {
+ println!("builddir env var not set, using current directory + \"build/\"");
+ default_build_dir.to_string_lossy().to_string()
+ });
+ PathBuf::from(build_dir)
+ .join("testfonts")
+ .join("roboto-flex-fonts")
+ .join("fonts")
+ .join("variable")
+ }
+
+ fn variable_collection_testfont() -> PathBuf {
+ testfontdir().join(
+ "RobotoFlex[GRAD,XOPQ,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght].ttf",
+ )
+ }
+
+ const EXPECTED_INSTANCES: [InstanceMode; 21] = [
+ InstanceMode::Default,
+ InstanceMode::Named(0),
+ InstanceMode::Named(1),
+ InstanceMode::Named(2),
+ InstanceMode::Named(4),
+ InstanceMode::Named(5),
+ InstanceMode::Named(6),
+ InstanceMode::Named(7),
+ InstanceMode::Named(8),
+ InstanceMode::Named(9),
+ InstanceMode::Named(10),
+ InstanceMode::Named(11),
+ InstanceMode::Named(12),
+ InstanceMode::Named(13),
+ InstanceMode::Named(14),
+ InstanceMode::Named(15),
+ InstanceMode::Named(16),
+ InstanceMode::Named(17),
+ InstanceMode::Named(18),
+ InstanceMode::Named(19),
+ InstanceMode::Variable,
+ ];
+
+ #[test]
+ fn test_instance_iterator() {
+ let bytes = std::fs::read(variable_collection_testfont())
+ .ok()
+ .unwrap_or_default();
+ let fileref = FileRef::new(&bytes).ok();
+ let fonts = fonts_and_indices(fileref);
+ for (font, _ttc_index) in fonts {
+ all_instances(&font)
+ .zip(EXPECTED_INSTANCES.iter())
+ .for_each(|(instance, expected_instance)| {
+ assert_eq!(instance, *expected_instance);
+ })
+ }
+ }
+}
diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs
index 1c4780d..3f43266 100644
--- a/fc-fontations/mod.rs
+++ b/fc-fontations/mod.rs
@@ -24,6 +24,7 @@
mod attributes;
mod foundries;
+mod instance_enumerate;
mod names;
mod pattern_bindings;
@@ -33,8 +34,8 @@ use names::add_names;
use fc_fontations_bindgen::{
fcint::{
- FC_COLOR_OBJECT, FC_FONTFORMAT_OBJECT, FC_FONTVERSION_OBJECT, FC_FONT_HAS_HINT_OBJECT,
- FC_FOUNDRY_OBJECT, FC_OUTLINE_OBJECT, FC_SCALABLE_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,
};
@@ -43,10 +44,7 @@ use font_types::Tag;
use pattern_bindings::{FcPatternBuilder, PatternElement};
use std::str::FromStr;
-use read_fonts::{
- FileRef::{self, Collection, Font},
- FontRef, TableProvider,
-};
+use read_fonts::{FileRef, FontRef, TableProvider};
use std::{
ffi::{CStr, CString, OsStr},
@@ -54,6 +52,8 @@ use std::{
os::unix::ffi::OsStrExt,
};
+use instance_enumerate::{all_instances, fonts_and_indices};
+
#[no_mangle]
/// Externally called in fcfontations.c as the file scanner function
/// similar to the job that FreeType performs.
@@ -86,7 +86,7 @@ pub unsafe extern "C" fn add_patterns_to_fontset(
///
/// We add one pattern for the default instance, one for each named instance,
/// and one for using the font as a variable font, with ranges of values where applicable.
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(dead_code)]
enum InstanceMode {
Default,
@@ -94,34 +94,6 @@ enum InstanceMode {
Variable,
}
-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,
@@ -154,8 +126,6 @@ fn build_patterns_for_font(
) -> 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"]);
@@ -214,14 +184,48 @@ fn build_patterns_for_font(
version.into(),
));
- // TODO: Handle variable instance and named instances.
- append_style_elements(font, InstanceMode::Default, ttc_index, &mut pattern);
+ // So far the pattern elements applied to te whole font file, in the below,
+ // clone the current pattern state and add instance specific
+ // attributes. FontConfig for variable fonts produces a pattern for the
+ // default instance, each named instance, and a separate one for the
+ // "variable instance", which may contain ranges for pattern elements that
+ // describe variable aspects, such as weight of the font.
+ all_instances(font)
+ .flat_map(move |instance_mode| {
+ let mut instance_pattern = pattern.clone();
+
+ // Style names: fcfreetype adds TT_NAME_ID_WWS_SUBFAMILY, TT_NAME_ID_TYPOGRAPHIC_SUBFAMILY,
+ // TT_NAME_ID_FONT_SUBFAMILY as FC_STYLE_OBJECT, FC_STYLE_OBJECT_LANG unless a named instance
+ // is added,then the instance's name id is used as FC_STYLE_OBJECT.
+
+ append_style_elements(font, instance_mode, ttc_index, &mut instance_pattern);
+
+ // For variable fonts:
+ // Names (mainly postscript name and style), weight, width and opsz (font-size?) are affected.
+ // * Add the variable font itself, with ranges for weight, width, opsz.
+ // * Add an entry for each named instance
+ // * With instance name turning into FC_STYLE_OBJECT.
+ // * Fixed width, wgth, opsz
+ // * Add the default instance with fixed values.
+ let mut had_decoratve = false;
+ // Family and full name.
+ add_names(
+ font,
+ instance_mode,
+ &mut instance_pattern,
+ &mut had_decoratve,
+ );
+
+ instance_pattern.append_element(PatternElement::new(
+ FC_DECORATIVE_OBJECT as i32,
+ had_decoratve.into(),
+ ));
- pattern
- .create_fc_pattern()
- .map(|p| p.into_raw() as *mut FcPattern)
- .into_iter()
- .collect()
+ instance_pattern
+ .create_fc_pattern()
+ .map(|wrapper| wrapper.into_raw() as *mut FcPattern)
+ })
+ .collect::<Vec<_>>()
}
#[cfg(test)]
diff --git a/fc-fontations/names.rs b/fc-fontations/names.rs
index b4464b4..80fb980 100644
--- a/fc-fontations/names.rs
+++ b/fc-fontations/names.rs
@@ -25,10 +25,11 @@
use skrifa::{string::StringId, MetadataProvider};
use fc_fontations_bindgen::fcint::{
- FC_FAMILYLANG_OBJECT, FC_FAMILY_OBJECT, FC_INVALID_OBJECT, FC_POSTSCRIPT_NAME_OBJECT,
+ FC_FAMILYLANG_OBJECT, FC_FAMILY_OBJECT, FC_FULLNAMELANG_OBJECT, FC_FULLNAME_OBJECT,
+ FC_INVALID_OBJECT, FC_POSTSCRIPT_NAME_OBJECT, FC_STYLELANG_OBJECT, FC_STYLE_OBJECT,
};
-use crate::{FcPatternBuilder, PatternElement};
+use crate::{FcPatternBuilder, InstanceMode, PatternElement};
use read_fonts::FontRef;
use std::ffi::CString;
@@ -39,7 +40,14 @@ fn objects_for_id(string_id: StringId) -> (i32, i32) {
StringId::FAMILY_NAME | StringId::WWS_FAMILY_NAME | StringId::TYPOGRAPHIC_FAMILY_NAME => {
(FC_FAMILY_OBJECT as i32, FC_FAMILYLANG_OBJECT as i32)
}
+ StringId::FULL_NAME => (FC_FULLNAME_OBJECT as i32, FC_FULLNAMELANG_OBJECT as i32),
StringId::POSTSCRIPT_NAME => (FC_POSTSCRIPT_NAME_OBJECT as i32, FC_INVALID_OBJECT as i32),
+ StringId::SUBFAMILY_NAME
+ | StringId::WWS_SUBFAMILY_NAME
+ | StringId::TYPOGRAPHIC_SUBFAMILY_NAME => {
+ (FC_STYLE_OBJECT as i32, FC_STYLELANG_OBJECT as i32)
+ }
+
_ => panic!("No equivalent FontConfig objects found for StringId."),
}
}
@@ -52,12 +60,79 @@ fn normalize_name(name: &CString) -> String {
.replace(' ', "")
}
-pub fn add_names(font: &FontRef, pattern: &mut FcPatternBuilder) {
+fn mangle_postscript_name_for_named_instance(
+ font: &FontRef,
+ named_instance_id: i32,
+) -> Option<CString> {
+ let instance_ps_name_id = font
+ .named_instances()
+ .get(named_instance_id as usize)?
+ .postscript_name_id()?;
+ let ps_name = font
+ .localized_strings(instance_ps_name_id)
+ .english_or_first()?
+ .clone()
+ .to_string();
+ CString::new(ps_name).ok()
+}
+
+fn mangle_subfamily_name_for_named_instance(
+ font: &FontRef,
+ named_instance_id: i32,
+) -> Option<CString> {
+ let instance_subfamily_name_id = font
+ .named_instances()
+ .get(named_instance_id as usize)?
+ .subfamily_name_id();
+ let subfamily = font
+ .localized_strings(instance_subfamily_name_id)
+ .english_or_first()?
+ .clone()
+ .to_string();
+ CString::new(subfamily).ok()
+}
+
+fn mangle_full_name_for_named_instance(font: &FontRef, named_instance_id: i32) -> Option<CString> {
+ let instance_subfamily_name_id = font
+ .named_instances()
+ .get(named_instance_id as usize)?
+ .subfamily_name_id();
+ let full_name = font
+ .localized_strings(StringId::FAMILY_NAME)
+ .english_or_first()?
+ .to_string()
+ + " ";
+ let subfam = font
+ .localized_strings(instance_subfamily_name_id)
+ .english_or_first()?
+ .to_string();
+
+ CString::new(full_name + &subfam).ok()
+}
+
+fn determine_decorative(object_id: i32, name: &Option<CString>) -> bool {
+ object_id == FC_STYLE_OBJECT as i32
+ && name
+ .as_ref()
+ .is_some_and(|name| name.to_string_lossy().to_lowercase().contains("decorative"))
+}
+
+pub fn add_names(
+ font: &FontRef,
+ instance_mode: InstanceMode,
+ pattern: &mut FcPatternBuilder,
+ had_decorative: &mut bool,
+) {
// 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::WWS_FAMILY_NAME,
StringId::TYPOGRAPHIC_FAMILY_NAME,
StringId::FAMILY_NAME,
+ StringId::FULL_NAME,
StringId::POSTSCRIPT_NAME,
+ StringId::TYPOGRAPHIC_SUBFAMILY_NAME,
+ StringId::SUBFAMILY_NAME,
+ StringId::WWS_SUBFAMILY_NAME,
];
let mut already_encountered_names: HashSet<(i32, String)> = HashSet::new();
@@ -78,6 +153,22 @@ pub fn add_names(font: &FontRef, pattern: &mut FcPatternBuilder) {
CString::new(lang).ok()
});
+ // Instance postscript name.
+ let name = match (instance_mode, string_id) {
+ (InstanceMode::Named(instance), &StringId::POSTSCRIPT_NAME) => {
+ mangle_postscript_name_for_named_instance(font, instance).or(name)
+ }
+ (InstanceMode::Named(instance), &StringId::SUBFAMILY_NAME) => {
+ mangle_subfamily_name_for_named_instance(font, instance).or(name)
+ }
+ (InstanceMode::Named(instance), &StringId::FULL_NAME) => {
+ mangle_full_name_for_named_instance(font, instance).or(name)
+ }
+ _ => name,
+ };
+
+ *had_decorative = determine_decorative(object_ids.0, &name);
+
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())) {
diff --git a/fc-fontations/pattern_bindings/fc_wrapper.rs b/fc-fontations/pattern_bindings/fc_wrapper.rs
index 125b172..ccc3269 100644
--- a/fc-fontations/pattern_bindings/fc_wrapper.rs
+++ b/fc-fontations/pattern_bindings/fc_wrapper.rs
@@ -24,10 +24,11 @@
use fc_fontations_bindgen::{
fcint::{
- FcCharSet, FcCharSetCreate, FcCharSetDestroy, FcLangSet, FcPattern, FcPatternCreate,
- FcPatternDestroy, FcRange, FcRangeCopy, FcRangeCreateDouble, FcRangeDestroy,
+ FcCharSet, FcCharSetCopy, FcCharSetCreate, FcCharSetDestroy, FcLangSet, FcPattern,
+ FcPatternCreate, FcPatternDestroy, FcRange, FcRangeCopy, FcRangeCreateDouble,
+ FcRangeDestroy,
},
- FcLangSetCreate, FcLangSetDestroy,
+ FcLangSetCopy, FcLangSetCreate, FcLangSetDestroy,
};
macro_rules! wrap_fc_object {
@@ -128,7 +129,8 @@ impl FcPatternWrapper {
wrap_fc_object! {
FcCharSetWrapper,
FcCharSet,
- FcCharSetDestroy
+ FcCharSetDestroy,
+ FcCharSetCopy
}
impl FcCharSetWrapper {
@@ -151,7 +153,8 @@ impl FcCharSetWrapper {
wrap_fc_object! {
FcLangSetWrapper,
FcLangSet,
- FcLangSetDestroy
+ FcLangSetDestroy,
+ FcLangSetCopy
}
impl FcLangSetWrapper {
diff --git a/fc-fontations/pattern_bindings/mod.rs b/fc-fontations/pattern_bindings/mod.rs
index 24f7158..32f604a 100644
--- a/fc-fontations/pattern_bindings/mod.rs
+++ b/fc-fontations/pattern_bindings/mod.rs
@@ -24,7 +24,7 @@
extern crate fc_fontations_bindgen;
-mod fc_wrapper;
+pub mod fc_wrapper;
use std::ffi::CString;
use std::fmt::Debug;
@@ -35,10 +35,10 @@ use fc_fontations_bindgen::fcint::{
FcPatternObjectAddString, FC_FAMILY_OBJECT,
};
-use self::fc_wrapper::{FcCharSetWrapper, FcLangSetWrapper, FcPatternWrapper, FcRangeWrapper};
+use fc_wrapper::{FcCharSetWrapper, FcLangSetWrapper, FcPatternWrapper, FcRangeWrapper};
#[allow(unused)]
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum PatternValue {
String(CString),
Boolean(bool),
@@ -73,7 +73,13 @@ impl From<f64> for PatternValue {
}
}
-#[derive(Debug)]
+impl From<FcRangeWrapper> for PatternValue {
+ fn from(item: FcRangeWrapper) -> Self {
+ PatternValue::Range(item)
+ }
+}
+
+#[derive(Debug, Clone)]
pub struct PatternElement {
object_id: i32,
value: PatternValue,
@@ -127,7 +133,7 @@ impl PatternElement {
}
}
-#[derive(Default, Debug)]
+#[derive(Default, Debug, Clone)]
pub struct FcPatternBuilder {
elements: Vec<PatternElement>,
}
diff --git a/test/meson.build b/test/meson.build
index 36cf4fe..3a08da7 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -75,7 +75,7 @@ endforeach
if get_option('fontations').enabled()
rust = import('rust')
- rust.test('fc_fontations_rust_tests', fc_fontations, link_with: [libfontconfig_internal], depends: fetch_test_fonts)
+ rust.test('fc_fontations_rust_tests', fc_fontations, link_with: [libfontconfig_internal], depends: fetch_test_fonts, env: ['builddir=@0@'.format(meson.project_build_root())],)
endif
fs = import('fs')
diff --git a/test/test_fontations_ft_query.py b/test/test_fontations_ft_query.py
index 9a00e3e..37a2aab 100644
--- a/test/test_fontations_ft_query.py
+++ b/test/test_fontations_ft_query.py
@@ -19,7 +19,7 @@ def list_test_fonts():
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:
+ if file.endswith(".ttf"):
font_files.append(os.path.join(root, file))
return font_files
@@ -53,8 +53,8 @@ def test_fontations_freetype_fcquery_equal(font_file):
print(f"Testing with: {font_file}") # Example usage
supported_format_entitites = [
- "family[0]",
- "familylang[0]",
+ "family",
+ "familylang",
"outline",
"scalable",
"fontformat",
commit f0c0fbe16b0929c5a4e5ad4dea00842765cd0897
Author: Dominik Röttsches <drott at chromium.org>
Date: Wed Apr 30 12:53:25 2025 +0300
Cargo build improvements
diff --git a/fc-fontations-bindgen/build.rs b/fc-fontations-bindgen/build.rs
index 0591cf0..61877e4 100644
--- a/fc-fontations-bindgen/build.rs
+++ b/fc-fontations-bindgen/build.rs
@@ -11,6 +11,7 @@ fn main() {
meson.arg("setup")
.arg(build_dir.to_str().unwrap())
.arg("--reconfigure")
+ .arg("--default-library=static")
.arg("-Dfontations=enabled");
let status = meson.status().expect("Failed to execute meson");
@@ -27,7 +28,7 @@ fn main() {
}
// Tell cargo to look for fontconfig in the build directory
- println!("cargo:rustc-link-search=native={}", build_dir.join("src").display());
+ println!("cargo:rustc-link-search=native={}", build_dir.display());
println!("cargo:rustc-link-lib=static=fontconfig");
// FreeType and Expat from the system.
More information about the Fontconfig
mailing list