Split out coretext fontmgr factory.

There can be only one SkFontMgr::Factory, so each should go in its own
translation unit. In addition, the name SkFontHost_mac is now rather out
of date and should now be SkFontMgr_mac_ct. Since the additional burden
of additional build changes after the first is minimal, also split out
SkTypeface_mac_ct and SkScalerContext_mac_ct. The original
SkFontHost_mac.cpp is kept as a shell which #includes the cpp files
which are replacing it. Once references to it are removed from all
builds it can be removed.

This is intended to be a reorganization without much code change. Most
changes are simple renaming of functions which are now shared between
translation units. However, there are a few behavior changes here.
  * Drop SkTypefaceCache global for SkTypeface_Mac 'local' global.
  * SkCTFontCTWidthForCSSWidth returns CGFloat instead of 'int'.
  * SkFontMgr_New_CoreText takes a CTFontCollectionRef.

Change-Id: Iac548f9fd920c426ea5c6dcdefe8da0a9b89ec90
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294456
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294704
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294714
Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
Ben Wagner 2020-06-05 11:25:42 -04:00 committed by Skia Commit-Bot
parent bb5b2a17c2
commit 0ef90dd5e9
15 changed files with 3576 additions and 3219 deletions

View File

@ -378,7 +378,16 @@ optional("fontmgr_fuchsia") {
optional("fontmgr_mac") {
enabled = skia_use_fonthost_mac
sources = [ "src/ports/SkFontHost_mac.cpp" ]
public = [ "include/ports/SkFontMgr_mac_ct.h" ]
sources = [
#"src/ports/SkFontHost_mac.cpp",
"src/ports/SkFontMgr_mac_ct.cpp",
"src/ports/SkFontMgr_mac_ct_factory.cpp",
"src/ports/SkScalerContext_mac_ct.cpp",
"src/ports/SkScalerContext_mac_ct.h",
"src/ports/SkTypeface_mac_ct.cpp",
"src/ports/SkTypeface_mac_ct.h",
]
if (is_mac) {
libs = [

View File

@ -81,6 +81,10 @@ skia_utils_sources = [
"$_src/utils/SkWhitelistTypefaces.cpp",
#mac
"$_src/utils/mac/SkCGBase.h",
"$_src/utils/mac/SkCGGeometry.h",
"$_src/utils/mac/SkCTFontSmoothBehavior.cpp",
"$_src/utils/mac/SkCTFontSmoothBehavior.h",
"$_src/utils/mac/SkCreateCGImageRef.cpp",
"$_src/utils/mac/SkUniqueCFRef.h",

View File

@ -0,0 +1,27 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkFontMgr_mac_ct_DEFINED
#define SkFontMgr_mac_ct_DEFINED
#include "include/core/SkRefCnt.h"
#include "include/core/SkTypes.h"
#ifdef SK_BUILD_FOR_MAC
#import <ApplicationServices/ApplicationServices.h>
#endif
#ifdef SK_BUILD_FOR_IOS
#include <CoreText/CoreText.h>
#endif
class SkFontMgr;
/** Create a font manager for CoreText. If the collection is nullptr the system default will be used. */
SK_API extern sk_sp<SkFontMgr> SkFontMgr_New_CoreText(CTFontCollectionRef);
#endif // SkFontMgr_mac_ct_DEFINED

View File

@ -347,6 +347,7 @@ PORTS_SRCS_IOS = struct(
"src/ports/*mozalloc*",
"src/ports/*nacl*",
"src/ports/*win*",
"src/ports/SkFontHost_mac.cpp",
"src/ports/SkFontMgr_custom.cpp",
"src/ports/SkFontMgr_custom_directory.cpp",
"src/ports/SkFontMgr_custom_embedded.cpp",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,669 @@
/*
* 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/SkData.h"
#include "include/core/SkFontArguments.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypeface.h"
#include "include/ports/SkFontMgr_mac_ct.h"
#include "include/private/SkFixed.h"
#include "include/private/SkOnce.h"
#include "include/private/SkTemplates.h"
#include "include/private/SkTo.h"
#include "src/core/SkFontDescriptor.h"
#include "src/ports/SkTypeface_mac_ct.h"
#include "src/utils/SkUTF.h"
#include <string.h>
#include <memory>
static SkUniqueCFRef<CFStringRef> make_CFString(const char s[]) {
return SkUniqueCFRef<CFStringRef>(CFStringCreateWithCString(nullptr, s, kCFStringEncodingUTF8));
}
/** Creates a typeface from a descriptor, searching the cache. */
static sk_sp<SkTypeface> create_from_desc(CTFontDescriptorRef desc) {
SkUniqueCFRef<CTFontRef> ctFont(CTFontCreateWithFontDescriptor(desc, 0, nullptr));
if (!ctFont) {
return nullptr;
}
return SkTypeface_Mac::Make(std::move(ctFont), OpszVariation(), nullptr);
}
static SkUniqueCFRef<CTFontDescriptorRef> create_descriptor(const char familyName[],
const SkFontStyle& style) {
SkUniqueCFRef<CFMutableDictionaryRef> cfAttributes(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
SkUniqueCFRef<CFMutableDictionaryRef> cfTraits(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
if (!cfAttributes || !cfTraits) {
return nullptr;
}
// TODO(crbug.com/1018581) Some CoreText versions have errant behavior when
// certain traits set. Temporary workaround to omit specifying trait for
// those versions.
// Long term solution will involve serializing typefaces instead of relying
// upon this to match between processes.
//
// Compare CoreText.h in an up to date SDK for where these values come from.
static const uint32_t kSkiaLocalCTVersionNumber10_14 = 0x000B0000;
static const uint32_t kSkiaLocalCTVersionNumber10_15 = 0x000C0000;
// CTFontTraits (symbolic)
// macOS 14 and iOS 12 seem to behave badly when kCTFontSymbolicTrait is set.
// macOS 15 yields LastResort font instead of a good default font when
// kCTFontSymbolicTrait is set.
if (!(&CTGetCoreTextVersion && CTGetCoreTextVersion() >= kSkiaLocalCTVersionNumber10_14)) {
CTFontSymbolicTraits ctFontTraits = 0;
if (style.weight() >= SkFontStyle::kBold_Weight) {
ctFontTraits |= kCTFontBoldTrait;
}
if (style.slant() != SkFontStyle::kUpright_Slant) {
ctFontTraits |= kCTFontItalicTrait;
}
SkUniqueCFRef<CFNumberRef> cfFontTraits(
CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctFontTraits));
if (cfFontTraits) {
CFDictionaryAddValue(cfTraits.get(), kCTFontSymbolicTrait, cfFontTraits.get());
}
}
// CTFontTraits (weight)
CGFloat ctWeight = SkCTFontCTWeightForCSSWeight(style.weight());
SkUniqueCFRef<CFNumberRef> cfFontWeight(
CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &ctWeight));
if (cfFontWeight) {
CFDictionaryAddValue(cfTraits.get(), kCTFontWeightTrait, cfFontWeight.get());
}
// CTFontTraits (width)
CGFloat ctWidth = SkCTFontCTWidthForCSSWidth(style.width());
SkUniqueCFRef<CFNumberRef> cfFontWidth(
CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &ctWidth));
if (cfFontWidth) {
CFDictionaryAddValue(cfTraits.get(), kCTFontWidthTrait, cfFontWidth.get());
}
// CTFontTraits (slant)
// macOS 15 behaves badly when kCTFontSlantTrait is set.
if (!(&CTGetCoreTextVersion && CTGetCoreTextVersion() == kSkiaLocalCTVersionNumber10_15)) {
CGFloat ctSlant = style.slant() == SkFontStyle::kUpright_Slant ? 0 : 1;
SkUniqueCFRef<CFNumberRef> cfFontSlant(
CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &ctSlant));
if (cfFontSlant) {
CFDictionaryAddValue(cfTraits.get(), kCTFontSlantTrait, cfFontSlant.get());
}
}
// CTFontTraits
CFDictionaryAddValue(cfAttributes.get(), kCTFontTraitsAttribute, cfTraits.get());
// CTFontFamilyName
if (familyName) {
SkUniqueCFRef<CFStringRef> cfFontName = make_CFString(familyName);
if (cfFontName) {
CFDictionaryAddValue(cfAttributes.get(), kCTFontFamilyNameAttribute, cfFontName.get());
}
}
return SkUniqueCFRef<CTFontDescriptorRef>(
CTFontDescriptorCreateWithAttributes(cfAttributes.get()));
}
// Same as the above function except style is included so we can
// compare whether the created font conforms to the style. If not, we need
// to recreate the font with symbolic traits. This is needed due to MacOS 10.11
// font creation problem https://bugs.chromium.org/p/skia/issues/detail?id=8447.
static sk_sp<SkTypeface> create_from_desc_and_style(CTFontDescriptorRef desc,
const SkFontStyle& style) {
SkUniqueCFRef<CTFontRef> ctFont(CTFontCreateWithFontDescriptor(desc, 0, nullptr));
if (!ctFont) {
return nullptr;
}
const CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctFont.get());
CTFontSymbolicTraits expected_traits = traits;
if (style.slant() != SkFontStyle::kUpright_Slant) {
expected_traits |= kCTFontItalicTrait;
}
if (style.weight() >= SkFontStyle::kBold_Weight) {
expected_traits |= kCTFontBoldTrait;
}
if (expected_traits != traits) {
SkUniqueCFRef<CTFontRef> ctNewFont(CTFontCreateCopyWithSymbolicTraits(
ctFont.get(), 0, nullptr, expected_traits, expected_traits));
if (ctNewFont) {
ctFont = std::move(ctNewFont);
}
}
return SkTypeface_Mac::Make(std::move(ctFont), OpszVariation(), nullptr);
}
/** Creates a typeface from a name, searching the cache. */
static sk_sp<SkTypeface> create_from_name(const char familyName[], const SkFontStyle& style) {
SkUniqueCFRef<CTFontDescriptorRef> desc = create_descriptor(familyName, style);
if (!desc) {
return nullptr;
}
return create_from_desc_and_style(desc.get(), style);
}
static const char* map_css_names(const char* name) {
static const struct {
const char* fFrom; // name the caller specified
const char* fTo; // "canonical" name we map to
} gPairs[] = {
{ "sans-serif", "Helvetica" },
{ "serif", "Times" },
{ "monospace", "Courier" }
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) {
if (strcmp(name, gPairs[i].fFrom) == 0) {
return gPairs[i].fTo;
}
}
return name; // no change
}
namespace {
/** Creates a dictionary suitable for setting the axes on a CTFont. */
static CTFontVariation ctvariation_from_skfontdata(CTFontRef ct, SkFontData* fontData) {
// In macOS 10.15 CTFontCreate* overrides any 'opsz' variation with the 'size'.
// Track the 'opsz' and return it, since it is an out of band axis.
OpszVariation opsz;
constexpr const SkFourByteTag opszTag = SkSetFourByteTag('o','p','s','z');
SkUniqueCFRef<CFArrayRef> ctAxes(CTFontCopyVariationAxes(ct));
if (!ctAxes) {
return CTFontVariation();
}
CFIndex axisCount = CFArrayGetCount(ctAxes.get());
if (0 == axisCount || axisCount != fontData->getAxisCount()) {
return CTFontVariation();
}
SkUniqueCFRef<CFMutableDictionaryRef> dict(
CFDictionaryCreateMutable(kCFAllocatorDefault, axisCount,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
for (int i = 0; i < fontData->getAxisCount(); ++i) {
CFTypeRef axisInfo = CFArrayGetValueAtIndex(ctAxes.get(), i);
if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
return CTFontVariation();
}
CFDictionaryRef axisInfoDict = static_cast<CFDictionaryRef>(axisInfo);
CFTypeRef tag = CFDictionaryGetValue(axisInfoDict,
kCTFontVariationAxisIdentifierKey);
if (!tag || CFGetTypeID(tag) != CFNumberGetTypeID()) {
return CTFontVariation();
}
CFNumberRef tagNumber = static_cast<CFNumberRef>(tag);
int64_t tagLong;
if (!CFNumberGetValue(tagNumber, kCFNumberSInt64Type, &tagLong)) {
return CTFontVariation();
}
// The variation axes can be set to any value, but cg will effectively pin them.
// Pin them here to normalize.
CFTypeRef min = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMinimumValueKey);
CFTypeRef max = CFDictionaryGetValue(axisInfoDict, kCTFontVariationAxisMaximumValueKey);
if (!min || CFGetTypeID(min) != CFNumberGetTypeID() ||
!max || CFGetTypeID(max) != CFNumberGetTypeID())
{
return CTFontVariation();
}
CFNumberRef minNumber = static_cast<CFNumberRef>(min);
CFNumberRef maxNumber = static_cast<CFNumberRef>(max);
double minDouble;
double maxDouble;
if (!CFNumberGetValue(minNumber, kCFNumberDoubleType, &minDouble) ||
!CFNumberGetValue(maxNumber, kCFNumberDoubleType, &maxDouble))
{
return CTFontVariation();
}
double value = SkTPin(SkFixedToDouble(fontData->getAxis()[i]), minDouble, maxDouble);
if (tagLong == opszTag) {
opsz.isSet = true;
opsz.value = value;
}
SkUniqueCFRef<CFNumberRef> valueNumber(
CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
CFDictionaryAddValue(dict.get(), tagNumber, valueNumber.get());
}
return { SkUniqueCFRef<CFDictionaryRef>(std::move(dict)), opsz };
}
static sk_sp<SkData> skdata_from_skstreamasset(std::unique_ptr<SkStreamAsset> stream) {
size_t size = stream->getLength();
if (const void* base = stream->getMemoryBase()) {
return SkData::MakeWithProc(base, size,
[](const void*, void* ctx) -> void {
delete (SkStreamAsset*)ctx;
}, stream.release());
}
return SkData::MakeFromStream(stream.get(), size);
}
static SkUniqueCFRef<CFDataRef> cfdata_from_skdata(sk_sp<SkData> data) {
void const * const addr = data->data();
size_t const size = data->size();
CFAllocatorContext ctx = {
0, // CFIndex version
data.release(), // void* info
nullptr, // const void *(*retain)(const void *info);
nullptr, // void (*release)(const void *info);
nullptr, // CFStringRef (*copyDescription)(const void *info);
nullptr, // void * (*allocate)(CFIndex size, CFOptionFlags hint, void *info);
nullptr, // void*(*reallocate)(void* ptr,CFIndex newsize,CFOptionFlags hint,void* info);
[](void*,void* info) -> void { // void (*deallocate)(void *ptr, void *info);
SkASSERT(info);
((SkData*)info)->unref();
},
nullptr, // CFIndex (*preferredSize)(CFIndex size, CFOptionFlags hint, void *info);
};
SkUniqueCFRef<CFAllocatorRef> alloc(CFAllocatorCreate(kCFAllocatorDefault, &ctx));
return SkUniqueCFRef<CFDataRef>(CFDataCreateWithBytesNoCopy(
kCFAllocatorDefault, (const UInt8 *)addr, size, alloc.get()));
}
static SkUniqueCFRef<CTFontRef> ctfont_from_skdata(sk_sp<SkData> data, int ttcIndex) {
// TODO: Use CTFontManagerCreateFontDescriptorsFromData when available.
if (ttcIndex != 0) {
return nullptr;
}
SkUniqueCFRef<CFDataRef> cfData(cfdata_from_skdata(std::move(data)));
SkUniqueCFRef<CTFontDescriptorRef> desc(
CTFontManagerCreateFontDescriptorFromData(cfData.get()));
if (!desc) {
return nullptr;
}
return SkUniqueCFRef<CTFontRef>(CTFontCreateWithFontDescriptor(desc.get(), 0, nullptr));
}
static bool find_desc_str(CTFontDescriptorRef desc, CFStringRef name, SkString* value) {
SkUniqueCFRef<CFStringRef> ref((CFStringRef)CTFontDescriptorCopyAttribute(desc, name));
if (!ref) {
return false;
}
SkStringFromCFString(ref.get(), value);
return true;
}
static inline int sqr(int value) {
SkASSERT(SkAbs32(value) < 0x7FFF); // check for overflow
return value * value;
}
// We normalize each axis (weight, width, italic) to be base-900
static int compute_metric(const SkFontStyle& a, const SkFontStyle& b) {
return sqr(a.weight() - b.weight()) +
sqr((a.width() - b.width()) * 100) +
sqr((a.slant() != b.slant()) * 900);
}
class SkFontStyleSet_Mac : public SkFontStyleSet {
public:
SkFontStyleSet_Mac(CTFontDescriptorRef desc)
: fArray(CTFontDescriptorCreateMatchingFontDescriptors(desc, nullptr))
, fCount(0)
{
if (!fArray) {
fArray.reset(CFArrayCreate(nullptr, nullptr, 0, nullptr));
}
fCount = SkToInt(CFArrayGetCount(fArray.get()));
}
int count() override {
return fCount;
}
void getStyle(int index, SkFontStyle* style, SkString* name) override {
SkASSERT((unsigned)index < (unsigned)fCount);
CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray.get(), index);
if (style) {
*style = SkCTFontDescriptorGetSkFontStyle(desc, false);
}
if (name) {
if (!find_desc_str(desc, kCTFontStyleNameAttribute, name)) {
name->reset();
}
}
}
SkTypeface* createTypeface(int index) override {
SkASSERT((unsigned)index < (unsigned)CFArrayGetCount(fArray.get()));
CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray.get(), index);
return create_from_desc(desc).release();
}
SkTypeface* matchStyle(const SkFontStyle& pattern) override {
if (0 == fCount) {
return nullptr;
}
return create_from_desc(findMatchingDesc(pattern)).release();
}
private:
SkUniqueCFRef<CFArrayRef> fArray;
int fCount;
CTFontDescriptorRef findMatchingDesc(const SkFontStyle& pattern) const {
int bestMetric = SK_MaxS32;
CTFontDescriptorRef bestDesc = nullptr;
for (int i = 0; i < fCount; ++i) {
CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray.get(), i);
int metric = compute_metric(pattern, SkCTFontDescriptorGetSkFontStyle(desc, false));
if (0 == metric) {
return desc;
}
if (metric < bestMetric) {
bestMetric = metric;
bestDesc = desc;
}
}
SkASSERT(bestDesc);
return bestDesc;
}
};
} // namespace
class SkFontMgr_Mac : public SkFontMgr {
SkUniqueCFRef<CFArrayRef> fNames;
int fCount;
CFStringRef getFamilyNameAt(int index) const {
SkASSERT((unsigned)index < (unsigned)fCount);
return (CFStringRef)CFArrayGetValueAtIndex(fNames.get(), index);
}
static SkFontStyleSet* CreateSet(CFStringRef cfFamilyName) {
SkUniqueCFRef<CFMutableDictionaryRef> cfAttr(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionaryAddValue(cfAttr.get(), kCTFontFamilyNameAttribute, cfFamilyName);
SkUniqueCFRef<CTFontDescriptorRef> desc(
CTFontDescriptorCreateWithAttributes(cfAttr.get()));
return new SkFontStyleSet_Mac(desc.get());
}
/** CTFontManagerCopyAvailableFontFamilyNames() is not always available, so we
* provide a wrapper here that will return an empty array if need be.
*/
static SkUniqueCFRef<CFArrayRef> CopyAvailableFontFamilyNames() {
#ifdef SK_BUILD_FOR_IOS
return SkUniqueCFRef<CFArrayRef>(CFArrayCreate(nullptr, nullptr, 0, nullptr));
#else
return SkUniqueCFRef<CFArrayRef>(CTFontManagerCopyAvailableFontFamilyNames());
#endif
}
public:
SkUniqueCFRef<CTFontCollectionRef> fFontCollection;
SkFontMgr_Mac(CTFontCollectionRef fontCollection)
: fNames(CopyAvailableFontFamilyNames())
, fCount(fNames ? SkToInt(CFArrayGetCount(fNames.get())) : 0)
, fFontCollection(fontCollection ? (CTFontCollectionRef)CFRetain(fontCollection)
: CTFontCollectionCreateFromAvailableFonts(nullptr))
{}
protected:
int onCountFamilies() const override {
return fCount;
}
void onGetFamilyName(int index, SkString* familyName) const override {
if ((unsigned)index < (unsigned)fCount) {
SkStringFromCFString(this->getFamilyNameAt(index), familyName);
} else {
familyName->reset();
}
}
SkFontStyleSet* onCreateStyleSet(int index) const override {
if ((unsigned)index >= (unsigned)fCount) {
return nullptr;
}
return CreateSet(this->getFamilyNameAt(index));
}
SkFontStyleSet* onMatchFamily(const char familyName[]) const override {
if (!familyName) {
return nullptr;
}
SkUniqueCFRef<CFStringRef> cfName = make_CFString(familyName);
return CreateSet(cfName.get());
}
SkTypeface* onMatchFamilyStyle(const char familyName[],
const SkFontStyle& style) const override {
SkUniqueCFRef<CTFontDescriptorRef> desc = create_descriptor(familyName, style);
return create_from_desc(desc.get()).release();
}
SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
const SkFontStyle& style,
const char* bcp47[], int bcp47Count,
SkUnichar character) const override {
SkUniqueCFRef<CTFontDescriptorRef> desc = create_descriptor(familyName, style);
SkUniqueCFRef<CTFontRef> familyFont(CTFontCreateWithFontDescriptor(desc.get(), 0, nullptr));
// kCFStringEncodingUTF32 is BE unless there is a BOM.
// Since there is no machine endian option, explicitly state machine endian.
#ifdef SK_CPU_LENDIAN
constexpr CFStringEncoding encoding = kCFStringEncodingUTF32LE;
#else
constexpr CFStringEncoding encoding = kCFStringEncodingUTF32BE;
#endif
SkUniqueCFRef<CFStringRef> string(CFStringCreateWithBytes(
kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(&character), sizeof(character),
encoding, false));
// If 0xD800 <= codepoint <= 0xDFFF || 0x10FFFF < codepoint 'string' may be nullptr.
// No font should be covering such codepoints (even the magic fallback font).
if (!string) {
return nullptr;
}
CFRange range = CFRangeMake(0, CFStringGetLength(string.get())); // in UniChar units.
SkUniqueCFRef<CTFontRef> fallbackFont(
CTFontCreateForString(familyFont.get(), string.get(), range));
return SkTypeface_Mac::Make(std::move(fallbackFont), OpszVariation(), nullptr).release();
}
SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember,
const SkFontStyle&) const override {
return nullptr;
}
sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData> data, int ttcIndex) const override {
if (ttcIndex != 0) {
return nullptr;
}
SkUniqueCFRef<CTFontRef> ct = ctfont_from_skdata(data, ttcIndex);
if (!ct) {
return nullptr;
}
return SkTypeface_Mac::Make(std::move(ct), OpszVariation(),
SkMemoryStream::Make(std::move(data)));
}
sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset> stream,
int ttcIndex) const override {
if (ttcIndex != 0) {
return nullptr;
}
sk_sp<SkData> data = skdata_from_skstreamasset(stream->duplicate());
if (!data) {
return nullptr;
}
SkUniqueCFRef<CTFontRef> ct = ctfont_from_skdata(std::move(data), ttcIndex);
if (!ct) {
return nullptr;
}
return SkTypeface_Mac::Make(std::move(ct), OpszVariation(), std::move(stream));
}
sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset> stream,
const SkFontArguments& args) const override
{
// TODO: Use CTFontManagerCreateFontDescriptorsFromData when available.
int ttcIndex = args.getCollectionIndex();
if (ttcIndex != 0) {
return nullptr;
}
sk_sp<SkData> data = skdata_from_skstreamasset(stream->duplicate());
if (!data) {
return nullptr;
}
SkUniqueCFRef<CTFontRef> ct = ctfont_from_skdata(std::move(data), ttcIndex);
if (!ct) {
return nullptr;
}
CTFontVariation ctVariation = SkCTVariationFromSkFontArguments(ct.get(), args);
SkUniqueCFRef<CTFontRef> ctVariant;
if (ctVariation.dict) {
SkUniqueCFRef<CFMutableDictionaryRef> attributes(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionaryAddValue(attributes.get(),
kCTFontVariationAttribute, ctVariation.dict.get());
SkUniqueCFRef<CTFontDescriptorRef> varDesc(
CTFontDescriptorCreateWithAttributes(attributes.get()));
ctVariant.reset(CTFontCreateCopyWithAttributes(ct.get(), 0, nullptr, varDesc.get()));
} else {
ctVariant.reset(ct.release());
}
if (!ctVariant) {
return nullptr;
}
return SkTypeface_Mac::Make(std::move(ctVariant), ctVariation.opsz, std::move(stream));
}
sk_sp<SkTypeface> onMakeFromFontData(std::unique_ptr<SkFontData> fontData) const override {
// TODO: Use CTFontManagerCreateFontDescriptorsFromData when available.
if (fontData->getIndex() != 0) {
return nullptr;
}
sk_sp<SkData> data = skdata_from_skstreamasset(fontData->getStream()->duplicate());
if (!data) {
return nullptr;
}
SkUniqueCFRef<CTFontRef> ct = ctfont_from_skdata(std::move(data), fontData->getIndex());
if (!ct) {
return nullptr;
}
CTFontVariation ctVariation = ctvariation_from_skfontdata(ct.get(), fontData.get());
SkUniqueCFRef<CTFontRef> ctVariant;
if (ctVariation.dict) {
SkUniqueCFRef<CFMutableDictionaryRef> attributes(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionaryAddValue(attributes.get(),
kCTFontVariationAttribute, ctVariation.dict.get());
SkUniqueCFRef<CTFontDescriptorRef> varDesc(
CTFontDescriptorCreateWithAttributes(attributes.get()));
ctVariant.reset(CTFontCreateCopyWithAttributes(ct.get(), 0, nullptr, varDesc.get()));
} else {
ctVariant.reset(ct.release());
}
if (!ctVariant) {
return nullptr;
}
return SkTypeface_Mac::Make(std::move(ctVariant), ctVariation.opsz,
fontData->detachStream());
}
sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
if (ttcIndex != 0) {
return nullptr;
}
sk_sp<SkData> data = SkData::MakeFromFileName(path);
if (!data) {
return nullptr;
}
return this->onMakeFromData(std::move(data), ttcIndex);
}
sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const override {
if (familyName) {
familyName = map_css_names(familyName);
}
sk_sp<SkTypeface> face = create_from_name(familyName, style);
if (face) {
return face;
}
static SkTypeface* gDefaultFace;
static SkOnce lookupDefault;
static const char FONT_DEFAULT_NAME[] = "Lucida Sans";
lookupDefault([]{
gDefaultFace = create_from_name(FONT_DEFAULT_NAME, SkFontStyle()).release();
});
return sk_ref_sp(gDefaultFace);
}
};
sk_sp<SkFontMgr> SkFontMgr_New_CoreText(CTFontCollectionRef fontCollection) {
return sk_make_sp<SkFontMgr_Mac>(fontCollection);
}
#endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)

