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 "platform/fonts/WidthIterator.h" 24 25 #include "platform/fonts/Character.h" 26 #include "platform/fonts/FontPlatformFeatures.h" 27 #include "platform/fonts/GlyphBuffer.h" 28 #include "platform/fonts/Latin1TextIterator.h" 29 #include "platform/fonts/SimpleFontData.h" 30 #include "platform/text/SurrogatePairAwareTextIterator.h" 31 #include "wtf/MathExtras.h" 32 33 using namespace WTF; 34 using namespace Unicode; 35 using namespace std; 36 37 namespace WebCore { 38 39 WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, bool accountForGlyphBounds, bool forTextEmphasis) 40 : m_font(font) 41 , m_run(run) 42 , m_currentCharacter(0) 43 , m_runWidthSoFar(0) 44 , m_isAfterExpansion(!run.allowsLeadingExpansion()) 45 , m_finalRoundingWidth(0) 46 , m_typesettingFeatures(font->fontDescription().typesettingFeatures()) 47 , m_fallbackFonts(fallbackFonts) 48 , m_accountForGlyphBounds(accountForGlyphBounds) 49 , m_maxGlyphBoundingBoxY(numeric_limits<float>::min()) 50 , m_minGlyphBoundingBoxY(numeric_limits<float>::max()) 51 , m_firstGlyphOverflow(0) 52 , m_lastGlyphOverflow(0) 53 , m_forTextEmphasis(forTextEmphasis) 54 { 55 // If the padding is non-zero, count the number of spaces in the run 56 // and divide that by the padding for per space addition. 57 m_expansion = m_run.expansion(); 58 if (!m_expansion) 59 m_expansionPerOpportunity = 0; 60 else { 61 bool isAfterExpansion = m_isAfterExpansion; 62 unsigned expansionOpportunityCount = m_run.is8Bit() ? Character::expansionOpportunityCount(m_run.characters8(), m_run.length(), m_run.direction(), isAfterExpansion) : Character::expansionOpportunityCount(m_run.characters16(), m_run.length(), m_run.direction(), isAfterExpansion); 63 if (isAfterExpansion && !m_run.allowsTrailingExpansion()) 64 expansionOpportunityCount--; 65 66 if (!expansionOpportunityCount) 67 m_expansionPerOpportunity = 0; 68 else 69 m_expansionPerOpportunity = m_expansion / expansionOpportunityCount; 70 } 71 } 72 73 GlyphData WidthIterator::glyphDataForCharacter(UChar32 character, bool mirror, int currentCharacter, unsigned& advanceLength) 74 { 75 ASSERT(m_font); 76 77 #if ENABLE(SVG_FONTS) 78 if (TextRun::RenderingContext* renderingContext = m_run.renderingContext()) 79 return renderingContext->glyphDataForCharacter(*m_font, m_run, *this, character, mirror, currentCharacter, advanceLength); 80 #endif 81 82 return m_font->glyphDataForCharacter(character, mirror); 83 } 84 85 struct OriginalAdvancesForCharacterTreatedAsSpace { 86 public: 87 OriginalAdvancesForCharacterTreatedAsSpace(bool isSpace, float advanceBefore, float advanceAt) 88 : characterIsSpace(isSpace) 89 , advanceBeforeCharacter(advanceBefore) 90 , advanceAtCharacter(advanceAt) 91 { 92 } 93 94 bool characterIsSpace; 95 float advanceBeforeCharacter; 96 float advanceAtCharacter; 97 }; 98 99 typedef Vector<pair<int, OriginalAdvancesForCharacterTreatedAsSpace>, 64> CharactersTreatedAsSpace; 100 101 static inline float applyFontTransforms(GlyphBuffer* glyphBuffer, unsigned& lastGlyphCount, TypesettingFeatures typesettingFeatures, CharactersTreatedAsSpace& charactersTreatedAsSpace) 102 { 103 ASSERT(typesettingFeatures & (Kerning | Ligatures)); 104 105 if (!glyphBuffer) 106 return 0; 107 108 unsigned glyphBufferSize = glyphBuffer->size(); 109 if (glyphBuffer->size() <= lastGlyphCount + 1) 110 return 0; 111 112 FloatSize* advances = glyphBuffer->advances(0); 113 float widthDifference = 0; 114 for (unsigned i = lastGlyphCount; i < glyphBufferSize; ++i) 115 widthDifference -= advances[i].width(); 116 117 for (size_t i = 0; i < charactersTreatedAsSpace.size(); ++i) { 118 int spaceOffset = charactersTreatedAsSpace[i].first; 119 const OriginalAdvancesForCharacterTreatedAsSpace& originalAdvances = charactersTreatedAsSpace[i].second; 120 if (spaceOffset && !originalAdvances.characterIsSpace) 121 glyphBuffer->advances(spaceOffset - 1)->setWidth(originalAdvances.advanceBeforeCharacter); 122 glyphBuffer->advances(spaceOffset)->setWidth(originalAdvances.advanceAtCharacter); 123 } 124 charactersTreatedAsSpace.clear(); 125 126 for (unsigned i = lastGlyphCount; i < glyphBufferSize; ++i) 127 widthDifference += advances[i].width(); 128 129 lastGlyphCount = glyphBufferSize; 130 return widthDifference; 131 } 132 133 template <typename TextIterator> 134 inline unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer) 135 { 136 bool rtl = m_run.rtl(); 137 bool hasExtraSpacing = (m_font->fontDescription().letterSpacing() || m_font->fontDescription().wordSpacing() || m_expansion) && !m_run.spacingDisabled(); 138 139 float widthSinceLastRounding = m_runWidthSoFar; 140 m_runWidthSoFar = floorf(m_runWidthSoFar); 141 widthSinceLastRounding -= m_runWidthSoFar; 142 143 float lastRoundingWidth = m_finalRoundingWidth; 144 FloatRect bounds; 145 146 const SimpleFontData* primaryFont = m_font->primaryFont(); 147 const SimpleFontData* lastFontData = primaryFont; 148 unsigned lastGlyphCount = glyphBuffer ? glyphBuffer->size() : 0; 149 150 UChar32 character = 0; 151 unsigned clusterLength = 0; 152 CharactersTreatedAsSpace charactersTreatedAsSpace; 153 while (textIterator.consume(character, clusterLength)) { 154 unsigned advanceLength = clusterLength; 155 const GlyphData& glyphData = glyphDataForCharacter(character, rtl, textIterator.currentCharacter(), advanceLength); 156 Glyph glyph = glyphData.glyph; 157 const SimpleFontData* fontData = glyphData.fontData; 158 159 ASSERT(fontData); 160 161 // Now that we have a glyph and font data, get its width. 162 float width; 163 if (character == '\t' && m_run.allowTabs()) 164 width = m_font->tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding); 165 else { 166 width = fontData->widthForGlyph(glyph); 167 168 // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text. 169 width *= m_run.horizontalGlyphStretch(); 170 171 // We special case spaces in two ways when applying word rounding. 172 // First, we round spaces to an adjusted width in all fonts. 173 // Second, in fixed-pitch fonts we ensure that all characters that 174 // match the width of the space character have the same width as the space character. 175 if (m_run.applyWordRounding() && width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph())) 176 width = fontData->adjustedSpaceWidth(); 177 } 178 179 if (fontData != lastFontData && width) { 180 if (shouldApplyFontTransforms()) 181 m_runWidthSoFar += applyFontTransforms(glyphBuffer, lastGlyphCount, m_typesettingFeatures, charactersTreatedAsSpace); 182 183 lastFontData = fontData; 184 if (m_fallbackFonts && fontData != primaryFont) { 185 // FIXME: This does a little extra work that could be avoided if 186 // glyphDataForCharacter() returned whether it chose to use a small caps font. 187 if (m_font->fontDescription().variant() == FontVariantNormal || character == toUpper(character)) 188 m_fallbackFonts->add(fontData); 189 else { 190 ASSERT(m_font->fontDescription().variant() == FontVariantSmallCaps); 191 const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(character), rtl); 192 if (uppercaseGlyphData.fontData != primaryFont) 193 m_fallbackFonts->add(uppercaseGlyphData.fontData); 194 } 195 } 196 } 197 198 if (hasExtraSpacing) { 199 // Account for letter-spacing. 200 if (width && m_font->fontDescription().letterSpacing()) 201 width += m_font->fontDescription().letterSpacing(); 202 203 static bool expandAroundIdeographs = FontPlatformFeatures::canExpandAroundIdeographsInComplexText(); 204 bool treatAsSpace = Character::treatAsSpace(character); 205 if (treatAsSpace || (expandAroundIdeographs && Character::isCJKIdeographOrSymbol(character))) { 206 // Distribute the run's total expansion evenly over all expansion opportunities in the run. 207 if (m_expansion) { 208 float previousExpansion = m_expansion; 209 if (!treatAsSpace && !m_isAfterExpansion) { 210 // Take the expansion opportunity before this ideograph. 211 m_expansion -= m_expansionPerOpportunity; 212 float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); 213 m_runWidthSoFar += expansionAtThisOpportunity; 214 if (glyphBuffer) { 215 if (glyphBuffer->isEmpty()) { 216 if (m_forTextEmphasis) 217 glyphBuffer->add(fontData->zeroWidthSpaceGlyph(), fontData, m_expansionPerOpportunity); 218 else 219 glyphBuffer->add(fontData->spaceGlyph(), fontData, expansionAtThisOpportunity); 220 } else 221 glyphBuffer->expandLastAdvance(expansionAtThisOpportunity); 222 } 223 previousExpansion = m_expansion; 224 } 225 if (m_run.allowsTrailingExpansion() || (m_run.ltr() && textIterator.currentCharacter() + advanceLength < static_cast<size_t>(m_run.length())) 226 || (m_run.rtl() && textIterator.currentCharacter())) { 227 m_expansion -= m_expansionPerOpportunity; 228 width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); 229 m_isAfterExpansion = true; 230 } 231 } else 232 m_isAfterExpansion = false; 233 234 // Account for word spacing. 235 // We apply additional space between "words" by adding width to the space character. 236 if (treatAsSpace && (character != '\t' || !m_run.allowTabs()) && (textIterator.currentCharacter() || character == noBreakSpace) && m_font->fontDescription().wordSpacing()) 237 width += m_font->fontDescription().wordSpacing(); 238 } else 239 m_isAfterExpansion = false; 240 } 241 242 if (shouldApplyFontTransforms() && glyphBuffer && Character::treatAsSpace(character)) { 243 charactersTreatedAsSpace.append(make_pair(glyphBuffer->size(), 244 OriginalAdvancesForCharacterTreatedAsSpace(character == ' ', 245 glyphBuffer->size() ? glyphBuffer->advanceAt(glyphBuffer->size() - 1).width() : 0, 246 width))); 247 } 248 249 if (m_accountForGlyphBounds) { 250 bounds = fontData->boundsForGlyph(glyph); 251 if (!textIterator.currentCharacter()) 252 m_firstGlyphOverflow = max<float>(0, -bounds.x()); 253 } 254 255 if (m_forTextEmphasis && !Character::canReceiveTextEmphasis(character)) 256 glyph = 0; 257 258 // Advance past the character we just dealt with. 259 textIterator.advance(advanceLength); 260 261 float oldWidth = width; 262 263 // Force characters that are used to determine word boundaries for the rounding hack 264 // to be integer width, so following words will start on an integer boundary. 265 if (m_run.applyWordRounding() && Character::isRoundingHackCharacter(character)) { 266 width = ceilf(width); 267 268 // Since widthSinceLastRounding can lose precision if we include measurements for 269 // preceding whitespace, we bypass it here. 270 m_runWidthSoFar += width; 271 272 // Since this is a rounding hack character, we should have reset this sum on the previous 273 // iteration. 274 ASSERT(!widthSinceLastRounding); 275 } else { 276 // Check to see if the next character is a "rounding hack character", if so, adjust 277 // width so that the total run width will be on an integer boundary. 278 if ((m_run.applyWordRounding() && textIterator.currentCharacter() < m_run.length() && Character::isRoundingHackCharacter(*(textIterator.characters()))) 279 || (m_run.applyRunRounding() && textIterator.currentCharacter() >= m_run.length())) { 280 float totalWidth = widthSinceLastRounding + width; 281 widthSinceLastRounding = ceilf(totalWidth); 282 width += widthSinceLastRounding - totalWidth; 283 m_runWidthSoFar += widthSinceLastRounding; 284 widthSinceLastRounding = 0; 285 } else 286 widthSinceLastRounding += width; 287 } 288 289 if (glyphBuffer) 290 glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width)); 291 292 lastRoundingWidth = width - oldWidth; 293 294 if (m_accountForGlyphBounds) { 295 m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY()); 296 m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y()); 297 m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width); 298 } 299 } 300 301 if (shouldApplyFontTransforms()) 302 m_runWidthSoFar += applyFontTransforms(glyphBuffer, lastGlyphCount, m_typesettingFeatures, charactersTreatedAsSpace); 303 304 unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter; 305 m_currentCharacter = textIterator.currentCharacter(); 306 m_runWidthSoFar += widthSinceLastRounding; 307 m_finalRoundingWidth = lastRoundingWidth; 308 return consumedCharacters; 309 } 310 311 unsigned WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer) 312 { 313 int length = m_run.length(); 314 315 if (offset > length) 316 offset = length; 317 318 if (m_currentCharacter >= static_cast<unsigned>(offset)) 319 return 0; 320 321 if (m_run.is8Bit()) { 322 Latin1TextIterator textIterator(m_run.data8(m_currentCharacter), m_currentCharacter, offset, length); 323 return advanceInternal(textIterator, glyphBuffer); 324 } 325 326 SurrogatePairAwareTextIterator textIterator(m_run.data16(m_currentCharacter), m_currentCharacter, offset, length); 327 return advanceInternal(textIterator, glyphBuffer); 328 } 329 330 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer& glyphBuffer) 331 { 332 unsigned oldSize = glyphBuffer.size(); 333 advance(m_currentCharacter + 1, &glyphBuffer); 334 float w = 0; 335 for (unsigned i = oldSize; i < glyphBuffer.size(); ++i) 336 w += glyphBuffer.advanceAt(i).width(); 337 width = w; 338 return glyphBuffer.size() > oldSize; 339 } 340 341 } 342