MathOperator: Add fallback mechanisms for stretching and mirroring radical symbols
[WebKit-https.git] / Source / WebCore / rendering / mathml / MathOperator.cpp
1 /*
2  * Copyright (C) 2016 Igalia S.L. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
17  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27
28 #if ENABLE(MATHML)
29 #include "MathOperator.h"
30
31 #include "RenderStyle.h"
32 #include "StyleInheritedData.h"
33
34 static const unsigned kRadicalOperator = 0x221A;
35
36 namespace WebCore {
37
38 static inline FloatRect boundsForGlyph(const GlyphData& data)
39 {
40     return data.isValid() ? data.font->boundsForGlyph(data.glyph) : FloatRect();
41 }
42
43 static inline float heightForGlyph(const GlyphData& data)
44 {
45     return boundsForGlyph(data).height();
46 }
47
48 static inline void getAscentAndDescentForGlyph(const GlyphData& data, LayoutUnit& ascent, LayoutUnit& descent)
49 {
50     FloatRect bounds = boundsForGlyph(data);
51     ascent = -bounds.y();
52     descent = bounds.maxY();
53 }
54
55 static inline float advanceWidthForGlyph(const GlyphData& data)
56 {
57     return data.isValid() ? data.font->widthForGlyph(data.glyph) : 0;
58 }
59
60 // FIXME: This hardcoded data can be removed when OpenType MATH font are widely available (http://wkbug/156837).
61 struct StretchyCharacter {
62     UChar character;
63     UChar topChar;
64     UChar extensionChar;
65     UChar bottomChar;
66     UChar middleChar;
67 };
68 // The first leftRightPairsCount pairs correspond to left/right fences that can easily be mirrored in RTL.
69 static const short leftRightPairsCount = 5;
70 static const StretchyCharacter stretchyCharacters[14] = {
71     { 0x28  , 0x239b, 0x239c, 0x239d, 0x0    }, // left parenthesis
72     { 0x29  , 0x239e, 0x239f, 0x23a0, 0x0    }, // right parenthesis
73     { 0x5b  , 0x23a1, 0x23a2, 0x23a3, 0x0    }, // left square bracket
74     { 0x5d  , 0x23a4, 0x23a5, 0x23a6, 0x0    }, // right square bracket
75     { 0x7b  , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket
76     { 0x7d  , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
77     { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0    }, // left ceiling
78     { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0    }, // right ceiling
79     { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0    }, // left floor
80     { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0    }, // right floor
81     { 0x7c  , 0x7c,   0x7c,   0x7c,   0x0    }, // vertical bar
82     { 0x2016, 0x2016, 0x2016, 0x2016, 0x0    }, // double vertical line
83     { 0x2225, 0x2225, 0x2225, 0x2225, 0x0    }, // parallel to
84     { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0    } // integral sign
85 };
86
87 void MathOperator::setOperator(const RenderStyle& style, UChar baseCharacter, Type operatorType)
88 {
89     m_baseCharacter = baseCharacter;
90     m_operatorType = operatorType;
91     reset(style);
92 }
93
94 void MathOperator::reset(const RenderStyle& style)
95 {
96     m_stretchType = StretchType::Unstretched;
97     m_maxPreferredWidth = 0;
98     m_width = 0;
99     m_ascent = 0;
100     m_descent = 0;
101     m_italicCorrection = 0;
102     m_radicalVerticalScale = 1;
103
104     // We use the base size for the calculation of the preferred width.
105     GlyphData baseGlyph;
106     if (!getBaseGlyph(style, baseGlyph))
107         return;
108     m_maxPreferredWidth = m_width = advanceWidthForGlyph(baseGlyph);
109     getAscentAndDescentForGlyph(baseGlyph, m_ascent, m_descent);
110
111     if (m_operatorType == Type::VerticalOperator)
112         calculateStretchyData(style, true); // We also take into account the width of larger sizes for the calculation of the preferred width.
113     else if (m_operatorType == Type::DisplayOperator)
114         calculateDisplayStyleLargeOperator(style); // We can directly select the size variant and determine the final metrics.
115 }
116
117 LayoutUnit MathOperator::stretchSize() const
118 {
119     ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
120     return m_operatorType == Type::VerticalOperator ? m_ascent + m_descent : m_width;
121 }
122
123 bool MathOperator::getBaseGlyph(const RenderStyle& style, GlyphData& baseGlyph) const
124 {
125     baseGlyph = style.fontCascade().glyphDataForCharacter(m_baseCharacter, !style.isLeftToRightDirection());
126     return baseGlyph.isValid() && baseGlyph.font == &style.fontCascade().primaryFont();
127 }
128
129 void MathOperator::setSizeVariant(const GlyphData& sizeVariant)
130 {
131     ASSERT(sizeVariant.isValid());
132     ASSERT(sizeVariant.font->mathData());
133     m_stretchType = StretchType::SizeVariant;
134     m_variant = sizeVariant;
135     m_width = advanceWidthForGlyph(sizeVariant);
136     getAscentAndDescentForGlyph(sizeVariant, m_ascent, m_descent);
137 }
138
139 void MathOperator::setGlyphAssembly(const GlyphAssemblyData& assemblyData)
140 {
141     ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
142     m_stretchType = StretchType::GlyphAssembly;
143     m_assembly = assemblyData;
144     if (m_operatorType == Type::VerticalOperator) {
145         m_width = 0;
146         m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.topOrRight));
147         m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.extension));
148         m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.bottomOrLeft));
149         m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.middle));
150     } else {
151         m_ascent = 0;
152         m_descent = 0;
153         LayoutUnit ascent, descent;
154         getAscentAndDescentForGlyph(m_assembly.bottomOrLeft, ascent, descent);
155         m_ascent = std::max(m_ascent, ascent);
156         m_descent = std::max(m_descent, descent);
157         getAscentAndDescentForGlyph(m_assembly.extension, ascent, descent);
158         m_ascent = std::max(m_ascent, ascent);
159         m_descent = std::max(m_descent, descent);
160         getAscentAndDescentForGlyph(m_assembly.topOrRight, ascent, descent);
161         m_ascent = std::max(m_ascent, ascent);
162         m_descent = std::max(m_descent, descent);
163         getAscentAndDescentForGlyph(m_assembly.middle, ascent, descent);
164         m_ascent = std::max(m_ascent, ascent);
165         m_descent = std::max(m_descent, descent);
166     }
167 }
168
169 void MathOperator::calculateDisplayStyleLargeOperator(const RenderStyle& style)
170 {
171     ASSERT(m_operatorType == Type::DisplayOperator);
172
173     GlyphData baseGlyph;
174     if (!getBaseGlyph(style, baseGlyph) || !baseGlyph.font->mathData())
175         return;
176
177     // The value of displayOperatorMinHeight is sometimes too small, so we ensure that it is at least \sqrt{2} times the size of the base glyph.
178     float displayOperatorMinHeight = std::max(heightForGlyph(baseGlyph) * sqrtOfTwoFloat, baseGlyph.font->mathData()->getMathConstant(*baseGlyph.font, OpenTypeMathData::DisplayOperatorMinHeight));
179
180     Vector<Glyph> sizeVariants;
181     Vector<OpenTypeMathData::AssemblyPart> assemblyParts;
182     baseGlyph.font->mathData()->getMathVariants(baseGlyph.glyph, true, sizeVariants, assemblyParts);
183
184     // We choose the first size variant that is larger than the expected displayOperatorMinHeight and otherwise fallback to the largest variant.
185     for (auto& sizeVariant : sizeVariants) {
186         GlyphData glyphData(sizeVariant, baseGlyph.font);
187         setSizeVariant(glyphData);
188         m_maxPreferredWidth = m_width;
189         m_italicCorrection = glyphData.font->mathData()->getItalicCorrection(*glyphData.font, glyphData.glyph);
190         if (heightForGlyph(glyphData) >= displayOperatorMinHeight)
191             break;
192     }
193 }
194
195 bool MathOperator::calculateGlyphAssemblyFallback(const RenderStyle& style, const Vector<OpenTypeMathData::AssemblyPart>& assemblyParts, GlyphAssemblyData& assemblyData) const
196 {
197     // The structure of the Open Type Math table is a bit more general than the one currently used by the MathOperator code, so we try to fallback in a reasonable way.
198     // FIXME: MathOperator should support the most general format (https://bugs.webkit.org/show_bug.cgi?id=130327).
199     // We use the approach of the copyComponents function in github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py
200
201     // We count the number of non extender pieces.
202     int nonExtenderCount = 0;
203     for (auto& part : assemblyParts) {
204         if (!part.isExtender)
205             nonExtenderCount++;
206     }
207     if (nonExtenderCount > 3)
208         return false; // This is not supported: there are too many pieces.
209
210     // We now browse the list of pieces from left to right for horizontal operators and from bottom to top for vertical operators.
211     enum PartType {
212         Start,
213         ExtenderBetweenStartAndMiddle,
214         Middle,
215         ExtenderBetweenMiddleAndEnd,
216         End,
217         None
218     };
219     PartType expectedPartType = Start;
220     assemblyData.extension.glyph = 0;
221     assemblyData.middle.glyph = 0;
222     for (auto& part : assemblyParts) {
223         if (nonExtenderCount < 3) {
224             // If we only have at most two non-extenders then we skip the middle glyph.
225             if (expectedPartType == ExtenderBetweenStartAndMiddle)
226                 expectedPartType = ExtenderBetweenMiddleAndEnd;
227             else if (expectedPartType == Middle)
228                 expectedPartType = End;
229         }
230         if (part.isExtender) {
231             if (!assemblyData.extension.glyph)
232                 assemblyData.extension.glyph = part.glyph; // We copy the extender part.
233             else if (assemblyData.extension.glyph != part.glyph)
234                 return false; // This is not supported: the assembly has different extenders.
235
236             switch (expectedPartType) {
237             case Start:
238                 // We ignore the left/bottom part.
239                 expectedPartType = ExtenderBetweenStartAndMiddle;
240                 continue;
241             case Middle:
242                 // We ignore the middle part.
243                 expectedPartType = ExtenderBetweenMiddleAndEnd;
244                 continue;
245             case End:
246             case None:
247                 // This is not supported: we got an unexpected extender.
248                 return false;
249             case ExtenderBetweenStartAndMiddle:
250             case ExtenderBetweenMiddleAndEnd:
251                 // We ignore multiple consecutive extenders.
252                 continue;
253             }
254         }
255
256         switch (expectedPartType) {
257         case Start:
258             // We copy the left/bottom part.
259             assemblyData.bottomOrLeft.glyph = part.glyph;
260             expectedPartType = ExtenderBetweenStartAndMiddle;
261             continue;
262         case ExtenderBetweenStartAndMiddle:
263         case Middle:
264             // We copy the middle part.
265             assemblyData.middle.glyph = part.glyph;
266             expectedPartType = ExtenderBetweenMiddleAndEnd;
267             continue;
268         case ExtenderBetweenMiddleAndEnd:
269         case End:
270             // We copy the right/top part.
271             assemblyData.topOrRight.glyph = part.glyph;
272             expectedPartType = None;
273             continue;
274         case None:
275             // This is not supported: we got an unexpected non-extender part.
276             return false;
277         }
278     }
279
280     if (!assemblyData.extension.glyph)
281         return false; // This is not supported: we always assume that we have an extension glyph.
282
283     // If we don't have top/bottom glyphs, we use the extension glyph.
284     if (!assemblyData.topOrRight.glyph)
285         assemblyData.topOrRight.glyph = assemblyData.extension.glyph;
286     if (!assemblyData.bottomOrLeft.glyph)
287         assemblyData.bottomOrLeft.glyph = assemblyData.extension.glyph;
288
289     assemblyData.topOrRight.font = &style.fontCascade().primaryFont();
290     assemblyData.extension.font = assemblyData.topOrRight.font;
291     assemblyData.bottomOrLeft.font = assemblyData.topOrRight.font;
292     assemblyData.middle.font = assemblyData.middle.glyph ? assemblyData.topOrRight.font : nullptr;
293
294     return true;
295 }
296
297 void MathOperator::calculateStretchyData(const RenderStyle& style, bool calculateMaxPreferredWidth, LayoutUnit targetSize)
298 {
299     ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
300     ASSERT(!calculateMaxPreferredWidth || m_operatorType == Type::VerticalOperator);
301     bool isVertical = m_operatorType == Type::VerticalOperator;
302
303     GlyphData baseGlyph;
304     if (!getBaseGlyph(style, baseGlyph))
305         return;
306
307     if (!calculateMaxPreferredWidth) {
308         // We do not stretch if the base glyph is large enough.
309         float baseSize = isVertical ? heightForGlyph(baseGlyph) : advanceWidthForGlyph(baseGlyph);
310         if (targetSize <= baseSize)
311             return;
312     }
313
314     GlyphAssemblyData assemblyData;
315     if (baseGlyph.font->mathData()) {
316         Vector<Glyph> sizeVariants;
317         Vector<OpenTypeMathData::AssemblyPart> assemblyParts;
318         baseGlyph.font->mathData()->getMathVariants(baseGlyph.glyph, isVertical, sizeVariants, assemblyParts);
319         // We verify the size variants.
320         for (auto& sizeVariant : sizeVariants) {
321             GlyphData glyphData(sizeVariant, baseGlyph.font);
322             if (calculateMaxPreferredWidth)
323                 m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(glyphData));
324             else {
325                 setSizeVariant(glyphData);
326                 LayoutUnit size = isVertical ? heightForGlyph(glyphData) : advanceWidthForGlyph(glyphData);
327                 if (size >= targetSize)
328                     return;
329             }
330         }
331
332         // We verify if there is a construction.
333         if (!calculateGlyphAssemblyFallback(style, assemblyParts, assemblyData))
334             return;
335     } else {
336         if (!isVertical)
337             return;
338
339         // If the font does not have a MATH table, we fallback to the Unicode-only constructions.
340         const StretchyCharacter* stretchyCharacter = nullptr;
341         const unsigned maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters);
342         for (unsigned index = 0; index < maxIndex; ++index) {
343             if (stretchyCharacters[index].character == m_baseCharacter) {
344                 stretchyCharacter = &stretchyCharacters[index];
345                 if (!style.isLeftToRightDirection() && index < leftRightPairsCount * 2) {
346                     // If we are in right-to-left direction we select the mirrored form by adding -1 or +1 according to the parity of index.
347                     index += index % 2 ? -1 : 1;
348                 }
349                 break;
350             }
351         }
352
353         // Unicode contains U+23B7 RADICAL SYMBOL BOTTOM but it is generally not provided by fonts without a MATH table.
354         // Moreover, it's not clear what the proper vertical extender or top hook would be.
355         // Hence we fallback to scaling the base glyph vertically.
356         if (!calculateMaxPreferredWidth && m_baseCharacter == kRadicalOperator) {
357             LayoutUnit height = m_ascent + m_descent;
358             if (height > 0 && height < targetSize) {
359                 m_radicalVerticalScale = targetSize.toFloat() / height;
360                 m_ascent *= m_radicalVerticalScale;
361                 m_descent *= m_radicalVerticalScale;
362             }
363             return;
364         }
365
366         // If we didn't find a stretchy character set for this character, we don't know how to stretch it.
367         if (!stretchyCharacter)
368             return;
369
370         // We convert the list of Unicode characters into a list of glyph data.
371         assemblyData.topOrRight = style.fontCascade().glyphDataForCharacter(stretchyCharacter->topChar, false);
372         assemblyData.extension = style.fontCascade().glyphDataForCharacter(stretchyCharacter->extensionChar, false);
373         assemblyData.bottomOrLeft = style.fontCascade().glyphDataForCharacter(stretchyCharacter->bottomChar, false);
374         assemblyData.middle = stretchyCharacter->middleChar ? style.fontCascade().glyphDataForCharacter(stretchyCharacter->middleChar, false) : GlyphData();
375     }
376
377     // If we are measuring the maximum width, verify each component.
378     if (calculateMaxPreferredWidth) {
379         m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.topOrRight));
380         m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.extension));
381         m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.middle));
382         m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.bottomOrLeft));
383         return;
384     }
385
386     // We ensure that the size is large enough to avoid glyph overlaps.
387     float minSize = isVertical ?
388         heightForGlyph(assemblyData.topOrRight) + heightForGlyph(assemblyData.middle) + heightForGlyph(assemblyData.bottomOrLeft)
389         : advanceWidthForGlyph(assemblyData.bottomOrLeft) + advanceWidthForGlyph(assemblyData.middle) + advanceWidthForGlyph(assemblyData.topOrRight);
390     if (minSize > targetSize)
391         return;
392
393     setGlyphAssembly(assemblyData);
394 }
395
396 void MathOperator::stretchTo(const RenderStyle& style, LayoutUnit ascent, LayoutUnit descent)
397 {
398     ASSERT(m_operatorType == Type::VerticalOperator);
399     calculateStretchyData(style, false, ascent + descent);
400     if (m_stretchType == StretchType::GlyphAssembly) {
401         m_ascent = ascent;
402         m_descent = descent;
403     }
404 }
405
406 void MathOperator::stretchTo(const RenderStyle& style, LayoutUnit width)
407 {
408     ASSERT(m_operatorType == Type::HorizontalOperator);
409     calculateStretchyData(style, false, width);
410     if (m_stretchType == StretchType::GlyphAssembly)
411         m_width = width;
412 }
413
414 LayoutRect MathOperator::paintGlyph(const RenderStyle& style, PaintInfo& info, const GlyphData& data, const LayoutPoint& origin, GlyphPaintTrimming trim)
415 {
416     FloatRect glyphBounds = boundsForGlyph(data);
417
418     LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height()));
419     glyphPaintRect.setY(origin.y() + glyphBounds.y());
420
421     // In order to have glyphs fit snugly with one another we snap the connecting edges to pixel boundaries
422     // and trim off one pixel. The pixel trim is to account for fonts that have edge pixels that have less
423     // than full coverage. These edge pixels can introduce small seams between connected glyphs.
424     FloatRect clipBounds = info.rect;
425     switch (trim) {
426     case TrimTop:
427         glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1);
428         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
429         break;
430     case TrimBottom:
431         glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
432         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
433         break;
434     case TrimTopAndBottom:
435         glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1);
436         glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
437         clipBounds.shiftYEdgeTo(glyphPaintRect.y());
438         clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
439         break;
440     case TrimLeft:
441         glyphPaintRect.shiftXEdgeTo(glyphPaintRect.x().ceil() + 1);
442         clipBounds.shiftXEdgeTo(glyphPaintRect.x());
443         break;
444     case TrimRight:
445         glyphPaintRect.shiftMaxXEdgeTo(glyphPaintRect.maxX().floor() - 1);
446         clipBounds.shiftMaxXEdgeTo(glyphPaintRect.maxX());
447         break;
448     case TrimLeftAndRight:
449         glyphPaintRect.shiftXEdgeTo(glyphPaintRect.x().ceil() + 1);
450         glyphPaintRect.shiftMaxXEdgeTo(glyphPaintRect.maxX().floor() - 1);
451         clipBounds.shiftXEdgeTo(glyphPaintRect.x());
452         clipBounds.shiftMaxXEdgeTo(glyphPaintRect.maxX());
453     }
454
455     // Clipping the enclosing IntRect avoids any potential issues at joined edges.
456     GraphicsContextStateSaver stateSaver(info.context());
457     info.context().clip(clipBounds);
458
459     GlyphBuffer buffer;
460     buffer.add(data.glyph, data.font, advanceWidthForGlyph(data));
461     info.context().drawGlyphs(style.fontCascade(), *data.font, buffer, 0, 1, origin);
462
463     return glyphPaintRect;
464 }
465
466 void MathOperator::fillWithVerticalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
467 {
468     ASSERT(m_operatorType == Type::VerticalOperator);
469     ASSERT(m_stretchType == StretchType::GlyphAssembly);
470     ASSERT(m_assembly.extension.isValid());
471     ASSERT(from.y() <= to.y());
472
473     // If there is no space for the extension glyph, we don't need to do anything.
474     if (from.y() == to.y())
475         return;
476
477     GraphicsContextStateSaver stateSaver(info.context());
478
479     FloatRect glyphBounds = boundsForGlyph(m_assembly.extension);
480
481     // Clipping the extender region here allows us to draw the bottom extender glyph into the
482     // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping.
483     LayoutRect clipBounds = info.rect;
484     clipBounds.shiftYEdgeTo(from.y());
485     clipBounds.shiftMaxYEdgeTo(to.y());
486     info.context().clip(clipBounds);
487
488     // Trimming may remove up to two pixels from the top of the extender glyph, so we move it up by two pixels.
489     float offsetToGlyphTop = glyphBounds.y() + 2;
490     LayoutPoint glyphOrigin = LayoutPoint(from.x(), from.y() - offsetToGlyphTop);
491     FloatRect lastPaintedGlyphRect(from, FloatSize());
492
493     // FIXME: In practice, only small stretch sizes are requested but we may need to limit the number
494     // of pieces that can be repeated to avoid hangs. See http://webkit.org/b/155434
495     while (lastPaintedGlyphRect.maxY() < to.y()) {
496         lastPaintedGlyphRect = paintGlyph(style, info, m_assembly.extension, glyphOrigin, TrimTopAndBottom);
497         glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height());
498
499         // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle
500         // with trimming. In that case we just draw nothing.
501         if (lastPaintedGlyphRect.isEmpty())
502             break;
503     }
504 }
505
506 void MathOperator::fillWithHorizontalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
507 {
508     ASSERT(m_operatorType == Type::HorizontalOperator);
509     ASSERT(m_stretchType == StretchType::GlyphAssembly);
510     ASSERT(m_assembly.extension.isValid());
511     ASSERT(from.x() <= to.x());
512     ASSERT(from.y() == to.y());
513
514     // If there is no space for the extension glyph, we don't need to do anything.
515     if (from.x() == to.x())
516         return;
517
518     GraphicsContextStateSaver stateSaver(info.context());
519
520     // Clipping the extender region here allows us to draw the bottom extender glyph into the
521     // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping.
522     LayoutRect clipBounds = info.rect;
523     clipBounds.shiftXEdgeTo(from.x());
524     clipBounds.shiftMaxXEdgeTo(to.x());
525     info.context().clip(clipBounds);
526
527     // Trimming may remove up to two pixels from the left of the extender glyph, so we move it left by two pixels.
528     float offsetToGlyphLeft = -2;
529     LayoutPoint glyphOrigin = LayoutPoint(from.x() + offsetToGlyphLeft, from.y());
530     FloatRect lastPaintedGlyphRect(from, FloatSize());
531
532     // FIXME: In practice, only small stretch sizes are requested but we may need to limit the number
533     // of pieces that can be repeated to avoid hangs. See http://webkit.org/b/155434
534     while (lastPaintedGlyphRect.maxX() < to.x()) {
535         lastPaintedGlyphRect = paintGlyph(style, info, m_assembly.extension, glyphOrigin, TrimLeftAndRight);
536         glyphOrigin.setX(glyphOrigin.x() + lastPaintedGlyphRect.width());
537
538         // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle
539         // with trimming. In that case we just draw nothing.
540         if (lastPaintedGlyphRect.isEmpty())
541             break;
542     }
543 }
544
545 void MathOperator::paintVerticalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
546 {
547     ASSERT(m_operatorType == Type::VerticalOperator);
548     ASSERT(m_stretchType == StretchType::GlyphAssembly);
549     ASSERT(m_assembly.topOrRight.isValid());
550     ASSERT(m_assembly.bottomOrLeft.isValid());
551
552     // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box.
553     LayoutPoint operatorTopLeft = paintOffset;
554     FloatRect topGlyphBounds = boundsForGlyph(m_assembly.topOrRight);
555     LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y());
556     LayoutRect topGlyphPaintRect = paintGlyph(style, info, m_assembly.topOrRight, topGlyphOrigin, TrimBottom);
557
558     FloatRect bottomGlyphBounds = boundsForGlyph(m_assembly.bottomOrLeft);
559     LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + stretchSize() - (bottomGlyphBounds.height() + bottomGlyphBounds.y()));
560     LayoutRect bottomGlyphPaintRect = paintGlyph(style, info, m_assembly.bottomOrLeft, bottomGlyphOrigin, TrimTop);
561
562     if (m_assembly.middle.isValid()) {
563         // Center the glyph origin between the start and end glyph paint extents. Then shift it half the paint height toward the bottom glyph.
564         FloatRect middleGlyphBounds = boundsForGlyph(m_assembly.middle);
565         LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y());
566         middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0));
567         middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0));
568
569         LayoutRect middleGlyphPaintRect = paintGlyph(style, info, m_assembly.middle, middleGlyphOrigin, TrimTopAndBottom);
570         fillWithVerticalExtensionGlyph(style, info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner());
571         fillWithVerticalExtensionGlyph(style, info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
572     } else
573         fillWithVerticalExtensionGlyph(style, info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
574 }
575
576 void MathOperator::paintHorizontalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
577 {
578     ASSERT(m_operatorType == Type::HorizontalOperator);
579     ASSERT(m_stretchType == StretchType::GlyphAssembly);
580     ASSERT(m_assembly.bottomOrLeft.isValid());
581     ASSERT(m_assembly.topOrRight.isValid());
582
583     // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box.
584     LayoutPoint operatorTopLeft = paintOffset;
585     LayoutUnit baselineY = operatorTopLeft.y() + m_ascent;
586     LayoutPoint leftGlyphOrigin(operatorTopLeft.x(), baselineY);
587     LayoutRect leftGlyphPaintRect = paintGlyph(style, info, m_assembly.bottomOrLeft, leftGlyphOrigin, TrimRight);
588
589     FloatRect rightGlyphBounds = boundsForGlyph(m_assembly.topOrRight);
590     LayoutPoint rightGlyphOrigin(operatorTopLeft.x() + stretchSize() - rightGlyphBounds.width(), baselineY);
591     LayoutRect rightGlyphPaintRect = paintGlyph(style, info, m_assembly.topOrRight, rightGlyphOrigin, TrimLeft);
592
593     if (m_assembly.middle.isValid()) {
594         // Center the glyph origin between the start and end glyph paint extents.
595         LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), baselineY);
596         middleGlyphOrigin.moveBy(LayoutPoint((rightGlyphPaintRect.x() - leftGlyphPaintRect.maxX()) / 2.0, 0));
597         LayoutRect middleGlyphPaintRect = paintGlyph(style, info, m_assembly.middle, middleGlyphOrigin, TrimLeftAndRight);
598         fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(leftGlyphPaintRect.maxX(), baselineY), LayoutPoint(middleGlyphPaintRect.x(), baselineY));
599         fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(middleGlyphPaintRect.maxX(), baselineY), LayoutPoint(rightGlyphPaintRect.x(), baselineY));
600     } else
601         fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(leftGlyphPaintRect.maxX(), baselineY), LayoutPoint(rightGlyphPaintRect.x(), baselineY));
602 }
603
604 void MathOperator::paint(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
605 {
606     if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style.visibility() != VISIBLE)
607         return;
608
609     GraphicsContextStateSaver stateSaver(info.context());
610     info.context().setFillColor(style.visitedDependentColor(CSSPropertyColor));
611
612     // For a radical character, we may need some scale transform to stretch it vertically or mirror it.
613     if (m_baseCharacter == kRadicalOperator) {
614         float radicalHorizontalScale = style.isLeftToRightDirection() ? 1 : -1;
615         if (radicalHorizontalScale == -1 || m_radicalVerticalScale > 1) {
616             LayoutPoint scaleOrigin = paintOffset;
617             scaleOrigin.move(m_width / 2, 0);
618             info.applyTransform(AffineTransform().translate(scaleOrigin).scale(radicalHorizontalScale, m_radicalVerticalScale).translate(-scaleOrigin));
619         }
620     }
621
622     if (m_stretchType == StretchType::GlyphAssembly) {
623         if (m_operatorType == Type::VerticalOperator)
624             paintVerticalGlyphAssembly(style, info, paintOffset);
625         else
626             paintHorizontalGlyphAssembly(style, info, paintOffset);
627         return;
628     }
629
630     GlyphData glyphData;
631     ASSERT(m_stretchType == StretchType::Unstretched || m_stretchType == StretchType::SizeVariant);
632     if (m_stretchType == StretchType::Unstretched) {
633         if (!getBaseGlyph(style, glyphData))
634             return;
635     } else if (m_stretchType == StretchType::SizeVariant) {
636         ASSERT(m_variant.isValid());
637         glyphData = m_variant;
638     }
639     GlyphBuffer buffer;
640     buffer.add(glyphData.glyph, glyphData.font, advanceWidthForGlyph(glyphData));
641     LayoutPoint operatorTopLeft = paintOffset;
642     FloatRect glyphBounds = boundsForGlyph(glyphData);
643     LayoutPoint operatorOrigin(operatorTopLeft.x(), operatorTopLeft.y() - glyphBounds.y());
644     info.context().drawGlyphs(style.fontCascade(), *glyphData.font, buffer, 0, 1, operatorOrigin);
645 }
646
647 }
648
649 #endif