323 lines
10 KiB
Rust
323 lines
10 KiB
Rust
use std::collections::HashMap;
|
|
use std::hash::BuildHasherDefault;
|
|
|
|
use crossfont::{
|
|
Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph,
|
|
Rasterizer, Size, Slant, Style, Weight,
|
|
};
|
|
use fnv::FnvHasher;
|
|
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;
|
|
|
|
/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
|
|
pub trait LoadGlyph {
|
|
/// Load the rasterized glyph into GPU memory.
|
|
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
|
|
|
|
/// Clear any state accumulated from previous loaded glyphs.
|
|
///
|
|
/// This can, for instance, be used to reset the texture Atlas.
|
|
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,
|
|
}
|
|
|
|
/// Naïve glyph cache.
|
|
///
|
|
/// Currently only keyed by `char`, and thus not possible to hold different
|
|
/// representations of the same code point.
|
|
pub struct GlyphCache {
|
|
/// Cache of buffered glyphs.
|
|
cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
|
|
|
|
/// Rasterizer for loading new glyphs.
|
|
rasterizer: Rasterizer,
|
|
|
|
/// Regular font.
|
|
pub font_key: FontKey,
|
|
|
|
/// Bold font.
|
|
pub bold_key: FontKey,
|
|
|
|
/// Italic font.
|
|
pub italic_key: FontKey,
|
|
|
|
/// Bold italic font.
|
|
pub bold_italic_key: FontKey,
|
|
|
|
/// Font size.
|
|
pub font_size: crossfont::Size,
|
|
|
|
/// Font offset.
|
|
font_offset: Delta<i8>,
|
|
|
|
/// Glyph offset.
|
|
glyph_offset: Delta<i8>,
|
|
|
|
/// Font metrics.
|
|
metrics: Metrics,
|
|
|
|
/// Whether to use the built-in font for box drawing characters.
|
|
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)?;
|
|
|
|
// Need to load at least one glyph for the face before calling metrics.
|
|
// The glyph requested here ('m' at the time of writing) has no special
|
|
// meaning.
|
|
rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
|
|
|
|
let metrics = rasterizer.metrics(regular, font.size())?;
|
|
|
|
Ok(Self {
|
|
cache: HashMap::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_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
|
|
let size = self.font_size;
|
|
|
|
// Cache all ascii characters.
|
|
for i in 32u8..=126u8 {
|
|
self.get(GlyphKey { font_key: font, character: i as char, size }, loader, true);
|
|
}
|
|
}
|
|
|
|
/// Computes font keys for (Regular, Bold, Italic, Bold Italic).
|
|
fn compute_font_keys(
|
|
font: &Font,
|
|
rasterizer: &mut Rasterizer,
|
|
) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> {
|
|
let size = font.size();
|
|
|
|
// Load regular font.
|
|
let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal);
|
|
|
|
let regular = Self::load_regular_font(rasterizer, ®ular_desc, size)?;
|
|
|
|
// Helper to load a description if it is not the `regular_desc`.
|
|
let mut load_or_regular = |desc: FontDesc| {
|
|
if desc == regular_desc {
|
|
regular
|
|
} else {
|
|
rasterizer.load_font(&desc, size).unwrap_or(regular)
|
|
}
|
|
};
|
|
|
|
// Load bold font.
|
|
let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold);
|
|
|
|
let bold = load_or_regular(bold_desc);
|
|
|
|
// Load italic font.
|
|
let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal);
|
|
|
|
let italic = load_or_regular(italic_desc);
|
|
|
|
// Load bold italic font.
|
|
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)
|
|
}
|
|
|
|
/// Get a glyph from the font.
|
|
///
|
|
/// If the glyph has never been loaded before, it will be rasterized and inserted into the
|
|
/// cache.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This will fail when the glyph could not be rasterized. Usually this is due to the glyph
|
|
/// not being present in any font.
|
|
pub fn get<L: ?Sized>(
|
|
&mut self,
|
|
glyph_key: GlyphKey,
|
|
loader: &mut L,
|
|
show_missing: bool,
|
|
) -> Glyph
|
|
where
|
|
L: LoadGlyph,
|
|
{
|
|
// Try to load glyph from cache.
|
|
if let Some(glyph) = self.cache.get(&glyph_key) {
|
|
return *glyph;
|
|
};
|
|
|
|
// Rasterize the glyph using the built-in font for special characters or the user's font
|
|
// for everything else.
|
|
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),
|
|
// Load fallback glyph.
|
|
Err(RasterizerError::MissingGlyph(rasterized)) if show_missing => {
|
|
// Use `\0` as "missing" glyph to cache it only once.
|
|
let missing_key = GlyphKey { character: '\0', ..glyph_key };
|
|
if let Some(glyph) = self.cache.get(&missing_key) {
|
|
*glyph
|
|
} else {
|
|
// If no missing glyph was loaded yet, insert it as `\0`.
|
|
let glyph = self.load_glyph(loader, rasterized);
|
|
self.cache.insert(missing_key, glyph);
|
|
|
|
glyph
|
|
}
|
|
},
|
|
Err(_) => self.load_glyph(loader, Default::default()),
|
|
};
|
|
|
|
// Cache rasterized glyph.
|
|
*self.cache.entry(glyph_key).or_insert(glyph)
|
|
}
|
|
|
|
/// Load glyph into the atlas.
|
|
///
|
|
/// This will apply all transforms defined for the glyph cache to the rasterized glyph before
|
|
pub fn load_glyph<L: ?Sized>(&self, loader: &mut L, mut glyph: RasterizedGlyph) -> Glyph
|
|
where
|
|
L: LoadGlyph,
|
|
{
|
|
glyph.left += i32::from(self.glyph_offset.x);
|
|
glyph.top += i32::from(self.glyph_offset.y);
|
|
glyph.top -= self.metrics.descent as i32;
|
|
|
|
// The metrics of zero-width characters are based on rendering
|
|
// the character after the current cell, with the anchor at the
|
|
// right side of the preceding character. Since we render the
|
|
// zero-width characters inside the preceding character, the
|
|
// anchor has been moved to the right by one cell.
|
|
if glyph.character.width() == Some(0) {
|
|
glyph.left += self.metrics.average_advance as i32;
|
|
}
|
|
|
|
// Add glyph to cache.
|
|
loader.load_glyph(&glyph)
|
|
}
|
|
|
|
/// Clear currently cached data in both GL and the registry.
|
|
pub fn clear_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) {
|
|
loader.clear();
|
|
self.cache = HashMap::default();
|
|
|
|
self.load_common_glyphs(loader);
|
|
}
|
|
|
|
pub fn update_font_size<L: LoadGlyph>(
|
|
&mut self,
|
|
font: &Font,
|
|
scale_factor: f64,
|
|
loader: &mut L,
|
|
) -> Result<(), crossfont::Error> {
|
|
// Update dpi scaling.
|
|
self.rasterizer.update_dpr(scale_factor as f32);
|
|
self.font_offset = font.offset;
|
|
|
|
// Recompute font keys.
|
|
let (regular, bold, italic, bold_italic) =
|
|
Self::compute_font_keys(font, &mut self.rasterizer)?;
|
|
|
|
self.rasterizer.get_glyph(GlyphKey {
|
|
font_key: regular,
|
|
character: 'm',
|
|
size: font.size(),
|
|
})?;
|
|
let metrics = self.rasterizer.metrics(regular, font.size())?;
|
|
|
|
info!("Font size changed to {:?} with scale factor of {}", font.size(), scale_factor);
|
|
|
|
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;
|
|
|
|
self.clear_glyph_cache(loader);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn font_metrics(&self) -> crossfont::Metrics {
|
|
self.metrics
|
|
}
|
|
|
|
/// Prefetch glyphs that are almost guaranteed to be loaded anyways.
|
|
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);
|
|
}
|
|
}
|