Nearly every example available online for rasterizing a PDF document into an image on iOS or OS X is wrong. Most of those take this approach:
//Document Loading
CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL(#URL#);
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, 1);
//Sizing
CGRect mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
size_t width = (size_t) CGRectGetWidth(mediaBox);
size_t height = (size_t) CGRectGetHeight(mediaBox);
size_t bytesPerRow = ((width * 4) + 0x0000000F) & ~0x0000000F;
//Drawing context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little));
CGColorSpaceRelease(colorSpace);
//Fill the background color
CGContextSetFillColorWithColor(context, UIColor.whiteColor.CGColor);
CGContextFillRect(context, CGRectMake(0.0, 0.0, width, height));
//Draw the page
CGContextDrawPDFPage(context, page);
//Get the image
CGImageRef result = CGBitmapContextCreateImage(context);
UIImage *resultUIImage = [UIImage imageWithCGImage:result];
//Clean up
CGContextRelease(context);
CGImageRelease(result);
CGPDFDocumentRelease(pdfDocument);
Error and bounds checking omitted for brevity.
This generally works, but where it really falls apart is when the PDF document has something unexpected, whether it’s a drawing affine transformation not equal to the identity matrix or a media box not equal to the crop box.
As you can see, anything beyond a simple document renders completely wrong. Creating an algorithm to get the correct result means incorporating both the document transform and using the correct page box.
//This is defined previously and is the size
//we want to draw the document at. After all,
//PDF documents are meant to be vector content
//so they can be scaled at will.
CGSize drawingSize;
//Document Loading
CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL(#URL#);
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, 1);
//Start by getting the crop box since only
//its contents should be drawn
CGRect cropBox = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
//Account for rotation of the page to figure
//out the size to create the context. Like images,
//rotation can be represented by one of two
//ways in a PDF: the contents can be pre-rotated
//in which case nothing needs to be done or the
//document can have its rotation value set and
//it means we need to apply the rotation as an
//affine transformation when drawing
NSInteger rotationAngle = CGPDFPageGetRotationAngle(page);
CGFloat angleInRadians = -rotationAngle * (M_PI / 180);
CGAffineTransform transform = CGAffineTransformMakeRotation(angleInRadians);
CGRect rotatedCropRect = CGRectApplyAffineTransform(cropBox, transform);
//Here we're figuring out the closest size we
//can draw the PDF at that's no larger than
//drawingSize
CGRect bestFit = BMBestFitFrameForSizeInRect(rotatedCropRect.size, CGRectMake(0.0, 0.0, drawingSize.width, drawingSize.height));
CGFloat scale = CGRectGetHeight(bestFit) / CGRectGetHeight(rotatedCropRect);
size_t width = (size_t) roundf(CGRectGetWidth(bestFit));
size_t height = (size_t) roundf(CGRectGetHeight(bestFit));
size_t bytesPerRow = ((width * 4) + 0x0000000F) & ~0x0000000F;
//Create the drawing context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little));
CGColorSpaceRelease(colorSpace);
//Fill the background color
CGContextSetFillColorWithColor(context, UIColor.whiteColor.CGColor);
CGContextFillRect(context, CGRectMake(0.0, 0.0, width, height));
//This portion is the core of drawing the PDF
//correctly and is seldom seen in any examples
//found online. This is where we create the
//affine transformation matrix to align the
//PDF's CropBox to our drawing context.
transform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, CGRectMake(0, 0, CGRectGetWidth(bestFit), CGRectGetHeight(bestFit)), 0, true);
if (scale > 1)
{
//Since CGPDFPageGetDrawingTransform won't
//scale up, we need to do it manually
transform = CGAffineTransformTranslate(transform, CGRectGetMidX(cropBox), CGRectGetMidY(cropBox));
transform = CGAffineTransformScale(transform, scale, scale);
transform = CGAffineTransformTranslate(transform, -CGRectGetMidX(cropBox), -CGRectGetMidY(cropBox));
}
CGContextConcatCTM(context, transform);
//Clip the drawing to the CropBox
CGContextAddRect(context, cropBox);
CGContextClip(context);
CGContextDrawPDFPage(context, page);
CGImageRef result = CGBitmapContextCreateImage(context);
UIImage *resultUIImage = [UIImage imageWithCGImage:result];
CGContextRelease(context);
CGImageRelease(result);
CGPDFDocumentRelease(pdfDocument);
Again, error and bounds checking omitted for brevity.
Now, let’s test the result:
Everything draws exactly as it should. This example has used the UI* classes rather than their NS* counterparts, but as of OS X 10.8 they’re almost fully API-compatible, so it’s a quick change to use one or the other. Beyond this it’s also relatively easy to add on support for interactive functionality, such as a table of contents or embedded links.