First Commit
This commit is contained in:
commit
ae0fab9400
424 files changed
+71006
No files matched your search
@@ -0,0 +1,505 @@
|
||||
// 12 february 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// this is what AppKit does internally
|
||||
// WebKit does this too; see https://github.com/adobe/webkit/blob/master/Source/WebCore/platform/graphics/mac/GraphicsContextMac.mm
|
||||
static NSColor *spellingColor = nil;
|
||||
static NSColor *grammarColor = nil;
|
||||
static NSColor *auxiliaryColor = nil;
|
||||
|
||||
static NSColor *tryColorNamed(NSString *name)
|
||||
{
|
||||
NSImage *img;
|
||||
|
||||
img = [NSImage imageNamed:name];
|
||||
if (img == nil)
|
||||
return nil;
|
||||
return [NSColor colorWithPatternImage:img];
|
||||
}
|
||||
|
||||
void uiprivInitUnderlineColors(void)
|
||||
{
|
||||
spellingColor = tryColorNamed(@"NSSpellingDot");
|
||||
if (spellingColor == nil) {
|
||||
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
|
||||
spellingColor = tryColorNamed(@"SpellingDot");
|
||||
if (spellingColor == nil)
|
||||
spellingColor = [NSColor redColor];
|
||||
}
|
||||
[spellingColor retain]; // override autoreleasing
|
||||
|
||||
grammarColor = tryColorNamed(@"NSGrammarDot");
|
||||
if (grammarColor == nil) {
|
||||
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
|
||||
grammarColor = tryColorNamed(@"GrammarDot");
|
||||
if (grammarColor == nil)
|
||||
grammarColor = [NSColor greenColor];
|
||||
}
|
||||
[grammarColor retain]; // override autoreleasing
|
||||
|
||||
auxiliaryColor = tryColorNamed(@"NSCorrectionDot");
|
||||
if (auxiliaryColor == nil) {
|
||||
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
|
||||
auxiliaryColor = tryColorNamed(@"CorrectionDot");
|
||||
if (auxiliaryColor == nil)
|
||||
auxiliaryColor = [NSColor blueColor];
|
||||
}
|
||||
[auxiliaryColor retain]; // override autoreleasing
|
||||
}
|
||||
|
||||
void uiprivUninitUnderlineColors(void)
|
||||
{
|
||||
[auxiliaryColor release];
|
||||
auxiliaryColor = nil;
|
||||
[grammarColor release];
|
||||
grammarColor = nil;
|
||||
[spellingColor release];
|
||||
spellingColor = nil;
|
||||
}
|
||||
|
||||
// TODO opentype features are lost when using uiFontDescriptor, so a handful of fonts in the font panel ("Titling" variants of some fonts and possibly others but those are the examples I know about) cannot be represented by uiFontDescriptor; what *can* we do about this since this info is NOT part of the font on other platforms?
|
||||
// TODO see if we could use NSAttributedString?
|
||||
// TODO consider renaming this struct and the fep variable(s)
|
||||
// TODO restructure all this so the important details at the top are below with the combined font attributes type?
|
||||
// TODO in fact I should just write something to explain everything in this file...
|
||||
struct foreachParams {
|
||||
CFMutableAttributedStringRef mas;
|
||||
NSMutableArray *backgroundParams;
|
||||
};
|
||||
|
||||
// unlike the other systems, Core Text rolls family, size, weight, italic, width, AND opentype features into the "font" attribute
|
||||
// instead of incrementally adjusting CTFontRefs (which, judging from NSFontManager, seems finicky and UI-centric), we use a custom class to incrementally store attributes that go into a CTFontRef, and then convert everything to CTFonts en masse later
|
||||
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/ChangingAttrStrings.html#//apple_ref/doc/uid/20000162-BBCBGCDG says we must have -hash and -isEqual: workign properly for this to work, so we must do that too, using a basic xor-based hash and leveraging Cocoa -hash implementations where useful and feasible (if not necessary)
|
||||
// TODO structure and rewrite this part
|
||||
// TODO re-find sources proving support of custom attributes
|
||||
// TODO what if this is NULL?
|
||||
static const CFStringRef combinedFontAttrName = CFSTR("libuiCombinedFontAttribute");
|
||||
|
||||
enum {
|
||||
cFamily,
|
||||
cSize,
|
||||
cWeight,
|
||||
cItalic,
|
||||
cStretch,
|
||||
cFeatures,
|
||||
nc,
|
||||
};
|
||||
|
||||
static const int toc[] = {
|
||||
[uiAttributeTypeFamily] = cFamily,
|
||||
[uiAttributeTypeSize] = cSize,
|
||||
[uiAttributeTypeWeight] = cWeight,
|
||||
[uiAttributeTypeItalic] = cItalic,
|
||||
[uiAttributeTypeStretch] = cStretch,
|
||||
[uiAttributeTypeFeatures] = cFeatures,
|
||||
};
|
||||
|
||||
static uiForEach featuresHash(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
|
||||
{
|
||||
NSUInteger *hash = (NSUInteger *) data;
|
||||
uint32_t tag;
|
||||
|
||||
tag = (((uint32_t) a) & 0xFF) << 24;
|
||||
tag |= (((uint32_t) b) & 0xFF) << 16;
|
||||
tag |= (((uint32_t) c) & 0xFF) << 8;
|
||||
tag |= ((uint32_t) d) & 0xFF;
|
||||
*hash ^= tag;
|
||||
*hash ^= value;
|
||||
return uiForEachContinue;
|
||||
}
|
||||
|
||||
@interface uiprivCombinedFontAttr : NSObject<NSCopying> {
|
||||
uiAttribute *attrs[nc];
|
||||
BOOL hasHash;
|
||||
NSUInteger hash;
|
||||
}
|
||||
- (void)addAttribute:(uiAttribute *)attr;
|
||||
- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont;
|
||||
@end
|
||||
|
||||
@implementation uiprivCombinedFontAttr
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
memset(self->attrs, 0, nc * sizeof (uiAttribute *));
|
||||
self->hasHash = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nc; i++)
|
||||
if (self->attrs[i] != NULL) {
|
||||
uiprivAttributeRelease(self->attrs[i]);
|
||||
self->attrs[i] = NULL;
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
uiprivCombinedFontAttr *ret;
|
||||
int i;
|
||||
|
||||
ret = [[uiprivCombinedFontAttr allocWithZone:zone] init];
|
||||
for (i = 0; i < nc; i++)
|
||||
if (self->attrs[i] != NULL)
|
||||
ret->attrs[i] = uiprivAttributeRetain(self->attrs[i]);
|
||||
ret->hasHash = self->hasHash;
|
||||
ret->hash = self->hash;
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)addAttribute:(uiAttribute *)attr
|
||||
{
|
||||
int index;
|
||||
|
||||
index = toc[uiAttributeGetType(attr)];
|
||||
if (self->attrs[index] != NULL)
|
||||
uiprivAttributeRelease(self->attrs[index]);
|
||||
self->attrs[index] = uiprivAttributeRetain(attr);
|
||||
self->hasHash = NO;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)bb
|
||||
{
|
||||
uiprivCombinedFontAttr *b = (uiprivCombinedFontAttr *) bb;
|
||||
int i;
|
||||
|
||||
if (b == nil)
|
||||
return NO;
|
||||
for (i = 0; i < nc; i++) {
|
||||
if (self->attrs[i] == NULL && b->attrs[i] == NULL)
|
||||
continue;
|
||||
if (self->attrs[i] == NULL || b->attrs[i] == NULL)
|
||||
return NO;
|
||||
if (!uiprivAttributeEqual(self->attrs[i], b->attrs[i]))
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
if (self->hasHash)
|
||||
return self->hash;
|
||||
@autoreleasepool {
|
||||
NSString *family;
|
||||
NSNumber *size;
|
||||
|
||||
self->hash = 0;
|
||||
if (self->attrs[cFamily] != NULL) {
|
||||
family = [NSString stringWithUTF8String:uiAttributeFamily(self->attrs[cFamily])];
|
||||
// TODO make sure this aligns with case-insensitive compares when those are done in common/attribute.c
|
||||
self->hash ^= [[family uppercaseString] hash];
|
||||
}
|
||||
if (self->attrs[cSize] != NULL) {
|
||||
size = [NSNumber numberWithDouble:uiAttributeSize(self->attrs[cSize])];
|
||||
self->hash ^= [size hash];
|
||||
}
|
||||
if (self->attrs[cWeight] != NULL)
|
||||
self->hash ^= (NSUInteger) uiAttributeWeight(self->attrs[cWeight]);
|
||||
if (self->attrs[cItalic] != NULL)
|
||||
self->hash ^= (NSUInteger) uiAttributeItalic(self->attrs[cItalic]);
|
||||
if (self->attrs[cStretch] != NULL)
|
||||
self->hash ^= (NSUInteger) uiAttributeStretch(self->attrs[cStretch]);
|
||||
if (self->attrs[cFeatures] != NULL)
|
||||
uiOpenTypeFeaturesForEach(uiAttributeFeatures(self->attrs[cFeatures]), featuresHash, &(self->hash));
|
||||
self->hasHash = YES;
|
||||
}
|
||||
return self->hash;
|
||||
}
|
||||
|
||||
- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont
|
||||
{
|
||||
uiFontDescriptor uidesc;
|
||||
CTFontDescriptorRef desc;
|
||||
CTFontRef font;
|
||||
|
||||
uidesc = *defaultFont;
|
||||
if (self->attrs[cFamily] != NULL)
|
||||
// TODO const-correct uiFontDescriptor or change this function below
|
||||
uidesc.Family = (char *) uiAttributeFamily(self->attrs[cFamily]);
|
||||
if (self->attrs[cSize] != NULL)
|
||||
uidesc.Size = uiAttributeSize(self->attrs[cSize]);
|
||||
if (self->attrs[cWeight] != NULL)
|
||||
uidesc.Weight = uiAttributeWeight(self->attrs[cWeight]);
|
||||
if (self->attrs[cItalic] != NULL)
|
||||
uidesc.Italic = uiAttributeItalic(self->attrs[cItalic]);
|
||||
if (self->attrs[cStretch] != NULL)
|
||||
uidesc.Stretch = uiAttributeStretch(self->attrs[cStretch]);
|
||||
desc = uiprivFontDescriptorToCTFontDescriptor(&uidesc);
|
||||
if (self->attrs[cFeatures] != NULL)
|
||||
desc = uiprivCTFontDescriptorAppendFeatures(desc, uiAttributeFeatures(self->attrs[cFeatures]));
|
||||
font = CTFontCreateWithFontDescriptor(desc, uidesc.Size, NULL);
|
||||
CFRelease(desc); // TODO correct?
|
||||
return font;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void addFontAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr)
|
||||
{
|
||||
uiprivCombinedFontAttr *cfa;
|
||||
CFRange range;
|
||||
size_t diff;
|
||||
|
||||
while (start < end) {
|
||||
cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(p->mas, start, combinedFontAttrName, &range);
|
||||
if (cfa == nil)
|
||||
cfa = [uiprivCombinedFontAttr new];
|
||||
else
|
||||
cfa = [cfa copy];
|
||||
[cfa addAttribute:attr];
|
||||
// clamp range within [start, end)
|
||||
if (range.location < start) {
|
||||
diff = start - range.location;
|
||||
range.location = start;
|
||||
range.length -= diff;
|
||||
}
|
||||
if ((range.location + range.length) > end)
|
||||
range.length = end - range.location;
|
||||
CFAttributedStringSetAttribute(p->mas, range, combinedFontAttrName, cfa);
|
||||
[cfa release];
|
||||
start += range.length;
|
||||
}
|
||||
}
|
||||
|
||||
static CGColorRef mkcolor(double r, double g, double b, double a)
|
||||
{
|
||||
CGColorSpaceRef colorspace;
|
||||
CGColorRef color;
|
||||
CGFloat components[4];
|
||||
|
||||
// TODO we should probably just create this once and recycle it throughout program execution...
|
||||
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
if (colorspace == NULL) {
|
||||
// TODO
|
||||
}
|
||||
components[0] = r;
|
||||
components[1] = g;
|
||||
components[2] = b;
|
||||
components[3] = a;
|
||||
color = CGColorCreate(colorspace, components);
|
||||
CFRelease(colorspace);
|
||||
return color;
|
||||
}
|
||||
|
||||
static void addBackgroundAttribute(struct foreachParams *p, size_t start, size_t end, double r, double g, double b, double a)
|
||||
{
|
||||
uiprivDrawTextBackgroundParams *dtb;
|
||||
|
||||
// TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course)
|
||||
if (uiprivFUTURE_kCTBackgroundColorAttributeName != NULL) {
|
||||
CGColorRef color;
|
||||
CFRange range;
|
||||
|
||||
color = mkcolor(r, g, b, a);
|
||||
range.location = start;
|
||||
range.length = end - start;
|
||||
CFAttributedStringSetAttribute(p->mas, range, *uiprivFUTURE_kCTBackgroundColorAttributeName, color);
|
||||
CFRelease(color);
|
||||
return;
|
||||
}
|
||||
|
||||
dtb = [[uiprivDrawTextBackgroundParams alloc] initWithStart:start end:end r:r g:g b:b a:a];
|
||||
[p->backgroundParams addObject:dtb];
|
||||
[dtb release];
|
||||
}
|
||||
|
||||
static uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data)
|
||||
{
|
||||
struct foreachParams *p = (struct foreachParams *) data;
|
||||
CFRange range;
|
||||
CGColorRef color;
|
||||
int32_t us;
|
||||
CFNumberRef num;
|
||||
double r, g, b, a;
|
||||
uiUnderlineColor colorType;
|
||||
|
||||
start = uiprivAttributedStringUTF8ToUTF16(s, start);
|
||||
end = uiprivAttributedStringUTF8ToUTF16(s, end);
|
||||
range.location = start;
|
||||
range.length = end - start;
|
||||
switch (uiAttributeGetType(attr)) {
|
||||
case uiAttributeTypeFamily:
|
||||
case uiAttributeTypeSize:
|
||||
case uiAttributeTypeWeight:
|
||||
case uiAttributeTypeItalic:
|
||||
case uiAttributeTypeStretch:
|
||||
case uiAttributeTypeFeatures:
|
||||
addFontAttributeToRange(p, start, end, attr);
|
||||
break;
|
||||
case uiAttributeTypeColor:
|
||||
uiAttributeColor(attr, &r, &g, &b, &a);
|
||||
color = mkcolor(r, g, b, a);
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTForegroundColorAttributeName, color);
|
||||
CFRelease(color);
|
||||
break;
|
||||
case uiAttributeTypeBackground:
|
||||
uiAttributeColor(attr, &r, &g, &b, &a);
|
||||
addBackgroundAttribute(p, start, end, r, g, b, a);
|
||||
break;
|
||||
// TODO turn into a class, like we did with the font attributes, or even integrate *into* the font attributes
|
||||
case uiAttributeTypeUnderline:
|
||||
switch (uiAttributeUnderline(attr)) {
|
||||
case uiUnderlineNone:
|
||||
us = kCTUnderlineStyleNone;
|
||||
break;
|
||||
case uiUnderlineSingle:
|
||||
us = kCTUnderlineStyleSingle;
|
||||
break;
|
||||
case uiUnderlineDouble:
|
||||
us = kCTUnderlineStyleDouble;
|
||||
break;
|
||||
case uiUnderlineSuggestion:
|
||||
// TODO incorrect if a solid color
|
||||
us = kCTUnderlineStyleThick;
|
||||
break;
|
||||
}
|
||||
num = CFNumberCreate(NULL, kCFNumberSInt32Type, &us);
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineStyleAttributeName, num);
|
||||
CFRelease(num);
|
||||
break;
|
||||
case uiAttributeTypeUnderlineColor:
|
||||
uiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a);
|
||||
switch (colorType) {
|
||||
case uiUnderlineColorCustom:
|
||||
color = mkcolor(r, g, b, a);
|
||||
break;
|
||||
case uiUnderlineColorSpelling:
|
||||
color = [spellingColor CGColor];
|
||||
break;
|
||||
case uiUnderlineColorGrammar:
|
||||
color = [grammarColor CGColor];
|
||||
break;
|
||||
case uiUnderlineColorAuxiliary:
|
||||
color = [auxiliaryColor CGColor];
|
||||
break;
|
||||
}
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineColorAttributeName, color);
|
||||
if (colorType == uiUnderlineColorCustom)
|
||||
CFRelease(color);
|
||||
break;
|
||||
}
|
||||
return uiForEachContinue;
|
||||
}
|
||||
|
||||
static void applyFontAttributes(CFMutableAttributedStringRef mas, uiFontDescriptor *defaultFont)
|
||||
{
|
||||
uiprivCombinedFontAttr *cfa;
|
||||
CTFontRef font;
|
||||
CFRange range;
|
||||
CFIndex n;
|
||||
|
||||
n = CFAttributedStringGetLength(mas);
|
||||
|
||||
// first apply the default font to the entire string
|
||||
// TODO is this necessary given the #if 0'd code in uiprivAttributedStringToCFAttributedString()?
|
||||
cfa = [uiprivCombinedFontAttr new];
|
||||
font = [cfa toCTFontWithDefaultFont:defaultFont];
|
||||
[cfa release];
|
||||
range.location = 0;
|
||||
range.length = n;
|
||||
CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);
|
||||
CFRelease(font);
|
||||
|
||||
// now go through, replacing every uiprivCombinedFontAttr with the proper CTFontRef
|
||||
// we are best off treating series of identical fonts as single ranges ourselves for parity across platforms, even if OS X does something similar itself
|
||||
range.location = 0;
|
||||
while (range.location < n) {
|
||||
// TODO consider seeing if CFAttributedStringGetAttributeAndLongestEffectiveRange() can make things faster by reducing the number of potential iterations, either here or above
|
||||
cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(mas, range.location, combinedFontAttrName, &range);
|
||||
if (cfa != nil) {
|
||||
font = [cfa toCTFontWithDefaultFont:defaultFont];
|
||||
CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);
|
||||
CFRelease(font);
|
||||
}
|
||||
range.location += range.length;
|
||||
}
|
||||
|
||||
// and finally, get rid of all the uiprivCombinedFontAttrs as we won't need them anymore
|
||||
range.location = 0;
|
||||
range.length = 0;
|
||||
CFAttributedStringRemoveAttribute(mas, range, combinedFontAttrName);
|
||||
}
|
||||
|
||||
static const CTTextAlignment ctaligns[] = {
|
||||
[uiDrawTextAlignLeft] = kCTTextAlignmentLeft,
|
||||
[uiDrawTextAlignCenter] = kCTTextAlignmentCenter,
|
||||
[uiDrawTextAlignRight] = kCTTextAlignmentRight,
|
||||
};
|
||||
|
||||
static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)
|
||||
{
|
||||
CTParagraphStyleRef ps;
|
||||
CTParagraphStyleSetting settings[16];
|
||||
size_t nSettings = 0;
|
||||
|
||||
settings[nSettings].spec = kCTParagraphStyleSpecifierAlignment;
|
||||
settings[nSettings].valueSize = sizeof (CTTextAlignment);
|
||||
settings[nSettings].value = ctaligns + p->Align;
|
||||
nSettings++;
|
||||
|
||||
ps = CTParagraphStyleCreate(settings, nSettings);
|
||||
if (ps == NULL) {
|
||||
// TODO
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
// TODO either rename this (on all platforms) to uiprivDrawTextLayoutParams... or rename this file or both or split the struct or something else...
|
||||
CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams)
|
||||
{
|
||||
CFStringRef cfstr;
|
||||
CFMutableDictionaryRef defaultAttrs;
|
||||
CTParagraphStyleRef ps;
|
||||
CFAttributedStringRef base;
|
||||
CFMutableAttributedStringRef mas;
|
||||
struct foreachParams fep;
|
||||
|
||||
cfstr = CFStringCreateWithCharacters(NULL, uiprivAttributedStringUTF16String(p->String), uiprivAttributedStringUTF16Len(p->String));
|
||||
if (cfstr == NULL) {
|
||||
// TODO
|
||||
}
|
||||
defaultAttrs = CFDictionaryCreateMutable(NULL, 0,
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
if (defaultAttrs == NULL) {
|
||||
// TODO
|
||||
}
|
||||
#if 0 /* TODO */
|
||||
ffp.desc = *(p->DefaultFont);
|
||||
defaultCTFont = fontdescToCTFont(&ffp);
|
||||
CFDictionaryAddValue(defaultAttrs, kCTFontAttributeName, defaultCTFont);
|
||||
CFRelease(defaultCTFont);
|
||||
#endif
|
||||
ps = mkParagraphStyle(p);
|
||||
CFDictionaryAddValue(defaultAttrs, kCTParagraphStyleAttributeName, ps);
|
||||
CFRelease(ps);
|
||||
|
||||
base = CFAttributedStringCreate(NULL, cfstr, defaultAttrs);
|
||||
if (base == NULL) {
|
||||
// TODO
|
||||
}
|
||||
CFRelease(cfstr);
|
||||
CFRelease(defaultAttrs);
|
||||
mas = CFAttributedStringCreateMutableCopy(NULL, 0, base);
|
||||
CFRelease(base);
|
||||
|
||||
CFAttributedStringBeginEditing(mas);
|
||||
fep.mas = mas;
|
||||
fep.backgroundParams = [NSMutableArray new];
|
||||
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep);
|
||||
applyFontAttributes(mas, p->DefaultFont);
|
||||
CFAttributedStringEndEditing(mas);
|
||||
|
||||
*backgroundParams = fep.backgroundParams;
|
||||
return mas;
|
||||
}
|
||||
Reference in new issue
Block a user