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