Use system fonts for fallback

This commit is contained in:
Jeremy Soller 2022-10-12 10:28:30 -06:00
parent 59d1b4c38d
commit a3f36c9b76
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
4 changed files with 139 additions and 71 deletions

View file

@ -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 }

View file

@ -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<Font<'a>>,
}
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<FontShapeGlyph>, Vec<usize>) {
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;
}

View file

@ -1,22 +1,71 @@
use std::ops::Deref;
use super::{Font, FontMatches};
pub struct FontSystem<'a> {
fonts: Vec<Font<'a>>,
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<FontMatches<'a>> {
pub fn matches<'a, F: Fn(&fontdb::FaceInfo) -> bool>(&'a self, f: F) -> Option<FontMatches<'a>> {
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

View file

@ -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);