Archived
1

Fundamental Redesign of the View and Controller

This commit is contained in:
Kim Wittenburg
2014-08-22 00:54:13 +02:00
parent a37d587e1f
commit c024886241
12 changed files with 255 additions and 150 deletions

View File

@@ -61,7 +61,6 @@
#pragma mark Actions #pragma mark Actions
- (IBAction)changeExpression:(id)sender { - (IBAction)changeExpression:(id)sender {
[self.resultExpressionView.expressionStorage insertElement:@"abc" atLocation:6]; [self.resultExpressionView.expressionStorage insertElement:@"abc" atLocation:6];
self.resultExpressionView.needsDisplay = YES;
} }
@end @end

View File

@@ -14,6 +14,6 @@
- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage; - (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage;
@property (readonly, nonatomic, weak) MPExpression *expression; // Convenience @property (readonly, nonatomic, weak) MPExpression *expression;
@end @end

View File

@@ -9,45 +9,38 @@
#import "MPExpressionLayout.h" #import "MPExpressionLayout.h"
#import "MPFunctionLayout.h" #import "MPFunctionLayout.h"
@interface MPExpressionLayout (MPPathGeneration) @interface MPExpressionLayout (MPLineGeneration)
- (NSBezierPath *)bezierPathForChildAtIndex:(NSUInteger)index;
- (NSBezierPath *)generateBezierPathForString:(NSString *)aString;
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
- (CTLineRef)createLineForString:(NSString *)aString;
@end @end
@implementation MPExpressionLayout (MPPathGeneration) @implementation MPExpressionLayout (MPLineGeneration)
- (NSBezierPath *)bezierPathForChildAtIndex:(NSUInteger)index - (CTLineRef)lineForElementAtIndex:(NSUInteger)index
{ {
id symbol = [self.expression elementAtIndex:index]; id element = [self.expression elementAtIndex:index];
if ([symbol isString]) { if (![element isString]) {
return [self cachableObjectForIndex:index return NULL;
generator:^id{ }
return [self generateBezierPathForString:symbol]; NSString *string = element;
id lineObject = [self cachableObjectForIndex:index generator:^id{
CTLineRef line = [self createLineForString:string];
return CFBridgingRelease(line);
}]; }];
} else { return (__bridge CTLineRef)lineObject;
MPLayout *layout = [self childLayoutAtIndex:index];
return layout.bezierPath;
}
} }
- (NSBezierPath *)generateBezierPathForString:(NSString *)aString - (CTLineRef)createLineForString:(NSString *)aString
{ {
NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}];
self.textStorage.attributedString = text; CFAttributedStringRef attributedString = CFBridgingRetain(text);
NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; CTLineRef line = CTLineCreateWithAttributedString(attributedString);
NSGlyph glyphs[glyphRange.length+1]; CFRelease(attributedString); // TODO: Is this release appropriate?
NSUInteger actualGlyphCount = [self.layoutManager getGlyphs:glyphs return line;
range:glyphRange];
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:NSZeroPoint];
[path appendBezierPathWithGlyphs:glyphs
count:actualGlyphCount
inFont:[NSFont fontWithName:@"Lucida Grande" size:18.0]];
return path;
} }
@end @end
@@ -60,6 +53,18 @@
self = [super init]; self = [super init];
if (self) { if (self) {
_expressionStorage = expressionStorage; _expressionStorage = expressionStorage;
_expression = expressionStorage;
}
return self;
}
- (instancetype)initWithPath:(NSIndexPath *)path
parent:(MPLayout *)parent
{
self = [super initWithPath:path
parent:parent];
if (self) {
_expression = [parent.expressionStorage elementAtIndexPath:path];
} }
return self; return self;
} }
@@ -74,52 +79,92 @@
return self.parent.expressionStorage; return self.parent.expressionStorage;
} }
- (MPExpression *)expression //- (MPExpression *)expression
{ //{
return [self.expressionStorage elementAtIndexPath:self.path]; // return [self.expressionStorage elementAtIndexPath:self.path];
} //}
#pragma mark Cache Methods #pragma mark Cache Methods
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index - (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{ {
id cachedObject = [self cachableObjectForIndex:index generator:^id{ id cachedObject = [self cachableObjectForIndex:index generator:^id{
NSIndexPath *indexPath = [self.path indexPathByAddingIndex:index]; NSIndexPath *indexPath = [self.expression.indexPath indexPathByAddingIndex:index];
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath
parent:self]; parent:self];
return layout; return layout;
}]; }];
if ([cachedObject isKindOfClass:[NSBezierPath class]]) { if ([cachedObject isKindOfClass:[MPLayout class]]) {
return nil;
}
return cachedObject; return cachedObject;
} }
return nil;
}
- (NSSize)sizeForChildAtIndex:(NSUInteger)index - (NSSize)sizeForElementAtIndex:(NSUInteger)index
{ {
id symbol = [self.expression elementAtIndex:index]; id symbol = [self.expression elementAtIndex:index];
if ([symbol isString]) { if ([symbol isString]) {
return [self bezierPathForChildAtIndex:index].bounds.size; CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
CGRect bounds = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/ 0);
CFRelease(line);
return bounds.size;
} else { } else {
return [self childLayoutAtIndex:index].size; return [self childLayoutAtIndex:index].size;
} }
} }
#pragma mark Drawing Methods #pragma mark Drawing Methods
- (NSBezierPath *)generateBezierPath - (NSSize)generateSize
{ {
NSBezierPath *fullPath = [NSBezierPath bezierPath]; CGFloat width = 0, height = 0;
[fullPath moveToPoint:NSZeroPoint]; for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
NSUInteger x = 0; NSSize elementSize = [self sizeForElementAtIndex:index];
for (NSInteger index = 0; index < self.expression.numberOfElements; ++index) { width += elementSize.width;
NSAffineTransform *transform = [NSAffineTransform transform]; height = MAX(height, elementSize.height);
// TODO: Translate by the right amount
[transform translateXBy:x yBy:0];
NSBezierPath *path = [self bezierPathForChildAtIndex:index].copy;
[path transformUsingAffineTransform:transform];
[fullPath appendBezierPath:path];
x += path.bounds.size.width;
} }
return fullPath; return NSMakeSize(width, height);
}
- (void)drawAtPoint:(NSPoint)point
{
// Get the current context
CGContextRef context =
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
// Set the text matrix
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Track the x position
CGFloat x = point.x;
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
// The current element
id element = [self.expression elementAtIndex:index];
NSSize elementSize = [self sizeForElementAtIndex:index];
CGFloat dy = (self.size.height - elementSize.height) / 2;
CGFloat y = point.y + dy;
if ([element isString]) {
// Get the line to draw
CTLineRef line = [self lineForElementAtIndex:index];
CFRetain(line);
// Move to the appropriate position
CGContextSetTextPosition(context, x, y);
// Perform the drawing
CTLineDraw(line, context);
CFRelease(line);
} else {
// Let the child layout draw itself
MPLayout *layout = [self childLayoutAtIndex:index];
[layout drawAtPoint:NSMakePoint(x, y)];
}
x += elementSize.width;
}
CGContextRestoreGState(context);
} }
@end @end

