/* 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 "mozilla/BinarySearch.h" #include 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(mathconstants); if (!ValidStructure(start, sizeof(MathConstants))) { return false; } // Verify the MathGlyphInfo header. const MathGlyphInfo* mathglyphinfo = GetMathGlyphInfo(); start = reinterpret_cast(mathglyphinfo); if (!ValidStructure(start, sizeof(MathGlyphInfo))) { return false; } // Verify the MathVariants header. const MathVariants* mathvariants = GetMathVariants(); start = reinterpret_cast(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(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(start); // Get the coverage index for the glyph. offset = italicsCorrectionInfo->mCoverage; const Coverage* coverage = reinterpret_cast(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(italicsCorrectionInfo + 1); if (!ValidStructure(start, count * sizeof(MathValueRecord))) { return false; } const MathValueRecord* mathValueRecordArray = reinterpret_cast(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(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(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(glyphAssembly + 1); if (!ValidStructure(start, count * sizeof(GlyphPartRecord))) { return false; } const GlyphPartRecord* recordArray = reinterpret_cast(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(mathData); } const MathConstants* gfxMathTable::GetMathConstants() { const char* mathData = hb_blob_get_data(mMathTable, nullptr); return reinterpret_cast(mathData + uint16_t(GetMATHTableHeader()-> mMathConstants)); } const MathGlyphInfo* gfxMathTable::GetMathGlyphInfo() { const char* mathData = hb_blob_get_data(mMathTable, nullptr); return reinterpret_cast(mathData + uint16_t(GetMATHTableHeader()-> mMathGlyphInfo)); } const MathVariants* gfxMathTable::GetMathVariants() { const char* mathData = hb_blob_get_data(mMathTable, nullptr); return reinterpret_cast(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(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(start); } namespace { struct GlyphArrayWrapper { const GlyphID* const mGlyphArray; explicit GlyphArrayWrapper(const GlyphID* const aGlyphArray) : mGlyphArray(aGlyphArray) {} uint16_t operator[](size_t index) const { return mGlyphArray[index]; } }; struct RangeRecordComparator { const uint32_t mGlyph; explicit RangeRecordComparator(uint32_t aGlyph) : mGlyph(aGlyph) {} int operator()(const RangeRecord& aRecord) const { if (mGlyph < static_cast(aRecord.mStart)) { return -1; } if (mGlyph > static_cast(aRecord.mEnd)) { return 1; } return 0; } }; } // namespace int32_t gfxMathTable::GetCoverageIndex(const Coverage* aCoverage, uint32_t aGlyph) { using mozilla::BinarySearch; using mozilla::BinarySearchIf; if (uint16_t(aCoverage->mFormat) == 1) { // Coverage Format 1: list of individual glyph indices in the glyph set. const CoverageFormat1* table = reinterpret_cast(aCoverage); const uint16_t count = table->mGlyphCount; const char* start = reinterpret_cast(table + 1); if (ValidStructure(start, count * sizeof(GlyphID))) { const GlyphID* glyphArray = reinterpret_cast(start); size_t index; if (BinarySearch(GlyphArrayWrapper(glyphArray), 0, count, aGlyph, &index)) { return index; } } } else if (uint16_t(aCoverage->mFormat) == 2) { // Coverage Format 2: ranges of consecutive indices. const CoverageFormat2* table = reinterpret_cast(aCoverage); const uint16_t count = table->mRangeCount; const char* start = reinterpret_cast(table + 1); if (ValidStructure(start, count * sizeof(RangeRecord))) { const RangeRecord* rangeArray = reinterpret_cast(start); size_t index; if (BinarySearchIf(rangeArray, 0, count, RangeRecordComparator(aGlyph), &index)) { uint16_t rStart = rangeArray[index].mStart; uint16_t startCoverageIndex = rangeArray[index].mStartCoverageIndex; return (startCoverageIndex + 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(mathvariants); uint16_t offset = (aVertical ? mathvariants->mVertGlyphCoverage : mathvariants->mHorizGlyphCoverage); const Coverage* coverage = reinterpret_cast(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(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(start); offset = uint16_t(offsetArray[i]); // Make mGlyphConstruction point to the desired glyph construction. start = reinterpret_cast(mathvariants); if (!ValidStructure(start + offset, sizeof(MathGlyphConstruction))) { return; } mGlyphConstruction = reinterpret_cast(start + offset); }