bug 670901 pt 1 - add support for an error-message callback to OTS. r=jdaggett
☠☠ backed out by 1c508a4f2dd0 ☠ ☠
authorJonathan Kew <jkew@mozilla.com>
Tue, 29 May 2012 23:42:56 +0100
changeset 95214 df4cf37667ac01fcb3ac34e3761e697153b339c6
parent 95213 fcbaab8ad541fa2f5fca5b4e5483d49b945dac21
child 95215 cd033f175e87bf63aa0237e4a21b8b6428b8c407
push idunknown
push userunknown
push dateunknown
reviewersjdaggett
bugs670901
milestone15.0a1
bug 670901 pt 1 - add support for an error-message callback to OTS. r=jdaggett
gfx/ots/include/opentype-sanitiser.h
gfx/ots/src/gdef.cc
gfx/ots/src/gpos.cc
gfx/ots/src/gsub.cc
gfx/ots/src/ots.cc
gfx/ots/src/ots.h
--- a/gfx/ots/include/opentype-sanitiser.h
+++ b/gfx/ots/include/opentype-sanitiser.h
@@ -194,26 +194,36 @@ 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,
+#endif
                      bool preserve_graphite_tables = false);
 
 // Force to disable debug output even when the library is compiled with
 // -DOTS_DEBUG.
 void DisableDebugOutput();
 
 }  // namespace ots
 
--- a/gfx/ots/src/gdef.cc
+++ b/gfx/ots/src/gdef.cc
@@ -10,16 +10,18 @@
 #include "gpos.h"
 #include "gsub.h"
 #include "layout.h"
 #include "maxp.h"
 
 // GDEF - The Glyph Definition Table
 // http://www.microsoft.com/typography/otspec/gdef.htm
 
+#define TABLE_NAME "GDEF"
+
 namespace {
 
 // The maximum class value in class definition tables.
 const uint16_t kMaxClassDefValue = 0xFFFF;
 // The maximum class value in the glyph class definision table.
 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
@@ -237,17 +239,21 @@ bool ParseMarkGlyphSetsDefTable(ots::Ope
   }
   file->gdef->num_mark_glyph_sets = mark_set_count;
   return true;
 }
 
 }  // namespace
 
 #define DROP_THIS_TABLE \
