1/* 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 4 * (C) 2000 Dirk Mueller (mueller@kde.org) 5 * Copyright (C) 2003, 2006 Apple Computer, Inc. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 */ 22 23#import "config.h" 24#import "Font.h" 25 26#if USE(ATSUI) 27 28#import "CharacterNames.h" 29#import "GraphicsContext.h" 30#import "Logging.h" 31#import "ShapeArabic.h" 32#import "SimpleFontData.h" 33#import <AppKit/NSGraphicsContext.h> 34#import <wtf/OwnArrayPtr.h> 35 36#define SYNTHETIC_OBLIQUE_ANGLE 14 37 38#ifdef __LP64__ 39#define URefCon void* 40#else 41#define URefCon UInt32 42#endif 43 44using namespace std; 45 46namespace WebCore { 47 48struct ATSULayoutParameters : Noncopyable 49{ 50 ATSULayoutParameters(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts = 0) 51 : m_run(run) 52 , m_font(0) 53 , m_hasSyntheticBold(false) 54 , m_syntheticBoldPass(false) 55 , m_padPerSpace(0) 56 , m_fallbackFonts(fallbackFonts) 57 { 58 } 59 60 ~ATSULayoutParameters() 61 { 62 ATSUDisposeTextLayout(m_layout); 63 } 64 65 void initialize(const Font*, const GraphicsContext* = 0); 66 67 const TextRun& m_run; 68 69 const Font* m_font; 70 71 ATSUTextLayout m_layout; 72 OwnArrayPtr<const SimpleFontData*> m_fonts; 73 74 OwnArrayPtr<UChar> m_charBuffer; 75 bool m_hasSyntheticBold; 76 bool m_syntheticBoldPass; 77 float m_padPerSpace; 78 HashSet<const SimpleFontData*>* m_fallbackFonts; 79}; 80 81static TextRun copyRunForDirectionalOverrideIfNecessary(const TextRun& run, OwnArrayPtr<UChar>& charactersWithOverride) 82{ 83 if (!run.directionalOverride()) 84 return run; 85 86 charactersWithOverride.set(new UChar[run.length() + 2]); 87 charactersWithOverride[0] = run.rtl() ? rightToLeftOverride : leftToRightOverride; 88 memcpy(&charactersWithOverride[1], run.data(0), sizeof(UChar) * run.length()); 89 charactersWithOverride[run.length() + 1] = popDirectionalFormatting; 90 91 TextRun result = run; 92 result.setText(charactersWithOverride.get(), run.length() + 2); 93 return result; 94} 95 96static bool fontHasMirroringInfo(ATSUFontID fontID) 97{ 98 ByteCount propTableSize; 99 OSStatus status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize); 100 if (status == noErr) // naively assume that if a 'prop' table exists then it contains mirroring info 101 return true; 102 else if (status != kATSInvalidFontTableAccess) // anything other than a missing table is logged as an error 103 LOG_ERROR("ATSFontGetTable failed (%d)", status); 104 105 return false; 106} 107 108static void disableLigatures(const SimpleFontData* fontData) 109{ 110 // Don't be too aggressive: if the font doesn't contain 'a', then assume that any ligatures it contains are 111 // in characters that always go through ATSUI, and therefore allow them. Geeza Pro is an example. 112 // See bugzilla 5166. 113 if (fontData->platformData().allowsLigatures()) 114 return; 115 116 ATSUFontFeatureType featureTypes[] = { kLigaturesType }; 117 ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector }; 118 OSStatus status = ATSUSetFontFeatures(fontData->m_ATSUStyle, 1, featureTypes, featureSelectors); 119 if (status != noErr) 120 LOG_ERROR("ATSUSetFontFeatures failed (%d) -- ligatures remain enabled", status); 121} 122 123static void initializeATSUStyle(const SimpleFontData* fontData) 124{ 125 if (fontData->m_ATSUStyleInitialized) 126 return; 127 128 ATSUFontID fontID = fontData->platformData().m_atsuFontID; 129 if (!fontID) { 130 LOG_ERROR("unable to get ATSUFontID for %@", fontData->platformData().font()); 131 return; 132 } 133 134 OSStatus status = ATSUCreateStyle(&fontData->m_ATSUStyle); 135 if (status != noErr) 136 // Who knows how many ATSU functions will crash when passed a NULL style... 137 LOG_ERROR("ATSUCreateStyle failed (%d)", status); 138 139 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1); 140 if (fontData->platformData().m_syntheticOblique) 141 transform = CGAffineTransformConcat(transform, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0)); 142 Fixed fontSize = FloatToFixed(fontData->platformData().m_size); 143 ByteCount styleSizes[4] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(Fract) }; 144 // Turn off automatic kerning until it is supported in the CG code path (bug 6136) 145 Fract kerningInhibitFactor = FloatToFract(1.0); 146 147 ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag }; 148 ATSUAttributeValuePtr styleValues[4] = { &fontSize, &fontID, &transform, &kerningInhibitFactor }; 149 status = ATSUSetAttributes(fontData->m_ATSUStyle, 4, styleTags, styleSizes, styleValues); 150 if (status != noErr) 151 LOG_ERROR("ATSUSetAttributes failed (%d)", status); 152 153 fontData->m_ATSUMirrors = fontHasMirroringInfo(fontID); 154 155 // Turn off ligatures such as 'fi' to match the CG code path's behavior, until bug 6135 is fixed. 156 disableLigatures(fontData); 157 158 fontData->m_ATSUStyleInitialized = true; 159} 160 161static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector, ATSULineRef iLineRef, URefCon iRefCon, void*, ATSULayoutOperationCallbackStatus* oCallbackStatus) 162{ 163 ATSULayoutParameters* params = reinterpret_cast<ATSULayoutParameters*>(iRefCon); 164 OSStatus status; 165 ItemCount count; 166 ATSLayoutRecord *layoutRecords; 167 168 if (params->m_run.applyWordRounding()) { 169 status = ATSUDirectGetLayoutDataArrayPtrFromLineRef(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, true, (void **)&layoutRecords, &count); 170 if (status != noErr) { 171 *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue; 172 return status; 173 } 174 175 Fixed lastNativePos = 0; 176 float lastAdjustedPos = 0; 177 const UChar* characters = params->m_charBuffer ? params->m_charBuffer.get() : params->m_run.characters(); 178 const SimpleFontData** renderers = params->m_fonts.get(); 179 const SimpleFontData* renderer; 180 const SimpleFontData* lastRenderer = 0; 181 ByteCount offset = layoutRecords[0].originalOffset; 182 UChar nextCh = *(UChar *)(((char *)characters)+offset); 183 bool shouldRound = false; 184 bool syntheticBoldPass = params->m_syntheticBoldPass; 185 Fixed syntheticBoldOffset = 0; 186 bool hasExtraSpacing = (params->m_font->letterSpacing() || params->m_font->wordSpacing() || params->m_run.padding()) && !params->m_run.spacingDisabled(); 187 float padding = params->m_run.padding(); 188 // In the CoreGraphics code path, the rounding hack is applied in logical order. 189 // Here it is applied in visual left-to-right order, which may be better. 190 ItemCount lastRoundingChar = 0; 191 ItemCount i; 192 for (i = 1; i < count; i++) { 193 bool isLastChar = i == count - 1; 194 renderer = renderers[offset / 2]; 195 float width; 196 if (nextCh == zeroWidthSpace || Font::treatAsZeroWidthSpace(nextCh) && !Font::treatAsSpace(nextCh)) { 197 width = 0; 198 layoutRecords[i-1].glyphID = renderer->spaceGlyph(); 199 } else { 200 width = FixedToFloat(layoutRecords[i].realPos - lastNativePos); 201 if (renderer != lastRenderer && width) { 202 lastRenderer = renderer; 203 // The CoreGraphics interpretation of NSFontAntialiasedIntegerAdvancementsRenderingMode seems 204 // to be "round each glyph's width to the nearest integer". This is not the same as ATSUI 205 // does in any of its device-metrics modes. 206 shouldRound = renderer->platformData().roundsGlyphAdvances(); 207 if (syntheticBoldPass) 208 syntheticBoldOffset = FloatToFixed(renderer->syntheticBoldOffset()); 209 if (params->m_fallbackFonts && renderer != params->m_font->primaryFont()) 210 params->m_fallbackFonts->add(renderer); 211 } 212 if (shouldRound) 213 width = roundf(width); 214 width += renderer->syntheticBoldOffset(); 215 if (renderer->pitch() == FixedPitch ? width == renderer->spaceWidth() : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace)) 216 width = renderer->adjustedSpaceWidth(); 217 } 218 lastNativePos = layoutRecords[i].realPos; 219 220 if (hasExtraSpacing) { 221 if (width && params->m_font->letterSpacing()) 222 width +=params->m_font->letterSpacing(); 223 if (Font::treatAsSpace(nextCh)) { 224 if (params->m_run.padding()) { 225 if (padding < params->m_padPerSpace) { 226 width += padding; 227 padding = 0; 228 } else { 229 width += params->m_padPerSpace; 230 padding -= params->m_padPerSpace; 231 } 232 } 233 if (offset != 0 && !Font::treatAsSpace(*((UChar *)(((char *)characters)+offset) - 1)) && params->m_font->wordSpacing()) 234 width += params->m_font->wordSpacing(); 235 } 236 } 237 238 UChar ch = nextCh; 239 offset = layoutRecords[i].originalOffset; 240 // Use space for nextCh at the end of the loop so that we get inside the rounding hack code. 241 // We won't actually round unless the other conditions are satisfied. 242 nextCh = isLastChar ? ' ' : *(UChar *)(((char *)characters)+offset); 243 244 if (Font::isRoundingHackCharacter(ch)) 245 width = ceilf(width); 246 lastAdjustedPos = lastAdjustedPos + width; 247 if (Font::isRoundingHackCharacter(nextCh) && (!isLastChar || params->m_run.applyRunRounding())){ 248 if (params->m_run.ltr()) 249 lastAdjustedPos = ceilf(lastAdjustedPos); 250 else { 251 float roundingWidth = ceilf(lastAdjustedPos) - lastAdjustedPos; 252 Fixed rw = FloatToFixed(roundingWidth); 253 ItemCount j; 254 for (j = lastRoundingChar; j < i; j++) 255 layoutRecords[j].realPos += rw; 256 lastRoundingChar = i; 257 lastAdjustedPos += roundingWidth; 258 } 259 } 260 if (syntheticBoldPass) { 261 if (syntheticBoldOffset) 262 layoutRecords[i-1].realPos += syntheticBoldOffset; 263 else 264 layoutRecords[i-1].glyphID = renderer->spaceGlyph(); 265 } 266 layoutRecords[i].realPos = FloatToFixed(lastAdjustedPos); 267 } 268 269 status = ATSUDirectReleaseLayoutDataArrayPtr(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **)&layoutRecords); 270 } 271 *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled; 272 return noErr; 273} 274 275static inline bool isArabicLamWithAlefLigature(UChar c) 276{ 277 return c >= 0xfef5 && c <= 0xfefc; 278} 279 280static void shapeArabic(const UChar* source, UChar* dest, unsigned totalLength, unsigned shapingStart) 281{ 282 while (shapingStart < totalLength) { 283 unsigned shapingEnd; 284 // We do not want to pass a Lam with Alef ligature followed by a space to the shaper, 285 // since we want to be able to identify this sequence as the result of shaping a Lam 286 // followed by an Alef and padding with a space. 287 bool foundLigatureSpace = false; 288 for (shapingEnd = shapingStart; !foundLigatureSpace && shapingEnd < totalLength - 1; ++shapingEnd) 289 foundLigatureSpace = isArabicLamWithAlefLigature(source[shapingEnd]) && source[shapingEnd + 1] == ' '; 290 shapingEnd++; 291 292 UErrorCode shapingError = U_ZERO_ERROR; 293 unsigned charsWritten = shapeArabic(source + shapingStart, shapingEnd - shapingStart, dest + shapingStart, shapingEnd - shapingStart, U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR, &shapingError); 294 295 if (U_SUCCESS(shapingError) && charsWritten == shapingEnd - shapingStart) { 296 for (unsigned j = shapingStart; j < shapingEnd - 1; ++j) { 297 if (isArabicLamWithAlefLigature(dest[j]) && dest[j + 1] == ' ') 298 dest[++j] = zeroWidthSpace; 299 } 300 if (foundLigatureSpace) { 301 dest[shapingEnd] = ' '; 302 shapingEnd++; 303 } else if (isArabicLamWithAlefLigature(dest[shapingEnd - 1])) { 304 // u_shapeArabic quirk: if the last two characters in the source string are a Lam and an Alef, 305 // the space is put at the beginning of the string, despite U_SHAPE_LENGTH_FIXED_SPACES_NEAR. 306 ASSERT(dest[shapingStart] == ' '); 307 dest[shapingStart] = zeroWidthSpace; 308 } 309 } else { 310 // Something went wrong. Abandon shaping and just copy the rest of the buffer. 311 LOG_ERROR("u_shapeArabic failed(%d)", shapingError); 312 shapingEnd = totalLength; 313 memcpy(dest + shapingStart, source + shapingStart, (shapingEnd - shapingStart) * sizeof(UChar)); 314 } 315 shapingStart = shapingEnd; 316 } 317} 318 319void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* graphicsContext) 320{ 321 m_font = font; 322 323 const SimpleFontData* fontData = font->primaryFont(); 324 m_fonts.set(new const SimpleFontData*[m_run.length()]); 325 if (font->isSmallCaps()) 326 m_charBuffer.set(new UChar[m_run.length()]); 327 328 ATSUTextLayout layout; 329 OSStatus status; 330 ATSULayoutOperationOverrideSpecifier overrideSpecifier; 331 332 initializeATSUStyle(fontData); 333 334 // FIXME: This is currently missing the following required features that the CoreGraphics code path has: 335 // - \n, \t, and nonbreaking space render as a space. 336 337 UniCharCount runLength = m_run.length(); 338 339 if (m_charBuffer) 340 memcpy(m_charBuffer.get(), m_run.characters(), runLength * sizeof(UChar)); 341 342 status = ATSUCreateTextLayoutWithTextPtr( 343 (m_charBuffer ? m_charBuffer.get() : m_run.characters()), 344 0, // offset 345 runLength, // length 346 runLength, // total length 347 1, // styleRunCount 348 &runLength, // length of style run 349 &fontData->m_ATSUStyle, 350 &layout); 351 if (status != noErr) 352 LOG_ERROR("ATSUCreateTextLayoutWithTextPtr failed(%d)", status); 353 m_layout = layout; 354 ATSUSetTextLayoutRefCon(m_layout, (URefCon)this); 355 356 // FIXME: There are certain times when this method is called, when we don't have access to a GraphicsContext 357 // measuring text runs with floatWidthForComplexText is one example. 358 // ATSUI requires that we pass a valid CGContextRef to it when specifying kATSUCGContextTag (crashes when passed 0) 359 // ATSUI disables sub-pixel rendering if kATSUCGContextTag is not specified! So we're in a bind. 360 // Sometimes [[NSGraphicsContext currentContext] graphicsPort] may return the wrong (or no!) context. Nothing we can do about it (yet). 361 CGContextRef cgContext = graphicsContext ? graphicsContext->platformContext() : (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; 362 363 ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers; 364 Boolean rtl = m_run.rtl(); 365 overrideSpecifier.operationSelector = kATSULayoutOperationPostLayoutAdjustment; 366 overrideSpecifier.overrideUPP = overrideLayoutOperation; 367 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag, kATSULayoutOperationOverrideTag }; 368 ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean), sizeof(ATSULayoutOperationOverrideSpecifier) }; 369 ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl, &overrideSpecifier }; 370 371 status = ATSUSetLayoutControls(layout, (m_run.applyWordRounding() ? 4 : 3), tags, sizes, values); 372 if (status != noErr) 373 LOG_ERROR("ATSUSetLayoutControls failed(%d)", status); 374 375 status = ATSUSetTransientFontMatching(layout, YES); 376 if (status != noErr) 377 LOG_ERROR("ATSUSetTransientFontMatching failed(%d)", status); 378 379 m_hasSyntheticBold = false; 380 ATSUFontID ATSUSubstituteFont; 381 UniCharArrayOffset substituteOffset = 0; 382 UniCharCount substituteLength; 383 UniCharArrayOffset lastOffset; 384 const SimpleFontData* substituteFontData = 0; 385 386 while (substituteOffset < runLength) { 387 // FIXME: Using ATSUMatchFontsToText() here results in several problems: the CSS font family list is not necessarily followed for the 2nd 388 // and onwards unmatched characters; segmented fonts do not work correctly; behavior does not match the simple text and Uniscribe code 389 // paths. Change this function to use Font::glyphDataForCharacter() for each character instead. 390 lastOffset = substituteOffset; 391 status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength); 392 if (status == kATSUFontsMatched || status == kATSUFontsNotMatched) { 393 const FontData* fallbackFontData = m_font->fontDataForCharacters(m_run.characters() + substituteOffset, substituteLength); 394 substituteFontData = fallbackFontData ? fallbackFontData->fontDataForCharacter(m_run[0]) : 0; 395 if (substituteFontData) { 396 initializeATSUStyle(substituteFontData); 397 if (substituteFontData->m_ATSUStyle) 398 ATSUSetRunStyle(layout, substituteFontData->m_ATSUStyle, substituteOffset, substituteLength); 399 } else 400 substituteFontData = fontData; 401 } else { 402 substituteOffset = runLength; 403 substituteLength = 0; 404 } 405 406 bool shapedArabic = false; 407 bool isSmallCap = false; 408 UniCharArrayOffset firstSmallCap = 0; 409 const SimpleFontData *r = fontData; 410 UniCharArrayOffset i; 411 for (i = lastOffset; ; i++) { 412 if (i == substituteOffset || i == substituteOffset + substituteLength) { 413 if (isSmallCap) { 414 isSmallCap = false; 415 initializeATSUStyle(r->smallCapsFontData(m_font->fontDescription())); 416 ATSUSetRunStyle(layout, r->smallCapsFontData(m_font->fontDescription())->m_ATSUStyle, firstSmallCap, i - firstSmallCap); 417 } 418 if (i == substituteOffset && substituteLength > 0) 419 r = substituteFontData; 420 else 421 break; 422 } 423 if (!shapedArabic && WTF::Unicode::isArabicChar(m_run[i]) && !r->shapesArabic()) { 424 shapedArabic = true; 425 if (!m_charBuffer) { 426 m_charBuffer.set(new UChar[runLength]); 427 memcpy(m_charBuffer.get(), m_run.characters(), i * sizeof(UChar)); 428 ATSUTextMoved(layout, m_charBuffer.get()); 429 } 430 shapeArabic(m_run.characters(), m_charBuffer.get(), runLength, i); 431 } 432 if (m_run.rtl() && !r->m_ATSUMirrors) { 433 UChar mirroredChar = u_charMirror(m_run[i]); 434 if (mirroredChar != m_run[i]) { 435 if (!m_charBuffer) { 436 m_charBuffer.set(new UChar[runLength]); 437 memcpy(m_charBuffer.get(), m_run.characters(), runLength * sizeof(UChar)); 438 ATSUTextMoved(layout, m_charBuffer.get()); 439 } 440 m_charBuffer[i] = mirroredChar; 441 } 442 } 443 if (m_font->isSmallCaps()) { 444 const SimpleFontData* smallCapsData = r->smallCapsFontData(m_font->fontDescription()); 445 UChar c = m_charBuffer[i]; 446 UChar newC; 447 if (U_GET_GC_MASK(c) & U_GC_M_MASK) 448 m_fonts[i] = isSmallCap ? smallCapsData : r; 449 else if (!u_isUUppercase(c) && (newC = u_toupper(c)) != c) { 450 m_charBuffer[i] = newC; 451 if (!isSmallCap) { 452 isSmallCap = true; 453 firstSmallCap = i; 454 } 455 m_fonts[i] = smallCapsData; 456 } else { 457 if (isSmallCap) { 458 isSmallCap = false; 459 initializeATSUStyle(smallCapsData); 460 ATSUSetRunStyle(layout, smallCapsData->m_ATSUStyle, firstSmallCap, i - firstSmallCap); 461 } 462 m_fonts[i] = r; 463 } 464 } else 465 m_fonts[i] = r; 466 if (m_fonts[i]->syntheticBoldOffset()) 467 m_hasSyntheticBold = true; 468 } 469 substituteOffset += substituteLength; 470 } 471 if (m_run.padding()) { 472 float numSpaces = 0; 473 unsigned k; 474 for (k = 0; k < runLength; k++) 475 if (Font::treatAsSpace(m_run[k])) 476 numSpaces++; 477 478 if (numSpaces == 0) 479 m_padPerSpace = 0; 480 else 481 m_padPerSpace = ceilf(m_run.padding() / numSpaces); 482 } else 483 m_padPerSpace = 0; 484} 485 486FloatRect Font::selectionRectForComplexText(const TextRun& run, const IntPoint& point, int h, int from, int to) const 487{ 488 OwnArrayPtr<UChar> charactersWithOverride; 489 TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride); 490 if (run.directionalOverride()) { 491 from++; 492 to++; 493 } 494 495 ATSULayoutParameters params(adjustedRun); 496 params.initialize(this); 497 498 ATSTrapezoid firstGlyphBounds; 499 ItemCount actualNumBounds; 500 501 OSStatus status = ATSUGetGlyphBounds(params.m_layout, 0, 0, from, to - from, kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds); 502 if (status != noErr || actualNumBounds != 1) { 503 static ATSTrapezoid zeroTrapezoid = { {0, 0}, {0, 0}, {0, 0}, {0, 0} }; 504 firstGlyphBounds = zeroTrapezoid; 505 } 506 507 float beforeWidth = MIN(FixedToFloat(firstGlyphBounds.lowerLeft.x), FixedToFloat(firstGlyphBounds.upperLeft.x)); 508 float afterWidth = MAX(FixedToFloat(firstGlyphBounds.lowerRight.x), FixedToFloat(firstGlyphBounds.upperRight.x)); 509 510 FloatRect rect(point.x() + floorf(beforeWidth), point.y(), roundf(afterWidth) - floorf(beforeWidth), h); 511 512 return rect; 513} 514 515void Font::drawComplexText(GraphicsContext* graphicsContext, const TextRun& run, const FloatPoint& point, int from, int to) const 516{ 517 OSStatus status; 518 519 int drawPortionLength = to - from; 520 OwnArrayPtr<UChar> charactersWithOverride; 521 TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride); 522 if (run.directionalOverride()) 523 from++; 524 525 ATSULayoutParameters params(adjustedRun); 526 params.initialize(this, graphicsContext); 527 528 // ATSUI can't draw beyond -32768 to +32767 so we translate the CTM and tell ATSUI to draw at (0, 0). 529 CGContextRef context = graphicsContext->platformContext(); 530 CGContextTranslateCTM(context, point.x(), point.y()); 531 532 IntSize shadowSize; 533 int shadowBlur; 534 Color shadowColor; 535 graphicsContext->getShadow(shadowSize, shadowBlur, shadowColor); 536 537 bool hasSimpleShadow = graphicsContext->textDrawingMode() == cTextFill && shadowColor.isValid() && !shadowBlur; 538 if (hasSimpleShadow) { 539 // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing. 540 graphicsContext->clearShadow(); 541 Color fillColor = graphicsContext->fillColor(); 542 Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255); 543 graphicsContext->setFillColor(shadowFillColor); 544 CGContextTranslateCTM(context, shadowSize.width(), shadowSize.height()); 545 status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0); 546 if (status == noErr && params.m_hasSyntheticBold) { 547 // Force relayout for the bold pass 548 ATSUClearLayoutCache(params.m_layout, 0); 549 params.m_syntheticBoldPass = true; 550 status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0); 551 // Force relayout for the next pass 552 ATSUClearLayoutCache(params.m_layout, 0); 553 params.m_syntheticBoldPass = false; 554 } 555 CGContextTranslateCTM(context, -shadowSize.width(), -shadowSize.height()); 556 graphicsContext->setFillColor(fillColor); 557 } 558 559 status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0); 560 if (status == noErr && params.m_hasSyntheticBold) { 561 // Force relayout for the bold pass 562 ATSUClearLayoutCache(params.m_layout, 0); 563 params.m_syntheticBoldPass = true; 564 status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0); 565 } 566 CGContextTranslateCTM(context, -point.x(), -point.y()); 567 568 if (status != noErr) 569 // Nothing to do but report the error (dev build only). 570 LOG_ERROR("ATSUDrawText() failed(%d)", status); 571 572 if (hasSimpleShadow) 573 graphicsContext->setShadow(shadowSize, shadowBlur, shadowColor); 574} 575 576float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts) const 577{ 578 if (run.length() == 0) 579 return 0; 580 581 ATSULayoutParameters params(run, fallbackFonts); 582 params.initialize(this); 583 584 OSStatus status; 585 586 ATSTrapezoid firstGlyphBounds; 587 ItemCount actualNumBounds; 588 status = ATSUGetGlyphBounds(params.m_layout, 0, 0, 0, run.length(), kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds); 589 if (status != noErr) 590 LOG_ERROR("ATSUGetGlyphBounds() failed(%d)", status); 591 if (actualNumBounds != 1) 592 LOG_ERROR("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds); 593 594 return MAX(FixedToFloat(firstGlyphBounds.upperRight.x), FixedToFloat(firstGlyphBounds.lowerRight.x)) - 595 MIN(FixedToFloat(firstGlyphBounds.upperLeft.x), FixedToFloat(firstGlyphBounds.lowerLeft.x)); 596} 597 598int Font::offsetForPositionForComplexText(const TextRun& run, int x, bool /*includePartialGlyphs*/) const 599{ 600 OwnArrayPtr<UChar> charactersWithOverride; 601 TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride); 602 603 ATSULayoutParameters params(adjustedRun); 604 params.initialize(this); 605 606 UniCharArrayOffset primaryOffset = 0; 607 608 // FIXME: No idea how to avoid including partial glyphs. 609 // Not even sure if that's the behavior this yields now. 610 Boolean isLeading; 611 UniCharArrayOffset secondaryOffset = 0; 612 OSStatus status = ATSUPositionToOffset(params.m_layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset); 613 unsigned offset; 614 if (status == noErr) { 615 offset = (unsigned)primaryOffset; 616 if (run.directionalOverride() && offset > 0) 617 offset--; 618 } else 619 // Failed to find offset! Return 0 offset. 620 offset = 0; 621 622 return offset; 623} 624 625} 626#endif // USE(ATSUI) 627