use super::{Handle, HandleStore, safe_helpers};
use crate::fitz::font::Font;
use std::sync::LazyLock;
pub static FONTS: LazyLock<HandleStore<Font>> = LazyLock::new(HandleStore::default);
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_font(
_ctx: Handle,
name: *const std::ffi::c_char,
_is_bold: i32,
_is_italic: i32,
_font_file: Handle,
) -> Handle {
let font_name = match safe_helpers::c_str_to_str(name) {
Some(s) => s,
None => return 0,
};
let font = Font::new(font_name);
FONTS.insert(font)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_font_from_memory(
_ctx: Handle,
name: *const std::ffi::c_char,
data: *const u8,
len: i32,
index: i32,
_use_glyph_bbox: i32,
) -> Handle {
if data.is_null() || len <= 0 {
return 0;
}
let font_name = safe_helpers::c_str_to_str(name).unwrap_or("Unknown");
let font_data = match safe_helpers::copy_from_ptr(data, len as usize) {
Some(data) => data,
None => return 0,
};
let font = Font::from_data(font_name, &font_data, index as usize);
match font {
Ok(f) => FONTS.insert(f),
Err(_) => 0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_font_from_file(
_ctx: Handle,
name: *const std::ffi::c_char,
path: *const std::ffi::c_char,
index: i32,
_use_glyph_bbox: i32,
) -> Handle {
let path_str = match safe_helpers::c_str_to_str(path) {
Some(s) => s,
None => return 0,
};
let font_name = safe_helpers::c_str_to_str(name).unwrap_or("Unknown");
match std::fs::read(path_str) {
Ok(data) => match Font::from_data(font_name, &data, index as usize) {
Ok(f) => FONTS.insert(f),
Err(_) => 0,
},
Err(_) => 0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_font(_ctx: Handle, font: Handle) -> Handle {
FONTS.keep(font)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_font(_ctx: Handle, font: Handle) {
let _ = FONTS.remove(font);
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_name(_ctx: Handle, font: Handle, buf: *mut std::ffi::c_char, size: i32) {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
let name = guard.name();
safe_helpers::str_to_c_buffer(name, buf, size);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_is_bold(_ctx: Handle, font: Handle) -> i32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return i32::from(guard.is_bold());
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_is_italic(_ctx: Handle, font: Handle) -> i32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return i32::from(guard.is_italic());
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_is_serif(_ctx: Handle, font: Handle) -> i32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return i32::from(guard.is_serif());
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_is_monospaced(_ctx: Handle, font: Handle) -> i32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return i32::from(guard.is_monospace());
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_encode_character(_ctx: Handle, font: Handle, unicode: i32) -> i32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return guard.encode_character(unicode as u32) as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_encode_character_with_fallback(
_ctx: Handle,
font: Handle,
unicode: i32,
_script: i32,
_language: i32,
out_font: *mut Handle,
) -> i32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
let glyph = guard.encode_character(unicode as u32);
safe_helpers::write_ptr(font, out_font);
return glyph as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_advance_glyph(_ctx: Handle, font: Handle, glyph: i32, _wmode: i32) -> f32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return guard.glyph_advance(glyph as u16);
}
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_bound_glyph(
_ctx: Handle,
font: Handle,
glyph: i32,
_transform: super::geometry::fz_matrix,
) -> super::geometry::fz_rect {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
let bbox = guard.glyph_bbox(glyph as u16);
return super::geometry::fz_rect {
x0: bbox.x0,
y0: bbox.y0,
x1: bbox.x1,
y1: bbox.y1,
};
}
}
super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 0.0,
y1: 0.0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_bbox(_ctx: Handle, font: Handle) -> super::geometry::fz_rect {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
let bbox = guard.bbox();
return super::geometry::fz_rect {
x0: bbox.x0,
y0: bbox.y0,
x1: bbox.x1,
y1: bbox.y1,
};
}
}
super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 0.0,
y1: 0.0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_outline_glyph(
_ctx: Handle,
font: Handle,
glyph: i32,
_transform: super::geometry::fz_matrix,
) -> Handle {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
let path = guard.outline_glyph(glyph as u16);
return super::path::PATHS.insert(path);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_is_valid(_ctx: Handle, font: Handle) -> i32 {
if FONTS.get(font).is_some() { 1 } else { 0 }
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_clone_font(_ctx: Handle, font: Handle) -> Handle {
fz_keep_font(_ctx, font)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_ascender(_ctx: Handle, font: Handle) -> f32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return guard.ascender();
}
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_descender(_ctx: Handle, font: Handle) -> f32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return guard.descender();
}
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_name(
_ctx: Handle,
font: Handle,
glyph: i32,
buf: *mut std::ffi::c_char,
size: i32,
) {
if buf.is_null() || size <= 0 {
return;
}
let name = adobe_glyph_name(glyph as u16);
let bytes = name.as_bytes();
let copy_len = bytes.len().min((size - 1) as usize);
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, copy_len);
*buf.add(copy_len) = 0;
}
}
fn adobe_glyph_name(gid: u16) -> String {
match gid {
0 => ".notdef".to_string(),
0x20 => "space".to_string(),
0x21 => "exclam".to_string(),
0x22 => "quotedbl".to_string(),
0x23 => "numbersign".to_string(),
0x24 => "dollar".to_string(),
0x25 => "percent".to_string(),
0x26 => "ampersand".to_string(),
0x27 => "quotesingle".to_string(),
0x28 => "parenleft".to_string(),
0x29 => "parenright".to_string(),
0x2A => "asterisk".to_string(),
0x2B => "plus".to_string(),
0x2C => "comma".to_string(),
0x2D => "hyphen".to_string(),
0x2E => "period".to_string(),
0x2F => "slash".to_string(),
0x30..=0x39 => {
const DIGITS: [&str; 10] = [
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
];
DIGITS[(gid - 0x30) as usize].to_string()
}
0x3A => "colon".to_string(),
0x3B => "semicolon".to_string(),
0x3C => "less".to_string(),
0x3D => "equal".to_string(),
0x3E => "greater".to_string(),
0x3F => "question".to_string(),
0x40 => "at".to_string(),
0x41..=0x5A => String::from(char::from(gid as u8)),
0x5B => "bracketleft".to_string(),
0x5C => "backslash".to_string(),
0x5D => "bracketright".to_string(),
0x5E => "asciicircum".to_string(),
0x5F => "underscore".to_string(),
0x60 => "grave".to_string(),
0x61..=0x7A => String::from(char::from(gid as u8)),
0x7B => "braceleft".to_string(),
0x7C => "bar".to_string(),
0x7D => "braceright".to_string(),
0x7E => "asciitilde".to_string(),
_ => format!("uni{:04X}", gid),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_font_is_embedded(_ctx: Handle, font: Handle) -> i32 {
if let Some(f) = FONTS.get(font) {
if let Ok(guard) = f.lock() {
return if guard.is_embedded() { 1 } else { 0 };
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_font() {
let font_handle = fz_new_font(0, c"Helvetica".as_ptr(), 0, 0, 0);
assert_ne!(font_handle, 0);
fz_drop_font(0, font_handle);
}
#[test]
fn test_new_font_null_name() {
let font_handle = fz_new_font(0, std::ptr::null(), 0, 0, 0);
assert_eq!(font_handle, 0);
}
#[test]
fn test_keep_font() {
let font_handle = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let kept = fz_keep_font(0, font_handle);
assert_eq!(kept, font_handle);
fz_drop_font(0, font_handle);
}
#[test]
fn test_font_name() {
let font_handle = fz_new_font(0, c"Times".as_ptr(), 0, 0, 0);
let mut buf = [0i8; 64];
fz_font_name(0, font_handle, buf.as_mut_ptr(), 64);
let name = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()).to_str().unwrap() };
assert_eq!(name, "Times");
fz_drop_font(0, font_handle);
}
#[test]
fn test_font_properties() {
let font_handle = fz_new_font(0, c"Courier".as_ptr(), 1, 1, 0);
let _is_bold = fz_font_is_bold(0, font_handle);
let _is_italic = fz_font_is_italic(0, font_handle);
let _is_serif = fz_font_is_serif(0, font_handle);
let _is_monospaced = fz_font_is_monospaced(0, font_handle);
fz_drop_font(0, font_handle);
}
#[test]
fn test_encode_character() {
let font_handle = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let glyph = fz_encode_character(0, font_handle, 65);
assert!(glyph >= 0);
fz_drop_font(0, font_handle);
}
#[test]
fn test_advance_glyph() {
let font_handle = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let glyph = fz_encode_character(0, font_handle, 65);
let advance = fz_advance_glyph(0, font_handle, glyph, 0);
assert!(advance >= 0.0);
fz_drop_font(0, font_handle);
}
#[test]
fn test_bound_glyph() {
let font_handle = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let glyph = fz_encode_character(0, font_handle, 65);
let bbox = fz_bound_glyph(
0,
font_handle,
glyph,
super::super::geometry::fz_matrix::identity(),
);
assert!(bbox.x1 >= bbox.x0);
fz_drop_font(0, font_handle);
}
#[test]
fn test_font_bbox() {
let font_handle = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let bbox = fz_font_bbox(0, font_handle);
assert!(bbox.x1 >= bbox.x0);
assert!(bbox.y1 >= bbox.y0);
fz_drop_font(0, font_handle);
}
#[test]
fn test_new_font_from_memory() {
let font_data = b"Fake font data";
let font_handle = fz_new_font_from_memory(
0,
c"Test".as_ptr(),
font_data.as_ptr(),
font_data.len() as i32,
0,
0,
);
if font_handle != 0 {
fz_drop_font(0, font_handle);
}
}
#[test]
fn test_new_font_from_memory_null_data() {
assert_eq!(
fz_new_font_from_memory(0, c"Test".as_ptr(), std::ptr::null(), 10, 0, 0),
0
);
assert_eq!(
fz_new_font_from_memory(0, c"Test".as_ptr(), b"x".as_ptr(), 0, 0, 0),
0
);
}
#[test]
fn test_new_font_from_memory_null_name() {
let data = b"fake";
let h = fz_new_font_from_memory(0, std::ptr::null(), data.as_ptr(), 4, 0, 0);
if h != 0 {
fz_drop_font(0, h);
}
}
#[test]
fn test_drop_font() {
let h = fz_new_font(0, c"DropTest".as_ptr(), 0, 0, 0);
fz_drop_font(0, h);
assert_eq!(fz_font_is_valid(0, h), 0);
}
#[test]
fn test_font_name_null_buf() {
let h = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
fz_font_name(0, h, std::ptr::null_mut(), 64);
fz_drop_font(0, h);
}
#[test]
fn test_encode_character_invalid_handle() {
assert_eq!(fz_encode_character(0, 0, 65), 0);
assert_eq!(fz_encode_character(0, 99999, 65), 0);
}
#[test]
fn test_encode_character_with_fallback() {
let h = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let mut out_font = 0u64;
let glyph = fz_encode_character_with_fallback(0, h, 65, 0, 0, &mut out_font as *mut u64);
assert!(glyph >= 0);
assert_eq!(out_font, h);
fz_drop_font(0, h);
}
#[test]
fn test_encode_character_with_fallback_invalid() {
let mut out = 0u64;
assert_eq!(
fz_encode_character_with_fallback(0, 0, 65, 0, 0, &mut out),
0
);
}
#[test]
fn test_advance_glyph_invalid() {
assert!((fz_advance_glyph(0, 0, 0, 0) - 0.0).abs() < 0.01);
}
#[test]
fn test_bound_glyph_invalid() {
let bbox = fz_bound_glyph(0, 0, 0, super::super::geometry::fz_matrix::identity());
assert_eq!(bbox.x0, 0.0);
assert_eq!(bbox.y0, 0.0);
}
#[test]
fn test_font_bbox_invalid() {
let bbox = fz_font_bbox(0, 0);
assert_eq!(bbox.x0, 0.0);
}
#[test]
fn test_outline_glyph() {
let h = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let glyph = fz_encode_character(0, h, 65);
let path = fz_outline_glyph(0, h, glyph, super::super::geometry::fz_matrix::identity());
if path != 0 {
super::super::path::PATHS.remove(path);
}
fz_drop_font(0, h);
}
#[test]
fn test_outline_glyph_invalid() {
assert_eq!(
fz_outline_glyph(0, 0, 0, super::super::geometry::fz_matrix::identity()),
0
);
}
#[test]
fn test_font_is_valid() {
let h = fz_new_font(0, c"Valid".as_ptr(), 0, 0, 0);
assert_eq!(fz_font_is_valid(0, h), 1);
assert_eq!(fz_font_is_valid(0, 0), 0);
fz_drop_font(0, h);
}
#[test]
fn test_clone_font() {
let h = fz_new_font(0, c"Clone".as_ptr(), 0, 0, 0);
let c = fz_clone_font(0, h);
assert_eq!(c, h);
fz_drop_font(0, h);
}
#[test]
fn test_font_ascender_descender() {
let h = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let _a = fz_font_ascender(0, h);
let _d = fz_font_descender(0, h);
assert_eq!(fz_font_ascender(0, 0), 0.0);
assert_eq!(fz_font_descender(0, 0), 0.0);
fz_drop_font(0, h);
}
#[test]
fn test_font_is_embedded() {
let h = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let _e = fz_font_is_embedded(0, h);
assert_eq!(fz_font_is_embedded(0, 0), 0);
fz_drop_font(0, h);
}
#[test]
fn test_glyph_name() {
let h = fz_new_font(0, c"Arial".as_ptr(), 0, 0, 0);
let mut buf = [0i8; 64];
fz_glyph_name(0, h, 65, buf.as_mut_ptr(), 64);
fz_glyph_name(0, h, 0, buf.as_mut_ptr(), 64);
fz_glyph_name(0, h, 0x20, buf.as_mut_ptr(), 64);
fz_glyph_name(0, h, 65, std::ptr::null_mut(), 64);
fz_glyph_name(0, h, 65, buf.as_mut_ptr(), 0);
fz_drop_font(0, h);
}
#[test]
fn test_keep_font_returns_handle() {
let h = fz_new_font(0, c"K".as_ptr(), 0, 0, 0);
assert_eq!(fz_keep_font(0, h), h);
fz_drop_font(0, h);
}
}