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 "MathMLNames.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)
60 : RenderMathMLBlock(element)
61 , m_baseWrapper(0)
62{
63 // Determine what kind of sub/sup expression we have by element name
64 if (element->hasLocalName(MathMLNames::msubTag))
65 m_kind = Sub;
66 else if (element->hasLocalName(MathMLNames::msupTag))
67 m_kind = Super;
68 else if (element->hasLocalName(MathMLNames::msubsupTag))
69 m_kind = SubSup;
70 else {
71 ASSERT(element->hasLocalName(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 RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(parent);
157 wrapper->addChildInternal(false, child, beforeChild);
158 return;
159 }
160 }
161
162 if (beforeChild == m_baseWrapper) {
163 // This is like inserting the child at the beginning of the base wrapper.
164 m_baseWrapper->addChildInternal(false, child, m_baseWrapper->firstChild());
165 return;
166 }
167
168 if (isPrescript(child)) {
169 // The new child becomes an <mprescripts/> separator.
170 RenderMathMLBlock::addChild(child, beforeChild);
171 return;
172 }
173
174 if (!beforeChild || isPrescript(beforeChild)) {
175 // We are at the end of a sequence of subSup pairs.
176 RenderMathMLBlock* previousSibling = toRenderMathMLBlock(beforeChild ? beforeChild->previousSibling() : lastChild());
177 if (previousSibling && previousSibling->isRenderMathMLScriptsWrapper()) {
178 RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(previousSibling);
179 if ((wrapper->m_kind == RenderMathMLScriptsWrapper::Base && wrapper->isEmpty()) || (wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !wrapper->firstChild()->nextSibling())) {
180 // 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.
181 wrapper->addChildInternal(true, child);
182 return;
183 }
184 }
185 // Otherwise we create a new subSupPair to store the new child.
186 RenderMathMLScriptsWrapper* subSupPair = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::SubSupPair);
187 subSupPair->addChildInternal(true, child);
188 RenderMathMLBlock::addChild(subSupPair, beforeChild);
189 return;
190 }
191
192 // beforeChild is a subSup pair. This is like inserting the new child at the beginning of the subSup wrapper.
193 RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(beforeChild);
194 ASSERT(wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair);
195 ASSERT(!(m_baseWrapper->isEmpty() && m_baseWrapper->nextSibling() == beforeChild));
196 wrapper->addChildInternal(false, child, wrapper->firstChild());
197}
198
199void RenderMathMLScripts::removeChildInternal(bool doNotRestructure, RenderObject* child)
200{
201 if (doNotRestructure) {
202 RenderMathMLBlock::removeChild(child);
203 return;
204 }
205
206 ASSERT(isPrescript(child));
207
208 RenderObject* previousSibling = child->previousSibling();
209 RenderObject* nextSibling = child->nextSibling();
210 ASSERT(previousSibling);
211
212 if (nextSibling && !isPrescript(previousSibling) && !isPrescript(nextSibling)) {
213 RenderMathMLScriptsWrapper* previousWrapper = toRenderMathMLScriptsWrapper(previousSibling);
214 RenderMathMLScriptsWrapper* nextWrapper = toRenderMathMLScriptsWrapper(nextSibling);
215 ASSERT(nextWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !nextWrapper->isEmpty());
216 if ((previousWrapper->m_kind == RenderMathMLScriptsWrapper::Base && previousWrapper->isEmpty()) || (previousWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !previousWrapper->firstChild()->nextSibling())) {
217 RenderObject* script = nextWrapper->firstChild();
218 nextWrapper->removeChildInternal(false, script);
219 previousWrapper->addChildInternal(true, script);
220 }
221 }
222
223 RenderMathMLBlock::removeChild(child);
224}
225
226void RenderMathMLScripts::addChild(RenderObject* child, RenderObject* beforeChild)
227{
228 if (isEmpty()) {
229 m_baseWrapper = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::Base);
230 RenderMathMLBlock::addChild(m_baseWrapper);
231 }
232
233 addChildInternal(false, child, beforeChild);
234
235 fixAnonymousStyles();
236}
237
238void RenderMathMLScripts::removeChild(RenderObject* child)
239{
240 if (beingDestroyed() || documentBeingDestroyed()) {
241 // The renderer is being destroyed so we remove the child normally.
242 RenderMathMLBlock::removeChild(child);
243 return;
244 }
245
246 removeChildInternal(false, child);
247
248 fixAnonymousStyles();
249}
250
251void RenderMathMLScripts::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
252{
253 RenderMathMLBlock::styleDidChange(diff, oldStyle);
254
255 if (!isEmpty())
256 fixAnonymousStyles();
257}
258
259RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator()
260{
261 RenderBoxModelObject* base = this->base();
262 if (!base || !base->isRenderMathMLBlock())
263 return 0;
264 return toRenderMathMLBlock(base)->unembellishedOperator();
265}
266
267void RenderMathMLScripts::layout()
268{
269 RenderMathMLBlock::layout();
270
271 if (!m_baseWrapper)
272 return;
273 RenderBox* base = m_baseWrapper->firstChildBox();
274 if (!base)
275 return;
276
277 // Our layout rules include: Don't let the superscript go below the "axis" (half x-height above the
278 // baseline), or the subscript above the axis. Also, don't let the superscript's top edge be
279 // below the base's top edge, or the subscript's bottom edge above the base's bottom edge.
280 //
281 // FIXME: Check any subscriptshift or superscriptshift attributes, and maybe use more sophisticated
282 // heuristics from TeX or elsewhere. See https://bugs.webkit.org/show_bug.cgi?id=79274#c5.
283
284 LayoutUnit baseHeight = base->logicalHeight();
285 LayoutUnit baseBaseline = base->firstLineBoxBaseline();
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 bool isPostScript = true;
298 RenderMathMLBlock* subSupPair = toRenderMathMLBlock(m_baseWrapper->nextSibling());
299 for (; subSupPair; subSupPair = toRenderMathMLBlock(subSupPair->nextSibling())) {
300
301 // We skip the base and <mprescripts/> elements.
302 if (isPrescript(subSupPair)) {
303 if (!isPostScript)
304 break;
305 isPostScript = false;
306 continue;
307 }
308
309 if (RenderBox* superscript = m_kind == Sub ? 0 : subSupPair->lastChildBox()) {
310 LayoutUnit superscriptHeight = superscript->logicalHeight();
311 LayoutUnit superscriptBaseline = superscript->firstLineBoxBaseline();
312 if (superscriptBaseline == -1)
313 superscriptBaseline = superscriptHeight;
314 LayoutUnit minBaseline = max<LayoutUnit>(fontSize / 3 + 1 + superscriptBaseline, superscriptHeight + axis);
315
316 topPadding = max<LayoutUnit>(topPadding, minBaseline - baseBaseline);
317 }
318
319 if (RenderBox* subscript = m_kind == Super ? 0 : subSupPair->firstChildBox()) {
320 LayoutUnit subscriptHeight = subscript->logicalHeight();
321 LayoutUnit subscriptBaseline = subscript->firstLineBoxBaseline();
322 if (subscriptBaseline == -1)
323 subscriptBaseline = subscriptHeight;
324 LayoutUnit baseExtendUnderBaseline = baseHeight - baseBaseline;
325 LayoutUnit subscriptUnderItsBaseline = subscriptHeight - subscriptBaseline;
326 LayoutUnit minExtendUnderBaseline = max<LayoutUnit>(fontSize / 5 + 1 + subscriptUnderItsBaseline, subscriptHeight - axis);
327
328 bottomPadding = max<LayoutUnit>(bottomPadding, minExtendUnderBaseline - baseExtendUnderBaseline);
329 }
330 }
331
332 Length newPadding(topPadding, Fixed);
333 if (newPadding != m_baseWrapper->style()->paddingTop()) {
334 m_baseWrapper->style()->setPaddingTop(newPadding);
335 needsSecondLayout = true;
336 }
337
338 newPadding = Length(bottomPadding, Fixed);
339 if (newPadding != m_baseWrapper->style()->paddingBottom()) {
340 m_baseWrapper->style()->setPaddingBottom(newPadding);
341 needsSecondLayout = true;
342 }
343
344 if (!needsSecondLayout)
345 return;
346
347 setNeedsLayout(true, MarkOnlyThis);
348 m_baseWrapper->setChildNeedsLayout(true, MarkOnlyThis);
349
350 RenderMathMLBlock::layout();
351}
352
353int RenderMathMLScripts::firstLineBoxBaseline() const
354{
355 if (m_baseWrapper) {
356 LayoutUnit baseline = m_baseWrapper->firstLineBoxBaseline();
357 if (baseline != -1)
358 return baseline;
359 }
360 return RenderMathMLBlock::firstLineBoxBaseline();
361}
362
363RenderMathMLScriptsWrapper* RenderMathMLScriptsWrapper::createAnonymousWrapper(RenderMathMLScripts* renderObject, WrapperType type)
364{
365 RefPtr<RenderStyle> newStyle = RenderStyle::createAnonymousStyleWithDisplay(renderObject->style(), FLEX);
366 RenderMathMLScriptsWrapper* newBlock = new (renderObject->renderArena()) RenderMathMLScriptsWrapper(0, type);
367 newBlock->setDocumentForAnonymous(&renderObject->document());
368 newBlock->setStyle(newStyle.release());
369 return newBlock;
370}
371
372void RenderMathMLScriptsWrapper::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild)
373{
374 if (doNotRestructure) {
375 RenderMathMLBlock::addChild(child, beforeChild);
376 return;
377 }
378
379 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
380
381 if (m_kind == Base) {
382 RenderObject* sibling = nextSibling();
383
384 if (!isEmpty() && !beforeChild) {
385 // This is like inserting the child after the base wrapper.
386 parentNode->addChildInternal(false, sibling);
387 return;
388 }
389
390 // The old base (if any) becomes a script ; the new child becomes either the base or an <mprescripts> separator.
391 RenderObject* oldBase = firstChild();
392 if (oldBase)
393 RenderMathMLBlock::removeChild(oldBase);
394 if (isPrescript(child))
395 parentNode->addChildInternal(true, child, sibling);
396 else
397 RenderMathMLBlock::addChild(child);
398 if (oldBase)
399 parentNode->addChildInternal(false, oldBase, sibling);
400 return;
401 }
402
403 if (isPrescript(child)) {
404 // We insert an <mprescripts> element.
405 if (!beforeChild)
406 parentNode->addChildInternal(true, child, nextSibling());
407 else if (beforeChild == firstChild())
408 parentNode->addChildInternal(true, child, this);
409 else {
410 // We insert the <mprescripts> in the middle of a subSup pair so we must split that pair.
411 RenderObject* sibling = nextSibling();
412 parentNode->removeChildInternal(true, this);
413 parentNode->addChildInternal(true, child, sibling);
414
415 RenderObject* script = firstChild();
416 RenderMathMLBlock::removeChild(script);
417 parentNode->addChildInternal(false, script, child);
418
419 script = beforeChild;
420 RenderMathMLBlock::removeChild(script);
421 parentNode->addChildInternal(false, script, sibling);
422 destroy();
423 }
424 return;
425 }
426
427 // We first move to the last subSup pair in the curent sequence of scripts.
428 RenderMathMLScriptsWrapper* subSupPair = this;
429 while (subSupPair->nextSibling() && !isPrescript(subSupPair->nextSibling()))
430 subSupPair = toRenderMathMLScriptsWrapper(subSupPair->nextSibling());
431 if (subSupPair->firstChild()->nextSibling()) {
432 // The last pair has two children so we need to create a new pair to leave room for the new child.
433 RenderMathMLScriptsWrapper* newPair = createAnonymousWrapper(parentNode, RenderMathMLScriptsWrapper::SubSupPair);
434 parentNode->addChildInternal(true, newPair, subSupPair->nextSibling());
435 subSupPair = newPair;
436 }
437
438 // We shift the successors in the current sequence of scripts.
439 for (RenderObject* previousSibling = subSupPair->previousSibling(); subSupPair != this; previousSibling = previousSibling->previousSibling()) {
440 RenderMathMLScriptsWrapper* previousSubSupPair = toRenderMathMLScriptsWrapper(previousSibling);
441 RenderObject* script = previousSubSupPair->lastChild();
442 previousSubSupPair->removeChildInternal(true, script);
443 subSupPair->addChildInternal(true, script, subSupPair->firstChild());
444 subSupPair = toRenderMathMLScriptsWrapper(previousSibling);
445 }
446
447 // 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.
448 RenderMathMLBlock::addChild(child, firstChild() == beforeChild ? beforeChild : 0);
449}
450
451void RenderMathMLScriptsWrapper::addChild(RenderObject* child, RenderObject* beforeChild)
452{
453 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
454
455 addChildInternal(false, child, beforeChild);
456
457 parentNode->fixAnonymousStyles();
458}
459
460void RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject* child)
461{
462 if (doNotRestructure) {
463 RenderMathMLBlock::removeChild(child);
464 return;
465 }
466
467 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
468
469 if (m_kind == Base) {
470 // We remove the child from the base wrapper.
471 RenderObject* sibling = nextSibling();
472 RenderMathMLBlock::removeChild(child);
473 if (sibling && !isPrescript(sibling)) {
474 // If there are postscripts, the first one becomes the base.
475 RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(sibling);
476 RenderObject* script = wrapper->firstChild();
477 wrapper->removeChildInternal(false, script);
478 RenderMathMLBlock::addChild(script);
479 }
480 return;
481 }
482
483 // We remove the child and shift the successors in the current sequence of scripts.
484 RenderMathMLBlock::removeChild(child);
485 RenderMathMLScriptsWrapper* subSupPair = this;
486 for (RenderObject* nextSibling = subSupPair->nextSibling(); nextSibling && !isPrescript(nextSibling); nextSibling = nextSibling->nextSibling()) {
487 RenderMathMLScriptsWrapper* nextSubSupPair = toRenderMathMLScriptsWrapper(nextSibling);
488 RenderObject* script = nextSubSupPair->firstChild();
489 nextSubSupPair->removeChildInternal(true, script);
490 subSupPair->addChildInternal(true, script);
491 subSupPair = toRenderMathMLScriptsWrapper(nextSibling);
492 }
493
494 // We remove the last subSup pair if it became empty.
495 if (subSupPair->isEmpty()) {
496 parentNode->removeChildInternal(true, subSupPair);
497 subSupPair->destroy();
498 }
499}
500
501void RenderMathMLScriptsWrapper::removeChild(RenderObject* child)
502{
503 if (beingDestroyed() || documentBeingDestroyed()) {
504 // The renderer is being destroyed so we remove the child normally.
505 RenderMathMLBlock::removeChild(child);
506 return;
507 }
508
509 RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
510
511 removeChildInternal(false, child);
512
513 parentNode->fixAnonymousStyles();
514}
515
516}
517
518#endif // ENABLE(MATHML)