#[derive(Debug, Clone, PartialEq)]
pub struct MsdfGlyph {
pub glyph_id: u32,
pub atlas_x: u32,
pub atlas_y: u32,
pub atlas_w: u32,
pub atlas_h: u32,
pub sdf_data: Vec<u8>,
}
impl MsdfGlyph {
pub fn new(
glyph_id: u32,
atlas_x: u32,
atlas_y: u32,
w: u32,
h: u32,
sdf_data: Vec<u8>,
) -> Self {
Self {
glyph_id,
atlas_x,
atlas_y,
atlas_w: w,
atlas_h: h,
sdf_data,
}
}
pub fn contains_uv(&self, u: f32, v: f32) -> bool {
let px = (u * self.atlas_w as f32).clamp(0.0, self.atlas_w as f32 - 1.0) as usize;
let py = (v * self.atlas_h as f32).clamp(0.0, self.atlas_h as f32 - 1.0) as usize;
let idx = py * self.atlas_w as usize + px;
self.sdf_data.get(idx).is_some_and(|&d| d >= 128)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MsdfAtlas {
pub width: u32,
pub height: u32,
pub glyphs: Vec<MsdfGlyph>,
pub sdf_buffer: Vec<u8>,
}
impl MsdfAtlas {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
glyphs: Vec::new(),
sdf_buffer: Vec::new(),
}
}
pub fn insert(&mut self, glyph: MsdfGlyph) -> bool {
self.glyphs.push(glyph);
true
}
pub fn get_glyph(&self, glyph_id: u32) -> Option<&MsdfGlyph> {
self.glyphs.iter().find(|g| g.glyph_id == glyph_id)
}
pub fn len(&self) -> usize {
self.glyphs.len()
}
pub fn is_empty(&self) -> bool {
self.glyphs.is_empty()
}
}
impl Default for MsdfAtlas {
fn default() -> Self {
Self::new(1024, 1024)
}
}
pub fn generate_sdf(bitmap: &[u8], width: usize, height: usize, spread: f32) -> Vec<u8> {
if width == 0 || height == 0 {
return Vec::new();
}
let spread_i = spread.ceil() as isize;
let _spread_sq = spread * spread;
let mut output = vec![0u8; width * height];
for y in 0..height {
for x in 0..width {
let idx = y * width + x;
let inside = bitmap[idx] != 0;
let mut min_dist_sq = f32::INFINITY;
let y_min = (y as isize - spread_i).max(0) as usize;
let y_max = ((y as isize + spread_i) as usize).min(height - 1);
let x_min = (x as isize - spread_i).max(0) as usize;
let x_max = ((x as isize + spread_i) as usize).min(width - 1);
for sy in y_min..=y_max {
for sx in x_min..=x_max {
let s_idx = sy * width + sx;
let s_inside = bitmap[s_idx] != 0;
if s_inside != inside {
let dx = sx as f32 - x as f32;
let dy = sy as f32 - y as f32;
let d_sq = dx * dx + dy * dy;
if d_sq < min_dist_sq {
min_dist_sq = d_sq;
}
}
}
}
let dist = min_dist_sq.sqrt();
let normalized = if inside {
128.0 + (dist / spread) * 127.0
} else {
128.0 - (dist / spread) * 128.0
};
output[idx] = normalized.clamp(0.0, 255.0) as u8;
}
}
output
}
pub fn pack_atlas(glyphs: &mut [MsdfGlyph], max_size: u32) -> Result<(u32, u32), String> {
if glyphs.is_empty() {
return Ok((0, 0));
}
glyphs.sort_by_key(|b| std::cmp::Reverse(b.atlas_h));
let mut current_x: u32 = 0;
let mut current_y: u32 = 0;
let mut shelf_height: u32 = 0;
let mut max_x: u32 = 0;
for glyph in glyphs.iter_mut() {
if glyph.atlas_w > max_size || glyph.atlas_h > max_size {
return Err(format!(
"Glyph {} ({}x{}) exceeds max atlas size {}",
glyph.glyph_id, glyph.atlas_w, glyph.atlas_h, max_size
));
}
if current_x + glyph.atlas_w > max_size {
current_y += shelf_height;
current_x = 0;
shelf_height = 0;
}
glyph.atlas_x = current_x;
glyph.atlas_y = current_y;
current_x += glyph.atlas_w;
shelf_height = shelf_height.max(glyph.atlas_h);
max_x = max_x.max(current_x);
}
let total_height = current_y + shelf_height;
Ok((max_x, total_height))
}
#[cfg(test)]
mod msdf_tests {
use super::*;
#[test]
fn test_generate_sdf_empty() {
let result = generate_sdf(&[], 0, 0, 8.0);
assert!(result.is_empty());
}
#[test]
fn test_generate_sdf_single_pixel() {
let mut bitmap = vec![0u8; 9];
bitmap[4] = 1; let sdf = generate_sdf(&bitmap, 3, 3, 2.0);
assert_eq!(sdf.len(), 9);
assert!(sdf[4] >= 128, "center pixel should be inside");
assert!(sdf[0] < 128, "corner should be outside");
}
#[test]
fn test_generate_sdf_solid_block() {
let bitmap = vec![1u8; 16];
let sdf = generate_sdf(&bitmap, 4, 4, 4.0);
assert_eq!(sdf.len(), 16);
for &v in &sdf {
assert!(v >= 128, "solid block pixels should be inside");
}
}
#[test]
fn test_pack_atlas_empty() {
let mut glyphs: Vec<MsdfGlyph> = vec![];
let result = pack_atlas(&mut glyphs, 1024);
assert_eq!(result, Ok((0, 0)));
}
#[test]
fn test_pack_atlas_single() {
let sdf = vec![128u8; 16]; let mut glyphs = vec![MsdfGlyph::new(1, 0, 0, 4, 4, sdf)];
let (w, h) = pack_atlas(&mut glyphs, 1024).unwrap();
assert_eq!(w, 4);
assert_eq!(h, 4);
assert_eq!(glyphs[0].atlas_x, 0);
assert_eq!(glyphs[0].atlas_y, 0);
}
#[test]
fn test_pack_atlas_wraps_shelf() {
let sdf = vec![128u8; 64 * 64];
let mut glyphs = vec![
MsdfGlyph::new(1, 0, 0, 64, 64, sdf.clone()),
MsdfGlyph::new(2, 0, 0, 64, 64, sdf),
];
let (w, h) = pack_atlas(&mut glyphs, 100).unwrap();
assert_eq!(w, 64);
assert_eq!(h, 128); assert_eq!(glyphs[0].atlas_y, 0);
assert_eq!(glyphs[1].atlas_y, 64);
}
#[test]
fn test_pack_atlas_oversized() {
let sdf = vec![128u8; 100];
let mut glyphs = vec![MsdfGlyph::new(1, 0, 0, 200, 200, sdf)];
let result = pack_atlas(&mut glyphs, 100);
assert!(result.is_err());
}
#[test]
fn test_msdf_glyph_contains_uv() {
let sdf = vec![200u8, 50, 50, 200];
let glyph = MsdfGlyph::new(1, 0, 0, 2, 2, sdf);
assert!(glyph.contains_uv(0.25, 0.25)); assert!(!glyph.contains_uv(0.75, 0.25)); assert!(!glyph.contains_uv(0.25, 0.75)); assert!(glyph.contains_uv(0.75, 0.75)); }
#[test]
fn test_msdf_atlas_lookup() {
let mut atlas = MsdfAtlas::new(512, 512);
assert!(atlas.is_empty());
atlas.insert(MsdfGlyph::new(42, 0, 0, 16, 16, vec![128u8; 256]));
assert_eq!(atlas.len(), 1);
assert!(atlas.get_glyph(42).is_some());
assert!(atlas.get_glyph(99).is_none());
}
}