ed209ad1a68e0647df54988e6307fd08125a2b99
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLRoot.cpp
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  *
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
29 #if ENABLE(MATHML)
30
31 #include "RenderMathMLRoot.h"
32
33 #include "FontCache.h"
34 #include "GraphicsContext.h"
35 #include "PaintInfo.h"
36 #include "RenderIterator.h"
37 #include "RenderMathMLRadicalOperator.h"
38 #include "RenderMathMLSquareRoot.h"
39
40 namespace WebCore {
41     
42 // RenderMathMLRoot implements drawing of radicals via the <mroot> and <msqrt> elements. For valid MathML elements, the DOM is
43 //
44 // <mroot> Base Index </mroot>
45 // <msqrt> Child1 Child2 ... ChildN </msqrt>
46 //
47 // and the structure of the render tree will be
48 //
49 // IndexWrapper RadicalWrapper BaseWrapper
50 //
51 // where RadicalWrapper contains an <mo>&#x221A;</mo>.
52 // For <mroot>, the IndexWrapper and BaseWrapper should contain exactly one child (Index and Base respectively).
53 // For <msqrt>, the IndexWrapper should be empty and the BaseWrapper can contain any number of children (Child1, ... ChildN).
54 //
55 // In order to accept invalid markup and to handle <mroot> and <msqrt> consistently, we will allow any number of children in the BaseWrapper of <mroot> too.
56 // We will allow the IndexWrapper to be empty and it will always contain the last child of the <mroot> if there are at least 2 elements.
57
58 RenderMathMLRoot::RenderMathMLRoot(Element& element, PassRef<RenderStyle> style)
59     : RenderMathMLBlock(element, WTF::move(style))
60 {
61 }
62
63 RenderMathMLRoot::RenderMathMLRoot(Document& document, PassRef<RenderStyle> style)
64     : RenderMathMLBlock(document, WTF::move(style))
65 {
66 }
67
68 RenderMathMLRootWrapper* RenderMathMLRoot::baseWrapper() const
69 {
70     ASSERT(!isEmpty());
71     return downcast<RenderMathMLRootWrapper>(lastChild());
72 }
73
74 RenderMathMLBlock* RenderMathMLRoot::radicalWrapper() const
75 {
76     ASSERT(!isEmpty());
77     return downcast<RenderMathMLBlock>(lastChild()->previousSibling());
78 }
79
80 RenderMathMLRootWrapper* RenderMathMLRoot::indexWrapper() const
81 {
82     ASSERT(!isEmpty());
83     return is<RenderMathMLSquareRoot>(*this) ? nullptr : downcast<RenderMathMLRootWrapper>(firstChild());
84 }
85
86 RenderMathMLRadicalOperator* RenderMathMLRoot::radicalOperator() const
87 {
88     ASSERT(!isEmpty());
89     return downcast<RenderMathMLRadicalOperator>(radicalWrapper()->firstChild());
90 }
91
92 void RenderMathMLRoot::restructureWrappers()
93 {
94     ASSERT(!isEmpty());
95
96     auto base = baseWrapper();
97     auto index = indexWrapper();
98     auto radical = radicalWrapper();
99
100     // For visual consistency with the initial state, we remove the radical when the base/index wrappers become empty.
101     if (base->isEmpty() && (!index || index->isEmpty())) {
102         if (!radical->isEmpty()) {
103             auto child = radicalOperator();
104             radical->removeChild(*child);
105             child->destroy();
106         }
107         // FIXME: early return!!!
108     }
109
110     if (radical->isEmpty()) {
111         // We create the radical operator.
112         RenderPtr<RenderMathMLRadicalOperator> radicalOperator = createRenderer<RenderMathMLRadicalOperator>(document(), RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX));
113         radicalOperator->initializeStyle();
114         radical->addChild(radicalOperator.leakPtr());
115     }
116
117     if (isRenderMathMLSquareRoot())
118         return;
119
120     if (auto childToMove = base->lastChild()) {
121         // We move the last child of the base wrapper into the index wrapper if the index wrapper is empty and the base wrapper has at least two children.
122         if (childToMove->previousSibling() && index->isEmpty()) {
123             base->removeChildWithoutRestructuring(*childToMove);
124             index->addChild(childToMove);
125         }
126     }
127
128     if (auto childToMove = index->firstChild()) {
129         // We move the first child of the index wrapper into the base wrapper if:
130         // - either the index wrapper has at least two children.
131         // - or the base wrapper is empty but the index wrapper is not.
132         if (childToMove->nextSibling() || base->isEmpty()) {
133             index->removeChildWithoutRestructuring(*childToMove);
134             base->addChild(childToMove);
135         }
136     }
137 }
138
139 void RenderMathMLRoot::addChild(RenderObject* newChild, RenderObject* beforeChild)
140 {
141     if (isEmpty()) {
142         if (!isRenderMathMLSquareRoot()) {
143             // We add the IndexWrapper.
144             RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr());
145         }
146
147         // We create the radicalWrapper
148         RenderMathMLBlock::addChild(RenderMathMLBlock::createAnonymousMathMLBlock().leakPtr());
149
150         // We create the BaseWrapper.
151         RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr());
152
153         updateStyle();
154     }
155
156     // We insert the child.
157     auto base = baseWrapper();
158     auto index = indexWrapper();
159     RenderElement* actualParent;
160     RenderElement* actualBeforeChild;
161     if (is<RenderMathMLSquareRoot>(*this)) {
162         // For square root, we always insert the child into the base wrapper.
163         actualParent = base;
164         if (beforeChild && beforeChild->parent() == base)
165             actualBeforeChild = downcast<RenderElement>(beforeChild);
166         else
167             actualBeforeChild = nullptr;
168     } else {
169         // For mroot, we insert the child into the parent of beforeChild, or at the end of the index. The wrapper structure is reorganize below.
170         actualParent = beforeChild ? beforeChild->parent() : nullptr;
171         if (actualParent == base || actualParent == index)
172             actualBeforeChild = downcast<RenderElement>(beforeChild);
173         else {
174             actualParent = index;
175             actualBeforeChild = nullptr;
176         }
177     }
178     actualParent->addChild(newChild, actualBeforeChild);
179     restructureWrappers();
180 }
181
182 void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
183 {
184     RenderMathMLBlock::styleDidChange(diff, oldStyle);
185     if (!isEmpty())
186         updateStyle();
187 }
188
189 void RenderMathMLRoot::updateFromElement()
190 {
191     RenderMathMLBlock::updateFromElement();
192     if (!isEmpty())
193         updateStyle();
194 }
195
196 void RenderMathMLRoot::updateStyle()
197 {
198     ASSERT(!isEmpty());
199
200     // We set some constants to draw the radical, as defined in the OpenType MATH tables.
201
202     m_ruleThickness = 0.05f * style().font().size();
203
204     // FIXME: The recommended default for m_verticalGap in displaystyle is rule thickness + 1/4 x-height (https://bugs.webkit.org/show_bug.cgi?id=118737).
205     m_verticalGap = 11 * m_ruleThickness / 4;
206     m_extraAscender = m_ruleThickness;
207     LayoutUnit kernBeforeDegree = 5 * style().font().size() / 18;
208     LayoutUnit kernAfterDegree = -10 * style().font().size() / 18;
209     m_degreeBottomRaisePercent = 0.6f;
210
211     const auto& primaryFontData = style().font().primaryFont();
212     if (primaryFontData && primaryFontData->mathData()) {
213         // FIXME: m_verticalGap should use RadicalDisplayStyleVertical in display mode (https://bugs.webkit.org/show_bug.cgi?id=118737).
214         m_verticalGap = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalVerticalGap);
215         m_ruleThickness = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalRuleThickness);
216         m_extraAscender = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalExtraAscender);
217
218         if (!isRenderMathMLSquareRoot()) {
219             kernBeforeDegree = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalKernBeforeDegree);
220             kernAfterDegree = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalKernAfterDegree);
221             m_degreeBottomRaisePercent = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalDegreeBottomRaisePercent);
222         }
223     }
224
225     // We set the style of the anonymous wrappers.
226
227     auto radical = radicalWrapper();
228     auto radicalStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX);
229     radicalStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout().
230     radical->setStyle(WTF::move(radicalStyle));
231     radical->setNeedsLayoutAndPrefWidthsRecalc();
232
233     auto base = baseWrapper();
234     auto baseStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX);
235     baseStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout().
236     baseStyle.get().setAlignItems(AlignBaseline);
237     base->setStyle(WTF::move(baseStyle));
238     base->setNeedsLayoutAndPrefWidthsRecalc();
239
240     if (!isRenderMathMLSquareRoot()) {
241         // For mroot, we also set the style of the index wrapper.
242         auto index = indexWrapper();
243         auto indexStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX);
244         indexStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout().
245         indexStyle.get().setMarginStart(Length(kernBeforeDegree, Fixed));
246         indexStyle.get().setMarginEnd(Length(kernAfterDegree, Fixed));
247         indexStyle.get().setAlignItems(AlignBaseline);
248         index->setStyle(WTF::move(indexStyle));
249         index->setNeedsLayoutAndPrefWidthsRecalc();
250     }
251 }
252
253 int RenderMathMLRoot::firstLineBaseline() const
254 {
255     if (!isEmpty()) {
256         auto base = baseWrapper();
257         return static_cast<int>(lroundf(base->firstLineBaseline() + base->marginTop()));
258     }
259
260     return RenderMathMLBlock::firstLineBaseline();
261 }
262
263 void RenderMathMLRoot::layout()
264 {
265     if (isEmpty()) {
266         RenderMathMLBlock::layout();
267         return;
268     }
269
270     // FIXME: It seems that changing the top margin of the base below modifies its logical height and leads to reftest failures.
271     // For now, we workaround that by avoiding to recompute the child margins if they were not reset in updateStyle().
272     auto base = baseWrapper();
273     if (base->marginTop() > 0) {
274         RenderMathMLBlock::layout();
275         return;
276     }
277
278     // We layout the children.
279     for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
280         if (child->needsLayout())
281             downcast<RenderElement>(*child).layout();
282     }
283
284     auto radical = radicalOperator();
285     if (radical) {
286         // We stretch the radical sign to cover the height of the base wrapper.
287         float baseHeight = base->logicalHeight();
288         float baseHeightAboveBaseline = base->firstLineBaseline();
289         if (baseHeightAboveBaseline == -1)
290             baseHeightAboveBaseline = baseHeight;
291         float baseDepthBelowBaseline = baseHeight - baseHeightAboveBaseline;
292         baseHeightAboveBaseline += m_verticalGap;
293         radical->stretchTo(baseHeightAboveBaseline, baseDepthBelowBaseline);
294
295         // We modify the top margins to adjust the vertical positions of wrappers.
296         float radicalTopMargin = m_extraAscender;
297         float baseTopMargin = m_verticalGap + m_ruleThickness + m_extraAscender;
298         if (!isRenderMathMLSquareRoot()) {
299             // For mroot, we try to place the index so the space below its baseline is m_degreeBottomRaisePercent times the height of the radical.
300             auto index = indexWrapper();
301             float indexHeight = 0;
302             if (!index->isEmpty())
303                 indexHeight = downcast<RenderBlock>(*index->firstChild()).logicalHeight();
304             float indexTopMargin = (1.0 - m_degreeBottomRaisePercent) * radical->stretchSize() + radicalTopMargin - indexHeight;
305             if (indexTopMargin < 0) {
306                 // If the index is too tall, we must add space at the top of renderer.
307                 radicalTopMargin -= indexTopMargin;
308                 baseTopMargin -= indexTopMargin;
309                 indexTopMargin = 0;
310             }
311             index->style().setMarginTop(Length(indexTopMargin, Fixed));
312         }
313         radical->style().setMarginTop(Length(radicalTopMargin, Fixed));
314         base->style().setMarginTop(Length(baseTopMargin, Fixed));
315     }
316
317     RenderMathMLBlock::layout();
318 }
319
320 void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset)
321 {
322     RenderMathMLBlock::paint(info, paintOffset);
323     
324     if (isEmpty() || info.context->paintingDisabled() || style().visibility() != VISIBLE)
325         return;
326
327     auto base = baseWrapper();
328     auto radical = radicalOperator();
329     if (!base || !radical || !m_ruleThickness)
330         return;
331
332     // We draw the radical line.
333     GraphicsContextStateSaver stateSaver(*info.context);
334
335     info.context->setStrokeThickness(m_ruleThickness);
336     info.context->setStrokeStyle(SolidStroke);
337     info.context->setStrokeColor(style().visitedDependentColor(CSSPropertyColor), ColorSpaceDeviceRGB);
338
339     // The preferred width of the radical is sometimes incorrect, so we draw a slightly longer line to ensure it touches the radical symbol (https://bugs.webkit.org/show_bug.cgi?id=130326).
340     LayoutUnit sizeError = radical->trailingSpaceError();
341     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + base->location() + LayoutPoint(-sizeError, -(m_verticalGap + m_ruleThickness / 2)));
342     info.context->drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + base->pixelSnappedOffsetWidth() + sizeError, adjustedPaintOffset.y()));
343 }
344
345 RenderPtr<RenderMathMLRootWrapper> RenderMathMLRootWrapper::createAnonymousWrapper(RenderMathMLRoot* renderObject)
346 {
347     RenderPtr<RenderMathMLRootWrapper> newBlock = createRenderer<RenderMathMLRootWrapper>(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX));
348     newBlock->initializeStyle();
349     return newBlock;
350 }
351
352 RenderObject* RenderMathMLRootWrapper::removeChildWithoutRestructuring(RenderObject& child)
353 {
354     return RenderMathMLBlock::removeChild(child);
355 }
356
357 RenderObject* RenderMathMLRootWrapper::removeChild(RenderObject& child)
358 {
359     RenderObject* next = RenderMathMLBlock::removeChild(child);
360
361     if (!(beingDestroyed() || documentBeingDestroyed()))
362         downcast<RenderMathMLRoot>(*parent()).restructureWrappers();
363
364     return next;
365 }
366
367 }
368
369 #endif // ENABLE(MATHML)