Added CFF support

Added sources hb-ot-cff-table.hh, hb-subset-cff.cc & hh
Templatized Index because CFF uses 16-bit count while CFF2 uses 32-bit
Misc code cleanup & bug fixes
diff --git a/src/hb-ot-cff-table.hh b/src/hb-ot-cff-table.hh
new file mode 100644
index 0000000..fb5f911
--- /dev/null
+++ b/src/hb-ot-cff-table.hh
@@ -0,0 +1,919 @@
+/*
+ * Copyright © 2018 Adobe Systems Incorporated.
+ *
+ *  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.
+ *
+ * Adobe Author(s): Michiharu Ariza
+ */
+
+#ifndef HB_OT_CFF_TABLE_HH
+#define HB_OT_CFF_TABLE_HH
+
+#include "hb-ot-cff-common-private.hh"
+#include "hb-subset-cff.hh"
+
+namespace CFF {
+
+/*
+ * CFF -- Compact Font Format (CFF)
+ * http://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf
+ */
+#define HB_OT_TAG_cff HB_TAG('C','F','F',' ')
+
+typedef Index<HBUINT16>   CFFIndex;
+template <typename Type> struct CFFIndexOf : IndexOf<HBUINT16, Type> {};
+
+typedef Index<HBUINT16>   CFFIndex;
+typedef CFFIndex          CFFCharStrings;
+typedef FDArray<HBUINT16> CFFFDArray;
+typedef CFFIndex          CFFSubrs;
+
+struct CFFFDSelect : FDSelect {};
+
+/* Encoding */
+struct Encoding0 {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && codes[nCodes - 1].sanitize (c));
+  }
+
+  inline bool get_code (hb_codepoint_t glyph, hb_codepoint_t &code) const
+  {
+    if (glyph < nCodes)
+    {
+      code = (hb_codepoint_t)codes[glyph];
+      return true;
+    }
+    else
+      return false;
+  }
+
+  inline unsigned int get_size (void) const
+  { return HBUINT8::static_size * (nCodes + 1); }
+
+  HBUINT8     nCodes;
+  HBUINT8     codes[VAR];
+
+  DEFINE_SIZE_ARRAY(1, codes);
+};
+
+struct Encoding1_Range {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  HBUINT8   first;
+  HBUINT8   nLeft;
+
+  DEFINE_SIZE_STATIC (2);
+};
+
+struct Encoding1 {
+  inline unsigned int get_size (void) const
+  { return HBUINT8::static_size + Encoding1_Range::static_size * nRanges; }
+
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && ((nRanges == 0) || (ranges[nRanges - 1]).sanitize (c)));
+  }
+
+  inline bool get_code (hb_codepoint_t glyph, hb_codepoint_t &code) const
+  {
+    for (unsigned int i = 0; i < nRanges; i++)
+    {
+      if (glyph <= ranges[i].nLeft)
+      {
+        code = (hb_codepoint_t)ranges[i].first + glyph;
+        return true;
+      }
+      glyph -= (ranges[i].nLeft + 1);
+    }
+  
+    return false;
+  }
+
+  HBUINT8           nRanges;
+  Encoding1_Range   ranges[VAR];
+
+  DEFINE_SIZE_ARRAY (1, ranges);
+};
+
+struct EncSupplement {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  HBUINT8   code;
+  HBUINT16  glyph;
+
+  DEFINE_SIZE_STATIC (3);
+};
+
+struct CFFSuppEncData {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && ((nSups == 0) || (supps[nSups - 1]).sanitize (c)));
+  }
+
+  inline bool get_code (hb_codepoint_t glyph, hb_codepoint_t &code) const
+  {
+    for (unsigned int i = 0; i < nSups; i++)
+      if (glyph == supps[i].glyph)
+      {
+        code = supps[i].code;
+        return true;
+      }
+  
+    return false;
+  }
+
+  inline unsigned int get_size (void) const
+  { return HBUINT8::static_size + EncSupplement::static_size * nSups; }
+
+  HBUINT8         nSups;
+  EncSupplement   supps[VAR];
+
+  DEFINE_SIZE_ARRAY (1, supps);
+};
+
+struct Encoding {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+
+    if (unlikely (!c->check_struct (this)))
+      return_trace (false);
+    unsigned int fmt = format & 0x7F;
+    if (unlikely (fmt > 1))
+      return_trace (false);
+    if (unlikely (!((fmt == 0)? u.format0.sanitize (c): u.format1.sanitize (c))))
+      return_trace (false);
+    return_trace (((format & 0x80) == 0) || suppEncData ().sanitize (c));
+  }
+
+  inline bool serialize (hb_serialize_context_t *c, const Encoding &src, unsigned int num_glyphs)
+  {
+    TRACE_SERIALIZE (this);
+    unsigned int size = src.get_size ();
+    Encoding *dest = c->allocate_size<Encoding> (size);
+    if (unlikely (dest == nullptr)) return_trace (false);
+    memcpy (dest, &src, size);
+    return_trace (true);
+  }
+
+  inline unsigned int calculate_serialized_size (void) const
+  { return get_size (); } // XXX: TODO
+
+  inline unsigned int get_size (void) const
+  {
+    unsigned int size = min_size;
+    if ((format & 0x7F) == 0)
+      size += u.format0.get_size ();
+    else
+      size += u.format1.get_size ();
+    if ((format & 0x80) != 0)
+      size += suppEncData ().get_size ();
+    return size;
+  }
+
+  inline bool get_code (hb_codepoint_t glyph, hb_codepoint_t &code) const
+  {
+    // XXX: TODO
+    return false;
+  }
+
+  inline const CFFSuppEncData &suppEncData (void) const
+  {
+    if ((format & 0x7F) == 0)
+      return StructAfter<CFFSuppEncData> (u.format0.codes[u.format0.nCodes-1]);
+    else
+      return StructAfter<CFFSuppEncData> (u.format1.ranges[u.format1.nRanges-1]);
+  }
+
+  HBUINT8       format;
+  union {
+    Encoding0   format0;
+    Encoding1   format1;
+  } u;
+  /* CFFSuppEncData  suppEncData; */
+
+  DEFINE_SIZE_MIN (1);
+};
+
+/* Charset */
+struct Charset0 {
+  inline bool sanitize (hb_sanitize_context_t *c, unsigned int num_glyphs) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && sids[num_glyphs - 1].sanitize (c));
+  }
+
+  inline unsigned int get_sid (hb_codepoint_t glyph)
+  {
+    if (glyph == 0)
+      return 0;
+    else
+      return sids[glyph - 1];
+  }
+
+  inline unsigned int get_size (unsigned int num_glyphs) const
+  {
+    assert (num_glyphs > 0);
+    return HBUINT16::static_size * (num_glyphs - 1);
+  }
+
+  HBUINT16  sids[VAR];
+
+  DEFINE_SIZE_ARRAY(0, sids);
+};
+
+template <typename TYPE>
+struct Charset_Range {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  HBUINT8   first;
+  TYPE      nLeft;
+
+  DEFINE_SIZE_STATIC (1 + TYPE::static_size);
+};
+
+template <typename TYPE>
+struct Charset1_2 {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && ((nRanges == 0) || (ranges[nRanges - 1]).sanitize (c)));
+  }
+
+  inline bool get_sid (hb_codepoint_t glyph, unsigned int &sid) const
+  {
+    for (unsigned int i = 0; i < nRanges; i++)
+    {
+      if (glyph <= ranges[i].nLeft)
+      {
+        sid = (hb_codepoint_t)ranges[i].first + glyph;
+        return true;
+      }
+      glyph -= (ranges[i].nLeft + 1);
+    }
+  
+    return false;
+  }
+
+  inline unsigned int get_size (void) const
+  { return HBUINT8::static_size + Charset_Range<TYPE>::static_size * nRanges; }
+
+  HBUINT8               nRanges;
+  Charset_Range<TYPE>   ranges[VAR];
+
+  DEFINE_SIZE_ARRAY (1, ranges);
+};
+
+struct Charset {
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+
+    if (unlikely (!c->check_struct (this)))
+      return_trace (false);
+    if (format == 0)
+      return_trace (u.format0.sanitize (c, c->get_num_glyphs ()));
+    else if (format == 1)
+      return_trace (u.format1.sanitize (c));
+    else if (likely (format == 2))
+      return_trace (u.format2.sanitize (c));
+    else
+      return_trace (false);
+  }
+
+  inline bool serialize (hb_serialize_context_t *c, const Charset &src, unsigned int num_glyphs)
+  {
+    TRACE_SERIALIZE (this);
+    unsigned int size = src.get_size (num_glyphs);
+    Charset *dest = c->allocate_size<Charset> (size);
+    if (unlikely (dest == nullptr)) return_trace (false);
+    memcpy (dest, &src, size);
+    return_trace (true);
+  }
+
+  inline unsigned int calculate_serialized_size (unsigned int num_glyphs) const
+  { return get_size (num_glyphs); } // XXX: TODO
+
+  inline unsigned int get_size (unsigned int num_glyphs) const
+  {
+    unsigned int size = min_size;
+    if (format == 0)
+      size += u.format0.get_size (num_glyphs);
+    else if (format == 1)
+      size += u.format1.get_size ();
+    else
+      size += u.format2.get_size ();
+    return size;
+  }
+
+  inline bool get_sid (hb_codepoint_t glyph, unsigned int &sid) const
+  {
+    // XXX: TODO
+    return false;
+  }
+
+  HBUINT8       format;
+  union {
+    Charset0              format0;
+    Charset1_2<HBUINT8>   format1;
+    Charset1_2<HBUINT16>  format2;
+  } u;
+
+  DEFINE_SIZE_MIN (1);
+};
+
+struct CFFTopDictValues : TopDictValues
+{
+  inline void init (void)
+  {
+    TopDictValues::init ();
+
+    ros[0] = ros[1] = ros[2] = 0;
+    cidCount = 8720;
+    EncodingOffset = 0;
+    CharsetOffset = 0;
+    FDSelectOffset = 0;
+    privateDictInfo.init ();
+  }
+
+  inline void fini (void)
+  {
+    TopDictValues::fini ();
+  }
+
+  inline bool is_CID (void) const
+  { return ros[0] != 0; }
+
+  inline unsigned int calculate_serialized_size (void) const
+  {
+    unsigned int size = 0;
+    for (unsigned int i = 0; i < values.len; i++)
+    {
+      OpCode op = values[i].op;
+      switch (op)
+      {
+        case OpCode_FDSelect:
+          size += OpCode_Size (OpCode_longint) + 4 + OpCode_Size (op);
+          break;
+        default:
+          size += TopDictValues::calculate_serialized_op_size (values[i]);
+          break;
+      }
+    }
+    return size;
+  }
+
+  unsigned int              ros[3]; /* registry, ordering, supplement */
+  unsigned int              cidCount;
+
+  enum EncodingID { StandardEncoding = 0, ExpertEncoding = 1 };
+  unsigned int  EncodingOffset;
+  unsigned int  CharsetOffset;
+  unsigned int  FDSelectOffset;
+  TableInfo     privateDictInfo;
+};
+
+struct CFFTopDictOpSet : TopDictOpSet
+{
+  static inline bool process_op (const ByteStr& str, unsigned int& offset,
+                                 OpCode op, Stack& stack, CFFTopDictValues& dictval)
+  {
+  
+    switch (op) {
+      case OpCode_version:
+      case OpCode_Notice:
+      case OpCode_Copyright:
+      case OpCode_FullName:
+      case OpCode_FamilyName:
+      case OpCode_Weight:
+      case OpCode_isFixedPitch:
+      case OpCode_ItalicAngle:
+      case OpCode_UnderlinePosition:
+      case OpCode_UnderlineThickness:
+      case OpCode_PaintType:
+      case OpCode_CharstringType:
+      case OpCode_UniqueID:
+      case OpCode_StrokeWidth:
+      case OpCode_SyntheticBase:
+      case OpCode_PostScript:
+      case OpCode_BaseFontName:
+      case OpCode_CIDFontVersion:
+      case OpCode_CIDFontRevision:
+      case OpCode_CIDFontType:
+      case OpCode_UIDBase:
+      case OpCode_FontBBox:
+      case OpCode_XUID:
+      case OpCode_BaseFontBlend:
+        stack.clear ();
+        break;
+        
+      case OpCode_CIDCount:
+        if (unlikely (!stack.check_pop_uint (dictval.cidCount)))
+          return false;
+        stack.clear ();
+        break;
+
+      case OpCode_ROS:
+        if (unlikely (!stack.check_pop_uint (dictval.ros[2]) ||
+                      !stack.check_pop_uint (dictval.ros[1]) ||
+                      !stack.check_pop_uint (dictval.ros[0])))
+          return false;
+        stack.clear ();
+        break;
+
+      case OpCode_Encoding:
+        if (unlikely (!stack.check_pop_uint (dictval.EncodingOffset)))
+          return false;
+        stack.clear ();
+        break;
+
+      case OpCode_charset:
+        if (unlikely (!stack.check_pop_uint (dictval.CharsetOffset)))
+          return false;
+        stack.clear ();
+        break;
+
+      case OpCode_FDSelect:
+        if (unlikely (!stack.check_pop_uint (dictval.FDSelectOffset)))
+          return false;
+        stack.clear ();
+        break;
+    
+      case OpCode_Private:
+        if (unlikely (!stack.check_pop_uint (dictval.privateDictInfo.offset)))
+          return false;
+        if (unlikely (!stack.check_pop_uint (dictval.privateDictInfo.size)))
+          return false;
+        stack.clear ();
+        break;
+    
+      default:
+        if (unlikely (!TopDictOpSet::process_op (str, offset, op, stack, dictval)))
+          return false;
+        /* Record this operand below if stack is empty, otherwise done */
+        if (!stack.is_empty ()) return true;
+        break;
+    }
+
+    dictval.pushVal (op, str, offset + 1);
+    return true;
+  }
+};
+
+struct CFFFontDictValues : DictValues<OpStr>
+{
+  inline void init (void)
+  {
+    DictValues<OpStr>::init ();
+    privateDictInfo.init ();
+  }
+
+  inline void fini (void)
+  {
+    DictValues<OpStr>::fini ();
+  }
+
+  TableInfo   privateDictInfo;
+};
+
+struct CFFFontDictOpSet
+{
+  static inline bool process_op (const ByteStr& str, unsigned int& offset,
+                                 OpCode op, Stack& stack, CFFFontDictValues& dictval)
+  {
+    switch (op) {
+      case OpCode_FontName:
+      case OpCode_FontMatrix:
+      case OpCode_PaintType:
+        stack.clear ();
+        break;
+      case OpCode_Private:
+        if (unlikely (!stack.check_pop_uint (dictval.privateDictInfo.offset)))
+          return false;
+        if (unlikely (!stack.check_pop_uint (dictval.privateDictInfo.size)))
+          return false;
+        stack.clear ();
+        break;
+      case OpCode_longint:  /* 5-byte integer */
+        return stack.push_longint_from_str (str, offset);
+      case OpCode_BCD:  /* real number */
+        float v;
+        if (unlikely (stack.check_overflow (1) || !parse_bcd (str, offset, v)))
+          return false;
+        stack.push_real (v);
+        return true;
+    
+      default:
+        /* XXX: invalid */
+        stack.clear ();
+        return false;
+    }
+
+    dictval.pushVal (op, str, offset + 1);
+    return true;
+  }
+};
+
+template <typename VAL>
+struct CFFPrivateDictValues_Base : DictValues<VAL>
+{
+  inline void init (void)
+  {
+    DictValues<VAL>::init ();
+    subrsOffset = 0;
+    localSubrs = &Null(CFFSubrs);
+  }
+
+  inline void fini (void)
+  {
+    DictValues<VAL>::fini ();
+  }
+
+  inline unsigned int calculate_serialized_size (void) const
+  {
+    unsigned int size = 0;
+    for (unsigned int i = 0; i < DictValues<VAL>::values.len; i++)
+      if (DictValues<VAL>::values[i].op == OpCode_Subrs)
+        size += OpCode_Size (OpCode_shortint) + 2 + OpCode_Size (OpCode_Subrs);
+      else
+        size += DictValues<VAL>::values[i].str.len;
+    return size;
+  }
+
+  unsigned int      subrsOffset;
+  const CFFSubrs    *localSubrs;
+};
+
+typedef CFFPrivateDictValues_Base<OpStr> CFFPrivateDictValues_Subset;
+typedef CFFPrivateDictValues_Base<DictVal> CFFPrivateDictValues;
+
+struct CFFPrivateDictOpSet
+{
+  static inline bool process_op (const ByteStr& str, unsigned int& offset,
+                                 OpCode op, Stack& stack, CFFPrivateDictValues& dictval)
+  {
+    DictVal val;
+    val.init ();
+
+    switch (op) {
+      case OpCode_BlueValues:
+      case OpCode_OtherBlues:
+      case OpCode_FamilyBlues:
+      case OpCode_FamilyOtherBlues:
+      case OpCode_StemSnapH:
+      case OpCode_StemSnapV:
+        if (unlikely (!stack.check_pop_delta (val.multi_val)))
+          return false;
+        break;
+      case OpCode_StdHW:
+      case OpCode_StdVW:
+      case OpCode_BlueScale:
+      case OpCode_BlueShift:
+      case OpCode_BlueFuzz:
+      case OpCode_ForceBold:
+      case OpCode_LanguageGroup:
+      case OpCode_ExpansionFactor:
+      case OpCode_initialRandomSeed:
+      case OpCode_defaultWidthX:
+      case OpCode_nominalWidthX:
+        if (unlikely (!stack.check_pop_num (val.single_val)))
+          return false;
+        stack.clear ();
+        break;
+      case OpCode_Subrs:
+        if (unlikely (!stack.check_pop_uint (dictval.subrsOffset)))
+          return false;
+        stack.clear ();
+        break;
+      case OpCode_longint:  /* 5-byte integer */
+        return stack.push_longint_from_str (str, offset);
+      case OpCode_BCD:  /* real number */
+        float v;
+        if (unlikely (!stack.check_overflow (1) || !parse_bcd (str, offset, v)))
+          return false;
+        stack.push_real (v);
+        return true;
+
+      default:
+        return false;
+    }
+
+    dictval.pushVal (op, str, offset + 1, val);
+    return true;
+  }
+};
+
+struct CFFPrivateDictOpSet_Subset
+{
+  static inline bool process_op (const ByteStr& str, unsigned int& offset,
+                                 OpCode op, Stack& stack, CFFPrivateDictValues_Subset& dictval)
+  {
+    switch (op) {
+      case OpCode_BlueValues:
+      case OpCode_OtherBlues:
+      case OpCode_FamilyBlues:
+      case OpCode_FamilyOtherBlues:
+      case OpCode_StemSnapH:
+      case OpCode_StemSnapV:
+      case OpCode_StdHW:
+      case OpCode_StdVW:
+      case OpCode_BlueScale:
+      case OpCode_BlueShift:
+      case OpCode_BlueFuzz:
+      case OpCode_ForceBold:
+      case OpCode_LanguageGroup:
+      case OpCode_ExpansionFactor:
+      case OpCode_initialRandomSeed:
+      case OpCode_defaultWidthX:
+      case OpCode_nominalWidthX:
+        stack.clear ();
+        break;
+
+      case OpCode_BCD:
+        {
+          float v;
+          return parse_bcd (str, offset, v);
+        }
+
+      case OpCode_Subrs:
+        if (unlikely (!stack.check_pop_uint (dictval.subrsOffset)))
+          return false;
+        stack.clear ();
+        break;
+      case OpCode_longint:  /* 5-byte integer */
+        return stack.push_longint_from_str (str, offset);
+
+      default:
+        return false;
+    }
+
+    dictval.pushVal (op, str, offset + 1);
+    return true;
+  }
+};
+
+typedef Interpreter<CFFTopDictOpSet, CFFTopDictValues> CFFTopDict_Interpreter;
+typedef Interpreter<CFFFontDictOpSet, CFFFontDictValues> CFFFontDict_Interpreter;
+typedef Interpreter<CFFPrivateDictOpSet, CFFPrivateDictValues> CFFPrivateDict_Interpreter;
+
+typedef CFFIndex NameIndex;
+typedef CFFIndexOf<TopDict> TopDictIndex;
+typedef CFFIndex StringIndex;
+
+}; /* namespace CFF */
+
+namespace OT {
+
+using namespace CFF;
+
+struct cff
+{
+  static const hb_tag_t tableTag        = HB_OT_TAG_cff;
+
+  inline bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) &&
+                  likely (version.major == 1));
+  }
+
+  template <typename PrivOpSet, typename PrivDictVal>
+  struct accelerator_templ_t
+  {
+    inline void init (hb_face_t *face)
+    {
+      topDicts.init ();
+      topDicts.resize (1);
+      topDicts[0].init ();
+      fontDicts.init ();
+      privateDicts.init ();
+      
+      this->blob = sc.reference_table<cff> (face);
+
+      /* setup for run-time santization */
+      sc.init (this->blob);
+      sc.start_processing ();
+      
+      const OT::cff *cff = this->blob->template as<OT::cff> ();
+
+      if (cff == &Null(OT::cff))
+      { fini (); return; }
+
+      nameIndex = &cff->nameIndex (cff);
+      if ((nameIndex == &Null (NameIndex)) || !nameIndex->sanitize (&sc))
+      { fini (); return; }
+
+      topDictIndex = &StructAtOffset<TopDictIndex> (nameIndex, nameIndex->get_size ());
+      if ((topDictIndex == &Null (TopDictIndex)) || !topDictIndex->sanitize (&sc) || (topDictIndex->count == 0))
+      { fini (); return; }
+
+      { /* parse top dict */
+        const ByteStr topDictStr = (*topDictIndex)[0];
+        CFFTopDict_Interpreter top_interp;
+        if (unlikely (!topDictStr.sanitize (&sc) ||
+                      !top_interp.interpret (topDictStr, topDicts[0])))
+        { fini (); return; }
+      }
+      
+      encoding = &Null(Encoding);
+      charset = &StructAtOffsetOrNull<Charset> (cff, topDicts[0].CharsetOffset);
+
+      fdCount = 1;
+      if (is_CID ())
+      {
+        fdArray = &StructAtOffsetOrNull<CFFFDArray> (cff, topDicts[0].FDArrayOffset);
+        fdSelect = &StructAtOffsetOrNull<CFFFDSelect> (cff, topDicts[0].FDSelectOffset);
+        if (unlikely ((fdArray == &Null(CFFFDArray)) || !fdArray->sanitize (&sc) ||
+            (fdSelect == &Null(CFFFDSelect)) || !fdSelect->sanitize (&sc, fdArray->count)))
+        { fini (); return; }
+
+        fdCount = fdArray->count;
+      }
+      else
+      {
+        fdArray = &Null(CFFFDArray);
+        fdSelect = &Null(CFFFDSelect);
+        if (topDicts[0].EncodingOffset != CFFTopDictValues::StandardEncoding &&
+            topDicts[0].EncodingOffset != CFFTopDictValues::ExpertEncoding)
+        {
+          encoding = &StructAtOffsetOrNull<Encoding> (cff, topDicts[0].EncodingOffset);
+          if ((encoding == &Null (Encoding)) || !encoding->sanitize (&sc))
+          { fini (); return; }
+        }
+      }
+
+      stringIndex = &StructAtOffset<StringIndex> (topDictIndex, topDictIndex->get_size ());
+      if ((stringIndex == &Null (StringIndex)) || !stringIndex->sanitize (&sc))
+      { fini (); return; }
+
+      globalSubrs = &StructAtOffset<CFFSubrs> (stringIndex, stringIndex->get_size ());
+      if ((globalSubrs != &Null (CFFSubrs)) && !stringIndex->sanitize (&sc))
+      { fini (); return; }
+
+      charStrings = &StructAtOffsetOrNull<CFFCharStrings> (cff, topDicts[0].charStringsOffset);
+
+      if ((charStrings == &Null(CFFCharStrings)) || unlikely (!charStrings->sanitize (&sc)))
+      { fini (); return; }
+
+      num_glyphs = charStrings->count;
+      if (num_glyphs != sc.get_num_glyphs ())
+      { fini (); return; }
+
+      privateDicts.resize (fdCount);
+      for (unsigned int i = 0; i < fdCount; i++)
+        privateDicts[i].init ();
+
+      // parse CID font dicts and gather private dicts
+      if (is_CID ())
+      {
+        for (unsigned int i = 0; i < fdCount; i++)
+        {
+          ByteStr fontDictStr = (*fdArray)[i];
+          CFFFontDictValues  *font;
+          CFFFontDict_Interpreter font_interp;
+          font = fontDicts.push ();
+          if (unlikely (!fontDictStr.sanitize (&sc) ||
+                        !font_interp.interpret (fontDictStr, *font)))
+          { fini (); return; }
+          PrivDictVal  *priv = &privateDicts[i];
+          const ByteStr privDictStr (StructAtOffset<UnsizedByteStr> (cff, font->privateDictInfo.offset), font->privateDictInfo.size);
+          Interpreter<PrivOpSet, PrivDictVal> priv_interp;
+          if (unlikely (!privDictStr.sanitize (&sc) ||
+                        !priv_interp.interpret (privDictStr, *priv)))
+          { fini (); return; }
+
+          priv->localSubrs = &StructAtOffsetOrNull<CFFSubrs> (privDictStr.str, priv->subrsOffset);
+          if (priv->localSubrs != &Null(CFFSubrs) &&
+              unlikely (!priv->localSubrs->sanitize (&sc)))
+          { fini (); return; }
+        }
+      }
+      else  /* non-CID */
+      {
+        CFFTopDictValues  *font = &topDicts[0];
+        PrivDictVal  *priv = &privateDicts[0];
+        
+        const ByteStr privDictStr (StructAtOffset<UnsizedByteStr> (cff, font->privateDictInfo.offset), font->privateDictInfo.size);
+        Interpreter<PrivOpSet, PrivDictVal> priv_interp;
+        if (unlikely (!privDictStr.sanitize (&sc) ||
+                      !priv_interp.interpret (privDictStr, *priv)))
+        { fini (); return; }
+
+        priv->localSubrs = &StructAtOffsetOrNull<CFFSubrs> (privDictStr.str, priv->subrsOffset);
+        if (priv->localSubrs != &Null(CFFSubrs) &&
+            unlikely (!priv->localSubrs->sanitize (&sc)))
+        { fini (); return; }
+      }
+    }
+
+    inline void fini (void)
+    {
+      sc.end_processing ();
+      topDicts[0].fini ();
+      topDicts.fini ();
+      fontDicts.fini ();
+      privateDicts.fini ();
+      hb_blob_destroy (blob);
+      blob = nullptr;
+    }
+
+    inline bool is_valid (void) const { return blob != nullptr; }
+    inline bool is_CID (void) const { return topDicts[0].is_CID (); }
+
+    inline bool get_extents (hb_codepoint_t glyph,
+           hb_glyph_extents_t *extents) const
+    {
+      // XXX: TODO
+      if (glyph >= num_glyphs)
+        return false;
+      
+      return true;
+    }
+
+    protected:
+    hb_blob_t               *blob;
+    hb_sanitize_context_t   sc;
+
+    public:
+    const NameIndex         *nameIndex;
+    const TopDictIndex      *topDictIndex;
+    const StringIndex       *stringIndex;
+    const Encoding          *encoding;
+    const Charset           *charset;
+    const CFFSubrs          *globalSubrs;
+    const CFFCharStrings    *charStrings;
+    const CFFFDArray        *fdArray;
+    const CFFFDSelect       *fdSelect;
+    unsigned int            fdCount;
+
+    hb_vector_t<CFFTopDictValues>     topDicts;
+    hb_vector_t<CFFFontDictValues>    fontDicts;
+    hb_vector_t<PrivDictVal>          privateDicts;
+
+    unsigned int            num_glyphs;
+  };
+
+  typedef accelerator_templ_t<CFFPrivateDictOpSet, CFFPrivateDictValues> accelerator_t;
+  typedef accelerator_templ_t<CFFPrivateDictOpSet_Subset, CFFPrivateDictValues_Subset> accelerator_subset_t;
+
+  inline bool subset (hb_subset_plan_t *plan) const
+  {
+    hb_blob_t *cff_prime = nullptr;
+
+    bool success = true;
+    if (hb_subset_cff (plan, &cff_prime)) {
+      success = success && plan->add_table (HB_OT_TAG_cff, cff_prime);
+    } else {
+      success = false;
+    }
+    hb_blob_destroy (cff_prime);
+
+    return success;
+  }
+
+  public:
+  FixedVersion<HBUINT8> version;          /* Version of CFF table. set to 0x0100u */
+  OffsetTo<NameIndex, HBUINT8> nameIndex; /* headerSize = Offset to Name INDEX. */
+  HBUINT8               offSize;          /* offset size (unused?) */
+
+  public:
+  DEFINE_SIZE_STATIC (4);
+};
+
+} /* namespace OT */
+
+#endif /* HB_OT_CFF_TABLE_HH */