cosmic-text/src/font/fallback/mod.rs
Mohammad AlSaleh 59ac0b824c Store a sorted list of monospace font ids in font system
Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
2024-03-10 15:05:07 -06:00

262 lines
9.1 KiB
Rust

// SPDX-License-Identifier: MIT OR Apache-2.0
use alloc::collections::BTreeSet;
use alloc::sync::Arc;
use fontdb::Family;
use unicode_script::Script;
use crate::{Font, FontMatchKey, FontSystem, ShapePlanCache};
use self::platform::*;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows",)))]
#[path = "other.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "macos.rs"]
mod platform;
#[cfg(target_os = "linux")]
#[path = "unix.rs"]
mod platform;
#[cfg(target_os = "windows")]
#[path = "windows.rs"]
mod platform;
#[cfg(not(feature = "warn_on_missing_glyphs"))]
use log::debug as missing_warn;
#[cfg(feature = "warn_on_missing_glyphs")]
use log::warn as missing_warn;
// Match on lowest font_weight_diff, then script_non_matches, then font_weight
// 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 {
font_weight_diff: Option<u16>,
codepoint_non_matches: Option<usize>,
font_weight: u16,
id: fontdb::ID,
}
pub struct FontFallbackIter<'a> {
font_system: &'a mut FontSystem,
font_match_keys: &'a [FontMatchKey],
default_families: &'a [&'a Family<'a>],
monospace_fallbacks: BTreeSet<MonospaceFallbackInfo>,
default_i: usize,
scripts: &'a [Script],
word: &'a str,
script_i: (usize, usize),
common_i: usize,
other_i: usize,
end: bool,
}
impl<'a> FontFallbackIter<'a> {
pub fn new(
font_system: &'a mut FontSystem,
font_match_keys: &'a [FontMatchKey],
default_families: &'a [&'a Family<'a>],
scripts: &'a [Script],
word: &'a str,
) -> Self {
Self {
font_system,
font_match_keys,
default_families,
monospace_fallbacks: BTreeSet::new(),
default_i: 0,
scripts,
word,
script_i: (0, 0),
common_i: 0,
other_i: 0,
end: false,
}
}
pub fn check_missing(&mut self, word: &str) {
if self.end {
missing_warn!(
"Failed to find any fallback for {:?} locale '{}': '{}'",
self.scripts,
self.font_system.locale(),
word
);
} else if self.other_i > 0 {
missing_warn!(
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts,
self.font_system.locale(),
self.face_name(self.font_match_keys[self.other_i - 1].id),
word
);
} else if !self.scripts.is_empty() && self.common_i > 0 {
let family = common_fallback()[self.common_i - 1];
missing_warn!(
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts,
self.font_system.locale(),
family,
word
);
}
}
pub fn face_name(&self, id: fontdb::ID) -> &str {
if let Some(face) = self.font_system.db().face(id) {
if let Some((name, _)) = face.families.first() {
name
} else {
&face.post_script_name
}
} else {
"invalid font id"
}
}
pub fn shape_plan_cache(&mut self) -> &mut ShapePlanCache {
self.font_system.shape_plan_cache()
}
fn face_contains_family(&self, id: fontdb::ID, family_name: &str) -> bool {
if let Some(face) = self.font_system.db().face(id) {
face.families.iter().any(|(name, _)| name == family_name)
} else {
false
}
}
}
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.font_weight_diff == 0 || is_mono)
};
while self.default_i < self.default_families.len() {
self.default_i += 1;
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(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.font_weight_diff == 0 {
// Default font
let fallback_info = MonospaceFallbackInfo {
font_weight_diff: None,
codepoint_non_matches: None,
font_weight: m_key.font_weight,
id: m_key.id,
};
assert!(self.monospace_fallbacks.insert(fallback_info));
}
}
}
// Set a monospace fallback if Monospace family is not found
if is_mono {
if self.font_system.is_monospace(m_key.id) {
let supported_cp_count_opt = self
.font_system
.get_font_supported_codepoints_in_word(m_key.id, self.word);
if let Some(supported_cp_count) = supported_cp_count_opt {
let codepoint_non_matches =
self.word.chars().count() - supported_cp_count;
let fallback_info = MonospaceFallbackInfo {
font_weight_diff: Some(m_key.font_weight_diff),
codepoint_non_matches: Some(codepoint_non_matches),
font_weight: m_key.font_weight,
id: m_key.id,
};
assert!(self.monospace_fallbacks.insert(fallback_info));
}
}
}
}
// If default family is Monospace fallback to first monospaced font
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);
}
}
}
while self.script_i.0 < self.scripts.len() {
let script = self.scripts[self.script_i.0];
let script_families = script_fallback(script, self.font_system.locale());
while self.script_i.1 < script_families.len() {
let script_family = script_families[self.script_i.1];
self.script_i.1 += 1;
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);
}
}
}
log::debug!(
"failed to find family '{}' for script {:?} and locale '{}'",
script_family,
script,
self.font_system.locale(),
);
}
self.script_i.0 += 1;
self.script_i.1 = 0;
}
let common_families = common_fallback();
while self.common_i < common_families.len() {
let common_family = common_families[self.common_i];
self.common_i += 1;
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);
}
}
}
log::debug!("failed to find family '{}'", common_family);
}
//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_match_keys.len() {
let id = self.font_match_keys[self.other_i].id;
self.other_i += 1;
if forbidden_families
.iter()
.all(|family_name| !self.face_contains_family(id, family_name))
{
if let Some(font) = self.font_system.get_font(id) {
return Some(font);
}
}
}
self.end = true;
None
}
}