e94fa2bf043eb261d516f27b79400fc751fe3afd
[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         stretchVerticalOperatorsAndLayoutChildren();
205         getContentBoundingBox(m_baseWidth, baseAscent, baseDescent);
206         layoutRowItems(m_baseWidth, baseAscent);
207     } else {
208         getBase().layoutIfNeeded();
209         m_baseWidth = getBase().logicalWidth();
210         baseAscent = ascentForChild(getBase());
211         baseDescent = getBase().logicalHeight() - baseAscent;
212         getIndex().layoutIfNeeded();
213     }
214
215     auto horizontal = horizontalParameters();
216     auto vertical = verticalParameters();
217
218     // Stretch the radical operator to cover the base height.
219     // We can then determine the metrics of the radical operator + the base.
220     m_radicalOperator.stretchTo(style(), baseAscent + baseDescent);
221     LayoutUnit radicalOperatorHeight = m_radicalOperator.ascent() + m_radicalOperator.descent();
222     LayoutUnit indexBottomRaise = vertical.degreeBottomRaisePercent * radicalOperatorHeight;
223     LayoutUnit radicalAscent = baseAscent + vertical.verticalGap + vertical.ruleThickness + vertical.extraAscender;
224     LayoutUnit radicalDescent = std::max<LayoutUnit>(baseDescent, radicalOperatorHeight + vertical.extraAscender - radicalAscent);
225     LayoutUnit descent = radicalDescent;
226     LayoutUnit ascent = radicalAscent;
227
228     // We set the logical width.
229     if (rootType() == RootType::SquareRoot)
230         setLogicalWidth(m_radicalOperator.width() + m_baseWidth);
231     else {
232         ASSERT(rootType() == RootType::RootWithIndex);
233         setLogicalWidth(horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree + m_radicalOperator.width() + m_baseWidth);
234     }
235
236     // For <mroot>, we update the metrics to take into account the index.
237     LayoutUnit indexAscent, indexDescent;
238     if (rootType() == RootType::RootWithIndex) {
239         indexAscent = ascentForChild(getIndex());
240         indexDescent = getIndex().logicalHeight() - indexAscent;
241         ascent = std::max<LayoutUnit>(radicalAscent, indexBottomRaise + indexDescent + indexAscent - descent);
242     }
243
244     // We set the final position of children.
245     m_radicalOperatorTop = ascent - radicalAscent + vertical.extraAscender;
246     LayoutUnit horizontalOffset = m_radicalOperator.width();
247     if (rootType() == RootType::RootWithIndex)
248         horizontalOffset += horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree;
249     LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, m_baseWidth), ascent - baseAscent);
250     if (rootType() == RootType::SquareRoot) {
251         for (auto* child = firstChildBox(); child; child = child->nextSiblingBox())
252             child->setLocation(child->location() + baseLocation);
253     } else {
254         ASSERT(rootType() == RootType::RootWithIndex);
255         getBase().setLocation(baseLocation);
256         LayoutPoint indexLocation(mirrorIfNeeded(horizontal.kernBeforeDegree, getIndex()), ascent + descent - indexBottomRaise - indexDescent - indexAscent);
257         getIndex().setLocation(indexLocation);
258     }
259
260     setLogicalHeight(ascent + descent);
261
262     layoutPositionedObjects(relayoutChildren);
263
264     clearNeedsLayout();
265 }
266
267 void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset)
268 {
269     RenderMathMLRow::paint(info, paintOffset);
270
271     if (!firstChild() || info.context().paintingDisabled() || style().visibility() != VISIBLE || !isValid())
272         return;
273
274     // We draw the radical operator.
275     LayoutPoint radicalOperatorTopLeft = paintOffset + location();
276     LayoutUnit horizontalOffset = 0;
277     if (rootType() == RootType::RootWithIndex) {
278         auto horizontal = horizontalParameters();
279         horizontalOffset = horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree;
280     }
281     radicalOperatorTopLeft.move(mirrorIfNeeded(horizontalOffset, m_radicalOperator.width()), m_radicalOperatorTop);
282     m_radicalOperator.paint(style(), info, radicalOperatorTopLeft);
283
284     // We draw the radical line.
285     LayoutUnit ruleThickness = verticalParameters().ruleThickness;
286     if (!ruleThickness)
287         return;
288     GraphicsContextStateSaver stateSaver(info.context());
289
290     info.context().setStrokeThickness(ruleThickness);
291     info.context().setStrokeStyle(SolidStroke);
292     info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor));
293     LayoutPoint ruleOffsetFrom = paintOffset + location() + LayoutPoint(0, m_radicalOperatorTop + ruleThickness / 2);
294     LayoutPoint ruleOffsetTo = ruleOffsetFrom;
295     horizontalOffset += m_radicalOperator.width();
296     ruleOffsetFrom.move(mirrorIfNeeded(horizontalOffset), 0);
297     horizontalOffset += m_baseWidth;
298     ruleOffsetTo.move(mirrorIfNeeded(horizontalOffset), 0);
299     info.context().drawLine(ruleOffsetFrom, ruleOffsetTo);
300 }
301
302 }
303
304 #endif // ENABLE(MATHML)