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