Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / layout / mathml / nsMathMLmpaddedFrame.cpp
blob07711fefa9dcda53572fa24eb1dd3c2493fc7cce
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 #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;
44 NS_IMETHODIMP
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;
51 return NS_OK;
54 void nsMathMLmpaddedFrame::ProcessAttributes() {
55 // clang-format off
57 parse the attributes
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)
65 // clang-format on
67 nsAutoString value;
69 // width
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());
78 // height
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());
87 // depth
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());
96 // lspace
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());
106 // voffset
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) {
122 aCSSValue.Reset();
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
135 int32_t i = 0;
137 if (aString[0] == '+') {
138 aSign = NS_MATHML_SIGN_PLUS;
139 i++;
140 } else if (aString[0] == '-') {
141 aSign = NS_MATHML_SIGN_MINUS;
142 i++;
143 } else
144 aSign = NS_MATHML_SIGN_UNSPECIFIED;
146 // get the number
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;
153 return false;
156 if (c == '.')
157 gotDot = true;
158 else if (!IsAsciiDigit(c)) {
159 break;
161 number.Append(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;
169 return false;
172 nsresult errorCode;
173 float floatValue = number.ToFloat(&errorCode);
174 if (NS_FAILED(errorCode)) {
175 aSign = NS_MATHML_SIGN_INVALID;
176 return false;
179 // see if this is a percentage-based value
180 if (i < stringLength && aString[i] == '%') {
181 i++;
182 gotPercent = true;
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()) {
189 if (gotPercent) {
190 // case ["+"|"-"] unsigned-number "%"
191 aCSSValue.SetPercentValue(floatValue / 100.0f);
192 aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
193 return true;
194 } else {
195 // case ["+"|"-"] unsigned-number
196 // XXXfredw: should we allow non-zero unitless values? See bug 757703.
197 if (!floatValue) {
198 aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
199 aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
200 return true;
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;
219 return true;
222 // see if the input was just a CSS value
223 // We are not supposed to have a unitless, percent, negative or namedspace
224 // value here.
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,
228 nullptr))
229 return true;
232 // if we enter here, we have a number that will act as a multiplier on a
233 // pseudo-unit
234 if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
235 if (gotPercent)
236 aCSSValue.SetPercentValue(floatValue / 100.0f);
237 else
238 aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
240 return true;
243 #ifdef DEBUG
244 printf("mpadded: attribute with bad numeric value: %s\n",
245 NS_LossyConvertUTF16toASCII(aString).get());
246 #endif
247 // if we reach here, it means we encounter an unexpected input
248 aSign = NS_MATHML_SIGN_INVALID;
249 return false;
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();
265 break;
267 case NS_MATHML_PSEUDO_UNIT_HEIGHT:
268 scaler = aDesiredSize.BlockStartAscent();
269 break;
271 case NS_MATHML_PSEUDO_UNIT_DEPTH:
272 scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
273 break;
275 default:
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");
279 return;
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());
287 else
288 amount = CalcLength(PresContext(), mComputedStyle, aCSSValue,
289 aFontSizeInflation);
291 if (NS_MATHML_SIGN_PLUS == aSign)
292 aValueToUpdate += amount;
293 else if (NS_MATHML_SIGN_MINUS == aSign)
294 aValueToUpdate -= amount;
295 else
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;
307 ProcessAttributes();
309 ///////////////
310 // Let the base class format our content like an inferred mrow
311 nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
312 aStatus);
313 // NS_ASSERTION(aStatus.IsComplete(), "bad status");
316 /* virtual */
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());
322 return rv;
325 nscoord height = aDesiredSize.BlockStartAscent();
326 nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
327 // The REC says:
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
333 // values."
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)
347 nscoord lspace = 0;
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();
353 nscoord voffset = 0;
355 int32_t pseudoUnit;
356 nscoord initialWidth = width;
357 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
359 // update width
360 pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
361 ? NS_MATHML_PSEUDO_UNIT_WIDTH
362 : mWidthPseudoUnit;
363 UpdateValue(mWidthSign, pseudoUnit, mWidth, aDesiredSize, width,
364 fontSizeInflation);
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
370 : mHeightPseudoUnit;
371 UpdateValue(mHeightSign, pseudoUnit, mHeight, aDesiredSize, height,
372 fontSizeInflation);
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
378 : mDepthPseudoUnit;
379 UpdateValue(mDepthSign, pseudoUnit, mDepth, aDesiredSize, depth,
380 fontSizeInflation);
381 depth = std::max(0, depth);
383 // update lspace
384 if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
385 pseudoUnit = mLeadingSpacePseudoUnit;
386 UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace, aDesiredSize,
387 lspace, fontSizeInflation);
390 // update voffset
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;
425 mReference.x = 0;
426 mReference.y = aDesiredSize.BlockStartAscent();
428 if (aPlaceOrigin) {
429 // Finish reflowing child frames, positioning their origins.
430 PositionRowChildFrames(dx, aDesiredSize.BlockStartAscent() - voffset);
433 return NS_OK;
436 /* virtual */
437 nsresult nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget* aDrawTarget,
438 ReflowOutput& aDesiredSize) {
439 ProcessAttributes();
440 return Place(aDrawTarget, false, aDesiredSize);