summaryrefslogtreecommitdiffstats
path: root/vendor/plotters/src/style/font/ttf.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/plotters/src/style/font/ttf.rs')
-rw-r--r--vendor/plotters/src/style/font/ttf.rs321
1 files changed, 321 insertions, 0 deletions
diff --git a/vendor/plotters/src/style/font/ttf.rs b/vendor/plotters/src/style/font/ttf.rs
new file mode 100644
index 000000000..e6feadc82
--- /dev/null
+++ b/vendor/plotters/src/style/font/ttf.rs
@@ -0,0 +1,321 @@
+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) {
+ // 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<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
+ }
+}
+
+/// 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<FontExt> {
+ 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::<str>::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::<str>::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<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<()> {
+ // 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(());
+ }
+}