1use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14use ab_glyph::{Font, FontVec, PxScale, ScaleFont};
15
16use super::atlas::ATLAS_CHARS;
17
18#[derive(Clone, Debug)]
22pub struct SdfConfig {
23 pub hires_size: u32,
25 pub output_size: u32,
27 pub spread: f32,
29 pub msdf: bool,
31 pub cache_path: Option<PathBuf>,
33}
34
35impl Default for SdfConfig {
36 fn default() -> Self {
37 Self {
38 hires_size: 256,
39 output_size: 64,
40 spread: 8.0,
41 msdf: false,
42 cache_path: None,
43 }
44 }
45}
46
47#[derive(Clone, Debug)]
51pub struct SdfGlyphData {
52 pub pixels: Vec<u8>,
55 pub width: u32,
56 pub height: u32,
57 pub advance: f32,
59 pub bearing_x: f32,
61 pub bearing_y: f32,
62 pub bbox_w: f32,
64 pub bbox_h: f32,
65}
66
67#[derive(Clone, Debug)]
72pub struct MsdfGlyphData {
73 pub r_channel: Vec<u8>,
74 pub g_channel: Vec<u8>,
75 pub b_channel: Vec<u8>,
76 pub width: u32,
77 pub height: u32,
78 pub advance: f32,
79 pub bearing_x: f32,
80 pub bearing_y: f32,
81 pub bbox_w: f32,
82 pub bbox_h: f32,
83}
84
85#[derive(Copy, Clone, Debug)]
89pub struct SdfGlyphMetric {
90 pub uv_rect: [f32; 4],
92 pub size: glam::Vec2,
94 pub bearing: glam::Vec2,
96 pub advance: f32,
98}
99
100pub struct SdfAtlasData {
102 pub pixels: Vec<u8>,
104 pub width: u32,
105 pub height: u32,
106 pub channels: u32,
108 pub metrics: HashMap<char, SdfGlyphMetric>,
109 pub spread: f32,
110 pub font_size_px: f32,
111}
112
113#[derive(Copy, Clone)]
120struct Offset {
121 dx: i32,
122 dy: i32,
123}
124
125impl Offset {
126 const FAR: Self = Self { dx: 9999, dy: 9999 };
127 const ZERO: Self = Self { dx: 0, dy: 0 };
128
129 fn dist_sq(self) -> i32 {
130 self.dx * self.dx + self.dy * self.dy
131 }
132}
133
134fn dead_reckoning_udf(bitmap: &[bool], w: usize, h: usize) -> Vec<f32> {
139 let n = w * h;
140 let mut grid = vec![Offset::FAR; n];
141
142 for y in 0..h {
144 for x in 0..w {
145 let idx = y * w + x;
146 let inside = bitmap[idx];
147 let on_boundary = if inside {
149 (x > 0 && !bitmap[idx - 1])
150 || (x + 1 < w && !bitmap[idx + 1])
151 || (y > 0 && !bitmap[idx - w])
152 || (y + 1 < h && !bitmap[idx + w])
153 } else {
154 (x > 0 && bitmap[idx - 1])
155 || (x + 1 < w && bitmap[idx + 1])
156 || (y > 0 && bitmap[idx - w])
157 || (y + 1 < h && bitmap[idx + w])
158 };
159 if on_boundary {
160 grid[idx] = Offset::ZERO;
161 }
162 }
163 }
164
165 for y in 0..h {
168 for x in 0..w {
169 let idx = y * w + x;
170 let cur = grid[idx];
171
172 macro_rules! check {
173 ($nx:expr, $ny:expr, $ddx:expr, $ddy:expr) => {
174 if $nx < w && $ny < h {
175 let nidx = $ny * w + $nx;
176 let candidate = Offset {
177 dx: grid[nidx].dx + $ddx,
178 dy: grid[nidx].dy + $ddy,
179 };
180 if candidate.dist_sq() < grid[idx].dist_sq() {
181 grid[idx] = candidate;
182 }
183 }
184 };
185 }
186
187 if y > 0 {
188 if x > 0 { check!(x - 1, y - 1, 1, 1); }
189 check!(x, y - 1, 0, 1);
190 if x + 1 < w { check!(x + 1, y - 1, -1, 1); }
191 }
192 if x > 0 { check!(x - 1, y, 1, 0); }
193 }
194 }
195
196 for y in (0..h).rev() {
199 for x in (0..w).rev() {
200 let idx = y * w + x;
201
202 macro_rules! check {
203 ($nx:expr, $ny:expr, $ddx:expr, $ddy:expr) => {
204 if $nx < w && $ny < h {
205 let nidx = $ny * w + $nx;
206 let candidate = Offset {
207 dx: grid[nidx].dx + $ddx,
208 dy: grid[nidx].dy + $ddy,
209 };
210 if candidate.dist_sq() < grid[idx].dist_sq() {
211 grid[idx] = candidate;
212 }
213 }
214 };
215 }
216
217 if y + 1 < h {
218 if x + 1 < w { check!(x + 1, y + 1, -1, -1); }
219 check!(x, y + 1, 0, -1);
220 if x > 0 { check!(x - 1, y + 1, 1, -1); }
221 }
222 if x + 1 < w { check!(x + 1, y, -1, 0); }
223 }
224 }
225
226 grid.iter().map(|o| (o.dist_sq() as f32).sqrt()).collect()
227}
228
229fn compute_sdf(bitmap: &[bool], w: usize, h: usize) -> Vec<f32> {
233 let outside_dist = dead_reckoning_udf(bitmap, w, h);
235
236 let inverted: Vec<bool> = bitmap.iter().map(|b| !b).collect();
238 let inside_dist = dead_reckoning_udf(&inverted, w, h);
239
240 outside_dist
242 .iter()
243 .zip(inside_dist.iter())
244 .map(|(out_d, in_d)| *in_d - *out_d)
245 .collect()
246}
247
248fn rasterize_glyph(
253 font: &FontVec,
254 ch: char,
255 hires_px: f32,
256) -> Option<(Vec<f32>, u32, u32, f32, f32, f32, f32, f32)> {
257 let scale = PxScale::from(hires_px);
258 let scaled = font.as_scaled(scale);
259
260 let glyph_id = font.glyph_id(ch);
261 if glyph_id.0 == 0 && ch != ' ' {
262 return None;
263 }
264
265 let advance = scaled.h_advance(glyph_id);
266 let ascent = scaled.ascent();
267
268 let glyph = glyph_id.with_scale_and_position(scale, ab_glyph::point(0.0, ascent));
269
270 if let Some(outlined) = font.outline_glyph(glyph) {
271 let bounds = outlined.px_bounds();
272 let w = (bounds.max.x - bounds.min.x).ceil() as u32 + 2;
273 let h = (bounds.max.y - bounds.min.y).ceil() as u32 + 2;
274 if w == 0 || h == 0 {
275 return None;
276 }
277
278 let mut coverage = vec![0.0_f32; (w * h) as usize];
279 let ox = bounds.min.x.floor() as i32;
280 let oy = bounds.min.y.floor() as i32;
281
282 outlined.draw(|x, y, v| {
283 let px = x as i32 - ox + 1;
284 let py = y as i32 - oy + 1;
285 if px >= 0 && py >= 0 && (px as u32) < w && (py as u32) < h {
286 coverage[(py as u32 * w + px as u32) as usize] = v;
287 }
288 });
289
290 let bearing_x = bounds.min.x;
291 let bearing_y = bounds.min.y;
292 let bbox_w = (bounds.max.x - bounds.min.x).max(1.0);
293 let bbox_h = (bounds.max.y - bounds.min.y).max(1.0);
294
295 Some((coverage, w, h, advance, bearing_x, bearing_y, bbox_w, bbox_h))
296 } else {
297 Some((vec![0.0; 4], 2, 2, advance, 0.0, 0.0, 1.0, 1.0))
299 }
300}
301
302pub fn generate_glyph_sdf(
304 font: &FontVec,
305 ch: char,
306 config: &SdfConfig,
307) -> Option<SdfGlyphData> {
308 let (coverage, hi_w, hi_h, advance, bearing_x, bearing_y, bbox_w, bbox_h) =
309 rasterize_glyph(font, ch, config.hires_size as f32)?;
310
311 let bitmap: Vec<bool> = coverage.iter().map(|&v| v > 0.5).collect();
313
314 let sdf_hires = compute_sdf(&bitmap, hi_w as usize, hi_h as usize);
316
317 let scale_factor = config.output_size as f32 / config.hires_size as f32;
319 let out_w = ((hi_w as f32 * scale_factor).ceil() as u32).max(1);
320 let out_h = ((hi_h as f32 * scale_factor).ceil() as u32).max(1);
321
322 let pad = (config.spread * 1.5).ceil() as u32;
324 let padded_w = out_w + pad * 2;
325 let padded_h = out_h + pad * 2;
326
327 let inv_scale = 1.0 / scale_factor;
328 let spread_pixels = config.spread;
329
330 let mut sdf_out = vec![128u8; (padded_w * padded_h) as usize];
331
332 for py in 0..padded_h {
333 for px in 0..padded_w {
334 let hx = ((px as f32 - pad as f32 + 0.5) * inv_scale).max(0.0);
336 let hy = ((py as f32 - pad as f32 + 0.5) * inv_scale).max(0.0);
337
338 let dist = sample_bilinear_f32(&sdf_hires, hi_w as usize, hi_h as usize, hx, hy);
340
341 let dist_scaled = dist * scale_factor;
343 let normalized = (dist_scaled / spread_pixels) * 0.5 + 0.5;
344 let byte = (normalized.clamp(0.0, 1.0) * 255.0) as u8;
345
346 sdf_out[(py * padded_w + px) as usize] = byte;
347 }
348 }
349
350 Some(SdfGlyphData {
351 pixels: sdf_out,
352 width: padded_w,
353 height: padded_h,
354 advance,
355 bearing_x,
356 bearing_y,
357 bbox_w,
358 bbox_h,
359 })
360}
361
362pub fn generate_glyph_msdf(
368 font: &FontVec,
369 ch: char,
370 config: &SdfConfig,
371) -> Option<MsdfGlyphData> {
372 let (coverage, hi_w, hi_h, advance, bearing_x, bearing_y, bbox_w, bbox_h) =
373 rasterize_glyph(font, ch, config.hires_size as f32)?;
374
375 let w = hi_w as usize;
376 let h = hi_h as usize;
377
378 let bitmap: Vec<bool> = coverage.iter().map(|&v| v > 0.5).collect();
383
384 let mut edge_class = vec![0u8; w * h]; for y in 1..h.saturating_sub(1) {
387 for x in 1..w.saturating_sub(1) {
388 let idx = y * w + x;
389 if !is_edge(&bitmap, w, h, x, y) {
390 continue;
391 }
392 let gx = coverage[idx + 1] - coverage[idx.saturating_sub(1)];
393 let gy = coverage[idx + w] - coverage[idx.saturating_sub(w)];
394 let angle = gy.atan2(gx); let angle_deg = (angle.to_degrees() + 360.0) % 360.0;
396 edge_class[idx] = if angle_deg < 120.0 {
397 0
398 } else if angle_deg < 240.0 {
399 1
400 } else {
401 2
402 };
403 }
404 }
405
406 let mut channels = Vec::new();
409 for ch_idx in 0..3u8 {
410 let channel_bitmap: Vec<bool> = (0..w * h)
411 .map(|i| {
412 if bitmap[i] {
413 true
415 } else {
416 false
418 }
419 })
420 .collect();
421
422 let sdf = compute_sdf(&channel_bitmap, w, h);
423
424 let full_sdf = compute_sdf(&bitmap, w, h);
427 let blended: Vec<f32> = (0..w * h)
428 .map(|i| {
429 if is_edge(&bitmap, w, h, i % w, i / w) && edge_class[i] != ch_idx {
430 full_sdf[i] + 0.5
432 } else {
433 full_sdf[i]
434 }
435 })
436 .collect();
437
438 channels.push(blended);
439 }
440
441 let scale_factor = config.output_size as f32 / config.hires_size as f32;
443 let out_w = ((hi_w as f32 * scale_factor).ceil() as u32).max(1);
444 let out_h = ((hi_h as f32 * scale_factor).ceil() as u32).max(1);
445 let pad = (config.spread * 1.5).ceil() as u32;
446 let padded_w = out_w + pad * 2;
447 let padded_h = out_h + pad * 2;
448 let inv_scale = 1.0 / scale_factor;
449
450 let mut r_out = vec![128u8; (padded_w * padded_h) as usize];
451 let mut g_out = vec![128u8; (padded_w * padded_h) as usize];
452 let mut b_out = vec![128u8; (padded_w * padded_h) as usize];
453
454 for py in 0..padded_h {
455 for px in 0..padded_w {
456 let hx = ((px as f32 - pad as f32 + 0.5) * inv_scale).max(0.0);
457 let hy = ((py as f32 - pad as f32 + 0.5) * inv_scale).max(0.0);
458
459 for (ch_idx, out) in [&mut r_out, &mut g_out, &mut b_out].iter_mut().enumerate() {
460 let dist = sample_bilinear_f32(&channels[ch_idx], w, h, hx, hy);
461 let dist_scaled = dist * scale_factor;
462 let normalized = (dist_scaled / config.spread) * 0.5 + 0.5;
463 out[(py * padded_w + px) as usize] = (normalized.clamp(0.0, 1.0) * 255.0) as u8;
464 }
465 }
466 }
467
468 Some(MsdfGlyphData {
469 r_channel: r_out,
470 g_channel: g_out,
471 b_channel: b_out,
472 width: padded_w,
473 height: padded_h,
474 advance,
475 bearing_x,
476 bearing_y,
477 bbox_w,
478 bbox_h,
479 })
480}
481
482fn is_edge(bitmap: &[bool], w: usize, h: usize, x: usize, y: usize) -> bool {
484 let idx = y * w + x;
485 if !bitmap[idx] {
486 return false;
487 }
488 (x > 0 && !bitmap[idx - 1])
489 || (x + 1 < w && !bitmap[idx + 1])
490 || (y > 0 && !bitmap[idx - w])
491 || (y + 1 < h && !bitmap[idx + w])
492}
493
494fn sample_bilinear_f32(data: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
496 let x0 = (x.floor() as usize).min(w.saturating_sub(1));
497 let y0 = (y.floor() as usize).min(h.saturating_sub(1));
498 let x1 = (x0 + 1).min(w.saturating_sub(1));
499 let y1 = (y0 + 1).min(h.saturating_sub(1));
500 let fx = x - x.floor();
501 let fy = y - y.floor();
502
503 let c00 = data[y0 * w + x0];
504 let c10 = data[y0 * w + x1];
505 let c01 = data[y1 * w + x0];
506 let c11 = data[y1 * w + x1];
507
508 let c0 = c00 + (c10 - c00) * fx;
509 let c1 = c01 + (c11 - c01) * fx;
510 c0 + (c1 - c0) * fy
511}
512
513struct ShelfPacker {
518 atlas_width: u32,
519 atlas_height: u32,
520 shelf_x: u32,
521 shelf_y: u32,
522 shelf_height: u32,
523}
524
525impl ShelfPacker {
526 fn new(atlas_width: u32, atlas_height: u32) -> Self {
527 Self {
528 atlas_width,
529 atlas_height,
530 shelf_x: 0,
531 shelf_y: 0,
532 shelf_height: 0,
533 }
534 }
535
536 fn pack(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
538 if w > self.atlas_width {
539 return None;
540 }
541
542 if self.shelf_x + w > self.atlas_width {
544 self.shelf_y += self.shelf_height;
546 self.shelf_x = 0;
547 self.shelf_height = 0;
548 }
549
550 if self.shelf_y + h > self.atlas_height {
552 return None;
553 }
554
555 let pos = (self.shelf_x, self.shelf_y);
556 self.shelf_x += w;
557 if h > self.shelf_height {
558 self.shelf_height = h;
559 }
560
561 Some(pos)
562 }
563}
564
565fn load_system_font() -> Option<FontVec> {
569 let paths: &[&str] = &[
570 r"C:\Windows\Fonts\consola.ttf",
571 r"C:\Windows\Fonts\cour.ttf",
572 r"C:\Windows\Fonts\lucon.ttf",
573 "/System/Library/Fonts/Menlo.ttc",
574 "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
575 "/usr/share/fonts/TTF/DejaVuSansMono.ttf",
576 "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
577 ];
578 for p in paths {
579 if let Ok(data) = std::fs::read(p) {
580 if let Ok(f) = FontVec::try_from_vec(data) {
581 log::info!("SdfGenerator: loaded '{}'", p);
582 return Some(f);
583 }
584 }
585 }
586 None
587}
588
589pub fn generate_sdf_atlas(config: &SdfConfig) -> SdfAtlasData {
591 if let Some(ref cache_path) = config.cache_path {
593 if let Some(cached) = load_cached_atlas(cache_path, config) {
594 log::info!("SdfGenerator: loaded cached atlas from {:?}", cache_path);
595 return cached;
596 }
597 }
598
599 let font = load_system_font();
600 let chars: Vec<char> = ATLAS_CHARS.chars().collect();
601
602 let mut glyph_sdfs: Vec<(char, SdfGlyphData)> = Vec::new();
604
605 if let Some(ref font) = font {
606 for &ch in &chars {
607 if let Some(sdf) = generate_glyph_sdf(font, ch, config) {
608 glyph_sdfs.push((ch, sdf));
609 } else {
610 glyph_sdfs.push((ch, SdfGlyphData {
612 pixels: vec![0u8; 16],
613 width: 4,
614 height: 4,
615 advance: config.output_size as f32 * 0.5,
616 bearing_x: 0.0,
617 bearing_y: 0.0,
618 bbox_w: 4.0,
619 bbox_h: 4.0,
620 }));
621 }
622 }
623 } else {
624 log::warn!("SdfGenerator: no system font found, generating fallback SDF atlas");
625 for &ch in &chars {
626 glyph_sdfs.push((ch, generate_fallback_sdf(config)));
627 }
628 }
629
630 let max_glyph_w = glyph_sdfs.iter().map(|(_, g)| g.width).max().unwrap_or(64);
632 let max_glyph_h = glyph_sdfs.iter().map(|(_, g)| g.height).max().unwrap_or(64);
633 let cells_per_row = 2048 / max_glyph_w.max(1);
634 let rows_needed = (glyph_sdfs.len() as u32 + cells_per_row - 1) / cells_per_row.max(1);
635 let atlas_h_needed = rows_needed * max_glyph_h;
636
637 let atlas_w = (cells_per_row * max_glyph_w).max(256).min(4096);
638 let atlas_h = atlas_h_needed.max(256).min(4096);
639
640 let mut atlas_pixels = vec![0u8; (atlas_w * atlas_h) as usize];
641 let mut metrics = HashMap::new();
642 let mut packer = ShelfPacker::new(atlas_w, atlas_h);
643
644 let hires = config.hires_size as f32;
645
646 for (ch, glyph_sdf) in &glyph_sdfs {
647 if let Some((ax, ay)) = packer.pack(glyph_sdf.width, glyph_sdf.height) {
648 for gy in 0..glyph_sdf.height {
650 for gx in 0..glyph_sdf.width {
651 let src = (gy * glyph_sdf.width + gx) as usize;
652 let dst = ((ay + gy) * atlas_w + (ax + gx)) as usize;
653 if src < glyph_sdf.pixels.len() && dst < atlas_pixels.len() {
654 atlas_pixels[dst] = glyph_sdf.pixels[src];
655 }
656 }
657 }
658
659 metrics.insert(*ch, SdfGlyphMetric {
660 uv_rect: [
661 ax as f32 / atlas_w as f32,
662 ay as f32 / atlas_h as f32,
663 (ax + glyph_sdf.width) as f32 / atlas_w as f32,
664 (ay + glyph_sdf.height) as f32 / atlas_h as f32,
665 ],
666 size: glam::Vec2::new(glyph_sdf.bbox_w, glyph_sdf.bbox_h),
667 bearing: glam::Vec2::new(glyph_sdf.bearing_x, glyph_sdf.bearing_y),
668 advance: glyph_sdf.advance,
669 });
670 } else {
671 log::warn!("SdfGenerator: atlas full, could not pack glyph '{}'", ch);
672 }
673 }
674
675 let atlas = SdfAtlasData {
676 pixels: atlas_pixels,
677 width: atlas_w,
678 height: atlas_h,
679 channels: 1,
680 metrics,
681 spread: config.spread,
682 font_size_px: config.output_size as f32,
683 };
684
685 if let Some(ref cache_path) = config.cache_path {
687 save_atlas_cache(cache_path, &atlas);
688 }
689
690 atlas
691}
692
693fn generate_fallback_sdf(config: &SdfConfig) -> SdfGlyphData {
695 let size = config.output_size.max(8);
696 let pad = (config.spread * 1.5).ceil() as u32;
697 let total = size + pad * 2;
698 let mut pixels = vec![0u8; (total * total) as usize];
699
700 for y in 0..total {
702 for x in 0..total {
703 let dx = if x < pad {
704 pad as f32 - x as f32
705 } else if x >= size + pad {
706 (x - size - pad + 1) as f32
707 } else {
708 0.0
709 };
710 let dy = if y < pad {
711 pad as f32 - y as f32
712 } else if y >= size + pad {
713 (y - size - pad + 1) as f32
714 } else {
715 0.0
716 };
717 let dist = (dx * dx + dy * dy).sqrt();
718 let normalized = (-dist / config.spread) * 0.5 + 0.5;
719 pixels[(y * total + x) as usize] = (normalized.clamp(0.0, 1.0) * 255.0) as u8;
720 }
721 }
722
723 SdfGlyphData {
724 pixels,
725 width: total,
726 height: total,
727 advance: size as f32,
728 bearing_x: 0.0,
729 bearing_y: 0.0,
730 bbox_w: size as f32,
731 bbox_h: size as f32,
732 }
733}
734
735fn save_atlas_cache(path: &Path, atlas: &SdfAtlasData) {
743 let mut data = Vec::new();
744
745 data.extend_from_slice(b"SDF1");
747 data.extend_from_slice(&atlas.width.to_le_bytes());
748 data.extend_from_slice(&atlas.height.to_le_bytes());
749 data.extend_from_slice(&atlas.spread.to_le_bytes());
750 data.extend_from_slice(&atlas.font_size_px.to_le_bytes());
751 data.extend_from_slice(&(atlas.metrics.len() as u32).to_le_bytes());
752
753 for (&ch, metric) in &atlas.metrics {
754 data.extend_from_slice(&(ch as u32).to_le_bytes());
755 for &uv in &metric.uv_rect {
756 data.extend_from_slice(&uv.to_le_bytes());
757 }
758 data.extend_from_slice(&metric.size.x.to_le_bytes());
759 data.extend_from_slice(&metric.size.y.to_le_bytes());
760 data.extend_from_slice(&metric.bearing.x.to_le_bytes());
761 data.extend_from_slice(&metric.bearing.y.to_le_bytes());
762 data.extend_from_slice(&metric.advance.to_le_bytes());
763 }
764
765 data.extend_from_slice(&atlas.pixels);
766
767 if let Err(e) = std::fs::write(path, &data) {
768 log::warn!("SdfGenerator: failed to write cache to {:?}: {}", path, e);
769 } else {
770 log::info!("SdfGenerator: cached atlas to {:?} ({} bytes)", path, data.len());
771 }
772}
773
774fn load_cached_atlas(path: &Path, config: &SdfConfig) -> Option<SdfAtlasData> {
775 let data = std::fs::read(path).ok()?;
776 if data.len() < 24 {
777 return None;
778 }
779
780 if &data[0..4] != b"SDF1" {
782 return None;
783 }
784
785 let mut cursor = 4usize;
786
787 macro_rules! read_u32 {
788 () => {{
789 if cursor + 4 > data.len() { return None; }
790 let val = u32::from_le_bytes(data[cursor..cursor + 4].try_into().ok()?);
791 cursor += 4;
792 val
793 }};
794 }
795
796 macro_rules! read_f32 {
797 () => {{
798 if cursor + 4 > data.len() { return None; }
799 let val = f32::from_le_bytes(data[cursor..cursor + 4].try_into().ok()?);
800 cursor += 4;
801 val
802 }};
803 }
804
805 let width = read_u32!();
806 let height = read_u32!();
807 let spread = read_f32!();
808 let font_size_px = read_f32!();
809 let num_glyphs = read_u32!();
810
811 if (spread - config.spread).abs() > 0.01 || (font_size_px - config.output_size as f32).abs() > 0.01 {
813 return None;
814 }
815
816 let mut metrics = HashMap::new();
817 for _ in 0..num_glyphs {
818 let ch_u32 = read_u32!();
819 let ch = char::from_u32(ch_u32)?;
820 let uv_rect = [read_f32!(), read_f32!(), read_f32!(), read_f32!()];
821 let size = glam::Vec2::new(read_f32!(), read_f32!());
822 let bearing = glam::Vec2::new(read_f32!(), read_f32!());
823 let advance = read_f32!();
824 metrics.insert(ch, SdfGlyphMetric {
825 uv_rect,
826 size,
827 bearing,
828 advance,
829 });
830 }
831
832 let pixel_count = (width * height) as usize;
833 if cursor + pixel_count > data.len() {
834 return None;
835 }
836 let pixels = data[cursor..cursor + pixel_count].to_vec();
837
838 Some(SdfAtlasData {
839 pixels,
840 width,
841 height,
842 channels: 1,
843 metrics,
844 spread,
845 font_size_px,
846 })
847}
848
849#[cfg(test)]
852mod tests {
853 use super::*;
854
855 #[test]
856 fn dead_reckoning_zero_for_boundary() {
857 let bitmap = vec![
859 false, false, false,
860 false, true, false,
861 false, false, false,
862 ];
863 let sdf = compute_sdf(&bitmap, 3, 3);
864 assert!(sdf[4] > 0.0);
866 assert!(sdf[0] < 0.0);
868 }
869
870 #[test]
871 fn dead_reckoning_all_inside() {
872 let bitmap = vec![true; 9];
873 let udf = dead_reckoning_udf(&bitmap, 3, 3);
874 let sdf = compute_sdf(&bitmap, 3, 3);
879 for &d in &sdf {
880 assert!(d >= 0.0);
881 }
882 }
883
884 #[test]
885 fn shelf_packer_fits_glyphs() {
886 let mut packer = ShelfPacker::new(128, 128);
887 let pos1 = packer.pack(32, 32);
888 assert!(pos1.is_some());
889 let pos2 = packer.pack(32, 32);
890 assert!(pos2.is_some());
891 assert_ne!(pos1, pos2);
892 }
893
894 #[test]
895 fn shelf_packer_new_shelf() {
896 let mut packer = ShelfPacker::new(64, 128);
897 let _ = packer.pack(40, 30); let pos2 = packer.pack(40, 30); assert!(pos2.is_some());
900 assert_eq!(pos2.unwrap().0, 0); assert_eq!(pos2.unwrap().1, 30); }
903
904 #[test]
905 fn shelf_packer_overflow() {
906 let mut packer = ShelfPacker::new(64, 64);
907 let _ = packer.pack(64, 64); let pos = packer.pack(10, 10); assert!(pos.is_none());
910 }
911
912 #[test]
913 fn bilinear_center() {
914 let data = vec![0.0, 1.0, 0.0, 1.0];
915 let val = sample_bilinear_f32(&data, 2, 2, 0.5, 0.5);
916 assert!((val - 0.5).abs() < 0.01);
917 }
918
919 #[test]
920 fn fallback_sdf_nonzero() {
921 let config = SdfConfig { output_size: 16, spread: 4.0, ..SdfConfig::default() };
922 let glyph = generate_fallback_sdf(&config);
923 assert!(!glyph.pixels.is_empty());
924 let cx = glyph.width / 2;
926 let cy = glyph.height / 2;
927 let center = glyph.pixels[(cy * glyph.width + cx) as usize];
928 assert!(center > 100, "Center pixel should be > 100, got {}", center);
929 }
930}