// Copyright (c) 2022 Marceline Cramer // SPDX-License-Identifier: LGPL-3.0-or-later use super::{Color, DrawCommand, MeshIndex, MeshVertex, Rect, Vec2}; use allsorts::binary::read::ReadScope; use allsorts::font::MatchingPresentation; use allsorts::font_data::{DynamicFontTableProvider, FontData as AllsortsFontData}; use allsorts::glyph_position::{GlyphLayout, TextDirection}; use allsorts::{tag, Font as AllsortsFont}; use ouroboros::self_referencing; use parking_lot::Mutex; use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; pub mod glyph; use glyph::GlyphCache; #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct GlyphPosition { pub index: u16, pub hori_advance: i32, pub vert_advance: i32, pub xoff: i32, pub yoff: i32, } pub struct TextLayout { pub font_id: u32, pub bounds: Rect, pub glyphs: Vec, } pub struct Font { font_id: u32, data: Mutex, glyph_cache: GlyphCache, } impl Font { pub fn load( font_id: u32, script: u32, direction: TextDirection, vertical: bool, file_buffer: Vec, ) -> Self { let mut data = FontData::load(script, direction, vertical, file_buffer); let glyph_cache = data.with_inner_mut(|font| GlyphCache::new(font)); let data = Mutex::new(data); Self { font_id, data, glyph_cache, } } pub fn shape(&self, text: &str) -> TextLayout { let positions = self.data.lock().shape(text); let glyphs = self.glyph_cache.glyphs.read(); let mut bounds = Rect::NEG_INFINITY; let units_per_em = self.glyph_cache.units_per_em; let mut xcur = 0; let mut ycur = 0; for position in positions.iter() { let xpos = xcur + position.xoff; let ypos = ycur + position.yoff; xcur += position.hori_advance; ycur += position.vert_advance; let pos = Vec2::new(xpos as f32, ypos as f32) / units_per_em; let bb = glyphs.get(position.index as usize).unwrap().bounding_box; bounds = bounds.union(&bb.offset(pos)); } TextLayout { font_id: self.font_id, bounds, glyphs: positions, } } pub fn draw( &self, positions: &[GlyphPosition], offset: Vec2, scale: f32, color: Color, ) -> Vec { let mut vertices = Vec::new(); let mut indices = Vec::new(); // TODO defer tessellation of missed glyph cache entries let mut glyphs = self.glyph_cache.glyphs.write(); let units_per_em = self.glyph_cache.units_per_em; let pos_scale = scale / units_per_em; let mut xcur = 0; let mut ycur = 0; for position in positions.iter() { let glyph = glyphs.get_mut(position.index as usize).unwrap().get_mesh(); let xpos = xcur + position.xoff; let ypos = ycur + position.yoff; xcur += position.hori_advance; ycur += position.vert_advance; let voff = vertices.len() as MeshIndex; let xpos = offset.x + xpos as f32 * pos_scale; let ypos = offset.y - ypos as f32 * pos_scale; for v in glyph.vertices.iter() { let x = v.x * scale + xpos; let y = v.y * scale + ypos; vertices.push(MeshVertex { position: Vec2::new(x, y), color, }); } for i in glyph.indices.iter() { indices.push(i + voff); } } vec![DrawCommand::Mesh { vertices, indices }] } } #[self_referencing] pub struct FontData { file_buffer: Vec, script: u32, direction: TextDirection, vertical: bool, #[borrows(file_buffer)] #[covariant] read_scope: ReadScope<'this>, #[borrows(read_scope)] #[not_covariant] font_data: AllsortsFontData<'this>, #[borrows(font_data)] #[covariant] inner: AllsortsFont>, } impl FontData { pub fn load( script: u32, direction: TextDirection, vertical: bool, file_buffer: Vec, ) -> Self { FontDataBuilder { file_buffer, script, direction, vertical, read_scope_builder: |buffer| ReadScope::new(buffer), font_data_builder: |scope| scope.read::>().unwrap(), inner_builder: |font_data| { AllsortsFont::new(font_data.table_provider(0).unwrap()) .unwrap() .unwrap() }, } .build() } pub fn shape(&mut self, text: &str) -> Vec { let presentation = MatchingPresentation::Required; let script = *self.borrow_script(); let lang_tag = None; let features = allsorts::gsub::Features::default(); let kerning = true; let direction = *self.borrow_direction(); let vertical = *self.borrow_vertical(); self.with_inner_mut(|font| { let mapped = font.map_glyphs(text, script, presentation); let infos = font .shape(mapped, script, lang_tag, &features, kerning) .unwrap(); let mut layout = GlyphLayout::new(font, &infos, direction, vertical); let positions = layout.glyph_positions().unwrap(); let mut glyphs = Vec::with_capacity(positions.len()); for (glyph, position) in infos.iter().zip(&positions) { glyphs.push(GlyphPosition { index: glyph.glyph.glyph_index, hori_advance: position.hori_advance, vert_advance: position.vert_advance, xoff: position.x_offset, yoff: position.y_offset, }); } glyphs }) } } pub struct FontStore { source: Box, loaded: Mutex>>, current_index: AtomicU32, } impl Default for FontStore { fn default() -> Self { Self::new() } } impl FontStore { pub fn new() -> Self { let source = font_kit::source::SystemSource::new(); let source = Box::new(source); Self { source, loaded: Default::default(), current_index: AtomicU32::new(0), } } pub fn load_font(&self, title: &str) -> Arc { let mut loaded = self.loaded.lock(); if let Some(loaded) = loaded.get(title) { return loaded.to_owned(); } use font_kit::family_name::FamilyName; use font_kit::handle::Handle; use font_kit::properties::Properties; println!("loading font family {}", title); let font_handle = self .source .select_best_match(&[FamilyName::Title(title.to_string())], &Properties::new()) .unwrap(); println!("loading font file: {:?}", font_handle); let font_data = if let Handle::Path { path, font_index: _, } = font_handle { std::fs::read(path).unwrap() } else { panic!("font is not a file for some reason"); }; let script = tag::LATN; let face_index = self.current_index.fetch_add(1, Ordering::Relaxed); let direction = TextDirection::LeftToRight; let vertical = false; let font = Font::load(face_index, script, direction, vertical, font_data.clone()); let font = Arc::new(font); loaded.insert(title.to_string(), font.to_owned()); font } }