/src/fontations/write-fonts/src/tables/variations.rs
Line | Count | Source |
1 | | //! OpenType variations common table formats |
2 | | |
3 | | include!("../../generated/generated_variations.rs"); |
4 | | |
5 | | pub use read_fonts::tables::variations::{DeltaRunType, TupleIndex, TupleVariationCount}; |
6 | | |
7 | | pub mod common_builder; |
8 | | pub mod ivs_builder; |
9 | | pub mod mivs_builder; |
10 | | |
11 | | impl TupleVariationHeader { |
12 | 0 | pub fn new( |
13 | 0 | variation_data_size: u16, |
14 | 0 | shared_tuple_idx: Option<u16>, |
15 | 0 | peak_tuple: Option<Tuple>, |
16 | 0 | intermediate_region: Option<(Tuple, Tuple)>, |
17 | 0 | has_private_points: bool, |
18 | 0 | ) -> Self { |
19 | 0 | assert!( |
20 | 0 | shared_tuple_idx.is_some() != peak_tuple.is_some(), |
21 | 0 | "one and only one of peak_tuple or shared_tuple_idx must be present" |
22 | | ); |
23 | 0 | let mut idx = shared_tuple_idx.unwrap_or_default(); |
24 | 0 | if peak_tuple.is_some() { |
25 | 0 | idx |= TupleIndex::EMBEDDED_PEAK_TUPLE; |
26 | 0 | } |
27 | 0 | if intermediate_region.is_some() { |
28 | 0 | idx |= TupleIndex::INTERMEDIATE_REGION; |
29 | 0 | } |
30 | 0 | if has_private_points { |
31 | 0 | idx |= TupleIndex::PRIVATE_POINT_NUMBERS; |
32 | 0 | } |
33 | 0 | let (intermediate_start_tuple, intermediate_end_tuple) = intermediate_region |
34 | 0 | .map(|(start, end)| (start.values, end.values)) |
35 | 0 | .unwrap_or_default(); |
36 | | |
37 | | TupleVariationHeader { |
38 | 0 | variation_data_size, |
39 | 0 | tuple_index: TupleIndex::from_bits(idx), |
40 | 0 | peak_tuple: peak_tuple.map(|tup| tup.values).unwrap_or_default(), |
41 | 0 | intermediate_start_tuple, |
42 | 0 | intermediate_end_tuple, |
43 | | } |
44 | 0 | } |
45 | | |
46 | | /// Return the number of bytes required to encode this header |
47 | 0 | pub fn compute_size(&self) -> u16 { |
48 | 0 | let len: usize = 2 + 2 // variationDataSize, tupleIndex |
49 | 0 | + self.peak_tuple.len() * F2Dot14::RAW_BYTE_LEN |
50 | 0 | + self.intermediate_start_tuple.len() * F2Dot14::RAW_BYTE_LEN |
51 | 0 | + self.intermediate_end_tuple.len() * F2Dot14::RAW_BYTE_LEN; |
52 | 0 | len.try_into().unwrap() |
53 | 0 | } |
54 | | } |
55 | | |
56 | | /// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-point-numbers> |
57 | | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] |
58 | | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
59 | | pub enum PackedPointNumbers { |
60 | | /// Contains deltas for all point numbers |
61 | | #[default] |
62 | | All, |
63 | | /// Contains deltas only for these specific point numbers |
64 | | Some(Vec<u16>), |
65 | | } |
66 | | |
67 | | /// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-deltas> |
68 | | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] |
69 | | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
70 | | pub struct PackedDeltas { |
71 | | deltas: Vec<i32>, |
72 | | } |
73 | | |
74 | | impl Validate for PackedDeltas { |
75 | 0 | fn validate_impl(&self, _ctx: &mut ValidationCtx) {} |
76 | | } |
77 | | |
78 | | impl FontWrite for PackedDeltas { |
79 | 0 | fn write_into(&self, writer: &mut TableWriter) { |
80 | 0 | for run in self.iter_runs() { |
81 | 0 | run.write_into(writer) |
82 | | } |
83 | 0 | } |
84 | | } |
85 | | |
86 | | impl PackedDeltas { |
87 | | /// Construct a `PackedDeltas` from a vector of raw delta values. |
88 | 0 | pub fn new(deltas: Vec<i32>) -> Self { |
89 | 0 | Self { deltas } |
90 | 0 | } |
91 | | |
92 | | /// Compute the number of bytes required to encode these deltas |
93 | 0 | pub(crate) fn compute_size(&self) -> u16 { |
94 | 0 | self.iter_runs().fold(0u16, |acc, run| { |
95 | 0 | acc.checked_add(run.compute_size()).unwrap() |
96 | 0 | }) |
97 | 0 | } |
98 | | |
99 | 0 | fn iter_runs(&self) -> impl Iterator<Item = PackedDeltaRun<'_>> { |
100 | | // 6 bits for length per https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-deltas |
101 | | const MAX_POINTS_PER_RUN: usize = 64; |
102 | | |
103 | | // preferred run type for this value |
104 | 0 | fn preferred_run_type(v: i32) -> DeltaRunType { |
105 | 0 | match v { |
106 | 0 | 0 => DeltaRunType::Zero, |
107 | 0 | _ if v > i16::MAX as i32 || v < i16::MIN as i32 => DeltaRunType::I32, |
108 | 0 | _ if v > i8::MAX as i32 || v < i8::MIN as i32 => DeltaRunType::I16, |
109 | 0 | _ => DeltaRunType::I8, |
110 | | } |
111 | 0 | } |
112 | | |
113 | 0 | fn count_leading_zeros(slice: &[i32]) -> u8 { |
114 | 0 | slice |
115 | 0 | .iter() |
116 | 0 | .take(MAX_POINTS_PER_RUN) |
117 | 0 | .take_while(|v| **v == 0) |
118 | 0 | .count() as u8 |
119 | 0 | } |
120 | | |
121 | | /// compute the number of deltas in the next run, and the value type |
122 | 0 | fn next_run_len(slice: &[i32]) -> (usize, DeltaRunType) { |
123 | 0 | let first = *slice.first().expect("bounds checked before here"); |
124 | 0 | debug_assert!(first != 0, "Zeroes are supposed to be handled separately"); |
125 | 0 | let run_type = preferred_run_type(first); |
126 | | |
127 | 0 | let mut idx = 1; |
128 | 0 | while idx < MAX_POINTS_PER_RUN && idx < slice.len() { |
129 | 0 | let cur = slice[idx]; |
130 | 0 | let cur_type = preferred_run_type(cur); |
131 | 0 | let next_type = slice.get(idx + 1).copied().map(preferred_run_type); |
132 | | |
133 | | // Any reason to stop? |
134 | 0 | if run_type == DeltaRunType::I8 { |
135 | | // a single zero is best stored literally inline, but two or more |
136 | | // should get a new run: |
137 | | // https://github.com/fonttools/fonttools/blob/eeaa499981c587/Lib/fontTools/ttLib/tables/TupleVariation.py#L423 |
138 | 0 | match cur_type { |
139 | 0 | DeltaRunType::Zero if next_type == Some(DeltaRunType::Zero) => break, |
140 | 0 | DeltaRunType::I16 | DeltaRunType::I32 => break, |
141 | 0 | _ => (), |
142 | | } |
143 | 0 | } else if run_type == DeltaRunType::I16 { |
144 | | // with word deltas, a single zero justifies a new run: |
145 | | //https://github.com/fonttools/fonttools/blob/eeaa499981c587/Lib/fontTools/ttLib/tables/TupleVariation.py#L457 |
146 | 0 | match (cur_type, next_type) { |
147 | 0 | (DeltaRunType::Zero | DeltaRunType::I32, _) => break, |
148 | | // and a single byte-size value should be inlined, if it lets |
149 | | // us combine two adjoining word-size runs: |
150 | | // https://github.com/fonttools/fonttools/blob/eeaa499981c587/Lib/fontTools/ttLib/tables/TupleVariation.py#L467 |
151 | 0 | (DeltaRunType::I8, Some(DeltaRunType::Zero | DeltaRunType::I8)) => break, |
152 | 0 | _ => (), |
153 | | } |
154 | 0 | } else if run_type == DeltaRunType::I32 && cur_type != DeltaRunType::I32 { |
155 | 0 | break; |
156 | 0 | } |
157 | | |
158 | 0 | idx += 1; |
159 | | } |
160 | 0 | (idx, run_type) |
161 | 0 | } |
162 | | |
163 | 0 | let mut deltas = self.deltas.as_slice(); |
164 | | |
165 | 0 | std::iter::from_fn(move || { |
166 | 0 | let run_start = *deltas.first()?; |
167 | 0 | if run_start == 0 { |
168 | 0 | let n_zeros = count_leading_zeros(deltas); |
169 | 0 | deltas = &deltas[n_zeros as usize..]; |
170 | 0 | Some(PackedDeltaRun::Zeros(n_zeros)) |
171 | | } else { |
172 | 0 | let (len, value_type) = next_run_len(deltas); |
173 | 0 | let (head, tail) = deltas.split_at(len); |
174 | 0 | deltas = tail; |
175 | 0 | Some(match value_type { |
176 | 0 | DeltaRunType::I32 => PackedDeltaRun::FourBytes(head), |
177 | 0 | DeltaRunType::I16 => PackedDeltaRun::TwoBytes(head), |
178 | 0 | DeltaRunType::I8 => PackedDeltaRun::OneByte(head), |
179 | | _ => { |
180 | 0 | unreachable!("We should have taken the other branch for first={run_start}") |
181 | | } |
182 | | }) |
183 | | } |
184 | 0 | }) |
185 | 0 | } |
186 | | } |
187 | | |
188 | | #[derive(Clone, Debug, PartialEq, Eq)] |
189 | | enum PackedDeltaRun<'a> { |
190 | | Zeros(u8), |
191 | | OneByte(&'a [i32]), |
192 | | TwoBytes(&'a [i32]), |
193 | | FourBytes(&'a [i32]), |
194 | | } |
195 | | |
196 | | impl PackedDeltaRun<'_> { |
197 | 0 | fn compute_flag(&self) -> u8 { |
198 | | /// Flag indicating that this run contains no data, |
199 | | /// and that the deltas for this run are all zero. |
200 | | const DELTAS_ARE_ZERO: u8 = 0x80; |
201 | | /// Flag indicating the data type for delta values in the run. |
202 | | const DELTAS_ARE_WORDS: u8 = 0x40; |
203 | | |
204 | 0 | match self { |
205 | 0 | PackedDeltaRun::Zeros(count) => (count - 1) | DELTAS_ARE_ZERO, |
206 | 0 | PackedDeltaRun::OneByte(deltas) => deltas.len() as u8 - 1, |
207 | 0 | PackedDeltaRun::TwoBytes(deltas) => (deltas.len() as u8 - 1) | DELTAS_ARE_WORDS, |
208 | 0 | PackedDeltaRun::FourBytes(deltas) => { |
209 | 0 | (deltas.len() as u8 - 1) | DELTAS_ARE_WORDS | DELTAS_ARE_ZERO |
210 | | } |
211 | | } |
212 | 0 | } |
213 | | |
214 | 0 | fn compute_size(&self) -> u16 { |
215 | 0 | match self { |
216 | 0 | PackedDeltaRun::Zeros(_) => 1, |
217 | 0 | PackedDeltaRun::OneByte(vals) => vals.len() as u16 + 1, |
218 | 0 | PackedDeltaRun::TwoBytes(vals) => vals.len() as u16 * 2 + 1, |
219 | 0 | PackedDeltaRun::FourBytes(vals) => vals.len() as u16 * 4 + 1, |
220 | | } |
221 | 0 | } |
222 | | } |
223 | | |
224 | | impl FontWrite for PackedDeltaRun<'_> { |
225 | 0 | fn write_into(&self, writer: &mut TableWriter) { |
226 | 0 | self.compute_flag().write_into(writer); |
227 | 0 | match self { |
228 | 0 | PackedDeltaRun::Zeros(_) => (), |
229 | 0 | PackedDeltaRun::OneByte(deltas) => { |
230 | 0 | deltas.iter().for_each(|v| (*v as i8).write_into(writer)) |
231 | | } |
232 | 0 | PackedDeltaRun::TwoBytes(deltas) => { |
233 | 0 | deltas.iter().for_each(|v| (*v as i16).write_into(writer)) |
234 | | } |
235 | 0 | PackedDeltaRun::FourBytes(deltas) => deltas.iter().for_each(|v| v.write_into(writer)), |
236 | | } |
237 | 0 | } |
238 | | } |
239 | | |
240 | | impl crate::validate::Validate for PackedPointNumbers { |
241 | 0 | fn validate_impl(&self, ctx: &mut ValidationCtx) { |
242 | 0 | if let PackedPointNumbers::Some(pts) = self { |
243 | 0 | if pts.len() > 0x7FFF { |
244 | 0 | ctx.report("length cannot be stored in 15 bites"); |
245 | 0 | } |
246 | 0 | } |
247 | 0 | } |
248 | | } |
249 | | |
250 | | impl FontWrite for PackedPointNumbers { |
251 | 0 | fn write_into(&self, writer: &mut TableWriter) { |
252 | | // compute the actual count: |
253 | 0 | match self.as_slice().len() { |
254 | 0 | len @ 0..=127 => (len as u8).write_into(writer), |
255 | 0 | len => (len as u16 | 0x8000u16).write_into(writer), |
256 | | } |
257 | 0 | for run in self.iter_runs() { |
258 | 0 | run.write_into(writer); |
259 | 0 | } |
260 | 0 | } |
261 | | } |
262 | | |
263 | | impl PackedPointNumbers { |
264 | | /// Compute the number of bytes required to encode these points |
265 | 0 | pub(crate) fn compute_size(&self) -> u16 { |
266 | 0 | let mut count = match self { |
267 | 0 | PackedPointNumbers::All => return 1, |
268 | 0 | PackedPointNumbers::Some(pts) if pts.len() < 128 => 1u16, |
269 | 0 | PackedPointNumbers::Some(_) => 2, |
270 | | }; |
271 | 0 | for run in self.iter_runs() { |
272 | 0 | count = count.checked_add(run.compute_size()).unwrap(); |
273 | 0 | } |
274 | 0 | count |
275 | 0 | } |
276 | | |
277 | 0 | fn as_slice(&self) -> &[u16] { |
278 | 0 | match self { |
279 | 0 | PackedPointNumbers::All => &[], |
280 | 0 | PackedPointNumbers::Some(pts) => pts.as_slice(), |
281 | | } |
282 | 0 | } |
283 | | |
284 | 0 | fn iter_runs(&self) -> impl Iterator<Item = PackedPointRun<'_>> { |
285 | | const U8_MAX: u16 = u8::MAX as u16; |
286 | | const MAX_POINTS_PER_RUN: usize = 128; |
287 | | |
288 | 0 | let mut points = match self { |
289 | 0 | PackedPointNumbers::Some(pts) => pts.as_slice(), |
290 | 0 | PackedPointNumbers::All => &[], |
291 | | }; |
292 | | |
293 | 0 | let mut prev_point = 0u16; |
294 | | |
295 | | // split a run off the front of points: |
296 | | // - if point is more than 255 away from prev, we're using words |
297 | 0 | std::iter::from_fn(move || { |
298 | 0 | let next = points.first()?; |
299 | 0 | let are_words = (next - prev_point) > U8_MAX; |
300 | 0 | let run_len = points |
301 | 0 | .iter() |
302 | 0 | .take(MAX_POINTS_PER_RUN) |
303 | 0 | .scan(prev_point, |prev, point| { |
304 | 0 | let take_this = if are_words { |
305 | 0 | (point - *prev) > U8_MAX |
306 | | } else { |
307 | 0 | (point - *prev) <= U8_MAX |
308 | | }; |
309 | 0 | *prev = *point; |
310 | 0 | take_this.then_some(point) |
311 | 0 | }) |
312 | 0 | .count(); |
313 | | |
314 | 0 | let (head, tail) = points.split_at(run_len); |
315 | 0 | points = tail; |
316 | 0 | let last_point = prev_point; |
317 | 0 | prev_point = head.last().copied().unwrap(); |
318 | | |
319 | 0 | Some(PackedPointRun { |
320 | 0 | last_point, |
321 | 0 | are_words, |
322 | 0 | points: head, |
323 | 0 | }) |
324 | 0 | }) |
325 | 0 | } |
326 | | } |
327 | | |
328 | | #[derive(Debug, PartialEq, Eq)] |
329 | | struct PackedPointRun<'a> { |
330 | | last_point: u16, |
331 | | are_words: bool, |
332 | | points: &'a [u16], |
333 | | } |
334 | | |
335 | | impl PackedPointRun<'_> { |
336 | 0 | fn compute_size(&self) -> u16 { |
337 | | const LEN_BYTE: u16 = 1; |
338 | 0 | let per_point_len = if self.are_words { 2 } else { 1 }; |
339 | 0 | self.points.len() as u16 * per_point_len + LEN_BYTE |
340 | 0 | } |
341 | | } |
342 | | |
343 | | impl FontWrite for PackedPointRun<'_> { |
344 | 0 | fn write_into(&self, writer: &mut TableWriter) { |
345 | 0 | assert!(!self.points.is_empty() && self.points.len() <= 128); |
346 | 0 | let mut len = self.points.len() as u8 - 1; |
347 | 0 | if self.are_words { |
348 | 0 | len |= 0x80; |
349 | 0 | } |
350 | 0 | len.write_into(writer); |
351 | 0 | let mut last_point = self.last_point; |
352 | 0 | for point in self.points { |
353 | 0 | let delta = point - last_point; |
354 | 0 | last_point = *point; |
355 | 0 | if self.are_words { |
356 | 0 | delta.write_into(writer); |
357 | 0 | } else { |
358 | 0 | debug_assert!(delta <= u8::MAX as u16); |
359 | 0 | (delta as u8).write_into(writer); |
360 | | } |
361 | | } |
362 | 0 | } |
363 | | } |
364 | | |
365 | | impl FontWrite for TupleIndex { |
366 | 0 | fn write_into(&self, writer: &mut TableWriter) { |
367 | 0 | self.bits().write_into(writer) |
368 | 0 | } |
369 | | } |
370 | | |
371 | | //hack: unclear if we're even going to do any codegen for writing, but |
372 | | //for the time being this lets us compile |
373 | | impl<'a> FromObjRef<Option<read_fonts::tables::variations::Tuple<'a>>> for Vec<F2Dot14> { |
374 | 0 | fn from_obj_ref( |
375 | 0 | from: &Option<read_fonts::tables::variations::Tuple<'a>>, |
376 | 0 | _data: FontData, |
377 | 0 | ) -> Self { |
378 | 0 | from.as_ref() |
379 | 0 | .map(|tup| tup.values.iter().map(BigEndian::get).collect()) |
380 | 0 | .unwrap_or_default() |
381 | 0 | } |
382 | | } |
383 | | |
384 | | impl Tuple { |
385 | 0 | pub fn len(&self) -> u16 { |
386 | 0 | self.values.len().try_into().unwrap() |
387 | 0 | } |
388 | | |
389 | 0 | pub fn is_empty(&self) -> bool { |
390 | 0 | self.values.is_empty() |
391 | 0 | } |
392 | | } |
393 | | |
394 | | impl DeltaSetIndexMap { |
395 | | /// Return the most compact entry format that can represent this mapping. |
396 | | /// |
397 | | /// EntryFormat is a packed u8 field that describes the compressed representation |
398 | | /// of delta-set indices. For more info, see: |
399 | | /// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#associating-target-items-to-variation-data> |
400 | | // This is a direct port from fonttools' DeltaSetMap.getEntryFormat: |
401 | | // https://github.com/fonttools/fonttools/blob/6d531f/Lib/fontTools/ttLib/tables/otTables.py#L644-L666 |
402 | 0 | fn get_entry_format(mapping: &[u32]) -> EntryFormat { |
403 | 0 | let ored = mapping.iter().fold(0, |acc, idx| acc | *idx); |
404 | | |
405 | 0 | let inner = (ored & 0xFFFF) as u16; |
406 | 0 | let inner_bits = (16 - inner.leading_zeros() as u8).max(1); |
407 | 0 | assert!(inner_bits <= 16); |
408 | | |
409 | 0 | let ored = (ored >> (16 - inner_bits)) | (ored & ((1 << inner_bits) - 1)); |
410 | 0 | let entry_size = match ored { |
411 | 0 | 0..=0xFF => 1, |
412 | 0 | 0x100..=0xFFFF => 2, |
413 | 0 | 0x10000..=0xFFFFFF => 3, |
414 | 0 | _ => 4, |
415 | | }; |
416 | | |
417 | 0 | EntryFormat::from_bits(((entry_size - 1) << 4) | (inner_bits - 1)).unwrap() |
418 | 0 | } |
419 | | |
420 | | /// Compress u32's into packed data using the most compact entry format. |
421 | | /// |
422 | | /// Returns the computed entry format and the packed data. |
423 | | /// |
424 | | /// For more info, see: |
425 | | /// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#associating-target-items-to-variation-data> |
426 | | // Ported from fonttools' VarIdxMapValue.write method: |
427 | | // https://github.com/fonttools/fonttools/blob/6d531fe/Lib/fontTools/ttLib/tables/otConverters.py#L1764-L1781 |
428 | 0 | fn pack_map_data(mapping: &[u32]) -> (EntryFormat, Vec<u8>) { |
429 | 0 | let fmt = DeltaSetIndexMap::get_entry_format(mapping); |
430 | 0 | let inner_bits = fmt.bit_count(); |
431 | 0 | let inner_mask = (1 << inner_bits as u32) - 1; |
432 | 0 | let outer_shift = 16 - inner_bits; |
433 | 0 | let entry_size = fmt.entry_size(); |
434 | 0 | assert!((1..=4).contains(&entry_size)); |
435 | | |
436 | | // omit trailing entries that are the same as the previous one; |
437 | | // the last entry is assumed when index is >= map_count |
438 | 0 | let mut map_count = mapping.len(); |
439 | 0 | while map_count > 1 && mapping[map_count - 1] == mapping[map_count - 2] { |
440 | 0 | map_count -= 1; |
441 | 0 | } |
442 | | |
443 | 0 | let mut map_data = Vec::with_capacity(map_count * entry_size as usize); |
444 | 0 | for idx in mapping.iter().take(map_count) { |
445 | 0 | let idx = ((idx & 0xFFFF0000) >> outer_shift) | (idx & inner_mask); |
446 | 0 | // append entry_size bytes to map_data in BigEndian order |
447 | 0 | map_data.extend_from_slice(&idx.to_be_bytes()[4 - entry_size as usize..]); |
448 | 0 | } |
449 | 0 | assert_eq!(map_data.len(), map_count * entry_size as usize); |
450 | 0 | (fmt, map_data) |
451 | 0 | } |
452 | | } |
453 | | |
454 | | impl<I> FromIterator<I> for DeltaSetIndexMap |
455 | | where |
456 | | I: Into<u32>, |
457 | | { |
458 | | /// Create a DeltaSetIndexMap from an iterator of delta-set indices. |
459 | 0 | fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self { |
460 | 0 | let mapping: Vec<u32> = iter.into_iter().map(|v| v.into()).collect(); |
461 | 0 | let (fmt, map_data) = DeltaSetIndexMap::pack_map_data(&mapping); |
462 | 0 | let map_count = map_data.len() / fmt.entry_size() as usize; |
463 | 0 | let delta_set_index_map: DeltaSetIndexMap = if map_count <= u16::MAX as usize { |
464 | 0 | DeltaSetIndexMap::format_0(fmt, map_count as u16, map_data) |
465 | | } else { |
466 | 0 | DeltaSetIndexMap::format_1(fmt, map_count as u32, map_data) |
467 | | }; |
468 | 0 | delta_set_index_map |
469 | 0 | } |
470 | | } |
471 | | |
472 | | #[cfg(test)] |
473 | | mod tests { |
474 | | use super::*; |
475 | | use rstest::rstest; |
476 | | |
477 | | #[test] |
478 | | fn point_pack_words() { |
479 | | let thing = PackedPointNumbers::Some(vec![1002, 2002, 8408, 12228]); |
480 | | |
481 | | let runs = thing.iter_runs().collect::<Vec<_>>(); |
482 | | assert_eq!(runs.len(), 1); |
483 | | assert!(runs[0].are_words); |
484 | | assert_eq!(runs[0].last_point, 0); |
485 | | assert_eq!(runs[0].points, &[1002, 2002, 8408, 12228]); |
486 | | } |
487 | | |
488 | | #[test] |
489 | | fn serialize_packed_points() { |
490 | | let thing = PackedPointNumbers::Some(vec![1002, 2002, 8408, 12228]); |
491 | | |
492 | | let bytes = crate::dump_table(&thing).unwrap(); |
493 | | assert_eq!(thing.compute_size() as usize, bytes.len()); |
494 | | let (read, _) = read_fonts::tables::variations::PackedPointNumbers::split_off_front( |
495 | | FontData::new(&bytes), |
496 | | ); |
497 | | assert_eq!(thing.as_slice(), read.iter().collect::<Vec<_>>()); |
498 | | } |
499 | | |
500 | | #[test] |
501 | | fn point_pack_runs() { |
502 | | let thing = PackedPointNumbers::Some(vec![5, 25, 225, 1002, 2002, 2008, 2228]); |
503 | | |
504 | | let runs = thing.iter_runs().collect::<Vec<_>>(); |
505 | | assert!(!runs[0].are_words); |
506 | | assert_eq!(runs[0].last_point, 0); |
507 | | assert_eq!(runs[0].points, &[5, 25, 225]); |
508 | | |
509 | | assert!(runs[1].are_words); |
510 | | assert_eq!(runs[1].last_point, 225); |
511 | | assert_eq!(runs[1].points, &[1002, 2002]); |
512 | | |
513 | | assert!(!runs[2].are_words); |
514 | | assert_eq!(runs[2].last_point, 2002); |
515 | | assert_eq!(runs[2].points, &[2008, 2228]); |
516 | | |
517 | | assert_eq!(runs.len(), 3); |
518 | | } |
519 | | |
520 | | #[test] |
521 | | fn point_pack_long_runs() { |
522 | | let mut numbers = vec![0u16; 130]; |
523 | | numbers.extend(1u16..=130u16); |
524 | | let thing = PackedPointNumbers::Some(numbers); |
525 | | |
526 | | let runs = thing.iter_runs().collect::<Vec<_>>(); |
527 | | assert!(!runs[0].are_words); |
528 | | assert_eq!(runs[0].points.len(), 128); |
529 | | assert_eq!(runs[1].last_point, 0); |
530 | | assert_eq!(runs[1].points.len(), 128); |
531 | | assert_eq!(runs[2].last_point, 126); |
532 | | assert_eq!(runs[2].points, &[127, 128, 129, 130]); |
533 | | assert!(runs.get(3).is_none()); |
534 | | } |
535 | | |
536 | | #[test] |
537 | | fn point_pack_write_one_byte() { |
538 | | let thing = PackedPointNumbers::Some(vec![5, 25, 225, 1002, 2002, 2008, 2228, 10000]); |
539 | | |
540 | | let bytes = crate::dump_table(&thing).unwrap(); |
541 | | assert_eq!(thing.compute_size() as usize, bytes.len()); |
542 | | let (read, _) = read_fonts::tables::variations::PackedPointNumbers::split_off_front( |
543 | | FontData::new(&bytes), |
544 | | ); |
545 | | assert_eq!(thing.as_slice(), read.iter().collect::<Vec<_>>()); |
546 | | } |
547 | | |
548 | | #[test] |
549 | | fn point_pack_write_two_byte() { |
550 | | let thing = PackedPointNumbers::Some(vec![0; 200]); |
551 | | |
552 | | let bytes = crate::dump_table(&thing).unwrap(); |
553 | | assert_eq!(thing.compute_size() as usize, bytes.len()); |
554 | | let (read, _) = read_fonts::tables::variations::PackedPointNumbers::split_off_front( |
555 | | FontData::new(&bytes), |
556 | | ); |
557 | | assert_eq!(thing.as_slice(), read.iter().collect::<Vec<_>>()); |
558 | | } |
559 | | |
560 | | static PACKED_DELTA_BYTES: &[u8] = &[ |
561 | | 0x03, 0x0A, 0x97, 0x00, 0xC6, 0x87, 0x41, 0x10, 0x22, 0xFB, 0x34, |
562 | | ]; |
563 | | |
564 | | // <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-deltas> |
565 | | #[test] |
566 | | fn packed_deltas_spec_runs() { |
567 | | let deltas = PackedDeltas::new(vec![10, -105, 0, -58, 0, 0, 0, 0, 0, 0, 0, 0, 4130, -1228]); |
568 | | let runs = deltas.iter_runs().collect::<Vec<_>>(); |
569 | | assert_eq!( |
570 | | runs, |
571 | | vec![ |
572 | | PackedDeltaRun::OneByte(&[10, -105, 0, -58]), |
573 | | PackedDeltaRun::Zeros(8), |
574 | | PackedDeltaRun::TwoBytes(&[4130, -1228]), |
575 | | ] |
576 | | ); |
577 | | } |
578 | | |
579 | | #[test] |
580 | | fn packed_deltas_spec_write() { |
581 | | let deltas = PackedDeltas::new(vec![10, -105, 0, -58, 0, 0, 0, 0, 0, 0, 0, 0, 4130, -1228]); |
582 | | let bytes = crate::dump_table(&deltas).unwrap(); |
583 | | assert_eq!(bytes, PACKED_DELTA_BYTES); |
584 | | let read = read_fonts::tables::variations::PackedDeltas::consume_all(FontData::new(&bytes)); |
585 | | let decoded = read.iter().collect::<Vec<_>>(); |
586 | | assert_eq!(deltas.deltas.len(), decoded.len()); |
587 | | assert_eq!(deltas.deltas, decoded); |
588 | | assert_eq!(bytes, PACKED_DELTA_BYTES); |
589 | | } |
590 | | |
591 | | #[test] |
592 | | fn empty_deltas() { |
593 | | let deltas = PackedDeltas::new(vec![]); |
594 | | let bytes = crate::dump_table(&deltas).unwrap(); |
595 | | assert!(bytes.is_empty()); |
596 | | } |
597 | | |
598 | | #[test] |
599 | | fn lots_of_zero() { |
600 | | let num_zeroes = 65; |
601 | | let deltas = PackedDeltas::new(vec![0; num_zeroes]); |
602 | | assert_eq!( |
603 | | vec![PackedDeltaRun::Zeros(64), PackedDeltaRun::Zeros(1)], |
604 | | deltas.iter_runs().collect::<Vec<_>>() |
605 | | ); |
606 | | } |
607 | | |
608 | | #[test] |
609 | | fn respect_my_run_length_authority() { |
610 | | let mut values = (1..196).collect::<Vec<_>>(); |
611 | | values.extend([0, 0, 0]); |
612 | | values.push(i16::MAX as i32 + 1); |
613 | | values.push(i16::MIN as i32 - 1); |
614 | | values.push(i16::MAX as i32 * 2); |
615 | | let deltas = PackedDeltas::new(values); |
616 | | assert_eq!( |
617 | | vec![ |
618 | | // 64 entries per run please and thank you |
619 | | PackedDeltaRun::OneByte(&(1..65).collect::<Vec<i32>>()), |
620 | | // 63 entries this time because at 128 we switch to 2 bytes |
621 | | PackedDeltaRun::OneByte(&(65..128).collect::<Vec<i32>>()), |
622 | | // 64 per run again |
623 | | PackedDeltaRun::TwoBytes(&(128..192).collect::<Vec<i32>>()), |
624 | | // tail |
625 | | PackedDeltaRun::TwoBytes(&(192..=195).collect::<Vec<i32>>()), |
626 | | PackedDeltaRun::Zeros(3), |
627 | | PackedDeltaRun::FourBytes(&[ |
628 | | i16::MAX as i32 + 1, |
629 | | i16::MIN as i32 - 1, |
630 | | i16::MAX as i32 * 2 |
631 | | ]), |
632 | | ], |
633 | | deltas.iter_runs().collect::<Vec<_>>() |
634 | | ) |
635 | | } |
636 | | |
637 | | #[test] |
638 | | fn inline_single_zeros_with_bytes() { |
639 | | let packed = PackedDeltas::new(vec![1, 2, 0, 3]); |
640 | | assert_eq!(packed.iter_runs().count(), 1) |
641 | | } |
642 | | |
643 | | #[test] |
644 | | fn split_two_zeros_in_bytes() { |
645 | | let packed = PackedDeltas::new(vec![1, 2, 0, 0, 3]); |
646 | | assert_eq!(packed.iter_runs().count(), 3) |
647 | | } |
648 | | |
649 | | #[test] |
650 | | fn split_single_zero_in_words() { |
651 | | let packed = PackedDeltas::new(vec![150, 200, 0, -300]); |
652 | | assert_eq!(packed.iter_runs().count(), 3) |
653 | | } |
654 | | |
655 | | #[test] |
656 | | fn inline_single_byte_in_words() { |
657 | | let packed = PackedDeltas::new(vec![150, 200, 1, -300]); |
658 | | assert_eq!(packed.iter_runs().count(), 1) |
659 | | } |
660 | | |
661 | | #[test] |
662 | | fn split_double_byte_in_words() { |
663 | | let packed = PackedDeltas::new(vec![150, 200, 1, 3, -300]); |
664 | | assert_eq!(packed.iter_runs().count(), 3) |
665 | | } |
666 | | |
667 | | #[test] |
668 | | fn split_byte_then_zero_after_words() { |
669 | | // without split: 10 = 1 + 2 + 2 + 2 + 1 + 2 |
670 | | // with split: 9 = 1 + 2 + 2 + 1 + 3 |
671 | | let packed = PackedDeltas::new(vec![150, 200, 1, 0, 1]); |
672 | | assert_eq!(packed.iter_runs().count(), 2); |
673 | | assert_eq!(packed.compute_size(), 9); |
674 | | } |
675 | | |
676 | | #[rstest] |
677 | | // Note how the packed data below is b"\x00\x01" and not b"\x00\x01\x01", for the |
678 | | // repeated trailing values can be omitted |
679 | | #[case::one_byte_one_inner_bit( |
680 | | vec![0, 1, 1], 0b00_0000, 1, 1, b"\x00\x01", |
681 | | )] |
682 | | #[case::one_byte_two_inner_bits( |
683 | | vec![0, 1, 2], 0b00_0001, 1, 2, b"\x00\x01\x02", |
684 | | )] |
685 | | #[case::one_byte_three_inner_bits( |
686 | | vec![0, 1, 4], 0b00_0010, 1, 3, b"\x00\x01\x04", |
687 | | )] |
688 | | #[case::one_byte_four_inner_bits( |
689 | | vec![0, 1, 8], 0b00_0011, 1, 4, b"\x00\x01\x08", |
690 | | )] |
691 | | // 256 needs 2 bytes, of which 9 bits for the inner value |
692 | | #[case::two_bytes_nine_inner_bits( |
693 | | vec![0, 1, 256], 0b01_1000, 2, 9, b"\x00\x00\x00\x01\x01\x00", |
694 | | )] |
695 | | #[case::two_bytes_sixteen_inner_bits( |
696 | | vec![0, 1, 0x8000], 0b01_1111, 2, 16, b"\x00\x00\x00\x01\x80\x00", |
697 | | )] |
698 | | // note this gets packed the same as case 'one_byte_two_inner_bits': [0, 1, 2] |
699 | | // above, but it uses only 1 bit for the inner value, while the other bits are |
700 | | // used for the outer value: |
701 | | // 0x0001_0000 => b"\x02" => 0b00000010 => {outer: 1, inner: 0) |
702 | | #[case::one_byte_one_inner_bit_two_vardatas( |
703 | | vec![0, 1, 0x01_0000], 0b00_0000, 1, 1, b"\x00\x01\x02", |
704 | | )] |
705 | | #[case::three_bytes_sixteen_inner_bits( |
706 | | vec![0, 0xFFFF, 0x01_0000], |
707 | | 0b10_1111, |
708 | | 3, |
709 | | 16, |
710 | | b"\x00\x00\x00\x00\xFF\xFF\x01\x00\x00", |
711 | | )] |
712 | | #[case::four_bytes_sixteen_inner_bits( |
713 | | vec![0, 0xFFFF, 0xFFFF_FFFF], |
714 | | 0b11_1111, |
715 | | 4, |
716 | | 16, |
717 | | b"\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF", |
718 | | )] |
719 | | #[test] |
720 | | fn delta_set_index_map_entry_format_and_packed_data( |
721 | | #[case] mapping: Vec<u32>, |
722 | | #[case] expected_format_bits: u8, |
723 | | #[case] expected_entry_size: u8, |
724 | | #[case] expected_inner_bit_count: u8, |
725 | | #[case] expected_map_data: &[u8], |
726 | | ) { |
727 | | let (format, data) = DeltaSetIndexMap::pack_map_data(&mapping); |
728 | | assert_eq!(format.bits(), expected_format_bits); |
729 | | assert_eq!(format.entry_size(), expected_entry_size); |
730 | | assert_eq!(format.bit_count(), expected_inner_bit_count); |
731 | | assert_eq!(data, expected_map_data); |
732 | | |
733 | | let dsim: DeltaSetIndexMap = mapping.iter().copied().collect(); |
734 | | // all test mappings have fewer than 65536 entries (for practical reasons) |
735 | | // so we should generate a Format0 |
736 | | assert!(matches!(dsim, DeltaSetIndexMap::Format0 { .. })); |
737 | | |
738 | | // make sure we get the same mapping back after round-tripping to/from bytes |
739 | | let raw_dsim = crate::dump_table(&dsim).unwrap(); |
740 | | let dsim2 = |
741 | | read_fonts::tables::variations::DeltaSetIndexMap::read(FontData::new(&raw_dsim)) |
742 | | .unwrap(); |
743 | | assert_eq!( |
744 | | (0..mapping.len()) |
745 | | .map(|i| { |
746 | | let index = dsim2.get(i as u32).unwrap(); |
747 | | ((index.outer as u32) << 16) | index.inner as u32 |
748 | | }) |
749 | | .collect::<Vec<_>>(), |
750 | | mapping |
751 | | ); |
752 | | } |
753 | | |
754 | | #[test] |
755 | | fn delta_set_index_map_from_variation_index_iterator() { |
756 | | // as returned from VariationStoreBuilder::build() in the VariationIndexRemapping |
757 | | use crate::tables::layout::VariationIndex; |
758 | | |
759 | | let mapping = vec![ |
760 | | VariationIndex::new(0, 0), |
761 | | VariationIndex::new(0, 1), |
762 | | VariationIndex::new(0, 2), |
763 | | VariationIndex::new(1, 0), |
764 | | VariationIndex::new(1, 1), |
765 | | VariationIndex::new(1, 2), |
766 | | ]; |
767 | | |
768 | | let dsim: DeltaSetIndexMap = mapping.into_iter().collect(); |
769 | | let DeltaSetIndexMap::Format0(dsim) = dsim else { |
770 | | panic!("expected DeltaSetIndexMap::Format0, got {:?}", dsim); |
771 | | }; |
772 | | assert_eq!(dsim.map_count, 6); |
773 | | assert_eq!(dsim.entry_format.bits(), 0b000001); |
774 | | assert_eq!(dsim.entry_format.entry_size(), 1); // one byte per entry |
775 | | assert_eq!(dsim.entry_format.bit_count(), 2); |
776 | | // for each entry/byte, the right-most 2 bits are the inner value, |
777 | | // the remaining bits are the outer value |
778 | | assert_eq!( |
779 | | dsim.map_data, |
780 | | vec![ |
781 | | 0b00_00, // (0, 0) |
782 | | 0b00_01, // (0, 1) |
783 | | 0b00_10, // (0, 2) |
784 | | 0b01_00, // (1, 0) |
785 | | 0b01_01, // (1, 1) |
786 | | 0b01_10, // (1, 2) |
787 | | ] |
788 | | ); |
789 | | } |
790 | | |
791 | | #[test] |
792 | | fn huge_mapping_generates_format_1_delta_set_index_map() { |
793 | | // 65536 entries, so we need a Format1 with u32 map_count |
794 | | let mapping = (0..=0xFFFF).collect::<Vec<u32>>(); |
795 | | let map_count = mapping.len() as u32; |
796 | | let dsim: DeltaSetIndexMap = mapping.into_iter().collect(); |
797 | | let DeltaSetIndexMap::Format1(dsim) = dsim else { |
798 | | panic!("expected DeltaSetIndexMap::Format1, got {:?}", dsim); |
799 | | }; |
800 | | assert_eq!(dsim.map_count, map_count); |
801 | | } |
802 | | } |