Skip to main content

concinnity_core/components/
font.rs

1// Font glyph-atlas schema.
2
3use crate::ecs::PayloadLocator;
4use crate::ecs::asset_id::AssetId;
5use alloc::string::String;
6
7/// Rasterises a TrueType font into a glyph atlas at build time.
8///
9/// Reference a Font by name from a [TextLabel](#textlabel). Declaring one is
10/// optional: text naming no Font draws with the engine's built-in face at 24px,
11/// and compiles no atlas at all. Declare a Font to pick the face, or to pick the
12/// size the glyphs are rasterised at.
13///
14/// An empty `path` rasterises that same built-in face, which is how to get it at
15/// a different `size_px`.
16///
17/// ```rust
18/// # use concinnity_core::components::Font;
19/// Font {
20///     path: "assets/fonts/JetBrainsMono-Regular.ttf".into(),
21///     size_px: 20,
22///     ..Default::default()
23/// };
24/// ```
25#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
26#[serde(default)]
27pub struct Font {
28    /// Asset identity; injected via `inject_name`. Not part of `args`.
29    #[serde(skip)]
30    pub asset_id: AssetId,
31    /// Path to the TTF file, relative to the project root.
32    pub path: String,
33    /// Rasterisation size in pixels. Determines the rendered glyph height.
34    pub size_px: u32,
35    /// Filled by inject_locator after the build step packs the payload.
36    #[serde(skip)]
37    pub locator: Option<PayloadLocator>,
38}
39
40impl Default for Font {
41    fn default() -> Self {
42        Self {
43            asset_id: AssetId::default(),
44            path: String::new(),
45            size_px: 20,
46            locator: None,
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn a_blank_font_rasterises_at_a_readable_body_size() {
57        let f = Font::default();
58        assert!(f.path.is_empty());
59        assert_eq!(f.size_px, 20);
60        assert_eq!(f.asset_id, AssetId::default());
61        assert!(f.locator.is_none());
62    }
63
64    #[test]
65    fn an_authored_atlas_size_parses_and_round_trips_through_postcard() {
66        let f: Font = serde_json::from_str(r#"{"path":"fonts/body.ttf","size_px":48}"#).unwrap();
67        assert_eq!(f.path, "fonts/body.ttf");
68        assert_eq!(f.size_px, 48);
69
70        let bytes = postcard::to_allocvec(&f).unwrap();
71        let back: Font = postcard::from_bytes(&bytes).unwrap();
72        assert_eq!(back.path, "fonts/body.ttf");
73        assert_eq!(back.size_px, 48);
74        // The glyph atlas rides the blob, so the locator is injected at load.
75        assert!(back.locator.is_none());
76    }
77}