diff options
Diffstat (limited to 'layout/mathml/nsMathMLFrame.cpp')
-rw-r--r-- | layout/mathml/nsMathMLFrame.cpp | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLFrame.cpp b/layout/mathml/nsMathMLFrame.cpp new file mode 100644 index 0000000000..9a7d94a992 --- /dev/null +++ b/layout/mathml/nsMathMLFrame.cpp @@ -0,0 +1,383 @@ +/* -*- 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->IsFrameOfType(nsIFrame::eMathML)) { + 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->IsFrameOfType(nsIFrame::eMathML)) { + 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 <math> 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 <math> 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<gfxFont> 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<nsFontMetrics> 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<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation); + GetSubDrop(fm, aSubDrop); +} + +/* static */ +void nsMathMLFrame::GetSupDropFromChild(nsIFrame* aChild, nscoord& aSupDrop, + float aFontSizeInflation) { + RefPtr<nsFontMetrics> 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<nsDisplayMathMLBoundingMetrics>( + 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<nsDisplayMathMLBar>( + aBuilder, aFrame, aIndex, aRect); +} + +void nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics, + bool aDisplayStyle, + nscoord& aRadicalRuleThickness, + nscoord& aRadicalExtraAscender, + nscoord& aRadicalVerticalGap) { + nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel(); + RefPtr<gfxFont> 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; + } +} |