use crate::bindgen::*;
use std::collections::HashMap;
use std::ffi::CStr;
use std::os::raw::*;
use std::ptr;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct FontDescriptor {
pub family: String,
pub weight: i32,
pub is_italic: bool,
pub charset: i32,
pub data: Arc<[u8]>,
}
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
struct FontKey {
family: String,
weight: i32,
is_italic: bool,
charset: i32,
}
struct FontHandle {
key: FontKey,
}
pub(crate) struct MemoryFontProvider {
sys_font_info: FPDF_SYSFONTINFO,
fonts: HashMap<FontKey, Arc<[u8]>>,
}
impl MemoryFontProvider {
pub(crate) fn new(descriptors: Vec<FontDescriptor>) -> Self {
let mut fonts = HashMap::new();
for descriptor in descriptors {
let key = FontKey {
family: descriptor.family.to_lowercase(),
weight: descriptor.weight,
is_italic: descriptor.is_italic,
charset: descriptor.charset,
};
fonts.insert(key, descriptor.data);
}
let sys_font_info = FPDF_SYSFONTINFO {
version: 2,
Release: Some(release_callback),
EnumFonts: None,
MapFont: Some(map_font_callback),
GetFont: Some(get_font_callback),
GetFontData: Some(get_font_data_callback),
GetFaceName: None,
GetFontCharset: None,
DeleteFont: Some(delete_font_callback),
};
MemoryFontProvider { sys_font_info, fonts }
}
pub(crate) fn as_mut_ptr(&mut self) -> *mut FPDF_SYSFONTINFO {
&mut self.sys_font_info as *mut FPDF_SYSFONTINFO
}
unsafe fn from_pthis<'a>(pthis: *mut FPDF_SYSFONTINFO) -> &'a mut Self {
unsafe { &mut *(pthis as *mut MemoryFontProvider) }
}
}
unsafe extern "C" fn release_callback(_pthis: *mut FPDF_SYSFONTINFO) {}
unsafe extern "C" fn map_font_callback(
pthis: *mut FPDF_SYSFONTINFO,
weight: c_int,
bitalic: FPDF_BOOL,
charset: c_int,
_pitch_family: c_int,
face: *const c_char,
bexact: *mut FPDF_BOOL,
) -> *mut c_void {
unsafe {
if pthis.is_null() || face.is_null() {
return ptr::null_mut();
}
let face_name = match CStr::from_ptr(face).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
};
let provider = MemoryFontProvider::from_pthis(pthis);
let face_lower = face_name.to_lowercase();
let is_italic = bitalic != 0;
let exact_key = FontKey {
family: face_lower.clone(),
weight,
is_italic,
charset,
};
if provider.fonts.contains_key(&exact_key) {
if !bexact.is_null() {
*bexact = 1;
}
let handle = Box::new(FontHandle { key: exact_key });
return Box::into_raw(handle) as *mut c_void;
}
let normal_weight_key = FontKey {
family: face_lower.clone(),
weight: 400,
is_italic,
charset,
};
if provider.fonts.contains_key(&normal_weight_key) {
if !bexact.is_null() {
*bexact = 0;
}
let handle = Box::new(FontHandle { key: normal_weight_key });
return Box::into_raw(handle) as *mut c_void;
}
for key in provider.fonts.keys() {
if key.family == face_lower && key.is_italic == is_italic {
if !bexact.is_null() {
*bexact = 0;
}
let handle = Box::new(FontHandle { key: key.clone() });
return Box::into_raw(handle) as *mut c_void;
}
}
ptr::null_mut()
}
}
unsafe extern "C" fn get_font_callback(pthis: *mut FPDF_SYSFONTINFO, face: *const c_char) -> *mut c_void {
unsafe {
if pthis.is_null() || face.is_null() {
return ptr::null_mut();
}
let face_name = match CStr::from_ptr(face).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
};
let provider = MemoryFontProvider::from_pthis(pthis);
let face_lower = face_name.to_lowercase();
for key in provider.fonts.keys() {
if key.family == face_lower {
let handle = Box::new(FontHandle { key: key.clone() });
return Box::into_raw(handle) as *mut c_void;
}
}
ptr::null_mut()
}
}
unsafe extern "C" fn get_font_data_callback(
pthis: *mut FPDF_SYSFONTINFO,
hfont: *mut c_void,
table: c_uint,
buffer: *mut c_uchar,
buf_size: c_ulong,
) -> c_ulong {
unsafe {
if pthis.is_null() || hfont.is_null() {
return 0;
}
if table != 0 {
return 0;
}
let provider = MemoryFontProvider::from_pthis(pthis);
let handle = &*(hfont as *const FontHandle);
let font_data = match provider.fonts.get(&handle.key) {
Some(data) => data,
None => return 0,
};
let font_size = font_data.len() as c_ulong;
if buffer.is_null() {
return font_size;
}
let copy_size = std::cmp::min(buf_size, font_size) as usize;
if copy_size > 0 {
ptr::copy_nonoverlapping(font_data.as_ptr(), buffer, copy_size);
}
copy_size as c_ulong
}
}
unsafe extern "C" fn delete_font_callback(_pthis: *mut FPDF_SYSFONTINFO, hfont: *mut c_void) {
unsafe {
if hfont.is_null() {
return;
}
let _handle = Box::from_raw(hfont as *mut FontHandle);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_key_case_insensitive() {
let key1 = FontKey {
family: "Arial".to_lowercase(),
weight: 400,
is_italic: false,
charset: 0,
};
let key2 = FontKey {
family: "arial".to_lowercase(),
weight: 400,
is_italic: false,
charset: 0,
};
assert_eq!(key1, key2);
}
#[test]
fn test_font_descriptor_creation() {
let data: Arc<[u8]> = Arc::from(vec![0u8, 1u8, 2u8].into_boxed_slice());
let descriptor = FontDescriptor {
family: "TestFont".to_string(),
weight: 400,
is_italic: false,
charset: 0,
data,
};
assert_eq!(descriptor.family, "TestFont");
assert_eq!(descriptor.weight, 400);
assert!(!descriptor.is_italic);
assert_eq!(descriptor.charset, 0);
}
#[test]
fn test_memory_font_provider_empty() {
let provider = MemoryFontProvider::new(vec![]);
assert_eq!(provider.fonts.len(), 0);
}
#[test]
fn test_memory_font_provider_with_fonts() {
let data: Arc<[u8]> = Arc::from(vec![0u8; 100].into_boxed_slice());
let descriptors = vec![
FontDescriptor {
family: "Arial".to_string(),
weight: 400,
is_italic: false,
charset: 0,
data: data.clone(),
},
FontDescriptor {
family: "Arial".to_string(),
weight: 700,
is_italic: false,
charset: 0,
data: data.clone(),
},
];
let provider = MemoryFontProvider::new(descriptors);
assert_eq!(provider.fonts.len(), 2);
}
#[test]
fn test_memory_font_provider_sys_font_info() {
let provider = MemoryFontProvider::new(vec![]);
assert_eq!(provider.sys_font_info.version, 2);
assert!(provider.sys_font_info.Release.is_some());
assert!(provider.sys_font_info.MapFont.is_some());
assert!(provider.sys_font_info.GetFont.is_some());
assert!(provider.sys_font_info.GetFontData.is_some());
assert!(provider.sys_font_info.DeleteFont.is_some());
assert!(provider.sys_font_info.EnumFonts.is_none());
}
}