Skip to main content

Creating PDFs with Core Text and Quartz

·5 mins

I’m working on a project where I need to create PDF files for long documents. When I searched online for information on Core Text and creating PDF files, I didn’t find much so I’m sharing what I’ve learned. I have not tried creating PDFs on iOS, but most of this material should apply to iOS as Core Text and Quartz are available on iOS.

One word of warning about this article: the code samples are for illustration only. If you copy and paste them into Xcode, they probably will not work. If you are looking for code to paste into your projects, stop reading now. This article shows the functions you need to call, but you have to write the code.

The code samples are written in Swift 3. If you are using Swift 2, you’ll get syntax errors. Swift 3 is in a state of flux so you might get errors in Swift 3 too. Xcode’s code completion should show you the proper function names and arguments. Pay attention to the data structures you need to know and the functions you need to call. They’re the most important material in this article.

Frameworks #

Core Text and Quartz are not part of the Cocoa framework so you have to import them in your code.

import CoreText
import Quartz

Data Structures #

To create PDF files with Core Text and Quartz, you need to know the following data structures: CGContext, CTFramesetter, and CTFrame. CGContext is a Quartz context (Quartz is also called Core Graphics in Apple’s documentation. That’s where the CG comes from) where you draw the PDF content.

CTFramesetter is a Core Text framesetter. You will have at least one framesetter for each page in your PDF. The framesetter’s job is to create a frame. CTFrame is a Core Text frame. You will draw one or more frames for each page. You need one frame for the main page content. If you want to draw any content in a page header or footer, you must create additional frames. A common reason to create an additional frame is to draw page numbers.

Coding Tasks #

Creating a PDF file involves the following tasks:

  1. Create a URL for the PDF file.
  2. Create a context to draw the PDF content.
  3. Create a framesetter.
  4. Create a frame.
  5. Draw the frame in the context.
  6. Repeat Steps 3-5 until you run out of text to draw.
  7. Save the PDF file.

Create the URL #

Every PDF you create requires a URL for the PDF file. In Mac applications you normally get the URL by opening a Save panel. You can also use the NSURL class to manually create a URL.

Create the Context #

Call the function CGPDFContextCreateWithURL() to create a context for drawing PDF content. Fortunately in Swift all you have to do is create a CGContext object. The function to create a CGContext takes three arguments. The first argument is the URL for the PDF file. The second argument is a media box, which is a rectangle that specifies the PDF bounds. If you pass nil, Quartz assumes the page size is the standard paper size, 8.5 by 11 inches. The final argument is an optional dictionary that specifies additional information for the PDF context to use when creating the PDF file. Examples of the additional information include the title and author of the document.

let pdfContext = CGContext(fileURL, mediaBox: nil, nil)

If you use a custom page rectangle in Swift, you must make the rectangle variable a var instead of a let and pass the address of the variable using the & character like you would do in C or Objective-C. The following code uses a 6 by 9 inch page size:

var pageBox = CGRect(x: 0.0, y: 0.0, width: 432.0, height: 648.0)
let pdfContext = CGContext(fileURL, mediaBox: &pageBox, nil)

Quartz uses points as its unit of measurement. There are 72 points per inch.

Create the Framesetter #

Call the function CTFramesetterCreateWithAttributedString() to create a framesetter. Supply an attributed string containing the text for the PDF.

let framesetter = CTFramesetterCreateWithAttributedString(text)

Create the Frame #

Core Text has a handy function to determine how much text can fit in a page frame: CTFramesetterSuggestFrameSizeWithConstraints(). This function takes five arguments. The first argument is the framesetter you created. The second argument is the range of the text being laid out. This range is initially the range of the document text. As you draw pages of text, update the range’s starting location so you don’t draw the same content more than once.

The third argument is an optional dictionary of attributes. The fourth argument is the page size. Remember to factor in page margins when calculating the page size. The final argument is the range of text that fits on the page. CTFramesetterSuggestFrameSizeWithConstraints() returns its result in the final argument.

var pageRange = CFRange()
CTFramesetterSuggestFrameSizeWithConstraints(framesetter, textRange, nil, pageSize, &pageRange)

Call the function CTFramesetterCreateFrame() to create the frame. This function takes four arguments. The first argument is the framesetter. The second argument is the range that CTFramesetterSuggestFrameSizeWithConstraints() returns. The third argument is a path for drawing. You can create a CGPath from a rectangle and a nil transform. The final argument is another optional dictionary of attributes. The following code creates a frame for a standard 8.5 by 11 inch page with one inch margins:

let pageRect = CGRect(x: 72.0, y: 72.0, width: 468.0, height: 648.0)
let framePath = CGPath(rect: pageRect, transform: nil)
let frame = CTFramesetterCreateFrame(framesetter, pageRange, framePath, nil)

Draw the Page #

Now you can draw the page. Call the function beginPDFPage() to begin drawing in a PDF page. Call the function CTFrameDraw() to draw a frame. Supply the frame and the context. Call the function endPDFPage() to finish drawing.

pdfContext?.beginPDFPage(nil)
CTFrameDraw(frame, pdfContext?)
pdfContext?.endPDFPage()

If you have multiple frames to draw in a page, add more CTFrameDraw() calls. Make sure the calls are between the calls to beginPDFPage() and endPDFPage().

Save the PDF #

At this point you have created a PDF. Now you have to save it. Close the PDF context to save the file by calling the function closePDF().

pdfContext?.closePDF()

Additional Reading #

To learn more about Quartz and Core Text, read Quartz 2D Programming Guide and Core Text Programming Guide. Both documents are part of Apple’s documentation. Quartz 2D Programming Guide will show you how to draw things besides text with Quartz.