allsorts_subset_browser/
gpos.rs

1//! Glyph positioning (`gpos`) implementation.
2//!
3//! > The Glyph Positioning table (GPOS) provides precise control over glyph placement for
4//! > sophisticated text layout and rendering in each script and language system that a font
5//! > supports.
6//!
7//! — <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos>
8use itertools::Itertools;
9use tinyvec::tiny_vec;
10use unicode_general_category::GeneralCategory;
11
12use crate::context::{ContextLookupHelper, Glyph, LookupFlag, MatchType};
13use crate::error::ParseError;
14use crate::gdef::gdef_is_mark;
15use crate::gsub::{FeatureInfo, Features, RawGlyph};
16use crate::layout::{
17    chain_context_lookup_info, context_lookup_info, Adjust, Anchor, ChainContextLookup,
18    ContextLookup, CursivePos, GDEFTable, LangSys, LayoutCache, LayoutTable, LookupList,
19    MarkBasePos, MarkLigPos, PairPos, PosLookup, SinglePos, ValueRecord, VariationIndex, GPOS,
20};
21use crate::scripts;
22use crate::scripts::ScriptType;
23use crate::tables::variable_fonts::fvar::Tuple;
24use crate::tables::variable_fonts::owned;
25use crate::tag;
26
27type PosContext<'a> = ContextLookupHelper<'a, GPOS>;
28
29/// Apply glyph positioning rules to glyph `Info`.
30pub fn apply(
31    gpos_cache: &LayoutCache<GPOS>,
32    opt_gdef_table: Option<&GDEFTable>,
33    kerning: bool,
34    features: &Features,
35    tuple: Option<Tuple<'_>>,
36    script_tag: u32,
37    opt_lang_tag: Option<u32>,
38    infos: &mut [Info],
39) -> Result<(), ParseError> {
40    let gpos_table = &gpos_cache.layout_table;
41    let script_type = ScriptType::from(script_tag);
42
43    let script = match script_type {
44        ScriptType::Indic => {
45            let indic2_tag = scripts::indic::indic2_tag(script_tag);
46            match gpos_table.find_script(indic2_tag)? {
47                Some(script) => script,
48                None => match gpos_table.find_script_or_default(script_tag)? {
49                    Some(script) => script,
50                    None => return Ok(()),
51                },
52            }
53        }
54        _ => match gpos_table.find_script_or_default(script_tag)? {
55            Some(script) => script,
56            None => return Ok(()),
57        },
58    };
59
60    let langsys = match script.find_langsys_or_default(opt_lang_tag)? {
61        Some(langsys) => langsys,
62        None => return Ok(()),
63    };
64
65    let base_features: &[u32] = match script_type {
66        ScriptType::Arabic => &[tag::CURS, tag::KERN, tag::MARK, tag::MKMK],
67        ScriptType::Indic => &[
68            tag::ABVM,
69            tag::BLWM,
70            tag::DIST,
71            tag::KERN,
72            tag::MARK,
73            tag::MKMK,
74        ],
75        ScriptType::Khmer => &[tag::ABVM, tag::BLWM, tag::DIST, tag::MARK, tag::MKMK],
76        ScriptType::Syriac => &[tag::CURS, tag::KERN, tag::MARK, tag::MKMK],
77        ScriptType::ThaiLao => &[tag::KERN, tag::MARK, tag::MKMK],
78        ScriptType::Default if kerning => &[tag::DIST, tag::KERN, tag::MARK, tag::MKMK],
79        ScriptType::Default => &[tag::DIST, tag::MARK, tag::MKMK],
80    };
81
82    apply_features(
83        gpos_cache,
84        gpos_table,
85        opt_gdef_table,
86        langsys,
87        base_features.iter().map(|&feature_tag| FeatureInfo {
88            feature_tag,
89            alternate: None,
90        }),
91        tuple,
92        infos,
93    )?;
94    match features {
95        Features::Custom(custom) => apply_features(
96            gpos_cache,
97            gpos_table,
98            opt_gdef_table,
99            langsys,
100            custom.iter().copied(),
101            tuple,
102            infos,
103        ),
104        Features::Mask(mask) => apply_features(
105            gpos_cache,
106            gpos_table,
107            opt_gdef_table,
108            langsys,
109            mask.iter(),
110            tuple,
111            infos,
112        ),
113    }
114}
115
116/// Apply glyph positioning using specified OpenType features.
117///
118/// Generally prefer to use [apply], which will enable features based on script and language.
119/// Use this method if you need more low-level control over the enabled features.
120pub fn apply_features(
121    gpos_cache: &LayoutCache<GPOS>,
122    gpos_table: &LayoutTable<GPOS>,
123    opt_gdef_table: Option<&GDEFTable>,
124    langsys: &LangSys,
125    features: impl Iterator<Item = FeatureInfo>,
126    tuple: Option<Tuple<'_>>,
127    infos: &mut [Info],
128) -> Result<(), ParseError> {
129    let mut lookup_indices = tiny_vec!([u16; 128]);
130    let feature_variations = gpos_table.feature_variations(tuple)?;
131    for feature in features {
132        let feature_table = gpos_table.find_langsys_feature(
133            langsys,
134            feature.feature_tag,
135            feature_variations.as_ref(),
136        )?;
137
138        if let Some(feature_table) = feature_table {
139            // Sort and remove duplicates
140            lookup_indices.clear();
141            lookup_indices.extend_from_slice(&feature_table.lookup_indices);
142            lookup_indices.sort_unstable();
143            for lookup_index in lookup_indices.iter().copied().dedup().map(usize::from) {
144                gpos_apply_lookup(
145                    gpos_cache,
146                    gpos_table,
147                    opt_gdef_table,
148                    lookup_index,
149                    tuple,
150                    infos,
151                )?;
152            }
153        }
154    }
155    Ok(())
156}
157
158/// Apply basic mark processing when there is no `gpos` table available.
159///
160/// Call this method when there is no `LayoutCache<GPOS>` available for this font.
161pub fn apply_fallback(infos: &mut [Info]) {
162    for info in infos.iter_mut() {
163        if !info.is_mark && unicodes_are_marks(&info.glyph.unicodes) {
164            info.is_mark = true;
165        }
166    }
167    let mut base_index = 0;
168    for (i, info) in infos.iter_mut().enumerate().skip(1) {
169        if info.is_mark {
170            info.placement = Placement::MarkOverprint(base_index);
171        } else {
172            base_index = i;
173        }
174    }
175}
176
177fn unicodes_are_marks(unicodes: &[char]) -> bool {
178    unicodes
179        .iter()
180        .copied()
181        .map(unicode_general_category::get_general_category)
182        .all(|cat| cat == GeneralCategory::NonspacingMark)
183}
184
185fn gpos_apply_lookup(
186    gpos_cache: &LayoutCache<GPOS>,
187    gpos_table: &LayoutTable<GPOS>,
188    opt_gdef_table: Option<&GDEFTable>,
189    lookup_index: usize,
190    tuple: Option<Tuple<'_>>,
191    infos: &mut [Info],
192) -> Result<(), ParseError> {
193    if let Some(ref lookup_list) = gpos_table.opt_lookup_list {
194        let lookup = lookup_list.lookup_cache_gpos(gpos_cache, lookup_index)?;
195        let match_type = MatchType::from_lookup_flag(lookup.lookup_flag);
196        match lookup.lookup_subtables {
197            PosLookup::SinglePos(ref subtables) => {
198                forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
199                    singlepos(subtables, tuple, opt_gdef_table, &mut infos[i])
200                })
201            }
202            PosLookup::PairPos(ref subtables) => {
203                // Spec suggests that the lookup will only be applied to the second glyph if it was
204                // not repositioned, ie. if the value_format is zero, but applying the lookup
205                // regardless does not break any test cases.
206                forall_glyph_pairs_match(match_type, opt_gdef_table, infos, |i1, i2, infos| {
207                    pairpos(subtables, tuple, opt_gdef_table, i1, i2, infos)
208                })
209            }
210            PosLookup::CursivePos(ref subtables) => forall_glyph_pairs_match(
211                MatchType::ignore_marks(),
212                opt_gdef_table,
213                infos,
214                |i1, i2, infos| cursivepos(subtables, i1, i2, lookup.lookup_flag, infos),
215            ),
216            PosLookup::MarkBasePos(ref subtables) => {
217                forall_base_mark_glyph_pairs(infos, |i1, i2, infos| {
218                    markbasepos(subtables, i1, i2, infos)
219                })
220            }
221            PosLookup::MarkLigPos(ref subtables) => {
222                forall_base_mark_glyph_pairs(infos, |i1, i2, infos| {
223                    markligpos(subtables, i1, i2, infos)
224                })
225            }
226            PosLookup::MarkMarkPos(ref subtables) => {
227                forall_mark_mark_glyph_pairs(infos, |i1, i2, infos| {
228                    markmarkpos(subtables, i1, i2, infos)
229                })
230            }
231            PosLookup::ContextPos(ref subtables) => {
232                forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
233                    contextpos(
234                        gpos_cache,
235                        lookup_list,
236                        opt_gdef_table,
237                        tuple,
238                        match_type,
239                        subtables,
240                        i,
241                        infos,
242                    )
243                })
244            }
245            PosLookup::ChainContextPos(ref subtables) => {
246                forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
247                    chaincontextpos(
248                        gpos_cache,
249                        lookup_list,
250                        opt_gdef_table,
251                        tuple,
252                        match_type,
253                        subtables,
254                        i,
255                        infos,
256                    )
257                })
258            }
259        }
260    } else {
261        Ok(())
262    }
263}
264
265fn gpos_lookup_singlepos(
266    subtables: &[SinglePos],
267    glyph_index: u16,
268) -> Result<ValueRecord, ParseError> {
269    for singlepos in subtables {
270        if let Some(val) = singlepos.apply(glyph_index)? {
271            return Ok(Some(val));
272        }
273    }
274    Ok(None)
275}
276
277fn gpos_lookup_pairpos(
278    subtables: &[PairPos],
279    glyph_index1: u16,
280    glyph_index2: u16,
281) -> Result<Option<(ValueRecord, ValueRecord)>, ParseError> {
282    for pairpos in subtables {
283        if let Some((val1, val2)) = pairpos.apply(glyph_index1, glyph_index2)? {
284            return Ok(Some((val1, val2)));
285        }
286    }
287    Ok(None)
288}
289
290fn gpos_lookup_cursivepos(
291    subtables: &[CursivePos],
292    glyph_index1: u16,
293    glyph_index2: u16,
294) -> Result<Option<(Anchor, Anchor)>, ParseError> {
295    for cursivepos in subtables {
296        if let Some((an1, an2)) = cursivepos.apply(glyph_index1, glyph_index2)? {
297            return Ok(Some((an1, an2)));
298        }
299    }
300    Ok(None)
301}
302
303fn gpos_lookup_markbasepos(
304    subtables: &[MarkBasePos],
305    glyph_index1: u16,
306    glyph_index2: u16,
307) -> Result<Option<(Anchor, Anchor)>, ParseError> {
308    for markbasepos in subtables {
309        if let Some((an1, an2)) = markbasepos.apply(glyph_index1, glyph_index2)? {
310            return Ok(Some((an1, an2)));
311        }
312    }
313    Ok(None)
314}
315
316fn gpos_lookup_markligpos(
317    subtables: &[MarkLigPos],
318    glyph_index1: u16,
319    glyph_index2: u16,
320    liga_component_index: u16,
321) -> Result<Option<(Anchor, Anchor)>, ParseError> {
322    for markligpos in subtables {
323        if let Some((an1, an2)) = markligpos.apply(
324            glyph_index1,
325            glyph_index2,
326            usize::from(liga_component_index),
327        )? {
328            return Ok(Some((an1, an2)));
329        }
330    }
331    Ok(None)
332}
333
334fn gpos_lookup_markmarkpos(
335    subtables: &[MarkBasePos],
336    glyph_index1: u16,
337    glyph_index2: u16,
338) -> Result<Option<(Anchor, Anchor)>, ParseError> {
339    for markmarkpos in subtables {
340        if let Some((an1, an2)) = markmarkpos.apply(glyph_index1, glyph_index2)? {
341            return Ok(Some((an1, an2)));
342        }
343    }
344    Ok(None)
345}
346
347fn gpos_lookup_contextpos<'a>(
348    opt_gdef_table: Option<&GDEFTable>,
349    match_type: MatchType,
350    subtables: &'a [ContextLookup<GPOS>],
351    glyph_index: u16,
352    i: usize,
353    infos: &mut [Info],
354) -> Result<Option<Box<PosContext<'a>>>, ParseError> {
355    for context_lookup in subtables {
356        if let Some(context) = context_lookup_info(context_lookup, glyph_index, |context| {
357            context.matches(opt_gdef_table, match_type, infos, i)
358        })? {
359            return Ok(Some(context));
360        }
361    }
362    Ok(None)
363}
364
365fn gpos_lookup_chaincontextpos<'a>(
366    opt_gdef_table: Option<&GDEFTable>,
367    match_type: MatchType,
368    subtables: &'a [ChainContextLookup<GPOS>],
369    glyph_index: u16,
370    i: usize,
371    infos: &mut [Info],
372) -> Result<Option<Box<PosContext<'a>>>, ParseError> {
373    for chain_context_lookup in subtables {
374        if let Some(context) =
375            chain_context_lookup_info(chain_context_lookup, glyph_index, |context| {
376                context.matches(opt_gdef_table, match_type, infos, i)
377            })?
378        {
379            return Ok(Some(context));
380        }
381    }
382    Ok(None)
383}
384
385/// Adjustment to the placement of a glyph as a result of kerning and
386/// placement of an attachment relative to a base glyph.
387#[derive(Copy, Clone, Eq, PartialEq, Debug)]
388pub enum Placement {
389    None,
390    /// Placement offset by distance delta.
391    ///
392    /// Fields
393    /// (delta x, delta y)
394    Distance(i32, i32),
395    /// An anchored mark.
396    ///
397    /// This is a mark where its anchor is aligned with the base glyph anchor.
398    ///
399    /// Fields:
400    /// (base glyph index in `Vec<Info>`, base glyph anchor, mark anchor)
401    MarkAnchor(usize, Anchor, Anchor),
402    /// An overprint mark.
403    ///
404    /// This mark is shown at the same position as the base glyph.
405    ///
406    /// Fields:
407    /// (base glyph index in `Vec<Info>`)
408    MarkOverprint(usize),
409    /// Cursive anchored placement.
410    ///
411    /// Fields:
412    /// * exit glyph index in `Vec<Info>`,
413    /// * [RIGHT_TO_LEFT flag from lookup table](https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#lookupFlags_1),
414    /// * exit glyph anchor,
415    /// * entry glyph anchor
416    ///
417    /// <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-3-cursive-attachment-positioning-subtable>
418    CursiveAnchor(usize, bool, Anchor, Anchor),
419}
420
421impl Placement {
422    fn combine_distance(&mut self, x2: i32, y2: i32) {
423        use Placement::*;
424
425        *self = match *self {
426            None | MarkOverprint(_) => Distance(x2, y2),
427            // FIXME HarfBuzz also updates cursive anchors
428            // but we haven't found any fonts that test this codepath yet.
429            CursiveAnchor(..) => Distance(x2, y2),
430            Distance(x1, y1) => Distance(x1 + x2, y1 + y2),
431            MarkAnchor(i, an1, an2) => {
432                let x = an1.x + (x2 as i16);
433                let y = an1.y + (y2 as i16);
434                MarkAnchor(i, Anchor { x, y }, an2)
435            }
436        }
437    }
438}
439
440/// A positioned glyph.
441///
442/// This struct is the output of applying glyph positioning (`gpos`). It contains the glyph
443/// and information about how it should be positioned.
444///
445/// For more information about glyph placement refer to the OpenType documentation:
446/// <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#positioning-glyphs-with-opentype>
447#[derive(Clone, Debug)]
448pub struct Info {
449    /// The glyph.
450    pub glyph: RawGlyph<()>,
451    /// An offset from the horizontal glyph advance position for this glyph.
452    pub kerning: i16,
453    /// When not `Placement::None` indicates that this glyph should be placed according to
454    /// the variant.
455    pub placement: Placement,
456    is_mark: bool,
457}
458
459impl Glyph for Info {
460    fn get_glyph_index(&self) -> u16 {
461        self.glyph.glyph_index
462    }
463}
464
465impl Info {
466    pub fn init_from_glyphs(
467        opt_gdef_table: Option<&GDEFTable>,
468        glyphs: Vec<RawGlyph<()>>,
469    ) -> Vec<Info> {
470        let mut infos = Vec::with_capacity(glyphs.len());
471        for glyph in glyphs {
472            let is_mark = gdef_is_mark(opt_gdef_table, glyph.glyph_index);
473            let info = Info {
474                glyph,
475                kerning: 0,
476                placement: Placement::None,
477                is_mark,
478            };
479            infos.push(info);
480        }
481        infos
482    }
483}
484
485impl Adjust {
486    fn apply(&self, tuple: Option<Tuple<'_>>, opt_gdef_table: Option<&GDEFTable>, info: &mut Info) {
487        let variation_store =
488            opt_gdef_table.and_then(|gdef| gdef.opt_item_variation_store.as_ref());
489        if self.x_placement == 0 && self.y_placement == 0 {
490            if self.x_advance != 0 && self.y_advance == 0 {
491                info.kerning +=
492                    self.x_advance + self.x_advance_delta(tuple, variation_store).round() as i16;
493            } else if self.y_advance != 0 {
494                // error: y_advance non-zero
495            } else {
496                // both zero, but delta could still be present
497                let x_advance_delta = self.x_advance_delta(tuple, variation_store).round() as i16;
498                info.kerning += x_advance_delta;
499            }
500        } else if self.y_advance == 0 {
501            let x_placement = i32::from(self.x_placement)
502                + self.x_placement_delta(tuple, variation_store).round() as i32;
503            let y_placement = i32::from(self.y_placement)
504                + self.y_placement_delta(tuple, variation_store).round() as i32;
505            info.placement.combine_distance(x_placement, y_placement);
506
507            let x_advance =
508                self.x_advance + self.x_advance_delta(tuple, variation_store).round() as i16;
509            info.kerning += x_advance;
510        } else {
511            // error: y_advance non-zero
512        }
513    }
514
515    pub fn x_advance_delta(
516        &self,
517        tuple: Option<Tuple<'_>>,
518        variation_store: Option<&owned::ItemVariationStore>,
519    ) -> f32 {
520        Self::delta(self.x_advance_variation.as_ref(), tuple, variation_store)
521    }
522
523    pub fn y_advance_delta(
524        &self,
525        tuple: Option<Tuple<'_>>,
526        variation_store: Option<&owned::ItemVariationStore>,
527    ) -> f32 {
528        Self::delta(self.y_advance_variation.as_ref(), tuple, variation_store)
529    }
530
531    pub fn x_placement_delta(
532        &self,
533        tuple: Option<Tuple<'_>>,
534        variation_store: Option<&owned::ItemVariationStore>,
535    ) -> f32 {
536        Self::delta(self.x_placement_variation.as_ref(), tuple, variation_store)
537    }
538
539    pub fn y_placement_delta(
540        &self,
541        tuple: Option<Tuple<'_>>,
542        variation_store: Option<&owned::ItemVariationStore>,
543    ) -> f32 {
544        Self::delta(self.y_placement_variation.as_ref(), tuple, variation_store)
545    }
546
547    fn delta(
548        variation: Option<&VariationIndex>,
549        tuple: Option<Tuple<'_>>,
550        variation_store: Option<&owned::ItemVariationStore>,
551    ) -> f32 {
552        match (tuple, variation_store, variation) {
553            (Some(tuple), Some(store), Some(placement_variation)) => {
554                store.adjustment(*placement_variation, tuple).unwrap_or(0.0)
555            }
556            _ => 0.0,
557        }
558    }
559}
560
561fn forall_glyphs_match(
562    match_type: MatchType,
563    opt_gdef_table: Option<&GDEFTable>,
564    infos: &mut [Info],
565    f: impl Fn(usize, &mut [Info]) -> Result<(), ParseError>,
566) -> Result<(), ParseError> {
567    for i in 0..infos.len() {
568        if match_type.match_glyph(opt_gdef_table, &infos[i]) {
569            f(i, infos)?;
570        }
571    }
572    Ok(())
573}
574
575fn forall_glyph_pairs_match(
576    match_type: MatchType,
577    opt_gdef_table: Option<&GDEFTable>,
578    infos: &mut [Info],
579    f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
580) -> Result<(), ParseError> {
581    if let Some(mut i1) = match_type.find_first(opt_gdef_table, infos) {
582        while let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
583            f(i1, i2, infos)?;
584            i1 = i2;
585        }
586    }
587    Ok(())
588}
589
590fn forall_base_mark_glyph_pairs(
591    infos: &mut [Info],
592    f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
593) -> Result<(), ParseError> {
594    let mut i = 0;
595    'outer: while i + 1 < infos.len() {
596        if !infos[i].is_mark {
597            for j in i + 1..infos.len() {
598                f(i, j, infos)?;
599                if !infos[j].is_mark {
600                    i = j;
601                    continue 'outer;
602                }
603            }
604        }
605        i += 1;
606    }
607    Ok(())
608}
609
610fn forall_mark_mark_glyph_pairs(
611    infos: &mut [Info],
612    f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
613) -> Result<(), ParseError> {
614    let mut start = 0;
615    'outer: loop {
616        let mut i = start;
617        while i + 1 < infos.len() {
618            if infos[i].is_mark {
619                // infos[i] is the base mark. Scan forward looking for attaching marks
620                for j in i + 1..infos.len() {
621                    if !infos[j].is_mark {
622                        start = i + 1;
623                        continue 'outer;
624                    }
625
626                    // infos[j] is a candidate attaching mark
627                    if infos[i].glyph.liga_component_pos == infos[j].glyph.liga_component_pos {
628                        f(i, j, infos)?;
629                    } else if infos[i].glyph.ligature() || infos[j].glyph.ligature() {
630                        f(i, j, infos)?;
631                    }
632                }
633            }
634            i += 1;
635        }
636        break;
637    }
638    Ok(())
639}
640
641fn singlepos(
642    subtables: &[SinglePos],
643    tuple: Option<Tuple<'_>>,
644    opt_gdef_table: Option<&GDEFTable>,
645    i: &mut Info,
646) -> Result<(), ParseError> {
647    let glyph_index = i.glyph.glyph_index;
648    if let Some(adj) = gpos_lookup_singlepos(subtables, glyph_index)? {
649        adj.apply(tuple, opt_gdef_table, i);
650    }
651    Ok(())
652}
653
654fn pairpos(
655    subtables: &[PairPos],
656    tuple: Option<Tuple<'_>>,
657    opt_gdef_table: Option<&GDEFTable>,
658    i1: usize,
659    i2: usize,
660    infos: &mut [Info],
661) -> Result<(), ParseError> {
662    match gpos_lookup_pairpos(
663        subtables,
664        infos[i1].glyph.glyph_index,
665        infos[i2].glyph.glyph_index,
666    )? {
667        Some((opt_adj1, opt_adj2)) => {
668            if let Some(adj1) = opt_adj1 {
669                adj1.apply(tuple, opt_gdef_table, &mut infos[i1]);
670            }
671            if let Some(adj2) = opt_adj2 {
672                adj2.apply(tuple, opt_gdef_table, &mut infos[i2]);
673            }
674            Ok(())
675        }
676        None => Ok(()),
677    }
678}
679
680fn cursivepos(
681    subtables: &[CursivePos],
682    i1: usize,
683    i2: usize,
684    lookup_flag: LookupFlag,
685    infos: &mut [Info],
686) -> Result<(), ParseError> {
687    match gpos_lookup_cursivepos(
688        subtables,
689        infos[i1].glyph.glyph_index,
690        infos[i2].glyph.glyph_index,
691    )? {
692        Some((anchor1, anchor2)) => {
693            infos[i1].placement =
694                Placement::CursiveAnchor(i2, lookup_flag.get_rtl(), anchor2, anchor1);
695            Ok(())
696        }
697        None => Ok(()),
698    }
699}
700
701fn markbasepos(
702    subtables: &[MarkBasePos],
703    i1: usize,
704    i2: usize,
705    infos: &mut [Info],
706) -> Result<(), ParseError> {
707    match gpos_lookup_markbasepos(
708        subtables,
709        infos[i1].glyph.glyph_index,
710        infos[i2].glyph.glyph_index,
711    )? {
712        Some((anchor1, anchor2)) => {
713            infos[i2].placement = Placement::MarkAnchor(i1, anchor1, anchor2);
714            infos[i2].is_mark = true;
715            Ok(())
716        }
717        None => Ok(()),
718    }
719}
720
721fn markligpos(
722    subtables: &[MarkLigPos],
723    i1: usize,
724    i2: usize,
725    infos: &mut [Info],
726) -> Result<(), ParseError> {
727    match gpos_lookup_markligpos(
728        subtables,
729        infos[i1].glyph.glyph_index,
730        infos[i2].glyph.glyph_index,
731        infos[i2].glyph.liga_component_pos,
732    )? {
733        Some((anchor1, anchor2)) => {
734            infos[i2].placement = Placement::MarkAnchor(i1, anchor1, anchor2);
735            infos[i2].is_mark = true;
736            Ok(())
737        }
738        None => Ok(()),
739    }
740}
741
742fn markmarkpos(
743    subtables: &[MarkBasePos],
744    i1: usize,
745    i2: usize,
746    infos: &mut [Info],
747) -> Result<(), ParseError> {
748    match gpos_lookup_markmarkpos(
749        subtables,
750        infos[i1].glyph.glyph_index,
751        infos[i2].glyph.glyph_index,
752    )? {
753        Some((anchor1, anchor2)) => {
754            infos[i2].placement = Placement::MarkAnchor(i1, anchor1, anchor2);
755            infos[i2].is_mark = true;
756            Ok(())
757        }
758        None => Ok(()),
759    }
760}
761
762fn contextpos(
763    gpos_cache: &LayoutCache<GPOS>,
764    lookup_list: &LookupList<GPOS>,
765    opt_gdef_table: Option<&GDEFTable>,
766    tuple: Option<Tuple<'_>>,
767    match_type: MatchType,
768    subtables: &[ContextLookup<GPOS>],
769    i: usize,
770    infos: &mut [Info],
771) -> Result<(), ParseError> {
772    let glyph_index = infos[i].glyph.glyph_index;
773    match gpos_lookup_contextpos(opt_gdef_table, match_type, subtables, glyph_index, i, infos)? {
774        Some(pos) => apply_pos_context(
775            gpos_cache,
776            lookup_list,
777            opt_gdef_table,
778            tuple,
779            match_type,
780            &pos,
781            i,
782            infos,
783        ),
784        None => Ok(()),
785    }
786}
787
788fn chaincontextpos(
789    gpos_cache: &LayoutCache<GPOS>,
790    lookup_list: &LookupList<GPOS>,
791    opt_gdef_table: Option<&GDEFTable>,
792    tuple: Option<Tuple<'_>>,
793    match_type: MatchType,
794    subtables: &[ChainContextLookup<GPOS>],
795    i: usize,
796    infos: &mut [Info],
797) -> Result<(), ParseError> {
798    let glyph_index = infos[i].glyph.glyph_index;
799    match gpos_lookup_chaincontextpos(opt_gdef_table, match_type, subtables, glyph_index, i, infos)?
800    {
801        Some(pos) => apply_pos_context(
802            gpos_cache,
803            lookup_list,
804            opt_gdef_table,
805            tuple,
806            match_type,
807            &pos,
808            i,
809            infos,
810        ),
811        None => Ok(()),
812    }
813}
814
815fn apply_pos_context(
816    gpos_cache: &LayoutCache<GPOS>,
817    lookup_list: &LookupList<GPOS>,
818    opt_gdef_table: Option<&GDEFTable>,
819    tuple: Option<Tuple<'_>>,
820    _match_type: MatchType,
821    pos: &PosContext<'_>,
822    i: usize,
823    infos: &mut [Info],
824) -> Result<(), ParseError> {
825    for (pos_index, pos_lookup_index) in pos.lookup_array {
826        apply_pos(
827            gpos_cache,
828            lookup_list,
829            opt_gdef_table,
830            tuple,
831            usize::from(*pos_index),
832            usize::from(*pos_lookup_index),
833            infos,
834            i,
835        )?;
836    }
837    Ok(())
838}
839
840fn apply_pos(
841    gpos_cache: &LayoutCache<GPOS>,
842    lookup_list: &LookupList<GPOS>,
843    opt_gdef_table: Option<&GDEFTable>,
844    tuple: Option<Tuple<'_>>,
845    pos_index: usize,
846    lookup_index: usize,
847    infos: &mut [Info],
848    index: usize,
849) -> Result<(), ParseError> {
850    let lookup = lookup_list.lookup_cache_gpos(gpos_cache, lookup_index)?;
851    let match_type = MatchType::from_lookup_flag(lookup.lookup_flag);
852    let i1 = match match_type.find_nth(opt_gdef_table, infos, index, pos_index) {
853        Some(index1) => index1,
854        None => return Ok(()),
855    };
856    match lookup.lookup_subtables {
857        PosLookup::SinglePos(ref subtables) => {
858            singlepos(subtables, tuple, opt_gdef_table, &mut infos[i1])
859        }
860        PosLookup::PairPos(ref subtables) => {
861            if let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
862                pairpos(subtables, tuple, opt_gdef_table, i1, i2, infos)
863            } else {
864                Ok(())
865            }
866        }
867        PosLookup::CursivePos(ref subtables) => {
868            if let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
869                cursivepos(subtables, i1, i2, lookup.lookup_flag, infos)
870            } else {
871                Ok(())
872            }
873        }
874        PosLookup::MarkBasePos(ref subtables) => {
875            // FIXME is this correct?
876            if let Some(base_index) = MatchType::ignore_marks().find_prev(opt_gdef_table, infos, i1)
877            {
878                markbasepos(subtables, base_index, i1, infos)
879            } else {
880                Ok(())
881            }
882        }
883        PosLookup::MarkLigPos(ref subtables) => {
884            // FIXME is this correct?
885            if let Some(base_index) = MatchType::ignore_marks().find_prev(opt_gdef_table, infos, i1)
886            {
887                markligpos(subtables, base_index, i1, infos)
888            } else {
889                Ok(())
890            }
891        }
892        PosLookup::MarkMarkPos(ref subtables) => {
893            // FIXME is this correct?
894            if let Some(base_index) = match_type.find_prev(opt_gdef_table, infos, i1) {
895                markmarkpos(subtables, base_index, i1, infos)
896            } else {
897                Ok(())
898            }
899        }
900        PosLookup::ContextPos(ref _subtables) => Ok(()),
901        PosLookup::ChainContextPos(ref _subtables) => Ok(()),
902    }
903}