feat: text can now be rendered, though only in the setup right now (will make a new render pass for that)

This commit is contained in:
lisk77 2025-03-17 23:13:44 +01:00
parent 0507703284
commit b2578f7673
3 changed files with 215 additions and 117 deletions

View file

@ -1,17 +1,31 @@
use image::{DynamicImage, Rgba, RgbaImage};
use ab_glyph::{FontArc, PxScale, ScaleFont, Glyph, point, Font as AbFont};
use comet_log::debug;
use crate::texture_atlas::{TextureAtlas, TextureRegion};
pub struct GlyphData {
pub name: String,
pub render: DynamicImage,
pub advance: f32,
pub offset_x: f32,
pub offset_y: f32,
}
pub struct Font {
name: String,
size: f32,
line_height: f32,
glyphs: TextureAtlas,
}
impl Font {
pub fn new(path: &str, size: f32) -> Self {
let (glyphs, line_height) = Self::generate_atlas(path, size);
Font {
name: path.to_string(),
glyphs: Self::generate_atlas(path, size)
size,
line_height,
glyphs
}
}
@ -19,6 +33,14 @@ impl Font {
&self.name
}
pub fn size(&self) -> f32 {
self.size
}
pub fn line_height(&self) -> f32 {
self.line_height
}
pub fn glyphs(&self) -> &TextureAtlas {
&self.glyphs
}
@ -27,31 +49,41 @@ impl Font {
self.glyphs.textures().get(&ch.to_string())
}
fn generate_atlas(path: &str, size: f32) -> TextureAtlas {
fn generate_atlas(path: &str, size: f32) -> (TextureAtlas, f32) {
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 names = Vec::new();
let mut images = Vec::new();
let mut glyphs: Vec<GlyphData> = 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 {
let glyph_id = font.glyph_id(ch);
if glyph_id.0 == 0 {
continue;
}
names.push(ch.to_string());
if ch == ' ' {
let advance = scaled_font.h_advance(glyph_id);
glyphs.push(GlyphData {
name: ch.to_string(),
render: DynamicImage::new_rgba8(0, 0), // no bitmap
advance,
offset_x: 0.0,
offset_y: 0.0,
});
continue;
}
let glyph = Glyph {
id: font.glyph_id(ch),
id: glyph_id,
scale,
position: point(0.0, 0.0),
};
if let Some(outline) = scaled_font.outline_glyph(glyph) {
if let Some(outline) = scaled_font.outline_glyph(glyph.clone()) {
let bounds = outline.px_bounds();
let width = bounds.width().ceil() as u32;
let height = bounds.height().ceil() as u32;
@ -70,11 +102,19 @@ impl Font {
image.put_pixel(x, y, Rgba([255, 255, 255, alpha]));
});
images.push(DynamicImage::ImageRgba8(image));
glyphs.push(
GlyphData {
name: ch.to_string(),
render: DynamicImage::ImageRgba8(image),
advance: scaled_font.h_advance(glyph_id),
offset_x: bounds.min.x,
offset_y: bounds.min.y,
}
)
}
}
}
TextureAtlas::from_textures(names, images)
(TextureAtlas::from_glyphs(glyphs), scaled_font.ascent() - scaled_font.descent())
}
}

View file

@ -4,47 +4,66 @@ use std::time::Instant;
use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat};
use comet_log::*;
use wgpu::{Device, FilterMode, TextureFormat, TextureUsages};
use crate::font::GlyphData;
use crate::Texture;
#[derive(Debug)]
pub struct TextureRegion {
x0: f32,
y0: f32,
x1: f32,
y1: f32,
dimensions: (u32, u32)
u0: f32,
v0: f32,
u1: f32,
v1: f32,
dimensions: (u32, u32),
advance: f32,
offset_x: f32,
offset_y: f32,
}
impl TextureRegion {
pub fn new(x0: f32, y0: f32, x1: f32, y1: f32, dimensions: (u32, u32)) -> Self {
pub fn new(u0: f32, v0: f32, u1: f32, v1: f32, dimensions: (u32, u32), advance: f32, offset_x: f32, offset_y: f32) -> Self {
Self {
x0,
y0,
x1,
y1,
dimensions
u0,
v0,
u1,
v1,
dimensions,
advance,
offset_x,
offset_y,
}
}
pub fn x0(&self) -> f32 {
self.x0
pub fn u0(&self) -> f32 {
self.u0
}
pub fn x1(&self) -> f32 {
self.x1
pub fn u1(&self) -> f32 {
self.u1
}
pub fn y0(&self) -> f32 {
self.y0
pub fn v0(&self) -> f32 {
self.v0
}
pub fn y1(&self) -> f32 {
self.y1
pub fn v1(&self) -> f32 {
self.v1
}
pub fn dimensions(&self) -> (u32, u32) {
self.dimensions
}
pub fn advance(&self) -> f32 {
self.advance
}
pub fn offset_x(&self) -> f32 {
self.offset_x
}
pub fn offset_y(&self) -> f32 {
self.offset_y
}
}
#[derive(Debug)]
@ -152,7 +171,10 @@ impl TextureAtlas {
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()
texture.dimensions(),
0.0,
0.0,
0.0
));
x_offset += texture.width();
}
@ -201,7 +223,10 @@ impl TextureAtlas {
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()
texture.dimensions(),
0.0,
0.0,
0.0
));
x_offset += texture.width();
}
@ -214,6 +239,58 @@ impl TextureAtlas {
}
}
pub fn from_glyphs(mut glyphs: Vec<GlyphData>) -> Self {
glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height()));
let height = Self::calculate_atlas_height(
&glyphs.iter().map(|g| g.render.clone()).collect::<Vec<_>>()
);
let width = Self::calculate_atlas_width(
&glyphs.iter().map(|g| g.render.clone()).collect::<Vec<_>>()
);
let mut base = DynamicImage::new_rgba8(width, height);
let mut regions = HashMap::new();
let mut current_row_height = glyphs[0].render.height();
let mut x_offset: u32 = 0;
let mut y_offset: u32 = 0;
for g in glyphs.iter() {
let glyph_w = g.render.width();
let glyph_h = g.render.height();
if glyph_h != current_row_height {
y_offset += current_row_height;
x_offset = 0;
current_row_height = glyph_h;
}
Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset);
let u0 = x_offset as f32 / width as f32;
let v0 = y_offset as f32 / height as f32;
let u1 = (x_offset + glyph_w) as f32 / width as f32;
let v1 = (y_offset + glyph_h) as f32 / height as f32;
let region = TextureRegion::new(
u0, v0, u1, v1,
(glyph_w, glyph_h),
g.advance,
g.offset_x,
g.offset_y,
);
regions.insert(g.name.clone(), region);
x_offset += glyph_w;
}
TextureAtlas {
atlas: base,
textures: regions,
}
}
pub fn atlas(&self) -> &DynamicImage {
&self.atlas
}