/*
 * Copyright © 2017  Google, Inc.
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * Google Author(s): Behdad Esfahbod
 */

#ifndef HB_AAT_LAYOUT_MORX_TABLE_HH
#define HB_AAT_LAYOUT_MORX_TABLE_HH

#include "hb-open-type.hh"
#include "hb-aat-layout-common.hh"
#include "hb-ot-layout.hh"
#include "hb-aat-map.hh"

/*
 * morx -- Extended Glyph Metamorphosis
 * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
 * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
 */
#define HB_AAT_TAG_morx HB_TAG('m','o','r','x')
#define HB_AAT_TAG_mort HB_TAG('m','o','r','t')


namespace AAT {

using namespace OT;

template <typename Types>
struct RearrangementSubtable
{
  typedef typename Types::HBUINT HBUINT;

  typedef void EntryData;

  enum Flags
  {
    MarkFirst           = 0x8000,       /* If set, make the current glyph the first
                                         * glyph to be rearranged. */
    DontAdvance         = 0x4000,       /* If set, don't advance to the next glyph
                                         * before going to the new state. This means
                                         * that the glyph index doesn't change, even
                                         * if the glyph at that index has changed. */
    MarkLast            = 0x2000,       /* If set, make the current glyph the last
                                         * glyph to be rearranged. */
    Reserved            = 0x1FF0,       /* These bits are reserved and should be set to 0. */
    Verb                = 0x000F,       /* The type of rearrangement specified. */
  };

  bool is_action_initiable (const Entry<EntryData> &entry) const
  {
    return (entry.flags & MarkFirst);
  }
  bool is_actionable (const Entry<EntryData> &entry) const
  {
    return (entry.flags & Verb);
  }

  struct driver_context_t
  {
    static constexpr bool in_place = true;

    driver_context_t (const RearrangementSubtable *table_) :
        ret (false),
        table (table_),
        start (0), end (0) {}

    void transition (hb_buffer_t *buffer,
                     StateTableDriver<Types, EntryData, Flags> *driver,
                     const Entry<EntryData> &entry)
    {
      unsigned int flags = entry.flags;

      if (flags & MarkFirst)
        start = buffer->idx;

      if (flags & MarkLast)
        end = hb_min (buffer->idx + 1, buffer->len);

      if ((flags & Verb) && start < end)
      {
        /* The following map has two nibbles, for start-side
         * and end-side. Values of 0,1,2 mean move that many
         * to the other side. Value of 3 means move 2 and
         * flip them. */
        const unsigned char map[16] =
        {
          0x00, /* 0    no change */
          0x10, /* 1    Ax => xA */
          0x01, /* 2    xD => Dx */
          0x11, /* 3    AxD => DxA */
          0x20, /* 4    ABx => xAB */
          0x30, /* 5    ABx => xBA */
          0x02, /* 6    xCD => CDx */
          0x03, /* 7    xCD => DCx */
          0x12, /* 8    AxCD => CDxA */
          0x13, /* 9    AxCD => DCxA */
          0x21, /* 10   ABxD => DxAB */
          0x31, /* 11   ABxD => DxBA */
          0x22, /* 12   ABxCD => CDxAB */
          0x32, /* 13   ABxCD => CDxBA */
          0x23, /* 14   ABxCD => DCxAB */
          0x33, /* 15   ABxCD => DCxBA */
        };

        unsigned int m = map[flags & Verb];
        unsigned int l = hb_min (2u, m >> 4);
        unsigned int r = hb_min (2u, m & 0x0F);
        bool reverse_l = 3 == (m >> 4);
        bool reverse_r = 3 == (m & 0x0F);

        if (end - start >= l + r && end-start <= HB_MAX_CONTEXT_LENGTH)
        {
          buffer->merge_clusters (start, hb_min (buffer->idx + 1, buffer->len));
          buffer->merge_clusters (start, end);

          hb_glyph_info_t *info = buffer->info;
          hb_glyph_info_t buf[4];

          hb_memcpy (buf, info + start, l * sizeof (buf[0]));
          hb_memcpy (buf + 2, info + end - r, r * sizeof (buf[0]));

          if (l != r)
            memmove (info + start + r, info + start + l, (end - start - l - r) * sizeof (buf[0]));

          hb_memcpy (info + start, buf + 2, r * sizeof (buf[0]));
          hb_memcpy (info + end - l, buf, l * sizeof (buf[0]));
          if (reverse_l)
          {
            buf[0] = info[end - 1];
            info[end - 1] = info[end - 2];
            info[end - 2] = buf[0];
          }
          if (reverse_r)
          {
            buf[0] = info[start];
            info[start] = info[start + 1];
            info[start + 1] = buf[0];
          }
        }
      }
    }

    public:
    bool ret;
    const RearrangementSubtable *table;
    private:
    unsigned int start;
    unsigned int end;
  };

  bool apply (hb_aat_apply_context_t *c) const
  {
    TRACE_APPLY (this);

    driver_context_t dc (this);

    StateTableDriver<Types, EntryData, Flags> driver (machine, c->face);

    driver.drive (&dc, c);

    return_trace (dc.ret);
  }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (machine.sanitize (c));
  }

  public:
  StateTable<Types, EntryData>  machine;
  public:
  DEFINE_SIZE_STATIC ((StateTable<Types, EntryData>::static_size));
};

template <typename Types>
struct ContextualSubtable
{
  typedef typename Types::HBUINT HBUINT;

  struct EntryData
  {
    HBUINT16    markIndex;      /* Index of the substitution table for the
                                 * marked glyph (use 0xFFFF for none). */
    HBUINT16    currentIndex;   /* Index of the substitution table for the
                                 * current glyph (use 0xFFFF for none). */
    public:
    DEFINE_SIZE_STATIC (4);
  };

  enum Flags
  {
    SetMark             = 0x8000,       /* If set, make the current glyph the marked glyph. */
    DontAdvance         = 0x4000,       /* If set, don't advance to the next glyph before
                                         * going to the new state. */
    Reserved            = 0x3FFF,       /* These bits are reserved and should be set to 0. */
  };