View File

@@ -8,10 +8,14 @@
#import "MPExpression.h" #import "MPExpression.h"
@class MPExpressionStorage, MPExpressionLayout; @class MPExpressionStorage, MPExpressionView, MPExpressionLayout;
@interface MPExpressionStorage : MPExpression @interface MPExpressionStorage : MPExpression
- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView
elements:(NSArray *)elements;
@property (readonly, nonatomic, weak) MPExpressionView *expressionView;
@property (nonatomic, strong) MPExpressionLayout *rootLayout; @property (nonatomic, strong) MPExpressionLayout *rootLayout;
- (NSLayoutManager *)layoutManager; - (NSLayoutManager *)layoutManager;

View File

@@ -7,6 +7,7 @@
// //
#import "MPExpressionStorage.h" #import "MPExpressionStorage.h"
#import "MPExpressionView.h"
#import "MPExpressionLayout.h" #import "MPExpressionLayout.h"
#import "MPFunctionLayout.h" #import "MPFunctionLayout.h"
#import "MPRangePath.h" #import "MPRangePath.h"
@@ -19,6 +20,15 @@
@implementation MPExpressionStorage @implementation MPExpressionStorage
- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView elements:(NSArray *)elements
{
self = [self initWithElements:elements];
if (self) {
_expressionView = expressionView;
}
return self;
}
- (instancetype)initWithElements:(NSArray *)elements - (instancetype)initWithElements:(NSArray *)elements
{ {
self = [super initWithElements:elements]; self = [super initWithElements:elements];
@@ -69,7 +79,9 @@
for (NSUInteger index = 1; index < rangePath.location.length-1; index++) { for (NSUInteger index = 1; index < rangePath.location.length-1; index++) {
current = [current childLayoutAtIndex:index]; current = [current childLayoutAtIndex:index];
} }
[current clearCacheInRange:rangePath.rangeAtLastIndex replacementLength:replacementLength]; [current clearCacheInRange:rangePath.rangeAtLastIndex
replacementLength:replacementLength];
self.expressionView.needsDisplay = YES;
} }
@end @end

