sr-aef 0.1.2

Uncompile OpenType fonts back into Adobe feature file (fea) source code
Documentation
use crate::{UncompileContext, add_subtable_break};
use fea_rs_ast::{
    AlternateSubstStatement, ChainedContextStatement, GlyphClass, GlyphContainer, GlyphName,
    IgnoreStatement, LigatureSubstStatement, LookupBlock, MultipleSubstStatement,
    ReverseChainSingleSubstStatement, SingleSubstStatement, Statement, Subst,
};
use skrifa::{
    GlyphId16,
    raw::{
        ReadError,
        tables::gsub::{
            AlternateSubstFormat1, LigatureSubstFormat1, LookupList, MultipleSubstFormat1,
            ReverseChainSingleSubstFormat1, SingleSubst, SingleSubstFormat1, SingleSubstFormat2,
            SubstitutionLookup, SubstitutionSubtables,
        },
    },
};
impl<'a> UncompileContext<'a> {
    pub(crate) fn uncompile_gsub_lookups(&mut self) -> Result<(), ReadError> {
        let gsub_lookup_list: LookupList<SubstitutionLookup> =
            match self.gsub.as_ref().and_then(|gsub| gsub.lookup_list().ok()) {
                Some(lookup_list) => lookup_list,
                None => return Ok(()),
            };
        for (i, lookup) in gsub_lookup_list.lookups().iter().flatten().enumerate() {
            let prefix = match lookup.subtables()? {
                SubstitutionSubtables::Single(_) => "gsub_single",
                SubstitutionSubtables::Multiple(_) => "gsub_multiple",
                SubstitutionSubtables::Alternate(_) => "gsub_alternate",
                SubstitutionSubtables::Ligature(_) => "gsub_ligature",
                SubstitutionSubtables::Contextual(_) => "gsub_contextual",
                SubstitutionSubtables::ChainContextual(_) => "gsub_chain_contextual",
                SubstitutionSubtables::Reverse(_) => "gsub_reverse",
                SubstitutionSubtables::EmptyExtension => "gsub_extension",
            };
            self.assign_lookup_name(prefix, i as u16, Subst);
        }
        for (i, lookup) in gsub_lookup_list.lookups().iter().flatten().enumerate() {
            let subtables = lookup.subtables()?;
            let mut lookupblock = match subtables {
                SubstitutionSubtables::Single(subtables) => {
                    let mut lookupblock = self.create_lookup_block(i as u16, Subst);
                    for subtable in subtables.iter().flatten() {
                        add_subtable_break(&mut lookupblock);
                        match subtable {
                            SingleSubst::Format1(table_ref) => {
                                self.uncompile_gsub1_format1(&mut lookupblock, table_ref)?;
                            }
                            SingleSubst::Format2(table_ref) => {
                                self.uncompile_gsub1_format2(&mut lookupblock, table_ref)?;
                            }
                        }
                    }
                    lookupblock
                }
                SubstitutionSubtables::Multiple(subtables) => {
                    let mut lookupblock = self.create_lookup_block(i as u16, Subst);
                    for subtable in subtables.iter().flatten() {
                        add_subtable_break(&mut lookupblock);
                        self.uncompile_gsub2(&mut lookupblock, subtable)?;
                    }
                    lookupblock
                }
                SubstitutionSubtables::Alternate(subtables) => {
                    let mut lookupblock = self.create_lookup_block(i as u16, Subst);
                    for subtable in subtables.iter().flatten() {
                        add_subtable_break(&mut lookupblock);
                        self.uncompile_gsub3(&mut lookupblock, subtable)?;
                    }
                    lookupblock
                }
                SubstitutionSubtables::Ligature(subtables) => {
                    let mut lookupblock = self.create_lookup_block(i as u16, Subst);
                    for subtable in subtables.iter().flatten() {
                        add_subtable_break(&mut lookupblock);
                        self.uncompile_gsub4(&mut lookupblock, subtable)?;
                    }
                    lookupblock
                }
                SubstitutionSubtables::Contextual(subtables) => {
                    let mut lookupblock = self.create_lookup_block(i as u16, Subst);
                    for subtable in subtables.iter().flatten() {
                        add_subtable_break(&mut lookupblock);
                        lookupblock.statements.extend(
                            self.uncompile_sequence_context(subtable, Subst)?
                                .into_iter()
                                .map(to_context_statement),
                        );
                    }
                    lookupblock
                }
                SubstitutionSubtables::ChainContextual(subtables) => {
                    let mut lookupblock = self.create_lookup_block(i as u16, Subst);
                    for subtable in subtables.iter().flatten() {
                        add_subtable_break(&mut lookupblock);
                        lookupblock.statements.extend(
                            self.uncompile_chain_sequence_context(subtable, Subst)?
                                .into_iter()
                                .map(to_context_statement),
                        );
                    }
                    lookupblock
                }
                SubstitutionSubtables::Reverse(subtables) => {
                    let mut lookupblock = self.create_lookup_block(i as u16, Subst);
                    for subtable in subtables.iter().flatten() {
                        add_subtable_break(&mut lookupblock);
                        self.uncompile_gsub7(&mut lookupblock, subtable)?;
                    }
                    lookupblock
                }

                SubstitutionSubtables::EmptyExtension => self.create_lookup_block(i as u16, Subst),
            };
            if let Some(Statement::Subtable(_)) = lookupblock.statements.last() {
                lookupblock.statements.pop();
            }
            lookupblock.use_extension = matches!(lookup, SubstitutionLookup::Extension(_));
            self.add_lookup_flags(
                &mut lookupblock,
                lookup.lookup_flag(),
                lookup.mark_filtering_set(),
            );
            self.lookups.insert(lookupblock.name.clone(), lookupblock);
        }
        Ok(())
    }

