WidthIterator.cpp revision 2fc2651226baac27029e38c9d6ef883fa32084db
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 35using namespace WTF; 36using namespace Unicode; 37using namespace std; 38 39namespace WebCore { 40 41// According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values 42static const uint8_t hiraganaKatakanaVoicingMarksCombiningClass = 8; 43 44WidthIterator::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(true) 51 , m_finalRoundingWidth(0) 52 , m_fallbackFonts(fallbackFonts) 53 , m_accountForGlyphBounds(accountForGlyphBounds) 54 , m_maxGlyphBoundingBoxY(numeric_limits<float>::min()) 55 , m_minGlyphBoundingBoxY(numeric_limits<float>::max()) 56 , m_firstGlyphOverflow(0) 57 , m_lastGlyphOverflow(0) 58 , m_forTextEmphasis(forTextEmphasis) 59{ 60 // If the padding is non-zero, count the number of spaces in the run 61 // and divide that by the padding for per space addition. 62 m_expansion = m_run.expansion(); 63 if (!m_expansion) 64 m_expansionPerOpportunity = 0; 65 else { 66 bool isAfterExpansion = true; 67 unsigned expansionOpportunityCount = Font::expansionOpportunityCount(m_run.characters(), m_end, m_run.ltr() ? LTR : RTL, isAfterExpansion); 68 if (isAfterExpansion && !m_run.allowsTrailingExpansion()) 69 expansionOpportunityCount--; 70 71 if (!expansionOpportunityCount) 72 m_expansionPerOpportunity = 0; 73 else 74 m_expansionPerOpportunity = m_expansion / expansionOpportunityCount; 75 } 76} 77 78void WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer) 79{ 80 if (offset > m_end) 81 offset = m_end; 82 83 int currentCharacter = m_currentCharacter; 84 const UChar* cp = m_run.data(currentCharacter); 85 86 bool rtl = m_run.rtl(); 87 bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled(); 88 89 float widthSinceLastRounding = m_runWidthSoFar; 90 m_runWidthSoFar = floorf(m_runWidthSoFar); 91 widthSinceLastRounding -= m_runWidthSoFar; 92 93 float lastRoundingWidth = m_finalRoundingWidth; 94 FloatRect bounds; 95 96 const SimpleFontData* primaryFont = m_font->primaryFont(); 97 const SimpleFontData* lastFontData = primaryFont; 98 99 while (currentCharacter < offset) { 100 UChar32 c = *cp; 101 unsigned clusterLength = 1; 102 if (c >= 0x3041) { 103 if (c <= 0x30FE) { 104 // Deal with Hiragana and Katakana voiced and semi-voiced syllables. 105 // Normalize into composed form, and then look for glyph with base + combined mark. 106 // Check above for character range to minimize performance impact. 107 UChar32 normalized = normalizeVoicingMarks(currentCharacter); 108 if (normalized) { 109 c = normalized; 110 clusterLength = 2; 111 } 112 } else if (U16_IS_SURROGATE(c)) { 113 if (!U16_IS_SURROGATE_LEAD(c)) 114 break; 115 116 // Do we have a surrogate pair? If so, determine the full Unicode (32 bit) 117 // code point before glyph lookup. 118 // Make sure we have another character and it's a low surrogate. 119 if (currentCharacter + 1 >= m_run.length()) 120 break; 121 UChar low = cp[1]; 122 if (!U16_IS_TRAIL(low)) 123 break; 124 c = U16_GET_SUPPLEMENTARY(c, low); 125 clusterLength = 2; 126 } 127 } 128 129 const GlyphData& glyphData = m_font->glyphDataForCharacter(c, rtl); 130 Glyph glyph = glyphData.glyph; 131 const SimpleFontData* fontData = glyphData.fontData; 132 133 ASSERT(fontData); 134 135 // Now that we have a glyph and font data, get its width. 136 float width; 137 if (c == '\t' && m_run.allowTabs()) { 138 float tabWidth = m_font->tabWidth(*fontData); 139 width = tabWidth - fmodf(m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding, tabWidth); 140 } else { 141 width = fontData->widthForGlyph(glyph); 142 143#if ENABLE(SVG) 144 // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text. 145 width *= m_run.horizontalGlyphStretch(); 146#endif 147 148 // We special case spaces in two ways when applying word rounding. 149 // First, we round spaces to an adjusted width in all fonts. 150 // Second, in fixed-pitch fonts we ensure that all characters that 151 // match the width of the space character have the same width as the space character. 152 if (width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()) && m_run.applyWordRounding()) 153 width = fontData->adjustedSpaceWidth(); 154 } 155 156 if (fontData != lastFontData && width) { 157 lastFontData = fontData; 158 if (m_fallbackFonts && fontData != primaryFont) { 159 // FIXME: This does a little extra work that could be avoided if 160 // glyphDataForCharacter() returned whether it chose to use a small caps font. 161 if (!m_font->isSmallCaps() || c == toUpper(c)) 162 m_fallbackFonts->add(fontData); 163 else { 164 const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(c), rtl); 165 if (uppercaseGlyphData.fontData != primaryFont) 166 m_fallbackFonts->add(uppercaseGlyphData.fontData); 167 } 168 } 169 } 170 171 if (hasExtraSpacing) { 172 // Account for letter-spacing. 173 if (width && m_font->letterSpacing()) 174 width += m_font->letterSpacing(); 175 176 static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText(); 177 bool treatAsSpace = Font::treatAsSpace(c); 178 if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(c))) { 179 // Distribute the run's total expansion evenly over all expansion opportunities in the run. 180 if (m_expansion && (m_run.allowsTrailingExpansion() || (m_run.ltr() && currentCharacter + clusterLength < static_cast<size_t>(m_run.length())) 181 || (m_run.rtl() && currentCharacter))) { 182 float previousExpansion = m_expansion; 183 if (!treatAsSpace && !m_isAfterExpansion) { 184 // Take the expansion opportunity before this ideograph. 185 m_expansion -= m_expansionPerOpportunity; 186 int expansion = roundf(previousExpansion) - roundf(m_expansion); 187 m_runWidthSoFar += expansion; 188 if (glyphBuffer) 189 glyphBuffer->expandLastAdvance(expansion); 190 previousExpansion = m_expansion; 191 } 192 m_expansion -= m_expansionPerOpportunity; 193 width += roundf(previousExpansion) - roundf(m_expansion); 194 m_isAfterExpansion = true; 195 } else 196 m_isAfterExpansion = false; 197 198 // Account for word spacing. 199 // We apply additional space between "words" by adding width to the space character. 200 if (treatAsSpace && currentCharacter && !Font::treatAsSpace(cp[-1]) && m_font->wordSpacing()) 201 width += m_font->wordSpacing(); 202 } else 203 m_isAfterExpansion = false; 204 } 205 206 if (m_accountForGlyphBounds) { 207 bounds = fontData->boundsForGlyph(glyph); 208 if (!currentCharacter) 209 m_firstGlyphOverflow = max<float>(0, -bounds.x()); 210 } 211 212 if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(c)) 213 glyph = 0; 214 215 // Advance past the character we just dealt with. 216 cp += clusterLength; 217 currentCharacter += clusterLength; 218 219 // Account for float/integer impedance mismatch between CG and KHTML. "Words" (characters 220 // followed by a character defined by isRoundingHackCharacter()) are always an integer width. 221 // We adjust the width of the last character of a "word" to ensure an integer width. 222 // If we move KHTML to floats we can remove this (and related) hacks. 223 224 float oldWidth = width; 225 226 // Force characters that are used to determine word boundaries for the rounding hack 227 // to be integer width, so following words will start on an integer boundary. 228 if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(c)) { 229 width = ceilf(width); 230 231 // Since widthSinceLastRounding can lose precision if we include measurements for 232 // preceding whitespace, we bypass it here. 233 m_runWidthSoFar += width; 234 235 // Since this is a rounding hack character, we should have reset this sum on the previous 236 // iteration. 237 ASSERT(!widthSinceLastRounding); 238 } else { 239 // Check to see if the next character is a "rounding hack character", if so, adjust 240 // width so that the total run width will be on an integer boundary. 241 if ((m_run.applyWordRounding() && currentCharacter < m_run.length() && Font::isRoundingHackCharacter(*cp)) 242 || (m_run.applyRunRounding() && currentCharacter >= m_end)) { 243 float totalWidth = widthSinceLastRounding + width; 244 widthSinceLastRounding = ceilf(totalWidth); 245 width += widthSinceLastRounding - totalWidth; 246 m_runWidthSoFar += widthSinceLastRounding; 247 widthSinceLastRounding = 0; 248 } else 249 widthSinceLastRounding += width; 250 } 251 252 if (glyphBuffer) 253 glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width)); 254 255 lastRoundingWidth = width - oldWidth; 256 257 if (m_accountForGlyphBounds) { 258 m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY()); 259 m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y()); 260 m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width); 261 } 262 } 263 264 m_currentCharacter = currentCharacter; 265 m_runWidthSoFar += widthSinceLastRounding; 266 m_finalRoundingWidth = lastRoundingWidth; 267} 268 269bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer* glyphBuffer) 270{ 271 int oldSize = glyphBuffer->size(); 272 advance(m_currentCharacter + 1, glyphBuffer); 273 float w = 0; 274 for (int i = oldSize; i < glyphBuffer->size(); ++i) 275 w += glyphBuffer->advanceAt(i); 276 width = w; 277 return glyphBuffer->size() > oldSize; 278} 279 280UChar32 WidthIterator::normalizeVoicingMarks(int currentCharacter) 281{ 282 if (currentCharacter + 1 < m_end) { 283 if (combiningClass(m_run[currentCharacter + 1]) == hiraganaKatakanaVoicingMarksCombiningClass) { 284#if USE(ICU_UNICODE) 285 // Normalize into composed form using 3.2 rules. 286 UChar normalizedCharacters[2] = { 0, 0 }; 287 UErrorCode uStatus = U_ZERO_ERROR; 288 int32_t resultLength = unorm_normalize(m_run.data(currentCharacter), 2, 289 UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus); 290 if (resultLength == 1 && uStatus == 0) 291 return normalizedCharacters[0]; 292#elif USE(QT4_UNICODE) 293 QString tmp(reinterpret_cast<const QChar*>(m_run.data(currentCharacter)), 2); 294 QString res = tmp.normalized(QString::NormalizationForm_C, QChar::Unicode_3_2); 295 if (res.length() == 1) 296 return res.at(0).unicode(); 297#endif 298 } 299 } 300 return 0; 301} 302 303} 304