Skip to content

Commit

Permalink
Merge pull request #311 from Monadic-Cat/feature-ab_glyph
Browse files Browse the repository at this point in the history
Add `ab_glyph` font backend
  • Loading branch information
AaronErhardt authored Sep 8, 2022
2 parents 0e3332a + 609a557 commit a4dab40
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 3 deletions.
7 changes: 6 additions & 1 deletion plotters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ ttf-parser = { version = "0.15.0", optional = true }
lazy_static = { version = "1.4.0", optional = true }
pathfinder_geometry = { version = "0.5.1", optional = true }
font-kit = { version = "0.11.0", optional = true }
ab_glyph = { version = "0.2.12", optional = true }
once_cell = { version = "1.8.0", optional = true }


[target.'cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))'.dependencies.image]
version = "0.24.3"
Expand Down Expand Up @@ -73,7 +76,7 @@ all_series = ["area_series", "line_series", "point_series", "surface_series"]
all_elements = ["errorbar", "candlestick", "boxplot", "histogram"]

# Tier 1 Backends
bitmap_backend = ["plotters-bitmap", "ttf"]
bitmap_backend = ["plotters-bitmap"]
bitmap_encoder = ["plotters-bitmap/image_encoder"]
bitmap_gif = ["plotters-bitmap/gif_backend"]
svg_backend = ["plotters-svg"]
Expand All @@ -99,6 +102,8 @@ ttf = ["font-kit", "ttf-parser", "lazy_static", "pathfinder_geometry"]
# Can be useful for cross compiling, especially considering fontconfig has lots of C dependencies
fontconfig-dlopen = ["font-kit/source-fontconfig-dlopen"]

ab_glyph = ["dep:ab_glyph", "once_cell"]

# Misc
datetime = ["chrono"]
evcxr = ["svg_backend"]
Expand Down
156 changes: 156 additions & 0 deletions plotters/src/style/font/ab_glyph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use super::{FontData, FontFamily, FontStyle, LayoutBox};
use ab_glyph::{Font, FontRef, ScaleFont};
use core::fmt::{self, Display};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::error::Error;
use std::sync::RwLock;

struct FontMap {
map: HashMap<String, FontRef<'static>>,
}
impl FontMap {
fn new() -> Self {
Self {
map: HashMap::with_capacity(4),
}
}
fn insert(&mut self, style: FontStyle, font: FontRef<'static>) -> Option<FontRef<'static>> {
self.map.insert(style.as_str().to_string(), font)
}
// fn get(&self, style: FontStyle) -> Option<&FontRef<'static>> {
// self.map.get(style.as_str())
// }
fn get_fallback(&self, style: FontStyle) -> Option<&FontRef<'static>> {
self.map
.get(style.as_str())
.or_else(|| self.map.get(FontStyle::Normal.as_str()))
}
}

static FONTS: Lazy<RwLock<HashMap<String, FontMap>>> = Lazy::new(|| RwLock::new(HashMap::new()));
pub struct InvalidFont {
_priv: (),
}

// Note for future contributors: There is nothing fundamental about the static reference requirement here.
// It would be reasonably easy to add a function which accepts an owned buffer,
// or even a reference counted buffer, instead.
/// Register a font in the fonts table.
///
/// The `name` parameter gives the name this font shall be referred to
/// in the other APIs, like `"sans-serif"`.
///
/// The `bytes` parameter should be the complete contents
/// of an OpenType font file, like:
/// ```ignore
/// include_bytes!("FiraGO-Regular.otf")
/// ```
pub fn register_font(
name: &str,
style: FontStyle,
bytes: &'static [u8],
) -> Result<(), InvalidFont> {
let font = FontRef::try_from_slice(bytes).map_err(|_| InvalidFont { _priv: () })?;
let mut lock = FONTS.write().unwrap();
lock.entry(name.to_string())
.or_insert_with(FontMap::new)
.insert(style, font);
Ok(())
}

#[derive(Clone)]
pub struct FontDataInternal {
font_ref: FontRef<'static>,
}

#[derive(Debug, Clone)]
pub enum FontError {
/// No idea what the problem is
Unknown,
/// No font data available for the requested family and style.
FontUnavailable,
}
impl Display for FontError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Since it makes literally no difference to how we'd format
// this, just delegate to the derived Debug formatter.
write!(f, "{:?}", self)
}
}
impl Error for FontError {}

