Backed out changeset b88172246b66 due to Win32 debug failures.
[mozilla-central.git] / layout / mathml / nsMathMLmpaddedFrame.cpp
blob2e238d0150ed464c7ad465c40dda20c8723febb0
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla MathML Project.
17 * The Initial Developer of the Original Code is
18 * The University Of Queensland.
19 * Portions created by the Initial Developer are Copyright (C) 1999
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Roger B. Sidje <rbs@maths.uq.edu.au>
24 * David J. Fiddes <D.J.Fiddes@hw.ac.uk>
25 * Karl Tomlinson <karlt+@karlt.net>, Mozilla Corporation
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 #include "nsCOMPtr.h"
43 #include "nsCRT.h" // to get NS_IS_SPACE
44 #include "nsFrame.h"
45 #include "nsPresContext.h"
46 #include "nsStyleContext.h"
47 #include "nsStyleConsts.h"
48 #include "nsIRenderingContext.h"
49 #include "nsIFontMetrics.h"
51 #include "nsMathMLmpaddedFrame.h"
54 // <mpadded> -- adjust space around content - implementation
57 #define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there
58 #define NS_MATHML_SIGN_UNSPECIFIED 0
59 #define NS_MATHML_SIGN_MINUS 1
60 #define NS_MATHML_SIGN_PLUS 2
62 #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
63 #define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special
64 #define NS_MATHML_PSEUDO_UNIT_WIDTH 2
65 #define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
66 #define NS_MATHML_PSEUDO_UNIT_DEPTH 4
67 #define NS_MATHML_PSEUDO_UNIT_LSPACE 5
68 #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 6
70 nsIFrame*
71 NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
73 return new (aPresShell) nsMathMLmpaddedFrame(aContext);
76 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
78 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
82 NS_IMETHODIMP
83 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent)
85 // let the base class get the default from our parent
86 nsMathMLContainerFrame::InheritAutomaticData(aParent);
88 mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
90 return NS_OK;
93 void
94 nsMathMLmpaddedFrame::ProcessAttributes()
97 parse the attributes
99 width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
100 height= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit)
101 depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit)
102 lspace= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit)
105 nsAutoString value;
107 /* The REC says:
108 There is one exceptional element, <mpadded>, whose attributes cannot be
109 set with <mstyle>. When the attributes width, height and depth are specified
110 on an <mstyle> element, they apply only to the <mspace/> element. Similarly,
111 when lspace is set with <mstyle>, it applies only to the <mo> element.
114 // See if attributes are local, don't access mstyle !
116 // width
117 mWidthSign = NS_MATHML_SIGN_INVALID;
118 GetAttribute(mContent, nsnull, nsGkAtoms::width, value);
119 if (!value.IsEmpty()) {
120 ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit);
123 // height
124 mHeightSign = NS_MATHML_SIGN_INVALID;
125 GetAttribute(mContent, nsnull, nsGkAtoms::height, value);
126 if (!value.IsEmpty()) {
127 ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit);
130 // depth
131 mDepthSign = NS_MATHML_SIGN_INVALID;
132 GetAttribute(mContent, nsnull, nsGkAtoms::depth_, value);
133 if (!value.IsEmpty()) {
134 ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit);
137 // lspace
138 mLeftSpaceSign = NS_MATHML_SIGN_INVALID;
139 GetAttribute(mContent, nsnull, nsGkAtoms::lspace_, value);
140 if (!value.IsEmpty()) {
141 ParseAttribute(value, mLeftSpaceSign, mLeftSpace, mLeftSpacePseudoUnit);
145 // parse an input string in the following format (see bug 148326 for testcases):
146 // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
147 PRBool
148 nsMathMLmpaddedFrame::ParseAttribute(nsString& aString,
149 PRInt32& aSign,
150 nsCSSValue& aCSSValue,
151 PRInt32& aPseudoUnit)
153 aCSSValue.Reset();
154 aSign = NS_MATHML_SIGN_INVALID;
155 aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
156 aString.CompressWhitespace(); // aString is not a const in this code
158 PRInt32 stringLength = aString.Length();
159 if (!stringLength)
160 return PR_FALSE;
162 nsAutoString number, unit;
164 //////////////////////
165 // see if the sign is there
167 PRInt32 i = 0;
169 if (aString[0] == '+') {
170 aSign = NS_MATHML_SIGN_PLUS;
171 i++;
173 else if (aString[0] == '-') {
174 aSign = NS_MATHML_SIGN_MINUS;
175 i++;
177 else
178 aSign = NS_MATHML_SIGN_UNSPECIFIED;
180 // skip any space after the sign
181 if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
182 i++;
184 // get the number
185 PRBool gotDot = PR_FALSE, gotPercent = PR_FALSE;
186 for (; i < stringLength; i++) {
187 PRUnichar c = aString[i];
188 if (gotDot && c == '.') {
189 // error - two dots encountered
190 aSign = NS_MATHML_SIGN_INVALID;
191 return PR_FALSE;
194 if (c == '.')
195 gotDot = PR_TRUE;
196 else if (!nsCRT::IsAsciiDigit(c)) {
197 break;
199 number.Append(c);
202 // catch error if we didn't enter the loop above... we could simply initialize
203 // floatValue = 1, to cater for cases such as width="height", but that wouldn't
204 // be in line with the spec which requires an explicit number
205 if (number.IsEmpty()) {
206 #ifdef NS_DEBUG
207 printf("mpadded: attribute with bad numeric value: %s\n",
208 NS_LossyConvertUTF16toASCII(aString).get());
209 #endif
210 aSign = NS_MATHML_SIGN_INVALID;
211 return PR_FALSE;
214 PRInt32 errorCode;
215 float floatValue = number.ToFloat(&errorCode);
216 if (errorCode) {
217 aSign = NS_MATHML_SIGN_INVALID;
218 return PR_FALSE;
221 // skip any space after the number
222 if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
223 i++;
225 // see if this is a percentage-based value
226 if (i < stringLength && aString[i] == '%') {
227 i++;
228 gotPercent = PR_TRUE;
230 // skip any space after the '%' sign
231 if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
232 i++;
235 // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
236 aString.Right(unit, stringLength - i);
238 if (unit.IsEmpty()) {
239 // also cater for the edge case of "0" for which the unit is optional
240 if (gotPercent || !floatValue) {
241 aCSSValue.SetPercentValue(floatValue / 100.0f);
242 aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
243 return PR_TRUE;
246 else {
247 // no explicit CSS unit and no explicit pseudo-unit...
248 // In this case, the MathML REC suggests taking ems for
249 // h-unit (width, lspace) or exs for v-unit (height, depth).
250 // Here, however, we explicitly request authors to specify
251 // the unit. This is more in line with the CSS REC (and
252 // it allows keeping the code simpler...)
256 else if (unit.EqualsLiteral("width")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
257 else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
258 else if (unit.EqualsLiteral("depth")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
259 else if (unit.EqualsLiteral("lspace")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_LSPACE;
260 else if (!gotPercent) { // percentage can only apply to a pseudo-unit
262 // see if the unit is a named-space
263 // XXX nsnull in ParseNamedSpacedValue()? don't access mstyle?
264 if (ParseNamedSpaceValue(nsnull, unit, aCSSValue)) {
265 // re-scale properly, and we know that the unit of the named-space is 'em'
266 floatValue *= aCSSValue.GetFloatValue();
267 aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
268 aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
269 return PR_TRUE;
272 // see if the input was just a CSS value
273 number.Append(unit); // leave the sign out if it was there
274 if (ParseNumericValue(number, aCSSValue))
275 return PR_TRUE;
278 // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
279 if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
280 if (gotPercent)
281 aCSSValue.SetPercentValue(floatValue / 100.0f);
282 else
283 aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
285 return PR_TRUE;
289 #ifdef NS_DEBUG
290 printf("mpadded: attribute with bad numeric value: %s\n",
291 NS_LossyConvertUTF16toASCII(aString).get());
292 #endif
293 // if we reach here, it means we encounter an unexpected input
294 aSign = NS_MATHML_SIGN_INVALID;
295 return PR_FALSE;
298 void
299 nsMathMLmpaddedFrame::UpdateValue(PRInt32 aSign,
300 PRInt32 aPseudoUnit,
301 const nsCSSValue& aCSSValue,
302 nscoord aLeftSpace,
303 const nsBoundingMetrics& aBoundingMetrics,
304 nscoord& aValueToUpdate) const
306 nsCSSUnit unit = aCSSValue.GetUnit();
307 if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
308 nscoord scaler = 0, amount = 0;
310 if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
311 switch(aPseudoUnit) {
312 case NS_MATHML_PSEUDO_UNIT_WIDTH:
313 scaler = aBoundingMetrics.width;
314 break;
316 case NS_MATHML_PSEUDO_UNIT_HEIGHT:
317 scaler = aBoundingMetrics.ascent;
318 break;
320 case NS_MATHML_PSEUDO_UNIT_DEPTH:
321 scaler = aBoundingMetrics.descent;
322 break;
324 case NS_MATHML_PSEUDO_UNIT_LSPACE:
325 scaler = aLeftSpace;
326 break;
328 default:
329 // if we ever reach here, it would mean something is wrong
330 // somewhere with the setup and/or the caller
331 NS_ERROR("Unexpected Pseudo Unit");
332 return;
336 if (eCSSUnit_Number == unit)
337 amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
338 else if (eCSSUnit_Percent == unit)
339 amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
340 else
341 amount = CalcLength(PresContext(), mStyleContext, aCSSValue);
343 nscoord oldValue = aValueToUpdate;
344 if (NS_MATHML_SIGN_PLUS == aSign)
345 aValueToUpdate += amount;
346 else if (NS_MATHML_SIGN_MINUS == aSign)
347 aValueToUpdate -= amount;
348 else
349 aValueToUpdate = amount;
351 /* The REC says:
352 Dimensions that would be positive if the content was rendered normally
353 cannot be made negative using <mpadded>; a positive dimension is set
354 to 0 if it would otherwise become negative. Dimensions which are
355 initially 0 can be made negative
357 if (0 < oldValue && 0 > aValueToUpdate)
358 aValueToUpdate = 0;
362 NS_IMETHODIMP
363 nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
364 nsHTMLReflowMetrics& aDesiredSize,
365 const nsHTMLReflowState& aReflowState,
366 nsReflowStatus& aStatus)
368 ProcessAttributes();
370 ///////////////
371 // Let the base class format our content like an inferred mrow
372 nsresult rv = nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize,
373 aReflowState, aStatus);
374 //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
375 return rv;
378 /* virtual */ nsresult
379 nsMathMLmpaddedFrame::Place(nsIRenderingContext& aRenderingContext,
380 PRBool aPlaceOrigin,
381 nsHTMLReflowMetrics& aDesiredSize)
383 nsresult rv =
384 nsMathMLContainerFrame::Place(aRenderingContext, PR_FALSE, aDesiredSize);
385 if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
386 DidReflowChildren(GetFirstChild(nsnull));
387 return rv;
390 nscoord height = mBoundingMetrics.ascent;
391 nscoord depth = mBoundingMetrics.descent;
392 // In MathML2 (http://www.w3.org/TR/MathML2/chapter3.html#presm.mpadded),
393 // lspace is "the amount of space between the left edge of a bounding box
394 // and the start of the rendering of its contents' bounding box" and the
395 // default is zero.
397 // In MathML3 draft
398 // http://www.w3.org/TR/2007/WD-MathML3-20070427/chapter3.html#id.3.3.6.2,
399 // lspace is "the amount of space between the left edge of the bounding box
400 // and the positioning poin [sic] of the mpadded element" and the default is
401 // "same as content".
403 // In both cases, "MathML renderers should ensure that, except for the
404 // effects of the attributes, relative spacing between the contents of
405 // mpadded and surrounding MathML elements is not modified by replacing an
406 // mpadded element with an mrow element with the same content."
407 nscoord lspace = 0;
408 // In MATHML3, "width" will be the bounding box width and "advancewidth" will
409 // refer "to the horizontal distance between the positioning point of the
410 // mpadded and the positioning point for the following content". MathML2
411 // doesn't make the distinction.
412 nscoord width = mBoundingMetrics.width;
414 PRInt32 pseudoUnit;
416 // update width
417 pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
418 ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit;
419 UpdateValue(mWidthSign, pseudoUnit, mWidth,
420 lspace, mBoundingMetrics, width);
422 // update "height" (this is the ascent in the terminology of the REC)
423 pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
424 ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit;
425 UpdateValue(mHeightSign, pseudoUnit, mHeight,
426 lspace, mBoundingMetrics, height);
428 // update "depth" (this is the descent in the terminology of the REC)
429 pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
430 ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit;
431 UpdateValue(mDepthSign, pseudoUnit, mDepth,
432 lspace, mBoundingMetrics, depth);
434 // update lspace -- should be *last* because lspace is overwritten!!
435 pseudoUnit = (mLeftSpacePseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
436 ? NS_MATHML_PSEUDO_UNIT_LSPACE : mLeftSpacePseudoUnit;
437 UpdateValue(mLeftSpaceSign, pseudoUnit, mLeftSpace,
438 lspace, mBoundingMetrics, lspace);
440 // do the padding now that we have everything
441 // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
442 // with no attributes) looks the same as <mrow>...</mrow>. But when there are
443 // attributes, tweak our metrics and move children to achieve the desired visual
444 // effects.
446 if (mLeftSpaceSign != NS_MATHML_SIGN_INVALID) { // there was padding on the left
447 // dismiss the left italic correction now (so that our parent won't correct us)
448 mBoundingMetrics.leftBearing = 0;
451 if (mLeftSpaceSign != NS_MATHML_SIGN_INVALID ||
452 mWidthSign != NS_MATHML_SIGN_INVALID) { // there was padding on the right
453 // dismiss the right italic correction now (so that our parent won't correct us)
454 mBoundingMetrics.width = NS_MAX(0, lspace + width);
455 mBoundingMetrics.rightBearing = mBoundingMetrics.width;
458 nscoord dy = height - mBoundingMetrics.ascent;
459 nscoord dx = lspace;
461 mBoundingMetrics.ascent = height;
462 mBoundingMetrics.descent = depth;
464 aDesiredSize.ascent += dy;
465 aDesiredSize.width = mBoundingMetrics.width;
466 aDesiredSize.height += dy + depth - mBoundingMetrics.descent;
467 aDesiredSize.mBoundingMetrics = mBoundingMetrics;
469 mReference.x = 0;
470 mReference.y = aDesiredSize.ascent;
472 if (aPlaceOrigin) {
473 // Finish reflowing child frames, positioning their origins.
474 PositionRowChildFrames(dx, aDesiredSize.ascent);
477 return NS_OK;