e88f63c8d57c4a6214811ce79a1102958a4cd3bb
[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  , 0x7c,   0x7c,   0x7c,   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(MathMLElement& element, PassRef<RenderStyle> style)
66     : RenderMathMLBlock(element, std::move(style))
67     , m_stretchHeight(0)
68     , m_operator(0)
69     , m_operatorType(Default)
70     , m_stretchyCharacter(nullptr)
71 {
72 }
73
74 RenderMathMLOperator::RenderMathMLOperator(MathMLElement& element, PassRef<RenderStyle> style, UChar operatorChar)
75     : RenderMathMLBlock(element, std::move(style))
76     , m_stretchHeight(0)
77     , m_operator(convertHyphenMinusToMinusSign(operatorChar))
78     , m_operatorType(Default)
79     , m_stretchyCharacter(nullptr)
80 {
81 }
82
83 bool RenderMathMLOperator::isChildAllowed(const RenderObject&, const 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     RenderElement* 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     auto newStyle = RenderStyle::create();
176     newStyle.get().inheritFrom(&style());
177     newStyle.get().setDisplay(FLEX);
178
179     RenderMathMLBlock* container = new RenderMathMLBlock(element(), std::move(newStyle));
180     // This container doesn't offer any useful information to accessibility.
181     container->setIgnoreInAccessibilityTree(true);
182     container->initializeStyle();
183
184     addChild(container);
185     RenderText* text;
186     if (m_operator)
187         text = new RenderText(document(), String(&m_operator, 1));
188     else
189         text = new RenderText(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     if (equalIgnoringCase(element().getAttribute(MathMLNames::stretchyAttr), "false"))
202         return false;
203
204     if (m_operator) {
205         stretchedCharacter = m_operator;
206         return true;
207     }
208
209     // FIXME: This does not handle surrogate pairs (http://wkbug.com/122296/).
210     String opText = element().textContent();
211     stretchedCharacter = 0;
212     for (unsigned i = 0; i < opText.length(); ++i) {
213         // If there's more than one non-whitespace character in this node, then don't even try to stretch it.
214         if (stretchedCharacter && !isSpaceOrNewline(opText[i]))
215             return false;
216
217         if (!isSpaceOrNewline(opText[i]))
218             stretchedCharacter = opText[i];
219     }
220
221     return stretchedCharacter;
222 }
223
224 // FIXME: We should also look at alternate characters defined in the OpenType MATH table (http://wkbug/122297).
225 RenderMathMLOperator::StretchyCharacter* RenderMathMLOperator::findAcceptableStretchyCharacter(UChar character)
226 {
227     StretchyCharacter* stretchyCharacter = 0;
228     const int maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters);
229     for (int index = 0; index < maxIndex; ++index) {
230         if (stretchyCharacters[index].character == character) {
231             stretchyCharacter = &stretchyCharacters[index];
232             break;
233         }
234     }
235
236     // If we didn't find a stretchy character set for this character, we don't know how to stretch it.
237     if (!stretchyCharacter)
238         return 0;
239
240     float height = glyphHeightForCharacter(stretchyCharacter->topGlyph) + glyphHeightForCharacter(stretchyCharacter->bottomGlyph);
241     if (stretchyCharacter->middleGlyph)
242         height += glyphHeightForCharacter(stretchyCharacter->middleGlyph);
243
244     if (height > expandedStretchHeight())
245         return 0;
246
247     return stretchyCharacter;
248 }
249
250 void RenderMathMLOperator::updateStyle()
251 {
252     ASSERT(firstChild());
253     if (!firstChild())
254         return;
255
256     UChar stretchedCharacter;
257     bool allowStretching = shouldAllowStretching(stretchedCharacter);
258
259     float stretchedCharacterHeight = style().fontMetrics().floatHeight();
260     m_isStretched = allowStretching && expandedStretchHeight() > stretchedCharacterHeight;
261
262     // Sometimes we cannot stretch an operator properly, so in that case, we should just use the original size.
263     m_stretchyCharacter = m_isStretched ? findAcceptableStretchyCharacter(stretchedCharacter) : 0;
264     if (!m_stretchyCharacter)
265         m_isStretched = false;
266 }
267
268 int RenderMathMLOperator::firstLineBaseline() const
269 {
270     if (m_isStretched)
271         return expandedStretchHeight() * 2 / 3 - (expandedStretchHeight() - m_stretchHeight) / 2;
272     return RenderMathMLBlock::firstLineBaseline();
273 }
274
275 void RenderMathMLOperator::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
276 {
277     if (m_isStretched)
278         logicalHeight = expandedStretchHeight();
279     RenderBox::computeLogicalHeight(logicalHeight, logicalTop, computedValues);
280 }
281
282 LayoutRect RenderMathMLOperator::paintCharacter(PaintInfo& info, UChar character, const LayoutPoint& origin, CharacterPaintTrimming trim)
283 {
284     GlyphData data = style().font().glyphDataForCharacter(character, false);
285     FloatRect glyphBounds = data.fontData->boundsForGlyph(data.glyph);
286
287     LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height()));
288     glyphPaintRect.setY(origin.y() + glyphBounds.y());
289
290     // In order to have glyphs fit snugly with one another we snap the connecting edges to pixel boundaries
291     // and trim off one pixel. The pixel trim is to account for fonts that have edge pixels that have less
292     // than full coverage. These edge pixels can introduce small seams between connected glyphs
293     FloatRect clipBounds = info.rect;
294     switch (trim) {
295     case TrimTop:
296         glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1);
297         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
298         break;
299     case TrimBottom:
300         glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
301         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
302         break;
303     case TrimTopAndBottom:
304         LayoutUnit temp = glyphPaintRect.y() + 1;
305         glyphPaintRect.shiftYEdgeTo(temp.ceil());
306         glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
307         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
308         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
309         break;
310     }
311
312     // Clipping the enclosing IntRect avoids any potential issues at joined edges.
313     GraphicsContextStateSaver stateSaver(*info.context);
314     info.context->clip(clipBounds);
315
316     info.context->drawText(style().font(), TextRun(&character, 1), origin);
317
318     return glyphPaintRect;
319 }
320
321 void RenderMathMLOperator::fillWithExtensionGlyph(PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
322 {
323     ASSERT(m_stretchyCharacter);
324     ASSERT(m_stretchyCharacter->extensionGlyph);
325     ASSERT(from.y() <= to.y());
326
327     // If there is no space for the extension glyph, we don't need to do anything.
328     if (from.y() == to.y())
329         return;
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());
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