/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 | | } |