use crate::error::{FontMeshError, Result};
use crate::types::{Contour, ContourPoint, Mesh2D, Mesh3D, Outline2D, Point2D};
use glam::Vec2;
use skrifa::{
instance::{LocationRef, Size},
outline::{DrawSettings, OutlinePen},
FontRef, GlyphId, MetadataProvider,
};
const DEFAULT_QUALITY: u8 = 20;
pub fn glyph_to_mesh_2d(font: &FontRef, glyph_id: GlyphId, subdivisions: u8) -> Result<Mesh2D> {
if subdivisions == 0 {
return Err(FontMeshError::InvalidQuality(subdivisions));
}
let outline = extract_and_linearize_outline(font, glyph_id, subdivisions)?;
crate::triangulate::triangulate(&outline)
}
pub fn glyph_to_mesh_3d(
font: &FontRef,
glyph_id: GlyphId,
depth: f32,
subdivisions: u8,
) -> Result<Mesh3D> {
if subdivisions == 0 {
return Err(FontMeshError::InvalidQuality(subdivisions));
}
if !depth.is_finite() {
return Err(FontMeshError::ExtrusionFailed(
"depth must be a finite value".to_string(),
));
}
let outline = extract_and_linearize_outline(font, glyph_id, subdivisions)?;
let mesh_2d = crate::triangulate::triangulate(&outline)?;
crate::extrude::extrude(&mesh_2d, &outline, depth)
}
fn extract_and_linearize_outline(
font: &FontRef,
glyph_id: GlyphId,
subdivisions: u8,
) -> Result<Outline2D> {
let outline = extract_outline(font, glyph_id)?;
crate::linearize::linearize_outline(outline, subdivisions)
}
fn extract_outline(font: &FontRef, glyph_id: GlyphId) -> Result<Outline2D> {
let outlines = font.outline_glyphs();
let glyph = outlines
.get(glyph_id)
.ok_or(FontMeshError::OutlineExtractionFailed(format!(
"glyph {} has no outline in this font",
glyph_id.to_u32()
)))?;
let units_per_em = font
.metrics(Size::unscaled(), LocationRef::default())
.units_per_em;
let mut pen = OutlineExtractor::new(units_per_em);
glyph
.draw(
DrawSettings::unhinted(Size::unscaled(), LocationRef::default()),
&mut pen,
)
.map_err(|e| {
FontMeshError::OutlineExtractionFailed(format!("skrifa draw failed: {e:?}"))
})?;
pen.finish_contour();
if pen.outline.is_empty() {
return Err(FontMeshError::NoOutline);
}
Ok(pen.outline)
}
pub struct GlyphMeshBuilder<'a> {
font: &'a FontRef<'a>,
glyph_id: GlyphId,
subdivisions: u8,
}
impl<'a> GlyphMeshBuilder<'a> {
pub fn new(font: &'a FontRef<'a>, glyph_id: GlyphId) -> Self {
Self {
font,
glyph_id,
subdivisions: DEFAULT_QUALITY,
}
}
#[must_use = "builder methods are intended to be chained"]
pub fn with_subdivisions(mut self, subdivisions: u8) -> Self {
self.subdivisions = subdivisions;
self
}
pub fn to_outline(self) -> Result<Outline2D> {
extract_and_linearize_outline(self.font, self.glyph_id, self.subdivisions)
}
pub fn to_mesh_2d(self) -> Result<Mesh2D> {
glyph_to_mesh_2d(self.font, self.glyph_id, self.subdivisions)
}
pub fn to_mesh_3d(self, depth: f32) -> Result<Mesh3D> {
glyph_to_mesh_3d(self.font, self.glyph_id, depth, self.subdivisions)
}
}
struct OutlineExtractor {
outline: Outline2D,
current_contour: Option<Contour>,
scale: f32,
}
impl OutlineExtractor {
fn new(units_per_em: u16) -> Self {
Self {
outline: Outline2D::new(),
current_contour: None,
scale: 1.0 / units_per_em.max(1) as f32,
}
}
#[inline(always)]
fn point(&self, x: f32, y: f32) -> Point2D {
Vec2::new(x * self.scale, y * self.scale)
}
#[inline(always)]
fn push(&mut self, point: ContourPoint) {
if let Some(c) = self.current_contour.as_mut() {
c.push(point);
}
}
fn finish_contour(&mut self) {
if let Some(contour) = self.current_contour.take() {
if !contour.is_empty() {
self.outline.add_contour(contour);
}
}
}
}
impl OutlinePen for OutlineExtractor {
fn move_to(&mut self, x: f32, y: f32) {
self.finish_contour();
let mut contour = Contour::new(true);
contour.push(ContourPoint::on_curve(self.point(x, y)));
self.current_contour = Some(contour);
}
fn line_to(&mut self, x: f32, y: f32) {
self.push(ContourPoint::on_curve(self.point(x, y)));
}
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.push(ContourPoint::off_curve(self.point(cx0, cy0)));
self.push(ContourPoint::on_curve(self.point(x, y)));
}
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.push(ContourPoint::off_curve_cubic(self.point(cx0, cy0)));
self.push(ContourPoint::off_curve_cubic(self.point(cx1, cy1)));
self.push(ContourPoint::on_curve(self.point(x, y)));
}
fn close(&mut self) {
self.finish_contour();
}
}