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
//! A library that handles caching rendered glyphs on the GPU
//!
//! This fits as a layer in your rendering pipeline between font rasterization and shaping and text
//! rendering. In other words, first you turn a string into a series of font glyphs. Each of those
//! glyphs is looked up against the cache, and if it hasn't been rendered, it is turned into a
//! bitmap and uploaded to the GPU. The string is then laid out and rendered by the client
//! application.
//!
//! Scope of this library:
//! - DO support various font libraries / types of fonts (TTFs, bitmap fonts)
//! - DO support whatever backend (rendering to an image, GPU frameworks, etc.)
//! - DON'T handle complex tasks like shaping. The font stack should handle that elsewhere, and
//! provide this library the glyphs to render
//! - DON'T handle layout. This can be taken care of by the client
//! application when rendering.
//!
//! Support is available out-of-the-box for software rendering via `image`, rendering via
//! `rusttype`, and performing automatic unicode normalization. All of these are optional features.

#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;

#[cfg(feature = "image")]
mod image_impl;
#[cfg(feature = "rusttype")]
pub mod rusttype_provider;

#[cfg(not(feature = "unicode-normalization"))]
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
#[cfg(feature = "unicode-normalization")]
use alloc::string::String;
use alloc::vec::Vec;
use hashbrown::HashMap;

/// Any object that can turn characters into glyphs and render them can be a FontProvider
///
/// FontProviders can be TTF font rasters, like rusttype (a pure-Rust library for decoding fonts) or
/// fontkit (a library that delegates to system APIs to handle fonts). Other FontProviders could
/// include bitmap fonts, or a combination of libraries (like a library to handle shaping and
/// another library to handle rendering.)
///
/// It is assumed that a given font provider will operate at a fixed size. For a variable-sized
/// source (like a TTF font), the font size can be paired with the font data to produce a single
/// FontProvider.
pub trait FontProvider {
    /// The format of the data generated by the FontProvider
    fn pixel_type(&self) -> PixelType;
    /// Convert a single character into a Glyph
    ///
    /// Generally you should use [`glyphs`], but when rendering just one character this method can
    /// be useful
    fn single_glyph(&self, character: char) -> Glyph;
    /// Convert the string into glyphs, and push the glyphs into the provided buffer
    ///
    /// This is not necessarily the same as running `single_glyph` over every character in the
    /// string! Text is hard.
    fn glyphs(&self, string: &str, glyphs: &mut Vec<Glyph>);
    /// How much space to include between baselines of the given font
    fn line_height(&self) -> f32;
    /// Get the metrics of a character (how to space it, where to include it on a line, etc.)
    fn metrics(&self, glyph: Glyph) -> Metrics;
    /// Convert a character into image bytes, with the format determined by [`pixel_type`]
    ///
    /// [`pixel_type`]: FontProvider::pixel_type
    fn rasterize(&self, glpyh: Glyph) -> Result<Vec<u8>, CacheError>;
    /// Optionally expose extra kerning information for glyphs
    ///
    /// By default, this is always 0.0. Some font providers may add more information here,
    /// however.
    fn kerning(&self, _a: Glyph, _b: Glyph) -> f32 {
        0.0
    }
}

/// Any object that can take the data for glyphs and store it over time
///
/// Textures can be image buffers on the CPU (like ones provided by the image crate) or a buffer
/// on the GPU, through any graphics library.
pub trait Texture {
    fn width(&self) -> u32;
    fn height(&self) -> u32;
    /// Write the data from a font into a texture
    fn put_rect(&mut self, pixel: PixelType, data: &[u8], gpu: &TextureGlyph);
}

/// The main structure for maintaing a cache of rendered glyphs
///
/// `FontCache` is specifically an intermediary step. It doesn't understand how to read font files
/// or how to break up a string into glyphs: that's handled by the [`FontProvider`]. It doesn't
/// handle sending glyphs to the GPU: if you want to do that, provide a [`Texture`] that stores its
/// data on the GPU. What it does do is keep track of which glyphs have already been rendered, where
/// they were stored, and provide a consistent API over a variety of ways of rendering characters.
pub struct FontCache<T: Texture> {
    glyph_buffer: Vec<Glyph>,
    cache: Cache<T>,
}

struct Cache<T: Texture> {
    font: Box<dyn FontProvider>,
    texture: T,
    map: HashMap<Glyph, TextureGlyph>,
    h_cursor: u32,
    v_cursor: u32,
    current_line_height: u32,
}

impl<T: Texture> FontCache<T> {
    /// Create a new FontCache that pulls from the given provider and renders to the provided
    /// texture
    pub fn new(font: Box<dyn FontProvider>, texture: T) -> Self {
        FontCache {
            glyph_buffer: Vec::new(),
            cache: Cache {
                font,
                texture,
                map: HashMap::new(),
                h_cursor: 0,
                v_cursor: 0,
                current_line_height: 0,
            },
        }
    }

    /// Forget the position of the characters in the texture, and re-set the cursor.
    ///
    /// This doesn't set any data in the Texture! Old glyphs may continue to work, but this is akin
    /// to a use-after-free.
    pub fn clear(&mut self) {
        self.cache.clear();
    }

    /// Render a glyph to the texture
    pub fn render_glyph(&mut self, key: Glyph) -> Result<(Metrics, TextureGlyph), CacheError> {
        self.cache.render_glyph(key)
    }

