summaryrefslogtreecommitdiffstats
path: root/layout/mathml/nsMathMLChar.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /layout/mathml/nsMathMLChar.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/mathml/nsMathMLChar.cpp')
-rw-r--r--layout/mathml/nsMathMLChar.cpp2261
1 files changed, 2261 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLChar.cpp b/layout/mathml/nsMathMLChar.cpp
new file mode 100644
index 0000000000..8ea4bd34ba
--- /dev/null
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -0,0 +1,2261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gfxContext.h"
+#include "gfxTextRun.h"
+#include "gfxUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/StaticPrefs_mathml.h"
+
+#include "nsCOMPtr.h"
+#include "nsDeviceContext.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsUnicharUtils.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"
+
+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) {
+ aFont.size.ScaleBy(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() = default;
+
+ virtual const nsCString& 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 nsACString& aName,
+ nsCOMPtr<nsIPersistentProperties>& aProperties) {
+ nsAutoCString 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),
+ uriStr);
+}
+
+class nsPropertiesTable final : public nsGlyphTable {
+ public:
+ explicit nsPropertiesTable(const nsACString& aPrimaryFontName)
+ : mState(NS_TABLE_STATE_EMPTY) {
+ MOZ_COUNT_CTOR(nsPropertiesTable);
+ mGlyphCodeFonts.AppendElement(aPrimaryFontName);
+ }
+
+ MOZ_COUNTED_DTOR(nsPropertiesTable)
+
+ const nsCString& PrimaryFontName() const { return mGlyphCodeFonts[0]; }
+
+ const nsCString& 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<nsCString> 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) {
+ nsresult rv = LoadProperties(PrimaryFontName(), mGlyphProperties);
+#ifdef DEBUG
+ nsAutoCString uriStr;
+ uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
+ uriStr.Append(PrimaryFontName());
+ 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(NS_ConvertUTF16toUTF8(value));
+ }
+ }
+
+ // 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;
+ }
+ }
+ 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, gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags(), 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:
+ MOZ_COUNTED_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 nsCString& 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 UniquePtr<nsOpenTypeTable> Create(gfxFont* aFont) {
+ if (!aFont->TryGetMathTable()) {
+ return nullptr;
+ }
+ return WrapUnique(new nsOpenTypeTable(aFont));
+ }
+
+ private:
+ RefPtr<gfxFont> mFont;
+ nsCString mFontFamilyName;
+ uint32_t mGlyphID;
+
+ explicit nsOpenTypeTable(gfxFont* aFont)
+ : mFont(aFont),
+ mFontFamilyName(aFont->GetFontEntry()->FamilyName()),
+ mGlyphID(0) {
+ 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, gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags(), 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, gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags());
+ RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont();
+ textRun->AddGlyphRun(font, FontMatchType::Kind::kFontGroup, 0, false,
+ gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL, false);
+ // 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 * font->GetGlyphAdvance(aGlyph.glyphID));
+ textRun->SetDetailedGlyphs(0, 1, &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("Unicode"_ns) {}
+
+ nsresult Initialize();
+ nsresult Finalize();
+
+ // Add a glyph table in the list, return the new table that was added
+ nsGlyphTable* AddGlyphTable(const nsACString& aPrimaryFontName);
+
+ // Find the glyph table in the list corresponding to the given font family.
+ nsGlyphTable* GetGlyphTableFor(const nsACString& aFamily);
+
+ private:
+ ~nsGlyphTableList() = default;
+
+ 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 nsACString& 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 nsACString& aFamily) {
+ for (int32_t i = 0; i < PropertiesTableCount(); i++) {
+ nsPropertiesTable* glyphTable = PropertiesTableAt(i);
+ const nsCString& primaryFontName = glyphTable->PrimaryFontName();
+ // TODO: would be nice to consider StripWhitespace and other aliasing
+ if (primaryFontName.Equals(aFamily, nsCaseInsensitiveCStringComparator)) {
+ 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("STIXGeneral"_ns)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ glyphTableList.forget(&gGlyphTableList);
+ return rv;
+}
+
+// -----------------------------------------------------------------------------
+// And now the implementation of nsMathMLChar
+
+nsMathMLChar::~nsMathMLChar() { MOZ_COUNT_DTOR(nsMathMLChar); }
+
+ComputedStyle* nsMathMLChar::GetComputedStyle() const {
+ NS_ASSERTION(mComputedStyle, "chars should always have a ComputedStyle");
+ return mComputedStyle;
+}
+
+void nsMathMLChar::SetComputedStyle(ComputedStyle* aComputedStyle) {
+ MOZ_ASSERT(aComputedStyle);
+ mComputedStyle = aComputedStyle;
+}
+
+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
+ ComputedStyles in use throughout the process. The leaf style
+ context of the char holds fonts with which to try to stretch
+ the char. The parent ComputedStyle 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 ComputedStyle (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 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 StyleFontFamilyList& aDefaultFamilyList,
+ nsFont& aFont,
+ RefPtr<gfxFontGroup>* aFontGroup) {
+ StyleFontFamilyList glyphCodeFont;
+ if (aGlyphCode.font) {
+ glyphCodeFont = StyleFontFamilyList::WithOneUnquotedFamily(
+ aGlyphTable->FontNameFor(aGlyphCode));
+ }
+
+ const StyleFontFamilyList& familyList =
+ aGlyphCode.font ? glyphCodeFont : aDefaultFamilyList;
+
+ if (!*aFontGroup || aFont.family.families != familyList) {
+ nsFont font = aFont;
+ font.family.families = familyList;
+ const nsStyleFont* styleFont = mComputedStyle->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.userFontSet = aPresContext->GetUserFontSet();
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
+ RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params);
+ // Set the font if it is an unicode table or if the same family name has
+ // been found.
+ const bool shouldSetFont = [&] {
+ if (aGlyphTable == &gGlyphTableList->mUnicodeTable) {
+ return true;
+ }
+
+ if (familyList.list.IsEmpty()) {
+ return false;
+ }
+
+ const auto& firstFontInList = familyList.list.AsSpan()[0];
+
+ RefPtr<gfxFont> firstFont = fm->GetThebesFontGroup()->GetFirstValidFont();
+ RefPtr<nsAtom> firstFontName =
+ NS_Atomize(firstFont->GetFontEntry()->FamilyName());
+
+ return firstFontInList.IsFamilyName() &&
+ firstFontInList.AsFamilyName().name.AsAtom() == firstFontName;
+ }();
+ if (!shouldSetFont) {
+ return false;
+ }
+ aFont.family.families = familyList;
+ *aFontGroup = fm->GetThebesFontGroup();
+ }
+ 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 StyleFontFamilyList& 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 StyleSingleFontFamily& aFamily, void* aData);
+
+ private:
+ bool TryVariants(nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
+ const StyleFontFamilyList& aFamilyList);
+ bool TryParts(nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
+ const StyleFontFamilyList& 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 StyleFontFamilyList& 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 StyleFontFamilyList& aFamilyList) {
+ // Use our stretchy ComputedStyle now that stretching is in progress
+ ComputedStyle* sc = mChar->mComputedStyle;
+ 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()) {
+ RefPtr<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 (nsMathMLOperators::IsIntegralOperator(mChar->mData)) {
+ // 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()) {
+ RefPtr<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] = std::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 StyleFontFamilyList& aFamilyList) {
+ // Use our stretchy ComputedStyle now that stretching is in progress
+ nsFont font = mChar->mComputedStyle->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] = std::move(textRun[i]);
+ mChar->mBmData[i] = bmdata[i];
+ }
+
+ return IsSizeOK(computedSize, mTargetSize, mStretchHint);
+}
+
+// Returns true iff stretching succeeded with the given family.
+// This is called for each family, whether it exists or not.
+bool nsMathMLChar::StretchEnumContext::EnumCallback(
+ const StyleSingleFontFamily& aFamily, void* aData) {
+ StretchEnumContext* context = static_cast<StretchEnumContext*>(aData);
+
+ // for comparisons, force use of unquoted names
+ StyleFontFamilyList family;
+ if (aFamily.IsFamilyName()) {
+ family = StyleFontFamilyList::WithOneUnquotedFamily(
+ nsAtomCString(aFamily.AsFamilyName().name.AsAtom()));
+ }
+
+ // Check font family if it is not a generic one
+ // We test with the kNullGlyph
+ ComputedStyle* sc = context->mChar->mComputedStyle;
+ nsFont font = sc->StyleFont()->mFont;
+ NormalizeDefaultFont(font, context->mFontSizeInflation);
+ RefPtr<gfxFontGroup> fontGroup;
+ if (!aFamily.IsGeneric() &&
+ !context->mChar->SetFontFamily(context->mPresContext, nullptr, kNullGlyph,
+ family, font, &fontGroup)) {
+ return false; // Could not set the family
+ }
+
+ // Determine the glyph table to use for this font.
+ UniquePtr<nsOpenTypeTable> openTypeTable;
+ nsGlyphTable* glyphTable;
+ if (aFamily.IsGeneric()) {
+ // This is a generic font, use the Unicode table.
+ glyphTable = &gGlyphTableList->mUnicodeTable;
+ } else {
+ // If the font contains an Open Type MATH table, use it.
+ RefPtr<gfxFont> font = fontGroup->GetFirstValidFont();
+ openTypeTable = nsOpenTypeTable::Create(font);
+ if (openTypeTable) {
+ glyphTable = openTypeTable.get();
+ } else if (StaticPrefs::mathml_stixgeneral_operator_stretching_disabled()) {
+ glyphTable = &gGlyphTableList->mUnicodeTable;
+ } else {
+ // Otherwise try to find a .properties file corresponding to that font
+ // family or fallback to the Unicode table.
+ glyphTable = gGlyphTableList->GetGlyphTableFor(
+ nsAtomCString(aFamily.AsFamilyName().name.AsAtom()));
+ }
+ }
+
+ if (!openTypeTable) {
+ if (context->mTablesTried.Contains(glyphTable))
+ return false; // 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 StyleFontFamilyList& familyList =
+ glyphTable == &gGlyphTableList->mUnicodeTable ? context->mFamilyList
+ : family;
+
+ return (context->mTryVariants &&
+ context->TryVariants(glyphTable, &fontGroup, familyList)) ||
+ (context->mTryParts &&
+ context->TryParts(glyphTable, &fontGroup, familyList));
+}
+
+static void AppendFallbacks(nsTArray<StyleSingleFontFamily>& aNames,
+ const nsTArray<nsCString>& aFallbacks) {
+ for (const nsCString& fallback : aFallbacks) {
+ aNames.AppendElement(StyleSingleFontFamily::FamilyName(
+ StyleFamilyName{StyleAtom(NS_Atomize(fallback)),
+ StyleFontFamilyNameSyntax::Identifiers}));
+ }
+}
+
+// insert math fallback families just before the first generic or at the end
+// when no generic present
+static void InsertMathFallbacks(StyleFontFamilyList& aFamilyList,
+ nsTArray<nsCString>& aFallbacks) {
+ nsTArray<StyleSingleFontFamily> mergedList;
+
+ bool inserted = false;
+ for (const auto& name : aFamilyList.list.AsSpan()) {
+ if (!inserted && name.IsGeneric()) {
+ inserted = true;
+ AppendFallbacks(mergedList, aFallbacks);
+ }
+ mergedList.AppendElement(name);
+ }
+
+ if (!inserted) {
+ AppendFallbacks(mergedList, aFallbacks);
+ }
+ aFamilyList = StyleFontFamilyList::WithNames(std::move(mergedList));
+}
+
+nsresult nsMathMLChar::StretchInternal(
+ nsIFrame* aForFrame, 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) {
+ nsPresContext* presContext = aForFrame->PresContext();
+
+ // 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
+ // mComputedStyle is a leaf context used only when stretching happens.
+ // For the base size, the default font should come from the parent context
+ nsFont font = aForFrame->StyleFont()->mFont;
+ NormalizeDefaultFont(font, aFontSizeInflation);
+
+ const nsStyleFont* styleFont = mComputedStyle->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.userFontSet = presContext->GetUserFontSet();
+ params.textPerf = presContext->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> fm = presContext->GetMetricsFor(font, params);
+ uint32_t len = uint32_t(mData.Length());
+ mGlyphs[0] = fm->GetThebesFontGroup()->MakeTextRun(
+ static_cast<const char16_t*>(mData.get()), len, aDrawTarget,
+ presContext->AppUnitsPerDevPixel(), gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags(), presContext->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 = mComputedStyle->StyleFont()->mFont;
+ NormalizeDefaultFont(font, aFontSizeInflation);
+
+ // really shouldn't be doing things this way but for now
+ // insert fallbacks into the list
+ AutoTArray<nsCString, 16> mathFallbacks;
+ nsAutoCString value;
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ pfl->Lock();
+ if (pfl->GetFontPrefs()->LookupName("serif.x-math"_ns, value)) {
+ gfxFontUtils::ParseFontList(value, mathFallbacks);
+ }
+ if (pfl->GetFontPrefs()->LookupNameList("serif.x-math"_ns, value)) {
+ gfxFontUtils::ParseFontList(value, mathFallbacks);
+ }
+ pfl->Unlock();
+ InsertMathFallbacks(font.family.families, 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, presContext, aDrawTarget,
+ aFontSizeInflation, aStretchDirection,
+ targetSize, aStretchHint, aDesiredStretchSize,
+ font.family.families, glyphFound);
+ enumData.mTryParts = !largeopOnly;
+
+ for (const StyleSingleFontFamily& name :
+ font.family.families.list.AsSpan()) {
+ if (StretchEnumContext::EnumCallback(name, &enumData)) {
+ if (name.IsNamedFamily(u"STIXGeneral"_ns)) {
+ AutoTArray<nsString, 1> params{
+ u"https://developer.mozilla.org/docs/Mozilla/"
+ "MathML_Project/Fonts"_ns};
+ aForFrame->PresContext()->Document()->WarnOnceAbout(
+ dom::DeprecatedOperations::
+ eMathML_DeprecatedStixgeneralOperatorStretching,
+ false, params);
+ }
+ break;
+ }
+ }
+ }
+
+ 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 = presContext->MissingFontRecorder();
+ RefPtr<gfxFont> firstMathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+ if (MFR && !firstMathFont) {
+ MFR->RecordScript(intl::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 (nsMathMLOperators::IsIntegralOperator(mData)) {
+ // 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(nsIFrame* aForFrame, 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)),
+ "Unexpected stretch flags");
+
+ mDraw = DRAW_NORMAL;
+ mMirrored = aRTL && nsMathMLOperators::IsMirrorableOperator(mData);
+ mScaleY = mScaleX = 1.0;
+ mDirection = aStretchDirection;
+ nsresult rv =
+ StretchInternal(aForFrame, 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(nsIFrame* aForFrame, DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ uint32_t aStretchHint) {
+ nsBoundingMetrics bm;
+ nsStretchDirection direction = NS_STRETCH_DIRECTION_VERTICAL;
+ const nsBoundingMetrics container; // zero target size
+
+ StretchInternal(aForFrame, aDrawTarget, aFontSizeInflation, direction,
+ container, bm, aStretchHint | NS_STRETCH_MAXWIDTH);
+
+ return std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing);
+}
+
+namespace mozilla {
+
+class nsDisplayMathMLSelectionRect final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLSelectionRect(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLSelectionRect);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLSelectionRect)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLSelectionRect", TYPE_MATHML_SELECTION_RECT)
+ private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLSelectionRect::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* 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::Color(LookAndFeel::ColorID::Highlight, mFrame);
+ drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(bgColor)));
+}
+
+class nsDisplayMathMLCharForeground final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLCharForeground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsMathMLChar* aChar,
+ const bool aIsSelected)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mChar(aChar),
+ mIsSelected(aIsSelected) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLCharForeground)
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const 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,
+ gfxContext* aCtx) override {
+ mChar->PaintForeground(mFrame, *aCtx, ToReferenceFrame(), mIsSelected);
+ }
+
+ NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND)
+
+ virtual nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+
+ private:
+ nsMathMLChar* mChar;
+ bool mIsSelected;
+};
+
+#ifdef DEBUG
+class nsDisplayMathMLCharDebug final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLCharDebug(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLCharDebug);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLCharDebug)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLCharDebug", TYPE_MATHML_CHAR_DEBUG)
+
+ private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLCharDebug::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ // for visual debug
+ Sides skipSides;
+ nsPresContext* presContext = mFrame->PresContext();
+ ComputedStyle* computedStyle = mFrame->Style();
+ nsRect rect = mRect + ToReferenceFrame();
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SyncDecodeImages
+ : PaintBorderFlags();
+
+ // Since this is used only for debugging, we don't need to worry about
+ // tracking the ImgDrawResult.
+ Unused << nsCSSRendering::PaintBorder(presContext, *aCtx, mFrame,
+ GetPaintRect(aBuilder, aCtx), rect,
+ computedStyle, flags, skipSides);
+
+ nsCSSRendering::PaintNonThemedOutline(presContext, *aCtx, mFrame,
+ GetPaintRect(aBuilder, aCtx), rect,
+ computedStyle);
+}
+#endif
+
+} // namespace mozilla
+
+void nsMathMLChar::Display(nsDisplayListBuilder* aBuilder, nsIFrame* aForFrame,
+ const nsDisplayListSet& aLists, uint32_t aIndex,
+ const nsRect* aSelectedRect) {
+ ComputedStyle* computedStyle = mComputedStyle;
+ if (!computedStyle->StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ const bool isSelected = aSelectedRect && !aSelectedRect->IsEmpty();
+
+ if (isSelected) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayMathMLSelectionRect>(
+ aBuilder, aForFrame, *aSelectedRect);
+ } else if (mRect.width && mRect.height) {
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // for visual debug
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayMathMLCharDebug>(
+ aBuilder, aForFrame, mRect);
+#endif
+ }
+ aLists.Content()->AppendNewToTopWithIndex<nsDisplayMathMLCharForeground>(
+ aBuilder, aForFrame, aIndex, this, isSelected);
+}
+
+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->SetMatrixDouble(aThebesContext->CurrentMatrixDouble()
+ .PreTranslate(devPixelOffset)
+ .PreScale(-mScaleX, mScaleY));
+ } else {
+ nsPoint pt = r.TopLeft();
+ gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit),
+ NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit));
+ aThebesContext->SetMatrixDouble(aThebesContext->CurrentMatrixDouble()
+ .PreTranslate(devPixelOffset)
+ .PreScale(mScaleX, mScaleY));
+ }
+
+ // update the bounding rectangle.
+ r.x = r.y = 0;
+ r.width /= mScaleX;
+ r.height /= mScaleY;
+}
+
+void nsMathMLChar::PaintForeground(nsIFrame* aForFrame,
+ gfxContext& aRenderingContext, nsPoint aPt,
+ bool aIsSelected) {
+ ComputedStyle* computedStyle = mComputedStyle;
+ nsPresContext* presContext = aForFrame->PresContext();
+
+ if (mDraw == DRAW_NORMAL) {
+ // normal drawing if there is nothing special about this char
+ // Use our parent element's style
+ computedStyle = aForFrame->Style();
+ }
+
+ // Set color ...
+ nscolor fgColor = computedStyle->GetVisitedDependentColor(
+ &nsStyleText::mWebkitTextFillColor);
+ if (aIsSelected) {
+ // get color to use for selection from the look&feel object
+ fgColor = LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext, aForFrame,
+ fgColor);
+ }
+ aRenderingContext.SetColor(sRGBColor::FromABGR(fgColor));
+ aRenderingContext.Save();
+ nsRect r = mRect + aPt;
+ ApplyTransforms(&aRenderingContext,
+ aForFrame->PresContext()->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()),
+ gfx::Point(0.0, mUnscaledAscent),
+ gfxTextRun::DrawParams(&aRenderingContext));
+ }
+ break;
+ case DRAW_PARTS: {
+ // paint by parts
+ if (NS_STRETCH_DIRECTION_VERTICAL == mDirection)
+ PaintVertically(presContext, &aRenderingContext, r, fgColor);
+ else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection)
+ PaintHorizontally(presContext, &aRenderingContext, r, fgColor);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown drawing method");
+ break;
+ }
+
+ aRenderingContext.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();
+ gfxRect clip = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit);
+ mThebesContext->SnappedClip(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);
+
+ 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()), gfx::Point(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()), gfx::Point(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);
+
+ 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()), gfx::Point(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()), gfx::Point(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;
+}