Skip to content

Commit a4dab40

Browse files
authored
Merge pull request #311 from Monadic-Cat/feature-ab_glyph
Add `ab_glyph` font backend
2 parents 0e3332a + 609a557 commit a4dab40

File tree

4 files changed

+181
-3
lines changed

4 files changed

+181
-3
lines changed

plotters/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ ttf-parser = { version = "0.15.0", optional = true }
3636
lazy_static = { version = "1.4.0", optional = true }
3737
pathfinder_geometry = { version = "0.5.1", optional = true }
3838
font-kit = { version = "0.11.0", optional = true }
39+
ab_glyph = { version = "0.2.12", optional = true }
40+
once_cell = { version = "1.8.0", optional = true }
41+
3942

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

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

105+
ab_glyph = ["dep:ab_glyph", "once_cell"]
106+
102107
# Misc
103108
datetime = ["chrono"]
104109
evcxr = ["svg_backend"]

plotters/src/style/font/ab_glyph.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use super::{FontData, FontFamily, FontStyle, LayoutBox};
2+
use ab_glyph::{Font, FontRef, ScaleFont};
3+
use core::fmt::{self, Display};
4+
use once_cell::sync::Lazy;
5+
use std::collections::HashMap;
6+
use std::error::Error;
7+
use std::sync::RwLock;
8+
9+
struct FontMap {
10+
map: HashMap<String, FontRef<'static>>,
11+
}
12+
impl FontMap {
13+
fn new() -> Self {
14+
Self {
15+
map: HashMap::with_capacity(4),
16+
}
17+
}
18+
fn insert(&mut self, style: FontStyle, font: FontRef<'static>) -> Option<FontRef<'static>> {
19+
self.map.insert(style.as_str().to_string(), font)
20+
}
21+
// fn get(&self, style: FontStyle) -> Option<&FontRef<'static>> {
22+
// self.map.get(style.as_str())
23+
// }
24+
fn get_fallback(&self, style: FontStyle) -> Option<&FontRef<'static>> {
25+
self.map
26+
.get(style.as_str())
27+
.or_else(|| self.map.get(FontStyle::Normal.as_str()))
28+
}
29+
}
30+
31+
static FONTS: Lazy<RwLock<HashMap<String, FontMap>>> = Lazy::new(|| RwLock::new(HashMap::new()));
32+
pub struct InvalidFont {
33+
_priv: (),
34+
}
35+
36+
// Note for future contributors: There is nothing fundamental about the static reference requirement here.
37+
// It would be reasonably easy to add a function which accepts an owned buffer,
38+
// or even a reference counted buffer, instead.
39+
/// Register a font in the fonts table.
40+
///
41+
/// The `name` parameter gives the name this font shall be referred to
42+
/// in the other APIs, like `"sans-serif"`.
43+
///
44+
/// The `bytes` parameter should be the complete contents
45+
/// of an OpenType font file, like:
46+
/// ```ignore
47+
/// include_bytes!("FiraGO-Regular.otf")
48+
/// ```
49+
pub fn register_font(
50+
name: &str,
51+
style: FontStyle,
52+
bytes: &'static [u8],
53+
) -> Result<(), InvalidFont> {
54+
let font = FontRef::try_from_slice(bytes).map_err(|_| InvalidFont { _priv: () })?;
55+
let mut lock = FONTS.write().unwrap();
56+
lock.entry(name.to_string())
57+
.or_insert_with(FontMap::new)
58+
.insert(style, font);
59+
Ok(())
60+
}
61+
62+
#[derive(Clone)]
63+
pub struct FontDataInternal {
64+
font_ref: FontRef<'static>,
65+
}
66+
67+
#[derive(Debug, Clone)]
68+
pub enum FontError {
69+
/// No idea what the problem is
70+
Unknown,
71+
/// No font data available for the requested family and style.
72+
FontUnavailable,
73+
}
74+
impl Display for FontError {
75+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76+
// Since it makes literally no difference to how we'd format
77+
// this, just delegate to the derived Debug formatter.
78+
write!(f, "{:?}", self)
79+
}
80+
}
81+
impl Error for FontError {}
82+
83+
impl FontData for FontDataInternal {
84+
// TODO: can we rename this to `Error`?
85+
type ErrorType = FontError;
86+
fn new(family: FontFamily<'_>, style: FontStyle) -> Result<Self, Self::ErrorType> {
87+
Ok(Self {
88+
font_ref: FONTS
89+
.read()
90+
.unwrap()
91+
.get(family.as_str())
92+
.and_then(|fam| fam.get_fallback(style))
93+
.ok_or(FontError::FontUnavailable)?
94+
.clone(),
95+
})
96+
}
97+
// TODO: ngl, it makes no sense that this uses the same error type as `new`
98+
fn estimate_layout(&self, size: f64, text: &str) -> Result<LayoutBox, Self::ErrorType> {
99+
let pixel_per_em = size / 1.24;
100+
// let units_per_em = self.font_ref.units_per_em().unwrap();
101+
let font = self.font_ref.as_scaled(size as f32);
102+
103+
let mut x_pixels = 0f32;
104+
105+
let mut prev = None;
106+
for c in text.chars() {
107+
let glyph_id = font.glyph_id(c);
108+
let size = font.h_advance(glyph_id);
109+
x_pixels += size;
110+
if let Some(pc) = prev {
111+
x_pixels += font.kern(pc, glyph_id);
112+
}
113+
prev = Some(glyph_id);
114+
}
115+
116+
Ok(((0, 0), (x_pixels as i32, pixel_per_em as i32)))
117+
}
118+
fn draw<E, DrawFunc: FnMut(i32, i32, f32) -> Result<(), E>>(
119+
&self,
120+
pos: (i32, i32),
121+
size: f64,
122+
text: &str,
123+
mut draw: DrawFunc,
124+
) -> Result<Result<(), E>, Self::ErrorType> {
125+
let font = self.font_ref.as_scaled(size as f32);
126+
let mut draw = |x: i32, y: i32, c| {
127+
let (base_x, base_y) = pos;
128+
draw(base_x + x, base_y + y, c)
129+
};
130+
let mut x_shift = 0f32;
131+
let mut prev = None;
132+
for c in text.chars() {
133+
if let Some(pc) = prev {
134+
x_shift += font.kern(font.glyph_id(pc), font.glyph_id(c));
135+
}
136+
prev = Some(c);
137+
let glyph = font.scaled_glyph(c);
138+
if let Some(q) = font.outline_glyph(glyph) {
139+
let rect = q.px_bounds();
140+
let y_shift = ((size as f32) / 2.0 + rect.min.y) as i32;
141+
let x_shift = x_shift as i32;
142+
let mut buf = vec![];
143+
q.draw(|x, y, c| buf.push((x, y, c)));
144+
for (x, y, c) in buf {
145+
draw(x as i32 + x_shift, y as i32 + y_shift, c).map_err(|_e| {
146+
// Note: If ever `plotters` adds a tracing or logging crate,
147+
// this would be a good place to use it.
148+
FontError::Unknown
149+
})?;
150+
}
151+
}
152+
x_shift += font.h_advance(font.glyph_id(c));
153+
}
154+
Ok(Ok(()))
155+
}
156+
}

