gecko-dev/layout/mathml/nsMathMLContainerFrame.cpp

1603 lines
60 KiB
C++

/* -*- 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 "nsINameSpaceManager.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<nsFontMetrics> 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);
NS_DISPLAY_DECL_NAME("MathMLError", TYPE_MATHML_ERROR)
};
void nsDisplayMathMLError::Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx)
{
// Set color and font ...
nsRefPtr<nsFontMetrics> 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<nsHTMLReflowMetrics*>(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<nsHTMLReflowMetrics*>
(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 <msup><mo>...</mo> ... </msup>,
// or <maction>...<mo>...</mo></maction>.
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 &Sum; might appear big in the following situation
// <math xmlns='http://www.w3.org/1998/Math/MathML'>
// <mstyle>
// <msub>
// <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
// <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
// </msub>
// </mstyle>
// </math>
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 <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(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 <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(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 <mo>...</mo> itself */
|| stretchAll) { /* or <mover><mo>...</mo>...</mover>, or friends */
// use our current size as computed earlier by Place()
defaultSize = aDesiredSize.mBoundingMetrics;
}
else { /* case of <msup><mo>...</mo>...</msup> 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
}
}
// see if we should fix the spacing
FixInterFrameSpacing(aDesiredSize);
// Also return our bounding metrics
aDesiredSize.mBoundingMetrics = mBoundingMetrics;
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 <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->GetNameSpaceID() == kNameSpaceID_MathML &&
content->Tag() == nsGkAtoms::math)
break;
// mark the frame dirty, and continue to climb up. It's important that
// we're NOT doing this to the frame we plan to pass to FrameNeedsReflow()
// XXXldb Why do we need to bother with this? Marking ancestor
// dirty (which we do below) should do a superset of the work this
// does.
frame->AddStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
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;
// Important: do not do this to the frame we plan to pass to
// ReLayoutChildren
// XXXldb Why do we need to bother with this? Marking ancestor
// dirty (which we do below) should do a superset of the work this
// does.
frame->AddStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
return ReLayoutChildren(frame);
}
NS_IMETHODIMP
nsMathMLContainerFrame::AppendFrames(ChildListID aListID,
nsFrameList& aFrameList)
{
if (aListID != kPrincipalList) {
return NS_ERROR_INVALID_ARG;
}
mFrames.AppendFrames(this, aFrameList);
return ChildListChanged(nsIDOMMutationEvent::ADDITION);
}
NS_IMETHODIMP
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);
}
NS_IMETHODIMP
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);
}
NS_IMETHODIMP
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 <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.
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., <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
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;
}
NS_IMETHODIMP
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.GetWritingMode(), // ???
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.GetWritingMode());
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;
}
/* virtual */ nscoord
nsMathMLContainerFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
{
nscoord result;
DISPLAY_MIN_WIDTH(this, result);
nsHTMLReflowMetrics desiredSize(GetWritingMode());
GetIntrinsicWidthMetrics(aRenderingContext, desiredSize);
nsBoundingMetrics bm = desiredSize.mBoundingMetrics;
// We include the overflow to compensate for FixInterFrameSpacing.
result = std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing);
return result;
}
/* virtual */ nscoord
nsMathMLContainerFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
{
nscoord result;
DISPLAY_MIN_WIDTH(this, result);
nsHTMLReflowMetrics desiredSize(GetWritingMode());
GetIntrinsicWidthMetrics(aRenderingContext, desiredSize);
nsBoundingMetrics bm = desiredSize.mBoundingMetrics;
// We include the overflow to compensate for FixInterFrameSpacing.
result = std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing);
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. <mi>sin</mi> <mo>&ApplyFunction;</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>&ApplyFunction;</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),
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 <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(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(), nullptr,
child.ReflowMetrics(), dx, dy, 0);
++child;
}
}
class ForceReflow : public nsIReflowCallback {
public:
virtual bool ReflowFinished() {
return true;
}
virtual void ReflowCallbackCanceled() {}
};
// 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<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->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;
}
nscoord
nsMathMLContainerFrame::FixInterFrameSpacing(nsHTMLReflowMetrics& aDesiredSize)
{
nscoord gap = 0;
nsIContent* parentContent = mParent->GetContent();
if (MOZ_UNLIKELY(!parentContent)) {
return 0;
}
nsIAtom *parentTag = parentContent->Tag();
if (parentContent->GetNameSpaceID() == kNameSpaceID_MathML &&
(parentTag == nsGkAtoms::math || parentTag == nsGkAtoms::mtd_)) {
gap = GetInterFrameSpacingFor(StyleFont()->mScriptLevel, mParent, this);
// add our own italic correction
nscoord leftCorrection = 0, italicCorrection = 0;
GetItalicCorrection(mBoundingMetrics, leftCorrection, italicCorrection);
gap += leftCorrection;
// see if we should shift our children to account for the correction
if (gap) {
nsIFrame* childFrame = mFrames.FirstChild();
while (childFrame) {
childFrame->SetPosition(childFrame->GetPosition() + nsPoint(gap, 0));
childFrame = childFrame->GetNextSibling();
}
mBoundingMetrics.leftBearing += gap;
mBoundingMetrics.rightBearing += gap;
mBoundingMetrics.width += gap;
aDesiredSize.Width() += gap;
}
mBoundingMetrics.width += italicCorrection;
aDesiredSize.Width() += italicCorrection;
}
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,
uint64_t 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,
uint32_t aFlags)
{
nsMathMLmathBlockFrame* it = new (aPresShell) nsMathMLmathBlockFrame(aContext);
it->SetFlags(aFlags);
return it;
}
NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame)
nsIFrame*
NS_NewMathMLmathInlineFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
return new (aPresShell) nsMathMLmathInlineFrame(aContext);
}
NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathInlineFrame)