Refactor RenderMathMLOperator and RenderMathMLToken to avoid using anonymous renderers.
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLOperator.cpp
1 /*
2  * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved.
3  * Copyright (C) 2010 Fran├žois Sausset (sausset@gmail.com). All rights reserved.
4  * Copyright (C) 2013, 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
30 #if ENABLE(MATHML)
31
32 #include "RenderMathMLOperator.h"
33
34 #include "FontSelector.h"
35 #include "MathMLNames.h"
36 #include "PaintInfo.h"
37 #include "RenderBlockFlow.h"
38 #include "RenderText.h"
39 #include "ScaleTransformOperation.h"
40 #include "TransformOperations.h"
41 #include <cmath>
42 #include <wtf/MathExtras.h>
43 #include <wtf/unicode/CharacterNames.h>
44
45 namespace WebCore {
46     
47 using namespace MathMLNames;
48
49 RenderMathMLOperator::RenderMathMLOperator(MathMLElement& element, RenderStyle&& style)
50     : RenderMathMLToken(element, WTFMove(style))
51     , m_stretchHeightAboveBaseline(0)
52     , m_stretchDepthBelowBaseline(0)
53     , m_textContent(0)
54     , m_isVertical(true)
55 {
56     updateTokenContent();
57 }
58
59 RenderMathMLOperator::RenderMathMLOperator(Document& document, RenderStyle&& style, const String& operatorString, MathMLOperatorDictionary::Form form, unsigned short flags)
60     : RenderMathMLToken(document, WTFMove(style))
61     , m_stretchHeightAboveBaseline(0)
62     , m_stretchDepthBelowBaseline(0)
63     , m_textContent(0)
64     , m_isVertical(true)
65     , m_operatorForm(form)
66     , m_operatorFlags(flags)
67 {
68     updateTokenContent(operatorString);
69 }
70
71 void RenderMathMLOperator::setOperatorFlagFromAttribute(MathMLOperatorDictionary::Flag flag, const QualifiedName& name)
72 {
73     setOperatorFlagFromAttributeValue(flag, element().fastGetAttribute(name));
74 }
75
76 void RenderMathMLOperator::setOperatorFlagFromAttributeValue(MathMLOperatorDictionary::Flag flag, const AtomicString& attributeValue)
77 {
78     ASSERT(!isAnonymous());
79
80     if (attributeValue == "true")
81         m_operatorFlags |= flag;
82     else if (attributeValue == "false")
83         m_operatorFlags &= ~flag;
84     // We ignore absent or invalid attributes.
85 }
86
87 void RenderMathMLOperator::setOperatorPropertiesFromOpDictEntry(const MathMLOperatorDictionary::Entry* entry)
88 {
89     // If this operator is anonymous, we preserve the Fence and Separator properties. This is to handle the case of RenderMathMLFenced.
90     if (isAnonymous())
91         m_operatorFlags = (m_operatorFlags & (MathMLOperatorDictionary::Fence | MathMLOperatorDictionary::Separator)) | entry->flags;
92     else
93         m_operatorFlags = entry->flags;
94
95     // Leading and trailing space is specified as multiple of 1/18em.
96     m_leadingSpace = entry->lspace * style().fontCascade().size() / 18;
97     m_trailingSpace = entry->rspace * style().fontCascade().size() / 18;
98 }
99
100 void RenderMathMLOperator::setOperatorProperties()
101 {
102     // We determine the stretch direction (default is vertical).
103     m_isVertical = MathMLOperatorDictionary::isVertical(m_textContent);
104
105     // We determine the form of the operator.
106     bool explicitForm = true;
107     if (!isAnonymous()) {
108         const AtomicString& form = element().fastGetAttribute(MathMLNames::formAttr);
109         if (form == "prefix")
110             m_operatorForm = MathMLOperatorDictionary::Prefix;
111         else if (form == "infix")
112             m_operatorForm = MathMLOperatorDictionary::Infix;
113         else if (form == "postfix")
114             m_operatorForm = MathMLOperatorDictionary::Postfix;
115         else {
116             // FIXME: We should use more advanced heuristics indicated in the specification to determine the operator form (https://bugs.webkit.org/show_bug.cgi?id=124829).
117             explicitForm = false;
118             if (!element().previousSibling() && element().nextSibling())
119                 m_operatorForm = MathMLOperatorDictionary::Prefix;
120             else if (element().previousSibling() && !element().nextSibling())
121                 m_operatorForm = MathMLOperatorDictionary::Postfix;
122             else
123                 m_operatorForm = MathMLOperatorDictionary::Infix;
124         }
125     }
126
127     // We determine the default values of the operator properties.
128
129     // First we initialize with the default values for unknown operators.
130     if (isAnonymous())
131         m_operatorFlags &= MathMLOperatorDictionary::Fence | MathMLOperatorDictionary::Separator; // This resets all but the Fence and Separator properties.
132     else
133         m_operatorFlags = 0; // This resets all the operator properties.
134     m_leadingSpace = 5 * style().fontCascade().size() / 18; // This sets leading space to "thickmathspace".
135     m_trailingSpace = 5 * style().fontCascade().size() / 18; // This sets trailing space to "thickmathspace".
136     m_minSize = style().fontCascade().size(); // This sets minsize to "1em".
137     m_maxSize = intMaxForLayoutUnit; // This sets maxsize to "infinity".
138
139     if (m_textContent) {
140         // Then we try to find the default values from the operator dictionary.
141         if (const MathMLOperatorDictionary::Entry* entry = MathMLOperatorDictionary::getEntry(m_textContent, m_operatorForm))
142             setOperatorPropertiesFromOpDictEntry(entry);
143         else if (!explicitForm) {
144             // If we did not find the desired operator form and if it was not set explicitely, we use the first one in the following order: Infix, Prefix, Postfix.
145             // This is to handle bad MathML markup without explicit <mrow> delimiters like "<mo>(</mo><mi>a</mi><mo>)</mo><mo>(</mo><mi>b</mi><mo>)</mo>" where the inner parenthesis should not be considered infix.
146             if (const MathMLOperatorDictionary::Entry* entry = MathMLOperatorDictionary::getEntry(m_textContent)) {
147                 m_operatorForm = static_cast<MathMLOperatorDictionary::Form>(entry->form); // We override the form previously determined.
148                 setOperatorPropertiesFromOpDictEntry(entry);
149             }
150         }
151     }
152
153     if (!isAnonymous()) {
154         // Finally, we make the attribute values override the default.
155
156         setOperatorFlagFromAttribute(MathMLOperatorDictionary::Fence, MathMLNames::fenceAttr);
157         setOperatorFlagFromAttribute(MathMLOperatorDictionary::Separator, MathMLNames::separatorAttr);
158         setOperatorFlagFromAttribute(MathMLOperatorDictionary::Stretchy, MathMLNames::stretchyAttr);
159         setOperatorFlagFromAttribute(MathMLOperatorDictionary::Symmetric, MathMLNames::symmetricAttr);
160         setOperatorFlagFromAttribute(MathMLOperatorDictionary::LargeOp, MathMLNames::largeopAttr);
161         setOperatorFlagFromAttribute(MathMLOperatorDictionary::MovableLimits, MathMLNames::movablelimitsAttr);
162         setOperatorFlagFromAttribute(MathMLOperatorDictionary::Accent, MathMLNames::accentAttr);
163
164         parseMathMLLength(element().fastGetAttribute(MathMLNames::lspaceAttr), m_leadingSpace, &style(), false); // FIXME: Negative leading space must be implemented (https://bugs.webkit.org/show_bug.cgi?id=124830).
165         parseMathMLLength(element().fastGetAttribute(MathMLNames::rspaceAttr), m_trailingSpace, &style(), false); // FIXME: Negative trailing space must be implemented (https://bugs.webkit.org/show_bug.cgi?id=124830).
166
167         parseMathMLLength(element().fastGetAttribute(MathMLNames::minsizeAttr), m_minSize, &style(), false);
168         const AtomicString& maxsize = element().fastGetAttribute(MathMLNames::maxsizeAttr);
169         if (maxsize != "infinity")
170             parseMathMLLength(maxsize, m_maxSize, &style(), false);
171     }
172 }
173
174 void RenderMathMLOperator::stretchTo(LayoutUnit heightAboveBaseline, LayoutUnit depthBelowBaseline)
175 {
176     ASSERT(hasOperatorFlag(MathMLOperatorDictionary::Stretchy));
177     ASSERT(m_isVertical);
178
179     if (!m_isVertical || (heightAboveBaseline == m_stretchHeightAboveBaseline && depthBelowBaseline == m_stretchDepthBelowBaseline))
180         return;
181
182     m_stretchHeightAboveBaseline = heightAboveBaseline;
183     m_stretchDepthBelowBaseline = depthBelowBaseline;
184
185     setOperatorProperties();
186     if (hasOperatorFlag(MathMLOperatorDictionary::Symmetric)) {
187         // We make the operator stretch symmetrically above and below the axis.
188         // FIXME: We should read the axis from the MATH table (https://bugs.webkit.org/show_bug.cgi?id=122297). For now, we use the same value as in RenderMathMLFraction::firstLineBaseline().
189         LayoutUnit axis = style().fontMetrics().xHeight() / 2;
190         LayoutUnit halfStretchSize = std::max(m_stretchHeightAboveBaseline - axis, m_stretchDepthBelowBaseline + axis);
191         m_stretchHeightAboveBaseline = halfStretchSize + axis;
192         m_stretchDepthBelowBaseline = halfStretchSize - axis;
193     }
194     // We try to honor the minsize/maxsize condition by increasing or decreasing both height and depth proportionately.
195     // The MathML specification does not indicate what to do when maxsize < minsize, so we follow Gecko and make minsize take precedence.
196     LayoutUnit size = stretchSize();
197     float aspect = 1.0;
198     if (size > 0) {
199         if (size < m_minSize)
200             aspect = float(m_minSize) / size;
201         else if (m_maxSize < size)
202             aspect = float(m_maxSize) / size;
203     }
204     m_stretchHeightAboveBaseline *= aspect;
205     m_stretchDepthBelowBaseline *= aspect;
206
207     m_mathOperator.stretchTo(style(), m_stretchHeightAboveBaseline, m_stretchDepthBelowBaseline);
208
209     setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent());
210 }
211
212 void RenderMathMLOperator::stretchTo(LayoutUnit width)
213 {
214     ASSERT(hasOperatorFlag(MathMLOperatorDictionary::Stretchy));
215     ASSERT(!m_isVertical);
216
217     if (m_isVertical || m_stretchWidth == width)
218         return;
219
220     m_stretchWidth = width;
221     m_mathOperator.stretchTo(style(), width);
222
223     setOperatorProperties();
224
225     setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent());
226 }
227
228 void RenderMathMLOperator::resetStretchSize()
229 {
230     if (m_isVertical) {
231         m_stretchHeightAboveBaseline = 0;
232         m_stretchDepthBelowBaseline = 0;
233     } else
234         m_stretchWidth = 0;
235 }
236
237 void RenderMathMLOperator::computePreferredLogicalWidths()
238 {
239     ASSERT(preferredLogicalWidthsDirty());
240
241     LayoutUnit preferredWidth = 0;
242
243     setOperatorProperties();
244     if (!useMathOperator()) {
245         RenderMathMLToken::computePreferredLogicalWidths();
246         preferredWidth = m_maxPreferredLogicalWidth;
247         if (isInvisibleOperator()) {
248             // In some fonts, glyphs for invisible operators have nonzero width. Consequently, we subtract that width here to avoid wide gaps.
249             GlyphData data = style().fontCascade().glyphDataForCharacter(m_textContent, false);
250             float glyphWidth = data.isValid() ? data.font->widthForGlyph(data.glyph) : 0;
251             ASSERT(glyphWidth <= preferredWidth);
252             preferredWidth -= glyphWidth;
253         }
254     } else
255         preferredWidth = m_mathOperator.maxPreferredWidth();
256
257     // FIXME: The spacing should be added to the whole embellished operator (https://webkit.org/b/124831).
258     // FIXME: The spacing should only be added inside (perhaps inferred) mrow (http://www.w3.org/TR/MathML/chapter3.html#presm.opspacing).
259     preferredWidth = m_leadingSpace + preferredWidth + m_trailingSpace;
260
261     m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = preferredWidth;
262
263     setPreferredLogicalWidthsDirty(false);
264 }
265
266 void RenderMathMLOperator::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight)
267 {
268     ASSERT(needsLayout());
269
270     if (!relayoutChildren && simplifiedLayout())
271         return;
272
273     if (useMathOperator()) {
274         for (auto child = firstChildBox(); child; child = child->nextSiblingBox())
275             child->layoutIfNeeded();
276         setLogicalWidth(m_leadingSpace + m_mathOperator.width() + m_trailingSpace);
277         setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent());
278     } else {
279         // We first do the normal layout without spacing.
280         recomputeLogicalWidth();
281         LayoutUnit width = logicalWidth();
282         setLogicalWidth(width - m_leadingSpace - m_trailingSpace);
283         RenderMathMLToken::layoutBlock(relayoutChildren, pageLogicalHeight);
284         setLogicalWidth(width);
285
286         // We then move the children to take spacing into account.
287         LayoutPoint horizontalShift(style().direction() == LTR ? m_leadingSpace : -m_leadingSpace, 0);
288         for (auto* child = firstChildBox(); child; child = child->nextSiblingBox())
289             child->setLocation(child->location() + horizontalShift);
290     }
291
292     clearNeedsLayout();
293 }
294
295 void RenderMathMLOperator::rebuildTokenContent(const String& operatorString)
296 {
297     // We collapse the whitespace and replace the hyphens by minus signs.
298     AtomicString textContent = operatorString.stripWhiteSpace().simplifyWhiteSpace().replace(hyphenMinus, minusSign).impl();
299
300     // We verify whether the operator text can be represented by a single UChar.
301     // FIXME: This does not handle surrogate pairs (https://bugs.webkit.org/show_bug.cgi?id=122296).
302     // FIXME: This does not handle <mo> operators with multiple characters (https://bugs.webkit.org/show_bug.cgi?id=124828).
303     m_textContent = textContent.length() == 1 ? textContent[0] : 0;
304     setOperatorProperties();
305
306     if (useMathOperator()) {
307         MathOperator::Type type;
308         if (!shouldAllowStretching())
309             type = MathOperator::Type::NormalOperator;
310         else if (isLargeOperatorInDisplayStyle())
311             type = MathOperator::Type::DisplayOperator;
312         else
313             type = m_isVertical ? MathOperator::Type::VerticalOperator : MathOperator::Type::HorizontalOperator;
314         m_mathOperator.setOperator(style(), m_textContent, type);
315     }
316
317     setNeedsLayoutAndPrefWidthsRecalc();
318 }
319
320 void RenderMathMLOperator::updateTokenContent(const String& operatorString)
321 {
322     ASSERT(isAnonymous());
323     rebuildTokenContent(operatorString);
324 }
325
326 void RenderMathMLOperator::updateTokenContent()
327 {
328     ASSERT(!isAnonymous());
329     rebuildTokenContent(element().textContent());
330 }
331
332 void RenderMathMLOperator::updateFromElement()
333 {
334     updateTokenContent();
335 }
336
337 void RenderMathMLOperator::updateOperatorProperties()
338 {
339     setOperatorProperties();
340     setNeedsLayoutAndPrefWidthsRecalc();
341 }
342
343 bool RenderMathMLOperator::shouldAllowStretching() const
344 {
345     return m_textContent && (hasOperatorFlag(MathMLOperatorDictionary::Stretchy) || isLargeOperatorInDisplayStyle());
346 }
347
348 bool RenderMathMLOperator::useMathOperator() const
349 {
350     // We use the MathOperator class to handle the following cases:
351     // 1) Stretchy and large operators, since they require special painting.
352     // 2) The minus sign, since it can be obtained from a hyphen in the DOM.
353     // 3) The anonymous operators created by mfenced, since they do not have text content in the DOM.
354     return shouldAllowStretching() || m_textContent == minusSign || isAnonymous();
355 }
356
357 void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
358 {
359     RenderMathMLBlock::styleDidChange(diff, oldStyle);
360     m_mathOperator.reset(style());
361     updateOperatorProperties();
362 }
363
364 Optional<int> RenderMathMLOperator::firstLineBaseline() const
365 {
366     if (useMathOperator())
367         return Optional<int>(std::lround(static_cast<float>(m_mathOperator.ascent())));
368     return RenderMathMLToken::firstLineBaseline();
369 }
370
371 void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset)
372 {
373     RenderMathMLToken::paint(info, paintOffset);
374     if (!useMathOperator())
375         return;
376
377     LayoutPoint operatorTopLeft = paintOffset + location();
378     operatorTopLeft.move(style().isLeftToRightDirection() ? m_leadingSpace : m_trailingSpace, 0);
379
380     // Center horizontal operators.
381     if (!m_isVertical)
382         operatorTopLeft.move(-(m_mathOperator.width() - width()) / 2, 0);
383
384     m_mathOperator.paint(style(), info, operatorTopLeft);
385 }
386
387 void RenderMathMLOperator::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect)
388 {
389     // We skip painting for invisible operators too to avoid some "missing character" glyph to appear if appropriate math fonts are not available.
390     if (useMathOperator() || isInvisibleOperator())
391         return;
392     RenderMathMLToken::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect);
393 }
394
395 }
396
397 #endif