1use super::buffer::GlyphPropsFlags;
2use super::ot_layout::TableIndex;
3use super::{common::TagExt, set_digest::hb_set_digest_t};
4use crate::hb::hb_tag_t;
5use crate::hb::ot_layout_gsubgpos::{BinaryCache, MappingCache};
6use crate::hb::tables::TableRanges;
7use alloc::vec::Vec;
8use lookup::{LookupCache, LookupInfo};
9use read_fonts::tables::layout::{ClassRangeRecord, RangeRecord};
10use read_fonts::types::GlyphId16;
11use read_fonts::{
12 tables::{
13 gdef::Gdef,
14 gpos::{AnchorTable, DeviceOrVariationIndex, Gpos},
15 gsub::{ClassDef, FeatureList, FeatureVariations, Gsub, ScriptList},
16 layout::{Feature, LangSys, Script},
17 varc::{Condition, CoverageTable},
18 variations::{DeltaSetIndex, ItemVariationStore},
19 },
20 types::{BigEndian, F2Dot14, GlyphId, Offset32},
21 FontData, FontRef, ReadError, ResolveOffset, TableProvider,
22};
23
24pub mod contextual;
25pub mod gpos;
26pub mod gsub;
27pub mod lookup;
28
29pub struct OtCache {
30 pub gsub: LookupCache,
31 pub gpos: LookupCache,
32 pub gdef_glyph_props_cache: MappingCache,
33 pub gdef_mark_set_digests: Vec<hb_set_digest_t>,
34}
35
36impl OtCache {
37 pub fn new<'a>(font: &impl TableProvider<'a>) -> Self {
38 let gsub = font
39 .gsub()
40 .map(|t| LookupCache::new(&t))
41 .unwrap_or_default();
42 let gpos = font
43 .gpos()
44 .map(|t| LookupCache::new(&t))
45 .unwrap_or_default();
46 let mut gdef_mark_set_digests = Vec::new();
47 if let Ok(gdef) = font.gdef() {
48 if let Some(Ok(mark_sets)) = gdef.mark_glyph_sets_def() {
49 gdef_mark_set_digests.extend(mark_sets.coverages().iter().map(|set| {
50 set.ok()
51 .map(|coverage| hb_set_digest_t::from_coverage(&coverage))
52 .unwrap_or_default()
53 }));
54 }
55 }
56 Self {
57 gsub,
58 gpos,
59 gdef_glyph_props_cache: MappingCache::new(),
60 gdef_mark_set_digests,
61 }
62 }
63}
64
65#[derive(Clone)]
66pub struct GsubTable<'a> {
67 pub table: Gsub<'a>,
68 pub lookups: &'a LookupCache,
69}
70
71impl crate::hb::ot_layout::LayoutTable for GsubTable<'_> {
72 const INDEX: TableIndex = TableIndex::GSUB;
73 const IN_PLACE: bool = false;
74
75 fn get_lookup(&self, index: u16) -> Option<&LookupInfo> {
76 self.lookups.get(&self.table, index)
77 }
78}
79
80#[derive(Clone)]
81pub struct GposTable<'a> {
82 pub table: Gpos<'a>,
83 pub lookups: &'a LookupCache,
84}
85
86impl crate::hb::ot_layout::LayoutTable for GposTable<'_> {
87 const INDEX: TableIndex = TableIndex::GPOS;
88 const IN_PLACE: bool = true;
89
90 fn get_lookup(&self, index: u16) -> Option<&LookupInfo> {
91 self.lookups.get(&self.table, index)
92 }
93}
94
95#[derive(Clone, Default)]
96pub struct GdefTable<'a> {
97 pub(crate) table: Option<Gdef<'a>>,
98 classes: Option<ClassDef<'a>>,
99 mark_classes: Option<ClassDef<'a>>,
100 mark_sets: Option<(FontData<'a>, &'a [BigEndian<Offset32>])>,
101}
102
103impl<'a> GdefTable<'a> {
104 fn new(gdef: Gdef<'a>) -> Self {
105 let classes = gdef.glyph_class_def().transpose().ok().flatten();
106 let mark_classes = gdef.mark_attach_class_def().transpose().ok().flatten();
107 let mark_sets = gdef
108 .mark_glyph_sets_def()
109 .transpose()
110 .ok()
111 .flatten()
112 .map(|sets| (sets.offset_data(), sets.coverage_offsets()));
113 Self {
114 table: Some(gdef),
115 classes,
116 mark_classes,
117 mark_sets,
118 }
119 }
120}
121
122#[derive(Clone)]
123pub struct OtTables<'a> {
124 pub gsub: Option<GsubTable<'a>>,
125 pub gpos: Option<GposTable<'a>>,
126 pub gdef: GdefTable<'a>,
127 pub gdef_glyph_props_cache: &'a MappingCache,
128 pub gdef_mark_set_digests: &'a [hb_set_digest_t],
129 pub coords: &'a [F2Dot14],
130 pub var_store: Option<ItemVariationStore<'a>>,
131 pub feature_variations: [Option<u32>; 2],
132}
133
134impl<'a> OtTables<'a> {
135 pub fn new(
136 font: &FontRef<'a>,
137 cache: &'a OtCache,
138 table_offsets: &TableRanges,
139 coords: &'a [F2Dot14],
140 feature_variations: [Option<u32>; 2],
141 ) -> Self {
142 let gsub = table_offsets
143 .gsub
144 .resolve_table(font)
145 .map(|table| GsubTable {
146 table,
147 lookups: &cache.gsub,
148 });
149 let gpos = table_offsets
150 .gpos
151 .resolve_table(font)
152 .map(|table| GposTable {
153 table,
154 lookups: &cache.gpos,
155 });
156 let coords = if coords.iter().any(|coord| *coord != F2Dot14::ZERO) {
157 coords
158 } else {
159 &[]
160 };
161 let gdef = if is_gdef_blocklisted(
162 table_offsets.gdef.len(),
163 table_offsets.gsub.len(),
164 table_offsets.gpos.len(),
165 ) {
166 GdefTable::default()
167 } else {
168 table_offsets
169 .gdef
170 .resolve_table(font)
171 .map(GdefTable::new)
172 .unwrap_or_default()
173 };
174 let var_store = if !coords.is_empty() {
175 gdef.table
176 .as_ref()
177 .and_then(|gdef| gdef.item_var_store().transpose().ok().flatten())
178 } else {
179 None
180 };
181 Self {
182 gsub,
183 gpos,
184 gdef,
185 gdef_glyph_props_cache: &cache.gdef_glyph_props_cache,
186 gdef_mark_set_digests: &cache.gdef_mark_set_digests,
187 var_store,
188 coords,
189 feature_variations,
190 }
191 }
192
193 pub fn from_tables(
194 font: &impl TableProvider<'a>,
195 cache: &'a OtCache,
196 coords: &'a [F2Dot14],
197 feature_variations: [Option<u32>; 2],
198 ) -> Self {
199 let gsub = font.gsub().ok().map(|table| GsubTable {
200 table,
201 lookups: &cache.gsub,
202 });
203 let gpos = font.gpos().ok().map(|table| GposTable {
204 table,
205 lookups: &cache.gpos,
206 });
207 let coords = if coords.iter().any(|coord| *coord != F2Dot14::ZERO) {
208 coords
209 } else {
210 &[]
211 };
212 let gdef = if let Ok(gdef) = font.gdef() {
213 let gsub_len = gsub
214 .as_ref()
215 .map(|t| t.table.offset_data().len() as u32)
216 .unwrap_or_default();
217 let gpos_len = gpos
218 .as_ref()
219 .map(|t| t.table.offset_data().len() as u32)
220 .unwrap_or_default();
221 if is_gdef_blocklisted(gdef.offset_data().len() as u32, gsub_len, gpos_len) {
222 GdefTable::default()
223 } else {
224 GdefTable::new(gdef)
225 }
226 } else {
227 GdefTable::default()
228 };
229 let var_store = if !coords.is_empty() {
230 gdef.table
231 .as_ref()
232 .and_then(|gdef| gdef.item_var_store().transpose().ok().flatten())
233 } else {
234 None
235 };
236 Self {
237 gsub,
238 gpos,
239 gdef,
240 gdef_glyph_props_cache: &cache.gdef_glyph_props_cache,
241 gdef_mark_set_digests: &cache.gdef_mark_set_digests,
242 var_store,
243 coords,
244 feature_variations,
245 }
246 }
247
248 pub fn has_glyph_classes(&self) -> bool {
249 self.gdef.classes.is_some()
250 }
251
252 pub fn glyph_class(&self, glyph_id: u32) -> u16 {
253 self.gdef
254 .classes
255 .as_ref()
256 .map_or(0, |class_def| class_def.get(glyph_id))
257 }
258
259 pub fn glyph_mark_attachment_class(&self, glyph_id: u32) -> u16 {
260 self.gdef
261 .mark_classes
262 .as_ref()
263 .map_or(0, |class_def| class_def.get(glyph_id))
264 }
265
266 pub(crate) fn glyph_props(&self, glyph: GlyphId) -> u16 {
267 let glyph = glyph.to_u32();
268
269 if let Some(props) = self.gdef_glyph_props_cache.get(glyph) {
270 return props as u16;
271 }
272
273 let props = match self.glyph_class(glyph) {
274 1 => GlyphPropsFlags::BASE_GLYPH.bits(),
275 2 => GlyphPropsFlags::LIGATURE.bits(),
276 3 => {
277 let class = self.glyph_mark_attachment_class(glyph);
278 (class << 8) | GlyphPropsFlags::MARK.bits()
279 }
280 _ => 0,
281 };
282
283 self.gdef_glyph_props_cache.set(glyph, props as u32);
284
285 props
286 }
287
288 #[inline(never)]
289 pub fn is_mark_glyph_gdef(&self, glyph_id: u32, set_index: u16) -> bool {
290 self.gdef
291 .mark_sets
292 .as_ref()
293 .and_then(|(data, offsets)| Some((data, offsets.get(set_index as usize)?.get())))
294 .and_then(|(data, offset)| offset.resolve::<CoverageTable>(*data).ok())
295 .is_some_and(|coverage| coverage.get(glyph_id).is_some())
296 }
297
298 #[inline(always)]
299 pub fn is_mark_glyph(&self, glyph_id: u32, set_index: u16) -> bool {
300 if self
301 .gdef_mark_set_digests
302 .get(set_index as usize)
303 .is_some_and(|digest| digest.may_have(glyph_id))
304 {
305 self.is_mark_glyph_gdef(glyph_id, set_index)
306 } else {
307 false
308 }
309 }
310
311 pub fn table_data(&self, table_index: TableIndex) -> Option<&'a [u8]> {
312 if table_index == TableIndex::GSUB {
313 self.gsub.as_ref().map(|t| t.table.offset_data().as_bytes())
314 } else {
315 self.gpos.as_ref().map(|t| t.table.offset_data().as_bytes())
316 }
317 }
318
319 pub fn table_data_and_lookup(
320 &self,
321 table_index: TableIndex,
322 lookup_index: u16,
323 ) -> Option<(&'a [u8], &'a LookupInfo)> {
324 if table_index == TableIndex::GSUB {
325 let table = self.gsub.as_ref()?;
326 Some((
327 table.table.offset_data().as_bytes(),
328 table.lookups.get(&table.table, lookup_index)?,
329 ))
330 } else {
331 let table = self.gpos.as_ref()?;
332 Some((
333 table.table.offset_data().as_bytes(),
334 table.lookups.get(&table.table, lookup_index)?,
335 ))
336 }
337 }
338
339 pub(super) fn resolve_anchor(&self, anchor: &AnchorTable) -> (f32, f32) {
340 let mut x = anchor.x_coordinate() as f32;
341 let mut y = anchor.y_coordinate() as f32;
342 if let Some(vs) = self.var_store.as_ref() {
343 let delta = |val: Option<Result<DeviceOrVariationIndex<'_>, ReadError>>| match val {
346 Some(Ok(DeviceOrVariationIndex::VariationIndex(varix))) => {
347 vs.compute_float_delta(
348 DeltaSetIndex {
349 outer: varix.delta_set_outer_index(),
350 inner: varix.delta_set_inner_index(),
351 },
352 self.coords,
353 )
354 .unwrap_or_default()
355 .to_f64() as f32
356 }
357 _ => 0.0,
358 };
359 x += delta(anchor.x_device());
360 y += delta(anchor.y_device());
361 }
362 (x, y)
363 }
364}
365
366pub enum LayoutTable<'a> {
367 Gsub(Gsub<'a>),
368 Gpos(Gpos<'a>),
369}
370
371impl<'a> LayoutTable<'a> {
372 fn script_list(&self) -> Option<ScriptList<'a>> {
373 match self {
374 Self::Gsub(gsub) => gsub.script_list().ok(),
375 Self::Gpos(gpos) => gpos.script_list().ok(),
376 }
377 }
378
379 fn feature_list(&self) -> Option<FeatureList<'a>> {
380 match self {
381 Self::Gsub(gsub) => gsub.feature_list().ok(),
382 Self::Gpos(gpos) => gpos.feature_list().ok(),
383 }
384 }
385
386 fn feature_variations(&self) -> Option<FeatureVariations<'a>> {
387 match self {
388 Self::Gsub(gsub) => gsub.feature_variations(),
389 Self::Gpos(gpos) => gpos.feature_variations(),
390 }
391 .transpose()
392 .ok()
393 .flatten()
394 }
395
396 fn script(&self, index: u16) -> Option<Script<'a>> {
397 self.script_list()?
398 .get(index)
399 .ok()
400 .map(|script| script.element)
401 }
402
403 fn langsys_index(&self, script_index: u16, tag: hb_tag_t) -> Option<u16> {
404 let script = self.script(script_index)?;
405 script.lang_sys_index_for_tag(tag)
406 }
407
408 fn langsys(&self, script_index: u16, langsys_index: Option<u16>) -> Option<LangSys<'a>> {
409 let script = self.script(script_index)?;
410 if let Some(index) = langsys_index {
411 let record = script.lang_sys_records().get(index as usize)?;
412 record.lang_sys(script.offset_data()).ok()
413 } else {
414 script.default_lang_sys().transpose().ok().flatten()
415 }
416 }
417
418 pub(crate) fn feature(&self, index: u16) -> Option<Feature<'a>> {
419 self.feature_list()?
420 .get(index)
421 .ok()
422 .map(|feature| feature.element)
423 }
424
425 fn feature_tag(&self, index: u16) -> Option<hb_tag_t> {
426 self.feature_list()?
427 .get(index)
428 .ok()
429 .map(|feature| feature.tag)
430 }
431
432 pub(crate) fn feature_variation_index(&self, coords: &[F2Dot14]) -> Option<u32> {
433 let feature_variations = self.feature_variations()?;
434 for (index, rec) in feature_variations
435 .feature_variation_records()
436 .iter()
437 .enumerate()
438 {
439 if rec.condition_set_offset().is_null() {
442 return Some(index as u32);
443 }
444 let Some(Ok(condition_set)) = rec.condition_set(feature_variations.offset_data())
445 else {
446 continue;
447 };
448 if condition_set
450 .conditions()
451 .iter()
452 .filter_map(Result::ok)
454 .all(|cond| match cond {
455 Condition::Format1AxisRange(format1) => {
456 let coord = coords
457 .get(format1.axis_index() as usize)
458 .copied()
459 .unwrap_or_default();
460 coord >= format1.filter_range_min_value()
461 && coord <= format1.filter_range_max_value()
462 }
463 _ => false,
464 })
465 {
466 return Some(index as u32);
467 }
468 }
469 None
470 }
471
472 pub(crate) fn feature_substitution(
473 &self,
474 variation_index: u32,
475 feature_index: u16,
476 ) -> Option<Feature<'a>> {
477 let feature_variations = self.feature_variations()?;
478 let record = feature_variations
479 .feature_variation_records()
480 .get(variation_index as usize)?;
481 let subst_table = record
482 .feature_table_substitution(feature_variations.offset_data())?
483 .ok()?;
484 let subst_records = subst_table.substitutions();
485 match subst_records.binary_search_by_key(&feature_index, |subst| subst.feature_index()) {
486 Ok(ix) => Some(
487 subst_records
488 .get(ix)?
489 .alternate_feature(subst_table.offset_data())
490 .ok()?,
491 ),
492 _ => None,
493 }
494 }
495
496 pub(crate) fn feature_index(&self, tag: hb_tag_t) -> Option<u16> {
497 let list = self.feature_list()?;
498 for (index, feature) in list.feature_records().iter().enumerate() {
499 if feature.feature_tag() == tag {
500 return Some(index as u16);
501 }
502 }
503 None
504 }
505
506 pub(crate) fn lookup_count(&self) -> u16 {
507 match self {
508 Self::Gsub(gsub) => gsub
509 .lookup_list()
510 .map(|list| list.lookup_count())
511 .unwrap_or_default(),
512 Self::Gpos(gpos) => gpos
513 .lookup_list()
514 .map(|list| list.lookup_count())
515 .unwrap_or_default(),
516 }
517 }
518
519 pub(crate) fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, u16, hb_tag_t)> {
523 let selected = self.script_list()?.select(script_tags)?;
524 Some((!selected.is_fallback, selected.index, selected.tag))
525 }
526
527 pub(crate) fn select_script_language(
531 &self,
532 script_index: u16,
533 lang_tags: &[hb_tag_t],
534 ) -> Option<u16> {
535 for &tag in lang_tags {
536 if let Some(index) = self.langsys_index(script_index, tag) {
537 return Some(index);
538 }
539 }
540
541 if let Some(index) = self.langsys_index(script_index, hb_tag_t::default_language()) {
543 return Some(index);
544 }
545
546 None
547 }
548
549 pub(crate) fn get_required_language_feature(
553 &self,
554 script_index: u16,
555 lang_index: Option<u16>,
556 ) -> Option<(u16, hb_tag_t)> {
557 let sys = self.langsys(script_index, lang_index)?;
558 let idx = sys.required_feature_index();
559 if idx == 0xFFFF {
560 return None;
561 }
562 let tag = self.feature_tag(idx)?;
563 Some((idx, tag))
564 }
565
566 pub(crate) fn find_language_feature(
570 &self,
571 script_index: u16,
572 lang_index: Option<u16>,
573 feature_tag: hb_tag_t,
574 ) -> Option<u16> {
575 self.langsys(script_index, lang_index)?
576 .feature_index_for_tag(&self.feature_list()?, feature_tag)
577 }
578}
579
580fn coverage_index(coverage: Result<CoverageTable, ReadError>, gid: GlyphId) -> Option<u16> {
581 coverage.ok().and_then(|coverage| coverage.get(gid))
582}
583
584fn coverage_index_cached(
585 coverage: impl Fn(GlyphId) -> Option<u16>,
586 gid: GlyphId,
587 cache: &MappingCache,
588) -> Option<u16> {
589 if let Some(index) = cache.get(gid.into()) {
590 if index == MappingCache::MAX_VALUE {
591 None
592 } else {
593 Some(index as u16)
594 }
595 } else {
596 let index = coverage(gid);
597 if let Some(index) = index {
598 if (index as u32) < MappingCache::MAX_VALUE {
599 cache.set(gid.into(), index as u32);
600 }
601 Some(index)
602 } else {
603 cache.set(gid.into(), MappingCache::MAX_VALUE);
604 None
605 }
606 }
607}
608
609fn coverage_binary_cached(
610 coverage: impl Fn(GlyphId) -> Option<u16>,
611 gid: GlyphId,
612 cache: &BinaryCache,
613) -> Option<bool> {
614 if let Some(index) = cache.get(gid.into()) {
615 if index == BinaryCache::MAX_VALUE {
616 None
617 } else {
618 Some(true)
619 }
620 } else {
621 let index = coverage(gid);
622 if index.is_some() {
623 cache.set(gid.into(), 0);
624 Some(true)
625 } else {
626 cache.set(gid.into(), BinaryCache::MAX_VALUE);
627 None
628 }
629 }
630}
631
632fn covered(coverage: Result<CoverageTable, ReadError>, gid: GlyphId) -> bool {
633 coverage_index(coverage, gid).is_some()
634}
635
636fn glyph_class(class_def: Result<ClassDef, ReadError>, gid: GlyphId) -> u16 {
637 class_def
638 .map(|class_def| class_def.get(gid))
639 .unwrap_or_default()
640}
641
642fn glyph_class_cached(
643 class_def: impl Fn(GlyphId) -> u16,
644 gid: GlyphId,
645 cache: &MappingCache,
646) -> u16 {
647 if let Some(index) = cache.get(gid.into()) {
648 index as u16
649 } else {
650 let index = class_def(gid);
651 cache.set(gid.into(), index as u32);
652 index
653 }
654}
655
656#[derive(Copy, Clone, Default, Debug)]
657pub(crate) struct CoverageInfo {
658 pub offset: u16,
659 pub format: u16,
660 pub count: u16,
661}
662
663impl CoverageInfo {
664 pub fn new(parent_data: &FontData, offset: u16) -> Option<Self> {
665 if offset == 0 {
666 return None;
667 }
668 let format = parent_data.read_at::<u16>(offset as usize).ok()?;
669 if format != 1 && format != 2 {
670 return None;
671 }
672 let count = parent_data.read_at::<u16>(offset as usize + 2).ok()?;
673 Some(Self {
674 offset,
675 format,
676 count,
677 })
678 }
679
680 pub fn index(&self, parent_data: &FontData, gid: GlyphId) -> Option<u16> {
681 if self.offset == 0 {
682 return None;
683 }
684 let gid = gid.to_u32();
685 let data_offset = self.offset as usize + 4;
686 let len = self.count as usize;
687 if self.format == 1 {
688 let glyphs = parent_data
689 .read_array::<BigEndian<GlyphId16>>(data_offset..data_offset + len * 2)
690 .ok()?;
691 glyphs
692 .binary_search_by_key(&gid, |g| g.get().to_u32())
693 .ok()
694 .map(|idx| idx as _)
695 } else {
696 use core::cmp::Ordering;
697 let records = parent_data
698 .read_array::<RangeRecord>(
699 data_offset..data_offset + len * size_of::<RangeRecord>(),
700 )
701 .ok()?;
702 records
703 .binary_search_by(|rec| {
704 if rec.end_glyph_id().to_u32() < gid {
705 Ordering::Less
706 } else if rec.start_glyph_id().to_u32() > gid {
707 Ordering::Greater
708 } else {
709 Ordering::Equal
710 }
711 })
712 .ok()
713 .map(|idx| {
714 let rec = &records[idx];
715 (rec.start_coverage_index() as u32 + gid - rec.start_glyph_id().to_u32()) as u16
716 })
717 }
718 }
719}
720
721#[derive(Copy, Clone, Default, Debug)]
722pub(crate) struct ClassDefInfo {
723 pub offset: u16,
724 pub format: u16,
725 pub start_glyph_id: u16,
727 pub count: u16,
728}
729
730impl ClassDefInfo {
731 pub fn new(parent_data: &FontData, offset: u16) -> Option<Self> {
732 if offset == 0 {
733 return None;
734 }
735 let format = parent_data.read_at::<u16>(offset as usize).ok()?;
736 if format != 1 && format != 2 {
737 return None;
738 }
739 let (start_glyph_id, count) = if format == 1 {
740 let start_glyph_id = parent_data.read_at::<u16>(offset as usize + 2).ok()?;
741 let count = parent_data.read_at::<u16>(offset as usize + 4).ok()?;
742 (start_glyph_id, count)
743 } else if format == 2 {
744 let count = parent_data.read_at::<u16>(offset as usize + 2).ok()?;
745 (0, count)
746 } else {
747 return None;
748 };
749 Some(Self {
750 offset,
751 format,
752 start_glyph_id,
753 count,
754 })
755 }
756
757 pub fn class(&self, parent_data: &FontData, gid: GlyphId) -> u16 {
758 let offset = self.offset as usize;
759 if offset == 0 {
760 return 0;
761 }
762 let gid = gid.to_u32();
763 if self.format == 1 {
764 let Some(idx) = gid.checked_sub(self.start_glyph_id as u32) else {
765 return 0;
766 };
767 if idx >= self.count as u32 {
768 return 0;
769 }
770 parent_data
771 .read_at::<u16>(offset + 6 + idx as usize * 2)
772 .unwrap_or(0)
773 } else {
774 use core::cmp::Ordering;
775 let start = offset + 4;
776 let end = start + self.count as usize * size_of::<ClassRangeRecord>();
777 let Ok(records) = parent_data.read_array::<ClassRangeRecord>(start..end) else {
778 return 0;
779 };
780 records
781 .binary_search_by(|rec| {
782 if rec.end_glyph_id().to_u32() < gid {
783 Ordering::Less
784 } else if rec.start_glyph_id().to_u32() > gid {
785 Ordering::Greater
786 } else {
787 Ordering::Equal
788 }
789 })
790 .ok()
791 .map_or(0, |idx| records[idx].class())
792 }
793 }
794}
795
796use super::algs::HB_CODEPOINT_ENCODE3 as encode3;
797
798fn is_gdef_blocklisted(gdef_len: u32, gsub_len: u32, gpos_len: u32) -> bool {
816 const BLOCKLIST: &[u64] = &[
817 encode3(442, 2874, 42038),
819 encode3(430, 2874, 40662),
821 encode3(442, 2874, 39116),
823 encode3(430, 2874, 39374),
825 encode3(490, 3046, 41638),
827 encode3(478, 3046, 41902),
829 encode3(898, 12554, 46470),
831 encode3(910, 12566, 47732),
833 encode3(928, 23298, 59332),
835 encode3(940, 23310, 60732),
837 encode3(964, 23836, 60072),
839 encode3(976, 23832, 61456),
841 encode3(994, 24474, 60336),
843 encode3(1006, 24470, 61740),
845 encode3(1006, 24576, 61346),
847 encode3(1018, 24572, 62828),
849 encode3(1006, 24576, 61352),
851 encode3(1018, 24572, 62834),
853 encode3(832, 7324, 47162),
855 encode3(844, 7302, 45474),
857 encode3(180, 13054, 7254),
859 encode3(192, 12638, 7254),
861 encode3(192, 12690, 7254),
863 encode3(188, 248, 3852),
865 encode3(188, 264, 3426),
867 encode3(1058, 47032, 11818),
869 encode3(1046, 47030, 12600),
871 encode3(1058, 71796, 16770),
873 encode3(1046, 71790, 17862),
875 encode3(1046, 71788, 17112),
877 encode3(1058, 71794, 17514),
879 encode3(1330, 109_904, 57938),
881 encode3(1330, 109_904, 58972),
883 encode3(1004, 59092, 14836),
885 encode3(588, 5078, 14418),
887 encode3(588, 5078, 14238),
889 encode3(894, 17162, 33960),
891 encode3(894, 17154, 34472),
893 encode3(816, 7868, 17052),
895 encode3(816, 7868, 17138),
897 ];
898 let key = encode3(gdef_len, gsub_len, gpos_len);
899 BLOCKLIST.contains(&key)
900}