Skip to content

Commit

Permalink
Merge pull request #792 from googlefonts/layout-norm-mark2liga
Browse files Browse the repository at this point in the history
[ttx_diff] Add basic support for mark2lig rules
  • Loading branch information
cmyr authored May 9, 2024
2 parents 1322b0d + 0a67924 commit 3dd835e
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 3dd835e

Please sign in to comment.