forked from mirrors/gecko-dev
		
	 8aa47bcfb7
			
		
	
	
		8aa47bcfb7
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D66016 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			439 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			439 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsMathMLmpaddedFrame.h"
 | |
| #include "mozilla/dom/MathMLElement.h"
 | |
| #include "mozilla/gfx/2D.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/TextUtils.h"
 | |
| #include <algorithm>
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| //
 | |
| // <mpadded> -- adjust space around content - implementation
 | |
| //
 | |
| 
 | |
| #define NS_MATHML_SIGN_INVALID -1  // if the attribute is not there
 | |
| #define NS_MATHML_SIGN_UNSPECIFIED 0
 | |
| #define NS_MATHML_SIGN_MINUS 1
 | |
| #define NS_MATHML_SIGN_PLUS 2
 | |
| 
 | |
| #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
 | |
| #define NS_MATHML_PSEUDO_UNIT_ITSELF 1  // special
 | |
| #define NS_MATHML_PSEUDO_UNIT_WIDTH 2
 | |
| #define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
 | |
| #define NS_MATHML_PSEUDO_UNIT_DEPTH 4
 | |
| #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5
 | |
| 
 | |
| nsIFrame* NS_NewMathMLmpaddedFrame(PresShell* aPresShell,
 | |
|                                    ComputedStyle* aStyle) {
 | |
|   return new (aPresShell)
 | |
|       nsMathMLmpaddedFrame(aStyle, aPresShell->GetPresContext());
 | |
| }
 | |
| 
 | |
| NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
 | |
| 
 | |
| nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() = default;
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent) {
 | |
|   // let the base class get the default from our parent
 | |
|   nsMathMLContainerFrame::InheritAutomaticData(aParent);
 | |
| 
 | |
|   mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsMathMLmpaddedFrame::ProcessAttributes() {
 | |
|   // clang-format off
 | |
|   /*
 | |
|   parse the attributes
 | |
| 
 | |
|   width  = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
 | |
|   height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
 | |
|   depth  = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
 | |
|   lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
 | |
|   voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
 | |
|   */
 | |
|   // clang-format on
 | |
| 
 | |
|   nsAutoString value;
 | |
| 
 | |
|   // width
 | |
|   mWidthSign = NS_MATHML_SIGN_INVALID;
 | |
|   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
 | |
|   if (!value.IsEmpty()) {
 | |
|     if (!ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit)) {
 | |
|       ReportParseError(nsGkAtoms::width->GetUTF16String(), value.get());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // height
 | |
|   mHeightSign = NS_MATHML_SIGN_INVALID;
 | |
|   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
 | |
|   if (!value.IsEmpty()) {
 | |
|     if (!ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit)) {
 | |
|       ReportParseError(nsGkAtoms::height->GetUTF16String(), value.get());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // depth
 | |
|   mDepthSign = NS_MATHML_SIGN_INVALID;
 | |
|   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value);
 | |
|   if (!value.IsEmpty()) {
 | |
|     if (!ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit)) {
 | |
|       ReportParseError(nsGkAtoms::depth_->GetUTF16String(), value.get());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // lspace
 | |
|   mLeadingSpaceSign = NS_MATHML_SIGN_INVALID;
 | |
|   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
 | |
|   if (!value.IsEmpty()) {
 | |
|     if (!ParseAttribute(value, mLeadingSpaceSign, mLeadingSpace,
 | |
|                         mLeadingSpacePseudoUnit)) {
 | |
|       ReportParseError(nsGkAtoms::lspace_->GetUTF16String(), value.get());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // voffset
 | |
|   mVerticalOffsetSign = NS_MATHML_SIGN_INVALID;
 | |
|   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::voffset_, value);
 | |
|   if (!value.IsEmpty()) {
 | |
|     if (!ParseAttribute(value, mVerticalOffsetSign, mVerticalOffset,
 | |
|                         mVerticalOffsetPseudoUnit)) {
 | |
|       ReportParseError(nsGkAtoms::voffset_->GetUTF16String(), value.get());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // parse an input string in the following format (see bug 148326 for testcases):
 | |
| // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
 | |
| bool nsMathMLmpaddedFrame::ParseAttribute(nsString& aString, int32_t& aSign,
 | |
|                                           nsCSSValue& aCSSValue,
 | |
|                                           int32_t& aPseudoUnit) {
 | |
|   aCSSValue.Reset();
 | |
|   aSign = NS_MATHML_SIGN_INVALID;
 | |
|   aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
 | |
|   aString.CompressWhitespace();  // aString is not a const in this code
 | |
| 
 | |
|   int32_t stringLength = aString.Length();
 | |
|   if (!stringLength) return false;
 | |
| 
 | |
|   nsAutoString number, unit;
 | |
| 
 | |
|   //////////////////////
 | |
|   // see if the sign is there
 | |
| 
 | |
|   int32_t i = 0;
 | |
| 
 | |
|   if (aString[0] == '+') {
 | |
|     aSign = NS_MATHML_SIGN_PLUS;
 | |
|     i++;
 | |
|   } else if (aString[0] == '-') {
 | |
|     aSign = NS_MATHML_SIGN_MINUS;
 | |
|     i++;
 | |
|   } else
 | |
|     aSign = NS_MATHML_SIGN_UNSPECIFIED;
 | |
| 
 | |
|   // get the number
 | |
|   bool gotDot = false, gotPercent = false;
 | |
|   for (; i < stringLength; i++) {
 | |
|     char16_t c = aString[i];
 | |
|     if (gotDot && c == '.') {
 | |
|       // error - two dots encountered
 | |
|       aSign = NS_MATHML_SIGN_INVALID;
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (c == '.')
 | |
|       gotDot = true;
 | |
|     else if (!IsAsciiDigit(c)) {
 | |
|       break;
 | |
|     }
 | |
|     number.Append(c);
 | |
|   }
 | |
| 
 | |
|   // catch error if we didn't enter the loop above... we could simply initialize
 | |
|   // floatValue = 1, to cater for cases such as width="height", but that
 | |
|   // wouldn't be in line with the spec which requires an explicit number
 | |
|   if (number.IsEmpty()) {
 | |
|     aSign = NS_MATHML_SIGN_INVALID;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsresult errorCode;
 | |
|   float floatValue = number.ToFloat(&errorCode);
 | |
|   if (NS_FAILED(errorCode)) {
 | |
|     aSign = NS_MATHML_SIGN_INVALID;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // see if this is a percentage-based value
 | |
|   if (i < stringLength && aString[i] == '%') {
 | |
|     i++;
 | |
|     gotPercent = true;
 | |
|   }
 | |
| 
 | |
|   // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
 | |
|   aString.Right(unit, stringLength - i);
 | |
| 
 | |
|   if (unit.IsEmpty()) {
 | |
|     if (gotPercent) {
 | |
|       // case ["+"|"-"] unsigned-number "%"
 | |
|       aCSSValue.SetPercentValue(floatValue / 100.0f);
 | |
|       aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
 | |
|       return true;
 | |
|     } else {
 | |
|       // case ["+"|"-"] unsigned-number
 | |
|       // XXXfredw: should we allow non-zero unitless values? See bug 757703.
 | |
|       if (!floatValue) {
 | |
|         aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
 | |
|         aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   } else if (unit.EqualsLiteral("width"))
 | |
|     aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
 | |
|   else if (unit.EqualsLiteral("height"))
 | |
|     aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
 | |
|   else if (unit.EqualsLiteral("depth"))
 | |
|     aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
 | |
|   else if (!gotPercent) {  // percentage can only apply to a pseudo-unit
 | |
| 
 | |
|     // see if the unit is a named-space
 | |
|     if (dom::MathMLElement::ParseNamedSpaceValue(
 | |
|             unit, aCSSValue, dom::MathMLElement::PARSE_ALLOW_NEGATIVE,
 | |
|             *mContent->OwnerDoc())) {
 | |
|       // re-scale properly, and we know that the unit of the named-space is 'em'
 | |
|       floatValue *= aCSSValue.GetFloatValue();
 | |
|       aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
 | |
|       aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     // see if the input was just a CSS value
 | |
|     // We are not supposed to have a unitless, percent, negative or namedspace
 | |
|     // value here.
 | |
|     number.Append(unit);  // leave the sign out if it was there
 | |
|     if (dom::MathMLElement::ParseNumericValue(
 | |
|             number, aCSSValue, dom::MathMLElement::PARSE_SUPPRESS_WARNINGS,
 | |
|             nullptr))
 | |
|       return true;
 | |
|   }
 | |
| 
 | |
|   // if we enter here, we have a number that will act as a multiplier on a
 | |
|   // pseudo-unit
 | |
|   if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
 | |
|     if (gotPercent)
 | |
|       aCSSValue.SetPercentValue(floatValue / 100.0f);
 | |
|     else
 | |
|       aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
 | |
| 
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   printf("mpadded: attribute with bad numeric value: %s\n",
 | |
|          NS_LossyConvertUTF16toASCII(aString).get());
 | |
| #endif
 | |
|   // if we reach here, it means we encounter an unexpected input
 | |
|   aSign = NS_MATHML_SIGN_INVALID;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void nsMathMLmpaddedFrame::UpdateValue(int32_t aSign, int32_t aPseudoUnit,
 | |
|                                        const nsCSSValue& aCSSValue,
 | |
|                                        const ReflowOutput& aDesiredSize,
 | |
|                                        nscoord& aValueToUpdate,
 | |
|                                        float aFontSizeInflation) const {
 | |
|   nsCSSUnit unit = aCSSValue.GetUnit();
 | |
|   if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
 | |
|     nscoord scaler = 0, amount = 0;
 | |
| 
 | |
|     if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
 | |
|       switch (aPseudoUnit) {
 | |
|         case NS_MATHML_PSEUDO_UNIT_WIDTH:
 | |
|           scaler = aDesiredSize.Width();
 | |
|           break;
 | |
| 
 | |
|         case NS_MATHML_PSEUDO_UNIT_HEIGHT:
 | |
|           scaler = aDesiredSize.BlockStartAscent();
 | |
|           break;
 | |
| 
 | |
|         case NS_MATHML_PSEUDO_UNIT_DEPTH:
 | |
|           scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
 | |
|           break;
 | |
| 
 | |
|         default:
 | |
|           // if we ever reach here, it would mean something is wrong
 | |
|           // somewhere with the setup and/or the caller
 | |
|           NS_ERROR("Unexpected Pseudo Unit");
 | |
|           return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (eCSSUnit_Number == unit)
 | |
|       amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
 | |
|     else if (eCSSUnit_Percent == unit)
 | |
|       amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
 | |
|     else
 | |
|       amount = CalcLength(PresContext(), mComputedStyle, aCSSValue,
 | |
|                           aFontSizeInflation);
 | |
| 
 | |
|     if (NS_MATHML_SIGN_PLUS == aSign)
 | |
|       aValueToUpdate += amount;
 | |
|     else if (NS_MATHML_SIGN_MINUS == aSign)
 | |
|       aValueToUpdate -= amount;
 | |
|     else
 | |
|       aValueToUpdate = amount;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
 | |
|                                   ReflowOutput& aDesiredSize,
 | |
|                                   const ReflowInput& aReflowInput,
 | |
|                                   nsReflowStatus& aStatus) {
 | |
|   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 | |
| 
 | |
|   mPresentationData.flags &= ~NS_MATHML_ERROR;
 | |
|   ProcessAttributes();
 | |
| 
 | |
|   ///////////////
 | |
|   // Let the base class format our content like an inferred mrow
 | |
|   nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
 | |
|                                  aStatus);
 | |
|   // NS_ASSERTION(aStatus.IsComplete(), "bad status");
 | |
| }
 | |
| 
 | |
| /* virtual */
 | |
| nsresult nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
 | |
|                                      ReflowOutput& aDesiredSize) {
 | |
|   nsresult rv = nsMathMLContainerFrame::Place(aDrawTarget, false, aDesiredSize);
 | |
|   if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
 | |
|     DidReflowChildren(PrincipalChildList().FirstChild());
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   nscoord height = aDesiredSize.BlockStartAscent();
 | |
|   nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
 | |
|   // The REC says:
 | |
|   //
 | |
|   // "The lspace attribute ('leading' space) specifies the horizontal location
 | |
|   // of the positioning point of the child content with respect to the
 | |
|   // positioning point of the mpadded element. By default they coincide, and
 | |
|   // therefore absolute values for lspace have the same effect as relative
 | |
|   // values."
 | |
|   //
 | |
|   // "MathML renderers should ensure that, except for the effects of the
 | |
|   // attributes, the relative spacing between the contents of the mpadded
 | |
|   // element and surrounding MathML elements would not be modified by replacing
 | |
|   // an mpadded element with an mrow element with the same content, even if
 | |
|   // linebreaking occurs within the mpadded element."
 | |
|   //
 | |
|   // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
 | |
|   //
 | |
|   // "In those discussions, the terms leading and trailing are used to specify
 | |
|   // a side of an object when which side to use depends on the directionality;
 | |
|   // ie. leading means left in LTR but right in RTL."
 | |
|   // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
 | |
|   nscoord lspace = 0;
 | |
|   // In MathML3, "width" will be the bounding box width and "advancewidth" will
 | |
|   // refer "to the horizontal distance between the positioning point of the
 | |
|   // mpadded and the positioning point for the following content".  MathML2
 | |
|   // doesn't make the distinction.
 | |
|   nscoord width = aDesiredSize.Width();
 | |
|   nscoord voffset = 0;
 | |
| 
 | |
|   int32_t pseudoUnit;
 | |
|   nscoord initialWidth = width;
 | |
|   float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
 | |
| 
 | |
|   // update width
 | |
|   pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
 | |
|                    ? NS_MATHML_PSEUDO_UNIT_WIDTH
 | |
|                    : mWidthPseudoUnit;
 | |
|   UpdateValue(mWidthSign, pseudoUnit, mWidth, aDesiredSize, width,
 | |
|               fontSizeInflation);
 | |
|   width = std::max(0, width);
 | |
| 
 | |
|   // update "height" (this is the ascent in the terminology of the REC)
 | |
|   pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
 | |
|                    ? NS_MATHML_PSEUDO_UNIT_HEIGHT
 | |
|                    : mHeightPseudoUnit;
 | |
|   UpdateValue(mHeightSign, pseudoUnit, mHeight, aDesiredSize, height,
 | |
|               fontSizeInflation);
 | |
|   height = std::max(0, height);
 | |
| 
 | |
|   // update "depth" (this is the descent in the terminology of the REC)
 | |
|   pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
 | |
|                    ? NS_MATHML_PSEUDO_UNIT_DEPTH
 | |
|                    : mDepthPseudoUnit;
 | |
|   UpdateValue(mDepthSign, pseudoUnit, mDepth, aDesiredSize, depth,
 | |
|               fontSizeInflation);
 | |
|   depth = std::max(0, depth);
 | |
| 
 | |
|   // update lspace
 | |
|   if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
 | |
|     pseudoUnit = mLeadingSpacePseudoUnit;
 | |
|     UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace, aDesiredSize,
 | |
|                 lspace, fontSizeInflation);
 | |
|   }
 | |
| 
 | |
|   // update voffset
 | |
|   if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
 | |
|     pseudoUnit = mVerticalOffsetPseudoUnit;
 | |
|     UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset, aDesiredSize,
 | |
|                 voffset, fontSizeInflation);
 | |
|   }
 | |
|   // do the padding now that we have everything
 | |
|   // The idea here is to maintain the invariant that <mpadded>...</mpadded>
 | |
|   // (i.e., with no attributes) looks the same as <mrow>...</mrow>. But when
 | |
|   // there are attributes, tweak our metrics and move children to achieve the
 | |
|   // desired visual effects.
 | |
| 
 | |
|   const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
 | |
|   if ((isRTL ? mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) {
 | |
|     // there was padding on the left. dismiss the left italic correction now
 | |
|     // (so that our parent won't correct us)
 | |
|     mBoundingMetrics.leftBearing = 0;
 | |
|   }
 | |
| 
 | |
|   if ((isRTL ? mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) {
 | |
|     // there was padding on the right. dismiss the right italic correction now
 | |
|     // (so that our parent won't correct us)
 | |
|     mBoundingMetrics.width = width;
 | |
|     mBoundingMetrics.rightBearing = mBoundingMetrics.width;
 | |
|   }
 | |
| 
 | |
|   nscoord dx = (isRTL ? width - initialWidth - lspace : lspace);
 | |
| 
 | |
|   aDesiredSize.SetBlockStartAscent(height);
 | |
|   aDesiredSize.Width() = mBoundingMetrics.width;
 | |
|   aDesiredSize.Height() = depth + aDesiredSize.BlockStartAscent();
 | |
|   mBoundingMetrics.ascent = height;
 | |
|   mBoundingMetrics.descent = depth;
 | |
|   aDesiredSize.mBoundingMetrics = mBoundingMetrics;
 | |
| 
 | |
|   mReference.x = 0;
 | |
|   mReference.y = aDesiredSize.BlockStartAscent();
 | |
| 
 | |
|   if (aPlaceOrigin) {
 | |
|     // Finish reflowing child frames, positioning their origins.
 | |
|     PositionRowChildFrames(dx, aDesiredSize.BlockStartAscent() - voffset);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* virtual */
 | |
| nsresult nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget* aDrawTarget,
 | |
|                                                ReflowOutput& aDesiredSize) {
 | |
|   ProcessAttributes();
 | |
|   return Place(aDrawTarget, false, aDesiredSize);
 | |
| }
 |