View File

@ -0,0 +1,18 @@
/*
* Copyright 2017 Google Inc.
*
* 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)
#include "include/core/SkFontMgr.h"
#include "include/ports/SkFontMgr_mac_ct.h"
sk_sp<SkFontMgr> SkFontMgr::Factory() {
return SkFontMgr_New_CoreText(nullptr);
}
#endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)

View File

@ -0,0 +1,721 @@
/*
* 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/SkColorPriv.h"
#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypeface.h"
#include "include/private/SkColorData.h"
#include "include/private/SkFixed.h"
#include "include/private/SkTemplates.h"
#include "include/private/SkTo.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkEndian.h"
#include "src/core/SkGlyph.h"
#include "src/core/SkMask.h"
#include "src/core/SkMaskGamma.h"
#include "src/core/SkMathPriv.h"
#include "src/core/SkUtils.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/utils/mac/SkCGBase.h"
#include "src/utils/mac/SkCGGeometry.h"
#include "src/utils/mac/SkCTFontSmoothBehavior.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#include <algorithm>
class SkDescriptor;
// Set to make glyph bounding boxes visible.
#define SK_SHOW_TEXT_BLIT_COVERAGE 0
static void sk_memset_rect32(uint32_t* ptr, uint32_t value,
int width, int height, size_t rowBytes) {
SkASSERT(width);
SkASSERT(width * sizeof(uint32_t) <= rowBytes);
if (width >= 32) {
while (height) {
sk_memset32(ptr, value, width);
ptr = (uint32_t*)((char*)ptr + rowBytes);
height -= 1;
}
return;
}
rowBytes -= width * sizeof(uint32_t);
if (width >= 8) {
while (height) {
int w = width;
do {
*ptr++ = value; *ptr++ = value;
*ptr++ = value; *ptr++ = value;
*ptr++ = value; *ptr++ = value;
*ptr++ = value; *ptr++ = value;
w -= 8;
} while (w >= 8);
while (--w >= 0) {
*ptr++ = value;
}
ptr = (uint32_t*)((char*)ptr + rowBytes);
height -= 1;
}
} else {
while (height) {
int w = width;
do {
*ptr++ = value;
} while (--w > 0);
ptr = (uint32_t*)((char*)ptr + rowBytes);
height -= 1;
}
}
}
static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) {
return pixel & 0xFF;
}
static CGAffineTransform MatrixToCGAffineTransform(const SkMatrix& matrix) {
return CGAffineTransformMake( SkScalarToCGFloat(matrix[SkMatrix::kMScaleX]),
-SkScalarToCGFloat(matrix[SkMatrix::kMSkewY] ),
-SkScalarToCGFloat(matrix[SkMatrix::kMSkewX] ),
SkScalarToCGFloat(matrix[SkMatrix::kMScaleY]),
SkScalarToCGFloat(matrix[SkMatrix::kMTransX]),
SkScalarToCGFloat(matrix[SkMatrix::kMTransY]));
}
SkScalerContext_Mac::SkScalerContext_Mac(sk_sp<SkTypeface_Mac> typeface,
const SkScalerContextEffects& effects,
const SkDescriptor* desc)
: INHERITED(std::move(typeface), effects, desc)
, fDoSubPosition(SkToBool(fRec.fFlags & kSubpixelPositioning_Flag))
{
CTFontRef ctFont = (CTFontRef)this->getTypeface()->internal_private_getCTFontRef();
CFIndex numGlyphs = CTFontGetGlyphCount(ctFont);
SkASSERT(numGlyphs >= 1 && numGlyphs <= 0xFFFF);
fGlyphCount = SkToU16(numGlyphs);
// CT on (at least) 10.9 will size color glyphs down from the requested size, but not up.
// As a result, it is necessary to know the actual device size and request that.
SkVector scale;
SkMatrix skTransform;
bool invertible = fRec.computeMatrices(SkScalerContextRec::kVertical_PreMatrixScale,
&scale, &skTransform, nullptr, nullptr, nullptr);
fTransform = MatrixToCGAffineTransform(skTransform);
// CGAffineTransformInvert documents that if the transform is non-invertible it will return the
// passed transform unchanged. It does so, but then also prints a message to stdout. Avoid this.
if (invertible) {
fInvTransform = CGAffineTransformInvert(fTransform);
} else {
fInvTransform = fTransform;
}
// The transform contains everything except the requested text size.
// Some properties, like 'trak', are based on the optical text size.
CGFloat textSize = SkScalarToCGFloat(scale.y());
fCTFont = SkCTFontCreateExactCopy(ctFont, textSize,
((SkTypeface_Mac*)this->getTypeface())->fOpszVariation);
fCGFont.reset(CTFontCopyGraphicsFont(fCTFont.get(), nullptr));
}
static int RoundSize(int dimension) {
return SkNextPow2(dimension);
}
CGRGBPixel* SkScalerContext_Mac::Offscreen::getCG(const SkScalerContext_Mac& context,
const SkGlyph& glyph, CGGlyph glyphID,
size_t* rowBytesPtr,
bool generateA8FromLCD) {
if (!fRGBSpace) {
//It doesn't appear to matter what color space is specified.
//Regular blends and antialiased text are always (s*a + d*(1-a))
//and subpixel antialiased text is always g=2.0.
fRGBSpace.reset(CGColorSpaceCreateDeviceRGB());
}
// default to kBW_Format
bool doAA = false;
bool doLCD = false;
if (SkMask::kBW_Format != glyph.maskFormat()) {
doLCD = true;
doAA = true;
}
// FIXME: lcd smoothed un-hinted rasterization unsupported.
if (!generateA8FromLCD && SkMask::kA8_Format == glyph.maskFormat()) {
doLCD = false;
doAA = true;
}
// If this font might have color glyphs, disable LCD as there's no way to support it.
// CoreText doesn't tell us which format it ended up using, so we can't detect it.
// A8 will end up black on transparent, but TODO: we can detect gray and set to A8.
if (SkMask::kARGB32_Format == glyph.maskFormat()) {
doLCD = false;
}
size_t rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
if (!fCG || fSize.fWidth < glyph.width() || fSize.fHeight < glyph.height()) {
if (fSize.fWidth < glyph.width()) {
fSize.fWidth = RoundSize(glyph.width());
}
if (fSize.fHeight < glyph.height()) {
fSize.fHeight = RoundSize(glyph.height());
}
rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
void* image = fImageStorage.reset(rowBytes * fSize.fHeight);
const CGImageAlphaInfo alpha = (glyph.isColor())
? kCGImageAlphaPremultipliedFirst
: kCGImageAlphaNoneSkipFirst;
const CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | alpha;
fCG.reset(CGBitmapContextCreate(image, fSize.fWidth, fSize.fHeight, 8,
rowBytes, fRGBSpace.get(), bitmapInfo));
// Skia handles quantization and subpixel positioning,
// so disable quantization and enable subpixel positioning in CG.
CGContextSetAllowsFontSubpixelQuantization(fCG.get(), false);
CGContextSetShouldSubpixelQuantizeFonts(fCG.get(), false);
// Because CG always draws from the horizontal baseline,
// if there is a non-integral translation from the horizontal origin to the vertical origin,
// then CG cannot draw the glyph in the correct location without subpixel positioning.
CGContextSetAllowsFontSubpixelPositioning(fCG.get(), true);
CGContextSetShouldSubpixelPositionFonts(fCG.get(), true);
CGContextSetTextDrawingMode(fCG.get(), kCGTextFill);
// Draw black on white to create mask. (Special path exists to speed this up in CG.)
CGContextSetGrayFillColor(fCG.get(), 0.0f, 1.0f);
// force our checks below to happen
fDoAA = !doAA;
fDoLCD = !doLCD;
CGContextSetTextMatrix(fCG.get(), context.fTransform);
}
if (fDoAA != doAA) {
CGContextSetShouldAntialias(fCG.get(), doAA);
fDoAA = doAA;
}
if (fDoLCD != doLCD) {
CGContextSetShouldSmoothFonts(fCG.get(), doLCD);
fDoLCD = doLCD;
}
CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get();
// skip rows based on the glyph's height
image += (fSize.fHeight - glyph.height()) * fSize.fWidth;
// Erase to white (or transparent black if it's a color glyph, to not composite against white).
uint32_t bgColor = (!glyph.isColor()) ? 0xFFFFFFFF : 0x00000000;
sk_memset_rect32(image, bgColor, glyph.width(), glyph.height(), rowBytes);
float subX = 0;
float subY = 0;
if (context.fDoSubPosition) {
subX = SkFixedToFloat(glyph.getSubXFixed());
subY = SkFixedToFloat(glyph.getSubYFixed());
}
CGPoint point = CGPointMake(-glyph.left() + subX, glyph.top() + glyph.height() - subY);
// Prior to 10.10, CTFontDrawGlyphs acted like CGContextShowGlyphsAtPositions and took
// 'positions' which are in text space. The glyph location (in device space) must be
// mapped into text space, so that CG can convert it back into device space.
// In 10.10.1, this is handled directly in CTFontDrawGlyphs.
//
// However, in 10.10.2 color glyphs no longer rotate based on the font transform.
// So always make the font transform identity and place the transform on the context.
point = CGPointApplyAffineTransform(point, context.fInvTransform);
CTFontDrawGlyphs(context.fCTFont.get(), &glyphID, &point, 1, fCG.get());
SkASSERT(rowBytesPtr);
*rowBytesPtr = rowBytes;
return image;
}
unsigned SkScalerContext_Mac::generateGlyphCount(void) {
return fGlyphCount;
}
bool SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) {
return false;
}
void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
glyph->fMaskFormat = fRec.fMaskFormat;
const CGGlyph cgGlyph = (CGGlyph) glyph->getGlyphID();
glyph->zeroMetrics();
// The following block produces cgAdvance in CG units (pixels, y up).
CGSize cgAdvance;
CTFontGetAdvancesForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
&cgGlyph, &cgAdvance, 1);
cgAdvance = CGSizeApplyAffineTransform(cgAdvance, fTransform);
glyph->fAdvanceX = SkFloatFromCGFloat(cgAdvance.width);
glyph->fAdvanceY = -SkFloatFromCGFloat(cgAdvance.height);
// The following produces skBounds in SkGlyph units (pixels, y down),
// or returns early if skBounds would be empty.
SkRect skBounds;
// Glyphs are always drawn from the horizontal origin. The caller must manually use the result
// of CTFontGetVerticalTranslationsForGlyphs to calculate where to draw the glyph for vertical
// glyphs. As a result, always get the horizontal bounds of a glyph and translate it if the
// glyph is vertical. This avoids any diagreement between the various means of retrieving
// vertical metrics.
{
// CTFontGetBoundingRectsForGlyphs produces cgBounds in CG units (pixels, y up).
CGRect cgBounds;
CTFontGetBoundingRectsForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
&cgGlyph, &cgBounds, 1);
cgBounds = CGRectApplyAffineTransform(cgBounds, fTransform);
// BUG?
// 0x200B (zero-advance space) seems to return a huge (garbage) bounds, when
// it should be empty. So, if we see a zero-advance, we check if it has an
// empty path or not, and if so, we jam the bounds to 0. Hopefully a zero-advance
// is rare, so we won't incur a big performance cost for this extra check.
if (0 == cgAdvance.width && 0 == cgAdvance.height) {
SkUniqueCFRef<CGPathRef> path(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph,nullptr));
if (!path || CGPathIsEmpty(path.get())) {
return;
}
}
if (SkCGRectIsEmpty(cgBounds)) {
return;
}
// Convert cgBounds to SkGlyph units (pixels, y down).
skBounds = SkRect::MakeXYWH(cgBounds.origin.x, -cgBounds.origin.y - cgBounds.size.height,
cgBounds.size.width, cgBounds.size.height);
}
// Currently the bounds are based on being rendered at (0,0).
// The top left must not move, since that is the base from which subpixel positioning is offset.
if (fDoSubPosition) {
skBounds.fRight += SkFixedToFloat(glyph->getSubXFixed());
skBounds.fBottom += SkFixedToFloat(glyph->getSubYFixed());
}
// We're trying to pack left and top into int16_t,
// and width and height into uint16_t, after outsetting by 1.
if (!SkRect::MakeXYWH(-32767, -32767, 65535, 65535).contains(skBounds)) {
return;
}
SkIRect skIBounds;
skBounds.roundOut(&skIBounds);
// Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
// Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
// is not currently known, as CG dilates the outlines by some percentage.
// Note that if this context is A8 and not back-forming from LCD, there is no need to outset.
skIBounds.outset(1, 1);
glyph->fLeft = SkToS16(skIBounds.fLeft);
glyph->fTop = SkToS16(skIBounds.fTop);
glyph->fWidth = SkToU16(skIBounds.width());
glyph->fHeight = SkToU16(skIBounds.height());
}
static constexpr uint8_t sk_pow2_table(size_t i) {
return SkToU8(((i * i + 128) / 255));
}
/**
* This will invert the gamma applied by CoreGraphics, so we can get linear
* values.
*
* CoreGraphics obscurely defaults to 2.0 as the subpixel coverage gamma value.
* The color space used does not appear to affect this choice.
*/
static constexpr auto gLinearCoverageFromCGLCDValue = SkMakeArray<256>(sk_pow2_table);
static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) {
while (count > 0) {
uint8_t mask = 0;
for (int i = 7; i >= 0; --i) {
mask |= ((CGRGBPixel_getAlpha(*src++) >> 7) ^ 0x1) << i;
if (0 == --count) {
break;
}
}
*dst++ = mask;
}
}
template<bool APPLY_PREBLEND>
static inline uint8_t rgb_to_a8(CGRGBPixel rgb, const uint8_t* table8) {
U8CPU r = 0xFF - ((rgb >> 16) & 0xFF);
U8CPU g = 0xFF - ((rgb >> 8) & 0xFF);
U8CPU b = 0xFF - ((rgb >> 0) & 0xFF);
U8CPU lum = sk_apply_lut_if<APPLY_PREBLEND>(SkComputeLuminance(r, g, b), table8);
#if SK_SHOW_TEXT_BLIT_COVERAGE
lum = std::max(lum, (U8CPU)0x30);
#endif
return lum;
}
template<bool APPLY_PREBLEND>
static void RGBToA8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
const SkGlyph& glyph, void* glyphImage, const uint8_t* table8) {
const int width = glyph.width();
const int height = glyph.height();
size_t dstRB = glyph.rowBytes();
uint8_t* SK_RESTRICT dst = (uint8_t*)glyphImage;
for (int y = 0; y < height; y++) {
for (int i = 0; i < width; ++i) {
dst[i] = rgb_to_a8<APPLY_PREBLEND>(cgPixels[i], table8);
}
cgPixels = SkTAddOffset<const CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<uint8_t>(dst, dstRB);
}
}
template<bool APPLY_PREBLEND>
static uint16_t RGBToLcd16(CGRGBPixel rgb,
const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >> 16) & 0xFF), tableR);
U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >> 8) & 0xFF), tableG);
U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >> 0) & 0xFF), tableB);
#if SK_SHOW_TEXT_BLIT_COVERAGE
r = std::max(r, (U8CPU)0x30);
g = std::max(g, (U8CPU)0x30);
b = std::max(b, (U8CPU)0x30);
#endif
return SkPack888ToRGB16(r, g, b);
}
template<bool APPLY_PREBLEND>
static void RGBToLcd16(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
const SkGlyph& glyph, void* glyphImage,
const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
const int width = glyph.width();
const int height = glyph.height();
size_t dstRB = glyph.rowBytes();
uint16_t* SK_RESTRICT dst = (uint16_t*)glyphImage;
for (int y = 0; y < height; y++) {
for (int i = 0; i < width; i++) {
dst[i] = RGBToLcd16<APPLY_PREBLEND>(cgPixels[i], tableR, tableG, tableB);
}
cgPixels = SkTAddOffset<const CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<uint16_t>(dst, dstRB);
}
}
static SkPMColor cgpixels_to_pmcolor(CGRGBPixel rgb) {
U8CPU a = (rgb >> 24) & 0xFF;
U8CPU r = (rgb >> 16) & 0xFF;
U8CPU g = (rgb >> 8) & 0xFF;
U8CPU b = (rgb >> 0) & 0xFF;
#if SK_SHOW_TEXT_BLIT_COVERAGE
a = std::max(a, (U8CPU)0x30);
#endif
return SkPackARGB32(a, r, g, b);
}
void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) {
CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
// FIXME: lcd smoothed un-hinted rasterization unsupported.
bool requestSmooth = fRec.getHinting() != SkFontHinting::kNone;
// Draw the glyph
size_t cgRowBytes;
CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth);
if (cgPixels == nullptr) {
return;
}
// Fix the glyph
if ((glyph.fMaskFormat == SkMask::kLCD16_Format) ||
(glyph.fMaskFormat == SkMask::kA8_Format
&& requestSmooth
&& SkCTFontGetSmoothBehavior() != SkCTFontSmoothBehavior::none))
{
const uint8_t* linear = gLinearCoverageFromCGLCDValue.data();
//Note that the following cannot really be integrated into the
//pre-blend, since we may not be applying the pre-blend; when we aren't
//applying the pre-blend it means that a filter wants linear anyway.
//Other code may also be applying the pre-blend, so we'd need another
//one with this and one without.
CGRGBPixel* addr = cgPixels;
for (int y = 0; y < glyph.fHeight; ++y) {
for (int x = 0; x < glyph.fWidth; ++x) {
int r = (addr[x] >> 16) & 0xFF;
int g = (addr[x] >> 8) & 0xFF;
int b = (addr[x] >> 0) & 0xFF;
addr[x] = (linear[r] << 16) | (linear[g] << 8) | linear[b];
}
addr = SkTAddOffset<CGRGBPixel>(addr, cgRowBytes);
}
}
// Convert glyph to mask
switch (glyph.fMaskFormat) {
case SkMask::kLCD16_Format: {
if (fPreBlend.isApplicable()) {
RGBToLcd16<true>(cgPixels, cgRowBytes, glyph, glyph.fImage,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
RGBToLcd16<false>(cgPixels, cgRowBytes, glyph, glyph.fImage,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} break;
case SkMask::kA8_Format: {
if (fPreBlend.isApplicable()) {
RGBToA8<true>(cgPixels, cgRowBytes, glyph, glyph.fImage, fPreBlend.fG);
} else {
RGBToA8<false>(cgPixels, cgRowBytes, glyph, glyph.fImage, fPreBlend.fG);
}
} break;
case SkMask::kBW_Format: {
const int width = glyph.fWidth;
size_t dstRB = glyph.rowBytes();
uint8_t* dst = (uint8_t*)glyph.fImage;
for (int y = 0; y < glyph.fHeight; y++) {
cgpixels_to_bits(dst, cgPixels, width);
cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<uint8_t>(dst, dstRB);
}
} break;
case SkMask::kARGB32_Format: {
const int width = glyph.fWidth;
size_t dstRB = glyph.rowBytes();
SkPMColor* dst = (SkPMColor*)glyph.fImage;
for (int y = 0; y < glyph.fHeight; y++) {
for (int x = 0; x < width; ++x) {
dst[x] = cgpixels_to_pmcolor(cgPixels[x]);
}
cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<SkPMColor>(dst, dstRB);
}
} break;
default:
SkDEBUGFAIL("unexpected mask format");
break;
}
}
namespace {
class SkCTPathGeometrySink {
SkPath* fPath;
bool fStarted;
CGPoint fCurrent;
void goingTo(const CGPoint pt) {
if (!fStarted) {
fStarted = true;
fPath->moveTo(fCurrent.x, -fCurrent.y);
}
fCurrent = pt;
}
bool currentIsNot(const CGPoint pt) {
return fCurrent.x != pt.x || fCurrent.y != pt.y;
}
public:
SkCTPathGeometrySink(SkPath* path) : fPath{path}, fStarted{false}, fCurrent{0,0} {}
static void ApplyElement(void *ctx, const CGPathElement *element) {
SkCTPathGeometrySink& self = *(SkCTPathGeometrySink*)ctx;
CGPoint* points = element->points;
switch (element->type) {
case kCGPathElementMoveToPoint:
self.fStarted = false;
self.fCurrent = points[0];
break;
case kCGPathElementAddLineToPoint:
if (self.currentIsNot(points[0])) {
self.goingTo(points[0]);
self.fPath->lineTo(points[0].x, -points[0].y);
}
break;
case kCGPathElementAddQuadCurveToPoint:
if (self.currentIsNot(points[0]) || self.currentIsNot(points[1])) {
self.goingTo(points[1]);
self.fPath->quadTo(points[0].x, -points[0].y,
points[1].x, -points[1].y);
}
break;
case kCGPathElementAddCurveToPoint:
if (self.currentIsNot(points[0]) ||
self.currentIsNot(points[1]) ||
self.currentIsNot(points[2]))
{
self.goingTo(points[2]);
self.fPath->cubicTo(points[0].x, -points[0].y,
points[1].x, -points[1].y,
points[2].x, -points[2].y);
}
break;
case kCGPathElementCloseSubpath:
if (self.fStarted) {
self.fPath->close();
}
break;
default:
SkDEBUGFAIL("Unknown path element!");
break;
}
}
};
} // namespace
/*
* Our subpixel resolution is only 2 bits in each direction, so a scale of 4
* seems sufficient, and possibly even correct, to allow the hinted outline
* to be subpixel positioned.
*/
#define kScaleForSubPixelPositionHinting (4.0f)
bool SkScalerContext_Mac::generatePath(SkGlyphID glyph, SkPath* path) {
SkScalar scaleX = SK_Scalar1;
SkScalar scaleY = SK_Scalar1;
CGAffineTransform xform = fTransform;
/*
* For subpixel positioning, we want to return an unhinted outline, so it
* can be positioned nicely at fractional offsets. However, we special-case
* if the baseline of the (horizontal) text is axis-aligned. In those cases
* we want to retain hinting in the direction orthogonal to the baseline.
* e.g. for horizontal baseline, we want to retain hinting in Y.
* The way we remove hinting is to scale the font by some value (4) in that
* direction, ask for the path, and then scale the path back down.
*/
if (fDoSubPosition) {
// start out by assuming that we want no hining in X and Y
scaleX = scaleY = kScaleForSubPixelPositionHinting;
// now see if we need to restore hinting for axis-aligned baselines
switch (this->computeAxisAlignmentForHText()) {
case kX_SkAxisAlignment:
scaleY = SK_Scalar1; // want hinting in the Y direction
break;
case kY_SkAxisAlignment:
scaleX = SK_Scalar1; // want hinting in the X direction
break;
default:
break;
}
CGAffineTransform scale(CGAffineTransformMakeScale(SkScalarToCGFloat(scaleX),
SkScalarToCGFloat(scaleY)));
xform = CGAffineTransformConcat(fTransform, scale);
}
CGGlyph cgGlyph = SkTo<CGGlyph>(glyph);
SkUniqueCFRef<CGPathRef> cgPath(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph, &xform));
path->reset();
if (!cgPath) {
return false;
}
SkCTPathGeometrySink sink(path);
CGPathApply(cgPath.get(), &sink, SkCTPathGeometrySink::ApplyElement);
if (fDoSubPosition) {
SkMatrix m;
m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY));
path->transform(m);
}
return true;
}
void SkScalerContext_Mac::generateFontMetrics(SkFontMetrics* metrics) {
if (nullptr == metrics) {
return;
}
CGRect theBounds = CTFontGetBoundingBox(fCTFont.get());
metrics->fTop = SkScalarFromCGFloat(-SkCGRectGetMaxY(theBounds));
metrics->fAscent = SkScalarFromCGFloat(-CTFontGetAscent(fCTFont.get()));
metrics->fDescent = SkScalarFromCGFloat( CTFontGetDescent(fCTFont.get()));
metrics->fBottom = SkScalarFromCGFloat(-SkCGRectGetMinY(theBounds));
metrics->fLeading = SkScalarFromCGFloat( CTFontGetLeading(fCTFont.get()));
metrics->fAvgCharWidth = SkScalarFromCGFloat( SkCGRectGetWidth(theBounds));
metrics->fXMin = SkScalarFromCGFloat( SkCGRectGetMinX(theBounds));
metrics->fXMax = SkScalarFromCGFloat( SkCGRectGetMaxX(theBounds));
metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin;
metrics->fXHeight = SkScalarFromCGFloat( CTFontGetXHeight(fCTFont.get()));
metrics->fCapHeight = SkScalarFromCGFloat( CTFontGetCapHeight(fCTFont.get()));
metrics->fUnderlineThickness = SkScalarFromCGFloat( CTFontGetUnderlineThickness(fCTFont.get()));
metrics->fUnderlinePosition = -SkScalarFromCGFloat( CTFontGetUnderlinePosition(fCTFont.get()));
metrics->fFlags = 0;
metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag;
metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag;
// See https://bugs.chromium.org/p/skia/issues/detail?id=6203
// At least on 10.12.3 with memory based fonts the x-height is always 0.6666 of the ascent and
// the cap-height is always 0.8888 of the ascent. It appears that the values from the 'OS/2'
// table are read, but then overwritten if the font is not a system font. As a result, if there
// is a valid 'OS/2' table available use the values from the table if they aren't too strange.
struct OS2HeightMetrics {
SK_OT_SHORT sxHeight;
SK_OT_SHORT sCapHeight;
} heights;
size_t bytesRead = this->getTypeface()->getTableData(
SkTEndian_SwapBE32(SkOTTableOS2::TAG), offsetof(SkOTTableOS2, version.v2.sxHeight),
sizeof(heights), &heights);
if (bytesRead == sizeof(heights)) {
// 'fontSize' is correct because the entire resolved size is set by the constructor.
CGFloat fontSize = CTFontGetSize(this->fCTFont.get());
unsigned upem = CTFontGetUnitsPerEm(this->fCTFont.get());
unsigned maxSaneHeight = upem * 2;
uint16_t xHeight = SkEndian_SwapBE16(heights.sxHeight);
if (xHeight && xHeight < maxSaneHeight) {
metrics->fXHeight = SkScalarFromCGFloat(xHeight * fontSize / upem);
}
uint16_t capHeight = SkEndian_SwapBE16(heights.sCapHeight);
if (capHeight && capHeight < maxSaneHeight) {
metrics->fCapHeight = SkScalarFromCGFloat(capHeight * fontSize / upem);
}
}
}
#endif

