layout/mathml/nsMathMLChar.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial