fyrox_ui/font/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#![allow(clippy::unnecessary_to_owned)] // false-positive

use crate::core::{
    algebra::Vector2, rectpack::RectPacker, reflect::prelude::*, uuid::Uuid, uuid_provider,
    visitor::prelude::*, TypeUuidProvider,
};
use fxhash::FxHashMap;
use fyrox_resource::untyped::UntypedResource;
use fyrox_resource::{io::ResourceIo, Resource, ResourceData};
use lazy_static::lazy_static;
use std::fmt::Formatter;
use std::{
    any::Any,
    error::Error,
    fmt::Debug,
    hash::{Hash, Hasher},
    ops::Deref,
    path::Path,
};

pub mod loader;

#[derive(Debug)]
pub struct FontGlyph {
    pub top: f32,
    pub left: f32,
    pub advance: f32,
    pub tex_coords: [Vector2<f32>; 4],
    pub bitmap_width: usize,
    pub bitmap_height: usize,
    pub page_index: usize,
}

/// Page is a storage for rasterized glyphs.
pub struct Page {
    pub pixels: Vec<u8>,
    pub texture: Option<UntypedResource>,
    pub rect_packer: RectPacker<usize>,
    pub modified: bool,
}

impl Debug for Page {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Page")
            .field("Pixels", &self.pixels)
            .field("Texture", &self.texture)
            .field("Modified", &self.modified)
            .finish()
    }
}

/// Atlas is a storage for glyphs of a particular size, each atlas could have any number of pages to
/// store the rasterized glyphs.
#[derive(Default, Debug)]
pub struct Atlas {
    pub glyphs: Vec<FontGlyph>,
    pub char_map: FxHashMap<char, usize>,
    pub pages: Vec<Page>,
}

impl Atlas {
    fn glyph(
        &mut self,
        font: &fontdue::Font,
        unicode: char,
        height: FontHeight,
        page_size: usize,
    ) -> Option<&FontGlyph> {
        let border = 2;

        match self.char_map.get(&unicode) {
            Some(glyph_index) => {
                return self.glyphs.get(*glyph_index);
            }
            None => {
                // Char might be missing, because it wasn't requested earlier. Try to find
                // it in the inner font and render/pack it.

                if let Some(char_index) = font.chars().get(&unicode) {
                    let (metrics, glyph_raster) =
                        font.rasterize_indexed(char_index.get(), height.0);

                    // Find a page, that is capable to fit the new character or create a new
                    // page and put the character there.
                    let mut placement_info =
                        self.pages
                            .iter_mut()
                            .enumerate()
                            .find_map(|(page_index, page)| {
                                page.rect_packer
                                    .find_free(metrics.width + border, metrics.height + border)
                                    .map(|bounds| (page_index, bounds))
                            });

                    // No space for the character in any of the existing pages, create a new page.
                    if placement_info.is_none() {
                        let mut page = Page {
                            pixels: vec![0; page_size * page_size],
                            texture: None,
                            rect_packer: RectPacker::new(page_size, page_size),
                            modified: true,
                        };

                        let page_index = self.pages.len();

                        match page
                            .rect_packer
                            .find_free(metrics.width + border, metrics.height + border)
                        {
                            Some(bounds) => {
                                placement_info = Some((page_index, bounds));

                                self.pages.push(page);
                            }
                            None => {
                                // No free space in the given page size (requested glyph is too big).
                                return None;
                            }
                        }
                    }

                    let (page_index, placement_rect) = placement_info?;
                    let page = &mut self.pages[page_index];
                    let glyph_index = self.glyphs.len();

                    // Raise a flag to notify users that the content of the page has changed, and
                    // it should be re-uploaded to GPU (if needed).
                    page.modified = true;

                    let mut glyph = FontGlyph {
                        left: metrics.xmin as f32,
                        top: metrics.ymin as f32,
                        advance: metrics.advance_width,
                        tex_coords: Default::default(),
                        bitmap_width: metrics.width,
                        bitmap_height: metrics.height,
                        page_index,
                    };

                    let k = 1.0 / page_size as f32;

                    let bw = placement_rect.w().saturating_sub(border);
                    let bh = placement_rect.h().saturating_sub(border);
                    let bx = placement_rect.x() + border / 2;
                    let by = placement_rect.y() + border / 2;

                    let tw = bw as f32 * k;
                    let th = bh as f32 * k;
                    let tx = bx as f32 * k;
                    let ty = by as f32 * k;

                    glyph.tex_coords[0] = Vector2::new(tx, ty);
                    glyph.tex_coords[1] = Vector2::new(tx + tw, ty);
                    glyph.tex_coords[2] = Vector2::new(tx + tw, ty + th);
                    glyph.tex_coords[3] = Vector2::new(tx, ty + th);

                    let row_end = by + bh;
                    let col_end = bx + bw;

                    // Copy glyph pixels to the atlas pixels
                    for (src_row, row) in (by..row_end).enumerate() {
                        for (src_col, col) in (bx..col_end).enumerate() {
                            page.pixels[row * page_size + col] =
                                glyph_raster[src_row * bw + src_col];
                        }
                    }

                    self.glyphs.push(glyph);

                    // Map the new glyph to its unicode position.
                    self.char_map.insert(unicode, glyph_index);

                    self.glyphs.get(glyph_index)
                } else {
                    None
                }
            }
        }
    }
}

