use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use denise::Size;
use skrifa::instance::LocationRef;
use skrifa::outline::{DrawSettings, OutlinePen};
use skrifa::raw::TableProvider;
use skrifa::{FontRef, MetadataProvider};
use crate::fill::{Bounds, Outline};
use crate::source::{FontMetrics, GlyphId, GlyphMetrics, GlyphSource, Rasterised};
enum Face {
Owned(Vec<u8>),
Static(&'static [u8]),
}
pub struct TrueTypeSource {
name: String,
face: Face,
ascii: [u32; 128],
outline: Outline,
scratch: Vec<u8>,
}
impl Face {
fn bytes(&self) -> &[u8] {
match self {
Face::Owned(data) => data,
Face::Static(data) => data,
}
}
}
fn parse(data: &[u8]) -> Result<FontRef<'_>, String> {
FontRef::from_index(data, 0).map_err(|e| e.to_string())
}
impl TrueTypeSource {
pub fn from_bytes(name: &str, data: &[u8]) -> Result<Self, String> {
Self::from_vec(name, data.to_vec())
}
pub fn from_vec(name: &str, data: Vec<u8>) -> Result<Self, String> {
Self::with(name, Face::Owned(data))
}
pub fn from_static(name: &str, data: &'static [u8]) -> Result<Self, String> {
Self::with(name, Face::Static(data))
}
fn with(name: &str, face: Face) -> Result<Self, String> {
let font = parse(face.bytes())?;
font.head().map_err(|e| e.to_string())?;
font.maxp().map_err(|e| e.to_string())?;
let charmap = font.charmap();
let mut ascii = [0; 128];
for (code, glyph) in (0u8..).zip(&mut ascii) {
*glyph = charmap.map(code).map_or(0, skrifa::GlyphId::to_u32);
}
Ok(Self {
name: name.to_owned(),
face,
ascii,
outline: Outline::default(),
scratch: Vec::new(),
})
}
fn font(&self) -> Option<FontRef<'_>> {
parse(self.face.bytes()).ok()
}
fn lookup(&self, ch: char) -> u32 {
if let Some(&glyph) = self.ascii.get(ch as usize) {
return glyph;
}
self.font()
.and_then(|font| font.charmap().map(ch))
.map_or(0, skrifa::GlyphId::to_u32)
}
fn outline(&mut self, glyph: GlyphId, size_px: u16) -> Option<(i32, Option<Bounds>)> {
let font = parse(self.face.bytes()).ok()?;
let id = skrifa::GlyphId::new(glyph.0);
let size = skrifa::instance::Size::new(f32::from(size_px));
let advance = font
.glyph_metrics(size, LocationRef::default())
.advance_width(id)?;
self.outline.clear();
if let Some(glyph) = font.outline_glyphs().get(id) {
let settings = DrawSettings::unhinted(size, LocationRef::default());
if glyph.draw(settings, &mut Pen(&mut self.outline)).is_err() {
self.outline.clear();
}
self.outline.close();
}
Some((round(advance), self.outline.bounds()))
}
}
struct Pen<'a>(&'a mut Outline);
impl OutlinePen for Pen<'_> {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to(x, y);
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to(x, y);
}
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.0.quad_to(cx0, cy0, x, y);
}
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.0.curve_to(cx0, cy0, cx1, cy1, x, y);
}
fn close(&mut self) {
self.0.close();
}
}
fn round(value: f32) -> i32 {
if value < 0.0 {
(value - 0.5) as i32
} else {
(value + 0.5) as i32
}
}
fn convert(advance: i32, bounds: Option<Bounds>) -> GlyphMetrics {
let Some(bounds) = bounds else {
return GlyphMetrics {
advance,
bearing_x: 0,
bearing_y: 0,
size: Size::new(0, 0),
};
};
GlyphMetrics {
advance,
bearing_x: bounds.x,
bearing_y: -bounds.y,
size: Size::new(bounds.width, bounds.height),
}
}
impl GlyphSource for TrueTypeSource {
fn name(&self) -> &str {
&self.name
}
fn metrics(&self, size_px: u16) -> FontMetrics {
let size = skrifa::instance::Size::new(f32::from(size_px));
let metrics = self
.font()
.map(|font| font.metrics(size, LocationRef::default()));
match metrics {
Some(metrics) if metrics.ascent != 0.0 || metrics.descent != 0.0 => FontMetrics {
ascent: round(metrics.ascent),
descent: round(-metrics.descent),
line_gap: round(metrics.leading),
},
_ => FontMetrics {
ascent: i32::from(size_px) * 4 / 5,
descent: i32::from(size_px) / 5,
line_gap: i32::from(size_px) / 8,
},
}
}
fn glyph_id(&self, ch: char) -> Option<GlyphId> {
Some(GlyphId(self.lookup(ch)))
}
fn glyph_metrics(&mut self, glyph: GlyphId, size_px: u16) -> Option<GlyphMetrics> {
let (advance, bounds) = self.outline(glyph, size_px)?;
Some(convert(advance, bounds))
}
fn rasterise(&mut self, glyph: GlyphId, size_px: u16) -> Option<Rasterised<'_>> {
let (advance, bounds) = self.outline(glyph, size_px)?;
match bounds {
Some(bounds) => self.outline.fill(bounds, &mut self.scratch),
None => self.scratch.clear(),
}
let metrics = convert(advance, bounds);
Some(Rasterised {
metrics,
coverage: &self.scratch,
stride: metrics.size.width as usize,
})
}
fn contains(&self, ch: char) -> bool {
self.lookup(ch) != 0
}
fn fallback_id(&self, _ch: char) -> Option<GlyphId> {
Some(GlyphId(0))
}
}
impl core::fmt::Debug for TrueTypeSource {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TrueTypeSource")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_file_that_is_not_a_font_fails_with_a_reason() {
let error = TrueTypeSource::from_bytes("junk", b"not a font at all")
.expect_err("that is not a font");
assert!(!error.is_empty(), "the failure has to say something");
}
#[cfg(feature = "std")]
fn system_face() -> Option<TrueTypeSource> {
[
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
"/System/Library/Fonts/Supplemental/Arial.ttf",
"C:\\Windows\\Fonts\\arial.ttf",
]
.iter()
.find_map(|path| std::fs::read(path).ok())
.map(|bytes| TrueTypeSource::from_vec("system", bytes).expect("a font"))
}
#[cfg(feature = "std")]
#[test]
fn a_descender_hangs_below_the_baseline_and_a_cap_stands_on_it() {
let Some(mut face) = system_face() else {
return;
};
let size = 20;
let line = face.metrics(size);
let g = face.glyph_id('g').expect("g");
let g = face.glyph_metrics(g, size).expect("metrics");
assert!(
g.bearing_y > 0 && g.bearing_y < g.size.height as i32,
"{g:?}"
);
let cap = face.glyph_id('H').expect("H");
let cap = face.glyph_metrics(cap, size).expect("metrics");
assert_eq!(cap.bearing_y, cap.size.height as i32, "{cap:?}");
assert!(cap.bearing_y <= line.ascent && cap.bearing_y > size as i32 / 2);
assert!(line.descent > 0 && line.ascent + line.descent >= size as i32);
}
#[cfg(feature = "std")]
#[test]
fn a_space_advances_without_ink_and_a_letter_is_drawn_to_its_size() {
let Some(mut face) = system_face() else {
return;
};
let space = face.glyph_id(' ').expect("space");
let space = face.rasterise(space, 16).expect("space");
assert!(space.metrics.advance > 0);
assert!(space.coverage.is_empty());
let m = face.glyph_id('M').expect("M");
let m = face.rasterise(m, 16).expect("M");
let size = m.metrics.size;
assert_eq!(m.coverage.len(), (size.width * size.height) as usize);
assert_eq!(m.stride, size.width as usize);
assert!(m.coverage.contains(&255), "a stem is solid somewhere");
}
#[cfg(feature = "std")]
fn variable_face() -> Option<TrueTypeSource> {
[
"/System/Library/Fonts/SFNS.ttf",
"C:\\Windows\\Fonts\\bahnschrift.ttf",
]
.iter()
.find_map(|path| std::fs::read(path).ok())
.map(|bytes| TrueTypeSource::from_vec("variable", bytes).expect("a font"))
}
#[cfg(feature = "std")]
#[test]
fn a_variable_face_has_ink_in_it() {
let Some(mut face) = variable_face() else {
return;
};
let a = face.glyph_id('a').expect("a");
let a = face.rasterise(a, 16).expect("an outline");
assert!(!a.metrics.is_blank(), "no mask at all: {:?}", a.metrics);
assert!(a.coverage.iter().any(|&ink| ink > 0), "no ink in the mask");
}
#[cfg(feature = "std")]
#[test]
fn a_damaged_face_is_refused_or_drawn_wrong_and_never_panics() {
let Some(bytes) = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
"/System/Library/Fonts/Supplemental/Arial.ttf",
"C:\\Windows\\Fonts\\arial.ttf",
]
.iter()
.find_map(|path| std::fs::read(path).ok()) else {
return;
};
let mut seed = 0x2545_f491_4f6c_dd1d_u64;
let mut next = move || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
let mut loaded = 0;
for round in 0..400 {
let mut damaged = bytes.clone();
if round % 4 == 0 {
damaged.truncate(next() as usize % bytes.len());
}
for _ in 0..1 + next() % 64 {
if !damaged.is_empty() {
let reach = if round % 2 == 0 {
damaged.len().min(4096)
} else {
damaged.len()
};
let at = next() as usize % reach;
damaged[at] = next() as u8;
}
}
let Ok(mut face) = TrueTypeSource::from_vec("damaged", damaged) else {
continue;
};
loaded += 1;
let _ = face.metrics(16);
for ch in ['a', 'g', '@', 'Ω', '\u{10FFFD}'] {
let id = face.glyph_id(ch).expect("always some glyph");
let _ = face.glyph_metrics(id, 16);
if let Some(drawn) = face.rasterise(id, 48) {
let size = drawn.metrics.size;
assert_eq!(drawn.coverage.len(), (size.width * size.height) as usize);
}
}
let _ = face.rasterise(GlyphId(u32::MAX), 16);
}
assert!(
loaded > 0,
"every damaged face was refused, so nothing was tested"
);
}
#[cfg(feature = "std")]
#[test]
fn a_glyph_the_face_does_not_have_is_the_box() {
let Some(face) = system_face() else {
return;
};
assert!(!face.contains('\u{10FFFD}'));
assert!(face.contains('a'));
}
}