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