Back to index

lightning-sunbird  0.9+nobinonly
nsMathMLmpaddedFrame.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is Mozilla MathML Project.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * The University Of Queensland.
00018  * Portions created by the Initial Developer are Copyright (C) 1999
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Roger B. Sidje <rbs@maths.uq.edu.au>
00023  *   David J. Fiddes <D.J.Fiddes@hw.ac.uk>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 
00040 #include "nsCOMPtr.h"
00041 #include "nsCRT.h"  // to get NS_IS_SPACE
00042 #include "nsFrame.h"
00043 #include "nsPresContext.h"
00044 #include "nsUnitConversion.h"
00045 #include "nsStyleContext.h"
00046 #include "nsStyleConsts.h"
00047 #include "nsIRenderingContext.h"
00048 #include "nsIFontMetrics.h"
00049 
00050 #include "nsMathMLmpaddedFrame.h"
00051 
00052 //
00053 // <mpadded> -- adjust space around content - implementation
00054 //
00055 
00056 #define NS_MATHML_SIGN_INVALID           -1 // if the attribute is not there
00057 #define NS_MATHML_SIGN_UNSPECIFIED        0
00058 #define NS_MATHML_SIGN_MINUS              1
00059 #define NS_MATHML_SIGN_PLUS               2
00060 
00061 #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
00062 #define NS_MATHML_PSEUDO_UNIT_ITSELF      1 // special
00063 #define NS_MATHML_PSEUDO_UNIT_WIDTH       2
00064 #define NS_MATHML_PSEUDO_UNIT_HEIGHT      3
00065 #define NS_MATHML_PSEUDO_UNIT_DEPTH       4
00066 #define NS_MATHML_PSEUDO_UNIT_LSPACE      5
00067 #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE  6
00068 
00069 nsresult
00070 NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
00071 {
00072   NS_PRECONDITION(aNewFrame, "null OUT ptr");
00073   if (nsnull == aNewFrame) {
00074     return NS_ERROR_NULL_POINTER;
00075   }
00076   nsMathMLmpaddedFrame* it = new (aPresShell) nsMathMLmpaddedFrame;
00077   if (nsnull == it) {
00078     return NS_ERROR_OUT_OF_MEMORY;
00079   }
00080   *aNewFrame = it;
00081   return NS_OK;
00082 }
00083 
00084 nsMathMLmpaddedFrame::nsMathMLmpaddedFrame()
00085 {
00086 }
00087 
00088 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
00089 {
00090 }
00091 
00092 NS_IMETHODIMP
00093 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent) 
00094 {
00095   // let the base class get the default from our parent
00096   nsMathMLContainerFrame::InheritAutomaticData(aParent);
00097 
00098   mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
00099 
00100   return NS_OK;
00101 }
00102 
00103 void
00104 nsMathMLmpaddedFrame::ProcessAttributes()
00105 {
00106   /*
00107   parse the attributes
00108 
00109   width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
00110   height= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit)
00111   depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit)
00112   lspace= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit)
00113   */
00114 
00115   nsAutoString value;
00116 
00117   /* The REC says:
00118   There is one exceptional element, <mpadded>, whose attributes cannot be 
00119   set with <mstyle>. When the attributes width, height and depth are specified
00120   on an <mstyle> element, they apply only to the <mspace/> element. Similarly, 
00121   when lspace is set with <mstyle>, it applies only to the <mo> element. 
00122   */
00123 
00124   // See if attributes are local, don't access mstyle !
00125 
00126   // width
00127   mWidthSign = NS_MATHML_SIGN_INVALID;
00128   if (NS_CONTENT_ATTR_HAS_VALUE == GetAttribute(mContent, nsnull,
00129                    nsMathMLAtoms::width_, value)) {
00130     ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit);
00131   }
00132 
00133   // height
00134   mHeightSign = NS_MATHML_SIGN_INVALID;
00135   if (NS_CONTENT_ATTR_HAS_VALUE == GetAttribute(mContent, nsnull,
00136                    nsMathMLAtoms::height_, value)) {
00137     ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit);
00138   }
00139 
00140   // depth
00141   mDepthSign = NS_MATHML_SIGN_INVALID;
00142   if (NS_CONTENT_ATTR_HAS_VALUE == GetAttribute(mContent, nsnull,
00143                    nsMathMLAtoms::depth_, value)) {
00144     ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit);
00145   }
00146 
00147   // lspace
00148   mLeftSpaceSign = NS_MATHML_SIGN_INVALID;
00149   if (NS_CONTENT_ATTR_HAS_VALUE == GetAttribute(mContent, nsnull,
00150                    nsMathMLAtoms::lspace_, value)) {
00151     ParseAttribute(value, mLeftSpaceSign, mLeftSpace, mLeftSpacePseudoUnit);
00152   }
00153 }
00154 
00155 // parse an input string in the following format (see bug 148326 for testcases):
00156 // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
00157 PRBool
00158 nsMathMLmpaddedFrame::ParseAttribute(nsString&   aString,
00159                                      PRInt32&    aSign,
00160                                      nsCSSValue& aCSSValue,
00161                                      PRInt32&    aPseudoUnit)
00162 {
00163   aCSSValue.Reset();
00164   aSign = NS_MATHML_SIGN_INVALID;
00165   aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
00166   aString.CompressWhitespace(); // aString is not a const in this code
00167 
00168   PRInt32 stringLength = aString.Length();
00169   if (!stringLength)
00170     return PR_FALSE;
00171 
00172   nsAutoString number, unit;
00173 
00175   // see if the sign is there
00176 
00177   PRInt32 i = 0;
00178 
00179   if (aString[0] == '+') {
00180     aSign = NS_MATHML_SIGN_PLUS;
00181     i++;
00182   }
00183   else if (aString[0] == '-') {
00184     aSign = NS_MATHML_SIGN_MINUS;
00185     i++;
00186   }
00187   else
00188     aSign = NS_MATHML_SIGN_UNSPECIFIED;
00189 
00190   // skip any space after the sign
00191   if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
00192     i++;
00193 
00194   // get the number
00195   PRBool gotDot = PR_FALSE, gotPercent = PR_FALSE;
00196   for (; i < stringLength; i++) {
00197     PRUnichar c = aString[i];
00198     if (gotDot && c == '.') {
00199       // error - two dots encountered
00200       aSign = NS_MATHML_SIGN_INVALID;
00201       return PR_FALSE;
00202     }
00203 
00204     if (c == '.')
00205       gotDot = PR_TRUE;
00206     else if (!nsCRT::IsAsciiDigit(c)) {
00207       break;
00208     }
00209     number.Append(c);
00210   }
00211 
00212   // catch error if we didn't enter the loop above... we could simply initialize
00213   // floatValue = 1, to cater for cases such as width="height", but that wouldn't
00214   // be in line with the spec which requires an explicit number
00215   if (number.IsEmpty()) {
00216 #ifdef NS_DEBUG
00217     printf("mpadded: attribute with bad numeric value: %s\n",
00218             NS_LossyConvertUCS2toASCII(aString).get());
00219 #endif
00220     aSign = NS_MATHML_SIGN_INVALID;
00221     return PR_FALSE;
00222   }
00223 
00224   PRInt32 errorCode;
00225   float floatValue = number.ToFloat(&errorCode);
00226   if (errorCode) {
00227     aSign = NS_MATHML_SIGN_INVALID;
00228     return PR_FALSE;
00229   }
00230 
00231   // skip any space after the number
00232   if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
00233     i++;
00234 
00235   // see if this is a percentage-based value
00236   if (i < stringLength && aString[i] == '%') {
00237     i++;
00238     gotPercent = PR_TRUE;
00239 
00240     // skip any space after the '%' sign
00241     if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
00242       i++;
00243   }
00244 
00245   // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
00246   aString.Right(unit, stringLength - i);
00247 
00248   if (unit.IsEmpty()) {
00249     // also cater for the edge case of "0" for which the unit is optional
00250     if (gotPercent || !floatValue) {
00251       aCSSValue.SetPercentValue(floatValue / 100.0f);
00252       aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
00253       return PR_TRUE;
00254     }
00255     /*
00256     else {
00257       // no explicit CSS unit and no explicit pseudo-unit...
00258       // In this case, the MathML REC suggests taking ems for
00259       // h-unit (width, lspace) or exs for v-unit (height, depth).
00260       // Here, however, we explicitly request authors to specify
00261       // the unit. This is more in line with the CSS REC (and
00262       // it allows keeping the code simpler...)
00263     }
00264     */
00265   }
00266   else if (unit.EqualsLiteral("width"))  aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
00267   else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
00268   else if (unit.EqualsLiteral("depth"))  aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
00269   else if (unit.EqualsLiteral("lspace")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_LSPACE;
00270   else if (!gotPercent) { // percentage can only apply to a pseudo-unit
00271 
00272     // see if the unit is a named-space
00273     // XXX nsnull in ParseNamedSpacedValue()? don't access mstyle?
00274     if (ParseNamedSpaceValue(nsnull, unit, aCSSValue)) {
00275       // re-scale properly, and we know that the unit of the named-space is 'em'
00276       floatValue *= aCSSValue.GetFloatValue();
00277       aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
00278       aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
00279       return PR_TRUE;
00280     }
00281 
00282     // see if the input was just a CSS value
00283     number.Append(unit); // leave the sign out if it was there
00284     if (ParseNumericValue(number, aCSSValue))
00285       return PR_TRUE;
00286   }
00287 
00288   // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
00289   if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
00290     if (gotPercent)
00291       aCSSValue.SetPercentValue(floatValue / 100.0f);
00292     else
00293       aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
00294 
00295     return PR_TRUE;
00296   }
00297 
00298 
00299 #ifdef NS_DEBUG
00300   printf("mpadded: attribute with bad numeric value: %s\n",
00301           NS_LossyConvertUCS2toASCII(aString).get());
00302 #endif
00303   // if we reach here, it means we encounter an unexpected input
00304   aSign = NS_MATHML_SIGN_INVALID;
00305   return PR_FALSE;
00306 }
00307 
00308 void
00309 nsMathMLmpaddedFrame::UpdateValue(nsPresContext*      aPresContext,
00310                                   nsStyleContext*      aStyleContext,
00311                                   PRInt32              aSign,
00312                                   PRInt32              aPseudoUnit,
00313                                   nsCSSValue&          aCSSValue,
00314                                   nscoord              aLeftSpace,
00315                                   nsBoundingMetrics&   aBoundingMetrics,
00316                                   nscoord&             aValueToUpdate)
00317 {
00318   nsCSSUnit unit = aCSSValue.GetUnit();
00319   if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
00320     nscoord scaler = 0, amount = 0;
00321 
00322     if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
00323       switch(aPseudoUnit) {
00324         case NS_MATHML_PSEUDO_UNIT_WIDTH:
00325              scaler = aBoundingMetrics.width;
00326              break;
00327 
00328         case NS_MATHML_PSEUDO_UNIT_HEIGHT:
00329              scaler = aBoundingMetrics.ascent;
00330              break;
00331 
00332         case NS_MATHML_PSEUDO_UNIT_DEPTH:
00333              scaler = aBoundingMetrics.descent;
00334              break;
00335 
00336         case NS_MATHML_PSEUDO_UNIT_LSPACE:
00337              scaler = aLeftSpace;
00338              break;
00339 
00340         default:
00341           // if we ever reach here, it would mean something is wrong 
00342           // somewhere with the setup and/or the caller
00343           NS_ASSERTION(0, "Unexpected Pseudo Unit");
00344           return;
00345       }
00346     }
00347 
00348     if (eCSSUnit_Number == unit)
00349       amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
00350     else if (eCSSUnit_Percent == unit)
00351       amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
00352     else
00353       amount = CalcLength(aPresContext, aStyleContext, aCSSValue);
00354 
00355     nscoord oldValue = aValueToUpdate;
00356     if (NS_MATHML_SIGN_PLUS == aSign)
00357       aValueToUpdate += amount;
00358     else if (NS_MATHML_SIGN_MINUS == aSign)
00359       aValueToUpdate -= amount;
00360     else
00361       aValueToUpdate  = amount;
00362 
00363     /* The REC says:
00364     Dimensions that would be positive if the content was rendered normally
00365     cannot be made negative using <mpadded>; a positive dimension is set 
00366     to 0 if it would otherwise become negative. Dimensions which are 
00367     initially 0 can be made negative
00368     */
00369     if (0 < oldValue && 0 > aValueToUpdate)
00370       aValueToUpdate = 0;
00371   }
00372 }
00373 
00374 NS_IMETHODIMP
00375 nsMathMLmpaddedFrame::Reflow(nsPresContext*          aPresContext,
00376                              nsHTMLReflowMetrics&     aDesiredSize,
00377                              const nsHTMLReflowState& aReflowState,
00378                              nsReflowStatus&          aStatus)
00379 {
00380   ProcessAttributes();
00381 
00383   // Let the base class format our content like an inferred mrow
00384   nsresult rv = nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize,
00385                                                aReflowState, aStatus);
00386   //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
00387   if (NS_FAILED(rv)) return rv;
00388 
00389   // use our overflow area to cache our natural size
00390   aDesiredSize.mOverflowArea = nsRect(0, 0, aDesiredSize.width, aDesiredSize.height);
00391 
00392   nscoord height = mBoundingMetrics.ascent;
00393   nscoord depth  = mBoundingMetrics.descent;
00394   nscoord width  = mBoundingMetrics.width;
00395   nscoord lspace = 0; // XXX it is unclear from the REC what is the default here 
00396 
00397   PRInt32 pseudoUnit;
00398 
00399   // update width
00400   pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
00401              ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit;
00402   UpdateValue(aPresContext, mStyleContext,
00403               mWidthSign, pseudoUnit, mWidth,
00404               lspace, mBoundingMetrics, width);
00405 
00406   // update "height" (this is the ascent in the terminology of the REC)
00407   pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
00408              ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit;
00409   UpdateValue(aPresContext, mStyleContext,
00410               mHeightSign, pseudoUnit, mHeight,
00411               lspace, mBoundingMetrics, height);
00412 
00413   // update "depth" (this is the descent in the terminology of the REC)
00414   pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
00415              ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit;
00416   UpdateValue(aPresContext, mStyleContext,
00417               mDepthSign, pseudoUnit, mDepth,
00418               lspace, mBoundingMetrics, depth);
00419 
00420   // update lspace -- should be *last* because lspace is overwritten!!
00421   pseudoUnit = (mLeftSpacePseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
00422              ? NS_MATHML_PSEUDO_UNIT_LSPACE : mLeftSpacePseudoUnit;
00423   UpdateValue(aPresContext, mStyleContext,
00424               mLeftSpaceSign, pseudoUnit, mLeftSpace,
00425               lspace, mBoundingMetrics, lspace);
00426 
00427   // do the padding now that we have everything
00428   // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
00429   // with no attributes) looks the same as <mrow>...</mrow>. But when there are
00430   // attributes, tweak our metrics and move children to achieve the desired visual
00431   // effects.
00432 
00433   if (mLeftSpaceSign != NS_MATHML_SIGN_INVALID) { // there was padding on the left
00434     // dismiss the left italic correction now (so that our parent won't correct us)
00435     mBoundingMetrics.leftBearing = 0;
00436   }
00437 
00438   if (mLeftSpaceSign != NS_MATHML_SIGN_INVALID ||
00439       mWidthSign != NS_MATHML_SIGN_INVALID) { // there was padding on the right
00440     // dismiss the right italic correction now (so that our parent won't correct us)
00441     mBoundingMetrics.width = PR_MAX(0, lspace + width);
00442     mBoundingMetrics.rightBearing = mBoundingMetrics.width;
00443   }
00444 
00445   nscoord dy = height - mBoundingMetrics.ascent;
00446   nscoord dx = lspace;
00447 
00448   mBoundingMetrics.ascent = height;
00449   mBoundingMetrics.descent = depth;
00450 
00451   aDesiredSize.ascent += dy;
00452   aDesiredSize.descent += depth - mBoundingMetrics.descent;
00453   aDesiredSize.width = mBoundingMetrics.width;
00454   aDesiredSize.height = aDesiredSize.ascent + aDesiredSize.descent;
00455   aDesiredSize.mBoundingMetrics = mBoundingMetrics;
00456 
00457   // combine our tweaked size and our natural size to get our real estate
00458   nsRect rect(0, 0, aDesiredSize.width, aDesiredSize.height);
00459   aDesiredSize.mOverflowArea.MoveTo(dx, dy);
00460   aDesiredSize.mOverflowArea.UnionRect(aDesiredSize.mOverflowArea, rect);
00461 
00462   if (dx || dy) {
00463     nsIFrame* childFrame = mFrames.FirstChild();
00464     while (childFrame) {
00465       childFrame->SetPosition(childFrame->GetPosition() + nsPoint(dx, dy));
00466       childFrame = childFrame->GetNextSibling();
00467     }
00468   }
00469 
00470   mReference.x = 0;
00471   mReference.y = aDesiredSize.ascent;
00472 
00473   FinishAndStoreOverflow(&aDesiredSize);
00474 
00475   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
00476   return NS_OK;
00477 }