Bug 407059 - Part 1: Add a gfxMathTable class to read the MATH table. r=jfkthame
authorFrédéric Wang <fred.wang@free.fr>
Wed, 23 Apr 2014 06:57:42 -0700
changeset 179767 de506f4610749e5082f238768635da06d12e204f
parent 179766 cb4deaae3336865e25b83a42a8d2a130d93ea659
child 179768 7d09c6ea9ae5bb52d8b0d214d133c02f003a4909
push id26639
push userryanvm@gmail.com
push dateWed, 23 Apr 2014 20:42:51 +0000
treeherdermozilla-central@ed0236a51ed3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs407059
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 407059 - Part 1: Add a gfxMathTable class to read the MATH table. r=jfkthame
gfx/thebes/MathTableStructures.h
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxMathTable.cpp
gfx/thebes/gfxMathTable.h
gfx/thebes/moz.build
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/MathTableStructures.h
@@ -0,0 +1,121 @@
+/* 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/. */
+
+// This file contains the structures described in Microsoft's document
+// "The MATH table and OpenType Features for Math Processing" (not yet public).
+//
+// Arrays of varying size are indicated in comments. Typically, gfxMathTable
+// will read the header of the structure first, verify that there is enough
+// space for the specified arrays and then use a pointer to browse these arrays.
+
+#ifndef MATH_TABLE_STRUCTURE_H
+#define MATH_TABLE_STRUCTURE_H
+
+#include "gfxFontUtils.h"
+
+typedef mozilla::AutoSwap_PRUint16 Count16;
+typedef mozilla::AutoSwap_PRUint16 GlyphID;
+typedef mozilla::AutoSwap_PRUint16 Offset;
+
+struct MathValueRecord {
+  mozilla::AutoSwap_PRInt16  mValue;
+  Offset                     mDeviceTable;
+};
+
+struct RangeRecord {
+  GlyphID                    mStart;
+  GlyphID                    mEnd;
+  mozilla::AutoSwap_PRUint16 mStartCoverageIndex;
+};
+
+struct Coverage {
+  mozilla::AutoSwap_PRUint16 mFormat;
+};
+
+struct CoverageFormat1 {
+  mozilla::AutoSwap_PRUint16 mFormat;
+  Count16                    mGlyphCount;
+  // GlyphID                 mGlyphArray[mGlyphCount]
+};
+
+struct CoverageFormat2 {
+  mozilla::AutoSwap_PRUint16 mFormat;
+  Count16                    mRangeCount;
+  // RangeRecord             mRangeArray[mRangeCount];
+};
+
+struct MATHTableHeader {
+  mozilla::AutoSwap_PRUint32 mVersion;
+  Offset                     mMathConstants;
+  Offset                     mMathGlyphInfo;
+  Offset                     mMathVariants;
+};
+
+struct MathConstants {
+  mozilla::AutoSwap_PRInt16  mInt16[gfxFontEntry::ScriptScriptPercentScaleDown -
+                                    gfxFontEntry::ScriptPercentScaleDown + 1];
+  mozilla::AutoSwap_PRUint16 mUint16[gfxFontEntry::DisplayOperatorMinHeight -
+                                     gfxFontEntry::
+                                     DelimitedSubFormulaMinHeight + 1];
+  MathValueRecord            mMathValues[gfxFontEntry::RadicalKernAfterDegree -
+                                         gfxFontEntry::MathLeading + 1];
+  mozilla::AutoSwap_PRUint16 mRadicalDegreeBottomRaisePercent;
+};
+
+struct MathGlyphInfo {
+  Offset mMathItalicsCorrectionInfo;
+  Offset mMathTopAccentAttachment;
+  Offset mExtendedShapeCoverage;
+  Offset mMathKernInfo;
+};
+
+struct MathItalicsCorrectionInfo {
+  Offset  mCoverage;
+  Count16 mItalicsCorrectionCount;
+  // MathValueRecord mItalicsCorrection[mItalicsCorrectionCount]
+};
+
+struct MathVariants {
+  mozilla::AutoSwap_PRUint16 mMinConnectorOverlap;
+  Offset                     mVertGlyphCoverage;
+  Offset                     mHorizGlyphCoverage;
+  Count16                    mVertGlyphCount;
+  Count16                    mHorizGlyphCount;
+  // Offset                  mVertGlyphConstruction[mVertGlyphCount];
+  // Offset                  mHorizGlyphConstruction[mHorizGlyphCount];
+};
+
+struct MathGlyphVariantRecord {
+  GlyphID                    mVariantGlyph;
+  mozilla::AutoSwap_PRUint16 mAdvanceMeasurement;
+};
+
+struct MathGlyphConstruction {
+  Offset                    mGlyphAssembly;
+  Count16                   mVariantCount;
+  // MathGlyphVariantRecord mMathGlyphVariantRecord[mVariantCount]
+};
+
+struct GlyphPartRecord {
+  GlyphID	              mGlyph;
+  mozilla::AutoSwap_PRUint16 mStartConnectorLength;
+  mozilla::AutoSwap_PRUint16 mEndConnectorLength;
+  mozilla::AutoSwap_PRUint16 mFullAdvance;
+  mozilla::AutoSwap_PRUint16 mPartFlags;
+};
+
+// PartFlags enumeration currently uses only one bit:
+// 0x0001 If set, the part can be skipped or repeated.
+// 0xFFFE Reserved.
+enum {
+  PART_FLAG_EXTENDER = 0x01
+};
+
+struct GlyphAssembly {
+  MathValueRecord    mItalicsCorrection;
+  Count16            mPartCount;
+  // GlyphPartRecord mPartRecords[mPartCount]
+};
+
+#endif
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -33,16 +33,17 @@
 #include "nsStyleConsts.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "gfxSVGGlyphs.h"
