nsMathMLmunderoverFrame.cpp (30690B)
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 "nsMathMLmunderoverFrame.h" 8 9 #include <algorithm> 10 11 #include "gfxContext.h" 12 #include "gfxMathTable.h" 13 #include "gfxTextRun.h" 14 #include "mozilla/PresShell.h" 15 #include "mozilla/StaticPrefs_mathml.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/MathMLElement.h" 18 #include "nsIMathMLFrame.h" 19 #include "nsLayoutUtils.h" 20 #include "nsMathMLmmultiscriptsFrame.h" 21 #include "nsPresContext.h" 22 23 using namespace mozilla; 24 25 // 26 // <munderover> -- attach an underscript-overscript pair to a base 27 // implementation 28 // <mover> -- attach an overscript to a base - implementation 29 // <munder> -- attach an underscript to a base - implementation 30 // 31 32 nsIFrame* NS_NewMathMLmunderoverFrame(PresShell* aPresShell, 33 ComputedStyle* aStyle) { 34 return new (aPresShell) 35 nsMathMLmunderoverFrame(aStyle, aPresShell->GetPresContext()); 36 } 37 38 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmunderoverFrame) 39 40 nsMathMLmunderoverFrame::~nsMathMLmunderoverFrame() = default; 41 42 nsresult nsMathMLmunderoverFrame::AttributeChanged(int32_t aNameSpaceID, 43 nsAtom* aAttribute, 44 AttrModType aModType) { 45 if (aNameSpaceID == kNameSpaceID_None && 46 (nsGkAtoms::accent == aAttribute || 47 nsGkAtoms::accentunder == aAttribute)) { 48 // When we have automatic data to update within ourselves, we ask our 49 // parent to re-layout its children 50 return ReLayoutChildren(GetParent()); 51 } 52 53 return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, 54 aModType); 55 } 56 57 NS_IMETHODIMP 58 nsMathMLmunderoverFrame::UpdatePresentationData( 59 MathMLPresentationFlags aFlagsValues, 60 MathMLPresentationFlags aFlagsToUpdate) { 61 nsMathMLContainerFrame::UpdatePresentationData(aFlagsValues, aFlagsToUpdate); 62 // disable the stretch-all flag if we are going to act like a 63 // subscript-superscript pair 64 if (mEmbellishData.flags.contains(MathMLEmbellishFlag::MovableLimits) && 65 StyleFont()->mMathStyle == StyleMathStyle::Compact) { 66 mPresentationData.flags -= 67 MathMLPresentationFlag::StretchAllChildrenHorizontally; 68 } else { 69 mPresentationData.flags += 70 MathMLPresentationFlag::StretchAllChildrenHorizontally; 71 } 72 return NS_OK; 73 } 74 75 NS_IMETHODIMP 76 nsMathMLmunderoverFrame::InheritAutomaticData(nsIFrame* aParent) { 77 // let the base class get the default from our parent 78 nsMathMLContainerFrame::InheritAutomaticData(aParent); 79 80 mPresentationData.flags += 81 MathMLPresentationFlag::StretchAllChildrenHorizontally; 82 83 return NS_OK; 84 } 85 86 void nsMathMLmunderoverFrame::Destroy(DestroyContext& aContext) { 87 if (!mPostReflowIncrementScriptLevelCommands.IsEmpty()) { 88 PresShell()->CancelReflowCallback(this); 89 } 90 nsMathMLContainerFrame::Destroy(aContext); 91 } 92 93 uint8_t nsMathMLmunderoverFrame::ScriptIncrement(nsIFrame* aFrame) { 94 nsIFrame* child = mFrames.FirstChild(); 95 if (!aFrame || aFrame == child) { 96 return 0; 97 } 98 child = child->GetNextSibling(); 99 if (aFrame == child) { 100 if (mContent->IsMathMLElement(nsGkAtoms::mover)) { 101 return mIncrementOver ? 1 : 0; 102 } 103 return mIncrementUnder ? 1 : 0; 104 } 105 if (child && aFrame == child->GetNextSibling()) { 106 // must be a over frame of munderover 107 return mIncrementOver ? 1 : 0; 108 } 109 return 0; // frame not found 110 } 111 112 void nsMathMLmunderoverFrame::SetIncrementScriptLevel(uint32_t aChildIndex, 113 bool aIncrement) { 114 nsIFrame* child = PrincipalChildList().FrameAt(aChildIndex); 115 if (!child || !child->GetContent()->IsMathMLElement() || 116 child->GetContent()->GetPrimaryFrame() != child) { 117 return; 118 } 119 120 auto element = dom::MathMLElement::FromNode(child->GetContent()); 121 if (element->GetIncrementScriptLevel() == aIncrement) { 122 return; 123 } 124 125 if (mPostReflowIncrementScriptLevelCommands.IsEmpty()) { 126 PresShell()->PostReflowCallback(this); 127 } 128 129 mPostReflowIncrementScriptLevelCommands.AppendElement( 130 SetIncrementScriptLevelCommand{aChildIndex, aIncrement}); 131 } 132 133 bool nsMathMLmunderoverFrame::ReflowFinished() { 134 SetPendingPostReflowIncrementScriptLevel(); 135 return true; 136 } 137 138 void nsMathMLmunderoverFrame::ReflowCallbackCanceled() { 139 // Do nothing, at this point our work will just be useless. 140 mPostReflowIncrementScriptLevelCommands.Clear(); 141 } 142 143 void nsMathMLmunderoverFrame::SetPendingPostReflowIncrementScriptLevel() { 144 MOZ_ASSERT(!mPostReflowIncrementScriptLevelCommands.IsEmpty()); 145 146 nsTArray<SetIncrementScriptLevelCommand> commands = 147 std::move(mPostReflowIncrementScriptLevelCommands); 148 149 for (const auto& command : commands) { 150 nsIFrame* child = PrincipalChildList().FrameAt(command.mChildIndex); 151 if (!child || !child->GetContent()->IsMathMLElement()) { 152 continue; 153 } 154 155 auto element = dom::MathMLElement::FromNode(child->GetContent()); 156 element->SetIncrementScriptLevel(command.mDoIncrement, true); 157 } 158 } 159 160 NS_IMETHODIMP 161 nsMathMLmunderoverFrame::TransmitAutomaticData() { 162 // At this stage, all our children are in sync and we can fully 163 // resolve our own mEmbellishData struct 164 //--------------------------------------------------------------------- 165 166 /* 167 The REC says: 168 169 As regards munder (respectively mover) : 170 The default value of accentunder is false, unless underscript 171 is an <mo> element or an embellished operator. If underscript is 172 an <mo> element, the value of its accent attribute is used as the 173 default value of accentunder. If underscript is an embellished 174 operator, the accent attribute of the <mo> element at its 175 core is used as the default value. As with all attributes, an 176 explicitly given value overrides the default. 177 178 XXX The winner is the outermost setting in conflicting settings like these: 179 <munder accentunder='true'> 180 <mi>...</mi> 181 <mo accentunder='false'> ... </mo> 182 </munder> 183 184 As regards munderover: 185 The accent and accentunder attributes have the same effect as 186 the attributes with the same names on <mover> and <munder>, 187 respectively. Their default values are also computed in the 188 same manner as described for those elements, with the default 189 value of accent depending on overscript and the default value 190 of accentunder depending on underscript. 191 */ 192 193 nsIFrame* overscriptFrame = nullptr; 194 nsIFrame* underscriptFrame = nullptr; 195 nsIFrame* baseFrame = mFrames.FirstChild(); 196 197 if (baseFrame) { 198 if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder, 199 nsGkAtoms::munderover)) { 200 underscriptFrame = baseFrame->GetNextSibling(); 201 } else { 202 NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover), 203 "mContent->NodeInfo()->NameAtom() not recognized"); 204 overscriptFrame = baseFrame->GetNextSibling(); 205 } 206 } 207 if (underscriptFrame && mContent->IsMathMLElement(nsGkAtoms::munderover)) { 208 overscriptFrame = underscriptFrame->GetNextSibling(); 209 } 210 211 // if our base is an embellished operator, let its state bubble to us (in 212 // particular, this is where we get the flag for 213 // MovableLimits). Our flags are reset to the default 214 // values of false if the base frame isn't embellished. 215 mPresentationData.baseFrame = baseFrame; 216 GetEmbellishDataFrom(baseFrame, mEmbellishData); 217 218 // The default value of accentunder is false, unless the underscript is 219 // embellished and its core <mo> is an accent 220 nsEmbellishData embellishData; 221 nsAutoString value; 222 if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder, 223 nsGkAtoms::munderover)) { 224 GetEmbellishDataFrom(underscriptFrame, embellishData); 225 if (embellishData.flags.contains(MathMLEmbellishFlag::Accent)) { 226 mEmbellishData.flags += MathMLEmbellishFlag::AccentUnder; 227 } else { 228 mEmbellishData.flags -= MathMLEmbellishFlag::AccentUnder; 229 } 230 231 // if we have an accentunder attribute, it overrides what the underscript 232 // said 233 if (mContent->AsElement()->GetAttr(nsGkAtoms::accentunder, value)) { 234 if (value.LowerCaseEqualsLiteral("true")) { 235 mEmbellishData.flags += MathMLEmbellishFlag::AccentUnder; 236 } else if (value.LowerCaseEqualsLiteral("false")) { 237 mEmbellishData.flags -= MathMLEmbellishFlag::AccentUnder; 238 } 239 } else if (mEmbellishData.flags.contains( 240 MathMLEmbellishFlag::AccentUnder)) { 241 AutoTArray<nsString, 1> params; 242 params.AppendElement(mContent->NodeInfo()->NodeName()); 243 PresContext()->Document()->WarnOnceAbout( 244 dom::DeprecatedOperations:: 245 eMathML_DeprecatedMunderNonExplicitAccentunder, 246 false, params); 247 } 248 } 249 250 // The default value of accent is false, unless the overscript is embellished 251 // and its core <mo> is an accent 252 if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover, 253 nsGkAtoms::munderover)) { 254 GetEmbellishDataFrom(overscriptFrame, embellishData); 255 if (embellishData.flags.contains(MathMLEmbellishFlag::Accent)) { 256 mEmbellishData.flags += MathMLEmbellishFlag::AccentOver; 257 } else { 258 mEmbellishData.flags -= MathMLEmbellishFlag::AccentOver; 259 } 260 261 // if we have an accent attribute, it overrides what the overscript said 262 if (mContent->AsElement()->GetAttr(nsGkAtoms::accent, value)) { 263 if (value.LowerCaseEqualsLiteral("true")) { 264 mEmbellishData.flags += MathMLEmbellishFlag::AccentOver; 265 } else if (value.LowerCaseEqualsLiteral("false")) { 266 mEmbellishData.flags -= MathMLEmbellishFlag::AccentOver; 267 } 268 } else if (mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentOver)) { 269 AutoTArray<nsString, 1> params; 270 params.AppendElement(mContent->NodeInfo()->NodeName()); 271 PresContext()->Document()->WarnOnceAbout( 272 dom::DeprecatedOperations::eMathML_DeprecatedMoverNonExplicitAccent, 273 false, params); 274 } 275 } 276 277 bool subsupDisplay = 278 mEmbellishData.flags.contains(MathMLEmbellishFlag::MovableLimits) && 279 StyleFont()->mMathStyle == StyleMathStyle::Compact; 280 281 // disable the stretch-all flag if we are going to act like a superscript 282 if (subsupDisplay) { 283 mPresentationData.flags -= 284 MathMLPresentationFlag::StretchAllChildrenHorizontally; 285 } 286 287 // Now transmit any change that we want to our children so that they 288 // can update their mPresentationData structs 289 //--------------------------------------------------------------------- 290 291 /* The REC says: 292 Within underscript, <munderover> always sets displaystyle to "false", 293 but increments scriptlevel by 1 only when accentunder is "false". 294 295 Within overscript, <munderover> always sets displaystyle to "false", 296 but increments scriptlevel by 1 only when accent is "false". 297 298 Within subscript and superscript it increments scriptlevel by 1, and 299 sets displaystyle to "false", but leaves both attributes unchanged within 300 base. 301 302 The TeXBook treats 'over' like a superscript, so p.141 or Rule 13a 303 say it shouldn't be compressed. However, The TeXBook says 304 that math accents and \overline change uncramped styles to their 305 cramped counterparts. 306 */ 307 if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover, 308 nsGkAtoms::munderover)) { 309 mIncrementOver = 310 !mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentOver) || 311 subsupDisplay; 312 SetIncrementScriptLevel(mContent->IsMathMLElement(nsGkAtoms::mover) ? 1 : 2, 313 mIncrementOver); 314 if (mIncrementOver) { 315 PropagateFrameFlagFor(overscriptFrame, NS_FRAME_MATHML_SCRIPT_DESCENDANT); 316 } 317 if (!StaticPrefs::mathml_math_shift_enabled()) { 318 MathMLPresentationFlags flags; 319 if (mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentOver)) { 320 flags += MathMLPresentationFlag::Compressed; 321 } 322 PropagatePresentationDataFor(overscriptFrame, flags, flags); 323 } 324 } 325 /* 326 The TeXBook treats 'under' like a subscript, so p.141 or Rule 13a 327 say it should be compressed 328 */ 329 if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder, 330 nsGkAtoms::munderover)) { 331 mIncrementUnder = 332 !mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentUnder) || 333 subsupDisplay; 334 SetIncrementScriptLevel(1, mIncrementUnder); 335 if (mIncrementUnder) { 336 PropagateFrameFlagFor(underscriptFrame, 337 NS_FRAME_MATHML_SCRIPT_DESCENDANT); 338 } 339 if (!StaticPrefs::mathml_math_shift_enabled()) { 340 PropagatePresentationDataFor(underscriptFrame, 341 MathMLPresentationFlag::Compressed, 342 MathMLPresentationFlag::Compressed); 343 } 344 } 345 346 /* Set flags for dtls font feature settings. 347 348 dtls 349 Dotless Forms 350 This feature provides dotless forms for Math Alphanumeric 351 characters, such as U+1D422 MATHEMATICAL BOLD SMALL I, 352 U+1D423 MATHEMATICAL BOLD SMALL J, U+1D456 353 U+MATHEMATICAL ITALIC SMALL I, U+1D457 MATHEMATICAL ITALIC 354 SMALL J, and so on. 355 The dotless forms are to be used as base forms for placing 356 mathematical accents over them. 357 358 To opt out of this change, add the following to the stylesheet: 359 "font-feature-settings: 'dtls' 0" 360 */ 361 if (overscriptFrame && 362 mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentOver) && 363 !mEmbellishData.flags.contains(MathMLEmbellishFlag::MovableLimits)) { 364 PropagatePresentationDataFor(baseFrame, MathMLPresentationFlag::Dtls, 365 MathMLPresentationFlag::Dtls); 366 } 367 368 return NS_OK; 369 } 370 371 /* 372 The REC says: 373 * If the base is an operator with movablelimits="true" (or an embellished 374 operator whose <mo> element core has movablelimits="true"), and 375 displaystyle="false", then underscript and overscript are drawn in 376 a subscript and superscript position, respectively. In this case, 377 the accent and accentunder attributes are ignored. This is often 378 used for limits on symbols such as ∑. 379 380 i.e.,: 381 if (mEmbellishDataflags.contains(MathMLEmbellishFlag::MovableLimits) && 382 StyleFont()->mMathStyle == StyleMathStyle::Compact) { 383 // place like subscript-superscript pair 384 } 385 else { 386 // place like underscript-overscript pair 387 } 388 */ 389 390 /* virtual */ 391 void nsMathMLmunderoverFrame::Place(DrawTarget* aDrawTarget, 392 const PlaceFlags& aFlags, 393 ReflowOutput& aDesiredSize) { 394 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); 395 if (mEmbellishData.flags.contains(MathMLEmbellishFlag::MovableLimits) && 396 StyleFont()->mMathStyle == StyleMathStyle::Compact) { 397 // place like sub sup or subsup 398 if (mContent->IsMathMLElement(nsGkAtoms::munderover)) { 399 return nsMathMLmmultiscriptsFrame::PlaceMultiScript( 400 PresContext(), aDrawTarget, aFlags, aDesiredSize, this, 0, 0, 401 fontSizeInflation); 402 } else if (mContent->IsMathMLElement(nsGkAtoms::munder)) { 403 return nsMathMLmmultiscriptsFrame::PlaceMultiScript( 404 PresContext(), aDrawTarget, aFlags, aDesiredSize, this, 0, 0, 405 fontSizeInflation); 406 } else { 407 NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover), 408 "mContent->NodeInfo()->NameAtom() not recognized"); 409 return nsMathMLmmultiscriptsFrame::PlaceMultiScript( 410 PresContext(), aDrawTarget, aFlags, aDesiredSize, this, 0, 0, 411 fontSizeInflation); 412 } 413 } 414 415 //////////////////////////////////// 416 // Get the children's desired sizes 417 418 nsBoundingMetrics bmBase, bmUnder, bmOver; 419 ReflowOutput baseSize(aDesiredSize.GetWritingMode()); 420 ReflowOutput underSize(aDesiredSize.GetWritingMode()); 421 ReflowOutput overSize(aDesiredSize.GetWritingMode()); 422 nsIFrame* overFrame = nullptr; 423 nsIFrame* underFrame = nullptr; 424 nsIFrame* baseFrame = mFrames.FirstChild(); 425 underSize.SetBlockStartAscent(0); 426 overSize.SetBlockStartAscent(0); 427 bool haveError = false; 428 if (baseFrame) { 429 if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder, 430 nsGkAtoms::munderover)) { 431 underFrame = baseFrame->GetNextSibling(); 432 } else if (mContent->IsMathMLElement(nsGkAtoms::mover)) { 433 overFrame = baseFrame->GetNextSibling(); 434 } 435 } 436 if (underFrame && mContent->IsMathMLElement(nsGkAtoms::munderover)) { 437 overFrame = underFrame->GetNextSibling(); 438 } 439 440 if (mContent->IsMathMLElement(nsGkAtoms::munder)) { 441 if (!baseFrame || !underFrame || underFrame->GetNextSibling()) { 442 // report an error, encourage people to get their markups in order 443 haveError = true; 444 } 445 } 446 if (mContent->IsMathMLElement(nsGkAtoms::mover)) { 447 if (!baseFrame || !overFrame || overFrame->GetNextSibling()) { 448 // report an error, encourage people to get their markups in order 449 haveError = true; 450 } 451 } 452 if (mContent->IsMathMLElement(nsGkAtoms::munderover)) { 453 if (!baseFrame || !underFrame || !overFrame || 454 overFrame->GetNextSibling()) { 455 // report an error, encourage people to get their markups in order 456 haveError = true; 457 } 458 } 459 if (haveError) { 460 if (!aFlags.contains(PlaceFlag::MeasureOnly)) { 461 ReportChildCountError(); 462 } 463 return PlaceAsMrow(aDrawTarget, aFlags, aDesiredSize); 464 } 465 GetReflowAndBoundingMetricsFor(baseFrame, baseSize, bmBase); 466 nsMargin baseMargin = GetMarginForPlace(aFlags, baseFrame); 467 nsMargin underMargin, overMargin; 468 if (underFrame) { 469 GetReflowAndBoundingMetricsFor(underFrame, underSize, bmUnder); 470 underMargin = GetMarginForPlace(aFlags, underFrame); 471 } 472 if (overFrame) { 473 GetReflowAndBoundingMetricsFor(overFrame, overSize, bmOver); 474 overMargin = GetMarginForPlace(aFlags, overFrame); 475 } 476 477 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); 478 479 //////////////////// 480 // Place Children 481 482 RefPtr<nsFontMetrics> fm = 483 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); 484 485 nscoord xHeight = fm->XHeight(); 486 nscoord oneDevPixel = fm->AppUnitsPerDevPixel(); 487 RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont(); 488 489 nscoord ruleThickness; 490 GetRuleThickness(aDrawTarget, fm, ruleThickness); 491 492 nscoord correction = 0; 493 GetItalicCorrection(bmBase, correction); 494 495 // there are 2 different types of placement depending on 496 // whether we want an accented under or not 497 498 nscoord underDelta1 = 0; // gap between base and underscript 499 nscoord underDelta2 = 0; // extra space beneath underscript 500 501 if (!mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentUnder)) { 502 // Rule 13a, App. G, TeXbook 503 nscoord bigOpSpacing2, bigOpSpacing4, bigOpSpacing5, dummy; 504 GetBigOpSpacings(fm, dummy, bigOpSpacing2, dummy, bigOpSpacing4, 505 bigOpSpacing5); 506 if (mathFont) { 507 // XXXfredw The Open Type MATH table has some StretchStack* parameters 508 // that we may use when the base is a stretchy horizontal operator. See 509 // bug 963131. 510 bigOpSpacing2 = mathFont->MathTable()->Constant( 511 gfxMathTable::LowerLimitGapMin, oneDevPixel); 512 bigOpSpacing4 = mathFont->MathTable()->Constant( 513 gfxMathTable::LowerLimitBaselineDropMin, oneDevPixel); 514 bigOpSpacing5 = 0; 515 } 516 underDelta1 = std::max( 517 bigOpSpacing2, (bigOpSpacing4 - bmUnder.ascent - underMargin.bottom)); 518 underDelta2 = bigOpSpacing5; 519 } else { 520 // No corresponding rule in TeXbook - we are on our own here 521 // XXX tune the gap delta between base and underscript 522 // XXX Should we use Rule 10 like \underline does? 523 // XXXfredw Perhaps use the Underbar* parameters of the MATH table. See 524 // bug 963125. 525 underDelta1 = ruleThickness + onePixel / 2; 526 underDelta2 = ruleThickness; 527 } 528 // empty under? 529 if (bmUnder.ascent + bmUnder.descent + underMargin.TopBottom() <= 0) { 530 underDelta1 = 0; 531 underDelta2 = 0; 532 } 533 534 nscoord overDelta1 = 0; // gap between base and overscript 535 nscoord overDelta2 = 0; // extra space above overscript 536 537 if (!mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentOver)) { 538 // Rule 13a, App. G, TeXbook 539 // XXXfredw The Open Type MATH table has some StretchStack* parameters 540 // that we may use when the base is a stretchy horizontal operator. See 541 // bug 963131. 542 nscoord bigOpSpacing1, bigOpSpacing3, bigOpSpacing5, dummy; 543 GetBigOpSpacings(fm, bigOpSpacing1, dummy, bigOpSpacing3, dummy, 544 bigOpSpacing5); 545 if (mathFont) { 546 // XXXfredw The Open Type MATH table has some StretchStack* parameters 547 // that we may use when the base is a stretchy horizontal operator. See 548 // bug 963131. 549 bigOpSpacing1 = mathFont->MathTable()->Constant( 550 gfxMathTable::UpperLimitGapMin, oneDevPixel); 551 bigOpSpacing3 = mathFont->MathTable()->Constant( 552 gfxMathTable::UpperLimitBaselineRiseMin, oneDevPixel); 553 bigOpSpacing5 = 0; 554 } 555 overDelta1 = std::max(bigOpSpacing1, 556 (bigOpSpacing3 - bmOver.descent - overMargin.bottom)); 557 overDelta2 = bigOpSpacing5; 558 559 // XXX This is not a TeX rule... 560 // delta1 (as computed abvove) can become really big when bmOver.descent is 561 // negative, e.g., if the content is &OverBar. In such case, we use the 562 // height 563 if (bmOver.descent + overMargin.bottom < 0) { 564 overDelta1 = std::max(bigOpSpacing1, 565 (bigOpSpacing3 - (bmOver.ascent + bmOver.descent + 566 overMargin.TopBottom()))); 567 } 568 } else { 569 // Rule 12, App. G, TeXbook 570 // We are going to modify this rule to make it more general. 571 // The idea behind Rule 12 in the TeXBook is to keep the accent 572 // as close to the base as possible, while ensuring that the 573 // distance between the *baseline* of the accent char and 574 // the *baseline* of the base is atleast x-height. 575 // The idea is that for normal use, we would like all the accents 576 // on a line to line up atleast x-height above the baseline 577 // if possible. 578 // When the ascent of the base is >= x-height, 579 // the baseline of the accent char is placed just above the base 580 // (specifically, the baseline of the accent char is placed 581 // above the baseline of the base by the ascent of the base). 582 // For ease of implementation, 583 // this assumes that the font-designer designs accents 584 // in such a way that the bottom of the accent is atleast x-height 585 // above its baseline, otherwise there will be collisions 586 // with the base. Also there should be proper padding between 587 // the bottom of the accent char and its baseline. 588 // The above rule may not be obvious from a first 589 // reading of rule 12 in the TeXBook !!! 590 // The mathml <mover> tag can use accent chars that 591 // do not follow this convention. So we modify TeX's rule 592 // so that TeX's rule gets subsumed for accents that follow 593 // TeX's convention, 594 // while also allowing accents that do not follow the convention : 595 // we try to keep the *bottom* of the accent char atleast x-height 596 // from the baseline of the base char. we also slap on an extra 597 // padding between the accent and base chars. 598 overDelta1 = ruleThickness + onePixel / 2; 599 nscoord accentBaseHeight = xHeight; 600 if (mathFont) { 601 accentBaseHeight = mathFont->MathTable()->Constant( 602 gfxMathTable::AccentBaseHeight, oneDevPixel); 603 } 604 if (bmBase.ascent + baseMargin.top < accentBaseHeight) { 605 // also ensure at least accentBaseHeight above the baseline of the base 606 overDelta1 += accentBaseHeight - bmBase.ascent - baseMargin.top; 607 } 608 overDelta2 = ruleThickness; 609 } 610 // empty over? 611 if (bmOver.ascent + bmOver.descent + overMargin.TopBottom() <= 0) { 612 overDelta1 = 0; 613 overDelta2 = 0; 614 } 615 616 nscoord dxBase = 0, dxOver = 0, dxUnder = 0; 617 nsAutoString valueAlign; 618 619 ////////// 620 // pass 1, do what <mover> does: attach the overscript on the base 621 622 // Ad-hoc - This is to override fonts which have ready-made _accent_ 623 // glyphs with negative lbearing and rbearing. We want to position 624 // the overscript ourselves 625 nscoord overWidth = bmOver.width + overMargin.LeftRight(); 626 if (overWidth <= 0 && (bmOver.rightBearing - bmOver.leftBearing > 0)) { 627 overWidth = bmOver.rightBearing - bmOver.leftBearing; 628 dxOver = -bmOver.leftBearing; 629 } 630 631 if (mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentOver)) { 632 mBoundingMetrics.width = bmBase.width + baseMargin.LeftRight(); 633 dxOver += correction; 634 } else { 635 mBoundingMetrics.width = 636 std::max(bmBase.width + baseMargin.LeftRight(), overWidth); 637 dxOver += correction / 2; 638 } 639 640 dxOver += (mBoundingMetrics.width - overWidth) / 2; 641 dxBase = (mBoundingMetrics.width - bmBase.width - baseMargin.LeftRight()) / 2; 642 643 mBoundingMetrics.ascent = baseMargin.top + bmBase.ascent + overDelta1 + 644 bmOver.ascent + bmOver.descent + 645 overMargin.TopBottom(); 646 mBoundingMetrics.descent = bmBase.descent + baseMargin.bottom; 647 mBoundingMetrics.leftBearing = 648 std::min(dxBase + bmBase.leftBearing, dxOver + bmOver.leftBearing); 649 mBoundingMetrics.rightBearing = 650 std::max(dxBase + bmBase.rightBearing + baseMargin.LeftRight(), 651 dxOver + bmOver.rightBearing + overMargin.LeftRight()); 652 653 ////////// 654 // pass 2, do what <munder> does: attach the underscript on the previous 655 // result. We conceptually view the previous result as an "anynomous base" 656 // from where to attach the underscript. Hence if the underscript is empty, 657 // we should end up like <mover>. If the overscript is empty, we should 658 // end up like <munder>. 659 660 nsBoundingMetrics bmAnonymousBase = mBoundingMetrics; 661 nscoord ascentAnonymousBase = std::max( 662 mBoundingMetrics.ascent + overDelta2, 663 overMargin.TopBottom() + overSize.BlockStartAscent() + bmOver.descent + 664 overDelta1 + baseMargin.top + bmBase.ascent); 665 ascentAnonymousBase = std::max(ascentAnonymousBase, 666 baseSize.BlockStartAscent() + baseMargin.top); 667 668 // Width of non-spacing marks is zero so use left and right bearing. 669 nscoord underWidth = bmUnder.width + underMargin.LeftRight(); 670 if (underWidth <= 0) { 671 underWidth = 672 bmUnder.rightBearing + underMargin.LeftRight() - bmUnder.leftBearing; 673 dxUnder = -bmUnder.leftBearing; 674 } 675 676 nscoord maxWidth = std::max(bmAnonymousBase.width, underWidth); 677 if (!mEmbellishData.flags.contains(MathMLEmbellishFlag::AccentUnder)) { 678 GetItalicCorrection(bmAnonymousBase, correction); 679 dxUnder += -correction / 2; 680 } 681 nscoord dxAnonymousBase = 0; 682 dxUnder += (maxWidth - underWidth) / 2; 683 dxAnonymousBase = (maxWidth - bmAnonymousBase.width) / 2; 684 685 // adjust the offsets of the real base and overscript since their 686 // final offsets should be relative to us... 687 dxOver += dxAnonymousBase; 688 dxBase += dxAnonymousBase; 689 690 mBoundingMetrics.width = 691 std::max(dxAnonymousBase + bmAnonymousBase.width, 692 dxUnder + bmUnder.width + underMargin.LeftRight()); 693 // At this point, mBoundingMetrics.ascent = bmAnonymousBase.ascent 694 mBoundingMetrics.descent = bmAnonymousBase.descent + underDelta1 + 695 bmUnder.ascent + bmUnder.descent + 696 underMargin.TopBottom(); 697 mBoundingMetrics.leftBearing = 698 std::min(dxAnonymousBase + bmAnonymousBase.leftBearing, 699 dxUnder + bmUnder.leftBearing); 700 mBoundingMetrics.rightBearing = 701 std::max(dxAnonymousBase + bmAnonymousBase.rightBearing, 702 dxUnder + bmUnder.rightBearing + underMargin.LeftRight()); 703 704 aDesiredSize.SetBlockStartAscent(ascentAnonymousBase); 705 aDesiredSize.Height() = 706 aDesiredSize.BlockStartAscent() + 707 std::max(mBoundingMetrics.descent + underDelta2, 708 bmAnonymousBase.descent + underDelta1 + underMargin.top + 709 bmUnder.ascent + underSize.Height() - 710 underSize.BlockStartAscent() + underMargin.bottom); 711 aDesiredSize.Height() = 712 std::max(aDesiredSize.Height(), 713 aDesiredSize.BlockStartAscent() + baseSize.Height() - 714 baseSize.BlockStartAscent() + baseMargin.bottom); 715 aDesiredSize.Width() = mBoundingMetrics.width; 716 aDesiredSize.mBoundingMetrics = mBoundingMetrics; 717 718 // Apply width/height to math content box. 719 auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags); 720 auto shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize, 721 mBoundingMetrics); 722 dxOver += shiftX; 723 dxBase += shiftX; 724 dxUnder += shiftX; 725 726 // Add padding+border. 727 auto borderPadding = GetBorderPaddingForPlace(aFlags); 728 InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize, 729 mBoundingMetrics); 730 dxOver += borderPadding.left + overMargin.left; 731 dxBase += borderPadding.left + baseMargin.left; 732 dxUnder += borderPadding.left + underMargin.left; 733 734 mReference.x = 0; 735 mReference.y = aDesiredSize.BlockStartAscent(); 736 737 if (!aFlags.contains(PlaceFlag::MeasureOnly)) { 738 nscoord dy; 739 // place overscript 740 if (overFrame) { 741 dy = aDesiredSize.BlockStartAscent() - mBoundingMetrics.ascent + 742 overMargin.top + bmOver.ascent - overSize.BlockStartAscent(); 743 FinishReflowChild(overFrame, PresContext(), overSize, nullptr, dxOver, dy, 744 ReflowChildFlags::Default); 745 } 746 // place base 747 dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent(); 748 FinishReflowChild(baseFrame, PresContext(), baseSize, nullptr, dxBase, dy, 749 ReflowChildFlags::Default); 750 // place underscript 751 if (underFrame) { 752 dy = aDesiredSize.BlockStartAscent() + mBoundingMetrics.descent - 753 bmUnder.descent - underMargin.bottom - underSize.BlockStartAscent(); 754 FinishReflowChild(underFrame, PresContext(), underSize, nullptr, dxUnder, 755 dy, ReflowChildFlags::Default); 756 } 757 } 758 } 759 760 bool nsMathMLmunderoverFrame::IsMathContentBoxHorizontallyCentered() const { 761 bool subsupDisplay = 762 mEmbellishData.flags.contains(MathMLEmbellishFlag::MovableLimits) && 763 StyleFont()->mMathStyle == StyleMathStyle::Compact; 764 return !subsupDisplay; 765 }