| /* |
| * Copyright 2006 The Android Open Source Project |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkTypes.h" |
| #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) |
| |
| #ifdef SK_BUILD_FOR_MAC |
| #import <ApplicationServices/ApplicationServices.h> |
| #endif |
| |
| #ifdef SK_BUILD_FOR_IOS |
| #include <CoreText/CoreText.h> |
| #include <CoreText/CTFontManager.h> |
| #include <CoreGraphics/CoreGraphics.h> |
| #include <CoreFoundation/CoreFoundation.h> |
| #endif |
| |
| #include "include/core/SkColor.h" |
| #include "include/core/SkData.h" |
| #include "include/core/SkFontArguments.h" |
| #include "include/core/SkFontParameters.h" |
| #include "include/core/SkFontStyle.h" |
| #include "include/core/SkFontTypes.h" |
| #include "include/core/SkRect.h" |
| #include "include/core/SkRefCnt.h" |
| #include "include/core/SkScalar.h" |
| #include "include/core/SkStream.h" |
| #include "include/core/SkString.h" |
| #include "include/core/SkTypeface.h" |
| #include "include/ports/SkTypeface_mac.h" |
| #include "include/private/SkFixed.h" |
| #include "include/private/base/SkTDArray.h" |
| #include "include/private/base/SkTPin.h" |
| #include "include/private/base/SkTemplates.h" |
| #include "include/private/base/SkMalloc.h" |
| #include "include/private/base/SkMutex.h" |
| #include "include/private/base/SkOnce.h" |
| #include "include/private/base/SkTo.h" |
| #include "src/base/SkUTF.h" |
| #include "src/core/SkAdvancedTypefaceMetrics.h" |
| #include "src/core/SkEndian.h" |
| #include "src/core/SkFontDescriptor.h" |
| #include "src/core/SkMask.h" |
| #include "src/core/SkScalerContext.h" |
| #include "src/core/SkTypefaceCache.h" |
| #include "src/ports/SkScalerContext_mac_ct.h" |
| #include "src/ports/SkTypeface_mac_ct.h" |
| #include "src/sfnt/SkOTTableTypes.h" |
| #include "src/sfnt/SkOTTable_OS_2.h" |
| #include "src/sfnt/SkOTTable_OS_2_V4.h" |
| #include "src/sfnt/SkOTUtils.h" |
| #include "src/sfnt/SkSFNTHeader.h" |
| #include "src/utils/mac/SkCGBase.h" |
| #include "src/utils/mac/SkCGGeometry.h" |
| #include "src/utils/mac/SkCTFont.h" |
| #include "src/utils/mac/SkUniqueCFRef.h" |
| |
| #include <dlfcn.h> |
| #include <limits.h> |
| #include <string.h> |
| #include <memory> |
| |
| using namespace skia_private; |
| |
| /** Assumes src and dst are not nullptr. */ |
| void SkStringFromCFString(CFStringRef src, SkString* dst) { |
| // Reserve enough room for the worst-case string, |
| // plus 1 byte for the trailing null. |
| CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(src), |
| kCFStringEncodingUTF8) + 1; |
| dst->resize(length); |
| CFStringGetCString(src, dst->data(), length, kCFStringEncodingUTF8); |
| // Resize to the actual UTF-8 length used, stripping the null character. |
| dst->resize(strlen(dst->c_str())); |
| } |
| |
| SkString SkCFTypeIDDescription(CFTypeID id) { |
| SkUniqueCFRef<CFStringRef> typeDescription(CFCopyTypeIDDescription(id)); |
| SkString skTypeDescription; |
| SkStringFromCFString(typeDescription.get(), &skTypeDescription); |
| return skTypeDescription; |
| } |
| |
| template<typename CF> CFTypeID SkCFGetTypeID(); |
| #define SK_GETCFTYPEID(cf) \ |
| template<> CFTypeID SkCFGetTypeID<cf##Ref>() { return cf##GetTypeID(); } |
| SK_GETCFTYPEID(CFBoolean); |
| SK_GETCFTYPEID(CFDictionary); |
| SK_GETCFTYPEID(CFNumber); |
| |
| /* Checked dynamic downcast of CFTypeRef. |
| * |
| * @param cf the ref to downcast. |
| * @param cfAsCF if cf can be cast to the type CF, receives the downcast ref. |
| * @param name if non-nullptr the cast is expected to succeed and failures will be logged. |
| * @return true if the cast succeeds, false otherwise. |
| */ |
| template <typename CF> |
| static bool SkCFDynamicCast(CFTypeRef cf, CF* cfAsCF, char const* name) { |
| //SkDEBUGF("SkCFDynamicCast '%s' of type %s to type %s\n", name ? name : "<annon>", |
| // SkCFTypeIDDescription( CFGetTypeID(cf) ).c_str() |
| // SkCFTypeIDDescription(SkCFGetTypeID<CF>()).c_str()); |
| if (!cf) { |
| if (name) { |
| SkDEBUGF("%s not present\n", name); |
| } |
| return false; |
| } |
| if (CFGetTypeID(cf) != SkCFGetTypeID<CF>()) { |
| if (name) { |
| SkDEBUGF("%s is a %s but expected a %s\n", name, |
| SkCFTypeIDDescription( CFGetTypeID(cf) ).c_str(), |
| SkCFTypeIDDescription(SkCFGetTypeID<CF>()).c_str()); |
| } |
| return false; |
| } |
| *cfAsCF = static_cast<CF>(cf); |
| return true; |
| } |
| |
| template<typename T> struct SkCFNumberTypeFor {}; |
| #define SK_CFNUMBERTYPE_FOR(c, cf) \ |
| template<> struct SkCFNumberTypeFor<c> : std::integral_constant<CFNumberType, cf> {}; |
| SK_CFNUMBERTYPE_FOR(char , kCFNumberCharType ); |
| SK_CFNUMBERTYPE_FOR(short , kCFNumberShortType ); |
| SK_CFNUMBERTYPE_FOR(int , kCFNumberIntType ); |
| SK_CFNUMBERTYPE_FOR(long , kCFNumberLongType ); |
| SK_CFNUMBERTYPE_FOR(long long, kCFNumberLongLongType); |
| SK_CFNUMBERTYPE_FOR(float , kCFNumberFloatType ); |
| SK_CFNUMBERTYPE_FOR(double , kCFNumberDoubleType ); |
| |
| template <typename T> |
| static bool SkCFNumberDynamicCast(CFTypeRef cf, T* number, CFNumberRef* cfNumber, char const* name){ |
| CFNumberRef cfAsCFNumber; |
| if (!SkCFDynamicCast(cf, &cfAsCFNumber, name)) { |
| return false; |
| } |
| if (!CFNumberGetValue(cfAsCFNumber, SkCFNumberTypeFor<T>::value, number)) { |
| if (name) { |
| SkDEBUGF("%s CFNumber not extractable\n", name); |
| } |
| return false; |
| } |
| if (cfNumber) { |
| *cfNumber = cfAsCFNumber; |
| } |
| return true; |
| } |
| |
| // In macOS 10.12 and later any variation on the CGFont which has default axis value will be |
| // dropped when creating the CTFont. Unfortunately, in macOS 10.15 the priority of setting |
| // the optical size (and opsz variation) is |
| // 1. the value of kCTFontOpticalSizeAttribute in the CTFontDescriptor (undocumented) |
| // 2. the opsz axis default value if kCTFontOpticalSizeAttribute is 'none' (undocumented) |
| // 3. the opsz variation on the nascent CTFont from the CGFont (was dropped if default) |
| // 4. the opsz variation in kCTFontVariationAttribute in CTFontDescriptor (crashes 10.10) |
| // 5. the size requested (can fudge in SkTypeface but not SkScalerContext) |
| // The first one which is found will be used to set the opsz variation (after clamping). |
| static void add_opsz_attr(CFMutableDictionaryRef attr, double opsz) { |
| SkUniqueCFRef<CFNumberRef> opszValueNumber( |
| CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &opsz)); |
| // Avoid using kCTFontOpticalSizeAttribute directly |
| CFStringRef SkCTFontOpticalSizeAttribute = CFSTR("NSCTFontOpticalSizeAttribute"); |
| CFDictionarySetValue(attr, SkCTFontOpticalSizeAttribute, opszValueNumber.get()); |
| } |
| |
| // This turns off application of the 'trak' table to advances, but also all other tracking. |
| static void add_notrak_attr(CFMutableDictionaryRef attr) { |
| int zero = 0; |
| SkUniqueCFRef<CFNumberRef> unscaledTrackingNumber( |
| CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &zero)); |
| CFStringRef SkCTFontUnscaledTrackingAttribute = CFSTR("NSCTFontUnscaledTrackingAttribute"); |
| CFDictionarySetValue(attr, SkCTFontUnscaledTrackingAttribute, unscaledTrackingNumber.get()); |
| } |
| |
| SkUniqueCFRef<CTFontRef> SkCTFontCreateExactCopy(CTFontRef baseFont, CGFloat textSize, |
| OpszVariation opszVariation) |
| { |
| SkUniqueCFRef<CFMutableDictionaryRef> attr( |
| CFDictionaryCreateMutable(kCFAllocatorDefault, 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| |
| if (opszVariation.isSet) { |
| add_opsz_attr(attr.get(), opszVariation.value); |
| } else { |
| // On (at least) 10.10 though 10.14 the default system font was SFNSText/SFNSDisplay. |
| // The CTFont is backed by both; optical size < 20 means SFNSText else SFNSDisplay. |
| // On at least 10.11 the glyph ids in these fonts became non-interchangable. |
| // To keep glyph ids stable over size changes, preserve the optical size. |
| // In 10.15 this was replaced with use of variable fonts with an opsz axis. |
| // A CTFont backed by multiple fonts picked by opsz where the multiple backing fonts are |
| // variable fonts with opsz axis and non-interchangeable glyph ids would break the |
| // opsz.isSet branch above, but hopefully that never happens. |
| // See https://crbug.com/524646 . |
| CFStringRef SkCTFontOpticalSizeAttribute = CFSTR("NSCTFontOpticalSizeAttribute"); |
| SkUniqueCFRef<CFTypeRef> opsz(CTFontCopyAttribute(baseFont, SkCTFontOpticalSizeAttribute)); |
| double opsz_val; |
| if (!opsz || |
| CFGetTypeID(opsz.get()) != CFNumberGetTypeID() || |
| !CFNumberGetValue(static_cast<CFNumberRef>(opsz.get()),kCFNumberDoubleType,&opsz_val) || |
| opsz_val <= 0) |
| { |
| opsz_val = CTFontGetSize(baseFont); |
| } |
| add_opsz_attr(attr.get(), opsz_val); |
| } |
| add_notrak_attr(attr.get()); |
| |
| SkUniqueCFRef<CTFontDescriptorRef> desc(CTFontDescriptorCreateWithAttributes(attr.get())); |
| |
| return SkUniqueCFRef<CTFontRef>( |
| CTFontCreateCopyWithAttributes(baseFont, textSize, nullptr, desc.get())); |
| } |
| |
| CTFontRef SkTypeface_GetCTFontRef(const SkTypeface* face) { |
| return face ? (CTFontRef)face->internal_private_getCTFontRef() : nullptr; |
| } |
| |
| static bool find_by_CTFontRef(SkTypeface* cached, void* context) { |
| CTFontRef self = (CTFontRef)context; |
| CTFontRef other = (CTFontRef)cached->internal_private_getCTFontRef(); |
| |
| return CFEqual(self, other); |
| } |
| |
| /** Creates a typeface, searching the cache if providedData is nullptr. */ |
| sk_sp<SkTypeface> SkTypeface_Mac::Make(SkUniqueCFRef<CTFontRef> font, |
| OpszVariation opszVariation, |
| std::unique_ptr<SkStreamAsset> providedData) { |
| static SkMutex gTFCacheMutex; |
| static SkTypefaceCache gTFCache; |
| |
| SkASSERT(font); |
| const bool isFromStream(providedData); |
| |
| auto makeTypeface = [&]() { |
| SkUniqueCFRef<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(font.get())); |
| SkFontStyle style = SkCTFontDescriptorGetSkFontStyle(desc.get(), isFromStream); |
| CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font.get()); |
| bool isFixedPitch = SkToBool(traits & kCTFontMonoSpaceTrait); |
| |
| return sk_sp<SkTypeface>(new SkTypeface_Mac(std::move(font), style, isFixedPitch, |
| opszVariation, std::move(providedData))); |
| }; |
| |
| if (isFromStream) { |
| return makeTypeface(); |
| } |
| |
| SkAutoMutexExclusive ama(gTFCacheMutex); |
| sk_sp<SkTypeface> face = gTFCache.findByProcAndRef(find_by_CTFontRef, (void*)font.get()); |
| if (!face) { |
| face = makeTypeface(); |
| if (face) { |
| gTFCache.add(face); |
| } |
| } |
| return face; |
| } |
| |
| /* This function is visible on the outside. It first searches the cache, and if |
| * not found, returns a new entry (after adding it to the cache). |
| */ |
| sk_sp<SkTypeface> SkMakeTypefaceFromCTFont(CTFontRef font) { |
| CFRetain(font); |
| return SkTypeface_Mac::Make(SkUniqueCFRef<CTFontRef>(font), |
| OpszVariation(), |
| nullptr); |
| } |
| |
| static bool find_dict_CGFloat(CFDictionaryRef dict, CFStringRef name, CGFloat* value) { |
| CFNumberRef num; |
| return CFDictionaryGetValueIfPresent(dict, name, (const void**)&num) |
| && CFNumberIsFloatType(num) |
| && CFNumberGetValue(num, kCFNumberCGFloatType, value); |
| } |
| |
| template <typename S, typename D, typename C> struct LinearInterpolater { |
| struct Mapping { |
| S src_val; |
| D dst_val; |
| }; |
| constexpr LinearInterpolater(Mapping const mapping[], int mappingCount) |
| : fMapping(mapping), fMappingCount(mappingCount) {} |
| |
| static D map(S value, S src_min, S src_max, D dst_min, D dst_max) { |
| SkASSERT(src_min < src_max); |
| SkASSERT(dst_min <= dst_max); |
| return C()(dst_min + (((value - src_min) * (dst_max - dst_min)) / (src_max - src_min))); |
| } |
| |
| D map(S val) const { |
| // -Inf to [0] |
| if (val < fMapping[0].src_val) { |
| return fMapping[0].dst_val; |
| } |
| |
| // Linear from [i] to [i+1] |
| for (int i = 0; i < fMappingCount - 1; ++i) { |
| if (val < fMapping[i+1].src_val) { |
| return map(val, fMapping[i].src_val, fMapping[i+1].src_val, |
| fMapping[i].dst_val, fMapping[i+1].dst_val); |
| } |
| } |
| |
| // From [n] to +Inf |
| // if (fcweight < Inf) |
| return fMapping[fMappingCount - 1].dst_val; |
| } |
| |
| Mapping const * fMapping; |
| int fMappingCount; |
| }; |
| |
| struct RoundCGFloatToInt { |
| int operator()(CGFloat s) { return s + 0.5; } |
| }; |
| struct CGFloatIdentity { |
| CGFloat operator()(CGFloat s) { return s; } |
| }; |
| |
| /** Convert the [0, 1000] CSS weight to [-1, 1] CTFontDescriptor weight (for system fonts). |
| * |
| * The -1 to 1 weights reported by CTFontDescriptors have different mappings depending on if the |
| * CTFont is native or created from a CGDataProvider. |
| */ |
| CGFloat SkCTFontCTWeightForCSSWeight(int fontstyleWeight) { |
| using Interpolator = LinearInterpolater<int, CGFloat, CGFloatIdentity>; |
| |
| // Note that Mac supports the old OS2 version A so 0 through 10 are as if multiplied by 100. |
| // However, on this end we can't tell, so this is ignored. |
| |
| static Interpolator::Mapping nativeWeightMappings[11]; |
| static SkOnce once; |
| once([&] { |
| const CGFloat(&nsFontWeights)[11] = SkCTFontGetNSFontWeightMapping(); |
| for (int i = 0; i < 11; ++i) { |
| nativeWeightMappings[i].src_val = i * 100; |
| nativeWeightMappings[i].dst_val = nsFontWeights[i]; |
| } |
| }); |
| static constexpr Interpolator nativeInterpolator( |
| nativeWeightMappings, std::size(nativeWeightMappings)); |
| |
| return nativeInterpolator.map(fontstyleWeight); |
| } |
| |
| /** Convert the [-1, 1] CTFontDescriptor weight to [0, 1000] CSS weight. |
| * |
| * The -1 to 1 weights reported by CTFontDescriptors have different mappings depending on if the |
| * CTFont is native or created from a CGDataProvider. |
| */ |
| static int ct_weight_to_fontstyle(CGFloat cgWeight, bool fromDataProvider) { |
| using Interpolator = LinearInterpolater<CGFloat, int, RoundCGFloatToInt>; |
| |
| // Note that Mac supports the old OS2 version A so 0 through 10 are as if multiplied by 100. |
| // However, on this end we can't tell, so this is ignored. |
| |
| static Interpolator::Mapping nativeWeightMappings[11]; |
| static Interpolator::Mapping dataProviderWeightMappings[11]; |
| static SkOnce once; |
| once([&] { |
| const CGFloat(&nsFontWeights)[11] = SkCTFontGetNSFontWeightMapping(); |
| const CGFloat(&userFontWeights)[11] = SkCTFontGetDataFontWeightMapping(); |
| for (int i = 0; i < 11; ++i) { |
| nativeWeightMappings[i].src_val = nsFontWeights[i]; |
| nativeWeightMappings[i].dst_val = i * 100; |
| |
| dataProviderWeightMappings[i].src_val = userFontWeights[i]; |
| dataProviderWeightMappings[i].dst_val = i * 100; |
| } |
| }); |
| static constexpr Interpolator nativeInterpolator( |
| nativeWeightMappings, std::size(nativeWeightMappings)); |
| static constexpr Interpolator dataProviderInterpolator( |
| dataProviderWeightMappings, std::size(dataProviderWeightMappings)); |
| |
| return fromDataProvider ? dataProviderInterpolator.map(cgWeight) |
| : nativeInterpolator.map(cgWeight); |
| } |
| |
| /** Convert the [0, 10] CSS weight to [-1, 1] CTFontDescriptor width. */ |
| CGFloat SkCTFontCTWidthForCSSWidth(int fontstyleWidth) { |
| using Interpolator = LinearInterpolater<int, CGFloat, CGFloatIdentity>; |
| |
| // Values determined by creating font data with every width, creating a CTFont, |
| // and asking the CTFont for its width. See TypefaceStyle test for basics. |
| static constexpr Interpolator::Mapping widthMappings[] = { |
| { 0, -0.5 }, |
| { 10, 0.5 }, |
| }; |
| static constexpr Interpolator interpolator(widthMappings, std::size(widthMappings)); |
| return interpolator.map(fontstyleWidth); |
| } |
| |
| /** Convert the [-1, 1] CTFontDescriptor width to [0, 10] CSS weight. */ |
| static int ct_width_to_fontstyle(CGFloat cgWidth) { |
| using Interpolator = LinearInterpolater<CGFloat, int, RoundCGFloatToInt>; |
| |
| // Values determined by creating font data with every width, creating a CTFont, |
| // and asking the CTFont for its width. See TypefaceStyle test for basics. |
| static constexpr Interpolator::Mapping widthMappings[] = { |
| { -0.5, 0 }, |
| { 0.5, 10 }, |
| }; |
| static constexpr Interpolator interpolator(widthMappings, std::size(widthMappings)); |
| return interpolator.map(cgWidth); |
| } |
| |
| SkFontStyle SkCTFontDescriptorGetSkFontStyle(CTFontDescriptorRef desc, bool fromDataProvider) { |
| SkUniqueCFRef<CFTypeRef> traits(CTFontDescriptorCopyAttribute(desc, kCTFontTraitsAttribute)); |
| CFDictionaryRef fontTraitsDict; |
| if (!SkCFDynamicCast(traits.get(), &fontTraitsDict, "Font traits")) { |
| return SkFontStyle(); |
| } |
| |
| CGFloat weight, width, slant; |
| if (!find_dict_CGFloat(fontTraitsDict, kCTFontWeightTrait, &weight)) { |
| weight = 0; |
| } |
| if (!find_dict_CGFloat(fontTraitsDict, kCTFontWidthTrait, &width)) { |
| width = 0; |
| } |
| if (!find_dict_CGFloat(fontTraitsDict, kCTFontSlantTrait, &slant)) { |
| slant = 0; |
| } |
| |
| return SkFontStyle(ct_weight_to_fontstyle(weight, fromDataProvider), |
| ct_width_to_fontstyle(width), |
| slant ? SkFontStyle::kItalic_Slant |
| : SkFontStyle::kUpright_Slant); |
| } |
| |
| |
| // Web fonts added to the CTFont registry do not return their character set. |
| // Iterate through the font in this case. The existing caller caches the result, |
| // so the performance impact isn't too bad. |
| static void populate_glyph_to_unicode_slow(CTFontRef ctFont, CFIndex glyphCount, |
| SkUnichar* out) { |
| sk_bzero(out, glyphCount * sizeof(SkUnichar)); |
| UniChar unichar = 0; |
| while (glyphCount > 0) { |
| CGGlyph glyph; |
| if (CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) { |
| if (out[glyph] == 0) { |
| out[glyph] = unichar; |
| --glyphCount; |
| } |
| } |
| if (++unichar == 0) { |
| break; |
| } |
| } |
| } |
| |
| static constexpr uint16_t kPlaneSize = 1 << 13; |
| |
| static void get_plane_glyph_map(const uint8_t* bits, |
| CTFontRef ctFont, |
| CFIndex glyphCount, |
| SkUnichar* glyphToUnicode, |
| uint8_t planeIndex) { |
| SkUnichar planeOrigin = (SkUnichar)planeIndex << 16; // top half of codepoint. |
| for (uint16_t i = 0; i < kPlaneSize; i++) { |
| uint8_t mask = bits[i]; |
| if (!mask) { |
| continue; |
| } |
| for (uint8_t j = 0; j < 8; j++) { |
| if (0 == (mask & ((uint8_t)1 << j))) { |
| continue; |
| } |
| uint16_t planeOffset = (i << 3) | j; |
| SkUnichar codepoint = planeOrigin | (SkUnichar)planeOffset; |
| uint16_t utf16[2] = {planeOffset, 0}; |
| size_t count = 1; |
| if (planeOrigin != 0) { |
| count = SkUTF::ToUTF16(codepoint, utf16); |
| } |
| CGGlyph glyphs[2] = {0, 0}; |
| if (CTFontGetGlyphsForCharacters(ctFont, utf16, glyphs, count)) { |
| SkASSERT(glyphs[1] == 0); |
| SkASSERT(glyphs[0] < glyphCount); |
| // CTFontCopyCharacterSet and CTFontGetGlyphsForCharacters seem to add 'support' |
| // for characters 0x9, 0xA, and 0xD mapping them to the glyph for character 0x20? |
| // Prefer mappings to codepoints at or above 0x20. |
| if (glyphToUnicode[glyphs[0]] < 0x20) { |
| glyphToUnicode[glyphs[0]] = codepoint; |
| } |
| } |
| } |
| } |
| } |
| // Construct Glyph to Unicode table. |
| static void populate_glyph_to_unicode(CTFontRef ctFont, CFIndex glyphCount, |
| SkUnichar* glyphToUnicode) { |
| sk_bzero(glyphToUnicode, sizeof(SkUnichar) * glyphCount); |
| SkUniqueCFRef<CFCharacterSetRef> charSet(CTFontCopyCharacterSet(ctFont)); |
| if (!charSet) { |
| populate_glyph_to_unicode_slow(ctFont, glyphCount, glyphToUnicode); |
| return; |
| } |
| |
| SkUniqueCFRef<CFDataRef> bitmap( |
| CFCharacterSetCreateBitmapRepresentation(nullptr, charSet.get())); |
| if (!bitmap) { |
| return; |
| } |
| CFIndex dataLength = CFDataGetLength(bitmap.get()); |
| if (!dataLength) { |
| return; |
| } |
| SkASSERT(dataLength >= kPlaneSize); |
| const UInt8* bits = CFDataGetBytePtr(bitmap.get()); |
| |
| get_plane_glyph_map(bits, ctFont, glyphCount, glyphToUnicode, 0); |
| /* |
| A CFData object that specifies the bitmap representation of the Unicode |
| character points the for the new character set. The bitmap representation could |
| contain all the Unicode character range starting from BMP to Plane 16. The |
| first 8KiB (8192 bytes) of the data represent the BMP range. The BMP range 8KiB |
| can be followed by zero to sixteen 8KiB bitmaps, each prepended with the plane |
| index byte. For example, the bitmap representing the BMP and Plane 2 has the |
| size of 16385 bytes (8KiB for BMP, 1 byte index, and a 8KiB bitmap for Plane |
| 2). The plane index byte, in this case, contains the integer value two. |
| */ |
| |
| if (dataLength <= kPlaneSize) { |
| return; |
| } |
| int extraPlaneCount = (dataLength - kPlaneSize) / (1 + kPlaneSize); |
| SkASSERT(dataLength == kPlaneSize + extraPlaneCount * (1 + kPlaneSize)); |
| while (extraPlaneCount-- > 0) { |
| bits += kPlaneSize; |
| uint8_t planeIndex = *bits++; |
| SkASSERT(planeIndex >= 1); |
| SkASSERT(planeIndex <= 16); |
| get_plane_glyph_map(bits, ctFont, glyphCount, glyphToUnicode, planeIndex); |
| } |
| } |
| |
| void SkTypeface_Mac::getGlyphToUnicodeMap(SkUnichar* dstArray) const { |
| SkUniqueCFRef<CTFontRef> ctFont = |
| SkCTFontCreateExactCopy(fFontRef.get(), CTFontGetUnitsPerEm(fFontRef.get()), |
| fOpszVariation); |
| CFIndex glyphCount = CTFontGetGlyphCount(ctFont.get()); |
| populate_glyph_to_unicode(ctFont.get(), glyphCount, dstArray); |
| } |
| |
| std::unique_ptr<SkAdvancedTypefaceMetrics> SkTypeface_Mac::onGetAdvancedMetrics() const { |
| |
| SkUniqueCFRef<CTFontRef> ctFont = |
| SkCTFontCreateExactCopy(fFontRef.get(), CTFontGetUnitsPerEm(fFontRef.get()), |
| fOpszVariation); |
| |
| std::unique_ptr<SkAdvancedTypefaceMetrics> info(new SkAdvancedTypefaceMetrics); |
| |
| { |
| SkUniqueCFRef<CFStringRef> fontName(CTFontCopyPostScriptName(ctFont.get())); |
| if (fontName.get()) { |
| SkStringFromCFString(fontName.get(), &info->fPostScriptName); |
| info->fFontName = info->fPostScriptName; |
| } |
| } |
| |
| CFArrayRef ctAxes = this->getVariationAxes(); |
| if (ctAxes && CFArrayGetCount(ctAxes) > 0) { |
| info->fFlags |= SkAdvancedTypefaceMetrics::kVariable_FontFlag; |
| } |
| |
| SkOTTableOS2_V4::Type fsType; |
| if (sizeof(fsType) == this->getTableData(SkTEndian_SwapBE32(SkOTTableOS2::TAG), |
| offsetof(SkOTTableOS2_V4, fsType), |
| sizeof(fsType), |
| &fsType)) { |
| SkOTUtils::SetAdvancedTypefaceFlags(fsType, info.get()); |
| } |
| |
| // If it's not a truetype font, mark it as 'other'. Assume that TrueType |
| // fonts always have both glyf and loca tables. At the least, this is what |
| // sfntly needs to subset the font. CTFontCopyAttribute() does not always |
| // succeed in determining this directly. |
| if (!this->getTableSize(SkSetFourByteTag('g','l','y','f')) || |
| !this->getTableSize(SkSetFourByteTag('l','o','c','a'))) |
| { |
| return info; |
| } |
| |
| info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font; |
| CTFontSymbolicTraits symbolicTraits = CTFontGetSymbolicTraits(ctFont.get()); |
| if (symbolicTraits & kCTFontMonoSpaceTrait) { |
| info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style; |
| } |
| if (symbolicTraits & kCTFontItalicTrait) { |
| info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style; |
| } |
| CTFontStylisticClass stylisticClass = symbolicTraits & kCTFontClassMaskTrait; |
| if (stylisticClass >= kCTFontOldStyleSerifsClass && stylisticClass <= kCTFontSlabSerifsClass) { |
| info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style; |
| } else if (stylisticClass & kCTFontScriptsClass) { |
| info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style; |
| } |
| info->fItalicAngle = (int16_t) CTFontGetSlantAngle(ctFont.get()); |
| info->fAscent = (int16_t) CTFontGetAscent(ctFont.get()); |
| info->fDescent = (int16_t) CTFontGetDescent(ctFont.get()); |
| info->fCapHeight = (int16_t) CTFontGetCapHeight(ctFont.get()); |
| CGRect bbox = CTFontGetBoundingBox(ctFont.get()); |
| |
| SkRect r; |
| r.setLTRB(SkScalarFromCGFloat(SkCGRectGetMinX(bbox)), // Left |
| SkScalarFromCGFloat(SkCGRectGetMaxY(bbox)), // Top |
| SkScalarFromCGFloat(SkCGRectGetMaxX(bbox)), // Right |
| SkScalarFromCGFloat(SkCGRectGetMinY(bbox))); // Bottom |
| |
| r.roundOut(&(info->fBBox)); |
| |
| // Figure out a good guess for StemV - Min width of i, I, !, 1. |
| // This probably isn't very good with an italic font. |
| int16_t min_width = SHRT_MAX; |
| info->fStemV = 0; |
| static const UniChar stem_chars[] = {'i', 'I', '!', '1'}; |
| const size_t count = sizeof(stem_chars) / sizeof(stem_chars[0]); |
| CGGlyph glyphs[count]; |
| CGRect boundingRects[count]; |
| if (CTFontGetGlyphsForCharacters(ctFont.get(), stem_chars, glyphs, count)) { |
| CTFontGetBoundingRectsForGlyphs(ctFont.get(), kCTFontOrientationHorizontal, |
| glyphs, boundingRects, count); |
| for (size_t i = 0; i < count; i++) { |
| int16_t width = (int16_t) boundingRects[i].size.width; |
| if (width > 0 && width < min_width) { |
| min_width = width; |
| info->fStemV = min_width; |
| } |
| } |
| } |
| return info; |
| } |
| |
| static SK_SFNT_ULONG get_font_type_tag(CTFontRef ctFont) { |
| SkUniqueCFRef<CFNumberRef> fontFormatRef( |
| static_cast<CFNumberRef>(CTFontCopyAttribute(ctFont, kCTFontFormatAttribute))); |
| if (!fontFormatRef) { |
| return 0; |
| } |
| |
| SInt32 fontFormatValue; |
| if (!CFNumberGetValue(fontFormatRef.get(), kCFNumberSInt32Type, &fontFormatValue)) { |
| return 0; |
| } |
| |
| switch (fontFormatValue) { |
| case kCTFontFormatOpenTypePostScript: |
| return SkSFNTHeader::fontType_OpenTypeCFF::TAG; |
| case kCTFontFormatOpenTypeTrueType: |
| return SkSFNTHeader::fontType_WindowsTrueType::TAG; |
| case kCTFontFormatTrueType: |
| return SkSFNTHeader::fontType_MacTrueType::TAG; |
| case kCTFontFormatPostScript: |
| return SkSFNTHeader::fontType_PostScript::TAG; |
| case kCTFontFormatBitmap: |
| return SkSFNTHeader::fontType_MacTrueType::TAG; |
| case kCTFontFormatUnrecognized: |
| default: |
| return 0; |
| } |
| } |
| |
| std::unique_ptr<SkStreamAsset> SkTypeface_Mac::onOpenStream(int* ttcIndex) const { |
| *ttcIndex = 0; |
| |
| fInitStream([this]{ |
| if (fStream) { |
| return; |
| } |
| |
| SK_SFNT_ULONG fontType = get_font_type_tag(fFontRef.get()); |
| |
| // get table tags |
| int numTables = this->countTables(); |
| SkTDArray<SkFontTableTag> tableTags; |
| tableTags.resize(numTables); |
| this->getTableTags(tableTags.begin()); |
| |
| // CT seems to be unreliable in being able to obtain the type, |
| // even if all we want is the first four bytes of the font resource. |
| // Just the presence of the FontForge 'FFTM' table seems to throw it off. |
| if (fontType == 0) { |
| fontType = SkSFNTHeader::fontType_WindowsTrueType::TAG; |
| |
| // see https://skbug.com/7630#c7 |
| bool couldBeCFF = false; |
| constexpr SkFontTableTag CFFTag = SkSetFourByteTag('C', 'F', 'F', ' '); |
| constexpr SkFontTableTag CFF2Tag = SkSetFourByteTag('C', 'F', 'F', '2'); |
| for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { |
| if (CFFTag == tableTags[tableIndex] || CFF2Tag == tableTags[tableIndex]) { |
| couldBeCFF = true; |
| } |
| } |
| if (couldBeCFF) { |
| fontType = SkSFNTHeader::fontType_OpenTypeCFF::TAG; |
| } |
| } |
| |
| // Sometimes CoreGraphics incorrectly thinks a font is kCTFontFormatPostScript. |
| // It is exceedingly unlikely that this is the case, so double check |
| // (see https://crbug.com/809763 ). |
| if (fontType == SkSFNTHeader::fontType_PostScript::TAG) { |
| // see if there are any required 'typ1' tables (see Adobe Technical Note #5180) |
| bool couldBeTyp1 = false; |
| constexpr SkFontTableTag TYPE1Tag = SkSetFourByteTag('T', 'Y', 'P', '1'); |
| constexpr SkFontTableTag CIDTag = SkSetFourByteTag('C', 'I', 'D', ' '); |
| for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { |
| if (TYPE1Tag == tableTags[tableIndex] || CIDTag == tableTags[tableIndex]) { |
| couldBeTyp1 = true; |
| } |
| } |
| if (!couldBeTyp1) { |
| fontType = SkSFNTHeader::fontType_OpenTypeCFF::TAG; |
| } |
| } |
| |
| // get the table sizes and accumulate the total size of the font |
| SkTDArray<size_t> tableSizes; |
| size_t totalSize = sizeof(SkSFNTHeader) + sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables; |
| for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { |
| size_t tableSize = this->getTableSize(tableTags[tableIndex]); |
| totalSize += (tableSize + 3) & ~3; |
| *tableSizes.append() = tableSize; |
| } |
| |
| // reserve memory for stream, and zero it (tables must be zero padded) |
| fStream = std::make_unique<SkMemoryStream>(totalSize); |
| char* dataStart = (char*)fStream->getMemoryBase(); |
| sk_bzero(dataStart, totalSize); |
| char* dataPtr = dataStart; |
| |
| // compute font header entries |
| uint16_t entrySelector = 0; |
| uint16_t searchRange = 1; |
| while (searchRange < numTables >> 1) { |
| entrySelector++; |
| searchRange <<= 1; |
| } |
| searchRange <<= 4; |
| uint16_t rangeShift = (numTables << 4) - searchRange; |
| |
| // write font header |
| SkSFNTHeader* header = (SkSFNTHeader*)dataPtr; |
| header->fontType = fontType; |
| header->numTables = SkEndian_SwapBE16(numTables); |
| header->searchRange = SkEndian_SwapBE16(searchRange); |
| header->entrySelector = SkEndian_SwapBE16(entrySelector); |
| header->rangeShift = SkEndian_SwapBE16(rangeShift); |
| dataPtr += sizeof(SkSFNTHeader); |
| |
| // write tables |
| SkSFNTHeader::TableDirectoryEntry* entry = (SkSFNTHeader::TableDirectoryEntry*)dataPtr; |
| dataPtr += sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables; |
| for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) { |
| size_t tableSize = tableSizes[tableIndex]; |
| this->getTableData(tableTags[tableIndex], 0, tableSize, dataPtr); |
| entry->tag = SkEndian_SwapBE32(tableTags[tableIndex]); |
| entry->checksum = SkEndian_SwapBE32(SkOTUtils::CalcTableChecksum((SK_OT_ULONG*)dataPtr, |
| tableSize)); |
| entry->offset = SkEndian_SwapBE32(SkToU32(dataPtr - dataStart)); |
| entry->logicalLength = SkEndian_SwapBE32(SkToU32(tableSize)); |
| |
| dataPtr += (tableSize + 3) & ~3; |
| ++entry; |
| } |
| }); |
| return fStream->duplicate(); |
| } |
| |
| std::unique_ptr<SkStreamAsset> SkTypeface_Mac::onOpenExistingStream(int* ttcIndex) const { |
| *ttcIndex = 0; |
| return fStream ? fStream->duplicate() : nullptr; |
| } |
| |
| bool SkTypeface_Mac::onGlyphMaskNeedsCurrentColor() const { |
| // `CPAL` (`COLR` and `SVG`) fonts may need the current color. |
| // However, even `sbix` fonts can have glyphs which need the current color. |
| // These may be glyphs with paths but no `sbix` entries, which are impossible to distinguish. |
| return this->fHasColorGlyphs; |
| } |
| |
| CFArrayRef SkTypeface_Mac::getVariationAxes() const { |
| fInitVariationAxes([this]{ |
| fVariationAxes.reset(CTFontCopyVariationAxes(fFontRef.get())); |
| }); |
| return fVariationAxes.get(); |
| } |
| |
| int SkTypeface_Mac::onGetVariationDesignPosition( |
| SkFontArguments::VariationPosition::Coordinate coordinates[], int coordinateCount) const |
| { |
| CFArrayRef ctAxes = this->getVariationAxes(); |
| if (!ctAxes) { |
| return -1; |
| } |
| CFIndex axisCount = CFArrayGetCount(ctAxes); |
| if (!coordinates || coordinateCount < axisCount) { |
| return axisCount; |
| } |
| |
| // On 10.12 and later, this only returns non-default variations. |
| SkUniqueCFRef<CFDictionaryRef> ctVariation(CTFontCopyVariation(fFontRef.get())); |
| if (!ctVariation) { |
| return -1; |
| } |
| |
| for (int i = 0; i < axisCount; ++i) { |
| CFDictionaryRef axisInfoDict; |
| if (!SkCFDynamicCast(CFArrayGetValueAtIndex(ctAxes, i), &axisInfoDict, "Axis")) { |
| return -1; |
| } |
| |
| int64_t tagLong; |
| CFNumberRef tagNumber; |
| CFTypeRef tag = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisIdentifierKey); |
| if (!SkCFNumberDynamicCast(tag, &tagLong, &tagNumber, "Axis tag")) { |
| return -1; |
| } |
| coordinates[i].axis = tagLong; |
| |
| CGFloat valueCGFloat; |
| CFTypeRef value = CFDictionaryGetValue(ctVariation.get(), tagNumber); |
| if (value) { |
| if (!SkCFNumberDynamicCast(value, &valueCGFloat, nullptr, "Variation value")) { |
| return -1; |
| } |
| } else { |
| CFTypeRef def = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisDefaultValueKey); |
| if (!SkCFNumberDynamicCast(def, &valueCGFloat, nullptr, "Axis default value")) { |
| return -1; |
| } |
| } |
| coordinates[i].value = SkScalarFromCGFloat(valueCGFloat); |
| } |
| return axisCount; |
| } |
| |
| int SkTypeface_Mac::onGetUPEM() const { |
| SkUniqueCFRef<CGFontRef> cgFont(CTFontCopyGraphicsFont(fFontRef.get(), nullptr)); |
| return CGFontGetUnitsPerEm(cgFont.get()); |
| } |
| |
| SkTypeface::LocalizedStrings* SkTypeface_Mac::onCreateFamilyNameIterator() const { |
| sk_sp<SkTypeface::LocalizedStrings> nameIter = |
| SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(*this); |
| if (!nameIter) { |
| CFStringRef cfLanguageRaw; |
| SkUniqueCFRef<CFStringRef> cfFamilyName( |
| CTFontCopyLocalizedName(fFontRef.get(), kCTFontFamilyNameKey, &cfLanguageRaw)); |
| SkUniqueCFRef<CFStringRef> cfLanguage(cfLanguageRaw); |
| |
| SkString skLanguage; |
| SkString skFamilyName; |
| if (cfLanguage) { |
| SkStringFromCFString(cfLanguage.get(), &skLanguage); |
| } else { |
| skLanguage = "und"; //undetermined |
| } |
| if (cfFamilyName) { |
| SkStringFromCFString(cfFamilyName.get(), &skFamilyName); |
| } |
| |
| nameIter = sk_make_sp<SkOTUtils::LocalizedStrings_SingleName>(skFamilyName, skLanguage); |
| } |
| return nameIter.release(); |
| } |
| |
| int SkTypeface_Mac::onGetTableTags(SkFontTableTag tags[]) const { |
| SkUniqueCFRef<CFArrayRef> cfArray( |
| CTFontCopyAvailableTables(fFontRef.get(), kCTFontTableOptionNoOptions)); |
| if (!cfArray) { |
| return 0; |
| } |
| CFIndex count = CFArrayGetCount(cfArray.get()); |
| if (tags) { |
| for (CFIndex i = 0; i < count; ++i) { |
| uintptr_t fontTag = reinterpret_cast<uintptr_t>( |
| CFArrayGetValueAtIndex(cfArray.get(), i)); |
| tags[i] = static_cast<SkFontTableTag>(fontTag); |
| } |
| } |
| return count; |
| } |
| |
| // If, as is the case with web fonts, the CTFont data isn't available, |
| // the CGFont data may work. While the CGFont may always provide the |
| // right result, leave the CTFont code path to minimize disruption. |
| static SkUniqueCFRef<CFDataRef> copy_table_from_font(CTFontRef ctFont, SkFontTableTag tag) { |
| SkUniqueCFRef<CFDataRef> data(CTFontCopyTable(ctFont, (CTFontTableTag) tag, |
| kCTFontTableOptionNoOptions)); |
| if (!data) { |
| SkUniqueCFRef<CGFontRef> cgFont(CTFontCopyGraphicsFont(ctFont, nullptr)); |
| data.reset(CGFontCopyTableForTag(cgFont.get(), tag)); |
| } |
| return data; |
| } |
| |
| size_t SkTypeface_Mac::onGetTableData(SkFontTableTag tag, size_t offset, |
| size_t length, void* dstData) const { |
| SkUniqueCFRef<CFDataRef> srcData = copy_table_from_font(fFontRef.get(), tag); |
| if (!srcData) { |
| return 0; |
| } |
| |
| size_t srcSize = CFDataGetLength(srcData.get()); |
| if (offset >= srcSize) { |
| return 0; |
| } |
| if (length > srcSize - offset) { |
| length = srcSize - offset; |
| } |
| if (dstData) { |
| memcpy(dstData, CFDataGetBytePtr(srcData.get()) + offset, length); |
| } |
| return length; |
| } |
| |
| sk_sp<SkData> SkTypeface_Mac::onCopyTableData(SkFontTableTag tag) const { |
| SkUniqueCFRef<CFDataRef> srcData = copy_table_from_font(fFontRef.get(), tag); |
| if (!srcData) { |
| return nullptr; |
| } |
| const UInt8* data = CFDataGetBytePtr(srcData.get()); |
| CFIndex length = CFDataGetLength(srcData.get()); |
| return SkData::MakeWithProc(data, length, |
| [](const void*, void* ctx) { |
| CFRelease((CFDataRef)ctx); |
| }, (void*)srcData.release()); |
| } |
| |
| std::unique_ptr<SkScalerContext> SkTypeface_Mac::onCreateScalerContext( |
| const SkScalerContextEffects& effects, const SkDescriptor* desc) const |
| { |
| return std::make_unique<SkScalerContext_Mac>( |
| sk_ref_sp(const_cast<SkTypeface_Mac*>(this)), effects, desc); |
| } |
| |
| void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const { |
| if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag || |
| rec->fFlags & SkScalerContext::kLCD_Vertical_Flag) |
| { |
| rec->fMaskFormat = SkMask::kA8_Format; |
| // Render the glyphs as close as possible to what was requested. |
| // The above turns off subpixel rendering, but the user requested it. |
| // Normal hinting will cause the A8 masks to be generated from CoreGraphics subpixel masks. |
| // See comments below for more details. |
| rec->setHinting(SkFontHinting::kNormal); |
| } |
| |
| unsigned flagsWeDontSupport = SkScalerContext::kForceAutohinting_Flag | |
| SkScalerContext::kLCD_BGROrder_Flag | |
| SkScalerContext::kLCD_Vertical_Flag; |
| |
| rec->fFlags &= ~flagsWeDontSupport; |
| |
| const SkCTFontSmoothBehavior smoothBehavior = SkCTFontGetSmoothBehavior(); |
| |
| // Only two levels of hinting are supported. |
| // kNo_Hinting means avoid CoreGraphics outline dilation (smoothing). |
| // kNormal_Hinting means CoreGraphics outline dilation (smoothing) is allowed. |
| if (rec->getHinting() != SkFontHinting::kNone) { |
| rec->setHinting(SkFontHinting::kNormal); |
| } |
| // If smoothing has no effect, don't request it. |
| if (smoothBehavior == SkCTFontSmoothBehavior::none) { |
| rec->setHinting(SkFontHinting::kNone); |
| } |
| |
| // FIXME: lcd smoothed un-hinted rasterization unsupported. |
| // Tracked by http://code.google.com/p/skia/issues/detail?id=915 . |
| // There is no current means to honor a request for unhinted lcd, |
| // so arbitrarilly ignore the hinting request and honor lcd. |
| |
| // Hinting and smoothing should be orthogonal, but currently they are not. |
| // CoreGraphics has no API to influence hinting. However, its lcd smoothed |
| // output is drawn from auto-dilated outlines (the amount of which is |
| // determined by AppleFontSmoothing). Its regular anti-aliased output is |
| // drawn from un-dilated outlines. |
| |
| // The behavior of Skia is as follows: |
| // [AA][no-hint]: generate AA using CoreGraphic's AA output. |
| // [AA][yes-hint]: use CoreGraphic's LCD output and reduce it to a single |
| // channel. This matches [LCD][yes-hint] in weight. |
| // [LCD][no-hint]: currently unable to honor, and must pick which to respect. |
| // Currently side with LCD, effectively ignoring the hinting setting. |
| // [LCD][yes-hint]: generate LCD using CoreGraphic's LCD output. |
| if (rec->fMaskFormat == SkMask::kLCD16_Format) { |
| if (smoothBehavior == SkCTFontSmoothBehavior::subpixel) { |
| //CoreGraphics creates 555 masks for smoothed text anyway. |
| rec->fMaskFormat = SkMask::kLCD16_Format; |
| rec->setHinting(SkFontHinting::kNormal); |
| } else { |
| rec->fMaskFormat = SkMask::kA8_Format; |
| if (smoothBehavior != SkCTFontSmoothBehavior::none) { |
| rec->setHinting(SkFontHinting::kNormal); |
| } |
| } |
| } |
| |
| // CoreText provides no information as to whether a glyph will be color or not. |
| // Fonts may mix outlines and bitmaps, so information is needed on a glyph by glyph basis. |
| // If a font contains an 'sbix' table, consider it to be a color font, and disable lcd. |
| if (fHasColorGlyphs) { |
| rec->fMaskFormat = SkMask::kARGB32_Format; |
| } |
| |
| // Unhinted A8 masks (those not derived from LCD masks) must respect SK_GAMMA_APPLY_TO_A8. |
| // All other masks can use regular gamma. |
| if (SkMask::kA8_Format == rec->fMaskFormat && SkFontHinting::kNone == rec->getHinting()) { |
| #ifndef SK_GAMMA_APPLY_TO_A8 |
| // SRGBTODO: Is this correct? Do we want contrast boost? |
| rec->ignorePreBlend(); |
| #endif |
| } else { |
| SkColor color = rec->getLuminanceColor(); |
| if (smoothBehavior == SkCTFontSmoothBehavior::some) { |
| // CoreGraphics smoothed text without subpixel coverage blitting goes from a gamma of |
| // 2.0 for black foreground to a gamma of 1.0 for white foreground. Emulate this |
| // through the mask gamma by reducing the color values to 1/2. |
| color = SkColorSetRGB(SkColorGetR(color) * 1/2, |
| SkColorGetG(color) * 1/2, |
| SkColorGetB(color) * 1/2); |
| } else if (smoothBehavior == SkCTFontSmoothBehavior::subpixel) { |
| // CoreGraphics smoothed text with subpixel coverage blitting goes from a gamma of |
| // 2.0 for black foreground to a gamma of ~1.4? for white foreground. Emulate this |
| // through the mask gamma by reducing the color values to 3/4. |
| color = SkColorSetRGB(SkColorGetR(color) * 3/4, |
| SkColorGetG(color) * 3/4, |
| SkColorGetB(color) * 3/4); |
| } |
| rec->setLuminanceColor(color); |
| |
| // CoreGraphics dialates smoothed text to provide contrast. |
| rec->setContrast(0); |
| } |
| } |
| |
| /** Takes ownership of the CFStringRef. */ |
| static const char* get_str(CFStringRef ref, SkString* str) { |
| if (nullptr == ref) { |
| return nullptr; |
| } |
| SkStringFromCFString(ref, str); |
| CFRelease(ref); |
| return str->c_str(); |
| } |
| |
| void SkTypeface_Mac::onGetFamilyName(SkString* familyName) const { |
| get_str(CTFontCopyFamilyName(fFontRef.get()), familyName); |
| } |
| |
| bool SkTypeface_Mac::onGetPostScriptName(SkString* skPostScriptName) const { |
| SkUniqueCFRef<CFStringRef> ctPostScriptName(CTFontCopyPostScriptName(fFontRef.get())); |
| if (!ctPostScriptName) { |
| return false; |
| } |
| if (skPostScriptName) { |
| SkStringFromCFString(ctPostScriptName.get(), skPostScriptName); |
| } |
| return true; |
| } |
| |
| void SkTypeface_Mac::onGetFontDescriptor(SkFontDescriptor* desc, |
| bool* isLocalStream) const { |
| SkString tmpStr; |
| |
| desc->setFamilyName(get_str(CTFontCopyFamilyName(fFontRef.get()), &tmpStr)); |
| desc->setFullName(get_str(CTFontCopyFullName(fFontRef.get()), &tmpStr)); |
| desc->setPostscriptName(get_str(CTFontCopyPostScriptName(fFontRef.get()), &tmpStr)); |
| desc->setStyle(this->fontStyle()); |
| *isLocalStream = fIsFromStream; |
| } |
| |
| void SkTypeface_Mac::onCharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const { |
| // Undocumented behavior of CTFontGetGlyphsForCharacters with non-bmp code points: |
| // When a surrogate pair is detected, the glyph index used is the index of the high surrogate. |
| // It is documented that if a mapping is unavailable, the glyph will be set to 0. |
| |
| AutoSTMalloc<1024, UniChar> charStorage; |
| const UniChar* src; // UniChar is a UTF-16 16-bit code unit. |
| int srcCount; |
| const SkUnichar* utf32 = reinterpret_cast<const SkUnichar*>(uni); |
| UniChar* utf16 = charStorage.reset(2 * count); |
| src = utf16; |
| for (int i = 0; i < count; ++i) { |
| utf16 += SkUTF::ToUTF16(utf32[i], utf16); |
| } |
| srcCount = SkToInt(utf16 - src); |
| |
| // If there are any non-bmp code points, the provided 'glyphs' storage will be inadequate. |
| AutoSTMalloc<1024, uint16_t> glyphStorage; |
| uint16_t* macGlyphs = glyphs; |
| if (srcCount > count) { |
| macGlyphs = glyphStorage.reset(srcCount); |
| } |
| |
| CTFontGetGlyphsForCharacters(fFontRef.get(), src, macGlyphs, srcCount); |
| |
| // If there were any non-bmp, then copy and compact. |
| // If all are bmp, 'glyphs' already contains the compact glyphs. |
| // If some are non-bmp, copy and compact into 'glyphs'. |
| if (srcCount > count) { |
| SkASSERT(glyphs != macGlyphs); |
| int extra = 0; |
| for (int i = 0; i < count; ++i) { |
| glyphs[i] = macGlyphs[i + extra]; |
| if (SkUTF::IsLeadingSurrogateUTF16(src[i + extra])) { |
| ++extra; |
| } |
| } |
| } else { |
| SkASSERT(glyphs == macGlyphs); |
| } |
| } |
| |
| int SkTypeface_Mac::onCountGlyphs() const { |
| return SkToInt(CTFontGetGlyphCount(fFontRef.get())); |
| } |
| |
| /** Creates a dictionary suitable for setting the axes on a CTFont. */ |
| CTFontVariation SkCTVariationFromSkFontArguments(CTFontRef ct, CFArrayRef ctAxes, |
| const SkFontArguments& args) { |
| OpszVariation opsz; |
| constexpr const SkFourByteTag opszTag = SkSetFourByteTag('o','p','s','z'); |
| |
| if (!ctAxes) { |
| return CTFontVariation(); |
| } |
| CFIndex axisCount = CFArrayGetCount(ctAxes); |
| |
| // On 10.12 and later, this only returns non-default variations. |
| SkUniqueCFRef<CFDictionaryRef> oldCtVariation(CTFontCopyVariation(ct)); |
| |
| const SkFontArguments::VariationPosition position = args.getVariationDesignPosition(); |
| |
| SkUniqueCFRef<CFMutableDictionaryRef> newCtVariation( |
| CFDictionaryCreateMutable(kCFAllocatorDefault, axisCount, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| SkUniqueCFRef<CFMutableDictionaryRef> wrongOpszVariation; |
| |
| for (int i = 0; i < axisCount; ++i) { |
| CFDictionaryRef axisInfoDict; |
| if (!SkCFDynamicCast(CFArrayGetValueAtIndex(ctAxes, i), &axisInfoDict, "Axis")) { |
| return CTFontVariation(); |
| } |
| |
| int64_t tagLong; |
| CFNumberRef tagNumber; |
| CFTypeRef tag = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisIdentifierKey); |
| if (!SkCFNumberDynamicCast(tag, &tagLong, &tagNumber, "Axis tag")) { |
| return CTFontVariation(); |
| } |
| |
| // The variation axes can be set to any value, but cg will effectively pin them. |
| // Pin them here to normalize. |
| double minDouble; |
| double maxDouble; |
| double defDouble; |
| CFTypeRef min = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMinimumValueKey); |
| CFTypeRef max = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMaximumValueKey); |
| CFTypeRef def = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisDefaultValueKey); |
| if (!SkCFNumberDynamicCast(min, &minDouble, nullptr, "Axis min") || |
| !SkCFNumberDynamicCast(max, &maxDouble, nullptr, "Axis max") || |
| !SkCFNumberDynamicCast(def, &defDouble, nullptr, "Axis def")) |
| { |
| return CTFontVariation(); |
| } |
| |
| // Start with the default value. |
| double value = defDouble; |
| |
| // Then the current value. |
| bool haveCurrentDouble = false; |
| double currentDouble = 0; |
| if (oldCtVariation) { |
| CFTypeRef currentNumber = CFDictionaryGetValue(oldCtVariation.get(), tagNumber); |
| if (currentNumber) { |
| if (!SkCFNumberDynamicCast(currentNumber, &value, nullptr, "Variation value")) { |
| return CTFontVariation(); |
| } |
| currentDouble = value; |
| haveCurrentDouble = true; |
| } |
| } |
| |
| // Then the requested value. |
| // The position may be over specified. If there are multiple values for a given axis, |
| // use the last one since that's what css-fonts-4 requires. |
| for (int j = position.coordinateCount; j --> 0;) { |
| if (position.coordinates[j].axis == tagLong) { |
| value = SkTPin<double>(position.coordinates[j].value, minDouble, maxDouble); |
| if (tagLong == opszTag) { |
| opsz.isSet = true; |
| } |
| break; |
| } |
| } |
| if (tagLong == opszTag) { |
| opsz.value = value; |
| if (haveCurrentDouble && value == currentDouble) { |
| // Calculate a value strictly in range but different from currentValue. |
| double wrongOpszDouble = ((maxDouble - minDouble) / 2.0) + minDouble; |
| if (wrongOpszDouble == currentDouble) { |
| wrongOpszDouble = ((maxDouble - minDouble) / 4.0) + minDouble; |
| } |
| wrongOpszVariation.reset( |
| CFDictionaryCreateMutable(kCFAllocatorDefault, 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| SkUniqueCFRef<CFNumberRef> wrongOpszNumber( |
| CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &wrongOpszDouble)); |
| CFDictionarySetValue(wrongOpszVariation.get(), tagNumber, wrongOpszNumber.get()); |
| } |
| } |
| SkUniqueCFRef<CFNumberRef> valueNumber( |
| CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value)); |
| CFDictionaryAddValue(newCtVariation.get(), tagNumber, valueNumber.get()); |
| } |
| return { SkUniqueCFRef<CFDictionaryRef>(std::move(newCtVariation)), |
| SkUniqueCFRef<CFDictionaryRef>(std::move(wrongOpszVariation)), |
| opsz }; |
| } |
| |
| sk_sp<SkTypeface> SkTypeface_Mac::onMakeClone(const SkFontArguments& args) const { |
| CTFontVariation ctVariation = SkCTVariationFromSkFontArguments(fFontRef.get(), |
| this->getVariationAxes(), |
| args); |
| |
| SkUniqueCFRef<CTFontRef> ctVariant; |
| if (ctVariation.variation) { |
| SkUniqueCFRef<CFMutableDictionaryRef> attributes( |
| CFDictionaryCreateMutable(kCFAllocatorDefault, 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| |
| CTFontRef ctFont = fFontRef.get(); |
| SkUniqueCFRef<CTFontRef> wrongOpszFont; |
| if (ctVariation.wrongOpszVariation) { |
| // On macOS 11 cloning a system font with an opsz axis and not changing the |
| // value of the opsz axis (either by setting it to the same value or not |
| // specifying it at all) when setting a variation causes the variation to |
| // be set but the cloned font will still compare CFEqual to the original |
| // font. Work around this by setting the opsz to something which isn't the |
| // desired value before setting the entire desired variation. |
| // |
| // A similar issue occurs with fonts from data on macOS 10.15 and the same |
| // work around seems to apply. This is less noticeable though since CFEqual |
| // isn't used on these fonts. |
| CFDictionarySetValue(attributes.get(), |
| kCTFontVariationAttribute, ctVariation.wrongOpszVariation.get()); |
| SkUniqueCFRef<CTFontDescriptorRef> varDesc( |
| CTFontDescriptorCreateWithAttributes(attributes.get())); |
| wrongOpszFont.reset(CTFontCreateCopyWithAttributes(ctFont, 0, nullptr, varDesc.get())); |
| ctFont = wrongOpszFont.get(); |
| } |
| |
| CFDictionarySetValue(attributes.get(), |
| kCTFontVariationAttribute, ctVariation.variation.get()); |
| SkUniqueCFRef<CTFontDescriptorRef> varDesc( |
| CTFontDescriptorCreateWithAttributes(attributes.get())); |
| ctVariant.reset(CTFontCreateCopyWithAttributes(ctFont, 0, nullptr, varDesc.get())); |
| } else { |
| ctVariant.reset((CTFontRef)CFRetain(fFontRef.get())); |
| } |
| if (!ctVariant) { |
| return nullptr; |
| } |
| |
| return SkTypeface_Mac::Make(std::move(ctVariant), ctVariation.opsz, |
| fStream ? fStream->duplicate() : nullptr); |
| } |
| |
| int SkTypeface_Mac::onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[], |
| int parameterCount) const |
| { |
| CFArrayRef ctAxes = this->getVariationAxes(); |
| if (!ctAxes) { |
| return -1; |
| } |
| CFIndex axisCount = CFArrayGetCount(ctAxes); |
| |
| if (!parameters || parameterCount < axisCount) { |
| return axisCount; |
| } |
| |
| // Added in 10.13 |
| static CFStringRef* kCTFontVariationAxisHiddenKeyPtr = |
| static_cast<CFStringRef*>(dlsym(RTLD_DEFAULT, "kCTFontVariationAxisHiddenKey")); |
| |
| for (int i = 0; i < axisCount; ++i) { |
| CFDictionaryRef axisInfoDict; |
| if (!SkCFDynamicCast(CFArrayGetValueAtIndex(ctAxes, i), &axisInfoDict, "Axis")) { |
| return -1; |
| } |
| |
| int64_t tagLong; |
| CFTypeRef tag = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisIdentifierKey); |
| if (!SkCFNumberDynamicCast(tag, &tagLong, nullptr, "Axis tag")) { |
| return -1; |
| } |
| |
| double minDouble; |
| double maxDouble; |
| double defDouble; |
| CFTypeRef min = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMinimumValueKey); |
| CFTypeRef max = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMaximumValueKey); |
| CFTypeRef def = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisDefaultValueKey); |
| if (!SkCFNumberDynamicCast(min, &minDouble, nullptr, "Axis min") || |
| !SkCFNumberDynamicCast(max, &maxDouble, nullptr, "Axis max") || |
| !SkCFNumberDynamicCast(def, &defDouble, nullptr, "Axis def")) |
| { |
| return -1; |
| } |
| |
| SkFontParameters::Variation::Axis& skAxis = parameters[i]; |
| skAxis.tag = tagLong; |
| skAxis.min = minDouble; |
| skAxis.max = maxDouble; |
| skAxis.def = defDouble; |
| skAxis.setHidden(false); |
| if (kCTFontVariationAxisHiddenKeyPtr) { |
| CFTypeRef hidden = CFDictionaryGetValue(axisInfoDict,*kCTFontVariationAxisHiddenKeyPtr); |
| if (hidden) { |
| // At least macOS 11 Big Sur Beta 4 uses CFNumberRef instead of CFBooleanRef. |
| // https://crbug.com/1113444 |
| CFBooleanRef hiddenBoolean; |
| int hiddenInt; |
| if (SkCFDynamicCast(hidden, &hiddenBoolean, nullptr)) { |
| skAxis.setHidden(CFBooleanGetValue(hiddenBoolean)); |
| } else if (SkCFNumberDynamicCast(hidden, &hiddenInt, nullptr, "Axis hidden")) { |
| skAxis.setHidden(hiddenInt); |
| } else { |
| return -1; |
| } |
| } |
| } |
| } |
| return axisCount; |
| } |
| |
| #endif |