Reset maxCanvasPixelMemory between tests
[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* horizontalStretchyOperator(const RenderBox& box)
54 {
55     if (!is<RenderMathMLBlock>(box))
56         return nullptr;
57
58     auto* renderOperator = downcast<RenderMathMLBlock>(box).unembellishedOperator();
59     if (!renderOperator)
60         return nullptr;
61
62     if (!renderOperator->isStretchy() || renderOperator->isVertical() || renderOperator->isStretchWidthLocked())
63         return nullptr;
64
65     return renderOperator;
66 }
67
68 static void fixLayoutAfterStretch(RenderBox& ancestor, RenderMathMLOperator& stretchyOperator)
69 {
70     stretchyOperator.setStretchWidthLocked(true);
71     stretchyOperator.setNeedsLayout();
72     ancestor.layoutIfNeeded();
73     stretchyOperator.setStretchWidthLocked(false);
74 }
75
76 void RenderMathMLUnderOver::stretchHorizontalOperatorsAndLayoutChildren()
77 {
78     ASSERT(isValid());
79     ASSERT(needsLayout());
80
81     // We apply horizontal stretchy rules from the MathML spec (sections 3.2.5.8.3 and 3.2.5.8.4), which
82     // can be roughly summarized as "stretching opersators to the maximum widths of all children" and
83     // minor variations of that algorithm do not affect the result. However, the spec is a bit ambiguous
84     // for embellished operators (section 3.2.5.7.3) and different approaches can lead to significant
85     // stretch size differences. We made the following decisions:
86     // - The unstretched size is the embellished operator width with the <mo> at the core unstretched.
87     // - In general, the target size is just the maximum widths of non-stretchy children because the
88     // embellishments could make widths significantly larger.
89     // - In the edge case when all operators of stretchy, we follow the specification and take the
90     // maximum of all unstretched sizes.
91     // - The <mo> at the core is stretched to cover the target size, even if the embellished operator
92     // might become much wider.
93     
94     Vector<RenderBox*, 3> embellishedOperators;
95     Vector<RenderMathMLOperator*, 3> stretchyOperators;
96     bool isAllStretchyOperators = true;
97     LayoutUnit stretchWidth;
98
99     for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) {
100         if (auto* stretchyOperator = horizontalStretchyOperator(*child)) {
101             embellishedOperators.append(child);
102             stretchyOperators.append(stretchyOperator);
103         } else {
104             isAllStretchyOperators = false;
105             child->layoutIfNeeded();
106             stretchWidth = std::max(stretchWidth, child->logicalWidth());
107         }
108     }
109
110     if (isAllStretchyOperators) {
111         for (size_t i = 0; i < embellishedOperators.size(); i++) {
112             stretchyOperators[i]->resetStretchSize();
113             fixLayoutAfterStretch(*embellishedOperators[i], *stretchyOperators[i]);
114             stretchWidth = std::max(stretchWidth, embellishedOperators[i]->logicalWidth());
115         }
116     }
117
118     for (size_t i = 0; i < embellishedOperators.size(); i++) {
119         stretchyOperators[i]->stretchTo(stretchWidth);
120         fixLayoutAfterStretch(*embellishedOperators[i], *stretchyOperators[i]);
121     }
122 }
123
124 bool RenderMathMLUnderOver::isValid() const
125 {
126     // Verify whether the list of children is valid:
127     // <munder> base under </munder>
128     // <mover> base over </mover>
129     // <munderover> base under over </munderover>
130     auto* child = firstChildBox();
131     if (!child)
132         return false;
133     child = child->nextSiblingBox();
134     if (!child)
135         return false;
136     child = child->nextSiblingBox();
137     switch (scriptType()) {
138     case MathMLScriptsElement::ScriptType::Over:
139     case MathMLScriptsElement::ScriptType::Under:
140         return !child;
141     case MathMLScriptsElement::ScriptType::UnderOver:
142         return child && !child->nextSiblingBox();
143     default:
144         ASSERT_NOT_REACHED();
145         return false;
146     }
147 }
148
149 bool RenderMathMLUnderOver::shouldMoveLimits()
150 {
151     if (auto* renderOperator = unembellishedOperator())
152         return renderOperator->shouldMoveLimits();
153     return false;
154 }
155
156 RenderBox& RenderMathMLUnderOver::base() const
157 {
158     ASSERT(isValid());
159     return *firstChildBox();
160 }
161
162 RenderBox& RenderMathMLUnderOver::under() const
163 {
164     ASSERT(isValid());
165     ASSERT(scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver);
166     return *firstChildBox()->nextSiblingBox();
167 }
168
169 RenderBox& RenderMathMLUnderOver::over() const
170 {
171     ASSERT(isValid());
172     ASSERT(scriptType() == MathMLScriptsElement::ScriptType::Over || scriptType() == MathMLScriptsElement::ScriptType::UnderOver);
173     auto* secondChild = firstChildBox()->nextSiblingBox();
174     return scriptType() == MathMLScriptsElement::ScriptType::Over ? *secondChild : *secondChild->nextSiblingBox();
175 }
176
177
178 void RenderMathMLUnderOver::computePreferredLogicalWidths()
179 {
180     ASSERT(preferredLogicalWidthsDirty());
181
182     if (!isValid()) {
183         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0;
184         setPreferredLogicalWidthsDirty(false);
185         return;
186     }
187
188     if (shouldMoveLimits()) {
189         RenderMathMLScripts::computePreferredLogicalWidths();
190         return;
191     }
192
193     LayoutUnit preferredWidth = base().maxPreferredLogicalWidth();
194
195     if (scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver)
196         preferredWidth = std::max(preferredWidth, under().maxPreferredLogicalWidth());
197
198     if (scriptType() == MathMLScriptsElement::ScriptType::Over || scriptType() == MathMLScriptsElement::ScriptType::UnderOver)
199         preferredWidth = std::max(preferredWidth, over().maxPreferredLogicalWidth());
200
201     m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth;
202
203     setPreferredLogicalWidthsDirty(false);
204 }
205
206 LayoutUnit RenderMathMLUnderOver::horizontalOffset(const RenderBox& child) const
207 {
208     return (logicalWidth() - child.logicalWidth()) / 2;
209 }
210
211 bool RenderMathMLUnderOver::hasAccent(bool accentUnder) const
212 {
213     ASSERT(scriptType() == MathMLScriptsElement::ScriptType::UnderOver || (accentUnder && scriptType() == MathMLScriptsElement::ScriptType::Under) || (!accentUnder && scriptType() == MathMLScriptsElement::ScriptType::Over));
214
215     const MathMLElement::BooleanValue& attributeValue = accentUnder ? element().accentUnder() : element().accent();
216     if (attributeValue == MathMLElement::BooleanValue::True)
217         return true;
218     if (attributeValue == MathMLElement::BooleanValue::False)
219         return false;
220     RenderBox& script = accentUnder ? under() : over();
221     if (!is<RenderMathMLBlock>(script))
222         return false;
223     auto* scriptOperator = downcast<RenderMathMLBlock>(script).unembellishedOperator();
224     return scriptOperator && scriptOperator->hasOperatorFlag(MathMLOperatorDictionary::Accent);
225 }
226
227 RenderMathMLUnderOver::VerticalParameters RenderMathMLUnderOver::verticalParameters() const
228 {
229     VerticalParameters parameters;
230
231     // By default, we set all values to zero.
232     parameters.underGapMin = 0;
233     parameters.overGapMin = 0;
234     parameters.underShiftMin = 0;
235     parameters.overShiftMin = 0;
236     parameters.underExtraDescender = 0;
237     parameters.overExtraAscender = 0;
238     parameters.accentBaseHeight = 0;
239
240     const auto& primaryFont = style().fontCascade().primaryFont();
241     auto* mathData = primaryFont.mathData();
242     if (!mathData) {
243         // The MATH table specification does not really provide any suggestions, except for some underbar/overbar values and AccentBaseHeight.
244         LayoutUnit defaultLineThickness = ruleThicknessFallback();
245         parameters.underGapMin = 3 * defaultLineThickness;
246         parameters.overGapMin = 3 * defaultLineThickness;
247         parameters.underExtraDescender = defaultLineThickness;
248         parameters.overExtraAscender = defaultLineThickness;
249         parameters.accentBaseHeight = style().fontMetrics().xHeight();
250         parameters.useUnderOverBarFallBack = true;
251         return parameters;
252     }
253
254     if (is<RenderMathMLBlock>(base())) {
255         if (auto* baseOperator = downcast<RenderMathMLBlock>(base()).unembellishedOperator()) {
256             if (baseOperator->hasOperatorFlag(MathMLOperatorDictionary::LargeOp)) {
257                 // The base is a large operator so we read UpperLimit/LowerLimit constants from the MATH table.
258                 parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitGapMin);
259                 parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitGapMin);
260                 parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitBaselineDropMin);
261                 parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitBaselineRiseMin);
262                 parameters.useUnderOverBarFallBack = false;
263                 return parameters;
264             }
265             if (baseOperator->isStretchy() && !baseOperator->isVertical()) {
266                 // The base is a horizontal stretchy operator, so we read StretchStack constants from the MATH table.
267                 parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapBelowMin);
268                 parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapAboveMin);
269                 parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackBottomShiftDown);
270                 parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackTopShiftUp);
271                 parameters.useUnderOverBarFallBack = false;
272                 return parameters;
273             }
274         }
275     }
276
277     // By default, we just use the underbar/overbar constants.
278     parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarVerticalGap);
279     parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarVerticalGap);
280     parameters.underExtraDescender = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarExtraDescender);
281     parameters.overExtraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarExtraAscender);
282     parameters.accentBaseHeight = mathData->getMathConstant(primaryFont, OpenTypeMathData::AccentBaseHeight);
283     parameters.useUnderOverBarFallBack = true;
284     return parameters;
285 }
286
287 void RenderMathMLUnderOver::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight)
288 {
289     ASSERT(needsLayout());
290
291     if (!relayoutChildren && simplifiedLayout())
292         return;
293
294     if (!isValid()) {
295         layoutInvalidMarkup(relayoutChildren);
296         return;
297     }
298
299     if (shouldMoveLimits()) {
300         RenderMathMLScripts::layoutBlock(relayoutChildren, pageLogicalHeight);
301         return;
302     }
303
304     recomputeLogicalWidth();
305
306     stretchHorizontalOperatorsAndLayoutChildren();
307
308     ASSERT(!base().needsLayout());
309     ASSERT(scriptType() == MathMLScriptsElement::ScriptType::Over || !under().needsLayout());
310     ASSERT(scriptType() == MathMLScriptsElement::ScriptType::Under || !over().needsLayout());
311
312     LayoutUnit logicalWidth = base().logicalWidth();
313     if (scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver)
314         logicalWidth = std::max(logicalWidth, under().logicalWidth());
315     if (scriptType() == MathMLScriptsElement::ScriptType::Over || scriptType() == MathMLScriptsElement::ScriptType::UnderOver)
316         logicalWidth = std::max(logicalWidth, over().logicalWidth());
317     setLogicalWidth(logicalWidth);
318
319     VerticalParameters parameters = verticalParameters();
320     LayoutUnit verticalOffset;
321     if (scriptType() == MathMLScriptsElement::ScriptType::Over || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) {
322         verticalOffset += parameters.overExtraAscender;
323         over().setLocation(LayoutPoint(horizontalOffset(over()), verticalOffset));
324         if (parameters.useUnderOverBarFallBack) {
325             verticalOffset += over().logicalHeight();
326             if (hasAccent()) {
327                 LayoutUnit baseAscent = ascentForChild(base());
328                 if (baseAscent < parameters.accentBaseHeight)
329                     verticalOffset += parameters.accentBaseHeight - baseAscent;
330             } else
331                 verticalOffset += parameters.overGapMin;
332         } else {
333             LayoutUnit overAscent = ascentForChild(over());
334             verticalOffset += std::max(over().logicalHeight() + parameters.overGapMin, overAscent + parameters.overShiftMin);
335         }
336     }
337     base().setLocation(LayoutPoint(horizontalOffset(base()), verticalOffset));
338     verticalOffset += base().logicalHeight();
339     if (scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver) {
340         if (parameters.useUnderOverBarFallBack) {
341             if (!hasAccentUnder())
342                 verticalOffset += parameters.underGapMin;
343         } else {
344             LayoutUnit underAscent = ascentForChild(under());
345             verticalOffset += std::max(parameters.underGapMin, parameters.underShiftMin - underAscent);
346         }
347         under().setLocation(LayoutPoint(horizontalOffset(under()), verticalOffset));
348         verticalOffset += under().logicalHeight();
349         verticalOffset += parameters.underExtraDescender;
350     }
351
352     setLogicalHeight(verticalOffset);
353
354     layoutPositionedObjects(relayoutChildren);
355
356     updateScrollInfoAfterLayout();
357
358     clearNeedsLayout();
359 }
360
361 }
362
363 #endif // ENABLE(MATHML)