2010-08-09 Fran├žois Sausset <sausset@gmail.com>
[WebKit.git] / WebCore / mathml / RenderMathMLOperator.cpp
1 /*
2  * Copyright (C) 2010 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 "RenderMathMLOperator.h"
31
32 #include "FontSelector.h"
33 #include "MathMLNames.h"
34 #include "RenderText.h"
35
36 namespace WebCore {
37     
38 using namespace MathMLNames;
39
40 RenderMathMLOperator::RenderMathMLOperator(Node* container)
41     : RenderMathMLBlock(container)
42     , m_stretchHeight(0)
43     , m_operator(0)
44 {
45 }
46
47 RenderMathMLOperator::RenderMathMLOperator(Node* container, UChar operatorChar)
48     : RenderMathMLBlock(container)
49     , m_stretchHeight(0)
50     , m_operator(operatorChar)
51 {
52 }
53
54 bool RenderMathMLOperator::isChildAllowed(RenderObject*, RenderStyle*) const
55 {
56     return false;
57 }
58
59 static const float gOperatorSpacer = 0.1f;
60 static const float gOperatorExpansion = 1.2f;
61
62 void RenderMathMLOperator::stretchToHeight(int height)
63 {
64     if (height == m_stretchHeight)
65         return;
66     m_stretchHeight = static_cast<int>(height * gOperatorExpansion);
67     
68     updateBoxModelInfoFromStyle();
69     setNeedsLayoutAndPrefWidthsRecalc();
70     markContainingBlocksForLayout();
71 }
72
73 void RenderMathMLOperator::layout() 
74 {
75     // FIXME: This probably shouldn't be called here but when the operator
76     // isn't stretched (e.g. outside of a mrow), it needs to be called somehow
77     updateFromElement();
78     RenderBlock::layout();
79 }
80
81 // This is a table of stretchy characters.
82 // FIXME: Should this be read from the unicode characteristics somehow?
83 // table:  stretchy operator, top char, extension char, bottom char, middle char
84 static struct StretchyCharacter {
85     UChar character;
86     UChar topGlyph;
87     UChar extensionGlyph;
88     UChar bottomGlyph;
89     UChar middleGlyph;
90 } stretchyCharacters[13] = {
91     { 0x28  , 0x239b, 0x239c, 0x239d, 0x0    }, // left parenthesis
92     { 0x29  , 0x239e, 0x239f, 0x23a0, 0x0    }, // right parenthesis
93     { 0x5b  , 0x23a1, 0x23a2, 0x23a3, 0x0    }, // left square bracket
94     { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0    }, // left ceiling
95     { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0    }, // left floor
96     { 0x5d  , 0x23a4, 0x23a5, 0x23a6, 0x0    }, // right square bracket
97     { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0    }, // right ceiling
98     { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0    }, // right floor
99     { 0x7b  , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket
100     { 0x7c  , 0x23d0, 0x23d0, 0x23d0, 0x0    }, // vertical bar
101     { 0x2016, 0x2016, 0x2016, 0x2016, 0x0    }, // double vertical line
102     { 0x7d  , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
103     { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0    } // integral sign
104 };
105
106 // We stack glyphs using a 14px height with a displayed glyph height
107 // of 10px.  The line height is set to less than the 14px so that there
108 // are no blank spaces between the stacked glyphs.
109 //
110 // Certain glyphs (e.g. middle and bottom) need to be adjusted upwards
111 // in the stack so that there isn't a gap.
112 //
113 // All of these settings are represented in the constants below.
114
115 // FIXME: use fractions of style()->fontSize() for proper zooming/resizing.
116 static const int gGlyphFontSize = 14;
117 static const int gGlyphLineHeight = 11;
118 static const int gMinimumStretchHeight = 24;
119 static const int gGlyphHeight = 10;
120 static const int gTopGlyphTopAdjust = 1;
121 static const int gMiddleGlyphTopAdjust = -1;
122 static const int gBottomGlyphTopAdjust = -3;
123 static const float gMinimumRatioForStretch = 0.10f;
124
125 void RenderMathMLOperator::updateFromElement()
126 {
127     // Destroy our current children
128     children()->destroyLeftoverChildren();
129
130     // Since we share a node with our children, destroying our children will set our node's
131     // renderer to 0, so we need to re-set it back to this.
132     node()->setRenderer(this);
133     
134     // If the operator is fixed, it will be contained in m_operator
135     UChar firstChar = m_operator;
136     
137     // This boolean indicates whether stretching is disabled via the markup.
138     bool stretchDisabled = false;
139     
140     // We made need the element later if we can't stretch.
141     if (node()->nodeType() == Node::ELEMENT_NODE) {
142         if (Element* mo = static_cast<Element*>(node())) {
143             AtomicString stretchyAttr = mo->getAttribute(MathMLNames::stretchyAttr);
144             stretchDisabled = equalIgnoringCase(stretchyAttr, "false");
145             
146             // If stretching isn't disabled, get the character from the text content.
147             if (!stretchDisabled && !firstChar) {
148                 String opText = mo->textContent();
149                 for (unsigned int i = 0; !firstChar && i < opText.length(); i++) {
150                     if (!isSpaceOrNewline(opText[i]))
151                         firstChar = opText[i];
152                 }
153             }
154         }
155     }
156     
157     // The 'index' holds the stretchable character's glyph information
158     int index = -1;
159     
160     // isStretchy indicates whether the character is streatchable via a number of factors.
161     bool isStretchy = false;
162     
163     // Check for a stretchable character.
164     if (!stretchDisabled && firstChar) {
165         const int maxIndex = sizeof(stretchyCharacters) / sizeof(stretchyCharacters[0]);
166         for (index++; index < maxIndex; index++) {
167             if (stretchyCharacters[index].character == firstChar) {
168                 isStretchy = true;
169                 break;
170             }
171         }
172     }
173     
174     // We only stretch character if the stretch height is larger than a minimum size (e.g. 24px).
175     bool shouldStretch = isStretchy && m_stretchHeight>gMinimumStretchHeight;
176     m_isCentered = true;
177     
178     // Either stretch is disabled or we don't have a stretchable character over the minimum height
179     if (stretchDisabled || !shouldStretch) {
180         m_isStacked = false;
181         RenderBlock* container = new (renderArena()) RenderMathMLBlock(node());
182         
183         RefPtr<RenderStyle> newStyle = RenderStyle::create();
184         newStyle->inheritFrom(style());
185         newStyle->setDisplay(INLINE_BLOCK);
186         
187         // Check for a stretchable character that is under the minimum height and use the
188         // font size to adjust the glyph size.
189         int currentFontSize = style()->fontSize();
190         if (!stretchDisabled && isStretchy && m_stretchHeight > 0 && m_stretchHeight <= gMinimumStretchHeight  && m_stretchHeight > currentFontSize) {
191             FontDescription desc;
192             desc.setIsAbsoluteSize(true);
193             desc.setSpecifiedSize(m_stretchHeight);
194             desc.setComputedSize(m_stretchHeight);
195             newStyle->setFontDescription(desc);
196             newStyle->font().update(newStyle->font().fontSelector());
197             newStyle->setVerticalAlign(BASELINE);
198             m_isCentered = false;
199         } else {
200             int topPad = (m_stretchHeight - currentFontSize) / 2;
201
202             if (topPad / static_cast<float>(m_stretchHeight) > gMinimumRatioForStretch) {
203                 newStyle->setVerticalAlign(TOP);
204                 newStyle->setPaddingTop(Length(topPad, Fixed));
205             } else {
206                 m_isCentered = false;
207                 newStyle->setVerticalAlign(BASELINE);
208             }
209         }
210
211         container->setStyle(newStyle.release());
212         addChild(container);
213         
214         // Build the text of the operator.
215         RenderText* text = 0;
216         if (m_operator) 
217             text = new (renderArena()) RenderText(node(), StringImpl::create(&m_operator, 1));
218         else if (node()->nodeType() == Node::ELEMENT_NODE)
219             if (Element* mo = static_cast<Element*>(node()))
220                 text = new (renderArena()) RenderText(node(), StringImpl::create(mo->textContent().characters(), mo->textContent().length()));
221         // If we can't figure out the text, leave it blank.
222         if (text) {
223             RefPtr<RenderStyle> textStyle = RenderStyle::create();
224             textStyle->inheritFrom(container->style());
225             text->setStyle(textStyle.release());
226             container->addChild(text);
227         }
228     } else {
229         // Build stretchable characters as a stack of glyphs.
230         m_isStacked = true;
231         
232         if (stretchyCharacters[index].middleGlyph) {
233             // We have a middle glyph (e.g. a curly bracket) that requires special processing.
234             int half = (m_stretchHeight - gGlyphHeight) / 2;
235             if (half <= gGlyphHeight) {
236                 // We only have enough space for a single middle glyph.
237                 createGlyph(stretchyCharacters[index].topGlyph, half, gTopGlyphTopAdjust);
238                 createGlyph(stretchyCharacters[index].middleGlyph, gGlyphHeight, gMiddleGlyphTopAdjust);
239                 createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
240             } else {
241                 // We have to extend both the top and bottom to the middle.
242                 createGlyph(stretchyCharacters[index].topGlyph, gGlyphHeight, gTopGlyphTopAdjust);
243                 int remaining = half - gGlyphHeight;
244                 while (remaining > 0) {
245                     if (remaining < gGlyphHeight) {
246                         createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
247                         remaining = 0;
248                     } else {
249                         createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
250                         remaining -= gGlyphHeight;
251                     }
252                 }
253                 
254                 // The middle glyph in the stack.
255                 createGlyph(stretchyCharacters[index].middleGlyph, gGlyphHeight, gMiddleGlyphTopAdjust);
256                 
257                 // The remaining is the top half minus the middle glyph height.
258                 remaining = half - gGlyphHeight;
259                 // We need to make sure we have the full height in case the height is odd.
260                 if (m_stretchHeight % 2 == 1)
261                     remaining++;
262                 
263                 // Extend to the bottom glyph.
264                 while (remaining > 0) {
265                     if (remaining < gGlyphHeight) {
266                         createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
267                         remaining = 0;
268                     } else {
269                         createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
270                         remaining -= gGlyphHeight;
271                     }
272                 }
273                 
274                 // The bottom glyph in the stack.
275                 createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
276             }
277         } else {
278             // We do not have a middle glyph and so we just extend from the top to the bottom glyph.
279             int remaining = m_stretchHeight - 2 * gGlyphHeight;
280             createGlyph(stretchyCharacters[index].topGlyph, gGlyphHeight, gTopGlyphTopAdjust);
281             while (remaining > 0) {
282                 if (remaining < gGlyphHeight) {
283                     createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
284                     remaining = 0;
285                 } else {
286                     createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
287                     remaining -= gGlyphHeight;
288                 }
289             }
290             createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
291         }
292     }
293 }
294
295 RefPtr<RenderStyle> RenderMathMLOperator::createStackableStyle(int size, int topRelative)
296 {
297     RefPtr<RenderStyle> newStyle = RenderStyle::create();
298     newStyle->inheritFrom(style());
299     newStyle->setDisplay(BLOCK);
300     
301     FontDescription desc;
302     desc.setIsAbsoluteSize(true);
303     desc.setSpecifiedSize(gGlyphFontSize);
304     desc.setComputedSize(gGlyphFontSize);
305     newStyle->setFontDescription(desc);
306     newStyle->font().update(newStyle->font().fontSelector());
307     newStyle->setLineHeight(Length(gGlyphLineHeight, Fixed));
308     newStyle->setVerticalAlign(TOP);
309
310     if (size > 0)
311         newStyle->setMaxHeight(Length(size, Fixed));
312     
313     newStyle->setOverflowY(OHIDDEN);
314     newStyle->setOverflowX(OHIDDEN);
315     if (topRelative) {
316         newStyle->setTop(Length(topRelative, Fixed));
317         newStyle->setPosition(RelativePosition);
318     }
319
320     return newStyle;
321 }
322
323 RenderBlock* RenderMathMLOperator::createGlyph(UChar glyph, int size, int charRelative, int topRelative)
324 {
325     RenderBlock* container = new (renderArena()) RenderMathMLBlock(node());
326     container->setStyle(createStackableStyle(size, topRelative).release());
327     addChild(container);
328     RenderBlock* parent = container;
329     if (charRelative) {
330         RenderBlock* charBlock = new (renderArena()) RenderBlock(node());
331         RefPtr<RenderStyle> charStyle = RenderStyle::create();
332         charStyle->inheritFrom(container->style());
333         charStyle->setDisplay(INLINE_BLOCK);
334         charStyle->setTop(Length(charRelative, Fixed));
335         charStyle->setPosition(RelativePosition);
336         charBlock->setStyle(charStyle);
337         parent->addChild(charBlock);
338         parent = charBlock;
339     }
340     
341     RenderText* text = new (renderArena()) RenderText(node(), StringImpl::create(&glyph, 1));
342     text->setStyle(container->style());
343     parent->addChild(text);
344     return container;
345 }
346
347 int RenderMathMLOperator::baselinePosition(bool firstLine, bool isRootLineBox) const
348 {
349     if (m_isStacked)
350         return m_stretchHeight * 2 / 3 - (m_stretchHeight - static_cast<int>(m_stretchHeight / gOperatorExpansion)) / 2;
351     if (m_isCentered && firstChild()) 
352         return firstChild()->baselinePosition(firstLine, isRootLineBox);
353     return RenderBlock::baselinePosition(firstLine, isRootLineBox);
354 }
355     
356 }
357
358 #endif