Bug 1369672 - Update OTS to support Graphite table sanitization. r?jfkthame draft
authorKevin Hsieh <kevin.hsieh@ucla.edu>
Fri, 11 Aug 2017 16:36:12 -0700
changeset 648531 38adffffeb9ab75cd175ee1706bb4957b613464f
parent 617946 ec666e910442ae55686160c1bd0c93b00a08dead
child 726849 432c4c96d6ff3ec7b6829c37070dd6ed598bb1ee
push id74783
push userbmo:kevin.hsieh@ucla.edu
push dateThu, 17 Aug 2017 22:37:44 +0000
reviewersjfkthame
bugs1369672
milestone56.0a1
Bug 1369672 - Update OTS to support Graphite table sanitization. r?jfkthame MozReview-Commit-ID: 4WU4nQcsQgt
gfx/ots/README.mozilla
gfx/ots/include/opentype-sanitiser.h
gfx/ots/ots-config.patch
gfx/ots/ots-lz4.patch
gfx/ots/ots-visibility.patch
gfx/ots/src/feat.cc
gfx/ots/src/feat.h
gfx/ots/src/glat.cc
gfx/ots/src/glat.h
gfx/ots/src/gloc.cc
gfx/ots/src/gloc.h
gfx/ots/src/graphite.h
gfx/ots/src/moz.build
gfx/ots/src/name.cc
gfx/ots/src/name.h
gfx/ots/src/ots.cc
gfx/ots/src/ots.h
gfx/ots/src/sile.cc
gfx/ots/src/sile.h
gfx/ots/src/silf.cc
gfx/ots/src/silf.h
gfx/ots/src/sill.cc
gfx/ots/src/sill.h
gfx/ots/sync.sh
gfx/thebes/gfxUserFontSet.cpp
--- a/gfx/ots/README.mozilla
+++ b/gfx/ots/README.mozilla
@@ -1,12 +1,12 @@
 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: 5f685b8e1fce77347c87f6d98511d53debbe64b2 (5.2.0)
+Current revision: 57ef618b11aa0409637af04988ccce7e6b92ed0f (5.2.0)
 
 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-config.patch (config.h not needed in mozilla build)
