tui-test-rs 0.1.0-beta.2

In-process terminal automation, inspection, assertions, and recording
Documentation
use std::collections::HashMap;

use tiny_skia::{Path, PathBuilder};
use ttf_parser::{Face, OutlineBuilder};

use super::super::{font as catalog, nerd_font};

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(super) struct GlyphKey {
    pub character: char,
    pub bold: bool,
    pub italic: bool,
}

pub(super) struct GlyphOutline {
    pub path: Path,
    pub bounds: ttf_parser::Rect,
    pub advance: u16,
    pub units_per_em: u16,
    pub synthetic_bold: bool,
    pub synthetic_italic: bool,
    pub powerline: bool,
}

pub(super) struct FontSystem {
    catalog: &'static catalog::Catalog,
    glyphs: HashMap<GlyphKey, Option<GlyphOutline>>,
}

impl FontSystem {
    pub fn new() -> Self {
        Self {
            catalog: catalog::catalog(),
            glyphs: HashMap::new(),
        }
    }

    pub fn resolve(&mut self, key: GlyphKey) -> Option<&GlyphOutline> {
        if !self.glyphs.contains_key(&key) {
            let glyph = self.load(key);
            self.glyphs.insert(key, glyph);
        }
        self.glyphs.get(&key).and_then(Option::as_ref)
    }

    fn load(&self, key: GlyphKey) -> Option<GlyphOutline> {
        for id in self.catalog.candidates(key.bold, key.italic, key.character) {
            let info = self.catalog.database.face(id)?;
            let synthetic_bold = key.bold && !is_bold_face(info);
            let synthetic_italic = key.italic && info.style == fontdb::Style::Normal;
            let glyph = self
                .catalog
                .database
                .with_face_data(id, |data, index| {
                    let face = Face::parse(data, index).ok()?;
                    let glyph_id = face.glyph_index(key.character)?;
                    let mut builder = TinyPathBuilder::default();
                    let bounds = face.outline_glyph(glyph_id, &mut builder)?;
                    let path = builder.finish()?;
                    Some(GlyphOutline {
                        path,
                        bounds,
                        advance: face
                            .glyph_hor_advance(glyph_id)
                            .unwrap_or(face.units_per_em()),
                        units_per_em: face.units_per_em(),
                        synthetic_bold,
                        synthetic_italic,
                        powerline: nerd_font::is_powerline_separator(key.character),
                    })
                })
                .flatten();
            if glyph.is_some() {
                return glyph;
            }
        }
        None
    }
}

fn is_bold_face(face: &fontdb::FaceInfo) -> bool {
    face.weight.0 >= fontdb::Weight::SEMIBOLD.0
        || face.post_script_name.to_ascii_lowercase().contains("bold")
}

#[derive(Default)]
struct TinyPathBuilder {
    inner: PathBuilder,
}

impl TinyPathBuilder {
    fn finish(self) -> Option<Path> {
        self.inner.finish()
    }
}

impl OutlineBuilder for TinyPathBuilder {
    fn move_to(&mut self, x: f32, y: f32) {
        self.inner.move_to(x, y);
    }

    fn line_to(&mut self, x: f32, y: f32) {
        self.inner.line_to(x, y);
    }

    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
        self.inner.quad_to(x1, y1, x, y);
    }

    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
        self.inner.cubic_to(x1, y1, x2, y2, x, y);
    }

    fn close(&mut self) {
        self.inner.close();
    }
}