Introduce RenderTreeBuilder
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLRoot.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 "RenderMathMLRoot.h"
30
31 #if ENABLE(MATHML)
32
33 #include "FontCache.h"
34 #include "GraphicsContext.h"
35 #include "MathMLNames.h"
36 #include "MathMLRootElement.h"
37 #include "PaintInfo.h"
38 #include "RenderIterator.h"
39 #include "RenderMathMLMenclose.h"
40 #include "RenderMathMLOperator.h"
41 #include <wtf/IsoMallocInlines.h>
42
43 static const UChar gRadicalCharacter = 0x221A;
44
45 namespace WebCore {
46
47 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLRoot);
48
49 RenderMathMLRoot::RenderMathMLRoot(MathMLRootElement& element, RenderStyle&& style)
50     : RenderMathMLRow(element, WTFMove(style))
51 {
52     m_radicalOperator.setOperator(RenderMathMLRoot::style(), gRadicalCharacter, MathOperator::Type::VerticalOperator);
53 }
54
55 MathMLRootElement& RenderMathMLRoot::element() const
56 {
57     return static_cast<MathMLRootElement&>(nodeForNonAnonymous());
58 }
59
60 RootType RenderMathMLRoot::rootType() const
61 {
62     return element().rootType();
63 }
64
65 bool RenderMathMLRoot::isValid() const
66 {
67     // Verify whether the list of children is valid:
68     // <msqrt> child1 child2 ... childN </msqrt>
69     // <mroot> base index </mroot>
70     if (rootType() == RootType::SquareRoot)
71         return true;
72
73     ASSERT(rootType() == RootType::RootWithIndex);
74     auto* child = firstChildBox();
75     if (!child)
76         return false;
77     child = child->nextSiblingBox();
78     return child && !child->nextSiblingBox();
79 }
80
81 RenderBox& RenderMathMLRoot::getBase() const
82 {
83     ASSERT(isValid());
84     ASSERT(rootType() == RootType::RootWithIndex);
85     return *firstChildBox();
86 }
87
88 RenderBox& RenderMathMLRoot::getIndex() const
89 {
90     ASSERT(isValid());
91     ASSERT(rootType() == RootType::RootWithIndex);
92     return *firstChildBox()->nextSiblingBox();
93 }
94
95 void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
96 {
97     RenderMathMLRow::styleDidChange(diff, oldStyle);
98     m_radicalOperator.reset(style());
99 }
100
101 RenderMathMLRoot::HorizontalParameters RenderMathMLRoot::horizontalParameters()
102 {
103     HorizontalParameters parameters;
104
105     // Square roots do not require horizontal parameters.
106     if (rootType() == RootType::SquareRoot)
107         return parameters;
108
109     // We try and read constants to draw the radical from the OpenType MATH and use fallback values otherwise.
110     const auto& primaryFont = style().fontCascade().primaryFont();
111     if (auto* mathData = style().fontCascade().primaryFont().mathData()) {
112         parameters.kernBeforeDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernBeforeDegree);
113         parameters.kernAfterDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernAfterDegree);
114     } else {
115         // RadicalKernBeforeDegree: No suggested value provided. OT Math Illuminated mentions 5/18 em, Gecko uses 0.
116         // RadicalKernAfterDegree: Suggested value is -10/18 of em.
117         parameters.kernBeforeDegree = 5 * style().fontCascade().size() / 18;
118         parameters.kernAfterDegree = -10 * style().fontCascade().size() / 18;
119     }
120     return parameters;
121 }
122
123 RenderMathMLRoot::VerticalParameters RenderMathMLRoot::verticalParameters()
124 {
125     VerticalParameters parameters;
126     // We try and read constants to draw the radical from the OpenType MATH and use fallback values otherwise.
127     const auto& primaryFont = style().fontCascade().primaryFont();
128     if (auto* mathData = style().fontCascade().primaryFont().mathData()) {
129         parameters.ruleThickness = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalRuleThickness);
130         parameters.verticalGap = mathData->getMathConstant(primaryFont, mathMLStyle().displayStyle() ? OpenTypeMathData::RadicalDisplayStyleVerticalGap : OpenTypeMathData::RadicalVerticalGap);
131         parameters.extraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalExtraAscender);
132         if (rootType() == RootType::RootWithIndex)
133             parameters.degreeBottomRaisePercent = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalDegreeBottomRaisePercent);
134     } else {
135         // RadicalVerticalGap: Suggested value is 5/4 default rule thickness.
136         // RadicalDisplayStyleVerticalGap: Suggested value is default rule thickness + 1/4 x-height.
137         // RadicalRuleThickness: Suggested value is default rule thickness.
138         // RadicalExtraAscender: Suggested value is RadicalRuleThickness.
139         // RadicalDegreeBottomRaisePercent: Suggested value is 60%.
140         parameters.ruleThickness = ruleThicknessFallback();
141         if (mathMLStyle().displayStyle())
142             parameters.verticalGap = parameters.ruleThickness + style().fontMetrics().xHeight() / 4;
143         else
144             parameters.verticalGap = 5 * parameters.ruleThickness / 4;
145
146         if (rootType() == RootType::RootWithIndex) {
147             parameters.extraAscender = parameters.ruleThickness;
148             parameters.degreeBottomRaisePercent = 0.6f;
149         }
150     }
151     return parameters;
152 }
153
154 void RenderMathMLRoot::computePreferredLogicalWidths()
155 {
156     ASSERT(preferredLogicalWidthsDirty());
157
158     if (!isValid()) {
159         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0;
160         setPreferredLogicalWidthsDirty(false);
161         return;
162     }
163
164     LayoutUnit preferredWidth = 0;
165     if (rootType() == RootType::SquareRoot) {
166         preferredWidth += m_radicalOperator.maxPreferredWidth();
167         setPreferredLogicalWidthsDirty(true);
168         RenderMathMLRow::computePreferredLogicalWidths();
169         preferredWidth += m_maxPreferredLogicalWidth;
170     } else {
171         ASSERT(rootType() == RootType::RootWithIndex);
172         auto horizontal = horizontalParameters();
173         preferredWidth += horizontal.kernBeforeDegree;
174         preferredWidth += getIndex().maxPreferredLogicalWidth();
175         preferredWidth += horizontal.kernAfterDegree;
176         preferredWidth += m_radicalOperator.maxPreferredWidth();
177         preferredWidth += getBase().maxPreferredLogicalWidth();
178     }
179
180     m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth;
181     setPreferredLogicalWidthsDirty(false);
182 }
183
184 void RenderMathMLRoot::layoutBlock(bool relayoutChildren, LayoutUnit)
185 {
186     ASSERT(needsLayout());
187
188     if (!relayoutChildren && simplifiedLayout())
189         return;
190
191     m_radicalOperatorTop = 0;
192     m_baseWidth = 0;
193
194     if (!isValid()) {
195         layoutInvalidMarkup(relayoutChildren);
196         return;
197     }
198
199     // We layout the children, determine the vertical metrics of the base and set the logical width.
200     // Note: Per the MathML specification, the children of <msqrt> are wrapped in an inferred <mrow>, which is the desired base.
201     LayoutUnit baseAscent, baseDescent;
202     recomputeLogicalWidth();
203     if (rootType() == RootType::SquareRoot) {
204         baseAscent = baseDescent;
205         RenderMathMLRow::computeLineVerticalStretch(baseAscent, baseDescent);
206         RenderMathMLRow::layoutRowItems(baseAscent, baseDescent);
207         m_baseWidth = logicalWidth();
208     } else {
209         getBase().layoutIfNeeded();
210         m_baseWidth = getBase().logicalWidth();
211         baseAscent = ascentForChild(getBase());
212         baseDescent = getBase().logicalHeight() - baseAscent;
213         getIndex().layoutIfNeeded();
214     }
215
216     auto horizontal = horizontalParameters();
217     auto vertical = verticalParameters();
218
219     // Stretch the radical operator to cover the base height.
220     // We can then determine the metrics of the radical operator + the base.
221     m_radicalOperator.stretchTo(style(), baseAscent + baseDescent);
222     LayoutUnit radicalOperatorHeight = m_radicalOperator.ascent() + m_radicalOperator.descent();
223     LayoutUnit indexBottomRaise = vertical.degreeBottomRaisePercent * radicalOperatorHeight;
224     LayoutUnit radicalAscent = baseAscent + vertical.verticalGap + vertical.ruleThickness + vertical.extraAscender;
225     LayoutUnit radicalDescent = std::max<LayoutUnit>(baseDescent, radicalOperatorHeight + vertical.extraAscender - radicalAscent);
226     LayoutUnit descent = radicalDescent;
227     LayoutUnit ascent = radicalAscent;
228
229     // We set the logical width.
230     if (rootType() == RootType::SquareRoot)
231         setLogicalWidth(m_radicalOperator.width() + m_baseWidth);
232     else {
233         ASSERT(rootType() == RootType::RootWithIndex);
234         setLogicalWidth(horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree + m_radicalOperator.width() + m_baseWidth);
235     }
236
237     // For <mroot>, we update the metrics to take into account the index.
238     LayoutUnit indexAscent, indexDescent;
239     if (rootType() == RootType::RootWithIndex) {
240         indexAscent = ascentForChild(getIndex());
241         indexDescent = getIndex().logicalHeight() - indexAscent;
242         ascent = std::max<LayoutUnit>(radicalAscent, indexBottomRaise + indexDescent + indexAscent - descent);
243     }
244
245     // We set the final position of children.
246     m_radicalOperatorTop = ascent - radicalAscent + vertical.extraAscender;
247     LayoutUnit horizontalOffset = m_radicalOperator.width();
248     if (rootType() == RootType::RootWithIndex)
249         horizontalOffset += horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree;
250     LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, m_baseWidth), ascent - baseAscent);
251     if (rootType() == RootType::SquareRoot) {
252         for (auto* child = firstChildBox(); child; child = child->nextSiblingBox())
253             child->setLocation(child->location() + baseLocation);
254     } else {
255         ASSERT(rootType() == RootType::RootWithIndex);
256         getBase().setLocation(baseLocation);
257         LayoutPoint indexLocation(mirrorIfNeeded(horizontal.kernBeforeDegree, getIndex()), ascent + descent - indexBottomRaise - indexDescent - indexAscent);
258         getIndex().setLocation(indexLocation);
259     }
260
261     setLogicalHeight(ascent + descent);
262
263     layoutPositionedObjects(relayoutChildren);
264
265     clearNeedsLayout();
266 }
267
268 void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset)
269 {
270     RenderMathMLRow::paint(info, paintOffset);
271
272     if (!firstChild() || info.context().paintingDisabled() || style().visibility() != VISIBLE || !isValid())
273         return;
274
275     // We draw the radical operator.
276     LayoutPoint radicalOperatorTopLeft = paintOffset + location();
277     LayoutUnit horizontalOffset = 0;
278     if (rootType() == RootType::RootWithIndex) {
279         auto horizontal = horizontalParameters();
280         horizontalOffset = horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree;
281     }
282     radicalOperatorTopLeft.move(mirrorIfNeeded(horizontalOffset, m_radicalOperator.width()), m_radicalOperatorTop);
283     m_radicalOperator.paint(style(), info, radicalOperatorTopLeft);
284
285     // We draw the radical line.
286     LayoutUnit ruleThickness = verticalParameters().ruleThickness;
287     if (!ruleThickness)
288         return;
289     GraphicsContextStateSaver stateSaver(info.context());
290
291     info.context().setStrokeThickness(ruleThickness);
292     info.context().setStrokeStyle(SolidStroke);
293     info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor));
294     LayoutPoint ruleOffsetFrom = paintOffset + location() + LayoutPoint(0, m_radicalOperatorTop + ruleThickness / 2);
295     LayoutPoint ruleOffsetTo = ruleOffsetFrom;
296     horizontalOffset += m_radicalOperator.width();
297     ruleOffsetFrom.move(mirrorIfNeeded(horizontalOffset), 0);
298     horizontalOffset += m_baseWidth;
299     ruleOffsetTo.move(mirrorIfNeeded(horizontalOffset), 0);
300     info.context().drawLine(ruleOffsetFrom, ruleOffsetTo);
301 }
302
303 }
304
305 #endif // ENABLE(MATHML)