+Additional patch: ots-lz4.patch
--- a/gfx/ots/include/opentype-sanitiser.h
+++ b/gfx/ots/include/opentype-sanitiser.h
@@ -171,17 +171,17 @@ class OTSStream {
 #ifdef __GCC__
 #define MSGFUNC_FMT_ATTR __attribute__((format(printf, 2, 3)))
 #else
 #define MSGFUNC_FMT_ATTR
 #endif
 
 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
 };
 
 class OTS_API OTSContext {
   public:
     OTSContext() {}
     virtual ~OTSContext() {}
deleted file mode 100644
--- a/gfx/ots/ots-config.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-diff --git a/gfx/ots/src/ots.h b/gfx/ots/src/ots.h
---- a/gfx/ots/src/ots.h
-+++ b/gfx/ots/src/ots.h
-@@ -1,16 +1,17 @@
- // 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_
- 
--#include "config.h"
-+// Not needed in the gecko build
-+// #include "config.h"
- 
- #include <stddef.h>
- #include <cstdarg>
- #include <cstddef>
- #include <cstdio>
- #include <cstdlib>
- #include <cstring>
- #include <limits>
new file mode 100644
--- /dev/null
+++ b/gfx/ots/ots-lz4.patch
@@ -0,0 +1,70 @@
+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,13 +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, 0);
+-      int ret = LZ4_decompress_safe(
+-          reinterpret_cast<const char*>(data + table.offset()),
+-          reinterpret_cast<char*>(decompressed.data()),
+-          table.remaining(),
+-          decompressed.size());
+-      if (ret < 0) {
+-        return DropGraphite("Decompression failed with error code %d", ret);
++      size_t outputSize = 0;
++      if (!mozilla::Compression::LZ4::decompress(
++            reinterpret_cast<const char*>(data + table.offset()),
++            table.remaining(),
++            reinterpret_cast<char*>(decompressed.data()),
++            decompressed.size(),
++            &outputSize) ||
++          outputSize != (this->compHead & FULL_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,13 +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, 0);
+-        int ret = LZ4_decompress_safe(
+-            reinterpret_cast<const char*>(data + table.offset()),
+-            reinterpret_cast<char*>(decompressed.data()),
+-            table.remaining(),
+-            decompressed.size());
+-        if (ret < 0) {
+-          return DropGraphite("Decompression failed with error code %d", ret);
++        size_t outputSize = 0;
++        if (!mozilla::Compression::LZ4::decompress(
++              reinterpret_cast<const char*>(data + table.offset()),
++              table.remaining(),
++              reinterpret_cast<char*>(decompressed.data()),
++              decompressed.size(),
++              &outputSize) ||
++            outputSize != (this->compHead & FULL_SIZE)) {
++          return DropGraphite("Decompression failed");
+         }
+         return this->Parse(decompressed.data(), decompressed.size(), true);
+       }
--- a/gfx/ots/ots-visibility.patch
+++ b/gfx/ots/ots-visibility.patch
@@ -1,16 +1,12 @@
 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_
  
 +#if defined(_WIN32) || defined(__CYGWIN__)
 +  #define OTS_DLL_IMPORT __declspec(dllimport)
 +  #define OTS_DLL_EXPORT __declspec(dllexport)
 +#else
 +  #if __GNUC__ >= 4
@@ -27,32 +23,17 @@ diff --git a/gfx/ots/include/opentype-sa
 +  #endif
 +#else
 +  #define OTS_API
 +#endif
 +
  #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
  };
  
 -class OTSContext {
 +class OTS_API OTSContext {
    public:
      OTSContext() {}
      virtual ~OTSContext() {}
- 
-     // 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.
new file mode 100644
--- /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
new file mode 100644
--- /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_
new file mode 100644
--- /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 (unsigned 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 (unsigned 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, 0);
+      size_t outputSize = 0;
+      if (!mozilla::Compression::LZ4::decompress(
+            reinterpret_cast<const char*>(data + table.offset()),
+            table.remaining(),
+            reinterpret_cast<char*>(decompressed.data()),
+            decompressed.size(),
+            &outputSize) ||
+          outputSize != (this->compHead & FULL_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 & 0b1) {
+      ++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 (unsigned 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
new file mode 100644
--- /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_
new file mode 100644
--- /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 > 0b11) {
+    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
new file mode 100644
--- /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 = 0b1;
+  static const uint16_t ATTRIB_IDS = 0b10;
+  uint16_t numAttribs;
+  std::vector<uint32_t> locations;
+  std::vector<uint16_t> attribIds;
+};
+
+}  // namespace ots
+
+#endif  // OTS_GLOC_H_
new file mode 100644
--- /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_
--- a/gfx/ots/src/moz.build
+++ b/gfx/ots/src/moz.build
@@ -5,59 +5,66 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     '../include/opentype-sanitiser.h',
     '../include/ots-memory-stream.h',
 ]
 
 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 += [
     'cff.cc',
     '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',
     'os2.cc',
     '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.
 ALLOW_COMPILER_WARNINGS = True
 
 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',
--- a/gfx/ots/src/name.cc
+++ b/gfx/ots/src/name.cc
@@ -144,16 +144,17 @@ bool OpenTypeNAME::Parse(const uint8_t* 
     }
 
     if (!this->names.empty() && !(this->names.back() < rec)) {
       Warning("name records are not sorted.");
       sort_required = true;
     }
 
     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 Error("Failed to read langTagCount");
     }
@@ -300,9 +301,55 @@ bool OpenTypeNAME::Serialize(OTSStream* 
 
   if (!out->Write(string_data.data(), string_data.size())) {
     return Error("Faile to write string data");
   }
 
   return true;
 }
 
+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
--- a/gfx/ots/src/name.h
+++ b/gfx/ots/src/name.h
@@ -4,16 +4,17 @@
 
 #ifndef OTS_NAME_H_
 #define OTS_NAME_H_
 
 #include <new>
 #include <string>
 #include <utility>
 #include <vector>
+#include <unordered_set>
 
 #include "ots.h"
 
 namespace ots {
 
 struct NameRecord {
   NameRecord() {
   }
@@ -45,17 +46,19 @@ struct NameRecord {
 
 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
 
 #endif  // OTS_NAME_H_
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -42,16 +42,26 @@
 #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
+
 namespace ots {
 
 struct Arena {
  public:
   ~Arena() {
     for (std::vector<uint8_t*>::iterator
          i = hunks_.begin(); i != hunks_.end(); ++i) {
       delete[] *i;
@@ -119,16 +129,25 @@ const struct {
   // We need to parse GDEF table in advance of parsing GSUB/GPOS tables
   // because they could refer GDEF table.
   { 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::FontFile *header,
                     ots::Font *font,
                     uint32_t signature,
                     ots::OTSStream *output,
                     const uint8_t *data, size_t length,
@@ -864,16 +883,25 @@ bool Font::ParseTable(const TableEntry& 
       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;
 
@@ -905,16 +933,31 @@ Table* Font::GetTable(uint32_t tag) cons
 
 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);
   m_font->file->context->Message(level, msg);
@@ -945,16 +988,26 @@ bool Table::Drop(const char *format, ...
   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)) {
--- a/gfx/ots/src/ots.h
+++ b/gfx/ots/src/ots.h
@@ -1,17 +1,18 @@
 // 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_
 
-// Not needed in the gecko build
-// #include "config.h"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
 
 #include <stddef.h>
 #include <cstdarg>
 #include <cstddef>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 #include <limits>
@@ -183,35 +184,41 @@ template<typename T> T Round2(T value) {
   return (value + 1) & ~1;
 }
 
 bool IsValidVersionTag(uint32_t tag);
 
 #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 FontFile;
 struct TableEntry;
@@ -239,16 +246,17 @@ class Table {
   // 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;
@@ -272,35 +280,40 @@ class TablePassthru : public Table {
 
 struct Font {
   explicit Font(FontFile *f)
       : file(f),
         version(0),
         num_tables(0),
         search_range(0),
         entry_selector(0),
-        range_shift(0) {
+        range_shift(0),
+        dropped_graphite(false) {
   }
 
   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;
 
+  // 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;
 
  private:
   std::map<uint32_t, Table*> m_tables;
 };
 
 struct TableEntry {
   uint32_t tag;
   uint32_t offset;
new file mode 100644
--- /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
new file mode 100644
--- /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_
new file mode 100644
--- /dev/null
+++ b/gfx/ots/src/silf.cc
@@ -0,0 +1,950 @@
+// 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, 0);
+        size_t outputSize = 0;
+        if (!mozilla::Compression::LZ4::decompress(
+              reinterpret_cast<const char*>(data + table.offset()),
+              table.remaining(),
+              reinterpret_cast<char*>(decompressed.data()),
+              decompressed.size(),
+              &outputSize) ||
+            outputSize != (this->compHead & FULL_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) || this->lbGID > this->maxGlyphID) {
+    return parent->Error("SILSub: Failed to read valid lbGID");
+  }
+
+  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");
+  }
+  if (!table.ReadU16(&this->searchPseudo) || this->searchPseudo !=
+      (this->numPseudo == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::pow(2, std::floor(std::log2(this->numPseudo))))) {
+    return parent->Error("SILSub: Failed to read valid searchPseudo");
+  }
+  if (!table.ReadU16(&this->pseudoSelector) || this->pseudoSelector !=
+      (this->numPseudo == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::floor(std::log2(this->numPseudo)))) {
+    return parent->Error("SILSub: Failed to read valid pseudoSelector");
+  }
+  if (!table.ReadU16(&this->pseudoShift) ||
+      this->pseudoShift != this->numPseudo - this->searchPseudo) {
+    return parent->Error("SILSub: Failed to read valid pseudoShift");
+  }
+
+  //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) || this->searchRange !=
+      (this->numIDs == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::pow(2, std::floor(std::log2(this->numIDs))))) {
+    return parent->Error("LookupClass: Failed to read valid searchRange");
+  }
+  if (!table.ReadU16(&this->entrySelector) || this->entrySelector !=
+      (this->numIDs == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::floor(std::log2(this->numIDs)))) {
+    return parent->Error("LookupClass: Failed to read valid entrySelector");
+  }
+  if (!table.ReadU16(&this->rangeShift) ||
+      this->rangeShift != this->numIDs - this->searchRange) {
+    return parent->Error("LookupClass: Failed to read valid rangeShift");
+  }
+
+  //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");
+  }
+  if (!table.ReadU16(&this->searchRange) || this->searchRange !=
+      (this->numRange == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::pow(2, std::floor(std::log2(this->numRange))))) {
+    return parent->Error("SILPass: Failed to read valid searchRange");
+  }
+  if (!table.ReadU16(&this->entrySelector) || this->entrySelector !=
+      (this->numRange == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::floor(std::log2(this->numRange)))) {
+    return parent->Error("SILPass: Failed to read valid entrySelector");
+  }
+  if (!table.ReadU16(&this->rangeShift) ||
+      this->rangeShift != this->numRange - this->searchRange) {
+    return parent->Error("SILPass: Failed to read valid rangeShift");
+  }
+
+  //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
new file mode 100644
--- /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_
new file mode 100644
--- /dev/null
+++ b/gfx/ots/src/sill.cc
@@ -0,0 +1,151 @@
+// 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");
+  }
+  if (!table.ReadU16(&this->searchRange) || this->searchRange !=
+      (this->numLangs == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::pow(2, std::floor(std::log2(this->numLangs))))) {
+    return Drop("Failed to read valid searchRange");
+  }
+  if (!table.ReadU16(&this->entrySelector) || this->entrySelector !=
+      (this->numLangs == 0 ? 0 :  // protect against log2(0)
+       (unsigned)std::floor(std::log2(this->numLangs)))) {
+    return Drop("Failed to read valid entrySelector");
+  }
+  if (!table.ReadU16(&this->rangeShift) ||
+      this->rangeShift != this->numLangs - this->searchRange) {
+    return Drop("Failed to read valid rangeShift");
+  }
+
+  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
new file mode 100644
--- /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_
--- a/gfx/ots/sync.sh
+++ b/gfx/ots/sync.sh
@@ -29,10 +29,10 @@ echo "Updating README.mozilla..."
 REVISION=`cd $1; git log | head -1 | sed "s/commit //"`
 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-config.patch..."
-patch -p3 < ots-config.patch
+echo "Applying ots-lz4.patch..."
+patch -p3 < ots-lz4.patch
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -192,21 +192,16 @@ public:
             (mKeepVariationTables &&
              (aTag == TRUETYPE_TAG('a', 'v', 'a', 'r') ||
               aTag == TRUETYPE_TAG('c', 'v', 'a', 'r') ||
               aTag == TRUETYPE_TAG('f', 'v', 'a', 'r') ||
               aTag == TRUETYPE_TAG('g', 'v', 'a', 'r') ||
               aTag == TRUETYPE_TAG('H', 'V', 'A', 'R') ||
               aTag == TRUETYPE_TAG('M', 'V', 'A', 'R') ||
               aTag == TRUETYPE_TAG('V', 'V', 'A', 'R'))) ||
-            aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') ||
-            aTag == TRUETYPE_TAG('S', 'i', 'l', 'l') ||
-            aTag == TRUETYPE_TAG('G', 'l', 'o', 'c') ||
-            aTag == TRUETYPE_TAG('G', 'l', 'a', 't') ||
-            aTag == TRUETYPE_TAG('F', 'e', 'a', 't') ||
             aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
             aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
             aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) {
             return ots::TABLE_ACTION_PASSTHRU;
         }
         return ots::TABLE_ACTION_DEFAULT;
     }