View File

@ -0,0 +1,118 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkScalerContext_mac_ct_DEFINED
#define SkScalerContext_mac_ct_DEFINED
#include "include/core/SkTypes.h"
#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
#include "include/core/SkRefCnt.h"
#include "include/core/SkSize.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkScalerContext.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#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 <memory>
class SkDescriptor;
class SkGlyph;
class SkPath;
class SkTypeface_Mac;
struct SkFontMetrics;
typedef uint32_t CGRGBPixel;
class SkScalerContext_Mac : public SkScalerContext {
public:
SkScalerContext_Mac(sk_sp<SkTypeface_Mac>, const SkScalerContextEffects&, const SkDescriptor*);
protected:
unsigned generateGlyphCount(void) override;
bool generateAdvance(SkGlyph* glyph) override;
void generateMetrics(SkGlyph* glyph) override;
void generateImage(const SkGlyph& glyph) override;
bool generatePath(SkGlyphID glyph, SkPath* path) override;
void generateFontMetrics(SkFontMetrics*) override;
private:
class Offscreen {
public:
Offscreen()
: fRGBSpace(nullptr)
, fCG(nullptr)
, fDoAA(false)
, fDoLCD(false)
{
fSize.set(0, 0);
}
CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
CGGlyph glyphID, size_t* rowBytesPtr, bool generateA8FromLCD);
private:
enum {
kSize = 32 * 32 * sizeof(CGRGBPixel)
};
SkAutoSMalloc<kSize> fImageStorage;
SkUniqueCFRef<CGColorSpaceRef> fRGBSpace;
// cached state
SkUniqueCFRef<CGContextRef> fCG;
SkISize fSize;
bool fDoAA;
bool fDoLCD;
};
Offscreen fOffscreen;
/** Unrotated variant of fCTFont.
*
* In 10.10.1 CTFontGetAdvancesForGlyphs applies the font transform to the width of the
* advances, but always sets the height to 0. This font is used to get the advances of the
* unrotated glyph, and then the rotation is applied separately.
*
* CT vertical metrics are pre-rotated (in em space, before transform) 90deg clock-wise.
* This makes kCTFontOrientationDefault dangerous, because the metrics from
* kCTFontOrientationHorizontal are in a different space from kCTFontOrientationVertical.
* With kCTFontOrientationVertical the advances must be unrotated.
*
* Sometimes, creating a copy of a CTFont with the same size but different trasform will select
* different underlying font data. As a result, avoid ever creating more than one CTFont per
* SkScalerContext to ensure that only one CTFont is used.
*
* As a result of the above (and other constraints) this font contains the size, but not the
* transform. The transform must always be applied separately.
*/
SkUniqueCFRef<CTFontRef> fCTFont;
/** The transform without the font size. */
CGAffineTransform fTransform;
CGAffineTransform fInvTransform;
SkUniqueCFRef<CGFontRef> fCGFont;
uint16_t fGlyphCount;
const bool fDoSubPosition;
friend class Offscreen;
typedef SkScalerContext INHERITED;
};
#endif
#endif //SkScalerContext_mac_ct_DEFINED

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkTypeface_mac_ct_DEFINED
#define SkTypeface_mac_ct_DEFINED
#include "include/core/SkTypes.h"
#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
#include "include/core/SkFontArguments.h"
#include "include/core/SkFontParameters.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkStream.h"
#include "include/core/SkTypeface.h"
#include "include/private/SkOnce.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#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 <memory>
class SkData;
class SkDescriptor;
class SkFontData;
class SkFontDescriptor;
class SkScalerContext;
class SkString;
struct SkAdvancedTypefaceMetrics;
struct SkScalerContextEffects;
struct SkScalerContextRec;
struct OpszVariation {
bool isSet = false;
double value = 0;
};
struct CTFontVariation {
SkUniqueCFRef<CFDictionaryRef> dict;
OpszVariation opsz;
};
CTFontVariation SkCTVariationFromSkFontArguments(CTFontRef ct, const SkFontArguments& args);
SkUniqueCFRef<CTFontRef> SkCTFontCreateExactCopy(CTFontRef baseFont, CGFloat textSize,
OpszVariation opsz);
SkFontStyle SkCTFontDescriptorGetSkFontStyle(CTFontDescriptorRef desc, bool fromDataProvider);
CGFloat SkCTFontCTWeightForCSSWeight(int fontstyleWeight);
CGFloat SkCTFontCTWidthForCSSWidth(int fontstyleWidth);
void SkStringFromCFString(CFStringRef src, SkString* dst);
class SkTypeface_Mac : public SkTypeface {
private:
SkTypeface_Mac(SkUniqueCFRef<CTFontRef> fontRef, const SkFontStyle& fs, bool isFixedPitch,
OpszVariation opszVariation, std::unique_ptr<SkStreamAsset> providedData)
: SkTypeface(fs, isFixedPitch)
, fFontRef(std::move(fontRef))
, fOpszVariation(opszVariation)
, fHasColorGlyphs(
SkToBool(CTFontGetSymbolicTraits(fFontRef.get()) & kCTFontColorGlyphsTrait))
, fStream(std::move(providedData))
, fIsFromStream(fStream)
{
SkASSERT(fFontRef);
}
public:
static sk_sp<SkTypeface> Make(SkUniqueCFRef<CTFontRef> font,
OpszVariation opszVariation,
std::unique_ptr<SkStreamAsset> providedData);
SkUniqueCFRef<CTFontRef> fFontRef;
const OpszVariation fOpszVariation;
const bool fHasColorGlyphs;
protected:
int onGetUPEM() const override;
std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override;
std::unique_ptr<SkFontData> onMakeFontData() const override;
int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
int coordinateCount) const override;
void onGetFamilyName(SkString* familyName) const override;
SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
int onGetTableTags(SkFontTableTag tags[]) const override;
size_t onGetTableData(SkFontTableTag, size_t offset, size_t length, void* data) const override;
sk_sp<SkData> onCopyTableData(SkFontTableTag) const override;
SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
const SkDescriptor*) const override;
void onFilterRec(SkScalerContextRec*) const override;
void onGetFontDescriptor(SkFontDescriptor*, bool*) const override;
void getGlyphToUnicodeMap(SkUnichar*) const override;
std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
void onCharsToGlyphs(const SkUnichar* chars, int count, SkGlyphID glyphs[]) const override;
int onCountGlyphs() const override;
void getPostScriptGlyphNames(SkString*) const override {}
int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
int parameterCount) const override;
sk_sp<SkTypeface> onMakeClone(const SkFontArguments&) const override;
void* onGetCTFontRef() const override { return (void*)fFontRef.get(); }
private:
mutable std::unique_ptr<SkStreamAsset> fStream;
bool fIsFromStream;
mutable SkOnce fInitStream;
typedef SkTypeface INHERITED;
};
#endif
#endif //SkTypeface_mac_ct_DEFINED

