diff options
Diffstat (limited to 'layout/mathml/nsMathMLmunderoverFrame.cpp')
-rw-r--r-- | layout/mathml/nsMathMLmunderoverFrame.cpp | 727 |
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 ∑. + +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; +} |