/* * 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 "nsHTMLAtoms.h" #include "nsUnitConversion.h" #include "nsIStyleContext.h" #include "nsStyleConsts.h" #include "nsINameSpaceManager.h" #include "nsIRenderingContext.h" #include "nsIFontMetrics.h" #include "nsStyleUtil.h" #include "nsMathMLmfencedFrame.h" // // -- surround content with a pair of fences // nsresult NS_NewMathMLmfencedFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsMathMLmfencedFrame* it = new (aPresShell) nsMathMLmfencedFrame; if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } *aNewFrame = it; return NS_OK; } nsMathMLmfencedFrame::nsMathMLmfencedFrame() { } nsMathMLmfencedFrame::~nsMathMLmfencedFrame() { RemoveFencesAndSeparators(); } NS_IMETHODIMP nsMathMLmfencedFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow) { nsresult rv = nsMathMLContainerFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); mEmbellishData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY; mOpenChar = nsnull; mCloseChar = nsnull; mSeparatorsChar = nsnull; mSeparatorsCount = 0; #if defined(NS_DEBUG) && defined(SHOW_BOUNDING_BOX) mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS; #endif return rv; } NS_IMETHODIMP nsMathMLmfencedFrame::SetInitialChildList(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame* aChildList) { nsresult rv; // First, let the base class do its work rv = nsMathMLContainerFrame::SetInitialChildList(aPresContext, aListName, aChildList); if (NS_FAILED(rv)) return rv; rv = ReCreateFencesAndSeparators(aPresContext); return rv; } void nsMathMLmfencedFrame::RemoveFencesAndSeparators() { if (mOpenChar) delete mOpenChar; if (mCloseChar) delete mCloseChar; if (mSeparatorsChar) delete[] mSeparatorsChar; mOpenChar = nsnull; mCloseChar = nsnull; mSeparatorsChar = nsnull; mSeparatorsCount = 0; } nsresult nsMathMLmfencedFrame::ReCreateFencesAndSeparators(nsIPresContext* aPresContext) { nsresult rv; RemoveFencesAndSeparators(); nsAutoString value, data; ////////////// // see if the opening fence is there ... rv = GetAttribute(mContent, mPresentationData.mstyle, nsMathMLAtoms::open_, value); if (NS_CONTENT_ATTR_HAS_VALUE == rv) { value.Trim(" "); data = value; } else if (NS_CONTENT_ATTR_NOT_THERE == rv) data = PRUnichar('('); // default as per the MathML REC else data = nsAutoString(); if (0 < data.Length()) { mOpenChar = new nsMathMLChar; if (!mOpenChar) return NS_ERROR_OUT_OF_MEMORY; mOpenChar->SetData(aPresContext, data); ResolveMathMLCharStyle(aPresContext, mContent, mStyleContext, mOpenChar); } ////////////// // see if the closing fence is there ... rv = GetAttribute(mContent, mPresentationData.mstyle, nsMathMLAtoms::close_, value); if (NS_CONTENT_ATTR_HAS_VALUE == rv) { value.Trim(" "); data = value; } else if (NS_CONTENT_ATTR_NOT_THERE == rv) data = PRUnichar(')'); // default as per the MathML REC else data = nsAutoString(); if (0 < data.Length()) { mCloseChar = new nsMathMLChar; if (!mCloseChar) return NS_ERROR_OUT_OF_MEMORY; mCloseChar->SetData(aPresContext, data); ResolveMathMLCharStyle(aPresContext, mContent, mStyleContext, mCloseChar); } ////////////// // see if separators are there ... rv = GetAttribute(mContent, mPresentationData.mstyle, nsMathMLAtoms::separators_, value); if (NS_CONTENT_ATTR_HAS_VALUE == rv) { value.Trim(" "); data = value; } else if (NS_CONTENT_ATTR_NOT_THERE == rv) data = PRUnichar(','); // default as per the MathML REC else data = nsAutoString(); mSeparatorsCount = data.Length(); if (0 < mSeparatorsCount) { PRInt32 sepCount = -1; nsIFrame* childFrame = mFrames.FirstChild(); while (childFrame) { if (!IsOnlyWhitespace(childFrame)) { sepCount++; } rv = childFrame->GetNextSibling(&childFrame); NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get next child"); } if (0 < sepCount) { mSeparatorsChar = new nsMathMLChar[sepCount]; if (!mSeparatorsChar) return NS_ERROR_OUT_OF_MEMORY; nsAutoString sepChar; for (PRInt32 i = 0; i < sepCount; i++) { sepChar = (i < mSeparatorsCount) ? data[i] : data[mSeparatorsCount-1]; mSeparatorsChar[i].SetData(aPresContext, sepChar); ResolveMathMLCharStyle(aPresContext, mContent, mStyleContext, &mSeparatorsChar[i]); } } mSeparatorsCount = sepCount; } return NS_OK; } NS_IMETHODIMP nsMathMLmfencedFrame::Paint(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer) { nsresult rv = NS_OK; ///////////// // paint the content rv = nsMathMLContainerFrame::Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer); //////////// // paint fences and separators if (NS_SUCCEEDED(rv)) { if (mOpenChar) mOpenChar->Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer, this); if (mCloseChar) mCloseChar->Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer, this); for (PRInt32 i = 0; i < mSeparatorsCount; i++) { mSeparatorsChar[i].Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer, this); } } return rv; } NS_IMETHODIMP nsMathMLmfencedFrame::Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { nsresult rv; aDesiredSize.width = aDesiredSize.height = 0; aDesiredSize.ascent = aDesiredSize.descent = 0; ///////////// // 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"); } // get our bounding metrics using a tentative Place() Place(aPresContext, *aReflowState.rendContext, PR_FALSE, aDesiredSize); // What size should we use to stretch our stretchy children // XXX tune this nsBoundingMetrics containerSize = mBoundingMetrics; ///////////// // Ask stretchy children to stretch themselves nsStretchDirection stretchDir = NS_STRETCH_DIRECTION_VERTICAL; nsRect rect; childFrame = mFrames.FirstChild(); while (childFrame) { if (!IsOnlyWhitespace(childFrame)) { nsIMathMLFrame* aMathMLFrame; rv = childFrame->QueryInterface(NS_GET_IID(nsIMathMLFrame), (void**)&aMathMLFrame); if (NS_SUCCEEDED(rv) && aMathMLFrame) { // retrieve the metrics that was stored at the previous pass 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)); if (aDesiredSize.descent < childDesiredSize.descent) aDesiredSize.descent = childDesiredSize.descent; if (aDesiredSize.ascent < childDesiredSize.ascent) aDesiredSize.ascent = childDesiredSize.ascent; } } childFrame->GetNextSibling(&childFrame); } ////////////////////////////////////////// // Prepare the opening fence, separators, and closing fence, and // adjust the origin of children. PRInt32 i; nsStyleFont font; mStyleContext->GetStyle(eStyleStruct_Font, font); nsCOMPtr fm; aReflowState.rendContext->SetFont(font.mFont); aReflowState.rendContext->GetFontMetrics(*getter_AddRefs(fm)); nscoord axisHeight, em; GetAxisHeight(*aReflowState.rendContext, fm, axisHeight); em = NSToCoordRound(float(font.mFont.size)); nscoord fontAscent, fontDescent; fm->GetMaxAscent(fontAscent); fm->GetMaxDescent(fontDescent); // we need to center around the axis nscoord delta = PR_MAX(containerSize.ascent - axisHeight, containerSize.descent + axisHeight); containerSize.ascent = delta + axisHeight; containerSize.descent = delta - axisHeight; ///////////////// // opening fence ... ReflowChar(aPresContext, *aReflowState.rendContext, mOpenChar, NS_MATHML_OPERATOR_FORM_PREFIX, mPresentationData.scriptLevel, fontAscent, fontDescent, axisHeight, em, containerSize, aDesiredSize); ///////////////// // separators ... for (i = 0; i < mSeparatorsCount; i++) { ReflowChar(aPresContext, *aReflowState.rendContext, &mSeparatorsChar[i], NS_MATHML_OPERATOR_FORM_INFIX, mPresentationData.scriptLevel, fontAscent, fontDescent, axisHeight, em, containerSize, aDesiredSize); } ///////////////// // closing fence ... ReflowChar(aPresContext, *aReflowState.rendContext, mCloseChar, NS_MATHML_OPERATOR_FORM_POSTFIX, mPresentationData.scriptLevel, fontAscent, fontDescent, axisHeight, em, containerSize, aDesiredSize); ////////////////// // Adjust the origins of each child. // and update our bounding metrics i = 0; nscoord dy, dx = 0; nsBoundingMetrics bm; PRBool firstTime = PR_TRUE; mBoundingMetrics.Clear(); if (mOpenChar) { mOpenChar->GetBoundingMetrics(bm); mOpenChar->GetRect(rect); dy = aDesiredSize.ascent - rect.y; if (mOpenChar->GetEnum() == eMathMLChar_DONT_STRETCH) dy += (fontAscent - bm.ascent); mOpenChar->SetRect(nsRect(dx + rect.x, dy, bm.width, rect.height)); dx += rect.width; bm.descent = (bm.ascent + bm.descent) - rect.y; bm.ascent = rect.y; mBoundingMetrics = bm; firstTime = PR_FALSE; } childFrame = mFrames.FirstChild(); while (childFrame) { if (!IsOnlyWhitespace(childFrame)) { nsHTMLReflowMetrics childSize(nsnull); GetReflowAndBoundingMetricsFor(childFrame, childSize, bm); if (firstTime) { firstTime = PR_FALSE; mBoundingMetrics = bm; } else mBoundingMetrics += bm; FinishReflowChild(childFrame, aPresContext, childSize, dx, aDesiredSize.ascent - childSize.ascent, 0); dx += childSize.width; if (i < mSeparatorsCount) { mSeparatorsChar[i].GetBoundingMetrics(bm); mSeparatorsChar[i].GetRect(rect); dy = aDesiredSize.ascent - rect.y; if (mSeparatorsChar[i].GetEnum() == eMathMLChar_DONT_STRETCH) dy += (fontAscent - bm.ascent); mSeparatorsChar[i].SetRect(nsRect(dx + rect.x, dy, bm.width, rect.height)); dx += rect.width; bm.descent = (bm.ascent + bm.descent) - rect.y; bm.ascent = rect.y; bm.leftBearing += rect.x; bm.rightBearing += rect.x; bm.width = rect.width; mBoundingMetrics += bm; } i++; } childFrame->GetNextSibling(&childFrame); } if (mCloseChar) { mCloseChar->GetBoundingMetrics(bm); mCloseChar->GetRect(rect); dy = aDesiredSize.ascent - rect.y; if (mCloseChar->GetEnum() == eMathMLChar_DONT_STRETCH) dy += (fontAscent - bm.ascent); mCloseChar->SetRect(nsRect(dx + rect.x, dy, bm.width, rect.height)); bm.descent = (bm.ascent + bm.descent) - rect.y; bm.ascent = rect.y; bm.leftBearing += rect.x; bm.rightBearing += rect.x; bm.width = rect.width; if (firstTime) mBoundingMetrics = bm; else mBoundingMetrics += bm; } aDesiredSize.width = mBoundingMetrics.width; aDesiredSize.mBoundingMetrics = mBoundingMetrics; aDesiredSize.height = aDesiredSize.ascent + aDesiredSize.descent; mReference.x = 0; mReference.y = aDesiredSize.ascent; if (aDesiredSize.maxElementSize) { aDesiredSize.maxElementSize->width = aDesiredSize.width; aDesiredSize.maxElementSize->height = aDesiredSize.height; } aStatus = NS_FRAME_COMPLETE; return NS_OK; } // helper function to perform the common task of formatting our chars nsresult nsMathMLmfencedFrame::ReflowChar(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsMathMLChar* aMathMLChar, nsOperatorFlags aForm, PRInt32 aScriptLevel, nscoord fontAscent, nscoord fontDescent, nscoord axisHeight, nscoord em, nsBoundingMetrics& aContainerSize, nsHTMLReflowMetrics& aDesiredSize) { if (aMathMLChar && 0 < aMathMLChar->Length()) { // stretch the char to the appropriate height if it is not big enough. nsBoundingMetrics charSize; charSize.Clear(); // this will tell stretch that we don't know the default size aMathMLChar->Stretch(aPresContext, aRenderingContext, NS_STRETCH_DIRECTION_VERTICAL, aContainerSize, charSize); if (eMathMLChar_DONT_STRETCH != aMathMLChar->GetEnum()) { // has changed... so center the char around the axis nscoord height = charSize.ascent + charSize.descent; charSize.ascent = height/2 + axisHeight; charSize.descent = height - charSize.ascent; } else { charSize.ascent = fontAscent; charSize.descent = fontDescent; } if (aDesiredSize.ascent < charSize.ascent) aDesiredSize.ascent = charSize.ascent; if (aDesiredSize.descent < charSize.descent) aDesiredSize.descent = charSize.descent; nsOperatorFlags aFlags; float aLeftSpace = 0.0f; float aRightSpace = 0.0f; nsAutoString aData; aMathMLChar->GetData(aData); PRBool found = nsMathMLOperators::LookupOperator(aData, aForm, &aFlags, &aLeftSpace, &aRightSpace); // If we don't want extra space when we are a script if (found && aScriptLevel > 0) { aLeftSpace /= 2.0f; aRightSpace /= 2.0f; } // account the spacing charSize.width += NSToCoordRound((aLeftSpace + aRightSpace) * em); // x-origin is used to store lspace ... // y-origin is used to stored the ascent ... aMathMLChar->SetRect(nsRect(NSToCoordRound(aLeftSpace * em), charSize.ascent, charSize.width, charSize.ascent + charSize.descent)); } return NS_OK; } // ---------------------- // the Style System will use these to pass the proper style context to our MathMLChar NS_IMETHODIMP nsMathMLmfencedFrame::GetAdditionalStyleContext(PRInt32 aIndex, nsIStyleContext** aStyleContext) const { NS_PRECONDITION(aStyleContext, "null OUT ptr"); PRInt32 openIndex = -1; PRInt32 closeIndex = -1; PRInt32 lastIndex = mSeparatorsCount-1; if (mOpenChar) { lastIndex++; openIndex = lastIndex; } if (mCloseChar) { lastIndex++; closeIndex = lastIndex; } if (aIndex < 0 || aIndex > lastIndex) { return NS_ERROR_INVALID_ARG; } *aStyleContext = nsnull; if (aIndex < mSeparatorsCount) { mSeparatorsChar[aIndex].GetStyleContext(aStyleContext); } else if (aIndex == openIndex) { mOpenChar->GetStyleContext(aStyleContext); } else if (aIndex == closeIndex) { mCloseChar->GetStyleContext(aStyleContext); } return NS_OK; } NS_IMETHODIMP nsMathMLmfencedFrame::SetAdditionalStyleContext(PRInt32 aIndex, nsIStyleContext* aStyleContext) { PRInt32 openIndex = -1; PRInt32 closeIndex = -1; PRInt32 lastIndex = mSeparatorsCount-1; if (mOpenChar) { lastIndex++; openIndex = lastIndex; } if (mCloseChar) { lastIndex++; closeIndex = lastIndex; } if (aIndex < 0 || aIndex > lastIndex) { return NS_ERROR_INVALID_ARG; } if (aIndex < mSeparatorsCount) { mSeparatorsChar[aIndex].SetStyleContext(aStyleContext); } else if (aIndex == openIndex) { mOpenChar->SetStyleContext(aStyleContext); } else if (aIndex == closeIndex) { mCloseChar->SetStyleContext(aStyleContext); } return NS_OK; }