  bool is_action_initiable (const Entry<EntryData> &entry) const
  {
    return (entry.flags & SetMark);
  }
  bool is_actionable (const Entry<EntryData> &entry) const
  {
    return entry.data.markIndex != 0xFFFF || entry.data.currentIndex != 0xFFFF;
  }

  struct driver_context_t
  {
    static constexpr bool in_place = true;

    driver_context_t (const ContextualSubtable *table_,
                             hb_aat_apply_context_t *c_) :
        ret (false),
        c (c_),
        table (table_),
        mark_set (false),
        mark (0),
        subs (table+table->substitutionTables) {}

    void transition (hb_buffer_t *buffer,
                     StateTableDriver<Types, EntryData, Flags> *driver,
                     const Entry<EntryData> &entry)
    {
      /* Looks like CoreText applies neither mark nor current substitution for
       * end-of-text if mark was not explicitly set. */
      if (buffer->idx == buffer->len && !mark_set)
        return;

      const HBGlyphID16 *replacement;

      replacement = nullptr;
      if (Types::extended)
      {
        if (entry.data.markIndex != 0xFFFF)
        {
          const Lookup<HBGlyphID16> &lookup = subs[entry.data.markIndex];
          replacement = lookup.get_value (buffer->info[mark].codepoint, driver->num_glyphs);
        }
      }
      else
      {
        unsigned int offset = entry.data.markIndex + buffer->info[mark].codepoint;
        const UnsizedArrayOf<HBGlyphID16> &subs_old = (const UnsizedArrayOf<HBGlyphID16> &) subs;
        replacement = &subs_old[Types::wordOffsetToIndex (offset, table, subs_old.arrayZ)];
        if (!(replacement->sanitize (&c->sanitizer) &&
              hb_barrier () &&
              *replacement))
          replacement = nullptr;
      }
      if (replacement)
      {
        buffer->unsafe_to_break (mark, hb_min (buffer->idx + 1, buffer->len));
        c->replace_glyph_inplace (mark, *replacement);
        ret = true;
      }

      replacement = nullptr;
      unsigned int idx = hb_min (buffer->idx, buffer->len - 1);
      if (Types::extended)
      {
        if (entry.data.currentIndex != 0xFFFF)
        {
          const Lookup<HBGlyphID16> &lookup = subs[entry.data.currentIndex];
          replacement = lookup.get_value (buffer->info[idx].codepoint, driver->num_glyphs);
        }
      }
      else
      {
        unsigned int offset = entry.data.currentIndex + buffer->info[idx].codepoint;
        const UnsizedArrayOf<HBGlyphID16> &subs_old = (const UnsizedArrayOf<HBGlyphID16> &) subs;
        replacement = &subs_old[Types::wordOffsetToIndex (offset, table, subs_old.arrayZ)];
        if (!(replacement->sanitize (&c->sanitizer) &&
              hb_barrier () &&
              *replacement))
          replacement = nullptr;
      }
      if (replacement)
      {
        c->replace_glyph_inplace (idx, *replacement);
        ret = true;
      }

      if (entry.flags & SetMark)
      {
        mark_set = true;
        mark = buffer->idx;
      }
    }

    public:
    bool ret;
    hb_aat_apply_context_t *c;
    const ContextualSubtable *table;
    private:
    bool mark_set;
    unsigned int mark;
    const UnsizedListOfOffset16To<Lookup<HBGlyphID16>, HBUINT, void, false> &subs;
  };

  bool apply (hb_aat_apply_context_t *c) const
  {
    TRACE_APPLY (this);

    driver_context_t dc (this, c);

    StateTableDriver<Types, EntryData, Flags> driver (machine, c->face);

    driver.drive (&dc, c);

    return_trace (dc.ret);
  }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);

    unsigned int num_entries = 0;
    if (unlikely (!machine.sanitize (c, &num_entries))) return_trace (false);
    hb_barrier ();

    if (!Types::extended)
      return_trace (substitutionTables.sanitize (c, this, 0));

    unsigned int num_lookups = 0;

    const Entry<EntryData> *entries = machine.get_entries ();
    for (unsigned int i = 0; i < num_entries; i++)
    {
      const EntryData &data = entries[i].data;

      if (data.markIndex != 0xFFFF)
        num_lookups = hb_max (num_lookups, 1u + data.markIndex);
      if (data.currentIndex != 0xFFFF)
        num_lookups = hb_max (num_lookups, 1u + data.currentIndex);
    }

    return_trace (substitutionTables.sanitize (c, this, num_lookups));
  }

  public:
  StateTable<Types, EntryData>
                machine;
  protected:
  NNOffsetTo<UnsizedListOfOffset16To<Lookup<HBGlyphID16>, HBUINT, void, false>, HBUINT>
                substitutionTables;
  public:
  DEFINE_SIZE_STATIC ((StateTable<Types, EntryData>::static_size + HBUINT::static_size));
};


template <bool extended>
struct LigatureEntry;

template <>
struct LigatureEntry<true>
{

  struct EntryData
  {
    HBUINT16    ligActionIndex; /* Index to the first ligActionTable entry
                                 * for processing this group, if indicated
                                 * by the flags. */
    public:
    DEFINE_SIZE_STATIC (2);
  };

  enum Flags
  {
    SetComponent        = 0x8000,       /* Push this glyph onto the component stack for
                                         * eventual processing. */
    DontAdvance         = 0x4000,       /* Leave the glyph pointer at this glyph for the
                                           next iteration. */
    PerformAction       = 0x2000,       /* Use the ligActionIndex to process a ligature
                                         * group. */
    Reserved            = 0x1FFF,       /* These bits are reserved and should be set to 0. */
  };

  static bool initiateAction (const Entry<EntryData> &entry)
  { return entry.flags & SetComponent; }

  static bool performAction (const Entry<EntryData> &entry)
  { return entry.flags & PerformAction; }

  static unsigned int ligActionIndex (const Entry<EntryData> &entry)
  { return entry.data.ligActionIndex; }
};
template <>
struct LigatureEntry<false>
{
  typedef void EntryData;

