1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla MathML Project.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * The University Of Queensland.
19 : * Portions created by the Initial Developer are Copyright (C) 1999
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Roger B. Sidje <rbs@maths.uq.edu.au>
24 : * David J. Fiddes <D.J.Fiddes@hw.ac.uk>
25 : * Karl Tomlinson <karlt+@karlt.net>, Mozilla Corporation
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 :
42 : #include "nsCOMPtr.h"
43 : #include "nsCRT.h" // to get NS_IS_SPACE
44 : #include "nsFrame.h"
45 : #include "nsPresContext.h"
46 : #include "nsStyleContext.h"
47 : #include "nsStyleConsts.h"
48 :
49 : #include "nsMathMLmpaddedFrame.h"
50 :
51 : //
52 : // <mpadded> -- adjust space around content - implementation
53 : //
54 :
55 : #define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there
56 : #define NS_MATHML_SIGN_UNSPECIFIED 0
57 : #define NS_MATHML_SIGN_MINUS 1
58 : #define NS_MATHML_SIGN_PLUS 2
59 :
60 : #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
61 : #define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special
62 : #define NS_MATHML_PSEUDO_UNIT_WIDTH 2
63 : #define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
64 : #define NS_MATHML_PSEUDO_UNIT_DEPTH 4
65 : #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5
66 :
67 : nsIFrame*
68 0 : NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
69 : {
70 0 : return new (aPresShell) nsMathMLmpaddedFrame(aContext);
71 : }
72 :
73 0 : NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
74 :
75 0 : nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
76 : {
77 0 : }
78 :
79 : NS_IMETHODIMP
80 0 : nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent)
81 : {
82 : // let the base class get the default from our parent
83 0 : nsMathMLContainerFrame::InheritAutomaticData(aParent);
84 :
85 0 : mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
86 :
87 0 : return NS_OK;
88 : }
89 :
90 : void
91 0 : nsMathMLmpaddedFrame::ProcessAttributes()
92 : {
93 : /*
94 : parse the attributes
95 :
96 : width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
97 : height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
98 : depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
99 : lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
100 : voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
101 : */
102 :
103 0 : nsAutoString value;
104 :
105 : /* The REC says:
106 : There is one exceptional element, <mpadded>, whose attributes cannot be
107 : set with <mstyle>. When the attributes width, height and depth are specified
108 : on an <mstyle> element, they apply only to the <mspace/> element. Similarly,
109 : when lspace is set with <mstyle>, it applies only to the <mo> element.
110 : */
111 :
112 : // See if attributes are local, don't access mstyle !
113 :
114 : // width
115 0 : mWidthSign = NS_MATHML_SIGN_INVALID;
116 0 : GetAttribute(mContent, nsnull, nsGkAtoms::width, value);
117 0 : if (!value.IsEmpty()) {
118 0 : ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit);
119 : }
120 :
121 : // height
122 0 : mHeightSign = NS_MATHML_SIGN_INVALID;
123 0 : GetAttribute(mContent, nsnull, nsGkAtoms::height, value);
124 0 : if (!value.IsEmpty()) {
125 0 : ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit);
126 : }
127 :
128 : // depth
129 0 : mDepthSign = NS_MATHML_SIGN_INVALID;
130 0 : GetAttribute(mContent, nsnull, nsGkAtoms::depth_, value);
131 0 : if (!value.IsEmpty()) {
132 0 : ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit);
133 : }
134 :
135 : // lspace
136 0 : mLeadingSpaceSign = NS_MATHML_SIGN_INVALID;
137 0 : GetAttribute(mContent, nsnull, nsGkAtoms::lspace_, value);
138 0 : if (!value.IsEmpty()) {
139 : ParseAttribute(value, mLeadingSpaceSign, mLeadingSpace,
140 0 : mLeadingSpacePseudoUnit);
141 : }
142 :
143 : // voffset
144 0 : mVerticalOffsetSign = NS_MATHML_SIGN_INVALID;
145 0 : GetAttribute(mContent, nsnull, nsGkAtoms::voffset_, value);
146 0 : if (!value.IsEmpty()) {
147 : ParseAttribute(value, mVerticalOffsetSign, mVerticalOffset,
148 0 : mVerticalOffsetPseudoUnit);
149 : }
150 :
151 0 : }
152 :
153 : // parse an input string in the following format (see bug 148326 for testcases):
154 : // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
155 : bool
156 0 : nsMathMLmpaddedFrame::ParseAttribute(nsString& aString,
157 : PRInt32& aSign,
158 : nsCSSValue& aCSSValue,
159 : PRInt32& aPseudoUnit)
160 : {
161 0 : aCSSValue.Reset();
162 0 : aSign = NS_MATHML_SIGN_INVALID;
163 0 : aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
164 0 : aString.CompressWhitespace(); // aString is not a const in this code
165 :
166 0 : PRInt32 stringLength = aString.Length();
167 0 : if (!stringLength)
168 0 : return false;
169 :
170 0 : nsAutoString number, unit;
171 :
172 : //////////////////////
173 : // see if the sign is there
174 :
175 0 : PRInt32 i = 0;
176 :
177 0 : if (aString[0] == '+') {
178 0 : aSign = NS_MATHML_SIGN_PLUS;
179 0 : i++;
180 : }
181 0 : else if (aString[0] == '-') {
182 0 : aSign = NS_MATHML_SIGN_MINUS;
183 0 : i++;
184 : }
185 : else
186 0 : aSign = NS_MATHML_SIGN_UNSPECIFIED;
187 :
188 : // skip any space after the sign
189 0 : if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
190 0 : i++;
191 :
192 : // get the number
193 0 : bool gotDot = false, gotPercent = false;
194 0 : for (; i < stringLength; i++) {
195 0 : PRUnichar c = aString[i];
196 0 : if (gotDot && c == '.') {
197 : // error - two dots encountered
198 0 : aSign = NS_MATHML_SIGN_INVALID;
199 0 : return false;
200 : }
201 :
202 0 : if (c == '.')
203 0 : gotDot = true;
204 0 : else if (!nsCRT::IsAsciiDigit(c)) {
205 0 : break;
206 : }
207 0 : number.Append(c);
208 : }
209 :
210 : // catch error if we didn't enter the loop above... we could simply initialize
211 : // floatValue = 1, to cater for cases such as width="height", but that wouldn't
212 : // be in line with the spec which requires an explicit number
213 0 : if (number.IsEmpty()) {
214 : #ifdef NS_DEBUG
215 : printf("mpadded: attribute with bad numeric value: %s\n",
216 0 : NS_LossyConvertUTF16toASCII(aString).get());
217 : #endif
218 0 : aSign = NS_MATHML_SIGN_INVALID;
219 0 : return false;
220 : }
221 :
222 : PRInt32 errorCode;
223 0 : float floatValue = number.ToFloat(&errorCode);
224 0 : if (errorCode) {
225 0 : aSign = NS_MATHML_SIGN_INVALID;
226 0 : return false;
227 : }
228 :
229 : // skip any space after the number
230 0 : if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
231 0 : i++;
232 :
233 : // see if this is a percentage-based value
234 0 : if (i < stringLength && aString[i] == '%') {
235 0 : i++;
236 0 : gotPercent = true;
237 :
238 : // skip any space after the '%' sign
239 0 : if (i < stringLength && nsCRT::IsAsciiSpace(aString[i]))
240 0 : i++;
241 : }
242 :
243 : // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
244 0 : aString.Right(unit, stringLength - i);
245 :
246 0 : if (unit.IsEmpty()) {
247 : // also cater for the edge case of "0" for which the unit is optional
248 0 : if (gotPercent || !floatValue) {
249 0 : aCSSValue.SetPercentValue(floatValue / 100.0f);
250 0 : aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
251 0 : return true;
252 : }
253 : /*
254 : else {
255 : // no explicit CSS unit and no explicit pseudo-unit...
256 : // In this case, the MathML REC suggests taking ems for
257 : // h-unit (width, lspace) or exs for v-unit (height, depth).
258 : // Here, however, we explicitly request authors to specify
259 : // the unit. This is more in line with the CSS REC (and
260 : // it allows keeping the code simpler...)
261 : }
262 : */
263 : }
264 0 : else if (unit.EqualsLiteral("width")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
265 0 : else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
266 0 : else if (unit.EqualsLiteral("depth")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
267 0 : else if (!gotPercent) { // percentage can only apply to a pseudo-unit
268 :
269 : // see if the unit is a named-space
270 : // XXX nsnull in ParseNamedSpacedValue()? don't access mstyle?
271 0 : if (ParseNamedSpaceValue(nsnull, unit, aCSSValue)) {
272 : // re-scale properly, and we know that the unit of the named-space is 'em'
273 0 : floatValue *= aCSSValue.GetFloatValue();
274 0 : aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
275 0 : aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
276 0 : return true;
277 : }
278 :
279 : // see if the input was just a CSS value
280 0 : number.Append(unit); // leave the sign out if it was there
281 0 : if (ParseNumericValue(number, aCSSValue))
282 0 : return true;
283 : }
284 :
285 : // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
286 0 : if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
287 0 : if (gotPercent)
288 0 : aCSSValue.SetPercentValue(floatValue / 100.0f);
289 : else
290 0 : aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
291 :
292 0 : return true;
293 : }
294 :
295 :
296 : #ifdef NS_DEBUG
297 : printf("mpadded: attribute with bad numeric value: %s\n",
298 0 : NS_LossyConvertUTF16toASCII(aString).get());
299 : #endif
300 : // if we reach here, it means we encounter an unexpected input
301 0 : aSign = NS_MATHML_SIGN_INVALID;
302 0 : return false;
303 : }
304 :
305 : void
306 0 : nsMathMLmpaddedFrame::UpdateValue(PRInt32 aSign,
307 : PRInt32 aPseudoUnit,
308 : const nsCSSValue& aCSSValue,
309 : const nsBoundingMetrics& aBoundingMetrics,
310 : nscoord& aValueToUpdate) const
311 : {
312 0 : nsCSSUnit unit = aCSSValue.GetUnit();
313 0 : if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
314 0 : nscoord scaler = 0, amount = 0;
315 :
316 0 : if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
317 0 : switch(aPseudoUnit) {
318 : case NS_MATHML_PSEUDO_UNIT_WIDTH:
319 0 : scaler = aBoundingMetrics.width;
320 0 : break;
321 :
322 : case NS_MATHML_PSEUDO_UNIT_HEIGHT:
323 0 : scaler = aBoundingMetrics.ascent;
324 0 : break;
325 :
326 : case NS_MATHML_PSEUDO_UNIT_DEPTH:
327 0 : scaler = aBoundingMetrics.descent;
328 0 : break;
329 :
330 : default:
331 : // if we ever reach here, it would mean something is wrong
332 : // somewhere with the setup and/or the caller
333 0 : NS_ERROR("Unexpected Pseudo Unit");
334 0 : return;
335 : }
336 : }
337 :
338 0 : if (eCSSUnit_Number == unit)
339 0 : amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
340 0 : else if (eCSSUnit_Percent == unit)
341 0 : amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
342 : else
343 0 : amount = CalcLength(PresContext(), mStyleContext, aCSSValue);
344 :
345 0 : if (NS_MATHML_SIGN_PLUS == aSign)
346 0 : aValueToUpdate += amount;
347 0 : else if (NS_MATHML_SIGN_MINUS == aSign)
348 0 : aValueToUpdate -= amount;
349 : else
350 0 : aValueToUpdate = amount;
351 : }
352 : }
353 :
354 : NS_IMETHODIMP
355 0 : nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
356 : nsHTMLReflowMetrics& aDesiredSize,
357 : const nsHTMLReflowState& aReflowState,
358 : nsReflowStatus& aStatus)
359 : {
360 0 : ProcessAttributes();
361 :
362 : ///////////////
363 : // Let the base class format our content like an inferred mrow
364 : nsresult rv = nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize,
365 0 : aReflowState, aStatus);
366 : //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
367 0 : return rv;
368 : }
369 :
370 : /* virtual */ nsresult
371 0 : nsMathMLmpaddedFrame::Place(nsRenderingContext& aRenderingContext,
372 : bool aPlaceOrigin,
373 : nsHTMLReflowMetrics& aDesiredSize)
374 : {
375 : nsresult rv =
376 0 : nsMathMLContainerFrame::Place(aRenderingContext, false, aDesiredSize);
377 0 : if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
378 0 : DidReflowChildren(GetFirstPrincipalChild());
379 0 : return rv;
380 : }
381 :
382 0 : nscoord height = mBoundingMetrics.ascent;
383 0 : nscoord depth = mBoundingMetrics.descent;
384 : // The REC says:
385 : //
386 : // "The lspace attribute ('leading' space) specifies the horizontal location
387 : // of the positioning point of the child content with respect to the
388 : // positioning point of the mpadded element. By default they coincide, and
389 : // therefore absolute values for lspace have the same effect as relative
390 : // values."
391 : //
392 : // "MathML renderers should ensure that, except for the effects of the
393 : // attributes, the relative spacing between the contents of the mpadded
394 : // element and surrounding MathML elements would not be modified by replacing
395 : // an mpadded element with an mrow element with the same content, even if
396 : // linebreaking occurs within the mpadded element."
397 : //
398 : // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
399 : //
400 : // "In those discussions, the terms leading and trailing are used to specify
401 : // a side of an object when which side to use depends on the directionality;
402 : // ie. leading means left in LTR but right in RTL."
403 : // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
404 0 : nscoord lspace = 0;
405 : // In MathML3, "width" will be the bounding box width and "advancewidth" will
406 : // refer "to the horizontal distance between the positioning point of the
407 : // mpadded and the positioning point for the following content". MathML2
408 : // doesn't make the distinction.
409 0 : nscoord width = mBoundingMetrics.width;
410 0 : nscoord voffset = 0;
411 :
412 : PRInt32 pseudoUnit;
413 0 : nscoord initialWidth = width;
414 :
415 : // update width
416 : pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
417 0 : ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit;
418 : UpdateValue(mWidthSign, pseudoUnit, mWidth,
419 0 : mBoundingMetrics, width);
420 0 : width = NS_MAX(0, width);
421 :
422 : // update "height" (this is the ascent in the terminology of the REC)
423 : pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
424 0 : ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit;
425 : UpdateValue(mHeightSign, pseudoUnit, mHeight,
426 0 : mBoundingMetrics, height);
427 0 : height = NS_MAX(0, height);
428 :
429 : // update "depth" (this is the descent in the terminology of the REC)
430 : pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
431 0 : ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit;
432 : UpdateValue(mDepthSign, pseudoUnit, mDepth,
433 0 : mBoundingMetrics, depth);
434 0 : depth = NS_MAX(0, depth);
435 :
436 : // update lspace
437 0 : if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
438 0 : pseudoUnit = mLeadingSpacePseudoUnit;
439 : UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace,
440 0 : mBoundingMetrics, lspace);
441 : }
442 :
443 : // update voffset
444 0 : if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
445 0 : pseudoUnit = mVerticalOffsetPseudoUnit;
446 : UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset,
447 0 : mBoundingMetrics, voffset);
448 : }
449 : // do the padding now that we have everything
450 : // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
451 : // with no attributes) looks the same as <mrow>...</mrow>. But when there are
452 : // attributes, tweak our metrics and move children to achieve the desired visual
453 : // effects.
454 :
455 0 : if ((NS_MATHML_IS_RTL(mPresentationData.flags) ?
456 : mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) {
457 : // there was padding on the left. dismiss the left italic correction now
458 : // (so that our parent won't correct us)
459 0 : mBoundingMetrics.leftBearing = 0;
460 : }
461 :
462 0 : if ((NS_MATHML_IS_RTL(mPresentationData.flags) ?
463 : mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) {
464 : // there was padding on the right. dismiss the right italic correction now
465 : // (so that our parent won't correct us)
466 0 : mBoundingMetrics.width = width;
467 0 : mBoundingMetrics.rightBearing = mBoundingMetrics.width;
468 : }
469 :
470 0 : nscoord dy = height - mBoundingMetrics.ascent;
471 : nscoord dx = NS_MATHML_IS_RTL(mPresentationData.flags) ?
472 0 : width - initialWidth - lspace : lspace;
473 :
474 0 : aDesiredSize.ascent += dy;
475 0 : aDesiredSize.width = mBoundingMetrics.width;
476 0 : aDesiredSize.height += dy + depth - mBoundingMetrics.descent;
477 0 : mBoundingMetrics.ascent = height;
478 0 : mBoundingMetrics.descent = depth;
479 0 : aDesiredSize.mBoundingMetrics = mBoundingMetrics;
480 :
481 0 : mReference.x = 0;
482 0 : mReference.y = aDesiredSize.ascent;
483 :
484 0 : if (aPlaceOrigin) {
485 : // Finish reflowing child frames, positioning their origins.
486 0 : PositionRowChildFrames(dx, aDesiredSize.ascent - voffset);
487 : }
488 :
489 0 : return NS_OK;
490 : }
|