1/*
2 * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
17 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(MATHML)
29
30#include "RenderMathMLUnderOver.h"
31
32#include "FontSelector.h"
33#include "MathMLNames.h"
34
35namespace WebCore {
36
37using namespace MathMLNames;
38
39static const double gOverSpacingAdjustment = 0.5;
40
41RenderMathMLUnderOver::RenderMathMLUnderOver(Node* expression)
42    : RenderMathMLBlock(expression)
43{
44    Element* element = static_cast<Element*>(expression);
45    // Determine what kind of under/over expression we have by element name
46
47    if (element->hasLocalName(MathMLNames::munderTag))
48        m_kind = Under;
49    else if (element->hasLocalName(MathMLNames::moverTag))
50        m_kind = Over;
51    else if (element->hasLocalName(MathMLNames::munderoverTag))
52        m_kind = UnderOver;
53    else
54        m_kind = Under;
55
56}
57
58void RenderMathMLUnderOver::addChild(RenderObject* child, RenderObject* beforeChild)
59{
60    RenderMathMLBlock* row = new (renderArena()) RenderMathMLBlock(node());
61    RefPtr<RenderStyle> rowStyle = makeBlockStyle();
62    row->setStyle(rowStyle.release());
63
64    // look through the children for rendered elements counting the blocks so we know what child
65    // we are adding
66    int blocks = 0;
67    RenderObject* current = this->firstChild();
68    while (current) {
69        blocks++;
70        current = current->nextSibling();
71    }
72
73    switch (blocks) {
74    case 0:
75        // this is the base so just append it
76        RenderBlock::addChild(row, beforeChild);
77        break;
78    case 1:
79        // the under or over
80        // FIXME: text-align: center does not work
81        row->style()->setTextAlign(CENTER);
82        if (m_kind == Over) {
83            // add the over as first
84            RenderBlock::addChild(row, firstChild());
85        } else {
86            // add the under as last
87            RenderBlock::addChild(row, beforeChild);
88        }
89        break;
90    case 2:
91        // the under or over
92        // FIXME: text-align: center does not work
93        row->style()->setTextAlign(CENTER);
94        if (m_kind == UnderOver) {
95            // add the over as first
96            RenderBlock::addChild(row, firstChild());
97        } else {
98            // we really shouldn't get here as only munderover should have three children
99            RenderBlock::addChild(row, beforeChild);
100        }
101        break;
102    default:
103        // munderover shouldn't have more than three children.  In theory we shouldn't
104        // get here if the MathML is correctly formed, but that isn't a guarantee.
105        // We will treat this as another under element and they'll get something funky.
106        RenderBlock::addChild(row, beforeChild);
107    }
108    row->addChild(child);
109}
110
111inline int getOffsetHeight(RenderObject* obj)
112{
113    if (obj->isBoxModelObject()) {
114        RenderBoxModelObject* box = toRenderBoxModelObject(obj);
115        return box->offsetHeight();
116    }
117
118    return 0;
119}
120
121void RenderMathMLUnderOver::stretchToHeight(int height)
122{
123
124    RenderObject* base = firstChild();
125    if (!base)
126        return;
127
128    // For over or underover, the base is the sibling of the first child
129    if (m_kind != Under)
130        base = base->nextSibling();
131
132    if (!base)
133        return;
134
135    // use the child of the row which is the actual base
136    base = base->firstChild();
137
138    if (base && base->isRenderMathMLBlock()) {
139        RenderMathMLBlock* block = toRenderMathMLBlock(base);
140        block->stretchToHeight(height);
141        setNeedsLayout(true);
142    }
143}
144
145void RenderMathMLUnderOver::layout()
146{
147    RenderBlock::layout();
148    RenderObject* over = 0;
149    RenderObject* base = 0;
150    switch (m_kind) {
151    case Over:
152        // We need to calculate the baseline over the over versus the start of the base and
153        // adjust the placement of the base.
154        over = firstChild();
155        if (over) {
156            // FIXME: descending glyphs intrude into base (e.g. lowercase y over base)
157            // FIXME: bases that ascend higher than the line box intrude into the over
158            if (!over->firstChild()->isBoxModelObject())
159                break;
160
161            int overSpacing = static_cast<int>(gOverSpacingAdjustment * (getOffsetHeight(over) - toRenderBoxModelObject(over->firstChild())->baselinePosition(AlphabeticBaseline, true, HorizontalLine)));
162
163            // base row wrapper
164            base = over->nextSibling();
165            if (base) {
166                if (overSpacing > 0)
167                    base->style()->setMarginTop(Length(-overSpacing, Fixed));
168                else
169                    base->style()->setMarginTop(Length(0, Fixed));
170            }
171
172        }
173        break;
174    case Under:
175        // FIXME: Non-ascending glyphs in the under should be moved closer to the base
176
177        // We need to calculate the baseline of the base versus the start of the under block and
178        // adjust the placement of the under block.
179
180        // base row wrapper
181        base = firstChild();
182        if (base) {
183            int baseHeight = getOffsetHeight(base);
184            // actual base
185            base = base->firstChild();
186            if (!base->isBoxModelObject())
187                break;
188
189            // FIXME: We need to look at the space between a single maximum height of
190            //        the line boxes and the baseline and squeeze them together
191            int underSpacing = baseHeight - toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, true, HorizontalLine);
192
193            // adjust the base's intrusion into the under
194            RenderObject* under = lastChild();
195            if (under && underSpacing > 0)
196                under->style()->setMarginTop(Length(-underSpacing, Fixed));
197        }
198        break;
199    case UnderOver:
200        // FIXME: Non-descending glyphs in the over should be moved closer to the base
201        // FIXME: Non-ascending glyphs in the under should be moved closer to the base
202
203        // We need to calculate the baseline of the over versus the start of the base and
204        // adjust the placement of the base.
205
206        over = firstChild();
207        if (over) {
208            // FIXME: descending glyphs intrude into base (e.g. lowercase y over base)
209            // FIXME: bases that ascend higher than the line box intrude into the over
210            if (!over->firstChild()->isBoxModelObject())
211                break;
212            int overSpacing = static_cast<int>(gOverSpacingAdjustment * (getOffsetHeight(over) - toRenderBoxModelObject(over->firstChild())->baselinePosition(AlphabeticBaseline, true, HorizontalLine)));
213
214            // base row wrapper
215            base = over->nextSibling();
216
217            if (base) {
218                if (overSpacing > 0)
219                    base->style()->setMarginTop(Length(-overSpacing, Fixed));
220
221                // We need to calculate the baseline of the base versus the start of the under block and
222                // adjust the placement of the under block.
223
224                int baseHeight = getOffsetHeight(base);
225                // actual base
226                base = base->firstChild();
227                if (!base->isBoxModelObject())
228                    break;
229
230                // FIXME: We need to look at the space between a single maximum height of
231                //        the line boxes and the baseline and squeeze them together
232                int underSpacing = baseHeight - toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, true, HorizontalLine);
233
234                RenderObject* under = lastChild();
235                if (under && under->firstChild()->isRenderInline() && underSpacing > 0)
236                    under->style()->setMarginTop(Length(-underSpacing, Fixed));
237
238            }
239        }
240        break;
241    }
242    setNeedsLayout(true);
243    RenderBlock::layout();
244}
245
246int RenderMathMLUnderOver::baselinePosition(FontBaseline, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
247{
248    RenderObject* current = firstChild();
249    if (!current)
250        return RenderBlock::baselinePosition(AlphabeticBaseline, firstLine, direction, linePositionMode);
251
252    int baseline = 0;
253    switch (m_kind) {
254    case UnderOver:
255    case Over:
256        baseline += getOffsetHeight(current);
257        current = current->nextSibling();
258        if (current) {
259            // actual base
260            RenderObject* base = current->firstChild();
261            if (!base || !base->isBoxModelObject())
262                break;
263            baseline += toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, firstLine, HorizontalLine, linePositionMode);
264            // added the negative top margin
265            baseline += current->style()->marginTop().value();
266        }
267        break;
268    case Under:
269        RenderObject* base = current->firstChild();
270        if (base && base->isBoxModelObject())
271            baseline += toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, true, HorizontalLine);
272    }
273
274    // FIXME: Where is the extra 2-3px adjusted for zoom coming from?
275    float zoomFactor = style()->effectiveZoom();
276    baseline += static_cast<int>((zoomFactor > 1.25 ? 2 : 3) * zoomFactor);
277    return baseline;
278}
279
280
281int RenderMathMLUnderOver::nonOperatorHeight() const
282{
283    int nonOperators = 0;
284    for (RenderObject* current = firstChild(); current; current = current->nextSibling()) {
285        if (current->firstChild()->isRenderMathMLBlock()) {
286            RenderMathMLBlock* block = toRenderMathMLBlock(current->firstChild());
287            if (!block->isRenderMathMLOperator())
288                nonOperators += getOffsetHeight(current);
289        } else {
290            nonOperators += getOffsetHeight(current);
291        }
292    }
293    return nonOperators;
294}
295
296}
297
298
299#endif // ENABLE(MATHML)
300