  enum Flags
  {
    SetComponent        = 0x8000,       /* Push this glyph onto the component stack for
                                         * eventual processing. */
    DontAdvance         = 0x4000,       /* Leave the glyph pointer at this glyph for the
                                           next iteration. */
    Offset              = 0x3FFF,       /* Byte offset from beginning of subtable to the
                                         * ligature action list. This value must be a
                                         * multiple of 4. */
  };

  static bool initiateAction (const Entry<EntryData> &entry)
  { return entry.flags & SetComponent; }

  static bool performAction (const Entry<EntryData> &entry)
  { return entry.flags & Offset; }

  static unsigned int ligActionIndex (const Entry<EntryData> &entry)
  { return entry.flags & Offset; }
};


template <typename Types>
struct LigatureSubtable
{
  typedef typename Types::HBUINT HBUINT;

  typedef LigatureEntry<Types::extended> LigatureEntryT;
  typedef typename LigatureEntryT::EntryData EntryData;

  enum Flags
  {
    DontAdvance = LigatureEntryT::DontAdvance,
  };

  bool is_action_initiable (const Entry<EntryData> &entry) const
  {
    return LigatureEntryT::initiateAction (entry);
  }
  bool is_actionable (const Entry<EntryData> &entry) const
  {
    return LigatureEntryT::performAction (entry);
  }

  struct driver_context_t
  {
    static constexpr bool in_place = false;
    enum LigActionFlags
    {
      LigActionLast     = 0x80000000,   /* This is the last action in the list. This also
                                         * implies storage. */
      LigActionStore    = 0x40000000,   /* Store the ligature at the current cumulated index
                                         * in the ligature table in place of the marked
                                         * (i.e. currently-popped) glyph. */
      LigActionOffset   = 0x3FFFFFFF,   /* A 30-bit value which is sign-extended to 32-bits
                                         * and added to the glyph ID, resulting in an index
                                         * into the component table. */
    };

    driver_context_t (const LigatureSubtable *table_,
                      hb_aat_apply_context_t *c_) :
        ret (false),
        c (c_),
        table (table_),
        ligAction (table+table->ligAction),
        component (table+table->component),
        ligature (table+table->ligature),
        match_length (0) {}

    void transition (hb_buffer_t *buffer,
                     StateTableDriver<Types, EntryData, Flags> *driver,
                     const Entry<EntryData> &entry)
    {
      DEBUG_MSG (APPLY, nullptr, "Ligature transition at %u", buffer->idx);
      if (entry.flags & LigatureEntryT::SetComponent)
      {
        /* Never mark same index twice, in case DontAdvance was used... */
        if (match_length && match_positions[(match_length - 1u) % ARRAY_LENGTH (match_positions)] == buffer->out_len)
          match_length--;

        match_positions[match_length++ % ARRAY_LENGTH (match_positions)] = buffer->out_len;
        DEBUG_MSG (APPLY, nullptr, "Set component at %u", buffer->out_len);
      }

      if (LigatureEntryT::performAction (entry))
      {
        DEBUG_MSG (APPLY, nullptr, "Perform action with %u", match_length);
        unsigned int end = buffer->out_len;

        if (unlikely (!match_length))
          return;

        if (buffer->idx >= buffer->len)
          return; /* TODO Work on previous instead? */

        unsigned int cursor = match_length;

        unsigned int action_idx = LigatureEntryT::ligActionIndex (entry);
        action_idx = Types::offsetToIndex (action_idx, table, ligAction.arrayZ);
        const HBUINT32 *actionData = &ligAction[action_idx];

        unsigned int ligature_idx = 0;
        unsigned int action;
        do
        {
          if (unlikely (!cursor))
          {
            /* Stack underflow.  Clear the stack. */
            DEBUG_MSG (APPLY, nullptr, "Stack underflow");
            match_length = 0;
            break;
          }

          DEBUG_MSG (APPLY, nullptr, "Moving to stack position %u", cursor - 1);
          if (unlikely (!buffer->move_to (match_positions[--cursor % ARRAY_LENGTH (match_positions)]))) return;

          if (unlikely (!actionData->sanitize (&c->sanitizer))) break;
          hb_barrier ();
          action = *actionData;

          uint32_t uoffset = action & LigActionOffset;
          if (uoffset & 0x20000000)
            uoffset |= 0xC0000000; /* Sign-extend. */
          int32_t offset = (int32_t) uoffset;
          unsigned int component_idx = buffer->cur().codepoint + offset;
          component_idx = Types::wordOffsetToIndex (component_idx, table, component.arrayZ);
          const HBUINT16 &componentData = component[component_idx];
          if (unlikely (!componentData.sanitize (&c->sanitizer))) break;
          hb_barrier ();
          ligature_idx += componentData;

          DEBUG_MSG (APPLY, nullptr, "Action store %d last %d",
                     bool (action & LigActionStore),
                     bool (action & LigActionLast));
          if (action & (LigActionStore | LigActionLast))
          {
            ligature_idx = Types::offsetToIndex (ligature_idx, table, ligature.arrayZ);
            const HBGlyphID16 &ligatureData = ligature[ligature_idx];
            if (unlikely (!ligatureData.sanitize (&c->sanitizer))) break;
            hb_barrier ();
            hb_codepoint_t lig = ligatureData;

            DEBUG_MSG (APPLY, nullptr, "Produced ligature %u", lig);
            if (unlikely (!c->replace_glyph (lig))) return;

            unsigned int lig_end = match_positions[(match_length - 1u) % ARRAY_LENGTH (match_positions)] + 1u;
            /* Now go and delete all subsequent components. */
            while (match_length - 1u > cursor)
            {
              DEBUG_MSG (APPLY, nullptr, "Skipping ligature component");
              if (unlikely (!buffer->move_to (match_positions[--match_length % ARRAY_LENGTH (match_positions)]))) return;
              if (!c->delete_glyph ()) return;
            }

            if (unlikely (!buffer->move_to (lig_end))) return;
            buffer->merge_out_clusters (match_positions[cursor % ARRAY_LENGTH (match_positions)], buffer->out_len);
          }

          actionData++;
        }
        while (!(action & LigActionLast));
        if (unlikely (!buffer->move_to (end))) return;
      }
    }

