2015-09-09 Geoffrey Garen <ggaren@apple.com>
[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 typedef Vector<std::pair<int, OriginalAdvancesForCharacterTreatedAsSpace>, 64> CharactersTreatedAsSpace;
99
100 static inline float applyFontTransforms(GlyphBuffer* glyphBuffer, bool ltr, int& lastGlyphCount, const Font* font, WidthIterator& iterator, TypesettingFeatures typesettingFeatures, bool force, CharactersTreatedAsSpace& charactersTreatedAsSpace)
101 {
102     ASSERT(typesettingFeatures & (Kerning | Ligatures));
103
104     if (!glyphBuffer)
105         return 0;
106
107     int glyphBufferSize = glyphBuffer->size();
108     if (!force && glyphBufferSize <= lastGlyphCount + 1) {
109         lastGlyphCount = glyphBufferSize;
110         return 0;
111     }
112
113     GlyphBufferAdvance* advances = glyphBuffer->advances(0);
114     float widthDifference = 0;
115     for (int i = lastGlyphCount; i < glyphBufferSize; ++i)
116         widthDifference -= advances[i].width();
117
118     if (!ltr)
119         glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
120
121 #if !ENABLE(SVG_FONTS)
122     UNUSED_PARAM(iterator);
123 #else
124     // We need to handle transforms on SVG fonts internally, since they are rendered internally.
125     if (font->isSVGFont()) {
126         // SVG font ligatures are handled during glyph selection, only kerning remaining.
127         if (iterator.run().renderingContext() && (typesettingFeatures & Kerning)) {
128             // FIXME: We could pass the necessary context down to this level so we can lazily create rendering contexts at this point.
129             // However, a larger refactoring of SVG fonts might necessary to sidestep this problem completely.
130             iterator.run().renderingContext()->applySVGKerning(font, iterator, glyphBuffer, lastGlyphCount);
131         }
132     } else
133 #endif
134         font->applyTransforms(glyphBuffer->glyphs(lastGlyphCount), advances + lastGlyphCount, glyphBufferSize - lastGlyphCount, typesettingFeatures);
135
136     if (!ltr)
137         glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
138
139     for (size_t i = 0; i < charactersTreatedAsSpace.size(); ++i) {
140         int spaceOffset = charactersTreatedAsSpace[i].first;
141         const OriginalAdvancesForCharacterTreatedAsSpace& originalAdvances = charactersTreatedAsSpace[i].second;
142         if (spaceOffset && !originalAdvances.characterIsSpace)
143             glyphBuffer->advances(spaceOffset - 1)->setWidth(originalAdvances.advanceBeforeCharacter);
144         glyphBuffer->advances(spaceOffset)->setWidth(originalAdvances.advanceAtCharacter);
145     }
146     charactersTreatedAsSpace.clear();
147
148 #if PLATFORM(MAC) || PLATFORM(IOS)
149     // Workaround for <rdar://problem/20230073> FIXME: Please remove this when no longer needed.
150     GlyphBufferGlyph* glyphs = glyphBuffer->glyphs(0);
151     int filteredIndex = lastGlyphCount;
152     for (int i = lastGlyphCount; i < glyphBufferSize; ++i) {
153         glyphs[filteredIndex] = glyphs[i];
154         advances[filteredIndex] = advances[i];
155         if (glyphs[filteredIndex] != kCGFontIndexInvalid)
156             ++filteredIndex;
157     }
158     glyphBufferSize = filteredIndex;
159 #endif
160
161     for (int i = lastGlyphCount; i < glyphBufferSize; ++i)
162         widthDifference += advances[i].width();
163
164     lastGlyphCount = glyphBufferSize;
165     return widthDifference;
166 }
167
168 static inline std::pair<bool, bool> expansionLocation(bool ideograph, bool treatAsSpace, bool ltr, bool isAfterExpansion, bool forbidLeadingExpansion, bool forbidTrailingExpansion, bool forceLeadingExpansion, bool forceTrailingExpansion)
169 {
170     bool expandLeft = ideograph;
171     bool expandRight = ideograph;
172     if (treatAsSpace) {
173         if (ltr)
174             expandRight = true;
175         else
176             expandLeft = true;
177     }
178     if (isAfterExpansion) {
179         if (ltr)
180             expandLeft = false;
181         else
182             expandRight = false;
183     }
184     ASSERT(!forbidLeadingExpansion || !forceLeadingExpansion);
185     ASSERT(!forbidTrailingExpansion || !forceTrailingExpansion);
186     if (forbidLeadingExpansion)
187         expandLeft = false;
188     if (forbidTrailingExpansion)
189         expandRight = false;
190     if (forceLeadingExpansion)
191         expandLeft = true;
192     if (forceTrailingExpansion)
193         expandRight = true;
194     return std::make_pair(expandLeft, expandRight);
195 }
196
197 static inline bool isSoftBankEmoji(UChar32 codepoint)
198 {
199     return codepoint >= 0xE001 && codepoint <= 0xE537;
200 }
201
202 inline auto WidthIterator::shouldApplyFontTransforms(const GlyphBuffer* glyphBuffer, int lastGlyphCount, UChar32 previousCharacter) const -> TransformsType
203 {
204     if (glyphBuffer && glyphBuffer->size() == lastGlyphCount + 1 && isSoftBankEmoji(previousCharacter))
205         return TransformsType::Forced;
206     if (m_run.length() <= 1 || !(m_typesettingFeatures & (Kerning | Ligatures)))
207         return TransformsType::None;
208     return TransformsType::NotForced;
209 }
210
211 template <typename TextIterator>
212 inline unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer)
213 {
214     bool rtl = m_run.rtl();
215     bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled();
216
217     bool runForcesLeadingExpansion = (m_run.expansionBehavior() & LeadingExpansionMask) == ForceLeadingExpansion;
218     bool runForcesTrailingExpansion = (m_run.expansionBehavior() & TrailingExpansionMask) == ForceTrailingExpansion;
219     bool runForbidsLeadingExpansion = (m_run.expansionBehavior() & LeadingExpansionMask) == ForbidLeadingExpansion;
220     bool runForbidsTrailingExpansion = (m_run.expansionBehavior() & TrailingExpansionMask) == ForbidTrailingExpansion;
221     float widthSinceLastRounding = m_runWidthSoFar;
222     float leftoverJustificationWidth = 0;
223     m_runWidthSoFar = floorf(m_runWidthSoFar);
224     widthSinceLastRounding -= m_runWidthSoFar;
225
226     float lastRoundingWidth = m_finalRoundingWidth;
227     FloatRect bounds;
228
229     const Font& primaryFont = m_font->primaryFont();
230     const Font* lastFontData = &primaryFont;
231     int lastGlyphCount = glyphBuffer ? glyphBuffer->size() : 0;
232
233     UChar32 character = 0;
234     UChar32 previousCharacter = 0;
235     unsigned clusterLength = 0;
236     CharactersTreatedAsSpace charactersTreatedAsSpace;
237     String normalizedSpacesStringCache;
238     // We are iterating in string order, not glyph order. Compare this to ComplexTextController::adjustGlyphsAndAdvances()
239     while (textIterator.consume(character, clusterLength)) {
240         unsigned advanceLength = clusterLength;
241         int currentCharacter = textIterator.currentCharacter();
242         const GlyphData& glyphData = glyphDataForCharacter(character, rtl, currentCharacter, advanceLength, normalizedSpacesStringCache);
243         Glyph glyph = glyphData.glyph;
244         if (!glyph) {
245             textIterator.advance(advanceLength);
246             continue;
247         }
248         const Font* font = glyphData.font;
249         ASSERT(font);
250
251         // Now that we have a glyph and font data, get its width.
252         float width;
253         if (character == '\t' && m_run.allowTabs())
254             width = m_font->tabWidth(*font, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding);
255         else {
256             width = font->widthForGlyph(glyph);
257
258             // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
259             width *= m_run.horizontalGlyphStretch();
260
261             // We special case spaces in two ways when applying word rounding.
262             // First, we round spaces to an adjusted width in all fonts.
263             // Second, in fixed-pitch fonts we ensure that all characters that
264             // match the width of the space character have the same width as the space character.
265             if (m_run.applyWordRounding() && width == font->spaceWidth() && (font->pitch() == FixedPitch || glyph == font->spaceGlyph()))
266                 width = font->adjustedSpaceWidth();
267         }
268
269         if (font != lastFontData && width) {
270             auto transformsType = shouldApplyFontTransforms(glyphBuffer, lastGlyphCount, previousCharacter);
271             if (transformsType != TransformsType::None) {
272                 m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, *this, m_typesettingFeatures, transformsType == TransformsType::Forced, charactersTreatedAsSpace);
273                 if (glyphBuffer)
274                     glyphBuffer->shrink(lastGlyphCount);
275             }
276
277             lastFontData = font;
278             if (m_fallbackFonts && font != &primaryFont) {
279                 // FIXME: This does a little extra work that could be avoided if
280                 // glyphDataForCharacter() returned whether it chose to use a small caps font.
281                 if (!m_font->isSmallCaps() || character == u_toupper(character))
282                     m_fallbackFonts->add(font);
283                 else {
284                     const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(u_toupper(character), rtl);
285                     if (uppercaseGlyphData.font != &primaryFont)
286                         m_fallbackFonts->add(uppercaseGlyphData.font);
287                 }
288             }
289         }
290
291         if (hasExtraSpacing) {
292             // Account for letter-spacing.
293             if (width) {
294                 width += m_font->letterSpacing();
295                 width += leftoverJustificationWidth;
296                 leftoverJustificationWidth = 0;
297             }
298
299             static bool expandAroundIdeographs = FontCascade::canExpandAroundIdeographsInComplexText();
300             bool treatAsSpace = FontCascade::treatAsSpace(character);
301             bool currentIsLastCharacter = currentCharacter + advanceLength == static_cast<size_t>(m_run.length());
302             bool forceLeadingExpansion = false; // On the left, regardless of m_run.ltr()
303             bool forceTrailingExpansion = false; // On the right, regardless of m_run.ltr()
304             bool forbidLeadingExpansion = false;
305             bool forbidTrailingExpansion = false;
306             if (runForcesLeadingExpansion)
307                 forceLeadingExpansion = m_run.ltr() ? !currentCharacter : currentIsLastCharacter;
308             if (runForcesTrailingExpansion)
309                 forceTrailingExpansion = m_run.ltr() ? currentIsLastCharacter : !currentCharacter;
310             if (runForbidsLeadingExpansion)
311                 forbidLeadingExpansion = m_run.ltr() ? !currentCharacter : currentIsLastCharacter;
312             if (runForbidsTrailingExpansion)
313                 forbidTrailingExpansion = m_run.ltr() ? currentIsLastCharacter : !currentCharacter;
314             bool ideograph = (expandAroundIdeographs && FontCascade::isCJKIdeographOrSymbol(character));
315             if (treatAsSpace || ideograph || forceLeadingExpansion || forceTrailingExpansion) {
316                 // Distribute the run's total expansion evenly over all expansion opportunities in the run.
317                 if (m_expansion) {
318                     bool expandLeft, expandRight;
319                     std::tie(expandLeft, expandRight) = expansionLocation(ideograph, treatAsSpace, m_run.ltr(), m_isAfterExpansion, forbidLeadingExpansion, forbidTrailingExpansion, forceLeadingExpansion, forceTrailingExpansion);
320                     float previousExpansion = m_expansion;
321                     if (expandLeft) {
322                         if (m_run.ltr()) {
323                             // Increase previous width
324                             m_expansion -= m_expansionPerOpportunity;
325                             float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
326                             m_runWidthSoFar += expansionAtThisOpportunity;
327                             if (glyphBuffer) {
328                                 if (glyphBuffer->isEmpty()) {
329                                     if (m_forTextEmphasis)
330                                         glyphBuffer->add(font->zeroWidthSpaceGlyph(), font, m_expansionPerOpportunity, currentCharacter);
331                                     else
332                                         glyphBuffer->add(font->spaceGlyph(), font, expansionAtThisOpportunity, currentCharacter);
333                                 } else
334                                     glyphBuffer->expandLastAdvance(expansionAtThisOpportunity);
335                             }
336                         } else {
337                             // Increase next width
338                             float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion - m_expansionPerOpportunity);
339                             leftoverJustificationWidth += expansionAtThisOpportunity;
340                             m_isAfterExpansion = true;
341                         }
342                         previousExpansion = m_expansion;
343                     }
344                     if (expandRight) {
345                         m_expansion -= m_expansionPerOpportunity;
346                         float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
347                         width += expansionAtThisOpportunity;
348                         if (m_run.ltr())
349                             m_isAfterExpansion = true;
350                     }
351                 } else
352                     m_isAfterExpansion = false;
353
354                 // Account for word spacing.
355                 // We apply additional space between "words" by adding width to the space character.
356                 if (treatAsSpace && (character != '\t' || !m_run.allowTabs()) && (currentCharacter || character == noBreakSpace) && m_font->wordSpacing())
357                     width += m_font->wordSpacing();
358             } else
359                 m_isAfterExpansion = false;
360         }
361
362         auto transformsType = shouldApplyFontTransforms(glyphBuffer, lastGlyphCount, previousCharacter);
363         if (transformsType != TransformsType::None && glyphBuffer && FontCascade::treatAsSpace(character)) {
364             charactersTreatedAsSpace.append(std::make_pair(glyphBuffer->size(),
365                 OriginalAdvancesForCharacterTreatedAsSpace(character == ' ', glyphBuffer->size() ? glyphBuffer->advanceAt(glyphBuffer->size() - 1).width() : 0, width)));
366         }
367
368         if (m_accountForGlyphBounds) {
369             bounds = font->boundsForGlyph(glyph);
370             if (!currentCharacter)
371                 m_firstGlyphOverflow = std::max<float>(0, -bounds.x());
372         }
373
374         if (m_forTextEmphasis && !FontCascade::canReceiveTextEmphasis(character))
375             glyph = 0;
376
377         // Advance past the character we just dealt with.
378         textIterator.advance(advanceLength);
379
380         float oldWidth = width;
381
382         // Force characters that are used to determine word boundaries for the rounding hack
383         // to be integer width, so following words will start on an integer boundary.
384         if (m_run.applyWordRounding() && FontCascade::isRoundingHackCharacter(character)) {
385             width = ceilf(width);
386
387             // Since widthSinceLastRounding can lose precision if we include measurements for
388             // preceding whitespace, we bypass it here.
389             m_runWidthSoFar += width;
390
391             // Since this is a rounding hack character, we should have reset this sum on the previous
392             // iteration.
393             ASSERT(!widthSinceLastRounding);
394         } else {
395             // Check to see if the next character is a "rounding hack character", if so, adjust
396             // width so that the total run width will be on an integer boundary.
397             if ((m_run.applyWordRounding() && static_cast<unsigned>(textIterator.currentCharacter()) < m_run.length() && FontCascade::isRoundingHackCharacter(*(textIterator.characters())))
398                 || (m_run.applyRunRounding() && static_cast<unsigned>(textIterator.currentCharacter()) >= m_run.length())) {
399                 float totalWidth = widthSinceLastRounding + width;
400                 widthSinceLastRounding = ceilf(totalWidth);
401                 width += widthSinceLastRounding - totalWidth;
402                 m_runWidthSoFar += widthSinceLastRounding;
403                 widthSinceLastRounding = 0;
404             } else
405                 widthSinceLastRounding += width;
406         }
407
408         if (glyphBuffer)
409             glyphBuffer->add(glyph, font, (rtl ? oldWidth + lastRoundingWidth : width), currentCharacter);
410
411         lastRoundingWidth = width - oldWidth;
412
413         if (m_accountForGlyphBounds) {
414             m_maxGlyphBoundingBoxY = std::max(m_maxGlyphBoundingBoxY, bounds.maxY());
415             m_minGlyphBoundingBoxY = std::min(m_minGlyphBoundingBoxY, bounds.y());
416             m_lastGlyphOverflow = std::max<float>(0, bounds.maxX() - width);
417         }
418         previousCharacter = character;
419     }
420
421     if (leftoverJustificationWidth) {
422         if (m_forTextEmphasis)
423             glyphBuffer->add(lastFontData->zeroWidthSpaceGlyph(), lastFontData, leftoverJustificationWidth, m_run.length());
424         else
425             glyphBuffer->add(lastFontData->spaceGlyph(), lastFontData, leftoverJustificationWidth, m_run.length());
426     }
427
428     auto transformsType = shouldApplyFontTransforms(glyphBuffer, lastGlyphCount, previousCharacter);
429     if (transformsType != TransformsType::None) {
430         m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, *this, m_typesettingFeatures, transformsType == TransformsType::Forced, charactersTreatedAsSpace);
431         if (glyphBuffer)
432             glyphBuffer->shrink(lastGlyphCount);
433     }
434
435     unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter;
436     m_currentCharacter = textIterator.currentCharacter();
437     m_runWidthSoFar += widthSinceLastRounding;
438     m_finalRoundingWidth = lastRoundingWidth;
439     return consumedCharacters;
440 }
441
442 unsigned WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
443 {
444     int length = m_run.length();
445
446     if (offset > length)
447         offset = length;
448
449     if (m_currentCharacter >= static_cast<unsigned>(offset))
450         return 0;
451
452     if (m_run.is8Bit()) {
453         Latin1TextIterator textIterator(m_run.data8(m_currentCharacter), m_currentCharacter, offset, length);
454         return advanceInternal(textIterator, glyphBuffer);
455     }
456
457     SurrogatePairAwareTextIterator textIterator(m_run.data16(m_currentCharacter), m_currentCharacter, offset, length);
458     return advanceInternal(textIterator, glyphBuffer);
459 }
460
461 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer& glyphBuffer)
462 {
463     int oldSize = glyphBuffer.size();
464     advance(m_currentCharacter + 1, &glyphBuffer);
465     float w = 0;
466     for (int i = oldSize; i < glyphBuffer.size(); ++i)
467         w += glyphBuffer.advanceAt(i).width();
468     width = w;
469     return glyphBuffer.size() > oldSize;
470 }
471
472 }