Vertical align with Core Text? - alignment

How do I change the vertical alignment of the text in a CTFramesetter frame? I want my text to be in the middle instead of being at the top. I am using Core Text framework. There is a setting of the paragraph to change horizontal aligment but not vertical.

Finally figured it out ...
CGRect boundingBox = CTFontGetBoundingBox(font);
//Get the position on the y axis
float midHeight = self.frame.size.height / 2;
midHeight -= boundingBox.size.height / 2;
CGPathAddRect(path, NULL, CGRectMake(0, midHeight, self.frame.size.width, boundingBox.size.height));

Thanks Nick, that was a great snippet.
Just expanding on that, if your doing Top, Middle and Bottom alignment with an enum, for example you could do it like so:
if (VerticalAlignmentTop == currentTextAlignment) {
CGPathAddRect(path, NULL, rect); // Draw normally (top)
}
else if (VerticalAlignmentMiddle == currentTextAlignment) {
CGRect boundingBox = CTFontGetBoundingBox(fontRef);
//Get the position on the y axis (middle)
float midHeight = rect.size.height / 2;
midHeight -= boundingBox.size.height / 2;
CGPathAddRect(path, NULL, CGRectMake(0, midHeight, rect.size.width, boundingBox.size.height));
}
else {
CGRect boundingBox = CTFontGetBoundingBox(fontRef);
CGPathAddRect(path, NULL, CGRectMake(0, 0, rect.size.width, boundingBox.size.height));
}

You can use [NSString boundingRectWithSize:options:attributes:context:] to get the rectangle of your string's bounding box, which allows multi-line text as well.
In your draw text method, do the following (RECT is the rectangle where you want to draw the text):
// get the graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// flip the context coordinate
CGContextTranslateCTM(context, 0.0f, 2*RECT.origin.y+RECT.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// set text horizontal alignment
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = #{NSParagraphStyleAttributeName:paragraphStyle, NSFontAttributeName:YOUR_FONT, NSForegroundColorAttributeName:TEXT_COLOR};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:YOUR_TEXT attributes:attributes];
CGMutablePathRef path = CGPathCreateMutable();
// set text vertical alignment
CGSize textSize = [text boundingRectWithSize:RECT.size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
CGPathAddRect(path, NULL, CGRectMake(RECT.origin.x, RECT.origin.y-(RECT.size.height-textSize.height)/2.0f, RECT.size.width, RECT.size.height));
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attrString.length), path, NULL);
CTFrameDraw(frame, context);
CFRelease(frame);
CFRelease(path);
CFRelease(frameSetter);
[attrString release];
[paragraphStyle release];
CGContextRestoreGState(context);

This accounts for the fact that multiple font types and styles can be used in a frame (calculates both height and width of text, look in the if(index == lastLineIndex) block to see where the height is calculated):
- (CGSize) measureFrame: (CTFrameRef) frame forContext: (CGContext *) cgContext
{
CGPathRef framePath = CTFrameGetPath(frame);
CGRect frameRect = CGPathGetBoundingBox(framePath);
CFArrayRef lines = CTFrameGetLines(frame);
CFIndex numLines = CFArrayGetCount(lines);
CGFloat maxWidth = 0;
CGFloat textHeight = 0;
// Now run through each line determining the maximum width of all the lines.
// We special case the last line of text. While we've got it's descent handy,
// we'll use it to calculate the typographic height of the text as well.
CFIndex lastLineIndex = numLines - 1;
for(CFIndex index = 0; index < numLines; index++)
{
CGFloat ascent, descent, leading, width;
CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex(lines, index);
width = CTLineGetTypographicBounds(line, &ascent,  &descent, &leading);
if(width > maxWidth)
{
maxWidth = width;
}
if(index == lastLineIndex)
{
// Get the origin of the last line. We add the descent to this
// (below) to get the bottom edge of the last line of text.
CGPoint lastLineOrigin;
CTFrameGetLineOrigins(frame, CFRangeMake(lastLineIndex, 1), &lastLineOrigin);
// The height needed to draw the text is from the bottom of the last line
// to the top of the frame.
textHeight =  CGRectGetMaxY(frameRect) - lastLineOrigin.y + descent;
}
}
// For some text the exact typographic bounds is a fraction of a point too
// small to fit the text when it is put into a context. We go ahead and round
// the returned drawing area up to the nearest point.  This takes care of the
// discrepencies.
return CGSizeMake(ceil(maxWidth), ceil(textHeight));
}
Reference: Scott Thompson (http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00079.html)