52
src/utils/mac/SkCGBase.h Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkCGBase_DEFINED
#define SkCGBase_DEFINED
#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 <CoreGraphics/CoreGraphics.h>
#endif
// Skia extensions for types in CGBase.h
static inline CGFloat SkScalarToCGFloat(SkScalar scalar) {
if (sizeof(CGFloat) == sizeof(float)) {
return SkScalarToFloat(scalar);
} else {
SkASSERT(sizeof(CGFloat) == sizeof(double));
return (CGFloat) SkScalarToDouble(scalar);
}
}
static inline SkScalar SkScalarFromCGFloat(CGFloat cgFloat) {
if (sizeof(CGFloat) == sizeof(float)) {
return SkFloatToScalar(cgFloat);
} else {
SkASSERT(sizeof(CGFloat) == sizeof(double));
return SkDoubleToScalar(cgFloat);
}
}
static inline float SkFloatFromCGFloat(CGFloat cgFloat) {
if (sizeof(CGFloat) == sizeof(float)) {
return cgFloat;
} else {
SkASSERT(sizeof(CGFloat) == sizeof(double));
return static_cast<float>(cgFloat);
}
}
#endif
#endif //SkCGBase_DEFINED

View File

@ -0,0 +1,52 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkCGGeometry_DEFINED
#define SkCGGeometry_DEFINED
#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 <CoreGraphics/CoreGraphics.h>
#endif
// Skia extensions for types in CGGeometry.h
// Inline versions of these CGRect helpers.
// The CG versions require making a call and a copy of the CGRect on the stack.
static inline bool SkCGRectIsEmpty(const CGRect& rect) {
return rect.size.width <= 0 || rect.size.height <= 0;
}
static inline CGFloat SkCGRectGetMinX(const CGRect& rect) {
return rect.origin.x;
}
static inline CGFloat SkCGRectGetMaxX(const CGRect& rect) {
return rect.origin.x + rect.size.width;
}
static inline CGFloat SkCGRectGetMinY(const CGRect& rect) {
return rect.origin.y;
}
static inline CGFloat SkCGRectGetMaxY(const CGRect& rect) {
return rect.origin.y + rect.size.height;
}
static inline CGFloat SkCGRectGetWidth(const CGRect& rect) {
return rect.size.width;
}
#endif
#endif //SkCGGeometry_DEFINED

