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