diff options
Diffstat (limited to 'layout/mathml/nsMathMLContainerFrame.cpp')
-rw-r--r-- | layout/mathml/nsMathMLContainerFrame.cpp | 1607 |
1 files changed, 1607 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLContainerFrame.cpp b/layout/mathml/nsMathMLContainerFrame.cpp new file mode 100644 index 000000000..ad1b13efd --- /dev/null +++ b/layout/mathml/nsMathMLContainerFrame.cpp @@ -0,0 +1,1607 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMathMLContainerFrame.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsStyleContext.h" +#include "nsNameSpaceManager.h" +#include "nsRenderingContext.h" +#include "nsIDOMMutationEvent.h" +#include "nsGkAtoms.h" +#include "nsDisplayList.h" +#include "nsIReflowCallback.h" +#include "mozilla/Likely.h" +#include "nsIScriptError.h" +#include "nsContentUtils.h" +#include "nsMathMLElement.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +// +// nsMathMLContainerFrame implementation +// + +NS_QUERYFRAME_HEAD(nsMathMLContainerFrame) + NS_QUERYFRAME_ENTRY(nsIMathMLFrame) + NS_QUERYFRAME_ENTRY(nsMathMLContainerFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +// ============================================================================= + +// error handlers +// provide a feedback to the user when a frame with bad markup can not be rendered +nsresult +nsMathMLContainerFrame::ReflowError(DrawTarget* aDrawTarget, + ReflowOutput& aDesiredSize) +{ + // clear all other flags and record that there is an error with this frame + mEmbellishData.flags = 0; + mPresentationData.flags = NS_MATHML_ERROR; + + /////////////// + // Set font + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetInflatedFontMetricsForFrame(this); + + // bounding metrics + nsAutoString errorMsg; errorMsg.AssignLiteral("invalid-markup"); + mBoundingMetrics = + nsLayoutUtils::AppUnitBoundsOfString(errorMsg.get(), errorMsg.Length(), + *fm, aDrawTarget); + + // reflow metrics + WritingMode wm = aDesiredSize.GetWritingMode(); + aDesiredSize.SetBlockStartAscent(fm->MaxAscent()); + nscoord descent = fm->MaxDescent(); + aDesiredSize.BSize(wm) = aDesiredSize.BlockStartAscent() + descent; + aDesiredSize.ISize(wm) = mBoundingMetrics.width; + + // Also return our bounding metrics + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + + return NS_OK; +} + +class nsDisplayMathMLError : public nsDisplayItem { +public: + nsDisplayMathMLError(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayMathMLError); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayMathMLError() { + MOZ_COUNT_DTOR(nsDisplayMathMLError); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + NS_DISPLAY_DECL_NAME("MathMLError", TYPE_MATHML_ERROR) +}; + +void nsDisplayMathMLError::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + // Set color and font ... + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f); + + nsPoint pt = ToReferenceFrame(); + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + Rect rect = NSRectToSnappedRect(nsRect(pt, mFrame->GetSize()), + appUnitsPerDevPixel, + *drawTarget); + ColorPattern red(ToDeviceColor(Color(1.f, 0.f, 0.f, 1.f))); + drawTarget->FillRect(rect, red); + + aCtx->ThebesContext()->SetColor(Color(1.f, 1.f, 1.f)); + nscoord ascent = fm->MaxAscent(); + NS_NAMED_LITERAL_STRING(errorMsg, "invalid-markup"); + nsLayoutUtils::DrawUniDirString(errorMsg.get(), uint32_t(errorMsg.Length()), + nsPoint(pt.x, pt.y + ascent), *fm, *aCtx); +} + +/* ///////////// + * nsIMathMLFrame - support methods for stretchy elements + * ============================================================================= + */ + +static bool +IsForeignChild(const nsIFrame* aFrame) +{ + // This counts nsMathMLmathBlockFrame as a foreign child, because it + // uses block reflow + return !(aFrame->IsFrameOfType(nsIFrame::eMathML)) || + aFrame->GetType() == nsGkAtoms::blockFrame; +} + +NS_DECLARE_FRAME_PROPERTY_DELETABLE(HTMLReflowOutputProperty, + ReflowOutput) + +/* static */ void +nsMathMLContainerFrame::SaveReflowAndBoundingMetricsFor(nsIFrame* aFrame, + const ReflowOutput& aReflowOutput, + const nsBoundingMetrics& aBoundingMetrics) +{ + ReflowOutput* reflowOutput = new ReflowOutput(aReflowOutput); + reflowOutput->mBoundingMetrics = aBoundingMetrics; + aFrame->Properties().Set(HTMLReflowOutputProperty(), reflowOutput); +} + +// helper method to facilitate getting the reflow and bounding metrics +/* static */ void +nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor(nsIFrame* aFrame, + ReflowOutput& aReflowOutput, + nsBoundingMetrics& aBoundingMetrics, + eMathMLFrameType* aMathMLFrameType) +{ + NS_PRECONDITION(aFrame, "null arg"); + + ReflowOutput* reflowOutput = + aFrame->Properties().Get(HTMLReflowOutputProperty()); + + // IMPORTANT: This function is only meant to be called in Place() methods + // where it is assumed that SaveReflowAndBoundingMetricsFor has recorded the + // information. + NS_ASSERTION(reflowOutput, "Didn't SaveReflowAndBoundingMetricsFor frame!"); + if (reflowOutput) { + aReflowOutput = *reflowOutput; + aBoundingMetrics = reflowOutput->mBoundingMetrics; + } + + if (aMathMLFrameType) { + if (!IsForeignChild(aFrame)) { + nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame); + if (mathMLFrame) { + *aMathMLFrameType = mathMLFrame->GetMathMLFrameType(); + return; + } + } + *aMathMLFrameType = eMathMLFrameType_UNKNOWN; + } + +} + +void +nsMathMLContainerFrame::ClearSavedChildMetrics() +{ + nsIFrame* childFrame = mFrames.FirstChild(); + FramePropertyTable* props = PresContext()->PropertyTable(); + while (childFrame) { + props->Delete(childFrame, HTMLReflowOutputProperty()); + childFrame = childFrame->GetNextSibling(); + } +} + +// helper to get the preferred size that a container frame should use to fire +// the stretch on its stretchy child frames. +void +nsMathMLContainerFrame::GetPreferredStretchSize(DrawTarget* aDrawTarget, + uint32_t aOptions, + nsStretchDirection aStretchDirection, + nsBoundingMetrics& aPreferredStretchSize) +{ + if (aOptions & STRETCH_CONSIDER_ACTUAL_SIZE) { + // when our actual size is ok, just use it + aPreferredStretchSize = mBoundingMetrics; + } + else if (aOptions & STRETCH_CONSIDER_EMBELLISHMENTS) { + // compute our up-to-date size using Place() + ReflowOutput reflowOutput(GetWritingMode()); + Place(aDrawTarget, false, reflowOutput); + aPreferredStretchSize = reflowOutput.mBoundingMetrics; + } + else { + // compute a size that includes embellishments iff the container stretches + // in the same direction as the embellished operator. + bool stretchAll = aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL ? + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) : + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags); + NS_ASSERTION(aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL || + aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL, + "You must specify a direction in which to stretch"); + NS_ASSERTION(NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) || + stretchAll, + "invalid call to GetPreferredStretchSize"); + bool firstTime = true; + nsBoundingMetrics bm, bmChild; + nsIFrame* childFrame = + stretchAll ? PrincipalChildList().FirstChild() : mPresentationData.baseFrame; + while (childFrame) { + // initializations in case this child happens not to be a MathML frame + nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame); + if (mathMLFrame) { + nsEmbellishData embellishData; + nsPresentationData presentationData; + mathMLFrame->GetEmbellishData(embellishData); + mathMLFrame->GetPresentationData(presentationData); + if (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags) && + embellishData.direction == aStretchDirection && + presentationData.baseFrame) { + // embellishements are not included, only consider the inner first child itself + // XXXkt Does that mean the core descendent frame should be used + // instead of the base child? + nsIMathMLFrame* mathMLchildFrame = do_QueryFrame(presentationData.baseFrame); + if (mathMLchildFrame) { + mathMLFrame = mathMLchildFrame; + } + } + mathMLFrame->GetBoundingMetrics(bmChild); + } + else { + ReflowOutput unused(GetWritingMode()); + GetReflowAndBoundingMetricsFor(childFrame, unused, bmChild); + } + + if (firstTime) { + firstTime = false; + bm = bmChild; + if (!stretchAll) { + // we may get here for cases such as <msup><mo>...</mo> ... </msup>, + // or <maction>...<mo>...</mo></maction>. + break; + } + } + else { + if (aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL) { + // if we get here, it means this is container that will stack its children + // vertically and fire an horizontal stretch on each them. This is the case + // for \munder, \mover, \munderover. We just sum-up the size vertically. + bm.descent += bmChild.ascent + bmChild.descent; + // Sometimes non-spacing marks (when width is zero) are positioned + // to the left of the origin, but it is the distance between left + // and right bearing that is important rather than the offsets from + // the origin. + if (bmChild.width == 0) { + bmChild.rightBearing -= bmChild.leftBearing; + bmChild.leftBearing = 0; + } + if (bm.leftBearing > bmChild.leftBearing) + bm.leftBearing = bmChild.leftBearing; + if (bm.rightBearing < bmChild.rightBearing) + bm.rightBearing = bmChild.rightBearing; + } + else if (aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL) { + // just sum-up the sizes horizontally. + bm += bmChild; + } + else { + NS_ERROR("unexpected case in GetPreferredStretchSize"); + break; + } + } + childFrame = childFrame->GetNextSibling(); + } + aPreferredStretchSize = bm; + } +} + +NS_IMETHODIMP +nsMathMLContainerFrame::Stretch(DrawTarget* aDrawTarget, + nsStretchDirection aStretchDirection, + nsBoundingMetrics& aContainerSize, + ReflowOutput& aDesiredStretchSize) +{ + if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) { + + if (NS_MATHML_STRETCH_WAS_DONE(mPresentationData.flags)) { + NS_WARNING("it is wrong to fire stretch more than once on a frame"); + return NS_OK; + } + mPresentationData.flags |= NS_MATHML_STRETCH_DONE; + + if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) { + NS_WARNING("it is wrong to fire stretch on a erroneous frame"); + return NS_OK; + } + + // Pass the stretch to the base child ... + + nsIFrame* baseFrame = mPresentationData.baseFrame; + if (baseFrame) { + nsIMathMLFrame* mathMLFrame = do_QueryFrame(baseFrame); + NS_ASSERTION(mathMLFrame, "Something is wrong somewhere"); + if (mathMLFrame) { + + // And the trick is that the child's rect.x is still holding the descent, + // and rect.y is still holding the ascent ... + ReflowOutput childSize(aDesiredStretchSize); + GetReflowAndBoundingMetricsFor(baseFrame, childSize, childSize.mBoundingMetrics); + + // See if we should downsize and confine the stretch to us... + // XXX there may be other cases where we can downsize the stretch, + // e.g., the first ∑ might appear big in the following situation + // <math xmlns='http://www.w3.org/1998/Math/MathML'> + // <mstyle> + // <msub> + // <msub><mo>∑</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub> + // <msub><mo>∑</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub> + // </msub> + // </mstyle> + // </math> + nsBoundingMetrics containerSize = aContainerSize; + if (aStretchDirection != mEmbellishData.direction && + mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED) { + NS_ASSERTION(mEmbellishData.direction != NS_STRETCH_DIRECTION_DEFAULT, + "Stretches may have a default direction, operators can not."); + if (mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL ? + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) : + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags)) { + GetPreferredStretchSize(aDrawTarget, 0, + mEmbellishData.direction, containerSize); + // Stop further recalculations + aStretchDirection = mEmbellishData.direction; + } else { + // We aren't going to stretch the child, so just use the child metrics. + containerSize = childSize.mBoundingMetrics; + } + } + + // do the stretching... + mathMLFrame->Stretch(aDrawTarget, + aStretchDirection, containerSize, childSize); + // store the updated metrics + SaveReflowAndBoundingMetricsFor(baseFrame, childSize, + childSize.mBoundingMetrics); + + // Remember the siblings which were _deferred_. + // Now that this embellished child may have changed, we need to + // fire the stretch on its siblings using our updated size + + if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags)) { + + nsStretchDirection stretchDir = + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) ? + NS_STRETCH_DIRECTION_VERTICAL : NS_STRETCH_DIRECTION_HORIZONTAL; + + GetPreferredStretchSize(aDrawTarget, STRETCH_CONSIDER_EMBELLISHMENTS, + stretchDir, containerSize); + + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + if (childFrame != mPresentationData.baseFrame) { + mathMLFrame = do_QueryFrame(childFrame); + if (mathMLFrame) { + // retrieve the metrics that was stored at the previous pass + GetReflowAndBoundingMetricsFor(childFrame, + childSize, childSize.mBoundingMetrics); + // do the stretching... + mathMLFrame->Stretch(aDrawTarget, stretchDir, + containerSize, childSize); + // store the updated metrics + SaveReflowAndBoundingMetricsFor(childFrame, childSize, + childSize.mBoundingMetrics); + } + } + childFrame = childFrame->GetNextSibling(); + } + } + + // re-position all our children + nsresult rv = Place(aDrawTarget, true, aDesiredStretchSize); + if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) { + // Make sure the child frames get their DidReflow() calls. + DidReflowChildren(mFrames.FirstChild()); + } + + // If our parent is not embellished, it means we are the outermost embellished + // container and so we put the spacing, otherwise we don't include the spacing, + // the outermost embellished container will take care of it. + + nsEmbellishData parentData; + GetEmbellishDataFrom(GetParent(), parentData); + // ensure that we are the embellished child, not just a sibling + // (need to test coreFrame since <mfrac> resets other things) + if (parentData.coreFrame != mEmbellishData.coreFrame) { + // (we fetch values from the core since they may use units that depend + // on style data, and style changes could have occurred in the core since + // our last visit there) + nsEmbellishData coreData; + GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData); + + mBoundingMetrics.width += + coreData.leadingSpace + coreData.trailingSpace; + aDesiredStretchSize.Width() = mBoundingMetrics.width; + aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width; + + nscoord dx = (StyleVisibility()->mDirection ? + coreData.trailingSpace : coreData.leadingSpace); + if (dx != 0) { + mBoundingMetrics.leftBearing += dx; + mBoundingMetrics.rightBearing += dx; + aDesiredStretchSize.mBoundingMetrics.leftBearing += dx; + aDesiredStretchSize.mBoundingMetrics.rightBearing += dx; + + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + childFrame->SetPosition(childFrame->GetPosition() + + nsPoint(dx, 0)); + childFrame = childFrame->GetNextSibling(); + } + } + } + + // Finished with these: + ClearSavedChildMetrics(); + // Set our overflow area + GatherAndStoreOverflow(&aDesiredStretchSize); + } + } + } + return NS_OK; +} + +nsresult +nsMathMLContainerFrame::FinalizeReflow(DrawTarget* aDrawTarget, + ReflowOutput& aDesiredSize) +{ + // During reflow, we use rect.x and rect.y as placeholders for the child's ascent + // and descent in expectation of a stretch command. Hence we need to ensure that + // a stretch command will actually be fired later on, after exiting from our + // reflow. If the stretch is not fired, the rect.x, and rect.y will remain + // with inappropriate data causing children to be improperly positioned. + // This helper method checks to see if our parent will fire a stretch command + // targeted at us. If not, we go ahead and fire an involutive stretch on + // ourselves. This will clear all the rect.x and rect.y, and return our + // desired size. + + + // First, complete the post-reflow hook. + // We use the information in our children rectangles to position them. + // If placeOrigin==false, then Place() will not touch rect.x, and rect.y. + // They will still be holding the ascent and descent for each child. + + // The first clause caters for any non-embellished container. + // The second clause is for a container which won't fire stretch even though it is + // embellished, e.g., as in <mfrac><mo>...</mo> ... </mfrac>, the test is convoluted + // because it excludes the particular case of the core <mo>...</mo> itself. + // (<mo> needs to fire stretch on its MathMLChar in any case to initialize it) + bool placeOrigin = !NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) || + (mEmbellishData.coreFrame != this && !mPresentationData.baseFrame && + mEmbellishData.direction == NS_STRETCH_DIRECTION_UNSUPPORTED); + nsresult rv = Place(aDrawTarget, placeOrigin, aDesiredSize); + + // Place() will call FinishReflowChild() when placeOrigin is true but if + // it returns before reaching FinishReflowChild() due to errors we need + // to fulfill the reflow protocol by calling DidReflow for the child frames + // that still needs it here (or we may crash - bug 366012). + // If placeOrigin is false we should reach Place() with aPlaceOrigin == true + // through Stretch() eventually. + if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) { + GatherAndStoreOverflow(&aDesiredSize); + DidReflowChildren(PrincipalChildList().FirstChild()); + return rv; + } + + bool parentWillFireStretch = false; + if (!placeOrigin) { + // This means the rect.x and rect.y of our children were not set!! + // Don't go without checking to see if our parent will later fire a Stretch() command + // targeted at us. The Stretch() will cause the rect.x and rect.y to clear... + nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetParent()); + if (mathMLFrame) { + nsEmbellishData embellishData; + nsPresentationData presentationData; + mathMLFrame->GetEmbellishData(embellishData); + mathMLFrame->GetPresentationData(presentationData); + if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(presentationData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(presentationData.flags) || + (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags) + && presentationData.baseFrame == this)) + { + parentWillFireStretch = true; + } + } + if (!parentWillFireStretch) { + // There is nobody who will fire the stretch for us, we do it ourselves! + + bool stretchAll = + /* NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) || */ + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags); + + nsStretchDirection stretchDir; + if (mEmbellishData.coreFrame == this || /* case of a bare <mo>...</mo> itself */ + (mEmbellishData.direction == NS_STRETCH_DIRECTION_HORIZONTAL && + stretchAll) || /* or <mover><mo>...</mo>...</mover>, or friends */ + mEmbellishData.direction == NS_STRETCH_DIRECTION_UNSUPPORTED) { /* Doesn't stretch */ + stretchDir = mEmbellishData.direction; + } else { + // Let the Stretch() call decide the direction. + stretchDir = NS_STRETCH_DIRECTION_DEFAULT; + } + // Use our current size as computed earlier by Place() + // The stretch call will detect if this is incorrect and recalculate the size. + nsBoundingMetrics defaultSize = aDesiredSize.mBoundingMetrics; + + Stretch(aDrawTarget, stretchDir, defaultSize, aDesiredSize); +#ifdef DEBUG + { + // The Place() call above didn't request FinishReflowChild(), + // so let's check that we eventually did through Stretch(). + for (nsIFrame* childFrame : PrincipalChildList()) { + NS_ASSERTION(!(childFrame->GetStateBits() & NS_FRAME_IN_REFLOW), + "DidReflow() was never called"); + } + } +#endif + } + } + + // Also return our bounding metrics + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + + // see if we should fix the spacing + FixInterFrameSpacing(aDesiredSize); + + if (!parentWillFireStretch) { + // Not expecting a stretch. + // Finished with these: + ClearSavedChildMetrics(); + // Set our overflow area. + GatherAndStoreOverflow(&aDesiredSize); + } + + return NS_OK; +} + + +/* ///////////// + * nsIMathMLFrame - support methods for scripting elements (nested frames + * within msub, msup, msubsup, munder, mover, munderover, mmultiscripts, + * mfrac, mroot, mtable). + * ============================================================================= + */ + +// helper to let the update of presentation data pass through +// a subtree that may contain non-mathml container frames +/* static */ void +nsMathMLContainerFrame::PropagatePresentationDataFor(nsIFrame* aFrame, + uint32_t aFlagsValues, + uint32_t aFlagsToUpdate) +{ + if (!aFrame || !aFlagsToUpdate) + return; + nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame); + if (mathMLFrame) { + // update + mathMLFrame->UpdatePresentationData(aFlagsValues, + aFlagsToUpdate); + // propagate using the base method to make sure that the control + // is passed on to MathML frames that may be overloading the method + mathMLFrame->UpdatePresentationDataFromChildAt(0, -1, + aFlagsValues, aFlagsToUpdate); + } + else { + // propagate down the subtrees + for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { + PropagatePresentationDataFor(childFrame, + aFlagsValues, aFlagsToUpdate); + } + } +} + +/* static */ void +nsMathMLContainerFrame::PropagatePresentationDataFromChildAt(nsIFrame* aParentFrame, + int32_t aFirstChildIndex, + int32_t aLastChildIndex, + uint32_t aFlagsValues, + uint32_t aFlagsToUpdate) +{ + if (!aParentFrame || !aFlagsToUpdate) + return; + int32_t index = 0; + for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) { + if ((index >= aFirstChildIndex) && + ((aLastChildIndex <= 0) || ((aLastChildIndex > 0) && + (index <= aLastChildIndex)))) { + PropagatePresentationDataFor(childFrame, + aFlagsValues, aFlagsToUpdate); + } + index++; + } +} + +/* ////////////////// + * Frame construction + * ============================================================================= + */ + + +void +nsMathMLContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // report an error if something wrong was found in this frame + if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) { + if (!IsVisibleForPainting(aBuilder)) + return; + + aLists.Content()->AppendNewToTop( + new (aBuilder) nsDisplayMathMLError(aBuilder, this)); + return; + } + + DisplayBorderBackgroundOutline(aBuilder, aLists); + + BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists, + DISPLAY_CHILD_INLINE); + +#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) + // for visual debug + // ---------------- + // if you want to see your bounding box, make sure to properly fill + // your mBoundingMetrics and mReference point, and set + // mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS + // in the Init() of your sub-class + DisplayBoundingMetrics(aBuilder, this, mReference, mBoundingMetrics, aLists); +#endif +} + +// Note that this method re-builds the automatic data in the children -- not +// in aParentFrame itself (except for those particular operations that the +// parent frame may do in its TransmitAutomaticData()). +/* static */ void +nsMathMLContainerFrame::RebuildAutomaticDataForChildren(nsIFrame* aParentFrame) +{ + // 1. As we descend the tree, make each child frame inherit data from + // the parent + // 2. As we ascend the tree, transmit any specific change that we want + // down the subtrees + for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) { + nsIMathMLFrame* childMathMLFrame = do_QueryFrame(childFrame); + if (childMathMLFrame) { + childMathMLFrame->InheritAutomaticData(aParentFrame); + } + RebuildAutomaticDataForChildren(childFrame); + } + nsIMathMLFrame* mathMLFrame = do_QueryFrame(aParentFrame); + if (mathMLFrame) { + mathMLFrame->TransmitAutomaticData(); + } +} + +/* static */ nsresult +nsMathMLContainerFrame::ReLayoutChildren(nsIFrame* aParentFrame) +{ + if (!aParentFrame) + return NS_OK; + + // walk-up to the first frame that is a MathML frame, stop if we reach <math> + nsIFrame* frame = aParentFrame; + while (1) { + nsIFrame* parent = frame->GetParent(); + if (!parent || !parent->GetContent()) + break; + + // stop if it is a MathML frame + nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame); + if (mathMLFrame) + break; + + // stop if we reach the root <math> tag + nsIContent* content = frame->GetContent(); + NS_ASSERTION(content, "dangling frame without a content node"); + if (!content) + break; + if (content->IsMathMLElement(nsGkAtoms::math)) + break; + + frame = parent; + } + + // re-sync the presentation data and embellishment data of our children + RebuildAutomaticDataForChildren(frame); + + // Ask our parent frame to reflow us + nsIFrame* parent = frame->GetParent(); + NS_ASSERTION(parent, "No parent to pass the reflow request up to"); + if (!parent) + return NS_OK; + + frame->PresContext()->PresShell()-> + FrameNeedsReflow(frame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + + return NS_OK; +} + +// There are precise rules governing children of a MathML frame, +// and properties such as the scriptlevel depends on those rules. +// Hence for things to work, callers must use Append/Insert/etc wisely. + +nsresult +nsMathMLContainerFrame::ChildListChanged(int32_t aModType) +{ + // If this is an embellished frame we need to rebuild the + // embellished hierarchy by walking-up to the parent of the + // outermost embellished container. + nsIFrame* frame = this; + if (mEmbellishData.coreFrame) { + nsIFrame* parent = GetParent(); + nsEmbellishData embellishData; + for ( ; parent; frame = parent, parent = parent->GetParent()) { + GetEmbellishDataFrom(parent, embellishData); + if (embellishData.coreFrame != mEmbellishData.coreFrame) + break; + } + } + return ReLayoutChildren(frame); +} + +void +nsMathMLContainerFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + MOZ_ASSERT(aListID == kPrincipalList); + mFrames.AppendFrames(this, aFrameList); + ChildListChanged(nsIDOMMutationEvent::ADDITION); +} + +void +nsMathMLContainerFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + MOZ_ASSERT(aListID == kPrincipalList); + mFrames.InsertFrames(this, aPrevFrame, aFrameList); + ChildListChanged(nsIDOMMutationEvent::ADDITION); +} + +void +nsMathMLContainerFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + MOZ_ASSERT(aListID == kPrincipalList); + mFrames.DestroyFrame(aOldFrame); + ChildListChanged(nsIDOMMutationEvent::REMOVAL); +} + +nsresult +nsMathMLContainerFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + // XXX Since they are numerous MathML attributes that affect layout, and + // we can't check all of them here, play safe by requesting a reflow. + // XXXldb This should only do work for attributes that cause changes! + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + + return NS_OK; +} + +void +nsMathMLContainerFrame::GatherAndStoreOverflow(ReflowOutput* aMetrics) +{ + mBlockStartAscent = aMetrics->BlockStartAscent(); + + // nsIFrame::FinishAndStoreOverflow likes the overflow area to include the + // frame rectangle. + aMetrics->SetOverflowAreasToDesiredBounds(); + + ComputeCustomOverflow(aMetrics->mOverflowAreas); + + // mBoundingMetrics does not necessarily include content of <mpadded> + // elements whose mBoundingMetrics may not be representative of the true + // bounds, and doesn't include the CSS2 outline rectangles of children, so + // make such to include child overflow areas. + UnionChildOverflow(aMetrics->mOverflowAreas); + + FinishAndStoreOverflow(aMetrics); +} + +bool +nsMathMLContainerFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) +{ + // All non-child-frame content such as nsMathMLChars (and most child-frame + // content) is included in mBoundingMetrics. + nsRect boundingBox(mBoundingMetrics.leftBearing, + mBlockStartAscent - mBoundingMetrics.ascent, + mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing, + mBoundingMetrics.ascent + mBoundingMetrics.descent); + + // REVIEW: Maybe this should contribute only to visual overflow + // and not scrollable? + aOverflowAreas.UnionAllWith(boundingBox); + return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); +} + +void +nsMathMLContainerFrame::ReflowChild(nsIFrame* aChildFrame, + nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + // Having foreign/hybrid children, e.g., from html markups, is not defined by + // the MathML spec. But it can happen in practice, e.g., <html:img> allows us + // to do some cool demos... or we may have a child that is an nsInlineFrame + // from a generated content such as :before { content: open-quote } or + // :after { content: close-quote }. Unfortunately, the other frames out-there + // may expect their own invariants that are not met when we mix things. + // Hence we do not claim their support, but we will nevertheless attempt to keep + // them in the flow, if we can get their desired size. We observed that most + // frames may be reflowed generically, but nsInlineFrames need extra care. + +#ifdef DEBUG + nsInlineFrame* inlineFrame = do_QueryFrame(aChildFrame); + NS_ASSERTION(!inlineFrame, "Inline frames should be wrapped in blocks"); +#endif + + nsContainerFrame:: + ReflowChild(aChildFrame, aPresContext, aDesiredSize, aReflowInput, + 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus); + + if (aDesiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) { + // This will be suitable for inline frames, which are wrapped in a block. + nscoord ascent; + WritingMode wm = aDesiredSize.GetWritingMode(); + if (!nsLayoutUtils::GetLastLineBaseline(wm, aChildFrame, &ascent)) { + // We don't expect any other block children so just place the frame on + // the baseline instead of going through DidReflow() and + // GetBaseline(). This is what nsFrame::GetBaseline() will do anyway. + aDesiredSize.SetBlockStartAscent(aDesiredSize.BSize(wm)); + } else { + aDesiredSize.SetBlockStartAscent(ascent); + } + } + if (IsForeignChild(aChildFrame)) { + // use ComputeTightBounds API as aDesiredSize.mBoundingMetrics is not set. + nsRect r = aChildFrame->ComputeTightBounds(aReflowInput.mRenderingContext->GetDrawTarget()); + aDesiredSize.mBoundingMetrics.leftBearing = r.x; + aDesiredSize.mBoundingMetrics.rightBearing = r.XMost(); + aDesiredSize.mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent() - r.y; + aDesiredSize.mBoundingMetrics.descent = r.YMost() - aDesiredSize.BlockStartAscent(); + aDesiredSize.mBoundingMetrics.width = aDesiredSize.Width(); + } +} + +void +nsMathMLContainerFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + mPresentationData.flags &= ~NS_MATHML_ERROR; + aDesiredSize.Width() = aDesiredSize.Height() = 0; + aDesiredSize.SetBlockStartAscent(0); + aDesiredSize.mBoundingMetrics = nsBoundingMetrics(); + + ///////////// + // Reflow children + // Asking each child to cache its bounding metrics + + nsReflowStatus childStatus; + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + ReflowOutput childDesiredSize(aReflowInput, // ??? + aDesiredSize.mFlags); + WritingMode wm = childFrame->GetWritingMode(); + LogicalSize availSize = aReflowInput.ComputedSize(wm); + availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput childReflowInput(aPresContext, aReflowInput, + childFrame, availSize); + ReflowChild(childFrame, aPresContext, childDesiredSize, + childReflowInput, childStatus); + //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status"); + SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize, + childDesiredSize.mBoundingMetrics); + childFrame = childFrame->GetNextSibling(); + } + + ///////////// + // If we are a container which is entitled to stretch its children, then we + // ask our stretchy children to stretch themselves + + // The stretching of siblings of an embellished child is _deferred_ until + // after finishing the stretching of the embellished child - bug 117652 + + DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget(); + + if (!NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) && + (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags))) { + + // get the stretchy direction + nsStretchDirection stretchDir = + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) + ? NS_STRETCH_DIRECTION_VERTICAL + : NS_STRETCH_DIRECTION_HORIZONTAL; + + // what size should we use to stretch our stretchy children + // We don't use STRETCH_CONSIDER_ACTUAL_SIZE -- because our size is not known yet + // We don't use STRETCH_CONSIDER_EMBELLISHMENTS -- because we don't want to + // include them in the caculations of the size of stretchy elements + nsBoundingMetrics containerSize; + GetPreferredStretchSize(drawTarget, 0, stretchDir, containerSize); + + // fire the stretch on each child + childFrame = mFrames.FirstChild(); + while (childFrame) { + nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame); + if (mathMLFrame) { + // retrieve the metrics that was stored at the previous pass + ReflowOutput childDesiredSize(aReflowInput); + GetReflowAndBoundingMetricsFor(childFrame, + childDesiredSize, childDesiredSize.mBoundingMetrics); + + mathMLFrame->Stretch(drawTarget, stretchDir, + containerSize, childDesiredSize); + // store the updated metrics + SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize, + childDesiredSize.mBoundingMetrics); + } + childFrame = childFrame->GetNextSibling(); + } + } + + ///////////// + // Place children now by re-adjusting the origins to align the baselines + FinalizeReflow(drawTarget, aDesiredSize); + + aStatus = NS_FRAME_COMPLETE; + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +static nscoord AddInterFrameSpacingToSize(ReflowOutput& aDesiredSize, + nsMathMLContainerFrame* aFrame); + +/* virtual */ void +nsMathMLContainerFrame::MarkIntrinsicISizesDirty() +{ + mIntrinsicWidth = NS_INTRINSIC_WIDTH_UNKNOWN; + nsContainerFrame::MarkIntrinsicISizesDirty(); +} + +void +nsMathMLContainerFrame::UpdateIntrinsicWidth(nsRenderingContext* aRenderingContext) +{ + if (mIntrinsicWidth == NS_INTRINSIC_WIDTH_UNKNOWN) { + ReflowOutput desiredSize(GetWritingMode()); + GetIntrinsicISizeMetrics(aRenderingContext, desiredSize); + + // Include the additional width added by FixInterFrameSpacing to ensure + // consistent width calculations. + AddInterFrameSpacingToSize(desiredSize, this); + mIntrinsicWidth = desiredSize.ISize(GetWritingMode()); + } +} + +/* virtual */ nscoord +nsMathMLContainerFrame::GetMinISize(nsRenderingContext* aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + UpdateIntrinsicWidth(aRenderingContext); + result = mIntrinsicWidth; + return result; +} + +/* virtual */ nscoord +nsMathMLContainerFrame::GetPrefISize(nsRenderingContext* aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + UpdateIntrinsicWidth(aRenderingContext); + result = mIntrinsicWidth; + return result; +} + +/* virtual */ void +nsMathMLContainerFrame::GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext, + ReflowOutput& aDesiredSize) +{ + // Get child widths + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + ReflowOutput childDesiredSize(GetWritingMode()); // ??? + + nsMathMLContainerFrame* containerFrame = do_QueryFrame(childFrame); + if (containerFrame) { + containerFrame->GetIntrinsicISizeMetrics(aRenderingContext, + childDesiredSize); + } else { + // XXX This includes margin while Reflow currently doesn't consider + // margin, so we may end up with too much space, but, with stretchy + // characters, this is an approximation anyway. + nscoord width = + nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame, + nsLayoutUtils::PREF_ISIZE); + + childDesiredSize.Width() = width; + childDesiredSize.mBoundingMetrics.width = width; + childDesiredSize.mBoundingMetrics.leftBearing = 0; + childDesiredSize.mBoundingMetrics.rightBearing = width; + + nscoord x, xMost; + if (NS_SUCCEEDED(childFrame->GetPrefWidthTightBounds(aRenderingContext, + &x, &xMost))) { + childDesiredSize.mBoundingMetrics.leftBearing = x; + childDesiredSize.mBoundingMetrics.rightBearing = xMost; + } + } + + SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize, + childDesiredSize.mBoundingMetrics); + + childFrame = childFrame->GetNextSibling(); + } + + // Measure + nsresult rv = MeasureForWidth(aRenderingContext->GetDrawTarget(), aDesiredSize); + if (NS_FAILED(rv)) { + ReflowError(aRenderingContext->GetDrawTarget(), aDesiredSize); + } + + ClearSavedChildMetrics(); +} + +/* virtual */ nsresult +nsMathMLContainerFrame::MeasureForWidth(DrawTarget* aDrawTarget, + ReflowOutput& aDesiredSize) +{ + return Place(aDrawTarget, false, aDesiredSize); +} + + +// see spacing table in Chapter 18, TeXBook (p.170) +// Our table isn't quite identical to TeX because operators have +// built-in values for lspace & rspace in the Operator Dictionary. +static int32_t kInterFrameSpacingTable[eMathMLFrameType_COUNT][eMathMLFrameType_COUNT] = +{ + // in units of muspace. + // upper half of the byte is set if the + // spacing is not to be used for scriptlevel > 0 + + /* Ord OpOrd OpInv OpUsr Inner Italic Upright */ + /*Ord */ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00}, + /*OpOrd*/ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + /*OpInv*/ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + /*OpUsr*/ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01}, + /*Inner*/ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01}, + /*Italic*/ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01}, + /*Upright*/ {0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00} +}; + +#define GET_INTERSPACE(scriptlevel_, frametype1_, frametype2_, space_) \ + /* no space if there is a frame that we know nothing about */ \ + if (frametype1_ == eMathMLFrameType_UNKNOWN || \ + frametype2_ == eMathMLFrameType_UNKNOWN) \ + space_ = 0; \ + else { \ + space_ = kInterFrameSpacingTable[frametype1_][frametype2_]; \ + space_ = (scriptlevel_ > 0 && (space_ & 0xF0)) \ + ? 0 /* spacing is disabled */ \ + : space_ & 0x0F; \ + } \ + +// This function computes the inter-space between two frames. However, +// since invisible operators need special treatment, the inter-space may +// be delayed when an invisible operator is encountered. In this case, +// the function will carry the inter-space forward until it is determined +// that it can be applied properly (i.e., until we encounter a visible +// frame where to decide whether to accept or reject the inter-space). +// aFromFrameType: remembers the frame when the carry-forward initiated. +// aCarrySpace: keeps track of the inter-space that is delayed. +// @returns: current inter-space (which is 0 when the true inter-space is +// delayed -- and thus has no effect since the frame is invisible anyway). +static nscoord +GetInterFrameSpacing(int32_t aScriptLevel, + eMathMLFrameType aFirstFrameType, + eMathMLFrameType aSecondFrameType, + eMathMLFrameType* aFromFrameType, // IN/OUT + int32_t* aCarrySpace) // IN/OUT +{ + eMathMLFrameType firstType = aFirstFrameType; + eMathMLFrameType secondType = aSecondFrameType; + + int32_t space; + GET_INTERSPACE(aScriptLevel, firstType, secondType, space); + + // feedback control to avoid the inter-space to be added when not necessary + if (secondType == eMathMLFrameType_OperatorInvisible) { + // see if we should start to carry the space forward until we + // encounter a visible frame + if (*aFromFrameType == eMathMLFrameType_UNKNOWN) { + *aFromFrameType = firstType; + *aCarrySpace = space; + } + // keep carrying *aCarrySpace forward, while returning 0 for this stage + space = 0; + } + else if (*aFromFrameType != eMathMLFrameType_UNKNOWN) { + // no carry-forward anymore, get the real inter-space between + // the two frames of interest + + firstType = *aFromFrameType; + + // But... the invisible operator that we encountered earlier could + // be sitting between italic and upright identifiers, e.g., + // + // 1. <mi>sin</mi> <mo>⁡</mo> <mi>x</mi> + // 2. <mi>x</mi> <mo>&InvisibileTime;</mo> <mi>sin</mi> + // + // the trick to get the inter-space in either situation + // is to promote "<mi>sin</mi><mo>⁡</mo>" and + // "<mo>&InvisibileTime;</mo><mi>sin</mi>" to user-defined operators... + if (firstType == eMathMLFrameType_UprightIdentifier) { + firstType = eMathMLFrameType_OperatorUserDefined; + } + else if (secondType == eMathMLFrameType_UprightIdentifier) { + secondType = eMathMLFrameType_OperatorUserDefined; + } + + GET_INTERSPACE(aScriptLevel, firstType, secondType, space); + + // Now, we have two values: the computed space and the space that + // has been carried forward until now. Which value do we pick? + // If the second type is an operator (e.g., fence), it already has + // built-in lspace & rspace, so we let them win. Otherwise we pick + // the max between the two values that we have. + if (secondType != eMathMLFrameType_OperatorOrdinary && + space < *aCarrySpace) + space = *aCarrySpace; + + // reset everything now that the carry-forward is done + *aFromFrameType = eMathMLFrameType_UNKNOWN; + *aCarrySpace = 0; + } + + return space; +} + +static nscoord GetThinSpace(const nsStyleFont* aStyleFont) +{ + return NSToCoordRound(float(aStyleFont->mFont.size)*float(3) / float(18)); +} + +class nsMathMLContainerFrame::RowChildFrameIterator { +public: + explicit RowChildFrameIterator(nsMathMLContainerFrame* aParentFrame) : + mParentFrame(aParentFrame), + mReflowOutput(aParentFrame->GetWritingMode()), + mX(0), + mCarrySpace(0), + mFromFrameType(eMathMLFrameType_UNKNOWN), + mRTL(aParentFrame->StyleVisibility()->mDirection) + { + if (!mRTL) { + mChildFrame = aParentFrame->mFrames.FirstChild(); + } else { + mChildFrame = aParentFrame->mFrames.LastChild(); + } + + if (!mChildFrame) + return; + + InitMetricsForChild(); + } + + RowChildFrameIterator& operator++() + { + // add child size + italic correction + mX += mReflowOutput.mBoundingMetrics.width + mItalicCorrection; + + if (!mRTL) { + mChildFrame = mChildFrame->GetNextSibling(); + } else { + mChildFrame = mChildFrame->GetPrevSibling(); + } + + if (!mChildFrame) + return *this; + + eMathMLFrameType prevFrameType = mChildFrameType; + InitMetricsForChild(); + + // add inter frame spacing + const nsStyleFont* font = mParentFrame->StyleFont(); + nscoord space = + GetInterFrameSpacing(font->mScriptLevel, + prevFrameType, mChildFrameType, + &mFromFrameType, &mCarrySpace); + mX += space * GetThinSpace(font); + return *this; + } + + nsIFrame* Frame() const { return mChildFrame; } + nscoord X() const { return mX; } + const ReflowOutput& GetReflowOutput() const { return mReflowOutput; } + nscoord Ascent() const { return mReflowOutput.BlockStartAscent(); } + nscoord Descent() const { return mReflowOutput.Height() - mReflowOutput.BlockStartAscent(); } + const nsBoundingMetrics& BoundingMetrics() const { + return mReflowOutput.mBoundingMetrics; + } + +private: + const nsMathMLContainerFrame* mParentFrame; + nsIFrame* mChildFrame; + ReflowOutput mReflowOutput; + nscoord mX; + + nscoord mItalicCorrection; + eMathMLFrameType mChildFrameType; + int32_t mCarrySpace; + eMathMLFrameType mFromFrameType; + + bool mRTL; + + void InitMetricsForChild() + { + GetReflowAndBoundingMetricsFor(mChildFrame, mReflowOutput, mReflowOutput.mBoundingMetrics, + &mChildFrameType); + nscoord leftCorrection, rightCorrection; + GetItalicCorrection(mReflowOutput.mBoundingMetrics, + leftCorrection, rightCorrection); + if (!mChildFrame->GetPrevSibling() && + mParentFrame->GetContent()->IsMathMLElement(nsGkAtoms::msqrt_)) { + // Remove leading correction in <msqrt> because the sqrt glyph itself is + // there first. + if (!mRTL) { + leftCorrection = 0; + } else { + rightCorrection = 0; + } + } + // add left correction -- this fixes the problem of the italic 'f' + // e.g., <mo>q</mo> <mi>f</mi> <mo>I</mo> + mX += leftCorrection; + mItalicCorrection = rightCorrection; + } +}; + +/* virtual */ nsresult +nsMathMLContainerFrame::Place(DrawTarget* aDrawTarget, + bool aPlaceOrigin, + ReflowOutput& aDesiredSize) +{ + // This is needed in case this frame is empty (i.e., no child frames) + mBoundingMetrics = nsBoundingMetrics(); + + RowChildFrameIterator child(this); + nscoord ascent = 0, descent = 0; + while (child.Frame()) { + if (descent < child.Descent()) + descent = child.Descent(); + if (ascent < child.Ascent()) + ascent = child.Ascent(); + // add the child size + mBoundingMetrics.width = child.X(); + mBoundingMetrics += child.BoundingMetrics(); + ++child; + } + // Add the italic correction at the end (including the last child). + // This gives a nice gap between math and non-math frames, and still + // gives the same math inter-spacing in case this frame connects to + // another math frame + mBoundingMetrics.width = child.X(); + + aDesiredSize.Width() = std::max(0, mBoundingMetrics.width); + aDesiredSize.Height() = ascent + descent; + aDesiredSize.SetBlockStartAscent(ascent); + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + + mReference.x = 0; + mReference.y = aDesiredSize.BlockStartAscent(); + + ////////////////// + // Place Children + + if (aPlaceOrigin) { + PositionRowChildFrames(0, aDesiredSize.BlockStartAscent()); + } + + return NS_OK; +} + +void +nsMathMLContainerFrame::PositionRowChildFrames(nscoord aOffsetX, + nscoord aBaseline) +{ + RowChildFrameIterator child(this); + while (child.Frame()) { + nscoord dx = aOffsetX + child.X(); + nscoord dy = aBaseline - child.Ascent(); + FinishReflowChild(child.Frame(), PresContext(), child.GetReflowOutput(), + nullptr, dx, dy, 0); + ++child; + } +} + +class ForceReflow : public nsIReflowCallback { +public: + virtual bool ReflowFinished() override { + return true; + } + virtual void ReflowCallbackCanceled() override {} +}; + +// We only need one of these so we just make it a static global, no need +// to dynamically allocate/destroy it. +static ForceReflow gForceReflow; + +void +nsMathMLContainerFrame::SetIncrementScriptLevel(int32_t aChildIndex, bool aIncrement) +{ + nsIFrame* child = PrincipalChildList().FrameAt(aChildIndex); + if (!child) + return; + nsIContent* content = child->GetContent(); + if (!content->IsMathMLElement()) + return; + nsMathMLElement* element = static_cast<nsMathMLElement*>(content); + + if (element->GetIncrementScriptLevel() == aIncrement) + return; + + // XXXroc this does a ContentStatesChanged, is it safe to call here? If + // not we should do it in a post-reflow callback. + element->SetIncrementScriptLevel(aIncrement, true); + PresContext()->PresShell()->PostReflowCallback(&gForceReflow); +} + +// helpers to fix the inter-spacing when <math> is the only parent +// e.g., it fixes <math> <mi>f</mi> <mo>q</mo> <mi>f</mi> <mo>I</mo> </math> + +static nscoord +GetInterFrameSpacingFor(int32_t aScriptLevel, + nsIFrame* aParentFrame, + nsIFrame* aChildFrame) +{ + nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild(); + if (!childFrame || aChildFrame == childFrame) + return 0; + + int32_t carrySpace = 0; + eMathMLFrameType fromFrameType = eMathMLFrameType_UNKNOWN; + eMathMLFrameType prevFrameType = eMathMLFrameType_UNKNOWN; + eMathMLFrameType childFrameType = nsMathMLFrame::GetMathMLFrameTypeFor(childFrame); + childFrame = childFrame->GetNextSibling(); + while (childFrame) { + prevFrameType = childFrameType; + childFrameType = nsMathMLFrame::GetMathMLFrameTypeFor(childFrame); + nscoord space = GetInterFrameSpacing(aScriptLevel, + prevFrameType, childFrameType, &fromFrameType, &carrySpace); + if (aChildFrame == childFrame) { + // get thinspace + nsStyleContext* parentContext = aParentFrame->StyleContext(); + nscoord thinSpace = GetThinSpace(parentContext->StyleFont()); + // we are done + return space * thinSpace; + } + childFrame = childFrame->GetNextSibling(); + } + + NS_NOTREACHED("child not in the childlist of its parent"); + return 0; +} + +static nscoord +AddInterFrameSpacingToSize(ReflowOutput& aDesiredSize, + nsMathMLContainerFrame* aFrame) +{ + nscoord gap = 0; + nsIFrame* parent = aFrame->GetParent(); + nsIContent* parentContent = parent->GetContent(); + if (MOZ_UNLIKELY(!parentContent)) { + return 0; + } + if (parentContent->IsAnyOfMathMLElements(nsGkAtoms::math, + nsGkAtoms::mtd_)) { + gap = GetInterFrameSpacingFor(aFrame->StyleFont()->mScriptLevel, + parent, aFrame); + // add our own italic correction + nscoord leftCorrection = 0, italicCorrection = 0; + aFrame->GetItalicCorrection(aDesiredSize.mBoundingMetrics, + leftCorrection, italicCorrection); + gap += leftCorrection; + if (gap) { + aDesiredSize.mBoundingMetrics.leftBearing += gap; + aDesiredSize.mBoundingMetrics.rightBearing += gap; + aDesiredSize.mBoundingMetrics.width += gap; + aDesiredSize.Width() += gap; + } + aDesiredSize.mBoundingMetrics.width += italicCorrection; + aDesiredSize.Width() += italicCorrection; + } + return gap; +} + +nscoord +nsMathMLContainerFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize) +{ + nscoord gap = 0; + gap = AddInterFrameSpacingToSize(aDesiredSize, this); + if (gap) { + // Shift our children to account for the correction + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + childFrame->SetPosition(childFrame->GetPosition() + nsPoint(gap, 0)); + childFrame = childFrame->GetNextSibling(); + } + } + return gap; +} + +/* static */ void +nsMathMLContainerFrame::DidReflowChildren(nsIFrame* aFirst, nsIFrame* aStop) + +{ + if (MOZ_UNLIKELY(!aFirst)) + return; + + for (nsIFrame* frame = aFirst; + frame != aStop; + frame = frame->GetNextSibling()) { + NS_ASSERTION(frame, "aStop isn't a sibling"); + if (frame->GetStateBits() & NS_FRAME_IN_REFLOW) { + // finish off principal descendants, too + nsIFrame* grandchild = frame->PrincipalChildList().FirstChild(); + if (grandchild) + DidReflowChildren(grandchild, nullptr); + + frame->DidReflow(frame->PresContext(), nullptr, + nsDidReflowStatus::FINISHED); + } + } +} + +// helper used by mstyle, mphantom, mpadded and mrow in their implementations +// of TransmitAutomaticData(). +nsresult +nsMathMLContainerFrame::TransmitAutomaticDataForMrowLikeElement() +{ + // + // One loop to check both conditions below: + // + // 1) whether all the children of the mrow-like element are space-like. + // + // The REC defines the following elements to be "space-like": + // * an mstyle, mphantom, or mpadded element, all of whose direct + // sub-expressions are space-like; + // * an mrow all of whose direct sub-expressions are space-like. + // + // 2) whether all but one child of the mrow-like element are space-like and + // this non-space-like child is an embellished operator. + // + // The REC defines the following elements to be embellished operators: + // * one of the elements mstyle, mphantom, or mpadded, such that an mrow + // containing the same arguments would be an embellished operator; + // * an mrow whose arguments consist (in any order) of one embellished + // operator and zero or more space-like elements. + // + nsIFrame *childFrame, *baseFrame; + bool embellishedOpFound = false; + nsEmbellishData embellishData; + + for (childFrame = PrincipalChildList().FirstChild(); + childFrame; + childFrame = childFrame->GetNextSibling()) { + nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame); + if (!mathMLFrame) break; + if (!mathMLFrame->IsSpaceLike()) { + if (embellishedOpFound) break; + baseFrame = childFrame; + GetEmbellishDataFrom(baseFrame, embellishData); + if (!NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags)) break; + embellishedOpFound = true; + } + } + + if (!childFrame) { + // we successfully went to the end of the loop. This means that one of + // condition 1) or 2) holds. + if (!embellishedOpFound) { + // the mrow-like element is space-like. + mPresentationData.flags |= NS_MATHML_SPACE_LIKE; + } else { + // the mrow-like element is an embellished operator. + // let the state of the embellished operator found bubble to us. + mPresentationData.baseFrame = baseFrame; + mEmbellishData = embellishData; + } + } + + if (childFrame || !embellishedOpFound) { + // The element is not embellished operator + mPresentationData.baseFrame = nullptr; + mEmbellishData.flags = 0; + mEmbellishData.coreFrame = nullptr; + mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; + mEmbellishData.leadingSpace = 0; + mEmbellishData.trailingSpace = 0; + } + + if (childFrame || embellishedOpFound) { + // The element is not space-like + mPresentationData.flags &= ~NS_MATHML_SPACE_LIKE; + } + + return NS_OK; +} + +/*static*/ void +nsMathMLContainerFrame::PropagateFrameFlagFor(nsIFrame* aFrame, + nsFrameState aFlags) +{ + if (!aFrame || !aFlags) + return; + + aFrame->AddStateBits(aFlags); + for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { + PropagateFrameFlagFor(childFrame, aFlags); + } +} + +nsresult +nsMathMLContainerFrame::ReportErrorToConsole(const char* errorMsgId, + const char16_t** aParams, + uint32_t aParamCount) +{ + return nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, + NS_LITERAL_CSTRING("Layout: MathML"), mContent->OwnerDoc(), + nsContentUtils::eMATHML_PROPERTIES, + errorMsgId, aParams, aParamCount); +} + +nsresult +nsMathMLContainerFrame::ReportParseError(const char16_t* aAttribute, + const char16_t* aValue) +{ + const char16_t* argv[] = + { aValue, aAttribute, mContent->NodeInfo()->NameAtom()->GetUTF16String() }; + return ReportErrorToConsole("AttributeParsingError", argv, 3); +} + +nsresult +nsMathMLContainerFrame::ReportChildCountError() +{ + const char16_t* arg = mContent->NodeInfo()->NameAtom()->GetUTF16String(); + return ReportErrorToConsole("ChildCountIncorrect", &arg, 1); +} + +nsresult +nsMathMLContainerFrame::ReportInvalidChildError(nsIAtom* aChildTag) +{ + const char16_t* argv[] = + { aChildTag->GetUTF16String(), + mContent->NodeInfo()->NameAtom()->GetUTF16String() }; + return ReportErrorToConsole("InvalidChild", argv, 2); +} + +//========================== + +nsContainerFrame* +NS_NewMathMLmathBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsMathMLmathBlockFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame) + +NS_QUERYFRAME_HEAD(nsMathMLmathBlockFrame) + NS_QUERYFRAME_ENTRY(nsMathMLmathBlockFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) + +nsContainerFrame* +NS_NewMathMLmathInlineFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsMathMLmathInlineFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathInlineFrame) + +NS_QUERYFRAME_HEAD(nsMathMLmathInlineFrame) + NS_QUERYFRAME_ENTRY(nsIMathMLFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame) |