-  do { file->gdef->data = 0; file->gdef->length = 0; } while (0)
+  do { \
+    file->gdef->data = 0; \
+    file->gdef->length = 0; \
+    OTS_FAILURE_MSG("OpenType layout data 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();
--- a/gfx/ots/src/gpos.cc
+++ b/gfx/ots/src/gpos.cc
@@ -10,16 +10,18 @@
 #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"
+
 namespace {
 
 enum GPOS_TYPE {
   GPOS_TYPE_SINGLE_ADJUSTMENT = 1,
   GPOS_TYPE_PAIR_ADJUSTMENT = 2,
   GPOS_TYPE_CURSIVE_ATTACHMENT = 3,
   GPOS_TYPE_MARK_TO_BASE_ATTACHMENT = 4,
   GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT = 5,
@@ -664,17 +666,21 @@ bool ParseExtensionPositioning(const ots
                                const uint8_t *data, const size_t length) {
   return ots::ParseExtensionSubtable(file, data, length,
                                      &kGposLookupSubtableParser);
 }
 
 }  // namespace
 
 #define DROP_THIS_TABLE \
-  do { file->gpos->data = 0; file->gpos->length = 0; } while (0)
+  do { \
+    file->gpos->data = 0; \
+    file->gpos->length = 0; \
+    OTS_FAILURE_MSG("OpenType layout data 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
 // samanata.ttf
--- a/gfx/ots/src/gsub.cc
+++ b/gfx/ots/src/gsub.cc
@@ -10,16 +10,18 @@
 #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;
 
 enum GSUB_TYPE {
   GSUB_TYPE_SINGLE = 1,
   GSUB_TYPE_MULTIPLE = 2,
@@ -524,17 +526,21 @@ bool ParseReverseChainingContextSingleSu
   }
 
   return true;
 }
 
 }  // namespace
 
 #define DROP_THIS_TABLE \
-  do { file->gsub->data = 0; file->gsub->length = 0; } while (0)
+  do { \
+    file->gsub->data = 0; \
+    file->gsub->length = 0; \
+    OTS_FAILURE_MSG("OpenType layout data 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)
 // kaiu.ttf
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -16,16 +16,30 @@
 
 // The OpenType Font File
 // http://www.microsoft.com/typography/otspec/cmap.htm
 
 namespace {
 
 bool g_debug_output = true;
 
+#ifdef MOZ_OTS_REPORT_ERRORS
+
+// 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;
 };
 
@@ -177,37 +191,37 @@ bool ProcessGeneric(ots::OpenTypeFile *h
                     ots::Buffer& file);
 
 bool ProcessTTF(ots::OpenTypeFile *header,
                 ots::OTSStream *output, const uint8_t *data, size_t length) {
   ots::Buffer file(data, length);
 
   // we disallow all files > 1GB in size for sanity.
   if (length > 1024 * 1024 * 1024) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("file exceeds 1GB");
   }
 
   if (!file.ReadTag(&header->version)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading version tag");
   }
   if (!IsValidVersionTag(header->version)) {
-      return OTS_FAILURE();
+      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();
+    return OTS_FAILURE_MSG_HDR("error reading table directory search header");
   }
 
   // search_range is (Maximum power of 2 <= numTables) x 16. Thus, to avoid
   // overflow num_tables is, at most, 2^16 / 16 = 2^12
   if (header->num_tables >= 4096 || header->num_tables < 1) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("excessive (or zero) number of tables");
   }
 
   unsigned max_pow2 = 0;
   while (1u << (max_pow2 + 1) <= header->num_tables) {
     max_pow2++;
   }
   const uint16_t expected_search_range = (1u << max_pow2) << 4;
 
@@ -215,17 +229,17 @@ bool ProcessTTF(ots::OpenTypeFile *heade
   // http://www.princexml.com/fonts/ have unexpected search_range value.
   if (header->search_range != expected_search_range) {
     OTS_WARNING("bad search range");
     header->search_range = expected_search_range;  // Fix the value.
   }
 
   // entry_selector is Log2(maximum power of 2 <= numTables)
   if (header->entry_selector != max_pow2) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("incorrect entrySelector for table directory");
   }
 
   // range_shift is NumTables x 16-searchRange. We know that 16*num_tables
   // doesn't over flow because we range checked it above. Also, we know that
   // it's > header->search_range by construction of search_range.
   const uint32_t expected_range_shift
       = 16 * header->num_tables - header->search_range;
   if (header->range_shift != expected_range_shift) {
@@ -237,105 +251,105 @@ bool ProcessTTF(ots::OpenTypeFile *heade
   std::vector<OpenTypeTable> tables;
 
   for (unsigned i = 0; i < header->num_tables; ++i) {
     OpenTypeTable table;
     if (!file.ReadTag(&table.tag) ||
         !file.ReadU32(&table.chksum) ||
         !file.ReadU32(&table.offset) ||
         !file.ReadU32(&table.length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("error reading table directory");
     }
 
     table.uncompressed_length = table.length;
     tables.push_back(table);
   }
 
   return ProcessGeneric(header, output, data, length, tables, file);
 }
 
 bool ProcessWOFF(ots::OpenTypeFile *header,
                  ots::OTSStream *output, const uint8_t *data, size_t length) {
   ots::Buffer file(data, length);
 
   // we disallow all files > 1GB in size for sanity.
   if (length > 1024 * 1024 * 1024) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("file exceeds 1GB");
   }
 
   uint32_t woff_tag;
   if (!file.ReadTag(&woff_tag)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading WOFF marker");
   }
 
   if (woff_tag != Tag("wOFF")) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("invalid WOFF marker");
   }
 
   if (!file.ReadTag(&header->version)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading version tag");
   }
   if (!IsValidVersionTag(header->version)) {
-      return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("invalid version tag");
   }
 
   header->search_range = 0;
   header->entry_selector = 0;
   header->range_shift = 0;
 
   uint32_t reported_length;
   if (!file.ReadU32(&reported_length) || length != reported_length) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("incorrect file size in WOFF header");
   }
 
   if (!file.ReadU16(&header->num_tables) || !header->num_tables) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading number of tables");
   }
 
   uint16_t reserved_value;
   if (!file.ReadU16(&reserved_value) || reserved_value) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error in reserved field of WOFF header");
   }
 
   uint32_t reported_total_sfnt_size;
   if (!file.ReadU32(&reported_total_sfnt_size)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading total sfnt size");
   }
 
   // We don't care about these fields of the header:
   //   uint16_t major_version, minor_version
   if (!file.Skip(2 * 2)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error skipping WOFF header fields");
   }
 
   // Checks metadata block size.
   uint32_t meta_offset;
   uint32_t meta_length;
   uint32_t meta_length_orig;
   if (!file.ReadU32(&meta_offset) ||
       !file.ReadU32(&meta_length) ||
       !file.ReadU32(&meta_length_orig)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading WOFF header fields");
   }
   if (meta_offset) {
     if (meta_offset >= length || length - meta_offset < meta_length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid metadata block location/size");
     }
   }
 
   // Checks private data block size.
   uint32_t priv_offset;
   uint32_t priv_length;
   if (!file.ReadU32(&priv_offset) ||
       !file.ReadU32(&priv_length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error reading WOFF header fields");
   }
   if (priv_offset) {
     if (priv_offset >= length || length - priv_offset < priv_length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid private block location/size");
     }
   }
 
   // Next up is the list of tables.
   std::vector<OpenTypeTable> tables;
 
   uint32_t first_index = 0;
   uint32_t last_index = 0;
@@ -343,73 +357,73 @@ bool ProcessWOFF(ots::OpenTypeFile *head
   uint64_t total_sfnt_size = 12 + 16 * header->num_tables;
   for (unsigned i = 0; i < header->num_tables; ++i) {
     OpenTypeTable table;
     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();
+      return OTS_FAILURE_MSG_HDR("error reading table directory");
     }
 
     total_sfnt_size += Round4(table.uncompressed_length);
     if (total_sfnt_size > std::numeric_limits<uint32_t>::max()) {
-      return OTS_FAILURE();
+      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();
+    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())) {
-    return OTS_FAILURE();
+    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();
+    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(
       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();
+    return OTS_FAILURE_MSG_HDR("invalid table location/size");
   }
   if (meta_offset) {
     if (block_end != meta_offset) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid metadata block location");
     }
     block_end = 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();
+      return OTS_FAILURE_MSG_HDR("invalid metadata block size");
     }
   }
   if (priv_offset) {
     if (block_end != priv_offset) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("invalid private block location");
     }
     block_end = 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();
+      return OTS_FAILURE_MSG_HDR("invalid private block size");
     }
   }
   if (block_end != Round4(length)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("file length mismatch (trailing junk?)");
   }
 
   return ProcessGeneric(header, output, data, length, tables, file);
 }
 
 bool ProcessGeneric(ots::OpenTypeFile *header, ots::OTSStream *output,
                     const uint8_t *data, size_t length,
                     const std::vector<OpenTypeTable>& tables,
@@ -420,74 +434,74 @@ bool ProcessGeneric(ots::OpenTypeFile *h
 
   for (unsigned i = 0; i < header->num_tables; ++i) {
     // the tables must be sorted by tag (when taken as big-endian numbers).
     // This also remove the possibility of duplicate tables.
     if (i) {
       const uint32_t this_tag = ntohl(tables[i].tag);
       const uint32_t prev_tag = ntohl(tables[i - 1].tag);
       if (this_tag <= prev_tag) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_HDR("table directory not correctly ordered");
       }
     }
 
     // all tag names must be built from printable ASCII characters
     if (!CheckTag(tables[i].tag)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("invalid table tag", tables[i].tag);
     }
 
     // tables must be 4-byte aligned
     if (tables[i].offset & 3) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("misaligned table", tables[i].tag);
     }
 
     // and must be within the file
     if (tables[i].offset < data_offset || tables[i].offset >= length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("invalid table offset", tables[i].tag);
     }
     // disallow all tables with a zero length
     if (tables[i].length < 1) {
       // Note: malayalam.ttf has zero length CVT table...
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("zero-length table", tables[i].tag);
     }
     // disallow all tables with a length > 1GB
     if (tables[i].length > 1024 * 1024 * 1024) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("table length exceeds 1GB", tables[i].tag);
     }
     // disallow tables where the uncompressed size is < the compressed size.
     if (tables[i].uncompressed_length < tables[i].length) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("invalid compressed table", tables[i].tag);
     }
     if (tables[i].uncompressed_length > tables[i].length) {
       // We'll probably be decompressing this table.
 
       // disallow all tables which uncompress to > 30 MB
       if (tables[i].uncompressed_length > 30 * 1024 * 1024) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_TAG("uncompressed length exceeds 30MB", tables[i].tag);
       }
       if (uncompressed_sum + tables[i].uncompressed_length < uncompressed_sum) {
-        return OTS_FAILURE();
+        return OTS_FAILURE_MSG_TAG("overflow of uncompressed sum", tables[i].tag);
       }
 
       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
     const uint32_t end_byte = tables[i].offset + tables[i].length;
     // Some fonts which are automatically generated by a font generator
     // called TTX seems not to add 0-padding to the final table. It might be
     // ok to accept these fonts so we round up the length of the font file.
     if (!end_byte || end_byte > length) {
-      return OTS_FAILURE();
+      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) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("uncompressed sum exceeds 30MB");
   }
 
   std::map<uint32_t, OpenTypeTable> table_map;
   for (unsigned i = 0; i < header->num_tables; ++i) {
     table_map[tables[i].tag] = tables[i];
   }
 
   // check that the tables are not overlapping.
