Try harder to succeed at fall-backing to a Monospace font
A combination of some ideas: * Try all Monospace fonts before giving up. * Relax exact weight restriction on font matching when trying Monospace fall-back. Try smaller weights if needed. * Make the fall-back try order weight-offset aware, AND script-aware. * And finally, add the option to adjust the font size of glyphs using fall-back Monospace fonts, so the width of them matches the default font width. For my use-case, the current fall-back attempt always fails with Arabic script. And none of the Arabic-supporting Monospace fonts in my system also support medium weight. So, if my default font is set to medium weight, script-aware fall-back alone will still not work. Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
This commit is contained in:
parent
054b7da828
commit
329941c4a6
8 changed files with 177 additions and 39 deletions
|
|
@ -3,10 +3,11 @@
|
|||
use alloc::sync::Arc;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use alloc::collections::BTreeSet;
|
||||
use fontdb::Family;
|
||||
use unicode_script::Script;
|
||||
|
||||
use crate::{Font, FontSystem, ShapePlanCache};
|
||||
use crate::{Font, FontSystem, ShapePlanCache, FontMatchKey};
|
||||
|
||||
use self::platform::*;
|
||||
|
||||
|
|
@ -31,10 +32,21 @@ use log::debug as missing_warn;
|
|||
#[cfg(feature = "warn_on_missing_glyphs")]
|
||||
use log::warn as missing_warn;
|
||||
|
||||
// Match on lowest weight_offset, then script_non_matches
|
||||
// Default font gets None for both `weight_offset` and `script_non_matches`, and thus, it is
|
||||
// always the first to be popped from the set.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct MonospaceFallbackInfo {
|
||||
weight_offset: Option<u16>,
|
||||
script_non_matches: Option<usize>,
|
||||
id: fontdb::ID,
|
||||
}
|
||||
|
||||
pub struct FontFallbackIter<'a> {
|
||||
font_system: &'a mut FontSystem,
|
||||
font_ids: &'a [fontdb::ID],
|
||||
font_match_keys: &'a [FontMatchKey],
|
||||
default_families: &'a [&'a Family<'a>],
|
||||
monospace_fallbacks: BTreeSet<MonospaceFallbackInfo>,
|
||||
default_i: usize,
|
||||
scripts: &'a [Script],
|
||||
script_i: (usize, usize),
|
||||
|
|
@ -46,14 +58,15 @@ pub struct FontFallbackIter<'a> {
|
|||
impl<'a> FontFallbackIter<'a> {
|
||||
pub fn new(
|
||||
font_system: &'a mut FontSystem,
|
||||
font_ids: &'a [fontdb::ID],
|
||||
font_match_keys: &'a [FontMatchKey],
|
||||
default_families: &'a [&'a Family<'a>],
|
||||
scripts: &'a [Script],
|
||||
) -> Self {
|
||||
Self {
|
||||
font_system,
|
||||
font_ids,
|
||||
font_match_keys,
|
||||
default_families,
|
||||
monospace_fallbacks: BTreeSet::new(),
|
||||
default_i: 0,
|
||||
scripts,
|
||||
script_i: (0, 0),
|
||||
|
|
@ -76,7 +89,7 @@ impl<'a> FontFallbackIter<'a> {
|
|||
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
|
||||
self.scripts,
|
||||
self.font_system.locale(),
|
||||
self.face_name(self.font_ids[self.other_i - 1]),
|
||||
self.face_name(self.font_match_keys[self.other_i - 1].id),
|
||||
word
|
||||
);
|
||||
} else if !self.scripts.is_empty() && self.common_i > 0 {
|
||||
|
|
@ -119,34 +132,71 @@ impl<'a> FontFallbackIter<'a> {
|
|||
impl<'a> Iterator for FontFallbackIter<'a> {
|
||||
type Item = Arc<Font>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(fallback_info) = self.monospace_fallbacks.pop_first() {
|
||||
if let Some(font) = self.font_system.get_font(fallback_info.id) {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
|
||||
let font_match_keys_iter = |is_mono| self.font_match_keys
|
||||
.iter()
|
||||
.filter(move |m_key| m_key.weight_offset == 0 || is_mono);
|
||||
|
||||
while self.default_i < self.default_families.len() {
|
||||
self.default_i += 1;
|
||||
let mut monospace_fallback = None;
|
||||
for id in self.font_ids.iter() {
|
||||
let is_mono = self.default_families[self.default_i - 1] == &Family::Monospace;
|
||||
|
||||
for m_key in font_match_keys_iter(is_mono) {
|
||||
let default_family = self
|
||||
.font_system
|
||||
.db()
|
||||
.family_name(self.default_families[self.default_i - 1]);
|
||||
if self.face_contains_family(*id, default_family) {
|
||||
if let Some(font) = self.font_system.get_font(*id) {
|
||||
return Some(font);
|
||||
if self.face_contains_family(m_key.id, default_family) {
|
||||
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||
if !is_mono {
|
||||
return Some(font);
|
||||
} else if m_key.weight_offset == 0 {
|
||||
// Default font
|
||||
let fallback_info = MonospaceFallbackInfo {
|
||||
weight_offset: None,
|
||||
script_non_matches: None,
|
||||
id: m_key.id
|
||||
};
|
||||
assert_eq!(self.monospace_fallbacks.insert(fallback_info), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set a monospace fallback if Monospace family is not found
|
||||
if self.default_families[self.default_i - 1] == &Family::Monospace
|
||||
&& monospace_fallback.is_none()
|
||||
{
|
||||
if let Some(face_info) = self.font_system.db().face(*id) {
|
||||
if is_mono {
|
||||
let script_tags = self.scripts.iter()
|
||||
.filter_map(|script| {
|
||||
let script_as_lower = script.short_name().to_lowercase();
|
||||
<[u8; 4]>::try_from(script_as_lower.as_bytes()).ok()
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
if let Some(face_info) = self.font_system.db().face(m_key.id) {
|
||||
// Don't use emoji fonts as Monospace
|
||||
if face_info.monospaced && !face_info.post_script_name.contains("Emoji") {
|
||||
monospace_fallback = Some(id);
|
||||
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||
let script_non_matches = self.scripts.len() - script_tags.iter()
|
||||
.filter(|&&script_tag| {
|
||||
font.scripts().iter().any(|&tag_bytes| tag_bytes == script_tag)
|
||||
}).count();
|
||||
|
||||
let fallback_info = MonospaceFallbackInfo {
|
||||
weight_offset: Some(m_key.weight_offset),
|
||||
script_non_matches: Some(script_non_matches),
|
||||
id: m_key.id
|
||||
};
|
||||
assert_eq!(self.monospace_fallbacks.insert(fallback_info), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If default family is Monospace fallback to first monospaced font
|
||||
if let Some(id) = monospace_fallback {
|
||||
if let Some(font) = self.font_system.get_font(*id) {
|
||||
if let Some(fallback_info) = self.monospace_fallbacks.pop_first() {
|
||||
if let Some(font) = self.font_system.get_font(fallback_info.id) {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
|
|
@ -159,9 +209,9 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
|||
while self.script_i.1 < script_families.len() {
|
||||
let script_family = script_families[self.script_i.1];
|
||||
self.script_i.1 += 1;
|
||||
for id in self.font_ids.iter() {
|
||||
if self.face_contains_family(*id, script_family) {
|
||||
if let Some(font) = self.font_system.get_font(*id) {
|
||||
for m_key in font_match_keys_iter(false) {
|
||||
if self.face_contains_family(m_key.id, script_family) {
|
||||
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
|
|
@ -182,9 +232,9 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
|||
while self.common_i < common_families.len() {
|
||||
let common_family = common_families[self.common_i];
|
||||
self.common_i += 1;
|
||||
for id in self.font_ids.iter() {
|
||||
if self.face_contains_family(*id, common_family) {
|
||||
if let Some(font) = self.font_system.get_font(*id) {
|
||||
for m_key in font_match_keys_iter(false) {
|
||||
if self.face_contains_family(m_key.id, common_family) {
|
||||
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
|
|
@ -195,8 +245,8 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
|||
//TODO: do we need to do this?
|
||||
//TODO: do not evaluate fonts more than once!
|
||||
let forbidden_families = forbidden_fallback();
|
||||
while self.other_i < self.font_ids.len() {
|
||||
let id = self.font_ids[self.other_i];
|
||||
while self.other_i < self.font_match_keys.len() {
|
||||
let id = self.font_match_keys[self.other_i].id;
|
||||
self.other_i += 1;
|
||||
if forbidden_families
|
||||
.iter()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue