Skip to main content

cvkg_render_gpu/
surtr_util.rs

1//! Atlas loading and text shaping utilities.
2use crate::renderer::SurtrRenderer;
3use cvkg_core::{Rect, Renderer};
4
5
6impl SurtrRenderer {
7    /// load_image_to_heim — Packs a raw asset into the Mega-Heim.
8    /// This is used for common icons to enable aggressive batching (1 draw call).
9    pub fn load_image_to_heim(&mut self, name: &str, data: &[u8]) {
10        if self.image_uv_registry.contains(name) {
11            return;
12        }
13        let img_result = image::load_from_memory(data);
14        let img = match img_result {
15            Ok(img) => img.to_rgba8(),
16            Err(e) => {
17                log::error!("Failed to load image {} to heim: {}", name, e);
18                return;
19            }
20        };
21        let (width, height) = img.dimensions();
22
23        // Pack into heim
24        if let Some((x, y)) = self.heim_packer.pack(width, height) {
25            let uv_rect = Rect {
26                x: x as f32 / 4096.0,
27                y: y as f32 / 4096.0,
28                width: width as f32 / 4096.0,
29                height: height as f32 / 4096.0,
30            };
31
32            // Upload to GPU
33            self.queue.write_texture(
34                wgpu::TexelCopyTextureInfo {
35                    texture: &self.mega_heim_tex,
36                    mip_level: 0,
37                    origin: wgpu::Origin3d { x, y, z: 0 },
38                    aspect: wgpu::TextureAspect::All,
39                },
40                &img,
41                wgpu::TexelCopyBufferLayout {
42                    offset: 0,
43                    bytes_per_row: Some(4 * width),
44                    rows_per_image: Some(height),
45                },
46                wgpu::Extent3d {
47                    width,
48                    height,
49                    depth_or_array_layers: 1,
50                },
51            );
52
53            self.image_uv_registry.put(name.to_string(), uv_rect);
54            // Index 0 = mega-heim texture (stored in texture_views[0])
55            self.texture_registry.put(name.to_string(), 0);
56            log::debug!(
57                "[Surtr] Packed '{}' into Mega-Heim at ({}, {})",
58                name,
59                x,
60                y
61            );
62        } else {
63            log::warn!(
64                "HEIM_FULL: Failed to pack '{}' into Mega-Heim. Falling back to Texture Array.",
65                name
66            );
67            self.load_image(name, data);
68        }
69    }
70
71    /// Shapes a text string using a predefined system font stack.
72    ///
73    /// # Contract
74    /// Evaluates text shaping with fallbacks: queries "SF Pro Text", "SF Pro", "Inter",
75    /// "Helvetica Neue", "Helvetica", "Arial", and defaults back to "sans-serif".
76    /// This ensures visual typographic consistency across platforms where specific
77    /// branding faces may or may not be installed.
78    /// Shapes a text string using a default font stack.
79    ///
80    /// # Contract
81    /// Resolves standard font families in order of system availability. Falls back from
82    /// common system sans-serif aliases, to platform-specific sans-serif faces, and finally
83    /// to the embedded "Jupiteroid" font as a last resort.
84    pub(crate) fn shape_text_with_stack(&mut self, text: &str, size: f32) -> cvkg_runic_text::ShapedText {
85        let mut style = cvkg_runic_text::TextStyle::new("Jupiteroid", size);
86        style.fallback_families = vec![
87            "sans-serif".to_string(),
88            // Linux-native (fontconfig standard aliases + common packages)
89            "DejaVu Sans".to_string(),
90            "Cantarell".to_string(),
91            "Liberation Sans".to_string(),
92            "Noto Sans".to_string(),
93            "Adwaita Sans".to_string(),
94            // macOS / Windows
95            "SF Pro".to_string(),
96            "SF Pro Text".to_string(),
97            "Inter".to_string(),
98            "Helvetica Neue".to_string(),
99            "Helvetica".to_string(),
100            "Arial".to_string(),
101        ];
102        style.render_mode = cvkg_runic_text::RenderMode::Subpixel;
103        let spans = vec![cvkg_runic_text::TextSpan::new(text, style)];
104        self.text_engine
105            .shape_layout(
106                &spans,
107                None,
108                cvkg_runic_text::TextAlign::Start,
109                cvkg_runic_text::TextOverflow::WordWrap,
110            )
111            .unwrap_or_else(|_| cvkg_runic_text::ShapedText {
112                glyphs: Vec::new(),
113                lines: Vec::new(),
114                width: 0.0,
115                height: 0.0,
116                text: text.to_string(),
117                spans: Vec::new(),
118                has_rtl: false,
119                ascent: 0.0,
120                descent: 0.0,
121                line_gap: 0.0,
122                grapheme_boundaries: vec![],
123            })
124    }
125}
126