Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing Text with Core Graphics

I need to draw centered text to a CGContext.

I started with a Cocoa approach. I created a NSCell with the text and tried to draw it thus:

NSGraphicsContext* newCtx = [NSGraphicsContext
     graphicsContextWithGraphicsPort:bitmapContext flipped:true];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:newCtx];
[pCell setFont:font];
[pCell drawWithFrame:rect inView:nil];
[NSGraphicsContext restoreGraphicsState];

But the CGBitmapContext doesn't seem to have the text rendered on it. Possibly because I have to pass nil for the inView: parameter.

So I tried switching text rendering to Core Graphics:

The simplest way seems to be to use CGContextSelectFont to select a font using its postscript name, and point size, but CGContextShowTextAtPoint only takes non-unicode characters, and there is no apparent way to fit the text to a rectangle: or to compute the extents of a line of text to manually lay out the rectangle.

Then, there is a CGFont that can be created, and set cia CGContextSetFont. Drawing this text requires CGContextShowGlyphsAtPoint, but again the CGContext seems to be lacking functions to compute the bounding rect of generated text, or to wrap the text to a rect. Plus how to transform a string to an array of CGGlyphs is not obvious.

The next option is to try using CoreText to render the string. But the Core Text classes are hugely complicated, and while there are samples that show how to display text, in a specified font, in a rect, there are no samples demonstrating how to compute the bounding rect of a CoreText string.

So:

  • Given a CGFont a CGContext how do I compute the bounding rect of some text, and how do I transform a text string into an array of CGGlyphs?
  • Given a string, a CGContext and a postscript name and point size, what Core Text objects do I need to create to compute the bounding rect of the string, and/or draw the string wrapped to a rect on the CGContext.
  • Given a string and a NSFont - how do I render the string onto a CGBitmapContext? I already know how to get its extents.
like image 894
Chris Becke Avatar asked Dec 08 '10 10:12

Chris Becke


3 Answers

I finally managed to find answers after 4 days of searching. I really wish Apple makes better documentation. So here we go

I assume you already have CGFontRef with you. If not let me know I will tell you how to load a ttf from resource bundle into CgFontRef.

Below is the code snippet to compute the bounds of any string with any CGFontref

        int charCount = [string length];
        CGGlyph glyphs[charCount];
        CGRect rects[charCount];

        CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount);
        CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount);

        int totalwidth = 0, maxheight = 0;
        for (int i=0; i < charCount; i++)
        {
            totalwidth += rects[i].size.width;
            maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight;
        }

        dim = CGSizeMake(totalwidth, maxheight);

Reuse the same function CTFontGetGlyphsForCharacters to get the glyphs. To get a CTFontRef from a CGFontRef use CTFontCreateWithGraphicsFont() function

Also remember NSFont and CGFontRef are toll-free bridged, meaning they can casted into each other and they will work seamlessly without any extra work.

like image 68
Rajavanya Subramaniyan Avatar answered Nov 03 '22 16:11

Rajavanya Subramaniyan


I would continue with your above approach but use NSAttributedString instead.

NSGraphicsContext* newCtx = [NSGraphicsContext graphicsContextWithGraphicsPort:bitmapContext flipped:true];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:newCtx];
NSAttributedString *string = /* make a string with all of the desired attributes */;
[string drawInRect:locationToDraw];
[NSGraphicsContext restoreGraphicsState];
like image 8
Jon Hess Avatar answered Nov 03 '22 16:11

Jon Hess


Swift 5 version!

let targetSize: CGSize = // the space you have available for drawing the text
let origin: CGPoint = // where you want to position the top-left corner
let string: String = // your string
let font: UIFont = // your font

let attrs: [NSAttributedString.Key:Any] = [.font: font]
let boundingRect = string.boundingRect(with: targetSize, options: [.usesLineFragmentOrigin], attributes: attrs, context: nil)
let textRect = CGRect(origin: origin, size: boundingRect.size)
text.draw(with: textRect, options: [.usesLineFragmentOrigin], attributes: attrs, context: nil)
like image 1
Robin Stewart Avatar answered Nov 03 '22 17:11

Robin Stewart