Backed out changeset d497ae104185 (bug 1908069) Backed out changeset c87b8ca300dc (bug 1908069) Backed out changeset 3e76c02843a7 (bug 1908069) Backed out changeset f4c5983c700b (bug 1908069) Backed out changeset 5468da212605 (bug 1908069) Backed out changeset ab5ab71ddfc4 (bug 1908069) Backed out changeset 21269d373fff (bug 1908069)
378 lines
14 KiB
C++
378 lines
14 KiB
C++
/* -*- 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 "nsMathMLmfracFrame.h"
|
|
|
|
#include "gfxUtils.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/StaticPrefs_mathml.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsDisplayList.h"
|
|
#include "gfxContext.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/MathMLElement.h"
|
|
#include <algorithm>
|
|
#include "gfxMathTable.h"
|
|
#include "gfxTextRun.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
|
|
//
|
|
// <mfrac> -- form a fraction from two subexpressions - implementation
|
|
//
|
|
|
|
nsIFrame* NS_NewMathMLmfracFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
|
|
return new (aPresShell)
|
|
nsMathMLmfracFrame(aStyle, aPresShell->GetPresContext());
|
|
}
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfracFrame)
|
|
|
|
nsMathMLmfracFrame::~nsMathMLmfracFrame() = default;
|
|
|
|
eMathMLFrameType nsMathMLmfracFrame::GetMathMLFrameType() {
|
|
// frac is "inner" in TeXBook, Appendix G, rule 15e. See also page 170.
|
|
return eMathMLFrameType_Inner;
|
|
}
|
|
|
|
uint8_t nsMathMLmfracFrame::ScriptIncrement(nsIFrame* aFrame) {
|
|
if (StyleFont()->mMathStyle == StyleMathStyle::Compact && aFrame &&
|
|
(mFrames.FirstChild() == aFrame || mFrames.LastChild() == aFrame)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsMathMLmfracFrame::TransmitAutomaticData() {
|
|
// The TeXbook (Ch 17. p.141) says the numerator inherits the compression
|
|
// while the denominator is compressed
|
|
UpdatePresentationDataFromChildAt(1, 1, NS_MATHML_COMPRESSED,
|
|
NS_MATHML_COMPRESSED);
|
|
|
|
// If displaystyle is false, then scriptlevel is incremented, so notify the
|
|
// children of this.
|
|
if (StyleFont()->mMathStyle == StyleMathStyle::Compact) {
|
|
PropagateFrameFlagFor(mFrames.FirstChild(),
|
|
NS_FRAME_MATHML_SCRIPT_DESCENDANT);
|
|
PropagateFrameFlagFor(mFrames.LastChild(),
|
|
NS_FRAME_MATHML_SCRIPT_DESCENDANT);
|
|
}
|
|
|
|
// if our numerator is an embellished operator, let its state bubble to us
|
|
GetEmbellishDataFrom(mFrames.FirstChild(), mEmbellishData);
|
|
if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) {
|
|
// even when embellished, we need to record that <mfrac> won't fire
|
|
// Stretch() on its embellished child
|
|
mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nscoord nsMathMLmfracFrame::CalcLineThickness(nsPresContext* aPresContext,
|
|
ComputedStyle* aComputedStyle,
|
|
nsString& aThicknessAttribute,
|
|
nscoord onePixel,
|
|
nscoord aDefaultRuleThickness,
|
|
float aFontSizeInflation) {
|
|
nscoord defaultThickness = aDefaultRuleThickness;
|
|
nscoord lineThickness = aDefaultRuleThickness;
|
|
nscoord minimumThickness = onePixel;
|
|
|
|
// linethickness
|
|
// https://w3c.github.io/mathml-core/#dfn-linethickness
|
|
if (!aThicknessAttribute.IsEmpty()) {
|
|
lineThickness = defaultThickness;
|
|
ParseNumericValue(aThicknessAttribute, &lineThickness,
|
|
dom::MathMLElement::PARSE_ALLOW_NEGATIVE, aPresContext,
|
|
aComputedStyle, aFontSizeInflation);
|
|
// MathML Core says a negative value is interpreted as 0.
|
|
if (lineThickness < 0) {
|
|
lineThickness = 0;
|
|
}
|
|
}
|
|
// use minimum if the lineThickness is a non-zero value less than minimun
|
|
if (lineThickness && lineThickness < minimumThickness)
|
|
lineThickness = minimumThickness;
|
|
|
|
return lineThickness;
|
|
}
|
|
|
|
void nsMathMLmfracFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|
const nsDisplayListSet& aLists) {
|
|
/////////////
|
|
// paint the numerator and denominator
|
|
nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
|
|
|
|
/////////////
|
|
// paint the fraction line
|
|
DisplayBar(aBuilder, this, mLineRect, aLists);
|
|
}
|
|
|
|
nsresult nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
int32_t aModType) {
|
|
if (nsGkAtoms::linethickness_ == aAttribute) {
|
|
InvalidateFrame();
|
|
}
|
|
return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
|
aModType);
|
|
}
|
|
|
|
/* virtual */
|
|
nsresult nsMathMLmfracFrame::MeasureForWidth(DrawTarget* aDrawTarget,
|
|
ReflowOutput& aDesiredSize) {
|
|
return PlaceInternal(aDrawTarget, false, aDesiredSize, true);
|
|
}
|
|
|
|
nscoord nsMathMLmfracFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize) {
|
|
nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize);
|
|
if (!gap) return 0;
|
|
|
|
mLineRect.MoveBy(gap, 0);
|
|
return gap;
|
|
}
|
|
|
|
/* virtual */
|
|
nsresult nsMathMLmfracFrame::Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
|
|
ReflowOutput& aDesiredSize) {
|
|
return PlaceInternal(aDrawTarget, aPlaceOrigin, aDesiredSize, false);
|
|
}
|
|
|
|
nsresult nsMathMLmfracFrame::PlaceInternal(DrawTarget* aDrawTarget,
|
|
bool aPlaceOrigin,
|
|
ReflowOutput& aDesiredSize,
|
|
bool aWidthOnly) {
|
|
////////////////////////////////////
|
|
// Get the children's desired sizes
|
|
nsBoundingMetrics bmNum, bmDen;
|
|
ReflowOutput sizeNum(aDesiredSize.GetWritingMode());
|
|
ReflowOutput sizeDen(aDesiredSize.GetWritingMode());
|
|
nsIFrame* frameDen = nullptr;
|
|
nsIFrame* frameNum = mFrames.FirstChild();
|
|
if (frameNum) frameDen = frameNum->GetNextSibling();
|
|
if (!frameNum || !frameDen || frameDen->GetNextSibling()) {
|
|
// report an error, encourage people to get their markups in order
|
|
if (aPlaceOrigin) {
|
|
ReportChildCountError();
|
|
}
|
|
return PlaceAsMrow(aDrawTarget, aPlaceOrigin, aDesiredSize);
|
|
}
|
|
GetReflowAndBoundingMetricsFor(frameNum, sizeNum, bmNum);
|
|
GetReflowAndBoundingMetricsFor(frameDen, sizeDen, bmDen);
|
|
|
|
nsPresContext* presContext = PresContext();
|
|
nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
|
|
|
|
float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
|
|
RefPtr<nsFontMetrics> fm =
|
|
nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
|
|
|
|
nscoord defaultRuleThickness, axisHeight;
|
|
nscoord oneDevPixel = fm->AppUnitsPerDevPixel();
|
|
RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
|
|
if (mathFont) {
|
|
defaultRuleThickness = mathFont->MathTable()->Constant(
|
|
gfxMathTable::FractionRuleThickness, oneDevPixel);
|
|
} else {
|
|
GetRuleThickness(aDrawTarget, fm, defaultRuleThickness);
|
|
}
|
|
GetAxisHeight(aDrawTarget, fm, axisHeight);
|
|
|
|
bool outermostEmbellished = false;
|
|
if (mEmbellishData.coreFrame) {
|
|
nsEmbellishData parentData;
|
|
GetEmbellishDataFrom(GetParent(), parentData);
|
|
outermostEmbellished = parentData.coreFrame != mEmbellishData.coreFrame;
|
|
}
|
|
|
|
// see if the linethickness attribute is there
|
|
nsAutoString value;
|
|
mContent->AsElement()->GetAttr(nsGkAtoms::linethickness_, value);
|
|
mLineThickness =
|
|
CalcLineThickness(presContext, mComputedStyle, value, onePixel,
|
|
defaultRuleThickness, fontSizeInflation);
|
|
|
|
bool displayStyle = StyleFont()->mMathStyle == StyleMathStyle::Normal;
|
|
|
|
mLineRect.height = mLineThickness;
|
|
|
|
// by default, leave at least one-pixel padding at either end, and add
|
|
// lspace & rspace that may come from <mo> if we are an outermost
|
|
// embellished container (we fetch values from the core since they may use
|
|
// units that depend on style data, and style changes could have occurred
|
|
// in the core since our last visit there)
|
|
nscoord leftSpace = onePixel;
|
|
nscoord rightSpace = onePixel;
|
|
if (outermostEmbellished) {
|
|
const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
|
|
nsEmbellishData coreData;
|
|
GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
|
|
leftSpace += isRTL ? coreData.trailingSpace : coreData.leadingSpace;
|
|
rightSpace += isRTL ? coreData.leadingSpace : coreData.trailingSpace;
|
|
}
|
|
|
|
nscoord actualRuleThickness = mLineThickness;
|
|
|
|
//////////////////
|
|
// Get shifts
|
|
nscoord numShift = 0;
|
|
nscoord denShift = 0;
|
|
|
|
// Rule 15b, App. G, TeXbook
|
|
nscoord numShift1, numShift2, numShift3;
|
|
nscoord denShift1, denShift2;
|
|
|
|
GetNumeratorShifts(fm, numShift1, numShift2, numShift3);
|
|
GetDenominatorShifts(fm, denShift1, denShift2);
|
|
|
|
if (0 == actualRuleThickness) {
|
|
numShift = displayStyle ? numShift1 : numShift3;
|
|
denShift = displayStyle ? denShift1 : denShift2;
|
|
if (mathFont) {
|
|
numShift = mathFont->MathTable()->Constant(
|
|
displayStyle ? gfxMathTable::StackTopDisplayStyleShiftUp
|
|
: gfxMathTable::StackTopShiftUp,
|
|
oneDevPixel);
|
|
denShift = mathFont->MathTable()->Constant(
|
|
displayStyle ? gfxMathTable::StackBottomDisplayStyleShiftDown
|
|
: gfxMathTable::StackBottomShiftDown,
|
|
oneDevPixel);
|
|
}
|
|
} else {
|
|
numShift = displayStyle ? numShift1 : numShift2;
|
|
denShift = displayStyle ? denShift1 : denShift2;
|
|
if (mathFont) {
|
|
numShift = mathFont->MathTable()->Constant(
|
|
displayStyle ? gfxMathTable::FractionNumeratorDisplayStyleShiftUp
|
|
: gfxMathTable::FractionNumeratorShiftUp,
|
|
oneDevPixel);
|
|
denShift = mathFont->MathTable()->Constant(
|
|
displayStyle ? gfxMathTable::FractionDenominatorDisplayStyleShiftDown
|
|
: gfxMathTable::FractionDenominatorShiftDown,
|
|
oneDevPixel);
|
|
}
|
|
}
|
|
|
|
if (0 == actualRuleThickness) {
|
|
// Rule 15c, App. G, TeXbook
|
|
|
|
// min clearance between numerator and denominator
|
|
nscoord minClearance =
|
|
displayStyle ? 7 * defaultRuleThickness : 3 * defaultRuleThickness;
|
|
if (mathFont) {
|
|
minClearance = mathFont->MathTable()->Constant(
|
|
displayStyle ? gfxMathTable::StackDisplayStyleGapMin
|
|
: gfxMathTable::StackGapMin,
|
|
oneDevPixel);
|
|
}
|
|
|
|
nscoord actualClearance =
|
|
(numShift - bmNum.descent) - (bmDen.ascent - denShift);
|
|
// actualClearance should be >= minClearance
|
|
if (actualClearance < minClearance) {
|
|
nscoord halfGap = (minClearance - actualClearance) / 2;
|
|
numShift += halfGap;
|
|
denShift += halfGap;
|
|
}
|
|
} else {
|
|
// Rule 15d, App. G, TeXbook
|
|
|
|
// min clearance between numerator or denominator and middle of bar
|
|
|
|
// TeX has a different interpretation of the thickness.
|
|
// Try $a \above10pt b$ to see. Here is what TeX does:
|
|
// minClearance = displayStyle ?
|
|
// 3 * actualRuleThickness : actualRuleThickness;
|
|
|
|
// we slightly depart from TeX here. We use the defaultRuleThickness
|
|
// instead of the value coming from the linethickness attribute, i.e., we
|
|
// recover what TeX does if the user hasn't set linethickness. But when
|
|
// the linethickness is set, we avoid the wide gap problem.
|
|
nscoord minClearanceNum = displayStyle ? 3 * defaultRuleThickness
|
|
: defaultRuleThickness + onePixel;
|
|
nscoord minClearanceDen = minClearanceNum;
|
|
if (mathFont) {
|
|
minClearanceNum = mathFont->MathTable()->Constant(
|
|
displayStyle ? gfxMathTable::FractionNumDisplayStyleGapMin
|
|
: gfxMathTable::FractionNumeratorGapMin,
|
|
oneDevPixel);
|
|
minClearanceDen = mathFont->MathTable()->Constant(
|
|
displayStyle ? gfxMathTable::FractionDenomDisplayStyleGapMin
|
|
: gfxMathTable::FractionDenominatorGapMin,
|
|
oneDevPixel);
|
|
}
|
|
|
|
// adjust numShift to maintain minClearanceNum if needed
|
|
nscoord actualClearanceNum =
|
|
(numShift - bmNum.descent) - (axisHeight + actualRuleThickness / 2);
|
|
if (actualClearanceNum < minClearanceNum) {
|
|
numShift += (minClearanceNum - actualClearanceNum);
|
|
}
|
|
// adjust denShift to maintain minClearanceDen if needed
|
|
nscoord actualClearanceDen =
|
|
(axisHeight - actualRuleThickness / 2) - (bmDen.ascent - denShift);
|
|
if (actualClearanceDen < minClearanceDen) {
|
|
denShift += (minClearanceDen - actualClearanceDen);
|
|
}
|
|
}
|
|
|
|
//////////////////
|
|
// Place Children
|
|
|
|
// XXX Need revisiting the width. TeX uses the exact width
|
|
// e.g. in $$\huge\frac{\displaystyle\int}{i}$$
|
|
nscoord width = std::max(bmNum.width, bmDen.width);
|
|
nscoord dxNum = leftSpace + (width - sizeNum.Width()) / 2;
|
|
nscoord dxDen = leftSpace + (width - sizeDen.Width()) / 2;
|
|
width += leftSpace + rightSpace;
|
|
|
|
mBoundingMetrics.rightBearing =
|
|
std::max(dxNum + bmNum.rightBearing, dxDen + bmDen.rightBearing);
|
|
if (mBoundingMetrics.rightBearing < width - rightSpace)
|
|
mBoundingMetrics.rightBearing = width - rightSpace;
|
|
mBoundingMetrics.leftBearing =
|
|
std::min(dxNum + bmNum.leftBearing, dxDen + bmDen.leftBearing);
|
|
if (mBoundingMetrics.leftBearing > leftSpace)
|
|
mBoundingMetrics.leftBearing = leftSpace;
|
|
mBoundingMetrics.ascent = bmNum.ascent + numShift;
|
|
mBoundingMetrics.descent = bmDen.descent + denShift;
|
|
mBoundingMetrics.width = width;
|
|
|
|
aDesiredSize.SetBlockStartAscent(sizeNum.BlockStartAscent() + numShift);
|
|
aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + sizeDen.Height() -
|
|
sizeDen.BlockStartAscent() + denShift;
|
|
aDesiredSize.Width() = mBoundingMetrics.width;
|
|
aDesiredSize.mBoundingMetrics = mBoundingMetrics;
|
|
|
|
mReference.x = 0;
|
|
mReference.y = aDesiredSize.BlockStartAscent();
|
|
|
|
if (aPlaceOrigin) {
|
|
nscoord dy;
|
|
// place numerator
|
|
dy = 0;
|
|
FinishReflowChild(frameNum, presContext, sizeNum, nullptr, dxNum, dy,
|
|
ReflowChildFlags::Default);
|
|
// place denominator
|
|
dy = aDesiredSize.Height() - sizeDen.Height();
|
|
FinishReflowChild(frameDen, presContext, sizeDen, nullptr, dxDen, dy,
|
|
ReflowChildFlags::Default);
|
|
// place the fraction bar - dy is top of bar
|
|
dy = aDesiredSize.BlockStartAscent() -
|
|
(axisHeight + actualRuleThickness / 2);
|
|
mLineRect.SetRect(leftSpace, dy, width - (leftSpace + rightSpace),
|
|
actualRuleThickness);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|