use super::{Handle, HandleStore};
use std::sync::LazyLock;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct GlyphOrigin {
pub x: f32,
pub y: f32,
}
impl Default for GlyphOrigin {
fn default() -> Self {
Self { x: 0.0, y: 0.0 }
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubpixelMode {
None = 0,
HorizontalOnly = 1,
Full = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlyphHints {
NoHint = 0,
LightHint = 1,
NormalHint = 2,
StrongHint = 3,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct GlyphMetrics {
pub advance_width: f32,
pub advance_height: f32,
pub left_bearing: f32,
pub top_bearing: f32,
pub bbox: [f32; 4],
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ColorLayer {
pub glyph_id: u32,
pub palette_index: u16,
pub _reserved: u16,
}
impl Default for ColorLayer {
fn default() -> Self {
Self {
glyph_id: 0,
palette_index: 0,
_reserved: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct Glyph {
pub font: Handle,
pub glyph_id: u32,
pub unicode: u32,
pub origin: GlyphOrigin,
pub matrix: [f32; 6],
pub metrics: GlyphMetrics,
pub subpixel_x: u8,
pub subpixel_y: u8,
pub hinting: GlyphHints,
pub is_color: bool,
pub color_layers: Vec<ColorLayer>,
pub bitmap: Option<GlyphBitmap>,
pub variations: Vec<f32>,
}
impl Default for Glyph {
fn default() -> Self {
Self {
font: 0,
glyph_id: 0,
unicode: 0,
origin: GlyphOrigin::default(),
matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], metrics: GlyphMetrics::default(),
subpixel_x: 0,
subpixel_y: 0,
hinting: GlyphHints::NormalHint,
is_color: false,
color_layers: Vec::new(),
bitmap: None,
variations: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct GlyphBitmap {
pub width: i32,
pub height: i32,
pub x_offset: i32,
pub y_offset: i32,
pub stride: i32,
pub n: i32,
pub data: Vec<u8>,
}
impl Default for GlyphBitmap {
fn default() -> Self {
Self {
width: 0,
height: 0,
x_offset: 0,
y_offset: 0,
stride: 0,
n: 1,
data: Vec::new(),
}
}
}
#[derive(Debug, Default)]
pub struct GlyphCache {
pub max_entries: usize,
pub entries: std::collections::HashMap<(Handle, u32, u32), Handle>,
pub lru: Vec<(Handle, u32, u32)>,
}
pub static GLYPHS: LazyLock<HandleStore<Glyph>> = LazyLock::new(HandleStore::new);
pub static GLYPH_CACHE: LazyLock<std::sync::Mutex<GlyphCache>> = LazyLock::new(|| {
std::sync::Mutex::new(GlyphCache {
max_entries: 1024,
entries: std::collections::HashMap::new(),
lru: Vec::new(),
})
});
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_glyph(_ctx: Handle, font: Handle, glyph_id: u32, unicode: u32) -> Handle {
let glyph = Glyph {
font,
glyph_id,
unicode,
..Default::default()
};
GLYPHS.insert(glyph)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_glyph_at(
_ctx: Handle,
font: Handle,
glyph_id: u32,
x: f32,
y: f32,
) -> Handle {
let glyph = Glyph {
font,
glyph_id,
origin: GlyphOrigin { x, y },
..Default::default()
};
GLYPHS.insert(glyph)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_glyph_with_matrix(
_ctx: Handle,
font: Handle,
glyph_id: u32,
matrix: *const f32,
) -> Handle {
let mut glyph = Glyph {
font,
glyph_id,
..Default::default()
};
if !matrix.is_null() {
let m = unsafe { std::slice::from_raw_parts(matrix, 6) };
glyph.matrix.copy_from_slice(m);
}
GLYPHS.insert(glyph)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_id(_ctx: Handle, glyph: Handle) -> u32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return guard.glyph_id;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_unicode(_ctx: Handle, glyph: Handle) -> u32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return guard.unicode;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_font(_ctx: Handle, glyph: Handle) -> Handle {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return guard.font;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_origin(_ctx: Handle, glyph: Handle, x: *mut f32, y: *mut f32) {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
if !x.is_null() {
unsafe { *x = guard.origin.x };
}
if !y.is_null() {
unsafe { *y = guard.origin.y };
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_origin(_ctx: Handle, glyph: Handle, x: f32, y: f32) {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
guard.origin.x = x;
guard.origin.y = y;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_matrix(_ctx: Handle, glyph: Handle, matrix: *mut f32) {
if matrix.is_null() {
return;
}
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
let m = unsafe { std::slice::from_raw_parts_mut(matrix, 6) };
m.copy_from_slice(&guard.matrix);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_matrix(_ctx: Handle, glyph: Handle, matrix: *const f32) {
if matrix.is_null() {
return;
}
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
let m = unsafe { std::slice::from_raw_parts(matrix, 6) };
guard.matrix.copy_from_slice(m);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_advance(_ctx: Handle, glyph: Handle, horizontal: i32) -> f32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return if horizontal != 0 {
guard.metrics.advance_width
} else {
guard.metrics.advance_height
};
}
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_advance(
_ctx: Handle,
glyph: Handle,
advance_width: f32,
advance_height: f32,
) {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
guard.metrics.advance_width = advance_width;
guard.metrics.advance_height = advance_height;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_bbox(_ctx: Handle, glyph: Handle, bbox: *mut f32) {
if bbox.is_null() {
return;
}
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
let b = unsafe { std::slice::from_raw_parts_mut(bbox, 4) };
b.copy_from_slice(&guard.metrics.bbox);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_bbox(
_ctx: Handle,
glyph: Handle,
x0: f32,
y0: f32,
x1: f32,
y1: f32,
) {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
guard.metrics.bbox = [x0, y0, x1, y1];
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_metrics(_ctx: Handle, glyph: Handle, metrics: *mut GlyphMetrics) {
if metrics.is_null() {
return;
}
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
unsafe { *metrics = guard.metrics };
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_subpixel(_ctx: Handle, glyph: Handle, x: *mut u8, y: *mut u8) {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
if !x.is_null() {
unsafe { *x = guard.subpixel_x };
}
if !y.is_null() {
unsafe { *y = guard.subpixel_y };
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_subpixel(_ctx: Handle, glyph: Handle, x: u8, y: u8) {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
guard.subpixel_x = x;
guard.subpixel_y = y;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_subpixel_adjust(
_ctx: Handle,
x: *mut f32,
y: *mut f32,
subpixel_x: *mut u8,
subpixel_y: *mut u8,
mode: i32,
) {
if x.is_null() || y.is_null() {
return;
}
let px = unsafe { *x };
let py = unsafe { *y };
let ix = px.floor();
let iy = py.floor();
let fx = ((px - ix) * 256.0) as u8;
let fy = ((py - iy) * 256.0) as u8;
unsafe {
*x = ix;
*y = iy;
}
match mode {
0 => {
if !subpixel_x.is_null() {
unsafe { *subpixel_x = 0 };
}
if !subpixel_y.is_null() {
unsafe { *subpixel_y = 0 };
}
}
1 => {
if !subpixel_x.is_null() {
unsafe { *subpixel_x = fx };
}
if !subpixel_y.is_null() {
unsafe { *subpixel_y = 0 };
}
}
_ => {
if !subpixel_x.is_null() {
unsafe { *subpixel_x = fx };
}
if !subpixel_y.is_null() {
unsafe { *subpixel_y = fy };
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_is_color(_ctx: Handle, glyph: Handle) -> i32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return i32::from(guard.is_color);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_color(_ctx: Handle, glyph: Handle, is_color: i32) {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
guard.is_color = is_color != 0;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_color_layer_count(_ctx: Handle, glyph: Handle) -> i32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return guard.color_layers.len() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_add_color_layer(
_ctx: Handle,
glyph: Handle,
layer_glyph_id: u32,
palette_index: u16,
) -> i32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
guard.is_color = true;
guard.color_layers.push(ColorLayer {
glyph_id: layer_glyph_id,
palette_index,
_reserved: 0,
});
return (guard.color_layers.len() - 1) as i32;
}
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_color_layer(
_ctx: Handle,
glyph: Handle,
idx: i32,
layer_glyph_id: *mut u32,
palette_index: *mut u16,
) -> i32 {
if idx < 0 {
return 0;
}
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
if let Some(layer) = guard.color_layers.get(idx as usize) {
if !layer_glyph_id.is_null() {
unsafe { *layer_glyph_id = layer.glyph_id };
}
if !palette_index.is_null() {
unsafe { *palette_index = layer.palette_index };
}
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_variation_count(_ctx: Handle, glyph: Handle) -> i32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return guard.variations.len() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_variation(_ctx: Handle, glyph: Handle, axis_index: i32, value: f32) {
if axis_index < 0 {
return;
}
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
let idx = axis_index as usize;
if idx >= guard.variations.len() {
guard.variations.resize(idx + 1, 0.0);
}
guard.variations[idx] = value;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_variation(_ctx: Handle, glyph: Handle, axis_index: i32) -> f32 {
if axis_index < 0 {
return 0.0;
}
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
if let Some(v) = guard.variations.get(axis_index as usize) {
return *v;
}
}
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_cache_key(font: Handle, glyph_id: u32, scale: f32) -> u32 {
let scale_key = (scale * 16.0) as u32 & 0xFFFF;
let font_key = (font as u32) & 0xFFFF;
(font_key << 16) | ((glyph_id & 0xFF) << 8) | (scale_key & 0xFF)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_cache_lookup(
_ctx: Handle,
font: Handle,
glyph_id: u32,
scale_key: u32,
) -> Handle {
if let Ok(cache) = GLYPH_CACHE.lock() {
if let Some(&handle) = cache.entries.get(&(font, glyph_id, scale_key)) {
return handle;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_cache_insert(
_ctx: Handle,
font: Handle,
glyph_id: u32,
scale_key: u32,
glyph: Handle,
) {
if let Ok(mut cache) = GLYPH_CACHE.lock() {
let key = (font, glyph_id, scale_key);
while cache.entries.len() >= cache.max_entries && !cache.lru.is_empty() {
let oldest = cache.lru.remove(0);
if let Some(old_handle) = cache.entries.remove(&oldest) {
GLYPHS.remove(old_handle);
}
}
cache.entries.insert(key, glyph);
cache.lru.push(key);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_cache_clear(_ctx: Handle) {
if let Ok(mut cache) = GLYPH_CACHE.lock() {
for (_, handle) in cache.entries.drain() {
GLYPHS.remove(handle);
}
cache.lru.clear();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_cache_set_size(_ctx: Handle, max_entries: i32) {
if max_entries <= 0 {
return;
}
if let Ok(mut cache) = GLYPH_CACHE.lock() {
cache.max_entries = max_entries as usize;
while cache.entries.len() > cache.max_entries && !cache.lru.is_empty() {
let oldest = cache.lru.remove(0);
if let Some(old_handle) = cache.entries.remove(&oldest) {
GLYPHS.remove(old_handle);
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_cache_size(_ctx: Handle) -> i32 {
if let Ok(cache) = GLYPH_CACHE.lock() {
return cache.entries.len() as i32;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_glyph_hinting(_ctx: Handle, glyph: Handle) -> i32 {
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(guard) = g.lock() {
return guard.hinting as i32;
}
}
GlyphHints::NormalHint as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_glyph_hinting(_ctx: Handle, glyph: Handle, hinting: i32) {
let h = match hinting {
0 => GlyphHints::NoHint,
1 => GlyphHints::LightHint,
3 => GlyphHints::StrongHint,
_ => GlyphHints::NormalHint,
};
if let Some(g) = GLYPHS.get(glyph) {
if let Ok(mut guard) = g.lock() {
guard.hinting = h;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_glyph(_ctx: Handle, glyph: Handle) -> Handle {
GLYPHS.keep(glyph)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_glyph(_ctx: Handle, glyph: Handle) {
GLYPHS.remove(glyph);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_glyph() {
let glyph = fz_new_glyph(0, 1, 65, 0x41); assert!(glyph > 0);
assert_eq!(fz_glyph_id(0, glyph), 65);
assert_eq!(fz_glyph_unicode(0, glyph), 0x41);
assert_eq!(fz_glyph_font(0, glyph), 1);
fz_drop_glyph(0, glyph);
}
#[test]
fn test_glyph_origin() {
let glyph = fz_new_glyph_at(0, 1, 65, 10.0, 20.0);
let mut x = 0.0f32;
let mut y = 0.0f32;
fz_glyph_origin(0, glyph, &mut x, &mut y);
assert_eq!(x, 10.0);
assert_eq!(y, 20.0);
fz_set_glyph_origin(0, glyph, 30.0, 40.0);
fz_glyph_origin(0, glyph, &mut x, &mut y);
assert_eq!(x, 30.0);
assert_eq!(y, 40.0);
fz_drop_glyph(0, glyph);
}
#[test]
fn test_glyph_matrix() {
let matrix = [2.0f32, 0.0, 0.0, 2.0, 100.0, 100.0];
let glyph = fz_new_glyph_with_matrix(0, 1, 65, matrix.as_ptr());
let mut out = [0.0f32; 6];
fz_glyph_matrix(0, glyph, out.as_mut_ptr());
assert_eq!(out, matrix);
fz_drop_glyph(0, glyph);
}
#[test]
fn test_glyph_advance() {
let glyph = fz_new_glyph(0, 1, 65, 0x41);
fz_set_glyph_advance(0, glyph, 600.0, 1000.0);
assert_eq!(fz_glyph_advance(0, glyph, 1), 600.0); assert_eq!(fz_glyph_advance(0, glyph, 0), 1000.0);
fz_drop_glyph(0, glyph);
}
#[test]
fn test_subpixel_adjust() {
let mut x = 10.75f32;
let mut y = 20.25f32;
let mut sx = 0u8;
let mut sy = 0u8;
fz_subpixel_adjust(0, &mut x, &mut y, &mut sx, &mut sy, 2);
assert_eq!(x, 10.0);
assert_eq!(y, 20.0);
assert!(sx > 0); }
#[test]
fn test_color_layers() {
let glyph = fz_new_glyph(0, 1, 65, 0x41);
assert_eq!(fz_glyph_is_color(0, glyph), 0);
fz_glyph_add_color_layer(0, glyph, 100, 0);
fz_glyph_add_color_layer(0, glyph, 101, 1);
assert_eq!(fz_glyph_is_color(0, glyph), 1);
assert_eq!(fz_glyph_color_layer_count(0, glyph), 2);
let mut gid = 0u32;
let mut pal = 0u16;
fz_glyph_color_layer(0, glyph, 1, &mut gid, &mut pal);
assert_eq!(gid, 101);
assert_eq!(pal, 1);
fz_drop_glyph(0, glyph);
}
#[test]
fn test_variations() {
let glyph = fz_new_glyph(0, 1, 65, 0x41);
fz_set_glyph_variation(0, glyph, 0, 400.0); fz_set_glyph_variation(0, glyph, 1, 100.0);
assert_eq!(fz_glyph_variation_count(0, glyph), 2);
assert_eq!(fz_glyph_variation(0, glyph, 0), 400.0);
assert_eq!(fz_glyph_variation(0, glyph, 1), 100.0);
fz_drop_glyph(0, glyph);
}
#[test]
fn test_glyph_cache() {
fz_glyph_cache_clear(0);
let glyph1 = fz_new_glyph(0, 1, 65, 0x41);
let glyph2 = fz_new_glyph(0, 1, 66, 0x42);
fz_glyph_cache_insert(0, 1, 65, 100, glyph1);
fz_glyph_cache_insert(0, 1, 66, 100, glyph2);
assert_eq!(fz_glyph_cache_size(0), 2);
let found = fz_glyph_cache_lookup(0, 1, 65, 100);
assert_eq!(found, glyph1);
fz_glyph_cache_clear(0);
assert_eq!(fz_glyph_cache_size(0), 0);
}
#[test]
fn test_hinting() {
let glyph = fz_new_glyph(0, 1, 65, 0x41);
assert_eq!(fz_glyph_hinting(0, glyph), GlyphHints::NormalHint as i32);
fz_set_glyph_hinting(0, glyph, GlyphHints::NoHint as i32);
assert_eq!(fz_glyph_hinting(0, glyph), GlyphHints::NoHint as i32);
fz_drop_glyph(0, glyph);
}
}