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 "nsMathMLmrootFrame.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRenderingContext.h" michael@0: #include michael@0: michael@0: // michael@0: // and -- form a radical - implementation michael@0: // michael@0: michael@0: //NOTE: michael@0: // The code assumes that TeX fonts are picked. michael@0: // There is no fall-back to draw the branches of the sqrt explicitly michael@0: // in the case where TeX fonts are not there. In general, there are no michael@0: // fall-back(s) in MathML when some (freely-downloadable) fonts are missing. michael@0: // Otherwise, this will add much work and unnecessary complexity to the core michael@0: // MathML engine. Assuming that authors have the free fonts is part of the michael@0: // deal. We are not responsible for cases of misconfigurations out there. michael@0: michael@0: // additional style context to be used by our MathMLChar. michael@0: #define NS_SQR_CHAR_STYLE_CONTEXT_INDEX 0 michael@0: michael@0: static const char16_t kSqrChar = char16_t(0x221A); michael@0: michael@0: nsIFrame* michael@0: NS_NewMathMLmrootFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsMathMLmrootFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame) michael@0: michael@0: nsMathMLmrootFrame::nsMathMLmrootFrame(nsStyleContext* aContext) : michael@0: nsMathMLContainerFrame(aContext), michael@0: mSqrChar(), michael@0: mBarRect() michael@0: { michael@0: } michael@0: michael@0: nsMathMLmrootFrame::~nsMathMLmrootFrame() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsMathMLmrootFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: nsPresContext *presContext = PresContext(); michael@0: michael@0: // No need to track the style context given to our MathML char. michael@0: // The Style System will use Get/SetAdditionalStyleContext() to keep it michael@0: // up-to-date if dynamic changes arise. michael@0: nsAutoString sqrChar; sqrChar.Assign(kSqrChar); michael@0: mSqrChar.SetData(presContext, sqrChar); michael@0: ResolveMathMLCharStyle(presContext, mContent, mStyleContext, &mSqrChar); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMathMLmrootFrame::TransmitAutomaticData() michael@0: { michael@0: // 1. The REC says: michael@0: // The element increments scriptlevel by 2, and sets displaystyle to michael@0: // "false", within index, but leaves both attributes unchanged within base. michael@0: // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed michael@0: UpdatePresentationDataFromChildAt(1, 1, michael@0: NS_MATHML_COMPRESSED, michael@0: NS_MATHML_COMPRESSED); michael@0: UpdatePresentationDataFromChildAt(0, 0, michael@0: NS_MATHML_COMPRESSED, NS_MATHML_COMPRESSED); michael@0: michael@0: PropagateFrameFlagFor(mFrames.LastChild(), michael@0: NS_FRAME_MATHML_SCRIPT_DESCENDANT); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: ///////////// michael@0: // paint the content we are square-rooting michael@0: nsMathMLContainerFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); michael@0: michael@0: ///////////// michael@0: // paint the sqrt symbol michael@0: if (!NS_MATHML_HAS_ERROR(mPresentationData.flags)) { michael@0: mSqrChar.Display(aBuilder, this, aLists, 0); michael@0: michael@0: DisplayBar(aBuilder, this, mBarRect, aLists); michael@0: michael@0: #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) michael@0: // for visual debug michael@0: nsRect rect; michael@0: mSqrChar.GetRect(rect); michael@0: nsBoundingMetrics bm; michael@0: mSqrChar.GetBoundingMetrics(bm); michael@0: DisplayBoundingMetrics(aBuilder, this, rect.TopLeft(), bm, aLists); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: static void michael@0: GetRadicalXOffsets(nscoord aIndexWidth, nscoord aSqrWidth, michael@0: nsFontMetrics* aFontMetrics, michael@0: nscoord* aIndexOffset, nscoord* aSqrOffset) michael@0: { michael@0: // The index is tucked in closer to the radical while making sure michael@0: // that the kern does not make the index and radical collide michael@0: nscoord dxIndex, dxSqr; michael@0: nscoord xHeight = aFontMetrics->XHeight(); michael@0: nscoord indexRadicalKern = NSToCoordRound(1.35f * xHeight); michael@0: if (indexRadicalKern > aIndexWidth) { michael@0: dxIndex = indexRadicalKern - aIndexWidth; michael@0: dxSqr = 0; michael@0: } michael@0: else { michael@0: dxIndex = 0; michael@0: dxSqr = aIndexWidth - indexRadicalKern; michael@0: } michael@0: // avoid collision by leaving a minimum space between index and radical michael@0: nscoord minimumClearance = aSqrWidth/2; michael@0: if (dxIndex + aIndexWidth + minimumClearance > dxSqr + aSqrWidth) { michael@0: if (aIndexWidth + minimumClearance < aSqrWidth) { michael@0: dxIndex = aSqrWidth - (aIndexWidth + minimumClearance); michael@0: dxSqr = 0; michael@0: } michael@0: else { michael@0: dxIndex = 0; michael@0: dxSqr = (aIndexWidth + minimumClearance) - aSqrWidth; michael@0: } michael@0: } michael@0: michael@0: if (aIndexOffset) michael@0: *aIndexOffset = dxIndex; michael@0: if (aSqrOffset) michael@0: *aSqrOffset = dxSqr; michael@0: } michael@0: michael@0: nsresult michael@0: nsMathMLmrootFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsSize availSize(aReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE); michael@0: nsReflowStatus childStatus; michael@0: michael@0: aDesiredSize.Width() = aDesiredSize.Height() = 0; michael@0: aDesiredSize.SetTopAscent(0); michael@0: michael@0: nsBoundingMetrics bmSqr, bmBase, bmIndex; michael@0: nsRenderingContext& renderingContext = *aReflowState.rendContext; michael@0: michael@0: ////////////////// michael@0: // Reflow Children michael@0: michael@0: int32_t count = 0; michael@0: nsIFrame* baseFrame = nullptr; michael@0: nsIFrame* indexFrame = nullptr; michael@0: nsHTMLReflowMetrics baseSize(aReflowState); michael@0: nsHTMLReflowMetrics indexSize(aReflowState); michael@0: nsIFrame* childFrame = mFrames.FirstChild(); michael@0: while (childFrame) { michael@0: // ask our children to compute their bounding metrics michael@0: nsHTMLReflowMetrics childDesiredSize(aReflowState, michael@0: aDesiredSize.mFlags michael@0: | NS_REFLOW_CALC_BOUNDING_METRICS); michael@0: nsHTMLReflowState childReflowState(aPresContext, aReflowState, michael@0: childFrame, availSize); michael@0: rv = ReflowChild(childFrame, aPresContext, michael@0: childDesiredSize, childReflowState, childStatus); michael@0: //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status"); michael@0: if (NS_FAILED(rv)) { michael@0: // Call DidReflow() for the child frames we successfully did reflow. michael@0: DidReflowChildren(mFrames.FirstChild(), childFrame); michael@0: return rv; michael@0: } michael@0: if (0 == count) { michael@0: // base michael@0: baseFrame = childFrame; michael@0: baseSize = childDesiredSize; michael@0: bmBase = childDesiredSize.mBoundingMetrics; michael@0: } michael@0: else if (1 == count) { michael@0: // index michael@0: indexFrame = childFrame; michael@0: indexSize = childDesiredSize; michael@0: bmIndex = childDesiredSize.mBoundingMetrics; michael@0: } michael@0: count++; michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: if (2 != count) { michael@0: // report an error, encourage people to get their markups in order michael@0: ReportChildCountError(); michael@0: rv = ReflowError(renderingContext, aDesiredSize); michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: // Call DidReflow() for the child frames we successfully did reflow. michael@0: DidReflowChildren(mFrames.FirstChild(), childFrame); michael@0: return rv; michael@0: } michael@0: michael@0: //////////// michael@0: // Prepare the radical symbol and the overline bar michael@0: michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); michael@0: renderingContext.SetFont(fm); michael@0: michael@0: // For radical glyphs from TeX fonts and some of the radical glyphs from michael@0: // Mathematica fonts, the thickness of the overline can be obtained from the michael@0: // ascent of the glyph. Most fonts however have radical glyphs above the michael@0: // baseline so no assumption can be made about the meaning of the ascent. michael@0: nscoord ruleThickness, leading, em; michael@0: GetRuleThickness(renderingContext, fm, ruleThickness); michael@0: michael@0: char16_t one = '1'; michael@0: nsBoundingMetrics bmOne = renderingContext.GetBoundingMetrics(&one, 1); michael@0: michael@0: // get the leading to be left at the top of the resulting frame michael@0: // this seems more reliable than using fm->GetLeading() on suspicious fonts michael@0: GetEmHeight(fm, em); michael@0: leading = nscoord(0.2f * em); michael@0: michael@0: // Rule 11, App. G, TeXbook michael@0: // psi = clearance between rule and content michael@0: nscoord phi = 0, psi = 0; michael@0: if (StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK) michael@0: phi = fm->XHeight(); michael@0: else michael@0: phi = ruleThickness; michael@0: psi = ruleThickness + phi/4; michael@0: michael@0: // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook, p.131) michael@0: if (bmOne.ascent > bmBase.ascent) michael@0: psi += bmOne.ascent - bmBase.ascent; michael@0: michael@0: // make sure that the rule appears on on screen michael@0: nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); michael@0: if (ruleThickness < onePixel) { michael@0: ruleThickness = onePixel; michael@0: } michael@0: michael@0: // adjust clearance psi to get an exact number of pixels -- this michael@0: // gives a nicer & uniform look on stacked radicals (bug 130282) michael@0: nscoord delta = psi % onePixel; michael@0: if (delta) michael@0: psi += onePixel - delta; // round up michael@0: michael@0: // Stretch the radical symbol to the appropriate height if it is not big enough. michael@0: nsBoundingMetrics contSize = bmBase; michael@0: contSize.descent = bmBase.ascent + bmBase.descent + psi; michael@0: contSize.ascent = ruleThickness; michael@0: michael@0: // height(radical) should be >= height(base) + psi + ruleThickness michael@0: nsBoundingMetrics radicalSize; michael@0: mSqrChar.Stretch(aPresContext, renderingContext, michael@0: NS_STRETCH_DIRECTION_VERTICAL, michael@0: contSize, radicalSize, michael@0: NS_STRETCH_LARGER, michael@0: StyleVisibility()->mDirection); michael@0: // radicalSize have changed at this point, and should match with michael@0: // the bounding metrics of the char michael@0: mSqrChar.GetBoundingMetrics(bmSqr); michael@0: michael@0: // Update the desired size for the container (like msqrt, index is not yet included) michael@0: // the baseline will be that of the base. michael@0: mBoundingMetrics.ascent = bmBase.ascent + psi + ruleThickness; michael@0: mBoundingMetrics.descent = michael@0: std::max(bmBase.descent, michael@0: (bmSqr.ascent + bmSqr.descent - mBoundingMetrics.ascent)); michael@0: mBoundingMetrics.width = bmSqr.width + bmBase.width; michael@0: mBoundingMetrics.leftBearing = bmSqr.leftBearing; michael@0: mBoundingMetrics.rightBearing = bmSqr.width + michael@0: std::max(bmBase.width, bmBase.rightBearing); // take also care of the rule michael@0: michael@0: aDesiredSize.SetTopAscent(mBoundingMetrics.ascent + leading); michael@0: aDesiredSize.Height() = aDesiredSize.TopAscent() + michael@0: std::max(baseSize.Height() - baseSize.TopAscent(), michael@0: mBoundingMetrics.descent + ruleThickness); michael@0: aDesiredSize.Width() = mBoundingMetrics.width; michael@0: michael@0: ///////////// michael@0: // Re-adjust the desired size to include the index. michael@0: michael@0: // the index is raised by some fraction of the height michael@0: // of the radical, see \mroot macro in App. B, TexBook michael@0: nscoord raiseIndexDelta = NSToCoordRound(0.6f * (bmSqr.ascent + bmSqr.descent)); michael@0: nscoord indexRaisedAscent = mBoundingMetrics.ascent // top of radical michael@0: - (bmSqr.ascent + bmSqr.descent) // to bottom of radical michael@0: + raiseIndexDelta + bmIndex.ascent + bmIndex.descent; // to top of raised index michael@0: michael@0: nscoord indexClearance = 0; michael@0: if (mBoundingMetrics.ascent < indexRaisedAscent) { michael@0: indexClearance = michael@0: indexRaisedAscent - mBoundingMetrics.ascent; // excess gap introduced by a tall index michael@0: mBoundingMetrics.ascent = indexRaisedAscent; michael@0: nscoord descent = aDesiredSize.Height() - aDesiredSize.TopAscent(); michael@0: aDesiredSize.SetTopAscent(mBoundingMetrics.ascent + leading); michael@0: aDesiredSize.Height() = aDesiredSize.TopAscent() + descent; michael@0: } michael@0: michael@0: nscoord dxIndex, dxSqr; michael@0: GetRadicalXOffsets(bmIndex.width, bmSqr.width, fm, &dxIndex, &dxSqr); michael@0: michael@0: mBoundingMetrics.width = dxSqr + bmSqr.width + bmBase.width; michael@0: mBoundingMetrics.leftBearing = michael@0: std::min(dxIndex + bmIndex.leftBearing, dxSqr + bmSqr.leftBearing); michael@0: mBoundingMetrics.rightBearing = dxSqr + bmSqr.width + michael@0: std::max(bmBase.width, bmBase.rightBearing); michael@0: michael@0: aDesiredSize.Width() = mBoundingMetrics.width; michael@0: aDesiredSize.mBoundingMetrics = mBoundingMetrics; michael@0: GatherAndStoreOverflow(&aDesiredSize); michael@0: michael@0: // place the index michael@0: nscoord dx = dxIndex; michael@0: nscoord dy = aDesiredSize.TopAscent() - (indexRaisedAscent + indexSize.TopAscent() - bmIndex.ascent); michael@0: FinishReflowChild(indexFrame, aPresContext, indexSize, nullptr, michael@0: MirrorIfRTL(aDesiredSize.Width(), indexSize.Width(), dx), michael@0: dy, 0); michael@0: michael@0: // place the radical symbol and the radical bar michael@0: dx = dxSqr; michael@0: dy = indexClearance + leading; // leave a leading at the top michael@0: mSqrChar.SetRect(nsRect(MirrorIfRTL(aDesiredSize.Width(), bmSqr.width, dx), michael@0: dy, bmSqr.width, bmSqr.ascent + bmSqr.descent)); michael@0: dx += bmSqr.width; michael@0: mBarRect.SetRect(MirrorIfRTL(aDesiredSize.Width(), bmBase.width, dx), michael@0: dy, bmBase.width, ruleThickness); michael@0: michael@0: // place the base michael@0: dy = aDesiredSize.TopAscent() - baseSize.TopAscent(); michael@0: FinishReflowChild(baseFrame, aPresContext, baseSize, nullptr, michael@0: MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx), michael@0: dy, 0); michael@0: michael@0: mReference.x = 0; michael@0: mReference.y = aDesiredSize.TopAscent(); michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsMathMLmrootFrame::GetIntrinsicWidthMetrics(nsRenderingContext* aRenderingContext, nsHTMLReflowMetrics& aDesiredSize) michael@0: { michael@0: nsIFrame* baseFrame = mFrames.FirstChild(); michael@0: nsIFrame* indexFrame = nullptr; michael@0: if (baseFrame) michael@0: indexFrame = baseFrame->GetNextSibling(); michael@0: if (!indexFrame || indexFrame->GetNextSibling()) { michael@0: ReflowError(*aRenderingContext, aDesiredSize); michael@0: return; michael@0: } michael@0: michael@0: nscoord baseWidth = michael@0: nsLayoutUtils::IntrinsicForContainer(aRenderingContext, baseFrame, michael@0: nsLayoutUtils::PREF_WIDTH); michael@0: nscoord indexWidth = michael@0: nsLayoutUtils::IntrinsicForContainer(aRenderingContext, indexFrame, michael@0: nsLayoutUtils::PREF_WIDTH); michael@0: nscoord sqrWidth = mSqrChar.GetMaxWidth(PresContext(), *aRenderingContext); michael@0: michael@0: nscoord dxSqr; michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); michael@0: GetRadicalXOffsets(indexWidth, sqrWidth, fm, nullptr, &dxSqr); michael@0: michael@0: nscoord width = dxSqr + sqrWidth + baseWidth; michael@0: michael@0: aDesiredSize.Width() = width; michael@0: aDesiredSize.mBoundingMetrics.width = width; michael@0: aDesiredSize.mBoundingMetrics.leftBearing = 0; michael@0: aDesiredSize.mBoundingMetrics.rightBearing = width; michael@0: } michael@0: michael@0: // ---------------------- michael@0: // the Style System will use these to pass the proper style context to our MathMLChar michael@0: nsStyleContext* michael@0: nsMathMLmrootFrame::GetAdditionalStyleContext(int32_t aIndex) const michael@0: { michael@0: switch (aIndex) { michael@0: case NS_SQR_CHAR_STYLE_CONTEXT_INDEX: michael@0: return mSqrChar.GetStyleContext(); michael@0: break; michael@0: default: michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMathMLmrootFrame::SetAdditionalStyleContext(int32_t aIndex, michael@0: nsStyleContext* aStyleContext) michael@0: { michael@0: switch (aIndex) { michael@0: case NS_SQR_CHAR_STYLE_CONTEXT_INDEX: michael@0: mSqrChar.SetStyleContext(aStyleContext); michael@0: break; michael@0: } michael@0: }