View File

@@ -12,7 +12,7 @@
@class MPExpressionView, MPExpressionStorage, MPExpressionLayout, MPRangePath; @class MPExpressionView, MPExpressionStorage, MPExpressionLayout, MPRangePath;
@interface MPExpressionView : NSView <NSUserInterfaceValidations> @interface MPExpressionView : NSView
#pragma mark Creation Methods #pragma mark Creation Methods
@@ -23,6 +23,7 @@
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; @property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
@property (nonatomic, getter = isEditable) BOOL editable; @property (nonatomic, getter = isEditable) BOOL editable;
@property (nonatomic, strong) MPRangePath *selection; //@property (nonatomic, strong) MPRangePath *selection;
@property (nonatomic, strong) NSIndexPath *caretLocation;
@end @end

View File

@@ -13,7 +13,7 @@
#import "MPSumFunction.h" #import "MPSumFunction.h"
@interface MPExpressionView (MPCursor) @interface MPExpressionView (MPCursor)
@property (nonatomic, strong) NSTimer *cursorTimer;
@end @end
@implementation MPExpressionView @implementation MPExpressionView
@@ -49,8 +49,9 @@
- (void)initializeObjects - (void)initializeObjects
{ {
MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithElements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithExpressionView:self elements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]];
_expressionStorage = expressionStorage; _expressionStorage = expressionStorage;
_caretLocation = [[NSIndexPath alloc] initWithIndex:0];
} }
#pragma mark Properties #pragma mark Properties
@@ -60,9 +61,25 @@
return NO; return NO;
} }
- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage - (BOOL)acceptsFirstResponder
{ {
_expressionStorage = expressionStorage; return YES;
}
- (BOOL)canBecomeKeyView
{
return YES;
}
#pragma mark Event Handling
- (void)keyDown:(NSEvent *)theEvent
{
[self interpretKeyEvents:@[theEvent]];
}
- (void)moveRight:(id)sender
{
[self.expressionStorage deleteElementsInRange:NSMakeRange(0, 1)];
} }
#pragma mark Drawing Methods #pragma mark Drawing Methods

View File

@@ -14,18 +14,24 @@
+ (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path + (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path
parent:(MPExpressionLayout *)parent; parent:(MPExpressionLayout *)parent;
@property (readonly, nonatomic, weak) MPFunction *function; // Convenience @property (readonly, nonatomic, weak) MPFunction *function;
@end @end
@interface MPFunctionLayout (MPSubclassOverride) @interface MPFunctionLayout (MPSubclassOverride)
#pragma mark Cache Methods
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator;
// Should also implement accessor method for special function type: // Should also implement accessor method for special function type:
// - (MPCustomFunction *)customFunction // - (MPCustomFunction *)customFunction
// { // {
// return (MPCustomFunction *)self.function; // return (MPCustomFunction *)self.function;
// } // }
- (NSBezierPath *)generateBezierPath; #pragma mark Size and Drawing Methods
- (NSSize)generateSize; // To be implemented
- (void)drawAtPoint:(NSPoint)point; // To be implemented
@end @end

View File

@@ -27,32 +27,63 @@
return [[self alloc] initWithPath:path parent:parent]; return [[self alloc] initWithPath:path parent:parent];
} }
#pragma mark Properties - (instancetype)initWithPath:(NSIndexPath *)path
- (MPFunction *)function parent:(MPLayout *)parent
{ {
return [self.expressionStorage elementAtIndexPath:self.path]; self = [super initWithPath:path
parent:parent];
if (self) {
_function = [parent.expressionStorage elementAtIndexPath:path];
}
return self;
} }
#pragma mark Properties
//- (MPFunction *)function
//{
// return [self.expressionStorage elementAtIndexPath:self.path];
//}
#pragma mark Cache Methods #pragma mark Cache Methods
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
generator:(CTLineRef (^)())generator
{
NSUInteger actualIndex = self.function.numberOfChildren + index;
id (^actualGenerator)() = ^{
CTLineRef line = generator();
return CFBridgingRelease(line);
};
id lineObject = [self cachableObjectForIndex:actualIndex
generator:actualGenerator];
return (__bridge CTLineRef)(lineObject);
}
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index - (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{ {
return [self cachableObjectForIndex:index generator:^id{ return [self cachableObjectForIndex:index generator:^id{
NSIndexPath *childPath = [self.path indexPathByAddingIndex:index]; NSIndexPath *childPath = [self.function.indexPath indexPathByAddingIndex:index];
MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithPath:childPath MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithPath:childPath
parent:self]; parent:self];
return layout; return layout;
}]; }];
} }
#pragma mark Size and Drawing Methods
- (NSSize)sizeForChildAtIndex:(NSUInteger)index - (NSSize)sizeForChildAtIndex:(NSUInteger)index
{ {
MPLayout *childLayout = [self childLayoutAtIndex:index]; MPLayout *childLayout = [self childLayoutAtIndex:index];
return [childLayout size]; return [childLayout size];
} }
- (NSBezierPath *)generateBezierPath - (NSSize)generateSize
{ {
return [NSBezierPath bezierPath]; return NSZeroSize;
}
- (void)drawAtPoint:(NSPoint)point
{
} }
@end @end

