I am creating the custom CTLine struct for my project. It is a very simple implementation because it doesn't use CFAttributeString to create line. The whole line has one color and all glyphs have equal size. But the longer CFString is, the more time it takes to create it, and it is slower than CTLineCreateWithAttributedString().
#import <Cocoa/Cocoa.h>
#define sc (CFStringRef)
struct Line {
CGGlyph*line_glyphs;
CGPoint*point;
CTFontRef font;
int length;
};
typedef struct Line* LineRef;
LineRef create(CFStringRef str, CTFontRef font);
@interface View : NSView
{
LineRef line;
CTLineRef l;
CTFontRef font;
CGFontRef font2;
CFMutableStringRef string;
}
@end
#import "View.h"
#include <time.h>
LineRef create(CFStringRef str, CTFontRef font){
LineRef line = malloc(sizeof(struct Line));
long length = CFStringGetLength(str);
CGGlyph* gl = malloc(sizeof(CGGlyph)*length);
CGPoint* points = malloc(sizeof(CGPoint)*length);
CGRect rects[length];
UniChar buffer[length];
CFStringGetCharacters(str, CFRangeMake(0, length), buffer);
CTFontGetGlyphsForCharacters(font, buffer, gl, length);
CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationHorizontal, gl, rects, length);
int x_offset = 0;
int y = 200;
CGPoint temp;
for(int i = 0;i<length;i++)
{
temp = CGPointMake(x_offset , y);
x_offset += rects[i].size.width + (rects[i].size.width == 0)*10;
points[i] = temp;
}
line->line_glyphs = gl;
line->point = points;
line->font = font;
line->length = length;
return line;
}
@implementation View
-(void)awakeFromNib{
font = CTFontCreateWithName(CFSTR("Comic Sans MS"), 30, 0);
font2= CGFontCreateWithFontName(CFSTR("Comic Sans MS"));
line = create(CFSTR(""), font);
string = CFStringCreateMutable(kCFAllocatorDefault, 0);
[[self window] makeFirstResponder:self];
}
-(void)keyDown:(NSEvent *)event{
static int index = 0;
NSString* i = [event characters];
CFStringAppend(string,sc i);
CFMutableAttributedStringRef s = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString(s, CFRangeMake(0, 0), string);
CFAttributedStringSetAttribute(s, CFRangeMake(0, CFStringGetLength(string)), kCTFontAttributeName, font);
CFAbsoluteTime t1 = CFAbsoluteTimeGetCurrent();
l = CTLineCreateWithAttributedString(s);
CFAbsoluteTime t2 = CFAbsoluteTimeGetCurrent();
double d1 = t2-t1;
CFAbsoluteTime T1 = CFAbsoluteTimeGetCurrent();
line = create(string, font);
CFAbsoluteTime T2 = CFAbsoluteTimeGetCurrent();
double d2 = T2 - T1;
printf("test:%i core text: %f my implem : %f \n",index, d1,d2);
index++;
}
@end
And output:
test:0 core text: 0.000761 my implem : 0.000016
test:1 core text: 0.000047 my implem : 0.000029
test:2 core text: 0.000041 my implem : 0.000027
test:3 core text: 0.000045 my implem : 0.000032
test:4 core text: 0.000045 my implem : 0.000032
test:5 core text: 0.000046 my implem : 0.000034
...
test:176 core text: 0.000068 my implem : 0.000151
test:177 core text: 0.000084 my implem : 0.000171
test:178 core text: 0.000099 my implem : 0.000230
test:179 core text: 0.000061 my implem : 0.000145
test:180 core text: 0.000071 my implem : 0.000224
test:181 core text: 0.000057 my implem : 0.000149
You see that first couple calls are faster then CTLine creation but than it starts to take more time for my implementation to do the work. Maybe it's multiple CTFontGetGlyphsForCharacters() calls fault? Can you give some advice to speed up this code?
tl;dr Use CTLineCreateWithAttributedString()
where necessary and move on; there are much larger performance issues elsewhere in your pipeline. CGContextDrawPath()
is your real nemesis.
By way of credentials, I wrote an iOS app that allows OSM's entire dataset to be rendered down and displayed on an iPhone - all the way down to footpaths and buildings. It looks like this (note that all the little feet for the footpaths are glyphs in a font):
My pipeline looks a little like this:
CTLineCreateWithAttributedString()
on the remaining strings.CTRun
and use CTFontCreatePathForGlyph()
to get each glyphs path.CGContextDrawPath()
the remaining pathsSteps 1 and 4 are the place to optimise. Drawing wiggly lines (glyphs) is computationaly intensive, there's no way of getting around that. The only winning move is not to play ¹.
(For more optimising fun, take look at MinimumRubber, specifically the MRPathMetrics
functions which can be used to avoid expensive CGPath
based functions)