Coverage Report

Created: 2026-08-11 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontations/read-fonts/src/tables/layout.rs
Line
Count
Source
1
//! OpenType Layout common table formats
2
3
#[cfg(feature = "std")]
4
mod closure;
5
6
mod feature;
7
mod lookup_flag;
8
mod script;
9
10
use core::cmp::Ordering;
11
12
pub use lookup_flag::LookupFlag;
13
pub use script::{ScriptTags, SelectedScript, UNICODE_TO_NEW_OPENTYPE_SCRIPT_TAGS};
14
15
use super::variations::DeltaSetIndex;
16
17
#[cfg(feature = "std")]
18
use crate::collections::IntSet;
19
20
#[cfg(feature = "std")]
21
pub(crate) use closure::{
22
    ContextFormat1, ContextFormat2, ContextFormat3, LayoutLookupList, LookupClosure,
23
    LookupClosureCtx, SeqCache, MAX_LOOKUP_VISIT_COUNT, MAX_NESTING_LEVEL,
24
};
25
26
#[cfg(feature = "std")]
27
pub use closure::Intersect;
28
29
#[cfg(test)]
30
mod spec_tests;
31
32
include!("../../generated/generated_layout.rs");
33
34
impl<'a, T: FontRead<'a, Args = ()>> Lookup<'a, T> {
35
0
    pub fn get_subtable(&self, offset: Offset16) -> Result<T, ReadError> {
36
0
        self.resolve_offset(offset)
37
0
    }
38
39
    #[cfg(feature = "experimental_traverse")]
40
    fn traverse_lookup_flag(&self) -> traversal::FieldType<'a> {
41
        self.lookup_flag().to_bits().into()
42
    }
43
}
44
45
/// A trait that abstracts the behaviour of an extension subtable
46
///
47
/// This is necessary because GPOS and GSUB have different concrete types
48
/// for their extension lookups.
49
pub trait ExtensionLookup<'a, T: FontRead<'a, Args = ()>>: FontRead<'a, Args = ()> {
50
    fn extension(&self) -> Result<T, ReadError>;
