use std::collections::HashMap;
use glam::Vec2;
use super::sdf_generator::{SdfConfig, SdfGlyphMetric, SdfAtlasData, generate_sdf_atlas};
pub struct SdfAtlas {
pub pixels: Vec<u8>,
pub width: u32,
pub height: u32,
pub channels: u32,
pub glyph_metrics: HashMap<char, SdfGlyphMetric>,
pub sdf_spread: f32,
pub font_size_px: f32,
}
impl SdfAtlas {
pub fn build() -> Self {
Self::build_with_config(&SdfConfig::default())
}
pub fn build_with_config(config: &SdfConfig) -> Self {
let data = generate_sdf_atlas(config);
Self::from_atlas_data(data)
}
pub fn from_atlas_data(data: SdfAtlasData) -> Self {
Self {
pixels: data.pixels,
width: data.width,
height: data.height,
channels: data.channels,
glyph_metrics: data.metrics,
sdf_spread: data.spread,
font_size_px: data.font_size_px,
}
}
pub fn metric_for(&self, ch: char) -> SdfGlyphMetric {
self.glyph_metrics
.get(&ch)
.or_else(|| self.glyph_metrics.get(&'?'))
.copied()
.unwrap_or(SdfGlyphMetric {
uv_rect: [0.0, 0.0, 0.01, 0.01],
size: Vec2::new(1.0, 1.0),
bearing: Vec2::ZERO,
advance: self.font_size_px * 0.6,
})
}
pub fn uv_for(&self, ch: char) -> [f32; 4] {
self.metric_for(ch).uv_rect
}
pub fn uv_offset(&self, ch: char) -> [f32; 2] {
let uv = self.uv_for(ch);
[uv[0], uv[1]]
}
pub fn uv_size(&self, ch: char) -> [f32; 2] {
let uv = self.uv_for(ch);
[uv[2] - uv[0], uv[3] - uv[1]]
}
pub fn compute_smoothing(&self, screen_px_per_unit: f32, glyph_scale: f32) -> f32 {
let effective_px = screen_px_per_unit * glyph_scale;
if effective_px <= 0.0 {
return 0.1;
}
let texels_per_screen_px = self.font_size_px / effective_px;
(texels_per_screen_px / self.sdf_spread).clamp(0.001, 0.25)
}
pub fn threshold(&self) -> f32 {
0.5
}
pub fn bold_threshold(&self) -> f32 {
0.45
}
pub fn outline_range(&self, outline_width: f32) -> (f32, f32) {
let inner = 0.5;
let outer = (0.5 - outline_width / self.sdf_spread).max(0.05);
(inner, outer)
}
pub fn shadow_uv_offset(&self, shadow_offset: Vec2, glyph_scale: f32) -> Vec2 {
let scale_factor = glyph_scale * self.font_size_px;
if scale_factor <= 0.0 {
return Vec2::ZERO;
}
Vec2::new(
shadow_offset.x / (self.width as f32 * scale_factor / self.font_size_px),
shadow_offset.y / (self.height as f32 * scale_factor / self.font_size_px),
)
}
pub fn glyph_count(&self) -> usize {
self.glyph_metrics.len()
}
pub fn has_char(&self, ch: char) -> bool {
self.glyph_metrics.contains_key(&ch)
}
pub fn measure_string_width(&self, text: &str, scale: f32) -> f32 {
let scale_factor = scale / self.font_size_px;
text.chars()
.map(|ch| self.metric_for(ch).advance * scale_factor)
.sum()
}
pub fn line_height(&self, scale: f32) -> f32 {
scale
}
}
#[derive(Clone, Debug)]
pub struct SdfEffects {
pub outline: bool,
pub outline_color: glam::Vec4,
pub outline_width: f32,
pub shadow: bool,
pub shadow_color: glam::Vec4,
pub shadow_offset: Vec2,
pub shadow_softness: f32,
pub glow: bool,
pub glow_color: glam::Vec4,
pub glow_radius: f32,
pub bold: bool,
pub wave_amplitude: f32,
pub wave_frequency: f32,
pub shake_amount: f32,
pub glitch_intensity: f32,
}
impl Default for SdfEffects {
fn default() -> Self {
Self {
outline: false,
outline_color: glam::Vec4::new(0.0, 0.0, 0.0, 1.0),
outline_width: 0.1,
shadow: false,
shadow_color: glam::Vec4::new(0.0, 0.0, 0.0, 0.6),
shadow_offset: Vec2::new(2.0, -2.0),
shadow_softness: 0.05,
glow: false,
glow_color: glam::Vec4::new(1.0, 1.0, 1.0, 0.5),
glow_radius: 0.3,
bold: false,
wave_amplitude: 0.0,
wave_frequency: 0.0,
shake_amount: 0.0,
glitch_intensity: 0.0,
}
}
}
impl SdfEffects {
pub fn none() -> Self {
Self::default()
}
pub fn outline(color: glam::Vec4, width: f32) -> Self {
Self {
outline: true,
outline_color: color,
outline_width: width,
..Self::default()
}
}
pub fn shadow(color: glam::Vec4, offset: Vec2, softness: f32) -> Self {
Self {
shadow: true,
shadow_color: color,
shadow_offset: offset,
shadow_softness: softness,
..Self::default()
}
}
pub fn glow(color: glam::Vec4, radius: f32) -> Self {
Self {
glow: true,
glow_color: color,
glow_radius: radius,
..Self::default()
}
}
pub fn bold() -> Self {
Self {
bold: true,
..Self::default()
}
}
pub fn wave(amplitude: f32, frequency: f32) -> Self {
Self {
wave_amplitude: amplitude,
wave_frequency: frequency,
..Self::default()
}
}
pub fn glitch(intensity: f32) -> Self {
Self {
glitch_intensity: intensity,
..Self::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoothing_inversely_proportional_to_scale() {
let atlas = SdfAtlas {
pixels: vec![],
width: 512,
height: 512,
channels: 1,
glyph_metrics: HashMap::new(),
sdf_spread: 8.0,
font_size_px: 64.0,
};
let small = atlas.compute_smoothing(10.0, 1.0);
let large = atlas.compute_smoothing(100.0, 1.0);
assert!(small > large, "Small scale should have more smoothing: {} vs {}", small, large);
}
#[test]
fn outline_range_valid() {
let atlas = SdfAtlas {
pixels: vec![],
width: 512,
height: 512,
channels: 1,
glyph_metrics: HashMap::new(),
sdf_spread: 8.0,
font_size_px: 64.0,
};
let (inner, outer) = atlas.outline_range(1.0);
assert!(inner > outer, "Inner threshold should be > outer: {} vs {}", inner, outer);
}
#[test]
fn measure_string_empty() {
let atlas = SdfAtlas {
pixels: vec![],
width: 512,
height: 512,
channels: 1,
glyph_metrics: HashMap::new(),
sdf_spread: 8.0,
font_size_px: 64.0,
};
assert_eq!(atlas.measure_string_width("", 1.0), 0.0);
}
#[test]
fn sdf_effects_defaults() {
let fx = SdfEffects::default();
assert!(!fx.outline);
assert!(!fx.shadow);
assert!(!fx.glow);
assert!(!fx.bold);
}
}