tor-browser

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

nsMathMLChar.cpp (81410B)


      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 "nsMathMLChar.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "gfxContext.h"
     12 #include "gfxMathTable.h"
     13 #include "gfxTextRun.h"
     14 #include "gfxUtils.h"
     15 #include "mozilla/BinarySearch.h"
     16 #include "mozilla/ComputedStyle.h"
     17 #include "mozilla/LookAndFeel.h"
     18 #include "mozilla/MathAlgorithms.h"
     19 #include "mozilla/Preferences.h"
     20 #include "mozilla/Sprintf.h"
     21 #include "mozilla/StaticPrefs_mathml.h"
     22 #include "mozilla/UniquePtr.h"
     23 #include "mozilla/dom/Document.h"
     24 #include "mozilla/gfx/2D.h"
     25 #include "mozilla/intl/UnicodeScriptCodes.h"
     26 #include "nsCSSRendering.h"
     27 #include "nsContentUtils.h"
     28 #include "nsDeviceContext.h"
     29 #include "nsDisplayList.h"
     30 #include "nsFontMetrics.h"
     31 #include "nsIFrame.h"
     32 #include "nsIObserver.h"
     33 #include "nsIObserverService.h"
     34 #include "nsLayoutUtils.h"
     35 #include "nsMathMLOperators.h"
     36 #include "nsNetUtil.h"
     37 #include "nsPresContext.h"
     38 #include "nsUnicharUtils.h"
     39 
     40 using namespace mozilla;
     41 using namespace mozilla::gfx;
     42 using namespace mozilla::image;
     43 
     44 // #define NOISY_SEARCH 1
     45 
     46 // BUG 848725 Drawing failure with stretchy horizontal parenthesis when no fonts
     47 // are installed. "kMaxScaleFactor" is required to limit the scale for the
     48 // vertical and horizontal stretchy operators.
     49 static const float kMaxScaleFactor = 20.0;
     50 static const float kLargeOpFactor = float(M_SQRT2);
     51 static const float kIntegralFactor = 2.0;
     52 
     53 static void NormalizeDefaultFont(nsFont& aFont, float aFontSizeInflation) {
     54  aFont.size.ScaleBy(aFontSizeInflation);
     55 }
     56 
     57 // -----------------------------------------------------------------------------
     58 static const nsGlyphCode kNullGlyph = {{0}, false};
     59 
     60 // -----------------------------------------------------------------------------
     61 // nsGlyphTable is a class that provides an interface for accessing glyphs
     62 // of stretchy chars. It acts like a table that stores the variants of bigger
     63 // sizes (if any) and the partial glyphs needed to build extensible symbols.
     64 //
     65 // Bigger sizes (if any) of the char can then be retrieved with BigOf(...).
     66 // Partial glyphs can be retrieved with ElementAt(...).
     67 //
     68 // A table consists of "nsGlyphCode"s which are viewed either as Unicode
     69 // points (for nsUnicodeTable) or as direct glyph indices (for
     70 // nsOpenTypeTable)
     71 // -----------------------------------------------------------------------------
     72 
     73 class nsGlyphTable {
     74 public:
     75  virtual ~nsGlyphTable() = default;
     76  virtual bool IsUnicodeTable() const { return false; }
     77 
     78  virtual const nsCString& FontNameFor(const nsGlyphCode& aGlyphCode) const = 0;
     79 
     80  // Getters for the parts
     81  virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget,
     82                                int32_t aAppUnitsPerDevPixel,
     83                                gfxFontGroup* aFontGroup, char16_t aChar,
     84                                bool aVertical, uint32_t aPosition) = 0;
     85  virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget,
     86                            int32_t aAppUnitsPerDevPixel,
     87                            gfxFontGroup* aFontGroup, char16_t aChar,
     88                            bool aVertical, uint32_t aSize) = 0;
     89 
     90  // True if this table contains parts to render this char
     91  virtual bool HasPartsOf(DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
     92                          gfxFontGroup* aFontGroup, char16_t aChar,
     93                          bool aVertical) = 0;
     94 
     95  virtual already_AddRefed<gfxTextRun> MakeTextRun(
     96      DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
     97      gfxFontGroup* aFontGroup, const nsGlyphCode& aGlyph) = 0;
     98 
     99 protected:
    100  // For speedy re-use, we always cache the last data used in the table.
    101  // mCharCache is the Unicode point of the last char that was queried in this
    102  // table.
    103  char16_t mCharCache = 0;
    104 };
    105 
    106 // A Unicode construction is a set of all the stretchy MathML characters that
    107 // can be rendered with Unicode using larger and/or partial glyphs. The entry
    108 // of each stretchy character gives the 4 partial glyphs and the variants of
    109 // bigger sizes (if any):
    110 // Base | Top (or Left) | Middle | Bottom (or Right) | Glue | Size 0 | Size 1
    111 // A position that is not relevant to a particular character is indicated there
    112 // with the UNICODE REPLACEMENT CHARACTER 0xFFFD.
    113 // FIXME: This table was converted from the original mathfontUnicode.properties
    114 // in bug 1007090. It has been unchanged for backward compatibility but probably
    115 // at some point this should be standardized and implemented cross-browser.
    116 // See also https://w3c.github.io/mathml-core/#unicode-based-glyph-assemblies
    117 typedef char16_t const UnicodeConstruction[7];
    118 // clang-format off
    119 static const UnicodeConstruction gUnicodeTableConstructions[] = {
    120  { 0x0028, 0x239B, 0x0000, 0x239D, 0x239C, 0x0028, 0x0000 },
    121  { 0x0029, 0x239E, 0x0000, 0x23A0, 0x239F, 0x0029, 0x0000 },
    122  { 0x003D, 0x0000, 0x0000, 0x0000, 0x003D, 0x003D, 0x0000 },
    123  { 0x005B, 0x23A1, 0x0000, 0x23A3, 0x23A2, 0x005B, 0x0000 },
    124  { 0x005D, 0x23A4, 0x0000, 0x23A6, 0x23A5, 0x005D, 0x0000 },
    125  { 0x005F, 0x0000, 0x0000, 0x0000, 0x0332, 0x0332, 0x0000 },
    126  { 0x007B, 0x23A7, 0x23A8, 0x23A9, 0x23AA, 0x007B, 0x0000 },
    127  { 0x007C, 0x0000, 0x0000, 0x0000, 0x007C, 0x007C, 0x0000 },
    128  { 0x007D, 0x23AB, 0x23AC, 0x23AD, 0x23AA, 0x007D, 0x0000 },
    129  { 0x00AF, 0x0000, 0x0000, 0x0000, 0x0305, 0x00AF, 0x0000 },
    130  { 0x0332, 0x0000, 0x0000, 0x0000, 0x0332, 0x0332, 0x0000 },
    131  { 0x2016, 0x0000, 0x0000, 0x0000, 0x2016, 0x2016, 0x0000 },
    132  { 0x203E, 0x0000, 0x0000, 0x0000, 0x0305, 0x00AF, 0x0000 },
    133  { 0x2190, 0x2190, 0x0000, 0x0000, 0x23AF, 0x2190, 0x27F5 },
    134  { 0x2191, 0x2191, 0x0000, 0x0000, 0x23D0, 0x2191, 0x0000 },
    135  { 0x2192, 0x0000, 0x0000, 0x2192, 0x23AF, 0x2192, 0x27F6 },
    136  { 0x2193, 0x0000, 0x0000, 0x2193, 0x23D0, 0x2193, 0x0000 },
    137  { 0x2194, 0x2190, 0x0000, 0x2192, 0x23AF, 0x2194, 0x27F7 },
    138  { 0x2195, 0x2191, 0x0000, 0x2193, 0x23D0, 0x2195, 0x0000 },
    139  { 0x21A4, 0x2190, 0x0000, 0x22A3, 0x23AF, 0x21AA, 0x27FB },
    140  { 0x21A6, 0x22A2, 0x0000, 0x2192, 0x23AF, 0x21A6, 0x27FC },
    141  { 0x21BC, 0x21BC, 0x0000, 0x0000, 0x23AF, 0x21BC, 0x0000 },
    142  { 0x21BD, 0x21BD, 0x0000, 0x0000, 0x23AF, 0x21BD, 0x0000 },
    143  { 0x21C0, 0x0000, 0x0000, 0x21C0, 0x23AF, 0x21C0, 0x0000 },
    144  { 0x21C1, 0x0000, 0x0000, 0x21C1, 0x23AF, 0x21C1, 0x0000 },
    145  { 0x21D0, 0x0000, 0x0000, 0x0000, 0x0000, 0x21D0, 0x27F8 },
    146  { 0x21D2, 0x0000, 0x0000, 0x0000, 0x0000, 0x21D2, 0x27F9 },
    147  { 0x21D4, 0x0000, 0x0000, 0x0000, 0x0000, 0x21D4, 0x27FA },
    148  { 0x2223, 0x0000, 0x0000, 0x0000, 0x2223, 0x2223, 0x0000 },
    149  { 0x2225, 0x0000, 0x0000, 0x0000, 0x2225, 0x2225, 0x0000 },
    150  { 0x222B, 0x2320, 0x0000, 0x2321, 0x23AE, 0x222B, 0x0000 },
    151  { 0x2308, 0x23A1, 0x0000, 0x0000, 0x23A2, 0x2308, 0x0000 },
    152  { 0x2309, 0x23A4, 0x0000, 0x0000, 0x23A5, 0x2309, 0x0000 },
    153  { 0x230A, 0x0000, 0x0000, 0x23A3, 0x23A2, 0x230A, 0x0000 },
    154  { 0x230B, 0x0000, 0x0000, 0x23A6, 0x23A5, 0x230B, 0x0000 },
    155  { 0x23B0, 0x23A7, 0x0000, 0x23AD, 0x23AA, 0x23B0, 0x0000 },
    156  { 0x23B1, 0x23AB, 0x0000, 0x23A9, 0x23AA, 0x23B1, 0x0000 },
    157  { 0x27F5, 0x2190, 0x0000, 0x0000, 0x23AF, 0x27F5, 0x0000 },
    158  { 0x27F6, 0x0000, 0x0000, 0x2192, 0x23AF, 0x27F6, 0x0000 },
    159  { 0x27F7, 0x2190, 0x0000, 0x2192, 0x23AF, 0x27F7, 0x0000 },
    160  { 0x294E, 0x21BC, 0x0000, 0x21C0, 0x23AF, 0x294E, 0x0000 },
    161  { 0x2950, 0x21BD, 0x0000, 0x21C1, 0x23AF, 0x2950, 0x0000 },
    162  { 0x295A, 0x21BC, 0x0000, 0x22A3, 0x23AF, 0x295A, 0x0000 },
    163  { 0x295B, 0x22A2, 0x0000, 0x21C0, 0x23AF, 0x295B, 0x0000 },
    164  { 0x295E, 0x21BD, 0x0000, 0x22A3, 0x23AF, 0x295E, 0x0000 },
    165  { 0x295F, 0x22A2, 0x0000, 0x21C1, 0x23AF, 0x295F, 0x0000 },
    166 };
    167 // clang-format on
    168 
    169 class nsUnicodeTable final : public nsGlyphTable {
    170 public:
    171  constexpr nsUnicodeTable() = default;
    172 
    173  bool IsUnicodeTable() const final { return true; };
    174 
    175  const nsCString& FontNameFor(const nsGlyphCode& aGlyphCode) const override {
    176    MOZ_ASSERT_UNREACHABLE();
    177    return VoidCString();
    178  }
    179 
    180  virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget,
    181                                int32_t aAppUnitsPerDevPixel,
    182                                gfxFontGroup* aFontGroup, char16_t aChar,
    183                                bool aVertical, uint32_t aPosition) override;
    184 
    185  virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget,
    186                            int32_t aAppUnitsPerDevPixel,
    187                            gfxFontGroup* aFontGroup, char16_t aChar,
    188                            bool aVertical, uint32_t aSize) override {
    189    return ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar,
    190                     aVertical, 4 + aSize);
    191  }
    192 
    193  virtual bool HasPartsOf(DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
    194                          gfxFontGroup* aFontGroup, char16_t aChar,
    195                          bool aVertical) override {
    196    return (ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar,
    197                      aVertical, 0)
    198                .Exists() ||
    199            ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar,
    200                      aVertical, 1)
    201                .Exists() ||
    202            ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar,
    203                      aVertical, 2)
    204                .Exists() ||
    205            ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar,
    206                      aVertical, 3)
    207                .Exists());
    208  }
    209 
    210  virtual already_AddRefed<gfxTextRun> MakeTextRun(
    211      DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
    212      gfxFontGroup* aFontGroup, const nsGlyphCode& aGlyph) override;
    213 
    214 private:
    215  // Not available for the heap!
    216  void* operator new(size_t) = delete;
    217  void* operator new[](size_t) = delete;
    218 
    219  struct UnicodeConstructionComparator {
    220    int operator()(const UnicodeConstruction& aValue) const {
    221      if (mTarget < aValue[0]) {
    222        return -1;
    223      }
    224      if (mTarget > aValue[0]) {
    225        return 1;
    226      }
    227      return 0;
    228    }
    229    explicit UnicodeConstructionComparator(char16_t aTarget)
    230        : mTarget(aTarget) {}
    231    const char16_t mTarget;
    232  };
    233  size_t mCachedIndex = 0;
    234 };
    235 
    236 static constinit nsUnicodeTable gUnicodeTable;
    237 
    238 /* virtual */
    239 nsGlyphCode nsUnicodeTable::ElementAt(DrawTarget* /* aDrawTarget */,
    240                                      int32_t /* aAppUnitsPerDevPixel */,
    241                                      gfxFontGroup* /* aFontGroup */,
    242                                      char16_t aChar, bool /* aVertical */,
    243                                      uint32_t aPosition) {
    244  // Update our cache if it is not associated to this character
    245  if (mCharCache != aChar) {
    246    size_t match;
    247    if (!BinarySearchIf(gUnicodeTableConstructions, 0,
    248                        std::size(gUnicodeTableConstructions),
    249                        UnicodeConstructionComparator(aChar), &match)) {
    250      return kNullGlyph;
    251    }
    252    // update our cache with the new settings
    253    mCachedIndex = match;
    254    mCharCache = aChar;
    255  }
    256 
    257  const UnicodeConstruction& construction =
    258      gUnicodeTableConstructions[mCachedIndex];
    259  if (aPosition + 1 >= std::size(construction)) {
    260    return kNullGlyph;
    261  }
    262  nsGlyphCode ch;
    263  ch.code = construction[aPosition + 1];
    264  ch.isGlyphID = false;
    265  return ch.code == char16_t(0xFFFD) ? kNullGlyph : ch;
    266 }
    267 
    268 /* virtual */
    269 already_AddRefed<gfxTextRun> nsUnicodeTable::MakeTextRun(
    270    DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
    271    gfxFontGroup* aFontGroup, const nsGlyphCode& aGlyph) {
    272  NS_ASSERTION(!aGlyph.isGlyphID,
    273               "nsUnicodeTable can only access glyphs by code point");
    274  return aFontGroup->MakeTextRun(&aGlyph.code, 1, aDrawTarget,
    275                                 aAppUnitsPerDevPixel, gfx::ShapedTextFlags(),
    276                                 nsTextFrameUtils::Flags(), nullptr);
    277 }
    278 
    279 // An instance of nsOpenTypeTable is associated with one gfxFontEntry that
    280 // corresponds to an Open Type font with a MATH table. All the glyphs come from
    281 // the same font and the calls to access size variants and parts are directly
    282 // forwarded to the gfx code.
    283 class nsOpenTypeTable final : public nsGlyphTable {
    284 public:
    285  MOZ_COUNTED_DTOR(nsOpenTypeTable)
    286 
    287  virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget,
    288                                int32_t aAppUnitsPerDevPixel,
    289                                gfxFontGroup* aFontGroup, char16_t aChar,
    290                                bool aVertical, uint32_t aPosition) override;
    291  virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget,
    292                            int32_t aAppUnitsPerDevPixel,
    293                            gfxFontGroup* aFontGroup, char16_t aChar,
    294                            bool aVertical, uint32_t aSize) override;
    295  virtual bool HasPartsOf(DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
    296                          gfxFontGroup* aFontGroup, char16_t aChar,
    297                          bool aVertical) override;
    298 
    299  const nsCString& FontNameFor(const nsGlyphCode& aGlyphCode) const override {
    300    NS_ASSERTION(aGlyphCode.isGlyphID,
    301                 "nsOpenTypeTable can only access glyphs by id");
    302    return mFontFamilyName;
    303  }
    304 
    305  virtual already_AddRefed<gfxTextRun> MakeTextRun(
    306      DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
    307      gfxFontGroup* aFontGroup, const nsGlyphCode& aGlyph) override;
    308 
    309  // This returns a new OpenTypeTable instance to give access to OpenType MATH
    310  // table or nullptr if the font does not have such table. Ownership is passed
    311  // to the caller.
    312  static UniquePtr<nsOpenTypeTable> Create(gfxFont* aFont,
    313                                           gfx::ShapedTextFlags aFlags) {
    314    if (!aFont->TryGetMathTable()) {
    315      return nullptr;
    316    }
    317    return WrapUnique(new nsOpenTypeTable(aFont, aFlags));
    318  }
    319 
    320 private:
    321  RefPtr<gfxFont> mFont;
    322  nsCString mFontFamilyName;
    323  uint32_t mGlyphID;
    324  gfx::ShapedTextFlags mFlags;
    325 
    326  nsOpenTypeTable(gfxFont* aFont, gfx::ShapedTextFlags aFlags)
    327      : mFont(aFont),
    328        mFontFamilyName(aFont->GetFontEntry()->FamilyName()),
    329        mGlyphID(0),
    330        mFlags(aFlags) {
    331    MOZ_COUNT_CTOR(nsOpenTypeTable);
    332  }
    333 
    334  void UpdateCache(DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
    335                   gfxFontGroup* aFontGroup, char16_t aChar);
    336 
    337  bool IsRtl() const {
    338    return bool(mFlags & gfx::ShapedTextFlags::TEXT_IS_RTL);
    339  };
    340 };
    341 
    342 void nsOpenTypeTable::UpdateCache(DrawTarget* aDrawTarget,
    343                                  int32_t aAppUnitsPerDevPixel,
    344                                  gfxFontGroup* aFontGroup, char16_t aChar) {
    345  if (mCharCache != aChar) {
    346    RefPtr<gfxTextRun> textRun =
    347        aFontGroup->MakeTextRun(&aChar, 1, aDrawTarget, aAppUnitsPerDevPixel,
    348                                mFlags, nsTextFrameUtils::Flags(), nullptr);
    349    const gfxTextRun::CompressedGlyph& data = textRun->GetCharacterGlyphs()[0];
    350    if (data.IsSimpleGlyph()) {
    351      mGlyphID = data.GetSimpleGlyph();
    352    } else if (data.GetGlyphCount() == 1) {
    353      mGlyphID = textRun->GetDetailedGlyphs(0)->mGlyphID;
    354    } else {
    355      mGlyphID = 0;
    356    }
    357    mCharCache = aChar;
    358  }
    359 }
    360 
    361 /* virtual */
    362 nsGlyphCode nsOpenTypeTable::ElementAt(DrawTarget* aDrawTarget,
    363                                       int32_t aAppUnitsPerDevPixel,
    364                                       gfxFontGroup* aFontGroup, char16_t aChar,
    365                                       bool aVertical, uint32_t aPosition) {
    366  UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
    367 
    368  uint32_t parts[4];
    369  if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, IsRtl(), parts)) {
    370    return kNullGlyph;
    371  }
    372 
    373  uint32_t glyphID = parts[aPosition];
    374  if (!glyphID) {
    375    return kNullGlyph;
    376  }
    377  nsGlyphCode glyph;
    378  glyph.glyphID = glyphID;
    379  glyph.isGlyphID = true;
    380  return glyph;
    381 }
    382 
    383 /* virtual */
    384 nsGlyphCode nsOpenTypeTable::BigOf(DrawTarget* aDrawTarget,
    385                                   int32_t aAppUnitsPerDevPixel,
    386                                   gfxFontGroup* aFontGroup, char16_t aChar,
    387                                   bool aVertical, uint32_t aSize) {
    388  UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
    389 
    390  uint32_t glyphID =
    391      mFont->MathTable()->VariantsSize(mGlyphID, aVertical, IsRtl(), aSize);
    392  if (!glyphID) {
    393    return kNullGlyph;
    394  }
    395 
    396  nsGlyphCode glyph;
    397  glyph.glyphID = glyphID;
    398  glyph.isGlyphID = true;
    399  return glyph;
    400 }
    401 
    402 /* virtual */
    403 bool nsOpenTypeTable::HasPartsOf(DrawTarget* aDrawTarget,
    404                                 int32_t aAppUnitsPerDevPixel,
    405                                 gfxFontGroup* aFontGroup, char16_t aChar,
    406                                 bool aVertical) {
    407  UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
    408 
    409  uint32_t parts[4];
    410  if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, IsRtl(), parts)) {
    411    return false;
    412  }
    413 
    414  return parts[0] || parts[1] || parts[2] || parts[3];
    415 }
    416 
    417 /* virtual */
    418 already_AddRefed<gfxTextRun> nsOpenTypeTable::MakeTextRun(
    419    DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
    420    gfxFontGroup* aFontGroup, const nsGlyphCode& aGlyph) {
    421  NS_ASSERTION(aGlyph.isGlyphID,
    422               "nsOpenTypeTable can only access glyphs by id");
    423 
    424  gfxTextRunFactory::Parameters params = {
    425      aDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel};
    426  RefPtr<gfxTextRun> textRun = gfxTextRun::Create(
    427      &params, 1, aFontGroup, mFlags, nsTextFrameUtils::Flags());
    428  RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont();
    429  textRun->AddGlyphRun(font, FontMatchType::Kind::kFontGroup, 0, false,
    430                       gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL, false);
    431  // We don't care about CSS writing mode here;
    432  // math runs are assumed to be horizontal.
    433  gfxTextRun::DetailedGlyph detailedGlyph;
    434  detailedGlyph.mGlyphID = aGlyph.glyphID;
    435  detailedGlyph.mAdvance = NSToCoordRound(
    436      aAppUnitsPerDevPixel * font->GetGlyphAdvance(aGlyph.glyphID));
    437  textRun->SetDetailedGlyphs(0, 1, &detailedGlyph);
    438 
    439  return textRun.forget();
    440 }
    441 
    442 // -----------------------------------------------------------------------------
    443 // And now the implementation of nsMathMLChar
    444 
    445 nsMathMLChar::~nsMathMLChar() { MOZ_COUNT_DTOR(nsMathMLChar); }
    446 
    447 ComputedStyle* nsMathMLChar::GetComputedStyle() const {
    448  NS_ASSERTION(mComputedStyle, "chars should always have a ComputedStyle");
    449  return mComputedStyle;
    450 }
    451 
    452 void nsMathMLChar::SetComputedStyle(ComputedStyle* aComputedStyle) {
    453  MOZ_ASSERT(aComputedStyle);
    454  mComputedStyle = aComputedStyle;
    455 }
    456 
    457 void nsMathMLChar::SetData(nsString& aData) {
    458  mData = aData;
    459  // some assumptions until proven otherwise
    460  // note that mGlyph is not initialized
    461  mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
    462  mBoundingMetrics = nsBoundingMetrics();
    463  // check if stretching is applicable ...
    464  if (1 == mData.Length()) {
    465    mDirection = nsMathMLOperators::GetStretchyDirection(mData);
    466    // default tentative table (not the one that is necessarily going
    467    // to be used)
    468  }
    469 }
    470 
    471 // -----------------------------------------------------------------------------
    472 /*
    473 The Stretch:
    474 @param aContainerSize - suggested size for the stretched char
    475 @param aDesiredStretchSize - OUT parameter. The desired size
    476 after stretching. If no stretching is done, the output will
    477 simply give the base size.
    478 
    479 How it works?
    480 Summary:-
    481 The Stretch() method first looks for a glyph of appropriate
    482 size; If a glyph is found, it is cached by this object and
    483 its size is returned in aDesiredStretchSize. The cached
    484 glyph will then be used at the painting stage.
    485 If no glyph of appropriate size is found, a search is made
    486 to see if the char can be built by parts.
    487 
    488 Details:-
    489 A character gets stretched through the following pipeline :
    490 
    491 1) If the base size of the char is sufficient to cover the
    492    container' size, we use that. If not, it will still be
    493    used as a fallback if the other stages in the pipeline fail.
    494    Issues :
    495    a) The base size, the parts and the variants of a char can
    496       be in different fonts. For eg., the base size for '(' should
    497       come from a normal ascii font if CMEX10 is used, since CMEX10
    498       only contains the stretched versions. Hence, there are two
    499       ComputedStyles in use throughout the process. The leaf style
    500       context of the char holds fonts with which to try to stretch
    501       the char. The parent ComputedStyle of the char contains fonts
    502       for normal rendering. So the parent context is the one used
    503       to get the initial base size at the start of the pipeline.
    504    b) For operators that can be largeop's in display mode,
    505       we will skip the base size even if it fits, so that
    506       the next stage in the pipeline is given a chance to find
    507       a largeop variant. If the next stage fails, we fallback
    508       to the base size.
    509 
    510 2) We search for the first larger variant of the char that fits the
    511    container' size.  We first search for larger variants using the glyph
    512    table corresponding to the first existing font specified in the list of
    513    stretchy fonts held by the leaf ComputedStyle (from -moz-math-stretchy in
    514    mathml.css).  Generic fonts are resolved by the preference
    515    "font.mathfont-family".
    516    Issues :
    517    a) the largeop and display settings determine the starting
    518       size when we do the above search, regardless of whether
    519       smaller variants already fit the container' size.
    520    b) if it is a largeopOnly request (i.e., a displaystyle operator
    521       with largeop=true and stretchy=false), we break after finding
    522       the first starting variant, regardless of whether that
    523       variant fits the container's size.
    524 
    525 3) If a variant of appropriate size wasn't found, we see if the char
    526    can be built by parts using the same glyph table.
    527    Issue:
    528       There are chars that have no middle and glue glyphs. For
    529       such chars, the parts need to be joined using the rule.
    530       By convention (TeXbook p.225), the descent of the parts is
    531       zero while their ascent gives the thickness of the rule that
    532       should be used to join them.
    533 
    534 4) If a match was not found in that glyph table, repeat from 2 to search the
    535    ordered list of stretchy fonts for the first font with a glyph table that
    536    provides a fit to the container size.  If no fit is found, the closest fit
    537    is used.
    538 
    539 Of note:
    540 When the pipeline completes successfully, the desired size of the
    541 stretched char can actually be slightly larger or smaller than
    542 aContainerSize. But it is the responsibility of the caller to
    543 account for the spacing when setting aContainerSize, and to leave
    544 any extra margin when placing the stretched char.
    545 */
    546 // -----------------------------------------------------------------------------
    547 
    548 // plain TeX settings (TeXbook p.152)
    549 #define NS_MATHML_DELIMITER_FACTOR 0.901f
    550 #define NS_MATHML_DELIMITER_SHORTFALL_POINTS 5.0f
    551 
    552 static bool IsSizeOK(nscoord a, nscoord b, uint32_t aHint) {
    553  // Normal: True if 'a' is around +/-10% of the target 'b' (10% is
    554  // 1-DelimiterFactor). This often gives a chance to the base size to
    555  // win, especially in the context of sloppy markups without protective
    556  // <mrow></mrow>
    557  bool isNormal =
    558      (aHint & NS_STRETCH_NORMAL) &&
    559      Abs<float>(a - b) < (1.0f - NS_MATHML_DELIMITER_FACTOR) * float(b);
    560 
    561  // Nearer: True if 'a' is around max{ +/-10% of 'b' , 'b' - 5pt },
    562  // as documented in The TeXbook, Ch.17, p.152.
    563  // i.e. within 10% and within 5pt
    564  bool isNearer = false;
    565  if (aHint & (NS_STRETCH_NEARER | NS_STRETCH_LARGEOP)) {
    566    float c = std::max(float(b) * NS_MATHML_DELIMITER_FACTOR,
    567                       float(b) - nsPresContext::CSSPointsToAppUnits(
    568                                      NS_MATHML_DELIMITER_SHORTFALL_POINTS));
    569    isNearer = Abs<float>(b - a) <= float(b) - c;
    570  }
    571 
    572  // Smaller: Mainly for transitory use, to compare two candidate
    573  // choices
    574  bool isSmaller = (aHint & NS_STRETCH_SMALLER) &&
    575                   float(a) >= NS_MATHML_DELIMITER_FACTOR * float(b) && a <= b;
    576 
    577  // Larger: Critical to the sqrt code to ensure that the radical
    578  // size is tall enough
    579  bool isLarger = (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP)) && a >= b;
    580 
    581  return (isNormal || isSmaller || isNearer || isLarger);
    582 }
    583 
    584 static bool IsSizeBetter(nscoord a, nscoord olda, nscoord b, uint32_t aHint) {
    585  if (0 == olda) {
    586    return true;
    587  }
    588  if (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP)) {
    589    return (a >= olda) ? (olda < b) : (a >= b);
    590  }
    591  if (aHint & NS_STRETCH_SMALLER) {
    592    return (a <= olda) ? (olda > b) : (a <= b);
    593  }
    594 
    595  // XXXkt prob want log scale here i.e. 1.5 is closer to 1 than 0.5
    596  return Abs(a - b) < Abs(olda - b);
    597 }
    598 
    599 // We want to place the glyphs even when they don't fit at their
    600 // full extent, i.e., we may clip to tolerate a small amount of
    601 // overlap between the parts. This is important to cater for fonts
    602 // with long glues.
    603 static nscoord ComputeSizeFromParts(nsPresContext* aPresContext,
    604                                    nsGlyphCode* aGlyphs, nscoord* aSizes,
    605                                    nscoord aTargetSize) {
    606  enum { first, middle, last, glue };
    607  // Add the parts that cannot be left out.
    608  nscoord sum = 0;
    609  for (int32_t i = first; i <= last; i++) {
    610    sum += aSizes[i];
    611  }
    612 
    613  // Determine how much is used in joins
    614  nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
    615  int32_t joins = aGlyphs[middle] == aGlyphs[glue] ? 1 : 2;
    616 
    617  // Pick a maximum size using a maximum number of glue glyphs that we are
    618  // prepared to draw for one character.
    619  const int32_t maxGlyphs = 1000;
    620 
    621  // This also takes into account the fact that, if the glue has no size,
    622  // then the character can't be lengthened.
    623  nscoord maxSize = sum - 2 * joins * oneDevPixel + maxGlyphs * aSizes[glue];
    624  if (maxSize < aTargetSize) {
    625    return maxSize;  // settle with the maximum size
    626  }
    627 
    628  // Get the minimum allowable size using some flex.
    629  nscoord minSize = NSToCoordRound(NS_MATHML_DELIMITER_FACTOR * sum);
    630 
    631  if (minSize > aTargetSize) {
    632    return minSize;  // settle with the minimum size
    633  }
    634 
    635  // Fill-up the target area
    636  return aTargetSize;
    637 }
    638 
    639 // Update the font if there is a family change and returns the font group.
    640 bool nsMathMLChar::SetFontFamily(nsPresContext* aPresContext,
    641                                 const nsGlyphTable* aGlyphTable,
    642                                 const nsGlyphCode& aGlyphCode,
    643                                 const StyleFontFamilyList& aDefaultFamilyList,
    644                                 nsFont& aFont,
    645                                 RefPtr<gfxFontGroup>* aFontGroup) {
    646  StyleFontFamilyList glyphCodeFont;
    647  if (aGlyphCode.isGlyphID) {
    648    glyphCodeFont = StyleFontFamilyList::WithOneUnquotedFamily(
    649        aGlyphTable->FontNameFor(aGlyphCode));
    650  }
    651 
    652  const StyleFontFamilyList& familyList =
    653      aGlyphCode.isGlyphID ? glyphCodeFont : aDefaultFamilyList;
    654 
    655  if (!*aFontGroup || aFont.family.families != familyList) {
    656    nsFont font = aFont;
    657    font.family.families = familyList;
    658    const nsStyleFont* styleFont = mComputedStyle->StyleFont();
    659    nsFontMetrics::Params params;
    660    params.language = styleFont->mLanguage;
    661    params.explicitLanguage = styleFont->mExplicitLanguage;
    662    params.userFontSet = aPresContext->GetUserFontSet();
    663    params.textPerf = aPresContext->GetTextPerfMetrics();
    664    params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
    665    RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params);
    666    // Set the font if it is an unicode table or if the same family name has
    667    // been found.
    668    const bool shouldSetFont = [&] {
    669      if (aGlyphTable && aGlyphTable->IsUnicodeTable()) {
    670        return true;
    671      }
    672 
    673      if (familyList.list.IsEmpty()) {
    674        return false;
    675      }
    676 
    677      const auto& firstFontInList = familyList.list.AsSpan()[0];
    678 
    679      RefPtr<gfxFont> firstFont = fm->GetThebesFontGroup()->GetFirstValidFont();
    680      RefPtr<nsAtom> firstFontName =
    681          NS_Atomize(firstFont->GetFontEntry()->FamilyName());
    682 
    683      return firstFontInList.IsFamilyName() &&
    684             firstFontInList.AsFamilyName().name.AsAtom() == firstFontName;
    685    }();
    686    if (!shouldSetFont) {
    687      return false;
    688    }
    689    aFont.family.families = familyList;
    690    *aFontGroup = fm->GetThebesFontGroup();
    691  }
    692  return true;
    693 }
    694 
    695 static nsBoundingMetrics MeasureTextRun(DrawTarget* aDrawTarget,
    696                                        gfxTextRun* aTextRun) {
    697  gfxTextRun::Metrics metrics =
    698      aTextRun->MeasureText(gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, aDrawTarget);
    699 
    700  nsBoundingMetrics bm;
    701  bm.leftBearing = NSToCoordFloor(metrics.mBoundingBox.X());
    702  bm.rightBearing = NSToCoordCeil(metrics.mBoundingBox.XMost());
    703  bm.ascent = NSToCoordCeil(-metrics.mBoundingBox.Y());
    704  bm.descent = NSToCoordCeil(metrics.mBoundingBox.YMost());
    705  bm.width = NSToCoordRound(metrics.mAdvanceWidth);
    706 
    707  return bm;
    708 }
    709 
    710 class nsMathMLChar::StretchEnumContext {
    711 public:
    712  StretchEnumContext(nsMathMLChar* aChar, nsPresContext* aPresContext,
    713                     DrawTarget* aDrawTarget, float aFontSizeInflation,
    714                     nsStretchDirection aStretchDirection, nscoord aTargetSize,
    715                     uint32_t aStretchHint,
    716                     nsBoundingMetrics& aStretchedMetrics,
    717                     const StyleFontFamilyList& aFamilyList, bool& aGlyphFound)
    718      : mChar(aChar),
    719        mPresContext(aPresContext),
    720        mDrawTarget(aDrawTarget),
    721        mFontSizeInflation(aFontSizeInflation),
    722        mDirection(aStretchDirection),
    723        mTargetSize(aTargetSize),
    724        mStretchHint(aStretchHint),
    725        mBoundingMetrics(aStretchedMetrics),
    726        mFamilyList(aFamilyList),
    727        mTryVariants(true),
    728        mTryParts(true),
    729        mGlyphFound(aGlyphFound) {}
    730 
    731  static bool EnumCallback(const StyleSingleFontFamily& aFamily, void* aData,
    732                           gfx::ShapedTextFlags aFlags, bool aRtl);
    733 
    734 private:
    735  bool TryVariants(nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
    736                   const StyleFontFamilyList& aFamilyList, bool aRtl);
    737  bool TryParts(nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
    738                const StyleFontFamilyList& aFamilyList);
    739 
    740  nsMathMLChar* mChar;
    741  nsPresContext* mPresContext;
    742  DrawTarget* mDrawTarget;
    743  float mFontSizeInflation;
    744  const nsStretchDirection mDirection;
    745  const nscoord mTargetSize;
    746  const uint32_t mStretchHint;
    747  nsBoundingMetrics& mBoundingMetrics;
    748  // Font families to search
    749  const StyleFontFamilyList& mFamilyList;
    750 
    751 public:
    752  bool mTryVariants;
    753  bool mTryParts;
    754 
    755 private:
    756  bool mUnicodeTableTried = false;
    757  bool& mGlyphFound;
    758 };
    759 
    760 // 2. See if there are any glyphs of the appropriate size.
    761 // Returns true if the size is OK, false to keep searching.
    762 // Always updates the char if a better match is found.
    763 bool nsMathMLChar::StretchEnumContext::TryVariants(
    764    nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
    765    const StyleFontFamilyList& aFamilyList, bool aRtl) {
    766  // Use our stretchy ComputedStyle now that stretching is in progress
    767  ComputedStyle* sc = mChar->mComputedStyle;
    768  nsFont font = sc->StyleFont()->mFont;
    769  NormalizeDefaultFont(font, mFontSizeInflation);
    770 
    771  bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
    772  nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel();
    773  char16_t uchar = mChar->mData[0];
    774  bool largeop = (NS_STRETCH_LARGEOP & mStretchHint) != 0;
    775  bool largeopOnly = largeop && (NS_STRETCH_VARIABLE_MASK & mStretchHint) == 0;
    776  bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
    777 
    778  nscoord bestSize =
    779      isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent
    780                 : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
    781  bool haveBetter = false;
    782 
    783  // start at size = 1 (size = 0 is the char at its normal size), except for
    784  // rtlm fonts since they might have a character there.
    785  int32_t size =
    786      (StaticPrefs::mathml_rtl_operator_mirroring_enabled() && aRtl) ? 0 : 1;
    787  nsGlyphCode ch;
    788  nscoord displayOperatorMinHeight = 0;
    789  if (largeopOnly) {
    790    NS_ASSERTION(isVertical, "Stretching should be in the vertical direction");
    791    ch = aGlyphTable->BigOf(mDrawTarget, oneDevPixel, *aFontGroup, uchar,
    792                            isVertical, 0);
    793    if (ch.isGlyphID) {
    794      RefPtr<gfxFont> mathFont = aFontGroup->get()->GetFirstMathFont();
    795      // For OpenType MATH fonts, we will rely on the DisplayOperatorMinHeight
    796      // to select the right size variant.
    797      if (mathFont) {
    798        displayOperatorMinHeight = mathFont->MathTable()->Constant(
    799            gfxMathTable::DisplayOperatorMinHeight, oneDevPixel);
    800      }
    801    }
    802  }
    803 #ifdef NOISY_SEARCH
    804  printf("  searching in %s ...\n", NS_LossyConvertUTF16toASCII(aFamily).get());
    805 #endif
    806  while ((ch = aGlyphTable->BigOf(mDrawTarget, oneDevPixel, *aFontGroup, uchar,
    807                                  isVertical, size))
    808             .Exists()) {
    809    if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamilyList, font,
    810                              aFontGroup)) {
    811      // if largeopOnly is set, break now
    812      if (largeopOnly) {
    813        break;
    814      }
    815      ++size;
    816      continue;
    817    }
    818 
    819    RefPtr<gfxTextRun> textRun =
    820        aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel, *aFontGroup, ch);
    821    nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun.get());
    822    if (ch.isGlyphID) {
    823      RefPtr<gfxFont> mathFont = aFontGroup->get()->GetFirstMathFont();
    824      if (mathFont) {
    825        // MeasureTextRun should have set the advance width to the right
    826        // bearing for OpenType MATH fonts. We now subtract the italic
    827        // correction, so that nsMathMLmmultiscripts will place the scripts
    828        // correctly.
    829        // Note that STIX-Word does not provide italic corrections but its
    830        // advance widths do not match right bearings.
    831        // (http://sourceforge.net/p/stixfonts/tracking/50/)
    832        gfxFloat italicCorrection =
    833            mathFont->MathTable()->ItalicsCorrection(ch.glyphID);
    834        if (italicCorrection) {
    835          bm.width -= NSToCoordRound(italicCorrection * oneDevPixel);
    836          if (bm.width < 0) {
    837            bm.width = 0;
    838          }
    839        }
    840      }
    841    }
    842 
    843    nscoord charSize =
    844        isVertical ? bm.ascent + bm.descent : bm.rightBearing - bm.leftBearing;
    845 
    846    if (largeopOnly ||
    847        IsSizeBetter(charSize, bestSize, mTargetSize, mStretchHint)) {
    848      mGlyphFound = true;
    849      if (maxWidth) {
    850        // IsSizeBetter() checked that charSize < maxsize;
    851        // Leave ascent, descent, and bestsize as these contain maxsize.
    852        if (mBoundingMetrics.width < bm.width) {
    853          mBoundingMetrics.width = bm.width;
    854        }
    855        if (mBoundingMetrics.leftBearing > bm.leftBearing) {
    856          mBoundingMetrics.leftBearing = bm.leftBearing;
    857        }
    858        if (mBoundingMetrics.rightBearing < bm.rightBearing) {
    859          mBoundingMetrics.rightBearing = bm.rightBearing;
    860        }
    861        // Continue to check other sizes unless largeopOnly
    862        haveBetter = largeopOnly;
    863      } else {
    864        mBoundingMetrics = bm;
    865        haveBetter = true;
    866        bestSize = charSize;
    867        mChar->mGlyphs[0] = std::move(textRun);
    868        mChar->mDrawingMethod = DrawingMethod::Variant;
    869      }
    870 #ifdef NOISY_SEARCH
    871      printf("    size:%d Current best\n", size);
    872 #endif
    873    } else {
    874 #ifdef NOISY_SEARCH
    875      printf("    size:%d Rejected!\n", size);
    876 #endif
    877      if (haveBetter) {
    878        break;  // Not making an futher progress, stop searching
    879      }
    880    }
    881 
    882    // If this a largeop only operator, we stop if the glyph is large enough.
    883    if (largeopOnly && (bm.ascent + bm.descent) >= displayOperatorMinHeight) {
    884      break;
    885    }
    886    ++size;
    887  }
    888 
    889  return haveBetter &&
    890         (largeopOnly || IsSizeOK(bestSize, mTargetSize, mStretchHint));
    891 }
    892 
    893 // 3. Build by parts.
    894 // Returns true if the size is OK, false to keep searching.
    895 // Always updates the char if a better match is found.
    896 bool nsMathMLChar::StretchEnumContext::TryParts(
    897    nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
    898    const StyleFontFamilyList& aFamilyList) {
    899  // Use our stretchy ComputedStyle now that stretching is in progress
    900  nsFont font = mChar->mComputedStyle->StyleFont()->mFont;
    901  NormalizeDefaultFont(font, mFontSizeInflation);
    902 
    903  // Compute the bounding metrics of all partial glyphs
    904  RefPtr<gfxTextRun> textRun[4];
    905  nsGlyphCode chdata[4];
    906  nsBoundingMetrics bmdata[4];
    907  nscoord sizedata[4];
    908 
    909  bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
    910  nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel();
    911  char16_t uchar = mChar->mData[0];
    912  bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
    913  if (!aGlyphTable->HasPartsOf(mDrawTarget, oneDevPixel, *aFontGroup, uchar,
    914                               isVertical)) {
    915    return false;  // to next table
    916  }
    917 
    918  for (int32_t i = 0; i < 4; i++) {
    919    nsGlyphCode ch = aGlyphTable->ElementAt(mDrawTarget, oneDevPixel,
    920                                            *aFontGroup, uchar, isVertical, i);
    921    chdata[i] = ch;
    922    if (ch.Exists()) {
    923      if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamilyList,
    924                                font, aFontGroup)) {
    925        return false;
    926      }
    927 
    928      textRun[i] =
    929          aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel, *aFontGroup, ch);
    930      nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun[i].get());
    931      bmdata[i] = bm;
    932      sizedata[i] = isVertical ? bm.ascent + bm.descent
    933                               : bm.rightBearing - bm.leftBearing;
    934    } else {
    935      // Null glue indicates that a rule will be drawn, which can stretch to
    936      // fill any space.
    937      textRun[i] = nullptr;
    938      bmdata[i] = nsBoundingMetrics();
    939      sizedata[i] = i == 3 ? mTargetSize : 0;
    940    }
    941  }
    942 
    943  // For the Unicode table, we check that all the glyphs are actually found and
    944  // come from the same font.
    945  if (aGlyphTable->IsUnicodeTable()) {
    946    gfxFont* unicodeFont = nullptr;
    947    for (int32_t i = 0; i < 4; i++) {
    948      if (!textRun[i]) {
    949        continue;
    950      }
    951      if (textRun[i]->GetLength() != 1 ||
    952          textRun[i]->GetCharacterGlyphs()[0].IsMissing()) {
    953        return false;
    954      }
    955      uint32_t numGlyphRuns;
    956      const gfxTextRun::GlyphRun* glyphRuns =
    957          textRun[i]->GetGlyphRuns(&numGlyphRuns);
    958      if (numGlyphRuns != 1) {
    959        return false;
    960      }
    961      if (!unicodeFont) {
    962        unicodeFont = glyphRuns[0].mFont;
    963      } else if (unicodeFont != glyphRuns[0].mFont) {
    964        return false;
    965      }
    966    }
    967  }
    968 
    969  // Build by parts if we have successfully computed the
    970  // bounding metrics of all parts.
    971  nscoord computedSize =
    972      ComputeSizeFromParts(mPresContext, chdata, sizedata, mTargetSize);
    973 
    974  nscoord currentSize =
    975      isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent
    976                 : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
    977 
    978  if (!IsSizeBetter(computedSize, currentSize, mTargetSize, mStretchHint)) {
    979 #ifdef NOISY_SEARCH
    980    printf("    Font %s Rejected!\n",
    981           NS_LossyConvertUTF16toASCII(fontName).get());
    982 #endif
    983    return false;  // to next table
    984  }
    985 
    986 #ifdef NOISY_SEARCH
    987  printf("    Font %s Current best!\n",
    988         NS_LossyConvertUTF16toASCII(fontName).get());
    989 #endif
    990 
    991  // The computed size is the best we have found so far...
    992  // now is the time to compute and cache our bounding metrics
    993  if (isVertical) {
    994    int32_t i;
    995    // Try and find the first existing part and then determine the extremal
    996    // horizontal metrics of the parts.
    997    for (i = 0; i <= 3 && !textRun[i]; i++) {
    998      ;
    999    }
   1000    if (i == 4) {
   1001      NS_ERROR("Cannot stretch - All parts missing");
   1002      return false;
   1003    }
   1004    nscoord lbearing = bmdata[i].leftBearing;
   1005    nscoord rbearing = bmdata[i].rightBearing;
   1006    nscoord width = bmdata[i].width;
   1007    i++;
   1008    for (; i <= 3; i++) {
   1009      if (!textRun[i]) {
   1010        continue;
   1011      }
   1012      lbearing = std::min(lbearing, bmdata[i].leftBearing);
   1013      rbearing = std::max(rbearing, bmdata[i].rightBearing);
   1014      width = std::max(width, bmdata[i].width);
   1015    }
   1016    if (maxWidth) {
   1017      lbearing = std::min(lbearing, mBoundingMetrics.leftBearing);
   1018      rbearing = std::max(rbearing, mBoundingMetrics.rightBearing);
   1019      width = std::max(width, mBoundingMetrics.width);
   1020    }
   1021    mBoundingMetrics.width = width;
   1022    // When maxWidth, updating ascent and descent indicates that no characters
   1023    // larger than this character's minimum size need to be checked as they
   1024    // will not be used.
   1025    mBoundingMetrics.ascent = bmdata[0].ascent;  // not used except with descent
   1026                                                 // for height
   1027    mBoundingMetrics.descent = computedSize - mBoundingMetrics.ascent;
   1028    mBoundingMetrics.leftBearing = lbearing;
   1029    mBoundingMetrics.rightBearing = rbearing;
   1030  } else {
   1031    int32_t i;
   1032    // Try and find the first existing part and then determine the extremal
   1033    // vertical metrics of the parts.
   1034    for (i = 0; i <= 3 && !textRun[i]; i++) {
   1035      ;
   1036    }
   1037    if (i == 4) {
   1038      NS_ERROR("Cannot stretch - All parts missing");
   1039      return false;
   1040    }
   1041    nscoord ascent = bmdata[i].ascent;
   1042    nscoord descent = bmdata[i].descent;
   1043    i++;
   1044    for (; i <= 3; i++) {
   1045      if (!textRun[i]) {
   1046        continue;
   1047      }
   1048      ascent = std::max(ascent, bmdata[i].ascent);
   1049      descent = std::max(descent, bmdata[i].descent);
   1050    }
   1051    mBoundingMetrics.width = computedSize;
   1052    mBoundingMetrics.ascent = ascent;
   1053    mBoundingMetrics.descent = descent;
   1054    mBoundingMetrics.leftBearing = 0;
   1055    mBoundingMetrics.rightBearing = computedSize;
   1056  }
   1057  mGlyphFound = true;
   1058  if (maxWidth) {
   1059    return false;  // Continue to check other sizes
   1060  }
   1061 
   1062  // reset
   1063  mChar->mDrawingMethod = DrawingMethod::Parts;
   1064  for (int32_t i = 0; i < 4; i++) {
   1065    mChar->mGlyphs[i] = std::move(textRun[i]);
   1066    mChar->mBmData[i] = bmdata[i];
   1067  }
   1068 
   1069  return IsSizeOK(computedSize, mTargetSize, mStretchHint);
   1070 }
   1071 
   1072 // Returns true iff stretching succeeded with the given family.
   1073 // This is called for each family, whether it exists or not.
   1074 bool nsMathMLChar::StretchEnumContext::EnumCallback(
   1075    const StyleSingleFontFamily& aFamily, void* aData,
   1076    gfx::ShapedTextFlags aFlags, bool aRtl) {
   1077  StretchEnumContext* context = static_cast<StretchEnumContext*>(aData);
   1078 
   1079  // for comparisons, force use of unquoted names
   1080  StyleFontFamilyList family;
   1081  if (aFamily.IsFamilyName()) {
   1082    family = StyleFontFamilyList::WithOneUnquotedFamily(
   1083        nsAtomCString(aFamily.AsFamilyName().name.AsAtom()));
   1084  }
   1085 
   1086  // Check font family if it is not a generic one
   1087  // We test with the kNullGlyph
   1088  ComputedStyle* sc = context->mChar->mComputedStyle;
   1089  nsFont font = sc->StyleFont()->mFont;
   1090  NormalizeDefaultFont(font, context->mFontSizeInflation);
   1091  RefPtr<gfxFontGroup> fontGroup;
   1092  if (!aFamily.IsGeneric() &&
   1093      !context->mChar->SetFontFamily(context->mPresContext, nullptr, kNullGlyph,
   1094                                     family, font, &fontGroup)) {
   1095    return false;  // Could not set the family
   1096  }
   1097 
   1098  // Determine the glyph table to use for this font.
   1099  UniquePtr<nsOpenTypeTable> openTypeTable;
   1100  auto glyphTable = [&aFamily, &fontGroup, &openTypeTable,
   1101                     &aFlags]() -> nsGlyphTable* {
   1102    if (!aFamily.IsGeneric()) {
   1103      // If the font contains an Open Type MATH table, use it.
   1104      RefPtr<gfxFont> font = fontGroup->GetFirstValidFont();
   1105      openTypeTable = nsOpenTypeTable::Create(font, aFlags);
   1106      if (openTypeTable) {
   1107        return openTypeTable.get();
   1108      }
   1109    }
   1110    // Fallback to the Unicode table.
   1111    return &gUnicodeTable;
   1112  }();
   1113  MOZ_ASSERT(glyphTable);
   1114 
   1115  if (!openTypeTable) {
   1116    // Make sure we only try the UnicodeTable once.
   1117    if (context->mUnicodeTableTried) {
   1118      return false;
   1119    }
   1120    context->mUnicodeTableTried = true;
   1121  }
   1122 
   1123  // If the unicode table is being used, then search all font families.  If a
   1124  // special table is being used then the font in this family should have the
   1125  // specified glyphs.
   1126  const StyleFontFamilyList& familyList =
   1127      glyphTable->IsUnicodeTable() ? context->mFamilyList : family;
   1128 
   1129  return (context->mTryVariants &&
   1130          context->TryVariants(glyphTable, &fontGroup, familyList, aRtl)) ||
   1131         (context->mTryParts &&
   1132          context->TryParts(glyphTable, &fontGroup, familyList));
   1133 }
   1134 
   1135 static void AppendFallbacks(nsTArray<StyleSingleFontFamily>& aNames,
   1136                            const nsTArray<nsCString>& aFallbacks) {
   1137  for (const nsCString& fallback : aFallbacks) {
   1138    aNames.AppendElement(StyleSingleFontFamily::FamilyName(
   1139        StyleFamilyName{StyleAtom(NS_Atomize(fallback)),
   1140                        StyleFontFamilyNameSyntax::Identifiers}));
   1141  }
   1142 }
   1143 
   1144 // insert math fallback families just before the first generic or at the end
   1145 // when no generic present
   1146 static void InsertMathFallbacks(StyleFontFamilyList& aFamilyList,
   1147                                nsTArray<nsCString>& aFallbacks) {
   1148  nsTArray<StyleSingleFontFamily> mergedList;
   1149 
   1150  bool inserted = false;
   1151  for (const auto& name : aFamilyList.list.AsSpan()) {
   1152    if (!inserted && name.IsGeneric()) {
   1153      inserted = true;
   1154      AppendFallbacks(mergedList, aFallbacks);
   1155    }
   1156    mergedList.AppendElement(name);
   1157  }
   1158 
   1159  if (!inserted) {
   1160    AppendFallbacks(mergedList, aFallbacks);
   1161  }
   1162  aFamilyList = StyleFontFamilyList::WithNames(std::move(mergedList));
   1163 }
   1164 
   1165 nsresult nsMathMLChar::StretchInternal(
   1166    nsIFrame* aForFrame, DrawTarget* aDrawTarget, float aFontSizeInflation,
   1167    nsStretchDirection& aStretchDirection,
   1168    const nsBoundingMetrics& aContainerSize,
   1169    nsBoundingMetrics& aDesiredStretchSize, uint32_t aStretchHint,
   1170    // These are currently only used when
   1171    // aStretchHint & NS_STRETCH_MAXWIDTH:
   1172    float aMaxSize, bool aMaxSizeIsAbsolute) {
   1173  nsPresContext* presContext = aForFrame->PresContext();
   1174 
   1175  // if we have been called before, and we didn't actually stretch, our
   1176  // direction may have been set to NS_STRETCH_DIRECTION_UNSUPPORTED.
   1177  // So first set our direction back to its instrinsic value
   1178  nsStretchDirection direction = nsMathMLOperators::GetStretchyDirection(mData);
   1179 
   1180  // Set default font and get the default bounding metrics
   1181  // mComputedStyle is a leaf context used only when stretching happens.
   1182  // For the base size, the default font should come from the parent context
   1183  nsFont font = aForFrame->StyleFont()->mFont;
   1184  NormalizeDefaultFont(font, aFontSizeInflation);
   1185 
   1186  const nsStyleFont* styleFont = mComputedStyle->StyleFont();
   1187  nsFontMetrics::Params params;
   1188  params.language = styleFont->mLanguage;
   1189  params.explicitLanguage = styleFont->mExplicitLanguage;
   1190  params.userFontSet = presContext->GetUserFontSet();
   1191  params.textPerf = presContext->GetTextPerfMetrics();
   1192  RefPtr<nsFontMetrics> fm = presContext->GetMetricsFor(font, params);
   1193  uint32_t len = uint32_t(mData.Length());
   1194 
   1195  gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
   1196  // If the math font doesn't support rtlm, fall back to using a scale of -1 on
   1197  // the Y axis.
   1198  if (mMirroringMethod == MirroringMethod::Glyph) {
   1199    RefPtr<gfxFont> font = fm->GetThebesFontGroup()->GetFirstMathFont();
   1200    const uint32_t kRtlm = HB_TAG('r', 't', 'l', 'm');
   1201    if (!font || !font->FeatureWillHandleChar(intl::Script::COMMON, kRtlm,
   1202                                              mData.First())) {
   1203      mMirroringMethod = MirroringMethod::ScaleFallback;
   1204    }
   1205  }
   1206  if (mMirroringMethod == MirroringMethod::Glyph ||
   1207      mMirroringMethod == MirroringMethod::Character) {
   1208    flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
   1209  }
   1210 
   1211  mGlyphs[0] = fm->GetThebesFontGroup()->MakeTextRun(
   1212      static_cast<const char16_t*>(mData.get()), len, aDrawTarget,
   1213      presContext->AppUnitsPerDevPixel(), flags, nsTextFrameUtils::Flags(),
   1214      presContext->MissingFontRecorder());
   1215  aDesiredStretchSize = MeasureTextRun(aDrawTarget, mGlyphs[0].get());
   1216 
   1217  bool maxWidth = (NS_STRETCH_MAXWIDTH & aStretchHint) != 0;
   1218  if (!maxWidth) {
   1219    mUnscaledAscent = aDesiredStretchSize.ascent;
   1220  }
   1221 
   1222  //////////////////////////////////////////////////////////////////////////////
   1223  // 1. Check the common situations where stretching is not actually needed
   1224  //////////////////////////////////////////////////////////////////////////////
   1225 
   1226  // quick return if there is nothing special about this char
   1227  if ((aStretchDirection != direction &&
   1228       aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT) ||
   1229      (aStretchHint & ~NS_STRETCH_MAXWIDTH) == NS_STRETCH_NONE) {
   1230    mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
   1231    return NS_OK;
   1232  }
   1233 
   1234  // if no specified direction, attempt to stretch in our preferred direction
   1235  if (aStretchDirection == NS_STRETCH_DIRECTION_DEFAULT) {
   1236    aStretchDirection = direction;
   1237  }
   1238 
   1239  // see if this is a particular largeop or largeopOnly request
   1240  bool largeop = (NS_STRETCH_LARGEOP & aStretchHint) != 0;
   1241  bool stretchy = (NS_STRETCH_VARIABLE_MASK & aStretchHint) != 0;
   1242  bool largeopOnly = largeop && !stretchy;
   1243 
   1244  bool isVertical = (direction == NS_STRETCH_DIRECTION_VERTICAL);
   1245 
   1246  nscoord targetSize =
   1247      isVertical ? aContainerSize.ascent + aContainerSize.descent
   1248                 : aContainerSize.rightBearing - aContainerSize.leftBearing;
   1249 
   1250  if (maxWidth) {
   1251    // See if it is only necessary to consider glyphs up to some maximum size.
   1252    // Set the current height to the maximum size, and set aStretchHint to
   1253    // NS_STRETCH_SMALLER if the size is variable, so that only smaller sizes
   1254    // are considered.  targetSize from GetMaxWidth() is 0.
   1255    if (stretchy) {
   1256      // variable size stretch - consider all sizes < maxsize
   1257      aStretchHint =
   1258          (aStretchHint & ~NS_STRETCH_VARIABLE_MASK) | NS_STRETCH_SMALLER;
   1259    }
   1260 
   1261    // Use NS_MATHML_DELIMITER_FACTOR to allow some slightly larger glyphs as
   1262    // maxsize is not enforced exactly.
   1263    if (aMaxSize == NS_MATHML_OPERATOR_SIZE_INFINITY) {
   1264      aDesiredStretchSize.ascent = nscoord_MAX;
   1265      aDesiredStretchSize.descent = 0;
   1266    } else {
   1267      nscoord height = aDesiredStretchSize.ascent + aDesiredStretchSize.descent;
   1268      if (height == 0) {
   1269        if (aMaxSizeIsAbsolute) {
   1270          aDesiredStretchSize.ascent =
   1271              NSToCoordRound(aMaxSize / NS_MATHML_DELIMITER_FACTOR);
   1272          aDesiredStretchSize.descent = 0;
   1273        }
   1274        // else: leave height as 0
   1275      } else {
   1276        float scale = aMaxSizeIsAbsolute ? aMaxSize / height : aMaxSize;
   1277        scale /= NS_MATHML_DELIMITER_FACTOR;
   1278        aDesiredStretchSize.ascent =
   1279            NSToCoordRound(scale * aDesiredStretchSize.ascent);
   1280        aDesiredStretchSize.descent =
   1281            NSToCoordRound(scale * aDesiredStretchSize.descent);
   1282      }
   1283    }
   1284  }
   1285 
   1286  nsBoundingMetrics initialSize = aDesiredStretchSize;
   1287  nscoord charSize = isVertical
   1288                         ? initialSize.ascent + initialSize.descent
   1289                         : initialSize.rightBearing - initialSize.leftBearing;
   1290 
   1291  bool done = false;
   1292 
   1293  if (!maxWidth && !largeop) {
   1294    // Doing Stretch() not GetMaxWidth(),
   1295    // and not a largeop in display mode; we're done if size fits
   1296    if ((targetSize <= 0) || ((isVertical && charSize >= targetSize) ||
   1297                              IsSizeOK(charSize, targetSize, aStretchHint))) {
   1298      done = true;
   1299    }
   1300  }
   1301 
   1302  //////////////////////////////////////////////////////////////////////////////
   1303  // 2/3. Search for a glyph or set of part glyphs of appropriate size
   1304  //////////////////////////////////////////////////////////////////////////////
   1305 
   1306  bool glyphFound = false;
   1307 
   1308  if (!done) {  // normal case
   1309    // Use the css font-family but add preferred fallback fonts.
   1310    font = mComputedStyle->StyleFont()->mFont;
   1311    NormalizeDefaultFont(font, aFontSizeInflation);
   1312 
   1313    // really shouldn't be doing things this way but for now
   1314    // insert fallbacks into the list
   1315    AutoTArray<nsCString, 16> mathFallbacks;
   1316    nsAutoCString value;
   1317    gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
   1318    pfl->Lock();
   1319    if (pfl->GetFontPrefs()->LookupName("serif.x-math"_ns, value)) {
   1320      gfxFontUtils::ParseFontList(value, mathFallbacks);
   1321    }
   1322    if (pfl->GetFontPrefs()->LookupNameList("serif.x-math"_ns, value)) {
   1323      gfxFontUtils::ParseFontList(value, mathFallbacks);
   1324    }
   1325    pfl->Unlock();
   1326    InsertMathFallbacks(font.family.families, mathFallbacks);
   1327 
   1328 #ifdef NOISY_SEARCH
   1329    nsAutoString fontlistStr;
   1330    font.fontlist.ToString(fontlistStr, false, true);
   1331    printf(
   1332        "Searching in " % s " for a glyph of appropriate size for: 0x%04X:%c\n",
   1333        NS_ConvertUTF16toUTF8(fontlistStr).get(), mData[0], mData[0] & 0x00FF);
   1334 #endif
   1335    StretchEnumContext enumData(this, presContext, aDrawTarget,
   1336                                aFontSizeInflation, aStretchDirection,
   1337                                targetSize, aStretchHint, aDesiredStretchSize,
   1338                                font.family.families, glyphFound);
   1339    enumData.mTryParts = !largeopOnly;
   1340 
   1341    for (const StyleSingleFontFamily& name :
   1342         font.family.families.list.AsSpan()) {
   1343      if (StretchEnumContext::EnumCallback(
   1344              name, &enumData, flags,
   1345              mMirroringMethod == MirroringMethod::Glyph)) {
   1346        break;
   1347      }
   1348    }
   1349  }
   1350 
   1351  if (!maxWidth) {
   1352    // Now, we know how we are going to draw the char. Update the member
   1353    // variables accordingly.
   1354    mUnscaledAscent = aDesiredStretchSize.ascent;
   1355  }
   1356 
   1357  if (glyphFound) {
   1358    return NS_OK;
   1359  }
   1360 
   1361  // We did not find a size variant or a glyph assembly to stretch this
   1362  // operator. Verify whether a font with an OpenType MATH table is available
   1363  // and record missing math script otherwise.
   1364  gfxMissingFontRecorder* MFR = presContext->MissingFontRecorder();
   1365  RefPtr<gfxFont> firstMathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
   1366  if (MFR && !firstMathFont) {
   1367    MFR->RecordScript(intl::Script::MATHEMATICAL_NOTATION);
   1368  }
   1369 
   1370  // If the scale_stretchy_operators option is disabled, we are done.
   1371  if (!Preferences::GetBool("mathml.scale_stretchy_operators.enabled", true)) {
   1372    return NS_OK;
   1373  }
   1374 
   1375  // stretchy character
   1376  if (stretchy) {
   1377    if (isVertical) {
   1378      float scale = std::min(
   1379          kMaxScaleFactor,
   1380          float(aContainerSize.ascent + aContainerSize.descent) /
   1381              (aDesiredStretchSize.ascent + aDesiredStretchSize.descent));
   1382      if (!largeop || scale > 1.0) {
   1383        // make the character match the desired height.
   1384        if (!maxWidth) {
   1385          mScaleY *= scale;
   1386        }
   1387        aDesiredStretchSize.ascent *= scale;
   1388        aDesiredStretchSize.descent *= scale;
   1389      }
   1390    } else {
   1391      float scale = std::min(
   1392          kMaxScaleFactor,
   1393          float(aContainerSize.rightBearing - aContainerSize.leftBearing) /
   1394              (aDesiredStretchSize.rightBearing -
   1395               aDesiredStretchSize.leftBearing));
   1396      if (!largeop || scale > 1.0) {
   1397        // make the character match the desired width.
   1398        if (!maxWidth) {
   1399          mScaleX *= scale;
   1400        }
   1401        aDesiredStretchSize.leftBearing *= scale;
   1402        aDesiredStretchSize.rightBearing *= scale;
   1403        aDesiredStretchSize.width *= scale;
   1404      }
   1405    }
   1406  }
   1407 
   1408  // We do not have a char variant for this largeop in display mode, so we
   1409  // apply a scale transform to the base char.
   1410  if (largeop) {
   1411    float scale;
   1412    float largeopFactor = kLargeOpFactor;
   1413 
   1414    // increase the width if it is not largeopFactor times larger
   1415    // than the initial one.
   1416    if ((aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing) <
   1417        largeopFactor * (initialSize.rightBearing - initialSize.leftBearing)) {
   1418      scale =
   1419          (largeopFactor *
   1420           (initialSize.rightBearing - initialSize.leftBearing)) /
   1421          (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing);
   1422      if (!maxWidth) {
   1423        mScaleX *= scale;
   1424      }
   1425      aDesiredStretchSize.leftBearing *= scale;
   1426      aDesiredStretchSize.rightBearing *= scale;
   1427      aDesiredStretchSize.width *= scale;
   1428    }
   1429 
   1430    // increase the height if it is not largeopFactor times larger
   1431    // than the initial one.
   1432    if (nsMathMLOperators::IsIntegralOperator(mData)) {
   1433      // integrals are drawn taller
   1434      largeopFactor = kIntegralFactor;
   1435    }
   1436    if ((aDesiredStretchSize.ascent + aDesiredStretchSize.descent) <
   1437        largeopFactor * (initialSize.ascent + initialSize.descent)) {
   1438      scale = (largeopFactor * (initialSize.ascent + initialSize.descent)) /
   1439              (aDesiredStretchSize.ascent + aDesiredStretchSize.descent);
   1440      if (!maxWidth) {
   1441        mScaleY *= scale;
   1442      }
   1443      aDesiredStretchSize.ascent *= scale;
   1444      aDesiredStretchSize.descent *= scale;
   1445    }
   1446  }
   1447 
   1448  return NS_OK;
   1449 }
   1450 
   1451 nsresult nsMathMLChar::Stretch(nsIFrame* aForFrame, DrawTarget* aDrawTarget,
   1452                               float aFontSizeInflation,
   1453                               nsStretchDirection aStretchDirection,
   1454                               const nsBoundingMetrics& aContainerSize,
   1455                               nsBoundingMetrics& aDesiredStretchSize,
   1456                               uint32_t aStretchHint, bool aRTL) {
   1457  NS_ASSERTION(
   1458      !(aStretchHint & ~(NS_STRETCH_VARIABLE_MASK | NS_STRETCH_LARGEOP)),
   1459      "Unexpected stretch flags");
   1460 
   1461  mDrawingMethod = DrawingMethod::Normal;
   1462  mMirroringMethod = [&] {
   1463    if (!aRTL || !nsMathMLOperators::IsMirrorableOperator(mData)) {
   1464      return MirroringMethod::None;
   1465    }
   1466    if (!StaticPrefs::mathml_rtl_operator_mirroring_enabled()) {
   1467      return MirroringMethod::ScaleFallback;
   1468    }
   1469    // Character level mirroring (always supported)
   1470    if (nsMathMLOperators::GetMirroredOperator(mData) != mData) {
   1471      return MirroringMethod::Character;
   1472    }
   1473    // Glyph level mirroring (needs rtlm feature)
   1474    return MirroringMethod::Glyph;
   1475  }();
   1476  mScaleY = mScaleX = 1.0;
   1477  mDirection = aStretchDirection;
   1478  nsresult rv =
   1479      StretchInternal(aForFrame, aDrawTarget, aFontSizeInflation, mDirection,
   1480                      aContainerSize, aDesiredStretchSize, aStretchHint);
   1481 
   1482  // Record the metrics
   1483  mBoundingMetrics = aDesiredStretchSize;
   1484 
   1485  return rv;
   1486 }
   1487 
   1488 // What happens here is that the StretchInternal algorithm is used but
   1489 // modified by passing the NS_STRETCH_MAXWIDTH stretch hint.  That causes
   1490 // StretchInternal to return horizontal bounding metrics that are the maximum
   1491 // that might be returned from a Stretch.
   1492 //
   1493 // In order to avoid considering widths of some characters in fonts that will
   1494 // not be used for any stretch size, StretchInternal sets the initial height
   1495 // to infinity and looks for any characters smaller than this height.  When a
   1496 // character built from parts is considered, (it will be used by Stretch for
   1497 // any characters greater than its minimum size, so) the height is set to its
   1498 // minimum size, so that only widths of smaller subsequent characters are
   1499 // considered.
   1500 nscoord nsMathMLChar::GetMaxWidth(nsIFrame* aForFrame, DrawTarget* aDrawTarget,
   1501                                  float aFontSizeInflation,
   1502                                  uint32_t aStretchHint) {
   1503  nsBoundingMetrics bm;
   1504  nsStretchDirection direction = NS_STRETCH_DIRECTION_VERTICAL;
   1505  const nsBoundingMetrics container;  // zero target size
   1506 
   1507  StretchInternal(aForFrame, aDrawTarget, aFontSizeInflation, direction,
   1508                  container, bm, aStretchHint | NS_STRETCH_MAXWIDTH);
   1509 
   1510  return std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing);
   1511 }
   1512 
   1513 namespace mozilla {
   1514 
   1515 class nsDisplayMathMLSelectionRect final : public nsPaintedDisplayItem {
   1516 public:
   1517  nsDisplayMathMLSelectionRect(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
   1518                               const nsRect& aRect)
   1519      : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
   1520    MOZ_COUNT_CTOR(nsDisplayMathMLSelectionRect);
   1521  }
   1522 
   1523  MOZ_COUNTED_DTOR_FINAL(nsDisplayMathMLSelectionRect)
   1524 
   1525  virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   1526  NS_DISPLAY_DECL_NAME("MathMLSelectionRect", TYPE_MATHML_SELECTION_RECT)
   1527 private:
   1528  nsRect mRect;
   1529 };
   1530 
   1531 void nsDisplayMathMLSelectionRect::Paint(nsDisplayListBuilder* aBuilder,
   1532                                         gfxContext* aCtx) {
   1533  DrawTarget* drawTarget = aCtx->GetDrawTarget();
   1534  Rect rect = NSRectToSnappedRect(mRect + ToReferenceFrame(),
   1535                                  mFrame->PresContext()->AppUnitsPerDevPixel(),
   1536                                  *drawTarget);
   1537  // get color to use for selection from the look&feel object
   1538  nscolor bgColor = LookAndFeel::Color(LookAndFeel::ColorID::Highlight, mFrame);
   1539  drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(bgColor)));
   1540 }
   1541 
   1542 class nsDisplayMathMLCharForeground final : public nsPaintedDisplayItem {
   1543 public:
   1544  nsDisplayMathMLCharForeground(nsDisplayListBuilder* aBuilder,
   1545                                nsIFrame* aFrame, nsMathMLChar* aChar,
   1546                                const bool aIsSelected)
   1547      : nsPaintedDisplayItem(aBuilder, aFrame),
   1548        mChar(aChar),
   1549        mIsSelected(aIsSelected) {
   1550    MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground);
   1551  }
   1552 
   1553  MOZ_COUNTED_DTOR_FINAL(nsDisplayMathMLCharForeground)
   1554 
   1555  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
   1556                           bool* aSnap) const override {
   1557    *aSnap = false;
   1558    nsRect rect;
   1559    mChar->GetRect(rect);
   1560    nsPoint offset = ToReferenceFrame() + rect.TopLeft();
   1561    nsBoundingMetrics bm;
   1562    mChar->GetBoundingMetrics(bm);
   1563    nsRect temp(offset.x + bm.leftBearing, offset.y,
   1564                bm.rightBearing - bm.leftBearing, bm.ascent + bm.descent);
   1565    // Bug 748220
   1566    temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
   1567    return temp;
   1568  }
   1569 
   1570  virtual void Paint(nsDisplayListBuilder* aBuilder,
   1571                     gfxContext* aCtx) override {
   1572    mChar->PaintForeground(mFrame, *aCtx, ToReferenceFrame(), mIsSelected);
   1573  }
   1574 
   1575  NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND)
   1576 
   1577  virtual nsRect GetComponentAlphaBounds(
   1578      nsDisplayListBuilder* aBuilder) const override {
   1579    bool snap;
   1580    return GetBounds(aBuilder, &snap);
   1581  }
   1582 
   1583 private:
   1584  nsMathMLChar* mChar;
   1585  bool mIsSelected;
   1586 };
   1587 
   1588 #ifdef DEBUG
   1589 class nsDisplayMathMLCharDebug final : public nsPaintedDisplayItem {
   1590 public:
   1591  nsDisplayMathMLCharDebug(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
   1592                           const nsRect& aRect)
   1593      : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
   1594    MOZ_COUNT_CTOR(nsDisplayMathMLCharDebug);
   1595  }
   1596 
   1597  MOZ_COUNTED_DTOR_FINAL(nsDisplayMathMLCharDebug)
   1598 
   1599  virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   1600  NS_DISPLAY_DECL_NAME("MathMLCharDebug", TYPE_MATHML_CHAR_DEBUG)
   1601 
   1602 private:
   1603  nsRect mRect;
   1604 };
   1605 
   1606 void nsDisplayMathMLCharDebug::Paint(nsDisplayListBuilder* aBuilder,
   1607                                     gfxContext* aCtx) {
   1608  // for visual debug
   1609  Sides skipSides;
   1610  nsPresContext* presContext = mFrame->PresContext();
   1611  ComputedStyle* computedStyle = mFrame->Style();
   1612  nsRect rect = mRect + ToReferenceFrame();
   1613 
   1614  PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
   1615                               ? PaintBorderFlags::SyncDecodeImages
   1616                               : PaintBorderFlags();
   1617 
   1618  // Since this is used only for debugging, we don't need to worry about
   1619  // tracking the ImgDrawResult.
   1620  (void)nsCSSRendering::PaintBorder(presContext, *aCtx, mFrame,
   1621                                    GetPaintRect(aBuilder, aCtx), rect,
   1622                                    computedStyle, flags, skipSides);
   1623 
   1624  nsCSSRendering::PaintNonThemedOutline(presContext, *aCtx, mFrame,
   1625                                        GetPaintRect(aBuilder, aCtx), rect,
   1626                                        computedStyle);
   1627 }
   1628 #endif
   1629 
   1630 }  // namespace mozilla
   1631 
   1632 void nsMathMLChar::Display(nsDisplayListBuilder* aBuilder, nsIFrame* aForFrame,
   1633                           const nsDisplayListSet& aLists, uint32_t aIndex,
   1634                           const nsRect* aSelectedRect) {
   1635  ComputedStyle* computedStyle = mComputedStyle;
   1636  if (!computedStyle->StyleVisibility()->IsVisible()) {
   1637    return;
   1638  }
   1639 
   1640  const bool isSelected = aSelectedRect && !aSelectedRect->IsEmpty();
   1641 
   1642  if (isSelected) {
   1643    aLists.BorderBackground()->AppendNewToTop<nsDisplayMathMLSelectionRect>(
   1644        aBuilder, aForFrame, *aSelectedRect);
   1645  }
   1646  aLists.Content()->AppendNewToTopWithIndex<nsDisplayMathMLCharForeground>(
   1647      aBuilder, aForFrame, aIndex, this, isSelected);
   1648 }
   1649 
   1650 void nsMathMLChar::ApplyTransforms(gfxContext* aThebesContext,
   1651                                   int32_t aAppUnitsPerGfxUnit, nsRect& r) {
   1652  // apply the transforms
   1653  nsPoint pt =
   1654      (mMirroringMethod != MirroringMethod::None) ? r.TopRight() : r.TopLeft();
   1655  gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit),
   1656                          NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit));
   1657  aThebesContext->SetMatrixDouble(
   1658      aThebesContext->CurrentMatrixDouble()
   1659          .PreTranslate(devPixelOffset)
   1660          .PreScale(
   1661              mScaleX *
   1662                  (mMirroringMethod == MirroringMethod::ScaleFallback ? -1 : 1),
   1663              mScaleY));
   1664 
   1665  // update the bounding rectangle.
   1666  r.x = r.y = 0;
   1667  r.width /= mScaleX;
   1668  r.height /= mScaleY;
   1669 }
   1670 
   1671 void nsMathMLChar::PaintForeground(nsIFrame* aForFrame,
   1672                                   gfxContext& aRenderingContext, nsPoint aPt,
   1673                                   bool aIsSelected) {
   1674  ComputedStyle* computedStyle = mComputedStyle;
   1675  nsPresContext* presContext = aForFrame->PresContext();
   1676 
   1677  if (mDrawingMethod == DrawingMethod::Normal) {
   1678    // normal drawing if there is nothing special about this char
   1679    // Use our parent element's style
   1680    computedStyle = aForFrame->Style();
   1681  }
   1682 
   1683  // Set color ...
   1684  nscolor fgColor = computedStyle->GetVisitedDependentColor(
   1685      &nsStyleText::mWebkitTextFillColor);
   1686  if (aIsSelected) {
   1687    // get color to use for selection from the look&feel object
   1688    fgColor = LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext, aForFrame,
   1689                                 fgColor);
   1690  }
   1691  aRenderingContext.SetColor(sRGBColor::FromABGR(fgColor));
   1692  aRenderingContext.Save();
   1693  nsRect r = mRect + aPt;
   1694  ApplyTransforms(&aRenderingContext,
   1695                  aForFrame->PresContext()->AppUnitsPerDevPixel(), r);
   1696 
   1697  switch (mDrawingMethod) {
   1698    case DrawingMethod::Normal:
   1699    case DrawingMethod::Variant:
   1700      // draw a single glyph (base size or size variant)
   1701      // XXXfredw verify if mGlyphs[0] is non-null to workaround bug 973322.
   1702      if (mGlyphs[0]) {
   1703        mGlyphs[0]->Draw(Range(mGlyphs[0].get()),
   1704                         gfx::Point(0.0, mUnscaledAscent),
   1705                         gfxTextRun::DrawParams(
   1706                             &aRenderingContext,
   1707                             aForFrame->PresContext()->FontPaletteCache()));
   1708      }
   1709      break;
   1710    case DrawingMethod::Parts: {
   1711      // paint by parts
   1712      if (NS_STRETCH_DIRECTION_VERTICAL == mDirection) {
   1713        PaintVertically(presContext, &aRenderingContext, r, fgColor);
   1714      } else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection) {
   1715        PaintHorizontally(presContext, &aRenderingContext, r, fgColor);
   1716      }
   1717      break;
   1718    }
   1719    default:
   1720      MOZ_ASSERT_UNREACHABLE("Unknown drawing method");
   1721      break;
   1722  }
   1723 
   1724  aRenderingContext.Restore();
   1725 }
   1726 
   1727 /* =============================================================================
   1728  Helper routines that actually do the job of painting the char by parts
   1729 */
   1730 
   1731 class AutoPushClipRect {
   1732  gfxContext* mThebesContext;
   1733 
   1734 public:
   1735  AutoPushClipRect(gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit,
   1736                   const nsRect& aRect)
   1737      : mThebesContext(aThebesContext) {
   1738    mThebesContext->Save();
   1739    gfxRect clip = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit);
   1740    mThebesContext->SnappedClip(clip);
   1741  }
   1742  ~AutoPushClipRect() { mThebesContext->Restore(); }
   1743 };
   1744 
   1745 static nsPoint SnapToDevPixels(const gfxContext* aThebesContext,
   1746                               int32_t aAppUnitsPerGfxUnit,
   1747                               const nsPoint& aPt) {
   1748  gfxPoint pt(NSAppUnitsToFloatPixels(aPt.x, aAppUnitsPerGfxUnit),
   1749              NSAppUnitsToFloatPixels(aPt.y, aAppUnitsPerGfxUnit));
   1750  pt = aThebesContext->UserToDevice(pt);
   1751  pt.Round();
   1752  pt = aThebesContext->DeviceToUser(pt);
   1753  return nsPoint(NSFloatPixelsToAppUnits(pt.x, aAppUnitsPerGfxUnit),
   1754                 NSFloatPixelsToAppUnits(pt.y, aAppUnitsPerGfxUnit));
   1755 }
   1756 
   1757 static void PaintRule(DrawTarget& aDrawTarget, int32_t aAppUnitsPerGfxUnit,
   1758                      nsRect& aRect, nscolor aColor) {
   1759  Rect rect = NSRectToSnappedRect(aRect, aAppUnitsPerGfxUnit, aDrawTarget);
   1760  ColorPattern color(ToDeviceColor(aColor));
   1761  aDrawTarget.FillRect(rect, color);
   1762 }
   1763 
   1764 // paint a stretchy char by assembling glyphs vertically
   1765 nsresult nsMathMLChar::PaintVertically(nsPresContext* aPresContext,
   1766                                       gfxContext* aThebesContext,
   1767                                       nsRect& aRect, nscolor aColor) {
   1768  DrawTarget& aDrawTarget = *aThebesContext->GetDrawTarget();
   1769 
   1770  // Get the device pixel size in the vertical direction.
   1771  // (This makes no effort to optimize for non-translation transformations.)
   1772  nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
   1773 
   1774  // get metrics data to be re-used later
   1775  int32_t i = 0;
   1776  nscoord dx = aRect.x;
   1777  nscoord offset[3], start[3], end[3];
   1778  for (i = 0; i <= 2; ++i) {
   1779    const nsBoundingMetrics& bm = mBmData[i];
   1780    nscoord dy;
   1781    if (0 == i) {  // top
   1782      dy = aRect.y + bm.ascent;
   1783    } else if (2 == i) {  // bottom
   1784      dy = aRect.y + aRect.height - bm.descent;
   1785    } else {  // middle
   1786      dy = aRect.y + bm.ascent + (aRect.height - (bm.ascent + bm.descent)) / 2;
   1787    }
   1788    // _cairo_scaled_font_show_glyphs snaps origins to device pixels.
   1789    // Do this now so that we can get the other dimensions right.
   1790    // (This may not achieve much with non-rectangular transformations.)
   1791    dy = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).y;
   1792    // abcissa passed to Draw
   1793    offset[i] = dy;
   1794    // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest
   1795    // pixel, so the bm values can include 1 row of faint pixels on each edge.
   1796    // Don't rely on this pixel as it can look like a gap.
   1797    if (bm.ascent + bm.descent >= 2 * oneDevPixel) {
   1798      start[i] = dy - bm.ascent + oneDevPixel;  // top join
   1799      end[i] = dy + bm.descent - oneDevPixel;   // bottom join
   1800    } else {
   1801      // To avoid overlaps, we don't add one pixel on each side when the part
   1802      // is too small.
   1803      start[i] = dy - bm.ascent;  // top join
   1804      end[i] = dy + bm.descent;   // bottom join
   1805    }
   1806  }
   1807 
   1808  // If there are overlaps, then join at the mid point
   1809  for (i = 0; i < 2; ++i) {
   1810    if (end[i] > start[i + 1]) {
   1811      end[i] = (end[i] + start[i + 1]) / 2;
   1812      start[i + 1] = end[i];
   1813    }
   1814  }
   1815 
   1816  nsRect unionRect = aRect;
   1817  unionRect.x += mBoundingMetrics.leftBearing;
   1818  unionRect.width =
   1819      mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
   1820  // RTL characters have the origin on the other side. To correctly display them
   1821  // we need to shift the X coordinate by the width.
   1822  if (mMirroringMethod == MirroringMethod::Glyph ||
   1823      mMirroringMethod == MirroringMethod::Character) {
   1824    unionRect.x -= unionRect.width;
   1825  }
   1826  unionRect.Inflate(oneDevPixel);
   1827 
   1828  gfxTextRun::DrawParams params(aThebesContext,
   1829                                aPresContext->FontPaletteCache());
   1830 
   1831  /////////////////////////////////////
   1832  // draw top, middle, bottom
   1833  for (i = 0; i <= 2; ++i) {
   1834    // glue can be null
   1835    if (mGlyphs[i]) {
   1836      nscoord dy = offset[i];
   1837      // Draw a glyph in a clipped area so that we don't have hairy chars
   1838      // pending outside
   1839      nsRect clipRect = unionRect;
   1840      // Clip at the join to get a solid edge (without overlap or gap), when
   1841      // this won't change the glyph too much.  If the glyph is too small to
   1842      // clip then we'll overlap rather than have a gap.
   1843      nscoord height = mBmData[i].ascent + mBmData[i].descent;
   1844      if (height * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) {
   1845        if (0 == i) {  // top
   1846          clipRect.height = end[i] - clipRect.y;
   1847        } else if (2 == i) {  // bottom
   1848          clipRect.height -= start[i] - clipRect.y;
   1849          clipRect.y = start[i];
   1850        } else {  // middle
   1851          clipRect.y = start[i];
   1852          clipRect.height = end[i] - start[i];
   1853        }
   1854      }
   1855      if (!clipRect.IsEmpty()) {
   1856        AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
   1857        mGlyphs[i]->Draw(Range(mGlyphs[i].get()), gfx::Point(dx, dy), params);
   1858      }
   1859    }
   1860  }
   1861 
   1862  ///////////////
   1863  // fill the gap between top and middle, and between middle and bottom.
   1864  if (!mGlyphs[3]) {  // null glue : draw a rule
   1865    // figure out the dimensions of the rule to be drawn :
   1866    // set lbearing to rightmost lbearing among the two current successive
   1867    // parts.
   1868    // set rbearing to leftmost rbearing among the two current successive parts.
   1869    // this not only satisfies the convention used for over/underbraces
   1870    // in TeX, but also takes care of broken fonts like the stretchy integral
   1871    // in Symbol for small font sizes in unix.
   1872    nscoord lbearing, rbearing;
   1873    int32_t first = 0, last = 1;
   1874    while (last <= 2) {
   1875      if (mGlyphs[last]) {
   1876        lbearing = mBmData[last].leftBearing;
   1877        rbearing = mBmData[last].rightBearing;
   1878        if (mGlyphs[first]) {
   1879          if (lbearing < mBmData[first].leftBearing) {
   1880            lbearing = mBmData[first].leftBearing;
   1881          }
   1882          if (rbearing > mBmData[first].rightBearing) {
   1883            rbearing = mBmData[first].rightBearing;
   1884          }
   1885        }
   1886      } else if (mGlyphs[first]) {
   1887        lbearing = mBmData[first].leftBearing;
   1888        rbearing = mBmData[first].rightBearing;
   1889      } else {
   1890        NS_ERROR("Cannot stretch - All parts missing");
   1891        return NS_ERROR_UNEXPECTED;
   1892      }
   1893      // paint the rule between the parts
   1894      nsRect rule(aRect.x + lbearing, end[first], rbearing - lbearing,
   1895                  start[last] - end[first]);
   1896      PaintRule(aDrawTarget, oneDevPixel, rule, aColor);
   1897      first = last;
   1898      last++;
   1899    }
   1900  } else if (mBmData[3].ascent + mBmData[3].descent > 0) {
   1901    // glue is present
   1902    nsBoundingMetrics& bm = mBmData[3];
   1903    // Ensure the stride for the glue is not reduced to less than one pixel
   1904    if (bm.ascent + bm.descent >= 3 * oneDevPixel) {
   1905      // To protect against gaps, pretend the glue is smaller than it is,
   1906      // in order to trim off ends and thus get a solid edge for the join.
   1907      bm.ascent -= oneDevPixel;
   1908      bm.descent -= oneDevPixel;
   1909    }
   1910 
   1911    nsRect clipRect = unionRect;
   1912 
   1913    for (i = 0; i < 2; ++i) {
   1914      // Make sure not to draw outside the character
   1915      nscoord dy = std::max(end[i], aRect.y);
   1916      nscoord fillEnd = std::min(start[i + 1], aRect.YMost());
   1917      while (dy < fillEnd) {
   1918        clipRect.y = dy;
   1919        clipRect.height = std::min(bm.ascent + bm.descent, fillEnd - dy);
   1920        AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
   1921        dy += bm.ascent;
   1922        mGlyphs[3]->Draw(Range(mGlyphs[3].get()), gfx::Point(dx, dy), params);
   1923        dy += bm.descent;
   1924      }
   1925    }
   1926  }
   1927 #ifdef DEBUG
   1928  else {
   1929    for (i = 0; i < 2; ++i) {
   1930      NS_ASSERTION(end[i] >= start[i + 1],
   1931                   "gap between parts with missing glue glyph");
   1932    }
   1933  }
   1934 #endif
   1935  return NS_OK;
   1936 }
   1937 
   1938 // paint a stretchy char by assembling glyphs horizontally
   1939 nsresult nsMathMLChar::PaintHorizontally(nsPresContext* aPresContext,
   1940                                         gfxContext* aThebesContext,
   1941                                         nsRect& aRect, nscolor aColor) {
   1942  DrawTarget& aDrawTarget = *aThebesContext->GetDrawTarget();
   1943 
   1944  // Get the device pixel size in the horizontal direction.
   1945  // (This makes no effort to optimize for non-translation transformations.)
   1946  nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
   1947 
   1948  // get metrics data to be re-used later
   1949  int32_t i = 0;
   1950  nscoord dy = aRect.y + mBoundingMetrics.ascent;
   1951  nscoord offset[3], start[3], end[3];
   1952  for (i = 0; i <= 2; ++i) {
   1953    const nsBoundingMetrics& bm = mBmData[i];
   1954    nscoord dx;
   1955    if (0 == i) {  // left
   1956      dx = aRect.x - bm.leftBearing;
   1957    } else if (2 == i) {  // right
   1958      dx = aRect.x + aRect.width - bm.rightBearing;
   1959    } else {  // middle
   1960      dx = aRect.x + (aRect.width - bm.width) / 2;
   1961    }
   1962    // _cairo_scaled_font_show_glyphs snaps origins to device pixels.
   1963    // Do this now so that we can get the other dimensions right.
   1964    // (This may not achieve much with non-rectangular transformations.)
   1965    dx = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).x;
   1966    // abcissa passed to Draw
   1967    offset[i] = dx;
   1968    // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest
   1969    // pixel, so the bm values can include 1 row of faint pixels on each edge.
   1970    // Don't rely on this pixel as it can look like a gap.
   1971    if (bm.rightBearing - bm.leftBearing >= 2 * oneDevPixel) {
   1972      start[i] = dx + bm.leftBearing + oneDevPixel;  // left join
   1973      end[i] = dx + bm.rightBearing - oneDevPixel;   // right join
   1974    } else {
   1975      // To avoid overlaps, we don't add one pixel on each side when the part
   1976      // is too small.
   1977      start[i] = dx + bm.leftBearing;  // left join
   1978      end[i] = dx + bm.rightBearing;   // right join
   1979    }
   1980  }
   1981 
   1982  // If there are overlaps, then join at the mid point
   1983  for (i = 0; i < 2; ++i) {
   1984    if (end[i] > start[i + 1]) {
   1985      end[i] = (end[i] + start[i + 1]) / 2;
   1986      start[i + 1] = end[i];
   1987    }
   1988  }
   1989 
   1990  nsRect unionRect = aRect;
   1991  unionRect.Inflate(oneDevPixel);
   1992 
   1993  gfxTextRun::DrawParams params(aThebesContext,
   1994                                aPresContext->FontPaletteCache());
   1995 
   1996  ///////////////////////////
   1997  // draw left, middle, right
   1998  for (i = 0; i <= 2; ++i) {
   1999    // glue can be null
   2000    if (mGlyphs[i]) {
   2001      nscoord dx = offset[i];
   2002      nsRect clipRect = unionRect;
   2003      // Clip at the join to get a solid edge (without overlap or gap), when
   2004      // this won't change the glyph too much.  If the glyph is too small to
   2005      // clip then we'll overlap rather than have a gap.
   2006      nscoord width = mBmData[i].rightBearing - mBmData[i].leftBearing;
   2007      if (width * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) {
   2008        if (0 == i) {  // left
   2009          clipRect.width = end[i] - clipRect.x;
   2010        } else if (2 == i) {  // right
   2011          clipRect.width -= start[i] - clipRect.x;
   2012          clipRect.x = start[i];
   2013        } else {  // middle
   2014          clipRect.x = start[i];
   2015          clipRect.width = end[i] - start[i];
   2016        }
   2017      }
   2018      if (!clipRect.IsEmpty()) {
   2019        AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
   2020        mGlyphs[i]->Draw(Range(mGlyphs[i].get()), gfx::Point(dx, dy), params);
   2021      }
   2022    }
   2023  }
   2024 
   2025  ////////////////
   2026  // fill the gap between left and middle, and between middle and right.
   2027  if (!mGlyphs[3]) {  // null glue : draw a rule
   2028    // figure out the dimensions of the rule to be drawn :
   2029    // set ascent to lowest ascent among the two current successive parts.
   2030    // set descent to highest descent among the two current successive parts.
   2031    // this satisfies the convention used for over/underbraces, and helps
   2032    // fix broken fonts.
   2033    nscoord ascent, descent;
   2034    int32_t first = 0, last = 1;
   2035    while (last <= 2) {
   2036      if (mGlyphs[last]) {
   2037        ascent = mBmData[last].ascent;
   2038        descent = mBmData[last].descent;
   2039        if (mGlyphs[first]) {
   2040          if (ascent > mBmData[first].ascent) {
   2041            ascent = mBmData[first].ascent;
   2042          }
   2043          if (descent > mBmData[first].descent) {
   2044            descent = mBmData[first].descent;
   2045          }
   2046        }
   2047      } else if (mGlyphs[first]) {
   2048        ascent = mBmData[first].ascent;
   2049        descent = mBmData[first].descent;
   2050      } else {
   2051        NS_ERROR("Cannot stretch - All parts missing");
   2052        return NS_ERROR_UNEXPECTED;
   2053      }
   2054      // paint the rule between the parts
   2055      nsRect rule(end[first], dy - ascent, start[last] - end[first],
   2056                  ascent + descent);
   2057      PaintRule(aDrawTarget, oneDevPixel, rule, aColor);
   2058      first = last;
   2059      last++;
   2060    }
   2061  } else if (mBmData[3].rightBearing - mBmData[3].leftBearing > 0) {
   2062    // glue is present
   2063    nsBoundingMetrics& bm = mBmData[3];
   2064    // Ensure the stride for the glue is not reduced to less than one pixel
   2065    if (bm.rightBearing - bm.leftBearing >= 3 * oneDevPixel) {
   2066      // To protect against gaps, pretend the glue is smaller than it is,
   2067      // in order to trim off ends and thus get a solid edge for the join.
   2068      bm.leftBearing += oneDevPixel;
   2069      bm.rightBearing -= oneDevPixel;
   2070    }
   2071 
   2072    nsRect clipRect = unionRect;
   2073 
   2074    for (i = 0; i < 2; ++i) {
   2075      // Make sure not to draw outside the character
   2076      nscoord dx = std::max(end[i], aRect.x);
   2077      nscoord fillEnd = std::min(start[i + 1], aRect.XMost());
   2078      while (dx < fillEnd) {
   2079        clipRect.x = dx;
   2080        clipRect.width =
   2081            std::min(bm.rightBearing - bm.leftBearing, fillEnd - dx);
   2082        AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
   2083        dx -= bm.leftBearing;
   2084        mGlyphs[3]->Draw(Range(mGlyphs[3].get()), gfx::Point(dx, dy), params);
   2085        dx += bm.rightBearing;
   2086      }
   2087    }
   2088  }
   2089 #ifdef DEBUG
   2090  else {  // no glue
   2091    for (i = 0; i < 2; ++i) {
   2092      NS_ASSERTION(end[i] >= start[i + 1],
   2093                   "gap between parts with missing glue glyph");
   2094    }
   2095  }
   2096 #endif
   2097  return NS_OK;
   2098 }