use crate as deft;
use crate::font::family::{FontFamilies, FontFamily};
use crate::font::Font;
use crate::style::font::FontStyle;
use crate::text::textbox::DEFAULT_FALLBACK_FONTS;
use deft_macros::mrc_object;
use font_kit::family_name::FamilyName;
use font_kit::handle::Handle;
use font_kit::source::Source;
#[cfg(not(target_os = "emscripten"))]
use font_kit::source::SystemSource;
#[cfg(target_os = "emscripten")]
use font_kit::sources::mem::MemSource as SystemSource;
use skia_safe::font_style::Slant;
use skia_safe::wrapper::NativeTransmutableWrapper;
use std::collections::HashMap;
use swash::{ObliqueAngle, Style, Weight};
#[mrc_object]
pub struct FontManager {
source: SystemSource,
cache: HashMap<FontCacheKey, Option<Font>>,
}
#[derive(Hash, PartialEq, Eq, Clone)]
struct FontCacheKey {
family_name: String,
weight: u16,
style: FontStyle,
}
impl FontManager {
pub fn new() -> FontManager {
#[cfg(target_os = "emscripten")]
let mut source = {
let mut source = SystemSource::empty();
source
.add_font(Handle::Memory {
bytes: std::sync::Arc::new(
include_bytes!("../../fonts/NotoSerif-Regular.ttf").to_vec(),
),
font_index: 0,
})
.unwrap();
source
};
#[cfg(not(target_os = "emscripten"))]
let source = SystemSource::new();
FontManagerData {
source,
cache: HashMap::new(),
}
.to_ref()
}
pub fn match_best(
&self,
family_names: &[impl AsRef<str>],
style: &skia_safe::FontStyle,
) -> Vec<Font> {
let mut result = Vec::new();
let mut family_names = family_names.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
for name in DEFAULT_FALLBACK_FONTS.split(",") {
family_names.push(name);
}
for name in family_names {
if let Some(font) = self.get_by_family_name(name.as_ref(), &style) {
result.push(font.clone());
}
}
result
}
pub fn all_font_families(&self) -> FontFamilies {
if let Ok(fonts) = self.source.all_families() {
let list = fonts.iter().map(|it| FontFamily::new(it)).collect();
FontFamilies::new(list)
} else {
FontFamilies::new(vec![])
}
}
fn get_by_family_name(
&self,
name: &str,
expected_font_style: &skia_safe::FontStyle,
) -> Option<Font> {
let weight = expected_font_style.weight().unwrap() as u16;
let expected_style = match expected_font_style.slant() {
Slant::Upright => FontStyle::Normal,
Slant::Italic => FontStyle::Italic,
Slant::Oblique => FontStyle::Oblique,
};
let cache_key = FontCacheKey {
family_name: name.to_string(),
weight,
style: expected_style,
};
let mut me = self.clone();
me.cache
.entry(cache_key)
.or_insert_with(move || {
let family_name = Self::str_to_family_name(name);
let fh = self
.source
.select_family_by_generic_name(&family_name)
.ok()?;
let expected_style = match expected_font_style.slant() {
Slant::Upright => Style::Normal,
Slant::Italic => Style::Italic,
Slant::Oblique => Style::Oblique(ObliqueAngle::default()),
};
let mut fonts = Vec::new();
for h in fh.fonts() {
if let Some(font) = Self::load_font(h, name) {
fonts.push(font);
}
}
let mut result = Self::filter_fonts_by_style(&fonts, expected_style);
if result.is_empty() && expected_style != Style::Normal {
result = Self::filter_fonts_by_style(&fonts, Style::Normal);
}
Self::select_best_font_by_weight(result, weight)
.map(|f| f.synthesize(Weight(weight), expected_style))
})
.clone()
}
fn filter_fonts_by_style(fonts: &Vec<Font>, style: Style) -> Vec<&Font> {
let mut result = Vec::with_capacity(fonts.len());
for f in fonts {
if f.attributes().style() == style {
result.push(f);
}
}
result
}
fn select_best_font_by_weight(fonts: Vec<&Font>, expected_weight: u16) -> Option<Font> {
let mut best_font = None;
let mut best_weight_diff = i32::MAX;
for f in fonts {
let attrs = f.attributes();
let weight_diff = (attrs.weight().0 as i32 - expected_weight as i32).abs();
if weight_diff < best_weight_diff {
best_font = Some(f);
best_weight_diff = weight_diff;
}
}
best_font.cloned()
}
fn load_font(h: &Handle, name: &str) -> Option<Font> {
match h {
Handle::Path { path, font_index } => {
if let Some(font) = Font::from_file(path, *font_index as usize, name.to_string()) {
return Some(font);
}
}
Handle::Memory { bytes, font_index } => {
if let Some(font) =
Font::from_bytes(bytes.to_vec(), *font_index as usize, name.to_string())
{
return Some(font);
}
}
}
None
}
fn str_to_family_name(family_name: &str) -> FamilyName {
match family_name {
"serif" => FamilyName::Serif,
"sans-serif" => FamilyName::SansSerif,
"monospace" => FamilyName::Monospace,
"cursive" => FamilyName::Cursive,
"fantasy" => FamilyName::Fantasy,
_ => FamilyName::Title(family_name.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use crate::element::font_manager::FontManager;
use crate::text::textbox::DEFAULT_FALLBACK_FONTS;
fn test_default_fonts() {
let fm = FontManager::new();
let names = DEFAULT_FALLBACK_FONTS.split(",");
for name in names {
let fonts = fm.get_by_family_name(name, &skia_safe::FontStyle::normal());
assert!(fonts.is_some(), "Fail to load font: {:?}", name);
}
}
#[test]
fn test_all_font_families() {
let fm = FontManager::new();
let families = fm.all_font_families();
println!("All font families");
for fm in families.as_slice() {
println!("{}", fm.name());
}
}
#[test]
fn test_font_weight() {
let fm = FontManager::new();
let font_style = skia_safe::FontStyle::bold();
let fonts = fm.match_best(&["fangsong"], &font_style);
println!("fonts count: {}", fonts.len());
for font in fonts {
println!("{}", font.attributes().weight().0);
}
}
}