Related

Draw and rotate Text along a stroke

I try to rotate a text along a stroke. The user can create strokes by touch and move the finger in an direction he wants, the stroke then scales to the point where the finger is. I want to show the current length along the stroke and the text show stay scale and rotate with the stroke.
I think im not so far away from the working solution. Currently the text is not always at the right position, its depends on the rotation. I think there is something wrong with Context Translation, but lets see my code.
This is my method to draw the text:
- (void)drawText:(NSString *)text withFrame:(CGRect)rect withFont:(UIFont *)font rotation:(float)radians alignment:(NSTextAlignment)alignment context:(CGContextRef)context {
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
paragraphStyle.alignment = alignment;
NSDictionary *attributes = #{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle,
NSForegroundColorAttributeName: [UIColor blackColor] };
CGContextSaveGState(context);
CGContextScaleCTM(context, 1.0, 1.0);
//CGRect textSize = [text boundingRectWithSize:rect.size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
CGContextTranslateCTM(context, rect.origin.x + (rect.size.width * 0.5), rect.origin.y + (rect.size.height * 0.5));
CGContextRotateCTM(context, radians);
CGContextTranslateCTM(context, -(rect.origin.x + (rect.size.width * 0.5)), -(rect.origin.y + (rect.size.height * 0.5)));
[[UIColor redColor] set];
CGContextFillRect(context, rect);
[text drawInRect:rect withAttributes:attributes];
CGContextRestoreGState(context);
}
I call it like this:
CGFloat a = shapeToBeDrawn.startPoint.y - shapeToBeDrawn.endPoint.y;
CGFloat c = shapeToBeDrawn.length;
CGFloat alpha = -asin(a/c);
CGRect r = CGRectMake(shapeToBeDrawn.startPoint.x, shapeToBeDrawn.startPoint.y - 30, shapeToBeDrawn.endPoint.x - shapeToBeDrawn.startPoint.x, 20);
[self drawText:lengthStr withFrame:r withFont:[UIFont fontWithName:#"Helvetica" size:18.0f] rotation:alpha alignment:NSTextAlignmentCenter context:context];
There im calculation the angle alpha and pass the string i want to display. And also create the frame for the text above the frame of the shape.
Here a small video how it looks currently:
Click
Hope someone can help me and my problem is clear. Thanks :)
To calculate angle properly in all quadrants, use atan2 function
alpha = atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x)
To define a rect - calculate starting coordinates for rotated rectangle:
x0 = startPoint.x + Sin(alpha) * 30
y0 = startPoint.y - Cos(alpha) * 30
rect.width = shapeToBeDrawn.length

Vertical Text Alignment in Core Text

