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