Skip to content

Instantly share code, notes, and snippets.

@SheffieldKevin
Created February 20, 2015 11:20
Show Gist options
  • Save SheffieldKevin/0b10fea23b97aea5d529 to your computer and use it in GitHub Desktop.
Save SheffieldKevin/0b10fea23b97aea5d529 to your computer and use it in GitHub Desktop.
bigears. Drawing rotated and positioned text within a path using CoreText (OS X command line, intended to be bug report for apple but highlighted a bug of mine)
//
// main.m
// bigears
//
// Created by Kevin Meaney on 18/02/2015.
// Copyright (c) 2015 Kevin Meaney. All rights reserved.
//
#import <Foundation/Foundation.h>
const size_t context_width = 400;
const size_t context_height = 400;
const CGFloat textBoxHeight = 22.0;
const CGFloat textBoxWidth = 124.0;
const CGRect textBoundingBox = { { -textBoxWidth * 0.5, -textBoxHeight * 0.5 },
{ textBoxWidth, textBoxHeight } };
NSString *GetFolderPath()
{
NSString *fp = [@"~/Desktop/RotatedText/" stringByExpandingTildeInPath];
return fp;
}
void MakeDestinationFolder()
{
NSString *fp = GetFolderPath();
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:fp])
{
[fm createDirectoryAtPath:fp
withIntermediateDirectories:YES
attributes:NULL
error:NULL];
}
}
void SaveCGImageToAPNGFile(CGImageRef theImage, NSString *fileName)
{
NSString *fp = GetFolderPath();
NSString *destination = [NSString stringWithFormat:@"%@/%@",
[fp stringByExpandingTildeInPath], fileName];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:destination];
CGImageDestinationRef exporter = CGImageDestinationCreateWithURL(
(__bridge CFURLRef)fileURL,
kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(exporter, theImage, nil);
CGImageDestinationFinalize(exporter);
CFRelease(exporter);
}
void SaveCGBitmapContextToAPNGFile(CGContextRef context, NSString *fileName)
{
CGImageRef image = CGBitmapContextCreateImage(context);
SaveCGImageToAPNGFile(image, fileName);
CGImageRelease(image);
}
CGContextRef CreateBitmapContext()
{
CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGContextRef context = CGBitmapContextCreate(nil, context_width,
context_height, 8, context_width * 4, space,
(CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(space);
return context;
}
void FillRectInContextWithColour(CGContextRef context, CGRect rect,
CGColorRef colour)
{
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, colour);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
}
void FillPathInContextWithColour(CGContextRef context, CGPathRef path,
CGColorRef colour)
{
CGContextSaveGState(context);
CGContextAddPath(context, path);
CGContextSetFillColorWithColor(context, colour);
CGContextFillPath(context);
CGContextRestoreGState(context);
}
void ClearContext(CGContextRef context)
{
CGColorRef colour = CGColorCreateGenericRGB(1.0, 1.0, 1.0, 1.0);
CGRect contextRect = CGRectMake(0.0, 0.0, context_width, context_height);
FillRectInContextWithColour(context, contextRect, colour);
CGColorRelease(colour);
}
CGPathRef CreateTextRoundedBoundingBoxPath()
{
CGFloat cornderRadius = 8.0;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, textBoundingBox.origin.x + cornderRadius,
textBoundingBox.origin.y);
CGPoint arcCentre;
arcCentre = CGPointMake(
textBoundingBox.origin.x + textBoundingBox.size.width - cornderRadius,
textBoundingBox.origin.y + cornderRadius);
CGPathAddArc(path, NULL, arcCentre.x, arcCentre.y,
cornderRadius, -M_PI_2, 0.0, 0);
arcCentre = CGPointMake(
textBoundingBox.origin.x + textBoundingBox.size.width - cornderRadius,
textBoundingBox.origin.y + textBoundingBox.size.height - cornderRadius);
CGPathAddArc(path, NULL, arcCentre.x, arcCentre.y, cornderRadius, 0.0, M_PI_2, 0);
arcCentre = CGPointMake(textBoundingBox.origin.x + cornderRadius,
textBoundingBox.origin.y + textBoundingBox.size.height - cornderRadius);
CGPathAddArc(path, NULL, arcCentre.x, arcCentre.y,
cornderRadius, M_PI_2, M_PI, 0);
arcCentre = CGPointMake(textBoundingBox.origin.x + cornderRadius,
textBoundingBox.origin.y + cornderRadius);
CGPathAddArc(path, NULL, arcCentre.x, arcCentre.y, cornderRadius,
M_PI, M_PI + M_PI_2, 0);
CGPathRef newPath = CGPathCreateCopy(path);
CGPathRelease(path);
return newPath;
// return CGPathCreateWithRoundedRect(textBoundingBox, 12, cornderRadius, NULL);
}
CFDictionaryRef CreateTextAttributesDictionary()
{
const CGFloat fontSize = 16.0;
CFStringRef postscriptFontName = CFSTR("Avenir-Black");
CTTextAlignment alignment = kCTTextAlignmentCenter;
// CGColorRef textColour = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 0.8);
CGFloat colour[4];
colour[0] = 0.0;
colour[1] = 0.1;
colour[2] = 0.1;
colour[3] = 0.8;
CGColorSpaceRef profile = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGColorRef textColour = CGColorCreate(profile, colour);
CTFontRef theFont = CTFontCreateWithName(postscriptFontName, fontSize, NULL);
// CTFontRef theFont = CTFontCreateWithName((__bridge CFStringRef)@"Avenir-Black", fontSize, NULL);
CFMutableDictionaryRef mutDict = CFDictionaryCreateMutable(kCFAllocatorDefault,
0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(mutDict, kCTFontAttributeName, theFont);
CFDictionaryAddValue(mutDict, kCTForegroundColorAttributeName, textColour);
CTParagraphStyleSetting settings[] = {
{ kCTParagraphStyleSpecifierAlignment,
sizeof(alignment),
&alignment} };
CTParagraphStyleRef paragraphStyle = NULL;
paragraphStyle = CTParagraphStyleCreate(settings,
sizeof(settings) / sizeof(settings[0]));
CFDictionaryAddValue(mutDict, kCTParagraphStyleAttributeName, paragraphStyle);
CFRelease(profile);
CFRelease(theFont);
CGColorRelease(textColour);
CFRelease(paragraphStyle);
CFDictionaryRef dict = CFDictionaryCreateCopy(kCFAllocatorDefault, mutDict);
CFRelease(mutDict);
return dict;
}
CFAttributedStringRef CreateTextAttributedString()
{
CFDictionaryRef textAttribs = CreateTextAttributesDictionary();
CFStringRef textToDraw = CFSTR("Going around");
CFAttributedStringRef attribString = CFAttributedStringCreate(
kCFAllocatorDefault, textToDraw, textAttribs);
CFRelease(textAttribs);
return attribString;
}
void RotateAndTranslateContext(CGContextRef context, CGFloat angle,
CGFloat translation)
{
// CGContextTranslateCTM(context, context_width * 0.5, context_height * 0.5);
CGContextTranslateCTM(context, 100 + translation, 100 + translation);
CGContextRotateCTM(context, angle);
}
void FillPathRectAndDrawStringInContext(CGContextRef context, CGPathRef path,
CFAttributedStringRef attribString)
{
// CGRect boundingBox = CGPathGetBoundingBox(path);
CGColorRef boxColour = CGColorCreateGenericRGB(0.7, 0.8, 0.9, 1.0);
FillPathInContextWithColour(context, path, boxColour);
CTFramesetterRef frameSetter;
frameSetter = CTFramesetterCreateWithAttributedString(attribString);
CTFrameRef theFrame = CTFramesetterCreateFrame(frameSetter,
CFRangeMake(0, CFAttributedStringGetLength(attribString)),
path, NULL);
CFRelease(frameSetter);
CTFrameDraw(theFrame, context);
CFRelease(theFrame);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MakeDestinationFolder();
CGPathRef textPath = CreateTextRoundedBoundingBoxPath();
CGContextRef bitmap = CreateBitmapContext();
CFAttributedStringRef attribString = CreateTextAttributedString();
const int numImages = 60;
CGFloat angle;
CGFloat translation;
// CGFloat startAngle = -M_PI;
CGFloat diff = M_PI * 2.0 / (numImages - 1);
for (int i = 0 ; i < numImages ; ++i)
{
ClearContext(bitmap);
CGContextSaveGState(bitmap);
// angle = startAngle + i * diff;
angle = i * diff;
translation = i * 200 / (numImages - 1);
RotateAndTranslateContext(bitmap, angle, translation);
FillPathRectAndDrawStringInContext(bitmap, textPath, attribString);
CGContextRestoreGState(bitmap);
NSString *fN;
fN = [[NSString alloc] initWithFormat:@"RotatedText%03d.png", i];
SaveCGBitmapContextToAPNGFile(bitmap, fN);
}
CGPathRelease(textPath);
CGContextRelease(bitmap);
CFRelease(attribString);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment