use std::borrow::{Borrow, Cow}; use std::cell::RefCell; use std::collections::HashMap; use std::i32; use std::sync::{Arc, RwLock}; use lazy_static::lazy_static; use font_kit::{ canvas::{Canvas, Format, RasterizationOptions}, error::{FontLoadingError, GlyphLoadingError}, family_name::FamilyName, font::Font, handle::Handle, hinting::HintingOptions, properties::{Properties, Style, Weight}, source::SystemSource, }; use ttf_parser::{Face, GlyphId}; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::{Vector2F, Vector2I}; use super::{FontData, FontFamily, FontStyle, LayoutBox}; type FontResult = Result; #[derive(Debug, Clone)] pub enum FontError { LockError, NoSuchFont(String, String), FontLoadError(Arc), GlyphError(Arc), } impl std::fmt::Display for FontError { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { match self { FontError::LockError => write!(fmt, "Could not lock mutex"), FontError::NoSuchFont(family, style) => { write!(fmt, "No such font: {} {}", family, style) } FontError::FontLoadError(e) => write!(fmt, "Font loading error {}", e), FontError::GlyphError(e) => write!(fmt, "Glyph error {}", e), } } } impl std::error::Error for FontError {} lazy_static! { static ref DATA_CACHE: RwLock>> = RwLock::new(HashMap::new()); } thread_local! { static FONT_SOURCE: SystemSource = SystemSource::new(); static FONT_OBJECT_CACHE: RefCell> = RefCell::new(HashMap::new()); } const PLACEHOLDER_CHAR: char = '�'; #[derive(Clone)] struct FontExt { inner: Font, face: Option>, } impl Drop for FontExt { fn drop(&mut self) { // We should make sure the face object dead first self.face.take(); } } impl FontExt { fn new(font: Font) -> Self { let handle = font.handle(); let (data, idx) = match handle.as_ref() { Some(Handle::Memory { bytes, font_index }) => (&bytes[..], *font_index), _ => unreachable!(), }; let face = unsafe { std::mem::transmute::<_, Option>>(ttf_parser::Face::parse(data, idx).ok()) }; Self { inner: font, face } } fn query_kerning_table(&self, prev: u32, next: u32) -> f32 { if let Some(face) = self.face.as_ref() { if let Some(kern) = face.tables().kern { let kern = kern .subtables .into_iter() .filter(|st| st.horizontal && !st.variable) .filter_map(|st| st.glyphs_kerning(GlyphId(prev as u16), GlyphId(next as u16))) .next() .unwrap_or(0); return kern as f32; } } 0.0 } } impl std::ops::Deref for FontExt { type Target = Font; fn deref(&self) -> &Font { &self.inner } } /// Lazily load font data. Font type doesn't own actual data, which /// lives in the cache. fn load_font_data(face: FontFamily, style: FontStyle) -> FontResult { let key = match style { FontStyle::Normal => Cow::Borrowed(face.as_str()), _ => Cow::Owned(format!("{}, {}", face.as_str(), style.as_str())), }; // First, we try to find the font object for current thread if let Some(font_object) = FONT_OBJECT_CACHE.with(|font_object_cache| { font_object_cache .borrow() .get(Borrow::::borrow(&key)) .map(Clone::clone) }) { return Ok(font_object); } // Then we need to check if the data cache contains the font data let cache = DATA_CACHE.read().unwrap(); if let Some(data) = cache.get(Borrow::::borrow(&key)) { return data.clone().map(|handle| { handle .load() .map(FontExt::new) .map_err(|e| FontError::FontLoadError(Arc::new(e))) })?; } drop(cache); // Otherwise we should load from system let mut properties = Properties::new(); match style { FontStyle::Normal => properties.style(Style::Normal), FontStyle::Italic => properties.style(Style::Italic), FontStyle::Oblique => properties.style(Style::Oblique), FontStyle::Bold => properties.weight(Weight::BOLD), }; let family = match face { FontFamily::Serif => FamilyName::Serif, FontFamily::SansSerif => FamilyName::SansSerif, FontFamily::Monospace => FamilyName::Monospace, FontFamily::Name(name) => FamilyName::Title(name.to_owned()), }; let make_not_found_error = || FontError::NoSuchFont(face.as_str().to_owned(), style.as_str().to_owned()); if let Ok(handle) = FONT_SOURCE .with(|source| source.select_best_match(&[family, FamilyName::SansSerif], &properties)) { let font = handle .load() .map(FontExt::new) .map_err(|e| FontError::FontLoadError(Arc::new(e))); let (should_cache, data) = match font.as_ref().map(|f| f.handle()) { Ok(None) => (false, Err(FontError::LockError)), Ok(Some(handle)) => (true, Ok(handle)), Err(e) => (true, Err(e.clone())), }; if should_cache { DATA_CACHE .write() .map_err(|_| FontError::LockError)? .insert(key.clone().into_owned(), data); } if let Ok(font) = font.as_ref() { FONT_OBJECT_CACHE.with(|font_object_cache| { font_object_cache .borrow_mut() .insert(key.into_owned(), font.clone()); }); } return font; } Err(make_not_found_error()) } #[derive(Clone)] pub struct FontDataInternal(FontExt); impl FontData for FontDataInternal { type ErrorType = FontError; fn new(family: FontFamily, style: FontStyle) -> Result { Ok(FontDataInternal(load_font_data(family, style)?)) } fn estimate_layout(&self, size: f64, text: &str) -> Result { let font = &self.0; let pixel_per_em = size / 1.24; let metrics = font.metrics(); let font = &self.0; let mut x_in_unit = 0f32; let mut prev = None; let place_holder = font.glyph_for_char(PLACEHOLDER_CHAR); for c in text.chars() { if let Some(glyph_id) = font.glyph_for_char(c).or(place_holder) { if let Ok(size) = font.advance(glyph_id) { x_in_unit += size.x(); } if let Some(pc) = prev { x_in_unit += font.query_kerning_table(pc, glyph_id); } prev = Some(glyph_id); } } let x_pixels = x_in_unit * pixel_per_em as f32 / metrics.units_per_em as f32; Ok(((0, 0), (x_pixels as i32, pixel_per_em as i32))) } fn draw Result<(), E>>( &self, (base_x, mut base_y): (i32, i32), size: f64, text: &str, mut draw: DrawFunc, ) -> Result, Self::ErrorType> { let em = (size / 1.24) as f32; let mut x = base_x as f32; let font = &self.0; let metrics = font.metrics(); let canvas_size = size as usize; base_y -= (0.24 * em) as i32; let mut prev = None; let place_holder = font.glyph_for_char(PLACEHOLDER_CHAR); let mut result = Ok(()); for c in text.chars() { if let Some(glyph_id) = font.glyph_for_char(c).or(place_holder) { if let Some(pc) = prev { x += font.query_kerning_table(pc, glyph_id) * em / metrics.units_per_em as f32; } let mut canvas = Canvas::new(Vector2I::splat(canvas_size as i32), Format::A8); result = font .rasterize_glyph( &mut canvas, glyph_id, em as f32, Transform2F::from_translation(Vector2F::new(0.0, em as f32)), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .map_err(|e| FontError::GlyphError(Arc::new(e))) .and(result); let base_x = x as i32; for dy in 0..canvas_size { for dx in 0..canvas_size { let alpha = canvas.pixels[dy * canvas_size + dx] as f32 / 255.0; if let Err(e) = draw(base_x + dx as i32, base_y + dy as i32, alpha) { return Ok(Err(e)); } } } x += font.advance(glyph_id).map(|size| size.x()).unwrap_or(0.0) * em / metrics.units_per_em as f32; prev = Some(glyph_id); } } result?; Ok(Ok(())) } } #[cfg(test)] mod test { use super::*; #[test] fn test_font_cache() -> FontResult<()> { // We cannot only check the size of font cache, because // the test case may be run in parallel. Thus the font cache // may contains other fonts. let _a = load_font_data(FontFamily::Serif, FontStyle::Normal)?; assert!(DATA_CACHE.read().unwrap().contains_key("serif")); let _b = load_font_data(FontFamily::Serif, FontStyle::Normal)?; assert!(DATA_CACHE.read().unwrap().contains_key("serif")); // TODO: Check they are the same return Ok(()); } }