michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMathMLChar.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsRenderingContext.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsIPersistentProperties2.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "prprf.h" // For PR_snprintf() michael@0: michael@0: #include "nsDisplayList.h" michael@0: michael@0: #include "nsMathMLOperators.h" michael@0: #include michael@0: michael@0: #include "gfxMathTable.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: //#define NOISY_SEARCH 1 michael@0: michael@0: static const float kLargeOpFactor = float(M_SQRT2); michael@0: static const float kIntegralFactor = 2.0; michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: static const nsGlyphCode kNullGlyph = {{{0, 0}}, 0}; michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // nsGlyphTable is a class that provides an interface for accessing glyphs michael@0: // of stretchy chars. It acts like a table that stores the variants of bigger michael@0: // sizes (if any) and the partial glyphs needed to build extensible symbols. michael@0: // michael@0: // Bigger sizes (if any) of the char can then be retrieved with BigOf(...). michael@0: // Partial glyphs can be retrieved with ElementAt(...). michael@0: // michael@0: // A table consists of "nsGlyphCode"s which are viewed either as Unicode michael@0: // points (for nsPropertiesTable) or as direct glyph indices (for michael@0: // nsOpenTypeTable) michael@0: // ----------------------------------------------------------------------------- michael@0: michael@0: class nsGlyphTable { michael@0: public: michael@0: virtual ~nsGlyphTable() {} michael@0: michael@0: virtual const nsAString& michael@0: FontNameFor(const nsGlyphCode& aGlyphCode) const = 0; michael@0: michael@0: // Getters for the parts michael@0: virtual nsGlyphCode ElementAt(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aPosition) = 0; michael@0: virtual nsGlyphCode BigOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aSize) = 0; michael@0: michael@0: // True if this table contains parts to render this char michael@0: virtual bool HasPartsOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical) = 0; michael@0: michael@0: virtual gfxTextRun* MakeTextRun(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: const nsGlyphCode& aGlyph) = 0; michael@0: protected: michael@0: nsGlyphTable() : mCharCache(0) {} michael@0: // For speedy re-use, we always cache the last data used in the table. michael@0: // mCharCache is the Unicode point of the last char that was queried in this michael@0: // table. michael@0: char16_t mCharCache; michael@0: }; michael@0: michael@0: // An instance of nsPropertiesTable is associated with one primary font. Extra michael@0: // glyphs can be taken in other additional fonts when stretching certain michael@0: // characters. michael@0: // These supplementary fonts are referred to as "external" fonts to the table. michael@0: michael@0: // General format of MathFont Property Files from which glyph data are michael@0: // retrieved: michael@0: // ----------------------------------------------------------------------------- michael@0: // Each font should have its set of glyph data. For example, the glyph data for michael@0: // the "Symbol" font and the "MT Extra" font are in "mathfontSymbol.properties" michael@0: // and "mathfontMTExtra.properties", respectively. The mathfont property file michael@0: // is a set of all the stretchy MathML characters that can be rendered with that michael@0: // font using larger and/or partial glyphs. The entry of each stretchy character michael@0: // in the mathfont property file gives, in that order, the 4 partial glyphs: michael@0: // Top (or Left), Middle, Bottom (or Right), Glue; and the variants of bigger michael@0: // sizes (if any). michael@0: // A position that is not relevant to a particular character is indicated there michael@0: // with the UNICODE REPLACEMENT CHARACTER 0xFFFD. michael@0: // ----------------------------------------------------------------------------- michael@0: michael@0: #define NS_TABLE_STATE_ERROR -1 michael@0: #define NS_TABLE_STATE_EMPTY 0 michael@0: #define NS_TABLE_STATE_READY 1 michael@0: michael@0: // helper to trim off comments from data in a MathFont Property File michael@0: static void michael@0: Clean(nsString& aValue) michael@0: { michael@0: // chop the trailing # comment portion if any ... michael@0: int32_t comment = aValue.RFindChar('#'); michael@0: if (comment > 0) aValue.Truncate(comment); michael@0: aValue.CompressWhitespace(); michael@0: } michael@0: michael@0: // helper to load a MathFont Property File michael@0: static nsresult michael@0: LoadProperties(const nsString& aName, michael@0: nsCOMPtr& aProperties) michael@0: { michael@0: nsAutoString uriStr; michael@0: uriStr.AssignLiteral("resource://gre/res/fonts/mathfont"); michael@0: uriStr.Append(aName); michael@0: uriStr.StripWhitespace(); // that may come from aName michael@0: uriStr.AppendLiteral(".properties"); michael@0: return NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(aProperties), michael@0: NS_ConvertUTF16toUTF8(uriStr)); michael@0: } michael@0: michael@0: class nsPropertiesTable MOZ_FINAL : public nsGlyphTable { michael@0: public: michael@0: explicit nsPropertiesTable(const nsString& aPrimaryFontName) michael@0: : mFontName(1) // ensure space for primary font name. michael@0: , mState(NS_TABLE_STATE_EMPTY) michael@0: { michael@0: MOZ_COUNT_CTOR(nsPropertiesTable); michael@0: mFontName.AppendElement(aPrimaryFontName); michael@0: } michael@0: michael@0: ~nsPropertiesTable() michael@0: { michael@0: MOZ_COUNT_DTOR(nsPropertiesTable); michael@0: } michael@0: michael@0: const nsAString& PrimaryFontName() const michael@0: { michael@0: return mFontName[0]; michael@0: } michael@0: michael@0: const nsAString& michael@0: FontNameFor(const nsGlyphCode& aGlyphCode) const MOZ_OVERRIDE michael@0: { michael@0: NS_ASSERTION(!aGlyphCode.IsGlyphID(), michael@0: "nsPropertiesTable can only access glyphs by code point"); michael@0: return mFontName[aGlyphCode.font]; michael@0: } michael@0: michael@0: virtual nsGlyphCode ElementAt(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aPosition) MOZ_OVERRIDE; michael@0: michael@0: virtual nsGlyphCode BigOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aSize) MOZ_OVERRIDE michael@0: { michael@0: return ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, michael@0: aChar, aVertical, 4 + aSize); michael@0: } michael@0: michael@0: virtual bool HasPartsOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical) MOZ_OVERRIDE michael@0: { michael@0: return (ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, michael@0: aChar, aVertical, 0).Exists() || michael@0: ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, michael@0: aChar, aVertical, 1).Exists() || michael@0: ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, michael@0: aChar, aVertical, 2).Exists() || michael@0: ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, michael@0: aChar, aVertical, 3).Exists()); michael@0: } michael@0: michael@0: virtual gfxTextRun* MakeTextRun(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: const nsGlyphCode& aGlyph) MOZ_OVERRIDE; michael@0: private: michael@0: michael@0: // mFontName[0] is the primary font associated to this table. The others michael@0: // are possible "external" fonts for glyphs not in the primary font michael@0: // but which are needed to stretch certain characters in the table michael@0: nsTArray mFontName; michael@0: michael@0: // Tri-state variable for error/empty/ready michael@0: int32_t mState; michael@0: michael@0: // The set of glyph data in this table, as provided by the MathFont Property michael@0: // File michael@0: nsCOMPtr mGlyphProperties; michael@0: michael@0: // mGlyphCache is a buffer containing the glyph data associated with michael@0: // mCharCache. michael@0: // For a property line 'key = value' in the MathFont Property File, michael@0: // mCharCache will retain the 'key' -- which is a Unicode point, while michael@0: // mGlyphCache will retain the 'value', which is a consecutive list of michael@0: // nsGlyphCodes, i.e., the pairs of 'code@font' needed by the char -- in michael@0: // which 'code@0' can be specified michael@0: // without the optional '@0'. However, to ease subsequent processing, michael@0: // mGlyphCache excludes the '@' symbol and explicitly inserts all optional '0' michael@0: // that indicates the primary font identifier. Specifically therefore, the michael@0: // k-th glyph is characterized by : michael@0: // 1) mGlyphCache[3*k],mGlyphCache[3*k+1] : its Unicode point michael@0: // 2) mGlyphCache[3*k+2] : the numeric identifier of the font where it comes michael@0: // from. michael@0: // A font identifier of '0' means the default primary font associated to this michael@0: // table. Other digits map to the "external" fonts that may have been michael@0: // specified in the MathFont Property File. michael@0: nsString mGlyphCache; michael@0: }; michael@0: michael@0: /* virtual */ michael@0: nsGlyphCode michael@0: nsPropertiesTable::ElementAt(gfxContext* /* aThebesContext */, michael@0: int32_t /* aAppUnitsPerDevPixel */, michael@0: gfxFontGroup* /* aFontGroup */, michael@0: char16_t aChar, michael@0: bool /* aVertical */, michael@0: uint32_t aPosition) michael@0: { michael@0: if (mState == NS_TABLE_STATE_ERROR) return kNullGlyph; michael@0: // Load glyph properties if this is the first time we have been here michael@0: if (mState == NS_TABLE_STATE_EMPTY) { michael@0: nsresult rv = LoadProperties(mFontName[0], mGlyphProperties); michael@0: #ifdef DEBUG michael@0: nsAutoCString uriStr; michael@0: uriStr.AssignLiteral("resource://gre/res/fonts/mathfont"); michael@0: LossyAppendUTF16toASCII(mFontName[0], uriStr); michael@0: uriStr.StripWhitespace(); // that may come from mFontName michael@0: uriStr.AppendLiteral(".properties"); michael@0: printf("Loading %s ... %s\n", michael@0: uriStr.get(), michael@0: (NS_FAILED(rv)) ? "Failed" : "Done"); michael@0: #endif michael@0: if (NS_FAILED(rv)) { michael@0: mState = NS_TABLE_STATE_ERROR; // never waste time with this table again michael@0: return kNullGlyph; michael@0: } michael@0: mState = NS_TABLE_STATE_READY; michael@0: michael@0: // see if there are external fonts needed for certain chars in this table michael@0: nsAutoCString key; michael@0: nsAutoString value; michael@0: for (int32_t i = 1; ; i++) { michael@0: key.AssignLiteral("external."); michael@0: key.AppendInt(i, 10); michael@0: rv = mGlyphProperties->GetStringProperty(key, value); michael@0: if (NS_FAILED(rv)) break; michael@0: Clean(value); michael@0: mFontName.AppendElement(value); // i.e., mFontName[i] holds this font name michael@0: } michael@0: } michael@0: michael@0: // Update our cache if it is not associated to this character michael@0: if (mCharCache != aChar) { michael@0: // The key in the property file is interpreted as ASCII and kept michael@0: // as such ... michael@0: char key[10]; PR_snprintf(key, sizeof(key), "\\u%04X", aChar); michael@0: nsAutoString value; michael@0: nsresult rv = mGlyphProperties->GetStringProperty(nsDependentCString(key), michael@0: value); michael@0: if (NS_FAILED(rv)) return kNullGlyph; michael@0: Clean(value); michael@0: // See if this char uses external fonts; e.g., if the 2nd glyph is taken michael@0: // from the external font '1', the property line looks like michael@0: // \uNNNN = \uNNNN\uNNNN@1\uNNNN. michael@0: // This is where mGlyphCache is pre-processed to explicitly store all glyph michael@0: // codes as combined pairs of 'code@font', excluding the '@' separator. This michael@0: // means that mGlyphCache[3*k],mGlyphCache[3*k+1] will later be rendered michael@0: // with mFontName[mGlyphCache[3*k+2]] michael@0: // Note: font identifier is internally an ASCII digit to avoid the null michael@0: // char issue michael@0: nsAutoString buffer; michael@0: int32_t length = value.Length(); michael@0: int32_t i = 0; // index in value michael@0: while (i < length) { michael@0: char16_t code = value[i]; michael@0: ++i; michael@0: buffer.Append(code); michael@0: // Read the next word if we have a non-BMP character. michael@0: if (i < length && NS_IS_HIGH_SURROGATE(code)) { michael@0: code = value[i]; michael@0: ++i; michael@0: } else { michael@0: code = char16_t('\0'); michael@0: } michael@0: buffer.Append(code); michael@0: michael@0: // See if an external font is needed for the code point. michael@0: // Limit of 9 external fonts michael@0: char16_t font = 0; michael@0: if (i+1 < length && value[i] == char16_t('@') && michael@0: value[i+1] >= char16_t('0') && value[i+1] <= char16_t('9')) { michael@0: ++i; michael@0: font = value[i] - '0'; michael@0: ++i; michael@0: if (font >= mFontName.Length()) { michael@0: NS_ERROR("Nonexistent font referenced in glyph table"); michael@0: return kNullGlyph; michael@0: } michael@0: // The char cannot be handled if this font is not installed michael@0: if (!mFontName[font].Length()) { michael@0: return kNullGlyph; michael@0: } michael@0: } michael@0: buffer.Append(font); michael@0: } michael@0: // update our cache with the new settings michael@0: mGlyphCache.Assign(buffer); michael@0: mCharCache = aChar; michael@0: } michael@0: michael@0: // 3* is to account for the code@font pairs michael@0: uint32_t index = 3*aPosition; michael@0: if (index+2 >= mGlyphCache.Length()) return kNullGlyph; michael@0: nsGlyphCode ch; michael@0: ch.code[0] = mGlyphCache.CharAt(index); michael@0: ch.code[1] = mGlyphCache.CharAt(index + 1); michael@0: ch.font = mGlyphCache.CharAt(index + 2); michael@0: return ch.code[0] == char16_t(0xFFFD) ? kNullGlyph : ch; michael@0: } michael@0: michael@0: /* virtual */ michael@0: gfxTextRun* michael@0: nsPropertiesTable::MakeTextRun(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: const nsGlyphCode& aGlyph) michael@0: { michael@0: NS_ASSERTION(!aGlyph.IsGlyphID(), michael@0: "nsPropertiesTable can only access glyphs by code point"); michael@0: return aFontGroup-> michael@0: MakeTextRun(aGlyph.code, aGlyph.Length(), aThebesContext, michael@0: aAppUnitsPerDevPixel, 0); michael@0: } michael@0: michael@0: // An instance of nsOpenTypeTable is associated with one gfxFontEntry that michael@0: // corresponds to an Open Type font with a MATH table. All the glyphs come from michael@0: // the same font and the calls to access size variants and parts are directly michael@0: // forwarded to the gfx code. michael@0: class nsOpenTypeTable MOZ_FINAL : public nsGlyphTable { michael@0: public: michael@0: ~nsOpenTypeTable() michael@0: { michael@0: MOZ_COUNT_DTOR(nsOpenTypeTable); michael@0: } michael@0: michael@0: virtual nsGlyphCode ElementAt(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aPosition) MOZ_OVERRIDE; michael@0: virtual nsGlyphCode BigOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aSize) MOZ_OVERRIDE; michael@0: virtual bool HasPartsOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical) MOZ_OVERRIDE; michael@0: michael@0: const nsAString& michael@0: FontNameFor(const nsGlyphCode& aGlyphCode) const MOZ_OVERRIDE { michael@0: NS_ASSERTION(aGlyphCode.IsGlyphID(), michael@0: "nsOpenTypeTable can only access glyphs by id"); michael@0: return mFontEntry->FamilyName(); michael@0: } michael@0: michael@0: virtual gfxTextRun* MakeTextRun(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: const nsGlyphCode& aGlyph) MOZ_OVERRIDE; michael@0: michael@0: // This returns a new OpenTypeTable instance to give access to OpenType MATH michael@0: // table or nullptr if the font does not have such table. Ownership is passed michael@0: // to the caller. michael@0: static nsOpenTypeTable* Create(gfxFont* aFont) michael@0: { michael@0: if (!aFont->GetFontEntry()->TryGetMathTable(aFont)) { michael@0: return nullptr; michael@0: } michael@0: return new nsOpenTypeTable(aFont->GetFontEntry()); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mFontEntry; michael@0: uint32_t mGlyphID; michael@0: michael@0: explicit nsOpenTypeTable(gfxFontEntry* aFontEntry) michael@0: : mFontEntry(aFontEntry) { michael@0: MOZ_COUNT_CTOR(nsOpenTypeTable); michael@0: } michael@0: michael@0: void UpdateCache(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar); michael@0: }; michael@0: michael@0: void michael@0: nsOpenTypeTable::UpdateCache(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar) michael@0: { michael@0: if (mCharCache != aChar) { michael@0: nsAutoPtr textRun; michael@0: textRun = aFontGroup-> michael@0: MakeTextRun(&aChar, 1, aThebesContext, aAppUnitsPerDevPixel, 0); michael@0: const gfxTextRun::CompressedGlyph& data = textRun->GetCharacterGlyphs()[0]; michael@0: if (data.IsSimpleGlyph()) { michael@0: mGlyphID = data.GetSimpleGlyph(); michael@0: } else if (data.GetGlyphCount() == 1) { michael@0: mGlyphID = textRun->GetDetailedGlyphs(0)->mGlyphID; michael@0: } else { michael@0: mGlyphID = 0; michael@0: } michael@0: mCharCache = aChar; michael@0: } michael@0: } michael@0: michael@0: /* virtual */ michael@0: nsGlyphCode michael@0: nsOpenTypeTable::ElementAt(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aPosition) michael@0: { michael@0: UpdateCache(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, aChar); michael@0: michael@0: uint32_t parts[4]; michael@0: if (!mFontEntry->GetMathVariantsParts(mGlyphID, aVertical, parts)) { michael@0: return kNullGlyph; michael@0: } michael@0: michael@0: uint32_t glyphID = parts[aPosition]; michael@0: if (!glyphID) { michael@0: return kNullGlyph; michael@0: } michael@0: nsGlyphCode glyph; michael@0: glyph.glyphID = glyphID; michael@0: glyph.font = -1; michael@0: return glyph; michael@0: } michael@0: michael@0: /* virtual */ michael@0: nsGlyphCode michael@0: nsOpenTypeTable::BigOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical, michael@0: uint32_t aSize) michael@0: { michael@0: UpdateCache(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, aChar); michael@0: michael@0: uint32_t glyphID = michael@0: mFontEntry->GetMathVariantsSize(mGlyphID, aVertical, aSize); michael@0: if (!glyphID) { michael@0: return kNullGlyph; michael@0: } michael@0: michael@0: nsGlyphCode glyph; michael@0: glyph.glyphID = glyphID; michael@0: glyph.font = -1; michael@0: return glyph; michael@0: } michael@0: michael@0: /* virtual */ michael@0: bool michael@0: nsOpenTypeTable::HasPartsOf(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: char16_t aChar, michael@0: bool aVertical) michael@0: { michael@0: UpdateCache(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, aChar); michael@0: michael@0: uint32_t parts[4]; michael@0: if (!mFontEntry->GetMathVariantsParts(mGlyphID, aVertical, parts)) { michael@0: return false; michael@0: } michael@0: michael@0: return parts[0] || parts[1] || parts[2] || parts[3]; michael@0: } michael@0: michael@0: /* virtual */ michael@0: gfxTextRun* michael@0: nsOpenTypeTable::MakeTextRun(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerDevPixel, michael@0: gfxFontGroup* aFontGroup, michael@0: const nsGlyphCode& aGlyph) michael@0: { michael@0: NS_ASSERTION(aGlyph.IsGlyphID(), michael@0: "nsOpenTypeTable can only access glyphs by id"); michael@0: michael@0: gfxTextRunFactory::Parameters params = { michael@0: aThebesContext, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel michael@0: }; michael@0: gfxTextRun* textRun = gfxTextRun::Create(¶ms, 1, aFontGroup, 0); michael@0: textRun->AddGlyphRun(aFontGroup->GetFontAt(0), gfxTextRange::kFontGroup, 0, michael@0: false); michael@0: gfxTextRun::DetailedGlyph detailedGlyph; michael@0: detailedGlyph.mGlyphID = aGlyph.glyphID; michael@0: detailedGlyph.mAdvance = michael@0: NSToCoordRound(aAppUnitsPerDevPixel * michael@0: aFontGroup->GetFontAt(0)-> michael@0: GetGlyphHAdvance(aThebesContext, aGlyph.glyphID)); michael@0: detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0; michael@0: gfxShapedText::CompressedGlyph g; michael@0: g.SetComplex(true, true, 1); michael@0: textRun->SetGlyphs(0, g, &detailedGlyph); michael@0: michael@0: return textRun; michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // This is the list of all the applicable glyph tables. michael@0: // We will maintain a single global instance that will only reveal those michael@0: // glyph tables that are associated to fonts currently installed on the michael@0: // user' system. The class is an XPCOM shutdown observer to allow us to michael@0: // free its allocated data at shutdown michael@0: michael@0: class nsGlyphTableList : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: nsPropertiesTable mUnicodeTable; michael@0: michael@0: nsGlyphTableList() michael@0: : mUnicodeTable(NS_LITERAL_STRING("Unicode")) michael@0: { michael@0: MOZ_COUNT_CTOR(nsGlyphTableList); michael@0: } michael@0: michael@0: virtual ~nsGlyphTableList() michael@0: { michael@0: MOZ_COUNT_DTOR(nsGlyphTableList); michael@0: } michael@0: michael@0: nsresult Initialize(); michael@0: nsresult Finalize(); michael@0: michael@0: // Add a glyph table in the list, return the new table that was added michael@0: nsGlyphTable* michael@0: AddGlyphTable(const nsString& aPrimaryFontName); michael@0: michael@0: // Find the glyph table in the list corresponding to the given font family. michael@0: nsGlyphTable* michael@0: GetGlyphTableFor(const nsAString& aFamily); michael@0: michael@0: private: michael@0: nsPropertiesTable* PropertiesTableAt(int32_t aIndex) { michael@0: return &mPropertiesTableList.ElementAt(aIndex); michael@0: } michael@0: int32_t PropertiesTableCount() { michael@0: return mPropertiesTableList.Length(); michael@0: } michael@0: // List of glyph tables; michael@0: nsTArray mPropertiesTableList; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsGlyphTableList, nsIObserver) michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // Here is the global list of applicable glyph tables that we will be using michael@0: static nsGlyphTableList* gGlyphTableList = nullptr; michael@0: michael@0: static bool gGlyphTableInitialized = false; michael@0: michael@0: // XPCOM shutdown observer michael@0: NS_IMETHODIMP michael@0: nsGlyphTableList::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* someData) michael@0: { michael@0: Finalize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Add an observer to XPCOM shutdown so that we can free our data at shutdown michael@0: nsresult michael@0: nsGlyphTableList::Initialize() michael@0: { michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (!obs) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Remove our observer and free the memory that were allocated for us michael@0: nsresult michael@0: nsGlyphTableList::Finalize() michael@0: { michael@0: // Remove our observer from the observer service michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) michael@0: rv = obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: else michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: gGlyphTableInitialized = false; michael@0: // our oneself will be destroyed when our |Release| is called by the observer michael@0: return rv; michael@0: } michael@0: michael@0: nsGlyphTable* michael@0: nsGlyphTableList::AddGlyphTable(const nsString& aPrimaryFontName) michael@0: { michael@0: // See if there is already a special table for this family. michael@0: nsGlyphTable* glyphTable = GetGlyphTableFor(aPrimaryFontName); michael@0: if (glyphTable != &mUnicodeTable) michael@0: return glyphTable; michael@0: michael@0: // allocate a table michael@0: glyphTable = mPropertiesTableList.AppendElement(aPrimaryFontName); michael@0: return glyphTable; michael@0: } michael@0: michael@0: nsGlyphTable* michael@0: nsGlyphTableList::GetGlyphTableFor(const nsAString& aFamily) michael@0: { michael@0: for (int32_t i = 0; i < PropertiesTableCount(); i++) { michael@0: nsPropertiesTable* glyphTable = PropertiesTableAt(i); michael@0: const nsAString& fontName = glyphTable->PrimaryFontName(); michael@0: // TODO: would be nice to consider StripWhitespace and other aliasing michael@0: if (fontName.Equals(aFamily, nsCaseInsensitiveStringComparator())) { michael@0: return glyphTable; michael@0: } michael@0: } michael@0: // Fall back to default Unicode table michael@0: return &mUnicodeTable; michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: michael@0: static nsresult michael@0: InitGlobals(nsPresContext* aPresContext) michael@0: { michael@0: NS_ASSERTION(!gGlyphTableInitialized, "Error -- already initialized"); michael@0: gGlyphTableInitialized = true; michael@0: michael@0: // Allocate the placeholders for the preferred parts and variants michael@0: nsresult rv = NS_ERROR_OUT_OF_MEMORY; michael@0: gGlyphTableList = new nsGlyphTableList(); michael@0: if (gGlyphTableList) { michael@0: rv = gGlyphTableList->Initialize(); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: delete gGlyphTableList; michael@0: gGlyphTableList = nullptr; michael@0: return rv; michael@0: } michael@0: // The gGlyphTableList has been successfully registered as a shutdown michael@0: // observer and will be deleted at shutdown. We now add some private michael@0: // per font-family tables for stretchy operators, in order of preference. michael@0: // Do not include the Unicode table in this list. michael@0: if (!gGlyphTableList->AddGlyphTable(NS_LITERAL_STRING("MathJax_Main")) || michael@0: !gGlyphTableList->AddGlyphTable(NS_LITERAL_STRING("STIXNonUnicode")) || michael@0: !gGlyphTableList->AddGlyphTable(NS_LITERAL_STRING("STIXSizeOneSym")) || michael@0: !gGlyphTableList->AddGlyphTable(NS_LITERAL_STRING("Standard Symbols L")) michael@0: #ifdef XP_WIN michael@0: || !gGlyphTableList->AddGlyphTable(NS_LITERAL_STRING("Symbol")) michael@0: #endif michael@0: ) { michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // And now the implementation of nsMathMLChar michael@0: michael@0: nsMathMLChar::~nsMathMLChar() michael@0: { michael@0: MOZ_COUNT_DTOR(nsMathMLChar); michael@0: mStyleContext->Release(); michael@0: } michael@0: michael@0: nsStyleContext* michael@0: nsMathMLChar::GetStyleContext() const michael@0: { michael@0: NS_ASSERTION(mStyleContext, "chars should always have style context"); michael@0: return mStyleContext; michael@0: } michael@0: michael@0: void michael@0: nsMathMLChar::SetStyleContext(nsStyleContext* aStyleContext) michael@0: { michael@0: NS_PRECONDITION(aStyleContext, "null ptr"); michael@0: if (aStyleContext != mStyleContext) { michael@0: if (mStyleContext) michael@0: mStyleContext->Release(); michael@0: if (aStyleContext) { michael@0: mStyleContext = aStyleContext; michael@0: aStyleContext->AddRef(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMathMLChar::SetData(nsPresContext* aPresContext, michael@0: nsString& aData) michael@0: { michael@0: if (!gGlyphTableInitialized) { michael@0: InitGlobals(aPresContext); michael@0: } michael@0: mData = aData; michael@0: // some assumptions until proven otherwise michael@0: // note that mGlyph is not initialized michael@0: mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED; michael@0: mBoundingMetrics = nsBoundingMetrics(); michael@0: // check if stretching is applicable ... michael@0: if (gGlyphTableList && (1 == mData.Length())) { michael@0: mDirection = nsMathMLOperators::GetStretchyDirection(mData); michael@0: // default tentative table (not the one that is necessarily going michael@0: // to be used) michael@0: } michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: /* michael@0: The Stretch: michael@0: @param aContainerSize - suggested size for the stretched char michael@0: @param aDesiredStretchSize - OUT parameter. The desired size michael@0: after stretching. If no stretching is done, the output will michael@0: simply give the base size. michael@0: michael@0: How it works? michael@0: Summary:- michael@0: The Stretch() method first looks for a glyph of appropriate michael@0: size; If a glyph is found, it is cached by this object and michael@0: its size is returned in aDesiredStretchSize. The cached michael@0: glyph will then be used at the painting stage. michael@0: If no glyph of appropriate size is found, a search is made michael@0: to see if the char can be built by parts. michael@0: michael@0: Details:- michael@0: A character gets stretched through the following pipeline : michael@0: michael@0: 1) If the base size of the char is sufficient to cover the michael@0: container' size, we use that. If not, it will still be michael@0: used as a fallback if the other stages in the pipeline fail. michael@0: Issues : michael@0: a) The base size, the parts and the variants of a char can michael@0: be in different fonts. For eg., the base size for '(' should michael@0: come from a normal ascii font if CMEX10 is used, since CMEX10 michael@0: only contains the stretched versions. Hence, there are two michael@0: style contexts in use throughout the process. The leaf style michael@0: context of the char holds fonts with which to try to stretch michael@0: the char. The parent style context of the char contains fonts michael@0: for normal rendering. So the parent context is the one used michael@0: to get the initial base size at the start of the pipeline. michael@0: b) For operators that can be largeop's in display mode, michael@0: we will skip the base size even if it fits, so that michael@0: the next stage in the pipeline is given a chance to find michael@0: a largeop variant. If the next stage fails, we fallback michael@0: to the base size. michael@0: michael@0: 2) We search for the first larger variant of the char that fits the michael@0: container' size. We first search for larger variants using the glyph michael@0: table corresponding to the first existing font specified in the list of michael@0: stretchy fonts held by the leaf style context (from -moz-math-stretchy in michael@0: mathml.css). Generic fonts are resolved by the preference michael@0: "font.mathfont-family". michael@0: Issues : michael@0: a) the largeop and display settings determine the starting michael@0: size when we do the above search, regardless of whether michael@0: smaller variants already fit the container' size. michael@0: b) if it is a largeopOnly request (i.e., a displaystyle operator michael@0: with largeop=true and stretchy=false), we break after finding michael@0: the first starting variant, regardless of whether that michael@0: variant fits the container's size. michael@0: michael@0: 3) If a variant of appropriate size wasn't found, we see if the char michael@0: can be built by parts using the same glyph table. michael@0: Issue: michael@0: There are chars that have no middle and glue glyphs. For michael@0: such chars, the parts need to be joined using the rule. michael@0: By convention (TeXbook p.225), the descent of the parts is michael@0: zero while their ascent gives the thickness of the rule that michael@0: should be used to join them. michael@0: michael@0: 4) If a match was not found in that glyph table, repeat from 2 to search the michael@0: ordered list of stretchy fonts for the first font with a glyph table that michael@0: provides a fit to the container size. If no fit is found, the closest fit michael@0: is used. michael@0: michael@0: Of note: michael@0: When the pipeline completes successfully, the desired size of the michael@0: stretched char can actually be slightly larger or smaller than michael@0: aContainerSize. But it is the responsibility of the caller to michael@0: account for the spacing when setting aContainerSize, and to leave michael@0: any extra margin when placing the stretched char. michael@0: */ michael@0: // ----------------------------------------------------------------------------- michael@0: michael@0: michael@0: // plain TeX settings (TeXbook p.152) michael@0: #define NS_MATHML_DELIMITER_FACTOR 0.901f michael@0: #define NS_MATHML_DELIMITER_SHORTFALL_POINTS 5.0f michael@0: michael@0: static bool michael@0: IsSizeOK(nsPresContext* aPresContext, nscoord a, nscoord b, uint32_t aHint) michael@0: { michael@0: // Normal: True if 'a' is around +/-10% of the target 'b' (10% is michael@0: // 1-DelimiterFactor). This often gives a chance to the base size to michael@0: // win, especially in the context of without tall elements michael@0: // or in sloppy markups without protective michael@0: bool isNormal = michael@0: (aHint & NS_STRETCH_NORMAL) && michael@0: Abs(a - b) < (1.0f - NS_MATHML_DELIMITER_FACTOR) * float(b); michael@0: michael@0: // Nearer: True if 'a' is around max{ +/-10% of 'b' , 'b' - 5pt }, michael@0: // as documented in The TeXbook, Ch.17, p.152. michael@0: // i.e. within 10% and within 5pt michael@0: bool isNearer = false; michael@0: if (aHint & (NS_STRETCH_NEARER | NS_STRETCH_LARGEOP)) { michael@0: float c = std::max(float(b) * NS_MATHML_DELIMITER_FACTOR, michael@0: float(b) - nsPresContext:: michael@0: CSSPointsToAppUnits(NS_MATHML_DELIMITER_SHORTFALL_POINTS)); michael@0: isNearer = Abs(b - a) <= float(b) - c; michael@0: } michael@0: michael@0: // Smaller: Mainly for transitory use, to compare two candidate michael@0: // choices michael@0: bool isSmaller = michael@0: (aHint & NS_STRETCH_SMALLER) && michael@0: float(a) >= NS_MATHML_DELIMITER_FACTOR * float(b) && michael@0: a <= b; michael@0: michael@0: // Larger: Critical to the sqrt code to ensure that the radical michael@0: // size is tall enough michael@0: bool isLarger = michael@0: (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP)) && michael@0: a >= b; michael@0: michael@0: return (isNormal || isSmaller || isNearer || isLarger); michael@0: } michael@0: michael@0: static bool michael@0: IsSizeBetter(nscoord a, nscoord olda, nscoord b, uint32_t aHint) michael@0: { michael@0: if (0 == olda) michael@0: return true; michael@0: if (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP)) michael@0: return (a >= olda) ? (olda < b) : (a >= b); michael@0: if (aHint & NS_STRETCH_SMALLER) michael@0: return (a <= olda) ? (olda > b) : (a <= b); michael@0: michael@0: // XXXkt prob want log scale here i.e. 1.5 is closer to 1 than 0.5 michael@0: return Abs(a - b) < Abs(olda - b); michael@0: } michael@0: michael@0: // We want to place the glyphs even when they don't fit at their michael@0: // full extent, i.e., we may clip to tolerate a small amount of michael@0: // overlap between the parts. This is important to cater for fonts michael@0: // with long glues. michael@0: static nscoord michael@0: ComputeSizeFromParts(nsPresContext* aPresContext, michael@0: nsGlyphCode* aGlyphs, michael@0: nscoord* aSizes, michael@0: nscoord aTargetSize) michael@0: { michael@0: enum {first, middle, last, glue}; michael@0: // Add the parts that cannot be left out. michael@0: nscoord sum = 0; michael@0: for (int32_t i = first; i <= last; i++) { michael@0: if (aGlyphs[i] != aGlyphs[glue]) { michael@0: sum += aSizes[i]; michael@0: } michael@0: } michael@0: michael@0: // Determine how much is used in joins michael@0: nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: int32_t joins = aGlyphs[middle] == aGlyphs[glue] ? 1 : 2; michael@0: michael@0: // Pick a maximum size using a maximum number of glue glyphs that we are michael@0: // prepared to draw for one character. michael@0: const int32_t maxGlyphs = 1000; michael@0: michael@0: // This also takes into account the fact that, if the glue has no size, michael@0: // then the character can't be lengthened. michael@0: nscoord maxSize = sum - 2 * joins * oneDevPixel + maxGlyphs * aSizes[glue]; michael@0: if (maxSize < aTargetSize) michael@0: return maxSize; // settle with the maximum size michael@0: michael@0: // Get the minimum allowable size using some flex. michael@0: nscoord minSize = NSToCoordRound(NS_MATHML_DELIMITER_FACTOR * sum); michael@0: michael@0: if (minSize > aTargetSize) michael@0: return minSize; // settle with the minimum size michael@0: michael@0: // Fill-up the target area michael@0: return aTargetSize; michael@0: } michael@0: michael@0: // Insert aFallbackFamilies before the first generic family in or at the end michael@0: // of a CSS aFontName. michael@0: static void michael@0: AddFallbackFonts(nsAString& aFontName, const nsAString& aFallbackFamilies) michael@0: { michael@0: if (aFallbackFamilies.IsEmpty()) michael@0: return; michael@0: michael@0: if (aFontName.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: static const char16_t kSingleQuote = char16_t('\''); michael@0: static const char16_t kDoubleQuote = char16_t('\"'); michael@0: static const char16_t kComma = char16_t(','); michael@0: michael@0: const char16_t *p_begin, *p_end; michael@0: aFontName.BeginReading(p_begin); michael@0: aFontName.EndReading(p_end); michael@0: michael@0: const char16_t *p = p_begin; michael@0: const char16_t *p_name = nullptr; michael@0: while (p < p_end) { michael@0: while (nsCRT::IsAsciiSpace(*p)) michael@0: if (++p == p_end) michael@0: goto insert; michael@0: michael@0: p_name = p; michael@0: if (*p == kSingleQuote || *p == kDoubleQuote) { michael@0: // quoted font family michael@0: char16_t quoteMark = *p; michael@0: if (++p == p_end) michael@0: goto insert; michael@0: michael@0: // XXX What about CSS character escapes? michael@0: while (*p != quoteMark) michael@0: if (++p == p_end) michael@0: goto insert; michael@0: michael@0: while (++p != p_end && *p != kComma) michael@0: /* nothing */ ; michael@0: michael@0: } else { michael@0: // unquoted font family michael@0: const char16_t *nameStart = p; michael@0: while (++p != p_end && *p != kComma) michael@0: /* nothing */ ; michael@0: michael@0: nsAutoString family; michael@0: family = Substring(nameStart, p); michael@0: family.CompressWhitespace(false, true); michael@0: michael@0: uint8_t generic; michael@0: nsFont::GetGenericID(family, &generic); michael@0: if (generic != kGenericFont_NONE) michael@0: goto insert; michael@0: } michael@0: michael@0: ++p; // may advance past p_end michael@0: } michael@0: michael@0: aFontName.Append(NS_LITERAL_STRING(",") + aFallbackFamilies); michael@0: return; michael@0: michael@0: insert: michael@0: if (p_name) { michael@0: aFontName.Insert(aFallbackFamilies + NS_LITERAL_STRING(","), michael@0: p_name - p_begin); michael@0: } michael@0: else { // whitespace or empty michael@0: aFontName = aFallbackFamilies; michael@0: } michael@0: } michael@0: michael@0: // Update the font if there is a family change and returns the font group. michael@0: bool michael@0: nsMathMLChar::SetFontFamily(nsPresContext* aPresContext, michael@0: const nsGlyphTable* aGlyphTable, michael@0: const nsGlyphCode& aGlyphCode, michael@0: const nsAString& aDefaultFamily, michael@0: nsFont& aFont, michael@0: nsRefPtr* aFontGroup) michael@0: { michael@0: const nsAString& family = michael@0: aGlyphCode.font ? aGlyphTable->FontNameFor(aGlyphCode) : aDefaultFamily; michael@0: if (!*aFontGroup || !family.Equals(aFont.name)) { michael@0: nsFont font = aFont; michael@0: font.name = family; michael@0: nsRefPtr fm; michael@0: aPresContext->DeviceContext()-> michael@0: GetMetricsFor(font, michael@0: mStyleContext->StyleFont()->mLanguage, michael@0: aPresContext->GetUserFontSet(), michael@0: aPresContext->GetTextPerfMetrics(), michael@0: *getter_AddRefs(fm)); michael@0: // Set the font if it is an unicode table michael@0: // or if the same family name has been found michael@0: if (aGlyphTable == &gGlyphTableList->mUnicodeTable || michael@0: fm->GetThebesFontGroup()->GetFontAt(0)->GetFontEntry()-> michael@0: FamilyName() == family) { michael@0: aFont.name = family; michael@0: *aFontGroup = fm->GetThebesFontGroup(); michael@0: } else { michael@0: return false; // We did not set the font michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static nsBoundingMetrics michael@0: MeasureTextRun(gfxContext* aThebesContext, gfxTextRun* aTextRun) michael@0: { michael@0: gfxTextRun::Metrics metrics = michael@0: aTextRun->MeasureText(0, aTextRun->GetLength(), michael@0: gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, michael@0: aThebesContext, nullptr); michael@0: michael@0: nsBoundingMetrics bm; michael@0: bm.leftBearing = NSToCoordFloor(metrics.mBoundingBox.X()); michael@0: bm.rightBearing = NSToCoordCeil(metrics.mBoundingBox.XMost()); michael@0: bm.ascent = NSToCoordCeil(-metrics.mBoundingBox.Y()); michael@0: bm.descent = NSToCoordCeil(metrics.mBoundingBox.YMost()); michael@0: bm.width = NSToCoordRound(metrics.mAdvanceWidth); michael@0: michael@0: return bm; michael@0: } michael@0: michael@0: class nsMathMLChar::StretchEnumContext { michael@0: public: michael@0: StretchEnumContext(nsMathMLChar* aChar, michael@0: nsPresContext* aPresContext, michael@0: gfxContext* aThebesContext, michael@0: nsStretchDirection aStretchDirection, michael@0: nscoord aTargetSize, michael@0: uint32_t aStretchHint, michael@0: nsBoundingMetrics& aStretchedMetrics, michael@0: const nsAString& aFamilies, michael@0: bool& aGlyphFound) michael@0: : mChar(aChar), michael@0: mPresContext(aPresContext), michael@0: mThebesContext(aThebesContext), michael@0: mDirection(aStretchDirection), michael@0: mTargetSize(aTargetSize), michael@0: mStretchHint(aStretchHint), michael@0: mBoundingMetrics(aStretchedMetrics), michael@0: mFamilies(aFamilies), michael@0: mTryVariants(true), michael@0: mTryParts(true), michael@0: mGlyphFound(aGlyphFound) {} michael@0: michael@0: static bool michael@0: EnumCallback(const nsString& aFamily, bool aGeneric, void *aData); michael@0: michael@0: private: michael@0: bool TryVariants(nsGlyphTable* aGlyphTable, michael@0: nsRefPtr* aFontGroup, michael@0: const nsAString& aFamily); michael@0: bool TryParts(nsGlyphTable* aGlyphTable, michael@0: nsRefPtr* aFontGroup, michael@0: const nsAString& aFamily); michael@0: michael@0: nsMathMLChar* mChar; michael@0: nsPresContext* mPresContext; michael@0: gfxContext* mThebesContext; michael@0: const nsStretchDirection mDirection; michael@0: const nscoord mTargetSize; michael@0: const uint32_t mStretchHint; michael@0: nsBoundingMetrics& mBoundingMetrics; michael@0: // Font families to search michael@0: const nsAString& mFamilies; michael@0: michael@0: public: michael@0: bool mTryVariants; michael@0: bool mTryParts; michael@0: michael@0: private: michael@0: nsAutoTArray mTablesTried; michael@0: bool& mGlyphFound; michael@0: }; michael@0: michael@0: michael@0: // 2. See if there are any glyphs of the appropriate size. michael@0: // Returns true if the size is OK, false to keep searching. michael@0: // Always updates the char if a better match is found. michael@0: bool michael@0: nsMathMLChar:: michael@0: StretchEnumContext::TryVariants(nsGlyphTable* aGlyphTable, michael@0: nsRefPtr* aFontGroup, michael@0: const nsAString& aFamily) michael@0: { michael@0: // Use our stretchy style context now that stretching is in progress michael@0: nsStyleContext *sc = mChar->mStyleContext; michael@0: nsFont font = sc->StyleFont()->mFont; michael@0: michael@0: bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL); michael@0: nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel(); michael@0: char16_t uchar = mChar->mData[0]; michael@0: bool largeop = (NS_STRETCH_LARGEOP & mStretchHint) != 0; michael@0: bool largeopOnly = michael@0: largeop && (NS_STRETCH_VARIABLE_MASK & mStretchHint) == 0; michael@0: bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0; michael@0: michael@0: nscoord bestSize = michael@0: isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent michael@0: : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; michael@0: bool haveBetter = false; michael@0: michael@0: // start at size = 1 (size = 0 is the char at its normal size) michael@0: int32_t size = 1; michael@0: nsGlyphCode ch; michael@0: nscoord displayOperatorMinHeight = 0; michael@0: if (largeopOnly) { michael@0: NS_ASSERTION(isVertical, "Stretching should be in the vertical direction"); michael@0: ch = aGlyphTable->BigOf(mThebesContext, oneDevPixel, *aFontGroup, uchar, michael@0: isVertical, 0); michael@0: if (ch.IsGlyphID()) { michael@0: gfxFont* mathFont = aFontGroup->get()->GetFontAt(0); michael@0: // For OpenType MATH fonts, we will rely on the DisplayOperatorMinHeight michael@0: // to select the right size variant. Note that the value is sometimes too michael@0: // small so we use kLargeOpFactor/kIntegralFactor as a minimum value. michael@0: displayOperatorMinHeight = michael@0: NSToCoordRound(mathFont->GetFontEntry()-> michael@0: GetMathConstant(gfxFontEntry::DisplayOperatorMinHeight) * michael@0: mathFont->GetAdjustedSize() * oneDevPixel); michael@0: nsAutoPtr textRun; michael@0: textRun = aGlyphTable->MakeTextRun(mThebesContext, oneDevPixel, michael@0: *aFontGroup, ch); michael@0: nsBoundingMetrics bm = MeasureTextRun(mThebesContext, textRun); michael@0: float largeopFactor = kLargeOpFactor; michael@0: if (NS_STRETCH_INTEGRAL & mStretchHint) { michael@0: // integrals are drawn taller michael@0: largeopFactor = kIntegralFactor; michael@0: } michael@0: nscoord minHeight = largeopFactor * (bm.ascent + bm.descent); michael@0: if (displayOperatorMinHeight < minHeight) { michael@0: displayOperatorMinHeight = minHeight; michael@0: } michael@0: } michael@0: } michael@0: #ifdef NOISY_SEARCH michael@0: printf(" searching in %s ...\n", michael@0: NS_LossyConvertUTF16toASCII(aFamily).get()); michael@0: #endif michael@0: while ((ch = aGlyphTable->BigOf(mThebesContext, oneDevPixel, *aFontGroup, michael@0: uchar, isVertical, size)).Exists()) { michael@0: michael@0: if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamily, font, michael@0: aFontGroup)) { michael@0: // if largeopOnly is set, break now michael@0: if (largeopOnly) break; michael@0: ++size; michael@0: continue; michael@0: } michael@0: michael@0: nsAutoPtr textRun; michael@0: textRun = aGlyphTable->MakeTextRun(mThebesContext, oneDevPixel, michael@0: *aFontGroup, ch); michael@0: nsBoundingMetrics bm = MeasureTextRun(mThebesContext, textRun); michael@0: if (ch.IsGlyphID()) { michael@0: gfxFont* mathFont = aFontGroup->get()->GetFontAt(0); michael@0: if (mathFont->GetFontEntry()->TryGetMathTable(mathFont)) { michael@0: // MeasureTextRun should have set the advance width to the right michael@0: // bearing for OpenType MATH fonts. We now subtract the italic michael@0: // correction, so that nsMathMLmmultiscripts will place the scripts michael@0: // correctly. michael@0: // Note that STIX-Word does not provide italic corrections but its michael@0: // advance widths do not match right bearings. michael@0: // (http://sourceforge.net/p/stixfonts/tracking/50/) michael@0: gfxFloat italicCorrection; michael@0: if (mathFont->GetFontEntry()-> michael@0: GetMathItalicsCorrection(ch.glyphID, &italicCorrection)) { michael@0: bm.width -= michael@0: NSToCoordRound(italicCorrection * michael@0: mathFont->GetAdjustedSize() * oneDevPixel); michael@0: if (bm.width < 0) { michael@0: bm.width = 0; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nscoord charSize = michael@0: isVertical ? bm.ascent + bm.descent michael@0: : bm.rightBearing - bm.leftBearing; michael@0: michael@0: if (largeopOnly || michael@0: IsSizeBetter(charSize, bestSize, mTargetSize, mStretchHint)) { michael@0: mGlyphFound = true; michael@0: if (maxWidth) { michael@0: // IsSizeBetter() checked that charSize < maxsize; michael@0: // Leave ascent, descent, and bestsize as these contain maxsize. michael@0: if (mBoundingMetrics.width < bm.width) michael@0: mBoundingMetrics.width = bm.width; michael@0: if (mBoundingMetrics.leftBearing > bm.leftBearing) michael@0: mBoundingMetrics.leftBearing = bm.leftBearing; michael@0: if (mBoundingMetrics.rightBearing < bm.rightBearing) michael@0: mBoundingMetrics.rightBearing = bm.rightBearing; michael@0: // Continue to check other sizes unless largeopOnly michael@0: haveBetter = largeopOnly; michael@0: } michael@0: else { michael@0: mBoundingMetrics = bm; michael@0: haveBetter = true; michael@0: bestSize = charSize; michael@0: mChar->mGlyphs[0] = textRun; michael@0: mChar->mDraw = DRAW_VARIANT; michael@0: } michael@0: #ifdef NOISY_SEARCH michael@0: printf(" size:%d Current best\n", size); michael@0: #endif michael@0: } michael@0: else { michael@0: #ifdef NOISY_SEARCH michael@0: printf(" size:%d Rejected!\n", size); michael@0: #endif michael@0: if (haveBetter) michael@0: break; // Not making an futher progress, stop searching michael@0: } michael@0: michael@0: // If this a largeop only operator, we stop if the glyph is large enough. michael@0: if (largeopOnly && (bm.ascent + bm.descent) >= displayOperatorMinHeight) { michael@0: break; michael@0: } michael@0: ++size; michael@0: } michael@0: michael@0: return haveBetter && michael@0: (largeopOnly || michael@0: IsSizeOK(mPresContext, bestSize, mTargetSize, mStretchHint)); michael@0: } michael@0: michael@0: // 3. Build by parts. michael@0: // Returns true if the size is OK, false to keep searching. michael@0: // Always updates the char if a better match is found. michael@0: bool michael@0: nsMathMLChar::StretchEnumContext::TryParts(nsGlyphTable* aGlyphTable, michael@0: nsRefPtr* aFontGroup, michael@0: const nsAString& aFamily) michael@0: { michael@0: // Use our stretchy style context now that stretching is in progress michael@0: nsFont font = mChar->mStyleContext->StyleFont()->mFont; michael@0: michael@0: // Compute the bounding metrics of all partial glyphs michael@0: nsAutoPtr textRun[4]; michael@0: nsGlyphCode chdata[4]; michael@0: nsBoundingMetrics bmdata[4]; michael@0: nscoord sizedata[4]; michael@0: michael@0: bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL); michael@0: nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel(); michael@0: char16_t uchar = mChar->mData[0]; michael@0: bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0; michael@0: if (!aGlyphTable->HasPartsOf(mThebesContext, oneDevPixel, *aFontGroup, michael@0: uchar, isVertical)) michael@0: return false; // to next table michael@0: michael@0: for (int32_t i = 0; i < 4; i++) { michael@0: nsGlyphCode ch = aGlyphTable->ElementAt(mThebesContext, oneDevPixel, michael@0: *aFontGroup, uchar, isVertical, i); michael@0: chdata[i] = ch; michael@0: if (ch.Exists()) { michael@0: if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamily, font, michael@0: aFontGroup)) michael@0: return false; michael@0: michael@0: textRun[i] = aGlyphTable->MakeTextRun(mThebesContext, oneDevPixel, michael@0: *aFontGroup, ch); michael@0: nsBoundingMetrics bm = MeasureTextRun(mThebesContext, textRun[i]); michael@0: michael@0: // TODO: For the generic Unicode table, ideally we should check that the michael@0: // glyphs are actually found and that they each come from the same michael@0: // font. michael@0: bmdata[i] = bm; michael@0: sizedata[i] = isVertical ? bm.ascent + bm.descent michael@0: : bm.rightBearing - bm.leftBearing; michael@0: } else { michael@0: // Null glue indicates that a rule will be drawn, which can stretch to michael@0: // fill any space. michael@0: textRun[i] = nullptr; michael@0: bmdata[i] = nsBoundingMetrics(); michael@0: sizedata[i] = i == 3 ? mTargetSize : 0; michael@0: } michael@0: } michael@0: michael@0: // Build by parts if we have successfully computed the michael@0: // bounding metrics of all parts. michael@0: nscoord computedSize = ComputeSizeFromParts(mPresContext, chdata, sizedata, michael@0: mTargetSize); michael@0: michael@0: nscoord currentSize = michael@0: isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent michael@0: : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; michael@0: michael@0: if (!IsSizeBetter(computedSize, currentSize, mTargetSize, mStretchHint)) { michael@0: #ifdef NOISY_SEARCH michael@0: printf(" Font %s Rejected!\n", michael@0: NS_LossyConvertUTF16toASCII(fontName).get()); michael@0: #endif michael@0: return false; // to next table michael@0: } michael@0: michael@0: #ifdef NOISY_SEARCH michael@0: printf(" Font %s Current best!\n", michael@0: NS_LossyConvertUTF16toASCII(fontName).get()); michael@0: #endif michael@0: michael@0: // The computed size is the best we have found so far... michael@0: // now is the time to compute and cache our bounding metrics michael@0: if (isVertical) { michael@0: int32_t i; michael@0: // Try and find the first existing part and then determine the extremal michael@0: // horizontal metrics of the parts. michael@0: for (i = 0; i <= 3 && !textRun[i]; i++); michael@0: if (i == 4) { michael@0: NS_ERROR("Cannot stretch - All parts missing"); michael@0: return false; michael@0: } michael@0: nscoord lbearing = bmdata[i].leftBearing; michael@0: nscoord rbearing = bmdata[i].rightBearing; michael@0: nscoord width = bmdata[i].width; michael@0: i++; michael@0: for (; i <= 3; i++) { michael@0: if (!textRun[i]) continue; michael@0: lbearing = std::min(lbearing, bmdata[i].leftBearing); michael@0: rbearing = std::max(rbearing, bmdata[i].rightBearing); michael@0: width = std::max(width, bmdata[i].width); michael@0: } michael@0: if (maxWidth) { michael@0: lbearing = std::min(lbearing, mBoundingMetrics.leftBearing); michael@0: rbearing = std::max(rbearing, mBoundingMetrics.rightBearing); michael@0: width = std::max(width, mBoundingMetrics.width); michael@0: } michael@0: mBoundingMetrics.width = width; michael@0: // When maxWidth, updating ascent and descent indicates that no characters michael@0: // larger than this character's minimum size need to be checked as they michael@0: // will not be used. michael@0: mBoundingMetrics.ascent = bmdata[0].ascent; // not used except with descent michael@0: // for height michael@0: mBoundingMetrics.descent = computedSize - mBoundingMetrics.ascent; michael@0: mBoundingMetrics.leftBearing = lbearing; michael@0: mBoundingMetrics.rightBearing = rbearing; michael@0: } michael@0: else { michael@0: int32_t i; michael@0: // Try and find the first existing part and then determine the extremal michael@0: // vertical metrics of the parts. michael@0: for (i = 0; i <= 3 && !textRun[i]; i++); michael@0: if (i == 4) { michael@0: NS_ERROR("Cannot stretch - All parts missing"); michael@0: return false; michael@0: } michael@0: nscoord ascent = bmdata[i].ascent; michael@0: nscoord descent = bmdata[i].descent; michael@0: i++; michael@0: for (; i <= 3; i++) { michael@0: if (!textRun[i]) continue; michael@0: ascent = std::max(ascent, bmdata[i].ascent); michael@0: descent = std::max(descent, bmdata[i].descent); michael@0: } michael@0: mBoundingMetrics.width = computedSize; michael@0: mBoundingMetrics.ascent = ascent; michael@0: mBoundingMetrics.descent = descent; michael@0: mBoundingMetrics.leftBearing = 0; michael@0: mBoundingMetrics.rightBearing = computedSize; michael@0: } michael@0: mGlyphFound = true; michael@0: if (maxWidth) michael@0: return false; // Continue to check other sizes michael@0: michael@0: // reset michael@0: mChar->mDraw = DRAW_PARTS; michael@0: for (int32_t i = 0; i < 4; i++) { michael@0: mChar->mGlyphs[i] = textRun[i]; michael@0: mChar->mBmData[i] = bmdata[i]; michael@0: } michael@0: michael@0: return IsSizeOK(mPresContext, computedSize, mTargetSize, mStretchHint); michael@0: } michael@0: michael@0: // This is called for each family, whether it exists or not michael@0: bool michael@0: nsMathMLChar::StretchEnumContext::EnumCallback(const nsString& aFamily, michael@0: bool aGeneric, void *aData) michael@0: { michael@0: StretchEnumContext* context = static_cast(aData); michael@0: michael@0: // Check font family if it is not a generic one michael@0: // We test with the kNullGlyph michael@0: nsStyleContext *sc = context->mChar->mStyleContext; michael@0: nsFont font = sc->StyleFont()->mFont; michael@0: nsRefPtr fontGroup; michael@0: if (!aGeneric && !context->mChar->SetFontFamily(context->mPresContext, michael@0: nullptr, kNullGlyph, aFamily, michael@0: font, &fontGroup)) michael@0: return true; // Could not set the family michael@0: michael@0: // Determine the glyph table to use for this font. michael@0: nsAutoPtr openTypeTable; michael@0: nsGlyphTable* glyphTable; michael@0: if (aGeneric) { michael@0: // This is a generic font, use the Unicode table. michael@0: glyphTable = &gGlyphTableList->mUnicodeTable; michael@0: } else { michael@0: // If the font contains an Open Type MATH table, use it. michael@0: openTypeTable = nsOpenTypeTable::Create(fontGroup->GetFontAt(0)); michael@0: if (openTypeTable) { michael@0: glyphTable = openTypeTable; michael@0: } else { michael@0: // Otherwise try to find a .properties file corresponding to that font michael@0: // family or fallback to the Unicode table. michael@0: glyphTable = gGlyphTableList->GetGlyphTableFor(aFamily); michael@0: } michael@0: } michael@0: michael@0: if (!openTypeTable) { michael@0: if (context->mTablesTried.Contains(glyphTable)) michael@0: return true; // already tried this one michael@0: michael@0: // Only try this table once. michael@0: context->mTablesTried.AppendElement(glyphTable); michael@0: } michael@0: michael@0: // If the unicode table is being used, then search all font families. If a michael@0: // special table is being used then the font in this family should have the michael@0: // specified glyphs. michael@0: const nsAString& family = glyphTable == &gGlyphTableList->mUnicodeTable ? michael@0: context->mFamilies : aFamily; michael@0: michael@0: if((context->mTryVariants && michael@0: context->TryVariants(glyphTable, &fontGroup, family)) || michael@0: (context->mTryParts && context->TryParts(glyphTable, &fontGroup, family))) michael@0: return false; // no need to continue michael@0: michael@0: return true; // true means continue michael@0: } michael@0: michael@0: nsresult michael@0: nsMathMLChar::StretchInternal(nsPresContext* aPresContext, michael@0: gfxContext* aThebesContext, michael@0: nsStretchDirection& aStretchDirection, michael@0: const nsBoundingMetrics& aContainerSize, michael@0: nsBoundingMetrics& aDesiredStretchSize, michael@0: uint32_t aStretchHint, michael@0: // These are currently only used when michael@0: // aStretchHint & NS_STRETCH_MAXWIDTH: michael@0: float aMaxSize, michael@0: bool aMaxSizeIsAbsolute) michael@0: { michael@0: // if we have been called before, and we didn't actually stretch, our michael@0: // direction may have been set to NS_STRETCH_DIRECTION_UNSUPPORTED. michael@0: // So first set our direction back to its instrinsic value michael@0: nsStretchDirection direction = nsMathMLOperators::GetStretchyDirection(mData); michael@0: michael@0: // Set default font and get the default bounding metrics michael@0: // mStyleContext is a leaf context used only when stretching happens. michael@0: // For the base size, the default font should come from the parent context michael@0: nsFont font = mStyleContext->GetParent()->StyleFont()->mFont; michael@0: michael@0: nsRefPtr fm; michael@0: aPresContext->DeviceContext()-> michael@0: GetMetricsFor(font, michael@0: mStyleContext->StyleFont()->mLanguage, michael@0: aPresContext->GetUserFontSet(), michael@0: aPresContext->GetTextPerfMetrics(), michael@0: *getter_AddRefs(fm)); michael@0: uint32_t len = uint32_t(mData.Length()); michael@0: nsAutoPtr textRun; michael@0: textRun = fm->GetThebesFontGroup()-> michael@0: MakeTextRun(static_cast(mData.get()), len, aThebesContext, michael@0: aPresContext->AppUnitsPerDevPixel(), 0); michael@0: aDesiredStretchSize = MeasureTextRun(aThebesContext, textRun); michael@0: mGlyphs[0] = textRun; michael@0: michael@0: bool maxWidth = (NS_STRETCH_MAXWIDTH & aStretchHint) != 0; michael@0: if (!maxWidth) { michael@0: mUnscaledAscent = aDesiredStretchSize.ascent; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: // 1. Check the common situations where stretching is not actually needed michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // quick return if there is nothing special about this char michael@0: if ((aStretchDirection != direction && michael@0: aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT) || michael@0: (aStretchHint & ~NS_STRETCH_MAXWIDTH) == NS_STRETCH_NONE) { michael@0: mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // if no specified direction, attempt to stretch in our preferred direction michael@0: if (aStretchDirection == NS_STRETCH_DIRECTION_DEFAULT) { michael@0: aStretchDirection = direction; michael@0: } michael@0: michael@0: // see if this is a particular largeop or largeopOnly request michael@0: bool largeop = (NS_STRETCH_LARGEOP & aStretchHint) != 0; michael@0: bool stretchy = (NS_STRETCH_VARIABLE_MASK & aStretchHint) != 0; michael@0: bool largeopOnly = largeop && !stretchy; michael@0: michael@0: bool isVertical = (direction == NS_STRETCH_DIRECTION_VERTICAL); michael@0: michael@0: nscoord targetSize = michael@0: isVertical ? aContainerSize.ascent + aContainerSize.descent michael@0: : aContainerSize.rightBearing - aContainerSize.leftBearing; michael@0: michael@0: if (maxWidth) { michael@0: // See if it is only necessary to consider glyphs up to some maximum size. michael@0: // Set the current height to the maximum size, and set aStretchHint to michael@0: // NS_STRETCH_SMALLER if the size is variable, so that only smaller sizes michael@0: // are considered. targetSize from GetMaxWidth() is 0. michael@0: if (stretchy) { michael@0: // variable size stretch - consider all sizes < maxsize michael@0: aStretchHint = michael@0: (aStretchHint & ~NS_STRETCH_VARIABLE_MASK) | NS_STRETCH_SMALLER; michael@0: } michael@0: michael@0: // Use NS_MATHML_DELIMITER_FACTOR to allow some slightly larger glyphs as michael@0: // maxsize is not enforced exactly. michael@0: if (aMaxSize == NS_MATHML_OPERATOR_SIZE_INFINITY) { michael@0: aDesiredStretchSize.ascent = nscoord_MAX; michael@0: aDesiredStretchSize.descent = 0; michael@0: } michael@0: else { michael@0: nscoord height = aDesiredStretchSize.ascent + aDesiredStretchSize.descent; michael@0: if (height == 0) { michael@0: if (aMaxSizeIsAbsolute) { michael@0: aDesiredStretchSize.ascent = michael@0: NSToCoordRound(aMaxSize / NS_MATHML_DELIMITER_FACTOR); michael@0: aDesiredStretchSize.descent = 0; michael@0: } michael@0: // else: leave height as 0 michael@0: } michael@0: else { michael@0: float scale = aMaxSizeIsAbsolute ? aMaxSize / height : aMaxSize; michael@0: scale /= NS_MATHML_DELIMITER_FACTOR; michael@0: aDesiredStretchSize.ascent = michael@0: NSToCoordRound(scale * aDesiredStretchSize.ascent); michael@0: aDesiredStretchSize.descent = michael@0: NSToCoordRound(scale * aDesiredStretchSize.descent); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsBoundingMetrics initialSize = aDesiredStretchSize; michael@0: nscoord charSize = michael@0: isVertical ? initialSize.ascent + initialSize.descent michael@0: : initialSize.rightBearing - initialSize.leftBearing; michael@0: michael@0: bool done = false; michael@0: michael@0: if (!maxWidth && !largeop) { michael@0: // Doing Stretch() not GetMaxWidth(), michael@0: // and not a largeop in display mode; we're done if size fits michael@0: if ((targetSize <= 0) || michael@0: ((isVertical && charSize >= targetSize) || michael@0: IsSizeOK(aPresContext, charSize, targetSize, aStretchHint))) michael@0: done = true; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: // 2/3. Search for a glyph or set of part glyphs of appropriate size michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: bool glyphFound = false; michael@0: michael@0: if (!done) { // normal case michael@0: // Use the css font-family but add preferred fallback fonts. michael@0: font = mStyleContext->StyleFont()->mFont; michael@0: NS_NAMED_LITERAL_CSTRING(defaultKey, "font.mathfont-family"); michael@0: nsAdoptingString fallbackFonts = Preferences::GetString(defaultKey.get()); michael@0: if (!fallbackFonts.IsEmpty()) { michael@0: AddFallbackFonts(font.name, fallbackFonts); michael@0: } michael@0: michael@0: #ifdef NOISY_SEARCH michael@0: printf("Searching in "%s" for a glyph of appropriate size for: 0x%04X:%c\n", michael@0: font.name, mData[0], mData[0]&0x00FF); michael@0: #endif michael@0: StretchEnumContext enumData(this, aPresContext, aThebesContext, michael@0: aStretchDirection, targetSize, aStretchHint, michael@0: aDesiredStretchSize, font.name, glyphFound); michael@0: enumData.mTryParts = !largeopOnly; michael@0: michael@0: font.EnumerateFamilies(StretchEnumContext::EnumCallback, &enumData); michael@0: } michael@0: michael@0: if (!maxWidth) { michael@0: // Now, we know how we are going to draw the char. Update the member michael@0: // variables accordingly. michael@0: mUnscaledAscent = aDesiredStretchSize.ascent; michael@0: } michael@0: michael@0: if (glyphFound) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // stretchy character michael@0: if (stretchy) { michael@0: if (isVertical) { michael@0: float scale = michael@0: float(aContainerSize.ascent + aContainerSize.descent) / michael@0: (aDesiredStretchSize.ascent + aDesiredStretchSize.descent); michael@0: if (!largeop || scale > 1.0) { michael@0: // make the character match the desired height. michael@0: if (!maxWidth) { michael@0: mScaleY *= scale; michael@0: } michael@0: aDesiredStretchSize.ascent *= scale; michael@0: aDesiredStretchSize.descent *= scale; michael@0: } michael@0: } else { michael@0: float scale = michael@0: float(aContainerSize.rightBearing - aContainerSize.leftBearing) / michael@0: (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing); michael@0: if (!largeop || scale > 1.0) { michael@0: // make the character match the desired width. michael@0: if (!maxWidth) { michael@0: mScaleX *= scale; michael@0: } michael@0: aDesiredStretchSize.leftBearing *= scale; michael@0: aDesiredStretchSize.rightBearing *= scale; michael@0: aDesiredStretchSize.width *= scale; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We do not have a char variant for this largeop in display mode, so we michael@0: // apply a scale transform to the base char. michael@0: if (largeop) { michael@0: float scale; michael@0: float largeopFactor = kLargeOpFactor; michael@0: michael@0: // increase the width if it is not largeopFactor times larger michael@0: // than the initial one. michael@0: if ((aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing) < michael@0: largeopFactor * (initialSize.rightBearing - initialSize.leftBearing)) { michael@0: scale = (largeopFactor * michael@0: (initialSize.rightBearing - initialSize.leftBearing)) / michael@0: (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing); michael@0: if (!maxWidth) { michael@0: mScaleX *= scale; michael@0: } michael@0: aDesiredStretchSize.leftBearing *= scale; michael@0: aDesiredStretchSize.rightBearing *= scale; michael@0: aDesiredStretchSize.width *= scale; michael@0: } michael@0: michael@0: // increase the height if it is not largeopFactor times larger michael@0: // than the initial one. michael@0: if (NS_STRETCH_INTEGRAL & aStretchHint) { michael@0: // integrals are drawn taller michael@0: largeopFactor = kIntegralFactor; michael@0: } michael@0: if ((aDesiredStretchSize.ascent + aDesiredStretchSize.descent) < michael@0: largeopFactor * (initialSize.ascent + initialSize.descent)) { michael@0: scale = (largeopFactor * michael@0: (initialSize.ascent + initialSize.descent)) / michael@0: (aDesiredStretchSize.ascent + aDesiredStretchSize.descent); michael@0: if (!maxWidth) { michael@0: mScaleY *= scale; michael@0: } michael@0: aDesiredStretchSize.ascent *= scale; michael@0: aDesiredStretchSize.descent *= scale; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsMathMLChar::Stretch(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsStretchDirection aStretchDirection, michael@0: const nsBoundingMetrics& aContainerSize, michael@0: nsBoundingMetrics& aDesiredStretchSize, michael@0: uint32_t aStretchHint, michael@0: bool aRTL) michael@0: { michael@0: NS_ASSERTION(!(aStretchHint & michael@0: ~(NS_STRETCH_VARIABLE_MASK | NS_STRETCH_LARGEOP | michael@0: NS_STRETCH_INTEGRAL)), michael@0: "Unexpected stretch flags"); michael@0: michael@0: mDraw = DRAW_NORMAL; michael@0: mMirrored = aRTL && nsMathMLOperators::IsMirrorableOperator(mData); michael@0: mScaleY = mScaleX = 1.0; michael@0: mDirection = aStretchDirection; michael@0: nsresult rv = michael@0: StretchInternal(aPresContext, aRenderingContext.ThebesContext(), mDirection, michael@0: aContainerSize, aDesiredStretchSize, aStretchHint); michael@0: michael@0: // Record the metrics michael@0: mBoundingMetrics = aDesiredStretchSize; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // What happens here is that the StretchInternal algorithm is used but michael@0: // modified by passing the NS_STRETCH_MAXWIDTH stretch hint. That causes michael@0: // StretchInternal to return horizontal bounding metrics that are the maximum michael@0: // that might be returned from a Stretch. michael@0: // michael@0: // In order to avoid considering widths of some characters in fonts that will michael@0: // not be used for any stretch size, StretchInternal sets the initial height michael@0: // to infinity and looks for any characters smaller than this height. When a michael@0: // character built from parts is considered, (it will be used by Stretch for michael@0: // any characters greater than its minimum size, so) the height is set to its michael@0: // minimum size, so that only widths of smaller subsequent characters are michael@0: // considered. michael@0: nscoord michael@0: nsMathMLChar::GetMaxWidth(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: uint32_t aStretchHint, michael@0: float aMaxSize, bool aMaxSizeIsAbsolute) michael@0: { michael@0: nsBoundingMetrics bm; michael@0: nsStretchDirection direction = NS_STRETCH_DIRECTION_VERTICAL; michael@0: const nsBoundingMetrics container; // zero target size michael@0: michael@0: StretchInternal(aPresContext, aRenderingContext.ThebesContext(), direction, michael@0: container, bm, aStretchHint | NS_STRETCH_MAXWIDTH); michael@0: michael@0: return std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing); michael@0: } michael@0: michael@0: class nsDisplayMathMLSelectionRect : public nsDisplayItem { michael@0: public: michael@0: nsDisplayMathMLSelectionRect(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, const nsRect& aRect) michael@0: : nsDisplayItem(aBuilder, aFrame), mRect(aRect) { michael@0: MOZ_COUNT_CTOR(nsDisplayMathMLSelectionRect); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayMathMLSelectionRect() { michael@0: MOZ_COUNT_DTOR(nsDisplayMathMLSelectionRect); michael@0: } michael@0: #endif michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx); michael@0: NS_DISPLAY_DECL_NAME("MathMLSelectionRect", TYPE_MATHML_SELECTION_RECT) michael@0: private: michael@0: nsRect mRect; michael@0: }; michael@0: michael@0: void nsDisplayMathMLSelectionRect::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: // get color to use for selection from the look&feel object michael@0: nscolor bgColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground, michael@0: NS_RGB(0, 0, 0)); michael@0: aCtx->SetColor(bgColor); michael@0: aCtx->FillRect(mRect + ToReferenceFrame()); michael@0: } michael@0: michael@0: class nsDisplayMathMLCharBackground : public nsDisplayItem { michael@0: public: michael@0: nsDisplayMathMLCharBackground(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, const nsRect& aRect, michael@0: nsStyleContext* aStyleContext) michael@0: : nsDisplayItem(aBuilder, aFrame), mStyleContext(aStyleContext), michael@0: mRect(aRect) { michael@0: MOZ_COUNT_CTOR(nsDisplayMathMLCharBackground); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayMathMLCharBackground() { michael@0: MOZ_COUNT_DTOR(nsDisplayMathMLCharBackground); michael@0: } michael@0: #endif michael@0: michael@0: virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion *aInvalidRegion) MOZ_OVERRIDE; michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx); michael@0: NS_DISPLAY_DECL_NAME("MathMLCharBackground", TYPE_MATHML_CHAR_BACKGROUND) michael@0: private: michael@0: nsStyleContext* mStyleContext; michael@0: nsRect mRect; michael@0: }; michael@0: michael@0: void michael@0: nsDisplayMathMLCharBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion *aInvalidRegion) michael@0: { michael@0: AddInvalidRegionForSyncDecodeBackgroundImages(aBuilder, aGeometry, aInvalidRegion); michael@0: michael@0: nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); michael@0: } michael@0: michael@0: void nsDisplayMathMLCharBackground::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: const nsStyleBorder* border = mStyleContext->StyleBorder(); michael@0: nsRect rect(mRect + ToReferenceFrame()); michael@0: nsCSSRendering::PaintBackgroundWithSC(mFrame->PresContext(), *aCtx, mFrame, michael@0: mVisibleRect, rect, michael@0: mStyleContext, *border, michael@0: aBuilder->GetBackgroundPaintFlags()); michael@0: } michael@0: michael@0: class nsDisplayMathMLCharForeground : public nsDisplayItem { michael@0: public: michael@0: nsDisplayMathMLCharForeground(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, nsMathMLChar* aChar, michael@0: uint32_t aIndex, bool aIsSelected) michael@0: : nsDisplayItem(aBuilder, aFrame), mChar(aChar), michael@0: mIndex(aIndex), mIsSelected(aIsSelected) { michael@0: MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayMathMLCharForeground() { michael@0: MOZ_COUNT_DTOR(nsDisplayMathMLCharForeground); michael@0: } michael@0: #endif michael@0: michael@0: virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { michael@0: *aSnap = false; michael@0: nsRect rect; michael@0: mChar->GetRect(rect); michael@0: nsPoint offset = ToReferenceFrame() + rect.TopLeft(); michael@0: nsBoundingMetrics bm; michael@0: mChar->GetBoundingMetrics(bm); michael@0: nsRect temp(offset.x + bm.leftBearing, offset.y, michael@0: bm.rightBearing - bm.leftBearing, bm.ascent + bm.descent); michael@0: // Bug 748220 michael@0: temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); michael@0: return temp; michael@0: } michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: mChar->PaintForeground(mFrame->PresContext(), *aCtx, michael@0: ToReferenceFrame(), mIsSelected); michael@0: } michael@0: michael@0: NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND) michael@0: michael@0: virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) michael@0: { michael@0: bool snap; michael@0: return GetBounds(aBuilder, &snap); michael@0: } michael@0: michael@0: virtual uint32_t GetPerFrameKey() { michael@0: return (mIndex << nsDisplayItem::TYPE_BITS) michael@0: | nsDisplayItem::GetPerFrameKey(); michael@0: } michael@0: michael@0: private: michael@0: nsMathMLChar* mChar; michael@0: uint32_t mIndex; michael@0: bool mIsSelected; michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: class nsDisplayMathMLCharDebug : public nsDisplayItem { michael@0: public: michael@0: nsDisplayMathMLCharDebug(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, const nsRect& aRect) michael@0: : nsDisplayItem(aBuilder, aFrame), mRect(aRect) { michael@0: MOZ_COUNT_CTOR(nsDisplayMathMLCharDebug); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayMathMLCharDebug() { michael@0: MOZ_COUNT_DTOR(nsDisplayMathMLCharDebug); michael@0: } michael@0: #endif michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx); michael@0: NS_DISPLAY_DECL_NAME("MathMLCharDebug", TYPE_MATHML_CHAR_DEBUG) michael@0: michael@0: private: michael@0: nsRect mRect; michael@0: }; michael@0: michael@0: void nsDisplayMathMLCharDebug::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: // for visual debug michael@0: int skipSides = 0; michael@0: nsPresContext* presContext = mFrame->PresContext(); michael@0: nsStyleContext* styleContext = mFrame->StyleContext(); michael@0: nsRect rect = mRect + ToReferenceFrame(); michael@0: nsCSSRendering::PaintBorder(presContext, *aCtx, mFrame, michael@0: mVisibleRect, rect, styleContext, skipSides); michael@0: nsCSSRendering::PaintOutline(presContext, *aCtx, mFrame, michael@0: mVisibleRect, rect, styleContext); michael@0: } michael@0: #endif michael@0: michael@0: michael@0: void michael@0: nsMathMLChar::Display(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aForFrame, michael@0: const nsDisplayListSet& aLists, michael@0: uint32_t aIndex, michael@0: const nsRect* aSelectedRect) michael@0: { michael@0: nsStyleContext* parentContext = mStyleContext->GetParent(); michael@0: nsStyleContext* styleContext = mStyleContext; michael@0: michael@0: if (mDraw == DRAW_NORMAL) { michael@0: // normal drawing if there is nothing special about this char michael@0: // Set default context to the parent context michael@0: styleContext = parentContext; michael@0: } michael@0: michael@0: if (!styleContext->StyleVisibility()->IsVisible()) michael@0: return; michael@0: michael@0: // if the leaf style context that we use for stretchy chars has a background michael@0: // color we use it -- this feature is mostly used for testing and debugging michael@0: // purposes. Normally, users will set the background on the container frame. michael@0: // paint the selection background -- beware MathML frames overlap a lot michael@0: if (aSelectedRect && !aSelectedRect->IsEmpty()) { michael@0: aLists.BorderBackground()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayMathMLSelectionRect(aBuilder, aForFrame, *aSelectedRect)); michael@0: } michael@0: else if (mRect.width && mRect.height) { michael@0: const nsStyleBackground* backg = styleContext->StyleBackground(); michael@0: if (styleContext != parentContext && michael@0: NS_GET_A(backg->mBackgroundColor) > 0) { michael@0: aLists.BorderBackground()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayMathMLCharBackground(aBuilder, aForFrame, mRect, michael@0: styleContext)); michael@0: } michael@0: //else michael@0: // our container frame will take care of painting its background michael@0: michael@0: #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) michael@0: // for visual debug michael@0: aLists.BorderBackground()->AppendToTop(new (aBuilder) michael@0: nsDisplayMathMLCharDebug(aBuilder, aForFrame, mRect)); michael@0: #endif michael@0: } michael@0: aLists.Content()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayMathMLCharForeground(aBuilder, aForFrame, this, michael@0: aIndex, michael@0: aSelectedRect && michael@0: !aSelectedRect->IsEmpty())); michael@0: } michael@0: michael@0: void michael@0: nsMathMLChar::ApplyTransforms(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerGfxUnit, michael@0: nsRect &r) michael@0: { michael@0: // apply the transforms michael@0: if (mMirrored) { michael@0: nsPoint pt = r.TopRight(); michael@0: aThebesContext-> michael@0: Translate(gfxPoint(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit), michael@0: NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit))); michael@0: aThebesContext->Scale(-mScaleX, mScaleY); michael@0: } else { michael@0: nsPoint pt = r.TopLeft(); michael@0: aThebesContext-> michael@0: Translate(gfxPoint(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit), michael@0: NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit))); michael@0: aThebesContext->Scale(mScaleX, mScaleY); michael@0: } michael@0: michael@0: // update the bounding rectangle. michael@0: r.x = r.y = 0; michael@0: r.width /= mScaleX; michael@0: r.height /= mScaleY; michael@0: } michael@0: michael@0: void michael@0: nsMathMLChar::PaintForeground(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsPoint aPt, michael@0: bool aIsSelected) michael@0: { michael@0: nsStyleContext* parentContext = mStyleContext->GetParent(); michael@0: nsStyleContext* styleContext = mStyleContext; michael@0: michael@0: if (mDraw == DRAW_NORMAL) { michael@0: // normal drawing if there is nothing special about this char michael@0: // Set default context to the parent context michael@0: styleContext = parentContext; michael@0: } michael@0: michael@0: nsRefPtr thebesContext = aRenderingContext.ThebesContext(); michael@0: michael@0: // Set color ... michael@0: nscolor fgColor = styleContext->GetVisitedDependentColor(eCSSProperty_color); michael@0: if (aIsSelected) { michael@0: // get color to use for selection from the look&feel object michael@0: fgColor = LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground, michael@0: fgColor); michael@0: } michael@0: thebesContext->SetColor(fgColor); michael@0: thebesContext->Save(); michael@0: nsRect r = mRect + aPt; michael@0: ApplyTransforms(thebesContext, aPresContext->AppUnitsPerDevPixel(), r); michael@0: michael@0: switch(mDraw) michael@0: { michael@0: case DRAW_NORMAL: michael@0: case DRAW_VARIANT: michael@0: // draw a single glyph (base size or size variant) michael@0: // XXXfredw verify if mGlyphs[0] is non-null to workaround bug 973322. michael@0: if (mGlyphs[0]) { michael@0: mGlyphs[0]->Draw(thebesContext, gfxPoint(0.0, mUnscaledAscent), michael@0: DrawMode::GLYPH_FILL, 0, mGlyphs[0]->GetLength(), michael@0: nullptr, nullptr, nullptr); michael@0: } michael@0: break; michael@0: case DRAW_PARTS: { michael@0: // paint by parts michael@0: if (NS_STRETCH_DIRECTION_VERTICAL == mDirection) michael@0: PaintVertically(aPresContext, thebesContext, r); michael@0: else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection) michael@0: PaintHorizontally(aPresContext, thebesContext, r); michael@0: break; michael@0: } michael@0: default: michael@0: NS_NOTREACHED("Unknown drawing method"); michael@0: break; michael@0: } michael@0: michael@0: thebesContext->Restore(); michael@0: } michael@0: michael@0: /* ============================================================================= michael@0: Helper routines that actually do the job of painting the char by parts michael@0: */ michael@0: michael@0: class AutoPushClipRect { michael@0: gfxContext* mThebesContext; michael@0: public: michael@0: AutoPushClipRect(gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit, michael@0: const nsRect& aRect) michael@0: : mThebesContext(aThebesContext) { michael@0: mThebesContext->Save(); michael@0: mThebesContext->NewPath(); michael@0: gfxRect clip = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit); michael@0: mThebesContext->SnappedRectangle(clip); michael@0: mThebesContext->Clip(); michael@0: } michael@0: ~AutoPushClipRect() { michael@0: mThebesContext->Restore(); michael@0: } michael@0: }; michael@0: michael@0: static nsPoint michael@0: SnapToDevPixels(const gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit, michael@0: const nsPoint& aPt) michael@0: { michael@0: gfxPoint pt(NSAppUnitsToFloatPixels(aPt.x, aAppUnitsPerGfxUnit), michael@0: NSAppUnitsToFloatPixels(aPt.y, aAppUnitsPerGfxUnit)); michael@0: pt = aThebesContext->UserToDevice(pt); michael@0: pt.Round(); michael@0: pt = aThebesContext->DeviceToUser(pt); michael@0: return nsPoint(NSFloatPixelsToAppUnits(pt.x, aAppUnitsPerGfxUnit), michael@0: NSFloatPixelsToAppUnits(pt.y, aAppUnitsPerGfxUnit)); michael@0: } michael@0: michael@0: static void michael@0: PaintRule(gfxContext* aThebesContext, michael@0: int32_t aAppUnitsPerGfxUnit, michael@0: nsRect& aRect) michael@0: { michael@0: aThebesContext->NewPath(); michael@0: gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit); michael@0: aThebesContext->SnappedRectangle(rect); michael@0: aThebesContext->Fill(); michael@0: } michael@0: michael@0: // paint a stretchy char by assembling glyphs vertically michael@0: nsresult michael@0: nsMathMLChar::PaintVertically(nsPresContext* aPresContext, michael@0: gfxContext* aThebesContext, michael@0: nsRect& aRect) michael@0: { michael@0: // Get the device pixel size in the vertical direction. michael@0: // (This makes no effort to optimize for non-translation transformations.) michael@0: nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: michael@0: // get metrics data to be re-used later michael@0: int32_t i = 0; michael@0: nscoord dx = aRect.x; michael@0: nscoord offset[3], start[3], end[3]; michael@0: for (i = 0; i <= 2; ++i) { michael@0: const nsBoundingMetrics& bm = mBmData[i]; michael@0: nscoord dy; michael@0: if (0 == i) { // top michael@0: dy = aRect.y + bm.ascent; michael@0: } michael@0: else if (2 == i) { // bottom michael@0: dy = aRect.y + aRect.height - bm.descent; michael@0: } michael@0: else { // middle michael@0: dy = aRect.y + bm.ascent + (aRect.height - (bm.ascent + bm.descent))/2; michael@0: } michael@0: // _cairo_scaled_font_show_glyphs snaps origins to device pixels. michael@0: // Do this now so that we can get the other dimensions right. michael@0: // (This may not achieve much with non-rectangular transformations.) michael@0: dy = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).y; michael@0: // abcissa passed to Draw michael@0: offset[i] = dy; michael@0: // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest michael@0: // pixel, so the bm values can include 1 row of faint pixels on each edge. michael@0: // Don't rely on this pixel as it can look like a gap. michael@0: if (bm.ascent + bm.descent >= 2 * oneDevPixel) { michael@0: start[i] = dy - bm.ascent + oneDevPixel; // top join michael@0: end[i] = dy + bm.descent - oneDevPixel; // bottom join michael@0: } else { michael@0: // To avoid overlaps, we don't add one pixel on each side when the part michael@0: // is too small. michael@0: start[i] = dy - bm.ascent; // top join michael@0: end[i] = dy + bm.descent; // bottom join michael@0: } michael@0: } michael@0: michael@0: // If there are overlaps, then join at the mid point michael@0: for (i = 0; i < 2; ++i) { michael@0: if (end[i] > start[i+1]) { michael@0: end[i] = (end[i] + start[i+1]) / 2; michael@0: start[i+1] = end[i]; michael@0: } michael@0: } michael@0: michael@0: nsRect unionRect = aRect; michael@0: unionRect.x += mBoundingMetrics.leftBearing; michael@0: unionRect.width = michael@0: mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; michael@0: unionRect.Inflate(oneDevPixel, oneDevPixel); michael@0: michael@0: ///////////////////////////////////// michael@0: // draw top, middle, bottom michael@0: for (i = 0; i <= 2; ++i) { michael@0: // glue can be null michael@0: if (mGlyphs[i]) { michael@0: nscoord dy = offset[i]; michael@0: // Draw a glyph in a clipped area so that we don't have hairy chars michael@0: // pending outside michael@0: nsRect clipRect = unionRect; michael@0: // Clip at the join to get a solid edge (without overlap or gap), when michael@0: // this won't change the glyph too much. If the glyph is too small to michael@0: // clip then we'll overlap rather than have a gap. michael@0: nscoord height = mBmData[i].ascent + mBmData[i].descent; michael@0: if (height * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) { michael@0: if (0 == i) { // top michael@0: clipRect.height = end[i] - clipRect.y; michael@0: } michael@0: else if (2 == i) { // bottom michael@0: clipRect.height -= start[i] - clipRect.y; michael@0: clipRect.y = start[i]; michael@0: } michael@0: else { // middle michael@0: clipRect.y = start[i]; michael@0: clipRect.height = end[i] - start[i]; michael@0: } michael@0: } michael@0: if (!clipRect.IsEmpty()) { michael@0: AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); michael@0: mGlyphs[i]->Draw(aThebesContext, gfxPoint(dx, dy), michael@0: DrawMode::GLYPH_FILL, 0, mGlyphs[i]->GetLength(), michael@0: nullptr, nullptr, nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /////////////// michael@0: // fill the gap between top and middle, and between middle and bottom. michael@0: if (!mGlyphs[3]) { // null glue : draw a rule michael@0: // figure out the dimensions of the rule to be drawn : michael@0: // set lbearing to rightmost lbearing among the two current successive michael@0: // parts. michael@0: // set rbearing to leftmost rbearing among the two current successive parts. michael@0: // this not only satisfies the convention used for over/underbraces michael@0: // in TeX, but also takes care of broken fonts like the stretchy integral michael@0: // in Symbol for small font sizes in unix. michael@0: nscoord lbearing, rbearing; michael@0: int32_t first = 0, last = 1; michael@0: while (last <= 2) { michael@0: if (mGlyphs[last]) { michael@0: lbearing = mBmData[last].leftBearing; michael@0: rbearing = mBmData[last].rightBearing; michael@0: if (mGlyphs[first]) { michael@0: if (lbearing < mBmData[first].leftBearing) michael@0: lbearing = mBmData[first].leftBearing; michael@0: if (rbearing > mBmData[first].rightBearing) michael@0: rbearing = mBmData[first].rightBearing; michael@0: } michael@0: } michael@0: else if (mGlyphs[first]) { michael@0: lbearing = mBmData[first].leftBearing; michael@0: rbearing = mBmData[first].rightBearing; michael@0: } michael@0: else { michael@0: NS_ERROR("Cannot stretch - All parts missing"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: // paint the rule between the parts michael@0: nsRect rule(aRect.x + lbearing, end[first], michael@0: rbearing - lbearing, start[last] - end[first]); michael@0: PaintRule(aThebesContext, oneDevPixel, rule); michael@0: first = last; michael@0: last++; michael@0: } michael@0: } michael@0: else if (mBmData[3].ascent + mBmData[3].descent > 0) { michael@0: // glue is present michael@0: nsBoundingMetrics& bm = mBmData[3]; michael@0: // Ensure the stride for the glue is not reduced to less than one pixel michael@0: if (bm.ascent + bm.descent >= 3 * oneDevPixel) { michael@0: // To protect against gaps, pretend the glue is smaller than it is, michael@0: // in order to trim off ends and thus get a solid edge for the join. michael@0: bm.ascent -= oneDevPixel; michael@0: bm.descent -= oneDevPixel; michael@0: } michael@0: michael@0: nsRect clipRect = unionRect; michael@0: michael@0: for (i = 0; i < 2; ++i) { michael@0: // Make sure not to draw outside the character michael@0: nscoord dy = std::max(end[i], aRect.y); michael@0: nscoord fillEnd = std::min(start[i+1], aRect.YMost()); michael@0: while (dy < fillEnd) { michael@0: clipRect.y = dy; michael@0: clipRect.height = std::min(bm.ascent + bm.descent, fillEnd - dy); michael@0: AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); michael@0: dy += bm.ascent; michael@0: mGlyphs[3]->Draw(aThebesContext, gfxPoint(dx, dy), michael@0: DrawMode::GLYPH_FILL, 0, mGlyphs[3]->GetLength(), michael@0: nullptr, nullptr, nullptr); michael@0: dy += bm.descent; michael@0: } michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: else { michael@0: for (i = 0; i < 2; ++i) { michael@0: NS_ASSERTION(end[i] >= start[i+1], michael@0: "gap between parts with missing glue glyph"); michael@0: } michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: // paint a stretchy char by assembling glyphs horizontally michael@0: nsresult michael@0: nsMathMLChar::PaintHorizontally(nsPresContext* aPresContext, michael@0: gfxContext* aThebesContext, michael@0: nsRect& aRect) michael@0: { michael@0: // Get the device pixel size in the horizontal direction. michael@0: // (This makes no effort to optimize for non-translation transformations.) michael@0: nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel(); michael@0: michael@0: // get metrics data to be re-used later michael@0: int32_t i = 0; michael@0: nscoord dy = aRect.y + mBoundingMetrics.ascent; michael@0: nscoord offset[3], start[3], end[3]; michael@0: for (i = 0; i <= 2; ++i) { michael@0: const nsBoundingMetrics& bm = mBmData[i]; michael@0: nscoord dx; michael@0: if (0 == i) { // left michael@0: dx = aRect.x - bm.leftBearing; michael@0: } michael@0: else if (2 == i) { // right michael@0: dx = aRect.x + aRect.width - bm.rightBearing; michael@0: } michael@0: else { // middle michael@0: dx = aRect.x + (aRect.width - bm.width)/2; michael@0: } michael@0: // _cairo_scaled_font_show_glyphs snaps origins to device pixels. michael@0: // Do this now so that we can get the other dimensions right. michael@0: // (This may not achieve much with non-rectangular transformations.) michael@0: dx = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).x; michael@0: // abcissa passed to Draw michael@0: offset[i] = dx; michael@0: // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest michael@0: // pixel, so the bm values can include 1 row of faint pixels on each edge. michael@0: // Don't rely on this pixel as it can look like a gap. michael@0: if (bm.rightBearing - bm.leftBearing >= 2 * oneDevPixel) { michael@0: start[i] = dx + bm.leftBearing + oneDevPixel; // left join michael@0: end[i] = dx + bm.rightBearing - oneDevPixel; // right join michael@0: } else { michael@0: // To avoid overlaps, we don't add one pixel on each side when the part michael@0: // is too small. michael@0: start[i] = dx + bm.leftBearing; // left join michael@0: end[i] = dx + bm.rightBearing; // right join michael@0: } michael@0: } michael@0: michael@0: // If there are overlaps, then join at the mid point michael@0: for (i = 0; i < 2; ++i) { michael@0: if (end[i] > start[i+1]) { michael@0: end[i] = (end[i] + start[i+1]) / 2; michael@0: start[i+1] = end[i]; michael@0: } michael@0: } michael@0: michael@0: nsRect unionRect = aRect; michael@0: unionRect.Inflate(oneDevPixel, oneDevPixel); michael@0: michael@0: /////////////////////////// michael@0: // draw left, middle, right michael@0: for (i = 0; i <= 2; ++i) { michael@0: // glue can be null michael@0: if (mGlyphs[i]) { michael@0: nscoord dx = offset[i]; michael@0: nsRect clipRect = unionRect; michael@0: // Clip at the join to get a solid edge (without overlap or gap), when michael@0: // this won't change the glyph too much. If the glyph is too small to michael@0: // clip then we'll overlap rather than have a gap. michael@0: nscoord width = mBmData[i].rightBearing - mBmData[i].leftBearing; michael@0: if (width * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) { michael@0: if (0 == i) { // left michael@0: clipRect.width = end[i] - clipRect.x; michael@0: } michael@0: else if (2 == i) { // right michael@0: clipRect.width -= start[i] - clipRect.x; michael@0: clipRect.x = start[i]; michael@0: } michael@0: else { // middle michael@0: clipRect.x = start[i]; michael@0: clipRect.width = end[i] - start[i]; michael@0: } michael@0: } michael@0: if (!clipRect.IsEmpty()) { michael@0: AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); michael@0: mGlyphs[i]->Draw(aThebesContext, gfxPoint(dx, dy), michael@0: DrawMode::GLYPH_FILL, 0, mGlyphs[i]->GetLength(), michael@0: nullptr, nullptr, nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: //////////////// michael@0: // fill the gap between left and middle, and between middle and right. michael@0: if (!mGlyphs[3]) { // null glue : draw a rule michael@0: // figure out the dimensions of the rule to be drawn : michael@0: // set ascent to lowest ascent among the two current successive parts. michael@0: // set descent to highest descent among the two current successive parts. michael@0: // this satisfies the convention used for over/underbraces, and helps michael@0: // fix broken fonts. michael@0: nscoord ascent, descent; michael@0: int32_t first = 0, last = 1; michael@0: while (last <= 2) { michael@0: if (mGlyphs[last]) { michael@0: ascent = mBmData[last].ascent; michael@0: descent = mBmData[last].descent; michael@0: if (mGlyphs[first]) { michael@0: if (ascent > mBmData[first].ascent) michael@0: ascent = mBmData[first].ascent; michael@0: if (descent > mBmData[first].descent) michael@0: descent = mBmData[first].descent; michael@0: } michael@0: } michael@0: else if (mGlyphs[first]) { michael@0: ascent = mBmData[first].ascent; michael@0: descent = mBmData[first].descent; michael@0: } michael@0: else { michael@0: NS_ERROR("Cannot stretch - All parts missing"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: // paint the rule between the parts michael@0: nsRect rule(end[first], dy - ascent, michael@0: start[last] - end[first], ascent + descent); michael@0: PaintRule(aThebesContext, oneDevPixel, rule); michael@0: first = last; michael@0: last++; michael@0: } michael@0: } michael@0: else if (mBmData[3].rightBearing - mBmData[3].leftBearing > 0) { michael@0: // glue is present michael@0: nsBoundingMetrics& bm = mBmData[3]; michael@0: // Ensure the stride for the glue is not reduced to less than one pixel michael@0: if (bm.rightBearing - bm.leftBearing >= 3 * oneDevPixel) { michael@0: // To protect against gaps, pretend the glue is smaller than it is, michael@0: // in order to trim off ends and thus get a solid edge for the join. michael@0: bm.leftBearing += oneDevPixel; michael@0: bm.rightBearing -= oneDevPixel; michael@0: } michael@0: michael@0: nsRect clipRect = unionRect; michael@0: michael@0: for (i = 0; i < 2; ++i) { michael@0: // Make sure not to draw outside the character michael@0: nscoord dx = std::max(end[i], aRect.x); michael@0: nscoord fillEnd = std::min(start[i+1], aRect.XMost()); michael@0: while (dx < fillEnd) { michael@0: clipRect.x = dx; michael@0: clipRect.width = std::min(bm.rightBearing - bm.leftBearing, fillEnd - dx); michael@0: AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect); michael@0: dx -= bm.leftBearing; michael@0: mGlyphs[3]->Draw(aThebesContext, gfxPoint(dx, dy), michael@0: DrawMode::GLYPH_FILL, 0, mGlyphs[3]->GetLength(), michael@0: nullptr, nullptr, nullptr); michael@0: dx += bm.rightBearing; michael@0: } michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: else { // no glue michael@0: for (i = 0; i < 2; ++i) { michael@0: NS_ASSERTION(end[i] >= start[i+1], michael@0: "gap between parts with missing glue glyph"); michael@0: } michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: }