nsMathMLContainerFrame.cpp (59674B)
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 "nsMathMLContainerFrame.h" 8 9 #include "gfxContext.h" 10 #include "gfxUtils.h" 11 #include "mozilla/Likely.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/dom/MathMLElement.h" 14 #include "mozilla/gfx/2D.h" 15 #include "nsContentUtils.h" 16 #include "nsDisplayList.h" 17 #include "nsGkAtoms.h" 18 #include "nsIScriptError.h" 19 #include "nsLayoutUtils.h" 20 #include "nsNameSpaceManager.h" 21 #include "nsPresContext.h" 22 23 using namespace mozilla; 24 using namespace mozilla::gfx; 25 26 // 27 // nsMathMLContainerFrame implementation 28 // 29 30 NS_QUERYFRAME_HEAD(nsMathMLContainerFrame) 31 NS_QUERYFRAME_ENTRY(nsIMathMLFrame) 32 NS_QUERYFRAME_ENTRY(nsMathMLContainerFrame) 33 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 34 35 /* ///////////// 36 * nsIMathMLFrame - support methods for stretchy elements 37 * ============================================================================= 38 */ 39 40 static bool IsForeignChild(const nsIFrame* aFrame) { 41 // This counts nsMathMLmathBlockFrame as a foreign child, because it 42 // uses block reflow 43 return !aFrame->IsMathMLFrame() || aFrame->IsBlockFrame(); 44 } 45 46 NS_DECLARE_FRAME_PROPERTY_DELETABLE(HTMLReflowOutputProperty, ReflowOutput) 47 48 /* static */ 49 void nsMathMLContainerFrame::SaveReflowAndBoundingMetricsFor( 50 nsIFrame* aFrame, const ReflowOutput& aReflowOutput, 51 const nsBoundingMetrics& aBoundingMetrics) { 52 ReflowOutput* reflowOutput = new ReflowOutput(aReflowOutput); 53 reflowOutput->mBoundingMetrics = aBoundingMetrics; 54 aFrame->SetProperty(HTMLReflowOutputProperty(), reflowOutput); 55 } 56 57 // helper method to facilitate getting the reflow and bounding metrics 58 /* static */ 59 void nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor( 60 nsIFrame* aFrame, ReflowOutput& aReflowOutput, 61 nsBoundingMetrics& aBoundingMetrics, MathMLFrameType* aMathMLFrameType) { 62 MOZ_ASSERT(aFrame, "null arg"); 63 64 ReflowOutput* reflowOutput = aFrame->GetProperty(HTMLReflowOutputProperty()); 65 66 // IMPORTANT: This function is only meant to be called in Place() methods 67 // where it is assumed that SaveReflowAndBoundingMetricsFor has recorded the 68 // information. 69 NS_ASSERTION(reflowOutput, "Didn't SaveReflowAndBoundingMetricsFor frame!"); 70 if (reflowOutput) { 71 aReflowOutput = *reflowOutput; 72 aBoundingMetrics = reflowOutput->mBoundingMetrics; 73 } 74 75 if (aMathMLFrameType) { 76 if (!IsForeignChild(aFrame)) { 77 nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame); 78 if (mathMLFrame) { 79 *aMathMLFrameType = mathMLFrame->GetMathMLFrameType(); 80 return; 81 } 82 } 83 *aMathMLFrameType = MathMLFrameType::Unknown; 84 } 85 } 86 87 void nsMathMLContainerFrame::ClearSavedChildMetrics() { 88 nsIFrame* childFrame = mFrames.FirstChild(); 89 while (childFrame) { 90 childFrame->RemoveProperty(HTMLReflowOutputProperty()); 91 childFrame = childFrame->GetNextSibling(); 92 } 93 } 94 95 nsMargin nsMathMLContainerFrame::GetBorderPaddingForPlace( 96 const PlaceFlags& aFlags) { 97 if (aFlags.contains(PlaceFlag::IgnoreBorderPadding)) { 98 return nsMargin(); 99 } 100 101 if (aFlags.contains(PlaceFlag::IntrinsicSize)) { 102 // Bug 1910859: Should we provide separate left and right border/padding? 103 return nsMargin(0, IntrinsicISizeOffsets().BorderPadding(), 0, 0); 104 } 105 106 return GetUsedBorderAndPadding(); 107 } 108 109 /* static */ 110 nsMargin nsMathMLContainerFrame::GetMarginForPlace(const PlaceFlags& aFlags, 111 nsIFrame* aChild) { 112 if (aFlags.contains(PlaceFlag::IntrinsicSize)) { 113 // Bug 1910859: Should we provide separate left and right margin? 114 return nsMargin(0, aChild->IntrinsicISizeOffsets().margin, 0, 0); 115 } 116 117 return aChild->GetUsedMargin(); 118 } 119 120 void nsMathMLContainerFrame::InflateReflowAndBoundingMetrics( 121 const nsMargin& aBorderPadding, ReflowOutput& aReflowOutput, 122 nsBoundingMetrics& aBoundingMetrics) { 123 // Bug 1910858: It is not really clear what is the right way to update the 124 // ink bounding box when adding border or padding. Below, we assume that 125 // border/padding inflate it. 126 aBoundingMetrics.rightBearing += aBorderPadding.LeftRight(); 127 aBoundingMetrics.width += aBorderPadding.LeftRight(); 128 aReflowOutput.mBoundingMetrics = aBoundingMetrics; 129 aReflowOutput.Width() += aBorderPadding.LeftRight(); 130 aReflowOutput.SetBlockStartAscent(aReflowOutput.BlockStartAscent() + 131 aBorderPadding.top); 132 aReflowOutput.Height() += aBorderPadding.TopBottom(); 133 } 134 135 nsMathMLContainerFrame::WidthAndHeightForPlaceAdjustment 136 nsMathMLContainerFrame::GetWidthAndHeightForPlaceAdjustment( 137 const PlaceFlags& aFlags) { 138 WidthAndHeightForPlaceAdjustment sizes; 139 if (aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight)) { 140 return sizes; 141 } 142 const nsStylePosition* stylePos = StylePosition(); 143 const auto& width = stylePos->mWidth; 144 // TODO: Resolve percentages. 145 // https://github.com/w3c/mathml-core/issues/76 146 if (width.ConvertsToLength()) { 147 sizes.width = Some(width.ToLength()); 148 } 149 if (!aFlags.contains(PlaceFlag::IntrinsicSize)) { 150 // TODO: Resolve percentages. 151 // https://github.com/w3c/mathml-core/issues/77 152 const auto& height = stylePos->mHeight; 153 if (height.ConvertsToLength()) { 154 sizes.height = Some(height.ToLength()); 155 } 156 } 157 return sizes; 158 } 159 160 nscoord nsMathMLContainerFrame::ApplyAdjustmentForWidthAndHeight( 161 const PlaceFlags& aFlags, const WidthAndHeightForPlaceAdjustment& aSizes, 162 ReflowOutput& aReflowOutput, nsBoundingMetrics& aBoundingMetrics) { 163 nscoord shiftX = 0; 164 if (aSizes.width) { 165 MOZ_ASSERT(!aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight)); 166 auto width = *aSizes.width; 167 auto oldWidth = aReflowOutput.Width(); 168 if (IsMathContentBoxHorizontallyCentered()) { 169 shiftX = (width - oldWidth) / 2; 170 } else if (StyleVisibility()->mDirection == StyleDirection::Rtl) { 171 shiftX = width - oldWidth; 172 } 173 aBoundingMetrics.leftBearing = 0; 174 aBoundingMetrics.rightBearing = width; 175 aBoundingMetrics.width = width; 176 aReflowOutput.mBoundingMetrics = aBoundingMetrics; 177 aReflowOutput.Width() = width; 178 } 179 if (aSizes.height) { 180 MOZ_ASSERT(!aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight)); 181 MOZ_ASSERT(!aFlags.contains(PlaceFlag::IntrinsicSize)); 182 auto height = *aSizes.height; 183 aReflowOutput.Height() = height; 184 } 185 return shiftX; 186 } 187 188 // helper to get the preferred size that a container frame should use to fire 189 // the stretch on its stretchy child frames. 190 void nsMathMLContainerFrame::GetPreferredStretchSize( 191 DrawTarget* aDrawTarget, uint32_t aOptions, 192 nsStretchDirection aStretchDirection, 193 nsBoundingMetrics& aPreferredStretchSize) { 194 if (aOptions & STRETCH_CONSIDER_ACTUAL_SIZE) { 195 // when our actual size is ok, just use it 196 aPreferredStretchSize = mBoundingMetrics; 197 } else if (aOptions & STRETCH_CONSIDER_EMBELLISHMENTS) { 198 // compute our up-to-date size using Place(), without border/padding. 199 ReflowOutput reflowOutput(GetWritingMode()); 200 PlaceFlags flags(PlaceFlag::MeasureOnly, PlaceFlag::IgnoreBorderPadding); 201 Place(aDrawTarget, flags, reflowOutput); 202 aPreferredStretchSize = reflowOutput.mBoundingMetrics; 203 } else { 204 // compute a size that includes embellishments iff the container stretches 205 // in the same direction as the embellished operator. 206 bool stretchAll = mPresentationData.flags.contains( 207 aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL 208 ? MathMLPresentationFlag::StretchAllChildrenVertically 209 : MathMLPresentationFlag::StretchAllChildrenHorizontally); 210 NS_ASSERTION(aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL || 211 aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL, 212 "You must specify a direction in which to stretch"); 213 NS_ASSERTION(mEmbellishData.flags.contains( 214 MathMLEmbellishFlag::EmbellishedOperator) || 215 stretchAll, 216 "invalid call to GetPreferredStretchSize"); 217 bool firstTime = true; 218 nsBoundingMetrics bm, bmChild; 219 nsIFrame* childFrame = stretchAll ? PrincipalChildList().FirstChild() 220 : mPresentationData.baseFrame; 221 while (childFrame) { 222 // initializations in case this child happens not to be a MathML frame 223 nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame); 224 if (mathMLFrame) { 225 nsEmbellishData embellishData; 226 nsPresentationData presentationData; 227 mathMLFrame->GetEmbellishData(embellishData); 228 mathMLFrame->GetPresentationData(presentationData); 229 if (embellishData.flags.contains( 230 MathMLEmbellishFlag::EmbellishedOperator) && 231 embellishData.direction == aStretchDirection && 232 presentationData.baseFrame) { 233 // embellishements are not included, only consider the inner first 234 // child itself 235 // XXXkt Does that mean the core descendent frame should be used 236 // instead of the base child? 237 nsIMathMLFrame* mathMLchildFrame = 238 do_QueryFrame(presentationData.baseFrame); 239 if (mathMLchildFrame) { 240 mathMLFrame = mathMLchildFrame; 241 } 242 } 243 mathMLFrame->GetBoundingMetrics(bmChild); 244 } else { 245 ReflowOutput unused(GetWritingMode()); 246 GetReflowAndBoundingMetricsFor(childFrame, unused, bmChild); 247 } 248 249 if (firstTime) { 250 firstTime = false; 251 bm = bmChild; 252 if (!stretchAll) { 253 // we may get here for cases such as <msup><mo>...</mo> ... </msup>, 254 // or <maction>...<mo>...</mo></maction>. 255 break; 256 } 257 } else { 258 if (aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL) { 259 // if we get here, it means this is container that will stack its 260 // children vertically and fire an horizontal stretch on each them. 261 // This is the case for \munder, \mover, \munderover. We just sum-up 262 // the size vertically. 263 bm.descent += bmChild.ascent + bmChild.descent; 264 // Sometimes non-spacing marks (when width is zero) are positioned 265 // to the left of the origin, but it is the distance between left 266 // and right bearing that is important rather than the offsets from 267 // the origin. 268 if (bmChild.width == 0) { 269 bmChild.rightBearing -= bmChild.leftBearing; 270 bmChild.leftBearing = 0; 271 } 272 if (bm.leftBearing > bmChild.leftBearing) { 273 bm.leftBearing = bmChild.leftBearing; 274 } 275 if (bm.rightBearing < bmChild.rightBearing) { 276 bm.rightBearing = bmChild.rightBearing; 277 } 278 } else if (aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL) { 279 // just sum-up the sizes horizontally. 280 bm += bmChild; 281 } else { 282 NS_ERROR("unexpected case in GetPreferredStretchSize"); 283 break; 284 } 285 } 286 childFrame = childFrame->GetNextSibling(); 287 } 288 aPreferredStretchSize = bm; 289 } 290 } 291 292 NS_IMETHODIMP 293 nsMathMLContainerFrame::Stretch(DrawTarget* aDrawTarget, 294 nsStretchDirection aStretchDirection, 295 nsBoundingMetrics& aContainerSize, 296 ReflowOutput& aDesiredStretchSize) { 297 if (mEmbellishData.flags.contains(MathMLEmbellishFlag::EmbellishedOperator)) { 298 if (mPresentationData.flags.contains(MathMLPresentationFlag::StretchDone)) { 299 NS_WARNING("it is wrong to fire stretch more than once on a frame"); 300 return NS_OK; 301 } 302 mPresentationData.flags += MathMLPresentationFlag::StretchDone; 303 304 // Pass the stretch to the base child ... 305 306 nsIFrame* baseFrame = mPresentationData.baseFrame; 307 if (baseFrame) { 308 nsIMathMLFrame* mathMLFrame = do_QueryFrame(baseFrame); 309 NS_ASSERTION(mathMLFrame, "Something is wrong somewhere"); 310 if (mathMLFrame) { 311 // And the trick is that the child's rect.x is still holding the 312 // descent, and rect.y is still holding the ascent ... 313 ReflowOutput childSize(aDesiredStretchSize); 314 GetReflowAndBoundingMetricsFor(baseFrame, childSize, 315 childSize.mBoundingMetrics); 316 317 // See if we should downsize and confine the stretch to us... 318 // XXX there may be other cases where we can downsize the stretch, 319 // e.g., the first ∑ might appear big in the following situation 320 // <math xmlns='http://www.w3.org/1998/Math/MathML'> 321 // <mstyle> 322 // <msub> 323 // <msub><mo>∑</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub> 324 // <msub><mo>∑</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub> 325 // </msub> 326 // </mstyle> 327 // </math> 328 nsBoundingMetrics containerSize = aContainerSize; 329 if (aStretchDirection != mEmbellishData.direction && 330 mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED) { 331 NS_ASSERTION( 332 mEmbellishData.direction != NS_STRETCH_DIRECTION_DEFAULT, 333 "Stretches may have a default direction, operators can not."); 334 if (mPresentationData.flags.contains( 335 mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL 336 ? MathMLPresentationFlag::StretchAllChildrenVertically 337 : MathMLPresentationFlag:: 338 StretchAllChildrenHorizontally)) { 339 GetPreferredStretchSize(aDrawTarget, 0, mEmbellishData.direction, 340 containerSize); 341 // Stop further recalculations 342 aStretchDirection = mEmbellishData.direction; 343 } else { 344 // We aren't going to stretch the child, so just use the child 345 // metrics. 346 containerSize = childSize.mBoundingMetrics; 347 } 348 } 349 350 // do the stretching... 351 mathMLFrame->Stretch(aDrawTarget, aStretchDirection, containerSize, 352 childSize); 353 // store the updated metrics 354 SaveReflowAndBoundingMetricsFor(baseFrame, childSize, 355 childSize.mBoundingMetrics); 356 357 // Remember the siblings which were _deferred_. 358 // Now that this embellished child may have changed, we need to 359 // fire the stretch on its siblings using our updated size 360 361 if (mPresentationData.flags.contains( 362 MathMLPresentationFlag::StretchAllChildrenVertically) || 363 mPresentationData.flags.contains( 364 MathMLPresentationFlag::StretchAllChildrenHorizontally)) { 365 nsStretchDirection stretchDir = 366 mPresentationData.flags.contains( 367 MathMLPresentationFlag::StretchAllChildrenVertically) 368 ? NS_STRETCH_DIRECTION_VERTICAL 369 : NS_STRETCH_DIRECTION_HORIZONTAL; 370 371 GetPreferredStretchSize(aDrawTarget, STRETCH_CONSIDER_EMBELLISHMENTS, 372 stretchDir, containerSize); 373 374 nsIFrame* childFrame = mFrames.FirstChild(); 375 while (childFrame) { 376 if (childFrame != mPresentationData.baseFrame) { 377 mathMLFrame = do_QueryFrame(childFrame); 378 if (mathMLFrame) { 379 // retrieve the metrics that was stored at the previous pass 380 GetReflowAndBoundingMetricsFor(childFrame, childSize, 381 childSize.mBoundingMetrics); 382 // do the stretching... 383 mathMLFrame->Stretch(aDrawTarget, stretchDir, containerSize, 384 childSize); 385 // store the updated metrics 386 SaveReflowAndBoundingMetricsFor(childFrame, childSize, 387 childSize.mBoundingMetrics); 388 } 389 } 390 childFrame = childFrame->GetNextSibling(); 391 } 392 } 393 394 // re-position all our children 395 PlaceFlags flags; 396 Place(aDrawTarget, flags, aDesiredStretchSize); 397 398 // If our parent is not embellished, it means we are the outermost 399 // embellished container and so we put the spacing, otherwise we don't 400 // include the spacing, the outermost embellished container will take 401 // care of it. 402 403 nsEmbellishData parentData; 404 GetEmbellishDataFrom(GetParent(), parentData); 405 // ensure that we are the embellished child, not just a sibling 406 // (need to test coreFrame since <mfrac> resets other things) 407 if (parentData.coreFrame != mEmbellishData.coreFrame) { 408 // (we fetch values from the core since they may use units that depend 409 // on style data, and style changes could have occurred in the core 410 // since our last visit there) 411 nsEmbellishData coreData; 412 GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData); 413 414 mBoundingMetrics.width += 415 coreData.leadingSpace + coreData.trailingSpace; 416 aDesiredStretchSize.Width() = mBoundingMetrics.width; 417 aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width; 418 419 nscoord dx = StyleVisibility()->mDirection == StyleDirection::Rtl 420 ? coreData.trailingSpace 421 : coreData.leadingSpace; 422 if (dx != 0) { 423 mBoundingMetrics.leftBearing += dx; 424 mBoundingMetrics.rightBearing += dx; 425 aDesiredStretchSize.mBoundingMetrics.leftBearing += dx; 426 aDesiredStretchSize.mBoundingMetrics.rightBearing += dx; 427 428 nsIFrame* childFrame = mFrames.FirstChild(); 429 while (childFrame) { 430 childFrame->SetPosition(childFrame->GetPosition() + 431 nsPoint(dx, 0)); 432 childFrame = childFrame->GetNextSibling(); 433 } 434 } 435 } 436 437 // Finished with these: 438 ClearSavedChildMetrics(); 439 // Set our overflow area 440 GatherAndStoreOverflow(&aDesiredStretchSize); 441 } 442 } 443 } 444 return NS_OK; 445 } 446 447 nsresult nsMathMLContainerFrame::FinalizeReflow(DrawTarget* aDrawTarget, 448 ReflowOutput& aDesiredSize) { 449 // During reflow, we use rect.x and rect.y as placeholders for the child's 450 // ascent and descent in expectation of a stretch command. Hence we need to 451 // ensure that a stretch command will actually be fired later on, after 452 // exiting from our reflow. If the stretch is not fired, the rect.x, and 453 // rect.y will remain with inappropriate data causing children to be 454 // improperly positioned. This helper method checks to see if our parent will 455 // fire a stretch command targeted at us. If not, we go ahead and fire an 456 // involutive stretch on ourselves. This will clear all the rect.x and rect.y, 457 // and return our desired size. 458 459 // First, complete the post-reflow hook. 460 // We use the information in our children rectangles to position them. 461 // If placeOrigin==false, then Place() will not touch rect.x, and rect.y. 462 // They will still be holding the ascent and descent for each child. 463 464 // The first clause caters for any non-embellished container. 465 // The second clause is for a container which won't fire stretch even though 466 // it is embellished, e.g., as in <mfrac><mo>...</mo> ... </mfrac>, the test 467 // is convoluted because it excludes the particular case of the core 468 // <mo>...</mo> itself. 469 // (<mo> needs to fire stretch on its MathMLChar in any case to initialize it) 470 bool placeOrigin = 471 !mEmbellishData.flags.contains( 472 MathMLEmbellishFlag::EmbellishedOperator) || 473 (mEmbellishData.coreFrame != this && !mPresentationData.baseFrame && 474 mEmbellishData.direction == NS_STRETCH_DIRECTION_UNSUPPORTED); 475 PlaceFlags flags; 476 if (!placeOrigin) { 477 flags += PlaceFlag::MeasureOnly; 478 } 479 Place(aDrawTarget, flags, aDesiredSize); 480 481 bool parentWillFireStretch = false; 482 if (!placeOrigin) { 483 // This means the rect.x and rect.y of our children were not set!! 484 // Don't go without checking to see if our parent will later fire a 485 // Stretch() command targeted at us. The Stretch() will cause the rect.x and 486 // rect.y to clear... 487 nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetParent()); 488 if (mathMLFrame) { 489 nsEmbellishData embellishData; 490 nsPresentationData presentationData; 491 mathMLFrame->GetEmbellishData(embellishData); 492 mathMLFrame->GetPresentationData(presentationData); 493 if (presentationData.flags.contains( 494 MathMLPresentationFlag::StretchAllChildrenVertically) || 495 presentationData.flags.contains( 496 MathMLPresentationFlag::StretchAllChildrenHorizontally) || 497 (embellishData.flags.contains( 498 MathMLEmbellishFlag::EmbellishedOperator) && 499 presentationData.baseFrame == this)) { 500 parentWillFireStretch = true; 501 } 502 } 503 if (!parentWillFireStretch) { 504 // There is nobody who will fire the stretch for us, we do it ourselves! 505 506 bool stretchAll = 507 /* mPresentationData.flags.contains(MathMLPresentationFlag::StretchAllChildrenVertically) 508 || */ 509 mPresentationData.flags.contains( 510 MathMLPresentationFlag::StretchAllChildrenHorizontally); 511 512 nsStretchDirection stretchDir; 513 if (mEmbellishData.coreFrame == 514 this || /* case of a bare <mo>...</mo> itself */ 515 (mEmbellishData.direction == NS_STRETCH_DIRECTION_HORIZONTAL && 516 stretchAll) || /* or <mover><mo>...</mo>...</mover>, or friends */ 517 mEmbellishData.direction == 518 NS_STRETCH_DIRECTION_UNSUPPORTED) { /* Doesn't stretch */ 519 stretchDir = mEmbellishData.direction; 520 } else { 521 // Let the Stretch() call decide the direction. 522 stretchDir = NS_STRETCH_DIRECTION_DEFAULT; 523 } 524 // Use our current size as computed earlier by Place() 525 // The stretch call will detect if this is incorrect and recalculate the 526 // size. 527 nsBoundingMetrics defaultSize = aDesiredSize.mBoundingMetrics; 528 529 Stretch(aDrawTarget, stretchDir, defaultSize, aDesiredSize); 530 #ifdef DEBUG 531 { 532 // The Place() call above didn't request FinishReflowChild(), 533 // so let's check that we eventually did through Stretch(). 534 for (nsIFrame* childFrame : PrincipalChildList()) { 535 NS_ASSERTION(!childFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW), 536 "DidReflow() was never called"); 537 } 538 } 539 #endif 540 } 541 } 542 543 // Also return our bounding metrics 544 aDesiredSize.mBoundingMetrics = mBoundingMetrics; 545 546 // see if we should fix the spacing 547 FixInterFrameSpacing(aDesiredSize); 548 549 if (!parentWillFireStretch) { 550 // Not expecting a stretch. 551 // Finished with these: 552 ClearSavedChildMetrics(); 553 // Set our overflow area. 554 GatherAndStoreOverflow(&aDesiredSize); 555 } 556 557 mPresentationData.flags -= MathMLPresentationFlag::StretchDone; 558 return NS_OK; 559 } 560 561 /* ///////////// 562 * nsIMathMLFrame - support methods for scripting elements (nested frames 563 * within msub, msup, msubsup, munder, mover, munderover, mmultiscripts, 564 * mfrac, mroot, mtable). 565 * ============================================================================= 566 */ 567 568 // helper to let the update of presentation data pass through 569 // a subtree that may contain non-mathml container frames 570 /* static */ 571 void nsMathMLContainerFrame::PropagatePresentationDataFor( 572 nsIFrame* aFrame, MathMLPresentationFlags aFlagsValues, 573 MathMLPresentationFlags aFlagsToUpdate) { 574 if (!aFrame || aFlagsToUpdate.isEmpty()) { 575 return; 576 } 577 nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame); 578 if (mathMLFrame) { 579 // update 580 mathMLFrame->UpdatePresentationData(aFlagsValues, aFlagsToUpdate); 581 // propagate using the base method to make sure that the control 582 // is passed on to MathML frames that may be overloading the method 583 mathMLFrame->UpdatePresentationDataFromChildAt(0, -1, aFlagsValues, 584 aFlagsToUpdate); 585 } else { 586 // propagate down the subtrees 587 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { 588 PropagatePresentationDataFor(childFrame, aFlagsValues, aFlagsToUpdate); 589 } 590 } 591 } 592 593 /* static */ 594 void nsMathMLContainerFrame::PropagatePresentationDataFromChildAt( 595 nsIFrame* aParentFrame, int32_t aFirstChildIndex, int32_t aLastChildIndex, 596 MathMLPresentationFlags aFlagsValues, 597 MathMLPresentationFlags aFlagsToUpdate) { 598 if (!aParentFrame || aFlagsToUpdate.isEmpty()) { 599 return; 600 } 601 int32_t index = 0; 602 for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) { 603 if ((index >= aFirstChildIndex) && 604 ((aLastChildIndex <= 0) || 605 ((aLastChildIndex > 0) && (index <= aLastChildIndex)))) { 606 PropagatePresentationDataFor(childFrame, aFlagsValues, aFlagsToUpdate); 607 } 608 index++; 609 } 610 } 611 612 /* ////////////////// 613 * Frame construction 614 * ============================================================================= 615 */ 616 617 void nsMathMLContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 618 const nsDisplayListSet& aLists) { 619 BuildDisplayListForInline(aBuilder, aLists); 620 } 621 622 // Note that this method re-builds the automatic data in the children -- not 623 // in aParentFrame itself (except for those particular operations that the 624 // parent frame may do in its TransmitAutomaticData()). 625 /* static */ 626 void nsMathMLContainerFrame::RebuildAutomaticDataForChildren( 627 nsIFrame* aParentFrame) { 628 // 1. As we descend the tree, make each child frame inherit data from 629 // the parent 630 // 2. As we ascend the tree, transmit any specific change that we want 631 // down the subtrees 632 for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) { 633 nsIMathMLFrame* childMathMLFrame = do_QueryFrame(childFrame); 634 if (childMathMLFrame) { 635 childMathMLFrame->InheritAutomaticData(aParentFrame); 636 } 637 RebuildAutomaticDataForChildren(childFrame); 638 } 639 nsIMathMLFrame* mathMLFrame = do_QueryFrame(aParentFrame); 640 if (mathMLFrame) { 641 mathMLFrame->TransmitAutomaticData(); 642 } 643 } 644 645 /* static */ 646 nsresult nsMathMLContainerFrame::ReLayoutChildren(nsIFrame* aParentFrame) { 647 if (!aParentFrame) { 648 return NS_OK; 649 } 650 651 // walk-up to the first frame that is a MathML frame, stop if we reach <math> 652 nsIFrame* frame = aParentFrame; 653 while (1) { 654 nsIFrame* parent = frame->GetParent(); 655 if (!parent || !parent->GetContent()) { 656 break; 657 } 658 659 // stop if it is a MathML frame 660 nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame); 661 if (mathMLFrame) { 662 break; 663 } 664 665 // stop if we reach the root <math> tag 666 nsIContent* content = frame->GetContent(); 667 NS_ASSERTION(content, "dangling frame without a content node"); 668 if (!content) { 669 break; 670 } 671 if (content->IsMathMLElement(nsGkAtoms::math)) { 672 break; 673 } 674 675 frame = parent; 676 } 677 678 // re-sync the presentation data and embellishment data of our children 679 RebuildAutomaticDataForChildren(frame); 680 681 // Ask our parent frame to reflow us 682 nsIFrame* parent = frame->GetParent(); 683 NS_ASSERTION(parent, "No parent to pass the reflow request up to"); 684 if (!parent) { 685 return NS_OK; 686 } 687 688 frame->PresShell()->FrameNeedsReflow( 689 frame, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); 690 691 return NS_OK; 692 } 693 694 // There are precise rules governing children of a MathML frame, 695 // and properties such as the scriptlevel depends on those rules. 696 // Hence for things to work, callers must use Append/Insert/etc wisely. 697 698 nsresult nsMathMLContainerFrame::ChildListChanged() { 699 // If this is an embellished frame we need to rebuild the 700 // embellished hierarchy by walking-up to the parent of the 701 // outermost embellished container. 702 nsIFrame* frame = this; 703 if (mEmbellishData.coreFrame) { 704 nsIFrame* parent = GetParent(); 705 nsEmbellishData embellishData; 706 for (; parent; frame = parent, parent = parent->GetParent()) { 707 GetEmbellishDataFrom(parent, embellishData); 708 if (embellishData.coreFrame != mEmbellishData.coreFrame) { 709 break; 710 } 711 } 712 } 713 return ReLayoutChildren(frame); 714 } 715 716 void nsMathMLContainerFrame::AppendFrames(ChildListID aListID, 717 nsFrameList&& aFrameList) { 718 MOZ_ASSERT(aListID == FrameChildListID::Principal); 719 mFrames.AppendFrames(this, std::move(aFrameList)); 720 ChildListChanged(); 721 } 722 723 void nsMathMLContainerFrame::InsertFrames( 724 ChildListID aListID, nsIFrame* aPrevFrame, 725 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { 726 MOZ_ASSERT(aListID == FrameChildListID::Principal); 727 mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList)); 728 ChildListChanged(); 729 } 730 731 void nsMathMLContainerFrame::RemoveFrame(DestroyContext& aContext, 732 ChildListID aListID, 733 nsIFrame* aOldFrame) { 734 MOZ_ASSERT(aListID == FrameChildListID::Principal); 735 mFrames.DestroyFrame(aContext, aOldFrame); 736 ChildListChanged(); 737 } 738 739 void nsMathMLContainerFrame::GatherAndStoreOverflow(ReflowOutput* aMetrics) { 740 mBlockStartAscent = aMetrics->BlockStartAscent(); 741 742 // nsIFrame::FinishAndStoreOverflow likes the overflow area to include the 743 // frame rectangle. 744 aMetrics->SetOverflowAreasToDesiredBounds(); 745 746 ComputeCustomOverflow(aMetrics->mOverflowAreas); 747 748 // mBoundingMetrics does not necessarily include content of <mpadded> 749 // elements whose mBoundingMetrics may not be representative of the true 750 // bounds, and doesn't include the CSS2 outline rectangles of children, so 751 // make such to include child overflow areas. 752 UnionChildOverflow(aMetrics->mOverflowAreas); 753 754 FinishAndStoreOverflow(aMetrics); 755 } 756 757 bool nsMathMLContainerFrame::ComputeCustomOverflow( 758 OverflowAreas& aOverflowAreas) { 759 // All non-child-frame content such as nsMathMLChars (and most child-frame 760 // content) is included in mBoundingMetrics. 761 nsRect boundingBox( 762 mBoundingMetrics.leftBearing, mBlockStartAscent - mBoundingMetrics.ascent, 763 mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing, 764 mBoundingMetrics.ascent + mBoundingMetrics.descent); 765 766 // REVIEW: Maybe this should contribute only to ink overflow 767 // and not scrollable? 768 aOverflowAreas.UnionAllWith(boundingBox); 769 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); 770 } 771 772 void nsMathMLContainerFrame::ReflowChild(nsIFrame* aChildFrame, 773 nsPresContext* aPresContext, 774 ReflowOutput& aDesiredSize, 775 const ReflowInput& aReflowInput, 776 nsReflowStatus& aStatus) { 777 // Having foreign/hybrid children, e.g., from html markups, is not defined by 778 // the MathML spec. But it can happen in practice, e.g., <html:img> allows us 779 // to do some cool demos... or we may have a child that is an nsInlineFrame 780 // from a generated content such as :before { content: open-quote } or 781 // :after { content: close-quote }. Unfortunately, the other frames out-there 782 // may expect their own invariants that are not met when we mix things. 783 // Hence we do not claim their support, but we will nevertheless attempt to 784 // keep them in the flow, if we can get their desired size. We observed that 785 // most frames may be reflowed generically, but nsInlineFrames need extra 786 // care. 787 788 #ifdef DEBUG 789 nsInlineFrame* inlineFrame = do_QueryFrame(aChildFrame); 790 NS_ASSERTION(!inlineFrame, "Inline frames should be wrapped in blocks"); 791 #endif 792 793 nsContainerFrame::ReflowChild(aChildFrame, aPresContext, aDesiredSize, 794 aReflowInput, 0, 0, 795 ReflowChildFlags::NoMoveFrame, aStatus); 796 797 if (aDesiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) { 798 // This will be suitable for inline frames, which are wrapped in a block. 799 nscoord ascent; 800 WritingMode wm = aDesiredSize.GetWritingMode(); 801 if (!nsLayoutUtils::GetLastLineBaseline(wm, aChildFrame, &ascent)) { 802 // We don't expect any other block children so just place the frame on 803 // the baseline instead of going through DidReflow() and 804 // GetBaseline(). This is what nsIFrame::GetBaseline() will do anyway. 805 aDesiredSize.SetBlockStartAscent(aDesiredSize.BSize(wm)); 806 } else { 807 aDesiredSize.SetBlockStartAscent(ascent); 808 } 809 } 810 if (IsForeignChild(aChildFrame)) { 811 // use ComputeTightBounds API as aDesiredSize.mBoundingMetrics is not set. 812 nsRect r = aChildFrame->ComputeTightBounds( 813 aReflowInput.mRenderingContext->GetDrawTarget()); 814 aDesiredSize.mBoundingMetrics.leftBearing = r.x; 815 aDesiredSize.mBoundingMetrics.rightBearing = r.XMost(); 816 aDesiredSize.mBoundingMetrics.ascent = 817 aDesiredSize.BlockStartAscent() - r.y; 818 aDesiredSize.mBoundingMetrics.descent = 819 r.YMost() - aDesiredSize.BlockStartAscent(); 820 aDesiredSize.mBoundingMetrics.width = aDesiredSize.Width(); 821 } 822 } 823 824 void nsMathMLContainerFrame::Reflow(nsPresContext* aPresContext, 825 ReflowOutput& aDesiredSize, 826 const ReflowInput& aReflowInput, 827 nsReflowStatus& aStatus) { 828 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { 829 return; 830 } 831 832 MarkInReflow(); 833 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 834 835 aDesiredSize.Width() = aDesiredSize.Height() = 0; 836 aDesiredSize.SetBlockStartAscent(0); 837 aDesiredSize.mBoundingMetrics = nsBoundingMetrics(); 838 839 ///////////// 840 // Reflow children 841 // Asking each child to cache its bounding metrics 842 843 nsReflowStatus childStatus; 844 nsIFrame* childFrame = mFrames.FirstChild(); 845 while (childFrame) { 846 ReflowOutput childDesiredSize(aReflowInput); 847 WritingMode wm = childFrame->GetWritingMode(); 848 LogicalSize availSize = aReflowInput.ComputedSize(wm); 849 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 850 ReflowInput childReflowInput(aPresContext, aReflowInput, childFrame, 851 availSize); 852 ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowInput, 853 childStatus); 854 // NS_ASSERTION(childStatus.IsComplete(), "bad status"); 855 SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize, 856 childDesiredSize.mBoundingMetrics); 857 childFrame = childFrame->GetNextSibling(); 858 } 859 860 ///////////// 861 // If we are a container which is entitled to stretch its children, then we 862 // ask our stretchy children to stretch themselves 863 864 // The stretching of siblings of an embellished child is _deferred_ until 865 // after finishing the stretching of the embellished child - bug 117652 866 867 DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget(); 868 869 if (!mEmbellishData.flags.contains( 870 MathMLEmbellishFlag::EmbellishedOperator) && 871 (mPresentationData.flags.contains( 872 MathMLPresentationFlag::StretchAllChildrenVertically) || 873 mPresentationData.flags.contains( 874 MathMLPresentationFlag::StretchAllChildrenHorizontally))) { 875 // get the stretchy direction 876 nsStretchDirection stretchDir = 877 mPresentationData.flags.contains( 878 MathMLPresentationFlag::StretchAllChildrenVertically) 879 ? NS_STRETCH_DIRECTION_VERTICAL 880 : NS_STRETCH_DIRECTION_HORIZONTAL; 881 882 // what size should we use to stretch our stretchy children 883 // We don't use STRETCH_CONSIDER_ACTUAL_SIZE -- because our size is not 884 // known yet We don't use STRETCH_CONSIDER_EMBELLISHMENTS -- because we 885 // don't want to include them in the caculations of the size of stretchy 886 // elements 887 nsBoundingMetrics containerSize; 888 GetPreferredStretchSize(drawTarget, 0, stretchDir, containerSize); 889 890 // fire the stretch on each child 891 childFrame = mFrames.FirstChild(); 892 while (childFrame) { 893 nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame); 894 if (mathMLFrame) { 895 // retrieve the metrics that was stored at the previous pass 896 ReflowOutput childDesiredSize(aReflowInput); 897 GetReflowAndBoundingMetricsFor(childFrame, childDesiredSize, 898 childDesiredSize.mBoundingMetrics); 899 900 mathMLFrame->Stretch(drawTarget, stretchDir, containerSize, 901 childDesiredSize); 902 // store the updated metrics 903 SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize, 904 childDesiredSize.mBoundingMetrics); 905 } 906 childFrame = childFrame->GetNextSibling(); 907 } 908 } 909 910 ///////////// 911 // Place children now by re-adjusting the origins to align the baselines 912 FinalizeReflow(drawTarget, aDesiredSize); 913 } 914 915 static nscoord AddInterFrameSpacingToSize(ReflowOutput& aDesiredSize, 916 nsMathMLContainerFrame* aFrame); 917 918 /* virtual */ 919 void nsMathMLContainerFrame::MarkIntrinsicISizesDirty() { 920 mIntrinsicISize = NS_INTRINSIC_ISIZE_UNKNOWN; 921 nsContainerFrame::MarkIntrinsicISizesDirty(); 922 } 923 924 void nsMathMLContainerFrame::UpdateIntrinsicISize( 925 gfxContext* aRenderingContext) { 926 if (mIntrinsicISize == NS_INTRINSIC_ISIZE_UNKNOWN) { 927 ReflowOutput desiredSize(GetWritingMode()); 928 GetIntrinsicISizeMetrics(aRenderingContext, desiredSize); 929 930 // Include the additional width added by FixInterFrameSpacing to ensure 931 // consistent width calculations. 932 AddInterFrameSpacingToSize(desiredSize, this); 933 934 // ReflowOuput::mSize corresponds to the border box, but callers 935 // expect padding/border are not included. 936 mIntrinsicISize = desiredSize.ISize(GetWritingMode()) - 937 IntrinsicISizeOffsets().BorderPadding(); 938 } 939 } 940 941 nscoord nsMathMLContainerFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 942 IntrinsicISizeType aType) { 943 UpdateIntrinsicISize(aInput.mContext); 944 return mIntrinsicISize; 945 } 946 947 /* virtual */ 948 void nsMathMLContainerFrame::GetIntrinsicISizeMetrics( 949 gfxContext* aRenderingContext, ReflowOutput& aDesiredSize) { 950 // Get child widths 951 nsIFrame* childFrame = mFrames.FirstChild(); 952 while (childFrame) { 953 ReflowOutput childDesiredSize(GetWritingMode()); // ??? 954 955 nsMathMLContainerFrame* containerFrame = do_QueryFrame(childFrame); 956 if (containerFrame) { 957 containerFrame->GetIntrinsicISizeMetrics(aRenderingContext, 958 childDesiredSize); 959 } else { 960 nscoord width = nsLayoutUtils::IntrinsicForContainer( 961 aRenderingContext, childFrame, IntrinsicISizeType::PrefISize); 962 963 childDesiredSize.Width() = width; 964 childDesiredSize.mBoundingMetrics.width = width; 965 childDesiredSize.mBoundingMetrics.leftBearing = 0; 966 childDesiredSize.mBoundingMetrics.rightBearing = width; 967 968 nscoord x, xMost; 969 if (NS_SUCCEEDED(childFrame->GetPrefWidthTightBounds(aRenderingContext, 970 &x, &xMost))) { 971 childDesiredSize.mBoundingMetrics.leftBearing = x; 972 childDesiredSize.mBoundingMetrics.rightBearing = xMost; 973 } 974 } 975 976 SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize, 977 childDesiredSize.mBoundingMetrics); 978 979 childFrame = childFrame->GetNextSibling(); 980 } 981 982 // Measure 983 PlaceFlags flags(PlaceFlag::IntrinsicSize, PlaceFlag::MeasureOnly); 984 Place(aRenderingContext->GetDrawTarget(), flags, aDesiredSize); 985 986 ClearSavedChildMetrics(); 987 } 988 989 // see spacing table in Chapter 18, TeXBook (p.170) 990 // Our table isn't quite identical to TeX because operators have 991 // built-in values for lspace & rspace in the Operator Dictionary. 992 static int32_t kInterFrameSpacingTable[size_t(MathMLFrameType::Count)][size_t( 993 MathMLFrameType::Count)] = { 994 // in units of muspace. 995 // upper half of the byte is set if the 996 // spacing is not to be used for scriptlevel > 0 997 998 /* Ord OpOrd OpInv OpUsr Inner Italic Upright */ 999 /*Ord */ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00}, 1000 /*OpOrd */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1001 /*OpInv */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1002 /*OpUsr */ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01}, 1003 /*Inner */ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01}, 1004 /*Italic */ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01}, 1005 /*Upright*/ {0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00}}; 1006 1007 #define GET_INTERSPACE(scriptlevel_, frametype1_, frametype2_, space_) \ 1008 /* no space if there is a frame that we know nothing about */ \ 1009 if (frametype1_ == MathMLFrameType::Unknown || \ 1010 frametype2_ == MathMLFrameType::Unknown) \ 1011 space_ = 0; \ 1012 else { \ 1013 space_ = \ 1014 kInterFrameSpacingTable[size_t(frametype1_)][size_t(frametype2_)]; \ 1015 space_ = (scriptlevel_ > 0 && (space_ & 0xF0)) \ 1016 ? 0 /* spacing is disabled */ \ 1017 : space_ & 0x0F; \ 1018 } 1019 1020 // This function computes the inter-space between two frames. However, 1021 // since invisible operators need special treatment, the inter-space may 1022 // be delayed when an invisible operator is encountered. In this case, 1023 // the function will carry the inter-space forward until it is determined 1024 // that it can be applied properly (i.e., until we encounter a visible 1025 // frame where to decide whether to accept or reject the inter-space). 1026 // aFromFrameType: remembers the frame when the carry-forward initiated. 1027 // aCarrySpace: keeps track of the inter-space that is delayed. 1028 // @returns: current inter-space (which is 0 when the true inter-space is 1029 // delayed -- and thus has no effect since the frame is invisible anyway). 1030 static nscoord GetInterFrameSpacing(int32_t aScriptLevel, 1031 MathMLFrameType aFirstFrameType, 1032 MathMLFrameType aSecondFrameType, 1033 MathMLFrameType* aFromFrameType, // IN/OUT 1034 int32_t* aCarrySpace) // IN/OUT 1035 { 1036 MathMLFrameType firstType = aFirstFrameType; 1037 MathMLFrameType secondType = aSecondFrameType; 1038 1039 int32_t space; 1040 GET_INTERSPACE(aScriptLevel, firstType, secondType, space); 1041 1042 // feedback control to avoid the inter-space to be added when not necessary 1043 if (secondType == MathMLFrameType::OperatorInvisible) { 1044 // see if we should start to carry the space forward until we 1045 // encounter a visible frame 1046 if (*aFromFrameType == MathMLFrameType::Unknown) { 1047 *aFromFrameType = firstType; 1048 *aCarrySpace = space; 1049 } 1050 // keep carrying *aCarrySpace forward, while returning 0 for this stage 1051 space = 0; 1052 } else if (*aFromFrameType != MathMLFrameType::Unknown) { 1053 // no carry-forward anymore, get the real inter-space between 1054 // the two frames of interest 1055 1056 firstType = *aFromFrameType; 1057 1058 // But... the invisible operator that we encountered earlier could 1059 // be sitting between italic and upright identifiers, e.g., 1060 // 1061 // 1. <mi>sin</mi> <mo>⁡</mo> <mi>x</mi> 1062 // 2. <mi>x</mi> <mo>&InvisibileTime;</mo> <mi>sin</mi> 1063 // 1064 // the trick to get the inter-space in either situation 1065 // is to promote "<mi>sin</mi><mo>⁡</mo>" and 1066 // "<mo>&InvisibileTime;</mo><mi>sin</mi>" to user-defined operators... 1067 if (firstType == MathMLFrameType::UprightIdentifier) { 1068 firstType = MathMLFrameType::OperatorUserDefined; 1069 } else if (secondType == MathMLFrameType::UprightIdentifier) { 1070 secondType = MathMLFrameType::OperatorUserDefined; 1071 } 1072 1073 GET_INTERSPACE(aScriptLevel, firstType, secondType, space); 1074 1075 // Now, we have two values: the computed space and the space that 1076 // has been carried forward until now. Which value do we pick? 1077 // If the second type is an operator (e.g., fence), it already has 1078 // built-in lspace & rspace, so we let them win. Otherwise we pick 1079 // the max between the two values that we have. 1080 if (secondType != MathMLFrameType::OperatorOrdinary && 1081 space < *aCarrySpace) { 1082 space = *aCarrySpace; 1083 } 1084 1085 // reset everything now that the carry-forward is done 1086 *aFromFrameType = MathMLFrameType::Unknown; 1087 *aCarrySpace = 0; 1088 } 1089 1090 return space; 1091 } 1092 1093 static nscoord GetThinSpace(const nsStyleFont* aStyleFont) { 1094 return aStyleFont->mFont.size.ScaledBy(3.0f / 18.0f).ToAppUnits(); 1095 } 1096 1097 class nsMathMLContainerFrame::RowChildFrameIterator { 1098 public: 1099 explicit RowChildFrameIterator(nsMathMLContainerFrame* aParentFrame, 1100 const PlaceFlags& aFlags) 1101 : mParentFrame(aParentFrame), 1102 mReflowOutput(aParentFrame->GetWritingMode()), 1103 mX(0), 1104 mFlags(aFlags), 1105 mChildFrameType(MathMLFrameType::Unknown), 1106 mCarrySpace(0), 1107 mFromFrameType(MathMLFrameType::Unknown), 1108 mRTL(aParentFrame->StyleVisibility()->mDirection == 1109 StyleDirection::Rtl) { 1110 if (!mRTL) { 1111 mChildFrame = aParentFrame->mFrames.FirstChild(); 1112 } else { 1113 mChildFrame = aParentFrame->mFrames.LastChild(); 1114 } 1115 1116 if (!mChildFrame) { 1117 return; 1118 } 1119 1120 InitMetricsForChild(); 1121 } 1122 1123 RowChildFrameIterator& operator++() { 1124 // add child size + italic correction 1125 mX += mReflowOutput.mBoundingMetrics.width + mItalicCorrection; 1126 mX += mMargin.LeftRight(); 1127 1128 if (!mRTL) { 1129 mChildFrame = mChildFrame->GetNextSibling(); 1130 } else { 1131 mChildFrame = mChildFrame->GetPrevSibling(); 1132 } 1133 1134 if (!mChildFrame) { 1135 return *this; 1136 } 1137 1138 MathMLFrameType prevFrameType = mChildFrameType; 1139 InitMetricsForChild(); 1140 1141 // add inter frame spacing 1142 const nsStyleFont* font = mParentFrame->StyleFont(); 1143 nscoord space = 1144 GetInterFrameSpacing(font->mMathDepth, prevFrameType, mChildFrameType, 1145 &mFromFrameType, &mCarrySpace); 1146 mX += space * GetThinSpace(font); 1147 return *this; 1148 } 1149 1150 nsIFrame* Frame() const { return mChildFrame; } 1151 nscoord X() const { return mX; } 1152 const ReflowOutput& GetReflowOutput() const { return mReflowOutput; } 1153 nscoord Ascent() const { return mReflowOutput.BlockStartAscent(); } 1154 nscoord Descent() const { 1155 return mReflowOutput.Height() - mReflowOutput.BlockStartAscent(); 1156 } 1157 const nsMargin& Margin() const { return mMargin; } 1158 const nsBoundingMetrics& BoundingMetrics() const { 1159 return mReflowOutput.mBoundingMetrics; 1160 } 1161 1162 private: 1163 const nsMathMLContainerFrame* mParentFrame; 1164 nsIFrame* mChildFrame; 1165 ReflowOutput mReflowOutput; 1166 nscoord mX; 1167 const PlaceFlags mFlags; 1168 nsMargin mMargin; 1169 1170 nscoord mItalicCorrection; 1171 MathMLFrameType mChildFrameType; 1172 int32_t mCarrySpace; 1173 MathMLFrameType mFromFrameType; 1174 1175 bool mRTL; 1176 1177 void InitMetricsForChild() { 1178 GetReflowAndBoundingMetricsFor(mChildFrame, mReflowOutput, 1179 mReflowOutput.mBoundingMetrics, 1180 &mChildFrameType); 1181 mMargin = GetMarginForPlace(mFlags, mChildFrame); 1182 nscoord leftCorrection, rightCorrection; 1183 GetItalicCorrection(mReflowOutput.mBoundingMetrics, leftCorrection, 1184 rightCorrection); 1185 if (!mChildFrame->GetPrevSibling() && 1186 mParentFrame->GetContent()->IsMathMLElement(nsGkAtoms::msqrt)) { 1187 // Remove leading correction in <msqrt> because the sqrt glyph itself is 1188 // there first. 1189 if (!mRTL) { 1190 leftCorrection = 0; 1191 } else { 1192 rightCorrection = 0; 1193 } 1194 } 1195 // add left correction -- this fixes the problem of the italic 'f' 1196 // e.g., <mo>q</mo> <mi>f</mi> <mo>I</mo> 1197 mX += leftCorrection; 1198 mItalicCorrection = rightCorrection; 1199 } 1200 }; 1201 1202 /* virtual */ 1203 void nsMathMLContainerFrame::Place(DrawTarget* aDrawTarget, 1204 const PlaceFlags& aFlags, 1205 ReflowOutput& aDesiredSize) { 1206 // This is needed in case this frame is empty (i.e., no child frames) 1207 mBoundingMetrics = nsBoundingMetrics(); 1208 1209 RowChildFrameIterator child(this, aFlags); 1210 nscoord ascent = 0, descent = 0; 1211 while (child.Frame()) { 1212 nscoord topMargin = child.Margin().top; 1213 nscoord bottomMargin = child.Margin().bottom; 1214 ascent = std::max(ascent, child.Ascent() + topMargin); 1215 descent = std::max(descent, child.Descent() + bottomMargin); 1216 1217 // add the child size 1218 mBoundingMetrics.width = child.X(); 1219 nsBoundingMetrics childBm = child.BoundingMetrics(); 1220 childBm.ascent += topMargin; 1221 childBm.descent += bottomMargin; 1222 childBm.rightBearing += child.Margin().LeftRight(); 1223 childBm.width += child.Margin().LeftRight(); 1224 mBoundingMetrics += childBm; 1225 1226 ++child; 1227 } 1228 1229 // Add the italic correction at the end (including the last child). 1230 // This gives a nice gap between math and non-math frames, and still 1231 // gives the same math inter-spacing in case this frame connects to 1232 // another math frame 1233 mBoundingMetrics.width = child.X(); 1234 1235 aDesiredSize.Width() = std::max(0, mBoundingMetrics.width); 1236 aDesiredSize.Height() = ascent + descent; 1237 aDesiredSize.SetBlockStartAscent(ascent); 1238 aDesiredSize.mBoundingMetrics = mBoundingMetrics; 1239 1240 // Apply inline/block sizes to math content box. 1241 auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags); 1242 nscoord shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize, 1243 mBoundingMetrics); 1244 1245 // Add padding+border. 1246 auto borderPadding = GetBorderPaddingForPlace(aFlags); 1247 InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize, 1248 mBoundingMetrics); 1249 shiftX += borderPadding.left; 1250 1251 mReference.x = 0; 1252 mReference.y = aDesiredSize.BlockStartAscent(); 1253 1254 ////////////////// 1255 // Place Children 1256 if (!aFlags.contains(PlaceFlag::MeasureOnly)) { 1257 PositionRowChildFrames(shiftX, aDesiredSize.BlockStartAscent()); 1258 } 1259 } 1260 1261 void nsMathMLContainerFrame::PlaceAsMrow(DrawTarget* aDrawTarget, 1262 const PlaceFlags& aFlags, 1263 ReflowOutput& aDesiredSize) { 1264 nsMathMLContainerFrame::Place(aDrawTarget, aFlags, aDesiredSize); 1265 } 1266 1267 void nsMathMLContainerFrame::PositionRowChildFrames(nscoord aOffsetX, 1268 nscoord aBaseline) { 1269 PlaceFlags flags; 1270 RowChildFrameIterator child(this, flags); 1271 while (child.Frame()) { 1272 nscoord dx = aOffsetX + child.X() + child.Margin().left; 1273 nscoord dy = aBaseline - child.Ascent(); 1274 FinishReflowChild(child.Frame(), PresContext(), child.GetReflowOutput(), 1275 nullptr, dx, dy, ReflowChildFlags::Default); 1276 ++child; 1277 } 1278 } 1279 1280 // helpers to fix the inter-spacing when <math> is the only parent 1281 // e.g., it fixes <math> <mi>f</mi> <mo>q</mo> <mi>f</mi> <mo>I</mo> </math> 1282 1283 static nscoord GetInterFrameSpacingFor(int32_t aScriptLevel, 1284 nsIFrame* aParentFrame, 1285 nsIFrame* aChildFrame) { 1286 nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild(); 1287 if (!childFrame || aChildFrame == childFrame) { 1288 return 0; 1289 } 1290 1291 int32_t carrySpace = 0; 1292 MathMLFrameType fromFrameType = MathMLFrameType::Unknown; 1293 MathMLFrameType prevFrameType = MathMLFrameType::Unknown; 1294 MathMLFrameType childFrameType = 1295 nsMathMLFrame::GetMathMLFrameTypeFor(childFrame); 1296 childFrame = childFrame->GetNextSibling(); 1297 while (childFrame) { 1298 prevFrameType = childFrameType; 1299 childFrameType = nsMathMLFrame::GetMathMLFrameTypeFor(childFrame); 1300 nscoord space = 1301 GetInterFrameSpacing(aScriptLevel, prevFrameType, childFrameType, 1302 &fromFrameType, &carrySpace); 1303 if (aChildFrame == childFrame) { 1304 // get thinspace 1305 ComputedStyle* parentContext = aParentFrame->Style(); 1306 nscoord thinSpace = GetThinSpace(parentContext->StyleFont()); 1307 // we are done 1308 return space * thinSpace; 1309 } 1310 childFrame = childFrame->GetNextSibling(); 1311 } 1312 1313 MOZ_ASSERT_UNREACHABLE("child not in the childlist of its parent"); 1314 return 0; 1315 } 1316 1317 static nscoord AddInterFrameSpacingToSize(ReflowOutput& aDesiredSize, 1318 nsMathMLContainerFrame* aFrame) { 1319 nscoord gap = 0; 1320 nsIFrame* parent = aFrame->GetParent(); 1321 nsIContent* parentContent = parent->GetContent(); 1322 if (MOZ_UNLIKELY(!parentContent)) { 1323 return 0; 1324 } 1325 if (parentContent->IsAnyOfMathMLElements(nsGkAtoms::math, nsGkAtoms::mtd)) { 1326 gap = GetInterFrameSpacingFor(aFrame->StyleFont()->mMathDepth, parent, 1327 aFrame); 1328 // add our own italic correction 1329 nscoord leftCorrection = 0, italicCorrection = 0; 1330 nsMathMLContainerFrame::GetItalicCorrection( 1331 aDesiredSize.mBoundingMetrics, leftCorrection, italicCorrection); 1332 gap += leftCorrection; 1333 if (gap) { 1334 aDesiredSize.mBoundingMetrics.leftBearing += gap; 1335 aDesiredSize.mBoundingMetrics.rightBearing += gap; 1336 aDesiredSize.mBoundingMetrics.width += gap; 1337 aDesiredSize.Width() += gap; 1338 } 1339 aDesiredSize.mBoundingMetrics.width += italicCorrection; 1340 aDesiredSize.Width() += italicCorrection; 1341 } 1342 return gap; 1343 } 1344 1345 nscoord nsMathMLContainerFrame::FixInterFrameSpacing( 1346 ReflowOutput& aDesiredSize) { 1347 nscoord gap = 0; 1348 gap = AddInterFrameSpacingToSize(aDesiredSize, this); 1349 if (gap) { 1350 // Shift our children to account for the correction 1351 nsIFrame* childFrame = mFrames.FirstChild(); 1352 while (childFrame) { 1353 childFrame->SetPosition(childFrame->GetPosition() + nsPoint(gap, 0)); 1354 childFrame = childFrame->GetNextSibling(); 1355 } 1356 } 1357 return gap; 1358 } 1359 1360 // helper used by mstyle, mphantom, mpadded and mrow in their implementations 1361 // of TransmitAutomaticData(). 1362 nsresult nsMathMLContainerFrame::TransmitAutomaticDataForMrowLikeElement() { 1363 // 1364 // One loop to check both conditions below: 1365 // 1366 // 1) whether all the children of the mrow-like element are space-like. 1367 // 1368 // The REC defines the following elements to be "space-like": 1369 // * an mstyle, mphantom, or mpadded element, all of whose direct 1370 // sub-expressions are space-like; 1371 // * an mrow all of whose direct sub-expressions are space-like. 1372 // 1373 // 2) whether all but one child of the mrow-like element are space-like and 1374 // this non-space-like child is an embellished operator. 1375 // 1376 // The REC defines the following elements to be embellished operators: 1377 // * one of the elements mstyle, mphantom, or mpadded, such that an mrow 1378 // containing the same arguments would be an embellished operator; 1379 // * an mrow whose arguments consist (in any order) of one embellished 1380 // operator and zero or more space-like elements. 1381 // 1382 nsIFrame *childFrame, *baseFrame; 1383 bool embellishedOpFound = false; 1384 nsEmbellishData embellishData; 1385 1386 for (childFrame = PrincipalChildList().FirstChild(); childFrame; 1387 childFrame = childFrame->GetNextSibling()) { 1388 nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame); 1389 if (!mathMLFrame) { 1390 break; 1391 } 1392 if (!mathMLFrame->IsSpaceLike()) { 1393 if (embellishedOpFound) { 1394 break; 1395 } 1396 baseFrame = childFrame; 1397 GetEmbellishDataFrom(baseFrame, embellishData); 1398 if (!embellishData.flags.contains( 1399 MathMLEmbellishFlag::EmbellishedOperator)) { 1400 break; 1401 } 1402 embellishedOpFound = true; 1403 } 1404 } 1405 1406 if (!childFrame) { 1407 // we successfully went to the end of the loop. This means that one of 1408 // condition 1) or 2) holds. 1409 if (!embellishedOpFound) { 1410 // the mrow-like element is space-like. 1411 mPresentationData.flags += MathMLPresentationFlag::SpaceLike; 1412 } else { 1413 // the mrow-like element is an embellished operator. 1414 // let the state of the embellished operator found bubble to us. 1415 mPresentationData.baseFrame = baseFrame; 1416 mEmbellishData = embellishData; 1417 } 1418 } 1419 1420 if (childFrame || !embellishedOpFound) { 1421 // The element is not embellished operator 1422 mPresentationData.baseFrame = nullptr; 1423 mEmbellishData.flags.clear(); 1424 mEmbellishData.coreFrame = nullptr; 1425 mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; 1426 mEmbellishData.leadingSpace = 0; 1427 mEmbellishData.trailingSpace = 0; 1428 } 1429 1430 if (childFrame || embellishedOpFound) { 1431 // The element is not space-like 1432 mPresentationData.flags -= MathMLPresentationFlag::SpaceLike; 1433 } 1434 1435 return NS_OK; 1436 } 1437 1438 /*static*/ 1439 void nsMathMLContainerFrame::PropagateFrameFlagFor(nsIFrame* aFrame, 1440 nsFrameState aFlags) { 1441 if (!aFrame || !aFlags) { 1442 return; 1443 } 1444 1445 aFrame->AddStateBits(aFlags); 1446 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { 1447 PropagateFrameFlagFor(childFrame, aFlags); 1448 } 1449 } 1450 1451 nsresult nsMathMLContainerFrame::ReportErrorToConsole( 1452 const char* errorMsgId, const nsTArray<nsString>& aParams) { 1453 return nsContentUtils::ReportToConsole( 1454 nsIScriptError::errorFlag, "Layout: MathML"_ns, mContent->OwnerDoc(), 1455 nsContentUtils::eMATHML_PROPERTIES, errorMsgId, aParams); 1456 } 1457 1458 nsresult nsMathMLContainerFrame::ReportParseError(const char16_t* aAttribute, 1459 const char16_t* aValue) { 1460 AutoTArray<nsString, 3> argv; 1461 argv.AppendElement(aValue); 1462 argv.AppendElement(aAttribute); 1463 argv.AppendElement(nsDependentAtomString(mContent->NodeInfo()->NameAtom())); 1464 return ReportErrorToConsole("AttributeParsingError", argv); 1465 } 1466 1467 nsresult nsMathMLContainerFrame::ReportChildCountError() { 1468 AutoTArray<nsString, 1> arg = { 1469 nsDependentAtomString(mContent->NodeInfo()->NameAtom())}; 1470 return ReportErrorToConsole("ChildCountIncorrect", arg); 1471 } 1472 1473 nsresult nsMathMLContainerFrame::ReportInvalidChildError(nsAtom* aChildTag) { 1474 AutoTArray<nsString, 2> argv = { 1475 nsDependentAtomString(aChildTag), 1476 nsDependentAtomString(mContent->NodeInfo()->NameAtom())}; 1477 return ReportErrorToConsole("InvalidChild", argv); 1478 } 1479 1480 //========================== 1481 1482 nsContainerFrame* NS_NewMathMLmathBlockFrame(PresShell* aPresShell, 1483 ComputedStyle* aStyle) { 1484 auto newFrame = new (aPresShell) 1485 nsMathMLmathBlockFrame(aStyle, aPresShell->GetPresContext()); 1486 return newFrame; 1487 } 1488 1489 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame) 1490 1491 NS_QUERYFRAME_HEAD(nsMathMLmathBlockFrame) 1492 NS_QUERYFRAME_ENTRY(nsMathMLmathBlockFrame) 1493 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) 1494 1495 nsContainerFrame* NS_NewMathMLmathInlineFrame(PresShell* aPresShell, 1496 ComputedStyle* aStyle) { 1497 return new (aPresShell) 1498 nsMathMLmathInlineFrame(aStyle, aPresShell->GetPresContext()); 1499 } 1500 1501 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathInlineFrame) 1502 1503 NS_QUERYFRAME_HEAD(nsMathMLmathInlineFrame) 1504 NS_QUERYFRAME_ENTRY(nsIMathMLFrame) 1505 NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)