use std::ffi::{CStr, CString};
mod sys;
use sys::*;
use crate::{Fallback, Family, Stretch, Style, Weight, XFontType};
pub struct XFont {
config: *mut FcConfig,
fc_funs: FcFuns,
}
impl Default for XFont {
fn default() -> Self {
let fc_funs = unsafe { FcFuns::load() };
let config = if let Some(fc_init) = fc_funs.fc_init_load_config_and_fonts {
unsafe { fc_init() }
} else {
core::ptr::null_mut()
};
Self { config, fc_funs }
}
}
impl Drop for XFont {
fn drop(&mut self) {
if let Some(fc_config_destroy) = self.fc_funs.fc_config_destroy {
unsafe { fc_config_destroy(self.config) };
}
}
}
impl XFontType for XFont {
fn fallback(&self, family: &Family, text: &str) -> Fallback {
self.fallback_impl(text, &family.name, family.weight, family.stretch, family.style)
.unwrap_or_default()
}
fn system() -> String {
"sans-serif".to_string()
}
}
impl XFont {
fn fallback_impl(&self, text: &str, family_name: &str, weight: Weight, stretch: Stretch, style: Style) -> Option<Fallback> {
if self.config.is_null() {
println!("fontconfig: config is null");
return None;
}
let text = if text.is_empty() { "a" } else { text };
let mut fallback = None;
unsafe {
let pattern_create = self.fc_funs.fc_pattern_create?;
let pattern_destroy = self.fc_funs.fc_pattern_destroy?;
let pattern_add_string = self.fc_funs.fc_pattern_add_string?;
let pattern_add_integer = self.fc_funs.fc_pattern_add_integer?;
let pattern_add_charset = self.fc_funs.fc_pattern_add_charset?;
let pattern_get_string = self.fc_funs.fc_pattern_get_string?;
let pattern_get_integer = self.fc_funs.fc_pattern_get_integer?;
let char_set_create = self.fc_funs.fc_char_set_create?;
let char_set_destroy = self.fc_funs.fc_char_set_destroy?;
let char_set_add_char = self.fc_funs.fc_char_set_add_char?;
let config_substitute = self.fc_funs.fc_config_substitute?;
let default_substitute = self.fc_funs.fc_default_substitute?;
let font_match = self.fc_funs.fc_font_match?;
let pattern = pattern_create();
if !pattern.is_null() {
pattern_add_string(pattern, FC_FAMILY.as_ptr(), CString::new(family_name).unwrap().as_ptr() as _);
pattern_add_integer(pattern, FC_WEIGHT.as_ptr(), conv_weight(weight));
pattern_add_integer(pattern, FC_WIDTH.as_ptr(), conv_stretch(stretch));
pattern_add_integer(pattern, FC_SLANT.as_ptr(), conv_style(style));
let char_set = char_set_create();
if !char_set.is_null() {
for c in text.chars() {
char_set_add_char(char_set, c as _);
}
pattern_add_charset(pattern, FC_CHARSET.as_ptr(), char_set);
config_substitute(self.config, pattern, FcMatchPattern);
default_substitute(pattern);
let mut result = FcResultNoMatch;
let matched = font_match(self.config, pattern, &mut result);
if !matched.is_null() {
let mut file = core::ptr::null_mut();
pattern_get_string(matched, FC_FILE.as_ptr(), 0, &mut file);
let mut index = 0;
pattern_get_integer(matched, FC_INDEX.as_ptr(), 0, &mut index);
if !file.is_null() {
if let Ok(file) = CStr::from_ptr(file as _).to_str() {
fallback = Some(Fallback {
file: file.into(),
index: Some(index as _),
unique: None,
})
}
}
pattern_destroy(matched);
}
char_set_destroy(char_set);
}
pattern_destroy(pattern);
}
}
fallback
}
}
fn conv_weight(weight: Weight) -> i32 {
match weight.0 {
v if v == Weight::THIN.0 => FC_WEIGHT_THIN,
v if v == Weight::EXTRA_LIGHT.0 => FC_WEIGHT_EXTRALIGHT,
v if v == Weight::LIGHT.0 => FC_WEIGHT_LIGHT,
v if v == Weight::NORMAL.0 => FC_WEIGHT_NORMAL,
v if v == Weight::MEDIUM.0 => FC_WEIGHT_MEDIUM,
v if v == Weight::SEMIBOLD.0 => FC_WEIGHT_SEMIBOLD,
v if v == Weight::BOLD.0 => FC_WEIGHT_BOLD,
v if v == Weight::EXTRA_BOLD.0 => FC_WEIGHT_EXTRABOLD,
v if v == Weight::BLACK.0 => FC_WEIGHT_BLACK,
_ => FC_WEIGHT_NORMAL,
}
}
fn conv_stretch(stretch: Stretch) -> i32 {
match stretch.0 {
v if v == Stretch::ULTRA_CONDENSED.0 => FC_WIDTH_ULTRACONDENSED,
v if v == Stretch::EXTRA_CONDENSED.0 => FC_WIDTH_EXTRACONDENSED,
v if v == Stretch::CONDENSED.0 => FC_WIDTH_CONDENSED,
v if v == Stretch::SEMI_CONDENSED.0 => FC_WIDTH_SEMICONDENSED,
v if v == Stretch::NORMAL.0 => FC_WIDTH_NORMAL,
v if v == Stretch::SEMI_EXPANDED.0 => FC_WIDTH_SEMIEXPANDED,
v if v == Stretch::EXPANDED.0 => FC_WIDTH_EXPANDED,
v if v == Stretch::EXTRA_EXPANDED.0 => FC_WIDTH_EXTRAEXPANDED,
v if v == Stretch::ULTRA_EXPANDED.0 => FC_WIDTH_ULTRAEXPANDED,
_ => FC_WIDTH_NORMAL,
}
}
fn conv_style(style: Style) -> i32 {
match style {
Style::Normal => FC_SLANT_ROMAN,
Style::Italic => FC_SLANT_ITALIC,
Style::Oblique => FC_SLANT_OBLIQUE,
}
}