MathML internals - use createXXX() function naming, ASSERT()s
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLUnderOver.cpp
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
35 namespace WebCore {
36
37 using namespace MathMLNames;
38     
39 static const double gOverSpacingAdjustment = 0.5;
40     
41 RenderMathMLUnderOver::RenderMathMLUnderOver(Element* element)
42     : RenderMathMLBlock(element)
43 {
44     // Determine what kind of under/over expression we have by element name
45     if (element->hasLocalName(MathMLNames::munderTag))
46         m_kind = Under;
47     else if (element->hasLocalName(MathMLNames::moverTag))
48         m_kind = Over;
49     else {
50         ASSERT(element->hasLocalName(MathMLNames::munderoverTag));
51         m_kind = UnderOver;
52     }
53 }
54
55 void RenderMathMLUnderOver::addChild(RenderObject* child, RenderObject* beforeChild)
56 {    
57     RenderMathMLBlock* row = new (renderArena()) RenderMathMLBlock(node());
58     RefPtr<RenderStyle> rowStyle = createBlockStyle();
59     row->setStyle(rowStyle.release());
60     row->setIsAnonymous(true);
61     
62     // look through the children for rendered elements counting the blocks so we know what child
63     // we are adding
64     int blocks = 0;
65     RenderObject* current = this->firstChild();
66     while (current) {
67         blocks++;
68         current = current->nextSibling();
69     }
70     
71     switch (blocks) {
72     case 0:
73         // this is the base so just append it
74         RenderBlock::addChild(row, beforeChild);
75         break;
76     case 1:
77         // the under or over
78         // FIXME: text-align: center does not work
79         row->style()->setTextAlign(CENTER);
80         if (m_kind == Over) {
81             // add the over as first
82             RenderBlock::addChild(row, firstChild());
83         } else {
84             // add the under as last
85             RenderBlock::addChild(row, beforeChild);
86         }
87         break;
88     case 2:
89         // the under or over
90         // FIXME: text-align: center does not work
91         row->style()->setTextAlign(CENTER);
92         if (m_kind == UnderOver) {
93             // add the over as first
94             RenderBlock::addChild(row, firstChild());
95         } else {
96             // we really shouldn't get here as only munderover should have three children
97             RenderBlock::addChild(row, beforeChild);
98         }
99         break;
100     default:
101         // munderover shouldn't have more than three children. In theory we shouldn't 
102         // get here if the MathML is correctly formed, but that isn't a guarantee.
103         // We will treat this as another under element and they'll get something funky.
104         RenderBlock::addChild(row, beforeChild);
105     }
106     row->addChild(child);    
107 }
108
109 inline int getOffsetHeight(RenderObject* obj) 
110 {
111     if (obj->isBoxModelObject()) {
112         RenderBoxModelObject* box = toRenderBoxModelObject(obj);
113         return box->offsetHeight();
114     }
115    
116     return 0;
117 }
118
119 void RenderMathMLUnderOver::stretchToHeight(int height)
120 {
121
122     RenderObject* base = firstChild();
123     if (!base)
124         return;
125         
126     // For over or underover, the base is the sibling of the first child
127     if (m_kind != Under) 
128         base = base->nextSibling();
129         
130     if (!base)
131         return;
132         
133     // use the child of the row which is the actual base
134     base = base->firstChild();
135     
136     if (base && base->isRenderMathMLBlock()) {
137         RenderMathMLBlock* block = toRenderMathMLBlock(base);
138         block->stretchToHeight(height);
139         setNeedsLayout(true);
140     }
141 }
142
143 void RenderMathMLUnderOver::layout() 
144 {
145     RenderBlock::layout();
146     RenderObject* over = 0;
147     RenderObject* base = 0;
148     switch (m_kind) {
149     case Over:
150         // We need to calculate the baseline over the over versus the start of the base and 
151         // adjust the placement of the base.
152         over = firstChild();
153         if (over) {
154             // FIXME: descending glyphs intrude into base (e.g. lowercase y over base)
155             // FIXME: bases that ascend higher than the line box intrude into the over
156             if (!over->firstChild() || !over->firstChild()->isBoxModelObject())
157                 break;
158             
159             LayoutUnit overSpacing = static_cast<LayoutUnit>(gOverSpacingAdjustment * (getOffsetHeight(over) - toRenderBoxModelObject(over->firstChild())->baselinePosition(AlphabeticBaseline, true, HorizontalLine)));
160             
161             // base row wrapper
162             base = over->nextSibling();
163             if (base) {
164                 if (overSpacing > 0) 
165                     base->style()->setMarginTop(Length(-overSpacing, Fixed));
166                 else 
167                     base->style()->setMarginTop(Length(0, Fixed));
168             }
169             
170         }
171         break;
172     case Under:
173         // FIXME: Non-ascending glyphs in the under should be moved closer to the base
174
175         // We need to calculate the baseline of the base versus the start of the under block and
176         // adjust the placement of the under block.
177         
178         // base row wrapper
179         base = firstChild();
180         if (base) {
181             LayoutUnit baseHeight = getOffsetHeight(base);
182             // actual base
183             base = base->firstChild();
184             if (!base || !base->isBoxModelObject())
185                 break;
186             
187             // FIXME: We need to look at the space between a single maximum height of
188             //        the line boxes and the baseline and squeeze them together
189             LayoutUnit underSpacing = baseHeight - toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, true, HorizontalLine);
190             
191             // adjust the base's intrusion into the under
192             RenderObject* under = lastChild();
193             if (under && underSpacing > 0)
194                 under->style()->setMarginTop(Length(-underSpacing, Fixed));
195         }
196         break;
197     case UnderOver:
198         // FIXME: Non-descending glyphs in the over should be moved closer to the base
199         // FIXME: Non-ascending glyphs in the under should be moved closer to the base
200         
201         // We need to calculate the baseline of the over versus the start of the base and 
202         // adjust the placement of the base.
203         
204         over = firstChild();
205         if (over) {
206             // FIXME: descending glyphs intrude into base (e.g. lowercase y over base)
207             // FIXME: bases that ascend higher than the line box intrude into the over
208             if (!over->firstChild() || !over->firstChild()->isBoxModelObject())
209                 break;
210             LayoutUnit overSpacing = static_cast<LayoutUnit>(gOverSpacingAdjustment * (getOffsetHeight(over) - toRenderBoxModelObject(over->firstChild())->baselinePosition(AlphabeticBaseline, true, HorizontalLine)));
211             
212             // base row wrapper
213             base = over->nextSibling();
214             
215             if (base) {
216                 if (overSpacing > 0)
217                     base->style()->setMarginTop(Length(-overSpacing, Fixed));
218                 
219                 // We need to calculate the baseline of the base versus the start of the under block and
220                 // adjust the placement of the under block.
221                 
222                 LayoutUnit baseHeight = getOffsetHeight(base);
223                 // actual base
224                 base = base->firstChild();
225                 if (!base || !base->isBoxModelObject())
226                     break;
227
228                 // FIXME: We need to look at the space between a single maximum height of
229                 //        the line boxes and the baseline and squeeze them together
230                 LayoutUnit underSpacing = baseHeight - toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, true, HorizontalLine);
231                 
232                 RenderObject* under = lastChild();
233                 if (under && under->firstChild() && under->firstChild()->isRenderInline() && underSpacing > 0)
234                     under->style()->setMarginTop(Length(-underSpacing, Fixed));
235                 
236             }
237         }
238         break;
239     }
240     setNeedsLayout(true);
241     RenderBlock::layout();
242 }
243
244 LayoutUnit RenderMathMLUnderOver::baselinePosition(FontBaseline, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
245 {
246     RenderObject* current = firstChild();
247     if (!current || linePositionMode == PositionOfInteriorLineBoxes)
248         return RenderBlock::baselinePosition(AlphabeticBaseline, firstLine, direction, linePositionMode);
249
250     LayoutUnit baseline = 0;
251     switch (m_kind) {
252     case UnderOver:
253     case Over:
254         baseline += getOffsetHeight(current);
255         current = current->nextSibling();
256         if (current) {
257             // actual base
258             RenderObject* base = current->firstChild();
259             if (!base || !base->isBoxModelObject())
260                 break;
261             baseline += toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, firstLine, HorizontalLine, linePositionMode);
262             // added the negative top margin
263             baseline += current->style()->marginTop().value();
264         }
265         break;
266     case Under:
267         RenderObject* base = current->firstChild();
268         if (base && base->isBoxModelObject())
269             baseline += toRenderBoxModelObject(base)->baselinePosition(AlphabeticBaseline, true, HorizontalLine);
270     }
271
272     return baseline;
273 }
274
275
276 int RenderMathMLUnderOver::nonOperatorHeight() const 
277 {
278     int nonOperators = 0;
279     for (RenderObject* current = firstChild(); current; current = current->nextSibling()) {
280         if (current->firstChild() && current->firstChild()->isRenderMathMLBlock()) {
281             RenderMathMLBlock* block = toRenderMathMLBlock(current->firstChild());
282             if (!block->isRenderMathMLOperator()) 
283                 nonOperators += getOffsetHeight(current);
284         } else
285             nonOperators += getOffsetHeight(current);
286     }
287     return nonOperators;
288 }
289
290 }
291
292 #endif // ENABLE(MATHML)