.gitlab-ci/build.sh | 12 + fc-fontations/mod.rs | 33 ++- fc-fontations/pattern_bindings/fc_wrapper.rs | 123 +++++++++++++ fc-fontations/pattern_bindings/mod.rs | 254 +++++++++++++++++++++++++++ 4 files changed, 417 insertions(+), 5 deletions(-) New commits: commit 31252db1e652d63ab34d3a63c22aea16dce23db4 Merge: 564a8a9 738eb8a Author: Akira TAGOH <akira@xxxxxxxxx> Date: Mon Mar 31 12:05:49 2025 +0000 Merge branch 'patternBindings' into 'main' [Fontations] Add internal PatternBuilder abstraction See merge request fontconfig/fontconfig!371 commit 738eb8a73ab05301163479ddab7ceebab88907bb Author: Dominik Röttsches <drott@xxxxxxxxxxxx> Date: Tue Mar 18 12:33:09 2025 +0200 [Fontations] Add internal PatternBuilder abstraction Rust abstraction for creating FcPatterns. Implements Drop methods for handling memory of destroying FontConfig-side allocated objects where needed for complex pattern objects. Used in the Fontations backend, not exposed as external API. Changelog: added diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs index b3a82a6..3929924 100644 --- a/fc-fontations/mod.rs +++ b/fc-fontations/mod.rs @@ -1,5 +1,31 @@ +/* + * 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. + */ + extern crate fc_fontations_bindgen; +mod pattern_bindings; + use fc_fontations_bindgen::{ fcint::{FcPatternCreate, FcPatternObjectAddBool, FC_COLOR_OBJECT}, FcFontSet, FcFontSetAdd, diff --git a/fc-fontations/pattern_bindings/fc_wrapper.rs b/fc-fontations/pattern_bindings/fc_wrapper.rs new file mode 100644 index 0000000..e3ad063 --- /dev/null +++ b/fc-fontations/pattern_bindings/fc_wrapper.rs @@ -0,0 +1,123 @@ +/* + * fontconfig/fc-fontations/pattern_bindings/fc_wrapper.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 fc_fontations_bindgen::fcint::{ + FcPattern, FcPatternCreate, FcPatternDestroy, FcRange, FcRangeCopy, FcRangeCreateDouble, + FcRangeDestroy, +}; + +macro_rules! wrap_fc_object { + ( + $wrapper_name:ident, + $wrapped_type:ident, + $destroy_fn:ident + $(, $copy_fn:ident)? + ) => { + #[allow(unused)] + #[derive(Debug)] + pub struct $wrapper_name { + inner: *mut $wrapped_type, + } + + impl $wrapper_name { + #[allow(unused)] + pub fn from_raw(ptr: *mut $wrapped_type) -> Self { + Self { inner: ptr } + } + + #[allow(unused)] + pub fn into_raw(self) -> *mut $wrapped_type { + let ptr = self.inner; + std::mem::forget(self); + ptr + } + + #[allow(unused)] + pub fn as_ptr(&self) -> *mut $wrapped_type { + assert!(!self.inner.is_null()); + self.inner + } + } + + impl Drop for $wrapper_name { + fn drop(&mut self) { + unsafe { + $destroy_fn(self.inner); + } + } + } + + $( + impl Clone for $wrapper_name { + fn clone(&self) -> Self { + Self { + inner: unsafe { $copy_fn(self.inner) }, + } + } + } + )? + }; +} + +wrap_fc_object! { + FcRangeWrapper, + FcRange, + FcRangeDestroy, + FcRangeCopy +} + +impl FcRangeWrapper { + #[allow(unused)] + pub fn new(min: f64, max: f64) -> Option<Self> { + unsafe { + let ptr = FcRangeCreateDouble(min, max); + if ptr.is_null() { + None + } else { + Some(Self { inner: ptr }) + } + } + } +} + +wrap_fc_object! { + FcPatternWrapper, + FcPattern, + FcPatternDestroy +} + +impl FcPatternWrapper { + #[allow(unused)] + pub fn new() -> Option<Self> { + // Corrected new function for FcPattern + unsafe { + let ptr = FcPatternCreate(); + if ptr.is_null() { + None + } else { + Some(Self { inner: ptr }) + } + } + } +} diff --git a/fc-fontations/pattern_bindings/mod.rs b/fc-fontations/pattern_bindings/mod.rs new file mode 100644 index 0000000..0174487 --- /dev/null +++ b/fc-fontations/pattern_bindings/mod.rs @@ -0,0 +1,254 @@ +/* + * fontconfig/fc-fontations/pattern_bindings/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. + */ + + extern crate fc_fontations_bindgen; + +mod fc_wrapper; + +use std::ffi::CString; +use std::fmt::Debug; + +use fc_fontations_bindgen::fcint::{ + FcPattern, FcPatternObjectAddBool, FcPatternObjectAddDouble, FcPatternObjectAddInteger, + FcPatternObjectAddRange, FcPatternObjectAddString, FC_FAMILY_OBJECT, +}; + +use self::fc_wrapper::{FcPatternWrapper, FcRangeWrapper}; + +#[allow(unused)] +#[derive(Debug)] +pub enum PatternValue { + String(CString), + Boolean(bool), + Integer(i32), + Double(f64), + Range(FcRangeWrapper), +} + +#[derive(Debug)] +pub struct PatternElement { + object_id: i32, + value: PatternValue, +} + +impl PatternElement { + #[allow(unused)] + fn new(object_id: i32, value: PatternValue) -> Self { + Self { object_id, value } + } +} + +#[derive(Debug, Clone)] +struct PatternAddError; + +impl std::fmt::Display for PatternAddError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Failed to add object to Fontconfig pattern.") + } +} + +impl PatternElement { + fn append_to_fc_pattern(self, pattern: *mut FcPattern) -> Result<(), PatternAddError> { + let pattern_add_success = match self.value { + PatternValue::String(string) => unsafe { + FcPatternObjectAddString(pattern, self.object_id, string.as_ptr() as *const u8) + }, + PatternValue::Boolean(value) => unsafe { + FcPatternObjectAddBool(pattern, self.object_id, value as i32) + }, + PatternValue::Integer(value) => unsafe { + FcPatternObjectAddInteger(pattern, self.object_id, value) + }, + PatternValue::Double(value) => unsafe { + FcPatternObjectAddDouble(pattern, self.object_id, value) + }, + PatternValue::Range(value) => unsafe { + FcPatternObjectAddRange(pattern, self.object_id, value.into_raw()) + }, + } == 1; + if pattern_add_success { + return Ok(()); + } + Err(PatternAddError) + } +} + +#[derive(Default, Debug)] +pub struct FcPatternBuilder { + elements: Vec<PatternElement>, +} + +impl FcPatternBuilder { + #[allow(unused)] + pub fn new() -> Self { + Self::default() + } + + #[allow(unused)] + pub fn append_element(&mut self, element: PatternElement) { + self.elements.push(element); + } + + #[allow(unused)] + pub fn create_fc_pattern(&mut self) -> Option<FcPatternWrapper> { + let pattern = FcPatternWrapper::new()?; + + let mut family_name_encountered = false; + + const FAMILY_ID: i32 = FC_FAMILY_OBJECT as i32; + for element in self.elements.drain(0..) { + if let PatternElement { + object_id: FAMILY_ID, + value: PatternValue::String(ref fam_name), + } = element + { + if !fam_name.is_empty() { + family_name_encountered = true; + } + } + element.append_to_fc_pattern(pattern.as_ptr()).ok()?; + } + + if !family_name_encountered { + return None; + } + + Some(pattern) + } +} + +#[cfg(test)] +mod test { + use std::ffi::CString; + + use super::{FcPatternBuilder, FcRangeWrapper, PatternElement, PatternValue}; + use fc_fontations_bindgen::fcint::{ + FcPatternObjectGetBool, FcPatternObjectGetDouble, FcPatternObjectGetInteger, + FcPatternObjectGetRange, FcPatternObjectGetString, FcRange, FC_COLOR_OBJECT, + FC_FAMILY_OBJECT, FC_SLANT_OBJECT, FC_WEIGHT_OBJECT, FC_WIDTH_OBJECT, + }; + + #[test] + fn verify_pattern_bindings() { + let mut pattern_builder = FcPatternBuilder::new(); + + // Add a bunch of test properties. + pattern_builder.append_element(PatternElement::new( + FC_COLOR_OBJECT as i32, + PatternValue::Boolean(true), + )); + pattern_builder.append_element(PatternElement::new( + FC_WEIGHT_OBJECT as i32, + PatternValue::Double(800.), + )); + pattern_builder.append_element(PatternElement::new( + FC_SLANT_OBJECT as i32, + PatternValue::Integer(15), + )); + + pattern_builder.append_element(PatternElement::new( + FC_WIDTH_OBJECT as i32, + PatternValue::Range(FcRangeWrapper::new(100., 400.).unwrap()), + )); + + pattern_builder.append_element(PatternElement::new( + FC_FAMILY_OBJECT as i32, + PatternValue::String(CString::new("TestFont").unwrap()), + )); + + let pattern = pattern_builder.create_fc_pattern().unwrap(); + + let fontconfig_pattern = pattern.as_ptr(); + unsafe { + // Verify color properties. + let mut result: i32 = 0; + let get_result = + FcPatternObjectGetBool(fontconfig_pattern, FC_COLOR_OBJECT as i32, 0, &mut result); + assert_eq!(get_result, 0); + assert_eq!(result, 1); + + // Verify weight value. + let mut weight_result: f64 = 0.; + let get_result = FcPatternObjectGetDouble( + fontconfig_pattern, + FC_WEIGHT_OBJECT as i32, + 0, + &mut weight_result, + ); + assert_eq!(get_result, 0); + assert_eq!(weight_result, 800.0); + + // Verify that weight is not a range. + let range_result: *mut *mut FcRange = std::mem::zeroed(); + assert_eq!( + FcPatternObjectGetRange( + fontconfig_pattern, + FC_WEIGHT_OBJECT as i32, + 0, + range_result + ), + 2 + ); + + // Verify slant. + let mut slant_result: i32 = 0; + let get_result = FcPatternObjectGetInteger( + fontconfig_pattern, + FC_SLANT_OBJECT as i32, + 0, + &mut slant_result, + ); + assert_eq!(get_result, 0); + assert_eq!(slant_result, 15); + + // Verify width. + let mut width_result: *mut FcRange = std::mem::zeroed(); + let get_result = FcPatternObjectGetRange( + fontconfig_pattern, + FC_WIDTH_OBJECT as i32, + 0, + &mut width_result, + ); + assert_eq!(get_result, 0); + assert_eq!((*width_result).begin, 100.); + assert_eq!((*width_result).end, 400.); + + // Verify family name. + let mut family_result: *mut u8 = std::mem::zeroed(); + let get_result = FcPatternObjectGetString( + fontconfig_pattern, + FC_FAMILY_OBJECT as i32, + 0, + &mut family_result, + ); + assert_eq!(get_result, 0); + assert_eq!( + std::ffi::CStr::from_ptr(family_result as *const i8) + .to_str() + .unwrap(), + "TestFont" + ); + } + } +} commit 5d8833ed65208c811c82d4855ce2c874684075a9 Author: Dominik Röttsches <drott@xxxxxxxxxxxx> Date: Mon Mar 24 11:00:51 2025 +0200 Upgrade bindgen in Fontations enabled Rust builds Fixes issues with Rust constant generation for FC_.*_OBJECT type constants. diff --git a/.gitlab-ci/build.sh b/.gitlab-ci/build.sh index 55a0722..db4accc 100755 --- a/.gitlab-ci/build.sh +++ b/.gitlab-ci/build.sh @@ -111,6 +111,18 @@ elif [ x"$buildsys" == "xmeson" ]; then pip install tomli for i in "${enable[@]}"; do buildopt+=(-D$i=enabled) + + # Update bindgen on Fontations builds to improve support for constants in fcint.h + if [[ "$i" == "fontations" ]]; then + cargo install bindgen-cli + # Prepend the cargo bin directory to PATH + if [[ -d "$HOME/.cargo/bin" ]]; then + export PATH="$HOME/.cargo/bin:$PATH" + echo "Cargo bin directory added to PATH." + else + echo "Cargo bin directory not found." + fi + fi done for i in "${disable[@]}"; do buildopt+=(-D$i=disabled) diff --git a/fc-fontations/mod.rs b/fc-fontations/mod.rs index 1a9be87..b3a82a6 100644 --- a/fc-fontations/mod.rs +++ b/fc-fontations/mod.rs @@ -1,7 +1,7 @@ extern crate fc_fontations_bindgen; use fc_fontations_bindgen::{ - fcint::{FcPatternCreate, FcPatternObjectAddBool}, + fcint::{FcPatternCreate, FcPatternObjectAddBool, FC_COLOR_OBJECT}, FcFontSet, FcFontSetAdd, }; @@ -20,10 +20,7 @@ pub unsafe extern "C" fn add_patterns_to_fontset( let empty_pattern = FcPatternCreate(); // Test call to ensure that an FcPrivate API function FcPatternObjectAddBool // is accessible and can be linked to. - // TODO(drott): This should be FC_COLOR_OBJECT imported from fcint.h, - // but there's a separate bindgen issue that needs to be sorted out. - const COLOR_OBJECT: i32 = 46; - FcPatternObjectAddBool(empty_pattern, COLOR_OBJECT, 0 as i32); + FcPatternObjectAddBool(empty_pattern, FC_COLOR_OBJECT as i32, 0_i32); if !font_set.is_null() { FcFontSetAdd( font_set,