/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * 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 Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Roger B. Sidje * David J. Fiddes * Karl Tomlinson , Mozilla Corporation * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsCOMPtr.h" #include "nsCRT.h" // to get NS_IS_SPACE #include "nsFrame.h" #include "nsPresContext.h" #include "nsStyleContext.h" #include "nsStyleConsts.h" #include "nsIRenderingContext.h" #include "nsIFontMetrics.h" #include "nsMathMLmpaddedFrame.h" // // -- adjust space around content - implementation // #define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there #define NS_MATHML_SIGN_UNSPECIFIED 0 #define NS_MATHML_SIGN_MINUS 1 #define NS_MATHML_SIGN_PLUS 2 #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0 #define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special #define NS_MATHML_PSEUDO_UNIT_WIDTH 2 #define NS_MATHML_PSEUDO_UNIT_HEIGHT 3 #define NS_MATHML_PSEUDO_UNIT_DEPTH 4 #define NS_MATHML_PSEUDO_UNIT_LSPACE 5 #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 6 nsIFrame* NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { return new (aPresShell) nsMathMLmpaddedFrame(aContext); } NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame) nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() { } NS_IMETHODIMP nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent) { // let the base class get the default from our parent nsMathMLContainerFrame::InheritAutomaticData(aParent); mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY; return NS_OK; } void nsMathMLmpaddedFrame::ProcessAttributes() { /* parse the attributes width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace) height= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit) depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit) lspace= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit) */ nsAutoString value; /* The REC says: There is one exceptional element, , whose attributes cannot be set with . When the attributes width, height and depth are specified on an element, they apply only to the element. Similarly, when lspace is set with , it applies only to the element. */ // See if attributes are local, don't access mstyle ! // width mWidthSign = NS_MATHML_SIGN_INVALID; GetAttribute(mContent, nsnull, nsGkAtoms::width, value); if (!value.IsEmpty()) { ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit); } // height mHeightSign = NS_MATHML_SIGN_INVALID; GetAttribute(mContent, nsnull, nsGkAtoms::height, value); if (!value.IsEmpty()) { ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit); } // depth mDepthSign = NS_MATHML_SIGN_INVALID; GetAttribute(mContent, nsnull, nsGkAtoms::depth_, value); if (!value.IsEmpty()) { ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit); } // lspace mLeftSpaceSign = NS_MATHML_SIGN_INVALID; GetAttribute(mContent, nsnull, nsGkAtoms::lspace_, value); if (!value.IsEmpty()) { ParseAttribute(value, mLeftSpaceSign, mLeftSpace, mLeftSpacePseudoUnit); } } // parse an input string in the following format (see bug 148326 for testcases): // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace) PRBool nsMathMLmpaddedFrame::ParseAttribute(nsString& aString, PRInt32& aSign, nsCSSValue& aCSSValue, PRInt32& aPseudoUnit) { aCSSValue.Reset(); aSign = NS_MATHML_SIGN_INVALID; aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED; aString.CompressWhitespace(); // aString is not a const in this code PRInt32 stringLength = aString.Length(); if (!stringLength) return PR_FALSE; nsAutoString number, unit; ////////////////////// // see if the sign is there PRInt32 i = 0; if (aString[0] == '+') { aSign = NS_MATHML_SIGN_PLUS; i++; } else if (aString[0] == '-') { aSign = NS_MATHML_SIGN_MINUS; i++; } else aSign = NS_MATHML_SIGN_UNSPECIFIED; // skip any space after the sign if (i < stringLength && nsCRT::IsAsciiSpace(aString[i])) i++; // get the number PRBool gotDot = PR_FALSE, gotPercent = PR_FALSE; for (; i < stringLength; i++) { PRUnichar c = aString[i]; if (gotDot && c == '.') { // error - two dots encountered aSign = NS_MATHML_SIGN_INVALID; return PR_FALSE; } if (c == '.') gotDot = PR_TRUE; else if (!nsCRT::IsAsciiDigit(c)) { break; } number.Append(c); } // catch error if we didn't enter the loop above... we could simply initialize // floatValue = 1, to cater for cases such as width="height", but that wouldn't // be in line with the spec which requires an explicit number if (number.IsEmpty()) { #ifdef NS_DEBUG printf("mpadded: attribute with bad numeric value: %s\n", NS_LossyConvertUTF16toASCII(aString).get()); #endif aSign = NS_MATHML_SIGN_INVALID; return PR_FALSE; } PRInt32 errorCode; float floatValue = number.ToFloat(&errorCode); if (errorCode) { aSign = NS_MATHML_SIGN_INVALID; return PR_FALSE; } // skip any space after the number if (i < stringLength && nsCRT::IsAsciiSpace(aString[i])) i++; // see if this is a percentage-based value if (i < stringLength && aString[i] == '%') { i++; gotPercent = PR_TRUE; // skip any space after the '%' sign if (i < stringLength && nsCRT::IsAsciiSpace(aString[i])) i++; } // the remainder now should be a css-unit, or a pseudo-unit, or a named-space aString.Right(unit, stringLength - i); if (unit.IsEmpty()) { // also cater for the edge case of "0" for which the unit is optional if (gotPercent || !floatValue) { aCSSValue.SetPercentValue(floatValue / 100.0f); aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF; return PR_TRUE; } /* else { // no explicit CSS unit and no explicit pseudo-unit... // In this case, the MathML REC suggests taking ems for // h-unit (width, lspace) or exs for v-unit (height, depth). // Here, however, we explicitly request authors to specify // the unit. This is more in line with the CSS REC (and // it allows keeping the code simpler...) } */ } else if (unit.EqualsLiteral("width")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH; else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT; else if (unit.EqualsLiteral("depth")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH; else if (unit.EqualsLiteral("lspace")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_LSPACE; else if (!gotPercent) { // percentage can only apply to a pseudo-unit // see if the unit is a named-space // XXX nsnull in ParseNamedSpacedValue()? don't access mstyle? if (ParseNamedSpaceValue(nsnull, unit, aCSSValue)) { // re-scale properly, and we know that the unit of the named-space is 'em' floatValue *= aCSSValue.GetFloatValue(); aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM); aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE; return PR_TRUE; } // see if the input was just a CSS value number.Append(unit); // leave the sign out if it was there if (ParseNumericValue(number, aCSSValue)) return PR_TRUE; } // if we enter here, we have a number that will act as a multiplier on a pseudo-unit if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) { if (gotPercent) aCSSValue.SetPercentValue(floatValue / 100.0f); else aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number); return PR_TRUE; } #ifdef NS_DEBUG printf("mpadded: attribute with bad numeric value: %s\n", NS_LossyConvertUTF16toASCII(aString).get()); #endif // if we reach here, it means we encounter an unexpected input aSign = NS_MATHML_SIGN_INVALID; return PR_FALSE; } void nsMathMLmpaddedFrame::UpdateValue(PRInt32 aSign, PRInt32 aPseudoUnit, const nsCSSValue& aCSSValue, nscoord aLeftSpace, const nsBoundingMetrics& aBoundingMetrics, nscoord& aValueToUpdate) const { nsCSSUnit unit = aCSSValue.GetUnit(); if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) { nscoord scaler = 0, amount = 0; if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) { switch(aPseudoUnit) { case NS_MATHML_PSEUDO_UNIT_WIDTH: scaler = aBoundingMetrics.width; break; case NS_MATHML_PSEUDO_UNIT_HEIGHT: scaler = aBoundingMetrics.ascent; break; case NS_MATHML_PSEUDO_UNIT_DEPTH: scaler = aBoundingMetrics.descent; break; case NS_MATHML_PSEUDO_UNIT_LSPACE: scaler = aLeftSpace; break; default: // if we ever reach here, it would mean something is wrong // somewhere with the setup and/or the caller NS_ERROR("Unexpected Pseudo Unit"); return; } } if (eCSSUnit_Number == unit) amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue()); else if (eCSSUnit_Percent == unit) amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue()); else amount = CalcLength(PresContext(), mStyleContext, aCSSValue); nscoord oldValue = aValueToUpdate; if (NS_MATHML_SIGN_PLUS == aSign) aValueToUpdate += amount; else if (NS_MATHML_SIGN_MINUS == aSign) aValueToUpdate -= amount; else aValueToUpdate = amount; /* The REC says: Dimensions that would be positive if the content was rendered normally cannot be made negative using ; a positive dimension is set to 0 if it would otherwise become negative. Dimensions which are initially 0 can be made negative */ if (0 < oldValue && 0 > aValueToUpdate) aValueToUpdate = 0; } } NS_IMETHODIMP nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { ProcessAttributes(); /////////////// // Let the base class format our content like an inferred mrow nsresult rv = nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status"); return rv; } /* virtual */ nsresult nsMathMLmpaddedFrame::Place(nsIRenderingContext& aRenderingContext, PRBool aPlaceOrigin, nsHTMLReflowMetrics& aDesiredSize) { nsresult rv = nsMathMLContainerFrame::Place(aRenderingContext, PR_FALSE, aDesiredSize); if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) { DidReflowChildren(GetFirstChild(nsnull)); return rv; } nscoord height = mBoundingMetrics.ascent; nscoord depth = mBoundingMetrics.descent; // In MathML2 (http://www.w3.org/TR/MathML2/chapter3.html#presm.mpadded), // lspace is "the amount of space between the left edge of a bounding box // and the start of the rendering of its contents' bounding box" and the // default is zero. // // In MathML3 draft // http://www.w3.org/TR/2007/WD-MathML3-20070427/chapter3.html#id.3.3.6.2, // lspace is "the amount of space between the left edge of the bounding box // and the positioning poin [sic] of the mpadded element" and the default is // "same as content". // // In both cases, "MathML renderers should ensure that, except for the // effects of the attributes, relative spacing between the contents of // mpadded and surrounding MathML elements is not modified by replacing an // mpadded element with an mrow element with the same content." nscoord lspace = 0; // In MATHML3, "width" will be the bounding box width and "advancewidth" will // refer "to the horizontal distance between the positioning point of the // mpadded and the positioning point for the following content". MathML2 // doesn't make the distinction. nscoord width = mBoundingMetrics.width; PRInt32 pseudoUnit; // update width pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF) ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit; UpdateValue(mWidthSign, pseudoUnit, mWidth, lspace, mBoundingMetrics, width); // update "height" (this is the ascent in the terminology of the REC) pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF) ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit; UpdateValue(mHeightSign, pseudoUnit, mHeight, lspace, mBoundingMetrics, height); // update "depth" (this is the descent in the terminology of the REC) pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF) ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit; UpdateValue(mDepthSign, pseudoUnit, mDepth, lspace, mBoundingMetrics, depth); // update lspace -- should be *last* because lspace is overwritten!! pseudoUnit = (mLeftSpacePseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF) ? NS_MATHML_PSEUDO_UNIT_LSPACE : mLeftSpacePseudoUnit; UpdateValue(mLeftSpaceSign, pseudoUnit, mLeftSpace, lspace, mBoundingMetrics, lspace); // do the padding now that we have everything // The idea here is to maintain the invariant that ... (i.e., // with no attributes) looks the same as .... But when there are // attributes, tweak our metrics and move children to achieve the desired visual // effects. if (mLeftSpaceSign != NS_MATHML_SIGN_INVALID) { // there was padding on the left // dismiss the left italic correction now (so that our parent won't correct us) mBoundingMetrics.leftBearing = 0; } if (mLeftSpaceSign != NS_MATHML_SIGN_INVALID || mWidthSign != NS_MATHML_SIGN_INVALID) { // there was padding on the right // dismiss the right italic correction now (so that our parent won't correct us) mBoundingMetrics.width = NS_MAX(0, lspace + width); mBoundingMetrics.rightBearing = mBoundingMetrics.width; } nscoord dy = height - mBoundingMetrics.ascent; nscoord dx = lspace; mBoundingMetrics.ascent = height; mBoundingMetrics.descent = depth; aDesiredSize.ascent += dy; aDesiredSize.width = mBoundingMetrics.width; aDesiredSize.height += dy + depth - mBoundingMetrics.descent; aDesiredSize.mBoundingMetrics = mBoundingMetrics; mReference.x = 0; mReference.y = aDesiredSize.ascent; if (aPlaceOrigin) { // Finish reflowing child frames, positioning their origins. PositionRowChildFrames(dx, aDesiredSize.ascent); } return NS_OK; }