Friday, 13 September 2013

Laying out individual glyphs with Core Text

Laying out individual glyphs with Core Text

I'm currently writing an application against the iOS 6.1 SDK. I know that
some things in iOS 7 may obviate the need for a solution to my question
but in the interest of learning I'm going to ask anyway.
The app will consist of a table view and custom table view cells. I'd like
the only subview of the cell's contentView to be a custom view with an
NSAttributedString drawn using Core Text. Since each cell's string will be
different, the glyph positioning needs to be dependent on the number of
glyphs (i.e. longer strings will have less visible space between glyphs) I
am trying to achieve the effect from this picture[add link here]. The size
of the font and the physical bounds must remain the same it is only the
glyph positioning that will be different.
I have the following code that for whatever reason does not do what I expect.
Here is the .h for the BMPTeamNameView - custom view (subview of contentView)
@interface BMPTeamNameView : UIView
-(id)initWithFrame:(CGRect)frame text:(NSString *)text
textInset:(UIEdgeInsets)insets font:(UIFont *)font;
@property (nonatomic, copy) NSAttributedString *attributedString;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) UIFont *font;
@property (nonatomic, assign) UIEdgeInsets insets;
@end
The new designated initializer will now set the frame, the text to use for
the attributed string, the insets to determine the text rect with respect
to contentView rect, and the font to use.
Originally in my custom drawRect: I used a CTFramesetterRef, however a
CTFramesetterRef will create an immutable frame which (may have?)
restricted the laying out of individual glyphs. In this implementation I
use a CTTypesetterRef to create the CTLineRef. Using a CTFrame leads to
different drawing behavior when you compare CTLineDraw() and CTFrameDraw()
but that is for another question. My drawRect: is as follows:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Flips the coordinates so that drawing will be right side up
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Path that will hold the textRect
CGMutablePathRef path = CGPathCreateMutable();
// rectForTextInsets: returns a rect based on the insets with respect
to cell contentView
self.textRect = [self rectForTextWithInsets:self.insets];
// Path adding / sets color for drawing
CGPathAddRect(path, NULL, self.textRect);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextAddPath(context, path);
CGContextFillPath(context);
// convenience method to return dictionary of attributes for string
NSDictionary *attributes = [self attributesForAttributedString];
// convenience method returns "Hello World" with attributes
// typesetter property is set in the custom setAttributedString:
self.attributedString = [self attributedStringWithAttributes:attributes];
CTTypesetterRef typesetter = self.typesetter;
// Creates the line for the attributed string
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, 0));
CGPoint *positions = NULL;
CGGlyph *glyphs = NULL;
CGPoint *positionsBuffer = NULL;
CGGlyph *glyphsBuffer = NULL;
// We will only have one glyph run
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
CTRunRef glyphRun = CFArrayGetValueAtIndex(glyphRuns, 0);
// Get the count of all the glyphs
NSUInteger glyphCount = CTRunGetGlyphCount(glyphRun);
// This function gets the ptr to the glyphs, may return NULL
glyphs = (CGGlyph *)CTRunGetGlyphsPtr(glyphRun);
if (glyphs == NULL) {
// if glyphs is NULL allocate a buffer for them
// store them in the buffer
// set the glyphs ptr to the buffer
size_t glyphsBufferSize = sizeof(CGGlyph) * glyphCount;
CGGlyph *glyphsBuffer = malloc(glyphsBufferSize);
CTRunGetGlyphs(glyphRun, CFRangeMake(0, 0), glyphsBuffer);
glyphs = glyphsBuffer;
}
// This function gets the ptr to the positions, may return NULL
positions = (CGPoint *)CTRunGetPositionsPtr(glyphRun);
if (positions == NULL) {
// if positions is NULL allocate a buffer for them
// store them in the buffer
// set the positions ptr to the buffer
size_t positionsBufferSize = sizeof(CGPoint) * glyphCount;
CGPoint *positionsBuffer = malloc(positionsBufferSize);
CTRunGetPositions(glyphRun, CFRangeMake(0, 0), positionsBuffer);
positions = positionsBuffer;
}
// Changes each x by 15 and then sets new value at array index
for (int i = 0; i < glyphCount; i++) {
NSLog(@"positionAtIndex: %@", NSStringFromCGPoint(positions[i]));
CGPoint oldPosition = positions[i];
CGPoint newPosition = CGPointZero;
NSLog(@"oldPosition = %@", NSStringFromCGPoint(oldPosition));
newPosition.x = oldPosition.x + 15.0f;
newPosition.y = oldPosition.y;
NSLog(@"newPosition = %@", NSStringFromCGPoint(newPosition));
positions[i] = newPosition;
NSLog(@"positionAtIndex: %@", NSStringFromCGPoint(positions[i]));
}
// When CTLineDraw is commented out this will not display the glyphs
on the screen
CGContextShowGlyphsAtPositions(context, glyphs, positions, glyphCount);
// When CGContextShowGlyphsAtPositions is commented out...
// This will draw the string however it aligns the text to the view's
lower left corner
// CTFrameDraw would draw the text properly in the view's upper left
corner
// This is the difference I was speaking of and I'm not sure why it is
CTLineDraw(line, context);
// Make sure to release any CF objects and release allocated buffers
CFRelease(path);
free(positionsBuffer);
free(glyphsBuffer);
}
I'm not sure exactly why CGContextShowGlyphsAtPositions() is not
displaying the glyphs properly or why CTLineDraw() will not make use of
the new glyph positions. Am I handling the allocation of those positions
and glyphs incorrectly? Caveman debugging shows that the glyphs are as
expected and the positions are being changed. I know that my code did not
satisfy exactly what I was looking for (I was changing glyph position by
15.0f rather than based on string) however, where am I going wrong in
laying out these glyphs?

No comments:

Post a Comment