1#![allow(clippy::unnecessary_to_owned)] use crate::core::{
24 algebra::Vector2, rectpack::RectPacker, reflect::prelude::*, uuid::Uuid, uuid_provider,
25 visitor::prelude::*, TypeUuidProvider,
26};
27use fxhash::FxHashMap;
28use fyrox_core::math::Rect;
29use fyrox_core::uuid;
30use fyrox_resource::untyped::ResourceKind;
31use fyrox_resource::{
32 embedded_data_source, io::ResourceIo, manager::BuiltInResource, untyped::UntypedResource,
33 Resource, ResourceData,
34};
35use lazy_static::lazy_static;
36use std::{
37 error::Error,
38 fmt::{Debug, Formatter},
39 hash::{Hash, Hasher},
40 ops::Deref,
41 path::Path,
42};
43
44pub mod loader;
45
46#[derive(Debug, Clone)]
47pub struct FontGlyph {
48 pub bitmap_top: f32,
49 pub bitmap_left: f32,
50 pub bitmap_width: f32,
51 pub bitmap_height: f32,
52 pub advance: f32,
53 pub tex_coords: [Vector2<f32>; 4],
54 pub page_index: usize,
55 pub bounds: Rect<f32>,
56}
57
58#[derive(Clone)]
60pub struct Page {
61 pub pixels: Vec<u8>,
62 pub texture: Option<UntypedResource>,
63 pub rect_packer: RectPacker<usize>,
64 pub modified: bool,
65}
66
67impl Debug for Page {
68 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69 f.debug_struct("Page")
70 .field("Pixels", &self.pixels)
71 .field("Texture", &self.texture)
72 .field("Modified", &self.modified)
73 .finish()
74 }
75}
76
77#[derive(Default, Clone, Debug)]
80pub struct Atlas {
81 pub glyphs: Vec<FontGlyph>,
82 pub char_map: FxHashMap<char, usize>,
83 pub pages: Vec<Page>,
84}
85
86impl Atlas {
87 fn glyph(
88 &mut self,
89 font: &fontdue::Font,
90 unicode: char,
91 height: FontHeight,
92 page_size: usize,
93 ) -> Option<&FontGlyph> {
94 let border = 2;
95
96 match self.char_map.get(&unicode) {
97 Some(glyph_index) => self.glyphs.get(*glyph_index),
98 None => {
99 if let Some(char_index) = font.chars().get(&unicode) {
103 if !height.0.is_finite() || height.0 <= f32::EPSILON {
104 return None;
105 }
106 let (metrics, glyph_raster) =
107 font.rasterize_indexed(char_index.get(), height.0);
108
109 let mut placement_info =
112 self.pages
113 .iter_mut()
114 .enumerate()
115 .find_map(|(page_index, page)| {
116 page.rect_packer
117 .find_free(metrics.width + border, metrics.height + border)
118 .map(|bounds| (page_index, bounds))
119 });
120
121 if placement_info.is_none() {
123 let mut page = Page {
124 pixels: vec![0; page_size * page_size],
125 texture: None,
126 rect_packer: RectPacker::new(page_size, page_size),
127 modified: true,
128 };
129
130 let page_index = self.pages.len();
131
132 match page
133 .rect_packer
134 .find_free(metrics.width + border, metrics.height + border)
135 {
136 Some(bounds) => {
137 placement_info = Some((page_index, bounds));
138
139 self.pages.push(page);
140 }
141 None => {
142 return None;
144 }
145 }
146 }
147
148 let (page_index, placement_rect) = placement_info?;
149 let page = &mut self.pages[page_index];
150 let glyph_index = self.glyphs.len();
151
152 page.modified = true;
155
156 let mut glyph = FontGlyph {
157 bitmap_left: metrics.xmin as f32,
158 bitmap_top: metrics.ymin as f32,
159 advance: metrics.advance_width,
160 tex_coords: Default::default(),
161 bitmap_width: metrics.width as f32,
162 bitmap_height: metrics.height as f32,
163 bounds: Rect::new(
164 metrics.bounds.xmin,
165 metrics.bounds.ymin,
166 metrics.bounds.width,
167 metrics.bounds.height,
168 ),
169 page_index,
170 };
171
172 let k = 1.0 / page_size as f32;
173
174 let bw = placement_rect.w().saturating_sub(border);
175 let bh = placement_rect.h().saturating_sub(border);
176 let bx = placement_rect.x() + border / 2;
177 let by = placement_rect.y() + border / 2;
178
179 let tw = bw as f32 * k;
180 let th = bh as f32 * k;
181 let tx = bx as f32 * k;
182 let ty = by as f32 * k;
183
184 glyph.tex_coords[0] = Vector2::new(tx, ty);
185 glyph.tex_coords[1] = Vector2::new(tx + tw, ty);
186 glyph.tex_coords[2] = Vector2::new(tx + tw, ty + th);
187 glyph.tex_coords[3] = Vector2::new(tx, ty + th);
188
189 let row_end = by + bh;
190 let col_end = bx + bw;
191
192 for (src_row, row) in (by..row_end).enumerate() {
194 for (src_col, col) in (bx..col_end).enumerate() {
195 page.pixels[row * page_size + col] =
196 glyph_raster[src_row * bw + src_col];
197 }
198 }
199
200 self.glyphs.push(glyph);
201
202 self.char_map.insert(unicode, glyph_index);
204
205 self.glyphs.get(glyph_index)
206 } else {
207 None
208 }
209 }
210 }
211 }
212}
213
214#[derive(Default, Clone, Debug, Reflect, Visit)]
215#[reflect(hide_all)]
216pub struct Font {
217 #[visit(skip)]
218 pub inner: Option<fontdue::Font>,
219 #[visit(skip)]
220 pub atlases: FxHashMap<FontHeight, Atlas>,
221 #[visit(skip)]
222 pub page_size: usize,
223}
224
225uuid_provider!(Font = "692fec79-103a-483c-bb0b-9fc3a349cb48");
226
227impl ResourceData for Font {
228 fn type_uuid(&self) -> Uuid {
229 <Self as TypeUuidProvider>::type_uuid()
230 }
231
232 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
233 Ok(())
234 }
235
236 fn can_be_saved(&self) -> bool {
237 false
238 }
239
240 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
241 Some(Box::new(self.clone()))
242 }
243}
244
245#[derive(Copy, Clone, Default, Debug)]
246pub struct FontHeight(pub f32);
247
248impl From<f32> for FontHeight {
249 fn from(value: f32) -> Self {
250 Self(value)
251 }
252}
253
254impl PartialEq for FontHeight {
255 fn eq(&self, other: &Self) -> bool {
256 fyrox_core::value_as_u8_slice(&self.0) == fyrox_core::value_as_u8_slice(&other.0)
257 }
258}
259
260impl Eq for FontHeight {}
261
262impl Hash for FontHeight {
263 fn hash<H: Hasher>(&self, state: &mut H) {
264 fyrox_core::hash_as_bytes(&self.0, state)
267 }
268}
269
270pub type FontResource = Resource<Font>;
271
272lazy_static! {
273 pub static ref BUILT_IN_FONT: BuiltInResource<Font> = BuiltInResource::new(
274 "__BUILT_IN_FONT__",
275 embedded_data_source!("./built_in_font.ttf"),
276 |data| {
277 FontResource::new_ok(
278 uuid!("77260e8e-f6fa-429c-8009-13dda2673925"),
279 ResourceKind::External,
280 Font::from_memory(data.to_vec(), 1024).unwrap(),
281 )
282 }
283 );
284}
285
286impl Font {
287 pub fn from_memory(
288 data: impl Deref<Target = [u8]>,
289 page_size: usize,
290 ) -> Result<Self, &'static str> {
291 let fontdue_font = fontdue::Font::from_bytes(data, fontdue::FontSettings::default())?;
292 Ok(Font {
293 inner: Some(fontdue_font),
294 atlases: Default::default(),
295 page_size,
296 })
297 }
298
299 pub async fn from_file<P: AsRef<Path>>(
300 path: P,
301 page_size: usize,
302 io: &dyn ResourceIo,
303 ) -> Result<Self, &'static str> {
304 if let Ok(file_content) = io.load_file(path.as_ref()).await {
305 Self::from_memory(file_content, page_size)
306 } else {
307 Err("Unable to read file")
308 }
309 }
310
311 #[inline]
319 pub fn glyph(&mut self, unicode: char, height: f32) -> Option<&FontGlyph> {
320 self.atlases
321 .entry(FontHeight(height))
322 .or_insert_with(|| Atlas {
323 glyphs: Default::default(),
324 char_map: Default::default(),
325 pages: Default::default(),
326 })
327 .glyph(
328 self.inner
329 .as_ref()
330 .expect("Font reader must be initialized!"),
331 unicode,
332 FontHeight(height),
333 self.page_size,
334 )
335 }
336
337 #[inline]
338 pub fn ascender(&self, height: f32) -> f32 {
339 self.inner
340 .as_ref()
341 .unwrap()
342 .horizontal_line_metrics(height)
343 .map(|m| m.ascent)
344 .unwrap_or_default()
345 }
346
347 #[inline]
348 pub fn descender(&self, height: f32) -> f32 {
349 self.inner
350 .as_ref()
351 .unwrap()
352 .horizontal_line_metrics(height)
353 .map(|m| m.descent)
354 .unwrap_or_default()
355 }
356
357 #[inline]
358 pub fn horizontal_kerning(&self, height: f32, left: char, right: char) -> Option<f32> {
359 self.inner
360 .as_ref()
361 .unwrap()
362 .horizontal_kern(left, right, height)
363 }
364
365 #[inline]
366 pub fn page_size(&self) -> usize {
367 self.page_size
368 }
369
370 #[inline]
371 pub fn glyph_advance(&mut self, unicode: char, height: f32) -> f32 {
372 self.glyph(unicode, height)
373 .map_or(height, |glyph| glyph.advance)
374 }
375}
376
377pub struct FontBuilder {
379 page_size: usize,
380}
381
382impl FontBuilder {
383 pub fn new() -> Self {
385 Self { page_size: 1024 }
386 }
387
388 pub async fn build_from_file(
390 self,
391 path: impl AsRef<Path>,
392 io: &dyn ResourceIo,
393 ) -> Result<Font, &'static str> {
394 Font::from_file(path, self.page_size, io).await
395 }
396
397 pub fn build_from_memory(self, data: impl Deref<Target = [u8]>) -> Result<Font, &'static str> {
399 Font::from_memory(data, self.page_size)
400 }
401}