summaryrefslogtreecommitdiffstats
path: root/layout/mathml/nsMathMLmunderoverFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/mathml/nsMathMLmunderoverFrame.cpp')
-rw-r--r--layout/mathml/nsMathMLmunderoverFrame.cpp727
1 files changed, 727 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLmunderoverFrame.cpp b/layout/mathml/nsMathMLmunderoverFrame.cpp
new file mode 100644
index 0000000000..a5b7a7fa85
--- /dev/null
+++ b/layout/mathml/nsMathMLmunderoverFrame.cpp
@@ -0,0 +1,727 @@
+/* -*- 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 "nsMathMLmunderoverFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsMathMLmmultiscriptsFrame.h"
+#include "mozilla/dom/MathMLElement.h"
+#include <algorithm>
+#include "gfxContext.h"
+#include "gfxMathTable.h"
+#include "gfxTextRun.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_mathml.h"
+
+using namespace mozilla;
+
+//
+// <munderover> -- attach an underscript-overscript pair to a base
+// implementation
+// <mover> -- attach an overscript to a base - implementation
+// <munder> -- attach an underscript to a base - implementation
+//
+
+nsIFrame* NS_NewMathMLmunderoverFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmunderoverFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmunderoverFrame)
+
+nsMathMLmunderoverFrame::~nsMathMLmunderoverFrame() = default;
+
+nsresult nsMathMLmunderoverFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (nsGkAtoms::accent_ == aAttribute ||
+ nsGkAtoms::accentunder_ == aAttribute) {
+ // When we have automatic data to update within ourselves, we ask our
+ // parent to re-layout its children
+ return ReLayoutChildren(GetParent());
+ }
+
+ return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+NS_IMETHODIMP
+nsMathMLmunderoverFrame::UpdatePresentationData(uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate) {
+ nsMathMLContainerFrame::UpdatePresentationData(aFlagsValues, aFlagsToUpdate);
+ // disable the stretch-all flag if we are going to act like a
+ // subscript-superscript pair
+ if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) &&
+ StyleFont()->mMathStyle == NS_STYLE_MATH_STYLE_COMPACT) {
+ mPresentationData.flags &= ~NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+ } else {
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMathMLmunderoverFrame::InheritAutomaticData(nsIFrame* aParent) {
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+
+ return NS_OK;
+}
+
+void nsMathMLmunderoverFrame::DestroyFrom(nsIFrame* aDestroyRoot,
+ PostDestroyData& aPostDestroyData) {
+ if (!mPostReflowIncrementScriptLevelCommands.IsEmpty()) {
+ PresShell()->CancelReflowCallback(this);
+ }
+ nsMathMLContainerFrame::DestroyFrom(aDestroyRoot, aPostDestroyData);
+}
+
+uint8_t nsMathMLmunderoverFrame::ScriptIncrement(nsIFrame* aFrame) {
+ nsIFrame* child = mFrames.FirstChild();
+ if (!aFrame || aFrame == child) {
+ return 0;
+ }
+ child = child->GetNextSibling();
+ if (aFrame == child) {
+ if (mContent->IsMathMLElement(nsGkAtoms::mover_)) {
+ return mIncrementOver ? 1 : 0;
+ }
+ return mIncrementUnder ? 1 : 0;
+ }
+ if (child && aFrame == child->GetNextSibling()) {
+ // must be a over frame of munderover
+ return mIncrementOver ? 1 : 0;
+ }
+ return 0; // frame not found
+}
+
+void nsMathMLmunderoverFrame::SetIncrementScriptLevel(uint32_t aChildIndex,
+ bool aIncrement) {
+ nsIFrame* child = PrincipalChildList().FrameAt(aChildIndex);
+ if (!child || !child->GetContent()->IsMathMLElement() ||
+ child->GetContent()->GetPrimaryFrame() != child) {
+ return;
+ }
+
+ auto element = dom::MathMLElement::FromNode(child->GetContent());
+ if (element->GetIncrementScriptLevel() == aIncrement) {
+ return;
+ }
+
+ if (mPostReflowIncrementScriptLevelCommands.IsEmpty()) {
+ PresShell()->PostReflowCallback(this);
+ }
+
+ mPostReflowIncrementScriptLevelCommands.AppendElement(
+ SetIncrementScriptLevelCommand{aChildIndex, aIncrement});
+}
+
+bool nsMathMLmunderoverFrame::ReflowFinished() {
+ SetPendingPostReflowIncrementScriptLevel();
+ return true;
+}
+
+void nsMathMLmunderoverFrame::ReflowCallbackCanceled() {
+ // Do nothing, at this point our work will just be useless.
+ mPostReflowIncrementScriptLevelCommands.Clear();
+}
+
+void nsMathMLmunderoverFrame::SetPendingPostReflowIncrementScriptLevel() {
+ MOZ_ASSERT(!mPostReflowIncrementScriptLevelCommands.IsEmpty());
+
+ nsTArray<SetIncrementScriptLevelCommand> commands =
+ std::move(mPostReflowIncrementScriptLevelCommands);
+
+ for (const auto& command : commands) {
+ nsIFrame* child = PrincipalChildList().FrameAt(command.mChildIndex);
+ if (!child || !child->GetContent()->IsMathMLElement()) {
+ continue;
+ }
+
+ auto element = dom::MathMLElement::FromNode(child->GetContent());
+ element->SetIncrementScriptLevel(command.mDoIncrement, true);
+ }
+}
+
+NS_IMETHODIMP
+nsMathMLmunderoverFrame::TransmitAutomaticData() {
+ // At this stage, all our children are in sync and we can fully
+ // resolve our own mEmbellishData struct
+ //---------------------------------------------------------------------
+
+ /*
+ The REC says:
+
+ As regards munder (respectively mover) :
+ The default value of accentunder is false, unless underscript
+ is an <mo> element or an embellished operator. If underscript is
+ an <mo> element, the value of its accent attribute is used as the
+ default value of accentunder. If underscript is an embellished
+ operator, the accent attribute of the <mo> element at its
+ core is used as the default value. As with all attributes, an
+ explicitly given value overrides the default.
+
+XXX The winner is the outermost setting in conflicting settings like these:
+<munder accentunder='true'>
+ <mi>...</mi>
+ <mo accentunder='false'> ... </mo>
+</munder>
+
+ As regards munderover:
+ The accent and accentunder attributes have the same effect as
+ the attributes with the same names on <mover> and <munder>,
+ respectively. Their default values are also computed in the
+ same manner as described for those elements, with the default
+ value of accent depending on overscript and the default value
+ of accentunder depending on underscript.
+ */
+
+ nsIFrame* overscriptFrame = nullptr;
+ nsIFrame* underscriptFrame = nullptr;
+ nsIFrame* baseFrame = mFrames.FirstChild();
+
+ if (baseFrame) {
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ underscriptFrame = baseFrame->GetNextSibling();
+ } else {
+ NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover_),
+ "mContent->NodeInfo()->NameAtom() not recognized");
+ overscriptFrame = baseFrame->GetNextSibling();
+ }
+ }
+ if (underscriptFrame && mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ overscriptFrame = underscriptFrame->GetNextSibling();
+ }
+
+ // if our base is an embellished operator, let its state bubble to us (in
+ // particular, this is where we get the flag for
+ // NS_MATHML_EMBELLISH_MOVABLELIMITS). Our flags are reset to the default
+ // values of false if the base frame isn't embellished.
+ mPresentationData.baseFrame = baseFrame;
+ GetEmbellishDataFrom(baseFrame, mEmbellishData);
+
+ // The default value of accentunder is false, unless the underscript is
+ // embellished and its core <mo> is an accent
+ nsEmbellishData embellishData;
+ nsAutoString value;
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ GetEmbellishDataFrom(underscriptFrame, embellishData);
+ if (NS_MATHML_EMBELLISH_IS_ACCENT(embellishData.flags)) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTUNDER;
+ } else {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTUNDER;
+ }
+
+ // if we have an accentunder attribute, it overrides what the underscript
+ // said
+ if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::accentunder_, value)) {
+ if (value.EqualsLiteral("true")) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTUNDER;
+ } else if (value.EqualsLiteral("false")) {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTUNDER;
+ }
+ }
+ }
+
+ // The default value of accent is false, unless the overscript is embellished
+ // and its core <mo> is an accent
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover_,
+ nsGkAtoms::munderover_)) {
+ GetEmbellishDataFrom(overscriptFrame, embellishData);
+ if (NS_MATHML_EMBELLISH_IS_ACCENT(embellishData.flags)) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTOVER;
+ } else {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTOVER;
+ }
+
+ // if we have an accent attribute, it overrides what the overscript said
+ if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accent_,
+ value)) {
+ if (value.EqualsLiteral("true")) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTOVER;
+ } else if (value.EqualsLiteral("false")) {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTOVER;
+ }
+ }
+ }
+
+ bool subsupDisplay =
+ NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) &&
+ StyleFont()->mMathStyle == NS_STYLE_MATH_STYLE_COMPACT;
+
+ // disable the stretch-all flag if we are going to act like a superscript
+ if (subsupDisplay) {
+ mPresentationData.flags &= ~NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+ }
+
+ // Now transmit any change that we want to our children so that they
+ // can update their mPresentationData structs
+ //---------------------------------------------------------------------
+
+ /* The REC says:
+ Within underscript, <munderover> always sets displaystyle to "false",
+ but increments scriptlevel by 1 only when accentunder is "false".
+
+ Within overscript, <munderover> always sets displaystyle to "false",
+ but increments scriptlevel by 1 only when accent is "false".
+
+ Within subscript and superscript it increments scriptlevel by 1, and
+ sets displaystyle to "false", but leaves both attributes unchanged within
+ base.
+
+ The TeXBook treats 'over' like a superscript, so p.141 or Rule 13a
+ say it shouldn't be compressed. However, The TeXBook says
+ that math accents and \overline change uncramped styles to their
+ cramped counterparts.
+ */
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover_,
+ nsGkAtoms::munderover_)) {
+ uint32_t compress = NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)
+ ? NS_MATHML_COMPRESSED
+ : 0;
+ mIncrementOver = !NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) ||
+ subsupDisplay;
+ SetIncrementScriptLevel(
+ mContent->IsMathMLElement(nsGkAtoms::mover_) ? 1 : 2, mIncrementOver);
+ if (mIncrementOver) {
+ PropagateFrameFlagFor(overscriptFrame, NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+ PropagatePresentationDataFor(overscriptFrame, compress, compress);
+ }
+ /*
+ The TeXBook treats 'under' like a subscript, so p.141 or Rule 13a
+ say it should be compressed
+ */
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ mIncrementUnder =
+ !NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags) ||
+ subsupDisplay;
+ SetIncrementScriptLevel(1, mIncrementUnder);
+ if (mIncrementUnder) {
+ PropagateFrameFlagFor(underscriptFrame,
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+ PropagatePresentationDataFor(underscriptFrame, NS_MATHML_COMPRESSED,
+ NS_MATHML_COMPRESSED);
+ }
+
+ /* Set flags for dtls font feature settings.
+
+ dtls
+ Dotless Forms
+ This feature provides dotless forms for Math Alphanumeric
+ characters, such as U+1D422 MATHEMATICAL BOLD SMALL I,
+ U+1D423 MATHEMATICAL BOLD SMALL J, U+1D456
+ U+MATHEMATICAL ITALIC SMALL I, U+1D457 MATHEMATICAL ITALIC
+ SMALL J, and so on.
+ The dotless forms are to be used as base forms for placing
+ mathematical accents over them.
+
+ To opt out of this change, add the following to the stylesheet:
+ "font-feature-settings: 'dtls' 0"
+ */
+ if (overscriptFrame &&
+ NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) &&
+ !NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags)) {
+ PropagatePresentationDataFor(baseFrame, NS_MATHML_DTLS, NS_MATHML_DTLS);
+ }
+
+ return NS_OK;
+}
+
+/*
+The REC says:
+* If the base is an operator with movablelimits="true" (or an embellished
+ operator whose <mo> element core has movablelimits="true"), and
+ displaystyle="false", then underscript and overscript are drawn in
+ a subscript and superscript position, respectively. In this case,
+ the accent and accentunder attributes are ignored. This is often
+ used for limits on symbols such as &sum;.
+
+i.e.,:
+ if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishDataflags) &&
+ StyleFont()->mMathStyle == NS_STYLE_MATH_STYLE_COMPACT) {
+ // place like subscript-superscript pair
+ }
+ else {
+ // place like underscript-overscript pair
+ }
+*/
+
+/* virtual */
+nsresult nsMathMLmunderoverFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) {
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) &&
+ StyleFont()->mMathStyle == NS_STYLE_MATH_STYLE_COMPACT) {
+ // place like sub sup or subsup
+ if (mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ return nsMathMLmmultiscriptsFrame::PlaceMultiScript(
+ PresContext(), aDrawTarget, aPlaceOrigin, aDesiredSize, this, 0, 0,
+ fontSizeInflation);
+ } else if (mContent->IsMathMLElement(nsGkAtoms::munder_)) {
+ return nsMathMLmmultiscriptsFrame::PlaceMultiScript(
+ PresContext(), aDrawTarget, aPlaceOrigin, aDesiredSize, this, 0, 0,
+ fontSizeInflation);
+ } else {
+ NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover_),
+ "mContent->NodeInfo()->NameAtom() not recognized");
+ return nsMathMLmmultiscriptsFrame::PlaceMultiScript(
+ PresContext(), aDrawTarget, aPlaceOrigin, aDesiredSize, this, 0, 0,
+ fontSizeInflation);
+ }
+ }
+
+ ////////////////////////////////////
+ // Get the children's desired sizes
+
+ nsBoundingMetrics bmBase, bmUnder, bmOver;
+ ReflowOutput baseSize(aDesiredSize.GetWritingMode());
+ ReflowOutput underSize(aDesiredSize.GetWritingMode());
+ ReflowOutput overSize(aDesiredSize.GetWritingMode());
+ nsIFrame* overFrame = nullptr;
+ nsIFrame* underFrame = nullptr;
+ nsIFrame* baseFrame = mFrames.FirstChild();
+ underSize.SetBlockStartAscent(0);
+ overSize.SetBlockStartAscent(0);
+ bool haveError = false;
+ if (baseFrame) {
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ underFrame = baseFrame->GetNextSibling();
+ } else if (mContent->IsMathMLElement(nsGkAtoms::mover_)) {
+ overFrame = baseFrame->GetNextSibling();
+ }
+ }
+ if (underFrame && mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ overFrame = underFrame->GetNextSibling();
+ }
+
+ if (mContent->IsMathMLElement(nsGkAtoms::munder_)) {
+ if (!baseFrame || !underFrame || underFrame->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ haveError = true;
+ }
+ }
+ if (mContent->IsMathMLElement(nsGkAtoms::mover_)) {
+ if (!baseFrame || !overFrame || overFrame->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ haveError = true;
+ }
+ }
+ if (mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ if (!baseFrame || !underFrame || !overFrame ||
+ overFrame->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ haveError = true;
+ }
+ }
+ if (haveError) {
+ if (aPlaceOrigin) {
+ ReportChildCountError();
+ }
+ return ReflowError(aDrawTarget, aDesiredSize);
+ }
+ GetReflowAndBoundingMetricsFor(baseFrame, baseSize, bmBase);
+ if (underFrame) {
+ GetReflowAndBoundingMetricsFor(underFrame, underSize, bmUnder);
+ }
+ if (overFrame) {
+ GetReflowAndBoundingMetricsFor(overFrame, overSize, bmOver);
+ }
+
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ ////////////////////
+ // Place Children
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+
+ nscoord xHeight = fm->XHeight();
+ nscoord oneDevPixel = fm->AppUnitsPerDevPixel();
+ gfxFont* mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+
+ nscoord ruleThickness;
+ GetRuleThickness(aDrawTarget, fm, ruleThickness);
+
+ nscoord correction = 0;
+ GetItalicCorrection(bmBase, correction);
+
+ // there are 2 different types of placement depending on
+ // whether we want an accented under or not
+
+ nscoord underDelta1 = 0; // gap between base and underscript
+ nscoord underDelta2 = 0; // extra space beneath underscript
+
+ if (!NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) {
+ // Rule 13a, App. G, TeXbook
+ nscoord bigOpSpacing2, bigOpSpacing4, bigOpSpacing5, dummy;
+ GetBigOpSpacings(fm, dummy, bigOpSpacing2, dummy, bigOpSpacing4,
+ bigOpSpacing5);
+ if (mathFont) {
+ // XXXfredw The Open Type MATH table has some StretchStack* parameters
+ // that we may use when the base is a stretchy horizontal operator. See
+ // bug 963131.
+ bigOpSpacing2 = mathFont->MathTable()->Constant(
+ gfxMathTable::LowerLimitGapMin, oneDevPixel);
+ bigOpSpacing4 = mathFont->MathTable()->Constant(
+ gfxMathTable::LowerLimitBaselineDropMin, oneDevPixel);
+ bigOpSpacing5 = 0;
+ }
+ underDelta1 = std::max(bigOpSpacing2, (bigOpSpacing4 - bmUnder.ascent));
+ underDelta2 = bigOpSpacing5;
+ } else {
+ // No corresponding rule in TeXbook - we are on our own here
+ // XXX tune the gap delta between base and underscript
+ // XXX Should we use Rule 10 like \underline does?
+ // XXXfredw Perhaps use the Underbar* parameters of the MATH table. See
+ // bug 963125.
+ underDelta1 = ruleThickness + onePixel / 2;
+ underDelta2 = ruleThickness;
+ }
+ // empty under?
+ if (!(bmUnder.ascent + bmUnder.descent)) {
+ underDelta1 = 0;
+ underDelta2 = 0;
+ }
+
+ nscoord overDelta1 = 0; // gap between base and overscript
+ nscoord overDelta2 = 0; // extra space above overscript
+
+ if (!NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)) {
+ // Rule 13a, App. G, TeXbook
+ // XXXfredw The Open Type MATH table has some StretchStack* parameters
+ // that we may use when the base is a stretchy horizontal operator. See
+ // bug 963131.
+ nscoord bigOpSpacing1, bigOpSpacing3, bigOpSpacing5, dummy;
+ GetBigOpSpacings(fm, bigOpSpacing1, dummy, bigOpSpacing3, dummy,
+ bigOpSpacing5);
+ if (mathFont) {
+ // XXXfredw The Open Type MATH table has some StretchStack* parameters
+ // that we may use when the base is a stretchy horizontal operator. See
+ // bug 963131.
+ bigOpSpacing1 = mathFont->MathTable()->Constant(
+ gfxMathTable::UpperLimitGapMin, oneDevPixel);
+ bigOpSpacing3 = mathFont->MathTable()->Constant(
+ gfxMathTable::UpperLimitBaselineRiseMin, oneDevPixel);
+ bigOpSpacing5 = 0;
+ }
+ overDelta1 = std::max(bigOpSpacing1, (bigOpSpacing3 - bmOver.descent));
+ overDelta2 = bigOpSpacing5;
+
+ // XXX This is not a TeX rule...
+ // delta1 (as computed abvove) can become really big when bmOver.descent is
+ // negative, e.g., if the content is &OverBar. In such case, we use the
+ // height
+ if (bmOver.descent < 0)
+ overDelta1 = std::max(bigOpSpacing1,
+ (bigOpSpacing3 - (bmOver.ascent + bmOver.descent)));
+ } else {
+ // Rule 12, App. G, TeXbook
+ // We are going to modify this rule to make it more general.
+ // The idea behind Rule 12 in the TeXBook is to keep the accent
+ // as close to the base as possible, while ensuring that the
+ // distance between the *baseline* of the accent char and
+ // the *baseline* of the base is atleast x-height.
+ // The idea is that for normal use, we would like all the accents
+ // on a line to line up atleast x-height above the baseline
+ // if possible.
+ // When the ascent of the base is >= x-height,
+ // the baseline of the accent char is placed just above the base
+ // (specifically, the baseline of the accent char is placed
+ // above the baseline of the base by the ascent of the base).
+ // For ease of implementation,
+ // this assumes that the font-designer designs accents
+ // in such a way that the bottom of the accent is atleast x-height
+ // above its baseline, otherwise there will be collisions
+ // with the base. Also there should be proper padding between
+ // the bottom of the accent char and its baseline.
+ // The above rule may not be obvious from a first
+ // reading of rule 12 in the TeXBook !!!
+ // The mathml <mover> tag can use accent chars that
+ // do not follow this convention. So we modify TeX's rule
+ // so that TeX's rule gets subsumed for accents that follow
+ // TeX's convention,
+ // while also allowing accents that do not follow the convention :
+ // we try to keep the *bottom* of the accent char atleast x-height
+ // from the baseline of the base char. we also slap on an extra
+ // padding between the accent and base chars.
+ overDelta1 = ruleThickness + onePixel / 2;
+ nscoord accentBaseHeight = xHeight;
+ if (mathFont) {
+ accentBaseHeight = mathFont->MathTable()->Constant(
+ gfxMathTable::AccentBaseHeight, oneDevPixel);
+ }
+ if (bmBase.ascent < accentBaseHeight) {
+ // also ensure at least accentBaseHeight above the baseline of the base
+ overDelta1 += accentBaseHeight - bmBase.ascent;
+ }
+ overDelta2 = ruleThickness;
+ }
+ // empty over?
+ if (!(bmOver.ascent + bmOver.descent)) {
+ overDelta1 = 0;
+ overDelta2 = 0;
+ }
+
+ nscoord dxBase = 0, dxOver = 0, dxUnder = 0;
+ nsAutoString valueAlign;
+ enum { center, left, right } alignPosition = center;
+
+ if (!StaticPrefs::mathml_deprecated_alignment_attributes_disabled() &&
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align,
+ valueAlign)) {
+ mContent->OwnerDoc()->WarnOnceAbout(
+ dom::DeprecatedOperations::eMathML_DeprecatedAlignmentAttributes);
+ if (valueAlign.EqualsLiteral("left")) {
+ alignPosition = left;
+ } else if (valueAlign.EqualsLiteral("right")) {
+ alignPosition = right;
+ }
+ }
+
+ //////////
+ // pass 1, do what <mover> does: attach the overscript on the base
+
+ // Ad-hoc - This is to override fonts which have ready-made _accent_
+ // glyphs with negative lbearing and rbearing. We want to position
+ // the overscript ourselves
+ nscoord overWidth = bmOver.width;
+ if (!overWidth && (bmOver.rightBearing - bmOver.leftBearing > 0)) {
+ overWidth = bmOver.rightBearing - bmOver.leftBearing;
+ dxOver = -bmOver.leftBearing;
+ }
+
+ if (NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)) {
+ mBoundingMetrics.width = bmBase.width;
+ if (alignPosition == center) {
+ dxOver += correction;
+ }
+ } else {
+ mBoundingMetrics.width = std::max(bmBase.width, overWidth);
+ if (alignPosition == center) {
+ dxOver += correction / 2;
+ }
+ }
+
+ if (alignPosition == center) {
+ dxOver += (mBoundingMetrics.width - overWidth) / 2;
+ dxBase = (mBoundingMetrics.width - bmBase.width) / 2;
+ } else if (alignPosition == right) {
+ dxOver += mBoundingMetrics.width - overWidth;
+ dxBase = mBoundingMetrics.width - bmBase.width;
+ }
+
+ mBoundingMetrics.ascent =
+ bmBase.ascent + overDelta1 + bmOver.ascent + bmOver.descent;
+ mBoundingMetrics.descent = bmBase.descent;
+ mBoundingMetrics.leftBearing =
+ std::min(dxBase + bmBase.leftBearing, dxOver + bmOver.leftBearing);
+ mBoundingMetrics.rightBearing =
+ std::max(dxBase + bmBase.rightBearing, dxOver + bmOver.rightBearing);
+
+ //////////
+ // pass 2, do what <munder> does: attach the underscript on the previous
+ // result. We conceptually view the previous result as an "anynomous base"
+ // from where to attach the underscript. Hence if the underscript is empty,
+ // we should end up like <mover>. If the overscript is empty, we should
+ // end up like <munder>.
+
+ nsBoundingMetrics bmAnonymousBase = mBoundingMetrics;
+ nscoord ascentAnonymousBase =
+ std::max(mBoundingMetrics.ascent + overDelta2,
+ overSize.BlockStartAscent() + bmOver.descent + overDelta1 +
+ bmBase.ascent);
+ ascentAnonymousBase =
+ std::max(ascentAnonymousBase, baseSize.BlockStartAscent());
+
+ // Width of non-spacing marks is zero so use left and right bearing.
+ nscoord underWidth = bmUnder.width;
+ if (!underWidth) {
+ underWidth = bmUnder.rightBearing - bmUnder.leftBearing;
+ dxUnder = -bmUnder.leftBearing;
+ }
+
+ nscoord maxWidth = std::max(bmAnonymousBase.width, underWidth);
+ if (alignPosition == center &&
+ !NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) {
+ GetItalicCorrection(bmAnonymousBase, correction);
+ dxUnder += -correction / 2;
+ }
+ nscoord dxAnonymousBase = 0;
+ if (alignPosition == center) {
+ dxUnder += (maxWidth - underWidth) / 2;
+ dxAnonymousBase = (maxWidth - bmAnonymousBase.width) / 2;
+ } else if (alignPosition == right) {
+ dxUnder += maxWidth - underWidth;
+ dxAnonymousBase = maxWidth - bmAnonymousBase.width;
+ }
+
+ // adjust the offsets of the real base and overscript since their
+ // final offsets should be relative to us...
+ dxOver += dxAnonymousBase;
+ dxBase += dxAnonymousBase;
+
+ mBoundingMetrics.width = std::max(dxAnonymousBase + bmAnonymousBase.width,
+ dxUnder + bmUnder.width);
+ // At this point, mBoundingMetrics.ascent = bmAnonymousBase.ascent
+ mBoundingMetrics.descent =
+ bmAnonymousBase.descent + underDelta1 + bmUnder.ascent + bmUnder.descent;
+ mBoundingMetrics.leftBearing =
+ std::min(dxAnonymousBase + bmAnonymousBase.leftBearing,
+ dxUnder + bmUnder.leftBearing);
+ mBoundingMetrics.rightBearing =
+ std::max(dxAnonymousBase + bmAnonymousBase.rightBearing,
+ dxUnder + bmUnder.rightBearing);
+
+ aDesiredSize.SetBlockStartAscent(ascentAnonymousBase);
+ aDesiredSize.Height() =
+ aDesiredSize.BlockStartAscent() +
+ std::max(mBoundingMetrics.descent + underDelta2,
+ bmAnonymousBase.descent + underDelta1 + bmUnder.ascent +
+ underSize.Height() - underSize.BlockStartAscent());
+ aDesiredSize.Height() =
+ std::max(aDesiredSize.Height(), aDesiredSize.BlockStartAscent() +
+ baseSize.Height() -
+ baseSize.BlockStartAscent());
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ nscoord dy;
+ // place overscript
+ if (overFrame) {
+ dy = aDesiredSize.BlockStartAscent() - mBoundingMetrics.ascent +
+ bmOver.ascent - overSize.BlockStartAscent();
+ FinishReflowChild(overFrame, PresContext(), overSize, nullptr, dxOver, dy,
+ ReflowChildFlags::Default);
+ }
+ // place base
+ dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent();
+ FinishReflowChild(baseFrame, PresContext(), baseSize, nullptr, dxBase, dy,
+ ReflowChildFlags::Default);
+ // place underscript
+ if (underFrame) {
+ dy = aDesiredSize.BlockStartAscent() + mBoundingMetrics.descent -
+ bmUnder.descent - underSize.BlockStartAscent();
+ FinishReflowChild(underFrame, PresContext(), underSize, nullptr, dxUnder,
+ dy, ReflowChildFlags::Default);
+ }
+ }
+ return NS_OK;
+}