View File

@ -0,0 +1,270 @@
/*
* Copyright 2020 Google Inc.
*
* 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)
#include "src/utils/mac/SkCTFontSmoothBehavior.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#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
#define BITMAP_INFO_RGB (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
/** Drawn in FontForge, reduced with fonttools ttx, converted by xxd -i,
* this TrueType font contains a glyph of the spider.
*
* To re-forge the original bytes of the TrueType font file,
* remove all ',|( +0x)' from this definition,
* copy the data to the clipboard,
* run 'pbpaste | xxd -p -r - spider.ttf'.
*/
static constexpr const uint8_t kSpiderSymbol_ttf[] = {
0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x80, 0x00, 0x03, 0x00, 0x40,
0x47, 0x44, 0x45, 0x46, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x07, 0xa8,
0x00, 0x00, 0x00, 0x18, 0x4f, 0x53, 0x2f, 0x32, 0x8a, 0xf4, 0xfb, 0xdb,
0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6d, 0x61, 0x70,
0xe0, 0x7f, 0x10, 0x7e, 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x54,
0x67, 0x61, 0x73, 0x70, 0xff, 0xff, 0x00, 0x03, 0x00, 0x00, 0x07, 0xa0,
0x00, 0x00, 0x00, 0x08, 0x67, 0x6c, 0x79, 0x66, 0x97, 0x0b, 0x6a, 0xf6,
0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x03, 0x40, 0x68, 0x65, 0x61, 0x64,
0x0f, 0xa2, 0x24, 0x1a, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x36,
0x68, 0x68, 0x65, 0x61, 0x0e, 0xd3, 0x07, 0x3f, 0x00, 0x00, 0x01, 0x04,
0x00, 0x00, 0x00, 0x24, 0x68, 0x6d, 0x74, 0x78, 0x10, 0x03, 0x00, 0x44,
0x00, 0x00, 0x01, 0xa8, 0x00, 0x00, 0x00, 0x0e, 0x6c, 0x6f, 0x63, 0x61,
0x01, 0xb4, 0x00, 0x28, 0x00, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x0a,
0x6d, 0x61, 0x78, 0x70, 0x00, 0x4a, 0x01, 0x4d, 0x00, 0x00, 0x01, 0x28,
0x00, 0x00, 0x00, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0xc3, 0xe5, 0x39, 0xd4,
0x00, 0x00, 0x05, 0x58, 0x00, 0x00, 0x02, 0x28, 0x70, 0x6f, 0x73, 0x74,
0xff, 0x03, 0x00, 0x67, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x20,
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x0f, 0x08, 0x1d,
0x5f, 0x0f, 0x3c, 0xf5, 0x00, 0x0b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
0xd1, 0x97, 0xa8, 0x5a, 0x00, 0x00, 0x00, 0x00, 0xd6, 0xe8, 0x32, 0x33,
0x00, 0x03, 0xff, 0x3b, 0x08, 0x00, 0x05, 0x55, 0x00, 0x00, 0x00, 0x08,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x05, 0x55, 0xff, 0x3b, 0x01, 0x79, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00,
0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00,
0x00, 0x04, 0x01, 0x1c, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x2e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x01, 0x90, 0x00, 0x05,
0x00, 0x00, 0x05, 0x33, 0x05, 0x99, 0x00, 0x00, 0x01, 0x1e, 0x05, 0x33,
0x05, 0x99, 0x00, 0x00, 0x03, 0xd7, 0x00, 0x66, 0x02, 0x12, 0x00, 0x00,
0x05, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x73, 0x6b, 0x69, 0x61, 0x00, 0xc0, 0x00, 0x00, 0xf0, 0x21,
0x06, 0x66, 0xfe, 0x66, 0x01, 0x79, 0x05, 0x55, 0x00, 0xc5, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x20, 0x00, 0x01, 0x08, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x48,
0x00, 0x00, 0x00, 0x0e, 0x00, 0x08, 0x00, 0x02, 0x00, 0x06, 0x00, 0x00,
0x00, 0x09, 0x00, 0x0d, 0x00, 0x1d, 0x00, 0x21, 0xf0, 0x21, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d, 0x00, 0x1d, 0x00, 0x21,
0xf0, 0x21, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf9, 0xff, 0xf5, 0xff, 0xe4,
0xff, 0xe2, 0x0f, 0xe2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
0x00, 0x14, 0x00, 0x14, 0x01, 0xa0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x44,
0x00, 0x00, 0x02, 0x64, 0x05, 0x55, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00,
0x33, 0x11, 0x21, 0x11, 0x25, 0x21, 0x11, 0x21, 0x44, 0x02, 0x20, 0xfe,
0x24, 0x01, 0x98, 0xfe, 0x68, 0x05, 0x55, 0xfa, 0xab, 0x44, 0x04, 0xcd,
0x00, 0x04, 0x00, 0x03, 0xff, 0x3b, 0x08, 0x00, 0x05, 0x4c, 0x00, 0x15,
0x00, 0x1d, 0x00, 0x25, 0x01, 0x1b, 0x00, 0x00, 0x01, 0x36, 0x37, 0x36,
0x27, 0x26, 0x07, 0x06, 0x06, 0x23, 0x22, 0x27, 0x26, 0x27, 0x26, 0x07,
0x06, 0x17, 0x16, 0x17, 0x16, 0x32, 0x37, 0x32, 0x35, 0x34, 0x23, 0x22,
0x15, 0x14, 0x27, 0x32, 0x35, 0x34, 0x23, 0x22, 0x15, 0x14, 0x03, 0x32,
0x17, 0x30, 0x17, 0x31, 0x36, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x33,
0x32, 0x33, 0x16, 0x33, 0x32, 0x17, 0x16, 0x07, 0x06, 0x23, 0x22, 0x27,
0x26, 0x27, 0x26, 0x23, 0x22, 0x07, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06,
0x1f, 0x02, 0x37, 0x36, 0x37, 0x36, 0x33, 0x32, 0x17, 0x17, 0x16, 0x33,
0x16, 0x17, 0x16, 0x07, 0x06, 0x23, 0x22, 0x27, 0x27, 0x26, 0x23, 0x22,
0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x33, 0x32, 0x33, 0x32,
0x37, 0x36, 0x37, 0x36, 0x17, 0x16, 0x1f, 0x02, 0x16, 0x17, 0x16, 0x15,
0x14, 0x23, 0x22, 0x27, 0x27, 0x26, 0x27, 0x27, 0x26, 0x27, 0x26, 0x07,
0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x15, 0x14, 0x07, 0x06, 0x07,
0x06, 0x23, 0x22, 0x27, 0x26, 0x07, 0x06, 0x07, 0x06, 0x15, 0x14, 0x17,
0x16, 0x17, 0x16, 0x15, 0x14, 0x07, 0x06, 0x23, 0x22, 0x27, 0x26, 0x27,
0x26, 0x35, 0x34, 0x37, 0x36, 0x37, 0x36, 0x37, 0x34, 0x27, 0x26, 0x07,
0x06, 0x07, 0x06, 0x0f, 0x02, 0x06, 0x23, 0x22, 0x27, 0x26, 0x35, 0x34,
0x37, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27,
0x26, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x07, 0x06, 0x23, 0x22,
0x27, 0x26, 0x35, 0x34, 0x37, 0x36, 0x37, 0x37, 0x36, 0x37, 0x37, 0x36,
0x37, 0x36, 0x37, 0x36, 0x35, 0x34, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26,
0x23, 0x22, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x27, 0x26, 0x27, 0x26,
0x27, 0x26, 0x35, 0x34, 0x37, 0x36, 0x37, 0x36, 0x37, 0x36, 0x33, 0x32,
0x17, 0x16, 0x33, 0x32, 0x37, 0x36, 0x35, 0x34, 0x37, 0x36, 0x37, 0x36,
0x33, 0x04, 0xf5, 0x23, 0x13, 0x11, 0x14, 0x16, 0x1d, 0x1b, 0x4c, 0x1f,
0x0e, 0x2d, 0x23, 0x14, 0x2c, 0x13, 0x18, 0x25, 0x2c, 0x10, 0x3c, 0x71,
0x1d, 0x5c, 0x5c, 0x3f, 0xae, 0x5c, 0x5c, 0x3f, 0x6a, 0x27, 0x31, 0x5b,
0x09, 0x27, 0x36, 0x03, 0x0a, 0x26, 0x35, 0x2e, 0x09, 0x08, 0xc6, 0x13,
0x81, 0x17, 0x20, 0x18, 0x21, 0x1e, 0x04, 0x04, 0x15, 0x5c, 0x22, 0x26,
0x48, 0x56, 0x3b, 0x10, 0x21, 0x01, 0x0c, 0x06, 0x06, 0x0f, 0x31, 0x44,
0x3c, 0x52, 0x4a, 0x1d, 0x11, 0x3f, 0xb4, 0x71, 0x01, 0x26, 0x06, 0x0d,
0x15, 0x1a, 0x2a, 0x13, 0x53, 0xaa, 0x42, 0x1d, 0x0a, 0x33, 0x20, 0x21,
0x2b, 0x01, 0x02, 0x3e, 0x21, 0x09, 0x02, 0x02, 0x0f, 0x2d, 0x4b, 0x0a,
0x22, 0x15, 0x20, 0x1f, 0x72, 0x8b, 0x2d, 0x2f, 0x1d, 0x1f, 0x0e, 0x25,
0x3f, 0x4d, 0x1b, 0x63, 0x2a, 0x2c, 0x14, 0x22, 0x18, 0x1c, 0x0f, 0x08,
0x2a, 0x08, 0x08, 0x0d, 0x3b, 0x4c, 0x52, 0x74, 0x27, 0x71, 0x2e, 0x01,
0x0c, 0x10, 0x15, 0x0d, 0x06, 0x0d, 0x05, 0x01, 0x06, 0x2c, 0x28, 0x14,
0x1b, 0x05, 0x04, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x16, 0x27, 0x03, 0x0d,
0x30, 0x4c, 0x4c, 0x4b, 0x1f, 0x0b, 0x22, 0x26, 0x0d, 0x15, 0x0d, 0x2d,
0x68, 0x34, 0x14, 0x3c, 0x25, 0x12, 0x04, 0x10, 0x18, 0x0b, 0x09, 0x30,
0x2b, 0x44, 0x66, 0x14, 0x47, 0x47, 0x59, 0x73, 0x25, 0x05, 0x03, 0x1f,
0x01, 0x08, 0x3f, 0x48, 0x4b, 0x4b, 0x76, 0x2f, 0x49, 0x2d, 0x22, 0x24,
0x0c, 0x15, 0x08, 0x0e, 0x33, 0x03, 0x44, 0x4c, 0x10, 0x46, 0x13, 0x1f,
0x27, 0x1b, 0x1d, 0x13, 0x02, 0x24, 0x08, 0x02, 0x42, 0x0e, 0x4d, 0x3c,
0x19, 0x1b, 0x40, 0x2b, 0x2b, 0x1e, 0x16, 0x11, 0x04, 0x1f, 0x11, 0x04,
0x18, 0x11, 0x35, 0x01, 0xa3, 0x13, 0x24, 0x1f, 0x0b, 0x0c, 0x19, 0x19,
0x18, 0x13, 0x0f, 0x0c, 0x1a, 0x18, 0x1f, 0x19, 0x1e, 0x07, 0x1a, 0xc3,
0x54, 0x51, 0x54, 0x51, 0x04, 0x53, 0x51, 0x54, 0x50, 0x02, 0x48, 0x1a,
0x31, 0x18, 0x55, 0x74, 0x04, 0x0e, 0x09, 0x0d, 0x06, 0x10, 0x16, 0x1b,
0x24, 0x01, 0x04, 0x0b, 0x04, 0x10, 0x3f, 0x0a, 0x41, 0x02, 0x41, 0x20,
0x06, 0x12, 0x16, 0x21, 0x17, 0x2a, 0x1e, 0x15, 0x40, 0x27, 0x11, 0x0e,
0x1e, 0x11, 0x15, 0x1f, 0x43, 0x13, 0x1a, 0x10, 0x15, 0x1b, 0x04, 0x09,
0x4d, 0x2a, 0x0f, 0x19, 0x0a, 0x0a, 0x03, 0x05, 0x15, 0x3c, 0x64, 0x21,
0x4b, 0x2e, 0x21, 0x28, 0x13, 0x47, 0x44, 0x19, 0x3f, 0x11, 0x18, 0x0b,
0x0a, 0x07, 0x18, 0x0d, 0x07, 0x24, 0x2c, 0x2b, 0x21, 0x32, 0x10, 0x48,
0x2a, 0x2d, 0x1e, 0x1a, 0x01, 0x0c, 0x43, 0x59, 0x28, 0x4e, 0x1c, 0x0d,
0x5d, 0x24, 0x14, 0x0a, 0x05, 0x1f, 0x24, 0x32, 0x46, 0x3e, 0x5f, 0x3e,
0x44, 0x1a, 0x30, 0x15, 0x0d, 0x07, 0x18, 0x2b, 0x03, 0x0d, 0x1a, 0x28,
0x28, 0x57, 0xb2, 0x29, 0x27, 0x40, 0x2c, 0x23, 0x16, 0x63, 0x58, 0x1a,
0x0a, 0x18, 0x11, 0x23, 0x08, 0x1b, 0x29, 0x05, 0x04, 0x0b, 0x15, 0x0d,
0x14, 0x0b, 0x2a, 0x29, 0x5a, 0x62, 0x01, 0x19, 0x1e, 0x05, 0x05, 0x26,
0x42, 0x42, 0x2a, 0x2a, 0x3f, 0x0d, 0x0f, 0x09, 0x05, 0x07, 0x01, 0x0b,
0x25, 0x3e, 0x0d, 0x17, 0x11, 0x01, 0x03, 0x0d, 0x13, 0x20, 0x19, 0x11,
0x03, 0x02, 0x01, 0x04, 0x11, 0x04, 0x05, 0x1b, 0x3d, 0x10, 0x29, 0x20,
0x04, 0x04, 0x0a, 0x07, 0x04, 0x1f, 0x15, 0x20, 0x3e, 0x0f, 0x2a, 0x1e,
0x00, 0x00, 0x00, 0x1b, 0x01, 0x4a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x07, 0x00, 0x27, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0x00, 0x02, 0x00, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x06, 0x00, 0x0c, 0x00, 0x1b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0d, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0e, 0x00, 0x1a, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x00, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x01, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x02, 0x00, 0x0e, 0x00, 0x98, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x03, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x04, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x05, 0x00, 0x04, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x06, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x0d, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x00, 0x04, 0x09,
0x00, 0x0e, 0x00, 0x34, 0x00, 0xaa, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x00, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x01, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x02, 0x00, 0x0e, 0x00, 0x98, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x03, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x04, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x05, 0x00, 0x04, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x06, 0x00, 0x18, 0x00, 0x80, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x0d, 0x00, 0x36, 0x00, 0x4a, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x0e, 0x00, 0x34, 0x00, 0xaa, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69,
0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x31, 0x35,
0x2c, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x53, 0x70, 0x69,
0x64, 0x65, 0x72, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x52, 0x65, 0x67,
0x75, 0x6c, 0x61, 0x72, 0x56, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x2e, 0x73, 0x69, 0x6c,
0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x4f, 0x46, 0x4c, 0x00, 0x43, 0x00, 0x6f,
0x00, 0x70, 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68,
0x00, 0x74, 0x00, 0x20, 0x00, 0x28, 0x00, 0x63, 0x00, 0x29, 0x00, 0x20,
0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x35, 0x00, 0x2c, 0x00, 0x20,
0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x67, 0x00, 0x6c, 0x00, 0x65,
0x00, 0x2e, 0x00, 0x53, 0x00, 0x70, 0x00, 0x69, 0x00, 0x64, 0x00, 0x65,
0x00, 0x72, 0x00, 0x53, 0x00, 0x79, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x6f,
0x00, 0x6c, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6c,
0x00, 0x61, 0x00, 0x72, 0x00, 0x56, 0x00, 0x31, 0x00, 0x68, 0x00, 0x74,
0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x73,
0x00, 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, 0x73,
0x00, 0x2e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x2e, 0x00, 0x6f,
0x00, 0x72, 0x00, 0x67, 0x00, 0x2f, 0x00, 0x4f, 0x00, 0x46, 0x00, 0x4c,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x66,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xff, 0xff, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0c, 0x00, 0x14, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00
};
/**
* There does not appear to be a publicly accessible API for determining if lcd
* font smoothing will be applied if we request it. The main issue is that if
* smoothing is applied a gamma of 2.0 will be used, if not a gamma of 1.0.
*/
SkCTFontSmoothBehavior SkCTFontGetSmoothBehavior() {
static SkCTFontSmoothBehavior gSmoothBehavior = []{
uint32_t noSmoothBitmap[16][16] = {};
uint32_t smoothBitmap[16][16] = {};
SkUniqueCFRef<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());
SkUniqueCFRef<CGContextRef> noSmoothContext(
CGBitmapContextCreate(&noSmoothBitmap, 16, 16, 8, 16*4,
colorspace.get(), BITMAP_INFO_RGB));
SkUniqueCFRef<CGContextRef> smoothContext(
CGBitmapContextCreate(&smoothBitmap, 16, 16, 8, 16*4,
colorspace.get(), BITMAP_INFO_RGB));
SkUniqueCFRef<CFDataRef> data(CFDataCreateWithBytesNoCopy(
kCFAllocatorDefault, kSpiderSymbol_ttf, SK_ARRAY_COUNT(kSpiderSymbol_ttf),
kCFAllocatorNull));
SkUniqueCFRef<CTFontDescriptorRef> desc(
CTFontManagerCreateFontDescriptorFromData(data.get()));
SkUniqueCFRef<CTFontRef> ctFont(CTFontCreateWithFontDescriptor(desc.get(), 16, nullptr));
SkASSERT(ctFont);
CGContextSetShouldSmoothFonts(noSmoothContext.get(), false);
CGContextSetShouldAntialias(noSmoothContext.get(), true);
CGContextSetTextDrawingMode(noSmoothContext.get(), kCGTextFill);
CGContextSetGrayFillColor(noSmoothContext.get(), 1, 1);
CGContextSetShouldSmoothFonts(smoothContext.get(), true);
CGContextSetShouldAntialias(smoothContext.get(), true);
CGContextSetTextDrawingMode(smoothContext.get(), kCGTextFill);
CGContextSetGrayFillColor(smoothContext.get(), 1, 1);
CGPoint point = CGPointMake(0, 3);
CGGlyph spiderGlyph = 3;
CTFontDrawGlyphs(ctFont.get(), &spiderGlyph, &point, 1, noSmoothContext.get());
CTFontDrawGlyphs(ctFont.get(), &spiderGlyph, &point, 1, smoothContext.get());
// For debugging.
//SkUniqueCFRef<CGImageRef> image(CGBitmapContextCreateImage(noSmoothContext()));
//SkUniqueCFRef<CGImageRef> image(CGBitmapContextCreateImage(smoothContext()));
SkCTFontSmoothBehavior smoothBehavior = SkCTFontSmoothBehavior::none;
for (int x = 0; x < 16; ++x) {
for (int y = 0; y < 16; ++y) {
uint32_t smoothPixel = smoothBitmap[x][y];
uint32_t r = (smoothPixel >> 16) & 0xFF;
uint32_t g = (smoothPixel >> 8) & 0xFF;
uint32_t b = (smoothPixel >> 0) & 0xFF;
if (r != g || r != b) {
return SkCTFontSmoothBehavior::subpixel;
}
if (noSmoothBitmap[x][y] != smoothPixel) {
smoothBehavior = SkCTFontSmoothBehavior::some;
}
}
}
return smoothBehavior;
}();
return gSmoothBehavior;
}
#endif

View File

@ -0,0 +1,23 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkCTFontSmoothBehavior_DEFINED
#define SkCTFontSmoothBehavior_DEFINED
#include "include/core/SkTypes.h"
#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
enum class SkCTFontSmoothBehavior {
none, // SmoothFonts produces no effect.
some, // SmoothFonts produces some effect, but not subpixel coverage.
subpixel, // SmoothFonts produces some effect and provides subpixel coverage.
};
SkCTFontSmoothBehavior SkCTFontGetSmoothBehavior();
#endif
#endif // SkCTFontSmoothBehavior_DEFINED