|
| 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 | +} |
0 commit comments