1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/mathml/nsMathMLmpaddedFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,446 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 + 1.10 +#include "nsMathMLmpaddedFrame.h" 1.11 +#include "nsMathMLElement.h" 1.12 +#include "mozilla/gfx/2D.h" 1.13 +#include <algorithm> 1.14 + 1.15 +// 1.16 +// <mpadded> -- adjust space around content - implementation 1.17 +// 1.18 + 1.19 +#define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there 1.20 +#define NS_MATHML_SIGN_UNSPECIFIED 0 1.21 +#define NS_MATHML_SIGN_MINUS 1 1.22 +#define NS_MATHML_SIGN_PLUS 2 1.23 + 1.24 +#define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0 1.25 +#define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special 1.26 +#define NS_MATHML_PSEUDO_UNIT_WIDTH 2 1.27 +#define NS_MATHML_PSEUDO_UNIT_HEIGHT 3 1.28 +#define NS_MATHML_PSEUDO_UNIT_DEPTH 4 1.29 +#define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5 1.30 + 1.31 +nsIFrame* 1.32 +NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.33 +{ 1.34 + return new (aPresShell) nsMathMLmpaddedFrame(aContext); 1.35 +} 1.36 + 1.37 +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame) 1.38 + 1.39 +nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() 1.40 +{ 1.41 +} 1.42 + 1.43 +NS_IMETHODIMP 1.44 +nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent) 1.45 +{ 1.46 + // let the base class get the default from our parent 1.47 + nsMathMLContainerFrame::InheritAutomaticData(aParent); 1.48 + 1.49 + mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY; 1.50 + 1.51 + return NS_OK; 1.52 +} 1.53 + 1.54 +void 1.55 +nsMathMLmpaddedFrame::ProcessAttributes() 1.56 +{ 1.57 + /* 1.58 + parse the attributes 1.59 + 1.60 + width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace) 1.61 + height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace) 1.62 + depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace) 1.63 + lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace) 1.64 + voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace) 1.65 + */ 1.66 + 1.67 + nsAutoString value; 1.68 + 1.69 + // width 1.70 + mWidthSign = NS_MATHML_SIGN_INVALID; 1.71 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value); 1.72 + if (!value.IsEmpty()) { 1.73 + if (!ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit)) { 1.74 + ReportParseError(nsGkAtoms::width->GetUTF16String(), value.get()); 1.75 + } 1.76 + } 1.77 + 1.78 + // height 1.79 + mHeightSign = NS_MATHML_SIGN_INVALID; 1.80 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value); 1.81 + if (!value.IsEmpty()) { 1.82 + if (!ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit)) { 1.83 + ReportParseError(nsGkAtoms::height->GetUTF16String(), value.get()); 1.84 + } 1.85 + } 1.86 + 1.87 + // depth 1.88 + mDepthSign = NS_MATHML_SIGN_INVALID; 1.89 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value); 1.90 + if (!value.IsEmpty()) { 1.91 + if (!ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit)) { 1.92 + ReportParseError(nsGkAtoms::depth_->GetUTF16String(), value.get()); 1.93 + } 1.94 + } 1.95 + 1.96 + // lspace 1.97 + mLeadingSpaceSign = NS_MATHML_SIGN_INVALID; 1.98 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value); 1.99 + if (!value.IsEmpty()) { 1.100 + if (!ParseAttribute(value, mLeadingSpaceSign, mLeadingSpace, 1.101 + mLeadingSpacePseudoUnit)) { 1.102 + ReportParseError(nsGkAtoms::lspace_->GetUTF16String(), value.get()); 1.103 + } 1.104 + } 1.105 + 1.106 + // voffset 1.107 + mVerticalOffsetSign = NS_MATHML_SIGN_INVALID; 1.108 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::voffset_, value); 1.109 + if (!value.IsEmpty()) { 1.110 + if (!ParseAttribute(value, mVerticalOffsetSign, mVerticalOffset, 1.111 + mVerticalOffsetPseudoUnit)) { 1.112 + ReportParseError(nsGkAtoms::voffset_->GetUTF16String(), value.get()); 1.113 + } 1.114 + } 1.115 + 1.116 +} 1.117 + 1.118 +// parse an input string in the following format (see bug 148326 for testcases): 1.119 +// [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace) 1.120 +bool 1.121 +nsMathMLmpaddedFrame::ParseAttribute(nsString& aString, 1.122 + int32_t& aSign, 1.123 + nsCSSValue& aCSSValue, 1.124 + int32_t& aPseudoUnit) 1.125 +{ 1.126 + aCSSValue.Reset(); 1.127 + aSign = NS_MATHML_SIGN_INVALID; 1.128 + aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED; 1.129 + aString.CompressWhitespace(); // aString is not a const in this code 1.130 + 1.131 + int32_t stringLength = aString.Length(); 1.132 + if (!stringLength) 1.133 + return false; 1.134 + 1.135 + nsAutoString number, unit; 1.136 + 1.137 + ////////////////////// 1.138 + // see if the sign is there 1.139 + 1.140 + int32_t i = 0; 1.141 + 1.142 + if (aString[0] == '+') { 1.143 + aSign = NS_MATHML_SIGN_PLUS; 1.144 + i++; 1.145 + } 1.146 + else if (aString[0] == '-') { 1.147 + aSign = NS_MATHML_SIGN_MINUS; 1.148 + i++; 1.149 + } 1.150 + else 1.151 + aSign = NS_MATHML_SIGN_UNSPECIFIED; 1.152 + 1.153 + // get the number 1.154 + bool gotDot = false, gotPercent = false; 1.155 + for (; i < stringLength; i++) { 1.156 + char16_t c = aString[i]; 1.157 + if (gotDot && c == '.') { 1.158 + // error - two dots encountered 1.159 + aSign = NS_MATHML_SIGN_INVALID; 1.160 + return false; 1.161 + } 1.162 + 1.163 + if (c == '.') 1.164 + gotDot = true; 1.165 + else if (!nsCRT::IsAsciiDigit(c)) { 1.166 + break; 1.167 + } 1.168 + number.Append(c); 1.169 + } 1.170 + 1.171 + // catch error if we didn't enter the loop above... we could simply initialize 1.172 + // floatValue = 1, to cater for cases such as width="height", but that wouldn't 1.173 + // be in line with the spec which requires an explicit number 1.174 + if (number.IsEmpty()) { 1.175 + aSign = NS_MATHML_SIGN_INVALID; 1.176 + return false; 1.177 + } 1.178 + 1.179 + nsresult errorCode; 1.180 + float floatValue = number.ToFloat(&errorCode); 1.181 + if (NS_FAILED(errorCode)) { 1.182 + aSign = NS_MATHML_SIGN_INVALID; 1.183 + return false; 1.184 + } 1.185 + 1.186 + // see if this is a percentage-based value 1.187 + if (i < stringLength && aString[i] == '%') { 1.188 + i++; 1.189 + gotPercent = true; 1.190 + } 1.191 + 1.192 + // the remainder now should be a css-unit, or a pseudo-unit, or a named-space 1.193 + aString.Right(unit, stringLength - i); 1.194 + 1.195 + if (unit.IsEmpty()) { 1.196 + if (gotPercent) { 1.197 + // case ["+"|"-"] unsigned-number "%" 1.198 + aCSSValue.SetPercentValue(floatValue / 100.0f); 1.199 + aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF; 1.200 + return true; 1.201 + } else { 1.202 + // case ["+"|"-"] unsigned-number 1.203 + // XXXfredw: should we allow non-zero unitless values? See bug 757703. 1.204 + if (!floatValue) { 1.205 + aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number); 1.206 + aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF; 1.207 + return true; 1.208 + } 1.209 + } 1.210 + } 1.211 + else if (unit.EqualsLiteral("width")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH; 1.212 + else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT; 1.213 + else if (unit.EqualsLiteral("depth")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH; 1.214 + else if (!gotPercent) { // percentage can only apply to a pseudo-unit 1.215 + 1.216 + // see if the unit is a named-space 1.217 + if (nsMathMLElement::ParseNamedSpaceValue(unit, aCSSValue, 1.218 + nsMathMLElement:: 1.219 + PARSE_ALLOW_NEGATIVE)) { 1.220 + // re-scale properly, and we know that the unit of the named-space is 'em' 1.221 + floatValue *= aCSSValue.GetFloatValue(); 1.222 + aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM); 1.223 + aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE; 1.224 + return true; 1.225 + } 1.226 + 1.227 + // see if the input was just a CSS value 1.228 + // We are not supposed to have a unitless, percent, negative or namedspace 1.229 + // value here. 1.230 + number.Append(unit); // leave the sign out if it was there 1.231 + if (nsMathMLElement::ParseNumericValue(number, aCSSValue, 1.232 + nsMathMLElement:: 1.233 + PARSE_SUPPRESS_WARNINGS, nullptr)) 1.234 + return true; 1.235 + } 1.236 + 1.237 + // if we enter here, we have a number that will act as a multiplier on a pseudo-unit 1.238 + if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) { 1.239 + if (gotPercent) 1.240 + aCSSValue.SetPercentValue(floatValue / 100.0f); 1.241 + else 1.242 + aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number); 1.243 + 1.244 + return true; 1.245 + } 1.246 + 1.247 + 1.248 +#ifdef DEBUG 1.249 + printf("mpadded: attribute with bad numeric value: %s\n", 1.250 + NS_LossyConvertUTF16toASCII(aString).get()); 1.251 +#endif 1.252 + // if we reach here, it means we encounter an unexpected input 1.253 + aSign = NS_MATHML_SIGN_INVALID; 1.254 + return false; 1.255 +} 1.256 + 1.257 +void 1.258 +nsMathMLmpaddedFrame::UpdateValue(int32_t aSign, 1.259 + int32_t aPseudoUnit, 1.260 + const nsCSSValue& aCSSValue, 1.261 + const nsHTMLReflowMetrics& aDesiredSize, 1.262 + nscoord& aValueToUpdate) const 1.263 +{ 1.264 + nsCSSUnit unit = aCSSValue.GetUnit(); 1.265 + if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) { 1.266 + nscoord scaler = 0, amount = 0; 1.267 + 1.268 + if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) { 1.269 + switch(aPseudoUnit) { 1.270 + case NS_MATHML_PSEUDO_UNIT_WIDTH: 1.271 + scaler = aDesiredSize.Width(); 1.272 + break; 1.273 + 1.274 + case NS_MATHML_PSEUDO_UNIT_HEIGHT: 1.275 + scaler = aDesiredSize.TopAscent(); 1.276 + break; 1.277 + 1.278 + case NS_MATHML_PSEUDO_UNIT_DEPTH: 1.279 + scaler = aDesiredSize.Height() - aDesiredSize.TopAscent(); 1.280 + break; 1.281 + 1.282 + default: 1.283 + // if we ever reach here, it would mean something is wrong 1.284 + // somewhere with the setup and/or the caller 1.285 + NS_ERROR("Unexpected Pseudo Unit"); 1.286 + return; 1.287 + } 1.288 + } 1.289 + 1.290 + if (eCSSUnit_Number == unit) 1.291 + amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue()); 1.292 + else if (eCSSUnit_Percent == unit) 1.293 + amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue()); 1.294 + else 1.295 + amount = CalcLength(PresContext(), mStyleContext, aCSSValue); 1.296 + 1.297 + if (NS_MATHML_SIGN_PLUS == aSign) 1.298 + aValueToUpdate += amount; 1.299 + else if (NS_MATHML_SIGN_MINUS == aSign) 1.300 + aValueToUpdate -= amount; 1.301 + else 1.302 + aValueToUpdate = amount; 1.303 + } 1.304 +} 1.305 + 1.306 +nsresult 1.307 +nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext, 1.308 + nsHTMLReflowMetrics& aDesiredSize, 1.309 + const nsHTMLReflowState& aReflowState, 1.310 + nsReflowStatus& aStatus) 1.311 +{ 1.312 + ProcessAttributes(); 1.313 + 1.314 + /////////////// 1.315 + // Let the base class format our content like an inferred mrow 1.316 + nsresult rv = nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize, 1.317 + aReflowState, aStatus); 1.318 + //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status"); 1.319 + return rv; 1.320 +} 1.321 + 1.322 +/* virtual */ nsresult 1.323 +nsMathMLmpaddedFrame::Place(nsRenderingContext& aRenderingContext, 1.324 + bool aPlaceOrigin, 1.325 + nsHTMLReflowMetrics& aDesiredSize) 1.326 +{ 1.327 + nsresult rv = 1.328 + nsMathMLContainerFrame::Place(aRenderingContext, false, aDesiredSize); 1.329 + if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) { 1.330 + DidReflowChildren(GetFirstPrincipalChild()); 1.331 + return rv; 1.332 + } 1.333 + 1.334 + nscoord height = aDesiredSize.TopAscent(); 1.335 + nscoord depth = aDesiredSize.Height() - aDesiredSize.TopAscent(); 1.336 + // The REC says: 1.337 + // 1.338 + // "The lspace attribute ('leading' space) specifies the horizontal location 1.339 + // of the positioning point of the child content with respect to the 1.340 + // positioning point of the mpadded element. By default they coincide, and 1.341 + // therefore absolute values for lspace have the same effect as relative 1.342 + // values." 1.343 + // 1.344 + // "MathML renderers should ensure that, except for the effects of the 1.345 + // attributes, the relative spacing between the contents of the mpadded 1.346 + // element and surrounding MathML elements would not be modified by replacing 1.347 + // an mpadded element with an mrow element with the same content, even if 1.348 + // linebreaking occurs within the mpadded element." 1.349 + // 1.350 + // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded) 1.351 + // 1.352 + // "In those discussions, the terms leading and trailing are used to specify 1.353 + // a side of an object when which side to use depends on the directionality; 1.354 + // ie. leading means left in LTR but right in RTL." 1.355 + // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math) 1.356 + nscoord lspace = 0; 1.357 + // In MathML3, "width" will be the bounding box width and "advancewidth" will 1.358 + // refer "to the horizontal distance between the positioning point of the 1.359 + // mpadded and the positioning point for the following content". MathML2 1.360 + // doesn't make the distinction. 1.361 + nscoord width = aDesiredSize.Width(); 1.362 + nscoord voffset = 0; 1.363 + 1.364 + int32_t pseudoUnit; 1.365 + nscoord initialWidth = width; 1.366 + 1.367 + // update width 1.368 + pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF) 1.369 + ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit; 1.370 + UpdateValue(mWidthSign, pseudoUnit, mWidth, 1.371 + aDesiredSize, width); 1.372 + width = std::max(0, width); 1.373 + 1.374 + // update "height" (this is the ascent in the terminology of the REC) 1.375 + pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF) 1.376 + ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit; 1.377 + UpdateValue(mHeightSign, pseudoUnit, mHeight, 1.378 + aDesiredSize, height); 1.379 + height = std::max(0, height); 1.380 + 1.381 + // update "depth" (this is the descent in the terminology of the REC) 1.382 + pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF) 1.383 + ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit; 1.384 + UpdateValue(mDepthSign, pseudoUnit, mDepth, 1.385 + aDesiredSize, depth); 1.386 + depth = std::max(0, depth); 1.387 + 1.388 + // update lspace 1.389 + if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) { 1.390 + pseudoUnit = mLeadingSpacePseudoUnit; 1.391 + UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace, 1.392 + aDesiredSize, lspace); 1.393 + } 1.394 + 1.395 + // update voffset 1.396 + if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) { 1.397 + pseudoUnit = mVerticalOffsetPseudoUnit; 1.398 + UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset, 1.399 + aDesiredSize, voffset); 1.400 + } 1.401 + // do the padding now that we have everything 1.402 + // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e., 1.403 + // with no attributes) looks the same as <mrow>...</mrow>. But when there are 1.404 + // attributes, tweak our metrics and move children to achieve the desired visual 1.405 + // effects. 1.406 + 1.407 + if ((StyleVisibility()->mDirection ? 1.408 + mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) { 1.409 + // there was padding on the left. dismiss the left italic correction now 1.410 + // (so that our parent won't correct us) 1.411 + mBoundingMetrics.leftBearing = 0; 1.412 + } 1.413 + 1.414 + if ((StyleVisibility()->mDirection ? 1.415 + mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) { 1.416 + // there was padding on the right. dismiss the right italic correction now 1.417 + // (so that our parent won't correct us) 1.418 + mBoundingMetrics.width = width; 1.419 + mBoundingMetrics.rightBearing = mBoundingMetrics.width; 1.420 + } 1.421 + 1.422 + nscoord dx = (StyleVisibility()->mDirection ? 1.423 + width - initialWidth - lspace : lspace); 1.424 + 1.425 + aDesiredSize.SetTopAscent(height); 1.426 + aDesiredSize.Width() = mBoundingMetrics.width; 1.427 + aDesiredSize.Height() = depth + aDesiredSize.TopAscent(); 1.428 + mBoundingMetrics.ascent = height; 1.429 + mBoundingMetrics.descent = depth; 1.430 + aDesiredSize.mBoundingMetrics = mBoundingMetrics; 1.431 + 1.432 + mReference.x = 0; 1.433 + mReference.y = aDesiredSize.TopAscent(); 1.434 + 1.435 + if (aPlaceOrigin) { 1.436 + // Finish reflowing child frames, positioning their origins. 1.437 + PositionRowChildFrames(dx, aDesiredSize.TopAscent() - voffset); 1.438 + } 1.439 + 1.440 + return NS_OK; 1.441 +} 1.442 + 1.443 +/* virtual */ nsresult 1.444 +nsMathMLmpaddedFrame::MeasureForWidth(nsRenderingContext& aRenderingContext, 1.445 + nsHTMLReflowMetrics& aDesiredSize) 1.446 +{ 1.447 + ProcessAttributes(); 1.448 + return Place(aRenderingContext, false, aDesiredSize); 1.449 +}