EDIT: Here's a Github Gist of the below code if that is more readable.
I'm writing a method to draw text within a rectangle in a PDF using Core Text. What I've written accomplishes everything I need, except vertical alignment (top, center, bottom). The standard way that the text is currently drawn is the top, and I specifically need the bottom alignment.
- (void)drawText:(NSString *)textToDraw inFrame:(CGRect)frameRect withFont:(UIFont *)originalFont textColor:(UIColor *)textColor alignment:(PDFTextAlignment)alignment verticalAlignment:(PDFTextVerticalAlignment)verticalAlignment {
if (!textToDraw) {
// If nil, give it an empty value to draw
textToDraw = #"";
}
// Prepare font
CTFontRef font = [self ctFontRefFromUIFont:originalFont];
CGColorRef color = textColor.CGColor;
// Paragraph
CTTextAlignment ctAlignment;
switch (alignment) {
case PDFTextAlignmentLeft:
ctAlignment = kCTTextAlignmentLeft;
break;
case PDFTextAlignmentCenter:
ctAlignment = kCTTextAlignmentCenter;
break;
case PDFTextAlignmentRight:
ctAlignment = kCTTextAlignmentRight;
break;
case PDFTextAlignmentJustified:
ctAlignment = kCTTextAlignmentJustified;
break;
default:
ctAlignment = kCTTextAlignmentLeft;
break;
}
CTParagraphStyleSetting settings[] = {
{kCTParagraphStyleSpecifierAlignment, sizeof(ctAlignment), &ctAlignment},
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, sizeof(settings) / sizeof(settings[0]));
// Create an attributed string
CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName, kCTParagraphStyleAttributeName };
CFTypeRef values[] = { font, color, paragraphStyle };
CFDictionaryRef attr = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values,
sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
// Prepare the text using a Core Text Framesetter.
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, attr);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
// Get the frame that will do the rendering.
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
// Modify this to take into consideration the origin.
CGContextTranslateCTM(currentContext, 0, frameRect.origin.y*2);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
// Add these two lines to reverse the earlier transformation.
CGContextScaleCTM(currentContext, 1.0, -1.0);
CGContextTranslateCTM(currentContext, 0, (-1)*frameRect.origin.y*2);
CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);
CFRelease(font);
CFRelease(paragraphStyle);
}
Here is an example of how this will currently draw text in a rectangle (note the rectangle drawn with the lines is the same frameRect I'm passing in)...
.
I want this text to appear as bottom aligned in this rectangle, like this...
.
I've gone through the solutions on this SO post, but have not had any luck getting the text to align correctly.
I'm passing in an enumerated value for the vertical alignment, so ideally I will determine the vertical alignment with the following conditional.
if (verticalAlignment == PDFTextVerticalAlignmentTop) {
// Top align
} else if (verticalAlignment == PDFTextVerticalAlignmentCenter) {
// Center (vertical) align
} else if (verticalAlignment == PDFTextVerticalAlignmentBottom) {
// Bottom align
} else {
// Default: Bottom alignment
}
Thanks!
As I am not quite sure how you approached the solutions from the linked post, I will suggest a solution based on one of those answers. This may be exactly what you tried, but it may also be the appropriate solution.
I assume a method exists for obtaining the size of CTFrameRef, leaving that implementation to you, as long as the appropriate CGSize is returned. Apart from the methods shown in the linked SO post, it might also be worth looking at CTFramesetterSuggestFrameSizeWithConstraints. Assuming this function is available, it should be enough to adjust this part of the drawing function
...
CGMutablePathRef framePath = CGPathCreateMutable();
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGRect textRect = frameRect; // Will also give you the default behavior for top align
CGSize textSize = [self measureFrame:frameref forContext:currentContext];
if (verticalAlignment == PDFTextVerticalAlignmentBottom) {
// Bottom align
textRect.size.height = textSize.height;
textRect.origin.y = frameRect.origin.y + frameRect.size.height - textSize.height;
} else if (verticalAlignment == PDFTextVerticalAlignmentCenter) {
// Center (vertical) align
textRect.size.height = textSize.height;
textRect.origin.y = frameRect.origin.y + (frameRect.size.height - textSize.height) / 2;
}
CGPathAddRect(framePath, NULL, textRect);
...

Creating multi page PDF from text content

I have a long text content and wanted to convert it in to a multipage PDF file.
I have followed the instructions provided in this tutorial http://www.raywenderlich.com/6581/how-to-create-a-pdf-with-quartz-2d-in-ios-5-tutorial-part-1.
But I am struggling to make the pdf multi page.
The code I'm using can create single page PDF.
+(void)drawText
{
NSString* textToDraw = #"The sample text";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
// Prepare the text using a Core Text Framesetter
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
CGRect frameRect = CGRectMake(50, 50, 512, 1000);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
// Get the frame that will do the rendering.
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);
}
+(void)drawPDF:(NSString*)fileName
{
// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
[self drawText];
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
}
you can draw pdf using following code:
// Use Core Text to draw the text in a frame on the page.
- (CFRange)renderPage:(NSInteger)pageNum withTextRange:(CFRange)currentRange
andFramesetter:(CTFramesetterRef)framesetter
{
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
// Create a path object to enclose the text. Use 72 point
// margins all around the text.
CGRect frameRect = CGRectMake(72, 72, 468, 648);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
// Get the frame that will do the rendering.
// The currentRange variable specifies only the starting point. The framesetter
// lays out as much text as will fit into the frame.
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, kDefaultPageHeight);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
// Update the current range based on what was drawn.
currentRange = CTFrameGetVisibleStringRange(frameRef);
currentRange.location += currentRange.length;
currentRange.length = 0;
CFRelease(frameRef);
return currentRange;
}
- (void)drawPageNumber:(NSInteger)pageNum
{
NSString* pageString = [NSString stringWithFormat:#"Page %ld", (long)pageNum];
UIFont* theFont = [UIFont systemFontOfSize:12];
// CGSize maxSize = CGSizeMake(kDefaultPageWidth, 72);
CGSize pageStringSize = [pageString sizeWithAttributes:
#{NSFontAttributeName:
theFont}];
// CGSize pageStringSize = [pageString sizeWithFont:theFont
// constrainedToSize:maxSize
// lineBreakMode:NSLineBreakByClipping];
CGRect stringRect = CGRectMake(((kDefaultPageWidth - pageStringSize.width) / 2.0),
720.0 + ((72.0 - pageStringSize.height) / 2.0) ,
pageStringSize.width,
pageStringSize.height);
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = #{NSFontAttributeName: [UIFont systemFontOfSize:15], NSParagraphStyleAttributeName: paragraphStyle};
[pageString drawInRect:stringRect withAttributes:attributes];
// [pageString drawInRect:stringRect withFont:theFont];
}
For save pdf:
- (IBAction)savePDFFile:(id)sender
{
NSString* path = [[NSBundle mainBundle] pathForResource:#"sampleData" ofType:#"plist"];
// get a temprorary filename for this PDF
path = NSTemporaryDirectory();
self.pdfFilePath = [path stringByAppendingPathComponent:
[NSString stringWithFormat:#"%d.pdf",
[[NSDate date]
timeIntervalSince1970] ]];
// Prepare the text using a Core Text Framesetter
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL,
(CFStringRef)textView.text, NULL);
if (currentText) {
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
if (framesetter) {
NSString* pdfFileName = self.pdfFilePath; //[NSString stringWithString:#"test.pdf"];
// Create the PDF context using the default page: currently constants at the size
// of 612 x 792.
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);
CFRange currentRange = CFRangeMake(0, 0);
NSInteger currentPage = 0;
BOOL done = NO;
do {
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kDefaultPageWidth,
kDefaultPageHeight), nil);
// Draw a page number at the bottom of each page
currentPage++;
[self drawPageNumber:currentPage];
// Render the current page and update the current range to
// point to the beginning of the next page.
currentRange = [self renderPage:currentPage withTextRange:
currentRange andFramesetter:framesetter];
// If we're at the end of the text, exit the loop.
if (currentRange.location == CFAttributedStringGetLength
((CFAttributedStringRef)currentText))
done = YES;
} while (!done);
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
// Release the framewetter.
CFRelease(framesetter);
} else {
NSLog(#"Could not create the framesetter needed to lay out the atrributed string.");
}
// Release the attributed string.
CFRelease(currentText);
} else {
NSLog(#"Could not create the attributed string for the framesetter");
}
// Ask the user if they'd like to see the file or email it.
UIActionSheet* actionSheet = [[[UIActionSheet alloc] initWithTitle:#"Would you like to preview or email this PDF?"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"Preview", #"Email", nil] autorelease];
[actionSheet showInView:self.view];
}
here it is full demo please check: http://ge.tt/1eW8iJt1/v/0

Draw Angled/Rotated MultiLine Text - Core Text + Core Graphics

After going through several blogs & forums I didn't find an appropriate solution for drawing inclined/angled text using core Text on a views context.
So here is how it goes.
I have a view whose - (void)drawRect:(CGRect)rect is invoked to draw a string (multi or single line text) on screen.
CODE:
- (void)drawRect:(CGRect)rect
{
NSString *text = #"This is some text being drawn by CoreText!\nAnd some more text on another line!";
//Core Text (Create Attributed String)
UIColor *textColor = [UIColor blackColor];
CGColorRef color = textColor.CGColor;
CTFontRef font = CTFontCreateWithName((CFStringRef) #"HelveticaNeue", 20.0, NULL);
CTTextAlignment theAlignment = kCTTextAlignmentLeft;
CFIndex theNumberOfSettings = 1;
CTParagraphStyleSetting theSettings[1] =
{
{ kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment),
&theAlignment }
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(theSettings, theNumberOfSettings);
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
CFBridgingRelease(font), (NSString *)kCTFontAttributeName,
color, (NSString *)kCTForegroundColorAttributeName,
paragraphStyle, (NSString *) kCTParagraphStyleAttributeName,
nil];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:text attributes:attributesDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)stringToDraw);
//Create Frame
CGMutablePathRef path = CGPathCreateMutable();
CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
//First translate your image View according to transform
transform = CGAffineTransformTranslate(transform, 0, - self.bounds.size.height);
// Then whenever you want any point according to UIKit related coordinates apply this transformation on the point or rect.
CGRect frameText = CGRectMake(60, 100, 200, 200);
CGRect newRectForUIKit = CGRectApplyAffineTransform(frameText, transform);
CGPathAddRect(path, NULL, newRectForUIKit);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, ([self bounds]).size.height );
CGContextScaleCTM(ctx, 1.0, -1.0);
//Draw Frame
CTFrameDraw(frame, ctx);
//Release all retained objects
CFRelease(path);
}
Output:
Apart from drawing text I want to add an angle to the entire drawn text. Something like this(Required output)
So how do I add an rotation angle to the drawn text in core text?
Note: 1)A single context can have multiple drawn text objects with their respective angles as shown below
I hope my question is clear.
Apply the rotation to the context before drawing the CTFrameRef into it.
Edit :
If you want multiple angles, you need to save/restore the graphics states, each time.
Something like :
- (void)drawRect:(CGRect)rect
{
NSString *text = #"This is some text being drawn by CoreText!\nAnd some more text on another line!";
//Core Text (Create Attributed String)
UIColor *textColor = [UIColor blackColor];
CGColorRef color = textColor.CGColor;
CTFontRef font = CTFontCreateWithName((CFStringRef) #"HelveticaNeue", 20.0, NULL);
CTTextAlignment theAlignment = kCTTextAlignmentLeft;
CFIndex theNumberOfSettings = 1;
CTParagraphStyleSetting theSettings[1] =
{
{ kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment),
&theAlignment }
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(theSettings, theNumberOfSettings);
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
CFBridgingRelease(font), (NSString *)kCTFontAttributeName,
color, (NSString *)kCTForegroundColorAttributeName,
paragraphStyle, (NSString *) kCTParagraphStyleAttributeName,
nil];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:text attributes:attributesDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)stringToDraw);
//Create Frame
CGMutablePathRef path = CGPathCreateMutable();
CGRect frameText = CGRectMake(60, 100, 200, 200);
CGPathAddRect(path, NULL, frameText);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx); /* save Graphic State for context rotation */
// transform (rotate) context
CGAffineTransform transform = CGAffineTransformMakeTranslation(self.bounds.size.width / 2.f, self.bounds.size.height / 2.f);
CGFloat rotation = -M_PI / 2.f;
transform = CGAffineTransformRotate(transform,rotation);
transform = CGAffineTransformTranslate(transform,-self.bounds.size.width / 2.f, -self.bounds.size.height / 2.f);
CGContextConcatCTM(ctx, transform);
CGContextSaveGState(ctx);
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, ([self bounds]).size.height );
CGContextScaleCTM(ctx, 1.0, -1.0);
//Draw Frame
CTFrameDraw(frame, ctx);
//Release all retained objects
CFRelease(path);
CGContextRestoreGState(ctx);
CGContextRestoreGState(ctx); /* restore Graphic State for context rotation */
CGContextSaveGState(ctx); /* save Graphic States for another drawing */
/* lets draw another string with different angle */
attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
CFBridgingRelease(CTFontCreateWithName((CFStringRef) #"HelveticaNeue", 14.0, NULL)), (NSString *)kCTFontAttributeName,
[UIColor yellowColor].CGColor, (NSString *)kCTForegroundColorAttributeName,
nil];
stringToDraw = [[NSAttributedString alloc] initWithString:#"another piece of text to drawn on same context with no angle" attributes:attributesDict];
framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)stringToDraw);
path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(60, -100, 200, 200));
frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFRelease(path);
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, ([self bounds]).size.height );
CGContextScaleCTM(ctx, 1.0, -1.0);
CTFrameDraw(frame, ctx);
/**
* #note don't forget to restore a previously saved GState, or this will be source of problems
*/
CGContextRestoreGState(ctx);
CFRelease(frame);
CFRelease(framesetter);
}

