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