diff --git a/Cargo.toml b/Cargo.toml index 63a7fce..f490c84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,6 @@ lyon = "1" ouroboros = "^0.15" parking_lot = "0.12" wasmtime = "0.38" + +[dependencies.font-kit] +version = "0.11" diff --git a/src/text.rs b/src/text.rs index b82c80d..9503c6a 100644 --- a/src/text.rs +++ b/src/text.rs @@ -14,16 +14,27 @@ use canary_types::GlyphPosition; use lyon::path::Path; use ouroboros::self_referencing; -const TEXT_SCALE: f32 = 0.0001; +const TEXT_SCALE: f32 = 0.075; pub struct GlyphCache { + units_per_em: f32, glyphs: Vec, } impl GlyphCache { - pub fn new(font: &AllsortsFont) -> Self { + pub fn empty() -> Self { + Self { + units_per_em: 0.0, + glyphs: Vec::new(), + } + } + + pub fn load_paths(&mut self, font: &AllsortsFont) { let glyph_num = font.num_glyphs(); - let mut glyphs = Vec::with_capacity(glyph_num as usize); + self.glyphs.clear(); + self.glyphs.reserve(glyph_num as usize); + + self.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 @@ -31,7 +42,8 @@ impl GlyphCache { 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(&mut cff, index)); + self.glyphs + .push(CachedGlyph::new(self.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(); @@ -47,24 +59,24 @@ impl GlyphCache { .unwrap(); for index in 0..glyph_num { - glyphs.push(CachedGlyph::new(&mut glyf, index)); + self.glyphs + .push(CachedGlyph::new(self.units_per_em, &mut glyf, index)); } } else { - panic!("no glyf or CFF table"); + panic!("no glyf or CFF table {:?}"); } - - Self { glyphs } } - pub fn draw(&mut self, mesh: &mut DummyText, index: u16, xpos: i32, ypos: i32) { + pub fn draw(&mut self, mesh: &mut DummyText, index: u16, xpos: i32, ypos: i32, yoff: f32) { let glyph = self.glyphs.get_mut(index as usize).unwrap().get_mesh(); let voff = mesh.vertices.len() as MeshIndex; - let xoff = (xpos as f32) * TEXT_SCALE; - let yoff = (ypos as f32) * TEXT_SCALE; + let scale = TEXT_SCALE / self.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 + xoff - 0.9; - let y = v.position.y + yoff + 0.5; + 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, @@ -83,8 +95,8 @@ pub struct CachedGlyph { } impl CachedGlyph { - pub fn new(builder: &mut impl OutlineBuilder, index: u16) -> Self { - let mut sink = OutlineSink::new(); + 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(); @@ -96,11 +108,12 @@ impl CachedGlyph { 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(), - &FillOptions::default(), + &fill_options, &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| { let position = vertex.position() * TEXT_SCALE; let position = canary_types::Vec2 { @@ -136,6 +149,7 @@ pub struct Font { script: u32, direction: TextDirection, vertical: bool, + glyph_cache: GlyphCache, #[borrows(file_buffer)] #[covariant] @@ -158,12 +172,13 @@ impl Font { vertical: bool, file_buffer: Vec, ) -> Self { - FontBuilder { + let mut built = FontBuilder { file_buffer, face_index, script, direction, vertical, + glyph_cache: GlyphCache::empty(), read_scope_builder: |buffer| ReadScope::new(buffer), font_data_builder: |scope| scope.read::>().unwrap(), inner_builder: |font_data| { @@ -172,11 +187,16 @@ impl Font { .unwrap() }, } - .build() - } + .build(); - pub fn create_glyph_cache(&self) -> GlyphCache { - GlyphCache::new(self.borrow_inner()) + let mut glyph_cache = GlyphCache::empty(); + built.with_inner_mut(|font| { + glyph_cache.load_paths(font); + }); + + built.with_glyph_cache_mut(|old| std::mem::replace(old, glyph_cache)); + + built } pub fn shape(&mut self, text: &str) -> Vec { @@ -213,6 +233,49 @@ impl Font { } } +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, Style}; + + 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, @@ -220,32 +283,34 @@ pub struct DummyText { impl Default for DummyText { fn default() -> Self { - let script = tag::LATN; - let face_index = 0; - let direction = TextDirection::LeftToRight; - let vertical = false; - let buffer = include_bytes!("linja-pona-4.9.otf"); - let mut font = Font::load(face_index, script, direction, vertical, buffer.to_vec()); - - let mut cache = font.create_glyph_cache(); - - let text = "toki o! nimi mi li [_mun_alasa_sona]. toki+pona li pona."; - - let glyphs = font.shape(text); + let store = FontStore::new(); + let text = "The quick brown fox jumps over the lazy dog. 🍑"; + let font_families = ["mononoki", "Liberation Sans", "Ubuntu", "Noto Color Emoji"]; let mut mesh = Self { vertices: Vec::new(), indices: Vec::new(), }; - let mut xcur = 0; - let mut ycur = 0; - for glyph in glyphs.iter() { - let xpos = xcur + glyph.xoff; - let ypos = ycur + glyph.yoff; - xcur += glyph.hori_advance; - ycur += glyph.vert_advance; - cache.draw(&mut mesh, glyph.index, xpos, ypos); + let line_height = -0.1; + let mut line_offset = 0.9; + for family in font_families.iter() { + let mut font = store.load_font(family); + let glyphs = font.shape(text); + + font.with_glyph_cache_mut(|cache| { + let mut xcur = 0; + let mut ycur = 0; + for glyph in glyphs.iter() { + let xpos = xcur + glyph.xoff; + let ypos = ycur + glyph.yoff; + xcur += glyph.hori_advance; + ycur += glyph.vert_advance; + cache.draw(&mut mesh, glyph.index, xpos, ypos, line_offset); + } + }); + + line_offset += line_height; } mesh @@ -262,48 +327,53 @@ impl DummyText { } pub struct OutlineSink { + units_per_em: f32, builder: lyon::path::path::Builder, } impl OutlineSink { - pub fn new() -> Self { + pub fn new(units_per_em: f32) -> Self { Self { + units_per_em, builder: lyon::path::Path::builder(), } } - fn pf_line_to_lyon(line: &LineSegment2F) -> (lyon::geom::Point, lyon::geom::Point) { + 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()), + self.pf_vector_to_lyon(&line.from()), + self.pf_vector_to_lyon(&line.to()), ) } - fn pf_vector_to_lyon(v: &Vector2F) -> lyon::geom::Point { - lyon::geom::Point::::new(v.x(), v.y()) + 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); + 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); + 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); + 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); + 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); }