diff --git a/plotters/Cargo.toml b/plotters/Cargo.toml index 88b84c38..8c90275c 100644 --- a/plotters/Cargo.toml +++ b/plotters/Cargo.toml @@ -32,7 +32,11 @@ optional = true 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.2" @@ -96,6 +100,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_ = ["ab_glyph", "once_cell"] + # Misc datetime = ["chrono"] evcxr = ["svg_backend"] diff --git a/plotters/src/style/font/ab_glyph.rs b/plotters/src/style/font/ab_glyph.rs new file mode 100644 index 00000000..3b8bf7bc --- /dev/null +++ b/plotters/src/style/font/ab_glyph.rs @@ -0,0 +1,122 @@ +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; + +static FONTS: Lazy>>> = + Lazy::new(|| RwLock::new(HashMap::new())); +pub struct InvalidFont { + _priv: (), +} + +/// Register a font in the fonts table. +pub fn register_font(name: &str, bytes: &'static [u8]) -> Result<(), InvalidFont> { + let font = FontRef::try_from_slice(bytes).map_err(|_| InvalidFont { _priv: () })?; + let mut lock = FONTS.write().unwrap(); + lock.insert(name.to_string(), 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 { + Ok(Self { + font_ref: FONTS + .read() + .unwrap() + .get(family.as_str()) + .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 { + 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 Result<(), E>>( + &self, + pos: (i32, i32), + size: f64, + text: &str, + mut draw: DrawFunc, + ) -> Result, Self::ErrorType> { + let font = self.font_ref.as_scaled(size as f32); + let mut draw = |x: u32, y: u32, c| { + let (x, y) = (x as i32, y as i32); + 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) { + use ::std::panic::{self, AssertUnwindSafe}; + let rect = q.px_bounds(); + // Vertically center the things. + let y_shift = (size as f32 - rect.height()) / 2.0; + let y_shift = y_shift as u32; + let res = panic::catch_unwind(AssertUnwindSafe(|| { + q.draw(|x, y, c| { + if let Err(_) = draw(x + (x_shift as u32), y + y_shift, c) { + panic!("fail") + } + }); + })); + if let Err(_) = res { + return Err(FontError::Unknown); + } + x_shift += font.h_advance(font.glyph_id(c)); + } else { + x_shift += font.h_advance(font.glyph_id(c)); + } + } + Ok(Ok(())) + } +} diff --git a/plotters/src/style/font/mod.rs b/plotters/src/style/font/mod.rs index 305978fd..efec6b2b 100644 --- a/plotters/src/style/font/mod.rs +++ b/plotters/src/style/font/mod.rs @@ -8,23 +8,41 @@ #[cfg(all( not(all(target_arch = "wasm32", not(target_os = "wasi"))), - feature = "ttf" + all(feature = "ttf", not(feature = "ab_glyph_")) ))] mod ttf; #[cfg(all( not(all(target_arch = "wasm32", not(target_os = "wasi"))), - feature = "ttf" + all(feature = "ttf", not(feature = "ab_glyph_")) ))] use ttf::FontDataInternal; #[cfg(all( not(all(target_arch = "wasm32", not(target_os = "wasi"))), - not(feature = "ttf") + feature = "ab_glyph_" +))] +mod ab_glyph; +#[cfg(all( + not(all(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"))), + feature = "ab_glyph_" +))] +use self::ab_glyph::FontDataInternal; + +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + 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; diff --git a/plotters/src/style/mod.rs b/plotters/src/style/mod.rs index 7d7c9ac3..7a74d433 100644 --- a/plotters/src/style/mod.rs +++ b/plotters/src/style/mod.rs @@ -20,6 +20,14 @@ pub use colors::full_palette; pub use font::{ FontDesc, FontError, FontFamily, FontResult, FontStyle, FontTransform, IntoFont, LayoutBox, }; + +#[cfg(all( + not(target_arch = "wasm32"), + not(target_os = "wasi"), + feature = "ab_glyph_" +))] +pub use font::register_font; + pub use shape::ShapeStyle; pub use size::{AsRelative, RelativeSize, SizeDesc}; pub use text::text_anchor;

AltStyle によって変換されたページ (->オリジナル) /