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 GlyphInfo {
89 pub x_advance: i16,
90 pub x_offset: i16,
91 pub y_offset: i16,
92}
93
94#[derive(Debug)]
95pub struct Glyph {
96 pub relative_position: Vec2,
97 pub texture_rectangle: URect,
98 pub cursor: Vec2,
99 pub info: GlyphInfo,
100}
101
102#[derive(Debug)]
103pub struct GlyphDraw {
104 pub glyphs: Vec<Glyph>,
105 pub cursor: Vec2,
106}
107
108impl Font {
109 #[must_use]
112 pub fn from_octets(bm_contents: &[u8]) -> Self {
113 let font = BMFont::from_octets(bm_contents).unwrap();
114 Self { font }
115 }
116
117 #[must_use]
118 pub const fn info(&self) -> &BMFont {
119 &self.font
120 }
121
122 #[must_use]
125 pub fn draw(&self, text: &str) -> GlyphDraw {
126 let mut x = 0;
127 let y = 0;
128 let common = self.font.common.as_ref().unwrap();
129 let mut glyphs = Vec::new();
130 let factor = 1u16;
131 let y_offset = (common.base as i16) + 1;
132 for ch in text.chars() {
133 if let Some(bm_char) = self.font.chars.get(&(ch as u32)) {
134 let cx = x + bm_char.x_offset * factor as i16;
135 let cy = y + y_offset - (bm_char.height as i16) - bm_char.y_offset;
136
137 let glyph = Glyph {
138 relative_position: Vec2 { x: cx, y: cy },
139 texture_rectangle: URect {
140 position: UVec2 {
141 x: bm_char.x,
142 y: bm_char.y,
143 },
144 size: UVec2 {
145 x: bm_char.width,
146 y: bm_char.height,
147 },
148 },
149 cursor: Vec2::new(x, y),
150 info: GlyphInfo {
151 x_offset: bm_char.x_offset,
152 y_offset: bm_char.y_offset,
153 x_advance: bm_char.x_advance,
154 },
155 };
156 x += bm_char.x_advance * factor as i16;
157
158 glyphs.push(glyph);
159 }
160 }
161
162 GlyphDraw {
163 glyphs,
164 cursor: Vec2::new(x, y),
165 }
166 }
167}