Started an experimental port from Core Text to Cocoa's typesetting system, since that seems to produce more sensible results (and is somewhat easier to use...). We lose per-line spacing though :/

This commit is contained in:
Pietro Gagliardi 2017-01-23 01:28:53 -05:00
parent 7eba767ffb
commit 4e2dc90f4f
4 changed files with 576 additions and 81 deletions

268
darwin/_coretext_drawtext.m Normal file
View File

@ -0,0 +1,268 @@
// 6 september 2015
#import "uipriv_darwin.h"
// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa)
struct uiDrawFontFamilies {
CFArrayRef fonts;
};
uiDrawFontFamilies *uiDrawListFontFamilies(void)
{
uiDrawFontFamilies *ff;
ff = uiNew(uiDrawFontFamilies);
ff->fonts = CTFontManagerCopyAvailableFontFamilyNames();
if (ff->fonts == NULL)
implbug("error getting available font names (no reason specified) (TODO)");
return ff;
}
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
{
return CFArrayGetCount(ff->fonts);
}
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
{
CFStringRef familystr;
char *family;
familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);
// toll-free bridge
family = uiDarwinNSStringToText((NSString *) familystr);
// Get Rule means we do not free familystr
return family;
}
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
{
CFRelease(ff->fonts);
uiFree(ff);
}
struct uiDrawTextFont {
CTFontRef f;
};
uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain)
{
uiDrawTextFont *font;
font = uiNew(uiDrawTextFont);
font->f = f;
if (retain)
CFRetain(font->f);
return font;
}
uiDrawTextFont *mkTextFontFromNSFont(NSFont *f)
{
// toll-free bridging; we do retain, though
return mkTextFont((CTFontRef) f, YES);
}
static CFMutableDictionaryRef newAttrList(void)
{
CFMutableDictionaryRef attr;
attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (attr == NULL)
complain("error creating attribute dictionary in newAttrList()()");
return attr;
}
static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
{
CFStringRef cfstr;
cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8);
if (cfstr == NULL)
complain("error creating font family name CFStringRef in addFontFamilyAttr()");
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);
CFRelease(cfstr); // dictionary holds its own reference
}
static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
{
CFNumberRef n;
n = CFNumberCreate(NULL, kCFNumberDoubleType, &size);
CFDictionaryAddValue(attr, kCTFontSizeAttribute, n);
CFRelease(n);
}
#if 0
TODO
// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do
// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D
static void addFontSmallCapsAttr(CFMutableDictionaryRef attr)
{
CFMutableArrayRef outerArray;
CFMutableDictionaryRef innerDict;
CFNumberRef numType, numSelector;
int num;
outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (outerArray == NULL)
complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()");
// Apple's headers say these are deprecated, but a few fonts still rely on them
num = kLetterCaseType;
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
num = kSmallCapsSelector;
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
innerDict = newAttrList();
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
CFRelease(numType);
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
CFRelease(numSelector);
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict); // and likewise for CFArray
// these are the non-deprecated versions of the above; some fonts have these instead
num = kLowerCaseType;
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
num = kLowerCaseSmallCapsSelector;
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
innerDict = newAttrList();
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
CFRelease(numType);
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
CFRelease(numSelector);
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict); // and likewise for CFArray
CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray);
CFRelease(outerArray);
}
#endif
#if 0
// 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
#endif
// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so.
CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc)
{
CFDictionaryRef dict;
CFMutableDictionaryRef mdict;
dict = CTFontDescriptorCopyAttributes(desc);
// this might not be mutable, so make a mutable copy
mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
CFRelease(dict);
return mdict;
}
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
{
CTFontRef f;
CFMutableDictionaryRef attr;
CTFontDescriptorRef cfdesc;
attr = newAttrList();
addFontFamilyAttr(attr, desc->Family);
addFontSizeAttr(attr, desc->Size);
// now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back
cfdesc = CTFontDescriptorCreateWithAttributes(attr);
// TODO release attr?
cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch);
// specify the initial size again just to be safe
f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL);
// TODO release cfdesc?
return mkTextFont(f, NO); // we hold the initial reference; no need to retain again
}
void uiDrawFreeTextFont(uiDrawTextFont *font)
{
CFRelease(font->f);
uiFree(font);
}
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
{
return (uintptr_t) (font->f);
}
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
{
// TODO
}
// text sizes and user space points are identical:
// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch
// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
{
metrics->Ascent = CTFontGetAscent(font->f);
metrics->Descent = CTFontGetDescent(font->f);
metrics->Leading = CTFontGetLeading(font->f);
metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f);
metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f);
}
// LONGTERM allow line separation and leading to be factored into a wrapping text layout
// TODO reconcile differences in character wrapping on platforms
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
{
struct framesetter fs;
mkFramesetter(layout, &fs);
*width = fs.extents.width;
*height = fs.extents.height;
freeFramesetter(&fs);
}
// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout?
// LONGTERM keep this for later features and documentation purposes
#if 0
// LONGTERM provide a way to get the image bounds as a separate function later
bounds = CTLineGetImageBounds(line, c);
// though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error
// CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead
CTLineGetTypographicBounds(line, &yoff, NULL, NULL);
// remember that we're flipped, so we subtract
y -= yoff;
CGContextSetTextPosition(c, x, y);
#endif
#if 0
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
{
CGColorSpaceRef colorspace;
CGFloat components[4];
CGColorRef color;
// for consistency with windows, use sRGB
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
components[0] = r;
components[1] = g;
components[2] = b;
components[3] = a;
color = CGColorCreate(colorspace, components);
CGColorSpaceRelease(colorspace);
CFAttributedStringSetAttribute(layout->mas,
rangeToCFRange(),
kCTForegroundColorAttributeName,
color);
CGColorRelease(color); // TODO safe?
}
#endif

