feat: added Font to get glyphs out of ttf files and make a TextureAtlas with them (right now only latin range of Unicode to not explode the atlas) and started trying to incorporate text rendering in ECS and Renderer2D

This commit is contained in:
lisk77 2025-03-15 23:17:02 +01:00
parent 5430ee0d7e
commit 9e16179df3
12 changed files with 191 additions and 317 deletions

View file

@ -12,6 +12,7 @@ anyhow = "1.0"
tobj = { version = "3.2", default-features = false, features = ["async"]}
bytemuck = { version = "1.16", features = [ "derive" ] }
log = "0.4.22"
ab_glyph = "0.2.29"
[dependencies.image]
version = "0.24"

View file

@ -0,0 +1,85 @@
use image::{DynamicImage, Rgba, RgbaImage};
use ab_glyph::{FontArc, PxScale, ScaleFont, Glyph, point, Font as AbFont};
pub struct Font {
name: String,
glyphs: Vec<DynamicImage>,
}
impl Font {
pub fn new(path: &str, size: f32) -> Self {
Font {
name: path.to_string(),
glyphs: Self::generate_images(path, size),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn names(&self) -> Vec<String> {
let mut names = Vec::new();
for code_point in 0x0020..=0x007E {
if let Some(ch) = std::char::from_u32(code_point) {
names.push(ch.to_string());
}
}
names
}
pub fn glyph(&self, index: usize) -> &DynamicImage {
&self.glyphs[index]
}
pub fn glyphs(&self) -> Vec<DynamicImage> {
self.glyphs.clone()
}
fn generate_images(path: &str, size: f32) -> Vec<DynamicImage> {
let font_data = std::fs::read(path).expect("Failed to read font file");
let font = FontArc::try_from_vec(font_data).expect("Failed to load font");
let scale = PxScale::from(size);
let scaled_font = font.as_scaled(scale);
let mut images = Vec::new();
for code_point in 0x0020..=0x007E {
if let Some(ch) = std::char::from_u32(code_point) {
if font.glyph_id(ch).0 == 0 {
continue;
}
let glyph = Glyph {
id: font.glyph_id(ch),
scale,
position: point(0.0, 0.0),
};
if let Some(outline) = scaled_font.outline_glyph(glyph) {
let bounds = outline.px_bounds();
let width = bounds.width().ceil() as u32;
let height = bounds.height().ceil() as u32;
if width == 0 || height == 0 {
continue;
}
let mut image = RgbaImage::new(width, height);
for pixel in image.pixels_mut() {
*pixel = Rgba([0, 0, 0, 0]);
}
outline.draw(|x, y, v| {
let alpha = (v * 255.0) as u8;
image.put_pixel(x, y, Rgba([255, 255, 255, alpha]));
});
images.push(DynamicImage::ImageRgba8(image));
}
}
}
images
}
}

View file

@ -4,19 +4,21 @@ use std::{
use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages};
use wgpu::naga::ShaderStage;
use crate::{texture, Texture};
use crate::{font, texture, Texture};
use crate::texture_atlas::{TextureAtlas, TextureRegion};
pub struct GraphicResorceManager {
pub struct GraphicResourceManager {
texture_atlas: TextureAtlas,
fonts: HashMap<String, TextureAtlas>,
data_files: HashMap<String, String>,
shaders: HashMap<String, ShaderModule>
}
impl GraphicResorceManager {
impl GraphicResourceManager {
pub fn new() -> Self {
Self {
texture_atlas: TextureAtlas::empty(),
fonts: HashMap::new(),
data_files: HashMap::new(),
shaders: HashMap::new()
}
@ -119,6 +121,12 @@ impl GraphicResorceManager {
self.shaders.get(shader)
}
pub fn load_font(&mut self, path: &str, size: f32) {
let font = font::Font::new(path, size);
let atlas = TextureAtlas::from_textures(font.glyphs(), font.names());
self.fonts.insert(font.name().to_string(), atlas);
}
/*pub async fn load_model(
&self,
file_name: &str,

View file

@ -8,6 +8,7 @@ pub mod vertex;
pub mod texture_atlas;
pub mod graphic_resource_manager;
mod material;
mod font;
/*use std::io::{BufReader, Cursor};
use wgpu::util::DeviceExt;

View file

@ -112,8 +112,6 @@ impl TextureAtlas {
pub fn from_texture_paths(
paths: Vec<String>,
) -> Self {
//let t0 = Instant::now();
let mut textures: Vec<DynamicImage> = Vec::new();
let mut regions: HashMap<String, TextureRegion> = HashMap::new();
@ -147,7 +145,7 @@ impl TextureAtlas {
x_offset = 0;
previous = texture.height();
}
//base.copy_from(texture, x_offset, y_offset).expect("Nope, you propably failed the offets");
Self::insert_texture_at(&mut base, &texture, x_offset, y_offset);
regions.insert(path.to_string(), TextureRegion::new(
x_offset as f32 / width as f32,
@ -159,16 +157,56 @@ impl TextureAtlas {
x_offset += texture.width();
}
// Save the image to disk as a PNG
//let output_path = Path::new(r"C:\Users\lisk77\Code Sharing\comet-engine\resources\textures\atlas.png");
//base.save_with_format(output_path, ImageFormat::Png).expect("Failed to save texture atlas");
info!("Texture atlas created!");
TextureAtlas {
atlas: base,
textures: regions
}
}
pub fn from_textures(
textures: Vec<DynamicImage>,
names: Vec<String>,
) -> Self {
let mut regions: HashMap<String, TextureRegion> = HashMap::new();
info!("Sorting textures by height...");
let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = textures.iter().zip(names.iter()).collect();
texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height()));
let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = texture_path_pairs.into_iter().unzip();
let sorted_textures: Vec<DynamicImage> = sorted_textures.into_iter().map(|t| t.clone()).collect();
let sorted_paths: Vec<String> = sorted_paths.into_iter().map(|s| s.to_string()).collect();
let (height, width) = (Self::calculate_atlas_height(&sorted_textures), Self::calculate_atlas_width(&sorted_textures));
let mut base = DynamicImage::new_rgba8(width,height);
let mut previous = sorted_textures.get(0).unwrap().height();
let mut x_offset: u32 = 0;
let mut y_offset: u32 = 0;
info!("Creating texture atlas...");
for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) {
if texture.height() != previous {
y_offset += previous;
x_offset = 0;
previous = texture.height();
}
Self::insert_texture_at(&mut base, &texture, x_offset, y_offset);
regions.insert(name.to_string(), TextureRegion::new(
x_offset as f32 / width as f32,
y_offset as f32 / height as f32,
(x_offset + texture.width()) as f32 / width as f32,
(y_offset + texture.height()) as f32 / height as f32,
texture.dimensions()
));
x_offset += texture.width();
}
info!("Texture atlas created!");
//debug!(format!("{:?}", regions));
/*let t1 = Instant::now();
let delta = t1.duration_since(t0);
println!("{:?}", delta);*/
TextureAtlas {
atlas: base,