1use std::{
4 collections::{BTreeMap, HashSet},
5 hash::Hash,
6};
7
8pub use read_fonts::tables::layout::LookupFlag;
9use read_fonts::FontRead;
10
11#[cfg(test)]
12#[path = "../tests/layout.rs"]
13mod spec_tests;
14
15include!("../../generated/generated_layout.rs");
16
17macro_rules! lookup_type {
19 (gpos, $ty:ty, $val:expr) => {
20 impl LookupSubtable for $ty {
21 const TYPE: LookupType = LookupType::Gpos($val);
22 }
23 };
24
25 (gsub, $ty:ty, $val:expr) => {
26 impl LookupSubtable for $ty {
27 const TYPE: LookupType = LookupType::Gsub($val);
28 }
29 };
30}
31
32macro_rules! table_newtype {
38 ($name:ident, $inner:ident, $read_type:path) => {
39 #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
46 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47 pub struct $name($inner);
48
49 impl $name {
50 pub fn as_inner(&self) -> &$inner {
52 &self.0
53 }
54 }
55
56 impl std::ops::Deref for $name {
57 type Target = $inner;
58 fn deref(&self) -> &Self::Target {
59 &self.0
60 }
61 }
62
63 impl std::ops::DerefMut for $name {
64 fn deref_mut(&mut self) -> &mut Self::Target {
65 &mut self.0
66 }
67 }
68
69 impl FontWrite for $name {
70 fn write_into(&self, writer: &mut TableWriter) {
71 self.0.write_into(writer)
72 }
73
74 fn table_type(&self) -> crate::table_type::TableType {
75 self.0.table_type()
76 }
77 }
78
79 impl Validate for $name {
80 fn validate_impl(&self, ctx: &mut ValidationCtx) {
81 self.0.validate_impl(ctx)
82 }
83 }
84
85 impl<'a> FromObjRef<$read_type> for $name {
86 fn from_obj_ref(obj: &$read_type, _data: FontData) -> Self {
87 Self(FromObjRef::from_obj_ref(obj, _data))
88 }
89 }
90
91 impl<'a> FromTableRef<$read_type> for $name {}
92
93 impl From<$inner> for $name {
94 fn from(src: $inner) -> $name {
95 $name(src)
96 }
97 }
98 };
99}
100
101pub(crate) use lookup_type;
102pub(crate) use table_newtype;
103
104impl FontWrite for LookupFlag {
105 fn write_into(&self, writer: &mut TableWriter) {
106 self.to_bits().write_into(writer)
107 }
108}
109
110impl<T: LookupSubtable + FontWrite> FontWrite for Lookup<T> {
111 fn write_into(&self, writer: &mut TableWriter) {
112 T::TYPE.write_into(writer);
113 self.lookup_flag.write_into(writer);
114 u16::try_from(self.subtables.len())
115 .unwrap()
116 .write_into(writer);
117 self.subtables.write_into(writer);
118 self.mark_filtering_set.write_into(writer);
119 }
120
121 fn table_type(&self) -> crate::table_type::TableType {
122 T::TYPE.into()
123 }
124}
125
126impl Lookup<SequenceContext> {
127 pub fn into_concrete<T: From<SequenceContext>>(self) -> Lookup<T> {
129 let Lookup {
130 lookup_flag,
131 subtables,
132 mark_filtering_set,
133 } = self;
134 let subtables = subtables
135 .into_iter()
136 .map(|offset| OffsetMarker::new(offset.into_inner().into()))
137 .collect();
138 Lookup {
139 lookup_flag,
140 subtables,
141 mark_filtering_set,
142 }
143 }
144}
145
146impl Lookup<ChainedSequenceContext> {
147 pub fn into_concrete<T: From<ChainedSequenceContext>>(self) -> Lookup<T> {
149 let Lookup {
150 lookup_flag,
151 subtables,
152 mark_filtering_set,
153 } = self;
154 let subtables = subtables
155 .into_iter()
156 .map(|offset| OffsetMarker::new(offset.into_inner().into()))
157 .collect();
158 Lookup {
159 lookup_flag,
160 subtables,
161 mark_filtering_set,
162 }
163 }
164}
165
166pub trait LookupSubtable {
171 const TYPE: LookupType;
173}
174
175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub enum LookupType {
178 Gpos(u16),
179 Gsub(u16),
180}
181
182impl LookupType {
183 pub(crate) const GSUB_EXT_TYPE: u16 = 7;
184 pub(crate) const GPOS_EXT_TYPE: u16 = 9;
185 pub(crate) const PAIR_POS: u16 = 2;
186 pub(crate) const MARK_TO_BASE: u16 = 4;
187
188 pub(crate) fn to_raw(self) -> u16 {
189 match self {
190 LookupType::Gpos(val) => val,
191 LookupType::Gsub(val) => val,
192 }
193 }
194
195 pub(crate) fn promote(self) -> Self {
196 match self {
197 LookupType::Gpos(Self::GPOS_EXT_TYPE) | LookupType::Gsub(Self::GSUB_EXT_TYPE) => {
198 panic!("should never be promoting an extension subtable")
199 }
200 LookupType::Gpos(_) => LookupType::Gpos(Self::GPOS_EXT_TYPE),
201 LookupType::Gsub(_) => LookupType::Gsub(Self::GSUB_EXT_TYPE),
202 }
203 }
204}
205
206impl FontWrite for LookupType {
207 fn write_into(&self, writer: &mut TableWriter) {
208 self.to_raw().write_into(writer)
209 }
210}
211
212#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214pub enum FeatureParams {
215 StylisticSet(StylisticSetParams),
216 Size(SizeParams),
217 CharacterVariant(CharacterVariantParams),
218}
219
220impl FontWrite for FeatureParams {
221 fn write_into(&self, writer: &mut TableWriter) {
222 match self {
223 FeatureParams::StylisticSet(table) => table.write_into(writer),
224 FeatureParams::Size(table) => table.write_into(writer),
225 FeatureParams::CharacterVariant(table) => table.write_into(writer),
226 }
227 }
228}
229
230impl Validate for FeatureParams {
231 fn validate_impl(&self, ctx: &mut ValidationCtx) {
232 match self {
233 Self::StylisticSet(table) => table.validate_impl(ctx),
234 Self::Size(table) => table.validate_impl(ctx),
235 Self::CharacterVariant(table) => table.validate_impl(ctx),
236 }
237 }
238}
239
240impl FromObjRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams {
241 fn from_obj_ref(from: &read_fonts::tables::layout::FeatureParams, data: FontData) -> Self {
242 use read_fonts::tables::layout::FeatureParams as FromType;
243 match from {
244 FromType::Size(thing) => Self::Size(SizeParams::from_obj_ref(thing, data)),
245 FromType::StylisticSet(thing) => {
246 Self::StylisticSet(FromObjRef::from_obj_ref(thing, data))
247 }
248 FromType::CharacterVariant(thing) => {
249 Self::CharacterVariant(FromObjRef::from_obj_ref(thing, data))
250 }
251 }
252 }
253}
254
255impl FromTableRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams {}
256
257impl ClassDefFormat1 {
258 fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
259 self.class_value_array.iter().enumerate().map(|(i, cls)| {
260 (
261 GlyphId16::new(self.start_glyph_id.to_u16().saturating_add(i as u16)),
262 *cls,
263 )
264 })
265 }
266}
267
268impl ClassRangeRecord {
269 fn validate_glyph_range(&self, ctx: &mut ValidationCtx) {
270 if self.start_glyph_id > self.end_glyph_id {
271 ctx.report(format!(
272 "start_glyph_id {} larger than end_glyph_id {}",
273 self.start_glyph_id, self.end_glyph_id
274 ));
275 }
276 }
277
278 fn contains(&self, gid: GlyphId16) -> bool {
279 (self.start_glyph_id..=self.end_glyph_id).contains(&gid)
280 }
281}
282
283impl ClassDefFormat2 {
284 fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
285 self.class_range_records.iter().flat_map(|rcd| {
286 (rcd.start_glyph_id.to_u16()..=rcd.end_glyph_id.to_u16())
287 .map(|gid| (GlyphId16::new(gid), rcd.class))
288 })
289 }
290}
291
292impl ClassDef {
293 pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
294 let (one, two) = match self {
295 Self::Format1(table) => (Some(table.iter()), None),
296 Self::Format2(table) => (None, Some(table.iter())),
297 };
298
299 one.into_iter().flatten().chain(two.into_iter().flatten())
300 }
301
302 pub fn get(&self, glyph: GlyphId16) -> u16 {
306 self.get_raw(glyph).unwrap_or(0)
307 }
308
309 fn get_raw(&self, glyph: GlyphId16) -> Option<u16> {
311 match self {
312 ClassDef::Format1(table) => glyph
313 .to_u16()
314 .checked_sub(table.start_glyph_id.to_u16())
315 .and_then(|idx| table.class_value_array.get(idx as usize))
316 .copied(),
317 ClassDef::Format2(table) => table
318 .class_range_records
319 .iter()
320 .find_map(|rec| rec.contains(glyph).then_some(rec.class)),
321 }
322 }
323
324 pub fn class_count(&self) -> u16 {
325 self.iter()
327 .map(|(_gid, cls)| cls)
328 .chain(std::iter::once(0))
329 .collect::<HashSet<_>>()
330 .len()
331 .try_into()
332 .unwrap()
333 }
334}
335
336impl CoverageFormat1 {
337 fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
338 self.glyph_array.iter().copied()
339 }
340
341 fn len(&self) -> usize {
342 self.glyph_array.len()
343 }
344}
345
346impl CoverageFormat2 {
347 fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
348 self.range_records
349 .iter()
350 .flat_map(|rcd| iter_gids(rcd.start_glyph_id, rcd.end_glyph_id))
351 }
352
353 fn len(&self) -> usize {
354 self.range_records
355 .iter()
356 .map(|rcd| {
357 rcd.end_glyph_id
358 .to_u16()
359 .saturating_sub(rcd.start_glyph_id.to_u16()) as usize
360 + 1
361 })
362 .sum()
363 }
364}
365
366impl CoverageTable {
367 pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
368 let (one, two) = match self {
369 Self::Format1(table) => (Some(table.iter()), None),
370 Self::Format2(table) => (None, Some(table.iter())),
371 };
372
373 one.into_iter().flatten().chain(two.into_iter().flatten())
374 }
375
376 pub fn len(&self) -> usize {
377 match self {
378 Self::Format1(table) => table.len(),
379 Self::Format2(table) => table.len(),
380 }
381 }
382
383 pub fn is_empty(&self) -> bool {
384 self.len() == 0
385 }
386}
387
388#[derive(Debug, PartialEq, Eq)]
392pub struct ClassDefBuilder {
393 pub items: BTreeMap<GlyphId16, u16>,
394}
395
396#[derive(Debug, Default, PartialEq, Eq)]
400pub struct CoverageTableBuilder {
401 glyphs: Vec<GlyphId16>,
403}
404
405impl FromIterator<GlyphId16> for CoverageTableBuilder {
406 fn from_iter<T: IntoIterator<Item = GlyphId16>>(iter: T) -> Self {
407 let glyphs = iter.into_iter().collect::<Vec<_>>();
408 CoverageTableBuilder::from_glyphs(glyphs)
409 }
410}
411
412impl FromIterator<GlyphId16> for CoverageTable {
413 fn from_iter<T: IntoIterator<Item = GlyphId16>>(iter: T) -> Self {
414 let glyphs = iter.into_iter().collect::<Vec<_>>();
415 CoverageTableBuilder::from_glyphs(glyphs).build()
416 }
417}
418
419impl CoverageTableBuilder {
420 pub fn from_glyphs(mut glyphs: Vec<GlyphId16>) -> Self {
422 glyphs.sort_unstable();
423 glyphs.dedup();
424 CoverageTableBuilder { glyphs }
425 }
426
427 pub fn add(&mut self, glyph: GlyphId16) -> u16 {
433 match self.glyphs.binary_search(&glyph) {
434 Ok(ix) => ix as u16,
435 Err(ix) => {
436 self.glyphs.insert(ix, glyph);
437 ix.try_into().unwrap()
439 }
440 }
441 }
442
443 pub fn build(self) -> CoverageTable {
447 if should_choose_coverage_format_2(&self.glyphs) {
448 CoverageTable::Format2(CoverageFormat2 {
449 range_records: RangeRecord::iter_for_glyphs(&self.glyphs).collect(),
450 })
451 } else {
452 CoverageTable::Format1(CoverageFormat1 {
453 glyph_array: self.glyphs,
454 })
455 }
456 }
457}
458
459impl FromIterator<(GlyphId16, u16)> for ClassDefBuilder {
460 fn from_iter<T: IntoIterator<Item = (GlyphId16, u16)>>(iter: T) -> Self {
461 Self {
462 items: iter.into_iter().filter(|(_, cls)| *cls != 0).collect(),
463 }
464 }
465}
466
467impl FromIterator<(GlyphId16, u16)> for ClassDef {
468 fn from_iter<T: IntoIterator<Item = (GlyphId16, u16)>>(iter: T) -> Self {
469 ClassDefBuilder::from_iter(iter).build()
470 }
471}
472
473impl ClassDefBuilder {
474 fn prefer_format_1(&self) -> bool {
475 const U16_LEN: usize = std::mem::size_of::<u16>();
476 const FORMAT1_HEADER_LEN: usize = U16_LEN * 3;
477 const FORMAT2_HEADER_LEN: usize = U16_LEN * 2;
478 const CLASS_RANGE_RECORD_LEN: usize = U16_LEN * 3;
479 if self.items.is_empty() {
481 return false;
482 }
483 let first = self.items.keys().next().map(|g| g.to_u16()).unwrap();
485 let last = self.items.keys().next_back().map(|g| g.to_u16()).unwrap();
486 let format1_array_len = (last - first) as usize + 1;
487 let len_format1 = FORMAT1_HEADER_LEN + format1_array_len * U16_LEN;
488 let len_format2 =
489 FORMAT2_HEADER_LEN + iter_class_ranges(&self.items).count() * CLASS_RANGE_RECORD_LEN;
490
491 len_format1 < len_format2
492 }
493
494 pub fn build(&self) -> ClassDef {
495 if self.prefer_format_1() {
496 let first = self.items.keys().next().map(|g| g.to_u16()).unwrap_or(0);
497 let last = self.items.keys().next_back().map(|g| g.to_u16());
498 let class_value_array = (first..=last.unwrap_or_default())
499 .map(|g| self.items.get(&GlyphId16::new(g)).copied().unwrap_or(0))
500 .collect();
501 ClassDef::Format1(ClassDefFormat1 {
502 start_glyph_id: self
503 .items
504 .keys()
505 .next()
506 .copied()
507 .unwrap_or(GlyphId16::NOTDEF),
508 class_value_array,
509 })
510 } else {
511 ClassDef::Format2(ClassDefFormat2 {
512 class_range_records: iter_class_ranges(&self.items).collect(),
513 })
514 }
515 }
516}
517
518fn iter_class_ranges(
519 values: &BTreeMap<GlyphId16, u16>,
520) -> impl Iterator<Item = ClassRangeRecord> + '_ {
521 let mut iter = values.iter();
522 let mut prev = None;
523
524 #[allow(clippy::while_let_on_iterator)]
525 std::iter::from_fn(move || {
526 while let Some((gid, class)) = iter.next() {
527 match prev.take() {
528 None => prev = Some((*gid, *gid, *class)),
529 Some((start, end, pclass)) if are_sequential(end, *gid) && pclass == *class => {
530 prev = Some((start, *gid, pclass))
531 }
532 Some((start_glyph_id, end_glyph_id, pclass)) => {
533 prev = Some((*gid, *gid, *class));
534 return Some(ClassRangeRecord {
535 start_glyph_id,
536 end_glyph_id,
537 class: pclass,
538 });
539 }
540 }
541 }
542 prev.take()
543 .map(|(start_glyph_id, end_glyph_id, class)| ClassRangeRecord {
544 start_glyph_id,
545 end_glyph_id,
546 class,
547 })
548 })
549}
550
551fn should_choose_coverage_format_2(glyphs: &[GlyphId16]) -> bool {
552 let format2_len = 4 + RangeRecord::iter_for_glyphs(glyphs).count() * 6;
553 let format1_len = 4 + glyphs.len() * 2;
554 format2_len < format1_len
555}
556
557impl RangeRecord {
558 pub fn iter_for_glyphs(glyphs: &[GlyphId16]) -> impl Iterator<Item = RangeRecord> + '_ {
564 let mut cur_range = glyphs.first().copied().map(|g| (g, g));
565 let mut len = 0u16;
566 let mut iter = glyphs.iter().skip(1).copied();
567
568 #[allow(clippy::while_let_on_iterator)]
569 std::iter::from_fn(move || {
570 while let Some(glyph) = iter.next() {
571 match cur_range {
572 None => return None,
573 Some((a, b)) if are_sequential(b, glyph) => cur_range = Some((a, glyph)),
574 Some((a, b)) => {
575 let result = RangeRecord {
576 start_glyph_id: a,
577 end_glyph_id: b,
578 start_coverage_index: len,
579 };
580 cur_range = Some((glyph, glyph));
581 len += 1 + b.to_u16().saturating_sub(a.to_u16());
582 return Some(result);
583 }
584 }
585 }
586 cur_range
587 .take()
588 .map(|(start_glyph_id, end_glyph_id)| RangeRecord {
589 start_glyph_id,
590 end_glyph_id,
591 start_coverage_index: len,
592 })
593 })
594 }
595}
596
597fn iter_gids(gid1: GlyphId16, gid2: GlyphId16) -> impl Iterator<Item = GlyphId16> {
598 (gid1.to_u16()..=gid2.to_u16()).map(GlyphId16::new)
599}
600
601fn are_sequential(gid1: GlyphId16, gid2: GlyphId16) -> bool {
602 gid2.to_u16().saturating_sub(gid1.to_u16()) == 1
603}
604
605impl Device {
606 pub fn new(start_size: u16, end_size: u16, values: &[i8]) -> Self {
607 debug_assert_eq!(
608 (start_size..=end_size).count(),
609 values.len(),
610 "device range and values must match"
611 );
612 let delta_format: DeltaFormat = values
613 .iter()
614 .map(|val| match val {
615 -2..=1 => DeltaFormat::Local2BitDeltas,
616 -8..=7 => DeltaFormat::Local4BitDeltas,
617 _ => DeltaFormat::Local8BitDeltas,
618 })
619 .max()
620 .unwrap_or_default();
621 let delta_value = encode_delta(delta_format, values);
622
623 Device {
624 start_size,
625 end_size,
626 delta_format,
627 delta_value,
628 }
629 }
630}
631
632impl DeviceOrVariationIndex {
633 pub fn device(start_size: u16, end_size: u16, values: &[i8]) -> Self {
635 DeviceOrVariationIndex::Device(Device::new(start_size, end_size, values))
636 }
637}
638
639impl FontWrite for PendingVariationIndex {
640 fn write_into(&self, _writer: &mut TableWriter) {
641 panic!(
642 "Attempted to write PendingVariationIndex.\n\
643 VariationIndex tables should always be resolved before compilation.\n\
644 Please report this bug at <https://github.com/googlefonts/fontations/issues>"
645 )
646 }
647}
648
649fn encode_delta(format: DeltaFormat, values: &[i8]) -> Vec<u16> {
650 let (chunk_size, mask, bits) = match format {
651 DeltaFormat::Local2BitDeltas => (8, 0b11, 2),
652 DeltaFormat::Local4BitDeltas => (4, 0b1111, 4),
653 DeltaFormat::Local8BitDeltas => (2, 0b11111111, 8),
654 _ => panic!("invalid format"),
655 };
656 values
657 .chunks(chunk_size)
658 .map(|chunk| encode_chunk(chunk, mask, bits))
659 .collect()
660}
661
662fn encode_chunk(chunk: &[i8], mask: u8, bits: usize) -> u16 {
663 let mut out = 0u16;
664 for (i, val) in chunk.iter().enumerate() {
665 out |= ((val.to_be_bytes()[0] & mask) as u16) << ((16 - bits) - i * bits);
666 }
667 out
668}
669
670impl From<VariationIndex> for u32 {
671 fn from(value: VariationIndex) -> Self {
672 ((value.delta_set_outer_index as u32) << 16) | value.delta_set_inner_index as u32
673 }
674}
675
676#[cfg(test)]
677mod tests {
678 use std::ops::RangeInclusive;
679
680 use super::*;
681
682 #[test]
683 #[should_panic(expected = "array exceeds max length")]
684 fn array_len_smoke_test() {
685 let table = ScriptList {
686 script_records: vec![ScriptRecord {
687 script_tag: Tag::new(b"hihi"),
688 script: OffsetMarker::new(Script {
689 default_lang_sys: NullableOffsetMarker::new(None),
690 lang_sys_records: vec![LangSysRecord {
691 lang_sys_tag: Tag::new(b"coco"),
692 lang_sys: OffsetMarker::new(LangSys {
693 required_feature_index: 0xffff,
694 feature_indices: vec![69; (u16::MAX) as usize + 5],
695 }),
696 }],
697 }),
698 }],
699 };
700
701 table.validate().unwrap();
702 }
703
704 #[test]
705 #[should_panic(expected = "larger than end_glyph_id")]
706 fn validate_classdef_ranges() {
707 let classdef = ClassDefFormat2::new(vec![ClassRangeRecord::new(
708 GlyphId16::new(12),
709 GlyphId16::new(3),
710 7,
711 )]);
712
713 classdef.validate().unwrap();
714 }
715
716 #[test]
717 fn classdef_format() {
718 let builder: ClassDefBuilder = [(3u16, 4u16), (4, 6), (5, 1), (9, 5), (10, 2), (11, 3)]
719 .map(|(gid, cls)| (GlyphId16::new(gid), cls))
720 .into_iter()
721 .collect();
722
723 assert!(builder.prefer_format_1());
724
725 let builder: ClassDefBuilder = [(1u16, 1u16), (3, 4), (9, 5), (10, 2), (11, 3)]
726 .map(|(gid, cls)| (GlyphId16::new(gid), cls))
727 .into_iter()
728 .collect();
729
730 assert!(builder.prefer_format_1());
731 }
732
733 #[test]
734 fn classdef_prefer_format2() {
735 fn iter_class_items(
736 start: u16,
737 end: u16,
738 cls: u16,
739 ) -> impl Iterator<Item = (GlyphId16, u16)> {
740 (start..=end).map(move |gid| (GlyphId16::new(gid), cls))
741 }
742
743 let builder: ClassDefBuilder = iter_class_items(5, 8, 3)
746 .chain(iter_class_items(9, 12, 4))
747 .chain(iter_class_items(13, 16, 5))
748 .collect();
749
750 assert!(!builder.prefer_format_1());
751 }
752
753 #[test]
754 fn delta_format_dflt() {
755 let some: DeltaFormat = Default::default();
756 assert_eq!(some, DeltaFormat::Local2BitDeltas);
757 }
758
759 #[test]
760 fn delta_encode() {
761 let inp = [1i8, 2, 3, -1];
762 let result = encode_delta(DeltaFormat::Local4BitDeltas, &inp);
763 assert_eq!(result.len(), 1);
764 assert_eq!(result[0], 0x123f_u16);
765
766 let inp = [1i8, 1, 1, 1, 1];
767 let result = encode_delta(DeltaFormat::Local2BitDeltas, &inp);
768 assert_eq!(result.len(), 1);
769 assert_eq!(result[0], 0x5540_u16);
770 }
771
772 fn make_glyph_vec<const N: usize>(gids: [u16; N]) -> Vec<GlyphId16> {
773 gids.into_iter().map(GlyphId16::new).collect()
774 }
775
776 #[test]
777 fn coverage_builder() {
778 let coverage = make_glyph_vec([1u16, 2, 9, 3, 6, 9])
779 .into_iter()
780 .collect::<CoverageTableBuilder>();
781 assert_eq!(coverage.glyphs, make_glyph_vec([1, 2, 3, 6, 9]));
782 }
783
784 fn make_class<const N: usize>(gid_class_pairs: [(u16, u16); N]) -> ClassDef {
785 gid_class_pairs
786 .iter()
787 .map(|(gid, cls)| (GlyphId16::new(*gid), *cls))
788 .collect::<ClassDefBuilder>()
789 .build()
790 }
791
792 #[test]
793 fn class_def_builder_zero() {
794 let class = make_class([(4, 0), (5, 1)]);
796 assert!(class.get_raw(GlyphId16::new(4)).is_none());
797 assert_eq!(class.get_raw(GlyphId16::new(5)), Some(1));
798 assert!(class.get_raw(GlyphId16::new(100)).is_none());
799 }
800
801 #[test]
804 fn class_def_builder_empty() {
805 let builder = ClassDefBuilder::from_iter([]);
806 let built = builder.build();
807
808 assert_eq!(
809 built,
810 ClassDef::Format2(ClassDefFormat2 {
811 class_range_records: vec![]
812 })
813 )
814 }
815
816 #[test]
817 fn class_def_small() {
818 let class = make_class([(1, 1), (2, 1), (3, 1)]);
819
820 assert_eq!(
821 class,
822 ClassDef::Format2(ClassDefFormat2 {
823 class_range_records: vec![ClassRangeRecord {
824 start_glyph_id: GlyphId16::new(1),
825 end_glyph_id: GlyphId16::new(3),
826 class: 1
827 }]
828 })
829 )
830 }
831
832 #[test]
833 fn classdef_f2_get() {
834 fn make_f2_class<const N: usize>(range: [RangeInclusive<u16>; N]) -> ClassDef {
835 ClassDefFormat2::new(
836 range
837 .into_iter()
838 .enumerate()
839 .map(|(i, range)| {
840 ClassRangeRecord::new(
841 GlyphId16::new(*range.start()),
842 GlyphId16::new(*range.end()),
843 (1 + i) as _,
844 )
845 })
846 .collect(),
847 )
848 .into()
849 }
850
851 let cls = make_f2_class([1..=1, 2..=9]);
852 assert_eq!(cls.get(GlyphId16::new(2)), 2);
853 assert_eq!(cls.get(GlyphId16::new(20)), 0);
854 }
855}