use crate::render::wgpu::font_atlas::{AtlasPacker, DEFAULT_FONT_DATA};
use crate::render::wgpu::sprite_texture_atlas::SpriteTextureAtlas;
use nalgebra_glm::Vec2;
use std::collections::HashMap;
const RASTERIZATION_SCALE: f32 = 48.0;
const FONT_BITMAP_SIZE: u32 = 512;
const FONT_ATLAS_SLOT: u32 = 127;
#[derive(Debug, Clone)]
pub struct SpriteGlyphInfo {
pub uv_min: Vec2,
pub uv_max: Vec2,
pub size: Vec2,
pub bearing: Vec2,
pub advance: f32,
}
pub struct SpriteFontAtlas {
pub glyphs: HashMap<char, SpriteGlyphInfo>,
pub atlas_slot: u32,
pub rasterization_scale: f32,
pub line_height: f32,
}
pub fn rasterize_sprite_font(
queue: &wgpu::Queue,
atlas: &mut SpriteTextureAtlas,
) -> SpriteFontAtlas {
rasterize_sprite_font_from_bytes(queue, atlas, DEFAULT_FONT_DATA, FONT_ATLAS_SLOT)
}
pub fn rasterize_sprite_font_from_bytes(
queue: &wgpu::Queue,
atlas: &mut SpriteTextureAtlas,
font_data: &[u8],
slot: u32,
) -> SpriteFontAtlas {
use swash::FontRef;
use swash::scale::{Render, ScaleContext, Source};
use swash::zeno::Format;
let font_ref =
FontRef::from_index(font_data, 0).expect("Failed to parse font data for sprite font atlas");
let mut context = ScaleContext::new();
let charmap = font_ref.charmap();
let metrics = font_ref.metrics(&[]).scale(RASTERIZATION_SCALE);
let glyph_metrics = font_ref.glyph_metrics(&[]).scale(RASTERIZATION_SCALE);
let line_height = metrics.ascent - metrics.descent + metrics.leading;
let bitmap_size = FONT_BITMAP_SIZE;
let mut rgba_bitmap = vec![0u8; (bitmap_size * bitmap_size * 4) as usize];
let mut packer = AtlasPacker::new(bitmap_size, bitmap_size);
let mut glyphs = HashMap::new();
for code in 32u8..127u8 {
let character = code as char;
let glyph_id = charmap.map(character);
let advance = glyph_metrics.advance_width(glyph_id);
let mut scaler = context
.builder(font_ref)
.size(RASTERIZATION_SCALE)
.hint(true)
.build();
let image = Render::new(&[Source::Outline])
.format(Format::Alpha)
.render(&mut scaler, glyph_id);
let Some(image) = image else {
glyphs.insert(
character,
SpriteGlyphInfo {
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(0.0, 0.0),
size: Vec2::new(0.0, 0.0),
bearing: Vec2::new(0.0, 0.0),
advance,
},
);
continue;
};
let glyph_width = image.placement.width;
let glyph_height = image.placement.height;
if glyph_width == 0 || glyph_height == 0 {
glyphs.insert(
character,
SpriteGlyphInfo {
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(0.0, 0.0),
size: Vec2::new(0.0, 0.0),
bearing: Vec2::new(0.0, 0.0),
advance,
},
);
continue;
}
let Some((pack_x, pack_y)) = packer.pack(glyph_width + 1, glyph_height + 1) else {
continue;
};
for row in 0..glyph_height as usize {
for col in 0..glyph_width as usize {
let source_index = row * glyph_width as usize + col;
let coverage = image.data[source_index];
let destination_x = pack_x as usize + col;
let destination_y = pack_y as usize + row;
let destination_index = (destination_y * bitmap_size as usize + destination_x) * 4;
rgba_bitmap[destination_index] = 255;
rgba_bitmap[destination_index + 1] = 255;
rgba_bitmap[destination_index + 2] = 255;
rgba_bitmap[destination_index + 3] = coverage;
}
}
let uv_min = Vec2::new(
pack_x as f32 / bitmap_size as f32,
pack_y as f32 / bitmap_size as f32,
);
let uv_max = Vec2::new(
(pack_x + glyph_width) as f32 / bitmap_size as f32,
(pack_y + glyph_height) as f32 / bitmap_size as f32,
);
glyphs.insert(
character,
SpriteGlyphInfo {
uv_min,
uv_max,
size: Vec2::new(glyph_width as f32, glyph_height as f32),
bearing: Vec2::new(image.placement.left as f32, image.placement.top as f32),
advance,
},
);
}
atlas.upload_texture(queue, slot, &rgba_bitmap, bitmap_size, bitmap_size);
SpriteFontAtlas {
glyphs,
atlas_slot: slot,
rasterization_scale: RASTERIZATION_SCALE,
line_height,
}
}