1 /*
2 * Copyright (C) 2003, 2006, 2008 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 <wtf/MathExtras.h>
29
30 #if USE(ICU_UNICODE)
31 #include <unicode/unorm.h>
32 #endif
33
34 using namespace WTF;
35 using namespace Unicode;
36
37 namespace WebCore {
38
39 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
40 static const uint8_t hiraganaKatakanaVoicingMarksCombiningClass = 8;
41
WidthIterator(const Font * font,const TextRun & run,HashSet<const SimpleFontData * > * fallbackFonts)42 WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts)
43 : m_font(font)
44 , m_run(run)
45 , m_end(run.length())
46 , m_currentCharacter(0)
47 , m_runWidthSoFar(0)
48 , m_finalRoundingWidth(0)
49 , m_fallbackFonts(fallbackFonts)
50 {
51 // If the padding is non-zero, count the number of spaces in the run
52 // and divide that by the padding for per space addition.
53 m_padding = m_run.padding();
54 if (!m_padding)
55 m_padPerSpace = 0;
56 else {
57 float numSpaces = 0;
58 for (int i = 0; i < run.length(); i++)
59 if (Font::treatAsSpace(m_run[i]))
60 numSpaces++;
61
62 if (numSpaces == 0)
63 m_padPerSpace = 0;
64 else
65 m_padPerSpace = ceilf(m_run.padding() / numSpaces);
66 }
67 }
68
advance(int offset,GlyphBuffer * glyphBuffer)69 void WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
70 {
71 if (offset > m_end)
72 offset = m_end;
73
74 int currentCharacter = m_currentCharacter;
75 const UChar* cp = m_run.data(currentCharacter);
76
77 bool rtl = m_run.rtl();
78 bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_padding) && !m_run.spacingDisabled();
79
80 float runWidthSoFar = m_runWidthSoFar;
81 float lastRoundingWidth = m_finalRoundingWidth;
82
83 const SimpleFontData* primaryFont = m_font->primaryFont();
84 const SimpleFontData* lastFontData = primaryFont;
85
86 while (currentCharacter < offset) {
87 UChar32 c = *cp;
88 unsigned clusterLength = 1;
89 if (c >= 0x3041) {
90 if (c <= 0x30FE) {
91 // Deal with Hiragana and Katakana voiced and semi-voiced syllables.
92 // Normalize into composed form, and then look for glyph with base + combined mark.
93 // Check above for character range to minimize performance impact.
94 UChar32 normalized = normalizeVoicingMarks(currentCharacter);
95 if (normalized) {
96 c = normalized;
97 clusterLength = 2;
98 }
99 } else if (U16_IS_SURROGATE(c)) {
100 if (!U16_IS_SURROGATE_LEAD(c))
101 break;
102
103 // Do we have a surrogate pair? If so, determine the full Unicode (32 bit)
104 // code point before glyph lookup.
105 // Make sure we have another character and it's a low surrogate.
106 if (currentCharacter + 1 >= m_run.length())
107 break;
108 UChar low = cp[1];
109 if (!U16_IS_TRAIL(low))
110 break;
111 c = U16_GET_SUPPLEMENTARY(c, low);
112 clusterLength = 2;
113 }
114 }
115
116 const GlyphData& glyphData = m_font->glyphDataForCharacter(c, rtl);
117 Glyph glyph = glyphData.glyph;
118 const SimpleFontData* fontData = glyphData.fontData;
119
120 ASSERT(fontData);
121
122 // Now that we have a glyph and font data, get its width.
123 float width;
124 if (c == '\t' && m_run.allowTabs()) {
125 float tabWidth = m_font->tabWidth();
126 width = tabWidth - fmodf(m_run.xPos() + runWidthSoFar, tabWidth);
127 } else {
128 width = fontData->widthForGlyph(glyph);
129 // We special case spaces in two ways when applying word rounding.
130 // First, we round spaces to an adjusted width in all fonts.
131 // Second, in fixed-pitch fonts we ensure that all characters that
132 // match the width of the space character have the same width as the space character.
133 if (width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()) && m_run.applyWordRounding())
134 width = fontData->adjustedSpaceWidth();
135 }
136
137 if (fontData != lastFontData && width) {
138 lastFontData = fontData;
139 if (m_fallbackFonts && fontData != primaryFont) {
140 // FIXME: This does a little extra work that could be avoided if
141 // glyphDataForCharacter() returned whether it chose to use a small caps font.
142 if (!m_font->isSmallCaps() || c == toUpper(c))
143 m_fallbackFonts->add(fontData);
144 else {
145 const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(c), rtl);
146 if (uppercaseGlyphData.fontData != primaryFont)
147 m_fallbackFonts->add(uppercaseGlyphData.fontData);
148 }
149 }
150 }
151
152 if (hasExtraSpacing) {
153 // Account for letter-spacing.
154 if (width && m_font->letterSpacing())
155 width += m_font->letterSpacing();
156
157 if (Font::treatAsSpace(c)) {
158 // Account for padding. WebCore uses space padding to justify text.
159 // We distribute the specified padding over the available spaces in the run.
160 if (m_padding) {
161 // Use left over padding if not evenly divisible by number of spaces.
162 if (m_padding < m_padPerSpace) {
163 width += m_padding;
164 m_padding = 0;
165 } else {
166 width += m_padPerSpace;
167 m_padding -= m_padPerSpace;
168 }
169 }
170
171 // Account for word spacing.
172 // We apply additional space between "words" by adding width to the space character.
173 if (currentCharacter != 0 && !Font::treatAsSpace(cp[-1]) && m_font->wordSpacing())
174 width += m_font->wordSpacing();
175 }
176 }
177
178 // Advance past the character we just dealt with.
179 cp += clusterLength;
180 currentCharacter += clusterLength;
181
182 // Account for float/integer impedance mismatch between CG and KHTML. "Words" (characters
183 // followed by a character defined by isRoundingHackCharacter()) are always an integer width.
184 // We adjust the width of the last character of a "word" to ensure an integer width.
185 // If we move KHTML to floats we can remove this (and related) hacks.
186
187 float oldWidth = width;
188
189 // Force characters that are used to determine word boundaries for the rounding hack
190 // to be integer width, so following words will start on an integer boundary.
191 if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(c))
192 width = ceilf(width);
193
194 // Check to see if the next character is a "rounding hack character", if so, adjust
195 // width so that the total run width will be on an integer boundary.
196 if ((m_run.applyWordRounding() && currentCharacter < m_run.length() && Font::isRoundingHackCharacter(*cp))
197 || (m_run.applyRunRounding() && currentCharacter >= m_end)) {
198 float totalWidth = runWidthSoFar + width;
199 width += ceilf(totalWidth) - totalWidth;
200 }
201
202 runWidthSoFar += width;
203
204 if (glyphBuffer)
205 glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width));
206
207 lastRoundingWidth = width - oldWidth;
208 }
209
210 m_currentCharacter = currentCharacter;
211 m_runWidthSoFar = runWidthSoFar;
212 m_finalRoundingWidth = lastRoundingWidth;
213 }
214
advanceOneCharacter(float & width,GlyphBuffer * glyphBuffer)215 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer* glyphBuffer)
216 {
217 glyphBuffer->clear();
218 advance(m_currentCharacter + 1, glyphBuffer);
219 float w = 0;
220 for (int i = 0; i < glyphBuffer->size(); ++i)
221 w += glyphBuffer->advanceAt(i);
222 width = w;
223 return !glyphBuffer->isEmpty();
224 }
225
normalizeVoicingMarks(int currentCharacter)226 UChar32 WidthIterator::normalizeVoicingMarks(int currentCharacter)
227 {
228 if (currentCharacter + 1 < m_end) {
229 if (combiningClass(m_run[currentCharacter + 1]) == hiraganaKatakanaVoicingMarksCombiningClass) {
230 #if USE(ICU_UNICODE)
231 // Normalize into composed form using 3.2 rules.
232 UChar normalizedCharacters[2] = { 0, 0 };
233 UErrorCode uStatus = U_ZERO_ERROR;
234 int32_t resultLength = unorm_normalize(m_run.data(currentCharacter), 2,
235 UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus);
236 if (resultLength == 1 && uStatus == 0)
237 return normalizedCharacters[0];
238 #elif USE(QT4_UNICODE)
239 QString tmp(reinterpret_cast<const QChar*>(m_run.data(currentCharacter)), 2);
240 QString res = tmp.normalized(QString::NormalizationForm_C, QChar::Unicode_3_2);
241 if (res.length() == 1)
242 return res.at(0).unicode();
243 #endif
244 }
245 }
246 return 0;
247 }
248
249 }
250