Skip to content

Commit caca096

Browse files
committed
preliminary work to add ab_glyph font backend
1 parent 7aeb1ba commit caca096

File tree

4 files changed

+142
-2
lines changed

4 files changed

+142
-2
lines changed

plotters/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ ttf-parser = { version = "0.15.0", optional = true }
3434
lazy_static = { version = "1.4.0", optional = true }
3535
pathfinder_geometry = { version = "0.5.1", optional = true }
3636
font-kit = { version = "0.11.0", optional = true }
37+
ab_glyph = { version = "0.2.12", optional = true }
38+
once_cell = { version = "1.8.0", optional = true }
39+
3740

3841
[target.'cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))'.dependencies.image]
3942
version = "0.24.3"
@@ -97,6 +100,8 @@ ttf = ["font-kit", "ttf-parser", "lazy_static", "pathfinder_geometry"]
97100
# Can be useful for cross compiling, especially considering fontconfig has lots of C dependencies
98101
fontconfig-dlopen = ["font-kit/source-fontconfig-dlopen"]
99102

103+
ab_glyph_ = ["ab_glyph", "once_cell"]
104+
100105
# Misc
101106
datetime = ["chrono"]
102107
evcxr = ["svg_backend"]

plotters/src/style/font/ab_glyph.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use super::{FontData, FontFamily, FontStyle, LayoutBox};
2+
use ::core::fmt::{self, Display};
3+
use ::std::error::Error;
4+
use ::std::collections::HashMap;
5+
use ::std::sync::RwLock;
6+
use ::once_cell::sync::Lazy;
7+
use ::ab_glyph::{FontRef, Font, ScaleFont};
8+
9+
static FONTS: Lazy<RwLock<HashMap<String, FontRef<'static>>>> = Lazy::new(|| RwLock::new(HashMap::new()));
10+
pub struct InvalidFont {
11+
_priv: (),
12+
}
13+
14+
/// Register a font in the fonts table.
15+
pub fn register_font(name: &str, bytes: &'static [u8]) -> Result<(), InvalidFont> {
16+
let font = FontRef::try_from_slice(bytes).map_err(|_| InvalidFont { _priv: () })?;
17+
let mut lock = FONTS.write().unwrap();
18+
lock.insert(name.to_string(), font);
19+
Ok(())
20+
}
21+
22+
#[derive(Clone)]
23+
pub struct FontDataInternal {
24+
font_ref: FontRef<'static>
25+
}
26+
27+
#[derive(Debug, Clone)]
28+
pub enum FontError {
29+
/// No idea what the problem is
30+
Unknown,
31+
/// No font data available for the requested family and style.
32+
FontUnavailable,
33+
}
34+
impl Display for FontError {
35+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36+
// Since it makes literally no difference to how we'd format
37+
// this, just delegate to the derived Debug formatter.
38+
write!(f, "{:?}", self)
39+
}
40+
}
41+
impl Error for FontError {}
42+
43+
impl FontData for FontDataInternal {
44+
// TODO: can we rename this to `Error`?
45+
type ErrorType = FontError;
46+
fn new(family: FontFamily<'_>, style: FontStyle) -> Result<Self, Self::ErrorType> {
47+
Ok(Self {
48+
font_ref: FONTS.read().unwrap().get(family.as_str()).ok_or(FontError::FontUnavailable)?.clone()
49+
})
50+
}
51+
// TODO: ngl, it makes no sense that this uses the same error type as `new`
52+
fn estimate_layout(&self, size: f64, text: &str) -> Result<LayoutBox, Self::ErrorType> {
53+
let pixel_per_em = size / 1.24;
54+
let units_per_em = self.font_ref.units_per_em().unwrap();
55+
let font = self.font_ref.as_scaled(size as f32);
56+
57+
let mut x_pixels = 0f32;
58+
59+
let mut prev = None;
60+
for c in text.chars() {
61+
let glyph_id = font.glyph_id(c);
62+
let size = font.h_advance(glyph_id);
63+
x_pixels += size;
64+
if let Some(pc) = prev {
65+
x_pixels += font.kern(pc, glyph_id);
66+
}
67+
prev = Some(glyph_id);
68+
}
69+
70+
Ok(((0, 0), (x_pixels as i32, pixel_per_em as i32)))
71+
}
72+
fn draw<E, DrawFunc: FnMut(i32, i32, f32) -> Result<(), E>>(
73+
&self,
74+
pos: (i32, i32),
75+
size: f64,
76+
text: &str,
77+
mut draw: DrawFunc,
78+
) -> Result<Result<(), E>, Self::ErrorType> {
79+
let font = self.font_ref.as_scaled(size as f32);
80+
let mut draw = |x: u32, y: u32, c| {
81+
let (x, y) = (x as i32, y as i32);
82+
let (base_x, base_y) = pos;
83+
draw(base_x + x, base_y + y, c)
84+
};
85+
let mut x_shift = 0f32;
86+
let mut prev = None;
87+
for c in text.chars() {
88+
if let Some(pc) = prev {
89+
x_shift += font.kern(font.glyph_id(pc), font.glyph_id(c));
90+
}
91+
prev = Some(c);
92+
let glyph = font.scaled_glyph(c);
93+
if let Some(q) = font.outline_glyph(glyph) {
94+
use ::std::panic::{self, AssertUnwindSafe};
95+
let rect = q.px_bounds();
96+
// Vertically center the things.
97+
let y_shift = (size as f32 - rect.height()) / 2.0;
98+
let y_shift = y_shift as u32;
99+
let res = panic::catch_unwind(AssertUnwindSafe(|| {
100+
q.draw(|x, y, c| {
101+
if let Err(_) = draw(x + (x_shift as u32), y + y_shift, c) {
102+
panic!("fail")
103+
}
104+
});
105+
}));
106+
if let Err(_) = res {
107+
return Err(FontError::Unknown)
108+
}
109+
x_shift += font.h_advance(font.glyph_id(c));
110+
} else {
111+
x_shift += font.h_advance(font.glyph_id(c));
112+
}
113+
}
114+
Ok(Ok(()))
115+
}
116+
}

plotters/src/style/font/mod.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,30 @@ 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_"
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+
34+
#[cfg(all(not(target_arch = "wasm32"), not(feature = "ttf"), not(feature = "ab_glyph_")))]
35+
mod naive;
2036
#[cfg(all(
2137
not(all(target_arch = "wasm32", not(target_os = "wasi"))),
22-
not(feature = "ttf")
38+
not(feature = "ttf"), not(feature = "ab_glyph_")
2339
))]
2440
mod naive;
2541
#[cfg(all(
2642
not(all(target_arch = "wasm32", not(target_os = "wasi"))),
27-
not(feature = "ttf")
43+
not(feature = "ttf"), not(feature = "ab_glyph_")
2844
))]
2945
use naive::FontDataInternal;
3046

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)