diff --git a/layout/mathml/base/src/nsMathMLContainerFrame.cpp b/layout/mathml/base/src/nsMathMLContainerFrame.cpp new file mode 100644 index 000000000000..46cb6b5d7a9b --- /dev/null +++ b/layout/mathml/base/src/nsMathMLContainerFrame.cpp @@ -0,0 +1,2010 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla MathML Project. + * + * The Initial Developer of the Original Code is The University Of + * Queensland. Portions created by The University Of Queensland are + * Copyright (C) 1999 The University Of Queensland. All Rights Reserved. + * + * Contributor(s): + * Roger B. Sidje + * David J. Fiddes + * Pierre Phaneuf + */ + +#include "nsCOMPtr.h" +#include "nsHTMLParts.h" +#include "nsIHTMLContent.h" +#include "nsFrame.h" +#include "nsLineLayout.h" +#include "nsHTMLIIDs.h" +#include "nsIPresContext.h" +#include "nsIPresShell.h" +#include "nsHTMLAtoms.h" +#include "nsUnitConversion.h" +#include "nsIStyleContext.h" +#include "nsStyleConsts.h" +#include "nsINameSpaceManager.h" +#include "nsIRenderingContext.h" +#include "nsIFontMetrics.h" +#include "nsStyleUtil.h" + +#include "nsITextContent.h" + +#include "nsMathMLAtoms.h" +#include "nsMathMLParts.h" +#include "nsMathMLChar.h" +#include "nsMathMLContainerFrame.h" + +// +// nsMathMLContainerFrame implementation +// + +// TODO: Proper management of ignorable whitespace +// and handling of non-math related frames. +// * Should math markups only enclose other math markups? +// Why bother... we can leave them in, as hinted in the newsgroup. +// +// * Space doesn't count. Handling space is more complicated than it seems. +// We have to clean inside and outside: +// a + b +// ^ ^ ^ ^ ^ ^ ^ ^ +// *Outside* currently handled using IsOnlyWhitespace(). +// For whitespace only frames, their rect is set zero. +// +// *Inside* not currently handled... so the user must do a +// What to do?! +// - Add a nsTextFrame::CompressWhitespace() *if* it is MathML content?! +// - via CSS property? Support for "none/compress"? (white-space: normal | pre | nowrap) + + +// nsISupports +// ============================================================================= + +NS_INTERFACE_MAP_BEGIN(nsMathMLContainerFrame) + NS_INTERFACE_MAP_ENTRY(nsIMathMLFrame) +#ifdef NS_DEBUG + NS_INTERFACE_MAP_ENTRY(nsIFrameDebug) +#endif + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMathMLFrame) +NS_INTERFACE_MAP_END_INHERITING(nsHTMLContainerFrame) + +NS_IMETHODIMP_(nsrefcnt) +nsMathMLContainerFrame::AddRef(void) +{ + return NS_OK; +} + +NS_IMETHODIMP_(nsrefcnt) +nsMathMLContainerFrame::Release(void) +{ + return NS_OK; +} + +/* /////////////// + * MathML specific - Whitespace management ... + * WHITESPACE: don't forget that whitespace doesn't count in MathML! + * empty frames shouldn't be created in MathML, oh well that's another story. + * ============================================================================= + */ + +PRBool +nsMathMLContainerFrame::IsOnlyWhitespace(nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "null arg"); + + // quick return if we have encountered this frame before + nsFrameState state; + aFrame->GetFrameState(&state); + if (NS_FRAME_IS_UNFLOWABLE & state) + return PR_TRUE; + + // by empty frame we mean a leaf frame whose text content is empty... + PRBool rv = PR_FALSE; + nsCOMPtr aContent; + aFrame->GetContent(getter_AddRefs(aContent)); + if (!aContent) return PR_TRUE; + PRInt32 numKids; + aContent->ChildCount(numKids); + if (0 == numKids) { + nsCOMPtr tc(do_QueryInterface(aContent)); + if (tc.get()) tc->IsOnlyWhitespace(&rv); + } + if (rv) { + // mark the frame as unflowable + aFrame->SetFrameState(NS_FRAME_IS_UNFLOWABLE | state); + } + return rv; +} + +// helper to get an attribute from the content or the surrounding hierarchy +nsresult +nsMathMLContainerFrame::GetAttribute(nsIContent* aContent, + nsIFrame* aMathMLmstyleFrame, + nsIAtom* aAttributeAtom, + nsString& aValue) +{ + nsresult rv = NS_CONTENT_ATTR_NOT_THERE; + + // see if we can get the attribute from the content + if (aContent) + { + rv = aContent->GetAttribute(kNameSpaceID_None, aAttributeAtom, aValue); + } + + if (NS_CONTENT_ATTR_NOT_THERE == rv) + { + // see if we can get the attribute from the mstyle frame + if (aMathMLmstyleFrame) + { + nsCOMPtr mstyleContent; + aMathMLmstyleFrame->GetContent(getter_AddRefs(mstyleContent)); + + nsIFrame* mstyleParent; + aMathMLmstyleFrame->GetParent(&mstyleParent); + + nsPresentationData mstyleParentData; + mstyleParentData.mstyle = nsnull; + + if (mstyleParent) + { + nsIMathMLFrame* aMathMLFrame = nsnull; + rv = mstyleParent->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_SUCCEEDED(rv) && nsnull != aMathMLFrame) + { + aMathMLFrame->GetPresentationData(mstyleParentData); + } + } + + // recurse all the way up into the hierarchy + rv = GetAttribute(mstyleContent, mstyleParentData.mstyle, aAttributeAtom, aValue); + } + } + return rv; +} + +void +nsMathMLContainerFrame::GetRuleThickness(nsIRenderingContext& aRenderingContext, + nsIFontMetrics* aFontMetrics, + nscoord& aRuleThickness) +{ + // get the bounding metrics of the overbar char, the rendering context + // is assumed to have been set with the font of the current style context + nscoord xHeight; + aFontMetrics->GetXHeight(xHeight); + PRUnichar overBar = 0x00AF; + nsBoundingMetrics bm; + nsresult rv = aRenderingContext.GetBoundingMetrics(&overBar, PRUint32(1), bm); + if (NS_SUCCEEDED(rv)) { + aRuleThickness = bm.ascent + bm.descent; + } + if (NS_FAILED(rv) || aRuleThickness <= 0 || aRuleThickness >= xHeight) { + // fall-back to the other version + GetRuleThickness(aFontMetrics, aRuleThickness); + } + +#if 0 + nscoord oldRuleThickness; + GetRuleThickness(aFontMetrics, oldRuleThickness); + + PRUnichar sqrt = 0xE063; // a sqrt glyph from TeX's CMEX font + rv = aRenderingContext.GetBoundingMetrics(&sqrt, PRUint32(1), bm); + nscoord sqrtrule = bm.ascent; // according to TeX, the ascent should be the rule + + printf("xheight:%4d rule:%4d oldrule:%4d sqrtrule:%4d\n", + xHeight, aRuleThickness, oldRuleThickness, sqrtrule); +#endif +} + +void +nsMathMLContainerFrame::GetAxisHeight(nsIRenderingContext& aRenderingContext, + nsIFontMetrics* aFontMetrics, + nscoord& aAxisHeight) +{ + // get the bounding metrics of the minus sign, the rendering context + // is assumed to have been set with the font of the current style context + nscoord xHeight; + aFontMetrics->GetXHeight(xHeight); + PRUnichar minus = '-'; + nsBoundingMetrics bm; + nsresult rv = aRenderingContext.GetBoundingMetrics(&minus, PRUint32(1), bm); + if (NS_SUCCEEDED(rv)) { + aAxisHeight = bm.ascent - (bm.ascent + bm.descent)/2; + } + if (NS_FAILED(rv) || aAxisHeight <= 0 || aAxisHeight >= xHeight) { + // fall-back to the other version + GetAxisHeight(aFontMetrics, aAxisHeight); + } + +#if 0 + nscoord oldAxis; + GetAxisHeight(aFontMetrics, oldAxis); + + PRUnichar plus = '+'; + rv = aRenderingContext.GetBoundingMetrics(&plus, PRUint32(1), bm); + nscoord plusAxis = bm.ascent - (bm.ascent + bm.descent)/2;; + + printf("xheight:%4d Axis:%4d oldAxis:%4d plusAxis:%4d\n", + xHeight, aAxisHeight, oldAxis, plusAxis); +#endif +} + +// ================ +// Utilities for parsing and retrieving numeric values +// All returned values are in twips. + +/* +The REC says: + An explicit plus sign ('+') is not allowed as part of a numeric value + except when it is specifically listed in the syntax (as a quoted '+' or "+"), + + Units allowed + ID Description + em ems (font-relative unit traditionally used for horizontal lengths) + ex exs (font-relative unit traditionally used for vertical lengths) + px pixels, or pixel size of a "typical computer display" + in inches (1 inch = 2.54 centimeters) + cm centimeters + mm millimeters + pt points (1 point = 1/72 inch) + pc picas (1 pica = 12 points) + % percentage of default value + +Implementation here: + The numeric value is valid only if it is of the form nnn.nnn [h/v-unit] +*/ + +// Adapted from nsCSSScanner.cpp & CSSParser.cpp +PRBool +nsMathMLContainerFrame::ParseNumericValue(nsString& aString, + nsCSSValue& aCSSValue) +{ + aCSSValue.Reset(); + aString.CompressWhitespace(); // aString is not a const in this code... + + PRInt32 stringLength = aString.Length(); + + if (!stringLength) return PR_FALSE; + + nsAutoString number(aString); + number.SetLength(0); + + nsAutoString unit(aString); + unit.SetLength(0); + + // Gather up characters that make up the number + PRBool gotDot = PR_FALSE; + PRUnichar c; + for (PRInt32 i = 0; i < stringLength; i++) { + c = aString[i]; + if (gotDot && c == '.') + return PR_FALSE; // two dots encountered + else if (c == '.') + gotDot = PR_TRUE; + else if (!nsCRT::IsAsciiDigit(c)) { + aString.Right(unit, stringLength - i); + unit.CompressWhitespace(); // some authors leave blanks before the unit + break; + } + number.Append(c); + } + +#if 0 +char s1[50], s2[50], s3[50]; +aString.ToCString(s1, 50); +number.ToCString(s2, 50); +unit.ToCString(s3, 50); +printf("String:%s, Number:%s, Unit:%s\n", s1, s2, s3); +#endif + + // Convert number to floating point + PRInt32 errorCode; + float floatValue = number.ToFloat(&errorCode); + if (NS_FAILED(errorCode)) return PR_FALSE; + + nsCSSUnit cssUnit; + if (0 == unit.Length()) { + cssUnit = eCSSUnit_Number; // no explicit unit, this is a number that will act as a multiplier + } + else if (unit.EqualsWithConversion("%")) { + floatValue = floatValue / 100.0f; + aCSSValue.SetPercentValue(floatValue); + return PR_TRUE; + } + else if (unit.EqualsWithConversion("em")) cssUnit = eCSSUnit_EM; + else if (unit.EqualsWithConversion("ex")) cssUnit = eCSSUnit_XHeight; + else if (unit.EqualsWithConversion("px")) cssUnit = eCSSUnit_Pixel; + else if (unit.EqualsWithConversion("in")) cssUnit = eCSSUnit_Inch; + else if (unit.EqualsWithConversion("cm")) cssUnit = eCSSUnit_Centimeter; + else if (unit.EqualsWithConversion("mm")) cssUnit = eCSSUnit_Millimeter; + else if (unit.EqualsWithConversion("pt")) cssUnit = eCSSUnit_Point; + else if (unit.EqualsWithConversion("pc")) cssUnit = eCSSUnit_Pica; + else // unexpected unit + return PR_FALSE; + + aCSSValue.SetFloatValue(floatValue, cssUnit); + return PR_TRUE; +} + +// Adapted from nsCSSStyleRule.cpp +nscoord +nsMathMLContainerFrame::CalcLength(nsIPresContext* aPresContext, + nsIStyleContext* aStyleContext, + const nsCSSValue& aCSSValue) +{ + NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit"); + + if (aCSSValue.IsFixedLengthUnit()) { + return aCSSValue.GetLengthTwips(); + } + + nsCSSUnit unit = aCSSValue.GetUnit(); + + if (eCSSUnit_Pixel == unit) { + float p2t; + aPresContext->GetScaledPixelsToTwips(&p2t); + return NSFloatPixelsToTwips(aCSSValue.GetFloatValue(), p2t); + } + else if (eCSSUnit_EM == unit) { + nsStyleFont font; + aStyleContext->GetStyle(eStyleStruct_Font, font); + return NSToCoordRound(aCSSValue.GetFloatValue() * (float)font.mFont.size); + } + else if (eCSSUnit_XHeight == unit) { + nscoord xHeight; + nsStyleFont font; + aStyleContext->GetStyle(eStyleStruct_Font, font); + nsCOMPtr fm; + aPresContext->GetMetricsFor(font.mFont, getter_AddRefs(fm)); + fm->GetXHeight(xHeight); + return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight); + } + + return 0; +} + +PRBool +nsMathMLContainerFrame::ParseNamedSpaceValue(nsIFrame* aMathMLmstyleFrame, + nsString& aString, + nsCSSValue& aCSSValue) +{ + aCSSValue.Reset(); + aString.CompressWhitespace(); // aString is not a const in this code... + if (!aString.Length()) return PR_FALSE; + + // See if it is one of the 'namedspace' (ranging 1/18em...7/18em) + PRInt32 i = 0; + nsIAtom* namedspaceAtom; + if (aString.EqualsWithConversion("veryverythinmathspace")) + { + i = 1; + namedspaceAtom = nsMathMLAtoms::veryverythinmathspace_; + } + else if (aString.EqualsWithConversion("verythinmathspace")) + { + i = 2; + namedspaceAtom = nsMathMLAtoms::verythinmathspace_; + } + else if (aString.EqualsWithConversion("thinmathspace")) + { + i = 3; + namedspaceAtom = nsMathMLAtoms::thinmathspace_; + } + else if (aString.EqualsWithConversion("mediummathspace")) + { + i = 4; + namedspaceAtom = nsMathMLAtoms::mediummathspace_; + } + else if (aString.EqualsWithConversion("thickmathspace")) + { + i = 5; + namedspaceAtom = nsMathMLAtoms::thickmathspace_; + } + else if (aString.EqualsWithConversion("verythickmathspace")) + { + i = 6; + namedspaceAtom = nsMathMLAtoms::verythickmathspace_; + } + else if (aString.EqualsWithConversion("veryverythickmathspace")) + { + i = 7; + namedspaceAtom = nsMathMLAtoms::veryverythickmathspace_; + } + + if (0 != i) + { + if (aMathMLmstyleFrame) + { + // see if there is a that has overriden the default value + // GetAttribute() will recurse all the way up into the hierarchy + nsAutoString value; + if (NS_CONTENT_ATTR_HAS_VALUE == + GetAttribute(nsnull, aMathMLmstyleFrame, namedspaceAtom, value)) + { + if (ParseNumericValue(value, aCSSValue) && + aCSSValue.IsLengthUnit()) + { + return PR_TRUE; + } + } + } + + // fall back to the default value + aCSSValue.SetFloatValue(float(i)/float(18), eCSSUnit_EM); + return PR_TRUE; + } + + return PR_FALSE; +} + +// ------------------------- +// error handlers +// by default show the Unicode REPLACEMENT CHARACTER U+FFFD +// when a frame with bad markup can not be rendered +nsresult +nsMathMLContainerFrame::ReflowError(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + nsHTMLReflowMetrics& aDesiredSize) +{ + nsresult rv; + + // clear all other flags and record that there is an error with this frame + mEmbellishData.flags = 0; + mPresentationData.flags = NS_MATHML_ERROR; + + /////////////// + // Set font + nsStyleFont font; + mStyleContext->GetStyle(eStyleStruct_Font, font); + aRenderingContext.SetFont(font.mFont); + + // bounding metrics + nsAutoString errorMsg(PRUnichar(0xFFFD)); + rv = aRenderingContext.GetBoundingMetrics(errorMsg.GetUnicode(), + PRUint32(errorMsg.Length()), + mBoundingMetrics); + if (NS_FAILED(rv)) { + printf("GetBoundingMetrics failed\n"); + aDesiredSize.width = aDesiredSize.height = 0; + aDesiredSize.ascent = aDesiredSize.descent = 0; + return NS_OK; + } + + // reflow metrics + nsCOMPtr fm; + aRenderingContext.GetFontMetrics(*getter_AddRefs(fm)); + fm->GetMaxAscent(aDesiredSize.ascent); + fm->GetMaxDescent(aDesiredSize.descent); + aDesiredSize.height = aDesiredSize.ascent + aDesiredSize.descent; + aDesiredSize.width = mBoundingMetrics.width; + + if (nsnull != aDesiredSize.maxElementSize) { + aDesiredSize.maxElementSize->width = aDesiredSize.width; + aDesiredSize.maxElementSize->height = aDesiredSize.height; + } + // Also return our bounding metrics + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + + return NS_OK; +} + +nsresult +nsMathMLContainerFrame::PaintError(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsFramePaintLayer aWhichLayer) +{ + if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer) + { + NS_ASSERTION(NS_MATHML_HAS_ERROR(mPresentationData.flags), + "There is nothing wrong with this frame!"); + // Set color and font ... + nsStyleFont font; + nsStyleColor color; + mStyleContext->GetStyle(eStyleStruct_Font, font); + mStyleContext->GetStyle(eStyleStruct_Color, color); + aRenderingContext.SetColor(color.mColor); + aRenderingContext.SetFont(font.mFont); + + nsAutoString errorMsg(PRUnichar(0xFFFD)); + aRenderingContext.DrawString(errorMsg.GetUnicode(), + PRUint32(errorMsg.Length()), + mRect.x, mRect.y); + } + return NS_OK; +} + +// ------------------------- + +void +nsMathMLContainerFrame::ReflowEmptyChild(nsIPresContext* aPresContext, + nsIFrame* aFrame) +{ +// nsHTMLReflowMetrics emptySize(nsnull); +// nsHTMLReflowState emptyReflowState(aPresContext, aReflowState, aFrame, nsSize(0,0)); +// nsresult rv = ReflowChild(aFrame, aPresContext, emptySize, emptyReflowState, aStatus); + + // 0-size the frame + aFrame->SetRect(aPresContext, nsRect(0,0,0,0)); + + // 0-size the view, if any + nsIView* view = nsnull; + aFrame->GetView(aPresContext, &view); + if (view) { + nsCOMPtr vm; + view->GetViewManager(*getter_AddRefs(vm)); + vm->ResizeView(view, 0,0); + } +} + +/* ///////////// + * nsIMathMLFrame - support methods for precise positioning + * ============================================================================= + */ + +NS_IMETHODIMP +nsMathMLContainerFrame::GetBoundingMetrics(nsBoundingMetrics& aBoundingMetrics) +{ + aBoundingMetrics = mBoundingMetrics; + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::SetBoundingMetrics(const nsBoundingMetrics& aBoundingMetrics) +{ + mBoundingMetrics = aBoundingMetrics; + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::GetReference(nsPoint& aReference) +{ + aReference = mReference; + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::SetReference(const nsPoint& aReference) +{ + mReference = aReference; + return NS_OK; +} + +// helper method to facilitate getting the reflow and bounding metrics +void +nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor(nsIFrame* aFrame, + nsHTMLReflowMetrics& aReflowMetrics, + nsBoundingMetrics& aBoundingMetrics) +{ + NS_PRECONDITION(aFrame, "null arg"); + + // IMPORTANT: This function is only meant to be called in Place() methods + // where it is assumed that the frame's rect is still acting as place holder + // for the frame's ascent and descent information + + nsRect aRect; + aFrame->GetRect(aRect); + aReflowMetrics.descent = aRect.x; + aReflowMetrics.ascent = aRect.y; + aReflowMetrics.width = aRect.width; + aReflowMetrics.height = aRect.height; + + aBoundingMetrics.Clear(); + nsIMathMLFrame* aMathMLFrame = nsnull; + nsresult rv = aFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_SUCCEEDED(rv) && aMathMLFrame) { + aMathMLFrame->GetBoundingMetrics(aBoundingMetrics); +#if 0 + nsFrame::ListTag(stdout, aFrame); + printf(" subItalicCorrection:%d supItalicCorrection:%d\n", + aBoundingMetrics.subItalicCorrection, aBoundingMetrics.supItalicCorrection); +#endif + } + else { // aFrame is not a MathML frame, just return the reflow metrics + aBoundingMetrics.descent = aReflowMetrics.descent; + aBoundingMetrics.ascent = aReflowMetrics.ascent; + aBoundingMetrics.width = aReflowMetrics.width; +#if 0 + printf("GetBoundingMetrics() failed for: "); /* getchar(); */ + nsFrame::ListTag(stdout, aFrame); + printf("\n"); +#endif + } +} + +/* ///////////// + * nsIMathMLFrame - support methods for stretchy elements + * ============================================================================= + */ + +NS_IMETHODIMP +nsMathMLContainerFrame::Stretch(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + nsStretchDirection aStretchDirection, + nsBoundingMetrics& aContainerSize, + nsHTMLReflowMetrics& aDesiredStretchSize) +{ + nsresult rv = NS_OK; + if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) { + + if (NS_MATHML_STRETCH_WAS_DONE(mEmbellishData.flags)) { + printf("WARNING *** it is wrong to fire stretch more than once on a frame...\n"); +// NS_ASSERTION(PR_FALSE,"Stretch() was fired more than once on a frame!"); + return NS_OK; + } + mEmbellishData.flags |= NS_MATHML_STRETCH_DONE; + + if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) { + printf("WARNING *** it is wrong to fire stretch on a erroneous frame...\n"); + return NS_OK; + } + + // Pass the stretch to the first non-empty child ... + + nsIFrame* childFrame = mEmbellishData.firstChild; + NS_ASSERTION(childFrame, "Something is wrong somewhere"); + + if (childFrame) { + nsIMathMLFrame* aMathMLFrame = nsnull; + rv = childFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + NS_ASSERTION(NS_SUCCEEDED(rv) && aMathMLFrame, "Something is wrong somewhere"); + if (NS_SUCCEEDED(rv) && aMathMLFrame) { + + // And the trick is that the child's rect.x is still holding the descent, + // and rect.y is still holding the ascent ... + nsRect rect; + childFrame->GetRect(rect); + + nsHTMLReflowMetrics childSize(aDesiredStretchSize); + aMathMLFrame->GetBoundingMetrics(childSize.mBoundingMetrics); + childSize.descent = rect.x; + childSize.ascent = rect.y; + childSize.height = rect.height; + childSize.width = rect.width; + + nsBoundingMetrics containerSize = aContainerSize; + + if (aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT && + aStretchDirection != mEmbellishData.direction) { + // change the direction and confine the stretch to us + // XXX tune this + containerSize = mBoundingMetrics; + } + + // do the stretching... + aMathMLFrame->Stretch(aPresContext, aRenderingContext, + mEmbellishData.direction, containerSize, childSize); + + // store the updated metrics + childFrame->SetRect(aPresContext, + nsRect(childSize.descent, childSize.ascent, + childSize.width, childSize.height)); + + // We now have one child that may have changed, re-position all our children + Place(aPresContext, aRenderingContext, PR_TRUE, aDesiredStretchSize); + + // 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. + + if (!IsEmbellishOperator(mParent)) { + + nsStyleFont font; + mStyleContext->GetStyle(eStyleStruct_Font, font); + nscoord em = NSToCoordRound(float(font.mFont.size)); + + nsEmbellishData coreData; + mEmbellishData.core->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + aMathMLFrame->GetEmbellishData(coreData); + + // cache these values + mEmbellishData.leftSpace = coreData.leftSpace; + mEmbellishData.rightSpace = coreData.rightSpace; + + aDesiredStretchSize.width += + NSToCoordRound((coreData.leftSpace + coreData.rightSpace) * em); + +// XXX is this what to do ? + aDesiredStretchSize.mBoundingMetrics.width += + NSToCoordRound((coreData.leftSpace + coreData.rightSpace) * em); + + nscoord dx = nscoord( coreData.leftSpace * em ); + if (0 == dx) return NS_OK; + + aDesiredStretchSize.mBoundingMetrics.leftBearing += dx; + aDesiredStretchSize.mBoundingMetrics.rightBearing += dx; + + nsPoint origin; + childFrame = mFrames.FirstChild(); + while (childFrame) { + childFrame->GetOrigin(origin); + childFrame->MoveTo(aPresContext, origin.x + dx, origin.y); + childFrame->GetNextSibling(&childFrame); + } + } + } + } + } + return NS_OK; +} + +nsresult +nsMathMLContainerFrame::FinalizeReflow(nsIPresContext* aPresContext, + nsIRenderingContext& 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. + PRBool placeOrigin = !NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags); + Place(aPresContext, aRenderingContext, placeOrigin, aDesiredSize); + + 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... + PRBool parentWillFireStretch = PR_FALSE; + nsEmbellishData parentData; + nsIMathMLFrame* aMathMLFrame = nsnull; + nsresult rv = mParent->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_SUCCEEDED(rv) && aMathMLFrame) { + aMathMLFrame->GetEmbellishData(parentData); + if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(parentData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(parentData.flags) || + (NS_MATHML_IS_EMBELLISH_OPERATOR(parentData.flags) + && parentData.firstChild == this)) + { + parentWillFireStretch = PR_TRUE; + } + } + if (!parentWillFireStretch) { + // There is nobody who will fire the stretch for us, we do it ourselves! + + // BEGIN of GETTING THE STRETCH SIZE + // What is the size that we should use to stretch our stretchy children ???? + +// 1) With this code, vertical stretching works. But horizontal stretching +// does not work when the firstChild happens to be the core embellished mo... +// nsRect rect; +// nsIFrame* childFrame = mEmbellishData.firstChild; +// NS_ASSERTION(childFrame, "Something is wrong somewhere"); +// childFrame->GetRect(rect); +// nsStretchMetrics curSize(rect.x, rect.y, rect.width, rect.height); + + +// 2) With this code, horizontal stretching works. But vertical stretching +// is done in some cases where frames could have simply been kept as is. +// nsStretchMetrics curSize(aDesiredSize); + nsBoundingMetrics curSize = mBoundingMetrics; + + +// 3) With this code, we should get appropriate size when it is done !! +// GetDesiredSize(aDirection, aPresContext, aRenderingContext, curSize); + + // XXX It is not clear if a direction should be imposed. + // With the default direction, the MathMLChar will attempt to stretch + // in its preferred direction. + + Stretch(aPresContext, aRenderingContext, NS_STRETCH_DIRECTION_DEFAULT, + curSize, aDesiredSize); + } + } + if (nsnull != aDesiredSize.maxElementSize) { + aDesiredSize.maxElementSize->width = aDesiredSize.width; + aDesiredSize.maxElementSize->height = aDesiredSize.height; + } + // Also return our bounding metrics + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + return NS_OK; +} + +// This is the method used to set the frame as an embellished container. +// It checks if the first (non-empty) child is embellished. Hence, calls +// must be bottom-up. The method must only be called from within frames who are +// entitled to be potential embellished operators as per the MathML REC. +NS_IMETHODIMP +nsMathMLContainerFrame::EmbellishOperator() +{ + // Get the first non-empty child + nsIFrame* firstChild = mFrames.FirstChild(); + while (firstChild) { + if (!IsOnlyWhitespace(firstChild)) break; + firstChild->GetNextSibling(&firstChild); + } + if (firstChild && IsEmbellishOperator(firstChild)) { + // Cache the first child + mEmbellishData.flags |= NS_MATHML_EMBELLISH_OPERATOR; + mEmbellishData.firstChild = firstChild; + // Cache also the inner-most embellished frame at the core of the hierarchy + nsIMathMLFrame* aMathMLFrame = nsnull; + firstChild->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + nsEmbellishData embellishData; + aMathMLFrame->GetEmbellishData(embellishData); + mEmbellishData.core = embellishData.core; + mEmbellishData.direction = embellishData.direction; + } + else { + mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_OPERATOR; + mEmbellishData.firstChild = nsnull; + mEmbellishData.core = nsnull; + mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::GetEmbellishData(nsEmbellishData& aEmbellishData) +{ + aEmbellishData = mEmbellishData; + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::SetEmbellishData(const nsEmbellishData& aEmbellishData) +{ + mEmbellishData = aEmbellishData; + return NS_OK; +} + +PRBool +nsMathMLContainerFrame::IsEmbellishOperator(nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "null arg"); + if (!aFrame) return PR_FALSE; + nsIMathMLFrame* aMathMLFrame = nsnull; + nsresult rv = aFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_FAILED(rv) || !aMathMLFrame) return PR_FALSE; + nsEmbellishData aEmbellishData; + aMathMLFrame->GetEmbellishData(aEmbellishData); + return NS_MATHML_IS_EMBELLISH_OPERATOR(aEmbellishData.flags); +} + +/* ///////////// + * nsIMathMLFrame - support methods for scripting elements (nested frames + * within msub, msup, msubsup, munder, mover, munderover, mmultiscripts, + * mfrac, mroot, mtable). + * ============================================================================= + */ + +NS_IMETHODIMP +nsMathMLContainerFrame::GetPresentationData(nsPresentationData& aPresentationData) +{ + aPresentationData = mPresentationData; + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::SetPresentationData(const nsPresentationData& aPresentationData) +{ + mPresentationData = aPresentationData; + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::UpdatePresentationData(PRInt32 aScriptLevelIncrement, + PRUint32 aFlagsValues, + PRUint32 aFlagsToUpdate) +{ + mPresentationData.scriptLevel += aScriptLevelIncrement; + // update flags that are relevant to this call + if (NS_MATHML_IS_DISPLAYSTYLE(aFlagsToUpdate)) { + // updating the displaystyle flag is allowed + if (NS_MATHML_IS_DISPLAYSTYLE(aFlagsValues)) { + mPresentationData.flags |= NS_MATHML_DISPLAYSTYLE; + } + else { + mPresentationData.flags &= ~NS_MATHML_DISPLAYSTYLE; + } + } + if (NS_MATHML_IS_COMPRESSED(aFlagsToUpdate)) { + // updating the compression flag is allowed + if (NS_MATHML_IS_COMPRESSED(aFlagsValues)) { + // 'compressed' means 'prime' style in App. G, TeXbook + mPresentationData.flags |= NS_MATHML_COMPRESSED; + } + // no else. the flag is sticky. it retains its value once it is set + } + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::UpdatePresentationDataFromChildAt(PRInt32 aFirstIndex, + PRInt32 aLastIndex, + PRInt32 aScriptLevelIncrement, + PRUint32 aFlagsValues, + PRUint32 aFlagsToUpdate) +{ + PRInt32 index = 0; + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + if (!IsOnlyWhitespace(childFrame)) { + if ((index >= aFirstIndex) && + ((aLastIndex <= 0) || ((aLastIndex > 0) && (index <= aLastIndex)))) { + nsIMathMLFrame* mathMLFrame = nsnull; + nsresult rv = childFrame->QueryInterface( + NS_GET_IID(nsIMathMLFrame), (void**)&mathMLFrame); + if (NS_SUCCEEDED(rv) && mathMLFrame) { + // update + mathMLFrame->UpdatePresentationData( + aScriptLevelIncrement, aFlagsValues, aFlagsToUpdate); + // propagate down the subtrees + mathMLFrame->UpdatePresentationDataFromChildAt(0, -1, + aScriptLevelIncrement, aFlagsValues, aFlagsToUpdate); + } + } + index++; + } + childFrame->GetNextSibling(&childFrame); + } + return NS_OK; +} + +PRInt32 +nsMathMLContainerFrame::FindSmallestFontSizeFor(nsIPresContext* aPresContext, + nsIFrame* aFrame) +{ + nsStyleFont aFont; + nsCOMPtr aStyleContext; + aFrame->GetStyleContext(getter_AddRefs(aStyleContext)); + aStyleContext->GetStyle(eStyleStruct_Font, aFont); + PRInt32 fontSize = aFont.mFont.size; + + PRInt32 childSize; + nsIFrame* childFrame; + aFrame->FirstChild(aPresContext, nsnull, &childFrame); + while (nsnull != childFrame) { + if (!IsOnlyWhitespace(childFrame)) { + childSize = FindSmallestFontSizeFor(aPresContext, childFrame); + if (fontSize > childSize) fontSize = childSize; + } + childFrame->GetNextSibling(&childFrame); + } + return fontSize; +} + +// Helper to give a style context suitable for doing the stretching of +// a MathMLChar. Frame classes that use this should ensure that the +// extra leaf style contexts given to the MathMLChars are acessible to +// the Style System via the Get/Set AdditionalStyleContext() APIs. +PRBool +nsMathMLContainerFrame::ResolveMathMLCharStyle(nsIPresContext* aPresContext, + nsIContent* aContent, + nsIStyleContext* aParentStyleContext, + nsMathMLChar* aMathMLChar) +{ + nsAutoString data; + aMathMLChar->GetData(data); + PRBool isStretchy = nsMathMLOperators::IsMutableOperator(data); + nsIAtom* fontAtom = (isStretchy) ? + nsMathMLAtoms::fontstyle_stretchy : + nsMathMLAtoms::fontstyle_anonymous; + nsCOMPtr newStyleContext; + nsresult rv = aPresContext->ResolvePseudoStyleContextFor(aContent, fontAtom, + aParentStyleContext, PR_FALSE, + getter_AddRefs(newStyleContext)); + if (NS_SUCCEEDED(rv) && newStyleContext) + aMathMLChar->SetStyleContext(newStyleContext); + + return isStretchy; +} + +// helper method to alter the style context +// This method is used for switching the font to a subscript/superscript font in +// mfrac, msub, msup, msubsup, munder, mover, munderover, mmultiscripts + +// XXX change the code to ensure that the size decreases in a top-down manner. +// the coide should always insert top-scriptstyle frames, and inner frames that +// cause the size to decrease pass the smallest limit should be removed +nsresult +nsMathMLContainerFrame::InsertScriptLevelStyleContext(nsIPresContext* aPresContext) +{ + nsresult rv = NS_OK; + nsIFrame* nextFrame = mFrames.FirstChild(); + while (nsnull != nextFrame && NS_SUCCEEDED(rv)) { + nsIFrame* childFrame = nextFrame; + rv = nextFrame->GetNextSibling(&nextFrame); + if (!IsOnlyWhitespace(childFrame) && NS_SUCCEEDED(rv)) { + + // see if the child frame implements the nsIMathMLFrame interface + nsIMathMLFrame* aMathMLFrame = nsnull; + rv = childFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (nsnull != aMathMLFrame && NS_SUCCEEDED(rv)) { + + // get the scriptlevel of the child + nsPresentationData childData; + aMathMLFrame->GetPresentationData(childData); + + // Iteration to set a style context for the script level font. + // Wow, here is what is happening: the style system requires that any style context + // *must* be uniquely associated to a frame. So we insert as many frames as needed + // to scale-down (or scale-up) the fontsize. + + PRInt32 gap = childData.scriptLevel - mPresentationData.scriptLevel; + if (0 != gap) { + // We are about to change the font-size... We first see if we + // are in the scope of a that tells us what to do. + // This is one of the most obscure part to implement in the spec... + /* + The REC says: + + Whenever the scriptlevel is changed, either automatically or by being + explicitly incremented, decremented, or set, the current font size is + multiplied by the value of scriptsizemultiplier to the power of the + change in scriptlevel. For example, if scriptlevel is increased by 2, + the font size is multiplied by scriptsizemultiplier twice in succession; + if scriptlevel is explicitly set to 2 when it had been 3, the font size + is divided by scriptsizemultiplier. + + The default value of scriptsizemultiplier is less than one (in fact, it + is approximately the square root of 1/2), resulting in a smaller font size + with increasing scriptlevel. To prevent scripts from becoming unreadably + small, the font size is never allowed to go below the value of + scriptminsize as a result of a change to scriptlevel, though it can be + set to a lower value using the fontsize attribute on or on + token elements. If a change to scriptlevel would cause the font size to + become lower than scriptminsize using the above formula, the font size + is instead set equal to scriptminsize within the subexpression for which + scriptlevel was changed. + + In the syntax for scriptminsize, v-unit represents a unit of vertical + length. The most common unit for specifying font sizes in typesetting + is pt (points). + */ + + // default scriptsizemultiplier = 0.71 + // default scriptminsize = 8pt + + // here we only consider scriptminsize, and use the default + // smaller-font-size algorithm of the style system + PRInt32 scriptminsize = NSIntPointsToTwips(8); + + // see if there is a scriptminsize attribute on a that wraps us + nsAutoString value; + if (NS_CONTENT_ATTR_HAS_VALUE == + GetAttribute(nsnull, mPresentationData.mstyle, + nsMathMLAtoms::scriptminsize_, value)) + { + nsCSSValue cssValue; + if (ParseNumericValue(value, cssValue)) { + nsCSSUnit unit = cssValue.GetUnit(); + if (eCSSUnit_Number == unit) + scriptminsize = nscoord(float(scriptminsize) * cssValue.GetFloatValue()); + else if (eCSSUnit_Percent == unit) + scriptminsize = nscoord(float(scriptminsize) * cssValue.GetPercentValue()); + else if (eCSSUnit_Null != unit) + scriptminsize = CalcLength(aPresContext, mStyleContext, cssValue); + } +#ifdef NS_DEBUG + else { + char str[50]; + value.ToCString(str, 50); + printf("Invalid attribute scriptminsize=%s\n", str); + } +#endif + } + + // get Nav's magic font scaler + PRInt32 scaler; + aPresContext->GetFontScaler(&scaler); + float scaleFactor = nsStyleUtil::GetScalingFactor(scaler); + const nsFont& defaultFont = aPresContext->GetDefaultFontDeprecated(); + + nsCOMPtr childContent; + childFrame->GetContent(getter_AddRefs(childContent)); + + nsCOMPtr shell; + aPresContext->GetShell(getter_AddRefs(shell)); + + nsIFrame* firstFrame = nsnull; + nsIFrame* lastFrame = this; + nsIStyleContext* lastStyleContext = mStyleContext; + nsCOMPtr newStyleContext; + + // XXX seems not to decrease when the initail font-size is large (100pt) + nsIAtom* fontAtom = (0 < gap) ? + nsMathMLAtoms::fontsize_smaller : + nsMathMLAtoms::fontsize_larger; + + PRBool isSmaller = PR_TRUE; + if (0 > gap) { isSmaller = PR_FALSE; gap = -gap; } // absolute value + + PRInt32 smallestFontIndex, smallestFontSize = 0; + if (isSmaller) { + // find the smallest font-size in this subtree + smallestFontSize = FindSmallestFontSizeFor(aPresContext, childFrame); + } + + while (0 < gap--) { + + if (isSmaller) { + // look ahead for the next smallest font size that will be in the subtree + smallestFontIndex = nsStyleUtil::FindNextSmallerFontSize(smallestFontSize, (PRInt32)defaultFont.size, scaleFactor, aPresContext); + smallestFontSize = nsStyleUtil::CalcFontPointSize(smallestFontIndex, (PRInt32)defaultFont.size, scaleFactor, aPresContext); +//((nsFrame*)childFrame)->ListTag(stdout); +//printf(" About to move to fontsize:%dpt(%dtwips)\n", +//NSTwipsToFloorIntPoints(smallestFontSize), smallestFontSize); + if (smallestFontSize < scriptminsize) { + // don't bother doing any work +//printf("..... stopping ......\n"); +// XXX there should be a mechanism so that we never try this subtree again + break; + } + } + + aPresContext->ResolvePseudoStyleContextFor(childContent, fontAtom, lastStyleContext, + PR_FALSE, getter_AddRefs(newStyleContext)); + if (newStyleContext && newStyleContext.get() != lastStyleContext) { + // create a new wrapper frame and append it as sole child of the last created frame + nsIFrame* newFrame = nsnull; + NS_NewMathMLWrapperFrame(shell, &newFrame); + NS_ASSERTION(newFrame, "Failed to create new frame"); + if (!newFrame) break; + newFrame->Init(aPresContext, childContent, lastFrame, newStyleContext, nsnull); + + if (nsnull == firstFrame) { + firstFrame = newFrame; + } + if (this != lastFrame) { + lastFrame->SetInitialChildList(aPresContext, nsnull, newFrame); + } + lastStyleContext = newStyleContext; + lastFrame = newFrame; + } + else { + break; + } + } + if (nsnull != firstFrame) { // at least one new frame was created + mFrames.ReplaceFrame(this, childFrame, firstFrame); + childFrame->SetParent(lastFrame); + childFrame->SetNextSibling(nsnull); + aPresContext->ReParentStyleContext(childFrame, lastStyleContext); + lastFrame->SetInitialChildList(aPresContext, nsnull, childFrame); + + // if the child was an embellished operator, + // make the whole list embellished as well + nsEmbellishData embellishData; + aMathMLFrame->GetEmbellishData(embellishData); + if (0 != embellishData.flags && nsnull != embellishData.firstChild) { + do { // walk the hierarchy in a bottom-up manner + rv = lastFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + NS_ASSERTION(NS_SUCCEEDED(rv) && aMathMLFrame, "Mystery!"); + if (NS_FAILED(rv) || !aMathMLFrame) break; + embellishData.firstChild = childFrame; + aMathMLFrame->SetEmbellishData(embellishData); + childFrame = lastFrame; + lastFrame->GetParent(&lastFrame); + } while (lastFrame != this); + } + } + } + } + } + } + return rv; +} + + +/* ////////////////// + * Frame construction + * ============================================================================= + */ + +#if defined(NS_DEBUG) && defined(SHOW_BOUNDING_BOX) +NS_IMETHODIMP +nsMathMLContainerFrame::Paint(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsFramePaintLayer aWhichLayer) +{ + nsresult rv = NS_OK; + + // report an error if something wrong was found in this frame + if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) { + return PaintError(aPresContext, aRenderingContext, + aDirtyRect, aWhichLayer); + } + + rv = nsHTMLContainerFrame::Paint(aPresContext, aRenderingContext, + aDirtyRect, aWhichLayer); + + // 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 + + if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer && + NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags)) + { + aRenderingContext.SetColor(NS_RGB(0,0,255)); + + nscoord x = mReference.x + mBoundingMetrics.leftBearing; + nscoord y = mReference.y - mBoundingMetrics.ascent; + nscoord w = mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; + nscoord h = mBoundingMetrics.ascent + mBoundingMetrics.descent; + + aRenderingContext.DrawRect(x,y,w,h); + } + return rv; +} +#endif + +NS_IMETHODIMP +nsMathMLContainerFrame::Init(nsIPresContext* aPresContext, + nsIContent* aContent, + nsIFrame* aParent, + nsIStyleContext* aContext, + nsIFrame* aPrevInFlow) +{ + nsresult rv; + // first, let the base class do its Init() + rv = nsHTMLContainerFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); + + mBoundingMetrics.Clear(); + mReference.x = mReference.y = 0; + + // now, if our parent implements the nsIMathMLFrame interface, we inherit + // its scriptlevel and displaystyle. If the parent later wishes to increment + // with other values, it will do so in its SetInitialChildList() method. + + mPresentationData.flags = 0; + mPresentationData.scriptLevel = 0; + mPresentationData.mstyle = nsnull; + + mEmbellishData.flags = 0; + mEmbellishData.firstChild = nsnull; + + nsIMathMLFrame* aMathMLFrame = nsnull; + nsresult res = aParent->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_SUCCEEDED(res) && nsnull != aMathMLFrame) { + nsPresentationData parentData; + aMathMLFrame->GetPresentationData(parentData); + + mPresentationData.mstyle = parentData.mstyle; + mPresentationData.scriptLevel = parentData.scriptLevel; + if (NS_MATHML_IS_DISPLAYSTYLE(parentData.flags)) + mPresentationData.flags |= NS_MATHML_DISPLAYSTYLE; + else + mPresentationData.flags &= ~NS_MATHML_DISPLAYSTYLE; + } + else { + // see if our parent has 'display: block' + // XXX should we restrict this to the top level parent ? + const nsStyleDisplay* display; + aParent->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); + if (display->mDisplay == NS_STYLE_DISPLAY_BLOCK) { + mPresentationData.flags |= NS_MATHML_DISPLAYSTYLE; + } + } + + return rv; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::SetInitialChildList(nsIPresContext* aPresContext, + nsIAtom* aListName, + nsIFrame* aChildList) +{ + // First, let the base class do its job + nsresult rv; + rv = nsHTMLContainerFrame::SetInitialChildList(aPresContext, aListName, aChildList); + + // Next, since we are an inline frame, and since we are a container, we have to + // be very careful with the way we treat our children. Things look okay when + // all of our children are only MathML frames. But there are problems if one of + // our children happens to be an nsInlineFrame, e.g., from generated content such + // as :before { content: open-quote } or :after { content: close-quote } + // The code asserts during reflow (in nsLineLayout::BeginSpan) + // Also there are problems when our children are hybrid, e.g., from html markups. + // In short, the nsInlineFrame class expects a number of *invariants* that are not + // met when we mix things. + + // So what we do here is to wrap children that happen to be nsInlineFrames in + // anonymous block frames. + // XXX Question: Do we have to handle Insert/Remove/Append on behalf of + // these anonymous blocks? + // Note: By construction, our anonymous blocks have only one child. + + nsIFrame* next = mFrames.FirstChild(); + while (next) { + nsIFrame* child = next; + next->GetNextSibling(&next); + if (!IsOnlyWhitespace(child)) { + nsInlineFrame* inlineFrame = nsnull; + nsresult res = child->QueryInterface(nsInlineFrame::kInlineFrameCID, (void**)&inlineFrame); + if (NS_SUCCEEDED(res) && inlineFrame) { + // create a new anonymous block frame to wrap this child... + nsCOMPtr shell; + aPresContext->GetShell(getter_AddRefs(shell)); + nsIFrame* anonymous; + rv = NS_NewBlockFrame(shell, &anonymous); + if (NS_FAILED(rv)) + return rv; + nsCOMPtr newStyleContext; + aPresContext->ResolvePseudoStyleContextFor(mContent, nsHTMLAtoms::mozAnonymousBlock, + mStyleContext, PR_FALSE, + getter_AddRefs(newStyleContext)); + rv = anonymous->Init(aPresContext, mContent, this, newStyleContext, nsnull); + if (NS_FAILED(rv)) { + anonymous->Destroy(aPresContext); + return rv; + } + mFrames.ReplaceFrame(this, child, anonymous); + child->SetParent(anonymous); + child->SetNextSibling(nsnull); + aPresContext->ReParentStyleContext(child, newStyleContext); + anonymous->SetInitialChildList(aPresContext, nsnull, child); + } + } + } + + return rv; +} + +// helper function to reflow token elements +// note that mBoundingMetrics is computed here +nsresult +nsMathMLContainerFrame::ReflowTokenFor(nsIFrame* aFrame, + nsIPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + NS_PRECONDITION(aFrame, "null arg"); + nsresult rv = NS_OK; + + // See if this is an incremental reflow + if (aReflowState.reason == eReflowReason_Incremental) { + nsIFrame* targetFrame; + aReflowState.reflowCommand->GetTarget(targetFrame); +#ifdef MATHML_NOISY_INCREMENTAL_REFLOW +printf("nsMathMLContainerFrame::ReflowTokenFor:IncrementalReflow received by: "); +nsFrame::ListTag(stdout, aFrame); +printf("for target: "); +nsFrame::ListTag(stdout, targetFrame); +printf("\n"); +#endif + if (aFrame == targetFrame) { + } + else { + // Remove the next frame from the reflow path + nsIFrame* nextFrame; + aReflowState.reflowCommand->GetNext(nextFrame); + } + } + + // initializations needed for empty markup like + aDesiredSize.width = aDesiredSize.height = 0; + aDesiredSize.ascent = aDesiredSize.descent = 0; + aDesiredSize.mBoundingMetrics.Clear(); + + // ask our children to compute their bounding metrics + nsHTMLReflowMetrics childDesiredSize(aDesiredSize.maxElementSize, + aDesiredSize.mFlags | NS_REFLOW_CALC_BOUNDING_METRICS); + nsSize availSize(aReflowState.mComputedWidth, aReflowState.mComputedHeight); + PRInt32 count = 0; + nsIFrame* childFrame; + aFrame->FirstChild(aPresContext, nsnull, &childFrame); + while (childFrame) { + if (IsOnlyWhitespace(childFrame)) { + ReflowEmptyChild(aPresContext, childFrame); + } + else { + nsHTMLReflowState childReflowState(aPresContext, aReflowState, + childFrame, availSize); + rv = NS_STATIC_CAST(nsMathMLContainerFrame*, + aFrame)->ReflowChild(childFrame, + aPresContext, childDesiredSize, + childReflowState, aStatus); + //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status"); + if (NS_FAILED(rv)) return rv; + + // origins are used as placeholders to store the child's ascent and descent. + childFrame->SetRect(aPresContext, + nsRect(childDesiredSize.descent, childDesiredSize.ascent, + childDesiredSize.width, childDesiredSize.height)); + // compute and cache the bounding metrics + if (0 == count) + aDesiredSize.mBoundingMetrics = childDesiredSize.mBoundingMetrics; + else + aDesiredSize.mBoundingMetrics += childDesiredSize.mBoundingMetrics; + + count++; + } + rv = childFrame->GetNextSibling(&childFrame); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get next child"); + } + // cache the frame's mBoundingMetrics + NS_STATIC_CAST(nsMathMLContainerFrame*, + aFrame)->SetBoundingMetrics(aDesiredSize.mBoundingMetrics); + // place and size children + NS_STATIC_CAST(nsMathMLContainerFrame*, + aFrame)->FinalizeReflow(aPresContext, *aReflowState.rendContext, + aDesiredSize); + return NS_OK; +} + +// helper function to place token elements +// mBoundingMetrics is computed at the ReflowToken pass, it is +// not computed here because our children may be text frames that +// do not implement the GetBoundingMetrics() interface. +nsresult +nsMathMLContainerFrame::PlaceTokenFor(nsIFrame* aFrame, + nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + PRBool aPlaceOrigin, + nsHTMLReflowMetrics& aDesiredSize) +{ + aDesiredSize.width = aDesiredSize.height = 0; + aDesiredSize.ascent = aDesiredSize.descent = 0; + + nsRect rect; + nsIFrame* childFrame; + aFrame->FirstChild(aPresContext, nsnull, &childFrame); + while (childFrame) { + if (!IsOnlyWhitespace(childFrame)) { + childFrame->GetRect(rect); + aDesiredSize.width += rect.width; + if (aDesiredSize.descent < rect.x) aDesiredSize.descent = rect.x; + if (aDesiredSize.ascent < rect.y) aDesiredSize.ascent = rect.y; + } + childFrame->GetNextSibling(&childFrame); + } + aDesiredSize.height = aDesiredSize.ascent + aDesiredSize.descent; + NS_STATIC_CAST(nsMathMLContainerFrame*, + aFrame)->GetBoundingMetrics(aDesiredSize.mBoundingMetrics); + + if (aPlaceOrigin) { + nscoord dy, dx = 0; + aFrame->FirstChild(aPresContext, nsnull, &childFrame); + while (childFrame) { + childFrame->GetRect(rect); + nsHTMLReflowMetrics childSize(nsnull); + childSize.width = rect.width; + childSize.height = rect.height; + + // place and size the child + dy = aDesiredSize.ascent - rect.y; + NS_STATIC_CAST(nsMathMLContainerFrame*, + aFrame)->FinishReflowChild(childFrame, aPresContext, + childSize, dx, dy, 0); + dx += rect.width; + childFrame->GetNextSibling(&childFrame); + } + } + NS_STATIC_CAST(nsMathMLContainerFrame*, + aFrame)->SetReference(nsPoint(0, aDesiredSize.ascent)); + return NS_OK; +} + +// We are an inline frame, so we handle dirty request like nsInlineFrame +NS_IMETHODIMP +nsMathMLContainerFrame::ReflowDirtyChild(nsIPresShell* aPresShell, nsIFrame* aChild) +{ + // The inline container frame does not handle the reflow + // request. It passes it up to its parent container. + + // If you don't already have dirty children, + if (!(mState & NS_FRAME_HAS_DIRTY_CHILDREN)) { + if (mParent) { + // Record that you are dirty and have dirty children + mState |= NS_FRAME_IS_DIRTY; + mState |= NS_FRAME_HAS_DIRTY_CHILDREN; + + // Pass the reflow request up to the parent + mParent->ReflowDirtyChild(aPresShell, (nsIFrame*) this); + } + else { + NS_ASSERTION(0, "No parent to pass the reflow request up to."); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMathMLContainerFrame::Reflow(nsIPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + nsresult rv; + aDesiredSize.width = aDesiredSize.height = 0; + aDesiredSize.ascent = aDesiredSize.descent = 0; + aDesiredSize.mBoundingMetrics.Clear(); + + // See if this is an incremental reflow + if (aReflowState.reason == eReflowReason_Incremental) { + nsIFrame* targetFrame; + aReflowState.reflowCommand->GetTarget(targetFrame); +#ifdef MATHML_NOISY_INCREMENTAL_REFLOW +printf("nsMathMLContainerFrame::Reflow:IncrementalReflow received by: "); +nsFrame::ListTag(stdout, this); +printf("for target: "); +nsFrame::ListTag(stdout, targetFrame); +printf("\n"); +#endif + if (this == targetFrame) { + // XXX We are the target of the incremental reflow. + // Rather than reflowing everything, see if we can speedup things + // by just doing the minimal work needed to update ourselves + } + else { + // Remove the next frame from the reflow path + nsIFrame* nextFrame; + aReflowState.reflowCommand->GetNext(nextFrame); + } + } + + ///////////// + // Reflow children + // Asking each child to cache its bounding metrics + + nsReflowStatus childStatus; + nsSize availSize(aReflowState.mComputedWidth, aReflowState.mComputedHeight); + nsHTMLReflowMetrics childDesiredSize(aDesiredSize.maxElementSize, + aDesiredSize.mFlags | NS_REFLOW_CALC_BOUNDING_METRICS); + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + ////////////// + // WHITESPACE: don't forget that whitespace doesn't count in MathML! + if (IsOnlyWhitespace(childFrame)) { + ReflowEmptyChild(aPresContext, childFrame); + } + else { + nsHTMLReflowState childReflowState(aPresContext, aReflowState, + childFrame, availSize); + rv = ReflowChild(childFrame, aPresContext, childDesiredSize, + childReflowState, childStatus); + //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status"); + if (NS_FAILED(rv)) return rv; + + // At this stage, the origin points of the children have no use, so we will use the + // origins as placeholders to store the child's ascent and descent. Later on, + // we should set the origins so as to overwrite what we are storing there now. + childFrame->SetRect(aPresContext, + nsRect(childDesiredSize.descent, childDesiredSize.ascent, + childDesiredSize.width, childDesiredSize.height)); + } + rv = childFrame->GetNextSibling(&childFrame); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get next child"); + } + + ///////////// + // If we are a container which is entitled to stretch its children, then we + // ask our stretchy children to stretch themselves + + if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mEmbellishData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mEmbellishData.flags)) { + + // get our tentative bounding metrics using Place() + Place(aPresContext, *aReflowState.rendContext, PR_FALSE, aDesiredSize); + + // What size should we use to stretch our stretchy children + // XXX tune this + nsBoundingMetrics containerSize = mBoundingMetrics; + + // get the strech direction + nsStretchDirection stretchDir = + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mEmbellishData.flags) ? + NS_STRETCH_DIRECTION_VERTICAL : NS_STRETCH_DIRECTION_HORIZONTAL; + + // fire the stretch on each child + childFrame = mFrames.FirstChild(); + while (childFrame) { + if (mEmbellishData.firstChild == childFrame) { + // skip this child... because: + // If we are here it means we are an embellished container and + // for now, we don't touch our embellished child frame. + // Its stretch will be handled separatedly when we receive + // the stretch command fired by our parent frame. + } + else if (!IsOnlyWhitespace(childFrame)) { + nsIMathMLFrame* aMathMLFrame = nsnull; + rv = childFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_SUCCEEDED(rv) && aMathMLFrame) { + // retrieve the metrics that was stored at the previous pass + nsRect rect; + childFrame->GetRect(rect); + aMathMLFrame->GetBoundingMetrics(childDesiredSize.mBoundingMetrics); + childDesiredSize.descent = rect.x; + childDesiredSize.ascent = rect.y; + childDesiredSize.height = rect.height; + childDesiredSize.width = rect.width; + + aMathMLFrame->Stretch(aPresContext, *aReflowState.rendContext, + stretchDir, containerSize, childDesiredSize); + // store the updated metrics + childFrame->SetRect(aPresContext, + nsRect(childDesiredSize.descent, childDesiredSize.ascent, + childDesiredSize.width, childDesiredSize.height)); + } + } + childFrame->GetNextSibling(&childFrame); + } + } + + ///////////// + // Place children now by re-adjusting the origins to align the baselines + FinalizeReflow(aPresContext, *aReflowState.rendContext, aDesiredSize); + + aStatus = NS_FRAME_COMPLETE; + return NS_OK; +} + +// For MathML, the 'type' will be used to determine the spacing between frames +// Subclasses can override this method to return a 'type' that will give +// them a particular spacing +NS_IMETHODIMP +nsMathMLContainerFrame::GetFrameType(nsIAtom** aType) const +{ + NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer"); + // see if this is an embellished operator (mapped to 'Op' in TeX) + if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) { + *aType = nsMathMLAtoms::operatorMathMLFrame; + } + else { + nsCOMPtr tag; + mContent->GetTag(*getter_AddRefs(tag)); + // see if this a token element (mapped to 'Ord'in TeX) + if (tag.get() == nsMathMLAtoms::mi_ || + tag.get() == nsMathMLAtoms::mn_ || + tag.get() == nsMathMLAtoms::ms_ || + tag.get() == nsMathMLAtoms::mtext_) { + *aType = nsMathMLAtoms::ordinaryMathMLFrame; + } + else { + // everything else is a schematta element (mapped to 'Inner' in TeX) + *aType = nsMathMLAtoms::schemataMathMLFrame; + } + } + NS_ADDREF(*aType); + return NS_OK; +} + +enum nsMathMLFrameTypeEnum { + eMathMLFrameType_UNKNOWN = -1, + eMathMLFrameType_Ordinary, + eMathMLFrameType_Operator, + eMathMLFrameType_Punctuation, + eMathMLFrameType_Inner +}; + +// see spacing table in Chapter 18, TeXBook (p.170) +static PRInt32 interFrameSpacingTable[4][4] = +{ + // in units of muspace. + // upper half of the byte is set if the + // spacing is not to be used for scriptlevel > 0 + /* Ord Op Punc Inner */ + /*Ord */ {0x00, 0x00, 0x00, 0x11}, + /*Op */ {0x00, 0x00, 0x00, 0x00}, + /*Punc */ {0x11, 0x00, 0x11, 0x11}, + /*Inner*/ {0x11, 0x00, 0x11, 0x11} +}; + +static nscoord +GetInterFrameSpacing(PRInt32 aScriptLevel, + nsIAtom* aFirstFrameType, + nsIAtom* aSecondFrameType) +{ + nsMathMLFrameTypeEnum firstType = eMathMLFrameType_UNKNOWN; + nsMathMLFrameTypeEnum secondType = eMathMLFrameType_UNKNOWN; + + // do the mapping for the first frame + if (aFirstFrameType == nsMathMLAtoms::ordinaryMathMLFrame) + firstType = eMathMLFrameType_Ordinary; + else if (aFirstFrameType == nsMathMLAtoms::operatorMathMLFrame) + firstType = eMathMLFrameType_Operator; + else if (aFirstFrameType == nsMathMLAtoms::schemataMathMLFrame) + firstType = eMathMLFrameType_Inner; + + // do the mapping for the second frame + if (aSecondFrameType == nsMathMLAtoms::ordinaryMathMLFrame) + secondType = eMathMLFrameType_Ordinary; + else if (aSecondFrameType == nsMathMLAtoms::operatorMathMLFrame) + secondType = eMathMLFrameType_Operator; + else if (aSecondFrameType == nsMathMLAtoms::operatorMathMLFrame) + secondType = eMathMLFrameType_Inner; + + // return 0 if there is a frame that we know nothing about + if (firstType == eMathMLFrameType_UNKNOWN || + secondType == eMathMLFrameType_UNKNOWN) { + return 0; + } + + PRInt32 space = interFrameSpacingTable[firstType][secondType]; + if (aScriptLevel > 0 && (space & 0xF0)) { + // spacing is disabled + return 0; + } + else { + return (space & 0x0F); + } +} + +NS_IMETHODIMP +nsMathMLContainerFrame::Place(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + PRBool aPlaceOrigin, + nsHTMLReflowMetrics& aDesiredSize) +{ + nsresult rv = NS_OK; + + // these are needed in case this frame is empty (i.e., we don't enter the loop) + aDesiredSize.width = aDesiredSize.height = 0; + aDesiredSize.ascent = aDesiredSize.descent = 0; + mBoundingMetrics.Clear(); + + // cache away thinspace + nsStyleFont font; + mStyleContext->GetStyle(eStyleStruct_Font, font); + nscoord thinSpace = NSToCoordRound(float(font.mFont.size)*float(3) / float(18)); + + PRInt32 count = 0; + nsHTMLReflowMetrics childSize (nsnull); + nsBoundingMetrics bmChild; + nscoord italicCorrection = 0; + nsCOMPtr prevFrameType; + + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + if (!IsOnlyWhitespace(childFrame)) { + nsCOMPtr childFrameType; + childFrame->GetFrameType(getter_AddRefs(childFrameType)); + GetReflowAndBoundingMetricsFor(childFrame, childSize, bmChild); + if (0 == count) { + aDesiredSize.ascent = childSize.ascent; + aDesiredSize.descent = childSize.descent; + mBoundingMetrics = bmChild; + } + else { + if (aDesiredSize.descent < childSize.descent) + aDesiredSize.descent = childSize.descent; + if (aDesiredSize.ascent < childSize.ascent) + aDesiredSize.ascent = childSize.ascent; + // add inter frame spacing + nscoord space = GetInterFrameSpacing(mPresentationData.scriptLevel, + prevFrameType, childFrameType); + mBoundingMetrics.width += space * thinSpace; + // add the child size + mBoundingMetrics += bmChild; + } + count++; + prevFrameType = childFrameType; + // 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 + GetItalicCorrection(bmChild, italicCorrection); + mBoundingMetrics.width += italicCorrection; + } + childFrame->GetNextSibling(&childFrame); + } + aDesiredSize.width = mBoundingMetrics.width; + aDesiredSize.height = aDesiredSize.ascent + aDesiredSize.descent; + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + + mReference.x = 0; + mReference.y = aDesiredSize.ascent; + + ////////////////// + // Place Children + + if (aPlaceOrigin) { + count = 0; + nscoord dx = 0, dy = 0; + italicCorrection = 0; + childFrame = mFrames.FirstChild(); + while (childFrame) { + if (!IsOnlyWhitespace(childFrame)) { + nsCOMPtr childFrameType; + childFrame->GetFrameType(getter_AddRefs(childFrameType)); + GetReflowAndBoundingMetricsFor(childFrame, childSize, bmChild); + dy = aDesiredSize.ascent - childSize.ascent; + if (0 < count) { + // add inter frame spacing + nscoord space = GetInterFrameSpacing(mPresentationData.scriptLevel, + prevFrameType, childFrameType); + dx += space * thinSpace; + } + count++; + prevFrameType = childFrameType; + FinishReflowChild(childFrame, aPresContext, childSize, dx, dy, 0); + // add child size + dx += bmChild.width; + // add italic correction + GetItalicCorrection(bmChild, italicCorrection); + dx += italicCorrection; + } + childFrame->GetNextSibling(&childFrame); + } + } + + return NS_OK; +} + + +//========================== +// *BEWARE* of the wrapper frame! +// This is the frame that is inserted to alter the style context of +// scripting elements. What this means is that looking for your parent +// or your siblings with (possible several) wrapper frames around you +// can make you wonder what is going on. For example, the direct parent +// of the subscript within is not , but instead the wrapper +// frame that was insterted to alter the style context of the subscript! +// You will seldom need to find out who is exactly your parent. You should +// first rethink your code to see if you can avoid finding who is your parent. +// Be careful, there are wrapper frames all over the place, and probably +// one or many are wrapping you if you are in a position where the +// scriptlevel is non zero. + +nsresult +NS_NewMathMLWrapperFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame) +{ + NS_PRECONDITION(aNewFrame, "null OUT ptr"); + if (nsnull == aNewFrame) { + return NS_ERROR_NULL_POINTER; + } + nsMathMLWrapperFrame* it = new (aPresShell) nsMathMLWrapperFrame; + if (nsnull == it) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aNewFrame = it; + return NS_OK; +} + +nsMathMLWrapperFrame::nsMathMLWrapperFrame() +{ +} + +nsMathMLWrapperFrame::~nsMathMLWrapperFrame() +{ +} + +NS_IMETHODIMP +nsMathMLWrapperFrame::Reflow(nsIPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + nsresult rv = NS_OK; + aStatus = NS_FRAME_COMPLETE; + aDesiredSize.width = aDesiredSize.height = aDesiredSize.ascent = aDesiredSize.descent = 0; + + // See if this is an incremental reflow + if (aReflowState.reason == eReflowReason_Incremental) { + nsIFrame* targetFrame; + aReflowState.reflowCommand->GetTarget(targetFrame); +#ifdef MATHML_NOISY_INCREMENTAL_REFLOW +printf("nsMathMLWrapperFrame::Reflow:IncrementalReflow received by: "); +nsFrame::ListTag(stdout, this); +printf("for target: "); +nsFrame::ListTag(stdout, targetFrame); +printf("\n"); +#endif + if (this == targetFrame) { + } + else { + // Remove the next frame from the reflow path + nsIFrame* nextFrame; + aReflowState.reflowCommand->GetNext(nextFrame); + } + } + + nsIFrame* childFrame = mFrames.FirstChild(); + if (childFrame) { + nsReflowStatus childStatus; + nsHTMLReflowMetrics childDesiredSize(aDesiredSize.maxElementSize, + aDesiredSize.mFlags | NS_REFLOW_CALC_BOUNDING_METRICS); + nsSize availSize(aReflowState.mComputedWidth, aReflowState.mComputedHeight); + nsHTMLReflowState childReflowState(aPresContext, aReflowState, childFrame, availSize); + rv = ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowState, childStatus); + childFrame->SetRect(aPresContext, + nsRect(childDesiredSize.descent,childDesiredSize.ascent, + childDesiredSize.width,childDesiredSize.height)); + aDesiredSize = childDesiredSize; + aStatus = childStatus; + + mBoundingMetrics = childDesiredSize.mBoundingMetrics; + + mReference.x = 0; + mReference.y = aDesiredSize.ascent; + + FinalizeReflow(aPresContext, *aReflowState.rendContext, aDesiredSize); + } + return rv; +} + +NS_IMETHODIMP +nsMathMLWrapperFrame::Place(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + PRBool aPlaceOrigin, + nsHTMLReflowMetrics& aDesiredSize) +{ + aDesiredSize.width = aDesiredSize.height = 0; + aDesiredSize.ascent = aDesiredSize.descent = 0; + aDesiredSize.mBoundingMetrics.Clear(); + + nsIFrame* childFrame = mFrames.FirstChild(); + if (childFrame) { + + // get a possibly updated bounding metrics if the child is + // a stretchy MathML frame that has just been stretched, otherwise + // keep the bounding metrics that was computed in Reflow() + nsIMathMLFrame* aMathMLFrame = nsnull; + nsresult rv = childFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_SUCCEEDED(rv) && aMathMLFrame) { + aMathMLFrame->GetBoundingMetrics(mBoundingMetrics); + aMathMLFrame->GetReference(mReference); // XXX doesn't set the correct value (due to lspace) fix me! + } + + nsRect rect; + childFrame->GetRect(rect); + aDesiredSize.descent = rect.x; + aDesiredSize.ascent = rect.y; + aDesiredSize.width = rect.width; + aDesiredSize.height = aDesiredSize.ascent + aDesiredSize.descent; + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + + if (aPlaceOrigin) { + nsHTMLReflowMetrics childSize(nsnull); + childSize.width = rect.width; + childSize.height = rect.height; + FinishReflowChild(childFrame, aPresContext, childSize, 0, 0, 0); + } + } + return NS_OK; +} + +#if defined(NS_DEBUG) && defined(SHOW_BOUNDING_BOX) +NS_IMETHODIMP +nsMathMLWrapperFrame::Paint(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsFramePaintLayer aWhichLayer) +{ + nsresult rv = NS_OK; + + rv = nsHTMLContainerFrame::Paint(aPresContext, + aRenderingContext, + aDirtyRect, + aWhichLayer); + + if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer) + { + aRenderingContext.SetColor(NS_RGB(255,0,0)); + + nscoord x = mReference.x + mBoundingMetrics.leftBearing; + nscoord y = mReference.y - mBoundingMetrics.ascent; + nscoord w = mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; + nscoord h = mBoundingMetrics.ascent + mBoundingMetrics.descent; + + aRenderingContext.DrawRect(x,y,w,h); + } + return rv; +} +#endif diff --git a/layout/mathml/base/src/nsMathMLmactionFrame.cpp b/layout/mathml/base/src/nsMathMLmactionFrame.cpp new file mode 100644 index 000000000000..d6dffc61cc62 --- /dev/null +++ b/layout/mathml/base/src/nsMathMLmactionFrame.cpp @@ -0,0 +1,472 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla MathML Project. + * + * The Initial Developer of the Original Code is The University Of + * Queensland. Portions created by The University Of Queensland are + * Copyright (C) 1999 The University Of Queensland. All Rights Reserved. + * + * Contributor(s): + * Roger B. Sidje + */ + +#include "nsCOMPtr.h" +#include "nsHTMLParts.h" +#include "nsIHTMLContent.h" +#include "nsFrame.h" +#include "nsLineLayout.h" +#include "nsHTMLIIDs.h" +#include "nsIPresContext.h" +#include "nsHTMLAtoms.h" +#include "nsUnitConversion.h" +#include "nsIStyleContext.h" +#include "nsStyleConsts.h" +#include "nsINameSpaceManager.h" +#include "nsIRenderingContext.h" +#include "nsIFontMetrics.h" +#include "nsStyleUtil.h" + +#include "nsCSSRendering.h" +#include "prprf.h" // For PR_snprintf() + +#include "nsIWebShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIWebBrowserChrome.h" +#include "nsIInterfaceRequestor.h" +#include "nsIDOMElement.h" + +#include "nsIDOMEventReceiver.h" +#include "nsIDOMMouseListener.h" + +#include "nsMathMLmactionFrame.h" + +// +// -- bind actions to a subexpression - implementation +// + +#define NS_MATHML_ACTION_TYPE_NONE 0 +#define NS_MATHML_ACTION_TYPE_TOGGLE 1 +#define NS_MATHML_ACTION_TYPE_STATUSLINE 2 +#define NS_MATHML_ACTION_TYPE_TOOLTIP 3 // unsupported +#define NS_MATHML_ACTION_TYPE_RESTYLE 4 + +NS_INTERFACE_MAP_BEGIN(nsMathMLmactionFrame) + NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener) +NS_INTERFACE_MAP_END_INHERITING(nsMathMLContainerFrame) + +NS_IMETHODIMP_(nsrefcnt) +nsMathMLmactionFrame::AddRef(void) +{ + return NS_OK; +} + +NS_IMETHODIMP_(nsrefcnt) +nsMathMLmactionFrame::Release(void) +{ + return NS_OK; +} + +nsresult +NS_NewMathMLmactionFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame) +{ + NS_PRECONDITION(aNewFrame, "null OUT ptr"); + if (nsnull == aNewFrame) { + return NS_ERROR_NULL_POINTER; + } + nsMathMLmactionFrame* it = new (aPresShell) nsMathMLmactionFrame; + if (nsnull == it) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aNewFrame = it; + return NS_OK; +} + +nsMathMLmactionFrame::nsMathMLmactionFrame() +{ +} + +nsMathMLmactionFrame::~nsMathMLmactionFrame() +{ + // unregister us as a mouse event listener ... +// printf("maction:%p unregistering as mouse event listener ...\n", this); + nsCOMPtr receiver(do_QueryInterface(mContent)); + receiver->RemoveEventListenerByIID(this, NS_GET_IID(nsIDOMMouseListener)); +} + +NS_IMETHODIMP +nsMathMLmactionFrame::Init(nsIPresContext* aPresContext, + nsIContent* aContent, + nsIFrame* aParent, + nsIStyleContext* aContext, + nsIFrame* aPrevInFlow) +{ + nsresult rv; + nsAutoString value, prefix; + + // Init our local attributes + + mPresContext = aPresContext; + + mChildCount = -1; // these will be updated in GetSelectedFrame() + mSelection = 0; + mSelectedFrame = nsnull; + + mActionType = NS_MATHML_ACTION_TYPE_NONE; + if (NS_CONTENT_ATTR_HAS_VALUE == aContent->GetAttribute(kNameSpaceID_None, + nsMathMLAtoms::actiontype_, value)) { + if (value.EqualsWithConversion("toggle")) + mActionType = NS_MATHML_ACTION_TYPE_TOGGLE; + + // XXX use goto to jump out of these if? + + if (NS_MATHML_ACTION_TYPE_NONE == mActionType) { + // expected tooltip prefix (8ch)... + prefix.AssignWithConversion("tooltip#"); + if (8 < value.Length() && 0 == value.Find(prefix)) + mActionType = NS_MATHML_ACTION_TYPE_TOOLTIP; + } + + if (NS_MATHML_ACTION_TYPE_NONE == mActionType) { + // expected statusline prefix (11ch)... + prefix.AssignWithConversion("statusline#"); + if (11 < value.Length() && 0 == value.Find(prefix)) + mActionType = NS_MATHML_ACTION_TYPE_STATUSLINE; + } + + if (NS_MATHML_ACTION_TYPE_NONE == mActionType) { + // expected restyle prefix (8ch)... + prefix.AssignWithConversion("restyle#"); + if (8 < value.Length() && 0 == value.Find(prefix)) { + mActionType = NS_MATHML_ACTION_TYPE_RESTYLE; + mRestyle = value; + + // Here is the situation: + // When the attribute [actiontype="restyle#id"] is set, the Style System has + // given us the associated style. But we want to start with our default style. + + // So... first, remove the attribute actiontype="restyle#id" + value.SetLength(0); + PRBool notify = PR_FALSE; // don't trigger a reflow yet! + aContent->SetAttribute(kNameSpaceID_None, nsMathMLAtoms::actiontype_, value, notify); + + // then, re-resolve our style + nsCOMPtr parentStyleContext; + aParent->GetStyleContext(getter_AddRefs(parentStyleContext)); + nsIStyleContext* newStyleContext; + aPresContext->ResolveStyleContextFor(aContent, parentStyleContext, + PR_FALSE, &newStyleContext); + if (!newStyleContext) + mRestyle.Truncate(); + else { + if (newStyleContext != aContext) + aContext = newStyleContext; + else { + NS_RELEASE(newStyleContext); + mRestyle.Truncate(); + } + } + } + } + } + + // Let the base class do the rest + rv = nsMathMLContainerFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); + +#if defined(NS_DEBUG) && defined(SHOW_BOUNDING_BOX) + mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS; +#endif + + return rv; +} + +// return the frame whose number is given by the attribute selection="number" +nsIFrame* +nsMathMLmactionFrame::GetSelectedFrame() +{ + nsresult rv = NS_OK; + nsAutoString value; + PRInt32 selection; + + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute(kNameSpaceID_None, + nsMathMLAtoms::selection_, value)) { + PRInt32 errorCode; + selection = value.ToInteger(&errorCode); + if (NS_FAILED(errorCode)) + selection = 1; + } + else selection = 1; // default is first frame + + if (-1 != mChildCount) { // we have been in this function before... + // cater for invalid user-supplied selection + if (selection > mChildCount || selection < 1) + selection = 1; + // quick return if it is identical with our cache + if (selection == mSelection) + return mSelectedFrame; + } + + // get the selected child and cache new values... + PRInt32 count = 0; + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + if (!IsOnlyWhitespace(childFrame)) { + if (!mSelectedFrame) + mSelectedFrame = childFrame; // default is first child + if (++count == selection) + mSelectedFrame = childFrame; + } + childFrame->GetNextSibling(&childFrame); + } + // cater for invalid user-supplied selection + if (selection > count || selection < 1) + selection = 1; + + mChildCount = count; + mSelection = selection; + + // if the selected child is an embellished operator, + // we become embellished as well + mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_OPERATOR; + mEmbellishData.firstChild = nsnull; + mEmbellishData.core = nsnull; + mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; + if (mSelectedFrame) { + nsIMathMLFrame* aMathMLFrame = nsnull; + rv = mSelectedFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); + if (NS_SUCCEEDED(rv) && aMathMLFrame) { + nsEmbellishData embellishData; + aMathMLFrame->GetEmbellishData(embellishData); + if (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags) && embellishData.firstChild) { + mEmbellishData.flags |= NS_MATHML_EMBELLISH_OPERATOR; + mEmbellishData.firstChild = mSelectedFrame; // yes! + mEmbellishData.core = embellishData.core; + mEmbellishData.direction = embellishData.direction; + } + } + } + + return mSelectedFrame; +} + +NS_IMETHODIMP +nsMathMLmactionFrame::SetInitialChildList(nsIPresContext* aPresContext, + nsIAtom* aListName, + nsIFrame* aChildList) +{ + nsresult rv = nsMathMLContainerFrame::SetInitialChildList(aPresContext, aListName, aChildList); + + // This very first call to GetSelectedFrame() will cause us to be marked as an + // embellished operator if the selected child is an embellished operator, + GetSelectedFrame(); + + // register us as a mouse event listener ... +// printf("maction:%p registering as mouse event listener ...\n", this); + nsCOMPtr receiver(do_QueryInterface(mContent)); + receiver->AddEventListenerByIID(this, NS_GET_IID(nsIDOMMouseListener)); + + return rv; +} + +// Return the selected frame ... +NS_IMETHODIMP +nsMathMLmactionFrame::GetFrameForPoint(nsIPresContext* aPresContext, + const nsPoint& aPoint, + nsFramePaintLayer aWhichLayer, + nsIFrame** aFrame) +{ + nsIFrame* childFrame = GetSelectedFrame(); + if (childFrame) { + nsPoint pt; + pt.MoveTo(aPoint.x - mRect.x, aPoint.y - mRect.y); + return childFrame->GetFrameForPoint(aPresContext, pt, aWhichLayer, aFrame); + } + else + return nsFrame::GetFrameForPoint(aPresContext, aPoint, aWhichLayer, aFrame); +} + +// Only paint the selected child... +NS_IMETHODIMP +nsMathMLmactionFrame::Paint(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsFramePaintLayer aWhichLayer) +{ + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) { + if (disp->IsVisible() && mRect.width && mRect.height) { + // Paint our background and border + PRIntn skipSides = GetSkipSides(); + const nsStyleColor* color = (const nsStyleColor*) + mStyleContext->GetStyleData(eStyleStruct_Color); + const nsStyleSpacing* spacing = (const nsStyleSpacing*) + mStyleContext->GetStyleData(eStyleStruct_Spacing); + + nsRect rect(0, 0, mRect.width, mRect.height); + nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this, + aDirtyRect, rect, *color, *spacing, 0, 0); + nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, + aDirtyRect, rect, *spacing, mStyleContext, skipSides); + nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this, + aDirtyRect, rect, *spacing, mStyleContext, 0); + } + } + + nsIFrame* childFrame = GetSelectedFrame(); + if (childFrame) + PaintChild(aPresContext, aRenderingContext, aDirtyRect, childFrame, aWhichLayer); + +#if defined(NS_DEBUG) && defined(SHOW_BOUNDING_BOX) + // visual debug + if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer && + NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags)) + { + aRenderingContext.SetColor(NS_RGB(0,0,255)); + + nscoord x = mReference.x + mBoundingMetrics.leftBearing; + nscoord y = mReference.y - mBoundingMetrics.ascent; + nscoord w = mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing; + nscoord h = mBoundingMetrics.ascent + mBoundingMetrics.descent; + + aRenderingContext.DrawRect(x,y,w,h); + } +#endif + return NS_OK; +} + +// Only place the selected child ... +NS_IMETHODIMP +nsMathMLmactionFrame::Place(nsIPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + PRBool aPlaceOrigin, + nsHTMLReflowMetrics& aDesiredSize) +{ + aDesiredSize.width = aDesiredSize.height = 0; + aDesiredSize.ascent = aDesiredSize.descent = 0; + mBoundingMetrics.Clear(); + nsIFrame* childFrame = GetSelectedFrame(); + if (childFrame) { + GetReflowAndBoundingMetricsFor(childFrame, aDesiredSize, mBoundingMetrics); + if (aPlaceOrigin) { + FinishReflowChild(childFrame, aPresContext, aDesiredSize, 0, 0, 0); + } + mReference.x = 0; + mReference.y = aDesiredSize.ascent; + } + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + return NS_OK; +} + +// ################################################################ +// Event handlers +// ################################################################ + +// helper to show a msg on the status bar +// curled from nsObjectFrame.cpp ... +nsresult +nsMathMLmactionFrame::ShowStatus(nsIPresContext* aPresContext, + nsString& aStatusMsg) +{ + nsCOMPtr cont; + nsresult rv = aPresContext->GetContainer(getter_AddRefs(cont)); + if (NS_SUCCEEDED(rv) && cont) { + nsCOMPtr docShellItem(do_QueryInterface(cont)); + if (docShellItem) { + + nsCOMPtr treeOwner; + docShellItem->GetTreeOwner(getter_AddRefs(treeOwner)); + + if(treeOwner) + { + nsCOMPtr browserChrome(do_GetInterface(treeOwner)); + + if(browserChrome) + browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_SCRIPT, aStatusMsg.GetUnicode()); + } + } + } + return rv; +} + +nsresult +nsMathMLmactionFrame::MouseOver(nsIDOMEvent* aMouseEvent) +{ + // see if we should display a status message + if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) + { + nsAutoString value; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute(kNameSpaceID_None, + nsMathMLAtoms::actiontype_, value)) + { + // expected statusline prefix (11ch)... + nsAutoString statusline; + statusline.AssignWithConversion("statusline#"); + if (11 < value.Length() && 0 == value.Find(statusline)) { + value.Cut(0, 11); + ShowStatus(mPresContext, value); + } + } + } + return NS_OK; +} + +nsresult +nsMathMLmactionFrame::MouseOut(nsIDOMEvent* aMouseEvent) +{ + // see if we should remove the status message + if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) + { + nsAutoString value; + value.SetLength(0); + ShowStatus(mPresContext, value); + } + return NS_OK; +} + +nsresult +nsMathMLmactionFrame::MouseClick(nsIDOMEvent* aMouseEvent) +{ + nsAutoString value; + if (NS_MATHML_ACTION_TYPE_TOGGLE == mActionType) + { + if (mChildCount > 1) { + PRInt32 selection = (mSelection == mChildCount)? 1 : mSelection + 1; + char cbuf[10]; + PR_snprintf(cbuf, sizeof(cbuf), "%d", selection); + value.AssignWithConversion(cbuf); + PRBool notify = PR_FALSE; // don't yet notify the document + mContent->SetAttribute(kNameSpaceID_None, nsMathMLAtoms::selection_, value, notify); + + // Now trigger a content-changed reflow... +// ContentChanged(mPresContext, mContent, nsnull); + + nsCOMPtr presShell; + mPresContext->GetShell(getter_AddRefs(presShell)); + ReflowDirtyChild(presShell, mSelectedFrame); + } + } + else if (NS_MATHML_ACTION_TYPE_RESTYLE == mActionType) + { + if (0 < mRestyle.Length()) { + nsCOMPtr node( do_QueryInterface(mContent) ); + if (node.get()) { + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute(kNameSpaceID_None, + nsMathMLAtoms::actiontype_, value)) + node->RemoveAttribute(NS_ConvertASCIItoUCS2("actiontype")); + else + node->SetAttribute(NS_ConvertASCIItoUCS2("actiontype"), mRestyle); + } + } + } + return NS_OK; +}