impl FontData for FontDataInternal {
// TODO: can we rename this to `Error`?
type ErrorType = FontError;
fn new(family: FontFamily<'_>, style: FontStyle) -> Result<Self, Self::ErrorType> {
Ok(Self {
font_ref: FONTS
.read()
.unwrap()
.get(family.as_str())
.and_then(|fam| fam.get_fallback(style))
.ok_or(FontError::FontUnavailable)?
.clone(),
})
}
// TODO: ngl, it makes no sense that this uses the same error type as `new`
fn estimate_layout(&self, size: f64, text: &str) -> Result<LayoutBox, Self::ErrorType> {
let pixel_per_em = size / 1.24;
// let units_per_em = self.font_ref.units_per_em().unwrap();
let font = self.font_ref.as_scaled(size as f32);

let mut x_pixels = 0f32;

let mut prev = None;
for c in text.chars() {
let glyph_id = font.glyph_id(c);
let size = font.h_advance(glyph_id);
x_pixels += size;
if let Some(pc) = prev {
x_pixels += font.kern(pc, glyph_id);
}
prev = Some(glyph_id);
}

Ok(((0, 0), (x_pixels as i32, pixel_per_em as i32)))
}
fn draw<E, DrawFunc: FnMut(i32, i32, f32) -> Result<(), E>>(
&self,
pos: (i32, i32),
size: f64,
text: &str,
mut draw: DrawFunc,
) -> Result<Result<(), E>, Self::ErrorType> {
let font = self.font_ref.as_scaled(size as f32);
let mut draw = |x: i32, y: i32, c| {
let (base_x, base_y) = pos;
draw(base_x + x, base_y + y, c)
};
let mut x_shift = 0f32;
let mut prev = None;
for c in text.chars() {
if let Some(pc) = prev {
x_shift += font.kern(font.glyph_id(pc), font.glyph_id(c));
}
prev = Some(c);
let glyph = font.scaled_glyph(c);
if let Some(q) = font.outline_glyph(glyph) {
let rect = q.px_bounds();
let y_shift = ((size as f32) / 2.0 + rect.min.y) as i32;
let x_shift = x_shift as i32;
let mut buf = vec![];
q.draw(|x, y, c| buf.push((x, y, c)));
for (x, y, c) in buf {
draw(x as i32 + x_shift, y as i32 + y_shift, c).map_err(|_e| {
// Note: If ever `plotters` adds a tracing or logging crate,
// this would be a good place to use it.
FontError::Unknown
})?;
}
}
x_shift += font.h_advance(font.glyph_id(c));
}
Ok(Ok(()))
}
}
18 changes: 16 additions & 2 deletions plotters/src/style/font/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,28 @@ mod ttf;
))]
use ttf::FontDataInternal;

#[cfg(all(not(target_arch = "wasm32"), not(target_os = "wasi"),
feature = "ab_glyph"))]
mod ab_glyph;
#[cfg(all(
not(target_arch = "wasm32"), not(target_os = "wasi"),
feature = "ab_glyph", not(feature = "ttf")
))]
use self::ab_glyph::FontDataInternal;
#[cfg(all(
not(target_arch = "wasm32"), not(target_os = "wasi"),
feature = "ab_glyph"
))]
pub use self::ab_glyph::register_font;

#[cfg(all(
not(all(target_arch = "wasm32", not(target_os = "wasi"))),
not(feature = "ttf")
not(feature = "ttf"), not(feature = "ab_glyph")
))]
mod naive;
#[cfg(all(
not(all(target_arch = "wasm32", not(target_os = "wasi"))),
not(feature = "ttf")
not(feature = "ttf"), not(feature = "ab_glyph")
))]
use naive::FontDataInternal;

Expand Down
3 changes: 3 additions & 0 deletions plotters/src/style/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub use colors::full_palette;
pub use font::{
FontDesc, FontError, FontFamily, FontResult, FontStyle, FontTransform, IntoFont, LayoutBox,
};
#[cfg(all(not(target_arch = "wasm32"), feature = "ab_glyph"))]
pub use font::register_font;

pub use shape::ShapeStyle;
pub use size::{AsRelative, RelativeSize, SizeDesc};
pub use text::text_anchor;
Expand Down

0 comments on commit a4dab40

Please sign in to comment.