Skip to main content

elefont/
lib.rs

1//! A library that handles caching rendered glyphs on the GPU
2//!
3//! This fits as a layer in your rendering pipeline between font rasterization and shaping and text
4//! rendering. In other words, first you turn a string into a series of font glyphs. Each of those
5//! glyphs is looked up against the cache, and if it hasn't been rendered, it is turned into a
6//! bitmap and uploaded to the GPU. The string is then laid out and rendered by the client
7//! application.
8//!
9//! Scope of this library:
10//! - DO support various font libraries / types of fonts (TTFs, bitmap fonts)
11//! - DO support whatever backend (rendering to an image, GPU frameworks, etc.)
12//! - DON'T handle complex tasks like shaping. The font stack should handle that elsewhere, and
13//! provide this library the glyphs to render
14//! - DON'T handle layout. This can be taken care of by the client
15//! application when rendering.
16//!
17//! Support is available out-of-the-box for software rendering via `image`, rendering via
18//! `rusttype`, and performing automatic unicode normalization. All of these are optional features.
19
20#![cfg_attr(not(feature = "std"), no_std)]
21extern crate alloc;
22
23#[cfg(feature = "image")]
24mod image_impl;
25#[cfg(feature = "rusttype")]
26pub mod rusttype_provider;
27
28#[cfg(not(feature = "unicode-normalization"))]
29use alloc::borrow::ToOwned;
30use alloc::boxed::Box;
31#[cfg(feature = "unicode-normalization")]
32use alloc::string::String;
33use alloc::vec::Vec;
34use hashbrown::HashMap;
35
36/// Any object that can turn characters into glyphs and render them can be a FontProvider
37///
38/// FontProviders can be TTF font rasters, like rusttype (a pure-Rust library for decoding fonts) or
39/// fontkit (a library that delegates to system APIs to handle fonts). Other FontProviders could
40/// include bitmap fonts, or a combination of libraries (like a library to handle shaping and
41/// another library to handle rendering.)
42///
43/// It is assumed that a given font provider will operate at a fixed size. For a variable-sized
44/// source (like a TTF font), the font size can be paired with the font data to produce a single
45/// FontProvider.
46pub trait FontProvider {
47    /// The format of the data generated by the FontProvider
48    fn pixel_type(&self) -> PixelType;
49    /// Convert a single character into a Glyph
50    ///
51    /// Generally you should use [`glyphs`], but when rendering just one character this method can
52    /// be useful
53    fn single_glyph(&self, character: char) -> Glyph;
54    /// Convert the string into glyphs, and push the glyphs into the provided buffer
55    ///
56    /// This is not necessarily the same as running `single_glyph` over every character in the
57    /// string! Text is hard.
58    fn glyphs(&self, string: &str, glyphs: &mut Vec<Glyph>);
59    /// How much space to include between baselines of the given font
60    fn line_height(&self) -> f32;
61    /// Get the metrics of a character (how to space it, where to include it on a line, etc.)
62    fn metrics(&self, glyph: Glyph) -> Metrics;
63    /// Convert a character into image bytes, with the format determined by [`pixel_type`]
64    ///
65    /// [`pixel_type`]: FontProvider::pixel_type
66    fn rasterize(&self, glpyh: Glyph) -> Result<Vec<u8>, CacheError>;
67    /// Optionally expose extra kerning information for glyphs
68    ///
69    /// By default, this is always 0.0. Some font providers may add more information here,
70    /// however.
71    fn kerning(&self, _a: Glyph, _b: Glyph) -> f32 {
72        0.0
73    }
74}
75
76/// Any object that can take the data for glyphs and store it over time
77///
78/// Textures can be image buffers on the CPU (like ones provided by the image crate) or a buffer
79/// on the GPU, through any graphics library.
80pub trait Texture {
81    fn width(&self) -> u32;
82    fn height(&self) -> u32;
83    /// Write the data from a font into a texture
84    fn put_rect(&mut self, pixel: PixelType, data: &[u8], gpu: &TextureGlyph);
85}
86
87/// The main structure for maintaing a cache of rendered glyphs
88///
89/// `FontCache` is specifically an intermediary step. It doesn't understand how to read font files
90/// or how to break up a string into glyphs: that's handled by the [`FontProvider`]. It doesn't
91/// handle sending glyphs to the GPU: if you want to do that, provide a [`Texture`] that stores its
92/// data on the GPU. What it does do is keep track of which glyphs have already been rendered, where
93/// they were stored, and provide a consistent API over a variety of ways of rendering characters.
94pub struct FontCache<T: Texture> {
95    glyph_buffer: Vec<Glyph>,
96    cache: Cache<T>,
97}
98
99struct Cache<T: Texture> {
100    font: Box<dyn FontProvider>,
101    texture: T,
102    map: HashMap<Glyph, TextureGlyph>,
103    h_cursor: u32,
104    v_cursor: u32,
105    current_line_height: u32,
106}
107
108impl<T: Texture> FontCache<T> {
109    /// Create a new FontCache that pulls from the given provider and renders to the provided
110    /// texture
111    pub fn new(font: Box<dyn FontProvider>, texture: T) -> Self {
112        FontCache {
113            glyph_buffer: Vec::new(),
114            cache: Cache {
115                font,
116                texture,
117                map: HashMap::new(),
118                h_cursor: 0,
119                v_cursor: 0,
120                current_line_height: 0,
121            },
122        }
123    }
124
125    /// Forget the position of the characters in the texture, and re-set the cursor.
126    ///
127    /// This doesn't set any data in the Texture! Old glyphs may continue to work, but this is akin
128    /// to a use-after-free.
129    pub fn clear(&mut self) {
130        self.cache.clear();
131    }
132
133    /// Render a glyph to the texture
134    pub fn render_glyph(&mut self, key: Glyph) -> Result<(Metrics, TextureGlyph), CacheError> {
135        self.cache.render_glyph(key)
136    }
137
138    /// Attempt to convert a string into a series of glyphs or errors
139    ///
140    /// Before being converted, the string is normalized if the "unicode-normalilzation" feature is
141    /// activated, and whitespace characters are removed.
142    pub fn render_string<'a>(
143        &'a mut self,
144        string: &str,
145    ) -> impl 'a + Iterator<Item = Result<(Metrics, TextureGlyph), CacheError>> {
146        #[cfg(feature = "unicode-normalization")]
147        let mut string = {
148            use unicode_normalization::UnicodeNormalization;
149            string.nfc().collect::<String>()
150        };
151        #[cfg(not(feature = "unicode-normalization"))]
152        let mut string = string.to_owned();
153        string.retain(|c| !c.is_whitespace());
154        let glyph_buffer = &mut self.glyph_buffer;
155        let cache = &mut self.cache;
156        cache.font.glyphs(&string, glyph_buffer);
157        glyph_buffer
158            .drain(..)
159            .map(move |glyph| cache.render_glyph(glyph))
160    }
161
162    /// Cache a string or return an error if one occurred
163    ///
164    /// This can be useful if the entire domain of the possible glyphs is known beforehand (like a
165    /// bitmap font.) Under the hood, this just calls [`render_string`] and ignores the returned
166    /// glyphs.
167    pub fn cache_string(&mut self, string: &str) -> Result<(), CacheError> {
168        self.render_string(string).map(|r| r.map(|_| ())).collect()
169    }
170
171    /// Swap out the internal texture for another one
172    ///
173    /// This will clear the cache automatically, to avoid holding references to invalid areas of
174    /// the texture
175    pub fn replace_texture(&mut self, mut texture: T) -> T {
176        self.clear();
177        core::mem::swap(&mut self.cache.texture, &mut texture);
178
179        texture
180    }
181
182    pub fn texture(&self) -> &T {
183        &self.cache.texture
184    }
185
186    pub fn font(&self) -> &dyn FontProvider {
187        self.cache.font.as_ref()
188    }
189}
190
191impl<T: Texture> Cache<T> {
192    fn clear(&mut self) {
193        self.map.clear();
194        self.h_cursor = 0;
195        self.v_cursor = 0;
196        self.current_line_height = 0;
197    }
198
199    fn render_glyph(&mut self, glyph: Glyph) -> Result<(Metrics, TextureGlyph), CacheError> {
200        if let Some(tex_glyph) = self.map.get(&glyph) {
201            return Ok((self.font.metrics(glyph), *tex_glyph));
202        }
203        let metrics = self.font.metrics(glyph);
204        let bounds = metrics.bounds.unwrap();
205        if bounds.width > self.texture.width() || bounds.height > self.texture.height() {
206            return Err(CacheError::TextureTooSmall);
207        }
208        if bounds.width + self.h_cursor > self.texture.width() {
209            self.h_cursor = 0;
210            self.v_cursor += self.current_line_height + 1;
211            self.current_line_height = 0;
212        }
213        if bounds.height + self.v_cursor > self.texture.height() {
214            return Err(CacheError::OutOfSpace);
215        }
216        let pixel_type = self.font.pixel_type();
217        let data = self.font.rasterize(glyph)?;
218        let gpu = TextureGlyph {
219            glyph,
220            bounds: Bounds {
221                x: self.h_cursor as i32,
222                y: self.v_cursor as i32,
223                width: bounds.width,
224                height: bounds.height,
225            },
226        };
227        self.texture.put_rect(pixel_type, &data[..], &gpu);
228        self.h_cursor += gpu.bounds.width + 1;
229        self.current_line_height = self.current_line_height.max(gpu.bounds.height);
230        self.map.insert(glyph, gpu);
231
232        Ok((self.font.metrics(glyph), gpu))
233    }
234}
235
236/// The index of the font character to render
237///
238/// Glyphs are what actually gets rendered to the screen. It might be tempting to think of a glyph
239/// like a 'rendered character.' In specific scripts, this is often the case. 'A' and 'a' have
240/// distinct glyphs, and are unconditionally the same glyph. In others, this might not be true. See
241/// ['Text Rendering Hates You'](https://gankra.github.io/blah/text-hates-you) for more information
242/// on why text is complicated.
243#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
244pub struct Glyph(pub u32);
245
246/// The relevant information for a glyph stored on the texture
247#[derive(Copy, Clone, Debug)]
248pub struct TextureGlyph {
249    pub glyph: Glyph,
250    pub bounds: Bounds,
251}
252
253/// The layout information for a glyph
254#[non_exhaustive]
255#[derive(Clone, Debug)]
256pub struct Metrics {
257    pub bounds: Option<Bounds>,
258    pub bearing_x: f32,
259    pub advance_x: f32,
260    pub bearing_y: f32,
261    pub advance_y: f32,
262}
263
264#[derive(Copy, Clone, Debug)]
265pub struct Bounds {
266    pub x: i32,
267    pub y: i32,
268    pub width: u32,
269    pub height: u32,
270}
271
272/// An error generated during a cache operation
273#[derive(Copy, Clone, Debug)]
274pub enum CacheError {
275    /// No matter what, the texture is too small to render the glyph (even when empty)
276    ///
277    /// To fix this error, expand the texture. Make sure to clear the cache if the texture data is
278    /// also invalidated
279    TextureTooSmall,
280    /// The cache cannot store the current request without clearing it first
281    OutOfSpace,
282    /// A glyph was passed to a render method but it could not be rendered
283    ///
284    /// For example, unsized glyphs (glyphs with None for their [`bounds`]) cannot be rendered
285    ///
286    /// [`bounds`]: Metrics::bounds
287    NonRenderableGlyph(Glyph),
288}
289
290#[cfg(feature = "std")]
291use std::{error::Error, fmt};
292
293#[cfg(feature = "std")]
294impl fmt::Display for CacheError {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        match self {
297            CacheError::TextureTooSmall => {
298                write!(f, "The texture is too small to render the given input")
299            }
300            CacheError::OutOfSpace => write!(
301                f,
302                "The cache is out of space, and must be cleared before more rendering"
303            ),
304            CacheError::NonRenderableGlyph(glyph) => {
305                write!(f, "Attempted to render an un-renderable glyph: {:?}", glyph)
306            }
307        }
308    }
309}
310
311#[cfg(feature = "std")]
312impl Error for CacheError {
313    fn source(&self) -> Option<&(dyn Error + 'static)> {
314        None
315    }
316}
317
318/// How the pixels of the rasterized font are represented
319pub enum PixelType {
320    /// A series of values representing the alpha with no associated color
321    Alpha,
322    /// A series of complete color values
323    RGBA,
324}