Browse Source

backports and restore fix

Frank-Rainer Grahl 5 months ago
parent
commit
ecc79e7582

+ 107 - 0
comm-release/patches/1353704-69a1.patch

@@ -0,0 +1,107 @@
+# HG changeset patch
+# User Jorg K <jorgk@jorgk.com>
+# Date 1565856020 -7200
+# Node ID e47ba37adeb2effda5b1f756e3ea23f5193b5177
+# Parent  9a0fa902d9128277234d2def82ffc750a1ddadb9
+Bug 1353704 - Use a ref-counted reference to the database for the DB reporter. r=BenC
+
+diff --git a/mailnews/db/msgdb/src/nsMsgDatabase.cpp b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
+--- a/mailnews/db/msgdb/src/nsMsgDatabase.cpp
++++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
+@@ -33,16 +33,17 @@
+ #include "nsServiceManagerUtils.h"
+ #include "nsMemory.h"
+ #include "nsICollation.h"
+ #include "nsCollationCID.h"
+ #include "nsIPrefService.h"
+ #include "nsIPrefBranch.h"
+ #include "nsArrayEnumerator.h"
+ #include "nsIMemoryReporter.h"
++#include "nsIWeakReferenceUtils.h"
+ #include "mozilla/mailnews/MimeHeaderParser.h"
+ #include "mozilla/mailnews/Services.h"
+ 
+ using namespace mozilla::mailnews;
+ using namespace mozilla;
+ 
+ #if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_)
+ #define DEBUG_MSGKEYSET 1
+@@ -1010,48 +1011,54 @@ size_t nsMsgDatabase::SizeOfExcludingThi
+   return totalSize;
+ }
+ 
+ namespace mozilla {
+ namespace mailnews {
+ 
+ MOZ_DEFINE_MALLOC_SIZE_OF(GetMallocSize)
+ 
+-class MsgDBReporter final : public nsIMemoryReporter
+-{
+-  nsMsgDatabase *mDatabase;
++class MsgDBReporter final : public nsIMemoryReporter {
++  nsWeakPtr mDatabase;
++
+ public:
+-  MsgDBReporter(nsMsgDatabase *db) : mDatabase(db) {}
++  MsgDBReporter(nsMsgDatabase *db)
++      : mDatabase(do_GetWeakReference(db)) {}
+ 
+   NS_DECL_ISUPPORTS
+   NS_IMETHOD GetName(nsACString &aName)
+   {
+     aName.AssignLiteral("msg-database-objects");
+     return NS_OK;
+   }
+ 
+   NS_IMETHOD CollectReports(nsIHandleReportCallback* aCb,
+                             nsISupports* aClosure,
+                             bool aAnonymize) override
+   {
+     nsCString path;
+     GetPath(path, aAnonymize);
++    nsCOMPtr<nsIMsgDatabase> database = do_QueryReferent(mDatabase);
++    nsMsgDatabase *db =
++        database ? static_cast<nsMsgDatabase *>(database.get()) : nullptr;
++
+     return aCb->Callback(EmptyCString(), path,
+                          nsIMemoryReporter::KIND_HEAP,
+                          nsIMemoryReporter::UNITS_BYTES,
+-                         mDatabase->SizeOfIncludingThis(GetMallocSize),
++                         db ? db->SizeOfIncludingThis(GetMallocSize) : 0,
+                          NS_LITERAL_CSTRING("Memory used for the folder database."),
+                          aClosure);
+   }
+ 
+   void GetPath(nsACString &memoryPath, bool aAnonymize)
+   {
+     memoryPath.AssignLiteral("explicit/maildb/database(");
++    nsCOMPtr<nsIMsgDatabase> database = do_QueryReferent(mDatabase);
+     nsCOMPtr<nsIMsgFolder> folder;
+-    mDatabase->GetFolder(getter_AddRefs(folder));
++    if (database) database->GetFolder(getter_AddRefs(folder));
+     if (folder)
+     {
+       if (aAnonymize)
+         memoryPath.AppendLiteral("<anonymized>");
+       else
+       {
+         nsAutoCString folderURL;
+         folder->GetFolderURL(folderURL);
+@@ -1119,16 +1126,17 @@ nsMsgDatabase::nsMsgDatabase()
+ {
+   mMemReporter = new mozilla::mailnews::MsgDBReporter(this);
+   mozilla::RegisterWeakMemoryReporter(mMemReporter);
+ }
+ 
+ nsMsgDatabase::~nsMsgDatabase()
+ {
+   mozilla::UnregisterWeakMemoryReporter(mMemReporter);
++  mMemReporter = nullptr;
+   //  Close(FALSE);  // better have already been closed.
+   ClearCachedObjects(true);
+   ClearEnumerators();
+   delete m_cachedHeaders;
+   delete m_headersInUse;
+ 
+   if (m_msgReferences)
+   {

+ 148 - 0
comm-release/patches/1896174-checkbox-25319.patch

@@ -0,0 +1,148 @@
+# HG changeset patch
+# User Frank-Rainer Grahl <frgrahl@gmx.net>
+# Date 1715329582 -7200
+# Parent  440e3813a3715bc2fdc2f8c4f32c4e57f56d5eab
+Bug 1896174 - Restore missing checkbox icons in classic theme. r=IanN a=IanN
+
+diff --git a/suite/themes/classic/communicator/aboutSessionRestore.css b/suite/themes/classic/communicator/aboutSessionRestore.css
+--- a/suite/themes/classic/communicator/aboutSessionRestore.css
++++ b/suite/themes/classic/communicator/aboutSessionRestore.css
+@@ -23,21 +23,21 @@ treechildren::-moz-tree-image(container,
+   list-style-image: url("chrome://communicator/skin/places/bookmark-folder-closed.png");
+ }
+ 
+ treechildren::-moz-tree-image(container, noicon, open) {
+   list-style-image: url("chrome://communicator/skin/places/bookmark-folder-open.png");
+ }
+ 
+ treechildren::-moz-tree-checkbox(checked) {
+-  list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
++  list-style-image: url("chrome://communicator/skin/checkbox/cbox-check.png");
+ }
+ 
+ treechildren::-moz-tree-checkbox(partial) {
+-  list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
++  list-style-image: url("chrome://communicator/skin/checkbox/cbox-check-dis.png");
+ }
+ 
+ treechildren::-moz-tree-row(alternate) {
+   background-color: -moz-oddtreerow;
+ }
+ 
+ treechildren::-moz-tree-row(alternate, selected) {
+   background-color: Highlight;
+diff --git a/suite/themes/classic/communicator/icons/checkbox/cbox-check-dis.png b/suite/themes/classic/communicator/icons/checkbox/cbox-check-dis.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..5dc5783267d738f04e6495e0c3085b0388d2ebd8
+GIT binary patch
+literal 122
+zc%17D@N?(olHy`uVBq!ia0vp^>>$j+0wnXI=SKi3&H|6fVg?5OL=a|NVZS^RD5&G<
+z;uyj)GqlH%i@|{7a8Ufc`MaG3WBqs=4;@^q9q>+cMNh}ll?jd8YSbSxO7G`qvUY5d
+Q1nOh(boFyt=akR{09P#_a{vGU
+
+diff --git a/suite/themes/classic/communicator/icons/checkbox/cbox-check.png b/suite/themes/classic/communicator/icons/checkbox/cbox-check.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..e02661436c5ed35b9c7cdb0702f7f30684e0445a
+GIT binary patch
+literal 120
+zc%17D@N?(olHy`uVBq!ia0vp^>>$j+0wnXI=SKi3&H|6fVg?5OL=a|NVZS^RD5&M>
+z;uyj)GqmR<FM|Qc;hb;xXWFf>_|%oGxnXi%g95uk-O9k*O%G-i2UWgZ$jOv?_VrAl
+OE(T9mKbLh*2~7ZStRnyb
+
+diff --git a/suite/themes/classic/communicator/sync/syncQuota.css b/suite/themes/classic/communicator/sync/syncQuota.css
+--- a/suite/themes/classic/communicator/sync/syncQuota.css
++++ b/suite/themes/classic/communicator/sync/syncQuota.css
+@@ -6,20 +6,20 @@
+   width: 33em;
+   height: 25em;
+ }
+ 
+ treechildren::-moz-tree-checkbox {
+   list-style-image: none;
+ }
+ treechildren::-moz-tree-checkbox(checked) {
+-  list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
++  list-style-image: url("chrome://communicator/skin/checkbox/cbox-check.png");
+ }
+ treechildren::-moz-tree-checkbox(disabled) {
+-  list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
++  list-style-image: url("chrome://communicator/skin/checkbox/cbox-check-dis.png");
+ }
+ 
+ #treeCaption {
+   height: 4em;
+ }
+ 
+ .captionWarning {
+   font-weight: bold;
+diff --git a/suite/themes/classic/editor/EditorDialog.css b/suite/themes/classic/editor/EditorDialog.css
+--- a/suite/themes/classic/editor/EditorDialog.css
++++ b/suite/themes/classic/editor/EditorDialog.css
+@@ -244,17 +244,17 @@
+ .AttributesTree {
+   min-width : 200px;
+   min-height: 200px;
+ }
+ 
+ /* ::::: select edit dialog ::::: */
+ 
+ #SelectTreeChildren::-moz-tree-cell(SelectSelCol, checked-true) {
+-  background: url("chrome://global/skin/checkbox/cbox-check.gif") 50% 50% no-repeat;
++  background: url("chrome://communicator/skin/checkbox/cbox-check.png") 50% 50% no-repeat;
+ }
+ 
+ /* ::::: Publishing Progress ::::: */
+ 
+ .progressitem[progress="busy"] {
+ 	list-style-image: url("chrome://editor/skin/icons/progress-busy.png");
+ }
+ 
+diff --git a/suite/themes/classic/jar.mn b/suite/themes/classic/jar.mn
+--- a/suite/themes/classic/jar.mn
++++ b/suite/themes/classic/jar.mn
+@@ -119,16 +119,18 @@ classic.jar:
+   skin/classic/communicator/communicatorBindings.xml                    (communicator/communicatorBindings.xml)
+   skin/classic/communicator/preferences.css                             (communicator/preferences.css)
+   skin/classic/communicator/prefpanels.css                              (communicator/prefpanels.css)
+   skin/classic/communicator/smileys.css                                 (communicator/smileys.css)
+   skin/classic/communicator/tasksOverlay.css                            (communicator/tasksOverlay.css)
+   skin/classic/communicator/brand/throbber-anim.png                     (communicator/brand/throbber-anim.png)
+   skin/classic/communicator/brand/throbber-single.png                   (communicator/brand/throbber-single.png)
+   skin/classic/communicator/brand/throbber16-anim.png                   (communicator/brand/throbber16-anim.png)
++  skin/classic/communicator/checkbox/cbox-check.png                     (communicator/icons/checkbox/cbox-check.png)
++  skin/classic/communicator/checkbox/cbox-check-dis.png                 (communicator/icons/checkbox/cbox-check-dis.png)
+   skin/classic/communicator/brand/throbber16-single.png                 (communicator/brand/throbber16-single.png)
+   skin/classic/communicator/dataman/dataman.css                         (communicator/dataman/dataman.css)
+   skin/classic/communicator/dataman/datamanIcon-16.png                  (communicator/dataman/datamanIcon-16.png)
+ #ifdef MOZ_WIDGET_GTK
+   skin/classic/communicator/feed-subscribe.css                          (linux/communicator/feed-subscribe.css)
+ #else
+   skin/classic/communicator/feed-subscribe.css                          (communicator/feed-subscribe.css)
+ #endif
+diff --git a/suite/themes/classic/mac/communicator/aboutSessionRestore.css b/suite/themes/classic/mac/communicator/aboutSessionRestore.css
+--- a/suite/themes/classic/mac/communicator/aboutSessionRestore.css
++++ b/suite/themes/classic/mac/communicator/aboutSessionRestore.css
+@@ -19,21 +19,21 @@ treechildren::-moz-tree-image(noicon) {
+   list-style-image: url("chrome://communicator/skin/places/bookmark-item.svg");
+ }
+ 
+ treechildren::-moz-tree-image(container, noicon) {
+   list-style-image: url("chrome://global/skin/tree/folder.png");
+ }
+ 
+ treechildren::-moz-tree-checkbox(checked) {
+-  list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
++  list-style-image: url("chrome://communicator/skin/checkbox/cbox-check.png");
+ }
+ 
+ treechildren::-moz-tree-checkbox(partial) {
+-  list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
++  list-style-image: url("chrome://communicator/skin/checkbox/cbox-check-dis.png");
+ }
+ 
+ #buttons {
+   margin-inline-start: 80px;
+ }
+ #buttons > button {
+   margin-top: 2em;
+   margin-inline-start: 5px;

+ 2 - 0
comm-release/patches/series

@@ -2146,6 +2146,7 @@ TOP-1378089-4-bookmarks-wip-25319.patch
 TOP-1872623-cancelbookmark-25319.patch
 1466297-62a1.patch
 1877001-port1407891-25319.patch
+1353704-69a1.patch
 1879726-port1398229-25319.patch
 1445374-61a1.patch
 815638-125a1.patch
@@ -2153,3 +2154,4 @@ TOP-1872623-cancelbookmark-25319.patch
 1446050-2-61a1.patch
 WIP-NOBUG-implement-about-seamonkey-comm.patch
 1437393-fontsasync-25319.patch
+1896174-checkbox-25319.patch

+ 59 - 0
mozilla-release/patches/1789403-3-106a1.patch

@@ -0,0 +1,59 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1662826238 0
+# Node ID 0215f5326a146b6e4180d6ba46dc5ebcfd363c6e
+# Parent  eda226e114cde173fa0e1c8d3dac37cf0a4069ac
+Bug 1789403 - Implement the size() method in our OTSStream subclass. r=RyanVM
+
+Depends on D156858
+
+Differential Revision: https://phabricator.services.mozilla.com/D156859
+
+diff --git a/gfx/thebes/gfxOTSUtils.h.1789403-3.later b/gfx/thebes/gfxOTSUtils.h.1789403-3.later
+new file mode 100644
+--- /dev/null
++++ b/gfx/thebes/gfxOTSUtils.h.1789403-3.later
+@@ -0,0 +1,21 @@
++--- gfxOTSUtils.h
+++++ gfxOTSUtils.h
++@@ -30,16 +30,18 @@ class gfxOTSExpandingMemoryStream : publ
++   explicit gfxOTSExpandingMemoryStream(size_t initial,
++                                        size_t limit = DEFAULT_LIMIT)
++       : mLength(initial), mLimit(limit), mOff(0) {
++     mPtr = mAlloc.Grow(nullptr, mLength);
++   }
++ 
++   ~gfxOTSExpandingMemoryStream() { mAlloc.Free(mPtr); }
++ 
+++  size_t size() override { return mLimit; }
+++
++   // Return the buffer, resized to fit its contents (as it may have been
++   // over-allocated during growth), and give up ownership of it so the
++   // caller becomes responsible to call free() when finished with it.
++   auto forget() {
++     auto p = mAlloc.ShrinkToFit(mPtr, mOff);
++     mPtr = nullptr;
++     return p;
++   }
+diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp
+--- a/gfx/thebes/gfxUserFontSet.cpp
++++ b/gfx/thebes/gfxUserFontSet.cpp
+@@ -44,16 +44,18 @@ public:
+         : mLength(initial), mLimit(limit), mOff(0) {
+         mPtr = moz_xmalloc(mLength);
+     }
+ 
+     ~ExpandingMemoryStream() {
+         free(mPtr);
+     }
+ 
++    size_t size() override { return mLimit; }
++
+     // Return the buffer, resized to fit its contents (as it may have been
+     // over-allocated during growth), and give up ownership of it so the
+     // caller becomes responsible to call free() when finished with it.
+     void* forget() {
+         void* p = moz_xrealloc(mPtr, mOff);
+         mPtr = nullptr;
+         return p;
+     }

+ 1004 - 0
mozilla-release/patches/mozilla-central_603605.patch

@@ -0,0 +1,1004 @@
+# HG changeset patch
+# User Ryan VanderMeulen <ryanvm@gmail.com>
+# Date 1641251507 0
+#      Mon Jan 03 23:11:47 2022 +0000
+# Node ID 0ab387c3ce5e1fd43a36bcfc1f44b0c24c3e37ce
+# Parent  0a61b641285d99803580655013bc6d7fd226902b
+Bug 1748352 - Update OTS to 8.2.1. r=jfkthame
+
+Differential Revision: https://phabricator.services.mozilla.com/D134984
+
+diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
+--- a/gfx/ots/README.mozilla
++++ b/gfx/ots/README.mozilla
+@@ -1,12 +1,12 @@
+ This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
+ 
+ Our reference repository is https://github.com/khaledhosny/ots/.
+ 
+-Current revision: a6533c4f6a7cb777153c78b78295e42f185ccd3d (8.1.4)
++Current revision: 2369c7c00de9e729c79d13e41e2c3601ed2f6c69 (8.2.1)
+ 
+ Upstream files included: LICENSE, src/, include/, tests/*.cc
+ 
+ Additional files: README.mozilla, src/moz.build
+ 
+ Additional patch: ots-visibility.patch (bug 711079).
+ Additional patch: ots-lz4.patch
+diff --git a/gfx/ots/src/cff.cc b/gfx/ots/src/cff.cc
+--- a/gfx/ots/src/cff.cc
++++ b/gfx/ots/src/cff.cc
+@@ -399,23 +399,27 @@ bool ValidCFF2DictOp(uint32_t op, DICT_D
+ 
+ bool ParsePrivateDictData(
+     ots::Buffer &table, size_t offset, size_t dict_length,
+     DICT_DATA_TYPE type, ots::OpenTypeCFF *out_cff) {
+   ots::Buffer dict(table.buffer() + offset, dict_length);
+   std::vector<Operand> operands;
+   bool cff2 = (out_cff->major == 2);
+   bool blend_seen = false;
+-  uint32_t vsindex = 0;
++  int32_t vsindex = 0;
+ 
+   // Since a Private DICT for FDArray might not have a Local Subr (e.g. Hiragino
+   // Kaku Gothic Std W8), we create an empty Local Subr here to match the size
+   // of FDArray the size of |local_subrs_per_font|.
++  // For CFF2, |vsindex_per_font| gets a similar treatment.
+   if (type == DICT_DATA_FDARRAY) {
+     out_cff->local_subrs_per_font.push_back(new ots::CFFIndex);
++    if (cff2) {
++      out_cff->vsindex_per_font.push_back(vsindex);
++    }
+   }
+ 
+   while (dict.offset() < dict.length()) {
+     if (!ParseDictDataReadOperands(dict, operands, cff2)) {
+       return OTS_FAILURE();
+     }
+     if (operands.back().second != DICT_OPERATOR) {
+       continue;
+@@ -522,30 +526,32 @@ bool ParsePrivateDictData(
+         }
+         if (operands.back().second != DICT_OPERAND_INTEGER) {
+           return OTS_FAILURE();
+         }
+         if (blend_seen) {
+           return OTS_FAILURE();
+         }
+         vsindex = operands.back().first;
+-        if (vsindex >= out_cff->region_index_count.size()) {
++        if (vsindex < 0 ||
++            vsindex >= (int32_t)out_cff->region_index_count.size()) {
+           return OTS_FAILURE();
+         }
++        out_cff->vsindex_per_font.back() = vsindex;
+         break;
+       }
+ 
+       case 23: { // blend
+         if (!cff2) {
+           return OTS_FAILURE();
+         }
+         if (operands.size() < 1) {
+           return OTS_FAILURE();
+         }
+-        if (vsindex >= out_cff->region_index_count.size()) {
++        if (vsindex >= (int32_t)out_cff->region_index_count.size()) {
+           return OTS_FAILURE();
+         }
+         uint16_t k = out_cff->region_index_count.at(vsindex);
+         uint16_t n = operands.back().first;
+         if (operands.size() < n * (k + 1) + 1) {
+           return OTS_FAILURE();
+         }
+         size_t operands_size = operands.size();
+@@ -620,31 +626,36 @@ bool ParseDictData(ots::Buffer& table, o
+   bool cff2 = (out_cff->major == 2);
+   std::vector<Operand> operands;
+ 
+   FONT_FORMAT font_format = FORMAT_UNKNOWN;
+   bool have_ros = false;
+   bool have_charstrings = false;
+   bool have_vstore = false;
+   size_t charset_offset = 0;
++  bool have_private = false;
+ 
+   if (cff2) {
+     // Parse VariationStore first, since it might be referenced in other places
+     // (e.g. FDArray) that might be parsed after it.
+     size_t dict_offset = dict.offset();
+     while (dict.offset() < dict.length()) {
+       if (!ParseDictDataReadOperands(dict, operands, cff2)) {
+         return OTS_FAILURE();
+       }
+       if (operands.back().second != DICT_OPERATOR) continue;
+ 
+       // got operator
+       const uint32_t op = operands.back().first;
+       operands.pop_back();
+ 
++      if (op == 18 && type == DICT_DATA_FDARRAY) {
++        have_private = true;
++      }
++
+       if (op == 24) {  // vstore
+         if (type != DICT_DATA_TOPLEVEL) {
+           return OTS_FAILURE();
+         }
+         if (operands.size() != 1) {
+           return OTS_FAILURE();
+         }
+         if (!CheckOffset(operands.back(), table.length())) {
+@@ -656,16 +667,21 @@ bool ParseDictData(ots::Buffer& table, o
+           return OTS_FAILURE();
+         }
+         break;
+       }
+       operands.clear();
+     }
+     operands.clear();
+     dict.set_offset(dict_offset);
++
++    if (type == DICT_DATA_FDARRAY && !have_private) {
++      return OTS_FAILURE();  // CFF2 FD must have PrivateDICT entry (even if 0, 0)
++    }
++
+   }
+ 
+   while (dict.offset() < dict.length()) {
+     if (!ParseDictDataReadOperands(dict, operands, cff2)) {
+       return OTS_FAILURE();
+     }
+     if (operands.back().second != DICT_OPERATOR) continue;
+ 
+diff --git a/gfx/ots/src/cff.h b/gfx/ots/src/cff.h
+--- a/gfx/ots/src/cff.h
++++ b/gfx/ots/src/cff.h
+@@ -59,16 +59,21 @@ class OpenTypeCFF : public Table {
+   // A list of Local Subrs associated with FDArrays. Can be empty.
+   std::vector<CFFIndex *> local_subrs_per_font;
+   // A Local Subrs associated with Top DICT. Can be NULL.
+   CFFIndex *local_subrs;
+ 
+   // CFF2 VariationStore regionIndexCount.
+   std::vector<uint16_t> region_index_count;
+ 
++  // CFF2 vsindex: per FontDICT->PrivateDICT
++  // default of 0 is stored for each font if not
++  // explicitly set in Font's PrivateDICT.
++  std::vector<int32_t> vsindex_per_font;
++
+  protected:
+   bool ValidateFDSelect(uint16_t num_glyphs);
+ 
+  private:
+   const uint8_t *m_data;
+   size_t m_length;
+ };
+ 
+diff --git a/gfx/ots/src/cff_charstring.cc b/gfx/ots/src/cff_charstring.cc
+--- a/gfx/ots/src/cff_charstring.cc
++++ b/gfx/ots/src/cff_charstring.cc
+@@ -31,20 +31,17 @@ const int32_t dummy_result = INT_MAX;
+ 
+ bool ExecuteCharString(ots::OpenTypeCFF& cff,
+                        size_t call_depth,
+                        const ots::CFFIndex& global_subrs_index,
+                        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,
+-                       bool cff2);
++                       ots::CharStringContext& cs_ctx);
+ 
+ bool ArgumentStackOverflows(std::stack<int32_t> *argument_stack, bool cff2) {
+   if ((cff2 && argument_stack->size() > ots::kMaxCFF2ArgumentStack) ||
+       (!cff2 && argument_stack->size() > ots::kMaxCFF1ArgumentStack)) {
+     return true;
+   }
+   return false;
+ }
+@@ -261,37 +258,31 @@ bool ValidCFF2Operator(int32_t op) {
+     return false;
+   }
+ 
+   return true;
+ }
+ 
+ // Executes |op| and updates |argument_stack|. Returns true if the execution
+ // succeeds. If the |op| is kCallSubr or kCallGSubr, the function recursively
+-// calls ExecuteCharString() function. The arguments other than |op| and
+-// |argument_stack| are passed for that reason.
++// calls ExecuteCharString() function. The |cs_ctx| argument holds values that
++// need to persist through these calls (see CharStringContext for details)
+ bool ExecuteCharStringOperator(ots::OpenTypeCFF& cff,
+                                int32_t op,
+                                size_t call_depth,
+                                const ots::CFFIndex& global_subrs_index,
+                                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 *in_out_found_width,
+-                               size_t *in_out_num_stems,
+-                               bool *in_out_have_blend,
+-                               bool *in_out_have_visindex,
+-                               int32_t *in_out_vsindex,
+-                               bool cff2) {
++                               ots::CharStringContext& cs_ctx) {
+   ots::Font* font = cff.GetFont();
+   const size_t stack_size = argument_stack->size();
+ 
+-  if (cff2 && !ValidCFF2Operator(op)) {
++  if (cs_ctx.cff2 && !ValidCFF2Operator(op)) {
+     return OTS_FAILURE();
+   }
+ 
+   switch (op) {
+   case ots::kCallSubr:
+   case ots::kCallGSubr: {
+     const ots::CFFIndex& subrs_index =
+         (op == ots::kCallSubr ? local_subrs_index : global_subrs_index);
+@@ -344,164 +335,162 @@ bool ExecuteCharStringOperator(ots::Open
+ 
+     return ExecuteCharString(cff,
+                              call_depth + 1,
+                              global_subrs_index,
+                              local_subrs_index,
+                              cff_table,
+                              &char_string_to_jump,
+                              argument_stack,
+-                             out_found_endchar,
+-                             in_out_found_width,
+-                             in_out_num_stems,
+-                             cff2);
++                             cs_ctx);
+   }
+ 
+   case ots::kReturn:
+     return true;
+ 
+   case ots::kEndChar:
+-    *out_found_endchar = true;
+-    *in_out_found_width = true;  // just in case.
++    cs_ctx.endchar_seen = true;
++    cs_ctx.width_seen = true;  // just in case.
+     return true;
+ 
+   case ots::kVSIndex: {
+-    if (!cff2) {
++    if (!cs_ctx.cff2) {
+       return OTS_FAILURE();
+     }
+     if (stack_size != 1) {
+       return OTS_FAILURE();
+     }
+-    if (*in_out_have_blend || *in_out_have_visindex) {
++    if (cs_ctx.blend_seen || cs_ctx.vsindex_seen) {
+       return OTS_FAILURE();
+     }
+-    if (argument_stack->top() >= cff.region_index_count.size()) {
++    if (argument_stack->top() < 0 ||
++        argument_stack->top() >= (int32_t)cff.region_index_count.size()) {
+       return OTS_FAILURE();
+     }
+-    *in_out_have_visindex = true;
+-    *in_out_vsindex = argument_stack->top();
++    cs_ctx.vsindex_seen = true;
++    cs_ctx.vsindex = argument_stack->top();
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+   }
+ 
+   case ots::kBlend: {
+-    if (!cff2) {
++    if (!cs_ctx.cff2) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 1) {
+       return OTS_FAILURE();
+     }
+-    if (*in_out_vsindex >= cff.region_index_count.size()) {
++    if (cs_ctx.vsindex >= (int32_t)cff.region_index_count.size()) {
+       return OTS_FAILURE();
+     }
+-    uint16_t k = cff.region_index_count.at(*in_out_vsindex);
++    uint16_t k = cff.region_index_count.at(cs_ctx.vsindex);
+     uint16_t n = argument_stack->top();
+     if (stack_size < n * (k + 1) + 1) {
+       return OTS_FAILURE();
+     }
+ 
+     // Keep the 1st n operands on the stack for the next operator to use and
+-    // pop the rest. There can be multiple consecutive blend operator, so this
++    // pop the rest. There can be multiple consecutive blend operators, so this
+     // makes sure the operands of all of them are kept on the stack.
+     while (argument_stack->size() > stack_size - ((n * k) + 1))
+       argument_stack->pop();
+-    *in_out_have_blend = true;
++    cs_ctx.blend_seen = true;
+     return true;
+   }
+ 
+   case ots::kHStem:
+   case ots::kVStem:
+   case ots::kHStemHm:
+   case ots::kVStemHm: {
+     bool successful = false;
+     if (stack_size < 2) {
+       return OTS_FAILURE();
+     }
+     if ((stack_size % 2) == 0) {
+       successful = true;
+-    } else if ((!(*in_out_found_width)) && (((stack_size - 1) % 2) == 0)) {
++    } else if ((!(cs_ctx.width_seen)) && (((stack_size - 1) % 2) == 0)) {
+       // The -1 is for "width" argument. For details, see Adobe Technical Note
+       // #5177, page 16, note 4.
+       successful = true;
+     }
+-    (*in_out_num_stems) += (stack_size / 2);
+-    if ((*in_out_num_stems) > kMaxNumberOfStemHints) {
++    cs_ctx.num_stems += (stack_size / 2);
++    if ((cs_ctx.num_stems) > kMaxNumberOfStemHints) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+-    *in_out_found_width = true;  // always set true since "w" might be 0 byte.
++    cs_ctx.width_seen = true;  // always set true since "w" might be 0 byte.
+     return successful ? true : OTS_FAILURE();
+   }
+ 
+   case ots::kRMoveTo: {
+     bool successful = false;
+     if (stack_size == 2) {
+       successful = true;
+-    } else if ((!(*in_out_found_width)) && (stack_size - 1 == 2)) {
++    } else if ((!(cs_ctx.width_seen)) && (stack_size - 1 == 2)) {
+       successful = true;
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+-    *in_out_found_width = true;
++    cs_ctx.width_seen = true;
+     return successful ? true : OTS_FAILURE();
+   }
+ 
+   case ots::kVMoveTo:
+   case ots::kHMoveTo: {
+     bool successful = false;
+     if (stack_size == 1) {
+       successful = true;
+-    } else if ((!(*in_out_found_width)) && (stack_size - 1 == 1)) {
++    } else if ((!(cs_ctx.width_seen)) && (stack_size - 1 == 1)) {
+       successful = true;
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+-    *in_out_found_width = true;
++    cs_ctx.width_seen = true;
+     return successful ? true : OTS_FAILURE();
+   }
+ 
+   case ots::kHintMask:
+   case ots::kCntrMask: {
+     bool successful = false;
+     if (stack_size == 0) {
+       successful = true;
+-    } else if ((!(*in_out_found_width)) && (stack_size == 1)) {
++    } else if ((!(cs_ctx.width_seen)) && (stack_size == 1)) {
+       // A number for "width" is found.
+       successful = true;
+-    } else if ((!(*in_out_found_width)) ||  // in this case, any sizes are ok.
++    } else if ((!(cs_ctx.width_seen)) ||  // in this case, any sizes are ok.
+                ((stack_size % 2) == 0)) {
+       // The numbers are vstem definition.
+       // See Adobe Technical Note #5177, page 24, hintmask.
+-      (*in_out_num_stems) += (stack_size / 2);
+-      if ((*in_out_num_stems) > kMaxNumberOfStemHints) {
++      cs_ctx.num_stems += (stack_size / 2);
++      if ((cs_ctx.num_stems) > kMaxNumberOfStemHints) {
+         return OTS_FAILURE();
+       }
+       successful = true;
+     }
+     if (!successful) {
+        return OTS_FAILURE();
+     }
+ 
+-    if ((*in_out_num_stems) == 0) {
++    if ((cs_ctx.num_stems) == 0) {
+       return OTS_FAILURE();
+     }
+-    const size_t mask_bytes = (*in_out_num_stems + 7) / 8;
++    const size_t mask_bytes = (cs_ctx.num_stems + 7) / 8;
+     if (!char_string->Skip(mask_bytes)) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+-    *in_out_found_width = true;
++    cs_ctx.width_seen = true;
+     return true;
+   }
+ 
+   case ots::kRLineTo:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       // The first stack-clearing operator should be one of hstem, hstemhm,
+       // vstem, vstemhm, cntrmask, hintmask, hmoveto, vmoveto, rmoveto, or
+       // endchar. For details, see Adobe Technical Note #5177, page 16, note 4.
+       return OTS_FAILURE();
+     }
+     if (stack_size < 2) {
+       return OTS_FAILURE();
+     }
+@@ -509,86 +498,86 @@ bool ExecuteCharStringOperator(ots::Open
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kHLineTo:
+   case ots::kVLineTo:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 1) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kRRCurveTo:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 6) {
+       return OTS_FAILURE();
+     }
+     if ((stack_size % 6) != 0) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kRCurveLine:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 8) {
+       return OTS_FAILURE();
+     }
+     if (((stack_size - 2) % 6) != 0) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kRLineCurve:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 8) {
+       return OTS_FAILURE();
+     }
+     if (((stack_size - 6) % 2) != 0) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kVVCurveTo:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 4) {
+       return OTS_FAILURE();
+     }
+     if (((stack_size % 4) != 0) &&
+         (((stack_size - 1) % 4) != 0)) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kHHCurveTo: {
+     bool successful = false;
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 4) {
+       return OTS_FAILURE();
+     }
+     if ((stack_size % 4) == 0) {
+       // {dxa dxb dyb dxc}+
+       successful = true;
+@@ -599,17 +588,17 @@ bool ExecuteCharStringOperator(ots::Open
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return successful ? true : OTS_FAILURE();
+   }
+ 
+   case ots::kVHCurveTo:
+   case ots::kHVCurveTo: {
+     bool successful = false;
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size < 4) {
+       return OTS_FAILURE();
+     }
+     if (((stack_size - 4) % 8) == 0) {
+       // dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}*
+       successful = true;
+@@ -742,17 +731,17 @@ bool ExecuteCharStringOperator(ots::Open
+ 
+   case ots::kDup:
+     if (stack_size < 1) {
+       return OTS_FAILURE();
+     }
+     argument_stack->pop();
+     argument_stack->push(dummy_result);
+     argument_stack->push(dummy_result);
+-    if (ArgumentStackOverflows(argument_stack, cff2)) {
++    if (ArgumentStackOverflows(argument_stack, cs_ctx.cff2)) {
+       return OTS_FAILURE();
+     }
+     // TODO(yusukes): Implement this. We should push a real value for all
+     // arithmetic and conditional operations.
+     return true;
+ 
+   case ots::kExch:
+     if (stack_size < 2) {
+@@ -762,94 +751,96 @@ bool ExecuteCharStringOperator(ots::Open
+     argument_stack->pop();
+     argument_stack->push(dummy_result);
+     argument_stack->push(dummy_result);
+     // TODO(yusukes): Implement this. We should push a real value for all
+     // arithmetic and conditional operations.
+     return true;
+ 
+   case ots::kHFlex:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size != 7) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kFlex:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size != 13) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kHFlex1:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size != 9) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+ 
+   case ots::kFlex1:
+-    if (!(*in_out_found_width)) {
++    if (!(cs_ctx.width_seen)) {
+       return OTS_FAILURE();
+     }
+     if (stack_size != 11) {
+       return OTS_FAILURE();
+     }
+     while (!argument_stack->empty())
+       argument_stack->pop();
+     return true;
+   }
+ 
+   return OTS_FAILURE_MSG("Undefined operator: %d (0x%x)", op, op);
+ }
+ 
+ // Executes |char_string| and updates |argument_stack|.
+ //
++// cff: parent OpenTypeCFF reference
+ // call_depth: The current call depth. Initial value is zero.
+ // global_subrs_index: Global subroutines.
+ // local_subrs_index: Local subroutines for the current glyph.
+ // cff_table: A whole CFF table which contains all global and local subroutines.
+ // char_string: A charstring we'll execute. |char_string| can be a main routine
+ //              in CharString INDEX, or a subroutine in GlobalSubr/LocalSubr.
+ // argument_stack: The stack which an operator in |char_string| operates.
+-// out_found_endchar: true is set if |char_string| contains 'endchar'.
+-// in_out_found_width: true is set if |char_string| contains 'width' byte (which
+-//                     is 0 or 1 byte.)
+-// in_out_num_stems: total number of hstems and vstems processed so far.
++// cs_ctx: a CharStringContext holding values to persist across subrs, etc.
++//   endchar_seen: true is set if |char_string| contains 'endchar'.
++//   width_seen: true is set if |char_string| contains 'width' byte (which
++//               is 0 or 1 byte) or if cff2
++//   num_stems: total number of hstems and vstems processed so far.
++//   cff2: true if this is a CFF2 table
++//   blend_seen: initially false; set to true if 'blend' operator encountered.
++//   vsindex_seen: initially false; set to true if 'vsindex' encountered.
++//   vsindex: initially = PrivateDICT's vsindex; may be changed by 'vsindex'
++//            operator in CharString
+ bool ExecuteCharString(ots::OpenTypeCFF& cff,
+                        size_t call_depth,
+                        const ots::CFFIndex& global_subrs_index,
+                        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 *in_out_found_width,
+-                       size_t *in_out_num_stems,
+-                       bool cff2) {
++                       ots::CharStringContext& cs_ctx) {
+   if (call_depth > kMaxSubrNesting) {
+     return OTS_FAILURE();
+   }
+-  *out_found_endchar = false;
++  cs_ctx.endchar_seen = false;
+ 
+-  bool in_out_have_blend = false, in_out_have_visindex = false;
+-  int32_t in_out_vsindex = 0;
+   const size_t length = char_string->length();
+   while (char_string->offset() < length) {
+     int32_t operator_or_operand = 0;
+     bool is_operator = false;
+     if (!ReadNextNumberFromCharString(char_string,
+                                            &operator_or_operand,
+                                            &is_operator)) {
+       return OTS_FAILURE();
+@@ -868,55 +859,49 @@ bool ExecuteCharString(ots::OpenTypeCFF&
+            CharStringOperatorToString(
+                ots::CharStringOperator(operator_or_operand))
+            );
+       }
+ #endif
+ 
+     if (!is_operator) {
+       argument_stack->push(operator_or_operand);
+-      if (ArgumentStackOverflows(argument_stack, cff2)) {
++      if (ArgumentStackOverflows(argument_stack, cs_ctx.cff2)) {
+         return OTS_FAILURE();
+       }
+       continue;
+     }
+ 
+     // An operator is found. Execute it.
+     if (!ExecuteCharStringOperator(cff,
+                                    operator_or_operand,
+                                    call_depth,
+                                    global_subrs_index,
+                                    local_subrs_index,
+                                    cff_table,
+                                    char_string,
+                                    argument_stack,
+-                                   out_found_endchar,
+-                                   in_out_found_width,
+-                                   in_out_num_stems,
+-                                   &in_out_have_blend,
+-                                   &in_out_have_visindex,
+-                                   &in_out_vsindex,
+-                                   cff2)) {
++                                   cs_ctx)) {
+       return OTS_FAILURE();
+     }
+-    if (*out_found_endchar) {
++    if (cs_ctx.endchar_seen) {
+       return true;
+     }
+     if (operator_or_operand == ots::kReturn) {
+       return true;
+     }
+   }
+ 
+-  // No endchar operator is found.
+-  if (cff2)
++  // No endchar operator is found (CFF1 only)
++  if (cs_ctx.cff2)
+     return true;
+   return OTS_FAILURE();
+ }
+ 
+-// Selects a set of subroutings for |glyph_index| from |cff| and sets it on
++// Selects a set of subroutines for |glyph_index| from |cff| and sets it on
+ // |out_local_subrs_to_use|. Returns true on success.
+ bool SelectLocalSubr(const ots::OpenTypeCFF& cff,
+                      uint16_t glyph_index,  // 0-origin
+                      const ots::CFFIndex **out_local_subrs_to_use) {
+   bool cff2 = (cff.major == 2);
+   *out_local_subrs_to_use = NULL;
+ 
+   // First, find local subrs from |local_subrs_per_font|.
+@@ -955,17 +940,16 @@ bool ValidateCFFCharStrings(
+     ots::OpenTypeCFF& cff,
+     const CFFIndex& global_subrs_index,
+     Buffer* cff_table) {
+   const CFFIndex& char_strings_index = *(cff.charstrings_index);
+   if (char_strings_index.offsets.size() == 0) {
+     return OTS_FAILURE();  // no charstring.
+   }
+ 
+-  bool cff2 = (cff.major == 2);
+   // For each glyph, validate the corresponding charstring.
+   for (unsigned i = 1; i < char_strings_index.offsets.size(); ++i) {
+     // Prepare a Buffer object, |char_string|, which contains the charstring
+     // for the |i|-th glyph.
+     const size_t length =
+       char_strings_index.offsets[i] - char_strings_index.offsets[i - 1];
+     if (length > kMaxCharStringLength) {
+       return OTS_FAILURE();
+@@ -988,30 +972,48 @@ bool ValidateCFFCharStrings(
+     // If |local_subrs_to_use| is still NULL, use an empty one.
+     CFFIndex default_empty_subrs;
+     if (!local_subrs_to_use){
+       local_subrs_to_use = &default_empty_subrs;
+     }
+ 
+     // Check a charstring for the |i|-th glyph.
+     std::stack<int32_t> argument_stack;
+-    bool found_endchar = false;
++    // Context to store values that must persist across subrs, etc.
++    CharStringContext cs_ctx;
++    cs_ctx.cff2 = (cff.major == 2);
+     // CFF2 CharString has no value for width, so we start with true here to
+     // error out if width is found.
+-    bool found_width = cff2;
+-    size_t num_stems = 0;
++    cs_ctx.width_seen = cs_ctx.cff2;
++    // CFF2 CharStrings' default vsindex comes from the associated PrivateDICT
++    if (cs_ctx.cff2) {
++      const auto& iter = cff.fd_select.find(glyph_index);
++      auto fd_index = 0;
++      if (iter != cff.fd_select.end()) {
++        fd_index = iter->second;
++      }
++      if (fd_index >= (int32_t)cff.vsindex_per_font.size()) {
++        // shouldn't get this far with a font in this condition, but just in case
++        return OTS_FAILURE();  // fd_index out-of-range
++      }
++      cs_ctx.vsindex = cff.vsindex_per_font.at(fd_index);
++    }
++
++#ifdef DUMP_T2CHARSTRING
++    fprintf(stderr, "\n---- CharString %*d ----\n", 5, glyph_index);
++#endif
++
+     if (!ExecuteCharString(cff,
+                            0 /* initial call_depth is zero */,
+                            global_subrs_index, *local_subrs_to_use,
+                            cff_table, &char_string, &argument_stack,
+-                           &found_endchar, &found_width, &num_stems,
+-                           cff2)) {
++                           cs_ctx)) {
+       return OTS_FAILURE();
+     }
+-    if (!cff2 && !found_endchar) {
++    if (!cs_ctx.cff2 && !cs_ctx.endchar_seen) {
+       return OTS_FAILURE();
+     }
+   }
+   return true;
+ }
+ 
+ }  // namespace ots
+ 
+diff --git a/gfx/ots/src/cff_charstring.h b/gfx/ots/src/cff_charstring.h
+--- a/gfx/ots/src/cff_charstring.h
++++ b/gfx/ots/src/cff_charstring.h
+@@ -16,30 +16,27 @@ namespace ots {
+ const size_t kMaxCFF1ArgumentStack = 48;
+ const size_t kMaxCFF2ArgumentStack = 513;
+ 
+ // Validates all charstrings in |char_strings_index|. Charstring is a small
+ // language for font hinting defined in Adobe Technical Note #5177.
+ // http://www.adobe.com/devnet/font/pdfs/5177.Type2.pdf
+ //
+ // The validation will fail if one of the following conditions is met:
+-//  1. The code uses more than 48 values of argument stack.
++//  1. The code uses more than the max argument stack size (48 for CFF1; 513 for CFF2)
+ //  2. The code uses deeply nested subroutine calls (more than 10 levels.)
+ //  3. The code passes invalid number of operands to an operator.
+ //  4. The code calls an undefined global or local subroutine.
+ //  5. The code uses one of the following operators that are unlikely used in
+ //     an ordinary fonts, and could be dangerous: random, put, get, index, roll.
+ //
+ // Arguments:
++//  cff: parent OpenTypeCFF reference
+ //  global_subrs_index: Global subroutines which could be called by a charstring
+ //                      in |char_strings_index|.
+-//  fd_select: A map from glyph # to font #.
+-//  local_subrs_per_font: A list of Local Subrs associated with FDArrays. Can be
+-//                        empty.
+-//  local_subrs: A Local Subrs associated with Top DICT. Can be NULL.
+ //  cff_table: A buffer which contains actual byte code of charstring, global
+ //             subroutines and local subroutines.
+ bool ValidateCFFCharStrings(
+     OpenTypeCFF& cff,
+     const CFFIndex &global_subrs_index,
+     Buffer *cff_table);
+ 
+ // The list of Operators. See Appendix. A in Adobe Technical Note #5177.
+@@ -90,14 +87,24 @@ enum CharStringOperator {
+   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 undocumented, such as 'blend', will be rejected.
++  // Operators that are undocumented will be rejected.
++};
++
++struct CharStringContext {
++  bool endchar_seen = false;
++  bool width_seen = false;
++  size_t num_stems = 0;
++  bool cff2 = false;
++  bool blend_seen = false;
++  bool vsindex_seen = false;
++  int32_t vsindex = 0;
+ };
+ 
+ }  // namespace ots
+ 
+ #endif  // OTS_CFF_TYPE2_CHARSTRING_H_
+diff --git a/gfx/ots/src/cmap.cc b/gfx/ots/src/cmap.cc
+--- a/gfx/ots/src/cmap.cc
++++ b/gfx/ots/src/cmap.cc
+@@ -509,23 +509,26 @@ bool OpenTypeCMAP::Parse0514(const uint8
+           = 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 Error("Can't read mapping %d in variation selector record %d", j, i);
+         }
+-        if (mappings[j].glyph_id == 0 ||
+-            mappings[j].unicode_value == 0 ||
+-            mappings[j].unicode_value > kUnicodeUpperLimit ||
+-            (last_unicode_value &&
+-             mappings[j].unicode_value <= last_unicode_value)) {
++        if (mappings[j].glyph_id == 0 || mappings[j].unicode_value == 0) {
+           return Error("Bad mapping (%04X -> %d) in mapping %d of variation selector %d", mappings[j].unicode_value, mappings[j].glyph_id, j, i);
+         }
++        if (mappings[j].unicode_value > kUnicodeUpperLimit) {
++          return Error("Invalid Unicode value (%04X > %04X) in mapping %d of variation selector %d", mappings[j].unicode_value, kUnicodeUpperLimit, j, i);
++        }
++        if (last_unicode_value &&
++            mappings[j].unicode_value <= last_unicode_value) {
++          return Error("Out of order Unicode value (%04X <= %04X) in mapping %d of variation selector %d", mappings[j].unicode_value, last_unicode_value, j, i);
++        }
+         last_unicode_value = mappings[j].unicode_value;
+       }
+     }
+   }
+ 
+   if (subtable.offset() != length) {
+     return Error("Bad subtable offset (%ld != %ld)", subtable.offset(), length);
+   }
+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
+@@ -224,33 +224,33 @@ bool ProcessTTF(ots::FontFile *header,
+   while (1u << (max_pow2 + 1) <= font->num_tables) {
+     max_pow2++;
+   }
+   const uint16_t expected_search_range = (1u << max_pow2) << 4;
+ 
+   // Don't call ots_failure() here since ~25% of fonts (250+ fonts) in
+   // http://www.princexml.com/fonts/ have unexpected search_range value.
+   if (font->search_range != expected_search_range) {
+-    OTS_WARNING_MSG_HDR("bad search range");
++    OTS_WARNING_MSG_HDR("bad table directory searchRange");
+     font->search_range = expected_search_range;  // Fix the value.
+   }
+ 
+   // entry_selector is Log2(maximum power of 2 <= numTables)
+   if (font->entry_selector != max_pow2) {
+-    OTS_WARNING_MSG_HDR("incorrect entrySelector for table directory");
++    OTS_WARNING_MSG_HDR("bad table directory entrySelector");
+     font->entry_selector = max_pow2;  // Fix the value.
+   }
+ 
+   // 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 > font->search_range by construction of search_range.
+   const uint16_t expected_range_shift =
+       16 * font->num_tables - font->search_range;
+   if (font->range_shift != expected_range_shift) {
+-    OTS_WARNING_MSG_HDR("bad range shift");
++    OTS_WARNING_MSG_HDR("bad table directory rangeShift");
+     font->range_shift = expected_range_shift;  // the same as above.
+   }
+ 
+   // Next up is the list of tables.
+   std::vector<ots::TableEntry> tables;
+ 
+   for (unsigned i = 0; i < font->num_tables; ++i) {
+     ots::TableEntry table;
+diff --git a/gfx/ots/src/variations.cc b/gfx/ots/src/variations.cc
+--- a/gfx/ots/src/variations.cc
++++ b/gfx/ots/src/variations.cc
+@@ -201,42 +201,42 @@ bool ParseVariationData(const Font* font
+ 
+     if (tupleIndex & EMBEDDED_PEAK_TUPLE) {
+       for (unsigned axis = 0; axis < axisCount; axis++) {
+         int16_t coordinate;
+         if (!subtable.ReadS16(&coordinate)) {
+           return OTS_FAILURE_MSG("Failed to read tuple coordinate");
+         }
+         if (coordinate < -0x4000 || coordinate > 0x4000) {
+-          return OTS_FAILURE_MSG("Invalid tuple coordinate");
++          return OTS_FAILURE_MSG("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.);
+         }
+       }
+     }
+ 
+     if (tupleIndex & INTERMEDIATE_REGION) {
+       std::vector<int16_t> startTuple(axisCount);
+       for (unsigned axis = 0; axis < axisCount; axis++) {
+         int16_t coordinate;
+         if (!subtable.ReadS16(&coordinate)) {
+           return OTS_FAILURE_MSG("Failed to read tuple coordinate");
+         }
+         if (coordinate < -0x4000 || coordinate > 0x4000) {
+-          return OTS_FAILURE_MSG("Invalid tuple coordinate");
++          return OTS_FAILURE_MSG("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.);
+         }
+         startTuple.push_back(coordinate);
+       }
+ 
+       std::vector<int16_t> endTuple(axisCount);
+       for (unsigned axis = 0; axis < axisCount; axis++) {
+         int16_t coordinate;
+         if (!subtable.ReadS16(&coordinate)) {
+           return OTS_FAILURE_MSG("Failed to read tuple coordinate");
+         }
+         if (coordinate < -0x4000 || coordinate > 0x4000) {
+-          return OTS_FAILURE_MSG("Invalid tuple coordinate");
++          return OTS_FAILURE_MSG("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.);
+         }
+         endTuple.push_back(coordinate);
+       }
+ 
+       for (unsigned axis = 0; axis < axisCount; axis++) {
+         if (startTuple[axis] > endTuple[axis]) {
+           return OTS_FAILURE_MSG("Invalid intermediate range");
+         }

+ 242 - 0
mozilla-release/patches/mozilla-central_635136.patch

@@ -0,0 +1,242 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1662826237 0
+#      Sat Sep 10 16:10:37 2022 +0000
+# Node ID 142c153da6e3c581d2301b6a9ecfe0d007e85c7c
+# Parent  bc97208be9ba8b17a4be5e20492f9a8428f398bd
+Bug 1789403 - Update OTS source to 8.2.2. r=RyanVM
+
+Depends on D156857
+
+Differential Revision: https://phabricator.services.mozilla.com/D156858
+
+diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
+--- a/gfx/ots/README.mozilla
++++ b/gfx/ots/README.mozilla
+@@ -1,12 +1,12 @@
+ This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
+ 
+ Our reference repository is https://github.com/khaledhosny/ots/.
+ 
+-Current revision: 2369c7c00de9e729c79d13e41e2c3601ed2f6c69 (8.2.1)
++Current revision: dd99d9a451d070048b73b7c385c19c8dfb28c24d (8.2.2)
+ 
+ Upstream files included: LICENSE, src/, include/, tests/*.cc
+ 
+ Additional files: README.mozilla, src/moz.build
+ 
+ Additional patch: ots-visibility.patch (bug 711079).
+ Additional patch: ots-lz4.patch
+diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-sanitiser.h
+--- a/gfx/ots/include/opentype-sanitiser.h
++++ b/gfx/ots/include/opentype-sanitiser.h
+@@ -73,16 +73,18 @@ namespace ots {
+ // the serialised results out.
+ // -----------------------------------------------------------------------------
+ class OTSStream {
+  public:
+   OTSStream() : chksum_(0) {}
+ 
+   virtual ~OTSStream() {}
+ 
++  virtual size_t size() = 0;
++
+   // This should be implemented to perform the actual write.
+   virtual bool WriteRaw(const void *data, size_t length) = 0;
+ 
+   bool Write(const void *data, size_t length) {
+     if (!length) return false;
+ 
+     const size_t orig_length = length;
+     size_t offset = 0;
+diff --git a/gfx/ots/include/ots-memory-stream.h b/gfx/ots/include/ots-memory-stream.h
+--- a/gfx/ots/include/ots-memory-stream.h
++++ b/gfx/ots/include/ots-memory-stream.h
+@@ -13,34 +13,36 @@
+ namespace ots {
+ 
+ class MemoryStream : public OTSStream {
+  public:
+   MemoryStream(void *ptr, size_t length)
+       : ptr_(ptr), length_(length), off_(0) {
+   }
+ 
+-  virtual bool WriteRaw(const void *data, size_t length) {
++  size_t size() override { return length_; }
++
++  bool WriteRaw(const void *data, size_t length) override {
+     if ((off_ + length > length_) ||
+         (length > std::numeric_limits<size_t>::max() - off_)) {
+       return false;
+     }
+     std::memcpy(static_cast<char*>(ptr_) + off_, data, length);
+     off_ += length;
+     return true;
+   }
+ 
+-  virtual bool Seek(off_t position) {
++  bool Seek(off_t position) override {
+     if (position < 0) return false;
+     if (static_cast<size_t>(position) > length_) return false;
+     off_ = position;
+     return true;
+   }
+ 
+-  virtual off_t Tell() const {
++  off_t Tell() const override {
+     return off_;
+   }
+ 
+  private:
+   void* const ptr_;
+   size_t length_;
+   off_t off_;
+ };
+@@ -55,17 +57,19 @@ class ExpandingMemoryStream : public OTS
+   ~ExpandingMemoryStream() {
+     delete[] static_cast<uint8_t*>(ptr_);
+   }
+ 
+   void* get() const {
+     return ptr_;
+   }
+ 
+-  bool WriteRaw(const void *data, size_t length) {
++  size_t size() override { return limit_; }
++
++  bool WriteRaw(const void *data, size_t length) override {
+     if ((off_ + length > length_) ||
+         (length > std::numeric_limits<size_t>::max() - off_)) {
+       if (length_ == limit_)
+         return false;
+       size_t new_length = (length_ + 1) * 2;
+       if (new_length < length_)
+         return false;
+       if (new_length > limit_)
+@@ -77,24 +81,24 @@ class ExpandingMemoryStream : public OTS
+       ptr_ = new_buf;
+       return WriteRaw(data, length);
+     }
+     std::memcpy(static_cast<char*>(ptr_) + off_, data, length);
+     off_ += length;
+     return true;
+   }
+ 
+-  bool Seek(off_t position) {
++  bool Seek(off_t position) override {
+     if (position < 0) return false;
+     if (static_cast<size_t>(position) > length_) return false;
+     off_ = position;
+     return true;
+   }
+ 
+-  off_t Tell() const {
++  off_t Tell() const override {
+     return off_;
+   }
+ 
+  private:
+   void* ptr_;
+   size_t length_;
+   const size_t limit_;
+   off_t off_;
+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
+@@ -667,16 +667,20 @@ bool ProcessGeneric(ots::FontFile *heade
+   }
+ 
+   // All decompressed tables decompressed must be <= OTS_MAX_DECOMPRESSED_FILE_SIZE.
+   if (uncompressed_sum > OTS_MAX_DECOMPRESSED_FILE_SIZE) {
+     return OTS_FAILURE_MSG_HDR("decompressed sum exceeds %gMB",
+                                OTS_MAX_DECOMPRESSED_FILE_SIZE / (1024.0 * 1024.0));        
+   }
+ 
++  if (uncompressed_sum > output->size()) {
++    return OTS_FAILURE_MSG_HDR("decompressed sum exceeds output size (%gMB)", output->size() / (1024.0 * 1024.0));
++  }
++
+   // check that the tables are not overlapping.
+   std::vector<std::pair<uint32_t, uint8_t> > overlap_checker;
+   for (unsigned i = 0; i < font->num_tables; ++i) {
+     overlap_checker.push_back(
+         std::make_pair(tables[i].offset, static_cast<uint8_t>(1) /* start */));
+     overlap_checker.push_back(
+         std::make_pair(tables[i].offset + tables[i].length,
+                        static_cast<uint8_t>(0) /* end */));
+diff --git a/gfx/ots/src/stat.cc b/gfx/ots/src/stat.cc
+--- a/gfx/ots/src/stat.cc
++++ b/gfx/ots/src/stat.cc
+@@ -6,30 +6,25 @@
+ #include "name.h"
+ 
+ namespace ots {
+ 
+ // -----------------------------------------------------------------------------
+ // OpenTypeSTAT
+ // -----------------------------------------------------------------------------
+ 
+-bool OpenTypeSTAT::ValidateNameId(uint16_t nameid, bool allowPredefined) {
++bool OpenTypeSTAT::ValidateNameId(uint16_t nameid) {
+   OpenTypeNAME* name = static_cast<OpenTypeNAME*>(
+       GetFont()->GetTypedTable(OTS_TAG_NAME));
+ 
+   if (!name || !name->IsValidNameId(nameid)) {
+     Drop("Invalid nameID: %d", nameid);
+     return false;
+   }
+ 
+-  if (!allowPredefined && nameid < 26) {
+-    Warning("nameID out of range: %d", nameid);
+-    return true;
+-  }
+-
+   if ((nameid >= 26 && nameid <= 255) || nameid >= 32768) {
+     Warning("nameID out of range: %d", nameid);
+     return true;
+   }
+ 
+   return  true;
+ }
+ 
+@@ -79,17 +74,17 @@ bool OpenTypeSTAT::Parse(const uint8_t* 
+     if (!table.ReadU32(&axis.axisTag) ||
+         !table.ReadU16(&axis.axisNameID) ||
+         !table.ReadU16(&axis.axisOrdering)) {
+       return Drop("Failed to read design axis");
+     }
+     if (!CheckTag(axis.axisTag)) {
+       return Drop("Bad design axis tag");
+     }
+-    if (!ValidateNameId(axis.axisNameID, false)) {
++    if (!ValidateNameId(axis.axisNameID)) {
+       return true;
+     }
+   }
+ 
+   // TODO
+   // - check that all axes defined in fvar are covered by STAT
+   // - check that axisOrdering values are not duplicated (warn only)
+ 
+diff --git a/gfx/ots/src/stat.h b/gfx/ots/src/stat.h
+--- a/gfx/ots/src/stat.h
++++ b/gfx/ots/src/stat.h
+@@ -19,17 +19,17 @@ class OpenTypeSTAT : public Table {
+  public:
+   explicit OpenTypeSTAT(Font* font, uint32_t tag)
+       : Table(font, tag, tag) { }
+ 
+   bool Parse(const uint8_t* data, size_t length);
+   bool Serialize(OTSStream* out);
+ 
+  private:
+-  bool ValidateNameId(uint16_t nameid, bool allowPredefined = true);
++  bool ValidateNameId(uint16_t nameid);
+ 
+   uint16_t majorVersion;
+   uint16_t minorVersion;
+   uint16_t designAxisSize;
+   uint16_t designAxisCount;
+   uint32_t designAxesOffset;
+   uint16_t axisValueCount;
+   uint32_t offsetToAxisValueOffsets;

+ 37 - 0
mozilla-release/patches/mozilla-central_635139.patch

@@ -0,0 +1,37 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1662826238 0
+#      Sat Sep 10 16:10:38 2022 +0000
+# Node ID 8225d0c430de3c37c47d4b05fb7daab4471843e7
+# Parent  1ca9f4524d416f7299124b90d9f5b2f0f90df02f
+Bug 1789403 - Cherry-pick https://github.com/khaledhosny/ots/pull/247/commits/2dc0c5eca9da5d430b8012028f69c0ffb65fada5 to fix build failure in ots/tests. r=RyanVM
+
+Depends on D156860
+
+Differential Revision: https://phabricator.services.mozilla.com/D156861
+
+diff --git a/gfx/ots/tests/layout_common_table_test.cc b/gfx/ots/tests/layout_common_table_test.cc
+--- a/gfx/ots/tests/layout_common_table_test.cc
++++ b/gfx/ots/tests/layout_common_table_test.cc
+@@ -231,19 +231,19 @@ bool BuildFakeDeviceTable(ots::OTSStream
+ class TestStream : public ots::MemoryStream {
+  public:
+   TestStream()
+       : ots::MemoryStream(data_, sizeof(data_)), size_(0) {
+     std::memset(reinterpret_cast<char*>(data_), 0, sizeof(data_));
+   }
+ 
+   uint8_t* data() { return data_; }
+-  size_t size() const { return size_; }
++  size_t size() override { return size_; }
+ 
+-  virtual bool WriteRaw(const void *d, size_t length) {
++  bool WriteRaw(const void *d, size_t length) override {
+     if (Tell() + length > size_) {
+       size_ = Tell() + length;
+     }
+     return ots::MemoryStream::WriteRaw(d, length);
+   }
+ 
+  private:
+   size_t size_;

+ 1649 - 0
mozilla-release/patches/mozilla-central_636122.patch

@@ -0,0 +1,1649 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1663873893 0
+#      Thu Sep 22 19:11:33 2022 +0000
+# Node ID 23c9a4c9b468b43106f50295a52422851d71dfda
+# Parent  99c31e1a7ac6fb56c83bf981621d206373bf6219
+Bug 1791017 - Update OTS to 9.0.0. r=RyanVM
+
+Differential Revision: https://phabricator.services.mozilla.com/D157474
+
+diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
+--- a/gfx/ots/README.mozilla
++++ b/gfx/ots/README.mozilla
+@@ -1,12 +1,12 @@
+ This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
+ 
+ Our reference repository is https://github.com/khaledhosny/ots/.
+ 
+-Current revision: dd99d9a451d070048b73b7c385c19c8dfb28c24d (8.2.2)
++Current revision: 35643038c4904538aa74c2c691f8d792efdbd6c0 (9.0.0)
+ 
+ Upstream files included: LICENSE, src/, include/, tests/*.cc
+ 
+ Additional files: README.mozilla, src/moz.build
+ 
+ Additional patch: ots-visibility.patch (bug 711079).
+ Additional patch: ots-lz4.patch
+diff --git a/gfx/ots/src/colr.cc b/gfx/ots/src/colr.cc
+new file mode 100644
+--- /dev/null
++++ b/gfx/ots/src/colr.cc
+@@ -0,0 +1,1092 @@
++// Copyright (c) 2022 The OTS Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "colr.h"
++
++#include "cpal.h"
++#include "maxp.h"
++#include "variations.h"
++
++#include <map>
++#include <set>
++#include <vector>
++
++// COLR - Color Table
++// http://www.microsoft.com/typography/otspec/colr.htm
++
++#define TABLE_NAME "COLR"
++
++namespace {
++
++// Some typedefs so that our local variables will more closely parallel the spec.
++typedef int16_t F2DOT14;  // 2.14 fixed-point
++typedef int32_t Fixed;    // 16.16 fixed-point
++typedef int16_t FWORD;    // A 16-bit integer value in font design units
++typedef uint16_t UFWORD;
++typedef uint32_t VarIdxBase;
++
++constexpr F2DOT14 F2DOT14_one = 0x4000;
++
++struct colrState
++{
++  // We track addresses of structs that we have already seen and checked,
++  // because fonts may share these among multiple glyph descriptions.
++  // (We only do this for color lines, which may be large, depending on the
++  // number of color stops, and for paints, which may refer to an extensive
++  // sub-graph; for small records like ClipBox and Affine2x3, we just read
++  // them directly whenever encountered.)
++  std::set<const uint8_t*> colorLines;
++  std::set<const uint8_t*> varColorLines;
++  std::set<const uint8_t*> paints;
++
++  std::map<uint16_t, std::pair<const uint8_t*, size_t>> baseGlyphMap;
++  std::vector<std::pair<const uint8_t*, size_t>> layerList;
++
++  // Set of visited records, for cycle detection.
++  // We don't actually need to track every paint table here, as most of the
++  // paint-offsets that create the graph can only point forwards;
++  // only PaintColrLayers and PaintColrGlyph can cause a backward jump
++  // and hence a potential cycle.
++  std::set<const uint8_t*> visited;
++
++  uint16_t numGlyphs;  // from maxp
++  uint16_t numPaletteEntries;  // from CPAL
++};
++
++// std::set<T>::contains isn't available until C++20, so we use this instead
++// for better compatibility with old compilers.
++template <typename T>
++bool setContains(const std::set<T>& set, T item)
++{
++  return set.find(item) != set.end();
++}
++
++enum Extend : uint8_t
++{
++  EXTEND_PAD     = 0,
++  EXTEND_REPEAT  = 1,
++  EXTEND_REFLECT = 2,
++};
++
++bool ParseColorLine(const ots::Font* font,
++                    const uint8_t* data, size_t length,
++                    colrState& state, bool var)
++{
++  auto& set = var ? state.varColorLines : state.colorLines;
++  if (setContains(set, data)) {
++    return true;
++  }
++  set.insert(data);
++
++  ots::Buffer subtable(data, length);
++
++  uint8_t extend;
++  uint16_t numColorStops;
++
++  if (!subtable.ReadU8(&extend) ||
++      !subtable.ReadU16(&numColorStops)) {
++    return OTS_FAILURE_MSG("Failed to read [Var]ColorLine");
++  }
++
++  if (extend != EXTEND_PAD && extend != EXTEND_REPEAT && extend != EXTEND_REFLECT) {
++    OTS_WARNING("Unknown color-line extend mode %u", extend);
++  }
++
++  for (auto i = 0u; i < numColorStops; ++i) {
++    F2DOT14 stopOffset;
++    uint16_t paletteIndex;
++    F2DOT14 alpha;
++    VarIdxBase varIndexBase;
++
++    if (!subtable.ReadS16(&stopOffset) ||
++        !subtable.ReadU16(&paletteIndex) ||
++        !subtable.ReadS16(&alpha) ||
++        (var && !subtable.ReadU32(&varIndexBase))) {
++      return OTS_FAILURE_MSG("Failed to read [Var]ColorStop");
++    }
++
++    if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) {
++      return OTS_FAILURE_MSG("Invalid palette index %u in color stop", paletteIndex);
++    }
++
++    if (alpha < 0 || alpha > F2DOT14_one) {
++      OTS_WARNING("Alpha value outside valid range 0.0 - 1.0");
++    }
++  }
++
++  return true;
++}
++
++// Composition modes
++enum CompositeMode : uint8_t
++{
++  // Porter-Duff modes
++  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators
++  COMPOSITE_CLEAR          =  0,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_clear
++  COMPOSITE_SRC            =  1,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_src
++  COMPOSITE_DEST           =  2,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dst
++  COMPOSITE_SRC_OVER       =  3,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcover
++  COMPOSITE_DEST_OVER      =  4,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstover
++  COMPOSITE_SRC_IN         =  5,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcin
++  COMPOSITE_DEST_IN        =  6,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstin
++  COMPOSITE_SRC_OUT        =  7,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcout
++  COMPOSITE_DEST_OUT       =  8,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstout
++  COMPOSITE_SRC_ATOP       =  9,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcatop
++  COMPOSITE_DEST_ATOP      = 10,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstatop
++  COMPOSITE_XOR            = 11,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_xor
++  COMPOSITE_PLUS           = 12,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_plus
++
++  // Blend modes
++  // https://www.w3.org/TR/compositing-1/#blending
++  COMPOSITE_SCREEN         = 13,  // https://www.w3.org/TR/compositing-1/#blendingscreen
++  COMPOSITE_OVERLAY        = 14,  // https://www.w3.org/TR/compositing-1/#blendingoverlay
++  COMPOSITE_DARKEN         = 15,  // https://www.w3.org/TR/compositing-1/#blendingdarken
++  COMPOSITE_LIGHTEN        = 16,  // https://www.w3.org/TR/compositing-1/#blendinglighten
++  COMPOSITE_COLOR_DODGE    = 17,  // https://www.w3.org/TR/compositing-1/#blendingcolordodge
++  COMPOSITE_COLOR_BURN     = 18,  // https://www.w3.org/TR/compositing-1/#blendingcolorburn
++  COMPOSITE_HARD_LIGHT     = 19,  // https://www.w3.org/TR/compositing-1/#blendinghardlight
++  COMPOSITE_SOFT_LIGHT     = 20,  // https://www.w3.org/TR/compositing-1/#blendingsoftlight
++  COMPOSITE_DIFFERENCE     = 21,  // https://www.w3.org/TR/compositing-1/#blendingdifference
++  COMPOSITE_EXCLUSION      = 22,  // https://www.w3.org/TR/compositing-1/#blendingexclusion
++  COMPOSITE_MULTIPLY       = 23,  // https://www.w3.org/TR/compositing-1/#blendingmultiply
++
++  // Modes that, uniquely, do not operate on components
++  // https://www.w3.org/TR/compositing-1/#blendingnonseparable
++  COMPOSITE_HSL_HUE        = 24,  // https://www.w3.org/TR/compositing-1/#blendinghue
++  COMPOSITE_HSL_SATURATION = 25,  // https://www.w3.org/TR/compositing-1/#blendingsaturation
++  COMPOSITE_HSL_COLOR      = 26,  // https://www.w3.org/TR/compositing-1/#blendingcolor
++  COMPOSITE_HSL_LUMINOSITY = 27,  // https://www.w3.org/TR/compositing-1/#blendingluminosity
++};
++
++bool ParseAffine(const ots::Font* font,
++                 const uint8_t* data, size_t length,
++                 bool var)
++{
++  ots::Buffer subtable(data, length);
++
++  Fixed xx, yx, xy, yy, dx, dy;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.ReadS32(&xx) ||
++      !subtable.ReadS32(&yx) ||
++      !subtable.ReadS32(&xy) ||
++      !subtable.ReadS32(&yy) ||
++      !subtable.ReadS32(&dx) ||
++      !subtable.ReadS32(&dy) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read [Var]Affine2x3");
++  }
++
++  return true;
++}
++
++// Paint-record dispatch function that reads the format byte and then dispatches
++// to one of the record-specific helpers.
++bool ParsePaint(const ots::Font* font, const uint8_t* data, size_t length, colrState& state);
++
++// All these paint record parsers start with Skip(1) to ignore the format field,
++// which the caller has already read in order to dispatch here.
++
++bool ParsePaintColrLayers(const ots::Font* font,
++                          const uint8_t* data, size_t length,
++                          colrState& state)
++{
++  if (setContains(state.visited, data)) {
++    return OTS_FAILURE_MSG("Cycle detected in PaintColrLayers");
++  }
++  state.visited.insert(data);
++
++  ots::Buffer subtable(data, length);
++
++  uint8_t numLayers;
++  uint32_t firstLayerIndex;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU8(&numLayers) ||
++      !subtable.ReadU32(&firstLayerIndex)) {
++    return OTS_FAILURE_MSG("Failed to read PaintColrLayers record");
++  }
++
++  if (uint64_t(firstLayerIndex) + numLayers > state.layerList.size()) {
++    return OTS_FAILURE_MSG("PaintColrLayers exceeds bounds of layer list");
++  }
++
++  for (auto i = firstLayerIndex; i < firstLayerIndex + numLayers; ++i) {
++    auto layer = state.layerList[i];
++    if (!ParsePaint(font, layer.first, layer.second, state)) {
++      return OTS_FAILURE_MSG("Failed to parse layer");
++    }
++  }
++
++  state.visited.erase(data);
++
++  return true;
++}
++
++bool ParsePaintSolid(const ots::Font* font,
++                     const uint8_t* data, size_t length,
++                     colrState& state, bool var)
++{
++  ots::Buffer subtable(data, length);
++
++  uint16_t paletteIndex;
++  F2DOT14 alpha;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU16(&paletteIndex) ||
++      !subtable.ReadS16(&alpha)) {
++    return OTS_FAILURE_MSG("Failed to read PaintSolid");
++  }
++
++  if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) {
++    return OTS_FAILURE_MSG("Invalid palette index %u PaintSolid", paletteIndex);
++  }
++
++  if (alpha < 0 || alpha > F2DOT14_one) {
++    OTS_WARNING("Alpha value outside valid range 0.0 - 1.0");
++  }
++
++  if (var) {
++    VarIdxBase varIndexBase;
++    if (!subtable.ReadU32(&varIndexBase)) {
++      return OTS_FAILURE_MSG("Failed to read PaintVarSolid");
++    }
++  }
++
++  return true;
++}
++
++bool ParsePaintLinearGradient(const ots::Font* font,
++                              const uint8_t* data, size_t length,
++                              colrState& state, bool var)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t colorLineOffset;
++  FWORD x0, y0, x1, y1, x2, y2;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&colorLineOffset) ||
++      !subtable.ReadS16(&x0) ||
++      !subtable.ReadS16(&y0) ||
++      !subtable.ReadS16(&x1) ||
++      !subtable.ReadS16(&y1) ||
++      !subtable.ReadS16(&x2) ||
++      !subtable.ReadS16(&y2) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]LinearGradient");
++  }
++
++  if (colorLineOffset >= length) {
++    return OTS_FAILURE_MSG("ColorLine is out of bounds");
++  }
++
++  if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) {
++    return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine");
++  }
++
++  return true;
++}
++
++bool ParsePaintRadialGradient(const ots::Font* font,
++                              const uint8_t* data, size_t length,
++                              colrState& state, bool var)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t colorLineOffset;
++  FWORD x0, y0;
++  UFWORD radius0;
++  FWORD x1, y1;
++  UFWORD radius1;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&colorLineOffset) ||
++      !subtable.ReadS16(&x0) ||
++      !subtable.ReadS16(&y0) ||
++      !subtable.ReadU16(&radius0) ||
++      !subtable.ReadS16(&x1) ||
++      !subtable.ReadS16(&y1) ||
++      !subtable.ReadU16(&radius1) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]RadialGradient");
++  }
++
++  if (colorLineOffset >= length) {
++    return OTS_FAILURE_MSG("ColorLine is out of bounds");
++  }
++
++  if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) {
++    return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine");
++  }
++
++  return true;
++}
++
++bool ParsePaintSweepGradient(const ots::Font* font,
++                             const uint8_t* data, size_t length,
++                             colrState& state, bool var)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t colorLineOffset;
++  FWORD centerX, centerY;
++  F2DOT14 startAngle, endAngle;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&colorLineOffset) ||
++      !subtable.ReadS16(&centerX) ||
++      !subtable.ReadS16(&centerY) ||
++      !subtable.ReadS16(&startAngle) ||
++      !subtable.ReadS16(&endAngle) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]SweepGradient");
++  }
++
++  if (colorLineOffset >= length) {
++    return OTS_FAILURE_MSG("ColorLine is out of bounds");
++  }
++
++  if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) {
++    return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine");
++  }
++
++  return true;
++}
++
++bool ParsePaintGlyph(const ots::Font* font,
++                     const uint8_t* data, size_t length,
++                     colrState& state)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t paintOffset;
++  uint16_t glyphID;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&paintOffset) ||
++      !subtable.ReadU16(&glyphID)) {
++    return OTS_FAILURE_MSG("Failed to read PaintGlyph");
++  }
++
++  if (!paintOffset || paintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid paint offset in PaintGlyph");
++  }
++
++  if (glyphID >= state.numGlyphs) {
++    return OTS_FAILURE_MSG("Glyph ID %u out of bounds", glyphID);
++  }
++
++  if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse paint for PaintGlyph");
++  }
++
++  return true;
++}
++
++bool ParsePaintColrGlyph(const ots::Font* font,
++                         const uint8_t* data, size_t length,
++                         colrState& state)
++{
++  if (setContains(state.visited, data)) {
++    return OTS_FAILURE_MSG("Cycle detected in PaintColrGlyph");
++  }
++  state.visited.insert(data);
++
++  ots::Buffer subtable(data, length);
++
++  uint16_t glyphID;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU16(&glyphID)) {
++    return OTS_FAILURE_MSG("Failed to read PaintColrGlyph");
++  }
++
++  auto baseGlyph = state.baseGlyphMap.find(glyphID);
++  if (baseGlyph == state.baseGlyphMap.end()) {
++    return OTS_FAILURE_MSG("Glyph ID %u not found in BaseGlyphList", glyphID);
++  }
++
++  if (!ParsePaint(font, baseGlyph->second.first, baseGlyph->second.second, state)) {
++    return OTS_FAILURE_MSG("Failed to parse referenced color glyph %u", glyphID);
++  }
++
++  state.visited.erase(data);
++
++  return true;
++}
++
++bool ParsePaintTransform(const ots::Font* font,
++                         const uint8_t* data, size_t length,
++                         colrState& state, bool var)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t paintOffset;
++  uint32_t transformOffset;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&paintOffset) ||
++      !subtable.ReadU24(&transformOffset)) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]Transform");
++  }
++
++  if (!paintOffset || paintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Transform");
++  }
++  if (transformOffset >= length) {
++    return OTS_FAILURE_MSG("Transform offset out of bounds");
++  }
++
++  if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Transform");
++  }
++
++  if (!ParseAffine(font, data + transformOffset, length - transformOffset, var)) {
++    return OTS_FAILURE_MSG("Failed to parse affine transform");
++  }
++
++  return true;
++}
++
++bool ParsePaintTranslate(const ots::Font* font,
++                         const uint8_t* data, size_t length,
++                         colrState& state, bool var)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t paintOffset;
++  FWORD dx, dy;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&paintOffset) ||
++      !subtable.ReadS16(&dx) ||
++      !subtable.ReadS16(&dy) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]Translate");
++  }
++
++  if (!paintOffset || paintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Translate");
++  }
++
++  if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Translate");
++  }
++
++  return true;
++}
++
++bool ParsePaintScale(const ots::Font* font,
++                     const uint8_t* data, size_t length,
++                     colrState& state,
++                     bool var, bool aroundCenter, bool uniform)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t paintOffset;
++  F2DOT14 scaleX, scaleY;
++  FWORD centerX, centerY;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&paintOffset) ||
++      !subtable.ReadS16(&scaleX) ||
++      (!uniform && !subtable.ReadS16(&scaleY)) ||
++      (aroundCenter && (!subtable.ReadS16(&centerX) ||
++                        !subtable.ReadS16(&centerY))) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]Scale[...]");
++  }
++
++  if (!paintOffset || paintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Scale[...]");
++  }
++
++  if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Scale[...]");
++  }
++
++  return true;
++}
++
++bool ParsePaintRotate(const ots::Font* font,
++                      const uint8_t* data, size_t length,
++                      colrState& state,
++                      bool var, bool aroundCenter)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t paintOffset;
++  F2DOT14 angle;
++  FWORD centerX, centerY;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&paintOffset) ||
++      !subtable.ReadS16(&angle) ||
++      (aroundCenter && (!subtable.ReadS16(&centerX) ||
++                        !subtable.ReadS16(&centerY))) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]Rotate[...]");
++  }
++
++  if (!paintOffset || paintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Rotate[...]");
++  }
++
++  if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Rotate[...]");
++  }
++
++  return true;
++}
++
++bool ParsePaintSkew(const ots::Font* font,
++                    const uint8_t* data, size_t length,
++                    colrState& state,
++                    bool var, bool aroundCenter)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t paintOffset;
++  F2DOT14 xSkewAngle, ySkewAngle;
++  FWORD centerX, centerY;
++  VarIdxBase varIndexBase;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&paintOffset) ||
++      !subtable.ReadS16(&xSkewAngle) ||
++      !subtable.ReadS16(&ySkewAngle) ||
++      (aroundCenter && (!subtable.ReadS16(&centerX) ||
++                        !subtable.ReadS16(&centerY))) ||
++      (var && !subtable.ReadU32(&varIndexBase))) {
++    return OTS_FAILURE_MSG("Failed to read Paint[Var]Skew[...]");
++  }
++
++  if (!paintOffset || paintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Skew[...]");
++  }
++
++  if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Skew[...]");
++  }
++
++  return true;
++}
++
++bool ParsePaintComposite(const ots::Font* font,
++                         const uint8_t* data, size_t length,
++                         colrState& state)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t sourcePaintOffset;
++  uint8_t compositeMode;
++  uint32_t backdropPaintOffset;
++
++  if (!subtable.Skip(1) ||
++      !subtable.ReadU24(&sourcePaintOffset) ||
++      !subtable.ReadU8(&compositeMode) ||
++      !subtable.ReadU24(&backdropPaintOffset)) {
++    return OTS_FAILURE_MSG("Failed to read PaintComposite");
++  }
++  if (compositeMode > COMPOSITE_HSL_LUMINOSITY) {
++    OTS_WARNING("Unknown composite mode %u\n", compositeMode);
++  }
++
++  if (!sourcePaintOffset || sourcePaintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid source paint offset");
++  }
++  if (!ParsePaint(font, data + sourcePaintOffset, length - sourcePaintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse source paint");
++  }
++
++  if (!backdropPaintOffset || backdropPaintOffset >= length) {
++    return OTS_FAILURE_MSG("Invalid backdrop paint offset");
++  }
++  if (!ParsePaint(font, data + backdropPaintOffset, length - backdropPaintOffset, state)) {
++    return OTS_FAILURE_MSG("Failed to parse backdrop paint");
++  }
++
++  return true;
++}
++
++bool ParsePaint(const ots::Font* font,
++                const uint8_t* data, size_t length,
++                colrState& state)
++{
++  if (setContains(state.paints, data)) {
++    return true;
++  }
++
++  ots::Buffer subtable(data, length);
++
++  uint8_t format;
++
++  if (!subtable.ReadU8(&format)) {
++    return OTS_FAILURE_MSG("Failed to read paint record format");
++  }
++
++  bool ok = true;
++  switch (format) {
++    case 1: ok = ParsePaintColrLayers(font, data, length, state); break;
++    case 2: ok = ParsePaintSolid(font, data, length, state, false); break;
++    case 3: ok = ParsePaintSolid(font, data, length, state, true); break;
++    case 4: ok = ParsePaintLinearGradient(font, data, length, state, false); break;
++    case 5: ok = ParsePaintLinearGradient(font, data, length, state, true); break;
++    case 6: ok = ParsePaintRadialGradient(font, data, length, state, false); break;
++    case 7: ok = ParsePaintRadialGradient(font, data, length, state, true); break;
++    case 8: ok = ParsePaintSweepGradient(font, data, length, state, false); break;
++    case 9: ok = ParsePaintSweepGradient(font, data, length, state, true); break;
++    case 10: ok = ParsePaintGlyph(font, data, length, state); break;
++    case 11: ok = ParsePaintColrGlyph(font, data, length, state); break;
++    case 12: ok = ParsePaintTransform(font, data, length, state, false); break;
++    case 13: ok = ParsePaintTransform(font, data, length, state, true); break;
++    case 14: ok = ParsePaintTranslate(font, data, length, state, false); break;
++    case 15: ok = ParsePaintTranslate(font, data, length, state, true); break;
++    case 16: ok = ParsePaintScale(font, data, length, state, false, false, false); break; // Scale
++    case 17: ok = ParsePaintScale(font, data, length, state, true, false, false); break; // VarScale
++    case 18: ok = ParsePaintScale(font, data, length, state, false, true, false); break; // ScaleAroundCenter
++    case 19: ok = ParsePaintScale(font, data, length, state, true, true, false); break; // VarScaleAroundCenter
++    case 20: ok = ParsePaintScale(font, data, length, state, false, false, true); break; // ScaleUniform
++    case 21: ok = ParsePaintScale(font, data, length, state, true, false, true); break; // VarScaleUniform
++    case 22: ok = ParsePaintScale(font, data, length, state, false, true, true); break; // ScaleUniformAroundCenter
++    case 23: ok = ParsePaintScale(font, data, length, state, true, true, true); break; // VarScaleUniformAroundCenter
++    case 24: ok = ParsePaintRotate(font, data, length, state, false, false); break; // Rotate
++    case 25: ok = ParsePaintRotate(font, data, length, state, true, false); break; // VarRotate
++    case 26: ok = ParsePaintRotate(font, data, length, state, false, true); break; // RotateAroundCenter
++    case 27: ok = ParsePaintRotate(font, data, length, state, true, true); break; // VarRotateAroundCenter
++    case 28: ok = ParsePaintSkew(font, data, length, state, false, false); break; // Skew
++    case 29: ok = ParsePaintSkew(font, data, length, state, true, false); break; // VarSkew
++    case 30: ok = ParsePaintSkew(font, data, length, state, false, true); break; // SkewAroundCenter
++    case 31: ok = ParsePaintSkew(font, data, length, state, true, true); break; // VarSkewAroundCenter
++    case 32: ok = ParsePaintComposite(font, data, length, state); break;
++    default:
++      // Clients are supposed to ignore unknown paint types.
++      OTS_WARNING("Unknown paint type %u", format);
++      break;
++  }
++
++  state.paints.insert(data);
++
++  return ok;
++}
++
++#pragma pack(1)
++struct COLRv1
++{
++  // Version-0 fields
++  uint16_t version;
++  uint16_t numBaseGlyphRecords;
++  uint32_t baseGlyphRecordsOffset;
++  uint32_t layerRecordsOffset;
++  uint16_t numLayerRecords;
++  // Version-1 additions
++  uint32_t baseGlyphListOffset;
++  uint32_t layerListOffset;
++  uint32_t clipListOffset;
++  uint32_t varIdxMapOffset;
++  uint32_t varStoreOffset;
++};
++#pragma pack()
++
++bool ParseBaseGlyphRecords(const ots::Font* font,
++                           const uint8_t* data, size_t length,
++                           uint32_t numBaseGlyphRecords,
++                           uint32_t numLayerRecords,
++                           colrState& state)
++{
++  ots::Buffer subtable(data, length);
++
++  int32_t prevGlyphID = -1;
++  for (auto i = 0u; i < numBaseGlyphRecords; ++i) {
++    uint16_t glyphID,
++             firstLayerIndex,
++             numLayers;
++
++    if (!subtable.ReadU16(&glyphID) ||
++        !subtable.ReadU16(&firstLayerIndex) ||
++        !subtable.ReadU16(&numLayers)) {
++      return OTS_FAILURE_MSG("Failed to read base glyph record");
++    }
++
++    if (glyphID >= int32_t(state.numGlyphs)) {
++      return OTS_FAILURE_MSG("Base glyph record glyph ID %u out of bounds", glyphID);
++    }
++
++    if (int32_t(glyphID) <= prevGlyphID) {
++      return OTS_FAILURE_MSG("Base glyph record for glyph ID %u out of order", glyphID);
++    }
++
++    if (uint32_t(firstLayerIndex) + uint32_t(numLayers) > numLayerRecords) {
++      return OTS_FAILURE_MSG("Layer index out of bounds");
++    }
++
++    prevGlyphID = glyphID;
++  }
++
++  return true;
++}
++
++bool ParseLayerRecords(const ots::Font* font,
++                       const uint8_t* data, size_t length,
++                       uint32_t numLayerRecords,
++                       colrState& state)
++{
++  ots::Buffer subtable(data, length);
++
++  for (auto i = 0u; i < numLayerRecords; ++i) {
++    uint16_t glyphID,
++             paletteIndex;
++
++    if (!subtable.ReadU16(&glyphID) ||
++        !subtable.ReadU16(&paletteIndex)) {
++      return OTS_FAILURE_MSG("Failed to read layer record");
++    }
++
++    if (glyphID >= int32_t(state.numGlyphs)) {
++      return OTS_FAILURE_MSG("Layer record glyph ID %u out of bounds", glyphID);
++    }
++
++    if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) {
++      return OTS_FAILURE_MSG("Invalid palette index %u in layer record", paletteIndex);
++    }
++  }
++
++  return true;
++}
++
++bool ParseBaseGlyphList(const ots::Font* font,
++                        const uint8_t* data, size_t length,
++                        colrState& state)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t numBaseGlyphPaintRecords;
++
++  if (!subtable.ReadU32(&numBaseGlyphPaintRecords)) {
++    return OTS_FAILURE_MSG("Failed to read base glyph list");
++  }
++
++  int32_t prevGlyphID = -1;
++  // We loop over the list twice, first to collect all the glyph IDs present,
++  // and then to check they can be parsed.
++  size_t saveOffset = subtable.offset();
++  for (auto i = 0u; i < numBaseGlyphPaintRecords; ++i) {
++    uint16_t glyphID;
++    uint32_t paintOffset;
++
++    if (!subtable.ReadU16(&glyphID) ||
++        !subtable.ReadU32(&paintOffset)) {
++      return OTS_FAILURE_MSG("Failed to read base glyph list");
++    }
++
++    if (glyphID >= int32_t(state.numGlyphs)) {
++      return OTS_FAILURE_MSG("Base glyph list glyph ID %u out of bounds", glyphID);
++    }
++
++    if (int32_t(glyphID) <= prevGlyphID) {
++      return OTS_FAILURE_MSG("Base glyph list record for glyph ID %u out of order", glyphID);
++    }
++
++    if (!paintOffset || paintOffset >= length) {
++      return OTS_FAILURE_MSG("Invalid paint offset for base glyph ID %u", glyphID);
++    }
++
++    // Record the base glyph list records so that we can follow them when processing
++    // PaintColrGlyph records.
++    state.baseGlyphMap[glyphID] = std::pair<const uint8_t*, size_t>(data + paintOffset, length - paintOffset);
++    prevGlyphID = glyphID;
++  }
++
++  subtable.set_offset(saveOffset);
++  for (auto i = 0u; i < numBaseGlyphPaintRecords; ++i) {
++    uint16_t glyphID;
++    uint32_t paintOffset;
++
++    if (!subtable.ReadU16(&glyphID) ||
++        !subtable.ReadU32(&paintOffset)) {
++      return OTS_FAILURE_MSG("Failed to read base glyph list");
++    }
++
++    if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) {
++      return OTS_FAILURE_MSG("Failed to parse paint for base glyph ID %u", glyphID);
++    }
++
++    // After each base glyph record is fully processed, the visited set should be clear;
++    // otherwise, we have a bug in the cycle-detection logic.
++    assert(state.visited.empty());
++  }
++
++  return true;
++}
++
++// We call this twice: first with parsePaints = false, to just get the number of layers,
++// and then with parsePaints = true to actually descend the paint graphs.
++bool ParseLayerList(const ots::Font* font,
++                    const uint8_t* data, size_t length,
++                    colrState& state)
++{
++  ots::Buffer subtable(data, length);
++
++  uint32_t numLayers;
++  if (!subtable.ReadU32(&numLayers)) {
++    return OTS_FAILURE_MSG("Failed to read layer list");
++  }
++
++  for (auto i = 0u; i < numLayers; ++i) {
++    uint32_t paintOffset;
++
++    if (!subtable.ReadU32(&paintOffset)) {
++      return OTS_FAILURE_MSG("Failed to read layer list");
++    }
++
++    if (!paintOffset || paintOffset >= length) {
++      return OTS_FAILURE_MSG("Invalid paint offset in layer list");
++    }
++
++    state.layerList.push_back(std::pair<const uint8_t*, size_t>(data + paintOffset, length - paintOffset));
++  }
++
++  return true;
++}
++
++bool ParseClipBox(const ots::Font* font,
++                  const uint8_t* data, size_t length)
++{
++  ots::Buffer subtable(data, length);
++
++  uint8_t format;
++  FWORD xMin, yMin, xMax, yMax;
++
++  if (!subtable.ReadU8(&format) ||
++      !subtable.ReadS16(&xMin) ||
++      !subtable.ReadS16(&yMin) ||
++      !subtable.ReadS16(&xMax) ||
++      !subtable.ReadS16(&yMax)) {
++    return OTS_FAILURE_MSG("Failed to read clip box");
++  }
++
++  switch (format) {
++    case 1:
++      break;
++    case 2: {
++      uint32_t varIndexBase;
++      if (!subtable.ReadU32(&varIndexBase)) {
++        return OTS_FAILURE_MSG("Failed to read clip box");
++      }
++      break;
++    }
++    default:
++      return OTS_FAILURE_MSG("Invalid clip box format: %u", format);
++  }
++
++  if (xMin > xMax || yMin > yMax) {
++    return OTS_FAILURE_MSG("Invalid clip box bounds");
++  }
++
++  return true;
++}
++
++bool ParseClipList(const ots::Font* font,
++                   const uint8_t* data, size_t length,
++                   colrState& state)
++{
++  ots::Buffer subtable(data, length);
++
++  uint8_t format;
++  uint32_t numClipRecords;
++
++  if (!subtable.ReadU8(&format) ||
++      !subtable.ReadU32(&numClipRecords)) {
++    return OTS_FAILURE_MSG("Failed to read clip list");
++  }
++
++  if (format != 1) {
++    return OTS_FAILURE_MSG("Unknown clip list format: %u", format);
++  }
++
++  int32_t prevEndGlyphID = -1;
++  for (auto i = 0u; i < numClipRecords; ++i) {
++    uint16_t startGlyphID,
++             endGlyphID;
++    uint32_t clipBoxOffset;
++
++    if (!subtable.ReadU16(&startGlyphID) ||
++        !subtable.ReadU16(&endGlyphID) ||
++        !subtable.ReadU24(&clipBoxOffset)) {
++      return OTS_FAILURE_MSG("Failed to read clip list");
++    }
++
++    if (int32_t(startGlyphID) <= prevEndGlyphID ||
++        endGlyphID < startGlyphID ||
++        endGlyphID >= int32_t(state.numGlyphs)) {
++      return OTS_FAILURE_MSG("Bad or out-of-order glyph ID range %u-%u in clip list", startGlyphID, endGlyphID);
++    }
++
++    if (clipBoxOffset >= length) {
++      return OTS_FAILURE_MSG("Clip box offset out of bounds for glyphs %u-%u", startGlyphID, endGlyphID);
++    }
++
++    if (!ParseClipBox(font, data + clipBoxOffset, length - clipBoxOffset)) {
++      return OTS_FAILURE_MSG("Failed to parse clip box for glyphs %u-%u", startGlyphID, endGlyphID);
++    }
++
++    prevEndGlyphID = endGlyphID;
++  }
++
++  return true;
++}
++
++}  // namespace
++
++namespace ots {
++
++bool OpenTypeCOLR::Parse(const uint8_t *data, size_t length) {
++  // Parsing COLR table requires |maxp->num_glyphs| and |cpal->num_palette_entries|.
++  Font *font = GetFont();
++  Buffer table(data, length);
++
++  uint32_t headerSize = offsetof(COLRv1, baseGlyphListOffset);
++
++  // Version 0 header fields.
++  uint16_t version = 0;
++  uint16_t numBaseGlyphRecords = 0;
++  uint32_t offsetBaseGlyphRecords = 0;
++  uint32_t offsetLayerRecords = 0;
++  uint16_t numLayerRecords = 0;
++  if (!table.ReadU16(&version) ||
++      !table.ReadU16(&numBaseGlyphRecords) ||
++      !table.ReadU32(&offsetBaseGlyphRecords) ||
++      !table.ReadU32(&offsetLayerRecords) ||
++      !table.ReadU16(&numLayerRecords)) {
++    return Error("Incomplete table");
++  }
++
++  if (version > 1) {
++    return Error("Unknown COLR table version %u", version);
++  }
++
++  // Additional header fields for Version 1.
++  uint32_t offsetBaseGlyphList = 0;
++  uint32_t offsetLayerList = 0;
++  uint32_t offsetClipList = 0;
++  uint32_t offsetVarIdxMap = 0;
++  uint32_t offsetItemVariationStore = 0;
++
++  if (version == 1) {
++    if (!table.ReadU32(&offsetBaseGlyphList) ||
++        !table.ReadU32(&offsetLayerList) ||
++        !table.ReadU32(&offsetClipList) ||
++        !table.ReadU32(&offsetVarIdxMap) ||
++        !table.ReadU32(&offsetItemVariationStore)) {
++      return Error("Incomplete v.1 table");
++    }
++    headerSize = sizeof(COLRv1);
++  }
++
++  colrState state;
++
++  auto* maxp = static_cast<ots::OpenTypeMAXP*>(font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return OTS_FAILURE_MSG("Required maxp table missing");
++  }
++  state.numGlyphs = maxp->num_glyphs;
++
++  auto *cpal = static_cast<ots::OpenTypeCPAL*>(font->GetTypedTable(OTS_TAG_CPAL));
++  if (!cpal) {
++    return OTS_FAILURE_MSG("Required cpal table missing");
++  }
++  state.numPaletteEntries = cpal->num_palette_entries;
++
++  if (numBaseGlyphRecords) {
++    if (offsetBaseGlyphRecords < headerSize || offsetBaseGlyphRecords >= length) {
++      return Error("Bad base glyph records offset in table header");
++    }
++    if (!ParseBaseGlyphRecords(font, data + offsetBaseGlyphRecords, length - offsetBaseGlyphRecords,
++                               numBaseGlyphRecords, numLayerRecords, state)) {
++      return Error("Failed to parse base glyph records");
++    }
++  }
++
++  if (numLayerRecords) {
++    if (offsetLayerRecords < headerSize || offsetLayerRecords >= length) {
++      return Error("Bad layer records offset in table header");
++    }
++    if (!ParseLayerRecords(font, data + offsetLayerRecords, length - offsetLayerRecords, numLayerRecords,
++                           state)) {
++      return Error("Failed to parse layer records");
++    }
++  }
++
++  if (offsetLayerList) {
++    if (offsetLayerList < headerSize || offsetLayerList >= length) {
++      return Error("Bad layer list offset in table header");
++    }
++    // This reads the layer list into our state.layerList vector, but does not parse the
++    // paint graphs within it; these will be checked when visited via PaintColrLayers.
++    if (!ParseLayerList(font, data + offsetLayerList, length - offsetLayerList, state)) {
++      return Error("Failed to parse layer list");
++    }
++  }
++
++  if (offsetBaseGlyphList) {
++    if (offsetBaseGlyphList < headerSize || offsetBaseGlyphList >= length) {
++      return Error("Bad base glyph list offset in table header");
++    }
++    // Here, we recursively check the paint graph starting at each base glyph record.
++    if (!ParseBaseGlyphList(font, data + offsetBaseGlyphList, length - offsetBaseGlyphList,
++                            state)) {
++      return Error("Failed to parse base glyph list");
++    }
++  }
++
++  if (offsetClipList) {
++    if (offsetClipList < headerSize || offsetClipList >= length) {
++      return Error("Bad clip list offset in table header");
++    }
++    if (!ParseClipList(font, data + offsetClipList, length - offsetClipList, state)) {
++      return Error("Failed to parse clip list");
++    }
++  }
++
++  if (offsetVarIdxMap) {
++    if (offsetVarIdxMap < headerSize || offsetVarIdxMap >= length) {
++      return Error("Bad delta set index offset in table header");
++    }
++    if (!ParseDeltaSetIndexMap(font, data + offsetVarIdxMap, length - offsetVarIdxMap)) {
++      return Error("Failed to parse delta set index map");
++    }
++  }
++
++  if (offsetItemVariationStore) {
++    if (offsetItemVariationStore < headerSize || offsetItemVariationStore >= length) {
++      return Error("Bad item variation store offset in table header");
++    }
++    if (!ParseItemVariationStore(font, data + offsetItemVariationStore, length - offsetItemVariationStore)) {
++      return Error("Failed to parse item variation store");
++    }
++  }
++
++  this->m_data = data;
++  this->m_length = length;
++  return true;
++}
++
++bool OpenTypeCOLR::Serialize(OTSStream *out) {
++  if (!out->Write(this->m_data, this->m_length)) {
++    return Error("Failed to write COLR table");
++  }
++
++  return true;
++}
++
++}  // namespace ots
++
++#undef TABLE_NAME
+diff --git a/gfx/ots/src/colr.h b/gfx/ots/src/colr.h
+new file mode 100644
+--- /dev/null
++++ b/gfx/ots/src/colr.h
+@@ -0,0 +1,31 @@
++// Copyright (c) 2022 The OTS Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef OTS_COLR_H_
++#define OTS_COLR_H_
++
++#include "ots.h"
++
++namespace ots {
++
++class OpenTypeCOLR : public Table {
++ public:
++  explicit OpenTypeCOLR(Font *font, uint32_t tag)
++      : Table(font, tag, tag),
++        m_data(NULL),
++        m_length(0) {
++  }
++
++  bool Parse(const uint8_t *data, size_t length);
++  bool Serialize(OTSStream *out);
++
++ private:
++  const uint8_t *m_data;
++  size_t m_length;
++};
++
++}  // namespace ots
++
++#endif  // OTS_COLR_H_
++
+diff --git a/gfx/ots/src/cpal.cc b/gfx/ots/src/cpal.cc
+new file mode 100644
+--- /dev/null
++++ b/gfx/ots/src/cpal.cc
+@@ -0,0 +1,283 @@
++// Copyright (c) 2022 The OTS Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "cpal.h"
++#include "name.h"
++
++// CPAL - Color Palette Table
++// http://www.microsoft.com/typography/otspec/cpal.htm
++
++#define TABLE_NAME "CPAL"
++
++namespace {
++
++// Caller has sized the colorRecords array, so we know how much to try and read.
++bool ParseColorRecordsArray(const ots::Font* font,
++                            const uint8_t* data, size_t length,
++                            std::vector<uint32_t>* colorRecords)
++{
++  ots::Buffer subtable(data, length);
++
++  for (auto& color : *colorRecords) {
++    if (!subtable.ReadU32(&color)) {
++      return OTS_FAILURE_MSG("Failed to read color record");
++    }
++  }
++
++  return true;
++}
++
++// Caller has sized the paletteTypes array, so we know how much to try and read.
++bool ParsePaletteTypesArray(const ots::Font* font,
++                            const uint8_t* data, size_t length,
++                            std::vector<uint32_t>* paletteTypes)
++{
++  ots::Buffer subtable(data, length);
++
++  constexpr uint32_t USABLE_WITH_LIGHT_BACKGROUND = 0x0001;
++  constexpr uint32_t USABLE_WITH_DARK_BACKGROUND = 0x0002;
++  constexpr uint32_t RESERVED = ~(USABLE_WITH_LIGHT_BACKGROUND | USABLE_WITH_DARK_BACKGROUND);
++
++  for (auto& type : *paletteTypes) {
++    if (!subtable.ReadU32(&type)) {
++      return OTS_FAILURE_MSG("Failed to read palette type");
++    }
++    if (type & RESERVED) {
++      // Should we treat this as failure? For now, just a warning; seems unlikely
++      // to be dangerous.
++      OTS_WARNING("Invalid (reserved) palette type flags %08x", type);
++      type &= ~RESERVED;
++    }
++  }
++
++  return true;
++}
++
++// Caller has sized the labels array, so we know how much to try and read.
++bool ParseLabelsArray(const ots::Font* font,
++                      const uint8_t* data, size_t length,
++                      std::vector<uint16_t>* labels,
++                      const char* labelType)
++{
++  ots::Buffer subtable(data, length);
++
++  auto* name = static_cast<ots::OpenTypeNAME*>(font->GetTypedTable(OTS_TAG_NAME));
++  if (!name) {
++    return OTS_FAILURE_MSG("Required name table missing");
++  }
++
++  for (auto& nameID : *labels) {
++    if (!subtable.ReadU16(&nameID)) {
++      return OTS_FAILURE_MSG("Failed to read %s label ID", labelType);
++    }
++    if (nameID != 0xffff) {
++      if (!name->IsValidNameId(nameID)) {
++        OTS_WARNING("Label ID %u for %s missing from name table", nameID, labelType);
++        nameID = 0xffff;
++      }
++    }
++  }
++
++  return true;
++}
++
++}  // namespace
++
++namespace ots {
++
++bool OpenTypeCPAL::Parse(const uint8_t *data, size_t length) {
++  Font *font = GetFont();
++  Buffer table(data, length);
++
++  // Header fields common to versions 0 and 1. These are recomputed
++  // from the array sizes during serialization.
++  uint16_t numPalettes;
++  uint16_t numColorRecords;
++  uint32_t colorRecordsArrayOffset;
++
++  if (!table.ReadU16(&this->version) ||
++      !table.ReadU16(&this->num_palette_entries) ||
++      !table.ReadU16(&numPalettes) ||
++      !table.ReadU16(&numColorRecords) ||
++      !table.ReadU32(&colorRecordsArrayOffset)) {
++    return Error("Failed to read CPAL table header");
++  }
++
++  if (this->version > 1) {
++    return Error("Unknown CPAL table version %u", this->version);
++  }
++
++  if (!this->num_palette_entries || !numPalettes || !numColorRecords) {
++    return Error("Empty CPAL is not valid");
++  }
++
++  if (this->num_palette_entries > numColorRecords) {
++    return Error("Not enough color records for a complete palette");
++  }
++
++  uint32_t headerSize = 4 * sizeof(uint16_t) + sizeof(uint32_t) +
++      numPalettes * sizeof(uint16_t);
++
++  // uint16_t colorRecordIndices[numPalettes]
++  this->colorRecordIndices.resize(numPalettes);
++  for (auto& colorRecordIndex : this->colorRecordIndices) {
++    if (!table.ReadU16(&colorRecordIndex)) {
++      return Error("Failed to read color record index");
++    }
++    if (colorRecordIndex > numColorRecords - this->num_palette_entries) {
++      return Error("Palette overflows color records array");
++    }
++  }
++
++  uint32_t paletteTypesArrayOffset = 0;
++  uint32_t paletteLabelsArrayOffset = 0;
++  uint32_t paletteEntryLabelsArrayOffset = 0;
++  if (this->version == 1) {
++    if (!table.ReadU32(&paletteTypesArrayOffset) ||
++        !table.ReadU32(&paletteLabelsArrayOffset) ||
++        !table.ReadU32(&paletteEntryLabelsArrayOffset)) {
++      return Error("Failed to read CPAL v.1 table header");
++    }
++    headerSize += 3 * sizeof(uint32_t);
++  }
++
++  // The following arrays may occur in any order, as they're independently referenced
++  // by offsets in the header.
++
++  if (colorRecordsArrayOffset < headerSize || colorRecordsArrayOffset >= length) {
++    return Error("Bad color records array offset in table header");
++  }
++  this->colorRecords.resize(numColorRecords);
++  if (!ParseColorRecordsArray(font, data + colorRecordsArrayOffset, length - colorRecordsArrayOffset,
++                              &this->colorRecords)) {
++    return Error("Failed to parse color records array");
++  }
++
++  if (paletteTypesArrayOffset) {
++    if (paletteTypesArrayOffset < headerSize || paletteTypesArrayOffset >= length) {
++      return Error("Bad palette types array offset in table header");
++    }
++    this->paletteTypes.resize(numPalettes);
++    if (!ParsePaletteTypesArray(font, data + paletteTypesArrayOffset, length - paletteTypesArrayOffset,
++                                &this->paletteTypes)) {
++      return Error("Failed to parse palette types array");
++    }
++  }
++
++  if (paletteLabelsArrayOffset) {
++    if (paletteLabelsArrayOffset < headerSize || paletteLabelsArrayOffset >= length) {
++      return Error("Bad palette labels array offset in table header");
++    }
++    this->paletteLabels.resize(numPalettes);
++    if (!ParseLabelsArray(font, data + paletteLabelsArrayOffset, length - paletteLabelsArrayOffset,
++                          &this->paletteLabels, "palette")) {
++      return Error("Failed to parse palette labels array");
++    }
++  }
++
++  if (paletteEntryLabelsArrayOffset) {
++    if (paletteEntryLabelsArrayOffset < headerSize || paletteEntryLabelsArrayOffset >= length) {
++      return Error("Bad palette entry labels array offset in table header");
++    }
++    this->paletteEntryLabels.resize(this->num_palette_entries);
++    if (!ParseLabelsArray(font, data + paletteEntryLabelsArrayOffset, length - paletteEntryLabelsArrayOffset,
++                          &this->paletteEntryLabels, "palette entry")) {
++      return Error("Failed to parse palette entry labels array");
++    }
++  }
++
++  return true;
++}
++
++bool OpenTypeCPAL::Serialize(OTSStream *out) {
++  uint16_t numPalettes = this->colorRecordIndices.size();
++  uint16_t numColorRecords = this->colorRecords.size();
++
++  off_t start = out->Tell();
++
++  size_t colorRecordsArrayOffset = 4 * sizeof(uint16_t) + sizeof(uint32_t) +
++      numPalettes * sizeof(uint16_t);
++  if (this->version == 1) {
++    colorRecordsArrayOffset += 3 * sizeof(uint32_t);
++  }
++
++  size_t totalLen = colorRecordsArrayOffset + numColorRecords * sizeof(uint32_t);
++
++  if (!out->WriteU16(this->version) ||
++      !out->WriteU16(this->num_palette_entries) ||
++      !out->WriteU16(numPalettes) ||
++      !out->WriteU16(numColorRecords) ||
++      !out->WriteU32(colorRecordsArrayOffset)) {
++    return Error("Failed to write CPAL header");
++  }
++
++  for (auto i : this->colorRecordIndices) {
++    if (!out->WriteU16(i)) {
++      return Error("Failed to write color record indices");
++    }
++  }
++
++  if (this->version == 1) {
++    size_t paletteTypesArrayOffset = 0;
++    if (!this->paletteTypes.empty()) {
++      assert(paletteTypes.size() == numPalettes);
++      paletteTypesArrayOffset = totalLen;
++      totalLen += numPalettes * sizeof(uint32_t);
++    }
++
++    size_t paletteLabelsArrayOffset = 0;
++    if (!this->paletteLabels.empty()) {
++      assert(paletteLabels.size() == numPalettes);
++      paletteLabelsArrayOffset = totalLen;
++      totalLen += numPalettes * sizeof(uint16_t);
++    }
++
++    size_t paletteEntryLabelsArrayOffset = 0;
++    if (!this->paletteEntryLabels.empty()) {
++      assert(paletteEntryLabels.size() == this->num_palette_entries);
++      paletteEntryLabelsArrayOffset = totalLen;
++      totalLen += this->num_palette_entries * sizeof(uint16_t);
++    }
++
++    if (!out->WriteU32(paletteTypesArrayOffset) ||
++        !out->WriteU32(paletteLabelsArrayOffset) ||
++        !out->WriteU32(paletteEntryLabelsArrayOffset)) {
++      return Error("Failed to write CPAL v.1 header");
++    }
++  }
++
++  for (auto i : this->colorRecords) {
++    if (!out->WriteU32(i)) {
++      return Error("Failed to write color records");
++    }
++  }
++
++  if (this->version == 1) {
++    for (auto i : this->paletteTypes) {
++      if (!out->WriteU32(i)) {
++        return Error("Failed to write palette types");
++      }
++    }
++
++    for (auto i : this->paletteLabels) {
++      if (!out->WriteU16(i)) {
++        return Error("Failed to write palette labels");
++      }
++    }
++
++    for (auto i : this->paletteEntryLabels) {
++      if (!out->WriteU16(i)) {
++        return Error("Failed to write palette entry labels");
++      }
++    }
++  }
++
++  assert(size_t(out->Tell() - start) == totalLen);
++
++  return true;
++}
++
++}  // namespace ots
++
++#undef TABLE_NAME
+diff --git a/gfx/ots/src/cpal.h b/gfx/ots/src/cpal.h
+new file mode 100644
+--- /dev/null
++++ b/gfx/ots/src/cpal.h
+@@ -0,0 +1,40 @@
++// Copyright (c) 2022 The OTS Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef OTS_CPAL_H_
++#define OTS_CPAL_H_
++
++#include "ots.h"
++
++#include <vector>
++
++namespace ots {
++
++class OpenTypeCPAL : public Table {
++ public:
++  explicit OpenTypeCPAL(Font *font, uint32_t tag)
++      : Table(font, tag, tag) {
++  }
++
++  bool Parse(const uint8_t *data, size_t length);
++  bool Serialize(OTSStream *out);
++
++  // This is public so that COLR can access it.
++  uint16_t num_palette_entries;
++
++ private:
++  uint16_t version;
++
++  std::vector<uint16_t> colorRecordIndices;
++  std::vector<uint32_t> colorRecords;
++
++  // Arrays present only if version == 1.
++  std::vector<uint32_t> paletteTypes;
++  std::vector<uint16_t> paletteLabels;
++  std::vector<uint16_t> paletteEntryLabels;
++};
++
++}  // namespace ots
++
++#endif  // OTS_CPAL_H_
+diff --git a/gfx/ots/src/moz.build b/gfx/ots/src/moz.build
+--- a/gfx/ots/src/moz.build
++++ b/gfx/ots/src/moz.build
+@@ -9,16 +9,18 @@ EXPORTS += [
+     '../include/ots-memory-stream.h',
+ ]
+ 
+ UNIFIED_SOURCES += [
+     'avar.cc',
+     'cff.cc',
+     'cff_charstring.cc',
+     'cmap.cc',
++    'colr.cc',
++    'cpal.cc',
+     'cvar.cc',
+     'cvt.cc',
+     'feat.cc',
+     'fpgm.cc',
+     'fvar.cc',
+     'gasp.cc',
+     'gdef.cc',
+     'glat.cc',
+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
+@@ -17,16 +17,18 @@
+ #include <woff2/decode.h>
+ 
+ // The OpenType Font File
+ // http://www.microsoft.com/typography/otspec/otff.htm
+ 
+ #include "avar.h"
+ #include "cff.h"
+ #include "cmap.h"
++#include "colr.h"
++#include "cpal.h"
+ #include "cvar.h"
+ #include "cvt.h"
+ #include "fpgm.h"
+ #include "fvar.h"
+ #include "gasp.h"
+ #include "gdef.h"
+ #include "glyf.h"
+ #include "gpos.h"
+@@ -139,16 +141,21 @@ const struct {
+   { OTS_TAG_AVAR, false },
+   { OTS_TAG_CVAR, false },
+   { OTS_TAG_GVAR, false },
+   { OTS_TAG_HVAR, false },
+   { OTS_TAG_MVAR, false },
+   { OTS_TAG_STAT, false },
+   { OTS_TAG_VVAR, false },
+   { OTS_TAG_CFF2, false },
++  // Color font tables.
++  // We need to parse CPAL before COLR so that the number of palette entries
++  // is known; and these tables follow fvar because COLR may use variations.
++  { OTS_TAG_CPAL, false },
++  { OTS_TAG_COLR, false },
+   // We need to parse GDEF table in advance of parsing GSUB/GPOS tables
+   // because they could refer GDEF table.
+   { OTS_TAG_GDEF, false },
+   { OTS_TAG_GPOS, false },
+   { OTS_TAG_GSUB, false },
+   { OTS_TAG_VHEA, false },
+   { OTS_TAG_VMTX, false },
+   { OTS_TAG_MATH, false },
+@@ -909,16 +916,18 @@ bool Font::ParseTable(const TableEntry& 
+   if (action == TABLE_ACTION_PASSTHRU) {
+     table = new TablePassthru(this, tag);
+   } else {
+     switch (tag) {
+       case OTS_TAG_AVAR: table = new OpenTypeAVAR(this, tag); break;
+       case OTS_TAG_CFF:  table = new OpenTypeCFF(this,  tag); break;
+       case OTS_TAG_CFF2: table = new OpenTypeCFF2(this, tag); break;
+       case OTS_TAG_CMAP: table = new OpenTypeCMAP(this, tag); break;
++      case OTS_TAG_COLR: table = new OpenTypeCOLR(this, tag); break;
++      case OTS_TAG_CPAL: table = new OpenTypeCPAL(this, tag); break;
+       case OTS_TAG_CVAR: table = new OpenTypeCVAR(this, tag); break;
+       case OTS_TAG_CVT:  table = new OpenTypeCVT(this,  tag); break;
+       case OTS_TAG_FPGM: table = new OpenTypeFPGM(this, tag); break;
+       case OTS_TAG_FVAR: table = new OpenTypeFVAR(this, tag); break;
+       case OTS_TAG_GASP: table = new OpenTypeGASP(this, tag); break;
+       case OTS_TAG_GDEF: table = new OpenTypeGDEF(this, tag); break;
+       case OTS_TAG_GLYF: table = new OpenTypeGLYF(this, tag); break;
+       case OTS_TAG_GPOS: table = new OpenTypeGPOS(this, tag); break;
+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
+@@ -185,16 +185,18 @@ template<typename T> T Round2(T value) {
+ }
+ 
+ // Check that a tag consists entirely of printable ASCII characters
+ bool CheckTag(uint32_t tag_value);
+ 
+ #define OTS_TAG_CFF  OTS_TAG('C','F','F',' ')
+ #define OTS_TAG_CFF2 OTS_TAG('C','F','F','2')
+ #define OTS_TAG_CMAP OTS_TAG('c','m','a','p')
++#define OTS_TAG_COLR OTS_TAG('C','O','L','R')
++#define OTS_TAG_CPAL OTS_TAG('C','P','A','L')
+ #define OTS_TAG_CVT  OTS_TAG('c','v','t',' ')
+ #define OTS_TAG_FEAT OTS_TAG('F','e','a','t')
+ #define OTS_TAG_FPGM OTS_TAG('f','p','g','m')
+ #define OTS_TAG_GASP OTS_TAG('g','a','s','p')
+ #define OTS_TAG_GDEF OTS_TAG('G','D','E','F')
+ #define OTS_TAG_GLAT OTS_TAG('G','l','a','t')
+ #define OTS_TAG_GLOC OTS_TAG('G','l','o','c')
+ #define OTS_TAG_GLYF OTS_TAG('g','l','y','f')
+diff --git a/gfx/ots/src/variations.cc b/gfx/ots/src/variations.cc
+--- a/gfx/ots/src/variations.cc
++++ b/gfx/ots/src/variations.cc
+@@ -62,32 +62,42 @@ bool ParseVariationRegionList(const ots:
+ 
+ bool
+ ParseVariationDataSubtable(const ots::Font* font, const uint8_t* data, const size_t length,
+                            const uint16_t regionCount,
+                            uint16_t* regionIndexCount) {
+   ots::Buffer subtable(data, length);
+ 
+   uint16_t itemCount;
+-  uint16_t shortDeltaCount;
++  uint16_t wordDeltaCount;
++
++  const uint16_t LONG_WORDS	= 0x8000u;
++  const uint16_t WORD_DELTA_COUNT_MASK = 0x7FFF;
+ 
+   if (!subtable.ReadU16(&itemCount) ||
+-      !subtable.ReadU16(&shortDeltaCount) ||
++      !subtable.ReadU16(&wordDeltaCount) ||
+       !subtable.ReadU16(regionIndexCount)) {
+     return OTS_FAILURE_MSG("Failed to read variation data subtable header");
+   }
+ 
++  size_t valueSize = (wordDeltaCount & LONG_WORDS) ? 2 : 1;
++  wordDeltaCount &= WORD_DELTA_COUNT_MASK;
++
++  if (wordDeltaCount > *regionIndexCount) {
++    return OTS_FAILURE_MSG("Bad word delta count");
++  }
++
+   for (unsigned i = 0; i < *regionIndexCount; i++) {
+     uint16_t regionIndex;
+     if (!subtable.ReadU16(&regionIndex) || regionIndex >= regionCount) {
+       return OTS_FAILURE_MSG("Bad region index");
+     }
+   }
+ 
+-  if (!subtable.Skip(size_t(itemCount) * (size_t(shortDeltaCount) + size_t(*regionIndexCount)))) {
++  if (!subtable.Skip(valueSize * size_t(itemCount) * (size_t(wordDeltaCount) + size_t(*regionIndexCount)))) {
+     return OTS_FAILURE_MSG("Failed to read delta data");
+   }
+ 
+   return true;
+ }
+ 
+ } // namespace
+ 

+ 256 - 0
mozilla-release/patches/mozilla-central_636497.patch

@@ -0,0 +1,256 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1664316411 0
+#      Tue Sep 27 22:06:51 2022 +0000
+# Node ID 431ae85fa1a6035d08683fb2864544f53c9a2cf1
+# Parent  e8a0cacdc4a5c41f55fc51d74d5ac79c3ffcb947
+Bug 1792214 - Apply OTS patch from https://github.com/khaledhosny/ots/pull/249 to work around Core Text and FreeType failures with variable COLR fonts that lack a gvar table. r=gfx-reviewers,jrmuizel
+
+Differential Revision: https://phabricator.services.mozilla.com/D158141
+
+diff --git a/gfx/ots/src/gvar.cc b/gfx/ots/src/gvar.cc
+--- a/gfx/ots/src/gvar.cc
++++ b/gfx/ots/src/gvar.cc
+@@ -2,16 +2,17 @@
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+ 
+ #include "gvar.h"
+ 
+ #include "fvar.h"
+ #include "maxp.h"
+ #include "variations.h"
++#include "ots-memory-stream.h"
+ 
+ #define TABLE_NAME "gvar"
+ 
+ namespace ots {
+ 
+ // -----------------------------------------------------------------------------
+ // OpenTypeGVAR
+ // -----------------------------------------------------------------------------
+@@ -140,16 +141,66 @@ bool OpenTypeGVAR::Parse(const uint8_t* 
+   }
+ 
+   this->m_data = data;
+   this->m_length = length;
+ 
+   return true;
+ }
+ 
++#ifdef OTS_SYNTHESIZE_MISSING_GVAR
++bool OpenTypeGVAR::InitEmpty() {
++  // Generate an empty but well-formed 'gvar' table for the font.
++  const ots::Font* font = GetFont();
++
++  OpenTypeFVAR* fvar = static_cast<OpenTypeFVAR*>(font->GetTypedTable(OTS_TAG_FVAR));
++  if (!fvar) {
++    return DropVariations("Required fvar table missing");
++  }
++
++  OpenTypeMAXP* maxp = static_cast<OpenTypeMAXP*>(font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return DropVariations("Required maxp table missing");
++  }
++
++  uint16_t majorVersion = 1;
++  uint16_t minorVersion = 0;
++  uint16_t axisCount = fvar->AxisCount();
++  uint16_t sharedTupleCount = 0;
++  uint32_t sharedTuplesOffset = 0;
++  uint16_t glyphCount = maxp->num_glyphs;
++  uint16_t flags = 0;
++  uint32_t glyphVariationDataArrayOffset = 0;
++
++  size_t length = 6 * sizeof(uint16_t) + 2 * sizeof(uint32_t)  // basic header fields
++      + (glyphCount + 1) * sizeof(uint16_t);  // glyphVariationDataOffsets[] array
++
++  uint8_t* data = new uint8_t[length];
++  MemoryStream stream(data, length);
++  if (!stream.WriteU16(majorVersion) ||
++      !stream.WriteU16(minorVersion) ||
++      !stream.WriteU16(axisCount) ||
++      !stream.WriteU16(sharedTupleCount) ||
++      !stream.WriteU32(sharedTuplesOffset) ||
++      !stream.WriteU16(glyphCount) ||
++      !stream.WriteU16(flags) ||
++      !stream.WriteU32(glyphVariationDataArrayOffset) ||
++      !stream.Pad((glyphCount + 1) * sizeof(uint16_t))) {
++    delete[] data;
++    return DropVariations("Failed to generate dummy gvar table");
++  }
++
++  this->m_data = data;
++  this->m_length = length;
++  this->m_ownsData = true;
++
++  return true;
++}
++#endif
++
+ bool OpenTypeGVAR::Serialize(OTSStream* out) {
+   if (!out->Write(this->m_data, this->m_length)) {
+     return Error("Failed to write gvar table");
+   }
+ 
+   return true;
+ }
+ 
+diff --git a/gfx/ots/src/gvar.h b/gfx/ots/src/gvar.h
+--- a/gfx/ots/src/gvar.h
++++ b/gfx/ots/src/gvar.h
+@@ -11,21 +11,33 @@ namespace ots {
+ 
+ // -----------------------------------------------------------------------------
+ // OpenTypeGVAR Interface
+ // -----------------------------------------------------------------------------
+ 
+ class OpenTypeGVAR : public Table {
+  public:
+   explicit OpenTypeGVAR(Font* font, uint32_t tag)
+-      : Table(font, tag, tag) { }
++      : Table(font, tag, tag), m_ownsData(false) { }
++
++  virtual ~OpenTypeGVAR() {
++    if (m_ownsData) {
++      delete[] m_data;
++    }
++  }
+ 
+   bool Parse(const uint8_t* data, size_t length);
+   bool Serialize(OTSStream* out);
+ 
++#ifdef OTS_SYNTHESIZE_MISSING_GVAR
++  bool InitEmpty();
++#endif
++
+  private:
+   const uint8_t *m_data;
+   size_t m_length;
++
++  bool m_ownsData;
+ };
+ 
+ }  // namespace ots
+ 
+ #endif  // OTS_GVAR_H_
+diff --git a/gfx/ots/src/moz.build b/gfx/ots/src/moz.build
+--- a/gfx/ots/src/moz.build
++++ b/gfx/ots/src/moz.build
+@@ -61,16 +61,17 @@ UNIFIED_SOURCES += [
+ AllowCompilerWarnings()
+ 
+ FINAL_LIBRARY = 'gkmedias'
+ 
+ DEFINES['PACKAGE_VERSION'] = '"moz"'
+ DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"'
+ DEFINES['OTS_GRAPHITE'] = 1
+ DEFINES['OTS_VARIATIONS'] = 1
++DEFINES['OTS_SYNTHESIZE_MISSING_GVAR'] = 1
+ 
+ USE_LIBS += [
+     'brotli',
+     'woff2',
+ ]
+ 
+ LOCAL_INCLUDES += [
+     '/modules/woff2/src',
+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
+@@ -728,16 +728,28 @@ bool ProcessGeneric(ots::FontFile *heade
+   for (const auto &table_entry : tables) {
+     if (!font->GetTable(table_entry.tag)) {
+       if (!font->ParseTable(table_entry, data, arena)) {
+         return OTS_FAILURE_MSG_TAG("Failed to parse table", table_entry.tag);
+       }
+     }
+   }
+ 
++#ifdef OTS_SYNTHESIZE_MISSING_GVAR
++  // If there was an fvar table but no gvar, synthesize an empty gvar to avoid
++  // issues with rasterizers (e.g. Core Text) that assume it must be present.
++  if (font->GetTable(OTS_TAG_FVAR) && !font->GetTable(OTS_TAG_GVAR)) {
++    ots::OpenTypeGVAR *gvar = new ots::OpenTypeGVAR(font, OTS_TAG_GVAR);
++    if (gvar->InitEmpty()) {
++      table_map[OTS_TAG_GVAR] = { OTS_TAG_GVAR, 0, 0, 0, 0 };
++      font->AddTable(gvar);
++    }
++  }
++#endif
++
+   ots::Table *glyf = font->GetTable(OTS_TAG_GLYF);
+   ots::Table *loca = font->GetTable(OTS_TAG_LOCA);
+   ots::Table *cff  = font->GetTable(OTS_TAG_CFF);
+   ots::Table *cff2 = font->GetTable(OTS_TAG_CFF2);
+ 
+   if (glyf && loca) {
+     if (font->version != 0x000010000) {
+       OTS_WARNING_MSG_HDR("wrong sfntVersion for glyph data");
+@@ -999,16 +1011,23 @@ Table* Font::GetTable(uint32_t tag) cons
+ 
+ Table* Font::GetTypedTable(uint32_t tag) const {
+   Table* t = GetTable(tag);
+   if (t && t->Type() == tag)
+     return t;
+   return NULL;
+ }
+ 
++void Font::AddTable(Table* table) {
++  // Attempting to add a duplicate table would be an error; this should only
++  // be used to add a table that does not already exist.
++  assert(m_tables.find(table->Tag()) == m_tables.end());
++  m_tables[table->Tag()] = table;
++}
++
+ void Font::DropGraphite() {
+   file->context->Message(0, "Dropping all Graphite tables");
+   for (const std::pair<uint32_t, Table*> entry : m_tables) {
+     if (entry.first == OTS_TAG_FEAT ||
+         entry.first == OTS_TAG_GLAT ||
+         entry.first == OTS_TAG_GLOC ||
+         entry.first == OTS_TAG_SILE ||
+         entry.first == OTS_TAG_SILF ||
+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
+@@ -258,16 +258,19 @@ class Table {
+ 
+   // Return the tag (table type) this Table was parsed as, to support
+   // "poor man's RTTI" so that we know if we can safely down-cast to
+   // a specific Table subclass. The m_type field is initialized to the
+   // appropriate tag when a subclass is constructed, or to zero for
+   // TablePassthru (indicating unparsed data).
+   uint32_t Type() { return m_type; }
+ 
++  // Return the tag assigned when this table was constructed.
++  uint32_t Tag() { return m_tag; }
++
+   Font* GetFont() { return m_font; }
+ 
+   bool Error(const char *format, ...);
+   bool Warning(const char *format, ...);
+   bool Drop(const char *format, ...);
+   bool DropGraphite(const char *format, ...);
+   bool DropVariations(const char *format, ...);
+ 
+@@ -310,16 +313,19 @@ struct Font {
+                   Arena &arena);
+   Table* GetTable(uint32_t tag) const;
+ 
+   // This checks that the returned Table is actually of the correct subclass
+   // for |tag|, so it can safely be downcast to the corresponding OpenTypeXXXX;
+   // if not (i.e. if the table was treated as Passthru), it will return NULL.
+   Table* GetTypedTable(uint32_t tag) const;
+ 
++  // Insert a new table. Asserts if a table with the same tag already exists.
++  void AddTable(Table* table);
++
+   // Drop all Graphite tables and don't parse new ones.
+   void DropGraphite();
+ 
+   // Drop all Variations tables and don't parse new ones.
+   void DropVariations();
+ 
+   FontFile *file;
+ 

+ 136 - 0
mozilla-release/patches/mozilla-central_637080.patch

@@ -0,0 +1,136 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1665050726 0
+#      Thu Oct 06 10:05:26 2022 +0000
+# Node ID ddbd657d52dd29e05d9c2d2d1cef56679e257935
+# Parent  119144758463ba3a87fa23a220a573e5d4bad7f9
+Bug 1761233 - Apply VDMX sanitization fix from https://github.com/khaledhosny/ots/pull/250 to avoid generating invalid "sanitized" data. r=gfx-reviewers,lsalzman
+
+With this fix, the site from comment 21 loads successfully.
+
+Differential Revision: https://phabricator.services.mozilla.com/D158712
+
+diff --git a/gfx/ots/src/vdmx.cc b/gfx/ots/src/vdmx.cc
+--- a/gfx/ots/src/vdmx.cc
++++ b/gfx/ots/src/vdmx.cc
+@@ -1,41 +1,46 @@
+ // Copyright (c) 2009-2017 The OTS Authors. All rights reserved.
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+ 
+ #include "vdmx.h"
+ 
++#include <set>
++
+ // VDMX - Vertical Device Metrics
+ // http://www.microsoft.com/typography/otspec/vdmx.htm
+ 
+ namespace ots {
+ 
++#define TABLE_NAME "VDMX"
++
+ bool OpenTypeVDMX::Parse(const uint8_t *data, size_t length) {
+   Buffer table(data, length);
++  ots::Font* font = this->GetFont();
+ 
+   if (!table.ReadU16(&this->version) ||
+       !table.ReadU16(&this->num_recs) ||
+       !table.ReadU16(&this->num_ratios)) {
+-    return Error("Failed to read table header");
++    return Drop("Failed to read table header");
+   }
+ 
+   if (this->version > 1) {
+     return Drop("Unsupported table version: %u", this->version);
+   }
+ 
+   this->rat_ranges.reserve(this->num_ratios);
+   for (unsigned i = 0; i < this->num_ratios; ++i) {
+     OpenTypeVDMXRatioRecord rec;
+ 
+     if (!table.ReadU8(&rec.charset) ||
+         !table.ReadU8(&rec.x_ratio) ||
+         !table.ReadU8(&rec.y_start_ratio) ||
+         !table.ReadU8(&rec.y_end_ratio)) {
+-      return Error("Failed to read RatioRange record %d", i);
++      return Drop("Failed to read RatioRange record %d", i);
+     }
+ 
+     if (rec.charset > 1) {
+       return Drop("Unsupported character set: %u", rec.charset);
+     }
+ 
+     if (rec.y_start_ratio > rec.y_end_ratio) {
+       return Drop("Bad y ratio");
+@@ -51,44 +56,55 @@ bool OpenTypeVDMX::Parse(const uint8_t *
+       return Drop("Superfluous terminator found");
+     }
+ 
+     this->rat_ranges.push_back(rec);
+   }
+ 
+   this->offsets.reserve(this->num_ratios);
+   const size_t current_offset = table.offset();
++  std::set<uint16_t> unique_offsets;
+   // current_offset is less than (2 bytes * 3) + (4 bytes * USHRT_MAX) = 256k.
+   for (unsigned i = 0; i < this->num_ratios; ++i) {
+     uint16_t offset;
+     if (!table.ReadU16(&offset)) {
+-      return Error("Failed to read ratio offset %d", i);
++      return Drop("Failed to read ratio offset %d", i);
+     }
+     if (current_offset + offset >= length) {  // thus doesn't overflow.
+-      return Error("Bad ratio offset %d for ration %d", offset, i);
++      return Drop("Bad ratio offset %d for ration %d", offset, i);
+     }
+ 
+     this->offsets.push_back(offset);
++    unique_offsets.insert(offset);
++  }
++
++  // Check that num_recs is sufficient to provide as many VDMXGroup records
++  // as there are unique offsets; if not, update it (we'll return an error
++  // below if they're not actually present).
++  if (unique_offsets.size() > this->num_recs) {
++    OTS_WARNING("increasing num_recs (%u is too small for %u unique offsets)",
++                this->num_recs, unique_offsets.size());
++    this->num_recs = unique_offsets.size();
+   }
+ 
+   this->groups.reserve(this->num_recs);
+   for (unsigned i = 0; i < this->num_recs; ++i) {
+     OpenTypeVDMXGroup group;
+     if (!table.ReadU16(&group.recs) ||
+         !table.ReadU8(&group.startsz) ||
+         !table.ReadU8(&group.endsz)) {
+-      return Error("Failed to read record header %d", i);
++      return Drop("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 Error("Failed to read reacord %d group %d", i, j);
++        return Drop("Failed to read record %d group %d", i, j);
+       }
+       if (vt.y_max < vt.y_min) {
+         return Drop("bad y min/max");
+       }
+ 
+       // This table must appear in sorted order (sorted by yPelHeight),
+       // but need not be continuous.
+       if ((j != 0) && (group.entries[j - 1].y_pel_height >= vt.y_pel_height)) {
+@@ -147,9 +163,11 @@ bool OpenTypeVDMX::Serialize(OTSStream *
+         return Error("Failed to write group %d entry %d", i, j);
+       }
+     }
+   }
+ 
+   return true;
+ }
+ 
++#undef TABLE_NAME
++
+ }  // namespace ots

+ 807 - 0
mozilla-release/patches/mozilla-central_645670.patch

@@ -0,0 +1,807 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1670857447 0
+#      Mon Dec 12 15:04:07 2022 +0000
+# Node ID 10bc76edb5fb1f3f012cb1be33e8b52986e5a2a7
+# Parent  3e815af522af15c2069c3dcdeff415ee4fbc77c7
+Bug 1466443 - Update WOFF2 library to upstream tip, to pick up overlapSimpleBitmap support. r=gfx-reviewers,lsalzman
+
+Differential Revision: https://phabricator.services.mozilla.com/D164324
+
+diff --git a/modules/woff2/README.mozilla b/modules/woff2/README.mozilla
+--- a/modules/woff2/README.mozilla
++++ b/modules/woff2/README.mozilla
+@@ -6,9 +6,9 @@ Upstream code can be viewed at
+ 
+ and cloned by
+   git clone https://github.com/google/woff2
+ 
+ The in-tree copy is updated by running
+   sh update.sh
+ from within the modules/woff2 directory.
+ 
+-Current version: [commit 1bccf208bca986e53a647dfe4811322adb06ecf8].
++Current version: [commit 4721483ad780ee2b63cb787bfee4aa64b61a0446].
+diff --git a/modules/woff2/include/woff2/encode.h b/modules/woff2/include/woff2/encode.h
+--- a/modules/woff2/include/woff2/encode.h
++++ b/modules/woff2/include/woff2/encode.h
+@@ -21,18 +21,18 @@ struct WOFF2Params {
+ 
+   std::string extended_metadata;
+   int brotli_quality;
+   bool allow_transforms;
+ };
+ 
+ // Returns an upper bound on the size of the compressed file.
+ size_t MaxWOFF2CompressedSize(const uint8_t* data, size_t length);
+-size_t MaxWOFF2CompressedSize(const uint8_t* data, size_t length,
+-                              const std::string& extended_metadata);
++size_t MaxWOFF2CompressedSize(const uint8_t *data, size_t length,
++                              const std::string &extended_metadata);
+ 
+ // Compresses the font into the target buffer. *result_length should be at least
+ // the value returned by MaxWOFF2CompressedSize(), upon return, it is set to the
+ // actual compressed size. Returns true on successful compression.
+ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length,
+                        uint8_t *result, size_t *result_length);
+ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length,
+                        uint8_t *result, size_t *result_length,
+diff --git a/modules/woff2/include/woff2/output.h b/modules/woff2/include/woff2/output.h
+--- a/modules/woff2/include/woff2/output.h
++++ b/modules/woff2/include/woff2/output.h
+@@ -46,25 +46,25 @@ class WOFF2Out {
+ /**
+  * Expanding memory block for woff2 out. By default limited to kDefaultMaxSize.
+  */
+ class WOFF2StringOut : public WOFF2Out {
+  public:
+   // Create a writer that writes its data to buf.
+   // buf->size() will grow to at most max_size
+   // buf may be sized (e.g. using EstimateWOFF2FinalSize) or empty.
+-  explicit WOFF2StringOut(std::string* buf);
++  explicit WOFF2StringOut(std::string *buf);
+ 
+   bool Write(const void *buf, size_t n) override;
+   bool Write(const void *buf, size_t offset, size_t n) override;
+   size_t Size() override { return offset_; }
+   size_t MaxSize() { return max_size_; }
+   void SetMaxSize(size_t max_size);
+  private:
+-  std::string* buf_;
++  std::string *buf_;
+   size_t max_size_;
+   size_t offset_;
+ };
+ 
+ /**
+  * Fixed memory block for woff2 out.
+  */
+ class WOFF2MemoryOut : public WOFF2Out {
+diff --git a/modules/woff2/src/file.h b/modules/woff2/src/file.h
+--- a/modules/woff2/src/file.h
++++ b/modules/woff2/src/file.h
+@@ -9,26 +9,22 @@
+ #ifndef WOFF2_FILE_H_
+ #define WOFF2_FILE_H_
+ 
+ #include <fstream>
+ #include <iterator>
+ 
+ namespace woff2 {
+ 
+-using std::string;
+-
+-
+-inline string GetFileContent(string filename) {
++inline std::string GetFileContent(std::string filename) {
+   std::ifstream ifs(filename.c_str(), std::ios::binary);
+-  return string(
+-    std::istreambuf_iterator<char>(ifs.rdbuf()),
+-    std::istreambuf_iterator<char>());
++  return std::string(std::istreambuf_iterator<char>(ifs.rdbuf()),
++                     std::istreambuf_iterator<char>());
+ }
+ 
+-inline void SetFileContents(string filename, string::iterator start,
+-    string::iterator end) {
++inline void SetFileContents(std::string filename, std::string::iterator start,
++                            std::string::iterator end) {
+   std::ofstream ofs(filename.c_str(), std::ios::binary);
+   std::copy(start, end, std::ostream_iterator<char>(ofs));
+ }
+ 
+ } // namespace woff2
+ #endif  // WOFF2_FILE_H_
+diff --git a/modules/woff2/src/glyph.cc b/modules/woff2/src/glyph.cc
+--- a/modules/woff2/src/glyph.cc
++++ b/modules/woff2/src/glyph.cc
+@@ -16,16 +16,17 @@
+ namespace woff2 {
+ 
+ static const int32_t kFLAG_ONCURVE = 1;
+ static const int32_t kFLAG_XSHORT = 1 << 1;
+ static const int32_t kFLAG_YSHORT = 1 << 2;
+ static const int32_t kFLAG_REPEAT = 1 << 3;
+ static const int32_t kFLAG_XREPEATSIGN = 1 << 4;
+ static const int32_t kFLAG_YREPEATSIGN = 1 << 5;
++static const int32_t kFLAG_OVERLAP_SIMPLE = 1 << 6;
+ static const int32_t kFLAG_ARG_1_AND_2_ARE_WORDS = 1 << 0;
+ static const int32_t kFLAG_WE_HAVE_A_SCALE = 1 << 3;
+ static const int32_t kFLAG_MORE_COMPONENTS = 1 << 5;
+ static const int32_t kFLAG_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
+ static const int32_t kFLAG_WE_HAVE_A_TWO_BY_TWO = 1 << 7;
+ static const int32_t kFLAG_WE_HAVE_INSTRUCTIONS = 1 << 8;
+ 
+ bool ReadCompositeGlyphData(Buffer* buffer, Glyph* glyph) {
+@@ -129,16 +130,20 @@ bool ReadGlyph(const uint8_t* data, size
+             flag_repeat--;
+           }
+           flags[i][j] = flag;
+           glyph->contours[i][j].on_curve = flag & kFLAG_ONCURVE;
+         }
+       }
+     }
+ 
++    if (!flags.empty() && !flags[0].empty()) {
++      glyph->overlap_simple_flag_set = (flags[0][0] & kFLAG_OVERLAP_SIMPLE);
++    }
++
+     // Read the x coordinates.
+     int prev_x = 0;
+     for (int i = 0; i < num_contours; ++i) {
+       for (size_t j = 0; j < glyph->contours[i].size(); ++j) {
+         uint8_t flag = flags[i][j];
+         if (flag & kFLAG_XSHORT) {
+           // single byte x-delta coord value
+           uint8_t x_delta;
+@@ -234,27 +239,31 @@ bool StoreEndPtsOfContours(const Glyph& 
+     }
+     Store16(end_point, offset, dst);
+   }
+   return true;
+ }
+ 
+ bool StorePoints(const Glyph& glyph, size_t* offset,
+                  uint8_t* dst, size_t dst_size) {
+-  int last_flag = -1;
++  int previous_flag = -1;
+   int repeat_count = 0;
+   int last_x = 0;
+   int last_y = 0;
+   size_t x_bytes = 0;
+   size_t y_bytes = 0;
+ 
+   // Store the flags and calculate the total size of the x and y coordinates.
+   for (const auto& contour : glyph.contours) {
+     for (const auto& point : contour) {
+       int flag = point.on_curve ? kFLAG_ONCURVE : 0;
++      if (previous_flag == -1 && glyph.overlap_simple_flag_set) {
++        // First flag needs to have overlap simple bit set.
++        flag = flag | kFLAG_OVERLAP_SIMPLE;
++      }
+       int dx = point.x - last_x;
+       int dy = point.y - last_y;
+       if (dx == 0) {
+         flag |= kFLAG_XREPEATSIGN;
+       } else if (dx > -256 && dx < 256) {
+         flag |= kFLAG_XSHORT | (dx > 0 ? kFLAG_XREPEATSIGN : 0);
+         x_bytes += 1;
+       } else {
+@@ -263,17 +272,17 @@ bool StorePoints(const Glyph& glyph, siz
+       if (dy == 0) {
+         flag |= kFLAG_YREPEATSIGN;
+       } else if (dy > -256 && dy < 256) {
+         flag |= kFLAG_YSHORT | (dy > 0 ? kFLAG_YREPEATSIGN : 0);
+         y_bytes += 1;
+       } else {
+         y_bytes += 2;
+       }
+-      if (flag == last_flag && repeat_count != 255) {
++      if (flag == previous_flag && repeat_count != 255) {
+         dst[*offset - 1] |= kFLAG_REPEAT;
+         repeat_count++;
+       } else {
+         if (repeat_count != 0) {
+           if (*offset >= dst_size) {
+             return FONT_COMPRESSION_FAILURE();
+           }
+           dst[(*offset)++] = repeat_count;
+@@ -281,17 +290,17 @@ bool StorePoints(const Glyph& glyph, siz
+         if (*offset >= dst_size) {
+           return FONT_COMPRESSION_FAILURE();
+         }
+         dst[(*offset)++] = flag;
+         repeat_count = 0;
+       }
+       last_x = point.x;
+       last_y = point.y;
+-      last_flag = flag;
++      previous_flag = flag;
+     }
+   }
+   if (repeat_count != 0) {
+     if (*offset >= dst_size) {
+       return FONT_COMPRESSION_FAILURE();
+     }
+     dst[(*offset)++] = repeat_count;
+   }
+diff --git a/modules/woff2/src/glyph.h b/modules/woff2/src/glyph.h
+--- a/modules/woff2/src/glyph.h
++++ b/modules/woff2/src/glyph.h
+@@ -5,40 +5,48 @@
+ */
+ 
+ /* Data model and I/O for glyph data within sfnt format files for the purpose of
+    performing the preprocessing step of the WOFF 2.0 conversion. */
+ 
+ #ifndef WOFF2_GLYPH_H_
+ #define WOFF2_GLYPH_H_
+ 
++#include <inttypes.h>
+ #include <stddef.h>
+-#include <inttypes.h>
++
++#include <cstdint>
+ #include <vector>
+ 
+ namespace woff2 {
+ 
+ // Represents a parsed simple or composite glyph. The composite glyph data and
+ // instructions are un-parsed and we keep only pointers to the raw data,
+ // therefore the glyph is valid only so long the data from which it was parsed
+ // is around.
+ class Glyph {
+  public:
+-  Glyph() : instructions_size(0), composite_data_size(0) {}
++  Glyph()
++      : instructions_size(0),
++        overlap_simple_flag_set(false),
++        composite_data_size(0) {}
+ 
+   // Bounding box.
+   int16_t x_min;
+   int16_t x_max;
+   int16_t y_min;
+   int16_t y_max;
+ 
+   // Instructions.
+   uint16_t instructions_size;
+   const uint8_t* instructions_data;
+ 
++  // Flags.
++  bool overlap_simple_flag_set;
++
+   // Data model for simple glyphs.
+   struct Point {
+     int x;
+     int y;
+     bool on_curve;
+   };
+   std::vector<std::vector<Point> > contours;
+ 
+diff --git a/modules/woff2/src/transform.cc b/modules/woff2/src/transform.cc
+--- a/modules/woff2/src/transform.cc
++++ b/modules/woff2/src/transform.cc
+@@ -17,16 +17,17 @@
+ #include "./variable_length.h"
+ 
+ namespace woff2 {
+ 
+ namespace {
+ 
+ const int FLAG_ARG_1_AND_2_ARE_WORDS = 1 << 0;
+ const int FLAG_WE_HAVE_INSTRUCTIONS = 1 << 8;
++const int FLAG_OVERLAP_SIMPLE_BITMAP = 1 << 0;
+ 
+ void WriteBytes(std::vector<uint8_t>* out, const uint8_t* data, size_t len) {
+   if (len == 0) return;
+   size_t offset = out->size();
+   out->resize(offset + len);
+   memcpy(&(*out)[offset], data, len);
+ }
+ 
+@@ -64,17 +65,20 @@ class GlyfEncoder {
+       WriteSimpleGlyph(glyph_id, glyph);
+     } else {
+       WriteUShort(&n_contour_stream_, 0);
+     }
+     return true;
+   }
+ 
+   void GetTransformedGlyfBytes(std::vector<uint8_t>* result) {
+-    WriteLong(result, 0);  // version
++    WriteUShort(result, 0);  // Version
++    WriteUShort(result, overlap_bitmap_.empty()
++                            ? 0x00
++                            : FLAG_OVERLAP_SIMPLE_BITMAP);  // Flags
+     WriteUShort(result, n_glyphs_);
+     WriteUShort(result, 0);  // index_format, will be set later
+     WriteLong(result, n_contour_stream_.size());
+     WriteLong(result, n_points_stream_.size());
+     WriteLong(result, flag_byte_stream_.size());
+     WriteLong(result, glyph_stream_.size());
+     WriteLong(result, composite_stream_.size());
+     WriteLong(result, bbox_bitmap_.size() + bbox_stream_.size());
+@@ -82,16 +86,19 @@ class GlyfEncoder {
+     WriteBytes(result, n_contour_stream_);
+     WriteBytes(result, n_points_stream_);
+     WriteBytes(result, flag_byte_stream_);
+     WriteBytes(result, glyph_stream_);
+     WriteBytes(result, composite_stream_);
+     WriteBytes(result, bbox_bitmap_);
+     WriteBytes(result, bbox_stream_);
+     WriteBytes(result, instruction_stream_);
++    if (!overlap_bitmap_.empty()) {
++      WriteBytes(result, overlap_bitmap_);
++    }
+   }
+ 
+  private:
+   void WriteInstructions(const Glyph& glyph) {
+     Write255UShort(&glyph_stream_, glyph.instructions_size);
+     WriteBytes(&instruction_stream_,
+                glyph.instructions_data, glyph.instructions_size);
+   }
+@@ -122,16 +129,20 @@ class GlyfEncoder {
+       return true;
+     if (glyph.y_max != y_max)
+       return true;
+ 
+     return false;
+   }
+ 
+   void WriteSimpleGlyph(int glyph_id, const Glyph& glyph) {
++    if (glyph.overlap_simple_flag_set) {
++      EnsureOverlapBitmap();
++      overlap_bitmap_[glyph_id >> 3] |= 0x80 >> (glyph_id & 7);
++    }
+     int num_contours = glyph.contours.size();
+     WriteUShort(&n_contour_stream_, num_contours);
+     if (ShouldWriteSimpleGlyphBbox(glyph)) {
+       WriteBbox(glyph_id, glyph);
+     }
+     for (int i = 0; i < num_contours; i++) {
+       Write255UShort(&n_points_stream_, glyph.contours[i].size());
+     }
+@@ -209,24 +220,31 @@ class GlyfEncoder {
+       flag_byte_stream_.push_back(on_curve_bit + 124 + xy_sign_bits);
+       glyph_stream_.push_back(abs_x >> 8);
+       glyph_stream_.push_back(abs_x & 0xff);
+       glyph_stream_.push_back(abs_y >> 8);
+       glyph_stream_.push_back(abs_y & 0xff);
+     }
+   }
+ 
++  void EnsureOverlapBitmap() {
++    if (overlap_bitmap_.empty()) {
++      overlap_bitmap_.resize((n_glyphs_ + 7) >> 3);
++    }
++  }
++
+   std::vector<uint8_t> n_contour_stream_;
+   std::vector<uint8_t> n_points_stream_;
+   std::vector<uint8_t> flag_byte_stream_;
+   std::vector<uint8_t> composite_stream_;
+   std::vector<uint8_t> bbox_bitmap_;
+   std::vector<uint8_t> bbox_stream_;
+   std::vector<uint8_t> glyph_stream_;
+   std::vector<uint8_t> instruction_stream_;
++  std::vector<uint8_t> overlap_bitmap_;
+   int n_glyphs_;
+ };
+ 
+ }  // namespace
+ 
+ bool TransformGlyfAndLocaTables(Font* font) {
+   // no transform for CFF
+   const Font::Table* glyf_table = font->FindTable(kGlyfTableTag);
+diff --git a/modules/woff2/src/woff2_compress.cc b/modules/woff2/src/woff2_compress.cc
+--- a/modules/woff2/src/woff2_compress.cc
++++ b/modules/woff2/src/woff2_compress.cc
+@@ -8,32 +8,30 @@
+ 
+ #include <string>
+ 
+ #include "file.h"
+ #include <woff2/encode.h>
+ 
+ 
+ int main(int argc, char **argv) {
+-  using std::string;
+-
+   if (argc != 2) {
+     fprintf(stderr, "One argument, the input filename, must be provided.\n");
+     return 1;
+   }
+ 
+-  string filename(argv[1]);
+-  string outfilename = filename.substr(0, filename.find_last_of(".")) + ".woff2";
++  std::string filename(argv[1]);
++  std::string outfilename = filename.substr(0, filename.find_last_of(".")) + ".woff2";
+   fprintf(stdout, "Processing %s => %s\n",
+     filename.c_str(), outfilename.c_str());
+-  string input = woff2::GetFileContent(filename);
++  std::string input = woff2::GetFileContent(filename);
+ 
+   const uint8_t* input_data = reinterpret_cast<const uint8_t*>(input.data());
+   size_t output_size = woff2::MaxWOFF2CompressedSize(input_data, input.size());
+-  string output(output_size, 0);
++  std::string output(output_size, 0);
+   uint8_t* output_data = reinterpret_cast<uint8_t*>(&output[0]);
+ 
+   woff2::WOFF2Params params;
+   if (!woff2::ConvertTTFToWOFF2(input_data, input.size(),
+                                 output_data, &output_size, params)) {
+     fprintf(stderr, "Compression failed.\n");
+     return 1;
+   }
+diff --git a/modules/woff2/src/woff2_dec.cc b/modules/woff2/src/woff2_dec.cc
+--- a/modules/woff2/src/woff2_dec.cc
++++ b/modules/woff2/src/woff2_dec.cc
+@@ -27,37 +27,37 @@
+ #include "./table_tags.h"
+ #include "./variable_length.h"
+ #include "./woff2_common.h"
+ 
+ namespace woff2 {
+ 
+ namespace {
+ 
+-using std::string;
+-using std::vector;
+-
+-
+ // simple glyph flags
+ const int kGlyfOnCurve = 1 << 0;
+ const int kGlyfXShort = 1 << 1;
+ const int kGlyfYShort = 1 << 2;
+ const int kGlyfRepeat = 1 << 3;
+ const int kGlyfThisXIsSame = 1 << 4;
+ const int kGlyfThisYIsSame = 1 << 5;
++const int kOverlapSimple = 1 << 6;
+ 
+ // composite glyph flags
+ // See CompositeGlyph.java in sfntly for full definitions
+ const int FLAG_ARG_1_AND_2_ARE_WORDS = 1 << 0;
+ const int FLAG_WE_HAVE_A_SCALE = 1 << 3;
+ const int FLAG_MORE_COMPONENTS = 1 << 5;
+ const int FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
+ const int FLAG_WE_HAVE_A_TWO_BY_TWO = 1 << 7;
+ const int FLAG_WE_HAVE_INSTRUCTIONS = 1 << 8;
+ 
++// glyf flags
++const int FLAG_OVERLAP_SIMPLE_BITMAP = 1 << 0;
++
+ const size_t kCheckSumAdjustmentOffset = 8;
+ 
+ const size_t kEndPtsOfContoursOffset = 10;
+ const size_t kCompositeGlyphBegin = 10;
+ 
+ // 98% of Google Fonts have no glyph above 5k bytes
+ // Largest glyph ever observed was 72k bytes
+ const size_t kDefaultGlyphBuf = 5120;
+@@ -106,16 +106,26 @@ struct RebuildMetadata {
+   std::map<std::pair<uint32_t, uint32_t>, uint32_t> checksums;
+ };
+ 
+ int WithSign(int flag, int baseval) {
+   // Precondition: 0 <= baseval < 65536 (to avoid integer overflow)
+   return (flag & 1) ? baseval : -baseval;
+ }
+ 
++bool _SafeIntAddition(int a, int b, int* result) {
++  if (PREDICT_FALSE(
++          ((a > 0) && (b > std::numeric_limits<int>::max() - a)) ||
++          ((a < 0) && (b < std::numeric_limits<int>::min() - a)))) {
++    return false;
++  }
++  *result = a + b;
++  return true;
++}
++
+ bool TripletDecode(const uint8_t* flags_in, const uint8_t* in, size_t in_size,
+     unsigned int n_points, Point* result, size_t* in_bytes_consumed) {
+   int x = 0;
+   int y = 0;
+ 
+   if (PREDICT_FALSE(n_points > in_size)) {
+     return FONT_COMPRESSION_FAILURE();
+   }
+@@ -161,44 +171,52 @@ bool TripletDecode(const uint8_t* flags_
+       dx = WithSign(flag, (in[triplet_index] << 4) + (b2 >> 4));
+       dy = WithSign(flag >> 1, ((b2 & 0x0f) << 8) + in[triplet_index + 2]);
+     } else {
+       dx = WithSign(flag, (in[triplet_index] << 8) + in[triplet_index + 1]);
+       dy = WithSign(flag >> 1,
+           (in[triplet_index + 2] << 8) + in[triplet_index + 3]);
+     }
+     triplet_index += n_data_bytes;
+-    // Possible overflow but coordinate values are not security sensitive
+-    x += dx;
+-    y += dy;
++    if (!_SafeIntAddition(x, dx, &x)) {
++      return false;
++    }
++    if (!_SafeIntAddition(y, dy, &y)) {
++      return false;
++    }
+     *result++ = {x, y, on_curve};
+   }
+   *in_bytes_consumed = triplet_index;
+   return true;
+ }
+ 
+ // This function stores just the point data. On entry, dst points to the
+ // beginning of a simple glyph. Returns true on success.
+ bool StorePoints(unsigned int n_points, const Point* points,
+-    unsigned int n_contours, unsigned int instruction_length,
+-    uint8_t* dst, size_t dst_size, size_t* glyph_size) {
++                 unsigned int n_contours, unsigned int instruction_length,
++                 bool has_overlap_bit, uint8_t* dst, size_t dst_size,
++                 size_t* glyph_size) {
+   // I believe that n_contours < 65536, in which case this is safe. However, a
+   // comment and/or an assert would be good.
+   unsigned int flag_offset = kEndPtsOfContoursOffset + 2 * n_contours + 2 +
+     instruction_length;
+   int last_flag = -1;
+   int repeat_count = 0;
+   int last_x = 0;
+   int last_y = 0;
+   unsigned int x_bytes = 0;
+   unsigned int y_bytes = 0;
+ 
+   for (unsigned int i = 0; i < n_points; ++i) {
+     const Point& point = points[i];
+     int flag = point.on_curve ? kGlyfOnCurve : 0;
++    if (has_overlap_bit && i == 0) {
++      flag |= kOverlapSimple;
++    }
++
+     int dx = point.x - last_x;
+     int dy = point.y - last_y;
+     if (dx == 0) {
+       flag |= kGlyfThisXIsSame;
+     } else if (dx > -256 && dx < 256) {
+       flag |= kGlyfXShort | (dx > 0 ? kGlyfThisXIsSame : 0);
+       x_bytes += 1;
+     } else {
+@@ -386,23 +404,30 @@ bool StoreLoca(const std::vector<uint32_
+ 
+ // Reconstruct entire glyf table based on transformed original
+ bool ReconstructGlyf(const uint8_t* data, Table* glyf_table,
+                      uint32_t* glyf_checksum, Table * loca_table,
+                      uint32_t* loca_checksum, WOFF2FontInfo* info,
+                      WOFF2Out* out) {
+   static const int kNumSubStreams = 7;
+   Buffer file(data, glyf_table->transform_length);
+-  uint32_t version;
++  uint16_t version;
+   std::vector<std::pair<const uint8_t*, size_t> > substreams(kNumSubStreams);
+   const size_t glyf_start = out->Size();
+ 
+-  if (PREDICT_FALSE(!file.ReadU32(&version))) {
++  if (PREDICT_FALSE(!file.ReadU16(&version))) {
+     return FONT_COMPRESSION_FAILURE();
+   }
++
++  uint16_t flags;
++  if (PREDICT_FALSE(!file.ReadU16(&flags))) {
++    return FONT_COMPRESSION_FAILURE();
++  }
++  bool has_overlap_bitmap = (flags & FLAG_OVERLAP_SIMPLE_BITMAP);
++
+   if (PREDICT_FALSE(!file.ReadU16(&info->num_glyphs) ||
+       !file.ReadU16(&info->index_format))) {
+     return FONT_COMPRESSION_FAILURE();
+   }
+ 
+   // https://dev.w3.org/webfonts/WOFF2/spec/#conform-mustRejectLoca
+   // dst_length here is origLength in the spec
+   uint32_t expected_loca_dst_length = (info->index_format ? 4 : 2)
+@@ -430,16 +455,27 @@ bool ReconstructGlyf(const uint8_t* data
+   Buffer n_contour_stream(substreams[0].first, substreams[0].second);
+   Buffer n_points_stream(substreams[1].first, substreams[1].second);
+   Buffer flag_stream(substreams[2].first, substreams[2].second);
+   Buffer glyph_stream(substreams[3].first, substreams[3].second);
+   Buffer composite_stream(substreams[4].first, substreams[4].second);
+   Buffer bbox_stream(substreams[5].first, substreams[5].second);
+   Buffer instruction_stream(substreams[6].first, substreams[6].second);
+ 
++  const uint8_t* overlap_bitmap = nullptr;
++  unsigned int overlap_bitmap_length = 0;
++  if (has_overlap_bitmap) {
++    overlap_bitmap_length = (info->num_glyphs + 7) >> 3;
++    overlap_bitmap = data + offset;
++    if (PREDICT_FALSE(overlap_bitmap_length >
++                           glyf_table->transform_length - offset)) {
++      return FONT_COMPRESSION_FAILURE();
++    }
++  }
++
+   std::vector<uint32_t> loca_values(info->num_glyphs + 1);
+   std::vector<unsigned int> n_points_vec;
+   std::unique_ptr<Point[]> points;
+   size_t points_size = 0;
+   const uint8_t* bbox_bitmap = bbox_stream.buffer();
+   // Safe because num_glyphs is bounded
+   unsigned int bitmap_length = ((info->num_glyphs + 31) >> 5) << 2;
+   if (!bbox_stream.Skip(bitmap_length)) {
+@@ -583,18 +619,22 @@ bool ReconstructGlyf(const uint8_t* data
+ 
+       glyph_size = Store16(glyph_buf.get(), glyph_size, instruction_size);
+       if (PREDICT_FALSE(!instruction_stream.Read(glyph_buf.get() + glyph_size,
+                                                  instruction_size))) {
+         return FONT_COMPRESSION_FAILURE();
+       }
+       glyph_size += instruction_size;
+ 
+-      if (PREDICT_FALSE(!StorePoints(total_n_points, points.get(), n_contours,
+-            instruction_size, glyph_buf.get(), glyph_buf_size, &glyph_size))) {
++      bool has_overlap_bit =
++          has_overlap_bitmap && overlap_bitmap[i >> 3] & (0x80 >> (i & 7));
++
++      if (PREDICT_FALSE(!StorePoints(
++              total_n_points, points.get(), n_contours, instruction_size,
++              has_overlap_bit, glyph_buf.get(), glyph_buf_size, &glyph_size))) {
+         return FONT_COMPRESSION_FAILURE();
+       }
+     } else {
+       // n_contours == 0; empty glyph. Must NOT have a bbox.
+       if (PREDICT_FALSE(have_bbox)) {
+ #ifdef FONT_COMPRESSION_BIN
+         fprintf(stderr, "Empty glyph has a bbox\n");
+ #endif
+diff --git a/modules/woff2/src/woff2_decompress.cc b/modules/woff2/src/woff2_decompress.cc
+--- a/modules/woff2/src/woff2_decompress.cc
++++ b/modules/woff2/src/woff2_decompress.cc
+@@ -9,31 +9,31 @@
+ 
+ #include <string>
+ 
+ #include "./file.h"
+ #include <woff2/decode.h>
+ 
+ 
+ int main(int argc, char **argv) {
+-  using std::string;
+-
+   if (argc != 2) {
+     fprintf(stderr, "One argument, the input filename, must be provided.\n");
+     return 1;
+   }
+ 
+-  string filename(argv[1]);
+-  string outfilename = filename.substr(0, filename.find_last_of(".")) + ".ttf";
++  std::string filename(argv[1]);
++  std::string outfilename = filename.substr(0, filename.find_last_of(".")) + ".ttf";
+ 
+   // Note: update woff2_dec_fuzzer_new_entry.cc if this pattern changes.
+-  string input = woff2::GetFileContent(filename);
++  std::string input = woff2::GetFileContent(filename);
+   const uint8_t* raw_input = reinterpret_cast<const uint8_t*>(input.data());
+-  string output(std::min(woff2::ComputeWOFF2FinalSize(raw_input, input.size()),
+-                         woff2::kDefaultMaxSize), 0);
++  std::string output(
++      std::min(woff2::ComputeWOFF2FinalSize(raw_input, input.size()),
++               woff2::kDefaultMaxSize),
++      0);
+   woff2::WOFF2StringOut out(&output);
+ 
+   const bool ok = woff2::ConvertWOFF2ToTTF(raw_input, input.size(), &out);
+ 
+   if (ok) {
+     woff2::SetFileContents(outfilename, output.begin(),
+         output.begin() + out.Size());
+   }
+diff --git a/modules/woff2/src/woff2_enc.cc b/modules/woff2/src/woff2_enc.cc
+--- a/modules/woff2/src/woff2_enc.cc
++++ b/modules/woff2/src/woff2_enc.cc
+@@ -23,23 +23,19 @@
+ #include "./store_bytes.h"
+ #include "./table_tags.h"
+ #include "./transform.h"
+ #include "./variable_length.h"
+ #include "./woff2_common.h"
+ 
+ namespace woff2 {
+ 
++
+ namespace {
+ 
+-
+-using std::string;
+-using std::vector;
+-
+-
+ const size_t kWoff2HeaderSize = 48;
+ const size_t kWoff2EntrySize = 20;
+ 
+ bool Compress(const uint8_t* data, const size_t len, uint8_t* result,
+               uint32_t* result_len, BrotliEncoderMode mode, int quality) {
+   size_t compressed_len = *result_len;
+   if (BrotliEncoderCompress(quality, BROTLI_DEFAULT_WINDOW, mode, len, data,
+                             &compressed_len, result) == 0) {
+@@ -178,17 +174,17 @@ size_t ComputeTotalTransformLength(const
+ 
+ }  // namespace
+ 
+ size_t MaxWOFF2CompressedSize(const uint8_t* data, size_t length) {
+   return MaxWOFF2CompressedSize(data, length, "");
+ }
+ 
+ size_t MaxWOFF2CompressedSize(const uint8_t* data, size_t length,
+-    const string& extended_metadata) {
++                              const std::string& extended_metadata) {
+   // Except for the header size, which is 32 bytes larger in woff2 format,
+   // all other parts should be smaller (table header in short format,
+   // transformations and compression). Just to be sure, we will give some
+   // headroom anyway.
+   return length + 1024 + extended_metadata.length();
+ }
+ 
+ uint32_t CompressedBufferSize(uint32_t original_size) {
+diff --git a/modules/woff2/src/woff2_info.cc b/modules/woff2/src/woff2_info.cc
+--- a/modules/woff2/src/woff2_info.cc
++++ b/modules/woff2/src/woff2_info.cc
+@@ -24,28 +24,26 @@ std::string PrintTag(int tag) {
+     static_cast<char>((tag >> 16) & 0xFF),
+     static_cast<char>((tag >> 8) & 0xFF),
+     static_cast<char>(tag & 0xFF)
+   };
+   return std::string(printable, 4);
+ }
+ 
+ int main(int argc, char **argv) {
+-  using std::string;
+-
+   if (argc != 2) {
+     fprintf(stderr, "One argument, the input filename, must be provided.\n");
+     return 1;
+   }
+ 
+-  string filename(argv[1]);
+-  string outfilename = filename.substr(0, filename.find_last_of(".")) + ".woff2";
++  std::string filename(argv[1]);
++  std::string outfilename = filename.substr(0, filename.find_last_of(".")) + ".woff2";
+   fprintf(stdout, "Processing %s => %s\n",
+     filename.c_str(), outfilename.c_str());
+-  string input = woff2::GetFileContent(filename);
++  std::string input = woff2::GetFileContent(filename);
+ 
+   woff2::Buffer file(reinterpret_cast<const uint8_t*>(input.data()),
+     input.size());
+ 
+   printf("WOFF2Header\n");
+   uint32_t signature, flavor, length, totalSfntSize, totalCompressedSize;
+   uint32_t metaOffset, metaLength, metaOrigLength, privOffset, privLength;
+   uint16_t num_tables, reserved, major, minor;
+diff --git a/modules/woff2/src/woff2_out.cc b/modules/woff2/src/woff2_out.cc
+--- a/modules/woff2/src/woff2_out.cc
++++ b/modules/woff2/src/woff2_out.cc
+@@ -3,24 +3,20 @@
+    Distributed under MIT license.
+    See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
+ */
+ 
+ /* Output buffer for WOFF2 decompression. */
+ 
+ #include <woff2/output.h>
+ 
+-using std::string;
+-
+ namespace woff2 {
+ 
+-WOFF2StringOut::WOFF2StringOut(string* buf)
+-  : buf_(buf),
+-    max_size_(kDefaultMaxSize),
+-    offset_(0) {}
++WOFF2StringOut::WOFF2StringOut(std::string *buf)
++    : buf_(buf), max_size_(kDefaultMaxSize), offset_(0) {}
+ 
+ bool WOFF2StringOut::Write(const void *buf, size_t n) {
+   return Write(buf, offset_, n);
+ }
+ 
+ bool WOFF2StringOut::Write(const void *buf, size_t offset, size_t n) {
+   if (offset > max_size_ || n > max_size_ - offset) {
+     return false;

+ 163 - 0
mozilla-release/patches/mozilla-central_655952.patch

@@ -0,0 +1,163 @@
+# HG changeset patch
+# User serge-sans-paille <sguelton@mozilla.com>
+# Date 1678295588 0
+#      Wed Mar 08 17:13:08 2023 +0000
+# Node ID a2a291040eceb85064ea1825c806586dcc93a6ef
+# Parent  f9994d3f7d903b3762b2b4c0d36667f62c8da97d
+Bug 1821011 - Vendor woff2 using `mach vendor` r=jfkthame
+
+Differential Revision: https://phabricator.services.mozilla.com/D171976
+
+diff --git a/modules/woff2/LICENSE b/modules/woff2/LICENSE
+new file mode 100644
+--- /dev/null
++++ b/modules/woff2/LICENSE
+@@ -0,0 +1,19 @@
++Copyright (c) 2013-2017 by the WOFF2 Authors.
++
++Permission is hereby granted, free of charge, to any person obtaining a copy
++of this software and associated documentation files (the "Software"), to deal
++in the Software without restriction, including without limitation the rights
++to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++copies of the Software, and to permit persons to whom the Software is
++furnished to do so, subject to the following conditions:
++
++The above copyright notice and this permission notice shall be included in
++all copies or substantial portions of the Software.
++
++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
++AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
++THE SOFTWARE.
+diff --git a/modules/woff2/README.mozilla b/modules/woff2/README.mozilla
+deleted file mode 100644
+--- a/modules/woff2/README.mozilla
++++ /dev/null
+@@ -1,14 +0,0 @@
+-This is the woff2 library from
+-https://github.com/google/woff2.
+-
+-Upstream code can be viewed at
+-  https://github.com/google/woff2/tree/master
+-
+-and cloned by
+-  git clone https://github.com/google/woff2
+-
+-The in-tree copy is updated by running
+-  sh update.sh
+-from within the modules/woff2 directory.
+-
+-Current version: [commit 4721483ad780ee2b63cb787bfee4aa64b61a0446].
+diff --git a/modules/woff2/moz.yaml b/modules/woff2/moz.yaml
+new file mode 100644
+--- /dev/null
++++ b/modules/woff2/moz.yaml
+@@ -0,0 +1,42 @@
++schema: 1
++
++bugzilla:
++  product: Toolkit
++  component: "General"
++
++origin:
++  name: woff2
++  description: font compression
++
++  url: https://github.com/google/woff2
++
++  release: 4721483ad780ee2b63cb787bfee4aa64b61a0446 (2022-03-30T15:59:13Z).
++  revision: 4721483ad780ee2b63cb787bfee4aa64b61a0446
++
++  license: MIT
++
++vendoring:
++  url: https://github.com/google/woff2
++  source-hosting: github
++  tracking: commit
++
++  exclude:
++    - ".*"
++    - CMakeLists.txt
++    - CONTRIBUTING.md
++    - Makefile
++    - brotli
++    - cmake
++    - README.md
++
++  include:
++    - include/
++    - src/
++
++  keep:
++    - "RLBoxWOFF2Sandbox.*"
++    - sources.mozbuild
++
++  patches:
++    - woff2-rlbox.patch
++
+diff --git a/modules/woff2/update.sh b/modules/woff2/update.sh
+deleted file mode 100755
+--- a/modules/woff2/update.sh
++++ /dev/null
+@@ -1,24 +0,0 @@
+-#!/bin/sh
+-
+-# Script to update the mozilla in-tree copy of the woff2 library.
+-# Run this within the /modules/woff2 directory of the source tree.
+-
+-MY_TEMP_DIR=`mktemp -d -t woff2_update.XXXXXX` || exit 1
+-
+-git clone https://github.com/google/woff2 ${MY_TEMP_DIR}/woff2
+-
+-COMMIT=`(cd ${MY_TEMP_DIR}/woff2 && git log | head -n 1)`
+-perl -p -i -e "s/\[commit [0-9a-f]{40}\]/[${COMMIT}]/" README.mozilla;
+-
+-rm -rf src
+-rm -rf include
+-mv ${MY_TEMP_DIR}/woff2/src src
+-mv ${MY_TEMP_DIR}/woff2/include include
+-rm -rf ${MY_TEMP_DIR}
+-hg add src
+-hg add include
+-
+-echo "###"
+-echo "### Updated woff2 to $COMMIT."
+-echo "### Remember to verify and commit the changes to source control!"
+-echo "###"
+diff --git a/modules/woff2/woff2-rlbox.patch b/modules/woff2/woff2-rlbox.patch
+new file mode 100644
+--- /dev/null
++++ b/modules/woff2/woff2-rlbox.patch
+@@ -0,0 +1,29 @@
++diff --git a/src/woff2_dec.cc b/src/woff2_dec.cc
++--- a/src/woff2_dec.cc
+++++ b/src/woff2_dec.cc
++@@ -19,7 +19,6 @@
++ #include <memory>
++ #include <utility>
++ 
++-#include <brotli/decode.h>
++ #include "./buffer.h"
++ #include "./port.h"
++ #include "./round.h"
++@@ -28,6 +27,8 @@
++ #include "./variable_length.h"
++ #include "./woff2_common.h"
++ 
+++#include "../RLBoxWOFF2Sandbox.h"
+++
++ namespace woff2 {
++ 
++ namespace {
++@@ -758,7 +759,7 @@ bool ReconstructTransformedHmtx(const uint8_t* transformed_buf,
++ bool Woff2Uncompress(uint8_t* dst_buf, size_t dst_size,
++   const uint8_t* src_buf, size_t src_size) {
++   size_t uncompressed_size = dst_size;
++-  BrotliDecoderResult result = BrotliDecoderDecompress(
+++  BrotliDecoderResult result = RLBoxBrotliDecoderDecompress(
++       src_size, src_buf, &uncompressed_size, dst_buf);
++   if (PREDICT_FALSE(result != BROTLI_DECODER_RESULT_SUCCESS ||
++                     uncompressed_size != dst_size)) {

+ 4118 - 0
mozilla-release/patches/mozilla-central_665930.patch

@@ -0,0 +1,4118 @@
+# HG changeset patch
+# User Ryan VanderMeulen <ryanvm@gmail.com>
+# Date 1685465808 0
+#      Tue May 30 16:56:48 2023 +0000
+# Node ID fdae8692c5f724b7209d50201e52c8efac172cd7
+# Parent  3e815af522af15c2069c3dcdeff415ee4fbc77c7
+Bug 1835833 - Update OTS to 9.1.0. r=jfkthame
+
+Differential Revision: https://phabricator.services.mozilla.com/D179464
+
+diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
+--- a/gfx/ots/README.mozilla
++++ b/gfx/ots/README.mozilla
+@@ -1,12 +1,12 @@
+ This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
+ 
+ Our reference repository is https://github.com/khaledhosny/ots/.
+ 
+-Current revision: 35643038c4904538aa74c2c691f8d792efdbd6c0 (9.0.0)
++Current revision: 75933e1bdc98bdb095f6274b284e1c365f2c510e (9.1.0)
+ 
+ Upstream files included: LICENSE, src/, include/, tests/*.cc
+ 
+ Additional files: README.mozilla, src/moz.build
+ 
+ Additional patch: ots-visibility.patch (bug 711079).
+ Additional patch: ots-lz4.patch
+diff --git a/gfx/ots/src/avar.cc b/gfx/ots/src/avar.cc
+--- a/gfx/ots/src/avar.cc
++++ b/gfx/ots/src/avar.cc
+@@ -1,41 +1,54 @@
+ // Copyright (c) 2018 The OTS Authors. All rights reserved.
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+ 
+ #include "avar.h"
+ 
+ #include "fvar.h"
+ 
++#include "variations.h"
++
+ namespace ots {
+ 
+ // -----------------------------------------------------------------------------
+ // OpenTypeAVAR
+ // -----------------------------------------------------------------------------
+ 
+ bool OpenTypeAVAR::Parse(const uint8_t* data, size_t length) {
+   Buffer table(data, length);
+   if (!table.ReadU16(&this->majorVersion) ||
+       !table.ReadU16(&this->minorVersion) ||
+       !table.ReadU16(&this->reserved) ||
+       !table.ReadU16(&this->axisCount)) {
+     return Drop("Failed to read table header");
+   }
+-  if (this->majorVersion != 1) {
++  if (this->majorVersion > 2) {
+     return Drop("Unknown table version");
+   }
+-  if (this->minorVersion > 0) {
+-    // we only know how to serialize version 1.0
+-    Warning("Downgrading minor version to 0");
+-    this->minorVersion = 0;
+-  }
+-  if (this->reserved != 0) {
+-    Warning("Expected reserved=0");
+-    this->reserved = 0;
++  if (this->majorVersion == 1) {
++    // We can fix table
++    if (this->minorVersion > 0) {
++      // we only know how to serialize version 1.0
++      Warning("Downgrading minor version to 0");
++      this->minorVersion = 0;
++    }
++    if (this->reserved != 0) {
++      Warning("Expected reserved=0");
++      this->reserved = 0;
++    }
++  } else {
++    // We serialize data unchanged, so drop even for minor errors
++    if (this->minorVersion > 0) {
++      return Drop("Unknown minor table version");
++    }
++    if (this->reserved != 0) {
++      return Drop("Expected reserved=0");
++    }
+   }
+ 
+   OpenTypeFVAR* fvar = static_cast<OpenTypeFVAR*>(
+       GetFont()->GetTypedTable(OTS_TAG_FVAR));
+   if (!fvar) {
+     return DropVariations("Required fvar table is missing");
+   }
+   if (axisCount != fvar->AxisCount()) {
+@@ -74,20 +87,62 @@ bool OpenTypeAVAR::Parse(const uint8_t* 
+       }
+       this->axisSegmentMaps[i].push_back(map);
+     }
+     if (positionMapCount > 0 && foundRequiredMappings != 3) {
+       return Drop("A required mapping (for -1, 0 or 1) is missing");
+     }
+   }
+ 
++  if (this->majorVersion < 2)
++    return true;
++
++  uint32_t axisIndexMapOffset;
++  uint32_t varStoreOffset;
++
++  if (!table.ReadU32(&axisIndexMapOffset) ||
++      !table.ReadU32(&varStoreOffset)) {
++    return Drop("Failed to read version 2 offsets");
++  }
++
++  Font *font = GetFont();
++  uint32_t headerSize = table.offset();
++
++  if (axisIndexMapOffset) {
++    if (axisIndexMapOffset < headerSize || axisIndexMapOffset >= length) {
++      return Drop("Bad delta set index offset in table header");
++    }
++    if (!ParseDeltaSetIndexMap(font, data + axisIndexMapOffset, length - axisIndexMapOffset)) {
++      return Drop("Failed to parse delta set index map");
++    }
++  }
++
++  if (varStoreOffset) {
++    if (varStoreOffset < headerSize || varStoreOffset >= length) {
++      return Drop("Bad item variation store offset in table header");
++    }
++    if (!ParseItemVariationStore(font, data + varStoreOffset, length - varStoreOffset)) {
++      return Drop("Failed to parse item variation store");
++    }
++  }
++
++  this->m_data = data;
++  this->m_length = length;
++
+   return true;
+ }
+ 
+ bool OpenTypeAVAR::Serialize(OTSStream* out) {
++  if (this->majorVersion >= 2) {
++    if (!out->Write(this->m_data, this->m_length)) {
++      return Error("Failed to write table");
++    }
++    return true;
++  }
++
+   if (!out->WriteU16(this->majorVersion) ||
+       !out->WriteU16(this->minorVersion) ||
+       !out->WriteU16(this->reserved) ||
+       !out->WriteU16(this->axisCount)) {
+     return Error("Failed to write table");
+   }
+ 
+   for (size_t i = 0; i < this->axisCount; i++) {
+diff --git a/gfx/ots/src/avar.h b/gfx/ots/src/avar.h
+--- a/gfx/ots/src/avar.h
++++ b/gfx/ots/src/avar.h
+@@ -30,13 +30,17 @@ class OpenTypeAVAR : public Table {
+   uint16_t axisCount;
+ 
+   struct AxisValueMap {
+     int16_t fromCoordinate;
+     int16_t toCoordinate;
+   };
+ 
+   std::vector<std::vector<AxisValueMap>> axisSegmentMaps;
++
++  // Only used for versions >= 2
++  const uint8_t *m_data;
++  size_t m_length;
+ };
+ 
+ }  // namespace ots
+ 
+ #endif  // OTS_AVAR_H_
+diff --git a/gfx/ots/src/cff_charstring.cc b/gfx/ots/src/cff_charstring.cc
+--- a/gfx/ots/src/cff_charstring.cc
++++ b/gfx/ots/src/cff_charstring.cc
+@@ -379,17 +379,17 @@ bool ExecuteCharStringOperator(ots::Open
+     if (stack_size < 1) {
+       return OTS_FAILURE();
+     }
+     if (cs_ctx.vsindex >= (int32_t)cff.region_index_count.size()) {
+       return OTS_FAILURE();
+     }
+     uint16_t k = cff.region_index_count.at(cs_ctx.vsindex);
+     uint16_t n = argument_stack->top();
+-    if (stack_size < n * (k + 1) + 1) {
++    if (stack_size < n * (k + 1u) + 1u) {
+       return OTS_FAILURE();
+     }
+ 
+     // Keep the 1st n operands on the stack for the next operator to use and
+     // pop the rest. There can be multiple consecutive blend operators, so this
+     // makes sure the operands of all of them are kept on the stack.
+     while (argument_stack->size() > stack_size - ((n * k) + 1))
+       argument_stack->pop();
+diff --git a/gfx/ots/src/cpal.cc b/gfx/ots/src/cpal.cc
+--- a/gfx/ots/src/cpal.cc
++++ b/gfx/ots/src/cpal.cc
+@@ -189,17 +189,19 @@ bool OpenTypeCPAL::Parse(const uint8_t *
+ 
+   return true;
+ }
+ 
+ bool OpenTypeCPAL::Serialize(OTSStream *out) {
+   uint16_t numPalettes = this->colorRecordIndices.size();
+   uint16_t numColorRecords = this->colorRecords.size();
+ 
++#ifndef NDEBUG
+   off_t start = out->Tell();
++#endif
+ 
+   size_t colorRecordsArrayOffset = 4 * sizeof(uint16_t) + sizeof(uint32_t) +
+       numPalettes * sizeof(uint16_t);
+   if (this->version == 1) {
+     colorRecordsArrayOffset += 3 * sizeof(uint32_t);
+   }
+ 
+   size_t totalLen = colorRecordsArrayOffset + numColorRecords * sizeof(uint32_t);
+diff --git a/gfx/ots/src/glyf.cc b/gfx/ots/src/glyf.cc
+--- a/gfx/ots/src/glyf.cc
++++ b/gfx/ots/src/glyf.cc
+@@ -107,19 +107,19 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buff
+ 
+   uint16_t bytecode_length = 0;
+   if (!glyph.ReadU16(&bytecode_length)) {
+     return Error("Can't read bytecode length");
+   }
+ 
+   if (this->maxp->version_1 &&
+       this->maxp->max_size_glyf_instructions < bytecode_length) {
+-    this->maxp->max_size_glyf_instructions = bytecode_length;
+     Warning("Bytecode length is bigger than maxp.maxSizeOfInstructions %d: %d",
+             this->maxp->max_size_glyf_instructions, bytecode_length);
++    this->maxp->max_size_glyf_instructions = bytecode_length;
+   }
+ 
+   if (!glyph.Skip(bytecode_length)) {
+     return Error("Can't read bytecode of length %d", bytecode_length);
+   }
+ 
+   uint32_t coordinates_length = 0;
+   for (uint32_t i = 0; i < num_flags; ++i) {
+@@ -210,20 +210,20 @@ bool OpenTypeGLYF::ParseCompositeGlyph(
+   if (flags & WE_HAVE_INSTRUCTIONS) {
+     uint16_t bytecode_length;
+     if (!glyph.ReadU16(&bytecode_length)) {
+       return Error("Can't read instructions size");
+     }
+ 
+     if (this->maxp->version_1 &&
+         this->maxp->max_size_glyf_instructions < bytecode_length) {
+-      this->maxp->max_size_glyf_instructions = bytecode_length;
+       Warning("Bytecode length is bigger than maxp.maxSizeOfInstructions "
+               "%d: %d",
+               this->maxp->max_size_glyf_instructions, bytecode_length);
++      this->maxp->max_size_glyf_instructions = bytecode_length;
+     }
+ 
+     if (!glyph.Skip(bytecode_length)) {
+       return Error("Can't read bytecode of length %d", bytecode_length);
+     }
+   }
+ 
+   this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
+diff --git a/gfx/ots/src/gpos.cc b/gfx/ots/src/gpos.cc
+--- a/gfx/ots/src/gpos.cc
++++ b/gfx/ots/src/gpos.cc
+@@ -25,60 +25,19 @@ enum GPOS_TYPE {
+   GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT = 5,
+   GPOS_TYPE_MARK_TO_MARK_ATTACHMENT = 6,
+   GPOS_TYPE_CONTEXT_POSITIONING = 7,
+   GPOS_TYPE_CHAINED_CONTEXT_POSITIONING = 8,
+   GPOS_TYPE_EXTENSION_POSITIONING = 9,
+   GPOS_TYPE_RESERVED = 10
+ };
+ 
+-// The size of gpos header, version 1.0.
+-const unsigned kGposHeaderSize_1_0 = 10;
+-// The size of gpos header, version 1.1.
+-const unsigned kGposHeaderSize_1_1 = 14;
+ // The maximum format number for anchor tables.
+ const uint16_t kMaxAnchorFormat = 3;
+ 
+-// Lookup type parsers.
+-bool ParseSingleAdjustment(const ots::Font *font,
+-                           const uint8_t *data, const size_t length);
+-bool ParsePairAdjustment(const ots::Font *font,
+-                         const uint8_t *data, const size_t length);
+-bool ParseCursiveAttachment(const ots::Font *font,
+-                            const uint8_t *data, const size_t length);
+-bool ParseMarkToBaseAttachment(const ots::Font *font,
+-                               const uint8_t *data, const size_t length);
+-bool ParseMarkToLigatureAttachment(const ots::Font *font,
+-                                   const uint8_t *data, const size_t length);
+-bool ParseMarkToMarkAttachment(const ots::Font *font,
+-                               const uint8_t *data, const size_t length);
+-bool ParseContextPositioning(const ots::Font *font,
+-                             const uint8_t *data, const size_t length);
+-bool ParseChainedContextPositioning(const ots::Font *font,
+-                                    const uint8_t *data, const size_t length);
+-bool ParseExtensionPositioning(const ots::Font *font,
+-                               const uint8_t *data, const size_t length);
+-
+-const ots::LookupSubtableParser::TypeParser kGposTypeParsers[] = {
+-  {GPOS_TYPE_SINGLE_ADJUSTMENT, ParseSingleAdjustment},
+-  {GPOS_TYPE_PAIR_ADJUSTMENT, ParsePairAdjustment},
+-  {GPOS_TYPE_CURSIVE_ATTACHMENT, ParseCursiveAttachment},
+-  {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}
+-};
+-
+-const ots::LookupSubtableParser kGposLookupSubtableParser = {
+-  arraysize(kGposTypeParsers),
+-  GPOS_TYPE_EXTENSION_POSITIONING, kGposTypeParsers
+-};
+-
+ // Shared Tables: ValueRecord, Anchor Table, and MarkArray
+ 
+ size_t CalcValueRecordSize(const uint16_t value_format) {
+   size_t size = 0;
+   for (unsigned i = 0; i < 8; ++i) {
+     if ((value_format >> i) & 0x1) {
+       size += 2;
+     }
+@@ -209,69 +168,16 @@ bool ParseMarkArrayTable(const ots::Font
+                           length - offset_mark_anchor)) {
+       return OTS_FAILURE_MSG("Faled to parse anchor table for mark table %d", i);
+     }
+   }
+ 
+   return true;
+ }
+ 
+-// Lookup Type 1:
+-// Single Adjustment Positioning Subtable
+-bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data,
+-                           const size_t length) {
+-  ots::Buffer subtable(data, length);
+-
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-
+-  uint16_t format = 0;
+-  uint16_t offset_coverage = 0;
+-  uint16_t value_format = 0;
+-  if (!subtable.ReadU16(&format) ||
+-      !subtable.ReadU16(&offset_coverage) ||
+-      !subtable.ReadU16(&value_format)) {
+-    return OTS_FAILURE_MSG("Can't read single adjustment information");
+-  }
+-
+-  if (format == 1) {
+-    // Format 1 exactly one value record.
+-    if (!ParseValueRecord(font, &subtable, 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_MSG("Failed to parse format 2 single adjustment table");
+-    }
+-    for (unsigned i = 0; i < value_count; ++i) {
+-      if (!ParseValueRecord(font, &subtable, value_format)) {
+-        return OTS_FAILURE_MSG("Failed to parse value record %d in format 2 single adjustment table", i);
+-      }
+-    }
+-  } else {
+-    return OTS_FAILURE_MSG("Bad format %d in single adjustment table", format);
+-  }
+-
+-  if (offset_coverage < subtable.offset() || offset_coverage >= length) {
+-    return OTS_FAILURE_MSG("Bad coverage offset %d in single adjustment table", offset_coverage);
+-  }
+-
+-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+-                               length - offset_coverage,
+-                               maxp->num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table in single adjustment table");
+-  }
+-
+-  return true;
+-}
+-
+ bool ParsePairSetTable(const ots::Font *font,
+                        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;
+@@ -399,138 +305,16 @@ bool ParsePairPosFormat2(const ots::Font
+                                length - offset_class_def2,
+                                num_glyphs, ots::kMaxClassDefValue)) {
+     return OTS_FAILURE_MSG("Failed to parse class definition table 2");
+   }
+ 
+   return true;
+ }
+ 
+-// Lookup Type 2:
+-// Pair Adjustment Positioning Subtable
+-bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data,
+-                         const size_t length) {
+-  ots::Buffer subtable(data, length);
+-
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-
+-  uint16_t format = 0;
+-  uint16_t offset_coverage = 0;
+-  uint16_t value_format1 = 0;
+-  uint16_t value_format2 = 0;
+-  if (!subtable.ReadU16(&format) ||
+-      !subtable.ReadU16(&offset_coverage) ||
+-      !subtable.ReadU16(&value_format1) ||
+-      !subtable.ReadU16(&value_format2)) {
+-    return OTS_FAILURE_MSG("Failed to read pair adjustment structure");
+-  }
+-
+-  if (format == 1) {
+-    if (!ParsePairPosFormat1(font, data, length, value_format1, value_format2,
+-                             maxp->num_glyphs)) {
+-      return OTS_FAILURE_MSG("Failed to parse pair pos format 1");
+-    }
+-  } else if (format == 2) {
+-    if (!ParsePairPosFormat2(font, data, length, value_format1, value_format2,
+-                             maxp->num_glyphs)) {
+-      return OTS_FAILURE_MSG("Failed to parse pair format 2");
+-    }
+-  } else {
+-    return OTS_FAILURE_MSG("Bad pos pair format %d", format);
+-  }
+-
+-  if (offset_coverage < subtable.offset() || offset_coverage >= length) {
+-    return OTS_FAILURE_MSG("Bad pair pos offset coverage %d", offset_coverage);
+-  }
+-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+-                               length - offset_coverage,
+-                               maxp->num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table");
+-  }
+-
+-  return true;
+-}
+-
+-// Lookup Type 3
+-// Cursive Attachment Positioning Subtable
+-bool ParseCursiveAttachment(const ots::Font *font, const uint8_t *data,
+-                            const size_t length) {
+-  ots::Buffer subtable(data, length);
+-
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-
+-  uint16_t format = 0;
+-  uint16_t offset_coverage = 0;
+-  uint16_t entry_exit_count = 0;
+-  if (!subtable.ReadU16(&format) ||
+-      !subtable.ReadU16(&offset_coverage) ||
+-      !subtable.ReadU16(&entry_exit_count)) {
+-    return OTS_FAILURE_MSG("Failed to read cursive attachment structure");
+-  }
+-
+-  if (format != 1) {
+-    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_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_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_MSG("Bad entry anchor offset %d in entry exit record %d", offset_entry_anchor, i);
+-      }
+-      if (!ParseAnchorTable(font, data + offset_entry_anchor,
+-                            length - offset_entry_anchor)) {
+-        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_MSG("Bad exit anchor offset %d in entry exit record %d", offset_exit_anchor, i);
+-      }
+-      if (!ParseAnchorTable(font, data + offset_exit_anchor,
+-                            length - offset_exit_anchor)) {
+-        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_MSG("Bad coverage offset in cursive attachment %d", offset_coverage);
+-  }
+-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+-                               length - offset_coverage,
+-                               maxp->num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table in cursive attachment");
+-  }
+-
+-  return true;
+-}
+-
+ bool ParseAnchorArrayTable(const ots::Font *font,
+                            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_MSG("Can't read anchor array length");
+@@ -666,176 +450,262 @@ bool ParseMarkToAttachmentSubtables(cons
+     }
+   } else {
+     return OTS_FAILURE_MSG("Bad attachment type %d", type);
+   }
+ 
+   return true;
+ }
+ 
++}  // namespace
++
++namespace ots {
++
++// Lookup Type 1:
++// Single Adjustment Positioning Subtable
++bool OpenTypeGPOS::ParseSingleAdjustment(const uint8_t *data,
++                                         const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
++  }
++
++  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 Error("Can't read single adjustment information");
++  }
++
++  if (format == 1) {
++    // Format 1 exactly one value record.
++    if (!ParseValueRecord(font, &subtable, value_format)) {
++      return Error("Failed to parse format 1 single adjustment table");
++    }
++  } else if (format == 2) {
++    uint16_t value_count = 0;
++    if (!subtable.ReadU16(&value_count)) {
++      return Error("Failed to parse format 2 single adjustment table");
++    }
++    for (unsigned i = 0; i < value_count; ++i) {
++      if (!ParseValueRecord(font, &subtable, value_format)) {
++        return Error("Failed to parse value record %d in format 2 single adjustment table", i);
++      }
++    }
++  } else {
++    return Error("Bad format %d in single adjustment table", format);
++  }
++
++  if (offset_coverage < subtable.offset() || offset_coverage >= length) {
++    return Error("Bad coverage offset %d in single adjustment table", offset_coverage);
++  }
++
++  if (!ots::ParseCoverageTable(font, data + offset_coverage,
++                               length - offset_coverage,
++                               maxp->num_glyphs)) {
++    return Error("Failed to parse coverage table in single adjustment table");
++  }
++
++  return true;
++}
++
++// Lookup Type 2:
++// Pair Adjustment Positioning Subtable
++bool OpenTypeGPOS::ParsePairAdjustment(const uint8_t *data,
++                                       const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
++  }
++
++  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 Error("Failed to read pair adjustment structure");
++  }
++
++  if (format == 1) {
++    if (!ParsePairPosFormat1(font, data, length, value_format1, value_format2,
++                             maxp->num_glyphs)) {
++      return Error("Failed to parse pair pos format 1");
++    }
++  } else if (format == 2) {
++    if (!ParsePairPosFormat2(font, data, length, value_format1, value_format2,
++                             maxp->num_glyphs)) {
++      return Error("Failed to parse pair format 2");
++    }
++  } else {
++    return Error("Bad pos pair format %d", format);
++  }
++
++  if (offset_coverage < subtable.offset() || offset_coverage >= length) {
++    return Error("Bad pair pos offset coverage %d", offset_coverage);
++  }
++  if (!ots::ParseCoverageTable(font, data + offset_coverage,
++                               length - offset_coverage,
++                               maxp->num_glyphs)) {
++    return Error("Failed to parse coverage table");
++  }
++
++  return true;
++}
++
++// Lookup Type 3
++// Cursive Attachment Positioning Subtable
++bool OpenTypeGPOS::ParseCursiveAttachment(const uint8_t *data,
++                                          const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
++  }
++
++  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 Error("Failed to read cursive attachment structure");
++  }
++
++  if (format != 1) {
++    return Error("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 Error("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 Error("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 Error("Bad entry anchor offset %d in entry exit record %d", offset_entry_anchor, i);
++      }
++      if (!ParseAnchorTable(font, data + offset_entry_anchor,
++                            length - offset_entry_anchor)) {
++        return Error("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 Error("Bad exit anchor offset %d in entry exit record %d", offset_exit_anchor, i);
++      }
++      if (!ParseAnchorTable(font, data + offset_exit_anchor,
++                            length - offset_exit_anchor)) {
++        return Error("Failed to parse exit anchor table in entry exit record %d", i);
++      }
++    }
++  }
++
++  if (offset_coverage < subtable.offset() || offset_coverage >= length) {
++    return Error("Bad coverage offset in cursive attachment %d", offset_coverage);
++  }
++  if (!ots::ParseCoverageTable(font, data + offset_coverage,
++                               length - offset_coverage,
++                               maxp->num_glyphs)) {
++    return Error("Failed to parse coverage table in cursive attachment");
++  }
++
++  return true;
++}
++
+ // Lookup Type 4:
+ // MarkToBase Attachment Positioning Subtable
+-bool ParseMarkToBaseAttachment(const ots::Font *font,
+-                               const uint8_t *data, const size_t length) {
+-  return ParseMarkToAttachmentSubtables(font, data, length,
++bool OpenTypeGPOS::ParseMarkToBaseAttachment(const uint8_t *data,
++                                             const size_t length) {
++  return ParseMarkToAttachmentSubtables(GetFont(), data, length,
+                                         GPOS_TYPE_MARK_TO_BASE_ATTACHMENT);
+ }
+ 
+ // Lookup Type 5:
+ // MarkToLigature Attachment Positioning Subtable
+-bool ParseMarkToLigatureAttachment(const ots::Font *font,
+-                                   const uint8_t *data, const size_t length) {
+-  return ParseMarkToAttachmentSubtables(font, data, length,
++bool OpenTypeGPOS::ParseMarkToLigatureAttachment(const uint8_t *data,
++                                                 const size_t length) {
++  return ParseMarkToAttachmentSubtables(GetFont(), data, length,
+                                         GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT);
+ }
+ 
+ // Lookup Type 6:
+ // MarkToMark Attachment Positioning Subtable
+-bool ParseMarkToMarkAttachment(const ots::Font *font,
+-                               const uint8_t *data, const size_t length) {
+-  return ParseMarkToAttachmentSubtables(font, data, length,
++bool OpenTypeGPOS::ParseMarkToMarkAttachment(const uint8_t *data,
++                                             const size_t length) {
++  return ParseMarkToAttachmentSubtables(GetFont(), data, length,
+                                         GPOS_TYPE_MARK_TO_MARK_ATTACHMENT);
+ }
+ 
+ // Lookup Type 7:
+ // Contextual Positioning Subtables
+-bool ParseContextPositioning(const ots::Font *font,
+-                             const uint8_t *data, const size_t length) {
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-  ots::OpenTypeGPOS *gpos = static_cast<ots::OpenTypeGPOS*>(
+-      font->GetTypedTable(OTS_TAG_GPOS));
+-  if (!gpos) {
+-    return OTS_FAILURE_MSG("Internal error!");
+-  }
+-  return ots::ParseContextSubtable(font, data, length, maxp->num_glyphs,
+-                                   gpos->num_lookups);
+-}
++// OpenTypeLayoutTable::ParseContextSubtable()
+ 
+ // Lookup Type 8:
+ // Chaining Contexual Positioning Subtable
+-bool ParseChainedContextPositioning(const ots::Font *font,
+-                                    const uint8_t *data, const size_t length) {
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-  ots::OpenTypeGPOS *gpos = static_cast<ots::OpenTypeGPOS*>(
+-      font->GetTypedTable(OTS_TAG_GPOS));
+-  if (!gpos) {
+-    return OTS_FAILURE_MSG("Internal error!");
+-  }
+-  return ots::ParseChainingContextSubtable(font, data, length,
+-                                           maxp->num_glyphs,
+-                                           gpos->num_lookups);
+-}
++// OpenTypeLayoutTable::ParseChainingContextSubtable()
+ 
+ // Lookup Type 9:
+ // Extension Positioning
+-bool ParseExtensionPositioning(const ots::Font *font,
+-                               const uint8_t *data, const size_t length) {
+-  return ots::ParseExtensionSubtable(font, data, length,
+-                                     &kGposLookupSubtableParser);
++// OpenTypeLayoutTable::ParseExtensionSubtable
++
++
++bool OpenTypeGPOS::ValidLookupSubtableType(const uint16_t lookup_type,
++                                           bool extension) const {
++  if (extension && lookup_type == GPOS_TYPE_EXTENSION_POSITIONING)
++    return false;
++  return lookup_type >= GPOS_TYPE_SINGLE_ADJUSTMENT && lookup_type < GPOS_TYPE_RESERVED;
+ }
+ 
+-}  // namespace
+-
+-namespace ots {
+-
+-bool OpenTypeGPOS::Parse(const uint8_t *data, size_t length) {
+-  Font *font = GetFont();
+-  Buffer table(data, length);
+-
+-  uint16_t version_major = 0, version_minor = 0;
+-  uint16_t offset_script_list = 0;
+-  uint16_t offset_feature_list = 0;
+-  uint16_t offset_lookup_list = 0;
+-  uint32_t offset_feature_variations = 0;
+-  if (!table.ReadU16(&version_major) ||
+-      !table.ReadU16(&version_minor) ||
+-      !table.ReadU16(&offset_script_list) ||
+-      !table.ReadU16(&offset_feature_list) ||
+-      !table.ReadU16(&offset_lookup_list)) {
+-    return Error("Incomplete table");
+-  }
+-
+-  if (version_major != 1 || version_minor > 1) {
+-    return Error("Bad version");
+-  }
+-
+-  if (version_minor > 0) {
+-    if (!table.ReadU32(&offset_feature_variations)) {
+-      return Error("Incomplete table");
+-    }
+-  }
+-
+-  const size_t header_size =
+-    (version_minor == 0) ? kGposHeaderSize_1_0 : kGposHeaderSize_1_1;
+-
+-  if (offset_lookup_list) {
+-    if (offset_lookup_list < header_size || offset_lookup_list >= length) {
+-      return Error("Bad lookup list offset in table header");
+-    }
+-
+-    if (!ParseLookupListTable(font, data + offset_lookup_list,
+-                              length - offset_lookup_list,
+-                              &kGposLookupSubtableParser,
+-                              &this->num_lookups)) {
+-      return Error("Failed to parse lookup list table");
+-    }
++bool OpenTypeGPOS::ParseLookupSubtable(const uint8_t *data, const size_t length,
++                                       const uint16_t lookup_type) {
++  switch (lookup_type) {
++    case GPOS_TYPE_SINGLE_ADJUSTMENT:
++      return ParseSingleAdjustment(data, length);
++    case GPOS_TYPE_PAIR_ADJUSTMENT:
++      return ParsePairAdjustment(data, length);
++    case GPOS_TYPE_CURSIVE_ATTACHMENT:
++      return ParseCursiveAttachment(data, length);
++    case GPOS_TYPE_MARK_TO_BASE_ATTACHMENT:
++      return ParseMarkToBaseAttachment(data, length);
++    case GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT:
++      return ParseMarkToLigatureAttachment(data, length);
++    case GPOS_TYPE_MARK_TO_MARK_ATTACHMENT:
++      return ParseMarkToMarkAttachment(data, length);
++    case GPOS_TYPE_CONTEXT_POSITIONING:
++      return ParseContextSubtable(data, length);
++    case GPOS_TYPE_CHAINED_CONTEXT_POSITIONING:
++      return ParseChainingContextSubtable(data, length);
++    case GPOS_TYPE_EXTENSION_POSITIONING:
++      return ParseExtensionSubtable(data, length);
+   }
+-
+-  uint16_t num_features = 0;
+-  if (offset_feature_list) {
+-    if (offset_feature_list < header_size || offset_feature_list >= length) {
+-      return Error("Bad feature list offset in table header");
+-    }
+-
+-    if (!ParseFeatureListTable(font, data + offset_feature_list,
+-                               length - offset_feature_list, this->num_lookups,
+-                               &num_features)) {
+-      return Error("Failed to parse feature list table");
+-    }
+-  }
+-
+-  if (offset_script_list) {
+-    if (offset_script_list < header_size || offset_script_list >= length) {
+-      return Error("Bad script list offset in table header");
+-    }
+-
+-    if (!ParseScriptListTable(font, data + offset_script_list,
+-                              length - offset_script_list, num_features)) {
+-      return Error("Failed to parse script list table");
+-    }
+-  }
+-
+-  if (offset_feature_variations) {
+-    if (offset_feature_variations < header_size || offset_feature_variations >= length) {
+-      return Error("Bad feature variations offset in table header");
+-    }
+-
+-    if (!ParseFeatureVariationsTable(font, data + offset_feature_variations,
+-                                     length - offset_feature_variations,
+-                                     this->num_lookups)) {
+-      return Error("Failed to parse feature variations table");
+-    }
+-  }
+-
+-  this->m_data = data;
+-  this->m_length = length;
+-  return true;
+-}
+-
+-bool OpenTypeGPOS::Serialize(OTSStream *out) {
+-  if (!out->Write(this->m_data, this->m_length)) {
+-    return Error("Failed to write GPOS table");
+-  }
+-
+-  return true;
++  return false;
+ }
+ 
+ }  // namespace ots
+ 
+ #undef TABLE_NAME
+diff --git a/gfx/ots/src/gpos.h b/gfx/ots/src/gpos.h
+--- a/gfx/ots/src/gpos.h
++++ b/gfx/ots/src/gpos.h
+@@ -1,35 +1,35 @@
+ // Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+ 
+ #ifndef OTS_GPOS_H_
+ #define OTS_GPOS_H_
+ 
+ #include "ots.h"
++#include "layout.h"
+ 
+ namespace ots {
+ 
+-class OpenTypeGPOS : public Table {
++class OpenTypeGPOS : public OpenTypeLayoutTable {
+  public:
+   explicit OpenTypeGPOS(Font *font, uint32_t tag)
+-      : Table(font, tag, tag),
+-        num_lookups(0),
+-        m_data(NULL),
+-        m_length(0) {
+-  }
+-
+-  bool Parse(const uint8_t *data, size_t length);
+-  bool Serialize(OTSStream *out);
+-
+-  // Number of lookups in GPOS table
+-  uint16_t num_lookups;
++      : OpenTypeLayoutTable(font, tag, tag) { }
+ 
+  private:
+-  const uint8_t *m_data;
+-  size_t m_length;
++  bool ValidLookupSubtableType(const uint16_t lookup_type,
++                               bool extension = false) const;
++  bool ParseLookupSubtable(const uint8_t *data, const size_t length,
++                           const uint16_t lookup_type);
++
++  bool ParseSingleAdjustment(const uint8_t *data, const size_t length);
++  bool ParsePairAdjustment(const uint8_t *data, const size_t length);
++  bool ParseCursiveAttachment(const uint8_t *data, const size_t length);
++  bool ParseMarkToBaseAttachment(const uint8_t *data, const size_t length);
++  bool ParseMarkToLigatureAttachment(const uint8_t *data, const size_t length);
++  bool ParseMarkToMarkAttachment(const uint8_t *data, const size_t length);
+ };
+ 
+ }  // namespace ots
+ 
+ #endif
+ 
+diff --git a/gfx/ots/src/gsub.cc b/gfx/ots/src/gsub.cc
+--- a/gfx/ots/src/gsub.cc
++++ b/gfx/ots/src/gsub.cc
+@@ -12,131 +12,28 @@
+ 
+ // GSUB - The Glyph Substitution Table
+ // http://www.microsoft.com/typography/otspec/gsub.htm
+ 
+ #define TABLE_NAME "GSUB"
+ 
+ namespace {
+ 
+-// The GSUB header size for table version 1.0
+-const size_t kGsubHeaderSize_1_0 = 4 + 3 * 2;
+-// GSUB header size v1.1
+-const size_t kGsubHeaderSize_1_1 = 4 + 3 * 2 + 4;
+-
+ 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,
+   GSUB_TYPE_EXTENSION_SUBSTITUTION = 7,
+   GSUB_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE = 8,
+   GSUB_TYPE_RESERVED = 9
+ };
+ 
+-// Lookup type parsers.
+-bool ParseSingleSubstitution(const ots::Font *font,
+-                             const uint8_t *data, const size_t length);
+-bool ParseMutipleSubstitution(const ots::Font *font,
+-                              const uint8_t *data, const size_t length);
+-bool ParseAlternateSubstitution(const ots::Font *font,
+-                                const uint8_t *data, const size_t length);
+-bool ParseLigatureSubstitution(const ots::Font *font,
+-      const uint8_t *data, const size_t length);
+-bool ParseContextSubstitution(const ots::Font *font,
+-                              const uint8_t *data, const size_t length);
+-bool ParseChainingContextSubstitution(const ots::Font *font,
+-                                      const uint8_t *data,
+-                                      const size_t length);
+-bool ParseExtensionSubstitution(const ots::Font *font,
+-                                const uint8_t *data, const size_t length);
+-bool ParseReverseChainingContextSingleSubstitution(
+-    const ots::Font *font, const uint8_t *data, const size_t length);
+-
+-const ots::LookupSubtableParser::TypeParser kGsubTypeParsers[] = {
+-  {GSUB_TYPE_SINGLE, ParseSingleSubstitution},
+-  {GSUB_TYPE_MULTIPLE, ParseMutipleSubstitution},
+-  {GSUB_TYPE_ALTERNATE, ParseAlternateSubstitution},
+-  {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}
+-};
+-
+-const ots::LookupSubtableParser kGsubLookupSubtableParser = {
+-  arraysize(kGsubTypeParsers),
+-  GSUB_TYPE_EXTENSION_SUBSTITUTION, kGsubTypeParsers
+-};
+-
+-// Lookup Type 1:
+-// Single Substitution Subtable
+-bool ParseSingleSubstitution(const ots::Font *font,
+-                             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_MSG("Failed to read single subst table header");
+-  }
+-
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-  const uint16_t num_glyphs = maxp->num_glyphs;
+-  if (format == 1) {
+-    // Parse SingleSubstFormat1
+-    int16_t delta_glyph_id = 0;
+-    if (!subtable.ReadS16(&delta_glyph_id)) {
+-      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_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_MSG("Failed to read glyph cound in format 2 single subst table");
+-    }
+-    if (glyph_count > num_glyphs) {
+-      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_MSG("Failed to read substitution %d in format 2 single subst table", i);
+-      }
+-      if (substitute >= num_glyphs) {
+-        return OTS_FAILURE_MSG("too large substitute: %u", substitute);
+-      }
+-    }
+-  } else {
+-    return OTS_FAILURE_MSG("Bad single subst table format %d", format);
+-  }
+-
+-  if (offset_coverage < subtable.offset() || offset_coverage >= length) {
+-    return OTS_FAILURE_MSG("Bad coverage offset %x", offset_coverage);
+-  }
+-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+-                               length - offset_coverage, num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table");
+-  }
+-
+-  return true;
+-}
+-
+ bool ParseSequenceTable(const ots::Font *font,
+                         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_MSG("Failed to read glyph count in sequence table");
+@@ -152,72 +49,16 @@ bool ParseSequenceTable(const ots::Font 
+     if (substitute >= num_glyphs) {
+       return OTS_FAILURE_MSG("Bad substitution (%d) %d > %d", i, substitute, num_glyphs);
+     }
+   }
+ 
+   return true;
+ }
+ 
+-// Lookup Type 2:
+-// Multiple Substitution Subtable
+-bool ParseMutipleSubstitution(const ots::Font *font,
+-                              const uint8_t *data, const size_t length) {
+-  ots::Buffer subtable(data, length);
+-
+-  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_MSG("Can't read header of multiple subst table");
+-  }
+-
+-  if (format != 1) {
+-    return OTS_FAILURE_MSG("Bad multiple subst table format %d", format);
+-  }
+-
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-  const uint16_t num_glyphs = maxp->num_glyphs;
+-  const unsigned sequence_end = static_cast<unsigned>(6) +
+-      sequence_count * 2;
+-  if (sequence_end > std::numeric_limits<uint16_t>::max()) {
+-    return OTS_FAILURE_MSG("Bad sequence 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_MSG("Failed to read sequence offset for sequence %d", i);
+-    }
+-    if (offset_sequence < sequence_end || offset_sequence >= length) {
+-      return OTS_FAILURE_MSG("Bad sequence offset %d for sequence %d", offset_sequence, i);
+-    }
+-    if (!ParseSequenceTable(font, data + offset_sequence, length - offset_sequence,
+-                            num_glyphs)) {
+-      return OTS_FAILURE_MSG("Failed to parse sequence table %d", i);
+-    }
+-  }
+-
+-  if (offset_coverage < sequence_end || offset_coverage >= length) {
+-    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
+-  }
+-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+-                               length - offset_coverage, num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table");
+-  }
+-
+-  return true;
+-}
+-
+ bool ParseAlternateSetTable(const ots::Font *font,
+                             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_MSG("Failed to read alternate set header");
+@@ -232,74 +73,16 @@ bool ParseAlternateSetTable(const ots::F
+     }
+     if (alternate >= num_glyphs) {
+       return OTS_FAILURE_MSG("Too large alternate: %u", alternate);
+     }
+   }
+   return true;
+ }
+ 
+-// Lookup Type 3:
+-// Alternate Substitution Subtable
+-bool ParseAlternateSubstitution(const ots::Font *font,
+-                                const uint8_t *data, const size_t length) {
+-  ots::Buffer subtable(data, length);
+-
+-  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_MSG("Can't read alternate subst header");
+-  }
+-
+-  if (format != 1) {
+-    return OTS_FAILURE_MSG("Bad alternate subst table format %d", format);
+-  }
+-
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-  const uint16_t num_glyphs = maxp->num_glyphs;
+-  const unsigned alternate_set_end = static_cast<unsigned>(6) +
+-      alternate_set_count * 2;
+-  if (alternate_set_end > std::numeric_limits<uint16_t>::max()) {
+-    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_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_MSG("Bad alternate set offset %d for set %d", offset_alternate_set, i);
+-    }
+-    if (!ParseAlternateSetTable(font, data + offset_alternate_set,
+-                                length - offset_alternate_set,
+-                                num_glyphs)) {
+-      return OTS_FAILURE_MSG("Failed to parse alternate set");
+-    }
+-  }
+-
+-  if (offset_coverage < alternate_set_end || offset_coverage >= length) {
+-    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
+-  }
+-  if (!ots::ParseCoverageTable(font, data + offset_coverage,
+-                               length - offset_coverage, num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table");
+-  }
+-
+-  return true;
+-}
+-
+ bool ParseLigatureTable(const ots::Font *font,
+                         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;
+ 
+@@ -354,313 +137,397 @@ bool ParseLigatureSetTable(const ots::Fo
+                             num_glyphs)) {
+       return OTS_FAILURE_MSG("Failed to parse ligature %d", i);
+     }
+   }
+ 
+   return true;
+ }
+ 
++}  // namespace
++
++namespace ots {
++
++// Lookup Type 1:
++// Single Substitution Subtable
++bool OpenTypeGSUB::ParseSingleSubstitution(const uint8_t *data,
++                                           const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
++
++  uint16_t format = 0;
++  uint16_t offset_coverage = 0;
++
++  if (!subtable.ReadU16(&format) ||
++      !subtable.ReadU16(&offset_coverage)) {
++    return Error("Failed to read single subst table header");
++  }
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
++  }
++  const uint16_t num_glyphs = maxp->num_glyphs;
++  if (format == 1) {
++    // Parse SingleSubstFormat1
++    int16_t delta_glyph_id = 0;
++    if (!subtable.ReadS16(&delta_glyph_id)) {
++      return Error("Failed to read glyph shift from format 1 single subst table");
++    }
++    if (std::abs(delta_glyph_id) >= num_glyphs) {
++      return Error("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 Error("Failed to read glyph cound in format 2 single subst table");
++    }
++    if (glyph_count > num_glyphs) {
++      return Error("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 Error("Failed to read substitution %d in format 2 single subst table", i);
++      }
++      if (substitute >= num_glyphs) {
++        return Error("too large substitute: %u", substitute);
++      }
++    }
++  } else {
++    return Error("Bad single subst table format %d", format);
++  }
++
++  if (offset_coverage < subtable.offset() || offset_coverage >= length) {
++    return Error("Bad coverage offset %x", offset_coverage);
++  }
++  if (!ots::ParseCoverageTable(font, data + offset_coverage,
++                               length - offset_coverage, num_glyphs)) {
++    return Error("Failed to parse coverage table");
++  }
++
++  return true;
++}
++
++// Lookup Type 2:
++// Multiple Substitution Subtable
++bool OpenTypeGSUB::ParseMutipleSubstitution(const uint8_t *data,
++                                            const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
++
++  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 Error("Can't read header of multiple subst table");
++  }
++
++  if (format != 1) {
++    return Error("Bad multiple subst table format %d", format);
++  }
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
++  }
++  const uint16_t num_glyphs = maxp->num_glyphs;
++  const unsigned sequence_end = static_cast<unsigned>(6) +
++      sequence_count * 2;
++  if (sequence_end > std::numeric_limits<uint16_t>::max()) {
++    return Error("Bad sequence 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 Error("Failed to read sequence offset for sequence %d", i);
++    }
++    if (offset_sequence < sequence_end || offset_sequence >= length) {
++      return Error("Bad sequence offset %d for sequence %d", offset_sequence, i);
++    }
++    if (!ParseSequenceTable(font, data + offset_sequence, length - offset_sequence,
++                            num_glyphs)) {
++      return Error("Failed to parse sequence table %d", i);
++    }
++  }
++
++  if (offset_coverage < sequence_end || offset_coverage >= length) {
++    return Error("Bad coverage offset %d", offset_coverage);
++  }
++  if (!ots::ParseCoverageTable(font, data + offset_coverage,
++                               length - offset_coverage, num_glyphs)) {
++    return Error("Failed to parse coverage table");
++  }
++
++  return true;
++}
++
++// Lookup Type 3:
++// Alternate Substitution Subtable
++bool OpenTypeGSUB::ParseAlternateSubstitution(const uint8_t *data,
++                                              const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
++
++  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 Error("Can't read alternate subst header");
++  }
++
++  if (format != 1) {
++    return Error("Bad alternate subst table format %d", format);
++  }
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
++  }
++  const uint16_t num_glyphs = maxp->num_glyphs;
++  const unsigned alternate_set_end = static_cast<unsigned>(6) +
++      alternate_set_count * 2;
++  if (alternate_set_end > std::numeric_limits<uint16_t>::max()) {
++    return Error("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 Error("Can't read alternate set offset for set %d", i);
++    }
++    if (offset_alternate_set < alternate_set_end ||
++        offset_alternate_set >= length) {
++      return Error("Bad alternate set offset %d for set %d", offset_alternate_set, i);
++    }
++    if (!ParseAlternateSetTable(font, data + offset_alternate_set,
++                                length - offset_alternate_set,
++                                num_glyphs)) {
++      return Error("Failed to parse alternate set");
++    }
++  }
++
++  if (offset_coverage < alternate_set_end || offset_coverage >= length) {
++    return Error("Bad coverage offset %d", offset_coverage);
++  }
++  if (!ots::ParseCoverageTable(font, data + offset_coverage,
++                               length - offset_coverage, num_glyphs)) {
++    return Error("Failed to parse coverage table");
++  }
++
++  return true;
++}
++
+ // Lookup Type 4:
+ // Ligature Substitution Subtable
+-bool ParseLigatureSubstitution(const ots::Font *font,
+-                               const uint8_t *data, const size_t length) {
+-  ots::Buffer subtable(data, length);
++bool OpenTypeGSUB::ParseLigatureSubstitution(const uint8_t *data,
++                                             const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
+ 
+   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_MSG("Failed to read ligature substitution header");
++    return Error("Failed to read ligature substitution header");
+   }
+ 
+   if (format != 1) {
+-    return OTS_FAILURE_MSG("Bad ligature substitution table format %d", format);
++    return Error("Bad ligature substitution table format %d", format);
+   }
+ 
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+       font->GetTypedTable(OTS_TAG_MAXP));
+   if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
++    return Error("Required maxp table missing");
+   }
+   const uint16_t num_glyphs = maxp->num_glyphs;
+   const unsigned ligature_set_end = static_cast<unsigned>(6) +
+       lig_set_count * 2;
+   if (ligature_set_end > std::numeric_limits<uint16_t>::max()) {
+-    return OTS_FAILURE_MSG("Bad end of ligature set %d in ligature substitution table", ligature_set_end);
++    return Error("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_MSG("Can't read ligature set offset %d", i);
++      return Error("Can't read ligature set offset %d", i);
+     }
+     if (offset_ligature_set < ligature_set_end ||
+         offset_ligature_set >= length) {
+-      return OTS_FAILURE_MSG("Bad ligature set offset %d for set %d", offset_ligature_set, i);
++      return Error("Bad ligature set offset %d for set %d", offset_ligature_set, i);
+     }
+     if (!ParseLigatureSetTable(font, data + offset_ligature_set,
+                                length - offset_ligature_set, num_glyphs)) {
+-      return OTS_FAILURE_MSG("Failed to parse ligature set %d", i);
++      return Error("Failed to parse ligature set %d", i);
+     }
+   }
+ 
+   if (offset_coverage < ligature_set_end || offset_coverage >= length) {
+-    return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage);
++    return Error("Bad coverage offset %d", offset_coverage);
+   }
+   if (!ots::ParseCoverageTable(font, data + offset_coverage,
+                                length - offset_coverage, num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table");
++    return Error("Failed to parse coverage table");
+   }
+ 
+   return true;
+ }
+ 
+ // Lookup Type 5:
+ // Contextual Substitution Subtable
+-bool ParseContextSubstitution(const ots::Font *font,
+-                              const uint8_t *data, const size_t length) {
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-  ots::OpenTypeGSUB *gsub = static_cast<ots::OpenTypeGSUB*>(
+-      font->GetTypedTable(OTS_TAG_GSUB));
+-  if (!gsub) {
+-    return OTS_FAILURE_MSG("Internal error!");
+-  }
+-  return ots::ParseContextSubtable(font, data, length, maxp->num_glyphs,
+-                                   gsub->num_lookups);
+-}
++// OpenTypeLayoutTable::ParseContextSubtable()
+ 
+ // Lookup Type 6:
+ // Chaining Contextual Substitution Subtable
+-bool ParseChainingContextSubstitution(const ots::Font *font,
+-                                      const uint8_t *data,
+-                                      const size_t length) {
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
+-      font->GetTypedTable(OTS_TAG_MAXP));
+-  if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
+-  }
+-  ots::OpenTypeGSUB *gsub = static_cast<ots::OpenTypeGSUB*>(
+-      font->GetTypedTable(OTS_TAG_GSUB));
+-  if (!gsub) {
+-    return OTS_FAILURE_MSG("Internal error!");
+-  }
+-  return ots::ParseChainingContextSubtable(font, data, length,
+-                                           maxp->num_glyphs,
+-                                           gsub->num_lookups);
+-}
++// OpenTypeLayoutTable::ParseChainingContextSubtable
+ 
+ // Lookup Type 7:
+ // Extension Substition
+-bool ParseExtensionSubstitution(const ots::Font *font,
+-                                const uint8_t *data, const size_t length) {
+-  return ots::ParseExtensionSubtable(font, data, length,
+-                                     &kGsubLookupSubtableParser);
+-}
++// OpenTypeLayoutTable::ParseExtensionSubtable
+ 
+ // Lookup Type 8:
+ // Reverse Chaining Contexual Single Substitution Subtable
+-bool ParseReverseChainingContextSingleSubstitution(
+-    const ots::Font *font, const uint8_t *data, const size_t length) {
+-  ots::Buffer subtable(data, length);
++bool OpenTypeGSUB::ParseReverseChainingContextSingleSubstitution(const uint8_t *data,
++                                                                 const size_t length) {
++  Font* font = GetFont();
++  Buffer subtable(data, length);
+ 
+   uint16_t format = 0;
+   uint16_t offset_coverage = 0;
+ 
+   if (!subtable.ReadU16(&format) ||
+       !subtable.ReadU16(&offset_coverage)) {
+-    return OTS_FAILURE_MSG("Failed to read reverse chaining header");
++    return Error("Failed to read reverse chaining header");
+   }
+ 
+-  ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>(
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
+       font->GetTypedTable(OTS_TAG_MAXP));
+   if (!maxp) {
+-    return OTS_FAILURE_MSG("Required maxp table missing");
++    return Error("Required maxp table missing");
+   }
+   const uint16_t num_glyphs = maxp->num_glyphs;
+ 
+   uint16_t backtrack_glyph_count = 0;
+   if (!subtable.ReadU16(&backtrack_glyph_count)) {
+-    return OTS_FAILURE_MSG("Failed to read backtrack glyph count in reverse chaining table");
++    return Error("Failed to read backtrack glyph count in reverse chaining table");
+   }
+   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_MSG("Failed to read backtrack offset %d", i);
++      return Error("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_MSG("Failed to read look ahead glyph count");
++    return Error("Failed to read look ahead 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_MSG("Can't read look ahead offset %d", i);
++      return Error("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_MSG("Can't read glyph count in reverse chaining table");
++    return Error("Can't read glyph count in reverse chaining table");
+   }
+   for (unsigned i = 0; i < glyph_count; ++i) {
+     uint16_t substitute = 0;
+     if (!subtable.ReadU16(&substitute)) {
+-      return OTS_FAILURE_MSG("Failed to read substitution %d reverse chaining table", i);
++      return Error("Failed to read substitution %d reverse chaining table", i);
+     }
+     if (substitute >= num_glyphs) {
+-      return OTS_FAILURE_MSG("Bad substitute glyph %d in reverse chaining table substitution %d", substitute, i);
++      return Error("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_MSG("Bad substitute end offset in reverse chaining table");
++    return Error("Bad substitute end offset in reverse chaining table");
+   }
+ 
+   if (offset_coverage < substitute_end || offset_coverage >= length) {
+-    return OTS_FAILURE_MSG("Bad coverage offset %d in reverse chaining table", offset_coverage);
++    return Error("Bad coverage offset %d in reverse chaining table", offset_coverage);
+   }
+   if (!ots::ParseCoverageTable(font, data + offset_coverage,
+                                length - offset_coverage, num_glyphs)) {
+-    return OTS_FAILURE_MSG("Failed to parse coverage table in reverse chaining table");
++    return Error("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_MSG("Bad backtrack offset %d for backtrack %d in reverse chaining table", offsets_backtrack[i], i);
++      return Error("Bad backtrack offset %d for backtrack %d in reverse chaining table", offsets_backtrack[i], i);
+     }
+     if (!ots::ParseCoverageTable(font, data + offsets_backtrack[i],
+                                  length - offsets_backtrack[i], num_glyphs)) {
+-      return OTS_FAILURE_MSG("Failed to parse coverage table for backtrack %d in reverse chaining table", i);
++      return Error("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_MSG("Bad lookahead offset %d for lookahead %d in reverse chaining table", offsets_lookahead[i], i);
++      return Error("Bad lookahead offset %d for lookahead %d in reverse chaining table", offsets_lookahead[i], i);
+     }
+     if (!ots::ParseCoverageTable(font, data + offsets_lookahead[i],
+                                  length - offsets_lookahead[i], num_glyphs)) {
+-      return OTS_FAILURE_MSG("Failed to parse lookahead coverage table %d in reverse chaining table", i);
++      return Error("Failed to parse lookahead coverage table %d in reverse chaining table", i);
+     }
+   }
+ 
+   return true;
+ }
+ 
+-}  // namespace
+-
+-namespace ots {
+-
+-bool OpenTypeGSUB::Parse(const uint8_t *data, size_t length) {
+-  // Parsing gsub table requires |maxp->num_glyphs|
+-  Font *font = GetFont();
+-  Buffer table(data, length);
+-
+-  uint16_t version_major = 0, version_minor = 0;
+-  uint16_t offset_script_list = 0;
+-  uint16_t offset_feature_list = 0;
+-  uint16_t offset_lookup_list = 0;
+-  uint32_t offset_feature_variations = 0;
+-  if (!table.ReadU16(&version_major) ||
+-      !table.ReadU16(&version_minor) ||
+-      !table.ReadU16(&offset_script_list) ||
+-      !table.ReadU16(&offset_feature_list) ||
+-      !table.ReadU16(&offset_lookup_list)) {
+-    return Error("Incomplete table");
+-  }
+-
+-  if (version_major != 1 || version_minor > 1) {
+-    return Error("Bad version");
+-  }
+-
+-  if (version_minor > 0) {
+-    if (!table.ReadU32(&offset_feature_variations)) {
+-      return Error("Incomplete table");
+-    }
+-  }
+-
+-  const size_t header_size =
+-    (version_minor == 0) ? kGsubHeaderSize_1_0 : kGsubHeaderSize_1_1;
+-
+-  if (offset_lookup_list) {
+-    if (offset_lookup_list < header_size || offset_lookup_list >= length) {
+-      return Error("Bad lookup list offset in table header");
+-    }
+-
+-    if (!ParseLookupListTable(font, data + offset_lookup_list,
+-                              length - offset_lookup_list,
+-                              &kGsubLookupSubtableParser,
+-                              &this->num_lookups)) {
+-      return Error("Failed to parse lookup list table");
+-    }
+-  }
+-
+-  uint16_t num_features = 0;
+-  if (offset_feature_list) {
+-    if (offset_feature_list < header_size || offset_feature_list >= length) {
+-      return Error("Bad feature list offset in table header");
+-    }
+-
+-    if (!ParseFeatureListTable(font, data + offset_feature_list,
+-                               length - offset_feature_list, this->num_lookups,
+-                               &num_features)) {
+-      return Error("Failed to parse feature list table");
+-    }
+-  }
+-
+-  if (offset_script_list) {
+-    if (offset_script_list < header_size || offset_script_list >= length) {
+-      return Error("Bad script list offset in table header");
+-    }
+-
+-    if (!ParseScriptListTable(font, data + offset_script_list,
+-                              length - offset_script_list, num_features)) {
+-      return Error("Failed to parse script list table");
+-    }
+-  }
+-
+-  if (offset_feature_variations) {
+-    if (offset_feature_variations < header_size || offset_feature_variations >= length) {
+-      return Error("Bad feature variations offset in table header");
+-    }
+-
+-    if (!ParseFeatureVariationsTable(font, data + offset_feature_variations,
+-                                     length - offset_feature_variations,
+-                                     this->num_lookups)) {
+-      return Error("Failed to parse feature variations table");
+-    }
+-  }
+-
+-  this->m_data = data;
+-  this->m_length = length;
+-  return true;
++bool OpenTypeGSUB::ValidLookupSubtableType(const uint16_t lookup_type,
++                                           bool extension) const {
++  if (extension && lookup_type == GSUB_TYPE_EXTENSION_SUBSTITUTION)
++    return false;
++  return lookup_type >= GSUB_TYPE_SINGLE && lookup_type < GSUB_TYPE_RESERVED;
+ }
+ 
+-bool OpenTypeGSUB::Serialize(OTSStream *out) {
+-  if (!out->Write(this->m_data, this->m_length)) {
+-    return Error("Failed to write GSUB table");
++bool OpenTypeGSUB::ParseLookupSubtable(const uint8_t *data, const size_t length,
++                                       const uint16_t lookup_type) {
++  switch (lookup_type) {
++    case GSUB_TYPE_SINGLE:
++      return ParseSingleSubstitution(data, length);
++    case GSUB_TYPE_MULTIPLE:
++      return ParseMutipleSubstitution(data, length);
++    case GSUB_TYPE_ALTERNATE:
++      return ParseAlternateSubstitution(data, length);
++    case GSUB_TYPE_LIGATURE:
++      return ParseLigatureSubstitution(data, length);
++    case GSUB_TYPE_CONTEXT:
++      return ParseContextSubtable(data, length);
++    case GSUB_TYPE_CHANGING_CONTEXT:
++      return ParseChainingContextSubtable(data, length);
++    case GSUB_TYPE_EXTENSION_SUBSTITUTION:
++      return ParseExtensionSubtable(data, length);
++    case GSUB_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
++      return ParseReverseChainingContextSingleSubstitution(data, length);
+   }
+-
+-  return true;
++  return false;
+ }
+ 
+ }  // namespace ots
+ 
+ #undef TABLE_NAME
+diff --git a/gfx/ots/src/gsub.h b/gfx/ots/src/gsub.h
+--- a/gfx/ots/src/gsub.h
++++ b/gfx/ots/src/gsub.h
+@@ -1,35 +1,34 @@
+ // Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+ 
+ #ifndef OTS_GSUB_H_
+ #define OTS_GSUB_H_
+ 
+ #include "ots.h"
++#include "layout.h"
+ 
+ namespace ots {
+ 
+-class OpenTypeGSUB : public Table {
++class OpenTypeGSUB : public OpenTypeLayoutTable {
+  public:
+   explicit OpenTypeGSUB(Font *font, uint32_t tag)
+-      : Table(font, tag, tag),
+-        num_lookups(0),
+-        m_data(NULL),
+-        m_length(0) {
+-  }
++      : OpenTypeLayoutTable(font, tag, tag) { }
+ 
+-  bool Parse(const uint8_t *data, size_t length);
+-  bool Serialize(OTSStream *out);
++ private:
++  bool ValidLookupSubtableType(uint16_t lookup_type,
++                               bool extension = false) const;
++  bool ParseLookupSubtable(const uint8_t *data, const size_t length,
++                           const uint16_t lookup_type);
+ 
+-  // Number of lookups in GPSUB table
+-  uint16_t num_lookups;
+-
+- //private:
+-  const uint8_t *m_data;
+-  size_t m_length;
++  bool ParseSingleSubstitution(const uint8_t *data, const size_t length);
++  bool ParseMutipleSubstitution(const uint8_t *data, const size_t length);
++  bool ParseAlternateSubstitution(const uint8_t *data, const size_t length);
++  bool ParseLigatureSubstitution(const uint8_t *data, const size_t length);
++  bool ParseReverseChainingContextSingleSubstitution(const uint8_t *data, const size_t length);
+ };
+ 
+ }  // namespace ots
+ 
+ #endif  // OTS_GSUB_H_
+ 
+diff --git a/gfx/ots/src/layout.cc b/gfx/ots/src/layout.cc
+--- a/gfx/ots/src/layout.cc
++++ b/gfx/ots/src/layout.cc
+@@ -4,16 +4,17 @@
+ 
+ #include "layout.h"
+ 
+ #include <limits>
+ #include <vector>
+ 
+ #include "fvar.h"
+ #include "gdef.h"
++#include "maxp.h"
+ 
+ // OpenType Layout Common Table Formats
+ // http://www.microsoft.com/typography/otspec/chapter2.htm
+ 
+ #define TABLE_NAME "Layout" // XXX: use individual table names
+ 
+ namespace {
+ 
+@@ -169,85 +170,16 @@ bool ParseFeatureTable(const ots::Font *
+     // lookup index starts with 0.
+     if (lookup_index >= num_lookups) {
+       return OTS_FAILURE_MSG("Bad lookup index %d for lookup %d", lookup_index, i);
+     }
+   }
+   return true;
+ }
+ 
+-bool ParseLookupTable(ots::Font *font, 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_MSG("Failed to read lookup table header");
+-  }
+-
+-  if (lookup_type == 0 || lookup_type > parser->num_types) {
+-    return OTS_FAILURE_MSG("Bad lookup type %d", lookup_type);
+-  }
+-
+-  bool use_mark_filtering_set = lookup_flag & kUseMarkFilteringSetBit;
+-
+-  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_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_MSG("Failed to read subtable offset %d", i);
+-    }
+-    if (offset_subtable < lookup_table_end ||
+-        offset_subtable >= length) {
+-      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_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_MSG("Failed to read mark filtering set");
+-    }
+-
+-    ots::OpenTypeGDEF *gdef = static_cast<ots::OpenTypeGDEF*>(
+-        font->GetTypedTable(OTS_TAG_GDEF));
+-
+-    if (gdef && (gdef->num_mark_glyph_sets == 0 ||
+-        mark_filtering_set >= gdef->num_mark_glyph_sets)) {
+-      return OTS_FAILURE_MSG("Bad mark filtering set %d", mark_filtering_set);
+-    }
+-  }
+-
+-  // Parse lookup subtables for this lookup type.
+-  for (unsigned i = 0; i < subtable_count; ++i) {
+-    if (!parser->Parse(font, data + subtables[i], length - subtables[i],
+-                       lookup_type)) {
+-      return OTS_FAILURE_MSG("Failed to parse subtable %d", i);
+-    }
+-  }
+-  return true;
+-}
+-
+ bool ParseClassDefFormat1(const ots::Font *font,
+                           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)) {
+@@ -441,17 +373,17 @@ bool ParseRuleSubtable(const ots::Font *
+ 
+   uint16_t glyph_count = 0;
+   uint16_t lookup_count = 0;
+   if (!subtable.ReadU16(&glyph_count) ||
+       !subtable.ReadU16(&lookup_count)) {
+     return OTS_FAILURE_MSG("Failed to read rule subtable header");
+   }
+ 
+-  if (glyph_count == 0 || glyph_count >= num_glyphs) {
++  if (glyph_count == 0) {
+     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_MSG("Failed to read glyph %d", i);
+     }
+     if (glyph_id > num_glyphs) {
+@@ -720,53 +652,47 @@ bool ParseChainRuleSubtable(const ots::F
+                             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_MSG("Failed to read backtrack count in chain rule subtable");
+   }
+-  if (backtrack_count >= num_glyphs) {
+-    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_MSG("Failed to read backtrack glyph %d in chain rule subtable", i);
+     }
+     if (glyph_id > num_glyphs) {
+       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_MSG("Failed to read input count in chain rule subtable");
+   }
+-  if (input_count == 0 || input_count >= num_glyphs) {
++  if (input_count == 0) {
+     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_MSG("Failed to read input glyph %d in chain rule subtable", i);
+     }
+     if (glyph_id > num_glyphs) {
+       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_MSG("Failed to read lookahead count in chain rule subtable");
+   }
+-  if (lookahead_count >= num_glyphs) {
+-    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_MSG("Failed to read lookahead glyph %d in chain rule subtable", i);
+     }
+     if (glyph_id > num_glyphs) {
+       return OTS_FAILURE_MSG("Bad glyph id %d for lookadhead glyph %d in chain rule subtable", glyph_id, i);
+     }
+@@ -873,41 +799,35 @@ bool ParseChainClassRuleSubtable(const o
+ 
+   // 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_MSG("Failed to read backtrack count in chain class rule subtable");
+   }
+-  if (backtrack_count >= num_glyphs) {
+-    return OTS_FAILURE_MSG("Bad backtrack count %d in chain class rule subtable", backtrack_count);
+-  }
+   if (!subtable.Skip(2 * backtrack_count)) {
+     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_MSG("Failed to read input count in chain class rule subtable");
+   }
+-  if (input_count == 0 || input_count >= num_glyphs) {
++  if (input_count == 0) {
+     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_MSG("Failed to skip input offsets in chain class rule subtable");
+   }
+ 
+   uint16_t lookahead_count = 0;
+   if (!subtable.ReadU16(&lookahead_count)) {
+     return OTS_FAILURE_MSG("Failed to read lookahead count in chain class rule subtable");
+   }
+-  if (lookahead_count >= num_glyphs) {
+-    return OTS_FAILURE_MSG("Bad lookahead count %d in chain class rule subtable", lookahead_count);
+-  }
+   if (!subtable.Skip(2 * lookahead_count)) {
+     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_MSG("Failed to read lookup count in chain class rule subtable");
+   }
+@@ -1053,19 +973,16 @@ bool ParseChainContextFormat3(const ots:
+ 
+   uint16_t backtrack_count = 0;
+   // Skip format field.
+   if (!subtable.Skip(2) ||
+       !subtable.ReadU16(&backtrack_count)) {
+     return OTS_FAILURE_MSG("Failed to read backtrack count in chain context format 3");
+   }
+ 
+-  if (backtrack_count >= num_glyphs) {
+-    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_MSG("Failed to read backtrack offset %d in chain context format 3", i);
+     }
+     offsets_backtrack.push_back(offset);
+@@ -1073,19 +990,16 @@ bool ParseChainContextFormat3(const ots:
+   if (offsets_backtrack.size() != backtrack_count) {
+     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_MSG("Failed to read input count in chain context format 3");
+   }
+-  if (input_count >= num_glyphs) {
+-    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_MSG("Failed to read input offset %d in chain context format 3", i);
+     }
+     offsets_input.push_back(offset);
+@@ -1093,19 +1007,16 @@ bool ParseChainContextFormat3(const ots:
+   if (offsets_input.size() != input_count) {
+     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_MSG("Failed ot read lookahead count in chain context format 3");
+   }
+-  if (lookahead_count >= num_glyphs) {
+-    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_MSG("Failed to read lookahead offset %d in chain context format 3", i);
+     }
+     offsets_lookahead.push_back(offset);
+@@ -1160,177 +1071,332 @@ bool ParseChainContextFormat3(const ots:
+                                  length - offsets_lookahead[i], num_glyphs)) {
+       return OTS_FAILURE_MSG("Failed to parse lookahead coverage table %d in chain context format 3", i);
+     }
+   }
+ 
+   return true;
+ }
+ 
++bool ParseFeatureTableSubstitutionTable(const ots::Font *font,
++                                        const uint8_t *data, const size_t length,
++                                        const uint16_t num_lookups) {
++  ots::Buffer subtable(data, length);
++
++  uint16_t version_major = 0;
++  uint16_t version_minor = 0;
++  uint16_t substitution_count = 0;
++  const size_t kFeatureTableSubstitutionHeaderSize = 3 * sizeof(uint16_t);
++
++  if (!subtable.ReadU16(&version_major) ||
++      !subtable.ReadU16(&version_minor) ||
++      !subtable.ReadU16(&substitution_count)) {
++    return OTS_FAILURE_MSG("Failed to read feature table substitution table header");
++  }
++
++  for (uint16_t i = 0; i < substitution_count; i++) {
++    uint16_t feature_index = 0;
++    uint32_t alternate_feature_table_offset = 0;
++    const size_t kFeatureTableSubstitutionRecordSize = sizeof(uint16_t) + sizeof(uint32_t);
++
++    if (!subtable.ReadU16(&feature_index) ||
++        !subtable.ReadU32(&alternate_feature_table_offset)) {
++      return OTS_FAILURE_MSG("Failed to read feature table substitution record");
++    }
++
++    if (alternate_feature_table_offset < kFeatureTableSubstitutionHeaderSize +
++                                         kFeatureTableSubstitutionRecordSize * substitution_count ||
++        alternate_feature_table_offset >= length) {
++      return OTS_FAILURE_MSG("Invalid alternate feature table offset");
++    }
++
++    if (!ParseFeatureTable(font, data + alternate_feature_table_offset,
++                           length - alternate_feature_table_offset, num_lookups)) {
++      return OTS_FAILURE_MSG("Failed to parse alternate feature table");
++    }
++  }
++
++  return true;
++}
++
++bool ParseConditionTable(const ots::Font *font,
++                         const uint8_t *data, const size_t length,
++                         const uint16_t axis_count) {
++  ots::Buffer subtable(data, length);
++
++  uint16_t format = 0;
++  if (!subtable.ReadU16(&format)) {
++    return OTS_FAILURE_MSG("Failed to read condition table format");
++  }
++
++  if (format != 1) {
++    // An unknown format is not an error, but should be ignored per spec.
++    return true;
++  }
++
++  uint16_t axis_index = 0;
++  int16_t filter_range_min_value = 0;
++  int16_t filter_range_max_value = 0;
++  if (!subtable.ReadU16(&axis_index) ||
++      !subtable.ReadS16(&filter_range_min_value) ||
++      !subtable.ReadS16(&filter_range_max_value)) {
++    return OTS_FAILURE_MSG("Failed to read condition table (format 1)");
++  }
++
++  if (axis_index >= axis_count) {
++    return OTS_FAILURE_MSG("Axis index out of range in condition");
++  }
++
++  // Check min/max values are within range -1.0 .. 1.0 and properly ordered
++  if (filter_range_min_value < -0x4000 || // -1.0 in F2DOT14 format
++      filter_range_max_value > 0x4000 || // +1.0 in F2DOT14 format
++      filter_range_min_value > filter_range_max_value) {
++    return OTS_FAILURE_MSG("Invalid filter range in condition");
++  }
++
++  return true;
++}
++
++bool ParseConditionSetTable(const ots::Font *font,
++                            const uint8_t *data, const size_t length,
++                            const uint16_t axis_count) {
++  ots::Buffer subtable(data, length);
++
++  uint16_t condition_count = 0;
++  if (!subtable.ReadU16(&condition_count)) {
++    return OTS_FAILURE_MSG("Failed to read condition count");
++  }
++
++  for (uint16_t i = 0; i < condition_count; i++) {
++    uint32_t condition_offset = 0;
++    if (!subtable.ReadU32(&condition_offset)) {
++      return OTS_FAILURE_MSG("Failed to read condition offset");
++    }
++    if (condition_offset < subtable.offset() || condition_offset >= length) {
++      return OTS_FAILURE_MSG("Offset out of range");
++    }
++    if (!ParseConditionTable(font, data + condition_offset, length - condition_offset,
++                             axis_count)) {
++      return OTS_FAILURE_MSG("Failed to parse condition table");
++    }
++  }
++
++  return true;
++}
++
+ }  // namespace
+ 
+ namespace ots {
+ 
+-bool LookupSubtableParser::Parse(const Font *font, 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(font, data, length)) {
+-        return OTS_FAILURE_MSG("Failed to parse lookup subtable %d", i);
+-      }
+-      return true;
+-    }
+-  }
+-  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 ots::Font *font,
+-                          const uint8_t *data, const size_t length,
+-                          const uint16_t num_features) {
++bool OpenTypeLayoutTable::ParseScriptListTable(const uint8_t *data, const size_t length) {
++  Font* font = GetFont();
+   Buffer subtable(data, length);
+ 
+   uint16_t script_count = 0;
+   if (!subtable.ReadU16(&script_count)) {
+-    return OTS_FAILURE_MSG("Failed to read script count in script list table");
++    return Error("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_MSG("Bad end of script record %d in script list table", script_record_end);
++    return Error("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_MSG("Failed to read script record %d in script list table", i);
++      return Error("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_MSG("Bad record offset %d for script %c%c%c%c entry %d in script list table", record.offset, OTS_UNTAG(record.tag), i);
++      return Error("Bad record offset %d for script %c%c%c%c entry %d in script list table", record.offset, OTS_UNTAG(record.tag), i);
+     }
+     script_list.push_back(record);
+   }
+   if (script_list.size() != script_count) {
+-    return OTS_FAILURE_MSG("Bad script list size %ld in script list table", script_list.size());
++    return Error("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(font, data + script_list[i].offset,
+                           length - script_list[i].offset,
+-                          script_list[i].tag, num_features)) {
+-      return OTS_FAILURE_MSG("Failed to parse script table %d", i);
++                          script_list[i].tag, m_num_features)) {
++      return Error("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 ots::Font *font,
+-                           const uint8_t *data, const size_t length,
+-                           const uint16_t num_lookups,
+-                           uint16_t* num_features) {
++bool OpenTypeLayoutTable::ParseFeatureListTable(const uint8_t *data, const size_t length) {
++  Font *font = GetFont();
+   Buffer subtable(data, length);
+ 
+   uint16_t feature_count = 0;
+   if (!subtable.ReadU16(&feature_count)) {
+-    return OTS_FAILURE_MSG("Failed to read feature count");
++    return Error("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_MSG("Bad end of feature record %d", feature_record_end);
++    return Error("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_MSG("Failed to read feature header %d", i);
++      return Error("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_MSG("Bad feature offset %d for feature %d %c%c%c%c", feature_records[i].offset, i, OTS_UNTAG(feature_records[i].tag));
++      return Error("Bad feature offset %d for feature %d %c%c%c%c", feature_records[i].offset, i, OTS_UNTAG(feature_records[i].tag));
+     }
+   }
+ 
+   for (unsigned i = 0; i < feature_count; ++i) {
+     if (!ParseFeatureTable(font, data + feature_records[i].offset,
+-                           length - feature_records[i].offset, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse feature table %d", i);
++                           length - feature_records[i].offset, m_num_lookups)) {
++      return Error("Failed to parse feature table %d", i);
+     }
+   }
+-  *num_features = feature_count;
++  m_num_features = feature_count;
++  return true;
++}
++
++bool OpenTypeLayoutTable::ParseLookupTable(const uint8_t *data,
++                                           const size_t length) {
++  Font* font = GetFont(); 
++  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 Error("Failed to read lookup table header");
++  }
++
++  if (!ValidLookupSubtableType(lookup_type)) {
++    return Error("Bad lookup type %d", lookup_type);
++  }
++
++  bool use_mark_filtering_set = lookup_flag & kUseMarkFilteringSetBit;
++
++  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 Error("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 Error("Failed to read subtable offset %d", i);
++    }
++    if (offset_subtable < lookup_table_end ||
++        offset_subtable >= length) {
++      return Error("Bad subtable offset %d for subtable %d", offset_subtable, i);
++    }
++    subtables.push_back(offset_subtable);
++  }
++  if (subtables.size() != subtable_count) {
++    return Error("Bad subtable size %ld", subtables.size());
++  }
++
++  if (use_mark_filtering_set) {
++    uint16_t mark_filtering_set = 0;
++    if (!subtable.ReadU16(&mark_filtering_set)) {
++      return Error("Failed to read mark filtering set");
++    }
++
++    OpenTypeGDEF *gdef = static_cast<OpenTypeGDEF*>(
++        font->GetTypedTable(OTS_TAG_GDEF));
++
++    if (gdef && (gdef->num_mark_glyph_sets == 0 ||
++        mark_filtering_set >= gdef->num_mark_glyph_sets)) {
++      return Error("Bad mark filtering set %d", mark_filtering_set);
++    }
++  }
++
++  // Parse lookup subtables for this lookup type.
++  for (unsigned i = 0; i < subtable_count; ++i) {
++    if (!ParseLookupSubtable(data + subtables[i], length - subtables[i],
++                             lookup_type)) {
++      return Error("Failed to parse subtable %d", i);
++    }
++  }
+   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(Font *font, const uint8_t *data,
+-                          const size_t length,
+-                          const LookupSubtableParser* parser,
+-                          uint16_t *num_lookups) {
++bool OpenTypeLayoutTable::ParseLookupListTable(const uint8_t *data,
++                                               const size_t length) {
+   Buffer subtable(data, length);
+ 
+-  if (!subtable.ReadU16(num_lookups)) {
+-    return OTS_FAILURE_MSG("Failed to read number of lookups");
++  if (!subtable.ReadU16(&m_num_lookups)) {
++    return Error("Failed to read number of lookups");
+   }
+ 
+   std::vector<uint16_t> lookups;
+-  lookups.reserve(*num_lookups);
++  lookups.reserve(m_num_lookups);
+   const unsigned lookup_end =
+-      2 * static_cast<unsigned>(*num_lookups) + 2;
++      2 * static_cast<unsigned>(m_num_lookups) + 2;
+   if (lookup_end > std::numeric_limits<uint16_t>::max()) {
+-    return OTS_FAILURE_MSG("Bad end of lookups %d", lookup_end);
++    return Error("Bad end of lookups %d", lookup_end);
+   }
+-  for (unsigned i = 0; i < *num_lookups; ++i) {
++  for (unsigned i = 0; i < m_num_lookups; ++i) {
+     uint16_t offset = 0;
+     if (!subtable.ReadU16(&offset)) {
+-      return OTS_FAILURE_MSG("Failed to read lookup offset %d", i);
++      return Error("Failed to read lookup offset %d", i);
+     }
+     if (offset < lookup_end || offset >= length) {
+-      return OTS_FAILURE_MSG("Bad lookup offset %d for lookup %d", offset, i);
++      return Error("Bad lookup offset %d for lookup %d", offset, i);
+     }
+     lookups.push_back(offset);
+   }
+-  if (lookups.size() != *num_lookups) {
+-    return OTS_FAILURE_MSG("Bad lookup offsets list size %ld", lookups.size());
++  if (lookups.size() != m_num_lookups) {
++    return Error("Bad lookup offsets list size %ld", lookups.size());
+   }
+ 
+-  for (unsigned i = 0; i < *num_lookups; ++i) {
+-    if (!ParseLookupTable(font, data + lookups[i], length - lookups[i],
+-                          parser)) {
+-      return OTS_FAILURE_MSG("Failed to parse lookup %d", i);
++  for (unsigned i = 0; i < m_num_lookups; ++i) {
++    if (!ParseLookupTable(data + lookups[i], length - lookups[i])) {
++      return Error("Failed to parse lookup %d", i);
+     }
+   }
+ 
+   return true;
+ }
+ 
+ bool ParseClassDefTable(const ots::Font *font,
+                         const uint8_t *data, size_t length,
+@@ -1401,276 +1467,269 @@ bool ParseDeviceTable(const ots::Font *f
+   // Just skip |num_units| * 2 bytes since the compressed data could take
+   // arbitrary values.
+   if (!subtable.Skip(num_units * 2)) {
+     return OTS_FAILURE_MSG("Failed to skip data in device table");
+   }
+   return true;
+ }
+ 
+-bool ParseContextSubtable(const ots::Font *font,
+-                          const uint8_t *data, const size_t length,
+-                          const uint16_t num_glyphs,
+-                          const uint16_t num_lookups) {
++bool OpenTypeLayoutTable::ParseContextSubtable(const uint8_t *data,
++                                               const size_t length) {
++  Font *font = GetFont();
+   Buffer subtable(data, length);
+ 
+   uint16_t format = 0;
+   if (!subtable.ReadU16(&format)) {
+-    return OTS_FAILURE_MSG("Failed to read context subtable format");
++    return Error("Failed to read context subtable format");
++  }
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
+   }
+ 
+   if (format == 1) {
+-    if (!ParseContextFormat1(font, data, length, num_glyphs, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse context format 1 subtable");
++    if (!ParseContextFormat1(font, data, length, maxp->num_glyphs, m_num_lookups)) {
++      return Error("Failed to parse context format 1 subtable");
+     }
+   } else if (format == 2) {
+-    if (!ParseContextFormat2(font, data, length, num_glyphs, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse context format 2 subtable");
++    if (!ParseContextFormat2(font, data, length, maxp->num_glyphs, m_num_lookups)) {
++      return Error("Failed to parse context format 2 subtable");
+     }
+   } else if (format == 3) {
+-    if (!ParseContextFormat3(font, data, length, num_glyphs, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse context format 3 subtable");
++    if (!ParseContextFormat3(font, data, length, maxp->num_glyphs, m_num_lookups)) {
++      return Error("Failed to parse context format 3 subtable");
+     }
+   } else {
+-    return OTS_FAILURE_MSG("Bad context subtable format %d", format);
++    return Error("Bad context subtable format %d", format);
+   }
+ 
+   return true;
+ }
+ 
+-bool ParseChainingContextSubtable(const ots::Font *font,
+-                                  const uint8_t *data, const size_t length,
+-                                  const uint16_t num_glyphs,
+-                                  const uint16_t num_lookups) {
++bool OpenTypeLayoutTable::ParseChainingContextSubtable(const uint8_t *data,
++                                                       const size_t length) {
++  Font *font = GetFont();
+   Buffer subtable(data, length);
+ 
+   uint16_t format = 0;
+   if (!subtable.ReadU16(&format)) {
+-    return OTS_FAILURE_MSG("Failed to read chaining context subtable format");
++    return Error("Failed to read chaining context subtable format");
++  }
++
++  OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>(
++      font->GetTypedTable(OTS_TAG_MAXP));
++  if (!maxp) {
++    return Error("Required maxp table missing");
+   }
+ 
+   if (format == 1) {
+-    if (!ParseChainContextFormat1(font, data, length, num_glyphs, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse chaining context format 1 subtable");
++    if (!ParseChainContextFormat1(font, data, length, maxp->num_glyphs, m_num_lookups)) {
++      return Error("Failed to parse chaining context format 1 subtable");
+     }
+   } else if (format == 2) {
+-    if (!ParseChainContextFormat2(font, data, length, num_glyphs, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse chaining context format 2 subtable");
++    if (!ParseChainContextFormat2(font, data, length, maxp->num_glyphs, m_num_lookups)) {
++      return Error("Failed to parse chaining context format 2 subtable");
+     }
+   } else if (format == 3) {
+-    if (!ParseChainContextFormat3(font, data, length, num_glyphs, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse chaining context format 3 subtable");
++    if (!ParseChainContextFormat3(font, data, length, maxp->num_glyphs, m_num_lookups)) {
++      return Error("Failed to parse chaining context format 3 subtable");
+     }
+   } else {
+-    return OTS_FAILURE_MSG("Bad chaining context subtable format %d", format);
++    return Error("Bad chaining context subtable format %d", format);
+   }
+ 
+   return true;
+ }
+ 
+-bool ParseExtensionSubtable(const Font *font,
+-                            const uint8_t *data, const size_t length,
+-                            const LookupSubtableParser* parser) {
++bool OpenTypeLayoutTable::ParseExtensionSubtable(const uint8_t *data,
++                                                 const size_t length) {
+   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_MSG("Failed to read extension table header");
++    return Error("Failed to read extension table header");
+   }
+ 
+   if (format != 1) {
+-    return OTS_FAILURE_MSG("Bad extension table format %d", format);
++    return Error("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_MSG("Bad lookup type %d in extension table", lookup_type);
++  if (!ValidLookupSubtableType(lookup_type, true)) {
++    return Error("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_MSG("Bad extension offset %d", offset_extension);
++    return Error("Bad extension offset %d", offset_extension);
+   }
+ 
+   // Parse the extension subtable of |lookup_type|.
+-  if (!parser->Parse(font, data + offset_extension, length - offset_extension,
+-                     lookup_type)) {
+-    return OTS_FAILURE_MSG("Failed to parse lookup from extension lookup");
+-  }
+-
+-  return true;
+-}
+-
+-bool ParseConditionTable(const Font *font,
+-                         const uint8_t *data, const size_t length,
+-                         const uint16_t axis_count) {
+-  Buffer subtable(data, length);
+-
+-  uint16_t format = 0;
+-  if (!subtable.ReadU16(&format)) {
+-    return OTS_FAILURE_MSG("Failed to read condition table format");
+-  }
+-
+-  if (format != 1) {
+-    // An unknown format is not an error, but should be ignored per spec.
+-    return true;
+-  }
+-
+-  uint16_t axis_index = 0;
+-  int16_t filter_range_min_value = 0;
+-  int16_t filter_range_max_value = 0;
+-  if (!subtable.ReadU16(&axis_index) ||
+-      !subtable.ReadS16(&filter_range_min_value) ||
+-      !subtable.ReadS16(&filter_range_max_value)) {
+-    return OTS_FAILURE_MSG("Failed to read condition table (format 1)");
+-  }
+-
+-  if (axis_index >= axis_count) {
+-    return OTS_FAILURE_MSG("Axis index out of range in condition");
+-  }
+-
+-  // Check min/max values are within range -1.0 .. 1.0 and properly ordered
+-  if (filter_range_min_value < -0x4000 || // -1.0 in F2DOT14 format
+-      filter_range_max_value > 0x4000 || // +1.0 in F2DOT14 format
+-      filter_range_min_value > filter_range_max_value) {
+-    return OTS_FAILURE_MSG("Invalid filter range in condition");
++  if (!ParseLookupSubtable(data + offset_extension, length - offset_extension,
++                           lookup_type)) {
++    return Error("Failed to parse lookup from extension lookup");
+   }
+ 
+   return true;
+ }
+ 
+-bool ParseConditionSetTable(const Font *font,
+-                            const uint8_t *data, const size_t length,
+-                            const uint16_t axis_count) {
+-  Buffer subtable(data, length);
+-
+-  uint16_t condition_count = 0;
+-  if (!subtable.ReadU16(&condition_count)) {
+-    return OTS_FAILURE_MSG("Failed to read condition count");
+-  }
+-
+-  for (uint16_t i = 0; i < condition_count; i++) {
+-    uint32_t condition_offset = 0;
+-    if (!subtable.ReadU32(&condition_offset)) {
+-      return OTS_FAILURE_MSG("Failed to read condition offset");
+-    }
+-    if (condition_offset < subtable.offset() || condition_offset >= length) {
+-      return OTS_FAILURE_MSG("Offset out of range");
+-    }
+-    if (!ParseConditionTable(font, data + condition_offset, length - condition_offset,
+-                             axis_count)) {
+-      return OTS_FAILURE_MSG("Failed to parse condition table");
+-    }
+-  }
+-
+-  return true;
+-}
+-
+-bool ParseFeatureTableSubstitutionTable(const Font *font,
+-                                        const uint8_t *data, const size_t length,
+-                                        const uint16_t num_lookups) {
+-  Buffer subtable(data, length);
+-
+-  uint16_t version_major = 0;
+-  uint16_t version_minor = 0;
+-  uint16_t substitution_count = 0;
+-  const size_t kFeatureTableSubstitutionHeaderSize = 3 * sizeof(uint16_t);
+-
+-  if (!subtable.ReadU16(&version_major) ||
+-      !subtable.ReadU16(&version_minor) ||
+-      !subtable.ReadU16(&substitution_count)) {
+-    return OTS_FAILURE_MSG("Failed to read feature table substitution table header");
+-  }
+-
+-  for (uint16_t i = 0; i < substitution_count; i++) {
+-    uint16_t feature_index = 0;
+-    uint32_t alternate_feature_table_offset = 0;
+-    const size_t kFeatureTableSubstitutionRecordSize = sizeof(uint16_t) + sizeof(uint32_t);
+-
+-    if (!subtable.ReadU16(&feature_index) ||
+-        !subtable.ReadU32(&alternate_feature_table_offset)) {
+-      return OTS_FAILURE_MSG("Failed to read feature table substitution record");
+-    }
+-
+-    if (alternate_feature_table_offset < kFeatureTableSubstitutionHeaderSize +
+-                                         kFeatureTableSubstitutionRecordSize * substitution_count ||
+-        alternate_feature_table_offset >= length) {
+-      return OTS_FAILURE_MSG("Invalid alternate feature table offset");
+-    }
+-
+-    if (!ParseFeatureTable(font, data + alternate_feature_table_offset,
+-                           length - alternate_feature_table_offset, num_lookups)) {
+-      return OTS_FAILURE_MSG("Failed to parse alternate feature table");
+-    }
+-  }
+-
+-  return true;
+-}
+-
+-bool ParseFeatureVariationsTable(const Font *font,
+-                                 const uint8_t *data, const size_t length,
+-                                 const uint16_t num_lookups) {
++// Parsing feature variations table (in GSUB/GPOS v1.1)
++bool OpenTypeLayoutTable::ParseFeatureVariationsTable(const uint8_t *data, const size_t length) {
++  Font *font = GetFont();
+   Buffer subtable(data, length);
+ 
+   uint16_t version_major = 0;
+   uint16_t version_minor = 0;
+   uint32_t feature_variation_record_count = 0;
+ 
+   if (!subtable.ReadU16(&version_major) ||
+       !subtable.ReadU16(&version_minor) ||
+       !subtable.ReadU32(&feature_variation_record_count)) {
+-    return OTS_FAILURE_MSG("Failed to read feature variations table header");
++    return Error("Failed to read feature variations table header");
+   }
+ 
+   OpenTypeFVAR* fvar = static_cast<OpenTypeFVAR*>(font->GetTypedTable(OTS_TAG_FVAR));
+   if (!fvar) {
+-    return OTS_FAILURE_MSG("Not a variation font");
++    return Error("Not a variation font");
+   }
+   const uint16_t axis_count = fvar->AxisCount();
+ 
+   const size_t kEndOfFeatureVariationRecords =
+     2 * sizeof(uint16_t) + sizeof(uint32_t) +
+     feature_variation_record_count * 2 * sizeof(uint32_t);
+ 
+   for (uint32_t i = 0; i < feature_variation_record_count; i++) {
+     uint32_t condition_set_offset = 0;
+     uint32_t feature_table_substitution_offset = 0;
+     if (!subtable.ReadU32(&condition_set_offset) ||
+         !subtable.ReadU32(&feature_table_substitution_offset)) {
+-      return OTS_FAILURE_MSG("Failed to read feature variation record");
++      return Error("Failed to read feature variation record");
+     }
+ 
+     if (condition_set_offset) {
+       if (condition_set_offset < kEndOfFeatureVariationRecords ||
+           condition_set_offset >= length) {
+-        return OTS_FAILURE_MSG("Condition set offset out of range");
++        return Error("Condition set offset out of range");
+       }
+       if (!ParseConditionSetTable(font, data + condition_set_offset,
+                                   length - condition_set_offset,
+                                   axis_count)) {
+-        return OTS_FAILURE_MSG("Failed to parse condition set table");
++        return Error("Failed to parse condition set table");
+       }
+     }
+ 
+     if (feature_table_substitution_offset) {
+       if (feature_table_substitution_offset < kEndOfFeatureVariationRecords ||
+           feature_table_substitution_offset >= length) {
+-        return OTS_FAILURE_MSG("Feature table substitution offset out of range");
++        return Error("Feature table substitution offset out of range");
+       }
+       if (!ParseFeatureTableSubstitutionTable(font, data + feature_table_substitution_offset,
+                                               length - feature_table_substitution_offset,
+-                                              num_lookups)) {
+-        return OTS_FAILURE_MSG("Failed to parse feature table substitution table");
++                                              m_num_lookups)) {
++        return Error("Failed to parse feature table substitution table");
+       }
+     }
+   }
+ 
+   return true;
+ }
+ 
++// GSUB/GPOS header size for table version 1.0
++const size_t kHeaderSize_1_0 = 4 + 3 * 2;
++// GSUB/GPOS header size for table versio 1.1
++const size_t kHeaderSize_1_1 = 4 + 3 * 2 + 4;
++
++bool OpenTypeLayoutTable::Parse(const uint8_t *data, size_t length) {
++  Buffer table(data, length);
++
++  uint16_t version_major = 0, version_minor = 0;
++  uint16_t offset_script_list = 0;
++  uint16_t offset_feature_list = 0;
++  uint16_t offset_lookup_list = 0;
++  uint32_t offset_feature_variations = 0;
++  if (!table.ReadU16(&version_major) ||
++      !table.ReadU16(&version_minor) ||
++      !table.ReadU16(&offset_script_list) ||
++      !table.ReadU16(&offset_feature_list) ||
++      !table.ReadU16(&offset_lookup_list)) {
++    return Error("Incomplete table");
++  }
++
++  if (version_major != 1 || version_minor > 1) {
++    return Error("Bad version");
++  }
++
++  if (version_minor > 0) {
++    if (!table.ReadU32(&offset_feature_variations)) {
++      return Error("Incomplete table");
++    }
++  }
++
++  const size_t header_size =
++    (version_minor == 0) ? kHeaderSize_1_0 : kHeaderSize_1_1;
++
++  if (offset_lookup_list) {
++    if (offset_lookup_list < header_size || offset_lookup_list >= length) {
++      return Error("Bad lookup list offset in table header");
++    }
++
++    if (!ParseLookupListTable(data + offset_lookup_list,
++                              length - offset_lookup_list)) {
++      return Error("Failed to parse lookup list table");
++    }
++  }
++
++  if (offset_feature_list) {
++    if (offset_feature_list < header_size || offset_feature_list >= length) {
++      return Error("Bad feature list offset in table header");
++    }
++
++    if (!ParseFeatureListTable(data + offset_feature_list,
++                               length - offset_feature_list)) {
++      return Error("Failed to parse feature list table");
++    }
++  }
++
++  if (offset_script_list) {
++    if (offset_script_list < header_size || offset_script_list >= length) {
++      return Error("Bad script list offset in table header");
++    }
++
++    if (!ParseScriptListTable(data + offset_script_list,
++                              length - offset_script_list)) {
++      return Error("Failed to parse script list table");
++    }
++  }
++
++  if (offset_feature_variations) {
++    if (offset_feature_variations < header_size || offset_feature_variations >= length) {
++      return Error("Bad feature variations offset in table header");
++    }
++
++    if (!ParseFeatureVariationsTable(data + offset_feature_variations,
++                                     length - offset_feature_variations)) {
++      return Error("Failed to parse feature variations table");
++    }
++  }
++
++  this->m_data = data;
++  this->m_length = length;
++  return true;
++}
++
++bool OpenTypeLayoutTable::Serialize(OTSStream *out) {
++  if (!out->Write(this->m_data, this->m_length)) {
++    return Error("Failed to write table");
++  }
++
++  return true;
++}
++
+ }  // namespace ots
+ 
+ #undef TABLE_NAME
+diff --git a/gfx/ots/src/layout.h b/gfx/ots/src/layout.h
+--- a/gfx/ots/src/layout.h
++++ b/gfx/ots/src/layout.h
+@@ -10,86 +10,56 @@
+ // Utility functions for OpenType layout common table formats.
+ // http://www.microsoft.com/typography/otspec/chapter2.htm
+ 
+ namespace ots {
+ 
+ // The maximum number of class value.
+ const uint16_t kMaxClassDefValue = 0xFFFF;
+ 
+-struct LookupSubtableParser {
+-  struct TypeParser {
+-    uint16_t type;
+-    bool (*parse)(const Font *font, const uint8_t *data,
+-                  const size_t length);
+-  };
+-  size_t num_types;
+-  uint16_t extension_type;
+-  const TypeParser *parsers;
++class OpenTypeLayoutTable : public Table {
++  public:
++    explicit OpenTypeLayoutTable(Font *font, uint32_t tag, uint32_t type)
++      : Table(font, tag, type) { }
+ 
+-  bool Parse(const Font *font, const uint8_t *data,
+-             const size_t length, const uint16_t lookup_type) const;
+-};
++    bool Parse(const uint8_t *data, size_t length);
++    bool Serialize(OTSStream *out);
++
++  protected:
++    bool ParseContextSubtable(const uint8_t *data, const size_t length);
++    bool ParseChainingContextSubtable(const uint8_t *data, const size_t length);
++    bool ParseExtensionSubtable(const uint8_t *data, const size_t length);
+ 
+-bool ParseScriptListTable(const ots::Font *font,
+-                          const uint8_t *data, const size_t length,
+-                          const uint16_t num_features);
++  private:
++    bool ParseScriptListTable(const uint8_t *data, const size_t length);
++    bool ParseFeatureListTable(const uint8_t *data, const size_t length);
++    bool ParseLookupListTable(const uint8_t *data, const size_t length);
++    bool ParseFeatureVariationsTable(const uint8_t *data, const size_t length);
++    bool ParseLookupTable(const uint8_t *data, const size_t length);
+ 
+-bool ParseFeatureListTable(const ots::Font *font,
+-                           const uint8_t *data, const size_t length,
+-                           const uint16_t num_lookups,
+-                           uint16_t *num_features);
++    virtual bool ValidLookupSubtableType(const uint16_t lookup_type,
++                                         bool extension = false) const = 0;
++    virtual bool ParseLookupSubtable(const uint8_t *data, const size_t length,
++                                     const uint16_t lookup_type) = 0;
+ 
+-bool ParseLookupListTable(Font *font, const uint8_t *data,
+-                          const size_t length,
+-                          const LookupSubtableParser* parser,
+-                          uint16_t* num_lookups);
++    const uint8_t *m_data = nullptr;
++    size_t m_length = 0;
++    uint16_t m_num_features = 0;
++    uint16_t m_num_lookups = 0;
++};
+ 
+ bool ParseClassDefTable(const ots::Font *font,
+                         const uint8_t *data, size_t length,
+                         const uint16_t num_glyphs,
+                         const uint16_t num_classes);
+ 
+ bool ParseCoverageTable(const ots::Font *font,
+                         const uint8_t *data, size_t length,
+                         const uint16_t num_glyphs,
+                         const uint16_t expected_num_glyphs = 0);
+ 
+ bool ParseDeviceTable(const ots::Font *font,
+                       const uint8_t *data, size_t length);
+ 
+-// Parser for 'Contextual' subtable shared by GSUB/GPOS tables.
+-bool ParseContextSubtable(const ots::Font *font,
+-                          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 ots::Font *font,
+-                                  const uint8_t *data, const size_t length,
+-                                  const uint16_t num_glyphs,
+-                                  const uint16_t num_lookups);
+-
+-bool ParseExtensionSubtable(const Font *font,
+-                            const uint8_t *data, const size_t length,
+-                            const LookupSubtableParser* parser);
+-
+-// For feature variations table (in GSUB/GPOS v1.1)
+-bool ParseConditionTable(const Font *font,
+-                         const uint8_t *data, const size_t length,
+-                         const uint16_t axis_count);
+-
+-bool ParseConditionSetTable(const Font *font,
+-                            const uint8_t *data, const size_t length,
+-                            const uint16_t axis_count);
+-
+-bool ParseFeatureTableSubstitutionTable(const Font *font,
+-                                        const uint8_t *data, const size_t length,
+-                                        const uint16_t num_lookups);
+-
+-bool ParseFeatureVariationsTable(const Font *font,
+-                                 const uint8_t *data, const size_t length,
+-                                 const uint16_t num_lookups);
+-
+ }  // namespace ots
+ 
+ #endif  // OTS_LAYOUT_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
+@@ -732,20 +732,29 @@ bool ProcessGeneric(ots::FontFile *heade
+       }
+     }
+   }
+ 
+ #ifdef OTS_SYNTHESIZE_MISSING_GVAR
+   // If there was an fvar table but no gvar, synthesize an empty gvar to avoid
+   // issues with rasterizers (e.g. Core Text) that assume it must be present.
+   if (font->GetTable(OTS_TAG_FVAR) && !font->GetTable(OTS_TAG_GVAR)) {
+-    ots::OpenTypeGVAR *gvar = new ots::OpenTypeGVAR(font, OTS_TAG_GVAR);
+-    if (gvar->InitEmpty()) {
+-      table_map[OTS_TAG_GVAR] = { OTS_TAG_GVAR, 0, 0, 0, 0 };
+-      font->AddTable(gvar);
++    ots::TableEntry table_entry{ OTS_TAG_GVAR, 0, 0, 0, 0 };
++    const auto &it = font->file->tables.find(table_entry);
++    if (it != font->file->tables.end()) {
++      table_map[OTS_TAG_GVAR] = table_entry;
++      font->AddTable(table_entry, it->second);
++    } else {
++      ots::OpenTypeGVAR *gvar = new ots::OpenTypeGVAR(font, OTS_TAG_GVAR);
++      if (gvar->InitEmpty()) {
++        table_map[OTS_TAG_GVAR] = table_entry;
++        font->AddTable(table_entry, gvar);
++      } else {
++        delete gvar;
++      }
+     }
+   }
+ #endif
+ 
+   ots::Table *glyf = font->GetTable(OTS_TAG_GLYF);
+   ots::Table *loca = font->GetTable(OTS_TAG_LOCA);
+   ots::Table *cff  = font->GetTable(OTS_TAG_CFF);
+   ots::Table *cff2 = font->GetTable(OTS_TAG_CFF2);
+@@ -892,16 +901,42 @@ bool ProcessGeneric(ots::FontFile *heade
+ 
+   if (!output->Seek(end_of_file)) {
+     return OTS_FAILURE_MSG_HDR("error writing output");
+   }
+ 
+   return true;
+ }
+ 
++bool IsGraphiteTag(uint32_t tag) {
++  if (tag == OTS_TAG_FEAT ||
++      tag == OTS_TAG_GLAT ||
++      tag == OTS_TAG_GLOC ||
++      tag == OTS_TAG_SILE ||
++      tag == OTS_TAG_SILF ||
++      tag == OTS_TAG_SILL) {
++    return true;
++  }
++  return false;
++}
++
++bool IsVariationsTag(uint32_t tag) {
++  if (tag == OTS_TAG_AVAR ||
++      tag == OTS_TAG_CVAR ||
++      tag == OTS_TAG_FVAR ||
++      tag == OTS_TAG_GVAR ||
++      tag == OTS_TAG_HVAR ||
++      tag == OTS_TAG_MVAR ||
++      tag == OTS_TAG_STAT ||
++      tag == OTS_TAG_VVAR) {
++    return true;
++  }
++  return false;
++}
++
+ }  // namespace
+ 
+ namespace ots {
+ 
+ FontFile::~FontFile() {
+   for (const auto& it : tables) {
+     delete it.second;
+   }
+@@ -980,24 +1015,19 @@ bool Font::ParseTable(const TableEntry& 
+   }
+ 
+   if (table) {
+     const uint8_t* table_data;
+     size_t table_length;
+ 
+     ret = GetTableData(data, table_entry, arena, &table_length, &table_data);
+     if (ret) {
+-      // FIXME: Parsing some tables will fail if the table is not added to
+-      // m_tables first.
+-      m_tables[tag] = table;
+       ret = table->Parse(table_data, table_length);
+       if (ret)
+-        file->tables[table_entry] = table;
+-      else
+-        m_tables.erase(tag);
++        AddTable(table_entry, table);
+     }
+   }
+ 
+   if (!ret)
+     delete table;
+ 
+   return ret;
+ }
+@@ -1011,48 +1041,37 @@ Table* Font::GetTable(uint32_t tag) cons
+ 
+ Table* Font::GetTypedTable(uint32_t tag) const {
+   Table* t = GetTable(tag);
+   if (t && t->Type() == tag)
+     return t;
+   return NULL;
+ }
+ 
+-void Font::AddTable(Table* table) {
++void Font::AddTable(TableEntry entry, Table* table) {
+   // Attempting to add a duplicate table would be an error; this should only
+   // be used to add a table that does not already exist.
+   assert(m_tables.find(table->Tag()) == m_tables.end());
+   m_tables[table->Tag()] = table;
++  file->tables[entry] = table;
+ }
+ 
+ void Font::DropGraphite() {
+   file->context->Message(0, "Dropping all Graphite tables");
+   for (const std::pair<uint32_t, Table*> entry : m_tables) {
+-    if (entry.first == OTS_TAG_FEAT ||
+-        entry.first == OTS_TAG_GLAT ||
+-        entry.first == OTS_TAG_GLOC ||
+-        entry.first == OTS_TAG_SILE ||
+-        entry.first == OTS_TAG_SILF ||
+-        entry.first == OTS_TAG_SILL) {
++    if (IsGraphiteTag(entry.first)) {
+       entry.second->Drop("Discarding Graphite table");
+     }
+   }
+ }
+ 
+ void Font::DropVariations() {
+   file->context->Message(0, "Dropping all Variation tables");
+   for (const std::pair<uint32_t, Table*> entry : m_tables) {
+-    if (entry.first == OTS_TAG_AVAR ||
+-        entry.first == OTS_TAG_CVAR ||
+-        entry.first == OTS_TAG_FVAR ||
+-        entry.first == OTS_TAG_GVAR ||
+-        entry.first == OTS_TAG_HVAR ||
+-        entry.first == OTS_TAG_MVAR ||
+-        entry.first == OTS_TAG_STAT ||
+-        entry.first == OTS_TAG_VVAR) {
++    if (IsVariationsTag(entry.first)) {
+       entry.second->Drop("Discarding Variations table");
+     }
+   }
+ }
+ 
+ bool Table::ShouldSerialize() {
+   return m_shouldSerialize;
+ }
+@@ -1095,26 +1114,32 @@ bool Table::Drop(const char *format, ...
+ 
+ bool Table::DropGraphite(const char *format, ...) {
+   va_list va;
+   va_start(va, format);
+   Message(0, format, va);
+   va_end(va);
+ 
+   m_font->DropGraphite();
++  if (IsGraphiteTag(m_tag))
++      Drop("Discarding Graphite table");
++
+   return true;
+ }
+ 
+ bool Table::DropVariations(const char *format, ...) {
+   va_list va;
+   va_start(va, format);
+   Message(0, format, va);
+   va_end(va);
+ 
+   m_font->DropVariations();
++  if (IsVariationsTag(m_tag))
++      Drop("Discarding Variations table");
++
+   return true;
+ }
+ 
+ bool TablePassthru::Parse(const uint8_t *data, size_t length) {
+   m_data = data;
+   m_length = length;
+   return true;
+ }
+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
+@@ -314,17 +314,17 @@ struct Font {
+   Table* GetTable(uint32_t tag) const;
+ 
+   // This checks that the returned Table is actually of the correct subclass
+   // for |tag|, so it can safely be downcast to the corresponding OpenTypeXXXX;
+   // if not (i.e. if the table was treated as Passthru), it will return NULL.
+   Table* GetTypedTable(uint32_t tag) const;
+ 
+   // Insert a new table. Asserts if a table with the same tag already exists.
+-  void AddTable(Table* table);
++  void AddTable(TableEntry entry, Table* table);
+ 
+   // Drop all Graphite tables and don't parse new ones.
+   void DropGraphite();
+ 
+   // Drop all Variations tables and don't parse new ones.
+   void DropVariations();
+ 
+   FontFile *file;
+diff --git a/gfx/ots/tests/layout_common_table_test.cc b/gfx/ots/tests/layout_common_table_test.cc
+deleted file mode 100644
+--- a/gfx/ots/tests/layout_common_table_test.cc
++++ /dev/null
+@@ -1,770 +0,0 @@
+-// Copyright (c) 2011-2017 The OTS Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style license that can be
+-// found in the LICENSE file.
+-
+-#include <cmath>
+-#include <vector>
+-#include <gtest/gtest.h>
+-
+-#include "layout.h"
+-#include "ots-memory-stream.h"
+-
+-namespace {
+-
+-const uint32_t kFakeTag = 0x00000000;
+-const size_t kScriptRecordSize = 6;
+-const size_t kLangSysRecordSize = 6;
+-
+-bool BuildFakeScriptListTable(ots::OTSStream *out, const uint16_t script_count,
+-                              const uint16_t langsys_count,
+-                              const uint16_t feature_count) {
+-  if (!out->WriteU16(script_count)) {
+-    return false;
+-  }
+-  const off_t script_record_end = out->Tell() +
+-      kScriptRecordSize * script_count;
+-  const size_t script_table_size = 4 + kLangSysRecordSize * langsys_count;
+-  for (unsigned i = 0; i < script_count; ++i) {
+-    if (!out->WriteU32(kFakeTag) ||
+-        !out->WriteU16(script_record_end + i * script_table_size)) {
+-      return false;
+-    }
+-  }
+-
+-  // Offsets to LangSys tables are measured from the beginning of each
+-  // script table.
+-  const off_t langsys_record_end = 4 + kLangSysRecordSize * langsys_count;
+-  const size_t langsys_table_size = 6 + 2 * feature_count;
+-  // Write Fake Script tables.
+-  for (unsigned i = 0; i < script_count; ++i) {
+-    if (!out->WriteU16(0x0000) ||
+-        !out->WriteU16(langsys_count)) {
+-      return false;
+-    }
+-    for (unsigned j = 0; j < langsys_count; ++j) {
+-      if (!out->WriteU32(kFakeTag) ||
+-          !out->WriteU16(langsys_record_end + j * langsys_table_size)) {
+-        return false;
+-      }
+-    }
+-  }
+-
+-  // Write Fake LangSys tables.
+-  for (unsigned i = 0; i < langsys_count; ++i) {
+-    if (!out->WriteU16(0x0000) ||
+-        !out->WriteU16(0xFFFF) ||
+-        !out->WriteU16(feature_count)) {
+-      return false;
+-    }
+-    for (unsigned j = 0; j < feature_count; ++j) {
+-      if (!out->WriteU16(j)) {
+-        return false;
+-      }
+-    }
+-  }
+-  return true;
+-}
+-
+-const size_t kFeatureRecordSize = 6;
+-
+-bool BuildFakeFeatureListTable(ots::OTSStream *out,
+-                               const uint16_t feature_count,
+-                               const uint16_t lookup_count) {
+-  if (!out->WriteU16(feature_count)) {
+-    return false;
+-  }
+-  const off_t feature_record_end = out->Tell() +
+-      kFeatureRecordSize * feature_count;
+-  const size_t feature_table_size = 4 + 2 * lookup_count;
+-  for (unsigned i = 0; i < feature_count; ++i) {
+-    if (!out->WriteU32(kFakeTag) ||
+-        !out->WriteU16(feature_record_end + i * feature_table_size)) {
+-      return false;
+-    }
+-  }
+-
+-  // Write FeatureTable
+-  for (unsigned i = 0; i < feature_count; ++i) {
+-    if (!out->WriteU16(0x0000) ||
+-        !out->WriteU16(lookup_count)) {
+-      return false;
+-    }
+-    for (uint16_t j = 0; j < lookup_count; ++j) {
+-      if (!out->WriteU16(j)) {
+-        return false;
+-      }
+-    }
+-  }
+-  return true;
+-}
+-
+-bool BuildFakeLookupListTable(ots::OTSStream *out, const uint16_t lookup_count,
+-                              const uint16_t subtable_count) {
+-  if (!out->WriteU16(lookup_count)) {
+-    return false;
+-  }
+-  const off_t base_offset_lookup = out->Tell();
+-  if (!out->Pad(2 * lookup_count)) {
+-    return false;
+-  }
+-
+-  std::vector<off_t> offsets_lookup(lookup_count, 0);
+-  for (uint16_t i = 0; i < lookup_count; ++i) {
+-    offsets_lookup[i] = out->Tell();
+-    if (!out->WriteU16(i + 1) ||
+-        !out->WriteU16(0) ||
+-        !out->WriteU16(subtable_count) ||
+-        !out->Pad(2 * subtable_count) ||
+-        !out->WriteU16(0)) {
+-      return false;
+-    }
+-  }
+-
+-  const off_t offset_lookup_table_end = out->Tell();
+-  // Allocate 256 bytes for each subtable.
+-  if (!out->Pad(256 * lookup_count * subtable_count)) {
+-    return false;
+-  }
+-
+-  if (!out->Seek(base_offset_lookup)) {
+-    return false;
+-  }
+-  for (unsigned i = 0; i < lookup_count; ++i) {
+-    if (!out->WriteU16(offsets_lookup[i])) {
+-      return false;
+-    }
+-  }
+-
+-  for (unsigned i = 0; i < lookup_count; ++i) {
+-    if (!out->Seek(offsets_lookup[i] + 6)) {
+-      return false;
+-    }
+-    for (unsigned j = 0; j < subtable_count; ++j) {
+-      if (!out->WriteU16(offset_lookup_table_end +
+-                         256*i*subtable_count + 256*j)) {
+-        return false;
+-      }
+-    }
+-  }
+-  return true;
+-}
+-
+-bool BuildFakeCoverageFormat1(ots::OTSStream *out, const uint16_t glyph_count) {
+-  if (!out->WriteU16(1) || !out->WriteU16(glyph_count)) {
+-    return false;
+-  }
+-  for (uint16_t glyph_id = 1; glyph_id <= glyph_count; ++glyph_id) {
+-    if (!out->WriteU16(glyph_id)) {
+-      return false;
+-    }
+-  }
+-  return true;
+-}
+-
+-bool BuildFakeCoverageFormat2(ots::OTSStream *out, const uint16_t range_count) {
+-  if (!out->WriteU16(2) || !out->WriteU16(range_count)) {
+-    return false;
+-  }
+-  uint16_t glyph_id = 1;
+-  uint16_t start_coverage_index = 0;
+-  for (unsigned i = 0; i < range_count; ++i) {
+-    // Write consecutive ranges in which each range consists of two glyph id.
+-    if (!out->WriteU16(glyph_id) ||
+-        !out->WriteU16(glyph_id + 1) ||
+-        !out->WriteU16(start_coverage_index)) {
+-      return false;
+-    }
+-    glyph_id += 2;
+-    start_coverage_index += 2;
+-  }
+-  return true;
+-}
+-
+-bool BuildFakeClassDefFormat1(ots::OTSStream *out, const uint16_t glyph_count) {
+-  if (!out->WriteU16(1) ||
+-      !out->WriteU16(1) ||
+-      !out->WriteU16(glyph_count)) {
+-    return false;
+-  }
+-  for (uint16_t class_value = 1; class_value <= glyph_count; ++class_value) {
+-    if (!out->WriteU16(class_value)) {
+-      return false;
+-    }
+-  }
+-  return true;
+-}
+-
+-bool BuildFakeClassDefFormat2(ots::OTSStream *out, const uint16_t range_count) {
+-  if (!out->WriteU16(2) || !out->WriteU16(range_count)) {
+-    return false;
+-  }
+-  uint16_t glyph_id = 1;
+-  for (uint16_t class_value = 1; class_value <= range_count; ++class_value) {
+-    // Write consecutive ranges in which each range consists of one glyph id.
+-    if (!out->WriteU16(glyph_id) ||
+-        !out->WriteU16(glyph_id + 1) ||
+-        !out->WriteU16(class_value)) {
+-      return false;
+-    }
+-    glyph_id += 2;
+-  }
+-  return true;
+-}
+-
+-bool BuildFakeDeviceTable(ots::OTSStream *out, const uint16_t start_size,
+-                          const uint16_t end_size, const uint16_t format) {
+-  if (!out->WriteU16(start_size) ||
+-      !out->WriteU16(end_size) ||
+-      !out->WriteU16(format)) {
+-    return false;
+-  }
+-
+-  const unsigned num_values = std::abs(end_size - start_size) + 1;
+-  const unsigned num_bits = (1 << format) * num_values;
+-  const unsigned num_units = (num_bits - 1) / 16 + 1;
+-  if (!out->Pad(num_units * 2)) {
+-    return false;
+-  }
+-  return true;
+-}
+-
+-class TestStream : public ots::MemoryStream {
+- public:
+-  TestStream()
+-      : ots::MemoryStream(data_, sizeof(data_)), size_(0) {
+-    std::memset(reinterpret_cast<char*>(data_), 0, sizeof(data_));
+-  }
+-
+-  uint8_t* data() { return data_; }
+-  size_t size() override { return size_; }
+-
+-  bool WriteRaw(const void *d, size_t length) override {
+-    if (Tell() + length > size_) {
+-      size_ = Tell() + length;
+-    }
+-    return ots::MemoryStream::WriteRaw(d, length);
+-  }
+-
+- private:
+-  size_t size_;
+-  uint8_t data_[4096];
+-};
+-
+-class TableTest : public ::testing::Test {
+- protected:
+-
+-  virtual void SetUp() {
+-    ots::FontFile *file = new ots::FontFile();
+-    file->context = new ots::OTSContext();
+-    font = new ots::Font(file);
+-  }
+-
+-  virtual void TearDown() {
+-    delete font->file->context;
+-    delete font->file;
+-    delete font;
+-  }
+-
+-  TestStream out;
+-  ots::Font *font;
+-};
+-
+-class ScriptListTableTest : public TableTest { };
+-class DeviceTableTest : public TableTest { };
+-class CoverageTableTest : public TableTest { };
+-class CoverageFormat1Test : public TableTest { };
+-class CoverageFormat2Test : public TableTest { };
+-class ClassDefTableTest : public TableTest { };
+-class ClassDefFormat1Test : public TableTest { };
+-class ClassDefFormat2Test : public TableTest { };
+-class LookupSubtableParserTest : public TableTest { };
+-
+-class FeatureListTableTest : public TableTest {
+- protected:
+-
+-  virtual void SetUp() {
+-    TableTest::SetUp();
+-    num_features = 0;
+-  }
+-
+-  uint16_t num_features;
+-};
+-
+-bool fakeTypeParserReturnsTrue(const ots::Font*, const uint8_t *,
+-                               const size_t) {
+-  return true;
+-}
+-
+-bool fakeTypeParserReturnsFalse(const ots::Font*, const uint8_t *,
+-                                const size_t) {
+-  return false;
+-}
+-
+-const ots::LookupSubtableParser::TypeParser TypeParsersReturnTrue[] = {
+-  {1, fakeTypeParserReturnsTrue},
+-  {2, fakeTypeParserReturnsTrue},
+-  {3, fakeTypeParserReturnsTrue},
+-  {4, fakeTypeParserReturnsTrue},
+-  {5, fakeTypeParserReturnsTrue}
+-};
+-
+-// Fake lookup subtable parser which always returns true.
+-const ots::LookupSubtableParser FakeLookupParserReturnsTrue = {
+-  5, 5, TypeParsersReturnTrue,
+-};
+-
+-const ots::LookupSubtableParser::TypeParser TypeParsersReturnFalse[] = {
+-  {1, fakeTypeParserReturnsFalse}
+-};
+-
+-// Fake lookup subtable parser which always returns false.
+-const ots::LookupSubtableParser FakeLookupParserReturnsFalse = {
+-  1, 1, TypeParsersReturnFalse
+-};
+-
+-class LookupListTableTest : public TableTest {
+- protected:
+-
+-  virtual void SetUp() {
+-    TableTest::SetUp();
+-    num_lookups = 0;
+-  }
+-
+-  bool Parse() {
+-    return ots::ParseLookupListTable(font, out.data(), out.size(),
+-                                     &FakeLookupParserReturnsTrue,
+-                                     &num_lookups);
+-  }
+-
+-  uint16_t num_lookups;
+-};
+-
+-}  // namespace
+-
+-TEST_F(ScriptListTableTest, TestSuccess) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  EXPECT_TRUE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestBadScriptCount) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set too large script count.
+-  out.Seek(0);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestScriptRecordOffsetUnderflow) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set bad offset to ScriptRecord[0].
+-  out.Seek(6);
+-  out.WriteU16(0);
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestScriptRecordOffsetOverflow) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set bad offset to ScriptRecord[0].
+-  out.Seek(6);
+-  out.WriteU16(out.size());
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestBadLangSysCount) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set too large langsys count.
+-  out.Seek(10);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestLangSysRecordOffsetUnderflow) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set bad offset to LangSysRecord[0].
+-  out.Seek(16);
+-  out.WriteU16(0);
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestLangSysRecordOffsetOverflow) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set bad offset to LangSysRecord[0].
+-  out.Seek(16);
+-  out.WriteU16(out.size());
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestBadReqFeatureIndex) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set too large feature index to ReqFeatureIndex of LangSysTable[0].
+-  out.Seek(20);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestBadFeatureCount) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set too large feature count to LangSysTable[0].
+-  out.Seek(22);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(ScriptListTableTest, TestBadFeatureIndex) {
+-  BuildFakeScriptListTable(&out, 1, 1, 1);
+-  // Set too large feature index to ReatureIndex[0] of LangSysTable[0].
+-  out.Seek(24);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(FeatureListTableTest, TestSuccess) {
+-  BuildFakeFeatureListTable(&out, 1, 1);
+-  EXPECT_TRUE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+-                                         &num_features));
+-  EXPECT_EQ(num_features, 1);
+-}
+-
+-TEST_F(FeatureListTableTest, TestSuccess2) {
+-  BuildFakeFeatureListTable(&out, 5, 1);
+-  EXPECT_TRUE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+-                                         &num_features));
+-  EXPECT_EQ(num_features, 5);
+-}
+-
+-TEST_F(FeatureListTableTest, TestBadFeatureCount) {
+-  BuildFakeFeatureListTable(&out, 1, 1);
+-  // Set too large feature count.
+-  out.Seek(0);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+-                                          &num_features));
+-}
+-
+-TEST_F(FeatureListTableTest, TestOffsetFeatureUnderflow) {
+-  BuildFakeFeatureListTable(&out, 1, 1);
+-  // Set bad offset to FeatureRecord[0].
+-  out.Seek(6);
+-  out.WriteU16(0);
+-  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+-                                          &num_features));
+-}
+-
+-TEST_F(FeatureListTableTest, TestOffsetFeatureOverflow) {
+-  BuildFakeFeatureListTable(&out, 1, 1);
+-  // Set bad offset to FeatureRecord[0].
+-  out.Seek(6);
+-  out.WriteU16(out.size());
+-  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+-                                          &num_features));
+-}
+-
+-TEST_F(FeatureListTableTest, TestBadLookupCount) {
+-  BuildFakeFeatureListTable(&out, 1, 1);
+-  // Set too large lookup count to FeatureTable[0].
+-  out.Seek(10);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1,
+-                                          &num_features));
+-}
+-
+-TEST_F(LookupListTableTest, TestSuccess) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  EXPECT_TRUE(Parse());
+-  EXPECT_EQ(num_lookups, 1);
+-}
+-
+-TEST_F(LookupListTableTest, TestSuccess2) {
+-  BuildFakeLookupListTable(&out, 5, 1);
+-  EXPECT_TRUE(Parse());
+-  EXPECT_EQ(num_lookups, 5);
+-}
+-
+-TEST_F(LookupListTableTest, TestOffsetLookupTableUnderflow) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set bad offset to Lookup[0].
+-  out.Seek(2);
+-  out.WriteU16(0);
+-  EXPECT_FALSE(Parse());
+-}
+-
+-TEST_F(LookupListTableTest, TestOffsetLookupTableOverflow) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set bad offset to Lookup[0].
+-  out.Seek(2);
+-  out.WriteU16(out.size());
+-  EXPECT_FALSE(Parse());
+-}
+-
+-TEST_F(LookupListTableTest, TestOffsetSubtableUnderflow) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set bad offset to SubTable[0] of LookupTable[0].
+-  out.Seek(10);
+-  out.WriteU16(0);
+-  EXPECT_FALSE(Parse());
+-}
+-
+-TEST_F(LookupListTableTest, TestOffsetSubtableOverflow) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set bad offset to SubTable[0] of LookupTable[0].
+-  out.Seek(10);
+-  out.WriteU16(out.size());
+-  EXPECT_FALSE(Parse());
+-}
+-
+-TEST_F(LookupListTableTest, TesBadLookupCount) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set too large lookup count of LookupTable[0].
+-  out.Seek(0);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(Parse());
+-}
+-
+-TEST_F(LookupListTableTest, TesBadLookupType) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set too large lookup type of LookupTable[0].
+-  out.Seek(4);
+-  out.WriteU16(6);
+-  EXPECT_FALSE(Parse());
+-}
+-
+-TEST_F(LookupListTableTest, TesBadLookupFlag) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set IgnoreBaseGlyphs(0x0002) to the lookup flag of LookupTable[0].
+-  out.Seek(6);
+-  out.WriteU16(0x0002);
+-  EXPECT_TRUE(Parse());
+-}
+-
+-TEST_F(LookupListTableTest, TesBadSubtableCount) {
+-  BuildFakeLookupListTable(&out, 1, 1);
+-  // Set too large sutable count of LookupTable[0].
+-  out.Seek(8);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(Parse());
+-}
+-
+-TEST_F(CoverageTableTest, TestSuccessFormat1) {
+-  BuildFakeCoverageFormat1(&out, 1);
+-  EXPECT_TRUE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(CoverageTableTest, TestSuccessFormat2) {
+-  BuildFakeCoverageFormat2(&out, 1);
+-  EXPECT_TRUE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(CoverageTableTest, TestBadFormat) {
+-  BuildFakeCoverageFormat1(&out, 1);
+-  // Set bad format.
+-  out.Seek(0);
+-  out.WriteU16(3);
+-  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(CoverageFormat1Test, TestBadGlyphCount) {
+-  BuildFakeCoverageFormat1(&out, 1);
+-  // Set too large glyph count.
+-  out.Seek(2);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(CoverageFormat1Test, TestBadGlyphId) {
+-  BuildFakeCoverageFormat1(&out, 1);
+-  // Set too large glyph id.
+-  out.Seek(4);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(CoverageFormat2Test, TestBadRangeCount) {
+-  BuildFakeCoverageFormat2(&out, 1);
+-  // Set too large range count.
+-  out.Seek(2);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(CoverageFormat2Test, TestBadRange) {
+-  BuildFakeCoverageFormat2(&out, 1);
+-  // Set reverse order glyph id to start/end fields.
+-  out.Seek(4);
+-  out.WriteU16(2);
+-  out.WriteU16(1);
+-  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1));
+-}
+-
+-TEST_F(CoverageFormat2Test, TestRangeOverlap) {
+-  BuildFakeCoverageFormat2(&out, 2);
+-  // Set overlapping glyph id to an end field.
+-  out.Seek(12);
+-  out.WriteU16(1);
+-  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 2));
+-}
+-
+-TEST_F(CoverageFormat2Test, TestRangeOverlap2) {
+-  BuildFakeCoverageFormat2(&out, 2);
+-  // Set overlapping range.
+-  out.Seek(10);
+-  out.WriteU16(1);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 2));
+-}
+-
+-TEST_F(ClassDefTableTest, TestSuccessFormat1) {
+-  BuildFakeClassDefFormat1(&out, 1);
+-  EXPECT_TRUE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefTableTest, TestSuccessFormat2) {
+-  BuildFakeClassDefFormat2(&out, 1);
+-  EXPECT_TRUE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefTableTest, TestBadFormat) {
+-  BuildFakeClassDefFormat1(&out, 1);
+-  // Set bad format.
+-  out.Seek(0);
+-  out.WriteU16(3);
+-  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefFormat1Test, TestBadStartGlyph) {
+-  BuildFakeClassDefFormat1(&out, 1);
+-  // Set too large start glyph id.
+-  out.Seek(2);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefFormat1Test, TestBadGlyphCount) {
+-  BuildFakeClassDefFormat1(&out, 1);
+-  // Set too large glyph count.
+-  out.Seek(4);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefFormat1Test, TestBadClassValue) {
+-  BuildFakeClassDefFormat1(&out, 1);
+-  // Set too large class value.
+-  out.Seek(6);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefFormat2Test, TestBadRangeCount) {
+-  BuildFakeClassDefFormat2(&out, 1);
+-  // Set too large range count.
+-  out.Seek(2);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefFormat2Test, TestRangeOverlap) {
+-  BuildFakeClassDefFormat2(&out, 2);
+-  // Set overlapping glyph id to an end field.
+-  out.Seek(12);
+-  out.WriteU16(1);
+-  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(ClassDefFormat2Test, TestRangeOverlap2) {
+-  BuildFakeClassDefFormat2(&out, 2);
+-  // Set overlapping range.
+-  out.Seek(10);
+-  out.WriteU16(1);
+-  out.WriteU16(2);
+-  EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat1Success) {
+-  BuildFakeDeviceTable(&out, 1, 8, 1);
+-  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat1Success2) {
+-  BuildFakeDeviceTable(&out, 1, 9, 1);
+-  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat1Fail) {
+-  // Pass shorter length than expected.
+-  BuildFakeDeviceTable(&out, 1, 8, 1);
+-  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat1Fail2) {
+-  // Pass shorter length than expected.
+-  BuildFakeDeviceTable(&out, 1, 9, 1);
+-  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat2Success) {
+-  BuildFakeDeviceTable(&out, 1, 1, 2);
+-  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat2Success2) {
+-  BuildFakeDeviceTable(&out, 1, 8, 2);
+-  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat2Fail) {
+-  // Pass shorter length than expected.
+-  BuildFakeDeviceTable(&out, 1, 8, 2);
+-  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat2Fail2) {
+-  // Pass shorter length than expected.
+-  BuildFakeDeviceTable(&out, 1, 9, 2);
+-  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat3Success) {
+-  BuildFakeDeviceTable(&out, 1, 1, 3);
+-  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat3Success2) {
+-  BuildFakeDeviceTable(&out, 1, 8, 3);
+-  EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size()));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat3Fail) {
+-  // Pass shorter length than expected.
+-  BuildFakeDeviceTable(&out, 1, 8, 3);
+-  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+-}
+-
+-TEST_F(DeviceTableTest, TestDeltaFormat3Fail2) {
+-  // Pass shorter length than expected.
+-  BuildFakeDeviceTable(&out, 1, 9, 3);
+-  EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1));
+-}
+-
+-TEST_F(LookupSubtableParserTest, TestSuccess) {
+-  {
+-    EXPECT_TRUE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 1));
+-  }
+-  {
+-    EXPECT_TRUE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 5));
+-  }
+-}
+-
+-TEST_F(LookupSubtableParserTest, TestFail) {
+-  {
+-    // Pass bad lookup type which less than the smallest type.
+-    EXPECT_FALSE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 0));
+-  }
+-  {
+-    // Pass bad lookup type which greater than the maximum type.
+-    EXPECT_FALSE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 6));
+-  }
+-  {
+-    // Check the type parser failure.
+-    EXPECT_FALSE(FakeLookupParserReturnsFalse.Parse(font, 0, 0, 1));
+-  }
+-}
+diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build
+--- a/gfx/tests/gtest/moz.build
++++ b/gfx/tests/gtest/moz.build
+@@ -34,24 +34,18 @@ UNIFIED_SOURCES += [
+ UNIFIED_SOURCES += [ '/gfx/2d/unittest/%s' % p for p in [
+     'TestBase.cpp',
+     'TestBugs.cpp',
+     'TestCairo.cpp',
+     'TestPoint.cpp',
+     'TestScaling.cpp',
+ ]]
+ 
+-# not UNIFIED_SOURCES because layout_common_table_test.cc has classes
+-# in an anonymous namespace which result in a GCC error when used in
+-# tests (e g. "error: 'ScriptListTableTest_TestSuccess_Test' has a field
+-# 'ScriptListTableTest_TestSuccess_Test::<anonymous>' whose type uses
+-# the anonymous namespace").
+-SOURCES += [ '/gfx/ots/tests/%s' % p for p in [
+-    'cff_charstring_test.cc',
+-    'layout_common_table_test.cc',
+++UNIFIED_SOURCES += [
+++    "/gfx/ots/tests/cff_charstring_test.cc",
+ ]]
+ 
+ # ICC profiles used for verifying QCMS transformations. The copyright
+ # notice embedded in the profiles should be reviewed to ensure there are
+ # no known restrictions on distribution.
+ TEST_HARNESS_FILES.gtest += [
+     'icc_profiles/lcms_samsung_syncmaster.icc',
+     'icc_profiles/lcms_thinkpad_w540.icc',

+ 361 - 0
mozilla-release/patches/mozilla-central_675504.patch

@@ -0,0 +1,361 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1692294250 0
+#      Thu Aug 17 17:44:10 2023 +0000
+# Node ID d412b291820d7a24a94db9d8f082f4cb080cf225
+# Parent  9ae7709803c1fa3e4097ee1f78b663b39a4fce35
+Bug 1847959 - Update OTS to pick up https://github.com/khaledhosny/ots/pull/264, to avoid clipping glyphs with incorrect bbox values. r=gfx-reviewers,lsalzman
+
+Differential Revision: https://phabricator.services.mozilla.com/D186476
+
+diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
+--- a/gfx/ots/README.mozilla
++++ b/gfx/ots/README.mozilla
+@@ -1,12 +1,12 @@
+ This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
+ 
+ Our reference repository is https://github.com/khaledhosny/ots/.
+ 
+-Current revision: 75933e1bdc98bdb095f6274b284e1c365f2c510e (9.1.0)
++Current revision: 6ba665aa307ea360283191736814863ca398398d (9.1.0)
+ 
+ Upstream files included: LICENSE, src/, include/, tests/*.cc
+ 
+ Additional files: README.mozilla, src/moz.build
+ 
+ Additional patch: ots-visibility.patch (bug 711079).
+ Additional patch: ots-lz4.patch
+diff --git a/gfx/ots/src/glyf.cc b/gfx/ots/src/glyf.cc
+--- a/gfx/ots/src/glyf.cc
++++ b/gfx/ots/src/glyf.cc
+@@ -13,16 +13,17 @@
+ 
+ // glyf - Glyph Data
+ // http://www.microsoft.com/typography/otspec/glyf.htm
+ 
+ namespace ots {
+ 
+ bool OpenTypeGLYF::ParseFlagsForSimpleGlyph(Buffer &glyph,
+                                             uint32_t num_flags,
++                                            std::vector<uint8_t>& flags,
+                                             uint32_t *flag_index,
+                                             uint32_t *coordinates_length) {
+   uint8_t flag = 0;
+   if (!glyph.ReadU8(&flag)) {
+     return Error("Can't read flag");
+   }
+ 
+   uint32_t delta = 0;
+@@ -43,107 +44,206 @@ bool OpenTypeGLYF::ParseFlagsForSimpleGl
+    * contour flag for simple glyphs with overlapping contours:
+    * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6AATIntro.html
+    * (“Overlapping contours” section) */
+   if (flag & (1u << 6) && *flag_index != 0) {
+     return Error("Bad glyph flag (%d), "
+                  "bit 6 must be set to zero for flag %d", flag, *flag_index);
+   }
+ 
++  flags[*flag_index] = flag & ~(1u << 3);
++
+   if (flag & (1u << 3)) {  // repeat
+     if (*flag_index + 1 >= num_flags) {
+       return Error("Count too high (%d + 1 >= %d)", *flag_index, num_flags);
+     }
+     uint8_t repeat = 0;
+     if (!glyph.ReadU8(&repeat)) {
+       return Error("Can't read repeat value");
+     }
+     if (repeat == 0) {
+       return Error("Zero repeat");
+     }
+     delta += (delta * repeat);
+ 
+-    *flag_index += repeat;
+-    if (*flag_index >= num_flags) {
+-      return Error("Count too high (%d >= %d)", *flag_index, num_flags);
++    if (*flag_index + repeat >= num_flags) {
++      return Error("Count too high (%d >= %d)", *flag_index + repeat, num_flags);
++    }
++
++    while (repeat--) {
++      flags[++*flag_index] = flag & ~(1u << 3);
+     }
+   }
+ 
+   if (flag & (1u << 7)) {  // reserved flag
+     return Error("Bad glyph flag (%d), reserved bit 7 must be set to zero", flag);
+   }
+ 
+   *coordinates_length += delta;
+   if (glyph.length() < *coordinates_length) {
+     return Error("Glyph coordinates length bigger than glyph length (%d > %d)",
+                  *coordinates_length, glyph.length());
+   }
+ 
+   return true;
+ }
+ 
++#define X_SHORT_VECTOR                        (1u << 1)
++#define Y_SHORT_VECTOR                        (1u << 2)
++#define X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR  (1u << 4)
++#define Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR  (1u << 5)
++
+ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph,
+-                                    int16_t num_contours) {
++                                    unsigned gid,
++                                    int16_t num_contours,
++                                    int16_t& xmin,
++                                    int16_t& ymin,
++                                    int16_t& xmax,
++                                    int16_t& ymax) {
+   // read the end-points array
+   uint16_t num_flags = 0;
+   for (int i = 0; i < num_contours; ++i) {
+     uint16_t tmp_index = 0;
+     if (!glyph.ReadU16(&tmp_index)) {
+-      return Error("Can't read contour index %d", i);
++      return Error("Can't read contour index %d (glyph %u)", i, gid);
+     }
+     if (tmp_index == 0xffffu) {
+-      return Error("Bad contour index %d", i);
++      return Error("Bad contour index %d (glyph %u)", i, gid);
+     }
+     // check if the indices are monotonically increasing
+     if (i && (tmp_index + 1 <= num_flags)) {
+-      return Error("Decreasing contour index %d + 1 <= %d", tmp_index, num_flags);
++      return Error("Decreasing contour index %d + 1 <= %d (glyph %u)", tmp_index, num_flags, gid);
+     }
+     num_flags = tmp_index + 1;
+   }
+ 
+   if (this->maxp->version_1 &&
+       num_flags > this->maxp->max_points) {
+-    Warning("Number of contour points exceeds maxp maxPoints, adjusting limit.");
++    Warning("Number of contour points exceeds maxp maxPoints, adjusting limit (glyph %u)", gid);
+     this->maxp->max_points = num_flags;
+   }
+ 
+   uint16_t bytecode_length = 0;
+   if (!glyph.ReadU16(&bytecode_length)) {
+     return Error("Can't read bytecode length");
+   }
+ 
+   if (this->maxp->version_1 &&
+       this->maxp->max_size_glyf_instructions < bytecode_length) {
+-    Warning("Bytecode length is bigger than maxp.maxSizeOfInstructions %d: %d",
+-            this->maxp->max_size_glyf_instructions, bytecode_length);
++    Warning("Bytecode length is bigger than maxp.maxSizeOfInstructions %d: %d (glyph %u)",
++            this->maxp->max_size_glyf_instructions, bytecode_length, gid);
+     this->maxp->max_size_glyf_instructions = bytecode_length;
+   }
+ 
+   if (!glyph.Skip(bytecode_length)) {
+-    return Error("Can't read bytecode of length %d", bytecode_length);
++    return Error("Can't read bytecode of length %d (glyph %u)", bytecode_length, gid);
+   }
+ 
+   uint32_t coordinates_length = 0;
++  std::vector<uint8_t> flags(num_flags);
+   for (uint32_t i = 0; i < num_flags; ++i) {
+-    if (!ParseFlagsForSimpleGlyph(glyph, num_flags, &i, &coordinates_length)) {
+-      return Error("Failed to parse glyph flags %d", i);
++    if (!ParseFlagsForSimpleGlyph(glyph, num_flags, flags, &i, &coordinates_length)) {
++      return Error("Failed to parse glyph flags %d (glyph %u)", i, gid);
+     }
+   }
+ 
+-  if (!glyph.Skip(coordinates_length)) {
+-    return Error("Glyph too short %d", glyph.length());
++  bool adjusted_bbox = false;
++  int16_t x = 0, y = 0;
++
++  // Read and check x-coords
++  for (uint32_t i = 0; i < num_flags; ++i) {
++    uint8_t flag = flags[i];
++    if (flag & X_SHORT_VECTOR) {
++      uint8_t dx;
++      if (!glyph.ReadU8(&dx)) {
++        return Error("Glyph too short %d (glyph %u)", glyph.length(), gid);
++      }
++      if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) {
++        x += dx;
++      } else {
++        x -= dx;
++      }
++    } else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) {
++      // x remains unchanged
++    } else {
++      int16_t dx;
++      if (!glyph.ReadS16(&dx)) {
++        return Error("Glyph too short %d (glyph %u)", glyph.length(), gid);
++      }
++      x += dx;
++    }
++    if (x < xmin) {
++      xmin = x;
++      adjusted_bbox = true;
++    }
++    if (x > xmax) {
++      xmax = x;
++      adjusted_bbox = true;
++    }
++  }
++
++  // Read and check y-coords
++  for (uint32_t i = 0; i < num_flags; ++i) {
++    uint8_t flag = flags[i];
++    if (flag & Y_SHORT_VECTOR) {
++      uint8_t dy;
++      if (!glyph.ReadU8(&dy)) {
++        return Error("Glyph too short %d (glyph %u)", glyph.length(), gid);
++      }
++      if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) {
++        y += dy;
++      } else {
++        y -= dy;
++      }
++    } else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) {
++      // x remains unchanged
++    } else {
++      int16_t dy;
++      if (!glyph.ReadS16(&dy)) {
++        return Error("Glyph too short %d (glyph %u)", glyph.length(), gid);
++      }
++      y += dy;
++    }
++    if (y < ymin) {
++      ymin = y;
++      adjusted_bbox = true;
++    }
++    if (y > ymax) {
++      ymax = y;
++      adjusted_bbox = true;
++    }
+   }
+ 
+   if (glyph.remaining() > 3) {
+     // We allow 0-3 bytes difference since gly_length is 4-bytes aligned,
+     // zero-padded length.
+-    Warning("Extra bytes at end of the glyph: %d", glyph.remaining());
++    Warning("Extra bytes at end of the glyph: %d (glyph %u)", glyph.remaining(), gid);
+   }
+ 
+-  this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
++  if (adjusted_bbox) {
++    Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid);
++    // copy the numberOfContours field
++    this->iov.push_back(std::make_pair(glyph.buffer(), 2));
++    // output a fixed-up version of the bounding box
++    uint8_t* fixed_bbox = new uint8_t[8];
++    fixed_bboxes.push_back(fixed_bbox);
++    xmin = ots_htons(xmin);
++    std::memcpy(fixed_bbox, &xmin, 2);
++    ymin = ots_htons(ymin);
++    std::memcpy(fixed_bbox + 2, &ymin, 2);
++    xmax = ots_htons(xmax);
++    std::memcpy(fixed_bbox + 4, &xmax, 2);
++    ymax = ots_htons(ymax);
++    std::memcpy(fixed_bbox + 6, &ymax, 2);
++    this->iov.push_back(std::make_pair(fixed_bbox, 8));
++    // copy the remainder of the glyph data
++    this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10));
++  } else {
++    this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
++  }
+ 
+   return true;
+ }
+ 
+ #define ARG_1_AND_2_ARE_WORDS    (1u << 0)
+ #define WE_HAVE_A_SCALE          (1u << 3)
+ #define MORE_COMPONENTS          (1u << 5)
+ #define WE_HAVE_AN_X_AND_Y_SCALE (1u << 6)
+@@ -292,17 +392,17 @@ bool OpenTypeGLYF::Parse(const uint8_t *
+       return Error("Bad bounding box values bl=(%d, %d), tr=(%d, %d) in glyph %d", xmin, ymin, xmax, ymax, i);
+     }
+ 
+     if (num_contours == 0) {
+       // This is an empty glyph and shouldn’t have any glyph data, but if it
+       // does we will simply ignore it.
+       glyph.set_offset(0);
+     } else if (num_contours > 0) {
+-      if (!ParseSimpleGlyph(glyph, num_contours)) {
++      if (!ParseSimpleGlyph(glyph, i, num_contours, xmin, ymin, xmax, ymax)) {
+         return Error("Failed to parse glyph %d", i);
+       }
+     } else {
+ 
+       ComponentPointCount component_point_count;
+       if (!ParseCompositeGlyph(glyph, &component_point_count)) {
+         return Error("Failed to parse glyph %d", i);
+       }
+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
+@@ -14,16 +14,22 @@
+ namespace ots {
+ class OpenTypeMAXP;
+ 
+ class OpenTypeGLYF : public Table {
+  public:
+   explicit OpenTypeGLYF(Font *font, uint32_t tag)
+       : Table(font, tag, tag), maxp(NULL) { }
+ 
++  ~OpenTypeGLYF() {
++    for (auto* p : fixed_bboxes) {
++      delete[] p;
++    }
++  }
++
+   bool Parse(const uint8_t *data, size_t length);
+   bool Serialize(OTSStream *out);
+ 
+  private:
+   struct GidAtLevel {
+     uint16_t gid;
+     uint32_t level;
+   };
+@@ -31,19 +37,26 @@ class OpenTypeGLYF : public Table {
+   struct ComponentPointCount {
+     ComponentPointCount() : accumulated_component_points(0) {};
+     uint32_t accumulated_component_points;
+     std::vector<GidAtLevel> gid_stack;
+   };
+ 
+   bool ParseFlagsForSimpleGlyph(Buffer &glyph,
+                                 uint32_t num_flags,
++                                std::vector<uint8_t>& flags,
+                                 uint32_t *flag_index,
+                                 uint32_t *coordinates_length);
+-  bool ParseSimpleGlyph(Buffer &glyph, int16_t num_contours);
++  bool ParseSimpleGlyph(Buffer &glyph,
++                        unsigned gid,
++                        int16_t num_contours,
++                        int16_t& xmin,
++                        int16_t& ymin,
++                        int16_t& xmax,
++                        int16_t& ymax);
+   bool ParseCompositeGlyph(
+       Buffer &glyph,
+       ComponentPointCount* component_point_count);
+ 
+ 
+   bool TraverseComponentsCountingPoints(
+       Buffer& glyph,
+       uint16_t base_glyph_id,
+@@ -54,13 +67,15 @@ class OpenTypeGLYF : public Table {
+       const uint8_t *data,
+       size_t length,
+       const std::vector<uint32_t>& loca_offsets,
+       unsigned glyph_id);
+ 
+   OpenTypeMAXP* maxp;
+ 
+   std::vector<std::pair<const uint8_t*, size_t> > iov;
++
++  std::vector<uint8_t*> fixed_bboxes;
+ };
+ 
+ }  // namespace ots
+ 
+ #endif  // OTS_GLYF_H_

+ 250 - 0
mozilla-release/patches/mozilla-central_676459.patch

@@ -0,0 +1,250 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1693400102 0
+#      Wed Aug 30 12:55:02 2023 +0000
+# Node ID cdb5bfd0656742d67dd2feafb57ae49d871bd5fa
+# Parent  eccf1dcce67f8194cb6d01a615d4f65b78d53f82
+Bug 1850314 - Don't do glyph bounding-box fixup for "tricky" fonts, because it may disrupt glyph rendering on macOS. r=gfx-reviewers,lsalzman
+
+Differential Revision: https://phabricator.services.mozilla.com/D187096
+
+diff --git a/gfx/ots/src/glyf.cc b/gfx/ots/src/glyf.cc
+--- a/gfx/ots/src/glyf.cc
++++ b/gfx/ots/src/glyf.cc
+@@ -5,16 +5,17 @@
+ #include "glyf.h"
+ 
+ #include <algorithm>
+ #include <limits>
+ 
+ #include "head.h"
+ #include "loca.h"
+ #include "maxp.h"
++#include "name.h"
+ 
+ // glyf - Glyph Data
+ // http://www.microsoft.com/typography/otspec/glyf.htm
+ 
+ namespace ots {
+ 
+ bool OpenTypeGLYF::ParseFlagsForSimpleGlyph(Buffer &glyph,
+                                             uint32_t num_flags,
+@@ -92,17 +93,18 @@ bool OpenTypeGLYF::ParseFlagsForSimpleGl
+ #define Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR  (1u << 5)
+ 
+ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph,
+                                     unsigned gid,
+                                     int16_t num_contours,
+                                     int16_t& xmin,
+                                     int16_t& ymin,
+                                     int16_t& xmax,
+-                                    int16_t& ymax) {
++                                    int16_t& ymax,
++                                    bool is_tricky_font) {
+   // read the end-points array
+   uint16_t num_flags = 0;
+   for (int i = 0; i < num_contours; ++i) {
+     uint16_t tmp_index = 0;
+     if (!glyph.ReadU16(&tmp_index)) {
+       return Error("Can't read contour index %d (glyph %u)", i, gid);
+     }
+     if (tmp_index == 0xffffu) {
+@@ -214,37 +216,42 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buff
+ 
+   if (glyph.remaining() > 3) {
+     // We allow 0-3 bytes difference since gly_length is 4-bytes aligned,
+     // zero-padded length.
+     Warning("Extra bytes at end of the glyph: %d (glyph %u)", glyph.remaining(), gid);
+   }
+ 
+   if (adjusted_bbox) {
+-    Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid);
+-    // copy the numberOfContours field
+-    this->iov.push_back(std::make_pair(glyph.buffer(), 2));
+-    // output a fixed-up version of the bounding box
+-    uint8_t* fixed_bbox = new uint8_t[8];
+-    fixed_bboxes.push_back(fixed_bbox);
+-    xmin = ots_htons(xmin);
+-    std::memcpy(fixed_bbox, &xmin, 2);
+-    ymin = ots_htons(ymin);
+-    std::memcpy(fixed_bbox + 2, &ymin, 2);
+-    xmax = ots_htons(xmax);
+-    std::memcpy(fixed_bbox + 4, &xmax, 2);
+-    ymax = ots_htons(ymax);
+-    std::memcpy(fixed_bbox + 6, &ymax, 2);
+-    this->iov.push_back(std::make_pair(fixed_bbox, 8));
+-    // copy the remainder of the glyph data
+-    this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10));
+-  } else {
+-    this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
++    if (is_tricky_font) {
++      Warning("Glyph bbox was incorrect; NOT adjusting tricky font (glyph %u)", gid);
++    } else {
++      Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid);
++      // copy the numberOfContours field
++      this->iov.push_back(std::make_pair(glyph.buffer(), 2));
++      // output a fixed-up version of the bounding box
++      uint8_t* fixed_bbox = new uint8_t[8];
++      fixed_bboxes.push_back(fixed_bbox);
++      xmin = ots_htons(xmin);
++      std::memcpy(fixed_bbox, &xmin, 2);
++      ymin = ots_htons(ymin);
++      std::memcpy(fixed_bbox + 2, &ymin, 2);
++      xmax = ots_htons(xmax);
++      std::memcpy(fixed_bbox + 4, &xmax, 2);
++      ymax = ots_htons(ymax);
++      std::memcpy(fixed_bbox + 6, &ymax, 2);
++      this->iov.push_back(std::make_pair(fixed_bbox, 8));
++      // copy the remainder of the glyph data
++      this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10));
++      return true;
++    }
+   }
+ 
++  this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
++
+   return true;
+ }
+ 
+ #define ARG_1_AND_2_ARE_WORDS    (1u << 0)
+ #define WE_HAVE_A_SCALE          (1u << 3)
+ #define MORE_COMPONENTS          (1u << 5)
+ #define WE_HAVE_AN_X_AND_Y_SCALE (1u << 6)
+ #define WE_HAVE_A_TWO_BY_TWO     (1u << 7)
+@@ -337,16 +344,20 @@ bool OpenTypeGLYF::Parse(const uint8_t *
+   OpenTypeLOCA *loca = static_cast<OpenTypeLOCA*>(
+       GetFont()->GetTypedTable(OTS_TAG_LOCA));
+   OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>(
+       GetFont()->GetTypedTable(OTS_TAG_HEAD));
+   if (!maxp || !loca || !head) {
+     return Error("Missing maxp or loca or head table needed by glyf table");
+   }
+ 
++  OpenTypeNAME *name = static_cast<OpenTypeNAME*>(
++      GetFont()->GetTypedTable(OTS_TAG_NAME));
++  bool is_tricky = name->IsTrickyFont();
++
+   this->maxp = maxp;
+ 
+   const unsigned num_glyphs = maxp->num_glyphs;
+   std::vector<uint32_t> &offsets = loca->offsets;
+ 
+   if (offsets.size() != num_glyphs + 1) {
+     return Error("Invalid glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1);
+   }
+@@ -392,17 +403,17 @@ bool OpenTypeGLYF::Parse(const uint8_t *
+       return Error("Bad bounding box values bl=(%d, %d), tr=(%d, %d) in glyph %d", xmin, ymin, xmax, ymax, i);
+     }
+ 
+     if (num_contours == 0) {
+       // This is an empty glyph and shouldn’t have any glyph data, but if it
+       // does we will simply ignore it.
+       glyph.set_offset(0);
+     } else if (num_contours > 0) {
+-      if (!ParseSimpleGlyph(glyph, i, num_contours, xmin, ymin, xmax, ymax)) {
++      if (!ParseSimpleGlyph(glyph, i, num_contours, xmin, ymin, xmax, ymax, is_tricky)) {
+         return Error("Failed to parse glyph %d", i);
+       }
+     } else {
+ 
+       ComponentPointCount component_point_count;
+       if (!ParseCompositeGlyph(glyph, &component_point_count)) {
+         return Error("Failed to parse glyph %d", i);
+       }
+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
+@@ -46,17 +46,18 @@ class OpenTypeGLYF : public Table {
+                                 uint32_t *flag_index,
+                                 uint32_t *coordinates_length);
+   bool ParseSimpleGlyph(Buffer &glyph,
+                         unsigned gid,
+                         int16_t num_contours,
+                         int16_t& xmin,
+                         int16_t& ymin,
+                         int16_t& xmax,
+-                        int16_t& ymax);
++                        int16_t& ymax,
++                        bool is_tricky_font);
+   bool ParseCompositeGlyph(
+       Buffer &glyph,
+       ComponentPointCount* component_point_count);
+ 
+ 
+   bool TraverseComponentsCountingPoints(
+       Buffer& glyph,
+       uint16_t base_glyph_id,
+diff --git a/gfx/ots/src/name.cc b/gfx/ots/src/name.cc
+--- a/gfx/ots/src/name.cc
++++ b/gfx/ots/src/name.cc
+@@ -361,9 +361,49 @@ bool OpenTypeNAME::IsValidNameId(uint16_
+     if (added_unicode || added_macintosh || added_windows) {
+       std::sort(this->names.begin(), this->names.end());
+       this->name_ids.insert(nameID);
+     }
+   }
+   return this->name_ids.count(nameID);
+ }
+ 
++// List of font names considered "tricky" (dependent on applying original TrueType instructions) by FreeType, see
++// https://gitlab.freedesktop.org/freetype/freetype/-/blob/2d9fce53d4ce89f36075168282fcdd7289e082f9/src/truetype/ttobjs.c#L170-241
++static const char* tricky_font_names[] = {
++  "cpop",
++  "DFGirl-W6-WIN-BF",
++  "DFGothic-EB",
++  "DFGyoSho-Lt",
++  "DFHei",
++  "DFHSGothic-W5",
++  "DFHSMincho-W3",
++  "DFHSMincho-W7",
++  "DFKaiSho-SB",
++  "DFKaiShu",
++  "DFKai-SB",
++  "DFMing",
++  "DLC",
++  "HuaTianKaiTi?",
++  "HuaTianSongTi?",
++  "Ming(for ISO10646)",
++  "MingLiU",
++  "MingMedium",
++  "PMingLiU",
++  "MingLi43"
++};
++
++bool OpenTypeNAME::IsTrickyFont() const {
++  for (const auto& name : this->names) {
++    const uint16_t id = name.name_id;
++    if (id != 1) {
++      continue;
++    }
++    for (const auto* p : tricky_font_names) {
++      if (name.text.find(p) != std::string::npos) {
++        return true;
++      }
++    }
++  }
++  return false;
++}
++
+ }  // namespace
+diff --git a/gfx/ots/src/name.h b/gfx/ots/src/name.h
+--- a/gfx/ots/src/name.h
++++ b/gfx/ots/src/name.h
+@@ -47,16 +47,17 @@ struct NameRecord {
+ class OpenTypeNAME : public Table {
+  public:
+   explicit OpenTypeNAME(Font *font, uint32_t tag)
+       : Table(font, tag, tag) { }
+ 
+   bool Parse(const uint8_t *data, size_t length);
+   bool Serialize(OTSStream *out);
+   bool IsValidNameId(uint16_t nameID, bool addIfMissing = false);
++  bool IsTrickyFont() const;
+ 
+  private:
+   std::vector<NameRecord> names;
+   std::vector<std::string> lang_tags;
+   std::unordered_set<uint16_t> name_ids;
+ };
+ 
+ }  // namespace ots

+ 425 - 0
mozilla-release/patches/mozilla-central_692390.patch

@@ -0,0 +1,425 @@
+# HG changeset patch
+# User serge-sans-paille <sguelton@mozilla.com>
+# Date 1705584388 0
+#      Thu Jan 18 13:26:28 2024 +0000
+# Node ID 78a0b6dac25d8cabaa44c398988603a79de2f8e4
+# Parent  1b825155fcbc8f6314bd5c16d18949c37403ede6
+Bug 1873636 - vendor gfx/ots using mach vendor r=jfkthame
+
+As a consequence, explicitly extract bug 1850314 as a patch.
+
+Differential Revision: https://phabricator.services.mozilla.com/D198006
+
+diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla
+deleted file mode 100644
+--- a/gfx/ots/README.mozilla
++++ /dev/null
+@@ -1,12 +0,0 @@
+-This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
+-
+-Our reference repository is https://github.com/khaledhosny/ots/.
+-
+-Current revision: 6ba665aa307ea360283191736814863ca398398d (9.1.0)
+-
+-Upstream files included: LICENSE, src/, include/, tests/*.cc
+-
+-Additional files: README.mozilla, src/moz.build
+-
+-Additional patch: ots-visibility.patch (bug 711079).
+-Additional patch: ots-lz4.patch
+diff --git a/gfx/ots/moz.yaml b/gfx/ots/moz.yaml
+new file mode 100644
+--- /dev/null
++++ b/gfx/ots/moz.yaml
+@@ -0,0 +1,42 @@
++schema: 1
++
++bugzilla:
++  product: Core
++  component: "Graphics: Text"
++
++origin:
++  name: ots
++  description: Sanitiser for OpenType project
++
++  url: https://github.com/khaledhosny/ots
++
++  release: 6ba665aa307ea360283191736814863ca398398d (2023-08-16T17:30:00Z).
++  revision: 6ba665aa307ea360283191736814863ca398398d
++
++  license: BSD-3-Clause
++  license-file: LICENSE
++
++vendoring:
++  url: https://github.com/khaledhosny/ots
++  source-hosting: github
++  tracking: commit
++
++  exclude:
++    - ".*"
++    - "**"
++
++  include:
++    - include/
++    - src/
++    - tests/*.cc
++
++  keep:
++    - LICENSE
++    - RLBoxWOFF2Host.*
++    - RLBoxWOFF2Types.*
++
++  patches:
++    - ots-lz4.patch
++    - ots-rlbox.patch
++    - ots-visibility.patch
++    - ots-1850314.patch
+diff --git a/gfx/ots/ots-1850314.patch b/gfx/ots/ots-1850314.patch
+new file mode 100644
+--- /dev/null
++++ b/gfx/ots/ots-1850314.patch
+@@ -0,0 +1,177 @@
++commit 362a59be47f9e187eec43df0938def661be6c972
++Author: Jonathan Kew <jkew@mozilla.com>
++Date:   Wed Aug 30 12:55:02 2023 +0000
++
++    Bug 1850314 - Don't do glyph bounding-box fixup for "tricky" fonts, because it may disrupt glyph rendering on macOS. r=gfx-reviewers,lsalzman
++    
++    Differential Revision: https://phabricator.services.mozilla.com/D187096
++
++diff --git a/src/glyf.cc b/src/glyf.cc
++index 0ed9515ef16d6..31487957bf99b 100644
++--- a/src/glyf.cc
+++++ b/src/glyf.cc
++@@ -10,6 +10,7 @@
++ #include "head.h"
++ #include "loca.h"
++ #include "maxp.h"
+++#include "name.h"
++ 
++ // glyf - Glyph Data
++ // http://www.microsoft.com/typography/otspec/glyf.htm
++@@ -97,7 +98,8 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph,
++                                     int16_t& xmin,
++                                     int16_t& ymin,
++                                     int16_t& xmax,
++-                                    int16_t& ymax) {
+++                                    int16_t& ymax,
+++                                    bool is_tricky_font) {
++   // read the end-points array
++   uint16_t num_flags = 0;
++   for (int i = 0; i < num_contours; ++i) {
++@@ -219,27 +221,32 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph,
++   }
++ 
++   if (adjusted_bbox) {
++-    Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid);
++-    // copy the numberOfContours field
++-    this->iov.push_back(std::make_pair(glyph.buffer(), 2));
++-    // output a fixed-up version of the bounding box
++-    uint8_t* fixed_bbox = new uint8_t[8];
++-    fixed_bboxes.push_back(fixed_bbox);
++-    xmin = ots_htons(xmin);
++-    std::memcpy(fixed_bbox, &xmin, 2);
++-    ymin = ots_htons(ymin);
++-    std::memcpy(fixed_bbox + 2, &ymin, 2);
++-    xmax = ots_htons(xmax);
++-    std::memcpy(fixed_bbox + 4, &xmax, 2);
++-    ymax = ots_htons(ymax);
++-    std::memcpy(fixed_bbox + 6, &ymax, 2);
++-    this->iov.push_back(std::make_pair(fixed_bbox, 8));
++-    // copy the remainder of the glyph data
++-    this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10));
++-  } else {
++-    this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
+++    if (is_tricky_font) {
+++      Warning("Glyph bbox was incorrect; NOT adjusting tricky font (glyph %u)", gid);
+++    } else {
+++      Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid);
+++      // copy the numberOfContours field
+++      this->iov.push_back(std::make_pair(glyph.buffer(), 2));
+++      // output a fixed-up version of the bounding box
+++      uint8_t* fixed_bbox = new uint8_t[8];
+++      fixed_bboxes.push_back(fixed_bbox);
+++      xmin = ots_htons(xmin);
+++      std::memcpy(fixed_bbox, &xmin, 2);
+++      ymin = ots_htons(ymin);
+++      std::memcpy(fixed_bbox + 2, &ymin, 2);
+++      xmax = ots_htons(xmax);
+++      std::memcpy(fixed_bbox + 4, &xmax, 2);
+++      ymax = ots_htons(ymax);
+++      std::memcpy(fixed_bbox + 6, &ymax, 2);
+++      this->iov.push_back(std::make_pair(fixed_bbox, 8));
+++      // copy the remainder of the glyph data
+++      this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10));
+++      return true;
+++    }
++   }
++ 
+++  this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
+++
++   return true;
++ }
++ 
++@@ -342,6 +349,10 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) {
++     return Error("Missing maxp or loca or head table needed by glyf table");
++   }
++ 
+++  OpenTypeNAME *name = static_cast<OpenTypeNAME*>(
+++      GetFont()->GetTypedTable(OTS_TAG_NAME));
+++  bool is_tricky = name->IsTrickyFont();
+++
++   this->maxp = maxp;
++ 
++   const unsigned num_glyphs = maxp->num_glyphs;
++@@ -397,7 +408,7 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) {
++       // does we will simply ignore it.
++       glyph.set_offset(0);
++     } else if (num_contours > 0) {
++-      if (!ParseSimpleGlyph(glyph, i, num_contours, xmin, ymin, xmax, ymax)) {
+++      if (!ParseSimpleGlyph(glyph, i, num_contours, xmin, ymin, xmax, ymax, is_tricky)) {
++         return Error("Failed to parse glyph %d", i);
++       }
++     } else {
++diff --git a/src/glyf.h b/src/glyf.h
++index 05e846f1cb6e8..f85fdc4652fcf 100644
++--- a/src/glyf.h
+++++ b/src/glyf.h
++@@ -51,7 +51,8 @@ class OpenTypeGLYF : public Table {
++                         int16_t& xmin,
++                         int16_t& ymin,
++                         int16_t& xmax,
++-                        int16_t& ymax);
+++                        int16_t& ymax,
+++                        bool is_tricky_font);
++   bool ParseCompositeGlyph(
++       Buffer &glyph,
++       ComponentPointCount* component_point_count);
++diff --git a/src/name.cc b/src/name.cc
++index fc5074b0587a3..7526e1f72b9ea 100644
++--- a/src/name.cc
+++++ b/src/name.cc
++@@ -366,4 +366,44 @@ bool OpenTypeNAME::IsValidNameId(uint16_t nameID, bool addIfMissing) {
++   return this->name_ids.count(nameID);
++ }
++ 
+++// List of font names considered "tricky" (dependent on applying original TrueType instructions) by FreeType, see
+++// https://gitlab.freedesktop.org/freetype/freetype/-/blob/2d9fce53d4ce89f36075168282fcdd7289e082f9/src/truetype/ttobjs.c#L170-241
+++static const char* tricky_font_names[] = {
+++  "cpop",
+++  "DFGirl-W6-WIN-BF",
+++  "DFGothic-EB",
+++  "DFGyoSho-Lt",
+++  "DFHei",
+++  "DFHSGothic-W5",
+++  "DFHSMincho-W3",
+++  "DFHSMincho-W7",
+++  "DFKaiSho-SB",
+++  "DFKaiShu",
+++  "DFKai-SB",
+++  "DFMing",
+++  "DLC",
+++  "HuaTianKaiTi?",
+++  "HuaTianSongTi?",
+++  "Ming(for ISO10646)",
+++  "MingLiU",
+++  "MingMedium",
+++  "PMingLiU",
+++  "MingLi43"
+++};
+++
+++bool OpenTypeNAME::IsTrickyFont() const {
+++  for (const auto& name : this->names) {
+++    const uint16_t id = name.name_id;
+++    if (id != 1) {
+++      continue;
+++    }
+++    for (const auto* p : tricky_font_names) {
+++      if (name.text.find(p) != std::string::npos) {
+++        return true;
+++      }
+++    }
+++  }
+++  return false;
+++}
+++
++ }  // namespace
++diff --git a/src/name.h b/src/name.h
++index 68c7ac096d3f8..a241e77ee26bb 100644
++--- a/src/name.h
+++++ b/src/name.h
++@@ -52,6 +52,7 @@ class OpenTypeNAME : public Table {
++   bool Parse(const uint8_t *data, size_t length);
++   bool Serialize(OTSStream *out);
++   bool IsValidNameId(uint16_t nameID, bool addIfMissing = false);
+++  bool IsTrickyFont() const;
++ 
++  private:
++   std::vector<NameRecord> names;
+diff --git a/gfx/ots/ots-lz4.patch b/gfx/ots/ots-lz4.patch
+--- a/gfx/ots/ots-lz4.patch
++++ b/gfx/ots/ots-lz4.patch
+@@ -1,11 +1,11 @@
+-diff --git a/gfx/ots/src/glat.cc b/gfx/ots/src/glat.cc
+---- a/gfx/ots/src/glat.cc
+-+++ b/gfx/ots/src/glat.cc
++diff --git a/src/glat.cc b/src/glat.cc
++--- a/src/glat.cc
+++++ b/src/glat.cc
+ @@ -4,9 +4,9 @@
+  
+  #include "glat.h"
+  
+  #include "gloc.h"
+ -#include "lz4.h"
+ +#include "mozilla/Compression.h"
+  #include <list>
+@@ -30,19 +30,19 @@ diff --git a/gfx/ots/src/glat.cc b/gfx/o
+ -        return DropGraphite("Decompression failed with error code %d", ret);
+ +          &outputSize);  // return output size
+ +      if (!ret || outputSize != decompressed_size) {
+ +        return DropGraphite("Decompression failed");
+        }
+        return this->Parse(decompressed.get(), decompressed_size, true);
+      }
+      default:
+-diff --git a/gfx/ots/src/silf.cc b/gfx/ots/src/silf.cc
+---- a/gfx/ots/src/silf.cc
+-+++ b/gfx/ots/src/silf.cc
++diff --git a/src/silf.cc b/src/silf.cc
++--- a/src/silf.cc
+++++ b/src/silf.cc
+ @@ -4,9 +4,9 @@
+  
+  #include "silf.h"
+  
+  #include "name.h"
+ -#include "lz4.h"
+ +#include "mozilla/Compression.h"
+  #include <cmath>
+diff --git a/gfx/ots/ots-rlbox.patch b/gfx/ots/ots-rlbox.patch
+new file mode 100644
+--- /dev/null
++++ b/gfx/ots/ots-rlbox.patch
+@@ -0,0 +1,60 @@
++diff --git a/src/ots.cc b/src/ots.cc
++--- a/src/ots.cc
+++++ b/src/ots.cc
++@@ -14,7 +14,7 @@
++ #include <map>
++ #include <vector>
++ 
++-#include <woff2/decode.h>
+++#include "../RLBoxWOFF2Host.h"
++ 
++ // The OpenType Font File
++ // http://www.microsoft.com/typography/otspec/otff.htm
++@@ -511,43 +511,9 @@ bool ProcessWOFF(ots::FontFile *header,
++   return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file);
++ }
++ 
++-bool ProcessWOFF2(ots::FontFile *header,
++-                  ots::OTSStream *output,
++-                  const uint8_t *data,
++-                  size_t length,
++-                  uint32_t index) {
++-  size_t decompressed_size = woff2::ComputeWOFF2FinalSize(data, length);
++-
++-  if (decompressed_size < length) {
++-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is less than compressed size");
++-  }
++-
++-  if (decompressed_size == 0) {
++-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is set to 0");
++-  }
++-  // decompressed font must be <= OTS_MAX_DECOMPRESSED_FILE_SIZE
++-  if (decompressed_size > OTS_MAX_DECOMPRESSED_FILE_SIZE) {
++-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 font exceeds %gMB",
++-                               OTS_MAX_DECOMPRESSED_FILE_SIZE / (1024.0 * 1024.0));
++-  }
++-
++-  if (decompressed_size > output->size()) {
++-    return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 font exceeds output size (%gMB)", output->size() / (1024.0 * 1024.0));
++-  }
++-
++-  std::string buf(decompressed_size, 0);
++-  woff2::WOFF2StringOut out(&buf);
++-  if (!woff2::ConvertWOFF2ToTTF(data, length, &out)) {
++-    return OTS_FAILURE_MSG_HDR("Failed to convert WOFF 2.0 font to SFNT");
++-  }
++-  const uint8_t *decompressed = reinterpret_cast<const uint8_t*>(buf.data());
++-
++-  if (data[4] == 't' && data[5] == 't' && data[6] == 'c' && data[7] == 'f') {
++-    return ProcessTTC(header, output, decompressed, out.Size(), index);
++-  } else {
++-    ots::Font font(header);
++-    return ProcessTTF(header, &font, output, decompressed, out.Size());
++-  }
+++bool ProcessWOFF2(ots::FontFile* header, ots::OTSStream* output,
+++                  const uint8_t* data, size_t length, uint32_t index) {
+++  return RLBoxProcessWOFF2(header, output, data, length, index, ProcessTTC, ProcessTTF);
++ }
++ 
++ ots::TableAction GetTableAction(const ots::FontFile *header, uint32_t tag) {
++
+diff --git a/gfx/ots/ots-visibility.patch b/gfx/ots/ots-visibility.patch
+--- a/gfx/ots/ots-visibility.patch
++++ b/gfx/ots/ots-visibility.patch
+@@ -1,11 +1,11 @@
+-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
++diff --git a/include/opentype-sanitiser.h b/include/opentype-sanitiser.h
++--- a/include/opentype-sanitiser.h
+++++ b/include/opentype-sanitiser.h
+ @@ -4,8 +4,28 @@
+  
+  #ifndef OPENTYPE_SANITISER_H_
+  #define OPENTYPE_SANITISER_H_
+  
+ +#if defined(_WIN32) || defined(__CYGWIN__)
+ +  #define OTS_DLL_IMPORT __declspec(dllimport)
+ +  #define OTS_DLL_EXPORT __declspec(dllexport)
+diff --git a/gfx/ots/sync.sh b/gfx/ots/sync.sh
+deleted file mode 100755
+--- a/gfx/ots/sync.sh
++++ /dev/null
+@@ -1,38 +0,0 @@
+-# This Source Code Form is subject to the terms of the Mozilla Public
+-# License, v. 2.0. If a copy of the MPL was not distributed with this
+-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-
+-if [ $# = 0 ] ; then
+-    echo "usage: ./sync.sh ots-git-directory"
+-    exit 1
+-fi
+-
+-echo "Updating LICENSE..."
+-cp $1/LICENSE .
+-
+-echo "Updating src..."
+-cd src
+-ls | fgrep -v moz.build | xargs rm -rf
+-cp -r $1/src/* .
+-cd ..
+-
+-echo "Updating include..."
+-rm -rf include/
+-cp -r $1/include .
+-
+-echo "Updating tests..."
+-rm -rf tests/*
+-mkdir -p tests
+-cp -r $1/tests/*.cc tests
+-
+-echo "Updating README.mozilla..."
+-REVISION=`cd $1; git log | head -1 | sed "s/commit //"`
+-VERSION=`cd $1; git describe | cut -d '-' -f 1 | sed 's/v//'`
+-sed -e "s/\(Current revision: \).*/\1$REVISION \($VERSION\)/" README.mozilla > README.tmp
+-mv README.tmp README.mozilla
+-
+-echo "Applying ots-visibility.patch..."
+-patch -p3 < ots-visibility.patch
+-
+-echo "Applying ots-lz4.patch..."
+-patch -p3 < ots-lz4.patch

+ 34 - 0
mozilla-release/patches/mozilla-central_731979.patch

@@ -0,0 +1,34 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1710854861 0
+#      Tue Mar 19 13:27:41 2024 +0000
+# Node ID 35bd4ace2aab29a326fe45ac8af6263a654e4923
+# Parent  43d89580ba9c295f67bc3df94ba1aac23b435b63
+Bug 1875367 - Make absolute_offset a size_t variable. r=gfx-reviewers,lsalzman
+
+Differential Revision: https://phabricator.services.mozilla.com/D204912
+
+diff --git a/gfx/ots/src/gdef.cc b/gfx/ots/src/gdef.cc
+--- a/gfx/ots/src/gdef.cc
++++ b/gfx/ots/src/gdef.cc
+@@ -171,18 +171,18 @@ bool OpenTypeGDEF::ParseLigCaretListTabl
+         return Error("Bad caret value table structure %d in glyph %d", j, i);
+       }
+       if (caret_format == 3) {
+         uint16_t offset_device = 0;
+         if (!subtable.ReadU16(&offset_device)) {
+           return Error("Can't read device offset for caret value %d "
+                        "in glyph %d", j, i);
+         }
+-        uint16_t absolute_offset = lig_glyphs[i] + caret_value_offsets[j]
+-                                   + offset_device;
++        size_t absolute_offset = lig_glyphs[i] + caret_value_offsets[j]
++                                 + offset_device;
+         if (offset_device == 0 || absolute_offset >= length) {
+           return Error("Bad device offset for caret value %d in glyph %d: %d",
+                        j, i, offset_device);
+         }
+         if (!ots::ParseDeviceTable(GetFont(), data + absolute_offset,
+                                    length - absolute_offset)) {
+           return Error("Bad device table for caret value %d in glyph %d",
+                        j, i, offset_device);

+ 48 - 0
mozilla-release/patches/mozilla-central_732156.patch

@@ -0,0 +1,48 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1710947406 0
+#      Wed Mar 20 15:10:06 2024 +0000
+# Node ID dfd86e34e53f9f59977cda70a90d29f84429bce3
+# Parent  538aa6838630426342580bd8ea1330e0fcf3d56c
+Bug 1874489 - patch 1 - Don't check STAT designAxisSize if the designAxisCount is zero. r=gfx-reviewers,lsalzman
+
+This is just https://github.com/khaledhosny/ots/pull/277 from upstream, not really part
+of this issue but touching the same code, so simplest to include it here.
+
+Differential Revision: https://phabricator.services.mozilla.com/D204916
+
+diff --git a/gfx/ots/src/stat.cc b/gfx/ots/src/stat.cc
+--- a/gfx/ots/src/stat.cc
++++ b/gfx/ots/src/stat.cc
+@@ -43,28 +43,27 @@ bool OpenTypeSTAT::Parse(const uint8_t* 
+   if (this->majorVersion != 1) {
+     return Drop("Unknown table version");
+   }
+   if (this->minorVersion > 2) {
+     Warning("Unknown minor version, downgrading to 2");
+     this->minorVersion = 2;
+   }
+ 
+-  if (this->designAxisSize < sizeof(AxisRecord)) {
+-    return Drop("Invalid designAxisSize");
+-  }
+-
+   size_t headerEnd = table.offset();
+ 
+   if (this->designAxisCount == 0) {
+     if (this->designAxesOffset != 0) {
+       Warning("Unexpected non-zero designAxesOffset");
+       this->designAxesOffset = 0;
+     }
+   } else {
++    if (this->designAxisSize < sizeof(AxisRecord)) {
++      return Drop("Invalid designAxisSize");
++    }
+     if (this->designAxesOffset < headerEnd ||
+         size_t(this->designAxesOffset) +
+           size_t(this->designAxisCount) * size_t(this->designAxisSize) > length) {
+       return Drop("Invalid designAxesOffset");
+     }
+   }
+ 
+   for (size_t i = 0; i < this->designAxisCount; i++) {

+ 97 - 0
mozilla-release/patches/mozilla-central_732157.patch

@@ -0,0 +1,97 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1710947406 0
+#      Wed Mar 20 15:10:06 2024 +0000
+# Node ID eed1acdcdfbd5e142e91d16ba0a40cd9029fa65d
+# Parent  dfd86e34e53f9f59977cda70a90d29f84429bce3
+Bug 1874489 - patch 2 - Avoid potential arithmetic overflow during Buffer read operations. r=gfx-reviewers,lsalzman
+
+Differential Revision: https://phabricator.services.mozilla.com/D204917
+
+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
+@@ -82,77 +82,76 @@ class Buffer {
+   bool Skip(size_t n_bytes) {
+     return Read(NULL, 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)) {
++    if (length_ < n_bytes || offset_ > length_ - n_bytes) {
+       return OTS_FAILURE();
+     }
+     if (buf) {
+       std::memcpy(buf, buffer_ + offset_, n_bytes);
+     }
+     offset_ += n_bytes;
+     return true;
+   }
+ 
+   inline bool ReadU8(uint8_t *value) {
+-    if (offset_ + 1 > length_) {
++    if (length_ < 1 || offset_ > length_ - 1) {
+       return OTS_FAILURE();
+     }
+     *value = buffer_[offset_];
+     ++offset_;
+     return true;
+   }
+ 
+   bool ReadU16(uint16_t *value) {
+-    if (offset_ + 2 > length_) {
++    if (length_ < 2 || offset_ > length_ - 2) {
+       return OTS_FAILURE();
+     }
+     std::memcpy(value, buffer_ + offset_, sizeof(uint16_t));
+     *value = ots_ntohs(*value);
+     offset_ += 2;
+     return true;
+   }
+ 
+   bool ReadS16(int16_t *value) {
+     return ReadU16(reinterpret_cast<uint16_t*>(value));
+   }
+ 
+   bool ReadU24(uint32_t *value) {
+-    if (offset_ + 3 > length_) {
++    if (length_ < 3 || offset_ > length_ - 3) {
+       return OTS_FAILURE();
+     }
+     *value = static_cast<uint32_t>(buffer_[offset_]) << 16 |
+         static_cast<uint32_t>(buffer_[offset_ + 1]) << 8 |
+         static_cast<uint32_t>(buffer_[offset_ + 2]);
+     offset_ += 3;
+     return true;
+   }
+ 
+   bool ReadU32(uint32_t *value) {
+-    if (offset_ + 4 > length_) {
++    if (length_ < 4 || offset_ > length_ - 4) {
+       return OTS_FAILURE();
+     }
+     std::memcpy(value, buffer_ + offset_, sizeof(uint32_t));
+     *value = ots_ntohl(*value);
+     offset_ += 4;
+     return true;
+   }
+ 
+   bool ReadS32(int32_t *value) {
+     return ReadU32(reinterpret_cast<uint32_t*>(value));
+   }
+ 
+   bool ReadR64(uint64_t *value) {
+-    if (offset_ + 8 > length_) {
++    if (length_ < 8 || offset_ > length_ - 8) {
+       return OTS_FAILURE();
+     }
+     std::memcpy(value, buffer_ + offset_, sizeof(uint64_t));
+     offset_ += 8;
+     return true;
+   }
+ 
+   const uint8_t *buffer() const { return buffer_; }

+ 71 - 0
mozilla-release/patches/mozilla-central_732158.patch

@@ -0,0 +1,71 @@
+# HG changeset patch
+# User Jonathan Kew <jkew@mozilla.com>
+# Date 1710947407 0
+#      Wed Mar 20 15:10:07 2024 +0000
+# Node ID df5fdbb9e3ec967ce01d97c4b36e86a8e6091ed7
+# Parent  eed1acdcdfbd5e142e91d16ba0a40cd9029fa65d
+Bug 1874489 - patch 3 - More careful range checks in STAT parsing. r=gfx-reviewers,lsalzman
+
+Differential Revision: https://phabricator.services.mozilla.com/D204918
+
+diff --git a/gfx/ots/src/stat.cc b/gfx/ots/src/stat.cc
+--- a/gfx/ots/src/stat.cc
++++ b/gfx/ots/src/stat.cc
+@@ -55,18 +55,19 @@ bool OpenTypeSTAT::Parse(const uint8_t* 
+       Warning("Unexpected non-zero designAxesOffset");
+       this->designAxesOffset = 0;
+     }
+   } else {
+     if (this->designAxisSize < sizeof(AxisRecord)) {
+       return Drop("Invalid designAxisSize");
+     }
+     if (this->designAxesOffset < headerEnd ||
+-        size_t(this->designAxesOffset) +
+-          size_t(this->designAxisCount) * size_t(this->designAxisSize) > length) {
++        size_t(this->designAxesOffset) > length ||
++        size_t(this->designAxisCount) * size_t(this->designAxisSize) >
++          length - size_t(this->designAxesOffset)) {
+       return Drop("Invalid designAxesOffset");
+     }
+   }
+ 
+   for (size_t i = 0; i < this->designAxisCount; i++) {
+     table.set_offset(this->designAxesOffset + i * this->designAxisSize);
+     this->designAxes.emplace_back();
+     auto& axis = this->designAxes[i];
+@@ -89,29 +90,32 @@ bool OpenTypeSTAT::Parse(const uint8_t* 
+ 
+   if (this->axisValueCount == 0) {
+     if (this->offsetToAxisValueOffsets != 0) {
+       Warning("Unexpected non-zero offsetToAxisValueOffsets");
+       this->offsetToAxisValueOffsets = 0;
+     }
+   } else {
+     if (this->offsetToAxisValueOffsets < headerEnd ||
+-        size_t(this->offsetToAxisValueOffsets) +
+-          size_t(this->axisValueCount) * sizeof(uint16_t) > length) {
++        size_t(this->offsetToAxisValueOffsets) > length ||
++        size_t(this->axisValueCount) * sizeof(uint16_t) >
++          length - size_t(this->offsetToAxisValueOffsets)) {
+       return Drop("Invalid offsetToAxisValueOffsets");
+     }
+   }
+ 
+   for (size_t i = 0; i < this->axisValueCount; i++) {
+     table.set_offset(this->offsetToAxisValueOffsets + i * sizeof(uint16_t));
+     uint16_t axisValueOffset;
+     if (!table.ReadU16(&axisValueOffset)) {
+       return Drop("Failed to read axis value offset");
+     }
+-    if (this->offsetToAxisValueOffsets + axisValueOffset > length) {
++    // We already checked that offsetToAxisValueOffsets doesn't exceed length,
++    // so this subtraction will not underflow.
++    if (axisValueOffset > length - this->offsetToAxisValueOffsets) {
+       return Drop("Invalid axis value offset");
+     }
+     table.set_offset(this->offsetToAxisValueOffsets + axisValueOffset);
+     uint16_t format;
+     if (!table.ReadU16(&format)) {
+       return Drop("Failed to read axis value format");
+     }
+     this->axisValues.emplace_back(format);

+ 17 - 0
mozilla-release/patches/series

@@ -6742,3 +6742,20 @@ TOP-NOBUG-REGEXP-45-final-25318.patch
 TOP-NOBUG-REGEXP-46-fixes-25318.patch
 WIP-NOBUG-seamonkey-credits.patch
 WIP-NOBUG-implement-about-seamonkey-mozilla.patch
+mozilla-central_603605.patch
+mozilla-central_635136.patch
+1789403-3-106a1.patch
+mozilla-central_635139.patch
+mozilla-central_636122.patch
+mozilla-central_636497.patch
+mozilla-central_637080.patch
+mozilla-central_645670.patch
+mozilla-central_655952.patch
+mozilla-central_665930.patch
+mozilla-central_675504.patch
+mozilla-central_676459.patch
+mozilla-central_692390.patch
+mozilla-central_731979.patch
+mozilla-central_732156.patch
+mozilla-central_732157.patch
+mozilla-central_732158.patch