1use super::{Feature, FeatureIndex, RecordListItem, VariationIndex};
2use crate::parser::{FromData, LazyArray16, LazyArray32};
3use crate::parser::{Offset, Offset32, Stream};
4use crate::{NormalizedCoordinate, Tag};
5
6/// A [Feature Variations Table](https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#featurevariations-table).
7#[derive(Clone, Copy, Debug)]
8pub struct FeatureVariations<'a> {
9 data: &'a [u8],
10 records: LazyArray32<'a, FeatureVariationRecord>,
11}
12
13impl<'a> FeatureVariations<'a> {
14 pub(crate) fn parse(data: &'a [u8]) -> Option<Self> {
15 let mut s = Stream::new(data);
16 let major_version = s.read::<u16>()?;
17 s.skip::<u16>(); // minor version
18 if major_version != 1 {
19 return None;
20 }
21
22 let count = s.read::<u32>()?;
23 let records = s.read_array32(count)?;
24 Some(Self { data, records })
25 }
26
27 /// Returns a [`VariationIndex`] for variation coordinates.
28 pub fn find_index(&self, coords: &[NormalizedCoordinate]) -> Option<VariationIndex> {
29 for i in 0..self.records.len() {
30 let record = self.records.get(i)?;
31 let offset = record.conditions.to_usize();
32 let set = ConditionSet::parse(self.data.get(offset..)?)?;
33 if set.evaluate(coords) {
34 return Some(i);
35 }
36 }
37 None
38 }
39
40 /// Returns a [`Feature`] at specified indices.
41 pub fn find_substitute(
42 &self,
43 feature_index: FeatureIndex,
44 variation_index: VariationIndex,
45 ) -> Option<Feature<'a>> {
46 let offset = self.records.get(variation_index)?.substitutions.to_usize();
47 let subst = FeatureTableSubstitution::parse(self.data.get(offset..)?)?;
48 subst.find_substitute(feature_index)
49 }
50}
51
52#[derive(Clone, Copy, Debug)]
53struct FeatureVariationRecord {
54 conditions: Offset32,
55 substitutions: Offset32,
56}
57
58impl FromData for FeatureVariationRecord {
59 const SIZE: usize = 8;
60
61 #[inline]
62 fn parse(data: &[u8]) -> Option<Self> {
63 let mut s: Stream<'_> = Stream::new(data);
64 Some(Self {
65 conditions: s.read::<Offset32>()?,
66 substitutions: s.read::<Offset32>()?,
67 })
68 }
69}
70
71#[derive(Clone, Copy, Debug)]
72struct ConditionSet<'a> {
73 data: &'a [u8],
74 conditions: LazyArray16<'a, Offset32>,
75}
76
77impl<'a> ConditionSet<'a> {
78 fn parse(data: &'a [u8]) -> Option<Self> {
79 let mut s: Stream<'_> = Stream::new(data);
80 let count: u16 = s.read::<u16>()?;
81 let conditions: LazyArray16<'_, Offset32> = s.read_array16(count)?;
82 Some(Self { data, conditions })
83 }
84
85 fn evaluate(&self, coords: &[NormalizedCoordinate]) -> bool {
86 self.conditions.into_iter().all(|offset: Offset32| {
87 self.data
88 .get(offset.to_usize()..)
89 .and_then(Condition::parse)
90 .map_or(default:false, |c: Condition| c.evaluate(coords))
91 })
92 }
93}
94
95#[derive(Clone, Copy, Debug)]
96enum Condition {
97 Format1 {
98 axis_index: u16,
99 filter_range_min: i16,
100 filter_range_max: i16,
101 },
102}
103
104impl Condition {
105 fn parse(data: &[u8]) -> Option<Self> {
106 let mut s = Stream::new(data);
107 let format = s.read::<u16>()?;
108 match format {
109 1 => {
110 let axis_index = s.read::<u16>()?;
111 let filter_range_min = s.read::<i16>()?;
112 let filter_range_max = s.read::<i16>()?;
113 Some(Self::Format1 {
114 axis_index,
115 filter_range_min,
116 filter_range_max,
117 })
118 }
119 _ => None,
120 }
121 }
122
123 fn evaluate(&self, coords: &[NormalizedCoordinate]) -> bool {
124 let Self::Format1 {
125 axis_index,
126 filter_range_min,
127 filter_range_max,
128 } = *self;
129 let coord = coords
130 .get(usize::from(axis_index))
131 .map(|c| c.get())
132 .unwrap_or(0);
133 filter_range_min <= coord && coord <= filter_range_max
134 }
135}
136
137#[derive(Clone, Copy, Debug)]
138struct FeatureTableSubstitution<'a> {
139 data: &'a [u8],
140 records: LazyArray16<'a, FeatureTableSubstitutionRecord>,
141}
142
143impl<'a> FeatureTableSubstitution<'a> {
144 fn parse(data: &'a [u8]) -> Option<Self> {
145 let mut s = Stream::new(data);
146 let major_version = s.read::<u16>()?;
147 s.skip::<u16>(); // minor version
148 if major_version != 1 {
149 return None;
150 }
151
152 let count = s.read::<u16>()?;
153 let records = s.read_array16(count)?;
154 Some(Self { data, records })
155 }
156
157 fn find_substitute(&self, feature_index: FeatureIndex) -> Option<Feature<'a>> {
158 for record in self.records {
159 if record.feature_index == feature_index {
160 let offset = record.feature.to_usize();
161 // TODO: set tag
162 return Feature::parse(Tag::from_bytes(b"DFLT"), self.data.get(offset..)?);
163 }
164 }
165 None
166 }
167}
168
169#[derive(Clone, Copy, Debug)]
170struct FeatureTableSubstitutionRecord {
171 feature_index: FeatureIndex,
172 feature: Offset32,
173}
174
175impl FromData for FeatureTableSubstitutionRecord {
176 const SIZE: usize = 6;
177
178 #[inline]
179 fn parse(data: &[u8]) -> Option<Self> {
180 let mut s: Stream<'_> = Stream::new(data);
181 Some(Self {
182 feature_index: s.read::<FeatureIndex>()?,
183 feature: s.read::<Offset32>()?,
184 })
185 }
186}
187