Imported Upstream version 1.7.6
[platform/upstream/harfbuzz.git] / src / hb-aat-layout-morx-table.hh
1 /*
2  * Copyright © 2017  Google, Inc.
3  *
4  *  This is part of HarfBuzz, a text shaping 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  * Google Author(s): Behdad Esfahbod
25  */
26
27 #ifndef HB_AAT_LAYOUT_MORX_TABLE_HH
28 #define HB_AAT_LAYOUT_MORX_TABLE_HH
29
30 #include "hb-open-type-private.hh"
31 #include "hb-aat-layout-common-private.hh"
32
33 #define HB_AAT_TAG_MORX HB_TAG('m','o','r','x')
34
35
36 namespace AAT {
37
38 using namespace OT;
39
40
41 struct RearrangementSubtable
42 {
43   typedef void EntryData;
44
45   struct driver_context_t
46   {
47     static const bool in_place = true;
48     enum Flags {
49       MarkFirst         = 0x8000,       /* If set, make the current glyph the first
50                                          * glyph to be rearranged. */
51       DontAdvance       = 0x4000,       /* If set, don't advance to the next glyph
52                                          * before going to the new state. This means
53                                          * that the glyph index doesn't change, even
54                                          * if the glyph at that index has changed. */
55       MarkLast          = 0x2000,       /* If set, make the current glyph the last
56                                          * glyph to be rearranged. */
57       Reserved          = 0x1FF0,       /* These bits are reserved and should be set to 0. */
58       Verb              = 0x000F,       /* The type of rearrangement specified. */
59     };
60
61     inline driver_context_t (const RearrangementSubtable *table) :
62         ret (false),
63         start (0), end (0) {}
64
65     inline bool is_actionable (StateTableDriver<EntryData> *driver,
66                                const Entry<EntryData> *entry)
67     {
68       return (entry->flags & Verb) && start < end;
69     }
70     inline bool transition (StateTableDriver<EntryData> *driver,
71                             const Entry<EntryData> *entry)
72     {
73       hb_buffer_t *buffer = driver->buffer;
74       unsigned int flags = entry->flags;
75
76       if (flags & MarkFirst)
77         start = buffer->idx;
78
79       if (flags & MarkLast)
80         end = MIN (buffer->idx + 1, buffer->len);
81
82       if ((flags & Verb) && start < end)
83       {
84         /* The following map has two nibbles, for start-side
85          * and end-side. Values of 0,1,2 mean move that many
86          * to the other side. Value of 3 means move 2 and
87          * flip them. */
88         const unsigned char map[16] =
89         {
90           0x00, /* 0    no change */
91           0x10, /* 1    Ax => xA */
92           0x01, /* 2    xD => Dx */
93           0x11, /* 3    AxD => DxA */
94           0x20, /* 4    ABx => xAB */
95           0x30, /* 5    ABx => xBA */
96           0x02, /* 6    xCD => CDx */
97           0x03, /* 7    xCD => DCx */
98           0x12, /* 8    AxCD => CDxA */
99           0x13, /* 9    AxCD => DCxA */
100           0x21, /* 10   ABxD => DxAB */
101           0x31, /* 11   ABxD => DxBA */
102           0x22, /* 12   ABxCD => CDxAB */
103           0x32, /* 13   ABxCD => CDxBA */
104           0x23, /* 14   ABxCD => DCxAB */
105           0x33, /* 15   ABxCD => DCxBA */
106         };
107
108         unsigned int m = map[flags & Verb];
109         unsigned int l = MIN<unsigned int> (2, m >> 4);
110         unsigned int r = MIN<unsigned int> (2, m & 0x0F);
111         bool reverse_l = 3 == (m >> 4);
112         bool reverse_r = 3 == (m & 0x0F);
113
114         if (end - start >= l + r)
115         {
116           buffer->merge_clusters (start, MIN (buffer->idx + 1, buffer->len));
117           buffer->merge_clusters (start, end);
118
119           hb_glyph_info_t *info = buffer->info;
120           hb_glyph_info_t buf[4];
121
122           memcpy (buf, info + start, l * sizeof (buf[0]));
123           memcpy (buf + 2, info + end - r, r * sizeof (buf[0]));
124
125           if (l != r)
126             memmove (info + start + r, info + start + l, (end - start - l - r) * sizeof (buf[0]));
127
128           memcpy (info + start, buf + 2, r * sizeof (buf[0]));
129           memcpy (info + end - l, buf, l * sizeof (buf[0]));
130           if (reverse_l)
131           {
132             buf[0] = info[end - 1];
133             info[end - 1] = info[end - 2];
134             info[end - 2] = buf[0];
135           }
136           if (reverse_r)
137           {
138             buf[0] = info[start];
139             info[start] = info[start + 1];
140             info[start + 1] = buf[0];
141           }
142         }
143       }
144
145       return true;
146     }
147
148     public:
149     bool ret;
150     private:
151     unsigned int start;
152     unsigned int end;
153   };
154
155   inline bool apply (hb_aat_apply_context_t *c) const
156   {
157     TRACE_APPLY (this);
158
159     driver_context_t dc (this);
160
161     StateTableDriver<void> driver (machine, c->buffer, c->face);
162     driver.drive (&dc);
163
164     return_trace (dc.ret);
165   }
166
167   inline bool sanitize (hb_sanitize_context_t *c) const
168   {
169     TRACE_SANITIZE (this);
170     return_trace (machine.sanitize (c));
171   }
172
173   protected:
174   StateTable<EntryData> machine;
175   public:
176   DEFINE_SIZE_STATIC (16);
177 };
178
179 struct ContextualSubtable
180 {
181   struct EntryData
182   {
183     HBUINT16    markIndex;      /* Index of the substitution table for the
184                                  * marked glyph (use 0xFFFF for none). */
185     HBUINT16    currentIndex;   /* Index of the substitution table for the
186                                  * current glyph (use 0xFFFF for none). */
187     public:
188     DEFINE_SIZE_STATIC (4);
189   };
190
191   struct driver_context_t
192   {
193     static const bool in_place = true;
194     enum Flags {
195       SetMark           = 0x8000,       /* If set, make the current glyph the marked glyph. */
196       DontAdvance       = 0x4000,       /* If set, don't advance to the next glyph before
197                                          * going to the new state. */
198       Reserved          = 0x3FFF,       /* These bits are reserved and should be set to 0. */
199     };
200
201     inline driver_context_t (const ContextualSubtable *table) :
202         ret (false),
203         mark_set (false),
204         mark (0),
205         subs (table+table->substitutionTables) {}
206
207     inline bool is_actionable (StateTableDriver<EntryData> *driver,
208                                const Entry<EntryData> *entry)
209     {
210       hb_buffer_t *buffer = driver->buffer;
211
212       if (buffer->idx == buffer->len && !mark_set)
213         return false;
214
215       return entry->data.markIndex != 0xFFFF || entry->data.currentIndex != 0xFFFF;
216     }
217     inline bool transition (StateTableDriver<EntryData> *driver,
218                             const Entry<EntryData> *entry)
219     {
220       hb_buffer_t *buffer = driver->buffer;
221
222       /* Looks like CoreText applies neither mark nor current substitution for
223        * end-of-text if mark was not explicitly set. */
224       if (buffer->idx == buffer->len && !mark_set)
225         return true;
226
227       if (entry->data.markIndex != 0xFFFF)
228       {
229         const Lookup<GlyphID> &lookup = subs[entry->data.markIndex];
230         hb_glyph_info_t *info = buffer->info;
231         const GlyphID *replacement = lookup.get_value (info[mark].codepoint, driver->num_glyphs);
232         if (replacement)
233         {
234           buffer->unsafe_to_break (mark, MIN (buffer->idx + 1, buffer->len));
235           info[mark].codepoint = *replacement;
236           ret = true;
237         }
238       }
239       if (entry->data.currentIndex != 0xFFFF)
240       {
241         unsigned int idx = MIN (buffer->idx, buffer->len - 1);
242         const Lookup<GlyphID> &lookup = subs[entry->data.currentIndex];
243         hb_glyph_info_t *info = buffer->info;
244         const GlyphID *replacement = lookup.get_value (info[idx].codepoint, driver->num_glyphs);
245         if (replacement)
246         {
247           info[idx].codepoint = *replacement;
248           ret = true;
249         }
250       }
251
252       if (entry->flags & SetMark)
253       {
254         mark_set = true;
255         mark = buffer->idx;
256       }
257
258       return true;
259     }
260
261     public:
262     bool ret;
263     private:
264     bool mark_set;
265     unsigned int mark;
266     const UnsizedOffsetListOf<Lookup<GlyphID>, HBUINT32> &subs;
267   };
268
269   inline bool apply (hb_aat_apply_context_t *c) const
270   {
271     TRACE_APPLY (this);
272
273     driver_context_t dc (this);
274
275     StateTableDriver<EntryData> driver (machine, c->buffer, c->face);
276     driver.drive (&dc);
277
278     return_trace (dc.ret);
279   }
280
281   inline bool sanitize (hb_sanitize_context_t *c) const
282   {
283     TRACE_SANITIZE (this);
284
285     unsigned int num_entries = 0;
286     if (unlikely (!machine.sanitize (c, &num_entries))) return_trace (false);
287
288     unsigned int num_lookups = 0;
289
290     const Entry<EntryData> *entries = machine.get_entries ();
291     for (unsigned int i = 0; i < num_entries; i++)
292     {
293       const EntryData &data = entries[i].data;
294
295       if (data.markIndex != 0xFFFF)
296         num_lookups = MAX<unsigned int> (num_lookups, 1 + data.markIndex);
297       if (data.currentIndex != 0xFFFF)
298         num_lookups = MAX<unsigned int> (num_lookups, 1 + data.currentIndex);
299     }
300
301     return_trace (substitutionTables.sanitize (c, this, num_lookups));
302   }
303
304   protected:
305   StateTable<EntryData> machine;
306   OffsetTo<UnsizedOffsetListOf<Lookup<GlyphID>, HBUINT32>, HBUINT32>
307                         substitutionTables;
308   public:
309   DEFINE_SIZE_STATIC (20);
310 };
311
312 struct LigatureSubtable
313 {
314   struct EntryData
315   {
316     HBUINT16    ligActionIndex; /* Index to the first ligActionTable entry
317                                  * for processing this group, if indicated
318                                  * by the flags. */
319     public:
320     DEFINE_SIZE_STATIC (2);
321   };
322
323   struct driver_context_t
324   {
325     static const bool in_place = false;
326     enum Flags {
327       SetComponent      = 0x8000,       /* Push this glyph onto the component stack for
328                                          * eventual processing. */
329       DontAdvance       = 0x4000,       /* Leave the glyph pointer at this glyph for the
330                                            next iteration. */
331       PerformAction     = 0x2000,       /* Use the ligActionIndex to process a ligature
332                                          * group. */
333       Reserved          = 0x1FFF,       /* These bits are reserved and should be set to 0. */
334     };
335     enum LigActionFlags {
336       LigActionLast     = 0x80000000,   /* This is the last action in the list. This also
337                                          * implies storage. */
338       LigActionStore    = 0x40000000,   /* Store the ligature at the current cumulated index
339                                          * in the ligature table in place of the marked
340                                          * (i.e. currently-popped) glyph. */
341       LigActionOffset   = 0x3FFFFFFF,   /* A 30-bit value which is sign-extended to 32-bits
342                                          * and added to the glyph ID, resulting in an index
343                                          * into the component table. */
344     };
345
346     inline driver_context_t (const LigatureSubtable *table,
347                              hb_aat_apply_context_t *c_) :
348         ret (false),
349         c (c_),
350         ligAction (table+table->ligAction),
351         component (table+table->component),
352         ligature (table+table->ligature),
353         match_length (0) {}
354
355     inline bool is_actionable (StateTableDriver<EntryData> *driver,
356                                const Entry<EntryData> *entry)
357     {
358       return !!(entry->flags & PerformAction);
359     }
360     inline bool transition (StateTableDriver<EntryData> *driver,
361                             const Entry<EntryData> *entry)
362     {
363       hb_buffer_t *buffer = driver->buffer;
364       unsigned int flags = entry->flags;
365
366       if (flags & SetComponent)
367       {
368         if (unlikely (match_length >= ARRAY_LENGTH (match_positions)))
369           return false;
370
371         /* Never mark same index twice, in case DontAdvance was used... */
372         if (match_length && match_positions[match_length - 1] == buffer->out_len)
373           match_length--;
374
375         match_positions[match_length++] = buffer->out_len;
376       }
377
378       if (flags & PerformAction)
379       {
380         unsigned int end = buffer->out_len;
381         unsigned int action_idx = entry->data.ligActionIndex;
382         unsigned int action;
383         unsigned int ligature_idx = 0;
384         do
385         {
386           if (unlikely (!match_length))
387             return false;
388
389           buffer->move_to (match_positions[--match_length]);
390
391           const HBUINT32 &actionData = ligAction[action_idx];
392           if (unlikely (!actionData.sanitize (&c->sanitizer))) return false;
393           action = actionData;
394
395           uint32_t uoffset = action & LigActionOffset;
396           if (uoffset & 0x20000000)
397             uoffset += 0xC0000000;
398           int32_t offset = (int32_t) uoffset;
399           unsigned int component_idx = buffer->cur().codepoint + offset;
400
401           const HBUINT16 &componentData = component[component_idx];
402           if (unlikely (!componentData.sanitize (&c->sanitizer))) return false;
403           ligature_idx += componentData;
404
405           if (action & (LigActionStore | LigActionLast))
406           {
407             const GlyphID &ligatureData = ligature[ligature_idx];
408             if (unlikely (!ligatureData.sanitize (&c->sanitizer))) return false;
409             hb_codepoint_t lig = ligatureData;
410
411             match_positions[match_length++] = buffer->out_len;
412             buffer->replace_glyph (lig);
413
414             //ligature_idx = 0; // XXX Yes or no?
415           }
416           else
417           {
418             buffer->skip_glyph ();
419             end--;
420           }
421           /* TODO merge_clusters / unsafe_to_break */
422
423           action_idx++;
424         }
425         while (!(action & LigActionLast));
426         buffer->move_to (end);
427       }
428
429       return true;
430     }
431
432     public:
433     bool ret;
434     private:
435     hb_aat_apply_context_t *c;
436     const UnsizedArrayOf<HBUINT32> &ligAction;
437     const UnsizedArrayOf<HBUINT16> &component;
438     const UnsizedArrayOf<GlyphID> &ligature;
439     unsigned int match_length;
440     unsigned int match_positions[HB_MAX_CONTEXT_LENGTH];
441   };
442
443   inline bool apply (hb_aat_apply_context_t *c) const
444   {
445     TRACE_APPLY (this);
446
447     driver_context_t dc (this, c);
448
449     StateTableDriver<EntryData> driver (machine, c->buffer, c->face);
450     driver.drive (&dc);
451
452     return_trace (dc.ret);
453   }
454
455   inline bool sanitize (hb_sanitize_context_t *c) const
456   {
457     TRACE_SANITIZE (this);
458     /* The rest of array sanitizations are done at run-time. */
459     return_trace (c->check_struct (this) && machine.sanitize (c) &&
460                   ligAction && component && ligature);
461   }
462
463   protected:
464   StateTable<EntryData> machine;
465   OffsetTo<UnsizedArrayOf<HBUINT32>, HBUINT32>
466                 ligAction;      /* Offset to the ligature action table. */
467   OffsetTo<UnsizedArrayOf<HBUINT16>, HBUINT32>
468                 component;      /* Offset to the component table. */
469   OffsetTo<UnsizedArrayOf<GlyphID>, HBUINT32>
470                 ligature;       /* Offset to the actual ligature lists. */
471   public:
472   DEFINE_SIZE_STATIC (28);
473 };
474
475 struct NoncontextualSubtable
476 {
477   inline bool apply (hb_aat_apply_context_t *c) const
478   {
479     TRACE_APPLY (this);
480
481     bool ret = false;
482     unsigned int num_glyphs = c->face->get_num_glyphs ();
483
484     hb_glyph_info_t *info = c->buffer->info;
485     unsigned int count = c->buffer->len;
486     for (unsigned int i = 0; i < count; i++)
487     {
488       const GlyphID *replacement = substitute.get_value (info[i].codepoint, num_glyphs);
489       if (replacement)
490       {
491         info[i].codepoint = *replacement;
492         ret = true;
493       }
494     }
495
496     return_trace (ret);
497   }
498
499   inline bool sanitize (hb_sanitize_context_t *c) const
500   {
501     TRACE_SANITIZE (this);
502     return_trace (substitute.sanitize (c));
503   }
504
505   protected:
506   Lookup<GlyphID>       substitute;
507   public:
508   DEFINE_SIZE_MIN (2);
509 };
510
511 struct InsertionSubtable
512 {
513   inline bool apply (hb_aat_apply_context_t *c) const
514   {
515     TRACE_APPLY (this);
516     /* TODO */
517     return_trace (false);
518   }
519
520   inline bool sanitize (hb_sanitize_context_t *c) const
521   {
522     TRACE_SANITIZE (this);
523     /* TODO */
524     return_trace (true);
525   }
526 };
527
528
529 struct Feature
530 {
531   inline bool sanitize (hb_sanitize_context_t *c) const
532   {
533     TRACE_SANITIZE (this);
534     return_trace (c->check_struct (this));
535   }
536
537   public:
538   HBUINT16      featureType;    /* The type of feature. */
539   HBUINT16      featureSetting; /* The feature's setting (aka selector). */
540   HBUINT32      enableFlags;    /* Flags for the settings that this feature
541                                  * and setting enables. */
542   HBUINT32      disableFlags;   /* Complement of flags for the settings that this
543                                  * feature and setting disable. */
544
545   public:
546   DEFINE_SIZE_STATIC (12);
547 };
548
549
550 struct ChainSubtable
551 {
552   friend struct Chain;
553
554   inline unsigned int get_size (void) const { return length; }
555   inline unsigned int get_type (void) const { return coverage & 0xFF; }
556
557   enum Type {
558     Rearrangement       = 0,
559     Contextual          = 1,
560     Ligature            = 2,
561     Noncontextual       = 4,
562     Insertion           = 5
563   };
564
565   inline void apply (hb_aat_apply_context_t *c) const
566   {
567     dispatch (c);
568   }
569
570   template <typename context_t>
571   inline typename context_t::return_t dispatch (context_t *c) const
572   {
573     unsigned int subtable_type = get_type ();
574     TRACE_DISPATCH (this, subtable_type);
575     switch (subtable_type) {
576     case Rearrangement:         return_trace (c->dispatch (u.rearrangement));
577     case Contextual:            return_trace (c->dispatch (u.contextual));
578     case Ligature:              return_trace (c->dispatch (u.ligature));
579     case Noncontextual:         return_trace (c->dispatch (u.noncontextual));
580     case Insertion:             return_trace (c->dispatch (u.insertion));
581     default:                    return_trace (c->default_return_value ());
582     }
583   }
584
585   inline bool sanitize (hb_sanitize_context_t *c) const
586   {
587     TRACE_SANITIZE (this);
588     if (!length.sanitize (c) ||
589         length < min_size ||
590         !c->check_range (this, length))
591       return_trace (false);
592
593     return_trace (dispatch (c));
594   }
595
596   protected:
597   HBUINT32      length;         /* Total subtable length, including this header. */
598   HBUINT32      coverage;       /* Coverage flags and subtable type. */
599   HBUINT32      subFeatureFlags;/* The 32-bit mask identifying which subtable this is. */
600   union {
601   RearrangementSubtable         rearrangement;
602   ContextualSubtable            contextual;
603   LigatureSubtable              ligature;
604   NoncontextualSubtable         noncontextual;
605   InsertionSubtable             insertion;
606   } u;
607   public:
608   DEFINE_SIZE_MIN (12);
609 };
610
611 struct Chain
612 {
613   inline void apply (hb_aat_apply_context_t *c) const
614   {
615     const ChainSubtable *subtable = &StructAtOffset<ChainSubtable> (featureZ, featureZ[0].static_size * featureCount);
616     unsigned int count = subtableCount;
617     for (unsigned int i = 0; i < count; i++)
618     {
619       if (!c->buffer->message (c->font, "start chain subtable %d", c->lookup_index))
620       {
621         c->set_lookup_index (c->lookup_index + 1);
622         continue;
623       }
624
625       subtable->apply (c);
626       subtable = &StructAfter<ChainSubtable> (*subtable);
627
628       (void) c->buffer->message (c->font, "end chain subtable %d", c->lookup_index);
629
630       c->set_lookup_index (c->lookup_index + 1);
631     }
632   }
633
634   inline unsigned int get_size (void) const { return length; }
635
636   inline bool sanitize (hb_sanitize_context_t *c, unsigned int major) const
637   {
638     TRACE_SANITIZE (this);
639     if (!length.sanitize (c) ||
640         length < min_size ||
641         !c->check_range (this, length))
642       return_trace (false);
643
644     if (!c->check_array (featureZ, featureZ[0].static_size, featureCount))
645       return_trace (false);
646
647     const ChainSubtable *subtable = &StructAtOffset<ChainSubtable> (featureZ, featureZ[0].static_size * featureCount);
648     unsigned int count = subtableCount;
649     for (unsigned int i = 0; i < count; i++)
650     {
651       if (!subtable->sanitize (c))
652         return_trace (false);
653       subtable = &StructAfter<ChainSubtable> (*subtable);
654     }
655
656     return_trace (true);
657   }
658
659   protected:
660   HBUINT32      defaultFlags;   /* The default specification for subtables. */
661   HBUINT32      length;         /* Total byte count, including this header. */
662   HBUINT32      featureCount;   /* Number of feature subtable entries. */
663   HBUINT32      subtableCount;  /* The number of subtables in the chain. */
664
665   Feature       featureZ[VAR];  /* Features. */
666   ChainSubtable subtableX[VAR]; /* Subtables. */
667   // subtableGlyphCoverageArray if major == 3
668
669   public:
670   DEFINE_SIZE_MIN (16);
671 };
672
673
674 /*
675  * The 'mort'/'morx' Tables
676  */
677
678 struct morx
679 {
680   static const hb_tag_t tableTag = HB_AAT_TAG_MORX;
681
682   inline void apply (hb_aat_apply_context_t *c) const
683   {
684     c->set_lookup_index (0);
685     const Chain *chain = chains;
686     unsigned int count = chainCount;
687     for (unsigned int i = 0; i < count; i++)
688     {
689       chain->apply (c);
690       chain = &StructAfter<Chain> (*chain);
691     }
692   }
693
694   inline bool sanitize (hb_sanitize_context_t *c) const
695   {
696     TRACE_SANITIZE (this);
697     if (!version.sanitize (c) ||
698         (version.major >> (sizeof (HBUINT32) == 4 ? 1 : 0)) != 1 ||
699         !chainCount.sanitize (c))
700       return_trace (false);
701
702     const Chain *chain = chains;
703     unsigned int count = chainCount;
704     for (unsigned int i = 0; i < count; i++)
705     {
706       if (!chain->sanitize (c, version.major))
707         return_trace (false);
708       chain = &StructAfter<Chain> (*chain);
709     }
710
711     return_trace (true);
712   }
713
714   protected:
715   FixedVersion<>version;        /* Version number of the glyph metamorphosis table.
716                                  * 1 for mort, 2 or 3 for morx. */
717   HBUINT32      chainCount;     /* Number of metamorphosis chains contained in this
718                                  * table. */
719   Chain         chains[VAR];    /* Chains. */
720
721   public:
722   DEFINE_SIZE_MIN (8);
723 };
724
725 } /* namespace AAT */
726
727
728 #endif /* HB_AAT_LAYOUT_MORX_TABLE_HH */