    public:
    bool ret;
    hb_aat_apply_context_t *c;
    const LigatureSubtable *table;
    private:
    const UnsizedArrayOf<HBUINT32> &ligAction;
    const UnsizedArrayOf<HBUINT16> &component;
    const UnsizedArrayOf<HBGlyphID16> &ligature;
    unsigned int match_length;
    unsigned int match_positions[HB_MAX_CONTEXT_LENGTH];
  };

  bool apply (hb_aat_apply_context_t *c) const
  {
    TRACE_APPLY (this);

    driver_context_t dc (this, c);

    StateTableDriver<Types, EntryData, Flags> driver (machine, c->face);

    driver.drive (&dc, c);

    return_trace (dc.ret);
  }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    /* The rest of array sanitizations are done at run-time. */
    return_trace (c->check_struct (this) && machine.sanitize (c) &&
                  hb_barrier () &&
                  ligAction && component && ligature);
  }

  public:
  StateTable<Types, EntryData>
                machine;
  protected:
  NNOffsetTo<UnsizedArrayOf<HBUINT32>, HBUINT>
                ligAction;      /* Offset to the ligature action table. */
  NNOffsetTo<UnsizedArrayOf<HBUINT16>, HBUINT>
                component;      /* Offset to the component table. */
  NNOffsetTo<UnsizedArrayOf<HBGlyphID16>, HBUINT>
                ligature;       /* Offset to the actual ligature lists. */
  public:
  DEFINE_SIZE_STATIC ((StateTable<Types, EntryData>::static_size + 3 * HBUINT::static_size));
};

template <typename Types>
struct NoncontextualSubtable
{
  bool apply (hb_aat_apply_context_t *c) const
  {
    TRACE_APPLY (this);

    bool ret = false;
    unsigned int num_glyphs = c->face->get_num_glyphs ();

    hb_glyph_info_t *info = c->buffer->info;
    unsigned int count = c->buffer->len;
    // If there's only one range, we already checked the flag.
    auto *last_range = c->range_flags && (c->range_flags->length > 1) ? &(*c->range_flags)[0] : nullptr;
    for (unsigned int i = 0; i < count; i++)
    {
      /* This block copied from StateTableDriver::drive. Keep in sync. */
      if (last_range)
      {
        auto *range = last_range;
        {
          unsigned cluster = info[i].cluster;
          while (cluster < range->cluster_first)
            range--;
          while (cluster > range->cluster_last)
            range++;

          last_range = range;
        }
        if (!(range->flags & c->subtable_flags))
          continue;
      }

      const HBGlyphID16 *replacement = substitute.get_value (info[i].codepoint, num_glyphs);
      if (replacement)
      {
        c->replace_glyph_inplace (i, *replacement);
        ret = true;
      }
    }

    return_trace (ret);
  }

  template <typename set_t>
  void collect_initial_glyphs (set_t &glyphs, unsigned num_glyphs) const
  {
    substitute.collect_glyphs (glyphs, num_glyphs);
  }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (substitute.sanitize (c));
  }

  protected:
  Lookup<HBGlyphID16>   substitute;
  public:
  DEFINE_SIZE_MIN (2);
};

template <typename Types>
struct InsertionSubtable
{
  typedef typename Types::HBUINT HBUINT;

  struct EntryData
  {
    HBUINT16    currentInsertIndex;     /* Zero-based index into the insertion glyph table.
                                         * The number of glyphs to be inserted is contained
                                         * in the currentInsertCount field in the flags.
                                         * A value of 0xFFFF indicates no insertion is to
                                         * be done. */
    HBUINT16    markedInsertIndex;      /* Zero-based index into the insertion glyph table.
                                         * The number of glyphs to be inserted is contained
                                         * in the markedInsertCount field in the flags.
                                         * A value of 0xFFFF indicates no insertion is to
                                         * be done. */
    public:
    DEFINE_SIZE_STATIC (4);
  };

  enum Flags
  {
    SetMark             = 0x8000,     /* If set, mark the current glyph. */
    DontAdvance         = 0x4000,     /* If set, don't advance to the next glyph before
                                       * going to the new state.  This does not mean
                                       * that the glyph pointed to is the same one as
                                       * before. If you've made insertions immediately
                                       * downstream of the current glyph, the next glyph
                                       * processed would in fact be the first one
                                       * inserted. */
    CurrentIsKashidaLike= 0x2000,     /* If set, and the currentInsertList is nonzero,
                                       * then the specified glyph list will be inserted
                                       * as a kashida-like insertion, either before or
                                       * after the current glyph (depending on the state
                                       * of the currentInsertBefore flag). If clear, and
                                       * the currentInsertList is nonzero, then the
                                       * specified glyph list will be inserted as a
                                       * split-vowel-like insertion, either before or
                                       * after the current glyph (depending on the state
                                       * of the currentInsertBefore flag). */
    MarkedIsKashidaLike= 0x1000,      /* If set, and the markedInsertList is nonzero,
                                       * then the specified glyph list will be inserted
                                       * as a kashida-like insertion, either before or
                                       * after the marked glyph (depending on the state
                                       * of the markedInsertBefore flag). If clear, and
                                       * the markedInsertList is nonzero, then the
                                       * specified glyph list will be inserted as a
                                       * split-vowel-like insertion, either before or
                                       * after the marked glyph (depending on the state
                                       * of the markedInsertBefore flag). */
    CurrentInsertBefore= 0x0800,      /* If set, specifies that insertions are to be made
                                       * to the left of the current glyph. If clear,
                                       * they're made to the right of the current glyph. */
    MarkedInsertBefore= 0x0400,       /* If set, specifies that insertions are to be
                                       * made to the left of the marked glyph. If clear,
                                       * they're made to the right of the marked glyph. */
    CurrentInsertCount= 0x3E0,        /* This 5-bit field is treated as a count of the
                                       * number of glyphs to insert at the current
                                       * position. Since zero means no insertions, the
                                       * largest number of insertions at any given
                                       * current location is 31 glyphs. */
    MarkedInsertCount= 0x001F,        /* This 5-bit field is treated as a count of the
                                       * number of glyphs to insert at the marked
                                       * position. Since zero means no insertions, the
                                       * largest number of insertions at any given
                                       * marked location is 31 glyphs. */
  };

