Whilst deciding what to include with the App, we write a PDF generator!

Managed to implement rendering of recipes into multipage PDF documents. They look something like this:

Lasagne Recipe

One thing to note whilst writing the code was the usual inverted co-ordinates. I wanted an image in the top left, title and summary in the top right. After some internal debate I settled on a two column format for the rest. The image is easy, just use the UIImage drawInRect message once you’ve started your PDF context. As for the text the origin of your text box is the bottom left due to the inverted co-ordinates, so it is important to remember when calculating offsets that you need to set the offset.x and offset.y options to the margins of your page and adjust the size.height & size.width settings of the rect to ensure the text renders at the appropriate point. If you need to calculate the size of the rect the text occupies before rendering (not to adjust the rect for rendering but to calculate the location of the next rect) use CTFramesetterSuggestFrameSizeWithConstraints. For example when rendering the summary in the top right I use that function so I can decide whether the image or the summary is the larger and can therefore decide which value to use when calculating the available height for the columns. The following PDF has some debug code turned on that shows a red outline for the rect used to render the text and a small circle at the origin point of the rect. As you can see although the summary text is small the rect was the full height of the page.

Lasagne Recipe with Guidelines

As for rendering the core text itself I settled on a function like this:


+ (CFRange)renderTextRange:(CFRange)currentRange andFrameSetter:(CTFramesetterRef)frameSetter intoRect:(CGRect)frameRect {
	CGMutablePathRef framePath = CGPathCreateMutable();
	CGPathAddRect(framePath, NULL, frameRect);
	CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, currentRange, framePath, NULL);
	CGContextRef currentContext = UIGraphicsGetCurrentContext();
	CGContextSaveGState(currentContext);
	CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);	
	CGContextTranslateCTM(currentContext, 0, 792); 
	CGContextScaleCTM(currentContext, 1.0, -1.0);
	CTFrameDraw(frameRef, currentContext);
	/*
	CGContextSetStrokeColorWithColor(currentContext, [[UIColor redColor] CGColor]);
	CGContextAddRect(currentContext, frameRect);
	CGContextStrokePath(currentContext);
	CGContextAddEllipseInRect(currentContext, CGRectMake(frameRect.origin.x-5, frameRect.origin.y-5, 10, 10));
	CGContextStrokePath(currentContext);
	 */
	CGContextRestoreGState(currentContext);
	CGPathRelease(framePath);
	currentRange = CTFrameGetVisibleStringRange(frameRef);
	currentRange.location += currentRange.length;
	currentRange.length = 0;
	CFRelease(frameRef);
	return currentRange;
}

The debug code is the commented out section. The Save/Restore CGState calls are for my own sanity as is the CGContextSetTextMatrix so I can ensure that drawing operations outside of this routine do not affect the drawing process. Although if you remove them keep in mind that the CGContextXXXCTM calls will need undoing somehow or each time the routine is called you’ll apply new and interesting transformations to your text.

If anyone wants me to post a full example block of code comment and I’ll update or make a new post.

This entry was posted in Programming and tagged , , , , . Bookmark the permalink.