diff --git a/examples/text/Cargo.toml b/examples/text/Cargo.toml index 60f1b05..09c06c2 100644 --- a/examples/text/Cargo.toml +++ b/examples/text/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] ab_glyph = { version = "0.2", optional = true } +fontdb = "0.9" orbclient = "0.3" memmap2 = "0.5" rusttype = { version = "0.9", optional = true } diff --git a/examples/text/src/font/matches.rs b/examples/text/src/font/matches.rs index 0ed2a07..3cf88ea 100644 --- a/examples/text/src/font/matches.rs +++ b/examples/text/src/font/matches.rs @@ -1,11 +1,11 @@ use super::{Font, FontLineIndex, FontShapeGlyph, FontShapeWord, FontShapeLine, FontShapeSpan}; pub struct FontMatches<'a> { - pub fonts: Vec<&'a Font<'a>>, + pub fonts: Vec>, } impl<'a> FontMatches<'a> { - fn shape_word(&self, font_i: usize, line: &str, start_word: usize, end_word: usize, span_rtl: bool, blank: bool) -> FontShapeWord { + fn shape_fallback(&self, font_i: usize, line: &str, start_word: usize, end_word: usize, span_rtl: bool, blank: bool) -> (Vec, Vec) { let word = &line[start_word..end_word]; let font_scale = self.fonts[font_i].rustybuzz.units_per_em() as f32; @@ -101,63 +101,74 @@ impl<'a> FontMatches<'a> { } } + (glyphs, missing) + } + + fn shape_word(&self, line: &str, start_word: usize, end_word: usize, span_rtl: bool, blank: bool) -> FontShapeWord { + let mut font_i = 0; + let (mut glyphs, mut missing) = self.shape_fallback(font_i, line, start_word, end_word, span_rtl, blank); + //TODO: improve performance! - if !missing.is_empty() && font_i + 1 < self.fonts.len() { - let mut missing_i = 0; - while missing_i < missing.len() { - let mut missing_glyph = missing[missing_i]; - missing_i += 1; + font_i += 1; + while !missing.is_empty() && font_i < self.fonts.len() { + // println!("Evaluating fallback with font {}", font_i); + let (mut fb_glyphs, fb_missing) = self.shape_fallback(font_i, line, start_word, end_word, span_rtl, blank); - let mut start_glyph = missing_glyph; + // Insert all matching glyphs + let mut fb_i = 0; + while fb_i < fb_glyphs.len() { + let start = fb_glyphs[fb_i].start; + let end = fb_glyphs[fb_i].end; - // Find beginning of glyphs to replace + // Skip clusters that are not missing, or where the fallback font is missing + if !missing.contains(&start) || fb_missing.contains(&start) { + fb_i += 1; + continue; + } + + let mut missing_i = 0; + while missing_i < missing.len() { + if missing[missing_i] >= start && missing[missing_i] < end { + // println!("No longer missing {}", missing[missing_i]); + missing.remove(missing_i); + } else { + missing_i += 1; + } + } + + // Find prior glyphs let mut i = 0; while i < glyphs.len() { - if glyphs[i].start == start_glyph { + if glyphs[i].start >= start && glyphs[i].end <= end { break; + } else { + i += 1; } - i += 1; } - // Remove all matching glyphs and find end - let mut end_glyph = start_glyph; + // Remove prior glyphs while i < glyphs.len() { - if glyphs[i].start == missing_glyph { - let glyph = glyphs.remove(i); - if glyph.start < start_glyph { - start_glyph = glyph.start; - } - if glyph.end > end_glyph { - end_glyph = glyph.end; - } - } else if missing_i < missing.len() { - // Combine repeated missing items - if glyphs[i].start == missing[missing_i] { - missing_glyph = missing[missing_i]; - missing_i += 1; - } else { - break; - } + if glyphs[i].start >= start && glyphs[i].end <= end { + let _glyph = glyphs.remove(i); + // println!("Removed {},{} from {}", _glyph.start, _glyph.end, i); } else { break; } } - let mut fb_word = self.shape_word(font_i + 1, line, start_glyph, end_glyph, span_rtl, blank); - - // Insert all matching glyphs - // println!("Locate fallback for {},{} '{}' from font {} to font {}", start_glyph, end_glyph, &line[start_glyph..end_glyph], font_i + 1, font_i); - let mut j = 0; - while j < fb_word.glyphs.len() { - if fb_word.glyphs[j].start >= start_glyph && fb_word.glyphs[j].end <= end_glyph { - // println!("Copy fallback for {},{} '{}' from font {} cluster {} to font {}", start_glyph, end_glyph, &line[start_glyph..end_glyph], font_i + 1, fb_word.glyphs[j].start, font_i); - glyphs.insert(i, fb_word.glyphs.remove(j)); + while fb_i < fb_glyphs.len() { + if fb_glyphs[fb_i].start >= start && fb_glyphs[fb_i].end <= end { + let fb_glyph = fb_glyphs.remove(fb_i); + // println!("Insert {},{} from font {} at {}", fb_glyph.start, fb_glyph.end, font_i, i); + glyphs.insert(i, fb_glyph); i += 1; } else { - j += 1; + break; } } } + + font_i += 1; } /* @@ -187,10 +198,10 @@ impl<'a> FontMatches<'a> { } } if start_word < start_lb { - words.push(self.shape_word(0, line, start_span + start_word, start_span + start_lb, span_rtl, false)); + words.push(self.shape_word(line, start_span + start_word, start_span + start_lb, span_rtl, false)); } if start_lb < end_lb { - words.push(self.shape_word(0, line, start_span + start_lb, start_span + end_lb, span_rtl, true)); + words.push(self.shape_word(line, start_span + start_lb, start_span + end_lb, span_rtl, true)); } start_word = end_lb; } diff --git a/examples/text/src/font/system.rs b/examples/text/src/font/system.rs index 1957818..a45cbf3 100644 --- a/examples/text/src/font/system.rs +++ b/examples/text/src/font/system.rs @@ -1,22 +1,71 @@ +use std::ops::Deref; + use super::{Font, FontMatches}; -pub struct FontSystem<'a> { - fonts: Vec>, +pub struct FontSystem { + db: fontdb::Database, } -impl<'a> FontSystem<'a> { +impl FontSystem { pub fn new() -> Self { + let mut db = fontdb::Database::new(); + let now = std::time::Instant::now(); + db.load_system_fonts(); + //TODO: configurable default fonts + db.set_monospace_family("Fira Mono"); + db.set_sans_serif_family("Fira Sans"); + db.set_serif_family("DejaVu Serif"); + println!( + "Loaded {} font faces in {}ms.", + db.len(), + now.elapsed().as_millis() + ); + + //TODO only do this on demand! + assert_eq!(db.len(), db.faces().len()); + for i in 0..db.len() { + let id = db.faces()[i].id; + unsafe { + db.make_shared_face_data(id); + } + } + Self { - fonts: Vec::new(), + db, } } - pub fn add(&mut self, font: Font<'a>) { - self.fonts.push(font); - } - - pub fn matches(&'a self, patterns: &[&str]) -> Option> { + pub fn matches<'a, F: Fn(&fontdb::FaceInfo) -> bool>(&'a self, f: F) -> Option> { let mut fonts = Vec::new(); + for face in self.db.faces() { + if ! f(face) { + continue; + } + + let font_opt = Font::new( + match &face.source { + fontdb::Source::Binary(data) => { + data.deref().as_ref() + }, + fontdb::Source::File(path) => { + println!("Unsupported fontdb Source::File('{}')", path.display()); + continue; + }, + fontdb::Source::SharedFile(_path, data) => { + data.deref().as_ref() + }, + }, + face.index, + ); + + match font_opt { + Some(font) => fonts.push(font), + None => { + eprintln!("failed to load font '{}'", face.post_script_name); + } + } + } + /* for font in self.fonts.iter() { for rec in font.rustybuzz.names() { if rec.name_id == 4 && rec.is_unicode() { @@ -52,6 +101,7 @@ impl<'a> FontSystem<'a> { } } } + */ if ! fonts.is_empty() { Some(FontMatches { fonts diff --git a/examples/text/src/main.rs b/examples/text/src/main.rs index 09d78df..f5cb931 100644 --- a/examples/text/src/main.rs +++ b/examples/text/src/main.rs @@ -6,7 +6,6 @@ use std::{ time::Instant, }; use text::{ - Font, FontSystem, TextAction, TextCursor, @@ -55,30 +54,37 @@ fn main() { } } - let mut font_system = FontSystem::new(); + let font_system = FontSystem::new(); - font_system.add( - Font::new(include_bytes!("../../../res/Fira/FiraSans-Regular.otf"), 0).unwrap() - ); - font_system.add( - Font::new(include_bytes!("../../../res/Fira/FiraMono-Regular.otf"), 0).unwrap() - ); + let font_matches = font_system.matches(|info| -> bool { + #[cfg(feature = "mono")] + let monospaced = true; - for (font_path, font_data, font_index) in &font_datas { - match Font::new(font_data, *font_index) { - Some(font) => font_system.add(font), - None => { - eprintln!("failed to parse font '{}'", font_path) - } + #[cfg(not(feature = "mono"))] + let monospaced = false; + + let matched = { + info.style == fontdb::Style::Normal && + info.weight == fontdb::Weight::NORMAL && + info.stretch == fontdb::Stretch::Normal && + (info.monospaced == monospaced || info.post_script_name.contains("Emoji")) + }; + + if matched { + println!( + "{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}", + info.id, + info.family, + info.post_script_name, + info.style, + info.weight, + info.stretch, + info.monospaced + ); } - } - #[cfg(feature = "mono")] - let font_pattern = &["Mono", "Emoji"]; - #[cfg(not(feature = "mono"))] - let font_pattern = &["Sans", "Serif", "Emoji"]; - - let font_matches = font_system.matches(font_pattern).unwrap(); + matched + }).unwrap(); let bg_color = Color::rgb(0x34, 0x34, 0x34); let font_color = Color::rgb(0xFF, 0xFF, 0xFF);