c5e6e3694cfd89bd5e7f04e2b75c9bfdcfe5567c
[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 "FontCache.h"
34 #include "FontSelector.h"
35 #include "MathMLNames.h"
36 #include "RenderText.h"
37
38 namespace WebCore {
39     
40 using namespace MathMLNames;
41
42 RenderMathMLOperator::RenderMathMLOperator(Element* element)
43     : RenderMathMLBlock(element)
44     , m_stretchHeight(0)
45     , m_operator(0)
46     , m_operatorType(Default)
47 {
48 }
49
50 RenderMathMLOperator::RenderMathMLOperator(Element* element, UChar operatorChar)
51     : RenderMathMLBlock(element)
52     , m_stretchHeight(0)
53     , m_operator(convertHyphenMinusToMinusSign(operatorChar))
54     , m_operatorType(Default)
55 {
56 }
57
58 bool RenderMathMLOperator::isChildAllowed(RenderObject*, RenderStyle*) const
59 {
60     return false;
61 }
62
63 static const float gOperatorExpansion = 1.2f;
64
65 void RenderMathMLOperator::stretchToHeight(int height)
66 {
67     height *= gOperatorExpansion;
68     if (m_stretchHeight == height)
69         return;
70     m_stretchHeight = height;
71     
72     updateFromElement();
73 }
74
75 void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
76 {
77     RenderMathMLBlock::styleDidChange(diff, oldStyle);
78     
79     if (firstChild())
80         updateFromElement();
81 }
82
83 void RenderMathMLOperator::computePreferredLogicalWidths() 
84 {
85     ASSERT(preferredLogicalWidthsDirty());
86
87 #ifndef NDEBUG
88     // FIXME: Remove the setNeedsLayoutIsForbidden calls once mathml stops modifying the render tree here.
89     bool oldSetNeedsLayoutIsForbidden = isSetNeedsLayoutForbidden();
90     setNeedsLayoutIsForbidden(false);
91 #endif
92     
93     // Check for an uninitialized operator.
94     if (!firstChild())
95         updateFromElement();
96
97 #ifndef NDEBUG
98     setNeedsLayoutIsForbidden(oldSetNeedsLayoutIsForbidden);
99 #endif
100
101     RenderMathMLBlock::computePreferredLogicalWidths();
102 }
103
104 // This is a table of stretchy characters.
105 // FIXME: Should this be read from the unicode characteristics somehow?
106 // table:  stretchy operator, top char, extension char, bottom char, middle char
107 static struct StretchyCharacter {
108     UChar character;
109     UChar topGlyph;
110     UChar extensionGlyph;
111     UChar bottomGlyph;
112     UChar middleGlyph;
113 } stretchyCharacters[13] = {
114     { 0x28  , 0x239b, 0x239c, 0x239d, 0x0    }, // left parenthesis
115     { 0x29  , 0x239e, 0x239f, 0x23a0, 0x0    }, // right parenthesis
116     { 0x5b  , 0x23a1, 0x23a2, 0x23a3, 0x0    }, // left square bracket
117     { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0    }, // left ceiling
118     { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0    }, // left floor
119     { 0x5d  , 0x23a4, 0x23a5, 0x23a6, 0x0    }, // right square bracket
120     { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0    }, // right ceiling
121     { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0    }, // right floor
122     { 0x7b  , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket
123     { 0x7c  , 0x23aa, 0x23aa, 0x23aa, 0x0    }, // vertical bar
124     { 0x2016, 0x2016, 0x2016, 0x2016, 0x0    }, // double vertical line
125     { 0x7d  , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
126     { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0    } // integral sign
127 };
128
129 // Note glyphHeightForCharacter truncates its result to an int.
130 int RenderMathMLOperator::glyphHeightForCharacter(UChar character)
131 {
132     GlyphData data = style()->font().glyphDataForCharacter(character, false);
133     FloatRect glyphBounds = data.fontData->boundsForGlyph(data.glyph);
134     return glyphBounds.height();
135 }
136
137 // FIXME: It's cleaner to only call updateFromElement when an attribute has changed. The body of
138 // this method should probably be moved to a private stretchHeightChanged or checkStretchHeight
139 // method. Probably at the same time, addChild/removeChild methods should be made to work for
140 // dynamic DOM changes.
141 void RenderMathMLOperator::updateFromElement()
142 {
143     RenderObject* savedRenderer = node()->renderer();
144
145     // Destroy our current children
146     children()->destroyLeftoverChildren();
147
148     // Since we share a node with our children, destroying our children may set our node's
149     // renderer to 0, so we need to restore it.
150     node()->setRenderer(savedRenderer);
151     
152     // If the operator is fixed, it will be contained in m_operator
153     UChar firstChar = m_operator;
154     
155     // This boolean indicates whether stretching is disabled via the markup.
156     bool stretchDisabled = false;
157     
158     // We may need the element later if we can't stretch.
159     if (node()->isElementNode()) {
160         if (Element* mo = static_cast<Element*>(node())) {
161             AtomicString stretchyAttr = mo->getAttribute(MathMLNames::stretchyAttr);
162             stretchDisabled = equalIgnoringCase(stretchyAttr, "false");
163             
164             // If stretching isn't disabled, get the character from the text content.
165             if (!stretchDisabled && !firstChar) {
166                 String opText = mo->textContent();
167                 for (unsigned int i = 0; !firstChar && i < opText.length(); i++) {
168                     if (!isSpaceOrNewline(opText[i]))
169                         firstChar = opText[i];
170                 }
171             }
172         }
173     }
174     
175     // The 'index' holds the stretchable character's glyph information
176     int index = -1;
177     
178     // isStretchy indicates whether the character is streatchable via a number of factors.
179     bool isStretchy = false;
180     
181     // Check for a stretchable character.
182     if (!stretchDisabled && firstChar) {
183         const int maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters);
184         for (index++; index < maxIndex; index++) {
185             if (stretchyCharacters[index].character == firstChar) {
186                 isStretchy = true;
187                 break;
188             }
189         }
190     }
191     
192     // We only stack glyphs if the stretch height is larger than a minimum size.
193     bool shouldStack = isStretchy && m_stretchHeight > style()->fontSize();
194     struct StretchyCharacter* partsData = 0;
195     int topGlyphHeight = 0;
196     int extensionGlyphHeight = 0;
197     int bottomGlyphHeight = 0;
198     int middleGlyphHeight = 0;
199     if (shouldStack) {
200         partsData = &stretchyCharacters[index];
201         
202         FontCachePurgePreventer fontCachePurgePreventer;
203         
204         topGlyphHeight = glyphHeightForCharacter(partsData->topGlyph);
205         extensionGlyphHeight = glyphHeightForCharacter(partsData->extensionGlyph) - 1;
206         bottomGlyphHeight = glyphHeightForCharacter(partsData->bottomGlyph);
207         if (partsData->middleGlyph)
208             middleGlyphHeight = glyphHeightForCharacter(partsData->middleGlyph) - 1;
209         shouldStack = m_stretchHeight >= topGlyphHeight + middleGlyphHeight + bottomGlyphHeight && extensionGlyphHeight > 0;
210     }
211     
212     // Either stretch is disabled or we don't have a stretchable character over the minimum height
213     if (stretchDisabled || !shouldStack) {
214         m_isStacked = false;
215         RenderBlock* container = new (renderArena()) RenderMathMLBlock(toElement(node()));
216         // This container doesn't offer any useful information to accessibility.
217         toRenderMathMLBlock(container)->setIgnoreInAccessibilityTree(true);
218         
219         RefPtr<RenderStyle> newStyle = RenderStyle::create();
220         newStyle->inheritFrom(style());
221         newStyle->setDisplay(FLEX);
222         
223         // Check for a stretchable character that is under the minimum height.
224         if (!stretchDisabled && isStretchy && m_stretchHeight > style()->fontSize()) {
225             FontDescription desc = style()->fontDescription();
226             desc.setIsAbsoluteSize(true);
227             desc.setSpecifiedSize(m_stretchHeight);
228             desc.setComputedSize(m_stretchHeight);
229             newStyle->setFontDescription(desc);
230             newStyle->font().update(style()->font().fontSelector());
231         }
232
233         container->setStyle(newStyle.release());
234         addChild(container);
235         
236         // Build the text of the operator.
237         RenderText* text = 0;
238         if (m_operator) 
239             text = new (renderArena()) RenderText(node(), StringImpl::create(&m_operator, 1));
240         else if (node()->isElementNode())
241             if (Element* mo = static_cast<Element*>(node()))
242                 text = new (renderArena()) RenderText(node(), mo->textContent().replace(hyphenMinus, minusSign).impl());
243         // If we can't figure out the text, leave it blank.
244         if (text) {
245             RefPtr<RenderStyle> textStyle = RenderStyle::create();
246             textStyle->inheritFrom(container->style());
247             text->setStyle(textStyle.release());
248             container->addChild(text);
249         }
250     } else {
251         // Build stretchable characters as a stack of glyphs.
252         m_isStacked = true;
253         
254         // To avoid gaps, we position glyphs after the top glyph upward by 1px. We also truncate
255         // glyph heights to ints, and then reduce all but the top & bottom such heights by 1px.
256         
257         int remaining = m_stretchHeight - topGlyphHeight - bottomGlyphHeight;
258         createGlyph(partsData->topGlyph, topGlyphHeight, 0);
259         if (partsData->middleGlyph) {
260             // We have a middle glyph (e.g. a curly bracket) that requires special processing.
261             remaining -= middleGlyphHeight;
262             int half = (remaining + 1) / 2;
263             remaining -= half;
264             while (remaining > 0) {
265                 int height = std::min<int>(remaining, extensionGlyphHeight);
266                 createGlyph(partsData->extensionGlyph, height, -1);
267                 remaining -= height;
268             }
269             
270             // The middle glyph in the stack.
271             createGlyph(partsData->middleGlyph, middleGlyphHeight, -1);
272             
273             remaining = half;
274             while (remaining > 0) {
275                 int height = std::min<int>(remaining, extensionGlyphHeight);
276                 createGlyph(partsData->extensionGlyph, height, -1);
277                 remaining -= height;
278             }
279         } else {
280             // We do not have a middle glyph and so we just extend from the top to the bottom glyph.
281             while (remaining > 0) {
282                 int height = std::min<int>(remaining, extensionGlyphHeight);
283                 createGlyph(partsData->extensionGlyph, height, -1);
284                 remaining -= height;
285             }
286         }
287         createGlyph(partsData->bottomGlyph, bottomGlyphHeight, -1);
288     }
289     
290     setNeedsLayoutAndPrefWidthsRecalc();
291 }
292
293 PassRefPtr<RenderStyle> RenderMathMLOperator::createStackableStyle(int maxHeightForRenderer)
294 {
295     RefPtr<RenderStyle> newStyle = RenderStyle::create();
296     newStyle->inheritFrom(style());
297     newStyle->setDisplay(FLEX);
298     
299     newStyle->setMaxHeight(Length(maxHeightForRenderer, Fixed));
300     
301     newStyle->setOverflowY(OHIDDEN);
302     newStyle->setOverflowX(OHIDDEN);
303
304     return newStyle.release();
305 }
306
307 RenderBlock* RenderMathMLOperator::createGlyph(UChar glyph, int maxHeightForRenderer, int charRelative)
308 {
309     RenderBlock* container = new (renderArena()) RenderMathMLBlock(toElement(node()));
310     toRenderMathMLBlock(container)->setIgnoreInAccessibilityTree(true);
311     container->setStyle(createStackableStyle(maxHeightForRenderer));
312     addChild(container);
313     RenderBlock* parent = container;
314     if (charRelative) {
315         RenderBlock* charBlock = new (renderArena()) RenderBlock(node());
316         RefPtr<RenderStyle> charStyle = RenderStyle::create();
317         charStyle->inheritFrom(container->style());
318         charStyle->setDisplay(INLINE_BLOCK);
319         charStyle->setTop(Length(charRelative, Fixed));
320         charStyle->setPosition(RelativePosition);
321         charBlock->setStyle(charStyle);
322         parent->addChild(charBlock);
323         parent = charBlock;
324     }
325     
326     RenderText* text = new (renderArena()) RenderText(node(), StringImpl::create(&glyph, 1));
327     text->setStyle(container->style());
328     parent->addChild(text);
329     return container;
330 }
331
332 int RenderMathMLOperator::firstLineBoxBaseline() const
333 {
334     if (m_isStacked)
335         return m_stretchHeight * 2 / 3 - (m_stretchHeight - static_cast<int>(m_stretchHeight / gOperatorExpansion)) / 2;    
336     return RenderMathMLBlock::firstLineBoxBaseline();
337 }
338     
339 }
340
341 #endif