iPad SDK : Preparing Dudel for a New Tool (part 5) - Rendering Multiple Styles

3/8/2011 3:28:59 PM

6. Rendering Multiple Styles

So, we now have a tool for drawing text, but we still haven't reached the core of what's interesting about Core Text: rendering multiple styles. At this point, lacking a standard GUI widget that gives us anything like a WYSIWYG display while editing the text, there's no really nice way to enter rich text as you can in a word processor, with buttons to change fonts or set colors.

Fortunately for me, I know that you're a computer programmer, and chances are you're already familiar with a way of marking text attributes that isn't as nice, but is applicable to a wide range of problems: HTML! Let's extend our text-rendering algorithm to include a very basic parsing of the text that the user enters, looking for embedded tags that we can use to assign attributes to the text.

I'm going to show you a very simple approach that uses an NSScanner object to scan through the entire text string, searching for just a single kind of tag: <font> (and its matching end tag). It will use the specified values to add attributes to the text. What we're doing here is just barely what I would call "parsing," and will probably make you cringe if your computer science education is less rusty than mine. I'm also well aware that the font tag has been deprecated for years, but it's sure an easy way to do quick-'n-dirty markup compared to using CSS! And it works well for our purposes here.

Edit the beginning of the draw method of TextDrawingInfo as shown here, removing the crossed-out lines and replacing them with the bold line:

- (void)draw {
CGContextRef context = UIGraphicsGetCurrentContext();

//NSMutableAttributedString *attrString = [[[NSMutableAttributedString alloc] initWithString:self.text] autorelease];
//[attrString addAttribute:(NSString *)(kCTForegroundColorAttributeName)
value:(id)self.strokeColor.CGColor range:NSMakeRange(0, [self.text length])];
NSAttributedString *attrString = [self attributedStringFromMarkup:self.text];
CTFramesetterRef framesetter =

Now we need to define the attributedStringFromMarkup: method in the same class (anywhere above the draw method should be fine). This uses NSScanner to look for the tags it knows about, and makes one big NSMutableAttributedString out of a number of smaller NSAttributedStrings generated between tags. Here, you also see a little usage of CTFontRef, which is Core Text's own way of referring to fonts.