  bool is_action_initiable (const Entry<EntryData> &entry) const
  {
    return (entry.flags & SetMark);
  }
  bool is_actionable (const Entry<EntryData> &entry) const
  {
    return (entry.flags & (CurrentInsertCount | MarkedInsertCount)) &&
           (entry.data.currentInsertIndex != 0xFFFF ||entry.data.markedInsertIndex != 0xFFFF);
  }

  struct driver_context_t
  {
    static constexpr bool in_place = false;

    driver_context_t (const InsertionSubtable *table_,
                      hb_aat_apply_context_t *c_) :
        ret (false),
        c (c_),
        table (table_),
        mark (0),
        insertionAction (table+table->insertionAction) {}

    void transition (hb_buffer_t *buffer,
                     StateTableDriver<Types, EntryData, Flags> *driver,
                     const Entry<EntryData> &entry)
    {
      unsigned int flags = entry.flags;

      unsigned mark_loc = buffer->out_len;

      if (entry.data.markedInsertIndex != 0xFFFF)
      {
        unsigned int count = (flags & MarkedInsertCount);
        if (unlikely ((buffer->max_ops -= count) <= 0)) return;
        unsigned int start = entry.data.markedInsertIndex;
        const HBGlyphID16 *glyphs = &insertionAction[start];
        if (unlikely (!c->sanitizer.check_array (glyphs, count))) count = 0;
        hb_barrier ();

        bool before = flags & MarkedInsertBefore;

        unsigned int end = buffer->out_len;
        if (unlikely (!buffer->move_to (mark))) return;

        if (buffer->idx < buffer->len && !before)
          if (unlikely (!buffer->copy_glyph ())) return;
        /* TODO We ignore KashidaLike setting. */
        if (unlikely (!c->output_glyphs (count, glyphs))) return;
        ret = true;
        if (buffer->idx < buffer->len && !before)
          buffer->skip_glyph ();

        if (unlikely (!buffer->move_to (end + count))) return;

        buffer->unsafe_to_break_from_outbuffer (mark, hb_min (buffer->idx + 1, buffer->len));
      }

      if (flags & SetMark)
        mark = mark_loc;

      if (entry.data.currentInsertIndex != 0xFFFF)
      {
        unsigned int count = (flags & CurrentInsertCount) >> 5;
        if (unlikely ((buffer->max_ops -= count) <= 0)) return;
        unsigned int start = entry.data.currentInsertIndex;
        const HBGlyphID16 *glyphs = &insertionAction[start];
        if (unlikely (!c->sanitizer.check_array (glyphs, count))) count = 0;
        hb_barrier ();

        bool before = flags & CurrentInsertBefore;

        unsigned int end = buffer->out_len;

        if (buffer->idx < buffer->len && !before)
          if (unlikely (!buffer->copy_glyph ())) return;
        /* TODO We ignore KashidaLike setting. */
        if (unlikely (!c->output_glyphs (count, glyphs))) return;
        ret = true;
        if (buffer->idx < buffer->len && !before)
          buffer->skip_glyph ();

        /* Humm. Not sure where to move to.  There's this wording under
         * DontAdvance flag:
         *
         * "If set, don't update the glyph index before going to the new state.
         * This does not mean that the glyph pointed to is the same one as
         * before. If you've made insertions immediately downstream of the
         * current glyph, the next glyph processed would in fact be the first
         * one inserted."
         *
         * This suggests that if DontAdvance is NOT set, we should move to
         * end+count.  If it *was*, then move to end, such that newly inserted
         * glyphs are now visible.
         *
         * https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417
         */
        if (unlikely (!buffer->move_to ((flags & DontAdvance) ? end : end + count))) return;
      }
    }

    public:
    bool ret;
    hb_aat_apply_context_t *c;
    const InsertionSubtable *table;
    private:
    unsigned int mark;
    const UnsizedArrayOf<HBGlyphID16> &insertionAction;
  };

  bool apply (hb_aat_apply_context_t *c) const
  {
    TRACE_APPLY (this);

    driver_context_t dc (this, c);

    StateTableDriver<Types, EntryData, Flags> driver (machine, c->face);

    driver.drive (&dc, c);

    return_trace (dc.ret);
  }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    /* The rest of array sanitizations are done at run-time. */
    return_trace (c->check_struct (this) && machine.sanitize (c) &&
                  hb_barrier () &&
                  insertionAction);
  }

  public:
  StateTable<Types, EntryData>
                machine;
  protected:
  NNOffsetTo<UnsizedArrayOf<HBGlyphID16>, HBUINT>
                insertionAction;        /* Byte offset from stateHeader to the start of
                                         * the insertion glyph table. */
  public:
  DEFINE_SIZE_STATIC ((StateTable<Types, EntryData>::static_size + HBUINT::static_size));
};


struct Feature
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  public:
  HBUINT16      featureType;    /* The type of feature. */
  HBUINT16      featureSetting; /* The feature's setting (aka selector). */
  HBUINT32      enableFlags;    /* Flags for the settings that this feature
                                 * and setting enables. */
  HBUINT32      disableFlags;   /* Complement of flags for the settings that this
                                 * feature and setting disable. */

  public:
  DEFINE_SIZE_STATIC (12);
};


