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