iPDFdev Tips & Tricks for PDF development

PDF page imposition using CoreGraphics on iOS

June 11th, 2013

A recent question on StackOverflow asked about how to create a single large PDF page from multiple PDF pages. This process is known as page imposition, it lets you combine multiple PDF pages onto larger sheets to create books, booklets or other special arrangements.

My answer to that question handled only the situation when all the source pages had the same size and rotation. The code here is more complex and it handles any combination of page sizes and rotation.

After the document is loaded the size of the destination page is computed. All the pages from the source document are drawn one after another in a single column. In this situation the width of the destination page matches the visual width of widest source page. The destination page height is the sum of visual heights of source PDF pages. The visual size (width/height) of the PDF page is given by the page crop box.

Once the size of the destination page has been determined, the new PDF document is created and the source pages are drawn on the destination page. The drawing process is handled by the PDFPageRenderer class (developed in the article Display a PDF page on the iPhone and iPad). Because the PDFPageRenderer expects a top-left coordinate system and the PDF page coordinate system is located in bottom left corner, we need to translate the destination page coordinate system to top left corner and reverse the Y axis.

The complete code is shown below:

// Open the PDF document
NSURL *pdfURL = [[NSBundle mainBundle] URLForResource:@"source.pdf" withExtension:nil];
pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
 
float destPageHeight = 0;
float destPageWidth = 0;
// Compute the size of the destination page.
// The width is given by the widest page in the source document.
// The height is the sum of the visual heights of the pages in the source document.
int pageCount = CGPDFDocumentGetNumberOfPages(pdf);
for (int i = 1; i <= pageCount; i++) {
    CGPDFPageRef pageRef = CGPDFDocumentGetPage(pdf, i);
    CGRect pageCropBox = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
    int rotation = CGPDFPageGetRotationAngle(pageRef);
 
    if ((rotation == 90) || (rotation == 270) ||
        (rotation == -90) || (rotation == -270)) {
        destPageHeight = destPageHeight + pageCropBox.size.width;
        if (destPageWidth < pageCropBox.size.height) {
            destPageWidth =  pageCropBox.size.height;
        }
    }
    else {
        destPageHeight = destPageHeight + pageCropBox.size.height;
        if (destPageWidth < pageCropBox.size.width) {
            destPageWidth =  pageCropBox.size.width;
        }
    }
}
 
// Setup the destination document.
CGRect destPageSize = CGRectMake(0, 0, destPageWidth, destPageHeight);
NSMutableData* pdfData = [[NSMutableData alloc] init];
CGDataConsumerRef pdfConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)pdfData);
CGContextRef pdfContext = CGPDFContextCreate(pdfConsumer, &destPageSize, NULL);
 
// Begin the PDF page
CGPDFContextBeginPage(pdfContext, NULL);
 
// Translate the coordinate system in the top left corner of the page and
// set the Y axis to grow from top to bottom because the PDFPageRenderer expects this coordinate system.
CGContextTranslateCTM(pdfContext, 0, destPageHeight);
CGContextScaleCTM(pdfContext, 1, -1);
 
// The point where the current page is drawn.
CGPoint crtPoint = CGPointMake(0, 0);
 
// Loop through all the pages and draw each source page on the destination page.
for (int i = 1; i <= pageCount; i++) {
    CGPDFPageRef pageRef = CGPDFDocumentGetPage(pdf, i);
    CGSize size = [PDFPageRenderer renderPage:pageRef inContext:pdfContext atPoint:crtPoint];
 
    // Current drawing point is updated with the height of the previously drawn page.
    crtPoint.y = crtPoint.y + size.height;
}
 
// End the page and the document.
CGPDFContextEndPage(pdfContext);
CGPDFContextClose(pdfContext);
 
// Save the final PDF document
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfFile = [documentsDirectory stringByAppendingPathComponent:@"destination-imposed.pdf"];
[pdfData writeToFile:pdfFile atomically:NO];
[pdfData release];
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.