Added font matching to the OS X backend.
This commit is contained in:
parent
dec34670bb
commit
3cc2a06147
324
darwin/draw.m
324
darwin/draw.m
|
@ -578,96 +578,6 @@ static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
|
||||||
CFRelease(cfstr); // dictionary holds its own reference
|
CFRelease(cfstr); // dictionary holds its own reference
|
||||||
}
|
}
|
||||||
|
|
||||||
struct traits {
|
|
||||||
uiDrawTextWeight weight;
|
|
||||||
uiDrawTextItalic italic;
|
|
||||||
uiDrawTextStretch stretch;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :(
|
|
||||||
// kode54 got these for me before I had access to El Capitan; thanks to him.
|
|
||||||
#define ourNSFontWeightUltraLight -0.800000
|
|
||||||
#define ourNSFontWeightThin -0.600000
|
|
||||||
#define ourNSFontWeightLight -0.400000
|
|
||||||
#define ourNSFontWeightRegular 0.000000
|
|
||||||
#define ourNSFontWeightMedium 0.230000
|
|
||||||
#define ourNSFontWeightSemibold 0.300000
|
|
||||||
#define ourNSFontWeightBold 0.400000
|
|
||||||
#define ourNSFontWeightHeavy 0.560000
|
|
||||||
#define ourNSFontWeightBlack 0.620000
|
|
||||||
static const CGFloat ctWeights[] = {
|
|
||||||
// yeah these two have their names swapped; blame Pango
|
|
||||||
// TODO note that these names do not necessarily line up with their OS names
|
|
||||||
[uiDrawTextWeightThin] = ourNSFontWeightUltraLight,
|
|
||||||
[uiDrawTextWeightUltraLight] = ourNSFontWeightThin,
|
|
||||||
[uiDrawTextWeightLight] = ourNSFontWeightLight,
|
|
||||||
// for this one let's go between Light and Regular
|
|
||||||
// TODO figure out if we can rely on the order for these (and the one below)
|
|
||||||
[uiDrawTextWeightBook] = ourNSFontWeightLight + ((ourNSFontWeightRegular - ourNSFontWeightLight) / 2),
|
|
||||||
[uiDrawTextWeightNormal] = ourNSFontWeightRegular,
|
|
||||||
[uiDrawTextWeightMedium] = ourNSFontWeightMedium,
|
|
||||||
[uiDrawTextWeightSemiBold] = ourNSFontWeightSemibold,
|
|
||||||
[uiDrawTextWeightBold] = ourNSFontWeightBold,
|
|
||||||
// for this one let's go between Bold and Heavy
|
|
||||||
[uiDrawTextWeightUtraBold] = ourNSFontWeightBold + ((ourNSFontWeightHeavy - ourNSFontWeightBold) / 2),
|
|
||||||
[uiDrawTextWeightHeavy] = ourNSFontWeightHeavy,
|
|
||||||
[uiDrawTextWeightUltraHeavy] = ourNSFontWeightBlack,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unfortunately there are still no named constants for these.
|
|
||||||
// Let's just use normalized widths.
|
|
||||||
static const CGFloat ctStretches[] = {
|
|
||||||
[uiDrawTextStretchUltraCondensed] = -1.0,
|
|
||||||
[uiDrawTextStretchExtraCondensed] = -0.75,
|
|
||||||
[uiDrawTextStretchCondensed] = -0.5,
|
|
||||||
[uiDrawTextStretchSemiCondensed] = -0.25,
|
|
||||||
[uiDrawTextStretchNormal] = 0.0,
|
|
||||||
[uiDrawTextStretchSemiExpanded] = 0.25,
|
|
||||||
[uiDrawTextStretchExpanded] = 0.5,
|
|
||||||
[uiDrawTextStretchExtraExpanded] = 0.75,
|
|
||||||
[uiDrawTextStretchUltraExpanded] = 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void addFontTraitsAttr(CFMutableDictionaryRef attr, struct traits *traits)
|
|
||||||
{
|
|
||||||
CFMutableDictionaryRef td;
|
|
||||||
CFNumberRef num;
|
|
||||||
SInt64 symbolic;
|
|
||||||
CGFloat slant;
|
|
||||||
|
|
||||||
td = newAttrList();
|
|
||||||
symbolic = 0;
|
|
||||||
|
|
||||||
symbolic |= (SInt64) kCTFontBoldTrait;
|
|
||||||
num = CFNumberCreate(NULL, kCFNumberCGFloatType, &ctWeights[traits->weight]);
|
|
||||||
CFDictionaryAddValue(td, kCTFontWeightTrait, num);
|
|
||||||
CFRelease(num);
|
|
||||||
|
|
||||||
switch (traits->italic) {
|
|
||||||
case uiDrawTextItalicOblique:
|
|
||||||
slant = 1.0; // TODO
|
|
||||||
num = CFNumberCreate(NULL, kCFNumberCGFloatType, &slant);
|
|
||||||
CFDictionaryAddValue(td, kCTFontSlantTrait, num);
|
|
||||||
CFRelease(num);
|
|
||||||
// fall through
|
|
||||||
case uiDrawTextItalicItalic:
|
|
||||||
symbolic |= (SInt64) kCTFontItalicTrait;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
symbolic |= (SInt64) kCTFontCondensedTrait;
|
|
||||||
num = CFNumberCreate(NULL, kCFNumberCGFloatType, &ctStretches[traits->stretch]);
|
|
||||||
CFDictionaryAddValue(td, kCTFontWidthTrait, num);
|
|
||||||
CFRelease(num);
|
|
||||||
|
|
||||||
num = CFNumberCreate(NULL, kCFNumberSInt64Type, &symbolic);
|
|
||||||
CFDictionaryAddValue(td, kCTFontSymbolicTrait, num);
|
|
||||||
CFRelease(num);
|
|
||||||
|
|
||||||
CFDictionaryAddValue(attr, kCTFontTraitsAttribute, td);
|
|
||||||
CFRelease(td);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
|
static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
|
||||||
{
|
{
|
||||||
CFNumberRef n;
|
CFNumberRef n;
|
||||||
|
@ -711,12 +621,234 @@ static void addFontGravityAttr(CFMutableDictionaryRef dict, uiDrawTextGravity gr
|
||||||
// TODO: matrix setting? kCTFontOrientationAttribute? or is it a kCTVerticalFormsAttributeName of the CFAttributedString attributes and thus not part of the CTFontDescriptor?
|
// TODO: matrix setting? kCTFontOrientationAttribute? or is it a kCTVerticalFormsAttributeName of the CFAttributedString attributes and thus not part of the CTFontDescriptor?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :(
|
||||||
|
// kode54 got these for me before I had access to El Capitan; thanks to him.
|
||||||
|
#define ourNSFontWeightUltraLight -0.800000
|
||||||
|
#define ourNSFontWeightThin -0.600000
|
||||||
|
#define ourNSFontWeightLight -0.400000
|
||||||
|
#define ourNSFontWeightRegular 0.000000
|
||||||
|
#define ourNSFontWeightMedium 0.230000
|
||||||
|
#define ourNSFontWeightSemibold 0.300000
|
||||||
|
#define ourNSFontWeightBold 0.400000
|
||||||
|
#define ourNSFontWeightHeavy 0.560000
|
||||||
|
#define ourNSFontWeightBlack 0.620000
|
||||||
|
static const CGFloat ctWeights[] = {
|
||||||
|
// yeah these two have their names swapped; blame Pango
|
||||||
|
// TODO note that these names do not necessarily line up with their OS names
|
||||||
|
[uiDrawTextWeightThin] = ourNSFontWeightUltraLight,
|
||||||
|
[uiDrawTextWeightUltraLight] = ourNSFontWeightThin,
|
||||||
|
[uiDrawTextWeightLight] = ourNSFontWeightLight,
|
||||||
|
// for this one let's go between Light and Regular
|
||||||
|
// TODO figure out if we can rely on the order for these (and the one below)
|
||||||
|
[uiDrawTextWeightBook] = ourNSFontWeightLight + ((ourNSFontWeightRegular - ourNSFontWeightLight) / 2),
|
||||||
|
[uiDrawTextWeightNormal] = ourNSFontWeightRegular,
|
||||||
|
[uiDrawTextWeightMedium] = ourNSFontWeightMedium,
|
||||||
|
[uiDrawTextWeightSemiBold] = ourNSFontWeightSemibold,
|
||||||
|
[uiDrawTextWeightBold] = ourNSFontWeightBold,
|
||||||
|
// for this one let's go between Bold and Heavy
|
||||||
|
[uiDrawTextWeightUtraBold] = ourNSFontWeightBold + ((ourNSFontWeightHeavy - ourNSFontWeightBold) / 2),
|
||||||
|
[uiDrawTextWeightHeavy] = ourNSFontWeightHeavy,
|
||||||
|
[uiDrawTextWeightUltraHeavy] = ourNSFontWeightBlack,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unfortunately there are still no named constants for these.
|
||||||
|
// Let's just use normalized widths.
|
||||||
|
// TODO verify this is correct
|
||||||
|
static const CGFloat ctStretches[] = {
|
||||||
|
[uiDrawTextStretchUltraCondensed] = -1.0,
|
||||||
|
[uiDrawTextStretchExtraCondensed] = -0.75,
|
||||||
|
[uiDrawTextStretchCondensed] = -0.5,
|
||||||
|
[uiDrawTextStretchSemiCondensed] = -0.25,
|
||||||
|
[uiDrawTextStretchNormal] = 0.0,
|
||||||
|
[uiDrawTextStretchSemiExpanded] = 0.25,
|
||||||
|
[uiDrawTextStretchExpanded] = 0.5,
|
||||||
|
[uiDrawTextStretchExtraExpanded] = 0.75,
|
||||||
|
[uiDrawTextStretchUltraExpanded] = 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct closeness {
|
||||||
|
CFIndex index;
|
||||||
|
CGFloat weight;
|
||||||
|
CGFloat italic;
|
||||||
|
CGFloat stretch;
|
||||||
|
CGFloat distance;
|
||||||
|
};
|
||||||
|
|
||||||
|
// see below for details
|
||||||
|
// TODO document that font matching is closest match but the search method is OS defined
|
||||||
|
CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, uiDrawTextWeight weight, uiDrawTextItalic italic, uiDrawTextStretch stretch)
|
||||||
|
{
|
||||||
|
CGFloat targetWeight;
|
||||||
|
CGFloat italicCloseness, obliqueCloseness, normalCloseness;
|
||||||
|
CGFloat targetStretch;
|
||||||
|
CFArrayRef matching;
|
||||||
|
CFIndex i, n;
|
||||||
|
struct closeness *closeness;
|
||||||
|
CTFontDescriptorRef current;
|
||||||
|
CTFontDescriptorRef out;
|
||||||
|
|
||||||
|
targetWeight = ctWeights[weight];
|
||||||
|
switch (italic) {
|
||||||
|
case uiDrawTextItalicNormal:
|
||||||
|
italicCloseness = 1;
|
||||||
|
obliqueCloseness = 1;
|
||||||
|
normalCloseness = 0;
|
||||||
|
break;
|
||||||
|
case uiDrawTextItalicOblique:
|
||||||
|
italicCloseness = 0.5;
|
||||||
|
obliqueCloseness = 0;
|
||||||
|
normalCloseness = 1;
|
||||||
|
break;
|
||||||
|
case uiDrawTextItalicItalic:
|
||||||
|
italicCloseness = 0;
|
||||||
|
obliqueCloseness = 0.5;
|
||||||
|
normalCloseness = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
targetStretch = ctStretches[stretch];
|
||||||
|
|
||||||
|
matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);
|
||||||
|
if (matching == NULL)
|
||||||
|
// no matches; give the original back and hope for the best
|
||||||
|
return against;
|
||||||
|
n = CFArrayGetCount(matching);
|
||||||
|
if (n == 0) {
|
||||||
|
// likewise
|
||||||
|
CFRelease(matching);
|
||||||
|
return against;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]");
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
CFDictionaryRef traits;
|
||||||
|
CFNumberRef cfnum;
|
||||||
|
CTFontSymbolicTraits symbolic;
|
||||||
|
|
||||||
|
closeness[i].index = i;
|
||||||
|
|
||||||
|
current = CFArrayGetValueAtIndex(matching, i);
|
||||||
|
traits = CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute);
|
||||||
|
if (traits == NULL) {
|
||||||
|
// couldn't get traits; be safe by ranking it lowest
|
||||||
|
// TODO figure out what the longest possible distances are
|
||||||
|
closeness[i].weight = 3;
|
||||||
|
closeness[i].italic = 2;
|
||||||
|
closeness[i].stretch = 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
symbolic = 0; // assume no symbolic traits if none are listed
|
||||||
|
cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait);
|
||||||
|
if (cfnum != NULL) {
|
||||||
|
SInt32 s;
|
||||||
|
|
||||||
|
if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false)
|
||||||
|
complain("error getting symbolic traits in matchTraits()");
|
||||||
|
symbolic = (CTFontSymbolicTraits) s;
|
||||||
|
// Get rule; do not release cfnum
|
||||||
|
}
|
||||||
|
|
||||||
|
// now try weight
|
||||||
|
cfnum = CFDictionaryGetValue(traits, kCTFontWeightTrait);
|
||||||
|
if (cfnum != NULL) {
|
||||||
|
CGFloat val;
|
||||||
|
|
||||||
|
// TODO instead of complaining for this and width, should we just fall through to the default?
|
||||||
|
if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
|
||||||
|
complain("error getting weight value in matchTraits()");
|
||||||
|
closeness[i].weight = val - targetWeight;
|
||||||
|
} else
|
||||||
|
// okay there's no weight key; let's try the literal meaning of the symbolic constant
|
||||||
|
// TODO is the weight key guaranteed?
|
||||||
|
if ((symbolic & kCTFontBoldTrait) != 0)
|
||||||
|
closeness[i].weight = ourNSFontWeightBold - targetWeight;
|
||||||
|
else
|
||||||
|
closeness[i].weight = ourNSFontWeightRegular - targetWeight;
|
||||||
|
|
||||||
|
// italics is a bit harder because Core Text doesn't expose a concept of obliqueness
|
||||||
|
// Pango just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
|
||||||
|
if ((symbolic & kCTFontItalicTrait) != 0)
|
||||||
|
closeness[i].italic = italicCloseness;
|
||||||
|
else {
|
||||||
|
CFStringRef styleName;
|
||||||
|
BOOL isOblique;
|
||||||
|
|
||||||
|
isOblique = NO; // default value
|
||||||
|
styleName = CTFontDescriptorCopyAttribute(current, kCTFontStyleNameAttribute);
|
||||||
|
if (styleName != NULL) {
|
||||||
|
CFRange range;
|
||||||
|
|
||||||
|
// note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL
|
||||||
|
range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards);
|
||||||
|
if (range.location != kCFNotFound)
|
||||||
|
isOblique = YES;
|
||||||
|
CFRelease(styleName);
|
||||||
|
}
|
||||||
|
if (isOblique)
|
||||||
|
closeness[i].italic = obliqueCloseness;
|
||||||
|
else
|
||||||
|
closeness[i].italic = normalCloseness;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now try width
|
||||||
|
cfnum = CFDictionaryGetValue(traits, kCTFontWidthTrait);
|
||||||
|
if (cfnum != NULL) {
|
||||||
|
CGFloat val;
|
||||||
|
|
||||||
|
if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
|
||||||
|
complain("error getting width value in matchTraits()");
|
||||||
|
closeness[i].stretch = val - targetStretch;
|
||||||
|
} else
|
||||||
|
// okay there's no width key; let's try the literal meaning of the symbolic constant
|
||||||
|
// TODO is the width key guaranteed?
|
||||||
|
if ((symbolic & kCTFontExpandedTrait) != 0)
|
||||||
|
closeness[i].stretch = 1.0 - targetStretch;
|
||||||
|
else if ((symbolic & kCTFontCondensedTrait) != 0)
|
||||||
|
closeness[i].stretch = -1.0 - targetStretch;
|
||||||
|
else
|
||||||
|
closeness[i].stretch = 0.0 - targetStretch;
|
||||||
|
|
||||||
|
CFRelease(traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now figure out the 3-space difference between the three and sort by that
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
CGFloat weight, italic, stretch;
|
||||||
|
|
||||||
|
weight = closeness[i].weight;
|
||||||
|
weight *= weight;
|
||||||
|
italic = closeness[i].italic;
|
||||||
|
italic *= italic;
|
||||||
|
stretch = closeness[i].stretch;
|
||||||
|
stretch *= stretch;
|
||||||
|
closeness[i].distance = sqrt(weight + italic + stretch);
|
||||||
|
}
|
||||||
|
qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
|
||||||
|
const struct closeness *a = (const struct closeness *) aa;
|
||||||
|
const struct closeness *b = (const struct closeness *) bb;
|
||||||
|
|
||||||
|
// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
|
||||||
|
// TODO is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
|
||||||
|
return (a->distance > b->distance) - (a->distance < b->distance);
|
||||||
|
});
|
||||||
|
// and the first element of the sorted array is what we want
|
||||||
|
out = CFArrayGetValueAtIndex(matching, closeness[0].index);
|
||||||
|
CFRetain(out); // get rule
|
||||||
|
|
||||||
|
// release everything
|
||||||
|
uiFree(closeness);
|
||||||
|
CFRelease(matching);
|
||||||
|
// and release the original descriptor since we no longer need it
|
||||||
|
CFRelease(against);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
uiDrawTextLayout *uiDrawNewTextLayout(const char *str, const uiDrawInitialTextStyle *initialStyle)
|
uiDrawTextLayout *uiDrawNewTextLayout(const char *str, const uiDrawInitialTextStyle *initialStyle)
|
||||||
{
|
{
|
||||||
uiDrawTextLayout *layout;
|
uiDrawTextLayout *layout;
|
||||||
CFMutableStringRef cfstr;
|
CFMutableStringRef cfstr;
|
||||||
CFMutableDictionaryRef attr;
|
CFMutableDictionaryRef attr;
|
||||||
struct traits t;
|
|
||||||
CTFontDescriptorRef desc;
|
CTFontDescriptorRef desc;
|
||||||
CTFontRef font;
|
CTFontRef font;
|
||||||
CFAttributedStringRef immutable;
|
CFAttributedStringRef immutable;
|
||||||
|
@ -727,10 +859,6 @@ uiDrawTextLayout *uiDrawNewTextLayout(const char *str, const uiDrawInitialTextSt
|
||||||
|
|
||||||
attr = newAttrList();
|
attr = newAttrList();
|
||||||
addFontFamilyAttr(attr, initialStyle->Family);
|
addFontFamilyAttr(attr, initialStyle->Family);
|
||||||
t.weight = initialStyle->Weight;
|
|
||||||
t.italic = initialStyle->Italic;
|
|
||||||
t.stretch = initialStyle->Stretch;
|
|
||||||
addFontTraitsAttr(attr, &t);
|
|
||||||
addFontSizeAttr(attr, initialStyle->Size);
|
addFontSizeAttr(attr, initialStyle->Size);
|
||||||
if (initialStyle->SmallCaps)
|
if (initialStyle->SmallCaps)
|
||||||
addFontSmallCapsAttr(attr);
|
addFontSmallCapsAttr(attr);
|
||||||
|
@ -738,6 +866,12 @@ uiDrawTextLayout *uiDrawNewTextLayout(const char *str, const uiDrawInitialTextSt
|
||||||
|
|
||||||
desc = CTFontDescriptorCreateWithAttributes(attr);
|
desc = CTFontDescriptorCreateWithAttributes(attr);
|
||||||
// TODO release attr?
|
// TODO release attr?
|
||||||
|
|
||||||
|
// unfortunately OS X requires an EXACT MATCH for the traits, otherwise it will *drop all the traits*
|
||||||
|
// we want a nearest match, so we have to do it ourselves
|
||||||
|
// TODO this does not preserve small caps
|
||||||
|
desc = matchTraits(desc, initialStyle->Weight, initialStyle->Italic, initialStyle->Stretch);
|
||||||
|
|
||||||
// specify the initial size again just to be safe
|
// specify the initial size again just to be safe
|
||||||
font = CTFontCreateWithFontDescriptor(desc, initialStyle->Size, NULL);
|
font = CTFontCreateWithFontDescriptor(desc, initialStyle->Size, NULL);
|
||||||
// TODO release desc?
|
// TODO release desc?
|
||||||
|
|
Loading…
Reference in New Issue