Core Text a bit smaller than NSString drawInRect:? - objective-c

In theory, these should be the same size, but they're not:
The text in blue is from Core Text, in black is from -[NSString drawInRect:]. Here is the code:
//Some formatting to get the correct frame
int y = MESSAGE_FRAME.origin.y + 8;
if (month) y = y + 27;
int height = [JHomeViewCellContentView heightOfMessage:self.entry.message];
CGRect rect = CGRectMake(MESSAGE_FRAME.origin.x + 8, y, MESSAGE_FRAME.size.width - 16, height);
//Draw in rect method
UIFont *font = [UIFont fontWithName:#"Crimson" size:15.0f];
[[UIColor colorWithWhite:0.2 alpha:1.0] setFill];
[self.entry.message drawInRect:rect withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentLeft];
//Frame change to suit CoreText
CGRect rect2 = CGRectMake(MESSAGE_FRAME.origin.x + 8, self.bounds.size.height - y - height, MESSAGE_FRAME.size.width - 16, height);
//Core text method
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)#"Crimson", 15.0f, NULL);
NSDictionary *attDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)fontRef, (NSString *)kCTFontAttributeName,
(id)[[UIColor blueColor] CGColor], (NSString *)kCTForegroundColorAttributeName,
nil];
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:self.entry.message attributes:attDictionary];
//Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, rect2);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge_retained CFAttributedStringRef)attString);
CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL);
CFRelease(framesetter);
CFRelease(path);
CTFrameDraw(theFrame, context);
CFRelease(theFrame);
The font is the same. I don't understand why one is being rendered differently.

It’s because NSLayoutManager uses some unusual heuristics to cope with certain fonts.
See How does line spacing work in Core Text? (and why is it different from NSLayoutManager?) for more detail.

Related

CGFontGetGlyphBBoxes wrong result

I have been trying to measure glyph bounds precisely but this code prints out 916!!! The real width of this is 69.
- (void)drawRect:(NSRect)dirtyRect {
CGContextRef main = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetTextMatrix(main, CGAffineTransformIdentity);
CGGlyph g;
CGPoint p = CGPointMake(100, 100);
CGRect rect = CGRectMake(0, 0, 0, 0);
CGFontRef font = CGFontCreateWithFontName((CFStringRef)#"Arial");
g = CGFontGetGlyphWithGlyphName(font, CFSTR("L"));
CGContextSetFont(main, font);
CGContextSetTextPosition(main, 0, 0);
CGContextSetFontSize(main, 200);
CGContextSetRGBFillColor(main, 0, 0, 1, 1);
CGContextShowGlyphsAtPositions(main, &g, &p, 1);
CGFontGetGlyphBBoxes(font, &g, 1, &rect);
printf("%f", rect.size.width);
}
You are using CGFontGetGlyphBBoxes, which returns the size in glyph space units. To use this, you need to scale it with the units per em and the font size.
CGRect rect;
CGFontRef font = CGFontCreateWithFontName((CFStringRef)#"Arial");
CGFloat fontSize = 200.0;
CGGlyph g = CGFontGetGlyphWithGlyphName(font, CFSTR("L"));
CGFontGetGlyphBBoxes(font, &g, 1, &rect);
CGFloat width = rect.size.Width / CGFontGetUnitsPerEm(font) * fontSize;
An alternate way to do it to use [NSFont boundingRectForCGGlyph:].
NSFont *font = [NSFont fontWithName:#"Arial" size:200];
NSRect rect = [font boundingRectForCGGlyph:g];
boundingRectForCGGlyph
Returns the bounding rectangle for the specified glyph, scaled to the receiver’s size.

How to change color of NSString in drawAtPoint

I have this chunk of code here that draws a block with a one-character string on it:
CGContextDrawImage(context, CGRectMake([blok getLocation].x * xunit, [blok getLocation].y * yunit, 40, 40), [blok getImage].CGImage);
[[blok getSymbol] drawAtPoint:CGPointMake([blok getLocation].x * xunit+15, [blok getLocation].y * yunit) withFont:[UIFont fontWithName:#"Helvetica" size:24]];
It's working fine, but I've been doing some layout changes, and now I need it so that the string drawn will be white. Using the methods for setting the fill color and the stroke color haven't done anything. Is there some other way to do this?
Set the foregroundcolor in the attributes and use the draw withAttributes functions
NSDictionary *attributes = [NSDictionary dictionaryWithObjects:
#[font, [UIColor whiteColor]]
forKeys:
#[NSFontAttributeName, NSForegroundColorAttributeName]];
[string drawInRect:frame withAttributes:attributes];
Have you tried:
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), textColor);
For example:
CGContextDrawImage(context, CGRectMake([blok getLocation].x * xunit, [blok getLocation].y * yunit, 40, 40), [blok getImage].CGImage);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), textColor);
[[blok getSymbol] drawAtPoint:CGPointMake([blok getLocation].x * xunit+15, [blok getLocation].y * yunit) withFont:[UIFont fontWithName:#"Helvetica" size:24]];
This is what I use for drawing labels:
- (void)_drawLabel:(NSString *)label withFont:(UIFont *)font forWidth:(CGFloat)width
atPoint:(CGPoint)point withAlignment:(UITextAlignment)alignment color:(UIColor *)color
{
// obtain current context
CGContextRef context = UIGraphicsGetCurrentContext();
// save context state first
CGContextSaveGState(context);
// obtain size of drawn label
CGSize size = [label sizeWithFont:font
forWidth:width
lineBreakMode:UILineBreakModeClip];
// determine correct rect for this label
CGRect rect = CGRectMake(point.x, point.y - (size.height / 2),
width, size.height);
// set text color in context
CGContextSetFillColorWithColor(context, color.CGColor);
// draw text
[label drawInRect:rect
withFont:font
lineBreakMode:UILineBreakModeClip
alignment:alignment];
// restore context state
CGContextRestoreGState(context);
}

Displaying a block of text with an oversized initial letter in a UITextView

Is there any way to provide this kind of effect in a UITextView? If not, then how can I achieve this effect?
Having the same requirement, I created a UIView subclass that draws text with a drop cap. The text is drawn using core text, and as #CodaFi suggested the drop cap is drawn in a separate core text frame.
The full implementation of the class: https://gist.github.com/4596476
The meat of it looks something like this:
- (void)drawRect:(CGRect)rect {
// Create attributed strings
NSAttributedString *bodyText = [[NSAttributedString alloc] initWithString:[self.text substringWithRange:NSMakeRange(1, self.text.length -1)] attributes:_bodyAttributes];
NSAttributedString *capText = [[NSAttributedString alloc] initWithString:[[self.text substringWithRange:NSMakeRange(0, 1)] uppercaseString] attributes:_capAttributes];
CGRect capFrame = CGRectMake(0, 0, [capText size].width, [capText size].height);
// Set up graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Create type frames
CGMutablePathRef bodyPath = CGPathCreateMutable();
CGAffineTransform transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1), 0, -self.bounds.size.height);
CGPathMoveToPoint(bodyPath, &transform, CGRectGetMaxX(capFrame), 0);
CGPathAddLineToPoint(bodyPath, &transform, self.bounds.size.width, 0);
CGPathAddLineToPoint(bodyPath, &transform, self.bounds.size.width, self.bounds.size.height);
CGPathAddLineToPoint(bodyPath, &transform, 0, self.bounds.size.height);
CGPathAddLineToPoint(bodyPath, &transform, 0, CGRectGetMaxY(capFrame));
CGPathAddLineToPoint(bodyPath, &transform, CGRectGetMaxX(capFrame), CGRectGetMaxY(capFrame));
CGPathCloseSubpath(bodyPath);
CGMutablePathRef capPath = CGPathCreateMutable();
CGPathAddRect(capPath, &transform, CGRectMake(0, 0, capFrame.size.width+10, capFrame.size.height+10));
// Draw text
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) bodyText);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), bodyPath, NULL);
CFRelease(framesetter);
CTFrameDraw(frame, context);
CFRelease(frame);
framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)capText);
frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), capPath, NULL);
CFRelease(framesetter);
CTFrameDraw(frame, context);
CFRelease(frame);
}
The gist of it is you create two paths, one rectangle to contain the drop cap, and a rectangle with a notch removed for the rest of the text. The full implementation on gist allows you to control the fonts, spacing around the drop cap, and content inset of the whole view using properties.
It does not implement most of the features of a UITextView (only the features I needed) so it may not be a full solution.
Hope that helps!

Draw text from uitextview to pdf as it is

