use std::collections::HashMap;
use ahash::RandomState;
use crossfont::{
Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph,
Rasterizer, Size, Slant, Style, Weight,
};
use log::{error, info};
use unicode_width::UnicodeWidthChar;
use crate::config::font::{Font, FontDescription};
use crate::config::ui_config::Delta;
use crate::gl::types::*;
use super::builtin_font;
pub trait LoadGlyph {
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
fn clear(&mut self);
}
#[derive(Copy, Clone, Debug)]
pub struct Glyph {
pub tex_id: GLuint,
pub multicolor: bool,
pub top: i16,
pub left: i16,
pub width: i16,
pub height: i16,
pub uv_bot: f32,
pub uv_left: f32,
pub uv_width: f32,
pub uv_height: f32,
}
pub struct GlyphCache {
cache: HashMap<GlyphKey, Glyph, RandomState>,
rasterizer: Rasterizer,
pub font_key: FontKey,
pub bold_key: FontKey,
pub italic_key: FontKey,
pub bold_italic_key: FontKey,
pub font_size: crossfont::Size,
font_offset: Delta<i8>,
glyph_offset: Delta<i8>,
metrics: Metrics,
builtin_box_drawing: bool,
}
impl GlyphCache {
pub fn new(mut rasterizer: Rasterizer, font: &Font) -> Result<GlyphCache, crossfont::Error> {
let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut rasterizer)?;
let metrics = GlyphCache::load_font_metrics(&mut rasterizer, font, regular)?;
Ok(Self {
cache: Default::default(),
rasterizer,
font_size: font.size(),
font_key: regular,
bold_key: bold,
italic_key: italic,
bold_italic_key: bold_italic,
font_offset: font.offset,
glyph_offset: font.glyph_offset,
metrics,
builtin_box_drawing: font.builtin_box_drawing,
})
}
fn load_font_metrics(
rasterizer: &mut Rasterizer,
font: &Font,
key: FontKey,
) -> Result<Metrics, crossfont::Error> {
rasterizer.get_glyph(GlyphKey { font_key: key, character: 'm', size: font.size() })?;
let mut metrics = rasterizer.metrics(key, font.size())?;
metrics.strikeout_position += font.glyph_offset.y as f32;
Ok(metrics)
}
fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
let size = self.font_size;
for i in 32u8..=126u8 {
self.get(GlyphKey { font_key: font, character: i as char, size }, loader, true);
}
}
fn compute_font_keys(
font: &Font,
rasterizer: &mut Rasterizer,
) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> {
let size = font.size();
let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal);
let regular = Self::load_regular_font(rasterizer, ®ular_desc, size)?;
let mut load_or_regular = |desc: FontDesc| {
if desc == regular_desc {
regular
} else {
rasterizer.load_font(&desc, size).unwrap_or(regular)
}
};
let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold);
let bold = load_or_regular(bold_desc);
let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal);
let italic = load_or_regular(italic_desc);
let bold_italic_desc = Self::make_desc(&font.bold_italic(), Slant::Italic, Weight::Bold);
let bold_italic = load_or_regular(bold_italic_desc);
Ok((regular, bold, italic, bold_italic))
}
fn load_regular_font(
rasterizer: &mut Rasterizer,
description: &FontDesc,
size: Size,
) -> Result<FontKey, crossfont::Error> {
match rasterizer.load_font(description, size) {
Ok(font) => Ok(font),
Err(err) => {
error!("{err}");
let fallback_desc =
Self::make_desc(Font::default().normal(), Slant::Normal, Weight::Normal);
rasterizer.load_font(&fallback_desc, size)
},
}
}
fn make_desc(desc: &FontDescription, slant: Slant, weight: Weight) -> FontDesc {
let style = if let Some(ref spec) = desc.style {
Style::Specific(spec.to_owned())
} else {
Style::Description { slant, weight }
};
FontDesc::new(desc.family.clone(), style)
}
pub fn get<L>(&mut self, glyph_key: GlyphKey, loader: &mut L, show_missing: bool) -> Glyph
where
L: LoadGlyph + ?Sized,
{
if let Some(glyph) = self.cache.get(&glyph_key) {
return *glyph;
};
let rasterized = self
.builtin_box_drawing
.then(|| {
builtin_font::builtin_glyph(
glyph_key.character,
&self.metrics,
&self.font_offset,
&self.glyph_offset,
)
})
.flatten()
.map_or_else(|| self.rasterizer.get_glyph(glyph_key), Ok);
let glyph = match rasterized {
Ok(rasterized) => self.load_glyph(loader, rasterized),
Err(RasterizerError::MissingGlyph(rasterized)) if show_missing => {
let missing_key = GlyphKey { character: '\0', ..glyph_key };
if let Some(glyph) = self.cache.get(&missing_key) {
*glyph
} else {
let glyph = self.load_glyph(loader, rasterized);
self.cache.insert(missing_key, glyph);
glyph
}
},
Err(_) => self.load_glyph(loader, Default::default()),
};
*self.cache.entry(glyph_key).or_insert(glyph)
}
pub fn load_glyph<L>(&self, loader: &mut L, mut glyph: RasterizedGlyph) -> Glyph
where
L: LoadGlyph + ?Sized,
{
glyph.left += i32::from(self.glyph_offset.x);
glyph.top += i32::from(self.glyph_offset.y);
glyph.top -= self.metrics.descent as i32;
if glyph.character.width() == Some(0) {
glyph.left += self.metrics.average_advance as i32;
}
loader.load_glyph(&glyph)
}
pub fn reset_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) {
loader.clear();
self.cache = Default::default();
self.load_common_glyphs(loader);
}
pub fn update_font_size(&mut self, font: &Font) -> Result<(), crossfont::Error> {
self.font_offset = font.offset;
self.glyph_offset = font.glyph_offset;
let (regular, bold, italic, bold_italic) =
Self::compute_font_keys(font, &mut self.rasterizer)?;
let metrics = GlyphCache::load_font_metrics(&mut self.rasterizer, font, regular)?;
info!("Font size changed to {:?} px", font.size().as_px());
self.font_size = font.size();
self.font_key = regular;
self.bold_key = bold;
self.italic_key = italic;
self.bold_italic_key = bold_italic;
self.metrics = metrics;
self.builtin_box_drawing = font.builtin_box_drawing;
Ok(())
}
pub fn font_metrics(&self) -> crossfont::Metrics {
self.metrics
}
pub fn load_common_glyphs<L: LoadGlyph>(&mut self, loader: &mut L) {
self.load_glyphs_for_font(self.font_key, loader);
self.load_glyphs_for_font(self.bold_key, loader);
self.load_glyphs_for_font(self.italic_key, loader);
self.load_glyphs_for_font(self.bold_italic_key, loader);
}
}