How to Draw Text in PDF Context in Swift

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

Drawing Text to a CGContext for Quartz PDF not working

Closing an open question that I got worked out (complete code).

Swift 5.4 on macOS

import Cocoa
import CoreText
import Quartz

var pageWidth: CGFloat = 72*8.5
var pageHeight: CGFloat = 72*11.0
var pageRect: CGRect = CGRect(x:0, y:0, width: pageWidth, height: pageHeight)

extension NSImage {
/*
Converts an NSImage to a CGImage for rendering in a CGContext
Credit - Xue Yu
- https://gist.github.com/KrisYu/83d7d97cae35a0b10fd238e5c86d288f
*/
var toCGImage: CGImage {
var imageRect = NSRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
guard let image = cgImage(forProposedRect: &imageRect, context: nil, hints: nil) else {
abort()
}
return image
}
}

class PDFText {

/*
Create a non-nil empty CGContext
Credit - hmali - 3/15/2019
https://stackoverflow.com/questions/41100895/empty-cgcontext
*/
var pdfContext = CGContext(data: nil,
width: 0,
height: 0,
bitsPerComponent: 1,
bytesPerRow: 1,
space: CGColorSpace.init(name: CGColorSpace.sRGB)!,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)

// Set a rectangle to be in the center of the page
let textRect = CGRect(x: pageRect.midX-50, y: pageRect.midY-50, width: 100, height: 100)

func createPDF() {
let filePath = "/Users/Shared/Text.pdf"
let fileURL = NSURL(fileURLWithPath: filePath)
pdfContext = CGContext(fileURL, mediaBox: &pageRect, nil)
// This must be called to begin a page in a PDF document
pdfContext!.beginPDFPage(nil)
drawBackground()
drawText(text: "This is page 1")
// This has to be called prior to writing another page to the PDF document
pdfContext!.endPDFPage()
pdfContext!.beginPDFPage(nil)
drawBackground()
drawText(text: "This is page 2")
// Call this or before closing the document.
pdfContext!.endPDFPage()
pdfContext!.closePDF()
}

func drawBackground() {
// Draws an image into the graphics context.
// NOTE: If the image is not sized for the specified rectangle it will be
// scaled (up/down) automatically to fit within the rectangle.
let cgImage = NSImage(contentsOfFile: "/Users/Shared/background.png")?.toCGImage
pdfContext?.draw(cgImage!, in: pageRect)
}

func drawText(text:String) {
// Credit: Nutchaphon Rewik, https://github.com/nRewik/SimplePDF

// Create a paragraph style to be used with the atributed string
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
// Set up the sttributes to be applied to the attributed text
let stringAttributes = [NSAttributedString.Key.font: NSFont(name: "Helvetica", size: 16.0),
NSAttributedString.Key.foregroundColor: NSColor.purple,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.paragraphStyle: paragraphStyle]
// Create the attributed string
let attributedString = NSAttributedString(string: text, attributes: stringAttributes as [NSAttributedString.Key : Any])
// Set up a CoreText frame that encloses the attributed string
let frameSetter = CTFramesetterCreateWithAttributedString(attributedString)
// Get the frame size for the attributed string
let frameSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, attributedString.string.count), nil, textRect.size, nil)
// Save the Graphics state of the context
pdfContext!.saveGState()
// Put the text matrix into a known state. This ensures that no old scaling
// factors are left in place.
pdfContext!.textMatrix = CGAffineTransform.identity
// Create a path object to enclose the text.
let framePath = CGPath(rect: CGRect(x: textRect.minX, y: textRect.midY-frameSize.height/2, width: textRect.width, height: frameSize.height), transform: nil)
// Get the frame that will do the rendering. The currentRange variable specifies
// only the starting point. The framesetter lays out as much text as will fit into
// the frame or until it runs out of text.
let frameRef = CTFramesetterCreateFrame(frameSetter, CFRange(location: 0, length: 0), framePath, nil)
// Draw the CoreText frame (that includes the text) into the graphics context.
CTFrameDraw(frameRef, pdfContext!)
// Restore the previous Graphics state.
pdfContext?.restoreGState()
}
}

let pdf = PDFText()

pdf.createPDF()

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()
}
}


Related Topics



Leave a reply



Submit