@@ -499,73 +513,75 @@ bool ProcessGeneric(ots::OpenTypeFile *h
         std::make_pair(tables[i].offset + tables[i].length,
                        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_HDR("overlapping tables");
     }
   }
 
   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));
 
     if (it == table_map.end()) {
       if (table_parsers[i].required) {
-        return OTS_FAILURE();
+        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();
+        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 (!table_parsers[i].parse(header, table_data, table_length)) {
-      return OTS_FAILURE();
+      // 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
     if (header->version != Tag("OTTO")) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("wrong font version for PostScript glyph data");
     }
     if (header->glyf || header->loca) {
       // mixing outline formats is not recommended
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("font contains both PS and TT glyphs");
     }
   } else {
     if (!header->glyf || !header->loca) {
       // No TrueType glyph found.
       // Note: bitmap-only fonts are not supported.
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("neither PS nor TT glyphs present");
     }
   }
 
   unsigned num_output_tables = 0;
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) {
       break;
     }
@@ -576,29 +592,31 @@ bool ProcessGeneric(ots::OpenTypeFile *h
   }
 
   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
   output->ResetChecksum();
   if (!output->WriteTag(header->version) ||
       !output->WriteU16(num_output_tables) ||
       !output->WriteU16(output_search_range) ||
       !output->WriteU16(max_pow2) ||
       !output->WriteU16((num_output_tables << 4) - output_search_range)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
   const uint32_t offset_table_chksum = output->chksum();
 
   const size_t table_record_offset = output->Tell();
   if (!output->Pad(16 * num_output_tables)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   std::vector<OutputTable> out_tables;
 
   size_t head_table_offset = 0;
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) {
       break;
@@ -613,92 +631,101 @@ bool ProcessGeneric(ots::OpenTypeFile *h
     out.tag = tag;
     out.offset = output->Tell();
 
     output->ResetChecksum();
     if (tag == Tag("head")) {
       head_table_offset = out.offset;
     }
     if (!table_parsers[i].serialise(output, header)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_TAG("failed to serialize table", table_parsers[i].tag);
     }
 
     const size_t end_offset = output->Tell();
     if (end_offset <= out.offset) {
       // paranoid check. |end_offset| is supposed to be greater than the offset,
       // as long as the Tell() interface is implemented correctly.
-      return OTS_FAILURE();
+      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();
+      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();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   output->ResetChecksum();
   uint32_t tables_chksum = 0;
   for (unsigned i = 0; i < out_tables.size(); ++i) {
     if (!output->WriteTag(out_tables[i].tag) ||
         !output->WriteU32(out_tables[i].chksum) ||
         !output->WriteU32(out_tables[i].offset) ||
         !output->WriteU32(out_tables[i].length)) {
-      return OTS_FAILURE();
+      return OTS_FAILURE_MSG_HDR("error writing output");
     }
     tables_chksum += out_tables[i].chksum;
   }
   const uint32_t table_record_chksum = output->chksum();
 
   // http://www.microsoft.com/typography/otspec/otff.htm
   const uint32_t file_chksum
       = offset_table_chksum + tables_chksum + table_record_chksum;
   const uint32_t chksum_magic = static_cast<uint32_t>(0xb1b0afba) - file_chksum;
 
   // seek into the 'head' table and write in the checksum magic value
   if (!head_table_offset) {
-    return OTS_FAILURE();  // not reached.
+    return OTS_FAILURE_MSG_HDR("internal error!");
   }
   if (!output->Seek(head_table_offset + 8)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
   if (!output->WriteU32(chksum_magic)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   if (!output->Seek(end_of_file)) {
-    return OTS_FAILURE();
+    return OTS_FAILURE_MSG_HDR("error writing output");
   }
 
   return true;
 }
 
 }  // namespace
 
 namespace ots {
 
 void DisableDebugOutput() {
   g_debug_output = false;
 }
 
-bool Process(OTSStream *output, const uint8_t *data, size_t length,
-             bool preserveGraphite) {
+bool OTS_API Process(OTSStream *output, const uint8_t *data, size_t length,
+#ifdef MOZ_OTS_REPORT_ERRORS
+                     MessageFunc message_func, void *user_data,
+#endif
+                     bool preserveGraphite) {
   OpenTypeFile header;
+
+#ifdef MOZ_OTS_REPORT_ERRORS
+  header.message_func = message_func;
+  header.user_data = user_data;
+#endif
+
   if (length < 4) {
-    return OTS_FAILURE();
+    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);
   } else {
--- a/gfx/ots/src/ots.h
+++ b/gfx/ots/src/ots.h
@@ -33,16 +33,50 @@ 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.
+
+// Generate a simple message
+#define OTS_FAILURE_MSG_(otf_,msg_) \
+  ((otf_)->message_func && \
+    (*(otf_)->message_func)((otf_)->user_data, "%s", msg_) && \
+    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 '%s': %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_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;
 #endif
@@ -202,16 +236,21 @@ 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
 };