Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / layout / mathml / nsMathMLmpaddedFrame.cpp
blob3b103f745faabb8c68135e9ec11c5f9f891ab5c7
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"
14 #include <algorithm>
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;
32 NS_IMETHODIMP
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;
39 return NS_OK;
42 nsresult nsMathMLmpaddedFrame::AttributeChanged(int32_t aNameSpaceID,
43 nsAtom* aAttribute,
44 int32_t aModType) {
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);
69 return NS_OK;
71 return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
72 aModType);
75 void nsMathMLmpaddedFrame::ParseAttribute(nsAtom* aAtom,
76 Attribute& aAttribute) {
77 if (aAttribute.mState != Attribute::ParsingState::Dirty) {
78 return;
80 nsAutoString value;
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
93 aAttribute.Reset();
94 aAttribute.mState = Attribute::ParsingState::Invalid;
96 aString.CompressWhitespace(); // aString is not a const in this code
98 int32_t stringLength = aString.Length();
99 if (!stringLength) {
100 return false;
103 nsAutoString number, unit;
105 //////////////////////
106 // see if the sign is there
108 int32_t i = 0;
110 if (aString[0] == '+') {
111 aAttribute.mSign = Attribute::Sign::Plus;
112 i++;
113 } else if (aString[0] == '-') {
114 aAttribute.mSign = Attribute::Sign::Minus;
115 i++;
116 } else {
117 aAttribute.mSign = Attribute::Sign::Unspecified;
120 // get the number
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
126 return false;
129 if (c == '.') {
130 gotDot = true;
131 } else if (!IsAsciiDigit(c)) {
132 break;
134 number.Append(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()) {
141 return false;
144 nsresult errorCode;
145 float floatValue = number.ToFloat(&errorCode);
146 if (NS_FAILED(errorCode)) {
147 return false;
150 // see if this is a percentage-based value
151 if (i < stringLength && aString[i] == '%') {
152 i++;
153 gotPercent = true;
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()) {
160 if (gotPercent) {
161 // case ["+"|"-"] unsigned-number "%"
162 aAttribute.mValue.SetPercentValue(floatValue / 100.0f);
163 aAttribute.mPseudoUnit = Attribute::PseudoUnit::ItSelf;
164 aAttribute.mState = Attribute::ParsingState::Valid;
165 return true;
166 } else {
167 // case ["+"|"-"] unsigned-number
168 // XXXfredw: should we allow non-zero unitless values? See bug 757703.
169 if (!floatValue) {
170 aAttribute.mValue.SetFloatValue(floatValue, eCSSUnit_Number);
171 aAttribute.mPseudoUnit = Attribute::PseudoUnit::ItSelf;
172 aAttribute.mState = Attribute::ParsingState::Valid;
173 return true;
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;
193 return true;
196 // see if the input was just a CSS value
197 // We are not supposed to have a unitless, percent, negative or namedspace
198 // value here.
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;
204 return true;
208 // if we enter here, we have a number that will act as a multiplier on a
209 // pseudo-unit
210 if (aAttribute.mPseudoUnit != Attribute::PseudoUnit::Unspecified) {
211 if (gotPercent) {
212 aAttribute.mValue.SetPercentValue(floatValue / 100.0f);
213 } else {
214 aAttribute.mValue.SetFloatValue(floatValue, eCSSUnit_Number);
217 aAttribute.mState = Attribute::ParsingState::Valid;
218 return true;
221 #ifdef DEBUG
222 printf("mpadded: attribute with bad numeric value: %s\n",
223 NS_LossyConvertUTF16toASCII(aString).get());
224 #endif
225 // if we reach here, it means we encounter an unexpected input
226 return false;
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();
246 break;
248 case Attribute::PseudoUnit::Height:
249 scaler = aDesiredSize.BlockStartAscent();
250 break;
252 case Attribute::PseudoUnit::Depth:
253 scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
254 break;
256 default:
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");
260 return;
264 if (eCSSUnit_Number == unit) {
265 amount =
266 NSToCoordRound(float(scaler) * aAttribute.mValue.GetFloatValue());
267 } else if (eCSSUnit_Percent == unit) {
268 amount =
269 NSToCoordRound(float(scaler) * aAttribute.mValue.GetPercentValue());
270 } else {
271 amount = CalcLength(PresContext(), mComputedStyle, aAttribute.mValue,
272 aFontSizeInflation);
275 switch (aAttribute.mSign) {
276 case Attribute::Sign::Plus:
277 aValueToUpdate += amount;
278 break;
279 case Attribute::Sign::Minus:
280 aValueToUpdate -= amount;
281 break;
282 case Attribute::Sign::Unspecified:
283 aValueToUpdate = amount;
284 break;
289 /* virtual */
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);
298 if (NS_FAILED(rv)) {
299 DidReflowChildren(PrincipalChildList().FirstChild());
300 return rv;
303 nscoord height = aDesiredSize.BlockStartAscent();
304 nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
305 // The REC says:
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
311 // values."
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)
325 nscoord lspace = 0;
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();
331 nscoord voffset = 0;
333 nscoord initialWidth = width;
334 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
336 // update width
337 ParseAttribute(nsGkAtoms::width, mWidth);
338 UpdateValue(mWidth, Attribute::PseudoUnit::Width, aDesiredSize, width,
339 fontSizeInflation);
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,
345 fontSizeInflation);
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,
351 fontSizeInflation);
352 depth = std::max(0, depth);
354 // update lspace
355 ParseAttribute(nsGkAtoms::lspace_, mLeadingSpace);
356 if (mLeadingSpace.mPseudoUnit != Attribute::PseudoUnit::ItSelf) {
357 UpdateValue(mLeadingSpace, Attribute::PseudoUnit::Unspecified, aDesiredSize,
358 lspace, fontSizeInflation);
361 // update voffset
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,
400 mBoundingMetrics);
402 // Add padding+border.
403 auto borderPadding = GetBorderPaddingForPlace(aFlags);
404 InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
405 mBoundingMetrics);
406 dx += borderPadding.left;
408 mReference.x = 0;
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);
416 return NS_OK;