2016-06-08 19:17:12 +03:00
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
|
|
|
|
@implementation TextDrawer {
|
2016-06-18 12:43:37 +03:00
|
|
|
NSLayoutManager *_layoutManager;
|
|
|
|
|
|
|
|
NSFont *_font;
|
2016-10-26 23:42:49 +03:00
|
|
|
CGFloat _ascent;
|
2016-06-19 14:39:20 +03:00
|
|
|
CGFloat _underlinePosition;
|
|
|
|
CGFloat _underlineThickness;
|
2016-10-26 23:42:49 +03:00
|
|
|
CGFloat _linespacing;
|
2016-06-18 12:43:37 +03:00
|
|
|
|
|
|
|
NSMutableArray *_fontLookupCache;
|
|
|
|
NSMutableDictionary *_fontTraitCache;
|
|
|
|
}
|
|
|
|
|
2016-10-26 23:42:49 +03:00
|
|
|
- (CGFloat)baselineOffset {
|
|
|
|
return _cellSize.height - _ascent;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setLinespacing:(CGFloat)linespacing {
|
|
|
|
// FIXME: reasonable min and max
|
|
|
|
_linespacing = linespacing;
|
|
|
|
_cellSize = [self cellSizeWithFont:_font linespacing:_linespacing];
|
|
|
|
}
|
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
- (void)setFont:(NSFont *)font {
|
|
|
|
[_font autorelease];
|
2016-07-06 20:51:31 +03:00
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
_font = [font retain];
|
2016-07-06 20:51:31 +03:00
|
|
|
[_fontTraitCache removeAllObjects];
|
|
|
|
[_fontLookupCache removeAllObjects];
|
2016-06-18 12:43:37 +03:00
|
|
|
|
2016-10-26 23:42:49 +03:00
|
|
|
_cellSize = [self cellSizeWithFont:font linespacing:_linespacing];
|
2016-06-19 14:39:20 +03:00
|
|
|
|
2016-10-26 23:42:49 +03:00
|
|
|
_ascent = CTFontGetAscent((CTFontRef) _font);
|
|
|
|
_leading = CTFontGetLeading((CTFontRef) _font);
|
|
|
|
_descent = CTFontGetDescent((CTFontRef) _font);
|
|
|
|
_underlinePosition = CTFontGetUnderlinePosition((CTFontRef) _font); // This seems to take the thickness into account
|
2016-06-19 14:39:20 +03:00
|
|
|
// TODO: Maybe we should use 0.5 or 1 as minimum thickness for Retina and non-Retina, respectively.
|
2016-10-26 23:42:49 +03:00
|
|
|
_underlineThickness = CTFontGetUnderlineThickness((CTFontRef) _font);
|
2016-06-08 19:17:12 +03:00
|
|
|
}
|
|
|
|
|
2016-10-26 23:42:49 +03:00
|
|
|
- (instancetype _Nonnull)initWithFont:(NSFont *_Nonnull)font {
|
2016-06-08 19:17:12 +03:00
|
|
|
self = [super init];
|
|
|
|
if (self == nil) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2016-10-26 23:42:49 +03:00
|
|
|
_usesLigatures = NO;
|
|
|
|
_linespacing = 1;
|
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
_layoutManager = [[NSLayoutManager alloc] init];
|
|
|
|
_fontLookupCache = [[NSMutableArray alloc] init];
|
|
|
|
_fontTraitCache = [[NSMutableDictionary alloc] init];
|
|
|
|
|
|
|
|
self.font = font;
|
2016-06-08 19:17:12 +03:00
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
2016-06-18 12:43:37 +03:00
|
|
|
[_layoutManager release];
|
|
|
|
[_font release];
|
|
|
|
[_fontLookupCache release];
|
|
|
|
[_fontTraitCache release];
|
2016-06-08 19:17:12 +03:00
|
|
|
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-06-18 12:43:37 +03:00
|
|
|
* We assume that the background is drawn elsewhere and that the caller has already called
|
2016-06-08 19:17:12 +03:00
|
|
|
*
|
|
|
|
* CGContextSetTextMatrix(context, CGAffineTransformIdentity); // or some other matrix
|
|
|
|
* CGContextSetTextDrawingMode(context, kCGTextFill); // or some other mode
|
|
|
|
*/
|
2016-06-18 12:43:37 +03:00
|
|
|
- (void)drawString:(NSString *_Nonnull)string
|
|
|
|
positions:(CGPoint *_Nonnull)positions
|
|
|
|
positionsCount:(NSInteger)positionsCount
|
|
|
|
highlightAttrs:(CellAttributes)attrs
|
|
|
|
context:(CGContextRef _Nonnull)context
|
|
|
|
{
|
|
|
|
CGContextSaveGState(context);
|
|
|
|
|
|
|
|
if (attrs.fontTrait & FontTraitUnderline) {
|
2016-07-13 21:26:39 +03:00
|
|
|
[self drawUnderline:positions count:positionsCount color:attrs.foreground context:context];
|
2016-06-18 12:43:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[self drawString:string positions:positions
|
|
|
|
fontTrait:attrs.fontTrait foreground:attrs.foreground
|
|
|
|
context:context];
|
|
|
|
|
|
|
|
CGContextRestoreGState(context);
|
|
|
|
}
|
|
|
|
|
2016-06-19 14:39:20 +03:00
|
|
|
- (void)drawUnderline:(const CGPoint *_Nonnull)positions
|
|
|
|
count:(NSInteger)count
|
|
|
|
color:(unsigned int)color
|
|
|
|
context:(CGContextRef _Nonnull)context
|
|
|
|
{
|
2016-06-18 12:43:37 +03:00
|
|
|
CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color), ALPHA(color));
|
2016-06-19 14:39:20 +03:00
|
|
|
CGRect rect = {
|
|
|
|
{positions[0].x, positions[0].y + _underlinePosition},
|
|
|
|
{positions[0].x + positions[count - 1].x + _cellSize.width, _underlineThickness}
|
|
|
|
};
|
2016-06-18 12:43:37 +03:00
|
|
|
CGContextFillRect(context, rect);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawString:(NSString *_Nonnull)nsstring
|
2016-06-08 19:17:12 +03:00
|
|
|
positions:(CGPoint *_Nonnull)positions
|
2016-06-18 12:43:37 +03:00
|
|
|
fontTrait:(FontTrait)fontTrait
|
2016-06-16 19:36:26 +03:00
|
|
|
foreground:(unsigned int)foreground
|
2016-06-08 19:17:12 +03:00
|
|
|
context:(CGContextRef _Nonnull)context
|
|
|
|
{
|
2016-06-18 12:43:37 +03:00
|
|
|
CFStringRef string = (CFStringRef) nsstring;
|
2016-06-08 19:17:12 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-06-19 14:39:20 +03:00
|
|
|
CGGlyph *glyphs = malloc(unilength * sizeof(CGGlyph));
|
2016-06-18 12:43:37 +03:00
|
|
|
CTFontRef fontWithTraits = [self fontWithTrait:fontTrait];
|
2016-06-08 19:17:12 +03:00
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
CGContextSetRGBFillColor(context, RED(foreground), GREEN(foreground), BLUE(foreground), 1.0);
|
2016-07-31 00:04:20 +03:00
|
|
|
recurseDraw(unichars, glyphs, positions, unilength, context, fontWithTraits, _fontLookupCache, _usesLigatures);
|
2016-06-08 19:17:12 +03:00
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
CFRelease(fontWithTraits);
|
|
|
|
free(glyphs);
|
2016-06-08 19:17:12 +03:00
|
|
|
if (unibuffer != NULL) {
|
|
|
|
free(unibuffer);
|
|
|
|
}
|
2016-06-18 12:43:37 +03:00
|
|
|
}
|
2016-06-08 19:17:12 +03:00
|
|
|
|
2016-10-26 23:42:49 +03:00
|
|
|
- (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;
|
|
|
|
}
|
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2016-07-13 23:09:49 +03:00
|
|
|
if (traits == 0) {
|
|
|
|
return CFRetain(_font);
|
|
|
|
}
|
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
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;
|
2016-06-08 19:17:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|