michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxMathTable.h" michael@0: michael@0: #include "MathTableStructures.h" michael@0: #include "harfbuzz/hb.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: gfxMathTable::gfxMathTable(hb_blob_t* aMathTable) michael@0: : mMathTable(aMathTable) michael@0: , mGlyphConstruction(nullptr) michael@0: , mGlyphID(-1) michael@0: , mVertical(false) michael@0: { michael@0: } michael@0: michael@0: gfxMathTable::~gfxMathTable() michael@0: { michael@0: hb_blob_destroy(mMathTable); michael@0: } michael@0: michael@0: bool michael@0: gfxMathTable::HasValidHeaders() michael@0: { michael@0: const char* mathData = hb_blob_get_data(mMathTable, nullptr); michael@0: // Verify the MATH table header. michael@0: if (!ValidStructure(mathData, sizeof(MATHTableHeader))) { michael@0: return false; michael@0: } michael@0: const MATHTableHeader* header = GetMATHTableHeader(); michael@0: if (uint32_t(header->mVersion) != 0x00010000 || michael@0: !ValidOffset(mathData, uint16_t(header->mMathConstants)) || michael@0: !ValidOffset(mathData, uint16_t(header->mMathGlyphInfo)) || michael@0: !ValidOffset(mathData, uint16_t(header->mMathVariants))) { michael@0: return false; michael@0: } michael@0: michael@0: // Verify the MathConstants header. michael@0: const MathConstants* mathconstants = GetMathConstants(); michael@0: const char* start = reinterpret_cast(mathconstants); michael@0: if (!ValidStructure(start, sizeof(MathConstants))) { michael@0: return false; michael@0: } michael@0: michael@0: // Verify the MathGlyphInfo header. michael@0: const MathGlyphInfo* mathglyphinfo = GetMathGlyphInfo(); michael@0: start = reinterpret_cast(mathglyphinfo); michael@0: if (!ValidStructure(start, sizeof(MathGlyphInfo))) { michael@0: return false; michael@0: } michael@0: michael@0: // Verify the MathVariants header. michael@0: const MathVariants* mathvariants = GetMathVariants(); michael@0: start = reinterpret_cast(mathvariants); michael@0: if (!ValidStructure(start, sizeof(MathVariants)) || michael@0: !ValidStructure(start, michael@0: sizeof(MathVariants) + sizeof(Offset) * michael@0: (uint16_t(mathvariants->mVertGlyphCount) + michael@0: uint16_t(mathvariants->mHorizGlyphCount))) || michael@0: !ValidOffset(start, uint16_t(mathvariants->mVertGlyphCoverage)) || michael@0: !ValidOffset(start, uint16_t(mathvariants->mHorizGlyphCoverage))) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: int32_t michael@0: gfxMathTable::GetMathConstant(gfxFontEntry::MathConstant aConstant) michael@0: { michael@0: const MathConstants* mathconstants = GetMathConstants(); michael@0: michael@0: if (aConstant <= gfxFontEntry::ScriptScriptPercentScaleDown) { michael@0: return int16_t(mathconstants->mInt16[aConstant]); michael@0: } michael@0: michael@0: if (aConstant <= gfxFontEntry::DisplayOperatorMinHeight) { michael@0: return michael@0: uint16_t(mathconstants-> michael@0: mUint16[aConstant - gfxFontEntry::DelimitedSubFormulaMinHeight]); michael@0: } michael@0: michael@0: if (aConstant <= gfxFontEntry::RadicalKernAfterDegree) { michael@0: return int16_t(mathconstants-> michael@0: mMathValues[aConstant - gfxFontEntry::MathLeading].mValue); michael@0: } michael@0: michael@0: return uint16_t(mathconstants->mRadicalDegreeBottomRaisePercent); michael@0: } michael@0: michael@0: bool michael@0: gfxMathTable::GetMathItalicsCorrection(uint32_t aGlyphID, michael@0: int16_t* aItalicCorrection) michael@0: { michael@0: const MathGlyphInfo* mathglyphinfo = GetMathGlyphInfo(); michael@0: michael@0: // Get the offset of the italic correction and verify whether it is valid. michael@0: const char* start = reinterpret_cast(mathglyphinfo); michael@0: uint16_t offset = mathglyphinfo->mMathItalicsCorrectionInfo; michael@0: if (offset == 0 || !ValidOffset(start, offset)) { michael@0: return false; michael@0: } michael@0: start += offset; michael@0: michael@0: // Verify the validity of the MathItalicsCorrectionInfo and retrieve it. michael@0: if (!ValidStructure(start, sizeof(MathItalicsCorrectionInfo))) { michael@0: return false; michael@0: } michael@0: const MathItalicsCorrectionInfo* italicsCorrectionInfo = michael@0: reinterpret_cast(start); michael@0: michael@0: // Get the coverage index for the glyph. michael@0: offset = italicsCorrectionInfo->mCoverage; michael@0: const Coverage* coverage = michael@0: reinterpret_cast(start + offset); michael@0: int32_t i = GetCoverageIndex(coverage, aGlyphID); michael@0: michael@0: // Get the ItalicsCorrection. michael@0: uint16_t count = italicsCorrectionInfo->mItalicsCorrectionCount; michael@0: if (i < 0 || i >= count) { michael@0: return false; michael@0: } michael@0: start = reinterpret_cast(italicsCorrectionInfo + 1); michael@0: if (!ValidStructure(start, count * sizeof(MathValueRecord))) { michael@0: return false; michael@0: } michael@0: const MathValueRecord* mathValueRecordArray = michael@0: reinterpret_cast(start); michael@0: michael@0: *aItalicCorrection = int16_t(mathValueRecordArray[i].mValue); michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: gfxMathTable::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical, michael@0: uint16_t aSize) michael@0: { michael@0: // Select the glyph construction. michael@0: SelectGlyphConstruction(aGlyphID, aVertical); michael@0: if (!mGlyphConstruction) { michael@0: return 0; michael@0: } michael@0: michael@0: // Verify the validity of the array of the MathGlyphVariantRecord's and michael@0: // whether there is a variant of the requested size. michael@0: uint16_t count = mGlyphConstruction->mVariantCount; michael@0: const char* start = reinterpret_cast(mGlyphConstruction + 1); michael@0: if (aSize >= count || michael@0: !ValidStructure(start, count * sizeof(MathGlyphVariantRecord))) { michael@0: return 0; michael@0: } michael@0: michael@0: // Return the glyph index of the requested size variant. michael@0: const MathGlyphVariantRecord* recordArray = michael@0: reinterpret_cast(start); michael@0: return uint32_t(recordArray[aSize].mVariantGlyph); michael@0: } michael@0: michael@0: bool michael@0: gfxMathTable::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, michael@0: uint32_t aGlyphs[4]) michael@0: { michael@0: // Get the glyph assembly corresponding to that (aGlyphID, aVertical) pair. michael@0: const GlyphAssembly* glyphAssembly = GetGlyphAssembly(aGlyphID, aVertical); michael@0: if (!glyphAssembly) { michael@0: return false; michael@0: } michael@0: michael@0: // Verify the validity of the array of GlyphPartRecord's and retrieve it. michael@0: uint16_t count = glyphAssembly->mPartCount; michael@0: const char* start = reinterpret_cast(glyphAssembly + 1); michael@0: if (!ValidStructure(start, count * sizeof(GlyphPartRecord))) { michael@0: return false; michael@0: } michael@0: const GlyphPartRecord* recordArray = michael@0: reinterpret_cast(start); michael@0: michael@0: // XXXfredw The structure of the Open Type Math table is a bit more general michael@0: // than the one currently used by the nsMathMLChar code, so we try to fallback michael@0: // in reasonable way. We use the approach of the copyComponents function in michael@0: // github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py michael@0: // michael@0: // The nsMathMLChar code can use at most 3 non extender pieces (aGlyphs[0], michael@0: // aGlyphs[1] and aGlyphs[2]) and the extenders between these pieces should michael@0: // all be the same (aGlyphs[4]). Also, the parts of vertical assembly are michael@0: // stored from bottom to top in the Open Type MATH table while they are michael@0: // stored from top to bottom in nsMathMLChar. michael@0: michael@0: // Count the number of non extender pieces michael@0: uint16_t nonExtenderCount = 0; michael@0: for (uint16_t i = 0; i < count; i++) { michael@0: if (!(uint16_t(recordArray[i].mPartFlags) & PART_FLAG_EXTENDER)) { michael@0: nonExtenderCount++; michael@0: } michael@0: } michael@0: if (nonExtenderCount > 3) { michael@0: // Not supported: too many pieces michael@0: return false; michael@0: } michael@0: michael@0: // Now browse the list of pieces michael@0: michael@0: // 0 = look for a left/bottom glyph michael@0: // 1 = look for an extender between left/bottom and mid michael@0: // 2 = look for a middle glyph michael@0: // 3 = look for an extender between middle and right/top michael@0: // 4 = look for a right/top glyph michael@0: // 5 = no more piece expected michael@0: uint8_t state = 0; michael@0: michael@0: // First extender char found. michael@0: uint32_t extenderChar = 0; michael@0: michael@0: // Clear the aGlyphs table. michael@0: memset(aGlyphs, 0, sizeof(uint32_t) * 4); michael@0: michael@0: for (uint16_t i = 0; i < count; i++) { michael@0: michael@0: bool isExtender = uint16_t(recordArray[i].mPartFlags) & PART_FLAG_EXTENDER; michael@0: uint32_t glyph = recordArray[i].mGlyph; michael@0: michael@0: if ((state == 1 || state == 2) && nonExtenderCount < 3) { michael@0: // do not try to find a middle glyph michael@0: state += 2; michael@0: } michael@0: michael@0: if (isExtender) { michael@0: if (!extenderChar) { michael@0: extenderChar = glyph; michael@0: aGlyphs[3] = extenderChar; michael@0: } else if (extenderChar != glyph) { michael@0: // Not supported: different extenders michael@0: return false; michael@0: } michael@0: michael@0: if (state == 0) { // or state == 1 michael@0: // ignore left/bottom piece and multiple successive extenders michael@0: state = 1; michael@0: } else if (state == 2) { // or state == 3 michael@0: // ignore middle piece and multiple successive extenders michael@0: state = 3; michael@0: } else if (state >= 4) { michael@0: // Not supported: unexpected extender michael@0: return false; michael@0: } michael@0: michael@0: continue; michael@0: } michael@0: michael@0: if (state == 0) { michael@0: // copy left/bottom part michael@0: aGlyphs[mVertical ? 2 : 0] = glyph; michael@0: state = 1; michael@0: continue; michael@0: } michael@0: michael@0: if (state == 1 || state == 2) { michael@0: // copy middle part michael@0: aGlyphs[1] = glyph; michael@0: state = 3; michael@0: continue; michael@0: } michael@0: michael@0: if (state == 3 || state == 4) { michael@0: // copy right/top part michael@0: aGlyphs[mVertical ? 0 : 2] = glyph; michael@0: state = 5; michael@0: } michael@0: michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: gfxMathTable::ValidStructure(const char* aStart, uint16_t aSize) michael@0: { michael@0: unsigned int mathDataLength; michael@0: const char* mathData = hb_blob_get_data(mMathTable, &mathDataLength); michael@0: return (mathData <= aStart && michael@0: aStart + aSize <= mathData + mathDataLength); michael@0: } michael@0: michael@0: bool michael@0: gfxMathTable::ValidOffset(const char* aStart, uint16_t aOffset) michael@0: { michael@0: unsigned int mathDataLength; michael@0: const char* mathData = hb_blob_get_data(mMathTable, &mathDataLength); michael@0: return (mathData <= aStart + aOffset && michael@0: aStart + aOffset < mathData + mathDataLength); michael@0: } michael@0: michael@0: const MATHTableHeader* michael@0: gfxMathTable::GetMATHTableHeader() michael@0: { michael@0: const char* mathData = hb_blob_get_data(mMathTable, nullptr); michael@0: return reinterpret_cast(mathData); michael@0: } michael@0: michael@0: const MathConstants* michael@0: gfxMathTable::GetMathConstants() michael@0: { michael@0: const char* mathData = hb_blob_get_data(mMathTable, nullptr); michael@0: return michael@0: reinterpret_cast(mathData + michael@0: uint16_t(GetMATHTableHeader()-> michael@0: mMathConstants)); michael@0: } michael@0: michael@0: const MathGlyphInfo* michael@0: gfxMathTable::GetMathGlyphInfo() michael@0: { michael@0: const char* mathData = hb_blob_get_data(mMathTable, nullptr); michael@0: return michael@0: reinterpret_cast(mathData + michael@0: uint16_t(GetMATHTableHeader()-> michael@0: mMathGlyphInfo)); michael@0: } michael@0: michael@0: const MathVariants* michael@0: gfxMathTable::GetMathVariants() michael@0: { michael@0: const char* mathData = hb_blob_get_data(mMathTable, nullptr); michael@0: return michael@0: reinterpret_cast(mathData + michael@0: uint16_t(GetMATHTableHeader()-> michael@0: mMathVariants)); michael@0: } michael@0: michael@0: const GlyphAssembly* michael@0: gfxMathTable::GetGlyphAssembly(uint32_t aGlyphID, bool aVertical) michael@0: { michael@0: // Select the glyph construction. michael@0: SelectGlyphConstruction(aGlyphID, aVertical); michael@0: if (!mGlyphConstruction) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get the offset of the glyph assembly and verify whether it is valid. michael@0: const char* start = reinterpret_cast(mGlyphConstruction); michael@0: uint16_t offset = mGlyphConstruction->mGlyphAssembly; michael@0: if (offset == 0 || !ValidOffset(start, offset)) { michael@0: return nullptr; michael@0: } michael@0: start += offset; michael@0: michael@0: // Verify the validity of the GlyphAssembly and return it. michael@0: if (!ValidStructure(start, sizeof(GlyphAssembly))) { michael@0: return nullptr; michael@0: } michael@0: return reinterpret_cast(start); michael@0: } michael@0: michael@0: int32_t michael@0: gfxMathTable::GetCoverageIndex(const Coverage* aCoverage, uint32_t aGlyph) michael@0: { michael@0: if (uint16_t(aCoverage->mFormat) == 1) { michael@0: // Coverage Format 1: list of individual glyph indices in the glyph set. michael@0: const CoverageFormat1* table = michael@0: reinterpret_cast(aCoverage); michael@0: uint16_t count = table->mGlyphCount; michael@0: const char* start = reinterpret_cast(table + 1); michael@0: if (ValidStructure(start, count * sizeof(GlyphID))) { michael@0: const GlyphID* glyphArray = michael@0: reinterpret_cast(start); michael@0: uint32_t imin = 0, imax = count; michael@0: while (imin < imax) { michael@0: uint32_t imid = (imin + imax) >> 1; michael@0: uint16_t glyphMid = glyphArray[imid]; michael@0: if (glyphMid == aGlyph) { michael@0: return imid; michael@0: } michael@0: if (glyphMid < aGlyph) { michael@0: imin = imid + 1; michael@0: } else { michael@0: imax = imid; michael@0: } michael@0: } michael@0: } michael@0: } else if (uint16_t(aCoverage->mFormat) == 2) { michael@0: // Coverage Format 2: ranges of consecutive indices. michael@0: const CoverageFormat2* table = michael@0: reinterpret_cast(aCoverage); michael@0: uint16_t count = table->mRangeCount; michael@0: const char* start = reinterpret_cast(table + 1); michael@0: if (ValidStructure(start, count * sizeof(RangeRecord))) { michael@0: const RangeRecord* rangeArray = michael@0: reinterpret_cast(start); michael@0: uint32_t imin = 0, imax = count; michael@0: while (imin < imax) { michael@0: uint32_t imid = (imin + imax) >> 1; michael@0: uint16_t rStart = rangeArray[imid].mStart; michael@0: uint16_t rEnd = rangeArray[imid].mEnd; michael@0: if (rEnd < aGlyph) { michael@0: imin = imid + 1; michael@0: } else if (aGlyph < rStart) { michael@0: imax = imid; michael@0: } else { michael@0: return (uint16_t(rangeArray[imid].mStartCoverageIndex) + michael@0: aGlyph - rStart); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: void michael@0: gfxMathTable::SelectGlyphConstruction(uint32_t aGlyphID, bool aVertical) michael@0: { michael@0: if (mGlyphID == aGlyphID && mVertical == aVertical) { michael@0: // The (glyph, direction) pair is already selected: nothing to do. michael@0: return; michael@0: } michael@0: michael@0: // Update our cached values. michael@0: mVertical = aVertical; michael@0: mGlyphID = aGlyphID; michael@0: mGlyphConstruction = nullptr; michael@0: michael@0: // Get the coverage index for the new values. michael@0: const MathVariants* mathvariants = GetMathVariants(); michael@0: const char* start = reinterpret_cast(mathvariants); michael@0: uint16_t offset = (aVertical ? michael@0: mathvariants->mVertGlyphCoverage : michael@0: mathvariants->mHorizGlyphCoverage); michael@0: const Coverage* coverage = michael@0: reinterpret_cast(start + offset); michael@0: int32_t i = GetCoverageIndex(coverage, aGlyphID); michael@0: michael@0: // Get the offset to the glyph construction. michael@0: uint16_t count = (aVertical ? michael@0: mathvariants->mVertGlyphCount : michael@0: mathvariants->mHorizGlyphCount); michael@0: start = reinterpret_cast(mathvariants + 1); michael@0: if (i < 0 || i >= count) { michael@0: return; michael@0: } michael@0: if (!aVertical) { michael@0: start += uint16_t(mathvariants->mVertGlyphCount) * sizeof(Offset); michael@0: } michael@0: if (!ValidStructure(start, count * sizeof(Offset))) { michael@0: return; michael@0: } michael@0: const Offset* offsetArray = reinterpret_cast(start); michael@0: offset = uint16_t(offsetArray[i]); michael@0: michael@0: // Make mGlyphConstruction point to the desired glyph construction. michael@0: start = reinterpret_cast(mathvariants); michael@0: if (!ValidStructure(start + offset, sizeof(MathGlyphConstruction))) { michael@0: return; michael@0: } michael@0: mGlyphConstruction = michael@0: reinterpret_cast(start + offset); michael@0: }