1use std::collections::HashMap;
7
8use crate::edt::SdfError;
9
10#[derive(Clone, Debug)]
12pub struct SdfTile {
13 pub glyph_id: u16,
15 pub width: u32,
17 pub height: u32,
19 pub data: Vec<u8>,
21 pub bearing_x: i32,
23 pub bearing_y: i32,
25 pub advance_x: f32,
27}
28
29impl SdfTile {
30 #[allow(clippy::too_many_arguments)]
44 pub fn from_coverage(
45 glyph_id: u16,
46 coverage: &[f32],
47 width: usize,
48 height: usize,
49 spread: f32,
50 bearing_x: i32,
51 bearing_y: i32,
52 advance_x: f32,
53 ) -> Result<Self, SdfError> {
54 if width == 0 || height == 0 {
55 return Err(SdfError::ZeroSize);
56 }
57 if coverage.len() != width * height {
58 return Err(SdfError::InvalidInput(format!(
59 "coverage length {} != width({}) * height({})",
60 coverage.len(),
61 width,
62 height
63 )));
64 }
65 let coverage_u8: Vec<u8> = coverage
67 .iter()
68 .map(|&v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
69 .collect();
70 let sdf_data = crate::edt::compute_sdf(&coverage_u8, width, height, spread, 0)?;
71 Ok(Self {
72 glyph_id,
73 width: width as u32,
74 height: height as u32,
75 data: sdf_data,
76 bearing_x,
77 bearing_y,
78 advance_x,
79 })
80 }
81}
82
83#[derive(Clone, Debug)]
85pub struct UvRect {
86 pub u_min: f32,
88 pub v_min: f32,
90 pub u_max: f32,
92 pub v_max: f32,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
100pub enum PackingAlgorithm {
101 #[default]
103 Shelf,
104 MaxRects,
106 Skyline,
108}
109
110#[derive(Debug, Clone)]
112pub struct AtlasOptions {
113 pub atlas_size: u32,
115 pub padding: u32,
117 pub max_size: Option<u32>,
119 pub algorithm: PackingAlgorithm,
121}
122
123impl Default for AtlasOptions {
124 fn default() -> Self {
125 Self {
126 atlas_size: 512,
127 padding: 1,
128 max_size: None,
129 algorithm: PackingAlgorithm::Shelf,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Default)]
136pub struct AtlasStats {
137 pub tiles_packed: usize,
139 pub tiles_dropped: usize,
141 pub utilization: f32,
143 pub wasted_pixels: u32,
145}
146
147struct PackResult {
150 texture: Vec<u8>,
151 uv_map: HashMap<u16, UvRect>,
152 dropped: usize,
154 used_pixels: u32,
156 cursor_x: u32,
158 shelf_y: u32,
159 shelf_max_h: u32,
160}
161
162fn pack_inner(tiles: &[SdfTile], atlas_w: u32, atlas_h: u32, padding: u32) -> PackResult {
169 let tex_len = atlas_w as usize * atlas_h as usize;
170 let mut texture = vec![0u8; tex_len];
171 let mut uv_map: HashMap<u16, UvRect> = HashMap::new();
172
173 let mut cx: u32 = padding;
174 let mut cy: u32 = padding;
175 let mut row_h: u32 = 0;
176 let mut dropped: usize = 0;
177 let mut used_pixels: u32 = 0;
178
179 for tile in tiles {
180 let needed_w = tile.width + padding;
181 if cx + tile.width > atlas_w.saturating_sub(padding) {
183 cx = padding;
184 cy += row_h + padding;
185 row_h = 0;
186 }
187
188 if cy + tile.height > atlas_h.saturating_sub(padding) {
190 dropped += 1;
191 continue;
192 }
193
194 for y in 0..tile.height {
196 for x in 0..tile.width {
197 let src_idx = (y * tile.width + x) as usize;
198 let dst_idx = ((cy + y) * atlas_w + (cx + x)) as usize;
199 if dst_idx < texture.len() && src_idx < tile.data.len() {
200 texture[dst_idx] = tile.data[src_idx];
201 }
202 }
203 }
204
205 uv_map.insert(
206 tile.glyph_id,
207 UvRect {
208 u_min: cx as f32 / atlas_w as f32,
209 v_min: cy as f32 / atlas_h as f32,
210 u_max: (cx + tile.width) as f32 / atlas_w as f32,
211 v_max: (cy + tile.height) as f32 / atlas_h as f32,
212 },
213 );
214
215 used_pixels += tile.width * tile.height;
216 cx += needed_w;
217 row_h = row_h.max(tile.height);
218 }
219
220 PackResult {
221 texture,
222 uv_map,
223 dropped,
224 used_pixels,
225 cursor_x: cx,
226 shelf_y: cy,
227 shelf_max_h: row_h,
228 }
229}
230
231#[derive(Clone, Copy, Debug)]
235struct Rect {
236 x: u32,
237 y: u32,
238 w: u32,
239 h: u32,
240}
241
242impl Rect {
243 fn contains(&self, other: &Rect) -> bool {
245 other.x >= self.x
246 && other.y >= self.y
247 && other.x + other.w <= self.x + self.w
248 && other.y + other.h <= self.y + self.h
249 }
250}
251
252struct MaxRectsState {
254 free_rects: Vec<Rect>,
255}
256
257impl MaxRectsState {
258 fn new(atlas_w: u32, atlas_h: u32) -> Self {
260 Self {
261 free_rects: vec![Rect {
262 x: 0,
263 y: 0,
264 w: atlas_w,
265 h: atlas_h,
266 }],
267 }
268 }
269}
270
271fn insert_maxrects(
282 state: &mut MaxRectsState,
283 tile_w: u32,
284 tile_h: u32,
285 padding: u32,
286) -> Option<Rect> {
287 let needed_w = tile_w + padding;
289 let needed_h = tile_h + padding;
290
291 let best_idx = state
293 .free_rects
294 .iter()
295 .enumerate()
296 .filter(|(_, r)| r.w >= needed_w && r.h >= needed_h)
297 .min_by_key(|(_, r)| {
298 let leftover_w = r.w.saturating_sub(needed_w);
299 let leftover_h = r.h.saturating_sub(needed_h);
300 leftover_w.min(leftover_h)
301 })
302 .map(|(i, _)| i);
303
304 let idx = best_idx?;
305 let chosen = state.free_rects[idx];
306
307 let placed = Rect {
309 x: chosen.x,
310 y: chosen.y,
311 w: tile_w,
312 h: tile_h,
313 };
314
315 let mut new_rects: Vec<Rect> = Vec::with_capacity(4);
320
321 if chosen.x + chosen.w > placed.x + needed_w {
323 let nr = Rect {
324 x: placed.x + needed_w,
325 y: chosen.y,
326 w: chosen.x + chosen.w - (placed.x + needed_w),
327 h: chosen.h,
328 };
329 if nr.w > 0 && nr.h > 0 {
330 new_rects.push(nr);
331 }
332 }
333 if chosen.y + chosen.h > placed.y + needed_h {
335 let nr = Rect {
336 x: chosen.x,
337 y: placed.y + needed_h,
338 w: chosen.w,
339 h: chosen.y + chosen.h - (placed.y + needed_h),
340 };
341 if nr.w > 0 && nr.h > 0 {
342 new_rects.push(nr);
343 }
344 }
345 if placed.x > chosen.x {
347 let nr = Rect {
348 x: chosen.x,
349 y: chosen.y,
350 w: placed.x - chosen.x,
351 h: chosen.h,
352 };
353 if nr.w > 0 && nr.h > 0 {
354 new_rects.push(nr);
355 }
356 }
357 if placed.y > chosen.y {
359 let nr = Rect {
360 x: chosen.x,
361 y: chosen.y,
362 w: chosen.w,
363 h: placed.y - chosen.y,
364 };
365 if nr.w > 0 && nr.h > 0 {
366 new_rects.push(nr);
367 }
368 }
369
370 state.free_rects.remove(idx);
372 state.free_rects.extend_from_slice(&new_rects);
373
374 let mut to_remove: Vec<bool> = vec![false; state.free_rects.len()];
377 let len = state.free_rects.len();
378 for i in 0..len {
379 if to_remove[i] {
380 continue;
381 }
382 for j in 0..len {
383 if i == j || to_remove[j] {
384 continue;
385 }
386 if state.free_rects[j].contains(&state.free_rects[i]) {
387 to_remove[i] = true;
388 break;
389 }
390 }
391 }
392 let mut keep_idx = 0;
393 state.free_rects.retain(|_| {
394 let keep = !to_remove[keep_idx];
395 keep_idx += 1;
396 keep
397 });
398
399 Some(placed)
400}
401
402fn pack_inner_maxrects(tiles: &[SdfTile], atlas_w: u32, atlas_h: u32, padding: u32) -> PackResult {
404 let tex_len = atlas_w as usize * atlas_h as usize;
405 let mut texture = vec![0u8; tex_len];
406 let mut uv_map: HashMap<u16, UvRect> = HashMap::new();
407 let mut dropped: usize = 0;
408 let mut used_pixels: u32 = 0;
409
410 let mut state = MaxRectsState::new(atlas_w, atlas_h);
411
412 for tile in tiles {
413 match insert_maxrects(&mut state, tile.width, tile.height, padding) {
414 None => {
415 dropped += 1;
416 }
417 Some(placed) => {
418 for y in 0..tile.height {
420 for x in 0..tile.width {
421 let src_idx = (y * tile.width + x) as usize;
422 let dst_idx = ((placed.y + y) * atlas_w + (placed.x + x)) as usize;
423 if dst_idx < texture.len() && src_idx < tile.data.len() {
424 texture[dst_idx] = tile.data[src_idx];
425 }
426 }
427 }
428
429 uv_map.insert(
430 tile.glyph_id,
431 UvRect {
432 u_min: placed.x as f32 / atlas_w as f32,
433 v_min: placed.y as f32 / atlas_h as f32,
434 u_max: (placed.x + placed.w) as f32 / atlas_w as f32,
435 v_max: (placed.y + placed.h) as f32 / atlas_h as f32,
436 },
437 );
438
439 used_pixels += tile.width * tile.height;
440 }
441 }
442 }
443
444 PackResult {
445 texture,
446 uv_map,
447 dropped,
448 used_pixels,
449 cursor_x: 0,
450 shelf_y: 0,
451 shelf_max_h: 0,
452 }
453}
454
455struct SkylineSegment {
459 x: u32,
461 y_top: u32,
463 width: u32,
465}
466
467struct SkylineState {
469 segments: Vec<SkylineSegment>,
470 atlas_w: u32,
471 atlas_h: u32,
472}
473
474impl SkylineState {
475 fn new(atlas_w: u32, atlas_h: u32) -> Self {
477 Self {
478 segments: vec![SkylineSegment {
479 x: 0,
480 y_top: 0,
481 width: atlas_w,
482 }],
483 atlas_w,
484 atlas_h,
485 }
486 }
487}
488
489fn insert_skyline(
495 state: &mut SkylineState,
496 tile_w: u32,
497 tile_h: u32,
498 padding: u32,
499) -> Option<(u32, u32)> {
500 let needed_w = tile_w + padding;
501 let n = state.segments.len();
502
503 let mut best_y = u32::MAX;
506 let mut best_x = 0u32;
507
508 'outer: for i in 0..n {
509 let x_start = state.segments[i].x;
510 if x_start + needed_w > state.atlas_w.saturating_sub(padding) {
512 continue;
513 }
514
515 let mut max_y = 0u32;
516 let mut covered_w = 0u32;
517
518 for j in i..n {
519 let seg = &state.segments[j];
520 max_y = max_y.max(seg.y_top);
521 covered_w += seg.width;
522
523 if covered_w >= needed_w {
524 if max_y + tile_h + padding <= state.atlas_h && max_y < best_y {
526 best_y = max_y;
527 best_x = x_start;
528 }
529 continue 'outer;
530 }
531 }
532 }
533
534 if best_y == u32::MAX {
535 return None; }
537
538 let place_x = best_x;
539 let place_y = best_y;
540 let new_top = best_y + tile_h + padding;
541 let tile_right = place_x + needed_w;
542
543 let mut new_segments: Vec<SkylineSegment> = Vec::with_capacity(state.segments.len() + 2);
546
547 for seg in &state.segments {
548 let seg_right = seg.x + seg.width;
549 if seg_right <= place_x || seg.x >= tile_right {
550 new_segments.push(SkylineSegment {
552 x: seg.x,
553 y_top: seg.y_top,
554 width: seg.width,
555 });
556 } else {
557 if seg.x < place_x {
561 new_segments.push(SkylineSegment {
562 x: seg.x,
563 y_top: seg.y_top,
564 width: place_x - seg.x,
565 });
566 }
567 }
568 }
569
570 new_segments.push(SkylineSegment {
572 x: place_x,
573 y_top: new_top,
574 width: needed_w,
575 });
576
577 for seg in &state.segments {
579 let seg_right = seg.x + seg.width;
580 if seg.x < tile_right && seg_right > tile_right {
581 new_segments.push(SkylineSegment {
582 x: tile_right,
583 y_top: seg.y_top,
584 width: seg_right - tile_right,
585 });
586 }
587 }
588
589 new_segments.sort_by_key(|s| s.x);
591
592 let mut merged: Vec<SkylineSegment> = Vec::with_capacity(new_segments.len());
594 for seg in new_segments {
595 if let Some(last) = merged.last_mut() {
596 if last.y_top == seg.y_top && last.x + last.width == seg.x {
597 last.width += seg.width;
598 continue;
599 }
600 }
601 merged.push(seg);
602 }
603
604 state.segments = merged;
605 Some((place_x, place_y))
606}
607
608fn pack_inner_skyline(tiles: &[SdfTile], atlas_w: u32, atlas_h: u32, padding: u32) -> PackResult {
610 let tex_len = atlas_w as usize * atlas_h as usize;
611 let mut texture = vec![0u8; tex_len];
612 let mut uv_map: HashMap<u16, UvRect> = HashMap::new();
613 let mut dropped: usize = 0;
614 let mut used_pixels: u32 = 0;
615
616 let mut state = SkylineState::new(atlas_w, atlas_h);
617
618 for tile in tiles {
619 match insert_skyline(&mut state, tile.width, tile.height, padding) {
620 None => {
621 dropped += 1;
622 }
623 Some((px, py)) => {
624 for y in 0..tile.height {
625 for x in 0..tile.width {
626 let src_idx = (y * tile.width + x) as usize;
627 let dst_idx = ((py + y) * atlas_w + (px + x)) as usize;
628 if dst_idx < texture.len() && src_idx < tile.data.len() {
629 texture[dst_idx] = tile.data[src_idx];
630 }
631 }
632 }
633
634 uv_map.insert(
635 tile.glyph_id,
636 UvRect {
637 u_min: px as f32 / atlas_w as f32,
638 v_min: py as f32 / atlas_h as f32,
639 u_max: (px + tile.width) as f32 / atlas_w as f32,
640 v_max: (py + tile.height) as f32 / atlas_h as f32,
641 },
642 );
643
644 used_pixels += tile.width * tile.height;
645 }
646 }
647 }
648
649 PackResult {
650 texture,
651 uv_map,
652 dropped,
653 used_pixels,
654 cursor_x: 0,
655 shelf_y: 0,
656 shelf_max_h: 0,
657 }
658}
659
660pub struct MultiPageAtlas {
667 pub pages: Vec<SdfAtlas>,
669 pub page_size: u32,
671}
672
673impl MultiPageAtlas {
674 pub fn pack(tiles: &[SdfTile], page_size: u32, padding: u32) -> Self {
680 let mut pages: Vec<SdfAtlas> = Vec::new();
681 let atlas_size = page_size.next_power_of_two().max(64);
682 let mut queue: Vec<SdfTile> = tiles.to_vec();
684
685 while !queue.is_empty() {
686 let tex_len = atlas_size as usize * atlas_size as usize;
687 let mut page = SdfAtlas {
688 width: atlas_size,
689 height: atlas_size,
690 texture: vec![0u8; tex_len],
691 uv_map: HashMap::new(),
692 cursor_x: padding,
693 shelf_y: padding,
694 shelf_max_h: 0,
695 padding,
696 };
697
698 let mut any_packed = false;
699 let mut leftover: Vec<SdfTile> = Vec::new();
700
701 for tile in &queue {
702 if tile.width + 2 * padding > atlas_size || tile.height + 2 * padding > atlas_size {
704 continue;
705 }
706 match page.add_tile(tile) {
707 Some(_) => {
708 any_packed = true;
709 }
710 None => {
711 leftover.push(tile.clone());
712 }
713 }
714 }
715
716 pages.push(page);
717
718 if !any_packed {
719 break;
721 }
722
723 queue = leftover;
724 }
725
726 Self { pages, page_size }
727 }
728
729 pub fn lookup(&self, glyph_id: u16) -> Option<(usize, &UvRect)> {
733 self.pages
734 .iter()
735 .enumerate()
736 .find_map(|(i, page)| page.uv_map.get(&glyph_id).map(|uv| (i, uv)))
737 }
738}
739
740pub fn pack_growing(
753 tiles: &[SdfTile],
754 initial_size: u32,
755 max_size: u32,
756 padding: u32,
757 algorithm: PackingAlgorithm,
758) -> (SdfAtlas, AtlasStats) {
759 let mut current_size = initial_size.max(1);
760 loop {
761 let options = AtlasOptions {
762 atlas_size: current_size,
763 padding,
764 max_size: Some(max_size),
765 algorithm,
766 };
767 let (atlas, stats) = SdfAtlas::pack_with_options(tiles, &options);
768 if stats.tiles_dropped == 0 || current_size >= max_size {
769 return (atlas, stats);
770 }
771 current_size = (current_size.saturating_mul(2)).min(max_size);
773 }
774}
775
776pub struct SdfAtlas {
786 pub width: u32,
788 pub height: u32,
790 pub texture: Vec<u8>,
792 pub uv_map: HashMap<u16, UvRect>,
794 cursor_x: u32,
796 shelf_y: u32,
798 shelf_max_h: u32,
800 padding: u32,
802}
803
804const MAGIC: &[u8; 4] = b"SDFA";
807const VERSION: u32 = 1;
808const ENTRY_SIZE: usize = 28;
810const ENTRIES_OFFSET: usize = 20;
812
813impl SdfAtlas {
814 pub fn new(width: u32, height: u32) -> Self {
819 let capacity = width as usize * height as usize;
820 Self {
821 width,
822 height,
823 texture: vec![0u8; capacity],
824 uv_map: HashMap::new(),
825 cursor_x: 0,
826 shelf_y: 0,
827 shelf_max_h: 0,
828 padding: 0,
829 }
830 }
831
832 pub fn with_capacity(
840 width: u32,
841 height: u32,
842 tile_count_hint: usize,
843 average_tile_area: usize,
844 ) -> Self {
845 let capacity = (tile_count_hint * average_tile_area).min((width * height) as usize);
846 let mut atlas = Self::new(width, height);
847 atlas
848 .texture
849 .reserve(capacity.saturating_sub(atlas.texture.len()));
850 atlas
851 }
852
853 pub fn pack(tiles: &[SdfTile]) -> Self {
860 if tiles.is_empty() {
861 return Self {
862 width: 1,
863 height: 1,
864 texture: vec![0],
865 uv_map: HashMap::new(),
866 cursor_x: 0,
867 shelf_y: 0,
868 shelf_max_h: 0,
869 padding: 0,
870 };
871 }
872
873 let tile_w = tiles[0].width;
875 let tile_h = tiles[0].height;
876 let count = tiles.len() as u32;
877 let cols = (count as f32).sqrt().ceil() as u32;
878 let rows = count.div_ceil(cols);
879 let atlas_w = (cols * tile_w).next_power_of_two().max(256);
880 let atlas_h = (rows * tile_h).next_power_of_two().max(256);
881
882 let res = pack_inner(tiles, atlas_w, atlas_h, 0);
883
884 Self {
885 width: atlas_w,
886 height: atlas_h,
887 texture: res.texture,
888 uv_map: res.uv_map,
889 cursor_x: res.cursor_x,
890 shelf_y: res.shelf_y,
891 shelf_max_h: res.shelf_max_h,
892 padding: 0,
893 }
894 }
895
896 pub fn pack_with_options(tiles: &[SdfTile], options: &AtlasOptions) -> (Self, AtlasStats) {
903 let atlas_size = options.atlas_size.next_power_of_two().max(64);
904
905 if tiles.is_empty() {
906 let atlas = Self {
907 width: atlas_size,
908 height: atlas_size,
909 texture: vec![0u8; (atlas_size as usize) * (atlas_size as usize)],
910 uv_map: HashMap::new(),
911 cursor_x: options.padding,
912 shelf_y: options.padding,
913 shelf_max_h: 0,
914 padding: options.padding,
915 };
916 return (
917 atlas,
918 AtlasStats {
919 tiles_packed: 0,
920 tiles_dropped: 0,
921 utilization: 0.0,
922 wasted_pixels: atlas_size * atlas_size,
923 },
924 );
925 }
926
927 let res = match options.algorithm {
928 PackingAlgorithm::Shelf => pack_inner(tiles, atlas_size, atlas_size, options.padding),
929 PackingAlgorithm::MaxRects => {
930 pack_inner_maxrects(tiles, atlas_size, atlas_size, options.padding)
931 }
932 PackingAlgorithm::Skyline => {
933 pack_inner_skyline(tiles, atlas_size, atlas_size, options.padding)
934 }
935 };
936
937 let total = atlas_size * atlas_size;
938 let tiles_packed = tiles.len() - res.dropped;
939 let utilization = res.used_pixels as f32 / total as f32;
940 let wasted_pixels = total.saturating_sub(res.used_pixels);
941
942 let stats = AtlasStats {
943 tiles_packed,
944 tiles_dropped: res.dropped,
945 utilization,
946 wasted_pixels,
947 };
948
949 let atlas = Self {
950 width: atlas_size,
951 height: atlas_size,
952 texture: res.texture,
953 uv_map: res.uv_map,
954 cursor_x: res.cursor_x,
955 shelf_y: res.shelf_y,
956 shelf_max_h: res.shelf_max_h,
957 padding: options.padding,
958 };
959
960 (atlas, stats)
961 }
962
963 pub fn pack_growing(tiles: &[SdfTile], initial_size: u32, max_size: u32) -> (Self, AtlasStats) {
968 pack_growing(tiles, initial_size, max_size, 1, PackingAlgorithm::Shelf)
969 }
970
971 pub fn add_tile(&mut self, tile: &SdfTile) -> Option<UvRect> {
978 let pad = self.padding;
979 let atlas_w = self.width;
980 let atlas_h = self.height;
981
982 if self.cursor_x + tile.width > atlas_w.saturating_sub(pad) {
984 self.cursor_x = pad;
985 self.shelf_y += self.shelf_max_h + pad;
986 self.shelf_max_h = 0;
987 }
988
989 if self.shelf_y + tile.height > atlas_h.saturating_sub(pad) {
991 return None;
992 }
993
994 let cx = self.cursor_x;
995 let cy = self.shelf_y;
996
997 for y in 0..tile.height {
999 for x in 0..tile.width {
1000 let src_idx = (y * tile.width + x) as usize;
1001 let dst_idx = ((cy + y) * atlas_w + (cx + x)) as usize;
1002 if dst_idx < self.texture.len() && src_idx < tile.data.len() {
1003 self.texture[dst_idx] = tile.data[src_idx];
1004 }
1005 }
1006 }
1007
1008 let uv = UvRect {
1009 u_min: cx as f32 / atlas_w as f32,
1010 v_min: cy as f32 / atlas_h as f32,
1011 u_max: (cx + tile.width) as f32 / atlas_w as f32,
1012 v_max: (cy + tile.height) as f32 / atlas_h as f32,
1013 };
1014
1015 self.uv_map.insert(tile.glyph_id, uv.clone());
1016 self.cursor_x += tile.width + pad;
1017 self.shelf_max_h = self.shelf_max_h.max(tile.height);
1018
1019 Some(uv)
1020 }
1021
1022 pub fn remove_tile(&mut self, glyph_id: u16) -> bool {
1030 self.uv_map.remove(&glyph_id).is_some()
1031 }
1032
1033 pub fn to_bytes(&self) -> Vec<u8> {
1055 let num_entries = self.uv_map.len() as u32;
1056 let texture_len = self.width as usize * self.height as usize;
1057 let total = ENTRIES_OFFSET + num_entries as usize * ENTRY_SIZE + texture_len;
1058 let mut buf = Vec::with_capacity(total);
1059
1060 buf.extend_from_slice(MAGIC);
1062 buf.extend_from_slice(&VERSION.to_le_bytes());
1063 buf.extend_from_slice(&self.width.to_le_bytes());
1064 buf.extend_from_slice(&self.height.to_le_bytes());
1065 buf.extend_from_slice(&num_entries.to_le_bytes());
1066
1067 let mut entries: Vec<(&u16, &UvRect)> = self.uv_map.iter().collect();
1069 entries.sort_by_key(|(gid, _)| *gid);
1070
1071 for (glyph_id, uv) in entries {
1072 buf.extend_from_slice(&glyph_id.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&uv.u_min.to_bits().to_le_bytes()); buf.extend_from_slice(&uv.v_min.to_bits().to_le_bytes()); buf.extend_from_slice(&uv.u_max.to_bits().to_le_bytes()); buf.extend_from_slice(&uv.v_max.to_bits().to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); }
1080
1081 buf.extend_from_slice(&self.texture);
1083
1084 buf
1085 }
1086
1087 pub fn from_bytes(data: &[u8]) -> Result<Self, SdfError> {
1095 if data.len() < ENTRIES_OFFSET {
1097 return Err(SdfError::InvalidData(format!(
1098 "buffer too short: need at least {ENTRIES_OFFSET} bytes, got {}",
1099 data.len()
1100 )));
1101 }
1102
1103 if &data[0..4] != MAGIC {
1105 return Err(SdfError::InvalidData(format!(
1106 "bad magic: expected {:?}, got {:?}",
1107 MAGIC,
1108 &data[0..4]
1109 )));
1110 }
1111
1112 let version = u32::from_le_bytes(
1114 data[4..8]
1115 .try_into()
1116 .map_err(|_| SdfError::InvalidData("cannot read version".into()))?,
1117 );
1118 if version != VERSION {
1119 return Err(SdfError::InvalidData(format!(
1120 "unsupported version {version}, expected {VERSION}"
1121 )));
1122 }
1123
1124 let atlas_w = u32::from_le_bytes(
1125 data[8..12]
1126 .try_into()
1127 .map_err(|_| SdfError::InvalidData("cannot read atlas_w".into()))?,
1128 );
1129 let atlas_h = u32::from_le_bytes(
1130 data[12..16]
1131 .try_into()
1132 .map_err(|_| SdfError::InvalidData("cannot read atlas_h".into()))?,
1133 );
1134 let num_entries = u32::from_le_bytes(
1135 data[16..20]
1136 .try_into()
1137 .map_err(|_| SdfError::InvalidData("cannot read num_entries".into()))?,
1138 ) as usize;
1139
1140 let texture_len = atlas_w as usize * atlas_h as usize;
1142 let expected_len = ENTRIES_OFFSET + num_entries * ENTRY_SIZE + texture_len;
1143 if data.len() < expected_len {
1144 return Err(SdfError::InvalidData(format!(
1145 "buffer too short: expected {expected_len} bytes, got {}",
1146 data.len()
1147 )));
1148 }
1149
1150 let mut uv_map: HashMap<u16, UvRect> = HashMap::with_capacity(num_entries);
1152 for i in 0..num_entries {
1153 let base = ENTRIES_OFFSET + i * ENTRY_SIZE;
1154 let glyph_id = u16::from_le_bytes(
1155 data[base..base + 2]
1156 .try_into()
1157 .map_err(|_| SdfError::InvalidData(format!("entry {i}: bad glyph_id")))?,
1158 );
1159 let u_min = f32::from_bits(u32::from_le_bytes(
1161 data[base + 4..base + 8]
1162 .try_into()
1163 .map_err(|_| SdfError::InvalidData(format!("entry {i}: bad u_min")))?,
1164 ));
1165 let v_min = f32::from_bits(u32::from_le_bytes(
1166 data[base + 8..base + 12]
1167 .try_into()
1168 .map_err(|_| SdfError::InvalidData(format!("entry {i}: bad v_min")))?,
1169 ));
1170 let u_max = f32::from_bits(u32::from_le_bytes(
1171 data[base + 12..base + 16]
1172 .try_into()
1173 .map_err(|_| SdfError::InvalidData(format!("entry {i}: bad u_max")))?,
1174 ));
1175 let v_max = f32::from_bits(u32::from_le_bytes(
1176 data[base + 16..base + 20]
1177 .try_into()
1178 .map_err(|_| SdfError::InvalidData(format!("entry {i}: bad v_max")))?,
1179 ));
1180 uv_map.insert(
1182 glyph_id,
1183 UvRect {
1184 u_min,
1185 v_min,
1186 u_max,
1187 v_max,
1188 },
1189 );
1190 }
1191
1192 let tex_start = ENTRIES_OFFSET + num_entries * ENTRY_SIZE;
1194 let texture = data[tex_start..tex_start + texture_len].to_vec();
1195
1196 Ok(Self {
1197 width: atlas_w,
1198 height: atlas_h,
1199 texture,
1200 uv_map,
1201 cursor_x: 0,
1202 shelf_y: 0,
1203 shelf_max_h: 0,
1204 padding: 0,
1205 })
1206 }
1207
1208 pub fn from_static(data: &'static [u8]) -> Result<Self, SdfError> {
1223 Self::from_bytes(data)
1224 }
1225
1226 pub fn export_png(&self, path: &std::path::Path) -> Result<(), SdfError> {
1231 use std::io::BufWriter;
1232 let file = std::fs::File::create(path).map_err(|e| SdfError::Io(e.to_string()))?;
1233 let w = BufWriter::new(file);
1234 let mut encoder = png::Encoder::new(w, self.width, self.height);
1235 encoder.set_color(png::ColorType::Grayscale);
1236 encoder.set_depth(png::BitDepth::Eight);
1237 let mut writer = encoder
1238 .write_header()
1239 .map_err(|e| SdfError::Io(e.to_string()))?;
1240 writer
1241 .write_image_data(&self.texture)
1242 .map_err(|e| SdfError::Io(e.to_string()))
1243 }
1244}
1245
1246pub struct MsdfAtlas {
1254 pub width: u32,
1256 pub height: u32,
1258 pub texture: Vec<u8>,
1260 pub uv_map: HashMap<u16, UvRect>,
1262}
1263
1264impl MsdfAtlas {
1265 pub fn pack(tiles: &[crate::msdf::MsdfTile], atlas_size: u32) -> Self {
1272 if tiles.is_empty() {
1273 return Self {
1274 width: 1,
1275 height: 1,
1276 texture: vec![0u8; 3],
1277 uv_map: HashMap::new(),
1278 };
1279 }
1280
1281 let atlas_w = atlas_size.next_power_of_two().max(64);
1282 let atlas_h = atlas_size.next_power_of_two().max(64);
1283 let tex_len = atlas_w as usize * atlas_h as usize * 3;
1284 let mut texture = vec![0u8; tex_len];
1285 let mut uv_map = HashMap::new();
1286
1287 let mut cx = 0u32;
1288 let mut cy = 0u32;
1289 let mut row_h = 0u32;
1290
1291 for tile in tiles {
1292 if cx + tile.width > atlas_w {
1294 cx = 0;
1295 cy += row_h;
1296 row_h = 0;
1297 }
1298
1299 if cy + tile.height > atlas_h {
1301 continue;
1302 }
1303
1304 for y in 0..tile.height {
1306 for x in 0..tile.width {
1307 let src_base = (y * tile.width + x) as usize * 3;
1308 let dst_base = ((cy + y) * atlas_w + (cx + x)) as usize * 3;
1309 if dst_base + 2 < texture.len() && src_base + 2 < tile.data.len() {
1310 texture[dst_base] = tile.data[src_base];
1311 texture[dst_base + 1] = tile.data[src_base + 1];
1312 texture[dst_base + 2] = tile.data[src_base + 2];
1313 }
1314 }
1315 }
1316
1317 uv_map.insert(
1318 tile.glyph_id,
1319 UvRect {
1320 u_min: cx as f32 / atlas_w as f32,
1321 v_min: cy as f32 / atlas_h as f32,
1322 u_max: (cx + tile.width) as f32 / atlas_w as f32,
1323 v_max: (cy + tile.height) as f32 / atlas_h as f32,
1324 },
1325 );
1326
1327 cx += tile.width;
1328 row_h = row_h.max(tile.height);
1329 }
1330
1331 Self {
1332 width: atlas_w,
1333 height: atlas_h,
1334 texture,
1335 uv_map,
1336 }
1337 }
1338
1339 pub fn export_png(&self, path: &std::path::Path) -> Result<(), SdfError> {
1344 use std::io::BufWriter;
1345 let file = std::fs::File::create(path).map_err(|e| SdfError::Io(e.to_string()))?;
1346 let w = BufWriter::new(file);
1347 let mut encoder = png::Encoder::new(w, self.width, self.height);
1348 encoder.set_color(png::ColorType::Rgb);
1349 encoder.set_depth(png::BitDepth::Eight);
1350 let mut writer = encoder
1351 .write_header()
1352 .map_err(|e| SdfError::Io(e.to_string()))?;
1353 writer
1354 .write_image_data(&self.texture)
1355 .map_err(|e| SdfError::Io(e.to_string()))
1356 }
1357}
1358
1359#[cfg(test)]
1360mod tests {
1361 use super::*;
1362 use crate::msdf::MsdfTile;
1363
1364 fn make_sdf_tile(glyph_id: u16, w: u32, h: u32) -> SdfTile {
1365 SdfTile {
1366 glyph_id,
1367 width: w,
1368 height: h,
1369 data: vec![128u8; (w * h) as usize],
1370 bearing_x: 0,
1371 bearing_y: 0,
1372 advance_x: w as f32,
1373 }
1374 }
1375
1376 #[test]
1377 fn msdf_atlas_packs_many_tiles() {
1378 let tiles: Vec<MsdfTile> = (0..20u16)
1379 .map(|i| MsdfTile {
1380 glyph_id: i,
1381 width: 16,
1382 height: 16,
1383 data: vec![128u8; 16 * 16 * 3],
1384 bearing_x: 0.0,
1385 bearing_y: 0.0,
1386 advance_x: 16.0,
1387 })
1388 .collect();
1389 let atlas = MsdfAtlas::pack(&tiles, 256);
1390 assert_eq!(atlas.uv_map.len(), 20);
1391 for uv in atlas.uv_map.values() {
1392 assert!(uv.u_min >= 0.0 && uv.u_max <= 1.0);
1393 assert!(uv.v_min >= 0.0 && uv.v_max <= 1.0);
1394 }
1395 }
1396
1397 #[test]
1398 fn pack_with_options_returns_stats() {
1399 let tiles: Vec<SdfTile> = (0..10u16).map(|i| make_sdf_tile(i, 16, 16)).collect();
1400 let opts = AtlasOptions {
1401 atlas_size: 128,
1402 padding: 1,
1403 ..Default::default()
1404 };
1405 let (atlas, stats) = SdfAtlas::pack_with_options(&tiles, &opts);
1406 assert_eq!(stats.tiles_packed + stats.tiles_dropped, 10);
1407 assert!(
1408 stats.utilization > 0.0 && stats.utilization <= 1.0,
1409 "utilization out of range: {}",
1410 stats.utilization
1411 );
1412 assert_eq!(atlas.uv_map.len(), stats.tiles_packed);
1413 }
1414
1415 #[test]
1416 fn remove_tile_removes_from_map() {
1417 let tiles = vec![make_sdf_tile(42, 16, 16)];
1418 let mut atlas = SdfAtlas::pack(&tiles);
1419 assert!(atlas.uv_map.contains_key(&42));
1420 assert!(atlas.remove_tile(42));
1421 assert!(!atlas.uv_map.contains_key(&42));
1422 assert!(!atlas.remove_tile(42));
1424 }
1425
1426 #[test]
1427 fn add_tile_places_new_tile() {
1428 let tiles: Vec<SdfTile> = (0..4u16).map(|i| make_sdf_tile(i, 16, 16)).collect();
1429 let opts = AtlasOptions {
1430 atlas_size: 128,
1431 padding: 0,
1432 ..Default::default()
1433 };
1434 let (mut atlas, _) = SdfAtlas::pack_with_options(&tiles, &opts);
1435 let new_tile = make_sdf_tile(99, 16, 16);
1436 let uv = atlas.add_tile(&new_tile);
1437 assert!(uv.is_some(), "expected tile to be placed");
1438 assert!(atlas.uv_map.contains_key(&99));
1439 }
1440
1441 #[test]
1442 fn from_coverage_basic() {
1443 let coverage = vec![1.0f32; 8 * 8];
1445 let tile =
1446 SdfTile::from_coverage(7, &coverage, 8, 8, 4.0, 0, 0, 8.0).expect("from_coverage");
1447 assert_eq!(tile.glyph_id, 7);
1448 assert_eq!(tile.width, 8);
1449 assert_eq!(tile.height, 8);
1450 let center = tile.data[4 * 8 + 4];
1451 assert!(
1452 center > 128,
1453 "center of solid square should be inside, got {center}"
1454 );
1455 }
1456
1457 #[test]
1458 fn from_coverage_zero_size_errors() {
1459 let cov = vec![1.0f32; 0];
1460 assert!(SdfTile::from_coverage(0, &cov, 0, 8, 4.0, 0, 0, 0.0).is_err());
1461 assert!(SdfTile::from_coverage(0, &cov, 8, 0, 4.0, 0, 0, 0.0).is_err());
1462 }
1463
1464 #[test]
1465 fn test_maxrects_non_overlapping() {
1466 let tiles: Vec<SdfTile> = (0..20u16)
1467 .map(|id| SdfTile {
1468 glyph_id: id,
1469 width: 16,
1470 height: 16,
1471 data: vec![128u8; 256],
1472 bearing_x: 0,
1473 bearing_y: 0,
1474 advance_x: 16.0,
1475 })
1476 .collect();
1477 let options = AtlasOptions {
1478 atlas_size: 128,
1479 padding: 1,
1480 algorithm: PackingAlgorithm::MaxRects,
1481 ..Default::default()
1482 };
1483 let (atlas, stats) = SdfAtlas::pack_with_options(&tiles, &options);
1484 let uvs: Vec<_> = atlas.uv_map.values().collect();
1486 for i in 0..uvs.len() {
1487 for j in (i + 1)..uvs.len() {
1488 let a = uvs[i];
1489 let b = uvs[j];
1490 let overlap = a.u_min < b.u_max
1491 && a.u_max > b.u_min
1492 && a.v_min < b.v_max
1493 && a.v_max > b.v_min;
1494 assert!(!overlap, "UV rects {:?} and {:?} overlap", a, b);
1495 }
1496 }
1497 let _ = stats;
1498 }
1499
1500 #[test]
1501 fn test_growing_pack_packs_all() {
1502 let tiles: Vec<SdfTile> = (0..50u16)
1503 .map(|id| SdfTile {
1504 glyph_id: id,
1505 width: 32,
1506 height: 32,
1507 data: vec![128u8; 32 * 32],
1508 bearing_x: 0,
1509 bearing_y: 0,
1510 advance_x: 32.0,
1511 })
1512 .collect();
1513 let (atlas, stats) = pack_growing(&tiles, 64, 1024, 1, PackingAlgorithm::Shelf);
1514 assert_eq!(stats.tiles_dropped, 0, "all tiles should fit after growing");
1515 assert_eq!(atlas.uv_map.len(), 50);
1516 }
1517
1518 #[test]
1519 fn test_sdf_atlas_serialization_roundtrip() {
1520 let tiles: Vec<SdfTile> = (0..5u16)
1521 .map(|id| SdfTile {
1522 glyph_id: id,
1523 width: 8,
1524 height: 8,
1525 data: vec![id as u8; 64],
1526 bearing_x: 0,
1527 bearing_y: 0,
1528 advance_x: 8.0,
1529 })
1530 .collect();
1531 let (atlas, _) = SdfAtlas::pack_with_options(
1532 &tiles,
1533 &AtlasOptions {
1534 atlas_size: 64,
1535 padding: 0,
1536 ..Default::default()
1537 },
1538 );
1539 let bytes = atlas.to_bytes();
1540 let restored = SdfAtlas::from_bytes(&bytes).expect("deserialization");
1541 assert_eq!(restored.width, atlas.width);
1542 assert_eq!(restored.height, atlas.height);
1543 assert_eq!(restored.uv_map.len(), atlas.uv_map.len());
1544 for (gid, uv) in &atlas.uv_map {
1545 let r = &restored.uv_map[gid];
1546 assert!((r.u_min - uv.u_min).abs() < 1e-5);
1547 assert!((r.u_max - uv.u_max).abs() < 1e-5);
1548 }
1549 }
1550
1551 #[test]
1552 fn test_export_png_produces_valid_file() {
1553 use std::env::temp_dir;
1554 let tmp = temp_dir().join("test_sdf_atlas.png");
1555 let mut atlas = SdfAtlas::new(4, 4);
1556 atlas.texture = vec![128u8; 16];
1557 atlas.export_png(&tmp).expect("export should succeed");
1558 assert!(tmp.exists());
1559 assert!(tmp.metadata().expect("metadata").len() > 0);
1560 std::fs::remove_file(&tmp).ok();
1561 }
1562
1563 #[test]
1564 fn test_msdf_export_png_produces_valid_file() {
1565 use std::env::temp_dir;
1566 let tmp = temp_dir().join("test_msdf_atlas.png");
1567 let atlas = MsdfAtlas {
1568 width: 2,
1569 height: 2,
1570 texture: vec![128u8; 2 * 2 * 3],
1571 uv_map: Default::default(),
1572 };
1573 atlas.export_png(&tmp).expect("msdf export should succeed");
1574 assert!(tmp.exists());
1575 assert!(tmp.metadata().expect("metadata").len() > 0);
1576 std::fs::remove_file(&tmp).ok();
1577 }
1578
1579 #[test]
1580 fn test_pre_allocated_atlas_capacity() {
1581 let atlas = SdfAtlas::with_capacity(256, 256, 100, 64);
1582 assert_eq!(atlas.width, 256);
1583 assert_eq!(atlas.height, 256);
1584 assert!(atlas.texture.capacity() >= atlas.texture.len());
1585 }
1586
1587 #[test]
1588 fn test_sdf_atlas_new_zeroed() {
1589 let atlas = SdfAtlas::new(8, 8);
1590 assert_eq!(atlas.width, 8);
1591 assert_eq!(atlas.height, 8);
1592 assert_eq!(atlas.texture.len(), 64);
1593 assert!(atlas.texture.iter().all(|&b| b == 0));
1594 assert!(atlas.uv_map.is_empty());
1595 }
1596
1597 #[test]
1598 fn test_from_static_roundtrip() {
1599 let atlas = SdfAtlas::new(64, 64);
1602 let bytes = atlas.to_bytes();
1603 let static_bytes: &'static [u8] = Box::leak(bytes.into_boxed_slice());
1604 let roundtrip = SdfAtlas::from_static(static_bytes).expect("from_static should succeed");
1605 assert_eq!(roundtrip.width, 64);
1606 assert_eq!(roundtrip.height, 64);
1607 assert_eq!(roundtrip.uv_map.len(), 0);
1608 }
1609}