/src/ttf-parser/src/var_store.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Implementation of Item Variation Store |
2 | | //! |
3 | | //! <https://docs.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#item-variation-store> |
4 | | |
5 | | use crate::parser::{FromData, LazyArray16, NumFrom, Stream}; |
6 | | use crate::NormalizedCoordinate; |
7 | | |
8 | | #[derive(Clone, Copy, Debug)] |
9 | | pub(crate) struct ItemVariationStore<'a> { |
10 | | data: &'a [u8], |
11 | | data_offsets: LazyArray16<'a, u32>, |
12 | | pub regions: VariationRegionList<'a>, |
13 | | } |
14 | | |
15 | | impl<'a> Default for ItemVariationStore<'a> { |
16 | | #[inline] |
17 | 3.20k | fn default() -> Self { |
18 | 3.20k | ItemVariationStore { |
19 | 3.20k | data: &[], |
20 | 3.20k | data_offsets: LazyArray16::new(&[]), |
21 | 3.20k | regions: VariationRegionList { |
22 | 3.20k | axis_count: 0, |
23 | 3.20k | regions: LazyArray16::new(&[]), |
24 | 3.20k | }, |
25 | 3.20k | } |
26 | 3.20k | } |
27 | | } |
28 | | |
29 | | impl<'a> ItemVariationStore<'a> { |
30 | | #[inline] |
31 | 3.40k | pub fn parse(mut s: Stream) -> Option<ItemVariationStore> { |
32 | 3.40k | let data = s.tail()?; |
33 | | |
34 | 3.40k | let mut regions_s = s.clone(); |
35 | 3.40k | let format = s.read::<u16>()?; |
36 | 3.38k | if format != 1 { |
37 | 241 | return None; |
38 | 3.14k | } |
39 | | |
40 | 3.14k | let region_list_offset = s.read::<u32>()?; |
41 | 3.12k | let count = s.read::<u16>()?; |
42 | 3.08k | let offsets = s.read_array16::<u32>(count)?; |
43 | | |
44 | 2.17k | let regions = { |
45 | 2.86k | regions_s.advance(usize::num_from(region_list_offset)); |
46 | | // TODO: should be the same as in `fvar` |
47 | 2.86k | let axis_count = regions_s.read::<u16>()?; |
48 | 2.40k | let count = regions_s.read::<u16>()?; |
49 | 2.39k | let total = count.checked_mul(axis_count)?; |
50 | | VariationRegionList { |
51 | 2.35k | axis_count, |
52 | 2.35k | regions: regions_s.read_array16::<RegionAxisCoordinatesRecord>(total)?, |
53 | | } |
54 | | }; |
55 | | |
56 | 2.17k | Some(ItemVariationStore { |
57 | 2.17k | data, |
58 | 2.17k | data_offsets: offsets, |
59 | 2.17k | regions, |
60 | 2.17k | }) |
61 | 3.40k | } |
62 | | |
63 | 124k | pub fn region_indices(&self, index: u16) -> Option<LazyArray16<u16>> { |
64 | | // Offsets in bytes from the start of the item variation store |
65 | | // to each item variation data subtable. |
66 | 124k | let offset = self.data_offsets.get(index)?; |
67 | 122k | let mut s = Stream::new_at(self.data, usize::num_from(offset))?; |
68 | 121k | s.skip::<u16>(); // item_count |
69 | 121k | s.skip::<u16>(); // short_delta_count |
70 | 121k | let count = s.read::<u16>()?; |
71 | 121k | s.read_array16::<u16>(count) |
72 | 124k | } |
73 | | |
74 | 52 | pub fn parse_delta( |
75 | 52 | &self, |
76 | 52 | outer_index: u16, |
77 | 52 | inner_index: u16, |
78 | 52 | coordinates: &[NormalizedCoordinate], |
79 | 52 | ) -> Option<f32> { |
80 | 52 | let offset = self.data_offsets.get(outer_index)?; |
81 | 42 | let mut s = Stream::new_at(self.data, usize::num_from(offset))?; |
82 | 38 | let item_count = s.read::<u16>()?; |
83 | 38 | let word_delta_count = s.read::<u16>()?; |
84 | 38 | let region_index_count = s.read::<u16>()?; |
85 | 38 | let region_indices = s.read_array16::<u16>(region_index_count)?; |
86 | | |
87 | 38 | if inner_index >= item_count { |
88 | 10 | return None; |
89 | 28 | } |
90 | 28 | |
91 | 28 | let has_long_words = (word_delta_count & 0x8000) != 0; |
92 | 28 | let word_delta_count = word_delta_count & 0x7FFF; |
93 | 28 | |
94 | 28 | // From the spec: The length of the data for each row, in bytes, is |
95 | 28 | // regionIndexCount + (wordDeltaCount & WORD_DELTA_COUNT_MASK) |
96 | 28 | // if the LONG_WORDS flag is not set, or 2 x that amount if the flag is set. |
97 | 28 | let mut delta_set_len = word_delta_count + region_index_count; |
98 | 28 | if has_long_words { |
99 | 14 | delta_set_len *= 2; |
100 | 14 | } |
101 | | |
102 | 28 | s.advance(usize::from(inner_index).checked_mul(usize::from(delta_set_len))?); |
103 | | |
104 | 28 | let mut delta = 0.0; |
105 | 28 | let mut i = 0; |
106 | 564k | while i < word_delta_count { |
107 | 564k | let idx = region_indices.get(i)?; |
108 | 564k | let num = if has_long_words { |
109 | | // TODO: use f64? |
110 | 261k | s.read::<i32>()? as f32 |
111 | | } else { |
112 | 303k | f32::from(s.read::<i16>()?) |
113 | | }; |
114 | 564k | delta += num * self.regions.evaluate_region(idx, coordinates); |
115 | 564k | i += 1; |
116 | | } |
117 | | |
118 | 536k | while i < region_index_count { |
119 | 536k | let idx = region_indices.get(i)?; |
120 | 536k | let num = if has_long_words { |
121 | 392k | f32::from(s.read::<i16>()?) |
122 | | } else { |
123 | 143k | f32::from(s.read::<i8>()?) |
124 | | }; |
125 | 536k | delta += num * self.regions.evaluate_region(idx, coordinates); |
126 | 536k | i += 1; |
127 | | } |
128 | | |
129 | 25 | Some(delta) |
130 | 52 | } |
131 | | } |
132 | | |
133 | | #[derive(Clone, Copy, Debug)] |
134 | | pub struct VariationRegionList<'a> { |
135 | | axis_count: u16, |
136 | | regions: LazyArray16<'a, RegionAxisCoordinatesRecord>, |
137 | | } |
138 | | |
139 | | impl<'a> VariationRegionList<'a> { |
140 | | #[inline] |
141 | 1.62M | pub(crate) fn evaluate_region(&self, index: u16, coordinates: &[NormalizedCoordinate]) -> f32 { |
142 | 1.62M | let mut v = 1.0; |
143 | 1.62M | for (i, coord) in coordinates.iter().enumerate() { |
144 | 448k | let region = match self.regions.get(index * self.axis_count + i as u16) { |
145 | 140k | Some(r) => r, |
146 | 308k | None => return 0.0, |
147 | | }; |
148 | | |
149 | 140k | let factor = region.evaluate_axis(coord.get()); |
150 | 140k | if factor == 0.0 { |
151 | 64.7k | return 0.0; |
152 | 75.3k | } |
153 | 75.3k | |
154 | 75.3k | v *= factor; |
155 | | } |
156 | | |
157 | 1.25M | v |
158 | 1.62M | } |
159 | | } |
160 | | |
161 | | #[derive(Clone, Copy, Debug)] |
162 | | struct RegionAxisCoordinatesRecord { |
163 | | start_coord: i16, |
164 | | peak_coord: i16, |
165 | | end_coord: i16, |
166 | | } |
167 | | |
168 | | impl RegionAxisCoordinatesRecord { |
169 | | #[inline] |
170 | 140k | pub fn evaluate_axis(&self, coord: i16) -> f32 { |
171 | 140k | let start = self.start_coord; |
172 | 140k | let peak = self.peak_coord; |
173 | 140k | let end = self.end_coord; |
174 | 140k | |
175 | 140k | if start > peak || peak > end { |
176 | 37.7k | return 1.0; |
177 | 102k | } |
178 | 102k | |
179 | 102k | if start < 0 && end > 0 && peak != 0 { |
180 | 4.40k | return 1.0; |
181 | 97.9k | } |
182 | 97.9k | |
183 | 97.9k | if peak == 0 || coord == peak { |
184 | 2.61k | return 1.0; |
185 | 95.3k | } |
186 | 95.3k | |
187 | 95.3k | if coord <= start || end <= coord { |
188 | 64.7k | return 0.0; |
189 | 30.5k | } |
190 | 30.5k | |
191 | 30.5k | if coord < peak { |
192 | 28.9k | f32::from(coord - start) / f32::from(peak - start) |
193 | | } else { |
194 | 1.58k | f32::from(end - coord) / f32::from(end - peak) |
195 | | } |
196 | 140k | } |
197 | | } |
198 | | |
199 | | impl FromData for RegionAxisCoordinatesRecord { |
200 | | const SIZE: usize = 6; |
201 | | |
202 | | #[inline] |
203 | 140k | fn parse(data: &[u8]) -> Option<Self> { |
204 | 140k | let mut s = Stream::new(data); |
205 | 140k | Some(RegionAxisCoordinatesRecord { |
206 | 140k | start_coord: s.read::<i16>()?, |
207 | 140k | peak_coord: s.read::<i16>()?, |
208 | 140k | end_coord: s.read::<i16>()?, |
209 | | }) |
210 | 140k | } |
211 | | } |