struct hb_accelerate_subtables_context_t :
       hb_dispatch_context_t<hb_accelerate_subtables_context_t>
{
  struct hb_applicable_t
  {
    friend struct hb_accelerate_subtables_context_t;
    friend struct hb_aat_layout_lookup_accelerator_t;

    public:
    hb_bit_set_t glyph_set;
    mutable hb_aat_class_cache_t class_cache;

    template <typename T>
    auto init_ (const T &obj_, unsigned num_glyphs, hb_priority<1>) HB_AUTO_RETURN
    (
      obj_.machine.collect_initial_glyphs (glyph_set, num_glyphs, obj_)
    )

    template <typename T>
    void init_ (const T &obj_, unsigned num_glyphs, hb_priority<0>)
    {
      obj_.collect_initial_glyphs (glyph_set, num_glyphs);
    }

    template <typename T>
    void init (const T &obj_, unsigned num_glyphs)
    {
      glyph_set.init ();
      init_ (obj_, num_glyphs, hb_prioritize);
      class_cache.clear ();
    }

    void
    fini ()
    {
      glyph_set.fini ();
    }
  };

  /* Dispatch interface. */
  template <typename T>
  return_t dispatch (const T &obj)
  {
    hb_applicable_t *entry = &array[i++];

    entry->init (obj, num_glyphs);

    return hb_empty_t ();
  }
  static return_t default_return_value () { return hb_empty_t (); }

  bool stop_sublookup_iteration (return_t r) const { return false; }

  hb_accelerate_subtables_context_t (hb_applicable_t *array_, unsigned num_glyphs_) :
                                     hb_dispatch_context_t<hb_accelerate_subtables_context_t> (),
                                     array (array_), num_glyphs (num_glyphs_) {}

  hb_applicable_t *array;
  unsigned num_glyphs;
  unsigned i = 0;
};

struct hb_aat_layout_chain_accelerator_t
{
  template <typename TChain>
  static hb_aat_layout_chain_accelerator_t *create (const TChain &chain, unsigned num_glyphs)
  {
    unsigned count = chain.get_subtable_count ();

    unsigned size = sizeof (hb_aat_layout_chain_accelerator_t) -
                    HB_VAR_ARRAY * sizeof (hb_accelerate_subtables_context_t::hb_applicable_t) +
                    count * sizeof (hb_accelerate_subtables_context_t::hb_applicable_t);

    /* The following is a calloc because when we are collecting subtables,
     * some of them might be invalid and hence not collect; as a result,
     * we might not fill in all the count entries of the subtables array.
     * Zeroing it allows the set digest to gatekeep it without having to
     * initialize it further. */
    auto *thiz = (hb_aat_layout_chain_accelerator_t *) hb_calloc (1, size);
    if (unlikely (!thiz))
      return nullptr;

    thiz->count = count;

    hb_accelerate_subtables_context_t c_accelerate_subtables (thiz->subtables, num_glyphs);
    chain.dispatch (&c_accelerate_subtables);

    return thiz;
  }

  void destroy ()
  {
    for (unsigned i = 0; i < count; i++)
      subtables[i].fini ();
  }

  unsigned count;
  hb_accelerate_subtables_context_t::hb_applicable_t subtables[HB_VAR_ARRAY];
};

template <typename Types>
struct ChainSubtable
{
  typedef typename Types::HBUINT HBUINT;

  template <typename T>
  friend struct Chain;

  unsigned int get_size () const     { return length; }
  unsigned int get_type () const     { return coverage & 0xFF; }
  unsigned int get_coverage () const { return coverage >> (sizeof (HBUINT) * 8 - 8); }

  enum Coverage
  {
    Vertical            = 0x80, /* If set, this subtable will only be applied
                                 * to vertical text. If clear, this subtable
                                 * will only be applied to horizontal text. */
    Backwards           = 0x40, /* If set, this subtable will process glyphs
                                 * in descending order. If clear, it will
                                 * process the glyphs in ascending order. */
    AllDirections       = 0x20, /* If set, this subtable will be applied to
                                 * both horizontal and vertical text (i.e.
                                 * the state of bit 0x80000000 is ignored). */
    Logical             = 0x10, /* If set, this subtable will process glyphs
                                 * in logical order (or reverse logical order,
                                 * depending on the value of bit 0x80000000). */
  };
  enum Type
  {
    Rearrangement       = 0,
    Contextual          = 1,
    Ligature            = 2,
    Noncontextual       = 4,
    Insertion           = 5
  };

  template <typename context_t, typename ...Ts>
  typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
  {
    unsigned int subtable_type = get_type ();
    TRACE_DISPATCH (this, subtable_type);
    switch (subtable_type) {
    case Rearrangement:         return_trace (c->dispatch (u.rearrangement, std::forward<Ts> (ds)...));
    case Contextual:            return_trace (c->dispatch (u.contextual, std::forward<Ts> (ds)...));
    case Ligature:              return_trace (c->dispatch (u.ligature, std::forward<Ts> (ds)...));
    case Noncontextual:         return_trace (c->dispatch (u.noncontextual, std::forward<Ts> (ds)...));
    case Insertion:             return_trace (c->dispatch (u.insertion, std::forward<Ts> (ds)...));
    default:                    return_trace (c->default_return_value ());
    }
  }

  bool apply (hb_aat_apply_context_t *c) const
  {
    TRACE_APPLY (this);
    // Disabled for https://github.com/harfbuzz/harfbuzz/issues/4873
    //hb_sanitize_with_object_t with (&c->sanitizer, this);
    return_trace (dispatch (c));
  }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    if (!(length.sanitize (c) &&
          hb_barrier () &&
          length >= min_size &&
          c->check_range (this, length)))
      return_trace (false);

    // Disabled for https://github.com/harfbuzz/harfbuzz/issues/4873
    //hb_sanitize_with_object_t with (c, this);
    return_trace (dispatch (c));
  }

  protected:
  HBUINT        length;         /* Total subtable length, including this header. */
  HBUINT        coverage;       /* Coverage flags and subtable type. */
  HBUINT32      subFeatureFlags;/* The 32-bit mask identifying which subtable this is. */
  union {
  RearrangementSubtable<Types>  rearrangement;
  ContextualSubtable<Types>     contextual;
  LigatureSubtable<Types>       ligature;
  NoncontextualSubtable<Types>  noncontextual;
  InsertionSubtable<Types>      insertion;
  } u;
  public:
  DEFINE_SIZE_MIN (2 * sizeof (HBUINT) + 4);
};

template <typename Types>
struct Chain
{
  typedef typename Types::HBUINT HBUINT;

  unsigned get_subtable_count () const { return subtableCount; }

