Skip to main content

read_fonts/generated/
generated_gsub.rs

1// THIS FILE IS AUTOGENERATED.
2// Any changes to this file will be overwritten.
3// For more information about how codegen works, see font-codegen/README.md
4
5#[allow(unused_imports)]
6use crate::codegen_prelude::*;
7
8impl<'a> MinByteRange<'a> for Gsub<'a> {
9    fn min_byte_range(&self) -> Range<usize> {
10        0..self.lookup_list_offset_byte_range().end
11    }
12    fn min_table_bytes(&self) -> &'a [u8] {
13        let range = self.min_byte_range();
14        self.data.as_bytes().get(range).unwrap_or_default()
15    }
16}
17
18impl TopLevelTable for Gsub<'_> {
19    /// `GSUB`
20    const TAG: Tag = Tag::new(b"GSUB");
21}
22
23impl<'a> FontRead<'a> for Gsub<'a> {
24    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
25        #[allow(clippy::absurd_extreme_comparisons)]
26        if data.len() < Self::MIN_SIZE {
27            return Err(ReadError::OutOfBounds);
28        }
29        Ok(Self { data })
30    }
31}
32
33/// [GSUB](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#gsub-header)
34#[derive(Clone)]
35pub struct Gsub<'a> {
36    data: FontData<'a>,
37}
38
39#[allow(clippy::needless_lifetimes)]
40impl<'a> Gsub<'a> {
41    pub const MIN_SIZE: usize = (MajorMinor::RAW_BYTE_LEN
42        + Offset16::RAW_BYTE_LEN
43        + Offset16::RAW_BYTE_LEN
44        + Offset16::RAW_BYTE_LEN);
45    basic_table_impls!(impl_the_methods);
46
47    /// The major and minor version of the GSUB table, as a tuple (u16, u16)
48    pub fn version(&self) -> MajorMinor {
49        let range = self.version_byte_range();
50        self.data.read_at(range.start).ok().unwrap()
51    }
52
53    /// Offset to ScriptList table, from beginning of GSUB table
54    pub fn script_list_offset(&self) -> Offset16 {
55        let range = self.script_list_offset_byte_range();
56        self.data.read_at(range.start).ok().unwrap()
57    }
58
59    /// Attempt to resolve [`script_list_offset`][Self::script_list_offset].
60    pub fn script_list(&self) -> Result<ScriptList<'a>, ReadError> {
61        let data = self.data;
62        self.script_list_offset().resolve(data)
63    }
64
65    /// Offset to FeatureList table, from beginning of GSUB table
66    pub fn feature_list_offset(&self) -> Offset16 {
67        let range = self.feature_list_offset_byte_range();
68        self.data.read_at(range.start).ok().unwrap()
69    }
70
71    /// Attempt to resolve [`feature_list_offset`][Self::feature_list_offset].
72    pub fn feature_list(&self) -> Result<FeatureList<'a>, ReadError> {
73        let data = self.data;
74        self.feature_list_offset().resolve(data)
75    }
76
77    /// Offset to LookupList table, from beginning of GSUB table
78    pub fn lookup_list_offset(&self) -> Offset16 {
79        let range = self.lookup_list_offset_byte_range();
80        self.data.read_at(range.start).ok().unwrap()
81    }
82
83    /// Attempt to resolve [`lookup_list_offset`][Self::lookup_list_offset].
84    pub fn lookup_list(&self) -> Result<SubstitutionLookupList<'a>, ReadError> {
85        let data = self.data;
86        self.lookup_list_offset().resolve(data)
87    }
88
89    /// Offset to FeatureVariations table, from beginning of the GSUB
90    /// table (may be NULL)
91    pub fn feature_variations_offset(&self) -> Option<Nullable<Offset32>> {
92        let range = self.feature_variations_offset_byte_range();
93        (!range.is_empty())
94            .then(|| self.data.read_at(range.start).ok())
95            .flatten()
96    }
97
98    /// Attempt to resolve [`feature_variations_offset`][Self::feature_variations_offset].
99    pub fn feature_variations(&self) -> Option<Result<FeatureVariations<'a>, ReadError>> {
100        let data = self.data;
101        self.feature_variations_offset().map(|x| x.resolve(data))?
102    }
103
104    pub fn version_byte_range(&self) -> Range<usize> {
105        let start = 0;
106        start..start + MajorMinor::RAW_BYTE_LEN
107    }
108
109    pub fn script_list_offset_byte_range(&self) -> Range<usize> {
110        let start = self.version_byte_range().end;
111        start..start + Offset16::RAW_BYTE_LEN
112    }
113
114    pub fn feature_list_offset_byte_range(&self) -> Range<usize> {
115        let start = self.script_list_offset_byte_range().end;
116        start..start + Offset16::RAW_BYTE_LEN
117    }
118
119    pub fn lookup_list_offset_byte_range(&self) -> Range<usize> {
120        let start = self.feature_list_offset_byte_range().end;
121        start..start + Offset16::RAW_BYTE_LEN
122    }
123
124    pub fn feature_variations_offset_byte_range(&self) -> Range<usize> {
125        let start = self.lookup_list_offset_byte_range().end;
126        start
127            ..(self.version().compatible((1u16, 1u16)))
128                .then(|| start + Offset32::RAW_BYTE_LEN)
129                .unwrap_or(start)
130    }
131}
132
133#[cfg(feature = "experimental_traverse")]
134impl<'a> SomeTable<'a> for Gsub<'a> {
135    fn type_name(&self) -> &str {
136        "Gsub"
137    }
138    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
139        match idx {
140            0usize => Some(Field::new("version", self.version())),
141            1usize => Some(Field::new(
142                "script_list_offset",
143                FieldType::offset(self.script_list_offset(), self.script_list()),
144            )),
145            2usize => Some(Field::new(
146                "feature_list_offset",
147                FieldType::offset(self.feature_list_offset(), self.feature_list()),
148            )),
149            3usize => Some(Field::new(
150                "lookup_list_offset",
151                FieldType::offset(self.lookup_list_offset(), self.lookup_list()),
152            )),
153            4usize if self.version().compatible((1u16, 1u16)) => Some(Field::new(
154                "feature_variations_offset",
155                FieldType::offset(
156                    self.feature_variations_offset().unwrap(),
157                    self.feature_variations(),
158                ),
159            )),
160            _ => None,
161        }
162    }
163}
164
165#[cfg(feature = "experimental_traverse")]
166#[allow(clippy::needless_lifetimes)]
167impl<'a> std::fmt::Debug for Gsub<'a> {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        (self as &dyn SomeTable<'a>).fmt(f)
170    }
171}
172
173/// A [GSUB Lookup](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#gsubLookupTypeEnum) subtable.
174pub enum SubstitutionLookup<'a> {
175    Single(Lookup<'a, SingleSubst<'a>>),
176    Multiple(Lookup<'a, MultipleSubstFormat1<'a>>),
177    Alternate(Lookup<'a, AlternateSubstFormat1<'a>>),
178    Ligature(Lookup<'a, LigatureSubstFormat1<'a>>),
179    Contextual(Lookup<'a, SubstitutionSequenceContext<'a>>),
180    ChainContextual(Lookup<'a, SubstitutionChainContext<'a>>),
181    Extension(Lookup<'a, ExtensionSubtable<'a>>),
182    Reverse(Lookup<'a, ReverseChainSingleSubstFormat1<'a>>),
183}
184
185impl<'a> FontRead<'a> for SubstitutionLookup<'a> {
186    fn read(bytes: FontData<'a>) -> Result<Self, ReadError> {
187        let untyped = Lookup::read(bytes)?;
188        match untyped.lookup_type() {
189            1 => Ok(SubstitutionLookup::Single(untyped.into_concrete())),
190            2 => Ok(SubstitutionLookup::Multiple(untyped.into_concrete())),
191            3 => Ok(SubstitutionLookup::Alternate(untyped.into_concrete())),
192            4 => Ok(SubstitutionLookup::Ligature(untyped.into_concrete())),
193            5 => Ok(SubstitutionLookup::Contextual(untyped.into_concrete())),
194            6 => Ok(SubstitutionLookup::ChainContextual(untyped.into_concrete())),
195            7 => Ok(SubstitutionLookup::Extension(untyped.into_concrete())),
196            8 => Ok(SubstitutionLookup::Reverse(untyped.into_concrete())),
197            other => Err(ReadError::InvalidFormat(other.into())),
198        }
199    }
200}
201
202impl<'a> SubstitutionLookup<'a> {
203    #[allow(dead_code)]
204    /// Return the inner table, removing the specific generics.
205    ///
206    /// This lets us return a single concrete type we can call methods on.
207    pub(crate) fn of_unit_type(&self) -> Lookup<'a, ()> {
208        match self {
209            SubstitutionLookup::Single(inner) => inner.of_unit_type(),
210            SubstitutionLookup::Multiple(inner) => inner.of_unit_type(),
211            SubstitutionLookup::Alternate(inner) => inner.of_unit_type(),
212            SubstitutionLookup::Ligature(inner) => inner.of_unit_type(),
213            SubstitutionLookup::Contextual(inner) => inner.of_unit_type(),
214            SubstitutionLookup::ChainContextual(inner) => inner.of_unit_type(),
215            SubstitutionLookup::Extension(inner) => inner.of_unit_type(),
216            SubstitutionLookup::Reverse(inner) => inner.of_unit_type(),
217        }
218    }
219}
220
221#[cfg(feature = "experimental_traverse")]
222impl<'a> SubstitutionLookup<'a> {
223    fn dyn_inner(&self) -> &(dyn SomeTable<'a> + 'a) {
224        match self {
225            SubstitutionLookup::Single(table) => table,
226            SubstitutionLookup::Multiple(table) => table,
227            SubstitutionLookup::Alternate(table) => table,
228            SubstitutionLookup::Ligature(table) => table,
229            SubstitutionLookup::Contextual(table) => table,
230            SubstitutionLookup::ChainContextual(table) => table,
231            SubstitutionLookup::Extension(table) => table,
232            SubstitutionLookup::Reverse(table) => table,
233        }
234    }
235}
236
237#[cfg(feature = "experimental_traverse")]
238impl<'a> SomeTable<'a> for SubstitutionLookup<'a> {
239    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
240        self.dyn_inner().get_field(idx)
241    }
242    fn type_name(&self) -> &str {
243        self.dyn_inner().type_name()
244    }
245}
246
247#[cfg(feature = "experimental_traverse")]
248impl std::fmt::Debug for SubstitutionLookup<'_> {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        self.dyn_inner().fmt(f)
251    }
252}
253
254/// LookupType 1: [Single Substitution](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#lookuptype-1-single-substitution-subtable) Subtable
255#[derive(Clone)]
256pub enum SingleSubst<'a> {
257    Format1(SingleSubstFormat1<'a>),
258    Format2(SingleSubstFormat2<'a>),
259}
260
261impl<'a> SingleSubst<'a> {
262    ///Return the `FontData` used to resolve offsets for this table.
263    pub fn offset_data(&self) -> FontData<'a> {
264        match self {
265            Self::Format1(item) => item.offset_data(),
266            Self::Format2(item) => item.offset_data(),
267        }
268    }
269
270    /// Format identifier: format = 1
271    pub fn subst_format(&self) -> u16 {
272        match self {
273            Self::Format1(item) => item.subst_format(),
274            Self::Format2(item) => item.subst_format(),
275        }
276    }
277
278    /// Offset to Coverage table, from beginning of substitution
279    /// subtable
280    pub fn coverage_offset(&self) -> Offset16 {
281        match self {
282            Self::Format1(item) => item.coverage_offset(),
283            Self::Format2(item) => item.coverage_offset(),
284        }
285    }
286}
287
288impl<'a> FontRead<'a> for SingleSubst<'a> {
289    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
290        let format: u16 = data.read_at(0usize)?;
291        match format {
292            SingleSubstFormat1::FORMAT => Ok(Self::Format1(FontRead::read(data)?)),
293            SingleSubstFormat2::FORMAT => Ok(Self::Format2(FontRead::read(data)?)),
294            other => Err(ReadError::InvalidFormat(other.into())),
295        }
296    }
297}
298
299impl<'a> MinByteRange<'a> for SingleSubst<'a> {
300    fn min_byte_range(&self) -> Range<usize> {
301        match self {
302            Self::Format1(item) => item.min_byte_range(),
303            Self::Format2(item) => item.min_byte_range(),
304        }
305    }
306    fn min_table_bytes(&self) -> &'a [u8] {
307        match self {
308            Self::Format1(item) => item.min_table_bytes(),
309            Self::Format2(item) => item.min_table_bytes(),
310        }
311    }
312}
313
314#[cfg(feature = "experimental_traverse")]
315impl<'a> SingleSubst<'a> {
316    fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> {
317        match self {
318            Self::Format1(table) => table,
319            Self::Format2(table) => table,
320        }
321    }
322}
323
324#[cfg(feature = "experimental_traverse")]
325impl std::fmt::Debug for SingleSubst<'_> {
326    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327        self.dyn_inner().fmt(f)
328    }
329}
330
331#[cfg(feature = "experimental_traverse")]
332impl<'a> SomeTable<'a> for SingleSubst<'a> {
333    fn type_name(&self) -> &str {
334        self.dyn_inner().type_name()
335    }
336    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
337        self.dyn_inner().get_field(idx)
338    }
339}
340
341impl Format<u16> for SingleSubstFormat1<'_> {
342    const FORMAT: u16 = 1;
343}
344
345impl<'a> MinByteRange<'a> for SingleSubstFormat1<'a> {
346    fn min_byte_range(&self) -> Range<usize> {
347        0..self.delta_glyph_id_byte_range().end
348    }
349    fn min_table_bytes(&self) -> &'a [u8] {
350        let range = self.min_byte_range();
351        self.data.as_bytes().get(range).unwrap_or_default()
352    }
353}
354
355impl<'a> FontRead<'a> for SingleSubstFormat1<'a> {
356    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
357        #[allow(clippy::absurd_extreme_comparisons)]
358        if data.len() < Self::MIN_SIZE {
359            return Err(ReadError::OutOfBounds);
360        }
361        Ok(Self { data })
362    }
363}
364
365/// [Single Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#11-single-substitution-format-1)
366#[derive(Clone)]
367pub struct SingleSubstFormat1<'a> {
368    data: FontData<'a>,
369}
370
371#[allow(clippy::needless_lifetimes)]
372impl<'a> SingleSubstFormat1<'a> {
373    pub const MIN_SIZE: usize = (u16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN + i16::RAW_BYTE_LEN);
374    basic_table_impls!(impl_the_methods);
375
376    /// Format identifier: format = 1
377    pub fn subst_format(&self) -> u16 {
378        let range = self.subst_format_byte_range();
379        self.data.read_at(range.start).ok().unwrap()
380    }
381
382    /// Offset to Coverage table, from beginning of substitution
383    /// subtable
384    pub fn coverage_offset(&self) -> Offset16 {
385        let range = self.coverage_offset_byte_range();
386        self.data.read_at(range.start).ok().unwrap()
387    }
388
389    /// Attempt to resolve [`coverage_offset`][Self::coverage_offset].
390    pub fn coverage(&self) -> Result<CoverageTable<'a>, ReadError> {
391        let data = self.data;
392        self.coverage_offset().resolve(data)
393    }
394
395    /// Add to original glyph ID to get substitute glyph ID
396    pub fn delta_glyph_id(&self) -> i16 {
397        let range = self.delta_glyph_id_byte_range();
398        self.data.read_at(range.start).ok().unwrap()
399    }
400
401    pub fn subst_format_byte_range(&self) -> Range<usize> {
402        let start = 0;
403        start..start + u16::RAW_BYTE_LEN
404    }
405
406    pub fn coverage_offset_byte_range(&self) -> Range<usize> {
407        let start = self.subst_format_byte_range().end;
408        start..start + Offset16::RAW_BYTE_LEN
409    }
410
411    pub fn delta_glyph_id_byte_range(&self) -> Range<usize> {
412        let start = self.coverage_offset_byte_range().end;
413        start..start + i16::RAW_BYTE_LEN
414    }
415}
416
417#[cfg(feature = "experimental_traverse")]
418impl<'a> SomeTable<'a> for SingleSubstFormat1<'a> {
419    fn type_name(&self) -> &str {
420        "SingleSubstFormat1"
421    }
422    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
423        match idx {
424            0usize => Some(Field::new("subst_format", self.subst_format())),
425            1usize => Some(Field::new(
426                "coverage_offset",
427                FieldType::offset(self.coverage_offset(), self.coverage()),
428            )),
429            2usize => Some(Field::new("delta_glyph_id", self.delta_glyph_id())),
430            _ => None,
431        }
432    }
433}
434
435#[cfg(feature = "experimental_traverse")]
436#[allow(clippy::needless_lifetimes)]
437impl<'a> std::fmt::Debug for SingleSubstFormat1<'a> {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        (self as &dyn SomeTable<'a>).fmt(f)
440    }
441}
442
443impl Format<u16> for SingleSubstFormat2<'_> {
444    const FORMAT: u16 = 2;
445}
446
447impl<'a> MinByteRange<'a> for SingleSubstFormat2<'a> {
448    fn min_byte_range(&self) -> Range<usize> {
449        0..self.substitute_glyph_ids_byte_range().end
450    }
451    fn min_table_bytes(&self) -> &'a [u8] {
452        let range = self.min_byte_range();
453        self.data.as_bytes().get(range).unwrap_or_default()
454    }
455}
456
457impl<'a> FontRead<'a> for SingleSubstFormat2<'a> {
458    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
459        #[allow(clippy::absurd_extreme_comparisons)]
460        if data.len() < Self::MIN_SIZE {
461            return Err(ReadError::OutOfBounds);
462        }
463        Ok(Self { data })
464    }
465}
466
467/// [Single Substitution Format 2](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#12-single-substitution-format-2)
468#[derive(Clone)]
469pub struct SingleSubstFormat2<'a> {
470    data: FontData<'a>,
471}
472
473#[allow(clippy::needless_lifetimes)]
474impl<'a> SingleSubstFormat2<'a> {
475    pub const MIN_SIZE: usize = (u16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN);
476    basic_table_impls!(impl_the_methods);
477
478    /// Format identifier: format = 2
479    pub fn subst_format(&self) -> u16 {
480        let range = self.subst_format_byte_range();
481        self.data.read_at(range.start).ok().unwrap()
482    }
483
484    /// Offset to Coverage table, from beginning of substitution
485    /// subtable
486    pub fn coverage_offset(&self) -> Offset16 {
487        let range = self.coverage_offset_byte_range();
488        self.data.read_at(range.start).ok().unwrap()
489    }
490
491    /// Attempt to resolve [`coverage_offset`][Self::coverage_offset].
492    pub fn coverage(&self) -> Result<CoverageTable<'a>, ReadError> {
493        let data = self.data;
494        self.coverage_offset().resolve(data)
495    }
496
497    /// Number of glyph IDs in the substituteGlyphIDs array
498    pub fn glyph_count(&self) -> u16 {
499        let range = self.glyph_count_byte_range();
500        self.data.read_at(range.start).ok().unwrap()
501    }
502
503    /// Array of substitute glyph IDs — ordered by Coverage index
504    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
505        let range = self.substitute_glyph_ids_byte_range();
506        self.data.read_array(range).ok().unwrap_or_default()
507    }
508
509    pub fn subst_format_byte_range(&self) -> Range<usize> {
510        let start = 0;
511        start..start + u16::RAW_BYTE_LEN
512    }
513
514    pub fn coverage_offset_byte_range(&self) -> Range<usize> {
515        let start = self.subst_format_byte_range().end;
516        start..start + Offset16::RAW_BYTE_LEN
517    }
518
519    pub fn glyph_count_byte_range(&self) -> Range<usize> {
520        let start = self.coverage_offset_byte_range().end;
521        start..start + u16::RAW_BYTE_LEN
522    }
523
524    pub fn substitute_glyph_ids_byte_range(&self) -> Range<usize> {
525        let glyph_count = self.glyph_count();
526        let start = self.glyph_count_byte_range().end;
527        start..start + (glyph_count as usize).saturating_mul(GlyphId16::RAW_BYTE_LEN)
528    }
529}
530
531#[cfg(feature = "experimental_traverse")]
532impl<'a> SomeTable<'a> for SingleSubstFormat2<'a> {
533    fn type_name(&self) -> &str {
534        "SingleSubstFormat2"
535    }
536    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
537        match idx {
538            0usize => Some(Field::new("subst_format", self.subst_format())),
539            1usize => Some(Field::new(
540                "coverage_offset",
541                FieldType::offset(self.coverage_offset(), self.coverage()),
542            )),
543            2usize => Some(Field::new("glyph_count", self.glyph_count())),
544            3usize => Some(Field::new(
545                "substitute_glyph_ids",
546                self.substitute_glyph_ids(),
547            )),
548            _ => None,
549        }
550    }
551}
552
553#[cfg(feature = "experimental_traverse")]
554#[allow(clippy::needless_lifetimes)]
555impl<'a> std::fmt::Debug for SingleSubstFormat2<'a> {
556    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557        (self as &dyn SomeTable<'a>).fmt(f)
558    }
559}
560
561impl Format<u16> for MultipleSubstFormat1<'_> {
562    const FORMAT: u16 = 1;
563}
564
565impl<'a> MinByteRange<'a> for MultipleSubstFormat1<'a> {
566    fn min_byte_range(&self) -> Range<usize> {
567        0..self.sequence_offsets_byte_range().end
568    }
569    fn min_table_bytes(&self) -> &'a [u8] {
570        let range = self.min_byte_range();
571        self.data.as_bytes().get(range).unwrap_or_default()
572    }
573}
574
575impl<'a> FontRead<'a> for MultipleSubstFormat1<'a> {
576    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
577        #[allow(clippy::absurd_extreme_comparisons)]
578        if data.len() < Self::MIN_SIZE {
579            return Err(ReadError::OutOfBounds);
580        }
581        Ok(Self { data })
582    }
583}
584
585/// [Multiple Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#21-multiple-substitution-format-1)
586#[derive(Clone)]
587pub struct MultipleSubstFormat1<'a> {
588    data: FontData<'a>,
589}
590
591#[allow(clippy::needless_lifetimes)]
592impl<'a> MultipleSubstFormat1<'a> {
593    pub const MIN_SIZE: usize = (u16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN);
594    basic_table_impls!(impl_the_methods);
595
596    /// Format identifier: format = 1
597    pub fn subst_format(&self) -> u16 {
598        let range = self.subst_format_byte_range();
599        self.data.read_at(range.start).ok().unwrap()
600    }
601
602    /// Offset to Coverage table, from beginning of substitution
603    /// subtable
604    pub fn coverage_offset(&self) -> Offset16 {
605        let range = self.coverage_offset_byte_range();
606        self.data.read_at(range.start).ok().unwrap()
607    }
608
609    /// Attempt to resolve [`coverage_offset`][Self::coverage_offset].
610    pub fn coverage(&self) -> Result<CoverageTable<'a>, ReadError> {
611        let data = self.data;
612        self.coverage_offset().resolve(data)
613    }
614
615    /// Number of Sequence table offsets in the sequenceOffsets array
616    pub fn sequence_count(&self) -> u16 {
617        let range = self.sequence_count_byte_range();
618        self.data.read_at(range.start).ok().unwrap()
619    }
620
621    /// Array of offsets to Sequence tables. Offsets are from beginning
622    /// of substitution subtable, ordered by Coverage index
623    pub fn sequence_offsets(&self) -> &'a [BigEndian<Offset16>] {
624        let range = self.sequence_offsets_byte_range();
625        self.data.read_array(range).ok().unwrap_or_default()
626    }
627
628    /// A dynamically resolving wrapper for [`sequence_offsets`][Self::sequence_offsets].
629    pub fn sequences(&self) -> ArrayOfOffsets<'a, Sequence<'a>, Offset16> {
630        let data = self.data;
631        let offsets = self.sequence_offsets();
632        ArrayOfOffsets::new(offsets, data, ())
633    }
634
635    pub fn subst_format_byte_range(&self) -> Range<usize> {
636        let start = 0;
637        start..start + u16::RAW_BYTE_LEN
638    }
639
640    pub fn coverage_offset_byte_range(&self) -> Range<usize> {
641        let start = self.subst_format_byte_range().end;
642        start..start + Offset16::RAW_BYTE_LEN
643    }
644
645    pub fn sequence_count_byte_range(&self) -> Range<usize> {
646        let start = self.coverage_offset_byte_range().end;
647        start..start + u16::RAW_BYTE_LEN
648    }
649
650    pub fn sequence_offsets_byte_range(&self) -> Range<usize> {
651        let sequence_count = self.sequence_count();
652        let start = self.sequence_count_byte_range().end;
653        start..start + (sequence_count as usize).saturating_mul(Offset16::RAW_BYTE_LEN)
654    }
655}
656
657#[cfg(feature = "experimental_traverse")]
658impl<'a> SomeTable<'a> for MultipleSubstFormat1<'a> {
659    fn type_name(&self) -> &str {
660        "MultipleSubstFormat1"
661    }
662    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
663        match idx {
664            0usize => Some(Field::new("subst_format", self.subst_format())),
665            1usize => Some(Field::new(
666                "coverage_offset",
667                FieldType::offset(self.coverage_offset(), self.coverage()),
668            )),
669            2usize => Some(Field::new("sequence_count", self.sequence_count())),
670            3usize => Some({
671                let data = self.data;
672                Field::new(
673                    "sequence_offsets",
674                    FieldType::array_of_offsets(
675                        better_type_name::<Sequence>(),
676                        self.sequence_offsets(),
677                        move |off| {
678                            let target = off.get().resolve::<Sequence>(data);
679                            FieldType::offset(off.get(), target)
680                        },
681                    ),
682                )
683            }),
684            _ => None,
685        }
686    }
687}
688
689#[cfg(feature = "experimental_traverse")]
690#[allow(clippy::needless_lifetimes)]
691impl<'a> std::fmt::Debug for MultipleSubstFormat1<'a> {
692    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
693        (self as &dyn SomeTable<'a>).fmt(f)
694    }
695}
696
697impl<'a> MinByteRange<'a> for Sequence<'a> {
698    fn min_byte_range(&self) -> Range<usize> {
699        0..self.substitute_glyph_ids_byte_range().end
700    }
701    fn min_table_bytes(&self) -> &'a [u8] {
702        let range = self.min_byte_range();
703        self.data.as_bytes().get(range).unwrap_or_default()
704    }
705}
706
707impl<'a> FontRead<'a> for Sequence<'a> {
708    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
709        #[allow(clippy::absurd_extreme_comparisons)]
710        if data.len() < Self::MIN_SIZE {
711            return Err(ReadError::OutOfBounds);
712        }
713        Ok(Self { data })
714    }
715}
716
717/// Part of [MultipleSubstFormat1]
718#[derive(Clone)]
719pub struct Sequence<'a> {
720    data: FontData<'a>,
721}
722
723#[allow(clippy::needless_lifetimes)]
724impl<'a> Sequence<'a> {
725    pub const MIN_SIZE: usize = u16::RAW_BYTE_LEN;
726    basic_table_impls!(impl_the_methods);
727
728    /// Number of glyph IDs in the substituteGlyphIDs array. This must
729    /// always be greater than 0.
730    pub fn glyph_count(&self) -> u16 {
731        let range = self.glyph_count_byte_range();
732        self.data.read_at(range.start).ok().unwrap()
733    }
734
735    /// String of glyph IDs to substitute
736    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
737        let range = self.substitute_glyph_ids_byte_range();
738        self.data.read_array(range).ok().unwrap_or_default()
739    }
740
741    pub fn glyph_count_byte_range(&self) -> Range<usize> {
742        let start = 0;
743        start..start + u16::RAW_BYTE_LEN
744    }
745
746    pub fn substitute_glyph_ids_byte_range(&self) -> Range<usize> {
747        let glyph_count = self.glyph_count();
748        let start = self.glyph_count_byte_range().end;
749        start..start + (glyph_count as usize).saturating_mul(GlyphId16::RAW_BYTE_LEN)
750    }
751}
752
753#[cfg(feature = "experimental_traverse")]
754impl<'a> SomeTable<'a> for Sequence<'a> {
755    fn type_name(&self) -> &str {
756        "Sequence"
757    }
758    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
759        match idx {
760            0usize => Some(Field::new("glyph_count", self.glyph_count())),
761            1usize => Some(Field::new(
762                "substitute_glyph_ids",
763                self.substitute_glyph_ids(),
764            )),
765            _ => None,
766        }
767    }
768}
769
770#[cfg(feature = "experimental_traverse")]
771#[allow(clippy::needless_lifetimes)]
772impl<'a> std::fmt::Debug for Sequence<'a> {
773    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
774        (self as &dyn SomeTable<'a>).fmt(f)
775    }
776}
777
778impl Format<u16> for AlternateSubstFormat1<'_> {
779    const FORMAT: u16 = 1;
780}
781
782impl<'a> MinByteRange<'a> for AlternateSubstFormat1<'a> {
783    fn min_byte_range(&self) -> Range<usize> {
784        0..self.alternate_set_offsets_byte_range().end
785    }
786    fn min_table_bytes(&self) -> &'a [u8] {
787        let range = self.min_byte_range();
788        self.data.as_bytes().get(range).unwrap_or_default()
789    }
790}
791
792impl<'a> FontRead<'a> for AlternateSubstFormat1<'a> {
793    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
794        #[allow(clippy::absurd_extreme_comparisons)]
795        if data.len() < Self::MIN_SIZE {
796            return Err(ReadError::OutOfBounds);
797        }
798        Ok(Self { data })
799    }
800}
801
802/// [Alternate Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#31-alternate-substitution-format-1)
803#[derive(Clone)]
804pub struct AlternateSubstFormat1<'a> {
805    data: FontData<'a>,
806}
807
808#[allow(clippy::needless_lifetimes)]
809impl<'a> AlternateSubstFormat1<'a> {
810    pub const MIN_SIZE: usize = (u16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN);
811    basic_table_impls!(impl_the_methods);
812
813    /// Format identifier: format = 1
814    pub fn subst_format(&self) -> u16 {
815        let range = self.subst_format_byte_range();
816        self.data.read_at(range.start).ok().unwrap()
817    }
818
819    /// Offset to Coverage table, from beginning of substitution
820    /// subtable
821    pub fn coverage_offset(&self) -> Offset16 {
822        let range = self.coverage_offset_byte_range();
823        self.data.read_at(range.start).ok().unwrap()
824    }
825
826    /// Attempt to resolve [`coverage_offset`][Self::coverage_offset].
827    pub fn coverage(&self) -> Result<CoverageTable<'a>, ReadError> {
828        let data = self.data;
829        self.coverage_offset().resolve(data)
830    }
831
832    /// Number of AlternateSet tables
833    pub fn alternate_set_count(&self) -> u16 {
834        let range = self.alternate_set_count_byte_range();
835        self.data.read_at(range.start).ok().unwrap()
836    }
837
838    /// Array of offsets to AlternateSet tables. Offsets are from
839    /// beginning of substitution subtable, ordered by Coverage index
840    pub fn alternate_set_offsets(&self) -> &'a [BigEndian<Offset16>] {
841        let range = self.alternate_set_offsets_byte_range();
842        self.data.read_array(range).ok().unwrap_or_default()
843    }
844
845    /// A dynamically resolving wrapper for [`alternate_set_offsets`][Self::alternate_set_offsets].
846    pub fn alternate_sets(&self) -> ArrayOfOffsets<'a, AlternateSet<'a>, Offset16> {
847        let data = self.data;
848        let offsets = self.alternate_set_offsets();
849        ArrayOfOffsets::new(offsets, data, ())
850    }
851
852    pub fn subst_format_byte_range(&self) -> Range<usize> {
853        let start = 0;
854        start..start + u16::RAW_BYTE_LEN
855    }
856
857    pub fn coverage_offset_byte_range(&self) -> Range<usize> {
858        let start = self.subst_format_byte_range().end;
859        start..start + Offset16::RAW_BYTE_LEN
860    }
861
862    pub fn alternate_set_count_byte_range(&self) -> Range<usize> {
863        let start = self.coverage_offset_byte_range().end;
864        start..start + u16::RAW_BYTE_LEN
865    }
866
867    pub fn alternate_set_offsets_byte_range(&self) -> Range<usize> {
868        let alternate_set_count = self.alternate_set_count();
869        let start = self.alternate_set_count_byte_range().end;
870        start..start + (alternate_set_count as usize).saturating_mul(Offset16::RAW_BYTE_LEN)
871    }
872}
873
874#[cfg(feature = "experimental_traverse")]
875impl<'a> SomeTable<'a> for AlternateSubstFormat1<'a> {
876    fn type_name(&self) -> &str {
877        "AlternateSubstFormat1"
878    }
879    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
880        match idx {
881            0usize => Some(Field::new("subst_format", self.subst_format())),
882            1usize => Some(Field::new(
883                "coverage_offset",
884                FieldType::offset(self.coverage_offset(), self.coverage()),
885            )),
886            2usize => Some(Field::new(
887                "alternate_set_count",
888                self.alternate_set_count(),
889            )),
890            3usize => Some({
891                let data = self.data;
892                Field::new(
893                    "alternate_set_offsets",
894                    FieldType::array_of_offsets(
895                        better_type_name::<AlternateSet>(),
896                        self.alternate_set_offsets(),
897                        move |off| {
898                            let target = off.get().resolve::<AlternateSet>(data);
899                            FieldType::offset(off.get(), target)
900                        },
901                    ),
902                )
903            }),
904            _ => None,
905        }
906    }
907}
908
909#[cfg(feature = "experimental_traverse")]
910#[allow(clippy::needless_lifetimes)]
911impl<'a> std::fmt::Debug for AlternateSubstFormat1<'a> {
912    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
913        (self as &dyn SomeTable<'a>).fmt(f)
914    }
915}
916
917impl<'a> MinByteRange<'a> for AlternateSet<'a> {
918    fn min_byte_range(&self) -> Range<usize> {
919        0..self.alternate_glyph_ids_byte_range().end
920    }
921    fn min_table_bytes(&self) -> &'a [u8] {
922        let range = self.min_byte_range();
923        self.data.as_bytes().get(range).unwrap_or_default()
924    }
925}
926
927impl<'a> FontRead<'a> for AlternateSet<'a> {
928    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
929        #[allow(clippy::absurd_extreme_comparisons)]
930        if data.len() < Self::MIN_SIZE {
931            return Err(ReadError::OutOfBounds);
932        }
933        Ok(Self { data })
934    }
935}
936
937/// Part of [AlternateSubstFormat1]
938#[derive(Clone)]
939pub struct AlternateSet<'a> {
940    data: FontData<'a>,
941}
942
943#[allow(clippy::needless_lifetimes)]
944impl<'a> AlternateSet<'a> {
945    pub const MIN_SIZE: usize = u16::RAW_BYTE_LEN;
946    basic_table_impls!(impl_the_methods);
947
948    /// Number of glyph IDs in the alternateGlyphIDs array
949    pub fn glyph_count(&self) -> u16 {
950        let range = self.glyph_count_byte_range();
951        self.data.read_at(range.start).ok().unwrap()
952    }
953
954    /// Array of alternate glyph IDs, in arbitrary order
955    pub fn alternate_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
956        let range = self.alternate_glyph_ids_byte_range();
957        self.data.read_array(range).ok().unwrap_or_default()
958    }
959
960    pub fn glyph_count_byte_range(&self) -> Range<usize> {
961        let start = 0;
962        start..start + u16::RAW_BYTE_LEN
963    }
964
965    pub fn alternate_glyph_ids_byte_range(&self) -> Range<usize> {
966        let glyph_count = self.glyph_count();
967        let start = self.glyph_count_byte_range().end;
968        start..start + (glyph_count as usize).saturating_mul(GlyphId16::RAW_BYTE_LEN)
969    }
970}
971
972#[cfg(feature = "experimental_traverse")]
973impl<'a> SomeTable<'a> for AlternateSet<'a> {
974    fn type_name(&self) -> &str {
975        "AlternateSet"
976    }
977    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
978        match idx {
979            0usize => Some(Field::new("glyph_count", self.glyph_count())),
980            1usize => Some(Field::new(
981                "alternate_glyph_ids",
982                self.alternate_glyph_ids(),
983            )),
984            _ => None,
985        }
986    }
987}
988
989#[cfg(feature = "experimental_traverse")]
990#[allow(clippy::needless_lifetimes)]
991impl<'a> std::fmt::Debug for AlternateSet<'a> {
992    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
993        (self as &dyn SomeTable<'a>).fmt(f)
994    }
995}
996
997impl Format<u16> for LigatureSubstFormat1<'_> {
998    const FORMAT: u16 = 1;
999}
1000
1001impl<'a> MinByteRange<'a> for LigatureSubstFormat1<'a> {
1002    fn min_byte_range(&self) -> Range<usize> {
1003        0..self.ligature_set_offsets_byte_range().end
1004    }
1005    fn min_table_bytes(&self) -> &'a [u8] {
1006        let range = self.min_byte_range();
1007        self.data.as_bytes().get(range).unwrap_or_default()
1008    }
1009}
1010
1011impl<'a> FontRead<'a> for LigatureSubstFormat1<'a> {
1012    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
1013        #[allow(clippy::absurd_extreme_comparisons)]
1014        if data.len() < Self::MIN_SIZE {
1015            return Err(ReadError::OutOfBounds);
1016        }
1017        Ok(Self { data })
1018    }
1019}
1020
1021/// [Ligature Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#41-ligature-substitution-format-1)
1022#[derive(Clone)]
1023pub struct LigatureSubstFormat1<'a> {
1024    data: FontData<'a>,
1025}
1026
1027#[allow(clippy::needless_lifetimes)]
1028impl<'a> LigatureSubstFormat1<'a> {
1029    pub const MIN_SIZE: usize = (u16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN);
1030    basic_table_impls!(impl_the_methods);
1031
1032    /// Format identifier: format = 1
1033    pub fn subst_format(&self) -> u16 {
1034        let range = self.subst_format_byte_range();
1035        self.data.read_at(range.start).ok().unwrap()
1036    }
1037
1038    /// Offset to Coverage table, from beginning of substitution
1039    /// subtable
1040    pub fn coverage_offset(&self) -> Offset16 {
1041        let range = self.coverage_offset_byte_range();
1042        self.data.read_at(range.start).ok().unwrap()
1043    }
1044
1045    /// Attempt to resolve [`coverage_offset`][Self::coverage_offset].
1046    pub fn coverage(&self) -> Result<CoverageTable<'a>, ReadError> {
1047        let data = self.data;
1048        self.coverage_offset().resolve(data)
1049    }
1050
1051    /// Number of LigatureSet tables
1052    pub fn ligature_set_count(&self) -> u16 {
1053        let range = self.ligature_set_count_byte_range();
1054        self.data.read_at(range.start).ok().unwrap()
1055    }
1056
1057    /// Array of offsets to LigatureSet tables. Offsets are from
1058    /// beginning of substitution subtable, ordered by Coverage index
1059    pub fn ligature_set_offsets(&self) -> &'a [BigEndian<Offset16>] {
1060        let range = self.ligature_set_offsets_byte_range();
1061        self.data.read_array(range).ok().unwrap_or_default()
1062    }
1063
1064    /// A dynamically resolving wrapper for [`ligature_set_offsets`][Self::ligature_set_offsets].
1065    pub fn ligature_sets(&self) -> ArrayOfOffsets<'a, LigatureSet<'a>, Offset16> {
1066        let data = self.data;
1067        let offsets = self.ligature_set_offsets();
1068        ArrayOfOffsets::new(offsets, data, ())
1069    }
1070
1071    pub fn subst_format_byte_range(&self) -> Range<usize> {
1072        let start = 0;
1073        start..start + u16::RAW_BYTE_LEN
1074    }
1075
1076    pub fn coverage_offset_byte_range(&self) -> Range<usize> {
1077        let start = self.subst_format_byte_range().end;
1078        start..start + Offset16::RAW_BYTE_LEN
1079    }
1080
1081    pub fn ligature_set_count_byte_range(&self) -> Range<usize> {
1082        let start = self.coverage_offset_byte_range().end;
1083        start..start + u16::RAW_BYTE_LEN
1084    }
1085
1086    pub fn ligature_set_offsets_byte_range(&self) -> Range<usize> {
1087        let ligature_set_count = self.ligature_set_count();
1088        let start = self.ligature_set_count_byte_range().end;
1089        start..start + (ligature_set_count as usize).saturating_mul(Offset16::RAW_BYTE_LEN)
1090    }
1091}
1092
1093#[cfg(feature = "experimental_traverse")]
1094impl<'a> SomeTable<'a> for LigatureSubstFormat1<'a> {
1095    fn type_name(&self) -> &str {
1096        "LigatureSubstFormat1"
1097    }
1098    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
1099        match idx {
1100            0usize => Some(Field::new("subst_format", self.subst_format())),
1101            1usize => Some(Field::new(
1102                "coverage_offset",
1103                FieldType::offset(self.coverage_offset(), self.coverage()),
1104            )),
1105            2usize => Some(Field::new("ligature_set_count", self.ligature_set_count())),
1106            3usize => Some({
1107                let data = self.data;
1108                Field::new(
1109                    "ligature_set_offsets",
1110                    FieldType::array_of_offsets(
1111                        better_type_name::<LigatureSet>(),
1112                        self.ligature_set_offsets(),
1113                        move |off| {
1114                            let target = off.get().resolve::<LigatureSet>(data);
1115                            FieldType::offset(off.get(), target)
1116                        },
1117                    ),
1118                )
1119            }),
1120            _ => None,
1121        }
1122    }
1123}
1124
1125#[cfg(feature = "experimental_traverse")]
1126#[allow(clippy::needless_lifetimes)]
1127impl<'a> std::fmt::Debug for LigatureSubstFormat1<'a> {
1128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129        (self as &dyn SomeTable<'a>).fmt(f)
1130    }
1131}
1132
1133impl<'a> MinByteRange<'a> for LigatureSet<'a> {
1134    fn min_byte_range(&self) -> Range<usize> {
1135        0..self.ligature_offsets_byte_range().end
1136    }
1137    fn min_table_bytes(&self) -> &'a [u8] {
1138        let range = self.min_byte_range();
1139        self.data.as_bytes().get(range).unwrap_or_default()
1140    }
1141}
1142
1143impl<'a> FontRead<'a> for LigatureSet<'a> {
1144    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
1145        #[allow(clippy::absurd_extreme_comparisons)]
1146        if data.len() < Self::MIN_SIZE {
1147            return Err(ReadError::OutOfBounds);
1148        }
1149        Ok(Self { data })
1150    }
1151}
1152
1153/// Part of [LigatureSubstFormat1]
1154#[derive(Clone)]
1155pub struct LigatureSet<'a> {
1156    data: FontData<'a>,
1157}
1158
1159#[allow(clippy::needless_lifetimes)]
1160impl<'a> LigatureSet<'a> {
1161    pub const MIN_SIZE: usize = u16::RAW_BYTE_LEN;
1162    basic_table_impls!(impl_the_methods);
1163
1164    /// Number of Ligature tables
1165    pub fn ligature_count(&self) -> u16 {
1166        let range = self.ligature_count_byte_range();
1167        self.data.read_at(range.start).ok().unwrap()
1168    }
1169
1170    /// Array of offsets to Ligature tables. Offsets are from beginning
1171    /// of LigatureSet table, ordered by preference.
1172    pub fn ligature_offsets(&self) -> &'a [BigEndian<Offset16>] {
1173        let range = self.ligature_offsets_byte_range();
1174        self.data.read_array(range).ok().unwrap_or_default()
1175    }
1176
1177    /// A dynamically resolving wrapper for [`ligature_offsets`][Self::ligature_offsets].
1178    pub fn ligatures(&self) -> ArrayOfOffsets<'a, Ligature<'a>, Offset16> {
1179        let data = self.data;
1180        let offsets = self.ligature_offsets();
1181        ArrayOfOffsets::new(offsets, data, ())
1182    }
1183
1184    pub fn ligature_count_byte_range(&self) -> Range<usize> {
1185        let start = 0;
1186        start..start + u16::RAW_BYTE_LEN
1187    }
1188
1189    pub fn ligature_offsets_byte_range(&self) -> Range<usize> {
1190        let ligature_count = self.ligature_count();
1191        let start = self.ligature_count_byte_range().end;
1192        start..start + (ligature_count as usize).saturating_mul(Offset16::RAW_BYTE_LEN)
1193    }
1194}
1195
1196#[cfg(feature = "experimental_traverse")]
1197impl<'a> SomeTable<'a> for LigatureSet<'a> {
1198    fn type_name(&self) -> &str {
1199        "LigatureSet"
1200    }
1201    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
1202        match idx {
1203            0usize => Some(Field::new("ligature_count", self.ligature_count())),
1204            1usize => Some({
1205                let data = self.data;
1206                Field::new(
1207                    "ligature_offsets",
1208                    FieldType::array_of_offsets(
1209                        better_type_name::<Ligature>(),
1210                        self.ligature_offsets(),
1211                        move |off| {
1212                            let target = off.get().resolve::<Ligature>(data);
1213                            FieldType::offset(off.get(), target)
1214                        },
1215                    ),
1216                )
1217            }),
1218            _ => None,
1219        }
1220    }
1221}
1222
1223#[cfg(feature = "experimental_traverse")]
1224#[allow(clippy::needless_lifetimes)]
1225impl<'a> std::fmt::Debug for LigatureSet<'a> {
1226    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1227        (self as &dyn SomeTable<'a>).fmt(f)
1228    }
1229}
1230
1231impl<'a> MinByteRange<'a> for Ligature<'a> {
1232    fn min_byte_range(&self) -> Range<usize> {
1233        0..self.component_glyph_ids_byte_range().end
1234    }
1235    fn min_table_bytes(&self) -> &'a [u8] {
1236        let range = self.min_byte_range();
1237        self.data.as_bytes().get(range).unwrap_or_default()
1238    }
1239}
1240
1241impl<'a> FontRead<'a> for Ligature<'a> {
1242    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
1243        #[allow(clippy::absurd_extreme_comparisons)]
1244        if data.len() < Self::MIN_SIZE {
1245            return Err(ReadError::OutOfBounds);
1246        }
1247        Ok(Self { data })
1248    }
1249}
1250
1251/// Part of [LigatureSubstFormat1]
1252#[derive(Clone)]
1253pub struct Ligature<'a> {
1254    data: FontData<'a>,
1255}
1256
1257#[allow(clippy::needless_lifetimes)]
1258impl<'a> Ligature<'a> {
1259    pub const MIN_SIZE: usize = (GlyphId16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN);
1260    basic_table_impls!(impl_the_methods);
1261
1262    /// glyph ID of ligature to substitute
1263    pub fn ligature_glyph(&self) -> GlyphId16 {
1264        let range = self.ligature_glyph_byte_range();
1265        self.data.read_at(range.start).ok().unwrap()
1266    }
1267
1268    /// Number of components in the ligature
1269    pub fn component_count(&self) -> u16 {
1270        let range = self.component_count_byte_range();
1271        self.data.read_at(range.start).ok().unwrap()
1272    }
1273
1274    /// Array of component glyph IDs — start with the second
1275    /// component, ordered in writing direction
1276    pub fn component_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
1277        let range = self.component_glyph_ids_byte_range();
1278        self.data.read_array(range).ok().unwrap_or_default()
1279    }
1280
1281    pub fn ligature_glyph_byte_range(&self) -> Range<usize> {
1282        let start = 0;
1283        start..start + GlyphId16::RAW_BYTE_LEN
1284    }
1285
1286    pub fn component_count_byte_range(&self) -> Range<usize> {
1287        let start = self.ligature_glyph_byte_range().end;
1288        start..start + u16::RAW_BYTE_LEN
1289    }
1290
1291    pub fn component_glyph_ids_byte_range(&self) -> Range<usize> {
1292        let component_count = self.component_count();
1293        let start = self.component_count_byte_range().end;
1294        start
1295            ..start
1296                + (transforms::subtract(component_count, 1_usize))
1297                    .saturating_mul(GlyphId16::RAW_BYTE_LEN)
1298    }
1299}
1300
1301#[cfg(feature = "experimental_traverse")]
1302impl<'a> SomeTable<'a> for Ligature<'a> {
1303    fn type_name(&self) -> &str {
1304        "Ligature"
1305    }
1306    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
1307        match idx {
1308            0usize => Some(Field::new("ligature_glyph", self.ligature_glyph())),
1309            1usize => Some(Field::new("component_count", self.component_count())),
1310            2usize => Some(Field::new(
1311                "component_glyph_ids",
1312                self.component_glyph_ids(),
1313            )),
1314            _ => None,
1315        }
1316    }
1317}
1318
1319#[cfg(feature = "experimental_traverse")]
1320#[allow(clippy::needless_lifetimes)]
1321impl<'a> std::fmt::Debug for Ligature<'a> {
1322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1323        (self as &dyn SomeTable<'a>).fmt(f)
1324    }
1325}
1326
1327impl Format<u16> for ExtensionSubstFormat1<'_> {
1328    const FORMAT: u16 = 1;
1329}
1330
1331impl<'a, T> MinByteRange<'a> for ExtensionSubstFormat1<'a, T> {
1332    fn min_byte_range(&self) -> Range<usize> {
1333        0..self.extension_offset_byte_range().end
1334    }
1335    fn min_table_bytes(&self) -> &'a [u8] {
1336        let range = self.min_byte_range();
1337        self.data.as_bytes().get(range).unwrap_or_default()
1338    }
1339}
1340
1341impl<'a, T> FontRead<'a> for ExtensionSubstFormat1<'a, T> {
1342    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
1343        #[allow(clippy::absurd_extreme_comparisons)]
1344        if data.len() < Self::MIN_SIZE {
1345            return Err(ReadError::OutOfBounds);
1346        }
1347        Ok(Self {
1348            data,
1349            offset_type: std::marker::PhantomData,
1350        })
1351    }
1352}
1353
1354impl<'a> ExtensionSubstFormat1<'a, ()> {
1355    #[allow(dead_code)]
1356    pub(crate) fn into_concrete<T>(self) -> ExtensionSubstFormat1<'a, T> {
1357        ExtensionSubstFormat1 {
1358            data: self.data,
1359            offset_type: std::marker::PhantomData,
1360        }
1361    }
1362}
1363
1364impl<'a, T> ExtensionSubstFormat1<'a, T> {
1365    #[allow(dead_code)]
1366    /// Replace the specific generic type on this implementation with `()`
1367    pub(crate) fn of_unit_type(&self) -> ExtensionSubstFormat1<'a, ()> {
1368        ExtensionSubstFormat1 {
1369            data: self.data,
1370            offset_type: std::marker::PhantomData,
1371        }
1372    }
1373}
1374
1375/// [Extension Substitution Subtable Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#71-extension-substitution-subtable-format-1)
1376#[derive(Clone)]
1377pub struct ExtensionSubstFormat1<'a, T = ()> {
1378    data: FontData<'a>,
1379    offset_type: std::marker::PhantomData<*const T>,
1380}
1381
1382#[allow(clippy::needless_lifetimes)]
1383impl<'a, T> ExtensionSubstFormat1<'a, T> {
1384    pub const MIN_SIZE: usize = (u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN);
1385    basic_table_impls!(impl_the_methods);
1386
1387    /// Format identifier. Set to 1.
1388    pub fn subst_format(&self) -> u16 {
1389        let range = self.subst_format_byte_range();
1390        self.data.read_at(range.start).ok().unwrap()
1391    }
1392
1393    /// Lookup type of subtable referenced by extensionOffset (that is,
1394    /// the extension subtable).
1395    pub fn extension_lookup_type(&self) -> u16 {
1396        let range = self.extension_lookup_type_byte_range();
1397        self.data.read_at(range.start).ok().unwrap()
1398    }
1399
1400    /// Offset to the extension subtable, of lookup type
1401    /// extensionLookupType, relative to the start of the
1402    /// ExtensionSubstFormat1 subtable.
1403    pub fn extension_offset(&self) -> Offset32 {
1404        let range = self.extension_offset_byte_range();
1405        self.data.read_at(range.start).ok().unwrap()
1406    }
1407
1408    /// Attempt to resolve [`extension_offset`][Self::extension_offset].
1409    pub fn extension(&self) -> Result<T, ReadError>
1410    where
1411        T: FontRead<'a>,
1412    {
1413        let data = self.data;
1414        self.extension_offset().resolve(data)
1415    }
1416
1417    pub fn subst_format_byte_range(&self) -> Range<usize> {
1418        let start = 0;
1419        start..start + u16::RAW_BYTE_LEN
1420    }
1421
1422    pub fn extension_lookup_type_byte_range(&self) -> Range<usize> {
1423        let start = self.subst_format_byte_range().end;
1424        start..start + u16::RAW_BYTE_LEN
1425    }
1426
1427    pub fn extension_offset_byte_range(&self) -> Range<usize> {
1428        let start = self.extension_lookup_type_byte_range().end;
1429        start..start + Offset32::RAW_BYTE_LEN
1430    }
1431}
1432
1433#[cfg(feature = "experimental_traverse")]
1434impl<'a, T: FontRead<'a> + SomeTable<'a> + 'a> SomeTable<'a> for ExtensionSubstFormat1<'a, T> {
1435    fn type_name(&self) -> &str {
1436        "ExtensionSubstFormat1"
1437    }
1438    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
1439        match idx {
1440            0usize => Some(Field::new("subst_format", self.subst_format())),
1441            1usize => Some(Field::new(
1442                "extension_lookup_type",
1443                self.extension_lookup_type(),
1444            )),
1445            2usize => Some(Field::new(
1446                "extension_offset",
1447                FieldType::offset(self.extension_offset(), self.extension()),
1448            )),
1449            _ => None,
1450        }
1451    }
1452}
1453
1454#[cfg(feature = "experimental_traverse")]
1455#[allow(clippy::needless_lifetimes)]
1456impl<'a, T: FontRead<'a> + SomeTable<'a> + 'a> std::fmt::Debug for ExtensionSubstFormat1<'a, T> {
1457    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1458        (self as &dyn SomeTable<'a>).fmt(f)
1459    }
1460}
1461
1462/// A [GSUB Extension Substitution](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#ES) subtable
1463pub enum ExtensionSubtable<'a> {
1464    Single(ExtensionSubstFormat1<'a, SingleSubst<'a>>),
1465    Multiple(ExtensionSubstFormat1<'a, MultipleSubstFormat1<'a>>),
1466    Alternate(ExtensionSubstFormat1<'a, AlternateSubstFormat1<'a>>),
1467    Ligature(ExtensionSubstFormat1<'a, LigatureSubstFormat1<'a>>),
1468    Contextual(ExtensionSubstFormat1<'a, SubstitutionSequenceContext<'a>>),
1469    ChainContextual(ExtensionSubstFormat1<'a, SubstitutionChainContext<'a>>),
1470    Reverse(ExtensionSubstFormat1<'a, ReverseChainSingleSubstFormat1<'a>>),
1471}
1472
1473impl<'a> FontRead<'a> for ExtensionSubtable<'a> {
1474    fn read(bytes: FontData<'a>) -> Result<Self, ReadError> {
1475        let untyped = ExtensionSubstFormat1::read(bytes)?;
1476        match untyped.extension_lookup_type() {
1477            1 => Ok(ExtensionSubtable::Single(untyped.into_concrete())),
1478            2 => Ok(ExtensionSubtable::Multiple(untyped.into_concrete())),
1479            3 => Ok(ExtensionSubtable::Alternate(untyped.into_concrete())),
1480            4 => Ok(ExtensionSubtable::Ligature(untyped.into_concrete())),
1481            5 => Ok(ExtensionSubtable::Contextual(untyped.into_concrete())),
1482            6 => Ok(ExtensionSubtable::ChainContextual(untyped.into_concrete())),
1483            8 => Ok(ExtensionSubtable::Reverse(untyped.into_concrete())),
1484            other => Err(ReadError::InvalidFormat(other.into())),
1485        }
1486    }
1487}
1488
1489impl<'a> ExtensionSubtable<'a> {
1490    #[allow(dead_code)]
1491    /// Return the inner table, removing the specific generics.
1492    ///
1493    /// This lets us return a single concrete type we can call methods on.
1494    pub(crate) fn of_unit_type(&self) -> ExtensionSubstFormat1<'a, ()> {
1495        match self {
1496            ExtensionSubtable::Single(inner) => inner.of_unit_type(),
1497            ExtensionSubtable::Multiple(inner) => inner.of_unit_type(),
1498            ExtensionSubtable::Alternate(inner) => inner.of_unit_type(),
1499            ExtensionSubtable::Ligature(inner) => inner.of_unit_type(),
1500            ExtensionSubtable::Contextual(inner) => inner.of_unit_type(),
1501            ExtensionSubtable::ChainContextual(inner) => inner.of_unit_type(),
1502            ExtensionSubtable::Reverse(inner) => inner.of_unit_type(),
1503        }
1504    }
1505}
1506
1507#[cfg(feature = "experimental_traverse")]
1508impl<'a> ExtensionSubtable<'a> {
1509    fn dyn_inner(&self) -> &(dyn SomeTable<'a> + 'a) {
1510        match self {
1511            ExtensionSubtable::Single(table) => table,
1512            ExtensionSubtable::Multiple(table) => table,
1513            ExtensionSubtable::Alternate(table) => table,
1514            ExtensionSubtable::Ligature(table) => table,
1515            ExtensionSubtable::Contextual(table) => table,
1516            ExtensionSubtable::ChainContextual(table) => table,
1517            ExtensionSubtable::Reverse(table) => table,
1518        }
1519    }
1520}
1521
1522#[cfg(feature = "experimental_traverse")]
1523impl<'a> SomeTable<'a> for ExtensionSubtable<'a> {
1524    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
1525        self.dyn_inner().get_field(idx)
1526    }
1527    fn type_name(&self) -> &str {
1528        self.dyn_inner().type_name()
1529    }
1530}
1531
1532#[cfg(feature = "experimental_traverse")]
1533impl std::fmt::Debug for ExtensionSubtable<'_> {
1534    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1535        self.dyn_inner().fmt(f)
1536    }
1537}
1538
1539impl Format<u16> for ReverseChainSingleSubstFormat1<'_> {
1540    const FORMAT: u16 = 1;
1541}
1542
1543impl<'a> MinByteRange<'a> for ReverseChainSingleSubstFormat1<'a> {
1544    fn min_byte_range(&self) -> Range<usize> {
1545        0..self.substitute_glyph_ids_byte_range().end
1546    }
1547    fn min_table_bytes(&self) -> &'a [u8] {
1548        let range = self.min_byte_range();
1549        self.data.as_bytes().get(range).unwrap_or_default()
1550    }
1551}
1552
1553impl<'a> FontRead<'a> for ReverseChainSingleSubstFormat1<'a> {
1554    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
1555        #[allow(clippy::absurd_extreme_comparisons)]
1556        if data.len() < Self::MIN_SIZE {
1557            return Err(ReadError::OutOfBounds);
1558        }
1559        Ok(Self { data })
1560    }
1561}
1562
1563/// [Reverse Chaining Contextual Single Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#81-reverse-chaining-contextual-single-substitution-format-1-coverage-based-glyph-contexts)
1564#[derive(Clone)]
1565pub struct ReverseChainSingleSubstFormat1<'a> {
1566    data: FontData<'a>,
1567}
1568
1569#[allow(clippy::needless_lifetimes)]
1570impl<'a> ReverseChainSingleSubstFormat1<'a> {
1571    pub const MIN_SIZE: usize = (u16::RAW_BYTE_LEN
1572        + Offset16::RAW_BYTE_LEN
1573        + u16::RAW_BYTE_LEN
1574        + u16::RAW_BYTE_LEN
1575        + u16::RAW_BYTE_LEN);
1576    basic_table_impls!(impl_the_methods);
1577
1578    /// Format identifier: format = 1
1579    pub fn subst_format(&self) -> u16 {
1580        let range = self.subst_format_byte_range();
1581        self.data.read_at(range.start).ok().unwrap()
1582    }
1583
1584    /// Offset to Coverage table, from beginning of substitution
1585    /// subtable.
1586    pub fn coverage_offset(&self) -> Offset16 {
1587        let range = self.coverage_offset_byte_range();
1588        self.data.read_at(range.start).ok().unwrap()
1589    }
1590
1591    /// Attempt to resolve [`coverage_offset`][Self::coverage_offset].
1592    pub fn coverage(&self) -> Result<CoverageTable<'a>, ReadError> {
1593        let data = self.data;
1594        self.coverage_offset().resolve(data)
1595    }
1596
1597    /// Number of glyphs in the backtrack sequence.
1598    pub fn backtrack_glyph_count(&self) -> u16 {
1599        let range = self.backtrack_glyph_count_byte_range();
1600        self.data.read_at(range.start).ok().unwrap()
1601    }
1602
1603    /// Array of offsets to coverage tables in backtrack sequence, in
1604    /// glyph sequence order.
1605    pub fn backtrack_coverage_offsets(&self) -> &'a [BigEndian<Offset16>] {
1606        let range = self.backtrack_coverage_offsets_byte_range();
1607        self.data.read_array(range).ok().unwrap_or_default()
1608    }
1609
1610    /// A dynamically resolving wrapper for [`backtrack_coverage_offsets`][Self::backtrack_coverage_offsets].
1611    pub fn backtrack_coverages(&self) -> ArrayOfOffsets<'a, CoverageTable<'a>, Offset16> {
1612        let data = self.data;
1613        let offsets = self.backtrack_coverage_offsets();
1614        ArrayOfOffsets::new(offsets, data, ())
1615    }
1616
1617    /// Number of glyphs in lookahead sequence.
1618    pub fn lookahead_glyph_count(&self) -> u16 {
1619        let range = self.lookahead_glyph_count_byte_range();
1620        self.data.read_at(range.start).ok().unwrap_or_default()
1621    }
1622
1623    /// Array of offsets to coverage tables in lookahead sequence, in
1624    /// glyph sequence order.
1625    pub fn lookahead_coverage_offsets(&self) -> &'a [BigEndian<Offset16>] {
1626        let range = self.lookahead_coverage_offsets_byte_range();
1627        self.data.read_array(range).ok().unwrap_or_default()
1628    }
1629
1630    /// A dynamically resolving wrapper for [`lookahead_coverage_offsets`][Self::lookahead_coverage_offsets].
1631    pub fn lookahead_coverages(&self) -> ArrayOfOffsets<'a, CoverageTable<'a>, Offset16> {
1632        let data = self.data;
1633        let offsets = self.lookahead_coverage_offsets();
1634        ArrayOfOffsets::new(offsets, data, ())
1635    }
1636
1637    /// Number of glyph IDs in the substituteGlyphIDs array.
1638    pub fn glyph_count(&self) -> u16 {
1639        let range = self.glyph_count_byte_range();
1640        self.data.read_at(range.start).ok().unwrap_or_default()
1641    }
1642
1643    /// Array of substitute glyph IDs — ordered by Coverage index.
1644    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
1645        let range = self.substitute_glyph_ids_byte_range();
1646        self.data.read_array(range).ok().unwrap_or_default()
1647    }
1648
1649    pub fn subst_format_byte_range(&self) -> Range<usize> {
1650        let start = 0;
1651        start..start + u16::RAW_BYTE_LEN
1652    }
1653
1654    pub fn coverage_offset_byte_range(&self) -> Range<usize> {
1655        let start = self.subst_format_byte_range().end;
1656        start..start + Offset16::RAW_BYTE_LEN
1657    }
1658
1659    pub fn backtrack_glyph_count_byte_range(&self) -> Range<usize> {
1660        let start = self.coverage_offset_byte_range().end;
1661        start..start + u16::RAW_BYTE_LEN
1662    }
1663
1664    pub fn backtrack_coverage_offsets_byte_range(&self) -> Range<usize> {
1665        let backtrack_glyph_count = self.backtrack_glyph_count();
1666        let start = self.backtrack_glyph_count_byte_range().end;
1667        start..start + (backtrack_glyph_count as usize).saturating_mul(Offset16::RAW_BYTE_LEN)
1668    }
1669
1670    pub fn lookahead_glyph_count_byte_range(&self) -> Range<usize> {
1671        let start = self.backtrack_coverage_offsets_byte_range().end;
1672        start..start + u16::RAW_BYTE_LEN
1673    }
1674
1675    pub fn lookahead_coverage_offsets_byte_range(&self) -> Range<usize> {
1676        let lookahead_glyph_count = self.lookahead_glyph_count();
1677        let start = self.lookahead_glyph_count_byte_range().end;
1678        start..start + (lookahead_glyph_count as usize).saturating_mul(Offset16::RAW_BYTE_LEN)
1679    }
1680
1681    pub fn glyph_count_byte_range(&self) -> Range<usize> {
1682        let start = self.lookahead_coverage_offsets_byte_range().end;
1683        start..start + u16::RAW_BYTE_LEN
1684    }
1685
1686    pub fn substitute_glyph_ids_byte_range(&self) -> Range<usize> {
1687        let glyph_count = self.glyph_count();
1688        let start = self.glyph_count_byte_range().end;
1689        start..start + (glyph_count as usize).saturating_mul(GlyphId16::RAW_BYTE_LEN)
1690    }
1691}
1692
1693#[cfg(feature = "experimental_traverse")]
1694impl<'a> SomeTable<'a> for ReverseChainSingleSubstFormat1<'a> {
1695    fn type_name(&self) -> &str {
1696        "ReverseChainSingleSubstFormat1"
1697    }
1698    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
1699        match idx {
1700            0usize => Some(Field::new("subst_format", self.subst_format())),
1701            1usize => Some(Field::new(
1702                "coverage_offset",
1703                FieldType::offset(self.coverage_offset(), self.coverage()),
1704            )),
1705            2usize => Some(Field::new(
1706                "backtrack_glyph_count",
1707                self.backtrack_glyph_count(),
1708            )),
1709            3usize => Some({
1710                let data = self.data;
1711                Field::new(
1712                    "backtrack_coverage_offsets",
1713                    FieldType::array_of_offsets(
1714                        better_type_name::<CoverageTable>(),
1715                        self.backtrack_coverage_offsets(),
1716                        move |off| {
1717                            let target = off.get().resolve::<CoverageTable>(data);
1718                            FieldType::offset(off.get(), target)
1719                        },
1720                    ),
1721                )
1722            }),
1723            4usize => Some(Field::new(
1724                "lookahead_glyph_count",
1725                self.lookahead_glyph_count(),
1726            )),
1727            5usize => Some({
1728                let data = self.data;
1729                Field::new(
1730                    "lookahead_coverage_offsets",
1731                    FieldType::array_of_offsets(
1732                        better_type_name::<CoverageTable>(),
1733                        self.lookahead_coverage_offsets(),
1734                        move |off| {
1735                            let target = off.get().resolve::<CoverageTable>(data);
1736                            FieldType::offset(off.get(), target)
1737                        },
1738                    ),
1739                )
1740            }),
1741            6usize => Some(Field::new("glyph_count", self.glyph_count())),
1742            7usize => Some(Field::new(
1743                "substitute_glyph_ids",
1744                self.substitute_glyph_ids(),
1745            )),
1746            _ => None,
1747        }
1748    }
1749}
1750
1751#[cfg(feature = "experimental_traverse")]
1752#[allow(clippy::needless_lifetimes)]
1753impl<'a> std::fmt::Debug for ReverseChainSingleSubstFormat1<'a> {
1754    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1755        (self as &dyn SomeTable<'a>).fmt(f)
1756    }
1757}