/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsMathMLFrame.h" #include "gfxContext.h" #include "gfxUtils.h" #include "mozilla/gfx/2D.h" #include "nsCSSValue.h" #include "nsLayoutUtils.h" #include "nsNameSpaceManager.h" #include "nsMathMLChar.h" #include "nsCSSPseudoElements.h" #include "mozilla/dom/MathMLElement.h" #include "gfxMathTable.h" #include "nsPresContextInlines.h" // used to map attributes into CSS rules #include "mozilla/ServoStyleSet.h" #include "nsDisplayList.h" using namespace mozilla; using namespace mozilla::gfx; eMathMLFrameType nsMathMLFrame::GetMathMLFrameType() { // see if it is an embellished operator (mapped to 'Op' in TeX) if (mEmbellishData.coreFrame) return GetMathMLFrameTypeFor(mEmbellishData.coreFrame); // if it has a prescribed base, fetch the type from there if (mPresentationData.baseFrame) return GetMathMLFrameTypeFor(mPresentationData.baseFrame); // everything else is treated as ordinary (mapped to 'Ord' in TeX) return eMathMLFrameType_Ordinary; } NS_IMETHODIMP nsMathMLFrame::InheritAutomaticData(nsIFrame* aParent) { mEmbellishData.flags = 0; mEmbellishData.coreFrame = nullptr; mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; mEmbellishData.leadingSpace = 0; mEmbellishData.trailingSpace = 0; mPresentationData.flags = 0; mPresentationData.baseFrame = nullptr; // by default, just inherit the display of our parent nsPresentationData parentData; GetPresentationDataFrom(aParent, parentData); #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS; #endif return NS_OK; } NS_IMETHODIMP nsMathMLFrame::UpdatePresentationData(uint32_t aFlagsValues, uint32_t aWhichFlags) { NS_ASSERTION(NS_MATHML_IS_COMPRESSED(aWhichFlags) || NS_MATHML_IS_DTLS_SET(aWhichFlags), "aWhichFlags should only be compression or dtls flag"); if (NS_MATHML_IS_COMPRESSED(aWhichFlags)) { // updating the compression flag is allowed if (NS_MATHML_IS_COMPRESSED(aFlagsValues)) { // 'compressed' means 'prime' style in App. G, TeXbook mPresentationData.flags |= NS_MATHML_COMPRESSED; } // no else. the flag is sticky. it retains its value once it is set } // These flags determine whether the dtls font feature settings should // be applied. if (NS_MATHML_IS_DTLS_SET(aWhichFlags)) { if (NS_MATHML_IS_DTLS_SET(aFlagsValues)) { mPresentationData.flags |= NS_MATHML_DTLS; } else { mPresentationData.flags &= ~NS_MATHML_DTLS; } } return NS_OK; } /* static */ void nsMathMLFrame::GetEmbellishDataFrom(nsIFrame* aFrame, nsEmbellishData& aEmbellishData) { // initialize OUT params aEmbellishData.flags = 0; aEmbellishData.coreFrame = nullptr; aEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; aEmbellishData.leadingSpace = 0; aEmbellishData.trailingSpace = 0; if (aFrame && aFrame->IsMathMLFrame()) { nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame); if (mathMLFrame) { mathMLFrame->GetEmbellishData(aEmbellishData); } } } // helper to get the presentation data of a frame, by possibly walking up // the frame hierarchy if we happen to be surrounded by non-MathML frames. /* static */ void nsMathMLFrame::GetPresentationDataFrom( nsIFrame* aFrame, nsPresentationData& aPresentationData, bool aClimbTree) { // initialize OUT params aPresentationData.flags = 0; aPresentationData.baseFrame = nullptr; nsIFrame* frame = aFrame; while (frame) { if (frame->IsMathMLFrame()) { nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame); if (mathMLFrame) { mathMLFrame->GetPresentationData(aPresentationData); break; } } // stop if the caller doesn't want to lookup beyond the frame if (!aClimbTree) { break; } // stop if we reach the root tag nsIContent* content = frame->GetContent(); NS_ASSERTION(content || !frame->GetParent(), // no assert for the root "dangling frame without a content node"); if (!content) break; if (content->IsMathMLElement(nsGkAtoms::math)) { break; } frame = frame->GetParent(); } NS_WARNING_ASSERTION( frame && frame->GetContent(), "bad MathML markup - could not find the top element"); } /* static */ void nsMathMLFrame::GetRuleThickness(DrawTarget* aDrawTarget, nsFontMetrics* aFontMetrics, nscoord& aRuleThickness) { nscoord xHeight = aFontMetrics->XHeight(); char16_t overBar = 0x00AF; nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString( &overBar, 1, *aFontMetrics, aDrawTarget); aRuleThickness = bm.ascent + bm.descent; if (aRuleThickness <= 0 || aRuleThickness >= xHeight) { // fall-back to the other version GetRuleThickness(aFontMetrics, aRuleThickness); } } /* static */ void nsMathMLFrame::GetAxisHeight(DrawTarget* aDrawTarget, nsFontMetrics* aFontMetrics, nscoord& aAxisHeight) { RefPtr mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont(); if (mathFont) { aAxisHeight = mathFont->MathTable()->Constant( gfxMathTable::AxisHeight, aFontMetrics->AppUnitsPerDevPixel()); return; } nscoord xHeight = aFontMetrics->XHeight(); char16_t minus = 0x2212; // not '-', but official Unicode minus sign nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString( &minus, 1, *aFontMetrics, aDrawTarget); aAxisHeight = bm.ascent - (bm.ascent + bm.descent) / 2; if (aAxisHeight <= 0 || aAxisHeight >= xHeight) { // fall-back to the other version GetAxisHeight(aFontMetrics, aAxisHeight); } } /* static */ nscoord nsMathMLFrame::CalcLength(nsPresContext* aPresContext, ComputedStyle* aComputedStyle, const nsCSSValue& aCSSValue, float aFontSizeInflation) { NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit"); if (aCSSValue.IsPixelLengthUnit()) { return aCSSValue.GetPixelLength(); } nsCSSUnit unit = aCSSValue.GetUnit(); if (eCSSUnit_EM == unit) { const nsStyleFont* font = aComputedStyle->StyleFont(); return font->mFont.size.ScaledBy(aCSSValue.GetFloatValue()).ToAppUnits(); } if (eCSSUnit_XHeight == unit) { RefPtr fm = nsLayoutUtils::GetFontMetricsForComputedStyle( aComputedStyle, aPresContext, aFontSizeInflation); nscoord xHeight = fm->XHeight(); return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight); } // MathML doesn't specify other CSS units such as rem or ch NS_ERROR("Unsupported unit"); return 0; } /* static */ void nsMathMLFrame::GetSubDropFromChild(nsIFrame* aChild, nscoord& aSubDrop, float aFontSizeInflation) { RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation); GetSubDrop(fm, aSubDrop); } /* static */ void nsMathMLFrame::GetSupDropFromChild(nsIFrame* aChild, nscoord& aSupDrop, float aFontSizeInflation) { RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation); GetSupDrop(fm, aSupDrop); } /* static */ void nsMathMLFrame::ParseNumericValue(const nsString& aString, nscoord* aLengthValue, uint32_t aFlags, nsPresContext* aPresContext, ComputedStyle* aComputedStyle, float aFontSizeInflation) { nsCSSValue cssValue; if (!dom::MathMLElement::ParseNumericValue(aString, cssValue, aFlags, aPresContext->Document())) { // Invalid attribute value. aLengthValue remains unchanged, so the default // length value is used. return; } nsCSSUnit unit = cssValue.GetUnit(); if (unit == eCSSUnit_Percent || unit == eCSSUnit_Number) { // Relative units. A multiple of the default length value is used. *aLengthValue = NSToCoordRound( *aLengthValue * (unit == eCSSUnit_Percent ? cssValue.GetPercentValue() : cssValue.GetFloatValue())); return; } // Absolute units. *aLengthValue = CalcLength(aPresContext, aComputedStyle, cssValue, aFontSizeInflation); } namespace mozilla { #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) class nsDisplayMathMLBoundingMetrics final : public nsDisplayItem { public: nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsRect& aRect) : nsDisplayItem(aBuilder, aFrame), mRect(aRect) { MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics); } MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBoundingMetrics) virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; NS_DISPLAY_DECL_NAME("MathMLBoundingMetrics", TYPE_MATHML_BOUNDING_METRICS) private: nsRect mRect; }; void nsDisplayMathMLBoundingMetrics::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { DrawTarget* drawTarget = aCtx->GetDrawTarget(); Rect r = NSRectToRect(mRect + ToReferenceFrame(), mFrame->PresContext()->AppUnitsPerDevPixel()); ColorPattern blue(ToDeviceColor(Color(0.f, 0.f, 1.f, 1.f))); drawTarget->StrokeRect(r, blue); } void nsMathMLFrame::DisplayBoundingMetrics(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsPoint& aPt, const nsBoundingMetrics& aMetrics, const nsDisplayListSet& aLists) { if (!NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags)) return; nscoord x = aPt.x + aMetrics.leftBearing; nscoord y = aPt.y - aMetrics.ascent; nscoord w = aMetrics.rightBearing - aMetrics.leftBearing; nscoord h = aMetrics.ascent + aMetrics.descent; aLists.Content()->AppendNewToTop( aBuilder, aFrame, nsRect(x, y, w, h)); } #endif class nsDisplayMathMLBar final : public nsPaintedDisplayItem { public: nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsRect& aRect) : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) { MOZ_COUNT_CTOR(nsDisplayMathMLBar); } MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBar) virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR) private: nsRect mRect; }; void nsDisplayMathMLBar::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { // paint the bar with the current text color DrawTarget* drawTarget = aCtx->GetDrawTarget(); Rect rect = NSRectToNonEmptySnappedRect( mRect + ToReferenceFrame(), mFrame->PresContext()->AppUnitsPerDevPixel(), *drawTarget); ColorPattern color(ToDeviceColor( mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor))); drawTarget->FillRect(rect, color); } } // namespace mozilla void nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsRect& aRect, const nsDisplayListSet& aLists, uint32_t aIndex) { if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty()) return; aLists.Content()->AppendNewToTopWithIndex( aBuilder, aFrame, aIndex, aRect); } void nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics, bool aDisplayStyle, nscoord& aRadicalRuleThickness, nscoord& aRadicalExtraAscender, nscoord& aRadicalVerticalGap) { nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel(); RefPtr mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont(); // get the radical rulethickness if (mathFont) { aRadicalRuleThickness = mathFont->MathTable()->Constant( gfxMathTable::RadicalRuleThickness, oneDevPixel); } else { GetRuleThickness(aFontMetrics, aRadicalRuleThickness); } // get the leading to be left at the top of the resulting frame if (mathFont) { aRadicalExtraAscender = mathFont->MathTable()->Constant( gfxMathTable::RadicalExtraAscender, oneDevPixel); } else { // This seems more reliable than using aFontMetrics->GetLeading() on // suspicious fonts. nscoord em; GetEmHeight(aFontMetrics, em); aRadicalExtraAscender = nscoord(0.2f * em); } // get the clearance between rule and content if (mathFont) { aRadicalVerticalGap = mathFont->MathTable()->Constant( aDisplayStyle ? gfxMathTable::RadicalDisplayStyleVerticalGap : gfxMathTable::RadicalVerticalGap, oneDevPixel); } else { // Rule 11, App. G, TeXbook aRadicalVerticalGap = aRadicalRuleThickness + (aDisplayStyle ? aFontMetrics->XHeight() : aRadicalRuleThickness) / 4; } }