1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMathMLmpaddedFrame.h"
9 #include "mozilla/dom/MathMLElement.h"
10 #include "mozilla/gfx/2D.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/TextUtils.h"
13 #include "nsLayoutUtils.h"
16 using namespace mozilla
;
19 // <mpadded> -- adjust space around content - implementation
22 #define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there
23 #define NS_MATHML_SIGN_UNSPECIFIED 0
24 #define NS_MATHML_SIGN_MINUS 1
25 #define NS_MATHML_SIGN_PLUS 2
27 #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
28 #define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special
29 #define NS_MATHML_PSEUDO_UNIT_WIDTH 2
30 #define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
31 #define NS_MATHML_PSEUDO_UNIT_DEPTH 4
32 #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5
34 nsIFrame
* NS_NewMathMLmpaddedFrame(PresShell
* aPresShell
,
35 ComputedStyle
* aStyle
) {
36 return new (aPresShell
)
37 nsMathMLmpaddedFrame(aStyle
, aPresShell
->GetPresContext());
40 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame
)
42 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() = default;
45 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame
* aParent
) {
46 // let the base class get the default from our parent
47 nsMathMLContainerFrame::InheritAutomaticData(aParent
);
49 mPresentationData
.flags
|= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY
;
54 void nsMathMLmpaddedFrame::ProcessAttributes() {
59 width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
60 height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
61 depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
62 lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
63 voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
70 mWidthSign
= NS_MATHML_SIGN_INVALID
;
71 mContent
->AsElement()->GetAttr(nsGkAtoms::width
, value
);
72 if (!value
.IsEmpty()) {
73 if (!ParseAttribute(value
, mWidthSign
, mWidth
, mWidthPseudoUnit
)) {
74 ReportParseError(nsGkAtoms::width
->GetUTF16String(), value
.get());
79 mHeightSign
= NS_MATHML_SIGN_INVALID
;
80 mContent
->AsElement()->GetAttr(nsGkAtoms::height
, value
);
81 if (!value
.IsEmpty()) {
82 if (!ParseAttribute(value
, mHeightSign
, mHeight
, mHeightPseudoUnit
)) {
83 ReportParseError(nsGkAtoms::height
->GetUTF16String(), value
.get());
88 mDepthSign
= NS_MATHML_SIGN_INVALID
;
89 mContent
->AsElement()->GetAttr(nsGkAtoms::depth_
, value
);
90 if (!value
.IsEmpty()) {
91 if (!ParseAttribute(value
, mDepthSign
, mDepth
, mDepthPseudoUnit
)) {
92 ReportParseError(nsGkAtoms::depth_
->GetUTF16String(), value
.get());
97 mLeadingSpaceSign
= NS_MATHML_SIGN_INVALID
;
98 mContent
->AsElement()->GetAttr(nsGkAtoms::lspace_
, value
);
99 if (!value
.IsEmpty()) {
100 if (!ParseAttribute(value
, mLeadingSpaceSign
, mLeadingSpace
,
101 mLeadingSpacePseudoUnit
)) {
102 ReportParseError(nsGkAtoms::lspace_
->GetUTF16String(), value
.get());
107 mVerticalOffsetSign
= NS_MATHML_SIGN_INVALID
;
108 mContent
->AsElement()->GetAttr(nsGkAtoms::voffset_
, value
);
109 if (!value
.IsEmpty()) {
110 if (!ParseAttribute(value
, mVerticalOffsetSign
, mVerticalOffset
,
111 mVerticalOffsetPseudoUnit
)) {
112 ReportParseError(nsGkAtoms::voffset_
->GetUTF16String(), value
.get());
117 // parse an input string in the following format (see bug 148326 for testcases):
118 // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
119 bool nsMathMLmpaddedFrame::ParseAttribute(nsString
& aString
, int32_t& aSign
,
120 nsCSSValue
& aCSSValue
,
121 int32_t& aPseudoUnit
) {
123 aSign
= NS_MATHML_SIGN_INVALID
;
124 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_UNSPECIFIED
;
125 aString
.CompressWhitespace(); // aString is not a const in this code
127 int32_t stringLength
= aString
.Length();
128 if (!stringLength
) return false;
130 nsAutoString number
, unit
;
132 //////////////////////
133 // see if the sign is there
137 if (aString
[0] == '+') {
138 aSign
= NS_MATHML_SIGN_PLUS
;
140 } else if (aString
[0] == '-') {
141 aSign
= NS_MATHML_SIGN_MINUS
;
144 aSign
= NS_MATHML_SIGN_UNSPECIFIED
;
147 bool gotDot
= false, gotPercent
= false;
148 for (; i
< stringLength
; i
++) {
149 char16_t c
= aString
[i
];
150 if (gotDot
&& c
== '.') {
151 // error - two dots encountered
152 aSign
= NS_MATHML_SIGN_INVALID
;
158 else if (!IsAsciiDigit(c
)) {
164 // catch error if we didn't enter the loop above... we could simply initialize
165 // floatValue = 1, to cater for cases such as width="height", but that
166 // wouldn't be in line with the spec which requires an explicit number
167 if (number
.IsEmpty()) {
168 aSign
= NS_MATHML_SIGN_INVALID
;
173 float floatValue
= number
.ToFloat(&errorCode
);
174 if (NS_FAILED(errorCode
)) {
175 aSign
= NS_MATHML_SIGN_INVALID
;
179 // see if this is a percentage-based value
180 if (i
< stringLength
&& aString
[i
] == '%') {
185 // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
186 aString
.Right(unit
, stringLength
- i
);
188 if (unit
.IsEmpty()) {
190 // case ["+"|"-"] unsigned-number "%"
191 aCSSValue
.SetPercentValue(floatValue
/ 100.0f
);
192 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_ITSELF
;
195 // case ["+"|"-"] unsigned-number
196 // XXXfredw: should we allow non-zero unitless values? See bug 757703.
198 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_Number
);
199 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_ITSELF
;
203 } else if (unit
.EqualsLiteral("width"))
204 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_WIDTH
;
205 else if (unit
.EqualsLiteral("height"))
206 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_HEIGHT
;
207 else if (unit
.EqualsLiteral("depth"))
208 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_DEPTH
;
209 else if (!gotPercent
) { // percentage can only apply to a pseudo-unit
211 // see if the unit is a named-space
212 if (dom::MathMLElement::ParseNamedSpaceValue(
213 unit
, aCSSValue
, dom::MathMLElement::PARSE_ALLOW_NEGATIVE
,
214 *mContent
->OwnerDoc())) {
215 // re-scale properly, and we know that the unit of the named-space is 'em'
216 floatValue
*= aCSSValue
.GetFloatValue();
217 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_EM
);
218 aPseudoUnit
= NS_MATHML_PSEUDO_UNIT_NAMEDSPACE
;
222 // see if the input was just a CSS value
223 // We are not supposed to have a unitless, percent, negative or namedspace
225 number
.Append(unit
); // leave the sign out if it was there
226 if (dom::MathMLElement::ParseNumericValue(
227 number
, aCSSValue
, dom::MathMLElement::PARSE_SUPPRESS_WARNINGS
,
232 // if we enter here, we have a number that will act as a multiplier on a
234 if (aPseudoUnit
!= NS_MATHML_PSEUDO_UNIT_UNSPECIFIED
) {
236 aCSSValue
.SetPercentValue(floatValue
/ 100.0f
);
238 aCSSValue
.SetFloatValue(floatValue
, eCSSUnit_Number
);
244 printf("mpadded: attribute with bad numeric value: %s\n",
245 NS_LossyConvertUTF16toASCII(aString
).get());
247 // if we reach here, it means we encounter an unexpected input
248 aSign
= NS_MATHML_SIGN_INVALID
;
252 void nsMathMLmpaddedFrame::UpdateValue(int32_t aSign
, int32_t aPseudoUnit
,
253 const nsCSSValue
& aCSSValue
,
254 const ReflowOutput
& aDesiredSize
,
255 nscoord
& aValueToUpdate
,
256 float aFontSizeInflation
) const {
257 nsCSSUnit unit
= aCSSValue
.GetUnit();
258 if (NS_MATHML_SIGN_INVALID
!= aSign
&& eCSSUnit_Null
!= unit
) {
259 nscoord scaler
= 0, amount
= 0;
261 if (eCSSUnit_Percent
== unit
|| eCSSUnit_Number
== unit
) {
262 switch (aPseudoUnit
) {
263 case NS_MATHML_PSEUDO_UNIT_WIDTH
:
264 scaler
= aDesiredSize
.Width();
267 case NS_MATHML_PSEUDO_UNIT_HEIGHT
:
268 scaler
= aDesiredSize
.BlockStartAscent();
271 case NS_MATHML_PSEUDO_UNIT_DEPTH
:
272 scaler
= aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
276 // if we ever reach here, it would mean something is wrong
277 // somewhere with the setup and/or the caller
278 NS_ERROR("Unexpected Pseudo Unit");
283 if (eCSSUnit_Number
== unit
)
284 amount
= NSToCoordRound(float(scaler
) * aCSSValue
.GetFloatValue());
285 else if (eCSSUnit_Percent
== unit
)
286 amount
= NSToCoordRound(float(scaler
) * aCSSValue
.GetPercentValue());
288 amount
= CalcLength(PresContext(), mComputedStyle
, aCSSValue
,
291 if (NS_MATHML_SIGN_PLUS
== aSign
)
292 aValueToUpdate
+= amount
;
293 else if (NS_MATHML_SIGN_MINUS
== aSign
)
294 aValueToUpdate
-= amount
;
296 aValueToUpdate
= amount
;
300 void nsMathMLmpaddedFrame::Reflow(nsPresContext
* aPresContext
,
301 ReflowOutput
& aDesiredSize
,
302 const ReflowInput
& aReflowInput
,
303 nsReflowStatus
& aStatus
) {
304 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
306 mPresentationData
.flags
&= ~NS_MATHML_ERROR
;
310 // Let the base class format our content like an inferred mrow
311 nsMathMLContainerFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
,
313 // NS_ASSERTION(aStatus.IsComplete(), "bad status");
317 nsresult
nsMathMLmpaddedFrame::Place(DrawTarget
* aDrawTarget
, bool aPlaceOrigin
,
318 ReflowOutput
& aDesiredSize
) {
319 nsresult rv
= nsMathMLContainerFrame::Place(aDrawTarget
, false, aDesiredSize
);
320 if (NS_MATHML_HAS_ERROR(mPresentationData
.flags
) || NS_FAILED(rv
)) {
321 DidReflowChildren(PrincipalChildList().FirstChild());
325 nscoord height
= aDesiredSize
.BlockStartAscent();
326 nscoord depth
= aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
329 // "The lspace attribute ('leading' space) specifies the horizontal location
330 // of the positioning point of the child content with respect to the
331 // positioning point of the mpadded element. By default they coincide, and
332 // therefore absolute values for lspace have the same effect as relative
335 // "MathML renderers should ensure that, except for the effects of the
336 // attributes, the relative spacing between the contents of the mpadded
337 // element and surrounding MathML elements would not be modified by replacing
338 // an mpadded element with an mrow element with the same content, even if
339 // linebreaking occurs within the mpadded element."
341 // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
343 // "In those discussions, the terms leading and trailing are used to specify
344 // a side of an object when which side to use depends on the directionality;
345 // ie. leading means left in LTR but right in RTL."
346 // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
348 // In MathML3, "width" will be the bounding box width and "advancewidth" will
349 // refer "to the horizontal distance between the positioning point of the
350 // mpadded and the positioning point for the following content". MathML2
351 // doesn't make the distinction.
352 nscoord width
= aDesiredSize
.Width();
356 nscoord initialWidth
= width
;
357 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(this);
360 pseudoUnit
= (mWidthPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
361 ? NS_MATHML_PSEUDO_UNIT_WIDTH
363 UpdateValue(mWidthSign
, pseudoUnit
, mWidth
, aDesiredSize
, width
,
365 width
= std::max(0, width
);
367 // update "height" (this is the ascent in the terminology of the REC)
368 pseudoUnit
= (mHeightPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
369 ? NS_MATHML_PSEUDO_UNIT_HEIGHT
371 UpdateValue(mHeightSign
, pseudoUnit
, mHeight
, aDesiredSize
, height
,
373 height
= std::max(0, height
);
375 // update "depth" (this is the descent in the terminology of the REC)
376 pseudoUnit
= (mDepthPseudoUnit
== NS_MATHML_PSEUDO_UNIT_ITSELF
)
377 ? NS_MATHML_PSEUDO_UNIT_DEPTH
379 UpdateValue(mDepthSign
, pseudoUnit
, mDepth
, aDesiredSize
, depth
,
381 depth
= std::max(0, depth
);
384 if (mLeadingSpacePseudoUnit
!= NS_MATHML_PSEUDO_UNIT_ITSELF
) {
385 pseudoUnit
= mLeadingSpacePseudoUnit
;
386 UpdateValue(mLeadingSpaceSign
, pseudoUnit
, mLeadingSpace
, aDesiredSize
,
387 lspace
, fontSizeInflation
);
391 if (mVerticalOffsetPseudoUnit
!= NS_MATHML_PSEUDO_UNIT_ITSELF
) {
392 pseudoUnit
= mVerticalOffsetPseudoUnit
;
393 UpdateValue(mVerticalOffsetSign
, pseudoUnit
, mVerticalOffset
, aDesiredSize
,
394 voffset
, fontSizeInflation
);
396 // do the padding now that we have everything
397 // The idea here is to maintain the invariant that <mpadded>...</mpadded>
398 // (i.e., with no attributes) looks the same as <mrow>...</mrow>. But when
399 // there are attributes, tweak our metrics and move children to achieve the
400 // desired visual effects.
402 const bool isRTL
= StyleVisibility()->mDirection
== StyleDirection::Rtl
;
403 if ((isRTL
? mWidthSign
: mLeadingSpaceSign
) != NS_MATHML_SIGN_INVALID
) {
404 // there was padding on the left. dismiss the left italic correction now
405 // (so that our parent won't correct us)
406 mBoundingMetrics
.leftBearing
= 0;
409 if ((isRTL
? mLeadingSpaceSign
: mWidthSign
) != NS_MATHML_SIGN_INVALID
) {
410 // there was padding on the right. dismiss the right italic correction now
411 // (so that our parent won't correct us)
412 mBoundingMetrics
.width
= width
;
413 mBoundingMetrics
.rightBearing
= mBoundingMetrics
.width
;
416 nscoord dx
= (isRTL
? width
- initialWidth
- lspace
: lspace
);
418 aDesiredSize
.SetBlockStartAscent(height
);
419 aDesiredSize
.Width() = mBoundingMetrics
.width
;
420 aDesiredSize
.Height() = depth
+ aDesiredSize
.BlockStartAscent();
421 mBoundingMetrics
.ascent
= height
;
422 mBoundingMetrics
.descent
= depth
;
423 aDesiredSize
.mBoundingMetrics
= mBoundingMetrics
;
426 mReference
.y
= aDesiredSize
.BlockStartAscent();
429 // Finish reflowing child frames, positioning their origins.
430 PositionRowChildFrames(dx
, aDesiredSize
.BlockStartAscent() - voffset
);
437 nsresult
nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget
* aDrawTarget
,
438 ReflowOutput
& aDesiredSize
) {
440 return Place(aDrawTarget
, false, aDesiredSize
);