allsorts_subset_browser/
gsub.rs

1//! Glyph substitution (`gsub`) implementation.
2//!
3//! > The Glyph Substitution (GSUB) table provides data for substition of glyphs for appropriate
4//! > rendering of scripts, such as cursively-connecting forms in Arabic script, or for advanced
5//! > typographic effects, such as ligatures.
6//!
7//! — <https://docs.microsoft.com/en-us/typography/opentype/spec/gsub>
8
9use std::collections::hash_map::Entry;
10use std::collections::BTreeMap;
11use std::fmt::Debug;
12use std::u16;
13
14use bitflags::bitflags;
15use tinyvec::{tiny_vec, TinyVec};
16
17use crate::context::{ContextLookupHelper, Glyph, GlyphTable, MatchType};
18use crate::error::{ParseError, ShapingError};
19use crate::layout::{
20    chain_context_lookup_info, context_lookup_info, AlternateSet, AlternateSubst,
21    ChainContextLookup, ContextLookup, FeatureTableSubstitution, GDEFTable, LangSys, LayoutCache,
22    LayoutTable, Ligature, LigatureSubst, LookupCacheItem, LookupList, MultipleSubst,
23    ReverseChainSingleSubst, SequenceTable, SingleSubst, SubstLookup, GSUB,
24};
25use crate::scripts::{self, ScriptType};
26use crate::tables::variable_fonts::Tuple;
27use crate::tag;
28use crate::unicode::VariationSelector;
29
30const SUBST_RECURSION_LIMIT: usize = 2;
31
32#[derive(Copy, Clone, Eq, PartialEq, Debug)]
33pub struct FeatureInfo {
34    pub feature_tag: u32,
35    pub alternate: Option<usize>,
36}
37
38/// Type indicating the features to use when shaping text.
39#[derive(Clone, Eq, PartialEq, Debug)]
40pub enum Features {
41    /// A custom feature list.
42    ///
43    /// Only the supplied features will be applied when shaping text.
44    Custom(Vec<FeatureInfo>),
45    /// A mask of features to enable.
46    ///
47    /// Unless you have a specific need for low-level control of the OpenType features to enable
48    /// this variant should be preferred.
49    ///
50    /// Enabled bits will be used to enable OpenType features when shaping text. When this variant
51    /// of the `Features` enum is used some common features are enabled by default based on the
52    /// script and language.
53    Mask(FeatureMask),
54}
55
56impl Default for Features {
57    fn default() -> Self {
58        Self::Mask(FeatureMask::default())
59    }
60}
61
62type SubstContext<'a> = ContextLookupHelper<'a, GSUB>;
63
64impl Ligature {
65    pub fn matches<T>(
66        &self,
67        match_type: MatchType,
68        opt_gdef_table: Option<&GDEFTable>,
69        i: usize,
70        glyphs: &[RawGlyph<T>],
71    ) -> bool {
72        let mut last_index = 0;
73        match_type.match_front(
74            opt_gdef_table,
75            &GlyphTable::ById(&self.component_glyphs),
76            glyphs,
77            i,
78            &mut last_index,
79        )
80    }
81
82    pub fn apply<T: GlyphData>(
83        &self,
84        match_type: MatchType,
85        opt_gdef_table: Option<&GDEFTable>,
86        i: usize,
87        glyphs: &mut Vec<RawGlyph<T>>,
88    ) -> usize {
89        let mut index = i + 1;
90        let mut matched = 0;
91        let mut skip = 0;
92        while matched < self.component_glyphs.len() {
93            if index < glyphs.len() {
94                if match_type.match_glyph(opt_gdef_table, &glyphs[index]) {
95                    matched += 1;
96                    let mut matched_glyph = glyphs.remove(index);
97                    glyphs[i].unicodes.append(&mut matched_glyph.unicodes);
98                    glyphs[i].extra_data =
99                        GlyphData::merge(glyphs[i].extra_data.clone(), matched_glyph.extra_data);
100                    glyphs[i].flags.set(RawGlyphFlags::LIGATURE, true);
101                } else {
102                    glyphs[index].liga_component_pos = matched as u16;
103                    skip += 1;
104                    index += 1;
105                }
106            } else {
107                panic!("ran out of glyphs");
108            }
109        }
110        while index < glyphs.len()
111            && MatchType::marks_only().match_glyph(opt_gdef_table, &glyphs[index])
112        {
113            glyphs[index].liga_component_pos = matched as u16;
114            index += 1;
115        }
116        glyphs[i].glyph_index = self.ligature_glyph;
117        glyphs[i].glyph_origin = GlyphOrigin::Direct;
118        skip
119    }
120}
121
122#[derive(Clone, Debug)]
123pub struct RawGlyph<T> {
124    pub unicodes: TinyVec<[char; 1]>,
125    pub glyph_index: u16,
126    pub liga_component_pos: u16,
127    pub glyph_origin: GlyphOrigin,
128    pub flags: RawGlyphFlags,
129    pub variation: Option<VariationSelector>,
130    pub extra_data: T,
131}
132
133bitflags! {
134    pub struct RawGlyphFlags: u8 {
135        const SMALL_CAPS      = 1 << 0;
136        const MULTI_SUBST_DUP = 1 << 1;
137        const IS_VERT_ALT     = 1 << 2;
138        const LIGATURE        = 1 << 3;
139        const FAKE_BOLD       = 1 << 4;
140        const FAKE_ITALIC     = 1 << 5;
141    }
142}
143
144/// `merge` is called during ligature substitution (i.e. merging of glyphs),
145/// and determines how the `RawGlyph.extra_data` field should be merged
146pub trait GlyphData: Clone {
147    fn merge(data1: Self, data2: Self) -> Self;
148}
149
150impl GlyphData for () {
151    fn merge(_data1: (), _data2: ()) {}
152}
153
154#[derive(Clone, Copy, PartialEq, Debug)]
155pub enum GlyphOrigin {
156    Char(char),
157    Direct,
158}
159
160impl<T> RawGlyph<T> {
161    pub fn small_caps(&self) -> bool {
162        self.flags.contains(RawGlyphFlags::SMALL_CAPS)
163    }
164
165    pub fn multi_subst_dup(&self) -> bool {
166        self.flags.contains(RawGlyphFlags::MULTI_SUBST_DUP)
167    }
168
169    pub fn is_vert_alt(&self) -> bool {
170        self.flags.contains(RawGlyphFlags::IS_VERT_ALT)
171    }
172
173    pub fn ligature(&self) -> bool {
174        self.flags.contains(RawGlyphFlags::LIGATURE)
175    }
176
177    pub fn fake_bold(&self) -> bool {
178        self.flags.contains(RawGlyphFlags::FAKE_BOLD)
179    }
180
181    pub fn fake_italic(&self) -> bool {
182        self.flags.contains(RawGlyphFlags::FAKE_ITALIC)
183    }
184}
185
186impl<T> Glyph for RawGlyph<T> {
187    fn get_glyph_index(&self) -> u16 {
188        self.glyph_index
189    }
190}
191
192pub fn gsub_feature_would_apply<T: GlyphData>(
193    gsub_cache: &LayoutCache<GSUB>,
194    gsub_table: &LayoutTable<GSUB>,
195    opt_gdef_table: Option<&GDEFTable>,
196    langsys: &LangSys,
197    feature_variations: Option<&FeatureTableSubstitution<'_>>,
198    feature_tag: u32,
199    glyphs: &[RawGlyph<T>],
200    i: usize,
201) -> Result<bool, ParseError> {
202    if let Some(feature_table) =
203        gsub_table.find_langsys_feature(langsys, feature_tag, feature_variations)?
204    {
205        if let Some(ref lookup_list) = gsub_table.opt_lookup_list {
206            for lookup_index in &feature_table.lookup_indices {
207                let lookup_index = usize::from(*lookup_index);
208                let lookup_cache_item = lookup_list.lookup_cache_gsub(gsub_cache, lookup_index)?;
209                if gsub_lookup_would_apply(opt_gdef_table, &lookup_cache_item, glyphs, i)? {
210                    return Ok(true);
211                }
212            }
213        }
214    }
215    Ok(false)
216}
217
218pub fn gsub_lookup_would_apply<T: GlyphData>(
219    opt_gdef_table: Option<&GDEFTable>,
220    lookup: &LookupCacheItem<SubstLookup>,
221    glyphs: &[RawGlyph<T>],
222    i: usize,
223) -> Result<bool, ParseError> {
224    let match_type = MatchType::from_lookup_flag(lookup.lookup_flag);
225    if i < glyphs.len() && match_type.match_glyph(opt_gdef_table, &glyphs[i]) {
226        return match lookup.lookup_subtables {
227            SubstLookup::SingleSubst(ref subtables) => {
228                match singlesubst_would_apply(subtables, &glyphs[i])? {
229                    Some(_output_glyph) => Ok(true),
230                    None => Ok(false),
231                }
232            }
233            SubstLookup::MultipleSubst(ref subtables) => {
234                match multiplesubst_would_apply(subtables, i, glyphs)? {
235                    Some(_sequence_table) => Ok(true),
236                    None => Ok(false),
237                }
238            }
239            SubstLookup::AlternateSubst(ref subtables) => {
240                match alternatesubst_would_apply(subtables, &glyphs[i])? {
241                    Some(_alternate_set) => Ok(true),
242                    None => Ok(false),
243                }
244            }
245            SubstLookup::LigatureSubst(ref subtables) => {
246                match ligaturesubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
247                    Some(_ligature) => Ok(true),
248                    None => Ok(false),
249                }
250            }
251            SubstLookup::ContextSubst(ref subtables) => {
252                match contextsubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
253                    Some(_subst) => Ok(true),
254                    None => Ok(false),
255                }
256            }
257            SubstLookup::ChainContextSubst(ref subtables) => {
258                match chaincontextsubst_would_apply(
259                    opt_gdef_table,
260                    subtables,
261                    match_type,
262                    i,
263                    glyphs,
264                )? {
265                    Some(_subst) => Ok(true),
266                    None => Ok(false),
267                }
268            }
269            SubstLookup::ReverseChainSingleSubst(ref subtables) => {
270                match reversechainsinglesubst_would_apply(
271                    opt_gdef_table,
272                    subtables,
273                    match_type,
274                    i,
275                    glyphs,
276                )? {
277                    Some(_subst) => Ok(true),
278                    None => Ok(false),
279                }
280            }
281        };
282    }
283    Ok(false)
284}
285
286pub fn gsub_apply_lookup<T: GlyphData>(
287    gsub_cache: &LayoutCache<GSUB>,
288    gsub_table: &LayoutTable<GSUB>,
289    opt_gdef_table: Option<&GDEFTable>,
290    lookup_index: usize,
291    feature_tag: u32,
292    opt_alternate: Option<usize>,
293    glyphs: &mut Vec<RawGlyph<T>>,
294    start: usize,
295    mut length: usize,
296    pred: impl Fn(&RawGlyph<T>) -> bool,
297) -> Result<usize, ParseError> {
298    if let Some(ref lookup_list) = gsub_table.opt_lookup_list {
299        let lookup = lookup_list.lookup_cache_gsub(gsub_cache, lookup_index)?;
300        let match_type = MatchType::from_lookup_flag(lookup.lookup_flag);
301        match lookup.lookup_subtables {
302            SubstLookup::SingleSubst(ref subtables) => {
303                for glyph in glyphs[start..(start + length)].iter_mut() {
304                    if match_type.match_glyph(opt_gdef_table, glyph) && pred(glyph) {
305                        singlesubst(subtables, feature_tag, glyph)?;
306                    }
307                }
308            }
309            SubstLookup::MultipleSubst(ref subtables) => {
310                let mut i = start;
311                while i < start + length {
312                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
313                        match multiplesubst(subtables, i, glyphs)? {
314                            Some(replace_count) => {
315                                i += replace_count;
316                                length += replace_count;
317                                length -= 1;
318                            }
319                            None => i += 1,
320                        }
321                    } else {
322                        i += 1;
323                    }
324                }
325            }
326            SubstLookup::AlternateSubst(ref subtables) => {
327                for glyph in glyphs[start..(start + length)].iter_mut() {
328                    if match_type.match_glyph(opt_gdef_table, glyph) && pred(glyph) {
329                        let alternate = opt_alternate.unwrap_or(0);
330                        alternatesubst(subtables, alternate, glyph)?;
331                    }
332                }
333            }
334            SubstLookup::LigatureSubst(ref subtables) => {
335                let mut i = start;
336                while i < start + length {
337                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
338                        match ligaturesubst(opt_gdef_table, subtables, match_type, i, glyphs)? {
339                            Some((removed_count, skip_count)) => {
340                                i += skip_count + 1;
341                                length -= removed_count;
342                            }
343                            None => i += 1,
344                        }
345                    } else {
346                        i += 1;
347                    }
348                }
349            }
350            SubstLookup::ContextSubst(ref subtables) => {
351                let mut i = start;
352                while i < start + length {
353                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
354                        match contextsubst(
355                            SUBST_RECURSION_LIMIT,
356                            gsub_cache,
357                            lookup_list,
358                            opt_gdef_table,
359                            subtables,
360                            feature_tag,
361                            match_type,
362                            i,
363                            glyphs,
364                        )? {
365                            Some((input_length, changes)) => {
366                                i += input_length;
367                                length = checked_add(length, changes).unwrap();
368                            }
369                            None => i += 1,
370                        }
371                    } else {
372                        i += 1;
373                    }
374                }
375            }
376            SubstLookup::ChainContextSubst(ref subtables) => {
377                let mut i = start;
378                while i < start + length {
379                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
380                        match chaincontextsubst(
381                            SUBST_RECURSION_LIMIT,
382                            gsub_cache,
383                            lookup_list,
384                            opt_gdef_table,
385                            subtables,
386                            feature_tag,
387                            match_type,
388                            i,
389                            glyphs,
390                        )? {
391                            Some((input_length, changes)) => {
392                                i += input_length;
393                                length = checked_add(length, changes).unwrap();
394                            }
395                            None => i += 1,
396                        }
397                    } else {
398                        i += 1;
399                    }
400                }
401            }
402            SubstLookup::ReverseChainSingleSubst(ref subtables) => {
403                for i in (start..start + length).rev() {
404                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
405                        reversechainsinglesubst(opt_gdef_table, subtables, match_type, i, glyphs)?;
406                    }
407                }
408            }
409        }
410    }
411    Ok(length)
412}
413
414fn singlesubst_would_apply<T: GlyphData>(
415    subtables: &[SingleSubst],
416    glyph: &RawGlyph<T>,
417) -> Result<Option<u16>, ParseError> {
418    let glyph_index = glyph.glyph_index;
419    for single_subst in subtables {
420        if let Some(glyph_index) = single_subst.apply_glyph(glyph_index)? {
421            return Ok(Some(glyph_index));
422        }
423    }
424    Ok(None)
425}
426
427fn singlesubst<T: GlyphData>(
428    subtables: &[SingleSubst],
429    subst_tag: u32,
430    glyph: &mut RawGlyph<T>,
431) -> Result<(), ParseError> {
432    if let Some(output_glyph) = singlesubst_would_apply(subtables, glyph)? {
433        glyph.glyph_index = output_glyph;
434        glyph.glyph_origin = GlyphOrigin::Direct;
435        if subst_tag == tag::VERT || subst_tag == tag::VRT2 {
436            glyph.flags.set(RawGlyphFlags::IS_VERT_ALT, true);
437        }
438    }
439    Ok(())
440}
441
442fn multiplesubst_would_apply<'a, T: GlyphData>(
443    subtables: &'a [MultipleSubst],
444    i: usize,
445    glyphs: &[RawGlyph<T>],
446) -> Result<Option<&'a SequenceTable>, ParseError> {
447    let glyph_index = glyphs[i].glyph_index;
448    for multiple_subst in subtables {
449        if let Some(sequence_table) = multiple_subst.apply_glyph(glyph_index)? {
450            return Ok(Some(sequence_table));
451        }
452    }
453    Ok(None)
454}
455
456fn multiplesubst<T: GlyphData>(
457    subtables: &[MultipleSubst],
458    i: usize,
459    glyphs: &mut Vec<RawGlyph<T>>,
460) -> Result<Option<usize>, ParseError> {
461    match multiplesubst_would_apply(subtables, i, glyphs)? {
462        Some(sequence_table) => {
463            if !sequence_table.substitute_glyphs.is_empty() {
464                let first_glyph_index = sequence_table.substitute_glyphs[0];
465                glyphs[i].glyph_index = first_glyph_index;
466                glyphs[i].glyph_origin = GlyphOrigin::Direct;
467                for j in 1..sequence_table.substitute_glyphs.len() {
468                    let output_glyph_index = sequence_table.substitute_glyphs[j];
469                    let mut flags = glyphs[i].flags;
470                    flags.set(RawGlyphFlags::MULTI_SUBST_DUP, true);
471                    flags.set(RawGlyphFlags::LIGATURE, false);
472                    let glyph = RawGlyph {
473                        unicodes: glyphs[i].unicodes.clone(),
474                        glyph_index: output_glyph_index,
475                        liga_component_pos: 0, //glyphs[i].liga_component_pos,
476                        glyph_origin: GlyphOrigin::Direct,
477                        flags,
478                        extra_data: glyphs[i].extra_data.clone(),
479                        variation: glyphs[i].variation,
480                    };
481                    glyphs.insert(i + j, glyph);
482                }
483                Ok(Some(sequence_table.substitute_glyphs.len()))
484            } else {
485                // the spec forbids this, but implementations all allow it
486                glyphs.remove(i);
487                Ok(Some(0))
488            }
489        }
490        None => Ok(None),
491    }
492}
493
494fn alternatesubst_would_apply<'a, T: GlyphData>(
495    subtables: &'a [AlternateSubst],
496    glyph: &RawGlyph<T>,
497) -> Result<Option<&'a AlternateSet>, ParseError> {
498    let glyph_index = glyph.glyph_index;
499    for alternate_subst in subtables {
500        if let Some(alternate_set) = alternate_subst.apply_glyph(glyph_index)? {
501            return Ok(Some(alternate_set));
502        }
503    }
504    Ok(None)
505}
506
507fn alternatesubst<T: GlyphData>(
508    subtables: &[AlternateSubst],
509    alternate: usize,
510    glyph: &mut RawGlyph<T>,
511) -> Result<(), ParseError> {
512    if let Some(alternateset) = alternatesubst_would_apply(subtables, glyph)? {
513        // TODO allow users to specify which alternate glyph they want
514        if alternate < alternateset.alternate_glyphs.len() {
515            glyph.glyph_index = alternateset.alternate_glyphs[alternate];
516            glyph.glyph_origin = GlyphOrigin::Direct;
517        }
518    }
519    Ok(())
520}
521
522fn ligaturesubst_would_apply<'a, T: GlyphData>(
523    opt_gdef_table: Option<&GDEFTable>,
524    subtables: &'a [LigatureSubst],
525    match_type: MatchType,
526    i: usize,
527    glyphs: &[RawGlyph<T>],
528) -> Result<Option<&'a Ligature>, ParseError> {
529    let glyph_index = glyphs[i].glyph_index;
530    for ligature_subst in subtables {
531        if let Some(ligatureset) = ligature_subst.apply_glyph(glyph_index)? {
532            for ligature in &ligatureset.ligatures {
533                if ligature.matches(match_type, opt_gdef_table, i, glyphs) {
534                    return Ok(Some(ligature));
535                }
536            }
537        }
538    }
539    Ok(None)
540}
541
542fn ligaturesubst<T: GlyphData>(
543    opt_gdef_table: Option<&GDEFTable>,
544    subtables: &[LigatureSubst],
545    match_type: MatchType,
546    i: usize,
547    glyphs: &mut Vec<RawGlyph<T>>,
548) -> Result<Option<(usize, usize)>, ParseError> {
549    match ligaturesubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
550        Some(ligature) => Ok(Some((
551            ligature.component_glyphs.len(),
552            ligature.apply(match_type, opt_gdef_table, i, glyphs),
553        ))),
554        None => Ok(None),
555    }
556}
557
558fn contextsubst_would_apply<'a, T: GlyphData>(
559    opt_gdef_table: Option<&GDEFTable>,
560    subtables: &'a [ContextLookup<GSUB>],
561    match_type: MatchType,
562    i: usize,
563    glyphs: &[RawGlyph<T>],
564) -> Result<Option<Box<SubstContext<'a>>>, ParseError> {
565    let glyph_index = glyphs[i].glyph_index;
566    for context_lookup in subtables {
567        if let Some(context) = context_lookup_info(context_lookup, glyph_index, |context| {
568            context.matches(opt_gdef_table, match_type, glyphs, i)
569        })? {
570            return Ok(Some(context));
571        }
572    }
573    Ok(None)
574}
575
576fn contextsubst<T: GlyphData>(
577    recursion_limit: usize,
578    gsub_cache: &LayoutCache<GSUB>,
579    lookup_list: &LookupList<GSUB>,
580    opt_gdef_table: Option<&GDEFTable>,
581    subtables: &[ContextLookup<GSUB>],
582    feature_tag: u32,
583    match_type: MatchType,
584    i: usize,
585    glyphs: &mut Vec<RawGlyph<T>>,
586) -> Result<Option<(usize, isize)>, ParseError> {
587    match contextsubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
588        Some(subst) => apply_subst_context(
589            recursion_limit,
590            gsub_cache,
591            lookup_list,
592            opt_gdef_table,
593            feature_tag,
594            match_type,
595            &subst,
596            i,
597            glyphs,
598        ),
599        None => Ok(None),
600    }
601}
602
603fn chaincontextsubst_would_apply<'a, T: GlyphData>(
604    opt_gdef_table: Option<&GDEFTable>,
605    subtables: &'a [ChainContextLookup<GSUB>],
606    match_type: MatchType,
607    i: usize,
608    glyphs: &[RawGlyph<T>],
609) -> Result<Option<Box<SubstContext<'a>>>, ParseError> {
610    let glyph_index = glyphs[i].glyph_index;
611    for chain_context_lookup in subtables {
612        if let Some(context) =
613            chain_context_lookup_info(chain_context_lookup, glyph_index, |context| {
614                context.matches(opt_gdef_table, match_type, glyphs, i)
615            })?
616        {
617            return Ok(Some(context));
618        }
619    }
620    Ok(None)
621}
622
623fn chaincontextsubst<T: GlyphData>(
624    recursion_limit: usize,
625    gsub_cache: &LayoutCache<GSUB>,
626    lookup_list: &LookupList<GSUB>,
627    opt_gdef_table: Option<&GDEFTable>,
628    subtables: &[ChainContextLookup<GSUB>],
629    feature_tag: u32,
630    match_type: MatchType,
631    i: usize,
632    glyphs: &mut Vec<RawGlyph<T>>,
633) -> Result<Option<(usize, isize)>, ParseError> {
634    match chaincontextsubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
635        Some(subst) => apply_subst_context(
636            recursion_limit,
637            gsub_cache,
638            lookup_list,
639            opt_gdef_table,
640            feature_tag,
641            match_type,
642            &subst,
643            i,
644            glyphs,
645        ),
646        None => Ok(None),
647    }
648}
649
650fn reversechainsinglesubst_would_apply<T: GlyphData>(
651    opt_gdef_table: Option<&GDEFTable>,
652    subtables: &[ReverseChainSingleSubst],
653    match_type: MatchType,
654    i: usize,
655    glyphs: &[RawGlyph<T>],
656) -> Result<Option<u16>, ParseError> {
657    let glyph_index = glyphs[i].glyph_index;
658    for reversechainsinglesubst in subtables {
659        if let new_glyph_index @ Some(_) = reversechainsinglesubst
660            .apply_glyph(glyph_index, |context| {
661                context.matches(opt_gdef_table, match_type, glyphs, i)
662            })?
663        {
664            return Ok(new_glyph_index);
665        }
666    }
667    Ok(None)
668}
669
670fn reversechainsinglesubst<T: GlyphData>(
671    opt_gdef_table: Option<&GDEFTable>,
672    subtables: &[ReverseChainSingleSubst],
673    match_type: MatchType,
674    i: usize,
675    glyphs: &mut [RawGlyph<T>],
676) -> Result<(), ParseError> {
677    if let Some(output_glyph_index) =
678        reversechainsinglesubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)?
679    {
680        glyphs[i].glyph_index = output_glyph_index;
681        glyphs[i].glyph_origin = GlyphOrigin::Direct;
682    }
683    Ok(())
684}
685
686fn apply_subst_context<T: GlyphData>(
687    recursion_limit: usize,
688    gsub_cache: &LayoutCache<GSUB>,
689    lookup_list: &LookupList<GSUB>,
690    opt_gdef_table: Option<&GDEFTable>,
691    feature_tag: u32,
692    match_type: MatchType,
693    subst: &SubstContext<'_>,
694    i: usize,
695    glyphs: &mut Vec<RawGlyph<T>>,
696) -> Result<Option<(usize, isize)>, ParseError> {
697    let mut changes = 0;
698    let len = match match_type.find_nth(
699        opt_gdef_table,
700        glyphs,
701        i,
702        subst.match_context.input_table.len(),
703    ) {
704        Some(last) => last - i + 1,
705        None => return Ok(None), // FIXME actually an error/impossible?
706    };
707    for (subst_index, subst_lookup_index) in subst.lookup_array {
708        match apply_subst(
709            recursion_limit,
710            gsub_cache,
711            lookup_list,
712            opt_gdef_table,
713            match_type,
714            usize::from(*subst_index),
715            usize::from(*subst_lookup_index),
716            feature_tag,
717            glyphs,
718            i,
719        )? {
720            Some(changes0) => changes += changes0,
721            None => {}
722        }
723    }
724    match checked_add(len, changes) {
725        Some(new_len) => Ok(Some((new_len, changes))),
726        None => panic!("apply_subst_context: len < 0"),
727    }
728}
729
730fn checked_add(base: usize, changes: isize) -> Option<usize> {
731    if changes < 0 {
732        base.checked_sub(changes.wrapping_abs() as usize)
733    } else {
734        base.checked_add(changes as usize)
735    }
736}
737
738fn apply_subst<T: GlyphData>(
739    recursion_limit: usize,
740    gsub_cache: &LayoutCache<GSUB>,
741    lookup_list: &LookupList<GSUB>,
742    opt_gdef_table: Option<&GDEFTable>,
743    parent_match_type: MatchType,
744    subst_index: usize,
745    lookup_index: usize,
746    feature_tag: u32,
747    glyphs: &mut Vec<RawGlyph<T>>,
748    index: usize,
749) -> Result<Option<isize>, ParseError> {
750    let lookup = lookup_list.lookup_cache_gsub(gsub_cache, lookup_index)?;
751    let match_type = MatchType::from_lookup_flag(lookup.lookup_flag);
752    let i = match parent_match_type.find_nth(opt_gdef_table, glyphs, index, subst_index) {
753        Some(index1) => index1,
754        None => return Ok(None), // FIXME error?
755    };
756    match lookup.lookup_subtables {
757        SubstLookup::SingleSubst(ref subtables) => {
758            singlesubst(subtables, feature_tag, &mut glyphs[i])?;
759            Ok(Some(0))
760        }
761        SubstLookup::MultipleSubst(ref subtables) => match multiplesubst(subtables, i, glyphs)? {
762            Some(replace_count) => Ok(Some((replace_count as isize) - 1)),
763            None => Ok(None),
764        },
765        SubstLookup::AlternateSubst(ref subtables) => {
766            alternatesubst(subtables, 0, &mut glyphs[i])?;
767            Ok(Some(0))
768        }
769        SubstLookup::LigatureSubst(ref subtables) => {
770            match ligaturesubst(opt_gdef_table, subtables, match_type, i, glyphs)? {
771                Some((removed_count, _skip_count)) => Ok(Some(-(removed_count as isize))),
772                None => Ok(None), // FIXME error?
773            }
774        }
775        SubstLookup::ContextSubst(ref subtables) => {
776            if recursion_limit > 0 {
777                match contextsubst(
778                    recursion_limit - 1,
779                    gsub_cache,
780                    lookup_list,
781                    opt_gdef_table,
782                    subtables,
783                    feature_tag,
784                    match_type,
785                    i,
786                    glyphs,
787                )? {
788                    Some((_length, change)) => Ok(Some(change)),
789                    None => Ok(None),
790                }
791            } else {
792                Err(ParseError::LimitExceeded)
793            }
794        }
795        SubstLookup::ChainContextSubst(ref subtables) => {
796            if recursion_limit > 0 {
797                match chaincontextsubst(
798                    recursion_limit - 1,
799                    gsub_cache,
800                    lookup_list,
801                    opt_gdef_table,
802                    subtables,
803                    feature_tag,
804                    match_type,
805                    i,
806                    glyphs,
807                )? {
808                    Some((_length, change)) => Ok(Some(change)),
809                    None => Ok(None),
810                }
811            } else {
812                Err(ParseError::LimitExceeded)
813            }
814        }
815        SubstLookup::ReverseChainSingleSubst(ref subtables) => {
816            reversechainsinglesubst(opt_gdef_table, subtables, match_type, i, glyphs)?;
817            Ok(Some(0))
818        }
819    }
820}
821
822// This struct exists to separate `rvrn` out from the rest so that it can be applied
823// early, as recommended by the OpenType spec.
824struct LookupsCustom {
825    rvrn: Option<Vec<u16>>,
826    lookups: BTreeMap<usize, u32>,
827}
828
829fn build_lookups_custom(
830    gsub_table: &LayoutTable<GSUB>,
831    langsys: &LangSys,
832    feature_tags: &[FeatureInfo],
833    feature_variations: Option<&FeatureTableSubstitution<'_>>,
834) -> Result<LookupsCustom, ParseError> {
835    let mut rvrn = None;
836    let mut lookups = BTreeMap::new();
837    for feature_info in feature_tags {
838        let feature_table = gsub_table.find_langsys_feature(
839            langsys,
840            feature_info.feature_tag,
841            feature_variations,
842        )?;
843        if let Some(feature_table) = feature_table {
844            if feature_info.feature_tag == tag::RVRN {
845                rvrn = Some(feature_table.lookup_indices.clone());
846            } else {
847                lookups.extend(
848                    feature_table
849                        .lookup_indices
850                        .iter()
851                        .map(|&lookup_index| (usize::from(lookup_index), feature_info.feature_tag)),
852                )
853            }
854        }
855    }
856    Ok(LookupsCustom { rvrn, lookups })
857}
858
859fn build_lookups_default(
860    gsub_table: &LayoutTable<GSUB>,
861    langsys: &LangSys,
862    feature_masks: FeatureMask,
863    feature_variations: Option<&FeatureTableSubstitution<'_>>,
864) -> Result<Vec<(usize, u32)>, ParseError> {
865    let mut lookups = BTreeMap::new();
866    for (feature_mask, feature_tag) in FEATURE_MASKS {
867        if feature_masks.contains(*feature_mask) {
868            if let Some(feature_table) =
869                gsub_table.find_langsys_feature(langsys, *feature_tag, feature_variations)?
870            {
871                for lookup_index in &feature_table.lookup_indices {
872                    lookups.insert(usize::from(*lookup_index), *feature_tag);
873                }
874            } else if *feature_tag == tag::VRT2 {
875                let vert_tag = tag::VERT;
876                if let Some(feature_table) =
877                    gsub_table.find_langsys_feature(langsys, vert_tag, feature_variations)?
878                {
879                    for lookup_index in &feature_table.lookup_indices {
880                        lookups.insert(usize::from(*lookup_index), vert_tag);
881                    }
882                }
883            }
884        }
885    }
886
887    // note: iter() returns sorted by key
888    Ok(lookups.into_iter().collect())
889}
890
891fn make_supported_features_mask(
892    gsub_table: &LayoutTable<GSUB>,
893    langsys: &LangSys,
894) -> Result<FeatureMask, ParseError> {
895    let mut feature_mask = FeatureMask::empty();
896    for feature_index in langsys.feature_indices_iter() {
897        let feature_record = gsub_table.feature_by_index(*feature_index)?;
898        feature_mask |= FeatureMask::from_tag(feature_record.feature_tag);
899    }
900    Ok(feature_mask)
901}
902
903fn lang_tag_key(opt_lang_tag: Option<u32>) -> u32 {
904    // `DFLT` is not a valid lang tag so we use it to indicate the default
905    opt_lang_tag.unwrap_or(tag::DFLT)
906}
907
908fn get_supported_features(
909    gsub_cache: &LayoutCache<GSUB>,
910    script_tag: u32,
911    opt_lang_tag: Option<u32>,
912) -> Result<FeatureMask, ParseError> {
913    let feature_mask = match gsub_cache
914        .supported_features
915        .borrow_mut()
916        .entry((script_tag, lang_tag_key(opt_lang_tag)))
917    {
918        Entry::Occupied(entry) => FeatureMask::from_bits_truncate(*entry.get()),
919        Entry::Vacant(entry) => {
920            let gsub_table = &gsub_cache.layout_table;
921            let feature_mask =
922                if let Some(script) = gsub_table.find_script_or_default(script_tag)? {
923                    if let Some(langsys) = script.find_langsys_or_default(opt_lang_tag)? {
924                        make_supported_features_mask(gsub_table, langsys)?
925                    } else {
926                        FeatureMask::empty()
927                    }
928                } else {
929                    FeatureMask::empty()
930                };
931            entry.insert(feature_mask.bits());
932            feature_mask
933        }
934    };
935    Ok(feature_mask)
936}
937
938fn find_alternate(features_list: &[FeatureInfo], feature_tag: u32) -> Option<usize> {
939    for feature_info in features_list {
940        if feature_info.feature_tag == feature_tag {
941            return feature_info.alternate;
942        }
943    }
944    None
945}
946
947/// Perform glyph substitution according to the supplied features, script and language.
948///
949/// `dotted_circle_index` is the glyph index of U+25CC DOTTED CIRCLE: ◌. This is inserted
950/// when shaping some complex scripts where the input text contains incomplete syllables.
951/// If you have an instance of `FontDataImpl` the glyph index can be retrieved via the
952/// `lookup_glyph_index` method.
953///
954/// ## Example
955///
956/// The following shows a complete example of loading a font, mapping text to glyphs, and
957/// applying glyph substitution.
958///
959/// ```
960/// use std::convert::TryFrom;
961/// use std::error::Error;
962/// use std::rc::Rc;
963///
964/// use allsorts::binary::read::ReadScope;
965/// use allsorts::error::ParseError;
966/// use allsorts::font::{MatchingPresentation};
967/// use allsorts::font_data::FontData;
968/// use allsorts::gsub::{Features, GlyphOrigin, FeatureMask, RawGlyph, RawGlyphFlags};
969/// use allsorts::tinyvec::tiny_vec;
970/// use allsorts::unicode::VariationSelector;
971/// use allsorts::DOTTED_CIRCLE;
972/// use allsorts::{gsub, tag, Font};
973///
974/// fn shape(text: &str) -> Result<Vec<RawGlyph<()>>, Box<dyn Error>> {
975///     let script = tag::from_string("LATN")?;
976///     let lang = tag::from_string("DFLT")?;
977///     let buffer = std::fs::read("tests/fonts/opentype/Klei.otf")
978///         .expect("unable to read Klei.otf");
979///     let scope = ReadScope::new(&buffer);
980///     let font_file = scope.read::<FontData<'_>>()?;
981///     // Use a different index to access other fonts in a font collection (E.g. TTC)
982///     let provider = font_file.table_provider(0)?;
983///     let mut font = Font::new(provider)?;
984///
985///     let opt_gsub_cache = font.gsub_cache()?;
986///     let opt_gpos_cache = font.gpos_cache()?;
987///     let opt_gdef_table = font.gdef_table()?;
988///     let opt_gdef_table = opt_gdef_table.as_ref().map(Rc::as_ref);
989///
990///     // Map glyphs
991///     //
992///     // We look ahead in the char stream for variation selectors. If one is found it is used for
993///     // mapping the current glyph. When a variation selector is reached in the stream it is
994///     // skipped as it was handled as part of the preceding character.
995///     let mut chars_iter = text.chars().peekable();
996///     let mut glyphs = Vec::new();
997///     while let Some(ch) = chars_iter.next() {
998///         match VariationSelector::try_from(ch) {
999///             Ok(_) => {} // filter out variation selectors
1000///             Err(()) => {
1001///                 let vs = chars_iter
1002///                     .peek()
1003///                     .and_then(|&next| VariationSelector::try_from(next).ok());
1004///                 let (glyph_index, used_variation) = font.lookup_glyph_index(
1005///                     ch,
1006///                     MatchingPresentation::NotRequired,
1007///                     vs,
1008///                 );
1009///                 let glyph = RawGlyph {
1010///                     unicodes: tiny_vec![[char; 1] => ch],
1011///                     glyph_index: glyph_index,
1012///                     liga_component_pos: 0,
1013///                     glyph_origin: GlyphOrigin::Char(ch),
1014///                     flags: RawGlyphFlags::empty(),
1015///                     extra_data: (),
1016///                     variation: Some(used_variation),
1017///                 };
1018///                 glyphs.push(glyph);
1019///             }
1020///         }
1021///     }
1022///
1023///     let (dotted_circle_index, _) = font.lookup_glyph_index(
1024///         DOTTED_CIRCLE,
1025///         MatchingPresentation::NotRequired,
1026///         None,
1027///     );
1028///
1029///     // If the font was a variable font you would want to supply the variation tuple
1030///     let tuple = None;
1031///
1032///     // Apply gsub if table is present
1033///     let num_glyphs = font.num_glyphs();
1034///     if let Some(gsub_cache) = opt_gsub_cache {
1035///         gsub::apply(
1036///             dotted_circle_index,
1037///             &gsub_cache,
1038///             opt_gdef_table,
1039///             script,
1040///             Some(lang),
1041///             &Features::Mask(FeatureMask::default()),
1042///             tuple,
1043///             num_glyphs,
1044///             &mut glyphs,
1045///         )?;
1046///     }
1047///
1048///     // This is where you would apply `gpos` if the table is present.
1049///
1050///     Ok(glyphs)
1051/// }
1052///
1053/// match shape("This is the first example.") {
1054///     Ok(glyphs) => {
1055///         assert!(!glyphs.is_empty());
1056///     }
1057///     Err(err) => panic!("Unable to shape text: {}", err),
1058/// }
1059/// ```
1060pub fn apply(
1061    dotted_circle_index: u16,
1062    gsub_cache: &LayoutCache<GSUB>,
1063    opt_gdef_table: Option<&GDEFTable>,
1064    script_tag: u32,
1065    opt_lang_tag: Option<u32>,
1066    features: &Features,
1067    tuple: Option<Tuple<'_>>,
1068    num_glyphs: u16,
1069    glyphs: &mut Vec<RawGlyph<()>>,
1070) -> Result<(), ShapingError> {
1071    match features {
1072        Features::Custom(features_list) => gsub_apply_custom(
1073            gsub_cache,
1074            opt_gdef_table,
1075            script_tag,
1076            opt_lang_tag,
1077            features_list,
1078            tuple,
1079            num_glyphs,
1080            glyphs,
1081        ),
1082        Features::Mask(feature_mask) => gsub_apply_default(
1083            dotted_circle_index,
1084            gsub_cache,
1085            opt_gdef_table,
1086            script_tag,
1087            opt_lang_tag,
1088            *feature_mask,
1089            tuple,
1090            num_glyphs,
1091            glyphs,
1092        ),
1093    }
1094}
1095
1096fn gsub_apply_custom(
1097    gsub_cache: &LayoutCache<GSUB>,
1098    opt_gdef_table: Option<&GDEFTable>,
1099    script_tag: u32,
1100    opt_lang_tag: Option<u32>,
1101    features_list: &[FeatureInfo],
1102    tuple: Option<Tuple<'_>>,
1103    num_glyphs: u16,
1104    glyphs: &mut Vec<RawGlyph<()>>,
1105) -> Result<(), ShapingError> {
1106    let gsub_table = &gsub_cache.layout_table;
1107    if let Some(script) = gsub_table.find_script_or_default(script_tag)? {
1108        if let Some(langsys) = script.find_langsys_or_default(opt_lang_tag)? {
1109            let feature_variations = gsub_table.feature_variations(tuple)?;
1110            let feature_variations = feature_variations.as_ref();
1111            let lookups =
1112                build_lookups_custom(gsub_table, langsys, features_list, feature_variations)?;
1113
1114            // Apply rvrn early if present:
1115            //
1116            // "It should be processed early in GSUB processing, before application of the
1117            // localized forms feature or features related to shaping of complex scripts or
1118            // discretionary typographic effects."
1119            //
1120            // https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#tag-rvrn
1121            if let Some(rvrn_lookups) = lookups.rvrn {
1122                for lookup_index in rvrn_lookups {
1123                    gsub_apply_lookup(
1124                        gsub_cache,
1125                        gsub_table,
1126                        opt_gdef_table,
1127                        usize::from(lookup_index),
1128                        tag::RVRN,
1129                        None,
1130                        glyphs,
1131                        0,
1132                        glyphs.len(),
1133                        |_| true,
1134                    )?;
1135                }
1136            }
1137
1138            // note: iter() returns sorted by key
1139            for (lookup_index, feature_tag) in lookups.lookups {
1140                let alternate = find_alternate(features_list, feature_tag);
1141                if feature_tag == tag::FINA && !glyphs.is_empty() {
1142                    gsub_apply_lookup(
1143                        gsub_cache,
1144                        gsub_table,
1145                        opt_gdef_table,
1146                        lookup_index,
1147                        feature_tag,
1148                        alternate,
1149                        glyphs,
1150                        glyphs.len() - 1,
1151                        1,
1152                        |_| true,
1153                    )?;
1154                } else {
1155                    gsub_apply_lookup(
1156                        gsub_cache,
1157                        gsub_table,
1158                        opt_gdef_table,
1159                        lookup_index,
1160                        feature_tag,
1161                        alternate,
1162                        glyphs,
1163                        0,
1164                        glyphs.len(),
1165                        |_| true,
1166                    )?;
1167                }
1168            }
1169        }
1170    }
1171    replace_missing_glyphs(glyphs, num_glyphs);
1172    Ok(())
1173}
1174
1175pub fn replace_missing_glyphs<T: GlyphData>(glyphs: &mut [RawGlyph<T>], num_glyphs: u16) {
1176    for glyph in glyphs.iter_mut() {
1177        if glyph.glyph_index >= num_glyphs {
1178            glyph.unicodes = tiny_vec![];
1179            glyph.glyph_index = 0;
1180            glyph.liga_component_pos = 0;
1181            glyph.glyph_origin = GlyphOrigin::Direct;
1182            glyph.flags = RawGlyphFlags::empty();
1183            glyph.variation = None;
1184        }
1185    }
1186}
1187
1188fn strip_joiners<T: GlyphData>(glyphs: &mut Vec<RawGlyph<T>>) {
1189    glyphs.retain(|g| match g.glyph_origin {
1190        GlyphOrigin::Char('\u{200C}') => false,
1191        GlyphOrigin::Char('\u{200D}') => false,
1192        _ => true,
1193    })
1194}
1195
1196bitflags! {
1197    // It is possible to squeeze these flags into a `u32` if we represent features
1198    // that are never applied together as numbers instead of separate bits.
1199    pub struct FeatureMask: u64 {
1200        const ABVF = 1 << 0;
1201        const ABVS = 1 << 1;
1202        const AFRC = 1 << 2;
1203        const AKHN = 1 << 3;
1204        const BLWF = 1 << 4;
1205        const BLWS = 1 << 5;
1206        const C2SC = 1 << 6;
1207        const CALT = 1 << 7;
1208        const CCMP = 1 << 8;
1209        const CFAR = 1 << 9;
1210        const CJCT = 1 << 10;
1211        const CLIG = 1 << 11;
1212        const DLIG = 1 << 12;
1213        const FINA = 1 << 13;
1214        const FIN2 = 1 << 14;
1215        const FIN3 = 1 << 15;
1216        const FRAC = 1 << 16;
1217        const HALF = 1 << 17;
1218        const HALN = 1 << 18;
1219        const HLIG = 1 << 19;
1220        const INIT = 1 << 20;
1221        const ISOL = 1 << 21;
1222        const LIGA = 1 << 22;
1223        const LNUM = 1 << 23;
1224        const LOCL = 1 << 24;
1225        const MEDI = 1 << 25;
1226        const MED2 = 1 << 26;
1227        const MSET = 1 << 27;
1228        const NUKT = 1 << 28;
1229        const ONUM = 1 << 29;
1230        const ORDN = 1 << 30;
1231        const PNUM = 1 << 31;
1232        const PREF = 1 << 32;
1233        const PRES = 1 << 33;
1234        const PSTF = 1 << 34;
1235        const PSTS = 1 << 35;
1236        const RCLT = 1 << 36;
1237        const RKRF = 1 << 37;
1238        const RLIG = 1 << 38;
1239        const RPHF = 1 << 39;
1240        const SMCP = 1 << 40;
1241        const TNUM = 1 << 41;
1242        const VATU = 1 << 42;
1243        const VRT2_OR_VERT = 1 << 43;
1244        const ZERO = 1 << 44;
1245        const RVRN = 1 << 45;
1246    }
1247}
1248const FEATURE_MASKS: &[(FeatureMask, u32)] = &[
1249    (FeatureMask::ABVF, tag::ABVF),
1250    (FeatureMask::ABVS, tag::ABVS),
1251    (FeatureMask::AFRC, tag::AFRC),
1252    (FeatureMask::AKHN, tag::AKHN),
1253    (FeatureMask::BLWF, tag::BLWF),
1254    (FeatureMask::BLWS, tag::BLWS),
1255    (FeatureMask::C2SC, tag::C2SC),
1256    (FeatureMask::CALT, tag::CALT),
1257    (FeatureMask::CCMP, tag::CCMP),
1258    (FeatureMask::CFAR, tag::CFAR),
1259    (FeatureMask::CJCT, tag::CJCT),
1260    (FeatureMask::CLIG, tag::CLIG),
1261    (FeatureMask::DLIG, tag::DLIG),
1262    (FeatureMask::FINA, tag::FINA),
1263    (FeatureMask::FIN2, tag::FIN2),
1264    (FeatureMask::FIN3, tag::FIN3),
1265    (FeatureMask::FRAC, tag::FRAC),
1266    (FeatureMask::HALF, tag::HALF),
1267    (FeatureMask::HALN, tag::HALN),
1268    (FeatureMask::HLIG, tag::HLIG),
1269    (FeatureMask::INIT, tag::INIT),
1270    (FeatureMask::ISOL, tag::ISOL),
1271    (FeatureMask::LIGA, tag::LIGA),
1272    (FeatureMask::LNUM, tag::LNUM),
1273    (FeatureMask::LOCL, tag::LOCL),
1274    (FeatureMask::MEDI, tag::MEDI),
1275    (FeatureMask::MED2, tag::MED2),
1276    (FeatureMask::MSET, tag::MSET),
1277    (FeatureMask::NUKT, tag::NUKT),
1278    (FeatureMask::ONUM, tag::ONUM),
1279    (FeatureMask::ORDN, tag::ORDN),
1280    (FeatureMask::PNUM, tag::PNUM),
1281    (FeatureMask::PREF, tag::PREF),
1282    (FeatureMask::PRES, tag::PRES),
1283    (FeatureMask::PSTF, tag::PSTF),
1284    (FeatureMask::PSTS, tag::PSTS),
1285    (FeatureMask::RCLT, tag::RCLT),
1286    (FeatureMask::RKRF, tag::RKRF),
1287    (FeatureMask::RLIG, tag::RLIG),
1288    (FeatureMask::RPHF, tag::RPHF),
1289    (FeatureMask::RVRN, tag::RVRN),
1290    (FeatureMask::SMCP, tag::SMCP),
1291    (FeatureMask::TNUM, tag::TNUM),
1292    (FeatureMask::VATU, tag::VATU),
1293    (FeatureMask::VRT2_OR_VERT, tag::VRT2),
1294    (FeatureMask::ZERO, tag::ZERO),
1295];
1296
1297impl FeatureMask {
1298    pub fn from_tag(tag: u32) -> FeatureMask {
1299        match tag {
1300            tag::ABVF => FeatureMask::ABVF,
1301            tag::ABVS => FeatureMask::ABVS,
1302            tag::AFRC => FeatureMask::AFRC,
1303            tag::AKHN => FeatureMask::AKHN,
1304            tag::BLWF => FeatureMask::BLWF,
1305            tag::BLWS => FeatureMask::BLWS,
1306            tag::C2SC => FeatureMask::C2SC,
1307            tag::CALT => FeatureMask::CALT,
1308            tag::CCMP => FeatureMask::CCMP,
1309            tag::CFAR => FeatureMask::CFAR,
1310            tag::CJCT => FeatureMask::CJCT,
1311            tag::CLIG => FeatureMask::CLIG,
1312            tag::DLIG => FeatureMask::DLIG,
1313            tag::FINA => FeatureMask::FINA,
1314            tag::FIN2 => FeatureMask::FIN2,
1315            tag::FIN3 => FeatureMask::FIN3,
1316            tag::FRAC => FeatureMask::FRAC,
1317            tag::HALF => FeatureMask::HALF,
1318            tag::HALN => FeatureMask::HALN,
1319            tag::HLIG => FeatureMask::HLIG,
1320            tag::INIT => FeatureMask::INIT,
1321            tag::ISOL => FeatureMask::ISOL,
1322            tag::LIGA => FeatureMask::LIGA,
1323            tag::LNUM => FeatureMask::LNUM,
1324            tag::LOCL => FeatureMask::LOCL,
1325            tag::MEDI => FeatureMask::MEDI,
1326            tag::MED2 => FeatureMask::MED2,
1327            tag::MSET => FeatureMask::MSET,
1328            tag::NUKT => FeatureMask::NUKT,
1329            tag::ONUM => FeatureMask::ONUM,
1330            tag::ORDN => FeatureMask::ORDN,
1331            tag::PNUM => FeatureMask::PNUM,
1332            tag::PREF => FeatureMask::PREF,
1333            tag::PRES => FeatureMask::PRES,
1334            tag::PSTF => FeatureMask::PSTF,
1335            tag::PSTS => FeatureMask::PSTS,
1336            tag::RCLT => FeatureMask::RCLT,
1337            tag::RKRF => FeatureMask::RKRF,
1338            tag::RLIG => FeatureMask::RLIG,
1339            tag::RPHF => FeatureMask::RPHF,
1340            tag::RVRN => FeatureMask::RVRN,
1341            tag::SMCP => FeatureMask::SMCP,
1342            tag::TNUM => FeatureMask::TNUM,
1343            tag::VATU => FeatureMask::VATU,
1344            tag::VERT => FeatureMask::VRT2_OR_VERT,
1345            tag::VRT2 => FeatureMask::VRT2_OR_VERT,
1346            tag::ZERO => FeatureMask::ZERO,
1347            _ => FeatureMask::empty(),
1348        }
1349    }
1350
1351    pub fn iter(&self) -> impl Iterator<Item = FeatureInfo> + '_ {
1352        let limit = if self.is_empty() {
1353            // Fast path for empty mask
1354            0
1355        } else {
1356            FeatureMask::all().bits().count_ones()
1357        };
1358        (0..limit).filter_map(move |i| {
1359            FeatureMask::from_bits(1 << i).and_then(|flag| {
1360                if self.contains(flag) {
1361                    Some(FeatureInfo {
1362                        // unwrap is safe as we know flag was constructed from a single enabled bit
1363                        feature_tag: flag.as_tag().unwrap(),
1364                        alternate: None,
1365                    })
1366                } else {
1367                    None
1368                }
1369            })
1370        })
1371    }
1372
1373    fn as_tag(&self) -> Option<u32> {
1374        FEATURE_MASKS
1375            .iter()
1376            .find(|(mask, _)| self == mask)
1377            .map(|(_, tag)| *tag)
1378    }
1379}
1380
1381impl Default for FeatureMask {
1382    fn default() -> Self {
1383        FeatureMask::CCMP
1384            | FeatureMask::RLIG
1385            | FeatureMask::CLIG
1386            | FeatureMask::LIGA
1387            | FeatureMask::LOCL
1388            | FeatureMask::CALT
1389    }
1390}
1391
1392pub fn features_supported(
1393    gsub_cache: &LayoutCache<GSUB>,
1394    script_tag: u32,
1395    opt_lang_tag: Option<u32>,
1396    feature_mask: FeatureMask,
1397) -> Result<bool, ShapingError> {
1398    let supported_features = get_supported_features(gsub_cache, script_tag, opt_lang_tag)?;
1399    Ok(supported_features.contains(feature_mask))
1400}
1401
1402pub fn get_lookups_cache_index(
1403    gsub_cache: &LayoutCache<GSUB>,
1404    script_tag: u32,
1405    opt_lang_tag: Option<u32>,
1406    feature_variations: Option<&FeatureTableSubstitution<'_>>,
1407    feature_mask: FeatureMask,
1408) -> Result<usize, ParseError> {
1409    let index = match gsub_cache.lookups_index.borrow_mut().entry((
1410        script_tag,
1411        lang_tag_key(opt_lang_tag),
1412        feature_mask.bits(),
1413    )) {
1414        Entry::Occupied(entry) => *entry.get(),
1415        Entry::Vacant(entry) => {
1416            let gsub_table = &gsub_cache.layout_table;
1417            if let Some(script) = gsub_table.find_script_or_default(script_tag)? {
1418                if let Some(langsys) = script.find_langsys_or_default(opt_lang_tag)? {
1419                    let lookups = build_lookups_default(
1420                        gsub_table,
1421                        langsys,
1422                        feature_mask,
1423                        feature_variations,
1424                    )?;
1425                    let index = gsub_cache.cached_lookups.borrow().len();
1426                    gsub_cache.cached_lookups.borrow_mut().push(lookups);
1427                    *entry.insert(index)
1428                } else {
1429                    *entry.insert(0)
1430                }
1431            } else {
1432                *entry.insert(0)
1433            }
1434        }
1435    };
1436    Ok(index)
1437}
1438
1439fn gsub_apply_default(
1440    dotted_circle_index: u16,
1441    gsub_cache: &LayoutCache<GSUB>,
1442    opt_gdef_table: Option<&GDEFTable>,
1443    script_tag: u32,
1444    opt_lang_tag: Option<u32>,
1445    mut feature_mask: FeatureMask,
1446    tuple: Option<Tuple<'_>>,
1447    num_glyphs: u16,
1448    glyphs: &mut Vec<RawGlyph<()>>,
1449) -> Result<(), ShapingError> {
1450    let gsub_table = &gsub_cache.layout_table;
1451    let feature_variations = gsub_table.feature_variations(tuple)?;
1452    let feature_variations = feature_variations.as_ref();
1453
1454    // Apply rvrn early if font is variable:
1455    //
1456    // "The 'rvrn' feature is mandatory: it should be active by default and not directly exposed to
1457    // user control."
1458    //
1459    // "It should be processed early in GSUB processing, before application of the localized forms
1460    // feature or features related to shaping of complex scripts or discretionary typographic
1461    // effects."
1462    //
1463    // https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#tag-rvrn
1464    if tuple.is_some() {
1465        apply_rvrn(
1466            &gsub_cache,
1467            opt_gdef_table,
1468            script_tag,
1469            opt_lang_tag,
1470            feature_variations,
1471            glyphs,
1472        )?;
1473    }
1474    feature_mask.remove(FeatureMask::RVRN);
1475
1476    match ScriptType::from(script_tag) {
1477        ScriptType::Arabic => scripts::arabic::gsub_apply_arabic(
1478            gsub_cache,
1479            gsub_table,
1480            opt_gdef_table,
1481            script_tag,
1482            opt_lang_tag,
1483            feature_variations,
1484            glyphs,
1485        )?,
1486        ScriptType::Indic => scripts::indic::gsub_apply_indic(
1487            dotted_circle_index,
1488            gsub_cache,
1489            gsub_table,
1490            opt_gdef_table,
1491            script_tag,
1492            opt_lang_tag,
1493            feature_variations,
1494            glyphs,
1495        )?,
1496        ScriptType::Khmer => scripts::khmer::gsub_apply_khmer(
1497            dotted_circle_index,
1498            gsub_cache,
1499            gsub_table,
1500            opt_gdef_table,
1501            script_tag,
1502            opt_lang_tag,
1503            feature_variations,
1504            glyphs,
1505        )?,
1506        ScriptType::Syriac => scripts::syriac::gsub_apply_syriac(
1507            gsub_cache,
1508            gsub_table,
1509            opt_gdef_table,
1510            script_tag,
1511            opt_lang_tag,
1512            feature_variations,
1513            glyphs,
1514        )?,
1515        ScriptType::ThaiLao => scripts::thai_lao::gsub_apply_thai_lao(
1516            gsub_cache,
1517            gsub_table,
1518            opt_gdef_table,
1519            script_tag,
1520            opt_lang_tag,
1521            feature_variations,
1522            glyphs,
1523        )?,
1524        ScriptType::Default => {
1525            feature_mask &= get_supported_features(gsub_cache, script_tag, opt_lang_tag)?;
1526            if feature_mask.contains(FeatureMask::FRAC) {
1527                let index_frac = get_lookups_cache_index(
1528                    gsub_cache,
1529                    script_tag,
1530                    opt_lang_tag,
1531                    feature_variations,
1532                    feature_mask,
1533                )?;
1534                feature_mask.remove(FeatureMask::FRAC);
1535                let index = get_lookups_cache_index(
1536                    gsub_cache,
1537                    script_tag,
1538                    opt_lang_tag,
1539                    feature_variations,
1540                    feature_mask,
1541                )?;
1542                let lookups = &gsub_cache.cached_lookups.borrow()[index];
1543                let lookups_frac = &gsub_cache.cached_lookups.borrow()[index_frac];
1544                gsub_apply_lookups_frac(
1545                    gsub_cache,
1546                    gsub_table,
1547                    opt_gdef_table,
1548                    lookups,
1549                    lookups_frac,
1550                    glyphs,
1551                )?;
1552            } else {
1553                let index = get_lookups_cache_index(
1554                    gsub_cache,
1555                    script_tag,
1556                    opt_lang_tag,
1557                    feature_variations,
1558                    feature_mask,
1559                )?;
1560                let lookups = &gsub_cache.cached_lookups.borrow()[index];
1561                gsub_apply_lookups(gsub_cache, gsub_table, opt_gdef_table, lookups, glyphs)?;
1562            }
1563        }
1564    }
1565
1566    strip_joiners(glyphs);
1567    replace_missing_glyphs(glyphs, num_glyphs);
1568    Ok(())
1569}
1570
1571fn gsub_apply_lookups(
1572    gsub_cache: &LayoutCache<GSUB>,
1573    gsub_table: &LayoutTable<GSUB>,
1574    opt_gdef_table: Option<&GDEFTable>,
1575    lookups: &[(usize, u32)],
1576    glyphs: &mut Vec<RawGlyph<()>>,
1577) -> Result<(), ShapingError> {
1578    gsub_apply_lookups_impl(
1579        gsub_cache,
1580        gsub_table,
1581        opt_gdef_table,
1582        lookups,
1583        glyphs,
1584        0,
1585        glyphs.len(),
1586    )?;
1587    Ok(())
1588}
1589
1590fn gsub_apply_lookups_impl(
1591    gsub_cache: &LayoutCache<GSUB>,
1592    gsub_table: &LayoutTable<GSUB>,
1593    opt_gdef_table: Option<&GDEFTable>,
1594    lookups: &[(usize, u32)],
1595    glyphs: &mut Vec<RawGlyph<()>>,
1596    start: usize,
1597    mut length: usize,
1598) -> Result<usize, ShapingError> {
1599    for (lookup_index, feature_tag) in lookups {
1600        length = gsub_apply_lookup(
1601            gsub_cache,
1602            gsub_table,
1603            opt_gdef_table,
1604            *lookup_index,
1605            *feature_tag,
1606            None,
1607            glyphs,
1608            start,
1609            length,
1610            |_| true,
1611        )?;
1612    }
1613    Ok(length)
1614}
1615
1616fn gsub_apply_lookups_frac(
1617    gsub_cache: &LayoutCache<GSUB>,
1618    gsub_table: &LayoutTable<GSUB>,
1619    opt_gdef_table: Option<&GDEFTable>,
1620    lookups: &[(usize, u32)],
1621    lookups_frac: &[(usize, u32)],
1622    glyphs: &mut Vec<RawGlyph<()>>,
1623) -> Result<(), ShapingError> {
1624    let mut i = 0;
1625    while i < glyphs.len() {
1626        if let Some((start_pos, _slash_pos, end_pos)) = find_fraction(&glyphs[i..]) {
1627            if start_pos > 0 {
1628                i += gsub_apply_lookups_impl(
1629                    gsub_cache,
1630                    gsub_table,
1631                    opt_gdef_table,
1632                    lookups,
1633                    glyphs,
1634                    i,
1635                    start_pos,
1636                )?;
1637            }
1638            i += gsub_apply_lookups_impl(
1639                gsub_cache,
1640                gsub_table,
1641                opt_gdef_table,
1642                lookups_frac,
1643                glyphs,
1644                i,
1645                end_pos - start_pos + 1,
1646            )?;
1647        } else {
1648            gsub_apply_lookups_impl(
1649                gsub_cache,
1650                gsub_table,
1651                opt_gdef_table,
1652                lookups,
1653                glyphs,
1654                i,
1655                glyphs.len() - i,
1656            )?;
1657            break;
1658        }
1659    }
1660    Ok(())
1661}
1662
1663fn find_fraction(glyphs: &[RawGlyph<()>]) -> Option<(usize, usize, usize)> {
1664    let slash_pos = glyphs
1665        .iter()
1666        .position(|g| g.glyph_origin == GlyphOrigin::Char('/'))?;
1667    let mut start_pos = slash_pos;
1668    while start_pos > 0 {
1669        match glyphs[start_pos - 1].glyph_origin {
1670            GlyphOrigin::Char(c) if c.is_ascii_digit() => {
1671                start_pos -= 1;
1672            }
1673            _ => break,
1674        }
1675    }
1676    let mut end_pos = slash_pos;
1677    while end_pos + 1 < glyphs.len() {
1678        match glyphs[end_pos + 1].glyph_origin {
1679            GlyphOrigin::Char(c) if c.is_ascii_digit() => {
1680                end_pos += 1;
1681            }
1682            _ => break,
1683        }
1684    }
1685    if start_pos < slash_pos && slash_pos < end_pos {
1686        Some((start_pos, slash_pos, end_pos))
1687    } else {
1688        None
1689    }
1690}
1691
1692fn apply_rvrn(
1693    gsub_cache: &LayoutCache<GSUB>,
1694    opt_gdef_table: Option<&GDEFTable>,
1695    script_tag: u32,
1696    opt_lang_tag: Option<u32>,
1697    feature_variations: Option<&FeatureTableSubstitution<'_>>,
1698    glyphs: &mut Vec<RawGlyph<()>>,
1699) -> Result<(), ShapingError> {
1700    let gsub_table = &gsub_cache.layout_table;
1701    let index = get_lookups_cache_index(
1702        gsub_cache,
1703        script_tag,
1704        opt_lang_tag,
1705        feature_variations,
1706        FeatureMask::RVRN,
1707    )?;
1708    let lookups = &gsub_cache.cached_lookups.borrow()[index];
1709    gsub_apply_lookups(gsub_cache, gsub_table, opt_gdef_table, lookups, glyphs)?;
1710    Ok(())
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715    use super::*;
1716
1717    #[test]
1718    fn feature_mask_iter() {
1719        let mask = FeatureMask::empty();
1720        assert_eq!(mask.iter().count(), 0);
1721
1722        let mask = FeatureMask::default();
1723        let expected = &[
1724            FeatureInfo {
1725                feature_tag: tag::CALT,
1726                alternate: None,
1727            },
1728            FeatureInfo {
1729                feature_tag: tag::CCMP,
1730                alternate: None,
1731            },
1732            FeatureInfo {
1733                feature_tag: tag::CLIG,
1734                alternate: None,
1735            },
1736            FeatureInfo {
1737                feature_tag: tag::LIGA,
1738                alternate: None,
1739            },
1740            FeatureInfo {
1741                feature_tag: tag::LOCL,
1742                alternate: None,
1743            },
1744            FeatureInfo {
1745                feature_tag: tag::RLIG,
1746                alternate: None,
1747            },
1748        ];
1749        assert_eq!(&mask.iter().collect::<Vec<_>>(), expected);
1750    }
1751}