blob: bb381cab76fc16583db2991ad11114a6b95595f4 [file]
/*
* 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.
*/
pub mod fc_wrapper;
use std::ffi::{CStr, CString};
use std::fmt::Debug;
use fcint_bindings::{
FcLangSet, FcPattern, FcPatternObjectAddBool, FcPatternObjectAddCharSet,
FcPatternObjectAddDouble, FcPatternObjectAddInteger, FcPatternObjectAddLangSet,
FcPatternObjectAddRange, FcPatternObjectAddString, FC_FAMILY_OBJECT, FC_FILE_OBJECT,
};
use fc_wrapper::{FcCharSetWrapper, FcLangSetWrapper, FcPatternWrapper, FcRangeWrapper};
#[allow(unused)]
#[derive(Debug, Clone)]
pub enum PatternValue {
String(CString),
Boolean(bool),
Integer(i32),
Double(f64),
Range(FcRangeWrapper),
LangSet(FcLangSetWrapper),
CharSet(FcCharSetWrapper),
}
impl From<CString> for PatternValue {
fn from(item: CString) -> Self {
PatternValue::String(item)
}
}
impl From<i32> for PatternValue {
fn from(item: i32) -> Self {
PatternValue::Integer(item)
}
}
impl From<bool> for PatternValue {
fn from(item: bool) -> Self {
PatternValue::Boolean(item)
}
}
impl From<f64> for PatternValue {
fn from(item: f64) -> Self {
PatternValue::Double(item)
}
}
impl From<FcRangeWrapper> for PatternValue {
fn from(item: FcRangeWrapper) -> Self {
PatternValue::Range(item)
}
}
impl From<FcCharSetWrapper> for PatternValue {
fn from(value: FcCharSetWrapper) -> Self {
PatternValue::CharSet(value)
}
}
impl From<FcLangSetWrapper> for PatternValue {
fn from(value: FcLangSetWrapper) -> Self {
PatternValue::LangSet(value)
}
}
#[derive(Debug, Clone)]
pub struct PatternElement {
pub object_id: i32,
pub value: PatternValue,
}
impl PatternElement {
#[allow(unused)]
pub 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.as_ptr())
},
PatternValue::CharSet(value) => unsafe {
FcPatternObjectAddCharSet(pattern, self.object_id, value.as_ptr())
},
PatternValue::LangSet(value) => unsafe {
FcPatternObjectAddLangSet(
pattern,
self.object_id,
value.as_ptr() as *const FcLangSet,
)
},
} == 1;
if pattern_add_success {
return Ok(());
}
Err(PatternAddError)
}
}
#[derive(Default, Debug, Clone)]
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);
}
pub fn family_names(&self) -> impl Iterator<Item = &CStr> {
self.elements.iter().filter_map(|element| {
if element.object_id == FC_FAMILY_OBJECT as i32 {
if let PatternValue::String(s) = &element.value {
if !s.is_empty() {
return Some(s.as_c_str());
}
}
}
None
})
}
#[allow(unused)]
pub fn create_fc_pattern(&mut self) -> Option<FcPatternWrapper> {
let pattern = FcPatternWrapper::new()?;
let mut family_name_or_file_name_encountered = false;
const FAMILY_ID: i32 = FC_FAMILY_OBJECT as i32;
const FILE_ID: i32 = FC_FILE_OBJECT as i32;
for element in self.elements.drain(0..) {
if let PatternElement {
object_id: FAMILY_ID | FILE_ID,
value: PatternValue::String(ref file_fam_name),
} = element
{
if !file_fam_name.is_empty() {
family_name_or_file_name_encountered = true;
}
}
element.append_to_fc_pattern(pattern.as_ptr()).ok()?;
}
if !family_name_or_file_name_encountered {
return None;
}
Some(pattern)
}
}
/// Mainly needed for finding the style PatternElement in attributes.rs.
impl<'a> IntoIterator for &'a FcPatternBuilder {
type Item = &'a PatternElement;
type IntoIter = std::slice::Iter<'a, PatternElement>;
fn into_iter(self) -> Self::IntoIter {
self.elements.iter()
}
}
#[cfg(test)]
mod test {
use std::ffi::CString;
use crate::pattern_bindings::fc_wrapper::FcLangSetWrapper;
use super::{
fc_wrapper::FcCharSetWrapper, FcPatternBuilder, FcRangeWrapper, PatternElement,
PatternValue,
};
use fontconfig_bindings::{
FcCharSet, FcCharSetAddChar, FcCharSetHasChar, FcLangSet, FcLangSetAdd, FcLangSetHasLang,
_FcLangResult_FcLangEqual,
};
use fcint_bindings::{
FcPatternObjectGetBool, FcPatternObjectGetCharSet, FcPatternObjectGetDouble,
FcPatternObjectGetInteger, FcPatternObjectGetLangSet, FcPatternObjectGetRange,
FcPatternObjectGetString, FcRange, FC_CHARSET_OBJECT, FC_COLOR_OBJECT, FC_FAMILY_OBJECT,
FC_LANG_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 test_charset = FcCharSetWrapper::new().unwrap();
const BIANG: u32 = 0x30EDE;
unsafe {
assert!(FcCharSetAddChar(test_charset.as_ptr(), BIANG) != 0);
}
pattern_builder.append_element(PatternElement::new(
FC_CHARSET_OBJECT as i32,
PatternValue::CharSet(test_charset),
));
let test_langset = FcLangSetWrapper::new().unwrap();
const LANG_EN: &[u8] = b"en\0";
unsafe {
assert!(FcLangSetAdd(test_langset.as_ptr(), LANG_EN.as_ptr()) != 0);
}
pattern_builder.append_element(PatternElement::new(
FC_LANG_OBJECT as i32,
PatternValue::LangSet(test_langset),
));
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"
);
// Verify CharSet.
let mut retrieved_charset: *mut FcCharSet = std::mem::zeroed();
let get_result = FcPatternObjectGetCharSet(
fontconfig_pattern,
FC_CHARSET_OBJECT as i32,
0,
&mut retrieved_charset,
);
assert_eq!(get_result, 0);
assert_eq!(FcCharSetHasChar(retrieved_charset, BIANG), 1);
// Verify LangSet.
let mut retrieved_langset: *mut FcLangSet = std::mem::zeroed();
let get_result = FcPatternObjectGetLangSet(
fontconfig_pattern,
FC_LANG_OBJECT as i32,
0,
&mut retrieved_langset,
);
assert_eq!(get_result, 0);
assert_eq!(
FcLangSetHasLang(retrieved_langset, LANG_EN.as_ptr()),
_FcLangResult_FcLangEqual
);
}
}
}