hb-ot-layout-gsub-table.hh revision 370f03e9c69d98d735eafb7e72b13b17f42cbaa9
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 j = c->buffer->idx;
347    unsigned int count = component.len;
348    unsigned int end = MIN (c->buffer->len, j + c->context_length);
349    if (unlikely (count < 2 || j >= end))
350      return false;
351
352    bool first_was_mark = (c->property & HB_OT_LAYOUT_GLYPH_CLASS_MARK);
353    bool found_non_mark = false;
354
355    for (unsigned int i = 1; i < count; i++)
356    {
357      unsigned int property;
358      do
359      {
360	j++;
361	if (unlikely (j == end))
362	  return false;
363      } while (_hb_ot_layout_skip_mark (c->face, &c->buffer->info[j], c->lookup_props, &property));
364
365      found_non_mark |= !(property & HB_OT_LAYOUT_GLYPH_CLASS_MARK);
366
367      if (likely (c->buffer->info[j].codepoint != component[i]))
368        return false;
369    }
370
371    if (first_was_mark && found_non_mark)
372      c->guess_glyph_class (HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE);
373
374    /* Allocate new ligature id */
375    unsigned int lig_id = allocate_lig_id (c->buffer);
376    c->buffer->info[c->buffer->idx].lig_comp() = 0;
377    c->buffer->info[c->buffer->idx].lig_id() = lig_id;
378
379    if (j < c->buffer->idx + count) /* No input glyphs skipped */
380    {
381      c->replace_glyphs_be16 (count, 1, (const uint16_t *) &ligGlyph);
382    }
383    else
384    {
385      c->replace_glyph (ligGlyph);
386
387      /* Now we must do a second loop to copy the skipped glyphs to
388	 `out' and assign component values to it.  We start with the
389	 glyph after the first component.  Glyphs between component
390	 i and i+1 belong to component i.  Together with the lig_id
391	 value it is later possible to check whether a specific
392	 component value really belongs to a given ligature. */
393
394      for (unsigned int i = 1; i < count; i++)
395      {
396	while (_hb_ot_layout_skip_mark (c->face, &c->buffer->info[c->buffer->idx], c->lookup_props, NULL))
397	{
398	  c->buffer->info[c->buffer->idx].lig_comp() = i;
399	  c->buffer->info[c->buffer->idx].lig_id() = lig_id;
400	  c->replace_glyph (c->buffer->info[c->buffer->idx].codepoint);
401	}
402
403	/* Skip the base glyph */
404	c->buffer->idx++;
405      }
406    }
407
408    return true;
409  }
410
411  public:
412  inline bool sanitize (hb_sanitize_context_t *c) {
413    TRACE_SANITIZE ();
414    return ligGlyph.sanitize (c)
415        && component.sanitize (c);
416  }
417
418  private:
419  GlyphID	ligGlyph;		/* GlyphID of ligature to substitute */
420  HeadlessArrayOf<GlyphID>
421		component;		/* Array of component GlyphIDs--start
422					 * with the second  component--ordered
423					 * in writing direction */
424  public:
425  DEFINE_SIZE_ARRAY (4, component);
426};
427
428struct LigatureSet
429{
430  friend struct LigatureSubstFormat1;
431
432  private:
433  inline bool apply (hb_apply_context_t *c) const
434  {
435    TRACE_APPLY ();
436    unsigned int num_ligs = ligature.len;
437    for (unsigned int i = 0; i < num_ligs; i++)
438    {
439      const Ligature &lig = this+ligature[i];
440      if (lig.apply (c))
441        return true;
442    }
443
444    return false;
445  }
446
447  public:
448  inline bool sanitize (hb_sanitize_context_t *c) {
449    TRACE_SANITIZE ();
450    return ligature.sanitize (c, this);
451  }
452
453  private:
454  OffsetArrayOf<Ligature>
455		ligature;		/* Array LigatureSet tables
456					 * ordered by preference */
457  public:
458  DEFINE_SIZE_ARRAY (2, ligature);
459};
460
461struct LigatureSubstFormat1
462{
463  friend struct LigatureSubst;
464
465  private:
466  inline bool apply (hb_apply_context_t *c) const
467  {
468    TRACE_APPLY ();
469    hb_codepoint_t glyph_id = c->buffer->info[c->buffer->idx].codepoint;
470
471    unsigned int index = (this+coverage) (glyph_id);
472    if (likely (index == NOT_COVERED))
473      return false;
474
475    const LigatureSet &lig_set = this+ligatureSet[index];
476    return lig_set.apply (c);
477  }
478
479  inline bool sanitize (hb_sanitize_context_t *c) {
480    TRACE_SANITIZE ();
481    return coverage.sanitize (c, this)
482	&& ligatureSet.sanitize (c, this);
483  }
484
485  private:
486  USHORT	format;			/* Format identifier--format = 1 */
487  OffsetTo<Coverage>
488		coverage;		/* Offset to Coverage table--from
489					 * beginning of Substitution table */
490  OffsetArrayOf<LigatureSet>
491		ligatureSet;		/* Array LigatureSet tables
492					 * ordered by Coverage Index */
493  public:
494  DEFINE_SIZE_ARRAY (6, ligatureSet);
495};
496
497struct LigatureSubst
498{
499  friend struct SubstLookupSubTable;
500
501  private:
502  inline bool apply (hb_apply_context_t *c) const
503  {
504    TRACE_APPLY ();
505    switch (u.format) {
506    case 1: return u.format1.apply (c);
507    default:return false;
508    }
509  }
510
511  inline bool sanitize (hb_sanitize_context_t *c) {
512    TRACE_SANITIZE ();
513    if (!u.format.sanitize (c)) return false;
514    switch (u.format) {
515    case 1: return u.format1.sanitize (c);
516    default:return true;
517    }
518  }
519
520  private:
521  union {
522  USHORT		format;		/* Format identifier */
523  LigatureSubstFormat1	format1;
524  } u;
525};
526
527
528static inline bool substitute_lookup (hb_apply_context_t *c, unsigned int lookup_index);
529
530struct ContextSubst : Context
531{
532  friend struct SubstLookupSubTable;
533
534  private:
535  inline bool apply (hb_apply_context_t *c) const
536  {
537    TRACE_APPLY ();
538    return Context::apply (c, substitute_lookup);
539  }
540};
541
542struct ChainContextSubst : ChainContext
543{
544  friend struct SubstLookupSubTable;
545
546  private:
547  inline bool apply (hb_apply_context_t *c) const
548  {
549    TRACE_APPLY ();
550    return ChainContext::apply (c, substitute_lookup);
551  }
552};
553
554
555struct ExtensionSubst : Extension
556{
557  friend struct SubstLookupSubTable;
558  friend struct SubstLookup;
559
560  private:
561  inline const struct SubstLookupSubTable& get_subtable (void) const
562  {
563    unsigned int offset = get_offset ();
564    if (unlikely (!offset)) return Null(SubstLookupSubTable);
565    return StructAtOffset<SubstLookupSubTable> (this, offset);
566  }
567
568  inline bool apply (hb_apply_context_t *c) const;
569
570  inline bool sanitize (hb_sanitize_context_t *c);
571
572  inline bool is_reverse (void) const;
573};
574
575
576struct ReverseChainSingleSubstFormat1
577{
578  friend struct ReverseChainSingleSubst;
579
580  private:
581  inline bool apply (hb_apply_context_t *c) const
582  {
583    TRACE_APPLY ();
584    if (unlikely (c->context_length != NO_CONTEXT))
585      return false; /* No chaining to this type */
586
587    unsigned int index = (this+coverage) (c->buffer->info[c->buffer->idx].codepoint);
588    if (likely (index == NOT_COVERED))
589      return false;
590
591    const OffsetArrayOf<Coverage> &lookahead = StructAfter<OffsetArrayOf<Coverage> > (backtrack);
592    const ArrayOf<GlyphID> &substitute = StructAfter<ArrayOf<GlyphID> > (lookahead);
593
594    if (match_backtrack (c,
595			 backtrack.len, (USHORT *) backtrack.array,
596			 match_coverage, this) &&
597        match_lookahead (c,
598			 lookahead.len, (USHORT *) lookahead.array,
599			 match_coverage, this,
600			 1))
601    {
602      c->buffer->info[c->buffer->idx].codepoint = substitute[index];
603      c->buffer->idx--; /* Reverse! */
604      return true;
605    }
606
607    return false;
608  }
609
610  inline bool sanitize (hb_sanitize_context_t *c) {
611    TRACE_SANITIZE ();
612    if (!(coverage.sanitize (c, this)
613       && backtrack.sanitize (c, this)))
614      return false;
615    OffsetArrayOf<Coverage> &lookahead = StructAfter<OffsetArrayOf<Coverage> > (backtrack);
616    if (!lookahead.sanitize (c, this))
617      return false;
618    ArrayOf<GlyphID> &substitute = StructAfter<ArrayOf<GlyphID> > (lookahead);
619    return substitute.sanitize (c);
620  }
621
622  private:
623  USHORT	format;			/* Format identifier--format = 1 */
624  OffsetTo<Coverage>
625		coverage;		/* Offset to Coverage table--from
626					 * beginning of table */
627  OffsetArrayOf<Coverage>
628		backtrack;		/* Array of coverage tables
629					 * in backtracking sequence, in  glyph
630					 * sequence order */
631  OffsetArrayOf<Coverage>
632		lookaheadX;		/* Array of coverage tables
633					 * in lookahead sequence, in glyph
634					 * sequence order */
635  ArrayOf<GlyphID>
636		substituteX;		/* Array of substitute
637					 * GlyphIDs--ordered by Coverage Index */
638  public:
639  DEFINE_SIZE_MIN (10);
640};
641
642struct ReverseChainSingleSubst
643{
644  friend struct SubstLookupSubTable;
645
646  private:
647  inline bool apply (hb_apply_context_t *c) const
648  {
649    TRACE_APPLY ();
650    switch (u.format) {
651    case 1: return u.format1.apply (c);
652    default:return false;
653    }
654  }
655
656  inline bool sanitize (hb_sanitize_context_t *c) {
657    TRACE_SANITIZE ();
658    if (!u.format.sanitize (c)) return false;
659    switch (u.format) {
660    case 1: return u.format1.sanitize (c);
661    default:return true;
662    }
663  }
664
665  private:
666  union {
667  USHORT				format;		/* Format identifier */
668  ReverseChainSingleSubstFormat1	format1;
669  } u;
670};
671
672
673
674/*
675 * SubstLookup
676 */
677
678struct SubstLookupSubTable
679{
680  friend struct SubstLookup;
681
682  enum {
683    Single		= 1,
684    Multiple		= 2,
685    Alternate		= 3,
686    Ligature		= 4,
687    Context		= 5,
688    ChainContext	= 6,
689    Extension		= 7,
690    ReverseChainSingle	= 8
691  };
692
693  inline bool apply (hb_apply_context_t *c, unsigned int lookup_type) const
694  {
695    TRACE_APPLY ();
696    switch (lookup_type) {
697    case Single:		return u.single.apply (c);
698    case Multiple:		return u.multiple.apply (c);
699    case Alternate:		return u.alternate.apply (c);
700    case Ligature:		return u.ligature.apply (c);
701    case Context:		return u.c.apply (c);
702    case ChainContext:		return u.chainContext.apply (c);
703    case Extension:		return u.extension.apply (c);
704    case ReverseChainSingle:	return u.reverseChainContextSingle.apply (c);
705    default:return false;
706    }
707  }
708
709  inline bool sanitize (hb_sanitize_context_t *c, unsigned int lookup_type) {
710    TRACE_SANITIZE ();
711    switch (lookup_type) {
712    case Single:		return u.single.sanitize (c);
713    case Multiple:		return u.multiple.sanitize (c);
714    case Alternate:		return u.alternate.sanitize (c);
715    case Ligature:		return u.ligature.sanitize (c);
716    case Context:		return u.c.sanitize (c);
717    case ChainContext:		return u.chainContext.sanitize (c);
718    case Extension:		return u.extension.sanitize (c);
719    case ReverseChainSingle:	return u.reverseChainContextSingle.sanitize (c);
720    default:return true;
721    }
722  }
723
724  private:
725  union {
726  USHORT			sub_format;
727  SingleSubst			single;
728  MultipleSubst			multiple;
729  AlternateSubst		alternate;
730  LigatureSubst			ligature;
731  ContextSubst			c;
732  ChainContextSubst		chainContext;
733  ExtensionSubst		extension;
734  ReverseChainSingleSubst	reverseChainContextSingle;
735  } u;
736  public:
737  DEFINE_SIZE_UNION (2, sub_format);
738};
739
740
741struct SubstLookup : Lookup
742{
743  inline const SubstLookupSubTable& get_subtable (unsigned int i) const
744  { return this+CastR<OffsetArrayOf<SubstLookupSubTable> > (subTable)[i]; }
745
746  inline static bool lookup_type_is_reverse (unsigned int lookup_type)
747  { return lookup_type == SubstLookupSubTable::ReverseChainSingle; }
748
749  inline bool is_reverse (void) const
750  {
751    unsigned int type = get_type ();
752    if (unlikely (type == SubstLookupSubTable::Extension))
753      return CastR<ExtensionSubst> (get_subtable(0)).is_reverse ();
754    return lookup_type_is_reverse (type);
755  }
756
757
758  inline bool apply_once (hb_face_t *face,
759			  hb_buffer_t *buffer,
760			  hb_mask_t lookup_mask,
761			  unsigned int context_length,
762			  unsigned int nesting_level_left) const
763  {
764    unsigned int lookup_type = get_type ();
765    hb_apply_context_t c[1] = {{0}};
766
767    c->face = face;
768    c->buffer = buffer;
769    c->direction = buffer->props.direction;
770    c->lookup_mask = lookup_mask;
771    c->context_length = context_length;
772    c->nesting_level_left = nesting_level_left;
773    c->lookup_props = get_props ();
774
775    if (!_hb_ot_layout_check_glyph_property (c->face, &c->buffer->info[c->buffer->idx], c->lookup_props, &c->property))
776      return false;
777
778    if (unlikely (lookup_type == SubstLookupSubTable::Extension))
779    {
780      /* The spec says all subtables should have the same type.
781       * This is specially important if one has a reverse type!
782       *
783       * This is rather slow to do this here for every glyph,
784       * but it's easiest, and who uses extension lookups anyway?!*/
785      unsigned int count = get_subtable_count ();
786      unsigned int type = get_subtable(0).u.extension.get_type ();
787      for (unsigned int i = 1; i < count; i++)
788        if (get_subtable(i).u.extension.get_type () != type)
789	  return false;
790    }
791
792    unsigned int count = get_subtable_count ();
793    for (unsigned int i = 0; i < count; i++)
794      if (get_subtable (i).apply (c, lookup_type))
795	return true;
796
797    return false;
798  }
799
800  inline bool apply_string (hb_face_t   *face,
801			    hb_buffer_t *buffer,
802			    hb_mask_t    mask) const
803  {
804    bool ret = false;
805
806    if (unlikely (!buffer->len))
807      return false;
808
809    if (likely (!is_reverse ()))
810    {
811	/* in/out forward substitution */
812	buffer->clear_output ();
813	buffer->idx = 0;
814	while (buffer->idx < buffer->len)
815	{
816	  if ((buffer->info[buffer->idx].mask & mask) &&
817	      apply_once (face, buffer, mask, NO_CONTEXT, MAX_NESTING_LEVEL))
818	    ret = true;
819	  else
820	    buffer->next_glyph ();
821
822	}
823	if (ret)
824	  buffer->swap_buffers ();
825    }
826    else
827    {
828	/* in-place backward substitution */
829	buffer->idx = buffer->len - 1;
830	do
831	{
832	  if ((buffer->info[buffer->idx].mask & mask) &&
833	      apply_once (face, buffer, mask, NO_CONTEXT, MAX_NESTING_LEVEL))
834	    ret = true;
835	  else
836	    buffer->idx--;
837
838	}
839	while ((int) buffer->idx >= 0);
840    }
841
842    return ret;
843  }
844
845  inline bool sanitize (hb_sanitize_context_t *c) {
846    TRACE_SANITIZE ();
847    if (unlikely (!Lookup::sanitize (c))) return false;
848    OffsetArrayOf<SubstLookupSubTable> &list = CastR<OffsetArrayOf<SubstLookupSubTable> > (subTable);
849    return list.sanitize (c, this, get_type ());
850  }
851};
852
853typedef OffsetListOf<SubstLookup> SubstLookupList;
854
855/*
856 * GSUB -- The Glyph Substitution Table
857 */
858
859struct GSUB : GSUBGPOS
860{
861  static const hb_tag_t Tag	= HB_OT_TAG_GSUB;
862
863  inline const SubstLookup& get_lookup (unsigned int i) const
864  { return CastR<SubstLookup> (GSUBGPOS::get_lookup (i)); }
865
866  inline bool substitute_lookup (hb_face_t    *face,
867				 hb_buffer_t  *buffer,
868			         unsigned int  lookup_index,
869				 hb_mask_t     mask) const
870  { return get_lookup (lookup_index).apply_string (face, buffer, mask); }
871
872  static inline void substitute_start (hb_buffer_t *buffer);
873  static inline void substitute_finish (hb_buffer_t *buffer);
874
875  inline bool sanitize (hb_sanitize_context_t *c) {
876    TRACE_SANITIZE ();
877    if (unlikely (!GSUBGPOS::sanitize (c))) return false;
878    OffsetTo<SubstLookupList> &list = CastR<OffsetTo<SubstLookupList> > (lookupList);
879    return list.sanitize (c, this);
880  }
881  public:
882  DEFINE_SIZE_STATIC (10);
883};
884
885
886void
887GSUB::substitute_start (hb_buffer_t *buffer)
888{
889  HB_BUFFER_ALLOCATE_VAR (buffer, props_cache);
890  HB_BUFFER_ALLOCATE_VAR (buffer, lig_id);
891  HB_BUFFER_ALLOCATE_VAR (buffer, lig_comp);
892
893  unsigned int count = buffer->len;
894  for (unsigned int i = 0; i < count; i++)
895    buffer->info[i].props_cache() = buffer->info[i].lig_id() = buffer->info[i].lig_comp() = 0;
896}
897
898void
899GSUB::substitute_finish (hb_buffer_t *buffer)
900{
901}
902
903
904/* Out-of-class implementation for methods recursing */
905
906inline bool ExtensionSubst::apply (hb_apply_context_t *c) const
907{
908  TRACE_APPLY ();
909  return get_subtable ().apply (c, get_type ());
910}
911
912inline bool ExtensionSubst::sanitize (hb_sanitize_context_t *c)
913{
914  TRACE_SANITIZE ();
915  if (unlikely (!Extension::sanitize (c))) return false;
916  unsigned int offset = get_offset ();
917  if (unlikely (!offset)) return true;
918  return StructAtOffset<SubstLookupSubTable> (this, offset).sanitize (c, get_type ());
919}
920
921inline bool ExtensionSubst::is_reverse (void) const
922{
923  unsigned int type = get_type ();
924  if (unlikely (type == SubstLookupSubTable::Extension))
925    return CastR<ExtensionSubst> (get_subtable()).is_reverse ();
926  return SubstLookup::lookup_type_is_reverse (type);
927}
928
929static inline bool substitute_lookup (hb_apply_context_t *c, unsigned int lookup_index)
930{
931  const GSUB &gsub = *(c->face->ot_layout->gsub);
932  const SubstLookup &l = gsub.get_lookup (lookup_index);
933
934  if (unlikely (c->nesting_level_left == 0))
935    return false;
936
937  if (unlikely (c->context_length < 1))
938    return false;
939
940  return l.apply_once (c->face, c->buffer, c->lookup_mask, c->context_length, c->nesting_level_left - 1);
941}
942
943
944
945#endif /* HB_OT_LAYOUT_GSUB_TABLE_HH */
946