use super::{DrawCommand, MeshIndex, MeshVertex}; use allsorts::binary::read::ReadScope; use allsorts::cff::CFF; use allsorts::font::{GlyphTableFlags, MatchingPresentation}; use allsorts::font_data::{DynamicFontTableProvider, FontData as AllsortsFontData}; use allsorts::glyph_position::{GlyphLayout, TextDirection}; use allsorts::outline::OutlineBuilder; use allsorts::pathfinder_geometry::{line_segment::LineSegment2F, vector::Vector2F}; use allsorts::tables::{glyf::GlyfTable, loca::LocaTable}; use allsorts::tables::{FontTableProvider, SfntVersion}; use allsorts::{tag, Font as AllsortsFont}; use canary_types::GlyphPosition; use lyon::path::Path; use ouroboros::self_referencing; use parking_lot::{Mutex, RwLock}; const TEXT_SCALE: f32 = 0.075; pub struct GlyphCache { units_per_em: f32, glyphs: RwLock>, } impl GlyphCache { pub fn new(font: &AllsortsFont) -> Self { let glyph_num = font.num_glyphs(); let mut glyphs = Vec::with_capacity(glyph_num as usize); let units_per_em = font.head_table().unwrap().unwrap().units_per_em as f32; if font.glyph_table_flags.contains(GlyphTableFlags::CFF) && font.font_table_provider.sfnt_version() == tag::OTTO { let cff_data = font.font_table_provider.read_table_data(tag::CFF).unwrap(); let mut cff = ReadScope::new(&cff_data).read::>().unwrap(); for index in 0..glyph_num { glyphs.push(CachedGlyph::new(units_per_em, &mut cff, index)); } } else if font.glyph_table_flags.contains(GlyphTableFlags::GLYF) { let loca_data = font.font_table_provider.read_table_data(tag::LOCA).unwrap(); let loca = ReadScope::new(&loca_data) .read_dep::>(( usize::from(font.maxp_table.num_glyphs), font.head_table().unwrap().unwrap().index_to_loc_format, )) .unwrap(); let glyf_data = font.font_table_provider.read_table_data(tag::GLYF).unwrap(); let mut glyf = ReadScope::new(&glyf_data) .read_dep::>(&loca) .unwrap(); for index in 0..glyph_num { glyphs.push(CachedGlyph::new(units_per_em, &mut glyf, index)); } } else { panic!("no glyf or CFF table"); } Self { units_per_em, glyphs: RwLock::new(glyphs), } } } pub struct CachedGlyph { path: Path, mesh: Option, } impl CachedGlyph { pub fn new(units_per_em: f32, builder: &mut impl OutlineBuilder, index: u16) -> Self { let mut sink = OutlineSink::new(units_per_em); builder.visit(index, &mut sink).unwrap(); let path = sink.builder.build(); Self { path, mesh: None } } pub fn get_mesh(&mut self) -> &GlyphMesh { self.mesh.get_or_insert_with(|| { use lyon::tessellation::*; let mut geometry: VertexBuffers = VertexBuffers::new(); let fill_options = FillOptions::default().with_tolerance(0.0005); let mut tessellator = FillTessellator::new(); tessellator .tessellate_path( &self.path.clone(), &fill_options, &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| { let position = vertex.position() * TEXT_SCALE; let position = canary_types::Vec2 { x: position.x, y: position.y, }; MeshVertex { position, color: canary_types::Color::WHITE, } }), ) .unwrap(); GlyphMesh { vertices: geometry.vertices, indices: geometry.indices, } }) } } pub struct GlyphMesh { vertices: Vec, indices: Vec, } pub struct Font { data: Mutex, glyph_cache: GlyphCache, } impl Font { pub fn load( face_index: u16, script: u32, direction: TextDirection, vertical: bool, file_buffer: Vec, ) -> Self { let mut data = FontData::load(face_index, script, direction, vertical, file_buffer); let glyph_cache = data.with_inner_mut(|font| GlyphCache::new(font)); let data = Mutex::new(data); Self { data, glyph_cache } } pub fn shape(&self, text: &str) -> Vec { self.data.lock().shape(text) } pub fn draw(&self, mesh: &mut DummyText, positions: &[GlyphPosition], yoff: f32) { // 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 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 = mesh.vertices.len() as MeshIndex; let scale = TEXT_SCALE / units_per_em; let xpos = xpos as f32 * scale; let ypos = ypos as f32 * scale; for v in glyph.vertices.iter() { let x = v.position.x + xpos - 0.9; let y = v.position.y + ypos + yoff; mesh.vertices.push(MeshVertex { position: canary_types::Vec2 { x, y }, color: v.color, }); } for i in glyph.indices.iter() { mesh.indices.push(i + voff); } } } } #[self_referencing] pub struct FontData { file_buffer: Vec, face_index: u16, 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( face_index: u16, script: u32, direction: TextDirection, vertical: bool, file_buffer: Vec, ) -> Self { FontDataBuilder { file_buffer, face_index, 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 face_index = *self.borrow_face_index() as u16; 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 { face: face_index, 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, } impl FontStore { pub fn new() -> Self { let source = font_kit::source::SystemSource::new(); let source = Box::new(source); Self { source } } pub fn load_font(&self, title: &str) -> Font { 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 = 0; let direction = TextDirection::LeftToRight; let vertical = false; Font::load(face_index, script, direction, vertical, font_data.clone()) } } pub struct DummyText { vertices: Vec, indices: Vec, } impl Default for DummyText { fn default() -> Self { let store = FontStore::new(); let text = "toki o! nimi mi li jan [_mun_alasa_sona]. toki+pona li pona."; let font_families = ["mononoki", "Liberation Sans", "Ubuntu"]; let mut mesh = Self { vertices: Vec::new(), indices: Vec::new(), }; let line_height = -0.1; let mut line_offset = 0.9; for family in font_families.iter() { let font = store.load_font(family); let glyphs = font.shape(text); font.draw(&mut mesh, &glyphs, line_offset); line_offset += line_height; } mesh } } impl DummyText { pub fn draw(&self, cmds: &mut Vec) { cmds.push(DrawCommand::Mesh { vertices: self.vertices.clone(), indices: self.indices.clone(), }); } } pub struct OutlineSink { units_per_em: f32, builder: lyon::path::path::Builder, } impl OutlineSink { pub fn new(units_per_em: f32) -> Self { Self { units_per_em, builder: lyon::path::Path::builder(), } } fn pf_line_to_lyon( &self, line: &LineSegment2F, ) -> (lyon::geom::Point, lyon::geom::Point) { ( self.pf_vector_to_lyon(&line.from()), self.pf_vector_to_lyon(&line.to()), ) } fn pf_vector_to_lyon(&self, v: &Vector2F) -> lyon::geom::Point { lyon::geom::Point::::new(v.x(), v.y()) / self.units_per_em } } impl allsorts::outline::OutlineSink for OutlineSink { fn move_to(&mut self, to: Vector2F) { let to = self.pf_vector_to_lyon(&to); self.builder.begin(to); } fn line_to(&mut self, to: Vector2F) { let to = self.pf_vector_to_lyon(&to); self.builder.line_to(to); } fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) { let control = self.pf_vector_to_lyon(&control); let to = self.pf_vector_to_lyon(&to); self.builder.quadratic_bezier_to(control, to); } fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) { let (ctrl1, ctrl2) = self.pf_line_to_lyon(&control); let to = self.pf_vector_to_lyon(&to); self.builder.cubic_bezier_to(ctrl1, ctrl2, to); } fn close(&mut self) { self.builder.close(); } }