Directly measure CoreText weight mapping.

CoreText reports weight on a [-1.0, 1.0] scale with 0 being 'regular'.
Unfortunately the mapping from the [1, 1000] scale more commonly used
is not uniform and also varies between system fonts and fonts created
from data. For system fonts the NSFontWeight values will be used if
available, but there is no corresponding API for fonts created from
data. Previously the values for fonts created from data were hardcoded,
but the values have changed again recently. Directly read these values
by varying the weight in the font data, creating a CTFontDescriptor from
this data, and then using the value reported by CoreText.

Bug: skia:11176
Change-Id: I071e2ff7ac3f676c8395b13aa82dde7a97fdf2ce
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/358535
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Herb Derby <herb@google.com>
This commit is contained in:
Ben Wagner 2021-01-25 15:25:22 -05:00 committed by Skia Commit-Bot
parent 131decbf41
commit e24127ce05
6 changed files with 208 additions and 101 deletions

View File

@ -82,8 +82,8 @@ skia_utils_sources = [
#mac
"$_src/utils/mac/SkCGBase.h",
"$_src/utils/mac/SkCGGeometry.h",
"$_src/utils/mac/SkCTFontSmoothBehavior.cpp",
"$_src/utils/mac/SkCTFontSmoothBehavior.h",
"$_src/utils/mac/SkCTFont.cpp",
"$_src/utils/mac/SkCTFont.h",
"$_src/utils/mac/SkCreateCGImageRef.cpp",
"$_src/utils/mac/SkUniqueCFRef.h",

View File

@ -46,7 +46,7 @@
#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/SkCTFont.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#include <algorithm>

View File

@ -57,7 +57,7 @@
#include "src/utils/SkUTF.h"
#include "src/utils/mac/SkCGBase.h"
#include "src/utils/mac/SkCGGeometry.h"
#include "src/utils/mac/SkCTFontSmoothBehavior.h"
#include "src/utils/mac/SkCTFont.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#include <dlfcn.h>
@ -321,56 +321,6 @@ struct CGFloatIdentity {
CGFloat operator()(CGFloat s) { return s; }
};
/** Returns the [-1, 1] CTFontDescriptor weights for the
* <0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000> CSS weights.
*
* It is assumed that the values will be interpolated linearly between these points.
* NSFontWeightXXX were added in 10.11, appear in 10.10, but do not appear in 10.9.
* The actual values appear to be stable, but they may change in the future without notice.
*/
static CGFloat(&get_NSFontWeight_mapping())[11] {
// Declarations in <AppKit/AppKit.h> on macOS, <UIKit/UIKit.h> on iOS
#ifdef SK_BUILD_FOR_MAC
# define SK_KIT_FONT_WEIGHT_PREFIX "NS"
#endif
#ifdef SK_BUILD_FOR_IOS
# define SK_KIT_FONT_WEIGHT_PREFIX "UI"
#endif
static constexpr struct {
CGFloat defaultValue;
const char* name;
} nsFontWeightLoaderInfos[] = {
{ -0.80f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightUltraLight" },
{ -0.60f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightThin" },
{ -0.40f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightLight" },
{ 0.00f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightRegular" },
{ 0.23f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightMedium" },
{ 0.30f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightSemibold" },
{ 0.40f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightBold" },
{ 0.56f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightHeavy" },
{ 0.62f, SK_KIT_FONT_WEIGHT_PREFIX "FontWeightBlack" },
};
static_assert(SK_ARRAY_COUNT(nsFontWeightLoaderInfos) == 9, "");
static CGFloat nsFontWeights[11];
static SkOnce once;
once([&] {
size_t i = 0;
nsFontWeights[i++] = -1.00;
for (const auto& nsFontWeightLoaderInfo : nsFontWeightLoaderInfos) {
void* nsFontWeightValuePtr = dlsym(RTLD_DEFAULT, nsFontWeightLoaderInfo.name);
if (nsFontWeightValuePtr) {
nsFontWeights[i++] = *(static_cast<CGFloat*>(nsFontWeightValuePtr));
} else {
nsFontWeights[i++] = nsFontWeightLoaderInfo.defaultValue;
}
}
nsFontWeights[i++] = 1.00;
});
return nsFontWeights;
}
/** 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
@ -385,7 +335,7 @@ CGFloat SkCTFontCTWeightForCSSWeight(int fontstyleWeight) {
static Interpolator::Mapping nativeWeightMappings[11];
static SkOnce once;
once([&] {
CGFloat(&nsFontWeights)[11] = get_NSFontWeight_mapping();
const CGFloat(&nsFontWeights)[11] = SkCTFontGetNSFontWeightMapping();
for (int i = 0; i < 11; ++i) {
nativeWeightMappings[i].src_val = i * 100;
nativeWeightMappings[i].dst_val = nsFontWeights[i];
@ -397,7 +347,6 @@ CGFloat SkCTFontCTWeightForCSSWeight(int fontstyleWeight) {
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
@ -409,37 +358,24 @@ static int ct_weight_to_fontstyle(CGFloat cgWeight, bool fromDataProvider) {
// 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.
/** This mapping for CGDataProvider created fonts is determined by creating font data with every
* weight, creating a CTFont, and asking the CTFont for its weight. See the TypefaceStyle test
* in tests/TypefaceTest.cpp for the code used to determine these values.
*/
static constexpr Interpolator::Mapping dataProviderWeightMappings[] = {
{ -1.00, 0 },
{ -0.70, 100 },
{ -0.50, 200 },
{ -0.23, 300 },
{ 0.00, 400 },
{ 0.20, 500 },
{ 0.30, 600 },
{ 0.40, 700 },
{ 0.60, 800 },
{ 0.80, 900 },
{ 1.00, 1000 },
};
static constexpr Interpolator dataProviderInterpolator(
dataProviderWeightMappings, SK_ARRAY_COUNT(dataProviderWeightMappings));
static Interpolator::Mapping nativeWeightMappings[11];
static Interpolator::Mapping dataProviderWeightMappings[11];
static SkOnce once;
once([&] {
CGFloat(&nsFontWeights)[11] = get_NSFontWeight_mapping();
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, SK_ARRAY_COUNT(nativeWeightMappings));
static constexpr Interpolator dataProviderInterpolator(
dataProviderWeightMappings, SK_ARRAY_COUNT(dataProviderWeightMappings));
return fromDataProvider ? dataProviderInterpolator.map(cgWeight)
: nativeInterpolator.map(cgWeight);

View File

@ -9,7 +9,12 @@
#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
#include "src/utils/mac/SkCTFontSmoothBehavior.h"
#include "include/core/SkData.h"
#include "include/core/SkRefCnt.h"
#include "include/private/SkOnce.h"
#include "src/sfnt/SkOTTable_OS_2.h"
#include "src/sfnt/SkSFNTHeader.h"
#include "src/utils/mac/SkCTFont.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#ifdef SK_BUILD_FOR_MAC
@ -23,6 +28,8 @@
#include <CoreFoundation/CoreFoundation.h>
#endif
#include <dlfcn.h>
static constexpr CGBitmapInfo kBitmapInfoRGB = ((CGBitmapInfo)kCGImageAlphaNoneSkipFirst |
kCGBitmapByteOrder32Host);
@ -268,4 +275,139 @@ SkCTFontSmoothBehavior SkCTFontGetSmoothBehavior() {
return gSmoothBehavior;
}
SkCTFontWeightMapping& SkCTFontGetNSFontWeightMapping() {
// In the event something goes wrong finding the real values, use this mapping.
static constexpr CGFloat defaultNSFontWeights[] =
{ -1.00, -0.80, -0.60, -0.40, 0.00, 0.23, 0.30, 0.40, 0.56, 0.62, 1.00 };
// Declarations in <AppKit/AppKit.h> on macOS, <UIKit/UIKit.h> on iOS
#ifdef SK_BUILD_FOR_MAC
# define SK_KIT_FONT_WEIGHT_PREFIX "NS"
#endif
#ifdef SK_BUILD_FOR_IOS
# define SK_KIT_FONT_WEIGHT_PREFIX "UI"
#endif
static constexpr const char* nsFontWeightNames[] = {
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightUltraLight",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightThin",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightLight",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightRegular",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightMedium",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightSemibold",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightBold",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightHeavy",
SK_KIT_FONT_WEIGHT_PREFIX "FontWeightBlack",
};
static_assert(SK_ARRAY_COUNT(nsFontWeightNames) == 9, "");
static CGFloat nsFontWeights[11];
static const CGFloat (*selectedNSFontWeights)[11] = &defaultNSFontWeights;
static SkOnce once;
once([&] {
size_t i = 0;
nsFontWeights[i++] = -1.00;
for (const char* nsFontWeightName : nsFontWeightNames) {
void* nsFontWeightValuePtr = dlsym(RTLD_DEFAULT, nsFontWeightName);
if (nsFontWeightValuePtr) {
nsFontWeights[i++] = *(static_cast<CGFloat*>(nsFontWeightValuePtr));
} else {
return;
}
}
nsFontWeights[i++] = 1.00;
selectedNSFontWeights = &nsFontWeights;
});
return *selectedNSFontWeights;
}
SkCTFontWeightMapping& SkCTFontGetDataFontWeightMapping() {
// In the event something goes wrong finding the real values, use this mapping.
// These were the values from macOS 10.13 to 10.15.
static constexpr CGFloat defaultDataFontWeights[] =
{ -1.00, -0.70, -0.50, -0.23, 0.00, 0.20, 0.30, 0.40, 0.60, 0.80, 1.00 };
static const CGFloat (*selectedDataFontWeights)[11] = &defaultDataFontWeights;
static CGFloat dataFontWeights[11];
static SkOnce once;
once([&] {
constexpr size_t dataSize = SK_ARRAY_COUNT(kSpiderSymbol_ttf);
sk_sp<SkData> data = SkData::MakeWithCopy(kSpiderSymbol_ttf, dataSize);
const SkSFNTHeader* sfntHeader = reinterpret_cast<const SkSFNTHeader*>(data->data());
const SkSFNTHeader::TableDirectoryEntry* tableEntry =
SkTAfter<const SkSFNTHeader::TableDirectoryEntry>(sfntHeader);
const SkSFNTHeader::TableDirectoryEntry* os2TableEntry = nullptr;
int numTables = SkEndian_SwapBE16(sfntHeader->numTables);
for (int tableEntryIndex = 0; tableEntryIndex < numTables; ++tableEntryIndex) {
if (SkOTTableOS2::TAG == tableEntry[tableEntryIndex].tag) {
os2TableEntry = tableEntry + tableEntryIndex;
break;
}
}
if (!os2TableEntry) {
return;
}
size_t os2TableOffset = SkEndian_SwapBE32(os2TableEntry->offset);
SkOTTableOS2_V0* os2Table = SkTAddOffset<SkOTTableOS2_V0>(data->writable_data(),
os2TableOffset);
for (int i = 0; i < 11; ++i) {
os2Table->usWeightClass.value = SkEndian_SwapBE16(i * 100);
// On macOS 10.14 and earlier it appears that the CFDataGetBytePtr is used somehow in
// font caching. Creating a slightly modified font with data at the same address seems
// to in some ways act like a font previously created at that address. As a result,
// always make a copy of the data.
SkUniqueCFRef<CFDataRef> cfData(
CFDataCreate(kCFAllocatorDefault, (const UInt8 *)data->data(), data->size()));
if (!cfData) {
return;
}
SkUniqueCFRef<CTFontDescriptorRef> desc(
CTFontManagerCreateFontDescriptorFromData(cfData.get()));
if (!desc) {
return;
}
// On macOS 10.14 and earlier, the CTFontDescriptorRef returned from
// CTFontManagerCreateFontDescriptorFromData is incomplete and does not have the
// correct traits. It is necessary to create the CTFont and then get the descriptor
// off of it.
SkUniqueCFRef<CTFontRef> ctFont(CTFontCreateWithFontDescriptor(desc.get(), 9, nullptr));
if (!ctFont) {
return;
}
SkUniqueCFRef<CTFontDescriptorRef> desc2(CTFontCopyFontDescriptor(ctFont.get()));
if (!desc2) {
return;
}
SkUniqueCFRef<CFTypeRef> traitsRef(
CTFontDescriptorCopyAttribute(desc2.get(), kCTFontTraitsAttribute));
if (!traitsRef || CFGetTypeID(traitsRef.get()) != CFDictionaryGetTypeID()) {
return;
}
CFDictionaryRef fontTraitsDict = static_cast<CFDictionaryRef>(traitsRef.get());
CFTypeRef weightRef;
if (!CFDictionaryGetValueIfPresent(fontTraitsDict, kCTFontWeightTrait, &weightRef) ||
!weightRef ||
CFGetTypeID(weightRef) != CFNumberGetTypeID())
{
return;
}
CGFloat weight;
CFNumberRef weightNumber = static_cast<CFNumberRef>(weightRef);
if (!CFNumberIsFloatType(weightNumber) ||
!CFNumberGetValue(weightNumber, kCFNumberCGFloatType, &weight))
{
return;
}
dataFontWeights[i] = weight;
}
selectedDataFontWeights = &dataFontWeights;
});
return *selectedDataFontWeights;
}
#endif

52
src/utils/mac/SkCTFont.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 SkCTFont_DEFINED
#define SkCTFont_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
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();
using SkCTFontWeightMapping = const CGFloat[11];
/** Returns the [-1, 1] CTFontDescriptor weights for the
* <0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000> CSS weights.
*
* It is assumed that the values will be interpolated linearly between these points.
* NSFontWeightXXX were added in 10.11, appear in 10.10, but do not appear in 10.9.
* The actual values appear to be stable, but they may change without notice.
* These values are valid for system fonts only.
*/
SkCTFontWeightMapping& SkCTFontGetNSFontWeightMapping();
/** Returns the [-1, 1] CTFontDescriptor weights for the
* <0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000> CSS weights.
*
* It is assumed that the values will be interpolated linearly between these points.
* The actual values appear to be stable, but they may change without notice.
* These values are valid for fonts created from data only.
*/
SkCTFontWeightMapping& SkCTFontGetDataFontWeightMapping();
#endif
#endif // SkCTFontSmoothBehavior_DEFINED

View File

@ -1,23 +0,0 @@
/*
* 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