|
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/. */ |
|
5 |
|
6 |
|
7 #include "nsMathMLmpaddedFrame.h" |
|
8 #include "nsMathMLElement.h" |
|
9 #include "mozilla/gfx/2D.h" |
|
10 #include <algorithm> |
|
11 |
|
12 // |
|
13 // <mpadded> -- adjust space around content - implementation |
|
14 // |
|
15 |
|
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 |
|
20 |
|
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 |
|
27 |
|
28 nsIFrame* |
|
29 NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
30 { |
|
31 return new (aPresShell) nsMathMLmpaddedFrame(aContext); |
|
32 } |
|
33 |
|
34 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame) |
|
35 |
|
36 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() |
|
37 { |
|
38 } |
|
39 |
|
40 NS_IMETHODIMP |
|
41 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent) |
|
42 { |
|
43 // let the base class get the default from our parent |
|
44 nsMathMLContainerFrame::InheritAutomaticData(aParent); |
|
45 |
|
46 mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY; |
|
47 |
|
48 return NS_OK; |
|
49 } |
|
50 |
|
51 void |
|
52 nsMathMLmpaddedFrame::ProcessAttributes() |
|
53 { |
|
54 /* |
|
55 parse the attributes |
|
56 |
|
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 */ |
|
63 |
|
64 nsAutoString value; |
|
65 |
|
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 } |
|
74 |
|
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 } |
|
83 |
|
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 } |
|
92 |
|
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 } |
|
102 |
|
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 } |
|
112 |
|
113 } |
|
114 |
|
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 |
|
127 |
|
128 int32_t stringLength = aString.Length(); |
|
129 if (!stringLength) |
|
130 return false; |
|
131 |
|
132 nsAutoString number, unit; |
|
133 |
|
134 ////////////////////// |
|
135 // see if the sign is there |
|
136 |
|
137 int32_t i = 0; |
|
138 |
|
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; |
|
149 |
|
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 } |
|
159 |
|
160 if (c == '.') |
|
161 gotDot = true; |
|
162 else if (!nsCRT::IsAsciiDigit(c)) { |
|
163 break; |
|
164 } |
|
165 number.Append(c); |
|
166 } |
|
167 |
|
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 } |
|
175 |
|
176 nsresult errorCode; |
|
177 float floatValue = number.ToFloat(&errorCode); |
|
178 if (NS_FAILED(errorCode)) { |
|
179 aSign = NS_MATHML_SIGN_INVALID; |
|
180 return false; |
|
181 } |
|
182 |
|
183 // see if this is a percentage-based value |
|
184 if (i < stringLength && aString[i] == '%') { |
|
185 i++; |
|
186 gotPercent = true; |
|
187 } |
|
188 |
|
189 // the remainder now should be a css-unit, or a pseudo-unit, or a named-space |
|
190 aString.Right(unit, stringLength - i); |
|
191 |
|
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 |
|
212 |
|
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 } |
|
223 |
|
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 } |
|
233 |
|
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); |
|
240 |
|
241 return true; |
|
242 } |
|
243 |
|
244 |
|
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 } |
|
253 |
|
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; |
|
264 |
|
265 if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) { |
|
266 switch(aPseudoUnit) { |
|
267 case NS_MATHML_PSEUDO_UNIT_WIDTH: |
|
268 scaler = aDesiredSize.Width(); |
|
269 break; |
|
270 |
|
271 case NS_MATHML_PSEUDO_UNIT_HEIGHT: |
|
272 scaler = aDesiredSize.TopAscent(); |
|
273 break; |
|
274 |
|
275 case NS_MATHML_PSEUDO_UNIT_DEPTH: |
|
276 scaler = aDesiredSize.Height() - aDesiredSize.TopAscent(); |
|
277 break; |
|
278 |
|
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 } |
|
286 |
|
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); |
|
293 |
|
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 } |
|
302 |
|
303 nsresult |
|
304 nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext, |
|
305 nsHTMLReflowMetrics& aDesiredSize, |
|
306 const nsHTMLReflowState& aReflowState, |
|
307 nsReflowStatus& aStatus) |
|
308 { |
|
309 ProcessAttributes(); |
|
310 |
|
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 } |
|
318 |
|
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 } |
|
330 |
|
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; |
|
360 |
|
361 int32_t pseudoUnit; |
|
362 nscoord initialWidth = width; |
|
363 |
|
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); |
|
370 |
|
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); |
|
377 |
|
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); |
|
384 |
|
385 // update lspace |
|
386 if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) { |
|
387 pseudoUnit = mLeadingSpacePseudoUnit; |
|
388 UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace, |
|
389 aDesiredSize, lspace); |
|
390 } |
|
391 |
|
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. |
|
403 |
|
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 } |
|
410 |
|
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 } |
|
418 |
|
419 nscoord dx = (StyleVisibility()->mDirection ? |
|
420 width - initialWidth - lspace : lspace); |
|
421 |
|
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; |
|
428 |
|
429 mReference.x = 0; |
|
430 mReference.y = aDesiredSize.TopAscent(); |
|
431 |
|
432 if (aPlaceOrigin) { |
|
433 // Finish reflowing child frames, positioning their origins. |
|
434 PositionRowChildFrames(dx, aDesiredSize.TopAscent() - voffset); |
|
435 } |
|
436 |
|
437 return NS_OK; |
|
438 } |
|
439 |
|
440 /* virtual */ nsresult |
|
441 nsMathMLmpaddedFrame::MeasureForWidth(nsRenderingContext& aRenderingContext, |
|
442 nsHTMLReflowMetrics& aDesiredSize) |
|
443 { |
|
444 ProcessAttributes(); |
|
445 return Place(aRenderingContext, false, aDesiredSize); |
|
446 } |