Optimize hasTagName when called on an HTMLElement
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLScripts.cpp
1 /*
2  * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved.
3  * Copyright (C) 2013 The MathJax Consortium.
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 "RenderMathMLScripts.h"
32
33 #include "MathMLElement.h"
34
35 namespace WebCore {
36     
37 using namespace MathMLNames;
38
39 // RenderMathMLScripts implements various MathML elements drawing scripts attached to a base. For valid MathML elements, the structure of the render tree should be:
40 //
41 // - msub, msup, msubsup: BaseWrapper SubSupPairWrapper
42 // - mmultiscripts: BaseWrapper SubSupPairWrapper* (mprescripts SubSupPairWrapper*)*
43 //
44 // where BaseWrapper and SubSupPairWrapper do not contain any <mprescripts/> children. In addition, BaseWrapper must have one child and SubSupPairWrapper must have either one child (msub, msup) or two children (msubsup, mmultiscripts).
45 //
46 // In order to accept invalid markup and to handle the script elements consistently and uniformly, we will use a more general structure that encompasses both valid and invalid elements:
47 //
48 // BaseWrapper SubSupPairWrapper* (mprescripts SubSupPairWrapper*)*
49 //
50 // where BaseWrapper can now be empty and SubSupPairWrapper can now have one or two elements.
51 //
52
53 static bool isPrescript(RenderObject* renderObject)
54 {
55     ASSERT(renderObject);
56     return renderObject->node() && renderObject->node()->hasTagName(MathMLNames::mprescriptsTag);
57 }
58
59 RenderMathMLScripts::RenderMathMLScripts(Element& element, PassRef<RenderStyle> style)
60     : RenderMathMLBlock(element, std::move(style))
61     , m_baseWrapper(0)
62 {
63     // Determine what kind of sub/sup expression we have by element name
64     if (element.hasTagName(MathMLNames::msubTag))
65         m_kind = Sub;
66     else if (element.hasTagName(MathMLNames::msupTag))
67         m_kind = Super;
68     else if (element.hasTagName(MathMLNames::msubsupTag))
69         m_kind = SubSup;
70     else {
71         ASSERT(element.hasTagName(MathMLNames::mmultiscriptsTag));
72         m_kind = Multiscripts;
73     }
74 }
75
76 RenderBoxModelObject* RenderMathMLScripts::base() const
77 {
78     if (!m_baseWrapper)
79         return 0;
80     RenderObject* base = m_baseWrapper->firstChild();
81     if (!base || !base->isBoxModelObject())
82         return 0;
83     return toRenderBoxModelObject(base);
84 }
85
86 void RenderMathMLScripts::fixAnonymousStyleForSubSupPair(RenderObject* subSupPair, bool isPostScript)
87 {
88     ASSERT(subSupPair && subSupPair->style().refCount() == 1);
89     RenderStyle& scriptsStyle = subSupPair->style();
90
91     // subSup pairs are drawn in column from bottom (subscript) to top (superscript).
92     scriptsStyle.setFlexDirection(FlowColumnReverse);
93
94     // The MathML specification does not specify horizontal alignment of
95     // scripts. We align the bottom (respectively top) edge of the subscript
96     // (respectively superscript) with the bottom (respectively top) edge of
97     // the flex container. Note that for valid <msub> and <msup> elements, the
98     // subSupPair should actually have only one script.
99     scriptsStyle.setJustifyContent(m_kind == Sub ? JustifyFlexStart : m_kind == Super ? JustifyFlexEnd : JustifySpaceBetween);
100
101     // The MathML specification does not specify vertical alignment of scripts.
102     // Let's right align prescripts and left align postscripts.
103     // See http://lists.w3.org/Archives/Public/www-math/2012Aug/0006.html
104     scriptsStyle.setAlignItems(isPostScript ? AlignFlexStart : AlignFlexEnd);
105
106     // We set the order property so that the prescripts are drawn before the base.
107     scriptsStyle.setOrder(isPostScript ? 0 : -1);
108
109     // We set this wrapper's font-size for its line-height.
110     LayoutUnit scriptSize = static_cast<int>(0.75 * style().fontSize());
111     scriptsStyle.setFontSize(scriptSize);
112 }
113
114 void RenderMathMLScripts::fixAnonymousStyles()
115 {
116     // We set the base wrapper's style so that baseHeight in layout() will be an unstretched height.
117     ASSERT(m_baseWrapper && m_baseWrapper->style().hasOneRef());
118     m_baseWrapper->style().setAlignSelf(AlignFlexStart);
119
120     // This sets the style for postscript pairs.
121     RenderObject* subSupPair = m_baseWrapper;
122     for (subSupPair = subSupPair->nextSibling(); subSupPair && !isPrescript(subSupPair); subSupPair = subSupPair->nextSibling())
123         fixAnonymousStyleForSubSupPair(subSupPair, true);
124
125     if (subSupPair && m_kind == Multiscripts) {
126         // This sets the style for prescript pairs.
127         for (subSupPair = subSupPair->nextSibling(); subSupPair && !isPrescript(subSupPair); subSupPair = subSupPair->nextSibling())
128             fixAnonymousStyleForSubSupPair(subSupPair, false);
129     }
130
131     // This resets style for extra subSup pairs.
132     for (; subSupPair; subSupPair = subSupPair->nextSibling()) {
133         if (!isPrescript(subSupPair)) {
134             ASSERT(subSupPair && subSupPair->style().refCount() == 1);
135             RenderStyle& scriptsStyle = subSupPair->style();
136             scriptsStyle.setFlexDirection(FlowRow);
137             scriptsStyle.setJustifyContent(JustifyFlexStart);
138             scriptsStyle.setAlignItems(AlignCenter);
139             scriptsStyle.setOrder(0);
140             scriptsStyle.setFontSize(style().fontSize());
141         }
142     }
143 }
144
145 void RenderMathMLScripts::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild)
146 {
147     if (doNotRestructure) {
148         RenderMathMLBlock::addChild(child, beforeChild);
149         return;
150     }
151
152     if (beforeChild) {
153         // beforeChild may be a grandchild, so we call the addChild function of the corresponding wrapper instead.
154         RenderObject* parent = beforeChild->parent();
155         if (parent != this) {
156             RenderMathMLBlock* parentBlock = toRenderMathMLBlock(parent);
157             if (parentBlock->isRenderMathMLScriptsWrapper()) {
158                 RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(parentBlock);
159                 wrapper->addChildInternal(false, child, beforeChild);
160                 return;
161             }
162         }
163     }
164
165     if (beforeChild == m_baseWrapper) {
166         // This is like inserting the child at the beginning of the base wrapper.
167         m_baseWrapper->addChildInternal(false, child, m_baseWrapper->firstChild());
168         return;
169     }
170     
171     if (isPrescript(child)) {
172         // The new child becomes an <mprescripts/> separator.
173         RenderMathMLBlock::addChild(child, beforeChild);
174         return;
175     }
176
177     if (!beforeChild || isPrescript(beforeChild)) {
178         // We are at the end of a sequence of subSup pairs.
179         RenderMathMLBlock* previousSibling = toRenderMathMLBlock(beforeChild ? beforeChild->previousSibling() : lastChild());
180         if (previousSibling && previousSibling->isRenderMathMLScriptsWrapper()) {
181             RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(previousSibling);
182             if ((wrapper->m_kind == RenderMathMLScriptsWrapper::Base && wrapper->isEmpty()) || (wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !wrapper->firstChild()->nextSibling())) {
183                 // The previous sibling is either an empty base or a SubSup pair with a single child so we can insert the new child into that wrapper.
184                 wrapper->addChildInternal(true, child);
185                 return;
186             }
187         }
188         // Otherwise we create a new subSupPair to store the new child.
189         RenderMathMLScriptsWrapper* subSupPair = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::SubSupPair);
190         subSupPair->addChildInternal(true, child);
191         RenderMathMLBlock::addChild(subSupPair, beforeChild);
192         return;
193     }
194
195     // beforeChild is a subSup pair. This is like inserting the new child at the beginning of the subSup wrapper.
196     RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(beforeChild);
197     ASSERT(wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair);
198     ASSERT(!(m_baseWrapper->isEmpty() && m_baseWrapper->nextSibling() == beforeChild));
199     wrapper->addChildInternal(false, child, wrapper->firstChild());
200 }
201
202 void RenderMathMLScripts::removeChildInternal(bool doNotRestructure, RenderObject& child)
203 {
204     if (doNotRestructure) {
205         RenderMathMLBlock::removeChild(child);
206         return;
207     }
208
209     ASSERT(isPrescript(&child));
210
211     RenderObject* previousSibling = child.previousSibling();
212     RenderObject* nextSibling = child.nextSibling();
213     ASSERT(previousSibling);
214
215     if (nextSibling && !isPrescript(previousSibling) && !isPrescript(nextSibling)) {
216         RenderMathMLScriptsWrapper* previousWrapper = toRenderMathMLScriptsWrapper(previousSibling);
217         RenderMathMLScriptsWrapper* nextWrapper = toRenderMathMLScriptsWrapper(nextSibling);
218         ASSERT(nextWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !nextWrapper->isEmpty());
219         if ((previousWrapper->m_kind == RenderMathMLScriptsWrapper::Base && previousWrapper->isEmpty()) || (previousWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !previousWrapper->firstChild()->nextSibling())) {
220             RenderObject* script = nextWrapper->firstChild();
221             nextWrapper->removeChildInternal(false, *script);
222             previousWrapper->addChildInternal(true, script);
223         }
224     }
225
226     RenderMathMLBlock::removeChild(child);
227 }
228
229 void RenderMathMLScripts::addChild(RenderObject* child, RenderObject* beforeChild)
230 {
231     if (isEmpty()) {
232         m_baseWrapper = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::Base);
233         RenderMathMLBlock::addChild(m_baseWrapper);
234     }
235
236     addChildInternal(false, child, beforeChild);
237
238     fixAnonymousStyles();
239 }
240
241 void RenderMathMLScripts::removeChild(RenderObject& child)
242 {
243     if (beingDestroyed() || documentBeingDestroyed()) {
244         // The renderer is being destroyed so we remove the child normally.
245         RenderMathMLBlock::removeChild(child);
246         return;
247     }
248
249     removeChildInternal(false, child);
250     
251     fixAnonymousStyles();
252 }
253
254 void RenderMathMLScripts::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
255 {
256     RenderMathMLBlock::styleDidChange(diff, oldStyle);
257     
258     if (!isEmpty())
259         fixAnonymousStyles();
260 }
261
262 RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator()
263 {
264     RenderBoxModelObject* base = this->base();
265     if (!base || !base->isRenderMathMLBlock())
266         return 0;
267     return toRenderMathMLBlock(base)->unembellishedOperator();
268 }
269
270 void RenderMathMLScripts::layout()
271 {
272     RenderMathMLBlock::layout();
273
274     if (!m_baseWrapper)
275         return;
276     RenderBox* base = m_baseWrapper->firstChildBox();
277     if (!base)
278         return;
279
280     // Our layout rules include: Don't let the superscript go below the "axis" (half x-height above the
281     // baseline), or the subscript above the axis. Also, don't let the superscript's top edge be
282     // below the base's top edge, or the subscript's bottom edge above the base's bottom edge.
283
284     LayoutUnit baseHeight = base->logicalHeight();
285     LayoutUnit baseBaseline = base->firstLineBaseline();
286     if (baseBaseline == -1)
287         baseBaseline = baseHeight;
288     LayoutUnit axis = style().fontMetrics().xHeight() / 2;
289     int fontSize = style().fontSize();
290
291     ASSERT(m_baseWrapper->style().hasOneRef());
292     bool needsSecondLayout = false;
293
294     LayoutUnit topPadding = 0;
295     LayoutUnit bottomPadding = 0;
296
297     Element* scriptElement = element();
298     LayoutUnit superscriptShiftValue = 0;
299     LayoutUnit subscriptShiftValue = 0;
300     if (m_kind == Sub || m_kind == SubSup || m_kind == Multiscripts)
301         parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::subscriptshiftAttr), subscriptShiftValue, &style(), false);
302     if (m_kind == Super || m_kind == SubSup || m_kind == Multiscripts)
303         parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::superscriptshiftAttr), superscriptShiftValue, &style(), false);
304
305     bool isPostScript = true;
306     RenderMathMLBlock* subSupPair = toRenderMathMLBlock(m_baseWrapper->nextSibling());
307     for (; subSupPair; subSupPair = toRenderMathMLBlock(subSupPair->nextSibling())) {
308
309         // We skip the base and <mprescripts/> elements.
310         if (isPrescript(subSupPair)) {
311             if (!isPostScript)
312                 break;
313             isPostScript = false;
314             continue;
315         }
316
317         if (RenderBox* superscript = m_kind == Sub ? 0 : subSupPair->lastChildBox()) {
318             LayoutUnit superscriptHeight = superscript->logicalHeight();
319             LayoutUnit superscriptBaseline = superscript->firstLineBaseline();
320             if (superscriptBaseline == -1)
321                 superscriptBaseline = superscriptHeight;
322             LayoutUnit minBaseline = std::max<LayoutUnit>(fontSize / 3 + 1 + superscriptBaseline, superscriptHeight + axis + superscriptShiftValue);
323
324             topPadding = std::max<LayoutUnit>(topPadding, minBaseline - baseBaseline);
325         }
326
327         if (RenderBox* subscript = m_kind == Super ? 0 : subSupPair->firstChildBox()) {
328             LayoutUnit subscriptHeight = subscript->logicalHeight();
329             LayoutUnit subscriptBaseline = subscript->firstLineBaseline();
330             if (subscriptBaseline == -1)
331                 subscriptBaseline = subscriptHeight;
332             LayoutUnit baseExtendUnderBaseline = baseHeight - baseBaseline;
333             LayoutUnit subscriptUnderItsBaseline = subscriptHeight - subscriptBaseline;
334             LayoutUnit minExtendUnderBaseline = std::max<LayoutUnit>(fontSize / 5 + 1 + subscriptUnderItsBaseline, subscriptHeight + subscriptShiftValue - axis);
335
336             bottomPadding = std::max<LayoutUnit>(bottomPadding, minExtendUnderBaseline - baseExtendUnderBaseline);
337         }
338     }
339
340     Length newPadding(topPadding, Fixed);
341     if (newPadding != m_baseWrapper->style().paddingTop()) {
342         m_baseWrapper->style().setPaddingTop(newPadding);
343         needsSecondLayout = true;
344     }
345
346     newPadding = Length(bottomPadding, Fixed);
347     if (newPadding != m_baseWrapper->style().paddingBottom()) {
348         m_baseWrapper->style().setPaddingBottom(newPadding);
349         needsSecondLayout = true;
350     }
351
352     if (!needsSecondLayout)
353         return;
354
355     setNeedsLayout(MarkOnlyThis);
356     m_baseWrapper->setChildNeedsLayout(MarkOnlyThis);
357
358     RenderMathMLBlock::layout();
359 }
360
361 int RenderMathMLScripts::firstLineBaseline() const
362 {
363     if (m_baseWrapper) {
364         LayoutUnit baseline = m_baseWrapper->firstLineBaseline();
365         if (baseline != -1)
366             return baseline;
367     }
368     return RenderMathMLBlock::firstLineBaseline();
369 }
370
371 RenderMathMLScriptsWrapper* RenderMathMLScriptsWrapper::createAnonymousWrapper(RenderMathMLScripts* renderObject, WrapperType type)
372 {
373     RenderMathMLScriptsWrapper* newBlock = new RenderMathMLScriptsWrapper(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX), type);
374     newBlock->initializeStyle();
375     return newBlock;
376 }
377
378 void RenderMathMLScriptsWrapper::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild)
379 {
380     if (doNotRestructure) {
381         RenderMathMLBlock::addChild(child, beforeChild);
382         return;
383     }
384
385     RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
386
387     if (m_kind == Base) {
388         RenderObject* sibling = nextSibling();
389
390         if (!isEmpty() && !beforeChild) {
391             // This is like inserting the child after the base wrapper.
392             parentNode->addChildInternal(false, sibling);
393             return;
394         }
395
396         // The old base (if any) becomes a script ; the new child becomes either the base or an <mprescripts> separator.
397         RenderObject* oldBase = firstChild();
398         if (oldBase)
399             RenderMathMLBlock::removeChild(*oldBase);
400         if (isPrescript(child))
401             parentNode->addChildInternal(true, child, sibling);
402         else
403             RenderMathMLBlock::addChild(child);
404         if (oldBase)
405             parentNode->addChildInternal(false, oldBase, sibling);
406         return;
407     }
408
409     if (isPrescript(child)) {
410         // We insert an <mprescripts> element.
411         if (!beforeChild)
412             parentNode->addChildInternal(true, child, nextSibling());
413         else if (beforeChild == firstChild())
414             parentNode->addChildInternal(true, child, this);
415         else {
416             // We insert the <mprescripts> in the middle of a subSup pair so we must split that pair.
417             RenderObject* sibling = nextSibling();
418             parentNode->removeChildInternal(true, *this);
419             parentNode->addChildInternal(true, child, sibling);
420
421             RenderObject* script = firstChild();
422             RenderMathMLBlock::removeChild(*script);
423             parentNode->addChildInternal(false, script, child);
424
425             script = beforeChild;
426             RenderMathMLBlock::removeChild(*script);
427             parentNode->addChildInternal(false, script, sibling);
428             destroy();
429         }
430         return;
431     }
432
433     // We first move to the last subSup pair in the curent sequence of scripts.
434     RenderMathMLScriptsWrapper* subSupPair = this;
435     while (subSupPair->nextSibling() && !isPrescript(subSupPair->nextSibling()))
436         subSupPair = toRenderMathMLScriptsWrapper(subSupPair->nextSibling());
437     if (subSupPair->firstChild()->nextSibling()) {
438         // The last pair has two children so we need to create a new pair to leave room for the new child.
439         RenderMathMLScriptsWrapper* newPair = createAnonymousWrapper(parentNode, RenderMathMLScriptsWrapper::SubSupPair);
440         parentNode->addChildInternal(true, newPair, subSupPair->nextSibling());
441         subSupPair = newPair;
442     }
443
444     // We shift the successors in the current sequence of scripts.
445     for (RenderObject* previousSibling = subSupPair->previousSibling(); subSupPair != this; previousSibling = previousSibling->previousSibling()) {
446         RenderMathMLScriptsWrapper* previousSubSupPair = toRenderMathMLScriptsWrapper(previousSibling);
447         RenderObject* script = previousSubSupPair->lastChild();
448         previousSubSupPair->removeChildInternal(true, *script);
449         subSupPair->addChildInternal(true, script, subSupPair->firstChild());
450         subSupPair = toRenderMathMLScriptsWrapper(previousSibling);
451     }
452
453     // This subSup pair now contain one element which is either beforeChild or the script that was before. Hence we can insert the new child before of after that element.
454     RenderMathMLBlock::addChild(child, firstChild() == beforeChild ? beforeChild : 0);
455 }
456
457 void RenderMathMLScriptsWrapper::addChild(RenderObject* child, RenderObject* beforeChild)
458 {
459     RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
460
461     addChildInternal(false, child, beforeChild);
462
463     parentNode->fixAnonymousStyles();
464 }
465
466 void RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject& child)
467 {
468     if (doNotRestructure) {
469         RenderMathMLBlock::removeChild(child);
470         return;
471     }
472
473     RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
474
475     if (m_kind == Base) {
476         // We remove the child from the base wrapper.
477         RenderObject* sibling = nextSibling();
478         RenderMathMLBlock::removeChild(child);
479         if (sibling && !isPrescript(sibling)) {
480             // If there are postscripts, the first one becomes the base.
481             RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(sibling);
482             RenderObject* script = wrapper->firstChild();
483             wrapper->removeChildInternal(false, *script);
484             RenderMathMLBlock::addChild(script);
485         }
486         return;
487     }
488
489     // We remove the child and shift the successors in the current sequence of scripts.
490     RenderMathMLBlock::removeChild(child);
491     RenderMathMLScriptsWrapper* subSupPair = this;
492     for (RenderObject* nextSibling = subSupPair->nextSibling(); nextSibling && !isPrescript(nextSibling); nextSibling = nextSibling->nextSibling()) {
493         RenderMathMLScriptsWrapper* nextSubSupPair = toRenderMathMLScriptsWrapper(nextSibling);
494         RenderObject* script = nextSubSupPair->firstChild();
495         nextSubSupPair->removeChildInternal(true, *script);
496         subSupPair->addChildInternal(true, script);
497         subSupPair = toRenderMathMLScriptsWrapper(nextSibling);
498     }
499
500     // We remove the last subSup pair if it became empty.
501     if (subSupPair->isEmpty()) {
502         parentNode->removeChildInternal(true, *subSupPair);
503         subSupPair->destroy();
504     }
505 }
506
507 void RenderMathMLScriptsWrapper::removeChild(RenderObject& child)
508 {
509     if (beingDestroyed() || documentBeingDestroyed()) {
510         // The renderer is being destroyed so we remove the child normally.
511         RenderMathMLBlock::removeChild(child);
512         return;
513     }
514
515     RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
516     removeChildInternal(false, child);
517     parentNode->fixAnonymousStyles();
518 }
519
520 }    
521
522 #endif // ENABLE(MATHML)