1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3pub mod analytic;
36mod atlas;
37pub mod build_helper;
38mod convert;
39mod edt;
40mod gpu;
41pub mod msdf;
42pub mod psdf;
43
44pub use analytic::glyph_to_sdf_tile_analytic;
45pub use atlas::{
46 pack_growing, AtlasOptions, AtlasStats, MsdfAtlas, MultiPageAtlas, PackingAlgorithm, SdfAtlas,
47 SdfTile, UvRect,
48};
49pub use build_helper::{generate_ascii_atlas, generate_atlas_binary};
50pub use convert::bitmap_to_sdf_tile;
51pub use edt::{compute_sdf, SdfError};
52pub use gpu::{AtlasGlyphMetrics, GpuAtlasDescriptor, GpuAtlasFormat, NormalizedUvRect};
53pub use msdf::{
54 color_edges, compute_msdf, compute_mtsdf, extract_glyph_shape, glyph_to_msdf_tile,
55 glyph_to_mtsdf_tile, EdgeColor, GlyphShape, MsdfTile, MtsdfTile,
56};
57pub use psdf::{glyph_to_psdf_tile, PsdfTile};
58
59fn bilinear_sample(src: &[f32], src_w: usize, src_h: usize, u: f32, v: f32) -> f32 {
66 let x = u.clamp(0.0, (src_w as f32) - 1.0);
67 let y = v.clamp(0.0, (src_h as f32) - 1.0);
68 let x0 = x.floor() as usize;
69 let y0 = y.floor() as usize;
70 let x1 = (x0 + 1).min(src_w - 1);
71 let y1 = (y0 + 1).min(src_h - 1);
72 let fx = x - x.floor();
73 let fy = y - y.floor();
74 let s00 = src[y0 * src_w + x0];
75 let s10 = src[y0 * src_w + x1];
76 let s01 = src[y1 * src_w + x0];
77 let s11 = src[y1 * src_w + x1];
78 s00 * (1.0 - fx) * (1.0 - fy) + s10 * fx * (1.0 - fy) + s01 * (1.0 - fx) * fy + s11 * fx * fy
79}
80
81pub fn glyph_to_sdf_tile(
103 coverage: &[u8],
104 src_width: usize,
105 src_height: usize,
106 tile_size: u32,
107) -> Result<Vec<u8>, SdfError> {
108 let tile = tile_size as usize;
109
110 let sdf_src = compute_sdf(coverage, src_width, src_height, 8.0, 0)?;
112
113 if src_width == tile && src_height == tile {
115 return Ok(sdf_src);
116 }
117
118 let sdf_f32: Vec<f32> = sdf_src.iter().map(|&v| v as f32).collect();
120
121 let mut out = vec![0u8; tile * tile];
123 for ty in 0..tile {
124 for tx in 0..tile {
125 let u = (tx as f32 + 0.5) * src_width as f32 / tile as f32 - 0.5;
127 let v = (ty as f32 + 0.5) * src_height as f32 / tile as f32 - 0.5;
128 let val = bilinear_sample(&sdf_f32, src_width, src_height, u, v);
129 out[ty * tile + tx] = val.clamp(0.0, 255.0).round() as u8;
130 }
131 }
132 Ok(out)
133}
134
135#[cfg(test)]
136mod bench_tests;
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_bilinear_sample_corner_values() {
144 let grid = [0.0f32, 255.0, 128.0, 64.0];
146 let width = 2usize;
147 let height = 2usize;
148 let tl = bilinear_sample(&grid, width, height, 0.0, 0.0);
150 let tr = bilinear_sample(&grid, width, height, 1.0, 0.0);
151 assert!((tl - 0.0).abs() < 1.0, "top-left should be ~0, got {tl}");
152 assert!(
153 (tr - 255.0).abs() < 1.0,
154 "top-right should be ~255, got {tr}"
155 );
156 }
157
158 #[test]
159 fn test_bilinear_sample_midpoint_interpolation() {
160 let grid = vec![100.0f32; 4];
162 let v = bilinear_sample(&grid, 2, 2, 0.5, 0.5);
163 assert!(
164 (v - 100.0).abs() < 0.01,
165 "uniform grid midpoint should be 100.0, got {v}"
166 );
167 }
168
169 #[test]
170 fn test_bilinear_sample_clamps_out_of_bounds() {
171 let grid = [10.0f32, 20.0, 30.0, 40.0];
173 let tl = bilinear_sample(&grid, 2, 2, -1.0, -1.0);
174 assert!(
175 (tl - 10.0).abs() < 0.01,
176 "negative coords should clamp to top-left, got {tl}"
177 );
178 let br = bilinear_sample(&grid, 2, 2, 100.0, 100.0);
179 assert!(
180 (br - 40.0).abs() < 0.01,
181 "large coords should clamp to bottom-right, got {br}"
182 );
183 }
184}