  hb_mask_t compile_flags (const hb_aat_map_builder_t *map) const
  {
    hb_mask_t flags = defaultFlags;
    {
      unsigned int count = featureCount;
      for (unsigned i = 0; i < count; i++)
      {
        const Feature &feature = featureZ[i];
        hb_aat_layout_feature_type_t type = (hb_aat_layout_feature_type_t) (unsigned int) feature.featureType;
        hb_aat_layout_feature_selector_t setting = (hb_aat_layout_feature_selector_t) (unsigned int) feature.featureSetting;
      retry:
        // Check whether this type/setting pair was requested in the map, and if so, apply its flags.
        // (The search here only looks at the type and setting fields of feature_info_t.)
        hb_aat_map_builder_t::feature_info_t info = { type, setting, false, 0 };
        if (map->current_features.bsearch (info))
        {
          flags &= feature.disableFlags;
          flags |= feature.enableFlags;
        }
        else if (type == HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE && setting == HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS)
        {
          /* Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342 */
          type = HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE;
          setting = HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS;
          goto retry;
        }
#ifndef HB_NO_AAT
        else if (type == HB_AAT_LAYOUT_FEATURE_TYPE_LANGUAGE_TAG_TYPE && setting &&
                 /* TODO: Rudimentary language matching. */
                 hb_language_matches (map->face->table.ltag->get_language (setting - 1), map->props.language))
        {
          flags &= feature.disableFlags;
          flags |= feature.enableFlags;
        }
#endif
      }
    }
    return flags;
  }

  void apply (hb_aat_apply_context_t *c,
              const hb_aat_layout_chain_accelerator_t *accel) const
  {
    const ChainSubtable<Types> *subtable = &StructAfter<ChainSubtable<Types>> (featureZ.as_array (featureCount));
    unsigned int count = subtableCount;
    for (unsigned int i = 0; i < count; i++)
    {
      bool reverse;

      auto coverage = subtable->get_coverage ();

      hb_mask_t subtable_flags = subtable->subFeatureFlags;
      if (hb_none (hb_iter (c->range_flags) |
                   hb_map ([subtable_flags] (const hb_aat_map_t::range_flags_t _) -> bool { return subtable_flags & (_.flags); })))
        goto skip;

      c->subtable_flags = subtable_flags;
      c->machine_glyph_set = accel ? &accel->subtables[i].glyph_set : &Null(hb_bit_set_t);
      c->machine_class_cache = accel ? &accel->subtables[i].class_cache : nullptr;

      if (!(coverage & ChainSubtable<Types>::AllDirections) &&
          HB_DIRECTION_IS_VERTICAL (c->buffer->props.direction) !=
          bool (coverage & ChainSubtable<Types>::Vertical))
        goto skip;

      if (!c->buffer_intersects_machine ())
      {
        (void) c->buffer->message (c->font, "skipped chainsubtable %u because no glyph matches", c->lookup_index);
        goto skip;
      }

      /* Buffer contents is always in logical direction.  Determine if
       * we need to reverse before applying this subtable.  We reverse
       * back after if we did reverse indeed.
       *
       * Quoting the spac:
       * """
       * Bits 28 and 30 of the coverage field control the order in which
       * glyphs are processed when the subtable is run by the layout engine.
       * Bit 28 is used to indicate if the glyph processing direction is
       * the same as logical order or layout order. Bit 30 is used to
       * indicate whether glyphs are processed forwards or backwards within
       * that order.

                Bit 30  Bit 28  Interpretation for Horizontal Text
                0       0       The subtable is processed in layout order
                                (the same order as the glyphs, which is
                                always left-to-right).
                1       0       The subtable is processed in reverse layout order
                                (the order opposite that of the glyphs, which is
                                always right-to-left).
                0       1       The subtable is processed in logical order
                                (the same order as the characters, which may be
                                left-to-right or right-to-left).
                1       1       The subtable is processed in reverse logical order
                                (the order opposite that of the characters, which
                                may be right-to-left or left-to-right).
       */
      reverse = coverage & ChainSubtable<Types>::Logical ?
                bool (coverage & ChainSubtable<Types>::Backwards) :
                bool (coverage & ChainSubtable<Types>::Backwards) !=
                HB_DIRECTION_IS_BACKWARD (c->buffer->props.direction);

      if (!c->buffer->message (c->font, "start chainsubtable %u", c->lookup_index))
        goto skip;

      if (reverse)
        c->buffer->reverse ();

      subtable->apply (c);

      if (reverse)
        c->buffer->reverse ();

      (void) c->buffer->message (c->font, "end chainsubtable %u", c->lookup_index);

      if (unlikely (!c->buffer->successful)) return;

    skip:
      subtable = &StructAfter<ChainSubtable<Types>> (*subtable);
      c->set_lookup_index (c->lookup_index + 1);
    }
  }

  unsigned int get_size () const { return length; }

  template <typename context_t, typename ...Ts>
  typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
  {
    const ChainSubtable<Types> *subtable = &StructAfter<ChainSubtable<Types>> (featureZ.as_array (featureCount));
    unsigned int count = subtableCount;
    for (unsigned int i = 0; i < count; i++)
    {
      typename context_t::return_t ret = subtable->dispatch (c, std::forward<Ts> (ds)...);
      if (c->stop_sublookup_iteration (ret))
        return ret;
      subtable = &StructAfter<ChainSubtable<Types>> (*subtable);
    }
    return c->default_return_value ();
  }

  bool sanitize (hb_sanitize_context_t *c, unsigned int version) const
  {
    TRACE_SANITIZE (this);
    if (!(length.sanitize (c) &&
          hb_barrier () &&
          length >= min_size &&
          c->check_range (this, length)))
      return_trace (false);

    if (!c->check_array (featureZ.arrayZ, featureCount))
      return_trace (false);

    const ChainSubtable<Types> *subtable = &StructAfter<ChainSubtable<Types>> (featureZ.as_array (featureCount));
    unsigned int count = subtableCount;
    for (unsigned int i = 0; i < count; i++)
    {
      if (!subtable->sanitize (c))
        return_trace (false);
      hb_barrier ();
      subtable = &StructAfter<ChainSubtable<Types>> (*subtable);
    }

    if (version >= 3)
    {
      const SubtableGlyphCoverage *coverage = (const SubtableGlyphCoverage *) subtable;
      if (!coverage->sanitize (c, count))
        return_trace (false);
    }

    return_trace (true);
  }

