2 * Copyright (C) 2016 Igalia S.L. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "MathOperator.h"
31 #include "RenderStyle.h"
32 #include "StyleInheritedData.h"
34 static const unsigned kRadicalOperator = 0x221A;
38 static inline FloatRect boundsForGlyph(const GlyphData& data)
40 return data.isValid() ? data.font->boundsForGlyph(data.glyph) : FloatRect();
43 static inline float heightForGlyph(const GlyphData& data)
45 return boundsForGlyph(data).height();
48 static inline void getAscentAndDescentForGlyph(const GlyphData& data, LayoutUnit& ascent, LayoutUnit& descent)
50 FloatRect bounds = boundsForGlyph(data);
52 descent = bounds.maxY();
55 static inline float advanceWidthForGlyph(const GlyphData& data)
57 return data.isValid() ? data.font->widthForGlyph(data.glyph) : 0;
60 // FIXME: This hardcoded data can be removed when OpenType MATH font are widely available (http://wkbug/156837).
61 struct StretchyCharacter {
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
87 void MathOperator::setOperator(const RenderStyle& style, UChar baseCharacter, Type operatorType)
89 m_baseCharacter = baseCharacter;
90 m_operatorType = operatorType;
94 void MathOperator::reset(const RenderStyle& style)
96 m_stretchType = StretchType::Unstretched;
97 m_maxPreferredWidth = 0;
101 m_italicCorrection = 0;
102 m_radicalVerticalScale = 1;
104 // We use the base size for the calculation of the preferred width.
106 if (!getBaseGlyph(style, baseGlyph))
108 m_maxPreferredWidth = m_width = advanceWidthForGlyph(baseGlyph);
109 getAscentAndDescentForGlyph(baseGlyph, m_ascent, m_descent);
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.
117 LayoutUnit MathOperator::stretchSize() const
119 ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
120 return m_operatorType == Type::VerticalOperator ? m_ascent + m_descent : m_width;
123 bool MathOperator::getBaseGlyph(const RenderStyle& style, GlyphData& baseGlyph) const
125 baseGlyph = style.fontCascade().glyphDataForCharacter(m_baseCharacter, !style.isLeftToRightDirection());
126 return baseGlyph.isValid() && baseGlyph.font == &style.fontCascade().primaryFont();
129 void MathOperator::setSizeVariant(const GlyphData& sizeVariant)
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);
139 void MathOperator::setGlyphAssembly(const GlyphAssemblyData& assemblyData)
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) {
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));
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);
169 void MathOperator::calculateDisplayStyleLargeOperator(const RenderStyle& style)
171 ASSERT(m_operatorType == Type::DisplayOperator);
174 if (!getBaseGlyph(style, baseGlyph) || !baseGlyph.font->mathData())
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));
180 Vector<Glyph> sizeVariants;
181 Vector<OpenTypeMathData::AssemblyPart> assemblyParts;
182 baseGlyph.font->mathData()->getMathVariants(baseGlyph.glyph, true, sizeVariants, assemblyParts);
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)
195 bool MathOperator::calculateGlyphAssemblyFallback(const RenderStyle& style, const Vector<OpenTypeMathData::AssemblyPart>& assemblyParts, GlyphAssemblyData& assemblyData) const
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
201 // We count the number of non extender pieces.
202 int nonExtenderCount = 0;
203 for (auto& part : assemblyParts) {
204 if (!part.isExtender)
207 if (nonExtenderCount > 3)
208 return false; // This is not supported: there are too many pieces.
210 // We now browse the list of pieces from left to right for horizontal operators and from bottom to top for vertical operators.
213 ExtenderBetweenStartAndMiddle,
215 ExtenderBetweenMiddleAndEnd,
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;
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.
236 switch (expectedPartType) {
238 // We ignore the left/bottom part.
239 expectedPartType = ExtenderBetweenStartAndMiddle;
242 // We ignore the middle part.
243 expectedPartType = ExtenderBetweenMiddleAndEnd;
247 // This is not supported: we got an unexpected extender.
249 case ExtenderBetweenStartAndMiddle:
250 case ExtenderBetweenMiddleAndEnd:
251 // We ignore multiple consecutive extenders.
256 switch (expectedPartType) {
258 // We copy the left/bottom part.
259 assemblyData.bottomOrLeft.glyph = part.glyph;
260 expectedPartType = ExtenderBetweenStartAndMiddle;
262 case ExtenderBetweenStartAndMiddle:
264 // We copy the middle part.
265 assemblyData.middle.glyph = part.glyph;
266 expectedPartType = ExtenderBetweenMiddleAndEnd;
268 case ExtenderBetweenMiddleAndEnd:
270 // We copy the right/top part.
271 assemblyData.topOrRight.glyph = part.glyph;
272 expectedPartType = None;
275 // This is not supported: we got an unexpected non-extender part.
280 if (!assemblyData.extension.glyph)
281 return false; // This is not supported: we always assume that we have an extension glyph.
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;
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;
297 void MathOperator::calculateStretchyData(const RenderStyle& style, bool calculateMaxPreferredWidth, LayoutUnit targetSize)
299 ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
300 ASSERT(!calculateMaxPreferredWidth || m_operatorType == Type::VerticalOperator);
301 bool isVertical = m_operatorType == Type::VerticalOperator;
304 if (!getBaseGlyph(style, baseGlyph))
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)
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));
325 setSizeVariant(glyphData);
326 LayoutUnit size = isVertical ? heightForGlyph(glyphData) : advanceWidthForGlyph(glyphData);
327 if (size >= targetSize)
332 // We verify if there is a construction.
333 if (!calculateGlyphAssemblyFallback(style, assemblyParts, assemblyData))
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;
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;
366 // If we didn't find a stretchy character set for this character, we don't know how to stretch it.
367 if (!stretchyCharacter)
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();
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));
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)
393 setGlyphAssembly(assemblyData);
396 void MathOperator::stretchTo(const RenderStyle& style, LayoutUnit ascent, LayoutUnit descent)
398 ASSERT(m_operatorType == Type::VerticalOperator);
399 calculateStretchyData(style, false, ascent + descent);
400 if (m_stretchType == StretchType::GlyphAssembly) {
406 void MathOperator::stretchTo(const RenderStyle& style, LayoutUnit width)
408 ASSERT(m_operatorType == Type::HorizontalOperator);
409 calculateStretchyData(style, false, width);
410 if (m_stretchType == StretchType::GlyphAssembly)
414 LayoutRect MathOperator::paintGlyph(const RenderStyle& style, PaintInfo& info, const GlyphData& data, const LayoutPoint& origin, GlyphPaintTrimming trim)
416 FloatRect glyphBounds = boundsForGlyph(data);
418 LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height()));
419 glyphPaintRect.setY(origin.y() + glyphBounds.y());
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;
427 glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1);
428 clipBounds.shiftYEdgeTo(glyphPaintRect.y());
431 glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
432 clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
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());
441 glyphPaintRect.shiftXEdgeTo(glyphPaintRect.x().ceil() + 1);
442 clipBounds.shiftXEdgeTo(glyphPaintRect.x());
445 glyphPaintRect.shiftMaxXEdgeTo(glyphPaintRect.maxX().floor() - 1);
446 clipBounds.shiftMaxXEdgeTo(glyphPaintRect.maxX());
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());
455 // Clipping the enclosing IntRect avoids any potential issues at joined edges.
456 GraphicsContextStateSaver stateSaver(info.context());
457 info.context().clip(clipBounds);
460 buffer.add(data.glyph, data.font, advanceWidthForGlyph(data));
461 info.context().drawGlyphs(style.fontCascade(), *data.font, buffer, 0, 1, origin);
463 return glyphPaintRect;
466 void MathOperator::fillWithVerticalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
468 ASSERT(m_operatorType == Type::VerticalOperator);
469 ASSERT(m_stretchType == StretchType::GlyphAssembly);
470 ASSERT(m_assembly.extension.isValid());
471 ASSERT(from.y() <= to.y());
473 // If there is no space for the extension glyph, we don't need to do anything.
474 if (from.y() == to.y())
477 GraphicsContextStateSaver stateSaver(info.context());
479 FloatRect glyphBounds = boundsForGlyph(m_assembly.extension);
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);
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());
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());
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())
506 void MathOperator::fillWithHorizontalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
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());
514 // If there is no space for the extension glyph, we don't need to do anything.
515 if (from.x() == to.x())
518 GraphicsContextStateSaver stateSaver(info.context());
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);
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());
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());
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())
545 void MathOperator::paintVerticalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
547 ASSERT(m_operatorType == Type::VerticalOperator);
548 ASSERT(m_stretchType == StretchType::GlyphAssembly);
549 ASSERT(m_assembly.topOrRight.isValid());
550 ASSERT(m_assembly.bottomOrLeft.isValid());
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);
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);
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));
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());
573 fillWithVerticalExtensionGlyph(style, info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
576 void MathOperator::paintHorizontalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
578 ASSERT(m_operatorType == Type::HorizontalOperator);
579 ASSERT(m_stretchType == StretchType::GlyphAssembly);
580 ASSERT(m_assembly.bottomOrLeft.isValid());
581 ASSERT(m_assembly.topOrRight.isValid());
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);
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);
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));
601 fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(leftGlyphPaintRect.maxX(), baselineY), LayoutPoint(rightGlyphPaintRect.x(), baselineY));
604 void MathOperator::paint(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
606 if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style.visibility() != VISIBLE)
609 GraphicsContextStateSaver stateSaver(info.context());
610 info.context().setFillColor(style.visitedDependentColor(CSSPropertyColor));
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));
622 if (m_stretchType == StretchType::GlyphAssembly) {
623 if (m_operatorType == Type::VerticalOperator)
624 paintVerticalGlyphAssembly(style, info, paintOffset);
626 paintHorizontalGlyphAssembly(style, info, paintOffset);
631 ASSERT(m_stretchType == StretchType::Unstretched || m_stretchType == StretchType::SizeVariant);
632 if (m_stretchType == StretchType::Unstretched) {
633 if (!getBaseGlyph(style, glyphData))
635 } else if (m_stretchType == StretchType::SizeVariant) {
636 ASSERT(m_variant.isValid());
637 glyphData = m_variant;
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);