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 "GlyphBuffer.h"
27 #include "SimpleFontData.h"
28 #include "TextRun.h"
29 #include <wtf/MathExtras.h>
30 
31 #if USE(ICU_UNICODE)
32 #include <unicode/unorm.h>
33 #endif
34 
35 using namespace WTF;
36 using namespace Unicode;
37 using namespace std;
38 
39 namespace WebCore {
40 
41 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
42 static const uint8_t hiraganaKatakanaVoicingMarksCombiningClass = 8;
43 
WidthIterator(const Font * font,const TextRun & run,HashSet<const SimpleFontData * > * fallbackFonts,bool accountForGlyphBounds,bool forTextEmphasis)44 WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, bool accountForGlyphBounds, bool forTextEmphasis)
45     : m_font(font)
46     , m_run(run)
47     , m_end(run.length())
48     , m_currentCharacter(0)
49     , m_runWidthSoFar(0)
50     , m_isAfterExpansion(!run.allowsLeadingExpansion())
51     , m_fallbackFonts(fallbackFonts)
52     , m_accountForGlyphBounds(accountForGlyphBounds)
53     , m_maxGlyphBoundingBoxY(numeric_limits<float>::min())
54     , m_minGlyphBoundingBoxY(numeric_limits<float>::max())
55     , m_firstGlyphOverflow(0)
56     , m_lastGlyphOverflow(0)
57     , m_forTextEmphasis(forTextEmphasis)
58 {
59     // If the padding is non-zero, count the number of spaces in the run
60     // and divide that by the padding for per space addition.
61     m_expansion = m_run.expansion();
62     if (!m_expansion)
63         m_expansionPerOpportunity = 0;
64     else {
65         bool isAfterExpansion = m_isAfterExpansion;
66         unsigned expansionOpportunityCount = Font::expansionOpportunityCount(m_run.characters(), m_end, m_run.ltr() ? LTR : RTL, isAfterExpansion);
67         if (isAfterExpansion && !m_run.allowsTrailingExpansion())
68             expansionOpportunityCount--;
69 
70         if (!expansionOpportunityCount)
71             m_expansionPerOpportunity = 0;
72         else
73             m_expansionPerOpportunity = m_expansion / expansionOpportunityCount;
74     }
75 }
76 
advance(int offset,GlyphBuffer * glyphBuffer)77 void WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
78 {
79     if (offset > m_end)
80         offset = m_end;
81 
82     int currentCharacter = m_currentCharacter;
83     if (currentCharacter >= offset)
84         return;
85 
86     const UChar* cp = m_run.data(currentCharacter);
87 
88     bool rtl = m_run.rtl();
89     bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled();
90 
91     FloatRect bounds;
92 
93     const SimpleFontData* primaryFont = m_font->primaryFont();
94     const SimpleFontData* lastFontData = primaryFont;
95 
96     while (currentCharacter < offset) {
97         UChar32 c = *cp;
98         unsigned clusterLength = 1;
99         if (c >= 0x3041) {
100             if (c <= 0x30FE) {
101                 // Deal with Hiragana and Katakana voiced and semi-voiced syllables.
102                 // Normalize into composed form, and then look for glyph with base + combined mark.
103                 // Check above for character range to minimize performance impact.
104                 UChar32 normalized = normalizeVoicingMarks(currentCharacter);
105                 if (normalized) {
106                     c = normalized;
107                     clusterLength = 2;
108                 }
109             } else if (U16_IS_SURROGATE(c)) {
110                 if (!U16_IS_SURROGATE_LEAD(c))
111                     break;
112 
113                 // Do we have a surrogate pair?  If so, determine the full Unicode (32 bit)
114                 // code point before glyph lookup.
115                 // Make sure we have another character and it's a low surrogate.
116                 if (currentCharacter + 1 >= m_run.length())
117                     break;
118                 UChar low = cp[1];
119                 if (!U16_IS_TRAIL(low))
120                     break;
121                 c = U16_GET_SUPPLEMENTARY(c, low);
122                 clusterLength = 2;
123             }
124         }
125 
126         const GlyphData& glyphData = m_font->glyphDataForCharacter(c, rtl);
127         Glyph glyph = glyphData.glyph;
128         const SimpleFontData* fontData = glyphData.fontData;
129 
130         ASSERT(fontData);
131 
132         // Now that we have a glyph and font data, get its width.
133         float width;
134         if (c == '\t' && m_run.allowTabs()) {
135             float tabWidth = m_font->tabWidth(*fontData);
136             width = tabWidth - fmodf(m_run.xPos() + m_runWidthSoFar, tabWidth);
137         } else {
138             width = fontData->widthForGlyph(glyph);
139 
140 #if ENABLE(SVG)
141             // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
142             width *= m_run.horizontalGlyphStretch();
143 #endif
144         }
145 
146         if (fontData != lastFontData && width) {
147             lastFontData = fontData;
148             if (m_fallbackFonts && fontData != primaryFont) {
149                 // FIXME: This does a little extra work that could be avoided if
150                 // glyphDataForCharacter() returned whether it chose to use a small caps font.
151                 if (!m_font->isSmallCaps() || c == toUpper(c))
152                     m_fallbackFonts->add(fontData);
153                 else {
154                     const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(c), rtl);
155                     if (uppercaseGlyphData.fontData != primaryFont)
156                         m_fallbackFonts->add(uppercaseGlyphData.fontData);
157                 }
158             }
159         }
160 
161         if (hasExtraSpacing) {
162             // Account for letter-spacing.
163             if (width && m_font->letterSpacing())
164                 width += m_font->letterSpacing();
165 
166             static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText();
167             bool treatAsSpace = Font::treatAsSpace(c);
168             if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(c))) {
169                 // Distribute the run's total expansion evenly over all expansion opportunities in the run.
170                 if (m_expansion) {
171                     if (!treatAsSpace && !m_isAfterExpansion) {
172                         // Take the expansion opportunity before this ideograph.
173                         m_expansion -= m_expansionPerOpportunity;
174                         m_runWidthSoFar += m_expansionPerOpportunity;
175                         if (glyphBuffer) {
176                             if (glyphBuffer->isEmpty())
177                                 glyphBuffer->add(fontData->spaceGlyph(), fontData, m_expansionPerOpportunity);
178                             else
179                                 glyphBuffer->expandLastAdvance(m_expansionPerOpportunity);
180                         }
181                     }
182                     if (m_run.allowsTrailingExpansion() || (m_run.ltr() && currentCharacter + clusterLength < static_cast<size_t>(m_run.length()))
183                         || (m_run.rtl() && currentCharacter)) {
184                         m_expansion -= m_expansionPerOpportunity;
185                         width += m_expansionPerOpportunity;
186                         m_isAfterExpansion = true;
187                     }
188                 } else
189                     m_isAfterExpansion = false;
190 
191                 // Account for word spacing.
192                 // We apply additional space between "words" by adding width to the space character.
193                 if (treatAsSpace && currentCharacter && !Font::treatAsSpace(cp[-1]) && m_font->wordSpacing())
194                     width += m_font->wordSpacing();
195             } else
196                 m_isAfterExpansion = false;
197         }
198 
199         if (m_accountForGlyphBounds) {
200             bounds = fontData->boundsForGlyph(glyph);
201             if (!currentCharacter)
202                 m_firstGlyphOverflow = max<float>(0, -bounds.x());
203         }
204 
205         if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(c))
206             glyph = 0;
207 
208         // Advance past the character we just dealt with.
209         cp += clusterLength;
210         currentCharacter += clusterLength;
211 
212         m_runWidthSoFar += width;
213 
214         if (glyphBuffer)
215             glyphBuffer->add(glyph, fontData, width);
216 
217         if (m_accountForGlyphBounds) {
218             m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY());
219             m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y());
220             m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width);
221         }
222     }
223 
224     m_currentCharacter = currentCharacter;
225 }
226 
advanceOneCharacter(float & width,GlyphBuffer * glyphBuffer)227 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer* glyphBuffer)
228 {
229     int oldSize = glyphBuffer->size();
230     advance(m_currentCharacter + 1, glyphBuffer);
231     float w = 0;
232     for (int i = oldSize; i < glyphBuffer->size(); ++i)
233         w += glyphBuffer->advanceAt(i);
234     width = w;
235     return glyphBuffer->size() > oldSize;
236 }
237 
normalizeVoicingMarks(int currentCharacter)238 UChar32 WidthIterator::normalizeVoicingMarks(int currentCharacter)
239 {
240     if (currentCharacter + 1 < m_end) {
241         if (combiningClass(m_run[currentCharacter + 1]) == hiraganaKatakanaVoicingMarksCombiningClass) {
242 #if USE(ICU_UNICODE)
243             // Normalize into composed form using 3.2 rules.
244             UChar normalizedCharacters[2] = { 0, 0 };
245             UErrorCode uStatus = U_ZERO_ERROR;
246             int32_t resultLength = unorm_normalize(m_run.data(currentCharacter), 2,
247                 UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus);
248             if (resultLength == 1 && uStatus == 0)
249                 return normalizedCharacters[0];
250 #elif USE(QT4_UNICODE)
251             QString tmp(reinterpret_cast<const QChar*>(m_run.data(currentCharacter)), 2);
252             QString res = tmp.normalized(QString::NormalizationForm_C, QChar::Unicode_3_2);
253             if (res.length() == 1)
254                 return res.at(0).unicode();
255 #endif
256         }
257     }
258     return 0;
259 }
260 
261 }
262