View File

@ -0,0 +1,255 @@
// 3 january 2017
#import "uipriv_darwin.h"
// Stupidity: Core Text requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**.
// This seems to be true for every function in Core Text that advertises that it performs font matching; I can confirm at the time of writing this is the case for
// - CTFontDescriptorCreateMatchingFontDescriptors() (though the conditions here seem odd)
// - CTFontCreateWithFontDescriptor()
// - CTFontCreateCopyWithAttributes()
// We have to implement the closest match ourselves.
// This needs to be done early in the matching process; in particular, we have to do this before adding any features (such as small caps), because the matching descriptors won't have those.
struct closeness {
CFIndex index;
double weight;
double italic;
double stretch;
double distance;
};
static double doubleAttr(CFDictionaryRef traits, CFStringRef attr)
{
CFNumberRef cfnum;
double val;
cfnum = (CFNumberRef) CFDictionaryGetValue(traits, attr);
if (cfnum == NULL) {
// TODO
}
if (CFNumberGetValue(cfnum, kCFNumberDoubleType, &val) == false) {
// TODO
}
// Get Rule; do not release cfnum
return val;
}
struct italicCloseness {
double normal;
double oblique;
double italic;
};
// remember that in closeness, 0 means exact
// in this case, since we define the range, we use 0.5 to mean "close enough" (oblique for italic and italic for oblique) and 1 to mean "not a match"
static const struct italicCloseness italicClosenesses[] = {
[uiDrawTextItalicNormal] = { 0, 1, 1 },
[uiDrawTextItalicOblique] = { 1, 0, 0.5 },
[uiDrawTextItalicItalic] = { 1, 0.5, 0 },
};
// Italics are hard because Core Text does NOT distinguish between italic and oblique.
// All Core Text provides is a slant value and the italic bit of the symbolic traits mask.
// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value.
// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.)
// TODO there is still one catch that might matter from a user's POV: the reverse is not true the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold)
static double italicCloseness(CTFontDescriptorRef desc, CFDictionaryRef traits, uiDrawTextItalic italic)
{
const struct italicCloseness *ic = &(italicClosenesses[italic]);
CFNumberRef cfnum;
CTFontSymbolicTraits symbolic;
// there is no kCFNumberUInt32Type, but CTFontSymbolicTraits is uint32_t, so SInt32 should work
SInt32 s;
CFStringRef styleName;
BOOL isOblique;
cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait);
if (cfnum == NULL) {
// TODO
}
if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false) {
// TODO
}
symbolic = (CTFontSymbolicTraits) s;
// Get Rule; do not release cfnum
if ((symbolic & kCTFontItalicTrait) == 0)
return ic->normal;
// Okay, now we know it's either Italic or Oblique
// Pango's Core Text code 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
isOblique = NO; // default value
styleName = (CFStringRef) CTFontDescriptorCopyAttribute(desc, kCTFontStyleNameAttribute);
// TODO is styleName guaranteed?
if (styleName != NULL) {
CFRange range;
// note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL
// TODO is this really the case? or is that just a copy-paste error from the other CFStringCreateXxx() functions? and what's this about -fconstant-cfstring?
range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards);
if (range.location != kCFNotFound)
isOblique = YES;
CFRelease(styleName);
}
if (isOblique)
return ic->oblique;
return ic->italic;
}
static CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, double targetWeight, uiDrawTextItalic targetItalic, double targetStretch)
{
CFArrayRef matching;
CFIndex i, n;
struct closeness *closeness;
CTFontDescriptorRef current;
CTFontDescriptorRef out;
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;
closeness[i].index = i;
current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, i);
traits = (CFDictionaryRef) CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute);
if (traits == NULL) {
// couldn't get traits; be safe by ranking it lowest
// LONGTERM figure out what the longest possible distances are
closeness[i].weight = 3;
closeness[i].italic = 2;
closeness[i].stretch = 3;
continue;
}
closeness[i].weight = doubleAttr(traits, kCTFontWeightTrait) - targetWeight;
closeness[i].italic = italicCloseness(current, traits, targetItalic);
closeness[i].stretch = doubleAttr(traits, kCTFontWidthTrait) - targetStretch;
CFRelease(traits);
}
// now figure out the 3-space difference between the three and sort by that
// TODO merge this loop with the previous loop?
for (i = 0; i < n; i++) {
double 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
// LONGTERM 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;
}
// since uiDrawTextWeight effectively corresponds to OS/2 weights (which roughly correspond to GDI, Pango, and DirectWrite weights, and to a lesser(? TODO) degree, CSS weights), let's just do what Core Text does with OS/2 weights
// TODO this will not be correct for system fonts, which use cached values that have no relation to the OS/2 weights; we need to figure out how to reconcile these
// for more information, see https://bugzilla.gnome.org/show_bug.cgi?id=766148 and TODO_put_blog_post_here_once_I_write_it (TODO keep this line when resolving the above TODO)
static const double weightsToCTWeights[] = {
-1.0, // 0..99
-0.7, // 100..199
-0.5, // 200..299
-0.23, // 300..399
0.0, // 400..499
0.2, // 500..599
0.3, // 600..699
0.4, // 700..799
0.6, // 800..899
0.8, // 900..999
1.0, // 1000
};
static double weightToCTWeight(uiDrawTextWeight weight)
{
int weightClass;
double ctclass;
double rest, weightFloor, nextFloor;
if (weight <= 0)
return -1.0;
if (weight >= 1000)
return 1.0;
weightClass = weight / 100;
rest = (double) weight;
weightFloor = (double) (weightClass * 100);
nextFloor = (double) ((weightClass + 1) * 100);
rest = (rest - weightFloor) / (nextFloor - weightFloor);
ctclass = weightsToCTWeights[weightClass];
return fma(rest,
weightsToCTWeights[weightClass + 1] - ctclass,
ctclass);
}
// based on what Core Text says about actual fonts (system fonts, system fonts in another folder to avoid using cached values, Adobe Font Folio 11, Google Fonts archive, fonts in Windows 7/8.1/10)
static const double stretchesToCTWidths[] = {
[uiDrawTextStretchUltraCondensed] = -0.400000,
[uiDrawTextStretchExtraCondensed] = -0.300000,
[uiDrawTextStretchCondensed] = -0.200000,
[uiDrawTextStretchSemiCondensed] = -0.100000,
[uiDrawTextStretchNormal] = 0.000000,
[uiDrawTextStretchSemiExpanded] = 0.100000,
[uiDrawTextStretchExpanded] = 0.200000,
[uiDrawTextStretchExtraExpanded] = 0.300000,
// this one isn't present in any of the fonts I tested, but it follows naturally from the pattern of the rest, so... (TODO verify by checking the font files directly)
[uiDrawTextStretchUltraExpanded] = 0.400000,
};
CTFontDescriptorRef fontdescToCTFontDescriptor(uiDrawFontDescriptor *fd)
{
CFMutableDictionaryRef attrs;
CFStringRef cffamily;
CFNumberRef cfsize;
CTFontDescriptorRef basedesc;
attrs = CFDictionaryCreateMutable(NULL, 2,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (attrs == NULL) {
// TODO
}
cffamily = CFStringCreateWithCString(NULL, fd->Family, kCFStringEncodingUTF8);
if (cffamily == NULL) {
// TODO
}
CFDictionaryAddValue(attrs, kCTFontFamilyNameAttribute, cffamily);
CFRelease(cffamily);
cfsize = CFNumberCreate(NULL, kCFNumberDoubleType, &(fd->Size));
CFDictionaryAddValue(attrs, kCTFontSizeAttribute, cfsize);
CFRelease(cfsize);
basedesc = CTFontDescriptorCreateWithAttributes(attrs);
CFRelease(attrs); // TODO correct?
return matchTraits(basedesc,
weightToCTWeight(fd->Weight),
fd->Italic,
stretchesToCTWidths[fd->Stretch]);
}

View File

@ -6,31 +6,25 @@
// - CTFontDescriptorCreateMatchingFontDescriptors() (though the conditions here seem odd)
// - CTFontCreateWithFontDescriptor()
// - CTFontCreateCopyWithAttributes()
// And as a bonus prize, this also applies to Cocoa's NSFontDescriptor methods as well!
// We have to implement the closest match ourselves.
// This needs to be done early in the matching process; in particular, we have to do this before adding any features (such as small caps), because the matching descriptors won't have those.
struct closeness {
CFIndex index;
NSUInteger index;
double weight;
double italic;
double stretch;
double distance;
};
static double doubleAttr(CFDictionaryRef traits, CFStringRef attr)
static double doubleAttr(NSDictionary *traits, NSString *attr)
{
CFNumberRef cfnum;
NSNumber *n;
double val;
cfnum = (CFNumberRef) CFDictionaryGetValue(traits, attr);
if (cfnum == NULL) {
// TODO
}
if (CFNumberGetValue(cfnum, kCFNumberDoubleType, &val) == false) {
// TODO
}
// Get Rule; do not release cfnum
return val;
n = (NSNumber *) [traits objectForKey:attr];
return [n doubleValue];
}
struct italicCloseness {
@ -52,75 +46,62 @@ static const struct italicCloseness italicClosenesses[] = {
// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value.
// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.)
// TODO there is still one catch that might matter from a user's POV: the reverse is not true the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold)
static double italicCloseness(CTFontDescriptorRef desc, CFDictionaryRef traits, uiDrawTextItalic italic)
// TODO see if the above applies to Cocoa as well
static double italicCloseness(NSFontDescriptor *desc, NSDictionary *traits, uiDrawTextItalic italic)
{
const struct italicCloseness *ic = &(italicClosenesses[italic]);
CFNumberRef cfnum;
CTFontSymbolicTraits symbolic;
// there is no kCFNumberUInt32Type, but CTFontSymbolicTraits is uint32_t, so SInt32 should work
SInt32 s;
CFStringRef styleName;
NSNumber *num;
NSFontSymbolicTraits symbolic;
NSString *styleName;
NSRange range;
BOOL isOblique;
cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait);
if (cfnum == NULL) {
// TODO
}
if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false) {
// TODO
}
symbolic = (CTFontSymbolicTraits) s;
// Get Rule; do not release cfnum
if ((symbolic & kCTFontItalicTrait) == 0)
num = (NSNumber *) [traits objectForKey:NSFontSymbolicTrait];
// TODO this should really be a uint32_t-specific one
symbolic = (NSFontSymbolicTraits) [num unsignedIntegerValue];
if ((symbolic & NSFontItalicTrait) == 0)
return ic->normal;
// Okay, now we know it's either Italic or Oblique
// Pango's Core Text code 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
// note that NSFontFaceAttribute is the Cocoa equivalent of the style name
isOblique = NO; // default value
styleName = (CFStringRef) CTFontDescriptorCopyAttribute(desc, kCTFontStyleNameAttribute);
styleName = (NSString *) [desc objectForKey:NSFontFaceAttribute];
// TODO is styleName guaranteed?
if (styleName != NULL) {
CFRange range;
// note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL
// TODO is this really the case? or is that just a copy-paste error from the other CFStringCreateXxx() functions? and what's this about -fconstant-cfstring?
range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards);
if (range.location != kCFNotFound)
isOblique = YES;
CFRelease(styleName);
}
if (isOblique)
// TODO NSLiteralSearch?
range = [styleName rangeOfString:@"Oblique" options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound)
return ic->oblique;
return ic->italic;
}
static CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, double targetWeight, uiDrawTextItalic targetItalic, double targetStretch)
static NSFontDescriptor *matchTraits(NSFontDescriptor *against, double targetWeight, uiDrawTextItalic targetItalic, double targetStretch)
{
CFArrayRef matching;
CFIndex i, n;
NSArray<NSFontDescriptor *> *matching;
NSUInteger i, n;
struct closeness *closeness;
CTFontDescriptorRef current;
CTFontDescriptorRef out;
NSFontDescriptor *current;
NSFontDescriptor *out;
matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);
if (matching == NULL)
matching = [against matchingFontDescriptorsWithMandatoryKeys:nil];
if (matching == nil)
// no matches; give the original back and hope for the best
return against;
n = CFArrayGetCount(matching);
n = [matching count];
if (n == 0) {
// likewise
CFRelease(matching);
[matching release];
return against;
}
closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]");
for (i = 0; i < n; i++) {
CFDictionaryRef traits;
NSDictionary *traits;
closeness[i].index = i;
current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, i);
traits = (CFDictionaryRef) CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute);
if (traits == NULL) {
current = (NSFontDescriptor *) [matching objectAtIndex:i];
traits = (NSDictionary *) [current objectAtIndex:NSFontTraitsAttribute];
if (traits == nil) {
// couldn't get traits; be safe by ranking it lowest
// LONGTERM figure out what the longest possible distances are
closeness[i].weight = 3;
@ -128,10 +109,10 @@ static CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, double targe
closeness[i].stretch = 3;
continue;
}
closeness[i].weight = doubleAttr(traits, kCTFontWeightTrait) - targetWeight;
closeness[i].weight = doubleAttr(traits, NSFontWeightTrait) - targetWeight;
closeness[i].italic = italicCloseness(current, traits, targetItalic);
closeness[i].stretch = doubleAttr(traits, kCTFontWidthTrait) - targetStretch;
CFRelease(traits);
closeness[i].stretch = doubleAttr(traits, NSFontWidthTrait) - targetStretch;
// TODO release traits?
}
// now figure out the 3-space difference between the three and sort by that
@ -156,14 +137,15 @@ static CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, double targe
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
out = (NSFontDescriptor *) [matching objectAtIndex:closeness[0].index];
// TODO is this correct?
[out retain]; // get rule
// release everything
uiFree(closeness);
CFRelease(matching);
[matching release];
// and release the original descriptor since we no longer need it
CFRelease(against);
[against release];
return out;
}
@ -222,32 +204,21 @@ static const double stretchesToCTWidths[] = {
[uiDrawTextStretchUltraExpanded] = 0.400000,
};
CTFontDescriptorRef fontdescToCTFontDescriptor(uiDrawFontDescriptor *fd)
NSFontDescriptor *fontdescToNSFontDescriptor(uiDrawFontDescriptor *fd)
{
CFMutableDictionaryRef attrs;
NSMutableDictionary *attrs;
CFStringRef cffamily;
CFNumberRef cfsize;
CTFontDescriptorRef basedesc;
attrs = CFDictionaryCreateMutable(NULL, 2,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (attrs == NULL) {
// TODO
}
cffamily = CFStringCreateWithCString(NULL, fd->Family, kCFStringEncodingUTF8);
if (cffamily == NULL) {
// TODO
}
CFDictionaryAddValue(attrs, kCTFontFamilyNameAttribute, cffamily);
CFRelease(cffamily);
cfsize = CFNumberCreate(NULL, kCFNumberDoubleType, &(fd->Size));
CFDictionaryAddValue(attrs, kCTFontSizeAttribute, cfsize);
CFRelease(cfsize);
attrs = [NSMutableDictionary new];
[attrs setObject:[NSString stringWithUTF8String:fd->Family]
forKey:NSFontFamilyAttribute];
[attrs setObject:[NSNumber numberWithDouble:fd->Size]
forKye:NSFontSizeAttribute];
basedesc = CTFontDescriptorCreateWithAttributes(attrs);
CFRelease(attrs); // TODO correct?
basedesc = [[NSFontDescriptor alloc] initWithFontAttributes:attrs];
[attrs release];
return matchTraits(basedesc,
weightToCTWeight(fd->Weight),
fd->Italic,

View File

@ -147,4 +147,5 @@ extern void doManualMove(NSWindow *w, NSEvent *initialEvent);
extern void doManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge);
// fontmatch.m
extern CTFontDescriptorRef fontdescToCTFontDescriptor(uiDrawFontDescriptor *fd);
//TODO extern CTFontDescriptorRef fontdescToCTFontDescriptor(uiDrawFontDescriptor *fd);
extern NSFontDescriptor *fontdescToNSFontDescriptor(uiDrawFontDescriptor *fd);