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