nsMathMLmrootFrame.cpp (15494B)
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 "nsMathMLmrootFrame.h" 8 9 #include <algorithm> 10 11 #include "gfxContext.h" 12 #include "gfxMathTable.h" 13 #include "mozilla/PresShell.h" 14 #include "mozilla/StaticPrefs_mathml.h" 15 #include "nsLayoutUtils.h" 16 #include "nsPresContext.h" 17 18 using namespace mozilla; 19 20 // 21 // <mroot> -- form a radical - implementation 22 // 23 24 static const char16_t kSqrChar = char16_t(0x221A); 25 26 nsIFrame* NS_NewMathMLmrootFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 27 return new (aPresShell) 28 nsMathMLmrootFrame(aStyle, aPresShell->GetPresContext()); 29 } 30 31 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame) 32 33 nsMathMLmrootFrame::nsMathMLmrootFrame(ComputedStyle* aStyle, 34 nsPresContext* aPresContext) 35 : nsMathMLContainerFrame(aStyle, aPresContext, kClassID) {} 36 37 nsMathMLmrootFrame::~nsMathMLmrootFrame() = default; 38 39 void nsMathMLmrootFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 40 nsIFrame* aPrevInFlow) { 41 nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow); 42 43 nsAutoString sqrChar; 44 sqrChar.Assign(kSqrChar); 45 mSqrChar.SetData(sqrChar); 46 mSqrChar.SetComputedStyle(Style()); 47 } 48 49 bool nsMathMLmrootFrame::ShouldUseRowFallback() { 50 bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot); 51 if (!isRootWithIndex) { 52 return false; 53 } 54 // An mroot element expects exactly two children. 55 nsIFrame* baseFrame = mFrames.FirstChild(); 56 if (!baseFrame) { 57 return true; 58 } 59 nsIFrame* indexFrame = baseFrame->GetNextSibling(); 60 return !indexFrame || indexFrame->GetNextSibling(); 61 } 62 63 bool nsMathMLmrootFrame::IsMrowLike() { 64 bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot); 65 if (isRootWithIndex) { 66 return false; 67 } 68 return mFrames.FirstChild() != mFrames.LastChild() || !mFrames.FirstChild(); 69 } 70 71 NS_IMETHODIMP 72 nsMathMLmrootFrame::InheritAutomaticData(nsIFrame* aParent) { 73 nsMathMLContainerFrame::InheritAutomaticData(aParent); 74 75 bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot); 76 if (!isRootWithIndex) { 77 mPresentationData.flags += 78 MathMLPresentationFlag::StretchAllChildrenVertically; 79 } 80 81 return NS_OK; 82 } 83 84 NS_IMETHODIMP 85 nsMathMLmrootFrame::TransmitAutomaticData() { 86 bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot); 87 if (isRootWithIndex) { 88 // 1. The REC says: 89 // The <mroot> element increments scriptlevel by 2, and sets displaystyle 90 // to "false", within index, but leaves both attributes unchanged within 91 // base. 92 // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed 93 if (!StaticPrefs::mathml_math_shift_enabled()) { 94 UpdatePresentationDataFromChildAt(1, 1, 95 MathMLPresentationFlag::Compressed, 96 MathMLPresentationFlag::Compressed); 97 UpdatePresentationDataFromChildAt(0, 0, 98 MathMLPresentationFlag::Compressed, 99 MathMLPresentationFlag::Compressed); 100 } 101 102 PropagateFrameFlagFor(mFrames.LastChild(), 103 NS_FRAME_MATHML_SCRIPT_DESCENDANT); 104 } else { 105 // The TeXBook (Ch 17. p.141) says that \sqrt is cramped 106 if (!StaticPrefs::mathml_math_shift_enabled()) { 107 UpdatePresentationDataFromChildAt(0, -1, 108 MathMLPresentationFlag::Compressed, 109 MathMLPresentationFlag::Compressed); 110 } 111 } 112 113 return NS_OK; 114 } 115 116 void nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 117 const nsDisplayListSet& aLists) { 118 ///////////// 119 // paint the content we are square-rooting 120 nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists); 121 122 if (ShouldUseRowFallback()) { 123 return; 124 } 125 126 ///////////// 127 // paint the sqrt symbol 128 mSqrChar.Display(aBuilder, this, aLists, 0); 129 130 DisplayBar(aBuilder, this, mBarRect, aLists); 131 } 132 133 void nsMathMLmrootFrame::GetRadicalXOffsets(nscoord aIndexWidth, 134 nscoord aSqrWidth, 135 nsFontMetrics* aFontMetrics, 136 nscoord* aIndexOffset, 137 nscoord* aSqrOffset) { 138 // The index is tucked in closer to the radical while making sure 139 // that the kern does not make the index and radical collide 140 nscoord dxIndex, dxSqr, radicalKernBeforeDegree, radicalKernAfterDegree; 141 nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel(); 142 RefPtr<gfxFont> mathFont = 143 aFontMetrics->GetThebesFontGroup()->GetFirstMathFont(); 144 145 if (mathFont) { 146 radicalKernBeforeDegree = mathFont->MathTable()->Constant( 147 gfxMathTable::RadicalKernBeforeDegree, oneDevPixel); 148 radicalKernAfterDegree = mathFont->MathTable()->Constant( 149 gfxMathTable::RadicalKernAfterDegree, oneDevPixel); 150 } else { 151 nscoord em; 152 GetEmHeight(aFontMetrics, em); 153 radicalKernBeforeDegree = NSToCoordRound(5.0f * em / 18); 154 radicalKernAfterDegree = NSToCoordRound(-10.0f * em / 18); 155 } 156 157 // Clamp radical kern degrees according to spec: 158 // https://w3c.github.io/mathml-core/#root-with-index 159 radicalKernBeforeDegree = std::max(0, radicalKernBeforeDegree); 160 radicalKernAfterDegree = std::max(-aIndexWidth, radicalKernAfterDegree); 161 162 dxIndex = radicalKernBeforeDegree; 163 dxSqr = radicalKernBeforeDegree + aIndexWidth + radicalKernAfterDegree; 164 if (aIndexOffset) { 165 *aIndexOffset = dxIndex; 166 } 167 if (aSqrOffset) { 168 *aSqrOffset = dxSqr; 169 } 170 } 171 172 void nsMathMLmrootFrame::Place(DrawTarget* aDrawTarget, 173 const PlaceFlags& aFlags, 174 ReflowOutput& aDesiredSize) { 175 if (ShouldUseRowFallback()) { 176 // report an error, encourage people to get their markups in order 177 if (!aFlags.contains(PlaceFlag::MeasureOnly)) { 178 ReportChildCountError(); 179 } 180 return PlaceAsMrow(aDrawTarget, aFlags, aDesiredSize); 181 } 182 183 const bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot); 184 nsBoundingMetrics bmSqr, bmBase, bmIndex; 185 nsIFrame *baseFrame = nullptr, *indexFrame = nullptr; 186 nsMargin baseMargin, indexMargin; 187 ReflowOutput baseSize(aDesiredSize.GetWritingMode()); 188 ReflowOutput indexSize(aDesiredSize.GetWritingMode()); 189 if (isRootWithIndex) { 190 baseFrame = mFrames.FirstChild(); 191 indexFrame = baseFrame->GetNextSibling(); 192 baseMargin = GetMarginForPlace(aFlags, baseFrame); 193 indexMargin = GetMarginForPlace(aFlags, indexFrame); 194 GetReflowAndBoundingMetricsFor(baseFrame, baseSize, bmBase); 195 GetReflowAndBoundingMetricsFor(indexFrame, indexSize, bmIndex); 196 } else { 197 // Format our content as an mrow without border/padding to obtain the 198 // square root base. The metrics/frame for the index are ignored. 199 PlaceFlags flags = aFlags + PlaceFlag::MeasureOnly + 200 PlaceFlag::IgnoreBorderPadding + 201 PlaceFlag::DoNotAdjustForWidthAndHeight; 202 nsMathMLContainerFrame::Place(aDrawTarget, flags, baseSize); 203 bmBase = baseSize.mBoundingMetrics; 204 } 205 206 //////////// 207 // Prepare the radical symbol and the overline bar 208 209 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); 210 RefPtr<nsFontMetrics> fm = 211 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); 212 213 nscoord ruleThickness, leading, psi; 214 GetRadicalParameters(fm, StyleFont()->mMathStyle == StyleMathStyle::Normal, 215 ruleThickness, leading, psi); 216 217 // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook, 218 // p.131) 219 char16_t one = '1'; 220 nsBoundingMetrics bmOne = 221 nsLayoutUtils::AppUnitBoundsOfString(&one, 1, *fm, aDrawTarget); 222 if (bmOne.ascent > bmBase.ascent + baseMargin.top) { 223 psi += bmOne.ascent - bmBase.ascent - baseMargin.top; 224 } 225 226 // make sure that the rule appears on on screen 227 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); 228 if (ruleThickness < onePixel) { 229 ruleThickness = onePixel; 230 } 231 232 // adjust clearance psi to get an exact number of pixels -- this 233 // gives a nicer & uniform look on stacked radicals (bug 130282) 234 nscoord delta = psi % onePixel; 235 if (delta) { 236 psi += onePixel - delta; // round up 237 } 238 239 // Stretch the radical symbol to the appropriate height if it is not big 240 // enough. 241 nsBoundingMetrics contSize = bmBase; 242 contSize.descent = 243 bmBase.ascent + bmBase.descent + baseMargin.TopBottom() + psi; 244 contSize.ascent = ruleThickness; 245 246 // height(radical) should be >= height(base) + psi + ruleThickness 247 nsBoundingMetrics radicalSize; 248 if (aFlags.contains(PlaceFlag::IntrinsicSize)) { 249 nscoord radical_width = 250 mSqrChar.GetMaxWidth(this, aDrawTarget, fontSizeInflation); 251 bmSqr.leftBearing = 0; 252 bmSqr.rightBearing = radical_width; 253 bmSqr.width = radical_width; 254 bmSqr.ascent = bmSqr.descent = 0; 255 } else { 256 mSqrChar.Stretch(this, aDrawTarget, fontSizeInflation, 257 NS_STRETCH_DIRECTION_VERTICAL, contSize, radicalSize, 258 NS_STRETCH_LARGER, 259 StyleVisibility()->mDirection == StyleDirection::Rtl); 260 // radicalSize have changed at this point, and should match with 261 // the bounding metrics of the char 262 mSqrChar.GetBoundingMetrics(bmSqr); 263 } 264 265 // Update the desired size for the container (like msqrt, index is not yet 266 // included) the baseline will be that of the base. 267 mBoundingMetrics.ascent = 268 bmBase.ascent + baseMargin.top + psi + ruleThickness; 269 mBoundingMetrics.descent = 270 std::max(bmBase.descent + baseMargin.bottom, 271 (bmSqr.ascent + bmSqr.descent - mBoundingMetrics.ascent)); 272 mBoundingMetrics.width = bmSqr.width + bmBase.width + baseMargin.LeftRight(); 273 mBoundingMetrics.leftBearing = bmSqr.leftBearing; 274 mBoundingMetrics.rightBearing = 275 bmSqr.width + 276 std::max( 277 bmBase.width + baseMargin.LeftRight(), 278 bmBase.rightBearing + baseMargin.left); // take also care of the rule 279 280 aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading); 281 aDesiredSize.Height() = 282 aDesiredSize.BlockStartAscent() + 283 std::max(baseSize.Height() - baseSize.BlockStartAscent(), 284 mBoundingMetrics.descent + ruleThickness); 285 aDesiredSize.Width() = mBoundingMetrics.width; 286 287 nscoord indexClearance = 0, dxIndex = 0, dxSqr = 0, indexRaisedAscent = 0; 288 if (isRootWithIndex) { 289 ///////////// 290 // Re-adjust the desired size to include the index. 291 292 // the index is raised by some fraction of the height 293 // of the radical, see \mroot macro in App. B, TexBook 294 float raiseIndexPercent = 0.6f; 295 RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont(); 296 if (mathFont) { 297 raiseIndexPercent = mathFont->MathTable()->Constant( 298 gfxMathTable::RadicalDegreeBottomRaisePercent); 299 } 300 nscoord raiseIndexDelta = 301 NSToCoordRound(raiseIndexPercent * (bmSqr.ascent + bmSqr.descent)); 302 indexRaisedAscent = mBoundingMetrics.ascent // top of radical 303 - 304 (bmSqr.ascent + bmSqr.descent) // to bottom of radical 305 + raiseIndexDelta + bmIndex.ascent + bmIndex.descent + 306 indexMargin.TopBottom(); // to top of raised index 307 308 if (mBoundingMetrics.ascent < indexRaisedAscent) { 309 indexClearance = 310 indexRaisedAscent - 311 mBoundingMetrics.ascent; // excess gap introduced by a tall index 312 mBoundingMetrics.ascent = indexRaisedAscent; 313 nscoord descent = aDesiredSize.Height() - aDesiredSize.BlockStartAscent(); 314 aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading); 315 aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + descent; 316 } 317 318 GetRadicalXOffsets(bmIndex.width + indexMargin.LeftRight(), bmSqr.width, fm, 319 &dxIndex, &dxSqr); 320 321 mBoundingMetrics.width = 322 dxSqr + bmSqr.width + bmBase.width + baseMargin.LeftRight(); 323 mBoundingMetrics.leftBearing = 324 std::min(dxIndex + bmIndex.leftBearing, dxSqr + bmSqr.leftBearing); 325 mBoundingMetrics.rightBearing = 326 dxSqr + bmSqr.width + 327 std::max(bmBase.width + baseMargin.LeftRight(), 328 bmBase.rightBearing + baseMargin.left); 329 330 aDesiredSize.Width() = mBoundingMetrics.width; 331 } 332 333 aDesiredSize.mBoundingMetrics = mBoundingMetrics; 334 335 // Apply width/height to math content box. 336 const PlaceFlags flags; 337 auto sizes = GetWidthAndHeightForPlaceAdjustment(flags); 338 nscoord shiftX = ApplyAdjustmentForWidthAndHeight(flags, sizes, aDesiredSize, 339 mBoundingMetrics); 340 341 // Add padding+border around the final layout. 342 auto borderPadding = GetBorderPaddingForPlace(aFlags); 343 InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize, 344 mBoundingMetrics); 345 346 if (!aFlags.contains(PlaceFlag::MeasureOnly)) { 347 nsPresContext* presContext = PresContext(); 348 const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl; 349 nscoord borderPaddingInlineStart = 350 isRTL ? borderPadding.right : borderPadding.left; 351 nscoord dx, dy; 352 353 if (isRootWithIndex) { 354 // place the index 355 dx = borderPaddingInlineStart + dxIndex + 356 indexMargin.Side(isRTL ? eSideRight : eSideLeft); 357 dy = aDesiredSize.BlockStartAscent() - 358 (indexRaisedAscent + indexSize.BlockStartAscent() - bmIndex.ascent); 359 FinishReflowChild( 360 indexFrame, presContext, indexSize, nullptr, 361 MirrorIfRTL(aDesiredSize.Width(), indexSize.Width(), dx), 362 dy + indexMargin.top, ReflowChildFlags::Default); 363 } 364 365 // place the radical symbol and the radical bar 366 dx = borderPaddingInlineStart + dxSqr; 367 dy = borderPadding.top + indexClearance + 368 leading; // leave a leading at the top 369 mSqrChar.SetRect(nsRect(MirrorIfRTL(aDesiredSize.Width(), bmSqr.width, dx), 370 dy, bmSqr.width, bmSqr.ascent + bmSqr.descent)); 371 dx += bmSqr.width; 372 mBarRect.SetRect(MirrorIfRTL(aDesiredSize.Width(), 373 bmBase.width + baseMargin.LeftRight(), dx), 374 dy, bmBase.width + baseMargin.LeftRight(), ruleThickness); 375 376 // place the base 377 if (isRootWithIndex) { 378 dx += isRTL ? baseMargin.right : baseMargin.left; 379 dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent(); 380 FinishReflowChild(baseFrame, presContext, baseSize, nullptr, 381 MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx), 382 dy, ReflowChildFlags::Default); 383 } else { 384 nscoord dx_left = borderPadding.left + shiftX; 385 if (!isRTL) { 386 dx_left += bmSqr.width; 387 } 388 PositionRowChildFrames(dx_left, aDesiredSize.BlockStartAscent()); 389 } 390 } 391 392 mReference.x = 0; 393 mReference.y = aDesiredSize.BlockStartAscent(); 394 } 395 396 void nsMathMLmrootFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { 397 nsMathMLContainerFrame::DidSetComputedStyle(aOldStyle); 398 mSqrChar.SetComputedStyle(Style()); 399 }