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 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 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 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 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