diff --git a/gfx/ots/LICENSE b/gfx/ots/LICENSE
index a7531cf7c..d5e3bff5a 100644
--- a/gfx/ots/LICENSE
+++ b/gfx/ots/LICENSE
@@ -1,27 +1,27 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//    * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//    * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//    * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
index b96826735..4227930d5 100644
--- a/gfx/ots/README.mozilla
+++ b/gfx/ots/README.mozilla
@@ -2,10 +2,11 @@ This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
 
 Our reference repository is https://github.com/khaledhosny/ots/.
 
-Current revision: ba8417620956a920ed1f05a2f666fb6317fb10cb
+Current revision: f87b4556191e4132ef5c47365762eb88ace97fc3 (6.0.0)
 
-Upstream files included: LICENSE, src/, include/
+Upstream files included: LICENSE, src/, include/, tests/*.cc
 
 Additional files: README.mozilla, src/moz.build
 
 Additional patch: ots-visibility.patch (bug 711079).
+Additional patch: ots-lz4.patch
diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-sanitiser.h
index 87d0f3d28..69a0c2251 100644
--- a/gfx/ots/include/opentype-sanitiser.h
+++ b/gfx/ots/include/opentype-sanitiser.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -52,7 +52,7 @@ typedef unsigned __int64 uint64_t;
 #include <cstring>
 
 #define OTS_TAG(c1,c2,c3,c4) ((uint32_t)((((uint8_t)(c1))<<24)|(((uint8_t)(c2))<<16)|(((uint8_t)(c3))<<8)|((uint8_t)(c4))))
-#define OTS_UNTAG(tag)       ((uint8_t)((tag)>>24)), ((uint8_t)((tag)>>16)), ((uint8_t)((tag)>>8)), ((uint8_t)(tag))
+#define OTS_UNTAG(tag)       ((char)((tag)>>24)), ((char)((tag)>>16)), ((char)((tag)>>8)), ((char)(tag))
 
 namespace ots {
 
@@ -176,7 +176,7 @@ class OTSStream {
 
 enum TableAction {
   TABLE_ACTION_DEFAULT,  // Use OTS's default action for that table
-  TABLE_ACTION_SANITIZE, // Sanitize the table, potentially droping it
+  TABLE_ACTION_SANITIZE, // Sanitize the table, potentially dropping it
   TABLE_ACTION_PASSTHRU, // Serialize the table unchanged
   TABLE_ACTION_DROP      // Drop the table
 };
@@ -186,7 +186,7 @@ class OTS_API OTSContext {
     OTSContext() {}
     virtual ~OTSContext() {}
 
-    // Process a given OpenType file and write out a sanitised version
+    // Process a given OpenType file and write out a sanitized version
     //   output: a pointer to an object implementing the OTSStream interface. The
     //     sanitisied output will be written to this. In the even of a failure,
     //     partial output may have been written.
diff --git a/gfx/ots/include/ots-memory-stream.h b/gfx/ots/include/ots-memory-stream.h
index 579da616f..cd5b089fa 100644
--- a/gfx/ots/include/ots-memory-stream.h
+++ b/gfx/ots/include/ots-memory-stream.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/gfx/ots/ots-lz4.patch b/gfx/ots/ots-lz4.patch
new file mode 100644
index 000000000..a3f8fa210
--- /dev/null
+++ b/gfx/ots/ots-lz4.patch
@@ -0,0 +1,66 @@
+diff --git a/gfx/ots/src/glat.cc b/gfx/ots/src/glat.cc
+--- a/gfx/ots/src/glat.cc
++++ b/gfx/ots/src/glat.cc
+@@ -5,7 +5,7 @@
+ #include "glat.h"
+ 
+ #include "gloc.h"
+-#include "lz4.h"
++#include "mozilla/Compression.h"
+ #include <list>
+ 
+ namespace ots {
+@@ -201,14 +201,15 @@ bool OpenTypeGLAT_v3::Parse(const uint8_t* data, size_t length,
+         return DropGraphite("Illegal nested compression");
+       }
+       std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
+-      int ret = LZ4_decompress_safe_partial(
++      size_t outputSize = 0;
++      bool ret = mozilla::Compression::LZ4::decompressPartial(
+           reinterpret_cast<const char*>(data + table.offset()),
+-          reinterpret_cast<char*>(decompressed.data()),
+           table.remaining(),  // input buffer size (input size + padding)
++          reinterpret_cast<char*>(decompressed.data()),
+           decompressed.size(),  // target output size
+-          decompressed.size());  // output buffer size
+-      if (ret != decompressed.size()) {
+-        return DropGraphite("Decompression failed with error code %d", ret);
++          &outputSize);  // return output size
++      if (!ret || outputSize != decompressed.size()) {
++        return DropGraphite("Decompression failed");
+       }
+       return this->Parse(decompressed.data(), decompressed.size(), true);
+     }
+diff --git a/gfx/ots/src/silf.cc b/gfx/ots/src/silf.cc
+--- a/gfx/ots/src/silf.cc
++++ b/gfx/ots/src/silf.cc
+@@ -5,7 +5,7 @@
+ #include "silf.h"
+ 
+ #include "name.h"
+-#include "lz4.h"
++#include "mozilla/Compression.h"
+ #include <cmath>
+ 
+ namespace ots {
+@@ -39,14 +39,15 @@ bool OpenTypeSILF::Parse(const uint8_t* data, size_t length,
+           return DropGraphite("Illegal nested compression");
+         }
+         std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
+-        int ret = LZ4_decompress_safe_partial(
++        size_t outputSize = 0;
++        bool ret = mozilla::Compression::LZ4::decompressPartial(
+             reinterpret_cast<const char*>(data + table.offset()),
+-            reinterpret_cast<char*>(decompressed.data()),
+             table.remaining(),  // input buffer size (input size + padding)
++            reinterpret_cast<char*>(decompressed.data()),
+             decompressed.size(),  // target output size
+-            decompressed.size());  // output buffer size
+-        if (ret != decompressed.size()) {
+-          return DropGraphite("Decompression failed with error code %d", ret);
++            &outputSize);   // return output size
++        if (!ret || outputSize != decompressed.size()) {
++          return DropGraphite("Decompression failed");
+         }
+         return this->Parse(decompressed.data(), decompressed.size(), true);
+       }
diff --git a/gfx/ots/ots-visibility.patch b/gfx/ots/ots-visibility.patch
index aeac81c4d..2bf5033cb 100644
--- a/gfx/ots/ots-visibility.patch
+++ b/gfx/ots/ots-visibility.patch
@@ -1,11 +1,7 @@
 diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-sanitiser.h
 --- a/gfx/ots/include/opentype-sanitiser.h
 +++ b/gfx/ots/include/opentype-sanitiser.h
-@@ -1,15 +1,35 @@
- // Copyright (c) 2009 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- 
+@@ -5,6 +5,26 @@
  #ifndef OPENTYPE_SANITISER_H_
  #define OPENTYPE_SANITISER_H_
  
@@ -32,17 +28,7 @@ diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-san
  #if defined(_WIN32)
  #include <stdlib.h>
  typedef signed char int8_t;
- typedef unsigned char uint8_t;
- typedef short int16_t;
- typedef unsigned short uint16_t;
- typedef int int32_t;
- typedef unsigned int uint32_t;
-@@ -187,17 +207,17 @@ class OTSStream {
- 
- enum TableAction {
-   TABLE_ACTION_DEFAULT,  // Use OTS's default action for that table
-   TABLE_ACTION_SANITIZE, // Sanitize the table, potentially droping it
-   TABLE_ACTION_PASSTHRU, // Serialize the table unchanged
+@@ -161,7 +181,7 @@ enum TableAction {
    TABLE_ACTION_DROP      // Drop the table
  };
  
@@ -51,8 +37,3 @@ diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-san
    public:
      OTSContext() {}
      virtual ~OTSContext() {}
- 
-     // Process a given OpenType file and write out a sanitised version
-     //   output: a pointer to an object implementing the OTSStream interface. The
-     //     sanitisied output will be written to this. In the even of a failure,
-     //     partial output may have been written.
diff --git a/gfx/ots/src/cff.cc b/gfx/ots/src/cff.cc
index 23b6dadac..57868e363 100644
--- a/gfx/ots/src/cff.cc
+++ b/gfx/ots/src/cff.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -904,14 +904,13 @@ bool ParseDictData(const uint8_t *data, size_t table_length,
 
 namespace ots {
 
-bool ots_cff_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeCFF::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  font->cff = new OpenTypeCFF;
-  font->cff->data = data;
-  font->cff->length = length;
-  font->cff->font_dict_length = 0;
-  font->cff->local_subrs = NULL;
+  Font *font = GetFont();
+
+  this->m_data = data;
+  this->m_length = length;
 
   // parse "6. Header" in the Adobe Compact Font Format Specification
   uint8_t major = 0;
@@ -949,7 +948,7 @@ bool ots_cff_parse(Font *font, const uint8_t *data, size_t length) {
   if (!ParseIndex(&table, &name_index)) {
     return OTS_FAILURE();
   }
-  if (!ParseNameData(&table, name_index, &(font->cff->name))) {
+  if (!ParseNameData(&table, name_index, &(this->name))) {
     return OTS_FAILURE();
   }
 
@@ -973,14 +972,19 @@ bool ots_cff_parse(Font *font, const uint8_t *data, size_t length) {
     return OTS_FAILURE();
   }
 
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+    GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return Error("Required maxp table missing");
+  }
+  const uint16_t num_glyphs = maxp->num_glyphs;
   const size_t sid_max = string_index.count + kNStdString;
   // string_index.count == 0 is allowed.
 
   // parse "9. Top DICT Data"
   if (!ParseDictData(data, length, top_dict_index,
                      num_glyphs, sid_max,
-                     DICT_DATA_TOPLEVEL, font->cff)) {
+                     DICT_DATA_TOPLEVEL, this)) {
     return OTS_FAILURE();
   }
 
@@ -993,58 +997,44 @@ bool ots_cff_parse(Font *font, const uint8_t *data, size_t length) {
 
   // Check if all fd_index in FDSelect are valid.
   std::map<uint16_t, uint8_t>::const_iterator iter;
-  std::map<uint16_t, uint8_t>::const_iterator end = font->cff->fd_select.end();
-  for (iter = font->cff->fd_select.begin(); iter != end; ++iter) {
-    if (iter->second >= font->cff->font_dict_length) {
+  std::map<uint16_t, uint8_t>::const_iterator end = this->fd_select.end();
+  for (iter = this->fd_select.begin(); iter != end; ++iter) {
+    if (iter->second >= this->font_dict_length) {
       return OTS_FAILURE();
     }
   }
 
   // Check if all charstrings (font hinting code for each glyph) are valid.
-  for (size_t i = 0; i < font->cff->char_strings_array.size(); ++i) {
+  for (size_t i = 0; i < this->char_strings_array.size(); ++i) {
     if (!ValidateType2CharStringIndex(font,
-                                      *(font->cff->char_strings_array.at(i)),
+                                      *(this->char_strings_array.at(i)),
                                       global_subrs_index,
-                                      font->cff->fd_select,
-                                      font->cff->local_subrs_per_font,
-                                      font->cff->local_subrs,
+                                      this->fd_select,
+                                      this->local_subrs_per_font,
+                                      this->local_subrs,
                                       &table)) {
-      return OTS_FAILURE_MSG("Failed validating charstring set %d", (int) i);
+      return Error("Failed validating charstring set %d", (int) i);
     }
   }
 
   return true;
 }
 
-bool ots_cff_should_serialise(Font *font) {
-  return font->cff != NULL;
-}
-
-bool ots_cff_serialise(OTSStream *out, Font *font) {
-  // TODO(yusukes): would be better to transcode the data,
-  //                rather than simple memcpy.
-  if (!out->Write(font->cff->data, font->cff->length)) {
-    return OTS_FAILURE();
+bool OpenTypeCFF::Serialize(OTSStream *out) {
+  if (!out->Write(this->m_data, this->m_length)) {
+    return Error("Failed to write table");
   }
   return true;
 }
 
-void ots_cff_reuse(Font *font, Font *other) {
-  font->cff = other->cff;
-  font->cff_reused = true;
-}
-
-void ots_cff_free(Font *font) {
-  if (font->cff) {
-    for (size_t i = 0; i < font->cff->char_strings_array.size(); ++i) {
-      delete (font->cff->char_strings_array)[i];
-    }
-    for (size_t i = 0; i < font->cff->local_subrs_per_font.size(); ++i) {
-      delete (font->cff->local_subrs_per_font)[i];
-    }
-    delete font->cff->local_subrs;
-    delete font->cff;
+OpenTypeCFF::~OpenTypeCFF() {
+  for (size_t i = 0; i < this->char_strings_array.size(); ++i) {
+    delete (this->char_strings_array)[i];
+  }
+  for (size_t i = 0; i < this->local_subrs_per_font.size(); ++i) {
+    delete (this->local_subrs_per_font)[i];
   }
+  delete this->local_subrs;
 }
 
 }  // namespace ots
diff --git a/gfx/ots/src/cff.h b/gfx/ots/src/cff.h
index 5584acc62..4d8134029 100644
--- a/gfx/ots/src/cff.h
+++ b/gfx/ots/src/cff.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -22,9 +22,21 @@ struct CFFIndex {
   uint32_t offset_to_next;
 };
 
-struct OpenTypeCFF {
-  const uint8_t *data;
-  size_t length;
+class OpenTypeCFF : public Table {
+ public:
+  explicit OpenTypeCFF(Font *font, uint32_t tag)
+      : Table(font, tag, tag),
+        font_dict_length(0),
+        local_subrs(NULL),
+        m_data(NULL),
+        m_length(0) {
+  }
+
+  ~OpenTypeCFF();
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   // Name INDEX. This name is used in name.cc as a postscript font name.
   std::string name;
 
@@ -39,6 +51,10 @@ struct OpenTypeCFF {
   std::vector<CFFIndex *> local_subrs_per_font;
   // A Local Subrs associated with Top DICT. Can be NULL.
   CFFIndex *local_subrs;
+
+ private:
+  const uint8_t *m_data;
+  size_t m_length;
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/cff_type2_charstring.cc b/gfx/ots/src/cff_type2_charstring.cc
index 8c5544b2e..3b134d155 100644
--- a/gfx/ots/src/cff_type2_charstring.cc
+++ b/gfx/ots/src/cff_type2_charstring.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/gfx/ots/src/cff_type2_charstring.h b/gfx/ots/src/cff_type2_charstring.h
index be44bc72c..bf6dff194 100644
--- a/gfx/ots/src/cff_type2_charstring.h
+++ b/gfx/ots/src/cff_type2_charstring.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/gfx/ots/src/cmap.cc b/gfx/ots/src/cmap.cc
index 325f8e0d1..6fb0eab1c 100644
--- a/gfx/ots/src/cmap.cc
+++ b/gfx/ots/src/cmap.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -15,8 +15,6 @@
 // cmap - Character To Glyph Index Mapping Table
 // http://www.microsoft.com/typography/otspec/cmap.htm
 
-#define TABLE_NAME "cmap"
-
 namespace {
 
 struct CMAPSubtableHeader {
@@ -56,8 +54,12 @@ const uint32_t kIVSStart = 0xE0100;
 const uint32_t kIVSEnd = 0xE01EF;
 const uint32_t kUVSUpperLimit = 0xFFFFFF;
 
+} // namespace
+
+namespace ots {
+
 // Parses Format 4 tables
-bool ParseFormat4(ots::Font *font, int platform, int encoding,
+bool OpenTypeCMAP::ParseFormat4(int platform, int encoding,
               const uint8_t *data, size_t length, uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
@@ -65,20 +67,22 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
   // whole thing and recompacting it, we validate it and include it verbatim
   // in the output.
 
-  if (!font->os2) {
-    return OTS_FAILURE_MSG("Required OS/2 table missing");
+  OpenTypeOS2 *os2 = static_cast<OpenTypeOS2*>(
+      GetFont()->GetTypedTable(OTS_TAG_OS2));
+  if (!os2) {
+    return Error("Required OS/2 table missing");
   }
 
   if (!subtable.Skip(4)) {
-    return OTS_FAILURE_MSG("Can't read 4 bytes at start of cmap format 4 subtable");
+    return Error("Can't read 4 bytes at start of cmap format 4 subtable");
   }
   uint16_t language = 0;
   if (!subtable.ReadU16(&language)) {
-    return OTS_FAILURE_MSG("Can't read language");
+    return Error("Can't read language");
   }
   if (language) {
     // Platform ID 3 (windows) subtables should have language '0'.
-    return OTS_FAILURE_MSG("Languages should be 0 (%d)", language);
+    return Error("Languages should be 0 (%d)", language);
   }
 
   uint16_t segcountx2, search_range, entry_selector, range_shift;
@@ -87,16 +91,16 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
       !subtable.ReadU16(&search_range) ||
       !subtable.ReadU16(&entry_selector) ||
       !subtable.ReadU16(&range_shift)) {
-    return OTS_FAILURE_MSG("Failed to read subcmap structure");
+    return Error("Failed to read subcmap structure");
   }
 
   if (segcountx2 & 1 || search_range & 1) {
-    return OTS_FAILURE_MSG("Bad subcmap structure");
+    return Error("Bad subcmap structure");
   }
   const uint16_t segcount = segcountx2 >> 1;
   // There must be at least one segment according the spec.
   if (segcount < 1) {
-    return OTS_FAILURE_MSG("Segcount < 1 (%d)", segcount);
+    return Error("Segcount < 1 (%d)", segcount);
   }
 
   // log2segcount is the maximal x s.t. 2^x < segcount
@@ -107,48 +111,48 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
 
   const uint16_t expected_search_range = 2 * 1u << log2segcount;
   if (expected_search_range != search_range) {
-    return OTS_FAILURE_MSG("expected search range != search range (%d != %d)", expected_search_range, search_range);
+    return Error("expected search range != search range (%d != %d)", expected_search_range, search_range);
   }
 
   if (entry_selector != log2segcount) {
-    return OTS_FAILURE_MSG("entry selector != log2(segement count) (%d != %d)", entry_selector, log2segcount);
+    return Error("entry selector != log2(segement count) (%d != %d)", entry_selector, log2segcount);
   }
 
   const uint16_t expected_range_shift = segcountx2 - search_range;
   if (range_shift != expected_range_shift) {
-    return OTS_FAILURE_MSG("unexpected range shift (%d != %d)", range_shift, expected_range_shift);
+    return Error("unexpected range shift (%d != %d)", range_shift, expected_range_shift);
   }
 
   std::vector<Subtable314Range> ranges(segcount);
 
   for (unsigned i = 0; i < segcount; ++i) {
     if (!subtable.ReadU16(&ranges[i].end_range)) {
-      return OTS_FAILURE_MSG("Failed to read segment %d", i);
+      return Error("Failed to read segment %d", i);
     }
   }
 
   uint16_t padding;
   if (!subtable.ReadU16(&padding)) {
-    return OTS_FAILURE_MSG("Failed to read cmap subtable segment padding");
+    return Error("Failed to read cmap subtable segment padding");
   }
   if (padding) {
-    return OTS_FAILURE_MSG("Non zero cmap subtable segment padding (%d)", padding);
+    return Error("Non zero cmap subtable segment padding (%d)", padding);
   }
 
   for (unsigned i = 0; i < segcount; ++i) {
     if (!subtable.ReadU16(&ranges[i].start_range)) {
-      return OTS_FAILURE_MSG("Failed to read segment start range %d", i);
+      return Error("Failed to read segment start range %d", i);
     }
   }
   for (unsigned i = 0; i < segcount; ++i) {
     if (!subtable.ReadS16(&ranges[i].id_delta)) {
-      return OTS_FAILURE_MSG("Failed to read segment delta %d", i);
+      return Error("Failed to read segment delta %d", i);
     }
   }
   for (unsigned i = 0; i < segcount; ++i) {
     ranges[i].id_range_offset_offset = subtable.offset();
     if (!subtable.ReadU16(&ranges[i].id_range_offset)) {
-      return OTS_FAILURE_MSG("Failed to read segment range offset %d", i);
+      return Error("Failed to read segment range offset %d", i);
     }
 
     if (ranges[i].id_range_offset & 1) {
@@ -156,12 +160,12 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
       // for 0xFFFF-0xFFFF range.
       // (e.g., many fonts in http://www.princexml.com/fonts/)
       if (i == segcount - 1u) {
-        OTS_WARNING("bad id_range_offset");
+        Warning("bad id_range_offset");
         ranges[i].id_range_offset = 0;
         // The id_range_offset value in the transcoded font will not change
         // since this table is not actually "transcoded" yet.
       } else {
-        return OTS_FAILURE_MSG("Bad segment offset (%d)", ranges[i].id_range_offset);
+        return Error("Bad segment offset (%d)", ranges[i].id_range_offset);
       }
     }
   }
@@ -176,36 +180,36 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
         (ranges[i].end_range == 0xffff)) {
       // Some fonts (e.g., Germania.ttf) have multiple 0xffff terminators.
       // We'll accept them as an exception.
-      OTS_WARNING("multiple 0xffff terminators found");
+      Warning("multiple 0xffff terminators found");
       continue;
     }
 
     // Note: some Linux fonts (e.g., LucidaSansOblique.ttf, bsmi00lp.ttf) have
     // unsorted table...
     if (ranges[i].end_range <= ranges[i - 1].end_range) {
-      return OTS_FAILURE_MSG("Out of order end range (%d <= %d)", ranges[i].end_range, ranges[i-1].end_range);
+      return Error("Out of order end range (%d <= %d)", ranges[i].end_range, ranges[i-1].end_range);
     }
     if (ranges[i].start_range <= ranges[i - 1].end_range) {
-      return OTS_FAILURE_MSG("out of order start range (%d <= %d)", ranges[i].start_range, ranges[i-1].end_range);
+      return Error("out of order start range (%d <= %d)", ranges[i].start_range, ranges[i-1].end_range);
     }
 
     // On many fonts, the value of {first, last}_char_index are incorrect.
     // Fix them.
-    if (font->os2->first_char_index != 0xFFFF &&
+    if (os2->table.first_char_index != 0xFFFF &&
         ranges[i].start_range != 0xFFFF &&
-        font->os2->first_char_index > ranges[i].start_range) {
-      font->os2->first_char_index = ranges[i].start_range;
+        os2->table.first_char_index > ranges[i].start_range) {
+      os2->table.first_char_index = ranges[i].start_range;
     }
-    if (font->os2->last_char_index != 0xFFFF &&
+    if (os2->table.last_char_index != 0xFFFF &&
         ranges[i].end_range != 0xFFFF &&
-        font->os2->last_char_index < ranges[i].end_range) {
-      font->os2->last_char_index = ranges[i].end_range;
+        os2->table.last_char_index < ranges[i].end_range) {
+      os2->table.last_char_index = ranges[i].end_range;
     }
   }
 
   // The last range must end at 0xffff
   if (ranges[segcount - 1].start_range != 0xffff || ranges[segcount - 1].end_range != 0xffff) {
-    return OTS_FAILURE_MSG("Final segment start and end must be 0xFFFF (0x%04X-0x%04X)",
+    return Error("Final segment start and end must be 0xFFFF (0x%04X-0x%04X)",
                            ranges[segcount - 1].start_range, ranges[segcount - 1].end_range);
   }
 
@@ -219,7 +223,7 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
         // this is explictly allowed to overflow in the spec
         const uint16_t glyph = code_point + ranges[i].id_delta;
         if (glyph >= num_glyphs) {
-          return OTS_FAILURE_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1);
+          return Error("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1);
         }
       } else {
         const uint16_t range_delta = code_point - ranges[i].start_range;
@@ -230,13 +234,13 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
                                          range_delta * 2;
         // We need to be able to access a 16-bit value from this offset
         if (glyph_id_offset + 1 >= length) {
-          return OTS_FAILURE_MSG("bad glyph id offset (%d > %ld)", glyph_id_offset, length);
+          return Error("bad glyph id offset (%d > %ld)", glyph_id_offset, length);
         }
         uint16_t glyph;
         std::memcpy(&glyph, data + glyph_id_offset, 2);
         glyph = ntohs(glyph);
         if (glyph >= num_glyphs) {
-          return OTS_FAILURE_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1);
+          return Error("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1);
         }
       }
     }
@@ -245,85 +249,85 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding,
   // We accept the table.
   // TODO(yusukes): transcode the subtable.
   if (platform == 3 && encoding == 0) {
-    font->cmap->subtable_3_0_4_data = data;
-    font->cmap->subtable_3_0_4_length = length;
+    this->subtable_3_0_4_data = data;
+    this->subtable_3_0_4_length = length;
   } else if (platform == 3 && encoding == 1) {
-    font->cmap->subtable_3_1_4_data = data;
-    font->cmap->subtable_3_1_4_length = length;
+    this->subtable_3_1_4_data = data;
+    this->subtable_3_1_4_length = length;
   } else if (platform == 0 && encoding == 3) {
-    font->cmap->subtable_0_3_4_data = data;
-    font->cmap->subtable_0_3_4_length = length;
+    this->subtable_0_3_4_data = data;
+    this->subtable_0_3_4_length = length;
   } else {
-    return OTS_FAILURE_MSG("Unknown cmap subtable type (platform=%d, encoding=%d)", platform, encoding);
+    return Error("Unknown cmap subtable type (platform=%d, encoding=%d)", platform, encoding);
   }
 
   return true;
 }
 
-bool Parse31012(ots::Font *font,
-                const uint8_t *data, size_t length, uint16_t num_glyphs) {
+bool OpenTypeCMAP::Parse31012(const uint8_t *data, size_t length,
+                              uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Format 12 tables are simple. We parse these and fully serialise them
   // later.
 
   if (!subtable.Skip(8)) {
-    return OTS_FAILURE_MSG("failed to skip the first 8 bytes of format 12 subtable");
+    return Error("failed to skip the first 8 bytes of format 12 subtable");
   }
   uint32_t language = 0;
   if (!subtable.ReadU32(&language)) {
-    return OTS_FAILURE_MSG("can't read format 12 subtable language");
+    return Error("can't read format 12 subtable language");
   }
   if (language) {
-    return OTS_FAILURE_MSG("format 12 subtable language should be zero (%d)", language);
+    return Error("format 12 subtable language should be zero (%d)", language);
   }
 
   uint32_t num_groups = 0;
   if (!subtable.ReadU32(&num_groups)) {
-    return OTS_FAILURE_MSG("can't read number of format 12 subtable groups");
+    return Error("can't read number of format 12 subtable groups");
   }
   if (num_groups == 0 || subtable.remaining() / 12 < num_groups) {
-    return OTS_FAILURE_MSG("Bad format 12 subtable group count %d", num_groups);
+    return Error("Bad format 12 subtable group count %d", num_groups);
   }
 
   std::vector<ots::OpenTypeCMAPSubtableRange> &groups
-      = font->cmap->subtable_3_10_12;
+      = this->subtable_3_10_12;
   groups.resize(num_groups);
 
   for (unsigned i = 0; i < num_groups; ++i) {
     if (!subtable.ReadU32(&groups[i].start_range) ||
         !subtable.ReadU32(&groups[i].end_range) ||
         !subtable.ReadU32(&groups[i].start_glyph_id)) {
-      return OTS_FAILURE_MSG("can't read format 12 subtable group");
+      return Error("can't read format 12 subtable group");
     }
 
     if (groups[i].start_range > kUnicodeUpperLimit ||
         groups[i].end_range > kUnicodeUpperLimit ||
         groups[i].start_glyph_id > 0xFFFF) {
-      return OTS_FAILURE_MSG("bad format 12 subtable group (startCharCode=0x%4X, endCharCode=0x%4X, startGlyphID=%d)",
+      return Error("bad format 12 subtable group (startCharCode=0x%4X, endCharCode=0x%4X, startGlyphID=%d)",
                              groups[i].start_range, groups[i].end_range, groups[i].start_glyph_id);
     }
 
     // We assert that the glyph value is within range. Because of the range
     // limits, above, we don't need to worry about overflow.
     if (groups[i].end_range < groups[i].start_range) {
-      return OTS_FAILURE_MSG("format 12 subtable group endCharCode before startCharCode (0x%4X < 0x%4X)",
+      return Error("format 12 subtable group endCharCode before startCharCode (0x%4X < 0x%4X)",
                              groups[i].end_range, groups[i].start_range);
     }
     if ((groups[i].end_range - groups[i].start_range) +
         groups[i].start_glyph_id > num_glyphs) {
-      return OTS_FAILURE_MSG("bad format 12 subtable group startGlyphID (%d)", groups[i].start_glyph_id);
+      return Error("bad format 12 subtable group startGlyphID (%d)", groups[i].start_glyph_id);
     }
   }
 
   // the groups must be sorted by start code and may not overlap
   for (unsigned i = 1; i < num_groups; ++i) {
     if (groups[i].start_range <= groups[i - 1].start_range) {
-      return OTS_FAILURE_MSG("out of order format 12 subtable group (startCharCode=0x%4X <= startCharCode=0x%4X of previous group)",
+      return Error("out of order format 12 subtable group (startCharCode=0x%4X <= startCharCode=0x%4X of previous group)",
                              groups[i].start_range, groups[i-1].start_range);
     }
     if (groups[i].start_range <= groups[i - 1].end_range) {
-      return OTS_FAILURE_MSG("overlapping format 12 subtable groups (startCharCode=0x%4X <= endCharCode=0x%4X of previous group)",
+      return Error("overlapping format 12 subtable groups (startCharCode=0x%4X <= endCharCode=0x%4X of previous group)",
                              groups[i].start_range, groups[i-1].end_range);
     }
   }
@@ -331,44 +335,43 @@ bool Parse31012(ots::Font *font,
   return true;
 }
 
-bool Parse31013(ots::Font *font,
-                const uint8_t *data, size_t length, uint16_t num_glyphs) {
+bool OpenTypeCMAP::Parse31013(const uint8_t *data, size_t length,
+                              uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Format 13 tables are simple. We parse these and fully serialise them
   // later.
 
   if (!subtable.Skip(8)) {
-    return OTS_FAILURE_MSG("Bad cmap subtable length");
+    return Error("Bad cmap subtable length");
   }
   uint32_t language = 0;
   if (!subtable.ReadU32(&language)) {
-    return OTS_FAILURE_MSG("Can't read cmap subtable language");
+    return Error("Can't read cmap subtable language");
   }
   if (language) {
-    return OTS_FAILURE_MSG("Cmap subtable language should be zero but is %d", language);
+    return Error("Cmap subtable language should be zero but is %d", language);
   }
 
   uint32_t num_groups = 0;
   if (!subtable.ReadU32(&num_groups)) {
-    return OTS_FAILURE_MSG("Can't read number of groups in a cmap subtable");
+    return Error("Can't read number of groups in a cmap subtable");
   }
 
   // We limit the number of groups in the same way as in 3.10.12 tables. See
   // the comment there in
   if (num_groups == 0 || subtable.remaining() / 12 < num_groups) {
-    return OTS_FAILURE_MSG("Bad format 13 subtable group count %d", num_groups);
+    return Error("Bad format 13 subtable group count %d", num_groups);
   }
 
-  std::vector<ots::OpenTypeCMAPSubtableRange> &groups
-      = font->cmap->subtable_3_10_13;
+  std::vector<ots::OpenTypeCMAPSubtableRange> &groups = this->subtable_3_10_13;
   groups.resize(num_groups);
 
   for (unsigned i = 0; i < num_groups; ++i) {
     if (!subtable.ReadU32(&groups[i].start_range) ||
         !subtable.ReadU32(&groups[i].end_range) ||
         !subtable.ReadU32(&groups[i].start_glyph_id)) {
-      return OTS_FAILURE_MSG("Can't read subrange structure in a cmap subtable");
+      return Error("Can't read subrange structure in a cmap subtable");
     }
 
     // We conservatively limit all of the values to protect some parsers from
@@ -376,29 +379,29 @@ bool Parse31013(ots::Font *font,
     if (groups[i].start_range > kUnicodeUpperLimit ||
         groups[i].end_range > kUnicodeUpperLimit ||
         groups[i].start_glyph_id > 0xFFFF) {
-      return OTS_FAILURE_MSG("Bad subrange with start_range=%d, end_range=%d, start_glyph_id=%d", groups[i].start_range, groups[i].end_range, groups[i].start_glyph_id);
+      return Error("Bad subrange with start_range=%d, end_range=%d, start_glyph_id=%d", groups[i].start_range, groups[i].end_range, groups[i].start_glyph_id);
     }
 
     if (groups[i].start_glyph_id >= num_glyphs) {
-      return OTS_FAILURE_MSG("Subrange starting glyph id too high (%d > %d)", groups[i].start_glyph_id, num_glyphs);
+      return Error("Subrange starting glyph id too high (%d > %d)", groups[i].start_glyph_id, num_glyphs);
     }
   }
 
   // the groups must be sorted by start code and may not overlap
   for (unsigned i = 1; i < num_groups; ++i) {
     if (groups[i].start_range <= groups[i - 1].start_range) {
-      return OTS_FAILURE_MSG("Overlapping subrange starts (%d >= %d)", groups[i]. start_range, groups[i-1].start_range);
+      return Error("Overlapping subrange starts (%d >= %d)", groups[i]. start_range, groups[i-1].start_range);
     }
     if (groups[i].start_range <= groups[i - 1].end_range) {
-      return OTS_FAILURE_MSG("Overlapping subranges (%d <= %d)", groups[i].start_range, groups[i-1].end_range);
+      return Error("Overlapping subranges (%d <= %d)", groups[i].start_range, groups[i-1].end_range);
     }
   }
 
   return true;
 }
 
-bool Parse0514(ots::Font *font,
-               const uint8_t *data, size_t length, uint16_t num_glyphs) {
+bool OpenTypeCMAP::Parse0514(const uint8_t *data, size_t length,
+                             uint16_t num_glyphs) {
   // Unicode Variation Selector table
   ots::Buffer subtable(data, length);
 
@@ -407,26 +410,26 @@ bool Parse0514(ots::Font *font,
 
   // Skip format (USHORT) and length (ULONG)
   if (!subtable.Skip(6)) {
-    return OTS_FAILURE_MSG("Can't read start of cmap subtable");
+    return Error("Can't read start of cmap subtable");
   }
 
   uint32_t num_records = 0;
   if (!subtable.ReadU32(&num_records)) {
-    return OTS_FAILURE_MSG("Can't read number of records in cmap subtable");
+    return Error("Can't read number of records in cmap subtable");
   }
   if (num_records == 0 || num_records > kMaxCMAPSelectorRecords) {
-    return OTS_FAILURE_MSG("Bad format 14 subtable records count %d", num_records);
+    return Error("Bad format 14 subtable records count %d", num_records);
   }
 
   std::vector<ots::OpenTypeCMAPSubtableVSRecord>& records
-      = font->cmap->subtable_0_5_14;
+      = this->subtable_0_5_14;
   records.resize(num_records);
 
   for (unsigned i = 0; i < num_records; ++i) {
     if (!subtable.ReadU24(&records[i].var_selector) ||
         !subtable.ReadU32(&records[i].default_offset) ||
         !subtable.ReadU32(&records[i].non_default_offset)) {
-      return OTS_FAILURE_MSG("Can't read record structure of record %d in cmap subtale", i);
+      return Error("Can't read record structure of record %d in cmap subtale", i);
     }
     // Checks the value of variation selector
     if (!((records[i].var_selector >= kMongolianVSStart &&
@@ -435,24 +438,24 @@ bool Parse0514(ots::Font *font,
            records[i].var_selector <= kVSEnd) ||
           (records[i].var_selector >= kIVSStart &&
            records[i].var_selector <= kIVSEnd))) {
-      return OTS_FAILURE_MSG("Bad record variation selector (%04X) in record %i", records[i].var_selector, i);
+      return Error("Bad record variation selector (%04X) in record %i", records[i].var_selector, i);
     }
     if (i > 0 &&
         records[i-1].var_selector >= records[i].var_selector) {
-      return OTS_FAILURE_MSG("Out of order variation selector (%04X >= %04X) in record %d", records[i-1].var_selector, records[i].var_selector, i);
+      return Error("Out of order variation selector (%04X >= %04X) in record %d", records[i-1].var_selector, records[i].var_selector, i);
     }
 
     // Checks offsets
     if (!records[i].default_offset && !records[i].non_default_offset) {
-      return OTS_FAILURE_MSG("No default aoffset in variation selector record %d", i);
+      return Error("No default aoffset in variation selector record %d", i);
     }
     if (records[i].default_offset &&
         records[i].default_offset >= length) {
-      return OTS_FAILURE_MSG("Default offset too high (%d >= %ld) in record %d", records[i].default_offset, length, i);
+      return Error("Default offset too high (%d >= %ld) in record %d", records[i].default_offset, length, i);
     }
     if (records[i].non_default_offset &&
         records[i].non_default_offset >= length) {
-      return OTS_FAILURE_MSG("Non default offset too high (%d >= %ld) in record %d", records[i].non_default_offset, length, i);
+      return Error("Non default offset too high (%d >= %ld) in record %d", records[i].non_default_offset, length, i);
     }
   }
 
@@ -462,10 +465,10 @@ bool Parse0514(ots::Font *font,
       subtable.set_offset(records[i].default_offset);
       uint32_t num_ranges = 0;
       if (!subtable.ReadU32(&num_ranges)) {
-        return OTS_FAILURE_MSG("Can't read number of ranges in record %d", i);
+        return Error("Can't read number of ranges in record %d", i);
       }
       if (num_ranges == 0 || subtable.remaining() / 4 < num_ranges) {
-        return OTS_FAILURE_MSG("Bad number of ranges (%d) in record %d", num_ranges, i);
+        return Error("Bad number of ranges (%d) in record %d", num_ranges, i);
       }
 
       uint32_t last_unicode_value = 0;
@@ -476,7 +479,7 @@ bool Parse0514(ots::Font *font,
       for (unsigned j = 0; j < num_ranges; ++j) {
         if (!subtable.ReadU24(&ranges[j].unicode_value) ||
             !subtable.ReadU8(&ranges[j].additional_count)) {
-          return OTS_FAILURE_MSG("Can't read range info in variation selector record %d", i);
+          return Error("Can't read range info in variation selector record %d", i);
         }
         const uint32_t check_value =
             ranges[j].unicode_value + ranges[j].additional_count;
@@ -485,7 +488,7 @@ bool Parse0514(ots::Font *font,
             check_value > kUVSUpperLimit ||
             (last_unicode_value &&
              ranges[j].unicode_value <= last_unicode_value)) {
-          return OTS_FAILURE_MSG("Bad Unicode value *%04X) in variation selector range %d record %d", ranges[j].unicode_value, j, i);
+          return Error("Bad Unicode value *%04X) in variation selector range %d record %d", ranges[j].unicode_value, j, i);
         }
         last_unicode_value = check_value;
       }
@@ -496,10 +499,10 @@ bool Parse0514(ots::Font *font,
       subtable.set_offset(records[i].non_default_offset);
       uint32_t num_mappings = 0;
       if (!subtable.ReadU32(&num_mappings)) {
-        return OTS_FAILURE_MSG("Can't read number of mappings in variation selector record %d", i);
+        return Error("Can't read number of mappings in variation selector record %d", i);
       }
       if (num_mappings == 0 || subtable.remaining() / 5 < num_mappings) {
-        return OTS_FAILURE_MSG("Bad number of mappings (%d) in variation selector record %d", num_mappings, i);
+        return Error("Bad number of mappings (%d) in variation selector record %d", num_mappings, i);
       }
 
       uint32_t last_unicode_value = 0;
@@ -510,14 +513,14 @@ bool Parse0514(ots::Font *font,
       for (unsigned j = 0; j < num_mappings; ++j) {
         if (!subtable.ReadU24(&mappings[j].unicode_value) ||
             !subtable.ReadU16(&mappings[j].glyph_id)) {
-          return OTS_FAILURE_MSG("Can't read mapping %d in variation selector record %d", j, i);
+          return Error("Can't read mapping %d in variation selector record %d", j, i);
         }
         if (mappings[j].glyph_id == 0 ||
             mappings[j].unicode_value == 0 ||
             mappings[j].unicode_value > kUnicodeUpperLimit ||
             (last_unicode_value &&
              mappings[j].unicode_value <= last_unicode_value)) {
-          return OTS_FAILURE_MSG("Bad mapping (%04X -> %d) in mapping %d of variation selector %d", mappings[j].unicode_value, mappings[j].glyph_id, j, i);
+          return Error("Bad mapping (%04X -> %d) in mapping %d of variation selector %d", mappings[j].unicode_value, mappings[j].glyph_id, j, i);
         }
         last_unicode_value = mappings[j].unicode_value;
       }
@@ -525,60 +528,55 @@ bool Parse0514(ots::Font *font,
   }
 
   if (subtable.offset() != length) {
-    return OTS_FAILURE_MSG("Bad subtable offset (%ld != %ld)", subtable.offset(), length);
+    return Error("Bad subtable offset (%ld != %ld)", subtable.offset(), length);
   }
-  font->cmap->subtable_0_5_14_length = subtable.offset();
+  this->subtable_0_5_14_length = subtable.offset();
   return true;
 }
 
-bool Parse100(ots::Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeCMAP::Parse100(const uint8_t *data, size_t length) {
   // Mac Roman table
   ots::Buffer subtable(data, length);
 
   if (!subtable.Skip(4)) {
-    return OTS_FAILURE_MSG("Bad cmap subtable");
+    return Error("Bad cmap subtable");
   }
   uint16_t language = 0;
   if (!subtable.ReadU16(&language)) {
-    return OTS_FAILURE_MSG("Can't read language in cmap subtable");
+    return Error("Can't read language in cmap subtable");
   }
   if (language) {
     // simsun.ttf has non-zero language id.
-    OTS_WARNING("language id should be zero: %u", language);
+    Warning("language id should be zero: %u", language);
   }
 
-  font->cmap->subtable_1_0_0.reserve(kFormat0ArraySize);
+  this->subtable_1_0_0.reserve(kFormat0ArraySize);
   for (size_t i = 0; i < kFormat0ArraySize; ++i) {
     uint8_t glyph_id = 0;
     if (!subtable.ReadU8(&glyph_id)) {
-      return OTS_FAILURE_MSG("Can't read glyph id at array[%ld] in cmap subtable", i);
+      return Error("Can't read glyph id at array[%ld] in cmap subtable", i);
     }
-    font->cmap->subtable_1_0_0.push_back(glyph_id);
+    this->subtable_1_0_0.push_back(glyph_id);
   }
 
   return true;
 }
 
-}  // namespace
-
-namespace ots {
-
-bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeCMAP::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
-  font->cmap = new OpenTypeCMAP;
 
   uint16_t version = 0;
   uint16_t num_tables = 0;
   if (!table.ReadU16(&version) ||
       !table.ReadU16(&num_tables)) {
-    return OTS_FAILURE_MSG("Can't read structure of cmap");
+    return Error("Can't read structure of cmap");
   }
 
   if (version != 0) {
-    return OTS_FAILURE_MSG("Non zero cmap version (%d)", version);
+    return Error("Non zero cmap version (%d)", version);
   }
   if (!num_tables) {
-    return OTS_FAILURE_MSG("No subtables in cmap!");
+    return Error("No subtables in cmap!");
   }
 
   std::vector<CMAPSubtableHeader> subtable_headers;
@@ -591,7 +589,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
     if (!table.ReadU16(&subt.platform) ||
         !table.ReadU16(&subt.encoding) ||
         !table.ReadU32(&subt.offset)) {
-      return OTS_FAILURE_MSG("Can't read subtable information cmap subtable %d", i);
+      return Error("Can't read subtable information cmap subtable %d", i);
     }
 
     subtable_headers.push_back(subt);
@@ -602,11 +600,11 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
   // make sure that all the offsets are valid.
   for (unsigned i = 0; i < num_tables; ++i) {
     if (subtable_headers[i].offset > 1024 * 1024 * 1024) {
-      return OTS_FAILURE_MSG("Bad subtable offset in cmap subtable %d", i);
+      return Error("Bad subtable offset in cmap subtable %d", i);
     }
     if (subtable_headers[i].offset < data_offset ||
         subtable_headers[i].offset >= length) {
-      return OTS_FAILURE_MSG("Bad subtable offset (%d) in cmap subtable %d", subtable_headers[i].offset, i);
+      return Error("Bad subtable offset (%d) in cmap subtable %d", subtable_headers[i].offset, i);
     }
   }
 
@@ -615,7 +613,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
   for (unsigned i = 0; i < num_tables; ++i) {
     table.set_offset(subtable_headers[i].offset);
     if (!table.ReadU16(&subtable_headers[i].format)) {
-      return OTS_FAILURE_MSG("Can't read cmap subtable header format %d", i);
+      return Error("Can't read cmap subtable header format %d", i);
     }
 
     uint16_t len = 0;
@@ -624,10 +622,10 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
       case 0:
       case 4:
         if (!table.ReadU16(&len)) {
-          return OTS_FAILURE_MSG("Can't read cmap subtable %d length", i);
+          return Error("Can't read cmap subtable %d length", i);
         }
         if (!table.ReadU16(&lang)) {
-          return OTS_FAILURE_MSG("Can't read cmap subtable %d language", i);
+          return Error("Can't read cmap subtable %d language", i);
         }
         subtable_headers[i].length = len;
         subtable_headers[i].language = lang;
@@ -635,18 +633,18 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
       case 12:
       case 13:
         if (!table.Skip(2)) {
-          return OTS_FAILURE_MSG("Bad cmap subtable %d structure", i);
+          return Error("Bad cmap subtable %d structure", i);
         }
         if (!table.ReadU32(&subtable_headers[i].length)) {
-          return OTS_FAILURE_MSG("Can read cmap subtable %d length", i);
+          return Error("Can read cmap subtable %d length", i);
         }
         if (!table.ReadU32(&subtable_headers[i].language)) {
-          return OTS_FAILURE_MSG("Can't read cmap subtable %d language", i);
+          return Error("Can't read cmap subtable %d language", i);
         }
         break;
       case 14:
         if (!table.ReadU32(&subtable_headers[i].length)) {
-          return OTS_FAILURE_MSG("Can't read cmap subtable %d length", i);
+          return Error("Can't read cmap subtable %d length", i);
         }
         subtable_headers[i].language = 0;
         break;
@@ -664,7 +662,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
          (subtable_headers[i - 1].encoding > subtable_headers[i].encoding ||
           (subtable_headers[i - 1].encoding == subtable_headers[i].encoding &&
            subtable_headers[i - 1].language > subtable_headers[i].language))))
-      OTS_WARNING("subtable %d with platform ID %d, encoding ID %d, language ID %d "
+      Warning("subtable %d with platform ID %d, encoding ID %d, language ID %d "
                   "following subtable with platform ID %d, encoding ID %d, language ID %d",
                   i,
                   subtable_headers[i].platform,
@@ -679,14 +677,14 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
   for (unsigned i = 0; i < num_tables; ++i) {
     if (!subtable_headers[i].length) continue;
     if (subtable_headers[i].length > 1024 * 1024 * 1024) {
-      return OTS_FAILURE_MSG("Bad cmap subtable %d length", i);
+      return Error("Bad cmap subtable %d length", i);
     }
     // We know that both the offset and length are < 1GB, so the following
     // addition doesn't overflow
     const uint32_t end_byte
         = subtable_headers[i].offset + subtable_headers[i].length;
     if (end_byte > length) {
-      return OTS_FAILURE_MSG("Over long cmap subtable %d @ %d for %d", i, subtable_headers[i].offset, subtable_headers[i].length);
+      return Error("Over long cmap subtable %d @ %d for %d", i, subtable_headers[i].offset, subtable_headers[i].length);
     }
   }
 
@@ -714,16 +712,18 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
   for (unsigned i = 0; i < overlap_checker.size(); ++i) {
     overlap_count += (overlap_checker[i].second ? 1 : -1);
     if (overlap_count > 1) {
-      return OTS_FAILURE_MSG("Excessive overlap count %d", overlap_count);
+      return Error("Excessive overlap count %d", overlap_count);
     }
   }
 
   // we grab the number of glyphs in the file from the maxp table to make sure
   // that the character map isn't referencing anything beyound this range.
-  if (!font->maxp) {
-    return OTS_FAILURE_MSG("No maxp table in font! Needed by cmap.");
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return Error("No maxp table in font! Needed by cmap.");
   }
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  const uint16_t num_glyphs = maxp->num_glyphs;
 
   // We only support a subset of the possible character map tables. Microsoft
   // 'strongly recommends' that everyone supports the Unicode BMP table with
@@ -760,29 +760,29 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
         // table actually points to MS symbol data and thus should be parsed as
         // 3-0-4 table (e.g., marqueem.ttf and quixotic.ttf). This error will be
         // recovered in ots_cmap_serialise().
-        if (!ParseFormat4(font, 3, 1, data + subtable_headers[i].offset,
+        if (!ParseFormat4(3, 1, data + subtable_headers[i].offset,
                       subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE_MSG("Failed to parse format 4 cmap subtable %d", i);
+          return Error("Failed to parse format 4 cmap subtable %d", i);
         }
       } else if ((subtable_headers[i].encoding == 3) &&
                  (subtable_headers[i].format == 4)) {
         // parse and output the 0-3-4 table as 0-3-4 table.
-        if (!ParseFormat4(font, 0, 3, data + subtable_headers[i].offset,
+        if (!ParseFormat4(0, 3, data + subtable_headers[i].offset,
                       subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE_MSG("Failed to parse format 4 cmap subtable %d", i);
+          return Error("Failed to parse format 4 cmap subtable %d", i);
         }
       } else if ((subtable_headers[i].encoding == 3) &&
                  (subtable_headers[i].format == 12)) {
         // parse and output the 0-3-12 table as 3-10-12 table.
-        if (!Parse31012(font, data + subtable_headers[i].offset,
+        if (!Parse31012(data + subtable_headers[i].offset,
                         subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE_MSG("Failed to parse format 12 cmap subtable %d", i);
+          return Error("Failed to parse format 12 cmap subtable %d", i);
         }
       } else if ((subtable_headers[i].encoding == 5) &&
                  (subtable_headers[i].format == 14)) {
-        if (!Parse0514(font, data + subtable_headers[i].offset,
+        if (!Parse0514(data + subtable_headers[i].offset,
                        subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE_MSG("Failed to parse format 14 cmap subtable %d", i);
+          return Error("Failed to parse format 14 cmap subtable %d", i);
         }
       }
     } else if (subtable_headers[i].platform == 1) {
@@ -791,7 +791,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
       if ((subtable_headers[i].encoding == 0) &&
           (subtable_headers[i].format == 0)) {
         // parse and output the 1-0-0 table.
-        if (!Parse100(font, data + subtable_headers[i].offset,
+        if (!Parse100(data + subtable_headers[i].offset,
                       subtable_headers[i].length)) {
           return OTS_FAILURE();
         }
@@ -804,7 +804,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
         case 1:
           if (subtable_headers[i].format == 4) {
             // parse 3-0-4 or 3-1-4 table.
-            if (!ParseFormat4(font, subtable_headers[i].platform,
+            if (!ParseFormat4(subtable_headers[i].platform,
                           subtable_headers[i].encoding,
                           data + subtable_headers[i].offset,
                           subtable_headers[i].length, num_glyphs)) {
@@ -814,14 +814,14 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
           break;
         case 10:
           if (subtable_headers[i].format == 12) {
-            font->cmap->subtable_3_10_12.clear();
-            if (!Parse31012(font, data + subtable_headers[i].offset,
+            this->subtable_3_10_12.clear();
+            if (!Parse31012(data + subtable_headers[i].offset,
                             subtable_headers[i].length, num_glyphs)) {
               return OTS_FAILURE();
             }
           } else if (subtable_headers[i].format == 13) {
-            font->cmap->subtable_3_10_13.clear();
-            if (!Parse31013(font, data + subtable_headers[i].offset,
+            this->subtable_3_10_13.clear();
+            if (!Parse31013(data + subtable_headers[i].offset,
                             subtable_headers[i].length, num_glyphs)) {
               return OTS_FAILURE();
             }
@@ -834,20 +834,16 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) {
   return true;
 }
 
-bool ots_cmap_should_serialise(Font *font) {
-  return font->cmap != NULL;
-}
-
-bool ots_cmap_serialise(OTSStream *out, Font *font) {
-  const bool have_034 = font->cmap->subtable_0_3_4_data != NULL;
-  const bool have_0514 = font->cmap->subtable_0_5_14.size() != 0;
-  const bool have_100 = font->cmap->subtable_1_0_0.size() != 0;
-  const bool have_304 = font->cmap->subtable_3_0_4_data != NULL;
+bool OpenTypeCMAP::Serialize(OTSStream *out) {
+  const bool have_034 = this->subtable_0_3_4_data != NULL;
+  const bool have_0514 = this->subtable_0_5_14.size() != 0;
+  const bool have_100 = this->subtable_1_0_0.size() != 0;
+  const bool have_304 = this->subtable_3_0_4_data != NULL;
   // MS Symbol and MS Unicode tables should not co-exist.
   // See the comment above in 0-0-4 parser.
-  const bool have_314 = (!have_304) && font->cmap->subtable_3_1_4_data;
-  const bool have_31012 = font->cmap->subtable_3_10_12.size() != 0;
-  const bool have_31013 = font->cmap->subtable_3_10_13.size() != 0;
+  const bool have_314 = (!have_304) && this->subtable_3_1_4_data;
+  const bool have_31012 = this->subtable_3_10_12.size() != 0;
+  const bool have_31013 = this->subtable_3_10_13.size() != 0;
   const uint16_t num_subtables = static_cast<uint16_t>(have_034) +
                                  static_cast<uint16_t>(have_0514) +
                                  static_cast<uint16_t>(have_100) +
@@ -860,7 +856,7 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) {
   // Some fonts don't have 3-0-4 MS Symbol nor 3-1-4 Unicode BMP tables
   // (e.g., old fonts for Mac). We don't support them.
   if (!have_304 && !have_314 && !have_034 && !have_31012 && !have_31013) {
-    return OTS_FAILURE_MSG("no supported subtables were found");
+    return Error("no supported subtables were found");
   }
 
   if (!out->WriteU16(0) ||
@@ -875,8 +871,8 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) {
 
   const off_t offset_034 = out->Tell();
   if (have_034) {
-    if (!out->Write(font->cmap->subtable_0_3_4_data,
-                    font->cmap->subtable_0_3_4_length)) {
+    if (!out->Write(this->subtable_0_3_4_data,
+                    this->subtable_0_3_4_length)) {
       return OTS_FAILURE();
     }
   }
@@ -884,10 +880,10 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) {
   const off_t offset_0514 = out->Tell();
   if (have_0514) {
     const std::vector<ots::OpenTypeCMAPSubtableVSRecord> &records
-        = font->cmap->subtable_0_5_14;
+        = this->subtable_0_5_14;
     const unsigned num_records = records.size();
     if (!out->WriteU16(14) ||
-        !out->WriteU32(font->cmap->subtable_0_5_14_length) ||
+        !out->WriteU32(this->subtable_0_5_14_length) ||
         !out->WriteU32(num_records)) {
       return OTS_FAILURE();
     }
@@ -939,23 +935,23 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) {
         !out->WriteU16(0)) {  // language
       return OTS_FAILURE();
     }
-    if (!out->Write(&(font->cmap->subtable_1_0_0[0]), kFormat0ArraySize)) {
+    if (!out->Write(&(this->subtable_1_0_0[0]), kFormat0ArraySize)) {
       return OTS_FAILURE();
     }
   }
 
   const off_t offset_304 = out->Tell();
   if (have_304) {
-    if (!out->Write(font->cmap->subtable_3_0_4_data,
-                    font->cmap->subtable_3_0_4_length)) {
+    if (!out->Write(this->subtable_3_0_4_data,
+                    this->subtable_3_0_4_length)) {
       return OTS_FAILURE();
     }
   }
 
   const off_t offset_314 = out->Tell();
   if (have_314) {
-    if (!out->Write(font->cmap->subtable_3_1_4_data,
-                    font->cmap->subtable_3_1_4_length)) {
+    if (!out->Write(this->subtable_3_1_4_data,
+                    this->subtable_3_1_4_length)) {
       return OTS_FAILURE();
     }
   }
@@ -963,7 +959,7 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) {
   const off_t offset_31012 = out->Tell();
   if (have_31012) {
     std::vector<OpenTypeCMAPSubtableRange> &groups
-        = font->cmap->subtable_3_10_12;
+        = this->subtable_3_10_12;
     const unsigned num_groups = groups.size();
     if (!out->WriteU16(12) ||
         !out->WriteU16(0) ||
@@ -985,7 +981,7 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) {
   const off_t offset_31013 = out->Tell();
   if (have_31013) {
     std::vector<OpenTypeCMAPSubtableRange> &groups
-        = font->cmap->subtable_3_10_13;
+        = this->subtable_3_10_13;
     const unsigned num_groups = groups.size();
     if (!out->WriteU16(13) ||
         !out->WriteU16(0) ||
@@ -1074,15 +1070,4 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) {
   return true;
 }
 
-void ots_cmap_reuse(Font *font, Font *other) {
-  font->cmap = other->cmap;
-  font->cmap_reused = true;
-}
-
-void ots_cmap_free(Font *font) {
-  delete font->cmap;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/cmap.h b/gfx/ots/src/cmap.h
index 5b09556b7..feddbc696 100644
--- a/gfx/ots/src/cmap.h
+++ b/gfx/ots/src/cmap.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -35,9 +35,11 @@ struct OpenTypeCMAPSubtableVSRecord {
   std::vector<OpenTypeCMAPSubtableVSMapping> mappings;
 };
 
-struct OpenTypeCMAP {
-  OpenTypeCMAP()
-      : subtable_0_3_4_data(NULL),
+class OpenTypeCMAP : public Table {
+ public:
+  explicit OpenTypeCMAP(Font *font, uint32_t tag)
+      : Table(font, tag, tag),
+        subtable_0_3_4_data(NULL),
         subtable_0_3_4_length(0),
         subtable_0_5_14_length(0),
         subtable_3_0_4_data(NULL),
@@ -46,6 +48,10 @@ struct OpenTypeCMAP {
         subtable_3_1_4_length(0) {
   }
 
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
+ private:
   // Platform 0, Encoding 3, Format 4, Unicode BMP table.
   const uint8_t *subtable_0_3_4_data;
   size_t subtable_0_3_4_length;
@@ -67,6 +73,13 @@ struct OpenTypeCMAP {
   std::vector<OpenTypeCMAPSubtableRange> subtable_3_10_13;
   // Platform 1, Encoding 0, Format 0, Mac Roman table.
   std::vector<uint8_t> subtable_1_0_0;
+
+  bool ParseFormat4(int platform, int encoding, const uint8_t *data,
+                    size_t length, uint16_t num_glyphs);
+  bool Parse31012(const uint8_t *data, size_t length, uint16_t num_glyphs);
+  bool Parse31013(const uint8_t *data, size_t length, uint16_t num_glyphs);
+  bool Parse0514(const uint8_t *data, size_t length, uint16_t num_glyphs);
+  bool Parse100(const uint8_t *data, size_t length);
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/cvt.cc b/gfx/ots/src/cvt.cc
index 1402e7c06..2e0257889 100644
--- a/gfx/ots/src/cvt.cc
+++ b/gfx/ots/src/cvt.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,59 +7,40 @@
 // cvt - Control Value Table
 // http://www.microsoft.com/typography/otspec/cvt.htm
 
-#define TABLE_NAME "cvt"
-
 namespace ots {
 
-bool ots_cvt_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeCVT::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypeCVT *cvt = new OpenTypeCVT;
-  font->cvt = cvt;
-
   if (length >= 128 * 1024u) {
-    return OTS_FAILURE_MSG("Length (%d) > 120K");  // almost all cvt tables are less than 4k bytes.
+    return Error("Length (%d) > 120K");  // almost all cvt tables are less than 4k bytes.
   }
 
   if (length % 2 != 0) {
-    return OTS_FAILURE_MSG("Uneven cvt length (%d)", length);
+    return Error("Uneven table length (%d)", length);
   }
 
   if (!table.Skip(length)) {
-    return OTS_FAILURE_MSG("Length too high");
+    return Error("Table length too high");
   }
 
-  cvt->data = data;
-  cvt->length = length;
+  this->data = data;
+  this->length = length;
   return true;
 }
 
-bool ots_cvt_should_serialise(Font *font) {
-  if (!font->glyf) {
-    return false;  // this table is not for CFF fonts.
-  }
-  return font->cvt != NULL;
-}
-
-bool ots_cvt_serialise(OTSStream *out, Font *font) {
-  const OpenTypeCVT *cvt = font->cvt;
-
-  if (!out->Write(cvt->data, cvt->length)) {
-    return OTS_FAILURE_MSG("Failed to write CVT table");
+bool OpenTypeCVT::Serialize(OTSStream *out) {
+  if (!out->Write(this->data, this->length)) {
+    return Error("Failed to write cvt table");
   }
 
   return true;
 }
 
-void ots_cvt_reuse(Font *font, Font *other) {
-  font->cvt = other->cvt;
-  font->cvt_reused = true;
-}
-
-void ots_cvt_free(Font *font) {
-  delete font->cvt;
+bool OpenTypeCVT::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for CFF fonts.
+         GetFont()->GetTable(OTS_TAG_GLYF) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/cvt.h b/gfx/ots/src/cvt.h
index 3c25f06f6..88a96ca20 100644
--- a/gfx/ots/src/cvt.h
+++ b/gfx/ots/src/cvt.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,7 +9,16 @@
 
 namespace ots {
 
-struct OpenTypeCVT {
+class OpenTypeCVT : public Table {
+ public:
+  explicit OpenTypeCVT(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
   const uint8_t *data;
   uint32_t length;
 };
diff --git a/gfx/ots/src/feat.cc b/gfx/ots/src/feat.cc
new file mode 100644
index 000000000..374a7ae9f
--- /dev/null
+++ b/gfx/ots/src/feat.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "feat.h"
+
+#include "name.h"
+
+namespace ots {
+
+bool OpenTypeFEAT::Parse(const uint8_t* data, size_t length) {
+  if (GetFont()->dropped_graphite) {
+    return Drop("Skipping Graphite table");
+  }
+  Buffer table(data, length);
+
+  if (!table.ReadU32(&this->version)) {
+    return DropGraphite("Failed to read version");
+  }
+  if (this->version >> 16 != 1 && this->version >> 16 != 2) {
+    return DropGraphite("Unsupported table version: %u", this->version >> 16);
+  }
+  if (!table.ReadU16(&this->numFeat)) {
+    return DropGraphite("Failed to read numFeat");
+  }
+  if (!table.ReadU16(&this->reserved)) {
+    return DropGraphite("Failed to read reserved");
+  }
+  if (this->reserved != 0) {
+    Warning("Nonzero reserved");
+  }
+  if (!table.ReadU32(&this->reserved2)) {
+    return DropGraphite("Failed to read valid reserved2");
+  }
+  if (this->reserved2 != 0) {
+    Warning("Nonzero reserved2");
+  }
+
+  std::unordered_set<size_t> unverified;
+  //this->features.resize(this->numFeat, this);
+  for (unsigned i = 0; i < this->numFeat; ++i) {
+    this->features.emplace_back(this);
+    FeatureDefn& feature = this->features[i];
+    if (!feature.ParsePart(table)) {
+      return DropGraphite("Failed to read features[%u]", i);
+    }
+    this->feature_ids.insert(feature.id);
+    for (unsigned j = 0; j < feature.numSettings; ++j) {
+      size_t offset = feature.offset + j * 4;
+      if (offset < feature.offset || offset > length) {
+        return DropGraphite("Invalid FeatSettingDefn offset %zu/%zu",
+                            offset, length);
+      }
+      unverified.insert(offset);
+        // need to verify that this FeatureDefn points to valid
+        // FeatureSettingDefn
+    }
+  }
+
+  while (table.remaining()) {
+    bool used = unverified.erase(table.offset());
+    FeatureSettingDefn featSetting(this);
+    if (!featSetting.ParsePart(table, used)) {
+      return DropGraphite("Failed to read a FeatureSettingDefn");
+    }
+    featSettings.push_back(featSetting);
+  }
+
+  if (!unverified.empty()) {
+    return DropGraphite("%zu incorrect offsets into featSettings",
+                        unverified.size());
+  }
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeFEAT::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      !out->WriteU16(this->numFeat) ||
+      !out->WriteU16(this->reserved) ||
+      !out->WriteU32(this->reserved2) ||
+      !SerializeParts(this->features, out) ||
+      !SerializeParts(this->featSettings, out)) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+bool OpenTypeFEAT::IsValidFeatureId(uint32_t id) const {
+  return feature_ids.count(id);
+}
+
+bool OpenTypeFEAT::FeatureDefn::ParsePart(Buffer& table) {
+  OpenTypeNAME* name = static_cast<OpenTypeNAME*>(
+      parent->GetFont()->GetTypedTable(OTS_TAG_NAME));
+  if (!name) {
+    return parent->Error("FeatureDefn: Required name table is missing");
+  }
+
+  if (parent->version >> 16 >= 2 && !table.ReadU32(&this->id)) {
+    return parent->Error("FeatureDefn: Failed to read id");
+  }
+  if (parent->version >> 16 == 1)  {
+    uint16_t id;
+    if (!table.ReadU16(&id)) {
+      return parent->Error("FeatureDefn: Failed to read id");
+    }
+    this->id = id;
+  }
+  if (!table.ReadU16(&this->numSettings)) {
+    return parent->Error("FeatureDefn: Failed to read numSettings");
+  }
+  if (parent->version >> 16 >= 2) {
+    if (!table.ReadU16(&this->reserved)) {
+      return parent->Error("FeatureDefn: Failed to read reserved");
+    }
+    if (this->reserved != 0) {
+      parent->Warning("FeatureDefn: Nonzero reserved");
+    }
+  }
+  if (!table.ReadU32(&this->offset)) {
+    return parent->Error("FeatureDefn: Failed to read offset");
+  }  // validity of offset verified in OpenTypeFEAT::Parse
+  if (!table.ReadU16(&this->flags)) {
+    return parent->Error("FeatureDefn: Failed to read flags");
+  }
+  if ((this->flags & RESERVED) != 0) {
+    this->flags &= ~RESERVED;
+    parent->Warning("FeatureDefn: Nonzero (flags & 0x%x) repaired", RESERVED);
+  }
+  if (this->flags & HAS_DEFAULT_SETTING &&
+      (this->flags & DEFAULT_SETTING) >= this->numSettings) {
+    return parent->Error("FeatureDefn: (flags & 0x%x) is set but (flags & 0x%x "
+                         "is not a valid setting index", HAS_DEFAULT_SETTING,
+                         DEFAULT_SETTING);
+  }
+  if (!table.ReadU16(&this->label)) {
+    return parent->Error("FeatureDefn: Failed to read label");
+  }
+  if (!name->IsValidNameId(this->label)) {
+    if (this->id == 1 && name->IsValidNameId(this->label, true)) {
+      parent->Warning("FeatureDefn: Missing NameRecord repaired for feature"
+                      " with id=%u, label=%u", this->id, this->label);
+    }
+    else {
+      return parent->Error("FeatureDefn: Invalid label");
+    }
+  }
+  return true;
+}
+
+bool OpenTypeFEAT::FeatureDefn::SerializePart(OTSStream* out) const {
+  if ((parent->version >> 16 >= 2 && !out->WriteU32(this->id)) ||
+      (parent->version >> 16 == 1 &&
+       !out->WriteU16(static_cast<uint16_t>(this->id))) ||
+      !out->WriteU16(this->numSettings) ||
+      (parent->version >> 16 >= 2 && !out->WriteU16(this->reserved)) ||
+      !out->WriteU32(this->offset) ||
+      !out->WriteU16(this->flags) ||
+      !out->WriteU16(this->label)) {
+    return parent->Error("FeatureDefn: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeFEAT::FeatureSettingDefn::ParsePart(Buffer& table, bool used) {
+  OpenTypeNAME* name = static_cast<OpenTypeNAME*>(
+      parent->GetFont()->GetTypedTable(OTS_TAG_NAME));
+  if (!name) {
+    return parent->Error("FeatureSettingDefn: Required name table is missing");
+  }
+
+  if (!table.ReadS16(&this->value)) {
+    return parent->Error("FeatureSettingDefn: Failed to read value");
+  }
+  if (!table.ReadU16(&this->label) ||
+      (used && !name->IsValidNameId(this->label))) {
+    return parent->Error("FeatureSettingDefn: Failed to read valid label");
+  }
+  return true;
+}
+
+bool OpenTypeFEAT::FeatureSettingDefn::SerializePart(OTSStream* out) const {
+  if (!out->WriteS16(this->value) ||
+      !out->WriteU16(this->label)) {
+    return parent->Error("FeatureSettingDefn: Failed to write");
+  }
+  return true;
+}
+
+}  // namespace ots
diff --git a/gfx/ots/src/feat.h b/gfx/ots/src/feat.h
new file mode 100644
index 000000000..29c2656ff
--- /dev/null
+++ b/gfx/ots/src/feat.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef OTS_FEAT_H_
+#define OTS_FEAT_H_
+
+#include <vector>
+#include <unordered_set>
+
+#include "ots.h"
+#include "graphite.h"
+
+namespace ots {
+
+class OpenTypeFEAT : public Table {
+ public:
+  explicit OpenTypeFEAT(Font* font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length);
+  bool Serialize(OTSStream* out);
+  bool IsValidFeatureId(uint32_t id) const;
+
+ private:
+  struct FeatureDefn : public TablePart<OpenTypeFEAT> {
+    explicit FeatureDefn(OpenTypeFEAT* parent)
+        : TablePart<OpenTypeFEAT>(parent) { }
+    bool ParsePart(Buffer& table);
+    bool SerializePart(OTSStream* out) const;
+    uint32_t id;
+    uint16_t numSettings;
+    uint16_t reserved;
+    uint32_t offset;
+    uint16_t flags;
+    static const uint16_t HAS_DEFAULT_SETTING = 0x4000;
+    static const uint16_t RESERVED = 0x3F00;
+    static const uint16_t DEFAULT_SETTING = 0x00FF;
+    uint16_t label;
+  };
+  struct FeatureSettingDefn : public TablePart<OpenTypeFEAT> {
+    explicit FeatureSettingDefn(OpenTypeFEAT* parent)
+        : TablePart<OpenTypeFEAT>(parent) { }
+    bool ParsePart(Buffer& table) { return ParsePart(table, true); }
+    bool ParsePart(Buffer& table, bool used);
+    bool SerializePart(OTSStream* out) const;
+    int16_t value;
+    uint16_t label;
+  };
+  uint32_t version;
+  uint16_t numFeat;
+  uint16_t reserved;
+  uint32_t reserved2;
+  std::vector<FeatureDefn> features;
+  std::vector<FeatureSettingDefn> featSettings;
+  std::unordered_set<uint32_t> feature_ids;
+};
+
+}  // namespace ots
+
+#endif  // OTS_FEAT_H_
diff --git a/gfx/ots/src/fpgm.cc b/gfx/ots/src/fpgm.cc
index faa9a2392..bb52b367f 100644
--- a/gfx/ots/src/fpgm.cc
+++ b/gfx/ots/src/fpgm.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,53 +7,36 @@
 // fpgm - Font Program
 // http://www.microsoft.com/typography/otspec/fpgm.htm
 
-#define TABLE_NAME "fpgm"
-
 namespace ots {
 
-bool ots_fpgm_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeFPGM::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypeFPGM *fpgm = new OpenTypeFPGM;
-  font->fpgm = fpgm;
-
   if (length >= 128 * 1024u) {
-    return OTS_FAILURE_MSG("length (%ld) > 120", length);  // almost all fpgm tables are less than 5k bytes.
+    return Error("length (%ld) > 120", length);  // almost all fpgm tables are less than 5k bytes.
   }
 
   if (!table.Skip(length)) {
-    return OTS_FAILURE_MSG("Bad fpgm length");
+    return Error("Bad table length");
   }
 
-  fpgm->data = data;
-  fpgm->length = length;
+  this->data = data;
+  this->length = length;
   return true;
 }
 
-bool ots_fpgm_should_serialise(Font *font) {
-  if (!font->glyf) return false;  // this table is not for CFF fonts.
-  return font->fpgm != NULL;
-}
-
-bool ots_fpgm_serialise(OTSStream *out, Font *font) {
-  const OpenTypeFPGM *fpgm = font->fpgm;
-
-  if (!out->Write(fpgm->data, fpgm->length)) {
-    return OTS_FAILURE_MSG("Failed to write fpgm");
+bool OpenTypeFPGM::Serialize(OTSStream *out) {
+  if (!out->Write(this->data, this->length)) {
+    return Error("Failed to write fpgm table");
   }
 
   return true;
 }
 
-void ots_fpgm_reuse(Font *font, Font *other) {
-  font->fpgm = other->fpgm;
-  font->fpgm_reused = true;
-}
-
-void ots_fpgm_free(Font *font) {
-  delete font->fpgm;
+bool OpenTypeFPGM::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for CFF fonts.
+         GetFont()->GetTable(OTS_TAG_GLYF) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/fpgm.h b/gfx/ots/src/fpgm.h
index 8fabac36d..9ed6b34bf 100644
--- a/gfx/ots/src/fpgm.h
+++ b/gfx/ots/src/fpgm.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,7 +9,16 @@
 
 namespace ots {
 
-struct OpenTypeFPGM {
+class OpenTypeFPGM : public Table {
+ public:
+  explicit OpenTypeFPGM(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
   const uint8_t *data;
   uint32_t length;
 };
diff --git a/gfx/ots/src/gasp.cc b/gfx/ots/src/gasp.cc
index 5ebf5d84b..2a03c831f 100644
--- a/gfx/ots/src/gasp.cc
+++ b/gfx/ots/src/gasp.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,113 +7,78 @@
 // gasp - Grid-fitting And Scan-conversion Procedure
 // http://www.microsoft.com/typography/otspec/gasp.htm
 
-#define TABLE_NAME "gasp"
-
-#define DROP_THIS_TABLE(...) \
-  do { \
-    OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \
-    OTS_FAILURE_MSG("Table discarded"); \
-    delete font->gasp; \
-    font->gasp = 0; \
-  } while (0)
-
 namespace ots {
 
-bool ots_gasp_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeGASP::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypeGASP *gasp = new OpenTypeGASP;
-  font->gasp = gasp;
-
   uint16_t num_ranges = 0;
-  if (!table.ReadU16(&gasp->version) ||
+  if (!table.ReadU16(&this->version) ||
       !table.ReadU16(&num_ranges)) {
-    return OTS_FAILURE_MSG("Failed to read table header");
+    return Error("Failed to read table header");
   }
 
-  if (gasp->version > 1) {
+  if (this->version > 1) {
     // Lots of Linux fonts have bad version numbers...
-    DROP_THIS_TABLE("bad version: %u", gasp->version);
-    return true;
+    return Drop("Unsupported version: %u", this->version);
   }
 
   if (num_ranges == 0) {
-    DROP_THIS_TABLE("num_ranges is zero");
-    return true;
+    return Drop("numRanges is zero");
   }
 
-  gasp->gasp_ranges.reserve(num_ranges);
+  this->gasp_ranges.reserve(num_ranges);
   for (unsigned i = 0; i < num_ranges; ++i) {
     uint16_t max_ppem = 0;
     uint16_t behavior = 0;
     if (!table.ReadU16(&max_ppem) ||
         !table.ReadU16(&behavior)) {
-      return OTS_FAILURE_MSG("Failed to read subrange %d", i);
+      return Error("Failed to read GASPRANGE %d", i);
     }
-    if ((i > 0) && (gasp->gasp_ranges[i - 1].first >= max_ppem)) {
+    if ((i > 0) && (this->gasp_ranges[i - 1].first >= max_ppem)) {
       // The records in the gaspRange[] array must be sorted in order of
       // increasing rangeMaxPPEM value.
-      DROP_THIS_TABLE("ranges are not sorted");
-      return true;
+      return Drop("Ranges are not sorted");
     }
     if ((i == num_ranges - 1u) &&  // never underflow.
         (max_ppem != 0xffffu)) {
-      DROP_THIS_TABLE("The last record should be 0xFFFF as a sentinel value "
+      return Drop("The last record should be 0xFFFF as a sentinel value "
                   "for rangeMaxPPEM");
-      return true;
     }
 
     if (behavior >> 8) {
-      OTS_WARNING("undefined bits are used: %x", behavior);
+      Warning("Undefined bits are used: %x", behavior);
       // mask undefined bits.
       behavior &= 0x000fu;
     }
 
-    if (gasp->version == 0 && (behavior >> 2) != 0) {
-      OTS_WARNING("changed the version number to 1");
-      gasp->version = 1;
+    if (this->version == 0 && (behavior >> 2) != 0) {
+      Warning("Changed the version number to 1");
+      this->version = 1;
     }
 
-    gasp->gasp_ranges.push_back(std::make_pair(max_ppem, behavior));
+    this->gasp_ranges.push_back(std::make_pair(max_ppem, behavior));
   }
 
   return true;
 }
 
-bool ots_gasp_should_serialise(Font *font) {
-  return font->gasp != NULL;
-}
-
-bool ots_gasp_serialise(OTSStream *out, Font *font) {
-  const OpenTypeGASP *gasp = font->gasp;
-
-  const uint16_t num_ranges = static_cast<uint16_t>(gasp->gasp_ranges.size());
-  if (num_ranges != gasp->gasp_ranges.size() ||
-      !out->WriteU16(gasp->version) ||
+bool OpenTypeGASP::Serialize(OTSStream *out) {
+  const uint16_t num_ranges = static_cast<uint16_t>(this->gasp_ranges.size());
+  if (num_ranges != this->gasp_ranges.size() ||
+      !out->WriteU16(this->version) ||
       !out->WriteU16(num_ranges)) {
-    return OTS_FAILURE_MSG("failed to write gasp header");
+    return Error("Failed to write table header");
   }
 
   for (uint16_t i = 0; i < num_ranges; ++i) {
-    if (!out->WriteU16(gasp->gasp_ranges[i].first) ||
-        !out->WriteU16(gasp->gasp_ranges[i].second)) {
-      return OTS_FAILURE_MSG("Failed to write gasp subtable %d", i);
+    if (!out->WriteU16(this->gasp_ranges[i].first) ||
+        !out->WriteU16(this->gasp_ranges[i].second)) {
+      return Error("Failed to write GASPRANGE %d", i);
     }
   }
 
   return true;
 }
 
-void ots_gasp_reuse(Font *font, Font *other) {
-  font->gasp = other->gasp;
-  font->gasp_reused = true;
-}
-
-void ots_gasp_free(Font *font) {
-  delete font->gasp;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
-#undef DROP_THIS_TABLE
diff --git a/gfx/ots/src/gasp.h b/gfx/ots/src/gasp.h
index 48d7e7c16..ce9e987aa 100644
--- a/gfx/ots/src/gasp.h
+++ b/gfx/ots/src/gasp.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -13,7 +13,15 @@
 
 namespace ots {
 
-struct OpenTypeGASP {
+class OpenTypeGASP : public Table {
+ public:
+  explicit OpenTypeGASP(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
+ private:
   uint16_t version;
   // A array of (max PPEM, GASP behavior) pairs.
   std::vector<std::pair<uint16_t, uint16_t> > gasp_ranges;
diff --git a/gfx/ots/src/gdef.cc b/gfx/ots/src/gdef.cc
index 71c6fc592..01eab15c0 100644
--- a/gfx/ots/src/gdef.cc
+++ b/gfx/ots/src/gdef.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -15,8 +15,6 @@
 // GDEF - The Glyph Definition Table
 // http://www.microsoft.com/typography/otspec/gdef.htm
 
-#define TABLE_NAME "GDEF"
-
 namespace {
 
 // The maximum class value in class definition tables.
@@ -28,51 +26,48 @@ const uint16_t kMaxGlyphClassDefValue = 4;
 // ParseLigCaretListTable() for the reason.
 const uint16_t kMaxCaretValueFormat = 2;
 
-bool ParseGlyphClassDefTable(ots::Font *font, const uint8_t *data,
-                             size_t length, const uint16_t num_glyphs) {
-  return ots::ParseClassDefTable(font, data, length, num_glyphs,
-                                 kMaxGlyphClassDefValue);
-}
+}  // namespace
+
+namespace ots {
 
-bool ParseAttachListTable(ots::Font *font, const uint8_t *data,
-                          size_t length, const uint16_t num_glyphs) {
+bool OpenTypeGDEF::ParseAttachListTable(const uint8_t *data, size_t length) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_coverage = 0;
   uint16_t glyph_count = 0;
   if (!subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&glyph_count)) {
-    return OTS_FAILURE_MSG("Failed to read gdef header");
+    return Error("Failed to read gdef header");
   }
   const unsigned attach_points_end =
       2 * static_cast<unsigned>(glyph_count) + 4;
   if (attach_points_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE_MSG("Bad glyph count in gdef");
+    return Error("Bad glyph count in gdef");
   }
   if (offset_coverage == 0 || offset_coverage >= length ||
       offset_coverage < attach_points_end) {
-    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
+    return Error("Bad coverage offset %d", offset_coverage);
   }
-  if (glyph_count > num_glyphs) {
-    return OTS_FAILURE_MSG("Bad glyph count %u", glyph_count);
+  if (glyph_count > this->m_num_glyphs) {
+    return Error("Bad glyph count %u", glyph_count);
   }
 
   std::vector<uint16_t> attach_points;
   attach_points.resize(glyph_count);
   for (unsigned i = 0; i < glyph_count; ++i) {
     if (!subtable.ReadU16(&attach_points[i])) {
-      return OTS_FAILURE_MSG("Can't read attachment point %d", i);
+      return Error("Can't read attachment point %d", i);
     }
     if (attach_points[i] >= length ||
         attach_points[i] < attach_points_end) {
-      return OTS_FAILURE_MSG("Bad attachment point %d of %d", i, attach_points[i]);
+      return Error("Bad attachment point %d of %d", i, attach_points[i]);
     }
   }
 
   // Parse coverage table
-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
-                               length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE_MSG("Bad coverage table");
+  if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage,
+                               length - offset_coverage, this->m_num_glyphs)) {
+    return Error("Bad coverage table");
   }
 
   // Parse attach point table
@@ -80,20 +75,20 @@ bool ParseAttachListTable(ots::Font *font, const uint8_t *data,
     subtable.set_offset(attach_points[i]);
     uint16_t point_count = 0;
     if (!subtable.ReadU16(&point_count)) {
-      return OTS_FAILURE_MSG("Can't read point count %d", i);
+      return Error("Can't read point count %d", i);
     }
     if (point_count == 0) {
-      return OTS_FAILURE_MSG("zero point count %d", i);
+      return Error("zero point count %d", i);
     }
     uint16_t last_point_index = 0;
     uint16_t point_index = 0;
     for (unsigned j = 0; j < point_count; ++j) {
       if (!subtable.ReadU16(&point_index)) {
-        return OTS_FAILURE_MSG("Can't read point index %d in point %d", j, i);
+        return Error("Can't read point index %d in point %d", j, i);
       }
       // Contour point indeces are in increasing numerical order
       if (last_point_index != 0 && last_point_index >= point_index) {
-        return OTS_FAILURE_MSG("bad contour indeces: %u >= %u",
+        return Error("bad contour indeces: %u >= %u",
                     last_point_index, point_index);
       }
       last_point_index = point_index;
@@ -102,43 +97,42 @@ bool ParseAttachListTable(ots::Font *font, const uint8_t *data,
   return true;
 }
 
-bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data,
-                            size_t length, const uint16_t num_glyphs) {
+bool OpenTypeGDEF::ParseLigCaretListTable(const uint8_t *data, size_t length) {
   ots::Buffer subtable(data, length);
   uint16_t offset_coverage = 0;
   uint16_t lig_glyph_count = 0;
   if (!subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&lig_glyph_count)) {
-    return OTS_FAILURE_MSG("Can't read caret structure");
+    return Error("Can't read caret structure");
   }
   const unsigned lig_glyphs_end =
       2 * static_cast<unsigned>(lig_glyph_count) + 4;
   if (lig_glyphs_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE_MSG("Bad caret structure");
+    return Error("Bad caret structure");
   }
   if (offset_coverage == 0 || offset_coverage >= length ||
       offset_coverage < lig_glyphs_end) {
-    return OTS_FAILURE_MSG("Bad caret coverate offset %d", offset_coverage);
+    return Error("Bad caret coverate offset %d", offset_coverage);
   }
-  if (lig_glyph_count > num_glyphs) {
-    return OTS_FAILURE_MSG("bad ligature glyph count: %u", lig_glyph_count);
+  if (lig_glyph_count > this->m_num_glyphs) {
+    return Error("bad ligature glyph count: %u", lig_glyph_count);
   }
 
   std::vector<uint16_t> lig_glyphs;
   lig_glyphs.resize(lig_glyph_count);
   for (unsigned i = 0; i < lig_glyph_count; ++i) {
     if (!subtable.ReadU16(&lig_glyphs[i])) {
-      return OTS_FAILURE_MSG("Can't read ligature glyph location %d", i);
+      return Error("Can't read ligature glyph location %d", i);
     }
     if (lig_glyphs[i] >= length || lig_glyphs[i] < lig_glyphs_end) {
-      return OTS_FAILURE_MSG("Bad ligature glyph location %d in glyph %d", lig_glyphs[i], i);
+      return Error("Bad ligature glyph location %d in glyph %d", lig_glyphs[i], i);
     }
   }
 
   // Parse coverage table
-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
-                               length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE_MSG("Can't parse caret coverage table");
+  if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage,
+                               length - offset_coverage, this->m_num_glyphs)) {
+    return Error("Can't parse caret coverage table");
   }
 
   // Parse ligature glyph table
@@ -146,10 +140,10 @@ bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data,
     subtable.set_offset(lig_glyphs[i]);
     uint16_t caret_count = 0;
     if (!subtable.ReadU16(&caret_count)) {
-      return OTS_FAILURE_MSG("Can't read caret count for glyph %d", i);
+      return Error("Can't read caret count for glyph %d", i);
     }
     if (caret_count == 0) {
-      return OTS_FAILURE_MSG("bad caret value count: %u", caret_count);
+      return Error("bad caret value count: %u", caret_count);
     }
 
     std::vector<uint16_t> caret_value_offsets;
@@ -157,10 +151,10 @@ bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data,
     unsigned caret_value_offsets_end = 2 * static_cast<unsigned>(caret_count) + 2;
     for (unsigned j = 0; j < caret_count; ++j) {
       if (!subtable.ReadU16(&caret_value_offsets[j])) {
-        return OTS_FAILURE_MSG("Can't read caret offset %d for glyph %d", j, i);
+        return Error("Can't read caret offset %d for glyph %d", j, i);
       }
       if (caret_value_offsets[j] >= length || caret_value_offsets[j] < caret_value_offsets_end) {
-        return OTS_FAILURE_MSG("Bad caret offset %d for caret %d glyph %d", caret_value_offsets[j], j, i);
+        return Error("Bad caret offset %d for caret %d glyph %d", caret_value_offsets[j], j, i);
       }
     }
 
@@ -169,91 +163,81 @@ bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data,
       subtable.set_offset(lig_glyphs[i] + caret_value_offsets[j]);
       uint16_t caret_format = 0;
       if (!subtable.ReadU16(&caret_format)) {
-        return OTS_FAILURE_MSG("Can't read caret values table %d in glyph %d", j, i);
+        return Error("Can't read caret values table %d in glyph %d", j, i);
       }
       // TODO(bashi): We only support caret value format 1 and 2 for now
       // because there are no fonts which contain caret value format 3
       // as far as we investigated.
       if (caret_format == 0 || caret_format > kMaxCaretValueFormat) {
-        return OTS_FAILURE_MSG("bad caret value format: %u", caret_format);
+        return Error("bad caret value format: %u", caret_format);
       }
       // CaretValueFormats contain a 2-byte field which could be
       // arbitrary value.
       if (!subtable.Skip(2)) {
-        return OTS_FAILURE_MSG("Bad caret value table structure %d in glyph %d", j, i);
+        return Error("Bad caret value table structure %d in glyph %d", j, i);
       }
     }
   }
   return true;
 }
 
-bool ParseMarkAttachClassDefTable(ots::Font *font, const uint8_t *data,
-                                  size_t length, const uint16_t num_glyphs) {
-  return ots::ParseClassDefTable(font, data, length, num_glyphs, kMaxClassDefValue);
-}
-
-bool ParseMarkGlyphSetsDefTable(ots::Font *font, const uint8_t *data,
-                                size_t length, const uint16_t num_glyphs) {
+bool OpenTypeGDEF::ParseMarkGlyphSetsDefTable(const uint8_t *data, size_t length) {
   ots::Buffer subtable(data, length);
   uint16_t format = 0;
   uint16_t mark_set_count = 0;
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&mark_set_count)) {
-    return OTS_FAILURE_MSG("Can' read mark glyph table structure");
+    return Error("Can' read mark glyph table structure");
   }
   if (format != 1) {
-    return OTS_FAILURE_MSG("bad mark glyph set table format: %u", format);
+    return Error("bad mark glyph set table format: %u", format);
   }
 
   const unsigned mark_sets_end = 2 * static_cast<unsigned>(mark_set_count) + 4;
   if (mark_sets_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE_MSG("Bad mark_set %d", mark_sets_end);
+    return Error("Bad mark_set %d", mark_sets_end);
   }
   for (unsigned i = 0; i < mark_set_count; ++i) {
     uint32_t offset_coverage = 0;
     if (!subtable.ReadU32(&offset_coverage)) {
-      return OTS_FAILURE_MSG("Can't read covrage location for mark set %d", i);
+      return Error("Can't read covrage location for mark set %d", i);
     }
     if (offset_coverage >= length ||
         offset_coverage < mark_sets_end) {
-      return OTS_FAILURE_MSG("Bad coverage location %d for mark set %d", offset_coverage, i);
+      return Error("Bad coverage location %d for mark set %d", offset_coverage, i);
     }
-    if (!ots::ParseCoverageTable(font, data + offset_coverage,
-                                 length - offset_coverage, num_glyphs)) {
-      return OTS_FAILURE_MSG("Failed to parse coverage table for mark set %d", i);
+    if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage,
+                                 length - offset_coverage, this->m_num_glyphs)) {
+      return Error("Failed to parse coverage table for mark set %d", i);
     }
   }
-  font->gdef->num_mark_glyph_sets = mark_set_count;
+  this->num_mark_glyph_sets = mark_set_count;
   return true;
 }
 
-}  // namespace
-
-namespace ots {
+bool OpenTypeGDEF::Parse(const uint8_t *data, size_t length) {
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
 
-bool ots_gdef_parse(Font *font, const uint8_t *data, size_t length) {
   // Grab the number of glyphs in the font from the maxp table to check
   // GlyphIDs in GDEF table.
-  if (!font->maxp) {
-    return OTS_FAILURE_MSG("No maxp table in font, needed by GDEF");
+  if (!maxp) {
+    return Error("No maxp table in font, needed by GDEF");
   }
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  this->m_num_glyphs = maxp->num_glyphs;
 
   Buffer table(data, length);
 
-  OpenTypeGDEF *gdef = new OpenTypeGDEF;
-  font->gdef = gdef;
-
   uint32_t version = 0;
   if (!table.ReadU32(&version)) {
-    return OTS_FAILURE_MSG("Incomplete table");
+    return Error("Incomplete table");
   }
   if (version < 0x00010000 || version == 0x00010001) {
-    return OTS_FAILURE_MSG("Bad version");
+    return Error("Bad version");
   }
 
   if (version >= 0x00010002) {
-    gdef->version_2 = true;
+    this->version_2 = true;
   }
 
   uint16_t offset_glyph_class_def = 0;
@@ -264,110 +248,91 @@ bool ots_gdef_parse(Font *font, const uint8_t *data, size_t length) {
       !table.ReadU16(&offset_attach_list) ||
       !table.ReadU16(&offset_lig_caret_list) ||
       !table.ReadU16(&offset_mark_attach_class_def)) {
-    return OTS_FAILURE_MSG("Incomplete table");
+    return Error("Incomplete table");
   }
   uint16_t offset_mark_glyph_sets_def = 0;
-  if (gdef->version_2) {
+  if (this->version_2) {
     if (!table.ReadU16(&offset_mark_glyph_sets_def)) {
-      return OTS_FAILURE_MSG("Incomplete table");
+      return Error("Incomplete table");
     }
   }
 
   unsigned gdef_header_end = 4 + 4 * 2;
-  if (gdef->version_2)
+  if (this->version_2)
     gdef_header_end += 2;
 
   // Parse subtables
   if (offset_glyph_class_def) {
     if (offset_glyph_class_def >= length ||
         offset_glyph_class_def < gdef_header_end) {
-      return OTS_FAILURE_MSG("Invalid offset to glyph classes");
+      return Error("Invalid offset to glyph classes");
     }
-    if (!ParseGlyphClassDefTable(font, data + offset_glyph_class_def,
+    if (!ots::ParseClassDefTable(GetFont(), data + offset_glyph_class_def,
                                  length - offset_glyph_class_def,
-                                 num_glyphs)) {
-      return OTS_FAILURE_MSG("Invalid glyph classes");
+                                 this->m_num_glyphs, kMaxGlyphClassDefValue)) {
+      return Error("Invalid glyph classes");
     }
-    gdef->has_glyph_class_def = true;
+    this->has_glyph_class_def = true;
   }
 
   if (offset_attach_list) {
     if (offset_attach_list >= length ||
         offset_attach_list < gdef_header_end) {
-      return OTS_FAILURE_MSG("Invalid offset to attachment list");
+      return Error("Invalid offset to attachment list");
     }
-    if (!ParseAttachListTable(font, data + offset_attach_list,
-                              length - offset_attach_list,
-                              num_glyphs)) {
-      return OTS_FAILURE_MSG("Invalid attachment list");
+    if (!ParseAttachListTable(data + offset_attach_list,
+                              length - offset_attach_list)) {
+      return Error("Invalid attachment list");
     }
   }
 
   if (offset_lig_caret_list) {
     if (offset_lig_caret_list >= length ||
         offset_lig_caret_list < gdef_header_end) {
-      return OTS_FAILURE_MSG("Invalid offset to ligature caret list");
+      return Error("Invalid offset to ligature caret list");
     }
-    if (!ParseLigCaretListTable(font, data + offset_lig_caret_list,
-                              length - offset_lig_caret_list,
-                              num_glyphs)) {
-      return OTS_FAILURE_MSG("Invalid ligature caret list");
+    if (!ParseLigCaretListTable(data + offset_lig_caret_list,
+                                length - offset_lig_caret_list)) {
+      return Error("Invalid ligature caret list");
     }
   }
 
   if (offset_mark_attach_class_def) {
     if (offset_mark_attach_class_def >= length ||
         offset_mark_attach_class_def < gdef_header_end) {
-      return OTS_FAILURE_MSG("Invalid offset to mark attachment list");
+      return Error("Invalid offset to mark attachment list");
     }
-    if (!ParseMarkAttachClassDefTable(font,
-                                      data + offset_mark_attach_class_def,
-                                      length - offset_mark_attach_class_def,
-                                      num_glyphs)) {
-      return OTS_FAILURE_MSG("Invalid mark attachment list");
+    if (!ots::ParseClassDefTable(GetFont(),
+                                 data + offset_mark_attach_class_def,
+                                 length - offset_mark_attach_class_def,
+                                 this->m_num_glyphs, kMaxClassDefValue)) {
+      return Error("Invalid mark attachment list");
     }
-    gdef->has_mark_attachment_class_def = true;
+    this->has_mark_attachment_class_def = true;
   }
 
   if (offset_mark_glyph_sets_def) {
     if (offset_mark_glyph_sets_def >= length ||
         offset_mark_glyph_sets_def < gdef_header_end) {
-      return OTS_FAILURE_MSG("invalid offset to mark glyph sets");
+      return Error("invalid offset to mark glyph sets");
     }
-    if (!ParseMarkGlyphSetsDefTable(font,
-                                    data + offset_mark_glyph_sets_def,
-                                    length - offset_mark_glyph_sets_def,
-                                    num_glyphs)) {
-      return OTS_FAILURE_MSG("Invalid mark glyph sets");
+    if (!ParseMarkGlyphSetsDefTable(data + offset_mark_glyph_sets_def,
+                                    length - offset_mark_glyph_sets_def)) {
+      return Error("Invalid mark glyph sets");
     }
-    gdef->has_mark_glyph_sets_def = true;
+    this->has_mark_glyph_sets_def = true;
   }
-  gdef->data = data;
-  gdef->length = length;
+  this->m_data = data;
+  this->m_length = length;
   return true;
 }
 
-bool ots_gdef_should_serialise(Font *font) {
-  return font->gdef != NULL && font->gdef->data != NULL;
-}
-
-bool ots_gdef_serialise(OTSStream *out, Font *font) {
-  if (!out->Write(font->gdef->data, font->gdef->length)) {
-    return OTS_FAILURE_MSG("Failed to write GDEF table");
+bool OpenTypeGDEF::Serialize(OTSStream *out) {
+  if (!out->Write(this->m_data, this->m_length)) {
+    return Error("Failed to write table");
   }
 
   return true;
 }
 
-void ots_gdef_reuse(Font *font, Font *other) {
-  font->gdef = other->gdef;
-  font->gdef_reused = true;
-}
-
-void ots_gdef_free(Font *font) {
-  delete font->gdef;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/gdef.h b/gfx/ots/src/gdef.h
index f46f419c7..686956e84 100644
--- a/gfx/ots/src/gdef.h
+++ b/gfx/ots/src/gdef.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,25 +9,37 @@
 
 namespace ots {
 
-struct OpenTypeGDEF {
-  OpenTypeGDEF()
-      : version_2(false),
+class OpenTypeGDEF : public Table {
+ public:
+  explicit OpenTypeGDEF(Font *font, uint32_t tag)
+      : Table(font, tag, tag),
+        version_2(false),
         has_glyph_class_def(false),
         has_mark_attachment_class_def(false),
         has_mark_glyph_sets_def(false),
         num_mark_glyph_sets(0),
-        data(NULL),
-        length(0) {
+        m_data(NULL),
+        m_length(0),
+        m_num_glyphs(0) {
   }
 
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   bool version_2;
   bool has_glyph_class_def;
   bool has_mark_attachment_class_def;
   bool has_mark_glyph_sets_def;
   uint16_t num_mark_glyph_sets;
 
-  const uint8_t *data;
-  size_t length;
+ private:
+  bool ParseAttachListTable(const uint8_t *data, size_t length);
+  bool ParseLigCaretListTable(const uint8_t *data, size_t length);
+  bool ParseMarkGlyphSetsDefTable(const uint8_t *data, size_t length);
+
+  const uint8_t *m_data;
+  size_t m_length;
+  uint16_t m_num_glyphs;
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/glat.cc b/gfx/ots/src/glat.cc
new file mode 100644
index 000000000..b7a2c47c4
--- /dev/null
+++ b/gfx/ots/src/glat.cc
@@ -0,0 +1,447 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "glat.h"
+
+#include "gloc.h"
+#include "mozilla/Compression.h"
+#include <list>
+
+namespace ots {
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT_v1
+// -----------------------------------------------------------------------------
+
+bool OpenTypeGLAT_v1::Parse(const uint8_t* data, size_t length) {
+  Buffer table(data, length);
+  OpenTypeGLOC* gloc = static_cast<OpenTypeGLOC*>(
+      GetFont()->GetTypedTable(OTS_TAG_GLOC));
+  if (!gloc) {
+    return DropGraphite("Required Gloc table is missing");
+  }
+
+  if (!table.ReadU32(&this->version) || this->version >> 16 != 1) {
+    return DropGraphite("Failed to read version");
+  }
+
+  const std::vector<uint32_t>& locations = gloc->GetLocations();
+  if (locations.empty()) {
+    return DropGraphite("No locations from Gloc table");
+  }
+  std::list<uint32_t> unverified(locations.begin(), locations.end());
+  while (table.remaining()) {
+    GlatEntry entry(this);
+    if (table.offset() > unverified.front()) {
+      return DropGraphite("Offset check failed for a GlatEntry");
+    }
+    if (table.offset() == unverified.front()) {
+      unverified.pop_front();
+    }
+    if (unverified.empty()) {
+      return DropGraphite("Expected more locations");
+    }
+    if (!entry.ParsePart(table)) {
+      return DropGraphite("Failed to read a GlatEntry");
+    }
+    this->entries.push_back(entry);
+  }
+
+  if (unverified.size() != 1 || unverified.front() != table.offset()) {
+    return DropGraphite("%zu location(s) could not be verified", unverified.size());
+  }
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v1::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      !SerializeParts(this->entries, out)) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v1::GlatEntry::ParsePart(Buffer& table) {
+  if (!table.ReadU8(&this->attNum)) {
+    return parent->Error("GlatEntry: Failed to read attNum");
+  }
+  if (!table.ReadU8(&this->num)) {
+    return parent->Error("GlatEntry: Failed to read num");
+  }
+
+  //this->attributes.resize(this->num);
+  for (int i = 0; i < this->num; ++i) {
+    this->attributes.emplace_back();
+    if (!table.ReadS16(&this->attributes[i])) {
+      return parent->Error("GlatEntry: Failed to read attribute %u", i);
+    }
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v1::GlatEntry::SerializePart(OTSStream* out) const {
+  if (!out->WriteU8(this->attNum) ||
+      !out->WriteU8(this->num) ||
+      !SerializeParts(this->attributes, out)) {
+    return parent->Error("GlatEntry: Failed to write");
+  }
+  return true;
+}
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT_v2
+// -----------------------------------------------------------------------------
+
+bool OpenTypeGLAT_v2::Parse(const uint8_t* data, size_t length) {
+  Buffer table(data, length);
+  OpenTypeGLOC* gloc = static_cast<OpenTypeGLOC*>(
+      GetFont()->GetTypedTable(OTS_TAG_GLOC));
+  if (!gloc) {
+    return DropGraphite("Required Gloc table is missing");
+  }
+
+  if (!table.ReadU32(&this->version) || this->version >> 16 != 1) {
+    return DropGraphite("Failed to read version");
+  }
+
+  const std::vector<uint32_t>& locations = gloc->GetLocations();
+  if (locations.empty()) {
+    return DropGraphite("No locations from Gloc table");
+  }
+  std::list<uint32_t> unverified(locations.begin(), locations.end());
+  while (table.remaining()) {
+    GlatEntry entry(this);
+    if (table.offset() > unverified.front()) {
+      return DropGraphite("Offset check failed for a GlatEntry");
+    }
+    if (table.offset() == unverified.front()) {
+      unverified.pop_front();
+    }
+    if (unverified.empty()) {
+      return DropGraphite("Expected more locations");
+    }
+    if (!entry.ParsePart(table)) {
+      return DropGraphite("Failed to read a GlatEntry");
+    }
+    this->entries.push_back(entry);
+  }
+
+  if (unverified.size() != 1 || unverified.front() != table.offset()) {
+    return DropGraphite("%zu location(s) could not be verified", unverified.size());
+  }
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v2::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      !SerializeParts(this->entries, out)) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v2::GlatEntry::ParsePart(Buffer& table) {
+  if (!table.ReadS16(&this->attNum)) {
+    return parent->Error("GlatEntry: Failed to read attNum");
+  }
+  if (!table.ReadS16(&this->num) || this->num < 0) {
+    return parent->Error("GlatEntry: Failed to read valid num");
+  }
+
+  //this->attributes.resize(this->num);
+  for (int i = 0; i < this->num; ++i) {
+    this->attributes.emplace_back();
+    if (!table.ReadS16(&this->attributes[i])) {
+      return parent->Error("GlatEntry: Failed to read attribute %u", i);
+    }
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v2::GlatEntry::SerializePart(OTSStream* out) const {
+  if (!out->WriteS16(this->attNum) ||
+      !out->WriteS16(this->num) ||
+      !SerializeParts(this->attributes, out)) {
+    return parent->Error("GlatEntry: Failed to write");
+  }
+  return true;
+}
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT_v3
+// -----------------------------------------------------------------------------
+
+bool OpenTypeGLAT_v3::Parse(const uint8_t* data, size_t length,
+                            bool prevent_decompression) {
+  Buffer table(data, length);
+  OpenTypeGLOC* gloc = static_cast<OpenTypeGLOC*>(
+      GetFont()->GetTypedTable(OTS_TAG_GLOC));
+  if (!gloc) {
+    return DropGraphite("Required Gloc table is missing");
+  }
+
+  if (!table.ReadU32(&this->version) || this->version >> 16 != 3) {
+    return DropGraphite("Failed to read version");
+  }
+  if (!table.ReadU32(&this->compHead)) {
+    return DropGraphite("Failed to read compression header");
+  }
+  switch ((this->compHead & SCHEME) >> 27) {
+    case 0:  // uncompressed
+      break;
+    case 1: {  // lz4
+      if (prevent_decompression) {
+        return DropGraphite("Illegal nested compression");
+      }
+      std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
+      size_t outputSize = 0;
+      bool ret = mozilla::Compression::LZ4::decompressPartial(
+          reinterpret_cast<const char*>(data + table.offset()),
+          table.remaining(),  // input buffer size (input size + padding)
+          reinterpret_cast<char*>(decompressed.data()),
+          decompressed.size(),  // target output size
+          &outputSize);  // return output size
+      if (!ret || outputSize != decompressed.size()) {
+        return DropGraphite("Decompression failed");
+      }
+      return this->Parse(decompressed.data(), decompressed.size(), true);
+    }
+    default:
+      return DropGraphite("Unknown compression scheme");
+  }
+  if (this->compHead & RESERVED) {
+    Warning("Nonzero reserved");
+  }
+
+  const std::vector<uint32_t>& locations = gloc->GetLocations();
+  if (locations.empty()) {
+    return DropGraphite("No locations from Gloc table");
+  }
+  std::list<uint32_t> unverified(locations.begin(), locations.end());
+  //this->entries.resize(locations.size() - 1, this);
+  for (size_t i = 0; i < locations.size() - 1; ++i) {
+    this->entries.emplace_back(this);
+    if (table.offset() != unverified.front()) {
+      return DropGraphite("Offset check failed for a GlyphAttrs");
+    }
+    unverified.pop_front();
+    if (!this->entries[i].ParsePart(table,
+                                    unverified.front() - table.offset())) {
+        // unverified.front() is guaranteed to exist because of the number of
+        // iterations of this loop
+      return DropGraphite("Failed to read a GlyphAttrs");
+    }
+  }
+
+  if (unverified.size() != 1 || unverified.front() != table.offset()) {
+    return DropGraphite("%zu location(s) could not be verified", unverified.size());
+  }
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      !out->WriteU32(this->compHead) ||
+      !SerializeParts(this->entries, out)) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::ParsePart(Buffer& table, const size_t size) {
+  size_t init_offset = table.offset();
+  if (parent->compHead & OCTABOXES && !octabox.ParsePart(table)) {
+    // parent->flags & 0b1: octaboxes are present flag
+    return parent->Error("GlyphAttrs: Failed to read octabox");
+  }
+
+  while (table.offset() < init_offset + size) {
+    GlatEntry entry(parent);
+    if (!entry.ParsePart(table)) {
+      return parent->Error("GlyphAttrs: Failed to read a GlatEntry");
+    }
+    this->entries.push_back(entry);
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::SerializePart(OTSStream* out) const {
+  if ((parent->compHead & OCTABOXES && !octabox.SerializePart(out)) ||
+      !SerializeParts(this->entries, out)) {
+    return parent->Error("GlyphAttrs: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::
+OctaboxMetrics::ParsePart(Buffer& table) {
+  if (!table.ReadU16(&this->subbox_bitmap)) {
+    return parent->Error("OctaboxMetrics: Failed to read subbox_bitmap");
+  }
+  if (!table.ReadU8(&this->diag_neg_min)) {
+    return parent->Error("OctaboxMetrics: Failed to read diag_neg_min");
+  }
+  if (!table.ReadU8(&this->diag_neg_max) ||
+      this->diag_neg_max < this->diag_neg_min) {
+    return parent->Error("OctaboxMetrics: Failed to read valid diag_neg_max");
+  }
+  if (!table.ReadU8(&this->diag_pos_min)) {
+    return parent->Error("OctaboxMetrics: Failed to read diag_pos_min");
+  }
+  if (!table.ReadU8(&this->diag_pos_max) ||
+      this->diag_pos_max < this->diag_pos_min) {
+    return parent->Error("OctaboxMetrics: Failed to read valid diag_pos_max");
+  }
+
+  unsigned subboxes_len = 0;  // count of 1's in this->subbox_bitmap
+  for (uint16_t i = this->subbox_bitmap; i; i >>= 1) {
+    if (i & 1) {
+      ++subboxes_len;
+    }
+  }
+  //this->subboxes.resize(subboxes_len, parent);
+  for (unsigned i = 0; i < subboxes_len; i++) {
+    this->subboxes.emplace_back(parent);
+    if (!this->subboxes[i].ParsePart(table)) {
+      return parent->Error("OctaboxMetrics: Failed to read subbox[%u]", i);
+    }
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::
+OctaboxMetrics::SerializePart(OTSStream* out) const {
+  if (!out->WriteU16(this->subbox_bitmap) ||
+      !out->WriteU8(this->diag_neg_min) ||
+      !out->WriteU8(this->diag_neg_max) ||
+      !out->WriteU8(this->diag_pos_min) ||
+      !out->WriteU8(this->diag_pos_max) ||
+      !SerializeParts(this->subboxes, out)) {
+    return parent->Error("OctaboxMetrics: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::OctaboxMetrics::
+SubboxEntry::ParsePart(Buffer& table) {
+  if (!table.ReadU8(&this->left)) {
+    return parent->Error("SubboxEntry: Failed to read left");
+  }
+  if (!table.ReadU8(&this->right) || this->right < this->left) {
+    return parent->Error("SubboxEntry: Failed to read valid right");
+  }
+  if (!table.ReadU8(&this->bottom)) {
+    return parent->Error("SubboxEntry: Failed to read bottom");
+  }
+  if (!table.ReadU8(&this->top) || this->top < this->bottom) {
+    return parent->Error("SubboxEntry: Failed to read valid top");
+  }
+  if (!table.ReadU8(&this->diag_pos_min)) {
+    return parent->Error("SubboxEntry: Failed to read diag_pos_min");
+  }
+  if (!table.ReadU8(&this->diag_pos_max) ||
+      this->diag_pos_max < this->diag_pos_min) {
+    return parent->Error("SubboxEntry: Failed to read valid diag_pos_max");
+  }
+  if (!table.ReadU8(&this->diag_neg_min)) {
+    return parent->Error("SubboxEntry: Failed to read diag_neg_min");
+  }
+  if (!table.ReadU8(&this->diag_neg_max) ||
+      this->diag_neg_max < this->diag_neg_min) {
+    return parent->Error("SubboxEntry: Failed to read valid diag_neg_max");
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::OctaboxMetrics::
+SubboxEntry::SerializePart(OTSStream* out) const {
+  if (!out->WriteU8(this->left) ||
+      !out->WriteU8(this->right) ||
+      !out->WriteU8(this->bottom) ||
+      !out->WriteU8(this->top) ||
+      !out->WriteU8(this->diag_pos_min) ||
+      !out->WriteU8(this->diag_pos_max) ||
+      !out->WriteU8(this->diag_neg_min) ||
+      !out->WriteU8(this->diag_neg_max)) {
+    return parent->Error("SubboxEntry: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::
+GlatEntry::ParsePart(Buffer& table) {
+  if (!table.ReadS16(&this->attNum)) {
+    return parent->Error("GlatEntry: Failed to read attNum");
+  }
+  if (!table.ReadS16(&this->num) || this->num < 0) {
+    return parent->Error("GlatEntry: Failed to read valid num");
+  }
+
+  //this->attributes.resize(this->num);
+  for (int i = 0; i < this->num; ++i) {
+    this->attributes.emplace_back();
+    if (!table.ReadS16(&this->attributes[i])) {
+      return parent->Error("GlatEntry: Failed to read attribute %u", i);
+    }
+  }
+  return true;
+}
+
+bool OpenTypeGLAT_v3::GlyphAttrs::
+GlatEntry::SerializePart(OTSStream* out) const {
+  if (!out->WriteS16(this->attNum) ||
+      !out->WriteS16(this->num) ||
+      !SerializeParts(this->attributes, out)) {
+    return parent->Error("GlatEntry: Failed to write");
+  }
+  return true;
+}
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT
+// -----------------------------------------------------------------------------
+
+bool OpenTypeGLAT::Parse(const uint8_t* data, size_t length) {
+  if (GetFont()->dropped_graphite) {
+    return Drop("Skipping Graphite table");
+  }
+  Buffer table(data, length);
+  uint32_t version;
+  if (!table.ReadU32(&version)) {
+    return DropGraphite("Failed to read version");
+  }
+  switch (version >> 16) {
+    case 1:
+      this->handler = new OpenTypeGLAT_v1(this->font, this->tag);
+      break;
+    case 2:
+      this->handler = new OpenTypeGLAT_v2(this->font, this->tag);
+      break;
+    case 3: {
+      this->handler = new OpenTypeGLAT_v3(this->font, this->tag);
+      break;
+    }
+    default:
+      return DropGraphite("Unsupported table version: %u", version >> 16);
+  }
+  return this->handler->Parse(data, length);
+}
+
+bool OpenTypeGLAT::Serialize(OTSStream* out) {
+  if (!this->handler) {
+    return Error("No Glat table parsed");
+  }
+  return this->handler->Serialize(out);
+}
+
+}  // namespace ots
diff --git a/gfx/ots/src/glat.h b/gfx/ots/src/glat.h
new file mode 100644
index 000000000..04c9c1cce
--- /dev/null
+++ b/gfx/ots/src/glat.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef OTS_GLAT_H_
+#define OTS_GLAT_H_
+
+#include <vector>
+
+#include "ots.h"
+#include "graphite.h"
+
+namespace ots {
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT_Basic Interface
+// -----------------------------------------------------------------------------
+
+class OpenTypeGLAT_Basic : public Table {
+ public:
+  explicit OpenTypeGLAT_Basic(Font* font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  virtual bool Parse(const uint8_t* data, size_t length) = 0;
+  virtual bool Serialize(OTSStream* out) = 0;
+};
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT_v1
+// -----------------------------------------------------------------------------
+
+class OpenTypeGLAT_v1 : public OpenTypeGLAT_Basic {
+ public:
+  explicit OpenTypeGLAT_v1(Font* font, uint32_t tag)
+      : OpenTypeGLAT_Basic(font, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length);
+  bool Serialize(OTSStream* out);
+
+ private:
+  struct GlatEntry : public TablePart<OpenTypeGLAT_v1> {
+    explicit GlatEntry(OpenTypeGLAT_v1* parent)
+        : TablePart<OpenTypeGLAT_v1>(parent) { }
+    bool ParsePart(Buffer& table);
+    bool SerializePart(OTSStream* out) const;
+    uint8_t attNum;
+    uint8_t num;
+    std::vector<int16_t> attributes;
+  };
+  uint32_t version;
+  std::vector<GlatEntry> entries;
+};
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT_v2
+// -----------------------------------------------------------------------------
+
+class OpenTypeGLAT_v2 : public OpenTypeGLAT_Basic {
+ public:
+  explicit OpenTypeGLAT_v2(Font* font, uint32_t tag)
+      : OpenTypeGLAT_Basic(font, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length);
+  bool Serialize(OTSStream* out);
+
+ private:
+ struct GlatEntry : public TablePart<OpenTypeGLAT_v2> {
+   explicit GlatEntry(OpenTypeGLAT_v2* parent)
+      : TablePart<OpenTypeGLAT_v2>(parent) { }
+   bool ParsePart(Buffer& table);
+   bool SerializePart(OTSStream* out) const;
+   int16_t attNum;
+   int16_t num;
+   std::vector<int16_t> attributes;
+  };
+  uint32_t version;
+  std::vector<GlatEntry> entries;
+};
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT_v3
+// -----------------------------------------------------------------------------
+
+class OpenTypeGLAT_v3 : public OpenTypeGLAT_Basic {
+ public:
+  explicit OpenTypeGLAT_v3(Font* font, uint32_t tag)
+     : OpenTypeGLAT_Basic(font, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length) {
+    return this->Parse(data, length, false);
+  }
+  bool Serialize(OTSStream* out);
+
+ private:
+  bool Parse(const uint8_t* data, size_t length, bool prevent_decompression);
+  struct GlyphAttrs : public TablePart<OpenTypeGLAT_v3> {
+    explicit GlyphAttrs(OpenTypeGLAT_v3* parent)
+      : TablePart<OpenTypeGLAT_v3>(parent), octabox(parent) { }
+    bool ParsePart(Buffer& table) { return false; }
+    bool ParsePart(Buffer& table, const size_t size);
+    bool SerializePart(OTSStream* out) const;
+    struct OctaboxMetrics : public TablePart<OpenTypeGLAT_v3> {
+      explicit OctaboxMetrics(OpenTypeGLAT_v3* parent)
+          : TablePart<OpenTypeGLAT_v3>(parent) { }
+      bool ParsePart(Buffer& table);
+      bool SerializePart(OTSStream* out) const;
+      struct SubboxEntry : public TablePart<OpenTypeGLAT_v3> {
+        explicit SubboxEntry(OpenTypeGLAT_v3* parent)
+            : TablePart<OpenTypeGLAT_v3>(parent) { }
+        bool ParsePart(Buffer& table);
+        bool SerializePart(OTSStream* out) const;
+        uint8_t left;
+        uint8_t right;
+        uint8_t bottom;
+        uint8_t top;
+        uint8_t diag_pos_min;
+        uint8_t diag_pos_max;
+        uint8_t diag_neg_min;
+        uint8_t diag_neg_max;
+      };
+      uint16_t subbox_bitmap;
+      uint8_t diag_neg_min;
+      uint8_t diag_neg_max;
+      uint8_t diag_pos_min;
+      uint8_t diag_pos_max;
+      std::vector<SubboxEntry> subboxes;
+    };
+    struct GlatEntry : public TablePart<OpenTypeGLAT_v3> {
+      explicit GlatEntry(OpenTypeGLAT_v3* parent)
+          : TablePart<OpenTypeGLAT_v3>(parent) { }
+      bool ParsePart(Buffer& table);
+      bool SerializePart(OTSStream* out) const;
+      int16_t attNum;
+      int16_t num;
+      std::vector<int16_t> attributes;
+     };
+    OctaboxMetrics octabox;
+    std::vector<GlatEntry> entries;
+  };
+  uint32_t version;
+  uint32_t compHead;  // compression header
+  static const uint32_t SCHEME = 0xF8000000;
+  static const uint32_t FULL_SIZE = 0x07FFFFFF;
+  static const uint32_t RESERVED = 0x07FFFFFE;
+  static const uint32_t OCTABOXES = 0x00000001;
+  std::vector<GlyphAttrs> entries;
+};
+
+// -----------------------------------------------------------------------------
+// OpenTypeGLAT
+// -----------------------------------------------------------------------------
+
+class OpenTypeGLAT : public Table {
+ public:
+  explicit OpenTypeGLAT(Font* font, uint32_t tag)
+      : Table(font, tag, tag), font(font), tag(tag) { }
+  OpenTypeGLAT(const OpenTypeGLAT& other) = delete;
+  OpenTypeGLAT& operator=(const OpenTypeGLAT& other) = delete;
+  ~OpenTypeGLAT() { delete handler; }
+
+  bool Parse(const uint8_t* data, size_t length);
+  bool Serialize(OTSStream* out);
+
+ private:
+  Font* font;
+  uint32_t tag;
+  OpenTypeGLAT_Basic* handler = nullptr;
+};
+
+}  // namespace ots
+
+#endif  // OTS_GLAT_H_
diff --git a/gfx/ots/src/gloc.cc b/gfx/ots/src/gloc.cc
new file mode 100644
index 000000000..e06e978bc
--- /dev/null
+++ b/gfx/ots/src/gloc.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gloc.h"
+
+#include "name.h"
+
+namespace ots {
+
+bool OpenTypeGLOC::Parse(const uint8_t* data, size_t length) {
+  if (GetFont()->dropped_graphite) {
+    return Drop("Skipping Graphite table");
+  }
+  Buffer table(data, length);
+  OpenTypeNAME* name = static_cast<OpenTypeNAME*>(
+      GetFont()->GetTypedTable(OTS_TAG_NAME));
+  if (!name) {
+    return DropGraphite("Required name table is missing");
+  }
+
+  if (!table.ReadU32(&this->version)) {
+    return DropGraphite("Failed to read version");
+  }
+  if (this->version >> 16 != 1) {
+    return DropGraphite("Unsupported table version: %u", this->version >> 16);
+  }
+  if (!table.ReadU16(&this->flags) || this->flags > 3) {
+    return DropGraphite("Failed to read valid flags");
+  }
+  if (!table.ReadU16(&this->numAttribs)) {
+    return DropGraphite("Failed to read numAttribs");
+  }
+
+  if (this->flags & ATTRIB_IDS && this->numAttribs * sizeof(uint16_t) >
+                                  table.remaining()) {
+    return DropGraphite("Failed to calulate length of locations");
+  }
+  size_t locations_len = (table.remaining() -
+    (this->flags & ATTRIB_IDS ? this->numAttribs * sizeof(uint16_t) : 0)) /
+    (this->flags & LONG_FORMAT ? sizeof(uint32_t) : sizeof(uint16_t));
+  //this->locations.resize(locations_len);
+  if (this->flags & LONG_FORMAT) {
+    unsigned long last_location = 0;
+    for (size_t i = 0; i < locations_len; ++i) {
+      this->locations.emplace_back();
+      uint32_t& location = this->locations[i];
+      if (!table.ReadU32(&location) || location < last_location) {
+        return DropGraphite("Failed to read valid locations[%lu]", i);
+      }
+      last_location = location;
+    }
+  } else {  // short (16-bit) offsets
+    unsigned last_location = 0;
+    for (size_t i = 0; i < locations_len; ++i) {
+      uint16_t location;
+      if (!table.ReadU16(&location) || location < last_location) {
+        return DropGraphite("Failed to read valid locations[%lu]", i);
+      }
+      last_location = location;
+      this->locations.push_back(static_cast<uint32_t>(location));
+    }
+  }
+  if (this->locations.empty()) {
+    return DropGraphite("No locations");
+  }
+
+  if (this->flags & ATTRIB_IDS) {  // attribIds array present
+    //this->attribIds.resize(numAttribs);
+    for (unsigned i = 0; i < this->numAttribs; ++i) {
+      this->attribIds.emplace_back();
+      if (!table.ReadU16(&this->attribIds[i]) ||
+          !name->IsValidNameId(this->attribIds[i])) {
+        return DropGraphite("Failed to read valid attribIds[%u]", i);
+      }
+    }
+  }
+
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeGLOC::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      !out->WriteU16(this->flags) ||
+      !out->WriteU16(this->numAttribs) ||
+      (this->flags & LONG_FORMAT ? !SerializeParts(this->locations, out) :
+       ![&] {
+         for (uint32_t location : this->locations) {
+           if (!out->WriteU16(static_cast<uint16_t>(location))) {
+             return false;
+           }
+         }
+         return true;
+       }()) ||
+      (this->flags & ATTRIB_IDS && !SerializeParts(this->attribIds, out))) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+const std::vector<uint32_t>& OpenTypeGLOC::GetLocations() {
+  return this->locations;
+}
+
+}  // namespace ots
diff --git a/gfx/ots/src/gloc.h b/gfx/ots/src/gloc.h
new file mode 100644
index 000000000..17d9c01b5
--- /dev/null
+++ b/gfx/ots/src/gloc.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef OTS_GLOC_H_
+#define OTS_GLOC_H_
+
+#include <vector>
+
+#include "ots.h"
+#include "graphite.h"
+
+namespace ots {
+
+class OpenTypeGLOC : public Table {
+ public:
+  explicit OpenTypeGLOC(Font* font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length);
+  bool Serialize(OTSStream* out);
+  const std::vector<uint32_t>& GetLocations();
+
+ private:
+  uint32_t version;
+  uint16_t flags;
+  static const uint16_t LONG_FORMAT = 1;
+  static const uint16_t ATTRIB_IDS = 2;
+  uint16_t numAttribs;
+  std::vector<uint32_t> locations;
+  std::vector<uint16_t> attribIds;
+};
+
+}  // namespace ots
+
+#endif  // OTS_GLOC_H_
diff --git a/gfx/ots/src/glyf.cc b/gfx/ots/src/glyf.cc
index 311916dc0..5d2aae5ff 100644
--- a/gfx/ots/src/glyf.cc
+++ b/gfx/ots/src/glyf.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -14,20 +14,15 @@
 // glyf - Glyph Data
 // http://www.microsoft.com/typography/otspec/glyf.htm
 
-#define TABLE_NAME "glyf"
-
-namespace {
+namespace ots {
 
-bool ParseFlagsForSimpleGlyph(ots::Font *font,
-                              ots::Buffer *table,
-                              uint32_t gly_length,
-                              uint32_t num_flags,
-                              uint32_t *flags_count_logical,
-                              uint32_t *flags_count_physical,
-                              uint32_t *xy_coordinates_length) {
+bool OpenTypeGLYF::ParseFlagsForSimpleGlyph(Buffer &glyph,
+                                            uint32_t num_flags,
+                                            uint32_t *flag_index,
+                                            uint32_t *coordinates_length) {
   uint8_t flag = 0;
-  if (!table->ReadU8(&flag)) {
-    return OTS_FAILURE_MSG("Can't read flag");
+  if (!glyph.ReadU8(&flag)) {
+    return Error("Can't read flag");
   }
 
   uint32_t delta = 0;
@@ -44,139 +39,192 @@ bool ParseFlagsForSimpleGlyph(ots::Font *font,
   }
 
   if (flag & (1u << 3)) {  // repeat
-    if (*flags_count_logical + 1 >= num_flags) {
-      return OTS_FAILURE_MSG("Count too high (%d + 1 >= %d)", *flags_count_logical, num_flags);
+    if (*flag_index + 1 >= num_flags) {
+      return Error("Count too high (%d + 1 >= %d)", *flag_index, num_flags);
     }
     uint8_t repeat = 0;
-    if (!table->ReadU8(&repeat)) {
-      return OTS_FAILURE_MSG("Can't read repeat value");
+    if (!glyph.ReadU8(&repeat)) {
+      return Error("Can't read repeat value");
     }
     if (repeat == 0) {
-      return OTS_FAILURE_MSG("Zero repeat");
+      return Error("Zero repeat");
     }
     delta += (delta * repeat);
 
-    *flags_count_logical += repeat;
-    if (*flags_count_logical >= num_flags) {
-      return OTS_FAILURE_MSG("Count too high (%d >= %d)", *flags_count_logical, num_flags);
+    *flag_index += repeat;
+    if (*flag_index >= num_flags) {
+      return Error("Count too high (%d >= %d)", *flag_index, num_flags);
     }
-    ++(*flags_count_physical);
   }
 
   if ((flag & (1u << 6)) || (flag & (1u << 7))) {  // reserved flags
-    return OTS_FAILURE_MSG("Bad glyph flag value (%d), reserved flags must be set to zero", flag);
+    return Error("Bad glyph flag value (%d), reserved flags must be set to zero", flag);
   }
 
-  *xy_coordinates_length += delta;
-  if (gly_length < *xy_coordinates_length) {
-    return OTS_FAILURE_MSG("Glyph coordinates length too low (%d < %d)", gly_length, *xy_coordinates_length);
+  *coordinates_length += delta;
+  if (glyph.length() < *coordinates_length) {
+    return Error("Glyph coordinates length bigger than glyph length (%d > %d)",
+                 *coordinates_length, glyph.length());
   }
 
   return true;
 }
 
-bool ParseSimpleGlyph(ots::Font *font, const uint8_t *data,
-                      ots::Buffer *table, int16_t num_contours,
-                      uint32_t gly_offset, uint32_t gly_length,
-                      uint32_t *new_size) {
-  ots::OpenTypeGLYF *glyf = font->glyf;
-
+bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph,
+                                    int16_t num_contours) {
   // read the end-points array
   uint16_t num_flags = 0;
   for (int i = 0; i < num_contours; ++i) {
     uint16_t tmp_index = 0;
-    if (!table->ReadU16(&tmp_index)) {
-      return OTS_FAILURE_MSG("Can't read contour index %d", i);
+    if (!glyph.ReadU16(&tmp_index)) {
+      return Error("Can't read contour index %d", i);
     }
     if (tmp_index == 0xffffu) {
-      return OTS_FAILURE_MSG("Bad contour index %d", i);
+      return Error("Bad contour index %d", i);
     }
     // check if the indices are monotonically increasing
     if (i && (tmp_index + 1 <= num_flags)) {
-      return OTS_FAILURE_MSG("Decreasing contour index %d + 1 <= %d", tmp_index, num_flags);
+      return Error("Decreasing contour index %d + 1 <= %d", tmp_index, num_flags);
     }
     num_flags = tmp_index + 1;
   }
 
   uint16_t bytecode_length = 0;
-  if (!table->ReadU16(&bytecode_length)) {
-    return OTS_FAILURE_MSG("Can't read bytecode length");
-  }
-  if ((font->maxp->version_1) &&
-      (font->maxp->max_size_glyf_instructions < bytecode_length)) {
-    return OTS_FAILURE_MSG("Bytecode length too high %d", bytecode_length);
+  if (!glyph.ReadU16(&bytecode_length)) {
+    return Error("Can't read bytecode length");
   }
 
-  const uint32_t gly_header_length = 10 + num_contours * 2 + 2;
-  if (gly_length < (gly_header_length + bytecode_length)) {
-    return OTS_FAILURE_MSG("Glyph header length too high %d", gly_header_length);
+  if (this->maxp->version_1 &&
+      this->maxp->max_size_glyf_instructions < bytecode_length) {
+    return Error("Bytecode length is bigger than maxp.maxSizeOfInstructions "
+        "%d: %d", this->maxp->max_size_glyf_instructions, bytecode_length);
   }
 
-  glyf->iov.push_back(std::make_pair(
-      data + gly_offset,
-      static_cast<size_t>(gly_header_length + bytecode_length)));
-
-  if (!table->Skip(bytecode_length)) {
-    return OTS_FAILURE_MSG("Can't skip bytecode of length %d", bytecode_length);
+  if (!glyph.Skip(bytecode_length)) {
+    return Error("Can't read bytecode of length %d", bytecode_length);
   }
 
-  uint32_t flags_count_physical = 0;  // on memory
-  uint32_t xy_coordinates_length = 0;
-  for (uint32_t flags_count_logical = 0;
-       flags_count_logical < num_flags;
-       ++flags_count_logical, ++flags_count_physical) {
-    if (!ParseFlagsForSimpleGlyph(font,
-                                  table,
-                                  gly_length,
-                                  num_flags,
-                                  &flags_count_logical,
-                                  &flags_count_physical,
-                                  &xy_coordinates_length)) {
-      return OTS_FAILURE_MSG("Failed to parse glyph flags %d", flags_count_logical);
+  uint32_t coordinates_length = 0;
+  for (uint32_t i = 0; i < num_flags; ++i) {
+    if (!ParseFlagsForSimpleGlyph(glyph, num_flags, &i, &coordinates_length)) {
+      return Error("Failed to parse glyph flags %d", i);
     }
   }
 
-  if (gly_length < (gly_header_length + bytecode_length +
-                    flags_count_physical + xy_coordinates_length)) {
-    return OTS_FAILURE_MSG("Glyph too short %d", gly_length);
+  if (!glyph.Skip(coordinates_length)) {
+    return Error("Glyph too short %d", glyph.length());
   }
 
-  if (gly_length - (gly_header_length + bytecode_length +
-                    flags_count_physical + xy_coordinates_length) > 3) {
+  if (glyph.remaining() > 3) {
     // We allow 0-3 bytes difference since gly_length is 4-bytes aligned,
     // zero-padded length.
-    return OTS_FAILURE_MSG("Invalid glyph length %d", gly_length);
+    Warning("Extra bytes at end of the glyph: %d", glyph.remaining());
   }
 
-  glyf->iov.push_back(std::make_pair(
-      data + gly_offset + gly_header_length + bytecode_length,
-      static_cast<size_t>(flags_count_physical + xy_coordinates_length)));
-
-  *new_size
-      = gly_header_length + flags_count_physical + xy_coordinates_length + bytecode_length;
+  this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
 
   return true;
 }
 
-}  // namespace
+#define ARG_1_AND_2_ARE_WORDS    (1u << 0)
+#define WE_HAVE_A_SCALE          (1u << 3)
+#define MORE_COMPONENTS          (1u << 5)
+#define WE_HAVE_AN_X_AND_Y_SCALE (1u << 6)
+#define WE_HAVE_A_TWO_BY_TWO     (1u << 7)
+#define WE_HAVE_INSTRUCTIONS     (1u << 8)
+
+bool OpenTypeGLYF::ParseCompositeGlyph(Buffer &glyph) {
+  uint16_t flags = 0;
+  uint16_t gid = 0;
+  do {
+    if (!glyph.ReadU16(&flags) || !glyph.ReadU16(&gid)) {
+      return Error("Can't read composite glyph flags or glyphIndex");
+    }
 
-namespace ots {
+    if (gid >= this->maxp->num_glyphs) {
+      return Error("Invalid glyph id used in composite glyph: %d", gid);
+    }
+
+    if (flags & ARG_1_AND_2_ARE_WORDS) {
+      int16_t argument1;
+      int16_t argument2;
+      if (!glyph.ReadS16(&argument1) || !glyph.ReadS16(&argument2)) {
+        return Error("Can't read argument1 or argument2");
+      }
+    } else {
+      uint8_t argument1;
+      uint8_t argument2;
+      if (!glyph.ReadU8(&argument1) || !glyph.ReadU8(&argument2)) {
+        return Error("Can't read argument1 or argument2");
+      }
+    }
+
+    if (flags & WE_HAVE_A_SCALE) {
+      int16_t scale;
+      if (!glyph.ReadS16(&scale)) {
+        return Error("Can't read scale");
+      }
+    } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
+      int16_t xscale;
+      int16_t yscale;
+      if (!glyph.ReadS16(&xscale) || !glyph.ReadS16(&yscale)) {
+        return Error("Can't read xscale or yscale");
+      }
+    } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
+      int16_t xscale;
+      int16_t scale01;
+      int16_t scale10;
+      int16_t yscale;
+      if (!glyph.ReadS16(&xscale) ||
+          !glyph.ReadS16(&scale01) ||
+          !glyph.ReadS16(&scale10) ||
+          !glyph.ReadS16(&yscale)) {
+        return Error("Can't read transform");
+      }
+    }
+  } while (flags & MORE_COMPONENTS);
+
+  if (flags & WE_HAVE_INSTRUCTIONS) {
+    uint16_t bytecode_length;
+    if (!glyph.ReadU16(&bytecode_length)) {
+      return Error("Can't read instructions size");
+    }
 
-bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
+    if (this->maxp->version_1 &&
+        this->maxp->max_size_glyf_instructions < bytecode_length) {
+      return Error("Bytecode length is bigger than maxp.maxSizeOfInstructions "
+                   "%d: %d",
+                   this->maxp->max_size_glyf_instructions, bytecode_length);
+    }
 
-  if (!font->maxp || !font->loca || !font->head) {
-    return OTS_FAILURE_MSG("Missing maxp or loca or head table needed by glyf table");
+    if (!glyph.Skip(bytecode_length)) {
+      return Error("Can't read bytecode of length %d", bytecode_length);
+    }
   }
 
-  OpenTypeGLYF *glyf = new OpenTypeGLYF;
-  font->glyf = glyf;
+  this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
+
+  return true;
+}
 
-  const unsigned num_glyphs = font->maxp->num_glyphs;
-  std::vector<uint32_t> &offsets = font->loca->offsets;
+bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) {
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  OpenTypeLOCA *loca = static_cast<OpenTypeLOCA*>(
+      GetFont()->GetTypedTable(OTS_TAG_LOCA));
+  OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>(
+      GetFont()->GetTypedTable(OTS_TAG_HEAD));
+  if (!maxp || !loca || !head) {
+    return Error("Missing maxp or loca or head table needed by glyf table");
+  }
+
+  this->maxp = maxp;
+
+  const unsigned num_glyphs = maxp->num_glyphs;
+  std::vector<uint32_t> &offsets = loca->offsets;
 
   if (offsets.size() != num_glyphs + 1) {
-    return OTS_FAILURE_MSG("Invalide glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1);
+    return Error("Invalide glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1);
   }
 
   std::vector<uint32_t> resulting_offsets(num_glyphs + 1);
@@ -193,30 +241,31 @@ bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) {
     }
 
     if (gly_offset >= length) {
-      return OTS_FAILURE_MSG("Glyph %d offset %d too high %ld", i, gly_offset, length);
+      return Error("Glyph %d offset %d too high %ld", i, gly_offset, length);
     }
     // Since these are unsigned types, the compiler is not allowed to assume
     // that they never overflow.
     if (gly_offset + gly_length < gly_offset) {
-      return OTS_FAILURE_MSG("Glyph %d length (%d < 0)!", i, gly_length);
+      return Error("Glyph %d length (%d < 0)!", i, gly_length);
     }
     if (gly_offset + gly_length > length) {
-      return OTS_FAILURE_MSG("Glyph %d length %d too high", i, gly_length);
+      return Error("Glyph %d length %d too high", i, gly_length);
     }
 
-    table.set_offset(gly_offset);
+    Buffer glyph(data + gly_offset, gly_length);
+
     int16_t num_contours, xmin, ymin, xmax, ymax;
-    if (!table.ReadS16(&num_contours) ||
-        !table.ReadS16(&xmin) ||
-        !table.ReadS16(&ymin) ||
-        !table.ReadS16(&xmax) ||
-        !table.ReadS16(&ymax)) {
-      return OTS_FAILURE_MSG("Can't read glyph %d header", i);
+    if (!glyph.ReadS16(&num_contours) ||
+        !glyph.ReadS16(&xmin) ||
+        !glyph.ReadS16(&ymin) ||
+        !glyph.ReadS16(&xmax) ||
+        !glyph.ReadS16(&ymax)) {
+      return Error("Can't read glyph %d header", i);
     }
 
     if (num_contours <= -2) {
       // -2, -3, -4, ... are reserved for future use.
-      return OTS_FAILURE_MSG("Bad number of contours %d in glyph %d", num_contours, i);
+      return Error("Bad number of contours %d in glyph %d", num_contours, i);
     }
 
     // workaround for fonts in http://www.princexml.com/fonts/
@@ -224,35 +273,36 @@ bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) {
         (xmax == -32767) &&
         (ymin == 32767) &&
         (ymax == -32767)) {
-      OTS_WARNING("bad xmin/xmax/ymin/ymax values");
+      Warning("bad xmin/xmax/ymin/ymax values");
       xmin = xmax = ymin = ymax = 0;
     }
 
     if (xmin > xmax || ymin > ymax) {
-      return OTS_FAILURE_MSG("Bad bounding box values bl=(%d, %d), tr=(%d, %d) in glyph %d", xmin, ymin, xmax, ymax, i);
+      return Error("Bad bounding box values bl=(%d, %d), tr=(%d, %d) in glyph %d", xmin, ymin, xmax, ymax, i);
     }
 
-    unsigned new_size = 0;
-    if (num_contours >= 0) {
-      // this is a simple glyph and might contain bytecode
-      if (!ParseSimpleGlyph(font, data, &table,
-                            num_contours, gly_offset, gly_length, &new_size)) {
-        return OTS_FAILURE_MSG("Failed to parse glyph %d", i);
+    if (num_contours == 0) {
+      // This is an empty glyph and shouldn’t have any glyph data, but if it
+      // does we will simply ignore it.
+      glyph.set_offset(0);
+    } else if (num_contours > 0) {
+      if (!ParseSimpleGlyph(glyph, num_contours)) {
+        return Error("Failed to parse glyph %d", i);
       }
     } else {
-      // it's a composite glyph without any bytecode. Enqueue the whole thing
-      glyf->iov.push_back(std::make_pair(data + gly_offset,
-                                         static_cast<size_t>(gly_length)));
-      new_size = gly_length;
+      if (!ParseCompositeGlyph(glyph)) {
+        return Error("Failed to parse glyph %d", i);
+      }
     }
 
+    size_t new_size = glyph.offset();
     resulting_offsets[i] = current_offset;
     // glyphs must be four byte aligned
     // TODO(yusukes): investigate whether this padding is really necessary.
     //                Which part of the spec requires this?
     const unsigned padding = (4 - (new_size & 3)) % 4;
     if (padding) {
-      glyf->iov.push_back(std::make_pair(
+      this->iov.push_back(std::make_pair(
           reinterpret_cast<const uint8_t*>("\x00\x00\x00\x00"),
           static_cast<size_t>(padding)));
       new_size += padding;
@@ -264,40 +314,32 @@ bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) {
   const uint16_t max16 = std::numeric_limits<uint16_t>::max();
   if ((*std::max_element(resulting_offsets.begin(),
                          resulting_offsets.end()) >= (max16 * 2u)) &&
-      (font->head->index_to_loc_format != 1)) {
-    OTS_WARNING("2-bytes indexing is not possible (due to the padding above)");
-    font->head->index_to_loc_format = 1;
+      (head->index_to_loc_format != 1)) {
+    head->index_to_loc_format = 1;
   }
 
-  font->loca->offsets = resulting_offsets;
-  return true;
-}
-
-bool ots_glyf_should_serialise(Font *font) {
-  return font->glyf != NULL;
-}
+  loca->offsets = resulting_offsets;
 
-bool ots_glyf_serialise(OTSStream *out, Font *font) {
-  const OpenTypeGLYF *glyf = font->glyf;
-
-  for (unsigned i = 0; i < glyf->iov.size(); ++i) {
-    if (!out->Write(glyf->iov[i].first, glyf->iov[i].second)) {
-      return OTS_FAILURE_MSG("Falied to write glyph %d", i);
-    }
+  if (this->iov.empty()) {
+    // As a special case when all glyph in the font are empty, add a zero byte
+    // to the table, so that we don’t reject it down the way, and to make the
+    // table work on Windows as well.
+    // See https://github.com/khaledhosny/ots/issues/52
+    static const uint8_t kZero = 0;
+    this->iov.push_back(std::make_pair(&kZero, 1));
   }
 
   return true;
 }
 
-void ots_glyf_reuse(Font *font, Font *other) {
-  font->glyf = other->glyf;
-  font->glyf_reused = true;
-}
+bool OpenTypeGLYF::Serialize(OTSStream *out) {
+  for (unsigned i = 0; i < this->iov.size(); ++i) {
+    if (!out->Write(this->iov[i].first, this->iov[i].second)) {
+      return Error("Falied to write glyph %d", i);
+    }
+  }
 
-void ots_glyf_free(Font *font) {
-  delete font->glyf;
+  return true;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/glyf.h b/gfx/ots/src/glyf.h
index 9a8baf5ec..1da94e4b9 100644
--- a/gfx/ots/src/glyf.h
+++ b/gfx/ots/src/glyf.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -12,8 +12,26 @@
 #include "ots.h"
 
 namespace ots {
+class OpenTypeMAXP;
+
+class OpenTypeGLYF : public Table {
+ public:
+  explicit OpenTypeGLYF(Font *font, uint32_t tag)
+      : Table(font, tag, tag), maxp(NULL) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
+ private:
+  bool ParseFlagsForSimpleGlyph(Buffer &glyph,
+                                uint32_t num_flags,
+                                uint32_t *flag_index,
+                                uint32_t *coordinates_length);
+  bool ParseSimpleGlyph(Buffer &glyph, int16_t num_contours);
+  bool ParseCompositeGlyph(Buffer &glyph);
+
+  OpenTypeMAXP* maxp;
 
-struct OpenTypeGLYF {
   std::vector<std::pair<const uint8_t*, size_t> > iov;
 };
 
diff --git a/gfx/ots/src/gpos.cc b/gfx/ots/src/gpos.cc
index 83d9ab053..074b5b1cf 100644
--- a/gfx/ots/src/gpos.cc
+++ b/gfx/ots/src/gpos.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -76,9 +76,23 @@ const ots::LookupSubtableParser kGposLookupSubtableParser = {
 
 // Shared Tables: ValueRecord, Anchor Table, and MarkArray
 
+size_t CalcValueRecordSize(const uint16_t value_format) {
+  size_t size = 0;
+  for (unsigned i = 0; i < 8; ++i) {
+    if ((value_format >> i) & 0x1) {
+      size += 2;
+    }
+  }
+
+  return size;
+}
+
 bool ParseValueRecord(const ots::Font *font,
-                      ots::Buffer* subtable, const uint8_t *data,
-                      const size_t length, const uint16_t value_format) {
+                      ots::Buffer* subtable,
+                      const uint16_t value_format) {
+  const uint8_t *data = subtable->buffer();
+  const size_t length = subtable->length();
+
   // Check existence of adjustment fields.
   for (unsigned i = 0; i < 4; ++i) {
     if ((value_format >> i) & 0x1) {
@@ -207,6 +221,12 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data,
                            const size_t length) {
   ots::Buffer subtable(data, length);
 
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t value_format = 0;
@@ -218,7 +238,7 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data,
 
   if (format == 1) {
     // Format 1 exactly one value record.
-    if (!ParseValueRecord(font, &subtable, data, length, value_format)) {
+    if (!ParseValueRecord(font, &subtable, value_format)) {
       return OTS_FAILURE_MSG("Failed to parse format 1 single adjustment table");
     }
   } else if (format == 2) {
@@ -227,7 +247,7 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data,
       return OTS_FAILURE_MSG("Failed to parse format 2 single adjustment table");
     }
     for (unsigned i = 0; i < value_count; ++i) {
-      if (!ParseValueRecord(font, &subtable, data, length, value_format)) {
+      if (!ParseValueRecord(font, &subtable, value_format)) {
         return OTS_FAILURE_MSG("Failed to parse value record %d in format 2 single adjustment table", i);
       }
     }
@@ -241,7 +261,7 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data,
 
   if (!ots::ParseCoverageTable(font, data + offset_coverage,
                                length - offset_coverage,
-                               font->maxp->num_glyphs)) {
+                               maxp->num_glyphs)) {
     return OTS_FAILURE_MSG("Failed to parse coverage table in single adjustment table");
   }
 
@@ -268,10 +288,10 @@ bool ParsePairSetTable(const ots::Font *font,
     if (glyph_id >= num_glyphs) {
       return OTS_FAILURE_MSG("glyph id %d too high >= %d", glyph_id, num_glyphs);
     }
-    if (!ParseValueRecord(font, &subtable, data, length, value_format1)) {
+    if (!ParseValueRecord(font, &subtable, value_format1)) {
       return OTS_FAILURE_MSG("Failed to parse value record in format 1 pair set table");
     }
-    if (!ParseValueRecord(font, &subtable, data, length, value_format2)) {
+    if (!ParseValueRecord(font, &subtable, value_format2)) {
       return OTS_FAILURE_MSG("Failed to parse value record in format 2 pair set table");
     }
   }
@@ -341,26 +361,36 @@ bool ParsePairPosFormat2(const ots::Font *font,
     return OTS_FAILURE_MSG("Failed to read pair pos format 2 data");
   }
 
+  size_t value_record1_size = CalcValueRecordSize(value_format1);
+  size_t value_record2_size = CalcValueRecordSize(value_format2);
+  size_t value_records_size = size_t(class1_count) * size_t(class2_count) *
+    (value_record1_size + value_record2_size);
+
+  // Check the validity of class definition offsets.
+  if (offset_class_def1 < subtable.offset() + value_records_size ||
+      offset_class_def2 < subtable.offset() + value_records_size ||
+      offset_class_def1 >= length || offset_class_def2 >= length) {
+    return OTS_FAILURE_MSG("Bad ParsePairPosFormat2 class definition offsets %d or %d", offset_class_def1, offset_class_def2);
+  }
+
   // Check class 1 records.
-  for (unsigned i = 0; i < class1_count; ++i) {
-    // Check class 2 records.
-    for (unsigned j = 0; j < class2_count; ++j) {
-      if (value_format1 && !ParseValueRecord(font, &subtable, data, length,
-                                             value_format1)) {
-        return OTS_FAILURE_MSG("Failed to parse value record 1 %d and %d", j, i);
-      }
-      if (value_format2 && !ParseValueRecord(font, &subtable, data, length,
-                                             value_format2)) {
-        return OTS_FAILURE_MSG("Falied to parse value record 2 %d and %d", j, i);
+  if (value_record1_size || value_record2_size) {
+    for (unsigned i = 0; i < class1_count; ++i) {
+      // Check class 2 records.
+      for (unsigned j = 0; j < class2_count; ++j) {
+        if (value_format1 && value_record2_size &&
+            !ParseValueRecord(font, &subtable, value_format1)) {
+          return OTS_FAILURE_MSG("Failed to parse value record 1 %d and %d", j, i);
+        }
+        if (value_format2 && value_record2_size &&
+            !ParseValueRecord(font, &subtable, value_format2)) {
+          return OTS_FAILURE_MSG("Falied to parse value record 2 %d and %d", j, i);
+        }
       }
     }
   }
 
   // Check class definition tables.
-  if (offset_class_def1 < subtable.offset() || offset_class_def1 >= length ||
-      offset_class_def2 < subtable.offset() || offset_class_def2 >= length) {
-    return OTS_FAILURE_MSG("Bad class definition table offsets %d or %d", offset_class_def1, offset_class_def2);
-  }
   if (!ots::ParseClassDefTable(font, data + offset_class_def1,
                                length - offset_class_def1,
                                num_glyphs, kMaxClassDefValue)) {
@@ -381,6 +411,12 @@ bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data,
                          const size_t length) {
   ots::Buffer subtable(data, length);
 
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t value_format1 = 0;
@@ -394,12 +430,12 @@ bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data,
 
   if (format == 1) {
     if (!ParsePairPosFormat1(font, data, length, value_format1, value_format2,
-                             font->maxp->num_glyphs)) {
+                             maxp->num_glyphs)) {
       return OTS_FAILURE_MSG("Failed to parse pair pos format 1");
     }
   } else if (format == 2) {
     if (!ParsePairPosFormat2(font, data, length, value_format1, value_format2,
-                             font->maxp->num_glyphs)) {
+                             maxp->num_glyphs)) {
       return OTS_FAILURE_MSG("Failed to parse pair format 2");
     }
   } else {
@@ -411,7 +447,7 @@ bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data,
   }
   if (!ots::ParseCoverageTable(font, data + offset_coverage,
                                length - offset_coverage,
-                               font->maxp->num_glyphs)) {
+                               maxp->num_glyphs)) {
     return OTS_FAILURE_MSG("Failed to parse coverage table");
   }
 
@@ -424,6 +460,12 @@ bool ParseCursiveAttachment(const ots::Font *font, const uint8_t *data,
                             const size_t length) {
   ots::Buffer subtable(data, length);
 
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t entry_exit_count = 0;
@@ -478,7 +520,7 @@ bool ParseCursiveAttachment(const ots::Font *font, const uint8_t *data,
   }
   if (!ots::ParseCoverageTable(font, data + offset_coverage,
                                length - offset_coverage,
-                               font->maxp->num_glyphs)) {
+                               maxp->num_glyphs)) {
     return OTS_FAILURE_MSG("Failed to parse coverage table in cursive attachment");
   }
 
@@ -552,6 +594,12 @@ bool ParseMarkToAttachmentSubtables(const ots::Font *font,
                                     const GPOS_TYPE type) {
   ots::Buffer subtable(data, length);
 
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+
   uint16_t format = 0;
   uint16_t offset_coverage1 = 0;
   uint16_t offset_coverage2 = 0;
@@ -580,7 +628,7 @@ bool ParseMarkToAttachmentSubtables(const ots::Font *font,
   }
   if (!ots::ParseCoverageTable(font, data + offset_coverage1,
                                length - offset_coverage1,
-                               font->maxp->num_glyphs)) {
+                               maxp->num_glyphs)) {
     return OTS_FAILURE_MSG("Failed to parse converge 1 table");
   }
   if (offset_coverage2 < header_end || offset_coverage2 >= length) {
@@ -588,7 +636,7 @@ bool ParseMarkToAttachmentSubtables(const ots::Font *font,
   }
   if (!ots::ParseCoverageTable(font, data + offset_coverage2,
                                length - offset_coverage2,
-                               font->maxp->num_glyphs)) {
+                               maxp->num_glyphs)) {
     return OTS_FAILURE_MSG("Failed to parse coverage table 2");
   }
 
@@ -652,17 +700,37 @@ bool ParseMarkToMarkAttachment(const ots::Font *font,
 // Contextual Positioning Subtables
 bool ParseContextPositioning(const ots::Font *font,
                              const uint8_t *data, const size_t length) {
-  return ots::ParseContextSubtable(font, data, length, font->maxp->num_glyphs,
-                                   font->gpos->num_lookups);
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  ots::OpenTypeGPOS *gpos = static_cast<ots::OpenTypeGPOS*>(
+      font->GetTypedTable(OTS_TAG_GPOS));
+  if (!gpos) {
+    return OTS_FAILURE_MSG("Internal error!");
+  }
+  return ots::ParseContextSubtable(font, data, length, maxp->num_glyphs,
+                                   gpos->num_lookups);
 }
 
 // Lookup Type 8:
 // Chaining Contexual Positioning Subtable
 bool ParseChainedContextPositioning(const ots::Font *font,
                                     const uint8_t *data, const size_t length) {
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  ots::OpenTypeGPOS *gpos = static_cast<ots::OpenTypeGPOS*>(
+      font->GetTypedTable(OTS_TAG_GPOS));
+  if (!gpos) {
+    return OTS_FAILURE_MSG("Internal error!");
+  }
   return ots::ParseChainingContextSubtable(font, data, length,
-                                           font->maxp->num_glyphs,
-                                           font->gpos->num_lookups);
+                                           maxp->num_glyphs,
+                                           gpos->num_lookups);
 }
 
 // Lookup Type 9:
@@ -677,63 +745,10 @@ bool ParseExtensionPositioning(const ots::Font *font,
 
 namespace ots {
 
-// As far as I checked, following fonts contain invalid GPOS table and
-// OTS will drop their GPOS table.
-//
-// # invalid delta format in device table
-// samanata.ttf
-//
-// # bad size range in device table
-// Sarai_07.ttf
-//
-// # bad offset to PairSetTable
-// chandas1-2.ttf
-//
-// # bad offset to FeatureTable
-// glrso12.ttf
-// gllr12.ttf
-// glbo12.ttf
-// glb12.ttf
-// glro12.ttf
-// glbso12.ttf
-// glrc12.ttf
-// glrsc12.ttf
-// glbs12.ttf
-// glrs12.ttf
-// glr12.ttf
-//
-// # ScriptRecords aren't sorted by tag
-// Garogier_unhinted.otf
-//
-// # bad start coverage index in CoverageFormat2
-// AndBasR.ttf
-// CharisSILB.ttf
-// CharisSILBI.ttf
-// CharisSILI.ttf
-// CharisSILR.ttf
-// DoulosSILR.ttf
-// GenBasBI.ttf
-// GenBasI.ttf
-// GenBkBasI.ttf
-// GenBkBasB.ttf
-// GenBkBasR.ttf
-// Padauk-Bold.ttf
-// Padauk.ttf
-//
-// # Contour point indexes aren't sorted
-// Arial Unicode.ttf
-
-bool ots_gpos_parse(Font *font, const uint8_t *data, size_t length) {
-  // Parsing GPOS table requires num_glyphs which is contained in maxp table.
-  if (!font->maxp) {
-    return OTS_FAILURE_MSG("missing maxp table needed in GPOS");
-  }
-
+bool OpenTypeGPOS::Parse(const uint8_t *data, size_t length) {
+  Font *font = GetFont();
   Buffer table(data, length);
 
-  OpenTypeGPOS *gpos = new OpenTypeGPOS;
-  font->gpos = gpos;
-
   uint32_t version = 0;
   uint16_t offset_script_list = 0;
   uint16_t offset_feature_list = 0;
@@ -742,76 +757,63 @@ bool ots_gpos_parse(Font *font, const uint8_t *data, size_t length) {
       !table.ReadU16(&offset_script_list) ||
       !table.ReadU16(&offset_feature_list) ||
       !table.ReadU16(&offset_lookup_list)) {
-    return OTS_FAILURE_MSG("Incomplete table");
+    return Error("Incomplete table");
   }
 
   if (version != 0x00010000) {
-    return OTS_FAILURE_MSG("Bad version");
+    return Error("Bad version");
   }
 
   if (offset_lookup_list) {
     if (offset_lookup_list < kGposHeaderSize || offset_lookup_list >= length) {
-      return OTS_FAILURE_MSG("Bad lookup list offset in table header");
+      return Error("Bad lookup list offset in table header");
     }
 
     if (!ParseLookupListTable(font, data + offset_lookup_list,
                               length - offset_lookup_list,
                               &kGposLookupSubtableParser,
-                              &gpos->num_lookups)) {
-      return OTS_FAILURE_MSG("Failed to parse lookup list table");
+                              &this->num_lookups)) {
+      return Error("Failed to parse lookup list table");
     }
   }
 
   uint16_t num_features = 0;
   if (offset_feature_list) {
     if (offset_feature_list < kGposHeaderSize || offset_feature_list >= length) {
-      return OTS_FAILURE_MSG("Bad feature list offset in table header");
+      return Error("Bad feature list offset in table header");
     }
 
     if (!ParseFeatureListTable(font, data + offset_feature_list,
-                               length - offset_feature_list, gpos->num_lookups,
+                               length - offset_feature_list, this->num_lookups,
                                &num_features)) {
-      return OTS_FAILURE_MSG("Failed to parse feature list table");
+      return Error("Failed to parse feature list table");
     }
   }
 
   if (offset_script_list) {
     if (offset_script_list < kGposHeaderSize || offset_script_list >= length) {
-      return OTS_FAILURE_MSG("Bad script list offset in table header");
+      return Error("Bad script list offset in table header");
     }
 
     if (!ParseScriptListTable(font, data + offset_script_list,
                               length - offset_script_list, num_features)) {
-      return OTS_FAILURE_MSG("Failed to parse script list table");
+      return Error("Failed to parse script list table");
     }
   }
 
-  gpos->data = data;
-  gpos->length = length;
+  this->m_data = data;
+  this->m_length = length;
   return true;
 }
 
-bool ots_gpos_should_serialise(Font *font) {
-  return font->gpos != NULL && font->gpos->data != NULL;
-}
-
-bool ots_gpos_serialise(OTSStream *out, Font *font) {
-  if (!out->Write(font->gpos->data, font->gpos->length)) {
-    return OTS_FAILURE_MSG("Failed to write GPOS table");
+bool OpenTypeGPOS::Serialize(OTSStream *out) {
+  if (!out->Write(this->m_data, this->m_length)) {
+    return Error("Failed to write GPOS table");
   }
 
   return true;
 }
 
-void ots_gpos_reuse(Font *font, Font *other) {
-  font->gpos = other->gpos;
-  font->gpos_reused = true;
-}
-
-void ots_gpos_free(Font *font) {
-  delete font->gpos;
-}
-
 }  // namespace ots
 
 #undef TABLE_NAME
diff --git a/gfx/ots/src/gpos.h b/gfx/ots/src/gpos.h
index 3a4034f6f..423c8ae83 100644
--- a/gfx/ots/src/gpos.h
+++ b/gfx/ots/src/gpos.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,18 +9,24 @@
 
 namespace ots {
 
-struct OpenTypeGPOS {
-  OpenTypeGPOS()
-      : num_lookups(0),
-        data(NULL),
-        length(0) {
+class OpenTypeGPOS : public Table {
+ public:
+  explicit OpenTypeGPOS(Font *font, uint32_t tag)
+      : Table(font, tag, tag),
+        num_lookups(0),
+        m_data(NULL),
+        m_length(0) {
   }
 
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   // Number of lookups in GPOS table
   uint16_t num_lookups;
 
-  const uint8_t *data;
-  size_t length;
+ private:
+  const uint8_t *m_data;
+  size_t m_length;
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/graphite.h b/gfx/ots/src/graphite.h
new file mode 100644
index 000000000..1bd95f5c9
--- /dev/null
+++ b/gfx/ots/src/graphite.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef OTS_GRAPHITE_H_
+#define OTS_GRAPHITE_H_
+
+#include <vector>
+#include <type_traits>
+
+namespace ots {
+
+template<typename ParentType>
+class TablePart {
+ public:
+  TablePart(ParentType* parent) : parent(parent) { }
+  virtual bool ParsePart(Buffer& table) = 0;
+  virtual bool SerializePart(OTSStream* out) const = 0;
+ protected:
+  ParentType* parent;
+};
+
+template<typename T>
+bool SerializeParts(const std::vector<T>& vec, OTSStream* out) {
+  for (const T& part : vec) {
+    if (!part.SerializePart(out)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+template<typename T>
+bool SerializeParts(const std::vector<std::vector<T>>& vec, OTSStream* out) {
+  for (const std::vector<T>& part : vec) {
+    if (!SerializeParts(part, out)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+inline bool SerializeParts(const std::vector<uint8_t>& vec, OTSStream* out) {
+  for (uint8_t part : vec) {
+    if (!out->WriteU8(part)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+inline bool SerializeParts(const std::vector<uint16_t>& vec, OTSStream* out) {
+  for (uint16_t part : vec) {
+    if (!out->WriteU16(part)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+inline bool SerializeParts(const std::vector<int16_t>& vec, OTSStream* out) {
+  for (int16_t part : vec) {
+    if (!out->WriteS16(part)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+inline bool SerializeParts(const std::vector<uint32_t>& vec, OTSStream* out) {
+  for (uint32_t part : vec) {
+    if (!out->WriteU32(part)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+inline bool SerializeParts(const std::vector<int32_t>& vec, OTSStream* out) {
+  for (int32_t part : vec) {
+    if (!out->WriteS32(part)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+template<typename T>
+size_t datasize(std::vector<T> vec) {
+  return sizeof(T) * vec.size();
+}
+
+}  // namespace ots
+
+#endif  // OTS_GRAPHITE_H_
diff --git a/gfx/ots/src/gsub.cc b/gfx/ots/src/gsub.cc
index 9baf2e88b..4db1b3e7a 100644
--- a/gfx/ots/src/gsub.cc
+++ b/gfx/ots/src/gsub.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -82,7 +82,12 @@ bool ParseSingleSubstitution(const ots::Font *font,
     return OTS_FAILURE_MSG("Failed to read single subst table header");
   }
 
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  const uint16_t num_glyphs = maxp->num_glyphs;
   if (format == 1) {
     // Parse SingleSubstFormat1
     int16_t delta_glyph_id = 0;
@@ -170,7 +175,12 @@ bool ParseMutipleSubstitution(const ots::Font *font,
     return OTS_FAILURE_MSG("Bad multiple subst table format %d", format);
   }
 
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  const uint16_t num_glyphs = maxp->num_glyphs;
   const unsigned sequence_end = static_cast<unsigned>(6) +
       sequence_count * 2;
   if (sequence_end > std::numeric_limits<uint16_t>::max()) {
@@ -245,7 +255,12 @@ bool ParseAlternateSubstitution(const ots::Font *font,
     return OTS_FAILURE_MSG("Bad alternate subst table format %d", format);
   }
 
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  const uint16_t num_glyphs = maxp->num_glyphs;
   const unsigned alternate_set_end = static_cast<unsigned>(6) +
       alternate_set_count * 2;
   if (alternate_set_end > std::numeric_limits<uint16_t>::max()) {
@@ -362,7 +377,12 @@ bool ParseLigatureSubstitution(const ots::Font *font,
     return OTS_FAILURE_MSG("Bad ligature substitution table format %d", format);
   }
 
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  const uint16_t num_glyphs = maxp->num_glyphs;
   const unsigned ligature_set_end = static_cast<unsigned>(6) +
       lig_set_count * 2;
   if (ligature_set_end > std::numeric_limits<uint16_t>::max()) {
@@ -398,8 +418,18 @@ bool ParseLigatureSubstitution(const ots::Font *font,
 // Contextual Substitution Subtable
 bool ParseContextSubstitution(const ots::Font *font,
                               const uint8_t *data, const size_t length) {
-  return ots::ParseContextSubtable(font, data, length, font->maxp->num_glyphs,
-                                   font->gsub->num_lookups);
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  ots::OpenTypeGSUB *gsub = static_cast<ots::OpenTypeGSUB*>(
+      font->GetTypedTable(OTS_TAG_GSUB));
+  if (!gsub) {
+    return OTS_FAILURE_MSG("Internal error!");
+  }
+  return ots::ParseContextSubtable(font, data, length, maxp->num_glyphs,
+                                   gsub->num_lookups);
 }
 
 // Lookup Type 6:
@@ -407,9 +437,19 @@ bool ParseContextSubstitution(const ots::Font *font,
 bool ParseChainingContextSubstitution(const ots::Font *font,
                                       const uint8_t *data,
                                       const size_t length) {
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  ots::OpenTypeGSUB *gsub = static_cast<ots::OpenTypeGSUB*>(
+      font->GetTypedTable(OTS_TAG_GSUB));
+  if (!gsub) {
+    return OTS_FAILURE_MSG("Internal error!");
+  }
   return ots::ParseChainingContextSubtable(font, data, length,
-                                           font->maxp->num_glyphs,
-                                           font->gsub->num_lookups);
+                                           maxp->num_glyphs,
+                                           gsub->num_lookups);
 }
 
 // Lookup Type 7:
@@ -434,7 +474,12 @@ bool ParseReverseChainingContextSingleSubstitution(
     return OTS_FAILURE_MSG("Failed to read reverse chaining header");
   }
 
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+      font->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return OTS_FAILURE_MSG("Required maxp table missing");
+  }
+  const uint16_t num_glyphs = maxp->num_glyphs;
 
   uint16_t backtrack_glyph_count = 0;
   if (!subtable.ReadU16(&backtrack_glyph_count)) {
@@ -530,67 +575,11 @@ bool ParseReverseChainingContextSingleSubstitution(
 
 namespace ots {
 
-// As far as I checked, following fonts contain invalid values in GSUB table.
-// OTS will drop their GSUB table.
-//
-// # too large substitute (value is 0xFFFF)
-// kaiu.ttf
-// mingliub2.ttf
-// mingliub1.ttf
-// mingliub0.ttf
-// GraublauWeb.otf
-// GraublauWebBold.otf
-//
-// # too large alternate (value is 0xFFFF)
-// ManchuFont.ttf
-//
-// # bad offset to lang sys table (NULL offset)
-// DejaVuMonoSansBold.ttf
-// DejaVuMonoSansBoldOblique.ttf
-// DejaVuMonoSansOblique.ttf
-// DejaVuSansMono-BoldOblique.ttf
-// DejaVuSansMono-Oblique.ttf
-// DejaVuSansMono-Bold.ttf
-//
-// # bad start coverage index
-// GenBasBI.ttf
-// GenBasI.ttf
-// AndBasR.ttf
-// GenBkBasI.ttf
-// CharisSILR.ttf
-// CharisSILBI.ttf
-// CharisSILI.ttf
-// CharisSILB.ttf
-// DoulosSILR.ttf
-// CharisSILBI.ttf
-// GenBkBasB.ttf
-// GenBkBasR.ttf
-// GenBkBasBI.ttf
-// GenBasB.ttf
-// GenBasR.ttf
-//
-// # glyph range is overlapping
-// KacstTitleL.ttf
-// KacstDecorative.ttf
-// KacstTitle.ttf
-// KacstArt.ttf
-// KacstPoster.ttf
-// KacstQurn.ttf
-// KacstDigital.ttf
-// KacstBook.ttf
-// KacstFarsi.ttf
-
-bool ots_gsub_parse(Font *font, const uint8_t *data, size_t length) {
-  // Parsing gsub table requires |font->maxp->num_glyphs|
-  if (!font->maxp) {
-    return OTS_FAILURE_MSG("Missing maxp table in font, needed by GSUB");
-  }
-
+bool OpenTypeGSUB::Parse(const uint8_t *data, size_t length) {
+  // Parsing gsub table requires |maxp->num_glyphs|
+  Font *font = GetFont();
   Buffer table(data, length);
 
-  OpenTypeGSUB *gsub = new OpenTypeGSUB;
-  font->gsub = gsub;
-
   uint32_t version = 0;
   uint16_t offset_script_list = 0;
   uint16_t offset_feature_list = 0;
@@ -599,76 +588,63 @@ bool ots_gsub_parse(Font *font, const uint8_t *data, size_t length) {
       !table.ReadU16(&offset_script_list) ||
       !table.ReadU16(&offset_feature_list) ||
       !table.ReadU16(&offset_lookup_list)) {
-    return OTS_FAILURE_MSG("Incomplete table");
+    return Error("Incomplete table");
   }
 
   if (version != 0x00010000) {
-    return OTS_FAILURE_MSG("Bad version");
+    return Error("Bad version");
   }
 
   if (offset_lookup_list) {
     if (offset_lookup_list < kGsubHeaderSize || offset_lookup_list >= length) {
-      return OTS_FAILURE_MSG("Bad lookup list offset in table header");
+      return Error("Bad lookup list offset in table header");
     }
 
     if (!ParseLookupListTable(font, data + offset_lookup_list,
                               length - offset_lookup_list,
                               &kGsubLookupSubtableParser,
-                              &gsub->num_lookups)) {
-      return OTS_FAILURE_MSG("Failed to parse lookup list table");
+                              &this->num_lookups)) {
+      return Error("Failed to parse lookup list table");
     }
   }
 
   uint16_t num_features = 0;
   if (offset_feature_list) {
     if (offset_feature_list < kGsubHeaderSize || offset_feature_list >= length) {
-      return OTS_FAILURE_MSG("Bad feature list offset in table header");
+      return Error("Bad feature list offset in table header");
     }
 
     if (!ParseFeatureListTable(font, data + offset_feature_list,
-                               length - offset_feature_list, gsub->num_lookups,
+                               length - offset_feature_list, this->num_lookups,
                                &num_features)) {
-      return OTS_FAILURE_MSG("Failed to parse feature list table");
+      return Error("Failed to parse feature list table");
     }
   }
 
   if (offset_script_list) {
     if (offset_script_list < kGsubHeaderSize || offset_script_list >= length) {
-      return OTS_FAILURE_MSG("Bad script list offset in table header");
+      return Error("Bad script list offset in table header");
     }
 
     if (!ParseScriptListTable(font, data + offset_script_list,
                               length - offset_script_list, num_features)) {
-      return OTS_FAILURE_MSG("Failed to parse script list table");
+      return Error("Failed to parse script list table");
     }
   }
 
-  gsub->data = data;
-  gsub->length = length;
+  this->m_data = data;
+  this->m_length = length;
   return true;
 }
 
-bool ots_gsub_should_serialise(Font *font) {
-  return font->gsub != NULL && font->gsub->data != NULL;
-}
-
-bool ots_gsub_serialise(OTSStream *out, Font *font) {
-  if (!out->Write(font->gsub->data, font->gsub->length)) {
-    return OTS_FAILURE_MSG("Failed to write GSUB table");
+bool OpenTypeGSUB::Serialize(OTSStream *out) {
+  if (!out->Write(this->m_data, this->m_length)) {
+    return Error("Failed to write GSUB table");
   }
 
   return true;
 }
 
-void ots_gsub_reuse(Font *font, Font *other) {
-  font->gsub = other->gsub;
-  font->gsub_reused = true;
-}
-
-void ots_gsub_free(Font *font) {
-  delete font->gsub;
-}
-
 }  // namespace ots
 
 #undef TABLE_NAME
diff --git a/gfx/ots/src/gsub.h b/gfx/ots/src/gsub.h
index f6f8cd3b1..76c990465 100644
--- a/gfx/ots/src/gsub.h
+++ b/gfx/ots/src/gsub.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,18 +9,24 @@
 
 namespace ots {
 
-struct OpenTypeGSUB {
-  OpenTypeGSUB()
-      : num_lookups(0),
-        data(NULL),
-        length(0) {
+class OpenTypeGSUB : public Table {
+ public:
+  explicit OpenTypeGSUB(Font *font, uint32_t tag)
+      : Table(font, tag, tag),
+        num_lookups(0),
+        m_data(NULL),
+        m_length(0) {
   }
 
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   // Number of lookups in GPSUB table
   uint16_t num_lookups;
 
-  const uint8_t *data;
-  size_t length;
+ //private:
+  const uint8_t *m_data;
+  size_t m_length;
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/hdmx.cc b/gfx/ots/src/hdmx.cc
index f57b71f59..42ac9de8d 100644
--- a/gfx/ots/src/hdmx.cc
+++ b/gfx/ots/src/hdmx.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,139 +9,112 @@
 // hdmx - Horizontal Device Metrics
 // http://www.microsoft.com/typography/otspec/hdmx.htm
 
-#define TABLE_NAME "hdmx"
-
-#define DROP_THIS_TABLE(...) \
-  do { \
-    OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \
-    OTS_FAILURE_MSG("Table discarded"); \
-    delete font->hdmx; \
-    font->hdmx = 0; \
-  } while (0)
-
 namespace ots {
 
-bool ots_hdmx_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeHDMX::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
-  font->hdmx = new OpenTypeHDMX;
-  OpenTypeHDMX * const hdmx = font->hdmx;
 
-  if (!font->head || !font->maxp) {
-    return OTS_FAILURE_MSG("Missing maxp or head tables in font, needed by hdmx");
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>(
+      GetFont()->GetTypedTable(OTS_TAG_HEAD));
+  if (!head || !maxp) {
+    return Error("Missing maxp or head tables in font, needed by hdmx");
   }
 
-  if ((font->head->flags & 0x14) == 0) {
-    // http://www.microsoft.com/typography/otspec/recom.htm
-    DROP_THIS_TABLE("the table should not be present when bit 2 and 4 of the "
-                    "head->flags are not set");
-    return true;
+  if ((head->flags & 0x14) == 0) {
+    // http://www.microsoft.com/typography/otspec/recom.htm#hdmx
+    return Drop("the table should not be present when bit 2 and 4 of the "
+                "head->flags are not set");
   }
 
   int16_t num_recs;
-  if (!table.ReadU16(&hdmx->version) ||
+  if (!table.ReadU16(&this->version) ||
       !table.ReadS16(&num_recs) ||
-      !table.ReadS32(&hdmx->size_device_record)) {
-    return OTS_FAILURE_MSG("Failed to read hdmx header");
+      !table.ReadS32(&this->size_device_record)) {
+    return Error("Failed to read table header");
   }
-  if (hdmx->version != 0) {
-    DROP_THIS_TABLE("bad version: %u", hdmx->version);
-    return true;
+  if (this->version != 0) {
+    return Drop("Unsupported version: %u", this->version);
   }
   if (num_recs <= 0) {
-    DROP_THIS_TABLE("bad num_recs: %d", num_recs);
-    return true;
+    return Drop("Bad numRecords: %d", num_recs);
   }
-  const int32_t actual_size_device_record = font->maxp->num_glyphs + 2;
-  if (hdmx->size_device_record < actual_size_device_record) {
-    DROP_THIS_TABLE("bad hdmx->size_device_record: %d", hdmx->size_device_record);
-    return true;
+  const int32_t actual_size_device_record = maxp->num_glyphs + 2;
+  if (this->size_device_record < actual_size_device_record) {
+    return Drop("Bad sizeDeviceRecord: %d", this->size_device_record);
   }
 
-  hdmx->pad_len = hdmx->size_device_record - actual_size_device_record;
-  if (hdmx->pad_len > 3) {
-    return OTS_FAILURE_MSG("Bad padding %d", hdmx->pad_len);
+  this->pad_len = this->size_device_record - actual_size_device_record;
+  if (this->pad_len > 3) {
+    return Error("Bad DeviceRecord padding %d", this->pad_len);
   }
 
   uint8_t last_pixel_size = 0;
-  hdmx->records.reserve(num_recs);
+  this->records.reserve(num_recs);
   for (int i = 0; i < num_recs; ++i) {
     OpenTypeHDMXDeviceRecord rec;
 
     if (!table.ReadU8(&rec.pixel_size) ||
         !table.ReadU8(&rec.max_width)) {
-      return OTS_FAILURE_MSG("Failed to read hdmx record %d", i);
+      return Error("Failed to read DeviceRecord %d", i);
     }
     if ((i != 0) &&
         (rec.pixel_size <= last_pixel_size)) {
-      DROP_THIS_TABLE("records are not sorted");
-      return true;
+      return Drop("DeviceRecord's are not sorted");
     }
     last_pixel_size = rec.pixel_size;
 
-    rec.widths.reserve(font->maxp->num_glyphs);
-    for (unsigned j = 0; j < font->maxp->num_glyphs; ++j) {
+    rec.widths.reserve(maxp->num_glyphs);
+    for (unsigned j = 0; j < maxp->num_glyphs; ++j) {
       uint8_t width;
       if (!table.ReadU8(&width)) {
-        return OTS_FAILURE_MSG("Failed to read glyph width %d in record %d", j, i);
+        return Error("Failed to read glyph width %d in DeviceRecord %d", j, i);
       }
       rec.widths.push_back(width);
     }
 
-    if ((hdmx->pad_len > 0) &&
-        !table.Skip(hdmx->pad_len)) {
-      return OTS_FAILURE_MSG("Failed to skip padding %d", hdmx->pad_len);
+    if ((this->pad_len > 0) &&
+        !table.Skip(this->pad_len)) {
+      return Error("DeviceRecord %d should be padded by %d", i, this->pad_len);
     }
 
-    hdmx->records.push_back(rec);
+    this->records.push_back(rec);
   }
 
   return true;
 }
 
-bool ots_hdmx_should_serialise(Font *font) {
-  if (!font->hdmx) return false;
-  if (!font->glyf) return false;  // this table is not for CFF fonts.
-  return true;
+bool OpenTypeHDMX::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for CFF fonts.
+         GetFont()->GetTable(OTS_TAG_GLYF) != NULL;
 }
 
-bool ots_hdmx_serialise(OTSStream *out, Font *font) {
-  OpenTypeHDMX * const hdmx = font->hdmx;
-
-  const int16_t num_recs = static_cast<int16_t>(hdmx->records.size());
-  if (hdmx->records.size() >
+bool OpenTypeHDMX::Serialize(OTSStream *out) {
+  const int16_t num_recs = static_cast<int16_t>(this->records.size());
+  if (this->records.size() >
           static_cast<size_t>(std::numeric_limits<int16_t>::max()) ||
-      !out->WriteU16(hdmx->version) ||
+      !out->WriteU16(this->version) ||
       !out->WriteS16(num_recs) ||
-      !out->WriteS32(hdmx->size_device_record)) {
-    return OTS_FAILURE_MSG("Failed to write hdmx header");
+      !out->WriteS32(this->size_device_record)) {
+    return Error("Failed to write table header");
   }
 
   for (int16_t i = 0; i < num_recs; ++i) {
-    const OpenTypeHDMXDeviceRecord& rec = hdmx->records[i];
+    const OpenTypeHDMXDeviceRecord& rec = this->records[i];
     if (!out->Write(&rec.pixel_size, 1) ||
         !out->Write(&rec.max_width, 1) ||
         !out->Write(&rec.widths[0], rec.widths.size())) {
-      return OTS_FAILURE_MSG("Failed to write hdmx record %d", i);
+      return Error("Failed to write DeviceRecord %d", i);
     }
-    if ((hdmx->pad_len > 0) &&
-        !out->Write((const uint8_t *)"\x00\x00\x00", hdmx->pad_len)) {
-      return OTS_FAILURE_MSG("Failed to write hdmx padding of length %d", hdmx->pad_len);
+    if ((this->pad_len > 0) &&
+        !out->Write((const uint8_t *)"\x00\x00\x00", this->pad_len)) {
+      return Error("Failed to write padding of length %d", this->pad_len);
     }
   }
 
   return true;
 }
 
-void ots_hdmx_reuse(Font *font, Font *other) {
-  font->hdmx = other->hdmx;
-  font->hdmx_reused = true;
-}
-
-void ots_hdmx_free(Font *font) {
-  delete font->hdmx;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
-#undef DROP_THIS_TABLE
diff --git a/gfx/ots/src/hdmx.h b/gfx/ots/src/hdmx.h
index 9ec212437..5d323dd7e 100644
--- a/gfx/ots/src/hdmx.h
+++ b/gfx/ots/src/hdmx.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -17,7 +17,16 @@ struct OpenTypeHDMXDeviceRecord {
   std::vector<uint8_t> widths;
 };
 
-struct OpenTypeHDMX {
+class OpenTypeHDMX : public Table {
+ public:
+  explicit OpenTypeHDMX(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
   uint16_t version;
   int32_t size_device_record;
   int32_t pad_len;
diff --git a/gfx/ots/src/head.cc b/gfx/ots/src/head.cc
index 229ea71f9..6088504c8 100644
--- a/gfx/ots/src/head.cc
+++ b/gfx/ots/src/head.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,151 +9,124 @@
 // head - Font Header
 // http://www.microsoft.com/typography/otspec/head.htm
 
-#define TABLE_NAME "head"
-
 namespace ots {
 
-bool ots_head_parse(Font* font, const uint8_t *data, size_t length) {
+bool OpenTypeHEAD::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
-  OpenTypeHEAD *head = new OpenTypeHEAD;
-  font->head = head;
 
   uint32_t version = 0;
   if (!table.ReadU32(&version) ||
-      !table.ReadU32(&head->revision)) {
-    return OTS_FAILURE_MSG("Failed to read head header");
+      !table.ReadU32(&this->revision)) {
+    return Error("Failed to read table header");
   }
 
   if (version >> 16 != 1) {
-    return OTS_FAILURE_MSG("Bad head table version of %d", version);
+    return Error("Unsupported majorVersion: %d", version >> 16);
   }
 
   // Skip the checksum adjustment
   if (!table.Skip(4)) {
-    return OTS_FAILURE_MSG("Failed to read checksum");
+    return Error("Failed to read checksum");
   }
 
   uint32_t magic;
   if (!table.ReadU32(&magic) || magic != 0x5F0F3CF5) {
-    return OTS_FAILURE_MSG("Failed to read font magic number");
+    return Error("Failed to read or incorrect magicNumber");
   }
 
-  if (!table.ReadU16(&head->flags)) {
-    return OTS_FAILURE_MSG("Failed to read head flags");
+  if (!table.ReadU16(&this->flags)) {
+    return Error("Failed to read flags");
   }
 
   // We allow bits 0..4, 11..13
-  head->flags &= 0x381f;
-
-  if (!table.ReadU16(&head->ppem)) {
-    return OTS_FAILURE_MSG("Failed to read pixels per em");
-  }
+  this->flags &= 0x381f;
 
-  // ppem must be in range
-  if (head->ppem < 16 ||
-      head->ppem > 16384) {
-    return OTS_FAILURE_MSG("Bad ppm of %d", head->ppem);
+  if (!table.ReadU16(&this->upem)) {
+    return Error("Failed to read unitsPerEm");
   }
 
-  // ppem must be a power of two
-#if 0
-  // We don't call ots_failure() for now since lots of TrueType fonts are
-  // not following this rule. Putting OTS_WARNING here is too noisy.
-  if ((head->ppem - 1) & head->ppem) {
-    return OTS_FAILURE_MSG("ppm not a power of two: %d", head->ppem);
+  // upem must be in range
+  if (this->upem < 16 ||
+      this->upem > 16384) {
+    return Error("unitsPerEm on in the range [16, 16384]: %d", this->upem);
   }
-#endif
 
-  if (!table.ReadR64(&head->created) ||
-      !table.ReadR64(&head->modified)) {
-    return OTS_FAILURE_MSG("Can't read font dates");
+  if (!table.ReadR64(&this->created) ||
+      !table.ReadR64(&this->modified)) {
+    return Error("Can't read font dates");
   }
 
-  if (!table.ReadS16(&head->xmin) ||
-      !table.ReadS16(&head->ymin) ||
-      !table.ReadS16(&head->xmax) ||
-      !table.ReadS16(&head->ymax)) {
-    return OTS_FAILURE_MSG("Failed to read font bounding box");
+  if (!table.ReadS16(&this->xmin) ||
+      !table.ReadS16(&this->ymin) ||
+      !table.ReadS16(&this->xmax) ||
+      !table.ReadS16(&this->ymax)) {
+    return Error("Failed to read font bounding box");
   }
 
-  if (head->xmin > head->xmax) {
-    return OTS_FAILURE_MSG("Bad x dimension in the font bounding box (%d, %d)", head->xmin, head->xmax);
+  if (this->xmin > this->xmax) {
+    return Error("Bad x dimension in the font bounding box (%d, %d)",
+                  this->xmin, this->xmax);
   }
-  if (head->ymin > head->ymax) {
-    return OTS_FAILURE_MSG("Bad y dimension in the font bounding box (%d, %d)", head->ymin, head->ymax);
+  if (this->ymin > this->ymax) {
+    return Error("Bad y dimension in the font bounding box (%d, %d)",
+                  this->ymin, this->ymax);
   }
 
-  if (!table.ReadU16(&head->mac_style)) {
-    return OTS_FAILURE_MSG("Failed to read font style");
+  if (!table.ReadU16(&this->mac_style)) {
+    return Error("Failed to read macStyle");
   }
 
   // We allow bits 0..6
-  head->mac_style &= 0x7f;
+  this->mac_style &= 0x7f;
 
-  if (!table.ReadU16(&head->min_ppem)) {
-    return OTS_FAILURE_MSG("Failed to read font minimum ppm");
+  if (!table.ReadU16(&this->min_ppem)) {
+    return Error("Failed to read lowestRecPPEM");
   }
 
   // We don't care about the font direction hint
   if (!table.Skip(2)) {
-    return OTS_FAILURE_MSG("Failed to skip font direction hint");
+    return Error("Failed to read fontDirectionHint");
   }
 
-  if (!table.ReadS16(&head->index_to_loc_format)) {
-    return OTS_FAILURE_MSG("Failed to read index to loc format");
+  if (!table.ReadS16(&this->index_to_loc_format)) {
+    return Error("Failed to read indexToLocFormat");
   }
-  if (head->index_to_loc_format < 0 ||
-      head->index_to_loc_format > 1) {
-    return OTS_FAILURE_MSG("Bad index to loc format %d", head->index_to_loc_format);
+  if (this->index_to_loc_format < 0 ||
+      this->index_to_loc_format > 1) {
+    return Error("Bad indexToLocFormat %d", this->index_to_loc_format);
   }
 
   int16_t glyph_data_format;
   if (!table.ReadS16(&glyph_data_format) ||
       glyph_data_format) {
-    return OTS_FAILURE_MSG("Failed to read glyph data format");
+    return Error("Failed to read or bad glyphDataFormat");
   }
 
   return true;
 }
 
-bool ots_head_should_serialise(Font *font) {
-  return font->head != NULL;
-}
-
-bool ots_head_serialise(OTSStream *out, Font *font) {
-  const OpenTypeHEAD *head = font->head;
+bool OpenTypeHEAD::Serialize(OTSStream *out) {
   if (!out->WriteU32(0x00010000) ||
-      !out->WriteU32(head->revision) ||
+      !out->WriteU32(this->revision) ||
       !out->WriteU32(0) ||  // check sum not filled in yet
       !out->WriteU32(0x5F0F3CF5) ||
-      !out->WriteU16(head->flags) ||
-      !out->WriteU16(head->ppem) ||
-      !out->WriteR64(head->created) ||
-      !out->WriteR64(head->modified) ||
-      !out->WriteS16(head->xmin) ||
-      !out->WriteS16(head->ymin) ||
-      !out->WriteS16(head->xmax) ||
-      !out->WriteS16(head->ymax) ||
-      !out->WriteU16(head->mac_style) ||
-      !out->WriteU16(head->min_ppem) ||
+      !out->WriteU16(this->flags) ||
+      !out->WriteU16(this->upem) ||
+      !out->WriteR64(this->created) ||
+      !out->WriteR64(this->modified) ||
+      !out->WriteS16(this->xmin) ||
+      !out->WriteS16(this->ymin) ||
+      !out->WriteS16(this->xmax) ||
+      !out->WriteS16(this->ymax) ||
+      !out->WriteU16(this->mac_style) ||
+      !out->WriteU16(this->min_ppem) ||
       !out->WriteS16(2) ||
-      !out->WriteS16(head->index_to_loc_format) ||
+      !out->WriteS16(this->index_to_loc_format) ||
       !out->WriteS16(0)) {
-    return OTS_FAILURE_MSG("Failed to write head table");
+    return Error("Failed to write table");
   }
 
   return true;
 }
 
-void ots_head_reuse(Font *font, Font *other) {
-  font->head = other->head;
-  font->head_reused = true;
-}
-
-void ots_head_free(Font *font) {
-  delete font->head;
-}
-
 }  // namespace
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/head.h b/gfx/ots/src/head.h
index 5967c4b89..a040fcf24 100644
--- a/gfx/ots/src/head.h
+++ b/gfx/ots/src/head.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,10 +9,17 @@
 
 namespace ots {
 
-struct OpenTypeHEAD {
+class OpenTypeHEAD : public Table {
+ public:
+  explicit OpenTypeHEAD(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   uint32_t revision;
   uint16_t flags;
-  uint16_t ppem;
+  uint16_t upem;
   uint64_t created;
   uint64_t modified;
 
diff --git a/gfx/ots/src/hhea.cc b/gfx/ots/src/hhea.cc
index 624287280..d024aaac4 100644
--- a/gfx/ots/src/hhea.cc
+++ b/gfx/ots/src/hhea.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,49 +10,23 @@
 // hhea - Horizontal Header
 // http://www.microsoft.com/typography/otspec/hhea.htm
 
-#define TABLE_NAME "hhea"
-
 namespace ots {
 
-bool ots_hhea_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeHHEA::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
-  OpenTypeHHEA *hhea = new OpenTypeHHEA;
-  font->hhea = hhea;
 
-  if (!table.ReadU32(&hhea->header.version)) {
-    return OTS_FAILURE_MSG("Failed to read hhea version");
-  }
-  if (hhea->header.version >> 16 != 1) {
-    return OTS_FAILURE_MSG("Bad hhea version of %d", hhea->header.version);
+  if (!table.ReadU32(&this->version)) {
+    return Error("Failed to read table version");
   }
-
-  if (!ParseMetricsHeader(font, &table, &hhea->header)) {
-    return OTS_FAILURE_MSG("Failed to parse horizontal metrics");
+  if (this->version >> 16 != 1) {
+    return Error("Unsupported majorVersion: %d", this->version >> 16);
   }
 
-  return true;
+  return OpenTypeMetricsHeader::Parse(data, length);
 }
 
-bool ots_hhea_should_serialise(Font *font) {
-  return font->hhea != NULL;
-}
-
-bool ots_hhea_serialise(OTSStream *out, Font *font) {
-  if (!SerialiseMetricsHeader(font, out, &font->hhea->header)) {
-    return OTS_FAILURE_MSG("Failed to serialise horizontal metrics");
-  }
-  return true;
-}
-
-void ots_hhea_reuse(Font *font, Font *other) {
-  font->hhea = other->hhea;
-  font->hhea_reused = true;
-}
-
-void ots_hhea_free(Font *font) {
-  delete font->hhea;
+bool OpenTypeHHEA::Serialize(OTSStream *out) {
+  return OpenTypeMetricsHeader::Serialize(out);
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/hhea.h b/gfx/ots/src/hhea.h
index bdea9aa0d..0405bd9d1 100644
--- a/gfx/ots/src/hhea.h
+++ b/gfx/ots/src/hhea.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,8 +10,13 @@
 
 namespace ots {
 
-struct OpenTypeHHEA {
-  OpenTypeMetricsHeader header;
+class OpenTypeHHEA : public OpenTypeMetricsHeader {
+ public:
+  explicit OpenTypeHHEA(Font *font, uint32_t tag)
+      : OpenTypeMetricsHeader(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/hmtx.cc b/gfx/ots/src/hmtx.cc
index 667d1fe6f..2ef7771fd 100644
--- a/gfx/ots/src/hmtx.cc
+++ b/gfx/ots/src/hmtx.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,47 +10,14 @@
 // hmtx - Horizontal Metrics
 // http://www.microsoft.com/typography/otspec/hmtx.htm
 
-#define TABLE_NAME "hmtx"
-
 namespace ots {
 
-bool ots_hmtx_parse(Font *font, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-  OpenTypeHMTX *hmtx = new OpenTypeHMTX;
-  font->hmtx = hmtx;
-
-  if (!font->hhea || !font->maxp) {
-    return OTS_FAILURE_MSG("Missing hhea or maxp tables in font, needed by hmtx");
-  }
-
-  if (!ParseMetricsTable(font, &table, font->maxp->num_glyphs,
-                         &font->hhea->header, &hmtx->metrics)) {
-    return OTS_FAILURE_MSG("Failed to parse hmtx metrics");
-  }
-
-  return true;
-}
-
-bool ots_hmtx_should_serialise(Font *font) {
-  return font->hmtx != NULL;
+bool OpenTypeHMTX::Parse(const uint8_t *data, size_t length) {
+  return OpenTypeMetricsTable::Parse(data, length);
 }
 
-bool ots_hmtx_serialise(OTSStream *out, Font *font) {
-  if (!SerialiseMetricsTable(font, out, &font->hmtx->metrics)) {
-    return OTS_FAILURE_MSG("Failed to serialise htmx metrics");
-  }
-  return true;
-}
-
-void ots_hmtx_reuse(Font *font, Font *other) {
-  font->hmtx = other->hmtx;
-  font->hmtx_reused = true;
-}
-
-void ots_hmtx_free(Font *font) {
-  delete font->hmtx;
+bool OpenTypeHMTX::Serialize(OTSStream *out) {
+  return OpenTypeMetricsTable::Serialize(out);
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/hmtx.h b/gfx/ots/src/hmtx.h
index 435949c5e..dbea9c80c 100644
--- a/gfx/ots/src/hmtx.h
+++ b/gfx/ots/src/hmtx.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,12 +6,18 @@
 #define OTS_HMTX_H_
 
 #include "metrics.h"
+#include "hhea.h"
 #include "ots.h"
 
 namespace ots {
 
-struct OpenTypeHMTX {
-  OpenTypeMetricsTable metrics;
+class OpenTypeHMTX : public OpenTypeMetricsTable {
+ public:
+  explicit OpenTypeHMTX(Font *font, uint32_t tag)
+      : OpenTypeMetricsTable(font, tag, tag, OTS_TAG_HHEA) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/kern.cc b/gfx/ots/src/kern.cc
index d4ae8fcc4..ec41dfb9c 100644
--- a/gfx/ots/src/kern.cc
+++ b/gfx/ots/src/kern.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,75 +7,61 @@
 // kern - Kerning
 // http://www.microsoft.com/typography/otspec/kern.htm
 
-#define TABLE_NAME "kern"
-
-#define DROP_THIS_TABLE(msg_) \
-  do { \
-    OTS_FAILURE_MSG(msg_ ", table discarded"); \
-    delete font->kern; \
-    font->kern = 0; \
-  } while (0)
-
 namespace ots {
 
-bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeKERN::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypeKERN *kern = new OpenTypeKERN;
-  font->kern = kern;
-
   uint16_t num_tables = 0;
-  if (!table.ReadU16(&kern->version) ||
+  if (!table.ReadU16(&this->version) ||
       !table.ReadU16(&num_tables)) {
-    return OTS_FAILURE_MSG("Failed to read kern header");
+    return Error("Failed to read table header");
   }
 
-  if (kern->version > 0) {
-    DROP_THIS_TABLE("bad table version");
-    return true;
+  if (this->version > 0) {
+    return Drop("Unsupported table version: %d", this->version);
   }
 
   if (num_tables == 0) {
-    DROP_THIS_TABLE("num_tables is zero");
-    return true;
+    return Drop("nTables is zero");
   }
 
-  kern->subtables.reserve(num_tables);
+  this->subtables.reserve(num_tables);
   for (unsigned i = 0; i < num_tables; ++i) {
     OpenTypeKERNFormat0 subtable;
     uint16_t sub_length = 0;
 
     if (!table.ReadU16(&subtable.version) ||
         !table.ReadU16(&sub_length)) {
-      return OTS_FAILURE_MSG("Failed to read kern subtable %d header", i);
+      return Error("Failed to read subtable %d header", i);
     }
 
     if (subtable.version > 0) {
-      OTS_WARNING("Bad subtable version: %d", subtable.version);
+      Warning("Ignoring subtable %d with unsupported version: %d",
+              i, subtable.version);
       continue;
     }
 
     const size_t current_offset = table.offset();
     if (current_offset - 4 + sub_length > length) {
-      return OTS_FAILURE_MSG("Bad kern subtable %d offset %ld", i, current_offset);
+      return Error("Bad subtable %d offset %ld", i, current_offset);
     }
 
     if (!table.ReadU16(&subtable.coverage)) {
-      return OTS_FAILURE_MSG("Cailed to read kern subtable %d coverage", i);
+      return Error("Failed to read subtable %d coverage", i);
     }
 
     if (!(subtable.coverage & 0x1)) {
-      OTS_WARNING(
+      Warning(
           "We don't support vertical data as the renderer doesn't support it.");
       continue;
     }
     if (subtable.coverage & 0xF0) {
-      DROP_THIS_TABLE("Reserved fields should zero-filled");
-      return true;
+      return Drop("Reserved fields should be zero");
     }
     const uint32_t format = (subtable.coverage & 0xFF00) >> 8;
     if (format != 0) {
-      OTS_WARNING("Format %d is not supported.", format);
+      Warning("Ignoring subtable %d with unsupported format: %d", i, format);
       continue;
     }
 
@@ -85,12 +71,11 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) {
         !table.ReadU16(&subtable.search_range) ||
         !table.ReadU16(&subtable.entry_selector) ||
         !table.ReadU16(&subtable.range_shift)) {
-      return OTS_FAILURE_MSG("Failed to read kern subtable %d format 0 fields", i);
+      return Error("Failed to read subtable %d format 0 fields", i);
     }
 
     if (!num_pairs) {
-      DROP_THIS_TABLE("Zero length subtable is found");
-      return true;
+      return Drop("Zero length subtable is found");
     }
 
     // Sanity checks for search_range, entry_selector, and range_shift. See the
@@ -98,8 +83,7 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) {
     const size_t kFormat0PairSize = 6;  // left, right, and value. 2 bytes each.
     if (num_pairs > (65536 / kFormat0PairSize)) {
       // Some fonts (e.g. calibri.ttf, pykes_peak_zero.ttf) have pairs >= 10923.
-      DROP_THIS_TABLE("Too large subtable");
-      return true;
+      return Drop("Too large subtable");
     }
     unsigned max_pow2 = 0;
     while (1u << (max_pow2 + 1) <= num_pairs) {
@@ -107,16 +91,16 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) {
     }
     const uint16_t expected_search_range = (1u << max_pow2) * kFormat0PairSize;
     if (subtable.search_range != expected_search_range) {
-      OTS_WARNING("bad search range");
+      Warning("bad search range");
       subtable.search_range = expected_search_range;
     }
     if (subtable.entry_selector != max_pow2) {
-      return OTS_FAILURE_MSG("Bad subtable %d entry selector %d", i, subtable.entry_selector);
+      return Error("Bad subtable %d entry selector %d", i, subtable.entry_selector);
     }
     const uint16_t expected_range_shift =
         kFormat0PairSize * num_pairs - subtable.search_range;
     if (subtable.range_shift != expected_range_shift) {
-      OTS_WARNING("bad range shift");
+      Warning("bad range shift");
       subtable.range_shift = expected_range_shift;
     }
 
@@ -128,64 +112,55 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) {
       if (!table.ReadU16(&kerning_pair.left) ||
           !table.ReadU16(&kerning_pair.right) ||
           !table.ReadS16(&kerning_pair.value)) {
-        return OTS_FAILURE_MSG("Failed to read subtable %d kerning pair %d", i, j);
+        return Error("Failed to read subtable %d kerning pair %d", i, j);
       }
       const uint32_t current_pair
           = (kerning_pair.left << 16) + kerning_pair.right;
       if (j != 0 && current_pair <= last_pair) {
         // Many free fonts don't follow this rule, so we don't call OTS_FAILURE
         // in order to support these fonts.
-        DROP_THIS_TABLE("Kerning pairs are not sorted");
-        return true;
+        return Drop("Kerning pairs are not sorted");
       }
       last_pair = current_pair;
       subtable.pairs.push_back(kerning_pair);
     }
 
-    kern->subtables.push_back(subtable);
+    this->subtables.push_back(subtable);
   }
 
-  if (!kern->subtables.size()) {
-    DROP_THIS_TABLE("All subtables are removed");
-    return true;
+  if (!this->subtables.size()) {
+    return Drop("All subtables were removed");
   }
 
   return true;
 }
 
-bool ots_kern_should_serialise(Font *font) {
-  if (!font->glyf) return false;  // this table is not for CFF fonts.
-  return font->kern != NULL;
-}
-
-bool ots_kern_serialise(OTSStream *out, Font *font) {
-  const OpenTypeKERN *kern = font->kern;
-
-  const uint16_t num_subtables = static_cast<uint16_t>(kern->subtables.size());
-  if (num_subtables != kern->subtables.size() ||
-      !out->WriteU16(kern->version) ||
+bool OpenTypeKERN::Serialize(OTSStream *out) {
+  const uint16_t num_subtables = static_cast<uint16_t>(this->subtables.size());
+  if (num_subtables != this->subtables.size() ||
+      !out->WriteU16(this->version) ||
       !out->WriteU16(num_subtables)) {
-    return OTS_FAILURE_MSG("Can't write kern table header");
+    return Error("Failed to write kern table header");
   }
 
   for (uint16_t i = 0; i < num_subtables; ++i) {
-    const size_t length = 14 + (6 * kern->subtables[i].pairs.size());
+    const size_t length = 14 + (6 * this->subtables[i].pairs.size());
     if (length > std::numeric_limits<uint16_t>::max() ||
-        !out->WriteU16(kern->subtables[i].version) ||
+        !out->WriteU16(this->subtables[i].version) ||
         !out->WriteU16(static_cast<uint16_t>(length)) ||
-        !out->WriteU16(kern->subtables[i].coverage) ||
+        !out->WriteU16(this->subtables[i].coverage) ||
         !out->WriteU16(
-            static_cast<uint16_t>(kern->subtables[i].pairs.size())) ||
-        !out->WriteU16(kern->subtables[i].search_range) ||
-        !out->WriteU16(kern->subtables[i].entry_selector) ||
-        !out->WriteU16(kern->subtables[i].range_shift)) {
-      return OTS_FAILURE_MSG("Failed to write kern subtable %d", i);
-    }
-    for (unsigned j = 0; j < kern->subtables[i].pairs.size(); ++j) {
-      if (!out->WriteU16(kern->subtables[i].pairs[j].left) ||
-          !out->WriteU16(kern->subtables[i].pairs[j].right) ||
-          !out->WriteS16(kern->subtables[i].pairs[j].value)) {
-        return OTS_FAILURE_MSG("Failed to write kern pair %d for subtable %d", j, i);
+            static_cast<uint16_t>(this->subtables[i].pairs.size())) ||
+        !out->WriteU16(this->subtables[i].search_range) ||
+        !out->WriteU16(this->subtables[i].entry_selector) ||
+        !out->WriteU16(this->subtables[i].range_shift)) {
+      return Error("Failed to write kern subtable %d", i);
+    }
+    for (unsigned j = 0; j < this->subtables[i].pairs.size(); ++j) {
+      if (!out->WriteU16(this->subtables[i].pairs[j].left) ||
+          !out->WriteU16(this->subtables[i].pairs[j].right) ||
+          !out->WriteS16(this->subtables[i].pairs[j].value)) {
+        return Error("Failed to write kern pair %d for subtable %d", j, i);
       }
     }
   }
@@ -193,16 +168,10 @@ bool ots_kern_serialise(OTSStream *out, Font *font) {
   return true;
 }
 
-void ots_kern_reuse(Font *font, Font *other) {
-  font->kern = other->kern;
-  font->kern_reused = true;
-}
-
-void ots_kern_free(Font *font) {
-  delete font->kern;
+bool OpenTypeKERN::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for CFF fonts.
+         GetFont()->GetTable(OTS_TAG_GLYF) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
-#undef DROP_THIS_TABLE
diff --git a/gfx/ots/src/kern.h b/gfx/ots/src/kern.h
index 9350ef7f8..38f056851 100644
--- a/gfx/ots/src/kern.h
+++ b/gfx/ots/src/kern.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -30,7 +30,16 @@ struct OpenTypeKERNFormat0 {
 // WebFonts unlikely use it. I've checked thousands of proprietary fonts and
 // free fonts, and found no font uses the format.
 
-struct OpenTypeKERN {
+class OpenTypeKERN : public Table {
+ public:
+  explicit OpenTypeKERN(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
   uint16_t version;
   std::vector<OpenTypeKERNFormat0> subtables;
 };
diff --git a/gfx/ots/src/layout.cc b/gfx/ots/src/layout.cc
index 1d87b53d4..c21ede7e9 100644
--- a/gfx/ots/src/layout.cc
+++ b/gfx/ots/src/layout.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -94,15 +94,12 @@ bool ParseScriptTable(const ots::Font *font,
   }
 
   // The spec requires a script table for 'DFLT' tag must contain non-NULL
-  // |offset_default_lang_sys| and |lang_sys_count| == 0
+  // |offset_default_lang_sys|.
   // https://www.microsoft.com/typography/otspec/chapter2.htm
   if (tag == kScriptTableTagDflt) {
     if (offset_default_lang_sys == 0) {
       return OTS_FAILURE_MSG("DFLT script doesn't satisfy the spec. DefaultLangSys is NULL");
     }
-    if (lang_sys_count != 0) {
-      return OTS_FAILURE_MSG("DFLT script doesn't satisfy the spec. LangSysCount is not zero: %d", lang_sys_count);
-    }
   }
 
   const unsigned lang_sys_record_end =
@@ -197,21 +194,24 @@ bool ParseLookupTable(ots::Font *font, const uint8_t *data,
     return OTS_FAILURE_MSG("Bad lookup type %d", lookup_type);
   }
 
+  ots::OpenTypeGDEF *gdef = static_cast<ots::OpenTypeGDEF*>(
+      font->GetTypedTable(OTS_TAG_GDEF));
+
   // Check lookup flags.
   if ((lookup_flag & kGdefRequiredFlags) &&
-      (!font->gdef || !font->gdef->has_glyph_class_def)) {
+      (!gdef || !gdef->has_glyph_class_def)) {
     return OTS_FAILURE_MSG("Lookup flags require GDEF table, "
                            "but none was found: %d", lookup_flag);
   }
   if ((lookup_flag & kMarkAttachmentTypeMask) &&
-      (!font->gdef || !font->gdef->has_mark_attachment_class_def)) {
+      (!gdef || !gdef->has_mark_attachment_class_def)) {
     return OTS_FAILURE_MSG("Lookup flags ask for mark attachment, "
                            "but there is no GDEF table or it has no "
                            "mark attachment classes: %d", lookup_flag);
   }
   bool use_mark_filtering_set = false;
   if (lookup_flag & kUseMarkFilteringSetBit) {
-    if (!font->gdef || !font->gdef->has_mark_glyph_sets_def) {
+    if (!gdef || !gdef->has_mark_glyph_sets_def) {
       return OTS_FAILURE_MSG("Lookup flags ask for mark filtering, "
                              "but there is no GDEF table or it has no "
                              "mark filtering sets: %d", lookup_flag);
@@ -248,8 +248,8 @@ bool ParseLookupTable(ots::Font *font, const uint8_t *data,
     if (!subtable.ReadU16(&mark_filtering_set)) {
       return OTS_FAILURE_MSG("Failed to read mark filtering set");
     }
-    if (font->gdef->num_mark_glyph_sets == 0 ||
-        mark_filtering_set >= font->gdef->num_mark_glyph_sets) {
+    if (gdef->num_mark_glyph_sets == 0 ||
+        mark_filtering_set >= gdef->num_mark_glyph_sets) {
       return OTS_FAILURE_MSG("Bad mark filtering set %d", mark_filtering_set);
     }
   }
@@ -311,15 +311,15 @@ bool ParseClassDefFormat2(const ots::Font *font,
 
   // Skip format field.
   if (!subtable.Skip(2)) {
-    return OTS_FAILURE_MSG("Failed to skip format of class defintion header");
+    return OTS_FAILURE_MSG("Failed to read class definition format");
   }
 
   uint16_t range_count = 0;
   if (!subtable.ReadU16(&range_count)) {
-    return OTS_FAILURE_MSG("Failed to read range count in class definition");
+    return OTS_FAILURE_MSG("Failed to read classRangeCount");
   }
   if (range_count > num_glyphs) {
-    return OTS_FAILURE_MSG("bad range count: %u", range_count);
+    return OTS_FAILURE_MSG("classRangeCount > glyph count: %u > %u", range_count, num_glyphs);
   }
 
   uint16_t last_end = 0;
@@ -330,13 +330,16 @@ bool ParseClassDefFormat2(const ots::Font *font,
     if (!subtable.ReadU16(&start) ||
         !subtable.ReadU16(&end) ||
         !subtable.ReadU16(&class_value)) {
-      return OTS_FAILURE_MSG("Failed to read class definition reange %d", i);
+      return OTS_FAILURE_MSG("Failed to read ClassRangeRecord %d", i);
+    }
+    if (start > end) {
+      return OTS_FAILURE_MSG("ClassRangeRecord %d, start > end: %u > %u", i, start, end);
     }
-    if (start > end || (last_end && start <= last_end)) {
-      return OTS_FAILURE_MSG("glyph range is overlapping.in range %d", i);
+    if (last_end && start <= last_end) {
+      return OTS_FAILURE_MSG("ClassRangeRecord %d start overlaps with end of the previous one: %u <= %u", i, start, last_end);
     }
     if (class_value > num_classes) {
-      return OTS_FAILURE_MSG("bad class value: %u", class_value);
+      return OTS_FAILURE_MSG("ClassRangeRecord %d class > number of classes: %u > %u", i, class_value, num_classes);
     }
     last_end = end;
   }
diff --git a/gfx/ots/src/layout.h b/gfx/ots/src/layout.h
index d195646d4..4d18f4040 100644
--- a/gfx/ots/src/layout.h
+++ b/gfx/ots/src/layout.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/gfx/ots/src/loca.cc b/gfx/ots/src/loca.cc
index aae04c25a..4f322027d 100644
--- a/gfx/ots/src/loca.cc
+++ b/gfx/ots/src/loca.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,84 +10,79 @@
 // loca - Index to Location
 // http://www.microsoft.com/typography/otspec/loca.htm
 
-#define TABLE_NAME "loca"
-
 namespace ots {
 
-bool ots_loca_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeLOCA::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   // We can't do anything useful in validating this data except to ensure that
   // the values are monotonically increasing.
 
-  OpenTypeLOCA *loca = new OpenTypeLOCA;
-  font->loca = loca;
-
-  if (!font->maxp || !font->head) {
-    return OTS_FAILURE_MSG("maxp or head tables missing from font, needed by loca");
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>(
+      GetFont()->GetTypedTable(OTS_TAG_HEAD));
+  if (!maxp || !head) {
+    return Error("Required maxp or head tables are missing");
   }
 
-  const unsigned num_glyphs = font->maxp->num_glyphs;
+  const unsigned num_glyphs = maxp->num_glyphs;
   unsigned last_offset = 0;
-  loca->offsets.resize(num_glyphs + 1);
+  this->offsets.resize(num_glyphs + 1);
   // maxp->num_glyphs is uint16_t, thus the addition never overflows.
 
-  if (font->head->index_to_loc_format == 0) {
+  if (head->index_to_loc_format == 0) {
     // Note that the <= here (and below) is correct. There is one more offset
     // than the number of glyphs in order to give the length of the final
     // glyph.
     for (unsigned i = 0; i <= num_glyphs; ++i) {
       uint16_t offset = 0;
       if (!table.ReadU16(&offset)) {
-        return OTS_FAILURE_MSG("Failed to read offset for glyph %d", i);
+        return Error("Failed to read offset for glyph %d", i);
       }
       if (offset < last_offset) {
-        return OTS_FAILURE_MSG("Out of order offset %d < %d for glyph %d", offset, last_offset, i);
+        return Error("Out of order offset %d < %d for glyph %d", offset, last_offset, i);
       }
       last_offset = offset;
-      loca->offsets[i] = offset * 2;
+      this->offsets[i] = offset * 2;
     }
   } else {
     for (unsigned i = 0; i <= num_glyphs; ++i) {
       uint32_t offset = 0;
       if (!table.ReadU32(&offset)) {
-        return OTS_FAILURE_MSG("Failed to read offset for glyph %d", i);
+        return Error("Failed to read offset for glyph %d", i);
       }
       if (offset < last_offset) {
-        return OTS_FAILURE_MSG("Out of order offset %d < %d for glyph %d", offset, last_offset, i);
+        return Error("Out of order offset %d < %d for glyph %d", offset, last_offset, i);
       }
       last_offset = offset;
-      loca->offsets[i] = offset;
+      this->offsets[i] = offset;
     }
   }
 
   return true;
 }
 
-bool ots_loca_should_serialise(Font *font) {
-  return font->loca != NULL;
-}
-
-bool ots_loca_serialise(OTSStream *out, Font *font) {
-  const OpenTypeLOCA *loca = font->loca;
-  const OpenTypeHEAD *head = font->head;
+bool OpenTypeLOCA::Serialize(OTSStream *out) {
+  OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>(
+      GetFont()->GetTypedTable(OTS_TAG_HEAD));
 
   if (!head) {
-    return OTS_FAILURE_MSG("Missing head table in font needed by loca");
+    return Error("Required head table is missing");
   }
 
   if (head->index_to_loc_format == 0) {
-    for (unsigned i = 0; i < loca->offsets.size(); ++i) {
-      const uint16_t offset = static_cast<uint16_t>(loca->offsets[i] >> 1);
-      if ((offset != (loca->offsets[i] >> 1)) ||
+    for (unsigned i = 0; i < this->offsets.size(); ++i) {
+      const uint16_t offset = static_cast<uint16_t>(this->offsets[i] >> 1);
+      if ((offset != (this->offsets[i] >> 1)) ||
           !out->WriteU16(offset)) {
-        return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i);
+        return Error("Failed to write glyph offset for glyph %d", i);
       }
     }
   } else {
-    for (unsigned i = 0; i < loca->offsets.size(); ++i) {
-      if (!out->WriteU32(loca->offsets[i])) {
-        return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i);
+    for (unsigned i = 0; i < this->offsets.size(); ++i) {
+      if (!out->WriteU32(this->offsets[i])) {
+        return Error("Failed to write glyph offset for glyph %d", i);
       }
     }
   }
@@ -95,15 +90,4 @@ bool ots_loca_serialise(OTSStream *out, Font *font) {
   return true;
 }
 
-void ots_loca_reuse(Font *font, Font *other) {
-  font->loca = other->loca;
-  font->loca_reused = true;
-}
-
-void ots_loca_free(Font *font) {
-  delete font->loca;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/loca.h b/gfx/ots/src/loca.h
index 255ef06ec..da3241842 100644
--- a/gfx/ots/src/loca.h
+++ b/gfx/ots/src/loca.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -11,7 +11,14 @@
 
 namespace ots {
 
-struct OpenTypeLOCA {
+class OpenTypeLOCA : public Table {
+ public:
+  explicit OpenTypeLOCA(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   std::vector<uint32_t> offsets;
 };
 
diff --git a/gfx/ots/src/ltsh.cc b/gfx/ots/src/ltsh.cc
index 5b34cf4ad..4c40c5eb7 100644
--- a/gfx/ots/src/ltsh.cc
+++ b/gfx/ots/src/ltsh.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,89 +9,63 @@
 // LTSH - Linear Threshold
 // http://www.microsoft.com/typography/otspec/ltsh.htm
 
-#define TABLE_NAME "LTSH"
-
-#define DROP_THIS_TABLE(...) \
-  do { \
-    OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \
-    OTS_FAILURE_MSG("Table discarded"); \
-    delete font->ltsh; \
-    font->ltsh = 0; \
-  } while (0)
-
 namespace ots {
 
-bool ots_ltsh_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeLTSH::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  if (!font->maxp) {
-    return OTS_FAILURE_MSG("Missing maxp table from font needed by ltsh");
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return Error("Required maxp table is missing");
   }
 
-  OpenTypeLTSH *ltsh = new OpenTypeLTSH;
-  font->ltsh = ltsh;
-
   uint16_t num_glyphs = 0;
-  if (!table.ReadU16(&ltsh->version) ||
+  if (!table.ReadU16(&this->version) ||
       !table.ReadU16(&num_glyphs)) {
-    return OTS_FAILURE_MSG("Failed to read ltsh header");
+    return Error("Failed to read table header");
   }
 
-  if (ltsh->version != 0) {
-    DROP_THIS_TABLE("bad version: %u", ltsh->version);
-    return true;
+  if (this->version != 0) {
+    return Drop("Unsupported version: %u", this->version);
   }
 
-  if (num_glyphs != font->maxp->num_glyphs) {
-    DROP_THIS_TABLE("bad num_glyphs: %u", num_glyphs);
-    return true;
+  if (num_glyphs != maxp->num_glyphs) {
+    return Drop("Bad numGlyphs: %u", num_glyphs);
   }
 
-  ltsh->ypels.reserve(num_glyphs);
+  this->ypels.reserve(num_glyphs);
   for (unsigned i = 0; i < num_glyphs; ++i) {
     uint8_t pel = 0;
     if (!table.ReadU8(&pel)) {
-      return OTS_FAILURE_MSG("Failed to read pixels for glyph %d", i);
+      return Error("Failed to read pixels for glyph %d", i);
     }
-    ltsh->ypels.push_back(pel);
+    this->ypels.push_back(pel);
   }
 
   return true;
 }
 
-bool ots_ltsh_should_serialise(Font *font) {
-  if (!font->glyf) return false;  // this table is not for CFF fonts.
-  return font->ltsh != NULL;
-}
-
-bool ots_ltsh_serialise(OTSStream *out, Font *font) {
-  const OpenTypeLTSH *ltsh = font->ltsh;
-
-  const uint16_t num_ypels = static_cast<uint16_t>(ltsh->ypels.size());
-  if (num_ypels != ltsh->ypels.size() ||
-      !out->WriteU16(ltsh->version) ||
+bool OpenTypeLTSH::Serialize(OTSStream *out) {
+  const uint16_t num_ypels = static_cast<uint16_t>(this->ypels.size());
+  if (num_ypels != this->ypels.size() ||
+      !out->WriteU16(this->version) ||
       !out->WriteU16(num_ypels)) {
-    return OTS_FAILURE_MSG("Failed to write pels size");
+    return Error("Failed to write table header");
   }
   for (uint16_t i = 0; i < num_ypels; ++i) {
-    if (!out->Write(&(ltsh->ypels[i]), 1)) {
-      return OTS_FAILURE_MSG("Failed to write pixel size for glyph %d", i);
+    if (!out->Write(&(this->ypels[i]), 1)) {
+      return Error("Failed to write pixel size for glyph %d", i);
     }
   }
 
   return true;
 }
 
-void ots_ltsh_reuse(Font *font, Font *other) {
-  font->ltsh = other->ltsh;
-  font->ltsh_reused = true;
-}
-
-void ots_ltsh_free(Font *font) {
-  delete font->ltsh;
+bool OpenTypeLTSH::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for CFF fonts.
+         GetFont()->GetTable(OTS_TAG_GLYF) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
-#undef DROP_THIS_TABLE
diff --git a/gfx/ots/src/ltsh.h b/gfx/ots/src/ltsh.h
index 23d97d784..cc9fbf62d 100644
--- a/gfx/ots/src/ltsh.h
+++ b/gfx/ots/src/ltsh.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -11,7 +11,16 @@
 
 namespace ots {
 
-struct OpenTypeLTSH {
+class OpenTypeLTSH : public Table {
+ public:
+  explicit OpenTypeLTSH(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
   uint16_t version;
   std::vector<uint8_t> ypels;
 };
diff --git a/gfx/ots/src/math.cc b/gfx/ots/src/math.cc
index 36417dc29..c94e3bee3 100644
--- a/gfx/ots/src/math.cc
+++ b/gfx/ots/src/math.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Copyright (c) 2014-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -12,12 +12,7 @@
 #include "maxp.h"
 
 // MATH - The MATH Table
-// The specification is not yet public but has been submitted to the MPEG group
-// in response to the 'Call for Proposals for ISO/IEC 14496-22 "Open Font
-// Format" Color Font Technology and MATH layout support'. Meanwhile, you can
-// contact Microsoft's engineer Murray Sargent to obtain a copy.
-
-#define TABLE_NAME "MATH"
+// http://www.microsoft.com/typography/otspec/math.htm
 
 namespace {
 
@@ -48,11 +43,15 @@ const unsigned kMathValueRecordSize = 2 * 2;
 // PartFlags
 const unsigned kGlyphPartRecordSize = 5 * 2;
 
+}  // namespace
+
+namespace ots {
+
 // Shared Table: MathValueRecord
 
-bool ParseMathValueRecord(const ots::Font *font,
-                          ots::Buffer* subtable, const uint8_t *data,
-                          const size_t length) {
+bool OpenTypeMATH::ParseMathValueRecord(ots::Buffer* subtable,
+                                        const uint8_t *data,
+                                        const size_t length) {
   // Check the Value field.
   if (!subtable->Skip(2)) {
     return OTS_FAILURE();
@@ -67,7 +66,7 @@ bool ParseMathValueRecord(const ots::Font *font,
     if (offset >= length) {
       return OTS_FAILURE();
     }
-    if (!ots::ParseDeviceTable(font, data + offset, length - offset)) {
+    if (!ots::ParseDeviceTable(GetFont(), data + offset, length - offset)) {
       return OTS_FAILURE();
     }
   }
@@ -75,8 +74,8 @@ bool ParseMathValueRecord(const ots::Font *font,
   return true;
 }
 
-bool ParseMathConstantsTable(const ots::Font *font,
-                             const uint8_t *data, size_t length) {
+bool OpenTypeMATH::ParseMathConstantsTable(const uint8_t *data,
+                                           size_t length) {
   ots::Buffer subtable(data, length);
 
   // Part 1: int16 or uint16 constants.
@@ -146,7 +145,7 @@ bool ParseMathConstantsTable(const ots::Font *font,
   //
   // RadicalKernAfterDegree
   for (unsigned i = 0; i < static_cast<unsigned>(51); ++i) {
-    if (!ParseMathValueRecord(font, &subtable, data, length)) {
+    if (!ParseMathValueRecord(&subtable, data, length)) {
       return OTS_FAILURE();
     }
   }
@@ -160,11 +159,10 @@ bool ParseMathConstantsTable(const ots::Font *font,
   return true;
 }
 
-bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font,
-                                           ots::Buffer* subtable,
-                                           const uint8_t *data,
-                                           const size_t length,
-                                           const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseMathValueRecordSequenceForGlyphs(ots::Buffer* subtable,
+                                                         const uint8_t *data,
+                                                         const size_t length,
+                                                         const uint16_t num_glyphs) {
   // Check the header.
   uint16_t offset_coverage = 0;
   uint16_t sequence_count = 0;
@@ -183,7 +181,7 @@ bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font,
   if (offset_coverage < sequence_end || offset_coverage >= length) {
     return OTS_FAILURE();
   }
-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+  if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage,
                                length - offset_coverage,
                                num_glyphs, sequence_count)) {
     return OTS_FAILURE();
@@ -191,7 +189,7 @@ bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font,
 
   // Check sequence.
   for (unsigned i = 0; i < sequence_count; ++i) {
-    if (!ParseMathValueRecord(font, subtable, data, length)) {
+    if (!ParseMathValueRecord(subtable, data, length)) {
       return OTS_FAILURE();
     }
   }
@@ -199,26 +197,23 @@ bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font,
   return true;
 }
 
-bool ParseMathItalicsCorrectionInfoTable(const ots::Font *font,
-                                         const uint8_t *data,
-                                         size_t length,
-                                         const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseMathItalicsCorrectionInfoTable(const uint8_t *data,
+                                                       size_t length,
+                                                       const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
-  return ParseMathValueRecordSequenceForGlyphs(font, &subtable, data, length,
+  return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length,
                                                num_glyphs);
 }
 
-bool ParseMathTopAccentAttachmentTable(const ots::Font *font,
-                                       const uint8_t *data,
-                                       size_t length,
-                                       const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseMathTopAccentAttachmentTable(const uint8_t *data,
+                                                     size_t length,
+                                                     const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
-  return ParseMathValueRecordSequenceForGlyphs(font, &subtable, data, length,
+  return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length,
                                                num_glyphs);
 }
 
-bool ParseMathKernTable(const ots::Font *font,
-                        const uint8_t *data, size_t length) {
+bool OpenTypeMATH::ParseMathKernTable(const uint8_t *data, size_t length) {
   ots::Buffer subtable(data, length);
 
   // Check the Height count.
@@ -229,14 +224,14 @@ bool ParseMathKernTable(const ots::Font *font,
 
   // Check the Correction Heights.
   for (unsigned i = 0; i < height_count; ++i) {
-    if (!ParseMathValueRecord(font, &subtable, data, length)) {
+    if (!ParseMathValueRecord(&subtable, data, length)) {
       return OTS_FAILURE();
     }
   }
 
   // Check the Kern Values.
   for (unsigned i = 0; i <= height_count; ++i) {
-    if (!ParseMathValueRecord(font, &subtable, data, length)) {
+    if (!ParseMathValueRecord(&subtable, data, length)) {
       return OTS_FAILURE();
     }
   }
@@ -244,9 +239,9 @@ bool ParseMathKernTable(const ots::Font *font,
   return true;
 }
 
-bool ParseMathKernInfoTable(const ots::Font *font,
-                            const uint8_t *data, size_t length,
-                            const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseMathKernInfoTable(const uint8_t *data,
+                                          size_t length,
+                                          const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Check the header.
@@ -267,7 +262,7 @@ bool ParseMathKernInfoTable(const ots::Font *font,
   if (offset_coverage < sequence_end || offset_coverage >= length) {
     return OTS_FAILURE();
   }
-  if (!ots::ParseCoverageTable(font, data + offset_coverage, length - offset_coverage,
+  if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, length - offset_coverage,
                                num_glyphs, sequence_count)) {
     return OTS_FAILURE();
   }
@@ -282,7 +277,7 @@ bool ParseMathKernInfoTable(const ots::Font *font,
       }
       if (offset_math_kern) {
         if (offset_math_kern < sequence_end || offset_math_kern >= length ||
-            !ParseMathKernTable(font, data + offset_math_kern,
+            !ParseMathKernTable(data + offset_math_kern,
                                 length - offset_math_kern)) {
           return OTS_FAILURE();
         }
@@ -293,9 +288,9 @@ bool ParseMathKernInfoTable(const ots::Font *font,
   return true;
 }
 
-bool ParseMathGlyphInfoTable(const ots::Font *font,
-                             const uint8_t *data, size_t length,
-                             const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseMathGlyphInfoTable(const uint8_t *data,
+                                           size_t length,
+                                           const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Check Header.
@@ -318,7 +313,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font,
     if (offset_math_italics_correction_info >= length ||
         offset_math_italics_correction_info < kMathGlyphInfoHeaderSize ||
         !ParseMathItalicsCorrectionInfoTable(
-            font, data + offset_math_italics_correction_info,
+            data + offset_math_italics_correction_info,
             length - offset_math_italics_correction_info,
             num_glyphs)) {
       return OTS_FAILURE();
@@ -327,7 +322,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font,
   if (offset_math_top_accent_attachment) {
     if (offset_math_top_accent_attachment >= length ||
         offset_math_top_accent_attachment < kMathGlyphInfoHeaderSize ||
-        !ParseMathTopAccentAttachmentTable(font, data +
+        !ParseMathTopAccentAttachmentTable(data +
                                            offset_math_top_accent_attachment,
                                            length -
                                            offset_math_top_accent_attachment,
@@ -338,7 +333,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font,
   if (offset_extended_shaped_coverage) {
     if (offset_extended_shaped_coverage >= length ||
         offset_extended_shaped_coverage < kMathGlyphInfoHeaderSize ||
-        !ots::ParseCoverageTable(font, data + offset_extended_shaped_coverage,
+        !ots::ParseCoverageTable(GetFont(), data + offset_extended_shaped_coverage,
                                  length - offset_extended_shaped_coverage,
                                  num_glyphs)) {
       return OTS_FAILURE();
@@ -347,7 +342,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font,
   if (offset_math_kern_info) {
     if (offset_math_kern_info >= length ||
         offset_math_kern_info < kMathGlyphInfoHeaderSize ||
-        !ParseMathKernInfoTable(font, data + offset_math_kern_info,
+        !ParseMathKernInfoTable(data + offset_math_kern_info,
                                 length - offset_math_kern_info, num_glyphs)) {
       return OTS_FAILURE();
     }
@@ -356,14 +351,14 @@ bool ParseMathGlyphInfoTable(const ots::Font *font,
   return true;
 }
 
-bool ParseGlyphAssemblyTable(const ots::Font *font,
-                             const uint8_t *data,
-                             size_t length, const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseGlyphAssemblyTable(const uint8_t *data,
+                                           size_t length,
+                                           const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Check the header.
   uint16_t part_count = 0;
-  if (!ParseMathValueRecord(font, &subtable, data, length) ||
+  if (!ParseMathValueRecord(&subtable, data, length) ||
       !subtable.ReadU16(&part_count)) {
     return OTS_FAILURE();
   }
@@ -384,19 +379,19 @@ bool ParseGlyphAssemblyTable(const ots::Font *font,
       return OTS_FAILURE();
     }
     if (glyph >= num_glyphs) {
-      return OTS_FAILURE_MSG("bad glyph ID: %u", glyph);
+      return Error("bad glyph ID: %u", glyph);
     }
     if (part_flags & ~0x00000001) {
-      return OTS_FAILURE_MSG("unknown part flag: %u", part_flags);
+      return Error("unknown part flag: %u", part_flags);
     }
   }
 
   return true;
 }
 
-bool ParseMathGlyphConstructionTable(const ots::Font *font,
-                                     const uint8_t *data,
-                                     size_t length, const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseMathGlyphConstructionTable(const uint8_t *data,
+                                                   size_t length,
+                                                   const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Check the header.
@@ -419,7 +414,7 @@ bool ParseMathGlyphConstructionTable(const ots::Font *font,
         offset_glyph_assembly < sequence_end) {
       return OTS_FAILURE();
     }
-    if (!ParseGlyphAssemblyTable(font, data + offset_glyph_assembly,
+    if (!ParseGlyphAssemblyTable(data + offset_glyph_assembly,
                                  length - offset_glyph_assembly, num_glyphs)) {
       return OTS_FAILURE();
     }
@@ -433,21 +428,20 @@ bool ParseMathGlyphConstructionTable(const ots::Font *font,
       return OTS_FAILURE();
     }
     if (glyph >= num_glyphs) {
-      return OTS_FAILURE_MSG("bad glyph ID: %u", glyph);
+      return Error("bad glyph ID: %u", glyph);
     }
   }
 
   return true;
 }
 
-bool ParseMathGlyphConstructionSequence(const ots::Font *font,
-                                        ots::Buffer* subtable,
-                                        const uint8_t *data,
-                                        size_t length,
-                                        const uint16_t num_glyphs,
-                                        uint16_t offset_coverage,
-                                        uint16_t glyph_count,
-                                        const unsigned sequence_end) {
+bool OpenTypeMATH::ParseMathGlyphConstructionSequence(ots::Buffer* subtable,
+                                                      const uint8_t *data,
+                                                      size_t length,
+                                                      const uint16_t num_glyphs,
+                                                      uint16_t offset_coverage,
+                                                      uint16_t glyph_count,
+                                                      const unsigned sequence_end) {
   // Zero glyph count, nothing to parse.
   if (!glyph_count) {
     return true;
@@ -457,7 +451,7 @@ bool ParseMathGlyphConstructionSequence(const ots::Font *font,
   if (offset_coverage < sequence_end || offset_coverage >= length) {
     return OTS_FAILURE();
   }
-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+  if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage,
                                length - offset_coverage,
                                num_glyphs, glyph_count)) {
     return OTS_FAILURE();
@@ -471,7 +465,7 @@ bool ParseMathGlyphConstructionSequence(const ots::Font *font,
       }
       if (offset_glyph_construction < sequence_end ||
           offset_glyph_construction >= length ||
-          !ParseMathGlyphConstructionTable(font, data + offset_glyph_construction,
+          !ParseMathGlyphConstructionTable(data + offset_glyph_construction,
                                            length - offset_glyph_construction,
                                            num_glyphs)) {
         return OTS_FAILURE();
@@ -481,9 +475,9 @@ bool ParseMathGlyphConstructionSequence(const ots::Font *font,
   return true;
 }
 
-bool ParseMathVariantsTable(const ots::Font *font,
-                            const uint8_t *data,
-                            size_t length, const uint16_t num_glyphs) {
+bool OpenTypeMATH::ParseMathVariantsTable(const uint8_t *data,
+                                          size_t length,
+                                          const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Check the header.
@@ -505,11 +499,11 @@ bool ParseMathVariantsTable(const ots::Font *font,
     return OTS_FAILURE();
   }
 
-  if (!ParseMathGlyphConstructionSequence(font, &subtable, data, length, num_glyphs,
+  if (!ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs,
                                           offset_vert_glyph_coverage,
                                           vert_glyph_count,
                                           sequence_end) ||
-      !ParseMathGlyphConstructionSequence(font, &subtable, data, length, num_glyphs,
+      !ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs,
                                           offset_horiz_glyph_coverage,
                                           horiz_glyph_count,
                                           sequence_end)) {
@@ -519,37 +513,24 @@ bool ParseMathVariantsTable(const ots::Font *font,
   return true;
 }
 
-}  // namespace
-
-#define DROP_THIS_TABLE(msg_) \
-  do { \
-    OTS_FAILURE_MSG(msg_ ", table discarded"); \
-    font->math->data = 0; \
-    font->math->length = 0; \
-  } while (0)
-
-namespace ots {
-
-bool ots_math_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeMATH::Parse(const uint8_t *data, size_t length) {
   // Grab the number of glyphs in the font from the maxp table to check
   // GlyphIDs in MATH table.
-  if (!font->maxp) {
-    return OTS_FAILURE();
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return Error("Required maxp table missing");
   }
-  const uint16_t num_glyphs = font->maxp->num_glyphs;
+  const uint16_t num_glyphs = maxp->num_glyphs;
 
   Buffer table(data, length);
 
-  OpenTypeMATH* math = new OpenTypeMATH;
-  font->math = math;
-
   uint32_t version = 0;
   if (!table.ReadU32(&version)) {
     return OTS_FAILURE();
   }
   if (version != 0x00010000) {
-    DROP_THIS_TABLE("bad MATH version");
-    return true;
+    return Drop("bad MATH version");
   }
 
   uint16_t offset_math_constants = 0;
@@ -567,53 +548,37 @@ bool ots_math_parse(Font *font, const uint8_t *data, size_t length) {
       offset_math_glyph_info < kMathHeaderSize ||
       offset_math_variants >= length ||
       offset_math_variants < kMathHeaderSize) {
-    DROP_THIS_TABLE("bad offset in MATH header");
-    return true;
+    return Drop("bad offset in MATH header");
   }
 
-  if (!ParseMathConstantsTable(font, data + offset_math_constants,
+  if (!ParseMathConstantsTable(data + offset_math_constants,
                                length - offset_math_constants)) {
-    DROP_THIS_TABLE("failed to parse MathConstants table");
-    return true;
+    return Drop("failed to parse MathConstants table");
   }
-  if (!ParseMathGlyphInfoTable(font, data + offset_math_glyph_info,
+  if (!ParseMathGlyphInfoTable(data + offset_math_glyph_info,
                                length - offset_math_glyph_info, num_glyphs)) {
-    DROP_THIS_TABLE("failed to parse MathGlyphInfo table");
-    return true;
+    return Drop("failed to parse MathGlyphInfo table");
   }
-  if (!ParseMathVariantsTable(font, data + offset_math_variants,
+  if (!ParseMathVariantsTable(data + offset_math_variants,
                               length - offset_math_variants, num_glyphs)) {
-    DROP_THIS_TABLE("failed to parse MathVariants table");
-    return true;
+    return Drop("failed to parse MathVariants table");
   }
 
-  math->data = data;
-  math->length = length;
+  this->m_data = data;
+  this->m_length = length;
   return true;
 }
 
-bool ots_math_should_serialise(Font *font) {
-  return font->math != NULL && font->math->data != NULL;
-}
-
-bool ots_math_serialise(OTSStream *out, Font *font) {
-  if (!out->Write(font->math->data, font->math->length)) {
+bool OpenTypeMATH::Serialize(OTSStream *out) {
+  if (!out->Write(this->m_data, this->m_length)) {
     return OTS_FAILURE();
   }
 
   return true;
 }
 
-void ots_math_reuse(Font *font, Font *other) {
-  font->math = other->math;
-  font->math_reused = true;
-}
-
-void ots_math_free(Font *font) {
-  delete font->math;
+bool OpenTypeMATH::ShouldSerialize() {
+  return Table::ShouldSerialize() && this->m_data != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
-#undef DROP_THIS_TABLE
diff --git a/gfx/ots/src/math_.h b/gfx/ots/src/math_.h
index 91c54dbe1..875cacd4d 100644
--- a/gfx/ots/src/math_.h
+++ b/gfx/ots/src/math_.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Copyright (c) 2014-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,14 +9,58 @@
 
 namespace ots {
 
-struct OpenTypeMATH {
-  OpenTypeMATH()
-      : data(NULL),
-        length(0) {
-  }
+class OpenTypeMATH : public Table {
+ public:
+  explicit OpenTypeMATH(Font *font, uint32_t tag)
+      : Table(font, tag, tag),
+        m_data(NULL),
+        m_length(0) { }
 
-  const uint8_t *data;
-  size_t length;
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
+  bool ParseMathValueRecord(ots::Buffer* subtable,
+                            const uint8_t *data,
+                            const size_t length);
+  bool ParseMathConstantsTable(const uint8_t *data, size_t length);
+  bool ParseMathValueRecordSequenceForGlyphs(ots::Buffer* subtable,
+                                             const uint8_t *data,
+                                             const size_t length,
+                                             const uint16_t num_glyphs);
+  bool ParseMathItalicsCorrectionInfoTable(const uint8_t *data,
+                                           size_t length,
+                                           const uint16_t num_glyphs);
+  bool ParseMathTopAccentAttachmentTable(const uint8_t *data,
+                                         size_t length,
+                                         const uint16_t num_glyphs);
+  bool ParseMathKernTable(const uint8_t *data, size_t length);
+  bool ParseMathKernInfoTable(const uint8_t *data,
+                              size_t length,
+                              const uint16_t num_glyphs);
+  bool ParseMathGlyphInfoTable(const uint8_t *data,
+                               size_t length,
+                               const uint16_t num_glyphs);
+  bool ParseGlyphAssemblyTable(const uint8_t *data,
+                               size_t length,
+                               const uint16_t num_glyphs);
+  bool ParseMathGlyphConstructionTable(const uint8_t *data,
+                                       size_t length,
+                                       const uint16_t num_glyphs);
+  bool ParseMathGlyphConstructionSequence(ots::Buffer* subtable,
+                                          const uint8_t *data,
+                                          size_t length,
+                                          const uint16_t num_glyphs,
+                                          uint16_t offset_coverage,
+                                          uint16_t glyph_count,
+                                          const unsigned sequence_end);
+  bool ParseMathVariantsTable(const uint8_t *data,
+                              size_t length,
+                              const uint16_t num_glyphs);
+
+  const uint8_t *m_data;
+  size_t m_length;
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/maxp.cc b/gfx/ots/src/maxp.cc
index 41e29a745..232e4a988 100644
--- a/gfx/ots/src/maxp.cc
+++ b/gfx/ots/src/maxp.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,119 +7,97 @@
 // maxp - Maximum Profile
 // http://www.microsoft.com/typography/otspec/maxp.htm
 
-#define TABLE_NAME "maxp"
-
 namespace ots {
 
-bool ots_maxp_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeMAXP::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypeMAXP *maxp = new OpenTypeMAXP;
-  font->maxp = maxp;
-
   uint32_t version = 0;
   if (!table.ReadU32(&version)) {
-    return OTS_FAILURE_MSG("Failed to read version of maxp table");
+    return Error("Failed to read table version");
   }
 
   if (version >> 16 > 1) {
-    return OTS_FAILURE_MSG("Bad maxp version %d", version);
+    return Error("Unsupported table version 0x%x", version);
   }
 
-  if (!table.ReadU16(&maxp->num_glyphs)) {
-    return OTS_FAILURE_MSG("Failed to read number of glyphs from maxp table");
+  if (!table.ReadU16(&this->num_glyphs)) {
+    return Error("Failed to read numGlyphs");
   }
 
-  if (!maxp->num_glyphs) {
-    return OTS_FAILURE_MSG("Bad number of glyphs 0 in maxp table");
+  if (!this->num_glyphs) {
+    return Error("numGlyphs is 0");
   }
 
   if (version >> 16 == 1) {
-    maxp->version_1 = true;
-    if (!table.ReadU16(&maxp->max_points) ||
-        !table.ReadU16(&maxp->max_contours) ||
-        !table.ReadU16(&maxp->max_c_points) ||
-        !table.ReadU16(&maxp->max_c_contours) ||
-        !table.ReadU16(&maxp->max_zones) ||
-        !table.ReadU16(&maxp->max_t_points) ||
-        !table.ReadU16(&maxp->max_storage) ||
-        !table.ReadU16(&maxp->max_fdefs) ||
-        !table.ReadU16(&maxp->max_idefs) ||
-        !table.ReadU16(&maxp->max_stack) ||
-        !table.ReadU16(&maxp->max_size_glyf_instructions) ||
-        !table.ReadU16(&maxp->max_c_components) ||
-        !table.ReadU16(&maxp->max_c_depth)) {
-      return OTS_FAILURE_MSG("Failed to read maxp table");
+    this->version_1 = true;
+    if (!table.ReadU16(&this->max_points) ||
+        !table.ReadU16(&this->max_contours) ||
+        !table.ReadU16(&this->max_c_points) ||
+        !table.ReadU16(&this->max_c_contours) ||
+        !table.ReadU16(&this->max_zones) ||
+        !table.ReadU16(&this->max_t_points) ||
+        !table.ReadU16(&this->max_storage) ||
+        !table.ReadU16(&this->max_fdefs) ||
+        !table.ReadU16(&this->max_idefs) ||
+        !table.ReadU16(&this->max_stack) ||
+        !table.ReadU16(&this->max_size_glyf_instructions) ||
+        !table.ReadU16(&this->max_c_components) ||
+        !table.ReadU16(&this->max_c_depth)) {
+      return Error("Failed to read version 1 table data");
     }
 
-    if (maxp->max_zones == 0) {
+    if (this->max_zones == 0) {
       // workaround for ipa*.ttf Japanese fonts.
-      OTS_WARNING("bad max_zones: %u", maxp->max_zones);
-      maxp->max_zones = 1;
-    } else if (maxp->max_zones == 3) {
+      Warning("Bad maxZones: %u", this->max_zones);
+      this->max_zones = 1;
+    } else if (this->max_zones == 3) {
       // workaround for Ecolier-*.ttf fonts.
-      OTS_WARNING("bad max_zones: %u", maxp->max_zones);
-      maxp->max_zones = 2;
+      Warning("Bad maxZones: %u", this->max_zones);
+      this->max_zones = 2;
     }
 
-    if ((maxp->max_zones != 1) && (maxp->max_zones != 2)) {
-      return OTS_FAILURE_MSG("Bad max zones %d in maxp", maxp->max_zones);
+    if ((this->max_zones != 1) && (this->max_zones != 2)) {
+      return Error("Bad maxZones: %u", this->max_zones);
     }
   } else {
-    maxp->version_1 = false;
+    this->version_1 = false;
   }
 
   return true;
 }
 
-bool ots_maxp_should_serialise(Font *font) {
-  return font->maxp != NULL;
-}
-
-bool ots_maxp_serialise(OTSStream *out, Font *font) {
-  const OpenTypeMAXP *maxp = font->maxp;
-
-  if (!out->WriteU32(maxp->version_1 ? 0x00010000 : 0x00005000) ||
-      !out->WriteU16(maxp->num_glyphs)) {
-    return OTS_FAILURE_MSG("Failed to write maxp version or number of glyphs");
+bool OpenTypeMAXP::Serialize(OTSStream *out) {
+  if (!out->WriteU32(this->version_1 ? 0x00010000 : 0x00005000) ||
+      !out->WriteU16(this->num_glyphs)) {
+    return Error("Failed to write version or numGlyphs");
   }
 
-  if (!maxp->version_1) return true;
+  if (!this->version_1) return true;
 
-  if (!out->WriteU16(maxp->max_points) ||
-      !out->WriteU16(maxp->max_contours) ||
-      !out->WriteU16(maxp->max_c_points) ||
-      !out->WriteU16(maxp->max_c_contours)) {
-    return OTS_FAILURE_MSG("Failed to write maxp");
+  if (!out->WriteU16(this->max_points) ||
+      !out->WriteU16(this->max_contours) ||
+      !out->WriteU16(this->max_c_points) ||
+      !out->WriteU16(this->max_c_contours)) {
+    return Error("Failed to write maxp");
   }
 
-  if (!out->WriteU16(maxp->max_zones) ||
-      !out->WriteU16(maxp->max_t_points) ||
-      !out->WriteU16(maxp->max_storage) ||
-      !out->WriteU16(maxp->max_fdefs) ||
-      !out->WriteU16(maxp->max_idefs) ||
-      !out->WriteU16(maxp->max_stack) ||
-      !out->WriteU16(maxp->max_size_glyf_instructions)) {
-    return OTS_FAILURE_MSG("Failed to write more maxp");
+  if (!out->WriteU16(this->max_zones) ||
+      !out->WriteU16(this->max_t_points) ||
+      !out->WriteU16(this->max_storage) ||
+      !out->WriteU16(this->max_fdefs) ||
+      !out->WriteU16(this->max_idefs) ||
+      !out->WriteU16(this->max_stack) ||
+      !out->WriteU16(this->max_size_glyf_instructions)) {
+    return Error("Failed to write more maxp");
   }
 
-  if (!out->WriteU16(maxp->max_c_components) ||
-      !out->WriteU16(maxp->max_c_depth)) {
-    return OTS_FAILURE_MSG("Failed to write yet more maxp");
+  if (!out->WriteU16(this->max_c_components) ||
+      !out->WriteU16(this->max_c_depth)) {
+    return Error("Failed to write yet more maxp");
   }
 
   return true;
 }
 
-void ots_maxp_reuse(Font *font, Font *other) {
-  font->maxp = other->maxp;
-  font->maxp_reused = true;
-}
-
-void ots_maxp_free(Font *font) {
-  delete font->maxp;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/maxp.h b/gfx/ots/src/maxp.h
index efca0c91a..99dbdc439 100644
--- a/gfx/ots/src/maxp.h
+++ b/gfx/ots/src/maxp.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,7 +9,14 @@
 
 namespace ots {
 
-struct OpenTypeMAXP {
+class OpenTypeMAXP : public Table {
+ public:
+  explicit OpenTypeMAXP(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   uint16_t num_glyphs;
   bool version_1;
 
diff --git a/gfx/ots/src/metrics.cc b/gfx/ots/src/metrics.cc
index cd89f4555..b49d73c5d 100644
--- a/gfx/ots/src/metrics.cc
+++ b/gfx/ots/src/metrics.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -11,149 +11,163 @@
 // http://www.microsoft.com/typography/otspec/hhea.htm
 // http://www.microsoft.com/typography/otspec/vhea.htm
 
-#define TABLE_NAME "metrics" // XXX: use individual table names
-
 namespace ots {
 
-bool ParseMetricsHeader(Font *font, Buffer *table,
-                        OpenTypeMetricsHeader *header) {
-  if (!table->ReadS16(&header->ascent) ||
-      !table->ReadS16(&header->descent) ||
-      !table->ReadS16(&header->linegap) ||
-      !table->ReadU16(&header->adv_width_max) ||
-      !table->ReadS16(&header->min_sb1) ||
-      !table->ReadS16(&header->min_sb2) ||
-      !table->ReadS16(&header->max_extent) ||
-      !table->ReadS16(&header->caret_slope_rise) ||
-      !table->ReadS16(&header->caret_slope_run) ||
-      !table->ReadS16(&header->caret_offset)) {
-    return OTS_FAILURE_MSG("Failed to read metrics header");
+bool OpenTypeMetricsHeader::Parse(const uint8_t *data, size_t length) {
+  Buffer table(data, length);
+
+  // Skip already read version.
+  if (!table.Skip(4)) {
+    return false;
+  }
+
+  if (!table.ReadS16(&this->ascent) ||
+      !table.ReadS16(&this->descent) ||
+      !table.ReadS16(&this->linegap) ||
+      !table.ReadU16(&this->adv_width_max) ||
+      !table.ReadS16(&this->min_sb1) ||
+      !table.ReadS16(&this->min_sb2) ||
+      !table.ReadS16(&this->max_extent) ||
+      !table.ReadS16(&this->caret_slope_rise) ||
+      !table.ReadS16(&this->caret_slope_run) ||
+      !table.ReadS16(&this->caret_offset)) {
+    return Error("Failed to read table");
   }
 
-  if (header->ascent < 0) {
-    OTS_WARNING("bad ascent: %d", header->ascent);
-    header->ascent = 0;
+  if (this->ascent < 0) {
+    Warning("bad ascent: %d", this->ascent);
+    this->ascent = 0;
   }
-  if (header->linegap < 0) {
-    OTS_WARNING("bad linegap: %d", header->linegap);
-    header->linegap = 0;
+  if (this->linegap < 0) {
+    Warning("bad linegap: %d", this->linegap);
+    this->linegap = 0;
   }
 
-  if (!font->head) {
-    return OTS_FAILURE_MSG("Missing head font table");
+  OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>(
+      GetFont()->GetTypedTable(OTS_TAG_HEAD));
+  if (!head) {
+    return Error("Missing head font table");
   }
 
   // if the font is non-slanted, caret_offset should be zero.
-  if (!(font->head->mac_style & 2) &&
-      (header->caret_offset != 0)) {
-    OTS_WARNING("bad caret offset: %d", header->caret_offset);
-    header->caret_offset = 0;
+  if (!(head->mac_style & 2) &&
+      (this->caret_offset != 0)) {
+    Warning("bad caret offset: %d", this->caret_offset);
+    this->caret_offset = 0;
   }
 
   // skip the reserved bytes
-  if (!table->Skip(8)) {
-    return OTS_FAILURE_MSG("Failed to skip reserverd bytes");
+  if (!table.Skip(8)) {
+    return Error("Failed to read reserverd bytes");
   }
 
   int16_t data_format;
-  if (!table->ReadS16(&data_format)) {
-    return OTS_FAILURE_MSG("Failed to read data format");
+  if (!table.ReadS16(&data_format)) {
+    return Error("Failed to read metricDataFormat");
   }
   if (data_format) {
-    return OTS_FAILURE_MSG("Bad data format %d", data_format);
+    return Error("Bad metricDataFormat: %d", data_format);
   }
 
-  if (!table->ReadU16(&header->num_metrics)) {
-    return OTS_FAILURE_MSG("Failed to read number of metrics");
+  if (!table.ReadU16(&this->num_metrics)) {
+    return Error("Failed to read number of metrics");
   }
 
-  if (!font->maxp) {
-    return OTS_FAILURE_MSG("Missing maxp font table");
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return Error("Missing maxp font table");
   }
 
-  if (header->num_metrics > font->maxp->num_glyphs) {
-    return OTS_FAILURE_MSG("Bad number of metrics %d", header->num_metrics);
+  if (this->num_metrics > maxp->num_glyphs) {
+    return Error("Bad number of metrics %d", this->num_metrics);
   }
 
   return true;
 }
 
-bool SerialiseMetricsHeader(const ots::Font *font,
-                            OTSStream *out,
-                            const OpenTypeMetricsHeader *header) {
-  if (!out->WriteU32(header->version) ||
-      !out->WriteS16(header->ascent) ||
-      !out->WriteS16(header->descent) ||
-      !out->WriteS16(header->linegap) ||
-      !out->WriteU16(header->adv_width_max) ||
-      !out->WriteS16(header->min_sb1) ||
-      !out->WriteS16(header->min_sb2) ||
-      !out->WriteS16(header->max_extent) ||
-      !out->WriteS16(header->caret_slope_rise) ||
-      !out->WriteS16(header->caret_slope_run) ||
-      !out->WriteS16(header->caret_offset) ||
+bool OpenTypeMetricsHeader::Serialize(OTSStream *out) {
+  if (!out->WriteU32(this->version) ||
+      !out->WriteS16(this->ascent) ||
+      !out->WriteS16(this->descent) ||
+      !out->WriteS16(this->linegap) ||
+      !out->WriteU16(this->adv_width_max) ||
+      !out->WriteS16(this->min_sb1) ||
+      !out->WriteS16(this->min_sb2) ||
+      !out->WriteS16(this->max_extent) ||
+      !out->WriteS16(this->caret_slope_rise) ||
+      !out->WriteS16(this->caret_slope_run) ||
+      !out->WriteS16(this->caret_offset) ||
       !out->WriteR64(0) ||  // reserved
       !out->WriteS16(0) ||  // metric data format
-      !out->WriteU16(header->num_metrics)) {
-    return OTS_FAILURE_MSG("Failed to write metrics");
+      !out->WriteU16(this->num_metrics)) {
+    return Error("Failed to write metrics");
   }
 
   return true;
 }
 
-bool ParseMetricsTable(const ots::Font *font,
-                       Buffer *table,
-                       const uint16_t num_glyphs,
-                       const OpenTypeMetricsHeader *header,
-                       OpenTypeMetricsTable *metrics) {
+bool OpenTypeMetricsTable::Parse(const uint8_t *data, size_t length) {
+  Buffer table(data, length);
+
+  // OpenTypeMetricsHeader is a superclass of both 'hhea' and 'vhea',
+  // so the cast here is OK, whichever m_header_tag we have.
+  OpenTypeMetricsHeader *header = static_cast<OpenTypeMetricsHeader*>(
+      GetFont()->GetTypedTable(m_header_tag));
+  if (!header) {
+    return Error("Required %c%c%c%c table missing", OTS_UNTAG(m_header_tag));
+  }
   // |num_metrics| is a uint16_t, so it's bounded < 65536. This limits that
   // amount of memory that we'll allocate for this to a sane amount.
   const unsigned num_metrics = header->num_metrics;
 
-  if (num_metrics > num_glyphs) {
-    return OTS_FAILURE_MSG("Bad number of metrics %d", num_metrics);
+  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+      GetFont()->GetTypedTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return Error("Required maxp table missing");
+  }
+  if (num_metrics > maxp->num_glyphs) {
+    return Error("Bad number of metrics %d", num_metrics);
   }
   if (!num_metrics) {
-    return OTS_FAILURE_MSG("No metrics!");
+    return Error("No metrics!");
   }
-  const unsigned num_sbs = num_glyphs - num_metrics;
+  const unsigned num_sbs = maxp->num_glyphs - num_metrics;
 
-  metrics->entries.reserve(num_metrics);
+  this->entries.reserve(num_metrics);
   for (unsigned i = 0; i < num_metrics; ++i) {
     uint16_t adv = 0;
     int16_t sb = 0;
-    if (!table->ReadU16(&adv) || !table->ReadS16(&sb)) {
-      return OTS_FAILURE_MSG("Failed to read metric %d", i);
+    if (!table.ReadU16(&adv) || !table.ReadS16(&sb)) {
+      return Error("Failed to read metric %d", i);
     }
-    metrics->entries.push_back(std::make_pair(adv, sb));
+    this->entries.push_back(std::make_pair(adv, sb));
   }
 
-  metrics->sbs.reserve(num_sbs);
+  this->sbs.reserve(num_sbs);
   for (unsigned i = 0; i < num_sbs; ++i) {
     int16_t sb;
-    if (!table->ReadS16(&sb)) {
+    if (!table.ReadS16(&sb)) {
       // Some Japanese fonts (e.g., mona.ttf) fail this test.
-      return OTS_FAILURE_MSG("Failed to read side bearing %d", i + num_metrics);
+      return Error("Failed to read side bearing %d", i + num_metrics);
     }
-    metrics->sbs.push_back(sb);
+    this->sbs.push_back(sb);
   }
 
   return true;
 }
 
-bool SerialiseMetricsTable(const ots::Font *font,
-                           OTSStream *out,
-                           const OpenTypeMetricsTable *metrics) {
-  for (unsigned i = 0; i < metrics->entries.size(); ++i) {
-    if (!out->WriteU16(metrics->entries[i].first) ||
-        !out->WriteS16(metrics->entries[i].second)) {
-      return OTS_FAILURE_MSG("Failed to write metric %d", i);
+bool OpenTypeMetricsTable::Serialize(OTSStream *out) {
+  for (unsigned i = 0; i < this->entries.size(); ++i) {
+    if (!out->WriteU16(this->entries[i].first) ||
+        !out->WriteS16(this->entries[i].second)) {
+      return Error("Failed to write metric %d", i);
     }
   }
 
-  for (unsigned i = 0; i < metrics->sbs.size(); ++i) {
-    if (!out->WriteS16(metrics->sbs[i])) {
-      return OTS_FAILURE_MSG("Failed to write side bearing %ld", i + metrics->entries.size());
+  for (unsigned i = 0; i < this->sbs.size(); ++i) {
+    if (!out->WriteS16(this->sbs[i])) {
+      return Error("Failed to write side bearing %ld", i + this->entries.size());
     }
   }
 
@@ -161,5 +175,3 @@ bool SerialiseMetricsTable(const ots::Font *font,
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/metrics.h b/gfx/ots/src/metrics.h
index 767be2fdb..efe14c070 100644
--- a/gfx/ots/src/metrics.h
+++ b/gfx/ots/src/metrics.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -13,7 +13,14 @@
 
 namespace ots {
 
-struct OpenTypeMetricsHeader {
+class OpenTypeMetricsHeader : public Table {
+ public:
+  explicit OpenTypeMetricsHeader(Font *font, uint32_t tag, uint32_t type)
+      : Table(font, tag, type) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
   uint32_t version;
   int16_t ascent;
   int16_t descent;
@@ -28,27 +35,22 @@ struct OpenTypeMetricsHeader {
   uint16_t num_metrics;
 };
 
-struct OpenTypeMetricsTable {
+struct OpenTypeMetricsTable : public Table {
+ public:
+  explicit OpenTypeMetricsTable(Font *font, uint32_t tag, uint32_t type,
+                                uint32_t header_tag)
+      : Table(font, tag, type), m_header_tag(header_tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
+ private:
+  uint32_t m_header_tag;
+
   std::vector<std::pair<uint16_t, int16_t> > entries;
   std::vector<int16_t> sbs;
 };
 
-bool ParseMetricsHeader(Font *font, Buffer *table,
-                        OpenTypeMetricsHeader *header);
-bool SerialiseMetricsHeader(const ots::Font *font,
-                            OTSStream *out,
-                            const OpenTypeMetricsHeader *header);
-
-bool ParseMetricsTable(const ots::Font *font,
-                       Buffer *table,
-                       const uint16_t num_glyphs,
-                       const OpenTypeMetricsHeader *header,
-                       OpenTypeMetricsTable *metrics);
-bool SerialiseMetricsTable(const ots::Font *font,
-                           OTSStream *out,
-                           const OpenTypeMetricsTable *metrics);
-
 }  // namespace ots
 
 #endif  // OTS_METRICS_H_
-
diff --git a/gfx/ots/src/moz.build b/gfx/ots/src/moz.build
index f8e60d5f8..ce217bcda 100644
--- a/gfx/ots/src/moz.build
+++ b/gfx/ots/src/moz.build
@@ -10,17 +10,8 @@ EXPORTS += [
 ]
 
 SOURCES += [
-    # don't unify sources that use a (file-specific) DROP_THIS_TABLE macro
-    'gasp.cc',
+    # needs to be separate because gpos.cc also defines kMaxClassDefValue
     'gdef.cc',
-    'gpos.cc',
-    'gsub.cc',
-    'hdmx.cc',
-    'kern.cc',
-    'ltsh.cc',
-    'math.cc',
-    'vdmx.cc',
-    'vorg.cc',
 ]
 
 UNIFIED_SOURCES += [
@@ -28,13 +19,23 @@ UNIFIED_SOURCES += [
     'cff_type2_charstring.cc',
     'cmap.cc',
     'cvt.cc',
+    'feat.cc',
     'fpgm.cc',
+    'gasp.cc',
+    'glat.cc',
+    'gloc.cc',
     'glyf.cc',
+    'gpos.cc',
+    'gsub.cc',
+    'hdmx.cc',
     'head.cc',
     'hhea.cc',
     'hmtx.cc',
+    'kern.cc',
     'layout.cc',
     'loca.cc',
+    'ltsh.cc',
+    'math.cc',
     'maxp.cc',
     'metrics.cc',
     'name.cc',
@@ -42,8 +43,13 @@ UNIFIED_SOURCES += [
     'ots.cc',
     'post.cc',
     'prep.cc',
+    'sile.cc',
+    'silf.cc',
+    'sill.cc',
+    'vdmx.cc',
     'vhea.cc',
     'vmtx.cc',
+    'vorg.cc',
 ]
 
 # We allow warnings for third-party code that can be updated from upstream.
@@ -53,8 +59,13 @@ FINAL_LIBRARY = 'gkmedias'
 
 DEFINES['PACKAGE_VERSION'] = '"moz"'
 DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"'
+DEFINES['OTS_GRAPHITE'] = 1
 
 USE_LIBS += [
     'brotli',
     'woff2',
 ]
+
+LOCAL_INCLUDES += [
+    '/modules/woff2/src',
+]
diff --git a/gfx/ots/src/name.cc b/gfx/ots/src/name.cc
index e55be7537..dd1e83206 100644
--- a/gfx/ots/src/name.cc
+++ b/gfx/ots/src/name.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,8 +10,6 @@
 // name - Naming Table
 // http://www.microsoft.com/typography/otspec/name.htm
 
-#define TABLE_NAME "name"
-
 namespace {
 
 bool ValidInPsName(char c) {
@@ -56,25 +54,22 @@ void AssignToUtf16BeFromAscii(std::string* target,
 
 namespace ots {
 
-bool ots_name_parse(Font *font, const uint8_t* data, size_t length) {
+bool OpenTypeNAME::Parse(const uint8_t* data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypeNAME* name = new OpenTypeNAME;
-  font->name = name;
-
   uint16_t format = 0;
   if (!table.ReadU16(&format) || format > 1) {
-    return OTS_FAILURE_MSG("Failed to read name table format or bad format %d", format);
+    return Error("Failed to read table format or bad format %d", format);
   }
 
   uint16_t count = 0;
   if (!table.ReadU16(&count)) {
-    return OTS_FAILURE_MSG("Failed to read name count");
+    return Error("Failed to read name count");
   }
 
   uint16_t string_offset = 0;
   if (!table.ReadU16(&string_offset) || string_offset > length) {
-    return OTS_FAILURE_MSG("Failed to read strings offset");
+    return Error("Failed to read or bad stringOffset");
   }
   const char* string_base = reinterpret_cast<const char*>(data) +
       string_offset;
@@ -94,7 +89,7 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) {
         !table.ReadU16(&rec.name_id) ||
         !table.ReadU16(&name_length) ||
         !table.ReadU16(&name_offset)) {
-      return OTS_FAILURE_MSG("Failed to read name entry %d", i);
+      return Error("Failed to read name entry %d", i);
     }
     // check platform & encoding, discard names with unknown values
     switch (rec.platform_id) {
@@ -148,40 +143,41 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) {
       }
     }
 
-    if (!name->names.empty() && !(name->names.back() < rec)) {
-      OTS_WARNING("name records are not sorted.");
+    if (!this->names.empty() && !(this->names.back() < rec)) {
+      Warning("name records are not sorted.");
       sort_required = true;
     }
 
-    name->names.push_back(rec);
+    this->names.push_back(rec);
+    this->name_ids.insert(rec.name_id);
   }
 
   if (format == 1) {
     // extended name table format with language tags
     uint16_t lang_tag_count;
     if (!table.ReadU16(&lang_tag_count)) {
-      return OTS_FAILURE_MSG("Failed to read language tag count");
+      return Error("Failed to read langTagCount");
     }
     for (unsigned i = 0; i < lang_tag_count; ++i) {
       uint16_t tag_length = 0;
       uint16_t tag_offset = 0;
       if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) {
-        return OTS_FAILURE_MSG("Faile to read tag length or offset");
+        return Error("Faile to read length or offset for langTagRecord %d", i);
       }
       const unsigned tag_end = static_cast<unsigned>(string_offset) +
           tag_offset + tag_length;
       if (tag_end > length) {
-        return OTS_FAILURE_MSG("bad end of tag %d > %ld for name entry %d", tag_end, length, i);
+        return Error("bad end of tag %d > %ld for langTagRecord %d", tag_end, length, i);
       }
       std::string tag(string_base + tag_offset, tag_length);
-      name->lang_tags.push_back(tag);
+      this->lang_tags.push_back(tag);
     }
   }
 
   if (table.offset() > string_offset) {
     // the string storage apparently overlapped the name/tag records;
     // consider this font to be badly broken
-    return OTS_FAILURE_MSG("Bad table offset %ld > %d", table.offset(), string_offset);
+    return Error("Bad table offset %ld > %d", table.offset(), string_offset);
   }
 
   // check existence of required name strings (synthesize if necessary)
@@ -207,8 +203,8 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) {
   // if not, we'll add our fixed versions here
   bool mac_name[kStdNameCount] = { 0 };
   bool win_name[kStdNameCount] = { 0 };
-  for (std::vector<NameRecord>::iterator name_iter = name->names.begin();
-       name_iter != name->names.end(); ++name_iter) {
+  for (std::vector<NameRecord>::iterator name_iter = this->names.begin();
+       name_iter != this->names.end(); ++name_iter) {
     const uint16_t id = name_iter->name_id;
     if (id >= kStdNameCount || kStdNames[id] == NULL) {
       continue;
@@ -236,48 +232,42 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) {
                          1033 /* language_id */ , i /* name_id */);
       AssignToUtf16BeFromAscii(&win_rec.text, std::string(kStdNames[i]));
 
-      name->names.push_back(mac_rec);
-      name->names.push_back(win_rec);
+      this->names.push_back(mac_rec);
+      this->names.push_back(win_rec);
       sort_required = true;
     }
   }
 
   if (sort_required) {
-    std::sort(name->names.begin(), name->names.end());
+    std::sort(this->names.begin(), this->names.end());
   }
 
   return true;
 }
 
-bool ots_name_should_serialise(Font *font) {
-  return font->name != NULL;
-}
-
-bool ots_name_serialise(OTSStream* out, Font *font) {
-  const OpenTypeNAME* name = font->name;
-
-  uint16_t name_count = static_cast<uint16_t>(name->names.size());
-  uint16_t lang_tag_count = static_cast<uint16_t>(name->lang_tags.size());
+bool OpenTypeNAME::Serialize(OTSStream* out) {
+  uint16_t name_count = static_cast<uint16_t>(this->names.size());
+  uint16_t lang_tag_count = static_cast<uint16_t>(this->lang_tags.size());
   uint16_t format = 0;
   size_t string_offset = 6 + name_count * 12;
 
-  if (name->lang_tags.size() > 0) {
+  if (this->lang_tags.size() > 0) {
     // lang tags require a format-1 name table
     format = 1;
     string_offset += 2 + lang_tag_count * 4;
   }
   if (string_offset > 0xffff) {
-    return OTS_FAILURE_MSG("Bad string offset %ld", string_offset);
+    return Error("Bad stringOffset: %ld", string_offset);
   }
   if (!out->WriteU16(format) ||
       !out->WriteU16(name_count) ||
       !out->WriteU16(static_cast<uint16_t>(string_offset))) {
-    return OTS_FAILURE_MSG("Failed to write name header");
+    return Error("Failed to write name header");
   }
 
   std::string string_data;
-  for (std::vector<NameRecord>::const_iterator name_iter = name->names.begin();
-       name_iter != name->names.end(); ++name_iter) {
+  for (std::vector<NameRecord>::const_iterator name_iter = this->names.begin();
+       name_iter != this->names.end(); ++name_iter) {
     const NameRecord& rec = *name_iter;
     if (string_data.size() + rec.text.size() >
             std::numeric_limits<uint16_t>::max() ||
@@ -287,44 +277,79 @@ bool ots_name_serialise(OTSStream* out, Font *font) {
         !out->WriteU16(rec.name_id) ||
         !out->WriteU16(static_cast<uint16_t>(rec.text.size())) ||
         !out->WriteU16(static_cast<uint16_t>(string_data.size())) ) {
-      return OTS_FAILURE_MSG("Faile to write name entry");
+      return Error("Faile to write nameRecord");
     }
     string_data.append(rec.text);
   }
 
   if (format == 1) {
     if (!out->WriteU16(lang_tag_count)) {
-      return OTS_FAILURE_MSG("Faile to write language tag count");
+      return Error("Faile to write langTagCount");
     }
     for (std::vector<std::string>::const_iterator tag_iter =
-             name->lang_tags.begin();
-         tag_iter != name->lang_tags.end(); ++tag_iter) {
+             this->lang_tags.begin();
+         tag_iter != this->lang_tags.end(); ++tag_iter) {
       if (string_data.size() + tag_iter->size() >
               std::numeric_limits<uint16_t>::max() ||
           !out->WriteU16(static_cast<uint16_t>(tag_iter->size())) ||
           !out->WriteU16(static_cast<uint16_t>(string_data.size()))) {
-        return OTS_FAILURE_MSG("Failed to write string");
+        return Error("Failed to write langTagRecord");
       }
       string_data.append(*tag_iter);
     }
   }
 
   if (!out->Write(string_data.data(), string_data.size())) {
-    return OTS_FAILURE_MSG("Faile to write string data");
+    return Error("Faile to write string data");
   }
 
   return true;
 }
 
-void ots_name_reuse(Font *font, Font *other) {
-  font->name = other->name;
-  font->name_reused = true;
-}
-
-void ots_name_free(Font *font) {
-  delete font->name;
+bool OpenTypeNAME::IsValidNameId(uint16_t nameID, bool addIfMissing) {
+  if (addIfMissing && !this->name_ids.count(nameID)) {
+    bool added_unicode = false;
+    bool added_macintosh = false;
+    bool added_windows = false;
+    const size_t names_size = this->names.size();  // original size
+    for (size_t i = 0; i < names_size; ++i) switch (names[i].platform_id) {
+     case 0:
+      if (!added_unicode) {
+        // If there is an existing NameRecord with platform_id == 0 (Unicode),
+        // then add a NameRecord for the the specified nameID with arguments
+        // 0 (Unicode), 0 (v1.0), 0 (unspecified language).
+        this->names.emplace_back(0, 0, 0, nameID);
+        this->names.back().text = "NoName";
+        added_unicode = true;
+      }
+      break;
+     case 1:
+      if (!added_macintosh) {
+        // If there is an existing NameRecord with platform_id == 1 (Macintosh),
+        // then add a NameRecord for the specified nameID with arguments
+        // 1 (Macintosh), 0 (Roman), 0 (English).
+        this->names.emplace_back(1, 0, 0, nameID);
+        this->names.back().text = "NoName";
+        added_macintosh = true;
+      }
+      break;
+     case 3:
+      if (!added_windows) {
+        // If there is an existing NameRecord with platform_id == 3 (Windows),
+        // then add a NameRecord for the specified nameID with arguments
+        // 3 (Windows), 1 (UCS), 1033 (US English).
+        this->names.emplace_back(3, 1, 1033, nameID);
+        this->names.back().text = "NoName";
+        added_windows = true;
+      }
+      break;
+    }
+    if (added_unicode || added_macintosh || added_windows) {
+      std::sort(this->names.begin(), this->names.end());
+      this->name_ids.insert(nameID);
+    }
+  }
+  return this->name_ids.count(nameID);
 }
 
 }  // namespace
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/name.h b/gfx/ots/src/name.h
index a11965f46..68c7ac096 100644
--- a/gfx/ots/src/name.h
+++ b/gfx/ots/src/name.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,6 +9,7 @@
 #include <string>
 #include <utility>
 #include <vector>
+#include <unordered_set>
 
 #include "ots.h"
 
@@ -43,9 +44,19 @@ struct NameRecord {
   }
 };
 
-struct OpenTypeNAME {
+class OpenTypeNAME : public Table {
+ public:
+  explicit OpenTypeNAME(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool IsValidNameId(uint16_t nameID, bool addIfMissing = false);
+
+ private:
   std::vector<NameRecord> names;
   std::vector<std::string> lang_tags;
+  std::unordered_set<uint16_t> name_ids;
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/os2.cc b/gfx/ots/src/os2.cc
index fd5cdd1d6..d29898710 100644
--- a/gfx/ots/src/os2.cc
+++ b/gfx/ots/src/os2.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,77 +10,76 @@
 // OS/2 - OS/2 and Windows Metrics
 // http://www.microsoft.com/typography/otspec/os2.htm
 
-#define TABLE_NAME "OS/2"
-
 namespace ots {
 
-bool ots_os2_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeOS2::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypeOS2 *os2 = new OpenTypeOS2;
-  font->os2 = os2;
-
-  if (!table.ReadU16(&os2->version) ||
-      !table.ReadS16(&os2->avg_char_width) ||
-      !table.ReadU16(&os2->weight_class) ||
-      !table.ReadU16(&os2->width_class) ||
-      !table.ReadU16(&os2->type) ||
-      !table.ReadS16(&os2->subscript_x_size) ||
-      !table.ReadS16(&os2->subscript_y_size) ||
-      !table.ReadS16(&os2->subscript_x_offset) ||
-      !table.ReadS16(&os2->subscript_y_offset) ||
-      !table.ReadS16(&os2->superscript_x_size) ||
-      !table.ReadS16(&os2->superscript_y_size) ||
-      !table.ReadS16(&os2->superscript_x_offset) ||
-      !table.ReadS16(&os2->superscript_y_offset) ||
-      !table.ReadS16(&os2->strikeout_size) ||
-      !table.ReadS16(&os2->strikeout_position) ||
-      !table.ReadS16(&os2->family_class)) {
-    return OTS_FAILURE_MSG("Error reading basic table elements");
-  }
-
-  if (os2->version > 5) {
-    return OTS_FAILURE_MSG("Unsupported table version: %u", os2->version);
+  if (!table.ReadU16(&this->table.version) ||
+      !table.ReadS16(&this->table.avg_char_width) ||
+      !table.ReadU16(&this->table.weight_class) ||
+      !table.ReadU16(&this->table.width_class) ||
+      !table.ReadU16(&this->table.type) ||
+      !table.ReadS16(&this->table.subscript_x_size) ||
+      !table.ReadS16(&this->table.subscript_y_size) ||
+      !table.ReadS16(&this->table.subscript_x_offset) ||
+      !table.ReadS16(&this->table.subscript_y_offset) ||
+      !table.ReadS16(&this->table.superscript_x_size) ||
+      !table.ReadS16(&this->table.superscript_y_size) ||
+      !table.ReadS16(&this->table.superscript_x_offset) ||
+      !table.ReadS16(&this->table.superscript_y_offset) ||
+      !table.ReadS16(&this->table.strikeout_size) ||
+      !table.ReadS16(&this->table.strikeout_position) ||
+      !table.ReadS16(&this->table.family_class)) {
+    return Error("Error reading basic table elements");
+  }
+
+  if (this->table.version > 5) {
+    return Error("Unsupported table version: %u", this->table.version);
   }
 
   // Follow WPF Font Selection Model's advice.
-  if (1 <= os2->weight_class && os2->weight_class <= 9) {
-    OTS_WARNING("Bad usWeightClass: %u, changing it to: %u", os2->weight_class, os2->weight_class * 100);
-    os2->weight_class *= 100;
+  if (1 <= this->table.weight_class && this->table.weight_class <= 9) {
+    Warning("Bad usWeightClass: %u, changing it to %u",
+            this->table.weight_class, this->table.weight_class * 100);
+    this->table.weight_class *= 100;
   }
   // Ditto.
-  if (os2->weight_class > 999) {
-    OTS_WARNING("Bad usWeightClass: %u, changing it to: %d", os2->weight_class, 999);
-    os2->weight_class = 999;
+  if (this->table.weight_class > 999) {
+    Warning("Bad usWeightClass: %u, changing it to %d",
+             this->table.weight_class, 999);
+    this->table.weight_class = 999;
   }
 
-  if (os2->width_class < 1) {
-    OTS_WARNING("Bad usWidthClass: %u, changing it to: %d", os2->width_class, 1);
-    os2->width_class = 1;
-  } else if (os2->width_class > 9) {
-    OTS_WARNING("Bad usWidthClass: %u, changing it to: %d", os2->width_class, 9);
-    os2->width_class = 9;
+  if (this->table.width_class < 1) {
+    Warning("Bad usWidthClass: %u, changing it to %d",
+            this->table.width_class, 1);
+    this->table.width_class = 1;
+  } else if (this->table.width_class > 9) {
+    Warning("Bad usWidthClass: %u, changing it to %d",
+            this->table.width_class, 9);
+    this->table.width_class = 9;
   }
 
   // lowest 3 bits of fsType are exclusive.
-  if (os2->type & 0x2) {
+  if (this->table.type & 0x2) {
     // mask bits 2 & 3.
-    os2->type &= 0xfff3u;
-  } else if (os2->type & 0x4) {
+    this->table.type &= 0xfff3u;
+  } else if (this->table.type & 0x4) {
     // mask bits 1 & 3.
-    os2->type &= 0xfff4u;
-  } else if (os2->type & 0x8) {
+    this->table.type &= 0xfff4u;
+  } else if (this->table.type & 0x8) {
     // mask bits 1 & 2.
-    os2->type &= 0xfff9u;
+    this->table.type &= 0xfff9u;
   }
 
   // mask reserved bits. use only 0..3, 8, 9 bits.
-  os2->type &= 0x30f;
+  this->table.type &= 0x30f;
 
-#define SET_TO_ZERO(a, b)                                     \
-  if (os2->b < 0) {                                           \
-    OTS_WARNING("Bad " a ": %d, setting it to zero", os2->b); \
-    os2->b = 0;                                               \
+#define SET_TO_ZERO(a, b)                                                      \
+  if (this->table.b < 0) {                                                     \
+    Warning("Bad " a ": %d, setting it to zero", this->table.b);               \
+    this->table.b = 0;                                                         \
   }
 
   SET_TO_ZERO("ySubscriptXSize", subscript_x_size);
@@ -103,234 +102,221 @@ bool ots_os2_parse(Font *font, const uint8_t *data, size_t length) {
     "bXHeight",
   };
   for (unsigned i = 0; i < 10; ++i) {
-    if (!table.ReadU8(&os2->panose[i])) {
-      return OTS_FAILURE_MSG("Error reading PANOSE %s", panose_strings[i].c_str());
+    if (!table.ReadU8(&this->table.panose[i])) {
+      return Error("Failed to read PANOSE %s", panose_strings[i].c_str());
     }
   }
 
-  if (!table.ReadU32(&os2->unicode_range_1) ||
-      !table.ReadU32(&os2->unicode_range_2) ||
-      !table.ReadU32(&os2->unicode_range_3) ||
-      !table.ReadU32(&os2->unicode_range_4) ||
-      !table.ReadU32(&os2->vendor_id) ||
-      !table.ReadU16(&os2->selection) ||
-      !table.ReadU16(&os2->first_char_index) ||
-      !table.ReadU16(&os2->last_char_index) ||
-      !table.ReadS16(&os2->typo_ascender) ||
-      !table.ReadS16(&os2->typo_descender) ||
-      !table.ReadS16(&os2->typo_linegap) ||
-      !table.ReadU16(&os2->win_ascent) ||
-      !table.ReadU16(&os2->win_descent)) {
-    return OTS_FAILURE_MSG("Error reading more basic table fields");
+  if (!table.ReadU32(&this->table.unicode_range_1) ||
+      !table.ReadU32(&this->table.unicode_range_2) ||
+      !table.ReadU32(&this->table.unicode_range_3) ||
+      !table.ReadU32(&this->table.unicode_range_4) ||
+      !table.ReadU32(&this->table.vendor_id) ||
+      !table.ReadU16(&this->table.selection) ||
+      !table.ReadU16(&this->table.first_char_index) ||
+      !table.ReadU16(&this->table.last_char_index) ||
+      !table.ReadS16(&this->table.typo_ascender) ||
+      !table.ReadS16(&this->table.typo_descender) ||
+      !table.ReadS16(&this->table.typo_linegap) ||
+      !table.ReadU16(&this->table.win_ascent) ||
+      !table.ReadU16(&this->table.win_descent)) {
+    return Error("Error reading more basic table fields");
   }
 
   // If bit 6 is set, then bits 0 and 5 must be clear.
-  if (os2->selection & 0x40) {
-    os2->selection &= 0xffdeu;
+  if (this->table.selection & 0x40) {
+    this->table.selection &= 0xffdeu;
   }
 
   // the settings of bits 0 and 1 must be reflected in the macStyle bits
   // in the 'head' table.
-  if (!font->head) {
-    return OTS_FAILURE_MSG("Needed head table is missing from the font");
-  }
-  if ((os2->selection & 0x1) &&
-      !(font->head->mac_style & 0x2)) {
-    OTS_WARNING("adjusting Mac style (italic)");
-    font->head->mac_style |= 0x2;
+  OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>(
+      GetFont()->GetTypedTable(OTS_TAG_HEAD));
+
+  if ((this->table.selection & 0x1) &&
+      head && !(head->mac_style & 0x2)) {
+    Warning("Adjusting head.macStyle (italic) to match fsSelection");
+    head->mac_style |= 0x2;
   }
-  if ((os2->selection & 0x2) &&
-      !(font->head->mac_style & 0x4)) {
-    OTS_WARNING("adjusting Mac style (underscore)");
-    font->head->mac_style |= 0x4;
+  if ((this->table.selection & 0x2) &&
+      head && !(head->mac_style & 0x4)) {
+    Warning("Adjusting head.macStyle (underscore) to match fsSelection");
+    head->mac_style |= 0x4;
   }
 
   // While bit 6 on implies that bits 0 and 1 of macStyle are clear,
   // the reverse is not true.
-  if ((os2->selection & 0x40) &&
-      (font->head->mac_style & 0x3)) {
-    OTS_WARNING("adjusting Mac style (regular)");
-    font->head->mac_style &= 0xfffcu;
+  if ((this->table.selection & 0x40) &&
+      head && (head->mac_style & 0x3)) {
+    Warning("Adjusting head.macStyle (regular) to match fsSelection");
+    head->mac_style &= 0xfffcu;
   }
 
-  if ((os2->version < 4) &&
-      (os2->selection & 0x300)) {
+  if ((this->table.version < 4) &&
+      (this->table.selection & 0x300)) {
     // bit 8 and 9 must be unset in OS/2 table versions less than 4.
-    return OTS_FAILURE_MSG("Version %d incompatible with selection %d", os2->version, os2->selection);
+    return Error("fSelection bits 8 and 9 must be unset for table version %d",
+                 this->table.version);
   }
 
   // mask reserved bits. use only 0..9 bits.
-  os2->selection &= 0x3ff;
+  this->table.selection &= 0x3ff;
 
-  if (os2->first_char_index > os2->last_char_index) {
-    return OTS_FAILURE_MSG("first char index %d > last char index %d in os2", os2->first_char_index, os2->last_char_index);
+  if (this->table.first_char_index > this->table.last_char_index) {
+    return Error("usFirstCharIndex %d > usLastCharIndex %d",
+                 this->table.first_char_index, this->table.last_char_index);
   }
-  if (os2->typo_linegap < 0) {
-    OTS_WARNING("bad linegap: %d", os2->typo_linegap);
-    os2->typo_linegap = 0;
+  if (this->table.typo_linegap < 0) {
+    Warning("Bad sTypoLineGap, setting it to 0: %d", this->table.typo_linegap);
+    this->table.typo_linegap = 0;
   }
 
-  if (os2->version < 1) {
+  if (this->table.version < 1) {
     // http://www.microsoft.com/typography/otspec/os2ver0.htm
     return true;
   }
 
-  if (length < offsetof(OpenTypeOS2, code_page_range_2)) {
-    OTS_WARNING("bad version number: %u", os2->version);
+  if (length < offsetof(OS2Data, code_page_range_2)) {
+    Warning("Bad version number, setting it to 0: %u", this->table.version);
     // Some fonts (e.g., kredit1.ttf and quinquef.ttf) have weird version
     // numbers. Fix them.
-    os2->version = 0;
+    this->table.version = 0;
     return true;
   }
 
-  if (!table.ReadU32(&os2->code_page_range_1) ||
-      !table.ReadU32(&os2->code_page_range_2)) {
-    return OTS_FAILURE_MSG("Failed to read codepage ranges");
+  if (!table.ReadU32(&this->table.code_page_range_1) ||
+      !table.ReadU32(&this->table.code_page_range_2)) {
+    return Error("Failed to read ulCodePageRange1 or ulCodePageRange2");
   }
 
-  if (os2->version < 2) {
+  if (this->table.version < 2) {
     // http://www.microsoft.com/typography/otspec/os2ver1.htm
     return true;
   }
 
-  if (length < offsetof(OpenTypeOS2, max_context)) {
-    OTS_WARNING("bad version number: %u", os2->version);
+  if (length < offsetof(OS2Data, max_context)) {
+    Warning("Bad version number, setting it to 1: %u", this->table.version);
     // some Japanese fonts (e.g., mona.ttf) have weird version number.
     // fix them.
-    os2->version = 1;
+    this->table.version = 1;
     return true;
   }
 
-  if (!table.ReadS16(&os2->x_height) ||
-      !table.ReadS16(&os2->cap_height) ||
-      !table.ReadU16(&os2->default_char) ||
-      !table.ReadU16(&os2->break_char) ||
-      !table.ReadU16(&os2->max_context)) {
-    return OTS_FAILURE_MSG("Failed to read version 2-specific fields");
+  if (!table.ReadS16(&this->table.x_height) ||
+      !table.ReadS16(&this->table.cap_height) ||
+      !table.ReadU16(&this->table.default_char) ||
+      !table.ReadU16(&this->table.break_char) ||
+      !table.ReadU16(&this->table.max_context)) {
+    return Error("Failed to read version 2-specific fields");
   }
 
-  if (os2->x_height < 0) {
-    OTS_WARNING("bad x_height: %d", os2->x_height);
-    os2->x_height = 0;
+  if (this->table.x_height < 0) {
+    Warning("Bad sxHeight settig it to 0: %d", this->table.x_height);
+    this->table.x_height = 0;
   }
-  if (os2->cap_height < 0) {
-    OTS_WARNING("bad cap_height: %d", os2->cap_height);
-    os2->cap_height = 0;
+  if (this->table.cap_height < 0) {
+    Warning("Bad sCapHeight setting it to 0: %d", this->table.cap_height);
+    this->table.cap_height = 0;
   }
 
-  if (os2->version < 5) {
+  if (this->table.version < 5) {
     // http://www.microsoft.com/typography/otspec/os2ver4.htm
     return true;
   }
 
-  if (!table.ReadU16(&os2->lower_optical_pointsize) ||
-      !table.ReadU16(&os2->upper_optical_pointsize)) {
-    return OTS_FAILURE_MSG("Failed to read version 5-specific fields");
+  if (!table.ReadU16(&this->table.lower_optical_pointsize) ||
+      !table.ReadU16(&this->table.upper_optical_pointsize)) {
+    return Error("Failed to read version 5-specific fields");
   }
 
-  if (os2->lower_optical_pointsize > 0xFFFE) {
-    OTS_WARNING("'usLowerOpticalPointSize' is bigger than 0xFFFE: %d", os2->lower_optical_pointsize);
-    os2->lower_optical_pointsize = 0xFFFE;
+  if (this->table.lower_optical_pointsize > 0xFFFE) {
+    Warning("usLowerOpticalPointSize is bigger than 0xFFFE: %d",
+            this->table.lower_optical_pointsize);
+    this->table.lower_optical_pointsize = 0xFFFE;
   }
 
-  if (os2->upper_optical_pointsize < 2) {
-    OTS_WARNING("'usUpperOpticalPointSize' is lower than 2: %d", os2->upper_optical_pointsize);
-    os2->upper_optical_pointsize = 2;
+  if (this->table.upper_optical_pointsize < 2) {
+    Warning("usUpperOpticalPointSize is lower than 2: %d",
+            this->table.upper_optical_pointsize);
+    this->table.upper_optical_pointsize = 2;
   }
 
   return true;
 }
 
-bool ots_os2_should_serialise(Font *font) {
-  return font->os2 != NULL;
-}
-
-bool ots_os2_serialise(OTSStream *out, Font *font) {
-  const OpenTypeOS2 *os2 = font->os2;
-
-  if (!out->WriteU16(os2->version) ||
-      !out->WriteS16(os2->avg_char_width) ||
-      !out->WriteU16(os2->weight_class) ||
-      !out->WriteU16(os2->width_class) ||
-      !out->WriteU16(os2->type) ||
-      !out->WriteS16(os2->subscript_x_size) ||
-      !out->WriteS16(os2->subscript_y_size) ||
-      !out->WriteS16(os2->subscript_x_offset) ||
-      !out->WriteS16(os2->subscript_y_offset) ||
-      !out->WriteS16(os2->superscript_x_size) ||
-      !out->WriteS16(os2->superscript_y_size) ||
-      !out->WriteS16(os2->superscript_x_offset) ||
-      !out->WriteS16(os2->superscript_y_offset) ||
-      !out->WriteS16(os2->strikeout_size) ||
-      !out->WriteS16(os2->strikeout_position) ||
-      !out->WriteS16(os2->family_class)) {
-    return OTS_FAILURE_MSG("Failed to write basic OS2 information");
+bool OpenTypeOS2::Serialize(OTSStream *out) {
+  if (!out->WriteU16(this->table.version) ||
+      !out->WriteS16(this->table.avg_char_width) ||
+      !out->WriteU16(this->table.weight_class) ||
+      !out->WriteU16(this->table.width_class) ||
+      !out->WriteU16(this->table.type) ||
+      !out->WriteS16(this->table.subscript_x_size) ||
+      !out->WriteS16(this->table.subscript_y_size) ||
+      !out->WriteS16(this->table.subscript_x_offset) ||
+      !out->WriteS16(this->table.subscript_y_offset) ||
+      !out->WriteS16(this->table.superscript_x_size) ||
+      !out->WriteS16(this->table.superscript_y_size) ||
+      !out->WriteS16(this->table.superscript_x_offset) ||
+      !out->WriteS16(this->table.superscript_y_offset) ||
+      !out->WriteS16(this->table.strikeout_size) ||
+      !out->WriteS16(this->table.strikeout_position) ||
+      !out->WriteS16(this->table.family_class)) {
+    return Error("Failed to write basic table data");
   }
 
   for (unsigned i = 0; i < 10; ++i) {
-    if (!out->Write(&os2->panose[i], 1)) {
-      return OTS_FAILURE_MSG("Failed to write os2 panose information");
+    if (!out->Write(&this->table.panose[i], 1)) {
+      return Error("Failed to write PANOSE data");
     }
   }
 
-  if (!out->WriteU32(os2->unicode_range_1) ||
-      !out->WriteU32(os2->unicode_range_2) ||
-      !out->WriteU32(os2->unicode_range_3) ||
-      !out->WriteU32(os2->unicode_range_4) ||
-      !out->WriteU32(os2->vendor_id) ||
-      !out->WriteU16(os2->selection) ||
-      !out->WriteU16(os2->first_char_index) ||
-      !out->WriteU16(os2->last_char_index) ||
-      !out->WriteS16(os2->typo_ascender) ||
-      !out->WriteS16(os2->typo_descender) ||
-      !out->WriteS16(os2->typo_linegap) ||
-      !out->WriteU16(os2->win_ascent) ||
-      !out->WriteU16(os2->win_descent)) {
-    return OTS_FAILURE_MSG("Failed to write version 1-specific fields");
+  if (!out->WriteU32(this->table.unicode_range_1) ||
+      !out->WriteU32(this->table.unicode_range_2) ||
+      !out->WriteU32(this->table.unicode_range_3) ||
+      !out->WriteU32(this->table.unicode_range_4) ||
+      !out->WriteU32(this->table.vendor_id) ||
+      !out->WriteU16(this->table.selection) ||
+      !out->WriteU16(this->table.first_char_index) ||
+      !out->WriteU16(this->table.last_char_index) ||
+      !out->WriteS16(this->table.typo_ascender) ||
+      !out->WriteS16(this->table.typo_descender) ||
+      !out->WriteS16(this->table.typo_linegap) ||
+      !out->WriteU16(this->table.win_ascent) ||
+      !out->WriteU16(this->table.win_descent)) {
+    return Error("Failed to write version 1-specific fields");
   }
 
-  if (os2->version < 1) {
+  if (this->table.version < 1) {
     return true;
   }
 
-  if (!out->WriteU32(os2->code_page_range_1) ||
-      !out->WriteU32(os2->code_page_range_2)) {
-    return OTS_FAILURE_MSG("Failed to write codepage ranges");
+  if (!out->WriteU32(this->table.code_page_range_1) ||
+      !out->WriteU32(this->table.code_page_range_2)) {
+    return Error("Failed to write codepage ranges");
   }
 
-  if (os2->version < 2) {
+  if (this->table.version < 2) {
     return true;
   }
 
-  if (!out->WriteS16(os2->x_height) ||
-      !out->WriteS16(os2->cap_height) ||
-      !out->WriteU16(os2->default_char) ||
-      !out->WriteU16(os2->break_char) ||
-      !out->WriteU16(os2->max_context)) {
-    return OTS_FAILURE_MSG("Failed to write version 2-specific fields");
+  if (!out->WriteS16(this->table.x_height) ||
+      !out->WriteS16(this->table.cap_height) ||
+      !out->WriteU16(this->table.default_char) ||
+      !out->WriteU16(this->table.break_char) ||
+      !out->WriteU16(this->table.max_context)) {
+    return Error("Failed to write version 2-specific fields");
   }
 
-  if (os2->version < 5) {
+  if (this->table.version < 5) {
     return true;
   }
 
-  if (!out->WriteU16(os2->lower_optical_pointsize) ||
-      !out->WriteU16(os2->upper_optical_pointsize)) {
-    return OTS_FAILURE_MSG("Failed to write version 5-specific fields");
+  if (!out->WriteU16(this->table.lower_optical_pointsize) ||
+      !out->WriteU16(this->table.upper_optical_pointsize)) {
+    return Error("Failed to write version 5-specific fields");
   }
 
   return true;
 }
 
-void ots_os2_reuse(Font *font, Font *other) {
-  font->os2 = other->os2;
-  font->os2_reused = true;
-}
-
-void ots_os2_free(Font *font) {
-  delete font->os2;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/os2.h b/gfx/ots/src/os2.h
index 01511c5dc..b3f1bad9b 100644
--- a/gfx/ots/src/os2.h
+++ b/gfx/ots/src/os2.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,7 +9,7 @@
 
 namespace ots {
 
-struct OpenTypeOS2 {
+struct OS2Data {
   uint16_t version;
   int16_t avg_char_width;
   uint16_t weight_class;
@@ -51,6 +51,17 @@ struct OpenTypeOS2 {
   uint16_t upper_optical_pointsize;
 };
 
+class OpenTypeOS2 : public Table {
+ public:
+  explicit OpenTypeOS2(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
+  OS2Data table;
+};
+
 }  // namespace ots
 
 #endif  // OTS_OS2_H_
diff --git a/gfx/ots/src/ots.cc b/gfx/ots/src/ots.cc
index 15794475a..281aa7288 100644
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -19,32 +19,45 @@
 // The OpenType Font File
 // http://www.microsoft.com/typography/otspec/cmap.htm
 
-namespace {
-
-// Generate a message with or without a table tag, when 'header' is the OpenTypeFile pointer
-#define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_FAILURE_MSG_TAG_(header, msg_, tag_)
-#define OTS_FAILURE_MSG_HDR(msg_)      OTS_FAILURE_MSG_(header, msg_)
-#define OTS_WARNING_MSG_HDR(msg_)      OTS_WARNING_MSG_(header, msg_)
-
-
-struct OpenTypeTable {
-  uint32_t tag;
-  uint32_t chksum;
-  uint32_t offset;
-  uint32_t length;
-  uint32_t uncompressed_length;
-};
+#include "cff.h"
+#include "cmap.h"
+#include "cvt.h"
+#include "fpgm.h"
+#include "gasp.h"
+#include "gdef.h"
+#include "glyf.h"
+#include "gpos.h"
+#include "gsub.h"
+#include "hdmx.h"
+#include "head.h"
+#include "hhea.h"
+#include "hmtx.h"
+#include "kern.h"
+#include "loca.h"
+#include "ltsh.h"
+#include "math_.h"
+#include "maxp.h"
+#include "name.h"
+#include "os2.h"
+#include "ots.h"
+#include "post.h"
+#include "prep.h"
+#include "vdmx.h"
+#include "vhea.h"
+#include "vmtx.h"
+#include "vorg.h"
+
+// Graphite tables
+#ifdef OTS_GRAPHITE
+#include "feat.h"
+#include "glat.h"
+#include "gloc.h"
+#include "sile.h"
+#include "silf.h"
+#include "sill.h"
+#endif
 
-bool CheckTag(uint32_t tag_value) {
-  for (unsigned i = 0; i < 4; ++i) {
-    const uint32_t check = tag_value & 0xff;
-    if (check < 32 || check > 126) {
-      return false;  // non-ASCII character found.
-    }
-    tag_value >>= 8;
-  }
-  return true;
-}
+namespace ots {
 
 struct Arena {
  public:
@@ -65,80 +78,83 @@ struct Arena {
   std::vector<uint8_t*> hunks_;
 };
 
+}; // namespace ots
+
+namespace {
+
+#define OTS_MSG_TAG_(level,otf_,msg_,tag_) \
+  (OTS_MESSAGE_(level,otf_,"%c%c%c%c: %s", OTS_UNTAG(tag_), msg_), false)
+
+// Generate a message with or without a table tag, when 'header' is the FontFile pointer
+#define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_MSG_TAG_(0, header, msg_, tag_)
+#define OTS_FAILURE_MSG_HDR(...)       OTS_FAILURE_MSG_(header, __VA_ARGS__)
+#define OTS_WARNING_MSG_HDR(...)       OTS_WARNING_MSG_(header, __VA_ARGS__)
+
+
+bool CheckTag(uint32_t tag_value) {
+  for (unsigned i = 0; i < 4; ++i) {
+    const uint32_t check = tag_value & 0xff;
+    if (check < 32 || check > 126) {
+      return false;  // non-ASCII character found.
+    }
+    tag_value >>= 8;
+  }
+  return true;
+}
+
 const struct {
   uint32_t tag;
-  bool (*parse)(ots::Font *font, const uint8_t *data, size_t length);
-  bool (*serialise)(ots::OTSStream *out, ots::Font *font);
-  bool (*should_serialise)(ots::Font *font);
-  void (*reuse)(ots::Font *font, ots::Font *other);
   bool required;
-} table_parsers[] = {
-  { OTS_TAG('m','a','x','p'), ots::ots_maxp_parse, ots::ots_maxp_serialise,
-    ots::ots_maxp_should_serialise, ots::ots_maxp_reuse, true },
-  { OTS_TAG('h','e','a','d'), ots::ots_head_parse, ots::ots_head_serialise,
-    ots::ots_head_should_serialise, ots::ots_head_reuse, true },
-  { OTS_TAG('O','S','/','2'), ots::ots_os2_parse, ots::ots_os2_serialise,
-    ots::ots_os2_should_serialise, ots::ots_os2_reuse, true },
-  { OTS_TAG('c','m','a','p'), ots::ots_cmap_parse, ots::ots_cmap_serialise,
-    ots::ots_cmap_should_serialise, ots::ots_cmap_reuse, true },
-  { OTS_TAG('h','h','e','a'), ots::ots_hhea_parse, ots::ots_hhea_serialise,
-    ots::ots_hhea_should_serialise, ots::ots_hhea_reuse, true },
-  { OTS_TAG('h','m','t','x'), ots::ots_hmtx_parse, ots::ots_hmtx_serialise,
-    ots::ots_hmtx_should_serialise, ots::ots_hmtx_reuse, true },
-  { OTS_TAG('n','a','m','e'), ots::ots_name_parse, ots::ots_name_serialise,
-    ots::ots_name_should_serialise, ots::ots_name_reuse, true },
-  { OTS_TAG('p','o','s','t'), ots::ots_post_parse, ots::ots_post_serialise,
-    ots::ots_post_should_serialise, ots::ots_post_reuse, true },
-  { OTS_TAG('l','o','c','a'), ots::ots_loca_parse, ots::ots_loca_serialise,
-    ots::ots_loca_should_serialise, ots::ots_loca_reuse, false },
-  { OTS_TAG('g','l','y','f'), ots::ots_glyf_parse, ots::ots_glyf_serialise,
-    ots::ots_glyf_should_serialise, ots::ots_glyf_reuse, false },
-  { OTS_TAG('C','F','F',' '), ots::ots_cff_parse, ots::ots_cff_serialise,
-    ots::ots_cff_should_serialise, ots::ots_cff_reuse, false },
-  { OTS_TAG('V','D','M','X'), ots::ots_vdmx_parse, ots::ots_vdmx_serialise,
-    ots::ots_vdmx_should_serialise, ots::ots_vdmx_reuse, false },
-  { OTS_TAG('h','d','m','x'), ots::ots_hdmx_parse, ots::ots_hdmx_serialise,
-    ots::ots_hdmx_should_serialise, ots::ots_hdmx_reuse, false },
-  { OTS_TAG('g','a','s','p'), ots::ots_gasp_parse, ots::ots_gasp_serialise,
-    ots::ots_gasp_should_serialise, ots::ots_gasp_reuse, false },
-  { OTS_TAG('c','v','t',' '), ots::ots_cvt_parse, ots::ots_cvt_serialise,
-    ots::ots_cvt_should_serialise, ots::ots_cvt_reuse, false },
-  { OTS_TAG('f','p','g','m'), ots::ots_fpgm_parse, ots::ots_fpgm_serialise,
-    ots::ots_fpgm_should_serialise, ots::ots_fpgm_reuse, false },
-  { OTS_TAG('p','r','e','p'), ots::ots_prep_parse, ots::ots_prep_serialise,
-    ots::ots_prep_should_serialise, ots::ots_prep_reuse, false },
-  { OTS_TAG('L','T','S','H'), ots::ots_ltsh_parse, ots::ots_ltsh_serialise,
-    ots::ots_ltsh_should_serialise, ots::ots_ltsh_reuse, false },
-  { OTS_TAG('V','O','R','G'), ots::ots_vorg_parse, ots::ots_vorg_serialise,
-    ots::ots_vorg_should_serialise, ots::ots_vorg_reuse, false },
-  { OTS_TAG('k','e','r','n'), ots::ots_kern_parse, ots::ots_kern_serialise,
-    ots::ots_kern_should_serialise, ots::ots_kern_reuse, false },
+} supported_tables[] = {
+  { OTS_TAG_MAXP, true },
+  { OTS_TAG_HEAD, true },
+  { OTS_TAG_OS2,  true },
+  { OTS_TAG_CMAP, true },
+  { OTS_TAG_HHEA, true },
+  { OTS_TAG_HMTX, true },
+  { OTS_TAG_NAME, true },
+  { OTS_TAG_POST, true },
+  { OTS_TAG_LOCA, false },
+  { OTS_TAG_GLYF, false },
+  { OTS_TAG_CFF,  false },
+  { OTS_TAG_VDMX, false },
+  { OTS_TAG_HDMX, false },
+  { OTS_TAG_GASP, false },
+  { OTS_TAG_CVT,  false },
+  { OTS_TAG_FPGM, false },
+  { OTS_TAG_PREP, false },
+  { OTS_TAG_LTSH, false },
+  { OTS_TAG_VORG, false },
+  { OTS_TAG_KERN, false },
   // We need to parse GDEF table in advance of parsing GSUB/GPOS tables
   // because they could refer GDEF table.
-  { OTS_TAG('G','D','E','F'), ots::ots_gdef_parse, ots::ots_gdef_serialise,
-    ots::ots_gdef_should_serialise, ots::ots_gdef_reuse, false },
-  { OTS_TAG('G','P','O','S'), ots::ots_gpos_parse, ots::ots_gpos_serialise,
-    ots::ots_gpos_should_serialise, ots::ots_gpos_reuse, false },
-  { OTS_TAG('G','S','U','B'), ots::ots_gsub_parse, ots::ots_gsub_serialise,
-    ots::ots_gsub_should_serialise, ots::ots_gsub_reuse, false },
-  { OTS_TAG('v','h','e','a'), ots::ots_vhea_parse, ots::ots_vhea_serialise,
-    ots::ots_vhea_should_serialise, ots::ots_vhea_reuse, false },
-  { OTS_TAG('v','m','t','x'), ots::ots_vmtx_parse, ots::ots_vmtx_serialise,
-    ots::ots_vmtx_should_serialise, ots::ots_vmtx_reuse, false },
-  { OTS_TAG('M','A','T','H'), ots::ots_math_parse, ots::ots_math_serialise,
-    ots::ots_math_should_serialise, ots::ots_math_reuse, false },
-  { 0, NULL, NULL, NULL, NULL, false },
+  { OTS_TAG_GDEF, false },
+  { OTS_TAG_GPOS, false },
+  { OTS_TAG_GSUB, false },
+  { OTS_TAG_VHEA, false },
+  { OTS_TAG_VMTX, false },
+  { OTS_TAG_MATH, false },
+  // Graphite tables
+#ifdef OTS_GRAPHITE
+  { OTS_TAG_GLOC, false },
+  { OTS_TAG_GLAT, false },
+  { OTS_TAG_FEAT, false },
+  { OTS_TAG_SILF, false },
+  { OTS_TAG_SILE, false },
+  { OTS_TAG_SILL, false },
+#endif
+  { 0, false },
 };
 
-bool ProcessGeneric(ots::OpenTypeFile *header,
+bool ProcessGeneric(ots::FontFile *header,
                     ots::Font *font,
                     uint32_t signature,
                     ots::OTSStream *output,
                     const uint8_t *data, size_t length,
-                    const std::vector<OpenTypeTable>& tables,
+                    const std::vector<ots::TableEntry>& tables,
                     ots::Buffer& file);
 
-bool ProcessTTF(ots::OpenTypeFile *header,
+bool ProcessTTF(ots::FontFile *header,
                 ots::Font *font,
                 ots::OTSStream *output, const uint8_t *data, size_t length,
                 uint32_t offset = 0) {
@@ -202,10 +218,10 @@ bool ProcessTTF(ots::OpenTypeFile *header,
   }
 
   // Next up is the list of tables.
-  std::vector<OpenTypeTable> tables;
+  std::vector<ots::TableEntry> tables;
 
   for (unsigned i = 0; i < font->num_tables; ++i) {
-    OpenTypeTable table;
+    ots::TableEntry table;
     if (!file.ReadU32(&table.tag) ||
         !file.ReadU32(&table.chksum) ||
         !file.ReadU32(&table.offset) ||
@@ -221,7 +237,7 @@ bool ProcessTTF(ots::OpenTypeFile *header,
                         tables, file);
 }
 
-bool ProcessTTC(ots::OpenTypeFile *header,
+bool ProcessTTC(ots::FontFile *header,
                 ots::OTSStream *output,
                 const uint8_t *data,
                 size_t length,
@@ -308,7 +324,7 @@ bool ProcessTTC(ots::OpenTypeFile *header,
   }
 }
 
-bool ProcessWOFF(ots::OpenTypeFile *header,
+bool ProcessWOFF(ots::FontFile *header,
                  ots::Font *font,
                  ots::OTSStream *output, const uint8_t *data, size_t length) {
   ots::Buffer file(data, length);
@@ -388,14 +404,14 @@ bool ProcessWOFF(ots::OpenTypeFile *header,
   }
 
   // Next up is the list of tables.
-  std::vector<OpenTypeTable> tables;
+  std::vector<ots::TableEntry> tables;
 
   uint32_t first_index = 0;
   uint32_t last_index = 0;
   // Size of sfnt header plus size of table records.
   uint64_t total_sfnt_size = 12 + 16 * font->num_tables;
   for (unsigned i = 0; i < font->num_tables; ++i) {
-    OpenTypeTable table;
+    ots::TableEntry table;
     if (!file.ReadU32(&table.tag) ||
         !file.ReadU32(&table.offset) ||
         !file.ReadU32(&table.length) ||
@@ -463,13 +479,17 @@ bool ProcessWOFF(ots::OpenTypeFile *header,
   return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file);
 }
 
-bool ProcessWOFF2(ots::OpenTypeFile *header,
+bool ProcessWOFF2(ots::FontFile *header,
                   ots::OTSStream *output,
                   const uint8_t *data,
                   size_t length,
                   uint32_t index) {
   size_t decompressed_size = woff2::ComputeWOFF2FinalSize(data, length);
 
+  if (decompressed_size < length) {
+    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is less than compressed size");
+  }
+
   if (decompressed_size == 0) {
     return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is set to 0");
   }
@@ -493,16 +513,16 @@ bool ProcessWOFF2(ots::OpenTypeFile *header,
   }
 }
 
-ots::TableAction GetTableAction(ots::OpenTypeFile *header, uint32_t tag) {
+ots::TableAction GetTableAction(const ots::FontFile *header, uint32_t tag) {
   ots::TableAction action = header->context->GetTableAction(tag);
 
   if (action == ots::TABLE_ACTION_DEFAULT) {
     action = ots::TABLE_ACTION_DROP;
 
     for (unsigned i = 0; ; ++i) {
-      if (table_parsers[i].parse == NULL) break;
+      if (supported_tables[i].tag == 0) break;
 
-      if (table_parsers[i].tag == tag) {
+      if (supported_tables[i].tag == tag) {
         action = ots::TABLE_ACTION_SANITIZE;
         break;
       }
@@ -514,14 +534,14 @@ ots::TableAction GetTableAction(ots::OpenTypeFile *header, uint32_t tag) {
 }
 
 bool GetTableData(const uint8_t *data,
-                  const OpenTypeTable& table,
-                  Arena *arena,
+                  const ots::TableEntry& table,
+                  ots::Arena &arena,
                   size_t *table_length,
                   const uint8_t **table_data) {
   if (table.uncompressed_length != table.length) {
     // Compressed table. Need to uncompress into memory first.
     *table_length = table.uncompressed_length;
-    *table_data = (*arena).Allocate(*table_length);
+    *table_data = arena.Allocate(*table_length);
     uLongf dest_len = *table_length;
     int r = uncompress((Bytef*) *table_data, &dest_len,
                        data + table.offset, table.length);
@@ -537,12 +557,12 @@ bool GetTableData(const uint8_t *data,
   return true;
 }
 
-bool ProcessGeneric(ots::OpenTypeFile *header,
+bool ProcessGeneric(ots::FontFile *header,
                     ots::Font *font,
                     uint32_t signature,
                     ots::OTSStream *output,
                     const uint8_t *data, size_t length,
-                    const std::vector<OpenTypeTable>& tables,
+                    const std::vector<ots::TableEntry>& tables,
                     ots::Buffer& file) {
   const size_t data_offset = file.offset();
 
@@ -561,7 +581,7 @@ bool ProcessGeneric(ots::OpenTypeFile *header,
 
     // all tag names must be built from printable ASCII characters
     if (!CheckTag(tables[i].tag)) {
-      return OTS_FAILURE_MSG_TAG("invalid table tag", tables[i].tag);
+      OTS_WARNING_MSG_HDR("Invalid table tag: 0x%X", tables[i].tag);
     }
 
     // tables must be 4-byte aligned
@@ -616,11 +636,6 @@ bool ProcessGeneric(ots::OpenTypeFile *header,
     return OTS_FAILURE_MSG_HDR("uncompressed sum exceeds 30MB");
   }
 
-  std::map<uint32_t, OpenTypeTable> table_map;
-  for (unsigned i = 0; i < font->num_tables; ++i) {
-    table_map[tables[i].tag] = tables[i];
-  }
-
   // check that the tables are not overlapping.
   std::vector<std::pair<uint32_t, uint8_t> > overlap_checker;
   for (unsigned i = 0; i < font->num_tables; ++i) {
@@ -639,80 +654,66 @@ bool ProcessGeneric(ots::OpenTypeFile *header,
     }
   }
 
-  Arena arena;
+  std::map<uint32_t, ots::TableEntry> table_map;
+  for (unsigned i = 0; i < font->num_tables; ++i) {
+    table_map[tables[i].tag] = tables[i];
+  }
 
+  ots::Arena arena;
+  // Parse known tables first as we need to parse them in specific order.
   for (unsigned i = 0; ; ++i) {
-    if (table_parsers[i].parse == NULL) break;
+    if (supported_tables[i].tag == 0) break;
 
-    uint32_t tag = table_parsers[i].tag;
-    const std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.find(tag);
-
-    ots::TableAction action = GetTableAction(header, tag);
-    if (it == table_map.end()) {
-      if (table_parsers[i].required && action == ots::TABLE_ACTION_SANITIZE) {
-        return OTS_FAILURE_MSG_TAG("missing required table", table_parsers[i].tag);
+    uint32_t tag = supported_tables[i].tag;
+    const auto &it = table_map.find(tag);
+    if (it == table_map.cend()) {
+      if (supported_tables[i].required) {
+        return OTS_FAILURE_MSG_TAG("missing required table", tag);
       }
-      continue;
-    }
-
-    uint32_t input_offset = it->second.offset;
-    const ots::TableMap::const_iterator ot = header->tables.find(input_offset);
-    if (ot == header->tables.end()) {
-      const uint8_t* table_data;
-      size_t table_length;
-
-      if (!GetTableData(data, it->second, &arena, &table_length, &table_data)) {
-        return OTS_FAILURE_MSG_TAG("uncompress failed", table_parsers[i].tag);
+    } else {
+      if (!font->ParseTable(it->second, data, arena)) {
+        return OTS_FAILURE_MSG_TAG("Failed to parse table", tag);
       }
+    }
+  }
 
-      if (action == ots::TABLE_ACTION_SANITIZE &&
-          !table_parsers[i].parse(font, table_data, table_length)) {
-        return OTS_FAILURE();
+  // Then parse any tables left.
+  for (const auto &table_entry : tables) {
+    if (!font->GetTable(table_entry.tag)) {
+      if (!font->ParseTable(table_entry, data, arena)) {
+        return OTS_FAILURE_MSG_TAG("Failed to parse table", table_entry.tag);
       }
-    } else if (action == ots::TABLE_ACTION_SANITIZE) {
-      table_parsers[i].reuse(font, ot->second.first);
     }
   }
 
-  if (font->cff) {
+  if (font->GetTable(OTS_TAG_CFF)) {
     // font with PostScript glyph
     if (font->version != OTS_TAG('O','T','T','O')) {
       return OTS_FAILURE_MSG_HDR("wrong font version for PostScript glyph data");
     }
-    if (font->glyf || font->loca) {
+    if (font->GetTable(OTS_TAG_GLYF) || font->GetTable(OTS_TAG_LOCA)) {
       // mixing outline formats is not recommended
       return OTS_FAILURE_MSG_HDR("font contains both PS and TT glyphs");
     }
   } else {
-    if (!font->glyf || !font->loca) {
+    if (!font->GetTable(OTS_TAG_GLYF) || !font->GetTable(OTS_TAG_LOCA)) {
       // No TrueType glyph found.
-#define PASSTHRU_TABLE(tag_) (table_map.find(tag_) != table_map.end() && \
-                              GetTableAction(header, tag_) == ots::TABLE_ACTION_PASSTHRU)
-      // We don't sanitise bitmap table, but don't reject bitmap-only fonts if
-      // we keep the tables.
-      if (!PASSTHRU_TABLE(OTS_TAG('C','B','D','T')) ||
-          !PASSTHRU_TABLE(OTS_TAG('C','B','L','C'))) {
+      //
+      // We don't sanitize bitmap tables, but don’t reject bitmap-only fonts if
+      // we are asked to pass them thru.
+      // Also don’t reject if we are asked to pass glyf/loca thru.
+      if (!font->GetTable(OTS_TAG('C','B','D','T')) &&
+          !font->GetTable(OTS_TAG('C','B','L','C'))) {
         return OTS_FAILURE_MSG_HDR("no supported glyph shapes table(s) present");
       }
-#undef PASSTHRU_TABLE
     }
   }
 
   uint16_t num_output_tables = 0;
-  for (std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.begin();
-       it != table_map.end(); ++it) {
-    ots::TableAction action = GetTableAction(header, it->first);
-    if (action == ots::TABLE_ACTION_PASSTHRU) {
+  for (const auto &it : table_map) {
+    ots::Table *table = font->GetTable(it.first);
+    if (table != NULL && table->ShouldSerialize())
       num_output_tables++;
-    } else {
-      for (unsigned i = 0; table_parsers[i].parse != NULL; ++i) {
-        if (table_parsers[i].tag == it->first &&
-            table_parsers[i].should_serialise(font)) {
-          num_output_tables++;
-          break;
-        }
-      }
-    }
   }
 
   uint16_t max_pow2 = 0;
@@ -738,85 +739,49 @@ bool ProcessGeneric(ots::OpenTypeFile *header,
     return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
-  std::vector<ots::OutputTable> out_tables;
+  std::vector<ots::TableEntry> out_tables;
 
   size_t head_table_offset = 0;
-  for (std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.begin();
-       it != table_map.end(); ++it) {
-    uint32_t input_offset = it->second.offset;
-    const ots::TableMap::const_iterator ot = header->tables.find(input_offset);
-    if (ot != header->tables.end()) {
-      ots::OutputTable out = ot->second.second;
+  for (const auto &it : table_map) {
+    uint32_t input_offset = it.second.offset;
+    const auto &ot = header->table_entries.find(input_offset);
+    if (ot != header->table_entries.end()) {
+      ots::TableEntry out = ot->second;
       if (out.tag == OTS_TAG('h','e','a','d')) {
         head_table_offset = out.offset;
       }
       out_tables.push_back(out);
     } else {
-      ots::OutputTable out;
-      out.tag = it->first;
+      ots::TableEntry out;
+      out.tag = it.first;
       out.offset = output->Tell();
 
       if (out.tag == OTS_TAG('h','e','a','d')) {
         head_table_offset = out.offset;
       }
 
-      ots::TableAction action = GetTableAction(header, it->first);
-      if (action == ots::TABLE_ACTION_PASSTHRU) {
+      ots::Table *table = font->GetTable(out.tag);
+      if (table != NULL && table->ShouldSerialize()) {
         output->ResetChecksum();
-        const uint8_t* table_data;
-        size_t table_length;
-
-        if (!GetTableData(data, it->second, &arena, &table_length, &table_data)) {
-          return OTS_FAILURE_MSG_HDR("Failed to uncompress table");
-        }
-
-        if (!output->Write(table_data, table_length)) {
-          return OTS_FAILURE_MSG_HDR("Failed to serialize table");
+        if (!table->Serialize(output)) {
+          return OTS_FAILURE_MSG_TAG("Failed to serialize table", out.tag);
         }
 
         const size_t end_offset = output->Tell();
         if (end_offset <= out.offset) {
           // paranoid check. |end_offset| is supposed to be greater than the offset,
           // as long as the Tell() interface is implemented correctly.
-          return OTS_FAILURE_MSG_HDR("error writing output");
+          return OTS_FAILURE_MSG_TAG("Table is empty or have -ve size", out.tag);
         }
         out.length = end_offset - out.offset;
 
         // align tables to four bytes
         if (!output->Pad((4 - (end_offset & 3)) % 4)) {
-          return OTS_FAILURE_MSG_HDR("error writing output");
+          return OTS_FAILURE_MSG_TAG("Failed to pad table to 4 bytes", out.tag);
         }
         out.chksum = output->chksum();
         out_tables.push_back(out);
-        header->tables[input_offset] = std::make_pair(font, out);
-      } else {
-        for (unsigned i = 0; table_parsers[i].parse != NULL; ++i) {
-          if (table_parsers[i].tag == it->first &&
-              table_parsers[i].should_serialise(font)) {
-            output->ResetChecksum();
-            if (!table_parsers[i].serialise(output, font)) {
-              return OTS_FAILURE_MSG_TAG("failed to serialize table", table_parsers[i].tag);
-            }
-
-            const size_t end_offset = output->Tell();
-            if (end_offset <= out.offset) {
-              // paranoid check. |end_offset| is supposed to be greater than the offset,
-              // as long as the Tell() interface is implemented correctly.
-              return OTS_FAILURE_MSG_HDR("error writing output");
-            }
-            out.length = end_offset - out.offset;
-
-            // align tables to four bytes
-            if (!output->Pad((4 - (end_offset & 3)) % 4)) {
-              return OTS_FAILURE_MSG_HDR("error writing output");
-            }
-            out.chksum = output->chksum();
-            out_tables.push_back(out);
-            header->tables[input_offset] = std::make_pair(font, out);
-
-            break;
-          }
-        }
+        header->table_entries[input_offset] = out;
       }
     }
   }
@@ -869,6 +834,190 @@ bool ProcessGeneric(ots::OpenTypeFile *header,
 
 namespace ots {
 
+FontFile::~FontFile() {
+  for (const auto& it : tables) {
+    delete it.second;
+  }
+  tables.clear();
+}
+
+bool Font::ParseTable(const TableEntry& table_entry, const uint8_t* data,
+                      Arena &arena) {
+  uint32_t tag = table_entry.tag;
+  TableAction action = GetTableAction(file, tag);
+  if (action == TABLE_ACTION_DROP) {
+    return true;
+  }
+
+  const auto &it = file->tables.find(table_entry);
+  if (it != file->tables.end()) {
+    m_tables[tag] = it->second;
+    return true;
+  }
+
+  Table *table = NULL;
+  bool ret = false;
+
+  if (action == TABLE_ACTION_PASSTHRU) {
+    table = new TablePassthru(this, tag);
+  } else {
+    switch (tag) {
+      case OTS_TAG_CFF:  table = new OpenTypeCFF(this,  tag); break;
+      case OTS_TAG_CMAP: table = new OpenTypeCMAP(this, tag); break;
+      case OTS_TAG_CVT:  table = new OpenTypeCVT(this,  tag); break;
+      case OTS_TAG_FPGM: table = new OpenTypeFPGM(this, tag); break;
+      case OTS_TAG_GASP: table = new OpenTypeGASP(this, tag); break;
+      case OTS_TAG_GDEF: table = new OpenTypeGDEF(this, tag); break;
+      case OTS_TAG_GLYF: table = new OpenTypeGLYF(this, tag); break;
+      case OTS_TAG_GPOS: table = new OpenTypeGPOS(this, tag); break;
+      case OTS_TAG_GSUB: table = new OpenTypeGSUB(this, tag); break;
+      case OTS_TAG_HDMX: table = new OpenTypeHDMX(this, tag); break;
+      case OTS_TAG_HEAD: table = new OpenTypeHEAD(this, tag); break;
+      case OTS_TAG_HHEA: table = new OpenTypeHHEA(this, tag); break;
+      case OTS_TAG_HMTX: table = new OpenTypeHMTX(this, tag); break;
+      case OTS_TAG_KERN: table = new OpenTypeKERN(this, tag); break;
+      case OTS_TAG_LOCA: table = new OpenTypeLOCA(this, tag); break;
+      case OTS_TAG_LTSH: table = new OpenTypeLTSH(this, tag); break;
+      case OTS_TAG_MATH: table = new OpenTypeMATH(this, tag); break;
+      case OTS_TAG_MAXP: table = new OpenTypeMAXP(this, tag); break;
+      case OTS_TAG_NAME: table = new OpenTypeNAME(this, tag); break;
+      case OTS_TAG_OS2:  table = new OpenTypeOS2(this,  tag); break;
+      case OTS_TAG_POST: table = new OpenTypePOST(this, tag); break;
+      case OTS_TAG_PREP: table = new OpenTypePREP(this, tag); break;
+      case OTS_TAG_VDMX: table = new OpenTypeVDMX(this, tag); break;
+      case OTS_TAG_VORG: table = new OpenTypeVORG(this, tag); break;
+      case OTS_TAG_VHEA: table = new OpenTypeVHEA(this, tag); break;
+      case OTS_TAG_VMTX: table = new OpenTypeVMTX(this, tag); break;
+      // Graphite tables
+#ifdef OTS_GRAPHITE
+      case OTS_TAG_FEAT: table = new OpenTypeFEAT(this, tag); break;
+      case OTS_TAG_GLAT: table = new OpenTypeGLAT(this, tag); break;
+      case OTS_TAG_GLOC: table = new OpenTypeGLOC(this, tag); break;
+      case OTS_TAG_SILE: table = new OpenTypeSILE(this, tag); break;
+      case OTS_TAG_SILF: table = new OpenTypeSILF(this, tag); break;
+      case OTS_TAG_SILL: table = new OpenTypeSILL(this, tag); break;
+#endif
+      default: break;
+    }
+  }
+
+  if (table) {
+    const uint8_t* table_data;
+    size_t table_length;
+
+    ret = GetTableData(data, table_entry, arena, &table_length, &table_data);
+    if (ret) {
+      // FIXME: Parsing some tables will fail if the table is not added to
+      // m_tables first.
+      m_tables[tag] = table;
+      ret = table->Parse(table_data, table_length);
+      if (ret)
+        file->tables[table_entry] = table;
+      else
+        m_tables.erase(tag);
+    }
+  }
+
+  if (!ret)
+    delete table;
+
+  return ret;
+}
+
+Table* Font::GetTable(uint32_t tag) const {
+  const auto &it = m_tables.find(tag);
+  if (it != m_tables.end())
+    return it->second;
+  return NULL;
+}
+
+Table* Font::GetTypedTable(uint32_t tag) const {
+  Table* t = GetTable(tag);
+  if (t && t->Type() == tag)
+    return t;
+  return NULL;
+}
+
+void Font::DropGraphite() {
+  file->context->Message(0, "Dropping all Graphite tables");
+  for (const std::pair<uint32_t, Table*> entry : m_tables) {
+    if (entry.first == OTS_TAG_FEAT ||
+        entry.first == OTS_TAG_GLAT ||
+        entry.first == OTS_TAG_GLOC ||
+        entry.first == OTS_TAG_SILE ||
+        entry.first == OTS_TAG_SILF ||
+        entry.first == OTS_TAG_SILL) {
+      entry.second->Drop("Discarding Graphite table");
+    }
+  }
+  dropped_graphite = true;
+}
+
+bool Table::ShouldSerialize() {
+  return m_shouldSerialize;
+}
+
+void Table::Message(int level, const char *format, va_list va) {
+  char msg[206] = { OTS_UNTAG(m_tag), ':', ' ' };
+  std::vsnprintf(msg + 6, 200, format, va);
+// fprintf(stderr, format, va); // font debugging, see TenFourFox issue 317
+  m_font->file->context->Message(level, msg);
+}
+
+bool Table::Error(const char *format, ...) {
+  va_list va;
+  va_start(va, format);
+  Message(0, format, va);
+  va_end(va);
+
+  return false;
+}
+
+bool Table::Warning(const char *format, ...) {
+  va_list va;
+  va_start(va, format);
+  Message(1, format, va);
+  va_end(va);
+
+  return true;
+}
+
+bool Table::Drop(const char *format, ...) {
+  m_shouldSerialize = false;
+
+  va_list va;
+  va_start(va, format);
+  Message(0, format, va);
+  m_font->file->context->Message(0, "Table discarded");
+  va_end(va);
+
+  return true;
+}
+
+bool Table::DropGraphite(const char *format, ...) {
+  va_list va;
+  va_start(va, format);
+  Message(0, format, va);
+  va_end(va);
+
+  m_font->DropGraphite();
+  return true;
+}
+
+bool TablePassthru::Parse(const uint8_t *data, size_t length) {
+  m_data = data;
+  m_length = length;
+  return true;
+}
+
+bool TablePassthru::Serialize(OTSStream *out) {
+    if (!out->Write(m_data, m_length)) {
+    return Error("Failed to write table");
+  }
+
+  return true;
+}
+
 bool IsValidVersionTag(uint32_t tag) {
   return tag == 0x000010000 ||
          // OpenType fonts with CFF data have 'OTTO' tag.
@@ -882,7 +1031,7 @@ bool OTSContext::Process(OTSStream *output,
                          const uint8_t *data,
                          size_t length,
                          uint32_t index) {
-  OpenTypeFile header;
+  FontFile header;
   Font font(&header);
   header.context = this;
 
diff --git a/gfx/ots/src/ots.h b/gfx/ots/src/ots.h
index 2d13f8d6d..b627bcc84 100644
--- a/gfx/ots/src/ots.h
+++ b/gfx/ots/src/ots.h
@@ -1,10 +1,14 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #ifndef OTS_H_
 #define OTS_H_
 
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include <stddef.h>
 #include <cstdarg>
 #include <cstddef>
@@ -54,13 +58,9 @@ namespace ots {
 #define OTS_WARNING_MSG_(otf_,...) \
   OTS_MESSAGE_(1,otf_,__VA_ARGS__)
 
-// Generate a message with an associated table tag
-#define OTS_FAILURE_MSG_TAG_(otf_,msg_,tag_) \
-  (OTS_MESSAGE_(0,otf_,"%c%c%c%c: %s", OTS_UNTAG(tag_), msg_), false)
-
 // Convenience macros for use in files that only handle a single table tag,
 // defined as TABLE_NAME at the top of the file; the 'file' variable is
-// expected to be the current OpenTypeFile pointer.
+// expected to be the current FontFile pointer.
 #define OTS_FAILURE_MSG(...) OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__)
 
 #define OTS_WARNING(...) OTS_WARNING_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__)
@@ -186,109 +186,154 @@ template<typename T> T Round2(T value) {
 
 bool IsValidVersionTag(uint32_t tag);
 
-#define FOR_EACH_TABLE_TYPE \
-  F(cff, CFF) \
-  F(cmap, CMAP) \
-  F(cvt, CVT) \
-  F(fpgm, FPGM) \
-  F(gasp, GASP) \
-  F(gdef, GDEF) \
-  F(glyf, GLYF) \
-  F(gpos, GPOS) \
-  F(gsub, GSUB) \
-  F(hdmx, HDMX) \
-  F(head, HEAD) \
-  F(hhea, HHEA) \
-  F(hmtx, HMTX) \
-  F(kern, KERN) \
-  F(loca, LOCA) \
-  F(ltsh, LTSH) \
-  F(math, MATH) \
-  F(maxp, MAXP) \
-  F(name, NAME) \
-  F(os2, OS2) \
-  F(post, POST) \
-  F(prep, PREP) \
-  F(vdmx, VDMX) \
-  F(vorg, VORG) \
-  F(vhea, VHEA) \
-  F(vmtx, VMTX)
-
-#define F(name, capname) struct OpenType##capname;
-FOR_EACH_TABLE_TYPE
-#undef F
+#define OTS_TAG_CFF  OTS_TAG('C','F','F',' ')
+#define OTS_TAG_CMAP OTS_TAG('c','m','a','p')
+#define OTS_TAG_CVT  OTS_TAG('c','v','t',' ')
+#define OTS_TAG_FEAT OTS_TAG('F','e','a','t')
+#define OTS_TAG_FPGM OTS_TAG('f','p','g','m')
+#define OTS_TAG_GASP OTS_TAG('g','a','s','p')
+#define OTS_TAG_GDEF OTS_TAG('G','D','E','F')
+#define OTS_TAG_GLAT OTS_TAG('G','l','a','t')
+#define OTS_TAG_GLOC OTS_TAG('G','l','o','c')
+#define OTS_TAG_GLYF OTS_TAG('g','l','y','f')
+#define OTS_TAG_GPOS OTS_TAG('G','P','O','S')
+#define OTS_TAG_GSUB OTS_TAG('G','S','U','B')
+#define OTS_TAG_HDMX OTS_TAG('h','d','m','x')
+#define OTS_TAG_HEAD OTS_TAG('h','e','a','d')
+#define OTS_TAG_HHEA OTS_TAG('h','h','e','a')
+#define OTS_TAG_HMTX OTS_TAG('h','m','t','x')
+#define OTS_TAG_KERN OTS_TAG('k','e','r','n')
+#define OTS_TAG_LOCA OTS_TAG('l','o','c','a')
+#define OTS_TAG_LTSH OTS_TAG('L','T','S','H')
+#define OTS_TAG_MATH OTS_TAG('M','A','T','H')
+#define OTS_TAG_MAXP OTS_TAG('m','a','x','p')
+#define OTS_TAG_NAME OTS_TAG('n','a','m','e')
+#define OTS_TAG_OS2  OTS_TAG('O','S','/','2')
+#define OTS_TAG_POST OTS_TAG('p','o','s','t')
+#define OTS_TAG_PREP OTS_TAG('p','r','e','p')
+#define OTS_TAG_SILE OTS_TAG('S','i','l','e')
+#define OTS_TAG_SILF OTS_TAG('S','i','l','f')
+#define OTS_TAG_SILL OTS_TAG('S','i','l','l')
+#define OTS_TAG_VDMX OTS_TAG('V','D','M','X')
+#define OTS_TAG_VHEA OTS_TAG('v','h','e','a')
+#define OTS_TAG_VMTX OTS_TAG('v','m','t','x')
+#define OTS_TAG_VORG OTS_TAG('V','O','R','G')
 
 struct Font;
-struct OpenTypeFile;
+struct FontFile;
+struct TableEntry;
+struct Arena;
+
+class Table {
+ public:
+  explicit Table(Font *font, uint32_t tag, uint32_t type)
+      : m_tag(tag),
+        m_type(type),
+        m_font(font),
+        m_shouldSerialize(true) {
+  }
+
+  virtual ~Table() { }
 
-#define F(name, capname) \
-bool ots_##name##_parse(Font *f, const uint8_t *d, size_t l); \
-bool ots_##name##_should_serialise(Font *f); \
-bool ots_##name##_serialise(OTSStream *s, Font *f); \
-void ots_##name##_reuse(Font *f, Font *o);\
-void ots_##name##_free(Font *f);
-FOR_EACH_TABLE_TYPE
-#undef F
+  virtual bool Parse(const uint8_t *data, size_t length) = 0;
+  virtual bool Serialize(OTSStream *out) = 0;
+  virtual bool ShouldSerialize();
+
+  // Return the tag (table type) this Table was parsed as, to support
+  // "poor man's RTTI" so that we know if we can safely down-cast to
+  // a specific Table subclass. The m_type field is initialized to the
+  // appropriate tag when a subclass is constructed, or to zero for
+  // TablePassthru (indicating unparsed data).
+  uint32_t Type() { return m_type; }
+
+  Font* GetFont() { return m_font; }
+
+  bool Error(const char *format, ...);
+  bool Warning(const char *format, ...);
+  bool Drop(const char *format, ...);
+  bool DropGraphite(const char *format, ...);
+
+ private:
+  void Message(int level, const char *format, va_list va);
+
+  uint32_t m_tag;
+  uint32_t m_type;
+  Font *m_font;
+  bool m_shouldSerialize;
+};
+
+class TablePassthru : public Table {
+ public:
+  explicit TablePassthru(Font *font, uint32_t tag)
+      : Table(font, tag, 0),
+        m_data(NULL),
+        m_length(0) {
+  }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
+ private:
+  const uint8_t *m_data;
+  size_t m_length;
+};
 
 struct Font {
-  explicit Font(const OpenTypeFile *f)
+  explicit Font(FontFile *f)
       : file(f),
         version(0),
         num_tables(0),
         search_range(0),
         entry_selector(0),
-        range_shift(0) {
-#define F(name, capname) \
-    name = NULL; \
-    name##_reused = false;
-    FOR_EACH_TABLE_TYPE
-#undef F
+        range_shift(0),
+        dropped_graphite(false) {
   }
 
-  ~Font() {
-#define F(name, capname) \
-    if (!name##_reused) {\
-      ots_##name##_free(this); \
-    }
-    FOR_EACH_TABLE_TYPE
-#undef F
-  }
+  bool ParseTable(const TableEntry& tableinfo, const uint8_t* data,
+                  Arena &arena);
+  Table* GetTable(uint32_t tag) const;
+
+  // This checks that the returned Table is actually of the correct subclass
+  // for |tag|, so it can safely be downcast to the corresponding OpenTypeXXXX;
+  // if not (i.e. if the table was treated as Passthru), it will return NULL.
+  Table* GetTypedTable(uint32_t tag) const;
 
-  const OpenTypeFile *file;
+  // Drop all Graphite tables and don't parse new ones.
+  void DropGraphite();
+
+  FontFile *file;
 
   uint32_t version;
   uint16_t num_tables;
   uint16_t search_range;
   uint16_t entry_selector;
   uint16_t range_shift;
+  bool dropped_graphite;
 
-#define F(name, capname) \
-  OpenType##capname *name; \
-  bool name##_reused;
-FOR_EACH_TABLE_TYPE
-#undef F
+ private:
+  std::map<uint32_t, Table*> m_tables;
 };
 
-struct OutputTable {
+struct TableEntry {
   uint32_t tag;
-  size_t offset;
-  size_t length;
+  uint32_t offset;
+  uint32_t length;
+  uint32_t uncompressed_length;
   uint32_t chksum;
 
-  bool operator<(const OutputTable& other) const {
+  bool operator<(const TableEntry& other) const {
     return tag < other.tag;
   }
 };
 
-typedef std::map<uint32_t, std::pair<Font*, OutputTable> > TableMap;
+struct FontFile {
+  ~FontFile();
 
-struct OpenTypeFile {
   OTSContext *context;
-  TableMap tables;
+  std::map<TableEntry, Table*> tables;
+  std::map<uint32_t, TableEntry> table_entries;
 };
 
 }  // namespace ots
 
-#undef FOR_EACH_TABLE_TYPE
-
 #endif  // OTS_H_
diff --git a/gfx/ots/src/post.cc b/gfx/ots/src/post.cc
index a110b2dea..707d66dbf 100644
--- a/gfx/ots/src/post.cc
+++ b/gfx/ots/src/post.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -13,70 +13,71 @@
 
 namespace ots {
 
-bool ots_post_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypePOST::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypePOST *post = new OpenTypePOST;
-  font->post = post;
+  if (!table.ReadU32(&this->version)) {
+    return Error("Failed to read table version");
+  }
 
-  if (!table.ReadU32(&post->version) ||
-      !table.ReadU32(&post->italic_angle) ||
-      !table.ReadS16(&post->underline) ||
-      !table.ReadS16(&post->underline_thickness) ||
-      !table.ReadU32(&post->is_fixed_pitch)) {
-    return OTS_FAILURE_MSG("Failed to read post header");
+  if (this->version != 0x00010000 &&
+      this->version != 0x00020000 &&
+      this->version != 0x00030000) {
+    // 0x00025000 is deprecated. We don't accept it.
+    return Error("Unsupported table version 0x%x", this->version);
   }
 
-  if (post->underline_thickness < 0) {
-    post->underline_thickness = 1;
+  if (!table.ReadU32(&this->italic_angle) ||
+      !table.ReadS16(&this->underline) ||
+      !table.ReadS16(&this->underline_thickness) ||
+      !table.ReadU32(&this->is_fixed_pitch) ||
+      // We don't care about the memory usage fields. We'll set all these to
+      // zero when serialising
+      !table.Skip(16)) {
+    return Error("Failed to read table header");
   }
 
-  if (post->version == 0x00010000) {
-    return true;
-  } else if (post->version == 0x00030000) {
+  if (this->underline_thickness < 0) {
+    this->underline_thickness = 1;
+  }
+
+  if (this->version == 0x00010000 || this->version == 0x00030000) {
     return true;
-  } else if (post->version != 0x00020000) {
-    // 0x00025000 is deprecated. We don't accept it.
-    return OTS_FAILURE_MSG("Bad post version %x", post->version);
   }
 
   // We have a version 2 table with a list of Pascal strings at the end
 
-  // We don't care about the memory usage fields. We'll set all these to zero
-  // when serialising
-  if (!table.Skip(16)) {
-    return OTS_FAILURE_MSG("Failed to skip memory usage in post table");
-  }
-
   uint16_t num_glyphs = 0;
   if (!table.ReadU16(&num_glyphs)) {
-    return OTS_FAILURE_MSG("Failed to read number of glyphs");
+    return Error("Failed to read numberOfGlyphs");
   }
 
-  if (!font->maxp) {
-    return OTS_FAILURE_MSG("No maxp table required by post table");
+  OpenTypeMAXP* maxp = static_cast<OpenTypeMAXP*>
+    (GetFont()->GetTable(OTS_TAG_MAXP));
+  if (!maxp) {
+    return Error("Missing required maxp table");
   }
 
   if (num_glyphs == 0) {
-    if (font->maxp->num_glyphs > 258) {
-      return OTS_FAILURE_MSG("Can't have no glyphs in the post table if there are more than 256 glyphs in the font");
+    if (maxp->num_glyphs > 258) {
+      return Error("Can't have no glyphs in the post table if there are more "
+                   "than 258 glyphs in the font");
     }
-    OTS_WARNING("table version is 1, but no glyf names are found");
     // workaround for fonts in http://www.fontsquirrel.com/fontface
     // (e.g., yataghan.ttf).
-    post->version = 0x00010000;
-    return true;
+    this->version = 0x00010000;
+    return Warning("Table version is 1, but no glyph names are found");
   }
 
-  if (num_glyphs != font->maxp->num_glyphs) {
+  if (num_glyphs != maxp->num_glyphs) {
     // Note: Fixedsys500c.ttf seems to have inconsistent num_glyphs values.
-    return OTS_FAILURE_MSG("Bad number of glyphs in post table %d", num_glyphs);
+    return Error("Bad number of glyphs: %d", num_glyphs);
   }
 
-  post->glyph_name_index.resize(num_glyphs);
+  this->glyph_name_index.resize(num_glyphs);
   for (unsigned i = 0; i < num_glyphs; ++i) {
-    if (!table.ReadU16(&post->glyph_name_index[i])) {
-      return OTS_FAILURE_MSG("Failed to read post information for glyph %d", i);
+    if (!table.ReadU16(&this->glyph_name_index[i])) {
+      return Error("Failed to read glyph name %d", i);
     }
     // Note: A strict interpretation of the specification requires name indexes
     // are less than 32768. This, however, excludes fonts like unifont.ttf
@@ -93,101 +94,85 @@ bool ots_post_parse(Font *font, const uint8_t *data, size_t length) {
     if (strings == strings_end) break;
     const unsigned string_length = *strings;
     if (strings + 1 + string_length > strings_end) {
-      return OTS_FAILURE_MSG("Bad string length %d", string_length);
+      return Error("Bad string length %d", string_length);
     }
     if (std::memchr(strings + 1, '\0', string_length)) {
-      return OTS_FAILURE_MSG("Bad string of length %d", string_length);
+      return Error("Bad string of length %d", string_length);
     }
-    post->names.push_back(
+    this->names.push_back(
         std::string(reinterpret_cast<const char*>(strings + 1), string_length));
     strings += 1 + string_length;
   }
-  const unsigned num_strings = post->names.size();
+  const unsigned num_strings = this->names.size();
 
   // check that all the references are within bounds
   for (unsigned i = 0; i < num_glyphs; ++i) {
-    unsigned offset = post->glyph_name_index[i];
+    unsigned offset = this->glyph_name_index[i];
     if (offset < 258) {
       continue;
     }
 
     offset -= 258;
     if (offset >= num_strings) {
-      return OTS_FAILURE_MSG("Bad string index %d", offset);
+      return Error("Bad string index %d", offset);
     }
   }
 
   return true;
 }
 
-bool ots_post_should_serialise(Font *font) {
-  return font->post != NULL;
-}
-
-bool ots_post_serialise(OTSStream *out, Font *font) {
-  const OpenTypePOST *post = font->post;
-
+bool OpenTypePOST::Serialize(OTSStream *out) {
   // OpenType with CFF glyphs must have v3 post table.
-  if (post && font->cff && post->version != 0x00030000) {
-    return OTS_FAILURE_MSG("Bad post version %x", post->version);
+  if (GetFont()->GetTable(OTS_TAG_CFF) && this->version != 0x00030000) {
+    return Error("Only version supported for fonts with CFF table is 0x00030000"
+                 " not 0x%x", this->version);
   }
 
-  if (!out->WriteU32(post->version) ||
-      !out->WriteU32(post->italic_angle) ||
-      !out->WriteS16(post->underline) ||
-      !out->WriteS16(post->underline_thickness) ||
-      !out->WriteU32(post->is_fixed_pitch) ||
+  if (!out->WriteU32(this->version) ||
+      !out->WriteU32(this->italic_angle) ||
+      !out->WriteS16(this->underline) ||
+      !out->WriteS16(this->underline_thickness) ||
+      !out->WriteU32(this->is_fixed_pitch) ||
       !out->WriteU32(0) ||
       !out->WriteU32(0) ||
       !out->WriteU32(0) ||
       !out->WriteU32(0)) {
-    return OTS_FAILURE_MSG("Failed to write post header");
+    return Error("Failed to write post header");
   }
 
-  if (post->version != 0x00020000) {
+  if (this->version != 0x00020000) {
     return true;  // v1.0 and v3.0 does not have glyph names.
   }
 
   const uint16_t num_indexes =
-      static_cast<uint16_t>(post->glyph_name_index.size());
-  if (num_indexes != post->glyph_name_index.size() ||
+      static_cast<uint16_t>(this->glyph_name_index.size());
+  if (num_indexes != this->glyph_name_index.size() ||
       !out->WriteU16(num_indexes)) {
-    return OTS_FAILURE_MSG("Failed to write number of indices");
+    return Error("Failed to write number of indices");
   }
 
   for (uint16_t i = 0; i < num_indexes; ++i) {
-    if (!out->WriteU16(post->glyph_name_index[i])) {
-      return OTS_FAILURE_MSG("Failed to write name index %d", i);
+    if (!out->WriteU16(this->glyph_name_index[i])) {
+      return Error("Failed to write name index %d", i);
     }
   }
 
   // Now we just have to write out the strings in the correct order
-  for (unsigned i = 0; i < post->names.size(); ++i) {
-    const std::string& s = post->names[i];
+  for (unsigned i = 0; i < this->names.size(); ++i) {
+    const std::string& s = this->names[i];
     const uint8_t string_length = static_cast<uint8_t>(s.size());
     if (string_length != s.size() ||
         !out->Write(&string_length, 1)) {
-      return OTS_FAILURE_MSG("Failed to write string %d", i);
+      return Error("Failed to write string %d", i);
     }
     // Some ttf fonts (e.g., frank.ttf on Windows Vista) have zero-length name.
     // We allow them.
     if (string_length > 0 && !out->Write(s.data(), string_length)) {
-      return OTS_FAILURE_MSG("Failed to write string length for string %d", i);
+      return Error("Failed to write string length for string %d", i);
     }
   }
 
   return true;
 }
 
-void ots_post_reuse(Font *font, Font *other) {
-  font->post = other->post;
-  font->post_reused = true;
-}
-
-void ots_post_free(Font *font) {
-  delete font->post;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/post.h b/gfx/ots/src/post.h
index f220d4fc7..c341e391c 100644
--- a/gfx/ots/src/post.h
+++ b/gfx/ots/src/post.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -13,7 +13,15 @@
 
 namespace ots {
 
-struct OpenTypePOST {
+class OpenTypePOST : public Table {
+ public:
+  explicit OpenTypePOST(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+
+private:
   uint32_t version;
   uint32_t italic_angle;
   int16_t underline;
diff --git a/gfx/ots/src/prep.cc b/gfx/ots/src/prep.cc
index 1c9b45f91..943bb45b9 100644
--- a/gfx/ots/src/prep.cc
+++ b/gfx/ots/src/prep.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,53 +7,37 @@
 // prep - Control Value Program
 // http://www.microsoft.com/typography/otspec/prep.htm
 
-#define TABLE_NAME "prep"
-
 namespace ots {
 
-bool ots_prep_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypePREP::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
-  OpenTypePREP *prep = new OpenTypePREP;
-  font->prep = prep;
-
   if (length >= 128 * 1024u) {
-    return OTS_FAILURE_MSG("table length %ld > 120K", length);  // almost all prep tables are less than 9k bytes.
+    // almost all prep tables are less than 9k bytes.
+    return Error("Table length %ld > 120K", length);
   }
 
   if (!table.Skip(length)) {
-    return OTS_FAILURE_MSG("Failed to read table of length %ld", length);
+    return Error("Failed to read table of length %ld", length);
   }
 
-  prep->data = data;
-  prep->length = length;
+  this->m_data = data;
+  this->m_length = length;
   return true;
 }
 
-bool ots_prep_should_serialise(Font *font) {
-  if (!font->glyf) return false;  // this table is not for CFF fonts.
-  return font->prep != NULL;
-}
-
-bool ots_prep_serialise(OTSStream *out, Font *font) {
-  const OpenTypePREP *prep = font->prep;
-
-  if (!out->Write(prep->data, prep->length)) {
-    return OTS_FAILURE_MSG("Failed to write table length");
+bool OpenTypePREP::Serialize(OTSStream *out) {
+  if (!out->Write(this->m_data, this->m_length)) {
+    return Error("Failed to write table length");
   }
 
   return true;
 }
 
-void ots_prep_reuse(Font *font, Font *other) {
-  font->prep = other->prep;
-  font->prep_reused = true;
-}
-
-void ots_prep_free(Font *font) {
-  delete font->prep;
+bool OpenTypePREP::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for CFF fonts.
+         GetFont()->GetTable(OTS_TAG_GLYF) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/prep.h b/gfx/ots/src/prep.h
index 935ca111c..4d3eda2cd 100644
--- a/gfx/ots/src/prep.h
+++ b/gfx/ots/src/prep.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,9 +9,18 @@
 
 namespace ots {
 
-struct OpenTypePREP {
-  const uint8_t *data;
-  uint32_t length;
+class OpenTypePREP : public Table {
+ public:
+  explicit OpenTypePREP(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
+  const uint8_t *m_data;
+  uint32_t m_length;
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/sile.cc b/gfx/ots/src/sile.cc
new file mode 100644
index 000000000..74405e56e
--- /dev/null
+++ b/gfx/ots/src/sile.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sile.h"
+
+namespace ots {
+
+bool OpenTypeSILE::Parse(const uint8_t* data, size_t length) {
+  if (GetFont()->dropped_graphite) {
+    return Drop("Skipping Graphite table");
+  }
+  Buffer table(data, length);
+
+  if (!table.ReadU32(&this->version) || this->version >> 16 != 1) {
+    return DropGraphite("Failed to read valid version");
+  }
+  if (!table.ReadU32(&this->checksum)) {
+    return DropGraphite("Failed to read checksum");
+  }
+  if (!table.ReadU32(&this->createTime[0]) ||
+      !table.ReadU32(&this->createTime[1])) {
+    return DropGraphite("Failed to read createTime");
+  }
+  if (!table.ReadU32(&this->modifyTime[0]) ||
+      !table.ReadU32(&this->modifyTime[1])) {
+    return DropGraphite("Failed to read modifyTime");
+  }
+
+  if (!table.ReadU16(&this->fontNameLength)) {
+    return DropGraphite("Failed to read fontNameLength");
+  }
+  //this->fontName.resize(this->fontNameLength);
+  for (unsigned i = 0; i < this->fontNameLength; ++i) {
+    this->fontName.emplace_back();
+    if (!table.ReadU16(&this->fontName[i])) {
+      return DropGraphite("Failed to read fontName[%u]", i);
+    }
+  }
+
+  if (!table.ReadU16(&this->fontFileLength)) {
+    return DropGraphite("Failed to read fontFileLength");
+  }
+  //this->baseFile.resize(this->fontFileLength);
+  for (unsigned i = 0; i < this->fontFileLength; ++i) {
+    this->baseFile.emplace_back();
+    if (!table.ReadU16(&this->baseFile[i])) {
+      return DropGraphite("Failed to read baseFile[%u]", i);
+    }
+  }
+
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeSILE::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      !out->WriteU32(this->checksum) ||
+      !out->WriteU32(this->createTime[0]) ||
+      !out->WriteU32(this->createTime[1]) ||
+      !out->WriteU32(this->modifyTime[0]) ||
+      !out->WriteU32(this->modifyTime[1]) ||
+      !out->WriteU16(this->fontNameLength) ||
+      !SerializeParts(this->fontName, out) ||
+      !out->WriteU16(this->fontFileLength) ||
+      !SerializeParts(this->baseFile, out)) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+}  // namespace ots
diff --git a/gfx/ots/src/sile.h b/gfx/ots/src/sile.h
new file mode 100644
index 000000000..bdb00606f
--- /dev/null
+++ b/gfx/ots/src/sile.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef OTS_SILE_H_
+#define OTS_SILE_H_
+
+#include "ots.h"
+#include "graphite.h"
+
+#include <vector>
+
+namespace ots {
+
+class OpenTypeSILE : public Table {
+ public:
+  explicit OpenTypeSILE(Font* font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length);
+  bool Serialize(OTSStream* out);
+
+ private:
+  uint32_t version;
+  uint32_t checksum;
+  uint32_t createTime[2];
+  uint32_t modifyTime[2];
+  uint16_t fontNameLength;
+  std::vector<uint16_t> fontName;
+  uint16_t fontFileLength;
+  std::vector<uint16_t> baseFile;
+};
+
+}  // namespace ots
+
+#endif  // OTS_SILE_H_
diff --git a/gfx/ots/src/silf.cc b/gfx/ots/src/silf.cc
new file mode 100644
index 000000000..56970427b
--- /dev/null
+++ b/gfx/ots/src/silf.cc
@@ -0,0 +1,978 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "silf.h"
+
+#include "name.h"
+#include "mozilla/Compression.h"
+#include <cmath>
+
+namespace ots {
+
+bool OpenTypeSILF::Parse(const uint8_t* data, size_t length,
+                         bool prevent_decompression) {
+  if (GetFont()->dropped_graphite) {
+    return Drop("Skipping Graphite table");
+  }
+  Buffer table(data, length);
+
+  if (!table.ReadU32(&this->version)) {
+    return DropGraphite("Failed to read version");
+  }
+  if (this->version >> 16 != 1 &&
+      this->version >> 16 != 2 &&
+      this->version >> 16 != 3 &&
+      this->version >> 16 != 4 &&
+      this->version >> 16 != 5) {
+    return DropGraphite("Unsupported table version: %u", this->version >> 16);
+  }
+  if (this->version >> 16 >= 3 && !table.ReadU32(&this->compHead)) {
+    return DropGraphite("Failed to read compHead");
+  }
+  if (this->version >> 16 >= 5) {
+    switch ((this->compHead & SCHEME) >> 27) {
+      case 0:  // uncompressed
+        break;
+      case 1: {  // lz4
+        if (prevent_decompression) {
+          return DropGraphite("Illegal nested compression");
+        }
+        std::vector<uint8_t> decompressed(this->compHead & FULL_SIZE);
+        size_t outputSize = 0;
+        bool ret = mozilla::Compression::LZ4::decompressPartial(
+            reinterpret_cast<const char*>(data + table.offset()),
+            table.remaining(),  // input buffer size (input size + padding)
+            reinterpret_cast<char*>(decompressed.data()),
+            decompressed.size(),  // target output size
+            &outputSize);   // return output size
+        if (!ret || outputSize != decompressed.size()) {
+          return DropGraphite("Decompression failed");
+        }
+        return this->Parse(decompressed.data(), decompressed.size(), true);
+      }
+      default:
+        return DropGraphite("Unknown compression scheme");
+    }
+  }
+  if (!table.ReadU16(&this->numSub)) {
+    return DropGraphite("Failed to read numSub");
+  }
+  if (this->version >> 16 >= 2 && !table.ReadU16(&this->reserved)) {
+    return DropGraphite("Failed to read reserved");
+  }
+  if (this->version >> 16 >= 2 && this->reserved != 0) {
+    Warning("Nonzero reserved");
+  }
+
+  unsigned long last_offset = 0;
+  //this->offset.resize(this->numSub);
+  for (unsigned i = 0; i < this->numSub; ++i) {
+    this->offset.emplace_back();
+    if (!table.ReadU32(&this->offset[i]) || this->offset[i] < last_offset) {
+      return DropGraphite("Failed to read offset[%u]", i);
+    }
+    last_offset = this->offset[i];
+  }
+
+  for (unsigned i = 0; i < this->numSub; ++i) {
+    if (table.offset() != this->offset[i]) {
+      return DropGraphite("Offset check failed for tables[%lu]", i);
+    }
+    SILSub subtable(this);
+    if (!subtable.ParsePart(table)) {
+      return DropGraphite("Failed to read tables[%u]", i);
+    }
+    tables.push_back(subtable);
+  }
+
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeSILF::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      (this->version >> 16 >= 3 && !out->WriteU32(this->compHead)) ||
+      !out->WriteU16(this->numSub) ||
+      (this->version >> 16 >= 2 && !out->WriteU16(this->reserved)) ||
+      !SerializeParts(this->offset, out) ||
+      !SerializeParts(this->tables, out)) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::ParsePart(Buffer& table) {
+  size_t init_offset = table.offset();
+  if (parent->version >> 16 >= 3) {
+    if (!table.ReadU32(&this->ruleVersion)) {
+      return parent->Error("SILSub: Failed to read ruleVersion");
+    }
+    if (!table.ReadU16(&this->passOffset)) {
+      return parent->Error("SILSub: Failed to read passOffset");
+    }
+    if (!table.ReadU16(&this->pseudosOffset)) {
+      return parent->Error("SILSub: Failed to read pseudosOffset");
+    }
+  }
+  if (!table.ReadU16(&this->maxGlyphID)) {
+    return parent->Error("SILSub: Failed to read maxGlyphID");
+  }
+  if (!table.ReadS16(&this->extraAscent)) {
+    return parent->Error("SILSub: Failed to read extraAscent");
+  }
+  if (!table.ReadS16(&this->extraDescent)) {
+    return parent->Error("SILSub: Failed to read extraDescent");
+  }
+  if (!table.ReadU8(&this->numPasses)) {
+    return parent->Error("SILSub: Failed to read numPasses");
+  }
+  if (!table.ReadU8(&this->iSubst) || this->iSubst > this->numPasses) {
+    return parent->Error("SILSub: Failed to read valid iSubst");
+  }
+  if (!table.ReadU8(&this->iPos) || this->iPos > this->numPasses) {
+    return parent->Error("SILSub: Failed to read valid iPos");
+  }
+  if (!table.ReadU8(&this->iJust) || this->iJust > this->numPasses) {
+    return parent->Error("SILSub: Failed to read valid iJust");
+  }
+  if (!table.ReadU8(&this->iBidi) ||
+      !(iBidi == 0xFF || this->iBidi <= this->iPos)) {
+    return parent->Error("SILSub: Failed to read valid iBidi");
+  }
+  if (!table.ReadU8(&this->flags)) {
+    return parent->Error("SILSub: Failed to read flags");
+    // checks omitted
+  }
+  if (!table.ReadU8(&this->maxPreContext)) {
+    return parent->Error("SILSub: Failed to read maxPreContext");
+  }
+  if (!table.ReadU8(&this->maxPostContext)) {
+    return parent->Error("SILSub: Failed to read maxPostContext");
+  }
+  if (!table.ReadU8(&this->attrPseudo)) {
+    return parent->Error("SILSub: Failed to read attrPseudo");
+  }
+  if (!table.ReadU8(&this->attrBreakWeight)) {
+    return parent->Error("SILSub: Failed to read attrBreakWeight");
+  }
+  if (!table.ReadU8(&this->attrDirectionality)) {
+    return parent->Error("SILSub: Failed to read attrDirectionality");
+  }
+  if (parent->version >> 16 >= 2) {
+    if (!table.ReadU8(&this->attrMirroring)) {
+      return parent->Error("SILSub: Failed to read attrMirroring");
+    }
+    if (parent->version >> 16 < 4 && this->attrMirroring != 0) {
+      parent->Warning("SILSub: Nonzero attrMirroring (reserved before v4)");
+    }
+    if (!table.ReadU8(&this->attrSkipPasses)) {
+      return parent->Error("SILSub: Failed to read attrSkipPasses");
+    }
+    if (parent->version >> 16 < 4 && this->attrSkipPasses != 0) {
+      parent->Warning("SILSub: Nonzero attrSkipPasses (reserved2 before v4)");
+    }
+
+    if (!table.ReadU8(&this->numJLevels)) {
+      return parent->Error("SILSub: Failed to read numJLevels");
+    }
+    //this->jLevels.resize(this->numJLevels, parent);
+    for (unsigned i = 0; i < this->numJLevels; ++i) {
+      this->jLevels.emplace_back(parent);
+      if (!this->jLevels[i].ParsePart(table)) {
+        return parent->Error("SILSub: Failed to read jLevels[%u]", i);
+      }
+    }
+  }
+
+  if (!table.ReadU16(&this->numLigComp)) {
+    return parent->Error("SILSub: Failed to read numLigComp");
+  }
+  if (!table.ReadU8(&this->numUserDefn)) {
+    return parent->Error("SILSub: Failed to read numUserDefn");
+  }
+  if (!table.ReadU8(&this->maxCompPerLig)) {
+    return parent->Error("SILSub: Failed to read maxCompPerLig");
+  }
+  if (!table.ReadU8(&this->direction)) {
+    return parent->Error("SILSub: Failed to read direction");
+  }
+  if (!table.ReadU8(&this->attCollisions)) {
+    return parent->Error("SILSub: Failed to read attCollisions");
+  }
+  if (parent->version >> 16 < 5 && this->attCollisions != 0) {
+    parent->Warning("SILSub: Nonzero attCollisions (reserved before v5)");
+  }
+  if (!table.ReadU8(&this->reserved4)) {
+    return parent->Error("SILSub: Failed to read reserved4");
+  }
+  if (this->reserved4 != 0) {
+    parent->Warning("SILSub: Nonzero reserved4");
+  }
+  if (!table.ReadU8(&this->reserved5)) {
+    return parent->Error("SILSub: Failed to read reserved5");
+  }
+  if (this->reserved5 != 0) {
+    parent->Warning("SILSub: Nonzero reserved5");
+  }
+  if (parent->version >> 16 >= 2) {
+    if (!table.ReadU8(&this->reserved6)) {
+      return parent->Error("SILSub: Failed to read reserved6");
+    }
+    if (this->reserved6 != 0) {
+      parent->Warning("SILSub: Nonzero reserved6");
+    }
+
+    if (!table.ReadU8(&this->numCritFeatures)) {
+      return parent->Error("SILSub: Failed to read numCritFeatures");
+    }
+    //this->critFeatures.resize(this->numCritFeatures);
+    for (unsigned i = 0; i < this->numCritFeatures; ++i) {
+      this->critFeatures.emplace_back();
+      if (!table.ReadU16(&this->critFeatures[i])) {
+        return parent->Error("SILSub: Failed to read critFeatures[%u]", i);
+      }
+    }
+
+    if (!table.ReadU8(&this->reserved7)) {
+      return parent->Error("SILSub: Failed to read reserved7");
+    }
+    if (this->reserved7 != 0) {
+      parent->Warning("SILSub: Nonzero reserved7");
+    }
+  }
+
+  if (!table.ReadU8(&this->numScriptTag)) {
+    return parent->Error("SILSub: Failed to read numScriptTag");
+  }
+  //this->scriptTag.resize(this->numScriptTag);
+  for (unsigned i = 0; i < this->numScriptTag; ++i) {
+    this->scriptTag.emplace_back();
+    if (!table.ReadU32(&this->scriptTag[i])) {
+      return parent->Error("SILSub: Failed to read scriptTag[%u]", i);
+    }
+  }
+
+  if (!table.ReadU16(&this->lbGID)) {
+    return parent->Error("SILSub: Failed to read lbGID");
+  }
+  if (this->lbGID > this->maxGlyphID) {
+    parent->Warning("SILSub: lbGID %u outside range 0..%u, replaced with 0",
+                    this->lbGID, this->maxGlyphID);
+    this->lbGID = 0;
+  }
+
+  if (parent->version >> 16 >= 3 &&
+      table.offset() != init_offset + this->passOffset) {
+    return parent->Error("SILSub: passOffset check failed");
+  }
+  unsigned long last_oPass = 0;
+  //this->oPasses.resize(static_cast<unsigned>(this->numPasses) + 1);
+  for (unsigned i = 0; i <= this->numPasses; ++i) {
+    this->oPasses.emplace_back();
+    if (!table.ReadU32(&this->oPasses[i]) || this->oPasses[i] < last_oPass) {
+      return false;
+    }
+    last_oPass = this->oPasses[i];
+  }
+
+  if (parent->version >> 16 >= 3 &&
+      table.offset() != init_offset + this->pseudosOffset) {
+    return parent->Error("SILSub: pseudosOffset check failed");
+  }
+  if (!table.ReadU16(&this->numPseudo)) {
+    return parent->Error("SILSub: Failed to read numPseudo");
+  }
+
+  // The following three fields are deprecated and ignored. We fix them up here
+  // just for internal consistency, but the Graphite engine doesn't care.
+  if (!table.ReadU16(&this->searchPseudo) ||
+      !table.ReadU16(&this->pseudoSelector) ||
+      !table.ReadU16(&this->pseudoShift)) {
+    return parent->Error("SILSub: Failed to read searchPseudo..pseudoShift");
+  }
+  if (this->numPseudo == 0) {
+    if (this->searchPseudo != 0 || this->pseudoSelector != 0 || this->pseudoShift != 0) {
+      this->searchPseudo = this->pseudoSelector = this->pseudoShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numPseudo));
+    if (this->searchPseudo != 6 * (unsigned)std::pow(2, floorLog2) ||
+        this->pseudoSelector != floorLog2 ||
+        this->pseudoShift != 6 * this->numPseudo - this->searchPseudo) {
+      this->searchPseudo = 6 * (unsigned)std::pow(2, floorLog2);
+      this->pseudoSelector = floorLog2;
+      this->pseudoShift = 6 * this->numPseudo - this->searchPseudo;
+    }
+  }
+
+  //this->pMaps.resize(this->numPseudo, parent);
+  for (unsigned i = 0; i < numPseudo; i++) {
+    this->pMaps.emplace_back(parent);
+    if (!this->pMaps[i].ParsePart(table)) {
+      return parent->Error("SILSub: Failed to read pMaps[%u]", i);
+    }
+  }
+
+  if (!this->classes.ParsePart(table)) {
+    return parent->Error("SILSub: Failed to read classes");
+  }
+
+  //this->passes.resize(this->numPasses, parent);
+  for (unsigned i = 0; i < this->numPasses; ++i) {
+    this->passes.emplace_back(parent);
+    if (table.offset() != init_offset + this->oPasses[i]) {
+      return parent->Error("SILSub: Offset check failed for passes[%u]", i);
+    }
+    if (!this->passes[i].ParsePart(table, init_offset, this->oPasses[i+1])) {
+      return parent->Error("SILSub: Failed to read passes[%u]", i);
+    }
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::SerializePart(OTSStream* out) const {
+  if ((parent->version >> 16 >= 3 &&
+       (!out->WriteU32(this->ruleVersion) ||
+        !out->WriteU16(this->passOffset) ||
+        !out->WriteU16(this->pseudosOffset))) ||
+      !out->WriteU16(this->maxGlyphID) ||
+      !out->WriteS16(this->extraAscent) ||
+      !out->WriteS16(this->extraDescent) ||
+      !out->WriteU8(this->numPasses) ||
+      !out->WriteU8(this->iSubst) ||
+      !out->WriteU8(this->iPos) ||
+      !out->WriteU8(this->iJust) ||
+      !out->WriteU8(this->iBidi) ||
+      !out->WriteU8(this->flags) ||
+      !out->WriteU8(this->maxPreContext) ||
+      !out->WriteU8(this->maxPostContext) ||
+      !out->WriteU8(this->attrPseudo) ||
+      !out->WriteU8(this->attrBreakWeight) ||
+      !out->WriteU8(this->attrDirectionality) ||
+      (parent->version >> 16 >= 2 &&
+       (!out->WriteU8(this->attrMirroring) ||
+        !out->WriteU8(this->attrSkipPasses) ||
+        !out->WriteU8(this->numJLevels) ||
+        !SerializeParts(this->jLevels, out))) ||
+      !out->WriteU16(this->numLigComp) ||
+      !out->WriteU8(this->numUserDefn) ||
+      !out->WriteU8(this->maxCompPerLig) ||
+      !out->WriteU8(this->direction) ||
+      !out->WriteU8(this->attCollisions) ||
+      !out->WriteU8(this->reserved4) ||
+      !out->WriteU8(this->reserved5) ||
+      (parent->version >> 16 >= 2 &&
+       (!out->WriteU8(this->reserved6) ||
+        !out->WriteU8(this->numCritFeatures) ||
+        !SerializeParts(this->critFeatures, out) ||
+        !out->WriteU8(this->reserved7))) ||
+      !out->WriteU8(this->numScriptTag) ||
+      !SerializeParts(this->scriptTag, out) ||
+      !out->WriteU16(this->lbGID) ||
+      !SerializeParts(this->oPasses, out) ||
+      !out->WriteU16(this->numPseudo) ||
+      !out->WriteU16(this->searchPseudo) ||
+      !out->WriteU16(this->pseudoSelector) ||
+      !out->WriteU16(this->pseudoShift) ||
+      !SerializeParts(this->pMaps, out) ||
+      !this->classes.SerializePart(out) ||
+      !SerializeParts(this->passes, out)) {
+    return parent->Error("SILSub: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+JustificationLevel::ParsePart(Buffer& table) {
+  if (!table.ReadU8(&this->attrStretch)) {
+    return parent->Error("JustificationLevel: Failed to read attrStretch");
+  }
+  if (!table.ReadU8(&this->attrShrink)) {
+    return parent->Error("JustificationLevel: Failed to read attrShrink");
+  }
+  if (!table.ReadU8(&this->attrStep)) {
+    return parent->Error("JustificationLevel: Failed to read attrStep");
+  }
+  if (!table.ReadU8(&this->attrWeight)) {
+    return parent->Error("JustificationLevel: Failed to read attrWeight");
+  }
+  if (!table.ReadU8(&this->runto)) {
+    return parent->Error("JustificationLevel: Failed to read runto");
+  }
+  if (!table.ReadU8(&this->reserved)) {
+    return parent->Error("JustificationLevel: Failed to read reserved");
+  }
+  if (this->reserved != 0) {
+    parent->Warning("JustificationLevel: Nonzero reserved");
+  }
+  if (!table.ReadU8(&this->reserved2)) {
+    return parent->Error("JustificationLevel: Failed to read reserved2");
+  }
+  if (this->reserved2 != 0) {
+    parent->Warning("JustificationLevel: Nonzero reserved2");
+  }
+  if (!table.ReadU8(&this->reserved3)) {
+    return parent->Error("JustificationLevel: Failed to read reserved3");
+  }
+  if (this->reserved3 != 0) {
+    parent->Warning("JustificationLevel: Nonzero reserved3");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+JustificationLevel::SerializePart(OTSStream* out) const {
+  if (!out->WriteU8(this->attrStretch) ||
+      !out->WriteU8(this->attrShrink) ||
+      !out->WriteU8(this->attrStep) ||
+      !out->WriteU8(this->attrWeight) ||
+      !out->WriteU8(this->runto) ||
+      !out->WriteU8(this->reserved) ||
+      !out->WriteU8(this->reserved2) ||
+      !out->WriteU8(this->reserved3)) {
+    return parent->Error("JustificationLevel: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+PseudoMap::ParsePart(Buffer& table) {
+  if (parent->version >> 16 >= 2 && !table.ReadU32(&this->unicode)) {
+    return parent->Error("PseudoMap: Failed to read unicode");
+  }
+  if (parent->version >> 16 == 1) {
+    uint16_t unicode;
+    if (!table.ReadU16(&unicode)) {
+      return parent->Error("PseudoMap: Failed to read unicode");
+    }
+    this->unicode = unicode;
+  }
+  if (!table.ReadU16(&this->nPseudo)) {
+    return parent->Error("PseudoMap: Failed to read nPseudo");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+PseudoMap::SerializePart(OTSStream* out) const {
+  if ((parent->version >> 16 >= 2 && !out->WriteU32(this->unicode)) ||
+      (parent->version >> 16 == 1 &&
+       !out->WriteU16(static_cast<uint16_t>(this->unicode))) ||
+      !out->WriteU16(this->nPseudo)) {
+    return parent->Error("PseudoMap: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+ClassMap::ParsePart(Buffer& table) {
+  size_t init_offset = table.offset();
+  if (!table.ReadU16(&this->numClass)) {
+    return parent->Error("ClassMap: Failed to read numClass");
+  }
+  if (!table.ReadU16(&this->numLinear) || this->numLinear > this->numClass) {
+    return parent->Error("ClassMap: Failed to read valid numLinear");
+  }
+
+  //this->oClass.resize(static_cast<unsigned long>(this->numClass) + 1);
+  if (parent->version >> 16 >= 4) {
+    unsigned long last_oClass = 0;
+    for (unsigned long i = 0; i <= this->numClass; ++i) {
+      this->oClass.emplace_back();
+      if (!table.ReadU32(&this->oClass[i]) || this->oClass[i] < last_oClass) {
+        return parent->Error("ClassMap: Failed to read oClass[%lu]", i);
+      }
+      last_oClass = this->oClass[i];
+    }
+  }
+  if (parent->version >> 16 < 4) {
+    unsigned last_oClass = 0;
+    for (unsigned long i = 0; i <= this->numClass; ++i) {
+      uint16_t offset;
+      if (!table.ReadU16(&offset) || offset < last_oClass) {
+        return parent->Error("ClassMap: Failed to read oClass[%lu]", i);
+      }
+      last_oClass = offset;
+      this->oClass.push_back(static_cast<uint32_t>(offset));
+    }
+  }
+
+  if (table.offset() - init_offset > this->oClass[this->numLinear]) {
+    return parent->Error("ClassMap: Failed to calculate length of glyphs");
+  }
+  unsigned long glyphs_len = (this->oClass[this->numLinear] -
+                             (table.offset() - init_offset))/2;
+  //this->glyphs.resize(glyphs_len);
+  for (unsigned long i = 0; i < glyphs_len; ++i) {
+    this->glyphs.emplace_back();
+    if (!table.ReadU16(&this->glyphs[i])) {
+      return parent->Error("ClassMap: Failed to read glyphs[%lu]", i);
+    }
+  }
+
+  unsigned lookups_len = this->numClass - this->numLinear;
+    // this->numLinear <= this->numClass
+  //this->lookups.resize(lookups_len, parent);
+  for (unsigned i = 0; i < lookups_len; ++i) {
+    this->lookups.emplace_back(parent);
+    if (table.offset() != init_offset + oClass[this->numLinear + i]) {
+      return parent->Error("ClassMap: Offset check failed for lookups[%u]", i);
+    }
+    if (!this->lookups[i].ParsePart(table)) {
+      return parent->Error("ClassMap: Failed to read lookups[%u]", i);
+    }
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+ClassMap::SerializePart(OTSStream* out) const {
+  if (!out->WriteU16(this->numClass) ||
+      !out->WriteU16(this->numLinear) ||
+      (parent->version >> 16 >= 4 && !SerializeParts(this->oClass, out)) ||
+      (parent->version >> 16 < 4 &&
+       ![&] {
+         for (uint32_t offset : this->oClass) {
+           if (!out->WriteU16(static_cast<uint16_t>(offset))) {
+             return false;
+           }
+         }
+         return true;
+       }()) ||
+      !SerializeParts(this->glyphs, out) ||
+      !SerializeParts(this->lookups, out)) {
+    return parent->Error("ClassMap: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::ClassMap::
+LookupClass::ParsePart(Buffer& table) {
+  if (!table.ReadU16(&this->numIDs)) {
+    return parent->Error("LookupClass: Failed to read numIDs");
+  }
+  if (!table.ReadU16(&this->searchRange) ||
+      !table.ReadU16(&this->entrySelector) ||
+      !table.ReadU16(&this->rangeShift)) {
+    return parent->Error("LookupClass: Failed to read searchRange..rangeShift");
+  }
+  if (this->numIDs == 0) {
+    if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) {
+      parent->Warning("LookupClass: Correcting binary-search header for zero-length LookupPair list");
+      this->searchRange = this->entrySelector = this->rangeShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numIDs));
+    if (this->searchRange != (unsigned)std::pow(2, floorLog2) ||
+        this->entrySelector != floorLog2 ||
+        this->rangeShift != this->numIDs - this->searchRange) {
+      parent->Warning("LookupClass: Correcting binary-search header for LookupPair list");
+      this->searchRange = (unsigned)std::pow(2, floorLog2);
+      this->entrySelector = floorLog2;
+      this->rangeShift = this->numIDs - this->searchRange;
+    }
+  }
+
+  //this->lookups.resize(this->numIDs, parent);
+  for (unsigned i = 0; i < numIDs; ++i) {
+    this->lookups.emplace_back(parent);
+    if (!this->lookups[i].ParsePart(table)) {
+      return parent->Error("LookupClass: Failed to read lookups[%u]", i);
+    }
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::ClassMap::
+LookupClass::SerializePart(OTSStream* out) const {
+  if (!out->WriteU16(this->numIDs) ||
+      !out->WriteU16(this->searchRange) ||
+      !out->WriteU16(this->entrySelector) ||
+      !out->WriteU16(this->rangeShift) ||
+      !SerializeParts(this->lookups, out)) {
+    return parent->Error("LookupClass: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::ClassMap::LookupClass::
+LookupPair::ParsePart(Buffer& table) {
+  if (!table.ReadU16(&this->glyphId)) {
+    return parent->Error("LookupPair: Failed to read glyphId");
+  }
+  if (!table.ReadU16(&this->index)) {
+    return parent->Error("LookupPair: Failed to read index");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::ClassMap::LookupClass::
+LookupPair::SerializePart(OTSStream* out) const {
+  if (!out->WriteU16(this->glyphId) ||
+      !out->WriteU16(this->index)) {
+    return parent->Error("LookupPair: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+SILPass::ParsePart(Buffer& table, const size_t SILSub_init_offset,
+                                  const size_t next_pass_offset) {
+  size_t init_offset = table.offset();
+  if (!table.ReadU8(&this->flags)) {
+    return parent->Error("SILPass: Failed to read flags");
+      // checks omitted
+  }
+  if (!table.ReadU8(&this->maxRuleLoop)) {
+    return parent->Error("SILPass: Failed to read valid maxRuleLoop");
+  }
+  if (!table.ReadU8(&this->maxRuleContext)) {
+    return parent->Error("SILPass: Failed to read maxRuleContext");
+  }
+  if (!table.ReadU8(&this->maxBackup)) {
+    return parent->Error("SILPass: Failed to read maxBackup");
+  }
+  if (!table.ReadU16(&this->numRules)) {
+    return parent->Error("SILPass: Failed to read numRules");
+  }
+  if (parent->version >> 16 >= 2) {
+    if (!table.ReadU16(&this->fsmOffset)) {
+      return parent->Error("SILPass: Failed to read fsmOffset");
+    }
+    if (parent->version >> 16 == 2 && this->fsmOffset != 0) {
+      parent->Warning("SILPass: Nonzero fsmOffset (reserved in SILSub v2)");
+    }
+    if (!table.ReadU32(&this->pcCode) ||
+        (parent->version >= 3 && this->pcCode < this->fsmOffset)) {
+      return parent->Error("SILPass: Failed to read pcCode");
+    }
+  }
+  if (!table.ReadU32(&this->rcCode) ||
+      (parent->version >> 16 >= 2 && this->rcCode < this->pcCode)) {
+    return parent->Error("SILPass: Failed to read valid rcCode");
+  }
+  if (!table.ReadU32(&this->aCode) || this->aCode < this->rcCode) {
+    return parent->Error("SILPass: Failed to read valid aCode");
+  }
+  if (!table.ReadU32(&this->oDebug) ||
+      (this->oDebug && this->oDebug < this->aCode)) {
+    return parent->Error("SILPass: Failed to read valid oDebug");
+  }
+  if (parent->version >> 16 >= 3 &&
+      table.offset() != init_offset + this->fsmOffset) {
+    return parent->Error("SILPass: fsmOffset check failed");
+  }
+  if (!table.ReadU16(&this->numRows) ||
+      (this->oDebug && this->numRows < this->numRules)) {
+    return parent->Error("SILPass: Failed to read valid numRows");
+  }
+  if (!table.ReadU16(&this->numTransitional)) {
+    return parent->Error("SILPass: Failed to read numTransitional");
+  }
+  if (!table.ReadU16(&this->numSuccess)) {
+    return parent->Error("SILPass: Failed to read numSuccess");
+  }
+  if (!table.ReadU16(&this->numColumns)) {
+    return parent->Error("SILPass: Failed to read numColumns");
+  }
+  if (!table.ReadU16(&this->numRange)) {
+    return parent->Error("SILPass: Failed to read numRange");
+  }
+
+  // The following three fields are deprecated and ignored. We fix them up here
+  // just for internal consistency, but the Graphite engine doesn't care.
+  if (!table.ReadU16(&this->searchRange) ||
+      !table.ReadU16(&this->entrySelector) ||
+      !table.ReadU16(&this->rangeShift)) {
+    return parent->Error("SILPass: Failed to read searchRange..rangeShift");
+  }
+  if (this->numRange == 0) {
+    if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) {
+      this->searchRange = this->entrySelector = this->rangeShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numRange));
+    if (this->searchRange != 6 * (unsigned)std::pow(2, floorLog2) ||
+        this->entrySelector != floorLog2 ||
+        this->rangeShift != 6 * this->numRange - this->searchRange) {
+      this->searchRange = 6 * (unsigned)std::pow(2, floorLog2);
+      this->entrySelector = floorLog2;
+      this->rangeShift = 6 * this->numRange - this->searchRange;
+    }
+  }
+
+  //this->ranges.resize(this->numRange, parent);
+  for (unsigned i = 0 ; i < this->numRange; ++i) {
+    this->ranges.emplace_back(parent);
+    if (!this->ranges[i].ParsePart(table)) {
+      return parent->Error("SILPass: Failed to read ranges[%u]", i);
+    }
+  }
+  unsigned ruleMap_len = 0;  // maximum value in oRuleMap
+  //this->oRuleMap.resize(static_cast<unsigned long>(this->numSuccess) + 1);
+  for (unsigned long i = 0; i <= this->numSuccess; ++i) {
+    this->oRuleMap.emplace_back();
+    if (!table.ReadU16(&this->oRuleMap[i])) {
+      return parent->Error("SILPass: Failed to read oRuleMap[%u]", i);
+    }
+    if (oRuleMap[i] > ruleMap_len) {
+      ruleMap_len = oRuleMap[i];
+    }
+  }
+
+  //this->ruleMap.resize(ruleMap_len);
+  for (unsigned i = 0; i < ruleMap_len; ++i) {
+    this->ruleMap.emplace_back();
+    if (!table.ReadU16(&this->ruleMap[i])) {
+      return parent->Error("SILPass: Failed to read ruleMap[%u]", i);
+    }
+  }
+
+  if (!table.ReadU8(&this->minRulePreContext)) {
+    return parent->Error("SILPass: Failed to read minRulePreContext");
+  }
+  if (!table.ReadU8(&this->maxRulePreContext) ||
+      this->maxRulePreContext < this->minRulePreContext) {
+    return parent->Error("SILPass: Failed to read valid maxRulePreContext");
+  }
+
+  unsigned startStates_len = this->maxRulePreContext - this->minRulePreContext
+                             + 1;
+    // this->minRulePreContext <= this->maxRulePreContext
+  //this->startStates.resize(startStates_len);
+  for (unsigned i = 0; i < startStates_len; ++i) {
+    this->startStates.emplace_back();
+    if (!table.ReadS16(&this->startStates[i])) {
+      return parent->Error("SILPass: Failed to read startStates[%u]", i);
+    }
+  }
+
+  //this->ruleSortKeys.resize(this->numRules);
+  for (unsigned i = 0; i < this->numRules; ++i) {
+    this->ruleSortKeys.emplace_back();
+    if (!table.ReadU16(&this->ruleSortKeys[i])) {
+      return parent->Error("SILPass: Failed to read ruleSortKeys[%u]", i);
+    }
+  }
+
+  //this->rulePreContext.resize(this->numRules);
+  for (unsigned i = 0; i < this->numRules; ++i) {
+    this->rulePreContext.emplace_back();
+    if (!table.ReadU8(&this->rulePreContext[i])) {
+      return parent->Error("SILPass: Failed to read rulePreContext[%u]", i);
+    }
+  }
+
+  if (parent->version >> 16 >= 2) {
+    if (!table.ReadU8(&this->collisionThreshold)) {
+      return parent->Error("SILPass: Failed to read collisionThreshold");
+    }
+    if (parent->version >> 16 < 5 && this->collisionThreshold != 0) {
+      parent->Warning("SILPass: Nonzero collisionThreshold"
+                      " (reserved before v5)");
+    }
+    if (!table.ReadU16(&this->pConstraint)) {
+      return parent->Error("SILPass: Failed to read pConstraint");
+    }
+  }
+
+  unsigned long ruleConstraints_len = this->aCode - this->rcCode;
+    // this->rcCode <= this->aCode
+  //this->oConstraints.resize(static_cast<unsigned long>(this->numRules) + 1);
+  for (unsigned long i = 0; i <= this->numRules; ++i) {
+    this->oConstraints.emplace_back();
+    if (!table.ReadU16(&this->oConstraints[i]) ||
+        this->oConstraints[i] > ruleConstraints_len) {
+      return parent->Error("SILPass: Failed to read valid oConstraints[%lu]",
+                           i);
+    }
+  }
+
+  if (!this->oDebug && this->aCode > next_pass_offset) {
+    return parent->Error("SILPass: Failed to calculate length of actions");
+  }
+  unsigned long actions_len = this->oDebug ? this->oDebug - this->aCode :
+                                             next_pass_offset - this->aCode;
+    // if this->oDebug, then this->aCode <= this->oDebug
+  //this->oActions.resize(static_cast<unsigned long>(this->numRules) + 1);
+  for (unsigned long i = 0; i <= this->numRules; ++i) {
+    this->oActions.emplace_back();
+    if (!table.ReadU16(&this->oActions[i]) ||
+        (this->oActions[i] > actions_len)) {
+      return parent->Error("SILPass: Failed to read valid oActions[%lu]", i);
+    }
+  }
+
+  //this->stateTrans.resize(this->numTransitional);
+  for (unsigned i = 0; i < this->numTransitional; ++i) {
+    this->stateTrans.emplace_back();
+    //this->stateTrans[i].resize(this->numColumns);
+    for (unsigned j = 0; j < this->numColumns; ++j) {
+      this->stateTrans[i].emplace_back();
+      if (!table.ReadU16(&stateTrans[i][j])) {
+        return parent->Error("SILPass: Failed to read stateTrans[%u][%u]",
+                             i, j);
+      }
+    }
+  }
+
+  if (parent->version >> 16 >= 2) {
+    if (!table.ReadU8(&this->reserved2)) {
+      return parent->Error("SILPass: Failed to read reserved2");
+    }
+    if (this->reserved2 != 0) {
+      parent->Warning("SILPass: Nonzero reserved2");
+    }
+
+    if (table.offset() != SILSub_init_offset + this->pcCode) {
+      return parent->Error("SILPass: pcCode check failed");
+    }
+    //this->passConstraints.resize(this->pConstraint);
+    for (unsigned i = 0; i < this->pConstraint; ++i) {
+      this->passConstraints.emplace_back();
+      if (!table.ReadU8(&this->passConstraints[i])) {
+        return parent->Error("SILPass: Failed to read passConstraints[%u]", i);
+      }
+    }
+  }
+
+  if (table.offset() != SILSub_init_offset + this->rcCode) {
+    return parent->Error("SILPass: rcCode check failed");
+  }
+  //this->ruleConstraints.resize(ruleConstraints_len);  // calculated above
+  for (unsigned long i = 0; i < ruleConstraints_len; ++i) {
+    this->ruleConstraints.emplace_back();
+    if (!table.ReadU8(&this->ruleConstraints[i])) {
+      return parent->Error("SILPass: Failed to read ruleConstraints[%u]", i);
+    }
+  }
+
+  if (table.offset() != SILSub_init_offset + this->aCode) {
+    return parent->Error("SILPass: aCode check failed");
+  }
+  //this->actions.resize(actions_len);  // calculated above
+  for (unsigned long i = 0; i < actions_len; ++i) {
+    this->actions.emplace_back();
+    if (!table.ReadU8(&this->actions[i])) {
+      return parent->Error("SILPass: Failed to read actions[%u]", i);
+    }
+  }
+
+  if (this->oDebug) {
+    OpenTypeNAME* name = static_cast<OpenTypeNAME*>(
+        parent->GetFont()->GetTypedTable(OTS_TAG_NAME));
+    if (!name) {
+      return parent->Error("SILPass: Required name table is missing");
+    }
+
+    if (table.offset() != SILSub_init_offset + this->oDebug) {
+      return parent->Error("SILPass: oDebug check failed");
+    }
+    //this->dActions.resize(this->numRules);
+    for (unsigned i = 0; i < this->numRules; ++i) {
+      this->dActions.emplace_back();
+      if (!table.ReadU16(&this->dActions[i]) ||
+          !name->IsValidNameId(this->dActions[i])) {
+        return parent->Error("SILPass: Failed to read valid dActions[%u]", i);
+      }
+    }
+
+    unsigned dStates_len = this->numRows - this->numRules;
+      // this->numRules <= this->numRows
+    //this->dStates.resize(dStates_len);
+    for (unsigned i = 0; i < dStates_len; ++i) {
+      this->dStates.emplace_back();
+      if (!table.ReadU16(&this->dStates[i]) ||
+          !name->IsValidNameId(this->dStates[i])) {
+        return parent->Error("SILPass: Failed to read valid dStates[%u]", i);
+      }
+    }
+
+    //this->dCols.resize(this->numRules);
+    for (unsigned i = 0; i < this->numRules; ++i) {
+      this->dCols.emplace_back();
+      if (!table.ReadU16(&this->dCols[i]) ||
+          !name->IsValidNameId(this->dCols[i])) {
+        return parent->Error("SILPass: Failed to read valid dCols[%u]");
+      }
+    }
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::
+SILPass::SerializePart(OTSStream* out) const {
+  if (!out->WriteU8(this->flags) ||
+      !out->WriteU8(this->maxRuleLoop) ||
+      !out->WriteU8(this->maxRuleContext) ||
+      !out->WriteU8(this->maxBackup) ||
+      !out->WriteU16(this->numRules) ||
+      (parent->version >> 16 >= 2 &&
+       (!out->WriteU16(this->fsmOffset) ||
+        !out->WriteU32(this->pcCode))) ||
+      !out->WriteU32(this->rcCode) ||
+      !out->WriteU32(this->aCode) ||
+      !out->WriteU32(this->oDebug) ||
+      !out->WriteU16(this->numRows) ||
+      !out->WriteU16(this->numTransitional) ||
+      !out->WriteU16(this->numSuccess) ||
+      !out->WriteU16(this->numColumns) ||
+      !out->WriteU16(this->numRange) ||
+      !out->WriteU16(this->searchRange) ||
+      !out->WriteU16(this->entrySelector) ||
+      !out->WriteU16(this->rangeShift) ||
+      !SerializeParts(this->ranges, out) ||
+      !SerializeParts(this->oRuleMap, out) ||
+      !SerializeParts(this->ruleMap, out) ||
+      !out->WriteU8(this->minRulePreContext) ||
+      !out->WriteU8(this->maxRulePreContext) ||
+      !SerializeParts(this->startStates, out) ||
+      !SerializeParts(this->ruleSortKeys, out) ||
+      !SerializeParts(this->rulePreContext, out) ||
+      (parent->version >> 16 >= 2 &&
+       (!out->WriteU8(this->collisionThreshold) ||
+        !out->WriteU16(this->pConstraint))) ||
+      !SerializeParts(this->oConstraints, out) ||
+      !SerializeParts(this->oActions, out) ||
+      !SerializeParts(this->stateTrans, out) ||
+      (parent->version >> 16 >= 2 &&
+       (!out->WriteU8(this->reserved2) ||
+        !SerializeParts(this->passConstraints, out))) ||
+      !SerializeParts(this->ruleConstraints, out) ||
+      !SerializeParts(this->actions, out) ||
+      !SerializeParts(this->dActions, out) ||
+      !SerializeParts(this->dStates, out) ||
+      !SerializeParts(this->dCols, out)) {
+    return parent->Error("SILPass: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::SILPass::
+PassRange::ParsePart(Buffer& table) {
+  if (!table.ReadU16(&this->firstId)) {
+    return parent->Error("PassRange: Failed to read firstId");
+  }
+  if (!table.ReadU16(&this->lastId)) {
+    return parent->Error("PassRange: Failed to read lastId");
+  }
+  if (!table.ReadU16(&this->colId)) {
+    return parent->Error("PassRange: Failed to read colId");
+  }
+  return true;
+}
+
+bool OpenTypeSILF::SILSub::SILPass::
+PassRange::SerializePart(OTSStream* out) const {
+  if (!out->WriteU16(this->firstId) ||
+      !out->WriteU16(this->lastId) ||
+      !out->WriteU16(this->colId)) {
+    return parent->Error("PassRange: Failed to write");
+  }
+  return true;
+}
+
+}  // namespace ots
diff --git a/gfx/ots/src/silf.h b/gfx/ots/src/silf.h
new file mode 100644
index 000000000..b6c08032f
--- /dev/null
+++ b/gfx/ots/src/silf.h
@@ -0,0 +1,196 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef OTS_SILF_H_
+#define OTS_SILF_H_
+
+#include <vector>
+
+#include "ots.h"
+#include "graphite.h"
+
+namespace ots {
+
+class OpenTypeSILF : public Table {
+ public:
+  explicit OpenTypeSILF(Font* font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length) {
+    return this->Parse(data, length, false);
+  }
+  bool Serialize(OTSStream* out);
+
+ private:
+  bool Parse(const uint8_t* data, size_t length, bool prevent_decompression);
+  struct SILSub : public TablePart<OpenTypeSILF> {
+    explicit SILSub(OpenTypeSILF* parent)
+        : TablePart<OpenTypeSILF>(parent), classes(parent) { }
+    bool ParsePart(Buffer& table);
+    bool SerializePart(OTSStream* out) const;
+    struct JustificationLevel : public TablePart<OpenTypeSILF> {
+      explicit JustificationLevel(OpenTypeSILF* parent)
+          : TablePart<OpenTypeSILF>(parent) { }
+      bool ParsePart(Buffer& table);
+      bool SerializePart(OTSStream* out) const;
+      uint8_t attrStretch;
+      uint8_t attrShrink;
+      uint8_t attrStep;
+      uint8_t attrWeight;
+      uint8_t runto;
+      uint8_t reserved;
+      uint8_t reserved2;
+      uint8_t reserved3;
+    };
+    struct PseudoMap : public TablePart<OpenTypeSILF> {
+      explicit PseudoMap(OpenTypeSILF* parent)
+          : TablePart<OpenTypeSILF>(parent) { }
+      bool ParsePart(Buffer& table);
+      bool SerializePart(OTSStream* out) const;
+      uint32_t unicode;
+      uint16_t nPseudo;
+    };
+    struct ClassMap : public TablePart<OpenTypeSILF> {
+      explicit ClassMap(OpenTypeSILF* parent)
+          : TablePart<OpenTypeSILF>(parent) { }
+      bool ParsePart(Buffer& table);
+      bool SerializePart(OTSStream* out) const;
+      struct LookupClass : public TablePart<OpenTypeSILF> {
+        explicit LookupClass(OpenTypeSILF* parent)
+            : TablePart<OpenTypeSILF>(parent) { }
+        bool ParsePart(Buffer& table);
+        bool SerializePart(OTSStream* out) const;
+        struct LookupPair : public TablePart<OpenTypeSILF> {
+          explicit LookupPair(OpenTypeSILF* parent)
+              : TablePart<OpenTypeSILF>(parent) { }
+          bool ParsePart(Buffer& table);
+          bool SerializePart(OTSStream* out) const;
+          uint16_t glyphId;
+          uint16_t index;
+        };
+        uint16_t numIDs;
+        uint16_t searchRange;
+        uint16_t entrySelector;
+        uint16_t rangeShift;
+        std::vector<LookupPair> lookups;
+      };
+      uint16_t numClass;
+      uint16_t numLinear;
+      std::vector<uint32_t> oClass;  // uint16_t before v4
+      std::vector<uint16_t> glyphs;
+      std::vector<LookupClass> lookups;
+    };
+    struct SILPass : public TablePart<OpenTypeSILF> {
+      explicit SILPass(OpenTypeSILF* parent)
+          : TablePart<OpenTypeSILF>(parent) { }
+      bool ParsePart(Buffer& table) { return false; }
+      bool ParsePart(Buffer& table, const size_t SILSub_init_offset,
+                                    const size_t next_pass_offset);
+      bool SerializePart(OTSStream* out) const;
+      struct PassRange : public TablePart<OpenTypeSILF> {
+        explicit PassRange(OpenTypeSILF* parent)
+            : TablePart<OpenTypeSILF>(parent) { }
+        bool ParsePart(Buffer& table);
+        bool SerializePart(OTSStream* out) const;
+        uint16_t firstId;
+        uint16_t lastId;
+        uint16_t colId;
+      };
+      uint8_t flags;
+      uint8_t maxRuleLoop;
+      uint8_t maxRuleContext;
+      uint8_t maxBackup;
+      uint16_t numRules;
+      uint16_t fsmOffset;
+      uint32_t pcCode;
+      uint32_t rcCode;
+      uint32_t aCode;
+      uint32_t oDebug;
+      uint16_t numRows;
+      uint16_t numTransitional;
+      uint16_t numSuccess;
+      uint16_t numColumns;
+      uint16_t numRange;
+      uint16_t searchRange;
+      uint16_t entrySelector;
+      uint16_t rangeShift;
+      std::vector<PassRange> ranges;
+      std::vector<uint16_t> oRuleMap;
+      std::vector<uint16_t> ruleMap;
+      uint8_t minRulePreContext;
+      uint8_t maxRulePreContext;
+      std::vector<int16_t> startStates;
+      std::vector<uint16_t> ruleSortKeys;
+      std::vector<uint8_t> rulePreContext;
+      uint8_t collisionThreshold;  // reserved before v5
+      uint16_t pConstraint;
+      std::vector<uint16_t> oConstraints;
+      std::vector<uint16_t> oActions;
+      std::vector<std::vector<uint16_t>> stateTrans;
+      uint8_t reserved2;
+      std::vector<uint8_t> passConstraints;
+      std::vector<uint8_t> ruleConstraints;
+      std::vector<uint8_t> actions;
+      std::vector<uint16_t> dActions;
+      std::vector<uint16_t> dStates;
+      std::vector<uint16_t> dCols;
+    };
+    uint32_t ruleVersion;
+    uint16_t passOffset;
+    uint16_t pseudosOffset;
+    uint16_t maxGlyphID;
+    int16_t extraAscent;
+    int16_t extraDescent;
+    uint8_t numPasses;
+    uint8_t iSubst;
+    uint8_t iPos;
+    uint8_t iJust;
+    uint8_t iBidi;
+    uint8_t flags;
+    uint8_t maxPreContext;
+    uint8_t maxPostContext;
+    uint8_t attrPseudo;
+    uint8_t attrBreakWeight;
+    uint8_t attrDirectionality;
+    uint8_t attrMirroring;  // reserved before v4
+    uint8_t attrSkipPasses;  // reserved2 before v4
+    uint8_t numJLevels;
+    std::vector<JustificationLevel> jLevels;
+    uint16_t numLigComp;
+    uint8_t numUserDefn;
+    uint8_t maxCompPerLig;
+    uint8_t direction;
+    uint8_t attCollisions;  // reserved3 before v5
+    uint8_t reserved4;
+    uint8_t reserved5;
+    uint8_t reserved6;
+    uint8_t numCritFeatures;
+    std::vector<uint16_t> critFeatures;
+    uint8_t reserved7;
+    uint8_t numScriptTag;
+    std::vector<uint32_t> scriptTag;
+    uint16_t lbGID;
+    std::vector<uint32_t> oPasses;
+    uint16_t numPseudo;
+    uint16_t searchPseudo;
+    uint16_t pseudoSelector;
+    uint16_t pseudoShift;
+    std::vector<PseudoMap> pMaps;
+    ClassMap classes;
+    std::vector<SILPass> passes;
+  };
+  uint32_t version;
+  uint32_t compHead;  // compression header
+  static const uint32_t SCHEME = 0xF8000000;
+  static const uint32_t FULL_SIZE = 0x07FFFFFF;
+  static const uint32_t COMPILER_VERSION = 0x07FFFFFF;
+  uint16_t numSub;
+  uint16_t reserved;
+  std::vector<uint32_t> offset;
+  std::vector<SILSub> tables;
+};
+
+}  // namespace ots
+
+#endif  // OTS_SILF_H_
diff --git a/gfx/ots/src/sill.cc b/gfx/ots/src/sill.cc
new file mode 100644
index 000000000..c7b20a980
--- /dev/null
+++ b/gfx/ots/src/sill.cc
@@ -0,0 +1,159 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sill.h"
+
+#include "feat.h"
+#include <cmath>
+#include <unordered_set>
+
+namespace ots {
+
+bool OpenTypeSILL::Parse(const uint8_t* data, size_t length) {
+  if (GetFont()->dropped_graphite) {
+    return Drop("Skipping Graphite table");
+  }
+  Buffer table(data, length);
+
+  if (!table.ReadU32(&this->version) || this->version >> 16 != 1) {
+    return Drop("Failed to read valid version");
+  }
+  if (!table.ReadU16(&this->numLangs)) {
+    return Drop("Failed to read numLangs");
+  }
+
+  // The following three fields are deprecated and ignored. We fix them up here
+  // just for internal consistency, but the Graphite engine doesn't care.
+  if (!table.ReadU16(&this->searchRange) ||
+      !table.ReadU16(&this->entrySelector) ||
+      !table.ReadU16(&this->rangeShift)) {
+    return Drop("Failed to read searchRange..rangeShift");
+  }
+  if (this->numLangs == 0) {
+    if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) {
+      this->searchRange = this->entrySelector = this->rangeShift = 0;
+    }
+  } else {
+    unsigned floorLog2 = std::floor(std::log2(this->numLangs));
+    if (this->searchRange != (unsigned)std::pow(2, floorLog2) ||
+        this->entrySelector != floorLog2 ||
+        this->rangeShift != this->numLangs - this->searchRange) {
+      this->searchRange = (unsigned)std::pow(2, floorLog2);
+      this->entrySelector = floorLog2;
+      this->rangeShift = this->numLangs - this->searchRange;
+    }
+  }
+
+  std::unordered_set<size_t> unverified;
+  //this->entries.resize(static_cast<unsigned long>(this->numLangs) + 1, this);
+  for (unsigned long i = 0; i <= this->numLangs; ++i) {
+    this->entries.emplace_back(this);
+    LanguageEntry& entry = this->entries[i];
+    if (!entry.ParsePart(table)) {
+      return Drop("Failed to read entries[%u]", i);
+    }
+    for (unsigned j = 0; j < entry.numSettings; ++j) {
+      size_t offset = entry.offset + j * 8;
+      if (offset < entry.offset || offset > length) {
+        return DropGraphite("Invalid LangFeatureSetting offset %zu/%zu",
+                            offset, length);
+      }
+      unverified.insert(offset);
+        // need to verify that this LanguageEntry points to valid
+        // LangFeatureSetting
+    }
+  }
+
+  while (table.remaining()) {
+    unverified.erase(table.offset());
+    LangFeatureSetting setting(this);
+    if (!setting.ParsePart(table)) {
+      return Drop("Failed to read a LangFeatureSetting");
+    }
+    settings.push_back(setting);
+  }
+
+  if (!unverified.empty()) {
+    return Drop("%zu incorrect offsets into settings", unverified.size());
+  }
+  if (table.remaining()) {
+    return Warning("%zu bytes unparsed", table.remaining());
+  }
+  return true;
+}
+
+bool OpenTypeSILL::Serialize(OTSStream* out) {
+  if (!out->WriteU32(this->version) ||
+      !out->WriteU16(this->numLangs) ||
+      !out->WriteU16(this->searchRange) ||
+      !out->WriteU16(this->entrySelector) ||
+      !out->WriteU16(this->rangeShift) ||
+      !SerializeParts(this->entries, out) ||
+      !SerializeParts(this->settings, out)) {
+    return Error("Failed to write table");
+  }
+  return true;
+}
+
+bool OpenTypeSILL::LanguageEntry::ParsePart(Buffer& table) {
+  if (!table.ReadU8(&this->langcode[0]) ||
+      !table.ReadU8(&this->langcode[1]) ||
+      !table.ReadU8(&this->langcode[2]) ||
+      !table.ReadU8(&this->langcode[3])) {
+    return parent->Error("LanguageEntry: Failed to read langcode");
+  }
+  if (!table.ReadU16(&this->numSettings)) {
+    return parent->Error("LanguageEntry: Failed to read numSettings");
+  }
+  if (!table.ReadU16(&this->offset)) {
+    return parent->Error("LanguageEntry: Failed to read offset");
+  }
+  return true;
+}
+
+bool OpenTypeSILL::LanguageEntry::SerializePart(OTSStream* out) const {
+  if (!out->WriteU8(this->langcode[0]) ||
+      !out->WriteU8(this->langcode[1]) ||
+      !out->WriteU8(this->langcode[2]) ||
+      !out->WriteU8(this->langcode[3]) ||
+      !out->WriteU16(this->numSettings) ||
+      !out->WriteU16(this->offset)) {
+    return parent->Error("LanguageEntry: Failed to write");
+  }
+  return true;
+}
+
+bool OpenTypeSILL::LangFeatureSetting::ParsePart(Buffer& table) {
+  OpenTypeFEAT* feat = static_cast<OpenTypeFEAT*>(
+      parent->GetFont()->GetTypedTable(OTS_TAG_FEAT));
+  if (!feat) {
+    return parent->Error("FeatureDefn: Required Feat table is missing");
+  }
+
+  if (!table.ReadU32(&this->featureId) ||
+      !feat->IsValidFeatureId(this->featureId)) {
+    return parent->Error("LangFeatureSetting: Failed to read valid featureId");
+  }
+  if (!table.ReadS16(&this->value)) {
+    return parent->Error("LangFeatureSetting: Failed to read value");
+  }
+  if (!table.ReadU16(&this->reserved)) {
+    return parent->Error("LangFeatureSetting: Failed to read reserved");
+  }
+  if (this->reserved != 0) {
+    parent->Warning("LangFeatureSetting: Nonzero reserved");
+  }
+  return true;
+}
+
+bool OpenTypeSILL::LangFeatureSetting::SerializePart(OTSStream* out) const {
+  if (!out->WriteU32(this->featureId) ||
+      !out->WriteS16(this->value) ||
+      !out->WriteU16(this->reserved)) {
+    return parent->Error("LangFeatureSetting: Failed to read reserved");
+  }
+  return true;
+}
+
+}  // namespace ots
diff --git a/gfx/ots/src/sill.h b/gfx/ots/src/sill.h
new file mode 100644
index 000000000..30f9b8d83
--- /dev/null
+++ b/gfx/ots/src/sill.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef OTS_SILL_H_
+#define OTS_SILL_H_
+
+#include "ots.h"
+#include "graphite.h"
+
+#include <vector>
+
+namespace ots {
+
+class OpenTypeSILL : public Table {
+ public:
+  explicit OpenTypeSILL(Font* font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t* data, size_t length);
+  bool Serialize(OTSStream* out);
+
+ private:
+  struct LanguageEntry : public TablePart<OpenTypeSILL> {
+    explicit LanguageEntry(OpenTypeSILL* parent)
+        : TablePart<OpenTypeSILL>(parent) { }
+    bool ParsePart(Buffer &table);
+    bool SerializePart(OTSStream* out) const;
+    uint8_t langcode[4];
+    uint16_t numSettings;
+    uint16_t offset;
+  };
+  struct LangFeatureSetting : public TablePart<OpenTypeSILL> {
+    explicit LangFeatureSetting(OpenTypeSILL* parent)
+        : TablePart<OpenTypeSILL>(parent) { }
+    bool ParsePart(Buffer &table);
+    bool SerializePart(OTSStream* out) const;
+    uint32_t featureId;
+    int16_t value;
+    uint16_t reserved;
+  };
+  uint32_t version;
+  uint16_t numLangs;
+  uint16_t searchRange;
+  uint16_t entrySelector;
+  uint16_t rangeShift;
+  std::vector<LanguageEntry> entries;
+  std::vector<LangFeatureSetting> settings;
+};
+
+}  // namespace ots
+
+#endif  // OTS_SILL_H_
diff --git a/gfx/ots/src/vdmx.cc b/gfx/ots/src/vdmx.cc
index cd80946ae..54055777a 100644
--- a/gfx/ots/src/vdmx.cc
+++ b/gfx/ots/src/vdmx.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,91 +7,75 @@
 // VDMX - Vertical Device Metrics
 // http://www.microsoft.com/typography/otspec/vdmx.htm
 
-#define TABLE_NAME "VDMX"
-
-#define DROP_THIS_TABLE(...) \
-  do { \
-    OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \
-    OTS_FAILURE_MSG("Table discarded"); \
-    delete font->vdmx; \
-    font->vdmx = 0; \
-  } while (0)
-
 namespace ots {
 
-bool ots_vdmx_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeVDMX::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
-  font->vdmx = new OpenTypeVDMX;
-  OpenTypeVDMX * const vdmx = font->vdmx;
 
-  if (!table.ReadU16(&vdmx->version) ||
-      !table.ReadU16(&vdmx->num_recs) ||
-      !table.ReadU16(&vdmx->num_ratios)) {
-    return OTS_FAILURE_MSG("Failed to read table header");
+  if (!table.ReadU16(&this->version) ||
+      !table.ReadU16(&this->num_recs) ||
+      !table.ReadU16(&this->num_ratios)) {
+    return Error("Failed to read table header");
   }
 
-  if (vdmx->version > 1) {
-    DROP_THIS_TABLE("bad version: %u", vdmx->version);
-    return true;  // continue transcoding
+  if (this->version > 1) {
+    return Drop("Unsupported table version: %u", this->version);
   }
 
-  vdmx->rat_ranges.reserve(vdmx->num_ratios);
-  for (unsigned i = 0; i < vdmx->num_ratios; ++i) {
+  this->rat_ranges.reserve(this->num_ratios);
+  for (unsigned i = 0; i < this->num_ratios; ++i) {
     OpenTypeVDMXRatioRecord rec;
 
     if (!table.ReadU8(&rec.charset) ||
         !table.ReadU8(&rec.x_ratio) ||
         !table.ReadU8(&rec.y_start_ratio) ||
         !table.ReadU8(&rec.y_end_ratio)) {
-      return OTS_FAILURE_MSG("Failed to read ratio header %d", i);
+      return Error("Failed to read RatioRange record %d", i);
     }
 
     if (rec.charset > 1) {
-      DROP_THIS_TABLE("bad charset: %u", rec.charset);
-      return true;
+      return Drop("Unsupported character set: %u", rec.charset);
     }
 
     if (rec.y_start_ratio > rec.y_end_ratio) {
-      DROP_THIS_TABLE("bad y ratio");
-      return true;
+      return Drop("Bad y ratio");
     }
 
     // All values set to zero signal the default grouping to use;
     // if present, this must be the last Ratio group in the table.
-    if ((i < vdmx->num_ratios - 1u) &&
+    if ((i < this->num_ratios - 1u) &&
         (rec.x_ratio == 0) &&
         (rec.y_start_ratio == 0) &&
         (rec.y_end_ratio == 0)) {
       // workaround for fonts which have 2 or more {0, 0, 0} terminators.
-      DROP_THIS_TABLE("superfluous terminator found");
-      return true;
+      return Drop("Superfluous terminator found");
     }
 
-    vdmx->rat_ranges.push_back(rec);
+    this->rat_ranges.push_back(rec);
   }
 
-  vdmx->offsets.reserve(vdmx->num_ratios);
+  this->offsets.reserve(this->num_ratios);
   const size_t current_offset = table.offset();
   // current_offset is less than (2 bytes * 3) + (4 bytes * USHRT_MAX) = 256k.
-  for (unsigned i = 0; i < vdmx->num_ratios; ++i) {
+  for (unsigned i = 0; i < this->num_ratios; ++i) {
     uint16_t offset;
     if (!table.ReadU16(&offset)) {
-      return OTS_FAILURE_MSG("Failed to read ratio offset %d", i);
+      return Error("Failed to read ratio offset %d", i);
     }
     if (current_offset + offset >= length) {  // thus doesn't overflow.
-      return OTS_FAILURE_MSG("Bad ratio offset %d for ration %d", offset, i);
+      return Error("Bad ratio offset %d for ration %d", offset, i);
     }
 
-    vdmx->offsets.push_back(offset);
+    this->offsets.push_back(offset);
   }
 
-  vdmx->groups.reserve(vdmx->num_recs);
-  for (unsigned i = 0; i < vdmx->num_recs; ++i) {
+  this->groups.reserve(this->num_recs);
+  for (unsigned i = 0; i < this->num_recs; ++i) {
     OpenTypeVDMXGroup group;
     if (!table.ReadU16(&group.recs) ||
         !table.ReadU8(&group.startsz) ||
         !table.ReadU8(&group.endsz)) {
-      return OTS_FAILURE_MSG("Failed to read record header %d", i);
+      return Error("Failed to read record header %d", i);
     }
     group.entries.reserve(group.recs);
     for (unsigned j = 0; j < group.recs; ++j) {
@@ -99,71 +83,68 @@ bool ots_vdmx_parse(Font *font, const uint8_t *data, size_t length) {
       if (!table.ReadU16(&vt.y_pel_height) ||
           !table.ReadS16(&vt.y_max) ||
           !table.ReadS16(&vt.y_min)) {
-        return OTS_FAILURE_MSG("Failed to read reacord %d group %d", i, j);
+        return Error("Failed to read reacord %d group %d", i, j);
       }
       if (vt.y_max < vt.y_min) {
-        DROP_THIS_TABLE("bad y min/max");
-        return true;
+        return Drop("bad y min/max");
       }
 
       // This table must appear in sorted order (sorted by yPelHeight),
       // but need not be continuous.
       if ((j != 0) && (group.entries[j - 1].y_pel_height >= vt.y_pel_height)) {
-        DROP_THIS_TABLE("the table is not sorted");
-        return true;
+        return Drop("The table is not sorted");
       }
 
       group.entries.push_back(vt);
     }
-    vdmx->groups.push_back(group);
+    this->groups.push_back(group);
   }
 
   return true;
 }
 
-bool ots_vdmx_should_serialise(Font *font) {
-  if (!font->glyf) return false;  // this table is not for CFF fonts.
-  return font->vdmx != NULL;
+bool OpenTypeVDMX::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for CFF fonts.
+         GetFont()->GetTable(OTS_TAG_GLYF) != NULL;
 }
 
-bool ots_vdmx_serialise(OTSStream *out, Font *font) {
-  OpenTypeVDMX * const vdmx = font->vdmx;
-
-  if (!out->WriteU16(vdmx->version) ||
-      !out->WriteU16(vdmx->num_recs) ||
-      !out->WriteU16(vdmx->num_ratios)) {
-    return OTS_FAILURE_MSG("Failed to write table header");
+bool OpenTypeVDMX::Serialize(OTSStream *out) {
+  if (!out->WriteU16(this->version) ||
+      !out->WriteU16(this->num_recs) ||
+      !out->WriteU16(this->num_ratios)) {
+    return Error("Failed to write table header");
   }
 
-  for (unsigned i = 0; i < vdmx->rat_ranges.size(); ++i) {
-    const OpenTypeVDMXRatioRecord& rec = vdmx->rat_ranges[i];
+  for (unsigned i = 0; i < this->rat_ranges.size(); ++i) {
+    const OpenTypeVDMXRatioRecord& rec = this->rat_ranges[i];
     if (!out->Write(&rec.charset, 1) ||
         !out->Write(&rec.x_ratio, 1) ||
         !out->Write(&rec.y_start_ratio, 1) ||
         !out->Write(&rec.y_end_ratio, 1)) {
-      return OTS_FAILURE_MSG("Failed to write ratio %d", i);
+      return Error("Failed to write RatioRange record %d", i);
     }
   }
 
-  for (unsigned i = 0; i < vdmx->offsets.size(); ++i) {
-    if (!out->WriteU16(vdmx->offsets[i])) {
-      return OTS_FAILURE_MSG("Failed to write ratio offset %d", i);
+  for (unsigned i = 0; i < this->offsets.size(); ++i) {
+    if (!out->WriteU16(this->offsets[i])) {
+      return Error("Failed to write ratio offset %d", i);
     }
   }
 
-  for (unsigned i = 0; i < vdmx->groups.size(); ++i) {
-    const OpenTypeVDMXGroup& group = vdmx->groups[i];
+  for (unsigned i = 0; i < this->groups.size(); ++i) {
+    const OpenTypeVDMXGroup& group = this->groups[i];
     if (!out->WriteU16(group.recs) ||
         !out->Write(&group.startsz, 1) ||
         !out->Write(&group.endsz, 1)) {
-      return OTS_FAILURE_MSG("Failed to write group %d", i);
+      return Error("Failed to write group %d", i);
     }
     for (unsigned j = 0; j < group.entries.size(); ++j) {
       const OpenTypeVDMXVTable& vt = group.entries[j];
       if (!out->WriteU16(vt.y_pel_height) ||
           !out->WriteS16(vt.y_max) ||
           !out->WriteS16(vt.y_min)) {
-        return OTS_FAILURE_MSG("Failed to write group %d entry %d", i, j);
+        return Error("Failed to write group %d entry %d", i, j);
       }
     }
   }
@@ -171,16 +152,4 @@ bool ots_vdmx_serialise(OTSStream *out, Font *font) {
   return true;
 }
 
-void ots_vdmx_reuse(Font *font, Font *other) {
-  font->vdmx = other->vdmx;
-  font->vdmx_reused = true;
-}
-
-void ots_vdmx_free(Font *font) {
-  delete font->vdmx;
-}
-
 }  // namespace ots
-
-#undef TABLE_NAME
-#undef DROP_THIS_TABLE
diff --git a/gfx/ots/src/vdmx.h b/gfx/ots/src/vdmx.h
index 1d959efe5..6ccf6dc1b 100644
--- a/gfx/ots/src/vdmx.h
+++ b/gfx/ots/src/vdmx.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -31,7 +31,16 @@ struct OpenTypeVDMXGroup {
   std::vector<OpenTypeVDMXVTable> entries;
 };
 
-struct OpenTypeVDMX {
+class OpenTypeVDMX : public Table {
+ public:
+  explicit OpenTypeVDMX(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
   uint16_t version;
   uint16_t num_recs;
   uint16_t num_ratios;
diff --git a/gfx/ots/src/vhea.cc b/gfx/ots/src/vhea.cc
index e721b971e..e44bedf20 100644
--- a/gfx/ots/src/vhea.cc
+++ b/gfx/ots/src/vhea.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,51 +10,30 @@
 // vhea - Vertical Header Table
 // http://www.microsoft.com/typography/otspec/vhea.htm
 
-#define TABLE_NAME "vhea"
-
 namespace ots {
 
-bool ots_vhea_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeVHEA::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
-  OpenTypeVHEA *vhea = new OpenTypeVHEA;
-  font->vhea = vhea;
 
-  if (!table.ReadU32(&vhea->header.version)) {
-    return OTS_FAILURE_MSG("Failed to read version");
-  }
-  if (vhea->header.version != 0x00010000 &&
-      vhea->header.version != 0x00011000) {
-    return OTS_FAILURE_MSG("Bad vhea version %x", vhea->header.version);
+  if (!table.ReadU32(&this->version)) {
+    return Error("Failed to read version");
   }
-
-  if (!ParseMetricsHeader(font, &table, &vhea->header)) {
-    return OTS_FAILURE_MSG("Failed to parse metrics in vhea");
+  if (this->version != 0x00010000 &&
+      this->version != 0x00011000) {
+    return Error("Unsupported table version: 0x%x", this->version);
   }
 
-  return true;
+  return OpenTypeMetricsHeader::Parse(data, length);
 }
 
-bool ots_vhea_should_serialise(Font *font) {
-  // vhea should'nt serialise when vmtx doesn't exist.
-  return font->vhea != NULL && font->vmtx != NULL;
+bool OpenTypeVHEA::Serialize(OTSStream *out) {
+  return OpenTypeMetricsHeader::Serialize(out);
 }
 
-bool ots_vhea_serialise(OTSStream *out, Font *font) {
-  if (!SerialiseMetricsHeader(font, out, &font->vhea->header)) {
-    return OTS_FAILURE_MSG("Failed to write vhea metrics");
-  }
-  return true;
-}
-
-void ots_vhea_reuse(Font *font, Font *other) {
-  font->vhea = other->vhea;
-  font->vhea_reused = true;
-}
-
-void ots_vhea_free(Font *font) {
-  delete font->vhea;
+bool OpenTypeVHEA::ShouldSerialize() {
+  return OpenTypeMetricsHeader::ShouldSerialize() &&
+         // vhea shouldn't serialise when vmtx doesn't exist.
+         GetFont()->GetTable(OTS_TAG_VMTX) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/vhea.h b/gfx/ots/src/vhea.h
index f8efde731..8776acd11 100644
--- a/gfx/ots/src/vhea.h
+++ b/gfx/ots/src/vhea.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,8 +10,14 @@
 
 namespace ots {
 
-struct OpenTypeVHEA {
-  OpenTypeMetricsHeader header;
+class OpenTypeVHEA : public OpenTypeMetricsHeader {
+ public:
+  explicit OpenTypeVHEA(Font *font, uint32_t tag)
+      : OpenTypeMetricsHeader(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/vmtx.cc b/gfx/ots/src/vmtx.cc
index 64a706148..d576ae294 100644
--- a/gfx/ots/src/vmtx.cc
+++ b/gfx/ots/src/vmtx.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,48 +10,20 @@
 // vmtx - Vertical Metrics Table
 // http://www.microsoft.com/typography/otspec/vmtx.htm
 
-#define TABLE_NAME "vmtx"
-
 namespace ots {
 
-bool ots_vmtx_parse(Font *font, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-  OpenTypeVMTX *vmtx = new OpenTypeVMTX;
-  font->vmtx = vmtx;
-
-  if (!font->vhea || !font->maxp) {
-    return OTS_FAILURE_MSG("vhea or maxp table missing as needed by vmtx");
-  }
-
-  if (!ParseMetricsTable(font, &table, font->maxp->num_glyphs,
-                         &font->vhea->header, &vmtx->metrics)) {
-    return OTS_FAILURE_MSG("Failed to parse vmtx metrics");
-  }
-
-  return true;
+bool OpenTypeVMTX::Parse(const uint8_t *data, size_t length) {
+  return OpenTypeMetricsTable::Parse(data, length);
 }
 
-bool ots_vmtx_should_serialise(Font *font) {
-  // vmtx should serialise when vhea is preserved.
-  return font->vmtx != NULL && font->vhea != NULL;
+bool OpenTypeVMTX::Serialize(OTSStream *out) {
+  return OpenTypeMetricsTable::Serialize(out);
 }
 
-bool ots_vmtx_serialise(OTSStream *out, Font *font) {
-  if (!SerialiseMetricsTable(font, out, &font->vmtx->metrics)) {
-    return OTS_FAILURE_MSG("Failed to write vmtx metrics");
-  }
-  return true;
-}
-
-void ots_vmtx_reuse(Font *font, Font *other) {
-  font->vmtx = other->vmtx;
-  font->vmtx_reused = true;
-}
-
-void ots_vmtx_free(Font *font) {
-  delete font->vmtx;
+bool OpenTypeVMTX::ShouldSerialize() {
+  return OpenTypeMetricsTable::ShouldSerialize() &&
+         // vmtx should serialise when vhea is preserved.
+         GetFont()->GetTable(OTS_TAG_VHEA) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
diff --git a/gfx/ots/src/vmtx.h b/gfx/ots/src/vmtx.h
index 061dc73ed..b243fe214 100644
--- a/gfx/ots/src/vmtx.h
+++ b/gfx/ots/src/vmtx.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,12 +6,19 @@
 #define OTS_VMTX_H_
 
 #include "metrics.h"
+#include "vhea.h"
 #include "ots.h"
 
 namespace ots {
 
-struct OpenTypeVMTX {
-  OpenTypeMetricsTable metrics;
+struct OpenTypeVMTX : public OpenTypeMetricsTable {
+ public:
+  explicit OpenTypeVMTX(Font *font, uint32_t tag)
+      : OpenTypeMetricsTable(font, tag, tag, OTS_TAG_VHEA) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
 };
 
 }  // namespace ots
diff --git a/gfx/ots/src/vorg.cc b/gfx/ots/src/vorg.cc
index 358923125..3b4b51c53 100644
--- a/gfx/ots/src/vorg.cc
+++ b/gfx/ots/src/vorg.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,37 +9,23 @@
 // VORG - Vertical Origin Table
 // http://www.microsoft.com/typography/otspec/vorg.htm
 
-#define TABLE_NAME "VORG"
-
-#define DROP_THIS_TABLE(...) \
-  do { \
-    OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \
-    OTS_FAILURE_MSG("Table discarded"); \
-    delete font->vorg; \
-    font->vorg = 0; \
-  } while (0)
-
 namespace ots {
 
-bool ots_vorg_parse(Font *font, const uint8_t *data, size_t length) {
+bool OpenTypeVORG::Parse(const uint8_t *data, size_t length) {
   Buffer table(data, length);
-  font->vorg = new OpenTypeVORG;
-  OpenTypeVORG * const vorg = font->vorg;
 
   uint16_t num_recs;
-  if (!table.ReadU16(&vorg->major_version) ||
-      !table.ReadU16(&vorg->minor_version) ||
-      !table.ReadS16(&vorg->default_vert_origin_y) ||
+  if (!table.ReadU16(&this->major_version) ||
+      !table.ReadU16(&this->minor_version) ||
+      !table.ReadS16(&this->default_vert_origin_y) ||
       !table.ReadU16(&num_recs)) {
-    return OTS_FAILURE_MSG("Failed to read header");
+    return Error("Failed to read header");
   }
-  if (vorg->major_version != 1) {
-    DROP_THIS_TABLE("bad major version: %u", vorg->major_version);
-    return true;
+  if (this->major_version != 1) {
+    return Drop("Unsupported majorVersion: %u", this->major_version);
   }
-  if (vorg->minor_version != 0) {
-    DROP_THIS_TABLE("bad minor version: %u", vorg->minor_version);
-    return true;
+  if (this->minor_version != 0) {
+    return Drop("Unsupported minorVersion: %u", this->minor_version);
   }
 
   // num_recs might be zero (e.g., DFHSMinchoPro5-W3-Demo.otf).
@@ -48,64 +34,50 @@ bool ots_vorg_parse(Font *font, const uint8_t *data, size_t length) {
   }
 
   uint16_t last_glyph_index = 0;
-  vorg->metrics.reserve(num_recs);
+  this->metrics.reserve(num_recs);
   for (unsigned i = 0; i < num_recs; ++i) {
     OpenTypeVORGMetrics rec;
 
     if (!table.ReadU16(&rec.glyph_index) ||
         !table.ReadS16(&rec.vert_origin_y)) {
-      return OTS_FAILURE_MSG("Failed to read record %d", i);
+      return Error("Failed to read record %d", i);
     }
     if ((i != 0) && (rec.glyph_index <= last_glyph_index)) {
-      DROP_THIS_TABLE("the table is not sorted");
-      return true;
+      return Drop("The table is not sorted");
     }
     last_glyph_index = rec.glyph_index;
 
-    vorg->metrics.push_back(rec);
+    this->metrics.push_back(rec);
   }
 
   return true;
 }
 
-bool ots_vorg_should_serialise(Font *font) {
-  if (!font->cff) return false;  // this table is not for fonts with TT glyphs.
-  return font->vorg != NULL;
-}
-
-bool ots_vorg_serialise(OTSStream *out, Font *font) {
-  OpenTypeVORG * const vorg = font->vorg;
-  
-  const uint16_t num_metrics = static_cast<uint16_t>(vorg->metrics.size());
-  if (num_metrics != vorg->metrics.size() ||
-      !out->WriteU16(vorg->major_version) ||
-      !out->WriteU16(vorg->minor_version) ||
-      !out->WriteS16(vorg->default_vert_origin_y) ||
+bool OpenTypeVORG::Serialize(OTSStream *out) {
+  const uint16_t num_metrics = static_cast<uint16_t>(this->metrics.size());
+  if (num_metrics != this->metrics.size() ||
+      !out->WriteU16(this->major_version) ||
+      !out->WriteU16(this->minor_version) ||
+      !out->WriteS16(this->default_vert_origin_y) ||
       !out->WriteU16(num_metrics)) {
-    return OTS_FAILURE_MSG("Failed to write table header");
+    return Error("Failed to write table header");
   }
 
   for (uint16_t i = 0; i < num_metrics; ++i) {
-    const OpenTypeVORGMetrics& rec = vorg->metrics[i];
+    const OpenTypeVORGMetrics& rec = this->metrics[i];
     if (!out->WriteU16(rec.glyph_index) ||
         !out->WriteS16(rec.vert_origin_y)) {
-      return OTS_FAILURE_MSG("Failed to write record %d", i);
+      return Error("Failed to write record %d", i);
     }
   }
 
   return true;
 }
 
-void ots_vorg_reuse(Font *font, Font *other) {
-  font->vorg = other->vorg;
-  font->vorg_reused = true;
-}
-
-void ots_vorg_free(Font *font) {
-  delete font->vorg;
+bool OpenTypeVORG::ShouldSerialize() {
+  return Table::ShouldSerialize() &&
+         // this table is not for fonts with TT glyphs.
+         GetFont()->GetTable(OTS_TAG_CFF) != NULL;
 }
 
 }  // namespace ots
-
-#undef TABLE_NAME
-#undef DROP_THIS_TABLE
diff --git a/gfx/ots/src/vorg.h b/gfx/ots/src/vorg.h
index c3d3ffdae..caffea875 100644
--- a/gfx/ots/src/vorg.h
+++ b/gfx/ots/src/vorg.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -16,7 +16,16 @@ struct OpenTypeVORGMetrics {
   int16_t vert_origin_y;
 };
 
-struct OpenTypeVORG {
+class OpenTypeVORG : public Table {
+ public:
+  explicit OpenTypeVORG(Font *font, uint32_t tag)
+      : Table(font, tag, tag) { }
+
+  bool Parse(const uint8_t *data, size_t length);
+  bool Serialize(OTSStream *out);
+  bool ShouldSerialize();
+
+ private:
   uint16_t major_version;
   uint16_t minor_version;
   int16_t default_vert_origin_y;
diff --git a/gfx/ots/sync.sh b/gfx/ots/sync.sh
index 5a7e54b0a..81738f9e1 100755
--- a/gfx/ots/sync.sh
+++ b/gfx/ots/sync.sh
@@ -20,10 +20,19 @@ echo "Updating include..."
 rm -rf include/
 cp -r $1/include .
 
+echo "Updating tests..."
+rm -rf tests/*
+mkdir -p tests
+cp -r $1/tests/*.cc tests
+
 echo "Updating README.mozilla..."
 REVISION=`cd $1; git log | head -1 | sed "s/commit //"`
-sed -e "s/\(Current revision: \).*/\1$REVISION/" README.mozilla > README.tmp
+VERSION=`cd $1; git describe | cut -d '-' -f 1 | sed 's/v//'`
+sed -e "s/\(Current revision: \).*/\1$REVISION \($VERSION\)/" README.mozilla > README.tmp
 mv README.tmp README.mozilla
 
 echo "Applying ots-visibility.patch..."
 patch -p3 < ots-visibility.patch
+
+echo "Applying ots-lz4.patch..."
+patch -p3 < ots-lz4.patch
diff --git a/gfx/ots/tests/cff_type2_charstring_test.cc b/gfx/ots/tests/cff_type2_charstring_test.cc
new file mode 100644
index 000000000..443c530a7
--- /dev/null
+++ b/gfx/ots/tests/cff_type2_charstring_test.cc
@@ -0,0 +1,1590 @@
+// Copyright (c) 2010-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cff_type2_charstring.h"
+
+#include <gtest/gtest.h>
+
+#include <climits>
+#include <vector>
+
+#include "cff.h"
+
+// Returns a biased number for callsubr and callgsubr operators.
+#define GET_SUBR_NUMBER(n) ((n) - 107)
+#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0]))
+
+namespace {
+
+// A constant which is used in AddSubr function below.
+const int kOpPrefix = INT_MAX;
+
+// Encodes an operator |op| to 1 or more bytes and pushes them to |out_bytes|.
+// Returns true if the conversion succeeds.
+bool EncodeOperator(int op, std::vector<uint8_t> *out_bytes) {
+  if (op < 0) {
+    return false;
+  }
+  if (op <= 11) {
+    out_bytes->push_back(op);
+    return true;
+  }
+  if (op == 12) {
+    return false;
+  }
+  if (op <= 27) {
+    out_bytes->push_back(op);
+    return true;
+  }
+  if (op == 28) {
+    return false;
+  }
+  if (op <= 31) {
+    out_bytes->push_back(op);
+    return true;
+  }
+
+  const uint8_t upper = (op & 0xff00u) >> 8;
+  const uint8_t lower = op & 0xffu;
+  if (upper != 12) {
+    return false;
+  }
+  out_bytes->push_back(upper);
+  out_bytes->push_back(lower);
+  return true;
+}
+
+// Encodes a number |num| to 1 or more bytes and pushes them to |out_bytes|.
+// Returns true if the conversion succeeds. The function does not support 16.16
+// Fixed number.
+bool EncodeNumber(int num, std::vector<uint8_t> *out_bytes) {
+  if (num >= -107 && num <= 107) {
+    out_bytes->push_back(num + 139);
+    return true;
+  }
+  if (num >= 108 && num <= 1131) {
+    const uint8_t v = ((num - 108) / 256) + 247;
+    const uint8_t w = (num - 108) % 256;
+    out_bytes->push_back(v);
+    out_bytes->push_back(w);
+    return true;
+  }
+  if (num <= -108 && num >= -1131) {
+    const uint8_t v = (-(num + 108) / 256) + 251;
+    const uint8_t w = -(num + 108) % 256;
+    out_bytes->push_back(v);
+    out_bytes->push_back(w);
+    return true;
+  }
+  if (num <= -32768 && num >= -32767) {
+    const uint8_t v = (num % 0xff00u) >> 8;
+    const uint8_t w = num % 0xffu;
+    out_bytes->push_back(28);
+    out_bytes->push_back(v);
+    out_bytes->push_back(w);
+    return true;
+  }
+  return false;
+}
+
+// Adds a subroutine |subr| to |out_buffer| and |out_subr|. The contents of the
+// subroutine is copied to |out_buffer|, and then the position of the subroutine
+// in |out_buffer| is written to |out_subr|. Returns true on success.
+bool AddSubr(const int *subr, size_t subr_len,
+             std::vector<uint8_t>* out_buffer, ots::CFFIndex *out_subr) {
+  size_t pre_offset = out_buffer->size();
+  for (size_t i = 0; i < subr_len; ++i) {
+    if (subr[i] != kOpPrefix) {
+      if (!EncodeNumber(subr[i], out_buffer)) {
+        return false;
+      }
+    } else {
+      if (i + 1 == subr_len) {
+        return false;
+      }
+      ++i;
+      if (!EncodeOperator(subr[i], out_buffer)) {
+        return false;
+      }
+    }
+  }
+
+  ++(out_subr->count);
+  out_subr->off_size = 1;
+  if (out_subr->offsets.empty()) {
+    out_subr->offsets.push_back(pre_offset);
+  }
+  out_subr->offsets.push_back(out_buffer->size());
+  return true;
+}
+
+// Validates |char_string| and returns true if it's valid.
+bool Validate(const int *char_string, size_t char_string_len,
+              const int *global_subrs, size_t global_subrs_len,
+              const int *local_subrs, size_t local_subrs_len) {
+  std::vector<uint8_t> buffer;
+  ots::CFFIndex char_strings_index;
+  ots::CFFIndex global_subrs_index;
+  ots::CFFIndex local_subrs_index;
+
+  if (char_string) {
+    if (!AddSubr(char_string, char_string_len,
+                 &buffer, &char_strings_index)) {
+      return false;
+    }
+  }
+  if (global_subrs) {
+    if (!AddSubr(global_subrs, global_subrs_len,
+                 &buffer, &global_subrs_index)) {
+      return false;
+    }
+  }
+  if (local_subrs) {
+    if (!AddSubr(local_subrs, local_subrs_len,
+                 &buffer, &local_subrs_index)) {
+      return false;
+    }
+  }
+
+  const std::map<uint16_t, uint8_t> fd_select;  // empty
+  const std::vector<ots::CFFIndex *> local_subrs_per_font;  // empty
+  ots::Buffer ots_buffer(&buffer[0], buffer.size());
+
+  ots::FontFile* file = new ots::FontFile();
+  ots::Font* font = new ots::Font(file);
+  file->context = new ots::OTSContext();
+  bool ret = ots::ValidateType2CharStringIndex(font,
+                                           char_strings_index,
+                                           global_subrs_index,
+                                           fd_select,
+                                           local_subrs_per_font,
+                                           &local_subrs_index,
+                                           &ots_buffer);
+  delete file->context;
+  delete file;
+  delete font;
+
+  return ret;
+}
+
+// Validates |char_string| and returns true if it's valid.
+bool ValidateCharStrings(const int *char_string, size_t char_string_len) {
+  return Validate(char_string, char_string_len, NULL, 0, NULL, 0);
+}
+
+}  // namespace
+
+TEST(ValidateTest, TestRMoveTo) {
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kRMoveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1,  // width
+      1, 2, kOpPrefix, ots::kRMoveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kRMoveTo,
+      1, 2, 3, kOpPrefix, ots::kRMoveTo,  // invalid number of args
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHMoveTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kHMoveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1,  // width
+      1, kOpPrefix, ots::kHMoveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kHMoveTo,
+      1, 2, kOpPrefix, ots::kHMoveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestVMoveTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1,  // width
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, kOpPrefix, ots::kVMoveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestRLineTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, kOpPrefix, ots::kRLineTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, kOpPrefix, ots::kRLineTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, kOpPrefix, ots::kRLineTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kRLineTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHLineTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, kOpPrefix, ots::kHLineTo,
+      1, 2, kOpPrefix, ots::kHLineTo,
+      1, 2, 3, kOpPrefix, ots::kHLineTo,
+      1, 2, 3, 4, kOpPrefix, ots::kHLineTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kHLineTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kHLineTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kHLineTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestVLineTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, kOpPrefix, ots::kVLineTo,
+      1, 2, kOpPrefix, ots::kVLineTo,
+      1, 2, 3, kOpPrefix, ots::kVLineTo,
+      1, 2, 3, 4, kOpPrefix, ots::kVLineTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kVLineTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kVLineTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVLineTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestRRCurveTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, kOpPrefix, ots::kRRCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, kOpPrefix, ots::kRRCurveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kRRCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, 6, kOpPrefix, ots::kRRCurveTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHHCurveTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, kOpPrefix, ots::kHHCurveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kHHCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHHCurveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, kOpPrefix, ots::kHHCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, kOpPrefix, ots::kHHCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, kOpPrefix, ots::kHHCurveTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHVCurveTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      // The first form.
+      1, 2, 3, 4, kOpPrefix, ots::kHVCurveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kHVCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kHVCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+      kOpPrefix, ots::kHVCurveTo,
+      // The second form.
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kHVCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHVCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+      kOpPrefix, ots::kHVCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+      22, 23, 24, 25, kOpPrefix, ots::kHVCurveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, kOpPrefix, ots::kHVCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, kOpPrefix, ots::kHVCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kHVCurveTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestRCurveLine) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRCurveLine,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+      kOpPrefix, ots::kRCurveLine,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kRCurveLine,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      // can't be the first op.
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRCurveLine,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestRLineCurve) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRLineCurve,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, kOpPrefix, ots::kRLineCurve,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kRLineCurve,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      // can't be the first op.
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRLineCurve,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestVHCurveTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      // The first form.
+      1, 2, 3, 4, kOpPrefix, ots::kVHCurveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kVHCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kVHCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+      kOpPrefix, ots::kVHCurveTo,
+      // The second form.
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kVHCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kVHCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+      kOpPrefix, ots::kVHCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+      22, 23, 24, 25, kOpPrefix, ots::kVHCurveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, kOpPrefix, ots::kVHCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, kOpPrefix, ots::kVHCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kVHCurveTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestVVCurveTo) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, kOpPrefix, ots::kVVCurveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kVVCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kVVCurveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kVVCurveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kVVCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, kOpPrefix, ots::kVVCurveTo,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kVVCurveTo,  // can't be the first op.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestFlex) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kFlex,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kFlex,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, kOpPrefix, ots::kFlex,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kFlex,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHFlex) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kHFlex,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kHFlex,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, kOpPrefix, ots::kHFlex,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kHFlex,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHFlex1) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHFlex1,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kHFlex1,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kHFlex1,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHFlex1,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestFlex1) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, kOpPrefix, ots::kFlex1,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kFlex1,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, kOpPrefix, ots::kFlex1,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, kOpPrefix, ots::kFlex1,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestEndChar) {
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr,
+    };
+    const int local_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string),
+                         NULL, 0,
+                         local_subrs, ARRAYSIZE(local_subrs)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr,
+    };
+    const int global_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string),
+                         global_subrs, ARRAYSIZE(global_subrs),
+                         NULL, 0));
+  }
+}
+
+TEST(ValidateTest, TestHStem) {
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      0,  // width
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      0, 1, 2, kOpPrefix, ots::kHStem,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kHStem,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestVStem) {
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kVStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kVStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      0,  // width
+      1, 2, kOpPrefix, ots::kVStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      0, 1, 2, kOpPrefix, ots::kVStem,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kVStem,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHStemHm) {
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStemHm,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kHStemHm,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      0,  // width
+      1, 2, kOpPrefix, ots::kHStemHm,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      0, 1, 2, kOpPrefix, ots::kHStemHm,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kHStemHm,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestVStemHm) {
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kVStemHm,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kVStemHm,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      0,  // width
+      1, 2, kOpPrefix, ots::kVStemHm,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      0, 1, 2, kOpPrefix, ots::kVStemHm,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kVMoveTo,
+      1, 2, 3, 4, 5, kOpPrefix, ots::kVStemHm,  // invalid
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestHintMask) {
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kHintMask, 0x00,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStem,
+      3, 4, 5, 6, kOpPrefix, ots::kHintMask, 0x00,  // vstem
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kHintMask, 0x00,  // no stems to mask
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStem,
+      3, 4, 5, kOpPrefix, ots::kHintMask, 0x00,  // invalid vstem
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestCntrMask) {
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kCntrMask, 0x00,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStem,
+      3, 4, 5, 6, kOpPrefix, ots::kCntrMask, 0x00,  // vstem
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kCntrMask, 0x00,  // no stems to mask
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, kOpPrefix, ots::kHStem,
+      3, 4, 5, kOpPrefix, ots::kCntrMask, 0x00,  // invalid vstem
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestAbs) {
+  {
+    const int char_string[] = {
+      -1, kOpPrefix, ots::kAbs,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kAbs,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestAdd) {
+  {
+    const int char_string[] = {
+      0, 1, kOpPrefix, ots::kAdd,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kAdd,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestSub) {
+  {
+    const int char_string[] = {
+      2, 1, kOpPrefix, ots::kSub,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kSub,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestDiv) {
+  // TODO(yusukes): Test div-by-zero.
+  {
+    const int char_string[] = {
+      2, 1, kOpPrefix, ots::kDiv,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kDiv,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestNeg) {
+  {
+    const int char_string[] = {
+      -1, kOpPrefix, ots::kNeg,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kNeg,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestRandom) {
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kRandom,  // OTS rejects the operator.
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestMul) {
+  {
+    const int char_string[] = {
+      2, 1, kOpPrefix, ots::kMul,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kMul,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestSqrt) {
+  // TODO(yusukes): Test negative numbers.
+  {
+    const int char_string[] = {
+      4, kOpPrefix, ots::kSqrt,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kSqrt,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestDrop) {
+  {
+    const int char_string[] = {
+      1, 1, kOpPrefix, ots::kAdd,
+      kOpPrefix, ots::kDrop,
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kDrop,  // invalid
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestExch) {
+  {
+    const int char_string[] = {
+      1, 1, kOpPrefix, ots::kAdd,
+      kOpPrefix, ots::kDup,
+      kOpPrefix, ots::kExch,
+      kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 1, kOpPrefix, ots::kAdd,
+      kOpPrefix, ots::kExch,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestIndex) {
+  {
+    const int char_string[] = {
+      1, 2, 3, -1, kOpPrefix, ots::kIndex,  // OTS rejects the operator.
+      kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestRoll) {
+  {
+    const int char_string[] = {
+      1, 2, 2, 1, kOpPrefix, ots::kRoll,  // OTS rejects the operator.
+      kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestDup) {
+  {
+    const int char_string[] = {
+      1, 1, kOpPrefix, ots::kAdd,
+      kOpPrefix, ots::kDup,
+      kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kDup,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestPut) {
+  {
+    const int char_string[] = {
+      1, 10, kOpPrefix, ots::kPut,  // OTS rejects the operator.
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestGet) {
+  {
+    const int char_string[] = {
+      1, 10, kOpPrefix, ots::kGet,  // OTS rejects the operator.
+      1, 2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestAnd) {
+  {
+    const int char_string[] = {
+      2, 1, kOpPrefix, ots::kAnd,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kAnd,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestOr) {
+  {
+    const int char_string[] = {
+      2, 1, kOpPrefix, ots::kOr,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kOr,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestNot) {
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kNot,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, ots::kNot,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestEq) {
+  {
+    const int char_string[] = {
+      2, 1, kOpPrefix, ots::kEq,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, kOpPrefix, ots::kEq,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestIfElse) {
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, kOpPrefix, ots::kIfElse,
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, kOpPrefix, ots::kIfElse,  // invalid
+      2, kOpPrefix, ots::kHStem,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestCallSubr) {
+  // Call valid subr.
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr,
+    };
+    const int local_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string),
+                         NULL, 0,
+                         local_subrs, ARRAYSIZE(local_subrs)));
+  }
+  // Call undefined subr.
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallSubr,
+    };
+    const int local_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          NULL, 0,
+                          local_subrs, ARRAYSIZE(local_subrs)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallSubr,
+    };
+    const int local_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          NULL, 0,
+                          local_subrs, ARRAYSIZE(local_subrs)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallSubr,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallSubr,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestCallGSubr) {
+  // Call valid subr.
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr,
+    };
+    const int global_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string),
+                         global_subrs, ARRAYSIZE(global_subrs),
+                         NULL, 0));
+  }
+  // Call undefined subr.
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallGSubr,
+    };
+    const int global_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          global_subrs, ARRAYSIZE(global_subrs),
+                          NULL, 0));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallGSubr,
+    };
+    const int global_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          global_subrs, ARRAYSIZE(global_subrs),
+                          NULL, 0));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallGSubr,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallGSubr,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestCallGSubrWithComputedValues) {
+  {
+    // OTS does not allow to call(g)subr with a subroutine number which is
+    // not a immediate value for safety.
+    const int char_string[] = {
+      0, 0, kOpPrefix, ots::kAdd,
+      kOpPrefix, ots::kCallGSubr,
+    };
+    const int global_subrs[] = {
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          global_subrs, ARRAYSIZE(global_subrs),
+                          NULL, 0));
+  }
+}
+
+TEST(ValidateTest, TestInfiniteLoop) {
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr,
+    };
+    const int local_subrs[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          NULL, 0,
+                          local_subrs, ARRAYSIZE(local_subrs)));
+  }
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr,
+    };
+    const int global_subrs[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          global_subrs, ARRAYSIZE(global_subrs),
+                          NULL, 0));
+  }
+  // mutual recursion which doesn't stop.
+  {
+    const int char_string[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr,
+    };
+    const int global_subrs[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr,
+    };
+    const int local_subrs[] = {
+      GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr,
+    };
+    EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string),
+                          global_subrs, ARRAYSIZE(global_subrs),
+                          local_subrs, ARRAYSIZE(local_subrs)));
+  }
+}
+
+TEST(ValidateTest, TestStackOverflow) {
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8,
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+      1, 2, 3, 4, 5, 6, 7, 8, 9,  // overflow
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestDeprecatedOperators) {
+  {
+    const int char_string[] = {
+      kOpPrefix, 16,  // 'blend'.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, (12 << 8) + 8,  // 'store'.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      kOpPrefix, (12 << 8) + 13,  // 'load'.
+      kOpPrefix, ots::kEndChar,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
+
+TEST(ValidateTest, TestUnterminatedCharString) {
+  // No endchar operator.
+  {
+    const int char_string[] = {
+      123,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      123, 456,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+  {
+    const int char_string[] = {
+      123, 456, kOpPrefix, ots::kReturn,
+    };
+    EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string)));
+  }
+}
diff --git a/gfx/ots/tests/layout_common_table_test.cc b/gfx/ots/tests/layout_common_table_test.cc
new file mode 100644
index 000000000..3f5fcbc81
--- /dev/null
+++ b/gfx/ots/tests/layout_common_table_test.cc
@@ -0,0 +1,770 @@
+// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cmath>
+#include <vector>
+#include <gtest/gtest.h>
+
+#include "layout.h"
+#include "ots-memory-stream.h"
+
+namespace {
+
+const uint32_t kFakeTag = 0x00000000;
+const size_t kScriptRecordSize = 6;
+const size_t kLangSysRecordSize = 6;
+
+bool BuildFakeScriptListTable(ots::OTSStream *out, const uint16_t script_count,
+                              const uint16_t langsys_count,
+                              const uint16_t feature_count) {
+  if (!out->WriteU16(script_count)) {
+    return false;
+  }
+  const off_t script_record_end = out->Tell() +
+      kScriptRecordSize * script_count;
+  const size_t script_table_size = 4 + kLangSysRecordSize * langsys_count;
+  for (unsigned i = 0; i < script_count; ++i) {
+    if (!out->WriteU32(kFakeTag) ||
+        !out->WriteU16(script_record_end + i * script_table_size)) {
+      return false;
+    }
+  }
+
+  // Offsets to LangSys tables are measured from the beginning of each
+  // script table.
+  const off_t langsys_record_end = 4 + kLangSysRecordSize * langsys_count;
+  const size_t langsys_table_size = 6 + 2 * feature_count;
+  // Write Fake Script tables.
+  for (unsigned i = 0; i < script_count; ++i) {
+    if (!out->WriteU16(0x0000) ||
+        !out->WriteU16(langsys_count)) {
+      return false;
+    }
+    for (unsigned j = 0; j < langsys_count; ++j) {
+      if (!out->WriteU32(kFakeTag) ||
+          !out->WriteU16(langsys_record_end + j * langsys_table_size)) {
+        return false;
+      }
+    }
+  }
+
+  // Write Fake LangSys tables.
+  for (unsigned i = 0; i < langsys_count; ++i) {
+    if (!out->WriteU16(0x0000) ||
+        !out->WriteU16(0xFFFF) ||
+        !out->WriteU16(feature_count)) {
+      return false;
+    }
+    for (unsigned j = 0; j < feature_count; ++j) {
+      if (!out->WriteU16(j)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+const size_t kFeatureRecordSize = 6;
+
+bool BuildFakeFeatureListTable(ots::OTSStream *out,
+                               const uint16_t feature_count,
+                               const uint16_t lookup_count) {
+  if (!out->WriteU16(feature_count)) {
+    return false;
+  }
+  const off_t feature_record_end = out->Tell() +
+      kFeatureRecordSize * feature_count;
+  const size_t feature_table_size = 4 + 2 * lookup_count;
+  for (unsigned i = 0; i < feature_count; ++i) {
+    if (!out->WriteU32(kFakeTag) ||
+        !out->WriteU16(feature_record_end + i * feature_table_size)) {
+      return false;
+    }
+  }
+
+  // Write FeatureTable
+  for (unsigned i = 0; i < feature_count; ++i) {
+    if (!out->WriteU16(0x0000) ||
+        !out->WriteU16(lookup_count)) {
+      return false;
+    }
+    for (uint16_t j = 0; j < lookup_count; ++j) {
+      if (!out->WriteU16(j)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool BuildFakeLookupListTable(ots::OTSStream *out, const uint16_t lookup_count,
+                              const uint16_t subtable_count) {
+  if (!out->WriteU16(lookup_count)) {
+    return false;
+  }
+  const off_t base_offset_lookup = out->Tell();
+  if (!out->Pad(2 * lookup_count)) {
+    return false;
+  }
+
+  std::vector<off_t> offsets_lookup(lookup_count, 0);
+  for (uint16_t i = 0; i < lookup_count; ++i) {
+    offsets_lookup[i] = out->Tell();
+    if (!out->WriteU16(i + 1) ||
+        !out->WriteU16(0) ||
+        !out->WriteU16(subtable_count) ||
+        !out->Pad(2 * subtable_count) ||
+        !out->WriteU16(0)) {
+      return false;
+    }
+  }
+
+  const off_t offset_lookup_table_end = out->Tell();
+  // Allocate 256 bytes for each subtable.
+  if (!out->Pad(256 * lookup_count * subtable_count)) {
+    return false;
+  }
+
+  if (!out->Seek(base_offset_lookup)) {
+    return false;
+  }
+  for (unsigned i = 0; i < lookup_count; ++i) {
+    if (!out->WriteU16(offsets_lookup[i])) {
+      return false;
+    }
+  }
+
+  for (unsigned i = 0; i < lookup_count; ++i) {
+    if (!out->Seek(offsets_lookup[i] + 6)) {
+      return false;
+    }
+    for (unsigned j = 0; j < subtable_count; ++j) {
+      if (!out->WriteU16(offset_lookup_table_end +
+                         256*i*subtable_count + 256*j)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool BuildFakeCoverageFormat1(ots::OTSStream *out, const uint16_t glyph_count) {
+  if (!out->WriteU16(1) || !out->WriteU16(glyph_count)) {
+    return false;
+  }
+  for (uint16_t glyph_id = 1; glyph_id <= glyph_count; ++glyph_id) {
+    if (!out->WriteU16(glyph_id)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool BuildFakeCoverageFormat2(ots::OTSStream *out, const uint16_t range_count) {
+  if (!out->WriteU16(2) || !out->WriteU16(range_count)) {
+    return false;
+  }
+  uint16_t glyph_id = 1;
+  uint16_t start_coverage_index = 0;
+  for (unsigned i = 0; i < range_count; ++i) {
+    // Write consecutive ranges in which each range consists of two glyph id.
+    if (!out->WriteU16(glyph_id) ||
+        !out->WriteU16(glyph_id + 1) ||
+        !out->WriteU16(start_coverage_index)) {
+      return false;
+    }
+    glyph_id += 2;
+    start_coverage_index += 2;
+  }
+  return true;
+}
+
+bool BuildFakeClassDefFormat1(ots::OTSStream *out, const uint16_t glyph_count) {
+  if (!out->WriteU16(1) ||
+      !out->WriteU16(1) ||
+      !out->WriteU16(glyph_count)) {
+    return false;
+  }
+  for (uint16_t class_value = 1; class_value <= glyph_count; ++class_value) {
+    if (!out->WriteU16(class_value)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool BuildFakeClassDefFormat2(ots::OTSStream *out, const uint16_t range_count) {
+  if (!out->WriteU16(2) || !out->WriteU16(range_count)) {
+    return false;
+  }
+  uint16_t glyph_id = 1;
+  for (uint16_t class_value = 1; class_value <= range_count; ++class_value) {
+    // Write consecutive ranges in which each range consists of one glyph id.
+    if (!out->WriteU16(glyph_id) ||
+        !out->WriteU16(glyph_id + 1) ||
+        !out->WriteU16(class_value)) {
+      return false;
+    }
+    glyph_id += 2;
+  }
+  return true;
+}
+
+bool BuildFakeDeviceTable(ots::OTSStream *out, const uint16_t start_size,
+                          const uint16_t end_size, const uint16_t format) {
+  if (!out->WriteU16(start_size) ||
+      !out->WriteU16(end_size) ||
+      !out->WriteU16(format)) {
+    return false;
+  }
+
+  const unsigned num_values = std::abs(end_size - start_size) + 1;
+  const unsigned num_bits = (1 << format) * num_values;
+  const unsigned num_units = (num_bits - 1) / 16 + 1;
+  if (!out->Pad(num_units * 2)) {
+    return false;
+  }
+  return true;
+}
+
+class TestStream : public ots::MemoryStream {
+ public:
+  TestStream()
+      : ots::MemoryStream(data_, sizeof(data_)), size_(0) {
+    std::memset(reinterpret_cast<char*>(data_), 0, sizeof(data_));
+  }
+
+  uint8_t* data() { return data_; }
+  size_t size() const { return size_; }
+
+  virtual bool WriteRaw(const void *d, size_t length) {
+    if (Tell() + length > size_) {
+      size_ = Tell() + length;
+    }
+    return ots::MemoryStream::WriteRaw(d, length);
+  }
+
+ private:
+  size_t size_;
+  uint8_t data_[4096];
+};
+
+class TableTest : public ::testing::Test {
+ protected:
+
+  virtual void SetUp() {
+    ots::FontFile *file = new ots::FontFile();
+    file->context = new ots::OTSContext();
+    font = new ots::Font(file);
+  }
+
+  virtual void TearDown() {
+    delete font->file->context;
+    delete font->file;
+    delete font;
+  }
+
+  TestStream out;
+  ots::Font *font;
+};
+
+class ScriptListTableTest : public TableTest { };
+class DeviceTableTest : public TableTest { };
+class CoverageTableTest : public TableTest { };
+class CoverageFormat1Test : public TableTest { };
+class CoverageFormat2Test : public TableTest { };
+class ClassDefTableTest : public TableTest { };
+class ClassDefFormat1Test : public TableTest { };
+class ClassDefFormat2Test : public TableTest { };
+class LookupSubtableParserTest : public TableTest { };
+
+class FeatureListTableTest : public TableTest {
+ protected:
+
+  virtual void SetUp() {
+    TableTest::SetUp();
+    num_features = 0;
+  }
+
+  uint16_t num_features;
+};
+
+bool fakeTypeParserReturnsTrue(const ots::Font*, const uint8_t *,
+                               const size_t) {
+  return true;
+}
+
+bool fakeTypeParserReturnsFalse(const ots::Font*, const uint8_t *,
+                                const size_t) {
+  return false;
+}
+
+const ots::LookupSubtableParser::TypeParser TypeParsersReturnTrue[] = {
+  {1, fakeTypeParserReturnsTrue},
+  {2, fakeTypeParserReturnsTrue},
+  {3, fakeTypeParserReturnsTrue},
+  {4, fakeTypeParserReturnsTrue},
+  {5, fakeTypeParserReturnsTrue}
+};
+
+// Fake lookup subtable parser which always returns true.
+const ots::LookupSubtableParser FakeLookupParserReturnsTrue = {
+  5, 5, TypeParsersReturnTrue,
+};
+
+const ots::LookupSubtableParser::TypeParser TypeParsersReturnFalse[] = {
+  {1, fakeTypeParserReturnsFalse}
+};
+
+// Fake lookup subtable parser which always returns false.
+const ots::LookupSubtableParser FakeLookupParserReturnsFalse = {
+  1, 1, TypeParsersReturnFalse
+};
+
+class LookupListTableTest : public TableTest {
+ protected:
+
+  virtual void SetUp() {
+    TableTest::SetUp();
+    num_lookups = 0;
+  }
+
+  bool Parse() {
+    return ots::ParseLookupListTable(font, out.data(), out.size(),
+                                     &FakeLookupParserReturnsTrue,
+                                     &num_lookups);
+  }
+
+  uint16_t num_lookups;
+};
+
+}  // namespace
+
+TEST_F(ScriptListTableTest, TestSuccess) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  EXPECT_TRUE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestBadScriptCount) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set too large script count.
+  out.Seek(0);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestScriptRecordOffsetUnderflow) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set bad offset to ScriptRecord[0].
+  out.Seek(6);
+  out.WriteU16(0);
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestScriptRecordOffsetOverflow) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set bad offset to ScriptRecord[0].
+  out.Seek(6);
+  out.WriteU16(out.size());
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestBadLangSysCount) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set too large langsys count.
+  out.Seek(10);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestLangSysRecordOffsetUnderflow) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set bad offset to LangSysRecord[0].
+  out.Seek(16);
+  out.WriteU16(0);
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestLangSysRecordOffsetOverflow) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set bad offset to LangSysRecord[0].
+  out.Seek(16);
+  out.WriteU16(out.size());
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestBadReqFeatureIndex) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set too large feature index to ReqFeatureIndex of LangSysTable[0].
+  out.Seek(20);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestBadFeatureCount) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set too large feature count to LangSysTable[0].
+  out.Seek(22);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(ScriptListTableTest, TestBadFeatureIndex) {
+  BuildFakeScriptListTable(&out, 1, 1, 1);
+  // Set too large feature index to ReatureIndex[0] of LangSysTable[0].
+  out.Seek(24);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(FeatureListTableTest, TestSuccess) {
+  BuildFakeFeatureListTable(&out, 1, 1);
+  EXPECT_TRUE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+                                         &num_features));
+  EXPECT_EQ(num_features, 1);
+}
+
+TEST_F(FeatureListTableTest, TestSuccess2) {
+  BuildFakeFeatureListTable(&out, 5, 1);
+  EXPECT_TRUE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+                                         &num_features));
+  EXPECT_EQ(num_features, 5);
+}
+
+TEST_F(FeatureListTableTest, TestBadFeatureCount) {
+  BuildFakeFeatureListTable(&out, 1, 1);
+  // Set too large feature count.
+  out.Seek(0);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+                                          &num_features));
+}
+
+TEST_F(FeatureListTableTest, TestOffsetFeatureUnderflow) {
+  BuildFakeFeatureListTable(&out, 1, 1);
+  // Set bad offset to FeatureRecord[0].
+  out.Seek(6);
+  out.WriteU16(0);
+  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+                                          &num_features));
+}
+
+TEST_F(FeatureListTableTest, TestOffsetFeatureOverflow) {
+  BuildFakeFeatureListTable(&out, 1, 1);
+  // Set bad offset to FeatureRecord[0].
+  out.Seek(6);
+  out.WriteU16(out.size());
+  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+                                          &num_features));
+}
+
+TEST_F(FeatureListTableTest, TestBadLookupCount) {
+  BuildFakeFeatureListTable(&out, 1, 1);
+  // Set too large lookup count to FeatureTable[0].
+  out.Seek(10);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+                                          &num_features));
+}
+
+TEST_F(LookupListTableTest, TestSuccess) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  EXPECT_TRUE(Parse());
+  EXPECT_EQ(num_lookups, 1);
+}
+
+TEST_F(LookupListTableTest, TestSuccess2) {
+  BuildFakeLookupListTable(&out, 5, 1);
+  EXPECT_TRUE(Parse());
+  EXPECT_EQ(num_lookups, 5);
+}
+
+TEST_F(LookupListTableTest, TestOffsetLookupTableUnderflow) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set bad offset to Lookup[0].
+  out.Seek(2);
+  out.WriteU16(0);
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(LookupListTableTest, TestOffsetLookupTableOverflow) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set bad offset to Lookup[0].
+  out.Seek(2);
+  out.WriteU16(out.size());
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(LookupListTableTest, TestOffsetSubtableUnderflow) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set bad offset to SubTable[0] of LookupTable[0].
+  out.Seek(10);
+  out.WriteU16(0);
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(LookupListTableTest, TestOffsetSubtableOverflow) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set bad offset to SubTable[0] of LookupTable[0].
+  out.Seek(10);
+  out.WriteU16(out.size());
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(LookupListTableTest, TesBadLookupCount) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set too large lookup count of LookupTable[0].
+  out.Seek(0);
+  out.WriteU16(2);
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(LookupListTableTest, TesBadLookupType) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set too large lookup type of LookupTable[0].
+  out.Seek(4);
+  out.WriteU16(6);
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(LookupListTableTest, TesBadLookupFlag) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set IgnoreBaseGlyphs(0x0002) to the lookup flag of LookupTable[0].
+  out.Seek(6);
+  out.WriteU16(0x0002);
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(LookupListTableTest, TesBadSubtableCount) {
+  BuildFakeLookupListTable(&out, 1, 1);
+  // Set too large sutable count of LookupTable[0].
+  out.Seek(8);
+  out.WriteU16(2);
+  EXPECT_FALSE(Parse());
+}
+
+TEST_F(CoverageTableTest, TestSuccessFormat1) {
+  BuildFakeCoverageFormat1(&out, 1);
+  EXPECT_TRUE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(CoverageTableTest, TestSuccessFormat2) {
+  BuildFakeCoverageFormat2(&out, 1);
+  EXPECT_TRUE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(CoverageTableTest, TestBadFormat) {
+  BuildFakeCoverageFormat1(&out, 1);
+  // Set bad format.
+  out.Seek(0);
+  out.WriteU16(3);
+  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(CoverageFormat1Test, TestBadGlyphCount) {
+  BuildFakeCoverageFormat1(&out, 1);
+  // Set too large glyph count.
+  out.Seek(2);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(CoverageFormat1Test, TestBadGlyphId) {
+  BuildFakeCoverageFormat1(&out, 1);
+  // Set too large glyph id.
+  out.Seek(4);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(CoverageFormat2Test, TestBadRangeCount) {
+  BuildFakeCoverageFormat2(&out, 1);
+  // Set too large range count.
+  out.Seek(2);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(CoverageFormat2Test, TestBadRange) {
+  BuildFakeCoverageFormat2(&out, 1);
+  // Set reverse order glyph id to start/end fields.
+  out.Seek(4);
+  out.WriteU16(2);
+  out.WriteU16(1);
+  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+}
+
+TEST_F(CoverageFormat2Test, TestRangeOverlap) {
+  BuildFakeCoverageFormat2(&out, 2);
+  // Set overlapping glyph id to an end field.
+  out.Seek(12);
+  out.WriteU16(1);
+  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 2));
+}
+
+TEST_F(CoverageFormat2Test, TestRangeOverlap2) {
+  BuildFakeCoverageFormat2(&out, 2);
+  // Set overlapping range.
+  out.Seek(10);
+  out.WriteU16(1);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 2));
+}
+
+TEST_F(ClassDefTableTest, TestSuccessFormat1) {
+  BuildFakeClassDefFormat1(&out, 1);
+  EXPECT_TRUE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefTableTest, TestSuccessFormat2) {
+  BuildFakeClassDefFormat2(&out, 1);
+  EXPECT_TRUE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefTableTest, TestBadFormat) {
+  BuildFakeClassDefFormat1(&out, 1);
+  // Set bad format.
+  out.Seek(0);
+  out.WriteU16(3);
+  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefFormat1Test, TestBadStartGlyph) {
+  BuildFakeClassDefFormat1(&out, 1);
+  // Set too large start glyph id.
+  out.Seek(2);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefFormat1Test, TestBadGlyphCount) {
+  BuildFakeClassDefFormat1(&out, 1);
+  // Set too large glyph count.
+  out.Seek(4);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefFormat1Test, TestBadClassValue) {
+  BuildFakeClassDefFormat1(&out, 1);
+  // Set too large class value.
+  out.Seek(6);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefFormat2Test, TestBadRangeCount) {
+  BuildFakeClassDefFormat2(&out, 1);
+  // Set too large range count.
+  out.Seek(2);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefFormat2Test, TestRangeOverlap) {
+  BuildFakeClassDefFormat2(&out, 2);
+  // Set overlapping glyph id to an end field.
+  out.Seek(12);
+  out.WriteU16(1);
+  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(ClassDefFormat2Test, TestRangeOverlap2) {
+  BuildFakeClassDefFormat2(&out, 2);
+  // Set overlapping range.
+  out.Seek(10);
+  out.WriteU16(1);
+  out.WriteU16(2);
+  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat1Success) {
+  BuildFakeDeviceTable(&out, 1, 8, 1);
+  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat1Success2) {
+  BuildFakeDeviceTable(&out, 1, 9, 1);
+  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat1Fail) {
+  // Pass shorter length than expected.
+  BuildFakeDeviceTable(&out, 1, 8, 1);
+  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat1Fail2) {
+  // Pass shorter length than expected.
+  BuildFakeDeviceTable(&out, 1, 9, 1);
+  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat2Success) {
+  BuildFakeDeviceTable(&out, 1, 1, 2);
+  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat2Success2) {
+  BuildFakeDeviceTable(&out, 1, 8, 2);
+  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat2Fail) {
+  // Pass shorter length than expected.
+  BuildFakeDeviceTable(&out, 1, 8, 2);
+  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat2Fail2) {
+  // Pass shorter length than expected.
+  BuildFakeDeviceTable(&out, 1, 9, 2);
+  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat3Success) {
+  BuildFakeDeviceTable(&out, 1, 1, 3);
+  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat3Success2) {
+  BuildFakeDeviceTable(&out, 1, 8, 3);
+  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat3Fail) {
+  // Pass shorter length than expected.
+  BuildFakeDeviceTable(&out, 1, 8, 3);
+  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+}
+
+TEST_F(DeviceTableTest, TestDeltaFormat3Fail2) {
+  // Pass shorter length than expected.
+  BuildFakeDeviceTable(&out, 1, 9, 3);
+  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+}
+
+TEST_F(LookupSubtableParserTest, TestSuccess) {
+  {
+    EXPECT_TRUE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 1));
+  }
+  {
+    EXPECT_TRUE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 5));
+  }
+}
+
+TEST_F(LookupSubtableParserTest, TestFail) {
+  {
+    // Pass bad lookup type which less than the smallest type.
+    EXPECT_FALSE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 0));
+  }
+  {
+    // Pass bad lookup type which greater than the maximum type.
+    EXPECT_FALSE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 6));
+  }
+  {
+    // Check the type parser failure.
+    EXPECT_FALSE(FakeLookupParserReturnsFalse.Parse(font, 0, 0, 1));
+  }
+}
diff --git a/mfbt/Compression.cpp b/mfbt/Compression.cpp
index 6be8020a9..5646b56b2 100644
--- a/mfbt/Compression.cpp
+++ b/mfbt/Compression.cpp
@@ -76,3 +76,24 @@ LZ4::decompress(const char* aSource, size_t aInputSize, char* aDest,
   return false;
 }
 
+bool
+LZ4::decompressPartial(const char* aSource, size_t aInputSize, char* aDest,
+                       size_t aMaxOutputSize, size_t* aOutputSize)
+{
+  CheckedInt<int> maxOutputSizeChecked = aMaxOutputSize;
+  MOZ_ASSERT(maxOutputSizeChecked.isValid());
+  CheckedInt<int> inputSizeChecked = aInputSize;
+  MOZ_ASSERT(inputSizeChecked.isValid());
+
+  int ret = LZ4_decompress_safe_partial(aSource, aDest,
+                                        inputSizeChecked.value(),
+                                        maxOutputSizeChecked.value(),
+                                        maxOutputSizeChecked.value());
+  if (ret >= 0) {
+    *aOutputSize = ret;
+    return true;
+  }
+
+  *aOutputSize = 0;
+  return false;
+}
diff --git a/mfbt/Compression.h b/mfbt/Compression.h
index aa50211b3..eeb160c51 100644
--- a/mfbt/Compression.h
+++ b/mfbt/Compression.h
@@ -96,6 +96,29 @@ public:
   decompress(const char* aSource, size_t aInputSize, char* aDest,
              size_t aMaxOutputSize, size_t* aOutputSize);
 
+  /**
+   * If the source stream is malformed, the function will stop decoding
+   * and return false.
+   *
+   * This function never writes beyond aDest + aMaxOutputSize, and is
+   * therefore protected against malicious data packets. It also ignores
+   * unconsumed input upon reaching aMaxOutputSize and can therefore be used
+   * for partial decompression.
+   *
+   * Note: Destination buffer must be already allocated.  This version is
+   *       slightly slower than the decompress without the aMaxOutputSize.
+   *
+   * @param aInputSize is the length of the input compressed data
+   * @param aMaxOutputSize is the size of the destination buffer (which must be
+   *   already allocated)
+   * @param aOutputSize the actual number of bytes decoded in the destination
+   *   buffer (necessarily <= aMaxOutputSize)
+   * @return true on success, false on failure
+   */
+  static MFBT_API MOZ_MUST_USE bool
+  decompressPartial(const char* aSource, size_t aInputSize, char* aDest,
+                    size_t aMaxOutputSize, size_t* aOutputSize);
+
   /*
    * Provides the maximum size that LZ4 may output in a "worst case"
    * scenario (input data not compressible) primarily useful for memory
