raster_font 0.1.1

A format for authoring and using image-backed fonts
Documentation
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
//! Backend-agnostic abstractions for raster font data.
//!
//! A [`RasterFont`] stores glyph bindings and backend-specific resources, but
//! does not know how to draw anything by itself. The split exists so that the same
//! font data can be used with different rendering backends.
//!
//! # Defining a backend
//!
//! To define a backend, implement the following traits:
//!
//! - [`Backend`] — a zero-sized tag struct that names the associated atlas,
//!   image, and resource types for your backend.
//! - [`BackendBuilder`] — converts a [`RawFont`] (the packed glyph sheet
//!   produced by [`FontAtlasBuilder`]) into [`Backend::Resources`].
//! - [`SpriteSheet`] — resolves an [`AtlasIndex`] to [`UTokenProps`]
//!   (unsigned pixel region and signed draw offset).
//!
#![cfg_attr(
    feature = "bevy",
    doc = "See the [Bevy backend](crate::backend::bevy_backend) for a concrete example."
)]
#![cfg_attr(
    not(feature = "bevy"),
    doc = "See the [Bevy backend](https://github.com/cuppachino/pixel_perfect/tree/raster_font-v0.1.0/crates/raster_font/src/bevy_backend) for an example implementation."
)]
//!
//! # Resolving glyphs
//!
//! [`RasterFont`] implements [`AsRef<LigatureTree<AtlasIndex>>`] and
//! [`MapLigature`], which together provide access to the [`InputResolver`] api.
//! That gives you `find`, `valid`, and gang directly on any font where
//! `B::Resources: SpriteSheet`.
//!
//! When the backend's resources live outside the font (e.g. in a Bevy `Assets` collection), call
//! [`RasterFont::upgrade`] with a [`FontResourceProvider`] to obtain a [`RasterFontCtx`] with
//! borrowed access to the resources. The context can be used as an `InputResolver` just like an
//! owned font.
//!
//! Which pattern to use depends entirely on whether your backend owns its resources or borrows
//! them from an external store. Both paths produce identical glyph resolution behaviour.
//!
//! # Feature flags
//!
//!| Feature             | Effect                                                                                                     |
//!| :-----------------: | :--------------------------------------------------------------------------------------------------------- |
//!| `font_sequence_map` | Adds a `HashMap` alongside the ligature tree, enabling O(1) single-sequence lookup with `RasterFont::get`. |
//!
//! [`FontAtlasBuilder`]: crate::builder::FontAtlasBuilder
//! [`InputResolver`]: crate::tree::InputResolver
//! [`UTokenProps`]: crate::builder::UTokenProps

#[cfg(feature = "bevy")]
use bevy_reflect::prelude::*;

use std::{error::Error, fmt::Debug, marker::PhantomData};

use crate::{
    builder::RawFont,
    collections::HashMap,
    core::{AtlasIndex, Sequence},
    tree::{BuildError as LigatureBindingError, LigatureTree, MapLigature},
};

#[cfg(feature = "bevy")]
pub mod bevy_backend;

/// Prelude module for backend implementors.
///
/// Re-exports all traits and types required to implement a rendering backend. Prefer importing
/// from here rather than from individual sub-modules so that your backend implementation is
/// insulated from internal reorganizations.
pub mod prelude {
    pub use super::{Backend, BackendBuilder, FontResourceProvider, RasterFont, SpriteSheet};
    pub use crate::{
        builder::{RawFont, UTokenProps},
        core::{AtlasIndex, IGlyphOffset},
        tree::{AsLigatureTree, MapLigature},
    };
}

/// Marker trait that associates platform-specific types with a rendering backend.
///
/// Implement this trait on a zero-sized tag struct to define the type family for your backend.
///
#[cfg_attr(
    feature = "bevy",
    doc = "See the [Bevy backend](crate::backend::bevy_backend) for an example implementation."
)]
#[cfg_attr(
    not(feature = "bevy"),
    doc = "See the [Bevy backend](https://github.com/cuppachino/pixel_perfect/tree/raster_font-v0.1.0/crates/raster_font/src/bevy_backend) for an example implementation."
)]
pub trait Backend {
    /// Backend-specific render resources stored inside every [`RasterFont`] for this backend.
    ///
    /// Fonts that own their resources can implement [`SpriteSheet`] directly on this
    /// type.
    ///
    /// Fonts that borrow resources can use it as input for a [`FontResourceProvider`] to produce
    /// a separate resources view type implementing [`SpriteSheet`].
    type Resources;
}

