Drawing PDF Documents


The technique of drawing PDF files in Quartz 2D is very similar to the technique for drawing bitmap images discussed in Chapter 8, "Image Basics." Instead of creating a CGImage, an application must create an instance of the opaque data type CGPDFDocument. The process used to create CGImages and CGPDFDocuments are very similar, though. With a PDF document object in hand, Quartz 2D can retrieve individual pages of the file as instances of the CGPDFPage abstract data type. Drawing that page onto a CGContext is as easy as calling CGContextDrawPDFPage.

Creating a CGPDFDocument

A CGPDFDocument obtains its PDF data from a CGDataProvider. CGDataProviders were first looked at in Chapter 8 when creating CGImages from bitmap data. Just to recap, Quartz 2D can create data providers for any of a number of data sources, and Quartz provides utility routines for creating data providers from popular sources. For example, two popular data sources are blocks of data in memory, which you can access with CGDataProviderCreateWithData, and files on disk you can access with a data provider created with CGDataProviderCreateWithURL. For custom data sources, the library can create a data provider called CGDataProviderCreatewithDirectAccess that uses a set of callbacks to obtain data.

Once you have created a data provider, the routine CGPDFDocumentCreateWithProvider will extract the PDF data and create an CGPDFDocument instance.

The case of reading PDF data from a file is so common that Quartz 2D also offers a utility routine, CGPDFDocumentCreateWithURL. This routine creates a data provider on your behalf and returns a CGPDFDocument that uses that data provider. As with CGImages, you will probably want to pass this routine a URL to a local resource most of the time. Core Graphics can handle URLs for remote files, but it has no mechanism to pass you progress information and errors. To provide a proper user experience with download progress bars and proper error handling, you are probably better off using another Mac OS X mechanism to download the file data.

Retrieving Pages

Quartz 2D does not draw PDF documents; it draws the pages in PDF documents. After creating a PDF document, you will have to extract the pages you want to draw from it. The routine CGPDFDocumentGetNumberOfPages returns the number of pages in a CGPDFDocument. An instance of the CGPDFPage opaque data type identifies an individual page in the document. Calling CGPDFDocumentGetPage retrieves the CGPDFPage from a document by its page number.

Drawing Pages

The CGContextDrawPDFPage method does just as its name implies; it draws a CGPDFPage object into the context. The only parameter to this routine is the CGPDFPageRef it should draw. Quartz 2D draws the page so that the lower left corner of the page's mediaBox attribute is at the origin and aligned to the coordinate axes.

Speaking of the mediaBox, the PDF file format keeps track of several different rectangles, and the mediaBox is just one of them. The mediaBox is the rectangle that defines the physical boundaries of the media (usually paper) that the file's author intended that page to be printed on. For example, if the page was intended for US letter sized paper, then the mediaBox would be 8.5 x 11 inches or 612 x 792 points.

In addition to the mediaBox, Quartz includes routines to retrieve any of these rectangles:

Crop box This is a hint to the program displaying the PDF that the page's image should be clipped to this rectangle. If no crop box is specified in the PDF data, the crop box defaults to the mediaBox.

Bleed box This box has great merit for PDFs used in professional printing but will probably be of little use to applications outside of that realm. The bleed box specifies an area of the page that, when printed, contains important marks about how to process the sheet of paper the page was printed on after it comes off the press. For example, if the PDF contains a graphic that is to be printed on a cereal box, the bleed box might include labels that indicate what month the box was printed, where to cut away the excess cardboard, and where the box must fold to form its final shape. If no bleed box is specified in the PDF data, its value defaults to the crop box.

Trim box The trim box is another professional printing tool. Professional printers will often print their images on large sheets and then cut those sheets down (trim them) to their final size. The trim box represents how much of the page will be left after trimming. Most applications will not need to work with the trim box, but Quartz 2D makes it available if you need it. If the trim box is not found in the PDF data, it also uses the crop box as its default.

Art box Simply put, the art box is that area of the page that the PDF author considers to have meaningful content. Exactly what constitutes the art box is at the discretion of the program that generated the file. This box also defaults to the crop box if it is otherwise unspecified.

Because Quartz 2D always draws the page at the origin when you call CGContextDrawPDFPage, coordinate system transformations are the only way to relocate it on the output device. The routine CGPDFPageGetDrawingTransform simplifies the task of creating an appropriate transformation. To use the routine, you select one of the boxes just described and the rectangle in user space the box will map into when you draw the page. The parameters to the routine can rotate the page by a multiple of 90 degrees, changing landscape pages to portrait orientations and vice-versa. The routine can also preserve the aspect ratio of the PDF box if the transform has a scaling component. Naturally CGPDFPageGetDrawingTransform returns a CGAffineTransform that should be concatenated onto the CTM of a context before a call to CGContextDrawPDFPage.

