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