View File

@@ -20,13 +20,9 @@
#pragma mark Text System Objects #pragma mark Text System Objects
@property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage; @property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage;
@property (readonly, nonatomic, weak) NSLayoutManager *layoutManager;
@property (readonly, nonatomic, weak) NSTextContainer *textContainer;
@property (readonly, nonatomic, weak) NSTextStorage *textStorage;
#pragma mark Cache Tree #pragma mark Cache Tree
@property (readonly, nonatomic, weak) MPLayout *parent; @property (readonly, nonatomic, weak) MPLayout *parent;
@property (readonly, nonatomic, strong) NSIndexPath *path;
#pragma mark Cache Methods #pragma mark Cache Methods
// Querying Caches // Querying Caches
@@ -39,18 +35,14 @@
- (void)invalidate; - (void)invalidate;
#pragma mark Calculation and Drawing Methods #pragma mark Calculation and Drawing Methods
// TODO: Implement Small Size
// @property (nonatomic) BOOL usesSmallSize; // @property (nonatomic) BOOL usesSmallSize;
- (NSSize)size; - (NSSize)size;
- (NSBezierPath *)bezierPath;
- (NSBezierPath *)bezierPathAtOrigin:(NSPoint)point;
- (void)drawAtPoint:(NSPoint)point; - (void)drawAtPoint:(NSPoint)point;
@end @end
@interface MPLayout (MPSubclassImplement) @interface MPLayout (MPSubclassImplement)
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented - (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented
- (NSSize)sizeForChildAtIndex:(NSUInteger)index; // To be implemented - (NSSize)generateSize; // To be implemented
- (NSBezierPath *)generateBezierPath; // To be implemented
@end @end

View File

@@ -10,19 +10,15 @@
@interface MPLayout () @interface MPLayout ()
// Querying Caches // Querying and Storing Caches
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index; - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index;
// Storing Caches
- (void)cacheObject:(id)anObject
forElementAtIndex:(NSUInteger)index;
- (void)ensureCacheSizeForIndex:(NSUInteger)index; - (void)ensureCacheSizeForIndex:(NSUInteger)index;
@end @end
@implementation MPLayout { @implementation MPLayout {
NSMutableArray *_cache; NSMutableArray *_cache;
NSBezierPath *_cachedPath; NSSize _cachedSize;
} }
#pragma mark Creation Methods #pragma mark Creation Methods
@@ -31,8 +27,7 @@
self = [super init]; self = [super init];
if (self) { if (self) {
_cache = [[NSMutableArray alloc] init]; _cache = [[NSMutableArray alloc] init];
_cachedPath = nil; _cachedSize = NSZeroSize;
_path = [[NSIndexPath alloc] init];
} }
return self; return self;
} }
@@ -42,7 +37,6 @@
{ {
self = [self init]; self = [self init];
if (self) { if (self) {
_path = path;
_parent = parent; _parent = parent;
} }
return self; return self;
@@ -54,23 +48,8 @@
return self.parent.expressionStorage; return self.parent.expressionStorage;
} }
- (NSLayoutManager *)layoutManager
{
return self.expressionStorage.layoutManager;
}
- (NSTextContainer *)textContainer
{
return self.expressionStorage.textContainer;
}
- (NSTextStorage *)textStorage
{
return self.expressionStorage.textStorage;
}
#pragma mark Cache Tree #pragma mark Cache Tree
// Querying Caches // Querying and Storing Caches
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index
{ {
if (index >= _cache.count) { if (index >= _cache.count) {
@@ -86,17 +65,9 @@
return _cache[index]; return _cache[index];
} }
id object = generator(); id object = generator();
[self cacheObject:object
forElementAtIndex:index];
return object;
}
// Storing Caches
- (void)cacheObject:(id)anObject
forElementAtIndex:(NSUInteger)index
{
[self ensureCacheSizeForIndex:index]; [self ensureCacheSizeForIndex:index];
_cache[index] = anObject; _cache[index] = object;
return object;
} }
- (void)ensureCacheSizeForIndex:(NSUInteger)index - (void)ensureCacheSizeForIndex:(NSUInteger)index
@@ -121,39 +92,41 @@
- (void)invalidate - (void)invalidate
{ {
_cachedPath = nil; _cachedSize = NSZeroSize;
[self.parent invalidate]; [self.parent invalidate];
} }
#pragma mark Calculation Methods #pragma mark Calculation and Drawing Methods
- (NSSize)size - (NSSize)size
{ {
return self.bezierPath.bounds.size; if (NSEqualSizes(_cachedSize, NSZeroSize)) {
_cachedSize = [self generateSize];
} }
return _cachedSize;
- (NSBezierPath *)bezierPath
{
if (!_cachedPath) {
_cachedPath = [self generateBezierPath];
}
return _cachedPath;
}
- (NSBezierPath *)bezierPathAtOrigin:(NSPoint)point
{
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:point.x
yBy:point.y];
NSBezierPath *path = [NSBezierPath bezierPath];
[path appendBezierPath:self.bezierPath];
[path transformUsingAffineTransform:transform];
return path;
} }
- (void)drawAtPoint:(NSPoint)point - (void)drawAtPoint:(NSPoint)point
{ {
NSBezierPath *path = [self bezierPathAtOrigin:point];
[path fill]; }
@end
@implementation MPLayout (MPSubclassImplement)
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{
return nil;
}
- (NSSize)sizeForChildAtIndex:(NSUInteger)index
{
return NSZeroSize;
}
- (NSSize)generateSize
{
return NSZeroSize;
} }
@end @end