- (NSAttributedString *)attributedStringFromMarkup:(NSString *)markup {
NSMutableAttributedString *attrString =
[[[NSMutableAttributedString alloc] initWithString:@""] autorelease];
NSString *nextTextChunk = nil;
NSScanner *markupScanner = [NSScanner scannerWithString:markup];
CGFloat fontSize = 0.0;
NSString *fontFace = nil;
UIColor *fontColor = nil;
while (![markupScanner isAtEnd]) {
[markupScanner scanUpToString:@"<" intoString:&nextTextChunk];
[markupScanner scanString:@"<" intoString:NULL];
if ([nextTextChunk length] > 0) {
CTFontRef currentFont =
CTFontCreateWithName((CFStringRef)(fontFace ? fontFace : self.font.fontName),
(fontSize != 0.0 ? fontSize : self.font.pointSize),
UIColor *color = fontColor ? fontColor : self.strokeColor;
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
(id)color.CGColor, kCTForegroundColorAttributeName,
(id)currentFont, kCTFontAttributeName,
NSAttributedString *newPiece = [[[NSAttributedString alloc]
initWithString:nextTextChunk attributes:attrs] autorelease];
[attrString appendAttributedString:newPiece];

NSString *elementData = nil;
[markupScanner scanUpToString:@">" intoString:&elementData];
[markupScanner scanString:@">" intoString:NULL];
if (elementData) {
if ([elementData length] > 3 &&
[[elementData substringToIndex:4] isEqual:@"font"]) {
fontFace = fontFaceNameFromString(elementData);
fontSize = fontSizeFromString(elementData);
fontColor = fontColorFromString(elementData);
} else if ([elementData length] > 4 &&
[[elementData substringToIndex:5] isEqual:@"/font"]) {
// reset all values
fontSize = 0.0;
fontFace = nil;
fontColor = nil;
return attrString;

This method, in turn, offloads the parsing of the font element attributes to the following three functions. Put these directly above the attributedStringFromMarkup:@implementation block is totally fine.) method. (Although it may seem wrong, putting them inside the

static NSString *fontFaceNameFromString(NSString *attrData) {
NSScanner *attributeDataScanner = [NSScanner scannerWithString:attrData];
NSString *faceName = nil;
if ([attributeDataScanner scanUpToString:@"face=\"" intoString:NULL]) {
[attributeDataScanner scanString:@"face=\"" intoString:NULL];
if ([attributeDataScanner scanUpToString:@"\"" intoString:&faceName]) {
return faceName;
return nil;
static CGFloat fontSizeFromString(NSString *attrData) {
NSScanner *attributeDataScanner = [NSScanner scannerWithString:attrData];
NSString *sizeString = nil;
if ([attributeDataScanner scanUpToString:@"size=\"" intoString:NULL]) {
[attributeDataScanner scanString:@"size=\"" intoString:NULL];
if ([attributeDataScanner scanUpToString:@"\"" intoString:&sizeString]) {
return [sizeString floatValue];
return 0.0;
static UIColor *fontColorFromString(NSString *attrData) {
return nil;

You'll notice that the third method, fontColorFromString(), isn't shown in a completed form here. In the interests of time and space, and not wandering too far afield from our main topic, let's leave that as an exercise for the reader, shall we?

With this in place, we now have a way to define some characteristics of the text we enter! Build and run Dudel, and create some new objects using the Text tool to try it out. Here are some suggestions for putting it through its paces:

  • Create a paragraph with some <font size="64">really big text</font> and then more normal-sized text.

  • Try sticking some <font face="Courier">Courier into the mix</font> to see how multiple fonts are rendered

Mix and match these however you like. Our parser is far from perfect, and throwing something like nested font tags at it will probably confuse it, but at least it's something!

Other -----------------
- jQuery 1.3 : AJAX - Loading data on demand (part 3) - Loading an XML document
- jQuery 1.3 : AJAX - Loading data on demand (part 2) - Working with JavaScript objects
- jQuery 1.3 : AJAX - Loading data on demand (part 1) - Appending HTML
- Coding JavaScript for Mobile Browsers (part 13) - Zoom and rotate gestures
- Coding JavaScript for Mobile Browsers (part 12) - Swipe gesture
- Coding JavaScript for Mobile Browsers (part 11)
- Coding JavaScript for Mobile Browsers (part 10) - Event Handling
- Coding JavaScript for Mobile Browsers (part 9) - Scripting Styles
- Coding JavaScript for Mobile Browsers (part 8) - DOM
- Coding JavaScript for Mobile Browsers (part 7)
- Coding JavaScript for Mobile Browsers (part 6)
- iPad SDK : The Structure of Core Text
- iPad SDK : PDF Generation
- jQuery 1.3 : Sorting and paging (part 5) - Finessing the sort keys
- jQuery 1.3 : Sorting and paging (part 4)
- jQuery 1.3 : Sorting and paging (part 3) - Using a comparator to sort table rows
- jQuery 1.3 : Sorting and paging (part 2) - JavaScript sorting
- jQuery 1.3 : Sorting and paging (part 1) - Server-side sorting
- Coding JavaScript for Mobile Browsers (part 5)
- Coding JavaScript for Mobile Browsers (part 4)
- First look: Apple Watch

- 10 Amazing Tools You Should Be Using with Dropbox

- Sigma 24mm f/1.4 DG HSM Art

- Canon EF11-24mm f/4L USM

- Creative Sound Blaster Roar 2

- Alienware 17 - Dell's Alienware laptops

- Smartwatch : Wellograph

- Xiaomi Redmi 2
Popular tags
Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8