[HB] GDEF sanitize()
[framework/uifw/harfbuzz.git] / src / hb-ot-layout-gsub-private.hh
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_HH
28 #define HB_OT_LAYOUT_GSUB_PRIVATE_HH
29
30 #include "hb-ot-layout-gsubgpos-private.hh"
31
32
33 struct SingleSubstFormat1
34 {
35   friend struct SingleSubst;
36
37   private:
38
39   inline bool apply (APPLY_ARG_DEF) const
40   {
41     hb_codepoint_t glyph_id = IN_CURGLYPH ();
42     unsigned int index = (this+coverage) (glyph_id);
43     if (HB_LIKELY (index == NOT_COVERED))
44       return false;
45
46     glyph_id += deltaGlyphID;
47     _hb_buffer_replace_glyph (buffer, glyph_id);
48
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);
52
53     return true;
54   }
55
56   inline bool sanitize (SANITIZE_ARG_DEF) {
57     return SANITIZE_THIS (coverage) && SANITIZE (deltaGlyphID);
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 apply (APPLY_ARG_DEF) const
77   {
78     hb_codepoint_t glyph_id = IN_CURGLYPH ();
79     unsigned int index = (this+coverage) (glyph_id);
80     if (HB_LIKELY (index == NOT_COVERED))
81       return false;
82
83     if (HB_UNLIKELY (index >= substitute.len))
84       return false;
85
86     glyph_id = substitute[index];
87     _hb_buffer_replace_glyph (buffer, glyph_id);
88
89     /* We inherit the old glyph class to the substituted glyph */
90     if (_hb_ot_layout_has_new_glyph_classes (context->face))
91       _hb_ot_layout_set_glyph_property (context->face, glyph_id, property);
92
93     return true;
94   }
95
96   inline bool sanitize (SANITIZE_ARG_DEF) {
97     return SANITIZE_THIS (coverage) && SANITIZE (substitute);
98   }
99
100   private:
101   USHORT        format;                 /* Format identifier--format = 2 */
102   OffsetTo<Coverage>
103                 coverage;               /* Offset to Coverage table--from
104                                          * beginning of Substitution table */
105   ArrayOf<GlyphID>
106                 substitute;             /* Array of substitute
107                                          * GlyphIDs--ordered by Coverage Index */
108 };
109 ASSERT_SIZE (SingleSubstFormat2, 6);
110
111 struct SingleSubst
112 {
113   friend struct SubstLookupSubTable;
114
115   private:
116
117   inline bool apply (APPLY_ARG_DEF) const
118   {
119     switch (u.format) {
120     case 1: return u.format1->apply (APPLY_ARG);
121     case 2: return u.format2->apply (APPLY_ARG);
122     default:return false;
123     }
124   }
125
126   inline bool sanitize (SANITIZE_ARG_DEF) {
127     if (!SANITIZE (u.format)) return false;
128     switch (u.format) {
129     case 1: return u.format1->sanitize (SANITIZE_ARG);
130     case 2: return u.format2->sanitize (SANITIZE_ARG);
131     default:return true;
132     }
133   }
134
135   private:
136   union {
137   USHORT                format;         /* Format identifier */
138   SingleSubstFormat1    format1[];
139   SingleSubstFormat2    format2[];
140   } u;
141 };
142 ASSERT_SIZE (SingleSubst, 2);
143
144
145 struct Sequence
146 {
147   friend struct MultipleSubstFormat1;
148
149   private:
150   inline bool apply (APPLY_ARG_DEF) const
151   {
152     if (HB_UNLIKELY (!substitute.len))
153       return false;
154
155     _hb_buffer_add_output_glyphs (buffer, 1,
156                                   substitute.len, (const uint16_t *) substitute.array,
157                                   0xFFFF, 0xFFFF);
158
159     /* This is a guess only ... */
160     if (_hb_ot_layout_has_new_glyph_classes (context->face))
161     {
162       if (property == HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE)
163         property = HB_OT_LAYOUT_GLYPH_CLASS_BASE_GLYPH;
164
165       unsigned int count = substitute.len;
166       for (unsigned int n = 0; n < count; n++)
167         _hb_ot_layout_set_glyph_property (context->face, substitute[n], property);
168     }
169
170     return true;
171   }
172
173   public:
174   inline bool sanitize (SANITIZE_ARG_DEF) {
175     return SANITIZE (substitute);
176   }
177
178   private:
179   ArrayOf<GlyphID>
180                 substitute;             /* String of GlyphIDs to substitute */
181 };
182 ASSERT_SIZE (Sequence, 2);
183
184 struct MultipleSubstFormat1
185 {
186   friend struct MultipleSubst;
187
188   private:
189
190   inline bool apply (APPLY_ARG_DEF) const
191   {
192
193     unsigned int index = (this+coverage) (IN_CURGLYPH ());
194     if (HB_LIKELY (index == NOT_COVERED))
195       return false;
196
197     return (this+sequence[index]).apply (APPLY_ARG);
198   }
199
200   inline bool sanitize (SANITIZE_ARG_DEF) {
201     return SANITIZE_THIS2 (coverage, sequence);
202   }
203
204   private:
205   USHORT        format;                 /* Format identifier--format = 1 */
206   OffsetTo<Coverage>
207                 coverage;               /* Offset to Coverage table--from
208                                          * beginning of Substitution table */
209   OffsetArrayOf<Sequence>
210                 sequence;               /* Array of Sequence tables
211                                          * ordered by Coverage Index */
212 };
213 ASSERT_SIZE (MultipleSubstFormat1, 6);
214
215 struct MultipleSubst
216 {
217   friend struct SubstLookupSubTable;
218
219   private:
220
221   inline bool apply (APPLY_ARG_DEF) const
222   {
223     switch (u.format) {
224     case 1: return u.format1->apply (APPLY_ARG);
225     default:return false;
226     }
227   }
228
229   inline bool sanitize (SANITIZE_ARG_DEF) {
230     if (!SANITIZE (u.format)) return false;
231     switch (u.format) {
232     case 1: return u.format1->sanitize (SANITIZE_ARG);
233     default:return true;
234     }
235   }
236
237   private:
238   union {
239   USHORT                format;         /* Format identifier */
240   MultipleSubstFormat1  format1[];
241   } u;
242 };
243 ASSERT_SIZE (MultipleSubst, 2);
244
245
246 typedef ArrayOf<GlyphID> AlternateSet;  /* Array of alternate GlyphIDs--in
247                                          * arbitrary order */
248 ASSERT_SIZE (AlternateSet, 2);
249
250 struct AlternateSubstFormat1
251 {
252   friend struct AlternateSubst;
253
254   private:
255
256   inline bool apply (APPLY_ARG_DEF) const
257   {
258     hb_codepoint_t glyph_id = IN_CURGLYPH ();
259
260     unsigned int index = (this+coverage) (glyph_id);
261     if (HB_LIKELY (index == NOT_COVERED))
262       return false;
263
264     const AlternateSet &alt_set = this+alternateSet[index];
265
266     if (HB_UNLIKELY (!alt_set.len))
267       return false;
268
269     unsigned int alt_index = 0;
270
271     /* XXX callback to user to choose alternate
272     if (context->face->altfunc)
273       alt_index = (context->face->altfunc)(context->layout, buffer,
274                                     buffer->out_pos, glyph_id,
275                                     alt_set.len, alt_set.array);
276                                    */
277
278     if (HB_UNLIKELY (alt_index >= alt_set.len))
279       return false;
280
281     glyph_id = alt_set[alt_index];
282
283     _hb_buffer_replace_glyph (buffer, glyph_id);
284
285     /* We inherit the old glyph class to the substituted glyph */
286     if (_hb_ot_layout_has_new_glyph_classes (context->face))
287       _hb_ot_layout_set_glyph_property (context->face, glyph_id, property);
288
289     return true;
290   }
291
292   inline bool sanitize (SANITIZE_ARG_DEF) {
293     return SANITIZE_THIS2 (coverage, alternateSet);
294   }
295
296   private:
297   USHORT        format;                 /* Format identifier--format = 1 */
298   OffsetTo<Coverage>
299                 coverage;               /* Offset to Coverage table--from
300                                          * beginning of Substitution table */
301   OffsetArrayOf<AlternateSet>
302                 alternateSet;           /* Array of AlternateSet tables
303                                          * ordered by Coverage Index */
304 };
305 ASSERT_SIZE (AlternateSubstFormat1, 6);
306
307 struct AlternateSubst
308 {
309   friend struct SubstLookupSubTable;
310
311   private:
312
313   inline bool apply (APPLY_ARG_DEF) const
314   {
315     switch (u.format) {
316     case 1: return u.format1->apply (APPLY_ARG);
317     default:return false;
318     }
319   }
320
321   inline bool sanitize (SANITIZE_ARG_DEF) {
322     if (!SANITIZE (u.format)) return false;
323     switch (u.format) {
324     case 1: return u.format1->sanitize (SANITIZE_ARG);
325     default:return true;
326     }
327   }
328
329   private:
330   union {
331   USHORT                format;         /* Format identifier */
332   AlternateSubstFormat1 format1[];
333   } u;
334 };
335 ASSERT_SIZE (AlternateSubst, 2);
336
337
338 struct Ligature
339 {
340   friend struct LigatureSet;
341
342   private:
343   inline bool apply (APPLY_ARG_DEF, bool is_mark) const
344   {
345     unsigned int i, j;
346     unsigned int count = component.len;
347     unsigned int end = MIN (buffer->in_length, buffer->in_pos + context_length);
348     if (HB_UNLIKELY (buffer->in_pos + count > end))
349       return false;
350
351     for (i = 1, j = buffer->in_pos + 1; i < count; i++, j++)
352     {
353       while (_hb_ot_layout_skip_mark (context->face, IN_INFO (j), lookup_flag, &property))
354       {
355         if (HB_UNLIKELY (j + count - i == end))
356           return false;
357         j++;
358       }
359
360       if (!(property & HB_OT_LAYOUT_GLYPH_CLASS_MARK))
361         is_mark = FALSE;
362
363       if (HB_LIKELY (IN_GLYPH (j) != component[i]))
364         return false;
365     }
366     /* This is just a guess ... */
367     if (_hb_ot_layout_has_new_glyph_classes (context->face))
368       _hb_ot_layout_set_glyph_class (context->face, ligGlyph,
369                                      is_mark ? HB_OT_LAYOUT_GLYPH_CLASS_MARK
370                                              : HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE);
371
372     if (j == buffer->in_pos + i) /* No input glyphs skipped */
373       /* We don't use a new ligature ID if there are no skipped
374          glyphs and the ligature already has an ID. */
375       _hb_buffer_add_output_glyphs (buffer, i,
376                                     1, (const uint16_t *) &ligGlyph,
377                                     0xFFFF,
378                                     IN_LIGID (buffer->in_pos) ?
379                                     0xFFFF : _hb_buffer_allocate_lig_id (buffer));
380     else
381     {
382       unsigned int lig_id = _hb_buffer_allocate_lig_id (buffer);
383       _hb_buffer_add_output_glyph (buffer, ligGlyph, 0xFFFF, lig_id);
384
385       /* Now we must do a second loop to copy the skipped glyphs to
386          `out' and assign component values to it.  We start with the
387          glyph after the first component.  Glyphs between component
388          i and i+1 belong to component i.  Together with the lig_id
389          value it is later possible to check whether a specific
390          component value really belongs to a given ligature. */
391
392       for ( i = 1; i < count; i++ )
393       {
394         while (_hb_ot_layout_skip_mark (context->face, IN_CURINFO (), lookup_flag, NULL))
395           _hb_buffer_add_output_glyph (buffer, IN_CURGLYPH (), i - 1, lig_id);
396
397         (buffer->in_pos)++;
398       }
399
400       /* TODO We should possibly reassign lig_id and component for any
401        * components of a previous ligature that s now being removed as part of
402        * this ligature. */
403     }
404
405     return true;
406   }
407
408   public:
409   inline bool sanitize (SANITIZE_ARG_DEF) {
410     return SANITIZE2 (ligGlyph, component);
411   }
412
413   private:
414   GlyphID       ligGlyph;               /* GlyphID of ligature to substitute */
415   HeadlessArrayOf<GlyphID>
416                 component;              /* Array of component GlyphIDs--start
417                                          * with the second  component--ordered
418                                          * in writing direction */
419 };
420 ASSERT_SIZE (Ligature, 4);
421
422 struct LigatureSet
423 {
424   friend struct LigatureSubstFormat1;
425
426   private:
427   inline bool apply (APPLY_ARG_DEF, bool is_mark) const
428   {
429     unsigned int num_ligs = ligature.len;
430     for (unsigned int i = 0; i < num_ligs; i++)
431     {
432       const Ligature &lig = this+ligature[i];
433       if (lig.apply (APPLY_ARG, is_mark))
434         return true;
435     }
436
437     return false;
438   }
439
440   public:
441   inline bool sanitize (SANITIZE_ARG_DEF) {
442     return SANITIZE_THIS (ligature);
443   }
444
445   private:
446   OffsetArrayOf<Ligature>
447                 ligature;               /* Array LigatureSet tables
448                                          * ordered by preference */
449 };
450 ASSERT_SIZE (LigatureSet, 2);
451
452 struct LigatureSubstFormat1
453 {
454   friend struct LigatureSubst;
455
456   private:
457   inline bool apply (APPLY_ARG_DEF) const
458   {
459     hb_codepoint_t glyph_id = IN_CURGLYPH ();
460
461     bool first_is_mark = !!(property & HB_OT_LAYOUT_GLYPH_CLASS_MARK);
462
463     unsigned int index = (this+coverage) (glyph_id);
464     if (HB_LIKELY (index == NOT_COVERED))
465       return false;
466
467     const LigatureSet &lig_set = this+ligatureSet[index];
468     return lig_set.apply (APPLY_ARG, first_is_mark);
469   }
470
471   inline bool sanitize (SANITIZE_ARG_DEF) {
472     return SANITIZE_THIS2 (coverage, ligatureSet);
473   }
474
475   private:
476   USHORT        format;                 /* Format identifier--format = 1 */
477   OffsetTo<Coverage>
478                 coverage;               /* Offset to Coverage table--from
479                                          * beginning of Substitution table */
480   OffsetArrayOf<LigatureSet>
481                 ligatureSet;            /* Array LigatureSet tables
482                                          * ordered by Coverage Index */
483 };
484 ASSERT_SIZE (LigatureSubstFormat1, 6);
485
486 struct LigatureSubst
487 {
488   friend struct SubstLookupSubTable;
489
490   private:
491   inline bool apply (APPLY_ARG_DEF) const
492   {
493     switch (u.format) {
494     case 1: return u.format1->apply (APPLY_ARG);
495     default:return false;
496     }
497   }
498
499   inline bool sanitize (SANITIZE_ARG_DEF) {
500     if (!SANITIZE (u.format)) return false;
501     switch (u.format) {
502     case 1: return u.format1->sanitize (SANITIZE_ARG);
503     default:return true;
504     }
505   }
506
507   private:
508   union {
509   USHORT                format;         /* Format identifier */
510   LigatureSubstFormat1  format1[];
511   } u;
512 };
513 ASSERT_SIZE (LigatureSubst, 2);
514
515
516
517 static inline bool substitute_lookup (APPLY_ARG_DEF, unsigned int lookup_index);
518
519 struct ContextSubst : Context
520 {
521   friend struct SubstLookupSubTable;
522
523   private:
524   inline bool apply (APPLY_ARG_DEF) const
525   { return Context::apply (APPLY_ARG, substitute_lookup); }
526 };
527 ASSERT_SIZE (ContextSubst, 2);
528
529 struct ChainContextSubst : ChainContext
530 {
531   friend struct SubstLookupSubTable;
532
533   private:
534   inline bool apply (APPLY_ARG_DEF) const
535   { return ChainContext::apply (APPLY_ARG, substitute_lookup); }
536 };
537 ASSERT_SIZE (ChainContextSubst, 2);
538
539
540 struct ExtensionSubst : Extension
541 {
542   friend struct SubstLookupSubTable;
543
544   private:
545   inline const struct SubstLookupSubTable& get_subtable (void) const
546   { return CONST_CAST (SubstLookupSubTable, Extension::get_subtable (), 0); }
547
548   inline bool apply (APPLY_ARG_DEF) const;
549
550   inline bool sanitize (SANITIZE_ARG_DEF);
551 };
552 ASSERT_SIZE (ExtensionSubst, 2);
553
554
555 struct ReverseChainSingleSubstFormat1
556 {
557   friend struct ReverseChainSingleSubst;
558
559   private:
560   inline bool apply (APPLY_ARG_DEF) const
561   {
562     if (HB_UNLIKELY (context_length != NO_CONTEXT))
563       return false; /* No chaining to this type */
564
565     unsigned int index = (this+coverage) (IN_CURGLYPH ());
566     if (HB_LIKELY (index == NOT_COVERED))
567       return false;
568
569     const OffsetArrayOf<Coverage> &lookahead = CONST_CAST (OffsetArrayOf<Coverage>, backtrack, backtrack.get_size ());
570     const ArrayOf<GlyphID> &substitute = CONST_CAST (ArrayOf<GlyphID>, lookahead, lookahead.get_size ());
571
572     if (match_backtrack (APPLY_ARG,
573                          backtrack.len, (USHORT *) backtrack.array,
574                          match_coverage, DECONST_CHARP(this)) &&
575         match_lookahead (APPLY_ARG,
576                          lookahead.len, (USHORT *) lookahead.array,
577                          match_coverage, DECONST_CHARP(this),
578                          1))
579     {
580       IN_CURGLYPH () = substitute[index];
581       buffer->in_pos--; /* Reverse! */
582       return true;
583     }
584
585     return false;
586   }
587
588   inline bool sanitize (SANITIZE_ARG_DEF) {
589     if (!SANITIZE_THIS2 (coverage, backtrack))
590       return false;
591     OffsetArrayOf<Coverage> &lookahead = CAST (OffsetArrayOf<Coverage>, backtrack, backtrack.get_size ());
592     if (!SANITIZE_THIS (lookahead))
593       return false;
594     ArrayOf<GlyphID> &substitute = CAST (ArrayOf<GlyphID>, lookahead, lookahead.get_size ());
595     if (!SANITIZE (substitute))
596       return false;
597   }
598
599   private:
600   USHORT        format;                 /* Format identifier--format = 1 */
601   OffsetTo<Coverage>
602                 coverage;               /* Offset to Coverage table--from
603                                          * beginning of table */
604   OffsetArrayOf<Coverage>
605                 backtrack;              /* Array of coverage tables
606                                          * in backtracking sequence, in  glyph
607                                          * sequence order */
608   OffsetArrayOf<Coverage>
609                 lookaheadX;             /* Array of coverage tables
610                                          * in lookahead sequence, in glyph
611                                          * sequence order */
612   ArrayOf<GlyphID>
613                 substituteX;            /* Array of substitute
614                                          * GlyphIDs--ordered by Coverage Index */
615 };
616 ASSERT_SIZE (ReverseChainSingleSubstFormat1, 10);
617
618 struct ReverseChainSingleSubst
619 {
620   friend struct SubstLookupSubTable;
621
622   private:
623   inline bool apply (APPLY_ARG_DEF) const
624   {
625     switch (u.format) {
626     case 1: return u.format1->apply (APPLY_ARG);
627     default:return false;
628     }
629   }
630
631   inline bool sanitize (SANITIZE_ARG_DEF) {
632     if (!SANITIZE (u.format)) return false;
633     switch (u.format) {
634     case 1: return u.format1->sanitize (SANITIZE_ARG);
635     default:return true;
636     }
637   }
638
639   private:
640   union {
641   USHORT                                format;         /* Format identifier */
642   ReverseChainSingleSubstFormat1        format1[];
643   } u;
644 };
645 ASSERT_SIZE (ReverseChainSingleSubst, 2);
646
647
648
649 /*
650  * SubstLookup
651  */
652
653 struct SubstLookupSubTable
654 {
655   friend struct SubstLookup;
656
657   enum {
658     Single              = 1,
659     Multiple            = 2,
660     Alternate           = 3,
661     Ligature            = 4,
662     Context             = 5,
663     ChainContext        = 6,
664     Extension           = 7,
665     ReverseChainSingle  = 8,
666   };
667
668   bool apply (APPLY_ARG_DEF, unsigned int lookup_type) const
669   {
670     switch (lookup_type) {
671     case Single:                return u.single->apply (APPLY_ARG);
672     case Multiple:              return u.multiple->apply (APPLY_ARG);
673     case Alternate:             return u.alternate->apply (APPLY_ARG);
674     case Ligature:              return u.ligature->apply (APPLY_ARG);
675     case Context:               return u.context->apply (APPLY_ARG);
676     case ChainContext:          return u.chainContext->apply (APPLY_ARG);
677     case Extension:             return u.extension->apply (APPLY_ARG);
678     case ReverseChainSingle:    return u.reverseChainContextSingle->apply (APPLY_ARG);
679     default:return false;
680     }
681   }
682
683   bool sanitize (SANITIZE_ARG_DEF) {
684     if (!SANITIZE (u.format)) return false;
685     switch (u.format) {
686     case Single:                return u.single->sanitize (SANITIZE_ARG);
687     case Multiple:              return u.multiple->sanitize (SANITIZE_ARG);
688     case Alternate:             return u.alternate->sanitize (SANITIZE_ARG);
689     case Ligature:              return u.ligature->sanitize (SANITIZE_ARG);
690     case Context:               return u.context->sanitize (SANITIZE_ARG);
691     case ChainContext:          return u.chainContext->sanitize (SANITIZE_ARG);
692     case Extension:             return u.extension->sanitize (SANITIZE_ARG);
693     case ReverseChainSingle:    return u.reverseChainContextSingle->sanitize (SANITIZE_ARG);
694     default:return true;
695     }
696   }
697
698   private:
699   union {
700   USHORT                        format;
701   SingleSubst                   single[];
702   MultipleSubst                 multiple[];
703   AlternateSubst                alternate[];
704   LigatureSubst                 ligature[];
705   ContextSubst                  context[];
706   ChainContextSubst             chainContext[];
707   ExtensionSubst                extension[];
708   ReverseChainSingleSubst       reverseChainContextSingle[];
709   } u;
710 };
711 ASSERT_SIZE (SubstLookupSubTable, 2);
712
713
714 struct SubstLookup : Lookup
715 {
716   inline const SubstLookupSubTable& get_subtable (unsigned int i) const
717   { return CONST_CAST (SubstLookupSubTable, Lookup::get_subtable (i), 0); }
718
719   /* Like get_type(), but looks through extension lookups.
720    * Never returns Extension */
721   inline unsigned int get_effective_type (void) const
722   {
723     unsigned int type = get_type ();
724
725     if (HB_UNLIKELY (type == SubstLookupSubTable::Extension))
726     {
727       unsigned int count = get_subtable_count ();
728       type = get_subtable(0).u.extension->get_type ();
729       /* The spec says all subtables should have the same type.
730        * This is specially important if one has a reverse type! */
731       for (unsigned int i = 1; i < count; i++)
732         if (get_subtable(i).u.extension->get_type () != type)
733           return 0;
734     }
735
736     return type;
737   }
738
739   inline bool is_reverse (void) const
740   { return HB_UNLIKELY (get_effective_type () == SubstLookupSubTable::ReverseChainSingle); }
741
742   inline bool apply_once (hb_ot_layout_context_t *context,
743                           hb_buffer_t    *buffer,
744                           unsigned int    context_length,
745                           unsigned int    nesting_level_left) const
746   {
747     unsigned int lookup_type = get_type ();
748     unsigned int lookup_flag = get_flag ();
749     unsigned int property;
750
751     if (!_hb_ot_layout_check_glyph_property (context->face, IN_CURINFO (), lookup_flag, &property))
752       return false;
753
754     unsigned int count = get_subtable_count ();
755     for (unsigned int i = 0; i < count; i++)
756       if (get_subtable (i).apply (APPLY_ARG, lookup_type))
757         return true;
758
759     return false;
760   }
761
762   bool apply_string (hb_ot_layout_context_t *context,
763                      hb_buffer_t    *buffer,
764                      hb_ot_layout_feature_mask_t mask) const
765   {
766     bool ret = false;
767
768     if (HB_UNLIKELY (!buffer->in_length))
769       return false;
770
771     if (HB_LIKELY (!is_reverse ()))
772     {
773         /* in/out forward substitution */
774         _hb_buffer_clear_output (buffer);
775         buffer->in_pos = 0;
776         while (buffer->in_pos < buffer->in_length)
777         {
778           if ((~IN_PROPERTIES (buffer->in_pos) & mask) &&
779               apply_once (context, buffer, NO_CONTEXT, MAX_NESTING_LEVEL))
780             ret = true;
781           else
782             _hb_buffer_next_glyph (buffer);
783
784         }
785         if (ret)
786           _hb_buffer_swap (buffer);
787
788     }
789     else
790     {
791
792         /* in-place backward substitution */
793         buffer->in_pos = buffer->in_length - 1;
794         do
795         {
796           if ((~IN_PROPERTIES (buffer->in_pos) & mask) &&
797               apply_once (context, buffer, NO_CONTEXT, MAX_NESTING_LEVEL))
798             ret = true;
799           else
800             buffer->in_pos--;
801
802         }
803         while ((int) buffer->in_pos >= 0);
804     }
805
806     return ret;
807   }
808
809   inline bool sanitize (SANITIZE_ARG_DEF) {
810     if (Lookup::sanitize (SANITIZE_ARG)) return false;
811     OffsetArrayOf<SubstLookupSubTable> &list = (OffsetArrayOf<SubstLookupSubTable> &) subTable;
812     return SANITIZE_THIS (list);
813   }
814 };
815 ASSERT_SIZE (SubstLookup, 6);
816
817 typedef OffsetListOf<SubstLookup> SubstLookupList;
818 ASSERT_SIZE (SubstLookupList, 2);
819
820 /*
821  * GSUB
822  */
823
824 struct GSUB : GSUBGPOS
825 {
826   static const hb_tag_t Tag             = HB_TAG ('G','S','U','B');
827
828   static inline const GSUB& get_for_data (const char *data)
829   { return (const GSUB&) GSUBGPOS::get_for_data (data); }
830
831   inline const SubstLookup& get_lookup (unsigned int i) const
832   { return (const SubstLookup&) GSUBGPOS::get_lookup (i); }
833
834   inline bool substitute_lookup (hb_ot_layout_context_t *context,
835                                  hb_buffer_t    *buffer,
836                                  unsigned int    lookup_index,
837                                  hb_ot_layout_feature_mask_t  mask) const
838   { return get_lookup (lookup_index).apply_string (context, buffer, mask); }
839
840
841   bool sanitize (SANITIZE_ARG_DEF) {
842     if (GSUBGPOS::sanitize (SANITIZE_ARG)) return false;
843     OffsetTo<SubstLookupList> &list = CAST(OffsetTo<SubstLookupList>, lookupList, 0);
844     return SANITIZE_THIS (list);
845   }
846 };
847 ASSERT_SIZE (GSUB, 10);
848
849
850 /* Out-of-class implementation for methods recursing */
851
852 inline bool ExtensionSubst::apply (APPLY_ARG_DEF) const
853 {
854   unsigned int lookup_type = get_type ();
855
856   if (HB_UNLIKELY (lookup_type == SubstLookupSubTable::Extension))
857     return false;
858
859   return get_subtable ().apply (APPLY_ARG, lookup_type);
860 }
861
862 inline bool ExtensionSubst::sanitize (SANITIZE_ARG_DEF)
863 {
864   return Extension::sanitize (SANITIZE_ARG) &&
865          (&(Extension::get_subtable ()) == &Null(LookupSubTable) ||
866           DECONST_CAST (SubstLookupSubTable, get_subtable (), 0).sanitize (SANITIZE_ARG));
867 }
868
869 static inline bool substitute_lookup (APPLY_ARG_DEF, unsigned int lookup_index)
870 {
871   const GSUB &gsub = *(context->face->ot_layout.gsub);
872   const SubstLookup &l = gsub.get_lookup (lookup_index);
873
874   if (HB_UNLIKELY (nesting_level_left == 0))
875     return false;
876   nesting_level_left--;
877
878   if (HB_UNLIKELY (context_length < 1))
879     return false;
880
881   return l.apply_once (context, buffer, context_length, nesting_level_left);
882 }
883
884
885 #endif /* HB_OT_LAYOUT_GSUB_PRIVATE_HH */