[MathML] The double bar vertical delimiter does not stretch properly
[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[14] = {
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     { 0x2225, 0x2225, 0x2225, 0x2225, 0x0    }, // parallel to
62     { 0x7d  , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
63     { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0    } // integral sign
64 };
65
66 RenderMathMLOperator::RenderMathMLOperator(MathMLElement& element, PassRef<RenderStyle> style)
67     : RenderMathMLBlock(element, std::move(style))
68     , m_stretchHeight(0)
69     , m_operator(0)
70     , m_operatorType(Default)
71     , m_stretchyCharacter(nullptr)
72 {
73 }
74
75 RenderMathMLOperator::RenderMathMLOperator(MathMLElement& element, PassRef<RenderStyle> style, UChar operatorChar)
76     : RenderMathMLBlock(element, std::move(style))
77     , m_stretchHeight(0)
78     , m_operator(convertHyphenMinusToMinusSign(operatorChar))
79     , m_operatorType(Default)
80     , m_stretchyCharacter(nullptr)
81 {
82 }
83
84 bool RenderMathMLOperator::isChildAllowed(const RenderObject&, const RenderStyle&) const
85 {
86     return false;
87 }
88
89 static const float gOperatorExpansion = 1.2f;
90
91 float RenderMathMLOperator::expandedStretchHeight() const
92 {
93     return m_stretchHeight * gOperatorExpansion;
94 }
95
96 void RenderMathMLOperator::stretchToHeight(int height)
97 {
98     if (m_stretchHeight == height)
99         return;
100
101     m_stretchHeight = height;
102     updateStyle();
103 }
104
105 void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
106 {
107     RenderMathMLBlock::styleDidChange(diff, oldStyle);
108     updateFromElement();
109 }
110
111 FloatRect RenderMathMLOperator::glyphBoundsForCharacter(UChar character)
112 {
113     GlyphData data = style().font().glyphDataForCharacter(character, false);
114     return data.fontData->boundsForGlyph(data.glyph);
115 }
116
117 float RenderMathMLOperator::glyphHeightForCharacter(UChar character)
118 {
119     return glyphBoundsForCharacter(character).height();
120 }
121
122 float RenderMathMLOperator::advanceForCharacter(UChar character)
123 {
124     // Hyphen minus is always replaced by the minus sign in rendered text.
125     GlyphData data = style().font().glyphDataForCharacter(convertHyphenMinusToMinusSign(character), false);
126     return data.fontData->widthForGlyph(data.glyph);
127 }
128
129 void RenderMathMLOperator::computePreferredLogicalWidths()
130 {
131     ASSERT(preferredLogicalWidthsDirty());
132
133     UChar stretchedCharacter;
134     bool allowStretching = shouldAllowStretching(stretchedCharacter);
135     if (!allowStretching) {
136         RenderMathMLBlock::computePreferredLogicalWidths();
137         return;
138     }
139
140     float maximumGlyphWidth = advanceForCharacter(stretchedCharacter);
141     for (unsigned index = 0; index < WTF_ARRAY_LENGTH(stretchyCharacters); ++index) {
142         if (stretchyCharacters[index].character != stretchedCharacter)
143             continue;
144
145         StretchyCharacter& character = stretchyCharacters[index];
146         if (character.topGlyph)
147             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.topGlyph));
148         if (character.extensionGlyph)
149             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.extensionGlyph));
150         if (character.bottomGlyph)
151             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.bottomGlyph));
152         if (character.middleGlyph)
153             maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.middleGlyph));
154         m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth;
155         return;
156     }
157
158     m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth;
159 }
160
161 // FIXME: It's cleaner to only call updateFromElement when an attribute has changed. The body of
162 // this method should probably be moved to a private stretchHeightChanged or checkStretchHeight
163 // method. Probably at the same time, addChild/removeChild methods should be made to work for
164 // dynamic DOM changes.
165 void RenderMathMLOperator::updateFromElement()
166 {
167     RenderElement* savedRenderer = element().renderer();
168
169     // Destroy our current children
170     destroyLeftoverChildren();
171
172     // Since we share a node with our children, destroying our children may set our node's
173     // renderer to 0, so we need to restore it.
174     element().setRenderer(savedRenderer);
175     
176     auto newStyle = RenderStyle::create();
177     newStyle.get().inheritFrom(&style());
178     newStyle.get().setDisplay(FLEX);
179
180     RenderMathMLBlock* container = new RenderMathMLBlock(element(), std::move(newStyle));
181     // This container doesn't offer any useful information to accessibility.
182     container->setIgnoreInAccessibilityTree(true);
183     container->initializeStyle();
184
185     addChild(container);
186     RenderText* text;
187     if (m_operator)
188         text = new RenderText(document(), String(&m_operator, 1));
189     else
190         text = new RenderText(document(), element().textContent().replace(hyphenMinus, minusSign).impl());
191
192     // If we can't figure out the text, leave it blank.
193     if (text)
194         container->addChild(text);
195
196     updateStyle();
197     setNeedsLayoutAndPrefWidthsRecalc();
198 }
199
200 bool RenderMathMLOperator::shouldAllowStretching(UChar& stretchedCharacter)
201 {
202     if (equalIgnoringCase(element().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 = element().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
269 int RenderMathMLOperator::firstLineBaseline() const
270 {
271     if (m_isStretched)
272         return expandedStretchHeight() * 2 / 3 - (expandedStretchHeight() - m_stretchHeight) / 2;
273     return RenderMathMLBlock::firstLineBaseline();
274 }
275
276 void RenderMathMLOperator::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
277 {
278     if (m_isStretched)
279         logicalHeight = expandedStretchHeight();
280     RenderBox::computeLogicalHeight(logicalHeight, logicalTop, computedValues);
281 }
282
283 LayoutRect RenderMathMLOperator::paintCharacter(PaintInfo& info, UChar character, const LayoutPoint& origin, CharacterPaintTrimming trim)
284 {
285     GlyphData data = style().font().glyphDataForCharacter(character, false);
286     FloatRect glyphBounds = data.fontData->boundsForGlyph(data.glyph);
287
288     LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height()));
289     glyphPaintRect.setY(origin.y() + glyphBounds.y());
290
291     // In order to have glyphs fit snugly with one another we snap the connecting edges to pixel boundaries
292     // and trim off one pixel. The pixel trim is to account for fonts that have edge pixels that have less
293     // than full coverage. These edge pixels can introduce small seams between connected glyphs
294     FloatRect clipBounds = info.rect;
295     switch (trim) {
296     case TrimTop:
297         glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1);
298         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
299         break;
300     case TrimBottom:
301         glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
302         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
303         break;
304     case TrimTopAndBottom:
305         LayoutUnit temp = glyphPaintRect.y() + 1;
306         glyphPaintRect.shiftYEdgeTo(temp.ceil());
307         glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
308         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
309         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
310         break;
311     }
312
313     // Clipping the enclosing IntRect avoids any potential issues at joined edges.
314     GraphicsContextStateSaver stateSaver(*info.context);
315     info.context->clip(clipBounds);
316
317     info.context->drawText(style().font(), TextRun(&character, 1), origin);
318
319     return glyphPaintRect;
320 }
321
322 void RenderMathMLOperator::fillWithExtensionGlyph(PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
323 {
324     ASSERT(m_stretchyCharacter);
325     ASSERT(m_stretchyCharacter->extensionGlyph);
326     ASSERT(from.y() <= to.y());
327
328     // If there is no space for the extension glyph, we don't need to do anything.
329     if (from.y() == to.y())
330         return;
331
332     GraphicsContextStateSaver stateSaver(*info.context);
333
334     FloatRect glyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->extensionGlyph);
335
336     // Clipping the extender region here allows us to draw the bottom extender glyph into the
337     // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping.
338     IntRect clipBounds = info.rect;
339     clipBounds.shiftYEdgeTo(from.y());
340     clipBounds.shiftMaxYEdgeTo(to.y());
341     info.context->clip(clipBounds);
342
343     // Trimming may remove up to two pixels from the top of the extender glyph, so we move it up by two pixels.
344     float offsetToGlyphTop = glyphBounds.y() + 2;
345     LayoutPoint glyphOrigin = LayoutPoint(from.x(), from.y() - offsetToGlyphTop);
346     FloatRect lastPaintedGlyphRect(from, FloatSize());
347
348     while (lastPaintedGlyphRect.maxY() < to.y()) {
349         lastPaintedGlyphRect = paintCharacter(info, m_stretchyCharacter->extensionGlyph, glyphOrigin, TrimTopAndBottom);
350         glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height());
351
352         // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle
353         // with trimming. In that case we just draw nothing.
354         if (lastPaintedGlyphRect.isEmpty())
355             break;
356     }
357 }
358
359 void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset)
360 {
361     RenderMathMLBlock::paint(info, paintOffset);
362
363     if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground)
364         return;
365
366     if (!m_isStretched && !m_stretchyCharacter) {
367         RenderMathMLBlock::paint(info, paintOffset);
368         return;
369     }
370
371     GraphicsContextStateSaver stateSaver(*info.context);
372     info.context->setFillColor(style().visitedDependentColor(CSSPropertyColor), style().colorSpace());
373
374     ASSERT(m_stretchyCharacter->topGlyph);
375     ASSERT(m_stretchyCharacter->bottomGlyph);
376
377     // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box.
378     LayoutPoint operatorTopLeft = ceiledIntPoint(paintOffset + location());
379     FloatRect topGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->topGlyph);
380     LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y());
381     LayoutRect topGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->topGlyph, topGlyphOrigin, TrimBottom);
382
383     FloatRect bottomGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->bottomGlyph);
384     LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + offsetHeight() - (bottomGlyphBounds.height() + bottomGlyphBounds.y()));
385     LayoutRect bottomGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->bottomGlyph, bottomGlyphOrigin, TrimTop);
386
387     if (m_stretchyCharacter->middleGlyph) {
388         // Center the glyph origin between the start and end glyph paint extents. Then shift it half the paint height toward the bottom glyph.
389         FloatRect middleGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->middleGlyph);
390         LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y());
391         middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0));
392         middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0));
393
394         LayoutRect middleGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->middleGlyph, middleGlyphOrigin, TrimTopAndBottom);
395         fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner());
396         fillWithExtensionGlyph(info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
397     } else
398         fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
399 }
400
401 void RenderMathMLOperator::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect)
402 {
403     if (m_isStretched)
404         return;
405     RenderMathMLBlock::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect);
406 }
407     
408 }
409
410 #endif