1include!("../../generated/generated_gpos.rs");
6
7use std::collections::HashSet;
8
9use super::{
11 layout::{
12 ChainedSequenceContext, ClassDef, CoverageTable, DeviceOrVariationIndex, FeatureList,
13 FeatureVariations, Lookup, LookupList, LookupSubtable, LookupType, ScriptList,
14 SequenceContext,
15 },
16 variations::ivs_builder::{RemapVariationIndices, VariationIndexRemapping},
17};
18
19#[cfg(test)]
20mod spec_tests;
21
22pub mod builders;
23mod value_record;
24pub use value_record::ValueRecord;
25
26pub type PositionLookupList = LookupList<PositionLookup>;
28
29super::layout::table_newtype!(
30 PositionSequenceContext,
31 SequenceContext,
32 read_fonts::tables::layout::SequenceContext<'a>
33);
34
35super::layout::table_newtype!(
36 PositionChainContext,
37 ChainedSequenceContext,
38 read_fonts::tables::layout::ChainedSequenceContext<'a>
39);
40
41impl Gpos {
42 fn compute_version(&self) -> MajorMinor {
43 if self.feature_variations.is_none() {
44 MajorMinor::VERSION_1_0
45 } else {
46 MajorMinor::VERSION_1_1
47 }
48 }
49}
50
51super::layout::lookup_type!(gpos, SinglePos, 1);
52super::layout::lookup_type!(gpos, PairPos, 2);
53super::layout::lookup_type!(gpos, CursivePosFormat1, 3);
54super::layout::lookup_type!(gpos, MarkBasePosFormat1, 4);
55super::layout::lookup_type!(gpos, MarkLigPosFormat1, 5);
56super::layout::lookup_type!(gpos, MarkMarkPosFormat1, 6);
57super::layout::lookup_type!(gpos, PositionSequenceContext, 7);
58super::layout::lookup_type!(gpos, PositionChainContext, 8);
59super::layout::lookup_type!(gpos, ExtensionSubtable, 9);
60
61impl<T: LookupSubtable + FontWrite> FontWrite for ExtensionPosFormat1<T> {
62 fn write_into(&self, writer: &mut TableWriter) {
63 1u16.write_into(writer);
64 T::TYPE.write_into(writer);
65 self.extension.write_into(writer);
66 }
67}
68
69impl<'a> FontRead<'a> for PositionLookup {
71 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
72 read_fonts::tables::gpos::PositionLookup::read(data).map(|x| x.to_owned_table())
73 }
74}
75
76impl<'a> FontRead<'a> for PositionLookupList {
77 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
78 read_fonts::tables::gpos::PositionLookupList::read(data).map(|x| x.to_owned_table())
79 }
80}
81
82impl SinglePosFormat1 {
83 fn compute_value_format(&self) -> ValueFormat {
84 self.value_record.format()
85 }
86}
87
88impl SinglePosFormat2 {
89 fn compute_value_format(&self) -> ValueFormat {
90 self.value_records
91 .first()
92 .map(ValueRecord::format)
93 .unwrap_or(ValueFormat::empty())
94 }
95}
96
97impl PairPosFormat1 {
98 fn compute_value_format1(&self) -> ValueFormat {
99 self.pair_sets
100 .first()
101 .and_then(|pairset| pairset.pair_value_records.first())
102 .map(|rec| rec.value_record1.format())
103 .unwrap_or(ValueFormat::empty())
104 }
105
106 fn compute_value_format2(&self) -> ValueFormat {
107 self.pair_sets
108 .first()
109 .and_then(|pairset| pairset.pair_value_records.first())
110 .map(|rec| rec.value_record2.format())
111 .unwrap_or(ValueFormat::empty())
112 }
113
114 fn check_format_consistency(&self, ctx: &mut ValidationCtx) {
115 let vf1 = self.compute_value_format1();
116 let vf2 = self.compute_value_format2();
117 ctx.with_array_items(self.pair_sets.iter(), |ctx, item| {
118 ctx.in_field("pair_value_records", |ctx| {
119 if item.pair_value_records.iter().any(|pairset| {
120 pairset.value_record1.format() != vf1 || pairset.value_record2.format() != vf2
121 }) {
122 ctx.report("all ValueRecords must have same format")
123 }
124 })
125 })
126 }
127}
128
129impl PairPosFormat2 {
130 fn compute_value_format1(&self) -> ValueFormat {
131 self.class1_records
132 .first()
133 .and_then(|rec| rec.class2_records.first())
134 .map(|rec| rec.value_record1.format())
135 .unwrap_or(ValueFormat::empty())
136 }
137
138 fn compute_value_format2(&self) -> ValueFormat {
139 self.class1_records
140 .first()
141 .and_then(|rec| rec.class2_records.first())
142 .map(|rec| rec.value_record2.format())
143 .unwrap_or(ValueFormat::empty())
144 }
145
146 fn compute_class1_count(&self) -> u16 {
147 self.class_def1.class_count()
148 }
149
150 fn compute_class2_count(&self) -> u16 {
151 self.class_def2.class_count()
152 }
153
154 fn check_length_and_format_conformance(&self, ctx: &mut ValidationCtx) {
155 let n_class_1s = self.class_def1.class_count();
156 let n_class_2s = self.class_def2.class_count();
157 let format_1 = self.compute_value_format1();
158 let format_2 = self.compute_value_format2();
159 if self.class1_records.len() != n_class_1s as usize {
160 ctx.report("class1_records length must match number of class1 classes");
161 }
162 ctx.in_field("class1_records", |ctx| {
163 ctx.with_array_items(self.class1_records.iter(), |ctx, c1rec| {
164 if c1rec.class2_records.len() != n_class_2s as usize {
165 ctx.report("class2_records length must match number of class2 classes ");
166 }
167 if c1rec.class2_records.iter().any(|rec| {
168 rec.value_record1.format() != format_1 || rec.value_record2.format() != format_2
169 }) {
170 ctx.report("all value records should report the same format");
171 }
172 })
173 });
174 }
175}
176
177impl MarkBasePosFormat1 {
178 fn compute_mark_class_count(&self) -> u16 {
179 self.mark_array.class_count()
180 }
181}
182
183impl MarkMarkPosFormat1 {
184 fn compute_mark_class_count(&self) -> u16 {
185 self.mark1_array.class_count()
186 }
187}
188
189impl MarkLigPosFormat1 {
190 fn compute_mark_class_count(&self) -> u16 {
191 self.mark_array.class_count()
192 }
193}
194
195impl MarkArray {
196 fn class_count(&self) -> u16 {
197 self.mark_records
198 .iter()
199 .map(|rec| rec.mark_class)
200 .collect::<HashSet<_>>()
201 .len() as u16
202 }
203}
204
205impl RemapVariationIndices for ValueRecord {
206 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
207 for table in [
208 self.x_placement_device.as_mut(),
209 self.y_placement_device.as_mut(),
210 self.x_advance_device.as_mut(),
211 self.y_advance_device.as_mut(),
212 ]
213 .into_iter()
214 .flatten()
215 {
216 table.remap_variation_indices(key_map)
217 }
218 }
219}
220
221impl RemapVariationIndices for DeviceOrVariationIndex {
222 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
223 if let DeviceOrVariationIndex::PendingVariationIndex(table) = self {
224 *self = key_map.get(table.delta_set_id).unwrap().into();
225 }
226 }
227}
228
229impl RemapVariationIndices for AnchorTable {
230 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
231 if let AnchorTable::Format3(table) = self {
232 table
233 .x_device
234 .as_mut()
235 .into_iter()
236 .chain(table.y_device.as_mut())
237 .for_each(|x| x.remap_variation_indices(key_map))
238 }
239 }
240}
241
242impl RemapVariationIndices for Gpos {
243 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
244 self.lookup_list.as_mut().remap_variation_indices(key_map)
245 }
246}
247
248impl RemapVariationIndices for PositionLookupList {
249 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
250 for lookup in &mut self.lookups {
251 lookup.remap_variation_indices(key_map)
252 }
253 }
254}
255
256impl RemapVariationIndices for PositionLookup {
257 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
258 match self {
259 PositionLookup::Single(lookup) => lookup.remap_variation_indices(key_map),
260 PositionLookup::Pair(lookup) => lookup.remap_variation_indices(key_map),
261 PositionLookup::Cursive(lookup) => lookup.remap_variation_indices(key_map),
262 PositionLookup::MarkToBase(lookup) => lookup.remap_variation_indices(key_map),
263 PositionLookup::MarkToLig(lookup) => lookup.remap_variation_indices(key_map),
264 PositionLookup::MarkToMark(lookup) => lookup.remap_variation_indices(key_map),
265
266 PositionLookup::Contextual(_)
268 | PositionLookup::ChainContextual(_)
269 | PositionLookup::Extension(_) => (),
270 }
271 }
272}
273
274impl<T: RemapVariationIndices> RemapVariationIndices for Lookup<T> {
275 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
276 for subtable in &mut self.subtables {
277 subtable.remap_variation_indices(key_map)
278 }
279 }
280}
281
282impl RemapVariationIndices for SinglePos {
283 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
284 match self {
285 SinglePos::Format1(table) => table.remap_variation_indices(key_map),
286 SinglePos::Format2(table) => table.remap_variation_indices(key_map),
287 }
288 }
289}
290
291impl RemapVariationIndices for SinglePosFormat1 {
292 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
293 self.value_record.remap_variation_indices(key_map);
294 }
295}
296
297impl RemapVariationIndices for SinglePosFormat2 {
298 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
299 for rec in &mut self.value_records {
300 rec.remap_variation_indices(key_map);
301 }
302 }
303}
304
305impl RemapVariationIndices for PairPosFormat1 {
306 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
307 for pairset in &mut self.pair_sets {
308 for pairrec in &mut pairset.pair_value_records {
309 pairrec.value_record1.remap_variation_indices(key_map);
310 pairrec.value_record2.remap_variation_indices(key_map);
311 }
312 }
313 }
314}
315
316impl RemapVariationIndices for PairPosFormat2 {
317 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
318 for class1rec in &mut self.class1_records {
319 for class2rec in &mut class1rec.class2_records {
320 class2rec.value_record1.remap_variation_indices(key_map);
321 class2rec.value_record2.remap_variation_indices(key_map);
322 }
323 }
324 }
325}
326
327impl RemapVariationIndices for PairPos {
328 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
329 match self {
330 PairPos::Format1(table) => table.remap_variation_indices(key_map),
331 PairPos::Format2(table) => table.remap_variation_indices(key_map),
332 }
333 }
334}
335
336impl RemapVariationIndices for MarkBasePosFormat1 {
337 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
338 self.mark_array.as_mut().remap_variation_indices(key_map);
339 for rec in &mut self.base_array.as_mut().base_records {
340 for anchor in &mut rec.base_anchors {
341 if let Some(anchor) = anchor.as_mut() {
342 anchor.remap_variation_indices(key_map);
343 }
344 }
345 }
346 }
347}
348
349impl RemapVariationIndices for MarkMarkPosFormat1 {
350 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
351 self.mark1_array.as_mut().remap_variation_indices(key_map);
352 for rec in &mut self.mark2_array.as_mut().mark2_records {
353 for anchor in &mut rec.mark2_anchors {
354 if let Some(anchor) = anchor.as_mut() {
355 anchor.remap_variation_indices(key_map);
356 }
357 }
358 }
359 }
360}
361
362impl RemapVariationIndices for MarkLigPosFormat1 {
363 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
364 self.mark_array.as_mut().remap_variation_indices(key_map);
365 for lig in &mut self.ligature_array.as_mut().ligature_attaches {
366 for rec in &mut lig.component_records {
367 for anchor in &mut rec.ligature_anchors {
368 if let Some(anchor) = anchor.as_mut() {
369 anchor.remap_variation_indices(key_map);
370 }
371 }
372 }
373 }
374 }
375}
376
377impl RemapVariationIndices for CursivePosFormat1 {
378 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
379 for rec in &mut self.entry_exit_record {
380 for anchor in [rec.entry_anchor.as_mut(), rec.exit_anchor.as_mut()]
381 .into_iter()
382 .flatten()
383 {
384 anchor.remap_variation_indices(key_map);
385 }
386 }
387 }
388}
389
390impl RemapVariationIndices for MarkArray {
391 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
392 for rec in &mut self.mark_records {
393 rec.mark_anchor.remap_variation_indices(key_map);
394 }
395 }
396}
397
398#[cfg(test)]
399mod tests {
400
401 use read_fonts::tables::{gpos as read_gpos, layout::LookupFlag};
402
403 use crate::tables::layout::VariationIndex;
404
405 use super::*;
406
407 #[test]
409 fn gpos_1_zero() {
410 let cov_one = CoverageTable::format_1(vec![GlyphId16::new(2)]);
411 let cov_two = CoverageTable::format_1(vec![GlyphId16::new(4)]);
412 let sub1 = SinglePos::format_1(cov_one, ValueRecord::default());
413 let sub2 = SinglePos::format_1(cov_two, ValueRecord::default().with_x_advance(500));
414 let lookup = Lookup::new(LookupFlag::default(), vec![sub1, sub2]);
415 let bytes = crate::dump_table(&lookup).unwrap();
416
417 let parsed = read_gpos::PositionLookup::read(FontData::new(&bytes)).unwrap();
418 let read_gpos::PositionLookup::Single(table) = parsed else {
419 panic!("something has gone seriously wrong");
420 };
421
422 assert_eq!(table.lookup_flag(), LookupFlag::empty());
423 assert_eq!(table.sub_table_count(), 2);
424 let read_gpos::SinglePos::Format1(sub1) = table.subtables().get(0).unwrap() else {
425 panic!("wrong table type");
426 };
427 let read_gpos::SinglePos::Format1(sub2) = table.subtables().get(1).unwrap() else {
428 panic!("wrong table type");
429 };
430
431 assert_eq!(sub1.value_format(), ValueFormat::empty());
432 assert_eq!(sub1.value_record(), read_gpos::ValueRecord::default());
433
434 assert_eq!(sub2.value_format(), ValueFormat::X_ADVANCE);
435 assert_eq!(
436 sub2.value_record(),
437 read_gpos::ValueRecord {
438 x_advance: Some(500.into()),
439 ..Default::default()
440 }
441 );
442 }
443
444 fn make_rec(i: u16) -> ValueRecord {
446 if i == 0 {
448 return ValueRecord::new().with_explicit_value_format(ValueFormat::X_ADVANCE_DEVICE);
449 }
450 ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, i))
451 }
452
453 #[test]
454 fn compile_devices_pairpos2() {
455 let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
456 let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
458
459 let class1recs = vec![
461 Class1Record::new(vec![
462 Class2Record::new(make_rec(0), make_rec(0)),
463 Class2Record::new(make_rec(1), make_rec(2)),
464 ]),
465 Class1Record::new(vec![
466 Class2Record::new(make_rec(0), make_rec(0)),
467 Class2Record::new(make_rec(2), make_rec(3)),
468 ]),
469 ];
470 let coverage = class1.iter().map(|(gid, _)| gid).collect();
471 let a_table = PairPos::format_2(coverage, class1, class2, class1recs);
472
473 let bytes = crate::dump_table(&a_table).unwrap();
474 let read_back = PairPosFormat2::read(bytes.as_slice().into()).unwrap();
475
476 assert!(read_back.class1_records[0].class2_records[0]
477 .value_record1
478 .x_advance_device
479 .is_none());
480 assert!(read_back.class1_records[1].class2_records[1]
481 .value_record1
482 .x_advance_device
483 .is_some());
484
485 let DeviceOrVariationIndex::VariationIndex(dev2) = read_back.class1_records[0]
486 .class2_records[1]
487 .value_record2
488 .x_advance_device
489 .as_ref()
490 .unwrap()
491 else {
492 panic!("not a variation index")
493 };
494 assert_eq!(dev2.delta_set_inner_index, 2);
495 }
496
497 #[should_panic(expected = "all value records should report the same format")]
498 #[test]
499 fn validate_bad_pairpos2() {
500 let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
501 let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
503 let coverage = class1.iter().map(|(gid, _)| gid).collect();
504
505 let class1recs = vec![
507 Class1Record::new(vec![
508 Class2Record::new(make_rec(0), make_rec(0)),
509 Class2Record::new(make_rec(1), make_rec(2)),
510 ]),
511 Class1Record::new(vec![
512 Class2Record::new(make_rec(0), make_rec(0)),
513 Class2Record::new(make_rec(2), make_rec(3).with_x_advance(0x514)),
515 ]),
516 ];
517 let ppf2 = PairPos::format_2(coverage, class1, class2, class1recs);
518 crate::dump_table(&ppf2).unwrap();
519 }
520
521 #[test]
522 fn validate_pairpos1() {
523 let coverage: CoverageTable = [1, 2].into_iter().map(GlyphId16::new).collect();
524 let good_table = PairPosFormat1::new(
525 coverage.clone(),
526 vec![
527 PairSet::new(vec![PairValueRecord::new(
528 GlyphId16::new(5),
529 ValueRecord::new().with_x_advance(5),
530 ValueRecord::new(),
531 )]),
532 PairSet::new(vec![PairValueRecord::new(
533 GlyphId16::new(1),
534 ValueRecord::new().with_x_advance(42),
535 ValueRecord::new(),
536 )]),
537 ],
538 );
539
540 let bad_table = PairPosFormat1::new(
541 coverage,
542 vec![
543 PairSet::new(vec![PairValueRecord::new(
544 GlyphId16::new(5),
545 ValueRecord::new().with_x_advance(5),
546 ValueRecord::new(),
547 )]),
548 PairSet::new(vec![PairValueRecord::new(
549 GlyphId16::new(1),
550 ValueRecord::new().with_x_placement(42),
552 ValueRecord::new(),
553 )]),
554 ],
555 );
556
557 assert!(crate::dump_table(&good_table).is_ok());
558 assert!(matches!(
559 crate::dump_table(&bad_table),
560 Err(crate::error::Error::ValidationFailed(_))
561 ));
562 }
563}