Merge m-c to fx-team.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 31 Jan 2014 16:59:29 -0500
changeset 182388 d02efae4db3a863c2d0ffaa479c735a128ca9bf7
parent 182387 b670b20cfcc7469605e66567ce5f03e29863876d (current diff)
parent 182290 8ded1c0760cc86b2f6cf1236a2a186415b8e87a2 (diff)
child 182389 f5d04ca4795d025e45d436820b9a289704d765d7
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.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
Merge m-c to fx-team.
gfx/ots/ots-fix-sparc64.patch
gfx/ots/ots-fix-vc10.patch
gfx/ots/ots-graphite.patch
gfx/ots/src/graphite.cc
gfx/ots/src/graphite.h
gfx/ots/src/svg.cc
gfx/ots/src/svg.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2830,18 +2830,18 @@ nsDOMWindowUtils::AreDialogsEnabled(bool
 static nsIDOMBlob*
 GetXPConnectNative(JSContext* aCx, JSObject* aObj) {
   nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(
     nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, aObj));
   return blob;
 }
 
 static nsresult
-GetFileOrBlob(const nsAString& aName, const JS::Value& aBlobParts,
-              const JS::Value& aParameters, JSContext* aCx,
+GetFileOrBlob(const nsAString& aName, JS::Handle<JS::Value> aBlobParts,
+              JS::Handle<JS::Value> aParameters, JSContext* aCx,
               uint8_t aOptionalArgCount, nsISupports** aResult)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsresult rv;
 
@@ -2853,19 +2853,22 @@ GetFileOrBlob(const nsAString& aName, co
   else {
     rv = nsDOMMultipartFile::NewFile(aName, getter_AddRefs(file));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsDOMMultipartFile* domFile =
     static_cast<nsDOMMultipartFile*>(static_cast<nsIDOMFile*>(file.get()));
 
-  JS::Value args[2] = { aBlobParts, aParameters };
-
-  rv = domFile->InitBlob(aCx, aOptionalArgCount, args, GetXPConnectNative);
+  JS::AutoValueVector args(aCx);
+  MOZ_ALWAYS_TRUE(args.resize(2));
+  args[0] = aBlobParts;
+  args[1] = aParameters;
+
+  rv = domFile->InitBlob(aCx, aOptionalArgCount, args.begin(), GetXPConnectNative);
   NS_ENSURE_SUCCESS(rv, rv);
 
   file.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetFile(const nsAString& aName, JS::Handle<JS::Value> aBlobParts,
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -904,17 +904,17 @@ CompositorOGL::CreateFBOWithTexture(cons
     bool isFormatCompatibleWithRGBA
         = gl()->IsGLES2() ? (format == LOCAL_GL_RGBA)
                           : true;
 
     if (isFormatCompatibleWithRGBA) {
       mGLContext->fCopyTexImage2D(mFBOTextureTarget,
                                   0,
                                   LOCAL_GL_RGBA,
-                                  aRect.x, aRect.y,
+                                  aRect.x, FlipY(aRect.y + aRect.height),
                                   aRect.width, aRect.height,
                                   0);
     } else {
       // Curses, incompatible formats.  Take a slow path.
 
       // RGBA
       size_t bufferSize = aRect.width * aRect.height * 4;
       nsAutoArrayPtr<uint8_t> buf(new uint8_t[bufferSize]);
--- a/gfx/ots/README.mozilla
+++ b/gfx/ots/README.mozilla
@@ -1,14 +1,10 @@
 This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
 
-Current revision: r95
+Our reference repository is https://github.com/khaledhosny/ots/.
+
+Current revision: d7d831edd171054c7974f5e0dec2fc19bf869574
 
 Applied local patches:
-    ots-fix-vc10.patch - workaround for VS10 STL wrappers (bug 602558)
-
-    ots-fix-sparc64.patch - fix alignment error on sparc64 (bug 643137)
-
-    ots-graphite.patch - preserve Graphite layout tables (bug 631479)
-
     ots-visibility.patch - make Process function externally visible for Windows DLL (bug 711079)
 
-Patches from https://bugzilla.mozilla.org/show_bug.cgi?id=670901.
+    ots-woff2 - disable WOFF2 support (bug 941019)
--- a/gfx/ots/include/opentype-sanitiser.h
+++ b/gfx/ots/include/opentype-sanitiser.h
@@ -39,17 +39,17 @@ typedef unsigned __int64 uint64_t;
 #define ntohs(x) _byteswap_ushort (x)
 #define htonl(x) _byteswap_ulong (x)
 #define htons(x) _byteswap_ushort (x)
 #else
 #include <arpa/inet.h>
 #include <stdint.h>
 #endif
 
-#include <algorithm>  // for std::min
+#include <algorithm>
 #include <cassert>
 #include <cstddef>
 #include <cstring>
 
 namespace ots {
 
 // -----------------------------------------------------------------------------
 // This is an interface for an abstract stream class which is used for writing
@@ -76,19 +76,19 @@ class OTSStream {
         std::min(length, static_cast<size_t>(4) - chksum_buffer_offset_);
       std::memcpy(chksum_buffer_ + chksum_buffer_offset_, data, l);
       chksum_buffer_offset_ += l;
       offset += l;
       length -= l;
     }
 
     if (chksum_buffer_offset_ == 4) {
-      uint32_t chksum;
-      std::memcpy(&chksum, chksum_buffer_, 4);
-      chksum_ += ntohl(chksum);
+      uint32_t tmp;
+      std::memcpy(&tmp, chksum_buffer_, 4);
+      chksum_ += ntohl(tmp);
       chksum_buffer_offset_ = 0;
     }
 
     while (length >= 4) {
       uint32_t tmp;
       std::memcpy(&tmp, reinterpret_cast<const uint8_t *>(data) + offset,
         sizeof(uint32_t));
       chksum_ += ntohl(tmp);
@@ -194,37 +194,58 @@ class OTSStream {
   }
 
  protected:
   uint32_t chksum_;
   uint8_t chksum_buffer_[4];
   unsigned chksum_buffer_offset_;
 };
 
-#ifdef MOZ_OTS_REPORT_ERRORS
-// Signature of the function to be provided by the client in order to report errors.
-// The return type is a boolean so that it can be used within an expression,
-// but the actual value is ignored. (Suggested convention is to always return 'false'.)
-typedef bool (*MessageFunc)(void *user_data, const char *format, ...);
-#endif
-
 // -----------------------------------------------------------------------------
 // Process a given OpenType file and write out a sanitised version
 //   output: a pointer to an object implementing the OTSStream interface. The
 //     sanitisied output will be written to this. In the even of a failure,
 //     partial output may have been written.
 //   input: the OpenType file
 //   length: the size, in bytes, of |input|
-//   preserve_graphite_tables: whether to preserve Graphite Layout tables
 // -----------------------------------------------------------------------------
-bool OTS_API Process(OTSStream *output, const uint8_t *input, size_t length,
-#ifdef MOZ_OTS_REPORT_ERRORS
-                     MessageFunc message_func, void *user_data,
+bool OTS_API Process(OTSStream *output, const uint8_t *input, size_t length);
+
+// Signature of the function to be provided by the client in order to report errors.
+// The return type is a boolean so that it can be used within an expression,
+// but the actual value is ignored. (Suggested convention is to always return 'false'.)
+#ifdef __GCC__
+#define MSGFUNC_FMT_ATTR __attribute__((format(printf, 2, 3)))
+#else
+#define MSGFUNC_FMT_ATTR
 #endif
-                     bool preserve_graphite_tables = false);
+typedef bool (*MessageFunc)(void *user_data, const char *format, ...)  MSGFUNC_FMT_ATTR;
+
+// Set a callback function that will be called when OTS is reporting an error.
+void OTS_API SetMessageCallback(MessageFunc func, void *user_data);
+
+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
+  TABLE_ACTION_DROP      // Drop the table
+};
+
+// Signature of the function to be provided by the client to decide what action
+// to do for a given table.
+typedef TableAction (*TableActionFunc)(uint32_t tag, void *user_data);
+
+// Set a callback function that will be called when OTS needs to decide what to
+// do for a font table.
+void OTS_API SetTableActionCallback(TableActionFunc func, void *user_data);
 
 // Force to disable debug output even when the library is compiled with
 // -DOTS_DEBUG.
 void DisableDebugOutput();
 
+#ifdef MOZ_OTS_WOFF2
+// Enable WOFF2 support(experimental).
+void EnableWOFF2();
+#endif
+
 }  // namespace ots
 
 #endif  // OPENTYPE_SANITISER_H_
--- a/gfx/ots/include/ots-memory-stream.h
+++ b/gfx/ots/include/ots-memory-stream.h
@@ -66,17 +66,17 @@ class ExpandingMemoryStream : public OTS
       if (length_ == limit_)
         return false;
       size_t new_length = (length_ + 1) * 2;
       if (new_length < length_)
         return false;
       if (new_length > limit_)
         new_length = limit_;
       uint8_t* new_buf = new uint8_t[new_length];
-      memcpy(new_buf, ptr_, length_);
+      std::memcpy(new_buf, ptr_, length_);
       length_ = new_length;
       delete[] static_cast<uint8_t*>(ptr_);
       ptr_ = new_buf;
       return WriteRaw(data, length);
     }
     std::memcpy(static_cast<char*>(ptr_) + off_, data, length);
     off_ += length;
     return true;
deleted file mode 100644
--- a/gfx/ots/ots-fix-sparc64.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-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
-@@ -83,18 +83,20 @@ class OTSStream {
-     if (chksum_buffer_offset_ == 4) {
-       uint32_t chksum;
-       std::memcpy(&chksum, chksum_buffer_, 4);
-       chksum_ += ntohl(chksum);
-       chksum_buffer_offset_ = 0;
-     }
- 
-     while (length >= 4) {
--      chksum_ += ntohl(*reinterpret_cast<const uint32_t*>(
--          reinterpret_cast<const uint8_t*>(data) + offset));
-+      uint32_t tmp;
-+      std::memcpy(&tmp, reinterpret_cast<const uint8_t *>(data) + offset,
-+        sizeof(uint32_t));
-+      chksum_ += ntohl(tmp);
-       length -= 4;
-       offset += 4;
-     }
- 
-     if (length) {
-       if (chksum_buffer_offset_ != 0) return false;  // not reached
-       if (length > 4) return false;  // not reached
-       std::memcpy(chksum_buffer_,
deleted file mode 100644
--- a/gfx/ots/ots-fix-vc10.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-diff --git a/gfx/ots/src/gasp.h b/gfx/ots/src/gasp.h
---- a/gfx/ots/src/gasp.h
-+++ b/gfx/ots/src/gasp.h
-@@ -1,15 +1,16 @@
- // 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.
- 
- #ifndef OTS_GASP_H_
- #define OTS_GASP_H_
- 
-+#include <new>
- #include <utility>  // std::pair
- #include <vector>
- 
- #include "ots.h"
- 
- namespace ots {
- 
- struct OpenTypeGASP {
-diff --git a/gfx/ots/src/glyf.h b/gfx/ots/src/glyf.h
---- a/gfx/ots/src/glyf.h
-+++ b/gfx/ots/src/glyf.h
-@@ -1,15 +1,16 @@
- // 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.
- 
- #ifndef OTS_GLYF_H_
- #define OTS_GLYF_H_
- 
-+#include <new>
- #include <utility>  // std::pair
- #include <vector>
- 
- #include "ots.h"
- 
- namespace ots {
- 
- struct OpenTypeGLYF {
-diff --git a/gfx/ots/src/metrics.h b/gfx/ots/src/metrics.h
---- a/gfx/ots/src/metrics.h
-+++ b/gfx/ots/src/metrics.h
-@@ -1,15 +1,16 @@
- // Copyright (c) 2011 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.
- 
- #ifndef OTS_METRICS_H_
- #define OTS_METRICS_H_
- 
-+#include <new>
- #include <utility>  // std::pair
- #include <vector>
- 
- #include "ots.h"
- 
- namespace ots {
- 
- struct OpenTypeMetricsHeader {
deleted file mode 100644
--- a/gfx/ots/ots-graphite.patch
+++ /dev/null
@@ -1,131 +0,0 @@
-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
-@@ -176,18 +176,20 @@ class OTSStream {
- 
- // -----------------------------------------------------------------------------
- // Process a given OpenType file and write out a sanitised version
- //   output: a pointer to an object implementing the OTSStream interface. The
- //     sanitisied output will be written to this. In the even of a failure,
- //     partial output may have been written.
- //   input: the OpenType file
- //   length: the size, in bytes, of |input|
-+//   preserve_graphite_tables: whether to preserve Graphite Layout tables
- // -----------------------------------------------------------------------------
--bool Process(OTSStream *output, const uint8_t *input, size_t length);
-+bool Process(OTSStream *output, const uint8_t *input, size_t length,
-+             bool preserve_graphite_tables = false);
- 
- // Force to disable debug output even when the library is compiled with
- // -DOTS_DEBUG.
- void DisableDebugOutput();
- 
- }  // namespace ots
- 
- #endif  // OPENTYPE_SANITISER_H_
-diff --git a/gfx/ots/src/ots.cc b/gfx/ots/src/ots.cc
---- a/gfx/ots/src/ots.cc
-+++ b/gfx/ots/src/ots.cc
-@@ -138,16 +138,27 @@ const struct {
-   { "GPOS", ots::ots_gpos_parse, ots::ots_gpos_serialise,
-     ots::ots_gpos_should_serialise, ots::ots_gpos_free, false },
-   { "GSUB", ots::ots_gsub_parse, ots::ots_gsub_serialise,
-     ots::ots_gsub_should_serialise, ots::ots_gsub_free, false },
-   { "vhea", ots::ots_vhea_parse, ots::ots_vhea_serialise,
-     ots::ots_vhea_should_serialise, ots::ots_vhea_free, false },
-   { "vmtx", ots::ots_vmtx_parse, ots::ots_vmtx_serialise,
-     ots::ots_vmtx_should_serialise, ots::ots_vmtx_free, false },
-+  // SILGraphite layout tables - not actually parsed, just copied
-+  { "Silf", ots::ots_silf_parse, ots::ots_silf_serialise,
-+    ots::ots_silf_should_serialise, ots::ots_silf_free, false },
-+  { "Sill", ots::ots_sill_parse, ots::ots_sill_serialise,
-+    ots::ots_sill_should_serialise, ots::ots_sill_free, false },
-+  { "Gloc", ots::ots_gloc_parse, ots::ots_gloc_serialise,
-+    ots::ots_gloc_should_serialise, ots::ots_gloc_free, false },
-+  { "Glat", ots::ots_glat_parse, ots::ots_glat_serialise,
-+    ots::ots_glat_should_serialise, ots::ots_glat_free, false },
-+  { "Feat", ots::ots_feat_parse, ots::ots_feat_serialise,
-+    ots::ots_feat_should_serialise, ots::ots_feat_free, false },
-   // TODO(bashi): Support mort, base, and jstf tables.
-   { 0, NULL, NULL, NULL, NULL, false },
- };
- 
- bool IsValidVersionTag(uint32_t tag) {
-   return tag == Tag("\x00\x01\x00\x00") ||
-          // OpenType fonts with CFF data have 'OTTO' tag.
-          tag == Tag("OTTO") ||
-@@ -581,22 +592,25 @@ bool ProcessGeneric(ots::OpenTypeFile *h
- }  // namespace
- 
- namespace ots {
- 
- void DisableDebugOutput() {
-   g_debug_output = false;
- }
- 
--bool Process(OTSStream *output, const uint8_t *data, size_t length) {
-+bool Process(OTSStream *output, const uint8_t *data, size_t length,
-+             bool preserveGraphite) {
-   OpenTypeFile header;
-   if (length < 4) {
-     return OTS_FAILURE();
-   }
- 
-+  header.preserve_graphite = preserveGraphite;
-+
-   bool result;
-   if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == 'F') {
-     result = ProcessWOFF(&header, output, data, length);
-   } else {
-     result = ProcessTTF(&header, output, data, length);
-   }
- 
-   for (unsigned i = 0; ; ++i) {
-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
-@@ -178,17 +178,22 @@ class Buffer {
-   F(maxp, MAXP) \
-   F(name, NAME) \
-   F(os2, OS2) \
-   F(post, POST) \
-   F(prep, PREP) \
-   F(vdmx, VDMX) \
-   F(vorg, VORG) \
-   F(vhea, VHEA) \
--  F(vmtx, VMTX)
-+  F(vmtx, VMTX) \
-+  F(silf, SILF) \
-+  F(sill, SILL) \
-+  F(glat, GLAT) \
-+  F(gloc, GLOC) \
-+  F(feat, FEAT)
- 
- #define F(name, capname) struct OpenType##capname;
- FOR_EACH_TABLE_TYPE
- #undef F
- 
- struct OpenTypeFile {
-   OpenTypeFile() {
- #define F(name, capname) name = NULL;
-@@ -197,16 +202,20 @@ struct OpenTypeFile {
-   }
- 
-   uint32_t version;
-   uint16_t num_tables;
-   uint16_t search_range;
-   uint16_t entry_selector;
-   uint16_t range_shift;
- 
-+  // This is used to tell the relevant parsers whether to preserve the
-+  // Graphite layout tables (currently _without_ any checking)
-+  bool preserve_graphite;
-+
- #define F(name, capname) OpenType##capname *name;
- FOR_EACH_TABLE_TYPE
- #undef F
- };
- 
- #define F(name, capname) \
- bool ots_##name##_parse(OpenTypeFile *f, const uint8_t *d, size_t l); \
- bool ots_##name##_should_serialise(OpenTypeFile *f); \
--- a/gfx/ots/ots-visibility.patch
+++ b/gfx/ots/ots-visibility.patch
@@ -32,29 +32,57 @@ diff --git a/gfx/ots/include/opentype-sa
  #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;
-@@ -178,18 +198,18 @@ class OTSStream {
+@@ -182,45 +202,45 @@ class OTSStream {
+ // -----------------------------------------------------------------------------
  // Process a given OpenType file and write out a sanitised version
  //   output: a pointer to an object implementing the OTSStream interface. The
  //     sanitisied output will be written to this. In the even of a failure,
  //     partial output may have been written.
  //   input: the OpenType file
  //   length: the size, in bytes, of |input|
- //   preserve_graphite_tables: whether to preserve Graphite Layout tables
  // -----------------------------------------------------------------------------
--bool Process(OTSStream *output, const uint8_t *input, size_t length,
--             bool preserve_graphite_tables = false);
-+bool OTS_API Process(OTSStream *output, const uint8_t *input, size_t length,
-+                     bool preserve_graphite_tables = false);
+-bool Process(OTSStream *output, const uint8_t *input, size_t length);
++bool OTS_API Process(OTSStream *output, const uint8_t *input, size_t length);
+ 
+ // Signature of the function to be provided by the client in order to report errors.
+ // The return type is a boolean so that it can be used within an expression,
+ // but the actual value is ignored. (Suggested convention is to always return 'false'.)
+ #ifdef __GCC__
+ #define MSGFUNC_FMT_ATTR __attribute__((format(printf, 2, 3)))
+ #else
+ #define MSGFUNC_FMT_ATTR
+ #endif
+ typedef bool (*MessageFunc)(void *user_data, const char *format, ...)  MSGFUNC_FMT_ATTR;
+ 
+ // Set a callback function that will be called when OTS is reporting an error.
+-void SetMessageCallback(MessageFunc func, void *user_data);
++void OTS_API SetMessageCallback(MessageFunc func, void *user_data);
+ 
+ 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
+   TABLE_ACTION_DROP      // Drop the table
+ };
+ 
+ // Signature of the function to be provided by the client to decide what action
+ // to do for a given table.
+ typedef TableAction (*TableActionFunc)(uint32_t tag, void *user_data);
+ 
+ // Set a callback function that will be called when OTS needs to decide what to
+ // do for a font table.
+-void SetTableActionCallback(TableActionFunc func, void *user_data);
++void OTS_API SetTableActionCallback(TableActionFunc func, void *user_data);
  
  // Force to disable debug output even when the library is compiled with
  // -DOTS_DEBUG.
  void DisableDebugOutput();
  
- }  // namespace ots
+ // Enable WOFF2 support(experimental).
+ void EnableWOFF2();
  
- #endif  // OPENTYPE_SANITISER_H_
new file mode 100644
--- /dev/null
+++ b/gfx/ots/ots-woff2.patch
@@ -0,0 +1,134 @@
+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
+@@ -236,14 +236,16 @@ typedef TableAction (*TableActionFunc)(u
+ // Set a callback function that will be called when OTS needs to decide what to
+ // do for a font table.
+ void OTS_API SetTableActionCallback(TableActionFunc func, void *user_data);
+ 
+ // Force to disable debug output even when the library is compiled with
+ // -DOTS_DEBUG.
+ void DisableDebugOutput();
+ 
++#ifdef MOZ_OTS_WOFF2
+ // Enable WOFF2 support(experimental).
+ void EnableWOFF2();
++#endif
+ 
+ }  // namespace ots
+ 
+ #endif  // OPENTYPE_SANITISER_H_
+diff --git a/gfx/ots/src/ots.cc b/gfx/ots/src/ots.cc
+--- a/gfx/ots/src/ots.cc
++++ b/gfx/ots/src/ots.cc
+@@ -9,25 +9,29 @@
+ 
+ #include <algorithm>
+ #include <cstdlib>
+ #include <cstring>
+ #include <limits>
+ #include <map>
+ #include <vector>
+ 
++#ifdef MOZ_OTS_WOFF2
+ #include "woff2.h"
++#endif
+ 
+ // The OpenType Font File
+ // http://www.microsoft.com/typography/otspec/cmap.htm
+ 
+ namespace {
+ 
+ bool g_debug_output = true;
++#ifdef MOZ_OTS_WOFF2
+ bool g_enable_woff2 = false;
++#endif
+ 
+ ots::MessageFunc  g_message_func = NULL;
+ void             *g_message_user_data = NULL;
+ 
+ ots::TableActionFunc  g_table_action_func = NULL;
+ void                 *g_table_action_user_data = NULL;
+ 
+ // Generate a message with or without a table tag, when 'header' is the OpenTypeFile pointer
+@@ -395,16 +399,17 @@ bool ProcessWOFF(ots::OpenTypeFile *head
+   }
+   if (block_end != ots::Round4(length)) {
+     return OTS_FAILURE_MSG_HDR("file length mismatch (trailing junk?)");
+   }
+ 
+   return ProcessGeneric(header, woff_tag, output, data, length, tables, file);
+ }
+ 
++#ifdef MOZ_OTS_WOFF2
+ bool ProcessWOFF2(ots::OpenTypeFile *header,
+                   ots::OTSStream *output, const uint8_t *data, size_t length) {
+   size_t decompressed_size = ots::ComputeWOFF2FinalSize(data, length);
+   if (decompressed_size == 0) {
+     return OTS_FAILURE();
+   }
+   // decompressed font must be <= 30MB
+   if (decompressed_size > 30 * 1024 * 1024) {
+@@ -413,16 +418,17 @@ bool ProcessWOFF2(ots::OpenTypeFile *hea
+ 
+   std::vector<uint8_t> decompressed_buffer(decompressed_size);
+   if (!ots::ConvertWOFF2ToTTF(&decompressed_buffer[0], decompressed_size,
+                               data, length)) {
+     return OTS_FAILURE();
+   }
+   return ProcessTTF(header, output, &decompressed_buffer[0], decompressed_size);
+ }
++#endif
+ 
+ ots::TableAction GetTableAction(uint32_t tag) {
+   ots::TableAction action = ots::TABLE_ACTION_DEFAULT;
+ 
+   if (g_table_action_func != NULL) {
+     action = g_table_action_func(htonl(tag), g_table_action_user_data);
+   }
+ 
+@@ -795,19 +801,21 @@ bool IsValidVersionTag(uint32_t tag) {
+          tag == Tag("true") ||
+          tag == Tag("typ1");
+ }
+ 
+ void DisableDebugOutput() {
+   g_debug_output = false;
+ }
+ 
++#ifdef MOZ_OTS_WOFF2
+ void EnableWOFF2() {
+   g_enable_woff2 = true;
+ }
++#endif
+ 
+ void SetMessageCallback(MessageFunc func, void *user_data) {
+   g_message_func = func;
+   g_message_user_data = user_data;
+ }
+ 
+ void SetTableActionCallback(TableActionFunc func, void *user_data) {
+   g_table_action_func = func;
+@@ -822,20 +830,22 @@ bool Process(OTSStream *output, const ui
+ 
+   if (length < 4) {
+     return OTS_FAILURE_MSG_(&header, "file less than 4 bytes");
+   }
+ 
+   bool result;
+   if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == 'F') {
+     result = ProcessWOFF(&header, output, data, length);
++#ifdef MOZ_OTS_WOFF2
+   } else if (g_enable_woff2 &&
+              data[0] == 'w' && data[1] == 'O' && data[2] == 'F' &&
+              data[3] == '2') {
+     result = ProcessWOFF2(&header, output, data, length);
++#endif
+   } else {
+     result = ProcessTTF(&header, output, data, length);
+   }
+ 
+   for (unsigned i = 0; ; ++i) {
+     if (table_parsers[i].parse == NULL) break;
+     table_parsers[i].free(&header);
+   }
--- a/gfx/ots/src/cff.cc
+++ b/gfx/ots/src/cff.cc
@@ -1,24 +1,26 @@
 // Copyright (c) 2012 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.
 
 #include "cff.h"
 
 #include <cstring>
-#include <utility>  // std::pair
+#include <utility>
 #include <vector>
 
 #include "cff_type2_charstring.h"
 
 // CFF - PostScript font program (Compact Font Format) table
 // http://www.microsoft.com/typography/otspec/cff.htm
 // http://www.microsoft.com/typography/otspec/cffspec.htm
 
+#define TABLE_NAME "CFF"
+
 namespace {
 
 enum DICT_OPERAND_TYPE {
   DICT_OPERAND_INTEGER,
   DICT_OPERAND_REAL,
   DICT_OPERATOR,
 };
 
@@ -410,32 +412,32 @@ bool ParsePrivateDictData(
         }
         if (operands.back().first >= 1024 * 1024 * 1024) {
           return OTS_FAILURE();
         }
         if (operands.back().first + offset >= table_length) {
           return OTS_FAILURE();
         }
         // parse "16. Local Subrs INDEX"
-        ots::Buffer table(data, table_length);
-        table.set_offset(operands.back().first + offset);
+        ots::Buffer cff_table(data, table_length);
+        cff_table.set_offset(operands.back().first + offset);
         ots::CFFIndex *local_subrs_index = NULL;
         if (type == DICT_DATA_FDARRAY) {
           if (out_cff->local_subrs_per_font.empty()) {
             return OTS_FAILURE();  // not reached.
           }
           local_subrs_index = out_cff->local_subrs_per_font.back();
         } else { // type == DICT_DATA_TOPLEVEL
           if (out_cff->local_subrs) {
             return OTS_FAILURE();  // two or more local_subrs?
           }
           local_subrs_index = new ots::CFFIndex;
           out_cff->local_subrs = local_subrs_index;
         }
-        if (!ParseIndex(&table, local_subrs_index)) {
+        if (!ParseIndex(&cff_table, local_subrs_index)) {
           return OTS_FAILURE();
         }
         break;
       }
 
       // boolean
       case (12U << 8) + 14:  // ForceBold
         if (operands.size() != 1) {
@@ -597,20 +599,20 @@ bool ParseDictData(const uint8_t *data, 
           if (operands.back().first <= 1) {
             break;  // predefined encoding, "Standard" or "Expert", is used.
           }
           if (!CheckOffset(operands.back(), table_length)) {
             return OTS_FAILURE();
           }
 
           // parse sub dictionary INDEX.
-          ots::Buffer table(data, table_length);
-          table.set_offset(operands.back().first);
+          ots::Buffer cff_table(data, table_length);
+          cff_table.set_offset(operands.back().first);
           uint8_t format = 0;
-          if (!table.ReadU8(&format)) {
+          if (!cff_table.ReadU8(&format)) {
             return OTS_FAILURE();
           }
           if (format & 0x80) {
             // supplemental encoding is not supported at the moment.
             return OTS_FAILURE();
           }
           // TODO(yusukes): support & parse supplemental encoding tables.
           break;
@@ -622,20 +624,20 @@ bool ParseDictData(const uint8_t *data, 
           }
           if (operands.size() != 1) {
             return OTS_FAILURE();
           }
           if (!CheckOffset(operands.back(), table_length)) {
             return OTS_FAILURE();
           }
           // parse "14. CharStrings INDEX"
-          ots::Buffer table(data, table_length);
-          table.set_offset(operands.back().first);
+          ots::Buffer cff_table(data, table_length);
+          cff_table.set_offset(operands.back().first);
           ots::CFFIndex *charstring_index = out_cff->char_strings_array.back();
-          if (!ParseIndex(&table, charstring_index)) {
+          if (!ParseIndex(&cff_table, charstring_index)) {
             return OTS_FAILURE();
           }
           if (charstring_index->count < 2) {
             return OTS_FAILURE();
           }
           if (glyphs) {
             return OTS_FAILURE();  // multiple charstring tables?
           }
@@ -650,20 +652,20 @@ bool ParseDictData(const uint8_t *data, 
           if (operands.size() != 1) {
             return OTS_FAILURE();
           }
           if (!CheckOffset(operands.back(), table_length)) {
             return OTS_FAILURE();
           }
 
           // parse sub dictionary INDEX.
-          ots::Buffer table(data, table_length);
-          table.set_offset(operands.back().first);
+          ots::Buffer cff_table(data, table_length);
+          cff_table.set_offset(operands.back().first);
           ots::CFFIndex sub_dict_index;
-          if (!ParseIndex(&table, &sub_dict_index)) {
+          if (!ParseIndex(&cff_table, &sub_dict_index)) {
             return OTS_FAILURE();
           }
           if (!ParseDictData(data, table_length,
                              sub_dict_index, sid_max, DICT_DATA_FDARRAY,
                              out_cff)) {
             return OTS_FAILURE();
           }
           if (out_cff->font_dict_length != 0) {
@@ -680,44 +682,44 @@ bool ParseDictData(const uint8_t *data, 
           if (operands.size() != 1) {
             return OTS_FAILURE();
           }
           if (!CheckOffset(operands.back(), table_length)) {
             return OTS_FAILURE();
           }
 
           // parse FDSelect data structure
-          ots::Buffer table(data, table_length);
-          table.set_offset(operands.back().first);
+          ots::Buffer cff_table(data, table_length);
+          cff_table.set_offset(operands.back().first);
           uint8_t format = 0;
-          if (!table.ReadU8(&format)) {
+          if (!cff_table.ReadU8(&format)) {
             return OTS_FAILURE();
           }
           if (format == 0) {
             for (size_t j = 0; j < glyphs; ++j) {
               uint8_t fd_index = 0;
-              if (!table.ReadU8(&fd_index)) {
+              if (!cff_table.ReadU8(&fd_index)) {
                 return OTS_FAILURE();
               }
               (out_cff->fd_select)[j] = fd_index;
             }
           } else if (format == 3) {
             uint16_t n_ranges = 0;
-            if (!table.ReadU16(&n_ranges)) {
+            if (!cff_table.ReadU16(&n_ranges)) {
               return OTS_FAILURE();
             }
             if (n_ranges == 0) {
               return OTS_FAILURE();
             }
 
             uint16_t last_gid = 0;
             uint8_t fd_index = 0;
             for (unsigned j = 0; j < n_ranges; ++j) {
               uint16_t first = 0;  // GID
-              if (!table.ReadU16(&first)) {
+              if (!cff_table.ReadU16(&first)) {
                 return OTS_FAILURE();
               }
 
               // Sanity checks.
               if ((j == 0) && (first != 0)) {
                 return OTS_FAILURE();
               }
               if ((j != 0) && (last_gid >= first)) {
@@ -729,24 +731,24 @@ bool ParseDictData(const uint8_t *data, 
                 for (uint16_t k = last_gid; k < first; ++k) {
                   if (!out_cff->fd_select.insert(
                           std::make_pair(k, fd_index)).second) {
                     return OTS_FAILURE();
                   }
                 }
               }
 
-              if (!table.ReadU8(&fd_index)) {
+              if (!cff_table.ReadU8(&fd_index)) {
                 return OTS_FAILURE();
               }
               last_gid = first;
               // TODO(yusukes): check GID?
             }
             uint16_t sentinel = 0;
-            if (!table.ReadU16(&sentinel)) {
+            if (!cff_table.ReadU16(&sentinel)) {
               return OTS_FAILURE();
             }
             if (last_gid >= sentinel) {
               return OTS_FAILURE();
             }
             for (uint16_t k = last_gid; k < sentinel; ++k) {
               if (!out_cff->fd_select.insert(
                       std::make_pair(k, fd_index)).second) {
@@ -823,58 +825,58 @@ bool ParseDictData(const uint8_t *data, 
 
       if (font_format == FORMAT_UNKNOWN) {
         font_format = FORMAT_OTHER;
       }
     }
 
     // parse "13. Charsets"
     if (charset_offset) {
-      ots::Buffer table(data, table_length);
-      table.set_offset(charset_offset);
+      ots::Buffer cff_table(data, table_length);
+      cff_table.set_offset(charset_offset);
       uint8_t format = 0;
-      if (!table.ReadU8(&format)) {
+      if (!cff_table.ReadU8(&format)) {
         return OTS_FAILURE();
       }
       switch (format) {
         case 0:
           for (unsigned j = 1 /* .notdef is omitted */; j < glyphs; ++j) {
             uint16_t sid = 0;
-            if (!table.ReadU16(&sid)) {
+            if (!cff_table.ReadU16(&sid)) {
               return OTS_FAILURE();
             }
             if (!have_ros && (sid > sid_max)) {
               return OTS_FAILURE();
             }
             // TODO(yusukes): check CIDs when have_ros is true.
           }
           break;
 
         case 1:
         case 2: {
           uint32_t total = 1;  // .notdef is omitted.
           while (total < glyphs) {
             uint16_t sid = 0;
-            if (!table.ReadU16(&sid)) {
+            if (!cff_table.ReadU16(&sid)) {
               return OTS_FAILURE();
             }
             if (!have_ros && (sid > sid_max)) {
               return OTS_FAILURE();
             }
             // TODO(yusukes): check CIDs when have_ros is true.
 
             if (format == 1) {
               uint8_t left = 0;
-              if (!table.ReadU8(&left)) {
+              if (!cff_table.ReadU8(&left)) {
                 return OTS_FAILURE();
               }
               total += (left + 1);
             } else {
               uint16_t left = 0;
-              if (!table.ReadU16(&left)) {
+              if (!cff_table.ReadU16(&left)) {
                 return OTS_FAILURE();
               }
               total += (left + 1);
             }
           }
           break;
         }
 
--- a/gfx/ots/src/cff_type2_charstring.cc
+++ b/gfx/ots/src/cff_type2_charstring.cc
@@ -33,16 +33,17 @@ bool ExecuteType2CharString(size_t call_
                             const ots::CFFIndex& local_subrs_index,
                             ots::Buffer *cff_table,
                             ots::Buffer *char_string,
                             std::stack<int32_t> *argument_stack,
                             bool *out_found_endchar,
                             bool *out_found_width,
                             size_t *in_out_num_stems);
 
+#ifdef DUMP_T2CHARSTRING
 // Converts |op| to a string and returns it.
 const char *Type2CharStringOperatorToString(ots::Type2CharStringOperator op) {
   switch (op) {
   case ots::kHStem:
     return "HStem";
   case ots::kVStem:
     return "VStem";
   case ots::kVMoveTo:
@@ -82,16 +83,18 @@ const char *Type2CharStringOperatorToStr
   case ots::kHHCurveTo:
     return "HHCurveTo";
   case ots::kCallGSubr:
     return "CallGSubr";
   case ots::kVHCurveTo:
     return "VHCurveTo";
   case ots::kHVCurveTo:
     return "HVCurveTo";
+  case ots::kDotSection:
+    return "DotSection";
   case ots::kAnd:
     return "And";
   case ots::kOr:
     return "Or";
   case ots::kNot:
     return "Not";
   case ots::kAbs:
     return "Abs";
@@ -134,16 +137,17 @@ const char *Type2CharStringOperatorToStr
   case ots::kHFlex1:
     return "HFlex1";
   case ots::kFlex1:
     return "Flex1";
   }
 
   return "UNKNOWN";
 }
+#endif
 
 // Read one or more bytes from the |char_string| buffer and stores the number
 // read on |out_number|. If the number read is an operator (ex 'vstem'), sets
 // true on |out_is_operator|. Returns true if the function read a number.
 bool ReadNextNumberFromType2CharString(ots::Buffer *char_string,
                                        int32_t *out_number,
                                        bool *out_is_operator) {
   uint8_t v = 0;
@@ -519,16 +523,23 @@ bool ExecuteType2CharStringOperator(int3
       // {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
       successful = true;
     }
     while (!argument_stack->empty())
       argument_stack->pop();
     return successful ? true : OTS_FAILURE();
   }
 
+  case ots::kDotSection:
+    // Deprecated operator but harmless, we probably should drop it some how.
+    if (stack_size != 0) {
+      return OTS_FAILURE();
+    }
+    return true;
+
   case ots::kAnd:
   case ots::kOr:
   case ots::kEq:
   case ots::kAdd:
   case ots::kSub:
     if (stack_size < 2) {
       return OTS_FAILURE();
     }
@@ -733,29 +744,31 @@ bool ExecuteType2CharString(size_t call_
     int32_t operator_or_operand = 0;
     bool is_operator = false;
     if (!ReadNextNumberFromType2CharString(char_string,
                                            &operator_or_operand,
                                            &is_operator)) {
       return OTS_FAILURE();
     }
 
+#ifdef DUMP_T2CHARSTRING
     /*
       You can dump all operators and operands (except mask bytes for hintmask
       and cntrmask) by the following code:
+    */
 
       if (!is_operator) {
         std::fprintf(stderr, "#%d# ", operator_or_operand);
       } else {
         std::fprintf(stderr, "#%s#\n",
            Type2CharStringOperatorToString(
-               Type2CharStringOperator(operator_or_operand)),
-           operator_or_operand);
+               ots::Type2CharStringOperator(operator_or_operand))
+           );
       }
-    */
+#endif
 
     if (!is_operator) {
       argument_stack->push(operator_or_operand);
       if (argument_stack->size() > kMaxArgumentStack) {
         return OTS_FAILURE();
       }
       continue;
     }
--- a/gfx/ots/src/cff_type2_charstring.h
+++ b/gfx/ots/src/cff_type2_charstring.h
@@ -62,16 +62,17 @@ enum Type2CharStringOperator {
   kVStemHm = 23,
   kRCurveLine = 24,
   kRLineCurve = 25,
   kVVCurveTo = 26,
   kHHCurveTo = 27,
   kCallGSubr = 29,
   kVHCurveTo = 30,
   kHVCurveTo = 31,
+  kDotSection = 12 << 8,
   kAnd = (12 << 8) + 3,
   kOr = (12 << 8) + 4,
   kNot = (12 << 8) + 5,
   kAbs = (12 << 8) + 9,
   kAdd = (12 << 8) + 10,
   kSub = (12 << 8) + 11,
   kDiv = (12 << 8) + 12,
   kNeg = (12 << 8) + 14,
@@ -86,15 +87,14 @@ enum Type2CharStringOperator {
   kDup = (12 << 8) + 27,
   kExch = (12 << 8) + 28,
   kIndex = (12 << 8) + 29,
   kRoll = (12 << 8) + 30,
   kHFlex = (12 << 8) + 34,
   kFlex = (12 << 8) + 35,
   kHFlex1 = (12 << 8) + 36,
   kFlex1 = (12 << 8) + 37,
-  // Operators that are obsoleted or undocumented, such as 'blend', will be
-  // rejected.
+  // Operators that are undocumented, such as 'blend', will be rejected.
 };
 
 }  // namespace ots
 
 #endif  // OTS_CFF_TYPE2_CHARSTRING_H_
--- a/gfx/ots/src/cmap.cc
+++ b/gfx/ots/src/cmap.cc
@@ -8,26 +8,29 @@
 #include <set>
 #include <utility>
 #include <vector>
 
 #include "maxp.h"
 #include "os2.h"
 
 // cmap - Character To Glyph Index Mapping Table
-// http://www.microsoft.com/opentype/otspec/cmap.htm
+// http://www.microsoft.com/typography/otspec/cmap.htm
+
+#define TABLE_NAME "cmap"
 
 namespace {
 
 struct CMAPSubtableHeader {
   uint16_t platform;
   uint16_t encoding;
   uint32_t offset;
   uint16_t format;
   uint32_t length;
+  uint32_t language;
 };
 
 struct Subtable314Range {
   uint16_t start_range;
   uint16_t end_range;
   int16_t id_delta;
   uint16_t id_range_offset;
   uint32_t id_range_offset_offset;
@@ -62,112 +65,112 @@ bool ParseFormat4(ots::OpenTypeFile *fil
               const uint8_t *data, size_t length, uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // 0.3.4, 3.0.4 or 3.1.4 subtables are complex and, rather than expanding the
   // whole thing and recompacting it, we validate it and include it verbatim
   // in the output.
 
   if (!file->os2) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Required OS/2 table missing");
   }
 
   if (!subtable.Skip(4)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read 4 bytes at start of cmap format 4 subtable");
   }
   uint16_t language = 0;
   if (!subtable.ReadU16(&language)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read language");
   }
   if (language) {
     // Platform ID 3 (windows) subtables should have language '0'.
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Languages should be 0 (%d)", language);
   }
 
   uint16_t segcountx2, search_range, entry_selector, range_shift;
   segcountx2 = search_range = entry_selector = range_shift = 0;
   if (!subtable.ReadU16(&segcountx2) ||
       !subtable.ReadU16(&search_range) ||
       !subtable.ReadU16(&entry_selector) ||
       !subtable.ReadU16(&range_shift)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read subcmap structure");
   }
 
   if (segcountx2 & 1 || search_range & 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad subcmap structure");
   }
   const uint16_t segcount = segcountx2 >> 1;
   // There must be at least one segment according the spec.
   if (segcount < 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Segcount < 1 (%d)", segcount);
   }
 
   // log2segcount is the maximal x s.t. 2^x < segcount
   unsigned log2segcount = 0;
   while (1u << (log2segcount + 1) <= segcount) {
     log2segcount++;
   }
 
   const uint16_t expected_search_range = 2 * 1u << log2segcount;
   if (expected_search_range != search_range) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("expected search range != search range (%d != %d)", expected_search_range, search_range);
   }
 
   if (entry_selector != log2segcount) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("entry selector != log2(segement count) (%d != %d)", entry_selector, log2segcount);
   }
 
   const uint16_t expected_range_shift = segcountx2 - search_range;
   if (range_shift != expected_range_shift) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("unexpected range shift (%d != %d)", range_shift, expected_range_shift);
   }
 
   std::vector<Subtable314Range> ranges(segcount);
 
   for (unsigned i = 0; i < segcount; ++i) {
     if (!subtable.ReadU16(&ranges[i].end_range)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read segment %d", i);
     }
   }
 
   uint16_t padding;
   if (!subtable.ReadU16(&padding)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read cmap subtable segment padding");
   }
   if (padding) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Non zero cmap subtable segment padding (%d)", padding);
   }
 
   for (unsigned i = 0; i < segcount; ++i) {
     if (!subtable.ReadU16(&ranges[i].start_range)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read segment start range %d", i);
     }
   }
   for (unsigned i = 0; i < segcount; ++i) {
     if (!subtable.ReadS16(&ranges[i].id_delta)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read segment delta %d", i);
     }
   }
   for (unsigned i = 0; i < segcount; ++i) {
     ranges[i].id_range_offset_offset = subtable.offset();
     if (!subtable.ReadU16(&ranges[i].id_range_offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read segment range offset %d", i);
     }
 
     if (ranges[i].id_range_offset & 1) {
       // Some font generators seem to put 65535 on id_range_offset
       // for 0xFFFF-0xFFFF range.
       // (e.g., many fonts in http://www.princexml.com/fonts/)
       if (i == segcount - 1u) {
         OTS_WARNING("bad id_range_offset");
         ranges[i].id_range_offset = 0;
         // The id_range_offset value in the transcoded font will not change
         // since this table is not actually "transcoded" yet.
       } else {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad segment offset (%d)", ranges[i].id_range_offset);
       }
     }
   }
 
   // ranges must be ascending order, based on the end_code. Ranges may not
   // overlap.
   for (unsigned i = 1; i < segcount; ++i) {
     if ((i == segcount - 1u) &&
@@ -179,20 +182,20 @@ bool ParseFormat4(ots::OpenTypeFile *fil
       // We'll accept them as an exception.
       OTS_WARNING("multiple 0xffff terminators found");
       continue;
     }
 
     // Note: some Linux fonts (e.g., LucidaSansOblique.ttf, bsmi00lp.ttf) have
     // unsorted table...
     if (ranges[i].end_range <= ranges[i - 1].end_range) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Out of order end range (%d <= %d)", ranges[i].end_range, ranges[i-1].end_range);
     }
     if (ranges[i].start_range <= ranges[i - 1].end_range) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("out of order start range (%d <= %d)", ranges[i].start_range, ranges[i-1].end_range);
     }
 
     // On many fonts, the value of {first, last}_char_index are incorrect.
     // Fix them.
     if (file->os2->first_char_index != 0xFFFF &&
         ranges[i].start_range != 0xFFFF &&
         file->os2->first_char_index > ranges[i].start_range) {
       file->os2->first_char_index = ranges[i].start_range;
@@ -200,48 +203,49 @@ bool ParseFormat4(ots::OpenTypeFile *fil
     if (file->os2->last_char_index != 0xFFFF &&
         ranges[i].end_range != 0xFFFF &&
         file->os2->last_char_index < ranges[i].end_range) {
       file->os2->last_char_index = ranges[i].end_range;
     }
   }
 
   // The last range must end at 0xffff
-  if (ranges[segcount - 1].end_range != 0xffff) {
-    return OTS_FAILURE();
+  if (ranges[segcount - 1].start_range != 0xffff || ranges[segcount - 1].end_range != 0xffff) {
+    return OTS_FAILURE_MSG("Final segment start and end must be 0xFFFF (0x%04X-0x%04X)",
+                           ranges[segcount - 1].start_range, ranges[segcount - 1].end_range);
   }
 
   // A format 4 CMAP subtable is complex. To be safe we simulate a lookup of
   // each code-point defined in the table and make sure that they are all valid
   // glyphs and that we don't access anything out-of-bounds.
   for (unsigned i = 0; i < segcount; ++i) {
     for (unsigned cp = ranges[i].start_range; cp <= ranges[i].end_range; ++cp) {
       const uint16_t code_point = cp;
       if (ranges[i].id_range_offset == 0) {
         // this is explictly allowed to overflow in the spec
         const uint16_t glyph = code_point + ranges[i].id_delta;
         if (glyph >= num_glyphs) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1);
         }
       } else {
         const uint16_t range_delta = code_point - ranges[i].start_range;
         // this might seem odd, but it's true. The offset is relative to the
         // location of the offset value itself.
         const uint32_t glyph_id_offset = ranges[i].id_range_offset_offset +
                                          ranges[i].id_range_offset +
                                          range_delta * 2;
         // We need to be able to access a 16-bit value from this offset
         if (glyph_id_offset + 1 >= length) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("bad glyph id offset (%d > %ld)", glyph_id_offset, length);
         }
         uint16_t glyph;
-        memcpy(&glyph, data + glyph_id_offset, 2);
+        std::memcpy(&glyph, data + glyph_id_offset, 2);
         glyph = ntohs(glyph);
         if (glyph >= num_glyphs) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1);
         }
       }
     }
   }
 
   // We accept the table.
   // TODO(yusukes): transcode the subtable.
   if (platform == 3 && encoding == 0) {
@@ -249,324 +253,329 @@ bool ParseFormat4(ots::OpenTypeFile *fil
     file->cmap->subtable_3_0_4_length = length;
   } else if (platform == 3 && encoding == 1) {
     file->cmap->subtable_3_1_4_data = data;
     file->cmap->subtable_3_1_4_length = length;
   } else if (platform == 0 && encoding == 3) {
     file->cmap->subtable_0_3_4_data = data;
     file->cmap->subtable_0_3_4_length = length;
   } else {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Unknown cmap subtable type (platform=%d, encoding=%d)", platform, encoding);
   }
 
   return true;
 }
 
 bool Parse31012(ots::OpenTypeFile *file,
                 const uint8_t *data, size_t length, uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Format 12 tables are simple. We parse these and fully serialise them
   // later.
 
   if (!subtable.Skip(8)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("failed to skip the first 8 bytes of format 12 subtable");
   }
   uint32_t language = 0;
   if (!subtable.ReadU32(&language)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("can't read format 12 subtable language");
   }
   if (language) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("format 12 subtable language should be zero (%d)", language);
   }
 
   uint32_t num_groups = 0;
   if (!subtable.ReadU32(&num_groups)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("can't read number of format 12 subtable groups");
   }
   if (num_groups == 0 || num_groups > kMaxCMAPGroups) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad format 12 subtable group count %d", num_groups);
   }
 
   std::vector<ots::OpenTypeCMAPSubtableRange> &groups
       = file->cmap->subtable_3_10_12;
   groups.resize(num_groups);
 
   for (unsigned i = 0; i < num_groups; ++i) {
     if (!subtable.ReadU32(&groups[i].start_range) ||
         !subtable.ReadU32(&groups[i].end_range) ||
         !subtable.ReadU32(&groups[i].start_glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("can't read format 12 subtable group");
     }
 
     if (groups[i].start_range > kUnicodeUpperLimit ||
         groups[i].end_range > kUnicodeUpperLimit ||
         groups[i].start_glyph_id > 0xFFFF) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad format 12 subtable group (startCharCode=0x%4X, endCharCode=0x%4X, startGlyphID=%d)",
+                             groups[i].start_range, groups[i].end_range, groups[i].start_glyph_id);
     }
 
     // [0xD800, 0xDFFF] are surrogate code points.
     if (groups[i].start_range >= 0xD800 &&
         groups[i].start_range <= 0xDFFF) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("format 12 subtable out of range group startCharCode (0x%4X)", groups[i].start_range);
     }
     if (groups[i].end_range >= 0xD800 &&
         groups[i].end_range <= 0xDFFF) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("format 12 subtable out of range group endCharCode (0x%4X)", groups[i].end_range);
     }
     if (groups[i].start_range < 0xD800 &&
         groups[i].end_range > 0xDFFF) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad format 12 subtable group startCharCode (0x%4X) or endCharCode (0x%4X)",
+                             groups[i].start_range, groups[i].end_range);
     }
 
     // We assert that the glyph value is within range. Because of the range
     // limits, above, we don't need to worry about overflow.
     if (groups[i].end_range < groups[i].start_range) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("format 12 subtable group endCharCode before startCharCode (0x%4X < 0x%4X)",
+                             groups[i].end_range, groups[i].start_range);
     }
     if ((groups[i].end_range - groups[i].start_range) +
         groups[i].start_glyph_id > num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad format 12 subtable group startGlyphID (%d)", groups[i].start_glyph_id);
     }
   }
 
   // the groups must be sorted by start code and may not overlap
   for (unsigned i = 1; i < num_groups; ++i) {
     if (groups[i].start_range <= groups[i - 1].start_range) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("out of order format 12 subtable group (startCharCode=0x%4X <= startCharCode=0x%4X of previous group)",
+                             groups[i].start_range, groups[i-1].start_range);
     }
     if (groups[i].start_range <= groups[i - 1].end_range) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("overlapping format 12 subtable groups (startCharCode=0x%4X <= endCharCode=0x%4X of previous group)",
+                             groups[i].start_range, groups[i-1].end_range);
     }
   }
 
   return true;
 }
 
 bool Parse31013(ots::OpenTypeFile *file,
                 const uint8_t *data, size_t length, uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Format 13 tables are simple. We parse these and fully serialise them
   // later.
 
   if (!subtable.Skip(8)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad cmap subtable length");
   }
   uint16_t language = 0;
   if (!subtable.ReadU16(&language)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read cmap subtable language");
   }
   if (language) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Cmap subtable language should be zero but is %d", language);
   }
 
   uint32_t num_groups = 0;
   if (!subtable.ReadU32(&num_groups)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read number of groups in a cmap subtable");
   }
 
   // We limit the number of groups in the same way as in 3.10.12 tables. See
   // the comment there in
   if (num_groups == 0 || num_groups > kMaxCMAPGroups) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad number of groups (%d) in a cmap subtable", num_groups);
   }
 
   std::vector<ots::OpenTypeCMAPSubtableRange> &groups
       = file->cmap->subtable_3_10_13;
   groups.resize(num_groups);
 
   for (unsigned i = 0; i < num_groups; ++i) {
     if (!subtable.ReadU32(&groups[i].start_range) ||
         !subtable.ReadU32(&groups[i].end_range) ||
         !subtable.ReadU32(&groups[i].start_glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read subrange structure in a cmap subtable");
     }
 
     // We conservatively limit all of the values to protect some parsers from
     // overflows
     if (groups[i].start_range > kUnicodeUpperLimit ||
         groups[i].end_range > kUnicodeUpperLimit ||
         groups[i].start_glyph_id > 0xFFFF) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad subrange with start_range=%d, end_range=%d, start_glyph_id=%d", groups[i].start_range, groups[i].end_range, groups[i].start_glyph_id);
     }
 
     if (groups[i].start_glyph_id >= num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Subrange starting glyph id too high (%d > %d)", groups[i].start_glyph_id, num_glyphs);
     }
   }
 
   // the groups must be sorted by start code and may not overlap
   for (unsigned i = 1; i < num_groups; ++i) {
     if (groups[i].start_range <= groups[i - 1].start_range) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Overlapping subrange starts (%d >= %d)", groups[i]. start_range, groups[i-1].start_range);
     }
     if (groups[i].start_range <= groups[i - 1].end_range) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Overlapping subranges (%d <= %d)", groups[i].start_range, groups[i-1].end_range);
     }
   }
 
   return true;
 }
 
 bool Parse0514(ots::OpenTypeFile *file,
                const uint8_t *data, size_t length, uint16_t num_glyphs) {
   // Unicode Variation Selector table
   ots::Buffer subtable(data, length);
 
   // Format 14 tables are simple. We parse these and fully serialise them
   // later.
 
   // Skip format (USHORT) and length (ULONG)
   if (!subtable.Skip(6)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read start of cmap subtable");
   }
 
   uint32_t num_records = 0;
   if (!subtable.ReadU32(&num_records)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read number of records in cmap subtable");
   }
   if (num_records == 0 || num_records > kMaxCMAPSelectorRecords) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad number of records (%d) in cmap subtable", num_records);
   }
 
   std::vector<ots::OpenTypeCMAPSubtableVSRecord>& records
       = file->cmap->subtable_0_5_14;
   records.resize(num_records);
 
   for (unsigned i = 0; i < num_records; ++i) {
     if (!subtable.ReadU24(&records[i].var_selector) ||
         !subtable.ReadU32(&records[i].default_offset) ||
         !subtable.ReadU32(&records[i].non_default_offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read record structure of record %d in cmap subtale", i);
     }
     // Checks the value of variation selector
     if (!((records[i].var_selector >= kMongolianVSStart &&
            records[i].var_selector <= kMongolianVSEnd) ||
           (records[i].var_selector >= kVSStart &&
            records[i].var_selector <= kVSEnd) ||
           (records[i].var_selector >= kIVSStart &&
            records[i].var_selector <= kIVSEnd))) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad record variation selector (%04X) in record %i", records[i].var_selector, i);
     }
     if (i > 0 &&
         records[i-1].var_selector >= records[i].var_selector) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Out of order variation selector (%04X >= %04X) in record %d", records[i-1].var_selector, records[i].var_selector, i);
     }
 
     // Checks offsets
     if (!records[i].default_offset && !records[i].non_default_offset) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("No default aoffset in variation selector record %d", i);
     }
     if (records[i].default_offset &&
         records[i].default_offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Default offset too high (%d >= %ld) in record %d", records[i].default_offset, length, i);
     }
     if (records[i].non_default_offset &&
         records[i].non_default_offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Non default offset too high (%d >= %ld) in record %d", records[i].non_default_offset, length, i);
     }
   }
 
   for (unsigned i = 0; i < num_records; ++i) {
     // Checks default UVS table
     if (records[i].default_offset) {
       subtable.set_offset(records[i].default_offset);
       uint32_t num_ranges = 0;
       if (!subtable.ReadU32(&num_ranges)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Can't read number of ranges in record %d", i);
       }
       if (!num_ranges || num_ranges > kMaxCMAPGroups) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("number of ranges too high (%d > %d) in record %d", num_ranges, kMaxCMAPGroups, i);
       }
 
       uint32_t last_unicode_value = 0;
       std::vector<ots::OpenTypeCMAPSubtableVSRange>& ranges
           = records[i].ranges;
       ranges.resize(num_ranges);
 
       for (unsigned j = 0; j < num_ranges; ++j) {
         if (!subtable.ReadU24(&ranges[j].unicode_value) ||
             !subtable.ReadU8(&ranges[j].additional_count)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Can't read range info in variation selector record %d", i);
         }
         const uint32_t check_value =
             ranges[j].unicode_value + ranges[j].additional_count;
         if (ranges[j].unicode_value == 0 ||
             ranges[j].unicode_value > kUnicodeUpperLimit ||
             check_value > kUVSUpperLimit ||
             (last_unicode_value &&
              ranges[j].unicode_value <= last_unicode_value)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Bad Unicode value *%04X) in variation selector range %d record %d", ranges[j].unicode_value, j, i);
         }
         last_unicode_value = check_value;
       }
     }
 
     // Checks non default UVS table
     if (records[i].non_default_offset) {
       subtable.set_offset(records[i].non_default_offset);
       uint32_t num_mappings = 0;
       if (!subtable.ReadU32(&num_mappings)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Can't read number of mappings in variation selector record %d", i);
       }
       if (!num_mappings || num_mappings > kMaxCMAPGroups) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Number of mappings too high (%d) in variation selector record %d", num_mappings, i);
       }
 
       uint32_t last_unicode_value = 0;
       std::vector<ots::OpenTypeCMAPSubtableVSMapping>& mappings
           = records[i].mappings;
       mappings.resize(num_mappings);
 
       for (unsigned j = 0; j < num_mappings; ++j) {
         if (!subtable.ReadU24(&mappings[j].unicode_value) ||
             !subtable.ReadU16(&mappings[j].glyph_id)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Can't read mapping %d in variation selector record %d", j, i);
         }
         if (mappings[j].glyph_id == 0 ||
             mappings[j].unicode_value == 0 ||
             mappings[j].unicode_value > kUnicodeUpperLimit ||
             (last_unicode_value &&
              mappings[j].unicode_value <= last_unicode_value)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Bad mapping (%04X -> %d) in mapping %d of variation selector %d", mappings[j].unicode_value, mappings[j].glyph_id, j, i);
         }
         last_unicode_value = mappings[j].unicode_value;
       }
     }
   }
 
   if (subtable.offset() != length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad subtable offset (%ld != %ld)", subtable.offset(), length);
   }
   file->cmap->subtable_0_5_14_length = subtable.offset();
   return true;
 }
 
 bool Parse100(ots::OpenTypeFile *file, const uint8_t *data, size_t length) {
   // Mac Roman table
   ots::Buffer subtable(data, length);
 
   if (!subtable.Skip(4)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad cmap subtable");
   }
   uint16_t language = 0;
   if (!subtable.ReadU16(&language)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read language in cmap subtable");
   }
   if (language) {
     // simsun.ttf has non-zero language id.
     OTS_WARNING("language id should be zero: %u", language);
   }
 
   file->cmap->subtable_1_0_0.reserve(kFormat0ArraySize);
   for (size_t i = 0; i < kFormat0ArraySize; ++i) {
     uint8_t glyph_id = 0;
     if (!subtable.ReadU8(&glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read glyph id at array[%ld] in cmap subtable", i);
     }
     file->cmap->subtable_1_0_0.push_back(glyph_id);
   }
 
   return true;
 }
 
 }  // namespace
@@ -576,113 +585,131 @@ namespace ots {
 bool ots_cmap_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   file->cmap = new OpenTypeCMAP;
 
   uint16_t version = 0;
   uint16_t num_tables = 0;
   if (!table.ReadU16(&version) ||
       !table.ReadU16(&num_tables)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read structure of cmap");
   }
 
   if (version != 0) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Non zero cmap version (%d)", version);
   }
   if (!num_tables) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("No subtables in cmap!");
   }
 
   std::vector<CMAPSubtableHeader> subtable_headers;
 
   // read the subtable headers
   subtable_headers.reserve(num_tables);
   for (unsigned i = 0; i < num_tables; ++i) {
     CMAPSubtableHeader subt;
 
     if (!table.ReadU16(&subt.platform) ||
         !table.ReadU16(&subt.encoding) ||
         !table.ReadU32(&subt.offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read subtable information cmap subtable %d", i);
     }
 
     subtable_headers.push_back(subt);
   }
 
   const size_t data_offset = table.offset();
 
   // make sure that all the offsets are valid.
-  uint32_t last_id = 0;
   for (unsigned i = 0; i < num_tables; ++i) {
     if (subtable_headers[i].offset > 1024 * 1024 * 1024) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad subtable offset in cmap subtable %d", i);
     }
     if (subtable_headers[i].offset < data_offset ||
         subtable_headers[i].offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad subtable offset (%d) in cmap subtable %d", subtable_headers[i].offset, i);
     }
-
-    // check if the table is sorted first by platform ID, then by encoding ID.
-    uint32_t current_id
-        = (subtable_headers[i].platform << 16) + subtable_headers[i].encoding;
-    if ((i != 0) && (last_id >= current_id)) {
-      return OTS_FAILURE();
-    }
-    last_id = current_id;
   }
 
   // the format of the table is the first couple of bytes in the table. The
   // length of the table is stored in a format-specific way.
   for (unsigned i = 0; i < num_tables; ++i) {
     table.set_offset(subtable_headers[i].offset);
     if (!table.ReadU16(&subtable_headers[i].format)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read cmap subtable header format %d", i);
     }
 
     uint16_t len = 0;
+    uint16_t lang = 0;
     switch (subtable_headers[i].format) {
       case 0:
       case 4:
         if (!table.ReadU16(&len)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Can't read cmap subtable %d length", i);
+        }
+        if (!table.ReadU16(&lang)) {
+          return OTS_FAILURE_MSG("Can't read cmap subtable %d language", i);
         }
         subtable_headers[i].length = len;
+        subtable_headers[i].language = lang;
         break;
       case 12:
       case 13:
         if (!table.Skip(2)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Bad cmap subtable %d structure", i);
         }
         if (!table.ReadU32(&subtable_headers[i].length)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Can read cmap subtable %d length", i);
+        }
+        if (!table.ReadU32(&subtable_headers[i].language)) {
+          return OTS_FAILURE_MSG("Can't read cmap subtable %d language", i);
         }
         break;
       case 14:
         if (!table.ReadU32(&subtable_headers[i].length)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Can't read cmap subtable %d length", i);
         }
+        subtable_headers[i].language = 0;
         break;
       default:
         subtable_headers[i].length = 0;
+        subtable_headers[i].language = 0;
         break;
     }
   }
 
+  // check if the table is sorted first by platform ID, then by encoding ID.
+  uint32_t last_id = 0;
+  for (unsigned i = 0; i < num_tables; ++i) {
+    uint32_t current_id
+        = (subtable_headers[i].platform << 24)
+        + (subtable_headers[i].encoding << 16)
+        + subtable_headers[i].language;
+    if ((i != 0) && (last_id >= current_id)) {
+      return OTS_FAILURE_MSG("subtable %d with platform ID %d, encoding ID %d, language ID %d "
+                             "following subtable with platform ID %d, encoding ID %d, language ID %d",
+                             i,
+                             (uint8_t)(current_id >> 24), (uint8_t)(current_id >> 16), (uint8_t)(current_id),
+                             (uint8_t)(last_id >> 24), (uint8_t)(last_id >> 16), (uint8_t)(last_id));
+    }
+    last_id = current_id;
+  }
+
   // Now, verify that all the lengths are sane
   for (unsigned i = 0; i < num_tables; ++i) {
     if (!subtable_headers[i].length) continue;
     if (subtable_headers[i].length > 1024 * 1024 * 1024) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad cmap subtable %d length", i);
     }
     // We know that both the offset and length are < 1GB, so the following
     // addition doesn't overflow
     const uint32_t end_byte
         = subtable_headers[i].offset + subtable_headers[i].length;
     if (end_byte > length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Over long cmap subtable %d @ %d for %d", i, subtable_headers[i].offset, subtable_headers[i].length);
     }
   }
 
   // check that the cmap subtables are not overlapping.
   std::set<std::pair<uint32_t, uint32_t> > uniq_checker;
   std::vector<std::pair<uint32_t, uint8_t> > overlap_checker;
   for (unsigned i = 0; i < num_tables; ++i) {
     const uint32_t end_byte
@@ -700,24 +727,24 @@ bool ots_cmap_parse(OpenTypeFile *file, 
     overlap_checker.push_back(
         std::make_pair(end_byte, static_cast<uint8_t>(0) /* end */));
   }
   std::sort(overlap_checker.begin(), overlap_checker.end());
   int overlap_count = 0;
   for (unsigned i = 0; i < overlap_checker.size(); ++i) {
     overlap_count += (overlap_checker[i].second ? 1 : -1);
     if (overlap_count > 1) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Excessive overlap count %d", overlap_count);
     }
   }
 
   // we grab the number of glyphs in the file from the maxp table to make sure
   // that the character map isn't referencing anything beyound this range.
   if (!file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("No maxp table in font! Needed by cmap.");
   }
   const uint16_t num_glyphs = file->maxp->num_glyphs;
 
   // We only support a subset of the possible character map tables. Microsoft
   // 'strongly recommends' that everyone supports the Unicode BMP table with
   // the UCS-4 table for non-BMP glyphs. We'll pass the following subtables:
   //   Platform ID   Encoding ID  Format
   //   0             0            4       (Unicode Default)
@@ -747,37 +774,37 @@ bool ots_cmap_parse(OpenTypeFile *file, 
       if ((subtable_headers[i].encoding == 0) &&
           (subtable_headers[i].format == 4)) {
         // parse and output the 0-0-4 table as 3-1-4 table. Sometimes the 0-0-4
         // table actually points to MS symbol data and thus should be parsed as
         // 3-0-4 table (e.g., marqueem.ttf and quixotic.ttf). This error will be
         // recovered in ots_cmap_serialise().
         if (!ParseFormat4(file, 3, 1, data + subtable_headers[i].offset,
                       subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Failed to parse format 4 cmap subtable %d", i);
         }
       } else if ((subtable_headers[i].encoding == 3) &&
                  (subtable_headers[i].format == 4)) {
         // parse and output the 0-3-4 table as 0-3-4 table.
         if (!ParseFormat4(file, 0, 3, data + subtable_headers[i].offset,
                       subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Failed to parse format 4 cmap subtable %d", i);
         }
       } else if ((subtable_headers[i].encoding == 3) &&
                  (subtable_headers[i].format == 12)) {
         // parse and output the 0-3-12 table as 3-10-12 table.
         if (!Parse31012(file, data + subtable_headers[i].offset,
                         subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Failed to parse format 12 cmap subtable %d", i);
         }
       } else if ((subtable_headers[i].encoding == 5) &&
                  (subtable_headers[i].format == 14)) {
         if (!Parse0514(file, data + subtable_headers[i].offset,
                        subtable_headers[i].length, num_glyphs)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Failed to parse format 14 cmap subtable %d", i);
         }
       }
     } else if (subtable_headers[i].platform == 1) {
       // Mac platform
 
       if ((subtable_headers[i].encoding == 0) &&
           (subtable_headers[i].format == 0)) {
         // parse and output the 1-0-0 table.
--- a/gfx/ots/src/cvt.cc
+++ b/gfx/ots/src/cvt.cc
@@ -1,35 +1,37 @@
 // 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.
 
 #include "cvt.h"
 
 // cvt - Control Value Table
-// http://www.microsoft.com/opentype/otspec/cvt.htm
+// http://www.microsoft.com/typography/otspec/cvt.htm
+
+#define TABLE_NAME "cvt"
 
 namespace ots {
 
 bool ots_cvt_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypeCVT *cvt = new OpenTypeCVT;
   file->cvt = cvt;
 
   if (length >= 128 * 1024u) {
-    return OTS_FAILURE();  // almost all cvt tables are less than 4k bytes.
+    return OTS_FAILURE_MSG("Length (%d) > 120K");  // almost all cvt tables are less than 4k bytes.
   }
 
   if (length % 2 != 0) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Uneven cvt length (%d)", length);
   }
 
   if (!table.Skip(length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Length too high");
   }
 
   cvt->data = data;
   cvt->length = length;
   return true;
 }
 
 bool ots_cvt_should_serialise(OpenTypeFile *file) {
@@ -38,17 +40,17 @@ bool ots_cvt_should_serialise(OpenTypeFi
   }
   return g_transcode_hints && file->cvt;
 }
 
 bool ots_cvt_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeCVT *cvt = file->cvt;
 
   if (!out->Write(cvt->data, cvt->length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write CVT table");
   }
 
   return true;
 }
 
 void ots_cvt_free(OpenTypeFile *file) {
   delete file->cvt;
 }
--- a/gfx/ots/src/fpgm.cc
+++ b/gfx/ots/src/fpgm.cc
@@ -1,48 +1,50 @@
 // 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.
 
 #include "fpgm.h"
 
 // fpgm - Font Program
-// http://www.microsoft.com/opentype/otspec/fpgm.htm
+// http://www.microsoft.com/typography/otspec/fpgm.htm
+
+#define TABLE_NAME "fpgm"
 
 namespace ots {
 
 bool ots_fpgm_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypeFPGM *fpgm = new OpenTypeFPGM;
   file->fpgm = fpgm;
 
   if (length >= 128 * 1024u) {
-    return OTS_FAILURE();  // almost all fpgm tables are less than 5k bytes.
+    return OTS_FAILURE_MSG("length (%ld) > 120", length);  // almost all fpgm tables are less than 5k bytes.
   }
 
   if (!table.Skip(length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad fpgm length");
   }
 
   fpgm->data = data;
   fpgm->length = length;
   return true;
 }
 
 bool ots_fpgm_should_serialise(OpenTypeFile *file) {
   if (!file->glyf) return false;  // this table is not for CFF fonts.
   return g_transcode_hints && file->fpgm;
 }
 
 bool ots_fpgm_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeFPGM *fpgm = file->fpgm;
 
   if (!out->Write(fpgm->data, fpgm->length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write fpgm");
   }
 
   return true;
 }
 
 void ots_fpgm_free(OpenTypeFile *file) {
   delete file->fpgm;
 }
--- a/gfx/ots/src/gasp.cc
+++ b/gfx/ots/src/gasp.cc
@@ -1,32 +1,38 @@
 // 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.
 
 #include "gasp.h"
 
 // gasp - Grid-fitting And Scan-conversion Procedure
-// http://www.microsoft.com/opentype/otspec/gasp.htm
+// http://www.microsoft.com/typography/otspec/gasp.htm
+
+#define TABLE_NAME "gasp"
 
 #define DROP_THIS_TABLE \
-  do { delete file->gasp; file->gasp = 0; } while (0)
+  do { \
+    delete file->gasp; \
+    file->gasp = 0; \
+    OTS_FAILURE_MSG("Table discarded"); \
+  } while (0)
 
 namespace ots {
 
 bool ots_gasp_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypeGASP *gasp = new OpenTypeGASP;
   file->gasp = gasp;
 
   uint16_t num_ranges = 0;
   if (!table.ReadU16(&gasp->version) ||
       !table.ReadU16(&num_ranges)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read table header");
   }
 
   if (gasp->version > 1) {
     // Lots of Linux fonts have bad version numbers...
     OTS_WARNING("bad version: %u", gasp->version);
     DROP_THIS_TABLE;
     return true;
   }
@@ -38,17 +44,17 @@ bool ots_gasp_parse(OpenTypeFile *file, 
   }
 
   gasp->gasp_ranges.reserve(num_ranges);
   for (unsigned i = 0; i < num_ranges; ++i) {
     uint16_t max_ppem = 0;
     uint16_t behavior = 0;
     if (!table.ReadU16(&max_ppem) ||
         !table.ReadU16(&behavior)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read subrange %d", i);
     }
     if ((i > 0) && (gasp->gasp_ranges[i - 1].first >= max_ppem)) {
       // The records in the gaspRange[] array must be sorted in order of
       // increasing rangeMaxPPEM value.
       OTS_WARNING("ranges are not sorted");
       DROP_THIS_TABLE;
       return true;
     }
@@ -81,23 +87,23 @@ bool ots_gasp_should_serialise(OpenTypeF
   return file->gasp != NULL;
 }
 
 bool ots_gasp_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeGASP *gasp = file->gasp;
 
   if (!out->WriteU16(gasp->version) ||
       !out->WriteU16(gasp->gasp_ranges.size())) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("failed to write gasp header");
   }
 
   for (unsigned i = 0; i < gasp->gasp_ranges.size(); ++i) {
     if (!out->WriteU16(gasp->gasp_ranges[i].first) ||
         !out->WriteU16(gasp->gasp_ranges[i].second)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write gasp subtable %d", i);
     }
   }
 
   return true;
 }
 
 void ots_gasp_free(OpenTypeFile *file) {
   delete file->gasp;
--- a/gfx/ots/src/gasp.h
+++ b/gfx/ots/src/gasp.h
@@ -1,17 +1,17 @@
 // 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.
 
 #ifndef OTS_GASP_H_
 #define OTS_GASP_H_
 
 #include <new>
-#include <utility>  // std::pair
+#include <utility>
 #include <vector>
 
 #include "ots.h"
 
 namespace ots {
 
 struct OpenTypeGASP {
   uint16_t version;
--- a/gfx/ots/src/gdef.cc
+++ b/gfx/ots/src/gdef.cc
@@ -25,391 +25,359 @@ const uint16_t kMaxClassDefValue = 0xFFF
 const uint16_t kMaxGlyphClassDefValue = 4;
 // The maximum format number of caret value tables.
 // We don't support format 3 for now. See the comment in
 // ParseLigCaretListTable() for the reason.
 const uint16_t kMaxCaretValueFormat = 2;
 
 bool ParseGlyphClassDefTable(ots::OpenTypeFile *file, const uint8_t *data,
                              size_t length, const uint16_t num_glyphs) {
-  return ots::ParseClassDefTable(data, length, num_glyphs,
+  return ots::ParseClassDefTable(file, data, length, num_glyphs,
                                  kMaxGlyphClassDefValue);
 }
 
 bool ParseAttachListTable(ots::OpenTypeFile *file, const uint8_t *data,
                           size_t length, const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_coverage = 0;
   uint16_t glyph_count = 0;
   if (!subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read gdef header");
   }
   const unsigned attach_points_end =
       2 * static_cast<unsigned>(glyph_count) + 4;
   if (attach_points_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad glyph count in gdef");
   }
   if (offset_coverage == 0 || offset_coverage >= length ||
       offset_coverage < attach_points_end) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
   }
   if (glyph_count > num_glyphs) {
-    OTS_WARNING("bad glyph count: %u", glyph_count);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad glyph count %u", glyph_count);
   }
 
   std::vector<uint16_t> attach_points;
   attach_points.resize(glyph_count);
   for (unsigned i = 0; i < glyph_count; ++i) {
     if (!subtable.ReadU16(&attach_points[i])) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read attachment point %d", i);
     }
     if (attach_points[i] >= length ||
         attach_points[i] < attach_points_end) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad attachment point %d of %d", i, attach_points[i]);
     }
   }
 
   // Parse coverage table
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage table");
   }
 
   // Parse attach point table
   for (unsigned i = 0; i < attach_points.size(); ++i) {
     subtable.set_offset(attach_points[i]);
     uint16_t point_count = 0;
     if (!subtable.ReadU16(&point_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read point count %d", i);
     }
     if (point_count == 0) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("zero point count %d", i);
     }
     uint16_t last_point_index = 0;
     uint16_t point_index = 0;
     for (unsigned j = 0; j < point_count; ++j) {
       if (!subtable.ReadU16(&point_index)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Can't read point index %d in point %d", j, i);
       }
       // Contour point indeces are in increasing numerical order
       if (last_point_index != 0 && last_point_index >= point_index) {
-        OTS_WARNING("bad contour indeces: %u >= %u",
+        return OTS_FAILURE_MSG("bad contour indeces: %u >= %u",
                     last_point_index, point_index);
-        return OTS_FAILURE();
       }
       last_point_index = point_index;
     }
   }
   return true;
 }
 
 bool ParseLigCaretListTable(ots::OpenTypeFile *file, const uint8_t *data,
                             size_t length, const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
   uint16_t offset_coverage = 0;
   uint16_t lig_glyph_count = 0;
   if (!subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&lig_glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read caret structure");
   }
   const unsigned lig_glyphs_end =
       2 * static_cast<unsigned>(lig_glyph_count) + 4;
   if (lig_glyphs_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad caret structure");
   }
   if (offset_coverage == 0 || offset_coverage >= length ||
       offset_coverage < lig_glyphs_end) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad caret coverate offset %d", offset_coverage);
   }
   if (lig_glyph_count > num_glyphs) {
-    OTS_WARNING("bad ligature glyph count: %u", lig_glyph_count);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad ligature glyph count: %u", lig_glyph_count);
   }
 
   std::vector<uint16_t> lig_glyphs;
   lig_glyphs.resize(lig_glyph_count);
   for (unsigned i = 0; i < lig_glyph_count; ++i) {
     if (!subtable.ReadU16(&lig_glyphs[i])) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read ligature glyph location %d", i);
     }
     if (lig_glyphs[i] >= length || lig_glyphs[i] < lig_glyphs_end) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad ligature glyph location %d in glyph %d", lig_glyphs[i], i);
     }
   }
 
   // Parse coverage table
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't parse caret coverage table");
   }
 
   // Parse ligature glyph table
   for (unsigned i = 0; i < lig_glyphs.size(); ++i) {
     subtable.set_offset(lig_glyphs[i]);
     uint16_t caret_count = 0;
     if (!subtable.ReadU16(&caret_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read caret count for glyph %d", i);
     }
     if (caret_count == 0) {
-      OTS_WARNING("bad caret value count: %u", caret_count);
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad caret value count: %u", caret_count);
     }
 
-    std::vector<uint16_t> caret_values;
-    caret_values.resize(caret_count);
-    uint16_t last_offset_caret = 0;
-    unsigned caret_values_end = 2 * static_cast<unsigned>(caret_count) + 2;
+    std::vector<uint16_t> caret_value_offsets;
+    caret_value_offsets.resize(caret_count);
+    unsigned caret_value_offsets_end = 2 * static_cast<unsigned>(caret_count) + 2;
     for (unsigned j = 0; j < caret_count; ++j) {
-      if (!subtable.ReadU16(&caret_values[j])) {
-        return OTS_FAILURE();
+      if (!subtable.ReadU16(&caret_value_offsets[j])) {
+        return OTS_FAILURE_MSG("Can't read caret offset %d for glyph %d", j, i);
       }
-      if (caret_values[j] >= length || caret_values[j] < caret_values_end) {
-        return OTS_FAILURE();
+      if (caret_value_offsets[j] >= length || caret_value_offsets[j] < caret_value_offsets_end) {
+        return OTS_FAILURE_MSG("Bad caret offset %d for caret %d glyph %d", caret_value_offsets[j], j, i);
       }
-      // Caret offsets are in increasing coordinate order
-      if (last_offset_caret != 0 && last_offset_caret >= caret_values[j]) {
-        OTS_WARNING("offset isn't in increasing coordinate order: %u >= %u",
-                    last_offset_caret, caret_values[j]);
-        return OTS_FAILURE();
-      }
-      last_offset_caret = caret_values[j];
     }
 
     // Parse caret values table
     for (unsigned j = 0; j < caret_count; ++j) {
-      subtable.set_offset(lig_glyphs[i] + caret_values[j]);
+      subtable.set_offset(lig_glyphs[i] + caret_value_offsets[j]);
       uint16_t caret_format = 0;
       if (!subtable.ReadU16(&caret_format)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Can't read caret values table %d in glyph %d", j, i);
       }
       // TODO(bashi): We only support caret value format 1 and 2 for now
       // because there are no fonts which contain caret value format 3
       // as far as we investigated.
       if (caret_format == 0 || caret_format > kMaxCaretValueFormat) {
-        OTS_WARNING("bad caret value format: %u", caret_format);
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("bad caret value format: %u", caret_format);
       }
       // CaretValueFormats contain a 2-byte field which could be
       // arbitrary value.
       if (!subtable.Skip(2)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad caret value table structure %d in glyph %d", j, i);
       }
     }
   }
   return true;
 }
 
 bool ParseMarkAttachClassDefTable(ots::OpenTypeFile *file, const uint8_t *data,
                                   size_t length, const uint16_t num_glyphs) {
-  return ots::ParseClassDefTable(data, length, num_glyphs, kMaxClassDefValue);
+  return ots::ParseClassDefTable(file, data, length, num_glyphs, kMaxClassDefValue);
 }
 
 bool ParseMarkGlyphSetsDefTable(ots::OpenTypeFile *file, const uint8_t *data,
                                 size_t length, const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
   uint16_t format = 0;
   uint16_t mark_set_count = 0;
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&mark_set_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can' read mark glyph table structure");
   }
   if (format != 1) {
-    OTS_WARNING("bad mark glyph set table format: %u", format);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad mark glyph set table format: %u", format);
   }
 
   const unsigned mark_sets_end = 2 * static_cast<unsigned>(mark_set_count) + 4;
   if (mark_sets_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad mark_set %d", mark_sets_end);
   }
   for (unsigned i = 0; i < mark_set_count; ++i) {
     uint32_t offset_coverage = 0;
     if (!subtable.ReadU32(&offset_coverage)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read covrage location for mark set %d", i);
     }
     if (offset_coverage >= length ||
         offset_coverage < mark_sets_end) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad coverage location %d for mark set %d", offset_coverage, i);
     }
-    if (!ots::ParseCoverageTable(data + offset_coverage,
+    if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                  length - offset_coverage, num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse coverage table for mark set %d", i);
     }
   }
   file->gdef->num_mark_glyph_sets = mark_set_count;
   return true;
 }
 
 }  // namespace
 
-#define DROP_THIS_TABLE \
+#define DROP_THIS_TABLE(msg_) \
   do { \
     file->gdef->data = 0; \
     file->gdef->length = 0; \
-    OTS_FAILURE_MSG("OpenType layout data discarded"); \
+    OTS_FAILURE_MSG(msg_ ", table discarded"); \
   } while (0)
 
 namespace ots {
 
 bool ots_gdef_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   // Grab the number of glyphs in the file from the maxp table to check
   // GlyphIDs in GDEF table.
   if (!file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("No maxp table in font, needed by GDEF");
   }
   const uint16_t num_glyphs = file->maxp->num_glyphs;
 
   Buffer table(data, length);
 
   OpenTypeGDEF *gdef = new OpenTypeGDEF;
   file->gdef = gdef;
 
   uint32_t version = 0;
   if (!table.ReadU32(&version)) {
-    OTS_WARNING("incomplete GDEF table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Incomplete table");
     return true;
   }
   if (version < 0x00010000 || version == 0x00010001) {
-    OTS_WARNING("bad GDEF version");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Bad version");
     return true;
   }
 
   if (version >= 0x00010002) {
     gdef->version_2 = true;
   }
 
   uint16_t offset_glyph_class_def = 0;
   uint16_t offset_attach_list = 0;
   uint16_t offset_lig_caret_list = 0;
   uint16_t offset_mark_attach_class_def = 0;
   if (!table.ReadU16(&offset_glyph_class_def) ||
       !table.ReadU16(&offset_attach_list) ||
       !table.ReadU16(&offset_lig_caret_list) ||
       !table.ReadU16(&offset_mark_attach_class_def)) {
-    OTS_WARNING("incomplete GDEF table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Incomplete table");
     return true;
   }
   uint16_t offset_mark_glyph_sets_def = 0;
   if (gdef->version_2) {
     if (!table.ReadU16(&offset_mark_glyph_sets_def)) {
-      OTS_WARNING("incomplete GDEF table");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Incomplete table");
       return true;
     }
   }
 
-  unsigned gdef_header_end = 8;
+  unsigned gdef_header_end = 4 + 4 * 2;
   if (gdef->version_2)
     gdef_header_end += 2;
 
   // Parse subtables
   if (offset_glyph_class_def) {
     if (offset_glyph_class_def >= length ||
         offset_glyph_class_def < gdef_header_end) {
-      OTS_WARNING("invalid offset to glyph classes");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid offset to glyph classes");
       return true;
     }
     if (!ParseGlyphClassDefTable(file, data + offset_glyph_class_def,
                                  length - offset_glyph_class_def,
                                  num_glyphs)) {
-      OTS_WARNING("invalid glyph classes");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid glyph classes");
       return true;
     }
     gdef->has_glyph_class_def = true;
   }
 
   if (offset_attach_list) {
     if (offset_attach_list >= length ||
         offset_attach_list < gdef_header_end) {
-      OTS_WARNING("invalid offset to attachment list");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid offset to attachment list");
       return true;
     }
     if (!ParseAttachListTable(file, data + offset_attach_list,
                               length - offset_attach_list,
                               num_glyphs)) {
-      OTS_WARNING("invalid attachment list");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid attachment list");
       return true;
     }
   }
 
   if (offset_lig_caret_list) {
     if (offset_lig_caret_list >= length ||
         offset_lig_caret_list < gdef_header_end) {
-      OTS_WARNING("invalid offset to lig-caret list");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid offset to ligature caret list");
       return true;
     }
     if (!ParseLigCaretListTable(file, data + offset_lig_caret_list,
                               length - offset_lig_caret_list,
                               num_glyphs)) {
-      OTS_WARNING("invalid ligature caret list");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid ligature caret list");
       return true;
     }
   }
 
   if (offset_mark_attach_class_def) {
     if (offset_mark_attach_class_def >= length ||
         offset_mark_attach_class_def < gdef_header_end) {
-      OTS_WARNING("invalid offset to mark attachment list");
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Invalid offset to mark attachment list");
     }
     if (!ParseMarkAttachClassDefTable(file,
                                       data + offset_mark_attach_class_def,
                                       length - offset_mark_attach_class_def,
                                       num_glyphs)) {
-      OTS_WARNING("invalid mark attachment list");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid mark attachment list");
       return true;
     }
     gdef->has_mark_attachment_class_def = true;
   }
 
   if (offset_mark_glyph_sets_def) {
     if (offset_mark_glyph_sets_def >= length ||
         offset_mark_glyph_sets_def < gdef_header_end) {
-      OTS_WARNING("invalid offset to mark glyph sets");
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("invalid offset to mark glyph sets");
     }
     if (!ParseMarkGlyphSetsDefTable(file,
                                     data + offset_mark_glyph_sets_def,
                                     length - offset_mark_glyph_sets_def,
                                     num_glyphs)) {
-      OTS_WARNING("invalid mark glyph sets");
-      DROP_THIS_TABLE;
+      DROP_THIS_TABLE("Invalid mark glyph sets");
       return true;
     }
     gdef->has_mark_glyph_sets_def = true;
   }
   gdef->data = data;
   gdef->length = length;
   return true;
 }
 
 bool ots_gdef_should_serialise(OpenTypeFile *file) {
-  const bool needed_tables_dropped =
-      (file->gsub && file->gsub->data == NULL) ||
-      (file->gpos && file->gpos->data == NULL);
-  return file->gdef != NULL && file->gdef->data != NULL &&
-      !needed_tables_dropped;
+  return file->gdef != NULL && file->gdef->data != NULL;
 }
 
 bool ots_gdef_serialise(OTSStream *out, OpenTypeFile *file) {
   if (!out->Write(file->gdef->data, file->gdef->length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write GDEF table");
   }
 
   return true;
 }
 
 void ots_gdef_free(OpenTypeFile *file) {
   delete file->gdef;
 }
--- a/gfx/ots/src/glyf.cc
+++ b/gfx/ots/src/glyf.cc
@@ -7,29 +7,32 @@
 #include <algorithm>
 #include <limits>
 
 #include "head.h"
 #include "loca.h"
 #include "maxp.h"
 
 // glyf - Glyph Data
-// http://www.microsoft.com/opentype/otspec/glyf.htm
+// http://www.microsoft.com/typography/otspec/glyf.htm
+
+#define TABLE_NAME "glyf"
 
 namespace {
 
-bool ParseFlagsForSimpleGlyph(ots::Buffer *table,
+bool ParseFlagsForSimpleGlyph(ots::OpenTypeFile *file,
+                              ots::Buffer *table,
                               uint32_t gly_length,
                               uint32_t num_flags,
                               uint32_t *flags_count_logical,
                               uint32_t *flags_count_physical,
                               uint32_t *xy_coordinates_length) {
   uint8_t flag = 0;
   if (!table->ReadU8(&flag)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read flag");
   }
 
   uint32_t delta = 0;
   if (flag & (1u << 1)) {  // x-Short
     ++delta;
   } else if (!(flag & (1u << 4))) {
     delta += 2;
   }
@@ -37,81 +40,81 @@ bool ParseFlagsForSimpleGlyph(ots::Buffe
   if (flag & (1u << 2)) {  // y-Short
     ++delta;
   } else if (!(flag & (1u << 5))) {
     delta += 2;
   }
 
   if (flag & (1u << 3)) {  // repeat
     if (*flags_count_logical + 1 >= num_flags) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Count too high (%d + 1 >= %d)", *flags_count_logical, num_flags);
     }
     uint8_t repeat = 0;
     if (!table->ReadU8(&repeat)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read repeat value");
     }
     if (repeat == 0) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Zero repeat");
     }
     delta += (delta * repeat);
 
     *flags_count_logical += repeat;
     if (*flags_count_logical >= num_flags) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Count too high (%d >= %d)", *flags_count_logical, num_flags);
     }
     ++(*flags_count_physical);
   }
 
   if ((flag & (1u << 6)) || (flag & (1u << 7))) {  // reserved flags
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad flag value (%d)", flag);
   }
 
   *xy_coordinates_length += delta;
   if (gly_length < *xy_coordinates_length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Glyph coordinates length too low (%d < %d)", gly_length, *xy_coordinates_length);
   }
 
   return true;
 }
 
 bool ParseSimpleGlyph(ots::OpenTypeFile *file, const uint8_t *data,
                       ots::Buffer *table, int16_t num_contours,
                       uint32_t gly_offset, uint32_t gly_length,
                       uint32_t *new_size) {
   ots::OpenTypeGLYF *glyf = file->glyf;
 
   // read the end-points array
   uint16_t num_flags = 0;
   for (int i = 0; i < num_contours; ++i) {
     uint16_t tmp_index = 0;
     if (!table->ReadU16(&tmp_index)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read contour index %d", i);
     }
     if (tmp_index == 0xffffu) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad contour index %d", i);
     }
     // check if the indices are monotonically increasing
     if (i && (tmp_index + 1 <= num_flags)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Decreasing contour index %d + 1 <= %d", tmp_index, num_flags);
     }
     num_flags = tmp_index + 1;
   }
 
   uint16_t bytecode_length = 0;
   if (!table->ReadU16(&bytecode_length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read bytecode length");
   }
   if ((file->maxp->version_1) &&
       (file->maxp->max_size_glyf_instructions < bytecode_length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bytecode length too high %d", bytecode_length);
   }
 
   const uint32_t gly_header_length = 10 + num_contours * 2 + 2;
   if (gly_length < (gly_header_length + bytecode_length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Glyph header length too high %d", gly_header_length);
   }
 
   if (ots::g_transcode_hints) {
     glyf->iov.push_back(std::make_pair(
         data + gly_offset,
         static_cast<size_t>(gly_header_length + bytecode_length)));
   } else {
     // enqueue two vectors: the glyph data up to the bytecode length, then
@@ -119,44 +122,45 @@ bool ParseSimpleGlyph(ots::OpenTypeFile 
     glyf->iov.push_back(std::make_pair(
         data + gly_offset,
         static_cast<size_t>(gly_header_length - 2)));
     glyf->iov.push_back(std::make_pair((const uint8_t*) "\x00\x00",
                                        static_cast<size_t>(2)));
   }
 
   if (!table->Skip(bytecode_length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't skip bytecode of length %d", bytecode_length);
   }
 
   uint32_t flags_count_physical = 0;  // on memory
   uint32_t xy_coordinates_length = 0;
   for (uint32_t flags_count_logical = 0;
        flags_count_logical < num_flags;
        ++flags_count_logical, ++flags_count_physical) {
-    if (!ParseFlagsForSimpleGlyph(table,
+    if (!ParseFlagsForSimpleGlyph(file,
+                                  table,
                                   gly_length,
                                   num_flags,
                                   &flags_count_logical,
                                   &flags_count_physical,
                                   &xy_coordinates_length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse glyph flags %d", flags_count_logical);
     }
   }
 
   if (gly_length < (gly_header_length + bytecode_length +
                     flags_count_physical + xy_coordinates_length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Glyph too short %d", gly_length);
   }
 
   if (gly_length - (gly_header_length + bytecode_length +
                     flags_count_physical + xy_coordinates_length) > 3) {
     // We allow 0-3 bytes difference since gly_length is 4-bytes aligned,
     // zero-padded length.
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Invalid glyph length %d", gly_length);
   }
 
   glyf->iov.push_back(std::make_pair(
       data + gly_offset + gly_header_length + bytecode_length,
       static_cast<size_t>(flags_count_physical + xy_coordinates_length)));
 
   *new_size
       = gly_header_length + flags_count_physical + xy_coordinates_length;
@@ -170,88 +174,88 @@ bool ParseSimpleGlyph(ots::OpenTypeFile 
 }  // namespace
 
 namespace ots {
 
 bool ots_glyf_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   if (!file->maxp || !file->loca || !file->head) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing maxp or loca or head table needed by glyf table");
   }
 
   OpenTypeGLYF *glyf = new OpenTypeGLYF;
   file->glyf = glyf;
 
   const unsigned num_glyphs = file->maxp->num_glyphs;
   std::vector<uint32_t> &offsets = file->loca->offsets;
 
   if (offsets.size() != num_glyphs + 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Invalide glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1);
   }
 
   std::vector<uint32_t> resulting_offsets(num_glyphs + 1);
   uint32_t current_offset = 0;
 
   for (unsigned i = 0; i < num_glyphs; ++i) {
     const unsigned gly_offset = offsets[i];
     // The LOCA parser checks that these values are monotonic
     const unsigned gly_length = offsets[i + 1] - offsets[i];
     if (!gly_length) {
       // this glyph has no outline (e.g. the space charactor)
       resulting_offsets[i] = current_offset;
       continue;
     }
 
     if (gly_offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Glyph %d offset %d too high %ld", i, gly_offset, length);
     }
     // Since these are unsigned types, the compiler is not allowed to assume
     // that they never overflow.
     if (gly_offset + gly_length < gly_offset) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Glyph %d length (%d < 0)!", i, gly_length);
     }
     if (gly_offset + gly_length > length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Glyph %d length %d too high", i, gly_length);
     }
 
     table.set_offset(gly_offset);
     int16_t num_contours, xmin, ymin, xmax, ymax;
     if (!table.ReadS16(&num_contours) ||
         !table.ReadS16(&xmin) ||
         !table.ReadS16(&ymin) ||
         !table.ReadS16(&xmax) ||
         !table.ReadS16(&ymax)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read glyph %d header", i);
     }
 
     if (num_contours <= -2) {
       // -2, -3, -4, ... are reserved for future use.
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad number of contours %d in glyph %d", num_contours, i);
     }
 
     // workaround for fonts in http://www.princexml.com/fonts/
     if ((xmin == 32767) &&
         (xmax == -32767) &&
         (ymin == 32767) &&
         (ymax == -32767)) {
       OTS_WARNING("bad xmin/xmax/ymin/ymax values");
       xmin = xmax = ymin = ymax = 0;
     }
 
     if (xmin > xmax || ymin > ymax) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad bounding box values bl=(%d, %d), tr=(%d, %d) in glyph %d", xmin, ymin, xmax, ymax, i);
     }
 
     unsigned new_size = 0;
     if (num_contours >= 0) {
       // this is a simple glyph and might contain bytecode
       if (!ParseSimpleGlyph(file, data, &table,
                             num_contours, gly_offset, gly_length, &new_size)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse glyph %d", i);
       }
     } else {
       // it's a composite glyph without any bytecode. Enqueue the whole thing
       glyf->iov.push_back(std::make_pair(data + gly_offset,
                                          static_cast<size_t>(gly_length)));
       new_size = gly_length;
     }
 
@@ -286,17 +290,17 @@ bool ots_glyf_should_serialise(OpenTypeF
   return file->glyf != NULL;
 }
 
 bool ots_glyf_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeGLYF *glyf = file->glyf;
 
   for (unsigned i = 0; i < glyf->iov.size(); ++i) {
     if (!out->Write(glyf->iov[i].first, glyf->iov[i].second)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Falied to write glyph %d", i);
     }
   }
 
   return true;
 }
 
 void ots_glyf_free(OpenTypeFile *file) {
   delete file->glyf;
--- a/gfx/ots/src/glyf.h
+++ b/gfx/ots/src/glyf.h
@@ -1,17 +1,17 @@
 // 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.
 
 #ifndef OTS_GLYF_H_
 #define OTS_GLYF_H_
 
 #include <new>
-#include <utility>  // std::pair
+#include <utility>
 #include <vector>
 
 #include "ots.h"
 
 namespace ots {
 
 struct OpenTypeGLYF {
   std::vector<std::pair<const uint8_t*, size_t> > iov;
--- a/gfx/ots/src/gpos.cc
+++ b/gfx/ots/src/gpos.cc
@@ -2,18 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "gpos.h"
 
 #include <limits>
 #include <vector>
 
-#include "gdef.h"
-#include "gsub.h"
 #include "layout.h"
 #include "maxp.h"
 
 // GPOS - The Glyph Positioning Table
 // http://www.microsoft.com/typography/otspec/gpos.htm
 
 #define TABLE_NAME "GPOS"
 
@@ -66,140 +64,142 @@ const ots::LookupSubtableParser::TypePar
   {GPOS_TYPE_MARK_TO_BASE_ATTACHMENT, ParseMarkToBaseAttachment},
   {GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT, ParseMarkToLigatureAttachment},
   {GPOS_TYPE_MARK_TO_MARK_ATTACHMENT, ParseMarkToMarkAttachment},
   {GPOS_TYPE_CONTEXT_POSITIONING, ParseContextPositioning},
   {GPOS_TYPE_CHAINED_CONTEXT_POSITIONING, ParseChainedContextPositioning},
   {GPOS_TYPE_EXTENSION_POSITIONING, ParseExtensionPositioning}
 };
 
-// TODO(bashi): Port Chromium's arraysize macro and use it instead of sizeof().
 const ots::LookupSubtableParser kGposLookupSubtableParser = {
-  sizeof(kGposTypeParsers) / sizeof(kGposTypeParsers[0]),
+  arraysize(kGposTypeParsers),
   GPOS_TYPE_EXTENSION_POSITIONING, kGposTypeParsers
 };
 
 // Shared Tables: ValueRecord, Anchor Table, and MarkArray
 
-bool ParseValueRecord(ots::Buffer* subtable, const uint8_t *data,
+bool ParseValueRecord(const ots::OpenTypeFile *file,
+                      ots::Buffer* subtable, const uint8_t *data,
                       const size_t length, const uint16_t value_format) {
   // Check existence of adjustment fields.
   for (unsigned i = 0; i < 4; ++i) {
     if ((value_format >> i) & 0x1) {
       // Just read the field since these fileds could take an arbitrary values.
       if (!subtable->Skip(2)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read value reacord component");
       }
     }
   }
 
   // Check existence of offsets to device table.
   for (unsigned i = 0; i < 4; ++i) {
     if ((value_format >> (i + 4)) & 0x1) {
       uint16_t offset = 0;
       if (!subtable->ReadU16(&offset)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read value record offset");
       }
       if (offset) {
         // TODO(bashi): Is it possible that device tables locate before
         // this record? No fonts contain such offset AKAIF.
         if (offset >= length) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Value record offset too high %d >= %ld", offset, length);
         }
-        if (!ots::ParseDeviceTable(data + offset, length - offset)) {
-          return OTS_FAILURE();
+        if (!ots::ParseDeviceTable(file, data + offset, length - offset)) {
+          return OTS_FAILURE_MSG("Failed to parse device table in value record");
         }
       }
     }
   }
   return true;
 }
 
-bool ParseAnchorTable(const uint8_t *data, const size_t length) {
+bool ParseAnchorTable(const ots::OpenTypeFile *file,
+                      const uint8_t *data, const size_t length) {
   ots::Buffer subtable(data, length);
 
   uint16_t format = 0;
   // Read format and skip 2 2-byte fields that could be arbitrary values.
   if (!subtable.ReadU16(&format) ||
       !subtable.Skip(4)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Faled to read anchor table");
   }
 
   if (format == 0 || format > kMaxAnchorFormat) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad Anchor table format %d", format);
   }
 
   // Format 2 and 3 has additional fields.
   if (format == 2) {
     // Format 2 provides an index to a glyph contour point, which will take
     // arbitrary value.
     uint16_t anchor_point = 0;
     if (!subtable.ReadU16(&anchor_point)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read anchor point in format 2 Anchor Table");
     }
   } else if (format == 3) {
     uint16_t offset_x_device = 0;
     uint16_t offset_y_device = 0;
     if (!subtable.ReadU16(&offset_x_device) ||
         !subtable.ReadU16(&offset_y_device)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read device table offsets in format 3 anchor table");
     }
     const unsigned format_end = static_cast<unsigned>(10);
     if (offset_x_device) {
       if (offset_x_device < format_end || offset_x_device >= length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad x device table offset %d", offset_x_device);
       }
-      if (!ots::ParseDeviceTable(data + offset_x_device,
+      if (!ots::ParseDeviceTable(file, data + offset_x_device,
                                  length - offset_x_device)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse device table in anchor table");
       }
     }
     if (offset_y_device) {
       if (offset_y_device < format_end || offset_y_device >= length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad y device table offset %d", offset_y_device);
       }
-      if (!ots::ParseDeviceTable(data + offset_y_device,
+      if (!ots::ParseDeviceTable(file, data + offset_y_device,
                                  length - offset_y_device)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse device table in anchor table");
       }
     }
   }
   return true;
 }
 
-bool ParseMarkArrayTable(const uint8_t *data, const size_t length,
+bool ParseMarkArrayTable(const ots::OpenTypeFile *file,
+                         const uint8_t *data, const size_t length,
                          const uint16_t class_count) {
   ots::Buffer subtable(data, length);
 
   uint16_t mark_count = 0;
   if (!subtable.ReadU16(&mark_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read mark table length");
   }
 
   // MarkRecord consists of 4-bytes.
   const unsigned mark_records_end = 4 * static_cast<unsigned>(mark_count) + 2;
   if (mark_records_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad mark table length");
   }
   for (unsigned i = 0; i < mark_count; ++i) {
     uint16_t class_value = 0;
     uint16_t offset_mark_anchor = 0;
     if (!subtable.ReadU16(&class_value) ||
         !subtable.ReadU16(&offset_mark_anchor)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read mark table %d", i);
     }
     // |class_value| may take arbitrary values including 0 here so we don't
     // check the value.
     if (offset_mark_anchor < mark_records_end ||
         offset_mark_anchor >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad mark anchor offset %d for mark table %d", offset_mark_anchor, i);
     }
-    if (!ParseAnchorTable(data + offset_mark_anchor,
+    if (!ParseAnchorTable(file, data + offset_mark_anchor,
                           length - offset_mark_anchor)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Faled to parse anchor table for mark table %d", i);
     }
   }
 
   return true;
 }
 
 // Lookup Type 1:
 // Single Adjustment Positioning Subtable
@@ -208,170 +208,173 @@ bool ParseSingleAdjustment(const ots::Op
   ots::Buffer subtable(data, length);
 
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t value_format = 0;
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&value_format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read single adjustment information");
   }
 
   if (format == 1) {
     // Format 1 exactly one value record.
-    if (!ParseValueRecord(&subtable, data, length, value_format)) {
-      return OTS_FAILURE();
+    if (!ParseValueRecord(file, &subtable, data, length, value_format)) {
+      return OTS_FAILURE_MSG("Failed to parse format 1 single adjustment table");
     }
   } else if (format == 2) {
     uint16_t value_count = 0;
     if (!subtable.ReadU16(&value_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse format 2 single adjustment table");
     }
     for (unsigned i = 0; i < value_count; ++i) {
-      if (!ParseValueRecord(&subtable, data, length, value_format)) {
-        return OTS_FAILURE();
+      if (!ParseValueRecord(file, &subtable, data, length, value_format)) {
+        return OTS_FAILURE_MSG("Failed to parse value record %d in format 2 single adjustment table", i);
       }
     }
   } else {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad format %d in single adjustment table", format);
   }
 
   if (offset_coverage < subtable.offset() || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d in single adjustment table", offset_coverage);
   }
 
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage,
                                file->maxp->num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table in single adjustment table");
   }
 
   return true;
 }
 
-bool ParsePairSetTable(const uint8_t *data, const size_t length,
+bool ParsePairSetTable(const ots::OpenTypeFile *file,
+                       const uint8_t *data, const size_t length,
                        const uint16_t value_format1,
                        const uint16_t value_format2,
                        const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   uint16_t value_count = 0;
   if (!subtable.ReadU16(&value_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read pair set table structure");
   }
   for (unsigned i = 0; i < value_count; ++i) {
     // Check pair value record.
     uint16_t glyph_id = 0;
     if (!subtable.ReadU16(&glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read glyph in pair value record %d", i);
     }
     if (glyph_id >= num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("glyph id %d too high >= %d", glyph_id, num_glyphs);
     }
-    if (!ParseValueRecord(&subtable, data, length, value_format1)) {
-      return OTS_FAILURE();
+    if (!ParseValueRecord(file, &subtable, data, length, value_format1)) {
+      return OTS_FAILURE_MSG("Failed to parse value record in format 1 pair set table");
     }
-    if (!ParseValueRecord(&subtable, data, length, value_format2)) {
-      return OTS_FAILURE();
+    if (!ParseValueRecord(file, &subtable, data, length, value_format2)) {
+      return OTS_FAILURE_MSG("Failed to parse value record in format 2 pair set table");
     }
   }
   return true;
 }
 
-bool ParsePairPosFormat1(const uint8_t *data, const size_t length,
+bool ParsePairPosFormat1(const ots::OpenTypeFile *file,
+                         const uint8_t *data, const size_t length,
                          const uint16_t value_format1,
                          const uint16_t value_format2,
                          const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Skip 8 bytes that are already read before.
   if (!subtable.Skip(8)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read pair pos table structure");
   }
 
   uint16_t pair_set_count = 0;
   if (!subtable.ReadU16(&pair_set_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read pair pos set count");
   }
 
   const unsigned pair_pos_end = 2 * static_cast<unsigned>(pair_set_count) + 10;
   if (pair_pos_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad pair set length %d", pair_pos_end);
   }
   for (unsigned i = 0; i < pair_set_count; ++i) {
     uint16_t pair_set_offset = 0;
     if (!subtable.ReadU16(&pair_set_offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read pair set offset for pair set %d", i);
     }
     if (pair_set_offset < pair_pos_end || pair_set_offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad pair set offset %d for pair set %d", pair_set_offset, i);
     }
     // Check pair set tables
-    if (!ParsePairSetTable(data + pair_set_offset, length - pair_set_offset,
+    if (!ParsePairSetTable(file, data + pair_set_offset, length - pair_set_offset,
                            value_format1, value_format2,
                            num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse pair set table %d", i);
     }
   }
 
   return true;
 }
 
-bool ParsePairPosFormat2(const uint8_t *data, const size_t length,
+bool ParsePairPosFormat2(const ots::OpenTypeFile *file,
+                         const uint8_t *data, const size_t length,
                          const uint16_t value_format1,
                          const uint16_t value_format2,
                          const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Skip 8 bytes that are already read before.
   if (!subtable.Skip(8)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read pair pos format 2 structure");
   }
 
   uint16_t offset_class_def1 = 0;
   uint16_t offset_class_def2 = 0;
   uint16_t class1_count = 0;
   uint16_t class2_count = 0;
   if (!subtable.ReadU16(&offset_class_def1) ||
       !subtable.ReadU16(&offset_class_def2) ||
       !subtable.ReadU16(&class1_count) ||
       !subtable.ReadU16(&class2_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read pair pos format 2 data");
   }
 
   // Check class 1 records.
   for (unsigned i = 0; i < class1_count; ++i) {
     // Check class 2 records.
     for (unsigned j = 0; j < class2_count; ++j) {
-      if (value_format1 && !ParseValueRecord(&subtable, data, length,
+      if (value_format1 && !ParseValueRecord(file, &subtable, data, length,
                                              value_format1)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse value record 1 %d and %d", j, i);
       }
-      if (value_format2 && !ParseValueRecord(&subtable, data, length,
+      if (value_format2 && !ParseValueRecord(file, &subtable, data, length,
                                              value_format2)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Falied to parse value record 2 %d and %d", j, i);
       }
     }
   }
 
   // Check class definition tables.
   if (offset_class_def1 < subtable.offset() || offset_class_def1 >= length ||
       offset_class_def2 < subtable.offset() || offset_class_def2 >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad class definition table offsets %d or %d", offset_class_def1, offset_class_def2);
   }
-  if (!ots::ParseClassDefTable(data + offset_class_def1,
+  if (!ots::ParseClassDefTable(file, data + offset_class_def1,
                                length - offset_class_def1,
                                num_glyphs, kMaxClassDefValue)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse class definition table 1");
   }
-  if (!ots::ParseClassDefTable(data + offset_class_def2,
+  if (!ots::ParseClassDefTable(file, data + offset_class_def2,
                                length - offset_class_def2,
                                num_glyphs, kMaxClassDefValue)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse class definition table 2");
   }
 
   return true;
 }
 
 // Lookup Type 2:
 // Pair Adjustment Positioning Subtable
 bool ParsePairAdjustment(const ots::OpenTypeFile *file, const uint8_t *data,
@@ -381,40 +384,40 @@ bool ParsePairAdjustment(const ots::Open
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t value_format1 = 0;
   uint16_t value_format2 = 0;
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&value_format1) ||
       !subtable.ReadU16(&value_format2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read pair adjustment structure");
   }
 
   if (format == 1) {
-    if (!ParsePairPosFormat1(data, length, value_format1, value_format2,
+    if (!ParsePairPosFormat1(file, data, length, value_format1, value_format2,
                              file->maxp->num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse pair pos format 1");
     }
   } else if (format == 2) {
-    if (!ParsePairPosFormat2(data, length, value_format1, value_format2,
+    if (!ParsePairPosFormat2(file, data, length, value_format1, value_format2,
                              file->maxp->num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse pair format 2");
     }
   } else {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad pos pair format %d", format);
   }
 
   if (offset_coverage < subtable.offset() || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad pair pos offset coverage %d", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage,
                                file->maxp->num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table");
   }
 
   return true;
 }
 
 // Lookup Type 3
 // Cursive Attachment Positioning Subtable
 bool ParseCursiveAttachment(const ots::OpenTypeFile *file, const uint8_t *data,
@@ -422,125 +425,127 @@ bool ParseCursiveAttachment(const ots::O
   ots::Buffer subtable(data, length);
 
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t entry_exit_count = 0;
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&entry_exit_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read cursive attachment structure");
   }
 
   if (format != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad cursive attachment format %d", format);
   }
 
   // Check entry exit records.
   const unsigned entry_exit_records_end =
       2 * static_cast<unsigned>(entry_exit_count) + 6;
   if (entry_exit_records_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad entry exit record end %d", entry_exit_records_end);
   }
   for (unsigned i = 0; i < entry_exit_count; ++i) {
     uint16_t offset_entry_anchor = 0;
     uint16_t offset_exit_anchor = 0;
     if (!subtable.ReadU16(&offset_entry_anchor) ||
         !subtable.ReadU16(&offset_exit_anchor)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read entry exit record %d", i);
     }
     // These offsets could be NULL.
     if (offset_entry_anchor) {
       if (offset_entry_anchor < entry_exit_records_end ||
           offset_entry_anchor >= length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad entry anchor offset %d in entry exit record %d", offset_entry_anchor, i);
       }
-      if (!ParseAnchorTable(data + offset_entry_anchor,
+      if (!ParseAnchorTable(file, data + offset_entry_anchor,
                             length - offset_entry_anchor)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse entry anchor table in entry exit record %d", i);
       }
     }
     if (offset_exit_anchor) {
       if (offset_exit_anchor < entry_exit_records_end ||
          offset_exit_anchor >= length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad exit anchor offset %d in entry exit record %d", offset_exit_anchor, i);
       }
-      if (!ParseAnchorTable(data + offset_exit_anchor,
+      if (!ParseAnchorTable(file, data + offset_exit_anchor,
                             length - offset_exit_anchor)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse exit anchor table in entry exit record %d", i);
       }
     }
   }
 
   if (offset_coverage < subtable.offset() || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset in cursive attachment %d", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage,
                                file->maxp->num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table in cursive attachment");
   }
 
   return true;
 }
 
-bool ParseAnchorArrayTable(const uint8_t *data, const size_t length,
+bool ParseAnchorArrayTable(const ots::OpenTypeFile *file,
+                           const uint8_t *data, const size_t length,
                            const uint16_t class_count) {
   ots::Buffer subtable(data, length);
 
   uint16_t record_count = 0;
   if (!subtable.ReadU16(&record_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read anchor array length");
   }
 
   const unsigned anchor_array_end = 2 * static_cast<unsigned>(record_count) *
       static_cast<unsigned>(class_count) + 2;
   if (anchor_array_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of anchor array %d", anchor_array_end);
   }
   for (unsigned i = 0; i < record_count; ++i) {
     for (unsigned j = 0; j < class_count; ++j) {
       uint16_t offset_record = 0;
       if (!subtable.ReadU16(&offset_record)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Can't read anchor array record offset for class %d and record %d", j, i);
       }
       // |offset_record| could be NULL.
       if (offset_record) {
         if (offset_record < anchor_array_end || offset_record >= length) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Bad record offset %d in class %d, record %d", offset_record, j, i);
         }
-        if (!ParseAnchorTable(data + offset_record,
+        if (!ParseAnchorTable(file, data + offset_record,
                               length - offset_record)) {
-          return OTS_FAILURE();
+          return OTS_FAILURE_MSG("Failed to parse anchor table for class %d, record %d", j, i);
         }
       }
     }
   }
   return true;
 }
 
-bool ParseLigatureArrayTable(const uint8_t *data, const size_t length,
+bool ParseLigatureArrayTable(const ots::OpenTypeFile *file,
+                             const uint8_t *data, const size_t length,
                              const uint16_t class_count) {
   ots::Buffer subtable(data, length);
 
   uint16_t ligature_count = 0;
   if (!subtable.ReadU16(&ligature_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read ligature count");
   }
   for (unsigned i = 0; i < ligature_count; ++i) {
     uint16_t offset_ligature_attach = 0;
     if (!subtable.ReadU16(&offset_ligature_attach)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read ligature offset %d", i);
     }
     if (offset_ligature_attach < 2 || offset_ligature_attach >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad ligature attachment offset %d in ligature %d", offset_ligature_attach, i);
     }
-    if (!ParseAnchorArrayTable(data + offset_ligature_attach,
+    if (!ParseAnchorArrayTable(file, data + offset_ligature_attach,
                                length - offset_ligature_attach, class_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse anchor table for ligature %d", i);
     }
   }
   return true;
 }
 
 // Common parser for Lookup Type 4, 5 and 6.
 bool ParseMarkToAttachmentSubtables(const ots::OpenTypeFile *file,
                                     const uint8_t *data, const size_t length,
@@ -554,71 +559,71 @@ bool ParseMarkToAttachmentSubtables(cons
   uint16_t offset_mark_array = 0;
   uint16_t offset_type_specific_array = 0;
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage1) ||
       !subtable.ReadU16(&offset_coverage2) ||
       !subtable.ReadU16(&class_count) ||
       !subtable.ReadU16(&offset_mark_array) ||
       !subtable.ReadU16(&offset_type_specific_array)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read mark attachment subtable header");
   }
 
   if (format != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad mark attachment subtable format %d", format);
   }
 
   const unsigned header_end = static_cast<unsigned>(subtable.offset());
   if (header_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad mark attachment subtable size ending at %d", header_end);
   }
   if (offset_coverage1 < header_end || offset_coverage1 >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage 1 offset %d", offset_coverage1);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage1,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage1,
                                length - offset_coverage1,
                                file->maxp->num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse converge 1 table");
   }
   if (offset_coverage2 < header_end || offset_coverage2 >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage 2 offset %d", offset_coverage2);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage2,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage2,
                                length - offset_coverage2,
                                file->maxp->num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table 2");
   }
 
   if (offset_mark_array < header_end || offset_mark_array >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad mark array offset %d", offset_mark_array);
   }
-  if (!ParseMarkArrayTable(data + offset_mark_array,
+  if (!ParseMarkArrayTable(file, data + offset_mark_array,
                            length - offset_mark_array, class_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse mark array");
   }
 
   if (offset_type_specific_array < header_end ||
       offset_type_specific_array >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad type specific array offset %d", offset_type_specific_array);
   }
   if (type == GPOS_TYPE_MARK_TO_BASE_ATTACHMENT ||
       type == GPOS_TYPE_MARK_TO_MARK_ATTACHMENT) {
-    if (!ParseAnchorArrayTable(data + offset_type_specific_array,
+    if (!ParseAnchorArrayTable(file, data + offset_type_specific_array,
                                length - offset_type_specific_array,
                                class_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse anchor array");
     }
   } else if (type == GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT) {
-    if (!ParseLigatureArrayTable(data + offset_type_specific_array,
+    if (!ParseLigatureArrayTable(file, data + offset_type_specific_array,
                                  length - offset_type_specific_array,
                                  class_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse ligature array");
     }
   } else {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad attachment type %d", type);
   }
 
   return true;
 }
 
 // Lookup Type 4:
 // MarkToBase Attachment Positioning Subtable
 bool ParseMarkToBaseAttachment(const ots::OpenTypeFile *file,
@@ -642,44 +647,44 @@ bool ParseMarkToMarkAttachment(const ots
   return ParseMarkToAttachmentSubtables(file, data, length,
                                         GPOS_TYPE_MARK_TO_MARK_ATTACHMENT);
 }
 
 // Lookup Type 7:
 // Contextual Positioning Subtables
 bool ParseContextPositioning(const ots::OpenTypeFile *file,
                              const uint8_t *data, const size_t length) {
-  return ots::ParseContextSubtable(data, length, file->maxp->num_glyphs,
+  return ots::ParseContextSubtable(file, data, length, file->maxp->num_glyphs,
                                    file->gpos->num_lookups);
 }
 
 // Lookup Type 8:
 // Chaining Contexual Positioning Subtable
 bool ParseChainedContextPositioning(const ots::OpenTypeFile *file,
                                     const uint8_t *data, const size_t length) {
-  return ots::ParseChainingContextSubtable(data, length,
+  return ots::ParseChainingContextSubtable(file, data, length,
                                            file->maxp->num_glyphs,
                                            file->gpos->num_lookups);
 }
 
 // Lookup Type 9:
 // Extension Positioning
 bool ParseExtensionPositioning(const ots::OpenTypeFile *file,
                                const uint8_t *data, const size_t length) {
   return ots::ParseExtensionSubtable(file, data, length,
                                      &kGposLookupSubtableParser);
 }
 
 }  // namespace
 
-#define DROP_THIS_TABLE \
+#define DROP_THIS_TABLE(msg_) \
   do { \
     file->gpos->data = 0; \
     file->gpos->length = 0; \
-    OTS_FAILURE_MSG("OpenType layout data discarded"); \
+    OTS_FAILURE_MSG(msg_ ", table discarded"); \
   } while (0)
 
 namespace ots {
 
 // As far as I checked, following fonts contain invalid GPOS table and
 // OTS will drop their GPOS table.
 //
 // # invalid delta format in device table
@@ -723,94 +728,84 @@ namespace ots {
 // Padauk.ttf
 //
 // # Contour point indexes aren't sorted
 // Arial Unicode.ttf
 
 bool ots_gpos_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   // Parsing GPOS table requires num_glyphs which is contained in maxp table.
   if (!file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("missing maxp table needed in GPOS");
   }
 
   Buffer table(data, length);
 
   OpenTypeGPOS *gpos = new OpenTypeGPOS;
   file->gpos = gpos;
 
   uint32_t version = 0;
   uint16_t offset_script_list = 0;
   uint16_t offset_feature_list = 0;
   uint16_t offset_lookup_list = 0;
   if (!table.ReadU32(&version) ||
       !table.ReadU16(&offset_script_list) ||
       !table.ReadU16(&offset_feature_list) ||
       !table.ReadU16(&offset_lookup_list)) {
-    OTS_WARNING("incomplete GPOS table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Incomplete table");
     return true;
   }
 
   if (version != 0x00010000) {
-    OTS_WARNING("bad GPOS version");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Bad version");
     return true;
   }
   if ((offset_script_list < kGposHeaderSize ||
        offset_script_list >= length) ||
       (offset_feature_list < kGposHeaderSize ||
        offset_feature_list >= length) ||
       (offset_lookup_list < kGposHeaderSize ||
        offset_lookup_list >= length)) {
-    OTS_WARNING("bad offset in GPOS header");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Bad offset in table header");
     return true;
   }
 
   if (!ParseLookupListTable(file, data + offset_lookup_list,
                             length - offset_lookup_list,
                             &kGposLookupSubtableParser,
                             &gpos->num_lookups)) {
-    OTS_WARNING("failed to parse lookup list table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Failed to parse lookup list table");
     return true;
   }
 
   uint16_t num_features = 0;
-  if (!ParseFeatureListTable(data + offset_feature_list,
+  if (!ParseFeatureListTable(file, data + offset_feature_list,
                              length - offset_feature_list, gpos->num_lookups,
                              &num_features)) {
-    OTS_WARNING("failed to parse feature list table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Failed to parse feature list table");
     return true;
   }
 
-  if (!ParseScriptListTable(data + offset_script_list,
+  if (!ParseScriptListTable(file, data + offset_script_list,
                             length - offset_script_list, num_features)) {
-    OTS_WARNING("failed to parse script list table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Failed to parse script list table");
     return true;
   }
 
   gpos->data = data;
   gpos->length = length;
   return true;
 }
 
 bool ots_gpos_should_serialise(OpenTypeFile *file) {
-  const bool needed_tables_dropped =
-      (file->gdef && file->gdef->data == NULL) ||
-      (file->gsub && file->gsub->data == NULL);
-  return file->gpos != NULL && file->gpos->data != NULL &&
-      !needed_tables_dropped;
+  return file->gpos != NULL && file->gpos->data != NULL;
 }
 
 bool ots_gpos_serialise(OTSStream *out, OpenTypeFile *file) {
   if (!out->Write(file->gpos->data, file->gpos->length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write GPOS table");
   }
 
   return true;
 }
 
 void ots_gpos_free(OpenTypeFile *file) {
   delete file->gpos;
 }
deleted file mode 100644
--- a/gfx/ots/src/graphite.cc
+++ /dev/null
@@ -1,181 +0,0 @@
-// Copyright (c) 2010 Mozilla Foundation. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "graphite.h"
-
-// Support for preserving (NOT sanitizing) the Graphite tables
-
-namespace ots {
-
-// 'Silf'
-bool ots_silf_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-
-  OpenTypeSILF *silf = new OpenTypeSILF;
-  file->silf = silf;
-
-  if (!table.Skip(length)) {
-    return OTS_FAILURE();
-  }
-
-  silf->data = data;
-  silf->length = length;
-  return true;
-}
-
-bool ots_silf_should_serialise(OpenTypeFile *file) {
-  return file->preserve_graphite && file->silf;
-}
-
-bool ots_silf_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeSILF *silf = file->silf;
-
-  if (!out->Write(silf->data, silf->length)) {
-    return OTS_FAILURE();
-  }
-
-  return true;
-}
-
-void ots_silf_free(OpenTypeFile *file) {
-  delete file->silf;
-}
-
-// 'Sill'
-bool ots_sill_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-
-  OpenTypeSILL *sill = new OpenTypeSILL;
-  file->sill = sill;
-
-  if (!table.Skip(length)) {
-    return OTS_FAILURE();
-  }
-
-  sill->data = data;
-  sill->length = length;
-  return true;
-}
-
-bool ots_sill_should_serialise(OpenTypeFile *file) {
-  return file->preserve_graphite && file->sill;
-}
-
-bool ots_sill_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeSILL *sill = file->sill;
-
-  if (!out->Write(sill->data, sill->length)) {
-    return OTS_FAILURE();
-  }
-
-  return true;
-}
-
-void ots_sill_free(OpenTypeFile *file) {
-  delete file->sill;
-}
-
-// 'Gloc'
-bool ots_gloc_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-
-  OpenTypeGLOC *gloc = new OpenTypeGLOC;
-  file->gloc = gloc;
-
-  if (!table.Skip(length)) {
-    return OTS_FAILURE();
-  }
-
-  gloc->data = data;
-  gloc->length = length;
-  return true;
-}
-
-bool ots_gloc_should_serialise(OpenTypeFile *file) {
-  return file->preserve_graphite && file->gloc;
-}
-
-bool ots_gloc_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeGLOC *gloc = file->gloc;
-
-  if (!out->Write(gloc->data, gloc->length)) {
-    return OTS_FAILURE();
-  }
-
-  return true;
-}
-
-void ots_gloc_free(OpenTypeFile *file) {
-  delete file->gloc;
-}
-
-// 'Glat'
-bool ots_glat_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-
-  OpenTypeGLAT *glat = new OpenTypeGLAT;
-  file->glat = glat;
-
-  if (!table.Skip(length)) {
-    return OTS_FAILURE();
-  }
-
-  glat->data = data;
-  glat->length = length;
-  return true;
-}
-
-bool ots_glat_should_serialise(OpenTypeFile *file) {
-  return file->preserve_graphite && file->glat;
-}
-
-bool ots_glat_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeGLAT *glat = file->glat;
-
-  if (!out->Write(glat->data, glat->length)) {
-    return OTS_FAILURE();
-  }
-
-  return true;
-}
-
-void ots_glat_free(OpenTypeFile *file) {
-  delete file->glat;
-}
-
-// 'Feat'
-bool ots_feat_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-
-  OpenTypeFEAT *feat = new OpenTypeFEAT;
-  file->feat = feat;
-
-  if (!table.Skip(length)) {
-    return OTS_FAILURE();
-  }
-
-  feat->data = data;
-  feat->length = length;
-  return true;
-}
-
-bool ots_feat_should_serialise(OpenTypeFile *file) {
-  return file->preserve_graphite && file->feat;
-}
-
-bool ots_feat_serialise(OTSStream *out, OpenTypeFile *file) {
-  const OpenTypeFEAT *feat = file->feat;
-
-  if (!out->Write(feat->data, feat->length)) {
-    return OTS_FAILURE();
-  }
-
-  return true;
-}
-
-void ots_feat_free(OpenTypeFile *file) {
-  delete file->feat;
-}
-
-}  // namespace ots
deleted file mode 100644
--- a/gfx/ots/src/graphite.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2010 Mozilla Foundation. 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 "ots.h"
-
-namespace ots {
-
-struct OpenTypeSILF {
-  const uint8_t *data;
-  uint32_t length;
-};
-
-struct OpenTypeSILL {
-  const uint8_t *data;
-  uint32_t length;
-};
-
-struct OpenTypeGLOC {
-  const uint8_t *data;
-  uint32_t length;
-};
-
-struct OpenTypeGLAT {
-  const uint8_t *data;
-  uint32_t length;
-};
-
-struct OpenTypeFEAT {
-  const uint8_t *data;
-  uint32_t length;
-};
-
-}  // namespace ots
-
-#endif  // OTS_GRAPHITE_H_
--- a/gfx/ots/src/gsub.cc
+++ b/gfx/ots/src/gsub.cc
@@ -2,30 +2,28 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "gsub.h"
 
 #include <limits>
 #include <vector>
 
-#include "gdef.h"
-#include "gpos.h"
 #include "layout.h"
 #include "maxp.h"
 
 // GSUB - The Glyph Substitution Table
 // http://www.microsoft.com/typography/otspec/gsub.htm
 
 #define TABLE_NAME "GSUB"
 
 namespace {
 
 // The GSUB header size
-const size_t kGsubHeaderSize = 8;
+const size_t kGsubHeaderSize = 4 + 3 * 2;
 
 enum GSUB_TYPE {
   GSUB_TYPE_SINGLE = 1,
   GSUB_TYPE_MULTIPLE = 2,
   GSUB_TYPE_ALTERNATE = 3,
   GSUB_TYPE_LIGATURE = 4,
   GSUB_TYPE_CONTEXT = 5,
   GSUB_TYPE_CHANGING_CONTEXT = 6,
@@ -60,98 +58,97 @@ const ots::LookupSubtableParser::TypePar
   {GSUB_TYPE_LIGATURE, ParseLigatureSubstitution},
   {GSUB_TYPE_CONTEXT, ParseContextSubstitution},
   {GSUB_TYPE_CHANGING_CONTEXT, ParseChainingContextSubstitution},
   {GSUB_TYPE_EXTENSION_SUBSTITUTION, ParseExtensionSubstitution},
   {GSUB_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE,
     ParseReverseChainingContextSingleSubstitution}
 };
 
-// TODO(bashi): Port Chromium's arraysize macro and use it instead of sizeof().
 const ots::LookupSubtableParser kGsubLookupSubtableParser = {
-  sizeof(kGsubTypeParsers) / sizeof(kGsubTypeParsers[0]),
+  arraysize(kGsubTypeParsers),
   GSUB_TYPE_EXTENSION_SUBSTITUTION, kGsubTypeParsers
 };
 
 // Lookup Type 1:
 // Single Substitution Subtable
 bool ParseSingleSubstitution(const ots::OpenTypeFile *file,
                              const uint8_t *data, const size_t length) {
   ots::Buffer subtable(data, length);
 
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
 
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read single subst table header");
   }
 
   const uint16_t num_glyphs = file->maxp->num_glyphs;
   if (format == 1) {
     // Parse SingleSubstFormat1
     int16_t delta_glyph_id = 0;
     if (!subtable.ReadS16(&delta_glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read glyph shift from format 1 single subst table");
     }
     if (std::abs(delta_glyph_id) >= num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad glyph shift of %d in format 1 single subst table", delta_glyph_id);
     }
   } else if (format == 2) {
     // Parse SingleSubstFormat2
     uint16_t glyph_count = 0;
     if (!subtable.ReadU16(&glyph_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read glyph cound in format 2 single subst table");
     }
     if (glyph_count > num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad glyph count %d > %d in format 2 single subst table", glyph_count, num_glyphs);
     }
     for (unsigned i = 0; i < glyph_count; ++i) {
       uint16_t substitute = 0;
       if (!subtable.ReadU16(&substitute)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read substitution %d in format 2 single subst table", i);
       }
       if (substitute >= num_glyphs) {
-        OTS_WARNING("too large substitute: %u", substitute);
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("too large substitute: %u", substitute);
       }
     }
   } else {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad single subst table format %d", format);
   }
 
   if (offset_coverage < subtable.offset() || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %x", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table");
   }
 
   return true;
 }
 
-bool ParseSequenceTable(const uint8_t *data, const size_t length,
+bool ParseSequenceTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, const size_t length,
                         const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   uint16_t glyph_count = 0;
   if (!subtable.ReadU16(&glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read glyph count in sequence table");
   }
   if (glyph_count > num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad glyph count %d > %d", glyph_count, num_glyphs);
   }
   for (unsigned i = 0; i < glyph_count; ++i) {
     uint16_t substitute = 0;
     if (!subtable.ReadU16(&substitute)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failedt o read substitution %d in sequence table", i);
     }
     if (substitute >= num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad subsitution (%d) %d > %d", i, substitute, num_glyphs);
     }
   }
 
   return true;
 }
 
 // Lookup Type 2:
 // Multiple Substitution Subtable
@@ -161,73 +158,73 @@ bool ParseMutipleSubstitution(const ots:
 
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t sequence_count = 0;
 
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&sequence_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read header of multiple subst table");
   }
 
   if (format != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad multiple subst table format %d", format);
   }
 
   const uint16_t num_glyphs = file->maxp->num_glyphs;
   const unsigned sequence_end = static_cast<unsigned>(6) +
       sequence_count * 2;
   if (sequence_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad segence end %d, in multiple subst", sequence_end);
   }
   for (unsigned i = 0; i < sequence_count; ++i) {
     uint16_t offset_sequence = 0;
     if (!subtable.ReadU16(&offset_sequence)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read sequence offset for sequence %d", i);
     }
     if (offset_sequence < sequence_end || offset_sequence >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad sequence offset %d for sequence %d", offset_sequence, i);
     }
-    if (!ParseSequenceTable(data + offset_sequence, length - offset_sequence,
+    if (!ParseSequenceTable(file, data + offset_sequence, length - offset_sequence,
                             num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse sequence table %d", i);
     }
   }
 
   if (offset_coverage < sequence_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table");
   }
 
   return true;
 }
 
-bool ParseAlternateSetTable(const uint8_t *data, const size_t length,
+bool ParseAlternateSetTable(const ots::OpenTypeFile *file,
+                            const uint8_t *data, const size_t length,
                             const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   uint16_t glyph_count = 0;
   if (!subtable.ReadU16(&glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read alternate set header");
   }
   if (glyph_count > num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad glyph count %d > %d in alternate set table", glyph_count, num_glyphs);
   }
   for (unsigned i = 0; i < glyph_count; ++i) {
     uint16_t alternate = 0;
     if (!subtable.ReadU16(&alternate)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read alternate %d", i);
     }
     if (alternate >= num_glyphs) {
-      OTS_WARNING("too large alternate: %u", alternate);
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Too large alternate: %u", alternate);
     }
   }
   return true;
 }
 
 // Lookup Type 3:
 // Alternate Substitution Subtable
 bool ParseAlternateSubstitution(const ots::OpenTypeFile *file,
@@ -236,113 +233,114 @@ bool ParseAlternateSubstitution(const ot
 
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t alternate_set_count = 0;
 
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&alternate_set_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read alternate subst header");
   }
 
   if (format != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad alternate subst table format %d", format);
   }
 
   const uint16_t num_glyphs = file->maxp->num_glyphs;
   const unsigned alternate_set_end = static_cast<unsigned>(6) +
       alternate_set_count * 2;
   if (alternate_set_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of alternate set %d", alternate_set_end);
   }
   for (unsigned i = 0; i < alternate_set_count; ++i) {
     uint16_t offset_alternate_set = 0;
     if (!subtable.ReadU16(&offset_alternate_set)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read alternate set offset for set %d", i);
     }
     if (offset_alternate_set < alternate_set_end ||
         offset_alternate_set >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad alternate set offset %d for set %d", offset_alternate_set, i);
     }
-    if (!ParseAlternateSetTable(data + offset_alternate_set,
+    if (!ParseAlternateSetTable(file, data + offset_alternate_set,
                                 length - offset_alternate_set,
                                 num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse alternate set");
     }
   }
 
   if (offset_coverage < alternate_set_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table");
   }
 
   return true;
 }
 
-bool ParseLigatureTable(const uint8_t *data, const size_t length,
+bool ParseLigatureTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, const size_t length,
                         const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   uint16_t lig_glyph = 0;
   uint16_t comp_count = 0;
 
   if (!subtable.ReadU16(&lig_glyph) ||
       !subtable.ReadU16(&comp_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read ligatuer table header");
   }
 
   if (lig_glyph >= num_glyphs) {
-    OTS_WARNING("too large lig_glyph: %u", lig_glyph);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("too large lig_glyph: %u", lig_glyph);
   }
   if (comp_count == 0 || comp_count > num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad component count of %d", comp_count);
   }
   for (unsigned i = 0; i < comp_count - static_cast<unsigned>(1); ++i) {
     uint16_t component = 0;
     if (!subtable.ReadU16(&component)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read ligature component %d", i);
     }
     if (component >= num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad ligature component %d of %d", i, component);
     }
   }
 
   return true;
 }
 
-bool ParseLigatureSetTable(const uint8_t *data, const size_t length,
+bool ParseLigatureSetTable(const ots::OpenTypeFile *file,
+                           const uint8_t *data, const size_t length,
                            const uint16_t num_glyphs) {
   ots::Buffer subtable(data, length);
 
   uint16_t ligature_count = 0;
 
   if (!subtable.ReadU16(&ligature_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read ligature count in ligature set");
   }
 
   const unsigned ligature_end = static_cast<unsigned>(2) + ligature_count * 2;
   if (ligature_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of ligature %d in ligature set", ligature_end);
   }
   for (unsigned i = 0; i < ligature_count; ++i) {
     uint16_t offset_ligature = 0;
     if (!subtable.ReadU16(&offset_ligature)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read ligature offset %d", i);
     }
     if (offset_ligature < ligature_end || offset_ligature >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad ligature offset %d for ligature %d", offset_ligature, i);
     }
-    if (!ParseLigatureTable(data + offset_ligature, length - offset_ligature,
+    if (!ParseLigatureTable(file, data + offset_ligature, length - offset_ligature,
                             num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse ligature %d", i);
     }
   }
 
   return true;
 }
 
 // Lookup Type 4:
 // Ligature Substitution Subtable
@@ -352,69 +350,69 @@ bool ParseLigatureSubstitution(const ots
 
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
   uint16_t lig_set_count = 0;
 
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&lig_set_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read ligature substitution header");
   }
 
   if (format != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad ligature substitution table format %d", format);
   }
 
   const uint16_t num_glyphs = file->maxp->num_glyphs;
   const unsigned ligature_set_end = static_cast<unsigned>(6) +
       lig_set_count * 2;
   if (ligature_set_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of ligature set %d in ligature substitution table", ligature_set_end);
   }
   for (unsigned i = 0; i < lig_set_count; ++i) {
     uint16_t offset_ligature_set = 0;
     if (!subtable.ReadU16(&offset_ligature_set)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read ligature set offset %d", i);
     }
     if (offset_ligature_set < ligature_set_end ||
         offset_ligature_set >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad ligature set offset %d for set %d", offset_ligature_set, i);
     }
-    if (!ParseLigatureSetTable(data + offset_ligature_set,
+    if (!ParseLigatureSetTable(file, data + offset_ligature_set,
                                length - offset_ligature_set, num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse ligature set %d", i);
     }
   }
 
   if (offset_coverage < ligature_set_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table");
   }
 
   return true;
 }
 
 // Lookup Type 5:
 // Contextual Substitution Subtable
 bool ParseContextSubstitution(const ots::OpenTypeFile *file,
                               const uint8_t *data, const size_t length) {
-  return ots::ParseContextSubtable(data, length, file->maxp->num_glyphs,
+  return ots::ParseContextSubtable(file, data, length, file->maxp->num_glyphs,
                                    file->gsub->num_lookups);
 }
 
 // Lookup Type 6:
 // Chaining Contextual Substitution Subtable
 bool ParseChainingContextSubstitution(const ots::OpenTypeFile *file,
                                       const uint8_t *data,
                                       const size_t length) {
-  return ots::ParseChainingContextSubtable(data, length,
+  return ots::ParseChainingContextSubtable(file, data, length,
                                            file->maxp->num_glyphs,
                                            file->gsub->num_lookups);
 }
 
 // Lookup Type 7:
 // Extension Substition
 bool ParseExtensionSubstitution(const ots::OpenTypeFile *file,
                                 const uint8_t *data, const size_t length) {
@@ -428,118 +426,118 @@ bool ParseReverseChainingContextSingleSu
     const ots::OpenTypeFile *file, const uint8_t *data, const size_t length) {
   ots::Buffer subtable(data, length);
 
   uint16_t format = 0;
   uint16_t offset_coverage = 0;
 
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&offset_coverage)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read reverse chaining header");
   }
 
   const uint16_t num_glyphs = file->maxp->num_glyphs;
 
   uint16_t backtrack_glyph_count = 0;
   if (!subtable.ReadU16(&backtrack_glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read backtrack glyph count in reverse chaining table");
   }
   if (backtrack_glyph_count > num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad backtrack glyph count of %d", backtrack_glyph_count);
   }
   std::vector<uint16_t> offsets_backtrack;
   offsets_backtrack.reserve(backtrack_glyph_count);
   for (unsigned i = 0; i < backtrack_glyph_count; ++i) {
     uint16_t offset = 0;
     if (!subtable.ReadU16(&offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read backtrack offset %d", i);
     }
     offsets_backtrack.push_back(offset);
   }
 
   uint16_t lookahead_glyph_count = 0;
   if (!subtable.ReadU16(&lookahead_glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read look ahead glyph count");
   }
   if (lookahead_glyph_count > num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad look ahead glyph count %d", lookahead_glyph_count);
   }
   std::vector<uint16_t> offsets_lookahead;
   offsets_lookahead.reserve(lookahead_glyph_count);
   for (unsigned i = 0; i < lookahead_glyph_count; ++i) {
     uint16_t offset = 0;
     if (!subtable.ReadU16(&offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't read look ahead offset %d", i);
     }
     offsets_lookahead.push_back(offset);
   }
 
   uint16_t glyph_count = 0;
   if (!subtable.ReadU16(&glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read glyph count in reverse chaining table");
   }
   if (glyph_count > num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad glyph count of %d", glyph_count);
   }
   for (unsigned i = 0; i < glyph_count; ++i) {
     uint16_t substitute = 0;
     if (!subtable.ReadU16(&substitute)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read substitution %d reverse chaining table", i);
     }
     if (substitute >= num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad substitute glyph %d in reverse chaining table substitution %d", substitute, i);
     }
   }
 
   const unsigned substitute_end = static_cast<unsigned>(10) +
       (backtrack_glyph_count + lookahead_glyph_count + glyph_count) * 2;
   if (substitute_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad substitute end offset in reverse chaining table");
   }
 
   if (offset_coverage < substitute_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d in reverse chaining table", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table in reverse chaining table");
   }
 
   for (unsigned i = 0; i < backtrack_glyph_count; ++i) {
     if (offsets_backtrack[i] < substitute_end ||
         offsets_backtrack[i] >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad backtrack offset %d for backtrack %d in reverse chaining table", offsets_backtrack[i], i);
     }
-    if (!ots::ParseCoverageTable(data + offsets_backtrack[i],
+    if (!ots::ParseCoverageTable(file, data + offsets_backtrack[i],
                                  length - offsets_backtrack[i], num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse coverage table for backtrack %d in reverse chaining table", i);
     }
   }
 
   for (unsigned i = 0; i < lookahead_glyph_count; ++i) {
     if (offsets_lookahead[i] < substitute_end ||
         offsets_lookahead[i] >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad lookahead offset %d for lookahead %d in reverse chaining table", offsets_lookahead[i], i);
     }
-    if (!ots::ParseCoverageTable(data + offsets_lookahead[i],
+    if (!ots::ParseCoverageTable(file, data + offsets_lookahead[i],
                                  length - offsets_lookahead[i], num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse lookahead coverage table %d in reverse chaining table", i);
     }
   }
 
   return true;
 }
 
 }  // namespace
 
-#define DROP_THIS_TABLE \
+#define DROP_THIS_TABLE(msg_) \
   do { \
     file->gsub->data = 0; \
     file->gsub->length = 0; \
-    OTS_FAILURE_MSG("OpenType layout data discarded"); \
+    OTS_FAILURE_MSG(msg_ ", table discarded"); \
   } while (0)
 
 namespace ots {
 
 // As far as I checked, following fonts contain invalid values in GSUB table.
 // OTS will drop their GSUB table.
 //
 // # too large substitute (value is 0xFFFF)
@@ -587,94 +585,84 @@ namespace ots {
 // KacstQurn.ttf
 // KacstDigital.ttf
 // KacstBook.ttf
 // KacstFarsi.ttf
 
 bool ots_gsub_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   // Parsing gsub table requires |file->maxp->num_glyphs|
   if (!file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing maxp table in font, needed by GSUB");
   }
 
   Buffer table(data, length);
 
   OpenTypeGSUB *gsub = new OpenTypeGSUB;
   file->gsub = gsub;
 
   uint32_t version = 0;
   uint16_t offset_script_list = 0;
   uint16_t offset_feature_list = 0;
   uint16_t offset_lookup_list = 0;
   if (!table.ReadU32(&version) ||
       !table.ReadU16(&offset_script_list) ||
       !table.ReadU16(&offset_feature_list) ||
       !table.ReadU16(&offset_lookup_list)) {
-    OTS_WARNING("incomplete GSUB table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Incomplete table");
     return true;
   }
 
   if (version != 0x00010000) {
-    OTS_WARNING("bad GSUB version");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Bad version");
     return true;
   }
   if ((offset_script_list < kGsubHeaderSize ||
        offset_script_list >= length) ||
       (offset_feature_list < kGsubHeaderSize ||
        offset_feature_list >= length) ||
       (offset_lookup_list < kGsubHeaderSize ||
        offset_lookup_list >= length)) {
-    OTS_WARNING("bad offset in GSUB header");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Bad offset in table header");
     return true;
   }
 
   if (!ParseLookupListTable(file, data + offset_lookup_list,
                             length - offset_lookup_list,
                             &kGsubLookupSubtableParser,
                             &gsub->num_lookups)) {
-    OTS_WARNING("failed to parse lookup list table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Failed to parse lookup list table");
     return true;
   }
 
   uint16_t num_features = 0;
-  if (!ParseFeatureListTable(data + offset_feature_list,
+  if (!ParseFeatureListTable(file, data + offset_feature_list,
                              length - offset_feature_list, gsub->num_lookups,
                              &num_features)) {
-    OTS_WARNING("failed to parse feature list table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Failed to parse feature list table");
     return true;
   }
 
-  if (!ParseScriptListTable(data + offset_script_list,
+  if (!ParseScriptListTable(file, data + offset_script_list,
                             length - offset_script_list, num_features)) {
-    OTS_WARNING("failed to parse script list table");
-    DROP_THIS_TABLE;
+    DROP_THIS_TABLE("Failed to parse script list table");
     return true;
   }
 
   gsub->data = data;
   gsub->length = length;
   return true;
 }
 
 bool ots_gsub_should_serialise(OpenTypeFile *file) {
-  const bool needed_tables_dropped =
-      (file->gdef && file->gdef->data == NULL) ||
-      (file->gpos && file->gpos->data == NULL);
-  return file->gsub != NULL && file->gsub->data != NULL
-      && !needed_tables_dropped;
+  return file->gsub != NULL && file->gsub->data != NULL;
 }
 
 bool ots_gsub_serialise(OTSStream *out, OpenTypeFile *file) {
   if (!out->Write(file->gsub->data, file->gsub->length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write GSUB table");
   }
 
   return true;
 }
 
 void ots_gsub_free(OpenTypeFile *file) {
   delete file->gsub;
 }
--- a/gfx/ots/src/hdmx.cc
+++ b/gfx/ots/src/hdmx.cc
@@ -2,45 +2,51 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "hdmx.h"
 #include "head.h"
 #include "maxp.h"
 
 // hdmx - Horizontal Device Metrics
-// http://www.microsoft.com/opentype/otspec/hdmx.htm
+// http://www.microsoft.com/typography/otspec/hdmx.htm
+
+#define TABLE_NAME "hdmx"
 
 #define DROP_THIS_TABLE \
-  do { delete file->hdmx; file->hdmx = 0; } while (0)
+  do { \
+    delete file->hdmx; \
+    file->hdmx = 0; \
+    OTS_FAILURE_MSG("Table discarded"); \
+  } while (0)
 
 namespace ots {
 
 bool ots_hdmx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   file->hdmx = new OpenTypeHDMX;
   OpenTypeHDMX * const hdmx = file->hdmx;
 
   if (!file->head || !file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing maxp or head tables in font, needed by hdmx");
   }
 
   if ((file->head->flags & 0x14) == 0) {
     // http://www.microsoft.com/typography/otspec/recom.htm
     OTS_WARNING("the table should not be present when bit 2 and 4 of the "
                 "head->flags are not set");
     DROP_THIS_TABLE;
     return true;
   }
 
   int16_t num_recs;
   if (!table.ReadU16(&hdmx->version) ||
       !table.ReadS16(&num_recs) ||
       !table.ReadS32(&hdmx->size_device_record)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read hdmx header");
   }
   if (hdmx->version != 0) {
     OTS_WARNING("bad version: %u", hdmx->version);
     DROP_THIS_TABLE;
     return true;
   }
   if (num_recs <= 0) {
     OTS_WARNING("bad num_recs: %d", num_recs);
@@ -51,48 +57,48 @@ bool ots_hdmx_parse(OpenTypeFile *file, 
   if (hdmx->size_device_record < actual_size_device_record) {
     OTS_WARNING("bad hdmx->size_device_record: %d", hdmx->size_device_record);
     DROP_THIS_TABLE;
     return true;
   }
 
   hdmx->pad_len = hdmx->size_device_record - actual_size_device_record;
   if (hdmx->pad_len > 3) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad padding %d", hdmx->pad_len);
   }
 
   uint8_t last_pixel_size = 0;
   hdmx->records.reserve(num_recs);
   for (int i = 0; i < num_recs; ++i) {
     OpenTypeHDMXDeviceRecord rec;
 
     if (!table.ReadU8(&rec.pixel_size) ||
         !table.ReadU8(&rec.max_width)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read hdmx record %d", i);
     }
     if ((i != 0) &&
         (rec.pixel_size <= last_pixel_size)) {
       OTS_WARNING("records are not sorted");
       DROP_THIS_TABLE;
       return true;
     }
     last_pixel_size = rec.pixel_size;
 
     rec.widths.reserve(file->maxp->num_glyphs);
     for (unsigned j = 0; j < file->maxp->num_glyphs; ++j) {
       uint8_t width;
       if (!table.ReadU8(&width)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read glyph width %d in record %d", j, i);
       }
       rec.widths.push_back(width);
     }
 
     if ((hdmx->pad_len > 0) &&
         !table.Skip(hdmx->pad_len)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to skip padding %d", hdmx->pad_len);
     }
 
     hdmx->records.push_back(rec);
   }
 
   return true;
 }
 
@@ -103,29 +109,29 @@ bool ots_hdmx_should_serialise(OpenTypeF
 }
 
 bool ots_hdmx_serialise(OTSStream *out, OpenTypeFile *file) {
   OpenTypeHDMX * const hdmx = file->hdmx;
 
   if (!out->WriteU16(hdmx->version) ||
       !out->WriteS16(hdmx->records.size()) ||
       !out->WriteS32(hdmx->size_device_record)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write hdmx header");
   }
 
   for (unsigned i = 0; i < hdmx->records.size(); ++i) {
     const OpenTypeHDMXDeviceRecord& rec = hdmx->records[i];
     if (!out->Write(&rec.pixel_size, 1) ||
         !out->Write(&rec.max_width, 1) ||
         !out->Write(&rec.widths[0], rec.widths.size())) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write hdmx record %d", i);
     }
     if ((hdmx->pad_len > 0) &&
         !out->Write((const uint8_t *)"\x00\x00\x00", hdmx->pad_len)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write hdmx padding of length %d", hdmx->pad_len);
     }
   }
 
   return true;
 }
 
 void ots_hdmx_free(OpenTypeFile *file) {
   delete file->hdmx;
--- a/gfx/ots/src/head.cc
+++ b/gfx/ots/src/head.cc
@@ -2,118 +2,120 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "head.h"
 
 #include <cstring>
 
 // head - Font Header
-// http://www.microsoft.com/opentype/otspec/head.htm
+// http://www.microsoft.com/typography/otspec/head.htm
+
+#define TABLE_NAME "head"
 
 namespace ots {
 
 bool ots_head_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   file->head = new OpenTypeHEAD;
 
   uint32_t version = 0;
   if (!table.ReadU32(&version) ||
       !table.ReadU32(&file->head->revision)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read head header");
   }
 
   if (version >> 16 != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad head table version of %d", version);
   }
 
   // Skip the checksum adjustment
   if (!table.Skip(4)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read checksum");
   }
 
   uint32_t magic;
   if (!table.ReadTag(&magic) ||
       std::memcmp(&magic, "\x5F\x0F\x3C\xF5", 4)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read font magic number");
   }
 
   if (!table.ReadU16(&file->head->flags)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read head flags");
   }
 
   // We allow bits 0..4, 11..13
   file->head->flags &= 0x381f;
 
   if (!table.ReadU16(&file->head->ppem)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read pixels per em");
   }
 
   // ppem must be in range
   if (file->head->ppem < 16 ||
       file->head->ppem > 16384) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad ppm of %d", file->head->ppem);
   }
 
   // ppem must be a power of two
 #if 0
   // We don't call ots_failure() for now since lots of TrueType fonts are
   // not following this rule. Putting OTS_WARNING here is too noisy.
   if ((file->head->ppem - 1) & file->head->ppem) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("ppm not a power of two: %d", file->head->ppem);
   }
 #endif
 
   if (!table.ReadR64(&file->head->created) ||
       !table.ReadR64(&file->head->modified)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't read font dates");
   }
 
   if (!table.ReadS16(&file->head->xmin) ||
       !table.ReadS16(&file->head->ymin) ||
       !table.ReadS16(&file->head->xmax) ||
       !table.ReadS16(&file->head->ymax)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read font bounding box");
   }
 
   if (file->head->xmin > file->head->xmax) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad x dimension in the font bounding box (%d, %d)", file->head->xmin, file->head->xmax);
   }
   if (file->head->ymin > file->head->ymax) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad y dimension in the font bounding box (%d, %d)", file->head->ymin, file->head->ymax);
   }
 
   if (!table.ReadU16(&file->head->mac_style)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read font style");
   }
 
   // We allow bits 0..6
   file->head->mac_style &= 0x7f;
 
   if (!table.ReadU16(&file->head->min_ppem)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read font minimum ppm");
   }
 
   // We don't care about the font direction hint
   if (!table.Skip(2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip font direction hint");
   }
 
   if (!table.ReadS16(&file->head->index_to_loc_format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read index to loc format");
   }
   if (file->head->index_to_loc_format < 0 ||
       file->head->index_to_loc_format > 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad index to loc format %d", file->head->index_to_loc_format);
   }
 
   int16_t glyph_data_format;
   if (!table.ReadS16(&glyph_data_format) ||
       glyph_data_format) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read glyph data format");
   }
 
   return true;
 }
 
 bool ots_head_should_serialise(OpenTypeFile *file) {
   return file->head != NULL;
 }
@@ -131,17 +133,17 @@ bool ots_head_serialise(OTSStream *out, 
       !out->WriteS16(file->head->ymin) ||
       !out->WriteS16(file->head->xmax) ||
       !out->WriteS16(file->head->ymax) ||
       !out->WriteU16(file->head->mac_style) ||
       !out->WriteU16(file->head->min_ppem) ||
       !out->WriteS16(2) ||
       !out->WriteS16(file->head->index_to_loc_format) ||
       !out->WriteS16(0)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write head table");
   }
 
   return true;
 }
 
 void ots_head_free(OpenTypeFile *file) {
   delete file->head;
 }
--- a/gfx/ots/src/hhea.cc
+++ b/gfx/ots/src/hhea.cc
@@ -3,46 +3,48 @@
 // found in the LICENSE file.
 
 #include "hhea.h"
 
 #include "head.h"
 #include "maxp.h"
 
 // hhea - Horizontal Header
-// http://www.microsoft.com/opentype/otspec/hhea.htm
+// http://www.microsoft.com/typography/otspec/hhea.htm
+
+#define TABLE_NAME "hhea"
 
 namespace ots {
 
 bool ots_hhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   OpenTypeHHEA *hhea = new OpenTypeHHEA;
   file->hhea = hhea;
 
   if (!table.ReadU32(&hhea->header.version)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read hhea version");
   }
   if (hhea->header.version >> 16 != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad hhea version of %d", hhea->header.version);
   }
 
   if (!ParseMetricsHeader(file, &table, &hhea->header)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse horizontal metrics");
   }
 
   return true;
 }
 
 bool ots_hhea_should_serialise(OpenTypeFile *file) {
   return file->hhea != NULL;
 }
 
 bool ots_hhea_serialise(OTSStream *out, OpenTypeFile *file) {
-  if (!SerialiseMetricsHeader(out, &file->hhea->header)) {
-    return OTS_FAILURE();
+  if (!SerialiseMetricsHeader(file, out, &file->hhea->header)) {
+    return OTS_FAILURE_MSG("Failed to serialise horizontal metrics");
   }
   return true;
 }
 
 void ots_hhea_free(OpenTypeFile *file) {
   delete file->hhea;
 }
 
--- a/gfx/ots/src/hmtx.cc
+++ b/gfx/ots/src/hmtx.cc
@@ -3,44 +3,46 @@
 // found in the LICENSE file.
 
 #include "hmtx.h"
 
 #include "hhea.h"
 #include "maxp.h"
 
 // hmtx - Horizontal Metrics
-// http://www.microsoft.com/opentype/otspec/hmtx.htm
+// http://www.microsoft.com/typography/otspec/hmtx.htm
+
+#define TABLE_NAME "hmtx"
 
 namespace ots {
 
 bool ots_hmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   OpenTypeHMTX *hmtx = new OpenTypeHMTX;
   file->hmtx = hmtx;
 
   if (!file->hhea || !file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing hhea or maxp tables in font, needed by hmtx");
   }
 
-  if (!ParseMetricsTable(&table, file->maxp->num_glyphs,
+  if (!ParseMetricsTable(file, &table, file->maxp->num_glyphs,
                          &file->hhea->header, &hmtx->metrics)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse hmtx metrics");
   }
 
   return true;
 }
 
 bool ots_hmtx_should_serialise(OpenTypeFile *file) {
   return file->hmtx != NULL;
 }
 
 bool ots_hmtx_serialise(OTSStream *out, OpenTypeFile *file) {
-  if (!SerialiseMetricsTable(out, &file->hmtx->metrics)) {
-    return OTS_FAILURE();
+  if (!SerialiseMetricsTable(file, out, &file->hmtx->metrics)) {
+    return OTS_FAILURE_MSG("Failed to serialise htmx metrics");
   }
   return true;
 }
 
 void ots_hmtx_free(OpenTypeFile *file) {
   delete file->hmtx;
 }
 
--- a/gfx/ots/src/kern.cc
+++ b/gfx/ots/src/kern.cc
@@ -1,32 +1,38 @@
 // 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.
 
 #include "kern.h"
 
 // kern - Kerning
-// http://www.microsoft.com/opentype/otspec/kern.htm
+// http://www.microsoft.com/typography/otspec/kern.htm
+
+#define TABLE_NAME "kern"
 
 #define DROP_THIS_TABLE \
-  do { delete file->kern; file->kern = 0; } while (0)
+  do { \
+    delete file->kern; \
+    file->kern = 0; \
+    OTS_FAILURE_MSG("Table discarded"); \
+  } while (0)
 
 namespace ots {
 
 bool ots_kern_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypeKERN *kern = new OpenTypeKERN;
   file->kern = kern;
 
   uint16_t num_tables = 0;
   if (!table.ReadU16(&kern->version) ||
       !table.ReadU16(&num_tables)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read kern header");
   }
 
   if (kern->version > 0) {
     DROP_THIS_TABLE;
     return true;
   }
 
   if (num_tables == 0) {
@@ -37,31 +43,31 @@ bool ots_kern_parse(OpenTypeFile *file, 
 
   kern->subtables.reserve(num_tables);
   for (unsigned i = 0; i < num_tables; ++i) {
     OpenTypeKERNFormat0 subtable;
     uint16_t sub_length = 0;
 
     if (!table.ReadU16(&subtable.version) ||
         !table.ReadU16(&sub_length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read kern subtable %d header", i);
     }
 
     if (subtable.version > 0) {
       OTS_WARNING("Bad subtable version: %d", subtable.version);
       continue;
     }
 
     const size_t current_offset = table.offset();
     if (current_offset - 4 + sub_length > length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad kern subtable %d offset %ld", i, current_offset);
     }
 
     if (!table.ReadU16(&subtable.coverage)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Cailed to read kern subtable %d coverage", i);
     }
 
     if (!(subtable.coverage & 0x1)) {
       OTS_WARNING(
           "We don't support vertical data as the renderer doesn't support it.");
       continue;
     }
     if (subtable.coverage & 0xF0) {
@@ -76,17 +82,17 @@ bool ots_kern_parse(OpenTypeFile *file, 
     }
 
     // Parse the format 0 field.
     uint16_t num_pairs = 0;
     if (!table.ReadU16(&num_pairs) ||
         !table.ReadU16(&subtable.search_range) ||
         !table.ReadU16(&subtable.entry_selector) ||
         !table.ReadU16(&subtable.range_shift)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read kern subtable %d format 0 fields", i);
     }
 
     if (!num_pairs) {
       OTS_WARNING("Zero length subtable is found.");
       DROP_THIS_TABLE;
       return true;
     }
 
@@ -104,34 +110,34 @@ bool ots_kern_parse(OpenTypeFile *file, 
       ++max_pow2;
     }
     const uint16_t expected_search_range = (1u << max_pow2) * kFormat0PairSize;
     if (subtable.search_range != expected_search_range) {
       OTS_WARNING("bad search range");
       subtable.search_range = expected_search_range;
     }
     if (subtable.entry_selector != max_pow2) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad subtable %d entry selector %d", i, subtable.entry_selector);
     }
     const uint32_t expected_range_shift
         = kFormat0PairSize * num_pairs - subtable.search_range;
     if (subtable.range_shift != expected_range_shift) {
       OTS_WARNING("bad range shift");
       subtable.range_shift = expected_range_shift;
     }
 
     // Read kerning pairs.
     subtable.pairs.reserve(num_pairs);
     uint32_t last_pair = 0;
     for (unsigned j = 0; j < num_pairs; ++j) {
       OpenTypeKERNFormat0Pair kerning_pair;
       if (!table.ReadU16(&kerning_pair.left) ||
           !table.ReadU16(&kerning_pair.right) ||
           !table.ReadS16(&kerning_pair.value)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read subtable %d kerning pair %d", i, j);
       }
       const uint32_t current_pair
           = (kerning_pair.left << 16) + kerning_pair.right;
       if (j != 0 && current_pair <= last_pair) {
         OTS_WARNING("Kerning pairs are not sorted.");
         // Many free fonts don't follow this rule, so we don't call OTS_FAILURE
         // in order to support these fonts.
         DROP_THIS_TABLE;
@@ -158,35 +164,35 @@ bool ots_kern_should_serialise(OpenTypeF
   return file->kern != NULL;
 }
 
 bool ots_kern_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeKERN *kern = file->kern;
 
   if (!out->WriteU16(kern->version) ||
       !out->WriteU16(kern->subtables.size())) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Can't write kern table header");
   }
 
   for (unsigned i = 0; i < kern->subtables.size(); ++i) {
     const uint16_t length = 14 + (6 * kern->subtables[i].pairs.size());
     if (!out->WriteU16(kern->subtables[i].version) ||
         !out->WriteU16(length) ||
         !out->WriteU16(kern->subtables[i].coverage) ||
         !out->WriteU16(kern->subtables[i].pairs.size()) ||
         !out->WriteU16(kern->subtables[i].search_range) ||
         !out->WriteU16(kern->subtables[i].entry_selector) ||
         !out->WriteU16(kern->subtables[i].range_shift)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write kern subtable %d", i);
     }
     for (unsigned j = 0; j < kern->subtables[i].pairs.size(); ++j) {
       if (!out->WriteU16(kern->subtables[i].pairs[j].left) ||
           !out->WriteU16(kern->subtables[i].pairs[j].right) ||
           !out->WriteS16(kern->subtables[i].pairs[j].value)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to write kern pair %d for subtable %d", j, i);
       }
     }
   }
 
   return true;
 }
 
 void ots_kern_free(OpenTypeFile *file) {
--- a/gfx/ots/src/layout.cc
+++ b/gfx/ots/src/layout.cc
@@ -7,16 +7,18 @@
 #include <limits>
 #include <vector>
 
 #include "gdef.h"
 
 // OpenType Layout Common Table Formats
 // http://www.microsoft.com/typography/otspec/chapter2.htm
 
+#define TABLE_NAME "Layout" // XXX: use individual table names
+
 namespace {
 
 // The 'DFLT' tag of script table.
 const uint32_t kScriptTableTagDflt = 0x44464c54;
 // The value which represents there is no required feature index.
 const uint16_t kNoRequiredFeatureIndexDefined = 0xFFFF;
 // The lookup flag bit which indicates existence of MarkFilteringSet.
 const uint16_t kUseMarkFilteringSetBit = 0x0010;
@@ -39,1444 +41,1472 @@ struct LangSysRecord {
   uint16_t offset;
 };
 
 struct FeatureRecord {
   uint32_t tag;
   uint16_t offset;
 };
 
-bool ParseLangSysTable(ots::Buffer *subtable, const uint32_t tag,
+bool ParseLangSysTable(const ots::OpenTypeFile *file,
+                       ots::Buffer *subtable, const uint32_t tag,
                        const uint16_t num_features) {
   uint16_t offset_lookup_order = 0;
   uint16_t req_feature_index = 0;
   uint16_t feature_count = 0;
   if (!subtable->ReadU16(&offset_lookup_order) ||
       !subtable->ReadU16(&req_feature_index) ||
       !subtable->ReadU16(&feature_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read langsys header for table %4s", (char *)&tag);
   }
   // |offset_lookup_order| is reserved and should be NULL.
   if (offset_lookup_order != 0) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookup offset order %d in table %4s", offset_lookup_order, (char *)&tag);
   }
   if (req_feature_index != kNoRequiredFeatureIndexDefined &&
       req_feature_index >= num_features) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad required features index %d in table %4s", req_feature_index, (char *)&tag);
   }
   if (feature_count > num_features) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad feature count %d in table %4s", feature_count, (char *)&tag);
   }
 
   for (unsigned i = 0; i < feature_count; ++i) {
     uint16_t feature_index = 0;
     if (!subtable->ReadU16(&feature_index)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read feature index %d in table %4s", i, (char *)&tag);
     }
     if (feature_index >= num_features) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad feature index %d for feature %d in table %4s", feature_index, i, (char *)&tag);
     }
   }
   return true;
 }
 
-bool ParseScriptTable(const uint8_t *data, const size_t length,
+bool ParseScriptTable(const ots::OpenTypeFile *file,
+                      const uint8_t *data, const size_t length,
                       const uint32_t tag, const uint16_t num_features) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_default_lang_sys = 0;
   uint16_t lang_sys_count = 0;
   if (!subtable.ReadU16(&offset_default_lang_sys) ||
       !subtable.ReadU16(&lang_sys_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read script header for table %4s", (char *)&tag);
   }
 
   // The spec requires a script table for 'DFLT' tag must contain non-NULL
   // |offset_default_lang_sys| and |lang_sys_count| == 0
   if (tag == kScriptTableTagDflt &&
       (offset_default_lang_sys == 0 || lang_sys_count != 0)) {
-    OTS_WARNING("DFLT table doesn't satisfy the spec.");
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("DFLT table doesn't satisfy the spec. in table %4s", (char *)&tag);
   }
 
   const unsigned lang_sys_record_end =
       6 * static_cast<unsigned>(lang_sys_count) + 4;
   if (lang_sys_record_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of langsys record %d in table %4s", lang_sys_record_end, (char *)&tag);
   }
 
   std::vector<LangSysRecord> lang_sys_records;
   lang_sys_records.resize(lang_sys_count);
   uint32_t last_tag = 0;
   for (unsigned i = 0; i < lang_sys_count; ++i) {
     if (!subtable.ReadU32(&lang_sys_records[i].tag) ||
         !subtable.ReadU16(&lang_sys_records[i].offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read langsys record header %d for table %4s", i, (char *)&tag);
     }
     // The record array must store the records alphabetically by tag
     if (last_tag != 0 && last_tag > lang_sys_records[i].tag) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad last tag %d for langsys record %d in table %4s", last_tag, i, (char *)&tag);
     }
     if (lang_sys_records[i].offset < lang_sys_record_end ||
         lang_sys_records[i].offset >= length) {
-      OTS_WARNING("bad offset to lang sys table: %x",
+      return OTS_FAILURE_MSG("bad offset to lang sys table: %x",
                   lang_sys_records[i].offset);
-      return OTS_FAILURE();
     }
     last_tag = lang_sys_records[i].tag;
   }
 
   // Check lang sys tables
   for (unsigned i = 0; i < lang_sys_count; ++i) {
     subtable.set_offset(lang_sys_records[i].offset);
-    if (!ParseLangSysTable(&subtable, lang_sys_records[i].tag, num_features)) {
-      return OTS_FAILURE();
+    if (!ParseLangSysTable(file, &subtable, lang_sys_records[i].tag, num_features)) {
+      return OTS_FAILURE_MSG("Failed to parse langsys table %d (%4s) in table %4s", i, (char *)&lang_sys_records[i].tag, (char *)&tag);
     }
   }
 
   return true;
 }
 
-bool ParseFeatureTable(const uint8_t *data, const size_t length,
+bool ParseFeatureTable(const ots::OpenTypeFile *file,
+                       const uint8_t *data, const size_t length,
                        const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_feature_params = 0;
   uint16_t lookup_count = 0;
   if (!subtable.ReadU16(&offset_feature_params) ||
       !subtable.ReadU16(&lookup_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read feature table header");
   }
 
   const unsigned feature_table_end =
       2 * static_cast<unsigned>(lookup_count) + 4;
   if (feature_table_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of feature table %d", feature_table_end);
   }
   // |offset_feature_params| is generally set to NULL.
   if (offset_feature_params != 0 &&
       (offset_feature_params < feature_table_end ||
        offset_feature_params >= length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Badd feature parames offset %d", offset_feature_params);
   }
 
   for (unsigned i = 0; i < lookup_count; ++i) {
     uint16_t lookup_index = 0;
     if (!subtable.ReadU16(&lookup_index)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read lookup index for lookup %d", i);
     }
     // lookup index starts with 0.
     if (lookup_index >= num_lookups) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad lookup index %d for lookup %d", lookup_index, i);
     }
   }
   return true;
 }
 
 bool ParseLookupTable(ots::OpenTypeFile *file, const uint8_t *data,
                       const size_t length,
                       const ots::LookupSubtableParser* parser) {
   ots::Buffer subtable(data, length);
 
   uint16_t lookup_type = 0;
   uint16_t lookup_flag = 0;
   uint16_t subtable_count = 0;
   if (!subtable.ReadU16(&lookup_type) ||
       !subtable.ReadU16(&lookup_flag) ||
       !subtable.ReadU16(&subtable_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read lookup table header");
   }
 
   if (lookup_type == 0 || lookup_type > parser->num_types) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookup type %d", lookup_type);
   }
 
   // Check lookup flags.
   if ((lookup_flag & kGdefRequiredFlags) &&
       (!file->gdef || !file->gdef->has_glyph_class_def)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookup flags %d", lookup_flag);
   }
   if ((lookup_flag & kMarkAttachmentTypeMask) &&
       (!file->gdef || !file->gdef->has_mark_attachment_class_def)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("lookup flag asks for mark attachment that is bad %d", lookup_flag);
   }
   bool use_mark_filtering_set = false;
   if (lookup_flag & kUseMarkFilteringSetBit) {
     if (!file->gdef || !file->gdef->has_mark_glyph_sets_def) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("lookup flag asks for mark filtering that is bad %d", lookup_flag);
     }
     use_mark_filtering_set = true;
   }
 
   std::vector<uint16_t> subtables;
   subtables.reserve(subtable_count);
   // If the |kUseMarkFilteringSetBit| of |lookup_flag| is set,
   // extra 2 bytes will follow after subtable offset array.
   const unsigned lookup_table_end = 2 * static_cast<unsigned>(subtable_count) +
       (use_mark_filtering_set ? 8 : 6);
   if (lookup_table_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of lookup %d", lookup_table_end);
   }
   for (unsigned i = 0; i < subtable_count; ++i) {
     uint16_t offset_subtable = 0;
     if (!subtable.ReadU16(&offset_subtable)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read subtable offset %d", i);
     }
     if (offset_subtable < lookup_table_end ||
         offset_subtable >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad subtable offset %d for subtable %d", offset_subtable, i);
     }
     subtables.push_back(offset_subtable);
   }
   if (subtables.size() != subtable_count) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad subtable size %ld", subtables.size());
   }
 
   if (use_mark_filtering_set) {
     uint16_t mark_filtering_set = 0;
     if (!subtable.ReadU16(&mark_filtering_set)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read mark filtering set");
     }
     if (file->gdef->num_mark_glyph_sets == 0 ||
         mark_filtering_set >= file->gdef->num_mark_glyph_sets) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad mark filtering set %d", mark_filtering_set);
     }
   }
 
   // Parse lookup subtables for this lookup type.
   for (unsigned i = 0; i < subtable_count; ++i) {
     if (!parser->Parse(file, data + subtables[i], length - subtables[i],
                        lookup_type)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse subtable %d", i);
     }
   }
   return true;
 }
 
-bool ParseClassDefFormat1(const uint8_t *data, size_t length,
+bool ParseClassDefFormat1(const ots::OpenTypeFile *file,
+                          const uint8_t *data, size_t length,
                           const uint16_t num_glyphs,
                           const uint16_t num_classes) {
   ots::Buffer subtable(data, length);
 
   // Skip format field.
   if (!subtable.Skip(2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip class definition header");
   }
 
   uint16_t start_glyph = 0;
   if (!subtable.ReadU16(&start_glyph)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read starting glyph of class definition");
   }
   if (start_glyph > num_glyphs) {
     OTS_WARNING("bad start glyph ID: %u", start_glyph);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad starting glyph %d in class definition", start_glyph);
   }
 
   uint16_t glyph_count = 0;
   if (!subtable.ReadU16(&glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read glyph count in class definition");
   }
   if (glyph_count > num_glyphs) {
-    OTS_WARNING("bad glyph count: %u", glyph_count);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad glyph count: %u", glyph_count);
   }
   for (unsigned i = 0; i < glyph_count; ++i) {
     uint16_t class_value = 0;
     if (!subtable.ReadU16(&class_value)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read class value for glyph %d in class definition", i);
     }
     if (class_value > num_classes) {
       OTS_WARNING("bad class value: %u", class_value);
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad class value %d for glyph %d in class definition", class_value, i);
     }
   }
 
   return true;
 }
 
-bool ParseClassDefFormat2(const uint8_t *data, size_t length,
+bool ParseClassDefFormat2(const ots::OpenTypeFile *file,
+                          const uint8_t *data, size_t length,
                           const uint16_t num_glyphs,
                           const uint16_t num_classes) {
   ots::Buffer subtable(data, length);
 
   // Skip format field.
   if (!subtable.Skip(2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip format of class defintion header");
   }
 
   uint16_t range_count = 0;
   if (!subtable.ReadU16(&range_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read range count in class definition");
   }
   if (range_count > num_glyphs) {
-    OTS_WARNING("bad range count: %u", range_count);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad range count: %u", range_count);
   }
 
   uint16_t last_end = 0;
   for (unsigned i = 0; i < range_count; ++i) {
     uint16_t start = 0;
     uint16_t end = 0;
     uint16_t class_value = 0;
     if (!subtable.ReadU16(&start) ||
         !subtable.ReadU16(&end) ||
         !subtable.ReadU16(&class_value)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read class definition reange %d", i);
     }
     if (start > end || (last_end && start <= last_end)) {
-      OTS_WARNING("glyph range is overlapping.");
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("glyph range is overlapping.in range %d", i);
     }
     if (class_value > num_classes) {
-      OTS_WARNING("bad class value: %u", class_value);
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad class value: %u", class_value);
     }
     last_end = end;
   }
 
   return true;
 }
 
-bool ParseCoverageFormat1(const uint8_t *data, size_t length,
-                          const uint16_t num_glyphs) {
+bool ParseCoverageFormat1(const ots::OpenTypeFile *file,
+                          const uint8_t *data, size_t length,
+                          const uint16_t num_glyphs,
+                          const uint16_t expected_num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Skip format field.
   if (!subtable.Skip(2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip coverage format");
   }
 
   uint16_t glyph_count = 0;
   if (!subtable.ReadU16(&glyph_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read glyph count in coverage");
   }
   if (glyph_count > num_glyphs) {
-    OTS_WARNING("bad glyph count: %u", glyph_count);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad glyph count: %u", glyph_count);
   }
   for (unsigned i = 0; i < glyph_count; ++i) {
     uint16_t glyph = 0;
     if (!subtable.ReadU16(&glyph)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read glyph %d in coverage", i);
     }
     if (glyph > num_glyphs) {
-      OTS_WARNING("bad glyph ID: %u", glyph);
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad glyph ID: %u", glyph);
     }
   }
 
+  if (expected_num_glyphs && expected_num_glyphs != glyph_count) {
+      return OTS_FAILURE_MSG("unexpected number of glyphs: %u", glyph_count);
+  }
+
   return true;
 }
 
-bool ParseCoverageFormat2(const uint8_t *data, size_t length,
-                          const uint16_t num_glyphs) {
+bool ParseCoverageFormat2(const ots::OpenTypeFile *file,
+                          const uint8_t *data, size_t length,
+                          const uint16_t num_glyphs,
+                          const uint16_t expected_num_glyphs) {
   ots::Buffer subtable(data, length);
 
   // Skip format field.
   if (!subtable.Skip(2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip format of coverage type 2");
   }
 
   uint16_t range_count = 0;
   if (!subtable.ReadU16(&range_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read range count in coverage");
   }
   if (range_count > num_glyphs) {
-    OTS_WARNING("bad range count: %u", range_count);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad range count: %u", range_count);
   }
   uint16_t last_end = 0;
   uint16_t last_start_coverage_index = 0;
   for (unsigned i = 0; i < range_count; ++i) {
     uint16_t start = 0;
     uint16_t end = 0;
     uint16_t start_coverage_index = 0;
     if (!subtable.ReadU16(&start) ||
         !subtable.ReadU16(&end) ||
         !subtable.ReadU16(&start_coverage_index)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read range %d in coverage", i);
     }
 
     // Some of the Adobe Pro fonts have ranges that overlap by one element: the
     // start of one range is equal to the end of the previous range. Therefore
     // the < in the following condition should be <= were it not for this.
     // See crbug.com/134135.
     if (start > end || (last_end && start < last_end)) {
-      OTS_WARNING("glyph range is overlapping.");
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("glyph range is overlapping.");
     }
     if (start_coverage_index != last_start_coverage_index) {
-      OTS_WARNING("bad start coverage index.");
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("bad start coverage index.");
     }
     last_end = end;
     last_start_coverage_index += end - start + 1;
   }
 
+  if (expected_num_glyphs &&
+      expected_num_glyphs != last_start_coverage_index) {
+      return OTS_FAILURE_MSG("unexpected number of glyphs: %u", last_start_coverage_index);
+  }
+
   return true;
 }
 
 // Parsers for Contextual subtables in GSUB/GPOS tables.
 
-bool ParseLookupRecord(ots::Buffer *subtable, const uint16_t num_glyphs,
+bool ParseLookupRecord(const ots::OpenTypeFile *file,
+                       ots::Buffer *subtable, const uint16_t num_glyphs,
                        const uint16_t num_lookups) {
   uint16_t sequence_index = 0;
   uint16_t lookup_list_index = 0;
   if (!subtable->ReadU16(&sequence_index) ||
       !subtable->ReadU16(&lookup_list_index)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header for lookup record");
   }
   if (sequence_index >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad sequence index %d in lookup record", sequence_index);
   }
   if (lookup_list_index >= num_lookups) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookup list index %d in lookup record", lookup_list_index);
   }
   return true;
 }
 
-bool ParseRuleSubtable(const uint8_t *data, const size_t length,
+bool ParseRuleSubtable(const ots::OpenTypeFile *file,
+                       const uint8_t *data, const size_t length,
                        const uint16_t num_glyphs,
                        const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t glyph_count = 0;
   uint16_t lookup_count = 0;
   if (!subtable.ReadU16(&glyph_count) ||
       !subtable.ReadU16(&lookup_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read rule subtable header");
   }
 
   if (glyph_count == 0 || glyph_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad glyph count %d in rule subtable", glyph_count);
   }
   for (unsigned i = 0; i < glyph_count - static_cast<unsigned>(1); ++i) {
     uint16_t glyph_id = 0;
     if (!subtable.ReadU16(&glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read glyph %d", i);
     }
     if (glyph_id > num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad glyph %d for entry %d", glyph_id, i);
     }
   }
 
   for (unsigned i = 0; i < lookup_count; ++i) {
-    if (!ParseLookupRecord(&subtable, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseLookupRecord(file, &subtable, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse lookup record %d", i);
     }
   }
   return true;
 }
 
-bool ParseRuleSetTable(const uint8_t *data, const size_t length,
+bool ParseRuleSetTable(const ots::OpenTypeFile *file,
+                       const uint8_t *data, const size_t length,
                        const uint16_t num_glyphs,
                        const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t rule_count = 0;
   if (!subtable.ReadU16(&rule_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read rule count in rule set");
   }
   const unsigned rule_end = 2 * static_cast<unsigned>(rule_count) + 2;
   if (rule_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of rule %d in rule set", rule_end);
   }
 
   for (unsigned i = 0; i < rule_count; ++i) {
     uint16_t offset_rule = 0;
     if (!subtable.ReadU16(&offset_rule)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read rule offset for rule set %d", i);
     }
     if (offset_rule < rule_end || offset_rule >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad rule offset %d in set %d", offset_rule, i);
     }
-    if (!ParseRuleSubtable(data + offset_rule, length - offset_rule,
+    if (!ParseRuleSubtable(file, data + offset_rule, length - offset_rule,
                            num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse rule set %d", i);
     }
   }
 
   return true;
 }
 
-bool ParseContextFormat1(const uint8_t *data, const size_t length,
+bool ParseContextFormat1(const ots::OpenTypeFile *file,
+                         const uint8_t *data, const size_t length,
                          const uint16_t num_glyphs,
                          const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_coverage = 0;
   uint16_t rule_set_count = 0;
   // Skip format field.
   if (!subtable.Skip(2) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&rule_set_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header of context format 1");
   }
 
   const unsigned rule_set_end = static_cast<unsigned>(6) +
       rule_set_count * 2;
   if (rule_set_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of rule set %d of context format 1", rule_set_end);
   }
   if (offset_coverage < rule_set_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d in context format 1", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table in context format 1");
   }
 
   for (unsigned i = 0; i < rule_set_count; ++i) {
     uint16_t offset_rule = 0;
     if (!subtable.ReadU16(&offset_rule)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read rule offset %d in context format 1", i);
     }
     if (offset_rule < rule_set_end || offset_rule >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad rule offset %d in rule %d in context format 1", offset_rule, i);
     }
-    if (!ParseRuleSetTable(data + offset_rule, length - offset_rule,
+    if (!ParseRuleSetTable(file, data + offset_rule, length - offset_rule,
                            num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse rule set %d in context format 1", i);
     }
   }
 
   return true;
 }
 
-bool ParseClassRuleTable(const uint8_t *data, const size_t length,
+bool ParseClassRuleTable(const ots::OpenTypeFile *file,
+                         const uint8_t *data, const size_t length,
                          const uint16_t num_glyphs,
                          const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t glyph_count = 0;
   uint16_t lookup_count = 0;
   if (!subtable.ReadU16(&glyph_count) ||
       !subtable.ReadU16(&lookup_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header of class rule table");
   }
 
   if (glyph_count == 0 || glyph_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad glyph count %d in class rule table", glyph_count);
   }
 
   // ClassRule table contains an array of classes. Each value of classes
   // could take arbitrary values including zero so we don't check these value.
   const unsigned num_classes = glyph_count - static_cast<unsigned>(1);
   if (!subtable.Skip(2 * num_classes)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip classes in class rule table");
   }
 
   for (unsigned i = 0; i < lookup_count; ++i) {
-    if (!ParseLookupRecord(&subtable, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseLookupRecord(file, &subtable, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse lookup record %d in class rule table", i);
     }
   }
   return true;
 }
 
-bool ParseClassSetTable(const uint8_t *data, const size_t length,
+bool ParseClassSetTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, const size_t length,
                         const uint16_t num_glyphs,
                         const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t class_rule_count = 0;
   if (!subtable.ReadU16(&class_rule_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read class rule count in class set table");
   }
   const unsigned class_rule_end =
       2 * static_cast<unsigned>(class_rule_count) + 2;
   if (class_rule_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad class rule end %d in class set table", class_rule_end);
   }
   for (unsigned i = 0; i < class_rule_count; ++i) {
     uint16_t offset_class_rule = 0;
     if (!subtable.ReadU16(&offset_class_rule)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read class rule offset %d in class set table", i);
     }
     if (offset_class_rule < class_rule_end || offset_class_rule >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad class rule offset %d in class %d", offset_class_rule, i);
     }
-    if (!ParseClassRuleTable(data + offset_class_rule,
+    if (!ParseClassRuleTable(file, data + offset_class_rule,
                              length - offset_class_rule, num_glyphs,
                              num_lookups)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse class rule table %d", i);
     }
   }
 
   return true;
 }
 
-bool ParseContextFormat2(const uint8_t *data, const size_t length,
-                            const uint16_t num_glyphs,
-                            const uint16_t num_lookups) {
+bool ParseContextFormat2(const ots::OpenTypeFile *file,
+                         const uint8_t *data, const size_t length,
+                         const uint16_t num_glyphs,
+                         const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_coverage = 0;
   uint16_t offset_class_def = 0;
   uint16_t class_set_cnt = 0;
   // Skip format field.
   if (!subtable.Skip(2) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&offset_class_def) ||
       !subtable.ReadU16(&class_set_cnt)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header for context format 2");
   }
 
   const unsigned class_set_end = 2 * static_cast<unsigned>(class_set_cnt) + 8;
   if (class_set_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of class set %d for context format 2", class_set_end);
   }
   if (offset_coverage < class_set_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d in context format 2", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table in context format 2");
   }
 
   if (offset_class_def < class_set_end || offset_class_def >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad class definition offset %d in context format 2", offset_class_def);
   }
-  if (!ots::ParseClassDefTable(data + offset_class_def,
+  if (!ots::ParseClassDefTable(file, data + offset_class_def,
                                length - offset_class_def,
                                num_glyphs, kMaxClassDefValue)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse class definition table in context format 2");
   }
 
   for (unsigned i = 0; i < class_set_cnt; ++i) {
     uint16_t offset_class_rule = 0;
     if (!subtable.ReadU16(&offset_class_rule)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read class rule offset %d in context format 2", i);
     }
     if (offset_class_rule) {
       if (offset_class_rule < class_set_end || offset_class_rule >= length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad class rule offset %d for rule %d in context format 2", offset_class_rule, i);
       }
-      if (!ParseClassSetTable(data + offset_class_rule,
+      if (!ParseClassSetTable(file, data + offset_class_rule,
                               length - offset_class_rule, num_glyphs,
                               num_lookups)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse class set %d in context format 2", i);
       }
     }
   }
 
   return true;
 }
 
-bool ParseContextFormat3(const uint8_t *data, const size_t length,
+bool ParseContextFormat3(const ots::OpenTypeFile *file,
+                         const uint8_t *data, const size_t length,
                          const uint16_t num_glyphs,
                          const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t glyph_count = 0;
   uint16_t lookup_count = 0;
   // Skip format field.
   if (!subtable.Skip(2) ||
       !subtable.ReadU16(&glyph_count) ||
       !subtable.ReadU16(&lookup_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header in context format 3");
   }
 
   if (glyph_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad glyph count %d in context format 3", glyph_count);
   }
   const unsigned lookup_record_end = 2 * static_cast<unsigned>(glyph_count) +
       4 * static_cast<unsigned>(lookup_count) + 6;
   if (lookup_record_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of lookup %d in context format 3", lookup_record_end);
   }
   for (unsigned i = 0; i < glyph_count; ++i) {
     uint16_t offset_coverage = 0;
     if (!subtable.ReadU16(&offset_coverage)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read coverage offset %d in conxtext format 3", i);
     }
     if (offset_coverage < lookup_record_end || offset_coverage >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad coverage offset %d for glyph %d in context format 3", offset_coverage, i);
     }
-    if (!ots::ParseCoverageTable(data + offset_coverage,
+    if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                  length - offset_coverage, num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse coverage table for glyph %d in context format 3", i);
     }
   }
 
   for (unsigned i = 0; i < lookup_count; ++i) {
-    if (!ParseLookupRecord(&subtable, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseLookupRecord(file, &subtable, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse lookup record %d in context format 3", i);
     }
   }
 
   return true;
 }
 
 // Parsers for Chaning Contextual subtables in GSUB/GPOS tables.
 
-bool ParseChainRuleSubtable(const uint8_t *data, const size_t length,
+bool ParseChainRuleSubtable(const ots::OpenTypeFile *file,
+                            const uint8_t *data, const size_t length,
                             const uint16_t num_glyphs,
                             const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t backtrack_count = 0;
   if (!subtable.ReadU16(&backtrack_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read backtrack count in chain rule subtable");
   }
   if (backtrack_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad backtrack count %d in chain rule subtable", backtrack_count);
   }
   for (unsigned i = 0; i < backtrack_count; ++i) {
     uint16_t glyph_id = 0;
     if (!subtable.ReadU16(&glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read backtrack glyph %d in chain rule subtable", i);
     }
     if (glyph_id > num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad glyph id %d for bactrack glyph %d in chain rule subtable", glyph_id, i);
     }
   }
 
   uint16_t input_count = 0;
   if (!subtable.ReadU16(&input_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read input count in chain rule subtable");
   }
   if (input_count == 0 || input_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad input count %d in chain rule subtable", input_count);
   }
   for (unsigned i = 0; i < input_count - static_cast<unsigned>(1); ++i) {
     uint16_t glyph_id = 0;
     if (!subtable.ReadU16(&glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read input glyph %d in chain rule subtable", i);
     }
     if (glyph_id > num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad glyph id %d for input glyph %d in chain rule subtable", glyph_id, i);
     }
   }
 
   uint16_t lookahead_count = 0;
   if (!subtable.ReadU16(&lookahead_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read lookahead count in chain rule subtable");
   }
   if (lookahead_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookahead count %d in chain rule subtable", lookahead_count);
   }
   for (unsigned i = 0; i < lookahead_count; ++i) {
     uint16_t glyph_id = 0;
     if (!subtable.ReadU16(&glyph_id)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read lookahead glyph %d in chain rule subtable", i);
     }
     if (glyph_id > num_glyphs) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad glyph id %d for lookadhead glyph %d in chain rule subtable", glyph_id, i);
     }
   }
 
   uint16_t lookup_count = 0;
   if (!subtable.ReadU16(&lookup_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read lookup count in chain rule subtable");
   }
   for (unsigned i = 0; i < lookup_count; ++i) {
-    if (!ParseLookupRecord(&subtable, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseLookupRecord(file, &subtable, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse lookup record %d in chain rule subtable", i);
     }
   }
 
   return true;
 }
 
-bool ParseChainRuleSetTable(const uint8_t *data, const size_t length,
+bool ParseChainRuleSetTable(const ots::OpenTypeFile *file,
+                            const uint8_t *data, const size_t length,
                             const uint16_t num_glyphs,
                             const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t chain_rule_count = 0;
   if (!subtable.ReadU16(&chain_rule_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read rule count in chain rule set");
   }
   const unsigned chain_rule_end =
       2 * static_cast<unsigned>(chain_rule_count) + 2;
   if (chain_rule_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of chain rule %d in chain rule set", chain_rule_end);
   }
   for (unsigned i = 0; i < chain_rule_count; ++i) {
     uint16_t offset_chain_rule = 0;
     if (!subtable.ReadU16(&offset_chain_rule)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read chain rule offset %d in chain rule set", i);
     }
     if (offset_chain_rule < chain_rule_end || offset_chain_rule >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad chain rule offset %d for chain rule %d in chain rule set", offset_chain_rule, i);
     }
-    if (!ParseChainRuleSubtable(data + offset_chain_rule,
+    if (!ParseChainRuleSubtable(file, data + offset_chain_rule,
                                 length - offset_chain_rule,
                                 num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse chain rule %d in chain rule set", i);
     }
   }
 
   return true;
 }
 
-bool ParseChainContextFormat1(const uint8_t *data, const size_t length,
+bool ParseChainContextFormat1(const ots::OpenTypeFile *file,
+                              const uint8_t *data, const size_t length,
                               const uint16_t num_glyphs,
                               const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_coverage = 0;
   uint16_t chain_rule_set_count = 0;
   // Skip format field.
   if (!subtable.Skip(2) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&chain_rule_set_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header of chain context format 1");
   }
 
   const unsigned chain_rule_set_end =
       2 * static_cast<unsigned>(chain_rule_set_count) + 6;
   if (chain_rule_set_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad chain rule end %d in chain context format 1", chain_rule_set_end);
   }
   if (offset_coverage < chain_rule_set_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d in chain context format 1", chain_rule_set_end);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table for chain context format 1");
   }
 
   for (unsigned i = 0; i < chain_rule_set_count; ++i) {
     uint16_t offset_chain_rule_set = 0;
     if (!subtable.ReadU16(&offset_chain_rule_set)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read chain rule offset %d in chain context format 1", i);
     }
     if (offset_chain_rule_set < chain_rule_set_end ||
         offset_chain_rule_set >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad chain rule set offset %d for chain rule set %d in chain context format 1", offset_chain_rule_set, i);
     }
-    if (!ParseChainRuleSetTable(data + offset_chain_rule_set,
+    if (!ParseChainRuleSetTable(file, data + offset_chain_rule_set,
                                    length - offset_chain_rule_set,
                                    num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse chain rule set %d in chain context format 1", i);
     }
   }
 
   return true;
 }
 
-bool ParseChainClassRuleSubtable(const uint8_t *data, const size_t length,
+bool ParseChainClassRuleSubtable(const ots::OpenTypeFile *file,
+                                 const uint8_t *data, const size_t length,
                                  const uint16_t num_glyphs,
                                  const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   // In this subtable, we don't check the value of classes for now since
   // these could take arbitrary values.
 
   uint16_t backtrack_count = 0;
   if (!subtable.ReadU16(&backtrack_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read backtrack count in chain class rule subtable");
   }
   if (backtrack_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad backtrack count %d in chain class rule subtable", backtrack_count);
   }
   if (!subtable.Skip(2 * backtrack_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip backtrack offsets in chain class rule subtable");
   }
 
   uint16_t input_count = 0;
   if (!subtable.ReadU16(&input_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read input count in chain class rule subtable");
   }
   if (input_count == 0 || input_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad input count %d in chain class rule subtable", input_count);
   }
   if (!subtable.Skip(2 * (input_count - 1))) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip input offsets in chain class rule subtable");
   }
 
   uint16_t lookahead_count = 0;
   if (!subtable.ReadU16(&lookahead_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read lookahead count in chain class rule subtable");
   }
   if (lookahead_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookahead count %d in chain class rule subtable", lookahead_count);
   }
   if (!subtable.Skip(2 * lookahead_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip lookahead offsets in chain class rule subtable");
   }
 
   uint16_t lookup_count = 0;
   if (!subtable.ReadU16(&lookup_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read lookup count in chain class rule subtable");
   }
   for (unsigned i = 0; i < lookup_count; ++i) {
-    if (!ParseLookupRecord(&subtable, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseLookupRecord(file, &subtable, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse lookup record %d in chain class rule subtable", i);
     }
   }
 
   return true;
 }
 
-bool ParseChainClassSetTable(const uint8_t *data, const size_t length,
+bool ParseChainClassSetTable(const ots::OpenTypeFile *file,
+                             const uint8_t *data, const size_t length,
                              const uint16_t num_glyphs,
                              const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t chain_class_rule_count = 0;
   if (!subtable.ReadU16(&chain_class_rule_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read rule count in chain class set");
   }
   const unsigned chain_class_rule_end =
       2 * static_cast<unsigned>(chain_class_rule_count) + 2;
   if (chain_class_rule_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of chain class set %d in chain class set", chain_class_rule_end);
   }
   for (unsigned i = 0; i < chain_class_rule_count; ++i) {
     uint16_t offset_chain_class_rule = 0;
     if (!subtable.ReadU16(&offset_chain_class_rule)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read chain class rule offset %d in chain class set", i);
     }
     if (offset_chain_class_rule < chain_class_rule_end ||
         offset_chain_class_rule >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad chain class rule offset %d for chain class %d in chain class set", offset_chain_class_rule, i);
     }
-    if (!ParseChainClassRuleSubtable(data + offset_chain_class_rule,
+    if (!ParseChainClassRuleSubtable(file, data + offset_chain_class_rule,
                                      length - offset_chain_class_rule,
                                      num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse chain class rule %d in chain class set", i);
     }
   }
 
   return true;
 }
 
-bool ParseChainContextFormat2(const uint8_t *data, const size_t length,
+bool ParseChainContextFormat2(const ots::OpenTypeFile *file,
+                              const uint8_t *data, const size_t length,
                               const uint16_t num_glyphs,
                               const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t offset_coverage = 0;
   uint16_t offset_backtrack_class_def = 0;
   uint16_t offset_input_class_def = 0;
   uint16_t offset_lookahead_class_def = 0;
   uint16_t chain_class_set_count = 0;
   // Skip format field.
   if (!subtable.Skip(2) ||
       !subtable.ReadU16(&offset_coverage) ||
       !subtable.ReadU16(&offset_backtrack_class_def) ||
       !subtable.ReadU16(&offset_input_class_def) ||
       !subtable.ReadU16(&offset_lookahead_class_def) ||
       !subtable.ReadU16(&chain_class_set_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header of chain context format 2");
   }
 
   const unsigned chain_class_set_end =
       2 * static_cast<unsigned>(chain_class_set_count) + 12;
   if (chain_class_set_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad chain class set end %d in chain context format 2", chain_class_set_end);
   }
   if (offset_coverage < chain_class_set_end || offset_coverage >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad coverage offset %d in chain context format 2", offset_coverage);
   }
-  if (!ots::ParseCoverageTable(data + offset_coverage,
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
                                length - offset_coverage, num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse coverage table in chain context format 2");
   }
 
   // Classes for backtrack/lookahead sequences might not be defined.
   if (offset_backtrack_class_def) {
     if (offset_backtrack_class_def < chain_class_set_end ||
         offset_backtrack_class_def >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad backtrack class offset %d in chain context format 2", offset_backtrack_class_def);
     }
-    if (!ots::ParseClassDefTable(data + offset_backtrack_class_def,
+    if (!ots::ParseClassDefTable(file, data + offset_backtrack_class_def,
                                  length - offset_backtrack_class_def,
                                  num_glyphs, kMaxClassDefValue)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse backtrack class defn table in chain context format 2");
     }
   }
 
   if (offset_input_class_def < chain_class_set_end ||
       offset_input_class_def >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad input class defn offset %d in chain context format 2", offset_input_class_def);
   }
-  if (!ots::ParseClassDefTable(data + offset_input_class_def,
+  if (!ots::ParseClassDefTable(file, data + offset_input_class_def,
                                length - offset_input_class_def,
                                num_glyphs, kMaxClassDefValue)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse input class defn in chain context format 2");
   }
 
   if (offset_lookahead_class_def) {
     if (offset_lookahead_class_def < chain_class_set_end ||
         offset_lookahead_class_def >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad lookahead class defn offset %d in chain context format 2", offset_lookahead_class_def);
     }
-    if (!ots::ParseClassDefTable(data + offset_lookahead_class_def,
+    if (!ots::ParseClassDefTable(file, data + offset_lookahead_class_def,
                                  length - offset_lookahead_class_def,
                                  num_glyphs, kMaxClassDefValue)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse lookahead class defn in chain context format 2");
     }
   }
 
   for (unsigned i = 0; i < chain_class_set_count; ++i) {
     uint16_t offset_chain_class_set = 0;
     if (!subtable.ReadU16(&offset_chain_class_set)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read chain class set offset %d", i);
     }
     // |offset_chain_class_set| could be NULL.
     if (offset_chain_class_set) {
       if (offset_chain_class_set < chain_class_set_end ||
           offset_chain_class_set >= length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Bad chain set class offset %d for chain set %d in chain context format 2", offset_chain_class_set, i);
       }
-      if (!ParseChainClassSetTable(data + offset_chain_class_set,
+      if (!ParseChainClassSetTable(file, data + offset_chain_class_set,
                                    length - offset_chain_class_set,
                                    num_glyphs, num_lookups)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse chain class set table %d in chain context format 2", i);
       }
     }
   }
 
   return true;
 }
 
-bool ParseChainContextFormat3(const uint8_t *data, const size_t length,
+bool ParseChainContextFormat3(const ots::OpenTypeFile *file,
+                              const uint8_t *data, const size_t length,
                               const uint16_t num_glyphs,
                               const uint16_t num_lookups) {
   ots::Buffer subtable(data, length);
 
   uint16_t backtrack_count = 0;
   // Skip format field.
   if (!subtable.Skip(2) ||
       !subtable.ReadU16(&backtrack_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read backtrack count in chain context format 3");
   }
 
   if (backtrack_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad backtrack count %d in chain context format 3", backtrack_count);
   }
   std::vector<uint16_t> offsets_backtrack;
   offsets_backtrack.reserve(backtrack_count);
   for (unsigned i = 0; i < backtrack_count; ++i) {
     uint16_t offset = 0;
     if (!subtable.ReadU16(&offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read backtrack offset %d in chain context format 3", i);
     }
     offsets_backtrack.push_back(offset);
   }
   if (offsets_backtrack.size() != backtrack_count) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad backtrack offsets size %ld in chain context format 3", offsets_backtrack.size());
   }
 
   uint16_t input_count = 0;
   if (!subtable.ReadU16(&input_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read input count in chain context format 3");
   }
   if (input_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad input count %d in chain context format 3", input_count);
   }
   std::vector<uint16_t> offsets_input;
   offsets_input.reserve(input_count);
   for (unsigned i = 0; i < input_count; ++i) {
     uint16_t offset = 0;
     if (!subtable.ReadU16(&offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read input offset %d in chain context format 3", i);
     }
     offsets_input.push_back(offset);
   }
   if (offsets_input.size() != input_count) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad input offsets size %ld in chain context format 3", offsets_input.size());
   }
 
   uint16_t lookahead_count = 0;
   if (!subtable.ReadU16(&lookahead_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed ot read lookahead count in chain context format 3");
   }
   if (lookahead_count >= num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookahead count %d in chain context format 3", lookahead_count);
   }
   std::vector<uint16_t> offsets_lookahead;
   offsets_lookahead.reserve(lookahead_count);
   for (unsigned i = 0; i < lookahead_count; ++i) {
     uint16_t offset = 0;
     if (!subtable.ReadU16(&offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read lookahead offset %d in chain context format 3", i);
     }
     offsets_lookahead.push_back(offset);
   }
   if (offsets_lookahead.size() != lookahead_count) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookahead offsets size %ld in chain context format 3", offsets_lookahead.size());
   }
 
   uint16_t lookup_count = 0;
   if (!subtable.ReadU16(&lookup_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read lookup count in chain context format 3");
   }
   for (unsigned i = 0; i < lookup_count; ++i) {
-    if (!ParseLookupRecord(&subtable, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseLookupRecord(file, &subtable, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse lookup %d in chain context format 3", i);
     }
   }
 
   const unsigned lookup_record_end =
       2 * (static_cast<unsigned>(backtrack_count) +
            static_cast<unsigned>(input_count) +
            static_cast<unsigned>(lookahead_count)) +
       4 * static_cast<unsigned>(lookup_count) + 10;
   if (lookup_record_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of lookup record %d in chain context format 3", lookup_record_end);
   }
   for (unsigned i = 0; i < backtrack_count; ++i) {
     if (offsets_backtrack[i] < lookup_record_end ||
         offsets_backtrack[i] >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad backtrack offset of %d for backtrack %d in chain context format 3", offsets_backtrack[i], i);
     }
-    if (!ots::ParseCoverageTable(data + offsets_backtrack[i],
+    if (!ots::ParseCoverageTable(file, data + offsets_backtrack[i],
                                  length - offsets_backtrack[i], num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse backtrack coverage %d in chain context format 3", i);
     }
   }
   for (unsigned i = 0; i < input_count; ++i) {
     if (offsets_input[i] < lookup_record_end || offsets_input[i] >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad input offset %d for input %d in chain context format 3", offsets_input[i], i);
     }
-    if (!ots::ParseCoverageTable(data + offsets_input[i],
+    if (!ots::ParseCoverageTable(file, data + offsets_input[i],
                                  length - offsets_input[i], num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse input coverage table %d in chain context format 3", i);
     }
   }
   for (unsigned i = 0; i < lookahead_count; ++i) {
     if (offsets_lookahead[i] < lookup_record_end ||
         offsets_lookahead[i] >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad lookadhead offset %d for lookahead %d in chain context format 3", offsets_lookahead[i], i);
     }
-    if (!ots::ParseCoverageTable(data + offsets_lookahead[i],
+    if (!ots::ParseCoverageTable(file, data + offsets_lookahead[i],
                                  length - offsets_lookahead[i], num_glyphs)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse lookahead coverage table %d in chain context format 3", i);
     }
   }
 
   return true;
 }
 
 }  // namespace
 
 namespace ots {
 
 bool LookupSubtableParser::Parse(const OpenTypeFile *file, const uint8_t *data,
                                  const size_t length,
                                  const uint16_t lookup_type) const {
   for (unsigned i = 0; i < num_types; ++i) {
     if (parsers[i].type == lookup_type && parsers[i].parse) {
       if (!parsers[i].parse(file, data, length)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to parse lookup subtable %d", i);
       }
       return true;
     }
   }
-  return OTS_FAILURE();
+  return OTS_FAILURE_MSG("No lookup subtables to parse");
 }
 
 // Parsing ScriptListTable requires number of features so we need to
 // parse FeatureListTable before calling this function.
-bool ParseScriptListTable(const uint8_t *data, const size_t length,
+bool ParseScriptListTable(const ots::OpenTypeFile *file,
+                          const uint8_t *data, const size_t length,
                           const uint16_t num_features) {
   Buffer subtable(data, length);
 
   uint16_t script_count = 0;
   if (!subtable.ReadU16(&script_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read script count in script list table");
   }
 
   const unsigned script_record_end =
       6 * static_cast<unsigned>(script_count) + 2;
   if (script_record_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of script record %d in script list table", script_record_end);
   }
   std::vector<ScriptRecord> script_list;
   script_list.reserve(script_count);
   uint32_t last_tag = 0;
   for (unsigned i = 0; i < script_count; ++i) {
     ScriptRecord record;
     if (!subtable.ReadU32(&record.tag) ||
         !subtable.ReadU16(&record.offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read script record %d in script list table", i);
     }
     // Script tags should be arranged alphabetically by tag
     if (last_tag != 0 && last_tag > record.tag) {
       // Several fonts don't arrange tags alphabetically.
       // It seems that the order of tags might not be a security issue
       // so we just warn it.
       OTS_WARNING("tags aren't arranged alphabetically.");
     }
     last_tag = record.tag;
     if (record.offset < script_record_end || record.offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad record offset %d for script %4s entry %d in script list table", record.offset, (char *)&record.tag, i);
     }
     script_list.push_back(record);
   }
   if (script_list.size() != script_count) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad script list size %ld in script list table", script_list.size());
   }
 
   // Check script records.
   for (unsigned i = 0; i < script_count; ++i) {
-    if (!ParseScriptTable(data + script_list[i].offset,
+    if (!ParseScriptTable(file, data + script_list[i].offset,
                           length - script_list[i].offset,
                           script_list[i].tag, num_features)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse script table %d", i);
     }
   }
 
   return true;
 }
 
 // Parsing FeatureListTable requires number of lookups so we need to parse
 // LookupListTable before calling this function.
-bool ParseFeatureListTable(const uint8_t *data, const size_t length,
+bool ParseFeatureListTable(const ots::OpenTypeFile *file,
+                           const uint8_t *data, const size_t length,
                            const uint16_t num_lookups,
                            uint16_t* num_features) {
   Buffer subtable(data, length);
 
   uint16_t feature_count = 0;
   if (!subtable.ReadU16(&feature_count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read feature count");
   }
 
   std::vector<FeatureRecord> feature_records;
   feature_records.resize(feature_count);
   const unsigned feature_record_end =
       6 * static_cast<unsigned>(feature_count) + 2;
   if (feature_record_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of feature record %d", feature_record_end);
   }
   uint32_t last_tag = 0;
   for (unsigned i = 0; i < feature_count; ++i) {
     if (!subtable.ReadU32(&feature_records[i].tag) ||
         !subtable.ReadU16(&feature_records[i].offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read feature header %d", i);
     }
     // Feature record array should be arranged alphabetically by tag
     if (last_tag != 0 && last_tag > feature_records[i].tag) {
       // Several fonts don't arrange tags alphabetically.
       // It seems that the order of tags might not be a security issue
       // so we just warn it.
       OTS_WARNING("tags aren't arranged alphabetically.");
     }
     last_tag = feature_records[i].tag;
     if (feature_records[i].offset < feature_record_end ||
         feature_records[i].offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad feature offset %d for feature %d %4s", feature_records[i].offset, i, (char *)&feature_records[i].tag);
     }
   }
 
   for (unsigned i = 0; i < feature_count; ++i) {
-    if (!ParseFeatureTable(data + feature_records[i].offset,
+    if (!ParseFeatureTable(file, data + feature_records[i].offset,
                            length - feature_records[i].offset, num_lookups)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse feature table %d", i);
     }
   }
   *num_features = feature_count;
   return true;
 }
 
 // For parsing GPOS/GSUB tables, this function should be called at first to
 // obtain the number of lookups because parsing FeatureTableList requires
 // the number.
 bool ParseLookupListTable(OpenTypeFile *file, const uint8_t *data,
                           const size_t length,
                           const LookupSubtableParser* parser,
                           uint16_t *num_lookups) {
   Buffer subtable(data, length);
 
   if (!subtable.ReadU16(num_lookups)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read number of lookups");
   }
 
   std::vector<uint16_t> lookups;
   lookups.reserve(*num_lookups);
   const unsigned lookup_end =
       2 * static_cast<unsigned>(*num_lookups) + 2;
   if (lookup_end > std::numeric_limits<uint16_t>::max()) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad end of lookups %d", lookup_end);
   }
   for (unsigned i = 0; i < *num_lookups; ++i) {
     uint16_t offset = 0;
     if (!subtable.ReadU16(&offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read lookup offset %d", i);
     }
     if (offset < lookup_end || offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad lookup offset %d for lookup %d", offset, i);
     }
     lookups.push_back(offset);
   }
   if (lookups.size() != *num_lookups) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookup offsets list size %ld", lookups.size());
   }
 
   for (unsigned i = 0; i < *num_lookups; ++i) {
     if (!ParseLookupTable(file, data + lookups[i], length - lookups[i],
                           parser)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to parse lookup %d", i);
     }
   }
 
   return true;
 }
 
-bool ParseClassDefTable(const uint8_t *data, size_t length,
+bool ParseClassDefTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, size_t length,
                         const uint16_t num_glyphs,
                         const uint16_t num_classes) {
   Buffer subtable(data, length);
 
   uint16_t format = 0;
   if (!subtable.ReadU16(&format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read class defn format");
   }
   if (format == 1) {
-    return ParseClassDefFormat1(data, length, num_glyphs, num_classes);
+    return ParseClassDefFormat1(file, data, length, num_glyphs, num_classes);
   } else if (format == 2) {
-    return ParseClassDefFormat2(data, length, num_glyphs, num_classes);
+    return ParseClassDefFormat2(file, data, length, num_glyphs, num_classes);
   }
 
-  return OTS_FAILURE();
+  return OTS_FAILURE_MSG("Bad class defn format %d", format);
 }
 
-bool ParseCoverageTable(const uint8_t *data, size_t length,
-                        const uint16_t num_glyphs) {
+bool ParseCoverageTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, size_t length,
+                        const uint16_t num_glyphs,
+                        const uint16_t expected_num_glyphs) {
   Buffer subtable(data, length);
 
   uint16_t format = 0;
   if (!subtable.ReadU16(&format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read coverage table format");
   }
   if (format == 1) {
-    return ParseCoverageFormat1(data, length, num_glyphs);
+    return ParseCoverageFormat1(file, data, length, num_glyphs, expected_num_glyphs);
   } else if (format == 2) {
-    return ParseCoverageFormat2(data, length, num_glyphs);
+    return ParseCoverageFormat2(file, data, length, num_glyphs, expected_num_glyphs);
   }
 
-  return OTS_FAILURE();
+  return OTS_FAILURE_MSG("Bad coverage table format %d", format);
 }
 
-bool ParseDeviceTable(const uint8_t *data, size_t length) {
+bool ParseDeviceTable(const ots::OpenTypeFile *file,
+                      const uint8_t *data, size_t length) {
   Buffer subtable(data, length);
 
   uint16_t start_size = 0;
   uint16_t end_size = 0;
   uint16_t delta_format = 0;
   if (!subtable.ReadU16(&start_size) ||
       !subtable.ReadU16(&end_size) ||
       !subtable.ReadU16(&delta_format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read device table header");
   }
   if (start_size > end_size) {
-    OTS_WARNING("bad size range: %u > %u", start_size, end_size);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad size range: %u > %u", start_size, end_size);
   }
   if (delta_format == 0 || delta_format > kMaxDeltaFormatType) {
-    OTS_WARNING("bad delta format: %u", delta_format);
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("bad delta format: %u", delta_format);
   }
   // The number of delta values per uint16. The device table should contain
   // at least |num_units| * 2 bytes compressed data.
   const unsigned num_units = (end_size - start_size) /
       (1 << (4 - delta_format)) + 1;
   // Just skip |num_units| * 2 bytes since the compressed data could take
   // arbitrary values.
   if (!subtable.Skip(num_units * 2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip data in device table");
   }
   return true;
 }
 
-bool ParseContextSubtable(const uint8_t *data, const size_t length,
+bool ParseContextSubtable(const ots::OpenTypeFile *file,
+                          const uint8_t *data, const size_t length,
                           const uint16_t num_glyphs,
                           const uint16_t num_lookups) {
   Buffer subtable(data, length);
 
   uint16_t format = 0;
   if (!subtable.ReadU16(&format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read context subtable format");
   }
 
   if (format == 1) {
-    if (!ParseContextFormat1(data, length, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseContextFormat1(file, data, length, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse context format 1 subtable");
     }
   } else if (format == 2) {
-    if (!ParseContextFormat2(data, length, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseContextFormat2(file, data, length, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse context format 2 subtable");
     }
   } else if (format == 3) {
-    if (!ParseContextFormat3(data, length, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseContextFormat3(file, data, length, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse context format 3 subtable");
     }
   } else {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad context subtable format %d", format);
   }
 
   return true;
 }
 
-bool ParseChainingContextSubtable(const uint8_t *data, const size_t length,
+bool ParseChainingContextSubtable(const ots::OpenTypeFile *file,
+                                  const uint8_t *data, const size_t length,
                                   const uint16_t num_glyphs,
                                   const uint16_t num_lookups) {
   Buffer subtable(data, length);
 
   uint16_t format = 0;
   if (!subtable.ReadU16(&format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read chaining context subtable format");
   }
 
   if (format == 1) {
-    if (!ParseChainContextFormat1(data, length, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseChainContextFormat1(file, data, length, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse chaining context format 1 subtable");
     }
   } else if (format == 2) {
-    if (!ParseChainContextFormat2(data, length, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseChainContextFormat2(file, data, length, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse chaining context format 2 subtable");
     }
   } else if (format == 3) {
-    if (!ParseChainContextFormat3(data, length, num_glyphs, num_lookups)) {
-      return OTS_FAILURE();
+    if (!ParseChainContextFormat3(file, data, length, num_glyphs, num_lookups)) {
+      return OTS_FAILURE_MSG("Failed to parse chaining context format 3 subtable");
     }
   } else {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad chaining context subtable format %d", format);
   }
 
   return true;
 }
 
 bool ParseExtensionSubtable(const OpenTypeFile *file,
                             const uint8_t *data, const size_t length,
                             const LookupSubtableParser* parser) {
   Buffer subtable(data, length);
 
   uint16_t format = 0;
   uint16_t lookup_type = 0;
   uint32_t offset_extension = 0;
   if (!subtable.ReadU16(&format) ||
       !subtable.ReadU16(&lookup_type) ||
       !subtable.ReadU32(&offset_extension)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read extension table header");
   }
 
   if (format != 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad extension table format %d", format);
   }
   // |lookup_type| should be other than |parser->extension_type|.
   if (lookup_type < 1 || lookup_type > parser->num_types ||
       lookup_type == parser->extension_type) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad lookup type %d in extension table", lookup_type);
   }
 
   const unsigned format_end = static_cast<unsigned>(8);
   if (offset_extension < format_end ||
       offset_extension >= length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad extension offset %d", offset_extension);
   }
 
   // Parse the extension subtable of |lookup_type|.
   if (!parser->Parse(file, data + offset_extension, length - offset_extension,
                      lookup_type)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse lookup from extension lookup");
   }
 
   return true;
 }
 
 }  // namespace ots
 
--- a/gfx/ots/src/layout.h
+++ b/gfx/ots/src/layout.h
@@ -22,44 +22,52 @@ struct LookupSubtableParser {
   size_t num_types;
   uint16_t extension_type;
   const TypeParser *parsers;
 
   bool Parse(const OpenTypeFile *file, const uint8_t *data,
              const size_t length, const uint16_t lookup_type) const;
 };
 
-bool ParseScriptListTable(const uint8_t *data, const size_t length,
+bool ParseScriptListTable(const ots::OpenTypeFile *file,
+                          const uint8_t *data, const size_t length,
                           const uint16_t num_features);
 
-bool ParseFeatureListTable(const uint8_t *data, const size_t length,
+bool ParseFeatureListTable(const ots::OpenTypeFile *file,
+                           const uint8_t *data, const size_t length,
                            const uint16_t num_lookups,
                            uint16_t *num_features);
 
 bool ParseLookupListTable(OpenTypeFile *file, const uint8_t *data,
                           const size_t length,
                           const LookupSubtableParser* parser,
                           uint16_t* num_lookups);
 
-bool ParseClassDefTable(const uint8_t *data, size_t length,
+bool ParseClassDefTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, size_t length,
                         const uint16_t num_glyphs,
                         const uint16_t num_classes);
 
-bool ParseCoverageTable(const uint8_t *data, size_t length,
-                        const uint16_t num_glyphs);
+bool ParseCoverageTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, size_t length,
+                        const uint16_t num_glyphs,
+                        const uint16_t expected_num_glyphs = 0);
 
-bool ParseDeviceTable(const uint8_t *data, size_t length);
+bool ParseDeviceTable(const ots::OpenTypeFile *file,
+                      const uint8_t *data, size_t length);
 
 // Parser for 'Contextual' subtable shared by GSUB/GPOS tables.
-bool ParseContextSubtable(const uint8_t *data, const size_t length,
+bool ParseContextSubtable(const ots::OpenTypeFile *file,
+                          const uint8_t *data, const size_t length,
                           const uint16_t num_glyphs,
                           const uint16_t num_lookups);
 
 // Parser for 'Chaining Contextual' subtable shared by GSUB/GPOS tables.
-bool ParseChainingContextSubtable(const uint8_t *data, const size_t length,
+bool ParseChainingContextSubtable(const ots::OpenTypeFile *file,
+                                  const uint8_t *data, const size_t length,
                                   const uint16_t num_glyphs,
                                   const uint16_t num_lookups);
 
 bool ParseExtensionSubtable(const OpenTypeFile *file,
                             const uint8_t *data, const size_t length,
                             const LookupSubtableParser* parser);
 
 }  // namespace ots
--- a/gfx/ots/src/loca.cc
+++ b/gfx/ots/src/loca.cc
@@ -3,61 +3,63 @@
 // found in the LICENSE file.
 
 #include "loca.h"
 
 #include "head.h"
 #include "maxp.h"
 
 // loca - Index to Location
-// http://www.microsoft.com/opentype/otspec/loca.htm
+// http://www.microsoft.com/typography/otspec/loca.htm
+
+#define TABLE_NAME "loca"
 
 namespace ots {
 
 bool ots_loca_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   // We can't do anything useful in validating this data except to ensure that
   // the values are monotonically increasing.
 
   OpenTypeLOCA *loca = new OpenTypeLOCA;
   file->loca = loca;
 
   if (!file->maxp || !file->head) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("maxp or head tables missing from font, needed by loca");
   }
 
   const unsigned num_glyphs = file->maxp->num_glyphs;
   unsigned last_offset = 0;
   loca->offsets.resize(num_glyphs + 1);
   // maxp->num_glyphs is uint16_t, thus the addition never overflows.
 
   if (file->head->index_to_loc_format == 0) {
     // Note that the <= here (and below) is correct. There is one more offset
     // than the number of glyphs in order to give the length of the final
     // glyph.
     for (unsigned i = 0; i <= num_glyphs; ++i) {
       uint16_t offset = 0;
       if (!table.ReadU16(&offset)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read offset for glyph %d", i);
       }
       if (offset < last_offset) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Out of order offset %d < %d for glyph %d", offset, last_offset, i);
       }
       last_offset = offset;
       loca->offsets[i] = offset * 2;
     }
   } else {
     for (unsigned i = 0; i <= num_glyphs; ++i) {
       uint32_t offset = 0;
       if (!table.ReadU32(&offset)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read offset for glyph %d", i);
       }
       if (offset < last_offset) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Out of order offset %d < %d for glyph %d", offset, last_offset, i);
       }
       last_offset = offset;
       loca->offsets[i] = offset;
     }
   }
 
   return true;
 }
@@ -66,29 +68,29 @@ bool ots_loca_should_serialise(OpenTypeF
   return file->loca != NULL;
 }
 
 bool ots_loca_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeLOCA *loca = file->loca;
   const OpenTypeHEAD *head = file->head;
 
   if (!head) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing head table in font needed by loca");
   }
 
   if (head->index_to_loc_format == 0) {
     for (unsigned i = 0; i < loca->offsets.size(); ++i) {
       if (!out->WriteU16(loca->offsets[i] >> 1)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i);
       }
     }
   } else {
     for (unsigned i = 0; i < loca->offsets.size(); ++i) {
       if (!out->WriteU32(loca->offsets[i])) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i);
       }
     }
   }
 
   return true;
 }
 
 void ots_loca_free(OpenTypeFile *file) {
--- a/gfx/ots/src/ltsh.cc
+++ b/gfx/ots/src/ltsh.cc
@@ -4,35 +4,41 @@
 
 #include "ltsh.h"
 
 #include "maxp.h"
 
 // LTSH - Linear Threshold
 // http://www.microsoft.com/typography/otspec/ltsh.htm
 
+#define TABLE_NAME "LTSH"
+
 #define DROP_THIS_TABLE \
-  do { delete file->ltsh; file->ltsh = 0; } while (0)
+  do { \
+    delete file->ltsh; \
+    file->ltsh = 0; \
+    OTS_FAILURE_MSG("Table discarded"); \
+  } while (0)
 
 namespace ots {
 
 bool ots_ltsh_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   if (!file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing maxp table from font needed by ltsh");
   }
 
   OpenTypeLTSH *ltsh = new OpenTypeLTSH;
   file->ltsh = ltsh;
 
   uint16_t num_glyphs = 0;
   if (!table.ReadU16(&ltsh->version) ||
       !table.ReadU16(&num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read ltsh header");
   }
 
   if (ltsh->version != 0) {
     OTS_WARNING("bad version: %u", ltsh->version);
     DROP_THIS_TABLE;
     return true;
   }
 
@@ -41,17 +47,17 @@ bool ots_ltsh_parse(OpenTypeFile *file, 
     DROP_THIS_TABLE;
     return true;
   }
 
   ltsh->ypels.reserve(num_glyphs);
   for (unsigned i = 0; i < num_glyphs; ++i) {
     uint8_t pel = 0;
     if (!table.ReadU8(&pel)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read pixels for glyph %d", i);
     }
     ltsh->ypels.push_back(pel);
   }
 
   return true;
 }
 
 bool ots_ltsh_should_serialise(OpenTypeFile *file) {
@@ -59,21 +65,21 @@ bool ots_ltsh_should_serialise(OpenTypeF
   return file->ltsh != NULL;
 }
 
 bool ots_ltsh_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeLTSH *ltsh = file->ltsh;
 
   if (!out->WriteU16(ltsh->version) ||
       !out->WriteU16(ltsh->ypels.size())) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write pels size");
   }
   for (unsigned i = 0; i < ltsh->ypels.size(); ++i) {
     if (!out->Write(&(ltsh->ypels[i]), 1)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write pixel size for glyph %d", i);
     }
   }
 
   return true;
 }
 
 void ots_ltsh_free(OpenTypeFile *file) {
   delete file->ltsh;
new file mode 100644
--- /dev/null
+++ b/gfx/ots/src/math.cc
@@ -0,0 +1,608 @@
+// Copyright (c) 2014 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.
+
+// We use an underscore to avoid confusion with the standard math.h library.
+#include "math_.h"
+
+#include <limits>
+#include <vector>
+
+#include "layout.h"
+#include "maxp.h"
+
+// MATH - The MATH Table
+// The specification is not yet public but has been submitted to the MPEG group
+// in response to the 'Call for Proposals for ISO/IEC 14496-22 "Open Font
+// Format" Color Font Technology and MATH layout support'. Meanwhile, you can
+// contact Microsoft's engineer Murray Sargent to obtain a copy.
+
+#define TABLE_NAME "MATH"
+
+namespace {
+
+// The size of MATH header.
+// Version
+// MathConstants
+// MathGlyphInfo
+// MathVariants
+const unsigned kMathHeaderSize = 4 + 3 * 2;
+
+// The size of the MathGlyphInfo header.
+// MathItalicsCorrectionInfo
+// MathTopAccentAttachment
+// ExtendedShapeCoverage
+// MathKernInfo
+const unsigned kMathGlyphInfoHeaderSize = 4 * 2;
+
+// The size of the MathValueRecord.
+// Value
+// DeviceTable
+const unsigned kMathValueRecordSize = 2 * 2;
+
+// The size of the GlyphPartRecord.
+// glyph
+// StartConnectorLength
+// EndConnectorLength
+// FullAdvance
+// PartFlags
+const unsigned kGlyphPartRecordSize = 5 * 2;
+
+// Shared Table: MathValueRecord
+
+bool ParseMathValueRecord(const ots::OpenTypeFile *file,
+                          ots::Buffer* subtable, const uint8_t *data,
+                          const size_t length) {
+  // Check the Value field.
+  if (!subtable->Skip(2)) {
+    return OTS_FAILURE();
+  }
+
+  // Check the offset to device table.
+  uint16_t offset = 0;
+  if (!subtable->ReadU16(&offset)) {
+    return OTS_FAILURE();
+  }
+  if (offset) {
+    if (offset >= length) {
+      return OTS_FAILURE();
+    }
+    if (!ots::ParseDeviceTable(file, data + offset, length - offset)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  return true;
+}
+
+bool ParseMathConstantsTable(const ots::OpenTypeFile *file,
+                             const uint8_t *data, size_t length) {
+  ots::Buffer subtable(data, length);
+
+  // Part 1: int16 or uint16 constants.
+  //  ScriptPercentScaleDown
+  //  ScriptScriptPercentScaleDown
+  //  DelimitedSubFormulaMinHeight
+  //  DisplayOperatorMinHeight
+  if (!subtable.Skip(4 * 2)) {
+    return OTS_FAILURE();
+  }
+
+  // Part 2: MathValueRecord constants.
+  // MathLeading
+  // AxisHeight
+  // AccentBaseHeight
+  // FlattenedAccentBaseHeight
+  // SubscriptShiftDown
+  // SubscriptTopMax
+  // SubscriptBaselineDropMin
+  // SuperscriptShiftUp
+  // SuperscriptShiftUpCramped
+  // SuperscriptBottomMin
+  //
+  // SuperscriptBaselineDropMax
+  // SubSuperscriptGapMin
+  // SuperscriptBottomMaxWithSubscript
+  // SpaceAfterScript
+  // UpperLimitGapMin
+  // UpperLimitBaselineRiseMin
+  // LowerLimitGapMin
+  // LowerLimitBaselineDropMin
+  // StackTopShiftUp
+  // StackTopDisplayStyleShiftUp
+  //
+  // StackBottomShiftDown
+  // StackBottomDisplayStyleShiftDown
+  // StackGapMin
+  // StackDisplayStyleGapMin
+  // StretchStackTopShiftUp
+  // StretchStackBottomShiftDown
+  // StretchStackGapAboveMin
+  // StretchStackGapBelowMin
+  // FractionNumeratorShiftUp
+  // FractionNumeratorDisplayStyleShiftUp
+  //
+  // FractionDenominatorShiftDown
+  // FractionDenominatorDisplayStyleShiftDown
+  // FractionNumeratorGapMin
+  // FractionNumDisplayStyleGapMin
+  // FractionRuleThickness
+  // FractionDenominatorGapMin
+  // FractionDenomDisplayStyleGapMin
+  // SkewedFractionHorizontalGap
+  // SkewedFractionVerticalGap
+  // OverbarVerticalGap
+  //
+  // OverbarRuleThickness
+  // OverbarExtraAscender
+  // UnderbarVerticalGap
+  // UnderbarRuleThickness
+  // UnderbarExtraDescender
+  // RadicalVerticalGap
+  // RadicalDisplayStyleVerticalGap
+  // RadicalRuleThickness
+  // RadicalExtraAscender
+  // RadicalKernBeforeDegree
+  //
+  // RadicalKernAfterDegree
+  for (unsigned i = 0; i < static_cast<unsigned>(51); ++i) {
+    if (!ParseMathValueRecord(file, &subtable, data, length)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  // Part 3: uint16 constant
+  // RadicalDegreeBottomRaisePercent
+  if (!subtable.Skip(2)) {
+    return OTS_FAILURE();
+  }
+
+  return true;
+}
+
+bool ParseMathValueRecordSequenceForGlyphs(const ots::OpenTypeFile *file,
+                                           ots::Buffer* subtable,
+                                           const uint8_t *data,
+                                           const size_t length,
+                                           const uint16_t num_glyphs) {
+  // Check the header.
+  uint16_t offset_coverage = 0;
+  uint16_t sequence_count = 0;
+  if (!subtable->ReadU16(&offset_coverage) ||
+      !subtable->ReadU16(&sequence_count)) {
+    return OTS_FAILURE();
+  }
+
+  const unsigned sequence_end = static_cast<unsigned>(2 * 2) +
+      sequence_count * kMathValueRecordSize;
+  if (sequence_end > std::numeric_limits<uint16_t>::max()) {
+    return OTS_FAILURE();
+  }
+
+  // Check coverage table.
+  if (offset_coverage < sequence_end || offset_coverage >= length) {
+    return OTS_FAILURE();
+  }
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
+                               length - offset_coverage,
+                               num_glyphs, sequence_count)) {
+    return OTS_FAILURE();
+  }
+
+  // Check sequence.
+  for (unsigned i = 0; i < sequence_count; ++i) {
+    if (!ParseMathValueRecord(file, subtable, data, length)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  return true;
+}
+
+bool ParseMathItalicsCorrectionInfoTable(const ots::OpenTypeFile *file,
+                                         const uint8_t *data,
+                                         size_t length,
+                                         const uint16_t num_glyphs) {
+  ots::Buffer subtable(data, length);
+  return ParseMathValueRecordSequenceForGlyphs(file, &subtable, data, length,
+                                               num_glyphs);
+}
+
+bool ParseMathTopAccentAttachmentTable(const ots::OpenTypeFile *file,
+                                       const uint8_t *data,
+                                       size_t length,
+                                       const uint16_t num_glyphs) {
+  ots::Buffer subtable(data, length);
+  return ParseMathValueRecordSequenceForGlyphs(file, &subtable, data, length,
+                                               num_glyphs);
+}
+
+bool ParseMathKernTable(const ots::OpenTypeFile *file,
+                        const uint8_t *data, size_t length) {
+  ots::Buffer subtable(data, length);
+
+  // Check the Height count.
+  uint16_t height_count = 0;
+  if (!subtable.ReadU16(&height_count)) {
+    return OTS_FAILURE();
+  }
+
+  // Check the Correction Heights.
+  for (unsigned i = 0; i < height_count; ++i) {
+    if (!ParseMathValueRecord(file, &subtable, data, length)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  // Check the Kern Values.
+  for (unsigned i = 0; i <= height_count; ++i) {
+    if (!ParseMathValueRecord(file, &subtable, data, length)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  return true;
+}
+
+bool ParseMathKernInfoTable(const ots::OpenTypeFile *file,
+                            const uint8_t *data, size_t length,
+                            const uint16_t num_glyphs) {
+  ots::Buffer subtable(data, length);
+
+  // Check the header.
+  uint16_t offset_coverage = 0;
+  uint16_t sequence_count = 0;
+  if (!subtable.ReadU16(&offset_coverage) ||
+      !subtable.ReadU16(&sequence_count)) {
+    return OTS_FAILURE();
+  }
+
+  const unsigned sequence_end = static_cast<unsigned>(2 * 2) +
+    sequence_count * 4 * 2;
+  if (sequence_end > std::numeric_limits<uint16_t>::max()) {
+    return OTS_FAILURE();
+  }
+
+  // Check coverage table.
+  if (offset_coverage < sequence_end || offset_coverage >= length) {
+    return OTS_FAILURE();
+  }
+  if (!ots::ParseCoverageTable(file, data + offset_coverage, length - offset_coverage,
+                               num_glyphs, sequence_count)) {
+    return OTS_FAILURE();
+  }
+
+  // Check sequence of MathKernInfoRecord
+  for (unsigned i = 0; i < sequence_count; ++i) {
+    // Check TopRight, TopLeft, BottomRight and BottomLeft Math Kern.
+    for (unsigned j = 0; j < 4; ++j) {
+      uint16_t offset_math_kern = 0;
+      if (!subtable.ReadU16(&offset_math_kern)) {
+        return OTS_FAILURE();
+      }
+      if (offset_math_kern) {
+        if (offset_math_kern < sequence_end || offset_math_kern >= length ||
+            !ParseMathKernTable(file, data + offset_math_kern,
+                                length - offset_math_kern)) {
+          return OTS_FAILURE();
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+bool ParseMathGlyphInfoTable(const ots::OpenTypeFile *file,
+                             const uint8_t *data, size_t length,
+                             const uint16_t num_glyphs) {
+  ots::Buffer subtable(data, length);
+
+  // Check Header.
+  uint16_t offset_math_italics_correction_info = 0;
+  uint16_t offset_math_top_accent_attachment = 0;
+  uint16_t offset_extended_shaped_coverage = 0;
+  uint16_t offset_math_kern_info = 0;
+  if (!subtable.ReadU16(&offset_math_italics_correction_info) ||
+      !subtable.ReadU16(&offset_math_top_accent_attachment) ||
+      !subtable.ReadU16(&offset_extended_shaped_coverage) ||
+      !subtable.ReadU16(&offset_math_kern_info)) {
+    return OTS_FAILURE();
+  }
+
+  // Check subtables.
+  // The specification does not say whether the offsets for
+  // MathItalicsCorrectionInfo, MathTopAccentAttachment and MathKernInfo may
+  // be NULL, but that's the case in some fonts (e.g STIX) so we accept that.
+  if (offset_math_italics_correction_info) {
+    if (offset_math_italics_correction_info >= length ||
+        offset_math_italics_correction_info < kMathGlyphInfoHeaderSize ||
+        !ParseMathItalicsCorrectionInfoTable(
+            file, data + offset_math_italics_correction_info,
+            length - offset_math_italics_correction_info,
+            num_glyphs)) {
+      return OTS_FAILURE();
+    }
+  }
+  if (offset_math_top_accent_attachment) {
+    if (offset_math_top_accent_attachment >= length ||
+        offset_math_top_accent_attachment < kMathGlyphInfoHeaderSize ||
+        !ParseMathTopAccentAttachmentTable(file, data +
+                                           offset_math_top_accent_attachment,
+                                           length -
+                                           offset_math_top_accent_attachment,
+                                           num_glyphs)) {
+      return OTS_FAILURE();
+    }
+  }
+  if (offset_extended_shaped_coverage) {
+    if (offset_extended_shaped_coverage >= length ||
+        offset_extended_shaped_coverage < kMathGlyphInfoHeaderSize ||
+        !ots::ParseCoverageTable(file, data + offset_extended_shaped_coverage,
+                                 length - offset_extended_shaped_coverage,
+                                 num_glyphs)) {
+      return OTS_FAILURE();
+    }
+  }
+  if (offset_math_kern_info) {
+    if (offset_math_kern_info >= length ||
+        offset_math_kern_info < kMathGlyphInfoHeaderSize ||
+        !ParseMathKernInfoTable(file, data + offset_math_kern_info,
+                                length - offset_math_kern_info, num_glyphs)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  return true;
+}
+
+bool ParseGlyphAssemblyTable(const ots::OpenTypeFile *file,
+                             const uint8_t *data,
+                             size_t length, const uint16_t num_glyphs) {
+  ots::Buffer subtable(data, length);
+
+  // Check the header.
+  uint16_t part_count = 0;
+  if (!ParseMathValueRecord(file, &subtable, data, length) ||
+      !subtable.ReadU16(&part_count)) {
+    return OTS_FAILURE();
+  }
+
+  const unsigned sequence_end = kMathValueRecordSize +
+    static_cast<unsigned>(2) + part_count * kGlyphPartRecordSize;
+  if (sequence_end > std::numeric_limits<uint16_t>::max()) {
+    return OTS_FAILURE();
+  }
+
+  // Check the sequence of GlyphPartRecord.
+  for (unsigned i = 0; i < part_count; ++i) {
+    uint16_t glyph = 0;
+    uint16_t part_flags = 0;
+    if (!subtable.ReadU16(&glyph) ||
+        !subtable.Skip(2 * 3) ||
+        !subtable.ReadU16(&part_flags)) {
+      return OTS_FAILURE();
+    }
+    if (glyph >= num_glyphs) {
+      OTS_WARNING("bad glyph ID: %u", glyph);
+      return OTS_FAILURE();
+    }
+    if (part_flags & ~0x00000001) {
+      OTS_WARNING("unknown part flag: %u", part_flags);
+      return OTS_FAILURE();
+    }
+  }
+
+  return true;
+}
+
+bool ParseMathGlyphConstructionTable(const ots::OpenTypeFile *file,
+                                     const uint8_t *data,
+                                     size_t length, const uint16_t num_glyphs) {
+  ots::Buffer subtable(data, length);
+
+  // Check the header.
+  uint16_t offset_glyph_assembly = 0;
+  uint16_t variant_count = 0;
+  if (!subtable.ReadU16(&offset_glyph_assembly) ||
+      !subtable.ReadU16(&variant_count)) {
+    return OTS_FAILURE();
+  }
+
+  const unsigned sequence_end = static_cast<unsigned>(2 * 2) +
+    variant_count * 2 * 2;
+  if (sequence_end > std::numeric_limits<uint16_t>::max()) {
+    return OTS_FAILURE();
+  }
+
+  // Check the GlyphAssembly offset.
+  if (offset_glyph_assembly) {
+    if (offset_glyph_assembly >= length ||
+        offset_glyph_assembly < sequence_end) {
+      return OTS_FAILURE();
+    }
+    if (!ParseGlyphAssemblyTable(file, data + offset_glyph_assembly,
+                                 length - offset_glyph_assembly, num_glyphs)) {
+      return OTS_FAILURE();
+    }
+  }
+
+  // Check the sequence of MathGlyphVariantRecord.
+  for (unsigned i = 0; i < variant_count; ++i) {
+    uint16_t glyph = 0;
+    if (!subtable.ReadU16(&glyph) ||
+        !subtable.Skip(2)) {
+      return OTS_FAILURE();
+    }
+    if (glyph >= num_glyphs) {
+      OTS_WARNING("bad glyph ID: %u", glyph);
+      return OTS_FAILURE();
+    }
+  }
+
+  return true;
+}
+
+bool ParseMathGlyphConstructionSequence(const ots::OpenTypeFile *file,
+                                        ots::Buffer* subtable,
+                                        const uint8_t *data,
+                                        size_t length,
+                                        const uint16_t num_glyphs,
+                                        uint16_t offset_coverage,
+                                        uint16_t glyph_count,
+                                        const unsigned sequence_end) {
+  // Check coverage table.
+  if (offset_coverage < sequence_end || offset_coverage >= length) {
+    return OTS_FAILURE();
+  }
+  if (!ots::ParseCoverageTable(file, data + offset_coverage,
+                               length - offset_coverage,
+                               num_glyphs, glyph_count)) {
+    return OTS_FAILURE();
+  }
+
+  // Check sequence of MathGlyphConstruction.
+  for (unsigned i = 0; i < glyph_count; ++i) {
+      uint16_t offset_glyph_construction = 0;
+      if (!subtable->ReadU16(&offset_glyph_construction)) {
+        return OTS_FAILURE();
+      }
+      if (offset_glyph_construction < sequence_end ||
+          offset_glyph_construction >= length ||
+          !ParseMathGlyphConstructionTable(file, data + offset_glyph_construction,
+                                           length - offset_glyph_construction,
+                                           num_glyphs)) {
+        return OTS_FAILURE();
+      }
+  }
+
+  return true;
+}
+
+bool ParseMathVariantsTable(const ots::OpenTypeFile *file,
+                            const uint8_t *data,
+                            size_t length, const uint16_t num_glyphs) {
+  ots::Buffer subtable(data, length);
+
+  // Check the header.
+  uint16_t offset_vert_glyph_coverage = 0;
+  uint16_t offset_horiz_glyph_coverage = 0;
+  uint16_t vert_glyph_count = 0;
+  uint16_t horiz_glyph_count = 0;
+  if (!subtable.Skip(2) ||  // MinConnectorOverlap
+      !subtable.ReadU16(&offset_vert_glyph_coverage) ||
+      !subtable.ReadU16(&offset_horiz_glyph_coverage) ||
+      !subtable.ReadU16(&vert_glyph_count) ||
+      !subtable.ReadU16(&horiz_glyph_count)) {
+    return OTS_FAILURE();
+  }
+
+  const unsigned sequence_end = 5 * 2 + vert_glyph_count * 2 +
+    horiz_glyph_count * 2;
+  if (sequence_end > std::numeric_limits<uint16_t>::max()) {
+    return OTS_FAILURE();
+  }
+
+  if (!ParseMathGlyphConstructionSequence(file, &subtable, data, length, num_glyphs,
+                                          offset_vert_glyph_coverage,
+                                          vert_glyph_count,
+                                          sequence_end) ||
+      !ParseMathGlyphConstructionSequence(file, &subtable, data, length, num_glyphs,
+                                          offset_horiz_glyph_coverage,
+                                          horiz_glyph_count,
+                                          sequence_end)) {
+    return OTS_FAILURE();
+  }
+
+  return true;
+}
+
+}  // namespace
+
+#define DROP_THIS_TABLE \
+  do { file->math->data = 0; file->math->length = 0; } while (0)
+
+namespace ots {
+
+bool ots_math_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
+  // Grab the number of glyphs in the file from the maxp table to check
+  // GlyphIDs in MATH table.
+  if (!file->maxp) {
+    return OTS_FAILURE();
+  }
+  const uint16_t num_glyphs = file->maxp->num_glyphs;
+
+  Buffer table(data, length);
+
+  OpenTypeMATH* math = new OpenTypeMATH;
+  file->math = math;
+
+  uint32_t version = 0;
+  if (!table.ReadU32(&version)) {
+    return OTS_FAILURE();
+  }
+  if (version != 0x00010000) {
+    OTS_WARNING("bad MATH version");
+    DROP_THIS_TABLE;
+    return true;
+  }
+
+  uint16_t offset_math_constants = 0;
+  uint16_t offset_math_glyph_info = 0;
+  uint16_t offset_math_variants = 0;
+  if (!table.ReadU16(&offset_math_constants) ||
+      !table.ReadU16(&offset_math_glyph_info) ||
+      !table.ReadU16(&offset_math_variants)) {
+    return OTS_FAILURE();
+  }
+
+  if (offset_math_constants >= length ||
+      offset_math_constants < kMathHeaderSize ||
+      offset_math_glyph_info >= length ||
+      offset_math_glyph_info < kMathHeaderSize ||
+      offset_math_variants >= length ||
+      offset_math_variants < kMathHeaderSize) {
+    OTS_WARNING("bad offset in MATH header");
+    DROP_THIS_TABLE;
+    return true;
+  }
+
+  if (!ParseMathConstantsTable(file, data + offset_math_constants,
+                               length - offset_math_constants)) {
+    DROP_THIS_TABLE;
+    return true;
+  }
+  if (!ParseMathGlyphInfoTable(file, data + offset_math_glyph_info,
+                               length - offset_math_glyph_info, num_glyphs)) {
+    DROP_THIS_TABLE;
+    return true;
+  }
+  if (!ParseMathVariantsTable(file, data + offset_math_variants,
+                              length - offset_math_variants, num_glyphs)) {
+    DROP_THIS_TABLE;
+    return true;
+  }
+
+  math->data = data;
+  math->length = length;
+  return true;
+}
+
+bool ots_math_should_serialise(OpenTypeFile *file) {
+  return file->math != NULL && file->math->data != NULL;
+}
+
+bool ots_math_serialise(OTSStream *out, OpenTypeFile *file) {
+  if (!out->Write(file->math->data, file->math->length)) {
+    return OTS_FAILURE();
+  }
+
+  return true;
+}
+
+void ots_math_free(OpenTypeFile *file) {
+  delete file->math;
+}
+
+}  // namespace ots
+
new file mode 100644
--- /dev/null
+++ b/gfx/ots/src/math_.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2014 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.
+
+#ifndef OTS_MATH_H_
+#define OTS_MATH_H_
+
+#include "ots.h"
+
+namespace ots {
+
+struct OpenTypeMATH {
+  OpenTypeMATH()
+      : data(NULL),
+        length(0) {
+  }
+
+  const uint8_t *data;
+  size_t length;
+};
+
+}  // namespace ots
+
+#endif
+
--- a/gfx/ots/src/maxp.cc
+++ b/gfx/ots/src/maxp.cc
@@ -1,40 +1,42 @@
 // 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.
 
 #include "maxp.h"
 
 // maxp - Maximum Profile
-// http://www.microsoft.com/opentype/otspec/maxp.htm
+// http://www.microsoft.com/typography/otspec/maxp.htm
+
+#define TABLE_NAME "maxp"
 
 namespace ots {
 
 bool ots_maxp_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypeMAXP *maxp = new OpenTypeMAXP;
   file->maxp = maxp;
 
   uint32_t version = 0;
   if (!table.ReadU32(&version)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read version of maxp table");
   }
 
   if (version >> 16 > 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad maxp version %d", version);
   }
 
   if (!table.ReadU16(&maxp->num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read number of glyphs from maxp table");
   }
 
   if (!maxp->num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad number of glyphs 0 in maxp table");
   }
 
   if (version >> 16 == 1) {
     maxp->version_1 = true;
     if (!table.ReadU16(&maxp->max_points) ||
         !table.ReadU16(&maxp->max_contours) ||
         !table.ReadU16(&maxp->max_c_points) ||
         !table.ReadU16(&maxp->max_c_contours) ||
@@ -42,31 +44,31 @@ bool ots_maxp_parse(OpenTypeFile *file, 
         !table.ReadU16(&maxp->max_t_points) ||
         !table.ReadU16(&maxp->max_storage) ||
         !table.ReadU16(&maxp->max_fdefs) ||
         !table.ReadU16(&maxp->max_idefs) ||
         !table.ReadU16(&maxp->max_stack) ||
         !table.ReadU16(&maxp->max_size_glyf_instructions) ||
         !table.ReadU16(&maxp->max_c_components) ||
         !table.ReadU16(&maxp->max_c_depth)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read maxp table");
     }
 
     if (maxp->max_zones == 0) {
       // workaround for ipa*.ttf Japanese fonts.
       OTS_WARNING("bad max_zones: %u", maxp->max_zones);
       maxp->max_zones = 1;
     } else if (maxp->max_zones == 3) {
       // workaround for Ecolier-*.ttf fonts.
       OTS_WARNING("bad max_zones: %u", maxp->max_zones);
       maxp->max_zones = 2;
     }
 
     if ((maxp->max_zones != 1) && (maxp->max_zones != 2)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad max zones %d in maxp", maxp->max_zones);
     }
   } else {
     maxp->version_1 = false;
   }
 
   return true;
 }
 
@@ -74,53 +76,53 @@ bool ots_maxp_should_serialise(OpenTypeF
   return file->maxp != NULL;
 }
 
 bool ots_maxp_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeMAXP *maxp = file->maxp;
 
   if (!out->WriteU32(maxp->version_1 ? 0x00010000 : 0x00005000) ||
       !out->WriteU16(maxp->num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write maxp version or number of glyphs");
   }
 
   if (!maxp->version_1) return true;
 
   if (!out->WriteU16(maxp->max_points) ||
       !out->WriteU16(maxp->max_contours) ||
       !out->WriteU16(maxp->max_c_points) ||
       !out->WriteU16(maxp->max_c_contours)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write maxp");
   }
 
   if (g_transcode_hints) {
     if (!out->WriteU16(maxp->max_zones) ||
         !out->WriteU16(maxp->max_t_points) ||
         !out->WriteU16(maxp->max_storage) ||
         !out->WriteU16(maxp->max_fdefs) ||
         !out->WriteU16(maxp->max_idefs) ||
         !out->WriteU16(maxp->max_stack) ||
         !out->WriteU16(maxp->max_size_glyf_instructions)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write more maxp");
     }
   } else {
     if (!out->WriteU16(1) ||  // max zones
         !out->WriteU16(0) ||  // max twilight points
         !out->WriteU16(0) ||  // max storage
         !out->WriteU16(0) ||  // max function defs
         !out->WriteU16(0) ||  // max instruction defs
         !out->WriteU16(0) ||  // max stack elements
         !out->WriteU16(0)) {  // max instruction byte count
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write more maxp");
     }
   }
 
   if (!out->WriteU16(maxp->max_c_components) ||
       !out->WriteU16(maxp->max_c_depth)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write yet more maxp");
   }
 
   return true;
 }
 
 void ots_maxp_free(OpenTypeFile *file) {
   delete file->maxp;
 }
--- a/gfx/ots/src/metrics.cc
+++ b/gfx/ots/src/metrics.cc
@@ -3,128 +3,132 @@
 // found in the LICENSE file.
 
 #include "metrics.h"
 
 #include "head.h"
 #include "maxp.h"
 
 // OpenType horizontal and vertical common header format
-// http://www.microsoft.com/opentype/otspec/hhea.htm
-// http://www.microsoft.com/opentype/otspec/vhea.htm
+// http://www.microsoft.com/typography/otspec/hhea.htm
+// http://www.microsoft.com/typography/otspec/vhea.htm
+
+#define TABLE_NAME "metrics" // XXX: use individual table names
 
 namespace ots {
 
 bool ParseMetricsHeader(OpenTypeFile *file, Buffer *table,
                         OpenTypeMetricsHeader *header) {
   if (!table->ReadS16(&header->ascent) ||
       !table->ReadS16(&header->descent) ||
       !table->ReadS16(&header->linegap) ||
       !table->ReadU16(&header->adv_width_max) ||
       !table->ReadS16(&header->min_sb1) ||
       !table->ReadS16(&header->min_sb2) ||
       !table->ReadS16(&header->max_extent) ||
       !table->ReadS16(&header->caret_slope_rise) ||
       !table->ReadS16(&header->caret_slope_run) ||
       !table->ReadS16(&header->caret_offset)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read metrics header");
   }
 
   if (header->ascent < 0) {
     OTS_WARNING("bad ascent: %d", header->ascent);
     header->ascent = 0;
   }
   if (header->linegap < 0) {
     OTS_WARNING("bad linegap: %d", header->linegap);
     header->linegap = 0;
   }
 
   if (!file->head) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing head font table");
   }
 
   // if the font is non-slanted, caret_offset should be zero.
   if (!(file->head->mac_style & 2) &&
       (header->caret_offset != 0)) {
     OTS_WARNING("bad caret offset: %d", header->caret_offset);
     header->caret_offset = 0;
   }
 
   // skip the reserved bytes
   if (!table->Skip(8)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip reserverd bytes");
   }
 
   int16_t data_format;
   if (!table->ReadS16(&data_format)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read data format");
   }
   if (data_format) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad data format %d", data_format);
   }
 
   if (!table->ReadU16(&header->num_metrics)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read number of metrics");
   }
 
   if (!file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Missing maxp font table");
   }
 
   if (header->num_metrics > file->maxp->num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad number of metrics %d", header->num_metrics);
   }
 
   return true;
 }
 
-bool SerialiseMetricsHeader(OTSStream *out,
+bool SerialiseMetricsHeader(const ots::OpenTypeFile *file,
+                            OTSStream *out,
                             const OpenTypeMetricsHeader *header) {
   if (!out->WriteU32(header->version) ||
       !out->WriteS16(header->ascent) ||
       !out->WriteS16(header->descent) ||
       !out->WriteS16(header->linegap) ||
       !out->WriteU16(header->adv_width_max) ||
       !out->WriteS16(header->min_sb1) ||
       !out->WriteS16(header->min_sb2) ||
       !out->WriteS16(header->max_extent) ||
       !out->WriteS16(header->caret_slope_rise) ||
       !out->WriteS16(header->caret_slope_run) ||
       !out->WriteS16(header->caret_offset) ||
       !out->WriteR64(0) ||  // reserved
       !out->WriteS16(0) ||  // metric data format
       !out->WriteU16(header->num_metrics)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write metrics");
   }
 
   return true;
 }
 
-bool ParseMetricsTable(Buffer *table,
+bool ParseMetricsTable(const ots::OpenTypeFile *file,
+                       Buffer *table,
                        const uint16_t num_glyphs,
                        const OpenTypeMetricsHeader *header,
                        OpenTypeMetricsTable *metrics) {
   // |num_metrics| is a uint16_t, so it's bounded < 65536. This limits that
   // amount of memory that we'll allocate for this to a sane amount.
   const unsigned num_metrics = header->num_metrics;
 
   if (num_metrics > num_glyphs) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad number of metrics %d", num_metrics);
   }
   if (!num_metrics) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("No metrics!");
   }
   const unsigned num_sbs = num_glyphs - num_metrics;
 
   metrics->entries.reserve(num_metrics);
   for (unsigned i = 0; i < num_metrics; ++i) {
     uint16_t adv = 0;
     int16_t sb = 0;
     if (!table->ReadU16(&adv) || !table->ReadS16(&sb)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read metric %d", i);
     }
 
     // Since so many fonts don't have proper value on |adv| and |sb|,
     // we should not call ots_failure() here. For example, about 20% of fonts
     // in http://www.princexml.com/fonts/ (200+ fonts) fails these tests.
     if (adv > header->adv_width_max) {
       OTS_WARNING("bad adv: %u > %u", adv, header->adv_width_max);
       adv = header->adv_width_max;
@@ -138,44 +142,45 @@ bool ParseMetricsTable(Buffer *table,
     metrics->entries.push_back(std::make_pair(adv, sb));
   }
 
   metrics->sbs.reserve(num_sbs);
   for (unsigned i = 0; i < num_sbs; ++i) {
     int16_t sb;
     if (!table->ReadS16(&sb)) {
       // Some Japanese fonts (e.g., mona.ttf) fail this test.
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read side bearing %d", i + num_metrics);
     }
 
     if (sb < header->min_sb1) {
       // The same as above. Three fonts in http://www.fontsquirrel.com/fontface
       // (e.g., Notice2Std.otf) have weird lsb values.
       OTS_WARNING("bad lsb: %d < %d", sb, header->min_sb1);
       sb = header->min_sb1;
     }
 
     metrics->sbs.push_back(sb);
   }
 
   return true;
 }
 
-bool SerialiseMetricsTable(OTSStream *out,
+bool SerialiseMetricsTable(const ots::OpenTypeFile *file,
+                           OTSStream *out,
                            const OpenTypeMetricsTable *metrics) {
   for (unsigned i = 0; i < metrics->entries.size(); ++i) {
     if (!out->WriteU16(metrics->entries[i].first) ||
         !out->WriteS16(metrics->entries[i].second)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write metric %d", i);
     }
   }
 
   for (unsigned i = 0; i < metrics->sbs.size(); ++i) {
     if (!out->WriteS16(metrics->sbs[i])) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write side bearing %ld", i + metrics->entries.size());
     }
   }
 
   return true;
 }
 
 }  // namespace ots
 
--- a/gfx/ots/src/metrics.h
+++ b/gfx/ots/src/metrics.h
@@ -1,17 +1,17 @@
 // Copyright (c) 2011 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.
 
 #ifndef OTS_METRICS_H_
 #define OTS_METRICS_H_
 
 #include <new>
-#include <utility>  // std::pair
+#include <utility>
 #include <vector>
 
 #include "ots.h"
 
 namespace ots {
 
 struct OpenTypeMetricsHeader {
   uint32_t version;
@@ -30,22 +30,25 @@ struct OpenTypeMetricsHeader {
 
 struct OpenTypeMetricsTable {
   std::vector<std::pair<uint16_t, int16_t> > entries;
   std::vector<int16_t> sbs;
 };
 
 bool ParseMetricsHeader(OpenTypeFile *file, Buffer *table,
                         OpenTypeMetricsHeader *header);
-bool SerialiseMetricsHeader(OTSStream *out,
+bool SerialiseMetricsHeader(const ots::OpenTypeFile *file,
+                            OTSStream *out,
                             const OpenTypeMetricsHeader *header);
 
-bool ParseMetricsTable(Buffer *table,
+bool ParseMetricsTable(const ots::OpenTypeFile *file,
+                       Buffer *table,
                        const uint16_t num_glyphs,
                        const OpenTypeMetricsHeader *header,
                        OpenTypeMetricsTable *metrics);
-bool SerialiseMetricsTable(OTSStream *out,
+bool SerialiseMetricsTable(const ots::OpenTypeFile *file,
+                           OTSStream *out,
                            const OpenTypeMetricsTable *metrics);
 
 }  // namespace ots
 
 #endif  // OTS_METRICS_H_
 
--- a/gfx/ots/src/moz.build
+++ b/gfx/ots/src/moz.build
@@ -13,52 +13,50 @@ SOURCES += [
     # don't unify sources that use a (file-specific) DROP_THIS_TABLE macro
     'gasp.cc',
     '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',
     'fpgm.cc',
     'glyf.cc',
-    'graphite.cc',
     'head.cc',
     'hhea.cc',
     'hmtx.cc',
     'layout.cc',
     'loca.cc',
     'maxp.cc',
     'metrics.cc',
     'name.cc',
     'os2.cc',
     'ots.cc',
     'post.cc',
     'prep.cc',
-    'svg.cc',
     'vhea.cc',
     'vmtx.cc',
 ]
 
 MSVC_ENABLE_PGO = True
 
 if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
     NO_VISIBILITY_FLAGS = True
 
 FINAL_LIBRARY = 'gkmedias'
 
 DEFINES['PACKAGE_VERSION'] = '"moz"'
 DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"'
 DEFINES['NOMINMAX'] = True
-DEFINES['MOZ_OTS_REPORT_ERRORS'] = True
 
 if CONFIG['OS_TARGET'] == 'WINNT':
     DEFINES['OTS_DLL'] = True
     DEFINES['OTS_DLL_EXPORTS'] = True
--- a/gfx/ots/src/name.cc
+++ b/gfx/ots/src/name.cc
@@ -5,17 +5,19 @@
 #include "name.h"
 
 #include <algorithm>
 #include <cstring>
 
 #include "cff.h"
 
 // name - Naming Table
-// http://www.microsoft.com/opentype/otspec/name.htm
+// http://www.microsoft.com/typography/otspec/name.htm
+
+#define TABLE_NAME "name"
 
 namespace {
 
 bool ValidInPsName(char c) {
   return (c > 0x20 && c < 0x7f && !std::strchr("[](){}<>/%", c));
 }
 
 bool CheckPsNameAscii(const std::string& name) {
@@ -59,27 +61,27 @@ namespace ots {
 bool ots_name_parse(OpenTypeFile* file, const uint8_t* data, size_t length) {
   Buffer table(data, length);
 
   OpenTypeNAME* name = new OpenTypeNAME;
   file->name = name;
 
   uint16_t format = 0;
   if (!table.ReadU16(&format) || format > 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read name table format or bad format %d", format);
   }
 
   uint16_t count = 0;
   if (!table.ReadU16(&count)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read name count");
   }
 
   uint16_t string_offset = 0;
   if (!table.ReadU16(&string_offset) || string_offset > length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read strings offset");
   }
   const char* string_base = reinterpret_cast<const char*>(data) +
       string_offset;
 
   NameRecord prev_record;
   bool sort_required = false;
 
   // Read all the names, discarding any with invalid IDs,
@@ -90,17 +92,17 @@ bool ots_name_parse(OpenTypeFile* file, 
     NameRecord rec;
     uint16_t name_length, name_offset;
     if (!table.ReadU16(&rec.platform_id) ||
         !table.ReadU16(&rec.encoding_id) ||
         !table.ReadU16(&rec.language_id) ||
         !table.ReadU16(&rec.name_id) ||
         !table.ReadU16(&name_length) ||
         !table.ReadU16(&name_offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read name entry %d", i);
     }
     // check platform & encoding, discard names with unknown values
     switch (rec.platform_id) {
       case 0:  // Unicode
         if (rec.encoding_id > 6) {
           continue;
         }
         break;
@@ -161,38 +163,38 @@ bool ots_name_parse(OpenTypeFile* file, 
     name->names.push_back(rec);
     prev_record = rec;
   }
 
   if (format == 1) {
     // extended name table format with language tags
     uint16_t lang_tag_count;
     if (!table.ReadU16(&lang_tag_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read language tag count");
     }
     for (unsigned i = 0; i < lang_tag_count; ++i) {
       uint16_t tag_length = 0;
       uint16_t tag_offset = 0;
       if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Faile to read tag length or offset");
       }
       const unsigned tag_end = static_cast<unsigned>(string_offset) +
           tag_offset + tag_length;
       if (tag_end > length) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("bad end of tag %d > %ld for name entry %d", tag_end, length, i);
       }
       std::string tag(string_base + tag_offset, tag_length);
       name->lang_tags.push_back(tag);
     }
   }
 
   if (table.offset() > string_offset) {
     // the string storage apparently overlapped the name/tag records;
     // consider this font to be badly broken
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad table offset %ld > %d", table.offset(), string_offset);
   }
 
   // check existence of required name strings (synthesize if necessary)
   //  [0 - copyright - skip]
   //   1 - family
   //   2 - subfamily
   //  [3 - unique ID - skip]
   //   4 - full name
@@ -275,56 +277,56 @@ bool ots_name_serialise(OTSStream* out, 
   size_t string_offset = 6 + name_count * 12;
 
   if (name->lang_tags.size() > 0) {
     // lang tags require a format-1 name table
     format = 1;
     string_offset += 2 + lang_tag_count * 4;
   }
   if (string_offset > 0xffff) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad string offset %ld", string_offset);
   }
   if (!out->WriteU16(format) ||
       !out->WriteU16(name_count) ||
       !out->WriteU16(string_offset)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write name header");
   }
 
   std::string string_data;
   for (std::vector<NameRecord>::const_iterator name_iter = name->names.begin();
        name_iter != name->names.end(); name_iter++) {
     const NameRecord& rec = *name_iter;
     if (!out->WriteU16(rec.platform_id) ||
         !out->WriteU16(rec.encoding_id) ||
         !out->WriteU16(rec.language_id) ||
         !out->WriteU16(rec.name_id) ||
         !out->WriteU16(rec.text.size()) ||
         !out->WriteU16(string_data.size()) ) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Faile to write name entry");
     }
     string_data.append(rec.text);
   }
 
   if (format == 1) {
     if (!out->WriteU16(lang_tag_count)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Faile to write language tag count");
     }
     for (std::vector<std::string>::const_iterator tag_iter =
              name->lang_tags.begin();
          tag_iter != name->lang_tags.end(); tag_iter++) {
       if (!out->WriteU16(tag_iter->size()) ||
           !out->WriteU16(string_data.size())) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to write string");
       }
       string_data.append(*tag_iter);
     }
   }
 
   if (!out->Write(string_data.data(), string_data.size())) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Faile to write string data");
   }
 
   return true;
 }
 
 void ots_name_free(OpenTypeFile* file) {
   delete file->name;
 }
--- a/gfx/ots/src/name.h
+++ b/gfx/ots/src/name.h
@@ -13,22 +13,22 @@
 #include "ots.h"
 
 namespace ots {
 
 struct NameRecord {
   NameRecord() {
   }
 
-  NameRecord(uint16_t platform_id, uint16_t encoding_id,
-             uint16_t language_id, uint16_t name_id)
-    : platform_id(platform_id),
-      encoding_id(encoding_id),
-      language_id(language_id),
-      name_id(name_id) {
+  NameRecord(uint16_t platformID, uint16_t encodingID,
+             uint16_t languageID, uint16_t nameID)
+    : platform_id(platformID),
+      encoding_id(encodingID),
+      language_id(languageID),
+      name_id(nameID) {
   }
 
   uint16_t platform_id;
   uint16_t encoding_id;
   uint16_t language_id;
   uint16_t name_id;
   std::string text;
 
--- a/gfx/ots/src/os2.cc
+++ b/gfx/ots/src/os2.cc
@@ -2,17 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "os2.h"
 
 #include "head.h"
 
 // OS/2 - OS/2 and Windows Metrics
-// http://www.microsoft.com/opentype/otspec/os2.htm
+// http://www.microsoft.com/typography/otspec/os2.htm
+
+#define TABLE_NAME "OS/2"
 
 namespace ots {
 
 bool ots_os2_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypeOS2 *os2 = new OpenTypeOS2;
   file->os2 = os2;
@@ -28,21 +30,21 @@ bool ots_os2_parse(OpenTypeFile *file, c
       !table.ReadS16(&os2->subscript_y_offset) ||
       !table.ReadS16(&os2->superscript_x_size) ||
       !table.ReadS16(&os2->superscript_y_size) ||
       !table.ReadS16(&os2->superscript_x_offset) ||
       !table.ReadS16(&os2->superscript_y_offset) ||
       !table.ReadS16(&os2->strikeout_size) ||
       !table.ReadS16(&os2->strikeout_position) ||
       !table.ReadS16(&os2->family_class)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed toi read basic os2 elements");
   }
 
   if (os2->version > 4) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("os2 version too high %d", os2->version);
   }
 
   // Some linux fonts (e.g., Kedage-t.ttf and LucidaSansDemiOblique.ttf) have
   // weird weight/width classes. Overwrite them with FW_NORMAL/1/9.
   if (os2->weight_class < 100 ||
       os2->weight_class > 900 ||
       os2->weight_class % 100) {
     OTS_WARNING("bad weight: %u", os2->weight_class);
@@ -89,45 +91,45 @@ bool ots_os2_parse(OpenTypeFile *file, c
   }
   if (os2->strikeout_size < 0) {
     OTS_WARNING("bad strikeout_size: %d", os2->strikeout_size);
     os2->strikeout_size = 0;
   }
 
   for (unsigned i = 0; i < 10; ++i) {
     if (!table.ReadU8(&os2->panose[i])) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read panose in os2 table");
     }
   }
 
   if (!table.ReadU32(&os2->unicode_range_1) ||
       !table.ReadU32(&os2->unicode_range_2) ||
       !table.ReadU32(&os2->unicode_range_3) ||
       !table.ReadU32(&os2->unicode_range_4) ||
       !table.ReadU32(&os2->vendor_id) ||
       !table.ReadU16(&os2->selection) ||
       !table.ReadU16(&os2->first_char_index) ||
       !table.ReadU16(&os2->last_char_index) ||
       !table.ReadS16(&os2->typo_ascender) ||
       !table.ReadS16(&os2->typo_descender) ||
       !table.ReadS16(&os2->typo_linegap) ||
       !table.ReadU16(&os2->win_ascent) ||
       !table.ReadU16(&os2->win_descent)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read more basic os2 fields");
   }
 
   // If bit 6 is set, then bits 0 and 5 must be clear.
   if (os2->selection & 0x40) {
     os2->selection &= 0xffdeu;
   }
 
   // the settings of bits 0 and 1 must be reflected in the macStyle bits
   // in the 'head' table.
   if (!file->head) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Head table missing from font as needed by os2 table");
   }
   if ((os2->selection & 0x1) &&
       !(file->head->mac_style & 0x2)) {
     OTS_WARNING("adjusting Mac style (italic)");
     file->head->mac_style |= 0x2;
   }
   if ((os2->selection & 0x2) &&
       !(file->head->mac_style & 0x4)) {
@@ -141,24 +143,24 @@ bool ots_os2_parse(OpenTypeFile *file, c
       (file->head->mac_style & 0x3)) {
     OTS_WARNING("adjusting Mac style (regular)");
     file->head->mac_style &= 0xfffcu;
   }
 
   if ((os2->version < 4) &&
       (os2->selection & 0x300)) {
     // bit 8 and 9 must be unset in OS/2 table versions less than 4.
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("OS2 version %d incompatible with selection %d", os2->version, os2->selection);
   }
 
   // mask reserved bits. use only 0..9 bits.
   os2->selection &= 0x3ff;
 
   if (os2->first_char_index > os2->last_char_index) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("first char index %d > last char index %d in os2", os2->first_char_index, os2->last_char_index);
   }
   if (os2->typo_linegap < 0) {
     OTS_WARNING("bad linegap: %d", os2->typo_linegap);
     os2->typo_linegap = 0;
   }
 
   if (os2->version < 1) {
     // http://www.microsoft.com/typography/otspec/os2ver0.htm
@@ -170,17 +172,17 @@ bool ots_os2_parse(OpenTypeFile *file, c
     // Some fonts (e.g., kredit1.ttf and quinquef.ttf) have weird version
     // numbers. Fix them.
     os2->version = 0;
     return true;
   }
 
   if (!table.ReadU32(&os2->code_page_range_1) ||
       !table.ReadU32(&os2->code_page_range_2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read codepage ranges");
   }
 
   if (os2->version < 2) {
     // http://www.microsoft.com/typography/otspec/os2ver1.htm
     return true;
   }
 
   if (length < offsetof(OpenTypeOS2, max_context)) {
@@ -191,17 +193,17 @@ bool ots_os2_parse(OpenTypeFile *file, c
     return true;
   }
 
   if (!table.ReadS16(&os2->x_height) ||
       !table.ReadS16(&os2->cap_height) ||
       !table.ReadU16(&os2->default_char) ||
       !table.ReadU16(&os2->break_char) ||
       !table.ReadU16(&os2->max_context)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read os2 version 2 information");
   }
 
   if (os2->x_height < 0) {
     OTS_WARNING("bad x_height: %d", os2->x_height);
     os2->x_height = 0;
   }
   if (os2->cap_height < 0) {
     OTS_WARNING("bad cap_height: %d", os2->cap_height);
@@ -229,60 +231,60 @@ bool ots_os2_serialise(OTSStream *out, O
       !out->WriteS16(os2->subscript_y_offset) ||
       !out->WriteS16(os2->superscript_x_size) ||
       !out->WriteS16(os2->superscript_y_size) ||
       !out->WriteS16(os2->superscript_x_offset) ||
       !out->WriteS16(os2->superscript_y_offset) ||
       !out->WriteS16(os2->strikeout_size) ||
       !out->WriteS16(os2->strikeout_position) ||
       !out->WriteS16(os2->family_class)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write basic OS2 information");
   }
 
   for (unsigned i = 0; i < 10; ++i) {
     if (!out->Write(&os2->panose[i], 1)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write os2 panose information");
     }
   }
 
   if (!out->WriteU32(os2->unicode_range_1) ||
       !out->WriteU32(os2->unicode_range_2) ||
       !out->WriteU32(os2->unicode_range_3) ||
       !out->WriteU32(os2->unicode_range_4) ||
       !out->WriteU32(os2->vendor_id) ||
       !out->WriteU16(os2->selection) ||
       !out->WriteU16(os2->first_char_index) ||
       !out->WriteU16(os2->last_char_index) ||
       !out->WriteS16(os2->typo_ascender) ||
       !out->WriteS16(os2->typo_descender) ||
       !out->WriteS16(os2->typo_linegap) ||
       !out->WriteU16(os2->win_ascent) ||
       !out->WriteU16(os2->win_descent)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write os2 version 1 information");
   }
 
   if (os2->version < 1) {
     return true;
   }
 
   if (!out->WriteU32(os2->code_page_range_1) ||
       !out->WriteU32(os2->code_page_range_2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write codepage ranges");
   }
 
   if (os2->version < 2) {
     return true;
   }
 
   if (!out->WriteS16(os2->x_height) ||
       !out->WriteS16(os2->cap_height) ||
       !out->WriteU16(os2->default_char) ||
       !out->WriteU16(os2->break_char) ||
       !out->WriteU16(os2->max_context)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write os2 version 2 information");
   }
 
   return true;
 }
 
 void ots_os2_free(OpenTypeFile *file) {
   delete file->os2;
 }
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -9,54 +9,49 @@
 
 #include <algorithm>
 #include <cstdlib>
 #include <cstring>
 #include <limits>
 #include <map>
 #include <vector>
 
+#ifdef MOZ_OTS_WOFF2
+#include "woff2.h"
+#endif
+
 // The OpenType Font File
 // http://www.microsoft.com/typography/otspec/cmap.htm
 
 namespace {
 
 bool g_debug_output = true;
+#ifdef MOZ_OTS_WOFF2
+bool g_enable_woff2 = false;
+#endif
 
-#ifdef MOZ_OTS_REPORT_ERRORS
+ots::MessageFunc  g_message_func = NULL;
+void             *g_message_user_data = NULL;
+
+ots::TableActionFunc  g_table_action_func = NULL;
+void                 *g_table_action_user_data = NULL;
 
 // Generate a message with or without a table tag, when 'header' is the OpenTypeFile pointer
 #define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_FAILURE_MSG_TAG_(header, msg_, tag_)
 #define OTS_FAILURE_MSG_HDR(msg_)      OTS_FAILURE_MSG_(header, msg_)
 
-#else
-
-#define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_FAILURE()
-#define OTS_FAILURE_MSG_HDR(msg_)      OTS_FAILURE()
-
-#endif
-
 
 struct OpenTypeTable {
   uint32_t tag;
   uint32_t chksum;
   uint32_t offset;
   uint32_t length;
   uint32_t uncompressed_length;
 };
 
-// Round a value up to the nearest multiple of 4. Don't round the value in the
-// case that rounding up overflows.
-template<typename T> T Round4(T value) {
-  if (std::numeric_limits<T>::max() - value < 3) {
-    return value;
-  }
-  return (value + 3) & ~3;
-}
-
 bool CheckTag(uint32_t tag_value) {
   for (unsigned i = 0; i < 4; ++i) {
     const uint32_t check = tag_value & 0xff;
     if (check < 32 || check > 126) {
       return false;  // non-ASCII character found.
     }
     tag_value >>= 8;
   }
@@ -156,43 +151,22 @@ const struct {
   { "GPOS", ots::ots_gpos_parse, ots::ots_gpos_serialise,
     ots::ots_gpos_should_serialise, ots::ots_gpos_free, false },
   { "GSUB", ots::ots_gsub_parse, ots::ots_gsub_serialise,
     ots::ots_gsub_should_serialise, ots::ots_gsub_free, false },
   { "vhea", ots::ots_vhea_parse, ots::ots_vhea_serialise,
     ots::ots_vhea_should_serialise, ots::ots_vhea_free, false },
   { "vmtx", ots::ots_vmtx_parse, ots::ots_vmtx_serialise,
     ots::ots_vmtx_should_serialise, ots::ots_vmtx_free, false },
-  // SILGraphite layout tables - not actually parsed, just copied
-  { "Silf", ots::ots_silf_parse, ots::ots_silf_serialise,
-    ots::ots_silf_should_serialise, ots::ots_silf_free, false },
-  { "Sill", ots::ots_sill_parse, ots::ots_sill_serialise,
-    ots::ots_sill_should_serialise, ots::ots_sill_free, false },
-  { "Gloc", ots::ots_gloc_parse, ots::ots_gloc_serialise,
-    ots::ots_gloc_should_serialise, ots::ots_gloc_free, false },
-  { "Glat", ots::ots_glat_parse, ots::ots_glat_serialise,
-    ots::ots_glat_should_serialise, ots::ots_glat_free, false },
-  { "Feat", ots::ots_feat_parse, ots::ots_feat_serialise,
-    ots::ots_feat_should_serialise, ots::ots_feat_free, false },
-  // SVG glyph table
-  { "SVG ", ots::ots_svg_parse, ots::ots_svg_serialise,
-    ots::ots_svg_should_serialise, ots::ots_svg_free, false},
+  { "MATH", ots::ots_math_parse, ots::ots_math_serialise,
+    ots::ots_math_should_serialise, ots::ots_math_free, false },
   // TODO(bashi): Support mort, base, and jstf tables.
   { 0, NULL, NULL, NULL, NULL, false },
 };
 
-bool IsValidVersionTag(uint32_t tag) {
-  return tag == Tag("\x00\x01\x00\x00") ||
-         // OpenType fonts with CFF data have 'OTTO' tag.
-         tag == Tag("OTTO") ||
-         // Older Mac fonts might have 'true' or 'typ1' tag.
-         tag == Tag("true") ||
-         tag == Tag("typ1");
-}
-
 bool ProcessGeneric(ots::OpenTypeFile *header,
                     uint32_t signature,
                     ots::OTSStream *output,
                     const uint8_t *data, size_t length,
                     const std::vector<OpenTypeTable>& tables,
                     ots::Buffer& file);
 
 bool ProcessTTF(ots::OpenTypeFile *header,
@@ -202,17 +176,17 @@ bool ProcessTTF(ots::OpenTypeFile *heade
   // we disallow all files > 1GB in size for sanity.
   if (length > 1024 * 1024 * 1024) {
     return OTS_FAILURE_MSG_HDR("file exceeds 1GB");
   }
 
   if (!file.ReadTag(&header->version)) {
     return OTS_FAILURE_MSG_HDR("error reading version tag");
   }
-  if (!IsValidVersionTag(header->version)) {
+  if (!ots::IsValidVersionTag(header->version)) {
       return OTS_FAILURE_MSG_HDR("invalid version tag");
   }
 
   if (!file.ReadU16(&header->num_tables) ||
       !file.ReadU16(&header->search_range) ||
       !file.ReadU16(&header->entry_selector) ||
       !file.ReadU16(&header->range_shift)) {
     return OTS_FAILURE_MSG_HDR("error reading table directory search header");
@@ -288,17 +262,17 @@ bool ProcessWOFF(ots::OpenTypeFile *head
 
   if (woff_tag != Tag("wOFF")) {
     return OTS_FAILURE_MSG_HDR("invalid WOFF marker");
   }
 
   if (!file.ReadTag(&header->version)) {
     return OTS_FAILURE_MSG_HDR("error reading version tag");
   }
-  if (!IsValidVersionTag(header->version)) {
+  if (!ots::IsValidVersionTag(header->version)) {
     return OTS_FAILURE_MSG_HDR("invalid version tag");
   }
 
   header->search_range = 0;
   header->entry_selector = 0;
   header->range_shift = 0;
 
   uint32_t reported_length;
@@ -366,75 +340,144 @@ bool ProcessWOFF(ots::OpenTypeFile *head
     if (!file.ReadTag(&table.tag) ||
         !file.ReadU32(&table.offset) ||
         !file.ReadU32(&table.length) ||
         !file.ReadU32(&table.uncompressed_length) ||
         !file.ReadU32(&table.chksum)) {
       return OTS_FAILURE_MSG_HDR("error reading table directory");
     }
 
-    total_sfnt_size += Round4(table.uncompressed_length);
+    total_sfnt_size += ots::Round4(table.uncompressed_length);
     if (total_sfnt_size > std::numeric_limits<uint32_t>::max()) {
       return OTS_FAILURE_MSG_HDR("sfnt size overflow");
     }
     tables.push_back(table);
     if (i == 0 || tables[first_index].offset > table.offset)
       first_index = i;
     if (i == 0 || tables[last_index].offset < table.offset)
       last_index = i;
   }
 
   if (reported_total_sfnt_size != total_sfnt_size) {
     return OTS_FAILURE_MSG_HDR("uncompressed sfnt size mismatch");
   }
 
   // Table data must follow immediately after the header.
-  if (tables[first_index].offset != Round4(file.offset())) {
+  if (tables[first_index].offset != ots::Round4(file.offset())) {
     return OTS_FAILURE_MSG_HDR("junk before tables in WOFF file");
   }
 
   if (tables[last_index].offset >= length ||
       length - tables[last_index].offset < tables[last_index].length) {
     return OTS_FAILURE_MSG_HDR("invalid table location/size");
   }
   // Blocks must follow immediately after the previous block.
   // (Except for padding with a maximum of three null bytes)
-  uint64_t block_end = Round4(
+  uint64_t block_end = ots::Round4(
       static_cast<uint64_t>(tables[last_index].offset) +
       static_cast<uint64_t>(tables[last_index].length));
   if (block_end > std::numeric_limits<uint32_t>::max()) {
     return OTS_FAILURE_MSG_HDR("invalid table location/size");
   }
   if (meta_offset) {
     if (block_end != meta_offset) {
       return OTS_FAILURE_MSG_HDR("invalid metadata block location");
     }
-    block_end = Round4(static_cast<uint64_t>(meta_offset) +
-                       static_cast<uint64_t>(meta_length));
+    block_end = ots::Round4(static_cast<uint64_t>(meta_offset) +
+                            static_cast<uint64_t>(meta_length));
     if (block_end > std::numeric_limits<uint32_t>::max()) {
       return OTS_FAILURE_MSG_HDR("invalid metadata block size");
     }
   }
   if (priv_offset) {
     if (block_end != priv_offset) {
       return OTS_FAILURE_MSG_HDR("invalid private block location");
     }
-    block_end = Round4(static_cast<uint64_t>(priv_offset) +
-                       static_cast<uint64_t>(priv_length));
+    block_end = ots::Round4(static_cast<uint64_t>(priv_offset) +
+                            static_cast<uint64_t>(priv_length));
     if (block_end > std::numeric_limits<uint32_t>::max()) {
       return OTS_FAILURE_MSG_HDR("invalid private block size");
     }
   }
-  if (block_end != Round4(length)) {
+  if (block_end != ots::Round4(length)) {
     return OTS_FAILURE_MSG_HDR("file length mismatch (trailing junk?)");
   }
 
   return ProcessGeneric(header, woff_tag, output, data, length, tables, file);
 }
 
+#ifdef MOZ_OTS_WOFF2
+bool ProcessWOFF2(ots::OpenTypeFile *header,
+                  ots::OTSStream *output, const uint8_t *data, size_t length) {
+  size_t decompressed_size = ots::ComputeWOFF2FinalSize(data, length);
+  if (decompressed_size == 0) {
+    return OTS_FAILURE();
+  }
+  // decompressed font must be <= 30MB
+  if (decompressed_size > 30 * 1024 * 1024) {
+    return OTS_FAILURE();
+  }
+
+  std::vector<uint8_t> decompressed_buffer(decompressed_size);
+  if (!ots::ConvertWOFF2ToTTF(&decompressed_buffer[0], decompressed_size,
+                              data, length)) {
+    return OTS_FAILURE();
+  }
+  return ProcessTTF(header, output, &decompressed_buffer[0], decompressed_size);
+}
+#endif
+
+ots::TableAction GetTableAction(uint32_t tag) {
+  ots::TableAction action = ots::TABLE_ACTION_DEFAULT;
+
+  if (g_table_action_func != NULL) {
+    action = g_table_action_func(htonl(tag), g_table_action_user_data);
+  }
+
+  if (action == ots::TABLE_ACTION_DEFAULT) {
+    action = ots::TABLE_ACTION_DROP;
+
+    for (unsigned i = 0; ; ++i) {
+      if (table_parsers[i].parse == NULL) break;
+
+      if (Tag(table_parsers[i].tag) == tag) {
+        action = ots::TABLE_ACTION_SANITIZE;
+        break;
+      }
+    }
+  }
+
+  assert(action != ots::TABLE_ACTION_DEFAULT); // Should never return this.
+  return action;
+}
+
+bool GetTableData(const uint8_t *data,
+                  const OpenTypeTable table,
+                  Arena *arena,
+                  size_t *table_length,
+                  const uint8_t **table_data) {
+  if (table.uncompressed_length != table.length) {
+    // Compressed table. Need to uncompress into memory first.
+    *table_length = table.uncompressed_length;
+    *table_data = (*arena).Allocate(*table_length);
+    uLongf dest_len = *table_length;
+    int r = uncompress((Bytef*) *table_data, &dest_len,
+                       data + table.offset, table.length);
+    if (r != Z_OK || dest_len != *table_length) {
+      return false;
+    }
+  } else {
+    // Uncompressed table. We can process directly from memory.
+    *table_data = data + table.offset;
+    *table_length = table.length;
+  }
+
+  return true;
+}
+
 bool ProcessGeneric(ots::OpenTypeFile *header, uint32_t signature,
                     ots::OTSStream *output,
                     const uint8_t *data, size_t length,
                     const std::vector<OpenTypeTable>& tables,
                     ots::Buffer& file) {
   const size_t data_offset = file.offset();
 
   uint32_t uncompressed_sum = 0;
@@ -490,17 +533,17 @@ bool ProcessGeneric(ots::OpenTypeFile *h
 
       uncompressed_sum += tables[i].uncompressed_length;
     }
     // since we required that the file be < 1GB in length, and that the table
     // length is < 1GB, the following addtion doesn't overflow
     uint32_t end_byte = tables[i].offset + tables[i].length;
     // Tables in the WOFF file must be aligned 4-byte boundary.
     if (signature == Tag("wOFF")) {
-        end_byte = Round4(end_byte);
+        end_byte = ots::Round4(end_byte);
     }
     if (!end_byte || end_byte > length) {
       return OTS_FAILURE_MSG_TAG("table overruns end of file", &tables[i].tag);
     }
   }
 
   // All decompressed tables uncompressed must be <= 30MB.
   if (uncompressed_sum > 30 * 1024 * 1024) {
@@ -533,43 +576,33 @@ bool ProcessGeneric(ots::OpenTypeFile *h
   Arena arena;
 
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) break;
 
     const std::map<uint32_t, OpenTypeTable>::const_iterator it
         = table_map.find(Tag(table_parsers[i].tag));
 
+    ots::TableAction action = GetTableAction(it->first);
     if (it == table_map.end()) {
-      if (table_parsers[i].required) {
+      if (table_parsers[i].required && action == ots::TABLE_ACTION_SANITIZE) {
         return OTS_FAILURE_MSG_TAG("missing required table", table_parsers[i].tag);
       }
       continue;
     }
 
     const uint8_t* table_data;
     size_t table_length;
 
-    if (it->second.uncompressed_length != it->second.length) {
-      // compressed table. Need to uncompress into memory first.
-      table_length = it->second.uncompressed_length;
-      table_data = arena.Allocate(table_length);
-      uLongf dest_len = table_length;
-      int r = uncompress((Bytef*) table_data, &dest_len,
-                         data + it->second.offset, it->second.length);
-      if (r != Z_OK || dest_len != table_length) {
-        return OTS_FAILURE_MSG_TAG("uncompress failed", table_parsers[i].tag);
-      }
-    } else {
-      // uncompressed table. We can process directly from memory.
-      table_data = data + it->second.offset;
-      table_length = it->second.length;
+    if (!GetTableData(data, it->second, &arena, &table_length, &table_data)) {
+      return OTS_FAILURE_MSG_TAG("uncompress failed", table_parsers[i].tag);
     }
 
-    if (!table_parsers[i].parse(header, table_data, table_length)) {
+    if (action == ots::TABLE_ACTION_SANITIZE &&
+        !table_parsers[i].parse(header, table_data, table_length)) {
       // TODO: parsers should generate specific messages detailing the failure;
       // once those are all added, we won't need a generic failure message here
       return OTS_FAILURE_MSG_TAG("failed to parse table", table_parsers[i].tag);
     }
   }
 
   if (header->cff) {
     // font with PostScript glyph
@@ -594,16 +627,24 @@ bool ProcessGeneric(ots::OpenTypeFile *h
       break;
     }
 
     if (table_parsers[i].should_serialise(header)) {
       num_output_tables++;
     }
   }
 
+  for (std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.begin();
+       it != table_map.end(); ++it) {
+    ots::TableAction action = GetTableAction(it->first);
+    if (action == ots::TABLE_ACTION_PASSTHRU) {
+      num_output_tables++;
+    }
+  }
+
   unsigned max_pow2 = 0;
   while (1u << (max_pow2 + 1) <= num_output_tables) {
     max_pow2++;
   }
   const uint16_t output_search_range = (1u << max_pow2) << 4;
 
   // most of the errors here are highly unlikely - they'd only occur if the
   // output stream returns a failure, e.g. lack of space to write
@@ -658,16 +699,57 @@ bool ProcessGeneric(ots::OpenTypeFile *h
     // align tables to four bytes
     if (!output->Pad((4 - (end_offset & 3)) % 4)) {
       return OTS_FAILURE_MSG_HDR("error writing output");
     }
     out.chksum = output->chksum();
     out_tables.push_back(out);
   }
 
+  for (std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.begin();
+       it != table_map.end(); ++it) {
+    ots::TableAction action = GetTableAction(it->first);
+    if (action == ots::TABLE_ACTION_PASSTHRU) {
+      OutputTable out;
+      out.tag = it->second.tag;
+      out.offset = output->Tell();
+
+      output->ResetChecksum();
+      if (it->second.tag == Tag("head")) {
+        head_table_offset = out.offset;
+      }
+
+      const uint8_t* table_data;
+      size_t table_length;
+
+      if (!GetTableData(data, it->second, &arena, &table_length, &table_data)) {
+        return OTS_FAILURE_MSG_HDR("Failed to uncompress table");
+      }
+
+      if (!output->Write(table_data, table_length)) {
+        return OTS_FAILURE_MSG_HDR("Failed to serialize table");
+      }
+
+      const size_t end_offset = output->Tell();
+      if (end_offset <= out.offset) {
+        // paranoid check. |end_offset| is supposed to be greater than the offset,
+        // as long as the Tell() interface is implemented correctly.
+        return OTS_FAILURE_MSG_HDR("error writing output");
+      }
+      out.length = end_offset - out.offset;
+
+      // align tables to four bytes
+      if (!output->Pad((4 - (end_offset & 3)) % 4)) {
+        return OTS_FAILURE_MSG_HDR("error writing output");
+      }
+      out.chksum = output->chksum();
+      out_tables.push_back(out);
+    }
+  }
+
   const size_t end_of_file = output->Tell();
 
   // Need to sort the output tables for inclusion in the file
   std::sort(out_tables.begin(), out_tables.end(), OutputTable::SortByTag);
   if (!output->Seek(table_record_offset)) {
     return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
@@ -706,41 +788,64 @@ bool ProcessGeneric(ots::OpenTypeFile *h
 
   return true;
 }
 
 }  // namespace
 
 namespace ots {
 
+bool IsValidVersionTag(uint32_t tag) {
+  return tag == Tag("\x00\x01\x00\x00") ||
+         // OpenType fonts with CFF data have 'OTTO' tag.
+         tag == Tag("OTTO") ||
+         // Older Mac fonts might have 'true' or 'typ1' tag.
+         tag == Tag("true") ||
+         tag == Tag("typ1");
+}
+
 void DisableDebugOutput() {
   g_debug_output = false;
 }
 
-bool OTS_API Process(OTSStream *output, const uint8_t *data, size_t length,
-#ifdef MOZ_OTS_REPORT_ERRORS
-                     MessageFunc message_func, void *user_data,
+#ifdef MOZ_OTS_WOFF2
+void EnableWOFF2() {
+  g_enable_woff2 = true;
+}
 #endif
-                     bool preserveGraphite) {
+
+void SetMessageCallback(MessageFunc func, void *user_data) {
+  g_message_func = func;
+  g_message_user_data = user_data;
+}
+
+void SetTableActionCallback(TableActionFunc func, void *user_data) {
+  g_table_action_func = func;
+  g_table_action_user_data = user_data;
+}
+
+bool Process(OTSStream *output, const uint8_t *data, size_t length) {
   OpenTypeFile header;
 
-#ifdef MOZ_OTS_REPORT_ERRORS
-  header.message_func = message_func;
-  header.user_data = user_data;
-#endif
+  header.message_func = g_message_func;
+  header.user_data = g_message_user_data;
 
   if (length < 4) {
     return OTS_FAILURE_MSG_(&header, "file less than 4 bytes");
   }
 
-  header.preserve_graphite = preserveGraphite;
-
   bool result;
   if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == 'F') {
     result = ProcessWOFF(&header, output, data, length);
+#ifdef MOZ_OTS_WOFF2
+  } else if (g_enable_woff2 &&
+             data[0] == 'w' && data[1] == 'O' && data[2] == 'F' &&
+             data[3] == '2') {
+    result = ProcessWOFF2(&header, output, data, length);
+#endif
   } else {
     result = ProcessTTF(&header, output, data, length);
   }
 
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) break;
     table_parsers[i].free(&header);
   }
--- a/gfx/ots/src/ots.h
+++ b/gfx/ots/src/ots.h
@@ -6,19 +6,25 @@
 #define OTS_H_
 
 #include <stddef.h>
 #include <cstdarg>
 #include <cstddef>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
+#include <limits>
 
 #include "opentype-sanitiser.h"
 
+// arraysize borrowed from base/basictypes.h
+template <typename T, size_t N>
+char (&ArraySizeHelper(T (&array)[N]))[N];
+#define arraysize(array) (sizeof(ArraySizeHelper(array)))
+
 namespace ots {
 
 #if defined(_MSC_VER) || !defined(OTS_DEBUG)
 #define OTS_FAILURE() false
 #else
 #define OTS_FAILURE() ots::Failure(__FILE__, __LINE__, __PRETTY_FUNCTION__)
 bool Failure(const char *f, int l, const char *fn);
 #endif
@@ -33,49 +39,38 @@ bool Failure(const char *f, int l, const
     ots::Warning(__FILE__, __LINE__, format, ##args)
 void Warning(const char *f, int l, const char *format, ...)
      __attribute__((format(printf, 3, 4)));
 #else
 #define OTS_WARNING(format, args...)
 #endif
 #endif
 
-#ifdef MOZ_OTS_REPORT_ERRORS
-
 // All OTS_FAILURE_* macros ultimately evaluate to 'false', just like the original
 // message-less OTS_FAILURE(), so that the current parser will return 'false' as
 // its result (indicating a failure).
-// If the message-callback feature is enabled, and a message_func pointer has been
-// provided, this will be called before returning the 'false' status.
+// If a message_func pointer has been provided, this will be called before returning
+// the 'false' status.
 
 // Generate a simple message
-#define OTS_FAILURE_MSG_(otf_,msg_) \
+#define OTS_FAILURE_MSG_(otf_,...) \
   ((otf_)->message_func && \
-    (*(otf_)->message_func)((otf_)->user_data, "%s", msg_) && \
+    (*(otf_)->message_func)((otf_)->user_data, __VA_ARGS__) && \
     false)
 
 // Generate a message with an associated table tag
 #define OTS_FAILURE_MSG_TAG_(otf_,msg_,tag_) \
   ((otf_)->message_func && \
-    (*(otf_)->message_func)((otf_)->user_data, "table '%4.4s': %s", tag_, msg_) && \
+    (*(otf_)->message_func)((otf_)->user_data, "%4.4s: %s", tag_, msg_) && \
     false)
 
 // Convenience macro for use in files that only handle a single table tag,
 // defined as TABLE_NAME at the top of the file; the 'file' variable is
 // expected to be the current OpenTypeFile pointer.
-#define OTS_FAILURE_MSG(msg_) OTS_FAILURE_MSG_TAG_(file, msg_, TABLE_NAME)
-
-#else
-
-// If the message-callback feature is not enabled, error messages are just dropped.
-#define OTS_FAILURE_MSG_(otf_,msg_)          OTS_FAILURE()
-#define OTS_FAILURE_MSG_TAG_(otf_,msg_,tag_) OTS_FAILURE()
-#define OTS_FAILURE_MSG(msg_)                OTS_FAILURE()
-
-#endif
+#define OTS_FAILURE_MSG(...) OTS_FAILURE_MSG_(file, TABLE_NAME ": " __VA_ARGS__)
 
 // Define OTS_NO_TRANSCODE_HINTS (i.e., g++ -DOTS_NO_TRANSCODE_HINTS) if you
 // want to omit TrueType hinting instructions and variables in glyf, fpgm, prep,
 // and cvt tables.
 #if defined(OTS_NO_TRANSCODE_HINTS)
 const bool g_transcode_hints = false;
 #else
 const bool g_transcode_hints = true;
@@ -85,35 +80,35 @@ const bool g_transcode_hints = true;
 // Buffer helper class
 //
 // This class perform some trival buffer operations while checking for
 // out-of-bounds errors. As a family they return false if anything is amiss,
 // updating the current offset otherwise.
 // -----------------------------------------------------------------------------
 class Buffer {
  public:
-  Buffer(const uint8_t *buffer, size_t len)
-      : buffer_(buffer),
+  Buffer(const uint8_t *buf, size_t len)
+      : buffer_(buf),
         length_(len),
         offset_(0) { }
 
   bool Skip(size_t n_bytes) {
     return Read(NULL, n_bytes);
   }
 
-  bool Read(uint8_t *buffer, size_t n_bytes) {
+  bool Read(uint8_t *buf, size_t n_bytes) {
     if (n_bytes > 1024 * 1024 * 1024) {
       return OTS_FAILURE();
     }
     if ((offset_ + n_bytes > length_) ||
         (offset_ > length_ - n_bytes)) {
       return OTS_FAILURE();
     }
-    if (buffer) {
-      std::memcpy(buffer, buffer_ + offset_, n_bytes);
+    if (buf) {
+      std::memcpy(buf, buffer_ + offset_, n_bytes);
     }
     offset_ += n_bytes;
     return true;
   }
 
   inline bool ReadU8(uint8_t *value) {
     if (offset_ + 1 > length_) {
       return OTS_FAILURE();
@@ -187,16 +182,34 @@ class Buffer {
   void set_offset(size_t newoffset) { offset_ = newoffset; }
 
  private:
   const uint8_t * const buffer_;
   const size_t length_;
   size_t offset_;
 };
 
+// Round a value up to the nearest multiple of 4. Don't round the value in the
+// case that rounding up overflows.
+template<typename T> T Round4(T value) {
+  if (std::numeric_limits<T>::max() - value < 3) {
+    return value;
+  }
+  return (value + 3) & ~3;
+}
+
+template<typename T> T Round2(T value) {
+  if (value == std::numeric_limits<T>::max()) {
+    return value;
+  }
+  return (value + 1) & ~1;
+}
+
+bool IsValidVersionTag(uint32_t tag);
+
 #define FOR_EACH_TABLE_TYPE \
   F(cff, CFF) \
   F(cmap, CMAP) \
   F(cvt, CVT) \
   F(fpgm, FPGM) \
   F(gasp, GASP) \
   F(gdef, GDEF) \
   F(glyf, GLYF) \
@@ -204,31 +217,26 @@ class Buffer {
   F(gsub, GSUB) \
   F(hdmx, HDMX) \
   F(head, HEAD) \
   F(hhea, HHEA) \
   F(hmtx, HMTX) \
   F(kern, KERN) \
   F(loca, LOCA) \
   F(ltsh, LTSH) \
+  F(math, MATH) \
   F(maxp, MAXP) \
   F(name, NAME) \
   F(os2, OS2) \
   F(post, POST) \
   F(prep, PREP) \
   F(vdmx, VDMX) \
   F(vorg, VORG) \
   F(vhea, VHEA) \
-  F(vmtx, VMTX) \
-  F(silf, SILF) \
-  F(sill, SILL) \
-  F(glat, GLAT) \
-  F(gloc, GLOC) \
-  F(feat, FEAT) \
-  F(svg, SVG)
+  F(vmtx, VMTX)
 
 #define F(name, capname) struct OpenType##capname;
 FOR_EACH_TABLE_TYPE
 #undef F
 
 struct OpenTypeFile {
   OpenTypeFile() {
 #define F(name, capname) name = NULL;
@@ -237,24 +245,18 @@ struct OpenTypeFile {
   }
 
   uint32_t version;
   uint16_t num_tables;
   uint16_t search_range;
   uint16_t entry_selector;
   uint16_t range_shift;
 
-#ifdef MOZ_OTS_REPORT_ERRORS
   MessageFunc message_func;
   void        *user_data;
-#endif
-
-  // This is used to tell the relevant parsers whether to preserve the
-  // Graphite layout tables (currently _without_ any checking)
-  bool preserve_graphite;
 
 #define F(name, capname) OpenType##capname *name;
 FOR_EACH_TABLE_TYPE
 #undef F
 };
 
 #define F(name, capname) \
 bool ots_##name##_parse(OpenTypeFile *f, const uint8_t *d, size_t l); \
--- a/gfx/ots/src/post.cc
+++ b/gfx/ots/src/post.cc
@@ -2,177 +2,178 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "post.h"
 
 #include "maxp.h"
 
 // post - PostScript
-// http://www.microsoft.com/opentype/otspec/post.htm
+// http://www.microsoft.com/typography/otspec/post.htm
+
+#define TABLE_NAME "post"
 
 namespace ots {
 
 bool ots_post_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypePOST *post = new OpenTypePOST;
   file->post = post;
 
   if (!table.ReadU32(&post->version) ||
       !table.ReadU32(&post->italic_angle) ||
       !table.ReadS16(&post->underline) ||
       !table.ReadS16(&post->underline_thickness) ||
       !table.ReadU32(&post->is_fixed_pitch)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read post header");
   }
 
   if (post->underline_thickness < 0) {
     post->underline_thickness = 1;
   }
 
   if (post->version == 0x00010000) {
     return true;
   } else if (post->version == 0x00030000) {
     return true;
   } else if (post->version != 0x00020000) {
     // 0x00025000 is deprecated. We don't accept it.
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad post version %x", post->version);
   }
 
   // We have a version 2 table with a list of Pascal strings at the end
 
   // We don't care about the memory usage fields. We'll set all these to zero
   // when serialising
   if (!table.Skip(16)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to skip memory usage in post table");
   }
 
   uint16_t num_glyphs = 0;
   if (!table.ReadU16(&num_glyphs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read number of glyphs");
   }
 
   if (!file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("No maxp table required by post table");
   }
 
   if (num_glyphs == 0) {
     if (file->maxp->num_glyphs > 258) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Can't have no glyphs in the post table if there are more than 256 glyphs in the font");
     }
     OTS_WARNING("table version is 1, but no glyf names are found");
     // workaround for fonts in http://www.fontsquirrel.com/fontface
     // (e.g., yataghan.ttf).
     post->version = 0x00010000;
     return true;
   }
 
   if (num_glyphs != file->maxp->num_glyphs) {
     // Note: Fixedsys500c.ttf seems to have inconsistent num_glyphs values.
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad number of glyphs in post table %d", num_glyphs);
   }
 
   post->glyph_name_index.resize(num_glyphs);
   for (unsigned i = 0; i < num_glyphs; ++i) {
     if (!table.ReadU16(&post->glyph_name_index[i])) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read post information for glyph %d", i);
     }
-    if (post->glyph_name_index[i] >= 32768) {
-      // Note: droid_arialuni.ttf fails this test.
-      return OTS_FAILURE();  // reserved area.
-    }
+    // Note: A strict interpretation of the specification requires name indexes
+    // are less than 32768. This, however, excludes fonts like unifont.ttf
+    // which cover all of unicode.
   }
 
   // Now we have an array of Pascal strings. We have to check that they are all
   // valid and read them in.
   const size_t strings_offset = table.offset();
   const uint8_t *strings = data + strings_offset;
   const uint8_t *strings_end = data + length;
 
   for (;;) {
     if (strings == strings_end) break;
     const unsigned string_length = *strings;
     if (strings + 1 + string_length > strings_end) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad string length %d", string_length);
     }
     if (std::memchr(strings + 1, '\0', string_length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad string of length %d", string_length);
     }
     post->names.push_back(
         std::string(reinterpret_cast<const char*>(strings + 1), string_length));
     strings += 1 + string_length;
   }
   const unsigned num_strings = post->names.size();
 
   // check that all the references are within bounds
   for (unsigned i = 0; i < num_glyphs; ++i) {
     unsigned offset = post->glyph_name_index[i];
     if (offset < 258) {
       continue;
     }
 
     offset -= 258;
     if (offset >= num_strings) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad string index %d", offset);
     }
   }
 
   return true;
 }
 
 bool ots_post_should_serialise(OpenTypeFile *file) {
   return file->post != NULL;
 }
 
 bool ots_post_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypePOST *post = file->post;
 
   // OpenType with CFF glyphs must have v3 post table.
   if (file->post && file->cff && file->post->version != 0x00030000) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad post version %x", post->version);
   }
 
   if (!out->WriteU32(post->version) ||
       !out->WriteU32(post->italic_angle) ||
       !out->WriteS16(post->underline) ||
       !out->WriteS16(post->underline_thickness) ||
       !out->WriteU32(post->is_fixed_pitch) ||
       !out->WriteU32(0) ||
       !out->WriteU32(0) ||
       !out->WriteU32(0) ||
       !out->WriteU32(0)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write post header");
   }
 
   if (post->version != 0x00020000) {
     return true;  // v1.0 and v3.0 does not have glyph names.
   }
 
   if (!out->WriteU16(post->glyph_name_index.size())) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write number of indices");
   }
 
   for (unsigned i = 0; i < post->glyph_name_index.size(); ++i) {
     if (!out->WriteU16(post->glyph_name_index[i])) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write name index %d", i);
     }
   }
 
   // Now we just have to write out the strings in the correct order
   for (unsigned i = 0; i < post->names.size(); ++i) {
     const std::string& s = post->names[i];
     const uint8_t string_length = s.size();
     if (!out->Write(&string_length, 1)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write string %d", i);
     }
     // Some ttf fonts (e.g., frank.ttf on Windows Vista) have zero-length name.
     // We allow them.
     if (string_length > 0 && !out->Write(s.data(), string_length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write string length for string %d", i);
     }
   }
 
   return true;
 }
 
 void ots_post_free(OpenTypeFile *file) {
   delete file->post;
--- a/gfx/ots/src/prep.cc
+++ b/gfx/ots/src/prep.cc
@@ -1,48 +1,50 @@
 // 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.
 
 #include "prep.h"
 
 // prep - Control Value Program
-// http://www.microsoft.com/opentype/otspec/prep.htm
+// http://www.microsoft.com/typography/otspec/prep.htm
+
+#define TABLE_NAME "prep"
 
 namespace ots {
 
 bool ots_prep_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
 
   OpenTypePREP *prep = new OpenTypePREP;
   file->prep = prep;
 
   if (length >= 128 * 1024u) {
-    return OTS_FAILURE();  // almost all prep tables are less than 9k bytes.
+    return OTS_FAILURE_MSG("table length %ld > 120K", length);  // almost all prep tables are less than 9k bytes.
   }
 
   if (!table.Skip(length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read table of length %ld", length);
   }
 
   prep->data = data;
   prep->length = length;
   return true;
 }
 
 bool ots_prep_should_serialise(OpenTypeFile *file) {
   if (!file->glyf) return false;  // this table is not for CFF fonts.
   return g_transcode_hints && file->prep;
 }
 
 bool ots_prep_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypePREP *prep = file->prep;
 
   if (!out->Write(prep->data, prep->length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write table length");
   }
 
   return true;
 }
 
 void ots_prep_free(OpenTypeFile *file) {
   delete file->prep;
 }
deleted file mode 100644
--- a/gfx/ots/src/svg.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2012 Mozilla Foundation. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ots.h"
-
-#include "svg.h"
-
-#define NONFATAL_FAILURE(msg) \
-  do { \
-    OTS_WARNING(msg); \
-    delete file->svg; file->svg = 0; \
-    return true; \
-  } while (0)
-
-namespace ots {
-
-bool ots_svg_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
-  Buffer table(data, length);
-
-  OpenTypeSVG *svg = new OpenTypeSVG;
-  file->svg = svg;
-
-  uint16_t version;
-  if (!table.ReadU16(&version)) {
-    NONFATAL_FAILURE("Couldn't read SVG table header");
-  }
-  if (version != 0) {
-    NONFATAL_FAILURE("Unknown SVG table version");
-  }
-
-  uint32_t doc_index_offset;
-  if (!table.ReadU32(&doc_index_offset)) {
-    NONFATAL_FAILURE("Couldn't read doc index offset from SVG table header");
-  }
-  if (doc_index_offset == 0 || doc_index_offset >= length) {
-    NONFATAL_FAILURE("Invalid doc index offset");
-  }
-
-  uint32_t color_palettes_offset;
-  if (!table.ReadU32(&color_palettes_offset)) {
-    NONFATAL_FAILURE("Couldn't read color palettes offset from SVG table header");
-  }
-  if (color_palettes_offset >= length) {
-    NONFATAL_FAILURE("Invalid doc index offset");
-  }
-
-  uint16_t start_glyph;
-  uint16_t end_glyph;
-  uint32_t doc_offset;
-  uint32_t doc_length;
-  uint16_t last_end_glyph = 0;
-
-  table.set_offset(doc_index_offset);
-  uint16_t index_length;
-  if (!table.ReadU16(&index_length)) {
-    NONFATAL_FAILURE("Couldn't read SVG documents index");
-  }
-  if (index_length == 0) {
-    NONFATAL_FAILURE("Zero-length documents index");
-  }
-
-  for (uint16_t i = 0; i < index_length; i++) {
-    if (!table.ReadU16(&start_glyph) ||
-        !table.ReadU16(&end_glyph) ||
-        !table.ReadU32(&doc_offset) ||
-        !table.ReadU32(&doc_length)) {
-      NONFATAL_FAILURE("Couldn't read SVG table index");
-    }
-
-    if (end_glyph < start_glyph) {
-      NONFATAL_FAILURE("Bad SVG table index range");
-    }
-
-    if (last_end_glyph && start_glyph <= last_end_glyph) {
-      NONFATAL_FAILURE("SVG table index range overlapping or not sorted");
-    }
-
-    if (doc_offset > 1024 * 1024 * 1024 ||
-        doc_length > 1024 * 1024 * 1024) {
-      NONFATAL_FAILURE("Bad SVG document length");
-    }
-
-    if (uint64_t(doc_index_offset) + doc_offset + doc_length > length) {
-      NONFATAL_FAILURE("SVG table document overflows table");
-    }
-
-    last_end_glyph = end_glyph;
-  }
-
-  svg->data = data;
-  svg->length = length;
-
-  return true;
-}
-
-bool ots_svg_serialise(OTSStream *out, OpenTypeFile *file) {
-  OpenTypeSVG *svg = file->svg;
-
-  if (!out->Write(svg->data, svg->length)) {
-    return OTS_FAILURE();
-  }
-
-  return true;
-}
-
-bool ots_svg_should_serialise(OpenTypeFile *file) {
-  return file->svg;
-}
-
-void ots_svg_free(OpenTypeFile *file) {
-  delete file->svg;
-}
-
-}
deleted file mode 100644
--- a/gfx/ots/src/svg.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2012 Mozilla Foundation. 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_SVG_H
-#define OTS_SVG_H
-
-#include <map>
-
-#include "ots.h"
-
-namespace ots {
-
-struct OpenTypeSVG {
-  const uint8_t *data;
-  size_t length;
-};
-
-}
-
-#endif
--- a/gfx/ots/src/vdmx.cc
+++ b/gfx/ots/src/vdmx.cc
@@ -1,48 +1,54 @@
 // 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.
 
 #include "vdmx.h"
 
 // VDMX - Vertical Device Metrics
-// http://www.microsoft.com/opentype/otspec/vdmx.htm
+// http://www.microsoft.com/typography/otspec/vdmx.htm
+
+#define TABLE_NAME "VDMX"
 
 #define DROP_THIS_TABLE \
-  do { delete file->vdmx; file->vdmx = 0; } while (0)
+  do { \
+    delete file->vdmx; \
+    file->vdmx = 0; \
+    OTS_FAILURE_MSG("Table discarded"); \
+  } while (0)
 
 namespace ots {
 
 bool ots_vdmx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   file->vdmx = new OpenTypeVDMX;
   OpenTypeVDMX * const vdmx = file->vdmx;
 
   if (!table.ReadU16(&vdmx->version) ||
       !table.ReadU16(&vdmx->num_recs) ||
       !table.ReadU16(&vdmx->num_ratios)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read table header");
   }
 
   if (vdmx->version > 1) {
     OTS_WARNING("bad version: %u", vdmx->version);
     DROP_THIS_TABLE;
     return true;  // continue transcoding
   }
 
   vdmx->rat_ranges.reserve(vdmx->num_ratios);
   for (unsigned i = 0; i < vdmx->num_ratios; ++i) {
     OpenTypeVDMXRatioRecord rec;
 
     if (!table.ReadU8(&rec.charset) ||
         !table.ReadU8(&rec.x_ratio) ||
         !table.ReadU8(&rec.y_start_ratio) ||
         !table.ReadU8(&rec.y_end_ratio)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read ratio header %d", i);
     }
 
     if (rec.charset > 1) {
       OTS_WARNING("bad charset: %u", rec.charset);
       DROP_THIS_TABLE;
       return true;
     }
 
@@ -68,40 +74,40 @@ bool ots_vdmx_parse(OpenTypeFile *file, 
   }
 
   vdmx->offsets.reserve(vdmx->num_ratios);
   const size_t current_offset = table.offset();
   // current_offset is less than (2 bytes * 3) + (4 bytes * USHRT_MAX) = 256k.
   for (unsigned i = 0; i < vdmx->num_ratios; ++i) {
     uint16_t offset;
     if (!table.ReadU16(&offset)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read ratio offset %d", i);
     }
     if (current_offset + offset >= length) {  // thus doesn't overflow.
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Bad ratio offset %d for ration %d", offset, i);
     }
 
     vdmx->offsets.push_back(offset);
   }
 
   vdmx->groups.reserve(vdmx->num_recs);
   for (unsigned i = 0; i < vdmx->num_recs; ++i) {
     OpenTypeVDMXGroup group;
     if (!table.ReadU16(&group.recs) ||
         !table.ReadU8(&group.startsz) ||
         !table.ReadU8(&group.endsz)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read record header %d", i);
     }
     group.entries.reserve(group.recs);
     for (unsigned j = 0; j < group.recs; ++j) {
       OpenTypeVDMXVTable vt;
       if (!table.ReadU16(&vt.y_pel_height) ||
           !table.ReadS16(&vt.y_max) ||
           !table.ReadS16(&vt.y_min)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to read reacord %d group %d", i, j);
       }
       if (vt.y_max < vt.y_min) {
         OTS_WARNING("bad y min/max");
         DROP_THIS_TABLE;
         return true;
       }
 
       // This table must appear in sorted order (sorted by yPelHeight),
@@ -126,48 +132,48 @@ bool ots_vdmx_should_serialise(OpenTypeF
 }
 
 bool ots_vdmx_serialise(OTSStream *out, OpenTypeFile *file) {
   OpenTypeVDMX * const vdmx = file->vdmx;
 
   if (!out->WriteU16(vdmx->version) ||
       !out->WriteU16(vdmx->num_recs) ||
       !out->WriteU16(vdmx->num_ratios)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write table header");
   }
 
   for (unsigned i = 0; i < vdmx->rat_ranges.size(); ++i) {
     const OpenTypeVDMXRatioRecord& rec = vdmx->rat_ranges[i];
     if (!out->Write(&rec.charset, 1) ||
         !out->Write(&rec.x_ratio, 1) ||
         !out->Write(&rec.y_start_ratio, 1) ||
         !out->Write(&rec.y_end_ratio, 1)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write ratio %d", i);
     }
   }
 
   for (unsigned i = 0; i < vdmx->offsets.size(); ++i) {
     if (!out->WriteU16(vdmx->offsets[i])) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write ratio offset %d", i);
     }
   }
 
   for (unsigned i = 0; i < vdmx->groups.size(); ++i) {
     const OpenTypeVDMXGroup& group = vdmx->groups[i];
     if (!out->WriteU16(group.recs) ||
         !out->Write(&group.startsz, 1) ||
         !out->Write(&group.endsz, 1)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write group %d", i);
     }
     for (unsigned j = 0; j < group.entries.size(); ++j) {
       const OpenTypeVDMXVTable& vt = group.entries[j];
       if (!out->WriteU16(vt.y_pel_height) ||
           !out->WriteS16(vt.y_max) ||
           !out->WriteS16(vt.y_min)) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG("Failed to write group %d entry %d", i, j);
       }
     }
   }
 
   return true;
 }
 
 void ots_vdmx_free(OpenTypeFile *file) {
--- a/gfx/ots/src/vhea.cc
+++ b/gfx/ots/src/vhea.cc
@@ -4,51 +4,53 @@
 
 #include "vhea.h"
 
 #include "gsub.h"
 #include "head.h"
 #include "maxp.h"
 
 // vhea - Vertical Header Table
-// http://www.microsoft.com/opentype/otspec/vhea.htm
+// http://www.microsoft.com/typography/otspec/vhea.htm
+
+#define TABLE_NAME "vhea"
 
 namespace ots {
 
 bool ots_vhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   OpenTypeVHEA *vhea = new OpenTypeVHEA;
   file->vhea = vhea;
 
   if (!table.ReadU32(&vhea->header.version)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read version");
   }
   if (vhea->header.version != 0x00010000 &&
       vhea->header.version != 0x00011000) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Bad vhea version %x", vhea->header.version);
   }
 
   if (!ParseMetricsHeader(file, &table, &vhea->header)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse metrics in vhea");
   }
 
   return true;
 }
 
 bool ots_vhea_should_serialise(OpenTypeFile *file) {
   // vhea should'nt serialise when vmtx doesn't exist.
   // Firefox developer pointed out that vhea/vmtx should serialise iff GSUB is
   // preserved. See http://crbug.com/77386
   return file->vhea != NULL && file->vmtx != NULL &&
       ots_gsub_should_serialise(file);
 }
 
 bool ots_vhea_serialise(OTSStream *out, OpenTypeFile *file) {
-  if (!SerialiseMetricsHeader(out, &file->vhea->header)) {
-    return OTS_FAILURE();
+  if (!SerialiseMetricsHeader(file, out, &file->vhea->header)) {
+    return OTS_FAILURE_MSG("Failed to write vhea metrics");
   }
   return true;
 }
 
 void ots_vhea_free(OpenTypeFile *file) {
   delete file->vhea;
 }
 
--- a/gfx/ots/src/vmtx.cc
+++ b/gfx/ots/src/vmtx.cc
@@ -4,47 +4,49 @@
 
 #include "vmtx.h"
 
 #include "gsub.h"
 #include "maxp.h"
 #include "vhea.h"
 
 // vmtx - Vertical Metrics Table
-// http://www.microsoft.com/opentype/otspec/vmtx.htm
+// http://www.microsoft.com/typography/otspec/vmtx.htm
+
+#define TABLE_NAME "vmtx"
 
 namespace ots {
 
 bool ots_vmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   OpenTypeVMTX *vmtx = new OpenTypeVMTX;
   file->vmtx = vmtx;
 
   if (!file->vhea || !file->maxp) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("vhea or maxp table missing as needed by vmtx");
   }
 
-  if (!ParseMetricsTable(&table, file->maxp->num_glyphs,
+  if (!ParseMetricsTable(file, &table, file->maxp->num_glyphs,
                          &file->vhea->header, &vmtx->metrics)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to parse vmtx metrics");
   }
 
   return true;
 }
 
 bool ots_vmtx_should_serialise(OpenTypeFile *file) {
   // vmtx should serialise when vhea and GSUB are preserved.
   // See the comment in ots_vhea_should_serialise().
   return file->vmtx != NULL && file->vhea != NULL &&
       ots_gsub_should_serialise(file);
 }
 
 bool ots_vmtx_serialise(OTSStream *out, OpenTypeFile *file) {
-  if (!SerialiseMetricsTable(out, &file->vmtx->metrics)) {
-    return OTS_FAILURE();
+  if (!SerialiseMetricsTable(file, out, &file->vmtx->metrics)) {
+    return OTS_FAILURE_MSG("Failed to write vmtx metrics");
   }
   return true;
 }
 
 void ots_vmtx_free(OpenTypeFile *file) {
   delete file->vmtx;
 }
 
--- a/gfx/ots/src/vorg.cc
+++ b/gfx/ots/src/vorg.cc
@@ -2,34 +2,40 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "vorg.h"
 
 #include <vector>
 
 // VORG - Vertical Origin Table
-// http://www.microsoft.com/opentype/otspec/vorg.htm
+// http://www.microsoft.com/typography/otspec/vorg.htm
+
+#define TABLE_NAME "VORG"
 
 #define DROP_THIS_TABLE \
-  do { delete file->vorg; file->vorg = 0; } while (0)
+  do { \
+    delete file->vorg; \
+    file->vorg = 0; \
+    OTS_FAILURE_MSG("Table discarded"); \
+  } while (0)
 
 namespace ots {
 
 bool ots_vorg_parse(OpenTypeFile *file, const uint8_t *data, size_t length) {
   Buffer table(data, length);
   file->vorg = new OpenTypeVORG;
   OpenTypeVORG * const vorg = file->vorg;
 
   uint16_t num_recs;
   if (!table.ReadU16(&vorg->major_version) ||
       !table.ReadU16(&vorg->minor_version) ||
       !table.ReadS16(&vorg->default_vert_origin_y) ||
       !table.ReadU16(&num_recs)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to read header");
   }
   if (vorg->major_version != 1) {
     OTS_WARNING("bad major version: %u", vorg->major_version);
     DROP_THIS_TABLE;
     return true;
   }
   if (vorg->minor_version != 0) {
     OTS_WARNING("bad minor version: %u", vorg->minor_version);
@@ -44,17 +50,17 @@ bool ots_vorg_parse(OpenTypeFile *file, 
 
   uint16_t last_glyph_index = 0;
   vorg->metrics.reserve(num_recs);
   for (unsigned i = 0; i < num_recs; ++i) {
     OpenTypeVORGMetrics rec;
 
     if (!table.ReadU16(&rec.glyph_index) ||
         !table.ReadS16(&rec.vert_origin_y)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to read record %d", i);
     }
     if ((i != 0) && (rec.glyph_index <= last_glyph_index)) {
       OTS_WARNING("the table is not sorted");
       DROP_THIS_TABLE;
       return true;
     }
     last_glyph_index = rec.glyph_index;
 
@@ -71,24 +77,24 @@ bool ots_vorg_should_serialise(OpenTypeF
 
 bool ots_vorg_serialise(OTSStream *out, OpenTypeFile *file) {
   OpenTypeVORG * const vorg = file->vorg;
 
   if (!out->WriteU16(vorg->major_version) ||
       !out->WriteU16(vorg->minor_version) ||
       !out->WriteS16(vorg->default_vert_origin_y) ||
       !out->WriteU16(vorg->metrics.size())) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG("Failed to write table header");
   }
 
   for (unsigned i = 0; i < vorg->metrics.size(); ++i) {
     const OpenTypeVORGMetrics& rec = vorg->metrics[i];
     if (!out->WriteU16(rec.glyph_index) ||
         !out->WriteS16(rec.vert_origin_y)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG("Failed to write record %d", i);
     }
   }
 
   return true;
 }
 
 void ots_vorg_free(OpenTypeFile *file) {
   delete file->vorg;
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -293,22 +293,26 @@ gfxPlatform::gfxPlatform()
     mFallbackUsesCmaps = UNINITIALIZED_VALUE;
 
     mWordCacheCharLimit = UNINITIALIZED_VALUE;
     mWordCacheMaxEntries = UNINITIALIZED_VALUE;
     mGraphiteShapingEnabled = UNINITIALIZED_VALUE;
     mOpenTypeSVGEnabled = UNINITIALIZED_VALUE;
     mBidiNumeralOption = UNINITIALIZED_VALUE;
 
-    mLayersPreferMemoryOverShmem =
-        XRE_GetProcessType() == GeckoProcessType_Default &&
-        Preferences::GetBool("layers.prefer-memory-over-shmem", true);
+    mLayersPreferMemoryOverShmem = XRE_GetProcessType() == GeckoProcessType_Default;
 
+#ifdef XP_WIN
+    // XXX - When 957560 is fixed, the pref can go away entirely
     mLayersUseDeprecated =
-        Preferences::GetBool("layers.use-deprecated-textures", true);
+        Preferences::GetBool("layers.use-deprecated-textures", true)
+        && !Preferences::GetBool("layers.prefer-opengl", false);
+#else
+    mLayersUseDeprecated = false;
+#endif
 
     Preferences::AddBoolVarCache(&mDrawLayerBorders,
                                  "layers.draw-borders",
                                  false);
     Preferences::AddBoolVarCache(&mDrawTileBorders,
                                  "layers.draw-tile-borders",
                                  false);
     Preferences::AddBoolVarCache(&mDrawBigImageBorders,
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -316,17 +316,31 @@ public:
 
 private:
     void*        mPtr;
     size_t       mLength;
     const size_t mLimit;
     off_t        mOff;
 };
 
-#ifdef MOZ_OTS_REPORT_ERRORS
+static ots::TableAction
+OTSTableAction(uint32_t aTag, void *aUserData)
+{
+    // preserve Graphite and SVG tables
+    if (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', ' ')) {
+        return ots::TABLE_ACTION_PASSTHRU;
+    }
+    return ots::TABLE_ACTION_DEFAULT;
+}
+
 struct OTSCallbackUserData {
     gfxUserFontSet     *mFontSet;
     gfxMixedFontFamily *mFamily;
     gfxProxyFontEntry  *mProxy;
 };
 
 /* static */ bool
 gfxUserFontSet::OTSMessage(void *aUserData, const char *format, ...)
@@ -341,43 +355,38 @@ gfxUserFontSet::OTSMessage(void *aUserDa
 
     va_end(va);
 
     OTSCallbackUserData *d = static_cast<OTSCallbackUserData*>(aUserData);
     d->mFontSet->LogMessage(d->mFamily, d->mProxy, buf);
 
     return false;
 }
-#endif
 
 // Call the OTS library to sanitize an sfnt before attempting to use it.
 // Returns a newly-allocated block, or nullptr in case of fatal errors.
 const uint8_t*
 gfxUserFontSet::SanitizeOpenTypeData(gfxMixedFontFamily *aFamily,
                                      gfxProxyFontEntry *aProxy,
                                      const uint8_t* aData, uint32_t aLength,
                                      uint32_t& aSaneLength, bool aIsCompressed)
 {
     // limit output/expansion to 256MB
     ExpandingMemoryStream output(aIsCompressed ? aLength * 2 : aLength,
                                  1024 * 1024 * 256);
 
-#ifdef MOZ_OTS_REPORT_ERRORS
     OTSCallbackUserData userData;
     userData.mFontSet = this;
     userData.mFamily = aFamily;
     userData.mProxy = aProxy;
-#define ERROR_REPORTING_ARGS &gfxUserFontSet::OTSMessage, &userData,
-#else
-#define ERROR_REPORTING_ARGS
-#endif
 
-    if (ots::Process(&output, aData, aLength,
-                     ERROR_REPORTING_ARGS
-                     true)) {
+    ots::SetTableActionCallback(&OTSTableAction, nullptr);
+    ots::SetMessageCallback(&gfxUserFontSet::OTSMessage, &userData);
+
+    if (ots::Process(&output, aData, aLength)) {
         aSaneLength = output.Tell();
         return static_cast<uint8_t*>(output.forget());
     } else {
         aSaneLength = 0;
         return nullptr;
     }
 }
 
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -409,19 +409,17 @@ protected:
 
     const uint8_t* SanitizeOpenTypeData(gfxMixedFontFamily *aFamily,
                                         gfxProxyFontEntry *aProxy,
                                         const uint8_t* aData,
                                         uint32_t aLength,
                                         uint32_t& aSaneLength,
                                         bool aIsCompressed);
 
-#ifdef MOZ_OTS_REPORT_ERRORS
     static bool OTSMessage(void *aUserData, const char *format, ...);
-#endif
 
     // font families defined by @font-face rules
     nsRefPtrHashtable<nsStringHashKey, gfxMixedFontFamily> mFontFamilies;
 
     uint64_t        mGeneration;
 
     static PRLogModuleInfo* GetUserFontsLog();
 
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -298,13 +298,12 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
         if CONFIG[var]:
             DEFINES[var] = True
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     # This is set for "normal Android", that is, when Gecko is running on
     # top of the android java runtime.
     DEFINES['MOZ_USING_ANDROID_JAVA_WIDGETS'] = True
 
-DEFINES['MOZ_OTS_REPORT_ERRORS'] = True
 DEFINES['GRAPHITE2_STATIC'] = True
 
 if CONFIG['OS_TARGET'] == 'WINNT':
     DEFINES['OTS_DLL'] = True
--- a/js/src/jit-test/tests/auto-regress/bug746377.js
+++ b/js/src/jit-test/tests/auto-regress/bug746377.js
@@ -1,14 +1,10 @@
 // |jit-test| error:InternalError
 
-// This test is temporarily disabled in GGC builds (bug 950932).
-if (getBuildConfiguration()['generational-gc'])
-    (function f() { f(); })();
-
 // Binary: cache/js-dbg-64-67bf9a4a1f77-linux
 // Flags: --ion-eager
 //
 
 var actual = '';
 test();
 function test()
 {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug964229-2.js
@@ -0,0 +1,58 @@
+function test1(re, test) {
+    return re.test(test);
+}
+
+assertEq(true, test1(/undefined/, undefined));
+assertEq(true, test1(/undefined/, undefined));
+
+function test2(re, test) {
+    return re.test(test);
+}
+
+assertEq(true, test2(/null/, null));
+assertEq(true, test2(/null/, null));
+
+function test3(re, test) {
+    return re.test(test);
+}
+
+assertEq(true, test3(/0/, 0));
+assertEq(true, test3(/0/, 0));
+
+function test4(re, test) {
+    return re.test(test);
+}
+
+assertEq(true, test4(/12.12/, 12.12));
+assertEq(true, test4(/12.12/, 12.12));
+
+function test5(re, test) {
+    return re.test(test);
+}
+
+assertEq(true, test5(/true/, true));
+assertEq(true, test5(/false/, false));
+assertEq(true, test5(/true/, true));
+assertEq(true, test5(/false/, false));
+
+function test6(re, test) {
+    return re.test(test);
+}
+
+assertEq(true, test6(/object/, {}));
+assertEq(true, test6(/object/, {}));
+
+assertEq(true, test1(/test/, "test"));
+assertEq(true, test1(/test/, "test"));
+assertEq(true, test1(/undefined/, undefined));
+assertEq(true, test1(/undefined/, undefined));
+assertEq(true, test1(/null/, null));
+assertEq(true, test1(/null/, null));
+assertEq(true, test1(/0.1/, 0.1));
+assertEq(true, test1(/0.1/, 0.1));
+assertEq(true, test1(/20000/, 20000));
+assertEq(true, test1(/20000/, 20000));
+assertEq(true, test1(/object/, {}));
+assertEq(true, test1(/object/, {}));
+
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug964229.js
@@ -0,0 +1,26 @@
+a = 'a';
+b = 0
+var i=0;
+exhaustiveSliceTest("exhaustive slice test 1", a);
+var i=1;
+exhaustiveSliceTest("exhaustive slice test 2", b);
+exhaustiveSliceTest("exhaustive slice test 3", 0);
+var i=0;
+var executed = false;
+try {
+    exhaustiveSliceTest("exhaustive slice test 4", 0);
+} catch(e) {
+    executed = true;
+}
+assertEq(executed, true);
+
+function exhaustiveSliceTest(testname, a) {
+    print(testname)
+    for (var y = 0; y < 2; y++)
+    {
+        print(a.length)
+        if (a.length == 2 || i == 1)
+            return 0;
+        var b  = a.slice(0,0);
+    }
+}
--- a/js/src/jit-test/tests/v8-v5/check-raytrace.js
+++ b/js/src/jit-test/tests/v8-v5/check-raytrace.js
@@ -1,13 +1,8 @@
-
-// This test is temporarily disabled in GGC builds (bug 950931).
-if (getBuildConfiguration()['generational-gc'])
-    quit();
-
 // The ray tracer code in this file is written by Adam Burmister. It
 // is available in its original form from:
 //
 //   http://labs.flog.nz.co/raytracer/
 //
 // It has been modified slightly by Google to work as a standalone
 // benchmark, but the all the computational code remains
 // untouched. This file also contains a copy of parts of the Prototype
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -692,16 +692,46 @@ CodeGenerator::visitTypeObjectDispatch(L
     }
 
     // Unknown function: jump to fallback block.
     LBlock *fallback = mir->getFallback()->lir();
     masm.jump(fallback->label());
     return true;
 }
 
+bool
+CodeGenerator::visitBooleanToString(LBooleanToString *lir)
+{
+    Register input = ToRegister(lir->input());
+    Register output = ToRegister(lir->output());
+    const JSAtomState &names = GetIonContext()->runtime->names();
+    Label true_, done;
+
+    masm.branchTest32(Assembler::NonZero, input, input, &true_);
+    masm.movePtr(ImmGCPtr(names.false_), output);
+    masm.jump(&done);
+
+    masm.bind(&true_);
+    masm.movePtr(ImmGCPtr(names.true_), output);
+
+    masm.bind(&done);
+
+    return true;
+}
+
+void
+CodeGenerator::emitIntToString(Register input, Register output, Label *ool)
+{
+    masm.branch32(Assembler::AboveOrEqual, input, Imm32(StaticStrings::INT_STATIC_LIMIT), ool);
+
+    // Fast path for small integers.
+    masm.movePtr(ImmPtr(&GetIonContext()->runtime->staticStrings().intStaticTable), output);
+    masm.loadPtr(BaseIndex(output, input, ScalePointer), output);
+}
+
 typedef JSFlatString *(*IntToStringFn)(ThreadSafeContext *, int);
 typedef JSFlatString *(*IntToStringParFn)(ForkJoinSlice *, int);
 static const VMFunctionsModal IntToStringInfo = VMFunctionsModal(
     FunctionInfo<IntToStringFn>(Int32ToString<CanGC>),
     FunctionInfo<IntToStringParFn>(IntToStringPar));
 
 bool
 CodeGenerator::visitIntToString(LIntToString *lir)
@@ -709,21 +739,17 @@ CodeGenerator::visitIntToString(LIntToSt
     Register input = ToRegister(lir->input());
     Register output = ToRegister(lir->output());
 
     OutOfLineCode *ool = oolCallVM(IntToStringInfo, lir, (ArgList(), input),
                                    StoreRegisterTo(output));
     if (!ool)
         return false;
 
-    masm.branch32(Assembler::AboveOrEqual, input, Imm32(StaticStrings::INT_STATIC_LIMIT),
-                  ool->entry());
-
-    masm.movePtr(ImmPtr(&GetIonContext()->runtime->staticStrings().intStaticTable), output);
-    masm.loadPtr(BaseIndex(output, input, ScalePointer), output);
+    emitIntToString(input, output, ool->entry());
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 typedef JSString *(*DoubleToStringFn)(ThreadSafeContext *, double);
 typedef JSString *(*DoubleToStringParFn)(ForkJoinSlice *, double);
 static const VMFunctionsModal DoubleToStringInfo = VMFunctionsModal(
@@ -737,23 +763,109 @@ CodeGenerator::visitDoubleToString(LDoub
     Register temp = ToRegister(lir->tempInt());
     Register output = ToRegister(lir->output());
 
     OutOfLineCode *ool = oolCallVM(DoubleToStringInfo, lir, (ArgList(), input),
                                    StoreRegisterTo(output));
     if (!ool)
         return false;
 
+    // Try double to integer conversion and run integer to string code.
     masm.convertDoubleToInt32(input, temp, ool->entry(), true);
-    masm.branch32(Assembler::AboveOrEqual, temp, Imm32(StaticStrings::INT_STATIC_LIMIT),
-                  ool->entry());
-
-    masm.movePtr(ImmPtr(&GetIonContext()->runtime->staticStrings().intStaticTable), output);
-    masm.loadPtr(BaseIndex(output, temp, ScalePointer), output);
-
+    emitIntToString(temp, output, ool->entry());
+
+    masm.bind(ool->rejoin());
+    return true;
+}
+
+typedef JSString *(*PrimitiveToStringFn)(JSContext *, HandleValue);
+typedef JSString *(*PrimitiveToStringParFn)(ForkJoinSlice *, HandleValue);
+static const VMFunctionsModal PrimitiveToStringInfo = VMFunctionsModal(
+    FunctionInfo<PrimitiveToStringFn>(ToStringSlow),
+    FunctionInfo<PrimitiveToStringParFn>(PrimitiveToStringPar));
+
+bool
+CodeGenerator::visitPrimitiveToString(LPrimitiveToString *lir)
+{
+    ValueOperand input = ToValue(lir, LPrimitiveToString::Input);
+    Register output = ToRegister(lir->output());
+
+    OutOfLineCode *ool = oolCallVM(PrimitiveToStringInfo, lir, (ArgList(), input),
+                                   StoreRegisterTo(output));
+    if (!ool)
+        return false;
+
+    Label done;
+    Register tag = masm.splitTagForTest(input);
+    const JSAtomState &names = GetIonContext()->runtime->names();
+
+    // String
+    if (lir->mir()->input()->mightBeType(MIRType_String)) {
+        Label notString;
+        masm.branchTestString(Assembler::NotEqual, tag, &notString);
+        masm.unboxString(input, output);
+        masm.jump(&done);
+        masm.bind(&notString);
+    }
+
+    // Integer
+    if (lir->mir()->input()->mightBeType(MIRType_Int32)) {
+        Label notInteger;
+        masm.branchTestInt32(Assembler::NotEqual, tag, &notInteger);
+        Register unboxed = ToTempUnboxRegister(lir->tempToUnbox());
+        unboxed = masm.extractInt32(input, unboxed);
+        emitIntToString(unboxed, output, ool->entry());
+        masm.jump(&done);
+        masm.bind(&notInteger);
+    }
+
+    // Double
+    if (lir->mir()->input()->mightBeType(MIRType_Double)) {
+        // Note: no fastpath. Need two extra registers and can only convert doubles
+        // that fit integers and are smaller than StaticStrings::INT_STATIC_LIMIT.
+        masm.branchTestDouble(Assembler::Equal, tag, ool->entry());
+    }
+
+    // Undefined
+    if (lir->mir()->input()->mightBeType(MIRType_Undefined)) {
+        Label notUndefined;
+        masm.branchTestUndefined(Assembler::NotEqual, tag, &notUndefined);
+        masm.movePtr(ImmGCPtr(names.undefined), output);
+        masm.jump(&done);
+        masm.bind(&notUndefined);
+    }
+
+    // Null
+    if (lir->mir()->input()->mightBeType(MIRType_Null)) {
+        Label notNull;
+        masm.branchTestNull(Assembler::NotEqual, tag, &notNull);
+        masm.movePtr(ImmGCPtr(names.null), output);
+        masm.jump(&done);
+        masm.bind(&notNull);
+    }
+
+    // Boolean
+    if (lir->mir()->input()->mightBeType(MIRType_Boolean)) {
+        Label notBoolean, true_;
+        masm.branchTestBoolean(Assembler::NotEqual, tag, &notBoolean);
+        masm.branchTestBooleanTruthy(true, input, &true_);
+        masm.movePtr(ImmGCPtr(names.false_), output);
+        masm.jump(&done);
+        masm.bind(&true_);
+        masm.movePtr(ImmGCPtr(names.true_), output);
+        masm.jump(&done);
+        masm.bind(&notBoolean);
+    }
+
+#ifdef DEBUG
+    // Objects are not supported or we see a type that wasn't accounted for.
+    masm.assumeUnreachable("Unexpected type for MPrimitiveToString.");
+#endif
+
+    masm.bind(&done);
     masm.bind(ool->rejoin());
     return true;
 }
 
 typedef JSObject *(*CloneRegExpObjectFn)(JSContext *, JSObject *);
 static const VMFunction CloneRegExpObjectInfo =
     FunctionInfo<CloneRegExpObjectFn>(CloneRegExpObject);
 
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -82,18 +82,21 @@ class CodeGenerator : public CodeGenerat
     bool visitDoubleToFloat32(LDoubleToFloat32 *lir);
     bool visitInt32ToFloat32(LInt32ToFloat32 *lir);
     bool visitInt32ToDouble(LInt32ToDouble *lir);
     void emitOOLTestObject(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch);
     bool visitTestOAndBranch(LTestOAndBranch *lir);
     bool visitTestVAndBranch(LTestVAndBranch *lir);
     bool visitFunctionDispatch(LFunctionDispatch *lir);
     bool visitTypeObjectDispatch(LTypeObjectDispatch *lir);
+    bool visitBooleanToString(LBooleanToString *lir);
+    void emitIntToString(Register input, Register output, Label *ool);
     bool visitIntToString(LIntToString *lir);
     bool visitDoubleToString(LDoubleToString *lir);
+    bool visitPrimitiveToString(LPrimitiveToString *lir);
     bool visitInteger(LInteger *lir);
     bool visitRegExp(LRegExp *lir);
     bool visitRegExpExec(LRegExpExec *lir);
     bool visitRegExpTest(LRegExpTest *lir);
     bool visitRegExpReplace(LRegExpReplace *lir);
     bool visitStringReplace(LStringReplace *lir);
     bool visitLambda(LLambda *lir);
     bool visitLambdaForSingleton(LLambdaForSingleton *lir);
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -3092,16 +3092,31 @@ class LTruncateFToInt32 : public LInstru
         setTemp(0, temp);
     }
 
     const LDefinition *tempFloat() {
         return getTemp(0);
     }
 };
 
+// Convert a boolean value to a string.
+class LBooleanToString : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(BooleanToString)
+
+    LBooleanToString(const LAllocation &input) {
+        setOperand(0, input);
+    }
+
+    const MToString *mir() {
+        return mir_->toToString();
+    }
+};
+
 // Convert an integer hosted on one definition to a string with a function call.
 class LIntToString : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(IntToString)
 
     LIntToString(const LAllocation &input) {
         setOperand(0, input);
@@ -3126,16 +3141,38 @@ class LDoubleToString : public LInstruct
     const LDefinition *tempInt() {
         return getTemp(0);
     }
     const MToString *mir() {
         return mir_->toToString();
     }
 };
 
+// Convert a primitive to a string with a function call.
+class LPrimitiveToString : public LInstructionHelper<1, BOX_PIECES, 1>
+{
+  public:
+    LIR_HEADER(PrimitiveToString)
+
+    LPrimitiveToString(const LDefinition &tempToUnbox)
+    {
+        setTemp(0, tempToUnbox);
+    }
+
+    static const size_t Input = 0;
+
+    const MToString *mir() {
+        return mir_->toToString();
+    }
+
+    const LDefinition *tempToUnbox() {
+        return getTemp(0);
+    }
+};
+
 // No-op instruction that is used to hold the entry snapshot. This simplifies
 // register allocation as it doesn't need to sniff the snapshot out of the