Draw on a PDF Using Swift on MACos

Draw on a PDF using Swift on macOS

You can create a PDF graphics context on macOS and draw a PDFPage into it. Then you can draw more objects into the context using either Core Graphics or AppKit graphics.

Here's a test PDF I created by printing your question:
input PDF

And here's the result from drawing that page into a PDF context, then drawing more text on top of it:

output PDF

Here's the code I wrote to transform the first PDF into the second PDF:

import Cocoa
import Quartz

let inUrl: URL = URL(fileURLWithPath: "/Users/mayoff/Desktop/test.pdf")
let outUrl: CFURL = URL(fileURLWithPath: "/Users/mayoff/Desktop/testout.pdf") as CFURL

let doc: PDFDocument = PDFDocument(url: inUrl)!
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = page.bounds(for: .mediaBox)

let gc = CGContext(outUrl, mediaBox: &mediaBox, nil)!
let nsgc = NSGraphicsContext(cgContext: gc, flipped: false)
NSGraphicsContext.current = nsgc
gc.beginPDFPage(nil); do {
page.draw(with: .mediaBox, to: gc)

let style = NSMutableParagraphStyle()
style.alignment = .center

let richText = NSAttributedString(string: "Hello, world!", attributes: [
NSFontAttributeName: NSFont.systemFont(ofSize: 64),
NSForegroundColorAttributeName: NSColor.red,
NSParagraphStyleAttributeName: style
])

let richTextBounds = richText.size()
let point = CGPoint(x: mediaBox.midX - richTextBounds.width / 2, y: mediaBox.midY - richTextBounds.height / 2)
gc.saveGState(); do {
gc.translateBy(x: point.x, y: point.y)
gc.rotate(by: .pi / 5)
richText.draw(at: .zero)
}; gc.restoreGState()

}; gc.endPDFPage()
NSGraphicsContext.current = nil
gc.closePDF()

How do I draw something on a PDF in Swift?

So I've solved my own problem. For anyone else stuck like me, here is what I did. For example to draw a box on a page:

  1. create a CGContext (ctx) for PDFDocuments. You can do this either with data or with a URL you want to write to.
  2. create a CGPDFDocument with the document you want to edit
  3. get the CGPage of the CGPDF you want to edit (cgPage)

and:

ctx?.beginPDFPage(nil)
ctx?.drawPDFPage(cgPage)
ctx?.beginPath()
let path = CGPath(roundedRect: box as CGRect, cornerWidth: 5, cornerHeight: 5, transform: nil)
ctx?.setStrokeColor(CGColor.black)
ctx?.setFillColor(color.cgColor)
ctx?.setLineWidth(10.0)
ctx?.addPath(path)
ctx?.strokePath()
ctx?.addPath(path)
ctx?.fillPath()
ctx?.endPDFPage()
ctx?.closePDF()

(If you created the context with a URL, close will write to disk. Otherwise you'll have to do something with the PDF data.)

Draw text on all pages of PDF using PDFKit

The main issue here is that context recreated, for multiple pages we should write into the same context (it manages pages by beginPDFPage/endPDFPage pair).

Here is fixed code. Tested with Xcode 13.4 / macOS 12.4

let pdffile = PDFDocument(url: input)
let data = NSMutableData()
let consumer = CGDataConsumer(data: data as CFMutableData)!

// create common context with no mediaBox, we will add it later
// per-page (because, actually they might be different)
let context = CGContext(consumer: consumer, mediaBox: nil, nil)!

for y in stride(from: 0, to: pdffile!.pageCount, by: 1)
{
let page: PDFPage = pdffile!.page(at: y)!

// re-use media box of original document as-is w/o changes !!
var mediaBox = page.bounds(for: PDFDisplayBox.mediaBox)
NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: false)

// prepare mediaBox data for page setup
let rectData = NSData(bytes: &mediaBox, length: MemoryLayout.size(ofValue: mediaBox))

context.beginPDFPage([kCGPDFContextMediaBox as String: rectData] as CFDictionary) // << here !!

page.draw(with: .mediaBox, to: context) // << original !!
text.draw(in:drawrect,withAttributes:textFontAttributes) // << over !!

context.endPDFPage()
}
context.closePDF() // close entire document

let anotherDocument = PDFDocument(data:data as Data)
// ... as used before

Swift iOS - Overlay text onto PDF with PDFKit and UI

Here is possible solution - actually you just need to operate with CoreGraphics context directly, set current, flip transform, etc. (style and conventions of original code preserved).

Tested with Xcode 12 / iOS 14.

demo

func executeContext(at srcURL: URL, to dstURL: URL) {

// Confirm there is a document there
if let doc: PDFDocument = PDFDocument(url: srcURL) {

// Create a document, get the first page, and set the size of the page
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = page.bounds(for: .mediaBox)

// This is where the magic happens. Create the drawing context on the PDF
let context = CGContext(dstURL as CFURL, mediaBox: &mediaBox, nil)

UIGraphicsPushContext(context!)
context!.beginPDFPage(nil)

// Draws the PDF into the context
page.draw(with: .mediaBox, to: context!)

let flipVertical: CGAffineTransform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: mediaBox.size.height)
context!.concatenate(flipVertical)

let attributes = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 72)
]
let text = "I'm a PDF!"
text.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)

context!.endPDFPage()
context?.closePDF()

UIGraphicsPopContext()
}
}

Saving PDF Files with Swift in iOS and display them

Since several people requested this, here is the equivalent to the first answer in Swift:

//The URL to Save
let yourURL = NSURL(string: "http://somewebsite.com/somefile.pdf")
//Create a URL request
let urlRequest = NSURLRequest(URL: yourURL!)
//get the data
let theData = NSURLConnection.sendSynchronousRequest(urlRequest, returningResponse: nil, error: nil)

//Get the local docs directory and append your local filename.
var docURL = (NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)).last as? NSURL

docURL = docURL?.URLByAppendingPathComponent( "myFileName.pdf")

//Lastly, write your file to the disk.
theData?.writeToURL(docURL!, atomically: true)

Also, since this code uses a synchronous network request, I highly recommend dispatching it to a background queue:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
//The URL to Save
let yourURL = NSURL(string: "http://somewebsite.com/somefile.pdf")
//Create a URL request
let urlRequest = NSURLRequest(URL: yourURL!)
//get the data
let theData = NSURLConnection.sendSynchronousRequest(urlRequest, returningResponse: nil, error: nil)

//Get the local docs directory and append your local filename.
var docURL = (NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)).last as? NSURL

docURL = docURL?.URLByAppendingPathComponent( "myFileName.pdf")

//Lastly, write your file to the disk.
theData?.writeToURL(docURL!, atomically: true)
})

And the answer to second question in Swift:

//Getting a list of the docs directory
let docURL = (NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last) as? NSURL

//put the contents in an array.
var contents = (NSFileManager.defaultManager().contentsOfDirectoryAtURL(docURL!, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.SkipsHiddenFiles, error: nil))
//print the file listing to the console
println(contents)

_x0013_



Related Topics



Leave a reply



Submit