[GSUB] Fix context_length handling in Ligature too
[framework/uifw/harfbuzz.git] / src / hb-ot-layout-gsub-private.h
1 /*
2  * Copyright (C) 2007,2008,2009  Red Hat, Inc.
3  *
4  *  This is part of HarfBuzz, an OpenType Layout engine library.
5  *
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.
11  *
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
16  * DAMAGE.
17  *
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.
23  *
24  * Red Hat Author(s): Behdad Esfahbod
25  */
26
27 #ifndef HB_OT_LAYOUT_GSUB_PRIVATE_H
28 #define HB_OT_LAYOUT_GSUB_PRIVATE_H
29
30 #include "hb-ot-layout-gsubgpos-private.h"
31
32 /* XXX */
33 #include "harfbuzz-impl.h"
34 HB_INTERNAL HB_Error
35 _hb_buffer_add_output_glyph_ids( HB_Buffer  buffer,
36                               HB_UShort  num_in,
37                               HB_UShort  num_out,
38                               const GlyphID *glyph_data,
39                               HB_UShort  component,
40                               HB_UShort  ligID );
41
42
43 struct SingleSubstFormat1 {
44
45   friend struct SingleSubst;
46
47   private:
48
49   inline bool single_substitute (hb_codepoint_t &glyph_id) const {
50
51     unsigned int index = (this+coverage) (glyph_id);
52     if (G_LIKELY (index == NOT_COVERED))
53       return false;
54
55     glyph_id += deltaGlyphID;
56
57     return true;
58   }
59
60   private:
61   USHORT        format;                 /* Format identifier--format = 1 */
62   OffsetTo<Coverage>
63                 coverage;               /* Offset to Coverage table--from
64                                          * beginning of Substitution table */
65   SHORT         deltaGlyphID;           /* Add to original GlyphID to get
66                                          * substitute GlyphID */
67 };
68 ASSERT_SIZE (SingleSubstFormat1, 6);
69
70 struct SingleSubstFormat2 {
71
72   friend struct SingleSubst;
73
74   private:
75
76   inline bool single_substitute (hb_codepoint_t &glyph_id) const {
77
78     unsigned int index = (this+coverage) (glyph_id);
79     if (G_LIKELY (index == NOT_COVERED))
80       return false;
81
82     if (G_UNLIKELY (index >= substitute.len))
83       return false;
84
85     glyph_id = substitute[index];
86     return true;
87   }
88
89   private:
90   USHORT        format;                 /* Format identifier--format = 2 */
91   OffsetTo<Coverage>
92                 coverage;               /* Offset to Coverage table--from
93                                          * beginning of Substitution table */
94   ArrayOf<GlyphID>
95                 substitute;             /* Array of substitute
96                                          * GlyphIDs--ordered by Coverage Index */
97 };
98 ASSERT_SIZE (SingleSubstFormat2, 6);
99
100 struct SingleSubst {
101
102   friend struct SubstLookupSubTable;
103
104   private:
105
106   inline bool single_substitute (hb_codepoint_t &glyph_id) const {
107     switch (u.format) {
108     case 1: return u.format1->single_substitute (glyph_id);
109     case 2: return u.format2->single_substitute (glyph_id);
110     default:return false;
111     }
112   }
113
114   inline bool substitute (LOOKUP_ARGS_DEF) const {
115
116     hb_codepoint_t glyph_id = IN_CURGLYPH ();
117
118     if (!single_substitute (glyph_id))
119       return false;
120
121     _hb_buffer_replace_glyph (buffer, glyph_id);
122
123     if ( _hb_ot_layout_has_new_glyph_classes (layout) )
124     {
125       /* we inherit the old glyph class to the substituted glyph */
126       _hb_ot_layout_set_glyph_property (layout, glyph_id, property);
127     }
128
129     return true;
130   }
131
132   private:
133   union {
134   USHORT                format;         /* Format identifier */
135   SingleSubstFormat1    format1[];
136   SingleSubstFormat2    format2[];
137   } u;
138 };
139 ASSERT_SIZE (SingleSubst, 2);
140
141
142 struct Sequence {
143
144   friend struct MultipleSubstFormat1;
145
146   private:
147
148   inline void set_glyph_class (hb_ot_layout_t *layout, unsigned int property) const {
149
150     unsigned int count = substitute.len;
151     for (unsigned int n = 0; n < count; n++)
152       _hb_ot_layout_set_glyph_property (layout, substitute[n], property);
153   }
154
155   inline bool substitute_sequence (LOOKUP_ARGS_DEF) const {
156
157     if (HB_UNLIKELY (!substitute.len))
158       return false;
159
160     _hb_buffer_add_output_glyph_ids (buffer, 1,
161                                      substitute.len, substitute.array,
162                                      0xFFFF, 0xFFFF);
163
164     if ( _hb_ot_layout_has_new_glyph_classes (layout) )
165     {
166       /* this is a guess only ... */
167
168       if ( property == HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE )
169         property = HB_OT_LAYOUT_GLYPH_CLASS_BASE_GLYPH;
170
171       set_glyph_class (layout, property);
172     }
173
174     return true;
175   }
176
177   private:
178   ArrayOf<GlyphID>
179                 substitute;             /* String of GlyphIDs to substitute */
180 };
181 ASSERT_SIZE (Sequence, 2);
182
183 struct MultipleSubstFormat1 {
184
185   friend struct MultipleSubst;
186
187   private:
188
189   inline bool substitute (LOOKUP_ARGS_DEF) const {
190
191     unsigned int index = (this+coverage) (IN_CURGLYPH ());
192     if (G_LIKELY (index == NOT_COVERED))
193       return false;
194
195     return (this+sequence[index]).substitute_sequence (LOOKUP_ARGS);
196   }
197
198   private:
199   USHORT        format;                 /* Format identifier--format = 1 */
200   OffsetTo<Coverage>
201                 coverage;               /* Offset to Coverage table--from
202                                          * beginning of Substitution table */
203   OffsetArrayOf<Sequence>
204                 sequence;               /* Array of Sequence tables
205                                          * ordered by Coverage Index */
206 };
207 ASSERT_SIZE (MultipleSubstFormat1, 6);
208
209 struct MultipleSubst {
210
211   friend struct SubstLookupSubTable;
212
213   private:
214
215   inline bool substitute (LOOKUP_ARGS_DEF) const {
216     switch (u.format) {
217     case 1: return u.format1->substitute (LOOKUP_ARGS);
218     default:return false;
219     }
220   }
221
222   private:
223   union {
224   USHORT                format;         /* Format identifier */
225   MultipleSubstFormat1  format1[];
226   } u;
227 };
228 ASSERT_SIZE (MultipleSubst, 2);
229
230
231 typedef ArrayOf<GlyphID> AlternateSet;  /* Array of alternate GlyphIDs--in
232                                          * arbitrary order */
233 ASSERT_SIZE (AlternateSet, 2);
234
235 struct AlternateSubstFormat1 {
236
237   friend struct AlternateSubst;
238
239   private:
240
241   inline bool substitute (LOOKUP_ARGS_DEF) const {
242
243     hb_codepoint_t glyph_id = IN_CURGLYPH ();
244
245     unsigned int index = (this+coverage) (glyph_id);
246     if (G_LIKELY (index == NOT_COVERED))
247       return false;
248
249     const AlternateSet &alt_set = this+alternateSet[index];
250
251     if (HB_UNLIKELY (!alt_set.len))
252       return false;
253
254     unsigned int alt_index = 0;
255
256     /* XXX callback to user to choose alternate
257     if ( gsub->altfunc )
258       alt_index = (gsub->altfunc)( buffer->out_pos, glyph_id,
259                                    aset.GlyphCount, aset.Alternate,
260                                    gsub->data );
261                                    */
262
263     if (HB_UNLIKELY (alt_index >= alt_set.len))
264       return false;
265
266     glyph_id = alt_set[alt_index];
267
268     _hb_buffer_replace_glyph (buffer, glyph_id);
269
270     if ( _hb_ot_layout_has_new_glyph_classes (layout) )
271     {
272       /* we inherit the old glyph class to the substituted glyph */
273       _hb_ot_layout_set_glyph_property (layout, glyph_id, property);
274     }
275
276     return true;
277   }
278
279   private:
280   USHORT        format;                 /* Format identifier--format = 1 */
281   OffsetTo<Coverage>
282                 coverage;               /* Offset to Coverage table--from
283                                          * beginning of Substitution table */
284   OffsetArrayOf<AlternateSet>
285                 alternateSet;           /* Array of AlternateSet tables
286                                          * ordered by Coverage Index */
287 };
288 ASSERT_SIZE (AlternateSubstFormat1, 6);
289
290 struct AlternateSubst {
291
292   friend struct SubstLookupSubTable;
293
294   private:
295
296   inline bool substitute (LOOKUP_ARGS_DEF) const {
297     switch (u.format) {
298     case 1: return u.format1->substitute (LOOKUP_ARGS);
299     default:return false;
300     }
301   }
302
303   private:
304   union {
305   USHORT                format;         /* Format identifier */
306   AlternateSubstFormat1 format1[];
307   } u;
308 };
309 ASSERT_SIZE (AlternateSubst, 2);
310
311
312 struct Ligature {
313
314   friend struct LigatureSet;
315
316   private:
317   inline bool substitute_ligature (LOOKUP_ARGS_DEF, bool is_mark) const {
318
319     unsigned int i, j;
320     unsigned int count = component.len;
321     unsigned int end = MIN (buffer->in_length, buffer->in_pos + context_length);
322     if (HB_UNLIKELY (buffer->in_pos + count > end))
323       return false;
324
325     for (i = 1, j = buffer->in_pos + 1; i < count; i++, j++) {
326       while (!_hb_ot_layout_check_glyph_property (layout, IN_ITEM (j), lookup_flag, &property)) {
327         if (HB_UNLIKELY (j + count - i == end))
328           return false;
329         j++;
330       }
331
332       if (!(property == HB_OT_LAYOUT_GLYPH_CLASS_MARK ||
333             property &  LookupFlag::MarkAttachmentType))
334         is_mark = FALSE;
335
336       if (HB_LIKELY (IN_GLYPH(j) != component[i]))
337         return false;
338     }
339     if ( _hb_ot_layout_has_new_glyph_classes (layout) )
340       /* this is just a guess ... */
341       hb_ot_layout_set_glyph_class (layout, ligGlyph,
342                                     is_mark ? HB_OT_LAYOUT_GLYPH_CLASS_MARK
343                                             : HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE);
344
345     if (j == buffer->in_pos + i) /* No input glyphs skipped */
346       /* We don't use a new ligature ID if there are no skipped
347          glyphs and the ligature already has an ID. */
348       _hb_buffer_add_output_glyph_ids (buffer, i,
349                                        1, &ligGlyph,
350                                        0xFFFF,
351                                        IN_LIGID (buffer->in_pos) ?
352                                        0xFFFF : _hb_buffer_allocate_ligid (buffer));
353     else
354     {
355       unsigned int lig_id = _hb_buffer_allocate_ligid (buffer);
356       _hb_buffer_add_output_glyph (buffer, ligGlyph, 0xFFFF, lig_id);
357
358       /* Now we must do a second loop to copy the skipped glyphs to
359          `out' and assign component values to it.  We start with the
360          glyph after the first component.  Glyphs between component
361          i and i+1 belong to component i.  Together with the lig_id
362          value it is later possible to check whether a specific
363          component value really belongs to a given ligature. */
364
365       for ( i = 1; i < count; i++ )
366       {
367         while (!_hb_ot_layout_check_glyph_property (layout, IN_CURITEM(), lookup_flag, &property))
368           _hb_buffer_add_output_glyph (buffer, IN_CURGLYPH(), i - 1, lig_id);
369
370         (buffer->in_pos)++;
371       }
372
373       /* XXX We  should possibly reassign lig_id and component for any
374        * components of a previous ligature that s now being removed as part of
375        * this ligature. */
376     }
377
378     return true;
379   }
380
381   private:
382   GlyphID       ligGlyph;               /* GlyphID of ligature to substitute */
383   HeadlessArrayOf<GlyphID>
384                 component;              /* Array of component GlyphIDs--start
385                                          * with the second  component--ordered
386                                          * in writing direction */
387 };
388 ASSERT_SIZE (Ligature, 4);
389
390 struct LigatureSet {
391
392   friend struct LigatureSubstFormat1;
393
394   private:
395
396   inline bool substitute_ligature (LOOKUP_ARGS_DEF, bool is_mark) const {
397
398     unsigned int num_ligs = ligature.len;
399     for (unsigned int i = 0; i < num_ligs; i++) {
400       const Ligature &lig = this+ligature[i];
401       if (lig.substitute_ligature (LOOKUP_ARGS, is_mark))
402         return true;
403     }
404
405     return false;
406   }
407
408   private:
409   OffsetArrayOf<Ligature>
410                 ligature;               /* Array LigatureSet tables
411                                          * ordered by preference */
412 };
413 ASSERT_SIZE (LigatureSet, 2);
414
415 struct LigatureSubstFormat1 {
416
417   friend struct LigatureSubst;
418
419   private:
420
421   inline bool substitute (LOOKUP_ARGS_DEF) const {
422
423     hb_codepoint_t glyph_id = IN_CURGLYPH ();
424
425     bool first_is_mark = (property == HB_OT_LAYOUT_GLYPH_CLASS_MARK ||
426                           property &  LookupFlag::MarkAttachmentType);
427
428     unsigned int index = (this+coverage) (glyph_id);
429     if (G_LIKELY (index == NOT_COVERED))
430       return false;
431
432     const LigatureSet &lig_set = this+ligatureSet[index];
433     return lig_set.substitute_ligature (LOOKUP_ARGS, first_is_mark);
434   }
435
436   private:
437   USHORT        format;                 /* Format identifier--format = 1 */
438   OffsetTo<Coverage>
439                 coverage;               /* Offset to Coverage table--from
440                                          * beginning of Substitution table */
441   OffsetArrayOf<LigatureSet>\
442                 ligatureSet;            /* Array LigatureSet tables
443                                          * ordered by Coverage Index */
444 };
445 ASSERT_SIZE (LigatureSubstFormat1, 6);
446
447 struct LigatureSubst {
448
449   friend struct SubstLookupSubTable;
450
451   private:
452
453   inline bool substitute (LOOKUP_ARGS_DEF) const {
454     switch (u.format) {
455     case 1: return u.format1->substitute (LOOKUP_ARGS);
456     default:return false;
457     }
458   }
459
460   private:
461   union {
462   USHORT                format;         /* Format identifier */
463   LigatureSubstFormat1  format1[];
464   } u;
465 };
466 ASSERT_SIZE (LigatureSubst, 2);
467
468
469
470 static inline bool substitute_lookup (LOOKUP_ARGS_DEF, unsigned int lookup_index);
471
472
473 struct ContextSubst : Context {
474
475   inline bool substitute (LOOKUP_ARGS_DEF) const {
476     return this->apply (LOOKUP_ARGS, substitute_lookup);
477   }
478 };
479 ASSERT_SIZE (ContextSubst, 2);
480
481
482 struct ChainContextSubst : ChainContext {
483
484   inline bool substitute (LOOKUP_ARGS_DEF) const {
485     return this->apply (LOOKUP_ARGS, substitute_lookup);
486   }
487 };
488 ASSERT_SIZE (ChainContextSubst, 2);
489
490
491 struct ExtensionSubstFormat1 {
492
493   friend struct ExtensionSubst;
494
495   private:
496   inline unsigned int get_type (void) const { return extensionLookupType; }
497   inline unsigned int get_offset (void) const { return (extensionOffset[0] << 16) + extensionOffset[1]; }
498   inline bool substitute (LOOKUP_ARGS_DEF) const;
499
500   private:
501   USHORT        format;                 /* Format identifier. Set to 1. */
502   USHORT        extensionLookupType;    /* Lookup type of subtable referenced
503                                          * by ExtensionOffset (i.e. the
504                                          * extension subtable). */
505   USHORT        extensionOffset[2];     /* Offset to the extension subtable,
506                                          * of lookup type subtable.
507                                          * Defined as two shorts to avoid
508                                          * alignment requirements. */
509 };
510 ASSERT_SIZE (ExtensionSubstFormat1, 8);
511
512 struct ExtensionSubst {
513
514   friend struct SubstLookup;
515   friend struct SubstLookupSubTable;
516
517   private:
518
519   inline unsigned int get_type (void) const {
520     switch (u.format) {
521     case 1: return u.format1->get_type ();
522     default:return 0;
523     }
524   }
525
526   inline bool substitute (LOOKUP_ARGS_DEF) const {
527     switch (u.format) {
528     case 1: return u.format1->substitute (LOOKUP_ARGS);
529     default:return false;
530     }
531   }
532
533   private:
534   union {
535   USHORT                format;         /* Format identifier */
536   ExtensionSubstFormat1 format1[];
537   } u;
538 };
539 ASSERT_SIZE (ExtensionSubst, 2);
540
541
542 struct ReverseChainSingleSubstFormat1 {
543   /* TODO */
544   inline bool substitute (LOOKUP_ARGS_DEF) const {
545     return false;
546   }
547
548   private:
549   USHORT        format;                 /* Format identifier--format = 1 */
550   Offset        coverage;               /* Offset to Coverage table -- from
551                                          * beginning of Substitution table */
552   USHORT        backtrackGlyphCount;    /* Number of glyphs in the backtracking
553                                          * sequence */
554   Offset        backtrackCoverage[];    /* Array of offsets to coverage tables
555                                          * in backtracking sequence, in  glyph
556                                          * sequence order */
557   USHORT        lookaheadGlyphCount;    /* Number of glyphs in lookahead
558                                          * sequence */
559   Offset        lookaheadCoverage[];    /* Array of offsets to coverage tables
560                                          * in lookahead sequence, in  glyph
561                                          * sequence order */
562   USHORT        glyphCount;             /* Number of GlyphIDs in the Substitute
563                                          * array */
564   GlyphID       substituteGlyphs[];     /* Array of substitute
565                                          * GlyphIDs--ordered by Coverage  Index */
566 };
567 ASSERT_SIZE (ReverseChainSingleSubstFormat1, 10);
568
569 struct ReverseChainSingleSubst {
570
571   friend struct SubstLookupSubTable;
572
573   private:
574
575   inline bool substitute (LOOKUP_ARGS_DEF) const {
576     switch (u.format) {
577     case 1: return u.format1->substitute (LOOKUP_ARGS);
578     default:return false;
579     }
580   }
581
582   private:
583   union {
584   USHORT                                format;         /* Format identifier */
585   ReverseChainSingleSubstFormat1        format1[];
586   } u;
587 };
588 ASSERT_SIZE (ReverseChainSingleSubst, 2);
589
590
591
592 /*
593  * SubstLookup
594  */
595
596 enum {
597   GSUB_Single                   = 1,
598   GSUB_Multiple                 = 2,
599   GSUB_Alternate                = 3,
600   GSUB_Ligature                 = 4,
601   GSUB_Context                  = 5,
602   GSUB_ChainContext             = 6,
603   GSUB_Extension                = 7,
604   GSUB_ReverseChainSingle       = 8,
605 };
606
607 struct SubstLookupSubTable {
608
609   friend struct SubstLookup;
610
611   inline bool substitute (LOOKUP_ARGS_DEF,
612                           unsigned int lookup_type) const {
613
614     switch (lookup_type) {
615     case GSUB_Single:                   return u.single->substitute (LOOKUP_ARGS);
616     case GSUB_Multiple:                 return u.multiple->substitute (LOOKUP_ARGS);
617     case GSUB_Alternate:                return u.alternate->substitute (LOOKUP_ARGS);
618     case GSUB_Ligature:                 return u.ligature->substitute (LOOKUP_ARGS);
619     case GSUB_Context:                  return u.context->substitute (LOOKUP_ARGS);
620     case GSUB_ChainContext:             return u.chainingContext->substitute (LOOKUP_ARGS);
621     case GSUB_Extension:                return u.extension->substitute (LOOKUP_ARGS);
622     case GSUB_ReverseChainSingle:       return u.reverseChainContextSingle->substitute (LOOKUP_ARGS);
623     default:return false;
624     }
625   }
626
627   private:
628   union {
629   USHORT                        format;
630   SingleSubst                   single[];
631   MultipleSubst                 multiple[];
632   AlternateSubst                alternate[];
633   LigatureSubst                 ligature[];
634   ContextSubst                  context[];
635   ChainContextSubst             chainingContext[];
636   ExtensionSubst                extension[];
637   ReverseChainSingleSubst       reverseChainContextSingle[];
638   } u;
639 };
640 ASSERT_SIZE (SubstLookupSubTable, 2);
641
642
643 struct SubstLookup : Lookup {
644
645   inline const SubstLookupSubTable& get_subtable (unsigned int i) const {
646     return *(SubstLookupSubTable*)&(((Lookup *)this)->get_subtable (i));
647   }
648
649   /* Like get_type(), but looks through extension lookups.
650    * Never returns Extension */
651   inline unsigned int get_effective_type (void) const {
652     unsigned int type = get_type ();
653
654     if (HB_UNLIKELY (type == GSUB_Extension)) {
655       /* Return lookup type of first extension subtable.
656        * The spec says all of them should have the same type.
657        * XXX check for that in sanitize() */
658       type = get_subtable(0).u.extension->get_type ();
659     }
660
661     return type;
662   }
663
664   inline bool is_reverse (void) const {
665     switch (get_effective_type ()) {
666     case GSUB_ReverseChainSingle:       return true;
667     default:                            return false;
668     }
669   }
670
671   inline bool substitute_subtables (hb_ot_layout_t *layout,
672                                     hb_buffer_t    *buffer,
673                                     unsigned int    context_length,
674                                     unsigned int    nesting_level_left,
675                                     unsigned int    property) const {
676     unsigned int lookup_type = get_type ();
677     unsigned int lookup_flag = get_flag ();
678
679     for (unsigned int i = 0; i < get_subtable_count (); i++)
680       if (get_subtable (i).substitute (LOOKUP_ARGS,
681                                        lookup_type))
682         return true;
683
684     return false;
685   }
686
687   inline bool substitute_once (hb_ot_layout_t *layout,
688                                hb_buffer_t    *buffer) const {
689
690     unsigned int lookup_flag = get_flag ();
691
692     unsigned int property;
693     if (!_hb_ot_layout_check_glyph_property (layout, IN_CURITEM (), lookup_flag, &property))
694       return false;
695
696     return substitute_subtables (layout, buffer, NO_CONTEXT, MAX_NESTING_LEVEL, property);
697   }
698
699   bool substitute_string (hb_ot_layout_t *layout,
700                           hb_buffer_t    *buffer,
701                           hb_ot_layout_feature_mask_t mask) const {
702
703     bool ret = false;
704
705     if (HB_UNLIKELY (!buffer->in_length))
706       return false;
707
708     if (HB_LIKELY (!is_reverse ())) {
709
710         /* in/out forward substitution */
711         _hb_buffer_clear_output (buffer);
712         buffer->in_pos = 0;
713         while (buffer->in_pos < buffer->in_length) {
714
715           if ((~IN_PROPERTIES (buffer->in_pos) & mask) &&
716               substitute_once (layout, buffer))
717             ret = true;
718           else
719             _hb_buffer_next_glyph (buffer);
720
721         }
722         if (ret)
723           _hb_buffer_swap (buffer);
724
725     } else {
726
727         /* in-place backward substitution */
728         buffer->in_pos = buffer->in_length - 1;
729         do {
730
731           if ((~IN_PROPERTIES (buffer->in_pos) & mask) &&
732               substitute_once (layout, buffer))
733             ret = true;
734           else
735             buffer->in_pos--;
736
737         } while ((int) buffer->in_pos >= 0);
738     }
739
740     return ret;
741   }
742 };
743 ASSERT_SIZE (SubstLookup, 6);
744
745
746 /*
747  * GSUB
748  */
749
750 struct GSUB : GSUBGPOS {
751   static const hb_tag_t Tag             = HB_TAG ('G','S','U','B');
752
753   STATIC_DEFINE_GET_FOR_DATA (GSUB);
754   /* XXX check version here? */
755
756   inline const SubstLookup& get_lookup (unsigned int i) const {
757     return *(SubstLookup*)&(((GSUBGPOS *)this)->get_lookup (i));
758   }
759
760   inline bool substitute_lookup (hb_ot_layout_t *layout,
761                                  hb_buffer_t    *buffer,
762                                  unsigned int    lookup_index,
763                                  hb_ot_layout_feature_mask_t  mask) const {
764     return get_lookup (lookup_index).substitute_string (layout, buffer, mask);
765   }
766
767 };
768 ASSERT_SIZE (GSUB, 10);
769
770
771 /* Out-of-class implementation for methods recursing */
772
773 inline bool ExtensionSubstFormat1::substitute (LOOKUP_ARGS_DEF) const {
774   unsigned int lookup_type = get_type ();
775
776   /* TODO: belongs to sanitize() */
777   if (HB_UNLIKELY (lookup_type == GSUB_ReverseChainSingle))
778     return false;
779
780   return (*(SubstLookupSubTable *)(((char *) this) + get_offset ())).substitute (LOOKUP_ARGS,
781                                                                                  lookup_type);
782 }
783
784 static inline bool substitute_lookup (LOOKUP_ARGS_DEF, unsigned int lookup_index) {
785   const GSUB &gsub = *(layout->gsub);
786   const SubstLookup &l = gsub.get_lookup (lookup_index);
787
788   if (HB_UNLIKELY (nesting_level_left == 0))
789     return false;
790   nesting_level_left--;
791
792   if (HB_UNLIKELY (context_length < 1))
793     return false;
794
795   return l.substitute_subtables (layout, buffer, context_length, nesting_level_left, property);
796 }
797
798
799 #endif /* HB_OT_LAYOUT_GSUB_PRIVATE_H */