1use crate::hb::{
2 hb_font_t,
3 ot_layout::TableIndex,
4 ot_layout_gsubgpos::{
5 Apply, SubtableExternalCache, SubtableExternalCacheMode, WouldApply, WouldApplyContext,
6 OT::hb_ot_apply_context_t,
7 },
8 set_digest::hb_set_digest_t,
9 GlyphInfo,
10};
11use alloc::vec::Vec;
12use read_fonts::{
13 tables::{
14 gpos::{
15 CursivePosFormat1, Gpos, MarkBasePosFormat1, MarkLigPosFormat1, MarkMarkPosFormat1,
16 PairPos, PairPosFormat1, PairPosFormat2, SinglePos, SinglePosFormat1, SinglePosFormat2,
17 },
18 gsub::{
19 AlternateSubstFormat1, ExtensionSubstFormat1, Gsub, LigatureSubstFormat1,
20 MultipleSubstFormat1, ReverseChainSingleSubstFormat1, SingleSubst, SingleSubstFormat1,
21 SingleSubstFormat2,
22 },
23 layout::{
24 ChainedSequenceContext, ChainedSequenceContextFormat1, ChainedSequenceContextFormat2,
25 ChainedSequenceContextFormat3, CoverageTable, Lookup, LookupFlag, SequenceContext,
26 SequenceContextFormat1, SequenceContextFormat2, SequenceContextFormat3,
27 },
28 },
29 FontData, FontRead, Offset, ReadError,
30};
31
32pub struct LookupData<'a> {
33 offset: usize,
35 is_subst: bool,
37 table_data: FontData<'a>,
39}
40
41pub trait LookupHost<'a> {
42 fn lookup_count(&self) -> u16;
43 fn lookup_data(&self, index: u16) -> Result<LookupData<'a>, ReadError>;
44}
45
46impl<'a> LookupHost<'a> for Gsub<'a> {
47 fn lookup_count(&self) -> u16 {
48 self.lookup_list()
49 .map(|list| list.lookup_count())
50 .unwrap_or_default()
51 }
52
53 fn lookup_data(&self, index: u16) -> Result<LookupData<'a>, ReadError> {
54 let list = self.lookup_list()?;
55 let offset = list
56 .lookup_offsets()
57 .get(index as usize)
58 .ok_or(ReadError::OutOfBounds)?
59 .get()
60 .to_usize()
61 + self.lookup_list_offset().to_usize();
62 Ok(LookupData {
63 offset,
64 is_subst: true,
65 table_data: self.offset_data(),
66 })
67 }
68}
69
70impl<'a> LookupHost<'a> for Gpos<'a> {
71 fn lookup_count(&self) -> u16 {
72 self.lookup_list()
73 .map(|list| list.lookup_count())
74 .unwrap_or_default()
75 }
76
77 fn lookup_data(&self, index: u16) -> Result<LookupData<'a>, ReadError> {
78 let list = self.lookup_list()?;
79 let offset = list
80 .lookup_offsets()
81 .get(index as usize)
82 .ok_or(ReadError::OutOfBounds)?
83 .get()
84 .to_usize()
85 + self.lookup_list_offset().to_usize();
86 Ok(LookupData {
87 offset,
88 is_subst: false,
89 table_data: self.offset_data(),
90 })
91 }
92}
93
94#[cfg(feature = "std")]
95mod cache {
96 use super::{LookupHost, LookupInfo};
97 use std::sync::OnceLock;
98
99 #[derive(Default)]
100 pub(crate) struct LookupCache {
101 lookups: Vec<OnceLock<Option<Box<LookupInfo>>>>,
102 }
103
104 impl LookupCache {
105 pub fn new<'a>(host: &impl LookupHost<'a>) -> Self {
106 let mut lookups = Vec::new();
107 lookups.resize_with(host.lookup_count() as usize, Default::default);
108 Self { lookups }
109 }
110
111 pub fn get<'a>(&self, host: &impl LookupHost<'a>, index: u16) -> Option<&LookupInfo> {
112 self.lookups
113 .get(index as usize)?
114 .get_or_init(|| {
115 host.lookup_data(index)
116 .ok()
117 .and_then(|data| LookupInfo::new(&data))
118 .map(Box::new)
119 })
120 .as_ref()
121 .map(|v| &**v)
122 }
123 }
124}
125
126#[cfg(not(feature = "std"))]
127mod cache {
128 use super::{LookupHost, LookupInfo, Vec};
129
130 #[derive(Default)]
131 pub(crate) struct LookupCache {
132 lookups: Vec<Option<LookupInfo>>,
133 }
134
135 impl LookupCache {
136 pub fn new<'a>(host: &impl LookupHost<'a>) -> Self {
137 let count = host.lookup_count();
138 let mut lookups = Vec::with_capacity(count as usize);
139 for i in 0..count {
140 lookups.push(
141 host.lookup_data(i)
142 .ok()
143 .and_then(|data| LookupInfo::new(&data)),
144 );
145 }
146 Self { lookups }
147 }
148
149 pub fn get<'a>(&self, _host: &impl LookupHost<'a>, index: u16) -> Option<&LookupInfo> {
150 self.lookups.get(index as usize)?.as_ref()
151 }
152 }
153}
154
155pub(crate) use cache::LookupCache;
156
157fn is_extension_lookup_type(is_subst: bool, lookup_type: u8) -> bool {
158 (is_subst && lookup_type == 7) || (!is_subst && lookup_type == 9)
159}
160
161fn is_reversed(table_data: FontData, lookup: &Lookup<()>, lookup_offset: usize) -> Option<bool> {
162 match lookup.lookup_type() {
163 8 => Some(true),
165 7 => {
167 let offset = lookup_offset + lookup.subtable_offsets().first()?.get().to_usize();
168 let data = table_data.split_off(offset)?;
169 let ext = ExtensionSubstFormat1::<()>::read(data).ok()?;
170 if is_extension_lookup_type(true, ext.extension_lookup_type() as u8) {
171 return None;
172 }
173 Some(ext.extension_lookup_type() == 8)
174 }
175 _ => Some(false),
176 }
177}
178
179#[derive(Default)]
181pub struct LookupInfo {
182 pub props: u32,
183 pub is_subst: bool,
184 pub is_reversed: bool,
185 pub digest: hb_set_digest_t,
186 pub subtable_cache_user_idx: Option<usize>,
187 pub subtables: Vec<SubtableInfo>,
188}
189
190impl LookupInfo {
191 pub fn new(data: &LookupData) -> Option<Self> {
192 let mut info = Self {
193 is_subst: data.is_subst,
194 ..Default::default()
195 };
196 let lookup_data = data.table_data.split_off(data.offset)?;
197 let lookup: Lookup<()> = Lookup::read(lookup_data).ok()?;
198 let lookup_type = lookup.lookup_type();
199 let lookup_flag = lookup.lookup_flag();
200 info.props = u32::from(lookup.lookup_flag().to_bits());
201 if lookup_flag.to_bits() & LookupFlag::USE_MARK_FILTERING_SET.to_bits() != 0 {
202 info.props |= (lookup.mark_filtering_set().unwrap_or_default() as u32) << 16;
203 }
204 if data.is_subst {
205 info.is_reversed =
206 is_reversed(data.table_data, &lookup, data.offset).unwrap_or_default();
207 }
208 let mut subtable_cache_user_cost = 0;
209 info.subtables.reserve(lookup.sub_table_count() as usize);
210 for (idx, subtable_offset) in lookup.subtable_offsets().iter().enumerate() {
211 let cache_mode = if idx < 8 {
212 SubtableExternalCacheMode::Full
213 } else {
214 SubtableExternalCacheMode::Small
215 };
216 let subtable_offset = subtable_offset.get().to_usize() + data.offset;
217 if let Some((subtable_info, cache_cost)) = SubtableInfo::new(
218 data.table_data,
219 subtable_offset as u32,
220 data.is_subst,
221 lookup_type as u8,
222 cache_mode,
223 ) {
224 info.digest.union(&subtable_info.digest);
225 if cache_cost > subtable_cache_user_cost {
226 info.subtable_cache_user_idx = Some(info.subtables.len());
227 subtable_cache_user_cost = cache_cost;
228 }
229 info.subtables.push(subtable_info);
230 }
231 }
232 info.subtables.shrink_to_fit();
233 Some(info)
234 }
235
236 pub fn props(&self) -> u32 {
237 self.props
238 }
239
240 pub fn is_reverse(&self) -> bool {
241 self.is_reversed
242 }
243
244 pub fn digest(&self) -> &hb_set_digest_t {
245 &self.digest
246 }
247}
248
249impl LookupInfo {
250 #[inline]
251 pub(crate) fn apply(
252 &self,
253 ctx: &mut hb_ot_apply_context_t,
254 table_data: &[u8],
255 use_hot_subtable_cache: bool,
256 ) -> Option<()> {
257 let glyph = ctx.buffer.cur(0).glyph_id;
258 for (subtable_idx, subtable_info) in self.subtables.iter().enumerate() {
259 if !subtable_info.digest.may_have(glyph) {
260 continue;
261 }
262 let is_cached =
263 use_hot_subtable_cache && (self.subtable_cache_user_idx == Some(subtable_idx));
264 if subtable_info.apply(ctx, table_data, is_cached).is_some() {
265 return Some(());
266 }
267 }
268 None
269 }
270
271 pub(crate) fn cache_enter(&self, ctx: &mut hb_ot_apply_context_t) -> bool {
272 let Some(idx) = self.subtable_cache_user_idx else {
273 return false;
274 };
275 let Some(subtable_info) = self.subtables.get(idx) else {
276 return false;
277 };
278 if matches!(
279 subtable_info.kind,
280 SubtableKind::ContextFormat2 | SubtableKind::ChainedContextFormat2
281 ) {
282 cache_enter(ctx)
283 } else {
284 false
285 }
286 }
287 pub(crate) fn cache_leave(&self, ctx: &mut hb_ot_apply_context_t) {
288 let Some(idx) = self.subtable_cache_user_idx else {
289 return;
290 };
291 let Some(subtable_info) = self.subtables.get(idx) else {
292 return;
293 };
294 if matches!(
295 subtable_info.kind,
296 SubtableKind::ContextFormat2 | SubtableKind::ChainedContextFormat2
297 ) {
298 cache_leave(ctx);
299 }
300 }
301}
302
303impl LookupInfo {
304 pub fn would_apply(&self, face: &hb_font_t, ctx: &WouldApplyContext) -> Option<bool> {
305 let glyph = ctx.glyphs[0];
306 if !self.digest.may_have(glyph.into()) {
307 return Some(false);
308 }
309 let table_index = if self.is_subst {
310 TableIndex::GSUB
311 } else {
312 TableIndex::GPOS
313 };
314 let table_data = face.ot_tables.table_data(table_index)?;
315 for subtable_info in &self.subtables {
316 if !subtable_info.digest.may_have(glyph.into()) {
317 continue;
318 }
319 let Some(data) = table_data.get(subtable_info.offset as usize..) else {
320 continue;
321 };
322 let data = FontData::new(data);
323 let result = match subtable_info.kind {
324 SubtableKind::SingleSubst1 => {
325 SingleSubstFormat1::read(data).map(|t| t.would_apply(ctx))
326 }
327 SubtableKind::SingleSubst2 => {
328 SingleSubstFormat2::read(data).map(|t| t.would_apply(ctx))
329 }
330 SubtableKind::MultipleSubst1 => {
331 MultipleSubstFormat1::read(data).map(|t| t.would_apply(ctx))
332 }
333 SubtableKind::AlternateSubst1 => {
334 AlternateSubstFormat1::read(data).map(|t| t.would_apply(ctx))
335 }
336 SubtableKind::LigatureSubst1 => {
337 LigatureSubstFormat1::read(data).map(|t| t.would_apply(ctx))
338 }
339 SubtableKind::ReverseChainContext => {
340 ReverseChainSingleSubstFormat1::read(data).map(|t| t.would_apply(ctx))
341 }
342 SubtableKind::ContextFormat1 => {
343 SequenceContextFormat1::read(data).map(|t| t.would_apply(ctx))
344 }
345 SubtableKind::ContextFormat2 => {
346 SequenceContextFormat2::read(data).map(|t| t.would_apply(ctx))
347 }
348 SubtableKind::ContextFormat3 => {
349 SequenceContextFormat3::read(data).map(|t| t.would_apply(ctx))
350 }
351 SubtableKind::ChainedContextFormat1 => {
352 ChainedSequenceContextFormat1::read(data).map(|t| t.would_apply(ctx))
353 }
354 SubtableKind::ChainedContextFormat2 => {
355 ChainedSequenceContextFormat2::read(data).map(|t| t.would_apply(ctx))
356 }
357 SubtableKind::ChainedContextFormat3 => {
358 ChainedSequenceContextFormat3::read(data).map(|t| t.would_apply(ctx))
359 }
360 _ => continue,
361 };
362 if result == Ok(true) {
363 return Some(true);
364 }
365 }
366 None
367 }
368}
369
370pub struct SubtableInfo {
372 pub kind: SubtableKind,
374 pub offset: u32,
377 pub digest: hb_set_digest_t,
378 pub apply_fns: [SubtableApplyFn; 2],
379 pub external_cache: SubtableExternalCache,
380}
381
382pub type SubtableApplyFn =
383 fn(&mut hb_ot_apply_context_t, &SubtableExternalCache, &[u8]) -> Option<()>;
384
385impl SubtableInfo {
386 #[inline]
387 pub(crate) fn apply(
388 &self,
389 ctx: &mut hb_ot_apply_context_t,
390 table_data: &[u8],
391 is_cached: bool,
392 ) -> Option<()> {
393 let subtable_data = table_data.get(self.offset as usize..)?;
394 self.apply_fns[is_cached as usize](ctx, &self.external_cache, subtable_data)
395 }
396}
397
398macro_rules! apply_fns {
399 ($apply:ident, $apply_cached:ident, $ty:ident) => {
400 fn $apply(
401 ctx: &mut hb_ot_apply_context_t,
402 external_cache: &SubtableExternalCache,
403 table_data: &[u8],
404 ) -> Option<()> {
405 let t = $ty::read(FontData::new(table_data)).ok()?;
406 t.apply_with_external_cache(ctx, external_cache)
407 }
408
409 fn $apply_cached(
410 ctx: &mut hb_ot_apply_context_t,
411 external_cache: &SubtableExternalCache,
412 table_data: &[u8],
413 ) -> Option<()> {
414 let t = $ty::read(FontData::new(table_data)).ok()?;
415 t.apply_cached(ctx, external_cache)
416 }
417 };
418}
419
420apply_fns!(single_subst1, single_subst1_cached, SingleSubstFormat1);
421apply_fns!(single_subst2, single_subst2_cached, SingleSubstFormat2);
422apply_fns!(
423 multiple_subst1,
424 multiple_subst1_cached,
425 MultipleSubstFormat1
426);
427apply_fns!(
428 alternate_subst1,
429 alternate_subst1_cached,
430 AlternateSubstFormat1
431);
432apply_fns!(
433 ligature_subst1,
434 ligature_subst1_cached,
435 LigatureSubstFormat1
436);
437apply_fns!(single_pos1, single_pos1_cached, SinglePosFormat1);
438apply_fns!(single_pos2, single_pos2_cached, SinglePosFormat2);
439apply_fns!(pair_pos1, pair_pos1_cached, PairPosFormat1);
440apply_fns!(pair_pos2, pair_pos2_cached, PairPosFormat2);
441apply_fns!(cursive_pos1, cursive_pos1_cached, CursivePosFormat1);
442apply_fns!(mark_base_pos1, mark_base_pos1_cached, MarkBasePosFormat1);
443apply_fns!(mark_mark_pos1, mark_mark_pos1_cached, MarkMarkPosFormat1);
444apply_fns!(mark_lig_pos1, mark_lig_pos1_cached, MarkLigPosFormat1);
445apply_fns!(context1, context1_cached, SequenceContextFormat1);
446apply_fns!(context2, context2_cached, SequenceContextFormat2);
447apply_fns!(context3, context3_cached, SequenceContextFormat3);
448apply_fns!(
449 chained_context1,
450 chained_context1_cached,
451 ChainedSequenceContextFormat1
452);
453apply_fns!(
454 chained_context2,
455 chained_context2_cached,
456 ChainedSequenceContextFormat2
457);
458apply_fns!(
459 chained_context3,
460 chained_context3_cached,
461 ChainedSequenceContextFormat3
462);
463apply_fns!(
464 rev_chain_single_subst1,
465 rev_chain_single_subst1_cached,
466 ReverseChainSingleSubstFormat1
467);
468
469#[derive(Copy, Clone, PartialEq, Eq, Debug)]
471pub enum SubtableKind {
472 SingleSubst1,
473 SingleSubst2,
474 MultipleSubst1,
475 AlternateSubst1,
476 LigatureSubst1,
477 SinglePos1,
478 SinglePos2,
479 PairPos1,
480 PairPos2,
481 CursivePos1,
482 MarkBasePos1,
483 MarkMarkPos1,
484 MarkLigPos1,
485 ContextFormat1,
486 ContextFormat2,
487 ContextFormat3,
488 ChainedContextFormat1,
489 ChainedContextFormat2,
490 ChainedContextFormat3,
491 ReverseChainContext,
492}
493
494impl SubtableInfo {
495 fn new(
496 table_data: FontData,
497 subtable_offset: u32,
498 is_subst: bool,
499 lookup_type: u8,
500 cache_mode: SubtableExternalCacheMode,
501 ) -> Option<(Self, u32)> {
502 let data = table_data.split_off(subtable_offset as usize)?;
503 let maybe_external_cache = |s: &dyn Apply| s.external_cache_create(cache_mode);
504 let (kind, (external_cache, cache_cost, coverage), apply_fns): (
505 SubtableKind,
506 (SubtableExternalCache, u32, CoverageTable),
507 [SubtableApplyFn; 2],
508 ) = match (is_subst, lookup_type) {
509 (true, 1) => match SingleSubst::read(data).ok()? {
510 SingleSubst::Format1(s) => (
511 SubtableKind::SingleSubst1,
512 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
513 [single_subst1, single_subst1_cached as _],
514 ),
515 SingleSubst::Format2(s) => (
516 SubtableKind::SingleSubst2,
517 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
518 [single_subst2, single_subst2_cached as _],
519 ),
520 },
521 (false, 1) => match SinglePos::read(data).ok()? {
522 SinglePos::Format1(s) => (
523 SubtableKind::SinglePos1,
524 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
525 [single_pos1, single_pos1_cached as _],
526 ),
527 SinglePos::Format2(s) => (
528 SubtableKind::SinglePos2,
529 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
530 [single_pos2, single_pos2_cached as _],
531 ),
532 },
533 (true, 2) => (
534 SubtableKind::MultipleSubst1,
535 MultipleSubstFormat1::read(data).ok().and_then(|t| {
536 Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?))
537 })?,
538 [multiple_subst1, multiple_subst1_cached as _],
539 ),
540 (false, 2) => match PairPos::read(data).ok()? {
541 PairPos::Format1(s) => (
542 SubtableKind::PairPos1,
543 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
544 [pair_pos1, pair_pos1_cached as _],
545 ),
546 PairPos::Format2(s) => (
547 SubtableKind::PairPos2,
548 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
549 [pair_pos2, pair_pos2_cached as _],
550 ),
551 },
552 (true, 3) => (
553 SubtableKind::AlternateSubst1,
554 AlternateSubstFormat1::read(data).ok().and_then(|t| {
555 Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?))
556 })?,
557 [alternate_subst1, alternate_subst1_cached as _],
558 ),
559 (false, 3) => (
560 SubtableKind::CursivePos1,
561 CursivePosFormat1::read(data).ok().and_then(|t| {
562 Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?))
563 })?,
564 [cursive_pos1, cursive_pos1_cached as _],
565 ),
566 (true, 4) => (
567 SubtableKind::LigatureSubst1,
568 LigatureSubstFormat1::read(data).ok().and_then(|t| {
569 Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?))
570 })?,
571 [ligature_subst1, ligature_subst1_cached as _],
572 ),
573 (false, 4) => (
574 SubtableKind::MarkBasePos1,
575 MarkBasePosFormat1::read(data).ok().and_then(|t| {
576 Some((
577 maybe_external_cache(&t),
578 t.cache_cost(),
579 t.mark_coverage().ok()?,
580 ))
581 })?,
582 [mark_base_pos1, mark_base_pos1_cached as _],
583 ),
584 (true, 5) | (false, 7) => match SequenceContext::read(data).ok()? {
585 SequenceContext::Format1(s) => (
586 SubtableKind::ContextFormat1,
587 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
588 [context1, context1_cached as _],
589 ),
590 SequenceContext::Format2(s) => (
591 SubtableKind::ContextFormat2,
592 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
593 [context2, context2_cached as _],
594 ),
595 SequenceContext::Format3(s) => (
596 SubtableKind::ContextFormat3,
597 (
598 maybe_external_cache(&s),
599 s.cache_cost(),
600 s.coverages().get(0).ok()?,
601 ),
602 [context3, context3_cached as _],
603 ),
604 },
605 (false, 5) => (
606 SubtableKind::MarkLigPos1,
607 MarkLigPosFormat1::read(data).ok().and_then(|t| {
608 Some((
609 maybe_external_cache(&t),
610 t.cache_cost(),
611 t.mark_coverage().ok()?,
612 ))
613 })?,
614 [mark_lig_pos1, mark_lig_pos1_cached as _],
615 ),
616 (true, 6) | (false, 8) => match ChainedSequenceContext::read(data).ok()? {
617 ChainedSequenceContext::Format1(s) => (
618 SubtableKind::ChainedContextFormat1,
619 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
620 [chained_context1, chained_context1_cached as _],
621 ),
622 ChainedSequenceContext::Format2(s) => (
623 SubtableKind::ChainedContextFormat2,
624 (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?),
625 [chained_context2, chained_context2_cached as _],
626 ),
627 ChainedSequenceContext::Format3(s) => (
628 SubtableKind::ChainedContextFormat3,
629 (
630 maybe_external_cache(&s),
631 s.cache_cost(),
632 s.input_coverages().get(0).ok()?,
633 ),
634 [chained_context3, chained_context3_cached as _],
635 ),
636 },
637 (true, 7) | (false, 9) => {
638 let ext = ExtensionSubstFormat1::<'_, ()>::read(data).ok()?;
639 let ext_type = ext.extension_lookup_type() as u8;
640 if is_extension_lookup_type(is_subst, ext_type) {
641 return None;
642 }
643 let ext_offset = ext.extension_offset().to_u32();
644 return Self::new(
645 table_data,
646 subtable_offset.checked_add(ext_offset)?,
647 is_subst,
648 ext_type,
649 cache_mode,
650 );
651 }
652 (false, 6) => (
653 SubtableKind::MarkMarkPos1,
654 MarkMarkPosFormat1::read(data).ok().and_then(|t| {
655 Some((
656 maybe_external_cache(&t),
657 t.cache_cost(),
658 t.mark1_coverage().ok()?,
659 ))
660 })?,
661 [mark_mark_pos1, mark_mark_pos1_cached as _],
662 ),
663 (true, 8) => (
664 SubtableKind::ReverseChainContext,
665 ReverseChainSingleSubstFormat1::read(data)
666 .ok()
667 .and_then(|t| {
668 Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?))
669 })?,
670 [rev_chain_single_subst1, rev_chain_single_subst1_cached as _],
671 ),
672 _ => return None,
673 };
674 let mut digest = hb_set_digest_t::new();
675 digest.add_coverage(&coverage);
676 Some((
677 SubtableInfo {
678 kind,
679 offset: subtable_offset,
680 digest,
681 apply_fns,
682 external_cache,
683 },
684 cache_cost,
685 ))
686 }
687}
688
689fn cache_enter(ctx: &mut hb_ot_apply_context_t) -> bool {
690 if !ctx.buffer.try_allocate_var(GlyphInfo::SYLLABLE_VAR) {
691 return false;
692 }
693 for info in &mut ctx.buffer.info {
694 info.set_syllable(255);
695 }
696 ctx.new_syllables = Some(255);
697 true
698}
699
700fn cache_leave(ctx: &mut hb_ot_apply_context_t) {
701 ctx.new_syllables = None;
702 ctx.buffer.deallocate_var(GlyphInfo::SYLLABLE_VAR);
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708
709 fn lookup_with_recursive_extension(lookup_type: u16) -> [u8; 16] {
710 let mut data = [0; 16];
711 data[0..2].copy_from_slice(&lookup_type.to_be_bytes());
713 data[4..6].copy_from_slice(&1u16.to_be_bytes());
714 data[6..8].copy_from_slice(&8u16.to_be_bytes());
715 data[8..10].copy_from_slice(&1u16.to_be_bytes());
717 data[10..12].copy_from_slice(&lookup_type.to_be_bytes());
718 data
719 }
720
721 #[test]
722 fn gsub_extension_lookup_cannot_target_extension_lookup() {
723 let data = lookup_with_recursive_extension(7);
724 let lookup = LookupData {
725 offset: 0,
726 is_subst: true,
727 table_data: FontData::new(&data),
728 };
729 let info = LookupInfo::new(&lookup).unwrap();
730
731 assert!(info.subtables.is_empty());
732 }
733
734 #[test]
735 fn gpos_extension_lookup_cannot_target_extension_lookup() {
736 let data = lookup_with_recursive_extension(9);
737 let lookup = LookupData {
738 offset: 0,
739 is_subst: false,
740 table_data: FontData::new(&data),
741 };
742 let info = LookupInfo::new(&lookup).unwrap();
743
744 assert!(info.subtables.is_empty());
745 }
746}