Posts for: 5/2010
Posts for: 6/2010
June 24, 2010

Displaying PDF documents on iOS

iOS has a very nice library for the displaying of PDFs, but because of their large size, take up significant memory and often are unable to be displayed all at once.  

In order to display large PDFs you must first know some UIView basics.  The UIView display area is generally composed of any number of CALayers.  There are several different types of CALayers which can be interchanged depending on what you want to display.  For the display of heavy PDFs or very large images, we will use the CATiledLayer.  This is a special layer which draws the layer content as a series of tiles, allowing the display of very large or memory intensive content such as huge images, PDFs, etc.

First we need to get a CGPDFPageRef from the PDF file. To do that we first get the PDF CGPDFDocumentRef, using the CGPDFDocumentCreateWithURL() function. From there we can retrieve the CGPDFPageRef by calling CGPDFDocumentGetPage(documentRef, pageNumber).

With the CGPDFPageRef in hand, we can now create our CATiledLayer. Below, is an example of the drawLayer method from a subclassed CATiledLayer:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
		CGPDFPageRef pageRef = myPageRef
		if (pageRef != nil && ctx != nil) {
			[(NSObject*)pageRef retain];
			//prevent releasing while drawing
			CGPDFPageRetain(pageRef);
			CGContextRetain(ctx);
			CGContextSaveGState(ctx);
			
			CGRect bounding = layer.bounds;			
			CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0);
			CGContextFillRect(ctx, CGContextGetClipBoundingBox(ctx));
			CGContextTranslateCTM(ctx, 0.0, bounding.size.height);
			CGContextScaleCTM(ctx, 1.0, -1.0);	
			CGContextConcatCTM(ctx, CGPDFPageGetDrawingTransform(pageRef, kCGPDFCropBox, bounding, 0, true));
			CGContextDrawPDFPage(ctx, pageRef);
			CGContextRestoreGState(ctx);
			CGContextRelease(ctx);
			CGPDFPageRelease(pageRef);
			[(NSObject*)pageRef release];
		}
		
}

First we retain both the pageRef and the CGContextRef.  (It is not strictly necessary to retain the pageRef but due to the multithreaded nature of CATiledLayer rendering a little defensive programming doesn't hurt) and save the CGContextRef state.

Next, using the layer bounds the pdf background color (white) is filled in. Since the iPhone coordinate system is the reverse of that of the Mac, we must translate and scale the context to flip the coordinate system and force rendering from the bottom left corner. After concacting the context to the PDF rendering rect, we finally draw the PDF to the context.After drawing, restore and release the context state, and if your done with the CGPDFPageRef and CGPDFDocumentRef, release them with CGPDFPageRelease and CGPDFDocumentRelease respectively.

This will get you a basic tiled PDF display.

One unfortunate drawback is that display of a tiled layer is not exactly speedy, and the user will be left staring at a black page while the various tiles are being drawn.  So how do we get something to be displayed immediately when the UIView is presented?  The easiest way is to create another CALayer behind the CATiledLayer and use it to draw a low resolution image of the pdf content.  Something like this in your UIView:

		//set up the background image layer
		self.imageLayer = [CALayer layer];
		//set the size and contents with the image		
		self.imageLayer.contents = (id) yourUIImage.CGImage;
		//add the layers
		[self.layer addSublayer:self.imageLayer];

But PDF display is still slow, memory intensive, etc and can create problems when paging quickly through a PDF document.  Since the true value of displaying a PDF is for the display of clean text while zooming, one way to limit the damage is in the CATiledLayer to only draw the pdf when zoomed in above a threshold and use images for content at lower zoom scales.  This will also limit exposure of your app to PDF rendering memory leaks that can be found in certain iOS versions. For example:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx

{
	if(self.zoomScale < 2.0 ){
		CGImageRef  cgImage = yourUIImage.CGImage;
		if( cgImage != nil )
		{		
			CGContextSaveGState( context );
			CGRect bounding = self.bounds;
			CGContextTranslateCTM(ctx, 0.0, bounding.size.height);
			CGContextScaleCTM(ctx, 1.0, -1.0);	
			CGContextDrawImage( context, bounding, cgImage );
			CGContextRestoreGState( context );
		}
	}else {
		CGPDFPageRef pageRef = myPageRef
		...
	}
}

 

While this doesn't cover all of the details, it should be enough to get you started.

June 20, 2010

Project Updates (Carbon Cube)

Since I left Ascendia at the end of March I've been pretty quiet on the blog and twitter.  The reason is pretty simple, worried that setting out on my own I would find myself without work and income, I filled my schedule.  The result of this is that I have been busier, and more productive than I have for a quite a while. In order to cope, I temporary shut down my blog writing processes and focused.  Now that I have a bit of (short lived) breather before the next group of projects kick in, it is time to reboot my blog writing.  The resulting silence as caused some concern among my friends and I apologize for that.

cCubeThe first project I became involved in is Carbon Cube. This is a tie up with the town of Oguni in Kumamoto to provide easily purchased, tangible, and verifiable carbon credits through tree planting.  Many credits or environmental tree planting programs are rightly viewed with suspicion.  Often the money is provided, but trees are either never planted, neglected and soon die off, or planted but then soon cut down.  This is money that could have been used elsewhere, provididing no real benefit other than to enrich those running said schemes.

Carbon CubeWith CarbonCube we provide through our website visual firm confirmation that the money provided is actually being used to plant and maintain the forest in a sustainable and responsible manner.  One carboncube can purchased for 500 yen (a little over 5 dollars US).  That 500 yen is then used to 1 purchase a seedling, and provide for continuing forest maintenance by the Oguni forestry association, creating a photographic and GPS record of where and when the seedling was planted, and for the creation of the cube and the basic running of the website itself. 

Plans are in the works for offering carboncubes at transportation hubs such as airports and highway rest areas, prefectural offices and shops , etc.  They are also being offered to corporations that purchase carbon credits from Oguni, which the corporations can in turn give to their customers as visible and verifiable proof of the corporation's green efforts.



Posts for: 5/2010
Posts for: 6/2010