Created
February 20, 2015 11:20
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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