use glam::{Mat4, Vec3};
use wgpu::{
BindGroup, Buffer, CommandEncoder, RenderPipeline,
TextureView,
};
use crate::renderer::RenderDevice;
use crate::renderer::buffer::{create_uniform_buffer, Vertex};
use crate::renderer::pipeline::RenderPipelineBuilder;
use crate::renderer::sprite::SpriteVertex;
const SPRITE_SHADER: &str = include_str!("../shaders/sprite.wgsl");
const GLYPH_W: u32 = 8;
const GLYPH_H: u32 = 16;
const GLYPHS_PER_ROW: u32 = 16;
const GLYPH_ROWS: u32 = 6;
const ATLAS_W: u32 = GLYPHS_PER_ROW * GLYPH_W; const ATLAS_H: u32 = GLYPH_ROWS * GLYPH_H;
const FIRST_CHAR: u8 = 32;
const LAST_CHAR: u8 = 127;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct OrthoUniform {
projection: [[f32; 4]; 4],
}
pub struct TextRenderer {
pipeline: RenderPipeline,
font_bind_group: BindGroup,
ortho_buffer: Buffer,
ortho_bind_group: BindGroup,
cached_vb: Option<(Buffer, u64)>,
}
impl TextRenderer {
pub fn new(device: &RenderDevice, format: wgpu::TextureFormat) -> Self {
let atlas_data = generate_font_atlas();
let texture_size = wgpu::Extent3d {
width: ATLAS_W,
height: ATLAS_H,
depth_or_array_layers: 1,
};
let font_texture = device.device().create_texture(&wgpu::TextureDescriptor {
label: Some("Font Atlas"),
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
device.queue().write_texture(
wgpu::ImageCopyTexture {
texture: &font_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&atlas_data,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * ATLAS_W),
rows_per_image: Some(ATLAS_H),
},
texture_size,
);
let font_texture_view = font_texture.create_view(&wgpu::TextureViewDescriptor::default());
let font_sampler = device.device().create_sampler(&wgpu::SamplerDescriptor {
label: Some("Font Sampler"),
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let ortho_uniform = OrthoUniform {
projection: Mat4::IDENTITY.to_cols_array_2d(),
};
let ortho_buffer = create_uniform_buffer(
device,
"Text Ortho Uniform",
bytemuck::bytes_of(&ortho_uniform),
);
let ortho_bgl = device.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Text Ortho BGL"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let font_bgl = device.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Text Font BGL"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let ortho_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Text Ortho BG"),
layout: &ortho_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: ortho_buffer.as_entire_binding(),
}],
});
let font_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Text Font BG"),
layout: &font_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&font_texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&font_sampler),
},
],
});
let pipeline = RenderPipelineBuilder::new()
.with_vertex_shader(SPRITE_SHADER)
.with_fragment_shader(SPRITE_SHADER)
.with_format(format)
.with_vertex_layouts(vec![SpriteVertex::layout()])
.with_bind_group_layouts(vec![ortho_bgl, font_bgl])
.with_label("Text Pipeline")
.build(device)
.expect("创建 Text 管线失败")
.into_pipeline();
Self {
pipeline,
font_bind_group,
ortho_buffer,
ortho_bind_group,
cached_vb: None,
}
}
pub fn draw_text(
&mut self,
device: &RenderDevice,
encoder: &mut CommandEncoder,
target: &TextureView,
text: &str,
x: f32,
y: f32,
font_size: f32,
color: Vec3,
screen_w: f32,
screen_h: f32,
) {
if text.is_empty() {
return;
}
let ortho = OrthoUniform {
projection: Mat4::orthographic_lh(0.0, screen_w, screen_h, 0.0, -1.0, 1.0)
.to_cols_array_2d(),
};
device
.queue()
.write_buffer(&self.ortho_buffer, 0, bytemuck::bytes_of(&ortho));
let glyph_w = font_size * (GLYPH_W as f32 / GLYPH_H as f32);
let glyph_h = font_size;
let mut vertices = Vec::with_capacity(text.len() * 6);
let mut cursor_x = x;
for ch in text.bytes() {
if ch < FIRST_CHAR || ch >= LAST_CHAR {
cursor_x += glyph_w;
continue;
}
let (u0, v0, u1, v1) = char_uv(ch);
let x0 = cursor_x;
let y0 = y;
let x1 = cursor_x + glyph_w;
let y1 = y + glyph_h;
let c = [color.x, color.y, color.z];
vertices.push(SpriteVertex { position: [x0, y0, 0.0], texcoord: [u0, v0], color: c });
vertices.push(SpriteVertex { position: [x1, y0, 0.0], texcoord: [u1, v0], color: c });
vertices.push(SpriteVertex { position: [x1, y1, 0.0], texcoord: [u1, v1], color: c });
vertices.push(SpriteVertex { position: [x0, y0, 0.0], texcoord: [u0, v0], color: c });
vertices.push(SpriteVertex { position: [x1, y1, 0.0], texcoord: [u1, v1], color: c });
vertices.push(SpriteVertex { position: [x0, y1, 0.0], texcoord: [u0, v1], color: c });
cursor_x += glyph_w;
}
if vertices.is_empty() {
return;
}
let data: &[u8] = bytemuck::cast_slice(&vertices);
let needed = data.len() as u64;
let reuse = self.cached_vb.as_ref().map_or(false, |(_, cap)| *cap >= needed);
if !reuse {
self.cached_vb = Some((
device.device().create_buffer(&wgpu::BufferDescriptor {
label: Some("Text VB (cached)"),
size: needed,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
}),
needed,
));
}
let vertex_buffer = &self.cached_vb.as_ref().unwrap().0;
device.queue().write_buffer(vertex_buffer, 0, data);
{
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Text Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rp.set_pipeline(&self.pipeline);
rp.set_bind_group(0, &self.ortho_bind_group, &[]);
rp.set_bind_group(1, &self.font_bind_group, &[]);
rp.set_vertex_buffer(0, vertex_buffer.slice(..));
rp.draw(0..vertices.len() as u32, 0..1);
}
}
}
fn char_uv(ascii: u8) -> (f32, f32, f32, f32) {
let idx = (ascii - FIRST_CHAR) as u32;
let col = idx % GLYPHS_PER_ROW;
let row = idx / GLYPHS_PER_ROW;
let u0 = (col * GLYPH_W) as f32 / ATLAS_W as f32;
let v0 = (row * GLYPH_H) as f32 / ATLAS_H as f32;
let u1 = ((col + 1) * GLYPH_W) as f32 / ATLAS_W as f32;
let v1 = ((row + 1) * GLYPH_H) as f32 / ATLAS_H as f32;
(u0, v0, u1, v1)
}
fn generate_font_atlas() -> Vec<u8> {
let font_data = include_cp437_font();
let mut rgba = vec![0u8; (ATLAS_W * ATLAS_H * 4) as usize];
for char_idx in 0..95u32 {
let col = char_idx % GLYPHS_PER_ROW;
let row = char_idx / GLYPHS_PER_ROW;
let glyph = &font_data[char_idx as usize];
for gy in 0..GLYPH_H {
let bits = glyph[gy as usize];
for gx in 0..GLYPH_W {
let px = col * GLYPH_W + gx;
let py = row * GLYPH_H + gy;
let offset = ((py * ATLAS_W + px) * 4) as usize;
let on = (bits >> (7 - gx)) & 1 != 0;
let val = if on { 255u8 } else { 0u8 };
rgba[offset] = val;
rgba[offset + 1] = val;
rgba[offset + 2] = val;
rgba[offset + 3] = val; }
}
}
rgba
}
fn include_cp437_font() -> Vec<[u8; 16]> {
let raw: &[u8] = &[
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00,
0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06,0x06,0x86,0xC6,0x7C,0x18,0x18,0x00,0x00,
0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00,
0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00,
0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xDE,0xF6,0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00,
0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00,
0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00,
0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00,
0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00,
0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00,
0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00,
0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00,
0x00,0x00,0xC6,0xC6,0xC6,0x6C,0x38,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00,
0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00,
0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,
0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00,
0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00,
0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00,
0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00,
0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xD6,0xD6,0xC6,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00,
0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00,
0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00,
0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00,
0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00,
0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,
0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00,
0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
];
let mut glyphs = Vec::with_capacity(95);
for i in 0..95 {
let mut glyph = [0u8; 16];
glyph.copy_from_slice(&raw[i * 16..(i + 1) * 16]);
glyphs.push(glyph);
}
glyphs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_uv_space() {
let (u0, v0, u1, v1) = char_uv(b' ');
assert_eq!(u0, 0.0);
assert_eq!(v0, 0.0);
assert!((u1 - GLYPH_W as f32 / ATLAS_W as f32).abs() < 1e-5);
assert!((v1 - GLYPH_H as f32 / ATLAS_H as f32).abs() < 1e-5);
}
#[test]
fn test_char_uv_a() {
let idx = b'A' - FIRST_CHAR;
let col = idx as u32 % GLYPHS_PER_ROW;
let row = idx as u32 / GLYPHS_PER_ROW;
let (u0, v0, _, _) = char_uv(b'A');
assert!((u0 - (col * GLYPH_W) as f32 / ATLAS_W as f32).abs() < 1e-5);
assert!((v0 - (row * GLYPH_H) as f32 / ATLAS_H as f32).abs() < 1e-5);
}
#[test]
fn test_font_atlas_size() {
let atlas = generate_font_atlas();
assert_eq!(atlas.len(), (ATLAS_W * ATLAS_H * 4) as usize);
}
#[test]
fn test_font_data_glyph_count() {
let glyphs = include_cp437_font();
assert_eq!(glyphs.len(), 95);
}
#[test]
fn test_space_glyph_is_empty() {
let glyphs = include_cp437_font();
let space = &glyphs[0]; for &row in space.iter() {
assert_eq!(row, 0, "Space glyph should be all zeros");
}
}
#[test]
fn test_char_uv_bounds() {
for ch in FIRST_CHAR..LAST_CHAR {
let (u0, v0, u1, v1) = char_uv(ch);
assert!(u0 >= 0.0 && u0 <= 1.0, "u0 out of bounds for char {}", ch);
assert!(v0 >= 0.0 && v0 <= 1.0, "v0 out of bounds for char {}", ch);
assert!(u1 >= 0.0 && u1 <= 1.0, "u1 out of bounds for char {}", ch);
assert!(v1 >= 0.0 && v1 <= 1.0, "v1 out of bounds for char {}", ch);
assert!(u1 > u0);
assert!(v1 > v0);
}
}
}