REGRESSION(r182192): Ligatures do not interact correctly with SHY in some fonts
[WebKit-https.git] / Source / WebCore / platform / graphics / WidthIterator.cpp
1 /*
2  * Copyright (C) 2003, 2006, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Holger Hans Peter Freyther
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  */
21
22 #include "config.h"
23 #include "WidthIterator.h"
24
25 #include "Font.h"
26 #include "FontCascade.h"
27 #include "GlyphBuffer.h"
28 #include "Latin1TextIterator.h"
29 #include "SurrogatePairAwareTextIterator.h"
30 #include <wtf/MathExtras.h>
31
32 using namespace WTF;
33 using namespace Unicode;
34
35 namespace WebCore {
36
37 WidthIterator::WidthIterator(const FontCascade* font, const TextRun& run, HashSet<const Font*>* fallbackFonts, bool accountForGlyphBounds, bool forTextEmphasis)
38     : m_font(font)
39     , m_run(run)
40     , m_currentCharacter(0)
41     , m_runWidthSoFar(0)
42     , m_isAfterExpansion((run.expansionBehavior() & LeadingExpansionMask) == ForbidLeadingExpansion)
43     , m_finalRoundingWidth(0)
44     , m_typesettingFeatures(font->typesettingFeatures())
45     , m_fallbackFonts(fallbackFonts)
46     , m_accountForGlyphBounds(accountForGlyphBounds)
47     , m_maxGlyphBoundingBoxY(std::numeric_limits<float>::min())
48     , m_minGlyphBoundingBoxY(std::numeric_limits<float>::max())
49     , m_firstGlyphOverflow(0)
50     , m_lastGlyphOverflow(0)
51     , m_forTextEmphasis(forTextEmphasis)
52 {
53     // If the padding is non-zero, count the number of spaces in the run
54     // and divide that by the padding for per space addition.
55     m_expansion = m_run.expansion();
56     if (!m_expansion)
57         m_expansionPerOpportunity = 0;
58     else {
59         unsigned expansionOpportunityCount = FontCascade::expansionOpportunityCount(m_run.text(), m_run.ltr() ? LTR : RTL, run.expansionBehavior()).first;
60
61         if (!expansionOpportunityCount)
62             m_expansionPerOpportunity = 0;
63         else
64             m_expansionPerOpportunity = m_expansion / expansionOpportunityCount;
65     }
66 }
67
68 GlyphData WidthIterator::glyphDataForCharacter(UChar32 character, bool mirror, int currentCharacter, unsigned& advanceLength, String& normalizedSpacesStringCache)
69 {
70     ASSERT(m_font);
71
72 #if ENABLE(SVG_FONTS)
73     if (TextRun::RenderingContext* renderingContext = m_run.renderingContext())
74         return renderingContext->glyphDataForCharacter(*m_font, *this, character, mirror, currentCharacter, advanceLength, normalizedSpacesStringCache);
75 #else
76     UNUSED_PARAM(currentCharacter);
77     UNUSED_PARAM(advanceLength);
78     UNUSED_PARAM(normalizedSpacesStringCache);
79 #endif
80
81     return m_font->glyphDataForCharacter(character, mirror);
82 }
83
84 struct OriginalAdvancesForCharacterTreatedAsSpace {
85 public:
86     OriginalAdvancesForCharacterTreatedAsSpace(bool isSpace, float advanceBefore, float advanceAt)
87         : characterIsSpace(isSpace)
88         , advanceBeforeCharacter(advanceBefore)
89         , advanceAtCharacter(advanceAt)
90     {
91     }
92
93     bool characterIsSpace;
94     float advanceBeforeCharacter;
95     float advanceAtCharacter;
96 };
97
98 static inline bool isSoftBankEmoji(UChar32 codepoint)
99 {
100     return codepoint >= 0xE001 && codepoint <= 0xE537;
101 }
102
103 inline auto WidthIterator::shouldApplyFontTransforms(const GlyphBuffer* glyphBuffer, int lastGlyphCount, UChar32 previousCharacter) const -> TransformsType
104 {
105     if (glyphBuffer && glyphBuffer->size() == (lastGlyphCount + 1) && isSoftBankEmoji(previousCharacter))
106         return TransformsType::Forced;
107     if (m_run.length() <= 1 || !(m_typesettingFeatures & (Kerning | Ligatures)))
108         return TransformsType::None;
109     return TransformsType::NotForced;
110 }
111
112 inline float WidthIterator::applyFontTransforms(GlyphBuffer* glyphBuffer, bool ltr, int& lastGlyphCount, const Font* font, TypesettingFeatures typesettingFeatures, UChar32 previousCharacter, bool force, CharactersTreatedAsSpace& charactersTreatedAsSpace)
113 {
114     ASSERT_UNUSED(previousCharacter, shouldApplyFontTransforms(glyphBuffer, lastGlyphCount, previousCharacter) != WidthIterator::TransformsType::None);
115
116     if (!glyphBuffer)
117         return 0;
118
119     int glyphBufferSize = glyphBuffer->size();
120     if (!force && glyphBufferSize <= lastGlyphCount + 1) {
121         lastGlyphCount = glyphBufferSize;
122         return 0;
123     }
124
125     GlyphBufferAdvance* advances = glyphBuffer->advances(0);
126     float widthDifference = 0;
127     for (int i = lastGlyphCount; i < glyphBufferSize; ++i)
128         widthDifference -= advances[i].width();
129
130     if (!ltr)
131         glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
132
133 #if ENABLE(SVG_FONTS)
134     // We need to handle transforms on SVG fonts internally, since they are rendered internally.
135     if (font->isSVGFont()) {
136         // SVG font ligatures are handled during glyph selection, only kerning remaining.
137         if (run().renderingContext() && (typesettingFeatures & Kerning)) {
138             // FIXME: We could pass the necessary context down to this level so we can lazily create rendering contexts at this point.
139             // However, a larger refactoring of SVG fonts might necessary to sidestep this problem completely.
140             run().renderingContext()->applySVGKerning(font, *this, glyphBuffer, lastGlyphCount);
141         }
142     } else
143 #endif
144         font->applyTransforms(glyphBuffer->glyphs(lastGlyphCount), advances + lastGlyphCount, glyphBufferSize - lastGlyphCount, typesettingFeatures);
145
146     if (!ltr)
147         glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
148
149     for (size_t i = 0; i < charactersTreatedAsSpace.size(); ++i) {
150         int spaceOffset = charactersTreatedAsSpace[i].first;
151         const OriginalAdvancesForCharacterTreatedAsSpace& originalAdvances = charactersTreatedAsSpace[i].second;
152         if (spaceOffset && !originalAdvances.characterIsSpace)
153             glyphBuffer->advances(spaceOffset - 1)->setWidth(originalAdvances.advanceBeforeCharacter);
154         glyphBuffer->advances(spaceOffset)->setWidth(originalAdvances.advanceAtCharacter);
155     }
156     charactersTreatedAsSpace.clear();
157
158     for (int i = lastGlyphCount; i < glyphBufferSize; ++i)
159         widthDifference += advances[i].width();
160
161     lastGlyphCount = glyphBufferSize;
162     return widthDifference;
163 }
164
165 static inline std::pair<bool, bool> expansionLocation(bool ideograph, bool treatAsSpace, bool ltr, bool isAfterExpansion, bool forbidLeadingExpansion, bool forbidTrailingExpansion, bool forceLeadingExpansion, bool forceTrailingExpansion)
166 {
167     bool expandLeft = ideograph;
168     bool expandRight = ideograph;
169     if (treatAsSpace) {
170         if (ltr)
171             expandRight = true;
172         else
173             expandLeft = true;
174     }
175     if (isAfterExpansion) {
176         if (ltr)
177             expandLeft = false;
178         else
179             expandRight = false;
180     }
181     ASSERT(!forbidLeadingExpansion || !forceLeadingExpansion);
182     ASSERT(!forbidTrailingExpansion || !forceTrailingExpansion);
183     if (forbidLeadingExpansion)
184         expandLeft = false;
185     if (forbidTrailingExpansion)
186         expandRight = false;
187     if (forceLeadingExpansion)
188         expandLeft = true;
189     if (forceTrailingExpansion)
190         expandRight = true;
191     return std::make_pair(expandLeft, expandRight);
192 }
193
194 template <typename TextIterator>
195 inline unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer)
196 {
197     bool rtl = m_run.rtl();
198     bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled();
199
200     bool runForcesLeadingExpansion = (m_run.expansionBehavior() & LeadingExpansionMask) == ForceLeadingExpansion;
201     bool runForcesTrailingExpansion = (m_run.expansionBehavior() & TrailingExpansionMask) == ForceTrailingExpansion;
202     bool runForbidsLeadingExpansion = (m_run.expansionBehavior() & LeadingExpansionMask) == ForbidLeadingExpansion;
203     bool runForbidsTrailingExpansion = (m_run.expansionBehavior() & TrailingExpansionMask) == ForbidTrailingExpansion;
204     float widthSinceLastRounding = m_runWidthSoFar;
205     float leftoverJustificationWidth = 0;
206     m_runWidthSoFar = floorf(m_runWidthSoFar);
207     widthSinceLastRounding -= m_runWidthSoFar;
208
209     float lastRoundingWidth = m_finalRoundingWidth;
210     FloatRect bounds;
211
212     const Font& primaryFont = m_font->primaryFont();
213     const Font* lastFontData = &primaryFont;
214     int lastGlyphCount = glyphBuffer ? glyphBuffer->size() : 0;
215
216     UChar32 character = 0;
217     UChar32 previousCharacter = 0;
218     unsigned clusterLength = 0;
219     CharactersTreatedAsSpace charactersTreatedAsSpace;
220     String normalizedSpacesStringCache;
221     // We are iterating in string order, not glyph order. Compare this to ComplexTextController::adjustGlyphsAndAdvances()
222     while (textIterator.consume(character, clusterLength)) {
223         unsigned advanceLength = clusterLength;
224         int currentCharacter = textIterator.currentCharacter();
225         const GlyphData& glyphData = glyphDataForCharacter(character, rtl, currentCharacter, advanceLength, normalizedSpacesStringCache);
226         Glyph glyph = glyphData.glyph;
227         if (!glyph) {
228             textIterator.advance(advanceLength);
229             continue;
230         }
231         const Font* font = glyphData.font;
232         ASSERT(font);
233
234         // Now that we have a glyph and font data, get its width.
235         float width;
236         if (character == '\t' && m_run.allowTabs())
237             width = m_font->tabWidth(*font, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding);
238         else {
239             width = font->widthForGlyph(glyph);
240
241             // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
242             width *= m_run.horizontalGlyphStretch();
243
244             // We special case spaces in two ways when applying word rounding.
245             // First, we round spaces to an adjusted width in all fonts.
246             // Second, in fixed-pitch fonts we ensure that all characters that
247             // match the width of the space character have the same width as the space character.
248             if (m_run.applyWordRounding() && width == font->spaceWidth() && (font->pitch() == FixedPitch || glyph == font->spaceGlyph()))
249                 width = font->adjustedSpaceWidth();
250         }
251
252         if (font != lastFontData && width) {
253             auto transformsType = shouldApplyFontTransforms(glyphBuffer, lastGlyphCount, previousCharacter);
254             if (transformsType != TransformsType::None) {
255                 m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, previousCharacter, transformsType == TransformsType::Forced, charactersTreatedAsSpace);
256                 if (glyphBuffer)
257                     glyphBuffer->shrink(lastGlyphCount);
258             }
259
260             lastFontData = font;
261             if (m_fallbackFonts && font != &primaryFont) {
262                 // FIXME: This does a little extra work that could be avoided if
263                 // glyphDataForCharacter() returned whether it chose to use a small caps font.
264                 if (!m_font->isSmallCaps() || character == u_toupper(character))
265                     m_fallbackFonts->add(font);
266                 else {
267                     const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(u_toupper(character), rtl);
268                     if (uppercaseGlyphData.font != &primaryFont)
269                         m_fallbackFonts->add(uppercaseGlyphData.font);
270                 }
271             }
272         }
273
274         if (hasExtraSpacing) {
275             // Account for letter-spacing.
276             if (width) {
277                 width += m_font->letterSpacing();
278                 width += leftoverJustificationWidth;
279                 leftoverJustificationWidth = 0;
280             }
281
282             static bool expandAroundIdeographs = FontCascade::canExpandAroundIdeographsInComplexText();
283             bool treatAsSpace = FontCascade::treatAsSpace(character);
284             bool currentIsLastCharacter = currentCharacter + advanceLength == static_cast<size_t>(m_run.length());
285             bool forceLeadingExpansion = false; // On the left, regardless of m_run.ltr()
286             bool forceTrailingExpansion = false; // On the right, regardless of m_run.ltr()
287             bool forbidLeadingExpansion = false;
288             bool forbidTrailingExpansion = false;
289             if (runForcesLeadingExpansion)
290                 forceLeadingExpansion = m_run.ltr() ? !currentCharacter : currentIsLastCharacter;
291             if (runForcesTrailingExpansion)
292                 forceTrailingExpansion = m_run.ltr() ? currentIsLastCharacter : !currentCharacter;
293             if (runForbidsLeadingExpansion)
294                 forbidLeadingExpansion = m_run.ltr() ? !currentCharacter : currentIsLastCharacter;
295             if (runForbidsTrailingExpansion)
296                 forbidTrailingExpansion = m_run.ltr() ? currentIsLastCharacter : !currentCharacter;
297             bool ideograph = (expandAroundIdeographs && FontCascade::isCJKIdeographOrSymbol(character));
298             if (treatAsSpace || ideograph || forceLeadingExpansion || forceTrailingExpansion) {
299                 // Distribute the run's total expansion evenly over all expansion opportunities in the run.
300                 if (m_expansion) {
301                     bool expandLeft, expandRight;
302                     std::tie(expandLeft, expandRight) = expansionLocation(ideograph, treatAsSpace, m_run.ltr(), m_isAfterExpansion, forbidLeadingExpansion, forbidTrailingExpansion, forceLeadingExpansion, forceTrailingExpansion);
303                     float previousExpansion = m_expansion;
304                     if (expandLeft) {
305                         if (m_run.ltr()) {
306                             // Increase previous width
307                             m_expansion -= m_expansionPerOpportunity;
308                             float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
309                             m_runWidthSoFar += expansionAtThisOpportunity;
310                             if (glyphBuffer) {
311                                 if (glyphBuffer->isEmpty()) {
312                                     if (m_forTextEmphasis)
313                                         glyphBuffer->add(font->zeroWidthSpaceGlyph(), font, m_expansionPerOpportunity, currentCharacter);
314                                     else
315                                         glyphBuffer->add(font->spaceGlyph(), font, expansionAtThisOpportunity, currentCharacter);
316                                 } else
317                                     glyphBuffer->expandLastAdvance(expansionAtThisOpportunity);
318                             }
319                         } else {
320                             // Increase next width
321                             float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion - m_expansionPerOpportunity);
322                             leftoverJustificationWidth += expansionAtThisOpportunity;
323                             m_isAfterExpansion = true;
324                         }
325                         previousExpansion = m_expansion;
326                     }
327                     if (expandRight) {
328                         m_expansion -= m_expansionPerOpportunity;
329                         float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
330                         width += expansionAtThisOpportunity;
331                         if (m_run.ltr())
332                             m_isAfterExpansion = true;
333                     }
334                 } else
335                     m_isAfterExpansion = false;
336
337                 // Account for word spacing.
338                 // We apply additional space between "words" by adding width to the space character.
339                 if (treatAsSpace && (character != '\t' || !m_run.allowTabs()) && (currentCharacter || character == noBreakSpace) && m_font->wordSpacing())
340                     width += m_font->wordSpacing();
341             } else
342                 m_isAfterExpansion = false;
343         }
344
345         auto transformsType = shouldApplyFontTransforms(glyphBuffer, lastGlyphCount, previousCharacter);
346         if (transformsType != TransformsType::None && glyphBuffer && FontCascade::treatAsSpace(character)) {
347             charactersTreatedAsSpace.append(std::make_pair(glyphBuffer->size(),
348                 OriginalAdvancesForCharacterTreatedAsSpace(character == ' ', glyphBuffer->size() ? glyphBuffer->advanceAt(glyphBuffer->size() - 1).width() : 0, width)));
349         }
350
351         if (m_accountForGlyphBounds) {
352             bounds = font->boundsForGlyph(glyph);
353             if (!currentCharacter)
354                 m_firstGlyphOverflow = std::max<float>(0, -bounds.x());
355         }
356
357         if (m_forTextEmphasis && !FontCascade::canReceiveTextEmphasis(character))
358             glyph = 0;
359
360         // Advance past the character we just dealt with.
361         textIterator.advance(advanceLength);
362
363         float oldWidth = width;
364
365         // Force characters that are used to determine word boundaries for the rounding hack
366         // to be integer width, so following words will start on an integer boundary.
367         if (m_run.applyWordRounding() && FontCascade::isRoundingHackCharacter(character)) {
368             width = ceilf(width);
369
370             // Since widthSinceLastRounding can lose precision if we include measurements for
371             // preceding whitespace, we bypass it here.
372             m_runWidthSoFar += width;
373
374             // Since this is a rounding hack character, we should have reset this sum on the previous
375             // iteration.
376             ASSERT(!widthSinceLastRounding);
377         } else {
378             // Check to see if the next character is a "rounding hack character", if so, adjust
379             // width so that the total run width will be on an integer boundary.
380             if ((m_run.applyWordRounding() && static_cast<unsigned>(textIterator.currentCharacter()) < m_run.length() && FontCascade::isRoundingHackCharacter(*(textIterator.characters())))
381                 || (m_run.applyRunRounding() && static_cast<unsigned>(textIterator.currentCharacter()) >= m_run.length())) {
382                 float totalWidth = widthSinceLastRounding + width;
383                 widthSinceLastRounding = ceilf(totalWidth);
384                 width += widthSinceLastRounding - totalWidth;
385                 m_runWidthSoFar += widthSinceLastRounding;
386                 widthSinceLastRounding = 0;
387             } else
388                 widthSinceLastRounding += width;
389         }
390
391         if (glyphBuffer)
392             glyphBuffer->add(glyph, font, (rtl ? oldWidth + lastRoundingWidth : width), currentCharacter);
393
394         lastRoundingWidth = width - oldWidth;
395
396         if (m_accountForGlyphBounds) {
397             m_maxGlyphBoundingBoxY = std::max(m_maxGlyphBoundingBoxY, bounds.maxY());
398             m_minGlyphBoundingBoxY = std::min(m_minGlyphBoundingBoxY, bounds.y());
399             m_lastGlyphOverflow = std::max<float>(0, bounds.maxX() - width);
400         }
401         previousCharacter = character;
402     }
403
404     if (leftoverJustificationWidth) {
405         if (m_forTextEmphasis)
406             glyphBuffer->add(lastFontData->zeroWidthSpaceGlyph(), lastFontData, leftoverJustificationWidth, m_run.length());
407         else
408             glyphBuffer->add(lastFontData->spaceGlyph(), lastFontData, leftoverJustificationWidth, m_run.length());
409     }
410
411     auto transformsType = shouldApplyFontTransforms(glyphBuffer, lastGlyphCount, previousCharacter);
412     if (transformsType != TransformsType::None) {
413         m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, previousCharacter, transformsType == TransformsType::Forced, charactersTreatedAsSpace);
414         if (glyphBuffer)
415             glyphBuffer->shrink(lastGlyphCount);
416     }
417
418     unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter;
419     m_currentCharacter = textIterator.currentCharacter();
420     m_runWidthSoFar += widthSinceLastRounding;
421     m_finalRoundingWidth = lastRoundingWidth;
422     return consumedCharacters;
423 }
424
425 unsigned WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
426 {
427     int length = m_run.length();
428
429     if (offset > length)
430         offset = length;
431
432     if (m_currentCharacter >= static_cast<unsigned>(offset))
433         return 0;
434
435     if (m_run.is8Bit()) {
436         Latin1TextIterator textIterator(m_run.data8(m_currentCharacter), m_currentCharacter, offset, length);
437         return advanceInternal(textIterator, glyphBuffer);
438     }
439
440     SurrogatePairAwareTextIterator textIterator(m_run.data16(m_currentCharacter), m_currentCharacter, offset, length);
441     return advanceInternal(textIterator, glyphBuffer);
442 }
443
444 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer& glyphBuffer)
445 {
446     int oldSize = glyphBuffer.size();
447     advance(m_currentCharacter + 1, &glyphBuffer);
448     float w = 0;
449     for (int i = oldSize; i < glyphBuffer.size(); ++i)
450         w += glyphBuffer.advanceAt(i).width();
451     width = w;
452     return glyphBuffer.size() > oldSize;
453 }
454
455 }