pub trait BackendBuilder {
    /// The backend this builder targets.
    type Backend: Backend;
    /// Error returned if resource construction fails.
    type Error;
    /// Raw image type expected in the [`RawFont`] passed to [`build_resources`](Self::build_resources).
    type Sheet;

    /// Convert raw font data into backend-specific render resources.
    ///
    /// Called automatically by [`FontAtlasBuilder::build`](crate::builder::FontAtlasBuilder::build)
    /// at the end of the builder pipeline. The [`RawFont`] contains the packed glyph sheet image
    /// and [`UTokenProps`] collection produced by the builder. This function must convert those
    /// into the backend's resource format.
    ///
    /// # Errors
    ///
    /// Returns `Err(Self::Error)` if backend-specific resource creation fails. The error surfaces as
    /// [`FontBuilderError::BackendBuilderError`](crate::builder::errors::FontBuilderError::BackendBuilderError)
    /// in the public API of `FontAtlasBuilder::build`.
    ///
    /// [`UTokenProps`]: crate::builder::UTokenProps
    fn build_resources(
        self,
        raw_font: RawFont<Self::Sheet>,
    ) -> Result<<Self::Backend as Backend>::Resources, Self::Error>;
}

/// Provides glyph pixel data (region + render offset) for an [`AtlasIndex`].
pub trait SpriteSheet {
    /// Commonly [`UTokenProps`] (UV rect + draw offset).
    ///
    /// [`UTokenProps`]: crate::builder::UTokenProps
    type Props;

    /// Resolves an [`AtlasIndex`] to its [`SpriteSheet::Props`]. Common implementations typically
    /// return [`UTokenProps`] (UV rect + draw offset).
    ///
    /// Returns `None` if the index is out of bounds.
    ///
    /// [`UTokenProps`]: crate::builder::UTokenProps
    fn props(&self, index: &AtlasIndex) -> Self::Props;
}

/// Borrows backend-specific render resources for a [`RasterFont`] from an external context.
///
/// Some backends (e.g. Bevy) store assets in a world-level\* resource map rather than inline in
/// the font struct. `FontResourceProvider` lets such backends *upgrade* a [`RasterFont`] into
/// a [`RasterFontCtx`] that can perform full glyph resolution, without embedding the world
/// reference permanently inside the font.
///
/// # Lifetimes
///
/// - `'f` — lifetime of the font.
/// - `'r` — lifetime of borrowed resources returned by [`upgrade_font`](Self::upgrade_font).
///
/// # Associated types
/// - **[`Backend`](Self::Backend)**: the backend this provider is compatible with.
///   This must match the backend of the font being upgraded.
///
/// - **[`Output`](Self::Output)**: the type of the resolved resources view returned by
///   [`upgrade_font`](Self::upgrade_font). This is typically a wrapper around borrowed
///   backend assets (e.g. a Bevy `TextureAtlas`), and must implement [`SpriteSheet`] to be
///   usable for glyph resolution in a [`RasterFontCtx`].
pub trait FontResourceProvider {
    type Backend: Backend;
    type Error: Error;
    type Output<'f, 'r>: SpriteSheet
    where
        Self: 'r;

    /// Prepare a borrowed resources view for the from a font's held resources, returning `None` if
    /// preparation fails
    fn upgrade_font<'f, 'r>(
        &'r self,
        res_in: &'f <Self::Backend as Backend>::Resources,
    ) -> Result<Self::Output<'f, 'r>, Self::Error>;
}

