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
RenderMathMLUnderOver(Node * expression)41 RenderMathMLUnderOver::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
addChild(RenderObject * child,RenderObject * beforeChild)58 void 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
getOffsetHeight(RenderObject * obj)111 inline 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
stretchToHeight(int height)121 void 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
layout()145 void 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
baselinePosition(FontBaseline,bool firstLine,LineDirectionMode direction,LinePositionMode linePositionMode) const246 int 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
nonOperatorHeight() const281 int 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