• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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