Line data Source code
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 0 : NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
30 : {
31 0 : return new (aPresShell) nsMathMLmpaddedFrame(aContext);
32 : }
33 :
34 0 : NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
35 :
36 0 : nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
37 : {
38 0 : }
39 :
40 : NS_IMETHODIMP
41 0 : nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent)
42 : {
43 : // let the base class get the default from our parent
44 0 : nsMathMLContainerFrame::InheritAutomaticData(aParent);
45 :
46 0 : mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
47 :
48 0 : return NS_OK;
49 : }
50 :
51 : void
52 0 : 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 0 : nsAutoString value;
65 :
66 : // width
67 0 : mWidthSign = NS_MATHML_SIGN_INVALID;
68 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
69 0 : if (!value.IsEmpty()) {
70 0 : if (!ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit)) {
71 0 : ReportParseError(nsGkAtoms::width->GetUTF16String(), value.get());
72 : }
73 : }
74 :
75 : // height
76 0 : mHeightSign = NS_MATHML_SIGN_INVALID;
77 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
78 0 : if (!value.IsEmpty()) {
79 0 : if (!ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit)) {
80 0 : ReportParseError(nsGkAtoms::height->GetUTF16String(), value.get());
81 : }
82 : }
83 :
84 : // depth
85 0 : mDepthSign = NS_MATHML_SIGN_INVALID;
86 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value);
87 0 : if (!value.IsEmpty()) {
88 0 : if (!ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit)) {
89 0 : ReportParseError(nsGkAtoms::depth_->GetUTF16String(), value.get());
90 : }
91 : }
92 :
93 : // lspace
94 0 : mLeadingSpaceSign = NS_MATHML_SIGN_INVALID;
95 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
96 0 : if (!value.IsEmpty()) {
97 0 : if (!ParseAttribute(value, mLeadingSpaceSign, mLeadingSpace,
98 : mLeadingSpacePseudoUnit)) {
99 0 : ReportParseError(nsGkAtoms::lspace_->GetUTF16String(), value.get());
100 : }
101 : }
102 :
103 : // voffset
104 0 : mVerticalOffsetSign = NS_MATHML_SIGN_INVALID;
105 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::voffset_, value);
106 0 : if (!value.IsEmpty()) {
107 0 : if (!ParseAttribute(value, mVerticalOffsetSign, mVerticalOffset,
108 : mVerticalOffsetPseudoUnit)) {
109 0 : ReportParseError(nsGkAtoms::voffset_->GetUTF16String(), value.get());
110 : }
111 : }
112 :
113 0 : }
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 0 : nsMathMLmpaddedFrame::ParseAttribute(nsString& aString,
119 : int32_t& aSign,
120 : nsCSSValue& aCSSValue,
121 : int32_t& aPseudoUnit)
122 : {
123 0 : aCSSValue.Reset();
124 0 : aSign = NS_MATHML_SIGN_INVALID;
125 0 : aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
126 0 : aString.CompressWhitespace(); // aString is not a const in this code
127 :
128 0 : int32_t stringLength = aString.Length();
129 0 : if (!stringLength)
130 0 : return false;
131 :
132 0 : nsAutoString number, unit;
133 :
134 : //////////////////////
135 : // see if the sign is there
136 :
137 0 : int32_t i = 0;
138 :
139 0 : if (aString[0] == '+') {
140 0 : aSign = NS_MATHML_SIGN_PLUS;
141 0 : i++;
142 : }
143 0 : else if (aString[0] == '-') {
144 0 : aSign = NS_MATHML_SIGN_MINUS;
145 0 : i++;
146 : }
147 : else
148 0 : aSign = NS_MATHML_SIGN_UNSPECIFIED;
149 :
150 : // get the number
151 0 : bool gotDot = false, gotPercent = false;
152 0 : for (; i < stringLength; i++) {
153 0 : char16_t c = aString[i];
154 0 : if (gotDot && c == '.') {
155 : // error - two dots encountered
156 0 : aSign = NS_MATHML_SIGN_INVALID;
157 0 : return false;
158 : }
159 :
160 0 : if (c == '.')
161 0 : gotDot = true;
162 0 : else if (!nsCRT::IsAsciiDigit(c)) {
163 0 : break;
164 : }
165 0 : 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 0 : if (number.IsEmpty()) {
172 0 : aSign = NS_MATHML_SIGN_INVALID;
173 0 : return false;
174 : }
175 :
176 : nsresult errorCode;
177 0 : float floatValue = number.ToFloat(&errorCode);
178 0 : if (NS_FAILED(errorCode)) {
179 0 : aSign = NS_MATHML_SIGN_INVALID;
180 0 : return false;
181 : }
182 :
183 : // see if this is a percentage-based value
184 0 : if (i < stringLength && aString[i] == '%') {
185 0 : i++;
186 0 : gotPercent = true;
187 : }
188 :
189 : // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
190 0 : aString.Right(unit, stringLength - i);
191 :
192 0 : if (unit.IsEmpty()) {
193 0 : if (gotPercent) {
194 : // case ["+"|"-"] unsigned-number "%"
195 0 : aCSSValue.SetPercentValue(floatValue / 100.0f);
196 0 : aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
197 0 : return true;
198 : } else {
199 : // case ["+"|"-"] unsigned-number
200 : // XXXfredw: should we allow non-zero unitless values? See bug 757703.
201 0 : if (!floatValue) {
202 0 : aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
203 0 : aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
204 0 : return true;
205 : }
206 : }
207 : }
208 0 : else if (unit.EqualsLiteral("width")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
209 0 : else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
210 0 : else if (unit.EqualsLiteral("depth")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
211 0 : else if (!gotPercent) { // percentage can only apply to a pseudo-unit
212 :
213 : // see if the unit is a named-space
214 0 : 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 0 : floatValue *= aCSSValue.GetFloatValue();
219 0 : aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
220 0 : aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
221 0 : 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 0 : number.Append(unit); // leave the sign out if it was there
228 0 : if (nsMathMLElement::ParseNumericValue(number, aCSSValue,
229 : nsMathMLElement::
230 : PARSE_SUPPRESS_WARNINGS, nullptr))
231 0 : return true;
232 : }
233 :
234 : // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
235 0 : if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
236 0 : if (gotPercent)
237 0 : aCSSValue.SetPercentValue(floatValue / 100.0f);
238 : else
239 0 : aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
240 :
241 0 : return true;
242 : }
243 :
244 :
245 : #ifdef DEBUG
246 0 : printf("mpadded: attribute with bad numeric value: %s\n",
247 0 : NS_LossyConvertUTF16toASCII(aString).get());
248 : #endif
249 : // if we reach here, it means we encounter an unexpected input
250 0 : aSign = NS_MATHML_SIGN_INVALID;
251 0 : return false;
252 : }
253 :
254 : void
255 0 : nsMathMLmpaddedFrame::UpdateValue(int32_t aSign,
256 : int32_t aPseudoUnit,
257 : const nsCSSValue& aCSSValue,
258 : const ReflowOutput& aDesiredSize,
259 : nscoord& aValueToUpdate,
260 : float aFontSizeInflation) const
261 : {
262 0 : nsCSSUnit unit = aCSSValue.GetUnit();
263 0 : if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
264 0 : nscoord scaler = 0, amount = 0;
265 :
266 0 : if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
267 0 : switch(aPseudoUnit) {
268 : case NS_MATHML_PSEUDO_UNIT_WIDTH:
269 0 : scaler = aDesiredSize.Width();
270 0 : break;
271 :
272 : case NS_MATHML_PSEUDO_UNIT_HEIGHT:
273 0 : scaler = aDesiredSize.BlockStartAscent();
274 0 : break;
275 :
276 : case NS_MATHML_PSEUDO_UNIT_DEPTH:
277 0 : scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
278 0 : break;
279 :
280 : default:
281 : // if we ever reach here, it would mean something is wrong
282 : // somewhere with the setup and/or the caller
283 0 : NS_ERROR("Unexpected Pseudo Unit");
284 0 : return;
285 : }
286 : }
287 :
288 0 : if (eCSSUnit_Number == unit)
289 0 : amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
290 0 : else if (eCSSUnit_Percent == unit)
291 0 : amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
292 : else
293 0 : amount = CalcLength(PresContext(), mStyleContext, aCSSValue,
294 0 : aFontSizeInflation);
295 :
296 0 : if (NS_MATHML_SIGN_PLUS == aSign)
297 0 : aValueToUpdate += amount;
298 0 : else if (NS_MATHML_SIGN_MINUS == aSign)
299 0 : aValueToUpdate -= amount;
300 : else
301 0 : aValueToUpdate = amount;
302 : }
303 : }
304 :
305 : void
306 0 : nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
307 : ReflowOutput& aDesiredSize,
308 : const ReflowInput& aReflowInput,
309 : nsReflowStatus& aStatus)
310 : {
311 0 : mPresentationData.flags &= ~NS_MATHML_ERROR;
312 0 : ProcessAttributes();
313 :
314 : ///////////////
315 : // Let the base class format our content like an inferred mrow
316 0 : nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize,
317 0 : aReflowInput, aStatus);
318 : //NS_ASSERTION(aStatus.IsComplete(), "bad status");
319 0 : }
320 :
321 : /* virtual */ nsresult
322 0 : nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget,
323 : bool aPlaceOrigin,
324 : ReflowOutput& aDesiredSize)
325 : {
326 : nsresult rv =
327 0 : nsMathMLContainerFrame::Place(aDrawTarget, false, aDesiredSize);
328 0 : if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
329 0 : DidReflowChildren(PrincipalChildList().FirstChild());
330 0 : return rv;
331 : }
332 :
333 0 : nscoord height = aDesiredSize.BlockStartAscent();
334 0 : nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
335 : // The REC says:
336 : //
337 : // "The lspace attribute ('leading' space) specifies the horizontal location
338 : // of the positioning point of the child content with respect to the
339 : // positioning point of the mpadded element. By default they coincide, and
340 : // therefore absolute values for lspace have the same effect as relative
341 : // values."
342 : //
343 : // "MathML renderers should ensure that, except for the effects of the
344 : // attributes, the relative spacing between the contents of the mpadded
345 : // element and surrounding MathML elements would not be modified by replacing
346 : // an mpadded element with an mrow element with the same content, even if
347 : // linebreaking occurs within the mpadded element."
348 : //
349 : // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
350 : //
351 : // "In those discussions, the terms leading and trailing are used to specify
352 : // a side of an object when which side to use depends on the directionality;
353 : // ie. leading means left in LTR but right in RTL."
354 : // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
355 0 : nscoord lspace = 0;
356 : // In MathML3, "width" will be the bounding box width and "advancewidth" will
357 : // refer "to the horizontal distance between the positioning point of the
358 : // mpadded and the positioning point for the following content". MathML2
359 : // doesn't make the distinction.
360 0 : nscoord width = aDesiredSize.Width();
361 0 : nscoord voffset = 0;
362 :
363 : int32_t pseudoUnit;
364 0 : nscoord initialWidth = width;
365 0 : float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
366 :
367 : // update width
368 0 : pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
369 0 : ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit;
370 0 : UpdateValue(mWidthSign, pseudoUnit, mWidth,
371 0 : aDesiredSize, width, fontSizeInflation);
372 0 : width = std::max(0, width);
373 :
374 : // update "height" (this is the ascent in the terminology of the REC)
375 0 : pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
376 0 : ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit;
377 0 : UpdateValue(mHeightSign, pseudoUnit, mHeight,
378 0 : aDesiredSize, height, fontSizeInflation);
379 0 : height = std::max(0, height);
380 :
381 : // update "depth" (this is the descent in the terminology of the REC)
382 0 : pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
383 0 : ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit;
384 0 : UpdateValue(mDepthSign, pseudoUnit, mDepth,
385 0 : aDesiredSize, depth, fontSizeInflation);
386 0 : depth = std::max(0, depth);
387 :
388 : // update lspace
389 0 : if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
390 0 : pseudoUnit = mLeadingSpacePseudoUnit;
391 0 : UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace,
392 0 : aDesiredSize, lspace, fontSizeInflation);
393 : }
394 :
395 : // update voffset
396 0 : if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
397 0 : pseudoUnit = mVerticalOffsetPseudoUnit;
398 0 : UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset,
399 0 : aDesiredSize, voffset, fontSizeInflation);
400 : }
401 : // do the padding now that we have everything
402 : // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
403 : // with no attributes) looks the same as <mrow>...</mrow>. But when there are
404 : // attributes, tweak our metrics and move children to achieve the desired visual
405 : // effects.
406 :
407 0 : if ((StyleVisibility()->mDirection ?
408 0 : mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) {
409 : // there was padding on the left. dismiss the left italic correction now
410 : // (so that our parent won't correct us)
411 0 : mBoundingMetrics.leftBearing = 0;
412 : }
413 :
414 0 : if ((StyleVisibility()->mDirection ?
415 0 : mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) {
416 : // there was padding on the right. dismiss the right italic correction now
417 : // (so that our parent won't correct us)
418 0 : mBoundingMetrics.width = width;
419 0 : mBoundingMetrics.rightBearing = mBoundingMetrics.width;
420 : }
421 :
422 0 : nscoord dx = (StyleVisibility()->mDirection ?
423 0 : width - initialWidth - lspace : lspace);
424 :
425 0 : aDesiredSize.SetBlockStartAscent(height);
426 0 : aDesiredSize.Width() = mBoundingMetrics.width;
427 0 : aDesiredSize.Height() = depth + aDesiredSize.BlockStartAscent();
428 0 : mBoundingMetrics.ascent = height;
429 0 : mBoundingMetrics.descent = depth;
430 0 : aDesiredSize.mBoundingMetrics = mBoundingMetrics;
431 :
432 0 : mReference.x = 0;
433 0 : mReference.y = aDesiredSize.BlockStartAscent();
434 :
435 0 : if (aPlaceOrigin) {
436 : // Finish reflowing child frames, positioning their origins.
437 0 : PositionRowChildFrames(dx, aDesiredSize.BlockStartAscent() - voffset);
438 : }
439 :
440 0 : return NS_OK;
441 : }
442 :
443 : /* virtual */ nsresult
444 0 : nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget* aDrawTarget,
445 : ReflowOutput& aDesiredSize)
446 : {
447 0 : ProcessAttributes();
448 0 : return Place(aDrawTarget, false, aDesiredSize);
449 : }
|