use std::{cell::RefCell, ops::Deref, sync::Arc};
use ahash::HashMap;
use fontdb::{Database, Query};
pub use fontdb::{FaceInfo, Family, ID};
use ribir_algo::{Resource, Sc};
use ribir_geom::{rect, Point, Rect};
use ribir_painter::{path_builder::PathBuilder, Path, PathStyle, PixelImage, Svg};
use rustybuzz::ttf_parser::{GlyphId, OutlineBuilder};
use crate::{svg_glyph_cache::SvgGlyphCache, FontFace, FontFamily};
pub struct FontDB {
default_fonts: Vec<ID>,
data_base: fontdb::Database,
cache: HashMap<ID, Option<Face>>,
}
type FontGlyphCache<K, V> = Sc<RefCell<HashMap<K, Option<V>>>>;
#[derive(Clone)]
pub struct Face {
pub face_id: ID,
pub source_data: Arc<dyn AsRef<[u8]> + Sync + Send>,
pub face_data_index: u32,
pub rb_face: rustybuzz::Face<'static>,
#[cfg(feature = "raster_png_font")]
raster_image_glyphs: FontGlyphCache<GlyphId, Resource<PixelImage>>,
outline_glyphs: FontGlyphCache<(GlyphId, PathStyle), Resource<Path>>,
svg_glyphs: Sc<RefCell<SvgGlyphCache>>,
}
impl FontDB {
pub fn set_default_fonts(&mut self, face: &FontFace) {
self.default_fonts = self.select_all_match(face);
}
pub fn default_fonts(&self) -> &[ID] { &self.default_fonts }
pub fn try_get_face_data(&self, face_id: ID) -> Option<&Face> {
self.cache.get(&face_id)?.as_ref()
}
pub fn face_data_or_insert(&mut self, face_id: ID) -> Option<&Face> {
get_or_insert_face(&mut self.cache, &self.data_base, face_id).as_ref()
}
#[inline]
pub fn face_info(&self, id: ID) -> Option<&FaceInfo> { self.data_base.face(id) }
#[inline]
pub fn faces_info_iter(&self) -> impl Iterator<Item = &FaceInfo> + '_ { self.data_base.faces() }
pub fn faces_data_iter(&mut self) -> impl Iterator<Item = Face> + '_ {
FaceIter {
face_id_iter: self.data_base.faces(),
data_base: &self.data_base,
cache: &mut self.cache,
}
}
#[inline]
pub fn load_from_bytes(&mut self, data: Vec<u8>) { self.data_base.load_font_data(data); }
#[inline]
pub fn load_font_file<P: AsRef<std::path::Path>>(
&mut self, path: P,
) -> Result<(), std::io::Error> {
self.data_base.load_font_file(path)
}
pub fn load_system_fonts(&mut self) {
self.data_base.load_system_fonts();
self.static_generic_families();
}
pub fn select_best_match(&self, face: &FontFace) -> Option<ID> {
let FontFace { families, stretch, style, weight } = face;
let families = families
.iter()
.map(to_db_family)
.collect::<Vec<_>>();
self.data_base.query(&Query {
families: &families,
weight: *weight,
stretch: *stretch,
style: *style,
})
}
pub fn select_all_match(&mut self, face: &FontFace) -> Vec<ID> {
let FontFace { families, stretch, style, weight } = face;
families
.iter()
.filter_map(|f| {
if let Some(id) = self.data_base.query(&Query {
families: &[to_db_family(f)],
weight: *weight,
stretch: *stretch,
style: *style,
}) {
if self.face_data_or_insert(id).is_some() {
return Some(id);
}
}
None
})
.collect()
}
fn static_generic_families(&mut self) {
let init_data: [(&[Family], _); 5] = [
(
&[
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "ios"))]
Family::Name("Times New Roman"),
#[cfg(target_os = "macos")]
Family::Name("Times"),
#[cfg(target_os = "linux")]
Family::Name("Free Serif"),
#[cfg(any(target_os = "linux", target_os = "android"))]
Family::Name("Noto Serif"),
],
Database::set_serif_family as fn(&mut Database, String),
),
(
&[
#[cfg(target_os = "windows")]
Family::Name("Segoe UI"),
#[cfg(target_os = "windows")]
Family::Name("Tahoma"),
#[cfg(any(target_os = "macos", target_os = "ios"))]
Family::Name("San Francisco"),
#[cfg(any(target_os = "macos", target_os = "ios"))]
Family::Name("Helvetica"),
#[cfg(any(target_os = "macos", target_os = "ios"))]
Family::Name("Helvetica Neue"),
#[cfg(target_os = "android")]
Family::Name("Roboto"),
#[cfg(target_os = "android")]
Family::Name("Droid Sans"),
#[cfg(target_os = "linux")]
Family::Name("Ubuntu"),
#[cfg(target_os = "linux")]
Family::Name("Red Hat"),
#[cfg(target_os = "linux")]
Family::Name("DejaVu Sans"),
#[cfg(target_os = "linux")]
Family::Name("Noto Sans"),
#[cfg(target_os = "linux")]
Family::Name("Liberation Sans"),
],
Database::set_sans_serif_family as fn(&mut Database, String),
),
(
&[
#[cfg(target_os = "macos")]
Family::Name("Apple Chancery"),
#[cfg(target_os = "ios")]
Family::Name("Snell Roundhand"),
#[cfg(target_os = "windows")]
Family::Name("Comic Sans MS"),
#[cfg(target_os = "android")]
Family::Name("Dancing Script"),
#[cfg(target_os = "linux")]
Family::Name("DejaVu Serif"),
#[cfg(target_os = "linux")]
Family::Name("Noto Serif"),
],
Database::set_cursive_family as fn(&mut Database, String),
),
(
&[
#[cfg(any(target_os = "macos", target_os = "ios"))]
Family::Name("Papyrus"),
#[cfg(target_os = "windows")]
Family::Name("Microsoft Sans Serif"),
#[cfg(target_os = "linux")]
Family::Name("Free Serif"),
#[cfg(target_os = "linux")]
Family::Name("DejaVu Serif"),
#[cfg(any(target_os = "linux", target_os = "android"))]
Family::Name("Noto Serif"),
],
Database::set_fantasy_family as fn(&mut Database, String),
),
(
&[
#[cfg(target_os = "macos")]
Family::Name("Andale Mono"),
#[cfg(target_os = "ios")]
Family::Name("Courier"),
#[cfg(target_os = "windows")]
Family::Name("Courier New"),
#[cfg(target_os = "android")]
Family::Name("Droid Sans Mono"),
#[cfg(target_os = "linux")]
Family::Name("DejaVu Sans Mono"),
#[cfg(target_os = "linux")]
Family::Name("Noto Sans Mono"),
],
Database::set_monospace_family as fn(&mut Database, String),
),
];
init_data.iter().for_each(|(families, set_fn)| {
let name = families
.iter()
.filter(|f| {
self
.data_base
.query(&Query { families: &[**f], ..<_>::default() })
.is_some()
})
.map(|f| self.data_base.family_name(f).to_string())
.next();
if let Some(name) = name {
set_fn(&mut self.data_base, name);
}
});
}
}
impl Default for FontDB {
fn default() -> FontDB {
let mut data_base = fontdb::Database::new();
data_base.load_font_data(include_bytes!("../Lato-Regular.ttf").to_vec());
let default_font = data_base.faces().next().map(|f| f.id).unwrap();
let mut this = FontDB { default_fonts: vec![default_font], data_base, cache: <_>::default() };
this.face_data_or_insert(default_font);
this
}
}
impl Face {
pub fn from_data(
face_id: ID, source_data: Arc<dyn AsRef<[u8]> + Sync + Send>, face_index: u32,
) -> Option<Self> {
let ptr_data = source_data.as_ref().as_ref() as *const [u8];
let rb_face = rustybuzz::Face::from_slice(unsafe { &*ptr_data }, face_index)?;
Some(Face {
source_data,
face_data_index: face_index,
rb_face,
face_id,
outline_glyphs: <_>::default(),
#[cfg(feature = "raster_png_font")]
raster_image_glyphs: <_>::default(),
svg_glyphs: <_>::default(),
})
}
#[inline]
pub fn has_char(&self, c: char) -> bool { self.rb_face.as_ref().glyph_index(c).is_some() }
pub fn as_rb_face(&self) -> &rustybuzz::Face { &self.rb_face }
pub fn outline_glyph(&self, glyph_id: GlyphId, style: &PathStyle) -> Option<Resource<Path>> {
self
.outline_glyphs
.borrow_mut()
.entry((glyph_id, style.clone()))
.or_insert_with(|| {
let mut builder = GlyphOutlineBuilder::default();
let bounds = self
.rb_face
.outline_glyph(glyph_id, &mut builder as &mut dyn OutlineBuilder);
bounds.and_then(move |b| {
let mut path = builder.build(rect(b.x_min, b.y_min, b.width(), b.height()).to_f32());
if let PathStyle::Stroke(options) = style {
path = path.stroke(options, None)?;
}
Some(Resource::new(path))
})
})
.as_ref()
.cloned()
}
#[cfg(feature = "raster_png_font")]
pub fn glyph_raster_image(
&self, glyph_id: GlyphId, pixels_per_em: u16,
) -> Option<Resource<PixelImage>> {
use rustybuzz::ttf_parser::RasterImageFormat;
self
.raster_image_glyphs
.borrow_mut()
.entry(glyph_id)
.or_insert_with(|| {
self
.rb_face
.glyph_raster_image(glyph_id, pixels_per_em)
.and_then(|img| {
if img.format == RasterImageFormat::PNG {
Some(Resource::new(PixelImage::from_png(img.data)))
} else {
None
}
})
})
.clone()
}
pub fn glyph_svg_image(&self, glyph_id: GlyphId) -> Option<Svg> {
self
.svg_glyphs
.borrow_mut()
.svg_or_insert(glyph_id, &self.rb_face)
.clone()
}
#[inline]
pub fn units_per_em(&self) -> u16 { self.rb_face.deref().units_per_em() }
}
fn to_db_family(f: &FontFamily) -> Family {
match f {
FontFamily::Name(name) => Family::Name(name),
FontFamily::Serif => Family::Serif,
FontFamily::SansSerif => Family::SansSerif,
FontFamily::Cursive => Family::Cursive,
FontFamily::Fantasy => Family::Fantasy,
FontFamily::Monospace => Family::Monospace,
}
}
#[derive(Default)]
struct GlyphOutlineBuilder {
builder: PathBuilder,
closed: bool,
}
impl GlyphOutlineBuilder {
fn build(mut self, bounds: Rect) -> Path {
if !self.closed {
self.builder.end_path(false);
}
self.builder.build_with_bounds(bounds)
}
}
impl OutlineBuilder for GlyphOutlineBuilder {
fn move_to(&mut self, x: f32, y: f32) {
self.closed = false;
self.builder.begin_path(Point::new(x, y));
}
fn line_to(&mut self, x: f32, y: f32) {
self.closed = false;
self.builder.line_to(Point::new(x, y));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.closed = false;
self
.builder
.quadratic_curve_to(Point::new(x1, y1), Point::new(x, y));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.closed = false;
self
.builder
.bezier_curve_to(Point::new(x1, y1), Point::new(x2, y2), Point::new(x, y));
}
fn close(&mut self) {
if !self.closed {
self.closed = true;
self.builder.end_path(true)
}
}
}
impl std::ops::Deref for Face {
type Target = rustybuzz::ttf_parser::Face<'static>;
#[inline]
fn deref(&self) -> &Self::Target { &self.rb_face }
}
pub struct FaceIter<'a, T>
where
T: Iterator<Item = &'a FaceInfo>,
{
face_id_iter: T,
data_base: &'a Database,
cache: &'a mut HashMap<ID, Option<Face>>,
}
impl<'a, T> Iterator for FaceIter<'a, T>
where
T: Iterator<Item = &'a FaceInfo>,
{
type Item = Face;
fn next(&mut self) -> Option<Self::Item> {
loop {
let info = self.face_id_iter.next()?;
let face = get_or_insert_face(self.cache, self.data_base, info.id)
.as_ref()
.cloned();
if face.is_some() {
return face;
}
}
}
}
fn get_or_insert_face<'a>(
cache: &'a mut HashMap<ID, Option<Face>>, data_base: &'a Database, id: ID,
) -> &'a Option<Face> {
cache.entry(id).or_insert_with(|| {
data_base
.face_source(id)
.and_then(|(src, face_index)| {
let source_data = match src {
fontdb::Source::Binary(data) => Some(data),
fontdb::Source::File(_) => {
let mut source_data = None;
data_base.with_face_data(id, |data, index| {
assert_eq!(face_index, index);
let data: Arc<dyn AsRef<[u8]> + Sync + Send> = Arc::new(data.to_owned());
source_data = Some(data);
});
source_data
}
fontdb::Source::SharedFile(_, data) => Some(data),
}?;
Face::from_data(id, source_data, face_index)
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::FontWeight;
#[test]
fn load_font_from_path() {
let mut db = FontDB::default();
let path = env!("CARGO_MANIFEST_DIR").to_owned() + "/../fonts/DejaVuSans.ttf";
db.load_font_file(path).unwrap();
let face_id = db.select_best_match(&FontFace {
families: vec![FontFamily::Name("DejaVu Sans".into())].into_boxed_slice(),
..<_>::default()
});
assert!(face_id.is_some());
let info = db.face_info(face_id.unwrap()).unwrap();
assert_eq!(info.families.len(), 1);
assert_eq!(info.families[0].0, "DejaVu Sans");
}
#[test]
fn load_font_from_bytes() {
let mut db = FontDB::default();
let bytes = include_bytes!("../../fonts/GaramondNo8-Reg.ttf");
db.load_from_bytes(bytes.to_vec());
let face_id = db.select_best_match(&FontFace {
families: vec![FontFamily::Name("GaramondNo8".into())].into_boxed_slice(),
..<_>::default()
});
assert!(face_id.is_some());
}
#[test]
fn load_sys_fonts() {
let mut db = FontDB::default();
db.load_system_fonts();
assert!(db.faces_info_iter().next().is_some())
}
#[test]
fn match_font() {
let mut fonts = FontDB::default();
fonts.load_system_fonts();
let path = env!("CARGO_MANIFEST_DIR").to_owned() + "/../fonts/DejaVuSans.ttf";
let _ = fonts.load_font_file(path);
let mut face = FontFace {
families: vec![FontFamily::Name("DejaVu Sans".into()), FontFamily::SansSerif]
.into_boxed_slice(),
..<_>::default()
};
let id = fonts.select_best_match(&face).unwrap();
let info = fonts.face_info(id).unwrap();
assert_eq!(info.families.len(), 1);
assert_eq!(info.families[0].0, "DejaVu Sans");
fonts.data_base.remove_face(id);
face.weight = FontWeight::BOLD;
let id = fonts.select_best_match(&face);
assert!(id.is_some());
let info = fonts.face_info(id.unwrap()).unwrap();
assert_eq!(info.weight, FontWeight::BOLD);
}
}