1/* 2 * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. 3 * Copyright (C) 2006 Alexey Proskuryakov 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#import "config.h" 31#import "SimpleFontData.h" 32 33#import "BlockExceptions.h" 34#import "Color.h" 35#import "FloatRect.h" 36#import "Font.h" 37#import "FontCache.h" 38#import "FontDescription.h" 39#import "SharedBuffer.h" 40#import "WebCoreSystemInterface.h" 41#import <AppKit/AppKit.h> 42#import <ApplicationServices/ApplicationServices.h> 43#import <float.h> 44#import <unicode/uchar.h> 45#import <wtf/Assertions.h> 46#import <wtf/StdLibExtras.h> 47#import <wtf/RetainPtr.h> 48 49@interface NSFont (WebAppKitSecretAPI) 50- (BOOL)_isFakeFixedPitch; 51@end 52 53using namespace std; 54 55namespace WebCore { 56 57const float smallCapsFontSizeMultiplier = 0.7f; 58static inline float scaleEmToUnits(float x, unsigned unitsPerEm) { return x / unitsPerEm; } 59 60static bool initFontData(SimpleFontData* fontData) 61{ 62 if (!fontData->platformData().cgFont()) 63 return false; 64 65#ifdef BUILDING_ON_TIGER 66 ATSUStyle fontStyle; 67 if (ATSUCreateStyle(&fontStyle) != noErr) 68 return false; 69 70 ATSUFontID fontId = fontData->platformData().m_atsuFontID; 71 if (!fontId) { 72 ATSUDisposeStyle(fontStyle); 73 return false; 74 } 75 76 ATSUAttributeTag tag = kATSUFontTag; 77 ByteCount size = sizeof(ATSUFontID); 78 ATSUFontID *valueArray[1] = {&fontId}; 79 OSStatus status = ATSUSetAttributes(fontStyle, 1, &tag, &size, (void* const*)valueArray); 80 if (status != noErr) { 81 ATSUDisposeStyle(fontStyle); 82 return false; 83 } 84 85 if (wkGetATSStyleGroup(fontStyle, &fontData->m_styleGroup) != noErr) { 86 ATSUDisposeStyle(fontStyle); 87 return false; 88 } 89 90 ATSUDisposeStyle(fontStyle); 91#endif 92 93 return true; 94} 95 96static NSString *webFallbackFontFamily(void) 97{ 98 DEFINE_STATIC_LOCAL(RetainPtr<NSString>, webFallbackFontFamily, ([[NSFont systemFontOfSize:16.0f] familyName])); 99 return webFallbackFontFamily.get(); 100} 101 102#if !ERROR_DISABLED 103#ifdef __LP64__ 104static NSString* pathFromFont(NSFont*) 105{ 106 // FMGetATSFontRefFromFont is not available in 64-bit. As pathFromFont is only used for debugging 107 // purposes, returning nil is acceptable. 108 return nil; 109} 110#else 111static NSString* pathFromFont(NSFont *font) 112{ 113#ifndef BUILDING_ON_TIGER 114 ATSFontRef atsFont = FMGetATSFontRefFromFont(CTFontGetPlatformFont(toCTFontRef(font), 0)); 115#else 116 ATSFontRef atsFont = FMGetATSFontRefFromFont(wkGetNSFontATSUFontId(font)); 117#endif 118 FSRef fileRef; 119 120#ifndef BUILDING_ON_TIGER 121 OSStatus status = ATSFontGetFileReference(atsFont, &fileRef); 122 if (status != noErr) 123 return nil; 124#else 125 FSSpec oFile; 126 OSStatus status = ATSFontGetFileSpecification(atsFont, &oFile); 127 if (status != noErr) 128 return nil; 129 130 status = FSpMakeFSRef(&oFile, &fileRef); 131 if (status != noErr) 132 return nil; 133#endif 134 135 UInt8 filePathBuffer[PATH_MAX]; 136 status = FSRefMakePath(&fileRef, filePathBuffer, PATH_MAX); 137 if (status == noErr) 138 return [NSString stringWithUTF8String:(const char*)filePathBuffer]; 139 140 return nil; 141} 142#endif // __LP64__ 143#endif // !ERROR_DISABLED 144 145void SimpleFontData::platformInit() 146{ 147#ifdef BUILDING_ON_TIGER 148 m_styleGroup = 0; 149#endif 150#if USE(ATSUI) 151 m_ATSUMirrors = false; 152 m_checkedShapesArabic = false; 153 m_shapesArabic = false; 154#endif 155 156 m_syntheticBoldOffset = m_platformData.m_syntheticBold ? 1.0f : 0.f; 157 158 bool failedSetup = false; 159 if (!initFontData(this)) { 160 // Ack! Something very bad happened, like a corrupt font. 161 // Try looking for an alternate 'base' font for this renderer. 162 163 // Special case hack to use "Times New Roman" in place of "Times". 164 // "Times RO" is a common font whose family name is "Times". 165 // It overrides the normal "Times" family font. 166 // It also appears to have a corrupt regular variant. 167 NSString *fallbackFontFamily; 168 if ([[m_platformData.font() familyName] isEqual:@"Times"]) 169 fallbackFontFamily = @"Times New Roman"; 170 else 171 fallbackFontFamily = webFallbackFontFamily(); 172 173 // Try setting up the alternate font. 174 // This is a last ditch effort to use a substitute font when something has gone wrong. 175#if !ERROR_DISABLED 176 RetainPtr<NSFont> initialFont = m_platformData.font(); 177#endif 178 if (m_platformData.font()) 179 m_platformData.setFont([[NSFontManager sharedFontManager] convertFont:m_platformData.font() toFamily:fallbackFontFamily]); 180 else 181 m_platformData.setFont([NSFont fontWithName:fallbackFontFamily size:m_platformData.size()]); 182#if !ERROR_DISABLED 183 NSString *filePath = pathFromFont(initialFont.get()); 184 if (!filePath) 185 filePath = @"not known"; 186#endif 187 if (!initFontData(this)) { 188 if ([fallbackFontFamily isEqual:@"Times New Roman"]) { 189 // OK, couldn't setup Times New Roman as an alternate to Times, fallback 190 // on the system font. If this fails we have no alternative left. 191 m_platformData.setFont([[NSFontManager sharedFontManager] convertFont:m_platformData.font() toFamily:webFallbackFontFamily()]); 192 if (!initFontData(this)) { 193 // We tried, Times, Times New Roman, and the system font. No joy. We have to give up. 194 LOG_ERROR("unable to initialize with font %@ at %@", initialFont.get(), filePath); 195 failedSetup = true; 196 } 197 } else { 198 // We tried the requested font and the system font. No joy. We have to give up. 199 LOG_ERROR("unable to initialize with font %@ at %@", initialFont.get(), filePath); 200 failedSetup = true; 201 } 202 } 203 204 // Report the problem. 205 LOG_ERROR("Corrupt font detected, using %@ in place of %@ located at \"%@\".", 206 [m_platformData.font() familyName], [initialFont.get() familyName], filePath); 207 } 208 209 // If all else fails, try to set up using the system font. 210 // This is probably because Times and Times New Roman are both unavailable. 211 if (failedSetup) { 212 m_platformData.setFont([NSFont systemFontOfSize:[m_platformData.font() pointSize]]); 213 LOG_ERROR("failed to set up font, using system font %s", m_platformData.font()); 214 initFontData(this); 215 } 216 217 int iAscent; 218 int iDescent; 219 int iLineGap; 220#ifdef BUILDING_ON_TIGER 221 wkGetFontMetrics(m_platformData.cgFont(), &iAscent, &iDescent, &iLineGap, &m_unitsPerEm); 222#else 223 iAscent = CGFontGetAscent(m_platformData.cgFont()); 224 iDescent = CGFontGetDescent(m_platformData.cgFont()); 225 iLineGap = CGFontGetLeading(m_platformData.cgFont()); 226 m_unitsPerEm = CGFontGetUnitsPerEm(m_platformData.cgFont()); 227#endif 228 229 float pointSize = m_platformData.m_size; 230 float fAscent = scaleEmToUnits(iAscent, m_unitsPerEm) * pointSize; 231 float fDescent = -scaleEmToUnits(iDescent, m_unitsPerEm) * pointSize; 232 float fLineGap = scaleEmToUnits(iLineGap, m_unitsPerEm) * pointSize; 233 234 // We need to adjust Times, Helvetica, and Courier to closely match the 235 // vertical metrics of their Microsoft counterparts that are the de facto 236 // web standard. The AppKit adjustment of 20% is too big and is 237 // incorrectly added to line spacing, so we use a 15% adjustment instead 238 // and add it to the ascent. 239 NSString *familyName = [m_platformData.font() familyName]; 240 if ([familyName isEqualToString:@"Times"] || [familyName isEqualToString:@"Helvetica"] || [familyName isEqualToString:@"Courier"]) 241 fAscent += floorf(((fAscent + fDescent) * 0.15f) + 0.5f); 242 else if ([familyName isEqualToString:@"Geeza Pro"]) { 243 // Geeza Pro has glyphs that draw slightly above the ascent or far below the descent. Adjust 244 // those vertical metrics to better match reality, so that diacritics at the bottom of one line 245 // do not overlap diacritics at the top of the next line. 246 fAscent *= 1.08f; 247 fDescent *= 2.f; 248 } 249 250 m_ascent = lroundf(fAscent); 251 m_descent = lroundf(fDescent); 252 m_lineGap = lroundf(fLineGap); 253 m_lineSpacing = m_ascent + m_descent + m_lineGap; 254 255 // Hack Hiragino line metrics to allow room for marked text underlines. 256 // <rdar://problem/5386183> 257 if (m_descent < 3 && m_lineGap >= 3 && [familyName hasPrefix:@"Hiragino"]) { 258 m_lineGap -= 3 - m_descent; 259 m_descent = 3; 260 } 261 262 // Measure the actual character "x", because AppKit synthesizes X height rather than getting it from the font. 263 // Unfortunately, NSFont will round this for us so we don't quite get the right value. 264 GlyphPage* glyphPageZero = GlyphPageTreeNode::getRootChild(this, 0)->page(); 265 NSGlyph xGlyph = glyphPageZero ? glyphPageZero->glyphDataForCharacter('x').glyph : 0; 266 if (xGlyph) { 267 NSRect xBox = [m_platformData.font() boundingRectForGlyph:xGlyph]; 268 // Use the maximum of either width or height because "x" is nearly square 269 // and web pages that foolishly use this metric for width will be laid out 270 // poorly if we return an accurate height. Classic case is Times 13 point, 271 // which has an "x" that is 7x6 pixels. 272 m_xHeight = max(NSMaxX(xBox), NSMaxY(xBox)); 273 } else 274 m_xHeight = [m_platformData.font() xHeight]; 275} 276 277void SimpleFontData::platformCharWidthInit() 278{ 279 m_avgCharWidth = 0.f; 280 281 // Calculate avgCharWidth according to http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6OS2.html 282 // We can try grabbing it out of the OS/2 table or via ATSFontGetHorizontalMetrics, but 283 // ATSFontGetHorizontalMetrics never seems to return a non-zero value and the OS/2 table 284 // contains zero for a large number of fonts. 285 GlyphPage* glyphPageZero = GlyphPageTreeNode::getRootChild(this, 0)->page(); 286 if (glyphPageZero) { 287 static int weights[] = { 64, 14, 27, 35, 100, 20, 14, 42, 63, 3, 6, 35, 20, 56, 56, 17, 4, 49, 56, 71, 31, 10, 18, 3, 18, 2, 166 }; 288 int numGlyphs = 27; 289 ASSERT(numGlyphs == sizeof(weights) / sizeof(int)); 290 // Compute the weighted sum of the space character and the lowercase letters in the Latin alphabet. 291 float sum = 0.f; 292 int totalWeight = 0; 293 for (int i = 0; i < numGlyphs; i++) { 294 Glyph glyph = glyphPageZero->glyphDataForCharacter((i < 26 ? i + 'a' : ' ')).glyph; 295 if (glyph) { 296 totalWeight += weights[i]; 297 sum += widthForGlyph(glyph) * weights[i]; 298 } 299 } 300 if (sum > 0.f && totalWeight > 0) 301 m_avgCharWidth = sum / totalWeight; 302 } 303 304 m_maxCharWidth = 0.f; 305 if (m_platformData.font()) 306 m_maxCharWidth = [m_platformData.font() maximumAdvancement].width; 307 308 // Fallback to a cross-platform estimate, which will populate these values if they are non-positive. 309 initCharWidths(); 310} 311 312void SimpleFontData::platformDestroy() 313{ 314#ifdef BUILDING_ON_TIGER 315 if (m_styleGroup) 316 wkReleaseStyleGroup(m_styleGroup); 317#endif 318#if USE(ATSUI) 319 HashMap<unsigned, ATSUStyle>::iterator end = m_ATSUStyleMap.end(); 320 for (HashMap<unsigned, ATSUStyle>::iterator it = m_ATSUStyleMap.begin(); it != end; ++it) 321 ATSUDisposeStyle(it->second); 322#endif 323} 324 325SimpleFontData* SimpleFontData::smallCapsFontData(const FontDescription& fontDescription) const 326{ 327 if (!m_smallCapsFontData) { 328 if (isCustomFont()) { 329 FontPlatformData smallCapsFontData(m_platformData); 330 smallCapsFontData.m_size = smallCapsFontData.m_size * smallCapsFontSizeMultiplier; 331 m_smallCapsFontData = new SimpleFontData(smallCapsFontData, true, false); 332 } else { 333 BEGIN_BLOCK_OBJC_EXCEPTIONS; 334 float size = [m_platformData.font() pointSize] * smallCapsFontSizeMultiplier; 335 FontPlatformData smallCapsFont([[NSFontManager sharedFontManager] convertFont:m_platformData.font() toSize:size]); 336 337 // AppKit resets the type information (screen/printer) when you convert a font to a different size. 338 // We have to fix up the font that we're handed back. 339 smallCapsFont.setFont(fontDescription.usePrinterFont() ? [smallCapsFont.font() printerFont] : [smallCapsFont.font() screenFont]); 340 341 if (smallCapsFont.font()) { 342 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 343 NSFontTraitMask fontTraits = [fontManager traitsOfFont:m_platformData.font()]; 344 345 if (m_platformData.m_syntheticBold) 346 fontTraits |= NSBoldFontMask; 347 if (m_platformData.m_syntheticOblique) 348 fontTraits |= NSItalicFontMask; 349 350 NSFontTraitMask smallCapsFontTraits = [fontManager traitsOfFont:smallCapsFont.font()]; 351 smallCapsFont.m_syntheticBold = (fontTraits & NSBoldFontMask) && !(smallCapsFontTraits & NSBoldFontMask); 352 smallCapsFont.m_syntheticOblique = (fontTraits & NSItalicFontMask) && !(smallCapsFontTraits & NSItalicFontMask); 353 354 m_smallCapsFontData = fontCache()->getCachedFontData(&smallCapsFont); 355 } 356 END_BLOCK_OBJC_EXCEPTIONS; 357 } 358 } 359 return m_smallCapsFontData; 360} 361 362bool SimpleFontData::containsCharacters(const UChar* characters, int length) const 363{ 364 NSString *string = [[NSString alloc] initWithCharactersNoCopy:const_cast<unichar*>(characters) length:length freeWhenDone:NO]; 365 NSCharacterSet *set = [[m_platformData.font() coveredCharacterSet] invertedSet]; 366 bool result = set && [string rangeOfCharacterFromSet:set].location == NSNotFound; 367 [string release]; 368 return result; 369} 370 371void SimpleFontData::determinePitch() 372{ 373 NSFont* f = m_platformData.font(); 374 // Special case Osaka-Mono. 375 // According to <rdar://problem/3999467>, we should treat Osaka-Mono as fixed pitch. 376 // Note that the AppKit does not report Osaka-Mono as fixed pitch. 377 378 // Special case MS-PGothic. 379 // According to <rdar://problem/4032938>, we should not treat MS-PGothic as fixed pitch. 380 // Note that AppKit does report MS-PGothic as fixed pitch. 381 382 // Special case MonotypeCorsiva 383 // According to <rdar://problem/5454704>, we should not treat MonotypeCorsiva as fixed pitch. 384 // Note that AppKit does report MonotypeCorsiva as fixed pitch. 385 386 NSString *name = [f fontName]; 387 m_treatAsFixedPitch = ([f isFixedPitch] || [f _isFakeFixedPitch] || 388 [name caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame) && 389 [name caseInsensitiveCompare:@"MS-PGothic"] != NSOrderedSame && 390 [name caseInsensitiveCompare:@"MonotypeCorsiva"] != NSOrderedSame; 391} 392 393float SimpleFontData::platformWidthForGlyph(Glyph glyph) const 394{ 395 NSFont* font = m_platformData.font(); 396 float pointSize = m_platformData.m_size; 397 CGAffineTransform m = CGAffineTransformMakeScale(pointSize, pointSize); 398 CGSize advance; 399 if (!wkGetGlyphTransformedAdvances(m_platformData.cgFont(), font, &m, &glyph, &advance)) { 400 LOG_ERROR("Unable to cache glyph widths for %@ %f", [font displayName], pointSize); 401 advance.width = 0; 402 } 403 return advance.width + m_syntheticBoldOffset; 404} 405 406#if USE(ATSUI) 407void SimpleFontData::checkShapesArabic() const 408{ 409 ASSERT(!m_checkedShapesArabic); 410 411 m_checkedShapesArabic = true; 412 413 ATSUFontID fontID = m_platformData.m_atsuFontID; 414 if (!fontID) { 415 LOG_ERROR("unable to get ATSUFontID for %@", m_platformData.font()); 416 return; 417 } 418 419 // This function is called only on fonts that contain Arabic glyphs. Our 420 // heuristic is that if such a font has a glyph metamorphosis table, then 421 // it includes shaping information for Arabic. 422 FourCharCode tables[] = { 'morx', 'mort' }; 423 for (unsigned i = 0; i < sizeof(tables) / sizeof(tables[0]); ++i) { 424 ByteCount tableSize; 425 OSStatus status = ATSFontGetTable(fontID, tables[i], 0, 0, 0, &tableSize); 426 if (status == noErr) { 427 m_shapesArabic = true; 428 return; 429 } 430 431 if (status != kATSInvalidFontTableAccess) 432 LOG_ERROR("ATSFontGetTable failed (%d)", status); 433 } 434} 435#endif 436 437#if USE(CORE_TEXT) 438CTFontRef SimpleFontData::getCTFont() const 439{ 440 if (getNSFont()) 441 return toCTFontRef(getNSFont()); 442 if (!m_CTFont) 443 m_CTFont.adoptCF(CTFontCreateWithGraphicsFont(m_platformData.cgFont(), m_platformData.size(), NULL, NULL)); 444 return m_CTFont.get(); 445} 446 447CFDictionaryRef SimpleFontData::getCFStringAttributes(TypesettingFeatures typesettingFeatures) const 448{ 449 unsigned key = typesettingFeatures + 1; 450 pair<HashMap<unsigned, RetainPtr<CFDictionaryRef> >::iterator, bool> addResult = m_CFStringAttributes.add(key, RetainPtr<CFDictionaryRef>()); 451 RetainPtr<CFDictionaryRef>& attributesDictionary = addResult.first->second; 452 if (!addResult.second) 453 return attributesDictionary.get(); 454 455 bool allowLigatures = platformData().allowsLigatures() || (typesettingFeatures & Ligatures); 456 457 static const int ligaturesNotAllowedValue = 0; 458 static CFNumberRef ligaturesNotAllowed = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &ligaturesNotAllowedValue); 459 460 static const int ligaturesAllowedValue = 1; 461 static CFNumberRef ligaturesAllowed = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &ligaturesAllowedValue); 462 463 if (!(typesettingFeatures & Kerning)) { 464 static const float kerningAdjustmentValue = 0; 465 static CFNumberRef kerningAdjustment = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &kerningAdjustmentValue); 466 static const void* keysWithKerningDisabled[] = { kCTFontAttributeName, kCTKernAttributeName, kCTLigatureAttributeName }; 467 const void* valuesWithKerningDisabled[] = { getCTFont(), kerningAdjustment, allowLigatures 468 ? ligaturesAllowed : ligaturesNotAllowed }; 469 attributesDictionary.adoptCF(CFDictionaryCreate(NULL, keysWithKerningDisabled, valuesWithKerningDisabled, 470 sizeof(keysWithKerningDisabled) / sizeof(*keysWithKerningDisabled), 471 &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); 472 } else { 473 // By omitting the kCTKernAttributeName attribute, we get Core Text's standard kerning. 474 static const void* keysWithKerningEnabled[] = { kCTFontAttributeName, kCTLigatureAttributeName }; 475 const void* valuesWithKerningEnabled[] = { getCTFont(), allowLigatures ? ligaturesAllowed : ligaturesNotAllowed }; 476 attributesDictionary.adoptCF(CFDictionaryCreate(NULL, keysWithKerningEnabled, valuesWithKerningEnabled, 477 sizeof(keysWithKerningEnabled) / sizeof(*keysWithKerningEnabled), 478 &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); 479 } 480 481 return attributesDictionary.get(); 482} 483 484#endif 485 486} // namespace WebCore 487