    fn uncompile_gsub1_format1(
        &self,
        lookupblock: &mut LookupBlock,
        gsub1: SingleSubstFormat1,
    ) -> Result<(), ReadError> {
        let delta = gsub1.delta_glyph_id();
        let inputs = self.resolve_coverage(&gsub1.coverage()?);
        let replacements = gsub1
            .coverage()?
            .iter()
            .map(|g| GlyphId16::new(g.to_u16().wrapping_add_signed(delta)))
            .map(|g| GlyphContainer::GlyphName(self.get_name(g)))
            .collect::<Vec<GlyphContainer>>();
        let subst = SingleSubstStatement::new(
            vec![self.class_members_to_container(inputs)],
            vec![self.class_members_to_container(replacements)],
            vec![],
            vec![],
            0..0,
            false,
        );
        lookupblock.statements.push(Statement::SingleSubst(subst));

        Ok(())
    }
    fn uncompile_gsub1_format2(
        &self,
        lookupblock: &mut LookupBlock,
        gsub1: SingleSubstFormat2,
    ) -> Result<(), ReadError> {
        let inputs = self.resolve_coverage(&gsub1.coverage()?);
        let replacements = gsub1
            .substitute_glyph_ids()
            .iter()
            .map(|g| GlyphContainer::GlyphName(self.get_name(g.get())))
            .collect();
        let subst = SingleSubstStatement::new(
            vec![self.class_members_to_container(inputs)],
            vec![self.class_members_to_container(replacements)],
            vec![],
            vec![],
            0..0,
            false,
        );
        lookupblock.statements.push(Statement::SingleSubst(subst));
        Ok(())
    }
    fn uncompile_gsub2(
        &self,
        lookupblock: &mut LookupBlock,
        gsub2: MultipleSubstFormat1,
    ) -> Result<(), ReadError> {
        let inputs = self.resolve_coverage(&gsub2.coverage()?);
        for (input, sequence) in inputs.iter().zip(gsub2.sequences().iter().flatten()) {
            let mut replacements: Vec<GlyphContainer> = sequence
                .substitute_glyph_ids()
                .iter()
                .map(|g| GlyphContainer::GlyphName(self.get_name(g.get())))
                .collect();
            if replacements.is_empty() {
                replacements.push(GlyphContainer::GlyphName(GlyphName::new("NULL")));
            }
            let subst = MultipleSubstStatement::new(
                input.clone(),
                replacements,
                vec![],
                vec![],
                0..0,
                false,
            );
            lookupblock.statements.push(Statement::MultipleSubst(subst));
        }
        Ok(())
    }
    fn uncompile_gsub3(
        &self,
        _lookupblock: &mut LookupBlock,
        gsub3: AlternateSubstFormat1,
    ) -> Result<(), ReadError> {
        let inputs = self.resolve_coverage(&gsub3.coverage()?);
        for (input, alternate_set) in inputs.iter().zip(gsub3.alternate_sets().iter().flatten()) {
            let alternates = alternate_set
                .alternate_glyph_ids()
                .iter()
                .map(|g| GlyphContainer::GlyphName(self.get_name(g.get())))
                .collect();
            let subst = AlternateSubstStatement::new(
                input.clone(),
                GlyphContainer::GlyphClass(GlyphClass::new(alternates, 0..0)),
                vec![],
                vec![],
                0..0,
                false,
            );
            _lookupblock
                .statements
                .push(Statement::AlternateSubst(subst));
        }
        {}
        Ok(())
    }
    fn uncompile_gsub4(
        &self,
        lookupblock: &mut LookupBlock,
        gsub4: LigatureSubstFormat1,
    ) -> Result<(), ReadError> {
        let inputs = self.resolve_coverage(&gsub4.coverage()?);
        for (input, ligature_set) in inputs.iter().zip(gsub4.ligature_sets().iter().flatten()) {
            for ligature in ligature_set.ligatures().iter().flatten() {
                let mut components: Vec<GlyphContainer> = ligature
                    .component_glyph_ids()
                    .iter()
                    .map(|g| GlyphContainer::GlyphName(self.get_name(g.get())))
                    .collect();
                components.insert(0, input.clone());
                let subst = LigatureSubstStatement::new(
                    components,
                    GlyphContainer::GlyphName(self.get_name(ligature.ligature_glyph())),
                    vec![],
                    vec![],
                    0..0,
                    false,
                );
                lookupblock.statements.push(Statement::LigatureSubst(subst));
            }
        }
        Ok(())
    }

