pinot/otl/
table.rs

1use super::{Gdef, Layout, Lookup, LookupFilter, LookupFlag, LookupKind, LookupRecord, Stage};
2use crate::parse_prelude::*;
3
4impl<'a> Layout<'a> {
5    /// Creates a new layout table for the specified stage, table data,
6    /// and glyph definitions. The table data should contain a `GSUB` or
7    /// `GPOS` table matching the specified stage.
8    pub fn new(stage: Stage, data: &'a [u8], gdef: Option<Gdef<'a>>) -> Self {
9        Self {
10            stage,
11            data: Buffer::new(data),
12            gdef,
13        }
14    }
15
16    /// Returns the stage.
17    pub fn stage(&self) -> Stage {
18        self.stage
19    }
20
21    /// Returns the underlying table data.
22    pub fn data(&self) -> &'a [u8] {
23        self.data.data()
24    }
25
26    /// Returns the associated glyph definitions.
27    pub fn gdef(&self) -> Option<&Gdef<'a>> {
28        self.gdef.as_ref()
29    }
30
31    /// Returns the number of available scripts.
32    pub fn num_scripts(&self) -> u16 {
33        if let Some(base) = self.data.read_u16(4) {
34            self.data.read_u16(base as usize).unwrap_or(0)
35        } else {
36            0
37        }
38    }
39
40    /// Returns the script at the specified index.
41    pub fn script(&'a self, index: u16) -> Option<Script<'a>> {
42        let b = &self.data;
43        let list_base = b.read_u16(4)? as usize;
44        let len = b.read_u16(list_base)?;
45        if index >= len {
46            return None;
47        }
48        let record_base = list_base + 2 + index as usize * 6;
49        let tag = b.read_tag(record_base)?;
50        let mut offset = b.read_u16(record_base + 4)? as u32;
51        if offset == 0 {
52            return None;
53        }
54        offset += list_base as u32;
55        let num_languages = b.read_u16(offset as usize + 2)?;
56        let record = ScriptRecord {
57            tag,
58            offset,
59            num_languages,
60        };
61        Some(record.materialize(self))
62    }
63
64    /// Returns an iterator over the available scripts.
65    pub fn scripts(&'a self) -> impl Iterator<Item = Script<'a>> + 'a + Clone {
66        (0..self.num_scripts()).filter_map(move |index| self.script(index))
67    }
68
69    /// Returns the number of available features.
70    pub fn num_features(&self) -> u16 {
71        if let Some(base) = self.data.read_u16(6) {
72            self.data.read_u16(base as usize).unwrap_or(0)
73        } else {
74            0
75        }
76    }
77
78    /// Returns the feature at the specified index.
79    pub fn feature(&'a self, index: u16) -> Option<Feature<'a>> {
80        let b = &self.data;
81        let list_base = b.read_u16(6)? as usize;
82        let len = b.read_u16(list_base)?;
83        if index >= len {
84            return None;
85        }
86        let record_base = list_base + 2 + index as usize * 6;
87        let tag = b.read_tag(record_base)?;
88        let offset = b.read_u16(record_base + 4)? as u32;
89        if offset == 0 {
90            return None;
91        }
92        Some(
93            FeatureRecord {
94                index,
95                tag,
96                offset: list_base as u32 + offset,
97            }
98            .materialize(self),
99        )
100    }
101
102    /// Returns an iterator over the available features.
103    pub fn features(&'a self) -> impl Iterator<Item = Feature<'a>> + 'a + Clone {
104        (0..self.num_features()).filter_map(move |index| self.feature(index))
105    }
106
107    /// Returns feature variation support for the layout table.
108    pub fn feature_variations(&'a self) -> Option<FeatureVariations<'a>> {
109        // If minor version is >= 1, feature variations offset should be present at offset 10
110        if self.data.read_u16(2) >= Some(1) {
111            let offset = self.data.read_offset32(10, 0)? as usize;
112            let len = self.data.read_u32(offset + 4)?;
113            Some(FeatureVariations {
114                layout: self,
115                base: offset,
116                len,
117            })
118        } else {
119            None
120        }
121    }
122
123    /// Returns the number of available lookups.
124    pub fn num_lookups(&self) -> u16 {
125        if let Some(base) = self.data.read_u16(8) {
126            self.data.read_u16(base as usize).unwrap_or(0)
127        } else {
128            0
129        }
130    }
131
132    /// Returns the lookup at the specified index.
133    pub fn lookup(&'a self, index: u16) -> Option<Lookup<'a>> {
134        let b = &self.data;
135        let list_base = b.read_u16(8)? as usize;
136        let len = b.read_u16(list_base)?;
137        if index >= len {
138            return None;
139        }
140        let base = list_base + b.read_u16(list_base + 2 + index as usize * 2)? as usize;
141        let mut kind = b.read_u16(base)? as u8;
142        let flag = b.read_u16(base + 2)?;
143        let f = flag as u8;
144        let num_subtables = b.read_u16(base + 4)?;
145        let mark_class = (flag >> 8) as u8;
146        let ignore_marks = f & (1 << 3) != 0;
147        let mut mark_check = false;
148        let mut mark_set = 0;
149        if !ignore_marks {
150            if let Some(gdef) = &self.gdef {
151                mark_check = mark_class != 0 && gdef.has_mark_classes();
152                mark_set = if flag & 0x10 != 0 {
153                    let idx = b.read_u16(base + 6 + num_subtables as usize * 2)?;
154                    mark_check = true;
155                    gdef.mark_set_offset(idx).unwrap_or(0)
156                } else {
157                    0
158                };
159            }
160        }
161        let is_sub = self.stage == Stage::Substitution;
162        let subtables = base + 6;
163        let is_extension = (is_sub && kind == 7) || (!is_sub && kind == 9);
164        if is_extension && num_subtables > 0 {
165            let s = base + b.read_u16(subtables)? as usize;
166            kind = b.read_u16(s + 2)? as u8;
167        }
168        use LookupKind::*;
169        let kind = if is_sub {
170            match kind {
171                1 => SingleSubst,
172                2 => MultipleSubst,
173                3 => AlternateSubst,
174                4 => LigatureSubst,
175                5 => SeqContext,
176                6 => ChainContext,
177                8 => RevChainContext,
178                _ => return None,
179            }
180        } else {
181            match kind {
182                1 => SinglePos,
183                2 => PairPos,
184                3 => CursivePos,
185                4 => MarkPos,
186                5 => MarkLigaturePos,
187                6 => MarkMarkPos,
188                7 => SeqContext,
189                8 => ChainContext,
190                _ => return None,
191            }
192        };
193        let ignored_classes = ((f as u8) & 0b1110) | 1 << 5;
194        let filter = LookupFilter {
195            ignored_classes,
196            mask: 0,
197            mark_check,
198            mark_class,
199            mark_set,
200        };
201        Some(
202            LookupRecord {
203                index,
204                stage: self.stage,
205                kind,
206                flag: LookupFlag(flag),
207                filter,
208                is_extension,
209                offset: base as u32,
210                num_subtables,
211            }
212            .materialize(self),
213        )
214    }
215
216    /// Returns an iterator over the available lookups.
217    pub fn lookups(&'a self) -> impl Iterator<Item = Lookup<'a>> + 'a + Clone {
218        (0..self.num_lookups()).filter_map(move |index| self.lookup(index))
219    }
220}
221
222/// Information about a script.
223#[derive(Copy, Clone)]
224pub struct ScriptRecord {
225    /// Tag that identifies the script.
226    pub tag: Tag,
227    /// Offset to the script table from the beginning of the associated layout
228    /// table.
229    pub offset: u32,
230    /// Number of languages associated with the script.
231    pub num_languages: u16,
232}
233
234impl ScriptRecord {
235    /// Creates a new bound script for the specified layout context. The script
236    /// must belong to the associated layout table.
237    pub fn materialize<'a>(&self, layout: &'a Layout<'a>) -> Script<'a> {
238        Script {
239            layout,
240            record: *self,
241        }
242    }
243}
244
245/// Script in a layout table.
246///
247/// <https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#script-table-and-language-system-record>
248#[derive(Copy, Clone)]
249pub struct Script<'a> {
250    /// Associated layout table.
251    pub layout: &'a Layout<'a>,
252    /// Record for the script.
253    pub record: ScriptRecord,
254}
255
256impl<'a> Script<'a> {
257    /// Returns the default language for the script.
258    pub fn default_language(&self) -> Option<Language<'a>> {
259        let data = &self.layout.data;
260        let base = self.record.offset;
261        let offset = data.read_u16(base as usize)? as u32;
262        if offset != 0 {
263            let tag = Tag::new(b"DFLT");
264            let record = LanguageRecord {
265                script: self.record,
266                is_default: true,
267                tag,
268                offset: base + offset,
269            };
270            Some(Language {
271                script: *self,
272                record,
273            })
274        } else {
275            None
276        }
277    }
278
279    /// Returns the number of languages supported by the script.
280    pub fn num_languages(&self) -> u16 {
281        self.record.num_languages
282    }
283
284    /// Returns the language at the specified index.
285    pub fn language(&self, index: u16) -> Option<Language<'a>> {
286        if index >= self.record.num_languages {
287            return None;
288        }
289        let data = &self.layout.data;
290        let base = self.record.offset;
291        let record_base = base as usize + 4 + index as usize * 6;
292        let tag = data.read_tag(record_base)?;
293        let mut offset = data.read_u16(record_base + 4)? as u32;
294        if offset == 0 {
295            return None;
296        }
297        offset += base;
298        let record = LanguageRecord {
299            script: self.record,
300            is_default: false,
301            tag,
302            offset,
303        };
304        Some(Language {
305            script: *self,
306            record,
307        })
308    }
309
310    /// Returns an iterator over the languages supported by the script.
311    pub fn languages(self) -> impl Iterator<Item = Language<'a>> + 'a + Clone {
312        (0..self.record.num_languages).filter_map(move |index| self.language(index))
313    }
314}
315
316/// Information about a language.
317#[derive(Copy, Clone)]
318pub struct LanguageRecord {
319    /// Script that contains the language.
320    pub script: ScriptRecord,
321    /// True for a default language.
322    pub is_default: bool,
323    /// Tag that identifies the language.
324    pub tag: Tag,
325    /// Offset to the language from the beginning of the associated layout
326    /// table.
327    pub offset: u32,
328}
329
330impl LanguageRecord {
331    /// Returns the BCP 47 language code.
332    pub fn code(&self) -> Option<&'static str> {
333        // super::tag::language_tag_to_id(self.tag)
334        None
335    }
336
337    /// Creates a new bound language for the specified layout context. The language
338    /// must belong to the associated layout table.    
339    pub fn materialize<'a>(&self, table: &'a Layout<'a>) -> Language<'a> {
340        let script = self.script.materialize(table);
341        Language {
342            script,
343            record: *self,
344        }
345    }
346}
347
348/// Language with an associated script.
349///
350/// <https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#language-system-table>
351#[derive(Copy, Clone)]
352pub struct Language<'a> {
353    /// Associated script.
354    pub script: Script<'a>,
355    /// Record for the language.
356    pub record: LanguageRecord,
357}
358
359impl<'a> Language<'a> {
360    /// Returns the associated layout table.
361    pub fn layout(&self) -> &Layout<'a> {
362        self.script.layout
363    }
364
365    /// Returns the language tag.
366    pub fn tag(&self) -> Tag {
367        self.record.tag
368    }
369
370    /// Returns the BCP 47 language code.
371    pub fn code(&self) -> Option<&'static str> {
372        self.record.code()
373    }
374
375    /// Returns the indices of the features associated with the language.
376    pub fn feature_indices(&self) -> Slice<'a, u16> {
377        let data = &self.layout().data;
378        data.read_slice16(self.record.offset as usize + 4)
379            .unwrap_or_default()
380    }
381
382    /// Returns an iterator over the features associated with the language.
383    pub fn features(&'a self) -> impl Iterator<Item = Feature<'a>> + 'a + Clone {
384        self.feature_indices()
385            .iter()
386            .filter_map(move |index| self.layout().feature(index))
387    }
388}
389
390/// Information about a feature.
391#[derive(Copy, Clone, Debug)]
392pub struct FeatureRecord {
393    /// Index of the feature.
394    pub index: u16,
395    /// Tag that identifies the feature.
396    pub tag: Tag,
397    /// Offset to the feature from the beginning of the associated layout
398    /// table.
399    pub offset: u32,
400}
401
402impl FeatureRecord {
403    pub fn materialize<'a>(&self, layout: &'a Layout<'a>) -> Feature<'a> {
404        Feature {
405            layout,
406            record: *self,
407        }
408    }
409}
410
411/// Typographic feature defined as a set of lookups.
412///
413/// <https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#feature-table>
414#[derive(Copy, Clone)]
415pub struct Feature<'a> {
416    /// Associated layout.
417    pub layout: &'a Layout<'a>,
418    /// Record for the feature.
419    pub record: FeatureRecord,
420}
421
422impl<'a> Feature<'a> {
423    /// Returns the lookup indices for the feature.
424    pub fn lookup_indices(&self) -> Slice<'a, u16> {
425        self.layout
426            .data
427            .read_slice16(self.record.offset as usize + 2)
428            .unwrap_or_default()
429    }
430
431    /// Returns an iterator over the lookups for the feature.
432    pub fn lookups(&'a self) -> impl Iterator<Item = Lookup<'a>> + 'a + Clone {
433        self.lookup_indices()
434            .iter()
435            .filter_map(move |index| self.layout.lookup(index))
436    }
437}
438
439/// Feature variations table.
440///
441/// <https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#featurevariations-table>
442#[derive(Copy, Clone)]
443pub struct FeatureVariations<'a> {
444    layout: &'a Layout<'a>,
445    base: usize,
446    len: u32,
447}
448
449impl<'a> FeatureVariations<'a> {
450    /// Returns the number of condition sets.
451    pub fn len(&self) -> u32 {
452        self.len
453    }
454
455    /// Returns true if there are no condition sets.
456    pub fn is_empty(&self) -> bool {
457        self.len == 0
458    }
459
460    /// Returns the condition set at the specified index.
461    pub fn get(&self, index: u32) -> Option<ConditionSet<'a>> {
462        if index >= self.len {
463            return None;
464        }
465        let data = &self.layout.data;
466        let record_base = self.base + 8 + index as usize * 8;
467        let condition_set = data.read_offset32(record_base, self.base as u32)? as usize;
468        let feature_subst = data.read_offset32(record_base + 4, self.base as u32)?;
469        let len = data.read_u16(condition_set)?;
470        Some(ConditionSet {
471            layout: self.layout,
472            base: condition_set,
473            feature_subst,
474            len,
475        })
476    }
477
478    /// Returns an iterator over the condition sets.
479    pub fn iter(&'a self) -> impl Iterator<Item = ConditionSet<'a>> + 'a + Clone {
480        (0..self.len).filter_map(move |index| self.get(index))
481    }
482
483    /// Returns the first condition set that is satisfied by the specified
484    /// normalized variation coordinates.
485    pub fn find(&'a self, coords: &[NormalizedCoord]) -> Option<ConditionSet<'a>> {
486        for set in self.iter() {
487            let mut satisfied = true;
488            for condition in set.iter() {
489                let coord = coords
490                    .get(condition.axis_index as usize)
491                    .copied()
492                    .unwrap_or(0);
493                if coord < condition.min_value || coord > condition.max_value {
494                    satisfied = false;
495                    break;
496                }
497            }
498            if satisfied {
499                return Some(set);
500            }
501        }
502        None
503    }
504}
505
506/// Set of conditions for selecting a feature substitution.
507///
508/// <https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#conditionset-table>
509#[derive(Copy, Clone)]
510pub struct ConditionSet<'a> {
511    layout: &'a Layout<'a>,
512    base: usize,
513    feature_subst: u32,
514    len: u16,
515}
516
517impl<'a> ConditionSet<'a> {
518    /// Returns the number of conditions in the set.
519    pub fn len(&self) -> u16 {
520        self.len
521    }
522
523    /// Returns true if the set is empty.
524    pub fn is_empty(&self) -> bool {
525        self.len == 0
526    }
527
528    /// Returns the condition at the specified index.
529    pub fn get(&self, index: u16) -> Option<Condition> {
530        if index >= self.len {
531            return None;
532        }
533        let data = &self.layout.data;
534        let offset =
535            data.read_offset32(self.base + 2 + index as usize * 4, self.base as u32)? as usize;
536        let format = data.read_u16(offset)?;
537        if format != 1 {
538            return None;
539        }
540        let axis_index = data.read_u16(offset + 2)?;
541        let min_value = data.read_i16(offset + 4)?;
542        let max_value = data.read_i16(offset + 6)?;
543        Some(Condition {
544            axis_index,
545            min_value,
546            max_value,
547        })
548    }
549
550    /// Returns an iterator over the conditions in the set.
551    pub fn iter(&'a self) -> impl Iterator<Item = Condition> + 'a + Clone {
552        (0..self.len).filter_map(move |index| self.get(index))
553    }
554
555    /// Returns the associated feature substitutions.
556    pub fn features(&self) -> FeatureSubst<'a> {
557        let data = &self.layout.data;
558        let len = data.read_u16(self.feature_subst as usize + 4).unwrap_or(0);
559        FeatureSubst {
560            layout: self.layout,
561            base: self.feature_subst as usize,
562            len,
563        }
564    }
565}
566
567/// Condition for selecting a feature substitution.
568///
569/// <https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#condition-table>
570#[derive(Copy, Clone, Debug)]
571pub struct Condition {
572    /// Index of the axis to which the condition applies.
573    pub axis_index: u16,
574    /// Minimum value that satisfies the condition.
575    pub min_value: NormalizedCoord,
576    /// Maximum value that satisfies the condition.
577    pub max_value: NormalizedCoord,
578}
579
580/// Feature substitution table.
581///
582/// <https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#featuretablesubstitution-table>
583#[derive(Copy, Clone)]
584pub struct FeatureSubst<'a> {
585    layout: &'a Layout<'a>,
586    base: usize,
587    len: u16,
588}
589
590impl<'a> FeatureSubst<'a> {
591    /// Returns the number of feature substitutions.
592    pub fn len(&self) -> u16 {
593        self.len
594    }
595
596    /// Returns true if the collection is empty.
597    pub fn is_empty(&self) -> bool {
598        self.len == 0
599    }
600
601    /// Returns the feature substitution at the specified index.
602    pub fn get(&self, index: u16) -> Option<Feature<'a>> {
603        if index >= self.len() {
604            return None;
605        }
606        let data = &self.layout.data;
607        let subst_base = self.base + 6 + index as usize * 6;
608        let feature_index = data.read_u16(subst_base)?;
609        let offset = data.read_offset32(subst_base + 2, self.base as u32)?;
610        Some(
611            FeatureRecord {
612                tag: Tag(0),
613                index: feature_index,
614                offset,
615            }
616            .materialize(self.layout),
617        )
618    }
619
620    /// Returns an iterator over the feature substitutions.
621    pub fn iter(&'a self) -> impl Iterator<Item = Feature<'a>> + 'a + Clone {
622        (0..self.len).filter_map(move |index| self.get(index))
623    }
624
625    /// Returns the substitution for the feature at the specified index.
626    pub fn find(&self, feature_index: u16) -> Option<Feature<'a>> {
627        for i in 0..self.len() {
628            if let Some(feature) = self.get(i) {
629                if feature.record.index == feature_index {
630                    return Some(feature);
631                }
632                if feature.record.index > feature_index {
633                    return None;
634                }
635            }
636        }
637        None
638    }
639}