diff -r 000000000000 -r 6474c204b198 layout/mathml/nsMathMLContainerFrame.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/mathml/nsMathMLContainerFrame.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1617 @@ +/* -*- 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 "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsStyleContext.h" +#include "nsNameSpaceManager.h" +#include "nsRenderingContext.h" +#include "nsIDOMMutationEvent.h" +#include "nsGkAtoms.h" +#include "nsAutoPtr.h" +#include "nsDisplayList.h" +#include "nsIReflowCallback.h" +#include "mozilla/Likely.h" +#include "nsIScriptError.h" +#include "nsContentUtils.h" +#include "nsMathMLElement.h" + +using namespace mozilla; + +// +// nsMathMLContainerFrame implementation +// + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLContainerFrame) + +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(nsRenderingContext& aRenderingContext, + nsHTMLReflowMetrics& 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 + nsRefPtr fm; + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); + aRenderingContext.SetFont(fm); + + // bounding metrics + nsAutoString errorMsg; errorMsg.AssignLiteral("invalid-markup"); + mBoundingMetrics = + aRenderingContext.GetBoundingMetrics(errorMsg.get(), errorMsg.Length()); + + // reflow metrics + aDesiredSize.SetTopAscent(fm->MaxAscent()); + nscoord descent = fm->MaxDescent(); + aDesiredSize.Height() = aDesiredSize.TopAscent() + descent; + aDesiredSize.Width() = 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) MOZ_OVERRIDE; + NS_DISPLAY_DECL_NAME("MathMLError", TYPE_MATHML_ERROR) +}; + +void nsDisplayMathMLError::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + // Set color and font ... + nsRefPtr fm; + nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm)); + aCtx->SetFont(fm); + + nsPoint pt = ToReferenceFrame(); + aCtx->SetColor(NS_RGB(255,0,0)); + aCtx->FillRect(nsRect(pt, mFrame->GetSize())); + aCtx->SetColor(NS_RGB(255,255,255)); + + nscoord ascent = aCtx->FontMetrics()->MaxAscent(); + + NS_NAMED_LITERAL_STRING(errorMsg, "invalid-markup"); + aCtx->DrawString(errorMsg.get(), uint32_t(errorMsg.Length()), + pt.x, pt.y+ascent); +} + +/* ///////////// + * 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; +} + +static void +DestroyHTMLReflowMetrics(void *aPropertyValue) +{ + delete static_cast(aPropertyValue); +} + +NS_DECLARE_FRAME_PROPERTY(HTMLReflowMetricsProperty, DestroyHTMLReflowMetrics) + +/* static */ void +nsMathMLContainerFrame::SaveReflowAndBoundingMetricsFor(nsIFrame* aFrame, + const nsHTMLReflowMetrics& aReflowMetrics, + const nsBoundingMetrics& aBoundingMetrics) +{ + nsHTMLReflowMetrics *metrics = new nsHTMLReflowMetrics(aReflowMetrics); + metrics->mBoundingMetrics = aBoundingMetrics; + aFrame->Properties().Set(HTMLReflowMetricsProperty(), metrics); +} + +// helper method to facilitate getting the reflow and bounding metrics +/* static */ void +nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor(nsIFrame* aFrame, + nsHTMLReflowMetrics& aReflowMetrics, + nsBoundingMetrics& aBoundingMetrics, + eMathMLFrameType* aMathMLFrameType) +{ + NS_PRECONDITION(aFrame, "null arg"); + + nsHTMLReflowMetrics *metrics = static_cast + (aFrame->Properties().Get(HTMLReflowMetricsProperty())); + + // IMPORTANT: This function is only meant to be called in Place() methods + // where it is assumed that SaveReflowAndBoundingMetricsFor has recorded the + // information. + NS_ASSERTION(metrics, "Didn't SaveReflowAndBoundingMetricsFor frame!"); + if (metrics) { + aReflowMetrics = *metrics; + aBoundingMetrics = metrics->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, HTMLReflowMetricsProperty()); + 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(nsRenderingContext& aRenderingContext, + 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() + nsHTMLReflowMetrics metrics(GetWritingMode()); // ??? + Place(aRenderingContext, false, metrics); + aPreferredStretchSize = metrics.mBoundingMetrics; + } + else { + // compute a size that doesn't include embellishements + bool stretchAll = + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags); + NS_ASSERTION(NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) || + stretchAll, + "invalid call to GetPreferredStretchSize"); + bool firstTime = true; + nsBoundingMetrics bm, bmChild; + nsIFrame* childFrame = + stretchAll ? GetFirstPrincipalChild() : 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 { + nsHTMLReflowMetrics unused(GetWritingMode()); + GetReflowAndBoundingMetricsFor(childFrame, unused, bmChild); + } + + if (firstTime) { + firstTime = false; + bm = bmChild; + if (!stretchAll) { + // we may get here for cases such as ... ... , + // or ....... + break; + } + } + else { + if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags)) { + // 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 (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags)) { + // 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(nsRenderingContext& aRenderingContext, + nsStretchDirection aStretchDirection, + nsBoundingMetrics& aContainerSize, + nsHTMLReflowMetrics& 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) { + bool stretchAll = + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags); + + // And the trick is that the child's rect.x is still holding the descent, + // and rect.y is still holding the ascent ... + nsHTMLReflowMetrics 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 + // + // + // + // ab + // ab + // + // + // + nsBoundingMetrics containerSize = aContainerSize; + if (aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT && + aStretchDirection != mEmbellishData.direction) { + if (mEmbellishData.direction == NS_STRETCH_DIRECTION_UNSUPPORTED) { + containerSize = childSize.mBoundingMetrics; + } + else { + GetPreferredStretchSize(aRenderingContext, + stretchAll ? STRETCH_CONSIDER_EMBELLISHMENTS : 0, + mEmbellishData.direction, containerSize); + } + } + + // do the stretching... + mathMLFrame->Stretch(aRenderingContext, + mEmbellishData.direction, 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 (stretchAll) { + + nsStretchDirection stretchDir = + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) ? + NS_STRETCH_DIRECTION_VERTICAL : NS_STRETCH_DIRECTION_HORIZONTAL; + + GetPreferredStretchSize(aRenderingContext, 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(aRenderingContext, stretchDir, + containerSize, childSize); + // store the updated metrics + SaveReflowAndBoundingMetricsFor(childFrame, childSize, + childSize.mBoundingMetrics); + } + } + childFrame = childFrame->GetNextSibling(); + } + } + + // re-position all our children + nsresult rv = Place(aRenderingContext, 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(mParent, parentData); + // ensure that we are the embellished child, not just a sibling + // (need to test coreFrame since 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(nsRenderingContext& aRenderingContext, + nsHTMLReflowMetrics& 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 ... ... , the test is convoluted + // because it excludes the particular case of the core ... itself. + // ( 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(aRenderingContext, 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)) { + DidReflowChildren(GetFirstPrincipalChild()); + 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(mParent); + 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); + + nsBoundingMetrics defaultSize; + if (mEmbellishData.coreFrame == this /* case of a bare ... itself */ + || stretchAll) { /* or ......, or friends */ + // use our current size as computed earlier by Place() + defaultSize = aDesiredSize.mBoundingMetrics; + } + else { /* case of ...... or friends */ + // compute a size that doesn't include embellishments + GetPreferredStretchSize(aRenderingContext, 0, mEmbellishData.direction, + defaultSize); + } + Stretch(aRenderingContext, NS_STRETCH_DIRECTION_DEFAULT, defaultSize, + aDesiredSize); +#ifdef DEBUG + { + // The Place() call above didn't request FinishReflowChild(), + // so let's check that we eventually did through Stretch(). + nsIFrame* childFrame = GetFirstPrincipalChild(); + for ( ; childFrame; childFrame = childFrame->GetNextSibling()) { + 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 + nsIFrame* childFrame = aFrame->GetFirstPrincipalChild(); + while (childFrame) { + PropagatePresentationDataFor(childFrame, + aFlagsValues, aFlagsToUpdate); + childFrame = childFrame->GetNextSibling(); + } + } +} + +/* 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; + nsIFrame* childFrame = aParentFrame->GetFirstPrincipalChild(); + while (childFrame) { + if ((index >= aFirstChildIndex) && + ((aLastChildIndex <= 0) || ((aLastChildIndex > 0) && + (index <= aLastChildIndex)))) { + PropagatePresentationDataFor(childFrame, + aFlagsValues, aFlagsToUpdate); + } + index++; + childFrame = childFrame->GetNextSibling(); + } +} + +/* ////////////////// + * 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 + nsIFrame* childFrame = aParentFrame->GetFirstPrincipalChild(); + while (childFrame) { + nsIMathMLFrame* childMathMLFrame = do_QueryFrame(childFrame); + if (childMathMLFrame) { + childMathMLFrame->InheritAutomaticData(aParentFrame); + } + RebuildAutomaticDataForChildren(childFrame); + childFrame = childFrame->GetNextSibling(); + } + 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 + 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 tag + nsIContent* content = frame->GetContent(); + NS_ASSERTION(content, "dangling frame without a content node"); + if (!content) + break; + if (content->GetNameSpaceID() == kNameSpaceID_MathML && + content->Tag() == 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 = mParent; + nsEmbellishData embellishData; + for ( ; parent; frame = parent, parent = parent->GetParent()) { + GetEmbellishDataFrom(parent, embellishData); + if (embellishData.coreFrame != mEmbellishData.coreFrame) + break; + } + } + return ReLayoutChildren(frame); +} + +nsresult +nsMathMLContainerFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + if (aListID != kPrincipalList) { + return NS_ERROR_INVALID_ARG; + } + mFrames.AppendFrames(this, aFrameList); + return ChildListChanged(nsIDOMMutationEvent::ADDITION); +} + +nsresult +nsMathMLContainerFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + if (aListID != kPrincipalList) { + return NS_ERROR_INVALID_ARG; + } + // Insert frames after aPrevFrame + mFrames.InsertFrames(this, aPrevFrame, aFrameList); + return ChildListChanged(nsIDOMMutationEvent::ADDITION); +} + +nsresult +nsMathMLContainerFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + if (aListID != kPrincipalList) { + return NS_ERROR_INVALID_ARG; + } + // remove the child frame + mFrames.DestroyFrame(aOldFrame); + return 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(nsHTMLReflowMetrics* aMetrics) +{ + // nsIFrame::FinishAndStoreOverflow likes the overflow area to include the + // frame rectangle. + aMetrics->SetOverflowAreasToDesiredBounds(); + + // All non-child-frame content such as nsMathMLChars (and most child-frame + // content) is included in mBoundingMetrics. + nsRect boundingBox(mBoundingMetrics.leftBearing, + aMetrics->TopAscent() - mBoundingMetrics.ascent, + mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing, + mBoundingMetrics.ascent + mBoundingMetrics.descent); + + // REVIEW: Maybe this should contribute only to visual overflow + // and not scrollable? + aMetrics->mOverflowAreas.UnionAllWith(boundingBox); + + // mBoundingMetrics does not necessarily include content of + // 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. + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + ConsiderChildOverflow(aMetrics->mOverflowAreas, childFrame); + childFrame = childFrame->GetNextSibling(); + } + + FinishAndStoreOverflow(aMetrics); +} + +bool +nsMathMLContainerFrame::UpdateOverflow() +{ + // Our overflow areas may have changed, so reflow the frame. + PresContext()->PresShell()->FrameNeedsReflow( + this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); + + // As we're reflowing, there's no need to propagate this change. + return false; +} + +nsresult +nsMathMLContainerFrame::ReflowChild(nsIFrame* aChildFrame, + nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + 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., 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 + + nsresult rv = nsContainerFrame:: + ReflowChild(aChildFrame, aPresContext, aDesiredSize, aReflowState, + 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus); + + if (NS_FAILED(rv)) + return rv; + + if (aDesiredSize.TopAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) { + // This will be suitable for inline frames, which are wrapped in a block. + nscoord ascent; + if (!nsLayoutUtils::GetLastLineBaseline(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.SetTopAscent(aDesiredSize.Height()); + } else { + aDesiredSize.SetTopAscent(ascent); + } + } + if (IsForeignChild(aChildFrame)) { + // use ComputeTightBounds API as aDesiredSize.mBoundingMetrics is not set. + nsRect r = aChildFrame->ComputeTightBounds(aReflowState.rendContext->ThebesContext()); + aDesiredSize.mBoundingMetrics.leftBearing = r.x; + aDesiredSize.mBoundingMetrics.rightBearing = r.XMost(); + aDesiredSize.mBoundingMetrics.ascent = aDesiredSize.TopAscent() - r.y; + aDesiredSize.mBoundingMetrics.descent = r.YMost() - aDesiredSize.TopAscent(); + aDesiredSize.mBoundingMetrics.width = aDesiredSize.Width(); + } + return rv; +} + +nsresult +nsMathMLContainerFrame::Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + aDesiredSize.Width() = aDesiredSize.Height() = 0; + aDesiredSize.SetTopAscent(0); + aDesiredSize.mBoundingMetrics = nsBoundingMetrics(); + + ///////////// + // Reflow children + // Asking each child to cache its bounding metrics + + nsReflowStatus childStatus; + nsSize availSize(aReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE); + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + nsHTMLReflowMetrics childDesiredSize(aReflowState, // ??? + aDesiredSize.mFlags); + nsHTMLReflowState childReflowState(aPresContext, aReflowState, + childFrame, availSize); + nsresult rv = ReflowChild(childFrame, aPresContext, childDesiredSize, + childReflowState, childStatus); + //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status"); + if (NS_FAILED(rv)) { + // Call DidReflow() for the child frames we successfully did reflow. + DidReflowChildren(mFrames.FirstChild(), childFrame); + return rv; + } + + 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 + + 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(*aReflowState.rendContext, 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 + nsHTMLReflowMetrics childDesiredSize(aReflowState); + GetReflowAndBoundingMetricsFor(childFrame, + childDesiredSize, childDesiredSize.mBoundingMetrics); + + mathMLFrame->Stretch(*aReflowState.rendContext, 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(*aReflowState.rendContext, aDesiredSize); + + aStatus = NS_FRAME_COMPLETE; + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); + return NS_OK; +} + +static nscoord AddInterFrameSpacingToSize(nsHTMLReflowMetrics& aDesiredSize, + nsMathMLContainerFrame* aFrame); + +/* virtual */ nscoord +nsMathMLContainerFrame::GetMinWidth(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + nsHTMLReflowMetrics desiredSize(GetWritingMode()); + GetIntrinsicWidthMetrics(aRenderingContext, desiredSize); + + // Include the additional width added by FixInterFrameSpacing to ensure + // consistent width calculations. + AddInterFrameSpacingToSize(desiredSize, this); + result = desiredSize.Width(); + return result; +} + +/* virtual */ nscoord +nsMathMLContainerFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + nsHTMLReflowMetrics desiredSize(GetWritingMode()); + GetIntrinsicWidthMetrics(aRenderingContext, desiredSize); + + // Include the additional width added by FixInterFrameSpacing to ensure + // consistent width calculations. + AddInterFrameSpacingToSize(desiredSize, this); + result = desiredSize.Width(); + return result; +} + +/* virtual */ void +nsMathMLContainerFrame::GetIntrinsicWidthMetrics(nsRenderingContext* aRenderingContext, nsHTMLReflowMetrics& aDesiredSize) +{ + // Get child widths + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + nsHTMLReflowMetrics childDesiredSize(GetWritingMode()); // ??? + + nsMathMLContainerFrame* containerFrame = do_QueryFrame(childFrame); + if (containerFrame) { + containerFrame->GetIntrinsicWidthMetrics(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_WIDTH); + + 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, aDesiredSize); + if (NS_FAILED(rv)) { + ReflowError(*aRenderingContext, aDesiredSize); + } + + ClearSavedChildMetrics(); +} + +/* virtual */ nsresult +nsMathMLContainerFrame::MeasureForWidth(nsRenderingContext& aRenderingContext, + nsHTMLReflowMetrics& aDesiredSize) +{ + return Place(aRenderingContext, 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. sin x + // 2. x &InvisibileTime; sin + // + // the trick to get the inter-space in either situation + // is to promote "sin" and + // "&InvisibileTime;sin" 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), + mSize(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 += mSize.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 nsHTMLReflowMetrics& ReflowMetrics() const { return mSize; } + nscoord Ascent() const { return mSize.TopAscent(); } + nscoord Descent() const { return mSize.Height() - mSize.TopAscent(); } + const nsBoundingMetrics& BoundingMetrics() const { + return mSize.mBoundingMetrics; + } + +private: + const nsMathMLContainerFrame* mParentFrame; + nsIFrame* mChildFrame; + nsHTMLReflowMetrics mSize; + nscoord mX; + + nscoord mItalicCorrection; + eMathMLFrameType mChildFrameType; + int32_t mCarrySpace; + eMathMLFrameType mFromFrameType; + + bool mRTL; + + void InitMetricsForChild() + { + GetReflowAndBoundingMetricsFor(mChildFrame, mSize, mSize.mBoundingMetrics, + &mChildFrameType); + nscoord leftCorrection, rightCorrection; + GetItalicCorrection(mSize.mBoundingMetrics, + leftCorrection, rightCorrection); + if (!mChildFrame->GetPrevSibling() && + mParentFrame->GetContent()->Tag() == nsGkAtoms::msqrt_) { + // Remove leading correction in 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., q f I + mX += leftCorrection; + mItalicCorrection = rightCorrection; + } +}; + +/* virtual */ nsresult +nsMathMLContainerFrame::Place(nsRenderingContext& aRenderingContext, + bool aPlaceOrigin, + nsHTMLReflowMetrics& 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.SetTopAscent(ascent); + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + + mReference.x = 0; + mReference.y = aDesiredSize.TopAscent(); + + ////////////////// + // Place Children + + if (aPlaceOrigin) { + PositionRowChildFrames(0, aDesiredSize.TopAscent()); + } + + 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.ReflowMetrics(), + nullptr, dx, dy, 0); + ++child; + } +} + +class ForceReflow : public nsIReflowCallback { +public: + virtual bool ReflowFinished() MOZ_OVERRIDE { + return true; + } + virtual void ReflowCallbackCanceled() MOZ_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->IsMathML()) + return; + nsMathMLElement* element = static_cast(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 is the only parent +// e.g., it fixes f q f I + +static nscoord +GetInterFrameSpacingFor(int32_t aScriptLevel, + nsIFrame* aParentFrame, + nsIFrame* aChildFrame) +{ + nsIFrame* childFrame = aParentFrame->GetFirstPrincipalChild(); + 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(nsHTMLReflowMetrics& aDesiredSize, + nsMathMLContainerFrame* aFrame) +{ + nscoord gap = 0; + nsIFrame* parent = aFrame->GetParent(); + nsIContent* parentContent = parent->GetContent(); + if (MOZ_UNLIKELY(!parentContent)) { + return 0; + } + nsIAtom *parentTag = parentContent->Tag(); + if (parentContent->GetNameSpaceID() == kNameSpaceID_MathML && + (parentTag == nsGkAtoms::math || parentTag == 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(nsHTMLReflowMetrics& 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->GetFirstPrincipalChild(); + 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 = GetFirstPrincipalChild(); + 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); + nsIFrame* childFrame = aFrame->GetFirstPrincipalChild(); + while (childFrame) { + PropagateFrameFlagFor(childFrame, aFlags); + childFrame = childFrame->GetNextSibling(); + } +} + +nsresult +nsMathMLContainerFrame::ReportErrorToConsole(const char* errorMsgId, + const char16_t** aParams, + uint32_t aParamCount) +{ + return nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, + NS_LITERAL_CSTRING("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->Tag()->GetUTF16String() }; + return ReportErrorToConsole("AttributeParsingError", argv, 3); +} + +nsresult +nsMathMLContainerFrame::ReportChildCountError() +{ + const char16_t* arg = mContent->Tag()->GetUTF16String(); + return ReportErrorToConsole("ChildCountIncorrect", &arg, 1); +} + +nsresult +nsMathMLContainerFrame::ReportInvalidChildError(nsIAtom* aChildTag) +{ + const char16_t* argv[] = + { aChildTag->GetUTF16String(), mContent->Tag()->GetUTF16String() }; + return ReportErrorToConsole("InvalidChild", argv, 2); +} + +//========================== + +nsIFrame* +NS_NewMathMLmathBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, + nsFrameState aFlags) +{ + nsMathMLmathBlockFrame* it = new (aPresShell) nsMathMLmathBlockFrame(aContext); + it->SetFlags(aFlags); + return it; +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame) + +NS_QUERYFRAME_HEAD(nsMathMLmathBlockFrame) + NS_QUERYFRAME_ENTRY(nsMathMLmathBlockFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) + +nsIFrame* +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)