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