1 /* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "UniscribeController.h" 31 #include "Font.h" 32 #include "SimpleFontData.h" 33 #include <wtf/MathExtras.h> 34 35 namespace WebCore { 36 37 // FIXME: Rearchitect this to be more like WidthIterator in Font.cpp. Have an advance() method 38 // that does stuff in that method instead of doing everything in the constructor. Have advance() 39 // take the GlyphBuffer as an arg so that we don't have to populate the glyph buffer when 40 // measuring. 41 UniscribeController::UniscribeController(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts) 42 : m_font(*font) 43 , m_run(run) 44 , m_fallbackFonts(fallbackFonts) 45 , m_end(run.length()) 46 , m_currentCharacter(0) 47 , m_runWidthSoFar(0) 48 , m_computingOffsetPosition(false) 49 , m_includePartialGlyphs(false) 50 , m_offsetX(0) 51 , m_offsetPosition(0) 52 { 53 m_padding = m_run.padding(); 54 if (!m_padding) 55 m_padPerSpace = 0; 56 else { 57 float numSpaces = 0; 58 for (int s = 0; s < m_run.length(); s++) 59 if (Font::treatAsSpace(m_run[s])) 60 numSpaces++; 61 62 if (numSpaces == 0) 63 m_padPerSpace = 0; 64 else 65 m_padPerSpace = ceilf(m_run.padding() / numSpaces); 66 } 67 68 // Null out our uniscribe structs 69 resetControlAndState(); 70 } 71 72 int UniscribeController::offsetForPosition(int x, bool includePartialGlyphs) 73 { 74 m_computingOffsetPosition = true; 75 m_includePartialGlyphs = includePartialGlyphs; 76 m_offsetX = x; 77 m_offsetPosition = 0; 78 advance(m_run.length()); 79 if (m_computingOffsetPosition) { 80 // The point is to the left or to the right of the entire run. 81 if (m_offsetX >= m_runWidthSoFar && m_run.ltr() || m_offsetX < 0 && m_run.rtl()) 82 m_offsetPosition = m_end; 83 } 84 m_computingOffsetPosition = false; 85 return m_offsetPosition; 86 } 87 88 void UniscribeController::advance(unsigned offset, GlyphBuffer* glyphBuffer) 89 { 90 // FIXME: We really want to be using a newer version of Uniscribe that supports the new OpenType 91 // functions. Those functions would allow us to turn off kerning and ligatures. Without being able 92 // to do that, we will have buggy line breaking and metrics when simple and complex text are close 93 // together (the complex code path will narrow the text because of kerning and ligatures and then 94 // when bidi processing splits into multiple runs, the simple portions will get wider and cause us to 95 // spill off the edge of a line). 96 if (static_cast<int>(offset) > m_end) 97 offset = m_end; 98 99 // Itemize the string. 100 const UChar* cp = m_run.data(m_currentCharacter); 101 int length = offset - m_currentCharacter; 102 if (length <= 0) 103 return; 104 105 unsigned baseCharacter = m_currentCharacter; 106 107 // We break up itemization of the string by fontData and (if needed) the use of small caps. 108 109 // FIXME: It's inconsistent that we use logical order when itemizing, since this 110 // does not match normal RTL. 111 112 // FIXME: This function should decode surrogate pairs. Currently it makes little difference that 113 // it does not because the font cache on Windows does not support non-BMP characters. 114 Vector<UChar, 256> smallCapsBuffer; 115 if (m_font.isSmallCaps()) 116 smallCapsBuffer.resize(length); 117 118 unsigned indexOfFontTransition = m_run.rtl() ? length - 1 : 0; 119 const UChar* curr = m_run.rtl() ? cp + length - 1 : cp; 120 const UChar* end = m_run.rtl() ? cp - 1 : cp + length; 121 122 const SimpleFontData* fontData; 123 const SimpleFontData* nextFontData = m_font.glyphDataForCharacter(*curr, false).fontData; 124 125 UChar newC = 0; 126 127 bool isSmallCaps; 128 bool nextIsSmallCaps = m_font.isSmallCaps() && !(U_GET_GC_MASK(*curr) & U_GC_M_MASK) && (newC = u_toupper(*curr)) != *curr; 129 130 if (nextIsSmallCaps) 131 smallCapsBuffer[curr - cp] = newC; 132 133 while (true) { 134 curr = m_run.rtl() ? curr - 1 : curr + 1; 135 if (curr == end) 136 break; 137 138 fontData = nextFontData; 139 isSmallCaps = nextIsSmallCaps; 140 int index = curr - cp; 141 UChar c = *curr; 142 143 bool forceSmallCaps = isSmallCaps && (U_GET_GC_MASK(c) & U_GC_M_MASK); 144 nextFontData = m_font.glyphDataForCharacter(*curr, false, forceSmallCaps).fontData; 145 if (m_font.isSmallCaps()) { 146 nextIsSmallCaps = forceSmallCaps || (newC = u_toupper(c)) != c; 147 if (nextIsSmallCaps) 148 smallCapsBuffer[index] = forceSmallCaps ? c : newC; 149 } 150 151 if (m_fallbackFonts && nextFontData != fontData && fontData != m_font.primaryFont()) 152 m_fallbackFonts->add(fontData); 153 154 if (nextFontData != fontData || nextIsSmallCaps != isSmallCaps) { 155 int itemStart = m_run.rtl() ? index + 1 : indexOfFontTransition; 156 int itemLength = m_run.rtl() ? indexOfFontTransition - index : index - indexOfFontTransition; 157 m_currentCharacter = baseCharacter + itemStart; 158 itemizeShapeAndPlace((isSmallCaps ? smallCapsBuffer.data() : cp) + itemStart, itemLength, fontData, glyphBuffer); 159 indexOfFontTransition = index; 160 } 161 } 162 163 int itemLength = m_run.rtl() ? indexOfFontTransition + 1 : length - indexOfFontTransition; 164 if (itemLength) { 165 if (m_fallbackFonts && nextFontData != m_font.primaryFont()) 166 m_fallbackFonts->add(nextFontData); 167 168 int itemStart = m_run.rtl() ? 0 : indexOfFontTransition; 169 m_currentCharacter = baseCharacter + itemStart; 170 itemizeShapeAndPlace((nextIsSmallCaps ? smallCapsBuffer.data() : cp) + itemStart, itemLength, nextFontData, glyphBuffer); 171 } 172 173 m_currentCharacter = baseCharacter + length; 174 } 175 176 void UniscribeController::itemizeShapeAndPlace(const UChar* cp, unsigned length, const SimpleFontData* fontData, GlyphBuffer* glyphBuffer) 177 { 178 // ScriptItemize (in Windows XP versions prior to SP2) can overflow by 1. This is why there is an extra empty item 179 // hanging out at the end of the array 180 m_items.resize(6); 181 int numItems = 0; 182 while (ScriptItemize(cp, length, m_items.size() - 1, &m_control, &m_state, m_items.data(), &numItems) == E_OUTOFMEMORY) { 183 m_items.resize(m_items.size() * 2); 184 resetControlAndState(); 185 } 186 m_items.resize(numItems + 1); 187 188 if (m_run.rtl()) { 189 for (int i = m_items.size() - 2; i >= 0; i--) { 190 if (!shapeAndPlaceItem(cp, i, fontData, glyphBuffer)) 191 return; 192 } 193 } else { 194 for (unsigned i = 0; i < m_items.size() - 1; i++) { 195 if (!shapeAndPlaceItem(cp, i, fontData, glyphBuffer)) 196 return; 197 } 198 } 199 } 200 201 void UniscribeController::resetControlAndState() 202 { 203 memset(&m_control, 0, sizeof(SCRIPT_CONTROL)); 204 memset(&m_state, 0, sizeof(SCRIPT_STATE)); 205 206 // Set up the correct direction for the run. 207 m_state.uBidiLevel = m_run.rtl(); 208 209 // Lock the correct directional override. 210 m_state.fOverrideDirection = m_run.directionalOverride(); 211 } 212 213 bool UniscribeController::shapeAndPlaceItem(const UChar* cp, unsigned i, const SimpleFontData* fontData, GlyphBuffer* glyphBuffer) 214 { 215 // Determine the string for this item. 216 const UChar* str = cp + m_items[i].iCharPos; 217 int len = m_items[i+1].iCharPos - m_items[i].iCharPos; 218 SCRIPT_ITEM item = m_items[i]; 219 220 // Set up buffers to hold the results of shaping the item. 221 Vector<WORD> glyphs; 222 Vector<WORD> clusters; 223 Vector<SCRIPT_VISATTR> visualAttributes; 224 clusters.resize(len); 225 226 // Shape the item. 227 // The recommended size for the glyph buffer is 1.5 * the character length + 16 in the uniscribe docs. 228 // Apparently this is a good size to avoid having to make repeated calls to ScriptShape. 229 glyphs.resize(1.5 * len + 16); 230 visualAttributes.resize(glyphs.size()); 231 232 if (!shape(str, len, item, fontData, glyphs, clusters, visualAttributes)) 233 return true; 234 235 // We now have a collection of glyphs. 236 Vector<GOFFSET> offsets; 237 Vector<int> advances; 238 offsets.resize(glyphs.size()); 239 advances.resize(glyphs.size()); 240 int glyphCount = 0; 241 HRESULT placeResult = ScriptPlace(0, fontData->scriptCache(), glyphs.data(), glyphs.size(), visualAttributes.data(), 242 &item.a, advances.data(), offsets.data(), 0); 243 if (placeResult == E_PENDING) { 244 // The script cache isn't primed with enough info yet. We need to select our HFONT into 245 // a DC and pass the DC in to ScriptPlace. 246 HDC hdc = GetDC(0); 247 HFONT hfont = fontData->platformData().hfont(); 248 HFONT oldFont = (HFONT)SelectObject(hdc, hfont); 249 placeResult = ScriptPlace(hdc, fontData->scriptCache(), glyphs.data(), glyphs.size(), visualAttributes.data(), 250 &item.a, advances.data(), offsets.data(), 0); 251 SelectObject(hdc, oldFont); 252 ReleaseDC(0, hdc); 253 } 254 255 if (FAILED(placeResult) || glyphs.isEmpty()) 256 return true; 257 258 // Convert all chars that should be treated as spaces to use the space glyph. 259 // We also create a map that allows us to quickly go from space glyphs or rounding 260 // hack glyphs back to their corresponding characters. 261 Vector<int> spaceCharacters(glyphs.size()); 262 spaceCharacters.fill(-1); 263 Vector<int> roundingHackCharacters(glyphs.size()); 264 roundingHackCharacters.fill(-1); 265 Vector<int> roundingHackWordBoundaries(glyphs.size()); 266 roundingHackWordBoundaries.fill(-1); 267 268 const float cLogicalScale = fontData->platformData().useGDI() ? 1.0f : 32.0f; 269 unsigned logicalSpaceWidth = fontData->spaceWidth() * cLogicalScale; 270 float roundedSpaceWidth = roundf(fontData->spaceWidth()); 271 272 for (int k = 0; k < len; k++) { 273 UChar ch = *(str + k); 274 if (Font::treatAsSpace(ch)) { 275 // Substitute in the space glyph at the appropriate place in the glyphs 276 // array. 277 glyphs[clusters[k]] = fontData->spaceGlyph(); 278 advances[clusters[k]] = logicalSpaceWidth; 279 spaceCharacters[clusters[k]] = m_currentCharacter + k + item.iCharPos; 280 } 281 282 if (Font::isRoundingHackCharacter(ch)) 283 roundingHackCharacters[clusters[k]] = m_currentCharacter + k + item.iCharPos; 284 285 int boundary = k + m_currentCharacter + item.iCharPos; 286 if (boundary < m_run.length() && 287 Font::isRoundingHackCharacter(*(str + k + 1))) 288 roundingHackWordBoundaries[clusters[k]] = boundary; 289 } 290 291 // Populate our glyph buffer with this information. 292 bool hasExtraSpacing = m_font.letterSpacing() || m_font.wordSpacing() || m_padding; 293 294 float leftEdge = m_runWidthSoFar; 295 296 for (unsigned k = 0; k < glyphs.size(); k++) { 297 Glyph glyph = glyphs[k]; 298 float advance = advances[k] / cLogicalScale; 299 float offsetX = offsets[k].du / cLogicalScale; 300 float offsetY = offsets[k].dv / cLogicalScale; 301 302 // Match AppKit's rules for the integer vs. non-integer rendering modes. 303 float roundedAdvance = roundf(advance); 304 if (!m_font.isPrinterFont() && !fontData->isSystemFont()) { 305 advance = roundedAdvance; 306 offsetX = roundf(offsetX); 307 offsetY = roundf(offsetY); 308 } 309 310 advance += fontData->syntheticBoldOffset(); 311 312 // We special case spaces in two ways when applying word rounding. 313 // First, we round spaces to an adjusted width in all fonts. 314 // Second, in fixed-pitch fonts we ensure that all glyphs that 315 // match the width of the space glyph have the same width as the space glyph. 316 if (roundedAdvance == roundedSpaceWidth && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()) && 317 m_run.applyWordRounding()) 318 advance = fontData->adjustedSpaceWidth(); 319 320 if (hasExtraSpacing) { 321 // If we're a glyph with an advance, go ahead and add in letter-spacing. 322 // That way we weed out zero width lurkers. This behavior matches the fast text code path. 323 if (advance && m_font.letterSpacing()) 324 advance += m_font.letterSpacing(); 325 326 // Handle justification and word-spacing. 327 if (glyph == fontData->spaceGlyph()) { 328 // Account for padding. WebCore uses space padding to justify text. 329 // We distribute the specified padding over the available spaces in the run. 330 if (m_padding) { 331 // Use leftover padding if not evenly divisible by number of spaces. 332 if (m_padding < m_padPerSpace) { 333 advance += m_padding; 334 m_padding = 0; 335 } else { 336 advance += m_padPerSpace; 337 m_padding -= m_padPerSpace; 338 } 339 } 340 341 // Account for word-spacing. 342 int characterIndex = spaceCharacters[k]; 343 if (characterIndex > 0 && !Font::treatAsSpace(*m_run.data(characterIndex - 1)) && m_font.wordSpacing()) 344 advance += m_font.wordSpacing(); 345 } 346 } 347 348 // Deal with the float/integer impedance mismatch between CG and WebCore. "Words" (characters 349 // followed by a character defined by isRoundingHackCharacter()) are always an integer width. 350 // We adjust the width of the last character of a "word" to ensure an integer width. 351 // Force characters that are used to determine word boundaries for the rounding hack 352 // to be integer width, so the following words will start on an integer boundary. 353 int roundingHackIndex = roundingHackCharacters[k]; 354 if (m_run.applyWordRounding() && roundingHackIndex != -1) 355 advance = ceilf(advance); 356 357 // Check to see if the next character is a "rounding hack character", if so, adjust the 358 // width so that the total run width will be on an integer boundary. 359 int position = m_currentCharacter + len; 360 bool lastGlyph = (k == glyphs.size() - 1) && (m_run.rtl() ? i == 0 : i == m_items.size() - 2) && (position >= m_end); 361 if ((m_run.applyWordRounding() && roundingHackWordBoundaries[k] != -1) || 362 (m_run.applyRunRounding() && lastGlyph)) { 363 float totalWidth = m_runWidthSoFar + advance; 364 advance += ceilf(totalWidth) - totalWidth; 365 } 366 367 m_runWidthSoFar += advance; 368 369 // FIXME: We need to take the GOFFSETS for combining glyphs and store them in the glyph buffer 370 // as well, so that when the time comes to draw those glyphs, we can apply the appropriate 371 // translation. 372 if (glyphBuffer) { 373 FloatSize size(offsetX, -offsetY); 374 glyphBuffer->add(glyph, fontData, advance, &size); 375 } 376 377 // Mutate the glyph array to contain our altered advances. 378 if (m_computingOffsetPosition) 379 advances[k] = advance; 380 } 381 382 while (m_computingOffsetPosition && m_offsetX >= leftEdge && m_offsetX < m_runWidthSoFar) { 383 // The position is somewhere inside this run. 384 int trailing = 0; 385 ScriptXtoCP(m_offsetX - leftEdge, clusters.size(), glyphs.size(), clusters.data(), visualAttributes.data(), 386 advances.data(), &item.a, &m_offsetPosition, &trailing); 387 if (trailing && m_includePartialGlyphs && m_offsetPosition < len - 1) { 388 m_offsetPosition += m_currentCharacter + m_items[i].iCharPos; 389 m_offsetX += m_run.rtl() ? -trailing : trailing; 390 } else { 391 m_computingOffsetPosition = false; 392 m_offsetPosition += m_currentCharacter + m_items[i].iCharPos; 393 if (trailing && m_includePartialGlyphs) 394 m_offsetPosition++; 395 return false; 396 } 397 } 398 399 return true; 400 } 401 402 bool UniscribeController::shape(const UChar* str, int len, SCRIPT_ITEM item, const SimpleFontData* fontData, 403 Vector<WORD>& glyphs, Vector<WORD>& clusters, 404 Vector<SCRIPT_VISATTR>& visualAttributes) 405 { 406 HDC hdc = 0; 407 HFONT oldFont = 0; 408 HRESULT shapeResult = E_PENDING; 409 int glyphCount = 0; 410 do { 411 shapeResult = ScriptShape(hdc, fontData->scriptCache(), str, len, glyphs.size(), &item.a, 412 glyphs.data(), clusters.data(), visualAttributes.data(), &glyphCount); 413 if (shapeResult == E_PENDING) { 414 // The script cache isn't primed with enough info yet. We need to select our HFONT into 415 // a DC and pass the DC in to ScriptShape. 416 ASSERT(!hdc); 417 hdc = GetDC(0); 418 HFONT hfont = fontData->platformData().hfont(); 419 oldFont = (HFONT)SelectObject(hdc, hfont); 420 } else if (shapeResult == E_OUTOFMEMORY) { 421 // Need to resize our buffers. 422 glyphs.resize(glyphs.size() * 2); 423 visualAttributes.resize(glyphs.size()); 424 } 425 } while (shapeResult == E_PENDING || shapeResult == E_OUTOFMEMORY); 426 427 if (hdc) { 428 SelectObject(hdc, oldFont); 429 ReleaseDC(0, hdc); 430 } 431 432 if (FAILED(shapeResult)) 433 return false; 434 435 glyphs.shrink(glyphCount); 436 visualAttributes.shrink(glyphCount); 437 438 return true; 439 } 440 441 } 442