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