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 35namespace WebCore { 36 37using 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 53static bool isPrescript(RenderObject* renderObject) 54{ 55 ASSERT(renderObject); 56 return renderObject->node() && renderObject->node()->hasTagName(MathMLNames::mprescriptsTag); 57} 58 59RenderMathMLScripts::RenderMathMLScripts(Element& element, PassRef<RenderStyle> style) 60 : RenderMathMLBlock(element, WTF::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 76RenderBoxModelObject* 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 86void 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 114void 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 145void 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 202RenderObject* RenderMathMLScripts::removeChildInternal(bool doNotRestructure, RenderObject& child) 203{ 204 if (doNotRestructure) 205 return RenderMathMLBlock::removeChild(child); 206 207 ASSERT(isPrescript(&child)); 208 209 RenderObject* previousSibling = child.previousSibling(); 210 RenderObject* nextSibling = child.nextSibling(); 211 ASSERT(previousSibling); 212 213 if (nextSibling && !isPrescript(previousSibling) && !isPrescript(nextSibling)) { 214 RenderMathMLScriptsWrapper* previousWrapper = toRenderMathMLScriptsWrapper(previousSibling); 215 RenderMathMLScriptsWrapper* nextWrapper = toRenderMathMLScriptsWrapper(nextSibling); 216 ASSERT(nextWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !nextWrapper->isEmpty()); 217 if ((previousWrapper->m_kind == RenderMathMLScriptsWrapper::Base && previousWrapper->isEmpty()) || (previousWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !previousWrapper->firstChild()->nextSibling())) { 218 RenderObject* script = nextWrapper->firstChild(); 219 nextWrapper->removeChildInternal(false, *script); 220 previousWrapper->addChildInternal(true, script); 221 } 222 } 223 224 return RenderMathMLBlock::removeChild(child); 225} 226 227void RenderMathMLScripts::addChild(RenderObject* child, RenderObject* beforeChild) 228{ 229 if (isEmpty()) { 230 m_baseWrapper = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::Base); 231 RenderMathMLBlock::addChild(m_baseWrapper); 232 } 233 234 addChildInternal(false, child, beforeChild); 235 236 fixAnonymousStyles(); 237} 238 239RenderObject* RenderMathMLScripts::removeChild(RenderObject& child) 240{ 241 if (beingDestroyed() || documentBeingDestroyed()) { 242 // The renderer is being destroyed so we remove the child normally. 243 return RenderMathMLBlock::removeChild(child); 244 } 245 246 RenderObject* next = removeChildInternal(false, child); 247 248 fixAnonymousStyles(); 249 250 return next; 251} 252 253void RenderMathMLScripts::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 254{ 255 RenderMathMLBlock::styleDidChange(diff, oldStyle); 256 257 if (!isEmpty()) 258 fixAnonymousStyles(); 259} 260 261RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator() 262{ 263 RenderBoxModelObject* base = this->base(); 264 if (!base || !base->isRenderMathMLBlock()) 265 return 0; 266 return toRenderMathMLBlock(base)->unembellishedOperator(); 267} 268 269void RenderMathMLScripts::layout() 270{ 271 RenderMathMLBlock::layout(); 272 273 if (!m_baseWrapper) 274 return; 275 RenderBox* base = m_baseWrapper->firstChildBox(); 276 if (!base) 277 return; 278 279 // Our layout rules include: Don't let the superscript go below the "axis" (half x-height above the 280 // baseline), or the subscript above the axis. Also, don't let the superscript's top edge be 281 // below the base's top edge, or the subscript's bottom edge above the base's bottom edge. 282 283 LayoutUnit baseHeight = base->logicalHeight(); 284 LayoutUnit baseBaseline = base->firstLineBaseline(); 285 if (baseBaseline == -1) 286 baseBaseline = baseHeight; 287 LayoutUnit axis = style().fontMetrics().xHeight() / 2; 288 int fontSize = style().fontSize(); 289 290 ASSERT(m_baseWrapper->style().hasOneRef()); 291 bool needsSecondLayout = false; 292 293 LayoutUnit topPadding = 0; 294 LayoutUnit bottomPadding = 0; 295 296 Element* scriptElement = element(); 297 LayoutUnit superscriptShiftValue = 0; 298 LayoutUnit subscriptShiftValue = 0; 299 if (m_kind == Sub || m_kind == SubSup || m_kind == Multiscripts) 300 parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::subscriptshiftAttr), subscriptShiftValue, &style(), false); 301 if (m_kind == Super || m_kind == SubSup || m_kind == Multiscripts) 302 parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::superscriptshiftAttr), superscriptShiftValue, &style(), false); 303 304 bool isPostScript = true; 305 RenderMathMLBlock* subSupPair = toRenderMathMLBlock(m_baseWrapper->nextSibling()); 306 for (; subSupPair; subSupPair = toRenderMathMLBlock(subSupPair->nextSibling())) { 307 308 // We skip the base and <mprescripts/> elements. 309 if (isPrescript(subSupPair)) { 310 if (!isPostScript) 311 break; 312 isPostScript = false; 313 continue; 314 } 315 316 if (RenderBox* superscript = m_kind == Sub ? 0 : subSupPair->lastChildBox()) { 317 LayoutUnit superscriptHeight = superscript->logicalHeight(); 318 LayoutUnit superscriptBaseline = superscript->firstLineBaseline(); 319 if (superscriptBaseline == -1) 320 superscriptBaseline = superscriptHeight; 321 LayoutUnit minBaseline = std::max<LayoutUnit>(fontSize / 3 + 1 + superscriptBaseline, superscriptHeight + axis + superscriptShiftValue); 322 323 topPadding = std::max<LayoutUnit>(topPadding, minBaseline - baseBaseline); 324 } 325 326 if (RenderBox* subscript = m_kind == Super ? 0 : subSupPair->firstChildBox()) { 327 LayoutUnit subscriptHeight = subscript->logicalHeight(); 328 LayoutUnit subscriptBaseline = subscript->firstLineBaseline(); 329 if (subscriptBaseline == -1) 330 subscriptBaseline = subscriptHeight; 331 LayoutUnit baseExtendUnderBaseline = baseHeight - baseBaseline; 332 LayoutUnit subscriptUnderItsBaseline = subscriptHeight - subscriptBaseline; 333 LayoutUnit minExtendUnderBaseline = std::max<LayoutUnit>(fontSize / 5 + 1 + subscriptUnderItsBaseline, subscriptHeight + subscriptShiftValue - axis); 334 335 bottomPadding = std::max<LayoutUnit>(bottomPadding, minExtendUnderBaseline - baseExtendUnderBaseline); 336 } 337 } 338 339 Length newPadding(topPadding, Fixed); 340 if (newPadding != m_baseWrapper->style().paddingTop()) { 341 m_baseWrapper->style().setPaddingTop(newPadding); 342 needsSecondLayout = true; 343 } 344 345 newPadding = Length(bottomPadding, Fixed); 346 if (newPadding != m_baseWrapper->style().paddingBottom()) { 347 m_baseWrapper->style().setPaddingBottom(newPadding); 348 needsSecondLayout = true; 349 } 350 351 if (!needsSecondLayout) 352 return; 353 354 setNeedsLayout(MarkOnlyThis); 355 m_baseWrapper->setChildNeedsLayout(MarkOnlyThis); 356 357 RenderMathMLBlock::layout(); 358} 359 360int RenderMathMLScripts::firstLineBaseline() const 361{ 362 if (m_baseWrapper) { 363 LayoutUnit baseline = m_baseWrapper->firstLineBaseline(); 364 if (baseline != -1) 365 return baseline; 366 } 367 return RenderMathMLBlock::firstLineBaseline(); 368} 369 370RenderMathMLScriptsWrapper* RenderMathMLScriptsWrapper::createAnonymousWrapper(RenderMathMLScripts* renderObject, WrapperType type) 371{ 372 RenderMathMLScriptsWrapper* newBlock = new RenderMathMLScriptsWrapper(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX), type); 373 newBlock->initializeStyle(); 374 return newBlock; 375} 376 377void RenderMathMLScriptsWrapper::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild) 378{ 379 if (doNotRestructure) { 380 RenderMathMLBlock::addChild(child, beforeChild); 381 return; 382 } 383 384 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); 385 386 if (m_kind == Base) { 387 RenderObject* sibling = nextSibling(); 388 389 if (!isEmpty() && !beforeChild) { 390 // This is like inserting the child after the base wrapper. 391 parentNode->addChildInternal(false, sibling); 392 return; 393 } 394 395 // The old base (if any) becomes a script ; the new child becomes either the base or an <mprescripts> separator. 396 RenderObject* oldBase = firstChild(); 397 if (oldBase) 398 RenderMathMLBlock::removeChild(*oldBase); 399 if (isPrescript(child)) 400 parentNode->addChildInternal(true, child, sibling); 401 else 402 RenderMathMLBlock::addChild(child); 403 if (oldBase) 404 parentNode->addChildInternal(false, oldBase, sibling); 405 return; 406 } 407 408 if (isPrescript(child)) { 409 // We insert an <mprescripts> element. 410 if (!beforeChild) 411 parentNode->addChildInternal(true, child, nextSibling()); 412 else if (beforeChild == firstChild()) 413 parentNode->addChildInternal(true, child, this); 414 else { 415 // We insert the <mprescripts> in the middle of a subSup pair so we must split that pair. 416 RenderObject* sibling = nextSibling(); 417 parentNode->removeChildInternal(true, *this); 418 parentNode->addChildInternal(true, child, sibling); 419 420 RenderObject* script = firstChild(); 421 RenderMathMLBlock::removeChild(*script); 422 parentNode->addChildInternal(false, script, child); 423 424 script = beforeChild; 425 RenderMathMLBlock::removeChild(*script); 426 parentNode->addChildInternal(false, script, sibling); 427 destroy(); 428 } 429 return; 430 } 431 432 // We first move to the last subSup pair in the curent sequence of scripts. 433 RenderMathMLScriptsWrapper* subSupPair = this; 434 while (subSupPair->nextSibling() && !isPrescript(subSupPair->nextSibling())) 435 subSupPair = toRenderMathMLScriptsWrapper(subSupPair->nextSibling()); 436 if (subSupPair->firstChild()->nextSibling()) { 437 // The last pair has two children so we need to create a new pair to leave room for the new child. 438 RenderMathMLScriptsWrapper* newPair = createAnonymousWrapper(parentNode, RenderMathMLScriptsWrapper::SubSupPair); 439 parentNode->addChildInternal(true, newPair, subSupPair->nextSibling()); 440 subSupPair = newPair; 441 } 442 443 // We shift the successors in the current sequence of scripts. 444 for (RenderObject* previousSibling = subSupPair->previousSibling(); subSupPair != this; previousSibling = previousSibling->previousSibling()) { 445 RenderMathMLScriptsWrapper* previousSubSupPair = toRenderMathMLScriptsWrapper(previousSibling); 446 RenderObject* script = previousSubSupPair->lastChild(); 447 previousSubSupPair->removeChildInternal(true, *script); 448 subSupPair->addChildInternal(true, script, subSupPair->firstChild()); 449 subSupPair = toRenderMathMLScriptsWrapper(previousSibling); 450 } 451 452 // 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. 453 RenderMathMLBlock::addChild(child, firstChild() == beforeChild ? beforeChild : 0); 454} 455 456void RenderMathMLScriptsWrapper::addChild(RenderObject* child, RenderObject* beforeChild) 457{ 458 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); 459 460 addChildInternal(false, child, beforeChild); 461 462 parentNode->fixAnonymousStyles(); 463} 464 465RenderObject* RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject& child) 466{ 467 if (doNotRestructure) 468 return RenderMathMLBlock::removeChild(child); 469 470 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); 471 472 if (m_kind == Base) { 473 // We remove the child from the base wrapper. 474 RenderObject* sibling = nextSibling(); 475 RenderMathMLBlock::removeChild(child); 476 if (sibling && !isPrescript(sibling)) { 477 // If there are postscripts, the first one becomes the base. 478 RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(sibling); 479 RenderObject* script = wrapper->firstChild(); 480 wrapper->removeChildInternal(false, *script); 481 RenderMathMLBlock::addChild(script); 482 } 483 return sibling; 484 } 485 486 // We remove the child and shift the successors in the current sequence of scripts. 487 RenderObject* next = RenderMathMLBlock::removeChild(child); 488 RenderMathMLScriptsWrapper* subSupPair = this; 489 for (RenderObject* nextSibling = subSupPair->nextSibling(); nextSibling && !isPrescript(nextSibling); nextSibling = nextSibling->nextSibling()) { 490 RenderMathMLScriptsWrapper* nextSubSupPair = toRenderMathMLScriptsWrapper(nextSibling); 491 RenderObject* script = nextSubSupPair->firstChild(); 492 nextSubSupPair->removeChildInternal(true, *script); 493 subSupPair->addChildInternal(true, script); 494 subSupPair = toRenderMathMLScriptsWrapper(nextSibling); 495 } 496 497 // We remove the last subSup pair if it became empty. 498 if (subSupPair->isEmpty()) { 499 parentNode->removeChildInternal(true, *subSupPair); 500 subSupPair->destroy(); 501 } 502 503 return next; 504} 505 506RenderObject* RenderMathMLScriptsWrapper::removeChild(RenderObject& child) 507{ 508 if (beingDestroyed() || documentBeingDestroyed()) { 509 // The renderer is being destroyed so we remove the child normally. 510 return RenderMathMLBlock::removeChild(child); 511 } 512 513 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); 514 RenderObject* next = removeChildInternal(false, child); 515 parentNode->fixAnonymousStyles(); 516 return next; 517} 518 519} 520 521#endif // ENABLE(MATHML) 522