Introduce RenderTreeBuilder
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLUnderOver.cpp
1 /*
2  * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
3  * Copyright (C) 2016 Igalia S.L.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "RenderMathMLUnderOver.h"
29
30 #if ENABLE(MATHML)
31
32 #include "MathMLElement.h"
33 #include "MathMLOperatorDictionary.h"
34 #include "MathMLUnderOverElement.h"
35 #include "RenderIterator.h"
36 #include "RenderMathMLOperator.h"
37 #include <wtf/IsoMallocInlines.h>
38
39 namespace WebCore {
40
41 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLUnderOver);
42
43 RenderMathMLUnderOver::RenderMathMLUnderOver(MathMLUnderOverElement& element, RenderStyle&& style)
44     : RenderMathMLScripts(element, WTFMove(style))
45 {
46 }
47
48 MathMLUnderOverElement& RenderMathMLUnderOver::element() const
49 {
50     return static_cast<MathMLUnderOverElement&>(nodeForNonAnonymous());
51 }
52
53 static RenderMathMLOperator* toHorizontalStretchyOperator(RenderBox* box)
54 {
55     if (is<RenderMathMLBlock>(box)) {
56         if (auto renderOperator = downcast<RenderMathMLBlock>(*box).unembellishedOperator()) {
57             if (renderOperator->isStretchy() && !renderOperator->isVertical() && !renderOperator->isStretchWidthLocked())
58                 return renderOperator;
59         }
60     }
61     return nullptr;
62 }
63     
64 static void fixLayoutAfterStretch(RenderBox* ancestor, RenderMathMLOperator* stretchyOperator)
65 {
66     stretchyOperator->setStretchWidthLocked(true);
67     stretchyOperator->setNeedsLayout();
68     ancestor->layoutIfNeeded();
69     stretchyOperator->setStretchWidthLocked(false);
70 }
71
72 void RenderMathMLUnderOver::stretchHorizontalOperatorsAndLayoutChildren()
73 {
74     ASSERT(isValid());
75     ASSERT(needsLayout());
76     
77     Vector<RenderBox*, 3> embellishedOperators;
78     Vector<RenderMathMLOperator*, 3> stretchyOperators;
79     bool isAllStretchyOperators = true;
80     LayoutUnit stretchWidth = 0;
81     
82     for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) {
83         if (auto* stretchyOperator = toHorizontalStretchyOperator(child)) {
84             embellishedOperators.append(child);
85             stretchyOperators.append(stretchyOperator);
86             stretchyOperator->resetStretchSize();
87             fixLayoutAfterStretch(child, stretchyOperator);
88         } else {
89             isAllStretchyOperators = false;
90             child->layoutIfNeeded();
91             stretchWidth = std::max(stretchWidth, child->logicalWidth());
92         }
93     }
94     
95     if (isAllStretchyOperators) {
96         for (auto* embellishedOperator : embellishedOperators)
97             stretchWidth = std::max(stretchWidth, embellishedOperator->logicalWidth());
98     }
99     
100     for (size_t i = 0; i < embellishedOperators.size(); i++) {
101         stretchyOperators[i]->stretchTo(stretchWidth);
102         fixLayoutAfterStretch(embellishedOperators[i], stretchyOperators[i]);
103     }
104 }
105
106 bool RenderMathMLUnderOver::isValid() const
107 {
108     // Verify whether the list of children is valid:
109     // <munder> base under </munder>
110     // <mover> base over </mover>
111     // <munderover> base under over </munderover>
112     auto* child = firstChildBox();
113     if (!child)
114         return false;
115     child = child->nextSiblingBox();
116     if (!child)
117         return false;
118     child = child->nextSiblingBox();
119     switch (scriptType()) {
120     case ScriptType::Over:
121     case ScriptType::Under:
122         return !child;
123     case ScriptType::UnderOver:
124         return child && !child->nextSiblingBox();
125     default:
126         ASSERT_NOT_REACHED();
127         return false;
128     }
129 }
130
131 bool RenderMathMLUnderOver::shouldMoveLimits()
132 {
133     if (auto* renderOperator = unembellishedOperator())
134         return renderOperator->shouldMoveLimits();
135     return false;
136 }
137
138 RenderBox& RenderMathMLUnderOver::base() const
139 {
140     ASSERT(isValid());
141     return *firstChildBox();
142 }
143
144 RenderBox& RenderMathMLUnderOver::under() const
145 {
146     ASSERT(isValid());
147     ASSERT(scriptType() == ScriptType::Under || scriptType() == ScriptType::UnderOver);
148     return *firstChildBox()->nextSiblingBox();
149 }
150
151 RenderBox& RenderMathMLUnderOver::over() const
152 {
153     ASSERT(isValid());
154     ASSERT(scriptType() == ScriptType::Over || scriptType() == ScriptType::UnderOver);
155     auto* secondChild = firstChildBox()->nextSiblingBox();
156     return scriptType() == ScriptType::Over ? *secondChild : *secondChild->nextSiblingBox();
157 }
158
159
160 void RenderMathMLUnderOver::computePreferredLogicalWidths()
161 {
162     ASSERT(preferredLogicalWidthsDirty());
163
164     if (!isValid()) {
165         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0;
166         setPreferredLogicalWidthsDirty(false);
167         return;
168     }
169
170     if (shouldMoveLimits()) {
171         RenderMathMLScripts::computePreferredLogicalWidths();
172         return;
173     }
174
175     LayoutUnit preferredWidth = base().maxPreferredLogicalWidth();
176
177     if (scriptType() == ScriptType::Under || scriptType() == ScriptType::UnderOver)
178         preferredWidth = std::max(preferredWidth, under().maxPreferredLogicalWidth());
179
180     if (scriptType() == ScriptType::Over || scriptType() == ScriptType::UnderOver)
181         preferredWidth = std::max(preferredWidth, over().maxPreferredLogicalWidth());
182
183     m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth;
184
185     setPreferredLogicalWidthsDirty(false);
186 }
187
188 LayoutUnit RenderMathMLUnderOver::horizontalOffset(const RenderBox& child) const
189 {
190     return (logicalWidth() - child.logicalWidth()) / 2;
191 }
192
193 bool RenderMathMLUnderOver::hasAccent(bool accentUnder) const
194 {
195     ASSERT(scriptType() == ScriptType::UnderOver || (accentUnder && scriptType() == ScriptType::Under) || (!accentUnder && scriptType() == ScriptType::Over));
196
197     const MathMLElement::BooleanValue& attributeValue = accentUnder ? element().accentUnder() : element().accent();
198     if (attributeValue == MathMLElement::BooleanValue::True)
199         return true;
200     if (attributeValue == MathMLElement::BooleanValue::False)
201         return false;
202     RenderBox& script = accentUnder ? under() : over();
203     if (!is<RenderMathMLBlock>(script))
204         return false;
205     auto* scriptOperator = downcast<RenderMathMLBlock>(script).unembellishedOperator();
206     return scriptOperator && scriptOperator->hasOperatorFlag(MathMLOperatorDictionary::Accent);
207 }
208
209 RenderMathMLUnderOver::VerticalParameters RenderMathMLUnderOver::verticalParameters() const
210 {
211     VerticalParameters parameters;
212
213     // By default, we set all values to zero.
214     parameters.underGapMin = 0;
215     parameters.overGapMin = 0;
216     parameters.underShiftMin = 0;
217     parameters.overShiftMin = 0;
218     parameters.underExtraDescender = 0;
219     parameters.overExtraAscender = 0;
220     parameters.accentBaseHeight = 0;
221
222     const auto& primaryFont = style().fontCascade().primaryFont();
223     auto* mathData = primaryFont.mathData();
224     if (!mathData) {
225         // The MATH table specification does not really provide any suggestions, except for some underbar/overbar values and AccentBaseHeight.
226         LayoutUnit defaultLineThickness = ruleThicknessFallback();
227         parameters.underGapMin = 3 * defaultLineThickness;
228         parameters.overGapMin = 3 * defaultLineThickness;
229         parameters.underExtraDescender = defaultLineThickness;
230         parameters.overExtraAscender = defaultLineThickness;
231         parameters.accentBaseHeight = style().fontMetrics().xHeight();
232         parameters.useUnderOverBarFallBack = true;
233         return parameters;
234     }
235
236     if (is<RenderMathMLBlock>(base())) {
237         if (auto* baseOperator = downcast<RenderMathMLBlock>(base()).unembellishedOperator()) {
238             if (baseOperator->hasOperatorFlag(MathMLOperatorDictionary::LargeOp)) {
239                 // The base is a large operator so we read UpperLimit/LowerLimit constants from the MATH table.
240                 parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitGapMin);
241                 parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitGapMin);
242                 parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitBaselineDropMin);
243                 parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitBaselineRiseMin);
244                 parameters.useUnderOverBarFallBack = false;
245                 return parameters;
246             }
247             if (baseOperator->isStretchy() && !baseOperator->isVertical()) {
248                 // The base is a horizontal stretchy operator, so we read StretchStack constants from the MATH table.
249                 parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapBelowMin);
250                 parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapAboveMin);
251                 parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackBottomShiftDown);
252                 parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackTopShiftUp);
253                 parameters.useUnderOverBarFallBack = false;
254                 return parameters;
255             }
256         }
257     }
258
259     // By default, we just use the underbar/overbar constants.
260     parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarVerticalGap);
261     parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarVerticalGap);
262     parameters.underExtraDescender = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarExtraDescender);
263     parameters.overExtraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarExtraAscender);
264     parameters.accentBaseHeight = mathData->getMathConstant(primaryFont, OpenTypeMathData::AccentBaseHeight);
265     parameters.useUnderOverBarFallBack = true;
266     return parameters;
267 }
268
269 void RenderMathMLUnderOver::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight)
270 {
271     ASSERT(needsLayout());
272
273     if (!relayoutChildren && simplifiedLayout())
274         return;
275
276     if (!isValid()) {
277         layoutInvalidMarkup(relayoutChildren);
278         return;
279     }
280
281     if (shouldMoveLimits()) {
282         RenderMathMLScripts::layoutBlock(relayoutChildren, pageLogicalHeight);
283         return;
284     }
285
286     recomputeLogicalWidth();
287
288     stretchHorizontalOperatorsAndLayoutChildren();
289
290     ASSERT(!base().needsLayout());
291     ASSERT(scriptType() == ScriptType::Over || !under().needsLayout());
292     ASSERT(scriptType() == ScriptType::Under || !over().needsLayout());
293     
294     LayoutUnit logicalWidth = base().logicalWidth();
295     if (scriptType() == ScriptType::Under || scriptType() == ScriptType::UnderOver)
296         logicalWidth = std::max(logicalWidth, under().logicalWidth());
297     if (scriptType() == ScriptType::Over || scriptType() == ScriptType::UnderOver)
298         logicalWidth = std::max(logicalWidth, over().logicalWidth());
299     setLogicalWidth(logicalWidth);
300
301     VerticalParameters parameters = verticalParameters();
302     LayoutUnit verticalOffset = 0;
303     if (scriptType() == ScriptType::Over || scriptType() == ScriptType::UnderOver) {
304         verticalOffset += parameters.overExtraAscender;
305         over().setLocation(LayoutPoint(horizontalOffset(over()), verticalOffset));
306         if (parameters.useUnderOverBarFallBack) {
307             verticalOffset += over().logicalHeight();
308             if (hasAccent()) {
309                 LayoutUnit baseAscent = ascentForChild(base());
310                 if (baseAscent < parameters.accentBaseHeight)
311                     verticalOffset += parameters.accentBaseHeight - baseAscent;
312             } else
313                 verticalOffset += parameters.overGapMin;
314         } else {
315             LayoutUnit overAscent = ascentForChild(over());
316             verticalOffset += std::max(over().logicalHeight() + parameters.overGapMin, overAscent + parameters.overShiftMin);
317         }
318     }
319     base().setLocation(LayoutPoint(horizontalOffset(base()), verticalOffset));
320     verticalOffset += base().logicalHeight();
321     if (scriptType() == ScriptType::Under || scriptType() == ScriptType::UnderOver) {
322         if (parameters.useUnderOverBarFallBack) {
323             if (!hasAccentUnder())
324                 verticalOffset += parameters.underGapMin;
325         } else {
326             LayoutUnit underAscent = ascentForChild(under());
327             verticalOffset += std::max(parameters.underGapMin, parameters.underShiftMin - underAscent);
328         }
329         under().setLocation(LayoutPoint(horizontalOffset(under()), verticalOffset));
330         verticalOffset += under().logicalHeight();
331         verticalOffset += parameters.underExtraDescender;
332     }
333
334     setLogicalHeight(verticalOffset);
335
336     layoutPositionedObjects(relayoutChildren);
337
338     clearNeedsLayout();
339 }
340
341 }
342
343 #endif // ENABLE(MATHML)