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