51
}
52
53
/// an array of subtables, maybe behind extension lookups
54
///
55
/// This is used to implement more ergonomic access to lookup subtables for
56
/// GPOS & GSUB lookup tables.
57
pub enum Subtables<'a, T: FontRead<'a, Args = ()>, Ext: ExtensionLookup<'a, T>> {
58
    Subtable(ArrayOfOffsets<'a, T>),
59
    Extension(ArrayOfOffsets<'a, Ext>),
60
}
61
62
impl<'a, T: FontRead<'a, Args = ()> + 'a, Ext: ExtensionLookup<'a, T> + 'a> Subtables<'a, T, Ext> {
63
    /// create a new subtables array given offsets to non-extension subtables
64
0
    pub(crate) fn new(offsets: &'a [BigEndian<Offset16>], data: FontData<'a>) -> Self {
65
0
        Subtables::Subtable(ArrayOfOffsets::new(offsets, data, ()))
66
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::SequenceContext>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::SequenceContext>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::CursivePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::CursivePosFormat1>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkLigPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkLigPosFormat1>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkBasePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkBasePosFormat1>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkMarkPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkMarkPosFormat1>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::PairPos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::PairPos>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::SinglePos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::SinglePos>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::SingleSubst, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::SingleSubst>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::LigatureSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::LigatureSubstFormat1>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::MultipleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::MultipleSubstFormat1>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::AlternateSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::AlternateSubstFormat1>>>::new
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1>>>::new
67
68
    /// create a new subtables array given offsets to extension subtables
69
0
    pub(crate) fn new_ext(offsets: &'a [BigEndian<Offset16>], data: FontData<'a>) -> Self {
70
0
        Subtables::Extension(ArrayOfOffsets::new(offsets, data, ()))
71
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::SequenceContext>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::SequenceContext>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::CursivePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::CursivePosFormat1>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkLigPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkLigPosFormat1>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkBasePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkBasePosFormat1>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkMarkPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkMarkPosFormat1>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::PairPos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::PairPos>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::SinglePos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::SinglePos>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::SingleSubst, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::SingleSubst>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::LigatureSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::LigatureSubstFormat1>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::MultipleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::MultipleSubstFormat1>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::AlternateSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::AlternateSubstFormat1>>>::new_ext
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1>>>::new_ext
72
73
    /// The number of subtables in this collection
74
0
    pub fn len(&self) -> usize {
75
0
        match self {
76
0
            Subtables::Subtable(inner) => inner.len(),
77
0
            Subtables::Extension(inner) => inner.len(),
78
        }
79
0
    }
80
81
0
    pub fn is_empty(&self) -> bool {
82
0
        self.len() == 0
83
0
    }
84
85
    /// Return the subtable at the given index
86
0
    pub fn get(&self, idx: usize) -> Result<T, ReadError> {
87
0
        match self {
88
0
            Subtables::Subtable(inner) => inner.get(idx),
89
0
            Subtables::Extension(inner) => inner.get(idx).and_then(|ext| ext.extension()),
90
        }
91
0
    }
92
93
    /// Return an iterator over all the subtables in the collection
94
0
    pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
95
0
        let (left, right) = match self {
96
0
            Subtables::Subtable(inner) => (Some(inner.iter()), None),
97
0
            Subtables::Extension(inner) => (
98
0
                None,
99
0
                Some(inner.iter().map(|ext| ext.and_then(|ext| ext.extension()))),
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::SequenceContext>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::SequenceContext>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::SingleSubst, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::SingleSubst>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::LigatureSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::LigatureSubstFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::MultipleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::MultipleSubstFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::AlternateSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::AlternateSubstFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::CursivePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::CursivePosFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkLigPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkLigPosFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkBasePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkBasePosFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkMarkPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkMarkPosFormat1>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::PairPos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::PairPos>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::SinglePos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::SinglePos>>>::iter::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::SequenceContext>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::SequenceContext>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::SingleSubst, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::SingleSubst>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::LigatureSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::LigatureSubstFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::MultipleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::MultipleSubstFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::AlternateSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::AlternateSubstFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::CursivePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::CursivePosFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkLigPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkLigPosFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkBasePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkBasePosFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkMarkPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkMarkPosFormat1>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::PairPos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::PairPos>>>::iter::{closure#0}::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::SinglePos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::SinglePos>>>::iter::{closure#0}::{closure#0}
100
            ),
101
        };
102
0
        left.into_iter()
103
0
            .flatten()
104
0
            .chain(right.into_iter().flatten())
105
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::SequenceContext>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::SequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::SequenceContext>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::layout::ChainedSequenceContext, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::layout::ChainedSequenceContext>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::SingleSubst, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::SingleSubst>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::LigatureSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::LigatureSubstFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::MultipleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::MultipleSubstFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::AlternateSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::AlternateSubstFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1, read_fonts::tables::gsub::ExtensionSubstFormat1<read_fonts::tables::gsub::ReverseChainSingleSubstFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::CursivePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::CursivePosFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkLigPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkLigPosFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkBasePosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkBasePosFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::MarkMarkPosFormat1, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::MarkMarkPosFormat1>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::PairPos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::PairPos>>>::iter
Unexecuted instantiation: <read_fonts::tables::layout::Subtables<read_fonts::tables::gpos::SinglePos, read_fonts::tables::gpos::ExtensionPosFormat1<read_fonts::tables::gpos::SinglePos>>>::iter
106
}
107
108
/// An enum for different possible tables referenced by [Feature::feature_params_offset]
109
pub enum FeatureParams<'a> {
110
    StylisticSet(StylisticSetParams<'a>),
111
    Size(SizeParams<'a>),
112
    CharacterVariant(CharacterVariantParams<'a>),
113
}
114
115
impl ReadArgs for FeatureParams<'_> {
116
    type Args = Tag;
117
}
118
119
impl<'a> FontRead<'a> for FeatureParams<'a> {
120
0
    fn read_with_args(bytes: FontData<'a>, args: Tag) -> Result<FeatureParams<'a>, ReadError> {
121
0
        match args {
122
0
            t if t == Tag::new(b"size") => SizeParams::read(bytes).map(Self::Size),
123
            // to whoever is debugging this dumb bug I wrote: I'm sorry.
124
0
            t if &t.to_raw()[..2] == b"ss" => {
125
0
                StylisticSetParams::read(bytes).map(Self::StylisticSet)
126
            }
127
0
            t if &t.to_raw()[..2] == b"cv" => {
128
0
                CharacterVariantParams::read(bytes).map(Self::CharacterVariant)
129
            }
130
            // NOTE: what even is our error condition here? an offset exists but
131
            // we don't know the tag?
132
0
            _ => Err(ReadError::InvalidFormat(0xdead)),
133
        }
134
0
    }
135
}
136
137
#[cfg(feature = "experimental_traverse")]
138
impl<'a> SomeTable<'a> for FeatureParams<'a> {
139
    fn type_name(&self) -> &str {
140
        match self {
141
            FeatureParams::StylisticSet(table) => table.type_name(),
142
            FeatureParams::Size(table) => table.type_name(),
143
            FeatureParams::CharacterVariant(table) => table.type_name(),
144
        }
145
    }
146
147
    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
148
        match self {
149
            FeatureParams::StylisticSet(table) => table.get_field(idx),
150
            FeatureParams::Size(table) => table.get_field(idx),
151
            FeatureParams::CharacterVariant(table) => table.get_field(idx),
152
        }
153
    }
154
}
155
156
impl FeatureTableSubstitutionRecord {
157
0
    pub fn alternate_feature<'a>(&self, data: FontData<'a>) -> Result<Feature<'a>, ReadError> {
158
0
        self.alternate_feature_offset()
159
0
            .resolve_with_args(data, Tag::new(b"NULL"))
160
0
    }
161
}
162
163
0
fn bit_storage(v: u32) -> u32 {
164
0
    u32::BITS - v.leading_zeros()
165
0
}
166
167
impl<'a> CoverageTable<'a> {
168
0
    pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + 'a {
169
        // all one expression so that we have a single return type
170
0
        let (iter1, iter2) = match self {
171
0
            CoverageTable::Format1(t) => (Some(t.glyph_array().iter().map(|g| g.get())), None),
172
0
            CoverageTable::Format2(t) => {
173
0
                let iter = t.range_records().iter().flat_map(RangeRecord::iter);
174
0
                (None, Some(iter))
175
            }
176
        };
177
178
0
        iter1
179
0
            .into_iter()
180
0
            .flatten()
181
0
            .chain(iter2.into_iter().flatten())
182
0
    }
183
184
    /// If this glyph is in the coverage table, returns its index
185
    #[inline]
186
0
    pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
187
0
        match self {
188
0
            CoverageTable::Format1(sub) => sub.get(gid),
189
0
            CoverageTable::Format2(sub) => sub.get(gid),
190
        }
191
0
    }
192
193
    /// Returns if this table contains at least one glyph in the 'glyphs' set.
194
    #[cfg(feature = "std")]
195
0
    pub fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
196
0
        match self {
197
0
            CoverageTable::Format1(sub) => sub.intersects(glyphs),
198
0
            CoverageTable::Format2(sub) => sub.intersects(glyphs),
199
        }
200
0
    }
201
202
    /// Returns the intersection of this table and input 'glyphs' set.
203
    #[cfg(feature = "std")]
204
0
    pub fn intersect_set(&self, glyphs: &IntSet<GlyphId>) -> IntSet<GlyphId> {
205
0
        match self {
206
0
            CoverageTable::Format1(sub) => sub.intersect_set(glyphs),
207
0
            CoverageTable::Format2(sub) => sub.intersect_set(glyphs),
208
        }
209
0
    }
210
211
    /// Return the number of glyphs in this table
212
0
    pub fn population(&self) -> usize {
213
0
        match self {
214
0
            CoverageTable::Format1(sub) => sub.population(),
215
0
            CoverageTable::Format2(sub) => sub.population(),
216
        }
217
0
    }
218
219
    /// Return the cost of looking up a glyph in this table
220
0
    pub fn cost(&self) -> u32 {
221
0
        match self {
222
0
            CoverageTable::Format1(sub) => sub.cost(),
223
0
            CoverageTable::Format2(sub) => sub.cost(),
224
        }
225
0
    }
226
}
227
228
impl CoverageFormat1<'_> {
229
    /// If this glyph is in the coverage table, returns its index
