9936539b0fa15f4705e17cf20cb677cc6ee855d5
[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  * Copyright (C) 2013 Igalia S.L.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29
30 #if ENABLE(MATHML)
31
32 #include "RenderMathMLOperator.h"
33
34 #include "FontCache.h"
35 #include "FontSelector.h"
36 #include "MathMLNames.h"
37 #include "PaintInfo.h"
38 #include "RenderBlockFlow.h"
39 #include "RenderText.h"
40 #include "ScaleTransformOperation.h"
41 #include "TransformOperations.h"
42 #include <wtf/MathExtras.h>
43
44 namespace WebCore {
45     
46 using namespace MathMLNames;
47
48 // FIXME: The OpenType MATH table contains information that should override this table (http://wkbug/122297).
49 static RenderMathMLOperator::StretchyCharacter stretchyCharacters[13] = {
50     { 0x28  , 0x239b, 0x239c, 0x239d, 0x0    }, // left parenthesis
51     { 0x29  , 0x239e, 0x239f, 0x23a0, 0x0    }, // right parenthesis
52     { 0x5b  , 0x23a1, 0x23a2, 0x23a3, 0x0    }, // left square bracket
53     { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0    }, // left ceiling
54     { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0    }, // left floor
55     { 0x5d  , 0x23a4, 0x23a5, 0x23a6, 0x0    }, // right square bracket
56     { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0    }, // right ceiling
57     { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0    }, // right floor
58     { 0x7b  , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket
59     { 0x7c  , 0x23aa, 0x23aa, 0x23aa, 0x0    }, // vertical bar
60     { 0x2016, 0x2016, 0x2016, 0x2016, 0x0    }, // double vertical line
61     { 0x7d  , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
62     { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0    } // integral sign
63 };
64
65 RenderMathMLOperator::RenderMathMLOperator(Element* element)
66     : RenderMathMLBlock(element)
67     , m_stretchHeight(0)
68     , m_operator(0)
69     , m_operatorType(Default)
70     , m_stretchyCharacter(0)
71 {
72 }
73
74 RenderMathMLOperator::RenderMathMLOperator(Element* element, UChar operatorChar)
75     : RenderMathMLBlock(element)
76     , m_stretchHeight(0)
77     , m_operator(convertHyphenMinusToMinusSign(operatorChar))
78     , m_operatorType(Default)
79     , m_stretchyCharacter(0)
80 {
81 }
82
83 bool RenderMathMLOperator::isChildAllowed(RenderObject*, RenderStyle*) const
84 {
85     return false;
86 }
87
88 static const float gOperatorExpansion = 1.2f;
89
90 float RenderMathMLOperator::expandedStretchHeight() const
91 {
92     return m_stretchHeight * gOperatorExpansion;
93 }
94
95 void RenderMathMLOperator::stretchToHeight(int height)
96 {
97     if (m_stretchHeight == height)
98         return;
99
100     m_stretchHeight = height;
101     updateStyle();
102 }
103
104 void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
105 {
106     RenderMathMLBlock::styleDidChange(diff, oldStyle);
107     updateFromElement();
108 }
109
110 FloatRect RenderMathMLOperator::glyphBoundsForCharacter(UChar character)
111 {
112     GlyphData data = style()->font().glyphDataForCharacter(character, false);
113     return data.fontData->boundsForGlyph(data.glyph);
114 }
115
116 float RenderMathMLOperator::glyphHeightForCharacter(UChar character)
117 {
118     return glyphBoundsForCharacter(character).height();
119 }
120
121 float RenderMathMLOperator::advanceForCharacter(UChar character)
122 {
123     // Hyphen minus is always replaced by the minus sign in rendered text.
124     GlyphData data = style()->font().glyphDataForCharacter(convertHyphenMinusToMinusSign(character), false);
125     return data.fontData->widthForGlyph(data.glyph);
126 }
127
128 void RenderMathMLOperator::computePreferredLogicalWidths()
129 {
130     ASSERT(preferredLogicalWidthsDirty());
131
132     UChar stretchedCharacter;
133     bool allowStretching = shouldAllowStretching(stretchedCharacter);
134     if (!allowStretching) {
135         RenderMathMLBlock::computePreferredLogicalWidths();
136         return;
137     }
138
139     float maximumGlyphWidth = advanceForCharacter(stretchedCharacter);
140     for (unsigned index = 0; index < WTF_ARRAY_LENGTH(stretchyCharacters); ++index) {
141         if (stretchyCharacters[index].character != stretchedCharacter)
142             continue;
143
144         StretchyCharacter& character = stretchyCharacters[index];
145         if (character.topGlyph)
146             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.topGlyph));
147         if (character.extensionGlyph)
148             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.extensionGlyph));
149         if (character.bottomGlyph)
150             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.bottomGlyph));
151         if (character.middleGlyph)
152             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.middleGlyph));
153         m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth;
154         return;
155     }
156
157     m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth;
158 }
159
160 // FIXME: It's cleaner to only call updateFromElement when an attribute has changed. The body of
161 // this method should probably be moved to a private stretchHeightChanged or checkStretchHeight
162 // method. Probably at the same time, addChild/removeChild methods should be made to work for
163 // dynamic DOM changes.
164 void RenderMathMLOperator::updateFromElement()
165 {
166     RenderObject* savedRenderer = element()->renderer();
167
168     // Destroy our current children
169     destroyLeftoverChildren();
170
171     // Since we share a node with our children, destroying our children may set our node's
172     // renderer to 0, so we need to restore it.
173     element()->setRenderer(savedRenderer);
174     
175     RefPtr<RenderStyle> newStyle = RenderStyle::create();
176     newStyle->inheritFrom(style());
177     newStyle->setDisplay(FLEX);
178
179     RenderMathMLBlock* container = new (renderArena()) RenderMathMLBlock(element());
180     // This container doesn't offer any useful information to accessibility.
181     container->setIgnoreInAccessibilityTree(true);
182     container->setStyle(newStyle.release());
183
184     addChild(container);
185     RenderText* text = 0;
186     if (m_operator)
187         text = RenderText::createAnonymous(document(), String(&m_operator, 1));
188     else
189         text = RenderText::createAnonymous(document(), element()->textContent().replace(hyphenMinus, minusSign).impl());
190
191     // If we can't figure out the text, leave it blank.
192     if (text)
193         container->addChild(text);
194
195     updateStyle();
196     setNeedsLayoutAndPrefWidthsRecalc();
197 }
198
199 bool RenderMathMLOperator::shouldAllowStretching(UChar& stretchedCharacter)
200 {
201     Element* mo = element();
202     if (equalIgnoringCase(mo->getAttribute(MathMLNames::stretchyAttr), "false"))
203         return false;
204
205     if (m_operator) {
206         stretchedCharacter = m_operator;
207         return true;
208     }
209
210     // FIXME: This does not handle surrogate pairs (http://wkbug.com/122296/).
211     String opText = mo->textContent();
212     stretchedCharacter = 0;
213     for (unsigned i = 0; i < opText.length(); ++i) {
214         // If there's more than one non-whitespace character in this node, then don't even try to stretch it.
215         if (stretchedCharacter && !isSpaceOrNewline(opText[i]))
216             return false;
217
218         if (!isSpaceOrNewline(opText[i]))
219             stretchedCharacter = opText[i];
220     }
221
222     return stretchedCharacter;
223 }
224
225 // FIXME: We should also look at alternate characters defined in the OpenType MATH table (http://wkbug/122297).
226 RenderMathMLOperator::StretchyCharacter* RenderMathMLOperator::findAcceptableStretchyCharacter(UChar character)
227 {
228     StretchyCharacter* stretchyCharacter = 0;
229     const int maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters);
230     for (int index = 0; index < maxIndex; ++index) {
231         if (stretchyCharacters[index].character == character) {
232             stretchyCharacter = &stretchyCharacters[index];
233             break;
234         }
235     }
236
237     // If we didn't find a stretchy character set for this character, we don't know how to stretch it.
238     if (!stretchyCharacter)
239         return 0;
240
241     float height = glyphHeightForCharacter(stretchyCharacter->topGlyph) + glyphHeightForCharacter(stretchyCharacter->bottomGlyph);
242     if (stretchyCharacter->middleGlyph)
243         height += glyphHeightForCharacter(stretchyCharacter->middleGlyph);
244
245     if (height > expandedStretchHeight())
246         return 0;
247
248     return stretchyCharacter;
249 }
250
251 void RenderMathMLOperator::updateStyle()
252 {
253     ASSERT(firstChild());
254     if (!firstChild())
255         return;
256
257     UChar stretchedCharacter;
258     bool allowStretching = shouldAllowStretching(stretchedCharacter);
259
260     float stretchedCharacterHeight = style()->fontMetrics().floatHeight();
261     m_isStretched = allowStretching && expandedStretchHeight() > stretchedCharacterHeight;
262
263     // Sometimes we cannot stretch an operator properly, so in that case, we should just use the original size.
264     m_stretchyCharacter = m_isStretched ? findAcceptableStretchyCharacter(stretchedCharacter) : 0;
265     if (!m_stretchyCharacter)
266         m_isStretched = false;
267
268     RefPtr<RenderStyle> newStyle = RenderStyle::create();
269     newStyle->inheritFrom(style());
270     newStyle->setDisplay(FLEX);
271
272     if (m_isStretched)
273         style()->setHeight(Length(expandedStretchHeight(), Fixed));
274     else
275         style()->setHeight(Length());
276
277     toRenderElement(firstChild())->setPseudoStyle(newStyle.release());
278 }
279
280 int RenderMathMLOperator::firstLineBoxBaseline() const
281 {
282     if (m_isStretched)
283         return expandedStretchHeight() * 2 / 3 - (expandedStretchHeight() - m_stretchHeight) / 2;
284     return RenderMathMLBlock::firstLineBoxBaseline();
285 }
286
287 LayoutRect RenderMathMLOperator::paintCharacter(PaintInfo& info, UChar character, const LayoutPoint& origin, CharacterPaintTrimming trim)
288 {
289     GlyphData data = style()->font().glyphDataForCharacter(character, false);
290     FloatRect glyphBounds = data.fontData->boundsForGlyph(data.glyph);
291
292     LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height()));
293     glyphPaintRect.setY(origin.y() + glyphBounds.y());
294
295     // In order to have glyphs fit snugly with one another we snap the connecting edges to pixel boundaries
296     // and trim off one pixel. The pixel trim is to account for fonts that have edge pixels that have less
297     // than full coverage. These edge pixels can introduce small seams between connected glyphs
298     FloatRect clipBounds = info.rect;
299     switch (trim) {
300     case TrimTop:
301         glyphPaintRect.shiftYEdgeTo(ceilf(glyphPaintRect.y()) + 1);
302         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
303         break;
304     case TrimBottom:
305         glyphPaintRect.shiftMaxYEdgeTo(floorf(glyphPaintRect.maxY()) - 1);
306         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
307         break;
308     case TrimTopAndBottom:
309         glyphPaintRect.shiftYEdgeTo(ceilf(glyphPaintRect.y() + 1));
310         glyphPaintRect.shiftMaxYEdgeTo(floorf(glyphPaintRect.maxY()) - 1);
311         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
312         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
313         break;
314     }
315
316     // Clipping the enclosing IntRect avoids any potential issues at joined edges.
317     GraphicsContextStateSaver stateSaver(*info.context);
318     info.context->clip(clipBounds);
319
320     info.context->drawText(style()->font(), TextRun(&character, 1), origin);
321
322     return glyphPaintRect;
323 }
324
325 void RenderMathMLOperator::fillWithExtensionGlyph(PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
326 {
327     ASSERT(m_stretchyCharacter);
328     ASSERT(m_stretchyCharacter->extensionGlyph);
329     ASSERT(from.y() < to.y());
330
331     GraphicsContextStateSaver stateSaver(*info.context);
332
333     FloatRect glyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->extensionGlyph);
334
335     // Clipping the extender region here allows us to draw the bottom extender glyph into the
336     // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping.
337     IntRect clipBounds = info.rect;
338     clipBounds.shiftYEdgeTo(from.y());
339     clipBounds.shiftMaxYEdgeTo(to.y());
340     info.context->clip(clipBounds);
341
342     // Trimming may remove up to two pixels from the top of the extender glyph, so we move it up by two pixels.
343     float offsetToGlyphTop = glyphBounds.y() + 2;
344     LayoutPoint glyphOrigin = LayoutPoint(from.x(), from.y() - offsetToGlyphTop);
345     FloatRect lastPaintedGlyphRect(from, FloatSize());
346
347     while (lastPaintedGlyphRect.maxY() < to.y()) {
348         lastPaintedGlyphRect = paintCharacter(info, m_stretchyCharacter->extensionGlyph, glyphOrigin, TrimTopAndBottom);
349         glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height());
350
351         // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle
352         // with trimming. In that case we just draw nothing.
353         if (lastPaintedGlyphRect.isEmpty())
354             break;
355     }
356 }
357
358 void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset)
359 {
360     RenderMathMLBlock::paint(info, paintOffset);
361
362     if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground)
363         return;
364
365     if (!m_isStretched && !m_stretchyCharacter) {
366         RenderMathMLBlock::paint(info, paintOffset);
367         return;
368     }
369
370     GraphicsContextStateSaver stateSaver(*info.context);
371     info.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
372
373     ASSERT(m_stretchyCharacter->topGlyph);
374     ASSERT(m_stretchyCharacter->bottomGlyph);
375
376     // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box.
377     LayoutPoint operatorTopLeft = ceiledIntPoint(paintOffset + location());
378     FloatRect topGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->topGlyph);
379     LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y());
380     LayoutRect topGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->topGlyph, topGlyphOrigin, TrimBottom);
381
382     FloatRect bottomGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->bottomGlyph);
383     LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + offsetHeight() - (bottomGlyphBounds.height() + bottomGlyphBounds.y()));
384     LayoutRect bottomGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->bottomGlyph, bottomGlyphOrigin, TrimTop);
385
386     if (m_stretchyCharacter->middleGlyph) {
387         // Center the glyph origin between the start and end glyph paint extents. Then shift it half the paint height  toward the bottom glyph.
388         FloatRect middleGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->middleGlyph);
389         LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y() + y());
390         middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0));
391         middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0));
392
393         LayoutRect middleGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->middleGlyph, middleGlyphOrigin, TrimTopAndBottom);
394         fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner());
395         fillWithExtensionGlyph(info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
396     } else
397         fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
398 }
399
400 void RenderMathMLOperator::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect)
401 {
402     if (m_isStretched)
403         return;
404     RenderMathMLBlock::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect);
405 }
406     
407 }
408
409 #endif