Drawing rotated text on iOS creates jumping characters

I am trying to draw slightly rotated text with Core Graphics on the iOS platform. Text renders fine when not rotating but the rendering system tries to lock onto pixels for rotated text.
For example: If you rotate a Core Graphics context by some small amount (like 2 degrees) and then draw text the individual characters will appear to jump up and down as Core Graphics locks the characters to the pixels (font hinting). I know that the text may become blurry if it would not lock onto the pixel grid but that's acceptable. Jumping characters are not. So how can I disable vertical font hinting? Hinting horizontal would be ok, but turning it all off is ok too.
Code for custom UIView:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
[self.backgroundColor setFill];
CGContextFillRect(context, rect);
CGContextSaveGState(context);
// rotate context
CGContextTranslateCTM(context, self.bounds.size.width / 2.0, self.bounds.size.width / 2.0);
CGContextRotateCTM(context, 2.0 * M_PI / 180.0);
CGContextTranslateCTM(context, -self.bounds.size.width / 2.0, -self.bounds.size.width / 2.0);
[[UIColor blackColor] setFill];
[self.title drawInRect:[self bounds] withFont:[UIFont systemFontOfSize:15.0]];
CGContextRestoreGState(context);
}
Result (not exactly of this code but similar, red lines inserted to guide to the "error"):
The only solution I have found is to get the actual Bezier paths of the glyphs with Core Text and draw those, which circumvents any vertical hinting. The following code excerpt is rather lengthy:
CGRect textRect = CGRectMake(0.0, 0.0, 300.0, 190.0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip coordinate system vertically.
CGContextSaveGState(context);
CGFloat rectHeight = textRect.size.height;
CGContextTranslateCTM(context, 0.0, rectHeight);
CGContextScaleCTM(context, 1.0f, -1.0f);
// Positive degrees because of flip.
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(2.0 * M_PI/180.0);
CGContextConcatCTM(context, rotationTransform);
CGFloat pointSize = 15.0;
CTFontRef font = CTFontCreateUIFontForLanguage(kCTFontSystemFontType,
pointSize,
NULL);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
NSDictionary *initialAttributes = (
#{
(NSString *)kCTFontAttributeName : (__bridge id)font,
(NSString *)kCTForegroundColorAttributeName : (__bridge id)[UIColor blackColor].CGColor
}
);
NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc] initWithString:[self string]
attributes:initialAttributes];
//
// For typesetting a frame, we should create a paragraph style.
// Includes fix for CTFramesetter’s wrong line spacing behavior.
// See Technical Q&A QA1698: “How do I work-around an issue where some lines
// in my Core Text output have extra line spacing?”
//
// Center alignment looks best when filling an ellipse.
CTTextAlignment alignment = kCTLeftTextAlignment;
CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
// This is the leading in the historical sense, which is added to the point
// size but does not include it like the line height does.
CGFloat leading = 2.0;
// Still, for the fix we do need the line height.
CGFloat lineHeight = pointSize + leading;
CTParagraphStyleSetting paragraphStyleSettings[] =
{
{
kCTParagraphStyleSpecifierAlignment,
sizeof(alignment),
&alignment
},
{
kCTParagraphStyleSpecifierLineBreakMode,
sizeof(lineBreakMode),
&lineBreakMode
},
// These two specifiers fix the line spacing when set to line height.
{
kCTParagraphStyleSpecifierMinimumLineHeight,
sizeof(lineHeight),
&lineHeight
},
{
kCTParagraphStyleSpecifierMaximumLineHeight,
sizeof(lineHeight),
&lineHeight
}
// Very important: Do not set kCTParagraphStyleSpecifierLineSpacing too,
// or it will be added again!
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(
paragraphStyleSettings,
sizeof(paragraphStyleSettings) / sizeof(paragraphStyleSettings[0])
);
// Apply paragraph style to entire string. This cannot be done when the
// string is empty, by the way, because attributes can only be applied to
// existing characters.
NSRange stringRange = NSMakeRange(0, [attributedString length]);
[attributedString addAttribute:(NSString *)kCTParagraphStyleAttributeName
value:(__bridge id)(paragraphStyle)
range:stringRange];
// Create bezier path to contain our text.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, textRect);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedString));
// Range with length 0 indicates that we want to typeset until we run out of
// text or space.
CTFrameRef frame = CTFramesetterCreateFrame(
framesetter,
CFRangeMake(0, 0),
path,
NULL
);
CFArrayRef lines = CTFrameGetLines(frame);
CFIndex lineCount = CFArrayGetCount(lines);
CFRange range = CFRangeMake(0, 0);
CGPoint lineOrigins[lineCount];
CTFrameGetLineOrigins(frame, range, lineOrigins);
for (NSUInteger lineIndex = 0; lineIndex < lineCount; ++lineIndex)
{
CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
CGPoint lineOrigin = lineOrigins[lineIndex];
CFArrayRef runs = CTLineGetGlyphRuns(line);
CFIndex runCount = CFArrayGetCount(runs);
for (NSUInteger runIndex = 0; runIndex < runCount; ++runIndex)
{
CTRunRef run = CFArrayGetValueAtIndex(runs, runIndex);
CFIndex glyphCount = CTRunGetGlyphCount(run);
CGGlyph glyphBuffer[glyphCount];
CTRunGetGlyphs(run, range, glyphBuffer);
CGPoint positionsBuffer[glyphCount];
CTRunGetPositions(run, range, positionsBuffer);
for (NSUInteger glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex)
{
CGGlyph glyph = glyphBuffer[glyphIndex];
CGPoint position = positionsBuffer[glyphIndex];
CGAffineTransform positionTransform = CGAffineTransformMakeTranslation(lineOrigin.x + position.x,
lineOrigin.y + position.y);
CGPathRef glyphPath = CTFontCreatePathForGlyph(font, glyph, &positionTransform);
CGContextAddPath(context, glyphPath);
}
}
}
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillPath(context);
CFRelease(font);
CFRelease(framesetter);
// Use specialized release function when it exists.
CGPathRelease(path);
CGContextRestoreGState(context);

Resources