A PDF Drawing Example

To demonstrate how this process comes together, the following sample draws a collage of the pages in a PDF document. Figure 14.1 shows the effect of running this code on a PDF with three pages.

Figure 14.1. Fanned Pages of a PDF File


The fan-shaped collage is created by reading a PDF from a file and then drawing successive pages with the appropriate transformations. This code is part of the DrawPDF sample application.

Listing 14.1. Drawing a PDF Document

void DrawTravelBrochure(CGContextRef cgContext, CGRect viewBounds) {     float backgroundColor[] = { 0.7, 1.0 };     CGColorSpaceRef grayColorSpace =             CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);     /* draw the background color */     CGContextSetFillColorSpace(cgContext, grayColorSpace);     CGContextSetFillColor(cgContext, backgroundColor);     CGContextAddRect(cgContext, viewBounds);     CGContextFillPath(cgContext);     // Adjust the origin for the rest of the image     CGContextTranslateCTM(cgContext,            CGRectGetMidX(viewBounds),            viewBounds.origin.y + viewBounds.size.height / 8.0);     // Grab a reference to the PDF file     CFURLRef pdfURL = CFBundleCopyResourceURL(             CFBundleGetMainBundle(),             CFSTR("TravelBrochure"),             CFSTR("pdf"), NULL);     // Create a CGPDFDocument from it     CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL(pdfURL);     // Count the number of pages in the document and calculate     // a rotation angle from it     size_t numPages = CGPDFDocumentGetNumberOfPages(pdfDocument);     float angleStep = pi / (3.0 * (float)numPages);     // Calculate a "nice" size for the page     for(short pageCtr = numPages; pageCtr > 0; pageCtr--) {             // Draw the current page             CGPDFPageRef currentPage =                     CGPDFDocumentGetPage(pdfDocument, pageCtr);     // Run a simple calculation to get a suggested drawingRect     CGRect mediaBox =             CGPDFPageGetBoxRect(currentPage, kCGPDFMediaBox);     float suggestedHeight = viewBounds.size.height * 2.0 / 3.0;     CGRect suggestedPageRect = CGRectMake(0, 0,            suggestedHeight *            (mediaBox.size.width / mediaBox.size.height),            suggestedHeight);     CGContextSaveGState(cgContext);     // Calculate the transform to position the page     CGAffineTransform pageTransform =            CGPDFPageGetDrawingTransform(                    currentPage,                    kCGPDFMediaBox,                    suggestedPageRect,                    0, true);     CGContextConcatCTM(cgContext, pageTransform);     // Erase a rectangle with a shadow where the page will go     CGContextSaveGState(cgContext);     CGContextSetShadow(cgContext, CGSizeMake(8, 8), 5);     CGContextAddRect(cgContext, mediaBox);     CGContextFillPath(cgContext);     CGContextRestoreGState(cgContext);     // Draw the PDF page     CGContextDrawPDFPage(cgContext, currentPage);     // Draw a frame around the page     CGContextAddRect(cgContext, mediaBox);     CGContextStrokePath(cgContext);     CGContextRestoreGState(cgContext);     CFRelease(currentPage);     currentPage = NULL;            CGContextRotateCTM(cgContext, angleStep);     } } 

The code begins by drawing the gray background using simple line art drawing techniques. It then positions the origin to the center of the drawing area and close to the bottom. This point will become the pivot of the fan.

The sample application stores its PDF in the application's resources. The code retrieves a URL for the file and creates a CGPDFDocumentRef by calling CGPDFDocumentCreateWithURL. The code uses the routine CGPDFDocumentGetNumberOfPages to find out how many pages are in the PDF. The code uses the page count to control the loop that draws the individual pages.

Using the Painter's Model, the loop stacks the pages from the last page to the first one. Inside of the loop, the computer retrieves a page from the document using CGPDFDocumentGetPage. There is a short calculation to determine a "nice" bounding rectangle for the page. The seed of that calculation is the mediabox retrieved by CGPDFPageGetBoxRect.

The drawing routine wants to scale the PDF to fit within the rectangle it just calculated. It uses the routine CGPDFPageGetDrawingTransform to request a transformation to fit the mediaBox to the "nice" rectangle. The CGContextConcatCTM incorporates this transformation into the context.

The short segment of code that follows uses Quartz 2D's drop-shadow capability to draw a gray rectangle with a drop shadow where the computer will draw the PDF page. The CGContextDrawPDFPage routine draws the page's image onto the context, and the drawing ends with a simple frame around the page to emphasize its boundaries.

After drawing the page, the code at the end of the loop simply adjusts the coordinate system for the next page.




Quartz 2D Graphics for Mac OS X Developers
Quartz 2D Graphics for Mac OS X Developers
ISBN: 0321336631
EAN: 2147483647
Year: 2006
Pages: 100

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net