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