nsMathMLFrame.cpp (11867B)
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 "nsMathMLFrame.h" 8 9 #include "gfxContext.h" 10 #include "gfxMathTable.h" 11 #include "gfxUtils.h" 12 #include "mozilla/StaticPrefs_mathml.h" 13 #include "mozilla/dom/MathMLElement.h" 14 #include "mozilla/gfx/2D.h" 15 #include "nsCSSPseudoElements.h" 16 #include "nsCSSValue.h" 17 #include "nsLayoutUtils.h" 18 #include "nsMathMLChar.h" 19 #include "nsNameSpaceManager.h" 20 #include "nsPresContextInlines.h" 21 22 // used for parsing CSS units 23 #include "mozilla/dom/SVGAnimatedLength.h" 24 #include "mozilla/dom/SVGLength.h" 25 26 // used to map attributes into CSS rules 27 #include "mozilla/ServoStyleSet.h" 28 #include "nsDisplayList.h" 29 30 using namespace mozilla; 31 using namespace mozilla::gfx; 32 33 MathMLFrameType nsMathMLFrame::GetMathMLFrameType() { 34 // see if it is an embellished operator (mapped to 'Op' in TeX) 35 if (mEmbellishData.coreFrame) { 36 return GetMathMLFrameTypeFor(mEmbellishData.coreFrame); 37 } 38 39 // if it has a prescribed base, fetch the type from there 40 if (mPresentationData.baseFrame) { 41 return GetMathMLFrameTypeFor(mPresentationData.baseFrame); 42 } 43 44 // everything else is treated as ordinary (mapped to 'Ord' in TeX) 45 return MathMLFrameType::Ordinary; 46 } 47 48 NS_IMETHODIMP 49 nsMathMLFrame::InheritAutomaticData(nsIFrame* aParent) { 50 mEmbellishData.flags.clear(); 51 mEmbellishData.coreFrame = nullptr; 52 mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; 53 mEmbellishData.leadingSpace = 0; 54 mEmbellishData.trailingSpace = 0; 55 56 mPresentationData.flags.clear(); 57 mPresentationData.baseFrame = nullptr; 58 59 // by default, just inherit the display of our parent 60 nsPresentationData parentData; 61 GetPresentationDataFrom(aParent, parentData); 62 63 return NS_OK; 64 } 65 66 NS_IMETHODIMP 67 nsMathMLFrame::UpdatePresentationData(MathMLPresentationFlags aFlagsValues, 68 MathMLPresentationFlags aWhichFlags) { 69 NS_ASSERTION(aWhichFlags.contains(MathMLPresentationFlag::Compressed) || 70 aWhichFlags.contains(MathMLPresentationFlag::Dtls), 71 "aWhichFlags should only be compression or dtls flag"); 72 73 if (!StaticPrefs::mathml_math_shift_enabled() && 74 aWhichFlags.contains(MathMLPresentationFlag::Compressed)) { 75 // updating the compression flag is allowed 76 if (aFlagsValues.contains(MathMLPresentationFlag::Compressed)) { 77 // 'compressed' means 'prime' style in App. G, TeXbook 78 mPresentationData.flags += MathMLPresentationFlag::Compressed; 79 } 80 // no else. the flag is sticky. it retains its value once it is set 81 } 82 // These flags determine whether the dtls font feature settings should 83 // be applied. 84 if (aWhichFlags.contains(MathMLPresentationFlag::Dtls)) { 85 if (aFlagsValues.contains(MathMLPresentationFlag::Dtls)) { 86 mPresentationData.flags += MathMLPresentationFlag::Dtls; 87 } else { 88 mPresentationData.flags -= MathMLPresentationFlag::Dtls; 89 } 90 } 91 return NS_OK; 92 } 93 94 /* static */ 95 void nsMathMLFrame::GetEmbellishDataFrom(nsIFrame* aFrame, 96 nsEmbellishData& aEmbellishData) { 97 // initialize OUT params 98 aEmbellishData.flags.clear(); 99 aEmbellishData.coreFrame = nullptr; 100 aEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; 101 aEmbellishData.leadingSpace = 0; 102 aEmbellishData.trailingSpace = 0; 103 104 if (aFrame && aFrame->IsMathMLFrame()) { 105 nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame); 106 if (mathMLFrame) { 107 mathMLFrame->GetEmbellishData(aEmbellishData); 108 } 109 } 110 } 111 112 // helper to get the presentation data of a frame, by possibly walking up 113 // the frame hierarchy if we happen to be surrounded by non-MathML frames. 114 /* static */ 115 void nsMathMLFrame::GetPresentationDataFrom( 116 nsIFrame* aFrame, nsPresentationData& aPresentationData, bool aClimbTree) { 117 // initialize OUT params 118 aPresentationData.flags.clear(); 119 aPresentationData.baseFrame = nullptr; 120 121 nsIFrame* frame = aFrame; 122 while (frame) { 123 if (frame->IsMathMLFrame()) { 124 nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame); 125 if (mathMLFrame) { 126 mathMLFrame->GetPresentationData(aPresentationData); 127 break; 128 } 129 } 130 // stop if the caller doesn't want to lookup beyond the frame 131 if (!aClimbTree) { 132 break; 133 } 134 // stop if we reach the root <math> tag 135 nsIContent* content = frame->GetContent(); 136 NS_ASSERTION(content || !frame->GetParent(), // no assert for the root 137 "dangling frame without a content node"); 138 if (!content) { 139 break; 140 } 141 142 if (content->IsMathMLElement(nsGkAtoms::math)) { 143 break; 144 } 145 frame = frame->GetParent(); 146 } 147 NS_WARNING_ASSERTION( 148 frame && frame->GetContent(), 149 "bad MathML markup - could not find the top <math> element"); 150 } 151 152 /* static */ 153 void nsMathMLFrame::GetRuleThickness(DrawTarget* aDrawTarget, 154 nsFontMetrics* aFontMetrics, 155 nscoord& aRuleThickness) { 156 nscoord xHeight = aFontMetrics->XHeight(); 157 char16_t overBar = 0x00AF; 158 nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString( 159 &overBar, 1, *aFontMetrics, aDrawTarget); 160 aRuleThickness = bm.ascent + bm.descent; 161 if (aRuleThickness <= 0 || aRuleThickness >= xHeight) { 162 // fall-back to the other version 163 GetRuleThickness(aFontMetrics, aRuleThickness); 164 } 165 } 166 167 /* static */ 168 void nsMathMLFrame::GetAxisHeight(DrawTarget* aDrawTarget, 169 nsFontMetrics* aFontMetrics, 170 nscoord& aAxisHeight) { 171 RefPtr<gfxFont> mathFont = 172 aFontMetrics->GetThebesFontGroup()->GetFirstMathFont(); 173 if (mathFont) { 174 aAxisHeight = mathFont->MathTable()->Constant( 175 gfxMathTable::AxisHeight, aFontMetrics->AppUnitsPerDevPixel()); 176 return; 177 } 178 179 nscoord xHeight = aFontMetrics->XHeight(); 180 char16_t minus = 0x2212; // not '-', but official Unicode minus sign 181 nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString( 182 &minus, 1, *aFontMetrics, aDrawTarget); 183 aAxisHeight = bm.ascent - (bm.ascent + bm.descent) / 2; 184 if (aAxisHeight <= 0 || aAxisHeight >= xHeight) { 185 // fall-back to the other version 186 GetAxisHeight(aFontMetrics, aAxisHeight); 187 } 188 } 189 190 /* static */ 191 nscoord nsMathMLFrame::CalcLength(const nsCSSValue& aCSSValue, 192 float aFontSizeInflation, nsIFrame* aFrame) { 193 NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit"); 194 195 nsCSSUnit unit = aCSSValue.GetUnit(); 196 mozilla::dom::NonSVGFrameUserSpaceMetrics userSpaceMetrics(aFrame); 197 198 // The axis is only relevant for percentages, so it doesn't matter what we use 199 // here. 200 auto axis = SVGContentUtils::X; 201 202 return nsPresContext::CSSPixelsToAppUnits( 203 aCSSValue.GetFloatValue() * 204 SVGLength::GetPixelsPerCSSUnit(userSpaceMetrics, unit, axis, 205 /* aApplyZoom = */ true)); 206 } 207 208 /* static */ 209 void nsMathMLFrame::GetSubDropFromChild(nsIFrame* aChild, nscoord& aSubDrop, 210 float aFontSizeInflation) { 211 RefPtr<nsFontMetrics> fm = 212 nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation); 213 GetSubDrop(fm, aSubDrop); 214 } 215 216 /* static */ 217 void nsMathMLFrame::GetSupDropFromChild(nsIFrame* aChild, nscoord& aSupDrop, 218 float aFontSizeInflation) { 219 RefPtr<nsFontMetrics> fm = 220 nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation); 221 GetSupDrop(fm, aSupDrop); 222 } 223 224 /* static */ 225 void nsMathMLFrame::ParseAndCalcNumericValue(const nsString& aString, 226 nscoord* aLengthValue, 227 uint32_t aFlags, 228 float aFontSizeInflation, 229 nsIFrame* aFrame) { 230 nsCSSValue cssValue; 231 232 if (!dom::MathMLElement::ParseNumericValue( 233 aString, cssValue, aFlags, aFrame->PresContext()->Document())) { 234 // Invalid attribute value. aLengthValue remains unchanged, so the default 235 // length value is used. 236 return; 237 } 238 239 nsCSSUnit unit = cssValue.GetUnit(); 240 241 // Since we're reusing the SVG code to calculate unit lengths, 242 // which handles percentage values specially, we have to deal 243 // with percentages early on. 244 if (unit == eCSSUnit_Percent) { 245 *aLengthValue = NSToCoordRound(*aLengthValue * cssValue.GetPercentValue()); 246 return; 247 } 248 249 *aLengthValue = CalcLength(cssValue, aFontSizeInflation, aFrame); 250 } 251 252 namespace mozilla { 253 254 class nsDisplayMathMLBar final : public nsPaintedDisplayItem { 255 public: 256 nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, 257 const nsRect& aRect) 258 : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) { 259 MOZ_COUNT_CTOR(nsDisplayMathMLBar); 260 } 261 262 MOZ_COUNTED_DTOR_FINAL(nsDisplayMathMLBar) 263 264 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; 265 NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR) 266 267 private: 268 nsRect mRect; 269 }; 270 271 void nsDisplayMathMLBar::Paint(nsDisplayListBuilder* aBuilder, 272 gfxContext* aCtx) { 273 // paint the bar with the current text color 274 DrawTarget* drawTarget = aCtx->GetDrawTarget(); 275 Rect rect = NSRectToNonEmptySnappedRect( 276 mRect + ToReferenceFrame(), mFrame->PresContext()->AppUnitsPerDevPixel(), 277 *drawTarget); 278 ColorPattern color(ToDeviceColor( 279 mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor))); 280 drawTarget->FillRect(rect, color); 281 } 282 283 } // namespace mozilla 284 285 void nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, 286 const nsRect& aRect, 287 const nsDisplayListSet& aLists, 288 uint32_t aIndex) { 289 if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty()) { 290 return; 291 } 292 293 aLists.Content()->AppendNewToTopWithIndex<nsDisplayMathMLBar>( 294 aBuilder, aFrame, aIndex, aRect); 295 } 296 297 void nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics, 298 bool aDisplayStyle, 299 nscoord& aRadicalRuleThickness, 300 nscoord& aRadicalExtraAscender, 301 nscoord& aRadicalVerticalGap) { 302 nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel(); 303 RefPtr<gfxFont> mathFont = 304 aFontMetrics->GetThebesFontGroup()->GetFirstMathFont(); 305 306 // get the radical rulethickness 307 if (mathFont) { 308 aRadicalRuleThickness = mathFont->MathTable()->Constant( 309 gfxMathTable::RadicalRuleThickness, oneDevPixel); 310 } else { 311 GetRuleThickness(aFontMetrics, aRadicalRuleThickness); 312 } 313 314 // get the leading to be left at the top of the resulting frame 315 if (mathFont) { 316 aRadicalExtraAscender = mathFont->MathTable()->Constant( 317 gfxMathTable::RadicalExtraAscender, oneDevPixel); 318 } else { 319 // This seems more reliable than using aFontMetrics->GetLeading() on 320 // suspicious fonts. 321 nscoord em; 322 GetEmHeight(aFontMetrics, em); 323 aRadicalExtraAscender = nscoord(0.2f * em); 324 } 325 326 // get the clearance between rule and content 327 if (mathFont) { 328 aRadicalVerticalGap = mathFont->MathTable()->Constant( 329 aDisplayStyle ? gfxMathTable::RadicalDisplayStyleVerticalGap 330 : gfxMathTable::RadicalVerticalGap, 331 oneDevPixel); 332 } else { 333 // Rule 11, App. G, TeXbook 334 aRadicalVerticalGap = 335 aRadicalRuleThickness + 336 (aDisplayStyle ? aFontMetrics->XHeight() : aRadicalRuleThickness) / 4; 337 } 338 }