nsMathMLmfracFrame.cpp (15860B)
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 "nsMathMLmfracFrame.h" 8 9 #include <algorithm> 10 11 #include "gfxContext.h" 12 #include "gfxMathTable.h" 13 #include "gfxTextRun.h" 14 #include "gfxUtils.h" 15 #include "mozilla/PresShell.h" 16 #include "mozilla/RefPtr.h" 17 #include "mozilla/StaticPrefs_mathml.h" 18 #include "mozilla/dom/Document.h" 19 #include "mozilla/dom/MathMLElement.h" 20 #include "mozilla/gfx/2D.h" 21 #include "nsDisplayList.h" 22 #include "nsLayoutUtils.h" 23 #include "nsPresContext.h" 24 25 using namespace mozilla; 26 using namespace mozilla::gfx; 27 28 // 29 // <mfrac> -- form a fraction from two subexpressions - implementation 30 // 31 32 nsIFrame* NS_NewMathMLmfracFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 33 return new (aPresShell) 34 nsMathMLmfracFrame(aStyle, aPresShell->GetPresContext()); 35 } 36 37 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfracFrame) 38 39 nsMathMLmfracFrame::~nsMathMLmfracFrame() = default; 40 41 MathMLFrameType nsMathMLmfracFrame::GetMathMLFrameType() { 42 // frac is "inner" in TeXBook, Appendix G, rule 15e. See also page 170. 43 return MathMLFrameType::Inner; 44 } 45 46 uint8_t nsMathMLmfracFrame::ScriptIncrement(nsIFrame* aFrame) { 47 if (StyleFont()->mMathStyle == StyleMathStyle::Compact && aFrame && 48 (mFrames.FirstChild() == aFrame || mFrames.LastChild() == aFrame)) { 49 return 1; 50 } 51 return 0; 52 } 53 54 NS_IMETHODIMP 55 nsMathMLmfracFrame::TransmitAutomaticData() { 56 // The TeXbook (Ch 17. p.141) says the numerator inherits the compression 57 // while the denominator is compressed 58 if (!StaticPrefs::mathml_math_shift_enabled()) { 59 UpdatePresentationDataFromChildAt(1, 1, MathMLPresentationFlag::Compressed, 60 MathMLPresentationFlag::Compressed); 61 } 62 63 // If displaystyle is false, then scriptlevel is incremented, so notify the 64 // children of this. 65 if (StyleFont()->mMathStyle == StyleMathStyle::Compact) { 66 PropagateFrameFlagFor(mFrames.FirstChild(), 67 NS_FRAME_MATHML_SCRIPT_DESCENDANT); 68 PropagateFrameFlagFor(mFrames.LastChild(), 69 NS_FRAME_MATHML_SCRIPT_DESCENDANT); 70 } 71 72 // if our numerator is an embellished operator, let its state bubble to us 73 GetEmbellishDataFrom(mFrames.FirstChild(), mEmbellishData); 74 if (mEmbellishData.flags.contains(MathMLEmbellishFlag::EmbellishedOperator)) { 75 // even when embellished, we need to record that <mfrac> won't fire 76 // Stretch() on its embellished child 77 mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; 78 } 79 80 return NS_OK; 81 } 82 83 nscoord nsMathMLmfracFrame::CalcLineThickness(nsString& aThicknessAttribute, 84 nscoord onePixel, 85 nscoord aDefaultRuleThickness, 86 float aFontSizeInflation) { 87 nscoord defaultThickness = aDefaultRuleThickness; 88 nscoord lineThickness = aDefaultRuleThickness; 89 nscoord minimumThickness = onePixel; 90 91 // linethickness 92 // https://w3c.github.io/mathml-core/#dfn-linethickness 93 if (!aThicknessAttribute.IsEmpty()) { 94 lineThickness = defaultThickness; 95 ParseAndCalcNumericValue(aThicknessAttribute, &lineThickness, 96 dom::MathMLElement::PARSE_ALLOW_NEGATIVE, 97 aFontSizeInflation, this); 98 // MathML Core says a negative value is interpreted as 0. 99 if (lineThickness < 0) { 100 lineThickness = 0; 101 } 102 } 103 // use minimum if the lineThickness is a non-zero value less than minimun 104 if (lineThickness && lineThickness < minimumThickness) { 105 lineThickness = minimumThickness; 106 } 107 108 return lineThickness; 109 } 110 111 void nsMathMLmfracFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 112 const nsDisplayListSet& aLists) { 113 ///////////// 114 // paint the numerator and denominator 115 nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists); 116 117 ///////////// 118 // paint the fraction line 119 DisplayBar(aBuilder, this, mLineRect, aLists); 120 } 121 122 nsresult nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID, 123 nsAtom* aAttribute, 124 AttrModType aModType) { 125 if (aNameSpaceID == kNameSpaceID_None && 126 nsGkAtoms::linethickness == aAttribute) { 127 // The thickness changes, so a repaint of the bar is needed. 128 InvalidateFrame(); 129 // The thickness affects vertical offsets. 130 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None, 131 NS_FRAME_IS_DIRTY); 132 return NS_OK; 133 } 134 return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, 135 aModType); 136 } 137 138 nscoord nsMathMLmfracFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize) { 139 nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize); 140 if (!gap) { 141 return 0; 142 } 143 144 mLineRect.MoveBy(gap, 0); 145 return gap; 146 } 147 148 /* virtual */ 149 void nsMathMLmfracFrame::Place(DrawTarget* aDrawTarget, 150 const PlaceFlags& aFlags, 151 ReflowOutput& aDesiredSize) { 152 //////////////////////////////////// 153 // Get the children's desired sizes 154 nsBoundingMetrics bmNum, bmDen; 155 ReflowOutput sizeNum(aDesiredSize.GetWritingMode()); 156 ReflowOutput sizeDen(aDesiredSize.GetWritingMode()); 157 nsIFrame* frameDen = nullptr; 158 nsIFrame* frameNum = mFrames.FirstChild(); 159 if (frameNum) { 160 frameDen = frameNum->GetNextSibling(); 161 } 162 if (!frameNum || !frameDen || frameDen->GetNextSibling()) { 163 // report an error, encourage people to get their markups in order 164 if (!aFlags.contains(PlaceFlag::MeasureOnly)) { 165 ReportChildCountError(); 166 } 167 return PlaceAsMrow(aDrawTarget, aFlags, aDesiredSize); 168 } 169 GetReflowAndBoundingMetricsFor(frameNum, sizeNum, bmNum); 170 GetReflowAndBoundingMetricsFor(frameDen, sizeDen, bmDen); 171 172 nsMargin numMargin = GetMarginForPlace(aFlags, frameNum), 173 denMargin = GetMarginForPlace(aFlags, frameDen); 174 175 nsPresContext* presContext = PresContext(); 176 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); 177 178 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); 179 RefPtr<nsFontMetrics> fm = 180 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); 181 182 nscoord defaultRuleThickness, axisHeight; 183 nscoord oneDevPixel = fm->AppUnitsPerDevPixel(); 184 RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont(); 185 if (mathFont) { 186 defaultRuleThickness = mathFont->MathTable()->Constant( 187 gfxMathTable::FractionRuleThickness, oneDevPixel); 188 } else { 189 GetRuleThickness(aDrawTarget, fm, defaultRuleThickness); 190 } 191 GetAxisHeight(aDrawTarget, fm, axisHeight); 192 193 bool outermostEmbellished = false; 194 if (mEmbellishData.coreFrame) { 195 nsEmbellishData parentData; 196 GetEmbellishDataFrom(GetParent(), parentData); 197 outermostEmbellished = parentData.coreFrame != mEmbellishData.coreFrame; 198 } 199 200 // see if the linethickness attribute is there 201 nsAutoString value; 202 mContent->AsElement()->GetAttr(nsGkAtoms::linethickness, value); 203 mLineThickness = CalcLineThickness(value, onePixel, defaultRuleThickness, 204 fontSizeInflation); 205 206 bool displayStyle = StyleFont()->mMathStyle == StyleMathStyle::Normal; 207 208 mLineRect.height = mLineThickness; 209 210 // Add lspace & rspace that may come from <mo> if we are an outermost 211 // embellished container (we fetch values from the core since they may use 212 // units that depend on style data, and style changes could have occurred 213 // in the core since our last visit there) 214 nscoord leftSpace = 0; 215 nscoord rightSpace = 0; 216 if (outermostEmbellished) { 217 const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl; 218 nsEmbellishData coreData; 219 GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData); 220 leftSpace += isRTL ? coreData.trailingSpace : coreData.leadingSpace; 221 rightSpace += isRTL ? coreData.leadingSpace : coreData.trailingSpace; 222 } 223 224 nscoord actualRuleThickness = mLineThickness; 225 226 ////////////////// 227 // Get shifts 228 nscoord numShift = 0; 229 nscoord denShift = 0; 230 231 // Rule 15b, App. G, TeXbook 232 nscoord numShift1, numShift2, numShift3; 233 nscoord denShift1, denShift2; 234 235 GetNumeratorShifts(fm, numShift1, numShift2, numShift3); 236 GetDenominatorShifts(fm, denShift1, denShift2); 237 238 if (0 == actualRuleThickness) { 239 numShift = displayStyle ? numShift1 : numShift3; 240 denShift = displayStyle ? denShift1 : denShift2; 241 if (mathFont) { 242 numShift = mathFont->MathTable()->Constant( 243 displayStyle ? gfxMathTable::StackTopDisplayStyleShiftUp 244 : gfxMathTable::StackTopShiftUp, 245 oneDevPixel); 246 denShift = mathFont->MathTable()->Constant( 247 displayStyle ? gfxMathTable::StackBottomDisplayStyleShiftDown 248 : gfxMathTable::StackBottomShiftDown, 249 oneDevPixel); 250 } 251 } else { 252 numShift = displayStyle ? numShift1 : numShift2; 253 denShift = displayStyle ? denShift1 : denShift2; 254 if (mathFont) { 255 numShift = mathFont->MathTable()->Constant( 256 displayStyle ? gfxMathTable::FractionNumeratorDisplayStyleShiftUp 257 : gfxMathTable::FractionNumeratorShiftUp, 258 oneDevPixel); 259 denShift = mathFont->MathTable()->Constant( 260 displayStyle ? gfxMathTable::FractionDenominatorDisplayStyleShiftDown 261 : gfxMathTable::FractionDenominatorShiftDown, 262 oneDevPixel); 263 } 264 } 265 266 if (0 == actualRuleThickness) { 267 // Rule 15c, App. G, TeXbook 268 269 // min clearance between numerator and denominator 270 nscoord minClearance = 271 displayStyle ? 7 * defaultRuleThickness : 3 * defaultRuleThickness; 272 if (mathFont) { 273 minClearance = mathFont->MathTable()->Constant( 274 displayStyle ? gfxMathTable::StackDisplayStyleGapMin 275 : gfxMathTable::StackGapMin, 276 oneDevPixel); 277 } 278 279 nscoord actualClearance = (numShift - bmNum.descent - numMargin.bottom) - 280 (bmDen.ascent + denMargin.top - denShift); 281 // actualClearance should be >= minClearance 282 if (actualClearance < minClearance) { 283 nscoord halfGap = (minClearance - actualClearance) / 2; 284 numShift += halfGap; 285 denShift += halfGap; 286 } 287 } else { 288 // Rule 15d, App. G, TeXbook 289 290 // min clearance between numerator or denominator and middle of bar 291 292 // TeX has a different interpretation of the thickness. 293 // Try $a \above10pt b$ to see. Here is what TeX does: 294 // minClearance = displayStyle ? 295 // 3 * actualRuleThickness : actualRuleThickness; 296 297 // we slightly depart from TeX here. We use the defaultRuleThickness 298 // instead of the value coming from the linethickness attribute, i.e., we 299 // recover what TeX does if the user hasn't set linethickness. But when 300 // the linethickness is set, we avoid the wide gap problem. 301 nscoord minClearanceNum = displayStyle ? 3 * defaultRuleThickness 302 : defaultRuleThickness + onePixel; 303 nscoord minClearanceDen = minClearanceNum; 304 if (mathFont) { 305 minClearanceNum = mathFont->MathTable()->Constant( 306 displayStyle ? gfxMathTable::FractionNumDisplayStyleGapMin 307 : gfxMathTable::FractionNumeratorGapMin, 308 oneDevPixel); 309 minClearanceDen = mathFont->MathTable()->Constant( 310 displayStyle ? gfxMathTable::FractionDenomDisplayStyleGapMin 311 : gfxMathTable::FractionDenominatorGapMin, 312 oneDevPixel); 313 } 314 315 // adjust numShift to maintain minClearanceNum if needed 316 nscoord actualClearanceNum = (numShift - bmNum.descent - numMargin.bottom) - 317 (axisHeight + actualRuleThickness / 2); 318 if (actualClearanceNum < minClearanceNum) { 319 numShift += (minClearanceNum - actualClearanceNum); 320 } 321 // adjust denShift to maintain minClearanceDen if needed 322 nscoord actualClearanceDen = (axisHeight - actualRuleThickness / 2) - 323 (bmDen.ascent + denMargin.top - denShift); 324 if (actualClearanceDen < minClearanceDen) { 325 denShift += (minClearanceDen - actualClearanceDen); 326 } 327 } 328 329 ////////////////// 330 // Place Children 331 332 // XXX Need revisiting the width. TeX uses the exact width 333 // e.g. in $$\huge\frac{\displaystyle\int}{i}$$ 334 nscoord width = std::max(bmNum.width + numMargin.LeftRight(), 335 bmDen.width + denMargin.LeftRight()); 336 nscoord dxNum = 337 leftSpace + (width - sizeNum.Width() - numMargin.LeftRight()) / 2; 338 nscoord dxDen = 339 leftSpace + (width - sizeDen.Width() - denMargin.LeftRight()) / 2; 340 width += leftSpace + rightSpace; 341 342 mBoundingMetrics.rightBearing = 343 std::max(dxNum + bmNum.rightBearing + numMargin.LeftRight(), 344 dxDen + bmDen.rightBearing + denMargin.LeftRight()); 345 if (mBoundingMetrics.rightBearing < width - rightSpace) { 346 mBoundingMetrics.rightBearing = width - rightSpace; 347 } 348 mBoundingMetrics.leftBearing = 349 std::min(dxNum + bmNum.leftBearing, dxDen + bmDen.leftBearing); 350 if (mBoundingMetrics.leftBearing > leftSpace) { 351 mBoundingMetrics.leftBearing = leftSpace; 352 } 353 mBoundingMetrics.ascent = bmNum.ascent + numShift + numMargin.top; 354 mBoundingMetrics.descent = bmDen.descent + denShift + denMargin.bottom; 355 mBoundingMetrics.width = width; 356 357 aDesiredSize.SetBlockStartAscent(numMargin.top + sizeNum.BlockStartAscent() + 358 numShift); 359 aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + sizeDen.Height() + 360 denMargin.bottom - sizeDen.BlockStartAscent() + 361 denShift; 362 aDesiredSize.Width() = mBoundingMetrics.width; 363 aDesiredSize.mBoundingMetrics = mBoundingMetrics; 364 365 // Apply width/height to math content box. 366 auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags); 367 auto shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize, 368 mBoundingMetrics); 369 if (sizes.width) { 370 // MathML Core says the math content box is horizontally centered 371 // but the fraction bar still takes the full width of the content box. 372 dxNum += shiftX; 373 dxDen += shiftX; 374 width = *sizes.width; 375 } 376 377 // Add padding+border. 378 auto borderPadding = GetBorderPaddingForPlace(aFlags); 379 InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize, 380 mBoundingMetrics); 381 leftSpace += borderPadding.left; 382 rightSpace += borderPadding.right; 383 width += borderPadding.LeftRight(); 384 dxNum += borderPadding.left; 385 dxDen += borderPadding.left; 386 387 mReference.x = 0; 388 mReference.y = aDesiredSize.BlockStartAscent(); 389 390 if (!aFlags.contains(PlaceFlag::MeasureOnly)) { 391 nscoord dy; 392 // place numerator 393 dxNum += numMargin.left; 394 dy = borderPadding.top + numMargin.top; 395 FinishReflowChild(frameNum, presContext, sizeNum, nullptr, dxNum, dy, 396 ReflowChildFlags::Default); 397 // place denominator 398 dxDen += denMargin.left; 399 dy = 400 aDesiredSize.BlockStartAscent() + denShift - sizeDen.BlockStartAscent(); 401 FinishReflowChild(frameDen, presContext, sizeDen, nullptr, dxDen, dy, 402 ReflowChildFlags::Default); 403 // place the fraction bar - dy is top of bar 404 dy = aDesiredSize.BlockStartAscent() - 405 (axisHeight + actualRuleThickness / 2); 406 mLineRect.SetRect(leftSpace, dy, width - (leftSpace + rightSpace), 407 actualRuleThickness); 408 } 409 }