View File

@@ -16,21 +16,46 @@
return (MPSumFunction *)self.function; return (MPSumFunction *)self.function;
} }
- (NSBezierPath *)generateBezierPath - (CTLineRef)line
{ {
NSAttributedString *text = [[NSAttributedString alloc] initWithString:@"∑" CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{
NSAttributedString *text =
[[NSAttributedString alloc] initWithString:@"∑"
attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}];
self.textStorage.attributedString = text; CFAttributedStringRef attributedString = CFBridgingRetain(text);
NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; CTLineRef line = CTLineCreateWithAttributedString(attributedString);
NSGlyph glyphs[glyphRange.length+1]; CFRelease(attributedString); // TODO: Is this release appropriate
NSUInteger actualGylphCount = [self.layoutManager getGlyphs:glyphs return line;
range:glyphRange]; }];
NSBezierPath *path = [NSBezierPath bezierPath]; return line;
[path moveToPoint:NSZeroPoint]; }
[path appendBezierPathWithGlyphs:glyphs
count:actualGylphCount - (NSSize)generateSize
inFont:[NSFont fontWithName:@"Lucida Grande" size:18.0]]; {
return path; CTLineRef line = [self line];
CFRetain(line);
CGSize size = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/0).size;
CFRelease(line);
return size;
}
- (void)drawAtPoint:(NSPoint)point
{
// Get the current context
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
// Set the text matrix
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Get the line of text
CTLineRef line = [self line];
CFRetain(line);
// Draw the line
CGContextSetTextPosition(context, point.x, point.y);
CTLineDraw(line, context);
CFRelease(line);
} }
@end @end