Introduce RenderTreeBuilder
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLFraction.cpp
1 /*
2  * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
3  * Copyright (C) 2010 Fran├žois Sausset (sausset@gmail.com). All rights reserved.
4  * Copyright (C) 2016 Igalia S.L.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "RenderMathMLFraction.h"
30
31 #if ENABLE(MATHML)
32
33 #include "GraphicsContext.h"
34 #include "MathMLFractionElement.h"
35 #include "PaintInfo.h"
36 #include <cmath>
37 #include <wtf/IsoMallocInlines.h>
38
39 namespace WebCore {
40
41 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLFraction);
42
43 RenderMathMLFraction::RenderMathMLFraction(MathMLFractionElement& element, RenderStyle&& style)
44     : RenderMathMLBlock(element, WTFMove(style))
45 {
46 }
47
48 bool RenderMathMLFraction::isValid() const
49 {
50     // Verify whether the list of children is valid:
51     // <mfrac> numerator denominator </mfrac>
52     auto* child = firstChildBox();
53     if (!child)
54         return false;
55     child = child->nextSiblingBox();
56     return child && !child->nextSiblingBox();
57 }
58
59 RenderBox& RenderMathMLFraction::numerator() const
60 {
61     ASSERT(isValid());
62     return *firstChildBox();
63 }
64
65 RenderBox& RenderMathMLFraction::denominator() const
66 {
67     ASSERT(isValid());
68     return *firstChildBox()->nextSiblingBox();
69 }
70
71 void RenderMathMLFraction::updateLineThickness()
72 {
73     // We first determine the default line thickness.
74     const auto& primaryFont = style().fontCascade().primaryFont();
75     const auto* mathData = style().fontCascade().primaryFont().mathData();
76     if (mathData)
77         m_defaultLineThickness = mathData->getMathConstant(primaryFont, OpenTypeMathData::FractionRuleThickness);
78     else
79         m_defaultLineThickness = ruleThicknessFallback();
80
81     // Next we resolve the thickness using m_defaultLineThickness as the default value.
82     m_lineThickness = toUserUnits(element().lineThickness(), style(), m_defaultLineThickness);
83     if (m_lineThickness < 0)
84         m_lineThickness = 0;
85 }
86
87 RenderMathMLFraction::FractionParameters RenderMathMLFraction::fractionParameters()
88 {
89     ASSERT(!isStack());
90     FractionParameters parameters;
91
92     // We try and read constants to draw the fraction from the OpenType MATH and use fallback values otherwise.
93     const auto& primaryFont = style().fontCascade().primaryFont();
94     const auto* mathData = style().fontCascade().primaryFont().mathData();
95     bool display = mathMLStyle().displayStyle();
96     if (mathData) {
97         parameters.numeratorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumDisplayStyleGapMin : OpenTypeMathData::FractionNumeratorGapMin);
98         parameters.denominatorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenomDisplayStyleGapMin : OpenTypeMathData::FractionDenominatorGapMin);
99         parameters.numeratorMinShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumeratorDisplayStyleShiftUp : OpenTypeMathData::FractionNumeratorShiftUp);
100         parameters.denominatorMinShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenominatorDisplayStyleShiftDown : OpenTypeMathData::FractionDenominatorShiftDown);
101     } else {
102         // The MATH table specification suggests default rule thickness or (in displaystyle) 3 times default rule thickness for the gaps.
103         parameters.numeratorGapMin = display ? 3 * ruleThicknessFallback() : ruleThicknessFallback();
104         parameters.denominatorGapMin = parameters.numeratorGapMin;
105
106         // The MATH table specification does not suggest any values for shifts, so we leave them at zero.
107         parameters.numeratorMinShiftUp = 0;
108         parameters.denominatorMinShiftDown = 0;
109     }
110
111     return parameters;
112 }
113
114 RenderMathMLFraction::StackParameters RenderMathMLFraction::stackParameters()
115 {
116     ASSERT(isStack());
117     StackParameters parameters;
118     
119     // We try and read constants to draw the stack from the OpenType MATH and use fallback values otherwise.
120     const auto& primaryFont = style().fontCascade().primaryFont();
121     const auto* mathData = style().fontCascade().primaryFont().mathData();
122     bool display = mathMLStyle().displayStyle();
123     if (mathData) {
124         parameters.gapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackDisplayStyleGapMin : OpenTypeMathData::StackGapMin);
125         parameters.topShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackTopDisplayStyleShiftUp : OpenTypeMathData::StackTopShiftUp);
126         parameters.bottomShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackBottomDisplayStyleShiftDown : OpenTypeMathData::StackBottomShiftDown);
127     } else {
128         // We use the values suggested in the MATH table specification.
129         parameters.gapMin = display ? 7 * ruleThicknessFallback() : 3 * ruleThicknessFallback();
130
131         // The MATH table specification does not suggest any values for shifts, so we leave them at zero.
132         parameters.topShiftUp = 0;
133         parameters.bottomShiftDown = 0;
134     }
135
136     return parameters;
137 }
138
139 RenderMathMLOperator* RenderMathMLFraction::unembellishedOperator()
140 {
141     if (!isValid() || !is<RenderMathMLBlock>(numerator()))
142         return nullptr;
143
144     return downcast<RenderMathMLBlock>(numerator()).unembellishedOperator();
145 }
146
147 void RenderMathMLFraction::computePreferredLogicalWidths()
148 {
149     ASSERT(preferredLogicalWidthsDirty());
150
151     m_minPreferredLogicalWidth = 0;
152     m_maxPreferredLogicalWidth = 0;
153
154     if (isValid()) {
155         LayoutUnit numeratorWidth = numerator().maxPreferredLogicalWidth();
156         LayoutUnit denominatorWidth = denominator().maxPreferredLogicalWidth();
157         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = std::max(numeratorWidth, denominatorWidth);
158     }
159
160     setPreferredLogicalWidthsDirty(false);
161 }
162
163 LayoutUnit RenderMathMLFraction::horizontalOffset(RenderBox& child, MathMLFractionElement::FractionAlignment align)
164 {
165     switch (align) {
166     case MathMLFractionElement::FractionAlignmentRight:
167         return LayoutUnit(logicalWidth() - child.logicalWidth());
168     case MathMLFractionElement::FractionAlignmentCenter:
169         return LayoutUnit((logicalWidth() - child.logicalWidth()) / 2);
170     case MathMLFractionElement::FractionAlignmentLeft:
171         return LayoutUnit(0);
172     }
173
174     ASSERT_NOT_REACHED();
175     return LayoutUnit(0);
176 }
177
178 void RenderMathMLFraction::layoutBlock(bool relayoutChildren, LayoutUnit)
179 {
180     ASSERT(needsLayout());
181
182     if (!relayoutChildren && simplifiedLayout())
183         return;
184
185     if (!isValid()) {
186         layoutInvalidMarkup(relayoutChildren);
187         return;
188     }
189
190     numerator().layoutIfNeeded();
191     denominator().layoutIfNeeded();
192
193     setLogicalWidth(std::max(numerator().logicalWidth(), denominator().logicalWidth()));
194
195     updateLineThickness();
196     LayoutUnit verticalOffset = 0; // This is the top of the renderer.
197     LayoutPoint numeratorLocation(horizontalOffset(numerator(), element().numeratorAlignment()), verticalOffset);
198     numerator().setLocation(numeratorLocation);
199
200     LayoutUnit numeratorAscent = ascentForChild(numerator());
201     LayoutUnit numeratorDescent = numerator().logicalHeight() - numeratorAscent;
202     LayoutUnit denominatorAscent = ascentForChild(denominator());
203     LayoutUnit denominatorDescent = denominator().logicalHeight() - denominatorAscent;
204     if (isStack()) {
205         StackParameters parameters = stackParameters();
206         LayoutUnit gap = parameters.topShiftUp - numeratorDescent + parameters.bottomShiftDown - denominatorAscent;
207         if (gap < parameters.gapMin) {
208             // If the gap is not large enough, we increase the shifts by the same value.
209             LayoutUnit delta = (parameters.gapMin - gap) / 2;
210             parameters.topShiftUp += delta;
211             parameters.bottomShiftDown += delta;
212         }
213         verticalOffset += numeratorAscent + parameters.topShiftUp; // This is the middle of the stack gap.
214         m_ascent = verticalOffset + mathAxisHeight();
215         verticalOffset += parameters.bottomShiftDown - denominatorAscent;
216     } else {
217         FractionParameters parameters = fractionParameters();
218         verticalOffset += std::max(numerator().logicalHeight() + parameters.numeratorGapMin + m_lineThickness / 2, numeratorAscent + parameters.numeratorMinShiftUp); // This is the middle of the fraction bar.
219         m_ascent = verticalOffset + mathAxisHeight();
220         verticalOffset += std::max(m_lineThickness / 2 + parameters.denominatorGapMin, parameters.denominatorMinShiftDown - denominatorAscent);
221     }
222
223     LayoutPoint denominatorLocation(horizontalOffset(denominator(), element().denominatorAlignment()), verticalOffset);
224     denominator().setLocation(denominatorLocation);
225
226     verticalOffset = std::max(verticalOffset + denominator().logicalHeight(), m_ascent + denominatorDescent); // This is the bottom of our renderer.
227     setLogicalHeight(verticalOffset);
228
229     layoutPositionedObjects(relayoutChildren);
230
231     clearNeedsLayout();
232 }
233
234 void RenderMathMLFraction::paint(PaintInfo& info, const LayoutPoint& paintOffset)
235 {
236     RenderMathMLBlock::paint(info, paintOffset);
237     if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE || !isValid() || isStack())
238         return;
239
240     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + LayoutPoint(0, m_ascent - mathAxisHeight()));
241
242     GraphicsContextStateSaver stateSaver(info.context());
243
244     info.context().setStrokeThickness(m_lineThickness);
245     info.context().setStrokeStyle(SolidStroke);
246     info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor));
247     info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + logicalWidth(), adjustedPaintOffset.y())));
248 }
249
250 std::optional<int> RenderMathMLFraction::firstLineBaseline() const
251 {
252     if (isValid())
253         return std::optional<int>(std::lround(static_cast<float>(m_ascent)));
254     return RenderMathMLBlock::firstLineBaseline();
255 }
256
257 }
258
259 #endif // ENABLE(MATHML)