1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMathMLmpaddedFrame.h"
8 #include "nsMathMLElement.h"
9 #include "mozilla/gfx/2D.h"
13 // <mpadded> -- adjust space around content - implementation
16 #define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there
17 #define NS_MATHML_SIGN_UNSPECIFIED 0
18 #define NS_MATHML_SIGN_MINUS 1
19 #define NS_MATHML_SIGN_PLUS 2
21 #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
22 #define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special
23 #define NS_MATHML_PSEUDO_UNIT_WIDTH 2
24 #define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
25 #define NS_MATHML_PSEUDO_UNIT_DEPTH 4
26 #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5
29 NS_NewMathMLmpaddedFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
31 return new (aPresShell
) nsMathMLmpaddedFrame(aContext
);
34 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame
)
36 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
41 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame
* aParent
)
43 // let the base class get the default from our parent
44 nsMathMLContainerFrame::InheritAutomaticData(aParent
);
46 mPresentationData
.flags
|= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY
;
52 nsMathMLmpaddedFrame::ProcessAttributes()
57 width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
58 height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
59 depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
60 lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
61 voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
67 mWidthSign
= NS_MATHML_SIGN_INVALID
;
68 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::width
, value
);
69 if (!value
.IsEmpty()) {
70 if (!ParseAttribute(value
, mWidthSign
, mWidth
, mWidthPseudoUnit
)) {
71 ReportParseError(nsGkAtoms::width
->GetUTF16String(), value
.get());
76 mHeightSign
= NS_MATHML_SIGN_INVALID
;
77 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::height
, value
);
78 if (!value
.IsEmpty()) {
79 if (!ParseAttribute(value
, mHeightSign
, mHeight
, mHeightPseudoUnit
)) {
80 ReportParseError(nsGkAtoms::height
->GetUTF16String(), value
.get());
85 mDepthSign
= NS_MATHML_SIGN_INVALID
;
86 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::depth_
, value
);
87 if (!value
.IsEmpty()) {
88 if (!ParseAttribute(value
, mDepthSign
, mDepth
, mDepthPseudoUnit
)) {
89 ReportParseError(nsGkAtoms::depth_
->GetUTF16String(), value
.get());
94 mLeadingSpaceSign
= NS_MATHML_SIGN_INVALID
;
95 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::lspace_
, value
);
96 if (!value
.IsEmpty()) {
97 if (!ParseAttribute(value
, mLeadingSpaceSign
, mLeadingSpace
,
98 mLeadingSpacePseudoUnit
)) {
99 ReportParseError(nsGkAtoms::lspace_
->GetUTF16String(), value
.get());
104 mVerticalOffsetSign
= NS_MATHML_SIGN_INVALID
;
105 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::voffset_
, value
);
106 if (!value
.IsEmpty()) {
107 if (!ParseAttribute(value
, mVerticalOffsetSign
, mVerticalOffset
,
108 mVerticalOffsetPseudoUnit
)) {
109 ReportParseError(nsGkAtoms::voffset_
->GetUTF16String(), value
.get());
115 // parse an input string in the following format (see bug 148326 for testcases):
116 // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
118 nsMathMLmpaddedFrame::ParseAttribute(nsString
& aString
,
120 nsCSSValue
& aCSSValue
,
121 int32_t& aPseudoUnit
)
124 aSign
= NS_MATHML_SIGN_INVALID
;
125 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_UNSPECIFIED
;
126 aString
.CompressWhitespace(); // aString is not a const in this code
128 int32_t stringLength
= aString
.Length();
132 nsAutoString number
, unit
;
134 //////////////////////
135 // see if the sign is there
139 if (aString
[0] == '+') {
140 aSign
= NS_MATHML_SIGN_PLUS
;
143 else if (aString
[0] == '-') {
144 aSign
= NS_MATHML_SIGN_MINUS
;
148 aSign
= NS_MATHML_SIGN_UNSPECIFIED
;
151 bool gotDot
= false, gotPercent
= false;
152 for (; i
< stringLength
; i
++) {
153 char16_t c
= aString
[i
];
154 if (gotDot
&& c
== '.') {
155 // error - two dots encountered
156 aSign
= NS_MATHML_SIGN_INVALID
;
162 else if (!nsCRT::IsAsciiDigit(c
)) {
168 // catch error if we didn't enter the loop above... we could simply initialize
169 // floatValue = 1, to cater for cases such as width="height", but that wouldn't
170 // be in line with the spec which requires an explicit number
171 if (number
.IsEmpty()) {
172 aSign
= NS_MATHML_SIGN_INVALID
;
177 float floatValue
= number
.ToFloat(&errorCode
);
178 if (NS_FAILED(errorCode
)) {
179 aSign
= NS_MATHML_SIGN_INVALID
;
183 // see if this is a percentage-based value
184 if (i
< stringLength
&& aString
[i
] == '%') {
189 // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
190 aString
.Right(unit
, stringLength
- i
);
192 if (unit
.IsEmpty()) {
194 // case ["+"|"-"] unsigned-number "%"
195 aCSSValue
.SetPercentValue(floatValue
/ 100.0f
);
196 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_ITSELF
;
199 // case ["+"|"-"] unsigned-number
200 // XXXfredw: should we allow non-zero unitless values? See bug 757703.
202 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_Number
);
203 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_ITSELF
;
208 else if (unit
.EqualsLiteral("width")) aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_WIDTH
;
209 else if (unit
.EqualsLiteral("height")) aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_HEIGHT
;
210 else if (unit
.EqualsLiteral("depth")) aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_DEPTH
;
211 else if (!gotPercent
) { // percentage can only apply to a pseudo-unit
213 // see if the unit is a named-space
214 if (nsMathMLElement::ParseNamedSpaceValue(unit
, aCSSValue
,
216 PARSE_ALLOW_NEGATIVE
)) {
217 // re-scale properly, and we know that the unit of the named-space is 'em'
218 floatValue
*= aCSSValue
.GetFloatValue();
219 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_EM
);
220 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_NAMEDSPACE
;
224 // see if the input was just a CSS value
225 // We are not supposed to have a unitless, percent, negative or namedspace
227 number
.Append(unit
); // leave the sign out if it was there
228 if (nsMathMLElement::ParseNumericValue(number
, aCSSValue
,
230 PARSE_SUPPRESS_WARNINGS
, nullptr))
234 // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
235 if (aPseudoUnit
!= NS_MATHML_PSEUDO_UNIT_UNSPECIFIED
) {
237 aCSSValue
.SetPercentValue(floatValue
/ 100.0f
);
239 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_Number
);
246 printf("mpadded: attribute with bad numeric value: %s\n",
247 NS_LossyConvertUTF16toASCII(aString
).get());
249 // if we reach here, it means we encounter an unexpected input
250 aSign
= NS_MATHML_SIGN_INVALID
;
255 nsMathMLmpaddedFrame::UpdateValue(int32_t aSign
,
257 const nsCSSValue
& aCSSValue
,
258 const nsHTMLReflowMetrics
& aDesiredSize
,
259 nscoord
& aValueToUpdate
,
260 float aFontSizeInflation
) const
262 nsCSSUnit unit
= aCSSValue
.GetUnit();
263 if (NS_MATHML_SIGN_INVALID
!= aSign
&& eCSSUnit_Null
!= unit
) {
264 nscoord scaler
= 0, amount
= 0;
266 if (eCSSUnit_Percent
== unit
|| eCSSUnit_Number
== unit
) {
267 switch(aPseudoUnit
) {
268 case NS_MATHML_PSEUDO_UNIT_WIDTH
:
269 scaler
= aDesiredSize
.Width();
272 case NS_MATHML_PSEUDO_UNIT_HEIGHT
:
273 scaler
= aDesiredSize
.BlockStartAscent();
276 case NS_MATHML_PSEUDO_UNIT_DEPTH
:
277 scaler
= aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
281 // if we ever reach here, it would mean something is wrong
282 // somewhere with the setup and/or the caller
283 NS_ERROR("Unexpected Pseudo Unit");
288 if (eCSSUnit_Number
== unit
)
289 amount
= NSToCoordRound(float(scaler
) * aCSSValue
.GetFloatValue());
290 else if (eCSSUnit_Percent
== unit
)
291 amount
= NSToCoordRound(float(scaler
) * aCSSValue
.GetPercentValue());
293 amount
= CalcLength(PresContext(), mStyleContext
, aCSSValue
,
296 if (NS_MATHML_SIGN_PLUS
== aSign
)
297 aValueToUpdate
+= amount
;
298 else if (NS_MATHML_SIGN_MINUS
== aSign
)
299 aValueToUpdate
-= amount
;
301 aValueToUpdate
= amount
;
306 nsMathMLmpaddedFrame::Reflow(nsPresContext
* aPresContext
,
307 nsHTMLReflowMetrics
& aDesiredSize
,
308 const nsHTMLReflowState
& aReflowState
,
309 nsReflowStatus
& aStatus
)
311 mPresentationData
.flags
&= ~NS_MATHML_ERROR
;
315 // Let the base class format our content like an inferred mrow
316 nsMathMLContainerFrame::Reflow(aPresContext
, aDesiredSize
,
317 aReflowState
, aStatus
);
318 //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
321 /* virtual */ nsresult
322 nsMathMLmpaddedFrame::Place(DrawTarget
* aDrawTarget
,
324 nsHTMLReflowMetrics
& aDesiredSize
)
327 nsMathMLContainerFrame::Place(aDrawTarget
, false, aDesiredSize
);
328 if (NS_MATHML_HAS_ERROR(mPresentationData
.flags
) || NS_FAILED(rv
)) {
329 DidReflowChildren(GetFirstPrincipalChild());
333 nscoord height
= aDesiredSize
.BlockStartAscent();
334 nscoord depth
= aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
337 // "The lspace attribute ('leading' space) specifies the horizontal location
338 // of the positioning point of the child content with respect to the
339 // positioning point of the mpadded element. By default they coincide, and
340 // therefore absolute values for lspace have the same effect as relative
343 // "MathML renderers should ensure that, except for the effects of the
344 // attributes, the relative spacing between the contents of the mpadded
345 // element and surrounding MathML elements would not be modified by replacing
346 // an mpadded element with an mrow element with the same content, even if
347 // linebreaking occurs within the mpadded element."
349 // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
351 // "In those discussions, the terms leading and trailing are used to specify
352 // a side of an object when which side to use depends on the directionality;
353 // ie. leading means left in LTR but right in RTL."
354 // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
356 // In MathML3, "width" will be the bounding box width and "advancewidth" will
357 // refer "to the horizontal distance between the positioning point of the
358 // mpadded and the positioning point for the following content". MathML2
359 // doesn't make the distinction.
360 nscoord width
= aDesiredSize
.Width();
364 nscoord initialWidth
= width
;
365 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(this);
368 pseudoUnit
= (mWidthPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
369 ? NS_MATHML_PSEUDO_UNIT_WIDTH
: mWidthPseudoUnit
;
370 UpdateValue(mWidthSign
, pseudoUnit
, mWidth
,
371 aDesiredSize
, width
, fontSizeInflation
);
372 width
= std::max(0, width
);
374 // update "height" (this is the ascent in the terminology of the REC)
375 pseudoUnit
= (mHeightPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
376 ? NS_MATHML_PSEUDO_UNIT_HEIGHT
: mHeightPseudoUnit
;
377 UpdateValue(mHeightSign
, pseudoUnit
, mHeight
,
378 aDesiredSize
, height
, fontSizeInflation
);
379 height
= std::max(0, height
);
381 // update "depth" (this is the descent in the terminology of the REC)
382 pseudoUnit
= (mDepthPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
383 ? NS_MATHML_PSEUDO_UNIT_DEPTH
: mDepthPseudoUnit
;
384 UpdateValue(mDepthSign
, pseudoUnit
, mDepth
,
385 aDesiredSize
, depth
, fontSizeInflation
);
386 depth
= std::max(0, depth
);
389 if (mLeadingSpacePseudoUnit
!= NS_MATHML_PSEUDO_UNIT_ITSELF
) {
390 pseudoUnit
= mLeadingSpacePseudoUnit
;
391 UpdateValue(mLeadingSpaceSign
, pseudoUnit
, mLeadingSpace
,
392 aDesiredSize
, lspace
, fontSizeInflation
);
396 if (mVerticalOffsetPseudoUnit
!= NS_MATHML_PSEUDO_UNIT_ITSELF
) {
397 pseudoUnit
= mVerticalOffsetPseudoUnit
;
398 UpdateValue(mVerticalOffsetSign
, pseudoUnit
, mVerticalOffset
,
399 aDesiredSize
, voffset
, fontSizeInflation
);
401 // do the padding now that we have everything
402 // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
403 // with no attributes) looks the same as <mrow>...</mrow>. But when there are
404 // attributes, tweak our metrics and move children to achieve the desired visual
407 if ((StyleVisibility()->mDirection
?
408 mWidthSign
: mLeadingSpaceSign
) != NS_MATHML_SIGN_INVALID
) {
409 // there was padding on the left. dismiss the left italic correction now
410 // (so that our parent won't correct us)
411 mBoundingMetrics
.leftBearing
= 0;
414 if ((StyleVisibility()->mDirection
?
415 mLeadingSpaceSign
: mWidthSign
) != NS_MATHML_SIGN_INVALID
) {
416 // there was padding on the right. dismiss the right italic correction now
417 // (so that our parent won't correct us)
418 mBoundingMetrics
.width
= width
;
419 mBoundingMetrics
.rightBearing
= mBoundingMetrics
.width
;
422 nscoord dx
= (StyleVisibility()->mDirection
?
423 width
- initialWidth
- lspace
: lspace
);
425 aDesiredSize
.SetBlockStartAscent(height
);
426 aDesiredSize
.Width() = mBoundingMetrics
.width
;
427 aDesiredSize
.Height() = depth
+ aDesiredSize
.BlockStartAscent();
428 mBoundingMetrics
.ascent
= height
;
429 mBoundingMetrics
.descent
= depth
;
430 aDesiredSize
.mBoundingMetrics
= mBoundingMetrics
;
433 mReference
.y
= aDesiredSize
.BlockStartAscent();
436 // Finish reflowing child frames, positioning their origins.
437 PositionRowChildFrames(dx
, aDesiredSize
.BlockStartAscent() - voffset
);
443 /* virtual */ nsresult
444 nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget
* aDrawTarget
,
445 nsHTMLReflowMetrics
& aDesiredSize
)
448 return Place(aDrawTarget
, false, aDesiredSize
);