write_fonts/tables/
layout.rs

1//! OpenType layout.
2
3use std::{
4    collections::{BTreeMap, HashSet},
5    hash::Hash,
6};
7
8pub use read_fonts::tables::layout::LookupFlag;
9use read_fonts::FontRead;
10
11#[cfg(test)]
12#[path = "../tests/layout.rs"]
13mod spec_tests;
14
15include!("../../generated/generated_layout.rs");
16
17/// A macro to implement the [LookupSubtable] trait.
18macro_rules! lookup_type {
19    (gpos, $ty:ty, $val:expr) => {
20        impl LookupSubtable for $ty {
21            const TYPE: LookupType = LookupType::Gpos($val);
22        }
23    };
24
25    (gsub, $ty:ty, $val:expr) => {
26        impl LookupSubtable for $ty {
27            const TYPE: LookupType = LookupType::Gsub($val);
28        }
29    };
30}
31
32/// A macro to define a newtype around an existing table, that defers all
33/// impls to that table.
34///
35/// We use this to ensure that shared lookup types (Sequence/Chain
36/// lookups) can be given different lookup ids for each of GSUB/GPOS.
37macro_rules! table_newtype {
38    ($name:ident, $inner:ident, $read_type:path) => {
39        /// A typed wrapper around a shared table.
40        ///
41        /// This is used so that we can associate the correct lookup ids for
42        /// lookups that are shared between GPOS/GSUB.
43        ///
44        /// You can access the inner type via `Deref` or the `as_inner` method.
45        #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
46        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47        pub struct $name($inner);
48
49        impl $name {
50            /// Return a reference to the inner type.
51            pub fn as_inner(&self) -> &$inner {
52                &self.0
53            }
54        }
55
56        impl std::ops::Deref for $name {
57            type Target = $inner;
58            fn deref(&self) -> &Self::Target {
59                &self.0
60            }
61        }
62
63        impl std::ops::DerefMut for $name {
64            fn deref_mut(&mut self) -> &mut Self::Target {
65                &mut self.0
66            }
67        }
68
69        impl FontWrite for $name {
70            fn write_into(&self, writer: &mut TableWriter) {
71                self.0.write_into(writer)
72            }
73
74            fn table_type(&self) -> crate::table_type::TableType {
75                self.0.table_type()
76            }
77        }
78
79        impl Validate for $name {
80            fn validate_impl(&self, ctx: &mut ValidationCtx) {
81                self.0.validate_impl(ctx)
82            }
83        }
84
85        impl<'a> FromObjRef<$read_type> for $name {
86            fn from_obj_ref(obj: &$read_type, _data: FontData) -> Self {
87                Self(FromObjRef::from_obj_ref(obj, _data))
88            }
89        }
90
91        impl<'a> FromTableRef<$read_type> for $name {}
92
93        impl From<$inner> for $name {
94            fn from(src: $inner) -> $name {
95                $name(src)
96            }
97        }
98    };
99}
100
101pub(crate) use lookup_type;
102pub(crate) use table_newtype;
103
104impl FontWrite for LookupFlag {
105    fn write_into(&self, writer: &mut TableWriter) {
106        self.to_bits().write_into(writer)
107    }
108}
109
110impl<T: LookupSubtable + FontWrite> FontWrite for Lookup<T> {
111    fn write_into(&self, writer: &mut TableWriter) {
112        T::TYPE.write_into(writer);
113        self.lookup_flag.write_into(writer);
114        u16::try_from(self.subtables.len())
115            .unwrap()
116            .write_into(writer);
117        self.subtables.write_into(writer);
118        self.mark_filtering_set.write_into(writer);
119    }
120
121    fn table_type(&self) -> crate::table_type::TableType {
122        T::TYPE.into()
123    }
124}
125
126impl Lookup<SequenceContext> {
127    /// Convert this untyped SequenceContext into its GSUB or GPOS specific version
128    pub fn into_concrete<T: From<SequenceContext>>(self) -> Lookup<T> {
129        let Lookup {
130            lookup_flag,
131            subtables,
132            mark_filtering_set,
133        } = self;
134        let subtables = subtables
135            .into_iter()
136            .map(|offset| OffsetMarker::new(offset.into_inner().into()))
137            .collect();
138        Lookup {
139            lookup_flag,
140            subtables,
141            mark_filtering_set,
142        }
143    }
144}
145
146impl Lookup<ChainedSequenceContext> {
147    /// Convert this untyped SequenceContext into its GSUB or GPOS specific version
148    pub fn into_concrete<T: From<ChainedSequenceContext>>(self) -> Lookup<T> {
149        let Lookup {
150            lookup_flag,
151            subtables,
152            mark_filtering_set,
153        } = self;
154        let subtables = subtables
155            .into_iter()
156            .map(|offset| OffsetMarker::new(offset.into_inner().into()))
157            .collect();
158        Lookup {
159            lookup_flag,
160            subtables,
161            mark_filtering_set,
162        }
163    }
164}
165
166/// A utility trait for writing lookup tables.
167///
168/// This allows us to attach the numerical lookup type to the appropriate concrete
169/// types, so that we can write it as needed without passing it around.
170pub trait LookupSubtable {
171    /// The lookup type of this layout subtable.
172    const TYPE: LookupType;
173}
174
175/// Raw values for the different layout subtables
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub enum LookupType {
178    Gpos(u16),
179    Gsub(u16),
180}
181
182impl LookupType {
183    pub(crate) const GSUB_EXT_TYPE: u16 = 7;
184    pub(crate) const GPOS_EXT_TYPE: u16 = 9;
185    pub(crate) const PAIR_POS: u16 = 2;
186    pub(crate) const MARK_TO_BASE: u16 = 4;
187
188    pub(crate) fn to_raw(self) -> u16 {
189        match self {
190            LookupType::Gpos(val) => val,
191            LookupType::Gsub(val) => val,
192        }
193    }
194
195    pub(crate) fn promote(self) -> Self {
196        match self {
197            LookupType::Gpos(Self::GPOS_EXT_TYPE) | LookupType::Gsub(Self::GSUB_EXT_TYPE) => {
198                panic!("should never be promoting an extension subtable")
199            }
200            LookupType::Gpos(_) => LookupType::Gpos(Self::GPOS_EXT_TYPE),
201            LookupType::Gsub(_) => LookupType::Gsub(Self::GSUB_EXT_TYPE),
202        }
203    }
204}
205
206impl FontWrite for LookupType {
207    fn write_into(&self, writer: &mut TableWriter) {
208        self.to_raw().write_into(writer)
209    }
210}
211
212#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214pub enum FeatureParams {
215    StylisticSet(StylisticSetParams),
216    Size(SizeParams),
217    CharacterVariant(CharacterVariantParams),
218}
219
220impl FontWrite for FeatureParams {
221    fn write_into(&self, writer: &mut TableWriter) {
222        match self {
223            FeatureParams::StylisticSet(table) => table.write_into(writer),
224            FeatureParams::Size(table) => table.write_into(writer),
225            FeatureParams::CharacterVariant(table) => table.write_into(writer),
226        }
227    }
228}
229
230impl Validate for FeatureParams {
231    fn validate_impl(&self, ctx: &mut ValidationCtx) {
232        match self {
233            Self::StylisticSet(table) => table.validate_impl(ctx),
234            Self::Size(table) => table.validate_impl(ctx),
235            Self::CharacterVariant(table) => table.validate_impl(ctx),
236        }
237    }
238}
239
240impl FromObjRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams {
241    fn from_obj_ref(from: &read_fonts::tables::layout::FeatureParams, data: FontData) -> Self {
242        use read_fonts::tables::layout::FeatureParams as FromType;
243        match from {
244            FromType::Size(thing) => Self::Size(SizeParams::from_obj_ref(thing, data)),
245            FromType::StylisticSet(thing) => {
246                Self::StylisticSet(FromObjRef::from_obj_ref(thing, data))
247            }
248            FromType::CharacterVariant(thing) => {
249                Self::CharacterVariant(FromObjRef::from_obj_ref(thing, data))
250            }
251        }
252    }
253}
254
255impl FromTableRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams {}
256
257impl ClassDefFormat1 {
258    fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
259        self.class_value_array.iter().enumerate().map(|(i, cls)| {
260            (
261                GlyphId16::new(self.start_glyph_id.to_u16().saturating_add(i as u16)),
262                *cls,
263            )
264        })
265    }
266}
267
268impl ClassRangeRecord {
269    fn validate_glyph_range(&self, ctx: &mut ValidationCtx) {
270        if self.start_glyph_id > self.end_glyph_id {
271            ctx.report(format!(
272                "start_glyph_id {} larger than end_glyph_id {}",
273                self.start_glyph_id, self.end_glyph_id
274            ));
275        }
276    }
277
278    fn contains(&self, gid: GlyphId16) -> bool {
279        (self.start_glyph_id..=self.end_glyph_id).contains(&gid)
280    }
281}
282
283impl ClassDefFormat2 {
284    fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
285        self.class_range_records.iter().flat_map(|rcd| {
286            (rcd.start_glyph_id.to_u16()..=rcd.end_glyph_id.to_u16())
287                .map(|gid| (GlyphId16::new(gid), rcd.class))
288        })
289    }
290}
291
292impl ClassDef {
293    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
294        let (one, two) = match self {
295            Self::Format1(table) => (Some(table.iter()), None),
296            Self::Format2(table) => (None, Some(table.iter())),
297        };
298
299        one.into_iter().flatten().chain(two.into_iter().flatten())
300    }
301
302    /// Return the glyph class for the provided glyph.
303    ///
304    /// Glyphs which have not been assigned a class are given class 0
305    pub fn get(&self, glyph: GlyphId16) -> u16 {
306        self.get_raw(glyph).unwrap_or(0)
307    }
308
309    // exposed for testing
310    fn get_raw(&self, glyph: GlyphId16) -> Option<u16> {
311        match self {
312            ClassDef::Format1(table) => glyph
313                .to_u16()
314                .checked_sub(table.start_glyph_id.to_u16())
315                .and_then(|idx| table.class_value_array.get(idx as usize))
316                .copied(),
317            ClassDef::Format2(table) => table
318                .class_range_records
319                .iter()
320                .find_map(|rec| rec.contains(glyph).then_some(rec.class)),
321        }
322    }
323
324    pub fn class_count(&self) -> u16 {
325        //TODO: implement a good integer set!!
326        self.iter()
327            .map(|(_gid, cls)| cls)
328            .chain(std::iter::once(0))
329            .collect::<HashSet<_>>()
330            .len()
331            .try_into()
332            .unwrap()
333    }
334}
335
336impl CoverageFormat1 {
337    fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
338        self.glyph_array.iter().copied()
339    }
340
341    fn len(&self) -> usize {
342        self.glyph_array.len()
343    }
344}
345
346impl CoverageFormat2 {
347    fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
348        self.range_records
349            .iter()
350            .flat_map(|rcd| iter_gids(rcd.start_glyph_id, rcd.end_glyph_id))
351    }
352
353    fn len(&self) -> usize {
354        self.range_records
355            .iter()
356            .map(|rcd| {
357                rcd.end_glyph_id
358                    .to_u16()
359                    .saturating_sub(rcd.start_glyph_id.to_u16()) as usize
360                    + 1
361            })
362            .sum()
363    }
364}
365
366impl CoverageTable {
367    pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
368        let (one, two) = match self {
369            Self::Format1(table) => (Some(table.iter()), None),
370            Self::Format2(table) => (None, Some(table.iter())),
371        };
372
373        one.into_iter().flatten().chain(two.into_iter().flatten())
374    }
375
376    pub fn len(&self) -> usize {
377        match self {
378            Self::Format1(table) => table.len(),
379            Self::Format2(table) => table.len(),
380        }
381    }
382
383    pub fn is_empty(&self) -> bool {
384        self.len() == 0
385    }
386}
387
388/// A builder for [ClassDef] tables.
389///
390/// This will choose the best format based for the included glyphs.
391#[derive(Debug, PartialEq, Eq)]
392pub struct ClassDefBuilder {
393    pub items: BTreeMap<GlyphId16, u16>,
394}
395
396/// A builder for [CoverageTable] tables.
397///
398/// This will choose the best format based for the included glyphs.
399#[derive(Debug, Default, PartialEq, Eq)]
400pub struct CoverageTableBuilder {
401    // invariant: is always sorted
402    glyphs: Vec<GlyphId16>,
403}
404
405impl FromIterator<GlyphId16> for CoverageTableBuilder {
406    fn from_iter<T: IntoIterator<Item = GlyphId16>>(iter: T) -> Self {
407        let glyphs = iter.into_iter().collect::<Vec<_>>();
408        CoverageTableBuilder::from_glyphs(glyphs)
409    }
410}
411
412impl FromIterator<GlyphId16> for CoverageTable {
413    fn from_iter<T: IntoIterator<Item = GlyphId16>>(iter: T) -> Self {
414        let glyphs = iter.into_iter().collect::<Vec<_>>();
415        CoverageTableBuilder::from_glyphs(glyphs).build()
416    }
417}
418
419impl CoverageTableBuilder {
420    /// Create a new builder from a vec of `GlyphId`.
421    pub fn from_glyphs(mut glyphs: Vec<GlyphId16>) -> Self {
422        glyphs.sort_unstable();
423        glyphs.dedup();
424        CoverageTableBuilder { glyphs }
425    }
426
427    /// Add a `GlyphId` to this coverage table.
428    ///
429    /// Returns the coverage index of the added glyph.
430    ///
431    /// If the glyph already exists, this returns its current index.
432    pub fn add(&mut self, glyph: GlyphId16) -> u16 {
433        match self.glyphs.binary_search(&glyph) {
434            Ok(ix) => ix as u16,
435            Err(ix) => {
436                self.glyphs.insert(ix, glyph);
437                // if we're over u16::MAX glyphs, crash
438                ix.try_into().unwrap()
439            }
440        }
441    }
442
443    //NOTE: it would be nice if we didn't do this intermediate step and instead
444    //wrote out bytes directly, but the current approach is simpler.
445    /// Convert this builder into the appropriate [CoverageTable] variant.
446    pub fn build(self) -> CoverageTable {
447        if should_choose_coverage_format_2(&self.glyphs) {
448            CoverageTable::Format2(CoverageFormat2 {
449                range_records: RangeRecord::iter_for_glyphs(&self.glyphs).collect(),
450            })
451        } else {
452            CoverageTable::Format1(CoverageFormat1 {
453                glyph_array: self.glyphs,
454            })
455        }
456    }
457}
458
459impl FromIterator<(GlyphId16, u16)> for ClassDefBuilder {
460    fn from_iter<T: IntoIterator<Item = (GlyphId16, u16)>>(iter: T) -> Self {
461        Self {
462            items: iter.into_iter().filter(|(_, cls)| *cls != 0).collect(),
463        }
464    }
465}
466
467impl FromIterator<(GlyphId16, u16)> for ClassDef {
468    fn from_iter<T: IntoIterator<Item = (GlyphId16, u16)>>(iter: T) -> Self {
469        ClassDefBuilder::from_iter(iter).build()
470    }
471}
472
473impl ClassDefBuilder {
474    fn prefer_format_1(&self) -> bool {
475        const U16_LEN: usize = std::mem::size_of::<u16>();
476        const FORMAT1_HEADER_LEN: usize = U16_LEN * 3;
477        const FORMAT2_HEADER_LEN: usize = U16_LEN * 2;
478        const CLASS_RANGE_RECORD_LEN: usize = U16_LEN * 3;
479        // format 2 is the most efficient way to represent an empty classdef
480        if self.items.is_empty() {
481            return false;
482        }
483        // calculate our format2 size:
484        let first = self.items.keys().next().map(|g| g.to_u16()).unwrap();
485        let last = self.items.keys().next_back().map(|g| g.to_u16()).unwrap();
486        let format1_array_len = (last - first) as usize + 1;
487        let len_format1 = FORMAT1_HEADER_LEN + format1_array_len * U16_LEN;
488        let len_format2 =
489            FORMAT2_HEADER_LEN + iter_class_ranges(&self.items).count() * CLASS_RANGE_RECORD_LEN;
490
491        len_format1 < len_format2
492    }
493
494    pub fn build(&self) -> ClassDef {
495        if self.prefer_format_1() {
496            let first = self.items.keys().next().map(|g| g.to_u16()).unwrap_or(0);
497            let last = self.items.keys().next_back().map(|g| g.to_u16());
498            let class_value_array = (first..=last.unwrap_or_default())
499                .map(|g| self.items.get(&GlyphId16::new(g)).copied().unwrap_or(0))
500                .collect();
501            ClassDef::Format1(ClassDefFormat1 {
502                start_glyph_id: self
503                    .items
504                    .keys()
505                    .next()
506                    .copied()
507                    .unwrap_or(GlyphId16::NOTDEF),
508                class_value_array,
509            })
510        } else {
511            ClassDef::Format2(ClassDefFormat2 {
512                class_range_records: iter_class_ranges(&self.items).collect(),
513            })
514        }
515    }
516}
517
518fn iter_class_ranges(
519    values: &BTreeMap<GlyphId16, u16>,
520) -> impl Iterator<Item = ClassRangeRecord> + '_ {
521    let mut iter = values.iter();
522    let mut prev = None;
523
524    #[allow(clippy::while_let_on_iterator)]
525    std::iter::from_fn(move || {
526        while let Some((gid, class)) = iter.next() {
527            match prev.take() {
528                None => prev = Some((*gid, *gid, *class)),
529                Some((start, end, pclass)) if are_sequential(end, *gid) && pclass == *class => {
530                    prev = Some((start, *gid, pclass))
531                }
532                Some((start_glyph_id, end_glyph_id, pclass)) => {
533                    prev = Some((*gid, *gid, *class));
534                    return Some(ClassRangeRecord {
535                        start_glyph_id,
536                        end_glyph_id,
537                        class: pclass,
538                    });
539                }
540            }
541        }
542        prev.take()
543            .map(|(start_glyph_id, end_glyph_id, class)| ClassRangeRecord {
544                start_glyph_id,
545                end_glyph_id,
546                class,
547            })
548    })
549}
550
551fn should_choose_coverage_format_2(glyphs: &[GlyphId16]) -> bool {
552    let format2_len = 4 + RangeRecord::iter_for_glyphs(glyphs).count() * 6;
553    let format1_len = 4 + glyphs.len() * 2;
554    format2_len < format1_len
555}
556
557impl RangeRecord {
558    /// An iterator over records for this array of glyphs.
559    ///
560    /// # Note
561    ///
562    /// this function expects that glyphs are already sorted.
563    pub fn iter_for_glyphs(glyphs: &[GlyphId16]) -> impl Iterator<Item = RangeRecord> + '_ {
564        let mut cur_range = glyphs.first().copied().map(|g| (g, g));
565        let mut len = 0u16;
566        let mut iter = glyphs.iter().skip(1).copied();
567
568        #[allow(clippy::while_let_on_iterator)]
569        std::iter::from_fn(move || {
570            while let Some(glyph) = iter.next() {
571                match cur_range {
572                    None => return None,
573                    Some((a, b)) if are_sequential(b, glyph) => cur_range = Some((a, glyph)),
574                    Some((a, b)) => {
575                        let result = RangeRecord {
576                            start_glyph_id: a,
577                            end_glyph_id: b,
578                            start_coverage_index: len,
579                        };
580                        cur_range = Some((glyph, glyph));
581                        len += 1 + b.to_u16().saturating_sub(a.to_u16());
582                        return Some(result);
583                    }
584                }
585            }
586            cur_range
587                .take()
588                .map(|(start_glyph_id, end_glyph_id)| RangeRecord {
589                    start_glyph_id,
590                    end_glyph_id,
591                    start_coverage_index: len,
592                })
593        })
594    }
595}
596
597fn iter_gids(gid1: GlyphId16, gid2: GlyphId16) -> impl Iterator<Item = GlyphId16> {
598    (gid1.to_u16()..=gid2.to_u16()).map(GlyphId16::new)
599}
600
601fn are_sequential(gid1: GlyphId16, gid2: GlyphId16) -> bool {
602    gid2.to_u16().saturating_sub(gid1.to_u16()) == 1
603}
604
605impl Device {
606    pub fn new(start_size: u16, end_size: u16, values: &[i8]) -> Self {
607        debug_assert_eq!(
608            (start_size..=end_size).count(),
609            values.len(),
610            "device range and values must match"
611        );
612        let delta_format: DeltaFormat = values
613            .iter()
614            .map(|val| match val {
615                -2..=1 => DeltaFormat::Local2BitDeltas,
616                -8..=7 => DeltaFormat::Local4BitDeltas,
617                _ => DeltaFormat::Local8BitDeltas,
618            })
619            .max()
620            .unwrap_or_default();
621        let delta_value = encode_delta(delta_format, values);
622
623        Device {
624            start_size,
625            end_size,
626            delta_format,
627            delta_value,
628        }
629    }
630}
631
632impl DeviceOrVariationIndex {
633    /// Create a new [`Device`] subtable
634    pub fn device(start_size: u16, end_size: u16, values: &[i8]) -> Self {
635        DeviceOrVariationIndex::Device(Device::new(start_size, end_size, values))
636    }
637}
638
639impl FontWrite for PendingVariationIndex {
640    fn write_into(&self, _writer: &mut TableWriter) {
641        panic!(
642            "Attempted to write PendingVariationIndex.\n\
643            VariationIndex tables should always be resolved before compilation.\n\
644            Please report this bug at <https://github.com/googlefonts/fontations/issues>"
645        )
646    }
647}
648
649fn encode_delta(format: DeltaFormat, values: &[i8]) -> Vec<u16> {
650    let (chunk_size, mask, bits) = match format {
651        DeltaFormat::Local2BitDeltas => (8, 0b11, 2),
652        DeltaFormat::Local4BitDeltas => (4, 0b1111, 4),
653        DeltaFormat::Local8BitDeltas => (2, 0b11111111, 8),
654        _ => panic!("invalid format"),
655    };
656    values
657        .chunks(chunk_size)
658        .map(|chunk| encode_chunk(chunk, mask, bits))
659        .collect()
660}
661
662fn encode_chunk(chunk: &[i8], mask: u8, bits: usize) -> u16 {
663    let mut out = 0u16;
664    for (i, val) in chunk.iter().enumerate() {
665        out |= ((val.to_be_bytes()[0] & mask) as u16) << ((16 - bits) - i * bits);
666    }
667    out
668}
669
670impl From<VariationIndex> for u32 {
671    fn from(value: VariationIndex) -> Self {
672        ((value.delta_set_outer_index as u32) << 16) | value.delta_set_inner_index as u32
673    }
674}
675
676#[cfg(test)]
677mod tests {
678    use std::ops::RangeInclusive;
679
680    use super::*;
681
682    #[test]
683    #[should_panic(expected = "array exceeds max length")]
684    fn array_len_smoke_test() {
685        let table = ScriptList {
686            script_records: vec![ScriptRecord {
687                script_tag: Tag::new(b"hihi"),
688                script: OffsetMarker::new(Script {
689                    default_lang_sys: NullableOffsetMarker::new(None),
690                    lang_sys_records: vec![LangSysRecord {
691                        lang_sys_tag: Tag::new(b"coco"),
692                        lang_sys: OffsetMarker::new(LangSys {
693                            required_feature_index: 0xffff,
694                            feature_indices: vec![69; (u16::MAX) as usize + 5],
695                        }),
696                    }],
697                }),
698            }],
699        };
700
701        table.validate().unwrap();
702    }
703
704    #[test]
705    #[should_panic(expected = "larger than end_glyph_id")]
706    fn validate_classdef_ranges() {
707        let classdef = ClassDefFormat2::new(vec![ClassRangeRecord::new(
708            GlyphId16::new(12),
709            GlyphId16::new(3),
710            7,
711        )]);
712
713        classdef.validate().unwrap();
714    }
715
716    #[test]
717    fn classdef_format() {
718        let builder: ClassDefBuilder = [(3u16, 4u16), (4, 6), (5, 1), (9, 5), (10, 2), (11, 3)]
719            .map(|(gid, cls)| (GlyphId16::new(gid), cls))
720            .into_iter()
721            .collect();
722
723        assert!(builder.prefer_format_1());
724
725        let builder: ClassDefBuilder = [(1u16, 1u16), (3, 4), (9, 5), (10, 2), (11, 3)]
726            .map(|(gid, cls)| (GlyphId16::new(gid), cls))
727            .into_iter()
728            .collect();
729
730        assert!(builder.prefer_format_1());
731    }
732
733    #[test]
734    fn classdef_prefer_format2() {
735        fn iter_class_items(
736            start: u16,
737            end: u16,
738            cls: u16,
739        ) -> impl Iterator<Item = (GlyphId16, u16)> {
740            (start..=end).map(move |gid| (GlyphId16::new(gid), cls))
741        }
742
743        // 3 ranges of 4 glyphs at 6 bytes a range should be smaller than writing
744        // out the 3 * 4 classes directly
745        let builder: ClassDefBuilder = iter_class_items(5, 8, 3)
746            .chain(iter_class_items(9, 12, 4))
747            .chain(iter_class_items(13, 16, 5))
748            .collect();
749
750        assert!(!builder.prefer_format_1());
751    }
752
753    #[test]
754    fn delta_format_dflt() {
755        let some: DeltaFormat = Default::default();
756        assert_eq!(some, DeltaFormat::Local2BitDeltas);
757    }
758
759    #[test]
760    fn delta_encode() {
761        let inp = [1i8, 2, 3, -1];
762        let result = encode_delta(DeltaFormat::Local4BitDeltas, &inp);
763        assert_eq!(result.len(), 1);
764        assert_eq!(result[0], 0x123f_u16);
765
766        let inp = [1i8, 1, 1, 1, 1];
767        let result = encode_delta(DeltaFormat::Local2BitDeltas, &inp);
768        assert_eq!(result.len(), 1);
769        assert_eq!(result[0], 0x5540_u16);
770    }
771
772    fn make_glyph_vec<const N: usize>(gids: [u16; N]) -> Vec<GlyphId16> {
773        gids.into_iter().map(GlyphId16::new).collect()
774    }
775
776    #[test]
777    fn coverage_builder() {
778        let coverage = make_glyph_vec([1u16, 2, 9, 3, 6, 9])
779            .into_iter()
780            .collect::<CoverageTableBuilder>();
781        assert_eq!(coverage.glyphs, make_glyph_vec([1, 2, 3, 6, 9]));
782    }
783
784    fn make_class<const N: usize>(gid_class_pairs: [(u16, u16); N]) -> ClassDef {
785        gid_class_pairs
786            .iter()
787            .map(|(gid, cls)| (GlyphId16::new(*gid), *cls))
788            .collect::<ClassDefBuilder>()
789            .build()
790    }
791
792    #[test]
793    fn class_def_builder_zero() {
794        // even if class 0 is provided, we don't need to assign explicit entries for it
795        let class = make_class([(4, 0), (5, 1)]);
796        assert!(class.get_raw(GlyphId16::new(4)).is_none());
797        assert_eq!(class.get_raw(GlyphId16::new(5)), Some(1));
798        assert!(class.get_raw(GlyphId16::new(100)).is_none());
799    }
800
801    // https://github.com/googlefonts/fontations/issues/923
802    // an empty classdef should always be format 2
803    #[test]
804    fn class_def_builder_empty() {
805        let builder = ClassDefBuilder::from_iter([]);
806        let built = builder.build();
807
808        assert_eq!(
809            built,
810            ClassDef::Format2(ClassDefFormat2 {
811                class_range_records: vec![]
812            })
813        )
814    }
815
816    #[test]
817    fn class_def_small() {
818        let class = make_class([(1, 1), (2, 1), (3, 1)]);
819
820        assert_eq!(
821            class,
822            ClassDef::Format2(ClassDefFormat2 {
823                class_range_records: vec![ClassRangeRecord {
824                    start_glyph_id: GlyphId16::new(1),
825                    end_glyph_id: GlyphId16::new(3),
826                    class: 1
827                }]
828            })
829        )
830    }
831
832    #[test]
833    fn classdef_f2_get() {
834        fn make_f2_class<const N: usize>(range: [RangeInclusive<u16>; N]) -> ClassDef {
835            ClassDefFormat2::new(
836                range
837                    .into_iter()
838                    .enumerate()
839                    .map(|(i, range)| {
840                        ClassRangeRecord::new(
841                            GlyphId16::new(*range.start()),
842                            GlyphId16::new(*range.end()),
843                            (1 + i) as _,
844                        )
845                    })
846                    .collect(),
847            )
848            .into()
849        }
850
851        let cls = make_f2_class([1..=1, 2..=9]);
852        assert_eq!(cls.get(GlyphId16::new(2)), 2);
853        assert_eq!(cls.get(GlyphId16::new(20)), 0);
854    }
855}