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    /// Add a new single-pos rule to this builder.
280    pub fn insert(&mut self, glyph: GlyphId16, record: ValueRecordBuilder) {
281        self.items.insert(glyph, record);
282    }
283
284    /// Check whether this glyph already has an assigned value in this builder.
285    pub fn can_add(&self, glyph: GlyphId16, value: &ValueRecordBuilder) -> bool {
286        self.items
287            .get(&glyph)
288            .map(|existing| existing == value)
289            .unwrap_or(true)
290    }
291}
292
293impl Builder for SinglePosBuilder {
294    type Output = Vec<SinglePos>;
295
296    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
297        fn build_subtable(items: BTreeMap<GlyphId16, &ValueRecord>) -> SinglePos {
298            let first = *items.values().next().unwrap();
299            let use_format_1 = first.format().is_empty() || items.values().all(|val| val == &first);
300            let coverage: CoverageTable = items.keys().copied().collect();
301            if use_format_1 {
302                SinglePos::format_1(coverage.clone(), first.clone())
303            } else {
304                SinglePos::format_2(coverage, items.into_values().cloned().collect())
305            }
306        }
307        const NEW_SUBTABLE_COST: usize = 10;
308        let items = self
309            .items
310            .into_iter()
311            .map(|(glyph, anchor)| (glyph, anchor.build(var_store)))
312            .collect::<BTreeMap<_, _>>();
313
314        // list of sets of glyph ids which will end up in their own subtables
315        let mut subtables = Vec::new();
316        let mut group_by_record: HashMap<&ValueRecord, BTreeMap<GlyphId16, &ValueRecord>> =
317            Default::default();
318
319        // first group by specific record; glyphs that share a record can use
320        // the more efficient format-1 subtable type
321        for (gid, value) in &items {
322            group_by_record
323                .entry(value)
324                .or_default()
325                .insert(*gid, value);
326        }
327        let mut group_by_format: HashMap<ValueFormat, BTreeMap<GlyphId16, &ValueRecord>> =
328            Default::default();
329        for (value, glyphs) in group_by_record {
330            // if this saves us size, use format 1
331            if glyphs.len() * value.encoded_size() > NEW_SUBTABLE_COST {
332                subtables.push(glyphs);
333                // else split based on value format; each format will be its own
334                // format 2 table
335            } else {
336                group_by_format
337                    .entry(value.format())
338                    .or_default()
339                    .extend(glyphs.into_iter());
340            }
341        }
342        subtables.extend(group_by_format.into_values());
343
344        let mut output = subtables
345            .into_iter()
346            .map(build_subtable)
347            .collect::<Vec<_>>();
348
349        // finally sort the subtables: first in decreasing order of size,
350        // using first glyph id to break ties (matches feaLib)
351        output.sort_unstable_by_key(|table| match table {
352            SinglePos::Format1(table) => cmp_coverage_key(&table.coverage),
353            SinglePos::Format2(table) => cmp_coverage_key(&table.coverage),
354        });
355        output
356    }
357}
358
359fn cmp_coverage_key(coverage: &CoverageTable) -> impl Ord {
360    (std::cmp::Reverse(coverage.len()), coverage.iter().next())
361}
362
363/// A builder for GPOS type 2 (PairPos) subtables
364///
365/// This builder can build both glyph and class-based kerning subtables.
366#[derive(Clone, Debug, Default, PartialEq)]
367#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
368pub struct PairPosBuilder {
369    pairs: GlyphPairPosBuilder,
370    classes: ClassPairPosBuilder,
371}
372
373#[derive(Clone, Debug, Default, PartialEq)]
374#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
375struct GlyphPairPosBuilder(
376    BTreeMap<GlyphId16, BTreeMap<GlyphId16, (ValueRecordBuilder, ValueRecordBuilder)>>,
377);
378
379#[derive(Clone, Debug, PartialEq)]
380#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
381struct ClassPairPosSubtable {
382    items:
383        BTreeMap<IntSet<GlyphId16>, BTreeMap<GlyphSet, (ValueRecordBuilder, ValueRecordBuilder)>>,
384    classdef_1: ClassDefBuilder,
385    classdef_2: ClassDefBuilder,
386}
387
388impl Default for ClassPairPosSubtable {
389    fn default() -> Self {
390        Self {
391            items: Default::default(),
392            classdef_1: ClassDefBuilder::new_using_class_0(),
393            classdef_2: Default::default(),
394        }
395    }
396}
397
398#[derive(Clone, Debug, Default, PartialEq)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
400struct ClassPairPosBuilder(Vec<ClassPairPosSubtable>);
401
402impl ClassPairPosBuilder {
403    fn insert(
404        &mut self,
405        class1: GlyphSet,
406        record1: ValueRecordBuilder,
407        class2: GlyphSet,
408        record2: ValueRecordBuilder,
409    ) {
410        if self.0.last().map(|last| last.can_add(&class1, &class2)) != Some(true) {
411            self.0.push(Default::default())
412        }
413        self.0
414            .last_mut()
415            .unwrap()
416            .add(class1, class2, record1, record2);
417    }
418}
419
420impl ClassPairPosSubtable {
421    fn can_add(&self, class1: &GlyphSet, class2: &GlyphSet) -> bool {
422        self.classdef_1.can_add(class1) && self.classdef_2.can_add(class2)
423    }
424
425    fn add(
426        &mut self,
427        class1: GlyphSet,
428        class2: GlyphSet,
429        record1: ValueRecordBuilder,
430        record2: ValueRecordBuilder,
431    ) {
432        self.classdef_1.checked_add(class1.clone());
433        self.classdef_2.checked_add(class2.clone());
434        self.items
435            .entry(class1)
436            .or_default()
437            .insert(class2, (record1, record2));
438    }
439
440    // determine the union of each of the two value formats
441    //
442    // we need a to ensure that the value format we use can represent all
443    // of the fields present in any of the value records in this subtable.
444    //
445    // see https://github.com/fonttools/fonttools/blob/770917d89e9/Lib/fontTools/otlLib/builder.py#L2066
446    fn compute_value_formats(&self) -> (ValueFormat, ValueFormat) {
447        self.items.values().flat_map(|v| v.values()).fold(
448            (ValueFormat::empty(), ValueFormat::empty()),
449            |(acc1, acc2), (f1, f2)| (acc1 | f1.format(), acc2 | f2.format()),
450        )
451    }
452}
453
454impl PairPosBuilder {
455    /// Returns `true` if no rules have been added to this builder
456    pub fn is_empty(&self) -> bool {
457        self.pairs.0.is_empty() && self.classes.0.is_empty()
458    }
459
460    /// The number of rules in the builder
461    pub fn len(&self) -> usize {
462        self.pairs.0.values().map(|vals| vals.len()).sum::<usize>()
463            + self
464                .classes
465                .0
466                .iter()
467                .map(|sub| sub.items.values().len())
468                .sum::<usize>()
469    }
470
471    /// Insert a new kerning pair
472    pub fn insert_pair(
473        &mut self,
474        glyph1: GlyphId16,
475        record1: ValueRecordBuilder,
476        glyph2: GlyphId16,
477        record2: ValueRecordBuilder,
478    ) {
479        // "When specific kern pair rules conflict, the first rule specified is used,
480        // and later conflicting rule are skipped"
481        // https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6bii-enumerating-pairs
482        // E.g.:
483        //   @A = [A Aacute Agrave]
484        //   feature kern {
485        //     pos A B 100;
486        //     enum pos @A B -50;
487        //   } kern;
488        // should result in a A B kerning value of 100, not -50.
489        // https://github.com/googlefonts/fontc/issues/550
490        self.pairs
491            .0
492            .entry(glyph1)
493            .or_default()
494            .entry(glyph2)
495            .or_insert((record1, record2));
496    }
497
498    /// Insert a new class-based kerning rule.
499    pub fn insert_classes(
500        &mut self,
501        class1: GlyphSet,
502        record1: ValueRecordBuilder,
503        class2: GlyphSet,
504        record2: ValueRecordBuilder,
505    ) {
506        self.classes.insert(class1, record1, class2, record2)
507    }
508}
509
510impl Builder for PairPosBuilder {
511    type Output = Vec<PairPos>;
512
513    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
514        let mut out = self.pairs.build(var_store);
515        out.extend(self.classes.build(var_store));
516        out
517    }
518}
519
520impl Builder for GlyphPairPosBuilder {
521    type Output = Vec<PairPos>;
522
523    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
524        let mut split_by_format = BTreeMap::<_, BTreeMap<_, Vec<_>>>::default();
525        for (g1, map) in self.0 {
526            for (g2, (v1, v2)) in map {
527                split_by_format
528                    .entry((v1.format(), v2.format()))
529                    .or_default()
530                    .entry(g1)
531                    .or_default()
532                    .push(PairValueRecord::new(
533                        g2,
534                        v1.build(var_store),
535                        v2.build(var_store),
536                    ));
537            }
538        }
539
540        split_by_format
541            .into_values()
542            .map(|map| {
543                let coverage = map.keys().copied().collect();
544                let pair_sets = map.into_values().map(PairSet::new).collect();
545                PairPos::format_1(coverage, pair_sets)
546            })
547            .collect()
548    }
549}
550
551impl Builder for ClassPairPosBuilder {
552    type Output = Vec<PairPos>;
553
554    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
555        self.0.into_iter().map(|sub| sub.build(var_store)).collect()
556    }
557}
558
559impl Builder for ClassPairPosSubtable {
560    type Output = PairPos;
561
562    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
563        assert!(!self.items.is_empty(), "filter before here");
564        let (format1, format2) = self.compute_value_formats();
565        // we have a set of classes/values with a single valueformat
566
567        // an empty record, if some pair of classes have no entry
568        let empty_record = Class2Record::new(
569            ValueRecord::new().with_explicit_value_format(format1),
570            ValueRecord::new().with_explicit_value_format(format2),
571        );
572
573        let (class1def, class1map) = self.classdef_1.build_with_mapping();
574        let (class2def, class2map) = self.classdef_2.build_with_mapping();
575
576        let coverage = self.items.keys().flat_map(GlyphSet::iter).collect();
577
578        let mut out = vec![Class1Record::default(); self.items.len()];
579        for (cls1, stuff) in self.items {
580            let idx = class1map.get(&cls1).unwrap();
581            let mut records = vec![empty_record.clone(); class2map.len() + 1];
582            for (class, (v1, v2)) in stuff {
583                let idx = class2map.get(&class).unwrap();
584                records[*idx as usize] = Class2Record::new(
585                    v1.build(var_store).with_explicit_value_format(format1),
586                    v2.build(var_store).with_explicit_value_format(format2),
587                );
588            }
589            out[*idx as usize] = Class1Record::new(records);
590        }
591        PairPos::format_2(coverage, class1def, class2def, out)
592    }
593}
594
595/// A builder for GPOS Lookup Type 3, Cursive Attachment
596#[derive(Clone, Debug, Default, PartialEq, Eq)]
597#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
598pub struct CursivePosBuilder {
599    // (entry, exit)
600    items: BTreeMap<GlyphId16, (Option<AnchorBuilder>, Option<AnchorBuilder>)>,
601}
602
603impl CursivePosBuilder {
604    /// Insert a new entry/exit anchor pair for a glyph.
605    pub fn insert(
606        &mut self,
607        glyph: GlyphId16,
608        entry: Option<AnchorBuilder>,
609        exit: Option<AnchorBuilder>,
610    ) {
611        self.items.insert(glyph, (entry, exit));
612    }
613}
614
615impl Builder for CursivePosBuilder {
616    type Output = Vec<CursivePosFormat1>;
617
618    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
619        let coverage = self.items.keys().copied().collect();
620        let records = self
621            .items
622            .into_values()
623            .map(|(entry, exit)| {
624                EntryExitRecord::new(
625                    entry.map(|x| x.build(var_store)),
626                    exit.map(|x| x.build(var_store)),
627                )
628            })
629            .collect();
630        vec![CursivePosFormat1::new(coverage, records)]
631    }
632}
633
634// shared between several tables
635#[derive(Clone, Debug, Default, PartialEq, Eq)]
636#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
637struct MarkList {
638    // (class id, anchor)
639    glyphs: BTreeMap<GlyphId16, (u16, AnchorBuilder)>,
640    // map class names to their idx for this table
641    classes: HashMap<String, u16>,
642}
643
644impl MarkList {
645    /// If this glyph is already part of another class, return the previous class name
646    ///
647    /// Otherwise return the u16 id for this class, in this lookup.
648    fn insert(
649        &mut self,
650        glyph: GlyphId16,
651        class: &str,
652        anchor: AnchorBuilder,
653    ) -> Result<u16, PreviouslyAssignedClass> {
654        let next_id = self.classes.len().try_into().unwrap();
655        let id = self.classes.get(class).copied().unwrap_or_else(|| {
656            self.classes.insert(class.to_owned(), next_id);
657            next_id
658        });
659        if let Some(prev) = self
660            .glyphs
661            .insert(glyph, (id, anchor))
662            .filter(|prev| prev.0 != id)
663        {
664            let class = self
665                .classes
666                .iter()
667                .find_map(|(name, idx)| (*idx == prev.0).then(|| name.clone()))
668                .unwrap();
669
670            return Err(PreviouslyAssignedClass {
671                glyph_id: glyph,
672                class,
673            });
674        }
675        Ok(id)
676    }
677
678    fn glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
679        self.glyphs.keys().copied()
680    }
681
682    fn get_class(&self, class_name: &str) -> u16 {
683        *self
684            .classes
685            .get(class_name)
686            // this is internal API, we uphold this
687            .expect("marks added before bases")
688    }
689}
690
691impl Builder for MarkList {
692    type Output = (CoverageTable, MarkArray);
693
694    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
695        let coverage = self.glyphs().collect();
696        let array = MarkArray::new(
697            self.glyphs
698                .into_values()
699                .map(|(class, anchor)| MarkRecord::new(class, anchor.build(var_store)))
700                .collect(),
701        );
702        (coverage, array)
703    }
704}
705
706/// A builder for GPOS Lookup Type 4, Mark-to-Base
707#[derive(Clone, Debug, Default, PartialEq, Eq)]
708#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
709pub struct MarkToBaseBuilder {
710    marks: MarkList,
711    bases: BTreeMap<GlyphId16, Vec<(u16, AnchorBuilder)>>,
712}
713
714/// An error indicating a given glyph has been assigned to multiple mark classes
715#[derive(Clone, Debug, Default)]
716pub struct PreviouslyAssignedClass {
717    /// The ID of the glyph in conflict
718    pub glyph_id: GlyphId16,
719    /// The name of the previous class
720    pub class: String,
721}
722
723impl std::error::Error for PreviouslyAssignedClass {}
724
725impl std::fmt::Display for PreviouslyAssignedClass {
726    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
727        write!(
728            f,
729            "Glyph '{}' was previously assigned to class '{}'",
730            self.glyph_id.to_u16(),
731            self.class
732        )
733    }
734}
735
736impl MarkToBaseBuilder {
737    /// Add a new mark glyph.
738    ///
739    /// If this glyph already exists in another mark class, we return the
740    /// previous class; this is likely an error.
741    pub fn insert_mark(
742        &mut self,
743        glyph: GlyphId16,
744        class: &str,
745        anchor: AnchorBuilder,
746    ) -> Result<u16, PreviouslyAssignedClass> {
747        self.marks.insert(glyph, class, anchor)
748    }
749
750    /// Insert a new base glyph.
751    pub fn insert_base(&mut self, glyph: GlyphId16, class: &str, anchor: AnchorBuilder) {
752        let class = self.marks.get_class(class);
753        self.bases.entry(glyph).or_default().push((class, anchor))
754    }
755
756    /// Returns an iterator over all of the base glyphs
757    pub fn base_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
758        self.bases.keys().copied()
759    }
760
761    /// Returns an iterator over all of the mark glyphs
762    pub fn mark_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
763        self.marks.glyphs()
764    }
765}
766
767impl Builder for MarkToBaseBuilder {
768    type Output = Vec<MarkBasePosFormat1>;
769
770    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
771        let MarkToBaseBuilder { marks, bases } = self;
772        let n_classes = marks.classes.len();
773
774        let (mark_coverage, mark_array) = marks.build(var_store);
775        let base_coverage = bases.keys().copied().collect();
776        let base_records = bases
777            .into_values()
778            .map(|anchors| {
779                let mut anchor_offsets = vec![None; n_classes];
780                for (class, anchor) in anchors {
781                    anchor_offsets[class as usize] = Some(anchor.build(var_store));
782                }
783                BaseRecord::new(anchor_offsets)
784            })
785            .collect();
786        let base_array = BaseArray::new(base_records);
787        vec![MarkBasePosFormat1::new(
788            mark_coverage,
789            base_coverage,
790            mark_array,
791            base_array,
792        )]
793    }
794}
795
796/// A builder for GPOS Lookup Type 5, Mark-to-Ligature
797#[derive(Clone, Debug, Default, PartialEq, Eq)]
798#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
799pub struct MarkToLigBuilder {
800    marks: MarkList,
801    ligatures: BTreeMap<GlyphId16, Vec<BTreeMap<String, AnchorBuilder>>>,
802}
803
804impl MarkToLigBuilder {
805    /// `true` if this builder contains no rules
806    pub fn is_empty(&self) -> bool {
807        self.ligatures.is_empty()
808    }
809
810    /// Add a new mark glyph.
811    ///
812    /// If this glyph already exists in another mark class, we return the
813    /// previous class; this is likely an error.
814    pub fn insert_mark(
815        &mut self,
816        glyph: GlyphId16,
817        class: &str,
818        anchor: AnchorBuilder,
819    ) -> Result<u16, PreviouslyAssignedClass> {
820        self.marks.insert(glyph, class, anchor)
821    }
822
823    /// Add a ligature base, providing a set of anchors for each component.
824    ///
825    /// There must be an item in the vec for each component in the ligature
826    /// glyph, but the anchors can be sparse; null anchors will be added for
827    /// any classes that are missing.
828    ///
829    /// NOTE: this API is designed for use from a FEA compiler, as it closely
830    /// mimics how the FEA source represents these rules where you process each
831    /// component in order, with all the marks defined for that component)
832    /// but this is less useful for public API, where you are more often dealing
833    /// with marks a class at a time. For that reason we provide an alternative
834    /// public method below.
835    pub fn add_ligature_components_directly(
836        &mut self,
837        glyph: GlyphId16,
838        components: Vec<BTreeMap<String, AnchorBuilder>>,
839    ) {
840        self.ligatures.insert(glyph, components);
841    }
842
843    /// Add ligature anchors for a specific mark class.
844    ///
845    /// This can be called multiple times for the same ligature glyph, to add anchors
846    /// for multiple mark classes; however the number of components must be equal
847    /// on each call for a given glyph id.
848    ///
849    /// If a component has no anchor for a given mark class, you must include an
850    /// explicit 'None' in the appropriate ordering.
851    pub fn insert_ligature(
852        &mut self,
853        glyph: GlyphId16,
854        class: &str,
855        components: Vec<Option<AnchorBuilder>>,
856    ) {
857        let component_list = self.ligatures.entry(glyph).or_default();
858        if component_list.is_empty() {
859            component_list.resize(components.len(), Default::default());
860        } else if component_list.len() != components.len() {
861            log::warn!("mismatched component lengths for anchors in glyph {glyph}");
862        }
863        for (i, anchor) in components.into_iter().enumerate() {
864            if let Some(anchor) = anchor {
865                component_list[i].insert(class.to_owned(), anchor);
866            }
867        }
868    }
869
870    /// Returns an iterator over all of the mark glyphs
871    pub fn mark_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
872        self.marks.glyphs()
873    }
874
875    /// Returns an iterator over all of the ligature glyphs
876    pub fn lig_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
877        self.ligatures.keys().copied()
878    }
879}
880
881impl Builder for MarkToLigBuilder {
882    type Output = Vec<MarkLigPosFormat1>;
883
884    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
885        let MarkToLigBuilder { marks, ligatures } = self;
886        let n_classes = marks.classes.len();
887
888        // LigArray:
889        // - [LigatureAttach] (one per ligature glyph)
890        //    - [ComponentRecord] (one per component)
891        //    - [Anchor] (one per mark-class)
892        let ligature_coverage = ligatures.keys().copied().collect();
893        let ligature_array = ligatures
894            .into_values()
895            .map(|components| {
896                let comp_records = components
897                    .into_iter()
898                    .map(|anchors| {
899                        let mut anchor_offsets = vec![None; n_classes];
900                        for (class, anchor) in anchors {
901                            let class_idx = marks.get_class(&class);
902                            anchor_offsets[class_idx as usize] = Some(anchor.build(var_store));
903                        }
904                        ComponentRecord::new(anchor_offsets)
905                    })
906                    .collect();
907                LigatureAttach::new(comp_records)
908            })
909            .collect();
910        let ligature_array = LigatureArray::new(ligature_array);
911        let (mark_coverage, mark_array) = marks.build(var_store);
912        vec![MarkLigPosFormat1::new(
913            mark_coverage,
914            ligature_coverage,
915            mark_array,
916            ligature_array,
917        )]
918    }
919}
920
921/// A builder for GPOS Type 6 (Mark-to-Mark)
922#[derive(Clone, Debug, Default, PartialEq, Eq)]
923#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
924pub struct MarkToMarkBuilder {
925    attaching_marks: MarkList,
926    base_marks: BTreeMap<GlyphId16, Vec<(u16, AnchorBuilder)>>,
927}
928
929impl MarkToMarkBuilder {
930    /// Add a new mark1 (combining) glyph.
931    ///
932    /// If this glyph already exists in another mark class, we return the
933    /// previous class; this is likely an error.
934    pub fn insert_mark1(
935        &mut self,
936        glyph: GlyphId16,
937        class: &str,
938        anchor: AnchorBuilder,
939    ) -> Result<u16, PreviouslyAssignedClass> {
940        self.attaching_marks.insert(glyph, class, anchor)
941    }
942
943    /// Insert a new mark2 (base) glyph
944    pub fn insert_mark2(&mut self, glyph: GlyphId16, class: &str, anchor: AnchorBuilder) {
945        let id = self.attaching_marks.get_class(class);
946        self.base_marks.entry(glyph).or_default().push((id, anchor))
947    }
948
949    /// Returns an iterator over all of the mark1 glyphs
950    pub fn mark1_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
951        self.attaching_marks.glyphs()
952    }
953
954    /// Returns an iterator over all of the mark2 glyphs
955    pub fn mark2_glyphs(&self) -> impl Iterator<Item = GlyphId16> + Clone + '_ {
956        self.base_marks.keys().copied()
957    }
958}
959
960impl Builder for MarkToMarkBuilder {
961    type Output = Vec<MarkMarkPosFormat1>;
962
963    fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
964        let MarkToMarkBuilder {
965            attaching_marks,
966            base_marks,
967        } = self;
968        let n_classes = attaching_marks.classes.len();
969
970        let (mark_coverage, mark_array) = attaching_marks.build(var_store);
971        let mark2_coverage = base_marks.keys().copied().collect();
972        let mark2_records = base_marks
973            .into_values()
974            .map(|anchors| {
975                let mut anchor_offsets = vec![None; n_classes];
976                for (class, anchor) in anchors {
977                    anchor_offsets[class as usize] = Some(anchor.build(var_store));
978                }
979                Mark2Record::new(anchor_offsets)
980            })
981            .collect();
982        let mark2array = Mark2Array::new(mark2_records);
983        vec![MarkMarkPosFormat1::new(
984            mark_coverage,
985            mark2_coverage,
986            mark_array,
987            mark2array,
988        )]
989    }
990}