#![allow(unsafe_op_in_unsafe_fn)]
use core::ptr::NonNull;
use std::borrow::Cow;
use std::ffi::{CStr, CString, c_void};
use std::os::raw::c_char;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::ffi::{
FontProviderVTable, RegisterFontFn, noesis_font_provider_create, noesis_font_provider_destroy,
noesis_set_font_provider, noesis_set_font_provider_assembly, noesis_set_font_provider_scheme,
noesis_set_font_provider_scheme_assembly,
};
#[derive(Clone, PartialEq, Eq)]
enum Scope {
Global,
Scheme(CString),
Assembly(CString),
SchemeAssembly(CString, CString),
}
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
static ACTIVE: Mutex<Vec<(Scope, u64)>> = Mutex::new(Vec::new());
unsafe fn install(scope: &Scope, handle: *mut c_void) {
match scope {
Scope::Global => noesis_set_font_provider(handle),
Scope::Scheme(s) => noesis_set_font_provider_scheme(s.as_ptr(), handle),
Scope::Assembly(a) => noesis_set_font_provider_assembly(a.as_ptr(), handle),
Scope::SchemeAssembly(s, a) => {
noesis_set_font_provider_scheme_assembly(s.as_ptr(), a.as_ptr(), handle)
}
}
}
pub trait FontProvider: Send + Sync + 'static {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn scan_folder(&mut self, folder_uri: &str, register: &mut dyn FnMut(&str));
fn open_font(&mut self, folder_uri: &str, filename: &str) -> Option<&[u8]>;
}
unsafe fn provider<'a>(userdata: *mut c_void) -> &'a mut Box<dyn FontProvider> {
&mut *userdata.cast::<Box<dyn FontProvider>>()
}
fn cstr_to_str<'a>(p: *const c_char) -> Cow<'a, str> {
if p.is_null() {
Cow::Borrowed("")
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy()
}
}
unsafe extern "C" fn t_scan_folder(
userdata: *mut c_void,
folder_uri: *const c_char,
register_fn: RegisterFontFn,
register_cx: *mut c_void,
) {
crate::panic_guard::guard(|| {
let folder = cstr_to_str(folder_uri);
provider(userdata).scan_folder(&folder, &mut |filename: &str| {
if let Ok(c) = std::ffi::CString::new(filename) {
register_fn(register_cx, c.as_ptr());
}
});
})
}
unsafe extern "C" fn t_open_font(
userdata: *mut c_void,
folder_uri: *const c_char,
filename: *const c_char,
out_data: *mut *const u8,
out_len: *mut u32,
) -> bool {
crate::panic_guard::guard(|| {
let folder = cstr_to_str(folder_uri);
let name = cstr_to_str(filename);
let Some(bytes) = provider(userdata).open_font(&folder, &name) else {
return false;
};
let Ok(len) = u32::try_from(bytes.len()) else {
return false;
};
out_data.write(bytes.as_ptr());
out_len.write(len);
true
})
}
static VTABLE: FontProviderVTable = FontProviderVTable {
scan_folder: t_scan_folder,
open_font: t_open_font,
};
#[must_use = "dropping the guard unregisters the provider and frees it"]
pub struct Registered {
handle: NonNull<c_void>,
userdata: NonNull<Box<dyn FontProvider>>,
scope: Scope,
id: u64,
}
unsafe impl Send for Registered {}
impl Registered {
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.handle.as_ptr()
}
pub fn provider_mut<F: FontProvider>(&mut self) -> &mut F {
let boxed: &mut Box<dyn FontProvider> = unsafe { self.userdata.as_mut() };
(**boxed)
.as_any_mut()
.downcast_mut::<F>()
.expect("Registered::provider_mut: type does not match set_font_provider")
}
pub fn register_font(&self, folder_uri: &str, filename: &str) {
use std::ffi::CString;
let folder = CString::new(folder_uri).expect("folder_uri contained interior NUL");
let name = CString::new(filename).expect("filename contained interior NUL");
unsafe {
crate::ffi::noesis_font_provider_register_font(
self.handle.as_ptr(),
folder.as_ptr(),
name.as_ptr(),
);
}
}
}
impl Drop for Registered {
fn drop(&mut self) {
{
let mut active = ACTIVE.lock().expect("font provider registry poisoned");
if let Some(pos) = active
.iter()
.position(|(s, i)| *s == self.scope && *i == self.id)
{
active.swap_remove(pos);
unsafe { install(&self.scope, core::ptr::null_mut()) };
}
}
unsafe {
noesis_font_provider_destroy(self.handle.as_ptr());
drop(Box::from_raw(self.userdata.as_ptr()));
}
}
}
pub fn set_font_provider<P: FontProvider>(provider: P) -> Registered {
register_with(provider, Scope::Global)
}
fn register_with<P: FontProvider>(provider: P, scope: Scope) -> Registered {
let outer: Box<Box<dyn FontProvider>> = Box::new(Box::new(provider));
let userdata = Box::into_raw(outer);
let handle = unsafe { noesis_font_provider_create(&raw const VTABLE, userdata.cast()) };
let handle = NonNull::new(handle).expect("noesis_font_provider_create returned null");
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
{
let mut active = ACTIVE.lock().expect("font provider registry poisoned");
unsafe { install(&scope, handle.as_ptr()) };
if let Some(slot) = active.iter_mut().find(|(s, _)| *s == scope) {
slot.1 = id;
} else {
active.push((scope.clone(), id));
}
}
Registered {
handle,
userdata: NonNull::new(userdata).expect("Box::into_raw returned null"),
scope,
id,
}
}
pub fn set_scheme_font_provider<P: FontProvider>(scheme: &str, provider: P) -> Registered {
let scheme = CString::new(scheme).expect("scheme contained interior NUL");
register_with(provider, Scope::Scheme(scheme))
}
pub fn set_assembly_font_provider<P: FontProvider>(assembly: &str, provider: P) -> Registered {
let assembly = CString::new(assembly).expect("assembly contained interior NUL");
register_with(provider, Scope::Assembly(assembly))
}
pub fn set_scheme_assembly_font_provider<P: FontProvider>(
scheme: &str,
assembly: &str,
provider: P,
) -> Registered {
let scheme = CString::new(scheme).expect("scheme contained interior NUL");
let assembly = CString::new(assembly).expect("assembly contained interior NUL");
register_with(provider, Scope::SchemeAssembly(scheme, assembly))
}
pub fn set_font_fallbacks<S: AsRef<str>>(families: &[S]) {
use std::ffi::CString;
use std::os::raw::c_char;
if families.is_empty() {
unsafe { crate::ffi::noesis_set_font_fallbacks(core::ptr::null(), 0) };
return;
}
let cstrings: Vec<CString> = families
.iter()
.map(|f| CString::new(f.as_ref()).expect("fallback family contained interior NUL"))
.collect();
let ptrs: Vec<*const c_char> = cstrings.iter().map(|c| c.as_ptr()).collect();
unsafe {
crate::ffi::noesis_set_font_fallbacks(ptrs.as_ptr(), ptrs.len() as u32);
}
}
pub fn set_font_default_properties(size: f32, weight: i32, stretch: i32, style: i32) {
unsafe {
crate::ffi::noesis_set_font_default_properties(size, weight, stretch, style);
}
}