Files
tubestation/layout/mathml/nsMathMLmfracFrame.cpp
Iulian Moraru 096c9e730e Backed out 7 changesets (bug 1908069) for causing wr failures on mo-lspace-rspace-4.html. CLOSED TREE
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)
2024-08-01 19:45:36 +03:00

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;
}