use crate::error::TextError;
use crate::types::{FontDescriptor, FontStyle};
use objc2_core_foundation::{CFDictionary, CFIndex, CFNumber, CFString};
use objc2_core_text::{
CTFontCollection, CTFontDescriptor, kCTFontFamilyNameAttribute, kCTFontNameAttribute,
kCTFontSlantTrait, kCTFontTraitsAttribute, kCTFontURLAttribute, kCTFontWeightTrait,
};
use std::ffi::c_void;
pub fn enumerate_system_fonts() -> Result<Vec<FontDescriptor>, TextError> {
let collection = unsafe { CTFontCollection::from_available_fonts(None) };
let descriptors = unsafe { collection.matching_font_descriptors() }
.ok_or_else(|| TextError::SystemFontEnumeration("Failed to get font descriptors".into()))?;
let mut result = Vec::new();
let count = descriptors.len();
for i in 0..count {
let desc_ptr = unsafe { descriptors.value_at_index(i as CFIndex) };
if desc_ptr.is_null() {
continue;
}
let desc: &CTFontDescriptor = unsafe { &*(desc_ptr as *const CTFontDescriptor) };
let family = unsafe { desc.attribute(kCTFontFamilyNameAttribute) }
.and_then(|attr| attr.downcast::<CFString>().ok())
.map(|s| s.to_string())
.unwrap_or_default();
if family.is_empty() {
continue;
}
let postscript_name = unsafe { desc.attribute(kCTFontNameAttribute) }
.and_then(|attr| attr.downcast::<CFString>().ok())
.map(|s| s.to_string())
.unwrap_or_else(|| family.clone());
let (weight, is_italic) = unsafe { desc.attribute(kCTFontTraitsAttribute) }
.and_then(|attr| attr.downcast::<CFDictionary>().ok())
.map(|traits| extract_weight_and_slant(&traits))
.unwrap_or((400, false));
let style = match (weight >= 700, is_italic) {
(true, true) => FontStyle::BoldItalic,
(true, false) => FontStyle::Bold,
(false, true) => FontStyle::Italic,
(false, false) => FontStyle::Regular,
};
let path = unsafe { desc.attribute(kCTFontURLAttribute) }
.and_then(|attr| attr.downcast::<objc2_core_foundation::CFURL>().ok())
.and_then(|url| url.to_file_path());
result.push(FontDescriptor {
family,
postscript_name,
weight,
style,
path,
});
}
Ok(result)
}
fn extract_weight_and_slant(traits: &CFDictionary) -> (u16, bool) {
let weight_ct = unsafe {
let key = kCTFontWeightTrait as *const _ as *const c_void;
let value_ptr = traits.value(key);
if value_ptr.is_null() {
None
} else {
let cf_number: &CFNumber = &*(value_ptr as *const CFNumber);
cf_number.as_f64()
}
}
.unwrap_or(0.0);
let weight_css = ((weight_ct + 1.0) * 400.0).clamp(100.0f64, 900.0f64) as u16;
let slant = unsafe {
let key = kCTFontSlantTrait as *const _ as *const c_void;
let value_ptr = traits.value(key);
if value_ptr.is_null() {
None
} else {
let cf_number: &CFNumber = &*(value_ptr as *const CFNumber);
cf_number.as_f64()
}
}
.unwrap_or(0.0);
(weight_css, slant > 0.05)
}