  protected:
  HBUINT32      defaultFlags;   /* The default specification for subtables. */
  HBUINT32      length;         /* Total byte count, including this header. */
  HBUINT        featureCount;   /* Number of feature subtable entries. */
  HBUINT        subtableCount;  /* The number of subtables in the chain. */

  UnsizedArrayOf<Feature>       featureZ;       /* Features. */
/*ChainSubtable firstSubtable;*//* Subtables. */
/*SubtableGlyphCoverage coverages*//* Only if version >= 3. */

  public:
  DEFINE_SIZE_MIN (8 + 2 * sizeof (HBUINT));
};


/*
 * The 'mort'/'morx' Table
 */

template <typename T, typename Types, hb_tag_t TAG>
struct mortmorx
{
  static constexpr hb_tag_t tableTag = TAG;

  bool has_data () const { return version != 0; }

  struct accelerator_t
  {
    accelerator_t (hb_face_t *face)
    {
      hb_sanitize_context_t sc;
      this->table = sc.reference_table<T> (face);

      if (unlikely (this->table->is_blocklisted (this->table.get_blob (), face)))
      {
        hb_blob_destroy (this->table.get_blob ());
        this->table = hb_blob_get_empty ();
      }

      this->chain_count = table->get_chain_count ();

      this->accels = (hb_atomic_t<hb_aat_layout_chain_accelerator_t *> *) hb_calloc (this->chain_count, sizeof (*accels));
      if (unlikely (!this->accels))
      {
        this->chain_count = 0;
        this->table.destroy ();
        this->table = hb_blob_get_empty ();
      }
    }
    ~accelerator_t ()
    {
      for (unsigned int i = 0; i < this->chain_count; i++)
      {
        if (this->accels[i])
          this->accels[i]->destroy ();
        hb_free (this->accels[i]);
      }
      hb_free (this->accels);
      this->table.destroy ();
    }

    hb_blob_t *get_blob () const { return table.get_blob (); }

    template <typename Chain>
    hb_aat_layout_chain_accelerator_t *get_accel (unsigned chain_index, const Chain &chain, unsigned num_glyphs) const
    {
      if (unlikely (chain_index >= chain_count)) return nullptr;

    retry:
      auto *accel = accels[chain_index].get_acquire ();
      if (unlikely (!accel))
      {
        accel = hb_aat_layout_chain_accelerator_t::create (chain, num_glyphs);
        if (unlikely (!accel))
          return nullptr;

        if (unlikely (!accels[chain_index].cmpexch (nullptr, accel)))
        {
          hb_free (accel);
          goto retry;
        }
      }

      return accel;
    }

    hb_blob_ptr_t<T> table;
    unsigned int chain_count;
    hb_atomic_t<hb_aat_layout_chain_accelerator_t *> *accels;
    hb_aat_scratch_t scratch;
  };


  void compile_flags (const hb_aat_map_builder_t *mapper,
                      hb_aat_map_t *map) const
  {
    const Chain<Types> *chain = &firstChain;
    unsigned int count = chainCount;
    if (unlikely (!map->chain_flags.resize (count)))
      return;
    for (unsigned int i = 0; i < count; i++)
    {
      map->chain_flags[i].push (hb_aat_map_t::range_flags_t {chain->compile_flags (mapper),
                                                             mapper->range_first,
                                                             mapper->range_last});
      chain = &StructAfter<Chain<Types>> (*chain);
    }
  }

  unsigned get_chain_count () const
  {
    return chainCount;
  }
  void apply (hb_aat_apply_context_t *c,
              const hb_aat_map_t &map,
              const accelerator_t &accel) const
  {
    if (unlikely (!c->buffer->successful)) return;

    c->buffer->unsafe_to_concat ();

    c->setup_buffer_glyph_set ();

    c->set_lookup_index (0);
    const Chain<Types> *chain = &firstChain;
    unsigned int count = chainCount;
    for (unsigned int i = 0; i < count; i++)
    {
      auto *chain_accel = accel.get_accel (i, *chain, c->face->get_num_glyphs ());
      c->range_flags = &map.chain_flags[i];
      chain->apply (c, chain_accel);
      if (unlikely (!c->buffer->successful)) return;
      chain = &StructAfter<Chain<Types>> (*chain);
    }
  }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    if (!(version.sanitize (c) &&
          hb_barrier () &&
          version &&
          chainCount.sanitize (c)))
      return_trace (false);

    const Chain<Types> *chain = &firstChain;
    unsigned int count = chainCount;
    for (unsigned int i = 0; i < count; i++)
    {
      if (!chain->sanitize (c, version))
        return_trace (false);
      hb_barrier ();
      chain = &StructAfter<Chain<Types>> (*chain);
    }

    return_trace (true);
  }

  protected:
  HBUINT16      version;        /* Version number of the glyph metamorphosis table.
                                 * 1, 2, or 3. */
  HBUINT16      unused;         /* Set to 0. */
  HBUINT32      chainCount;     /* Number of metamorphosis chains contained in this
                                 * table. */
  Chain<Types>  firstChain;     /* Chains. */

  public:
  DEFINE_SIZE_MIN (8);
};

struct morx : mortmorx<morx, ExtendedTypes, HB_AAT_TAG_morx>
{
  HB_INTERNAL bool is_blocklisted (hb_blob_t *blob,
                                   hb_face_t *face) const;
};

struct mort : mortmorx<mort, ObsoleteTypes, HB_AAT_TAG_mort>
{
  HB_INTERNAL bool is_blocklisted (hb_blob_t *blob,
                                   hb_face_t *face) const;
};

struct morx_accelerator_t : morx::accelerator_t {
  morx_accelerator_t (hb_face_t *face) : morx::accelerator_t (face) {}
};
struct mort_accelerator_t : mort::accelerator_t {
  mort_accelerator_t (hb_face_t *face) : mort::accelerator_t (face) {}
};


} /* namespace AAT */


#endif /* HB_AAT_LAYOUT_MORX_TABLE_HH */
