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