Skip to content

Commit

Permalink
Merge pull request #858 from simoncozens/curs-feature-writer
Browse files Browse the repository at this point in the history
Add cursive feature writer
  • Loading branch information
cmyr authored Jun 28, 2024
2 parents 975562b + 58e3cbe commit 2f85245
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 14 deletions.
4 changes: 2 additions & 2 deletions fea-rs/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub use compiler::Compiler;
pub use feature_writer::{FeatureBuilder, FeatureProvider, NopFeatureProvider, PendingLookup};
pub use language_system::LanguageSystem;
pub use lookups::{
Builder, FeatureKey, LookupId, MarkToBaseBuilder, MarkToLigBuilder, MarkToMarkBuilder,
PairPosBuilder, PreviouslyAssignedClass,
Builder, CursivePosBuilder, FeatureKey, LookupId, MarkToBaseBuilder, MarkToLigBuilder,
MarkToMarkBuilder, PairPosBuilder, PreviouslyAssignedClass,
};
pub use metrics::{Anchor, ValueRecord};
pub use opts::Opts;
Expand Down
6 changes: 4 additions & 2 deletions fea-rs/src/compile/lookups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ use contextual::{
SubChainContextBuilder, SubContextBuilder,
};

use gpos_builders::{CursivePosBuilder, SinglePosBuilder};
use gpos_builders::SinglePosBuilder;
pub use gpos_builders::{
MarkToBaseBuilder, MarkToLigBuilder, MarkToMarkBuilder, PairPosBuilder, PreviouslyAssignedClass,
CursivePosBuilder, MarkToBaseBuilder, MarkToLigBuilder, MarkToMarkBuilder, PairPosBuilder,
PreviouslyAssignedClass,
};
use gsub_builders::{
AlternateSubBuilder, LigatureSubBuilder, MultipleSubBuilder, SingleSubBuilder,
Expand Down Expand Up @@ -125,6 +126,7 @@ impl_into_pos_lookup!(PairPosBuilder, Pair);
impl_into_pos_lookup!(MarkToBaseBuilder, MarkToBase);
impl_into_pos_lookup!(MarkToMarkBuilder, MarkToMark);
impl_into_pos_lookup!(MarkToLigBuilder, MarkToLig);
impl_into_pos_lookup!(CursivePosBuilder, Cursive);

#[derive(Clone, Debug)]
pub(crate) enum SubstitutionLookup {
Expand Down
2 changes: 2 additions & 0 deletions fea-rs/src/compile/lookups/gpos_builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ impl Builder for ClassPairPosSubtable {
}
}

/// A builder for GPOS Lookup Type 3, Cursive Attachment
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CursivePosBuilder {
Expand All @@ -349,6 +350,7 @@ pub struct CursivePosBuilder {
}

impl CursivePosBuilder {
/// Insert a new entry/exit anchor pair for a glyph.
pub fn insert(&mut self, glyph: GlyphId, entry: Option<Anchor>, exit: Option<Anchor>) {
self.items.insert(glyph, (entry, exit));
}
Expand Down
69 changes: 63 additions & 6 deletions fontbe/src/features/marks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};

use fea_rs::{
compile::{
Anchor as FeaAnchor, FeatureProvider, MarkToBaseBuilder, MarkToLigBuilder,
MarkToMarkBuilder, NopFeatureProvider, NopVariationInfo, PendingLookup,
Anchor as FeaAnchor, CursivePosBuilder, FeatureProvider, MarkToBaseBuilder,
MarkToLigBuilder, MarkToMarkBuilder, NopFeatureProvider, NopVariationInfo, PendingLookup,
},
typed::{AstNode, LanguageSystem},
GlyphSet, Opts, ParseTree,
Expand Down Expand Up @@ -175,6 +175,8 @@ impl<'a> MarkLookupBuilder<'a> {
mark_groups.insert(group);
}
AnchorKind::ComponentMarker(_) => (),
AnchorKind::CursiveEntry => (),
AnchorKind::CursiveExit => (),
}
pruned
.entry(anchors.glyph_name.clone())
Expand All @@ -190,10 +192,11 @@ impl<'a> MarkLookupBuilder<'a> {
// <https://github.com/googlefonts/ufo2ft/blob/6787e37e63530/Lib/ufo2ft/featureWriters/markFeatureWriter.py#L359>
pruned.retain(|_, anchors| {
anchors.retain(|anchor| {
anchor
.mark_group_name()
.map(|group| used_groups.contains(&group))
.unwrap_or_else(|| anchor.is_component_marker())
anchor.is_cursive()
|| anchor
.mark_group_name()
.map(|group| used_groups.contains(&group))
.unwrap_or_else(|| anchor.is_component_marker())
});
!anchors.is_empty()
});
Expand All @@ -217,11 +220,13 @@ impl<'a> MarkLookupBuilder<'a> {
let mark_base = self.make_lookups::<MarkToBaseBuilder>(mark_base_groups)?;
let mark_mark = self.make_lookups::<MarkToMarkBuilder>(mark_mark_groups)?;
let mark_lig = self.make_lookups::<MarkToLigBuilder>(mark_lig_groups)?;
let curs = self.make_cursive_lookups()?;
Ok(FeaRsMarks {
glyphmap: self.glyph_order.iter().cloned().collect(),
mark_base,
mark_mark,
mark_lig,
curs,
})
}

Expand Down Expand Up @@ -399,6 +404,50 @@ impl<'a> MarkLookupBuilder<'a> {
}
groups
}

fn make_cursive_lookups(&self) -> Result<Vec<PendingLookup<CursivePosBuilder>>, Error> {
let mut builder = CursivePosBuilder::default();
let mut flags = LookupFlag::empty();
flags.set_ignore_marks(true);
flags.set_right_to_left(true);
let mut entries = BTreeMap::new();
let mut affected_glyphs = BTreeSet::new();
let mut exits = BTreeMap::new();

for (glyph_name, anchors) in &self.anchor_lists {
for anchor in anchors {
match anchor.kind {
AnchorKind::CursiveEntry => {
entries.insert(glyph_name, anchor);
affected_glyphs.insert(glyph_name);
}
AnchorKind::CursiveExit => {
exits.insert(glyph_name, anchor);
affected_glyphs.insert(glyph_name);
}
_ => {}
}
}
}
if affected_glyphs.is_empty() {
return Ok(vec![]);
}
for glyph_name in affected_glyphs {
let gid = self.glyph_order.glyph_id(glyph_name).unwrap();
let entry_anchor = entries
.get(glyph_name)
.map(|anchor| resolve_anchor_once(anchor, self.static_metadata, glyph_name))
.transpose()?;
let exit_anchor = exits
.get(glyph_name)
.map(|anchor| resolve_anchor_once(anchor, self.static_metadata, glyph_name))
.transpose()?;
builder.insert(gid, entry_anchor, exit_anchor);
}
// In the future we might to do an LTR/RTL split, but for now return a
// vector with one element.
Ok(vec![PendingLookup::new(vec![builder], flags, None)])
}
}

impl Work<Context, AnyWorkId, Error> for MarkWork {
Expand Down Expand Up @@ -571,6 +620,7 @@ impl FeatureProvider for FeaRsMarks {
fn add_features(&self, builder: &mut fea_rs::compile::FeatureBuilder) {
let mut mark_lookups = Vec::new();
let mut mkmk_lookups = Vec::new();
let mut curs_lookups = Vec::new();

for mark_base in self.mark_base.iter() {
// each mark to base it's own lookup, whch differs from fontmake
Expand All @@ -585,12 +635,19 @@ impl FeatureProvider for FeaRsMarks {
mkmk_lookups.push(builder.add_lookup(mark_mark.clone()));
}

for curs in self.curs.iter() {
curs_lookups.push(builder.add_lookup(curs.clone()));
}

if !mark_lookups.is_empty() {
builder.add_to_default_language_systems(Tag::new(b"mark"), &mark_lookups);
}
if !mkmk_lookups.is_empty() {
builder.add_to_default_language_systems(Tag::new(b"mkmk"), &mkmk_lookups);
}
if !curs_lookups.is_empty() {
builder.add_to_default_language_systems(Tag::new(b"curs"), &curs_lookups);
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions fontbe/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use std::{

use fea_rs::{
compile::{
FeatureKey, MarkToBaseBuilder, MarkToLigBuilder, MarkToMarkBuilder, PairPosBuilder,
PendingLookup, ValueRecord as ValueRecordBuilder,
CursivePosBuilder, FeatureKey, MarkToBaseBuilder, MarkToLigBuilder, MarkToMarkBuilder,
PairPosBuilder, PendingLookup, ValueRecord as ValueRecordBuilder,
},
GlyphMap, GlyphSet, ParseTree,
};
Expand Down Expand Up @@ -333,6 +333,7 @@ pub struct FeaRsMarks {
pub(crate) mark_base: Vec<PendingLookup<MarkToBaseBuilder>>,
pub(crate) mark_mark: Vec<PendingLookup<MarkToMarkBuilder>>,
pub(crate) mark_lig: Vec<PendingLookup<MarkToLigBuilder>>,
pub(crate) curs: Vec<PendingLookup<CursivePosBuilder>>,
}

impl Persistable for FeaRsMarks {
Expand Down
21 changes: 19 additions & 2 deletions fontir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,19 +1038,31 @@ pub enum AnchorKind {
/// An attachment anchor on a mark glyph
Mark(GroupName),
/// A base attachment on a ligature glyph
Ligature { group_name: GroupName, index: usize },
Ligature {
group_name: GroupName,
index: usize,
},
/// An anchor marking the presence of a ligature component with no anchors.
///
/// These are names like '_3'.
ComponentMarker(usize),
// we will add more anchor kinds in the future like entry/exit
CursiveEntry,
CursiveExit,
}

impl AnchorKind {
// this logic from
// <https://github.com/googlefonts/ufo2ft/blob/6787e37e6/Lib/ufo2ft/featureWriters/markFeatureWriter.py#L101>
pub fn new(name: impl AsRef<str>) -> Result<AnchorKind, BadAnchorReason> {
let name = name.as_ref();

if name == "entry" {
return Ok(AnchorKind::CursiveEntry);
}
if name == "exit" {
return Ok(AnchorKind::CursiveExit);
}

// the '_' char is used as a prefix for marks, and as a space character
// in ligature mark names (e.g. top_1, top_2). The funny exception is
// names like '_4', (i.e. an underscore followed by a number) which
Expand Down Expand Up @@ -1140,6 +1152,11 @@ impl Anchor {
matches!(self.kind, AnchorKind::ComponentMarker(_))
}

pub fn is_cursive(&self) -> bool {
matches!(self.kind, AnchorKind::CursiveEntry)
|| matches!(self.kind, AnchorKind::CursiveExit)
}

/// If this is a ligature component anchor, return the index
pub fn ligature_index(&self) -> Option<usize> {
match self.kind {
Expand Down

0 comments on commit 2f85245

Please sign in to comment.