/// # Raster Font
///
/// A fully built raster font, ready for glyph resolution and rendering.
///
/// # Input resolution
///
/// Depending on the backend's resource management strategy, a `RasterFont` may not hold all the
/// data it needs to resolve glyphs on its own.
///
/// ## Owned resources
///
/// Raster fonts with owned resources (i.e. `RasterFont<B::Resources: SpriteSheet>`) can
/// use the [`InputResolver`] API to map input sequences to glyphs without any additional setup.
///
/// ```rust
/// use raster_font::{backend::prelude::*, tree::InputResolver};
///
/// fn resolve_owned_resources<B: Backend<Resources: SpriteSheet>>(font: &RasterFont<B>) {
///     // Iterates leftmost-longest glyph matches, skipping unrecognized characters.
///     let _glyphs = font.valid("Hello :)").collect::<Vec<_>>();
/// }
/// ```
///
/// ## Borrowed resources
///
/// Backends with external resources must use [`upgrade`](Self::upgrade) to obtain a
/// [`RasterFontCtx`] with borrowed resources instead. The context functions the same as an owned
/// font for input resolution, but requires a [`FontResourceProvider`] to construct.
///
/// ```rust
/// use raster_font::{backend::prelude::*, tree::InputResolver};
///
/// fn resolve_borrowed_resources<B: Backend, P>(font: &RasterFont<B>, provider: &P)
/// where
///     P: for<'f, 'r> FontResourceProvider<Backend = B>,
/// {
///     let _glyphs = font
///         .upgrade(provider)
///         .unwrap()
///         .valid("Hello :)")
///         .collect::<Vec<_>>();
/// }
/// ```
///
/// # Feature flags
///
///| Feature               | Effect                                                                     |
///| :-------------------: | :-----                                                                     |
///| `bevy`                | Enables `bevy_asset` integrations.                                         |
///| `font_sequence_map`   | Stores a `HashMap` for O(1) single-sequence lookups with `get`.            |
///
/// [`InputResolver`]: crate::tree::InputResolver
#[derive(Clone, Debug)]
#[cfg_attr(
    feature = "bevy",
    derive(Reflect),
    reflect(from_reflect = false),
    reflect(where B: Clone + std::fmt::Debug, <B as Backend>::Resources: Clone + std::fmt::Debug),
    reflect(Clone, Debug),
)]
pub struct RasterFont<B: Backend> {
    /// A human-readable name for the font, if provided in the source metadata.
    pub name: Option<String>,
    /// Marker for the backend this font is built for.
    _backend: PhantomData<B>,
    /// Backend-specific resources needed to render this font.
    pub resources: B::Resources,
    /// The line height for this font, derived from the tallest glyph in the atlas.
    #[allow(
        dead_code,
        reason = "conditional compilation makes this appear unused in some contexts"
    )]
    pub(crate) height: u32,
    /// Aho-Corasick automaton for resolving ligatures and multi-character sequences
    /// to their corresponding glyphs. This is the primary resolution path.
    #[cfg_attr(feature = "bevy", reflect(ignore))]
    pub(crate) tree: LigatureTree<AtlasIndex>,

    /// ## Requires `font_sequence_map` feature.
    ///
    /// Enables O(1) access by sequence key at the cost of duplicating the binding data already
    /// held by the ligature tree.
    #[cfg(feature = "font_sequence_map")]
    pub(crate) sequence_map: HashMap<Sequence, AtlasIndex>,
}

/// `AsRef` and [`MapLigature`] together satisfy the [`InputResolver`] blanket impl for RasterFont
/// when B::Resources: SpriteSheet, giving `find`, `valid`, etc. without any extra boilerplate.
///
/// [`InputResolver`]: crate::tree::InputResolver
impl<B: Backend> AsRef<LigatureTree<AtlasIndex>> for RasterFont<B> {
    #[inline]
    fn as_ref(&self) -> &LigatureTree<AtlasIndex> {
        &self.tree
    }
}

/// [`AsRef<LigatureTree<AtlasIndex>>`] and `MapLigature` together satisfy the [`InputResolver`]
/// blanket impl for RasterFont when B::Resources: SpriteSheet, giving `find`, `valid`, etc. without
/// any extra boilerplate.
///
/// [`InputResolver`]: crate::tree::InputResolver
impl<B: Backend<Resources: SpriteSheet>> MapLigature<AtlasIndex> for RasterFont<B> {
    type Output = <B::Resources as SpriteSheet>::Props;

    fn map_ligature(&self, index: &AtlasIndex) -> Self::Output {
        self.resources.props(index)
    }
}

impl<B: Backend> RasterFont<B> {
    /// Construct a new `RasterFont` from its component parts.
    ///
    /// Builds the internal [`LigatureTree`] from `sequence_map` and returns an error if the
    /// Aho-Corasick automaton cannot be compiled (e.g. due to an excessively large pattern set).
    ///
    /// In practice this is called by [`FontAtlasBuilder::build`]
    /// rather than directly by user code.
    ///
    /// # Errors
    ///
    /// Returns [`LigatureBindingError`] if the Aho-Corasick automaton fails to compile.
    ///
    /// [`FontAtlasBuilder::build`]: crate::builder::FontAtlasBuilder::build
    pub fn new(
        name: Option<String>,
        height: u32,
        resources: B::Resources,
        sequence_map: HashMap<Sequence, AtlasIndex>,
    ) -> Result<Self, LigatureBindingError> {
        #[cfg(feature = "font_sequence_map")]
        let tree = LigatureTree::try_from_bindings(sequence_map.clone())?;
        #[cfg(not(feature = "font_sequence_map"))]
        let tree = LigatureTree::try_from_bindings(sequence_map)?;

        Ok(Self {
            name,
            _backend: PhantomData,
            height,
            resources,
            #[cfg(feature = "font_sequence_map")]
            sequence_map,
            tree,
        })
    }

