Coverage Report

Created: 2018-09-25 14:53

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