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