gfxMathTable.cpp (6987B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "gfxMathTable.h" 6 7 #include "harfbuzz/hb.h" 8 #include "harfbuzz/hb-ot.h" 9 #include "mozilla/StaticPrefs_mathml.h" 10 11 #define FloatToFixed(f) (65536 * (f)) 12 #define FixedToFloat(f) ((f) * (1.0 / 65536.0)) 13 14 using namespace mozilla; 15 16 gfxMathTable::gfxMathTable(hb_face_t* aFace, gfxFloat aSize) { 17 mMathVariantCache.vertical = false; 18 mMathVariantCache.isRTL = false; 19 mHBFont = hb_font_create(aFace); 20 if (mHBFont) { 21 hb_font_set_ppem(mHBFont, aSize, aSize); 22 uint32_t scale = FloatToFixed(aSize); 23 hb_font_set_scale(mHBFont, scale, scale); 24 } 25 26 mMathVariantCache.glyphID = 0; 27 ClearCache(); 28 } 29 30 gfxMathTable::~gfxMathTable() { 31 if (mHBFont) { 32 hb_font_destroy(mHBFont); 33 } 34 } 35 36 gfxFloat gfxMathTable::Constant(MathConstant aConstant) const { 37 int32_t value = hb_ot_math_get_constant( 38 mHBFont, static_cast<hb_ot_math_constant_t>(aConstant)); 39 if (aConstant == ScriptPercentScaleDown || 40 aConstant == ScriptScriptPercentScaleDown || 41 aConstant == RadicalDegreeBottomRaisePercent) { 42 return value / 100.0; 43 } 44 return FixedToFloat(value); 45 } 46 47 gfxFloat gfxMathTable::ItalicsCorrection(uint32_t aGlyphID) const { 48 return FixedToFloat( 49 hb_ot_math_get_glyph_italics_correction(mHBFont, aGlyphID)); 50 } 51 52 uint32_t gfxMathTable::VariantsSize(uint32_t aGlyphID, bool aVertical, 53 bool aRTL, uint16_t aSize) const { 54 UpdateMathVariantCache(aGlyphID, aVertical, aRTL); 55 if (aSize < kMaxCachedSizeCount) { 56 return mMathVariantCache.sizes[aSize]; 57 } 58 59 // If the size index exceeds the cache size, we just read the value with 60 // hb_ot_math_get_glyph_variants. 61 hb_direction_t direction = aVertical 62 ? HB_DIRECTION_BTT 63 : (aRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); 64 hb_ot_math_glyph_variant_t variant; 65 unsigned int count = 1; 66 hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, aSize, &count, 67 &variant); 68 return count > 0 ? variant.glyph : 0; 69 } 70 71 bool gfxMathTable::VariantsParts(uint32_t aGlyphID, bool aVertical, bool aRTL, 72 uint32_t aGlyphs[4]) const { 73 UpdateMathVariantCache(aGlyphID, aVertical, aRTL); 74 memcpy(aGlyphs, mMathVariantCache.parts, sizeof(mMathVariantCache.parts)); 75 return mMathVariantCache.arePartsValid; 76 } 77 78 void gfxMathTable::ClearCache() const { 79 memset(mMathVariantCache.sizes, 0, sizeof(mMathVariantCache.sizes)); 80 memset(mMathVariantCache.parts, 0, sizeof(mMathVariantCache.parts)); 81 mMathVariantCache.arePartsValid = false; 82 } 83 84 void gfxMathTable::UpdateMathVariantCache(uint32_t aGlyphID, bool aVertical, 85 bool aRTL) const { 86 if (!StaticPrefs::mathml_rtl_operator_mirroring_enabled()) { 87 aRTL = false; 88 } 89 90 if (aGlyphID == mMathVariantCache.glyphID && 91 aVertical == mMathVariantCache.vertical && 92 aRTL == mMathVariantCache.isRTL) 93 return; 94 95 mMathVariantCache.glyphID = aGlyphID; 96 mMathVariantCache.vertical = aVertical; 97 mMathVariantCache.isRTL = aRTL; 98 ClearCache(); 99 100 // Cache the first size variants. 101 hb_direction_t direction = aVertical 102 ? HB_DIRECTION_BTT 103 : (aRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); 104 hb_ot_math_glyph_variant_t variant[kMaxCachedSizeCount]; 105 unsigned int count = kMaxCachedSizeCount; 106 hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, 0, &count, 107 variant); 108 for (unsigned int i = 0; i < count; i++) { 109 mMathVariantCache.sizes[i] = variant[i].glyph; 110 } 111 112 // Try and cache the parts of the glyph assembly. 113 // XXXfredw The structure of the Open Type Math table is a bit more general 114 // than the one currently used by the nsMathMLChar code, so we try to fallback 115 // in reasonable way. We use the approach of the copyComponents function in 116 // github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py 117 // 118 // The nsMathMLChar code can use at most 3 non extender pieces (aGlyphs[0], 119 // aGlyphs[1] and aGlyphs[2]) and the extenders between these pieces should 120 // all be the same (aGlyphs[4]). Also, the parts of vertical assembly are 121 // stored from bottom to top in the Open Type MATH table while they are 122 // stored from top to bottom in nsMathMLChar. 123 124 hb_ot_math_glyph_part_t parts[5]; 125 count = std::size(parts); 126 unsigned int offset = 0; 127 if (hb_ot_math_get_glyph_assembly(mHBFont, aGlyphID, direction, offset, 128 &count, parts, NULL) > std::size(parts)) 129 return; // Not supported: Too many pieces. 130 if (count <= 0) return; // Not supported: No pieces. 131 132 // Count the number of non extender pieces 133 uint16_t nonExtenderCount = 0; 134 for (uint16_t i = 0; i < count; i++) { 135 if (!(parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER)) { 136 nonExtenderCount++; 137 } 138 } 139 if (nonExtenderCount > 3) { 140 // Not supported: too many pieces 141 return; 142 } 143 144 // Now browse the list of pieces 145 146 // 0 = look for a left/bottom glyph 147 // 1 = look for an extender between left/bottom and mid 148 // 2 = look for a middle glyph 149 // 3 = look for an extender between middle and right/top 150 // 4 = look for a right/top glyph 151 // 5 = no more piece expected 152 uint8_t state = 0; 153 154 // First extender char found. 155 uint32_t extenderChar = 0; 156 157 for (uint16_t i = 0; i < count; i++) { 158 bool isExtender = parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER; 159 uint32_t glyph = parts[i].glyph; 160 161 if ((state == 1 || state == 2) && nonExtenderCount < 3) { 162 // do not try to find a middle glyph 163 state += 2; 164 } 165 166 if (isExtender) { 167 if (!extenderChar) { 168 extenderChar = glyph; 169 mMathVariantCache.parts[3] = extenderChar; 170 } else if (extenderChar != glyph) { 171 // Not supported: different extenders 172 return; 173 } 174 175 if (state == 0) { // or state == 1 176 // ignore left/bottom piece and multiple successive extenders 177 state = 1; 178 } else if (state == 2) { // or state == 3 179 // ignore middle piece and multiple successive extenders 180 state = 3; 181 } else if (state >= 4) { 182 // Not supported: unexpected extender 183 return; 184 } 185 186 continue; 187 } 188 189 if (state == 0) { 190 // copy left/bottom part 191 mMathVariantCache.parts[aVertical ? 2 : 0] = glyph; 192 state = 1; 193 continue; 194 } 195 196 if (state == 1 || state == 2) { 197 // copy middle part 198 mMathVariantCache.parts[1] = glyph; 199 state = 3; 200 continue; 201 } 202 203 if (state == 3 || state == 4) { 204 // copy right/top part 205 mMathVariantCache.parts[aVertical ? 0 : 2] = glyph; 206 state = 5; 207 } 208 } 209 210 mMathVariantCache.arePartsValid = true; 211 }