Bug 785860 - fix sts preload list tests to skip private mode tests if private browsin...
[gecko.git] / layout / mathml / nsMathMLChar.cpp
bloba3f0fc1c26eea2f968a4215bd13bc193b1efedd3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsCOMPtr.h"
7 #include "nsFrame.h"
8 #include "nsPresContext.h"
9 #include "nsStyleContext.h"
10 #include "nsStyleConsts.h"
11 #include "nsString.h"
12 #include "nsUnicharUtils.h"
13 #include "nsRenderingContext.h"
14 #include "gfxPlatform.h"
16 #include "mozilla/Preferences.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsIComponentManager.h"
19 #include "nsIPersistentProperties2.h"
20 #include "nsIServiceManager.h"
21 #include "nsIObserverService.h"
22 #include "nsIObserver.h"
23 #include "nsNetUtil.h"
25 #include "mozilla/LookAndFeel.h"
26 #include "nsCSSRendering.h"
27 #include "prprf.h" // For PR_snprintf()
29 #include "nsDisplayList.h"
31 #include "nsMathMLOperators.h"
32 #include "nsMathMLChar.h"
34 using namespace mozilla;
36 //#define NOISY_SEARCH 1
38 // -----------------------------------------------------------------------------
39 static const PRUnichar kSpaceCh = PRUnichar(' ');
40 static const nsGlyphCode kNullGlyph = {{0, 0}, 0};
41 typedef enum {eExtension_base, eExtension_variants, eExtension_parts}
42 nsMathfontPrefExtension;
44 // -----------------------------------------------------------------------------
45 // nsGlyphTable is a class that provides an interface for accessing glyphs
46 // of stretchy chars. It acts like a table that stores the variants of bigger
47 // sizes (if any) and the partial glyphs needed to build extensible symbols.
48 // An instance of nsGlyphTable is associated to one primary font. Extra glyphs
49 // can be taken in other additional fonts when stretching certain characters.
50 // These supplementary fonts are referred to as "external" fonts to the table.
52 // A char for which nsGlyphTable::Has(aChar) is true means that the table
53 // contains some glyphs (bigger and/or partial) that can be used to render
54 // the char. Bigger sizes (if any) of the char can then be retrieved with
55 // BigOf(aSize). Partial glyphs can be retrieved with TopOf(), GlueOf(), etc.
57 // A table consists of "nsGlyphCode"s which are viewed either as Unicode
58 // points or as direct glyph indices, depending on the type of the table.
59 // XXX The latter is not yet supported.
61 // General format of MathFont Property Files from which glyph data are
62 // retrieved:
63 // -----------------------------------------------------------------------------
64 // Each font should have its set of glyph data. For example, the glyph data for
65 // the "Symbol" font and the "MT Extra" font are in "mathfontSymbol.properties"
66 // and "mathfontMTExtra.properties", respectively. The mathfont property file
67 // is a set of all the stretchy MathML characters that can be rendered with that
68 // font using larger and/or partial glyphs. The entry of each stretchy character
69 // in the mathfont property file gives, in that order, the 4 partial glyphs:
70 // Top (or Left), Middle, Bottom (or Right), Glue; and the variants of bigger
71 // sizes (if any).
72 // A position that is not relevant to a particular character is indicated there
73 // with the UNICODE REPLACEMENT CHARACTER 0xFFFD.
74 // -----------------------------------------------------------------------------
76 #define NS_TABLE_TYPE_UNICODE 0
77 #define NS_TABLE_TYPE_GLYPH_INDEX 1
79 #define NS_TABLE_STATE_ERROR -1
80 #define NS_TABLE_STATE_EMPTY 0
81 #define NS_TABLE_STATE_READY 1
83 // helper to trim off comments from data in a MathFont Property File
84 static void
85 Clean(nsString& aValue)
87 // chop the trailing # comment portion if any ...
88 int32_t comment = aValue.RFindChar('#');
89 if (comment > 0) aValue.Truncate(comment);
90 aValue.CompressWhitespace();
93 // helper to load a MathFont Property File
94 static nsresult
95 LoadProperties(const nsString& aName,
96 nsCOMPtr<nsIPersistentProperties>& aProperties)
98 nsAutoString uriStr;
99 uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
100 uriStr.Append(aName);
101 uriStr.StripWhitespace(); // that may come from aName
102 uriStr.AppendLiteral(".properties");
103 return NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(aProperties),
104 NS_ConvertUTF16toUTF8(uriStr));
107 // -----------------------------------------------------------------------------
109 class nsGlyphTable {
110 public:
111 explicit nsGlyphTable(const nsString& aPrimaryFontName)
112 : mType(NS_TABLE_TYPE_UNICODE),
113 mFontName(1), // ensure space for primary font name.
114 mState(NS_TABLE_STATE_EMPTY),
115 mCharCache(0)
117 MOZ_COUNT_CTOR(nsGlyphTable);
118 mFontName.AppendElement(aPrimaryFontName);
121 // not a virtual destructor: this class is not intended to be subclassed
122 ~nsGlyphTable()
124 MOZ_COUNT_DTOR(nsGlyphTable);
127 const nsAString& PrimaryFontName() const
129 return mFontName[0];
132 const nsAString& FontNameFor(const nsGlyphCode& aGlyphCode) const
134 return mFontName[aGlyphCode.font];
137 // True if this table contains some glyphs (variants and/or parts)
138 // or contains child chars that can be used to render this char
139 bool Has(nsPresContext* aPresContext, nsMathMLChar* aChar);
141 // True if this table contains variants of larger sizes to render this char
142 bool HasVariantsOf(nsPresContext* aPresContext, nsMathMLChar* aChar);
144 // True if this table contains parts to render this char
145 bool HasPartsOf(nsPresContext* aPresContext, nsMathMLChar* aChar);
147 // Getters for the parts
148 nsGlyphCode TopOf(nsPresContext* aPresContext, nsMathMLChar* aChar) {
149 return ElementAt(aPresContext, aChar, 0);
151 nsGlyphCode MiddleOf(nsPresContext* aPresContext, nsMathMLChar* aChar) {
152 return ElementAt(aPresContext, aChar, 1);
154 nsGlyphCode BottomOf(nsPresContext* aPresContext, nsMathMLChar* aChar) {
155 return ElementAt(aPresContext, aChar, 2);
157 nsGlyphCode GlueOf(nsPresContext* aPresContext, nsMathMLChar* aChar) {
158 return ElementAt(aPresContext, aChar, 3);
160 nsGlyphCode BigOf(nsPresContext* aPresContext, nsMathMLChar* aChar,
161 int32_t aSize) {
162 return ElementAt(aPresContext, aChar, 4 + aSize);
164 nsGlyphCode LeftOf(nsPresContext* aPresContext, nsMathMLChar* aChar) {
165 return ElementAt(aPresContext, aChar, 0);
167 nsGlyphCode RightOf(nsPresContext* aPresContext, nsMathMLChar* aChar) {
168 return ElementAt(aPresContext, aChar, 2);
171 private:
172 nsGlyphCode ElementAt(nsPresContext* aPresContext, nsMathMLChar* aChar,
173 uint32_t aPosition);
175 // The type is either NS_TABLE_TYPE_UNICODE or NS_TABLE_TYPE_GLYPH_INDEX
176 int32_t mType;
178 // mFontName[0] is the primary font associated to this table. The others
179 // are possible "external" fonts for glyphs not in the primary font
180 // but which are needed to stretch certain characters in the table
181 nsTArray<nsString> mFontName;
183 // Tri-state variable for error/empty/ready
184 int32_t mState;
186 // The set of glyph data in this table, as provided by the MathFont Property
187 // File
188 nsCOMPtr<nsIPersistentProperties> mGlyphProperties;
190 // For speedy re-use, we always cache the last data used in the table.
191 // mCharCache is the Unicode point of the last char that was queried in this
192 // table. mGlyphCache is a buffer containing the glyph data associated to
193 // that char. For a property line 'key = value' in the MathFont Property File,
194 // mCharCache will retain the 'key' -- which is a Unicode point, while
195 // mGlyphCache will retain the 'value', which is a consecutive list of
196 // nsGlyphCodes, i.e., the pairs of 'code@font' needed by the char -- in
197 // which 'code@0' can be specified
198 // without the optional '@0'. However, to ease subsequent processing,
199 // mGlyphCache excludes the '@' symbol and explicitly inserts all optional '0'
200 // that indicates the primary font identifier. Specifically therefore, the
201 // k-th glyph is characterized by :
202 // 1) mGlyphCache[3*k],mGlyphCache[3*k+1] : its Unicode point (or glyph index
203 // -- depending on mType),
204 // 2) mGlyphCache[3*k+2] : the numeric identifier of the font where it comes
205 // from.
206 // A font identifier of '0' means the default primary font associated to this
207 // table. Other digits map to the "external" fonts that may have been
208 // specified in the MathFont Property File.
209 nsString mGlyphCache;
210 PRUnichar mCharCache;
213 nsGlyphCode
214 nsGlyphTable::ElementAt(nsPresContext* aPresContext, nsMathMLChar* aChar,
215 uint32_t aPosition)
217 if (mState == NS_TABLE_STATE_ERROR) return kNullGlyph;
218 // Load glyph properties if this is the first time we have been here
219 if (mState == NS_TABLE_STATE_EMPTY) {
220 nsresult rv = LoadProperties(mFontName[0], mGlyphProperties);
221 #ifdef DEBUG
222 nsCAutoString uriStr;
223 uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
224 LossyAppendUTF16toASCII(mFontName[0], uriStr);
225 uriStr.StripWhitespace(); // that may come from mFontName
226 uriStr.AppendLiteral(".properties");
227 printf("Loading %s ... %s\n",
228 uriStr.get(),
229 (NS_FAILED(rv)) ? "Failed" : "Done");
230 #endif
231 if (NS_FAILED(rv)) {
232 mState = NS_TABLE_STATE_ERROR; // never waste time with this table again
233 return kNullGlyph;
235 mState = NS_TABLE_STATE_READY;
237 // see if there are external fonts needed for certain chars in this table
238 nsCAutoString key;
239 nsAutoString value;
240 for (int32_t i = 1; ; i++) {
241 key.AssignLiteral("external.");
242 key.AppendInt(i, 10);
243 rv = mGlyphProperties->GetStringProperty(key, value);
244 if (NS_FAILED(rv)) break;
245 Clean(value);
246 mFontName.AppendElement(value); // i.e., mFontName[i] holds this font name
250 // Update our cache if it is not associated to this character
251 PRUnichar uchar = aChar->mData[0];
252 if (mCharCache != uchar) {
253 // The key in the property file is interpreted as ASCII and kept
254 // as such ...
255 char key[10]; PR_snprintf(key, sizeof(key), "\\u%04X", uchar);
256 nsAutoString value;
257 nsresult rv = mGlyphProperties->GetStringProperty(nsDependentCString(key),
258 value);
259 if (NS_FAILED(rv)) return kNullGlyph;
260 Clean(value);
261 // See if this char uses external fonts; e.g., if the 2nd glyph is taken
262 // from the external font '1', the property line looks like
263 // \uNNNN = \uNNNN\uNNNN@1\uNNNN.
264 // This is where mGlyphCache is pre-processed to explicitly store all glyph
265 // codes as combined pairs of 'code@font', excluding the '@' separator. This
266 // means that mGlyphCache[3*k],mGlyphCache[3*k+1] will later be rendered
267 // with mFontName[mGlyphCache[3*k+2]]
268 // Note: font identifier is internally an ASCII digit to avoid the null
269 // char issue
270 nsAutoString buffer;
271 int32_t length = value.Length();
272 int32_t i = 0; // index in value
273 while (i < length) {
274 PRUnichar code = value[i];
275 ++i;
276 buffer.Append(code);
277 // Read the next word if we have a non-BMP character.
278 if (i < length && NS_IS_HIGH_SURROGATE(code)) {
279 code = value[i];
280 ++i;
281 } else {
282 code = PRUnichar('\0');
284 buffer.Append(code);
286 // See if an external font is needed for the code point.
287 // Limit of 9 external fonts
288 PRUnichar font = 0;
289 if (i+1 < length && value[i] == PRUnichar('@') &&
290 value[i+1] >= PRUnichar('0') && value[i+1] <= PRUnichar('9')) {
291 ++i;
292 font = value[i] - '0';
293 ++i;
294 if (font >= mFontName.Length()) {
295 NS_ERROR("Nonexistent font referenced in glyph table");
296 return kNullGlyph;
298 // The char cannot be handled if this font is not installed
299 if (!mFontName[font].Length()) {
300 return kNullGlyph;
303 buffer.Append(font);
305 // update our cache with the new settings
306 mGlyphCache.Assign(buffer);
307 mCharCache = uchar;
310 // 3* is to account for the code@font pairs
311 uint32_t index = 3*aPosition;
312 if (index+2 >= mGlyphCache.Length()) return kNullGlyph;
313 nsGlyphCode ch;
314 ch.code[0] = mGlyphCache.CharAt(index);
315 ch.code[1] = mGlyphCache.CharAt(index + 1);
316 ch.font = mGlyphCache.CharAt(index + 2);
317 return ch.code[0] == PRUnichar(0xFFFD) ? kNullGlyph : ch;
320 bool
321 nsGlyphTable::Has(nsPresContext* aPresContext, nsMathMLChar* aChar)
323 return HasVariantsOf(aPresContext, aChar) || HasPartsOf(aPresContext, aChar);
326 bool
327 nsGlyphTable::HasVariantsOf(nsPresContext* aPresContext, nsMathMLChar* aChar)
329 //XXXkt all variants must be in the same file as size 1
330 return BigOf(aPresContext, aChar, 1).Exists();
333 bool
334 nsGlyphTable::HasPartsOf(nsPresContext* aPresContext, nsMathMLChar* aChar)
336 return GlueOf(aPresContext, aChar).Exists() ||
337 TopOf(aPresContext, aChar).Exists() ||
338 BottomOf(aPresContext, aChar).Exists() ||
339 MiddleOf(aPresContext, aChar).Exists();
342 // -----------------------------------------------------------------------------
343 // This is the list of all the applicable glyph tables.
344 // We will maintain a single global instance that will only reveal those
345 // glyph tables that are associated to fonts currently installed on the
346 // user' system. The class is an XPCOM shutdown observer to allow us to
347 // free its allocated data at shutdown
349 class nsGlyphTableList : public nsIObserver
351 public:
352 NS_DECL_ISUPPORTS
353 NS_DECL_NSIOBSERVER
355 nsGlyphTable mUnicodeTable;
357 nsGlyphTableList()
358 : mUnicodeTable(NS_LITERAL_STRING("Unicode"))
360 MOZ_COUNT_CTOR(nsGlyphTableList);
363 virtual ~nsGlyphTableList()
365 MOZ_COUNT_DTOR(nsGlyphTableList);
368 nsresult Initialize();
369 nsresult Finalize();
371 // Add a glyph table in the list, return the new table that was added
372 nsGlyphTable*
373 AddGlyphTable(const nsString& aPrimaryFontName);
375 // Find a glyph table in the list that has a glyph for the given char
376 nsGlyphTable*
377 GetGlyphTableFor(nsPresContext* aPresContext,
378 nsMathMLChar* aChar);
380 // Find the glyph table in the list corresponding to the given font family.
381 nsGlyphTable*
382 GetGlyphTableFor(const nsAString& aFamily);
384 private:
385 nsGlyphTable* TableAt(int32_t aIndex) {
386 return &mTableList.ElementAt(aIndex);
388 int32_t Count() {
389 return mTableList.Length();
392 // List of glyph tables;
393 nsTArray<nsGlyphTable> mTableList;
396 NS_IMPL_ISUPPORTS1(nsGlyphTableList, nsIObserver)
398 // -----------------------------------------------------------------------------
399 // Here is the global list of applicable glyph tables that we will be using
400 static nsGlyphTableList* gGlyphTableList = nullptr;
402 static bool gInitialized = false;
404 // XPCOM shutdown observer
405 NS_IMETHODIMP
406 nsGlyphTableList::Observe(nsISupports* aSubject,
407 const char* aTopic,
408 const PRUnichar* someData)
410 Finalize();
411 return NS_OK;
414 // Add an observer to XPCOM shutdown so that we can free our data at shutdown
415 nsresult
416 nsGlyphTableList::Initialize()
418 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
419 if (!obs)
420 return NS_ERROR_FAILURE;
422 nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
423 NS_ENSURE_SUCCESS(rv, rv);
425 return NS_OK;
428 // Remove our observer and free the memory that were allocated for us
429 nsresult
430 nsGlyphTableList::Finalize()
432 // Remove our observer from the observer service
433 nsresult rv = NS_OK;
434 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
435 if (obs)
436 rv = obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
437 else
438 rv = NS_ERROR_FAILURE;
440 gInitialized = false;
441 // our oneself will be destroyed when our |Release| is called by the observer
442 return rv;
445 nsGlyphTable*
446 nsGlyphTableList::AddGlyphTable(const nsString& aPrimaryFontName)
448 // See if there is already a special table for this family.
449 nsGlyphTable* glyphTable = GetGlyphTableFor(aPrimaryFontName);
450 if (glyphTable != &mUnicodeTable)
451 return glyphTable;
453 // allocate a table
454 glyphTable = mTableList.AppendElement(aPrimaryFontName);
455 return glyphTable;
458 nsGlyphTable*
459 nsGlyphTableList::GetGlyphTableFor(nsPresContext* aPresContext,
460 nsMathMLChar* aChar)
462 if (mUnicodeTable.Has(aPresContext, aChar))
463 return &mUnicodeTable;
465 int32_t i;
466 for (i = 0; i < Count(); i++) {
467 nsGlyphTable* glyphTable = TableAt(i);
468 if (glyphTable->Has(aPresContext, aChar)) {
469 return glyphTable;
472 return nullptr;
475 nsGlyphTable*
476 nsGlyphTableList::GetGlyphTableFor(const nsAString& aFamily)
478 for (int32_t i = 0; i < Count(); i++) {
479 nsGlyphTable* glyphTable = TableAt(i);
480 const nsAString& fontName = glyphTable->PrimaryFontName();
481 // TODO: would be nice to consider StripWhitespace and other aliasing
482 if (fontName.Equals(aFamily, nsCaseInsensitiveStringComparator())) {
483 return glyphTable;
486 // Fall back to default Unicode table
487 return &mUnicodeTable;
490 // -----------------------------------------------------------------------------
492 // Lookup the preferences:
493 // "font.mathfont-family.\uNNNN.base" -- fonts for the base size
494 // "font.mathfont-family.\uNNNN.variants" -- fonts for larger glyphs
495 // "font.mathfont-family.\uNNNN.parts" -- fonts for partial glyphs
496 // Given the char code and mode of stretch, retrieve the preferred extension
497 // font families.
498 static bool
499 GetFontExtensionPref(PRUnichar aChar,
500 nsMathfontPrefExtension aExtension, nsString& aValue)
502 // initialize OUT param
503 aValue.Truncate();
505 // We are going to try two keys because some users specify their pref as
506 // user_pref("font.mathfont-family.\uNNNN.base", "...") rather than
507 // user_pref("font.mathfont-family.\\uNNNN.base", "...").
508 // The \uNNNN in the former is interpreted as an UTF16 escape sequence by
509 // JavaScript and is converted to the internal UTF8 string that JavaScript
510 // uses.
511 // But clueless users who are not savvy of JavaScript have no idea as to what
512 // is going on and are baffled as to why their pref setting is not working.
513 // So to save countless explanations, we are going to support both keys.
515 static const char* kMathFontPrefix = "font.mathfont-family.";
517 nsCAutoString extension;
518 switch (aExtension)
520 case eExtension_base:
521 extension.AssignLiteral(".base");
522 break;
523 case eExtension_variants:
524 extension.AssignLiteral(".variants");
525 break;
526 case eExtension_parts:
527 extension.AssignLiteral(".parts");
528 break;
529 default:
530 return false;
533 // .\\uNNNN key
534 nsCAutoString key;
535 key.AssignASCII(kMathFontPrefix);
536 char ustr[10];
537 PR_snprintf(ustr, sizeof(ustr), "\\u%04X", aChar);
538 key.Append(ustr);
539 key.Append(extension);
540 // .\uNNNN key
541 nsCAutoString alternateKey;
542 alternateKey.AssignASCII(kMathFontPrefix);
543 NS_ConvertUTF16toUTF8 tmp(&aChar, 1);
544 alternateKey.Append(tmp);
545 alternateKey.Append(extension);
547 aValue = Preferences::GetString(key.get());
548 if (aValue.IsEmpty()) {
549 aValue = Preferences::GetString(alternateKey.get());
551 return !aValue.IsEmpty();
555 static bool
556 MathFontEnumCallback(const nsString& aFamily, bool aGeneric, void *aData)
558 if (!gGlyphTableList->AddGlyphTable(aFamily))
559 return false; // stop in low-memory situations
560 return true; // don't stop
563 static nsresult
564 InitGlobals(nsPresContext* aPresContext)
566 NS_ASSERTION(!gInitialized, "Error -- already initialized");
567 gInitialized = true;
569 // Allocate the placeholders for the preferred parts and variants
570 nsresult rv = NS_ERROR_OUT_OF_MEMORY;
571 gGlyphTableList = new nsGlyphTableList();
572 if (gGlyphTableList) {
573 rv = gGlyphTableList->Initialize();
575 if (NS_FAILED(rv)) {
576 delete gGlyphTableList;
577 gGlyphTableList = nullptr;
578 return rv;
581 else
582 The gGlyphTableList has been successfully registered as a shutdown observer.
583 It will be deleted at shutdown, even if a failure happens below.
586 nsCAutoString key;
587 nsAutoString value;
588 nsCOMPtr<nsIPersistentProperties> mathfontProp;
590 // Add the math fonts in the gGlyphTableList in order of preference ...
591 // Note: we only load font-names at this stage. The actual glyph tables will
592 // be loaded lazily (see nsGlyphTable::ElementAt()).
594 // Load the "mathfont.properties" file
595 value.Truncate();
596 rv = LoadProperties(value, mathfontProp);
597 if (NS_FAILED(rv)) return rv;
599 // Get the list of mathfonts having special glyph tables to be used for
600 // stretchy characters.
601 // We just want to iterate over the font-family list using the
602 // callback mechanism that nsFont has...
603 nsFont font("", 0, 0, 0, 0, 0, 0);
604 NS_NAMED_LITERAL_CSTRING(defaultKey, "font.mathfont-glyph-tables");
605 rv = mathfontProp->GetStringProperty(defaultKey, font.name);
606 if (NS_FAILED(rv)) return rv;
608 // Parse the font list and append an entry for each family to gGlyphTableList
609 nsAutoString missingFamilyList;
611 font.EnumerateFamilies(MathFontEnumCallback, nullptr);
612 return rv;
615 // -----------------------------------------------------------------------------
616 // And now the implementation of nsMathMLChar
618 nsStyleContext*
619 nsMathMLChar::GetStyleContext() const
621 NS_ASSERTION(mStyleContext, "chars should always have style context");
622 return mStyleContext;
625 void
626 nsMathMLChar::SetStyleContext(nsStyleContext* aStyleContext)
628 NS_PRECONDITION(aStyleContext, "null ptr");
629 if (aStyleContext != mStyleContext) {
630 if (mStyleContext)
631 mStyleContext->Release();
632 if (aStyleContext) {
633 mStyleContext = aStyleContext;
634 aStyleContext->AddRef();
639 void
640 nsMathMLChar::SetData(nsPresContext* aPresContext,
641 nsString& aData)
643 if (!gInitialized) {
644 InitGlobals(aPresContext);
646 mData = aData;
647 // some assumptions until proven otherwise
648 // note that mGlyph is not initialized
649 mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
650 mBoundingMetrics = nsBoundingMetrics();
651 mGlyphTable = nullptr;
652 // check if stretching is applicable ...
653 if (gGlyphTableList && (1 == mData.Length())) {
654 mDirection = nsMathMLOperators::GetStretchyDirection(mData);
655 // default tentative table (not the one that is necessarily going
656 // to be used)
657 mGlyphTable = gGlyphTableList->GetGlyphTableFor(aPresContext, this);
661 // -----------------------------------------------------------------------------
663 The Stretch:
664 @param aContainerSize - suggested size for the stretched char
665 @param aDesiredStretchSize - OUT parameter. The desired size
666 after stretching. If no stretching is done, the output will
667 simply give the base size.
669 How it works?
670 Summary:-
671 The Stretch() method first looks for a glyph of appropriate
672 size; If a glyph is found, it is cached by this object and
673 its size is returned in aDesiredStretchSize. The cached
674 glyph will then be used at the painting stage.
675 If no glyph of appropriate size is found, a search is made
676 to see if the char can be built by parts.
678 Details:-
679 A character gets stretched through the following pipeline :
681 1) If the base size of the char is sufficient to cover the
682 container' size, we use that. If not, it will still be
683 used as a fallback if the other stages in the pipeline fail.
684 Issues :
685 a) The base size, the parts and the variants of a char can
686 be in different fonts. For eg., the base size for '(' should
687 come from a normal ascii font if CMEX10 is used, since CMEX10
688 only contains the stretched versions. Hence, there are two
689 style contexts in use throughout the process. The leaf style
690 context of the char holds fonts with which to try to stretch
691 the char. The parent style context of the char contains fonts
692 for normal rendering. So the parent context is the one used
693 to get the initial base size at the start of the pipeline.
694 b) For operators that can be largeop's in display mode,
695 we will skip the base size even if it fits, so that
696 the next stage in the pipeline is given a chance to find
697 a largeop variant. If the next stage fails, we fallback
698 to the base size.
700 2) We search for the first larger variant of the char that fits the
701 container' size. We first search for larger variants using the glyph
702 table corresponding to the first existing font specified in the list of
703 stretchy fonts held by the leaf style context (from -moz-math-stretchy in
704 mathml.css). Generic fonts are resolved by the preference
705 "font.mathfont-family".
706 Issues :
707 a) the largeop and display settings determine the starting
708 size when we do the above search, regardless of whether
709 smaller variants already fit the container' size.
710 b) if it is a largeopOnly request (i.e., a displaystyle operator
711 with largeop=true and stretchy=false), we break after finding
712 the first starting variant, regardless of whether that
713 variant fits the container's size.
715 3) If a variant of appropriate size wasn't found, we see if the char
716 can be built by parts using the same glyph table.
717 Issue:
718 There are chars that have no middle and glue glyphs. For
719 such chars, the parts need to be joined using the rule.
720 By convention (TeXbook p.225), the descent of the parts is
721 zero while their ascent gives the thickness of the rule that
722 should be used to join them.
724 4) If a match was not found in that glyph table, repeat from 2 to search the
725 ordered list of stretchy fonts for the first font with a glyph table that
726 provides a fit to the container size. If no fit is found, the closest fit
727 is used.
729 Of note:
730 When the pipeline completes successfully, the desired size of the
731 stretched char can actually be slightly larger or smaller than
732 aContainerSize. But it is the responsibility of the caller to
733 account for the spacing when setting aContainerSize, and to leave
734 any extra margin when placing the stretched char.
736 // -----------------------------------------------------------------------------
739 // plain TeX settings (TeXbook p.152)
740 #define NS_MATHML_DELIMITER_FACTOR 0.901f
741 #define NS_MATHML_DELIMITER_SHORTFALL_POINTS 5.0f
743 static bool
744 IsSizeOK(nsPresContext* aPresContext, nscoord a, nscoord b, uint32_t aHint)
746 // Normal: True if 'a' is around +/-10% of the target 'b' (10% is
747 // 1-DelimiterFactor). This often gives a chance to the base size to
748 // win, especially in the context of <mfenced> without tall elements
749 // or in sloppy markups without protective <mrow></mrow>
750 bool isNormal =
751 (aHint & NS_STRETCH_NORMAL)
752 && bool(float(NS_ABS(a - b))
753 < (1.0f - NS_MATHML_DELIMITER_FACTOR) * float(b));
754 // Nearer: True if 'a' is around max{ +/-10% of 'b' , 'b' - 5pt },
755 // as documented in The TeXbook, Ch.17, p.152.
756 // i.e. within 10% and within 5pt
757 bool isNearer = false;
758 if (aHint & (NS_STRETCH_NEARER | NS_STRETCH_LARGEOP)) {
759 float c = NS_MAX(float(b) * NS_MATHML_DELIMITER_FACTOR,
760 float(b) - nsPresContext::
761 CSSPointsToAppUnits(NS_MATHML_DELIMITER_SHORTFALL_POINTS));
762 isNearer = bool(float(NS_ABS(b - a)) <= (float(b) - c));
764 // Smaller: Mainly for transitory use, to compare two candidate
765 // choices
766 bool isSmaller =
767 (aHint & NS_STRETCH_SMALLER)
768 && bool((float(a) >= (NS_MATHML_DELIMITER_FACTOR * float(b)))
769 && (a <= b));
770 // Larger: Critical to the sqrt code to ensure that the radical
771 // size is tall enough
772 bool isLarger =
773 (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP))
774 && bool(a >= b);
775 return (isNormal || isSmaller || isNearer || isLarger);
778 static bool
779 IsSizeBetter(nscoord a, nscoord olda, nscoord b, uint32_t aHint)
781 if (0 == olda)
782 return true;
783 if (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP))
784 return (a >= olda) ? (olda < b) : (a >= b);
785 if (aHint & NS_STRETCH_SMALLER)
786 return (a <= olda) ? (olda > b) : (a <= b);
788 // XXXkt prob want log scale here i.e. 1.5 is closer to 1 than 0.5
789 return NS_ABS(a - b) < NS_ABS(olda - b);
792 // We want to place the glyphs even when they don't fit at their
793 // full extent, i.e., we may clip to tolerate a small amount of
794 // overlap between the parts. This is important to cater for fonts
795 // with long glues.
796 static nscoord
797 ComputeSizeFromParts(nsPresContext* aPresContext,
798 nsGlyphCode* aGlyphs,
799 nscoord* aSizes,
800 nscoord aTargetSize)
802 enum {first, middle, last, glue};
803 // Add the parts that cannot be left out.
804 nscoord sum = 0;
805 for (int32_t i = first; i <= last; i++) {
806 if (aGlyphs[i] != aGlyphs[glue]) {
807 sum += aSizes[i];
811 // Determine how much is used in joins
812 nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
813 int32_t joins = aGlyphs[middle] == aGlyphs[glue] ? 1 : 2;
815 // Pick a maximum size using a maximum number of glue glyphs that we are
816 // prepared to draw for one character.
817 const int32_t maxGlyphs = 1000;
819 // This also takes into account the fact that, if the glue has no size,
820 // then the character can't be lengthened.
821 nscoord maxSize = sum - 2 * joins * oneDevPixel + maxGlyphs * aSizes[glue];
822 if (maxSize < aTargetSize)
823 return maxSize; // settle with the maximum size
825 // Get the minimum allowable size using some flex.
826 nscoord minSize = NSToCoordRound(NS_MATHML_DELIMITER_FACTOR * sum);
828 if (minSize > aTargetSize)
829 return minSize; // settle with the minimum size
831 // Fill-up the target area
832 return aTargetSize;
835 // Insert aFallbackFamilies before the first generic family in or at the end
836 // of a CSS aFontName.
837 static void
838 AddFallbackFonts(nsAString& aFontName, const nsAString& aFallbackFamilies)
840 if (aFallbackFamilies.IsEmpty())
841 return;
843 if (aFontName.IsEmpty()) {
844 return;
847 static const PRUnichar kSingleQuote = PRUnichar('\'');
848 static const PRUnichar kDoubleQuote = PRUnichar('\"');
849 static const PRUnichar kComma = PRUnichar(',');
851 const PRUnichar *p_begin, *p_end;
852 aFontName.BeginReading(p_begin);
853 aFontName.EndReading(p_end);
855 const PRUnichar *p = p_begin;
856 const PRUnichar *p_name = nullptr;
857 while (p < p_end) {
858 while (nsCRT::IsAsciiSpace(*p))
859 if (++p == p_end)
860 goto insert;
862 p_name = p;
863 if (*p == kSingleQuote || *p == kDoubleQuote) {
864 // quoted font family
865 PRUnichar quoteMark = *p;
866 if (++p == p_end)
867 goto insert;
869 // XXX What about CSS character escapes?
870 while (*p != quoteMark)
871 if (++p == p_end)
872 goto insert;
874 while (++p != p_end && *p != kComma)
875 /* nothing */ ;
877 } else {
878 // unquoted font family
879 const PRUnichar *nameStart = p;
880 while (++p != p_end && *p != kComma)
881 /* nothing */ ;
883 nsAutoString family;
884 family = Substring(nameStart, p);
885 family.CompressWhitespace(false, true);
887 uint8_t generic;
888 nsFont::GetGenericID(family, &generic);
889 if (generic != kGenericFont_NONE)
890 goto insert;
893 ++p; // may advance past p_end
896 aFontName.Append(NS_LITERAL_STRING(",") + aFallbackFamilies);
897 return;
899 insert:
900 if (p_name) {
901 aFontName.Insert(aFallbackFamilies + NS_LITERAL_STRING(","),
902 p_name - p_begin);
904 else { // whitespace or empty
905 aFontName = aFallbackFamilies;
909 // Update the font and rendering context if there is a family change
910 static bool
911 SetFontFamily(nsStyleContext* aStyleContext,
912 nsRenderingContext& aRenderingContext,
913 nsFont& aFont,
914 const nsGlyphTable* aGlyphTable,
915 const nsGlyphCode& aGlyphCode,
916 const nsAString& aDefaultFamily)
918 const nsAString& family =
919 aGlyphCode.font ? aGlyphTable->FontNameFor(aGlyphCode) : aDefaultFamily;
920 if (! family.Equals(aFont.name)) {
921 nsFont font = aFont;
922 font.name = family;
923 nsRefPtr<nsFontMetrics> fm;
924 aRenderingContext.DeviceContext()->GetMetricsFor(font,
925 aStyleContext->GetStyleFont()->mLanguage,
926 aStyleContext->PresContext()->GetUserFontSet(),
927 *getter_AddRefs(fm));
928 // Set the font if it is an unicode table
929 // or if the same family name has been found
930 if (aGlyphTable == &gGlyphTableList->mUnicodeTable ||
931 fm->GetThebesFontGroup()->GetFontAt(0)->GetFontEntry()->
932 FamilyName() == family) {
933 aFont.name = family;
934 aRenderingContext.SetFont(fm);
935 } else
936 return false; // We did not set the font
938 return true;
941 class nsMathMLChar::StretchEnumContext {
942 public:
943 StretchEnumContext(nsMathMLChar* aChar,
944 nsPresContext* aPresContext,
945 nsRenderingContext& aRenderingContext,
946 nsStretchDirection aStretchDirection,
947 nscoord aTargetSize,
948 uint32_t aStretchHint,
949 nsBoundingMetrics& aStretchedMetrics,
950 const nsAString& aFamilies,
951 bool& aGlyphFound)
952 : mChar(aChar),
953 mPresContext(aPresContext),
954 mRenderingContext(aRenderingContext),
955 mDirection(aStretchDirection),
956 mTargetSize(aTargetSize),
957 mStretchHint(aStretchHint),
958 mBoundingMetrics(aStretchedMetrics),
959 mFamilies(aFamilies),
960 mTryVariants(true),
961 mTryParts(true),
962 mGlyphFound(aGlyphFound) {}
964 static bool
965 EnumCallback(const nsString& aFamily, bool aGeneric, void *aData);
967 private:
968 bool TryVariants(nsGlyphTable* aGlyphTable, const nsAString& aFamily);
969 bool TryParts(nsGlyphTable* aGlyphTable, const nsAString& aFamily);
971 nsMathMLChar* mChar;
972 nsPresContext* mPresContext;
973 nsRenderingContext& mRenderingContext;
974 const nsStretchDirection mDirection;
975 const nscoord mTargetSize;
976 const uint32_t mStretchHint;
977 nsBoundingMetrics& mBoundingMetrics;
978 // Font families to search
979 const nsAString& mFamilies;
981 public:
982 bool mTryVariants;
983 bool mTryParts;
985 private:
986 nsAutoTArray<nsGlyphTable*,16> mTablesTried;
987 nsGlyphTable* mGlyphTable; // for this callback
988 bool& mGlyphFound;
992 // 2. See if there are any glyphs of the appropriate size.
993 // Returns true if the size is OK, false to keep searching.
994 // Always updates the char if a better match is found.
995 bool
996 nsMathMLChar::StretchEnumContext::TryVariants(nsGlyphTable* aGlyphTable,
997 const nsAString& aFamily)
999 // Use our stretchy style context now that stretching is in progress
1000 nsStyleContext *sc = mChar->mStyleContext;
1001 nsFont font = sc->GetStyleFont()->mFont;
1002 // Ensure mRenderingContext.SetFont will be called:
1003 font.name.Truncate();
1005 bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
1006 bool largeop = (NS_STRETCH_LARGEOP & mStretchHint) != 0;
1007 bool largeopOnly =
1008 largeop && (NS_STRETCH_VARIABLE_MASK & mStretchHint) == 0;
1009 bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
1011 nscoord bestSize =
1012 isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent
1013 : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
1014 bool haveBetter = false;
1016 // start at size = 1 (size = 0 is the char at its normal size)
1017 int32_t size = 1;
1018 #ifdef NOISY_SEARCH
1019 printf(" searching in %s ...\n",
1020 NS_LossyConvertUTF16toASCII(aFamily).get());
1021 #endif
1023 nsGlyphCode ch;
1024 while ((ch = aGlyphTable->BigOf(mPresContext, mChar, size)).Exists()) {
1026 if(!SetFontFamily(sc, mRenderingContext, font, aGlyphTable, ch, aFamily)) {
1027 // if largeopOnly is set, break now
1028 if (largeopOnly) break;
1029 ++size;
1030 continue;
1033 NS_ASSERTION(maxWidth || ch.code[0] != mChar->mGlyph.code[0] ||
1034 ch.code[1] != mChar->mGlyph.code[1] ||
1035 !font.name.Equals(mChar->mFamily),
1036 "glyph table incorrectly set -- duplicate found");
1038 nsBoundingMetrics bm = mRenderingContext.GetBoundingMetrics(ch.code,
1039 ch.Length());
1040 nscoord charSize =
1041 isVertical ? bm.ascent + bm.descent
1042 : bm.rightBearing - bm.leftBearing;
1044 if (largeopOnly ||
1045 IsSizeBetter(charSize, bestSize, mTargetSize, mStretchHint)) {
1046 mGlyphFound = true;
1047 if (maxWidth) {
1048 // IsSizeBetter() checked that charSize < maxsize;
1049 // Leave ascent, descent, and bestsize as these contain maxsize.
1050 if (mBoundingMetrics.width < bm.width)
1051 mBoundingMetrics.width = bm.width;
1052 if (mBoundingMetrics.leftBearing > bm.leftBearing)
1053 mBoundingMetrics.leftBearing = bm.leftBearing;
1054 if (mBoundingMetrics.rightBearing < bm.rightBearing)
1055 mBoundingMetrics.rightBearing = bm.rightBearing;
1056 // Continue to check other sizes unless largeopOnly
1057 haveBetter = largeopOnly;
1059 else {
1060 mBoundingMetrics = bm;
1061 haveBetter = true;
1062 bestSize = charSize;
1063 mChar->mGlyphTable = aGlyphTable;
1064 mChar->mGlyph = ch;
1065 mChar->mFamily = font.name;
1067 #ifdef NOISY_SEARCH
1068 printf(" size:%d Current best\n", size);
1069 #endif
1071 else {
1072 #ifdef NOISY_SEARCH
1073 printf(" size:%d Rejected!\n", size);
1074 #endif
1075 if (haveBetter)
1076 break; // Not making an futher progress, stop searching
1079 // if largeopOnly is set, break now
1080 if (largeopOnly) break;
1081 ++size;
1084 return haveBetter &&
1085 (largeopOnly ||
1086 IsSizeOK(mPresContext, bestSize, mTargetSize, mStretchHint));
1089 // 3. Build by parts.
1090 // Returns true if the size is OK, false to keep searching.
1091 // Always updates the char if a better match is found.
1092 bool
1093 nsMathMLChar::StretchEnumContext::TryParts(nsGlyphTable* aGlyphTable,
1094 const nsAString& aFamily)
1096 if (!aGlyphTable->HasPartsOf(mPresContext, mChar))
1097 return false; // to next table
1099 // See if the parts of this table fit in the desired space //////////////////
1101 // Use our stretchy style context now that stretching is in progress
1102 nsFont font = mChar->mStyleContext->GetStyleFont()->mFont;
1103 // Ensure mRenderingContext.SetFont will be called:
1104 font.name.Truncate();
1106 // Compute the bounding metrics of all partial glyphs
1107 nsGlyphCode chdata[4];
1108 nsBoundingMetrics bmdata[4];
1109 nscoord sizedata[4];
1110 nsGlyphCode glue = aGlyphTable->GlueOf(mPresContext, mChar);
1112 bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
1113 bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
1115 for (int32_t i = 0; i < 4; i++) {
1116 nsGlyphCode ch;
1117 switch (i) {
1118 case 0: ch = aGlyphTable->TopOf(mPresContext, mChar); break;
1119 case 1: ch = aGlyphTable->MiddleOf(mPresContext, mChar); break;
1120 case 2: ch = aGlyphTable->BottomOf(mPresContext, mChar); break;
1121 case 3: ch = glue; break;
1123 // empty slots are filled with the glue if it is not null
1124 if (!ch.Exists()) ch = glue;
1125 chdata[i] = ch;
1126 if (!ch.Exists()) {
1127 // Null glue indicates that a rule will be drawn, which can stretch to
1128 // fill any space. Leave bounding metrics at 0.
1129 sizedata[i] = mTargetSize;
1131 else {
1132 if (!SetFontFamily(mChar->mStyleContext, mRenderingContext,
1133 font, aGlyphTable, ch, aFamily))
1134 return false;
1136 nsBoundingMetrics bm = mRenderingContext.GetBoundingMetrics(ch.code,
1137 ch.Length());
1139 // TODO: For the generic Unicode table, ideally we should check that the
1140 // glyphs are actually found and that they each come from the same
1141 // font.
1142 bmdata[i] = bm;
1143 sizedata[i] = isVertical ? bm.ascent + bm.descent
1144 : bm.rightBearing - bm.leftBearing;
1148 // Build by parts if we have successfully computed the
1149 // bounding metrics of all parts.
1150 nscoord computedSize = ComputeSizeFromParts(mPresContext, chdata, sizedata,
1151 mTargetSize);
1153 nscoord currentSize =
1154 isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent
1155 : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
1157 if (!IsSizeBetter(computedSize, currentSize, mTargetSize, mStretchHint)) {
1158 #ifdef NOISY_SEARCH
1159 printf(" Font %s Rejected!\n",
1160 NS_LossyConvertUTF16toASCII(fontName).get());
1161 #endif
1162 return false; // to next table
1165 #ifdef NOISY_SEARCH
1166 printf(" Font %s Current best!\n",
1167 NS_LossyConvertUTF16toASCII(fontName).get());
1168 #endif
1170 // The computed size is the best we have found so far...
1171 // now is the time to compute and cache our bounding metrics
1172 if (isVertical) {
1173 int32_t i;
1174 nscoord lbearing;
1175 nscoord rbearing;
1176 nscoord width;
1177 if (maxWidth) {
1178 lbearing = mBoundingMetrics.leftBearing;
1179 rbearing = mBoundingMetrics.rightBearing;
1180 width = mBoundingMetrics.width;
1181 i = 0;
1183 else {
1184 lbearing = bmdata[0].leftBearing;
1185 rbearing = bmdata[0].rightBearing;
1186 width = bmdata[0].width;
1187 i = 1;
1189 for (; i < 4; i++) {
1190 const nsBoundingMetrics& bm = bmdata[i];
1191 if (width < bm.width) width = bm.width;
1192 if (lbearing > bm.leftBearing) lbearing = bm.leftBearing;
1193 if (rbearing < bm.rightBearing) rbearing = bm.rightBearing;
1195 mBoundingMetrics.width = width;
1196 // When maxWidth, updating ascent and descent indicates that no characters
1197 // larger than this character's minimum size need to be checked as they
1198 // will not be used.
1199 mBoundingMetrics.ascent = bmdata[0].ascent; // not used except with descent
1200 // for height
1201 mBoundingMetrics.descent = computedSize - mBoundingMetrics.ascent;
1202 mBoundingMetrics.leftBearing = lbearing;
1203 mBoundingMetrics.rightBearing = rbearing;
1205 else {
1206 nscoord ascent = bmdata[0].ascent;
1207 nscoord descent = bmdata[0].descent;
1208 for (int32_t i = 1; i < 4; i++) {
1209 const nsBoundingMetrics& bm = bmdata[i];
1210 if (ascent < bm.ascent) ascent = bm.ascent;
1211 if (descent < bm.descent) descent = bm.descent;
1213 mBoundingMetrics.width = computedSize;
1214 mBoundingMetrics.ascent = ascent;
1215 mBoundingMetrics.descent = descent;
1216 mBoundingMetrics.leftBearing = 0;
1217 mBoundingMetrics.rightBearing = computedSize;
1219 mGlyphFound = true;
1220 if (maxWidth)
1221 return false; // Continue to check other sizes
1223 // reset
1224 mChar->mGlyph = kNullGlyph; // this will tell paint to build by parts
1225 mChar->mGlyphTable = aGlyphTable;
1226 mChar->mFamily = aFamily;
1228 return IsSizeOK(mPresContext, computedSize, mTargetSize, mStretchHint);
1231 // This is called for each family, whether it exists or not
1232 bool
1233 nsMathMLChar::StretchEnumContext::EnumCallback(const nsString& aFamily,
1234 bool aGeneric, void *aData)
1236 StretchEnumContext* context = static_cast<StretchEnumContext*>(aData);
1238 // See if there is a special table for the family, but always use the
1239 // Unicode table for generic fonts.
1240 nsGlyphTable* glyphTable = aGeneric ?
1241 &gGlyphTableList->mUnicodeTable :
1242 gGlyphTableList->GetGlyphTableFor(aFamily);
1244 if (context->mTablesTried.Contains(glyphTable))
1245 return true; // already tried this one
1247 // Check font family if it is not a generic one
1248 // We test with the kNullGlyph
1249 nsStyleContext *sc = context->mChar->mStyleContext;
1250 nsFont font = sc->GetStyleFont()->mFont;
1251 if (!aGeneric && !SetFontFamily(sc, context->mRenderingContext,
1252 font, NULL, kNullGlyph, aFamily))
1253 return true; // Could not set the family
1255 context->mGlyphTable = glyphTable;
1257 // Now see if the table has a glyph that matches the container
1259 // Only try this table once.
1260 context->mTablesTried.AppendElement(glyphTable);
1262 // If the unicode table is being used, then search all font families. If a
1263 // special table is being used then the font in this family should have the
1264 // specified glyphs.
1265 const nsAString& family = glyphTable == &gGlyphTableList->mUnicodeTable ?
1266 context->mFamilies : aFamily;
1268 if((context->mTryVariants && context->TryVariants(glyphTable, family)) ||
1269 (context->mTryParts && context->TryParts(glyphTable, family)))
1270 return false; // no need to continue
1272 return true; // true means continue
1275 nsresult
1276 nsMathMLChar::StretchInternal(nsPresContext* aPresContext,
1277 nsRenderingContext& aRenderingContext,
1278 nsStretchDirection& aStretchDirection,
1279 const nsBoundingMetrics& aContainerSize,
1280 nsBoundingMetrics& aDesiredStretchSize,
1281 uint32_t aStretchHint,
1282 // These are currently only used when
1283 // aStretchHint & NS_STRETCH_MAXWIDTH:
1284 float aMaxSize,
1285 bool aMaxSizeIsAbsolute)
1287 // if we have been called before, and we didn't actually stretch, our
1288 // direction may have been set to NS_STRETCH_DIRECTION_UNSUPPORTED.
1289 // So first set our direction back to its instrinsic value
1290 nsStretchDirection direction = nsMathMLOperators::GetStretchyDirection(mData);
1292 // Set default font and get the default bounding metrics
1293 // mStyleContext is a leaf context used only when stretching happens.
1294 // For the base size, the default font should come from the parent context
1295 nsFont font = mStyleContext->GetParent()->GetStyleFont()->mFont;
1297 // Override with specific fonts if applicable for this character
1298 nsAutoString families;
1299 if (GetFontExtensionPref(mData[0], eExtension_base, families)) {
1300 font.name = families;
1303 // Don't modify this nsMathMLChar when doing GetMaxWidth()
1304 bool maxWidth = (NS_STRETCH_MAXWIDTH & aStretchHint) != 0;
1305 if (!maxWidth) {
1306 // Record the families in case there is no stretch. But don't bother
1307 // storing families when they are just those from the StyleContext.
1308 mFamily = families;
1311 nsRefPtr<nsFontMetrics> fm;
1312 aRenderingContext.DeviceContext()->GetMetricsFor(font,
1313 mStyleContext->GetStyleFont()->mLanguage,
1314 aPresContext->GetUserFontSet(), *getter_AddRefs(fm));
1315 aRenderingContext.SetFont(fm);
1316 aDesiredStretchSize =
1317 aRenderingContext.GetBoundingMetrics(mData.get(), uint32_t(mData.Length()));
1319 if (!maxWidth) {
1320 mUnscaledAscent = aDesiredStretchSize.ascent;
1323 //////////////////////////////////////////////////////////////////////////////
1324 // 1. Check the common situations where stretching is not actually needed
1325 //////////////////////////////////////////////////////////////////////////////
1327 // quick return if there is nothing special about this char
1328 if ((aStretchDirection != direction &&
1329 aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT) ||
1330 (aStretchHint & ~NS_STRETCH_MAXWIDTH) == NS_STRETCH_NONE) {
1331 mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
1332 return NS_OK;
1335 // if no specified direction, attempt to stretch in our preferred direction
1336 if (aStretchDirection == NS_STRETCH_DIRECTION_DEFAULT) {
1337 aStretchDirection = direction;
1340 // see if this is a particular largeop or largeopOnly request
1341 bool largeop = (NS_STRETCH_LARGEOP & aStretchHint) != 0;
1342 bool stretchy = (NS_STRETCH_VARIABLE_MASK & aStretchHint) != 0;
1343 bool largeopOnly = largeop && !stretchy;
1345 bool isVertical = (direction == NS_STRETCH_DIRECTION_VERTICAL);
1347 nscoord targetSize =
1348 isVertical ? aContainerSize.ascent + aContainerSize.descent
1349 : aContainerSize.rightBearing - aContainerSize.leftBearing;
1351 if (maxWidth) {
1352 // See if it is only necessary to consider glyphs up to some maximum size.
1353 // Set the current height to the maximum size, and set aStretchHint to
1354 // NS_STRETCH_SMALLER if the size is variable, so that only smaller sizes
1355 // are considered. targetSize from GetMaxWidth() is 0.
1356 if (stretchy) {
1357 // variable size stretch - consider all sizes < maxsize
1358 aStretchHint =
1359 (aStretchHint & ~NS_STRETCH_VARIABLE_MASK) | NS_STRETCH_SMALLER;
1362 // Use NS_MATHML_DELIMITER_FACTOR to allow some slightly larger glyphs as
1363 // maxsize is not enforced exactly.
1364 if (aMaxSize == NS_MATHML_OPERATOR_SIZE_INFINITY) {
1365 aDesiredStretchSize.ascent = nscoord_MAX;
1366 aDesiredStretchSize.descent = 0;
1368 else {
1369 nscoord height = aDesiredStretchSize.ascent + aDesiredStretchSize.descent;
1370 if (height == 0) {
1371 if (aMaxSizeIsAbsolute) {
1372 aDesiredStretchSize.ascent =
1373 NSToCoordRound(aMaxSize / NS_MATHML_DELIMITER_FACTOR);
1374 aDesiredStretchSize.descent = 0;
1376 // else: leave height as 0
1378 else {
1379 float scale = aMaxSizeIsAbsolute ? aMaxSize / height : aMaxSize;
1380 scale /= NS_MATHML_DELIMITER_FACTOR;
1381 aDesiredStretchSize.ascent =
1382 NSToCoordRound(scale * aDesiredStretchSize.ascent);
1383 aDesiredStretchSize.descent =
1384 NSToCoordRound(scale * aDesiredStretchSize.descent);
1389 nsBoundingMetrics initialSize = aDesiredStretchSize;
1390 nscoord charSize =
1391 isVertical ? initialSize.ascent + initialSize.descent
1392 : initialSize.rightBearing - initialSize.leftBearing;
1394 bool done = (mGlyphTable ? false : true);
1396 if (!done && !maxWidth && !largeop) {
1397 // Doing Stretch() not GetMaxWidth(),
1398 // and not a largeop in display mode; we're done if size fits
1399 if ((targetSize <= 0) ||
1400 ((isVertical && charSize >= targetSize) ||
1401 IsSizeOK(aPresContext, charSize, targetSize, aStretchHint)))
1402 done = true;
1405 //////////////////////////////////////////////////////////////////////////////
1406 // 2/3. Search for a glyph or set of part glyphs of appropriate size
1407 //////////////////////////////////////////////////////////////////////////////
1409 bool glyphFound = false;
1410 nsAutoString cssFamilies;
1412 if (!done) {
1413 font = mStyleContext->GetStyleFont()->mFont;
1414 cssFamilies = font.name;
1417 // See if there are preferred fonts for the variants of this char
1418 if (!done && GetFontExtensionPref(mData[0], eExtension_variants, families)) {
1419 font.name = families;
1421 StretchEnumContext enumData(this, aPresContext, aRenderingContext,
1422 aStretchDirection, targetSize, aStretchHint,
1423 aDesiredStretchSize, font.name, glyphFound);
1424 enumData.mTryParts = false;
1426 done = !font.EnumerateFamilies(StretchEnumContext::EnumCallback, &enumData);
1429 // See if there are preferred fonts for the parts of this char
1430 if (!done && !largeopOnly
1431 && GetFontExtensionPref(mData[0], eExtension_parts, families)) {
1432 font.name = families;
1434 StretchEnumContext enumData(this, aPresContext, aRenderingContext,
1435 aStretchDirection, targetSize, aStretchHint,
1436 aDesiredStretchSize, font.name, glyphFound);
1437 enumData.mTryVariants = false;
1439 done = !font.EnumerateFamilies(StretchEnumContext::EnumCallback, &enumData);
1442 if (!done) { // normal case
1443 // Use the css font-family but add preferred fallback fonts.
1444 font.name = cssFamilies;
1445 NS_NAMED_LITERAL_CSTRING(defaultKey, "font.mathfont-family");
1446 nsAdoptingString fallbackFonts = Preferences::GetString(defaultKey.get());
1447 if (!fallbackFonts.IsEmpty()) {
1448 AddFallbackFonts(font.name, fallbackFonts);
1451 #ifdef NOISY_SEARCH
1452 printf("Searching in "%s" for a glyph of appropriate size for: 0x%04X:%c\n",
1453 font.name, mData[0], mData[0]&0x00FF);
1454 #endif
1455 StretchEnumContext enumData(this, aPresContext, aRenderingContext,
1456 aStretchDirection, targetSize, aStretchHint,
1457 aDesiredStretchSize, font.name, glyphFound);
1458 enumData.mTryParts = !largeopOnly;
1460 font.EnumerateFamilies(StretchEnumContext::EnumCallback, &enumData);
1463 if (!maxWidth) {
1464 // Now, we know how we are going to draw the char. Update the member
1465 // variables accordingly.
1466 mDrawNormal = !glyphFound;
1467 mUnscaledAscent = aDesiredStretchSize.ascent;
1470 // stretchy character
1471 if (stretchy) {
1472 if (isVertical) {
1473 float scale =
1474 float(aContainerSize.ascent + aContainerSize.descent) /
1475 (aDesiredStretchSize.ascent + aDesiredStretchSize.descent);
1476 if (!largeop || scale > 1.0) {
1477 // make the character match the desired height.
1478 if (!maxWidth) {
1479 mScaleY *= scale;
1481 aDesiredStretchSize.ascent *= scale;
1482 aDesiredStretchSize.descent *= scale;
1484 } else {
1485 float scale =
1486 float(aContainerSize.rightBearing - aContainerSize.leftBearing) /
1487 (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing);
1488 if (!largeop || scale > 1.0) {
1489 // make the character match the desired width.
1490 if (!maxWidth) {
1491 mScaleX *= scale;
1493 aDesiredStretchSize.leftBearing *= scale;
1494 aDesiredStretchSize.rightBearing *= scale;
1495 aDesiredStretchSize.width *= scale;
1500 // We do not have a char variant for this largeop in display mode, so we
1501 // apply a scale transform to the base char.
1502 if (!glyphFound && largeop) {
1503 float scale;
1504 float largeopFactor = M_SQRT2;
1506 // increase the width if it is not largeopFactor times larger
1507 // than the initial one.
1508 if ((aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing) <
1509 largeopFactor * (initialSize.rightBearing - initialSize.leftBearing)) {
1510 scale = (largeopFactor *
1511 (initialSize.rightBearing - initialSize.leftBearing)) /
1512 (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing);
1513 if (!maxWidth) {
1514 mScaleX *= scale;
1516 aDesiredStretchSize.leftBearing *= scale;
1517 aDesiredStretchSize.rightBearing *= scale;
1518 aDesiredStretchSize.width *= scale;
1521 // increase the height if it is not largeopFactor times larger
1522 // than the initial one.
1523 if (NS_STRETCH_INTEGRAL & aStretchHint) {
1524 // integrals are drawn taller
1525 largeopFactor = 2.0;
1527 if ((aDesiredStretchSize.ascent + aDesiredStretchSize.descent) <
1528 largeopFactor * (initialSize.ascent + initialSize.descent)) {
1529 scale = (largeopFactor *
1530 (initialSize.ascent + initialSize.descent)) /
1531 (aDesiredStretchSize.ascent + aDesiredStretchSize.descent);
1532 if (!maxWidth) {
1533 mScaleY *= scale;
1535 aDesiredStretchSize.ascent *= scale;
1536 aDesiredStretchSize.descent *= scale;
1540 return NS_OK;
1543 nsresult
1544 nsMathMLChar::Stretch(nsPresContext* aPresContext,
1545 nsRenderingContext& aRenderingContext,
1546 nsStretchDirection aStretchDirection,
1547 const nsBoundingMetrics& aContainerSize,
1548 nsBoundingMetrics& aDesiredStretchSize,
1549 uint32_t aStretchHint,
1550 bool aRTL)
1552 NS_ASSERTION(!(aStretchHint &
1553 ~(NS_STRETCH_VARIABLE_MASK | NS_STRETCH_LARGEOP |
1554 NS_STRETCH_INTEGRAL)),
1555 "Unexpected stretch flags");
1557 mDrawNormal = true;
1558 mMirrored = aRTL && nsMathMLOperators::IsMirrorableOperator(mData);
1559 mScaleY = mScaleX = 1.0;
1560 mDirection = aStretchDirection;
1561 nsresult rv =
1562 StretchInternal(aPresContext, aRenderingContext, mDirection,
1563 aContainerSize, aDesiredStretchSize, aStretchHint);
1565 // Record the metrics
1566 mBoundingMetrics = aDesiredStretchSize;
1568 return rv;
1571 // What happens here is that the StretchInternal algorithm is used but
1572 // modified by passing the NS_STRETCH_MAXWIDTH stretch hint. That causes
1573 // StretchInternal to return horizontal bounding metrics that are the maximum
1574 // that might be returned from a Stretch.
1576 // In order to avoid considering widths of some characters in fonts that will
1577 // not be used for any stretch size, StretchInternal sets the initial height
1578 // to infinity and looks for any characters smaller than this height. When a
1579 // character built from parts is considered, (it will be used by Stretch for
1580 // any characters greater than its minimum size, so) the height is set to its
1581 // minimum size, so that only widths of smaller subsequent characters are
1582 // considered.
1583 nscoord
1584 nsMathMLChar::GetMaxWidth(nsPresContext* aPresContext,
1585 nsRenderingContext& aRenderingContext,
1586 uint32_t aStretchHint,
1587 float aMaxSize, bool aMaxSizeIsAbsolute)
1589 nsBoundingMetrics bm;
1590 nsStretchDirection direction = NS_STRETCH_DIRECTION_VERTICAL;
1591 const nsBoundingMetrics container; // zero target size
1593 StretchInternal(aPresContext, aRenderingContext, direction, container,
1594 bm, aStretchHint | NS_STRETCH_MAXWIDTH);
1596 return NS_MAX(bm.width, bm.rightBearing) - NS_MIN(0, bm.leftBearing);
1599 class nsDisplayMathMLSelectionRect : public nsDisplayItem {
1600 public:
1601 nsDisplayMathMLSelectionRect(nsDisplayListBuilder* aBuilder,
1602 nsIFrame* aFrame, const nsRect& aRect)
1603 : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
1604 MOZ_COUNT_CTOR(nsDisplayMathMLSelectionRect);
1606 #ifdef NS_BUILD_REFCNT_LOGGING
1607 virtual ~nsDisplayMathMLSelectionRect() {
1608 MOZ_COUNT_DTOR(nsDisplayMathMLSelectionRect);
1610 #endif
1612 virtual void Paint(nsDisplayListBuilder* aBuilder,
1613 nsRenderingContext* aCtx);
1614 NS_DISPLAY_DECL_NAME("MathMLSelectionRect", TYPE_MATHML_SELECTION_RECT)
1615 private:
1616 nsRect mRect;
1619 void nsDisplayMathMLSelectionRect::Paint(nsDisplayListBuilder* aBuilder,
1620 nsRenderingContext* aCtx)
1622 // get color to use for selection from the look&feel object
1623 nscolor bgColor =
1624 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground,
1625 NS_RGB(0, 0, 0));
1626 aCtx->SetColor(bgColor);
1627 aCtx->FillRect(mRect + ToReferenceFrame());
1630 class nsDisplayMathMLCharBackground : public nsDisplayItem {
1631 public:
1632 nsDisplayMathMLCharBackground(nsDisplayListBuilder* aBuilder,
1633 nsIFrame* aFrame, const nsRect& aRect,
1634 nsStyleContext* aStyleContext)
1635 : nsDisplayItem(aBuilder, aFrame), mStyleContext(aStyleContext),
1636 mRect(aRect) {
1637 MOZ_COUNT_CTOR(nsDisplayMathMLCharBackground);
1639 #ifdef NS_BUILD_REFCNT_LOGGING
1640 virtual ~nsDisplayMathMLCharBackground() {
1641 MOZ_COUNT_DTOR(nsDisplayMathMLCharBackground);
1643 #endif
1645 virtual void Paint(nsDisplayListBuilder* aBuilder,
1646 nsRenderingContext* aCtx);
1647 NS_DISPLAY_DECL_NAME("MathMLCharBackground", TYPE_MATHML_CHAR_BACKGROUND)
1648 private:
1649 nsStyleContext* mStyleContext;
1650 nsRect mRect;
1653 void nsDisplayMathMLCharBackground::Paint(nsDisplayListBuilder* aBuilder,
1654 nsRenderingContext* aCtx)
1656 const nsStyleBorder* border = mStyleContext->GetStyleBorder();
1657 nsRect rect(mRect + ToReferenceFrame());
1658 nsCSSRendering::PaintBackgroundWithSC(mFrame->PresContext(), *aCtx, mFrame,
1659 mVisibleRect, rect,
1660 mStyleContext, *border,
1661 aBuilder->GetBackgroundPaintFlags());
1664 class nsDisplayMathMLCharForeground : public nsDisplayItem {
1665 public:
1666 nsDisplayMathMLCharForeground(nsDisplayListBuilder* aBuilder,
1667 nsIFrame* aFrame, nsMathMLChar* aChar,
1668 uint32_t aIndex, bool aIsSelected)
1669 : nsDisplayItem(aBuilder, aFrame), mChar(aChar),
1670 mIndex(aIndex), mIsSelected(aIsSelected) {
1671 MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground);
1673 #ifdef NS_BUILD_REFCNT_LOGGING
1674 virtual ~nsDisplayMathMLCharForeground() {
1675 MOZ_COUNT_DTOR(nsDisplayMathMLCharForeground);
1677 #endif
1679 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
1680 *aSnap = false;
1681 nsRect rect;
1682 mChar->GetRect(rect);
1683 nsPoint offset = ToReferenceFrame() + rect.TopLeft();
1684 nsBoundingMetrics bm;
1685 mChar->GetBoundingMetrics(bm);
1686 return nsRect(offset.x + bm.leftBearing, offset.y,
1687 bm.rightBearing - bm.leftBearing, bm.ascent + bm.descent);
1690 virtual void Paint(nsDisplayListBuilder* aBuilder,
1691 nsRenderingContext* aCtx)
1693 mChar->PaintForeground(mFrame->PresContext(), *aCtx,
1694 ToReferenceFrame(), mIsSelected);
1697 NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND)
1699 virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
1701 bool snap;
1702 return GetBounds(aBuilder, &snap);
1705 virtual uint32_t GetPerFrameKey() {
1706 return (mIndex << nsDisplayItem::TYPE_BITS)
1707 | nsDisplayItem::GetPerFrameKey();
1710 private:
1711 nsMathMLChar* mChar;
1712 uint32_t mIndex;
1713 bool mIsSelected;
1716 #ifdef DEBUG
1717 class nsDisplayMathMLCharDebug : public nsDisplayItem {
1718 public:
1719 nsDisplayMathMLCharDebug(nsDisplayListBuilder* aBuilder,
1720 nsIFrame* aFrame, const nsRect& aRect)
1721 : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
1722 MOZ_COUNT_CTOR(nsDisplayMathMLCharDebug);
1724 #ifdef NS_BUILD_REFCNT_LOGGING
1725 virtual ~nsDisplayMathMLCharDebug() {
1726 MOZ_COUNT_DTOR(nsDisplayMathMLCharDebug);
1728 #endif
1730 virtual void Paint(nsDisplayListBuilder* aBuilder,
1731 nsRenderingContext* aCtx);
1732 NS_DISPLAY_DECL_NAME("MathMLCharDebug", TYPE_MATHML_CHAR_DEBUG)
1734 private:
1735 nsRect mRect;
1738 void nsDisplayMathMLCharDebug::Paint(nsDisplayListBuilder* aBuilder,
1739 nsRenderingContext* aCtx)
1741 // for visual debug
1742 int skipSides = 0;
1743 nsPresContext* presContext = mFrame->PresContext();
1744 nsStyleContext* styleContext = mFrame->GetStyleContext();
1745 nsRect rect = mRect + ToReferenceFrame();
1746 nsCSSRendering::PaintBorder(presContext, *aCtx, mFrame,
1747 mVisibleRect, rect, styleContext, skipSides);
1748 nsCSSRendering::PaintOutline(presContext, *aCtx, mFrame,
1749 mVisibleRect, rect, styleContext);
1751 #endif
1754 nsresult
1755 nsMathMLChar::Display(nsDisplayListBuilder* aBuilder,
1756 nsIFrame* aForFrame,
1757 const nsDisplayListSet& aLists,
1758 uint32_t aIndex,
1759 const nsRect* aSelectedRect)
1761 nsresult rv = NS_OK;
1762 nsStyleContext* parentContext = mStyleContext->GetParent();
1763 nsStyleContext* styleContext = mStyleContext;
1765 if (mDrawNormal) {
1766 // normal drawing if there is nothing special about this char
1767 // Set default context to the parent context
1768 styleContext = parentContext;
1771 if (!styleContext->GetStyleVisibility()->IsVisible())
1772 return NS_OK;
1774 // if the leaf style context that we use for stretchy chars has a background
1775 // color we use it -- this feature is mostly used for testing and debugging
1776 // purposes. Normally, users will set the background on the container frame.
1777 // paint the selection background -- beware MathML frames overlap a lot
1778 if (aSelectedRect && !aSelectedRect->IsEmpty()) {
1779 rv = aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
1780 nsDisplayMathMLSelectionRect(aBuilder, aForFrame, *aSelectedRect));
1781 NS_ENSURE_SUCCESS(rv, rv);
1783 else if (mRect.width && mRect.height) {
1784 const nsStyleBackground* backg = styleContext->GetStyleBackground();
1785 if (styleContext != parentContext &&
1786 NS_GET_A(backg->mBackgroundColor) > 0) {
1787 rv = aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
1788 nsDisplayMathMLCharBackground(aBuilder, aForFrame, mRect,
1789 styleContext));
1790 NS_ENSURE_SUCCESS(rv, rv);
1792 //else
1793 // our container frame will take care of painting its background
1795 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
1796 // for visual debug
1797 rv = aLists.BorderBackground()->AppendToTop(new (aBuilder)
1798 nsDisplayMathMLCharDebug(aBuilder, aForFrame, mRect));
1799 NS_ENSURE_SUCCESS(rv, rv);
1800 #endif
1802 return aLists.Content()->AppendNewToTop(new (aBuilder)
1803 nsDisplayMathMLCharForeground(aBuilder, aForFrame, this,
1804 aIndex,
1805 aSelectedRect &&
1806 !aSelectedRect->IsEmpty()));
1809 void
1810 nsMathMLChar::ApplyTransforms(nsRenderingContext& aRenderingContext, nsRect &r)
1812 // apply the transforms
1813 if (mMirrored) {
1814 aRenderingContext.Translate(r.TopRight());
1815 aRenderingContext.Scale(-mScaleX, mScaleY);
1816 } else {
1817 aRenderingContext.Translate(r.TopLeft());
1818 aRenderingContext.Scale(mScaleX, mScaleY);
1821 // update the bounding rectangle.
1822 r.x = r.y = 0;
1823 r.width /= mScaleX;
1824 r.height /= mScaleY;
1827 void
1828 nsMathMLChar::PaintForeground(nsPresContext* aPresContext,
1829 nsRenderingContext& aRenderingContext,
1830 nsPoint aPt,
1831 bool aIsSelected)
1833 nsStyleContext* parentContext = mStyleContext->GetParent();
1834 nsStyleContext* styleContext = mStyleContext;
1836 if (mDrawNormal) {
1837 // normal drawing if there is nothing special about this char
1838 // Set default context to the parent context
1839 styleContext = parentContext;
1842 // Set color ...
1843 nscolor fgColor = styleContext->GetVisitedDependentColor(eCSSProperty_color);
1844 if (aIsSelected) {
1845 // get color to use for selection from the look&feel object
1846 fgColor = LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground,
1847 fgColor);
1849 aRenderingContext.SetColor(fgColor);
1851 nsFont theFont(styleContext->GetStyleFont()->mFont);
1852 if (! mFamily.IsEmpty()) {
1853 theFont.name = mFamily;
1855 nsRefPtr<nsFontMetrics> fm;
1856 aRenderingContext.DeviceContext()->GetMetricsFor(theFont,
1857 styleContext->GetStyleFont()->mLanguage,
1858 aPresContext->GetUserFontSet(),
1859 *getter_AddRefs(fm));
1860 aRenderingContext.SetFont(fm);
1862 aRenderingContext.PushState();
1863 nsRect r = mRect + aPt;
1864 ApplyTransforms(aRenderingContext, r);
1866 if (mDrawNormal) {
1867 // normal drawing if there is nothing special about this char ...
1868 // Grab some metrics to adjust the placements ...
1869 uint32_t len = uint32_t(mData.Length());
1870 aRenderingContext.DrawString(mData.get(), len, 0, mUnscaledAscent);
1872 else {
1873 // Grab some metrics to adjust the placements ...
1874 // if there is a glyph of appropriate size, paint that glyph
1875 if (mGlyph.Exists()) {
1876 aRenderingContext.DrawString(mGlyph.code, mGlyph.Length(),
1877 0, mUnscaledAscent);
1879 else { // paint by parts
1880 if (NS_STRETCH_DIRECTION_VERTICAL == mDirection)
1881 PaintVertically(aPresContext, aRenderingContext, theFont, styleContext,
1882 mGlyphTable, r);
1883 else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection)
1884 PaintHorizontally(aPresContext, aRenderingContext, theFont,
1885 styleContext, mGlyphTable, r);
1889 aRenderingContext.PopState();
1892 /* =============================================================================
1893 Helper routines that actually do the job of painting the char by parts
1896 class AutoPushClipRect {
1897 nsRenderingContext& mCtx;
1898 public:
1899 AutoPushClipRect(nsRenderingContext& aCtx, const nsRect& aRect)
1900 : mCtx(aCtx) {
1901 mCtx.PushState();
1902 mCtx.IntersectClip(aRect);
1904 ~AutoPushClipRect() {
1905 mCtx.PopState();
1909 static nsPoint
1910 SnapToDevPixels(const gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit,
1911 const nsPoint& aPt)
1913 gfxPoint pt(NSAppUnitsToFloatPixels(aPt.x, aAppUnitsPerGfxUnit),
1914 NSAppUnitsToFloatPixels(aPt.y, aAppUnitsPerGfxUnit));
1915 pt = aThebesContext->UserToDevice(pt);
1916 pt.Round();
1917 pt = aThebesContext->DeviceToUser(pt);
1918 return nsPoint(NSFloatPixelsToAppUnits(pt.x, aAppUnitsPerGfxUnit),
1919 NSFloatPixelsToAppUnits(pt.y, aAppUnitsPerGfxUnit));
1922 // paint a stretchy char by assembling glyphs vertically
1923 nsresult
1924 nsMathMLChar::PaintVertically(nsPresContext* aPresContext,
1925 nsRenderingContext& aRenderingContext,
1926 nsFont& aFont,
1927 nsStyleContext* aStyleContext,
1928 nsGlyphTable* aGlyphTable,
1929 nsRect& aRect)
1931 // Get the device pixel size in the vertical direction.
1932 // (This makes no effort to optimize for non-translation transformations.)
1933 nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
1935 // get metrics data to be re-used later
1936 int32_t i = 0;
1937 nsGlyphCode ch, chdata[4];
1938 nsBoundingMetrics bmdata[4];
1939 int32_t glue, bottom;
1940 nsGlyphCode chGlue = aGlyphTable->GlueOf(aPresContext, this);
1941 for (int32_t j = 0; j < 4; ++j) {
1942 switch (j) {
1943 case 0:
1944 ch = aGlyphTable->TopOf(aPresContext, this);
1945 break;
1946 case 1:
1947 ch = aGlyphTable->MiddleOf(aPresContext, this);
1948 if (!ch.Exists())
1949 continue; // no middle
1950 break;
1951 case 2:
1952 ch = aGlyphTable->BottomOf(aPresContext, this);
1953 bottom = i;
1954 break;
1955 case 3:
1956 ch = chGlue;
1957 glue = i;
1958 break;
1960 // empty slots are filled with the glue if it is not null
1961 if (!ch.Exists()) ch = chGlue;
1962 // if (!ch.Exists()) glue is null, leave bounding metrics at 0
1963 if (ch.Exists()) {
1964 SetFontFamily(aStyleContext, aRenderingContext,
1965 aFont, aGlyphTable, ch, mFamily);
1966 bmdata[i] = aRenderingContext.GetBoundingMetrics(ch.code, ch.Length());
1968 chdata[i] = ch;
1969 ++i;
1971 nscoord dx = aRect.x;
1972 nscoord offset[3], start[3], end[3];
1973 nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
1974 for (i = 0; i <= bottom; ++i) {
1975 ch = chdata[i];
1976 const nsBoundingMetrics& bm = bmdata[i];
1977 nscoord dy;
1978 if (0 == i) { // top
1979 dy = aRect.y + bm.ascent;
1981 else if (bottom == i) { // bottom
1982 dy = aRect.y + aRect.height - bm.descent;
1984 else { // middle
1985 dy = aRect.y + bm.ascent + (aRect.height - (bm.ascent + bm.descent))/2;
1987 // _cairo_scaled_font_show_glyphs snaps origins to device pixels.
1988 // Do this now so that we can get the other dimensions right.
1989 // (This may not achieve much with non-rectangular transformations.)
1990 dy = SnapToDevPixels(ctx, oneDevPixel, nsPoint(dx, dy)).y;
1991 // abcissa passed to DrawString
1992 offset[i] = dy;
1993 // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest
1994 // pixel, so the bm values can include 1 row of faint pixels on each edge.
1995 // Don't rely on this pixel as it can look like a gap.
1996 start[i] = dy - bm.ascent + oneDevPixel; // top join
1997 end[i] = dy + bm.descent - oneDevPixel; // bottom join
2000 // If there are overlaps, then join at the mid point
2001 for (i = 0; i < bottom; ++i) {
2002 if (end[i] > start[i+1]) {
2003 end[i] = (end[i] + start[i+1]) / 2;
2004 start[i+1] = end[i];
2008 nsRect unionRect = aRect;
2009 unionRect.x += mBoundingMetrics.leftBearing;
2010 unionRect.width =
2011 mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
2012 unionRect.Inflate(oneDevPixel, oneDevPixel);
2014 /////////////////////////////////////
2015 // draw top, middle, bottom
2016 for (i = 0; i <= bottom; ++i) {
2017 ch = chdata[i];
2018 // glue can be null, and other parts could have been set to glue
2019 if (ch.Exists()) {
2020 nscoord dy = offset[i];
2021 // Draw a glyph in a clipped area so that we don't have hairy chars
2022 // pending outside
2023 nsRect clipRect = unionRect;
2024 // Clip at the join to get a solid edge (without overlap or gap), when
2025 // this won't change the glyph too much. If the glyph is too small to
2026 // clip then we'll overlap rather than have a gap.
2027 nscoord height = bmdata[i].ascent + bmdata[i].descent;
2028 if (ch == chGlue ||
2029 height * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) {
2030 if (0 == i) { // top
2031 clipRect.height = end[i] - clipRect.y;
2033 else if (bottom == i) { // bottom
2034 clipRect.height -= start[i] - clipRect.y;
2035 clipRect.y = start[i];
2037 else { // middle
2038 clipRect.y = start[i];
2039 clipRect.height = end[i] - start[i];
2042 if (!clipRect.IsEmpty()) {
2043 AutoPushClipRect clip(aRenderingContext, clipRect);
2044 SetFontFamily(aStyleContext, aRenderingContext,
2045 aFont, aGlyphTable, ch, mFamily);
2046 aRenderingContext.DrawString(ch.code, ch.Length(), dx, dy);
2051 ///////////////
2052 // fill the gap between top and middle, and between middle and bottom.
2053 if (!chGlue.Exists()) { // null glue : draw a rule
2054 // figure out the dimensions of the rule to be drawn :
2055 // set lbearing to rightmost lbearing among the two current successive
2056 // parts.
2057 // set rbearing to leftmost rbearing among the two current successive parts.
2058 // this not only satisfies the convention used for over/underbraces
2059 // in TeX, but also takes care of broken fonts like the stretchy integral
2060 // in Symbol for small font sizes in unix.
2061 nscoord lbearing, rbearing;
2062 int32_t first = 0, last = 1;
2063 while (last <= bottom) {
2064 if (chdata[last].Exists()) {
2065 lbearing = bmdata[last].leftBearing;
2066 rbearing = bmdata[last].rightBearing;
2067 if (chdata[first].Exists()) {
2068 if (lbearing < bmdata[first].leftBearing)
2069 lbearing = bmdata[first].leftBearing;
2070 if (rbearing > bmdata[first].rightBearing)
2071 rbearing = bmdata[first].rightBearing;
2074 else if (chdata[first].Exists()) {
2075 lbearing = bmdata[first].leftBearing;
2076 rbearing = bmdata[first].rightBearing;
2078 else {
2079 NS_ERROR("Cannot stretch - All parts missing");
2080 return NS_ERROR_UNEXPECTED;
2082 // paint the rule between the parts
2083 nsRect rule(aRect.x + lbearing, end[first],
2084 rbearing - lbearing, start[last] - end[first]);
2085 if (!rule.IsEmpty())
2086 aRenderingContext.FillRect(rule);
2087 first = last;
2088 last++;
2091 else if (bmdata[glue].ascent + bmdata[glue].descent > 0) {
2092 // glue is present
2093 nsBoundingMetrics& bm = bmdata[glue];
2094 // Ensure the stride for the glue is not reduced to less than one pixel
2095 if (bm.ascent + bm.descent >= 3 * oneDevPixel) {
2096 // To protect against gaps, pretend the glue is smaller than it is,
2097 // in order to trim off ends and thus get a solid edge for the join.
2098 bm.ascent -= oneDevPixel;
2099 bm.descent -= oneDevPixel;
2102 SetFontFamily(aStyleContext, aRenderingContext,
2103 aFont, aGlyphTable, chGlue, mFamily);
2104 nsRect clipRect = unionRect;
2106 for (i = 0; i < bottom; ++i) {
2107 // Make sure not to draw outside the character
2108 nscoord dy = NS_MAX(end[i], aRect.y);
2109 nscoord fillEnd = NS_MIN(start[i+1], aRect.YMost());
2110 while (dy < fillEnd) {
2111 clipRect.y = dy;
2112 clipRect.height = NS_MIN(bm.ascent + bm.descent, fillEnd - dy);
2113 AutoPushClipRect clip(aRenderingContext, clipRect);
2114 dy += bm.ascent;
2115 aRenderingContext.DrawString(chGlue.code, chGlue.Length(), dx, dy);
2116 dy += bm.descent;
2120 #ifdef DEBUG
2121 else {
2122 for (i = 0; i < bottom; ++i) {
2123 NS_ASSERTION(end[i] >= start[i+1],
2124 "gap between parts with missing glue glyph");
2127 #endif
2128 return NS_OK;
2131 // paint a stretchy char by assembling glyphs horizontally
2132 nsresult
2133 nsMathMLChar::PaintHorizontally(nsPresContext* aPresContext,
2134 nsRenderingContext& aRenderingContext,
2135 nsFont& aFont,
2136 nsStyleContext* aStyleContext,
2137 nsGlyphTable* aGlyphTable,
2138 nsRect& aRect)
2140 // Get the device pixel size in the horizontal direction.
2141 // (This makes no effort to optimize for non-translation transformations.)
2142 nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
2144 // get metrics data to be re-used later
2145 int32_t i = 0;
2146 nsGlyphCode ch, chdata[4];
2147 nsBoundingMetrics bmdata[4];
2148 int32_t glue, right;
2149 nsGlyphCode chGlue = aGlyphTable->GlueOf(aPresContext, this);
2150 for (int32_t j = 0; j < 4; ++j) {
2151 switch (j) {
2152 case 0:
2153 ch = aGlyphTable->LeftOf(aPresContext, this);
2154 break;
2155 case 1:
2156 ch = aGlyphTable->MiddleOf(aPresContext, this);
2157 if (!ch.Exists())
2158 continue; // no middle
2159 break;
2160 case 2:
2161 ch = aGlyphTable->RightOf(aPresContext, this);
2162 right = i;
2163 break;
2164 case 3:
2165 ch = chGlue;
2166 glue = i;
2167 break;
2169 // empty slots are filled with the glue if it is not null
2170 if (!ch.Exists()) ch = chGlue;
2171 // if (!ch.Exists()) glue is null, leave bounding metrics at 0.
2172 if (ch.Exists()) {
2173 SetFontFamily(aStyleContext, aRenderingContext,
2174 aFont, aGlyphTable, ch, mFamily);
2175 bmdata[i] = aRenderingContext.GetBoundingMetrics(ch.code, ch.Length());
2177 chdata[i] = ch;
2178 ++i;
2180 nscoord dy = aRect.y + mBoundingMetrics.ascent;
2181 nscoord offset[3], start[3], end[3];
2182 nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
2183 for (i = 0; i <= right; ++i) {
2184 ch = chdata[i];
2185 const nsBoundingMetrics& bm = bmdata[i];
2186 nscoord dx;
2187 if (0 == i) { // left
2188 dx = aRect.x - bm.leftBearing;
2190 else if (right == i) { // right
2191 dx = aRect.x + aRect.width - bm.rightBearing;
2193 else { // middle
2194 dx = aRect.x + (aRect.width - bm.width)/2;
2196 // _cairo_scaled_font_show_glyphs snaps origins to device pixels.
2197 // Do this now so that we can get the other dimensions right.
2198 // (This may not achieve much with non-rectangular transformations.)
2199 dx = SnapToDevPixels(ctx, oneDevPixel, nsPoint(dx, dy)).x;
2200 // abcissa passed to DrawString
2201 offset[i] = dx;
2202 // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest
2203 // pixel, so the bm values can include 1 row of faint pixels on each edge.
2204 // Don't rely on this pixel as it can look like a gap.
2205 start[i] = dx + bm.leftBearing + oneDevPixel; // left join
2206 end[i] = dx + bm.rightBearing - oneDevPixel; // right join
2209 // If there are overlaps, then join at the mid point
2210 for (i = 0; i < right; ++i) {
2211 if (end[i] > start[i+1]) {
2212 end[i] = (end[i] + start[i+1]) / 2;
2213 start[i+1] = end[i];
2217 nsRect unionRect = aRect;
2218 unionRect.Inflate(oneDevPixel, oneDevPixel);
2220 ///////////////////////////
2221 // draw left, middle, right
2222 for (i = 0; i <= right; ++i) {
2223 ch = chdata[i];
2224 // glue can be null, and other parts could have been set to glue
2225 if (ch.Exists()) {
2226 nscoord dx = offset[i];
2227 nsRect clipRect = unionRect;
2228 // Clip at the join to get a solid edge (without overlap or gap), when
2229 // this won't change the glyph too much. If the glyph is too small to
2230 // clip then we'll overlap rather than have a gap.
2231 nscoord width = bmdata[i].rightBearing - bmdata[i].leftBearing;
2232 if (ch == chGlue ||
2233 width * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) {
2234 if (0 == i) { // left
2235 clipRect.width = end[i] - clipRect.x;
2237 else if (right == i) { // right
2238 clipRect.width -= start[i] - clipRect.x;
2239 clipRect.x = start[i];
2241 else { // middle
2242 clipRect.x = start[i];
2243 clipRect.width = end[i] - start[i];
2246 if (!clipRect.IsEmpty()) {
2247 AutoPushClipRect clip(aRenderingContext, clipRect);
2248 SetFontFamily(aStyleContext, aRenderingContext,
2249 aFont, aGlyphTable, ch, mFamily);
2250 aRenderingContext.DrawString(ch.code, ch.Length(), dx, dy);
2255 ////////////////
2256 // fill the gap between left and middle, and between middle and right.
2257 if (!chGlue.Exists()) { // null glue : draw a rule
2258 // figure out the dimensions of the rule to be drawn :
2259 // set ascent to lowest ascent among the two current successive parts.
2260 // set descent to highest descent among the two current successive parts.
2261 // this satisfies the convention used for over/underbraces, and helps
2262 // fix broken fonts.
2263 nscoord ascent, descent;
2264 int32_t first = 0, last = 1;
2265 while (last <= right) {
2266 if (chdata[last].Exists()) {
2267 ascent = bmdata[last].ascent;
2268 descent = bmdata[last].descent;
2269 if (chdata[first].Exists()) {
2270 if (ascent > bmdata[first].ascent)
2271 ascent = bmdata[first].ascent;
2272 if (descent > bmdata[first].descent)
2273 descent = bmdata[first].descent;
2276 else if (chdata[first].Exists()) {
2277 ascent = bmdata[first].ascent;
2278 descent = bmdata[first].descent;
2280 else {
2281 NS_ERROR("Cannot stretch - All parts missing");
2282 return NS_ERROR_UNEXPECTED;
2284 // paint the rule between the parts
2285 nsRect rule(end[first], dy - ascent,
2286 start[last] - end[first], ascent + descent);
2287 if (!rule.IsEmpty())
2288 aRenderingContext.FillRect(rule);
2289 first = last;
2290 last++;
2293 else if (bmdata[glue].rightBearing - bmdata[glue].leftBearing > 0) {
2294 // glue is present
2295 nsBoundingMetrics& bm = bmdata[glue];
2296 // Ensure the stride for the glue is not reduced to less than one pixel
2297 if (bm.rightBearing - bm.leftBearing >= 3 * oneDevPixel) {
2298 // To protect against gaps, pretend the glue is smaller than it is,
2299 // in order to trim off ends and thus get a solid edge for the join.
2300 bm.leftBearing += oneDevPixel;
2301 bm.rightBearing -= oneDevPixel;
2304 SetFontFamily(aStyleContext, aRenderingContext,
2305 aFont, aGlyphTable, chGlue, mFamily);
2306 nsRect clipRect = unionRect;
2308 for (i = 0; i < right; ++i) {
2309 // Make sure not to draw outside the character
2310 nscoord dx = NS_MAX(end[i], aRect.x);
2311 nscoord fillEnd = NS_MIN(start[i+1], aRect.XMost());
2312 while (dx < fillEnd) {
2313 clipRect.x = dx;
2314 clipRect.width = NS_MIN(bm.rightBearing - bm.leftBearing, fillEnd - dx);
2315 AutoPushClipRect clip(aRenderingContext, clipRect);
2316 dx -= bm.leftBearing;
2317 aRenderingContext.DrawString(chGlue.code, chGlue.Length(), dx, dy);
2318 dx += bm.rightBearing;
2322 #ifdef DEBUG
2323 else { // no glue
2324 for (i = 0; i < right; ++i) {
2325 NS_ASSERTION(end[i] >= start[i+1],
2326 "gap between parts with missing glue glyph");
2329 #endif
2330 return NS_OK;