use std::collections::HashMap; use std::collections::HashSet; use std::io; use std::path::PathBuf; use byteorder::{LE, ReadBytesExt}; use crate::common::{FILE_TYPES, Rect}; use crate::engine_constants::EngineConstants; use crate::framework::context::Context; use crate::framework::error::GameError::ResourceLoadError; use crate::framework::error::GameResult; use crate::framework::filesystem; use crate::graphics::font::{EMPTY_SYMBOLS, Font, Symbols, TextBuilderFlag}; use crate::graphics::texture_set::TextureSet; #[derive(Debug)] pub struct BMChar { pub x: u16, pub y: u16, pub width: u16, pub height: u16, pub x_offset: i16, pub y_offset: i16, pub x_advance: i16, pub page: u8, pub channel: u8, } #[derive(Debug)] pub struct BMFontMetadata { pub pages: u16, pub font_size: i16, pub line_height: u16, pub base: u16, pub chars: HashMap, } #[repr(u8)] pub enum BMFontBlockType { Unknown = 0, Info = 1, Common = 2, Pages = 3, Chars = 4, KerningPairs = 5, } impl From for BMFontBlockType { fn from(value: u8) -> Self { match value { 1 => BMFontBlockType::Info, 2 => BMFontBlockType::Common, 3 => BMFontBlockType::Pages, 4 => BMFontBlockType::Chars, 5 => BMFontBlockType::KerningPairs, _ => BMFontBlockType::Unknown, } } } const MAGIC: [u8; 4] = [b'B', b'M', b'F', 3]; impl BMFontMetadata { pub fn load_from(mut data: R) -> GameResult { let mut magic = [0u8; 4]; let mut pages = 0u16; let mut chars = HashMap::new(); let mut font_size = 0i16; let mut line_height = 0u16; let mut base = 0u16; data.read_exact(&mut magic)?; if magic != MAGIC { return Err(ResourceLoadError("Invalid magic".to_owned())); } while let Ok(block_type) = data.read_u8() { let length = data.read_u32::()?; match BMFontBlockType::from(block_type) { BMFontBlockType::Info => { font_size = data.read_i16::()?; data.seek(io::SeekFrom::Current(length as i64 - 2))?; } BMFontBlockType::Common => { line_height = data.read_u16::()?; base = data.read_u16::()?; data.seek(io::SeekFrom::Current(4))?; pages = data.read_u16::()?; data.seek(io::SeekFrom::Current(length as i64 - 10))?; } BMFontBlockType::Chars => { let count = length / 20; chars.reserve(count as usize); for _ in 0..count { let id = data.read_u32::()?; let x = data.read_u16::()?; let y = data.read_u16::()?; let width = data.read_u16::()?; let height = data.read_u16::()?; let x_offset = data.read_i16::()?; let y_offset = data.read_i16::()?; let x_advance = data.read_i16::()?; let page = data.read_u8()?; let channel = data.read_u8()?; if let Some(chr) = std::char::from_u32(id) { chars.insert( chr, BMChar { x, y, width, height, x_offset, y_offset, x_advance, page, channel }, ); } } } BMFontBlockType::Unknown => { return Err(ResourceLoadError("Unknown block type.".to_owned())); } _ => { data.seek(io::SeekFrom::Current(length as i64))?; } } } Ok(Self { pages, font_size, line_height, base, chars }) } } pub struct BMFont { font: BMFontMetadata, font_scale: f32, pages: Vec, } impl Font for BMFont { fn line_height(&self) -> f32 { self.font.line_height as f32 * self.font_scale } fn compute_width(&self, text: &mut dyn Iterator, symbols: Option<&Symbols>) -> f32 { let mut offset_x = 0.0; if let Some(syms) = symbols { for chr in text { let rect_map_entry = syms.symbols.iter().find(|(c, _)| *c == chr); if let Some((_, rect)) = rect_map_entry { offset_x += rect.width() as f32; } else if let Some(glyph) = self.font.chars.get(&chr) { offset_x += glyph.x_advance as f32 * self.font_scale; } } } else { for chr in text { if let Some(glyph) = self.font.chars.get(&chr) { offset_x += glyph.x_advance as f32 * self.font_scale; } } } offset_x } fn draw( &self, text: &mut dyn Iterator, mut x: f32, y: f32, scale: f32, box_width: f32, shadow_color: (u8, u8, u8, u8), color: (u8, u8, u8, u8), flags: TextBuilderFlag, constants: &EngineConstants, texture_set: &mut TextureSet, symbols: Option, ctx: &mut Context, ) -> GameResult { unsafe { static mut TEXT_BUF: Vec = Vec::new(); TEXT_BUF.clear(); for c in text { TEXT_BUF.push(c); } if flags.centered() { let text_width = self.compute_width(&mut TEXT_BUF.iter().copied(), symbols.as_ref()); x += (box_width - text_width) * 0.5; } if flags.shadow() { self.draw_text_line( &mut TEXT_BUF.iter().copied(), x + scale, y + scale, scale, shadow_color, constants, texture_set, symbols.as_ref(), ctx, )?; } self.draw_text_line( &mut TEXT_BUF.iter().copied(), x, y, scale, color, constants, texture_set, symbols.as_ref(), ctx, )?; } Ok(()) } } impl BMFont { pub fn load(roots: &Vec, desc_path: &str, ctx: &mut Context, font_scale: f32) -> GameResult { let full_path = PathBuf::from(desc_path); let desc_stem = full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?; let stem = full_path.parent().unwrap_or(&full_path).join(desc_stem); let font = BMFontMetadata::load_from(filesystem::open_find(ctx, roots, &full_path)?)?; let mut pages = Vec::new(); let (zeros, _, _) = FILE_TYPES .iter() .map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext))) .find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path)) .or_else(|| { FILE_TYPES .iter() .map(|ext| (2, ext, format!("{}_00{}", stem.to_string_lossy(), ext))) .find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path)) }) .ok_or_else(|| ResourceLoadError(format!("Cannot find glyph atlas 0 for font: {:?}", desc_path)))?; for i in 0..font.pages { let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros); pages.push(page_path); } Ok(Self { font, font_scale, pages }) } fn draw_text_line( &self, iter: &mut dyn Iterator, x: f32, y: f32, scale: f32, color: (u8, u8, u8, u8), constants: &EngineConstants, texture_set: &mut TextureSet, symbols: Option<&Symbols>, ctx: &mut Context, ) -> GameResult { unsafe { static mut RECTS_BUF: Vec<(f32, f32, *const Rect)> = Vec::new(); let syms = symbols.unwrap_or(&EMPTY_SYMBOLS); RECTS_BUF.clear(); if self.pages.len() == 1 { let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?; let mut offset_x = x; for chr in iter { if let Some(glyph) = self.font.chars.get(&chr) { let rect_map_entry = syms.symbols.iter().find(|(c, _)| *c == chr); if let Some((_, rect)) = rect_map_entry { RECTS_BUF.push(( offset_x, y + self.line_height() / 2.0 - rect.height() as f32 / 2.0, rect as *const _, )); offset_x += rect.width() as f32; } else { batch.add_rect_scaled_tinted( offset_x + (glyph.x_offset as f32 * self.font_scale), y + (glyph.y_offset as f32 * self.font_scale), color, self.font_scale * scale, self.font_scale * scale, &Rect::new_size( glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16, ), ); offset_x += glyph.x_advance as f32 * self.font_scale * scale; } } } batch.draw(ctx)?; } else { let mut pages = HashSet::new(); let mut chars = Vec::new(); for chr in iter { if let Some(glyph) = self.font.chars.get(&chr) { pages.insert(glyph.page); chars.push((chr, glyph)); } } for page in pages { let page_tex = if let Some(p) = self.pages.get(page as usize) { p } else { continue; }; let batch = texture_set.get_or_load_batch(ctx, constants, page_tex)?; let mut offset_x = x; for (chr, glyph) in chars.iter() { let rect_map_entry = syms.symbols.iter().find(|(c, _)| *c == *chr); if let Some((_, rect)) = rect_map_entry { RECTS_BUF.push((offset_x, y + self.line_height() / 2.0 - rect.height() as f32 / 2.0, rect)); offset_x += rect.width() as f32; } else { if glyph.page == page { batch.add_rect_scaled_tinted( offset_x + (glyph.x_offset as f32 * self.font_scale), y + (glyph.y_offset as f32 * self.font_scale), color, self.font_scale * scale, self.font_scale * scale, &Rect::new_size( glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16, ), ); } offset_x += scale * (glyph.x_advance as f32 * self.font_scale); } } batch.draw(ctx)?; } } if !RECTS_BUF.is_empty() && !syms.texture.is_empty() { let sprite_batch = texture_set.get_or_load_batch(ctx, constants, syms.texture)?; for &(x, y, rect) in RECTS_BUF.iter() { sprite_batch.add_rect_scaled(x, y, scale, scale, &*rect); } sprite_batch.draw(ctx)?; } } Ok(()) } pub fn scale(&mut self, scale: f32) { self.font_scale = scale; } pub fn get_scale(&self) -> f32 { self.font_scale } }