Skip to main content

write_fonts/tables/gpos/
builders.rs

1//! GPOS subtable builders
2
3use std::collections::{BTreeMap, HashMap};
4
5use read_fonts::collections::IntSet;
6use types::GlyphId16;
7
8use crate::tables::{
9    layout::{
10        builders::{Builder, ClassDefBuilder, DeviceOrDeltas, Metric},
11        CoverageTable,
12    },
13    variations::ivs_builder::VariationStoreBuilder,
14};
15
16use super::{
17    AnchorTable, BaseArray, BaseRecord, Class1Record, Class2Record, ComponentRecord,
18    CursivePosFormat1, EntryExitRecord, LigatureArray, LigatureAttach, Mark2Array, Mark2Record,
19    MarkArray, MarkBasePosFormat1, MarkLigPosFormat1, MarkMarkPosFormat1, MarkRecord, PairPos,
20    PairSet, PairValueRecord, SinglePos, ValueFormat, ValueRecord,
21};
22
23type GlyphSet = IntSet<GlyphId16>;
24
25/// A builder for [`ValueRecord`]s, which may contain raw deltas or device tables.
26#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct ValueRecordBuilder {
29    /// The x advance, plus a possible device table or set of deltas
30    pub x_advance: Option<Metric>,
31    /// The y advance, plus a possible device table or set of deltas
32    pub y_advance: Option<Metric>,
33    /// The x placement, plus a possible device table or set of deltas
34    pub x_placement: Option<Metric>,
35    /// The y placement, plus a possible device table or set of deltas
36    pub y_placement: Option<Metric>,
37}
38
39/// A builder for [`AnchorTable`]s, which may contain raw deltas or device tables.
40#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct AnchorBuilder {
43    /// The x coordinate, plus a possible device table or set of deltas
44    pub x: Metric,
45    /// The y coordinate, plus a possible device table or set of deltas
46    pub y: Metric,
47    /// The countourpoint, in a format 2 anchor.
48    ///
49    /// This is a rarely used format.
50    pub contourpoint: Option<u16>,
51}
52
53impl ValueRecordBuilder {
54    /// Create a new all-zeros `ValueRecordBuilder`
55    pub fn new() -> Self {
56        Default::default()
57    }
58
59    /// Duplicates the x-advance value to x-placement, required for RTL rules.
60    ///
61    /// This is only necessary when a record was originally created without
62    /// knowledge of the writing direction, and then later needs to be modified.
63    pub fn make_rtl_compatible(&mut self) {
64        if self.x_placement.is_none() {
65            self.x_placement.clone_from(&self.x_advance);
66        }
67    }
68
69    // these methods just match the existing builder methods on `ValueRecord`
70    /// Builder style method to set the default x_placement value
71    pub fn with_x_placement(mut self, val: i16) -> Self {
72        self.x_placement
73            .get_or_insert_with(Default::default)
74            .default = val;
75        self
76    }
77
78    /// Builder style method to set the default y_placement value
79    pub fn with_y_placement(mut self, val: i16) -> Self {
80        self.y_placement
81            .get_or_insert_with(Default::default)
82            .default = val;
83        self
84    }
85
86    /// Builder style method to set the default x_placement value
87    pub fn with_x_advance(mut self, val: i16) -> Self {
88        self.x_advance.get_or_insert_with(Default::default).default = val;
89        self
90    }
91
92    /// Builder style method to set the default y_placement value
93    pub fn with_y_advance(mut self, val: i16) -> Self {
94        self.y_advance.get_or_insert_with(Default::default).default = val;
95        self
96    }
97
98    /// Builder style method to set the device or deltas for x_placement
99    ///
100    /// The argument can be a `Device` table or a `Vec<(VariationRegion, i16)>`
101    pub fn with_x_placement_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
102        self.x_placement
103            .get_or_insert_with(Default::default)
104            .device_or_deltas = val.into();
105        self
106    }
107
108    /// Builder style method to set the device or deltas for y_placement
109    ///
110    /// The argument can be a `Device` table or a `Vec<(VariationRegion, i16)>`
111    pub fn with_y_placement_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
112        self.y_placement
113            .get_or_insert_with(Default::default)
114            .device_or_deltas = val.into();
115        self
116    }
117
118    /// Builder style method to set the device or deltas for x_advance
119    ///
120    /// The argument can be a `Device` table or a `Vec<(VariationRegion, i16)>`
121    pub fn with_x_advance_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
122        self.x_advance
123            .get_or_insert_with(Default::default)
124            .device_or_deltas = val.into();
125        self
126    }
127
128    /// Builder style method to set the device or deltas for y_advance
129    ///
130    /// The argument can be a `Device` table or a `Vec<(VariationRegion, i16)>`
131    pub fn with_y_advance_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
132        self.y_advance
133            .get_or_insert_with(Default::default)
134            .device_or_deltas = val.into();
135        self
136    }
137
138    /// Clear any fields that exist but are 'empty' (`0` default value, no device or deltas)
139    pub fn clear_zeros(mut self) -> Self {
140        self.x_advance = self.x_advance.filter(|m| !m.is_zero());
141        self.y_advance = self.y_advance.filter(|m| !m.is_zero());
142        self.x_placement = self.x_placement.filter(|m| !m.is_zero());
143        self.y_placement = self.y_placement.filter(|m| !m.is_zero());
144        self
145    }
146
147    /// Compute the `ValueFormat` for this record.
148    pub fn format(&self) -> ValueFormat {
149        const EMPTY: ValueFormat = ValueFormat::empty();
150        use ValueFormat as VF;
151
152        let get_flags = |field: &Option<Metric>, def_flag, dev_flag| {
153            let field = field.as_ref();
154            let def_flag = if field.is_some() { def_flag } else { EMPTY };
155            let dev_flag = field
156                .and_then(|fld| (!fld.device_or_deltas.is_none()).then_some(dev_flag))
157                .unwrap_or(EMPTY);
158            (def_flag, dev_flag)
159        };
160
161        let (x_adv, x_adv_dev) = get_flags(&self.x_advance, VF::X_ADVANCE, VF::X_ADVANCE_DEVICE);
162        let (y_adv, y_adv_dev) = get_flags(&self.y_advance, VF::Y_ADVANCE, VF::Y_ADVANCE_DEVICE);
163        let (x_place, x_place_dev) =
164            get_flags(&self.x_placement, VF::X_PLACEMENT, VF::X_PLACEMENT_DEVICE);
165        let (y_place, y_place_dev) =
166            get_flags(&self.y_placement, VF::Y_PLACEMENT, VF::Y_PLACEMENT_DEVICE);
167        x_adv | y_adv | x_place | y_place | x_adv_dev | y_adv_dev | x_place_dev | y_place_dev
168    }
169
170    /// `true` if we are not null, but our set values are all 0
171    pub fn is_all_zeros(&self) -> bool {
172        let device_mask = ValueFormat::X_PLACEMENT_DEVICE
173            | ValueFormat::Y_PLACEMENT_DEVICE
174            | ValueFormat::X_ADVANCE_DEVICE
175            | ValueFormat::Y_ADVANCE_DEVICE;
176
177        let format = self.format();
178        if format.is_empty() || format.intersects(device_mask) {
179            return false;
180        }
181        let all_values = [
182            &self.x_placement,
183            &self.y_placement,
184            &self.x_advance,
185            &self.y_advance,
186        ];
187        all_values
188            .iter()
189            .all(|v| v.as_ref().map(|v| v.is_zero()).unwrap_or(true))
190    }
191
192    /// Build the final [`ValueRecord`], compiling deltas if needed.
193    pub fn build(self, var_store: &mut VariationStoreBuilder) -> ValueRecord {
194        let mut result = ValueRecord::new();
195        result.x_advance = self.x_advance.as_ref().map(|val| val.default);
196        result.y_advance = self.y_advance.as_ref().map(|val| val.default);
197        result.x_placement = self.x_placement.as_ref().map(|val| val.default);
198        result.y_placement = self.y_placement.as_ref().map(|val| val.default);
199        result.x_advance_device = self
200            .x_advance
201            .and_then(|val| val.device_or_deltas.build(var_store))
202            .into();
203        result.y_advance_device = self
204            .y_advance
205            .and_then(|val| val.device_or_deltas.build(var_store))
206            .into();
207        result.x_placement_device = self
208            .x_placement
209            .and_then(|val| val.device_or_deltas.build(var_store))
210            .into();
211        result.y_placement_device = self
212            .y_placement
213            .and_then(|val| val.device_or_deltas.build(var_store))
214            .into();
215
216        result
217    }
218}
219
220impl AnchorBuilder {
221    /// Create a new [`AnchorBuilder`].
222    pub fn new(x: i16, y: i16) -> Self {
223        AnchorBuilder {
224            x: x.into(),
225            y: y.into(),
226            contourpoint: None,
227        }
228    }
229
230    /// Builder style method to set the device or deltas for the x value
231    ///
232    /// The argument can be a `Device` table or a `Vec<(VariationRegion, i16)>`
233    pub fn with_x_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
234        self.x.device_or_deltas = val.into();
235        self
236    }
237
238    /// Builder style method to set the device or deltas for the y value
239    ///
240    /// The argument can be a `Device` table or a `Vec<(VariationRegion, i16)>`
241    pub fn with_y_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
242        self.y.device_or_deltas = val.into();
243        self
244    }
245
246    /// Builder-style method to set the contourpoint.
247    ///
248    /// This is for the little-used format2 AnchorTable; it will be ignored
249    /// if any device or deltas have been set.
250    pub fn with_contourpoint(mut self, idx: u16) -> Self {
251        self.contourpoint = Some(idx);
252        self
253    }
254
255    /// Build the final [`AnchorTable`], adding deltas to the varstore if needed.
256    pub fn build(self, var_store: &mut VariationStoreBuilder) -> AnchorTable {
257        let x = self.x.default;
258        let y = self.y.default;
259        let x_dev = self.x.device_or_deltas.build(var_store);
260        let y_dev = self.y.device_or_deltas.build(var_store);
261        if x_dev.is_some() || y_dev.is_some() {
262            AnchorTable::format_3(x, y, x_dev, y_dev)
263        } else if let Some(point) = self.contourpoint {
264            AnchorTable::format_2(x, y, point)
265        } else {
266            AnchorTable::format_1(x, y)
267        }
268    }
269}
270
271/// A builder for [`SinglePos`] subtables.
272#[derive(Clone, Debug, Default, PartialEq, Eq)]
273#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
274pub struct SinglePosBuilder {
275    items: BTreeMap<GlyphId16, ValueRecordBuilder>,
276}
277
278impl SinglePosBuilder {
279    /// Returns the number of rules in the lookup.
280    pub fn len(&self) -> usize {
281        self.items.len()
282    }
283
284    /// Returns `true` if no rules have been added to the builder.
285    pub fn is_empty(&self) -> bool {
286        self.items.is_empty()
287    }
288
289    /// Add a new single-pos rule to this builder.
290    pub fn insert(&mut self, glyph: GlyphId16, record: ValueRecordBuilder) {
291        self.items.insert(glyph, record);
292    }
293
294    /// Check whether this glyph already has an assigned value in this builder.
295    pub fn can_add(&self, glyph: GlyphId16, value: &ValueRecordBuilder) -> bool {
296        self.items
297            .get(&glyph)
298            .map(|existing| existing == value)
299            .unwrap_or(true)
300    }
301
302    /// Returns an iterator over the rules in this builder, in glyph order.
303    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, &ValueRecordBuilder)> + '_ {
304        self.items.iter().map(|(glyph, value)| (*glyph, value))
305    }
306}
307
308impl Builder for SinglePosBuilder {
309    type Output = Vec<SinglePos>;
310
311    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
312        fn build_subtable(items: BTreeMap<GlyphId16, &ValueRecord>) -> SinglePos {
313            let first = *items.values().next().unwrap();
314            let use_format_1 = first.format().is_empty() || items.values().all(|val| val == &first);
315            let coverage: CoverageTable = items.keys().copied().collect();
316            if use_format_1 {
317                SinglePos::format_1(coverage.clone(), first.clone())
318            } else {
319                SinglePos::format_2(coverage, items.into_values().cloned().collect())
320            }
321        }
322        const NEW_SUBTABLE_COST: usize = 10;
323        let items = self
324            .items
325            .into_iter()
326            .map(|(glyph, anchor)| (glyph, anchor.build(var_store)))
327            .collect::<BTreeMap<_, _>>();
328
329        // list of sets of glyph ids which will end up in their own subtables
330        let mut subtables = Vec::new();
331        let mut group_by_record: HashMap<&ValueRecord, BTreeMap<GlyphId16, &ValueRecord>> =
332            Default::default();
333
334        // first group by specific record; glyphs that share a record can use
335        // the more efficient format-1 subtable type
336        for (gid, value) in &items {
337            group_by_record
338                .entry(value)
339                .or_default()
340                .insert(*gid, value);
341        }
342        let mut group_by_format: HashMap<ValueFormat, BTreeMap<GlyphId16, &ValueRecord>> =
343            Default::default();
344        for (value, glyphs) in group_by_record {
345            // if this saves us size, use format 1
346            if glyphs.len() * value.encoded_size() > NEW_SUBTABLE_COST {
347                subtables.push(glyphs);
348                // else split based on value format; each format will be its own
349                // format 2 table
350            } else {
351                group_by_format
352                    .entry(value.format())
353                    .or_default()
354                    .extend(glyphs.into_iter());
355            }
356        }
357        subtables.extend(group_by_format.into_values());
358
359        let mut output = subtables
360            .into_iter()
361            .map(build_subtable)
362            .collect::<Vec<_>>();
363
364        // finally sort the subtables: first in decreasing order of size,
365        // using first glyph id to break ties (matches feaLib)
366        output.sort_unstable_by_key(|table| match table {
367            SinglePos::Format1(table) => cmp_coverage_key(&table.coverage),
368            SinglePos::Format2(table) => cmp_coverage_key(&table.coverage),
369        });
370        output
371    }
372}
373
374fn cmp_coverage_key(coverage: &CoverageTable) -> impl Ord {
375    (std::cmp::Reverse(coverage.len()), coverage.iter().next())
376}
377
378/// A builder for GPOS type 2 (PairPos) subtables
379///
380/// This builder can build both glyph and class-based kerning subtables.
381#[derive(Clone, Debug, Default, PartialEq)]
382#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
383pub struct PairPosBuilder {
384    pairs: GlyphPairPosBuilder,
385    classes: ClassPairPosBuilder,
386}
387
388#[derive(Clone, Debug, Default, PartialEq)]
389#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
390struct GlyphPairPosBuilder(
391    BTreeMap<GlyphId16, BTreeMap<GlyphId16, (ValueRecordBuilder, ValueRecordBuilder)>>,
392);
393
394#[derive(Clone, Debug, PartialEq)]
395#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
396struct ClassPairPosSubtable {
397    items:
398        BTreeMap<IntSet<GlyphId16>, BTreeMap<GlyphSet, (ValueRecordBuilder, ValueRecordBuilder)>>,
399    classdef_1: ClassDefBuilder,
400    classdef_2: ClassDefBuilder,
401}
402
403impl Default for ClassPairPosSubtable {
404    fn default() -> Self {
405        Self {
406            items: Default::default(),
407            classdef_1: ClassDefBuilder::new_using_class_0(),
408            classdef_2: Default::default(),
409        }
410    }
411}
412
413#[derive(Clone, Debug, Default, PartialEq)]
414#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
415struct ClassPairPosBuilder(Vec<ClassPairPosSubtable>);
416
417impl ClassPairPosBuilder {
418    fn insert(
419        &mut self,
420        class1: GlyphSet,
421        record1: ValueRecordBuilder,
422        class2: GlyphSet,
423        record2: ValueRecordBuilder,
424    ) {
425        if self.0.last().map(|last| last.can_add(&class1, &class2)) != Some(true) {
426            self.0.push(Default::default())
427        }
428        self.0
429            .last_mut()
430            .unwrap()
431            .add(class1, class2, record1, record2);
432    }
433}
434
435impl ClassPairPosSubtable {
436    fn can_add(&self, class1: &GlyphSet, class2: &GlyphSet) -> bool {
437        self.classdef_1.can_add(class1) && self.classdef_2.can_add(class2)
438    }
439
440    fn add(
441        &mut self,
442        class1: GlyphSet,
443        class2: GlyphSet,
444        record1: ValueRecordBuilder,
445        record2: ValueRecordBuilder,
446    ) {
447        self.classdef_1.checked_add(class1.clone());
448        self.classdef_2.checked_add(class2.clone());
449        self.items
450            .entry(class1)
451            .or_default()
452            .insert(class2, (record1, record2));
453    }
454
455    // determine the union of each of the two value formats
456    //
457    // we need a to ensure that the value format we use can represent all
458    // of the fields present in any of the value records in this subtable.
459    //
460    // see https://github.com/fonttools/fonttools/blob/770917d89e9/Lib/fontTools/otlLib/builder.py#L2066
461    fn compute_value_formats(&self) -> (ValueFormat, ValueFormat) {
462        self.items.values().flat_map(|v| v.values()).fold(
463            (ValueFormat::empty(), ValueFormat::empty()),
464            |(acc1, acc2), (f1, f2)| (acc1 | f1.format(), acc2 | f2.format()),
465        )
466    }
467}
468
469impl PairPosBuilder {
470    /// Returns `true` if no rules have been added to this builder
471    pub fn is_empty(&self) -> bool {
472        self.pairs.0.is_empty() && self.classes.0.is_empty()
473    }
474
475    /// The number of rules in the builder
476    pub fn len(&self) -> usize {
477        self.pairs.0.values().map(|vals| vals.len()).sum::<usize>()
478            + self
479                .classes
480                .0
481                .iter()
482                .map(|sub| sub.items.values().len())
483                .sum::<usize>()
484    }
485
486    /// Insert a new kerning pair
487    pub fn insert_pair(
488        &mut self,
489        glyph1: GlyphId16,
490        record1: ValueRecordBuilder,
491        glyph2: GlyphId16,
492        record2: ValueRecordBuilder,
493    ) {
494        // "When specific kern pair rules conflict, the first rule specified is used,
495        // and later conflicting rule are skipped"
496        // https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6bii-enumerating-pairs
497        // E.g.:
498        //   @A = [A Aacute Agrave]
499        //   feature kern {
500        //     pos A B 100;
501        //     enum pos @A B -50;
502        //   } kern;
503        // should result in a A B kerning value of 100, not -50.
504        // https://github.com/googlefonts/fontc/issues/550
505        self.pairs
506            .0
507            .entry(glyph1)
508            .or_default()
509            .entry(glyph2)
510            .or_insert((record1, record2));
511    }
512
513    /// Insert a new class-based kerning rule.
514    pub fn insert_classes(
515        &mut self,
516        class1: GlyphSet,
517        record1: ValueRecordBuilder,
518        class2: GlyphSet,
519        record2: ValueRecordBuilder,
520    ) {
521        self.classes.insert(class1, record1, class2, record2)
522    }
523
524    /// Returns an iterator over the glyph-to-glyph rules in this builder.
525    ///
526    /// Rules are yielded in glyph order, first glyph then second.
527    pub fn iter_pairs(
528        &self,
529    ) -> impl Iterator<
530        Item = (
531            GlyphId16,
532            GlyphId16,
533            &ValueRecordBuilder,
534            &ValueRecordBuilder,
535        ),
536    > + '_ {
537        self.pairs.0.iter().flat_map(|(glyph1, seconds)| {
538            seconds
539                .iter()
540                .map(move |(glyph2, (record1, record2))| (*glyph1, *glyph2, record1, record2))
541        })
542    }
543
544    /// Returns an iterator over the class-based rules in this builder.
545    ///
546    /// The classes are the glyph sets themselves; the `ClassDef`s that assign
547    /// them ids are not computed until the builder is built. Rules from all of
548    /// this builder's class subtables are yielded, in subtable order; use
549    /// [`iter_class_subtables`](Self::iter_class_subtables) if the subtable
550    /// boundaries matter.
551    pub fn iter_class_pairs(
552        &self,
553    ) -> impl Iterator<
554        Item = (
555            &IntSet<GlyphId16>,
556            &IntSet<GlyphId16>,
557            &ValueRecordBuilder,
558            &ValueRecordBuilder,
559        ),
560    > + '_ {
561        self.iter_class_subtables().flatten()
562    }
563
564    /// Returns an iterator over the class-based subtables in this builder.
565    ///
566    /// Each item is an iterator over the rules of one subtable, in the order
567    /// the subtables will be built. A new subtable is started whenever a class
568    /// overlaps, without being equal to, a class already in the current one,
569    /// so within a subtable the classes on each side are disjoint.
570    pub fn iter_class_subtables(
571        &self,
572    ) -> impl Iterator<
573        Item = impl Iterator<
574            Item = (
575                &IntSet<GlyphId16>,
576                &IntSet<GlyphId16>,
577                &ValueRecordBuilder,
578                &ValueRecordBuilder,
579            ),
580        >,
581    > + '_ {
582        self.classes.0.iter().map(|subtable| {
583            subtable.items.iter().flat_map(|(class1, seconds)| {
584                seconds
585                    .iter()
586                    .map(move |(class2, (record1, record2))| (class1, class2, record1, record2))
587            })
588        })
589    }
590}
591
592impl Builder for PairPosBuilder {
593    type Output = Vec<PairPos>;
594
595    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
596        let mut out = self.pairs.build(var_store);
597        out.extend(self.classes.build(var_store));
598        out
599    }
600}
601
602impl Builder for GlyphPairPosBuilder {
603    type Output = Vec<PairPos>;
604
605    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
606        let mut split_by_format = BTreeMap::<_, BTreeMap<_, Vec<_>>>::default();
607        for (g1, map) in self.0 {
608            for (g2, (v1, v2)) in map {
609                split_by_format
610                    .entry((v1.format(), v2.format()))
611                    .or_default()
612                    .entry(g1)
613                    .or_default()
614                    .push(PairValueRecord::new(
615                        g2,
616                        v1.build(var_store),
617                        v2.build(var_store),
618                    ));
619            }
620        }
621
622        split_by_format
623            .into_values()
624            .map(|map| {
625                let coverage = map.keys().copied().collect();
626                let pair_sets = map.into_values().map(PairSet::new).collect();
627                PairPos::format_1(coverage, pair_sets)
628            })
629            .collect()
630    }
631}
632
633impl Builder for ClassPairPosBuilder {
634    type Output = Vec<PairPos>;
635
636    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
637        self.0.into_iter().map(|sub| sub.build(var_store)).collect()
638    }
639}
640
641impl Builder for ClassPairPosSubtable {
642    type Output = PairPos;
643
644    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
645        assert!(!self.items.is_empty(), "filter before here");
646        let (format1, format2) = self.compute_value_formats();
647        // we have a set of classes/values with a single valueformat
648
649        // an empty record, if some pair of classes have no entry
650        let empty_record = Class2Record::new(
651            ValueRecord::new().with_explicit_value_format(format1),
652            ValueRecord::new().with_explicit_value_format(format2),
653        );
654
655        let (class1def, class1map) = self.classdef_1.build_with_mapping();
656        let (class2def, class2map) = self.classdef_2.build_with_mapping();
657
658        let coverage = self.items.keys().flat_map(GlyphSet::iter).collect();
659
660        let mut out = vec![Class1Record::default(); self.items.len()];
661        for (cls1, stuff) in self.items {
662            let idx = class1map.get(&cls1).unwrap();
663            let mut records = vec![empty_record.clone(); class2map.len() + 1];
664            for (class, (v1, v2)) in stuff {
665                let idx = class2map.get(&class).unwrap();
666                records[*idx as usize] = Class2Record::new(
667                    v1.build(var_store).with_explicit_value_format(format1),
668                    v2.build(var_store).with_explicit_value_format(format2),
669                );
670            }
671            out[*idx as usize] = Class1Record::new(records);
672        }
673        PairPos::format_2(coverage, class1def, class2def, out)
674    }
675}
676
677/// A builder for GPOS Lookup Type 3, Cursive Attachment
678#[derive(Clone, Debug, Default, PartialEq, Eq)]
679#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
680pub struct CursivePosBuilder {
681    // (entry, exit)
682    items: BTreeMap<GlyphId16, (Option<AnchorBuilder>, Option<AnchorBuilder>)>,
683}
684
685impl CursivePosBuilder {
686    /// Returns the number of rules in the lookup.
687    pub fn len(&self) -> usize {
688        self.items.len()
689    }
690
691    /// Returns `true` if no rules have been added to the builder.
692    pub fn is_empty(&self) -> bool {
693        self.items.is_empty()
694    }
695
696    /// Insert a new entry/exit anchor pair for a glyph.
697    pub fn insert(
698        &mut self,
699        glyph: GlyphId16,
700        entry: Option<AnchorBuilder>,
701        exit: Option<AnchorBuilder>,
702    ) {
703        self.items.insert(glyph, (entry, exit));
704    }
705
706    /// Returns an iterator over the entry/exit anchors in this builder.
707    ///
708    /// Rules are yielded in glyph order. Either anchor may be absent.
709    pub fn iter(
710        &self,
711    ) -> impl Iterator<Item = (GlyphId16, Option<&AnchorBuilder>, Option<&AnchorBuilder>)> + '_
712    {
713        self.items
714            .iter()
715            .map(|(glyph, (entry, exit))| (*glyph, entry.as_ref(), exit.as_ref()))
716    }
717}
718
719impl Builder for CursivePosBuilder {
720    type Output = Vec<CursivePosFormat1>;
721
722    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
723        let coverage = self.items.keys().copied().collect();
724        let records = self
725            .items
726            .into_values()
727            .map(|(entry, exit)| {
728                EntryExitRecord::new(
729                    entry.map(|x| x.build(var_store)),
730                    exit.map(|x| x.build(var_store)),
731                )
732            })
733            .collect();
734        vec![CursivePosFormat1::new(coverage, records)]
735    }
736}
737
738// shared between several tables
739#[derive(Clone, Debug, Default, PartialEq, Eq)]
740#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
741struct MarkList {
742    // (class id, anchor)
743    glyphs: BTreeMap<GlyphId16, (u16, AnchorBuilder)>,
744    // map class names to their idx for this table
745    classes: HashMap<String, u16>,
746}
747
748impl MarkList {
749    /// If this glyph is already part of another class, return the previous class name
750    ///
751    /// Otherwise return the u16 id for this class, in this lookup.
752    fn insert(
753        &mut self,
754        glyph: GlyphId16,
755        class: &str,
756        anchor: AnchorBuilder,
757    ) -> Result<u16, PreviouslyAssignedClass> {
758        let next_id = self.classes.len().try_into().unwrap();
759        let id = self.classes.get(class).copied().unwrap_or_else(|| {
760            self.classes.insert(class.to_owned(), next_id);
761            next_id
762        });
763        if let Some(prev) = self
764            .glyphs
765            .insert(glyph, (id, anchor))
766            .filter(|prev| prev.0 != id)
767        {
768            let class = self
769                .classes
770                .iter()
771                .find_map(|(name, idx)| (*idx == prev.0).then(|| name.clone()))
772                .unwrap();
773
774            return Err(PreviouslyAssignedClass {
775                glyph_id: glyph,
776                class,
777            });
778        }
779        Ok(id)
780    }
781
782    fn glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
783        self.glyphs.keys().copied()
784    }
785
786    /// The name of each mark class, indexed by the class id.
787    ///
788    /// Class ids are assigned densely, in the order the classes are first
789    /// seen, so this is just an inversion of `self.classes`.
790    fn class_names(&self) -> Vec<&str> {
791        let mut names = vec![""; self.classes.len()];
792        for (name, id) in &self.classes {
793            names[*id as usize] = name.as_str();
794        }
795        names
796    }
797
798    fn iter(&self) -> impl Iterator<Item = (GlyphId16, &str, &AnchorBuilder)> + '_ {
799        let names = self.class_names();
800        self.glyphs
801            .iter()
802            .map(move |(glyph, (class, anchor))| (*glyph, names[*class as usize], anchor))
803    }
804
805    fn get_class(&self, class_name: &str) -> u16 {
806        *self
807            .classes
808            .get(class_name)
809            // this is internal API, we uphold this
810            .expect("marks added before bases")
811    }
812}
813
814impl Builder for MarkList {
815    type Output = (CoverageTable, MarkArray);
816
817    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
818        let coverage = self.glyphs().collect();
819        let array = MarkArray::new(
820            self.glyphs
821                .into_values()
822                .map(|(class, anchor)| MarkRecord::new(class, anchor.build(var_store)))
823                .collect(),
824        );
825        (coverage, array)
826    }
827}
828
829/// A builder for GPOS Lookup Type 4, Mark-to-Base
830#[derive(Clone, Debug, Default, PartialEq, Eq)]
831#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
832pub struct MarkToBaseBuilder {
833    marks: MarkList,
834    bases: BTreeMap<GlyphId16, Vec<(u16, AnchorBuilder)>>,
835}
836
837/// An error indicating a given glyph has been assigned to multiple mark classes
838#[derive(Clone, Debug, Default)]
839pub struct PreviouslyAssignedClass {
840    /// The ID of the glyph in conflict
841    pub glyph_id: GlyphId16,
842    /// The name of the previous class
843    pub class: String,
844}
845
846impl std::error::Error for PreviouslyAssignedClass {}
847
848impl std::fmt::Display for PreviouslyAssignedClass {
849    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
850        write!(
851            f,
852            "Glyph '{}' was previously assigned to class '{}'",
853            self.glyph_id.to_u16(),
854            self.class
855        )
856    }
857}
858
859impl MarkToBaseBuilder {
860    /// Returns the number of rules in the lookup.
861    pub fn len(&self) -> usize {
862        self.bases.len()
863    }
864
865    /// Returns `true` if no rules have been added to the builder.
866    pub fn is_empty(&self) -> bool {
867        self.bases.is_empty()
868    }
869
870    /// Add a new mark glyph.
871    ///
872    /// If this glyph already exists in another mark class, we return the
873    /// previous class; this is likely an error.
874    pub fn insert_mark(
875        &mut self,
876        glyph: GlyphId16,
877        class: &str,
878        anchor: AnchorBuilder,
879    ) -> Result<u16, PreviouslyAssignedClass> {
880        self.marks.insert(glyph, class, anchor)
881    }
882
883    /// Insert a new base glyph.
884    pub fn insert_base(&mut self, glyph: GlyphId16, class: &str, anchor: AnchorBuilder) {
885        let class = self.marks.get_class(class);
886        self.bases.entry(glyph).or_default().push((class, anchor))
887    }
888
889    /// Returns an iterator over all of the base glyphs
890    pub fn base_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
891        self.bases.keys().copied()
892    }
893
894    /// Returns an iterator over all of the mark glyphs
895    pub fn mark_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
896        self.marks.glyphs()
897    }
898
899    /// Returns an iterator over the mark glyphs, with their class and anchor.
900    ///
901    /// The class is identified by name; the numeric class ids used internally
902    /// are assigned per-lookup, in the order the classes are first seen, and so
903    /// are not comparable between two builders.
904    pub fn iter_marks(&self) -> impl Iterator<Item = (GlyphId16, &str, &AnchorBuilder)> + '_ {
905        self.marks.iter()
906    }
907
908    /// Returns an iterator over the base glyphs, with their class and anchor.
909    ///
910    /// A base glyph with anchors for several mark classes is yielded once per
911    /// class. See [`Self::iter_marks`] on why the class is named.
912    pub fn iter_bases(&self) -> impl Iterator<Item = (GlyphId16, &str, &AnchorBuilder)> + '_ {
913        let names = self.marks.class_names();
914        self.bases
915            .iter()
916            .flat_map(|(glyph, anchors)| {
917                anchors
918                    .iter()
919                    .map(move |(class, anchor)| (*glyph, *class, anchor))
920            })
921            .map(move |(glyph, class, anchor)| (glyph, names[class as usize], anchor))
922    }
923}
924
925impl Builder for MarkToBaseBuilder {
926    type Output = Vec<MarkBasePosFormat1>;
927
928    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
929        let MarkToBaseBuilder { marks, bases } = self;
930        let n_classes = marks.classes.len();
931
932        let (mark_coverage, mark_array) = marks.build(var_store);
933        let base_coverage = bases.keys().copied().collect();
934        let base_records = bases
935            .into_values()
936            .map(|anchors| {
937                let mut anchor_offsets = vec![None; n_classes];
938                for (class, anchor) in anchors {
939                    anchor_offsets[class as usize] = Some(anchor.build(var_store));
940                }
941                BaseRecord::new(anchor_offsets)
942            })
943            .collect();
944        let base_array = BaseArray::new(base_records);
945        vec![MarkBasePosFormat1::new(
946            mark_coverage,
947            base_coverage,
948            mark_array,
949            base_array,
950        )]
951    }
952}
953
954/// A builder for GPOS Lookup Type 5, Mark-to-Ligature
955#[derive(Clone, Debug, Default, PartialEq, Eq)]
956#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
957pub struct MarkToLigBuilder {
958    marks: MarkList,
959    ligatures: BTreeMap<GlyphId16, Vec<BTreeMap<String, AnchorBuilder>>>,
960}
961
962impl MarkToLigBuilder {
963    /// Returns the number of rules in the lookup.
964    pub fn len(&self) -> usize {
965        self.ligatures.len()
966    }
967
968    /// Returns `true` if no rules have been added to the builder.
969    pub fn is_empty(&self) -> bool {
970        self.ligatures.is_empty()
971    }
972
973    /// Add a new mark glyph.
974    ///
975    /// If this glyph already exists in another mark class, we return the
976    /// previous class; this is likely an error.
977    pub fn insert_mark(
978        &mut self,
979        glyph: GlyphId16,
980        class: &str,
981        anchor: AnchorBuilder,
982    ) -> Result<u16, PreviouslyAssignedClass> {
983        self.marks.insert(glyph, class, anchor)
984    }
985
986    /// Add a ligature base, providing a set of anchors for each component.
987    ///
988    /// There must be an item in the vec for each component in the ligature
989    /// glyph, but the anchors can be sparse; null anchors will be added for
990    /// any classes that are missing.
991    ///
992    /// NOTE: this API is designed for use from a FEA compiler, as it closely
993    /// mimics how the FEA source represents these rules where you process each
994    /// component in order, with all the marks defined for that component)
995    /// but this is less useful for public API, where you are more often dealing
996    /// with marks a class at a time. For that reason we provide an alternative
997    /// public method below.
998    pub fn add_ligature_components_directly(
999        &mut self,
1000        glyph: GlyphId16,
1001        components: Vec<BTreeMap<String, AnchorBuilder>>,
1002    ) {
1003        self.ligatures.insert(glyph, components);
1004    }
1005
1006    /// Add ligature anchors for a specific mark class.
1007    ///
1008    /// This can be called multiple times for the same ligature glyph, to add anchors
1009    /// for multiple mark classes; however the number of components must be equal
1010    /// on each call for a given glyph id.
1011    ///
1012    /// If a component has no anchor for a given mark class, you must include an
1013    /// explicit 'None' in the appropriate ordering.
1014    pub fn insert_ligature(
1015        &mut self,
1016        glyph: GlyphId16,
1017        class: &str,
1018        components: Vec<Option<AnchorBuilder>>,
1019    ) {
1020        let component_list = self.ligatures.entry(glyph).or_default();
1021        if component_list.is_empty() {
1022            component_list.resize(components.len(), Default::default());
1023        } else if component_list.len() != components.len() {
1024            log::warn!("mismatched component lengths for anchors in glyph {glyph}");
1025        }
1026        for (i, anchor) in components.into_iter().enumerate() {
1027            if let Some(anchor) = anchor {
1028                component_list[i].insert(class.to_owned(), anchor);
1029            }
1030        }
1031    }
1032
1033    /// Returns an iterator over all of the mark glyphs
1034    pub fn mark_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
1035        self.marks.glyphs()
1036    }
1037
1038    /// Returns an iterator over all of the ligature glyphs
1039    pub fn lig_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
1040        self.ligatures.keys().copied()
1041    }
1042
1043    /// Returns an iterator over the mark glyphs, with their class and anchor.
1044    ///
1045    /// The class is identified by name; the numeric class ids used internally
1046    /// are assigned per-lookup, in the order the classes are first seen, and so
1047    /// are not comparable between two builders.
1048    pub fn iter_marks(&self) -> impl Iterator<Item = (GlyphId16, &str, &AnchorBuilder)> + '_ {
1049        self.marks.iter()
1050    }
1051
1052    /// Returns an iterator over the ligature glyphs and their components.
1053    ///
1054    /// There is one item in the slice per component of the ligature, in order,
1055    /// and each maps mark class name to the anchor for that component. A
1056    /// component with no anchor for a class is absent from that map.
1057    pub fn iter_ligatures(
1058        &self,
1059    ) -> impl Iterator<Item = (GlyphId16, &[BTreeMap<String, AnchorBuilder>])> + '_ {
1060        self.ligatures
1061            .iter()
1062            .map(|(glyph, components)| (*glyph, components.as_slice()))
1063    }
1064}
1065
1066impl Builder for MarkToLigBuilder {
1067    type Output = Vec<MarkLigPosFormat1>;
1068
1069    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
1070        let MarkToLigBuilder { marks, ligatures } = self;
1071        let n_classes = marks.classes.len();
1072
1073        // LigArray:
1074        // - [LigatureAttach] (one per ligature glyph)
1075        //    - [ComponentRecord] (one per component)
1076        //    - [Anchor] (one per mark-class)
1077        let ligature_coverage = ligatures.keys().copied().collect();
1078        let ligature_array = ligatures
1079            .into_values()
1080            .map(|components| {
1081                let comp_records = components
1082                    .into_iter()
1083                    .map(|anchors| {
1084                        let mut anchor_offsets = vec![None; n_classes];
1085                        for (class, anchor) in anchors {
1086                            let class_idx = marks.get_class(&class);
1087                            anchor_offsets[class_idx as usize] = Some(anchor.build(var_store));
1088                        }
1089                        ComponentRecord::new(anchor_offsets)
1090                    })
1091                    .collect();
1092                LigatureAttach::new(comp_records)
1093            })
1094            .collect();
1095        let ligature_array = LigatureArray::new(ligature_array);
1096        let (mark_coverage, mark_array) = marks.build(var_store);
1097        vec![MarkLigPosFormat1::new(
1098            mark_coverage,
1099            ligature_coverage,
1100            mark_array,
1101            ligature_array,
1102        )]
1103    }
1104}
1105
1106/// A builder for GPOS Type 6 (Mark-to-Mark)
1107#[derive(Clone, Debug, Default, PartialEq, Eq)]
1108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1109pub struct MarkToMarkBuilder {
1110    attaching_marks: MarkList,
1111    base_marks: BTreeMap<GlyphId16, Vec<(u16, AnchorBuilder)>>,
1112}
1113
1114impl MarkToMarkBuilder {
1115    /// Returns the number of rules in the lookup.
1116    pub fn len(&self) -> usize {
1117        self.base_marks.len()
1118    }
1119
1120    /// Returns `true` if no rules have been added to the builder.
1121    pub fn is_empty(&self) -> bool {
1122        self.base_marks.is_empty()
1123    }
1124
1125    /// Add a new mark1 (combining) glyph.
1126    ///
1127    /// If this glyph already exists in another mark class, we return the
1128    /// previous class; this is likely an error.
1129    pub fn insert_mark1(
1130        &mut self,
1131        glyph: GlyphId16,
1132        class: &str,
1133        anchor: AnchorBuilder,
1134    ) -> Result<u16, PreviouslyAssignedClass> {
1135        self.attaching_marks.insert(glyph, class, anchor)
1136    }
1137
1138    /// Insert a new mark2 (base) glyph
1139    pub fn insert_mark2(&mut self, glyph: GlyphId16, class: &str, anchor: AnchorBuilder) {
1140        let id = self.attaching_marks.get_class(class);
1141        self.base_marks.entry(glyph).or_default().push((id, anchor))
1142    }
1143
1144    /// Returns an iterator over all of the mark1 glyphs
1145    pub fn mark1_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
1146        self.attaching_marks.glyphs()
1147    }
1148
1149    /// Returns an iterator over all of the mark2 glyphs
1150    pub fn mark2_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
1151        self.base_marks.keys().copied()
1152    }
1153
1154    /// Returns an iterator over the mark1 glyphs, with their class and anchor.
1155    ///
1156    /// The class is identified by name; the numeric class ids used internally
1157    /// are assigned per-lookup, in the order the classes are first seen, and so
1158    /// are not comparable between two builders.
1159    pub fn iter_mark1s(&self) -> impl Iterator<Item = (GlyphId16, &str, &AnchorBuilder)> + '_ {
1160        self.attaching_marks.iter()
1161    }
1162
1163    /// Returns an iterator over the mark2 glyphs, with their class and anchor.
1164    ///
1165    /// A mark2 glyph with anchors for several mark classes is yielded once per
1166    /// class. See [`Self::iter_mark1s`] on why the class is named.
1167    pub fn iter_mark2s(&self) -> impl Iterator<Item = (GlyphId16, &str, &AnchorBuilder)> + '_ {
1168        let names = self.attaching_marks.class_names();
1169        self.base_marks
1170            .iter()
1171            .flat_map(|(glyph, anchors)| {
1172                anchors
1173                    .iter()
1174                    .map(move |(class, anchor)| (*glyph, *class, anchor))
1175            })
1176            .map(move |(glyph, class, anchor)| (glyph, names[class as usize], anchor))
1177    }
1178}
1179
1180impl Builder for MarkToMarkBuilder {
1181    type Output = Vec<MarkMarkPosFormat1>;
1182
1183    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
1184        let MarkToMarkBuilder {
1185            attaching_marks,
1186            base_marks,
1187        } = self;
1188        let n_classes = attaching_marks.classes.len();
1189
1190        let (mark_coverage, mark_array) = attaching_marks.build(var_store);
1191        let mark2_coverage = base_marks.keys().copied().collect();
1192        let mark2_records = base_marks
1193            .into_values()
1194            .map(|anchors| {
1195                let mut anchor_offsets = vec![None; n_classes];
1196                for (class, anchor) in anchors {
1197                    anchor_offsets[class as usize] = Some(anchor.build(var_store));
1198                }
1199                Mark2Record::new(anchor_offsets)
1200            })
1201            .collect();
1202        let mark2array = Mark2Array::new(mark2_records);
1203        vec![MarkMarkPosFormat1::new(
1204            mark_coverage,
1205            mark2_coverage,
1206            mark_array,
1207            mark2array,
1208        )]
1209    }
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214    use super::*;
1215
1216    fn gid(raw: u16) -> GlyphId16 {
1217        GlyphId16::new(raw)
1218    }
1219
1220    fn advance(val: i16) -> ValueRecordBuilder {
1221        ValueRecordBuilder::new().with_x_advance(val)
1222    }
1223
1224    #[test]
1225    fn single_pos_round_trip() {
1226        let mut builder = SinglePosBuilder::default();
1227        builder.insert(gid(5), advance(-10));
1228        builder.insert(gid(1), advance(20));
1229
1230        // yielded in glyph order, not insertion order
1231        let items = builder.iter().collect::<Vec<_>>();
1232        assert_eq!(items, vec![(gid(1), &advance(20)), (gid(5), &advance(-10))]);
1233    }
1234
1235    #[test]
1236    fn pair_pos_round_trip() {
1237        let mut builder = PairPosBuilder::default();
1238        builder.insert_pair(gid(1), advance(7), gid(2), ValueRecordBuilder::new());
1239        builder.insert_classes(
1240            [gid(3), gid(4)].into_iter().collect(),
1241            advance(9),
1242            [gid(5)].into_iter().collect(),
1243            ValueRecordBuilder::new(),
1244        );
1245
1246        let pairs = builder
1247            .iter_pairs()
1248            .map(|(g1, g2, v1, _)| (g1, g2, v1.clone()))
1249            .collect::<Vec<_>>();
1250        assert_eq!(pairs, vec![(gid(1), gid(2), advance(7))]);
1251
1252        let classes = builder
1253            .iter_class_pairs()
1254            .map(|(c1, c2, v1, _)| (c1.iter().collect::<Vec<_>>(), c2.len(), v1.clone()))
1255            .collect::<Vec<_>>();
1256        assert_eq!(classes, vec![(vec![gid(3), gid(4)], 1, advance(9))]);
1257    }
1258
1259    #[test]
1260    fn class_pair_subtable_boundaries() {
1261        let class = |ids: &[u16]| ids.iter().map(|id| gid(*id)).collect::<IntSet<_>>();
1262        let mut builder = PairPosBuilder::default();
1263        builder.insert_classes(class(&[1, 2]), advance(1), class(&[9]), advance(0));
1264        // overlaps the first class without equalling it: new subtable
1265        builder.insert_classes(class(&[1, 2, 3]), advance(2), class(&[9]), advance(0));
1266        // disjoint from the second subtable's first class: joins it
1267        builder.insert_classes(class(&[4]), advance(3), class(&[9]), advance(0));
1268
1269        let subtables = builder
1270            .iter_class_subtables()
1271            .map(|rules| rules.map(|(_, _, v1, _)| v1.clone()).collect::<Vec<_>>())
1272            .collect::<Vec<_>>();
1273        assert_eq!(
1274            subtables,
1275            vec![vec![advance(1)], vec![advance(2), advance(3)]]
1276        );
1277        assert_eq!(builder.iter_class_pairs().count(), 3);
1278    }
1279
1280    #[test]
1281    fn cursive_round_trip() {
1282        let mut builder = CursivePosBuilder::default();
1283        builder.insert(gid(1), Some(AnchorBuilder::new(10, 20)), None);
1284
1285        let items = builder
1286            .iter()
1287            .map(|(g, entry, exit)| (g, entry.cloned(), exit.cloned()))
1288            .collect::<Vec<_>>();
1289        assert_eq!(
1290            items,
1291            vec![(gid(1), Some(AnchorBuilder::new(10, 20)), None)]
1292        );
1293    }
1294
1295    #[test]
1296    fn mark_to_base_round_trip() {
1297        let mut builder = MarkToBaseBuilder::default();
1298        builder
1299            .insert_mark(gid(10), "top", AnchorBuilder::new(1, 2))
1300            .unwrap();
1301        builder
1302            .insert_mark(gid(11), "bottom", AnchorBuilder::new(3, 4))
1303            .unwrap();
1304        builder.insert_base(gid(1), "top", AnchorBuilder::new(5, 6));
1305        builder.insert_base(gid(1), "bottom", AnchorBuilder::new(7, 8));
1306
1307        let marks = builder
1308            .iter_marks()
1309            .map(|(g, class, anchor)| (g, class, anchor.clone()))
1310            .collect::<Vec<_>>();
1311        assert_eq!(
1312            marks,
1313            vec![
1314                (gid(10), "top", AnchorBuilder::new(1, 2)),
1315                (gid(11), "bottom", AnchorBuilder::new(3, 4)),
1316            ]
1317        );
1318
1319        // one item per (glyph, class)
1320        let bases = builder
1321            .iter_bases()
1322            .map(|(g, class, anchor)| (g, class, anchor.clone()))
1323            .collect::<Vec<_>>();
1324        assert_eq!(
1325            bases,
1326            vec![
1327                (gid(1), "top", AnchorBuilder::new(5, 6)),
1328                (gid(1), "bottom", AnchorBuilder::new(7, 8)),
1329            ]
1330        );
1331    }
1332
1333    /// Two builders that saw the same classes in a different order assign them
1334    /// different internal ids; iterating by name must hide that.
1335    #[test]
1336    fn mark_class_names_are_id_order_independent() {
1337        let mut first = MarkToBaseBuilder::default();
1338        first
1339            .insert_mark(gid(10), "top", AnchorBuilder::new(1, 2))
1340            .unwrap();
1341        first
1342            .insert_mark(gid(11), "bottom", AnchorBuilder::new(3, 4))
1343            .unwrap();
1344        first.insert_base(gid(1), "bottom", AnchorBuilder::new(7, 8));
1345
1346        let mut second = MarkToBaseBuilder::default();
1347        second
1348            .insert_mark(gid(11), "bottom", AnchorBuilder::new(3, 4))
1349            .unwrap();
1350        second
1351            .insert_mark(gid(10), "top", AnchorBuilder::new(1, 2))
1352            .unwrap();
1353        second.insert_base(gid(1), "bottom", AnchorBuilder::new(7, 8));
1354
1355        assert_eq!(
1356            first.iter_bases().map(|(_, c, _)| c).collect::<Vec<_>>(),
1357            second.iter_bases().map(|(_, c, _)| c).collect::<Vec<_>>(),
1358        );
1359        // ...and they really did get different ids internally
1360        assert_ne!(first.marks.classes, second.marks.classes);
1361    }
1362
1363    #[test]
1364    fn mark_to_mark_round_trip() {
1365        let mut builder = MarkToMarkBuilder::default();
1366        builder
1367            .insert_mark1(gid(10), "top", AnchorBuilder::new(1, 2))
1368            .unwrap();
1369        builder.insert_mark2(gid(20), "top", AnchorBuilder::new(3, 4));
1370
1371        assert_eq!(
1372            builder
1373                .iter_mark1s()
1374                .map(|(g, c, _)| (g, c))
1375                .collect::<Vec<_>>(),
1376            vec![(gid(10), "top")]
1377        );
1378        assert_eq!(
1379            builder
1380                .iter_mark2s()
1381                .map(|(g, c, _)| (g, c))
1382                .collect::<Vec<_>>(),
1383            vec![(gid(20), "top")]
1384        );
1385    }
1386
1387    #[test]
1388    fn mark_to_lig_round_trip() {
1389        let mut builder = MarkToLigBuilder::default();
1390        builder
1391            .insert_mark(gid(10), "top", AnchorBuilder::new(1, 2))
1392            .unwrap();
1393        builder.insert_ligature(gid(1), "top", vec![Some(AnchorBuilder::new(5, 6)), None]);
1394
1395        let ligs = builder.iter_ligatures().collect::<Vec<_>>();
1396        assert_eq!(ligs.len(), 1);
1397        let (glyph, components) = ligs[0];
1398        assert_eq!(glyph, gid(1));
1399        assert_eq!(components.len(), 2);
1400        assert_eq!(components[0].get("top"), Some(&AnchorBuilder::new(5, 6)));
1401        // the second component had no anchor for this class
1402        assert!(components[1].is_empty());
1403    }
1404}