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 nsIFrame
* NS_NewMathMLmpaddedFrame(PresShell
* aPresShell
,
23 ComputedStyle
* aStyle
) {
24 return new (aPresShell
)
25 nsMathMLmpaddedFrame(aStyle
, aPresShell
->GetPresContext());
28 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame
)
30 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() = default;
33 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame
* aParent
) {
34 // let the base class get the default from our parent
35 nsMathMLContainerFrame::InheritAutomaticData(aParent
);
37 mPresentationData
.flags
|= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY
;
42 nsresult
nsMathMLmpaddedFrame::AttributeChanged(int32_t aNameSpaceID
,
45 if (aNameSpaceID
== kNameSpaceID_None
) {
46 bool hasDirtyAttributes
= false;
47 IntrinsicDirty intrinsicDirty
= IntrinsicDirty::None
;
48 if (aAttribute
== nsGkAtoms::width
) {
49 mWidth
.mState
= Attribute::ParsingState::Dirty
;
50 hasDirtyAttributes
= true;
51 intrinsicDirty
= IntrinsicDirty::FrameAndAncestors
;
52 } else if (aAttribute
== nsGkAtoms::height
) {
53 mHeight
.mState
= Attribute::ParsingState::Dirty
;
54 hasDirtyAttributes
= true;
55 } else if (aAttribute
== nsGkAtoms::depth_
) {
56 mDepth
.mState
= Attribute::ParsingState::Dirty
;
57 hasDirtyAttributes
= true;
58 } else if (aAttribute
== nsGkAtoms::lspace_
) {
59 mLeadingSpace
.mState
= Attribute::ParsingState::Dirty
;
60 hasDirtyAttributes
= true;
61 intrinsicDirty
= IntrinsicDirty::FrameAndAncestors
;
62 } else if (aAttribute
== nsGkAtoms::voffset_
) {
63 mVerticalOffset
.mState
= Attribute::ParsingState::Dirty
;
64 hasDirtyAttributes
= true;
66 if (hasDirtyAttributes
) {
67 PresShell()->FrameNeedsReflow(this, intrinsicDirty
, NS_FRAME_IS_DIRTY
);
71 return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
75 void nsMathMLmpaddedFrame::ParseAttribute(nsAtom
* aAtom
,
76 Attribute
& aAttribute
) {
77 if (aAttribute
.mState
!= Attribute::ParsingState::Dirty
) {
81 aAttribute
.mState
= Attribute::ParsingState::Invalid
;
82 mContent
->AsElement()->GetAttr(aAtom
, value
);
83 if (!value
.IsEmpty()) {
84 if (!ParseAttribute(value
, aAttribute
)) {
85 ReportParseError(aAtom
->GetUTF16String(), value
.get());
90 bool nsMathMLmpaddedFrame::ParseAttribute(nsString
& aString
,
91 Attribute
& aAttribute
) {
92 // See https://www.w3.org/TR/MathML3/chapter3.html#presm.mpaddedatt
94 aAttribute
.mState
= Attribute::ParsingState::Invalid
;
96 aString
.CompressWhitespace(); // aString is not a const in this code
98 int32_t stringLength
= aString
.Length();
103 nsAutoString number
, unit
;
105 //////////////////////
106 // see if the sign is there
110 if (aString
[0] == '+') {
111 aAttribute
.mSign
= Attribute::Sign::Plus
;
113 } else if (aString
[0] == '-') {
114 aAttribute
.mSign
= Attribute::Sign::Minus
;
117 aAttribute
.mSign
= Attribute::Sign::Unspecified
;
121 bool gotDot
= false, gotPercent
= false;
122 for (; i
< stringLength
; i
++) {
123 char16_t c
= aString
[i
];
124 if (gotDot
&& c
== '.') {
125 // error - two dots encountered
131 } else if (!IsAsciiDigit(c
)) {
137 // catch error if we didn't enter the loop above... we could simply initialize
138 // floatValue = 1, to cater for cases such as width="height", but that
139 // wouldn't be in line with the spec which requires an explicit number
140 if (number
.IsEmpty()) {
145 float floatValue
= number
.ToFloat(&errorCode
);
146 if (NS_FAILED(errorCode
)) {
150 // see if this is a percentage-based value
151 if (i
< stringLength
&& aString
[i
] == '%') {
156 // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
157 aString
.Right(unit
, stringLength
- i
);
159 if (unit
.IsEmpty()) {
161 // case ["+"|"-"] unsigned-number "%"
162 aAttribute
.mValue
.SetPercentValue(floatValue
/ 100.0f
);
163 aAttribute
.mPseudoUnit
= Attribute::PseudoUnit::ItSelf
;
164 aAttribute
.mState
= Attribute::ParsingState::Valid
;
167 // case ["+"|"-"] unsigned-number
168 // XXXfredw: should we allow non-zero unitless values? See bug 757703.
170 aAttribute
.mValue
.SetFloatValue(floatValue
, eCSSUnit_Number
);
171 aAttribute
.mPseudoUnit
= Attribute::PseudoUnit::ItSelf
;
172 aAttribute
.mState
= Attribute::ParsingState::Valid
;
176 } else if (unit
.EqualsLiteral("width")) {
177 aAttribute
.mPseudoUnit
= Attribute::PseudoUnit::Width
;
178 } else if (unit
.EqualsLiteral("height")) {
179 aAttribute
.mPseudoUnit
= Attribute::PseudoUnit::Height
;
180 } else if (unit
.EqualsLiteral("depth")) {
181 aAttribute
.mPseudoUnit
= Attribute::PseudoUnit::Depth
;
182 } else if (!gotPercent
) { // percentage can only apply to a pseudo-unit
184 // see if the unit is a named-space
185 if (dom::MathMLElement::ParseNamedSpaceValue(
186 unit
, aAttribute
.mValue
, dom::MathMLElement::PARSE_ALLOW_NEGATIVE
,
187 *mContent
->OwnerDoc())) {
188 // re-scale properly, and we know that the unit of the named-space is 'em'
189 floatValue
*= aAttribute
.mValue
.GetFloatValue();
190 aAttribute
.mValue
.SetFloatValue(floatValue
, eCSSUnit_EM
);
191 aAttribute
.mPseudoUnit
= Attribute::PseudoUnit::NamedSpace
;
192 aAttribute
.mState
= Attribute::ParsingState::Valid
;
196 // see if the input was just a CSS value
197 // We are not supposed to have a unitless, percent, negative or namedspace
199 number
.Append(unit
); // leave the sign out if it was there
200 if (dom::MathMLElement::ParseNumericValue(
201 number
, aAttribute
.mValue
,
202 dom::MathMLElement::PARSE_SUPPRESS_WARNINGS
, nullptr)) {
203 aAttribute
.mState
= Attribute::ParsingState::Valid
;
208 // if we enter here, we have a number that will act as a multiplier on a
210 if (aAttribute
.mPseudoUnit
!= Attribute::PseudoUnit::Unspecified
) {
212 aAttribute
.mValue
.SetPercentValue(floatValue
/ 100.0f
);
214 aAttribute
.mValue
.SetFloatValue(floatValue
, eCSSUnit_Number
);
217 aAttribute
.mState
= Attribute::ParsingState::Valid
;
222 printf("mpadded: attribute with bad numeric value: %s\n",
223 NS_LossyConvertUTF16toASCII(aString
).get());
225 // if we reach here, it means we encounter an unexpected input
229 void nsMathMLmpaddedFrame::UpdateValue(const Attribute
& aAttribute
,
230 Attribute::PseudoUnit aSelfUnit
,
231 const ReflowOutput
& aDesiredSize
,
232 nscoord
& aValueToUpdate
,
233 float aFontSizeInflation
) const {
234 nsCSSUnit unit
= aAttribute
.mValue
.GetUnit();
235 if (aAttribute
.IsValid() && eCSSUnit_Null
!= unit
) {
236 nscoord scaler
= 0, amount
= 0;
238 if (eCSSUnit_Percent
== unit
|| eCSSUnit_Number
== unit
) {
239 auto pseudoUnit
= aAttribute
.mPseudoUnit
;
240 if (pseudoUnit
== Attribute::PseudoUnit::ItSelf
) {
241 pseudoUnit
= aSelfUnit
;
243 switch (pseudoUnit
) {
244 case Attribute::PseudoUnit::Width
:
245 scaler
= aDesiredSize
.Width();
248 case Attribute::PseudoUnit::Height
:
249 scaler
= aDesiredSize
.BlockStartAscent();
252 case Attribute::PseudoUnit::Depth
:
253 scaler
= aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
257 // if we ever reach here, it would mean something is wrong
258 // somewhere with the setup and/or the caller
259 NS_ERROR("Unexpected Pseudo Unit");
264 if (eCSSUnit_Number
== unit
) {
266 NSToCoordRound(float(scaler
) * aAttribute
.mValue
.GetFloatValue());
267 } else if (eCSSUnit_Percent
== unit
) {
269 NSToCoordRound(float(scaler
) * aAttribute
.mValue
.GetPercentValue());
271 amount
= CalcLength(PresContext(), mComputedStyle
, aAttribute
.mValue
,
275 switch (aAttribute
.mSign
) {
276 case Attribute::Sign::Plus
:
277 aValueToUpdate
+= amount
;
279 case Attribute::Sign::Minus
:
280 aValueToUpdate
-= amount
;
282 case Attribute::Sign::Unspecified
:
283 aValueToUpdate
= amount
;
290 nsresult
nsMathMLmpaddedFrame::Place(DrawTarget
* aDrawTarget
,
291 const PlaceFlags
& aFlags
,
292 ReflowOutput
& aDesiredSize
) {
293 // First perform normal row layout without border/padding.
294 PlaceFlags flags
= aFlags
+ PlaceFlag::MeasureOnly
+
295 PlaceFlag::IgnoreBorderPadding
+
296 PlaceFlag::DoNotAdjustForWidthAndHeight
;
297 nsresult rv
= nsMathMLContainerFrame::Place(aDrawTarget
, flags
, aDesiredSize
);
299 DidReflowChildren(PrincipalChildList().FirstChild());
303 nscoord height
= aDesiredSize
.BlockStartAscent();
304 nscoord depth
= aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
307 // "The lspace attribute ('leading' space) specifies the horizontal location
308 // of the positioning point of the child content with respect to the
309 // positioning point of the mpadded element. By default they coincide, and
310 // therefore absolute values for lspace have the same effect as relative
313 // "MathML renderers should ensure that, except for the effects of the
314 // attributes, the relative spacing between the contents of the mpadded
315 // element and surrounding MathML elements would not be modified by replacing
316 // an mpadded element with an mrow element with the same content, even if
317 // linebreaking occurs within the mpadded element."
319 // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
321 // "In those discussions, the terms leading and trailing are used to specify
322 // a side of an object when which side to use depends on the directionality;
323 // ie. leading means left in LTR but right in RTL."
324 // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
326 // In MathML3, "width" will be the bounding box width and "advancewidth" will
327 // refer "to the horizontal distance between the positioning point of the
328 // mpadded and the positioning point for the following content". MathML2
329 // doesn't make the distinction.
330 nscoord width
= aDesiredSize
.Width();
333 nscoord initialWidth
= width
;
334 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(this);
337 ParseAttribute(nsGkAtoms::width
, mWidth
);
338 UpdateValue(mWidth
, Attribute::PseudoUnit::Width
, aDesiredSize
, width
,
340 width
= std::max(0, width
);
342 // update "height" (this is the ascent in the terminology of the REC)
343 ParseAttribute(nsGkAtoms::height
, mHeight
);
344 UpdateValue(mHeight
, Attribute::PseudoUnit::Height
, aDesiredSize
, height
,
346 height
= std::max(0, height
);
348 // update "depth" (this is the descent in the terminology of the REC)
349 ParseAttribute(nsGkAtoms::depth_
, mDepth
);
350 UpdateValue(mDepth
, Attribute::PseudoUnit::Depth
, aDesiredSize
, depth
,
352 depth
= std::max(0, depth
);
355 ParseAttribute(nsGkAtoms::lspace_
, mLeadingSpace
);
356 if (mLeadingSpace
.mPseudoUnit
!= Attribute::PseudoUnit::ItSelf
) {
357 UpdateValue(mLeadingSpace
, Attribute::PseudoUnit::Unspecified
, aDesiredSize
,
358 lspace
, fontSizeInflation
);
362 ParseAttribute(nsGkAtoms::voffset_
, mVerticalOffset
);
363 if (mVerticalOffset
.mPseudoUnit
!= Attribute::PseudoUnit::ItSelf
) {
364 UpdateValue(mVerticalOffset
, Attribute::PseudoUnit::Unspecified
,
365 aDesiredSize
, voffset
, fontSizeInflation
);
368 // do the padding now that we have everything
369 // The idea here is to maintain the invariant that <mpadded>...</mpadded>
370 // (i.e., with no attributes) looks the same as <mrow>...</mrow>. But when
371 // there are attributes, tweak our metrics and move children to achieve the
372 // desired visual effects.
374 const bool isRTL
= StyleVisibility()->mDirection
== StyleDirection::Rtl
;
375 if (isRTL
? mWidth
.IsValid() : mLeadingSpace
.IsValid()) {
376 // there was padding on the left. dismiss the left italic correction now
377 // (so that our parent won't correct us)
378 mBoundingMetrics
.leftBearing
= 0;
381 if (isRTL
? mLeadingSpace
.IsValid() : mWidth
.IsValid()) {
382 // there was padding on the right. dismiss the right italic correction now
383 // (so that our parent won't correct us)
384 mBoundingMetrics
.width
= width
;
385 mBoundingMetrics
.rightBearing
= mBoundingMetrics
.width
;
388 nscoord dx
= (isRTL
? width
- initialWidth
- lspace
: lspace
);
390 aDesiredSize
.SetBlockStartAscent(height
);
391 aDesiredSize
.Width() = mBoundingMetrics
.width
;
392 aDesiredSize
.Height() = depth
+ aDesiredSize
.BlockStartAscent();
393 mBoundingMetrics
.ascent
= height
;
394 mBoundingMetrics
.descent
= depth
;
395 aDesiredSize
.mBoundingMetrics
= mBoundingMetrics
;
397 // Apply width/height to math content box.
398 auto sizes
= GetWidthAndHeightForPlaceAdjustment(aFlags
);
399 dx
+= ApplyAdjustmentForWidthAndHeight(aFlags
, sizes
, aDesiredSize
,
402 // Add padding+border.
403 auto borderPadding
= GetBorderPaddingForPlace(aFlags
);
404 InflateReflowAndBoundingMetrics(borderPadding
, aDesiredSize
,
406 dx
+= borderPadding
.left
;
409 mReference
.y
= aDesiredSize
.BlockStartAscent();
411 if (!aFlags
.contains(PlaceFlag::MeasureOnly
)) {
412 // Finish reflowing child frames, positioning their origins.
413 PositionRowChildFrames(dx
, aDesiredSize
.BlockStartAscent() - voffset
);