layout/mathml/nsMathMLmpaddedFrame.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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 }

mercurial