1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-11-25 14:13:04 +03:00
vimr/SwiftNeoVim/TextDrawer.m

278 lines
8.2 KiB
Objective-C

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*
* Almost a verbatim copy from MacVim by Bjorn Winckler
* See VIM.LICENSE
*/
#import "TextDrawer.h"
#import "MMCoreTextView.h"
#define ALPHA(color_code) (((color_code >> 24) & 0xff) / 255.0f)
#define RED(color_code) (((color_code >> 16) & 0xff) / 255.0f)
#define GREEN(color_code) (((color_code >> 8) & 0xff) / 255.0f)
#define BLUE(color_code) (((color_code ) & 0xff) / 255.0f)
static dispatch_once_t token;
static NSMutableDictionary<NSNumber *, NSColor *> *colorCache;
static CGColorRef color_for(NSInteger value) {
NSColor *color = colorCache[@(value)];
if (color != nil) {
return color.CGColor;
}
color = [NSColor colorWithSRGBRed:RED(value) green:GREEN(value) blue:BLUE(value) alpha:1];
colorCache[@(value)] = color;
return color.CGColor;
}
@implementation TextDrawer {
NSLayoutManager *_layoutManager;
NSFont *_font;
CGFloat _ascent;
CGFloat _underlinePosition;
CGFloat _underlineThickness;
CGFloat _linespacing;
NSMutableArray *_fontLookupCache;
NSMutableDictionary *_fontTraitCache;
}
- (CGFloat)baselineOffset {
return _cellSize.height - _ascent;
}
- (void)setLinespacing:(CGFloat)linespacing {
// FIXME: reasonable min and max
_linespacing = linespacing;
_cellSize = [self cellSizeWithFont:_font linespacing:_linespacing];
}
- (void)setFont:(NSFont *)font {
[_font autorelease];
_font = [font retain];
[_fontTraitCache removeAllObjects];
[_fontLookupCache removeAllObjects];
_cellSize = [self cellSizeWithFont:font linespacing:_linespacing];
_ascent = CTFontGetAscent((CTFontRef) _font);
_leading = CTFontGetLeading((CTFontRef) _font);
_descent = CTFontGetDescent((CTFontRef) _font);
_underlinePosition = CTFontGetUnderlinePosition((CTFontRef) _font); // This seems to take the thickness into account
// TODO: Maybe we should use 0.5 or 1 as minimum thickness for Retina and non-Retina, respectively.
_underlineThickness = CTFontGetUnderlineThickness((CTFontRef) _font);
}
- (instancetype _Nonnull)initWithFont:(NSFont *_Nonnull)font {
dispatch_once (&token, ^{
colorCache = [[NSMutableDictionary alloc] init];
});
self = [super init];
if (self == nil) {
return nil;
}
_usesLigatures = NO;
_linespacing = 1;
_layoutManager = [[NSLayoutManager alloc] init];
_fontLookupCache = [[NSMutableArray alloc] init];
_fontTraitCache = [[NSMutableDictionary alloc] init];
self.font = font;
return self;
}
- (void)dealloc {
[_layoutManager release];
[_font release];
[_fontLookupCache release];
[_fontTraitCache release];
[super dealloc];
}
/**
* We assume that the background is drawn elsewhere and that the caller has already called
*
* CGContextSetTextMatrix(context, CGAffineTransformIdentity); // or some other matrix
* CGContextSetTextDrawingMode(context, kCGTextFill); // or some other mode
*/
- (void)drawString:(NSString *_Nonnull)string
positions:(CGPoint *_Nonnull)positions
positionsCount:(NSInteger)positionsCount
highlightAttrs:(CellAttributes)attrs
context:(CGContextRef _Nonnull)context
{
CGContextSaveGState(context);
[self drawString:string positions:positions
fontTrait:attrs.fontTrait foreground:attrs.foreground
context:context];
if (attrs.fontTrait & FontTraitUnderline) {
[self drawUnderline:positions count:positionsCount color:attrs.foreground context:context];
}
if (attrs.fontTrait & FontTraitUndercurl) {
[self drawUntercurl:positions count:positionsCount color:attrs.special context:context];
}
CGContextRestoreGState(context);
}
- (void)drawUntercurl:(const CGPoint *_Nonnull)positions
count:(NSInteger)count
color:(NSInteger)color
context:(CGContextRef _Nonnull)context
{
CGFloat x0 = positions[0].x;
CGFloat y0 = positions[0].y - 0.1 * _cellSize.height;
CGFloat w = _cellSize.width;
CGFloat h = 0.5 * _descent;
CGContextMoveToPoint(context, x0, y0);
for (int k = 0; k < count; k++) {
CGContextAddCurveToPoint(context, x0 + 0.25 * w, y0, x0 + 0.25 * w, y0 + h, x0 + 0.5 * w, y0 + h);
CGContextAddCurveToPoint(context, x0 + 0.75 * w, y0 + h, x0 + 0.75 * w, y0, x0 + w, y0);
x0 += w;
}
CGContextSetStrokeColorWithColor(context, color_for(color));
CGContextStrokePath(context);
}
- (void)drawUnderline:(const CGPoint *_Nonnull)positions
count:(NSInteger)count
color:(NSInteger)color
context:(CGContextRef _Nonnull)context
{
CGContextSetFillColorWithColor(context, color_for(color));
CGRect rect = {
{positions[0].x, positions[0].y + _underlinePosition},
{positions[0].x + positions[count - 1].x + _cellSize.width, _underlineThickness}
};
CGContextFillRect(context, rect);
}
- (void)drawString:(NSString *_Nonnull)nsstring
positions:(CGPoint *_Nonnull)positions
fontTrait:(FontTrait)fontTrait
foreground:(NSInteger)foreground
context:(CGContextRef _Nonnull)context
{
CFStringRef string = (CFStringRef) nsstring;
UniChar *unibuffer = NULL;
UniCharCount unilength = (UniCharCount) CFStringGetLength(string);
const UniChar *unichars = CFStringGetCharactersPtr(string);
if (unichars == NULL) {
unibuffer = malloc(unilength * sizeof(UniChar));
CFStringGetCharacters(string, CFRangeMake(0, unilength), unibuffer);
unichars = unibuffer;
}
CGGlyph *glyphs = malloc(unilength * sizeof(CGGlyph));
CTFontRef fontWithTraits = [self fontWithTrait:fontTrait];
CGContextSetFillColorWithColor(context, color_for(foreground));
CGGlyph *g = glyphs;
CGPoint *p = positions;
const UniChar *b = unichars;
const UniChar *bStart = unichars;
const UniChar *bEnd = unichars + unilength;
UniCharCount choppedLength;
bool wide;
bool pWide = NO;
while (b < bEnd) {
wide = CFStringIsSurrogateHighCharacter(*b) || CFStringIsSurrogateLowCharacter(*b);
if ((b > unichars) && (wide != pWide)) {
choppedLength = b - bStart;
// NSString *logged = [NSString stringWithCharacters:bStart length:choppedLength];
// NSLog(@"C(%d,%p..%p)[%@]", pWide, bStart, b, logged);
recurseDraw(bStart, glyphs, p, choppedLength, context, fontWithTraits, _fontLookupCache, _usesLigatures);
UniCharCount step = pWide ? choppedLength / 2 : choppedLength;
p += step;
g += step;
bStart = b;
}
pWide = wide;
b++;
}
if (bStart < bEnd) {
choppedLength = b - bStart;
// NSString *logged = [NSString stringWithCharacters:bStart length:choppedLength];
// NSLog(@"T(%d,%p..%p)[%@]", pWide, bStart, b, logged);
recurseDraw(bStart, glyphs, p, choppedLength, context, fontWithTraits, _fontLookupCache, _usesLigatures);
}
// NSLog(@"S(-,%p..%p)[%@]", unichars, unichars + unilength, string);
CFRelease(fontWithTraits);
free(glyphs);
if (unibuffer != NULL) {
free(unibuffer);
}
}
- (CGSize)cellSizeWithFont:(NSFont *)font linespacing:(CGFloat)linespacing {
// cf. https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html
CGFloat ascent = CTFontGetAscent((CTFontRef) _font);
CGFloat descent = CTFontGetDescent((CTFontRef) _font);
CGFloat leading = CTFontGetLeading((CTFontRef) _font);
CGSize result = CGSizeMake(
round([@"m" sizeWithAttributes:@{ NSFontAttributeName : _font }].width),
ceil(linespacing * (ascent + descent + leading))
);
return result;
}
/**
* The caller _must_ CFRelease the returned CTFont!
*/
- (CTFontRef)fontWithTrait:(FontTrait)fontTrait {
if (fontTrait == FontTraitNone) {
return CFRetain(_font);
}
CTFontSymbolicTraits traits = (CTFontSymbolicTraits) 0;
if (fontTrait & FontTraitBold) {
traits |= kCTFontBoldTrait;
}
if (fontTrait & FontTraitItalic) {
traits |= kCTFontItalicTrait;
}
if (traits == 0) {
return CFRetain(_font);
}
NSFont *cachedFont = _fontTraitCache[@(traits)];
if (cachedFont != nil) {
return CFRetain(cachedFont);
}
CTFontRef fontWithTraits = CTFontCreateCopyWithSymbolicTraits((CTFontRef) _font, 0.0, NULL, traits, traits);
if (fontWithTraits == NULL) {
return CFRetain(_font);
}
_fontTraitCache[@(traits)] = (NSFont *) fontWithTraits;
return fontWithTraits;
}
@end