Bug 1369672 - Update OTS to support Graphite table sanitization. r=jfkthame
authorKevin Hsieh <kevin.hsieh@ucla.edu>
Fri, 11 Aug 2017 16:36:12 -0700
changeset 375494 5b228a65aa8b11ccd5bab4b208167f180a789b44
parent 375493 cf69bd1dca31bc80e55ba597bdbfb5f062c6a5a1
child 375495 f3f620b20d19b86cfbd7013bca9fc041bd458f06
push id32357
push userkwierso@gmail.com
push dateFri, 18 Aug 2017 20:11:21 +0000
treeherdermozilla-central@8febdfacc716 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs1369672
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
     }