Skip to content

Commit

Permalink
[ttx_diff] Add basic support for mark2lig rules
Browse files Browse the repository at this point in the history
We weren't handling this at all. This implementation does not do some of
the fancier stuff that we do for other mark attachment rules, since
these lookups are less common and it seems much less likely that there
will be overlapping lookups that apply rules to the same glyphs.
  • Loading branch information
cmyr committed May 9, 2024
1 parent 5680183 commit 0a67924
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 2 deletions.
26 changes: 25 additions & 1 deletion layout-normalizer/src/gpos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ mod pairpos;
#[cfg(test)]
mod test_helpers;

use self::{marks::MarkAttachmentRule, pairpos::PairPosRule};
use self::{
marks::{MarkAttachmentRule, MarkLigaRule},
pairpos::PairPosRule,
};

pub(crate) fn print(f: &mut dyn io::Write, font: &FontRef, names: &NameMap) -> Result<(), Error> {
writeln!(f, "# GPOS #")?;
Expand Down Expand Up @@ -64,10 +67,12 @@ pub(crate) fn print(f: &mut dyn io::Write, font: &FontRef, names: &NameMap) -> R
let pairpos = lookup_rules.pairpos_rules(&sys.lookups);
let markmark = lookup_rules.markmark_rules(&sys.lookups);
let markbase = lookup_rules.markbase_rules(&sys.lookups);
let markliga = lookup_rules.markliga_rules(&sys.lookups);

print_rules(f, "PairPos", &pairpos, names, mark_glyph_sets.as_ref())?;
print_rules(f, "MarkToBase", &markbase, names, mark_glyph_sets.as_ref())?;
print_rules(f, "MarkToMark", &markmark, names, mark_glyph_sets.as_ref())?;
print_rules(f, "MarkToLig", &markliga, names, mark_glyph_sets.as_ref())?;
}

Ok(())
Expand Down Expand Up @@ -265,6 +270,7 @@ struct LookupRules {
pairpos: Vec<Lookup<PairPosRule>>,
markbase: Vec<Lookup<MarkAttachmentRule>>,
markmark: Vec<Lookup<MarkAttachmentRule>>,
markliga: Vec<Lookup<MarkLigaRule>>,
// decomposed rules for each lookup, in lookup order
}

Expand Down Expand Up @@ -334,6 +340,15 @@ impl LookupRules {
.collect::<Vec<_>>();
normalize_mark_lookups(&lookups)
}

fn markliga_rules<'a>(&'a self, lookups: &[u16]) -> Vec<SingleRule<'a, MarkLigaRule>> {
self.markliga
.iter()
.filter(|lookup| lookups.contains(&lookup.lookup_id))
.inspect(|lookup| eprintln!("liga lookup id {}", lookup.lookup_id))
.flat_map(|lookup| lookup.iter())
.collect()
}
}

// impl shared between markbase and markmark
Expand Down Expand Up @@ -428,6 +443,15 @@ fn get_lookup_rules(
.markbase
.push(Lookup::new(id, rules, flag, mark_filter_id));
}
PositionSubtables::MarkToLig(subs) => {
eprintln!("get lookup rules id {id}");
let subs = subs.iter().flat_map(|sub| sub.ok()).collect::<Vec<_>>();
let rules = marks::get_mark_liga_rules(&subs, delta_computer).unwrap();
eprintln!("got {} rules", rules.len());
result
.markliga
.push(Lookup::new(id, rules, flag, mark_filter_id));
}
_ => (),
}
}
Expand Down
112 changes: 111 additions & 1 deletion layout-normalizer/src/gpos/marks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
};

