mireforge_font/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/mireforge/mireforge
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use 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    /// # Panics
110    ///
111    #[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    /// # Panics
123    ///
124    #[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}