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
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.
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 ***** */
43 #include "nsCRT.h" // to get NS_IS_SPACE
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
71 NS_NewMathMLmpaddedFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
73 return new (aPresShell
) nsMathMLmpaddedFrame(aContext
);
76 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
81 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame
* aParent
)
83 // let the base class get the default from our parent
84 nsMathMLContainerFrame::InheritAutomaticData(aParent
);
86 mPresentationData
.flags
|= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY
;
92 nsMathMLmpaddedFrame::ProcessAttributes()
97 width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
98 height= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit)
99 depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit)
100 lspace= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit)
106 There is one exceptional element, <mpadded>, whose attributes cannot be
107 set with <mstyle>. When the attributes width, height and depth are specified
108 on an <mstyle> element, they apply only to the <mspace/> element. Similarly,
109 when lspace is set with <mstyle>, it applies only to the <mo> element.
112 // See if attributes are local, don't access mstyle !
115 mWidthSign
= NS_MATHML_SIGN_INVALID
;
116 GetAttribute(mContent
, nsnull
, nsGkAtoms::width
, value
);
117 if (!value
.IsEmpty()) {
118 ParseAttribute(value
, mWidthSign
, mWidth
, mWidthPseudoUnit
);
122 mHeightSign
= NS_MATHML_SIGN_INVALID
;
123 GetAttribute(mContent
, nsnull
, nsGkAtoms::height
, value
);
124 if (!value
.IsEmpty()) {
125 ParseAttribute(value
, mHeightSign
, mHeight
, mHeightPseudoUnit
);
129 mDepthSign
= NS_MATHML_SIGN_INVALID
;
130 GetAttribute(mContent
, nsnull
, nsGkAtoms::depth_
, value
);
131 if (!value
.IsEmpty()) {
132 ParseAttribute(value
, mDepthSign
, mDepth
, mDepthPseudoUnit
);
136 mLeftSpaceSign
= NS_MATHML_SIGN_INVALID
;
137 GetAttribute(mContent
, nsnull
, nsGkAtoms::lspace_
, value
);
138 if (!value
.IsEmpty()) {
139 ParseAttribute(value
, mLeftSpaceSign
, mLeftSpace
, mLeftSpacePseudoUnit
);
143 // parse an input string in the following format (see bug 148326 for testcases):
144 // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
146 nsMathMLmpaddedFrame::ParseAttribute(nsString
& aString
,
148 nsCSSValue
& aCSSValue
,
149 PRInt32
& aPseudoUnit
)
152 aSign
= NS_MATHML_SIGN_INVALID
;
153 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_UNSPECIFIED
;
154 aString
.CompressWhitespace(); // aString is not a const in this code
156 PRInt32 stringLength
= aString
.Length();
160 nsAutoString number
, unit
;
162 //////////////////////
163 // see if the sign is there
167 if (aString
[0] == '+') {
168 aSign
= NS_MATHML_SIGN_PLUS
;
171 else if (aString
[0] == '-') {
172 aSign
= NS_MATHML_SIGN_MINUS
;
176 aSign
= NS_MATHML_SIGN_UNSPECIFIED
;
178 // skip any space after the sign
179 if (i
< stringLength
&& nsCRT::IsAsciiSpace(aString
[i
]))
183 PRBool gotDot
= PR_FALSE
, gotPercent
= PR_FALSE
;
184 for (; i
< stringLength
; i
++) {
185 PRUnichar c
= aString
[i
];
186 if (gotDot
&& c
== '.') {
187 // error - two dots encountered
188 aSign
= NS_MATHML_SIGN_INVALID
;
194 else if (!nsCRT::IsAsciiDigit(c
)) {
200 // catch error if we didn't enter the loop above... we could simply initialize
201 // floatValue = 1, to cater for cases such as width="height", but that wouldn't
202 // be in line with the spec which requires an explicit number
203 if (number
.IsEmpty()) {
205 printf("mpadded: attribute with bad numeric value: %s\n",
206 NS_LossyConvertUTF16toASCII(aString
).get());
208 aSign
= NS_MATHML_SIGN_INVALID
;
213 float floatValue
= number
.ToFloat(&errorCode
);
215 aSign
= NS_MATHML_SIGN_INVALID
;
219 // skip any space after the number
220 if (i
< stringLength
&& nsCRT::IsAsciiSpace(aString
[i
]))
223 // see if this is a percentage-based value
224 if (i
< stringLength
&& aString
[i
] == '%') {
226 gotPercent
= PR_TRUE
;
228 // skip any space after the '%' sign
229 if (i
< stringLength
&& nsCRT::IsAsciiSpace(aString
[i
]))
233 // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
234 aString
.Right(unit
, stringLength
- i
);
236 if (unit
.IsEmpty()) {
237 // also cater for the edge case of "0" for which the unit is optional
238 if (gotPercent
|| !floatValue
) {
239 aCSSValue
.SetPercentValue(floatValue
/ 100.0f
);
240 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_ITSELF
;
245 // no explicit CSS unit and no explicit pseudo-unit...
246 // In this case, the MathML REC suggests taking ems for
247 // h-unit (width, lspace) or exs for v-unit (height, depth).
248 // Here, however, we explicitly request authors to specify
249 // the unit. This is more in line with the CSS REC (and
250 // it allows keeping the code simpler...)
254 else if (unit
.EqualsLiteral("width")) aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_WIDTH
;
255 else if (unit
.EqualsLiteral("height")) aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_HEIGHT
;
256 else if (unit
.EqualsLiteral("depth")) aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_DEPTH
;
257 else if (unit
.EqualsLiteral("lspace")) aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_LSPACE
;
258 else if (!gotPercent
) { // percentage can only apply to a pseudo-unit
260 // see if the unit is a named-space
261 // XXX nsnull in ParseNamedSpacedValue()? don't access mstyle?
262 if (ParseNamedSpaceValue(nsnull
, unit
, aCSSValue
)) {
263 // re-scale properly, and we know that the unit of the named-space is 'em'
264 floatValue
*= aCSSValue
.GetFloatValue();
265 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_EM
);
266 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_NAMEDSPACE
;
270 // see if the input was just a CSS value
271 number
.Append(unit
); // leave the sign out if it was there
272 if (ParseNumericValue(number
, aCSSValue
))
276 // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
277 if (aPseudoUnit
!= NS_MATHML_PSEUDO_UNIT_UNSPECIFIED
) {
279 aCSSValue
.SetPercentValue(floatValue
/ 100.0f
);
281 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_Number
);
288 printf("mpadded: attribute with bad numeric value: %s\n",
289 NS_LossyConvertUTF16toASCII(aString
).get());
291 // if we reach here, it means we encounter an unexpected input
292 aSign
= NS_MATHML_SIGN_INVALID
;
297 nsMathMLmpaddedFrame::UpdateValue(PRInt32 aSign
,
299 const nsCSSValue
& aCSSValue
,
301 const nsBoundingMetrics
& aBoundingMetrics
,
302 nscoord
& aValueToUpdate
) const
304 nsCSSUnit unit
= aCSSValue
.GetUnit();
305 if (NS_MATHML_SIGN_INVALID
!= aSign
&& eCSSUnit_Null
!= unit
) {
306 nscoord scaler
= 0, amount
= 0;
308 if (eCSSUnit_Percent
== unit
|| eCSSUnit_Number
== unit
) {
309 switch(aPseudoUnit
) {
310 case NS_MATHML_PSEUDO_UNIT_WIDTH
:
311 scaler
= aBoundingMetrics
.width
;
314 case NS_MATHML_PSEUDO_UNIT_HEIGHT
:
315 scaler
= aBoundingMetrics
.ascent
;
318 case NS_MATHML_PSEUDO_UNIT_DEPTH
:
319 scaler
= aBoundingMetrics
.descent
;
322 case NS_MATHML_PSEUDO_UNIT_LSPACE
:
327 // if we ever reach here, it would mean something is wrong
328 // somewhere with the setup and/or the caller
329 NS_ASSERTION(0, "Unexpected Pseudo Unit");
334 if (eCSSUnit_Number
== unit
)
335 amount
= NSToCoordRound(float(scaler
) * aCSSValue
.GetFloatValue());
336 else if (eCSSUnit_Percent
== unit
)
337 amount
= NSToCoordRound(float(scaler
) * aCSSValue
.GetPercentValue());
339 amount
= CalcLength(PresContext(), mStyleContext
, aCSSValue
);
341 nscoord oldValue
= aValueToUpdate
;
342 if (NS_MATHML_SIGN_PLUS
== aSign
)
343 aValueToUpdate
+= amount
;
344 else if (NS_MATHML_SIGN_MINUS
== aSign
)
345 aValueToUpdate
-= amount
;
347 aValueToUpdate
= amount
;
350 Dimensions that would be positive if the content was rendered normally
351 cannot be made negative using <mpadded>; a positive dimension is set
352 to 0 if it would otherwise become negative. Dimensions which are
353 initially 0 can be made negative
355 if (0 < oldValue
&& 0 > aValueToUpdate
)
361 nsMathMLmpaddedFrame::Reflow(nsPresContext
* aPresContext
,
362 nsHTMLReflowMetrics
& aDesiredSize
,
363 const nsHTMLReflowState
& aReflowState
,
364 nsReflowStatus
& aStatus
)
369 // Let the base class format our content like an inferred mrow
370 nsresult rv
= nsMathMLContainerFrame::Reflow(aPresContext
, aDesiredSize
,
371 aReflowState
, aStatus
);
372 //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
376 /* virtual */ nsresult
377 nsMathMLmpaddedFrame::Place(nsIRenderingContext
& aRenderingContext
,
379 nsHTMLReflowMetrics
& aDesiredSize
)
382 nsMathMLContainerFrame::Place(aRenderingContext
, PR_FALSE
, aDesiredSize
);
383 if (NS_MATHML_HAS_ERROR(mPresentationData
.flags
) || NS_FAILED(rv
)) {
384 DidReflowChildren(GetFirstChild(nsnull
));
388 nscoord height
= mBoundingMetrics
.ascent
;
389 nscoord depth
= mBoundingMetrics
.descent
;
390 // In MathML2 (http://www.w3.org/TR/MathML2/chapter3.html#presm.mpadded),
391 // lspace is "the amount of space between the left edge of a bounding box
392 // and the start of the rendering of its contents' bounding box" and the
396 // http://www.w3.org/TR/2007/WD-MathML3-20070427/chapter3.html#id.3.3.6.2,
397 // lspace is "the amount of space between the left edge of the bounding box
398 // and the positioning poin [sic] of the mpadded element" and the default is
399 // "same as content".
401 // In both cases, "MathML renderers should ensure that, except for the
402 // effects of the attributes, relative spacing between the contents of
403 // mpadded and surrounding MathML elements is not modified by replacing an
404 // mpadded element with an mrow element with the same content."
406 // In MATHML3, "width" will be the bounding box width and "advancewidth" will
407 // refer "to the horizontal distance between the positioning point of the
408 // mpadded and the positioning point for the following content". MathML2
409 // doesn't make the distinction.
410 nscoord width
= mBoundingMetrics
.width
;
415 pseudoUnit
= (mWidthPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
416 ? NS_MATHML_PSEUDO_UNIT_WIDTH
: mWidthPseudoUnit
;
417 UpdateValue(mWidthSign
, pseudoUnit
, mWidth
,
418 lspace
, mBoundingMetrics
, width
);
420 // update "height" (this is the ascent in the terminology of the REC)
421 pseudoUnit
= (mHeightPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
422 ? NS_MATHML_PSEUDO_UNIT_HEIGHT
: mHeightPseudoUnit
;
423 UpdateValue(mHeightSign
, pseudoUnit
, mHeight
,
424 lspace
, mBoundingMetrics
, height
);
426 // update "depth" (this is the descent in the terminology of the REC)
427 pseudoUnit
= (mDepthPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
428 ? NS_MATHML_PSEUDO_UNIT_DEPTH
: mDepthPseudoUnit
;
429 UpdateValue(mDepthSign
, pseudoUnit
, mDepth
,
430 lspace
, mBoundingMetrics
, depth
);
432 // update lspace -- should be *last* because lspace is overwritten!!
433 pseudoUnit
= (mLeftSpacePseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
434 ? NS_MATHML_PSEUDO_UNIT_LSPACE
: mLeftSpacePseudoUnit
;
435 UpdateValue(mLeftSpaceSign
, pseudoUnit
, mLeftSpace
,
436 lspace
, mBoundingMetrics
, lspace
);
438 // do the padding now that we have everything
439 // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
440 // with no attributes) looks the same as <mrow>...</mrow>. But when there are
441 // attributes, tweak our metrics and move children to achieve the desired visual
444 if (mLeftSpaceSign
!= NS_MATHML_SIGN_INVALID
) { // there was padding on the left
445 // dismiss the left italic correction now (so that our parent won't correct us)
446 mBoundingMetrics
.leftBearing
= 0;
449 if (mLeftSpaceSign
!= NS_MATHML_SIGN_INVALID
||
450 mWidthSign
!= NS_MATHML_SIGN_INVALID
) { // there was padding on the right
451 // dismiss the right italic correction now (so that our parent won't correct us)
452 mBoundingMetrics
.width
= PR_MAX(0, lspace
+ width
);
453 mBoundingMetrics
.rightBearing
= mBoundingMetrics
.width
;
456 nscoord dy
= height
- mBoundingMetrics
.ascent
;
459 mBoundingMetrics
.ascent
= height
;
460 mBoundingMetrics
.descent
= depth
;
462 aDesiredSize
.ascent
+= dy
;
463 aDesiredSize
.width
= mBoundingMetrics
.width
;
464 aDesiredSize
.height
+= dy
+ depth
- mBoundingMetrics
.descent
;
465 aDesiredSize
.mBoundingMetrics
= mBoundingMetrics
;
468 mReference
.y
= aDesiredSize
.ascent
;
471 // Finish reflowing child frames, positioning their origins.
472 PositionRowChildFrames(dx
, aDesiredSize
.ascent
);