#[derive(Default, Debug, Reflect, Visit)]
#[reflect(hide_all)]
pub struct Font {
    #[visit(skip)]
    pub inner: Option<fontdue::Font>,
    #[visit(skip)]
    pub atlases: FxHashMap<FontHeight, Atlas>,
    #[visit(skip)]
    pub page_size: usize,
}

uuid_provider!(Font = "692fec79-103a-483c-bb0b-9fc3a349cb48");

impl ResourceData for Font {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn type_uuid(&self) -> Uuid {
        <Self as TypeUuidProvider>::type_uuid()
    }

    fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
        Ok(())
    }

    fn can_be_saved(&self) -> bool {
        false
    }
}

#[derive(Copy, Clone, Default, Debug)]
pub struct FontHeight(pub f32);

impl From<f32> for FontHeight {
    fn from(value: f32) -> Self {
        Self(value)
    }
}

impl PartialEq for FontHeight {
    fn eq(&self, other: &Self) -> bool {
        fyrox_core::value_as_u8_slice(&self.0) == fyrox_core::value_as_u8_slice(&other.0)
    }
}

impl Eq for FontHeight {}

impl Hash for FontHeight {
    fn hash<H: Hasher>(&self, state: &mut H) {
        // Don't care about "genius" Rust decision to make f32 non-hashable. If a user is dumb enough
        // to put NaN or any other special value as a glyph height, then it is their choice.
        fyrox_core::hash_as_bytes(&self.0, state)
    }
}

pub type FontResource = Resource<Font>;

lazy_static! {
    pub static ref BUILT_IN_FONT: FontResource = FontResource::new_ok(
        "__BUILT_IN_FONT__".into(),
        Font::from_memory(include_bytes!("./built_in_font.ttf").to_vec(), 1024).unwrap(),
    );
}

impl Font {
    pub fn from_memory(
        data: impl Deref<Target = [u8]>,
        page_size: usize,
    ) -> Result<Self, &'static str> {
        let fontdue_font = fontdue::Font::from_bytes(data, fontdue::FontSettings::default())?;
        Ok(Font {
            inner: Some(fontdue_font),
            atlases: Default::default(),
            page_size,
        })
    }

    pub async fn from_file<P: AsRef<Path>>(
        path: P,
        page_size: usize,
        io: &dyn ResourceIo,
    ) -> Result<Self, &'static str> {
        if let Ok(file_content) = io.load_file(path.as_ref()).await {
            Self::from_memory(file_content, page_size)
        } else {
            Err("Unable to read file")
        }
    }

    /// Tries to get a glyph at the given unicode position of the given height. If there's no rendered
    /// glyph, this method tries to render the glyph and put into a suitable atlas (see [`Atlas`] docs
    /// for more info). If the given unicode position has no representation in the font, [`None`] will
    /// be returned. If the requested size of the glyph is too big to fit into the page size of the
    /// font, [`None`] will be returned. Keep in mind, that this method is free to create as many atlases
    /// with any number of pages in them. Each atlas corresponds to a particular glyph size, each glyph
    /// in the atlas could be rendered at any page in the atlas.
    #[inline]
    pub fn glyph(&mut self, unicode: char, height: f32) -> Option<&FontGlyph> {
        self.atlases
            .entry(FontHeight(height))
            .or_insert_with(|| Atlas {
                glyphs: Default::default(),
                char_map: Default::default(),
                pages: Default::default(),
            })
            .glyph(
                self.inner
                    .as_ref()
                    .expect("Font reader must be initialized!"),
                unicode,
                FontHeight(height),
                self.page_size,
            )
    }

    #[inline]
    pub fn ascender(&self, height: f32) -> f32 {
        self.inner
            .as_ref()
            .unwrap()
            .horizontal_line_metrics(height)
            .map(|m| m.ascent)
            .unwrap_or_default()
    }

    #[inline]
    pub fn descender(&self, height: f32) -> f32 {
        self.inner
            .as_ref()
            .unwrap()
            .horizontal_line_metrics(height)
            .map(|m| m.descent)
            .unwrap_or_default()
    }

    #[inline]
    pub fn page_size(&self) -> usize {
        self.page_size
    }

    #[inline]
    pub fn glyph_advance(&mut self, unicode: char, height: f32) -> f32 {
        self.glyph(unicode, height)
            .map_or(height, |glyph| glyph.advance)
    }
}

/// Font builder allows you to load fonts in declarative manner.
pub struct FontBuilder {
    page_size: usize,
}

impl FontBuilder {
    /// Creates a default FontBuilder.
    pub fn new() -> Self {
        Self { page_size: 1024 }
    }

    /// Creates a new font from the data at the specified path.
    pub async fn build_from_file(
        self,
        path: impl AsRef<Path>,
        io: &dyn ResourceIo,
    ) -> Result<Font, &'static str> {
        Font::from_file(path, self.page_size, io).await
    }

    /// Creates a new font from bytes in memory.
    pub fn build_from_memory(self, data: impl Deref<Target = [u8]>) -> Result<Font, &'static str> {
        Font::from_memory(data, self.page_size)
    }
}