blob: 2607e9b8fdd392f69402c557896b2028d98a73e4 [file] [log] [blame]
/*
* Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
* Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved.
* Copyright (C) 2016 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "RenderMathMLFraction.h"
#if ENABLE(MATHML)
#include "GraphicsContext.h"
#include "MathMLFractionElement.h"
#include "PaintInfo.h"
#include <cmath>
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLFraction);
RenderMathMLFraction::RenderMathMLFraction(MathMLFractionElement& element, RenderStyle&& style)
: RenderMathMLBlock(element, WTFMove(style))
{
}
bool RenderMathMLFraction::isValid() const
{
// Verify whether the list of children is valid:
// <mfrac> numerator denominator </mfrac>
auto* child = firstChildBox();
if (!child)
return false;
child = child->nextSiblingBox();
return child && !child->nextSiblingBox();
}
RenderBox& RenderMathMLFraction::numerator() const
{
ASSERT(isValid());
return *firstChildBox();
}
RenderBox& RenderMathMLFraction::denominator() const
{
ASSERT(isValid());
return *firstChildBox()->nextSiblingBox();
}
LayoutUnit RenderMathMLFraction::defaultLineThickness() const
{
const auto& primaryFont = style().fontCascade().primaryFont();
if (const auto* mathData = primaryFont.mathData())
return mathData->getMathConstant(primaryFont, OpenTypeMathData::FractionRuleThickness);
return ruleThicknessFallback();
}
LayoutUnit RenderMathMLFraction::lineThickness() const
{
return std::max<LayoutUnit>(toUserUnits(element().lineThickness(), style(), defaultLineThickness()), 0);
}
float RenderMathMLFraction::relativeLineThickness() const
{
if (LayoutUnit defaultThickness = defaultLineThickness())
return lineThickness() / defaultThickness;
return 0;
}
RenderMathMLFraction::FractionParameters RenderMathMLFraction::fractionParameters() const
{
ASSERT(lineThickness());
FractionParameters parameters;
// We try and read constants to draw the fraction from the OpenType MATH and use fallback values otherwise.
const auto& primaryFont = style().fontCascade().primaryFont();
const auto* mathData = style().fontCascade().primaryFont().mathData();
bool display = mathMLStyle().displayStyle();
if (mathData) {
parameters.numeratorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumDisplayStyleGapMin : OpenTypeMathData::FractionNumeratorGapMin);
parameters.denominatorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenomDisplayStyleGapMin : OpenTypeMathData::FractionDenominatorGapMin);
parameters.numeratorMinShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumeratorDisplayStyleShiftUp : OpenTypeMathData::FractionNumeratorShiftUp);
parameters.denominatorMinShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenominatorDisplayStyleShiftDown : OpenTypeMathData::FractionDenominatorShiftDown);
} else {
// The MATH table specification suggests default rule thickness or (in displaystyle) 3 times default rule thickness for the gaps.
parameters.numeratorGapMin = display ? 3 * ruleThicknessFallback() : ruleThicknessFallback();
parameters.denominatorGapMin = parameters.numeratorGapMin;
// The MATH table specification does not suggest any values for shifts, so we leave them at zero.
parameters.numeratorMinShiftUp = 0;
parameters.denominatorMinShiftDown = 0;
}
return parameters;
}
RenderMathMLFraction::StackParameters RenderMathMLFraction::stackParameters() const
{
ASSERT(!lineThickness());
ASSERT(isValid());
StackParameters parameters;
// We try and read constants to draw the stack from the OpenType MATH and use fallback values otherwise.
const auto& primaryFont = style().fontCascade().primaryFont();
const auto* mathData = style().fontCascade().primaryFont().mathData();
bool display = mathMLStyle().displayStyle();
if (mathData) {
parameters.gapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackDisplayStyleGapMin : OpenTypeMathData::StackGapMin);
parameters.topShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackTopDisplayStyleShiftUp : OpenTypeMathData::StackTopShiftUp);
parameters.bottomShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackBottomDisplayStyleShiftDown : OpenTypeMathData::StackBottomShiftDown);
} else {
// We use the values suggested in the MATH table specification.
parameters.gapMin = display ? 7 * ruleThicknessFallback() : 3 * ruleThicknessFallback();
// The MATH table specification does not suggest any values for shifts, so we leave them at zero.
parameters.topShiftUp = 0;
parameters.bottomShiftDown = 0;
}
LayoutUnit numeratorAscent = ascentForChild(numerator());
LayoutUnit numeratorDescent = numerator().logicalHeight() - numeratorAscent;
LayoutUnit denominatorAscent = ascentForChild(denominator());
LayoutUnit gap = parameters.topShiftUp - numeratorDescent + parameters.bottomShiftDown - denominatorAscent;
if (gap < parameters.gapMin) {
// If the gap is not large enough, we increase the shifts by the same value.
LayoutUnit delta = (parameters.gapMin - gap) / 2;
parameters.topShiftUp += delta;
parameters.bottomShiftDown += delta;
}
return parameters;
}
RenderMathMLOperator* RenderMathMLFraction::unembellishedOperator() const
{
if (!isValid() || !is<RenderMathMLBlock>(numerator()))
return nullptr;
return downcast<RenderMathMLBlock>(numerator()).unembellishedOperator();
}
void RenderMathMLFraction::computePreferredLogicalWidths()
{
ASSERT(preferredLogicalWidthsDirty());
m_minPreferredLogicalWidth = 0;
m_maxPreferredLogicalWidth = 0;
if (isValid()) {
LayoutUnit numeratorWidth = numerator().maxPreferredLogicalWidth();
LayoutUnit denominatorWidth = denominator().maxPreferredLogicalWidth();
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = std::max(numeratorWidth, denominatorWidth);
}
setPreferredLogicalWidthsDirty(false);
}
LayoutUnit RenderMathMLFraction::horizontalOffset(RenderBox& child, MathMLFractionElement::FractionAlignment align) const
{
switch (align) {
case MathMLFractionElement::FractionAlignmentRight:
return LayoutUnit(logicalWidth() - child.logicalWidth());
case MathMLFractionElement::FractionAlignmentCenter:
return LayoutUnit((logicalWidth() - child.logicalWidth()) / 2);
case MathMLFractionElement::FractionAlignmentLeft:
return LayoutUnit(0);
}
ASSERT_NOT_REACHED();
return LayoutUnit(0);
}
LayoutUnit RenderMathMLFraction::ascentOverHorizontalAxis() const
{
ASSERT(isValid());
LayoutUnit numeratorAscent = ascentForChild(numerator());
if (LayoutUnit thickness = lineThickness()) {
// For normal fraction layout, the axis is the middle of the fraction bar.
FractionParameters parameters = fractionParameters();
return std::max(numerator().logicalHeight() + parameters.numeratorGapMin + thickness / 2, numeratorAscent + parameters.numeratorMinShiftUp);
}
// For stack layout, the axis is the middle of the gap between numerator and denonimator.
StackParameters parameters = stackParameters();
return numeratorAscent + parameters.topShiftUp;
}
void RenderMathMLFraction::layoutBlock(bool relayoutChildren, LayoutUnit)
{
ASSERT(needsLayout());
if (!relayoutChildren && simplifiedLayout())
return;
if (!isValid()) {
layoutInvalidMarkup(relayoutChildren);
return;
}
numerator().layoutIfNeeded();
denominator().layoutIfNeeded();
setLogicalWidth(std::max(numerator().logicalWidth(), denominator().logicalWidth()));
LayoutUnit verticalOffset = 0; // This is the top of the renderer.
LayoutPoint numeratorLocation(horizontalOffset(numerator(), element().numeratorAlignment()), verticalOffset);
numerator().setLocation(numeratorLocation);
LayoutUnit denominatorAscent = ascentForChild(denominator());
LayoutUnit denominatorDescent = denominator().logicalHeight() - denominatorAscent;
LayoutUnit ascent = ascentOverHorizontalAxis();
verticalOffset += ascent;
if (LayoutUnit thickness = lineThickness()) {
FractionParameters parameters = fractionParameters();
verticalOffset += std::max(thickness / 2 + parameters.denominatorGapMin, parameters.denominatorMinShiftDown - denominatorAscent);
} else {
StackParameters parameters = stackParameters();
verticalOffset += parameters.bottomShiftDown - denominatorAscent;
}
LayoutPoint denominatorLocation(horizontalOffset(denominator(), element().denominatorAlignment()), verticalOffset);
denominator().setLocation(denominatorLocation);
verticalOffset = std::max(verticalOffset + denominator().logicalHeight(), ascent + mathAxisHeight() + denominatorDescent); // This is the bottom of our renderer.
setLogicalHeight(verticalOffset);
layoutPositionedObjects(relayoutChildren);
clearNeedsLayout();
}
void RenderMathMLFraction::paint(PaintInfo& info, const LayoutPoint& paintOffset)
{
RenderMathMLBlock::paint(info, paintOffset);
LayoutUnit thickness = lineThickness();
if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE || !isValid() || !thickness)
return;
IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + LayoutPoint(0, ascentOverHorizontalAxis()));
GraphicsContextStateSaver stateSaver(info.context());
info.context().setStrokeThickness(thickness);
info.context().setStrokeStyle(SolidStroke);
info.context().setStrokeColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor));
info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + logicalWidth(), adjustedPaintOffset.y())));
}
std::optional<int> RenderMathMLFraction::firstLineBaseline() const
{
if (isValid())
return std::optional<int>(std::lround(static_cast<float>(ascentOverHorizontalAxis() + mathAxisHeight())));
return RenderMathMLBlock::firstLineBaseline();
}
}
#endif // ENABLE(MATHML)