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<T> = Result<T, FontError>;
#[derive(Debug, Clone)]
pub enum FontError {
LockError,
NoSuchFont(String, String),
FontLoadError(Arc<FontLoadingError>),
GlyphError(Arc<GlyphLoadingError>),
}
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<HashMap<String, FontResult<Handle>>> =
RwLock::new(HashMap::new());
}
thread_local! {
static FONT_SOURCE: SystemSource = SystemSource::new();
static FONT_OBJECT_CACHE: RefCell<HashMap<String, FontExt>> = RefCell::new(HashMap::new());
}
const PLACEHOLDER_CHAR: char = '�';
#[derive(Clone)]
struct FontExt {
inner: Font,
face: Option<Face<'static>>,
}
impl Drop for FontExt {
fn drop(&mut self) {
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<Face<'static>>>(
ttf_parser::Face::from_slice(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
}
}
fn load_font_data(face: FontFamily, style: FontStyle) -> FontResult<FontExt> {
let key = match style {
FontStyle::Normal => Cow::Borrowed(face.as_str()),
_ => Cow::Owned(format!("{}, {}", face.as_str(), style.as_str())),
};
if let Some(font_object) = FONT_OBJECT_CACHE.with(|font_object_cache| {
font_object_cache
.borrow()
.get(Borrow::<str>::borrow(&key))
.map(Clone::clone)
}) {
return Ok(font_object);
}
let cache = DATA_CACHE.read().unwrap();
if let Some(data) = cache.get(Borrow::<str>::borrow(&key)) {
return data.clone().map(|handle| {
handle
.load()
.map(FontExt::new)
.map_err(|e| FontError::FontLoadError(Arc::new(e)))
})?;
}
drop(cache);
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<Self, FontError> {
Ok(FontDataInternal(load_font_data(family, style)?))
}
fn estimate_layout(&self, size: f64, text: &str) -> Result<LayoutBox, Self::ErrorType> {
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<E, DrawFunc: FnMut(i32, i32, f32) -> Result<(), E>>(
&self,
(base_x, mut base_y): (i32, i32),
size: f64,
text: &str,
mut draw: DrawFunc,
) -> Result<Result<(), E>, 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<()> {
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"));
return Ok(());
}
}