I have 4 editable uitextviews, user can set its font font color etc. Now i want to draw pdf of these textviews, but using [self.view.layer renderInContext:UIGraphicsGetCurrentContext()] method causes to lose its quality, so i cannot use this method, instead i am iterating over subviews and drawing the text in pdf. Now my problem is that for some textviews text is printed correctly in pdf,but for other textviews text is not drawn in proper position.This is my code to draw pdf from textview.
NSArray *arrayOfsubviews=[self.view subviews];
for (int i=0; i<[arrayOfsubviews count]; i++) {
if ([[arrayOfsubviews objectAtIndex:i]isKindOfClass:[UITextView class]]) {
UITextView *texts=[arrayOfsubviews objectAtIndex:i];
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)texts.font.fontName, texts.font.pointSize,NULL);
CGColorRef color = texts.textColor.CGColor;
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)ctFont, (id)kCTFontAttributeName,
color, (id)kCTForegroundColorAttributeName,nil];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:texts.text
attributes:attributesDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) stringToDraw);
CGRect rect=CGRectMake(texts.frame.origin.x,916-texts.frame.origin.y-texts.frame.size.height, texts.frame.size.width, texts.frame.size.height);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL,rect);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0),pathRef, NULL);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0,916);
CGContextScaleCTM(context, 1.0, -1.0);
CTFrameDraw(frameRef, context);
CGContextRestoreGState(context);
CGPathRelease(pathRef);
}
}
UIGraphicsEndPDFContext();
I use this to draw my text, works fairly well.. maybe it can point you in the right direction.
#define kBorderInset 20.0
#define kMarginInset 10.0
- (void) drawText{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(currentContext, 0.0, 0.0, 0.0, 1.0);
NSString *textToDraw = #"Your Text Here";
UIFont *font = [UIFont systemFontOfSize:14.0];
CGSize stringSize = [textToDraw sizeWithFont:font
constrainedToSize:CGSizeMake(pageSize.width - 2*kBorderInset-2*kMarginInset, pageSize.height - 2*kBorderInset - 2*kMarginInset)
lineBreakMode:UILineBreakModeWordWrap];
CGRect renderingRect = CGRectMake(kBorderInset + kMarginInset, kBorderInset + kMarginInset + 350.0, pageSize.width - 2*kBorderInset - 2*kMarginInset, stringSize.height);
[textToDraw drawInRect:renderingRect
withFont:font
lineBreakMode:UILineBreakModeWordWrap
alignment:UITextAlignmentLeft];
}
You want to use drawInContext: instead of renderInContext:. renderInContext will rasterize the text, drawInContext write encode it as editable, resolution-independent text.
Some looses of quality or blurry views are because of the frame. The vales for locate a frame in the screen are CGFloat, so you could have a frame located in the point (0.5, 5.7), obviously this dowsn't have any sense and the point.x must be 0 or 1. Decimal values confuse the Operative System, so it's recommend to ensure that frames have always values with no decimal numbers.
You can ges some more information here: http://www.cobiinteractive.com/blog/2011/06/objective-c-blurry-uiview/ or here: http://www.markj.net/iphone-uiview-blurred-blurry-view/

Wrap Text in CGContextShowTextAtPoint

I'm trying to display texts in a CGContext and I want to know how to wrap the texts. How can I make this possible?
Here's my code:
NSString *text1 = #"These three first articles describe the God in whom we believe. The pictures of the triangle represents the Triune God), of Jesus Christ and of the dove representing the Holy Spirit are grouped together in order to manifest their intimate relationships and unity.";
- (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx {
if(index>[images count]-1)return;
UIImage *image = [images objectAtIndex:index];
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
CGAffineTransform transform = aspectFit(imageRect,
CGContextGetClipBoundingBox(ctx));
NSString *text1 = [script objectAtIndex:index];
char* text = (char *)[text1 cStringUsingEncoding:NSASCIIStringEncoding];
CGContextSelectFont(ctx, "Arial", 30, kCGEncodingMacRoman);
CGContextConcatCTM(ctx, transform);
CGContextDrawImage(ctx, imageRect, [image CGImage]);
CGContextSetTextPosition(ctx, 0.0f, round(30 / 4.0f));
CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGContextSetRGBFillColor(ctx, 0, 0, 0, 1);
CGContextShowTextAtPoint(ctx,10,10,text, strlen(text));
}
If you need more advanced text formatting (like wrapping), use Core Text instead. To display a simple string in a rectangle, use
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CGPath p = CGPathCreateMutable();
CGPathAddRect(p, rect);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), p, NULL);
CTFrameDraw(frame, context);