230
    #[inline]
231
0
    pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
232
0
        let gid16: GlyphId16 = gid.into().try_into().ok()?;
233
0
        let be_glyph: BigEndian<GlyphId16> = gid16.into();
234
0
        self.glyph_array()
235
0
            .binary_search(&be_glyph)
236
0
            .ok()
237
0
            .map(|idx| idx as _)
238
0
    }
239
240
    /// Returns if this table contains at least one glyph in the 'glyphs' set.
241
    #[cfg(feature = "std")]
242
0
    fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
243
0
        let glyph_count = self.glyph_count() as u32;
244
0
        if glyph_count > (glyphs.len() as u32) * self.cost() {
245
0
            glyphs.iter().any(|g| self.get(g).is_some())
246
        } else {
247
0
            self.glyph_array()
248
0
                .iter()
249
0
                .any(|g| glyphs.contains(GlyphId::from(g.get())))
250
        }
251
0
    }
252
253
    /// Returns the intersection of this table and input 'glyphs' set.
254
    #[cfg(feature = "std")]
255
0
    fn intersect_set(&self, glyphs: &IntSet<GlyphId>) -> IntSet<GlyphId> {
256
0
        let glyph_count = self.glyph_count() as u32;
257
0
        if glyph_count > (glyphs.len() as u32) * self.cost() {
258
0
            glyphs
259
0
                .iter()
260
0
                .filter_map(|g| self.get(g).map(|_| g))
261
0
                .collect()
262
        } else {
263
0
            self.glyph_array()
264
0
                .iter()
265
0
                .filter(|g| glyphs.contains(GlyphId::from(g.get())))
266
0
                .map(|g| GlyphId::from(g.get()))
267
0
                .collect()
268
        }
269
0
    }
270
271
    /// Return the number of glyphs in this table
272
0
    pub fn population(&self) -> usize {
273
0
        self.glyph_count() as usize
274
0
    }
275
276
    /// Return the cost of looking up a glyph in this table
277
0
    pub fn cost(&self) -> u32 {
278
0
        bit_storage(self.glyph_count() as u32)
279
0
    }
280
}
281
282
impl CoverageFormat2<'_> {
283
    /// If this glyph is in the coverage table, returns its index
284
    #[inline]
285
0
    pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
286
0
        let gid: GlyphId16 = gid.into().try_into().ok()?;
287
0
        self.range_records()
288
0
            .binary_search_by(|rec| {
289
0
                if rec.end_glyph_id() < gid {
290
0
                    Ordering::Less
291
0
                } else if rec.start_glyph_id() > gid {
292
0
                    Ordering::Greater
293
                } else {
294
0
                    Ordering::Equal
295
                }
296
0
            })
297
0
            .ok()
298
0
            .and_then(|idx| {
299
0
                let rec = &self.range_records()[idx];
300
                // subtract first to avoid u16 overflow (https://github.com/googlefonts/fontations/issues/1887)
301
0
                rec.start_coverage_index()
302
0
                    .checked_add(gid.to_u16() - rec.start_glyph_id().to_u16())
303
0
            })
304
0
    }
305
306
    /// Returns if this table contains at least one glyph in the 'glyphs' set.
307
    #[cfg(feature = "std")]
308
0
    fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
309
0
        let range_count = self.range_count() as u32;
310
0
        if range_count > (glyphs.len() as u32) * self.cost() {
311
0
            glyphs.iter().any(|g| self.get(g).is_some())
312
        } else {
313
0
            self.range_records()
314
0
                .iter()
315
0
                .any(|record| record.intersects(glyphs))
316
        }
317
0
    }
318
319
    /// Returns the intersection of this table and input 'glyphs' set.
320
    #[cfg(feature = "std")]
321
0
    fn intersect_set(&self, glyphs: &IntSet<GlyphId>) -> IntSet<GlyphId> {
322
0
        let range_count = self.range_count() as u32;
323
0
        if range_count > (glyphs.len() as u32) * self.cost() {
324
0
            glyphs
325
0
                .iter()
326
0
                .filter_map(|g| self.get(g).map(|_| g))
327
0
                .collect()
328
        } else {
329
0
            let mut out = IntSet::empty();
330
0
            let mut last = GlyphId16::from(0);
331
0
            for record in self.range_records() {
332
                // break out of loop for overlapping/broken tables
333
0
                let start_glyph = record.start_glyph_id();
334
0
                if start_glyph < last {
335
0
                    break;
336
0
                }
337
0
                let end = record.end_glyph_id();
338
0
                last = end;
339
340
0
                let start = GlyphId::from(start_glyph);
341
0
                if glyphs.contains(start) {
342
0
                    out.insert(start);
343
0
                }
344
345
0
                for g in glyphs.iter_after(start) {
346
0
                    if g.to_u32() > end.to_u32() {
347
0
                        break;
348
0
                    }
349
0
                    out.insert(g);
350
                }
351
            }
352
0
            out
353
        }
354
0
    }
355
356
    /// Return the number of glyphs in this table
357
0
    pub fn population(&self) -> usize {
358
0
        self.range_records()
359
0
            .iter()
360
0
            .fold(0, |acc, record| acc + record.population())
361
0
    }
362
363
    /// Return the cost of looking up a glyph in this table
364
0
    pub fn cost(&self) -> u32 {
365
0
        bit_storage(self.range_count() as u32)
366
0
    }
367
}
368
369
impl RangeRecord {
370
0
    pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
371
0
        (self.start_glyph_id().to_u16()..=self.end_glyph_id().to_u16()).map(GlyphId16::new)
372
0
    }
373
374
    /// Returns if this table contains at least one glyph in the 'glyphs' set.
375
    #[cfg(feature = "std")]
376
0
    pub fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
377
0
        glyphs.intersects_range(
378
0
            GlyphId::from(self.start_glyph_id())..=GlyphId::from(self.end_glyph_id()),
379
        )
380
0
    }
381
382
    /// Return the number of glyphs in this record
383
0
    pub fn population(&self) -> usize {
384
0
        let start = self.start_glyph_id().to_u32() as usize;
385
0
        let end = self.end_glyph_id().to_u32() as usize;
386
0
        if start > end {
387
0
            0
388
        } else {
389
0
            end - start + 1
390
        }
391
0
    }
