Bug 407059 - Part 1: Add a gfxMathTable class to read the MATH table. r=jfkthame
☠☠ backed out by 545be5328235 ☠ ☠
authorFrédéric Wang <fred.wang@free.fr>
Tue, 22 Apr 2014 08:44:03 -0400
changeset 179921 d8244f3ecdcbd49290c039f45d7be35dacdc8b20
parent 179920 2dda3239f8c3e78e58c9e859954e79058662ae87
child 179922 cfcbc731d4db583918fe3131bf14466337e394e6
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjfkthame
bugs407059
milestone31.0a1
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',