Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
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"
10 #include <algorithm>
12 //
13 // <mpadded> -- adjust space around content - implementation
14 //
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
28 nsIFrame*
29 NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
30 {
31 return new (aPresShell) nsMathMLmpaddedFrame(aContext);
32 }
34 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
36 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
37 {
38 }
40 NS_IMETHODIMP
41 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent)
42 {
43 // let the base class get the default from our parent
44 nsMathMLContainerFrame::InheritAutomaticData(aParent);
46 mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
48 return NS_OK;
49 }
51 void
52 nsMathMLmpaddedFrame::ProcessAttributes()
53 {
54 /*
55 parse the attributes
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)
62 */
64 nsAutoString value;
66 // width
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());
72 }
73 }
75 // height
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());
81 }
82 }
84 // depth
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());
90 }
91 }
93 // lspace
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());
100 }
101 }
103 // voffset
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());
110 }
111 }
113 }
115 // parse an input string in the following format (see bug 148326 for testcases):
116 // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
117 bool
118 nsMathMLmpaddedFrame::ParseAttribute(nsString& aString,
119 int32_t& aSign,
120 nsCSSValue& aCSSValue,
121 int32_t& aPseudoUnit)
122 {
123 aCSSValue.Reset();
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();
129 if (!stringLength)
130 return false;
132 nsAutoString number, unit;
134 //////////////////////
135 // see if the sign is there
137 int32_t i = 0;
139 if (aString[0] == '+') {
140 aSign = NS_MATHML_SIGN_PLUS;
141 i++;
142 }
143 else if (aString[0] == '-') {
144 aSign = NS_MATHML_SIGN_MINUS;
145 i++;
146 }
147 else
148 aSign = NS_MATHML_SIGN_UNSPECIFIED;
150 // get the number
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;
157 return false;
158 }
160 if (c == '.')
161 gotDot = true;
162 else if (!nsCRT::IsAsciiDigit(c)) {
163 break;
164 }
165 number.Append(c);
166 }
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;
173 return false;
174 }
176 nsresult errorCode;
177 float floatValue = number.ToFloat(&errorCode);
178 if (NS_FAILED(errorCode)) {
179 aSign = NS_MATHML_SIGN_INVALID;
180 return false;
181 }
183 // see if this is a percentage-based value
184 if (i < stringLength && aString[i] == '%') {
185 i++;
186 gotPercent = true;
187 }
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()) {
193 if (gotPercent) {
194 // case ["+"|"-"] unsigned-number "%"
195 aCSSValue.SetPercentValue(floatValue / 100.0f);
196 aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
197 return true;
198 } else {
199 // case ["+"|"-"] unsigned-number
200 // XXXfredw: should we allow non-zero unitless values? See bug 757703.
201 if (!floatValue) {
202 aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
203 aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
204 return true;
205 }
206 }
207 }
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,
215 nsMathMLElement::
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;
221 return true;
222 }
224 // see if the input was just a CSS value
225 // We are not supposed to have a unitless, percent, negative or namedspace
226 // value here.
227 number.Append(unit); // leave the sign out if it was there
228 if (nsMathMLElement::ParseNumericValue(number, aCSSValue,
229 nsMathMLElement::
230 PARSE_SUPPRESS_WARNINGS, nullptr))
231 return true;
232 }
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) {
236 if (gotPercent)
237 aCSSValue.SetPercentValue(floatValue / 100.0f);
238 else
239 aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
241 return true;
242 }
245 #ifdef DEBUG
246 printf("mpadded: attribute with bad numeric value: %s\n",
247 NS_LossyConvertUTF16toASCII(aString).get());
248 #endif
249 // if we reach here, it means we encounter an unexpected input
250 aSign = NS_MATHML_SIGN_INVALID;
251 return false;
252 }
254 void
255 nsMathMLmpaddedFrame::UpdateValue(int32_t aSign,
256 int32_t aPseudoUnit,
257 const nsCSSValue& aCSSValue,
258 const nsHTMLReflowMetrics& aDesiredSize,
259 nscoord& aValueToUpdate) const
260 {
261 nsCSSUnit unit = aCSSValue.GetUnit();
262 if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
263 nscoord scaler = 0, amount = 0;
265 if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
266 switch(aPseudoUnit) {
267 case NS_MATHML_PSEUDO_UNIT_WIDTH:
268 scaler = aDesiredSize.Width();
269 break;
271 case NS_MATHML_PSEUDO_UNIT_HEIGHT:
272 scaler = aDesiredSize.TopAscent();
273 break;
275 case NS_MATHML_PSEUDO_UNIT_DEPTH:
276 scaler = aDesiredSize.Height() - aDesiredSize.TopAscent();
277 break;
279 default:
280 // if we ever reach here, it would mean something is wrong
281 // somewhere with the setup and/or the caller
282 NS_ERROR("Unexpected Pseudo Unit");
283 return;
284 }
285 }
287 if (eCSSUnit_Number == unit)
288 amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
289 else if (eCSSUnit_Percent == unit)
290 amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
291 else
292 amount = CalcLength(PresContext(), mStyleContext, aCSSValue);
294 if (NS_MATHML_SIGN_PLUS == aSign)
295 aValueToUpdate += amount;
296 else if (NS_MATHML_SIGN_MINUS == aSign)
297 aValueToUpdate -= amount;
298 else
299 aValueToUpdate = amount;
300 }
301 }
303 nsresult
304 nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
305 nsHTMLReflowMetrics& aDesiredSize,
306 const nsHTMLReflowState& aReflowState,
307 nsReflowStatus& aStatus)
308 {
309 ProcessAttributes();
311 ///////////////
312 // Let the base class format our content like an inferred mrow
313 nsresult rv = nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize,
314 aReflowState, aStatus);
315 //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
316 return rv;
317 }
319 /* virtual */ nsresult
320 nsMathMLmpaddedFrame::Place(nsRenderingContext& aRenderingContext,
321 bool aPlaceOrigin,
322 nsHTMLReflowMetrics& aDesiredSize)
323 {
324 nsresult rv =
325 nsMathMLContainerFrame::Place(aRenderingContext, false, aDesiredSize);
326 if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
327 DidReflowChildren(GetFirstPrincipalChild());
328 return rv;
329 }
331 nscoord height = aDesiredSize.TopAscent();
332 nscoord depth = aDesiredSize.Height() - aDesiredSize.TopAscent();
333 // The REC says:
334 //
335 // "The lspace attribute ('leading' space) specifies the horizontal location
336 // of the positioning point of the child content with respect to the
337 // positioning point of the mpadded element. By default they coincide, and
338 // therefore absolute values for lspace have the same effect as relative
339 // values."
340 //
341 // "MathML renderers should ensure that, except for the effects of the
342 // attributes, the relative spacing between the contents of the mpadded
343 // element and surrounding MathML elements would not be modified by replacing
344 // an mpadded element with an mrow element with the same content, even if
345 // linebreaking occurs within the mpadded element."
346 //
347 // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
348 //
349 // "In those discussions, the terms leading and trailing are used to specify
350 // a side of an object when which side to use depends on the directionality;
351 // ie. leading means left in LTR but right in RTL."
352 // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
353 nscoord lspace = 0;
354 // In MathML3, "width" will be the bounding box width and "advancewidth" will
355 // refer "to the horizontal distance between the positioning point of the
356 // mpadded and the positioning point for the following content". MathML2
357 // doesn't make the distinction.
358 nscoord width = aDesiredSize.Width();
359 nscoord voffset = 0;
361 int32_t pseudoUnit;
362 nscoord initialWidth = width;
364 // update width
365 pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
366 ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit;
367 UpdateValue(mWidthSign, pseudoUnit, mWidth,
368 aDesiredSize, width);
369 width = std::max(0, width);
371 // update "height" (this is the ascent in the terminology of the REC)
372 pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
373 ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit;
374 UpdateValue(mHeightSign, pseudoUnit, mHeight,
375 aDesiredSize, height);
376 height = std::max(0, height);
378 // update "depth" (this is the descent in the terminology of the REC)
379 pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
380 ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit;
381 UpdateValue(mDepthSign, pseudoUnit, mDepth,
382 aDesiredSize, depth);
383 depth = std::max(0, depth);
385 // update lspace
386 if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
387 pseudoUnit = mLeadingSpacePseudoUnit;
388 UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace,
389 aDesiredSize, lspace);
390 }
392 // update voffset
393 if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
394 pseudoUnit = mVerticalOffsetPseudoUnit;
395 UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset,
396 aDesiredSize, voffset);
397 }
398 // do the padding now that we have everything
399 // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
400 // with no attributes) looks the same as <mrow>...</mrow>. But when there are
401 // attributes, tweak our metrics and move children to achieve the desired visual
402 // effects.
404 if ((StyleVisibility()->mDirection ?
405 mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) {
406 // there was padding on the left. dismiss the left italic correction now
407 // (so that our parent won't correct us)
408 mBoundingMetrics.leftBearing = 0;
409 }
411 if ((StyleVisibility()->mDirection ?
412 mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) {
413 // there was padding on the right. dismiss the right italic correction now
414 // (so that our parent won't correct us)
415 mBoundingMetrics.width = width;
416 mBoundingMetrics.rightBearing = mBoundingMetrics.width;
417 }
419 nscoord dx = (StyleVisibility()->mDirection ?
420 width - initialWidth - lspace : lspace);
422 aDesiredSize.SetTopAscent(height);
423 aDesiredSize.Width() = mBoundingMetrics.width;
424 aDesiredSize.Height() = depth + aDesiredSize.TopAscent();
425 mBoundingMetrics.ascent = height;
426 mBoundingMetrics.descent = depth;
427 aDesiredSize.mBoundingMetrics = mBoundingMetrics;
429 mReference.x = 0;
430 mReference.y = aDesiredSize.TopAscent();
432 if (aPlaceOrigin) {
433 // Finish reflowing child frames, positioning their origins.
434 PositionRowChildFrames(dx, aDesiredSize.TopAscent() - voffset);
435 }
437 return NS_OK;
438 }
440 /* virtual */ nsresult
441 nsMathMLmpaddedFrame::MeasureForWidth(nsRenderingContext& aRenderingContext,
442 nsHTMLReflowMetrics& aDesiredSize)
443 {
444 ProcessAttributes();
445 return Place(aRenderingContext, false, aDesiredSize);
446 }