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
39namespace WebCore {
40
41WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLUnderOver);
42
43RenderMathMLUnderOver::RenderMathMLUnderOver(MathMLUnderOverElement& element, RenderStyle&& style)
44 : RenderMathMLScripts(element, WTFMove(style))
45{
46}
47
48MathMLUnderOverElement& RenderMathMLUnderOver::element() const
49{
50 return static_cast<MathMLUnderOverElement&>(nodeForNonAnonymous());
51}
52
53static 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
68static void fixLayoutAfterStretch(RenderBox& ancestor, RenderMathMLOperator& stretchyOperator)
69{
70 stretchyOperator.setStretchWidthLocked(true);
71 stretchyOperator.setNeedsLayout();
72 ancestor.layoutIfNeeded();
73 stretchyOperator.setStretchWidthLocked(false);
74}
75
76void 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
124bool 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
149bool RenderMathMLUnderOver::shouldMoveLimits()
150{
151 if (auto* renderOperator = unembellishedOperator())
152 return renderOperator->shouldMoveLimits();
153 return false;
154}
155
156RenderBox& RenderMathMLUnderOver::base() const
157{
158 ASSERT(isValid());
159 return *firstChildBox();
160}
161
162RenderBox& RenderMathMLUnderOver::under() const
163{
164 ASSERT(isValid());
165 ASSERT(scriptType() == MathMLScriptsElement::ScriptType::Under || scriptType() == MathMLScriptsElement::ScriptType::UnderOver);
166 return *firstChildBox()->nextSiblingBox();
167}
168
169RenderBox& 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
178void 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
206LayoutUnit RenderMathMLUnderOver::horizontalOffset(const RenderBox& child) const
207{
208 return (logicalWidth() - child.logicalWidth()) / 2;
209}
210
211bool 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
227RenderMathMLUnderOver::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
287void 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)
364