use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use ab_glyph::{Font as AbFont, FontRef, FontVec, PxScale, ScaleFont};
use crate::error::{Result, TetraError};
use crate::fs;
use crate::graphics::text::cache::{FontCache, RasterizedGlyph, Rasterizer};
use crate::graphics::text::{Font, FontTextureStyle};
use crate::graphics::Rectangle;
use crate::math::Vec2;
use crate::Context;
pub(crate) struct VectorRasterizer<F> {
font: Rc<F>,
scale: PxScale,
texture_type: FontTextureStyle,
}
impl<F> VectorRasterizer<F>
where
F: AbFont,
{
pub fn new(font: Rc<F>, size: f32, texture_type: FontTextureStyle) -> VectorRasterizer<F> {
let scale_factor = font
.units_per_em()
.map(|units_per_em| font.height_unscaled() / units_per_em)
.unwrap_or(1.0);
let px_size = size * scale_factor;
let scale = PxScale::from(px_size);
VectorRasterizer {
font,
scale,
texture_type,
}
}
}
impl<F> Rasterizer for VectorRasterizer<F>
where
F: AbFont,
{
fn rasterize(&self, ch: char, position: Vec2<f32>) -> Option<RasterizedGlyph> {
let font = self.font.as_scaled(self.scale);
let mut glyph = font.scaled_glyph(ch);
glyph.position = ab_glyph::point(position.x, position.y);
if let Some(outline) = font.outline_glyph(glyph.clone()) {
let mut data = Vec::new();
outline.draw(|_, _, v| {
let coverage = (v * 255.0) as u8;
data.extend_from_slice(&match self.texture_type {
FontTextureStyle::Normal => [255, 255, 255, coverage],
FontTextureStyle::Premultiplied => [coverage, coverage, coverage, coverage],
});
});
let bounds = outline.px_bounds();
Some(RasterizedGlyph {
data,
bounds: Rectangle::new(
bounds.min.x - glyph.position.x,
bounds.min.y - glyph.position.y,
bounds.width(),
bounds.height(),
),
})
} else {
None
}
}
fn advance(&self, glyph: char) -> f32 {
let scaled_font = self.font.as_scaled(self.scale);
scaled_font.h_advance(scaled_font.glyph_id(glyph))
}
fn line_height(&self) -> f32 {
let scaled_font = self.font.as_scaled(self.scale);
scaled_font.height() + scaled_font.line_gap()
}
fn ascent(&self) -> f32 {
let scaled_font = self.font.as_scaled(self.scale);
scaled_font.ascent()
}
fn kerning(&self, previous: char, current: char) -> f32 {
let scaled_font = self.font.as_scaled(self.scale);
scaled_font.kern(
scaled_font.glyph_id(previous),
scaled_font.glyph_id(current),
)
}
}
#[derive(Debug, Clone)]
enum VectorFontData {
Owned(Rc<FontVec>),
Slice(Rc<FontRef<'static>>),
}
#[derive(Debug, Clone)]
pub struct VectorFontBuilder {
data: VectorFontData,
texture_style: FontTextureStyle,
}
impl VectorFontBuilder {
pub fn new<P>(path: P) -> Result<VectorFontBuilder>
where
P: AsRef<Path>,
{
let font_bytes = fs::read(path)?;
let font = FontVec::try_from_vec(font_bytes).map_err(|_| TetraError::InvalidFont)?;
Ok(VectorFontBuilder {
data: VectorFontData::Owned(Rc::new(font)),
texture_style: FontTextureStyle::Normal,
})
}
pub fn from_file_data(data: &'static [u8]) -> Result<VectorFontBuilder> {
let font = FontRef::try_from_slice(data).map_err(|_| TetraError::InvalidFont)?;
Ok(VectorFontBuilder {
data: VectorFontData::Slice(Rc::new(font)),
texture_style: FontTextureStyle::Normal,
})
}
pub fn texture_style(&mut self, texture_style: FontTextureStyle) -> &mut VectorFontBuilder {
self.texture_style = texture_style;
self
}
pub fn with_size(&self, ctx: &mut Context, size: f32) -> Result<Font> {
let rasterizer: Box<dyn Rasterizer> = match &self.data {
VectorFontData::Owned(f) => Box::new(VectorRasterizer::new(
Rc::clone(f),
size,
self.texture_style,
)),
VectorFontData::Slice(f) => Box::new(VectorRasterizer::new(
Rc::clone(f),
size,
self.texture_style,
)),
};
let cache = FontCache::new(
&mut ctx.device,
rasterizer,
ctx.graphics.default_filter_mode,
)?;
Ok(Font {
data: Rc::new(RefCell::new(cache)),
})
}
}