    /// Attempt to convert a string into a series of glyphs or errors
    ///
    /// Before being converted, the string is normalized if the "unicode-normalilzation" feature is
    /// activated, and whitespace characters are removed.
    pub fn render_string<'a>(
        &'a mut self,
        string: &str,
    ) -> impl 'a + Iterator<Item = Result<(Metrics, TextureGlyph), CacheError>> {
        #[cfg(feature = "unicode-normalization")]
        let mut string = {
            use unicode_normalization::UnicodeNormalization;
            string.nfc().collect::<String>()
        };
        #[cfg(not(feature = "unicode-normalization"))]
        let mut string = string.to_owned();
        string.retain(|c| !c.is_whitespace());
        let glyph_buffer = &mut self.glyph_buffer;
        let cache = &mut self.cache;
        cache.font.glyphs(&string, glyph_buffer);
        glyph_buffer
            .drain(..)
            .map(move |glyph| cache.render_glyph(glyph))
    }

    /// Cache a string or return an error if one occurred
    ///
    /// This can be useful if the entire domain of the possible glyphs is known beforehand (like a
    /// bitmap font.) Under the hood, this just calls [`render_string`] and ignores the returned
    /// glyphs.
    pub fn cache_string(&mut self, string: &str) -> Result<(), CacheError> {
        self.render_string(string).map(|r| r.map(|_| ())).collect()
    }

    /// Swap out the internal texture for another one
    ///
    /// This will clear the cache automatically, to avoid holding references to invalid areas of
    /// the texture
    pub fn replace_texture(&mut self, mut texture: T) -> T {
        self.clear();
        core::mem::swap(&mut self.cache.texture, &mut texture);

        texture
    }

    pub fn texture(&self) -> &T {
        &self.cache.texture
    }

    pub fn font(&self) -> &dyn FontProvider {
        self.cache.font.as_ref()
    }
}

impl<T: Texture> Cache<T> {
    fn clear(&mut self) {
        self.map.clear();
        self.h_cursor = 0;
        self.v_cursor = 0;
        self.current_line_height = 0;
    }

    fn render_glyph(&mut self, glyph: Glyph) -> Result<(Metrics, TextureGlyph), CacheError> {
        if let Some(tex_glyph) = self.map.get(&glyph) {
            return Ok((self.font.metrics(glyph), *tex_glyph));
        }
        let metrics = self.font.metrics(glyph);
        let bounds = metrics.bounds.unwrap();
        if bounds.width > self.texture.width() || bounds.height > self.texture.height() {
            return Err(CacheError::TextureTooSmall);
        }
        if bounds.width + self.h_cursor > self.texture.width() {
            self.h_cursor = 0;
            self.v_cursor += self.current_line_height + 1;
            self.current_line_height = 0;
        }
        if bounds.height + self.v_cursor > self.texture.height() {
            return Err(CacheError::OutOfSpace);
        }
        let pixel_type = self.font.pixel_type();
        let data = self.font.rasterize(glyph)?;
        let gpu = TextureGlyph {
            glyph,
            bounds: Bounds {
                x: self.h_cursor as i32,
                y: self.v_cursor as i32,
                width: bounds.width,
                height: bounds.height,
            },
        };
        self.texture.put_rect(pixel_type, &data[..], &gpu);
        self.h_cursor += gpu.bounds.width + 1;
        self.current_line_height = self.current_line_height.max(gpu.bounds.height);
        self.map.insert(glyph, gpu);

        Ok((self.font.metrics(glyph), gpu))
    }
}

/// The index of the font character to render
///
/// Glyphs are what actually gets rendered to the screen. It might be tempting to think of a glyph
/// like a 'rendered character.' In specific scripts, this is often the case. 'A' and 'a' have
/// distinct glyphs, and are unconditionally the same glyph. In others, this might not be true. See
/// ['Text Rendering Hates You'](https://gankra.github.io/blah/text-hates-you) for more information
/// on why text is complicated.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Glyph(pub u32);

/// The relevant information for a glyph stored on the texture
#[derive(Copy, Clone, Debug)]
pub struct TextureGlyph {
    pub glyph: Glyph,
    pub bounds: Bounds,
}

/// The layout information for a glyph
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Metrics {
    pub bounds: Option<Bounds>,
    pub bearing_x: f32,
    pub advance_x: f32,
    pub bearing_y: f32,
    pub advance_y: f32,
}

#[derive(Copy, Clone, Debug)]
pub struct Bounds {
    pub x: i32,
    pub y: i32,
    pub width: u32,
    pub height: u32,
}

/// An error generated during a cache operation
#[derive(Copy, Clone, Debug)]
pub enum CacheError {
    /// No matter what, the texture is too small to render the glyph (even when empty)
    ///
    /// To fix this error, expand the texture. Make sure to clear the cache if the texture data is
    /// also invalidated
    TextureTooSmall,
    /// The cache cannot store the current request without clearing it first
    OutOfSpace,
    /// A glyph was passed to a render method but it could not be rendered
    ///
    /// For example, unsized glyphs (glyphs with None for their [`bounds`]) cannot be rendered
    ///
    /// [`bounds`]: Metrics::bounds
    NonRenderableGlyph(Glyph),
}

#[cfg(feature = "std")]
use std::{error::Error, fmt};

#[cfg(feature = "std")]
impl fmt::Display for CacheError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CacheError::TextureTooSmall => {
                write!(f, "The texture is too small to render the given input")
            }
            CacheError::OutOfSpace => write!(
                f,
                "The cache is out of space, and must be cleared before more rendering"
            ),
            CacheError::NonRenderableGlyph(glyph) => {
                write!(f, "Attempted to render an un-renderable glyph: {:?}", glyph)
            }
        }
    }
}

#[cfg(feature = "std")]
impl Error for CacheError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
}

/// How the pixels of the rasterized font are represented
pub enum PixelType {
    /// A series of values representing the alpha with no associated color
    Alpha,
    /// A series of complete color values
    RGBA,
}