mod outline;
use crate::geometry::Transform;
use crate::path::{Path, PathBuilder, PathSegment};
use outline::OutlineSink;
use core::fmt;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
const NOTDEF: ttf_parser::GlyphId = ttf_parser::GlyphId(0);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FontError {
Parse(ttf_parser::FaceParsingError),
}
impl fmt::Display for FontError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FontError::Parse(e) => write!(f, "failed to parse font: {e}"),
}
}
}
impl std::error::Error for FontError {}
impl From<ttf_parser::FaceParsingError> for FontError {
fn from(e: ttf_parser::FaceParsingError) -> Self {
FontError::Parse(e)
}
}
pub struct Font<'a> {
face: ttf_parser::Face<'a>,
glyph_cache: RefCell<HashMap<ttf_parser::GlyphId, Option<Arc<[PathSegment]>>>>,
}
impl<'a> Font<'a> {
pub fn from_slice(data: &'a [u8]) -> Result<Self, FontError> {
Self::from_collection(data, 0)
}
pub fn from_collection(data: &'a [u8], index: u32) -> Result<Self, FontError> {
let face = ttf_parser::Face::parse(data, index)?;
Ok(Font { face, glyph_cache: RefCell::new(HashMap::new()) })
}
fn glyph_outline(&self, gid: ttf_parser::GlyphId) -> Option<Arc<[PathSegment]>> {
if let Some(cached) = self.glyph_cache.borrow().get(&gid) {
return cached.clone();
}
let mut builder = PathBuilder::new();
let mut sink = OutlineSink::new(&mut builder, 1.0, 0.0, 0.0);
let outline = match self.face.outline_glyph(gid, &mut sink) {
Some(_) => builder.finish().map(|p| Arc::from(p.segments())),
None => None,
};
self.glyph_cache.borrow_mut().insert(gid, outline.clone());
outline
}
#[inline]
pub fn units_per_em(&self) -> u16 {
self.face.units_per_em()
}
#[inline]
fn scale(&self, size: f32) -> f32 {
match self.units_per_em() {
0 => 0.0,
upem => size / upem as f32,
}
}
pub fn ascent(&self, size: f32) -> f32 {
self.face.ascender() as f32 * self.scale(size)
}
pub fn descent(&self, size: f32) -> f32 {
-self.face.descender() as f32 * self.scale(size)
}
pub fn line_height(&self, size: f32) -> f32 {
self.face.height() as f32 * self.scale(size)
}
pub fn advance_width(&self, ch: char, size: f32) -> f32 {
let gid = self.face.glyph_index(ch).unwrap_or(NOTDEF);
let advance = self.face.glyph_hor_advance(gid).unwrap_or(0);
advance as f32 * self.scale(size)
}
pub fn measure(&self, text: &str, size: f32) -> f32 {
let mut widest = 0.0f32;
let mut current = 0.0f32;
for ch in text.chars() {
if ch == '\n' {
widest = widest.max(current);
current = 0.0;
} else {
current += self.advance_width(ch, size);
}
}
widest.max(current)
}
pub fn glyph_path(&self, ch: char, size: f32, x: f32, y: f32) -> Option<Path> {
let gid = self.face.glyph_index(ch)?;
let outline = self.glyph_outline(gid)?;
let mut builder = PathBuilder::new();
builder.push_path_transformed(&outline, &placement(self.scale(size), x, y));
builder.finish()
}
pub fn text_path(&self, text: &str, size: f32, x: f32, y: f32) -> Option<Path> {
let scale = self.scale(size);
let line_height = self.line_height(size);
let mut builder = PathBuilder::new();
let mut pen_x = x;
let mut baseline = y;
for ch in text.chars() {
if ch == '\n' {
pen_x = x;
baseline += line_height;
continue;
}
let gid = self.face.glyph_index(ch).unwrap_or(NOTDEF);
if let Some(outline) = self.glyph_outline(gid) {
builder.push_path_transformed(&outline, &placement(scale, pen_x, baseline));
}
let advance = self.face.glyph_hor_advance(gid).unwrap_or(0);
pen_x += advance as f32 * scale;
}
builder.finish()
}
}
#[inline]
fn placement(scale: f32, origin_x: f32, baseline_y: f32) -> Transform {
Transform::from_row(scale, 0.0, 0.0, scale, origin_x, baseline_y)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn font_is_send() {
fn assert_send<T: Send>() {}
assert_send::<Font<'static>>();
}
#[test]
fn invalid_data_fails_to_parse() {
match Font::from_slice(&[0u8; 16]) {
Ok(_) => panic!("garbage bytes must not parse as a font"),
Err(err) => {
assert!(matches!(err, FontError::Parse(_)));
assert!(!err.to_string().is_empty());
}
}
}
}