+#include "gfxMathTable.h"
 #include "gfx2DGlue.h"
 
 #if defined(XP_MACOSX)
 #include "nsCocoaFeatures.h"
 #endif
 
 #include "cairo.h"
 #include "gfxFontTest.h"
@@ -107,16 +108,17 @@ gfxFontEntry::gfxFontEntry() :
     mIsBadUnderlineFont(false),
     mIsUserFont(false),
     mIsLocalUserFont(false),
     mStandardFace(false),
     mSymbolFont(false),
     mIgnoreGDEF(false),
     mIgnoreGSUB(false),
     mSVGInitialized(false),
+    mMathInitialized(false),
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
     mSkipDefaultFeatureSpaceCheck(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
@@ -136,16 +138,17 @@ gfxFontEntry::gfxFontEntry(const nsAStri
     mName(aName), mItalic(false), mFixedPitch(false),
     mIsProxy(false), mIsValid(true),
     mIsBadUnderlineFont(false), mIsUserFont(false),
     mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
     mSymbolFont(false),
     mIgnoreGDEF(false),
     mIgnoreGSUB(false),
     mSVGInitialized(false),
+    mMathInitialized(false),
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
     mSkipDefaultFeatureSpaceCheck(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
@@ -384,16 +387,88 @@ void
 gfxFontEntry::NotifyGlyphsChanged()
 {
     for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
         gfxFont* font = mFontsUsingSVGGlyphs[i];
         font->NotifyGlyphsChanged();
     }
 }
 
+bool
+gfxFontEntry::TryGetMathTable(gfxFont* aFont)
+{
+    if (!mMathInitialized) {
+        mMathInitialized = true;
+
+        // If UnitsPerEm is not known/valid, we can't use MATH table
+        if (UnitsPerEm() == kInvalidUPEM) {
+            return false;
+        }
+
+        // We don't use AutoTable here because we'll pass ownership of this
+        // blob to the gfxMathTable, once we've confirmed the table exists
+        hb_blob_t *mathTable = GetFontTable(TRUETYPE_TAG('M','A','T','H'));
+        if (!mathTable) {
+            return false;
+        }
+
+        // gfxMathTable will hb_blob_destroy() the table when it is finished
+        // with it.
+        mMathTable = new gfxMathTable(mathTable);
+        if (!mMathTable->HasValidHeaders()) {
+            mMathTable = nullptr;
+            return false;
+        }
+    }
+
+    return !!mMathTable;
+}
+
+gfxFloat
+gfxFontEntry::GetMathConstant(gfxFontEntry::MathConstant aConstant)
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    gfxFloat value = mMathTable->GetMathConstant(aConstant);
+    if (aConstant == gfxFontEntry::ScriptPercentScaleDown ||
+        aConstant == gfxFontEntry::ScriptScriptPercentScaleDown ||
+        aConstant == gfxFontEntry::RadicalDegreeBottomRaisePercent) {
+        return value / 100.0;
+    }
+    return value / mUnitsPerEm;
+}
+
+bool
+gfxFontEntry::GetMathItalicsCorrection(uint32_t aGlyphID,
+                                       gfxFloat* aItalicCorrection)
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    int16_t italicCorrection;
+    if (!mMathTable->GetMathItalicsCorrection(aGlyphID, &italicCorrection)) {
+        return false;
+    }
+    *aItalicCorrection = gfxFloat(italicCorrection) / mUnitsPerEm;
+    return true;
+}
+
+uint32_t
+gfxFontEntry::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                  uint16_t aSize)
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    return mMathTable->GetMathVariantsSize(aGlyphID, aVertical, aSize);
+}
+
+bool
+gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                                   uint32_t aGlyphs[4])
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    return mMathTable->GetMathVariantsParts(aGlyphID, aVertical, aGlyphs);
+}
+
 /**
  * FontTableBlobData
  *
  * See FontTableHashEntry for the general strategy.
  */
 
 class gfxFontEntry::FontTableBlobData {
 public:
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -43,16 +43,17 @@ class gfxTextRun;
 class gfxFont;
 class gfxFontFamily;
 class gfxFontGroup;
 class gfxUserFontSet;
 class gfxUserFontData;
 class gfxShapedText;
 class gfxShapedWord;
 class gfxSVGGlyphs;
+class gfxMathTable;
 class gfxTextContextPaint;
 class FontInfoData;
 
 class nsILanguageAtomService;
 
 #define FONT_MAX_SIZE                  2000.0
 
 #define NO_FONT_LANGUAGE_OVERRIDE      0
@@ -306,16 +307,89 @@ public:
     bool GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId,
                             gfxRect *aResult);
     bool RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, int aDrawMode,
                         gfxTextContextPaint *aContextPaint);
     // Call this when glyph geometry or rendering has changed
     // (e.g. animated SVG glyphs)
     void NotifyGlyphsChanged();
 
