tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MathMLTextRunFactory.cpp (26290B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "MathMLTextRunFactory.h"
      8 
      9 #include "mozilla/BinarySearch.h"
     10 #include "mozilla/ComputedStyle.h"
     11 #include "mozilla/ComputedStyleInlines.h"
     12 #include "mozilla/StaticPrefs_mathml.h"
     13 #include "mozilla/intl/UnicodeScriptCodes.h"
     14 #include "nsDeviceContext.h"
     15 #include "nsFontMetrics.h"
     16 #include "nsStyleConsts.h"
     17 #include "nsTextFrameUtils.h"
     18 
     19 using namespace mozilla;
     20 
     21 /*
     22  Entries for the mathvariant lookup tables.  mKey represents the Unicode
     23  character to be transformed and is used for searching the tables.
     24  mReplacement represents the mapped mathvariant Unicode character.
     25 */
     26 typedef struct {
     27  uint32_t mKey;
     28  uint32_t mReplacement;
     29 } MathVarMapping;
     30 
     31 /*
     32 Lookup tables for use with mathvariant mappings to transform a unicode
     33 character point to another unicode character that indicates the proper output.
     34 mKey represents one of two concepts.
     35 1.  In the Latin table it represents a hole in the mathematical alphanumeric
     36     block, where the character that should occupy that position is located
     37     elsewhere.
     38 2.  It represents an Arabic letter.
     39 
     40  As a replacement, 0 is reserved to indicate no mapping was found.
     41 */
     42 static const MathVarMapping gArabicInitialMapTable[] = {
     43    {0x628, 0x1EE21}, {0x62A, 0x1EE35}, {0x62B, 0x1EE36}, {0x62C, 0x1EE22},
     44    {0x62D, 0x1EE27}, {0x62E, 0x1EE37}, {0x633, 0x1EE2E}, {0x634, 0x1EE34},
     45    {0x635, 0x1EE31}, {0x636, 0x1EE39}, {0x639, 0x1EE2F}, {0x63A, 0x1EE3B},
     46    {0x641, 0x1EE30}, {0x642, 0x1EE32}, {0x643, 0x1EE2A}, {0x644, 0x1EE2B},
     47    {0x645, 0x1EE2C}, {0x646, 0x1EE2D}, {0x647, 0x1EE24}, {0x64A, 0x1EE29}};
     48 
     49 static const MathVarMapping gArabicTailedMapTable[] = {
     50    {0x62C, 0x1EE42}, {0x62D, 0x1EE47}, {0x62E, 0x1EE57}, {0x633, 0x1EE4E},
     51    {0x634, 0x1EE54}, {0x635, 0x1EE51}, {0x636, 0x1EE59}, {0x639, 0x1EE4F},
     52    {0x63A, 0x1EE5B}, {0x642, 0x1EE52}, {0x644, 0x1EE4B}, {0x646, 0x1EE4D},
     53    {0x64A, 0x1EE49}, {0x66F, 0x1EE5F}, {0x6BA, 0x1EE5D}};
     54 
     55 static const MathVarMapping gArabicStretchedMapTable[] = {
     56    {0x628, 0x1EE61}, {0x62A, 0x1EE75}, {0x62B, 0x1EE76}, {0x62C, 0x1EE62},
     57    {0x62D, 0x1EE67}, {0x62E, 0x1EE77}, {0x633, 0x1EE6E}, {0x634, 0x1EE74},
     58    {0x635, 0x1EE71}, {0x636, 0x1EE79}, {0x637, 0x1EE68}, {0x638, 0x1EE7A},
     59    {0x639, 0x1EE6F}, {0x63A, 0x1EE7B}, {0x641, 0x1EE70}, {0x642, 0x1EE72},
     60    {0x643, 0x1EE6A}, {0x645, 0x1EE6C}, {0x646, 0x1EE6D}, {0x647, 0x1EE64},
     61    {0x64A, 0x1EE69}, {0x66E, 0x1EE7C}, {0x6A1, 0x1EE7E}};
     62 
     63 static const MathVarMapping gArabicLoopedMapTable[] = {
     64    {0x627, 0x1EE80}, {0x628, 0x1EE81}, {0x62A, 0x1EE95}, {0x62B, 0x1EE96},
     65    {0x62C, 0x1EE82}, {0x62D, 0x1EE87}, {0x62E, 0x1EE97}, {0x62F, 0x1EE83},
     66    {0x630, 0x1EE98}, {0x631, 0x1EE93}, {0x632, 0x1EE86}, {0x633, 0x1EE8E},
     67    {0x634, 0x1EE94}, {0x635, 0x1EE91}, {0x636, 0x1EE99}, {0x637, 0x1EE88},
     68    {0x638, 0x1EE9A}, {0x639, 0x1EE8F}, {0x63A, 0x1EE9B}, {0x641, 0x1EE90},
     69    {0x642, 0x1EE92}, {0x644, 0x1EE8B}, {0x645, 0x1EE8C}, {0x646, 0x1EE8D},
     70    {0x647, 0x1EE84}, {0x648, 0x1EE85}, {0x64A, 0x1EE89}};
     71 
     72 static const MathVarMapping gArabicDoubleMapTable[] = {
     73    {0x628, 0x1EEA1}, {0x62A, 0x1EEB5}, {0x62B, 0x1EEB6}, {0x62C, 0x1EEA2},
     74    {0x62D, 0x1EEA7}, {0x62E, 0x1EEB7}, {0x62F, 0x1EEA3}, {0x630, 0x1EEB8},
     75    {0x631, 0x1EEB3}, {0x632, 0x1EEA6}, {0x633, 0x1EEAE}, {0x634, 0x1EEB4},
     76    {0x635, 0x1EEB1}, {0x636, 0x1EEB9}, {0x637, 0x1EEA8}, {0x638, 0x1EEBA},
     77    {0x639, 0x1EEAF}, {0x63A, 0x1EEBB}, {0x641, 0x1EEB0}, {0x642, 0x1EEB2},
     78    {0x644, 0x1EEAB}, {0x645, 0x1EEAC}, {0x646, 0x1EEAD}, {0x648, 0x1EEA5},
     79    {0x64A, 0x1EEA9}};
     80 
     81 static const MathVarMapping gLatinExceptionMapTable[] = {
     82    {0x1D455, 0x210E}, {0x1D49D, 0x212C}, {0x1D4A0, 0x2130}, {0x1D4A1, 0x2131},
     83    {0x1D4A3, 0x210B}, {0x1D4A4, 0x2110}, {0x1D4A7, 0x2112}, {0x1D4A8, 0x2133},
     84    {0x1D4AD, 0x211B}, {0x1D4BA, 0x212F}, {0x1D4BC, 0x210A}, {0x1D4C4, 0x2134},
     85    {0x1D506, 0x212D}, {0x1D50B, 0x210C}, {0x1D50C, 0x2111}, {0x1D515, 0x211C},
     86    {0x1D51D, 0x2128}, {0x1D53A, 0x2102}, {0x1D53F, 0x210D}, {0x1D545, 0x2115},
     87    {0x1D547, 0x2119}, {0x1D548, 0x211A}, {0x1D549, 0x211D}, {0x1D551, 0x2124}};
     88 
     89 namespace {
     90 
     91 struct MathVarMappingWrapper {
     92  const MathVarMapping* const mTable;
     93  explicit MathVarMappingWrapper(const MathVarMapping* aTable)
     94      : mTable(aTable) {}
     95  uint32_t operator[](size_t index) const { return mTable[index].mKey; }
     96 };
     97 
     98 }  // namespace
     99 
    100 // Finds a MathVarMapping struct with the specified key (aKey) within aTable.
    101 // aTable must be an array, whose length is specified by aNumElements
    102 static uint32_t MathvarMappingSearch(uint32_t aKey,
    103                                     const MathVarMapping* aTable,
    104                                     uint32_t aNumElements) {
    105  size_t index;
    106  if (BinarySearch(MathVarMappingWrapper(aTable), 0, aNumElements, aKey,
    107                   &index)) {
    108    return aTable[index].mReplacement;
    109  }
    110 
    111  return 0;
    112 }
    113 
    114 #define GREEK_UPPER_THETA 0x03F4
    115 #define HOLE_GREEK_UPPER_THETA 0x03A2
    116 #define NABLA 0x2207
    117 #define PARTIAL_DIFFERENTIAL 0x2202
    118 #define GREEK_UPPER_ALPHA 0x0391
    119 #define GREEK_UPPER_OMEGA 0x03A9
    120 #define GREEK_LOWER_ALPHA 0x03B1
    121 #define GREEK_LOWER_OMEGA 0x03C9
    122 #define GREEK_LUNATE_EPSILON_SYMBOL 0x03F5
    123 #define GREEK_THETA_SYMBOL 0x03D1
    124 #define GREEK_KAPPA_SYMBOL 0x03F0
    125 #define GREEK_PHI_SYMBOL 0x03D5
    126 #define GREEK_RHO_SYMBOL 0x03F1
    127 #define GREEK_PI_SYMBOL 0x03D6
    128 #define GREEK_LETTER_DIGAMMA 0x03DC
    129 #define GREEK_SMALL_LETTER_DIGAMMA 0x03DD
    130 #define MATH_BOLD_CAPITAL_DIGAMMA 0x1D7CA
    131 #define MATH_BOLD_SMALL_DIGAMMA 0x1D7CB
    132 
    133 #define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
    134 #define LATIN_SMALL_LETTER_DOTLESS_J 0x0237
    135 
    136 #define MATH_ITALIC_SMALL_DOTLESS_I 0x1D6A4
    137 #define MATH_ITALIC_SMALL_DOTLESS_J 0x1D6A5
    138 
    139 #define MATH_BOLD_UPPER_A 0x1D400
    140 #define MATH_ITALIC_UPPER_A 0x1D434
    141 #define MATH_BOLD_SMALL_A 0x1D41A
    142 #define MATH_BOLD_UPPER_ALPHA 0x1D6A8
    143 #define MATH_BOLD_SMALL_ALPHA 0x1D6C2
    144 #define MATH_ITALIC_UPPER_ALPHA 0x1D6E2
    145 #define MATH_BOLD_DIGIT_ZERO 0x1D7CE
    146 #define MATH_DOUBLE_STRUCK_ZERO 0x1D7D8
    147 
    148 #define MATH_BOLD_UPPER_THETA 0x1D6B9
    149 #define MATH_BOLD_NABLA 0x1D6C1
    150 #define MATH_BOLD_PARTIAL_DIFFERENTIAL 0x1D6DB
    151 #define MATH_BOLD_EPSILON_SYMBOL 0x1D6DC
    152 #define MATH_BOLD_THETA_SYMBOL 0x1D6DD
    153 #define MATH_BOLD_KAPPA_SYMBOL 0x1D6DE
    154 #define MATH_BOLD_PHI_SYMBOL 0x1D6DF
    155 #define MATH_BOLD_RHO_SYMBOL 0x1D6E0
    156 #define MATH_BOLD_PI_SYMBOL 0x1D6E1
    157 
    158 /*
    159  Performs the character mapping needed to implement MathML's mathvariant
    160  attribute.  It takes a unicode character and maps it to its appropriate
    161  mathvariant counterpart specified by aMathVar.  The mapped character is
    162  typically located within Unicode's mathematical blocks (0x1D***, 0x1EE**) but
    163  there are exceptions which this function accounts for.
    164  Characters without a valid mapping or valid aMathvar value are returned
    165  unaltered.  Characters already in the mathematical blocks (or are one of the
    166  exceptions) are never transformed.
    167  Acceptable values for aMathVar are specified in layout/style/nsStyleConsts.h.
    168  The transformable characters can be found at:
    169  http://lists.w3.org/Archives/Public/www-math/2013Sep/0012.html and
    170  https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols
    171 */
    172 /*static */ uint32_t MathMLTextRunFactory::MathVariant(
    173    uint32_t aCh, StyleMathVariant aMathVar) {
    174  uint32_t baseChar;
    175  enum CharacterType {
    176    kIsLatin,
    177    kIsGreekish,
    178    kIsNumber,
    179    kIsArabic,
    180  };
    181  CharacterType varType;
    182 
    183  int8_t multiplier;
    184 
    185  if (aMathVar <= StyleMathVariant::Normal) {
    186    // nothing to do here
    187    return aCh;
    188  }
    189  if (aMathVar > StyleMathVariant::Stretched) {
    190    NS_ASSERTION(false, "Illegal mathvariant value");
    191    return aCh;
    192  }
    193 
    194  // Exceptional characters with at most one possible transformation
    195  if (aCh == HOLE_GREEK_UPPER_THETA) {
    196    // Nothing at this code point is transformed
    197    return aCh;
    198  }
    199  if (aCh == GREEK_LETTER_DIGAMMA) {
    200    if (aMathVar == StyleMathVariant::Bold) {
    201      return MATH_BOLD_CAPITAL_DIGAMMA;
    202    }
    203    return aCh;
    204  }
    205  if (aCh == GREEK_SMALL_LETTER_DIGAMMA) {
    206    if (aMathVar == StyleMathVariant::Bold) {
    207      return MATH_BOLD_SMALL_DIGAMMA;
    208    }
    209    return aCh;
    210  }
    211  if (aCh == LATIN_SMALL_LETTER_DOTLESS_I) {
    212    if (aMathVar == StyleMathVariant::Italic) {
    213      return MATH_ITALIC_SMALL_DOTLESS_I;
    214    }
    215    return aCh;
    216  }
    217  if (aCh == LATIN_SMALL_LETTER_DOTLESS_J) {
    218    if (aMathVar == StyleMathVariant::Italic) {
    219      return MATH_ITALIC_SMALL_DOTLESS_J;
    220    }
    221    return aCh;
    222  }
    223 
    224  // The Unicode mathematical blocks are divided into four segments: Latin,
    225  // Greek, numbers and Arabic.  In the case of the first three
    226  // baseChar represents the relative order in which the characters are
    227  // encoded in the Unicode mathematical block, normalised to the first
    228  // character of that sequence.
    229  //
    230  if ('A' <= aCh && aCh <= 'Z') {
    231    baseChar = aCh - 'A';
    232    varType = kIsLatin;
    233  } else if ('a' <= aCh && aCh <= 'z') {
    234    // Lowercase characters are placed immediately after the uppercase
    235    // characters in the Unicode mathematical block.  The constant subtraction
    236    // represents the number of characters between the start of the sequence
    237    // (capital A) and the first lowercase letter.
    238    baseChar = MATH_BOLD_SMALL_A - MATH_BOLD_UPPER_A + aCh - 'a';
    239    varType = kIsLatin;
    240  } else if ('0' <= aCh && aCh <= '9') {
    241    baseChar = aCh - '0';
    242    varType = kIsNumber;
    243  } else if (GREEK_UPPER_ALPHA <= aCh && aCh <= GREEK_UPPER_OMEGA) {
    244    baseChar = aCh - GREEK_UPPER_ALPHA;
    245    varType = kIsGreekish;
    246  } else if (GREEK_LOWER_ALPHA <= aCh && aCh <= GREEK_LOWER_OMEGA) {
    247    // Lowercase Greek comes after uppercase Greek.
    248    // Note in this instance the presence of an additional character (Nabla)
    249    // between the end of the uppercase Greek characters and the lowercase
    250    // ones.
    251    baseChar =
    252        MATH_BOLD_SMALL_ALPHA - MATH_BOLD_UPPER_ALPHA + aCh - GREEK_LOWER_ALPHA;
    253    varType = kIsGreekish;
    254  } else if (0x0600 <= aCh && aCh <= 0x06FF) {
    255    // Arabic characters are defined within this range
    256    varType = kIsArabic;
    257  } else {
    258    switch (aCh) {
    259      case GREEK_UPPER_THETA:
    260        baseChar = MATH_BOLD_UPPER_THETA - MATH_BOLD_UPPER_ALPHA;
    261        break;
    262      case NABLA:
    263        baseChar = MATH_BOLD_NABLA - MATH_BOLD_UPPER_ALPHA;
    264        break;
    265      case PARTIAL_DIFFERENTIAL:
    266        baseChar = MATH_BOLD_PARTIAL_DIFFERENTIAL - MATH_BOLD_UPPER_ALPHA;
    267        break;
    268      case GREEK_LUNATE_EPSILON_SYMBOL:
    269        baseChar = MATH_BOLD_EPSILON_SYMBOL - MATH_BOLD_UPPER_ALPHA;
    270        break;
    271      case GREEK_THETA_SYMBOL:
    272        baseChar = MATH_BOLD_THETA_SYMBOL - MATH_BOLD_UPPER_ALPHA;
    273        break;
    274      case GREEK_KAPPA_SYMBOL:
    275        baseChar = MATH_BOLD_KAPPA_SYMBOL - MATH_BOLD_UPPER_ALPHA;
    276        break;
    277      case GREEK_PHI_SYMBOL:
    278        baseChar = MATH_BOLD_PHI_SYMBOL - MATH_BOLD_UPPER_ALPHA;
    279        break;
    280      case GREEK_RHO_SYMBOL:
    281        baseChar = MATH_BOLD_RHO_SYMBOL - MATH_BOLD_UPPER_ALPHA;
    282        break;
    283      case GREEK_PI_SYMBOL:
    284        baseChar = MATH_BOLD_PI_SYMBOL - MATH_BOLD_UPPER_ALPHA;
    285        break;
    286      default:
    287        return aCh;
    288    }
    289 
    290    varType = kIsGreekish;
    291  }
    292 
    293  if (varType == kIsNumber) {
    294    switch (aMathVar) {
    295      // Each possible number mathvariant is encoded in a single, contiguous
    296      // block.  For example the beginning of the double struck number range
    297      // follows immediately after the end of the bold number range.
    298      // multiplier represents the order of the sequences relative to the first
    299      // one.
    300      case StyleMathVariant::Bold:
    301        multiplier = 0;
    302        break;
    303      case StyleMathVariant::DoubleStruck:
    304        multiplier = 1;
    305        break;
    306      case StyleMathVariant::SansSerif:
    307        multiplier = 2;
    308        break;
    309      case StyleMathVariant::BoldSansSerif:
    310        multiplier = 3;
    311        break;
    312      case StyleMathVariant::Monospace:
    313        multiplier = 4;
    314        break;
    315      default:
    316        // This mathvariant isn't defined for numbers or is otherwise normal
    317        return aCh;
    318    }
    319    // As the ranges are contiguous, to find the desired mathvariant range it
    320    // is sufficient to multiply the position within the sequence order
    321    // (multiplier) with the period of the sequence (which is constant for all
    322    // number sequences) and to add the character point of the first character
    323    // within the number mathvariant range.
    324    // To this the baseChar calculated earlier is added to obtain the final
    325    // code point.
    326    return baseChar +
    327           multiplier * (MATH_DOUBLE_STRUCK_ZERO - MATH_BOLD_DIGIT_ZERO) +
    328           MATH_BOLD_DIGIT_ZERO;
    329  } else if (varType == kIsGreekish) {
    330    switch (aMathVar) {
    331      case StyleMathVariant::Bold:
    332        multiplier = 0;
    333        break;
    334      case StyleMathVariant::Italic:
    335        multiplier = 1;
    336        break;
    337      case StyleMathVariant::BoldItalic:
    338        multiplier = 2;
    339        break;
    340      case StyleMathVariant::BoldSansSerif:
    341        multiplier = 3;
    342        break;
    343      case StyleMathVariant::SansSerifBoldItalic:
    344        multiplier = 4;
    345        break;
    346      default:
    347        // This mathvariant isn't defined for Greek or is otherwise normal
    348        return aCh;
    349    }
    350    // See the kIsNumber case for an explanation of the following calculation
    351    return baseChar + MATH_BOLD_UPPER_ALPHA +
    352           multiplier * (MATH_ITALIC_UPPER_ALPHA - MATH_BOLD_UPPER_ALPHA);
    353  }
    354 
    355  uint32_t tempChar;
    356  uint32_t newChar;
    357  if (varType == kIsArabic) {
    358    const MathVarMapping* mapTable;
    359    uint32_t tableLength;
    360    switch (aMathVar) {
    361      /* The Arabic mathematical block is not continuous, nor does it have a
    362       * monotonic mapping to the unencoded characters, requiring the use of a
    363       * lookup table.
    364       */
    365      case StyleMathVariant::Initial:
    366        mapTable = gArabicInitialMapTable;
    367        tableLength = std::size(gArabicInitialMapTable);
    368        break;
    369      case StyleMathVariant::Tailed:
    370        mapTable = gArabicTailedMapTable;
    371        tableLength = std::size(gArabicTailedMapTable);
    372        break;
    373      case StyleMathVariant::Stretched:
    374        mapTable = gArabicStretchedMapTable;
    375        tableLength = std::size(gArabicStretchedMapTable);
    376        break;
    377      case StyleMathVariant::Looped:
    378        mapTable = gArabicLoopedMapTable;
    379        tableLength = std::size(gArabicLoopedMapTable);
    380        break;
    381      case StyleMathVariant::DoubleStruck:
    382        mapTable = gArabicDoubleMapTable;
    383        tableLength = std::size(gArabicDoubleMapTable);
    384        break;
    385      default:
    386        // No valid transformations exist
    387        return aCh;
    388    }
    389    newChar = MathvarMappingSearch(aCh, mapTable, tableLength);
    390  } else {
    391    // Must be Latin
    392    if (aMathVar > StyleMathVariant::Monospace) {
    393      // Latin doesn't support the Arabic mathvariants
    394      return aCh;
    395    }
    396    multiplier = uint8_t(aMathVar) - 2;
    397    // This is possible because the values for StyleMathVariant::* are
    398    // chosen to coincide with the order in which the encoded mathvariant
    399    // characters are located within their unicode block (less an offset to
    400    // avoid _NONE and _NORMAL variants)
    401    // See the kIsNumber case for an explanation of the following calculation
    402    tempChar = baseChar + MATH_BOLD_UPPER_A +
    403               multiplier * (MATH_ITALIC_UPPER_A - MATH_BOLD_UPPER_A);
    404    // There are roughly twenty characters that are located outside of the
    405    // mathematical block, so the spaces where they ought to be are used
    406    // as keys for a lookup table containing the correct character mappings.
    407    newChar = MathvarMappingSearch(tempChar, gLatinExceptionMapTable,
    408                                   std::size(gLatinExceptionMapTable));
    409  }
    410 
    411  if (newChar) {
    412    return newChar;
    413  } else if (varType == kIsLatin) {
    414    return tempChar;
    415  } else {
    416    // An Arabic character without a corresponding mapping
    417    return aCh;
    418  }
    419 }
    420 
    421 #define TT_SSTY TRUETYPE_TAG('s', 's', 't', 'y')
    422 #define TT_DTLS TRUETYPE_TAG('d', 't', 'l', 's')
    423 
    424 void MathMLTextRunFactory::RebuildTextRun(
    425    nsTransformedTextRun* aTextRun, mozilla::gfx::DrawTarget* aRefDrawTarget,
    426    gfxMissingFontRecorder* aMFR) {
    427  gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
    428 
    429  nsAutoString convertedString;
    430  AutoTArray<bool, 50> charsToMergeArray;
    431  AutoTArray<bool, 50> deletedCharsArray;
    432  AutoTArray<RefPtr<nsTransformedCharStyle>, 50> styleArray;
    433  AutoTArray<uint8_t, 50> canBreakBeforeArray;
    434  bool mergeNeeded = false;
    435 
    436  bool singleCharMI =
    437      !!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSingleCharMi);
    438 
    439  uint32_t length = aTextRun->GetLength();
    440  const char16_t* str = aTextRun->mString.BeginReading();
    441  const nsTArray<RefPtr<nsTransformedCharStyle>>& styles = aTextRun->mStyles;
    442  nsFont font;
    443  if (length) {
    444    font = styles[0]->mFont;
    445 
    446    if (mSSTYScriptLevel || (mFlags & MATH_FONT_FEATURE_DTLS)) {
    447      bool foundSSTY = false;
    448      bool foundDTLS = false;
    449      // We respect ssty settings explicitly set by the user
    450      for (uint32_t i = 0; i < font.fontFeatureSettings.Length(); i++) {
    451        if (font.fontFeatureSettings[i].mTag == TT_SSTY) {
    452          foundSSTY = true;
    453        } else if (font.fontFeatureSettings[i].mTag == TT_DTLS) {
    454          foundDTLS = true;
    455        }
    456      }
    457      if (mSSTYScriptLevel && !foundSSTY) {
    458        uint8_t sstyLevel = 0;
    459        // FIXME: Use the same logic as scale_factor_for_math_depth_change?
    460        float scriptScaling =
    461            pow(kMathMLDefaultScriptSizeMultiplier, mSSTYScriptLevel);
    462        static_assert(kMathMLDefaultScriptSizeMultiplier < 1,
    463                      "Shouldn't it make things smaller?");
    464        /*
    465          An SSTY level of 2 is set if the scaling factor is less than or equal
    466          to halfway between that for a scriptlevel of 1 (0.71) and that of a
    467          scriptlevel of 2 (0.71^2), assuming the default script size
    468          multiplier. An SSTY level of 1 is set if the script scaling factor is
    469          less than or equal that for a scriptlevel of 1 assuming the default
    470          script size multiplier.
    471 
    472          User specified values of script size multiplier will change the
    473          scaling factor which mSSTYScriptLevel values correspond to.
    474 
    475          In the event that the script size multiplier actually makes things
    476          larger, no change is made.
    477 
    478          To opt out of this change, add the following to the stylesheet:
    479          "font-feature-settings: 'ssty' 0"
    480        */
    481        if (scriptScaling <= (kMathMLDefaultScriptSizeMultiplier +
    482                              (kMathMLDefaultScriptSizeMultiplier *
    483                               kMathMLDefaultScriptSizeMultiplier)) /
    484                                 2) {
    485          // Currently only the first two ssty settings are used, so two is
    486          // large as we go
    487          sstyLevel = 2;
    488        } else if (scriptScaling <= kMathMLDefaultScriptSizeMultiplier) {
    489          sstyLevel = 1;
    490        }
    491        if (sstyLevel) {
    492          gfxFontFeature settingSSTY;
    493          settingSSTY.mTag = TT_SSTY;
    494          settingSSTY.mValue = sstyLevel;
    495          font.fontFeatureSettings.AppendElement(settingSSTY);
    496        }
    497      }
    498      /*
    499        Apply the dtls font feature setting (dotless).
    500        This gets applied to the base frame and all descendants of the base
    501        frame of certain <mover> and <munderover> frames.
    502 
    503        See nsMathMLmunderoverFrame.cpp for a full description.
    504 
    505        To opt out of this change, add the following to the stylesheet:
    506        "font-feature-settings: 'dtls' 0"
    507      */
    508      if ((mFlags & MATH_FONT_FEATURE_DTLS) && !foundDTLS) {
    509        gfxFontFeature settingDTLS;
    510        settingDTLS.mTag = TT_DTLS;
    511        settingDTLS.mValue = 1;
    512        font.fontFeatureSettings.AppendElement(settingDTLS);
    513      }
    514    }
    515  }
    516 
    517  StyleMathVariant mathVar = StyleMathVariant::None;
    518  bool doMathvariantStyling = true;
    519 
    520  // Ensure the fontGroup is ready to be searched.
    521  fontGroup->EnsureFontList();
    522 
    523  for (uint32_t i = 0; i < length; ++i) {
    524    int extraChars = 0;
    525    mathVar = styles[i]->mMathVariant;
    526 
    527    if (singleCharMI && mathVar == StyleMathVariant::None &&
    528        (!StaticPrefs::mathml_legacy_mathvariant_attribute_disabled() ||
    529         styles[i]->mTextTransform & StyleTextTransform::MATH_AUTO)) {
    530      mathVar = StyleMathVariant::Italic;
    531    }
    532 
    533    uint32_t ch = str[i];
    534    if (i < length - 1 && NS_IS_SURROGATE_PAIR(ch, str[i + 1])) {
    535      ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
    536    }
    537    uint32_t ch2 = MathVariant(ch, mathVar);
    538 
    539    if (!StaticPrefs::mathml_mathvariant_styling_fallback_disabled() &&
    540        (mathVar == StyleMathVariant::Bold ||
    541         mathVar == StyleMathVariant::BoldItalic ||
    542         mathVar == StyleMathVariant::Italic)) {
    543      if (ch == ch2 && ch != 0x20 && ch != 0xA0) {
    544        // Don't apply the CSS style if a character cannot be
    545        // transformed. There is an exception for whitespace as it is both
    546        // common and innocuous.
    547        doMathvariantStyling = false;
    548      }
    549      if (ch2 != ch) {
    550        // Bug 930504. Some platforms do not have fonts for Mathematical
    551        // Alphanumeric Symbols. Hence we check whether the transformed
    552        // character is actually available.
    553        FontMatchType matchType;
    554        RefPtr<gfxFont> mathFont = fontGroup->FindFontForChar(
    555            ch2, 0, 0, intl::Script::COMMON, nullptr, &matchType);
    556        if (mathFont) {
    557          // Don't apply the CSS style if there is a math font for at least one
    558          // of the transformed character in this text run.
    559          doMathvariantStyling = false;
    560        } else {
    561          // We fallback to the original character.
    562          ch2 = ch;
    563          if (aMFR) {
    564            aMFR->RecordScript(intl::Script::MATHEMATICAL_NOTATION);
    565          }
    566        }
    567      }
    568    }
    569 
    570    deletedCharsArray.AppendElement(false);
    571    charsToMergeArray.AppendElement(false);
    572    styleArray.AppendElement(styles[i]);
    573    canBreakBeforeArray.AppendElement(aTextRun->CanBreakLineBefore(i));
    574 
    575    if (IS_IN_BMP(ch2)) {
    576      convertedString.Append(ch2);
    577    } else {
    578      convertedString.Append(H_SURROGATE(ch2));
    579      convertedString.Append(L_SURROGATE(ch2));
    580      ++extraChars;
    581      if (!IS_IN_BMP(ch)) {
    582        deletedCharsArray.AppendElement(
    583            true);  // not exactly deleted, but
    584                    // the trailing surrogate is skipped
    585        ++i;
    586      }
    587    }
    588 
    589    while (extraChars-- > 0) {
    590      mergeNeeded = true;
    591      charsToMergeArray.AppendElement(true);
    592      styleArray.AppendElement(styles[i]);
    593      canBreakBeforeArray.AppendElement(false);
    594    }
    595  }
    596 
    597  gfx::ShapedTextFlags flags;
    598  gfxTextRunFactory::Parameters innerParams =
    599      GetParametersForInner(aTextRun, &flags, aRefDrawTarget);
    600 
    601  RefPtr<nsTransformedTextRun> transformedChild;
    602  RefPtr<gfxTextRun> cachedChild;
    603  gfxTextRun* child;
    604 
    605  if (!StaticPrefs::mathml_mathvariant_styling_fallback_disabled() &&
    606      doMathvariantStyling) {
    607    if (mathVar == StyleMathVariant::Bold) {
    608      font.style = FontSlantStyle::NORMAL;
    609      font.weight = FontWeight::BOLD;
    610    } else if (mathVar == StyleMathVariant::Italic) {
    611      font.style = FontSlantStyle::ITALIC;
    612      font.weight = FontWeight::NORMAL;
    613    } else if (mathVar == StyleMathVariant::BoldItalic) {
    614      font.style = FontSlantStyle::ITALIC;
    615      font.weight = FontWeight::BOLD;
    616    }
    617  }
    618  gfxFontGroup* newFontGroup = nullptr;
    619 
    620  // Get the correct gfxFontGroup that corresponds to the earlier font changes.
    621  if (length) {
    622    font.size = font.size.ScaledBy(mFontInflation);
    623    nsPresContext* pc = styles[0]->mPresContext;
    624    nsFontMetrics::Params params;
    625    params.language = styles[0]->mLanguage;
    626    params.explicitLanguage = styles[0]->mExplicitLanguage;
    627    params.userFontSet = pc->GetUserFontSet();
    628    params.textPerf = pc->GetTextPerfMetrics();
    629    params.featureValueLookup = pc->GetFontFeatureValuesLookup();
    630    RefPtr<nsFontMetrics> metrics = pc->GetMetricsFor(font, params);
    631    newFontGroup = metrics->GetThebesFontGroup();
    632  }
    633 
    634  if (!newFontGroup) {
    635    // If we can't get a new font group, fall back to the old one.  Rendering
    636    // will be incorrect, but not significantly so.
    637    newFontGroup = fontGroup;
    638  }
    639 
    640  if (mInnerTransformingTextRunFactory) {
    641    transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
    642        convertedString.BeginReading(), convertedString.Length(), &innerParams,
    643        newFontGroup, flags, nsTextFrameUtils::Flags(), std::move(styleArray),
    644        false);
    645    child = transformedChild.get();
    646  } else {
    647    cachedChild = newFontGroup->MakeTextRun(
    648        convertedString.BeginReading(), convertedString.Length(), &innerParams,
    649        flags, nsTextFrameUtils::Flags(), aMFR);
    650    child = cachedChild.get();
    651  }
    652  if (!child) {
    653    return;
    654  }
    655 
    656  typedef gfxTextRun::Range Range;
    657 
    658  // Copy potential linebreaks into child so they're preserved
    659  // (and also child will be shaped appropriately)
    660  NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
    661               "Dropped characters or break-before values somewhere!");
    662  Range range(0, uint32_t(canBreakBeforeArray.Length()));
    663  child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
    664  if (transformedChild) {
    665    transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
    666  }
    667 
    668  aTextRun->ResetGlyphRuns();
    669  if (mergeNeeded) {
    670    // Now merge multiple characters into one multi-glyph character as required
    671    NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
    672                 "source length mismatch");
    673    NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
    674                 "destination length mismatch");
    675    MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
    676                             deletedCharsArray.Elements());
    677  } else {
    678    // No merging to do, so just copy; this produces a more optimized textrun.
    679    // We can't steal the data because the child may be cached and stealing
    680    // the data would break the cache.
    681    aTextRun->CopyGlyphDataFrom(child, Range(child), 0);
    682  }
    683 }