#![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;
pub trait FontProvider {
fn pixel_type(&self) -> PixelType;
fn single_glyph(&self, character: char) -> Glyph;
fn glyphs(&self, string: &str, glyphs: &mut Vec<Glyph>);
fn line_height(&self) -> f32;
fn metrics(&self, glyph: Glyph) -> Metrics;
fn rasterize(&self, glpyh: Glyph) -> Result<Vec<u8>, CacheError>;
fn kerning(&self, _a: Glyph, _b: Glyph) -> f32 {
0.0
}
}
pub trait Texture {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn put_rect(&mut self, pixel: PixelType, data: &[u8], gpu: &TextureGlyph);
}
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> {
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,
},
}
}
pub fn clear(&mut self) {
self.cache.clear();
}
pub fn render_glyph(&mut self, key: Glyph) -> Result<(Metrics, TextureGlyph), CacheError> {
self.cache.render_glyph(key)
}
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))
}
pub fn cache_string(&mut self, string: &str) -> Result<(), CacheError> {
self.render_string(string).map(|r| r.map(|_| ())).collect()
}
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))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Glyph(pub u32);
#[derive(Copy, Clone, Debug)]
pub struct TextureGlyph {
pub glyph: Glyph,
pub bounds: Bounds,
}
#[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,
}
#[derive(Copy, Clone, Debug)]
pub enum CacheError {
TextureTooSmall,
OutOfSpace,
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
}
}
pub enum PixelType {
Alpha,
RGBA,
}