392
}
393
394
impl DeltaFormat {
395
0
    pub(crate) fn value_count(self, start_size: u16, end_size: u16) -> usize {
396
0
        let range_len = end_size.saturating_add(1).saturating_sub(start_size) as usize;
397
0
        let val_per_word = match self {
398
0
            DeltaFormat::Local2BitDeltas => 8,
399
0
            DeltaFormat::Local4BitDeltas => 4,
400
0
            DeltaFormat::Local8BitDeltas => 2,
401
0
            _ => return 0,
402
        };
403
404
0
        let count = range_len / val_per_word;
405
0
        let extra = (range_len % val_per_word).min(1);
406
0
        count + extra
407
0
    }
408
}
409
410
// we as a 'format' in codegen, and the generic error type for an invalid format
411
// stores the value as an i64, so we need this conversion.
412
impl From<DeltaFormat> for i64 {
413
0
    fn from(value: DeltaFormat) -> Self {
414
0
        value as u16 as _
415
0
    }
416
}
417
418
impl<'a> ClassDefFormat1<'a> {
419
    /// Get the class for this glyph id
420
    #[inline]
421
0
    pub fn get(&self, gid: impl Into<GlyphId>) -> u16 {
422
0
        let Some(idx) = gid
423
0
            .into()
424
0
            .to_u32()
425
0
            .checked_sub(self.start_glyph_id().to_u32())
426
        else {
427
0
            return 0;
428
        };
429
0
        self.class_value_array()
430
0
            .get(idx as usize)
431
0
            .map(|x| x.get())
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat1>::get::<_>::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat1>::get::<font_types::glyph_id::GlyphId16>::{closure#0}
432
0
            .unwrap_or(0)
433
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat1>::get::<_>
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat1>::get::<font_types::glyph_id::GlyphId16>
434
435
    /// Iterate over each glyph and its class.
436
0
    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + 'a {
437
0
        let start = self.start_glyph_id();
438
0
        self.class_value_array()
439
0
            .iter()
440
0
            .enumerate()
441
0
            .map(move |(i, val)| {
442
0
                let gid = start.to_u16().saturating_add(i as u16);
443
0
                (GlyphId16::new(gid), val.get())
444
0
            })
445
0
    }
446
447
    /// Return the number of glyphs explicitly assigned to a class in this table
448
0
    pub fn population(&self) -> usize {
449
0
        self.glyph_count() as usize
450
0
    }
451
452
    /// Return the cost of looking up a glyph in this table
453
0
    pub fn cost(&self) -> u32 {
454
0
        1
455
0
    }
456
457
    /// Returns class values for the intersected glyphs of this table and input 'glyphs' set.
458
    #[cfg(feature = "std")]
459
0
    fn intersect_classes(&self, glyphs: &IntSet<GlyphId>) -> IntSet<u16> {
460
0
        let mut out = IntSet::empty();
461
0
        if glyphs.is_empty() {
462
0
            return out;
463
0
        }
464
465
0
        let start_glyph = self.start_glyph_id().to_u32();
466
0
        let class_values = self.class_value_array();
467
0
        if class_values.is_empty() {
468
0
            out.insert(0);
469
0
            return out;
470
0
        }
471
0
        let end_glyph = start_glyph + class_values.len() as u32 - 1;
472
0
        if glyphs.first().unwrap().to_u32() < start_glyph
473
0
            || glyphs.last().unwrap().to_u32() > end_glyph
474
0
        {
475
0
            out.insert(0);
476
0
        }
477
478
0
        if glyphs.contains(GlyphId::from(start_glyph)) {
479
0
            let Some(start_glyph_class) = class_values.first() else {
480
0
                return out;
481
            };
482
0
            out.insert(start_glyph_class.get());
483
0
        }
484
485
0
        for g in glyphs.iter_after(GlyphId::from(start_glyph)) {
486
0
            let g = g.to_u32();
487
0
            if g > end_glyph {
488
0
                break;
489
0
            }
490
491
0
            let idx = g - start_glyph;
492
0
            let Some(class) = class_values.get(idx as usize) else {
493
0
                break;
494
            };
495
0
            out.insert(class.get());
496
        }
497
0
        out
498
0
    }
499
500
    /// Returns intersected glyphs of this table and input 'glyphs' set that are assigned to input class value.
501
    #[cfg(feature = "std")]
502
0
    fn intersected_class_glyphs(&self, glyphs: &IntSet<GlyphId>, class: u16) -> IntSet<GlyphId> {
503
0
        let mut out = IntSet::empty();
504
0
        if glyphs.is_empty() {
505
0
            return out;
506
0
        }
507
508
0
        let start_glyph = self.start_glyph_id().to_u32();
509
0
        let glyph_count = self.glyph_count();
510
0
        let end_glyph = start_glyph + glyph_count as u32 - 1;
511
0
        if class == 0 {
512
0
            let first = glyphs.first().unwrap();
513
0
            if first.to_u32() < start_glyph {
514
0
                out.extend(glyphs.range(first..GlyphId::from(start_glyph)));
515
0
            }
516
517
0
            let last = glyphs.last().unwrap();
518
0
            if last.to_u32() > end_glyph {
519
0
                out.extend(glyphs.range(GlyphId::from(end_glyph + 1)..=last));
520
0
            }
521
0
            return out;
522
0
        }
523
524
0
        let class_values = self.class_value_array();
525
0
        for g in glyphs.range(GlyphId::from(start_glyph)..=GlyphId::from(end_glyph)) {
526
0
            let idx = g.to_u32() - start_glyph;
527
0
            let Some(c) = class_values.get(idx as usize) else {
528
0
                break;
529
            };
530
0
            if c.get() == class {
531
0
                out.insert(g);
532
0
            }
533
        }
534
0
        out
535
0
    }
536
537
    /// Checks whether any glyph in the given glyphs set intersects with this table and is assigned to the specified class value.
538
    #[cfg(feature = "std")]
539
0
    fn intersects_class_glyphs(&self, glyphs: &IntSet<GlyphId>, class: u16) -> bool {
540
0
        if glyphs.is_empty() {
541
0
            return false;
542
0
        }
543
544
0
        let start_glyph = self.start_glyph_id().to_u32();
545
0
        let end_glyph = start_glyph + self.glyph_count() as u32 - 1;
546
0
        if class == 0 {
547
0
            let first = glyphs.first().unwrap();
548
0
            if first.to_u32() < start_glyph {
549
0
                return true;
550
0
            }
551
552
0
            let last = glyphs.last().unwrap();
553
0
            if last.to_u32() > end_glyph {
554
0
                return true;
555
0
            }
556
0
        }
557
558
0
        let class_values = self.class_value_array();
559
0
        for g in glyphs.range(GlyphId::from(start_glyph)..=GlyphId::from(end_glyph)) {
560
0
            let idx = g.to_u32() - start_glyph;
561
0
            let Some(c) = class_values.get(idx as usize) else {
562
0
                return false;
563
            };
564
0
            if c.get() == class {
565
0
                return true;
566
0
            }
567
        }
568
0
        false
569
0
    }
570
}
571
572
impl<'a> ClassDefFormat2<'a> {
573
    /// Get the class for this glyph id
574
    #[inline]
575
0
    pub fn get(&self, gid: impl Into<GlyphId>) -> u16 {
576
0
        let gid = gid.into().to_u32();
577
0
        let records = self.class_range_records();
578
0
        let ix = match records.binary_search_by(|rec| rec.start_glyph_id().to_u32().cmp(&gid)) {
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat2>::get::<font_types::glyph_id::GlyphId>::{closure#0}
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat2>::get::<font_types::glyph_id::GlyphId16>::{closure#0}
579
0
            Ok(ix) => ix,
580
0
            Err(ix) => ix.saturating_sub(1),
581
        };
582
0
        if let Some(record) = records.get(ix) {
583
0
            if (record.start_glyph_id().to_u32()..=record.end_glyph_id().to_u32()).contains(&gid) {
584
0
                return record.class();
585
0
            }
586
0
        }
587
0
        0
588
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat2>::get::<font_types::glyph_id::GlyphId>
Unexecuted instantiation: <read_fonts::tables::layout::ClassDefFormat2>::get::<font_types::glyph_id::GlyphId16>
589
590
    /// Iterate over each glyph and its class.
591
0
    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + 'a {
592
0
        self.class_range_records().iter().flat_map(|range| {
593
0
            let start = range.start_glyph_id().to_u16();
594
0
            let end = range.end_glyph_id().to_u16();
595
0
            (start..=end).map(|gid| (GlyphId16::new(gid), range.class()))
596
0
        })
597
0
    }
598
599
    /// Return the number of glyphs explicitly assigned to a class in this table
600
0
    pub fn population(&self) -> usize {
601
0
        self.class_range_records()
602
0
            .iter()
603
0
            .fold(0, |acc, record| acc + record.population())
604
0
    }
605
606
    /// Return the cost of looking up a glyph in this table
607
0
    pub fn cost(&self) -> u32 {
608
0
        bit_storage(self.class_range_count() as u32)
609
0
    }
610
611
    /// Returns class values for the intersected glyphs of this table and input 'glyphs' set.
612
    #[cfg(feature = "std")]
613
0
    fn intersect_classes(&self, glyphs: &IntSet<GlyphId>) -> IntSet<u16> {
614
0
        let mut out = IntSet::empty();
615
0
        if glyphs.is_empty() {
616
0
            return out;
617
0
        }
618
619
0
        let range_records = self.class_range_records();
620
0
        let Some(first_record) = range_records.first() else {
621
0
            out.insert(0);
622
0
            return out;
623
        };
624
625
0
        if glyphs.first().unwrap() < first_record.start_glyph_id() {
626
0
            out.insert(0);
627
0
        } else {
628
0
            let mut glyph = GlyphId::from(first_record.end_glyph_id());
629
0
            for record in range_records.iter().skip(1) {
630
0
                let Some(g) = glyphs.iter_after(glyph).next() else {
631
0
                    break;
632
                };
633
634
0
                if g < record.start_glyph_id() {
635
0
                    out.insert(0);
636
0
                    break;
637
0
                }
638
0
                glyph = GlyphId::from(record.end_glyph_id());
639
            }
640
0
            if glyphs.iter_after(glyph).next().is_some() {
641
0
                out.insert(0);
642
0
            }
643
        }
644
645
0
        let num_ranges = self.class_range_count();
646
0
        if num_ranges as u64 > glyphs.len() * self.cost() as u64 {
647
0
            for g in glyphs.iter() {
648
0
                let class = self.get(g);
649
0
                if class != 0 {
650
0
                    out.insert(class);
651
0
                }
652
            }
653
        } else {
654
0
            for record in range_records {
655
0
                if glyphs.intersects_range(
656
0
                    GlyphId::from(record.start_glyph_id())..=GlyphId::from(record.end_glyph_id()),
657
0
                ) {
658
0
                    out.insert(record.class());
659
0
                }
660
            }
661
        }
662
0
        out
663
0
    }
664
665
    /// Returns intersected glyphs of this table and input 'glyphs' set that are assgiend to input class value.
666
    #[cfg(feature = "std")]
667
0
    fn intersected_class_glyphs(&self, glyphs: &IntSet<GlyphId>, class: u16) -> IntSet<GlyphId> {
668
0
        let mut out = IntSet::empty();
669
0
        if glyphs.is_empty() {
670
0
            return out;
671
0
        }
672
673
0
        let first = glyphs.first().unwrap().to_u32();
674
0
        let last = glyphs.last().unwrap().to_u32();
675
0
        if class == 0 {
676
0
            let mut start = first;
677
0
            for range in self.class_range_records() {
678
0
                let range_start = range.start_glyph_id().to_u32();
679
0
                if start < range_start {
680
0
                    out.extend(glyphs.range(GlyphId::from(start)..GlyphId::from(range_start)));
681
0
                }
682
683
0
                let range_end = range.end_glyph_id().to_u32();
684
0
                if range_end >= last {
685
0
                    break;
686
0
                }
687
0
                start = range_end + 1;
688
            }
689
690
0
            if start <= last {
691
0
                out.extend(glyphs.range(GlyphId::from(start)..=GlyphId::from(last)));
692
0
            }
693
0
            return out;
694
0
        }
695
696
0
        let num_ranges = self.class_range_count();
697
0
        if num_ranges as u64 > glyphs.len() * self.cost() as u64 {
698
0
            for g in glyphs.iter() {
699
0
                let c = self.get(g);
700
0
                if c == class {
701
0
                    out.insert(g);
702
0
                }
703
            }
704
        } else {
705
0
            for range in self.class_range_records() {
706
0
                let range_start = range.start_glyph_id().to_u32();
707
0
                let range_end = range.end_glyph_id().to_u32();
708
0
                if range_start > last {
709
0
                    break;
710
0
                }
711
0
                if range.class() != class || range.end_glyph_id().to_u32() < first {
712
0
                    continue;
713
0
                }
714
0
                out.extend(glyphs.range(GlyphId::from(range_start)..=GlyphId::from(range_end)));
715
            }
716
        }
717
0
        out
718
0
    }
719
720
    /// Checks whether any glyph in the given glyphs set intersects with this table and is assigned to the specified class value.
721
    #[cfg(feature = "std")]
722
0
    fn intersects_class_glyphs(&self, glyphs: &IntSet<GlyphId>, class: u16) -> bool {
723
0
        if glyphs.is_empty() {
724
0
            return false;
725
0
        }
726
727
0
        let first = glyphs.first().unwrap().to_u32();
728
0
        if class == 0 {
729
0
            let mut last_end = first;
730
0
            for (i, range) in self.class_range_records().iter().enumerate() {
731
0
                let range_start = range.start_glyph_id().to_u32();
732
0
                let range_end = range.end_glyph_id().to_u32();
733
0
                if i == 0 {
734
0
                    if first < range_start {
735
0
                        return true;
736
0
                    }
737
0
                    last_end = range_end;
738
0
                    continue;
739
0
                }
740
741
0
                if range_start == last_end + 1 {
742
0
                    last_end = range_end;
743
0
                    continue;
744
0
                }
745
746
0
                if glyphs
747
0
                    .intersects_range(GlyphId::from(last_end + 1)..=GlyphId::from(range_start - 1))
748
                {
749
0
                    return true;
750
0
                };
751
0
                last_end = range_end + 1;
752
            }
753
0
            if glyphs
754
0
                .iter_after(GlyphId::from(last_end + 1))
755
0
                .next()
756
0
                .is_some()
757
            {
758
0
                return true;
759
0
            }
760
0
        }
761
762
0
        let num_ranges = self.class_range_count();
763
0
        if num_ranges as u64 > glyphs.len() * self.cost() as u64 {
764
0
            for g in glyphs.iter() {
765
0
                let c = self.get(g);
766
0
                if c == class {
767
0
                    return true;
768
0
                }
769
            }
770
        } else {
771
0
            let last = glyphs.last().unwrap().to_u32();
772
0
            for range in self.class_range_records() {
773
0
                let range_start = range.start_glyph_id().to_u32();
774
0
                let range_end = range.end_glyph_id().to_u32();
775
0
                if range_start > last {
776
0
                    break;
777
0
                }
778
0
                if range_end < first {
779
0
                    continue;
780
0
                }
781
0
                if range.class() == class
782
0
                    && glyphs
783
0
                        .intersects_range(GlyphId::from(range_start)..=GlyphId::from(range_end))
784
                {
785
0
                    return true;
786
0
                }
787
            }
788
        }
789
0
        false
790
0
    }
791
}
792
793
impl ClassRangeRecord {
794
    /// Return the number of glyphs explicitly assigned to a class in this table
795
0
    pub fn population(&self) -> usize {
796
0
        let start = self.start_glyph_id().to_u32() as usize;
797
0
        let end = self.end_glyph_id().to_u32() as usize;
798
0
        if start > end {
799
0
            0
800
        } else {
801
0
            end - start + 1
802
        }
803
0
    }
804
}
805
806
impl ClassDef<'_> {
807
    /// Get the class for this glyph id
808
    #[inline]
809
0
    pub fn get(&self, gid: impl Into<GlyphId>) -> u16 {
810
0
        match self {
811
0
            ClassDef::Format1(table) => table.get(gid),
812
0
            ClassDef::Format2(table) => table.get(gid),
813
        }
814
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::ClassDef>::get::<_>
Unexecuted instantiation: <read_fonts::tables::layout::ClassDef>::get::<font_types::glyph_id::GlyphId16>
815
816
    /// Iterate over each glyph and its class.
817
    ///
818
    /// This will not include class 0 unless it has been explicitly assigned.
819
0
    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
820
0
        let (one, two) = match self {
821
0
            ClassDef::Format1(inner) => (Some(inner.iter()), None),
822
0
            ClassDef::Format2(inner) => (None, Some(inner.iter())),
823
        };
824
0
        one.into_iter().flatten().chain(two.into_iter().flatten())
825
0
    }
826
827
    /// Return the number of glyphs explicitly assigned to a class in this table
828
0
    pub fn population(&self) -> usize {
829
0
        match self {
830
0
            ClassDef::Format1(table) => table.population(),
831
0
            ClassDef::Format2(table) => table.population(),
832
        }
833
0
    }
834
835
    /// Return the cost of looking up a glyph in this table
836
0
    pub fn cost(&self) -> u32 {
837
0
        match self {
838
0
            ClassDef::Format1(sub) => sub.cost(),
839
0
            ClassDef::Format2(sub) => sub.cost(),
840
        }
841
0
    }
842
843
    /// Returns class values for the intersected glyphs of this table and input 'glyphs' set.
844
    #[cfg(feature = "std")]
845
0
    pub fn intersect_classes(&self, glyphs: &IntSet<GlyphId>) -> IntSet<u16> {
846
0
        match self {
847
0
            ClassDef::Format1(table) => table.intersect_classes(glyphs),
848
0
            ClassDef::Format2(table) => table.intersect_classes(glyphs),
849
        }
850
0
    }
851
852
    /// Returns intersected glyphs of this table and input 'glyphs' set that are assgiend to input class value.
853
    #[cfg(feature = "std")]
854
0
    pub fn intersected_class_glyphs(
855
0
        &self,
856
0
        glyphs: &IntSet<GlyphId>,
857
0
        class: u16,
858
0
    ) -> IntSet<GlyphId> {
859
0
        match self {
860
0
            ClassDef::Format1(table) => table.intersected_class_glyphs(glyphs, class),
861
0
            ClassDef::Format2(table) => table.intersected_class_glyphs(glyphs, class),
862
        }
863
0
    }
864
865
    /// Checks whether any glyph in the given glyphs set intersects with this table and is assigned to the specified class value.
866
    #[cfg(feature = "std")]
867
0
    pub fn intersects_class_glyphs(&self, glyphs: &IntSet<GlyphId>, class: u16) -> bool {
868
0
        match self {
869
0
            ClassDef::Format1(table) => table.intersects_class_glyphs(glyphs, class),
870
0
            ClassDef::Format2(table) => table.intersects_class_glyphs(glyphs, class),
871
        }
872
0
    }
873
}
874
875
impl<'a> Device<'a> {
876
    /// Iterate over the decoded values for this device
877
0
    pub fn iter(&self) -> impl Iterator<Item = i8> + 'a {
878
0
        let format = self.delta_format();
879
0
        let mut n = self
880
0
            .end_size()
881
0
            .checked_sub(self.start_size())
882
0
            .map(|x| x as usize + 1)
883
0
            .unwrap_or(0);
884
0
        let deltas_per_word = match format {
885
0
            DeltaFormat::Local2BitDeltas => 8,
886
0
            DeltaFormat::Local4BitDeltas => 4,
887
0
            DeltaFormat::Local8BitDeltas => 2,
888
0
            _ => 0,
889
        };
890
891
0
        self.delta_value().iter().flat_map(move |val| {
892
0
            let iter = iter_packed_values(val.get(), format, n);
893
0
            n = n.saturating_sub(deltas_per_word);
894
0
            iter
895
0
        })
896
0
    }
897
}
898
899
0
fn iter_packed_values(raw: u16, format: DeltaFormat, n: usize) -> impl Iterator<Item = i8> {
900
0
    let mut decoded = [None; 8];
901
0
    let (mask, sign_mask, bits) = match format {
902
0
        DeltaFormat::Local2BitDeltas => (0b11, 0b10, 2usize),
903
0
        DeltaFormat::Local4BitDeltas => (0b1111, 0b1000, 4),
904
0
        DeltaFormat::Local8BitDeltas => (0b1111_1111, 0b1000_0000, 8),
905
0
        _ => (0, 0, 0),
906
    };
907
908
0
    let max_per_word = 16 / bits;
909
    #[allow(clippy::needless_range_loop)] // enumerate() feels weird here
910
0
    for i in 0..n.min(max_per_word) {
911
0
        let mask = mask << ((16 - bits) - i * bits);
912
0
        let val = (raw & mask) >> ((16 - bits) - i * bits);
913
0
        let sign = val & sign_mask != 0;
914
915
0
        let val = if sign {
916
            // it is 2023 and I am googling to remember how twos compliment works
917
0
            -((((!val) & mask) + 1) as i8)
918
        } else {
919
0
            val as i8
920
        };
921
0
        decoded[i] = Some(val)
922
    }
923
0
    decoded.into_iter().flatten()
924
0
}
925
926
impl From<VariationIndex<'_>> for DeltaSetIndex {
927
0
    fn from(src: VariationIndex) -> DeltaSetIndex {
928
0
        DeltaSetIndex {
929
0
            outer: src.delta_set_outer_index(),
930
0
            inner: src.delta_set_inner_index(),
931
0
        }
932
0
    }
933
}
934
935
/// Combination of a tag and a child table.
936
///
937
/// Used in script and feature lists where a data structure has an array
938
/// of records with each containing a tag and an offset to a table. This
939
/// allows us to provide convenience methods that return both values.
940
#[derive(Clone)]
941
pub struct TaggedElement<T> {
942
    pub tag: Tag,
943
    pub element: T,
944
}
945
946
impl<T> TaggedElement<T> {
947
0
    pub fn new(tag: Tag, element: T) -> Self {
948
0
        Self { tag, element }
949
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::TaggedElement<read_fonts::tables::layout::Script>>::new
Unexecuted instantiation: <read_fonts::tables::layout::TaggedElement<read_fonts::tables::layout::Feature>>::new
Unexecuted instantiation: <read_fonts::tables::layout::TaggedElement<read_fonts::tables::layout::LangSys>>::new
950
}
951
952
impl<T> std::ops::Deref for TaggedElement<T> {
953
    type Target = T;
954
955
0
    fn deref(&self) -> &Self::Target {
956
0
        &self.element
957
0
    }
Unexecuted instantiation: <read_fonts::tables::layout::TaggedElement<read_fonts::tables::layout::Script> as core::ops::deref::Deref>::deref
Unexecuted instantiation: <read_fonts::tables::layout::TaggedElement<_> as core::ops::deref::Deref>::deref
958
}
959
960
#[cfg(test)]
961
mod tests {
962
    use super::*;
963
964
    #[test]
965
    fn coverage_get_format1() {
966
        // manually generated, corresponding to the glyphs (1, 7, 13, 27, 44);
967
        const COV1_DATA: FontData = FontData::new(&[0, 1, 0, 5, 0, 1, 0, 7, 0, 13, 0, 27, 0, 44]);
968
969
        let coverage = CoverageFormat1::read(COV1_DATA).unwrap();
970
        assert_eq!(coverage.get(GlyphId::new(1)), Some(0));
971
        assert_eq!(coverage.get(GlyphId::new(2)), None);
972
        assert_eq!(coverage.get(GlyphId::new(7)), Some(1));
973
        assert_eq!(coverage.get(GlyphId::new(27)), Some(3));
974
        assert_eq!(coverage.get(GlyphId::new(45)), None);
975
    }
976
977
    #[test]
978
    fn coverage_get_format2() {
979
        // manually generated, corresponding to glyphs (5..10) and (30..40).
980
        const COV2_DATA: FontData =
981
            FontData::new(&[0, 2, 0, 2, 0, 5, 0, 9, 0, 0, 0, 30, 0, 39, 0, 5]);
982
        let coverage = CoverageFormat2::read(COV2_DATA).unwrap();
983
        assert_eq!(coverage.get(GlyphId::new(2)), None);
984
        assert_eq!(coverage.get(GlyphId::new(7)), Some(2));
985
        assert_eq!(coverage.get(GlyphId::new(9)), Some(4));
986
        assert_eq!(coverage.get(GlyphId::new(10)), None);
987
        assert_eq!(coverage.get(GlyphId::new(32)), Some(7));
988
        assert_eq!(coverage.get(GlyphId::new(39)), Some(14));
989
        assert_eq!(coverage.get(GlyphId::new(40)), None);
990
    }
991
992
    // <https://github.com/googlefonts/fontations/issues/1887>
993
    #[test]
994
    fn coverage_get_format2_no_u16_overflow() {
995
        // A single range covering glyphs 40000..=40010 with a high
996
        // start_coverage_index, as occurs in large CJK fonts, and which
997
        // was causing an overflow.
998
        const COV2_DATA: FontData =
999
            FontData::new(&[0, 2, 0, 1, 0x9c, 0x40, 0x9c, 0x4a, 0x9c, 0x40]);
1000
        let coverage = CoverageFormat2::read(COV2_DATA).unwrap();
1001
        assert_eq!(coverage.get(GlyphId::new(40000)), Some(40000));
1002
        assert_eq!(coverage.get(GlyphId::new(40005)), Some(40005));
1003
        assert_eq!(coverage.get(GlyphId::new(40010)), Some(40010));
1004
        assert_eq!(coverage.get(GlyphId::new(40011)), None);
1005
    }
1006
1007
    #[test]
1008
    fn coverage_get_format2_rejects_overflowing_coverage_index() {
1009
        // The start_coverage_index plus offset to glyph 2 would overflow u16.
1010
        const COV2_DATA: FontData = FontData::new(&[0, 2, 0, 1, 0, 1, 0, 2, 0xff, 0xff]);
1011
        let coverage = CoverageFormat2::read(COV2_DATA).unwrap();
1012
        assert_eq!(coverage.get(GlyphId::new(1)), Some(u16::MAX));
1013
        assert_eq!(coverage.get(GlyphId::new(2)), None);
1014
    }
1015
1016
    #[test]
1017
    fn classdef_get_format2() {
1018
        let classdef = ClassDef::read(FontData::new(
1019
            font_test_data::gdef::MARKATTACHCLASSDEF_TABLE,
1020
        ))
1021
        .unwrap();
1022
        assert!(matches!(classdef, ClassDef::Format2(..)));
1023
        let gid_class_pairs = [
1024
            (616, 1),
1025
            (617, 1),
1026
            (618, 1),
1027
            (624, 1),
1028
            (625, 1),
1029
            (626, 1),
1030
            (652, 2),
1031
            (653, 2),
1032
            (654, 2),
1033
            (655, 2),
1034
            (661, 2),
1035
        ];
1036
        for (gid, class) in gid_class_pairs {
1037
            assert_eq!(classdef.get(GlyphId16::new(gid)), class);
1038
        }
1039
        for (gid, class) in classdef.iter() {
1040
            assert_eq!(classdef.get(gid), class);
1041
        }
1042
    }
1043
1044
    #[test]
1045
    fn classdef_format1_short_read_no_panic() {
1046
        // glyph_count is 5, but only one class value is present.
1047
        let classdef = ClassDefFormat1::read(FontData::new(&[0, 1, 0, 10, 0, 5, 0, 1])).unwrap();
1048
        let glyphs: IntSet<GlyphId> = [GlyphId::new(10), GlyphId::new(11), GlyphId::new(14)]
1049
            .into_iter()
1050
            .collect();
1051
1052
        assert_eq!(classdef.get(GlyphId::new(10)), 0);
1053
        assert_eq!(classdef.get(GlyphId::new(11)), 0);
1054
        assert!(!classdef.intersects_class_glyphs(&glyphs, 2));
1055
1056
        let class_ones = classdef.intersected_class_glyphs(&glyphs, 1);
1057
        assert!(class_ones.is_empty());
1058
    }
1059
1060
    #[test]
1061
    fn delta_decode() {
1062
        // these examples come from the spec
1063
        assert_eq!(
1064
            iter_packed_values(0x123f, DeltaFormat::Local4BitDeltas, 4).collect::<Vec<_>>(),
1065
            &[1, 2, 3, -1]
1066
        );
1067
1068
        assert_eq!(
1069
            iter_packed_values(0x5540, DeltaFormat::Local2BitDeltas, 5).collect::<Vec<_>>(),
1070
            &[1, 1, 1, 1, 1]
1071
        );
1072
    }
1073
1074
    #[test]
1075
    fn delta_decode_all() {
1076
        // manually generated with write-fonts
1077
        let bytes: &[u8] = &[0, 7, 0, 13, 0, 3, 1, 244, 30, 245, 101, 8, 42, 0];
1078
        let device = Device::read(bytes.into()).unwrap();
1079
        assert_eq!(
1080
            device.iter().collect::<Vec<_>>(),
1081
            &[1i8, -12, 30, -11, 101, 8, 42]
1082
        );
1083
    }
1084
1085
    #[test]
1086
    fn device_decode_does_not_overflow() {
1087
        // manually generated with write-fonts
1088
        let bytes: &[u8] = &[0, 0xA, 0, 1, 0, 1];
1089
        // Don't panic with overflow
1090
        Device::read(bytes.into()).unwrap().iter().count();
1091
    }
1092
1093
    #[test]
1094
    fn bit_storage_tests() {
1095
        assert_eq!(bit_storage(0), 0);
1096
        assert_eq!(bit_storage(1), 1);
1097
        assert_eq!(bit_storage(2), 2);
1098
        assert_eq!(bit_storage(4), 3);
1099
        assert_eq!(bit_storage(9), 4);
1100
        assert_eq!(bit_storage(0x123), 9);
1101
        assert_eq!(bit_storage(0x1234), 13);
1102
        assert_eq!(bit_storage(0xffff), 16);
1103
        assert_eq!(bit_storage(0xffff_ffff), 32);
1104
    }
1105
1106
    #[test]
1107
    fn default_coverage() {
1108
        let coverage = CoverageTable::default();
1109
        assert_eq!(coverage.iter().count(), 0)
1110
    }
1111
1112
    #[test]
1113
    fn default_classdef() {
1114
        let classdef = ClassDef::default();
1115
        assert_eq!(classdef.population(), 0);
1116
        assert_eq!(classdef.iter().count(), 0);
1117
    }
1118
}