224 lines
6.2 KiB
Objective-C
224 lines
6.2 KiB
Objective-C
// 1 november 2017
|
|
#import "uipriv_darwin.h"
|
|
#import "attrstr.h"
|
|
|
|
// This is the part of the font style matching and normalization code
|
|
// that handles fonts that use a traits dictionary.
|
|
//
|
|
// Matching stupidity: Core Text requires an **exact match for the
|
|
// entire traits dictionary**, otherwise it will **drop ALL the traits**.
|
|
//
|
|
// Normalization stupidity: Core Text uses its own scaled values for
|
|
// weight and width, but the values are different if the font is not
|
|
// registered and if said font is TrueType or OpenType. The values
|
|
// for all other cases do have some named constants starting with
|
|
// OS X 10.11, but even these aren't very consistent in practice.
|
|
//
|
|
// Of course, none of this is documented anywhere, so I had to do
|
|
// both trial-and-error AND reverse engineering to figure out what's
|
|
// what. We'll just convert Core Text's values into libui constants
|
|
// and use those for matching.
|
|
|
|
static BOOL fontRegistered(uiprivFontStyleData *d)
|
|
{
|
|
if (![d hasRegistrationScope])
|
|
// header says this should be treated as the same as not registered
|
|
return NO;
|
|
// examination of Core Text shows this is accurate
|
|
return [d registrationScope] != kCTFontManagerScopeNone;
|
|
}
|
|
|
|
// Core Text does (usWidthClass / 10) - 0.5 here.
|
|
// This roughly maps to our values with increments of 0.1, except for the fact 0 and 10 are allowed by Core Text, despite being banned by TrueType and OpenType themselves.
|
|
// We'll just treat them as identical to 1 and 9, respectively.
|
|
static const uiTextStretch os2WidthsToStretches[] = {
|
|
uiTextStretchUltraCondensed,
|
|
uiTextStretchUltraCondensed,
|
|
uiTextStretchExtraCondensed,
|
|
uiTextStretchCondensed,
|
|
uiTextStretchSemiCondensed,
|
|
uiTextStretchNormal,
|
|
uiTextStretchSemiExpanded,
|
|
uiTextStretchExpanded,
|
|
uiTextStretchExtraExpanded,
|
|
uiTextStretchUltraExpanded,
|
|
uiTextStretchUltraExpanded,
|
|
};
|
|
|
|
static const CFStringRef exceptions[] = {
|
|
CFSTR("LucidaGrande"),
|
|
CFSTR(".LucidaGrandeUI"),
|
|
CFSTR("STHeiti"),
|
|
CFSTR("STXihei"),
|
|
CFSTR("TimesNewRomanPSMT"),
|
|
NULL,
|
|
};
|
|
|
|
static void trySecondaryOS2Values(uiprivFontStyleData *d, uiFontDescriptor *out, BOOL *hasWeight, BOOL *hasWidth)
|
|
{
|
|
CFDataRef os2;
|
|
uint16_t usWeightClass, usWidthClass;
|
|
CFStringRef psname;
|
|
const CFStringRef *ex;
|
|
|
|
*hasWeight = NO;
|
|
*hasWidth = NO;
|
|
|
|
// only applies to unregistered fonts
|
|
if (fontRegistered(d))
|
|
return;
|
|
|
|
os2 = [d table:kCTFontTableOS2];
|
|
if (os2 == NULL)
|
|
// no OS2 table, so no secondary values
|
|
return;
|
|
|
|
if (CFDataGetLength(os2) > 77) {
|
|
const UInt8 *b;
|
|
|
|
b = CFDataGetBytePtr(os2);
|
|
|
|
usWeightClass = ((uint16_t) (b[4])) << 8;
|
|
usWeightClass |= (uint16_t) (b[5]);
|
|
if (usWeightClass <= 1000) {
|
|
if (usWeightClass < 11)
|
|
usWeightClass *= 100;
|
|
*hasWeight = YES;
|
|
}
|
|
|
|
usWidthClass = ((uint16_t) (b[6])) << 8;
|
|
usWidthClass |= (uint16_t) (b[7]);
|
|
if (usWidthClass <= 10)
|
|
*hasWidth = YES;
|
|
} else {
|
|
usWeightClass = 0;
|
|
*hasWeight = YES;
|
|
|
|
usWidthClass = 0;
|
|
*hasWidth = YES;
|
|
}
|
|
if (*hasWeight)
|
|
// we can just use this directly
|
|
out->Weight = usWeightClass;
|
|
if (*hasWidth)
|
|
out->Stretch = os2WidthsToStretches[usWidthClass];
|
|
CFRelease(os2);
|
|
|
|
// don't use secondary weights in the event of special predefined names
|
|
psname = [d postScriptName];
|
|
for (ex = exceptions; *ex != NULL; ex++)
|
|
if (CFEqual(psname, *ex)) {
|
|
*hasWeight = NO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static BOOL testTTFOTFSubfamilyName(CFStringRef name, CFStringRef want)
|
|
{
|
|
CFRange range;
|
|
|
|
if (name == NULL)
|
|
return NO;
|
|
range.location = 0;
|
|
range.length = CFStringGetLength(name);
|
|
return CFStringFindWithOptions(name, want, range,
|
|
(kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral), NULL) != false;
|
|
}
|
|
|
|
static BOOL testTTFOTFSubfamilyNames(uiprivFontStyleData *d, CFStringRef want)
|
|
{
|
|
switch ([d fontFormat]) {
|
|
case kCTFontFormatOpenTypePostScript:
|
|
case kCTFontFormatOpenTypeTrueType:
|
|
case kCTFontFormatTrueType:
|
|
break;
|
|
default:
|
|
return NO;
|
|
}
|
|
|
|
if (testTTFOTFSubfamilyName([d preferredSubFamilyName], want))
|
|
return YES;
|
|
if (testTTFOTFSubfamilyName([d subFamilyName], want))
|
|
return YES;
|
|
if (testTTFOTFSubfamilyName([d fullName], want))
|
|
return YES;
|
|
if (testTTFOTFSubfamilyName([d preferredFamilyName], want))
|
|
return YES;
|
|
return testTTFOTFSubfamilyName([d familyName], want);
|
|
}
|
|
|
|
// work around a bug in libFontRegistry.dylib
|
|
static BOOL shouldReallyBeThin(uiprivFontStyleData *d)
|
|
{
|
|
return testTTFOTFSubfamilyNames(d, CFSTR("W1"));
|
|
}
|
|
|
|
// work around a bug in libFontRegistry.dylib
|
|
static BOOL shouldReallyBeSemiCondensed(uiprivFontStyleData *d)
|
|
{
|
|
return testTTFOTFSubfamilyNames(d, CFSTR("Semi Condensed"));
|
|
}
|
|
|
|
void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out)
|
|
{
|
|
double weight, width;
|
|
BOOL hasWeight, hasWidth;
|
|
|
|
hasWeight = NO;
|
|
hasWidth = NO;
|
|
trySecondaryOS2Values(d, out, &hasWeight, &hasWidth);
|
|
|
|
weight = [d weight];
|
|
width = [d width];
|
|
|
|
if (!hasWeight)
|
|
// TODO this scale is a bit lopsided
|
|
if (weight <= -0.7)
|
|
out->Weight = uiTextWeightThin;
|
|
else if (weight <= -0.5)
|
|
out->Weight = uiTextWeightUltraLight;
|
|
else if (weight <= -0.3)
|
|
out->Weight = uiTextWeightLight;
|
|
else if (weight <= -0.23) {
|
|
out->Weight = uiTextWeightBook;
|
|
if (shouldReallyBeThin(d))
|
|
out->Weight = uiTextWeightThin;
|
|
} else if (weight <= 0.0)
|
|
out->Weight = uiTextWeightNormal;
|
|
else if (weight <= 0.23)
|
|
out->Weight = uiTextWeightMedium;
|
|
else if (weight <= 0.3)
|
|
out->Weight = uiTextWeightSemiBold;
|
|
else if (weight <= 0.4)
|
|
out->Weight = uiTextWeightBold;
|
|
else if (weight <= 0.5)
|
|
out->Weight = uiTextWeightUltraBold;
|
|
else if (weight <= 0.7)
|
|
out->Weight = uiTextWeightHeavy;
|
|
else
|
|
out->Weight = uiTextWeightUltraHeavy;
|
|
|
|
if (!hasWidth)
|
|
// TODO this scale is a bit lopsided
|
|
if (width <= -0.7) {
|
|
out->Stretch = uiTextStretchUltraCondensed;
|
|
if (shouldReallyBeSemiCondensed(d))
|
|
out->Stretch = uiTextStretchSemiCondensed;
|
|
} else if (width <= -0.5)
|
|
out->Stretch = uiTextStretchExtraCondensed;
|
|
else if (width <= -0.2)
|
|
out->Stretch = uiTextStretchCondensed;
|
|
else if (width <= -0.1)
|
|
out->Stretch = uiTextStretchSemiCondensed;
|
|
else if (width <= 0.0)
|
|
out->Stretch = uiTextStretchNormal;
|
|
else if (width <= 0.1)
|
|
out->Stretch = uiTextStretchSemiExpanded;
|
|
else if (width <= 0.2)
|
|
out->Stretch = uiTextStretchExpanded;
|
|
else if (width <= 0.6)
|
|
out->Stretch = uiTextStretchExtraExpanded;
|
|
else
|
|
out->Stretch = uiTextStretchUltraExpanded;
|
|
}
|