plotters/src/style/font/mod.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,28 @@ mod ttf;
1717
))]
1818
use ttf::FontDataInternal;
1919

20+
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "wasi"),
21+
feature = "ab_glyph"))]
22+
mod ab_glyph;
23+
#[cfg(all(
24+
not(target_arch = "wasm32"), not(target_os = "wasi"),
25+
feature = "ab_glyph", not(feature = "ttf")
26+
))]
27+
use self::ab_glyph::FontDataInternal;
28+
#[cfg(all(
29+
not(target_arch = "wasm32"), not(target_os = "wasi"),
30+
feature = "ab_glyph"
31+
))]
32+
pub use self::ab_glyph::register_font;
33+
2034
#[cfg(all(
2135
not(all(target_arch = "wasm32", not(target_os = "wasi"))),
22-
not(feature = "ttf")
36+
not(feature = "ttf"), not(feature = "ab_glyph")
2337
))]
2438
mod naive;
2539
#[cfg(all(
2640
not(all(target_arch = "wasm32", not(target_os = "wasi"))),
27-
not(feature = "ttf")
41+
not(feature = "ttf"), not(feature = "ab_glyph")
2842
))]
2943
use naive::FontDataInternal;
3044

plotters/src/style/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub use colors::full_palette;
2020
pub use font::{
2121
FontDesc, FontError, FontFamily, FontResult, FontStyle, FontTransform, IntoFont, LayoutBox,
2222
};
23+
#[cfg(all(not(target_arch = "wasm32"), feature = "ab_glyph"))]
24+
pub use font::register_font;
25+
2326
pub use shape::ShapeStyle;
2427
pub use size::{AsRelative, RelativeSize, SizeDesc};
2528
pub use text::text_anchor;

0 commit comments

Comments
 (0)