1use bmf_parser::BMFont;
6use int_math::{URect, UVec2, Vec2};
7use limnus_app::prelude::{App, Plugin};
8use limnus_asset_registry::AssetRegistry;
9use limnus_assets::Assets;
10use limnus_assets::prelude::{Asset, AssetName, Id, RawWeakId, WeakId};
11use limnus_assets_loader::{
12 AssetLoader, ConversionError, ResourceStorage, WrappedAssetLoaderRegistry,
13};
14use limnus_local_resource::LocalResourceStorage;
15use std::str::FromStr;
16use tracing::debug;
17
18pub type FontRef = Id<Font>;
19pub type WeakFontRef = WeakId<Font>;
20
21#[derive(Debug, Asset)]
22pub struct Font {
23 font: BMFont,
24}
25
26pub struct FontPlugin;
27
28impl Plugin for FontPlugin {
29 fn build(&self, app: &mut App) {
30 {
31 let registry = app.resource_mut::<WrappedAssetLoaderRegistry>();
32 let loader = FontConverter::new();
33
34 registry.value.lock().unwrap().register_loader(loader);
35 }
36
37 app.insert_resource(Assets::<Font>::default());
38 }
39}
40
41#[derive(Default)]
42pub struct FontConverter;
43
44impl FontConverter {
45 #[must_use]
46 pub const fn new() -> Self {
47 Self {}
48 }
49}
50
51impl AssetLoader for FontConverter {
52 type AssetType = Font;
53
54 fn convert_and_insert(
55 &self,
56 id: RawWeakId,
57 octets: &[u8],
58 resources: &mut ResourceStorage,
59 _local_resources: &mut LocalResourceStorage,
60 ) -> Result<(), ConversionError> {
61 let name: AssetName;
62 {
63 let asset_container = resources.fetch::<AssetRegistry>();
64 name = asset_container
65 .name_raw(id)
66 .expect("should know about this Id");
67 }
68
69 debug!("convert from fnt {name}");
70
71 let font = if name.value().ends_with(".txt.fnt") {
72 let str = String::from_utf8(octets.to_vec()).unwrap();
73 BMFont::from_str(&str)?
74 } else {
75 BMFont::from_octets(octets)?
76 };
77
78 debug!("font complete {name}");
79 let font_assets = resources.fetch_mut::<Assets<Font>>();
80
81 font_assets.set_raw(id, Font { font });
82
83 Ok(())
84 }
85}
86
87#[derive(Debug)]
88pub struct Glyph {
89 pub relative_position: Vec2,
90 pub texture_rectangle: URect,
91}
92
93impl Font {
94 #[must_use]
97 pub fn from_octets(bm_contents: &[u8]) -> Self {
98 let font = BMFont::from_octets(bm_contents).unwrap();
99 Self { font }
100 }
101
102 pub fn info(&self) -> &BMFont {
103 &self.font
104 }
105
106 #[must_use]
109 pub fn draw(&self, text: &str) -> Vec<Glyph> {
110 let mut x = 0;
111 let y = 0;
112 let common = self.font.common.as_ref().unwrap();
113 let height = self.font.info.as_ref().unwrap().font_size;
114 let mut glyphs = Vec::new();
115 let factor = 1u16;
116 let y_offset = -(common.line_height as i16 - common.base as i16);
117 for ch in text.chars() {
118 if let Some(bm_char) = self.font.chars.get(&(ch as u32)) {
119 let cx = x + bm_char.x_offset * factor as i16;
120 let cy = y + y_offset + height - (bm_char.height as i16) - bm_char.y_offset;
121
122 let glyph = Glyph {
123 relative_position: Vec2 { x: cx, y: cy },
124 texture_rectangle: URect {
125 position: UVec2 {
126 x: bm_char.x,
127 y: bm_char.y,
128 },
129 size: UVec2 {
130 x: bm_char.width,
131 y: bm_char.height,
132 },
133 },
134 };
135 x += bm_char.x_advance * factor as i16;
136
137 glyphs.push(glyph);
138 }
139 }
140
141 glyphs
142 }
143}