michael@0: michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include michael@0: #ifdef SK_BUILD_FOR_MAC michael@0: #import michael@0: #endif michael@0: michael@0: #ifdef SK_BUILD_FOR_IOS michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #include "SkFontHost.h" michael@0: #include "SkCGUtils.h" michael@0: #include "SkColorPriv.h" michael@0: #include "SkDescriptor.h" michael@0: #include "SkEndian.h" michael@0: #include "SkFontDescriptor.h" michael@0: #include "SkFloatingPoint.h" michael@0: #include "SkGlyph.h" michael@0: #include "SkMaskGamma.h" michael@0: #include "SkSFNTHeader.h" michael@0: #include "SkOTTable_glyf.h" michael@0: #include "SkOTTable_head.h" michael@0: #include "SkOTTable_hhea.h" michael@0: #include "SkOTTable_loca.h" michael@0: #include "SkOTUtils.h" michael@0: #include "SkPaint.h" michael@0: #include "SkPath.h" michael@0: #include "SkString.h" michael@0: #include "SkStream.h" michael@0: #include "SkThread.h" michael@0: #include "SkTypeface_mac.h" michael@0: #include "SkUtils.h" michael@0: #include "SkTypefaceCache.h" michael@0: #include "SkFontMgr.h" michael@0: #include "SkUtils.h" michael@0: michael@0: //#define HACK_COLORGLYPHS michael@0: michael@0: class SkScalerContext_Mac; michael@0: michael@0: // CTFontManagerCopyAvailableFontFamilyNames() is not always available, so we michael@0: // provide a wrapper here that will return an empty array if need be. michael@0: static CFArrayRef SkCTFontManagerCopyAvailableFontFamilyNames() { michael@0: #ifdef SK_BUILD_FOR_IOS michael@0: return CFArrayCreate(NULL, NULL, 0, NULL); michael@0: #else michael@0: return CTFontManagerCopyAvailableFontFamilyNames(); michael@0: #endif michael@0: } michael@0: michael@0: michael@0: // Being templated and taking const T* prevents calling michael@0: // CFSafeRelease(autoCFRelease) through implicit conversion. michael@0: template static void CFSafeRelease(/*CFTypeRef*/const T* cfTypeRef) { michael@0: if (cfTypeRef) { michael@0: CFRelease(cfTypeRef); michael@0: } michael@0: } michael@0: michael@0: // Being templated and taking const T* prevents calling michael@0: // CFSafeRetain(autoCFRelease) through implicit conversion. michael@0: template static void CFSafeRetain(/*CFTypeRef*/const T* cfTypeRef) { michael@0: if (cfTypeRef) { michael@0: CFRetain(cfTypeRef); michael@0: } michael@0: } michael@0: michael@0: /** Acts like a CFRef, but calls CFSafeRelease when it goes out of scope. */ michael@0: template class AutoCFRelease : private SkNoncopyable { michael@0: public: michael@0: explicit AutoCFRelease(CFRef cfRef = NULL) : fCFRef(cfRef) { } michael@0: ~AutoCFRelease() { CFSafeRelease(fCFRef); } michael@0: michael@0: void reset(CFRef that = NULL) { michael@0: CFSafeRetain(that); michael@0: CFSafeRelease(fCFRef); michael@0: fCFRef = that; michael@0: } michael@0: michael@0: AutoCFRelease& operator =(CFRef that) { michael@0: reset(that); michael@0: return *this; michael@0: } michael@0: michael@0: operator CFRef() const { return fCFRef; } michael@0: CFRef get() const { return fCFRef; } michael@0: michael@0: CFRef* operator&() { SkASSERT(fCFRef == NULL); return &fCFRef; } michael@0: private: michael@0: CFRef fCFRef; michael@0: }; michael@0: michael@0: static CFStringRef make_CFString(const char str[]) { michael@0: return CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8); michael@0: } michael@0: michael@0: template class AutoCGTable : SkNoncopyable { michael@0: public: michael@0: AutoCGTable(CGFontRef font) michael@0: //Undocumented: the tag parameter in this call is expected in machine order and not BE order. michael@0: : fCFData(CGFontCopyTableForTag(font, SkSetFourByteTag(T::TAG0, T::TAG1, T::TAG2, T::TAG3))) michael@0: , fData(fCFData ? reinterpret_cast(CFDataGetBytePtr(fCFData)) : NULL) michael@0: { } michael@0: michael@0: const T* operator->() const { return fData; } michael@0: michael@0: private: michael@0: AutoCFRelease fCFData; michael@0: public: michael@0: const T* fData; michael@0: }; michael@0: michael@0: // inline versions of these rect helpers michael@0: michael@0: static bool CGRectIsEmpty_inline(const CGRect& rect) { michael@0: return rect.size.width <= 0 || rect.size.height <= 0; michael@0: } michael@0: michael@0: static CGFloat CGRectGetMinX_inline(const CGRect& rect) { michael@0: return rect.origin.x; michael@0: } michael@0: michael@0: static CGFloat CGRectGetMaxX_inline(const CGRect& rect) { michael@0: return rect.origin.x + rect.size.width; michael@0: } michael@0: michael@0: static CGFloat CGRectGetMinY_inline(const CGRect& rect) { michael@0: return rect.origin.y; michael@0: } michael@0: michael@0: static CGFloat CGRectGetMaxY_inline(const CGRect& rect) { michael@0: return rect.origin.y + rect.size.height; michael@0: } michael@0: michael@0: static CGFloat CGRectGetWidth_inline(const CGRect& rect) { michael@0: return rect.size.width; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static void sk_memset_rect32(uint32_t* ptr, uint32_t value, michael@0: int width, int height, size_t rowBytes) { michael@0: SkASSERT(width); michael@0: SkASSERT(width * sizeof(uint32_t) <= rowBytes); michael@0: michael@0: if (width >= 32) { michael@0: while (height) { michael@0: sk_memset32(ptr, value, width); michael@0: ptr = (uint32_t*)((char*)ptr + rowBytes); michael@0: height -= 1; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: rowBytes -= width * sizeof(uint32_t); michael@0: michael@0: if (width >= 8) { michael@0: while (height) { michael@0: int w = width; michael@0: do { michael@0: *ptr++ = value; *ptr++ = value; michael@0: *ptr++ = value; *ptr++ = value; michael@0: *ptr++ = value; *ptr++ = value; michael@0: *ptr++ = value; *ptr++ = value; michael@0: w -= 8; michael@0: } while (w >= 8); michael@0: while (--w >= 0) { michael@0: *ptr++ = value; michael@0: } michael@0: ptr = (uint32_t*)((char*)ptr + rowBytes); michael@0: height -= 1; michael@0: } michael@0: } else { michael@0: while (height) { michael@0: int w = width; michael@0: do { michael@0: *ptr++ = value; michael@0: } while (--w > 0); michael@0: ptr = (uint32_t*)((char*)ptr + rowBytes); michael@0: height -= 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #include michael@0: michael@0: typedef uint32_t CGRGBPixel; michael@0: michael@0: static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) { michael@0: return pixel & 0xFF; michael@0: } michael@0: michael@0: // The calls to support subpixel are present in 10.5, but are not included in michael@0: // the 10.5 SDK. The needed calls have been extracted from the 10.6 SDK and are michael@0: // included below. To verify that CGContextSetShouldSubpixelQuantizeFonts, for michael@0: // instance, is present in the 10.5 CoreGraphics libary, use: michael@0: // cd /Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/ michael@0: // cd ApplicationServices.framework/Frameworks/CoreGraphics.framework/ michael@0: // nm CoreGraphics | grep CGContextSetShouldSubpixelQuantizeFonts michael@0: michael@0: #if !defined(MAC_OS_X_VERSION_10_6) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6) michael@0: CG_EXTERN void CGContextSetAllowsFontSmoothing(CGContextRef context, bool value); michael@0: CG_EXTERN void CGContextSetAllowsFontSubpixelPositioning(CGContextRef context, bool value); michael@0: CG_EXTERN void CGContextSetShouldSubpixelPositionFonts(CGContextRef context, bool value); michael@0: CG_EXTERN void CGContextSetAllowsFontSubpixelQuantization(CGContextRef context, bool value); michael@0: CG_EXTERN void CGContextSetShouldSubpixelQuantizeFonts(CGContextRef context, bool value); michael@0: #endif michael@0: michael@0: static const char FONT_DEFAULT_NAME[] = "Lucida Sans"; michael@0: michael@0: // See Source/WebKit/chromium/base/mac/mac_util.mm DarwinMajorVersionInternal for original source. michael@0: static int readVersion() { michael@0: struct utsname info; michael@0: if (uname(&info) != 0) { michael@0: SkDebugf("uname failed\n"); michael@0: return 0; michael@0: } michael@0: if (strcmp(info.sysname, "Darwin") != 0) { michael@0: SkDebugf("unexpected uname sysname %s\n", info.sysname); michael@0: return 0; michael@0: } michael@0: char* dot = strchr(info.release, '.'); michael@0: if (!dot) { michael@0: SkDebugf("expected dot in uname release %s\n", info.release); michael@0: return 0; michael@0: } michael@0: int version = atoi(info.release); michael@0: if (version == 0) { michael@0: SkDebugf("could not parse uname release %s\n", info.release); michael@0: } michael@0: return version; michael@0: } michael@0: michael@0: static int darwinVersion() { michael@0: static int darwin_version = readVersion(); michael@0: return darwin_version; michael@0: } michael@0: michael@0: static bool isSnowLeopard() { michael@0: return darwinVersion() == 10; michael@0: } michael@0: michael@0: static bool isLion() { michael@0: return darwinVersion() == 11; michael@0: } michael@0: michael@0: static bool isMountainLion() { michael@0: return darwinVersion() == 12; michael@0: } michael@0: michael@0: static bool isLCDFormat(unsigned format) { michael@0: return SkMask::kLCD16_Format == format || SkMask::kLCD32_Format == format; michael@0: } michael@0: michael@0: static CGFloat ScalarToCG(SkScalar scalar) { michael@0: if (sizeof(CGFloat) == sizeof(float)) { michael@0: return SkScalarToFloat(scalar); michael@0: } else { michael@0: SkASSERT(sizeof(CGFloat) == sizeof(double)); michael@0: return (CGFloat) SkScalarToDouble(scalar); michael@0: } michael@0: } michael@0: michael@0: static SkScalar CGToScalar(CGFloat cgFloat) { michael@0: if (sizeof(CGFloat) == sizeof(float)) { michael@0: return cgFloat; michael@0: } else { michael@0: SkASSERT(sizeof(CGFloat) == sizeof(double)); michael@0: return SkDoubleToScalar(cgFloat); michael@0: } michael@0: } michael@0: michael@0: static CGAffineTransform MatrixToCGAffineTransform(const SkMatrix& matrix, michael@0: SkScalar sx = SK_Scalar1, michael@0: SkScalar sy = SK_Scalar1) { michael@0: return CGAffineTransformMake( ScalarToCG(matrix[SkMatrix::kMScaleX] * sx), michael@0: -ScalarToCG(matrix[SkMatrix::kMSkewY] * sy), michael@0: -ScalarToCG(matrix[SkMatrix::kMSkewX] * sx), michael@0: ScalarToCG(matrix[SkMatrix::kMScaleY] * sy), michael@0: ScalarToCG(matrix[SkMatrix::kMTransX] * sx), michael@0: ScalarToCG(matrix[SkMatrix::kMTransY] * sy)); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #define BITMAP_INFO_RGB (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host) michael@0: #define BITMAP_INFO_GRAY (kCGImageAlphaNone) michael@0: michael@0: /** michael@0: * There does not appear to be a publicly accessable API for determining if lcd michael@0: * font smoothing will be applied if we request it. The main issue is that if michael@0: * smoothing is applied a gamma of 2.0 will be used, if not a gamma of 1.0. michael@0: */ michael@0: static bool supports_LCD() { michael@0: static int gSupportsLCD = -1; michael@0: if (gSupportsLCD >= 0) { michael@0: return (bool) gSupportsLCD; michael@0: } michael@0: uint32_t rgb = 0; michael@0: AutoCFRelease colorspace(CGColorSpaceCreateDeviceRGB()); michael@0: AutoCFRelease cgContext(CGBitmapContextCreate(&rgb, 1, 1, 8, 4, michael@0: colorspace, BITMAP_INFO_RGB)); michael@0: CGContextSelectFont(cgContext, "Helvetica", 16, kCGEncodingMacRoman); michael@0: CGContextSetShouldSmoothFonts(cgContext, true); michael@0: CGContextSetShouldAntialias(cgContext, true); michael@0: CGContextSetTextDrawingMode(cgContext, kCGTextFill); michael@0: CGContextSetGrayFillColor(cgContext, 1, 1); michael@0: CGContextShowTextAtPoint(cgContext, -1, 0, "|", 1); michael@0: uint32_t r = (rgb >> 16) & 0xFF; michael@0: uint32_t g = (rgb >> 8) & 0xFF; michael@0: uint32_t b = (rgb >> 0) & 0xFF; michael@0: gSupportsLCD = (r != g || r != b); michael@0: return (bool) gSupportsLCD; michael@0: } michael@0: michael@0: class Offscreen { michael@0: public: michael@0: Offscreen(); michael@0: michael@0: CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph, michael@0: CGGlyph glyphID, size_t* rowBytesPtr, michael@0: bool generateA8FromLCD); michael@0: michael@0: private: michael@0: enum { michael@0: kSize = 32 * 32 * sizeof(CGRGBPixel) michael@0: }; michael@0: SkAutoSMalloc fImageStorage; michael@0: AutoCFRelease fRGBSpace; michael@0: michael@0: // cached state michael@0: AutoCFRelease fCG; michael@0: SkISize fSize; michael@0: bool fDoAA; michael@0: bool fDoLCD; michael@0: michael@0: static int RoundSize(int dimension) { michael@0: return SkNextPow2(dimension); michael@0: } michael@0: }; michael@0: michael@0: Offscreen::Offscreen() : fRGBSpace(NULL), fCG(NULL), michael@0: fDoAA(false), fDoLCD(false) { michael@0: fSize.set(0, 0); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static SkTypeface::Style computeStyleBits(CTFontRef font, bool* isFixedPitch) { michael@0: unsigned style = SkTypeface::kNormal; michael@0: CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font); michael@0: michael@0: if (traits & kCTFontBoldTrait) { michael@0: style |= SkTypeface::kBold; michael@0: } michael@0: if (traits & kCTFontItalicTrait) { michael@0: style |= SkTypeface::kItalic; michael@0: } michael@0: if (isFixedPitch) { michael@0: *isFixedPitch = (traits & kCTFontMonoSpaceTrait) != 0; michael@0: } michael@0: return (SkTypeface::Style)style; michael@0: } michael@0: michael@0: static SkFontID CTFontRef_to_SkFontID(CTFontRef fontRef) { michael@0: SkFontID id = 0; michael@0: // CTFontGetPlatformFont and ATSFontRef are not supported on iOS, so we have to michael@0: // bracket this to be Mac only. michael@0: #ifdef SK_BUILD_FOR_MAC michael@0: ATSFontRef ats = CTFontGetPlatformFont(fontRef, NULL); michael@0: id = (SkFontID)ats; michael@0: if (id != 0) { michael@0: id &= 0x3FFFFFFF; // make top two bits 00 michael@0: return id; michael@0: } michael@0: #endif michael@0: // CTFontGetPlatformFont returns NULL if the font is local michael@0: // (e.g., was created by a CSS3 @font-face rule). michael@0: AutoCFRelease cgFont(CTFontCopyGraphicsFont(fontRef, NULL)); michael@0: AutoCGTable headTable(cgFont); michael@0: if (headTable.fData) { michael@0: id = (SkFontID) headTable->checksumAdjustment; michael@0: id = (id & 0x3FFFFFFF) | 0x40000000; // make top two bits 01 michael@0: } michael@0: // well-formed fonts have checksums, but as a last resort, use the pointer. michael@0: if (id == 0) { michael@0: id = (SkFontID) (uintptr_t) fontRef; michael@0: id = (id & 0x3FFFFFFF) | 0x80000000; // make top two bits 10 michael@0: } michael@0: return id; michael@0: } michael@0: michael@0: static SkFontStyle stylebits2fontstyle(SkTypeface::Style styleBits) { michael@0: return SkFontStyle((styleBits & SkTypeface::kBold) michael@0: ? SkFontStyle::kBold_Weight michael@0: : SkFontStyle::kNormal_Weight, michael@0: SkFontStyle::kNormal_Width, michael@0: (styleBits & SkTypeface::kItalic) michael@0: ? SkFontStyle::kItalic_Slant michael@0: : SkFontStyle::kUpright_Slant); michael@0: } michael@0: michael@0: #define WEIGHT_THRESHOLD ((SkFontStyle::kNormal_Weight + SkFontStyle::kBold_Weight)/2) michael@0: michael@0: static SkTypeface::Style fontstyle2stylebits(const SkFontStyle& fs) { michael@0: unsigned style = 0; michael@0: if (fs.width() >= WEIGHT_THRESHOLD) { michael@0: style |= SkTypeface::kBold; michael@0: } michael@0: if (fs.isItalic()) { michael@0: style |= SkTypeface::kItalic; michael@0: } michael@0: return (SkTypeface::Style)style; michael@0: } michael@0: michael@0: class SkTypeface_Mac : public SkTypeface { michael@0: public: michael@0: SkTypeface_Mac(SkTypeface::Style style, SkFontID fontID, bool isFixedPitch, michael@0: CTFontRef fontRef, const char name[]) michael@0: : SkTypeface(style, fontID, isFixedPitch) michael@0: , fName(name) michael@0: , fFontRef(fontRef) // caller has already called CFRetain for us michael@0: , fFontStyle(stylebits2fontstyle(style)) michael@0: { michael@0: SkASSERT(fontRef); michael@0: } michael@0: michael@0: SkTypeface_Mac(const SkFontStyle& fs, SkFontID fontID, bool isFixedPitch, michael@0: CTFontRef fontRef, const char name[]) michael@0: : SkTypeface(fontstyle2stylebits(fs), fontID, isFixedPitch) michael@0: , fName(name) michael@0: , fFontRef(fontRef) // caller has already called CFRetain for us michael@0: , fFontStyle(fs) michael@0: { michael@0: SkASSERT(fontRef); michael@0: } michael@0: michael@0: SkString fName; michael@0: AutoCFRelease fFontRef; michael@0: SkFontStyle fFontStyle; michael@0: michael@0: protected: michael@0: friend class SkFontHost; // to access our protected members for deprecated methods michael@0: michael@0: virtual int onGetUPEM() const SK_OVERRIDE; michael@0: virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE; michael@0: virtual SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const SK_OVERRIDE; michael@0: virtual int onGetTableTags(SkFontTableTag tags[]) const SK_OVERRIDE; michael@0: virtual size_t onGetTableData(SkFontTableTag, size_t offset, michael@0: size_t length, void* data) const SK_OVERRIDE; michael@0: virtual SkScalerContext* onCreateScalerContext(const SkDescriptor*) const SK_OVERRIDE; michael@0: virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE; michael@0: virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE; michael@0: virtual SkAdvancedTypefaceMetrics* onGetAdvancedTypefaceMetrics( michael@0: SkAdvancedTypefaceMetrics::PerGlyphInfo, michael@0: const uint32_t*, uint32_t) const SK_OVERRIDE; michael@0: virtual int onCharsToGlyphs(const void* chars, Encoding, uint16_t glyphs[], michael@0: int glyphCount) const SK_OVERRIDE; michael@0: virtual int onCountGlyphs() const SK_OVERRIDE; michael@0: michael@0: private: michael@0: michael@0: typedef SkTypeface INHERITED; michael@0: }; michael@0: michael@0: static SkTypeface* NewFromFontRef(CTFontRef fontRef, const char name[]) { michael@0: SkASSERT(fontRef); michael@0: bool isFixedPitch; michael@0: SkTypeface::Style style = computeStyleBits(fontRef, &isFixedPitch); michael@0: SkFontID fontID = CTFontRef_to_SkFontID(fontRef); michael@0: michael@0: return new SkTypeface_Mac(style, fontID, isFixedPitch, fontRef, name); michael@0: } michael@0: michael@0: static SkTypeface* NewFromName(const char familyName[], SkTypeface::Style theStyle) { michael@0: CTFontRef ctFont = NULL; michael@0: michael@0: CTFontSymbolicTraits ctFontTraits = 0; michael@0: if (theStyle & SkTypeface::kBold) { michael@0: ctFontTraits |= kCTFontBoldTrait; michael@0: } michael@0: if (theStyle & SkTypeface::kItalic) { michael@0: ctFontTraits |= kCTFontItalicTrait; michael@0: } michael@0: michael@0: // Create the font info michael@0: AutoCFRelease cfFontName(make_CFString(familyName)); michael@0: michael@0: AutoCFRelease cfFontTraits( michael@0: CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctFontTraits)); michael@0: michael@0: AutoCFRelease cfAttributes( michael@0: CFDictionaryCreateMutable(kCFAllocatorDefault, 0, michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks)); michael@0: michael@0: AutoCFRelease cfTraits( michael@0: CFDictionaryCreateMutable(kCFAllocatorDefault, 0, michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks)); michael@0: michael@0: // Create the font michael@0: if (cfFontName != NULL && cfFontTraits != NULL && cfAttributes != NULL && cfTraits != NULL) { michael@0: CFDictionaryAddValue(cfTraits, kCTFontSymbolicTrait, cfFontTraits); michael@0: michael@0: CFDictionaryAddValue(cfAttributes, kCTFontFamilyNameAttribute, cfFontName); michael@0: CFDictionaryAddValue(cfAttributes, kCTFontTraitsAttribute, cfTraits); michael@0: michael@0: AutoCFRelease ctFontDesc( michael@0: CTFontDescriptorCreateWithAttributes(cfAttributes)); michael@0: michael@0: if (ctFontDesc != NULL) { michael@0: ctFont = CTFontCreateWithFontDescriptor(ctFontDesc, 0, NULL); michael@0: } michael@0: } michael@0: michael@0: return ctFont ? NewFromFontRef(ctFont, familyName) : NULL; michael@0: } michael@0: michael@0: static SkTypeface* GetDefaultFace() { michael@0: SK_DECLARE_STATIC_MUTEX(gMutex); michael@0: SkAutoMutexAcquire ma(gMutex); michael@0: michael@0: static SkTypeface* gDefaultFace; michael@0: michael@0: if (NULL == gDefaultFace) { michael@0: gDefaultFace = NewFromName(FONT_DEFAULT_NAME, SkTypeface::kNormal); michael@0: SkTypefaceCache::Add(gDefaultFace, SkTypeface::kNormal); michael@0: } michael@0: return gDefaultFace; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: extern CTFontRef SkTypeface_GetCTFontRef(const SkTypeface* face); michael@0: CTFontRef SkTypeface_GetCTFontRef(const SkTypeface* face) { michael@0: const SkTypeface_Mac* macface = (const SkTypeface_Mac*)face; michael@0: return macface ? macface->fFontRef.get() : NULL; michael@0: } michael@0: michael@0: /* This function is visible on the outside. It first searches the cache, and if michael@0: * not found, returns a new entry (after adding it to the cache). michael@0: */ michael@0: SkTypeface* SkCreateTypefaceFromCTFont(CTFontRef fontRef) { michael@0: SkFontID fontID = CTFontRef_to_SkFontID(fontRef); michael@0: SkTypeface* face = SkTypefaceCache::FindByID(fontID); michael@0: if (face) { michael@0: face->ref(); michael@0: } else { michael@0: face = NewFromFontRef(fontRef, NULL); michael@0: SkTypefaceCache::Add(face, face->style()); michael@0: // NewFromFontRef doesn't retain the parameter, but the typeface it michael@0: // creates does release it in its destructor, so we balance that with michael@0: // a retain call here. michael@0: CFRetain(fontRef); michael@0: } michael@0: SkASSERT(face->getRefCnt() > 1); michael@0: return face; michael@0: } michael@0: michael@0: struct NameStyleRec { michael@0: const char* fName; michael@0: SkTypeface::Style fStyle; michael@0: }; michael@0: michael@0: static bool FindByNameStyle(SkTypeface* face, SkTypeface::Style style, michael@0: void* ctx) { michael@0: const SkTypeface_Mac* mface = reinterpret_cast(face); michael@0: const NameStyleRec* rec = reinterpret_cast(ctx); michael@0: michael@0: return rec->fStyle == style && mface->fName.equals(rec->fName); michael@0: } michael@0: michael@0: static const char* map_css_names(const char* name) { michael@0: static const struct { michael@0: const char* fFrom; // name the caller specified michael@0: const char* fTo; // "canonical" name we map to michael@0: } gPairs[] = { michael@0: { "sans-serif", "Helvetica" }, michael@0: { "serif", "Times" }, michael@0: { "monospace", "Courier" } michael@0: }; michael@0: michael@0: for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) { michael@0: if (strcmp(name, gPairs[i].fFrom) == 0) { michael@0: return gPairs[i].fTo; michael@0: } michael@0: } michael@0: return name; // no change michael@0: } michael@0: michael@0: static SkTypeface* create_typeface(const SkTypeface* familyFace, michael@0: const char familyName[], michael@0: SkTypeface::Style style) { michael@0: if (familyName) { michael@0: familyName = map_css_names(familyName); michael@0: } michael@0: michael@0: // Clone an existing typeface michael@0: // TODO: only clone if style matches the familyFace's style... michael@0: if (familyName == NULL && familyFace != NULL) { michael@0: familyFace->ref(); michael@0: return const_cast(familyFace); michael@0: } michael@0: michael@0: if (!familyName || !*familyName) { michael@0: familyName = FONT_DEFAULT_NAME; michael@0: } michael@0: michael@0: NameStyleRec rec = { familyName, style }; michael@0: SkTypeface* face = SkTypefaceCache::FindByProcAndRef(FindByNameStyle, &rec); michael@0: michael@0: if (NULL == face) { michael@0: face = NewFromName(familyName, style); michael@0: if (face) { michael@0: SkTypefaceCache::Add(face, style); michael@0: } else { michael@0: face = GetDefaultFace(); michael@0: face->ref(); michael@0: } michael@0: } michael@0: return face; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: /** GlyphRect is in FUnits (em space, y up). */ michael@0: struct GlyphRect { michael@0: int16_t fMinX; michael@0: int16_t fMinY; michael@0: int16_t fMaxX; michael@0: int16_t fMaxY; michael@0: }; michael@0: michael@0: class SkScalerContext_Mac : public SkScalerContext { michael@0: public: michael@0: SkScalerContext_Mac(SkTypeface_Mac*, const SkDescriptor*); michael@0: michael@0: protected: michael@0: unsigned generateGlyphCount(void) SK_OVERRIDE; michael@0: uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE; michael@0: void generateAdvance(SkGlyph* glyph) SK_OVERRIDE; michael@0: void generateMetrics(SkGlyph* glyph) SK_OVERRIDE; michael@0: void generateImage(const SkGlyph& glyph) SK_OVERRIDE; michael@0: void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE; michael@0: void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY) SK_OVERRIDE; michael@0: michael@0: private: michael@0: static void CTPathElement(void *info, const CGPathElement *element); michael@0: michael@0: /** Returns the offset from the horizontal origin to the vertical origin in SkGlyph units. */ michael@0: void getVerticalOffset(CGGlyph glyphID, SkPoint* offset) const; michael@0: michael@0: /** Initializes and returns the value of fFBoundingBoxesGlyphOffset. michael@0: * michael@0: * For use with (and must be called before) generateBBoxes. michael@0: */ michael@0: uint16_t getFBoundingBoxesGlyphOffset(); michael@0: michael@0: /** Initializes fFBoundingBoxes and returns true on success. michael@0: * michael@0: * On Lion and Mountain Lion, CTFontGetBoundingRectsForGlyphs has a bug which causes it to michael@0: * return a bad value in bounds.origin.x for SFNT fonts whose hhea::numberOfHMetrics is michael@0: * less than its maxp::numGlyphs. When this is the case we try to read the bounds from the michael@0: * font directly. michael@0: * michael@0: * This routine initializes fFBoundingBoxes to an array of michael@0: * fGlyphCount - fFBoundingBoxesGlyphOffset GlyphRects which contain the bounds in FUnits michael@0: * (em space, y up) of glyphs with ids in the range [fFBoundingBoxesGlyphOffset, fGlyphCount). michael@0: * michael@0: * Returns true if fFBoundingBoxes is properly initialized. The table can only be properly michael@0: * initialized for a TrueType font with 'head', 'loca', and 'glyf' tables. michael@0: * michael@0: * TODO: A future optimization will compute fFBoundingBoxes once per fCTFont. michael@0: */ michael@0: bool generateBBoxes(); michael@0: michael@0: /** Converts from FUnits (em space, y up) to SkGlyph units (pixels, y down). michael@0: * michael@0: * Used on Snow Leopard to correct CTFontGetVerticalTranslationsForGlyphs. michael@0: * Used on Lion to correct CTFontGetBoundingRectsForGlyphs. michael@0: */ michael@0: SkMatrix fFUnitMatrix; michael@0: michael@0: Offscreen fOffscreen; michael@0: AutoCFRelease fCTFont; michael@0: michael@0: /** Vertical variant of fCTFont. michael@0: * michael@0: * CT vertical metrics are pre-rotated (in em space, before transform) 90deg clock-wise. michael@0: * This makes kCTFontDefaultOrientation dangerous, because the metrics from michael@0: * kCTFontHorizontalOrientation are in a different space from kCTFontVerticalOrientation. michael@0: * Use fCTVerticalFont with kCTFontVerticalOrientation to get metrics in the same space. michael@0: */ michael@0: AutoCFRelease fCTVerticalFont; michael@0: michael@0: AutoCFRelease fCGFont; michael@0: SkAutoTMalloc fFBoundingBoxes; michael@0: uint16_t fFBoundingBoxesGlyphOffset; michael@0: uint16_t fGlyphCount; michael@0: bool fGeneratedFBoundingBoxes; michael@0: const bool fDoSubPosition; michael@0: const bool fVertical; michael@0: michael@0: friend class Offscreen; michael@0: michael@0: typedef SkScalerContext INHERITED; michael@0: }; michael@0: michael@0: SkScalerContext_Mac::SkScalerContext_Mac(SkTypeface_Mac* typeface, michael@0: const SkDescriptor* desc) michael@0: : INHERITED(typeface, desc) michael@0: , fFBoundingBoxes() michael@0: , fFBoundingBoxesGlyphOffset(0) michael@0: , fGeneratedFBoundingBoxes(false) michael@0: , fDoSubPosition(SkToBool(fRec.fFlags & kSubpixelPositioning_Flag)) michael@0: , fVertical(SkToBool(fRec.fFlags & kVertical_Flag)) michael@0: michael@0: { michael@0: CTFontRef ctFont = typeface->fFontRef.get(); michael@0: CFIndex numGlyphs = CTFontGetGlyphCount(ctFont); michael@0: SkASSERT(numGlyphs >= 1 && numGlyphs <= 0xFFFF); michael@0: fGlyphCount = SkToU16(numGlyphs); michael@0: michael@0: fRec.getSingleMatrix(&fFUnitMatrix); michael@0: CGAffineTransform transform = MatrixToCGAffineTransform(fFUnitMatrix); michael@0: michael@0: AutoCFRelease ctFontDesc; michael@0: if (fVertical) { michael@0: AutoCFRelease cfAttributes(CFDictionaryCreateMutable( michael@0: kCFAllocatorDefault, 0, michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks)); michael@0: if (cfAttributes) { michael@0: CTFontOrientation ctOrientation = kCTFontVerticalOrientation; michael@0: AutoCFRelease cfVertical(CFNumberCreate( michael@0: kCFAllocatorDefault, kCFNumberSInt32Type, &ctOrientation)); michael@0: CFDictionaryAddValue(cfAttributes, kCTFontOrientationAttribute, cfVertical); michael@0: ctFontDesc = CTFontDescriptorCreateWithAttributes(cfAttributes); michael@0: } michael@0: } michael@0: // Since our matrix includes everything, we pass 1 for size. michael@0: fCTFont = CTFontCreateCopyWithAttributes(ctFont, 1, &transform, ctFontDesc); michael@0: fCGFont = CTFontCopyGraphicsFont(fCTFont, NULL); michael@0: if (fVertical) { michael@0: CGAffineTransform rotateLeft = CGAffineTransformMake(0, -1, 1, 0, 0, 0); michael@0: transform = CGAffineTransformConcat(rotateLeft, transform); michael@0: fCTVerticalFont = CTFontCreateCopyWithAttributes(ctFont, 1, &transform, NULL); michael@0: } michael@0: michael@0: SkScalar emPerFUnit = SkScalarInvert(SkIntToScalar(CGFontGetUnitsPerEm(fCGFont))); michael@0: fFUnitMatrix.preScale(emPerFUnit, -emPerFUnit); michael@0: } michael@0: michael@0: CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph, michael@0: CGGlyph glyphID, size_t* rowBytesPtr, michael@0: bool generateA8FromLCD) { michael@0: if (!fRGBSpace) { michael@0: //It doesn't appear to matter what color space is specified. michael@0: //Regular blends and antialiased text are always (s*a + d*(1-a)) michael@0: //and smoothed text is always g=2.0. michael@0: fRGBSpace = CGColorSpaceCreateDeviceRGB(); michael@0: } michael@0: michael@0: // default to kBW_Format michael@0: bool doAA = false; michael@0: bool doLCD = false; michael@0: michael@0: if (SkMask::kBW_Format != glyph.fMaskFormat) { michael@0: doLCD = true; michael@0: doAA = true; michael@0: } michael@0: michael@0: // FIXME: lcd smoothed un-hinted rasterization unsupported. michael@0: if (!generateA8FromLCD && SkMask::kA8_Format == glyph.fMaskFormat) { michael@0: doLCD = false; michael@0: doAA = true; michael@0: } michael@0: michael@0: size_t rowBytes = fSize.fWidth * sizeof(CGRGBPixel); michael@0: if (!fCG || fSize.fWidth < glyph.fWidth || fSize.fHeight < glyph.fHeight) { michael@0: if (fSize.fWidth < glyph.fWidth) { michael@0: fSize.fWidth = RoundSize(glyph.fWidth); michael@0: } michael@0: if (fSize.fHeight < glyph.fHeight) { michael@0: fSize.fHeight = RoundSize(glyph.fHeight); michael@0: } michael@0: michael@0: rowBytes = fSize.fWidth * sizeof(CGRGBPixel); michael@0: void* image = fImageStorage.reset(rowBytes * fSize.fHeight); michael@0: fCG = CGBitmapContextCreate(image, fSize.fWidth, fSize.fHeight, 8, michael@0: rowBytes, fRGBSpace, BITMAP_INFO_RGB); michael@0: michael@0: // skia handles quantization itself, so we disable this for cg to get michael@0: // full fractional data from them. michael@0: CGContextSetAllowsFontSubpixelQuantization(fCG, false); michael@0: CGContextSetShouldSubpixelQuantizeFonts(fCG, false); michael@0: michael@0: CGContextSetTextDrawingMode(fCG, kCGTextFill); michael@0: CGContextSetFont(fCG, context.fCGFont); michael@0: CGContextSetFontSize(fCG, 1 /*CTFontGetSize(context.fCTFont)*/); michael@0: CGContextSetTextMatrix(fCG, CTFontGetMatrix(context.fCTFont)); michael@0: michael@0: // Because CG always draws from the horizontal baseline, michael@0: // if there is a non-integral translation from the horizontal origin to the vertical origin, michael@0: // then CG cannot draw the glyph in the correct location without subpixel positioning. michael@0: CGContextSetAllowsFontSubpixelPositioning(fCG, context.fDoSubPosition || context.fVertical); michael@0: CGContextSetShouldSubpixelPositionFonts(fCG, context.fDoSubPosition || context.fVertical); michael@0: michael@0: // Draw white on black to create mask. michael@0: // TODO: Draw black on white and invert, CG has a special case codepath. michael@0: CGContextSetGrayFillColor(fCG, 1.0f, 1.0f); michael@0: michael@0: // force our checks below to happen michael@0: fDoAA = !doAA; michael@0: fDoLCD = !doLCD; michael@0: } michael@0: michael@0: if (fDoAA != doAA) { michael@0: CGContextSetShouldAntialias(fCG, doAA); michael@0: fDoAA = doAA; michael@0: } michael@0: if (fDoLCD != doLCD) { michael@0: CGContextSetShouldSmoothFonts(fCG, doLCD); michael@0: fDoLCD = doLCD; michael@0: } michael@0: michael@0: CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get(); michael@0: // skip rows based on the glyph's height michael@0: image += (fSize.fHeight - glyph.fHeight) * fSize.fWidth; michael@0: michael@0: // erase to black michael@0: sk_memset_rect32(image, 0, glyph.fWidth, glyph.fHeight, rowBytes); michael@0: michael@0: float subX = 0; michael@0: float subY = 0; michael@0: if (context.fDoSubPosition) { michael@0: subX = SkFixedToFloat(glyph.getSubXFixed()); michael@0: subY = SkFixedToFloat(glyph.getSubYFixed()); michael@0: } michael@0: michael@0: // CGContextShowGlyphsAtPoint always draws using the horizontal baseline origin. michael@0: if (context.fVertical) { michael@0: SkPoint offset; michael@0: context.getVerticalOffset(glyphID, &offset); michael@0: subX += offset.fX; michael@0: subY += offset.fY; michael@0: } michael@0: michael@0: CGContextShowGlyphsAtPoint(fCG, -glyph.fLeft + subX, michael@0: glyph.fTop + glyph.fHeight - subY, michael@0: &glyphID, 1); michael@0: michael@0: SkASSERT(rowBytesPtr); michael@0: *rowBytesPtr = rowBytes; michael@0: return image; michael@0: } michael@0: michael@0: void SkScalerContext_Mac::getVerticalOffset(CGGlyph glyphID, SkPoint* offset) const { michael@0: // Snow Leopard returns cgVertOffset in completely un-transformed FUnits (em space, y up). michael@0: // Lion and Leopard return cgVertOffset in CG units (pixels, y up). michael@0: CGSize cgVertOffset; michael@0: CTFontGetVerticalTranslationsForGlyphs(fCTFont, &glyphID, &cgVertOffset, 1); michael@0: michael@0: SkPoint skVertOffset = { CGToScalar(cgVertOffset.width), CGToScalar(cgVertOffset.height) }; michael@0: if (isSnowLeopard()) { michael@0: // From FUnits (em space, y up) to SkGlyph units (pixels, y down). michael@0: fFUnitMatrix.mapPoints(&skVertOffset, 1); michael@0: } else { michael@0: // From CG units (pixels, y up) to SkGlyph units (pixels, y down). michael@0: skVertOffset.fY = -skVertOffset.fY; michael@0: } michael@0: michael@0: *offset = skVertOffset; michael@0: } michael@0: michael@0: uint16_t SkScalerContext_Mac::getFBoundingBoxesGlyphOffset() { michael@0: if (fFBoundingBoxesGlyphOffset) { michael@0: return fFBoundingBoxesGlyphOffset; michael@0: } michael@0: fFBoundingBoxesGlyphOffset = fGlyphCount; // fallback for all fonts michael@0: AutoCGTable hheaTable(fCGFont); michael@0: if (hheaTable.fData) { michael@0: fFBoundingBoxesGlyphOffset = SkEndian_SwapBE16(hheaTable->numberOfHMetrics); michael@0: } michael@0: return fFBoundingBoxesGlyphOffset; michael@0: } michael@0: michael@0: bool SkScalerContext_Mac::generateBBoxes() { michael@0: if (fGeneratedFBoundingBoxes) { michael@0: return NULL != fFBoundingBoxes.get(); michael@0: } michael@0: fGeneratedFBoundingBoxes = true; michael@0: michael@0: AutoCGTable headTable(fCGFont); michael@0: if (!headTable.fData) { michael@0: return false; michael@0: } michael@0: michael@0: AutoCGTable locaTable(fCGFont); michael@0: if (!locaTable.fData) { michael@0: return false; michael@0: } michael@0: michael@0: AutoCGTable glyfTable(fCGFont); michael@0: if (!glyfTable.fData) { michael@0: return false; michael@0: } michael@0: michael@0: uint16_t entries = fGlyphCount - fFBoundingBoxesGlyphOffset; michael@0: fFBoundingBoxes.reset(entries); michael@0: michael@0: SkOTTableHead::IndexToLocFormat locaFormat = headTable->indexToLocFormat; michael@0: SkOTTableGlyph::Iterator glyphDataIter(*glyfTable.fData, *locaTable.fData, locaFormat); michael@0: glyphDataIter.advance(fFBoundingBoxesGlyphOffset); michael@0: for (uint16_t boundingBoxesIndex = 0; boundingBoxesIndex < entries; ++boundingBoxesIndex) { michael@0: const SkOTTableGlyphData* glyphData = glyphDataIter.next(); michael@0: GlyphRect& rect = fFBoundingBoxes[boundingBoxesIndex]; michael@0: rect.fMinX = SkEndian_SwapBE16(glyphData->xMin); michael@0: rect.fMinY = SkEndian_SwapBE16(glyphData->yMin); michael@0: rect.fMaxX = SkEndian_SwapBE16(glyphData->xMax); michael@0: rect.fMaxY = SkEndian_SwapBE16(glyphData->yMax); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: unsigned SkScalerContext_Mac::generateGlyphCount(void) { michael@0: return fGlyphCount; michael@0: } michael@0: michael@0: uint16_t SkScalerContext_Mac::generateCharToGlyph(SkUnichar uni) { michael@0: CGGlyph cgGlyph[2]; michael@0: UniChar theChar[2]; // UniChar is a UTF-16 16-bit code unit. michael@0: michael@0: // Get the glyph michael@0: size_t numUniChar = SkUTF16_FromUnichar(uni, theChar); michael@0: SkASSERT(sizeof(CGGlyph) <= sizeof(uint16_t)); michael@0: michael@0: // Undocumented behavior of CTFontGetGlyphsForCharacters with non-bmp code points: michael@0: // When a surrogate pair is detected, the glyph index used is the index of the high surrogate. michael@0: // It is documented that if a mapping is unavailable, the glyph will be set to 0. michael@0: CTFontGetGlyphsForCharacters(fCTFont, theChar, cgGlyph, numUniChar); michael@0: return cgGlyph[0]; michael@0: } michael@0: michael@0: void SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) { michael@0: this->generateMetrics(glyph); michael@0: } michael@0: michael@0: void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) { michael@0: const CGGlyph cgGlyph = (CGGlyph) glyph->getGlyphID(fBaseGlyphCount); michael@0: glyph->zeroMetrics(); michael@0: michael@0: // The following block produces cgAdvance in CG units (pixels, y up). michael@0: CGSize cgAdvance; michael@0: if (fVertical) { michael@0: CTFontGetAdvancesForGlyphs(fCTVerticalFont, kCTFontVerticalOrientation, michael@0: &cgGlyph, &cgAdvance, 1); michael@0: } else { michael@0: CTFontGetAdvancesForGlyphs(fCTFont, kCTFontHorizontalOrientation, michael@0: &cgGlyph, &cgAdvance, 1); michael@0: } michael@0: glyph->fAdvanceX = SkFloatToFixed_Check(cgAdvance.width); michael@0: glyph->fAdvanceY = -SkFloatToFixed_Check(cgAdvance.height); michael@0: michael@0: // The following produces skBounds in SkGlyph units (pixels, y down), michael@0: // or returns early if skBounds would be empty. michael@0: SkRect skBounds; michael@0: michael@0: // On Mountain Lion, CTFontGetBoundingRectsForGlyphs with kCTFontVerticalOrientation and michael@0: // CTFontGetVerticalTranslationsForGlyphs do not agree when using OTF CFF fonts. michael@0: // For TTF fonts these two do agree and we can use CTFontGetBoundingRectsForGlyphs to get michael@0: // the bounding box and CTFontGetVerticalTranslationsForGlyphs to then draw the glyph michael@0: // inside that bounding box. However, with OTF CFF fonts this does not work. It appears that michael@0: // CTFontGetBoundingRectsForGlyphs with kCTFontVerticalOrientation on OTF CFF fonts tries michael@0: // to center the glyph along the vertical baseline and also perform some mysterious shift michael@0: // along the baseline. CTFontGetVerticalTranslationsForGlyphs does not appear to perform michael@0: // these steps. michael@0: // michael@0: // It is not known which is correct (or if either is correct). However, we must always draw michael@0: // from the horizontal origin and must use CTFontGetVerticalTranslationsForGlyphs to draw. michael@0: // As a result, we do not call CTFontGetBoundingRectsForGlyphs for vertical glyphs. michael@0: michael@0: // On Snow Leopard, CTFontGetBoundingRectsForGlyphs ignores kCTFontVerticalOrientation and michael@0: // returns horizontal bounds. michael@0: michael@0: // On Lion and Mountain Lion, CTFontGetBoundingRectsForGlyphs has a bug which causes it to michael@0: // return a bad value in cgBounds.origin.x for SFNT fonts whose hhea::numberOfHMetrics is michael@0: // less than its maxp::numGlyphs. When this is the case we try to read the bounds from the michael@0: // font directly. michael@0: if ((isLion() || isMountainLion()) && michael@0: (cgGlyph < fGlyphCount && cgGlyph >= getFBoundingBoxesGlyphOffset() && generateBBoxes())) michael@0: { michael@0: const GlyphRect& gRect = fFBoundingBoxes[cgGlyph - fFBoundingBoxesGlyphOffset]; michael@0: if (gRect.fMinX >= gRect.fMaxX || gRect.fMinY >= gRect.fMaxY) { michael@0: return; michael@0: } michael@0: skBounds = SkRect::MakeLTRB(gRect.fMinX, gRect.fMinY, gRect.fMaxX, gRect.fMaxY); michael@0: // From FUnits (em space, y up) to SkGlyph units (pixels, y down). michael@0: fFUnitMatrix.mapRect(&skBounds); michael@0: michael@0: } else { michael@0: // CTFontGetBoundingRectsForGlyphs produces cgBounds in CG units (pixels, y up). michael@0: CGRect cgBounds; michael@0: CTFontGetBoundingRectsForGlyphs(fCTFont, kCTFontHorizontalOrientation, michael@0: &cgGlyph, &cgBounds, 1); michael@0: michael@0: // BUG? michael@0: // 0x200B (zero-advance space) seems to return a huge (garbage) bounds, when michael@0: // it should be empty. So, if we see a zero-advance, we check if it has an michael@0: // empty path or not, and if so, we jam the bounds to 0. Hopefully a zero-advance michael@0: // is rare, so we won't incur a big performance cost for this extra check. michael@0: if (0 == cgAdvance.width && 0 == cgAdvance.height) { michael@0: AutoCFRelease path(CTFontCreatePathForGlyph(fCTFont, cgGlyph, NULL)); michael@0: if (NULL == path || CGPathIsEmpty(path)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (CGRectIsEmpty_inline(cgBounds)) { michael@0: return; michael@0: } michael@0: michael@0: // Convert cgBounds to SkGlyph units (pixels, y down). michael@0: skBounds = SkRect::MakeXYWH(cgBounds.origin.x, -cgBounds.origin.y - cgBounds.size.height, michael@0: cgBounds.size.width, cgBounds.size.height); michael@0: } michael@0: michael@0: if (fVertical) { michael@0: // Due to all of the vertical bounds bugs, skBounds is always the horizontal bounds. michael@0: // Convert these horizontal bounds into vertical bounds. michael@0: SkPoint offset; michael@0: getVerticalOffset(cgGlyph, &offset); michael@0: skBounds.offset(offset); michael@0: } michael@0: michael@0: // Currently the bounds are based on being rendered at (0,0). michael@0: // The top left must not move, since that is the base from which subpixel positioning is offset. michael@0: if (fDoSubPosition) { michael@0: skBounds.fRight += SkFixedToFloat(glyph->getSubXFixed()); michael@0: skBounds.fBottom += SkFixedToFloat(glyph->getSubYFixed()); michael@0: } michael@0: michael@0: SkIRect skIBounds; michael@0: skBounds.roundOut(&skIBounds); michael@0: // Expand the bounds by 1 pixel, to give CG room for anti-aliasing. michael@0: // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset michael@0: // is not currently known, as CG dilates the outlines by some percentage. michael@0: // Note that if this context is A8 and not back-forming from LCD, there is no need to outset. michael@0: skIBounds.outset(1, 1); michael@0: glyph->fLeft = SkToS16(skIBounds.fLeft); michael@0: glyph->fTop = SkToS16(skIBounds.fTop); michael@0: glyph->fWidth = SkToU16(skIBounds.width()); michael@0: glyph->fHeight = SkToU16(skIBounds.height()); michael@0: michael@0: #ifdef HACK_COLORGLYPHS michael@0: glyph->fMaskFormat = SkMask::kARGB32_Format; michael@0: #endif michael@0: } michael@0: michael@0: #include "SkColorPriv.h" michael@0: michael@0: static void build_power_table(uint8_t table[], float ee) { michael@0: for (int i = 0; i < 256; i++) { michael@0: float x = i / 255.f; michael@0: x = sk_float_pow(x, ee); michael@0: int xx = SkScalarRoundToInt(x * 255); michael@0: table[i] = SkToU8(xx); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This will invert the gamma applied by CoreGraphics, so we can get linear michael@0: * values. michael@0: * michael@0: * CoreGraphics obscurely defaults to 2.0 as the smoothing gamma value. michael@0: * The color space used does not appear to affect this choice. michael@0: */ michael@0: static const uint8_t* getInverseGammaTableCoreGraphicSmoothing() { michael@0: static bool gInited; michael@0: static uint8_t gTableCoreGraphicsSmoothing[256]; michael@0: if (!gInited) { michael@0: build_power_table(gTableCoreGraphicsSmoothing, 2.0f); michael@0: gInited = true; michael@0: } michael@0: return gTableCoreGraphicsSmoothing; michael@0: } michael@0: michael@0: static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) { michael@0: while (count > 0) { michael@0: uint8_t mask = 0; michael@0: for (int i = 7; i >= 0; --i) { michael@0: mask |= (CGRGBPixel_getAlpha(*src++) >> 7) << i; michael@0: if (0 == --count) { michael@0: break; michael@0: } michael@0: } michael@0: *dst++ = mask; michael@0: } michael@0: } michael@0: michael@0: template michael@0: static inline uint8_t rgb_to_a8(CGRGBPixel rgb, const uint8_t* table8) { michael@0: U8CPU r = (rgb >> 16) & 0xFF; michael@0: U8CPU g = (rgb >> 8) & 0xFF; michael@0: U8CPU b = (rgb >> 0) & 0xFF; michael@0: return sk_apply_lut_if(SkComputeLuminance(r, g, b), table8); michael@0: } michael@0: template michael@0: static void rgb_to_a8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, michael@0: const SkGlyph& glyph, const uint8_t* table8) { michael@0: const int width = glyph.fWidth; michael@0: size_t dstRB = glyph.rowBytes(); michael@0: uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; michael@0: michael@0: for (int y = 0; y < glyph.fHeight; y++) { michael@0: for (int i = 0; i < width; ++i) { michael@0: dst[i] = rgb_to_a8(cgPixels[i], table8); michael@0: } michael@0: cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); michael@0: dst += dstRB; michael@0: } michael@0: } michael@0: michael@0: template michael@0: static inline uint16_t rgb_to_lcd16(CGRGBPixel rgb, const uint8_t* tableR, michael@0: const uint8_t* tableG, michael@0: const uint8_t* tableB) { michael@0: U8CPU r = sk_apply_lut_if((rgb >> 16) & 0xFF, tableR); michael@0: U8CPU g = sk_apply_lut_if((rgb >> 8) & 0xFF, tableG); michael@0: U8CPU b = sk_apply_lut_if((rgb >> 0) & 0xFF, tableB); michael@0: return SkPack888ToRGB16(r, g, b); michael@0: } michael@0: template michael@0: static void rgb_to_lcd16(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, const SkGlyph& glyph, michael@0: const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { michael@0: const int width = glyph.fWidth; michael@0: size_t dstRB = glyph.rowBytes(); michael@0: uint16_t* SK_RESTRICT dst = (uint16_t*)glyph.fImage; michael@0: michael@0: for (int y = 0; y < glyph.fHeight; y++) { michael@0: for (int i = 0; i < width; i++) { michael@0: dst[i] = rgb_to_lcd16(cgPixels[i], tableR, tableG, tableB); michael@0: } michael@0: cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); michael@0: dst = (uint16_t*)((char*)dst + dstRB); michael@0: } michael@0: } michael@0: michael@0: template michael@0: static inline uint32_t rgb_to_lcd32(CGRGBPixel rgb, const uint8_t* tableR, michael@0: const uint8_t* tableG, michael@0: const uint8_t* tableB) { michael@0: U8CPU r = sk_apply_lut_if((rgb >> 16) & 0xFF, tableR); michael@0: U8CPU g = sk_apply_lut_if((rgb >> 8) & 0xFF, tableG); michael@0: U8CPU b = sk_apply_lut_if((rgb >> 0) & 0xFF, tableB); michael@0: return SkPackARGB32(0xFF, r, g, b); michael@0: } michael@0: template michael@0: static void rgb_to_lcd32(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, const SkGlyph& glyph, michael@0: const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { michael@0: const int width = glyph.fWidth; michael@0: size_t dstRB = glyph.rowBytes(); michael@0: uint32_t* SK_RESTRICT dst = (uint32_t*)glyph.fImage; michael@0: for (int y = 0; y < glyph.fHeight; y++) { michael@0: for (int i = 0; i < width; i++) { michael@0: dst[i] = rgb_to_lcd32(cgPixels[i], tableR, tableG, tableB); michael@0: } michael@0: cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); michael@0: dst = (uint32_t*)((char*)dst + dstRB); michael@0: } michael@0: } michael@0: michael@0: #ifdef HACK_COLORGLYPHS michael@0: // hack to colorize the output for testing kARGB32_Format michael@0: static SkPMColor cgpixels_to_pmcolor(CGRGBPixel rgb, const SkGlyph& glyph, michael@0: int x, int y) { michael@0: U8CPU r = (rgb >> 16) & 0xFF; michael@0: U8CPU g = (rgb >> 8) & 0xFF; michael@0: U8CPU b = (rgb >> 0) & 0xFF; michael@0: unsigned a = SkComputeLuminance(r, g, b); michael@0: michael@0: // compute gradient from x,y michael@0: r = x * 255 / glyph.fWidth; michael@0: g = 0; michael@0: b = (glyph.fHeight - y) * 255 / glyph.fHeight; michael@0: return SkPreMultiplyARGB(a, r, g, b); // red michael@0: } michael@0: #endif michael@0: michael@0: template T* SkTAddByteOffset(T* ptr, size_t byteOffset) { michael@0: return (T*)((char*)ptr + byteOffset); michael@0: } michael@0: michael@0: void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) { michael@0: CGGlyph cgGlyph = (CGGlyph) glyph.getGlyphID(fBaseGlyphCount); michael@0: michael@0: // FIXME: lcd smoothed un-hinted rasterization unsupported. michael@0: bool generateA8FromLCD = fRec.getHinting() != SkPaint::kNo_Hinting; michael@0: michael@0: // Draw the glyph michael@0: size_t cgRowBytes; michael@0: CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, generateA8FromLCD); michael@0: if (cgPixels == NULL) { michael@0: return; michael@0: } michael@0: michael@0: //TODO: see if drawing black on white and inverting is faster (at least in michael@0: //lcd case) as core graphics appears to have special case code for drawing michael@0: //black text. michael@0: michael@0: // Fix the glyph michael@0: const bool isLCD = isLCDFormat(glyph.fMaskFormat); michael@0: if (isLCD || (glyph.fMaskFormat == SkMask::kA8_Format && supports_LCD() && generateA8FromLCD)) { michael@0: const uint8_t* table = getInverseGammaTableCoreGraphicSmoothing(); michael@0: michael@0: //Note that the following cannot really be integrated into the michael@0: //pre-blend, since we may not be applying the pre-blend; when we aren't michael@0: //applying the pre-blend it means that a filter wants linear anyway. michael@0: //Other code may also be applying the pre-blend, so we'd need another michael@0: //one with this and one without. michael@0: CGRGBPixel* addr = cgPixels; michael@0: for (int y = 0; y < glyph.fHeight; ++y) { michael@0: for (int x = 0; x < glyph.fWidth; ++x) { michael@0: int r = (addr[x] >> 16) & 0xFF; michael@0: int g = (addr[x] >> 8) & 0xFF; michael@0: int b = (addr[x] >> 0) & 0xFF; michael@0: addr[x] = (table[r] << 16) | (table[g] << 8) | table[b]; michael@0: } michael@0: addr = SkTAddByteOffset(addr, cgRowBytes); michael@0: } michael@0: } michael@0: michael@0: // Convert glyph to mask michael@0: switch (glyph.fMaskFormat) { michael@0: case SkMask::kLCD32_Format: { michael@0: if (fPreBlend.isApplicable()) { michael@0: rgb_to_lcd32(cgPixels, cgRowBytes, glyph, michael@0: fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); michael@0: } else { michael@0: rgb_to_lcd32(cgPixels, cgRowBytes, glyph, michael@0: fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); michael@0: } michael@0: } break; michael@0: case SkMask::kLCD16_Format: { michael@0: if (fPreBlend.isApplicable()) { michael@0: rgb_to_lcd16(cgPixels, cgRowBytes, glyph, michael@0: fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); michael@0: } else { michael@0: rgb_to_lcd16(cgPixels, cgRowBytes, glyph, michael@0: fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); michael@0: } michael@0: } break; michael@0: case SkMask::kA8_Format: { michael@0: if (fPreBlend.isApplicable()) { michael@0: rgb_to_a8(cgPixels, cgRowBytes, glyph, fPreBlend.fG); michael@0: } else { michael@0: rgb_to_a8(cgPixels, cgRowBytes, glyph, fPreBlend.fG); michael@0: } michael@0: } break; michael@0: case SkMask::kBW_Format: { michael@0: const int width = glyph.fWidth; michael@0: size_t dstRB = glyph.rowBytes(); michael@0: uint8_t* dst = (uint8_t*)glyph.fImage; michael@0: for (int y = 0; y < glyph.fHeight; y++) { michael@0: cgpixels_to_bits(dst, cgPixels, width); michael@0: cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); michael@0: dst += dstRB; michael@0: } michael@0: } break; michael@0: #ifdef HACK_COLORGLYPHS michael@0: case SkMask::kARGB32_Format: { michael@0: const int width = glyph.fWidth; michael@0: size_t dstRB = glyph.rowBytes(); michael@0: SkPMColor* dst = (SkPMColor*)glyph.fImage; michael@0: for (int y = 0; y < glyph.fHeight; y++) { michael@0: for (int x = 0; x < width; ++x) { michael@0: dst[x] = cgpixels_to_pmcolor(cgPixels[x], glyph, x, y); michael@0: } michael@0: cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); michael@0: dst = (SkPMColor*)((char*)dst + dstRB); michael@0: } michael@0: } break; michael@0: #endif michael@0: default: michael@0: SkDEBUGFAIL("unexpected mask format"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Our subpixel resolution is only 2 bits in each direction, so a scale of 4 michael@0: * seems sufficient, and possibly even correct, to allow the hinted outline michael@0: * to be subpixel positioned. michael@0: */ michael@0: #define kScaleForSubPixelPositionHinting (4.0f) michael@0: michael@0: void SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) { michael@0: CTFontRef font = fCTFont; michael@0: SkScalar scaleX = SK_Scalar1; michael@0: SkScalar scaleY = SK_Scalar1; michael@0: michael@0: /* michael@0: * For subpixel positioning, we want to return an unhinted outline, so it michael@0: * can be positioned nicely at fractional offsets. However, we special-case michael@0: * if the baseline of the (horizontal) text is axis-aligned. In those cases michael@0: * we want to retain hinting in the direction orthogonal to the baseline. michael@0: * e.g. for horizontal baseline, we want to retain hinting in Y. michael@0: * The way we remove hinting is to scale the font by some value (4) in that michael@0: * direction, ask for the path, and then scale the path back down. michael@0: */ michael@0: if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { michael@0: SkMatrix m; michael@0: fRec.getSingleMatrix(&m); michael@0: michael@0: // start out by assuming that we want no hining in X and Y michael@0: scaleX = scaleY = kScaleForSubPixelPositionHinting; michael@0: // now see if we need to restore hinting for axis-aligned baselines michael@0: switch (SkComputeAxisAlignmentForHText(m)) { michael@0: case kX_SkAxisAlignment: michael@0: scaleY = SK_Scalar1; // want hinting in the Y direction michael@0: break; michael@0: case kY_SkAxisAlignment: michael@0: scaleX = SK_Scalar1; // want hinting in the X direction michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: CGAffineTransform xform = MatrixToCGAffineTransform(m, scaleX, scaleY); michael@0: // need to release font when we're done michael@0: font = CTFontCreateCopyWithAttributes(fCTFont, 1, &xform, NULL); michael@0: } michael@0: michael@0: CGGlyph cgGlyph = (CGGlyph)glyph.getGlyphID(fBaseGlyphCount); michael@0: AutoCFRelease cgPath(CTFontCreatePathForGlyph(font, cgGlyph, NULL)); michael@0: michael@0: path->reset(); michael@0: if (cgPath != NULL) { michael@0: CGPathApply(cgPath, path, SkScalerContext_Mac::CTPathElement); michael@0: } michael@0: michael@0: if (fDoSubPosition) { michael@0: SkMatrix m; michael@0: m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY)); michael@0: path->transform(m); michael@0: // balance the call to CTFontCreateCopyWithAttributes michael@0: CFSafeRelease(font); michael@0: } michael@0: if (fVertical) { michael@0: SkPoint offset; michael@0: getVerticalOffset(cgGlyph, &offset); michael@0: path->offset(offset.fX, offset.fY); michael@0: } michael@0: } michael@0: michael@0: void SkScalerContext_Mac::generateFontMetrics(SkPaint::FontMetrics* mx, michael@0: SkPaint::FontMetrics* my) { michael@0: CGRect theBounds = CTFontGetBoundingBox(fCTFont); michael@0: michael@0: SkPaint::FontMetrics theMetrics; michael@0: theMetrics.fTop = CGToScalar(-CGRectGetMaxY_inline(theBounds)); michael@0: theMetrics.fAscent = CGToScalar(-CTFontGetAscent(fCTFont)); michael@0: theMetrics.fDescent = CGToScalar( CTFontGetDescent(fCTFont)); michael@0: theMetrics.fBottom = CGToScalar(-CGRectGetMinY_inline(theBounds)); michael@0: theMetrics.fLeading = CGToScalar( CTFontGetLeading(fCTFont)); michael@0: theMetrics.fAvgCharWidth = CGToScalar( CGRectGetWidth_inline(theBounds)); michael@0: theMetrics.fXMin = CGToScalar( CGRectGetMinX_inline(theBounds)); michael@0: theMetrics.fXMax = CGToScalar( CGRectGetMaxX_inline(theBounds)); michael@0: theMetrics.fXHeight = CGToScalar( CTFontGetXHeight(fCTFont)); michael@0: theMetrics.fUnderlineThickness = CGToScalar( CTFontGetUnderlineThickness(fCTFont)); michael@0: theMetrics.fUnderlinePosition = -CGToScalar( CTFontGetUnderlinePosition(fCTFont)); michael@0: michael@0: theMetrics.fFlags |= SkPaint::FontMetrics::kUnderlineThinknessIsValid_Flag; michael@0: theMetrics.fFlags |= SkPaint::FontMetrics::kUnderlinePositionIsValid_Flag; michael@0: michael@0: if (mx != NULL) { michael@0: *mx = theMetrics; michael@0: } michael@0: if (my != NULL) { michael@0: *my = theMetrics; michael@0: } michael@0: } michael@0: michael@0: void SkScalerContext_Mac::CTPathElement(void *info, const CGPathElement *element) { michael@0: SkPath* skPath = (SkPath*)info; michael@0: michael@0: // Process the path element michael@0: switch (element->type) { michael@0: case kCGPathElementMoveToPoint: michael@0: skPath->moveTo(element->points[0].x, -element->points[0].y); michael@0: break; michael@0: michael@0: case kCGPathElementAddLineToPoint: michael@0: skPath->lineTo(element->points[0].x, -element->points[0].y); michael@0: break; michael@0: michael@0: case kCGPathElementAddQuadCurveToPoint: michael@0: skPath->quadTo(element->points[0].x, -element->points[0].y, michael@0: element->points[1].x, -element->points[1].y); michael@0: break; michael@0: michael@0: case kCGPathElementAddCurveToPoint: michael@0: skPath->cubicTo(element->points[0].x, -element->points[0].y, michael@0: element->points[1].x, -element->points[1].y, michael@0: element->points[2].x, -element->points[2].y); michael@0: break; michael@0: michael@0: case kCGPathElementCloseSubpath: michael@0: skPath->close(); michael@0: break; michael@0: michael@0: default: michael@0: SkDEBUGFAIL("Unknown path element!"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // Returns NULL on failure michael@0: // Call must still manage its ownership of provider michael@0: static SkTypeface* create_from_dataProvider(CGDataProviderRef provider) { michael@0: AutoCFRelease cg(CGFontCreateWithDataProvider(provider)); michael@0: if (NULL == cg) { michael@0: return NULL; michael@0: } michael@0: CTFontRef ct = CTFontCreateWithGraphicsFont(cg, 0, NULL, NULL); michael@0: return cg ? SkCreateTypefaceFromCTFont(ct) : NULL; michael@0: } michael@0: michael@0: // Web fonts added to the the CTFont registry do not return their character set. michael@0: // Iterate through the font in this case. The existing caller caches the result, michael@0: // so the performance impact isn't too bad. michael@0: static void populate_glyph_to_unicode_slow(CTFontRef ctFont, CFIndex glyphCount, michael@0: SkTDArray* glyphToUnicode) { michael@0: glyphToUnicode->setCount(SkToInt(glyphCount)); michael@0: SkUnichar* out = glyphToUnicode->begin(); michael@0: sk_bzero(out, glyphCount * sizeof(SkUnichar)); michael@0: UniChar unichar = 0; michael@0: while (glyphCount > 0) { michael@0: CGGlyph glyph; michael@0: if (CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) { michael@0: out[glyph] = unichar; michael@0: --glyphCount; michael@0: } michael@0: if (++unichar == 0) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Construct Glyph to Unicode table. michael@0: // Unicode code points that require conjugate pairs in utf16 are not michael@0: // supported. michael@0: static void populate_glyph_to_unicode(CTFontRef ctFont, CFIndex glyphCount, michael@0: SkTDArray* glyphToUnicode) { michael@0: AutoCFRelease charSet(CTFontCopyCharacterSet(ctFont)); michael@0: if (!charSet) { michael@0: populate_glyph_to_unicode_slow(ctFont, glyphCount, glyphToUnicode); michael@0: return; michael@0: } michael@0: michael@0: AutoCFRelease bitmap(CFCharacterSetCreateBitmapRepresentation(kCFAllocatorDefault, michael@0: charSet)); michael@0: if (!bitmap) { michael@0: return; michael@0: } michael@0: CFIndex length = CFDataGetLength(bitmap); michael@0: if (!length) { michael@0: return; michael@0: } michael@0: if (length > 8192) { michael@0: // TODO: Add support for Unicode above 0xFFFF michael@0: // Consider only the BMP portion of the Unicode character points. michael@0: // The bitmap may contain other planes, up to plane 16. michael@0: // See http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFCharacterSetRef/Reference/reference.html michael@0: length = 8192; michael@0: } michael@0: const UInt8* bits = CFDataGetBytePtr(bitmap); michael@0: glyphToUnicode->setCount(SkToInt(glyphCount)); michael@0: SkUnichar* out = glyphToUnicode->begin(); michael@0: sk_bzero(out, glyphCount * sizeof(SkUnichar)); michael@0: for (int i = 0; i < length; i++) { michael@0: int mask = bits[i]; michael@0: if (!mask) { michael@0: continue; michael@0: } michael@0: for (int j = 0; j < 8; j++) { michael@0: CGGlyph glyph; michael@0: UniChar unichar = static_cast((i << 3) + j); michael@0: if (mask & (1 << j) && CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) { michael@0: out[glyph] = unichar; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static bool getWidthAdvance(CTFontRef ctFont, int gId, int16_t* data) { michael@0: CGSize advance; michael@0: advance.width = 0; michael@0: CGGlyph glyph = gId; michael@0: CTFontGetAdvancesForGlyphs(ctFont, kCTFontHorizontalOrientation, &glyph, &advance, 1); michael@0: *data = sk_float_round2int(advance.width); michael@0: return true; michael@0: } michael@0: michael@0: // we might move this into our CGUtils... michael@0: static void CFStringToSkString(CFStringRef src, SkString* dst) { michael@0: // Reserve enough room for the worst-case string, michael@0: // plus 1 byte for the trailing null. michael@0: CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(src), michael@0: kCFStringEncodingUTF8) + 1; michael@0: dst->resize(length); michael@0: CFStringGetCString(src, dst->writable_str(), length, kCFStringEncodingUTF8); michael@0: // Resize to the actual UTF-8 length used, stripping the null character. michael@0: dst->resize(strlen(dst->c_str())); michael@0: } michael@0: michael@0: SkAdvancedTypefaceMetrics* SkTypeface_Mac::onGetAdvancedTypefaceMetrics( michael@0: SkAdvancedTypefaceMetrics::PerGlyphInfo perGlyphInfo, michael@0: const uint32_t* glyphIDs, michael@0: uint32_t glyphIDsCount) const { michael@0: michael@0: CTFontRef originalCTFont = fFontRef.get(); michael@0: AutoCFRelease ctFont(CTFontCreateCopyWithAttributes( michael@0: originalCTFont, CTFontGetUnitsPerEm(originalCTFont), NULL, NULL)); michael@0: SkAdvancedTypefaceMetrics* info = new SkAdvancedTypefaceMetrics; michael@0: michael@0: { michael@0: AutoCFRelease fontName(CTFontCopyPostScriptName(ctFont)); michael@0: CFStringToSkString(fontName, &info->fFontName); michael@0: } michael@0: michael@0: info->fMultiMaster = false; michael@0: CFIndex glyphCount = CTFontGetGlyphCount(ctFont); michael@0: info->fLastGlyphID = SkToU16(glyphCount - 1); michael@0: info->fEmSize = CTFontGetUnitsPerEm(ctFont); michael@0: michael@0: if (perGlyphInfo & SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo) { michael@0: populate_glyph_to_unicode(ctFont, glyphCount, &info->fGlyphToUnicode); michael@0: } michael@0: michael@0: info->fStyle = 0; michael@0: michael@0: // If it's not a truetype font, mark it as 'other'. Assume that TrueType michael@0: // fonts always have both glyf and loca tables. At the least, this is what michael@0: // sfntly needs to subset the font. CTFontCopyAttribute() does not always michael@0: // succeed in determining this directly. michael@0: if (!this->getTableSize('glyf') || !this->getTableSize('loca')) { michael@0: info->fType = SkAdvancedTypefaceMetrics::kOther_Font; michael@0: info->fItalicAngle = 0; michael@0: info->fAscent = 0; michael@0: info->fDescent = 0; michael@0: info->fStemV = 0; michael@0: info->fCapHeight = 0; michael@0: info->fBBox = SkIRect::MakeEmpty(); michael@0: return info; michael@0: } michael@0: michael@0: info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font; michael@0: CTFontSymbolicTraits symbolicTraits = CTFontGetSymbolicTraits(ctFont); michael@0: if (symbolicTraits & kCTFontMonoSpaceTrait) { michael@0: info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style; michael@0: } michael@0: if (symbolicTraits & kCTFontItalicTrait) { michael@0: info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style; michael@0: } michael@0: CTFontStylisticClass stylisticClass = symbolicTraits & kCTFontClassMaskTrait; michael@0: if (stylisticClass >= kCTFontOldStyleSerifsClass && stylisticClass <= kCTFontSlabSerifsClass) { michael@0: info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style; michael@0: } else if (stylisticClass & kCTFontScriptsClass) { michael@0: info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style; michael@0: } michael@0: info->fItalicAngle = (int16_t) CTFontGetSlantAngle(ctFont); michael@0: info->fAscent = (int16_t) CTFontGetAscent(ctFont); michael@0: info->fDescent = (int16_t) CTFontGetDescent(ctFont); michael@0: info->fCapHeight = (int16_t) CTFontGetCapHeight(ctFont); michael@0: CGRect bbox = CTFontGetBoundingBox(ctFont); michael@0: michael@0: SkRect r; michael@0: r.set( CGToScalar(CGRectGetMinX_inline(bbox)), // Left michael@0: CGToScalar(CGRectGetMaxY_inline(bbox)), // Top michael@0: CGToScalar(CGRectGetMaxX_inline(bbox)), // Right michael@0: CGToScalar(CGRectGetMinY_inline(bbox))); // Bottom michael@0: michael@0: r.roundOut(&(info->fBBox)); michael@0: michael@0: // Figure out a good guess for StemV - Min width of i, I, !, 1. michael@0: // This probably isn't very good with an italic font. michael@0: int16_t min_width = SHRT_MAX; michael@0: info->fStemV = 0; michael@0: static const UniChar stem_chars[] = {'i', 'I', '!', '1'}; michael@0: const size_t count = sizeof(stem_chars) / sizeof(stem_chars[0]); michael@0: CGGlyph glyphs[count]; michael@0: CGRect boundingRects[count]; michael@0: if (CTFontGetGlyphsForCharacters(ctFont, stem_chars, glyphs, count)) { michael@0: CTFontGetBoundingRectsForGlyphs(ctFont, kCTFontHorizontalOrientation, michael@0: glyphs, boundingRects, count); michael@0: for (size_t i = 0; i < count; i++) { michael@0: int16_t width = (int16_t) boundingRects[i].size.width; michael@0: if (width > 0 && width < min_width) { michael@0: min_width = width; michael@0: info->fStemV = min_width; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (false) { // TODO: haven't figured out how to know if font is embeddable michael@0: // (information is in the OS/2 table) michael@0: info->fType = SkAdvancedTypefaceMetrics::kNotEmbeddable_Font; michael@0: } else if (perGlyphInfo & SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo) { michael@0: if (info->fStyle & SkAdvancedTypefaceMetrics::kFixedPitch_Style) { michael@0: skia_advanced_typeface_metrics_utils::appendRange(&info->fGlyphWidths, 0); michael@0: info->fGlyphWidths->fAdvance.append(1, &min_width); michael@0: skia_advanced_typeface_metrics_utils::finishRange(info->fGlyphWidths.get(), 0, michael@0: SkAdvancedTypefaceMetrics::WidthRange::kDefault); michael@0: } else { michael@0: info->fGlyphWidths.reset( michael@0: skia_advanced_typeface_metrics_utils::getAdvanceData(ctFont.get(), michael@0: SkToInt(glyphCount), michael@0: glyphIDs, michael@0: glyphIDsCount, michael@0: &getWidthAdvance)); michael@0: } michael@0: } michael@0: return info; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static SK_SFNT_ULONG get_font_type_tag(const SkTypeface_Mac* typeface) { michael@0: CTFontRef ctFont = typeface->fFontRef.get(); michael@0: AutoCFRelease fontFormatRef( michael@0: static_cast(CTFontCopyAttribute(ctFont, kCTFontFormatAttribute))); michael@0: if (!fontFormatRef) { michael@0: return 0; michael@0: } michael@0: michael@0: SInt32 fontFormatValue; michael@0: if (!CFNumberGetValue(fontFormatRef, kCFNumberSInt32Type, &fontFormatValue)) { michael@0: return 0; michael@0: } michael@0: michael@0: switch (fontFormatValue) { michael@0: case kCTFontFormatOpenTypePostScript: michael@0: return SkSFNTHeader::fontType_OpenTypeCFF::TAG; michael@0: case kCTFontFormatOpenTypeTrueType: michael@0: return SkSFNTHeader::fontType_WindowsTrueType::TAG; michael@0: case kCTFontFormatTrueType: michael@0: return SkSFNTHeader::fontType_MacTrueType::TAG; michael@0: case kCTFontFormatPostScript: michael@0: return SkSFNTHeader::fontType_PostScript::TAG; michael@0: case kCTFontFormatBitmap: michael@0: return SkSFNTHeader::fontType_MacTrueType::TAG; michael@0: case kCTFontFormatUnrecognized: michael@0: default: michael@0: //CT seems to be unreliable in being able to obtain the type, michael@0: //even if all we want is the first four bytes of the font resource. michael@0: //Just the presence of the FontForge 'FFTM' table seems to throw it off. michael@0: return SkSFNTHeader::fontType_WindowsTrueType::TAG; michael@0: } michael@0: } michael@0: michael@0: SkStream* SkTypeface_Mac::onOpenStream(int* ttcIndex) const { michael@0: SK_SFNT_ULONG fontType = get_font_type_tag(this); michael@0: if (0 == fontType) { michael@0: return NULL; michael@0: } michael@0: michael@0: // get table tags michael@0: int numTables = this->countTables(); michael@0: SkTDArray tableTags; michael@0: tableTags.setCount(numTables); michael@0: this->getTableTags(tableTags.begin()); michael@0: michael@0: // calc total size for font, save sizes michael@0: SkTDArray tableSizes; michael@0: size_t totalSize = sizeof(SkSFNTHeader) + sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables; michael@0: for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { michael@0: size_t tableSize = this->getTableSize(tableTags[tableIndex]); michael@0: totalSize += (tableSize + 3) & ~3; michael@0: *tableSizes.append() = tableSize; michael@0: } michael@0: michael@0: // reserve memory for stream, and zero it (tables must be zero padded) michael@0: SkMemoryStream* stream = new SkMemoryStream(totalSize); michael@0: char* dataStart = (char*)stream->getMemoryBase(); michael@0: sk_bzero(dataStart, totalSize); michael@0: char* dataPtr = dataStart; michael@0: michael@0: // compute font header entries michael@0: uint16_t entrySelector = 0; michael@0: uint16_t searchRange = 1; michael@0: while (searchRange < numTables >> 1) { michael@0: entrySelector++; michael@0: searchRange <<= 1; michael@0: } michael@0: searchRange <<= 4; michael@0: uint16_t rangeShift = (numTables << 4) - searchRange; michael@0: michael@0: // write font header michael@0: SkSFNTHeader* header = (SkSFNTHeader*)dataPtr; michael@0: header->fontType = fontType; michael@0: header->numTables = SkEndian_SwapBE16(numTables); michael@0: header->searchRange = SkEndian_SwapBE16(searchRange); michael@0: header->entrySelector = SkEndian_SwapBE16(entrySelector); michael@0: header->rangeShift = SkEndian_SwapBE16(rangeShift); michael@0: dataPtr += sizeof(SkSFNTHeader); michael@0: michael@0: // write tables michael@0: SkSFNTHeader::TableDirectoryEntry* entry = (SkSFNTHeader::TableDirectoryEntry*)dataPtr; michael@0: dataPtr += sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables; michael@0: for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { michael@0: size_t tableSize = tableSizes[tableIndex]; michael@0: this->getTableData(tableTags[tableIndex], 0, tableSize, dataPtr); michael@0: entry->tag = SkEndian_SwapBE32(tableTags[tableIndex]); michael@0: entry->checksum = SkEndian_SwapBE32(SkOTUtils::CalcTableChecksum((SK_OT_ULONG*)dataPtr, michael@0: tableSize)); michael@0: entry->offset = SkEndian_SwapBE32(SkToU32(dataPtr - dataStart)); michael@0: entry->logicalLength = SkEndian_SwapBE32(SkToU32(tableSize)); michael@0: michael@0: dataPtr += (tableSize + 3) & ~3; michael@0: ++entry; michael@0: } michael@0: michael@0: return stream; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: int SkTypeface_Mac::onGetUPEM() const { michael@0: AutoCFRelease cgFont(CTFontCopyGraphicsFont(fFontRef, NULL)); michael@0: return CGFontGetUnitsPerEm(cgFont); michael@0: } michael@0: michael@0: SkTypeface::LocalizedStrings* SkTypeface_Mac::onCreateFamilyNameIterator() const { michael@0: SkTypeface::LocalizedStrings* nameIter = michael@0: SkOTUtils::LocalizedStrings_NameTable::CreateForFamilyNames(*this); michael@0: if (NULL == nameIter) { michael@0: AutoCFRelease cfLanguage; michael@0: AutoCFRelease cfFamilyName( michael@0: CTFontCopyLocalizedName(fFontRef, kCTFontFamilyNameKey, &cfLanguage)); michael@0: michael@0: SkString skLanguage; michael@0: SkString skFamilyName; michael@0: if (cfLanguage.get()) { michael@0: CFStringToSkString(cfLanguage.get(), &skLanguage); michael@0: } else { michael@0: skLanguage = "und"; //undetermined michael@0: } michael@0: if (cfFamilyName.get()) { michael@0: CFStringToSkString(cfFamilyName.get(), &skFamilyName); michael@0: } michael@0: michael@0: nameIter = new SkOTUtils::LocalizedStrings_SingleName(skFamilyName, skLanguage); michael@0: } michael@0: return nameIter; michael@0: } michael@0: michael@0: // If, as is the case with web fonts, the CTFont data isn't available, michael@0: // the CGFont data may work. While the CGFont may always provide the michael@0: // right result, leave the CTFont code path to minimize disruption. michael@0: static CFDataRef copyTableFromFont(CTFontRef ctFont, SkFontTableTag tag) { michael@0: CFDataRef data = CTFontCopyTable(ctFont, (CTFontTableTag) tag, michael@0: kCTFontTableOptionNoOptions); michael@0: if (NULL == data) { michael@0: AutoCFRelease cgFont(CTFontCopyGraphicsFont(ctFont, NULL)); michael@0: data = CGFontCopyTableForTag(cgFont, tag); michael@0: } michael@0: return data; michael@0: } michael@0: michael@0: int SkTypeface_Mac::onGetTableTags(SkFontTableTag tags[]) const { michael@0: AutoCFRelease cfArray(CTFontCopyAvailableTables(fFontRef, michael@0: kCTFontTableOptionNoOptions)); michael@0: if (NULL == cfArray) { michael@0: return 0; michael@0: } michael@0: int count = SkToInt(CFArrayGetCount(cfArray)); michael@0: if (tags) { michael@0: for (int i = 0; i < count; ++i) { michael@0: uintptr_t fontTag = reinterpret_cast(CFArrayGetValueAtIndex(cfArray, i)); michael@0: tags[i] = static_cast(fontTag); michael@0: } michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: size_t SkTypeface_Mac::onGetTableData(SkFontTableTag tag, size_t offset, michael@0: size_t length, void* dstData) const { michael@0: AutoCFRelease srcData(copyTableFromFont(fFontRef, tag)); michael@0: if (NULL == srcData) { michael@0: return 0; michael@0: } michael@0: michael@0: size_t srcSize = CFDataGetLength(srcData); michael@0: if (offset >= srcSize) { michael@0: return 0; michael@0: } michael@0: if (length > srcSize - offset) { michael@0: length = srcSize - offset; michael@0: } michael@0: if (dstData) { michael@0: memcpy(dstData, CFDataGetBytePtr(srcData) + offset, length); michael@0: } michael@0: return length; michael@0: } michael@0: michael@0: SkScalerContext* SkTypeface_Mac::onCreateScalerContext(const SkDescriptor* desc) const { michael@0: return new SkScalerContext_Mac(const_cast(this), desc); michael@0: } michael@0: michael@0: void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const { michael@0: if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag || michael@0: rec->fFlags & SkScalerContext::kLCD_Vertical_Flag) michael@0: { michael@0: rec->fMaskFormat = SkMask::kA8_Format; michael@0: // Render the glyphs as close as possible to what was requested. michael@0: // The above turns off subpixel rendering, but the user requested it. michael@0: // Normal hinting will cause the A8 masks to be generated from CoreGraphics subpixel masks. michael@0: // See comments below for more details. michael@0: rec->setHinting(SkPaint::kNormal_Hinting); michael@0: } michael@0: michael@0: unsigned flagsWeDontSupport = SkScalerContext::kDevKernText_Flag | michael@0: SkScalerContext::kForceAutohinting_Flag | michael@0: SkScalerContext::kLCD_BGROrder_Flag | michael@0: SkScalerContext::kLCD_Vertical_Flag; michael@0: michael@0: rec->fFlags &= ~flagsWeDontSupport; michael@0: michael@0: bool lcdSupport = supports_LCD(); michael@0: michael@0: // Only two levels of hinting are supported. michael@0: // kNo_Hinting means avoid CoreGraphics outline dilation. michael@0: // kNormal_Hinting means CoreGraphics outline dilation is allowed. michael@0: // If there is no lcd support, hinting (dilation) cannot be supported. michael@0: SkPaint::Hinting hinting = rec->getHinting(); michael@0: if (SkPaint::kSlight_Hinting == hinting || !lcdSupport) { michael@0: hinting = SkPaint::kNo_Hinting; michael@0: } else if (SkPaint::kFull_Hinting == hinting) { michael@0: hinting = SkPaint::kNormal_Hinting; michael@0: } michael@0: rec->setHinting(hinting); michael@0: michael@0: // FIXME: lcd smoothed un-hinted rasterization unsupported. michael@0: // Tracked by http://code.google.com/p/skia/issues/detail?id=915 . michael@0: // There is no current means to honor a request for unhinted lcd, michael@0: // so arbitrarilly ignore the hinting request and honor lcd. michael@0: michael@0: // Hinting and smoothing should be orthogonal, but currently they are not. michael@0: // CoreGraphics has no API to influence hinting. However, its lcd smoothed michael@0: // output is drawn from auto-dilated outlines (the amount of which is michael@0: // determined by AppleFontSmoothing). Its regular anti-aliased output is michael@0: // drawn from un-dilated outlines. michael@0: michael@0: // The behavior of Skia is as follows: michael@0: // [AA][no-hint]: generate AA using CoreGraphic's AA output. michael@0: // [AA][yes-hint]: use CoreGraphic's LCD output and reduce it to a single michael@0: // channel. This matches [LCD][yes-hint] in weight. michael@0: // [LCD][no-hint]: curently unable to honor, and must pick which to respect. michael@0: // Currenly side with LCD, effectively ignoring the hinting setting. michael@0: // [LCD][yes-hint]: generate LCD using CoreGraphic's LCD output. michael@0: michael@0: if (isLCDFormat(rec->fMaskFormat)) { michael@0: if (lcdSupport) { michael@0: //CoreGraphics creates 555 masks for smoothed text anyway. michael@0: rec->fMaskFormat = SkMask::kLCD16_Format; michael@0: rec->setHinting(SkPaint::kNormal_Hinting); michael@0: } else { michael@0: rec->fMaskFormat = SkMask::kA8_Format; michael@0: } michael@0: } michael@0: michael@0: // Unhinted A8 masks (those not derived from LCD masks) must respect SK_GAMMA_APPLY_TO_A8. michael@0: // All other masks can use regular gamma. michael@0: if (SkMask::kA8_Format == rec->fMaskFormat && SkPaint::kNo_Hinting == hinting) { michael@0: #ifndef SK_GAMMA_APPLY_TO_A8 michael@0: rec->ignorePreBlend(); michael@0: #endif michael@0: } else { michael@0: //CoreGraphics dialates smoothed text as needed. michael@0: rec->setContrast(0); michael@0: } michael@0: } michael@0: michael@0: // we take ownership of the ref michael@0: static const char* get_str(CFStringRef ref, SkString* str) { michael@0: CFStringToSkString(ref, str); michael@0: CFSafeRelease(ref); michael@0: return str->c_str(); michael@0: } michael@0: michael@0: void SkTypeface_Mac::onGetFontDescriptor(SkFontDescriptor* desc, michael@0: bool* isLocalStream) const { michael@0: SkString tmpStr; michael@0: michael@0: desc->setFamilyName(get_str(CTFontCopyFamilyName(fFontRef), &tmpStr)); michael@0: desc->setFullName(get_str(CTFontCopyFullName(fFontRef), &tmpStr)); michael@0: desc->setPostscriptName(get_str(CTFontCopyPostScriptName(fFontRef), &tmpStr)); michael@0: // TODO: need to add support for local-streams (here and openStream) michael@0: *isLocalStream = false; michael@0: } michael@0: michael@0: int SkTypeface_Mac::onCharsToGlyphs(const void* chars, Encoding encoding, michael@0: uint16_t glyphs[], int glyphCount) const michael@0: { michael@0: // Undocumented behavior of CTFontGetGlyphsForCharacters with non-bmp code points: michael@0: // When a surrogate pair is detected, the glyph index used is the index of the high surrogate. michael@0: // It is documented that if a mapping is unavailable, the glyph will be set to 0. michael@0: michael@0: SkAutoSTMalloc<1024, UniChar> charStorage; michael@0: const UniChar* src; // UniChar is a UTF-16 16-bit code unit. michael@0: int srcCount; michael@0: switch (encoding) { michael@0: case kUTF8_Encoding: { michael@0: const char* utf8 = reinterpret_cast(chars); michael@0: UniChar* utf16 = charStorage.reset(2 * glyphCount); michael@0: src = utf16; michael@0: for (int i = 0; i < glyphCount; ++i) { michael@0: SkUnichar uni = SkUTF8_NextUnichar(&utf8); michael@0: utf16 += SkUTF16_FromUnichar(uni, utf16); michael@0: } michael@0: srcCount = SkToInt(utf16 - src); michael@0: break; michael@0: } michael@0: case kUTF16_Encoding: { michael@0: src = reinterpret_cast(chars); michael@0: int extra = 0; michael@0: for (int i = 0; i < glyphCount; ++i) { michael@0: if (SkUTF16_IsHighSurrogate(src[i + extra])) { michael@0: ++extra; michael@0: } michael@0: } michael@0: srcCount = glyphCount + extra; michael@0: break; michael@0: } michael@0: case kUTF32_Encoding: { michael@0: const SkUnichar* utf32 = reinterpret_cast(chars); michael@0: UniChar* utf16 = charStorage.reset(2 * glyphCount); michael@0: src = utf16; michael@0: for (int i = 0; i < glyphCount; ++i) { michael@0: utf16 += SkUTF16_FromUnichar(utf32[i], utf16); michael@0: } michael@0: srcCount = SkToInt(utf16 - src); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // If glyphs is NULL, CT still needs glyph storage for finding the first failure. michael@0: // Also, if there are any non-bmp code points, the provided 'glyphs' storage will be inadequate. michael@0: SkAutoSTMalloc<1024, uint16_t> glyphStorage; michael@0: uint16_t* macGlyphs = glyphs; michael@0: if (NULL == macGlyphs || srcCount > glyphCount) { michael@0: macGlyphs = glyphStorage.reset(srcCount); michael@0: } michael@0: michael@0: bool allEncoded = CTFontGetGlyphsForCharacters(fFontRef, src, macGlyphs, srcCount); michael@0: michael@0: // If there were any non-bmp, then copy and compact. michael@0: // If 'glyphs' is NULL, then compact glyphStorage in-place. michael@0: // If all are bmp and 'glyphs' is non-NULL, 'glyphs' already contains the compact glyphs. michael@0: // If some are non-bmp and 'glyphs' is non-NULL, copy and compact into 'glyphs'. michael@0: uint16_t* compactedGlyphs = glyphs; michael@0: if (NULL == compactedGlyphs) { michael@0: compactedGlyphs = macGlyphs; michael@0: } michael@0: if (srcCount > glyphCount) { michael@0: int extra = 0; michael@0: for (int i = 0; i < glyphCount; ++i) { michael@0: if (SkUTF16_IsHighSurrogate(src[i + extra])) { michael@0: ++extra; michael@0: } michael@0: compactedGlyphs[i] = macGlyphs[i + extra]; michael@0: } michael@0: } michael@0: michael@0: if (allEncoded) { michael@0: return glyphCount; michael@0: } michael@0: michael@0: // If we got false, then we need to manually look for first failure. michael@0: for (int i = 0; i < glyphCount; ++i) { michael@0: if (0 == compactedGlyphs[i]) { michael@0: return i; michael@0: } michael@0: } michael@0: // Odd to get here, as we expected CT to have returned true up front. michael@0: return glyphCount; michael@0: } michael@0: michael@0: int SkTypeface_Mac::onCountGlyphs() const { michael@0: return SkToInt(CTFontGetGlyphCount(fFontRef)); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: #if 1 michael@0: michael@0: static bool find_desc_str(CTFontDescriptorRef desc, CFStringRef name, SkString* value) { michael@0: AutoCFRelease ref((CFStringRef)CTFontDescriptorCopyAttribute(desc, name)); michael@0: if (NULL == ref.get()) { michael@0: return false; michael@0: } michael@0: CFStringToSkString(ref, value); michael@0: return true; michael@0: } michael@0: michael@0: static bool find_dict_float(CFDictionaryRef dict, CFStringRef name, float* value) { michael@0: CFNumberRef num; michael@0: return CFDictionaryGetValueIfPresent(dict, name, (const void**)&num) michael@0: && CFNumberIsFloatType(num) michael@0: && CFNumberGetValue(num, kCFNumberFloatType, value); michael@0: } michael@0: michael@0: #include "SkFontMgr.h" michael@0: michael@0: static int unit_weight_to_fontstyle(float unit) { michael@0: float value; michael@0: if (unit < 0) { michael@0: value = 100 + (1 + unit) * 300; michael@0: } else { michael@0: value = 400 + unit * 500; michael@0: } michael@0: return sk_float_round2int(value); michael@0: } michael@0: michael@0: static int unit_width_to_fontstyle(float unit) { michael@0: float value; michael@0: if (unit < 0) { michael@0: value = 1 + (1 + unit) * 4; michael@0: } else { michael@0: value = 5 + unit * 4; michael@0: } michael@0: return sk_float_round2int(value); michael@0: } michael@0: michael@0: static inline int sqr(int value) { michael@0: SkASSERT(SkAbs32(value) < 0x7FFF); // check for overflow michael@0: return value * value; michael@0: } michael@0: michael@0: // We normalize each axis (weight, width, italic) to be base-900 michael@0: static int compute_metric(const SkFontStyle& a, const SkFontStyle& b) { michael@0: return sqr(a.weight() - b.weight()) + michael@0: sqr((a.width() - b.width()) * 100) + michael@0: sqr((a.isItalic() != b.isItalic()) * 900); michael@0: } michael@0: michael@0: static SkFontStyle desc2fontstyle(CTFontDescriptorRef desc) { michael@0: AutoCFRelease dict( michael@0: (CFDictionaryRef)CTFontDescriptorCopyAttribute(desc, michael@0: kCTFontTraitsAttribute)); michael@0: if (NULL == dict.get()) { michael@0: return SkFontStyle(); michael@0: } michael@0: michael@0: float weight, width, slant; michael@0: if (!find_dict_float(dict, kCTFontWeightTrait, &weight)) { michael@0: weight = 0; michael@0: } michael@0: if (!find_dict_float(dict, kCTFontWidthTrait, &width)) { michael@0: width = 0; michael@0: } michael@0: if (!find_dict_float(dict, kCTFontSlantTrait, &slant)) { michael@0: slant = 0; michael@0: } michael@0: michael@0: return SkFontStyle(unit_weight_to_fontstyle(weight), michael@0: unit_width_to_fontstyle(width), michael@0: slant ? SkFontStyle::kItalic_Slant michael@0: : SkFontStyle::kUpright_Slant); michael@0: } michael@0: michael@0: struct NameFontStyleRec { michael@0: SkString fFamilyName; michael@0: SkFontStyle fFontStyle; michael@0: }; michael@0: michael@0: static bool nameFontStyleProc(SkTypeface* face, SkTypeface::Style, michael@0: void* ctx) { michael@0: SkTypeface_Mac* macFace = (SkTypeface_Mac*)face; michael@0: const NameFontStyleRec* rec = (const NameFontStyleRec*)ctx; michael@0: michael@0: return macFace->fFontStyle == rec->fFontStyle && michael@0: macFace->fName == rec->fFamilyName; michael@0: } michael@0: michael@0: static SkTypeface* createFromDesc(CFStringRef cfFamilyName, michael@0: CTFontDescriptorRef desc) { michael@0: NameFontStyleRec rec; michael@0: CFStringToSkString(cfFamilyName, &rec.fFamilyName); michael@0: rec.fFontStyle = desc2fontstyle(desc); michael@0: michael@0: SkTypeface* face = SkTypefaceCache::FindByProcAndRef(nameFontStyleProc, michael@0: &rec); michael@0: if (face) { michael@0: return face; michael@0: } michael@0: michael@0: AutoCFRelease fontFamilyNameDictionary( michael@0: CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void**)&kCTFontFamilyNameAttribute, (const void**)&cfFamilyName, michael@0: 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); michael@0: AutoCFRelease fontDescriptor( michael@0: CTFontDescriptorCreateWithAttributes(fontFamilyNameDictionary)); michael@0: AutoCFRelease ctNamed(CTFontCreateWithFontDescriptor(fontDescriptor, 0, NULL)); michael@0: CTFontRef ctFont = CTFontCreateCopyWithAttributes(ctNamed, 1, NULL, desc); michael@0: if (NULL == ctFont) { michael@0: return NULL; michael@0: } michael@0: michael@0: SkString str; michael@0: CFStringToSkString(cfFamilyName, &str); michael@0: michael@0: bool isFixedPitch; michael@0: (void)computeStyleBits(ctFont, &isFixedPitch); michael@0: SkFontID fontID = CTFontRef_to_SkFontID(ctFont); michael@0: michael@0: face = SkNEW_ARGS(SkTypeface_Mac, (rec.fFontStyle, fontID, isFixedPitch, michael@0: ctFont, str.c_str())); michael@0: SkTypefaceCache::Add(face, face->style()); michael@0: return face; michael@0: } michael@0: michael@0: class SkFontStyleSet_Mac : public SkFontStyleSet { michael@0: public: michael@0: SkFontStyleSet_Mac(CFStringRef familyName, CTFontDescriptorRef desc) michael@0: : fArray(CTFontDescriptorCreateMatchingFontDescriptors(desc, NULL)) michael@0: , fFamilyName(familyName) michael@0: , fCount(0) { michael@0: CFRetain(familyName); michael@0: if (NULL == fArray) { michael@0: fArray = CFArrayCreate(NULL, NULL, 0, NULL); michael@0: } michael@0: fCount = SkToInt(CFArrayGetCount(fArray)); michael@0: } michael@0: michael@0: virtual ~SkFontStyleSet_Mac() { michael@0: CFRelease(fArray); michael@0: CFRelease(fFamilyName); michael@0: } michael@0: michael@0: virtual int count() SK_OVERRIDE { michael@0: return fCount; michael@0: } michael@0: michael@0: virtual void getStyle(int index, SkFontStyle* style, michael@0: SkString* name) SK_OVERRIDE { michael@0: SkASSERT((unsigned)index < (unsigned)fCount); michael@0: CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray, index); michael@0: if (style) { michael@0: *style = desc2fontstyle(desc); michael@0: } michael@0: if (name) { michael@0: if (!find_desc_str(desc, kCTFontStyleNameAttribute, name)) { michael@0: name->reset(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: virtual SkTypeface* createTypeface(int index) SK_OVERRIDE { michael@0: SkASSERT((unsigned)index < (unsigned)CFArrayGetCount(fArray)); michael@0: CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray, index); michael@0: michael@0: return createFromDesc(fFamilyName, desc); michael@0: } michael@0: michael@0: virtual SkTypeface* matchStyle(const SkFontStyle& pattern) SK_OVERRIDE { michael@0: if (0 == fCount) { michael@0: return NULL; michael@0: } michael@0: return createFromDesc(fFamilyName, findMatchingDesc(pattern)); michael@0: } michael@0: michael@0: private: michael@0: CFArrayRef fArray; michael@0: CFStringRef fFamilyName; michael@0: int fCount; michael@0: michael@0: CTFontDescriptorRef findMatchingDesc(const SkFontStyle& pattern) const { michael@0: int bestMetric = SK_MaxS32; michael@0: CTFontDescriptorRef bestDesc = NULL; michael@0: michael@0: for (int i = 0; i < fCount; ++i) { michael@0: CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray, i); michael@0: int metric = compute_metric(pattern, desc2fontstyle(desc)); michael@0: if (0 == metric) { michael@0: return desc; michael@0: } michael@0: if (metric < bestMetric) { michael@0: bestMetric = metric; michael@0: bestDesc = desc; michael@0: } michael@0: } michael@0: SkASSERT(bestDesc); michael@0: return bestDesc; michael@0: } michael@0: }; michael@0: michael@0: class SkFontMgr_Mac : public SkFontMgr { michael@0: CFArrayRef fNames; michael@0: int fCount; michael@0: michael@0: CFStringRef stringAt(int index) const { michael@0: SkASSERT((unsigned)index < (unsigned)fCount); michael@0: return (CFStringRef)CFArrayGetValueAtIndex(fNames, index); michael@0: } michael@0: michael@0: static SkFontStyleSet* CreateSet(CFStringRef cfFamilyName) { michael@0: AutoCFRelease cfAttr( michael@0: CFDictionaryCreateMutable(kCFAllocatorDefault, 0, michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks)); michael@0: michael@0: CFDictionaryAddValue(cfAttr, kCTFontFamilyNameAttribute, cfFamilyName); michael@0: michael@0: AutoCFRelease desc( michael@0: CTFontDescriptorCreateWithAttributes(cfAttr)); michael@0: return SkNEW_ARGS(SkFontStyleSet_Mac, (cfFamilyName, desc)); michael@0: } michael@0: michael@0: public: michael@0: SkFontMgr_Mac() michael@0: : fNames(SkCTFontManagerCopyAvailableFontFamilyNames()) michael@0: , fCount(fNames ? SkToInt(CFArrayGetCount(fNames)) : 0) {} michael@0: michael@0: virtual ~SkFontMgr_Mac() { michael@0: CFSafeRelease(fNames); michael@0: } michael@0: michael@0: protected: michael@0: virtual int onCountFamilies() const SK_OVERRIDE { michael@0: return fCount; michael@0: } michael@0: michael@0: virtual void onGetFamilyName(int index, SkString* familyName) const SK_OVERRIDE { michael@0: if ((unsigned)index < (unsigned)fCount) { michael@0: CFStringToSkString(this->stringAt(index), familyName); michael@0: } else { michael@0: familyName->reset(); michael@0: } michael@0: } michael@0: michael@0: virtual SkFontStyleSet* onCreateStyleSet(int index) const SK_OVERRIDE { michael@0: if ((unsigned)index >= (unsigned)fCount) { michael@0: return NULL; michael@0: } michael@0: return CreateSet(this->stringAt(index)); michael@0: } michael@0: michael@0: virtual SkFontStyleSet* onMatchFamily(const char familyName[]) const SK_OVERRIDE { michael@0: AutoCFRelease cfName(make_CFString(familyName)); michael@0: return CreateSet(cfName); michael@0: } michael@0: michael@0: virtual SkTypeface* onMatchFamilyStyle(const char familyName[], michael@0: const SkFontStyle&) const SK_OVERRIDE { michael@0: return NULL; michael@0: } michael@0: michael@0: virtual SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember, michael@0: const SkFontStyle&) const SK_OVERRIDE { michael@0: return NULL; michael@0: } michael@0: michael@0: virtual SkTypeface* onCreateFromData(SkData* data, michael@0: int ttcIndex) const SK_OVERRIDE { michael@0: AutoCFRelease pr(SkCreateDataProviderFromData(data)); michael@0: if (NULL == pr) { michael@0: return NULL; michael@0: } michael@0: return create_from_dataProvider(pr); michael@0: } michael@0: michael@0: virtual SkTypeface* onCreateFromStream(SkStream* stream, michael@0: int ttcIndex) const SK_OVERRIDE { michael@0: AutoCFRelease pr(SkCreateDataProviderFromStream(stream)); michael@0: if (NULL == pr) { michael@0: return NULL; michael@0: } michael@0: return create_from_dataProvider(pr); michael@0: } michael@0: michael@0: virtual SkTypeface* onCreateFromFile(const char path[], michael@0: int ttcIndex) const SK_OVERRIDE { michael@0: AutoCFRelease pr(CGDataProviderCreateWithFilename(path)); michael@0: if (NULL == pr) { michael@0: return NULL; michael@0: } michael@0: return create_from_dataProvider(pr); michael@0: } michael@0: michael@0: virtual SkTypeface* onLegacyCreateTypeface(const char familyName[], michael@0: unsigned styleBits) const SK_OVERRIDE { michael@0: return create_typeface(NULL, familyName, (SkTypeface::Style)styleBits); michael@0: } michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: SkFontMgr* SkFontMgr::Factory() { michael@0: return SkNEW(SkFontMgr_Mac); michael@0: } michael@0: #endif michael@0: michael@0: SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, michael@0: const char famillyName[], michael@0: SkTypeface::Style style) michael@0: { michael@0: SkDEBUGFAIL("SkFontHost::FindTypeface unimplemented"); michael@0: return NULL; michael@0: } michael@0: michael@0: SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream*) michael@0: { michael@0: SkDEBUGFAIL("SkFontHost::CreateTypeface unimplemented"); michael@0: return NULL; michael@0: } michael@0: michael@0: SkTypeface* SkFontHost::CreateTypefaceFromFile(char const*) michael@0: { michael@0: SkDEBUGFAIL("SkFontHost::CreateTypefaceFromFile unimplemented"); michael@0: return NULL; michael@0: } michael@0: