1use std::collections::HashMap;
10use glam::Vec2;
11
12use super::sdf_generator::{SdfConfig, SdfGlyphMetric, SdfAtlasData, generate_sdf_atlas};
13
14pub struct SdfAtlas {
26 pub pixels: Vec<u8>,
28 pub width: u32,
30 pub height: u32,
31 pub channels: u32,
33 pub glyph_metrics: HashMap<char, SdfGlyphMetric>,
35 pub sdf_spread: f32,
37 pub font_size_px: f32,
39}
40
41impl SdfAtlas {
42 pub fn build() -> Self {
44 Self::build_with_config(&SdfConfig::default())
45 }
46
47 pub fn build_with_config(config: &SdfConfig) -> Self {
49 let data = generate_sdf_atlas(config);
50 Self::from_atlas_data(data)
51 }
52
53 pub fn from_atlas_data(data: SdfAtlasData) -> Self {
55 Self {
56 pixels: data.pixels,
57 width: data.width,
58 height: data.height,
59 channels: data.channels,
60 glyph_metrics: data.metrics,
61 sdf_spread: data.spread,
62 font_size_px: data.font_size_px,
63 }
64 }
65
66 pub fn metric_for(&self, ch: char) -> SdfGlyphMetric {
68 self.glyph_metrics
69 .get(&ch)
70 .or_else(|| self.glyph_metrics.get(&'?'))
71 .copied()
72 .unwrap_or(SdfGlyphMetric {
73 uv_rect: [0.0, 0.0, 0.01, 0.01],
74 size: Vec2::new(1.0, 1.0),
75 bearing: Vec2::ZERO,
76 advance: self.font_size_px * 0.6,
77 })
78 }
79
80 pub fn uv_for(&self, ch: char) -> [f32; 4] {
82 self.metric_for(ch).uv_rect
83 }
84
85 pub fn uv_offset(&self, ch: char) -> [f32; 2] {
87 let uv = self.uv_for(ch);
88 [uv[0], uv[1]]
89 }
90
91 pub fn uv_size(&self, ch: char) -> [f32; 2] {
93 let uv = self.uv_for(ch);
94 [uv[2] - uv[0], uv[3] - uv[1]]
95 }
96
97 pub fn compute_smoothing(&self, screen_px_per_unit: f32, glyph_scale: f32) -> f32 {
106 let effective_px = screen_px_per_unit * glyph_scale;
107 if effective_px <= 0.0 {
108 return 0.1;
109 }
110 let texels_per_screen_px = self.font_size_px / effective_px;
114 (texels_per_screen_px / self.sdf_spread).clamp(0.001, 0.25)
115 }
116
117 pub fn threshold(&self) -> f32 {
122 0.5
123 }
124
125 pub fn bold_threshold(&self) -> f32 {
127 0.45
128 }
129
130 pub fn outline_range(&self, outline_width: f32) -> (f32, f32) {
134 let inner = 0.5;
135 let outer = (0.5 - outline_width / self.sdf_spread).max(0.05);
136 (inner, outer)
137 }
138
139 pub fn shadow_uv_offset(&self, shadow_offset: Vec2, glyph_scale: f32) -> Vec2 {
143 let scale_factor = glyph_scale * self.font_size_px;
145 if scale_factor <= 0.0 {
146 return Vec2::ZERO;
147 }
148 Vec2::new(
149 shadow_offset.x / (self.width as f32 * scale_factor / self.font_size_px),
150 shadow_offset.y / (self.height as f32 * scale_factor / self.font_size_px),
151 )
152 }
153
154 pub fn glyph_count(&self) -> usize {
156 self.glyph_metrics.len()
157 }
158
159 pub fn has_char(&self, ch: char) -> bool {
161 self.glyph_metrics.contains_key(&ch)
162 }
163
164 pub fn measure_string_width(&self, text: &str, scale: f32) -> f32 {
166 let scale_factor = scale / self.font_size_px;
167 text.chars()
168 .map(|ch| self.metric_for(ch).advance * scale_factor)
169 .sum()
170 }
171
172 pub fn line_height(&self, scale: f32) -> f32 {
174 scale
175 }
176}
177
178#[derive(Clone, Debug)]
182pub struct SdfEffects {
183 pub outline: bool,
185 pub outline_color: glam::Vec4,
187 pub outline_width: f32,
189
190 pub shadow: bool,
192 pub shadow_color: glam::Vec4,
194 pub shadow_offset: Vec2,
196 pub shadow_softness: f32,
198
199 pub glow: bool,
201 pub glow_color: glam::Vec4,
203 pub glow_radius: f32,
205
206 pub bold: bool,
208
209 pub wave_amplitude: f32,
211 pub wave_frequency: f32,
212 pub shake_amount: f32,
213 pub glitch_intensity: f32,
214}
215
216impl Default for SdfEffects {
217 fn default() -> Self {
218 Self {
219 outline: false,
220 outline_color: glam::Vec4::new(0.0, 0.0, 0.0, 1.0),
221 outline_width: 0.1,
222 shadow: false,
223 shadow_color: glam::Vec4::new(0.0, 0.0, 0.0, 0.6),
224 shadow_offset: Vec2::new(2.0, -2.0),
225 shadow_softness: 0.05,
226 glow: false,
227 glow_color: glam::Vec4::new(1.0, 1.0, 1.0, 0.5),
228 glow_radius: 0.3,
229 bold: false,
230 wave_amplitude: 0.0,
231 wave_frequency: 0.0,
232 shake_amount: 0.0,
233 glitch_intensity: 0.0,
234 }
235 }
236}
237
238impl SdfEffects {
239 pub fn none() -> Self {
241 Self::default()
242 }
243
244 pub fn outline(color: glam::Vec4, width: f32) -> Self {
246 Self {
247 outline: true,
248 outline_color: color,
249 outline_width: width,
250 ..Self::default()
251 }
252 }
253
254 pub fn shadow(color: glam::Vec4, offset: Vec2, softness: f32) -> Self {
256 Self {
257 shadow: true,
258 shadow_color: color,
259 shadow_offset: offset,
260 shadow_softness: softness,
261 ..Self::default()
262 }
263 }
264
265 pub fn glow(color: glam::Vec4, radius: f32) -> Self {
267 Self {
268 glow: true,
269 glow_color: color,
270 glow_radius: radius,
271 ..Self::default()
272 }
273 }
274
275 pub fn bold() -> Self {
277 Self {
278 bold: true,
279 ..Self::default()
280 }
281 }
282
283 pub fn wave(amplitude: f32, frequency: f32) -> Self {
285 Self {
286 wave_amplitude: amplitude,
287 wave_frequency: frequency,
288 ..Self::default()
289 }
290 }
291
292 pub fn glitch(intensity: f32) -> Self {
294 Self {
295 glitch_intensity: intensity,
296 ..Self::default()
297 }
298 }
299}
300
301#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn smoothing_inversely_proportional_to_scale() {
309 let atlas = SdfAtlas {
310 pixels: vec![],
311 width: 512,
312 height: 512,
313 channels: 1,
314 glyph_metrics: HashMap::new(),
315 sdf_spread: 8.0,
316 font_size_px: 64.0,
317 };
318
319 let small = atlas.compute_smoothing(10.0, 1.0);
320 let large = atlas.compute_smoothing(100.0, 1.0);
321 assert!(small > large, "Small scale should have more smoothing: {} vs {}", small, large);
322 }
323
324 #[test]
325 fn outline_range_valid() {
326 let atlas = SdfAtlas {
327 pixels: vec![],
328 width: 512,
329 height: 512,
330 channels: 1,
331 glyph_metrics: HashMap::new(),
332 sdf_spread: 8.0,
333 font_size_px: 64.0,
334 };
335
336 let (inner, outer) = atlas.outline_range(1.0);
337 assert!(inner > outer, "Inner threshold should be > outer: {} vs {}", inner, outer);
338 }
339
340 #[test]
341 fn measure_string_empty() {
342 let atlas = SdfAtlas {
343 pixels: vec![],
344 width: 512,
345 height: 512,
346 channels: 1,
347 glyph_metrics: HashMap::new(),
348 sdf_spread: 8.0,
349 font_size_px: 64.0,
350 };
351 assert_eq!(atlas.measure_string_width("", 1.0), 0.0);
352 }
353
354 #[test]
355 fn sdf_effects_defaults() {
356 let fx = SdfEffects::default();
357 assert!(!fx.outline);
358 assert!(!fx.shadow);
359 assert!(!fx.glow);
360 assert!(!fx.bold);
361 }
362}