+    enum MathConstant {
+        // The order of the constants must match the order of the fields
+        // defined in the MATH table.
+        ScriptPercentScaleDown,
+        ScriptScriptPercentScaleDown,
+        DelimitedSubFormulaMinHeight,
+        DisplayOperatorMinHeight,
+        MathLeading,
+        AxisHeight,
+        AccentBaseHeight,
+        FlattenedAccentBaseHeight,
+        SubscriptShiftDown,
+        SubscriptTopMax,
+        SubscriptBaselineDropMin,
+        SuperscriptShiftUp,
+        SuperscriptShiftUpCramped,
+        SuperscriptBottomMin,
+        SuperscriptBaselineDropMax,
+        SubSuperscriptGapMin,
+        SuperscriptBottomMaxWithSubscript,
+        SpaceAfterScript,
+        UpperLimitGapMin,
+        UpperLimitBaselineRiseMin,
+        LowerLimitGapMin,
+        LowerLimitBaselineDropMin,
+        StackTopShiftUp,
+        StackTopDisplayStyleShiftUp,
+        StackBottomShiftDown,
+        StackBottomDisplayStyleShiftDown,
+        StackGapMin,
+        StackDisplayStyleGapMin,
+        StretchStackTopShiftUp,
+        StretchStackBottomShiftDown,
+        StretchStackGapAboveMin,
+        StretchStackGapBelowMin,
+        FractionNumeratorShiftUp,
+        FractionNumeratorDisplayStyleShiftUp,
+        FractionDenominatorShiftDown,
+        FractionDenominatorDisplayStyleShiftDown,
+        FractionNumeratorGapMin,
+        FractionNumDisplayStyleGapMin,
+        FractionRuleThickness,
+        FractionDenominatorGapMin,
+        FractionDenomDisplayStyleGapMin,
+        SkewedFractionHorizontalGap,
+        SkewedFractionVerticalGap,
+        OverbarVerticalGap,
+        OverbarRuleThickness,
+        OverbarExtraAscender,
+        UnderbarVerticalGap,
+        UnderbarRuleThickness,
+        UnderbarExtraDescender,
+        RadicalVerticalGap,
+        RadicalDisplayStyleVerticalGap,
+        RadicalRuleThickness,
+        RadicalExtraAscender,
+        RadicalKernBeforeDegree,
+        RadicalKernAfterDegree,
+        RadicalDegreeBottomRaisePercent
+    };
+
+    // Call TryGetMathTable to try to load the Open Type MATH table. The other
+    // functions forward the call to the gfxMathTable class. The GetMath...()
+    // functions MUST NOT be called unless TryGetMathTable() has returned true.
+    bool     TryGetMathTable(gfxFont* aFont);
+    gfxFloat GetMathConstant(MathConstant aConstant);
+    bool     GetMathItalicsCorrection(uint32_t aGlyphID,
+                                      gfxFloat* aItalicCorrection);
+    uint32_t GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                 uint16_t aSize);
+    bool     GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                                  uint32_t aGlyphs[4]);
+
     virtual bool MatchesGenericFamily(const nsACString& aGeneric) const {
         return true;
     }
     virtual bool SupportsLangGroup(nsIAtom *aLangGroup) const {
         return true;
     }
 
     // Access to raw font table data (needed for Harfbuzz):
@@ -428,16 +502,17 @@ public:
     bool             mIsBadUnderlineFont : 1;
     bool             mIsUserFont  : 1;
     bool             mIsLocalUserFont  : 1;
     bool             mStandardFace : 1;
     bool             mSymbolFont  : 1;
     bool             mIgnoreGDEF  : 1;
     bool             mIgnoreGSUB  : 1;
     bool             mSVGInitialized : 1;
