#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod analytic;
mod atlas;
pub mod build_helper;
mod convert;
mod edt;
mod gpu;
pub mod msdf;
pub mod psdf;
pub use analytic::glyph_to_sdf_tile_analytic;
pub use atlas::{
pack_growing, AtlasOptions, AtlasStats, MsdfAtlas, MultiPageAtlas, PackingAlgorithm, SdfAtlas,
SdfTile, UvRect,
};
pub use build_helper::{generate_ascii_atlas, generate_atlas_binary};
pub use convert::bitmap_to_sdf_tile;
pub use edt::{compute_sdf, SdfError};
pub use gpu::{AtlasGlyphMetrics, GpuAtlasDescriptor, GpuAtlasFormat, NormalizedUvRect};
pub use msdf::{
color_edges, compute_msdf, compute_mtsdf, extract_glyph_shape, glyph_to_msdf_tile,
glyph_to_mtsdf_tile, EdgeColor, GlyphShape, MsdfTile, MtsdfTile,
};
pub use psdf::{glyph_to_psdf_tile, PsdfTile};
fn bilinear_sample(src: &[f32], src_w: usize, src_h: usize, u: f32, v: f32) -> f32 {
let x = u.clamp(0.0, (src_w as f32) - 1.0);
let y = v.clamp(0.0, (src_h as f32) - 1.0);
let x0 = x.floor() as usize;
let y0 = y.floor() as usize;
let x1 = (x0 + 1).min(src_w - 1);
let y1 = (y0 + 1).min(src_h - 1);
let fx = x - x.floor();
let fy = y - y.floor();
let s00 = src[y0 * src_w + x0];
let s10 = src[y0 * src_w + x1];
let s01 = src[y1 * src_w + x0];
let s11 = src[y1 * src_w + x1];
s00 * (1.0 - fx) * (1.0 - fy) + s10 * fx * (1.0 - fy) + s01 * (1.0 - fx) * fy + s11 * fx * fy
}
pub fn glyph_to_sdf_tile(
coverage: &[u8],
src_width: usize,
src_height: usize,
tile_size: u32,
) -> Result<Vec<u8>, SdfError> {
let tile = tile_size as usize;
let sdf_src = compute_sdf(coverage, src_width, src_height, 8.0, 0)?;
if src_width == tile && src_height == tile {
return Ok(sdf_src);
}
let sdf_f32: Vec<f32> = sdf_src.iter().map(|&v| v as f32).collect();
let mut out = vec![0u8; tile * tile];
for ty in 0..tile {
for tx in 0..tile {
let u = (tx as f32 + 0.5) * src_width as f32 / tile as f32 - 0.5;
let v = (ty as f32 + 0.5) * src_height as f32 / tile as f32 - 0.5;
let val = bilinear_sample(&sdf_f32, src_width, src_height, u, v);
out[ty * tile + tx] = val.clamp(0.0, 255.0).round() as u8;
}
}
Ok(out)
}
#[cfg(test)]
mod bench_tests;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bilinear_sample_corner_values() {
let grid = [0.0f32, 255.0, 128.0, 64.0];
let width = 2usize;
let height = 2usize;
let tl = bilinear_sample(&grid, width, height, 0.0, 0.0);
let tr = bilinear_sample(&grid, width, height, 1.0, 0.0);
assert!((tl - 0.0).abs() < 1.0, "top-left should be ~0, got {tl}");
assert!(
(tr - 255.0).abs() < 1.0,
"top-right should be ~255, got {tr}"
);
}
#[test]
fn test_bilinear_sample_midpoint_interpolation() {
let grid = vec![100.0f32; 4];
let v = bilinear_sample(&grid, 2, 2, 0.5, 0.5);
assert!(
(v - 100.0).abs() < 0.01,
"uniform grid midpoint should be 100.0, got {v}"
);
}
#[test]
fn test_bilinear_sample_clamps_out_of_bounds() {
let grid = [10.0f32, 20.0, 30.0, 40.0];
let tl = bilinear_sample(&grid, 2, 2, -1.0, -1.0);
assert!(
(tl - 10.0).abs() < 0.01,
"negative coords should clamp to top-left, got {tl}"
);
let br = bilinear_sample(&grid, 2, 2, 100.0, 100.0);
assert!(
(br - 40.0).abs() < 0.01,
"large coords should clamp to bottom-right, got {br}"
);
}
}