use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use std::collections::HashMap;
use std::sync::Arc;
use ttf_parser::gsub::{Ligature, SubstitutionSubtable};
use ttf_parser::GlyphId;
use typst::text::Font;
use reflexo::ImmutStr;
fn get_rev_cmap(face: &ttf_parser::Face<'_>) -> std::collections::HashMap<GlyphId, char> {
let mut rev_cmap = std::collections::HashMap::new();
if let Some(cmap) = face.tables().cmap {
for subtable in cmap.subtables {
if !subtable.is_unicode() {
continue;
}
subtable.codepoints(|c| {
let c = char::from_u32(c);
if let Some(c) = c {
let g = face.glyph_index(c);
if let Some(g) = g {
rev_cmap.insert(g, c);
}
}
})
}
}
rev_cmap
}
fn get_liga_cov(face: &ttf_parser::Face<'_>) -> std::collections::HashSet<GlyphId> {
let mut res = std::collections::HashSet::new();
let Some(gsub) = face.tables().gsub else {
return res;
};
for lookup in gsub.lookups {
for subtable in lookup.subtables.into_iter() {
if let SubstitutionSubtable::Ligature(ligatures) = subtable {
for ligature_set in ligatures.ligature_sets {
for ligature in ligature_set {
res.insert(ligature.glyph);
}
}
}
}
}
res
}
pub struct LigatureResolver {
rev_cmap: std::collections::HashMap<GlyphId, char>,
ligature_cov: std::collections::HashSet<GlyphId>,
ligature_cmap: RwLock<HashMap<GlyphId, Option<ImmutStr>>>,
}
impl LigatureResolver {
pub fn new(face: &ttf_parser::Face<'_>) -> Self {
Self {
rev_cmap: get_rev_cmap(face),
ligature_cov: get_liga_cov(face),
ligature_cmap: RwLock::default(),
}
}
pub fn resolve(&self, face: &ttf_parser::Face<'_>, id: GlyphId) -> Option<ImmutStr> {
let cmap = self.ligature_cmap.upgradable_read();
if let Some(s) = cmap.get(&id) {
return s.clone();
}
let mut cmap = RwLockUpgradableReadGuard::upgrade(cmap);
let res = if self.ligature_cov.contains(&id) {
let ligature = LigatureResolver::lookup(face, id).unwrap();
let c: ImmutStr = ligature
.components
.into_iter()
.map(|g| {
self.rev_cmap.get(&g).unwrap_or_else(|| {
eprintln!("ligature component not found: {g:?} {face:?}");
&' '
})
})
.collect::<String>()
.into();
Some(c)
} else {
None
};
cmap.insert(id, res.clone());
res
}
fn lookup<'a>(face: &ttf_parser::Face<'a>, id: GlyphId) -> Option<Ligature<'a>> {
let gsub = face.tables().gsub.unwrap();
for lookup in gsub.lookups {
for subtable in lookup.subtables.into_iter() {
if let SubstitutionSubtable::Ligature(ligatures) = subtable {
for ligature_set in ligatures.ligature_sets {
for ligature in ligature_set {
if ligature.glyph == id {
return Some(ligature);
}
}
}
}
}
}
None
}
}
#[comemo::memoize]
fn get_ligature_resolver(font: &Font) -> Arc<LigatureResolver> {
Arc::new(LigatureResolver::new(font.ttf()))
}
pub(super) fn resolve_ligature(font: &Font, id: GlyphId) -> Option<ImmutStr> {
let resolver = get_ligature_resolver(font);
resolver.resolve(font.ttf(), id)
}