use write_fonts::read::{
tables::gpos::{MarkBasePosFormat1, MarkMarkPosFormat1},
tables::gpos::{MarkBasePosFormat1, MarkLigPosFormat1, MarkMarkPosFormat1},
types::GlyphId,
ReadError,
};
Expand All @@ -20,6 +20,13 @@ pub(crate) struct MarkAttachmentRule {
marks: BTreeMap<ResolvedAnchor, GlyphSet>,
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct MarkLigaRule {
pub base: GlyphId,
base_anchors: Vec<Option<ResolvedAnchor>>,
marks: BTreeMap<ResolvedAnchor, GlyphSet>,
}

impl PrintNames for MarkAttachmentRule {
fn fmt_names(&self, f: &mut std::fmt::Formatter<'_>, names: &NameMap) -> std::fmt::Result {
let base_name = names.get(self.base);
Expand All @@ -35,6 +42,31 @@ impl PrintNames for MarkAttachmentRule {
}
}

impl PrintNames for MarkLigaRule {
fn fmt_names(&self, f: &mut std::fmt::Formatter<'_>, names: &NameMap) -> std::fmt::Result {
let base_name = names.get(self.base);
write!(f, "{base_name} (lig) [")?;
for (i, anchor) in self.base_anchors.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
match anchor {
Some(a) => write!(f, "{a}"),
None => write!(f, "<NULL>"),
}?
}
writeln!(f, "]")?;
for (i, (anchor, glyphs)) in self.marks.iter().enumerate() {
if i != 0 {
writeln!(f)?;
}

write!(f, " {anchor} {}", glyphs.printer(names))?;
}
Ok(())
}
}

impl MarkAttachmentRule {
pub(crate) fn iter_base_mark_pairs(&self) -> impl Iterator<Item = (GlyphId, GlyphId)> + '_ {
self.marks
Expand Down Expand Up @@ -143,6 +175,84 @@ fn append_mark_base_rules(
Ok(())
}

pub(super) fn get_mark_liga_rules(
subtables: &[MarkLigPosFormat1],
delta_computer: Option<&DeltaComputer>,
) -> Result<Vec<MarkLigaRule>, ReadError> {
// so we only take the first coverage hit in each subtable, which means
// we just need track what we've seen.
let mut seen = HashSet::new();
let mut result = Vec::new();
for sub in subtables.iter() {
append_mark_liga_rules(sub, delta_computer, &mut seen, &mut result)?;
}
Ok(result)
}

fn append_mark_liga_rules(
subtable: &MarkLigPosFormat1,
delta_computer: Option<&DeltaComputer>,
_seen: &mut HashSet<(GlyphId, GlyphId)>,
result: &mut Vec<MarkLigaRule>,
) -> Result<(), ReadError> {
let lig_array = subtable.ligature_array()?;
let lig_tables = lig_array.ligature_attaches();
let mark_array = subtable.mark_array()?;
let mark_records = mark_array.mark_records();
let mark_count = subtable.mark_class_count() as usize;

let cov_ix_to_mark_gid: HashMap<_, _> = subtable.mark_coverage()?.iter().enumerate().collect();

// okay backing up:
// a rule should be one ligature, and one mark class?
for (lig_ix, lig_glyph) in subtable.ligature_coverage()?.iter().enumerate() {
let lig_attach = lig_tables.get(lig_ix)?;

for anchor_ix in 0..mark_count {
// all the anchors in a given class, for this ligature
let comp_anchors = lig_attach
.component_records()
.iter()
.map(|comp| {
comp.and_then(|comp| {
comp.ligature_anchors(lig_attach.offset_data())
.get(anchor_ix)
.map(|anchor| {
anchor.and_then(|a| ResolvedAnchor::new(&a, delta_computer))
})
.transpose()
})
})
.collect::<Result<Vec<_>, _>>()?;

let mut marks = BTreeMap::default();
for (mark_ix, mark_record) in mark_records.iter().enumerate() {
let mark_class = mark_record.mark_class() as usize;
if mark_class != anchor_ix {
continue;
}
let Some(mark_glyph) = cov_ix_to_mark_gid.get(&mark_ix) else {
continue;
};

let mark_anchor = mark_record.mark_anchor(mark_array.offset_data())?;
let mark_anchor = ResolvedAnchor::new(&mark_anchor, delta_computer)?;
marks
.entry(mark_anchor)
.or_insert_with(|| GlyphSet::from(*mark_glyph))
.add(*mark_glyph);
}
let group = MarkLigaRule {
base: lig_glyph,
base_anchors: comp_anchors,
marks,
};
result.push(group);
}
}
Ok(())
}

pub(super) fn get_mark_mark_rules(
subtables: &[MarkMarkPosFormat1],
delta_computer: Option<&DeltaComputer>,
Expand Down

0 comments on commit 0a67924

Please sign in to comment.