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
43static const UChar gRadicalCharacter = 0x221A;
44
45namespace WebCore {
46
47WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLRoot);
48
49RenderMathMLRoot::RenderMathMLRoot(MathMLRootElement& element, RenderStyle&& style)
50 : RenderMathMLRow(element, WTFMove(style))
51{
52 m_radicalOperator.setOperator(RenderMathMLRoot::style(), gRadicalCharacter, MathOperator::Type::VerticalOperator);
53}
54
55MathMLRootElement& RenderMathMLRoot::element() const
56{
57 return static_cast<MathMLRootElement&>(nodeForNonAnonymous());
58}
59
60RootType RenderMathMLRoot::rootType() const
61{
62 return element().rootType();
63}
64
65bool 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
81RenderBox& RenderMathMLRoot::getBase() const
82{
83 ASSERT(isValid());
84 ASSERT(rootType() == RootType::RootWithIndex);
85 return *firstChildBox();
86}
87
88RenderBox& RenderMathMLRoot::getIndex() const
89{
90 ASSERT(isValid());
91 ASSERT(rootType() == RootType::RootWithIndex);
92 return *firstChildBox()->nextSiblingBox();
93}
94
95void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
96{
97 RenderMathMLRow::styleDidChange(diff, oldStyle);
98 m_radicalOperator.reset(style());
99}
100
101RenderMathMLRoot::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
123RenderMathMLRoot::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
154void 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;
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
184void 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 updateScrollInfoAfterLayout();
265
266 clearNeedsLayout();
267}
268
269void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset)
270{
271 RenderMathMLRow::paint(info, paintOffset);
272
273 if (!firstChild() || info.context().paintingDisabled() || style().visibility() != Visibility::Visible || !isValid())
274 return;
275
276 // We draw the radical operator.
277 LayoutPoint radicalOperatorTopLeft = paintOffset + location();
278 LayoutUnit horizontalOffset;
279 if (rootType() == RootType::RootWithIndex) {
280 auto horizontal = horizontalParameters();
281 horizontalOffset = horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree;
282 }
283 radicalOperatorTopLeft.move(mirrorIfNeeded(horizontalOffset, m_radicalOperator.width()), m_radicalOperatorTop);
284 m_radicalOperator.paint(style(), info, radicalOperatorTopLeft);
285
286 // We draw the radical line.
287 LayoutUnit ruleThickness = verticalParameters().ruleThickness;
288 if (!ruleThickness)
289 return;
290 GraphicsContextStateSaver stateSaver(info.context());
291
292 info.context().setStrokeThickness(ruleThickness);
293 info.context().setStrokeStyle(SolidStroke);
294 info.context().setStrokeColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor));
295 LayoutPoint ruleOffsetFrom = paintOffset + location() + LayoutPoint(0_lu, m_radicalOperatorTop + ruleThickness / 2);
296 LayoutPoint ruleOffsetTo = ruleOffsetFrom;
297 horizontalOffset += m_radicalOperator.width();
298 ruleOffsetFrom.move(mirrorIfNeeded(horizontalOffset), 0_lu);
299 horizontalOffset += m_baseWidth;
300 ruleOffsetTo.move(mirrorIfNeeded(horizontalOffset), 0_lu);
301 info.context().drawLine(ruleOffsetFrom, ruleOffsetTo);
302}
303
304}
305
306#endif // ENABLE(MATHML)
307