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