#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct GlyphId(pub u16);
impl GlyphId {
pub(crate) fn raw(self) -> ttf_parser::GlyphId {
ttf_parser::GlyphId(self.0)
}
}
#[derive(Debug)]
pub enum FontError {
Face(String),
NoMathTable,
}
impl core::fmt::Display for FontError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FontError::Face(e) => write!(f, "cannot parse font: {e}"),
FontError::NoMathTable => write!(f, "font has no OpenType MATH table"),
}
}
}
impl std::error::Error for FontError {}
#[derive(Debug, Clone, Copy)]
pub(crate) enum KernCorner {
TopRight,
TopLeft,
BottomRight,
BottomLeft,
}
pub(crate) enum Stretched {
Glyph(GlyphId),
Assembly {
parts: Vec<(GlyphId, f32)>,
extent: f32,
},
}
pub struct MathFont<'a> {
face: ttf_parser::Face<'a>,
units_per_em: f32,
ink_boxes: std::sync::OnceLock<Vec<std::sync::OnceLock<Option<ttf_parser::Rect>>>>,
ssty: Vec<ttf_parser::gsub::AlternateSubstitution<'a>>,
#[cfg(feature = "shaping")]
shaper: Option<rustybuzz::Face<'a>>,
#[cfg(feature = "shaping")]
plans: std::panic::AssertUnwindSafe<[std::sync::OnceLock<rustybuzz::ShapePlan>; 3]>,
}
const _: () = {
const fn assert_auto_traits<
T: Send + Sync + std::panic::RefUnwindSafe + std::panic::UnwindSafe,
>() {
}
assert_auto_traits::<MathFont<'static>>();
};
impl<'a> MathFont<'a> {
pub fn probe(data: &[u8], index: u32) -> bool {
let Ok(raw) = ttf_parser::RawFace::parse(data, index) else {
return false;
};
let Some(math) = raw.table(ttf_parser::Tag::from_bytes(b"MATH")) else {
return false;
};
ttf_parser::math::Table::parse(math).is_some_and(|m| m.constants.is_some())
}
pub fn new(data: &'a [u8], index: u32) -> Result<Self, FontError> {
let face =
ttf_parser::Face::parse(data, index).map_err(|e| FontError::Face(e.to_string()))?;
if face.tables().math.and_then(|m| m.constants).is_none() {
return Err(FontError::NoMathTable);
}
let units_per_em = face.units_per_em() as f32;
let ssty = ssty_subtables(&face);
Ok(MathFont {
face,
units_per_em,
ink_boxes: std::sync::OnceLock::new(),
ssty,
#[cfg(feature = "shaping")]
shaper: rustybuzz::Face::from_slice(data, index),
#[cfg(feature = "shaping")]
plans: std::panic::AssertUnwindSafe([const { std::sync::OnceLock::new() }; 3]),
})
}
#[cfg(feature = "shaping")]
pub(crate) fn shaper(&self) -> Option<&rustybuzz::Face<'a>> {
self.shaper.as_ref()
}
#[cfg(feature = "shaping")]
pub(crate) fn shape_plan(&self, script_level: u8) -> Option<&rustybuzz::ShapePlan> {
let shaper = self.shaper.as_ref()?;
let level = usize::from(script_level.min(2));
Some(self.plans[level].get_or_init(|| {
let mut features = Vec::new();
if level > 0 {
features.push(rustybuzz::Feature::new(
rustybuzz::ttf_parser::Tag::from_bytes(b"ssty"),
level as u32,
..,
));
}
rustybuzz::ShapePlan::new(
shaper,
rustybuzz::Direction::LeftToRight,
Some(rustybuzz::script::SCRIPT_MATH),
None,
&features,
)
}))
}
pub fn units_per_em(&self) -> f32 {
self.units_per_em
}
pub(crate) fn constants(&self) -> ttf_parser::math::Constants<'a> {
self.face
.tables()
.math
.and_then(|m| m.constants)
.expect("verified at construction")
}
pub(crate) fn face(&self) -> &ttf_parser::Face<'a> {
&self.face
}
pub(crate) fn glyph_index(&self, c: char) -> Option<GlyphId> {
self.face.glyph_index(c).map(|g| GlyphId(g.0))
}
pub(crate) fn advance(&self, glyph: GlyphId) -> f32 {
self.face.glyph_hor_advance(glyph.raw()).unwrap_or(0) as f32
}
pub(crate) fn ink_box(&self, glyph: GlyphId) -> Option<ttf_parser::Rect> {
let table = self.ink_boxes.get_or_init(|| {
std::iter::repeat_with(std::sync::OnceLock::new)
.take(usize::from(self.face.number_of_glyphs()))
.collect()
});
match table.get(usize::from(glyph.0)) {
Some(slot) => *slot.get_or_init(|| self.face.glyph_bounding_box(glyph.raw())),
None => self.face.glyph_bounding_box(glyph.raw()),
}
}
pub(crate) fn stretch_vertical(&self, glyph: GlyphId, target: f32) -> Stretched {
self.stretch(glyph, target, false)
}
pub(crate) fn stretch_horizontal(&self, glyph: GlyphId, target: f32) -> Stretched {
self.stretch(glyph, target, true)
}
fn stretch(&self, glyph: GlyphId, target: f32, horizontal: bool) -> Stretched {
let Some(variants) = self.face.tables().math.and_then(|m| m.variants) else {
return Stretched::Glyph(glyph);
};
let constructions = if horizontal {
variants.horizontal_constructions
} else {
variants.vertical_constructions
};
let Some(construction) = constructions.get(glyph.raw()) else {
return Stretched::Glyph(glyph);
};
let mut best = glyph;
for v in construction.variants {
best = GlyphId(v.variant_glyph.0);
if f32::from(v.advance_measurement) >= target {
return Stretched::Glyph(best);
}
}
construction
.assembly
.and_then(|asm| self.assemble(&asm, f32::from(variants.min_connector_overlap), target))
.unwrap_or(Stretched::Glyph(best))
}
fn assemble(
&self,
asm: &ttf_parser::math::GlyphAssembly,
min_overlap: f32,
target: f32,
) -> Option<Stretched> {
for repeats in 1..=32u32 {
let mut seq: Vec<ttf_parser::math::GlyphPart> = Vec::new();
for part in asm.parts {
let n = if part.part_flags.extender() {
repeats
} else {
1
};
for _ in 0..n {
seq.push(part);
}
}
if seq.len() < 2 {
return None;
}
let sum: f32 = seq.iter().map(|p| f32::from(p.full_advance)).sum();
let joints = (seq.len() - 1) as f32;
if sum - min_overlap * joints < target {
continue; }
let mut overlap = (sum - target) / joints;
let max_overlap = seq
.windows(2)
.map(|w| f32::from(w[0].end_connector_length.min(w[1].start_connector_length)))
.fold(f32::INFINITY, f32::min);
overlap = overlap.clamp(min_overlap, max_overlap.max(min_overlap));
let mut parts = Vec::with_capacity(seq.len());
let mut bottom = 0.0;
for p in &seq {
parts.push((GlyphId(p.glyph_id.0), bottom));
bottom += f32::from(p.full_advance) - overlap;
}
let extent = bottom + overlap;
return Some(Stretched::Assembly { parts, extent });
}
None
}
pub(crate) fn script_alternate(&self, glyph: GlyphId, level: u16) -> Option<GlyphId> {
debug_assert!(level >= 1);
for alt in &self.ssty {
if let Some(idx) = alt.coverage.get(glyph.raw()) {
let set = alt.alternate_sets.get(idx)?;
let last = set.alternates.len().checked_sub(1)?;
return set
.alternates
.get(level.saturating_sub(1).min(last))
.map(|g| GlyphId(g.0));
}
}
None
}
pub(crate) fn italic_correction(&self, glyph: GlyphId) -> f32 {
self.face
.tables()
.math
.and_then(|m| m.glyph_info)
.and_then(|gi| gi.italic_corrections)
.and_then(|ic| ic.get(glyph.raw()))
.map_or(0.0, |v| f32::from(v.value))
}
pub(crate) fn top_accent_attachment(&self, glyph: GlyphId) -> Option<f32> {
self.face
.tables()
.math
.and_then(|m| m.glyph_info)
.and_then(|gi| gi.top_accent_attachments)
.and_then(|t| t.get(glyph.raw()))
.map(|v| f32::from(v.value))
}
pub(crate) fn math_kern(&self, glyph: GlyphId, corner: KernCorner, height: f32) -> f32 {
let Some(info) = self
.face
.tables()
.math
.and_then(|m| m.glyph_info)
.and_then(|gi| gi.kern_infos)
.and_then(|k| k.get(glyph.raw()))
else {
return 0.0;
};
let Some(kern) = (match corner {
KernCorner::TopRight => info.top_right,
KernCorner::TopLeft => info.top_left,
KernCorner::BottomRight => info.bottom_right,
KernCorner::BottomLeft => info.bottom_left,
}) else {
return 0.0;
};
let count = kern.count();
let mut index = count;
for i in 0..count {
let step = kern.height(i).map_or(f32::MAX, |v| f32::from(v.value));
if height <= step {
index = i;
break;
}
}
kern.kern(index).map_or(0.0, |v| f32::from(v.value))
}
pub(crate) fn x_height(&self) -> f32 {
self.face
.x_height()
.map(f32::from)
.unwrap_or(self.units_per_em * 0.5)
}
pub(crate) fn line_metrics(&self) -> (f32, f32) {
(self.face.ascender() as f32, -(self.face.descender() as f32))
}
}
fn ssty_subtables<'a>(
face: &ttf_parser::Face<'a>,
) -> Vec<ttf_parser::gsub::AlternateSubstitution<'a>> {
let mut out = Vec::new();
let Some(gsub) = face.tables().gsub else {
return out;
};
let Some(feature) = gsub.features.find(ttf_parser::Tag::from_bytes(b"ssty")) else {
return out;
};
for li in feature.lookup_indices {
let Some(lookup) = gsub.lookups.get(li) else {
continue;
};
for sub in lookup
.subtables
.into_iter::<ttf_parser::gsub::SubstitutionSubtable>()
{
if let ttf_parser::gsub::SubstitutionSubtable::Alternate(alt) = sub {
out.push(alt);
}
}
}
out
}