1use glam::{Mat4, Vec3};
25use wgpu::{
26 BindGroup, Buffer, CommandEncoder, RenderPipeline,
27 TextureView,
28};
29
30use crate::renderer::RenderDevice;
31use crate::renderer::buffer::{create_uniform_buffer, Vertex};
32use crate::renderer::pipeline::RenderPipelineBuilder;
33use crate::renderer::sprite::SpriteVertex;
34
35const SPRITE_SHADER: &str = include_str!("../shaders/sprite.wgsl");
37
38const GLYPH_W: u32 = 8;
40const GLYPH_H: u32 = 16;
42const GLYPHS_PER_ROW: u32 = 16;
44const GLYPH_ROWS: u32 = 6;
46const ATLAS_W: u32 = GLYPHS_PER_ROW * GLYPH_W; const ATLAS_H: u32 = GLYPH_ROWS * GLYPH_H; const FIRST_CHAR: u8 = 32;
53const LAST_CHAR: u8 = 127;
55
56#[repr(C)]
58#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
59struct OrthoUniform {
60 projection: [[f32; 4]; 4],
61}
62
63pub struct TextRenderer {
67 pipeline: RenderPipeline,
68 font_bind_group: BindGroup,
69 ortho_buffer: Buffer,
70 ortho_bind_group: BindGroup,
71 cached_vb: Option<(Buffer, u64)>,
73}
74
75impl TextRenderer {
76 pub fn new(device: &RenderDevice, format: wgpu::TextureFormat) -> Self {
83 let atlas_data = generate_font_atlas();
85
86 let texture_size = wgpu::Extent3d {
88 width: ATLAS_W,
89 height: ATLAS_H,
90 depth_or_array_layers: 1,
91 };
92 let font_texture = device.device().create_texture(&wgpu::TextureDescriptor {
93 label: Some("Font Atlas"),
94 size: texture_size,
95 mip_level_count: 1,
96 sample_count: 1,
97 dimension: wgpu::TextureDimension::D2,
98 format: wgpu::TextureFormat::Rgba8UnormSrgb,
99 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
100 view_formats: &[],
101 });
102 device.queue().write_texture(
103 wgpu::ImageCopyTexture {
104 texture: &font_texture,
105 mip_level: 0,
106 origin: wgpu::Origin3d::ZERO,
107 aspect: wgpu::TextureAspect::All,
108 },
109 &atlas_data,
110 wgpu::ImageDataLayout {
111 offset: 0,
112 bytes_per_row: Some(4 * ATLAS_W),
113 rows_per_image: Some(ATLAS_H),
114 },
115 texture_size,
116 );
117 let font_texture_view = font_texture.create_view(&wgpu::TextureViewDescriptor::default());
118 let font_sampler = device.device().create_sampler(&wgpu::SamplerDescriptor {
119 label: Some("Font Sampler"),
120 mag_filter: wgpu::FilterMode::Nearest,
121 min_filter: wgpu::FilterMode::Nearest,
122 ..Default::default()
123 });
124
125 let ortho_uniform = OrthoUniform {
127 projection: Mat4::IDENTITY.to_cols_array_2d(),
128 };
129 let ortho_buffer = create_uniform_buffer(
130 device,
131 "Text Ortho Uniform",
132 bytemuck::bytes_of(&ortho_uniform),
133 );
134
135 let ortho_bgl = device.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
137 label: Some("Text Ortho BGL"),
138 entries: &[wgpu::BindGroupLayoutEntry {
139 binding: 0,
140 visibility: wgpu::ShaderStages::VERTEX,
141 ty: wgpu::BindingType::Buffer {
142 ty: wgpu::BufferBindingType::Uniform,
143 has_dynamic_offset: false,
144 min_binding_size: None,
145 },
146 count: None,
147 }],
148 });
149
150 let font_bgl = device.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
151 label: Some("Text Font BGL"),
152 entries: &[
153 wgpu::BindGroupLayoutEntry {
154 binding: 0,
155 visibility: wgpu::ShaderStages::FRAGMENT,
156 ty: wgpu::BindingType::Texture {
157 sample_type: wgpu::TextureSampleType::Float { filterable: true },
158 view_dimension: wgpu::TextureViewDimension::D2,
159 multisampled: false,
160 },
161 count: None,
162 },
163 wgpu::BindGroupLayoutEntry {
164 binding: 1,
165 visibility: wgpu::ShaderStages::FRAGMENT,
166 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
167 count: None,
168 },
169 ],
170 });
171
172 let ortho_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
174 label: Some("Text Ortho BG"),
175 layout: &ortho_bgl,
176 entries: &[wgpu::BindGroupEntry {
177 binding: 0,
178 resource: ortho_buffer.as_entire_binding(),
179 }],
180 });
181
182 let font_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
183 label: Some("Text Font BG"),
184 layout: &font_bgl,
185 entries: &[
186 wgpu::BindGroupEntry {
187 binding: 0,
188 resource: wgpu::BindingResource::TextureView(&font_texture_view),
189 },
190 wgpu::BindGroupEntry {
191 binding: 1,
192 resource: wgpu::BindingResource::Sampler(&font_sampler),
193 },
194 ],
195 });
196
197 let pipeline = RenderPipelineBuilder::new()
199 .with_vertex_shader(SPRITE_SHADER)
200 .with_fragment_shader(SPRITE_SHADER)
201 .with_format(format)
202 .with_vertex_layouts(vec![SpriteVertex::layout()])
203 .with_bind_group_layouts(vec![ortho_bgl, font_bgl])
204 .with_label("Text Pipeline")
205 .build(device)
206 .expect("创建 Text 管线失败")
207 .into_pipeline();
208
209 Self {
210 pipeline,
211 font_bind_group,
212 ortho_buffer,
213 ortho_bind_group,
214 cached_vb: None,
215 }
216 }
217
218 pub fn draw_text(
231 &mut self,
232 device: &RenderDevice,
233 encoder: &mut CommandEncoder,
234 target: &TextureView,
235 text: &str,
236 x: f32,
237 y: f32,
238 font_size: f32,
239 color: Vec3,
240 screen_w: f32,
241 screen_h: f32,
242 ) {
243 if text.is_empty() {
244 return;
245 }
246
247 let ortho = OrthoUniform {
249 projection: Mat4::orthographic_lh(0.0, screen_w, screen_h, 0.0, -1.0, 1.0)
250 .to_cols_array_2d(),
251 };
252 device
253 .queue()
254 .write_buffer(&self.ortho_buffer, 0, bytemuck::bytes_of(&ortho));
255
256 let glyph_w = font_size * (GLYPH_W as f32 / GLYPH_H as f32);
258 let glyph_h = font_size;
259
260 let mut vertices = Vec::with_capacity(text.len() * 6);
261 let mut cursor_x = x;
262
263 for ch in text.bytes() {
264 if ch < FIRST_CHAR || ch >= LAST_CHAR {
265 cursor_x += glyph_w;
266 continue;
267 }
268
269 let (u0, v0, u1, v1) = char_uv(ch);
270
271 let x0 = cursor_x;
273 let y0 = y;
274 let x1 = cursor_x + glyph_w;
275 let y1 = y + glyph_h;
276 let c = [color.x, color.y, color.z];
277
278 vertices.push(SpriteVertex { position: [x0, y0, 0.0], texcoord: [u0, v0], color: c });
279 vertices.push(SpriteVertex { position: [x1, y0, 0.0], texcoord: [u1, v0], color: c });
280 vertices.push(SpriteVertex { position: [x1, y1, 0.0], texcoord: [u1, v1], color: c });
281
282 vertices.push(SpriteVertex { position: [x0, y0, 0.0], texcoord: [u0, v0], color: c });
283 vertices.push(SpriteVertex { position: [x1, y1, 0.0], texcoord: [u1, v1], color: c });
284 vertices.push(SpriteVertex { position: [x0, y1, 0.0], texcoord: [u0, v1], color: c });
285
286 cursor_x += glyph_w;
287 }
288
289 if vertices.is_empty() {
290 return;
291 }
292
293 let data: &[u8] = bytemuck::cast_slice(&vertices);
295 let needed = data.len() as u64;
296 let reuse = self.cached_vb.as_ref().map_or(false, |(_, cap)| *cap >= needed);
297 if !reuse {
298 self.cached_vb = Some((
299 device.device().create_buffer(&wgpu::BufferDescriptor {
300 label: Some("Text VB (cached)"),
301 size: needed,
302 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
303 mapped_at_creation: false,
304 }),
305 needed,
306 ));
307 }
308 let vertex_buffer = &self.cached_vb.as_ref().unwrap().0;
309 device.queue().write_buffer(vertex_buffer, 0, data);
310
311 {
312 let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
313 label: Some("Text Pass"),
314 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
315 view: target,
316 resolve_target: None,
317 ops: wgpu::Operations {
318 load: wgpu::LoadOp::Load,
319 store: wgpu::StoreOp::Store,
320 },
321 })],
322 depth_stencil_attachment: None,
323 timestamp_writes: None,
324 occlusion_query_set: None,
325 });
326
327 rp.set_pipeline(&self.pipeline);
328 rp.set_bind_group(0, &self.ortho_bind_group, &[]);
329 rp.set_bind_group(1, &self.font_bind_group, &[]);
330 rp.set_vertex_buffer(0, vertex_buffer.slice(..));
331 rp.draw(0..vertices.len() as u32, 0..1);
332 }
333 }
334}
335
336fn char_uv(ascii: u8) -> (f32, f32, f32, f32) {
342 let idx = (ascii - FIRST_CHAR) as u32;
343 let col = idx % GLYPHS_PER_ROW;
344 let row = idx / GLYPHS_PER_ROW;
345
346 let u0 = (col * GLYPH_W) as f32 / ATLAS_W as f32;
347 let v0 = (row * GLYPH_H) as f32 / ATLAS_H as f32;
348 let u1 = ((col + 1) * GLYPH_W) as f32 / ATLAS_W as f32;
349 let v1 = ((row + 1) * GLYPH_H) as f32 / ATLAS_H as f32;
350
351 (u0, v0, u1, v1)
352}
353
354fn generate_font_atlas() -> Vec<u8> {
359 let font_data = include_cp437_font();
362
363 let mut rgba = vec![0u8; (ATLAS_W * ATLAS_H * 4) as usize];
364
365 for char_idx in 0..95u32 {
366 let col = char_idx % GLYPHS_PER_ROW;
367 let row = char_idx / GLYPHS_PER_ROW;
368 let glyph = &font_data[char_idx as usize];
369
370 for gy in 0..GLYPH_H {
371 let bits = glyph[gy as usize];
372 for gx in 0..GLYPH_W {
373 let px = col * GLYPH_W + gx;
374 let py = row * GLYPH_H + gy;
375 let offset = ((py * ATLAS_W + px) * 4) as usize;
376
377 let on = (bits >> (7 - gx)) & 1 != 0;
378 let val = if on { 255u8 } else { 0u8 };
379 rgba[offset] = val;
380 rgba[offset + 1] = val;
381 rgba[offset + 2] = val;
382 rgba[offset + 3] = val; }
384 }
385 }
386
387 rgba
388}
389
390fn include_cp437_font() -> Vec<[u8; 16]> {
394 let raw: &[u8] = &[
397 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
399 0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
401 0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
403 0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00,
405 0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06,0x06,0x86,0xC6,0x7C,0x18,0x18,0x00,0x00,
407 0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00,
409 0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
411 0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
413 0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00,
415 0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00,
417 0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00,
419 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
421 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00,
423 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
425 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
427 0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00,
429 0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xDE,0xF6,0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
431 0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00,
433 0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00,
435 0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00,
437 0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00,
439 0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00,
441 0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
443 0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00,
445 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
447 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00,
449 0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,
451 0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00,
453 0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00,
455 0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
457 0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00,
459 0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
461 0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00,
463 0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
465 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00,
467 0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00,
469 0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00,
471 0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00,
473 0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
475 0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00,
477 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
479 0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
481 0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00,
483 0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00,
485 0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00,
487 0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
489 0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,
491 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
493 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
495 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00,
497 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00,
499 0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
501 0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
503 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
505 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00,
507 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00,
509 0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00,
511 0x00,0x00,0xC6,0xC6,0xC6,0x6C,0x38,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
513 0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00,
515 0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00,
517 0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00,
519 0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00,
521 0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
523 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,
525 0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
527 0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
529 0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00,
531 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00,
533 0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
535 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00,
537 0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
539 0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00,
541 0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00,
543 0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
545 0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00,
547 0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00,
549 0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
551 0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xD6,0xD6,0xC6,0x00,0x00,0x00,0x00,
553 0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00,
555 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00,
557 0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00,
559 0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00,
561 0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00,
563 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00,
565 0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00,
567 0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,
569 0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00,
571 0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00,
573 0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,
575 0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00,
577 0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00,
579 0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00,
581 0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,
583 0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00,
585 0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
587 ];
588
589 let mut glyphs = Vec::with_capacity(95);
590 for i in 0..95 {
591 let mut glyph = [0u8; 16];
592 glyph.copy_from_slice(&raw[i * 16..(i + 1) * 16]);
593 glyphs.push(glyph);
594 }
595 glyphs
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601
602 #[test]
603 fn test_char_uv_space() {
604 let (u0, v0, u1, v1) = char_uv(b' ');
605 assert_eq!(u0, 0.0);
606 assert_eq!(v0, 0.0);
607 assert!((u1 - GLYPH_W as f32 / ATLAS_W as f32).abs() < 1e-5);
608 assert!((v1 - GLYPH_H as f32 / ATLAS_H as f32).abs() < 1e-5);
609 }
610
611 #[test]
612 fn test_char_uv_a() {
613 let idx = b'A' - FIRST_CHAR;
614 let col = idx as u32 % GLYPHS_PER_ROW;
615 let row = idx as u32 / GLYPHS_PER_ROW;
616
617 let (u0, v0, _, _) = char_uv(b'A');
618 assert!((u0 - (col * GLYPH_W) as f32 / ATLAS_W as f32).abs() < 1e-5);
619 assert!((v0 - (row * GLYPH_H) as f32 / ATLAS_H as f32).abs() < 1e-5);
620 }
621
622 #[test]
623 fn test_font_atlas_size() {
624 let atlas = generate_font_atlas();
625 assert_eq!(atlas.len(), (ATLAS_W * ATLAS_H * 4) as usize);
626 }
627
628 #[test]
629 fn test_font_data_glyph_count() {
630 let glyphs = include_cp437_font();
631 assert_eq!(glyphs.len(), 95);
632 }
633
634 #[test]
635 fn test_space_glyph_is_empty() {
636 let glyphs = include_cp437_font();
637 let space = &glyphs[0]; for &row in space.iter() {
639 assert_eq!(row, 0, "Space glyph should be all zeros");
640 }
641 }
642
643 #[test]
644 fn test_char_uv_bounds() {
645 for ch in FIRST_CHAR..LAST_CHAR {
646 let (u0, v0, u1, v1) = char_uv(ch);
647 assert!(u0 >= 0.0 && u0 <= 1.0, "u0 out of bounds for char {}", ch);
648 assert!(v0 >= 0.0 && v0 <= 1.0, "v0 out of bounds for char {}", ch);
649 assert!(u1 >= 0.0 && u1 <= 1.0, "u1 out of bounds for char {}", ch);
650 assert!(v1 >= 0.0 && v1 <= 1.0, "v1 out of bounds for char {}", ch);
651 assert!(u1 > u0);
652 assert!(v1 > v0);
653 }
654 }
655}