use anyhow::{anyhow, Result};
use skrifa::{font::name::NameId, raw::TableProvider, DrawMemory, PathSink, ScalerFont};
use super::{svg_glyph::parse_svg_glyph, FontMetrics, Glyph, GlyphId, PositionedGlyph};
use crate::{shapes::spline::sink::SplineSink, Scale, Spline, V2};
pub(crate) struct OutlineFont {
font: skrifa::Font,
scaler: ScalerFont,
units_per_em: f32,
metrics: FontMetrics,
family: Option<String>,
open_contours: bool,
}
impl OutlineFont {
pub(crate) fn from_bytes(bytes: Vec<u8>, index: u32, open_contours: bool) -> Result<Self> {
let font = skrifa::Font::new(bytes, index)
.ok_or_else(|| anyhow!("not a readable TrueType/OpenType font (index {index})"))?;
Ok(Self::from_font(font, open_contours))
}
pub(crate) fn from_font(font: skrifa::Font, open_contours: bool) -> Self {
let raw = font.metrics();
let units_per_em = match raw.units_per_em {
0 => 1000.0,
upem => f32::from(upem),
};
let line = raw.h_line();
let metrics = FontMetrics {
ascender: line.map_or(units_per_em * 0.8, |line| line.ascender.to_f32()),
descender: line.map_or(-units_per_em * 0.2, |line| line.descender.to_f32()),
line_gap: line.map_or(0.0, |line| line.line_gap.to_f32()),
cap_height: raw.cap_height.map(|v| v.to_f32()),
x_height: raw.x_height.map(|v| v.to_f32()),
};
let family = name(&font, NameId::FAMILY_NAME);
let scaler = ScalerFont::unhinted(font.clone(), None);
Self {
font,
scaler,
units_per_em,
metrics,
family,
open_contours,
}
}
pub(crate) fn open_contours(&self) -> bool {
self.open_contours
}
pub(crate) fn family(&self) -> Option<&str> {
self.family.as_deref()
}
pub(crate) fn units_per_em(&self) -> f32 {
self.units_per_em
}
pub(crate) fn metrics(&self) -> FontMetrics {
self.metrics
}
pub(crate) fn has_glyph(&self, ch: char) -> bool {
self.font.charmap().map_unicode(ch).is_some()
}
pub(crate) fn glyph_id(&self, ch: char) -> GlyphId {
GlyphId(
self.font
.charmap()
.map_unicode(ch)
.map_or(0, |id| id.to_u32()),
)
}
pub(crate) fn shape(&self, text: &str, scale: f32) -> Result<Vec<PositionedGlyph>> {
if text.is_empty() {
return Ok(Vec::new());
}
let shaper = harfrust::ShaperFont::new(&self.font);
let mut buffer = harfrust::Buffer::new();
buffer.push_str(text);
buffer.guess_segment_properties();
harfrust::shape(&shaper, &mut buffer, harfrust::ShapeOptions::default())
.map_err(|error| anyhow!("shaping {text:?} failed: {error}"))?;
Ok(buffer
.glyph_infos()
.iter()
.zip(buffer.glyph_positions())
.map(|(info, position)| PositionedGlyph {
id: GlyphId(info.glyph_id),
offset: V2::new(position.x_offset as f32, position.y_offset as f32) * scale,
advance: position.x_advance as f32 * scale,
})
.collect())
}
pub(crate) fn glyph(&self, id: GlyphId, scale: f32) -> Glyph {
let raw_id = skrifa::GlyphId::new(id.0);
let (contours, drawn_advance) = match self.svg_glyph(raw_id) {
Some(contours) => (contours, None),
None => self.drawn_outline(raw_id),
};
let advance = drawn_advance.unwrap_or_else(|| self.font.glyph_metrics().h_advance(raw_id));
Glyph {
id,
advance: advance * scale,
contours: contours
.iter()
.map(|contour| contour.scale(scale))
.collect(),
}
}
}
impl OutlineFont {
fn svg_glyph(&self, id: skrifa::GlyphId) -> Option<Vec<Spline>> {
let document = self.font.tables().svg().ok()?.glyph_data(id)?;
match parse_svg_glyph(document) {
Ok(contours) => Some(contours),
Err(error) => {
eprintln!("ignoring SVG glyph {}: {error:#}", id.to_u32());
None
}
}
}
fn drawn_outline(&self, id: skrifa::GlyphId) -> (Vec<Spline>, Option<f32>) {
let mut pen = SplinePen {
sink: SplineSink::new(),
open_contours: self.open_contours,
};
match self
.scaler
.outline(id)
.map(|outline| outline.draw(DrawMemory::Scratch, &mut pen))
{
Some(Ok(drawn)) => (pen.sink.finish(), drawn.h_advance),
_ => (Vec::new(), None),
}
}
}
pub(crate) fn name(font: &skrifa::Font, id: NameId) -> Option<String> {
font.names()
.get(id)?
.english_or_first()
.map(|localized| localized.name().to_string())
}
struct SplinePen {
sink: SplineSink,
open_contours: bool,
}
impl PathSink for SplinePen {
fn move_to(&mut self, x: f32, y: f32) {
self.sink.move_to(V2::new(x, y));
}
fn line_to(&mut self, x: f32, y: f32) {
self.sink.line_to(V2::new(x, y));
}
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.sink.quad_to(V2::new(cx0, cy0), V2::new(x, y));
}
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.sink
.cubic_to(V2::new(cx0, cy0), V2::new(cx1, cy1), V2::new(x, y));
}
fn close(&mut self) {
if self.open_contours {
self.sink.end();
} else {
self.sink.close();
}
}
}