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"
36 static inline FloatRect boundsForGlyph(const GlyphData& data)
38 return data.isValid() ? data.font->boundsForGlyph(data.glyph) : FloatRect();
41 static inline float heightForGlyph(const GlyphData& data)
43 return boundsForGlyph(data).height();
46 static inline void getAscentAndDescentForGlyph(const GlyphData& data, LayoutUnit& ascent, LayoutUnit& descent)
48 FloatRect bounds = boundsForGlyph(data);
50 descent = bounds.maxY();
53 static inline float advanceWidthForGlyph(const GlyphData& data)
55 return data.isValid() ? data.font->widthForGlyph(data.glyph) : 0;
58 // FIXME: This hardcoded data can be removed when OpenType MATH font are widely available (http://wkbug/156837).
59 struct StretchyCharacter {
66 // The first leftRightPairsCount pairs correspond to left/right fences that can easily be mirrored in RTL.
67 static const short leftRightPairsCount = 5;
68 static const StretchyCharacter stretchyCharacters[14] = {
69 { 0x28 , 0x239b, 0x239c, 0x239d, 0x0 }, // left parenthesis
70 { 0x29 , 0x239e, 0x239f, 0x23a0, 0x0 }, // right parenthesis
71 { 0x5b , 0x23a1, 0x23a2, 0x23a3, 0x0 }, // left square bracket
72 { 0x5d , 0x23a4, 0x23a5, 0x23a6, 0x0 }, // right square bracket
73 { 0x7b , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket
74 { 0x7d , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
75 { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0 }, // left ceiling
76 { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0 }, // right ceiling
77 { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0 }, // left floor
78 { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0 }, // right floor
79 { 0x7c , 0x7c, 0x7c, 0x7c, 0x0 }, // vertical bar
80 { 0x2016, 0x2016, 0x2016, 0x2016, 0x0 }, // double vertical line
81 { 0x2225, 0x2225, 0x2225, 0x2225, 0x0 }, // parallel to
82 { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0 } // integral sign
85 void MathOperator::setOperator(const RenderStyle& style, UChar baseCharacter, Type operatorType)
87 m_baseCharacter = baseCharacter;
88 m_operatorType = operatorType;
92 void MathOperator::reset(const RenderStyle& style)
94 m_stretchType = StretchType::Unstretched;
95 m_maxPreferredWidth = 0;
99 m_italicCorrection = 0;
101 // We use the base size for the calculation of the preferred width.
103 if (!getBaseGlyph(style, baseGlyph))
105 m_maxPreferredWidth = m_width = advanceWidthForGlyph(baseGlyph);
106 getAscentAndDescentForGlyph(baseGlyph, m_ascent, m_descent);
108 if (m_operatorType == Type::VerticalOperator)
109 calculateStretchyData(style, true); // We also take into account the width of larger sizes for the calculation of the preferred width.
110 else if (m_operatorType == Type::DisplayOperator)
111 calculateDisplayStyleLargeOperator(style); // We can directly select the size variant and determine the final metrics.
114 LayoutUnit MathOperator::stretchSize() const
116 ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
117 return m_operatorType == Type::VerticalOperator ? m_ascent + m_descent : m_width;
120 bool MathOperator::getBaseGlyph(const RenderStyle& style, GlyphData& baseGlyph) const
122 baseGlyph = style.fontCascade().glyphDataForCharacter(m_baseCharacter, !style.isLeftToRightDirection());
123 return baseGlyph.isValid() && baseGlyph.font == &style.fontCascade().primaryFont();
126 void MathOperator::setSizeVariant(const GlyphData& sizeVariant)
128 ASSERT(sizeVariant.isValid());
129 ASSERT(sizeVariant.font->mathData());
130 m_stretchType = StretchType::SizeVariant;
131 m_variant = sizeVariant;
132 m_width = advanceWidthForGlyph(sizeVariant);
133 getAscentAndDescentForGlyph(sizeVariant, m_ascent, m_descent);
136 void MathOperator::setGlyphAssembly(const GlyphAssemblyData& assemblyData)
138 ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
139 m_stretchType = StretchType::GlyphAssembly;
140 m_assembly = assemblyData;
141 if (m_operatorType == Type::VerticalOperator) {
143 m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.topOrRight));
144 m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.extension));
145 m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.bottomOrLeft));
146 m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(m_assembly.middle));
150 LayoutUnit ascent, descent;
151 getAscentAndDescentForGlyph(m_assembly.bottomOrLeft, ascent, descent);
152 m_ascent = std::max(m_ascent, ascent);
153 m_descent = std::max(m_descent, descent);
154 getAscentAndDescentForGlyph(m_assembly.extension, ascent, descent);
155 m_ascent = std::max(m_ascent, ascent);
156 m_descent = std::max(m_descent, descent);
157 getAscentAndDescentForGlyph(m_assembly.topOrRight, ascent, descent);
158 m_ascent = std::max(m_ascent, ascent);
159 m_descent = std::max(m_descent, descent);
160 getAscentAndDescentForGlyph(m_assembly.middle, ascent, descent);
161 m_ascent = std::max(m_ascent, ascent);
162 m_descent = std::max(m_descent, descent);
166 void MathOperator::calculateDisplayStyleLargeOperator(const RenderStyle& style)
168 ASSERT(m_operatorType == Type::DisplayOperator);
171 if (!getBaseGlyph(style, baseGlyph) || !baseGlyph.font->mathData())
174 // 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.
175 float displayOperatorMinHeight = std::max(heightForGlyph(baseGlyph) * sqrtOfTwoFloat, baseGlyph.font->mathData()->getMathConstant(*baseGlyph.font, OpenTypeMathData::DisplayOperatorMinHeight));
177 Vector<Glyph> sizeVariants;
178 Vector<OpenTypeMathData::AssemblyPart> assemblyParts;
179 baseGlyph.font->mathData()->getMathVariants(baseGlyph.glyph, true, sizeVariants, assemblyParts);
181 // We choose the first size variant that is larger than the expected displayOperatorMinHeight and otherwise fallback to the largest variant.
182 for (auto& sizeVariant : sizeVariants) {
183 GlyphData glyphData(sizeVariant, baseGlyph.font);
184 setSizeVariant(glyphData);
185 m_maxPreferredWidth = m_width;
186 m_italicCorrection = glyphData.font->mathData()->getItalicCorrection(*glyphData.font, glyphData.glyph);
187 if (heightForGlyph(glyphData) >= displayOperatorMinHeight)
192 bool MathOperator::calculateGlyphAssemblyFallback(const RenderStyle& style, const Vector<OpenTypeMathData::AssemblyPart>& assemblyParts, GlyphAssemblyData& assemblyData) const
194 // 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.
195 // FIXME: MathOperator should support the most general format (https://bugs.webkit.org/show_bug.cgi?id=130327).
196 // We use the approach of the copyComponents function in github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py
198 // We count the number of non extender pieces.
199 int nonExtenderCount = 0;
200 for (auto& part : assemblyParts) {
201 if (!part.isExtender)
204 if (nonExtenderCount > 3)
205 return false; // This is not supported: there are too many pieces.
207 // We now browse the list of pieces from left to right for horizontal operators and from bottom to top for vertical operators.
210 ExtenderBetweenStartAndMiddle,
212 ExtenderBetweenMiddleAndEnd,
216 PartType expectedPartType = Start;
217 assemblyData.extension.glyph = 0;
218 assemblyData.middle.glyph = 0;
219 for (auto& part : assemblyParts) {
220 if (nonExtenderCount < 3) {
221 // If we only have at most two non-extenders then we skip the middle glyph.
222 if (expectedPartType == ExtenderBetweenStartAndMiddle)
223 expectedPartType = ExtenderBetweenMiddleAndEnd;
224 else if (expectedPartType == Middle)
225 expectedPartType = End;
227 if (part.isExtender) {
228 if (!assemblyData.extension.glyph)
229 assemblyData.extension.glyph = part.glyph; // We copy the extender part.
230 else if (assemblyData.extension.glyph != part.glyph)
231 return false; // This is not supported: the assembly has different extenders.
233 switch (expectedPartType) {
235 // We ignore the left/bottom part.
236 expectedPartType = ExtenderBetweenStartAndMiddle;
239 // We ignore the middle part.
240 expectedPartType = ExtenderBetweenMiddleAndEnd;
244 // This is not supported: we got an unexpected extender.
246 case ExtenderBetweenStartAndMiddle:
247 case ExtenderBetweenMiddleAndEnd:
248 // We ignore multiple consecutive extenders.
253 switch (expectedPartType) {
255 // We copy the left/bottom part.
256 assemblyData.bottomOrLeft.glyph = part.glyph;
257 expectedPartType = ExtenderBetweenStartAndMiddle;
259 case ExtenderBetweenStartAndMiddle:
261 // We copy the middle part.
262 assemblyData.middle.glyph = part.glyph;
263 expectedPartType = ExtenderBetweenMiddleAndEnd;
265 case ExtenderBetweenMiddleAndEnd:
267 // We copy the right/top part.
268 assemblyData.topOrRight.glyph = part.glyph;
269 expectedPartType = None;
272 // This is not supported: we got an unexpected non-extender part.
277 if (!assemblyData.extension.glyph)
278 return false; // This is not supported: we always assume that we have an extension glyph.
280 // If we don't have top/bottom glyphs, we use the extension glyph.
281 if (!assemblyData.topOrRight.glyph)
282 assemblyData.topOrRight.glyph = assemblyData.extension.glyph;
283 if (!assemblyData.bottomOrLeft.glyph)
284 assemblyData.bottomOrLeft.glyph = assemblyData.extension.glyph;
286 assemblyData.topOrRight.font = &style.fontCascade().primaryFont();
287 assemblyData.extension.font = assemblyData.topOrRight.font;
288 assemblyData.bottomOrLeft.font = assemblyData.topOrRight.font;
289 assemblyData.middle.font = assemblyData.middle.glyph ? assemblyData.topOrRight.font : nullptr;
294 void MathOperator::calculateStretchyData(const RenderStyle& style, bool calculateMaxPreferredWidth, LayoutUnit targetSize)
296 ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator);
297 ASSERT(!calculateMaxPreferredWidth || m_operatorType == Type::VerticalOperator);
298 bool isVertical = m_operatorType == Type::VerticalOperator;
301 if (!getBaseGlyph(style, baseGlyph))
304 if (!calculateMaxPreferredWidth) {
305 // We do not stretch if the base glyph is large enough.
306 float baseSize = isVertical ? heightForGlyph(baseGlyph) : advanceWidthForGlyph(baseGlyph);
307 if (targetSize <= baseSize)
311 GlyphAssemblyData assemblyData;
312 if (baseGlyph.font->mathData()) {
313 Vector<Glyph> sizeVariants;
314 Vector<OpenTypeMathData::AssemblyPart> assemblyParts;
315 baseGlyph.font->mathData()->getMathVariants(baseGlyph.glyph, isVertical, sizeVariants, assemblyParts);
316 // We verify the size variants.
317 for (auto& sizeVariant : sizeVariants) {
318 GlyphData glyphData(sizeVariant, baseGlyph.font);
319 if (calculateMaxPreferredWidth)
320 m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(glyphData));
322 setSizeVariant(glyphData);
323 LayoutUnit size = isVertical ? heightForGlyph(glyphData) : advanceWidthForGlyph(glyphData);
324 if (size >= targetSize)
329 // We verify if there is a construction.
330 if (!calculateGlyphAssemblyFallback(style, assemblyParts, assemblyData))
336 // If the font does not have a MATH table, we fallback to the Unicode-only constructions.
337 const StretchyCharacter* stretchyCharacter = nullptr;
338 const unsigned maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters);
339 for (unsigned index = 0; index < maxIndex; ++index) {
340 if (stretchyCharacters[index].character == m_baseCharacter) {
341 stretchyCharacter = &stretchyCharacters[index];
342 if (!style.isLeftToRightDirection() && index < leftRightPairsCount * 2) {
343 // If we are in right-to-left direction we select the mirrored form by adding -1 or +1 according to the parity of index.
344 index += index % 2 ? -1 : 1;
350 // If we didn't find a stretchy character set for this character, we don't know how to stretch it.
351 if (!stretchyCharacter)
354 // We convert the list of Unicode characters into a list of glyph data.
355 assemblyData.topOrRight = style.fontCascade().glyphDataForCharacter(stretchyCharacter->topChar, false);
356 assemblyData.extension = style.fontCascade().glyphDataForCharacter(stretchyCharacter->extensionChar, false);
357 assemblyData.bottomOrLeft = style.fontCascade().glyphDataForCharacter(stretchyCharacter->bottomChar, false);
358 assemblyData.middle = stretchyCharacter->middleChar ? style.fontCascade().glyphDataForCharacter(stretchyCharacter->middleChar, false) : GlyphData();
361 // If we are measuring the maximum width, verify each component.
362 if (calculateMaxPreferredWidth) {
363 m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.topOrRight));
364 m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.extension));
365 m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.middle));
366 m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(assemblyData.bottomOrLeft));
370 // We ensure that the size is large enough to avoid glyph overlaps.
371 float minSize = isVertical ?
372 heightForGlyph(assemblyData.topOrRight) + heightForGlyph(assemblyData.middle) + heightForGlyph(assemblyData.bottomOrLeft)
373 : advanceWidthForGlyph(assemblyData.bottomOrLeft) + advanceWidthForGlyph(assemblyData.middle) + advanceWidthForGlyph(assemblyData.topOrRight);
374 if (minSize > targetSize)
377 setGlyphAssembly(assemblyData);
380 void MathOperator::stretchTo(const RenderStyle& style, LayoutUnit ascent, LayoutUnit descent)
382 ASSERT(m_operatorType == Type::VerticalOperator);
383 calculateStretchyData(style, false, ascent + descent);
384 if (m_stretchType == StretchType::GlyphAssembly) {
390 void MathOperator::stretchTo(const RenderStyle& style, LayoutUnit width)
392 ASSERT(m_operatorType == Type::HorizontalOperator);
393 calculateStretchyData(style, false, width);
394 if (m_stretchType == StretchType::GlyphAssembly)
398 LayoutRect MathOperator::paintGlyph(const RenderStyle& style, PaintInfo& info, const GlyphData& data, const LayoutPoint& origin, GlyphPaintTrimming trim)
400 FloatRect glyphBounds = boundsForGlyph(data);
402 LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height()));
403 glyphPaintRect.setY(origin.y() + glyphBounds.y());
405 // In order to have glyphs fit snugly with one another we snap the connecting edges to pixel boundaries
406 // and trim off one pixel. The pixel trim is to account for fonts that have edge pixels that have less
407 // than full coverage. These edge pixels can introduce small seams between connected glyphs.
408 FloatRect clipBounds = info.rect;
411 glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1);
412 clipBounds.shiftYEdgeTo(glyphPaintRect.y());
415 glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
416 clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
418 case TrimTopAndBottom:
419 glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1);
420 glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
421 clipBounds.shiftYEdgeTo(glyphPaintRect.y());
422 clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
425 glyphPaintRect.shiftXEdgeTo(glyphPaintRect.x().ceil() + 1);
426 clipBounds.shiftXEdgeTo(glyphPaintRect.x());
429 glyphPaintRect.shiftMaxXEdgeTo(glyphPaintRect.maxX().floor() - 1);
430 clipBounds.shiftMaxXEdgeTo(glyphPaintRect.maxX());
432 case TrimLeftAndRight:
433 glyphPaintRect.shiftXEdgeTo(glyphPaintRect.x().ceil() + 1);
434 glyphPaintRect.shiftMaxXEdgeTo(glyphPaintRect.maxX().floor() - 1);
435 clipBounds.shiftXEdgeTo(glyphPaintRect.x());
436 clipBounds.shiftMaxXEdgeTo(glyphPaintRect.maxX());
439 // Clipping the enclosing IntRect avoids any potential issues at joined edges.
440 GraphicsContextStateSaver stateSaver(info.context());
441 info.context().clip(clipBounds);
444 buffer.add(data.glyph, data.font, advanceWidthForGlyph(data));
445 info.context().drawGlyphs(style.fontCascade(), *data.font, buffer, 0, 1, origin);
447 return glyphPaintRect;
450 void MathOperator::fillWithVerticalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
452 ASSERT(m_operatorType == Type::VerticalOperator);
453 ASSERT(m_stretchType == StretchType::GlyphAssembly);
454 ASSERT(m_assembly.extension.isValid());
455 ASSERT(from.y() <= to.y());
457 // If there is no space for the extension glyph, we don't need to do anything.
458 if (from.y() == to.y())
461 GraphicsContextStateSaver stateSaver(info.context());
463 FloatRect glyphBounds = boundsForGlyph(m_assembly.extension);
465 // Clipping the extender region here allows us to draw the bottom extender glyph into the
466 // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping.
467 LayoutRect clipBounds = info.rect;
468 clipBounds.shiftYEdgeTo(from.y());
469 clipBounds.shiftMaxYEdgeTo(to.y());
470 info.context().clip(clipBounds);
472 // Trimming may remove up to two pixels from the top of the extender glyph, so we move it up by two pixels.
473 float offsetToGlyphTop = glyphBounds.y() + 2;
474 LayoutPoint glyphOrigin = LayoutPoint(from.x(), from.y() - offsetToGlyphTop);
475 FloatRect lastPaintedGlyphRect(from, FloatSize());
477 // FIXME: In practice, only small stretch sizes are requested but we may need to limit the number
478 // of pieces that can be repeated to avoid hangs. See http://webkit.org/b/155434
479 while (lastPaintedGlyphRect.maxY() < to.y()) {
480 lastPaintedGlyphRect = paintGlyph(style, info, m_assembly.extension, glyphOrigin, TrimTopAndBottom);
481 glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height());
483 // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle
484 // with trimming. In that case we just draw nothing.
485 if (lastPaintedGlyphRect.isEmpty())
490 void MathOperator::fillWithHorizontalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
492 ASSERT(m_operatorType == Type::HorizontalOperator);
493 ASSERT(m_stretchType == StretchType::GlyphAssembly);
494 ASSERT(m_assembly.extension.isValid());
495 ASSERT(from.x() <= to.x());
496 ASSERT(from.y() == to.y());
498 // If there is no space for the extension glyph, we don't need to do anything.
499 if (from.x() == to.x())
502 GraphicsContextStateSaver stateSaver(info.context());
504 // Clipping the extender region here allows us to draw the bottom extender glyph into the
505 // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping.
506 LayoutRect clipBounds = info.rect;
507 clipBounds.shiftXEdgeTo(from.x());
508 clipBounds.shiftMaxXEdgeTo(to.x());
509 info.context().clip(clipBounds);
511 // Trimming may remove up to two pixels from the left of the extender glyph, so we move it left by two pixels.
512 float offsetToGlyphLeft = -2;
513 LayoutPoint glyphOrigin = LayoutPoint(from.x() + offsetToGlyphLeft, from.y());
514 FloatRect lastPaintedGlyphRect(from, FloatSize());
516 // FIXME: In practice, only small stretch sizes are requested but we may need to limit the number
517 // of pieces that can be repeated to avoid hangs. See http://webkit.org/b/155434
518 while (lastPaintedGlyphRect.maxX() < to.x()) {
519 lastPaintedGlyphRect = paintGlyph(style, info, m_assembly.extension, glyphOrigin, TrimLeftAndRight);
520 glyphOrigin.setX(glyphOrigin.x() + lastPaintedGlyphRect.width());
522 // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle
523 // with trimming. In that case we just draw nothing.
524 if (lastPaintedGlyphRect.isEmpty())
529 void MathOperator::paintVerticalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
531 ASSERT(m_operatorType == Type::VerticalOperator);
532 ASSERT(m_stretchType == StretchType::GlyphAssembly);
533 ASSERT(m_assembly.topOrRight.isValid());
534 ASSERT(m_assembly.bottomOrLeft.isValid());
536 // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box.
537 LayoutPoint operatorTopLeft = paintOffset;
538 FloatRect topGlyphBounds = boundsForGlyph(m_assembly.topOrRight);
539 LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y());
540 LayoutRect topGlyphPaintRect = paintGlyph(style, info, m_assembly.topOrRight, topGlyphOrigin, TrimBottom);
542 FloatRect bottomGlyphBounds = boundsForGlyph(m_assembly.bottomOrLeft);
543 LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + stretchSize() - (bottomGlyphBounds.height() + bottomGlyphBounds.y()));
544 LayoutRect bottomGlyphPaintRect = paintGlyph(style, info, m_assembly.bottomOrLeft, bottomGlyphOrigin, TrimTop);
546 if (m_assembly.middle.isValid()) {
547 // Center the glyph origin between the start and end glyph paint extents. Then shift it half the paint height toward the bottom glyph.
548 FloatRect middleGlyphBounds = boundsForGlyph(m_assembly.middle);
549 LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y());
550 middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0));
551 middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0));
553 LayoutRect middleGlyphPaintRect = paintGlyph(style, info, m_assembly.middle, middleGlyphOrigin, TrimTopAndBottom);
554 fillWithVerticalExtensionGlyph(style, info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner());
555 fillWithVerticalExtensionGlyph(style, info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
557 fillWithVerticalExtensionGlyph(style, info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
560 void MathOperator::paintHorizontalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
562 ASSERT(m_operatorType == Type::HorizontalOperator);
563 ASSERT(m_stretchType == StretchType::GlyphAssembly);
564 ASSERT(m_assembly.bottomOrLeft.isValid());
565 ASSERT(m_assembly.topOrRight.isValid());
567 // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box.
568 LayoutPoint operatorTopLeft = paintOffset;
569 LayoutUnit baselineY = operatorTopLeft.y() + m_ascent;
570 LayoutPoint leftGlyphOrigin(operatorTopLeft.x(), baselineY);
571 LayoutRect leftGlyphPaintRect = paintGlyph(style, info, m_assembly.bottomOrLeft, leftGlyphOrigin, TrimRight);
573 FloatRect rightGlyphBounds = boundsForGlyph(m_assembly.topOrRight);
574 LayoutPoint rightGlyphOrigin(operatorTopLeft.x() + stretchSize() - rightGlyphBounds.width(), baselineY);
575 LayoutRect rightGlyphPaintRect = paintGlyph(style, info, m_assembly.topOrRight, rightGlyphOrigin, TrimLeft);
577 if (m_assembly.middle.isValid()) {
578 // Center the glyph origin between the start and end glyph paint extents.
579 LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), baselineY);
580 middleGlyphOrigin.moveBy(LayoutPoint((rightGlyphPaintRect.x() - leftGlyphPaintRect.maxX()) / 2.0, 0));
581 LayoutRect middleGlyphPaintRect = paintGlyph(style, info, m_assembly.middle, middleGlyphOrigin, TrimLeftAndRight);
582 fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(leftGlyphPaintRect.maxX(), baselineY), LayoutPoint(middleGlyphPaintRect.x(), baselineY));
583 fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(middleGlyphPaintRect.maxX(), baselineY), LayoutPoint(rightGlyphPaintRect.x(), baselineY));
585 fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(leftGlyphPaintRect.maxX(), baselineY), LayoutPoint(rightGlyphPaintRect.x(), baselineY));
588 void MathOperator::paint(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset)
590 if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style.visibility() != VISIBLE)
593 GraphicsContextStateSaver stateSaver(info.context());
594 info.context().setFillColor(style.visitedDependentColor(CSSPropertyColor));
596 if (m_stretchType == StretchType::GlyphAssembly) {
597 if (m_operatorType == Type::VerticalOperator)
598 paintVerticalGlyphAssembly(style, info, paintOffset);
600 paintHorizontalGlyphAssembly(style, info, paintOffset);
605 ASSERT(m_stretchType == StretchType::Unstretched || m_stretchType == StretchType::SizeVariant);
606 if (m_stretchType == StretchType::Unstretched) {
607 if (!getBaseGlyph(style, glyphData))
609 } else if (m_stretchType == StretchType::SizeVariant) {
610 ASSERT(m_variant.isValid());
611 glyphData = m_variant;
614 buffer.add(glyphData.glyph, glyphData.font, advanceWidthForGlyph(glyphData));
615 LayoutPoint operatorTopLeft = paintOffset;
616 FloatRect glyphBounds = boundsForGlyph(glyphData);
617 LayoutPoint operatorOrigin(operatorTopLeft.x(), operatorTopLeft.y() - glyphBounds.y());
618 info.context().drawGlyphs(style.fontCascade(), *glyphData.font, buffer, 0, 1, operatorOrigin);