Use system fonts for fallback
This commit is contained in:
parent
59d1b4c38d
commit
a3f36c9b76
4 changed files with 139 additions and 71 deletions
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue