RenderMathMLUnderOver.cpp [plain text]
#include "config.h"
#include "RenderMathMLUnderOver.h"
#if ENABLE(MATHML)
#include "MathMLElement.h"
#include "MathMLOperatorDictionary.h"
#include "MathMLUnderOverElement.h"
#include "RenderIterator.h"
#include "RenderMathMLOperator.h"
namespace WebCore {
RenderMathMLUnderOver::RenderMathMLUnderOver(MathMLUnderOverElement& element, RenderStyle&& style)
: RenderMathMLScripts(element, WTFMove(style))
{
}
MathMLUnderOverElement& RenderMathMLUnderOver::element() const
{
return static_cast<MathMLUnderOverElement&>(nodeForNonAnonymous());
}
void RenderMathMLUnderOver::computeOperatorsHorizontalStretch()
{
LayoutUnit stretchWidth = 0;
Vector<RenderMathMLOperator*, 2> renderOperators;
for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) {
if (child->needsLayout()) {
if (is<RenderMathMLBlock>(child)) {
if (auto renderOperator = downcast<RenderMathMLBlock>(*child).unembellishedOperator()) {
if (renderOperator->isStretchy() && !renderOperator->isVertical()) {
renderOperator->resetStretchSize();
renderOperators.append(renderOperator);
}
}
}
child->layout();
}
stretchWidth = std::max(stretchWidth, child->logicalWidth());
}
for (auto& renderOperator : renderOperators)
renderOperator->stretchTo(stretchWidth);
}
bool RenderMathMLUnderOver::isValid() const
{
auto* child = firstChildBox();
if (!child)
return false;
child = child->nextSiblingBox();
if (!child)
return false;
child = child->nextSiblingBox();
switch (m_scriptType) {
case Over:
case Under:
return !child;
case UnderOver:
return child && !child->nextSiblingBox();
default:
ASSERT_NOT_REACHED();
return false;
}
}
bool RenderMathMLUnderOver::shouldMoveLimits()
{
if (auto* renderOperator = unembellishedOperator())
return renderOperator->shouldMoveLimits();
return false;
}
RenderBox& RenderMathMLUnderOver::base() const
{
ASSERT(isValid());
return *firstChildBox();
}
RenderBox& RenderMathMLUnderOver::under() const
{
ASSERT(isValid());
ASSERT(m_scriptType == Under || m_scriptType == UnderOver);
return *firstChildBox()->nextSiblingBox();
}
RenderBox& RenderMathMLUnderOver::over() const
{
ASSERT(isValid());
ASSERT(m_scriptType == Over || m_scriptType == UnderOver);
auto* secondChild = firstChildBox()->nextSiblingBox();
return m_scriptType == Over ? *secondChild : *secondChild->nextSiblingBox();
}
void RenderMathMLUnderOver::computePreferredLogicalWidths()
{
ASSERT(preferredLogicalWidthsDirty());
if (!isValid()) {
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0;
setPreferredLogicalWidthsDirty(false);
return;
}
if (shouldMoveLimits()) {
RenderMathMLScripts::computePreferredLogicalWidths();
return;
}
LayoutUnit preferredWidth = base().maxPreferredLogicalWidth();
if (m_scriptType == Under || m_scriptType == UnderOver)
preferredWidth = std::max(preferredWidth, under().maxPreferredLogicalWidth());
if (m_scriptType == Over || m_scriptType == UnderOver)
preferredWidth = std::max(preferredWidth, over().maxPreferredLogicalWidth());
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth;
setPreferredLogicalWidthsDirty(false);
}
LayoutUnit RenderMathMLUnderOver::horizontalOffset(const RenderBox& child) const
{
return (logicalWidth() - child.logicalWidth()) / 2;
}
bool RenderMathMLUnderOver::hasAccent(bool accentUnder) const
{
ASSERT(m_scriptType == UnderOver || (accentUnder && m_scriptType == Under) || (!accentUnder && m_scriptType == Over));
const MathMLElement::BooleanValue& attributeValue = accentUnder ? element().accentUnder() : element().accent();
if (attributeValue == MathMLElement::BooleanValue::True)
return true;
if (attributeValue == MathMLElement::BooleanValue::False)
return false;
RenderBox& script = accentUnder ? under() : over();
if (!is<RenderMathMLBlock>(script))
return false;
auto* scriptOperator = downcast<RenderMathMLBlock>(script).unembellishedOperator();
return scriptOperator && scriptOperator->hasOperatorFlag(MathMLOperatorDictionary::Accent);
}
RenderMathMLUnderOver::VerticalParameters RenderMathMLUnderOver::verticalParameters() const
{
VerticalParameters parameters;
parameters.underGapMin = 0;
parameters.overGapMin = 0;
parameters.underShiftMin = 0;
parameters.overShiftMin = 0;
parameters.underExtraDescender = 0;
parameters.overExtraAscender = 0;
parameters.accentBaseHeight = 0;
const auto& primaryFont = style().fontCascade().primaryFont();
auto* mathData = primaryFont.mathData();
if (!mathData) {
LayoutUnit defaultLineThickness = ruleThicknessFallback();
parameters.underGapMin = 3 * defaultLineThickness;
parameters.overGapMin = 3 * defaultLineThickness;
parameters.underExtraDescender = defaultLineThickness;
parameters.overExtraAscender = defaultLineThickness;
parameters.accentBaseHeight = style().fontMetrics().xHeight();
parameters.useUnderOverBarFallBack = true;
return parameters;
}
if (is<RenderMathMLBlock>(base())) {
if (auto* baseOperator = downcast<RenderMathMLBlock>(base()).unembellishedOperator()) {
if (baseOperator->hasOperatorFlag(MathMLOperatorDictionary::LargeOp)) {
parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitGapMin);
parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitGapMin);
parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitBaselineDropMin);
parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitBaselineRiseMin);
parameters.useUnderOverBarFallBack = false;
return parameters;
}
if (baseOperator->isStretchy() && !baseOperator->isVertical()) {
parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapBelowMin);
parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapAboveMin);
parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackBottomShiftDown);
parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackTopShiftUp);
parameters.useUnderOverBarFallBack = false;
return parameters;
}
}
}
parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarVerticalGap);
parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarVerticalGap);
parameters.underExtraDescender = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarExtraDescender);
parameters.overExtraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarExtraAscender);
parameters.accentBaseHeight = mathData->getMathConstant(primaryFont, OpenTypeMathData::AccentBaseHeight);
parameters.useUnderOverBarFallBack = true;
return parameters;
}
void RenderMathMLUnderOver::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight)
{
ASSERT(needsLayout());
if (!relayoutChildren && simplifiedLayout())
return;
if (!isValid()) {
layoutInvalidMarkup();
return;
}
if (shouldMoveLimits()) {
RenderMathMLScripts::layoutBlock(relayoutChildren, pageLogicalHeight);
return;
}
recomputeLogicalWidth();
computeOperatorsHorizontalStretch();
base().layoutIfNeeded();
if (m_scriptType == Under || m_scriptType == UnderOver)
under().layoutIfNeeded();
if (m_scriptType == Over || m_scriptType == UnderOver)
over().layoutIfNeeded();
LayoutUnit logicalWidth = base().logicalWidth();
if (m_scriptType == Under || m_scriptType == UnderOver)
logicalWidth = std::max(logicalWidth, under().logicalWidth());
if (m_scriptType == Over || m_scriptType == UnderOver)
logicalWidth = std::max(logicalWidth, over().logicalWidth());
setLogicalWidth(logicalWidth);
VerticalParameters parameters = verticalParameters();
LayoutUnit verticalOffset = 0;
if (m_scriptType == Over || m_scriptType == UnderOver) {
verticalOffset += parameters.overExtraAscender;
over().setLocation(LayoutPoint(horizontalOffset(over()), verticalOffset));
if (parameters.useUnderOverBarFallBack) {
verticalOffset += over().logicalHeight();
if (hasAccent()) {
LayoutUnit baseAscent = ascentForChild(base());
if (baseAscent < parameters.accentBaseHeight)
verticalOffset += parameters.accentBaseHeight - baseAscent;
} else
verticalOffset += parameters.overGapMin;
} else {
LayoutUnit overAscent = ascentForChild(over());
verticalOffset += std::max(over().logicalHeight() + parameters.overGapMin, overAscent + parameters.overShiftMin);
}
}
base().setLocation(LayoutPoint(horizontalOffset(base()), verticalOffset));
verticalOffset += base().logicalHeight();
if (m_scriptType == Under || m_scriptType == UnderOver) {
if (parameters.useUnderOverBarFallBack) {
if (!hasAccentUnder())
verticalOffset += parameters.underGapMin;
} else {
LayoutUnit underAscent = ascentForChild(under());
verticalOffset += std::max(parameters.underGapMin, parameters.underShiftMin - underAscent);
}
under().setLocation(LayoutPoint(horizontalOffset(under()), verticalOffset));
verticalOffset += under().logicalHeight();
verticalOffset += parameters.underExtraDescender;
}
setLogicalHeight(verticalOffset);
clearNeedsLayout();
}
}
#endif // ENABLE(MATHML)