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 ¶ms, 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 }