use std::sync::Arc;
use fontdb::{Database, ID};
use svgtypes::FontFamily;
use self::layout::DatabaseExt;
use crate::{Cache, Font, FontStretch, FontStyle, Text};
pub(crate) mod flatten;
mod colr;
pub mod layout;
pub type FontSelectionFn<'a> =
Box<dyn Fn(&Font, &mut Arc<Database>) -> Option<ID> + Send + Sync + 'a>;
pub type FallbackSelectionFn<'a> =
Box<dyn Fn(char, &[ID], &mut Arc<Database>) -> Option<ID> + Send + Sync + 'a>;
pub struct FontResolver<'a> {
pub select_font: FontSelectionFn<'a>,
pub select_fallback: FallbackSelectionFn<'a>,
}
impl Default for FontResolver<'_> {
fn default() -> Self {
FontResolver {
select_font: FontResolver::default_font_selector(),
select_fallback: FontResolver::default_fallback_selector(),
}
}
}
impl FontResolver<'_> {
pub fn default_font_selector() -> FontSelectionFn<'static> {
Box::new(move |font, fontdb| {
let mut name_list = Vec::new();
for family in &font.families {
name_list.push(match family {
FontFamily::Serif => fontdb::Family::Serif,
FontFamily::SansSerif => fontdb::Family::SansSerif,
FontFamily::Cursive => fontdb::Family::Cursive,
FontFamily::Fantasy => fontdb::Family::Fantasy,
FontFamily::Monospace => fontdb::Family::Monospace,
FontFamily::Named(s) => fontdb::Family::Name(s),
});
}
name_list.push(fontdb::Family::Serif);
let stretch = match font.stretch {
FontStretch::UltraCondensed => fontdb::Stretch::UltraCondensed,
FontStretch::ExtraCondensed => fontdb::Stretch::ExtraCondensed,
FontStretch::Condensed => fontdb::Stretch::Condensed,
FontStretch::SemiCondensed => fontdb::Stretch::SemiCondensed,
FontStretch::Normal => fontdb::Stretch::Normal,
FontStretch::SemiExpanded => fontdb::Stretch::SemiExpanded,
FontStretch::Expanded => fontdb::Stretch::Expanded,
FontStretch::ExtraExpanded => fontdb::Stretch::ExtraExpanded,
FontStretch::UltraExpanded => fontdb::Stretch::UltraExpanded,
};
let style = match font.style {
FontStyle::Normal => fontdb::Style::Normal,
FontStyle::Italic => fontdb::Style::Italic,
FontStyle::Oblique => fontdb::Style::Oblique,
};
let query = fontdb::Query {
families: &name_list,
weight: fontdb::Weight(font.weight),
stretch,
style,
};
let id = fontdb.query(&query);
if id.is_none() {
log::warn!(
"No match for '{}' font-family.",
font.families
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(", ")
);
}
id
})
}
pub fn default_fallback_selector() -> FallbackSelectionFn<'static> {
Box::new(|c, exclude_fonts, fontdb| {
let base_font_id = exclude_fonts[0];
for face in fontdb.faces() {
if exclude_fonts.contains(&face.id) {
continue;
}
let base_face = fontdb.face(base_font_id)?;
if base_face.style != face.style
&& base_face.weight != face.weight
&& base_face.stretch != face.stretch
{
continue;
}
if !fontdb.has_char(face.id, c) {
continue;
}
let base_family = base_face
.families
.iter()
.find(|f| f.1 == fontdb::Language::English_UnitedStates)
.unwrap_or(&base_face.families[0]);
let new_family = face
.families
.iter()
.find(|f| f.1 == fontdb::Language::English_UnitedStates)
.unwrap_or(&base_face.families[0]);
log::warn!("Fallback from {} to {}.", base_family.0, new_family.0);
return Some(face.id);
}
None
})
}
}
impl std::fmt::Debug for FontResolver<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("FontResolver { .. }")
}
}
pub(crate) fn convert(text: &mut Text, resolver: &FontResolver, cache: &mut Cache) -> Option<()> {
let (text_fragments, bbox) = layout::layout_text(text, resolver, &mut cache.fontdb)?;
text.layouted = text_fragments;
text.bounding_box = bbox.to_rect();
text.abs_bounding_box = bbox.transform(text.abs_transform)?.to_rect();
let (group, stroke_bbox) = flatten::flatten(text, cache)?;
text.flattened = Box::new(group);
text.stroke_bounding_box = stroke_bbox.to_rect();
text.abs_stroke_bounding_box = stroke_bbox.transform(text.abs_transform)?.to_rect();
Some(())
}