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