hb-ot-layout-gsub-table.hh revision 4d3aeb8cb2bc1ca7cdd03ba28ba8c334f12d4c03
1/*
2 * Copyright © 2007,2008,2009,2010  Red Hat, Inc.
3 * Copyright © 2010  Google, Inc.
4 *
5 *  This is part of HarfBuzz, a text shaping library.
6 *
7 * Permission is hereby granted, without written agreement and without
8 * license or royalty fees, to use, copy, modify, and distribute this
9 * software and its documentation for any purpose, provided that the
10 * above copyright notice and the following two paragraphs appear in
11 * all copies of this software.
12 *
13 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
14 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
15 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
16 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
17 * DAMAGE.
18 *
19 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
20 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
22 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
23 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
24 *
25 * Red Hat Author(s): Behdad Esfahbod
26 * Google Author(s): Behdad Esfahbod
27 */
28
29#ifndef HB_OT_LAYOUT_GSUB_TABLE_HH
30#define HB_OT_LAYOUT_GSUB_TABLE_HH
31
32#include "hb-ot-layout-gsubgpos-private.hh"
33
34
35
36struct SingleSubstFormat1
37{
38  friend struct SingleSubst;
39
40  private:
41
42  inline bool apply (hb_apply_context_t *c) const
43  {
44    TRACE_APPLY ();
45    hb_codepoint_t glyph_id = c->buffer->info[c->buffer->idx].codepoint;
46    unsigned int index = (this+coverage) (glyph_id);
47    if (likely (index == NOT_COVERED))
48      return false;
49
50    /* According to the Adobe Annotated OpenType Suite, result is always
51     * limited to 16bit. */
52    glyph_id = (glyph_id + deltaGlyphID) & 0xFFFF;
53    c->replace_glyph (glyph_id);
54
55    return true;
56  }
57
58  inline bool sanitize (hb_sanitize_context_t *c) {
59    TRACE_SANITIZE ();
60    return coverage.sanitize (c, this)
61	&& deltaGlyphID.sanitize (c);
62  }
63
64  private:
65  USHORT	format;			/* Format identifier--format = 1 */
66  OffsetTo<Coverage>
67		coverage;		/* Offset to Coverage table--from
68					 * beginning of Substitution table */
69  SHORT		deltaGlyphID;		/* Add to original GlyphID to get
70					 * substitute GlyphID */
71  public:
72  DEFINE_SIZE_STATIC (6);
73};
74
75struct SingleSubstFormat2
76{
77  friend struct SingleSubst;
78
79  private:
80
81  inline bool apply (hb_apply_context_t *c) const
82  {
83    TRACE_APPLY ();
84    hb_codepoint_t glyph_id = c->buffer->info[c->buffer->idx].codepoint;
85    unsigned int index = (this+coverage) (glyph_id);
86    if (likely (index == NOT_COVERED))
87      return false;
88
89    if (unlikely (index >= substitute.len))
90      return false;
91
92    glyph_id = substitute[index];
93    c->replace_glyph (glyph_id);
94
95    return true;
96  }
97
98  inline bool sanitize (hb_sanitize_context_t *c) {
99    TRACE_SANITIZE ();
100    return coverage.sanitize (c, this)
101	&& substitute.sanitize (c);
102  }
103
104  private:
105  USHORT	format;			/* Format identifier--format = 2 */
106  OffsetTo<Coverage>
107		coverage;		/* Offset to Coverage table--from
108					 * beginning of Substitution table */
109  ArrayOf<GlyphID>
110		substitute;		/* Array of substitute
111					 * GlyphIDs--ordered by Coverage Index */
112  public:
113  DEFINE_SIZE_ARRAY (6, substitute);
114};
115
116struct SingleSubst
117{
118  friend struct SubstLookupSubTable;
119
120  private:
121
122  inline bool apply (hb_apply_context_t *c) const
123  {
124    TRACE_APPLY ();
125    switch (u.format) {
126    case 1: return u.format1.apply (c);
127    case 2: return u.format2.apply (c);
128    default:return false;
129    }
130  }
131
132  inline bool sanitize (hb_sanitize_context_t *c) {
133    TRACE_SANITIZE ();
134    if (!u.format.sanitize (c)) return false;
135    switch (u.format) {
136    case 1: return u.format1.sanitize (c);
137    case 2: return u.format2.sanitize (c);
138    default:return true;
139    }
140  }
141
142  private:
143  union {
144  USHORT		format;		/* Format identifier */
145  SingleSubstFormat1	format1;
146  SingleSubstFormat2	format2;
147  } u;
148};
149
150
151struct Sequence
152{
153  friend struct MultipleSubstFormat1;
154
155  private:
156  inline bool apply (hb_apply_context_t *c) const
157  {
158    TRACE_APPLY ();
159    if (unlikely (!substitute.len))
160      return false;
161
162    if (c->property & HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE)
163      c->guess_glyph_class (HB_OT_LAYOUT_GLYPH_CLASS_BASE_GLYPH);
164    c->replace_glyphs_be16 (1, substitute.len, (const uint16_t *) substitute.array);
165
166    return true;
167  }
168
169  public:
170  inline bool sanitize (hb_sanitize_context_t *c) {
171    TRACE_SANITIZE ();
172    return substitute.sanitize (c);
173  }
174
175  private:
176  ArrayOf<GlyphID>
177		substitute;		/* String of GlyphIDs to substitute */
178  public:
179  DEFINE_SIZE_ARRAY (2, substitute);
180};
181
182struct MultipleSubstFormat1
183{
184  friend struct MultipleSubst;
185
186  private:
187
188  inline bool apply (hb_apply_context_t *c) const
189  {
190    TRACE_APPLY ();
191
192    unsigned int index = (this+coverage) (c->buffer->info[c->buffer->idx].codepoint);
193    if (likely (index == NOT_COVERED))
194      return false;
195
196    return (this+sequence[index]).apply (c);
197  }
198
199  inline bool sanitize (hb_sanitize_context_t *c) {
200    TRACE_SANITIZE ();
201    return coverage.sanitize (c, this)
202	&& sequence.sanitize (c, this);
203  }
204
205  private:
206  USHORT	format;			/* Format identifier--format = 1 */
207  OffsetTo<Coverage>
208		coverage;		/* Offset to Coverage table--from
209					 * beginning of Substitution table */
210  OffsetArrayOf<Sequence>
211		sequence;		/* Array of Sequence tables
212					 * ordered by Coverage Index */
213  public:
214  DEFINE_SIZE_ARRAY (6, sequence);
215};
216
217struct MultipleSubst
218{
219  friend struct SubstLookupSubTable;
220
221  private:
222
223  inline bool apply (hb_apply_context_t *c) const
224  {
225    TRACE_APPLY ();
226    switch (u.format) {
227    case 1: return u.format1.apply (c);
228    default:return false;
229    }
230  }
231
232  inline bool sanitize (hb_sanitize_context_t *c) {
233    TRACE_SANITIZE ();
234    if (!u.format.sanitize (c)) return false;
235    switch (u.format) {
236    case 1: return u.format1.sanitize (c);
237    default:return true;
238    }
239  }
240
241  private:
242  union {
243  USHORT		format;		/* Format identifier */
244  MultipleSubstFormat1	format1;
245  } u;
246};
247
248
249typedef ArrayOf<GlyphID> AlternateSet;	/* Array of alternate GlyphIDs--in
250					 * arbitrary order */
251
252struct AlternateSubstFormat1
253{
254  friend struct AlternateSubst;
255
256  private:
257
258  inline bool apply (hb_apply_context_t *c) const
259  {
260    TRACE_APPLY ();
261    hb_codepoint_t glyph_id = c->buffer->info[c->buffer->idx].codepoint;
262    hb_mask_t glyph_mask = c->buffer->info[c->buffer->idx].mask;
263    hb_mask_t lookup_mask = c->lookup_mask;
264
265    unsigned int index = (this+coverage) (glyph_id);
266    if (likely (index == NOT_COVERED))
267      return false;
268
269    const AlternateSet &alt_set = this+alternateSet[index];
270
271    if (unlikely (!alt_set.len))
272      return false;
273
274    /* Note: This breaks badly if two features enabled this lookup together. */
275    unsigned int shift = _hb_ctz (lookup_mask);
276    unsigned int alt_index = ((lookup_mask & glyph_mask) >> shift);
277
278    if (unlikely (alt_index > alt_set.len || alt_index == 0))
279      return false;
280
281    glyph_id = alt_set[alt_index - 1];
282
283    c->replace_glyph (glyph_id);
284
285    return true;
286  }
287
288  inline bool sanitize (hb_sanitize_context_t *c) {
289    TRACE_SANITIZE ();
290    return coverage.sanitize (c, this)
291	&& alternateSet.sanitize (c, this);
292  }
293
294  private:
295  USHORT	format;			/* Format identifier--format = 1 */
296  OffsetTo<Coverage>
297		coverage;		/* Offset to Coverage table--from
298					 * beginning of Substitution table */
299  OffsetArrayOf<AlternateSet>
300		alternateSet;		/* Array of AlternateSet tables
301					 * ordered by Coverage Index */
302  public:
303  DEFINE_SIZE_ARRAY (6, alternateSet);
304};
305
306struct AlternateSubst
307{
308  friend struct SubstLookupSubTable;
309
310  private:
311
312  inline bool apply (hb_apply_context_t *c) const
313  {
314    TRACE_APPLY ();
315    switch (u.format) {
316    case 1: return u.format1.apply (c);
317    default:return false;
318    }
319  }
320
321  inline bool sanitize (hb_sanitize_context_t *c) {
322    TRACE_SANITIZE ();
323    if (!u.format.sanitize (c)) return false;
324    switch (u.format) {
325    case 1: return u.format1.sanitize (c);
326    default:return true;
327    }
328  }
329
330  private:
331  union {
332  USHORT		format;		/* Format identifier */
333  AlternateSubstFormat1	format1;
334  } u;
335};
336
337
338struct Ligature
339{
340  friend struct LigatureSet;
341
342  private:
343  inline bool apply (hb_apply_context_t *c) const
344  {
345    TRACE_APPLY ();
346    unsigned int i;
347    unsigned int j = c->buffer->idx;
348    unsigned int count = component.len;
349    unsigned int end = MIN (c->buffer->len, j + c->context_length);
350    if (unlikely (count < 2 || j >= end))
351      return false;
352
353    bool first_was_mark = (c->property & HB_OT_LAYOUT_GLYPH_CLASS_MARK);
354    bool found_non_mark = false;
355
356    for (i = 1; i < count; i++)
357    {
358      unsigned int property;
359      do
360      {
361	j++;
362	if (unlikely (j == end))
363	  return false;
364      } while (_hb_ot_layout_skip_mark (c->face, &c->buffer->info[j], c->lookup_props, &property));
365
366      found_non_mark |= !(property & HB_OT_LAYOUT_GLYPH_CLASS_MARK);
367
368      if (likely (c->buffer->info[j].codepoint != component[i]))
369        return false;
370    }
371
372    if (first_was_mark && found_non_mark)
373      c->guess_glyph_class (HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE);
374
375    /* Allocate new ligature id */
376    unsigned int lig_id = allocate_lig_id (c->buffer);
377    c->buffer->info[c->buffer->idx].lig_comp() = 0;
378    c->buffer->info[c->buffer->idx].lig_id() = lig_id;
379
380    if (j < c->buffer->idx + count) /* No input glyphs skipped */
381    {
382      c->replace_glyphs_be16 (count, 1, (const uint16_t *) &ligGlyph);
383    }
384    else
385    {
386      c->replace_glyph (ligGlyph);
387
388      /* Now we must do a second loop to copy the skipped glyphs to
389	 `out' and assign component values to it.  We start with the
390	 glyph after the first component.  Glyphs between component
391	 i and i+1 belong to component i.  Together with the lig_id
392	 value it is later possible to check whether a specific
393	 component value really belongs to a given ligature. */
394
395      for (i = 1; i < count; i++)
396      {
397	while (_hb_ot_layout_skip_mark (c->face, &c->buffer->info[c->buffer->idx], c->lookup_props, NULL))
398	{
399	  c->buffer->info[c->buffer->idx].lig_comp() = i;
400	  c->buffer->info[c->buffer->idx].lig_id() = lig_id;
401	  c->replace_glyph (c->buffer->info[c->buffer->idx].codepoint);
402	}
403
404	/* Skip the base glyph */
405	c->buffer->idx++;
406      }
407    }
408
409    return true;
410  }
411
412  public:
413  inline bool sanitize (hb_sanitize_context_t *c) {
414    TRACE_SANITIZE ();
415    return ligGlyph.sanitize (c)
416        && component.sanitize (c);
417  }
418
419  private:
420  GlyphID	ligGlyph;		/* GlyphID of ligature to substitute */
421  HeadlessArrayOf<GlyphID>
422		component;		/* Array of component GlyphIDs--start
423					 * with the second  component--ordered
424					 * in writing direction */
425  public:
426  DEFINE_SIZE_ARRAY (4, component);
427};
428
429struct LigatureSet
430{
431  friend struct LigatureSubstFormat1;
432
433  private:
434  inline bool apply (hb_apply_context_t *c) const
435  {
436    TRACE_APPLY ();
437    unsigned int num_ligs = ligature.len;
438    for (unsigned int i = 0; i < num_ligs; i++)
439    {
440      const Ligature &lig = this+ligature[i];
441      if (lig.apply (c))
442        return true;
443    }
444
445    return false;
446  }
447
448  public:
449  inline bool sanitize (hb_sanitize_context_t *c) {
450    TRACE_SANITIZE ();
451    return ligature.sanitize (c, this);
452  }
453
454  private:
455  OffsetArrayOf<Ligature>
456		ligature;		/* Array LigatureSet tables
457					 * ordered by preference */
458  public:
459  DEFINE_SIZE_ARRAY (2, ligature);
460};
461
462struct LigatureSubstFormat1
463{
464  friend struct LigatureSubst;
465
466  private:
467  inline bool apply (hb_apply_context_t *c) const
468  {
469    TRACE_APPLY ();
470    hb_codepoint_t glyph_id = c->buffer->info[c->buffer->idx].codepoint;
471
472    unsigned int index = (this+coverage) (glyph_id);
473    if (likely (index == NOT_COVERED))
474      return false;
475
476    const LigatureSet &lig_set = this+ligatureSet[index];
477    return lig_set.apply (c);
478  }
479
480  inline bool sanitize (hb_sanitize_context_t *c) {
481    TRACE_SANITIZE ();
482    return coverage.sanitize (c, this)
483	&& ligatureSet.sanitize (c, this);
484  }
485
486  private:
487  USHORT	format;			/* Format identifier--format = 1 */
488  OffsetTo<Coverage>
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 */
494  public:
495  DEFINE_SIZE_ARRAY (6, ligatureSet);
496};
497
498struct LigatureSubst
499{
500  friend struct SubstLookupSubTable;
501
502  private:
503  inline bool apply (hb_apply_context_t *c) const
504  {
505    TRACE_APPLY ();
506    switch (u.format) {
507    case 1: return u.format1.apply (c);
508    default:return false;
509    }
510  }
511
512  inline bool sanitize (hb_sanitize_context_t *c) {
513    TRACE_SANITIZE ();
514    if (!u.format.sanitize (c)) return false;
515    switch (u.format) {
516    case 1: return u.format1.sanitize (c);
517    default:return true;
518    }
519  }
520
521  private:
522  union {
523  USHORT		format;		/* Format identifier */
524  LigatureSubstFormat1	format1;
525  } u;
526};
527
528
529static inline bool substitute_lookup (hb_apply_context_t *c, unsigned int lookup_index);
530
531struct ContextSubst : Context
532{
533  friend struct SubstLookupSubTable;
534
535  private:
536  inline bool apply (hb_apply_context_t *c) const
537  {
538    TRACE_APPLY ();
539    return Context::apply (c, substitute_lookup);
540  }
541};
542
543struct ChainContextSubst : ChainContext
544{
545  friend struct SubstLookupSubTable;
546
547  private:
548  inline bool apply (hb_apply_context_t *c) const
549  {
550    TRACE_APPLY ();
551    return ChainContext::apply (c, substitute_lookup);
552  }
553};
554
555
556struct ExtensionSubst : Extension
557{
558  friend struct SubstLookupSubTable;
559  friend struct SubstLookup;
560
561  private:
562  inline const struct SubstLookupSubTable& get_subtable (void) const
563  {
564    unsigned int offset = get_offset ();
565    if (unlikely (!offset)) return Null(SubstLookupSubTable);
566    return StructAtOffset<SubstLookupSubTable> (this, offset);
567  }
568
569  inline bool apply (hb_apply_context_t *c) const;
570
571  inline bool sanitize (hb_sanitize_context_t *c);
572
573  inline bool is_reverse (void) const;
574};
575
576
577struct ReverseChainSingleSubstFormat1
578{
579  friend struct ReverseChainSingleSubst;
580
581  private:
582  inline bool apply (hb_apply_context_t *c) const
583  {
584    TRACE_APPLY ();
585    if (unlikely (c->context_length != NO_CONTEXT))
586      return false; /* No chaining to this type */
587
588    unsigned int index = (this+coverage) (c->buffer->info[c->buffer->idx].codepoint);
589    if (likely (index == NOT_COVERED))
590      return false;
591
592    const OffsetArrayOf<Coverage> &lookahead = StructAfter<OffsetArrayOf<Coverage> > (backtrack);
593    const ArrayOf<GlyphID> &substitute = StructAfter<ArrayOf<GlyphID> > (lookahead);
594
595    if (match_backtrack (c,
596			 backtrack.len, (USHORT *) backtrack.array,
597			 match_coverage, this) &&
598        match_lookahead (c,
599			 lookahead.len, (USHORT *) lookahead.array,
600			 match_coverage, this,
601			 1))
602    {
603      c->buffer->info[c->buffer->idx].codepoint = substitute[index];
604      c->buffer->idx--; /* Reverse! */
605      return true;
606    }
607
608    return false;
609  }
610
611  inline bool sanitize (hb_sanitize_context_t *c) {
612    TRACE_SANITIZE ();
613    if (!(coverage.sanitize (c, this)
614       && backtrack.sanitize (c, this)))
615      return false;
616    OffsetArrayOf<Coverage> &lookahead = StructAfter<OffsetArrayOf<Coverage> > (backtrack);
617    if (!lookahead.sanitize (c, this))
618      return false;
619    ArrayOf<GlyphID> &substitute = StructAfter<ArrayOf<GlyphID> > (lookahead);
620    return substitute.sanitize (c);
621  }
622
623  private:
624  USHORT	format;			/* Format identifier--format = 1 */
625  OffsetTo<Coverage>
626		coverage;		/* Offset to Coverage table--from
627					 * beginning of table */
628  OffsetArrayOf<Coverage>
629		backtrack;		/* Array of coverage tables
630					 * in backtracking sequence, in  glyph
631					 * sequence order */
632  OffsetArrayOf<Coverage>
633		lookaheadX;		/* Array of coverage tables
634					 * in lookahead sequence, in glyph
635					 * sequence order */
636  ArrayOf<GlyphID>
637		substituteX;		/* Array of substitute
638					 * GlyphIDs--ordered by Coverage Index */
639  public:
640  DEFINE_SIZE_MIN (10);
641};
642
643struct ReverseChainSingleSubst
644{
645  friend struct SubstLookupSubTable;
646
647  private:
648  inline bool apply (hb_apply_context_t *c) const
649  {
650    TRACE_APPLY ();
651    switch (u.format) {
652    case 1: return u.format1.apply (c);
653    default:return false;
654    }
655  }
656
657  inline bool sanitize (hb_sanitize_context_t *c) {
658    TRACE_SANITIZE ();
659    if (!u.format.sanitize (c)) return false;
660    switch (u.format) {
661    case 1: return u.format1.sanitize (c);
662    default:return true;
663    }
664  }
665
666  private:
667  union {
668  USHORT				format;		/* Format identifier */
669  ReverseChainSingleSubstFormat1	format1;
670  } u;
671};
672
673
674
675/*
676 * SubstLookup
677 */
678
679struct SubstLookupSubTable
680{
681  friend struct SubstLookup;
682
683  enum {
684    Single		= 1,
685    Multiple		= 2,
686    Alternate		= 3,
687    Ligature		= 4,
688    Context		= 5,
689    ChainContext	= 6,
690    Extension		= 7,
691    ReverseChainSingle	= 8
692  };
693
694  inline bool apply (hb_apply_context_t *c, unsigned int lookup_type) const
695  {
696    TRACE_APPLY ();
697    switch (lookup_type) {
698    case Single:		return u.single.apply (c);
699    case Multiple:		return u.multiple.apply (c);
700    case Alternate:		return u.alternate.apply (c);
701    case Ligature:		return u.ligature.apply (c);
702    case Context:		return u.c.apply (c);
703    case ChainContext:		return u.chainContext.apply (c);
704    case Extension:		return u.extension.apply (c);
705    case ReverseChainSingle:	return u.reverseChainContextSingle.apply (c);
706    default:return false;
707    }
708  }
709
710  inline bool sanitize (hb_sanitize_context_t *c, unsigned int lookup_type) {
711    TRACE_SANITIZE ();
712    switch (lookup_type) {
713    case Single:		return u.single.sanitize (c);
714    case Multiple:		return u.multiple.sanitize (c);
715    case Alternate:		return u.alternate.sanitize (c);
716    case Ligature:		return u.ligature.sanitize (c);
717    case Context:		return u.c.sanitize (c);
718    case ChainContext:		return u.chainContext.sanitize (c);
719    case Extension:		return u.extension.sanitize (c);
720    case ReverseChainSingle:	return u.reverseChainContextSingle.sanitize (c);
721    default:return true;
722    }
723  }
724
725  private:
726  union {
727  USHORT			sub_format;
728  SingleSubst			single;
729  MultipleSubst			multiple;
730  AlternateSubst		alternate;
731  LigatureSubst			ligature;
732  ContextSubst			c;
733  ChainContextSubst		chainContext;
734  ExtensionSubst		extension;
735  ReverseChainSingleSubst	reverseChainContextSingle;
736  } u;
737  public:
738  DEFINE_SIZE_UNION (2, sub_format);
739};
740
741
742struct SubstLookup : Lookup
743{
744  inline const SubstLookupSubTable& get_subtable (unsigned int i) const
745  { return this+CastR<OffsetArrayOf<SubstLookupSubTable> > (subTable)[i]; }
746
747  inline static bool lookup_type_is_reverse (unsigned int lookup_type)
748  { return lookup_type == SubstLookupSubTable::ReverseChainSingle; }
749
750  inline bool is_reverse (void) const
751  {
752    unsigned int type = get_type ();
753    if (unlikely (type == SubstLookupSubTable::Extension))
754      return CastR<ExtensionSubst> (get_subtable(0)).is_reverse ();
755    return lookup_type_is_reverse (type);
756  }
757
758
759  inline bool apply_once (hb_face_t *face,
760			  hb_buffer_t *buffer,
761			  hb_mask_t lookup_mask,
762			  unsigned int context_length,
763			  unsigned int nesting_level_left) const
764  {
765    unsigned int lookup_type = get_type ();
766    hb_apply_context_t c[1] = {{0}};
767
768    c->face = face;
769    c->buffer = buffer;
770    c->direction = buffer->props.direction;
771    c->lookup_mask = lookup_mask;
772    c->context_length = context_length;
773    c->nesting_level_left = nesting_level_left;
774    c->lookup_props = get_props ();
775
776    if (!_hb_ot_layout_check_glyph_property (c->face, &c->buffer->info[c->buffer->idx], c->lookup_props, &c->property))
777      return false;
778
779    if (unlikely (lookup_type == SubstLookupSubTable::Extension))
780    {
781      /* The spec says all subtables should have the same type.
782       * This is specially important if one has a reverse type!
783       *
784       * This is rather slow to do this here for every glyph,
785       * but it's easiest, and who uses extension lookups anyway?!*/
786      unsigned int count = get_subtable_count ();
787      unsigned int type = get_subtable(0).u.extension.get_type ();
788      for (unsigned int i = 1; i < count; i++)
789        if (get_subtable(i).u.extension.get_type () != type)
790	  return false;
791    }
792
793    unsigned int count = get_subtable_count ();
794    for (unsigned int i = 0; i < count; i++)
795      if (get_subtable (i).apply (c, lookup_type))
796	return true;
797
798    return false;
799  }
800
801  inline bool apply_string (hb_face_t   *face,
802			    hb_buffer_t *buffer,
803			    hb_mask_t    mask) const
804  {
805    bool ret = false;
806
807    if (unlikely (!buffer->len))
808      return false;
809
810    if (likely (!is_reverse ()))
811    {
812	/* in/out forward substitution */
813	buffer->clear_output ();
814	buffer->idx = 0;
815	while (buffer->idx < buffer->len)
816	{
817	  if ((buffer->info[buffer->idx].mask & mask) &&
818	      apply_once (face, buffer, mask, NO_CONTEXT, MAX_NESTING_LEVEL))
819	    ret = true;
820	  else
821	    buffer->next_glyph ();
822
823	}
824	if (ret)
825	  buffer->swap_buffers ();
826    }
827    else
828    {
829	/* in-place backward substitution */
830	buffer->idx = buffer->len - 1;
831	do
832	{
833	  if ((buffer->info[buffer->idx].mask & mask) &&
834	      apply_once (face, buffer, mask, NO_CONTEXT, MAX_NESTING_LEVEL))
835	    ret = true;
836	  else
837	    buffer->idx--;
838
839	}
840	while ((int) buffer->idx >= 0);
841    }
842
843    return ret;
844  }
845
846  inline bool sanitize (hb_sanitize_context_t *c) {
847    TRACE_SANITIZE ();
848    if (unlikely (!Lookup::sanitize (c))) return false;
849    OffsetArrayOf<SubstLookupSubTable> &list = CastR<OffsetArrayOf<SubstLookupSubTable> > (subTable);
850    return list.sanitize (c, this, get_type ());
851  }
852};
853
854typedef OffsetListOf<SubstLookup> SubstLookupList;
855
856/*
857 * GSUB -- The Glyph Substitution Table
858 */
859
860struct GSUB : GSUBGPOS
861{
862  static const hb_tag_t Tag	= HB_OT_TAG_GSUB;
863
864  inline const SubstLookup& get_lookup (unsigned int i) const
865  { return CastR<SubstLookup> (GSUBGPOS::get_lookup (i)); }
866
867  inline bool substitute_lookup (hb_face_t    *face,
868				 hb_buffer_t  *buffer,
869			         unsigned int  lookup_index,
870				 hb_mask_t     mask) const
871  { return get_lookup (lookup_index).apply_string (face, buffer, mask); }
872
873  static inline void substitute_start (hb_buffer_t *buffer);
874  static inline void substitute_finish (hb_buffer_t *buffer);
875
876  inline bool sanitize (hb_sanitize_context_t *c) {
877    TRACE_SANITIZE ();
878    if (unlikely (!GSUBGPOS::sanitize (c))) return false;
879    OffsetTo<SubstLookupList> &list = CastR<OffsetTo<SubstLookupList> > (lookupList);
880    return list.sanitize (c, this);
881  }
882  public:
883  DEFINE_SIZE_STATIC (10);
884};
885
886
887void
888GSUB::substitute_start (hb_buffer_t *buffer)
889{
890  HB_BUFFER_ALLOCATE_VAR (buffer, props_cache);
891  HB_BUFFER_ALLOCATE_VAR (buffer, lig_id);
892  HB_BUFFER_ALLOCATE_VAR (buffer, lig_comp);
893
894  unsigned int count = buffer->len;
895  for (unsigned int i = 0; i < count; i++)
896    buffer->info[i].props_cache() = buffer->info[i].lig_id() = buffer->info[i].lig_comp() = 0;
897}
898
899void
900GSUB::substitute_finish (hb_buffer_t *buffer)
901{
902}
903
904
905/* Out-of-class implementation for methods recursing */
906
907inline bool ExtensionSubst::apply (hb_apply_context_t *c) const
908{
909  TRACE_APPLY ();
910  return get_subtable ().apply (c, get_type ());
911}
912
913inline bool ExtensionSubst::sanitize (hb_sanitize_context_t *c)
914{
915  TRACE_SANITIZE ();
916  if (unlikely (!Extension::sanitize (c))) return false;
917  unsigned int offset = get_offset ();
918  if (unlikely (!offset)) return true;
919  return StructAtOffset<SubstLookupSubTable> (this, offset).sanitize (c, get_type ());
920}
921
922inline bool ExtensionSubst::is_reverse (void) const
923{
924  unsigned int type = get_type ();
925  if (unlikely (type == SubstLookupSubTable::Extension))
926    return CastR<ExtensionSubst> (get_subtable()).is_reverse ();
927  return SubstLookup::lookup_type_is_reverse (type);
928}
929
930static inline bool substitute_lookup (hb_apply_context_t *c, unsigned int lookup_index)
931{
932  const GSUB &gsub = *(c->face->ot_layout->gsub);
933  const SubstLookup &l = gsub.get_lookup (lookup_index);
934
935  if (unlikely (c->nesting_level_left == 0))
936    return false;
937
938  if (unlikely (c->context_length < 1))
939    return false;
940
941  return l.apply_once (c->face, c->buffer, c->lookup_mask, c->context_length, c->nesting_level_left - 1);
942}
943
944
945
946#endif /* HB_OT_LAYOUT_GSUB_TABLE_HH */
947