michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMathMLFrame.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsMathMLChar.h" michael@0: #include "nsCSSPseudoElements.h" michael@0: #include "nsMathMLElement.h" michael@0: michael@0: // used to map attributes into CSS rules michael@0: #include "nsStyleSet.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsRenderingContext.h" michael@0: michael@0: eMathMLFrameType michael@0: nsMathMLFrame::GetMathMLFrameType() michael@0: { michael@0: // see if it is an embellished operator (mapped to 'Op' in TeX) michael@0: if (mEmbellishData.coreFrame) michael@0: return GetMathMLFrameTypeFor(mEmbellishData.coreFrame); michael@0: michael@0: // if it has a prescribed base, fetch the type from there michael@0: if (mPresentationData.baseFrame) michael@0: return GetMathMLFrameTypeFor(mPresentationData.baseFrame); michael@0: michael@0: // everything else is treated as ordinary (mapped to 'Ord' in TeX) michael@0: return eMathMLFrameType_Ordinary; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMathMLFrame::InheritAutomaticData(nsIFrame* aParent) michael@0: { michael@0: mEmbellishData.flags = 0; michael@0: mEmbellishData.coreFrame = nullptr; michael@0: mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; michael@0: mEmbellishData.leadingSpace = 0; michael@0: mEmbellishData.trailingSpace = 0; michael@0: michael@0: mPresentationData.flags = 0; michael@0: mPresentationData.baseFrame = nullptr; michael@0: michael@0: // by default, just inherit the display of our parent michael@0: nsPresentationData parentData; michael@0: GetPresentationDataFrom(aParent, parentData); michael@0: michael@0: #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) michael@0: mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS; michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMathMLFrame::UpdatePresentationData(uint32_t aFlagsValues, michael@0: uint32_t aWhichFlags) michael@0: { michael@0: NS_ASSERTION(NS_MATHML_IS_COMPRESSED(aWhichFlags), michael@0: "aWhichFlags should only be compression flag"); michael@0: michael@0: if (NS_MATHML_IS_COMPRESSED(aWhichFlags)) { michael@0: // updating the compression flag is allowed michael@0: if (NS_MATHML_IS_COMPRESSED(aFlagsValues)) { michael@0: // 'compressed' means 'prime' style in App. G, TeXbook michael@0: mPresentationData.flags |= NS_MATHML_COMPRESSED; michael@0: } michael@0: // no else. the flag is sticky. it retains its value once it is set michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Helper to give a style context suitable for doing the stretching of michael@0: // a MathMLChar. Frame classes that use this should ensure that the michael@0: // extra leaf style contexts given to the MathMLChars are accessible to michael@0: // the Style System via the Get/Set AdditionalStyleContext() APIs. michael@0: /* static */ void michael@0: nsMathMLFrame::ResolveMathMLCharStyle(nsPresContext* aPresContext, michael@0: nsIContent* aContent, michael@0: nsStyleContext* aParentStyleContext, michael@0: nsMathMLChar* aMathMLChar) michael@0: { michael@0: nsCSSPseudoElements::Type pseudoType = michael@0: nsCSSPseudoElements::ePseudo_mozMathAnonymous; // savings michael@0: nsRefPtr newStyleContext; michael@0: newStyleContext = aPresContext->StyleSet()-> michael@0: ResolvePseudoElementStyle(aContent->AsElement(), pseudoType, michael@0: aParentStyleContext, nullptr); michael@0: michael@0: aMathMLChar->SetStyleContext(newStyleContext); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsMathMLFrame::GetEmbellishDataFrom(nsIFrame* aFrame, michael@0: nsEmbellishData& aEmbellishData) michael@0: { michael@0: // initialize OUT params michael@0: aEmbellishData.flags = 0; michael@0: aEmbellishData.coreFrame = nullptr; michael@0: aEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED; michael@0: aEmbellishData.leadingSpace = 0; michael@0: aEmbellishData.trailingSpace = 0; michael@0: michael@0: if (aFrame && aFrame->IsFrameOfType(nsIFrame::eMathML)) { michael@0: nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame); michael@0: if (mathMLFrame) { michael@0: mathMLFrame->GetEmbellishData(aEmbellishData); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // helper to get the presentation data of a frame, by possibly walking up michael@0: // the frame hierarchy if we happen to be surrounded by non-MathML frames. michael@0: /* static */ void michael@0: nsMathMLFrame::GetPresentationDataFrom(nsIFrame* aFrame, michael@0: nsPresentationData& aPresentationData, michael@0: bool aClimbTree) michael@0: { michael@0: // initialize OUT params michael@0: aPresentationData.flags = 0; michael@0: aPresentationData.baseFrame = nullptr; michael@0: michael@0: nsIFrame* frame = aFrame; michael@0: while (frame) { michael@0: if (frame->IsFrameOfType(nsIFrame::eMathML)) { michael@0: nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame); michael@0: if (mathMLFrame) { michael@0: mathMLFrame->GetPresentationData(aPresentationData); michael@0: break; michael@0: } michael@0: } michael@0: // stop if the caller doesn't want to lookup beyond the frame michael@0: if (!aClimbTree) { michael@0: break; michael@0: } michael@0: // stop if we reach the root tag michael@0: nsIContent* content = frame->GetContent(); michael@0: NS_ASSERTION(content || !frame->GetParent(), // no assert for the root michael@0: "dangling frame without a content node"); michael@0: if (!content) michael@0: break; michael@0: michael@0: if (content->Tag() == nsGkAtoms::math) { michael@0: break; michael@0: } michael@0: frame = frame->GetParent(); michael@0: } michael@0: NS_WARN_IF_FALSE(frame && frame->GetContent(), michael@0: "bad MathML markup - could not find the top element"); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsMathMLFrame::GetRuleThickness(nsRenderingContext& aRenderingContext, michael@0: nsFontMetrics* aFontMetrics, michael@0: nscoord& aRuleThickness) michael@0: { michael@0: // get the bounding metrics of the overbar char, the rendering context michael@0: // is assumed to have been set with the font of the current style context michael@0: NS_ASSERTION(aRenderingContext.FontMetrics()->Font(). michael@0: Equals(aFontMetrics->Font()), michael@0: "unexpected state"); michael@0: michael@0: nscoord xHeight = aFontMetrics->XHeight(); michael@0: char16_t overBar = 0x00AF; michael@0: nsBoundingMetrics bm = aRenderingContext.GetBoundingMetrics(&overBar, 1); michael@0: aRuleThickness = bm.ascent + bm.descent; michael@0: if (aRuleThickness <= 0 || aRuleThickness >= xHeight) { michael@0: // fall-back to the other version michael@0: GetRuleThickness(aFontMetrics, aRuleThickness); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsMathMLFrame::GetAxisHeight(nsRenderingContext& aRenderingContext, michael@0: nsFontMetrics* aFontMetrics, michael@0: nscoord& aAxisHeight) michael@0: { michael@0: // get the bounding metrics of the minus sign, the rendering context michael@0: // is assumed to have been set with the font of the current style context michael@0: NS_ASSERTION(aRenderingContext.FontMetrics()->Font(). michael@0: Equals(aFontMetrics->Font()), michael@0: "unexpected state"); michael@0: michael@0: nscoord xHeight = aFontMetrics->XHeight(); michael@0: char16_t minus = 0x2212; // not '-', but official Unicode minus sign michael@0: nsBoundingMetrics bm = aRenderingContext.GetBoundingMetrics(&minus, 1); michael@0: aAxisHeight = bm.ascent - (bm.ascent + bm.descent)/2; michael@0: if (aAxisHeight <= 0 || aAxisHeight >= xHeight) { michael@0: // fall-back to the other version michael@0: GetAxisHeight(aFontMetrics, aAxisHeight); michael@0: } michael@0: } michael@0: michael@0: /* static */ nscoord michael@0: nsMathMLFrame::CalcLength(nsPresContext* aPresContext, michael@0: nsStyleContext* aStyleContext, michael@0: const nsCSSValue& aCSSValue) michael@0: { michael@0: NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit"); michael@0: michael@0: if (aCSSValue.IsFixedLengthUnit()) { michael@0: return aCSSValue.GetFixedLength(aPresContext); michael@0: } michael@0: if (aCSSValue.IsPixelLengthUnit()) { michael@0: return aCSSValue.GetPixelLength(); michael@0: } michael@0: michael@0: nsCSSUnit unit = aCSSValue.GetUnit(); michael@0: michael@0: if (eCSSUnit_EM == unit) { michael@0: const nsStyleFont* font = aStyleContext->StyleFont(); michael@0: return NSToCoordRound(aCSSValue.GetFloatValue() * (float)font->mFont.size); michael@0: } michael@0: else if (eCSSUnit_XHeight == unit) { michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForStyleContext(aStyleContext, michael@0: getter_AddRefs(fm)); michael@0: nscoord xHeight = fm->XHeight(); michael@0: return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight); michael@0: } michael@0: michael@0: // MathML doesn't specify other CSS units such as rem or ch michael@0: NS_ERROR("Unsupported unit"); michael@0: return 0; michael@0: } michael@0: michael@0: /* static */ void michael@0: nsMathMLFrame::ParseNumericValue(const nsString& aString, michael@0: nscoord* aLengthValue, michael@0: uint32_t aFlags, michael@0: nsPresContext* aPresContext, michael@0: nsStyleContext* aStyleContext) michael@0: { michael@0: nsCSSValue cssValue; michael@0: michael@0: if (!nsMathMLElement::ParseNumericValue(aString, cssValue, aFlags, michael@0: aPresContext->Document())) { michael@0: // Invalid attribute value. aLengthValue remains unchanged, so the default michael@0: // length value is used. michael@0: return; michael@0: } michael@0: michael@0: nsCSSUnit unit = cssValue.GetUnit(); michael@0: michael@0: if (unit == eCSSUnit_Percent || unit == eCSSUnit_Number) { michael@0: // Relative units. A multiple of the default length value is used. michael@0: *aLengthValue = NSToCoordRound(*aLengthValue * (unit == eCSSUnit_Percent ? michael@0: cssValue.GetPercentValue() : michael@0: cssValue.GetFloatValue())); michael@0: return; michael@0: } michael@0: michael@0: // Absolute units. michael@0: *aLengthValue = CalcLength(aPresContext, aStyleContext, cssValue); michael@0: } michael@0: michael@0: // ================ michael@0: // Utils to map attributes into CSS rules (work-around to bug 69409 which michael@0: // is not scheduled to be fixed anytime soon) michael@0: // michael@0: michael@0: struct michael@0: nsCSSMapping { michael@0: int32_t compatibility; michael@0: const nsIAtom* attrAtom; michael@0: const char* cssProperty; michael@0: }; michael@0: michael@0: #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) michael@0: class nsDisplayMathMLBoundingMetrics : public nsDisplayItem { michael@0: public: michael@0: nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, const nsRect& aRect) michael@0: : nsDisplayItem(aBuilder, aFrame), mRect(aRect) { michael@0: MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayMathMLBoundingMetrics() { michael@0: MOZ_COUNT_DTOR(nsDisplayMathMLBoundingMetrics); michael@0: } michael@0: #endif michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: NS_DISPLAY_DECL_NAME("MathMLBoundingMetrics", TYPE_MATHML_BOUNDING_METRICS) michael@0: private: michael@0: nsRect mRect; michael@0: }; michael@0: michael@0: void nsDisplayMathMLBoundingMetrics::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: aCtx->SetColor(NS_RGB(0,0,255)); michael@0: aCtx->DrawRect(mRect + ToReferenceFrame()); michael@0: } michael@0: michael@0: nsresult michael@0: nsMathMLFrame::DisplayBoundingMetrics(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, const nsPoint& aPt, michael@0: const nsBoundingMetrics& aMetrics, michael@0: const nsDisplayListSet& aLists) { michael@0: if (!NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags)) michael@0: return NS_OK; michael@0: michael@0: nscoord x = aPt.x + aMetrics.leftBearing; michael@0: nscoord y = aPt.y - aMetrics.ascent; michael@0: nscoord w = aMetrics.rightBearing - aMetrics.leftBearing; michael@0: nscoord h = aMetrics.ascent + aMetrics.descent; michael@0: michael@0: return aLists.Content()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayMathMLBoundingMetrics(aBuilder, this, nsRect(x,y,w,h))); michael@0: } michael@0: #endif michael@0: michael@0: class nsDisplayMathMLBar : public nsDisplayItem { michael@0: public: michael@0: nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, const nsRect& aRect) michael@0: : nsDisplayItem(aBuilder, aFrame), mRect(aRect) { michael@0: MOZ_COUNT_CTOR(nsDisplayMathMLBar); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayMathMLBar() { michael@0: MOZ_COUNT_DTOR(nsDisplayMathMLBar); michael@0: } michael@0: #endif michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR) michael@0: private: michael@0: nsRect mRect; michael@0: }; michael@0: michael@0: void nsDisplayMathMLBar::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: // paint the bar with the current text color michael@0: aCtx->SetColor(mFrame->GetVisitedDependentColor(eCSSProperty_color)); michael@0: aCtx->FillRect(mRect + ToReferenceFrame()); michael@0: } michael@0: michael@0: void michael@0: nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame, const nsRect& aRect, michael@0: const nsDisplayListSet& aLists) { michael@0: if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty()) michael@0: return; michael@0: michael@0: aLists.Content()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayMathMLBar(aBuilder, aFrame, aRect)); michael@0: }