    /// Returns a reference to the internal [`LigatureTree`].
    ///
    /// See: [`InputResolver`](crate::tree::InputResolver)
    #[inline]
    pub fn tree(&self) -> &LigatureTree<AtlasIndex> {
        &self.tree
    }

    /// Returns the atlas index for a [`Sequence`] or `None` if the sequence is not found.
    ///
    /// Requires the `font_sequence_map` feature. This provides O(1) access to glyphs by sequence
    /// key, but requires additional memory to store the sequence map alongside the ligature tree.
    #[cfg(feature = "font_sequence_map")]
    #[inline]
    pub fn get(&self, sequence: &Sequence) -> Option<&AtlasIndex> {
        self.sequence_map.get(sequence)
    }
}

impl<B: Backend> RasterFont<B> {
    /// Upgrade this font with externally-held render resources, producing a [`RasterFontCtx`].
    ///
    /// Use this when the backend stores resources outside the font struct (e.g. in a Bevy
    /// `Assets<Image>` collection). For backends that store resources directly on the font, this
    /// is unnecessary because the font can resolve glyphs through the `InputResolver` API.
    ///
    /// Returns `Ctx::Error` if the resource provider cannot prepare the font's resources
    /// (e.g. an asset is unloaded, or there was a mismatch between the font and provider resources).
    ///
    /// # Example
    ///
    /// ```
    /// # use raster_font::{backend::prelude::*, tree::InputResolver};
    /// #
    /// fn render<B: Backend>(
    ///     font: &RasterFont<B>,
    ///     provider: &impl FontResourceProvider<Backend = B>,
    /// ) {
    ///     if let Ok(font_ctx) = font.upgrade(provider) {
    ///         // font_ctx implements InputResolver
    ///         font_ctx.valid("Hello world!").for_each(|glyph| {
    ///             // render glyph
    ///         });
    ///     }
    /// }
    /// ```
    pub fn upgrade<'f, 'r, Ctx: FontResourceProvider<Backend = B>>(
        &'f self,
        ctx: &'r Ctx,
    ) -> Result<RasterFontCtx<'f, 'r, Ctx>, Ctx::Error> {
        let resources = ctx.upgrade_font(&self.resources)?;
        Ok(RasterFontCtx {
            font: self,
            resources,
        })
    }
}

/// A [`RasterFont`] paired with externally-borrowed render resources.
///
/// Created by [`RasterFont::upgrade`]. Holds a shared reference to the font and a
/// [`FontResourceProvider::Output`] resolved from an external context (e.g. a Bevy world).
///
/// Because it implements both [`AsRef<LigatureTree<AtlasIndex>>`] and [`MapLigature`], the context
/// has full access to the [`InputResolver`](crate::tree::InputResolver) API.
///
/// # Lifetimes
///
/// - `'f` — lifetime of the borrowed [`RasterFont`].
/// - `'r` — lifetime of the borrowed backend resources.
pub struct RasterFontCtx<'f, 'r, Ctx: FontResourceProvider + 'r> {
    font: &'f RasterFont<Ctx::Backend>,
    resources: Ctx::Output<'f, 'r>,
}

impl<'f, 'r, Ctx: FontResourceProvider> AsRef<LigatureTree<AtlasIndex>>
    for RasterFontCtx<'f, 'r, Ctx>
{
    #[inline]
    fn as_ref(&self) -> &LigatureTree<AtlasIndex> {
        &self.font.tree
    }
}

impl<'f, 'r, Ctx: FontResourceProvider> MapLigature<AtlasIndex> for RasterFontCtx<'f, 'r, Ctx> {
    type Output = <<Ctx as FontResourceProvider>::Output<'f, 'r> as SpriteSheet>::Props;

    fn map_ligature(&self, index: &AtlasIndex) -> Self::Output {
        self.resources.props(index)
    }
}