+    bool             mMathInitialized : 1;
     bool             mHasSpaceFeaturesInitialized : 1;
     bool             mHasSpaceFeatures : 1;
     bool             mHasSpaceFeaturesKerning : 1;
     bool             mHasSpaceFeaturesNonKerning : 1;
     bool             mSkipDefaultFeatureSpaceCheck : 1;
     bool             mHasGraphiteTables : 1;
     bool             mCheckedForGraphiteTables : 1;
     bool             mHasCmapTable : 1;
@@ -453,16 +528,17 @@ public:
 
     nsRefPtr<gfxCharacterMap> mCharacterMap;
     uint32_t         mUVSOffset;
     nsAutoArrayPtr<uint8_t> mUVSData;
     nsAutoPtr<gfxUserFontData> mUserFontData;
     nsAutoPtr<gfxSVGGlyphs> mSVGGlyphs;
     // list of gfxFonts that are using SVG glyphs
     nsTArray<gfxFont*> mFontsUsingSVGGlyphs;
+    nsAutoPtr<gfxMathTable> mMathTable;
     nsTArray<gfxFontFeature> mFeatureSettings;
     uint32_t         mLanguageOverride;
 
 protected:
     friend class gfxPlatformFontList;
     friend class gfxMacPlatformFontList;
     friend class gfxUserFcFontEntry;
     friend class gfxFontFamily;
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.cpp
@@ -0,0 +1,459 @@
+/* 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/. */
+
+#include "gfxMathTable.h"
+
+#include "MathTableStructures.h"
+#include "harfbuzz/hb.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+gfxMathTable::gfxMathTable(hb_blob_t* aMathTable)
+  : mMathTable(aMathTable)
+  , mGlyphConstruction(nullptr)
+  , mGlyphID(-1)
+  , mVertical(false)
+{
+}
+
+gfxMathTable::~gfxMathTable()
+{
+  hb_blob_destroy(mMathTable);
+}
+
+bool
+gfxMathTable::HasValidHeaders()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  // Verify the MATH table header.
+  if (!ValidStructure(mathData, sizeof(MATHTableHeader))) {
+    return false;
+  }
+  const MATHTableHeader* header = GetMATHTableHeader();
+  if (uint32_t(header->mVersion) != 0x00010000 ||
+      !ValidOffset(mathData, uint16_t(header->mMathConstants)) ||
+      !ValidOffset(mathData, uint16_t(header->mMathGlyphInfo)) ||
+      !ValidOffset(mathData, uint16_t(header->mMathVariants))) {
+    return false;
+  }
+
+  // Verify the MathConstants header.
+  const MathConstants* mathconstants = GetMathConstants();
+  const char* start = reinterpret_cast<const char*>(mathconstants);
+  if (!ValidStructure(start, sizeof(MathConstants))) {
+    return false;
+  }
+
+  // Verify the MathGlyphInfo header.
+  const MathGlyphInfo* mathglyphinfo = GetMathGlyphInfo();
+  start = reinterpret_cast<const char*>(mathglyphinfo);
+  if (!ValidStructure(start, sizeof(MathGlyphInfo))) {
+    return false;
+  }
+
+  // Verify the MathVariants header.
+  const MathVariants* mathvariants = GetMathVariants();
+  start = reinterpret_cast<const char*>(mathvariants);
+  if (!ValidStructure(start, sizeof(MathVariants)) ||
+      !ValidStructure(start,
+                      sizeof(MathVariants) + sizeof(Offset) *
+                      (uint16_t(mathvariants->mVertGlyphCount) +
+                       uint16_t(mathvariants->mHorizGlyphCount))) ||
+      !ValidOffset(start, uint16_t(mathvariants->mVertGlyphCoverage)) ||
+      !ValidOffset(start, uint16_t(mathvariants->mHorizGlyphCoverage))) {
+    return false;
+  }
+
+  return true;
+}
+
+int32_t
+gfxMathTable::GetMathConstant(gfxFontEntry::MathConstant aConstant)
+{
+  const MathConstants* mathconstants = GetMathConstants();
+
+  if (aConstant <= gfxFontEntry::ScriptScriptPercentScaleDown) {
+    return int16_t(mathconstants->mInt16[aConstant]);
+  }
+
+  if (aConstant <= gfxFontEntry::DisplayOperatorMinHeight) {
+    return
+      uint16_t(mathconstants->
+               mUint16[aConstant - gfxFontEntry::DelimitedSubFormulaMinHeight]);
+  }
+
+  if (aConstant <= gfxFontEntry::RadicalKernAfterDegree) {
+    return int16_t(mathconstants->
+                   mMathValues[aConstant - gfxFontEntry::MathLeading].mValue);
+  }
+
+  return uint16_t(mathconstants->mRadicalDegreeBottomRaisePercent);
+}
+
+bool
+gfxMathTable::GetMathItalicsCorrection(uint32_t aGlyphID,
+                                       int16_t* aItalicCorrection)
+{
+  const MathGlyphInfo* mathglyphinfo = GetMathGlyphInfo();
+
+  // Get the offset of the italic correction and verify whether it is valid.
+  const char* start = reinterpret_cast<const char*>(mathglyphinfo);
+  uint16_t offset = mathglyphinfo->mMathItalicsCorrectionInfo;
+  if (offset == 0 || !ValidOffset(start, offset)) {
+    return false;
+  }
+  start += offset;
+
+  // Verify the validity of the MathItalicsCorrectionInfo and retrieve it.
+  if (!ValidStructure(start, sizeof(MathItalicsCorrectionInfo))) {
+    return false;
+  }
+  const MathItalicsCorrectionInfo* italicsCorrectionInfo =
+    reinterpret_cast<const MathItalicsCorrectionInfo*>(start);
+
+  // Get the coverage index for the glyph.
+  offset = italicsCorrectionInfo->mCoverage;
+  const Coverage* coverage =
+    reinterpret_cast<const Coverage*>(start + offset);
+  int32_t i = GetCoverageIndex(coverage, aGlyphID);
+
+  // Get the ItalicsCorrection.
+  uint16_t count = italicsCorrectionInfo->mItalicsCorrectionCount;
+  if (i < 0 || i >= count) {
+    return false;
+  }
+  start = reinterpret_cast<const char*>(italicsCorrectionInfo + 1);
+  if (!ValidStructure(start, count * sizeof(MathValueRecord))) {
+    return false;
+  }
+  const MathValueRecord* mathValueRecordArray =
+    reinterpret_cast<const MathValueRecord*>(start);
+
+  *aItalicCorrection = int16_t(mathValueRecordArray[i].mValue);
+  return true;
+}
+
+uint32_t
+gfxMathTable::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                  uint16_t aSize)
+{
+  // Select the glyph construction.
+  SelectGlyphConstruction(aGlyphID, aVertical);
+  if (!mGlyphConstruction) {
+    return 0;
+  }
+
+  // Verify the validity of the array of the MathGlyphVariantRecord's and
+  // whether there is a variant of the requested size.
+  uint16_t count = mGlyphConstruction->mVariantCount;
+  const char* start = reinterpret_cast<const char*>(mGlyphConstruction + 1);
+  if (aSize >= count ||
+      !ValidStructure(start, count * sizeof(MathGlyphVariantRecord))) {
+    return 0;
+  }
+
+  // Return the glyph index of the requested size variant.
+  const MathGlyphVariantRecord* recordArray =
+    reinterpret_cast<const MathGlyphVariantRecord*>(start);
+  return uint32_t(recordArray[aSize].mVariantGlyph);
+}
+
+bool
+gfxMathTable::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                                   uint32_t aGlyphs[4])
+{
+  // Get the glyph assembly corresponding to that (aGlyphID, aVertical) pair.
+  const GlyphAssembly* glyphAssembly = GetGlyphAssembly(aGlyphID, aVertical);
+  if (!glyphAssembly) {
+    return false;
+  }
+
+  // Verify the validity of the array of GlyphPartRecord's and retrieve it.
+  uint16_t count = glyphAssembly->mPartCount;
+  const char* start = reinterpret_cast<const char*>(glyphAssembly + 1);
+  if (!ValidStructure(start, count * sizeof(GlyphPartRecord))) {
+    return false;
+  }
+  const GlyphPartRecord* recordArray =
+    reinterpret_cast<const GlyphPartRecord*>(start);
+
+  // XXXfredw The structure of the Open Type Math table is a bit more general
+  // than the one currently used by the nsMathMLChar code, so we try to fallback
+  // in reasonable way. We use the approach of the copyComponents function in
+  // github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py
+  //
+  // The nsMathMLChar code can use at most 3 non extender pieces (aGlyphs[0],
+  // aGlyphs[1] and aGlyphs[2]) and the extenders between these pieces should
+  // all be the same (aGlyphs[4]). Also, the parts of vertical assembly are
+  // stored from bottom to top in the Open Type MATH table while they are
+  // stored from top to bottom in nsMathMLChar.
+
+  // Count the number of non extender pieces
+  uint16_t nonExtenderCount = 0;
+  for (uint16_t i = 0; i < count; i++) {
+    if (!(uint16_t(recordArray[i].mPartFlags) & PART_FLAG_EXTENDER)) {
+      nonExtenderCount++;
+    }
+  }
+  if (nonExtenderCount > 3) {
+    // Not supported: too many pieces
+    return false;
+  }
+
+  // Now browse the list of pieces
+
+  // 0 = look for a left/bottom glyph
+  // 1 = look for an extender between left/bottom and mid
+  // 2 = look for a middle glyph
+  // 3 = look for an extender between middle and right/top
+  // 4 = look for a right/top glyph
+  // 5 = no more piece expected
+  uint8_t state = 0;
+
+  // First extender char found.
+  uint32_t extenderChar = 0;
+
+  // Clear the aGlyphs table.
+  memset(aGlyphs, 0, sizeof(uint32_t) * 4);
+
+  for (uint16_t i = 0; i < count; i++) {
+
+    bool isExtender = uint16_t(recordArray[i].mPartFlags) & PART_FLAG_EXTENDER;
+    uint32_t glyph = recordArray[i].mGlyph;
+
+    if ((state == 1 || state == 2) && nonExtenderCount < 3) {
+      // do not try to find a middle glyph
+      state += 2;
+    }
+
+    if (isExtender) {
+      if (!extenderChar) {
+        extenderChar = glyph;
+        aGlyphs[3] = extenderChar;
+      } else if (extenderChar != glyph)  {
+        // Not supported: different extenders
+        return false;
+      }
+
+      if (state == 0) { // or state == 1
+        // ignore left/bottom piece and multiple successive extenders
+        state = 1;
+      } else if (state == 2) { // or state == 3
+        // ignore middle piece and multiple successive extenders
+        state = 3;
+      } else if (state >= 4) {
+        // Not supported: unexpected extender
+        return false;
+      }
+
+      continue;
+    }
+
+    if (state == 0) {
+      // copy left/bottom part
+      aGlyphs[mVertical ? 2 : 0] = glyph;
+      state = 1;
+      continue;
+    }
+
+    if (state == 1 || state == 2) {
+      // copy middle part
+      aGlyphs[1] = glyph;
+      state = 3;
+      continue;
+    }
+
+    if (state == 3 || state == 4) {
+      // copy right/top part
+      aGlyphs[mVertical ? 0 : 2] = glyph;
+      state = 5;
+    }
+
+  }
+
+  return true;
+}
+
+bool
+gfxMathTable::ValidStructure(const char* aStart, uint16_t aSize)
+{
+  unsigned int mathDataLength;
+  const char* mathData = hb_blob_get_data(mMathTable, &mathDataLength);
+  return (mathData <= aStart &&
+          aStart + aSize <= mathData + mathDataLength);
+}
+
+bool
+gfxMathTable::ValidOffset(const char* aStart, uint16_t aOffset)
+{
+  unsigned int mathDataLength;
+  const char* mathData = hb_blob_get_data(mMathTable, &mathDataLength);
+  return (mathData <= aStart + aOffset &&
+          aStart + aOffset < mathData + mathDataLength);
+}
+
+const MATHTableHeader*
+gfxMathTable::GetMATHTableHeader()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return reinterpret_cast<const MATHTableHeader*>(mathData);
+}
+
+const MathConstants*
+gfxMathTable::GetMathConstants()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return
+    reinterpret_cast<const MathConstants*>(mathData +
+                                           uint16_t(GetMATHTableHeader()->
+                                                    mMathConstants));
+}
+
+const MathGlyphInfo*
+gfxMathTable::GetMathGlyphInfo()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return
+    reinterpret_cast<const MathGlyphInfo*>(mathData +
+                                           uint16_t(GetMATHTableHeader()->
+                                                    mMathGlyphInfo));
+}
+
+const MathVariants*
+gfxMathTable::GetMathVariants()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return
+    reinterpret_cast<const MathVariants*>(mathData +
+                                          uint16_t(GetMATHTableHeader()->
+                                                   mMathVariants));
+}
+
+const GlyphAssembly*
+gfxMathTable::GetGlyphAssembly(uint32_t aGlyphID, bool aVertical)
+{
+  // Select the glyph construction.
+  SelectGlyphConstruction(aGlyphID, aVertical);
+  if (!mGlyphConstruction) {
+    return nullptr;
+  }
+
+  // Get the offset of the glyph assembly and verify whether it is valid.
+  const char* start = reinterpret_cast<const char*>(mGlyphConstruction);
+  uint16_t offset = mGlyphConstruction->mGlyphAssembly;
+  if (offset == 0 || !ValidOffset(start, offset)) {
+    return nullptr;
+  }
+  start += offset;
+
+  // Verify the validity of the GlyphAssembly and return it.
+  if (!ValidStructure(start, sizeof(GlyphAssembly))) {
+    return nullptr;
+  }
+  return reinterpret_cast<const GlyphAssembly*>(start);
+}
+
+int32_t
+gfxMathTable::GetCoverageIndex(const Coverage* aCoverage, uint32_t aGlyph)
+{
+  if (uint16_t(aCoverage->mFormat) == 1) {
+    // Coverage Format 1: list of individual glyph indices in the glyph set.
+    const CoverageFormat1* table =
+      reinterpret_cast<const CoverageFormat1*>(aCoverage);
+    uint16_t count = table->mGlyphCount;
+    const char* start = reinterpret_cast<const char*>(table + 1);
+    if (ValidStructure(start, count * sizeof(GlyphID))) {
+      const GlyphID* glyphArray =
+        reinterpret_cast<const GlyphID*>(start);
+      uint32_t imin = 0, imax = count;
+      while (imin < imax) {
+        uint32_t imid = (imin + imax) >> 1;
+        uint16_t glyphMid = glyphArray[imid];
+        if (glyphMid == aGlyph) {
+          return imid;
+        }
+        if (glyphMid < aGlyph) {
+          imin = imid + 1;
+        } else {
+          imax = imid;
+        }
+      }
+    }
+  } else if (uint16_t(aCoverage->mFormat) == 2) {
+    // Coverage Format 2: ranges of consecutive indices.
+    const CoverageFormat2* table =
+      reinterpret_cast<const CoverageFormat2*>(aCoverage);
+    uint16_t count = table->mRangeCount;
+    const char* start = reinterpret_cast<const char*>(table + 1);
+    if (ValidStructure(start, count * sizeof(RangeRecord))) {
+      const RangeRecord* rangeArray =
+        reinterpret_cast<const RangeRecord*>(start);
+      uint32_t imin = 0, imax = count;
+      while (imin < imax) {
+        uint32_t imid = (imin + imax) >> 1;
+        uint16_t rStart = rangeArray[imid].mStart;
+        uint16_t rEnd = rangeArray[imid].mEnd;
+        if (rEnd < aGlyph) {
+          imin = imid + 1;
+        } else if (aGlyph < rStart) {
+          imax = imid;
+        } else {
+          return (uint16_t(rangeArray[imid].mStartCoverageIndex) +
+                  aGlyph - rStart);
+        }
+      }
+    }
+  }
+  return -1;
+}
+
+void
+gfxMathTable::SelectGlyphConstruction(uint32_t aGlyphID, bool aVertical)
+{
+  if (mGlyphID == aGlyphID && mVertical == aVertical) {
+    // The (glyph, direction) pair is already selected: nothing to do.
+    return;
+  }
+
+  // Update our cached values.
+  mVertical = aVertical;
+  mGlyphID = aGlyphID;
+  mGlyphConstruction = nullptr;
+
+  // Get the coverage index for the new values.
+  const MathVariants* mathvariants = GetMathVariants();
+  const char* start = reinterpret_cast<const char*>(mathvariants);
+  uint16_t offset = (aVertical ?
+                     mathvariants->mVertGlyphCoverage :
+                     mathvariants->mHorizGlyphCoverage);
+  const Coverage* coverage =
+    reinterpret_cast<const Coverage*>(start + offset);
+  int32_t i = GetCoverageIndex(coverage, aGlyphID);
+
+  // Get the offset to the glyph construction.
+  uint16_t count = (aVertical ?
+                    mathvariants->mVertGlyphCount :
+                    mathvariants->mHorizGlyphCount);
+  start = reinterpret_cast<const char*>(mathvariants + 1);
+  if (i < 0 || i >= count) {
+    return;
+  }
+  if (!aVertical) {
+    start += uint16_t(mathvariants->mVertGlyphCount) * sizeof(Offset);
+  }
+  if (!ValidStructure(start, count * sizeof(Offset))) {
+    return;
+  }
+  const Offset* offsetArray = reinterpret_cast<const Offset*>(start);
+  offset = uint16_t(offsetArray[i]);
+
+  // Make mGlyphConstruction point to the desired glyph construction.
+  start = reinterpret_cast<const char*>(mathvariants);
+  if (!ValidStructure(start + offset, sizeof(MathGlyphConstruction))) {
+    return;
+  }
+  mGlyphConstruction =
+    reinterpret_cast<const MathGlyphConstruction*>(start + offset);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.h
@@ -0,0 +1,122 @@
+/* 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/. */
+
+#ifndef GFX_MATH_TABLE_H
+#define GFX_MATH_TABLE_H
+
+#include "gfxFont.h"
+
+struct Coverage;
+struct GlyphAssembly;
+struct MATHTableHeader;
+struct MathConstants;
+struct MathGlyphConstruction;
+struct MathGlyphInfo;
+struct MathVariants;
+
+/**
+ * Used by |gfxFontEntry| to represent the MATH table of an OpenType font.
+ * Each |gfxFontEntry| owns at most one |gfxMathTable| instance.
+ */
+class gfxMathTable
+{
+public:
+    /**
+     * @param aMathTable The MATH table from the OpenType font
+     *
+     * The gfxMathTable object takes over ownership of the blob references
+     * that are passed in, and will hb_blob_destroy() them when finished;
+     * the caller should -not- destroy this reference.
+     */
+    gfxMathTable(hb_blob_t* aMathTable);
+
+    /**
+     * Releases our reference to the MATH table and cleans up everything else.
+     */
+    ~gfxMathTable();
+
+    /**
+     * Returns the value of the specified constant from the MATH table.
+     */
+    int32_t GetMathConstant(gfxFontEntry::MathConstant aConstant);
+
+    /**
+     *  If the MATH table contains an italic correction for that glyph, this
+     *  function gets the value and returns true. Otherwise it returns false.
+     */
+    bool
+    GetMathItalicsCorrection(uint32_t aGlyphID, int16_t* aItalicCorrection);
+
+    /**
+     * @param aGlyphID  glyph index of the character we want to stretch
+     * @param aVertical direction of the stretching (vertical/horizontal)
+     * @param aSize     the desired size variant
+     *
+     * Returns the glyph index of the desired size variant or 0 if there is not
+     * any such size variant.
+     */
+    uint32_t GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                 uint16_t aSize);
+
+    /**
+     * @param aGlyphID  glyph index of the character we want to stretch
+     * @param aVertical direction of the stretching (vertical/horizontal)
+     * @param aGlyphs   pre-allocated buffer of 4 elements where the glyph
+     * indexes (or 0 for absent parts) will be stored. The parts are stored in
+     * the order expected by the nsMathMLChar: Top (or Left), Middle, Bottom
+     * (or Right), Glue.
+     *
+     * Tries to fill-in aGlyphs with the relevant glyph indexes and returns
+     * whether the operation was successful. The function returns false if
+     * there is not any assembly for the character we want to stretch or if
+     * the format is not supported by the nsMathMLChar code.
+     *
+     */
+    bool GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                              uint32_t aGlyphs[4]);
+
+protected:
+    friend class gfxFontEntry;
+    // This allows gfxFontEntry to verify the validity of the main headers
+    // before starting to use the MATH table.
+    bool HasValidHeaders();
+
+private:
+    // HarfBuzz blob where the MATH table is stored.
+    hb_blob_t*    mMathTable;
+
+    // Cached values for the latest (mGlyphID, mVertical) pair that has been
+    // accessed and the corresponding glyph construction. These are verified
+    // by SelectGlyphConstruction and updated if necessary.
+    // mGlyphConstruction will be set to nullptr if no construction is defined
+    // for the glyph. If non-null, its mGlyphAssembly and mVariantCount fields
+    // may be safely read, but no further validation will have been done.
+    const MathGlyphConstruction* mGlyphConstruction;
+    uint32_t mGlyphID;
+    bool     mVertical;
+    void     SelectGlyphConstruction(uint32_t aGlyphID, bool aVertical);
+
+    // Access to some structures of the MATH table.
+    // These accessors just return a pointer, but do NOT themselves check the
+    // validity of anything. Until we've checked that HasValidHeaders (which
+    // does validate them) returns true, they might return pointers that cannot
+    // even safely be dereferenced. GetGlyphAssembly may return nullptr if the
+    // given glyph has no assembly defined.
+    const MATHTableHeader* GetMATHTableHeader();
+    const MathConstants*   GetMathConstants();
+    const MathGlyphInfo*   GetMathGlyphInfo();
+    const MathVariants*    GetMathVariants();
+    const GlyphAssembly*   GetGlyphAssembly(uint32_t aGlyphID, bool aVertical);
+
+    // Verify whether a structure or an offset belongs to the math data and can
+    // be read safely.
+    bool ValidStructure(const char* aStructStart, uint16_t aStructSize);
+    bool ValidOffset(const char* aOffsetStart, uint16_t aOffset);
+
+    // Get the coverage index of a glyph index from an Open Type coverage table
+    // or -1 if the glyph index is not found.
+    int32_t GetCoverageIndex(const Coverage* aCoverage, uint32_t aGlyph);
+};
+
+#endif
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -21,16 +21,17 @@ EXPORTS += [
     'gfxFontConstants.h',
     'gfxFontFeatures.h',
     'gfxFontInfoLoader.h',
     'gfxFontTest.h',
     'gfxFontUtils.h',
     'gfxGradientCache.h',
     'gfxImageSurface.h',
     'gfxLineSegment.h',
+    'gfxMathTable.h',
     'gfxMatrix.h',
     'gfxPath.h',
     'gfxPattern.h',
     'gfxPlatform.h',
     'gfxPoint.h',
     'gfxPoint3D.h',
     'gfxPointH3D.h',
     'gfxPrefs.h',
@@ -230,16 +231,17 @@ UNIFIED_SOURCES += [
     'gfxFontFeatures.cpp',
     'gfxFontInfoLoader.cpp',
     'gfxFontMissingGlyphs.cpp',
     'gfxFontTest.cpp',
     'gfxGradientCache.cpp',
     'gfxGraphiteShaper.cpp',
     'gfxHarfBuzzShaper.cpp',
     'gfxImageSurface.cpp',
+    'gfxMathTable.cpp',
     'gfxMatrix.cpp',
     'gfxPath.cpp',
     'gfxPattern.cpp',
     'gfxRect.cpp',
     'gfxReusableImageSurfaceWrapper.cpp',
     'gfxReusableSharedImageSurfaceWrapper.cpp',
     'gfxScriptItemizer.cpp',
     'gfxSkipChars.cpp',