I create pdf file. I need to draw rotation text. In current time I have this pdf file
My code which draw the text
............
CGContextTranslateCTM(context, 0, frameRect.origin.y*2);
CGContextScaleCTM(context, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frame, context);
// Add these two lines to reverse the earlier transformation.
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, (-1)*frameRect.origin.y*2);
I know that I must use CGContextRotateCTM(CGContextRef c, GFloat angle), but I bad understand transformation and I can't do as I need.
The following adds text rotated -90 degrees to the specified context at the yOffset from the top of the page:
+(void)addRotatedLabel:(CGContextRef)context atYOffset:(CGFloat)yOffset{
// -90 degrees
CGFloat rotation = -M_PI / 2.f;
CGContextRotateCTM(context, rotation);
NSString *label = [NSString stringWithFormat:#"%#", #"some text"];
CGSize maxSize = CGSizeMake(300, 12);
UIFont *font = [UIFont systemFontOfSize:7.5f];
CGSize textSize = [label sizeWithFont:font constrainedToSize:maxSize lineBreakMode:NSLineBreakByClipping];
CGRect titleRect = CGRectMake(-yOffset, 32.f - textSize.height + 3.f, textSize.width, textSize.height);
[[UIColor blackColor] setFill];
[label drawInRect:titleRect withFont:font];
CGContextRotateCTM(context, -rotation);
}
It can be easily modified to handle other rotations, font sizes, pass in the NSString, etc.
Related
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
This is the first time I've used core graphics and text so could be doing something very simple thats incorrect. I've been reading the documentation to try and see where i'm going wrong but can't see it so wonder if someone can help me spot the issue.
I'm trying to draw a simple little banner of information at the top of the screen but the text i want to appear in the banner is not drawn in the correct place.
In my view i'm creating my control as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.banner = [[Banner alloc] initWithFrame:CGRectMake(0,20,768,200)];
[self.view addSubview:self.banner];
}
The controls code is as follows:
#import <CoreText/CoreText.h>
#import "Banner.h"
#implementation Banner
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Get the graphics context.
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw the banner border.
CGContextSaveGState(context);
UIColor *strokeColor = [UIColor blackColor];
CGContextSetStrokeColorWithColor(context, strokeColor.CGColor);
CGContextSetLineWidth(context, 5);
CGContextStrokeRect(context, CGRectMake(2,2,self.frame.size.width -4,100));
CGContextRestoreGState(context);
// Setup the text label & value.
NSString *addressLabelText = #"Address";
NSString *addressValue = #"123 Letsby Avenue\nSome Place\nSome City\nSome County\nSome Postcode";
UIFont *font = [UIFont fontWithName:#"Helvetica" size:14.0];
CGSize labelSize = [self getBoundsForString:addressLabelText usingFont:font];
CGSize valueSize = [self getBoundsForString:addressValue usingFont:font];
// So I want to Draw the label at X:10 Y:10 and the Value At X:65 Y:10
// So we get Something like:
// =============================
// || Address 123 letsby avenue
// || Some Place
// || Some City
// || Some County
// || Some Postcode
// =============================
CGRect labelBounds = CGRectMake(10,10,labelSize.width,labelSize.height);
// Move the address value over the margin width(10) + the label width + some spacing(5)
CGRect valueBounds = CGRectMake(10 + labelSize.width + 5, 10, valueSize.width, valueSize.height);
[self drawString: addressLabelText withFont: font inRect:labelBounds usingContext:context];
[self drawString: addressValue withFont: font inRect:valueBounds usingContext:context];
// The text hasn't drawn where i thought it should. Draw a rect there to make sure i had the right bounds
UIColor *rectFillColor = [UIColor colorWithRed:0 green:0 blue:1 alpha:0.3];
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, [rectFillColor CGColor]);
CGContextFillRect(context, labelBounds);
CGContextFillRect(context, valueBounds);
CGContextRestoreGState(context);
//Hmm that is drawing in the right place. Whats going wrong with drawing the text.
[self setNeedsDisplay];
}
-(void) drawString:(NSString*) string withFont:(UIFont*)font inRect:(CGRect)rect usingContext:(CGContextRef) context
{
CGContextSaveGState(context);
// flipping the coordinate system.
CGContextTranslateCTM(context, 0, 200); // The control is rendered in a frame 200 high.
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Define the path we are going to draw the text on.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, rect);
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), (CFStringRef)string);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);
// Create a frame.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
// Draw the specified frame in the given context.
CTFrameDraw(frame, context);
CGContextRestoreGState(context);
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
}
-(CGSize) getBoundsForString:(NSString *)string usingFont:(UIFont *)font
{
return [string boundingRectWithSize:CGSizeMake(999,999)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]
context:nil].size;
}
#end
The result of this is some blue rects drawn in the banner where i would expect the text to be but the text actually appears outside of the banner and I'm puzzled why.
https://www.dropbox.com/s/q2ap9tl4okaka49/Banner.png
I've found what seems to work for my code above... changing this:
-(void) drawString:(NSString*) string withFont:(UIFont*)font inRect:(CGRect)rect usingContext:(CGContextRef) context
{
CGContextSaveGState(context);
// flipping the coordinate system.
CGContextTranslateCTM(context, 0, 200); // The control is rendered in a frame 200 high.
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
to
-(void) drawString:(NSString*) string withFont:(UIFont*)font inRect:(CGRect)rect usingContext:(CGContextRef) context
{
CGContextSaveGState(context);
// flipping the coordinate system.
CGContextTranslateCTM(context, 0, rect.size.height+(2*rect.origin.y));
CGContextScaleCTM(context, 1.0, -1.0);
The line following line doesn't seem to change anything.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
And changing this line:
CGContextTranslateCTM(context, 0, 200);
to
CGContextTranslateCTM(context, 0, rect.size.height+(2*rect.origin.y));
places the text correctly where i'd expect it to be.... using Xamarin and the equivalent c# I have to invert the result of the height + 2Y result :-S
how do i rotate a NSString to a certain degree ? when i draw a string together with an image.
i referred this SO question Drawing rotated text with NSString drawInRect but my Ellipse is gone.
//Add text to UIImage
-(UIImage *)addMoodFound:(int)moodFoundCount andMoodColor:(CGColorRef)mColour
{
float scaleFactor = [[UIScreen mainScreen] scale];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(36, 36), NO,scaleFactor);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
//CGContextSetRGBFillColor(context, kCGAggressiveColor);
CGContextSetFillColorWithColor(context,mColour);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, 36, 36));
CGContextSetRGBFillColor(context, 250, 250, 250, 1);
//nsstring missing after adding this 3 line
CGAffineTransform transform1 = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(65));
CGContextConcatCTM(context, transform1);
CGContextTranslateCTM(context, 36, 0);
////////////////////////////////////////////////
[[NSString stringWithFormat:#"%d",moodFoundCount ] drawInRect : CGRectMake(0, 7, 36, 18)
withFont : [UIFont fontWithName:monR size:17]
lineBreakMode : NSLineBreakByTruncatingTail
alignment : NSTextAlignmentCenter ];
CGContextRestoreGState(context);
UIImage *theImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}
CGAffineTransformMakeRotation will rotate around the context's origin (in this case x = 0, y = 0).
To properly rotate your text you need to first translate the origin of the context with the center of the box containing the string, rotate and move back the origin to its original location.
Replace the 3 lines where you're applying the rotation with:
CGContextConcatCTM(context, CGAffineTransformMakeTranslation(18, 18));
CGContextConcatCTM(context, CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(65)));
CGContextConcatCTM(context, CGAffineTransformMakeTranslation(-18, -18));
I want to rotate my NSString that's at a set location so that it reads from bottom to up.
I know the issue lies in the CGContextTranslateCTM, but I'm not sure how to fix it. If I comment this line, I see my text where I want it, just not rotated. This must be moving my NSString to where it is not visible.
Thoughts?
Here's what I have tried:
NSString * xString = [NSString stringWithFormat:#"%#", self.xStringValue];
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor blackColor] CGColor]);
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, 0);
CGContextRotateCTM(UIGraphicsGetCurrentContext(), M_PI/2);
[xString drawInRect:(CGRectMake(x, y, width, height) withFont:self.Font];
CGContextRestoreGState(UIGraphicsGetCurrentContext());
EDIT:
The code above is drawing my NSString values, but they are being positioned off screen where I can not see them.
This code is actually getting them on screen, just not in the right place.
X and Y are both calculated distances.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGAffineTransform transform = CGAffineTransformMakeRotation(-90.0 * M_PI/180.0);
CGContextConcatCTM(context, transform);
CGContextTranslateCTM(context, -rect.size.height-x, rect.size.height-y);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor blackColor] CGColor]);
[xString drawInRect:CGRectMake(x, y, w, h) withFont:self.textFont];
CGContextRestoreGState(context);
}
EDIT THREE:
Solution:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, point.x, point.y);
CGAffineTransform textTransform = CGAffineTransformMakeRotation(-1.57);
CGContextConcatCTM(context, textTransform);
CGContextTranslateCTM(context, -point.x, -point.y);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor whiteColor] CGColor]);
[string drawInRect:CGRectMake(x, y, width, height) withFont:font];
CGContextRestoreGState(context);
If you want to rotate a UILabel vertically (90°) you can do :
[_myLabel setTransform:CGAffineTransformMakeRotation(-M_PI / 2)];
The result for a UILabel :
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);