    fn uncompile_gsub7(
        &mut self,
        lookupblock: &mut LookupBlock,
        gsub7: ReverseChainSingleSubstFormat1,
    ) -> Result<(), ReadError> {
        let inputs = self.resolve_coverage(&gsub7.coverage()?);
        let replacements = gsub7
            .substitute_glyph_ids()
            .iter()
            .map(|g| GlyphContainer::GlyphName(self.get_name(g.get())))
            .collect();
        let mut prefix: Vec<GlyphContainer> = gsub7
            .backtrack_coverages()
            .iter()
            .flatten()
            .map(|coverage| self.resolve_coverage_to_class(&coverage, true))
            .collect();
        prefix.reverse();
        let suffix = gsub7
            .lookahead_coverages()
            .iter()
            .flatten()
            .map(|coverage| self.resolve_coverage_to_class(&coverage, true))
            .collect();
        let subst = ReverseChainSingleSubstStatement::new(
            vec![self.class_members_to_container(inputs)],
            vec![self.class_members_to_container(replacements)],
            prefix,
            suffix,
            0..0,
        );
        lookupblock
            .statements
            .push(Statement::ReverseChainSubst(subst));
        Ok(())
    }
}

fn to_context_statement(statement: ChainedContextStatement<Subst>) -> Statement {
    if statement.lookups.iter().all(Vec::is_empty) {
        Statement::IgnoreSubst(IgnoreStatement::new(
            vec![(statement.prefix, statement.glyphs, statement.suffix)],
            0..0,
            Subst,
        ))
    } else {
        Statement::ChainedContextSubst(statement)
    }
}