use std::{collections::HashSet, fmt::Debug};
use compact_str::CompactString;
use crate::Error;
#[doc(hidden)]
pub mod sealed {
pub trait Sealed {}
}
pub type SlotId = u16;
pub(crate) const GLYPH_SLOT_MASK: u32 = 0x1FFF;
pub trait Atlas: sealed::Sealed {
fn get_glyph_id(&mut self, key: &str, style_bits: u16) -> Option<u16>;
fn get_base_glyph_id(&mut self, key: &str) -> Option<u16>;
fn cell_size(&self) -> beamterm_data::CellSize;
fn bind(&self, gl: &glow::Context);
fn underline(&self) -> beamterm_data::LineDecoration;
fn strikethrough(&self) -> beamterm_data::LineDecoration;
fn get_symbol(&self, glyph_id: u16) -> Option<CompactString>;
fn get_ascii_char(&self, glyph_id: u16) -> Option<char>;
fn glyph_tracker(&self) -> &GlyphTracker;
fn glyph_count(&self) -> u32;
fn flush(&mut self, gl: &glow::Context) -> Result<(), Error>;
fn recreate_texture(&mut self, gl: &glow::Context) -> Result<(), Error>;
fn for_each_symbol(&self, f: &mut dyn FnMut(u16, &str));
fn resolve_glyph_slot(&mut self, key: &str, style_bits: u16) -> Option<GlyphSlot>;
fn emoji_bit(&self) -> u32;
fn delete(&self, gl: &glow::Context);
fn update_pixel_ratio(&mut self, gl: &glow::Context, pixel_ratio: f32) -> Result<f32, Error>;
fn cell_scale_for_dpr(&self, pixel_ratio: f32) -> f32;
fn texture_cell_size(&self) -> beamterm_data::CellSize;
}
pub struct FontAtlas {
inner: Box<dyn Atlas>,
}
impl<A: Atlas + 'static> From<A> for FontAtlas {
fn from(atlas: A) -> Self {
FontAtlas::new(atlas)
}
}
impl Debug for FontAtlas {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FontAtlas")
.finish_non_exhaustive()
}
}
impl FontAtlas {
pub fn new(inner: impl Atlas + 'static) -> Self {
Self { inner: Box::new(inner) }
}
pub fn get_glyph_id(&mut self, key: &str, style_bits: u16) -> Option<u16> {
self.inner.get_glyph_id(key, style_bits)
}
pub fn get_base_glyph_id(&mut self, key: &str) -> Option<u16> {
self.inner.get_base_glyph_id(key)
}
#[must_use]
pub fn cell_size(&self) -> beamterm_data::CellSize {
self.inner.cell_size()
}
pub fn bind(&self, gl: &glow::Context) {
self.inner.bind(gl);
}
#[must_use]
pub fn underline(&self) -> beamterm_data::LineDecoration {
self.inner.underline()
}
#[must_use]
pub fn strikethrough(&self) -> beamterm_data::LineDecoration {
self.inner.strikethrough()
}
#[must_use]
pub fn get_symbol(&self, glyph_id: u16) -> Option<CompactString> {
self.inner.get_symbol(glyph_id)
}
#[must_use]
pub fn get_ascii_char(&self, glyph_id: u16) -> Option<char> {
self.inner.get_ascii_char(glyph_id)
}
#[must_use]
pub fn glyph_tracker(&self) -> &GlyphTracker {
self.inner.glyph_tracker()
}
#[must_use]
pub fn glyph_count(&self) -> u32 {
self.inner.glyph_count()
}
pub fn recreate_texture(&mut self, gl: &glow::Context) -> Result<(), Error> {
self.inner.recreate_texture(gl)
}
pub fn for_each_symbol(&self, f: &mut dyn FnMut(u16, &str)) {
self.inner.for_each_symbol(f);
}
pub fn resolve_glyph_slot(&mut self, key: &str, style_bits: u16) -> Option<GlyphSlot> {
self.inner.resolve_glyph_slot(key, style_bits)
}
pub fn flush(&mut self, gl: &glow::Context) -> Result<(), Error> {
self.inner.flush(gl)
}
pub(crate) fn emoji_bit(&self) -> u32 {
self.inner.emoji_bit()
}
pub(crate) fn space_glyph_id(&mut self) -> u16 {
self.get_glyph_id(" ", 0x0)
.expect("space glyph exists in every font atlas")
}
pub fn delete(&self, gl: &glow::Context) {
self.inner.delete(gl);
}
pub fn update_pixel_ratio(
&mut self,
gl: &glow::Context,
pixel_ratio: f32,
) -> Result<f32, Error> {
self.inner.update_pixel_ratio(gl, pixel_ratio)
}
#[must_use]
pub fn cell_scale_for_dpr(&self, pixel_ratio: f32) -> f32 {
self.inner.cell_scale_for_dpr(pixel_ratio)
}
#[must_use]
pub fn texture_cell_size(&self) -> beamterm_data::CellSize {
self.inner.texture_cell_size()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum GlyphSlot {
Normal(SlotId),
Wide(SlotId),
Emoji(SlotId),
}
impl GlyphSlot {
#[must_use]
pub fn slot_id(&self) -> SlotId {
match *self {
GlyphSlot::Normal(id) | GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => id,
}
}
#[must_use]
pub fn with_styling(self, style_bits: u16) -> Self {
use GlyphSlot::*;
match self {
Normal(id) => Normal(id | style_bits),
Wide(id) => Wide(id | style_bits),
Emoji(id) => Emoji(id | style_bits),
}
}
#[must_use]
pub fn is_double_width(&self) -> bool {
matches!(self, GlyphSlot::Wide(_) | GlyphSlot::Emoji(_))
}
}
#[derive(Debug, Default)]
pub struct GlyphTracker {
missing: HashSet<CompactString>,
}
impl GlyphTracker {
#[must_use]
pub fn new() -> Self {
Self { missing: HashSet::new() }
}
pub fn record_missing(&mut self, glyph: &str) {
self.missing.insert(glyph.into());
}
#[must_use]
pub fn missing_glyphs(&self) -> HashSet<CompactString> {
self.missing.clone()
}
pub fn clear(&mut self) {
self.missing.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.missing.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.missing.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glyph_tracker() {
let mut tracker = GlyphTracker::new();
assert!(tracker.is_empty());
assert_eq!(tracker.len(), 0);
tracker.record_missing("\u{1F3AE}");
tracker.record_missing("\u{1F3AF}");
tracker.record_missing("\u{1F3AE}");
assert!(!tracker.is_empty());
assert_eq!(tracker.len(), 2);
let missing = tracker.missing_glyphs();
assert!(missing.contains(&CompactString::new("\u{1F3AE}")));
assert!(missing.contains(&CompactString::new("\u{1F3AF}")));
tracker.clear();
assert!(tracker.is_empty());
assert_eq!(tracker.len(), 0);
}
}