2 * Copyright (C) 2007,2008,2009 Red Hat, Inc.
4 * This is part of HarfBuzz, an OpenType Layout engine library.
6 * Permission is hereby granted, without written agreement and without
7 * license or royalty fees, to use, copy, modify, and distribute this
8 * software and its documentation for any purpose, provided that the
9 * above copyright notice and the following two paragraphs appear in
10 * all copies of this software.
12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
24 * Red Hat Author(s): Behdad Esfahbod
27 #ifndef HB_OT_LAYOUT_GSUB_PRIVATE_HH
28 #define HB_OT_LAYOUT_GSUB_PRIVATE_HH
30 #include "hb-ot-layout-gsubgpos-private.hh"
33 struct SingleSubstFormat1
35 friend struct SingleSubst;
39 inline bool apply (APPLY_ARG_DEF) const
41 hb_codepoint_t glyph_id = IN_CURGLYPH ();
42 unsigned int index = (this+coverage) (glyph_id);
43 if (HB_LIKELY (index == NOT_COVERED))
46 glyph_id += deltaGlyphID;
47 _hb_buffer_replace_glyph (buffer, glyph_id);
49 /* We inherit the old glyph class to the substituted glyph */
50 if (_hb_ot_layout_has_new_glyph_classes (context->face))
51 _hb_ot_layout_set_glyph_property (context->face, glyph_id, property);
56 inline bool sanitize (SANITIZE_ARG_DEF) {
58 return SANITIZE_THIS (coverage) && SANITIZE (deltaGlyphID);
62 USHORT format; /* Format identifier--format = 1 */
64 coverage; /* Offset to Coverage table--from
65 * beginning of Substitution table */
66 SHORT deltaGlyphID; /* Add to original GlyphID to get
67 * substitute GlyphID */
69 ASSERT_SIZE (SingleSubstFormat1, 6);
71 struct SingleSubstFormat2
73 friend struct SingleSubst;
77 inline bool apply (APPLY_ARG_DEF) const
79 hb_codepoint_t glyph_id = IN_CURGLYPH ();
80 unsigned int index = (this+coverage) (glyph_id);
81 if (HB_LIKELY (index == NOT_COVERED))
84 if (HB_UNLIKELY (index >= substitute.len))
87 glyph_id = substitute[index];
88 _hb_buffer_replace_glyph (buffer, glyph_id);
90 /* We inherit the old glyph class to the substituted glyph */
91 if (_hb_ot_layout_has_new_glyph_classes (context->face))
92 _hb_ot_layout_set_glyph_property (context->face, glyph_id, property);
97 inline bool sanitize (SANITIZE_ARG_DEF) {
99 return SANITIZE_THIS (coverage) && SANITIZE (substitute);
103 USHORT format; /* Format identifier--format = 2 */
105 coverage; /* Offset to Coverage table--from
106 * beginning of Substitution table */
108 substitute; /* Array of substitute
109 * GlyphIDs--ordered by Coverage Index */
111 ASSERT_SIZE (SingleSubstFormat2, 6);
115 friend struct SubstLookupSubTable;
119 inline bool apply (APPLY_ARG_DEF) const
122 case 1: return u.format1->apply (APPLY_ARG);
123 case 2: return u.format2->apply (APPLY_ARG);
124 default:return false;
128 inline bool sanitize (SANITIZE_ARG_DEF) {
130 if (!SANITIZE (u.format)) return false;
132 case 1: return u.format1->sanitize (SANITIZE_ARG);
133 case 2: return u.format2->sanitize (SANITIZE_ARG);
140 USHORT format; /* Format identifier */
141 SingleSubstFormat1 format1[];
142 SingleSubstFormat2 format2[];
145 ASSERT_SIZE (SingleSubst, 2);
150 friend struct MultipleSubstFormat1;
153 inline bool apply (APPLY_ARG_DEF) const
155 if (HB_UNLIKELY (!substitute.len))
158 _hb_buffer_add_output_glyphs (buffer, 1,
159 substitute.len, (const uint16_t *) substitute.array,
162 /* This is a guess only ... */
163 if (_hb_ot_layout_has_new_glyph_classes (context->face))
165 if (property == HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE)
166 property = HB_OT_LAYOUT_GLYPH_CLASS_BASE_GLYPH;
168 unsigned int count = substitute.len;
169 for (unsigned int n = 0; n < count; n++)
170 _hb_ot_layout_set_glyph_property (context->face, substitute[n], property);
177 inline bool sanitize (SANITIZE_ARG_DEF) {
179 return SANITIZE (substitute);
184 substitute; /* String of GlyphIDs to substitute */
186 ASSERT_SIZE (Sequence, 2);
188 struct MultipleSubstFormat1
190 friend struct MultipleSubst;
194 inline bool apply (APPLY_ARG_DEF) const
197 unsigned int index = (this+coverage) (IN_CURGLYPH ());
198 if (HB_LIKELY (index == NOT_COVERED))
201 return (this+sequence[index]).apply (APPLY_ARG);
204 inline bool sanitize (SANITIZE_ARG_DEF) {
206 return SANITIZE_THIS2 (coverage, sequence);
210 USHORT format; /* Format identifier--format = 1 */
212 coverage; /* Offset to Coverage table--from
213 * beginning of Substitution table */
214 OffsetArrayOf<Sequence>
215 sequence; /* Array of Sequence tables
216 * ordered by Coverage Index */
218 ASSERT_SIZE (MultipleSubstFormat1, 6);
222 friend struct SubstLookupSubTable;
226 inline bool apply (APPLY_ARG_DEF) const
229 case 1: return u.format1->apply (APPLY_ARG);
230 default:return false;
234 inline bool sanitize (SANITIZE_ARG_DEF) {
236 if (!SANITIZE (u.format)) return false;
238 case 1: return u.format1->sanitize (SANITIZE_ARG);
245 USHORT format; /* Format identifier */
246 MultipleSubstFormat1 format1[];
249 ASSERT_SIZE (MultipleSubst, 2);
252 typedef ArrayOf<GlyphID> AlternateSet; /* Array of alternate GlyphIDs--in
254 ASSERT_SIZE (AlternateSet, 2);
256 struct AlternateSubstFormat1
258 friend struct AlternateSubst;
262 inline bool apply (APPLY_ARG_DEF) const
264 hb_codepoint_t glyph_id = IN_CURGLYPH ();
266 unsigned int index = (this+coverage) (glyph_id);
267 if (HB_LIKELY (index == NOT_COVERED))
270 const AlternateSet &alt_set = this+alternateSet[index];
272 if (HB_UNLIKELY (!alt_set.len))
275 unsigned int alt_index = 0;
277 /* XXX callback to user to choose alternate
278 if (context->face->altfunc)
279 alt_index = (context->face->altfunc)(context->layout, buffer,
280 buffer->out_pos, glyph_id,
281 alt_set.len, alt_set.array);
284 if (HB_UNLIKELY (alt_index >= alt_set.len))
287 glyph_id = alt_set[alt_index];
289 _hb_buffer_replace_glyph (buffer, glyph_id);
291 /* We inherit the old glyph class to the substituted glyph */
292 if (_hb_ot_layout_has_new_glyph_classes (context->face))
293 _hb_ot_layout_set_glyph_property (context->face, glyph_id, property);
298 inline bool sanitize (SANITIZE_ARG_DEF) {
300 return SANITIZE_THIS2 (coverage, alternateSet);
304 USHORT format; /* Format identifier--format = 1 */
306 coverage; /* Offset to Coverage table--from
307 * beginning of Substitution table */
308 OffsetArrayOf<AlternateSet>
309 alternateSet; /* Array of AlternateSet tables
310 * ordered by Coverage Index */
312 ASSERT_SIZE (AlternateSubstFormat1, 6);
314 struct AlternateSubst
316 friend struct SubstLookupSubTable;
320 inline bool apply (APPLY_ARG_DEF) const
323 case 1: return u.format1->apply (APPLY_ARG);
324 default:return false;
328 inline bool sanitize (SANITIZE_ARG_DEF) {
330 if (!SANITIZE (u.format)) return false;
332 case 1: return u.format1->sanitize (SANITIZE_ARG);
339 USHORT format; /* Format identifier */
340 AlternateSubstFormat1 format1[];
343 ASSERT_SIZE (AlternateSubst, 2);
348 friend struct LigatureSet;
351 inline bool apply (APPLY_ARG_DEF, bool is_mark) const
354 unsigned int count = component.len;
355 unsigned int end = MIN (buffer->in_length, buffer->in_pos + context_length);
356 if (HB_UNLIKELY (buffer->in_pos + count > end))
359 for (i = 1, j = buffer->in_pos + 1; i < count; i++, j++)
361 while (_hb_ot_layout_skip_mark (context->face, IN_INFO (j), lookup_flag, &property))
363 if (HB_UNLIKELY (j + count - i == end))
368 if (!(property & HB_OT_LAYOUT_GLYPH_CLASS_MARK))
371 if (HB_LIKELY (IN_GLYPH (j) != component[i]))
374 /* This is just a guess ... */
375 if (_hb_ot_layout_has_new_glyph_classes (context->face))
376 _hb_ot_layout_set_glyph_class (context->face, ligGlyph,
377 is_mark ? HB_OT_LAYOUT_GLYPH_CLASS_MARK
378 : HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE);
380 if (j == buffer->in_pos + i) /* No input glyphs skipped */
381 /* We don't use a new ligature ID if there are no skipped
382 glyphs and the ligature already has an ID. */
383 _hb_buffer_add_output_glyphs (buffer, i,
384 1, (const uint16_t *) &ligGlyph,
386 IN_LIGID (buffer->in_pos) ?
387 0xFFFF : _hb_buffer_allocate_lig_id (buffer));
390 unsigned int lig_id = _hb_buffer_allocate_lig_id (buffer);
391 _hb_buffer_add_output_glyph (buffer, ligGlyph, 0xFFFF, lig_id);
393 /* Now we must do a second loop to copy the skipped glyphs to
394 `out' and assign component values to it. We start with the
395 glyph after the first component. Glyphs between component
396 i and i+1 belong to component i. Together with the lig_id
397 value it is later possible to check whether a specific
398 component value really belongs to a given ligature. */
400 for ( i = 1; i < count; i++ )
402 while (_hb_ot_layout_skip_mark (context->face, IN_CURINFO (), lookup_flag, NULL))
403 _hb_buffer_add_output_glyph (buffer, IN_CURGLYPH (), i - 1, lig_id);
408 /* TODO We should possibly reassign lig_id and component for any
409 * components of a previous ligature that s now being removed as part of
417 inline bool sanitize (SANITIZE_ARG_DEF) {
419 return SANITIZE2 (ligGlyph, component);
423 GlyphID ligGlyph; /* GlyphID of ligature to substitute */
424 HeadlessArrayOf<GlyphID>
425 component; /* Array of component GlyphIDs--start
426 * with the second component--ordered
427 * in writing direction */
429 ASSERT_SIZE (Ligature, 4);
433 friend struct LigatureSubstFormat1;
436 inline bool apply (APPLY_ARG_DEF, bool is_mark) const
438 unsigned int num_ligs = ligature.len;
439 for (unsigned int i = 0; i < num_ligs; i++)
441 const Ligature &lig = this+ligature[i];
442 if (lig.apply (APPLY_ARG, is_mark))
450 inline bool sanitize (SANITIZE_ARG_DEF) {
452 return SANITIZE_THIS (ligature);
456 OffsetArrayOf<Ligature>
457 ligature; /* Array LigatureSet tables
458 * ordered by preference */
460 ASSERT_SIZE (LigatureSet, 2);
462 struct LigatureSubstFormat1
464 friend struct LigatureSubst;
467 inline bool apply (APPLY_ARG_DEF) const
469 hb_codepoint_t glyph_id = IN_CURGLYPH ();
471 bool first_is_mark = !!(property & HB_OT_LAYOUT_GLYPH_CLASS_MARK);
473 unsigned int index = (this+coverage) (glyph_id);
474 if (HB_LIKELY (index == NOT_COVERED))
477 const LigatureSet &lig_set = this+ligatureSet[index];
478 return lig_set.apply (APPLY_ARG, first_is_mark);
481 inline bool sanitize (SANITIZE_ARG_DEF) {
483 return SANITIZE_THIS2 (coverage, ligatureSet);
487 USHORT format; /* Format identifier--format = 1 */
489 coverage; /* Offset to Coverage table--from
490 * beginning of Substitution table */
491 OffsetArrayOf<LigatureSet>
492 ligatureSet; /* Array LigatureSet tables
493 * ordered by Coverage Index */
495 ASSERT_SIZE (LigatureSubstFormat1, 6);
499 friend struct SubstLookupSubTable;
502 inline bool apply (APPLY_ARG_DEF) const
505 case 1: return u.format1->apply (APPLY_ARG);
506 default:return false;
510 inline bool sanitize (SANITIZE_ARG_DEF) {
512 if (!SANITIZE (u.format)) return false;
514 case 1: return u.format1->sanitize (SANITIZE_ARG);
521 USHORT format; /* Format identifier */
522 LigatureSubstFormat1 format1[];
525 ASSERT_SIZE (LigatureSubst, 2);
529 static inline bool substitute_lookup (APPLY_ARG_DEF, unsigned int lookup_index);
531 struct ContextSubst : Context
533 friend struct SubstLookupSubTable;
536 inline bool apply (APPLY_ARG_DEF) const
537 { return Context::apply (APPLY_ARG, substitute_lookup); }
539 ASSERT_SIZE (ContextSubst, 2);
541 struct ChainContextSubst : ChainContext
543 friend struct SubstLookupSubTable;
546 inline bool apply (APPLY_ARG_DEF) const
547 { return ChainContext::apply (APPLY_ARG, substitute_lookup); }
549 ASSERT_SIZE (ChainContextSubst, 2);
552 struct ExtensionSubst : Extension
554 friend struct SubstLookupSubTable;
557 inline const struct SubstLookupSubTable& get_subtable (void) const
558 { return CONST_CAST (SubstLookupSubTable, Extension::get_subtable (), 0); }
560 inline bool apply (APPLY_ARG_DEF) const;
562 inline bool sanitize (SANITIZE_ARG_DEF);
564 ASSERT_SIZE (ExtensionSubst, 2);
567 struct ReverseChainSingleSubstFormat1
569 friend struct ReverseChainSingleSubst;
572 inline bool apply (APPLY_ARG_DEF) const
574 if (HB_UNLIKELY (context_length != NO_CONTEXT))
575 return false; /* No chaining to this type */
577 unsigned int index = (this+coverage) (IN_CURGLYPH ());
578 if (HB_LIKELY (index == NOT_COVERED))
581 const OffsetArrayOf<Coverage> &lookahead = CONST_NEXT (OffsetArrayOf<Coverage>, backtrack);
582 const ArrayOf<GlyphID> &substitute = CONST_NEXT (ArrayOf<GlyphID>, lookahead);
584 if (match_backtrack (APPLY_ARG,
585 backtrack.len, (USHORT *) backtrack.array,
586 match_coverage, DECONST_CHARP(this)) &&
587 match_lookahead (APPLY_ARG,
588 lookahead.len, (USHORT *) lookahead.array,
589 match_coverage, DECONST_CHARP(this),
592 IN_CURGLYPH () = substitute[index];
593 buffer->in_pos--; /* Reverse! */
600 inline bool sanitize (SANITIZE_ARG_DEF) {
602 if (!SANITIZE_THIS2 (coverage, backtrack))
604 OffsetArrayOf<Coverage> &lookahead = NEXT (OffsetArrayOf<Coverage>, backtrack);
605 if (!SANITIZE_THIS (lookahead))
607 ArrayOf<GlyphID> &substitute = NEXT (ArrayOf<GlyphID>, lookahead);
608 return SANITIZE (substitute);
612 USHORT format; /* Format identifier--format = 1 */
614 coverage; /* Offset to Coverage table--from
615 * beginning of table */
616 OffsetArrayOf<Coverage>
617 backtrack; /* Array of coverage tables
618 * in backtracking sequence, in glyph
620 OffsetArrayOf<Coverage>
621 lookaheadX; /* Array of coverage tables
622 * in lookahead sequence, in glyph
625 substituteX; /* Array of substitute
626 * GlyphIDs--ordered by Coverage Index */
628 ASSERT_SIZE (ReverseChainSingleSubstFormat1, 10);
630 struct ReverseChainSingleSubst
632 friend struct SubstLookupSubTable;
635 inline bool apply (APPLY_ARG_DEF) const
638 case 1: return u.format1->apply (APPLY_ARG);
639 default:return false;
643 inline bool sanitize (SANITIZE_ARG_DEF) {
645 if (!SANITIZE (u.format)) return false;
647 case 1: return u.format1->sanitize (SANITIZE_ARG);
654 USHORT format; /* Format identifier */
655 ReverseChainSingleSubstFormat1 format1[];
658 ASSERT_SIZE (ReverseChainSingleSubst, 2);
666 struct SubstLookupSubTable
668 friend struct SubstLookup;
678 ReverseChainSingle = 8,
681 bool apply (APPLY_ARG_DEF, unsigned int lookup_type) const
683 switch (lookup_type) {
684 case Single: return u.single->apply (APPLY_ARG);
685 case Multiple: return u.multiple->apply (APPLY_ARG);
686 case Alternate: return u.alternate->apply (APPLY_ARG);
687 case Ligature: return u.ligature->apply (APPLY_ARG);
688 case Context: return u.context->apply (APPLY_ARG);
689 case ChainContext: return u.chainContext->apply (APPLY_ARG);
690 case Extension: return u.extension->apply (APPLY_ARG);
691 case ReverseChainSingle: return u.reverseChainContextSingle->apply (APPLY_ARG);
692 default:return false;
696 bool sanitize (SANITIZE_ARG_DEF) {
698 if (!SANITIZE (u.format)) return false;
700 case Single: return u.single->sanitize (SANITIZE_ARG);
701 case Multiple: return u.multiple->sanitize (SANITIZE_ARG);
702 case Alternate: return u.alternate->sanitize (SANITIZE_ARG);
703 case Ligature: return u.ligature->sanitize (SANITIZE_ARG);
704 case Context: return u.context->sanitize (SANITIZE_ARG);
705 case ChainContext: return u.chainContext->sanitize (SANITIZE_ARG);
706 case Extension: return u.extension->sanitize (SANITIZE_ARG);
707 case ReverseChainSingle: return u.reverseChainContextSingle->sanitize (SANITIZE_ARG);
715 SingleSubst single[];
716 MultipleSubst multiple[];
717 AlternateSubst alternate[];
718 LigatureSubst ligature[];
719 ContextSubst context[];
720 ChainContextSubst chainContext[];
721 ExtensionSubst extension[];
722 ReverseChainSingleSubst reverseChainContextSingle[];
725 ASSERT_SIZE (SubstLookupSubTable, 2);
728 struct SubstLookup : Lookup
730 inline const SubstLookupSubTable& get_subtable (unsigned int i) const
731 { return CONST_CAST (SubstLookupSubTable, Lookup::get_subtable (i), 0); }
733 /* Like get_type(), but looks through extension lookups.
734 * Never returns Extension */
735 inline unsigned int get_effective_type (void) const
737 unsigned int type = get_type ();
739 if (HB_UNLIKELY (type == SubstLookupSubTable::Extension))
741 unsigned int count = get_subtable_count ();
742 type = get_subtable(0).u.extension->get_type ();
743 /* The spec says all subtables should have the same type.
744 * This is specially important if one has a reverse type! */
745 for (unsigned int i = 1; i < count; i++)
746 if (get_subtable(i).u.extension->get_type () != type)
753 inline bool is_reverse (void) const
754 { return HB_UNLIKELY (get_effective_type () == SubstLookupSubTable::ReverseChainSingle); }
756 inline bool apply_once (hb_ot_layout_context_t *context,
758 unsigned int context_length,
759 unsigned int nesting_level_left) const
761 unsigned int lookup_type = get_type ();
762 unsigned int lookup_flag = get_flag ();
763 unsigned int property;
765 if (!_hb_ot_layout_check_glyph_property (context->face, IN_CURINFO (), lookup_flag, &property))
768 unsigned int count = get_subtable_count ();
769 for (unsigned int i = 0; i < count; i++)
770 if (get_subtable (i).apply (APPLY_ARG, lookup_type))
776 bool apply_string (hb_ot_layout_context_t *context,
778 hb_mask_t mask) const
782 if (HB_UNLIKELY (!buffer->in_length))
785 if (HB_LIKELY (!is_reverse ()))
787 /* in/out forward substitution */
788 _hb_buffer_clear_output (buffer);
790 while (buffer->in_pos < buffer->in_length)
792 if ((~IN_MASK (buffer->in_pos) & mask) &&
793 apply_once (context, buffer, NO_CONTEXT, MAX_NESTING_LEVEL))
796 _hb_buffer_next_glyph (buffer);
800 _hb_buffer_swap (buffer);
806 /* in-place backward substitution */
807 buffer->in_pos = buffer->in_length - 1;
810 if ((~IN_MASK (buffer->in_pos) & mask) &&
811 apply_once (context, buffer, NO_CONTEXT, MAX_NESTING_LEVEL))
817 while ((int) buffer->in_pos >= 0);
823 inline bool sanitize (SANITIZE_ARG_DEF) {
825 if (!Lookup::sanitize (SANITIZE_ARG)) return false;
826 OffsetArrayOf<SubstLookupSubTable> &list = (OffsetArrayOf<SubstLookupSubTable> &) subTable;
827 return SANITIZE_THIS (list);
830 ASSERT_SIZE (SubstLookup, 6);
832 typedef OffsetListOf<SubstLookup> SubstLookupList;
833 ASSERT_SIZE (SubstLookupList, 2);
839 struct GSUB : GSUBGPOS
841 static const hb_tag_t Tag = HB_OT_TAG_GSUB;
843 static inline const GSUB& get_for_data (const char *data)
844 { return (const GSUB&) GSUBGPOS::get_for_data (data); }
846 inline const SubstLookup& get_lookup (unsigned int i) const
847 { return (const SubstLookup&) GSUBGPOS::get_lookup (i); }
849 inline bool substitute_lookup (hb_ot_layout_context_t *context,
851 unsigned int lookup_index,
852 hb_mask_t mask) const
853 { return get_lookup (lookup_index).apply_string (context, buffer, mask); }
856 bool sanitize (SANITIZE_ARG_DEF) {
858 if (!GSUBGPOS::sanitize (SANITIZE_ARG)) return false;
859 OffsetTo<SubstLookupList> &list = CAST(OffsetTo<SubstLookupList>, lookupList, 0);
860 return SANITIZE_THIS (list);
863 ASSERT_SIZE (GSUB, 10);
866 /* Out-of-class implementation for methods recursing */
868 inline bool ExtensionSubst::apply (APPLY_ARG_DEF) const
870 unsigned int lookup_type = get_type ();
872 if (HB_UNLIKELY (lookup_type == SubstLookupSubTable::Extension))
875 return get_subtable ().apply (APPLY_ARG, lookup_type);
878 inline bool ExtensionSubst::sanitize (SANITIZE_ARG_DEF)
881 return Extension::sanitize (SANITIZE_ARG) &&
882 (&(Extension::get_subtable ()) == &Null(LookupSubTable) ||
883 get_type () == SubstLookupSubTable::Extension ||
884 DECONST_CAST (SubstLookupSubTable, get_subtable (), 0).sanitize (SANITIZE_ARG));
887 static inline bool substitute_lookup (APPLY_ARG_DEF, unsigned int lookup_index)
889 const GSUB &gsub = *(context->face->ot_layout.gsub);
890 const SubstLookup &l = gsub.get_lookup (lookup_index);
892 if (HB_UNLIKELY (nesting_level_left == 0))
894 nesting_level_left--;
896 if (HB_UNLIKELY (context_length < 1))
899 return l.apply_once (context, buffer, context_length, nesting_level_left);
903 #endif /* HB_OT_LAYOUT_GSUB_PRIVATE_HH */