Internal Hyperlinks within PDF getting removed after processing using PDFKit - swift

I'm using the following code to draw a text over PDF page.Everything works fine, but if you process a PDF with internal links eg: a Book with Content links to pages, the resultant PDF file seems to have the links stripped off.Why does this happen?
let data = NSMutableData()
let consumer = CGDataConsumer(data: data as CFMutableData)!
let context = CGContext(consumer: consumer, mediaBox: nil, nil)!
let pdffile=PDFDocument(url: input)
for y in stride(from: 0, to: pagecount, by: 1)
{
let page: PDFPage = pdffile!.page(at: y)!
var mediaBox = page.bounds(for: PDFDisplayBox.mediaBox)
NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: false)
let bounds = page.bounds(for: PDFDisplayBox.mediaBox)
let size = bounds.size
page.draw(with: .mediaBox, to: context)
text.draw(in:drawrect,withAttributes:textFontAttributes);
context.endPDFPage()
}
context.closePDF()
try! data!.write(to: GetOutputFileName(inputfile: x), options[.atomicWrite])

Links are just kind of annotations, so we need just re-add annotations from original pages to new pages, like
...
}
context.closePDF()
let anotherDocument = PDFDocument(data:data as Data)
for y in stride(from: 0, to: pdffile!.pageCount, by: 1)
{
let page: PDFPage = pdffile!.page(at: y)!
let newPage: PDFPage = anotherDocument!.page(at: y)!
for anotatation in page.annotations {
newPage.addAnnotation(anotatation) // << here !!
}
}
Tested with Xcode 13.4 / macOS 12.5
Test module is here

Related

Swift: Loss of resolution when redrawing PDF

I'm trying to use a PDF file (contains a form with table for several 'records') as a template.
My idea is to read in the original file as a template, and then use Swifts drawing features to insert the text at the relevant positions before saving as a new pdf file and printing.
My problem is that i'm seeing a small loss of resolution (fonts are slightly wooly, gridlines are no longer crisp) when re-saving the output.
I've tried two approaches, the first with my 'template' as a file in the project, and the second with it as an Asset (Scales: Single Scale, Resizing: Preserve Vector Data).
Here is my code:
func createCompletedForm(records: [MyDataObject]) -> URL? {
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = URL(fileURLWithPath: "pdfToPrint", relativeTo: directoryURL).appendingPathExtension("pdf")
guard let templateUrl = Bundle.main.url(forResource: "MyPdfTemplate", withExtension: "pdf") else { return nil }
guard let document = CGPDFDocument(templateUrl as CFURL) else { return nil }
guard let page = document.page(at: 1) else { return nil }
let pageTemplate = page.getBoxRect(.mediaBox)
UIGraphicsBeginPDFContextToFile(fileURL.path, pageTemplate, nil)
guard let pdfContext = UIGraphicsGetCurrentContext() else {
print("Unable to access PDF Context.")
return nil
}
// Mark the beginning of the page.
pdfContext.beginPDFPage(nil)
// Save the context state to restore after we are done drawing the image.
pdfContext.saveGState()
// Change the PDF context to match the UIKit coordinate system.
pdfContext.translateBy(x: 0, y: StandardPageDimensions.ISO216_A4.height)
pdfContext.scaleBy(x: 1, y: -1)
// Option 1: Draw PDF from a file added to my project
DrawingHelper.drawPDFfromCGPDF(page: page, drawingArea: pageTemplate)
// Option 2: Draw PDF from Assets
//let baseTemplate = UIImage(named: "MyPdfTemplate")
//baseTemplate?.draw(at: CGPoint(x: 0, y: 0))
// Draw the records over the template - NOT the source of the problem, happens even when commented out
//addRecordsToTemplate(records: records)
// Restoring the context back to its original state.
pdfContext.restoreGState()
// Mark the end of the current page.
pdfContext.endPDFPage()
UIGraphicsEndPDFContext()
// Useful to find and open the file produced on the simulator
print("pdf created at : \(fileURL.path)")
return fileURL
}
// And the drawing function from my helper class
static func drawPDFfromCGPDF(page: CGPDFPage, drawingArea: CGRect) {
let renderer = UIGraphicsImageRenderer(size: drawingArea.size)
let img = renderer.image { ctx in
UIColor.white.set()
ctx.fill(drawingArea)
ctx.cgContext.translateBy(x: 0.0, y: drawingArea.size.height)
ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
ctx.cgContext.drawPDFPage(page)
}
img.draw(at: CGPoint(x: 0, y: 0))
}

How to Save PDF (and print) from UITableView (or cell) easy way?

I read and search a lot on internet and stack, but I have a problem
I've used this extension
extension UITableView {
// Export pdf from UITableView and save pdf in drectory and return pdf file path
func exportAsPdfFromTable() -> String {
self.showsVerticalScrollIndicator = false
let originalBounds = self.bounds
self.bounds = CGRect(x:originalBounds.origin.x, y: originalBounds.origin.y, width: self.contentSize.width, height: self.contentSize.height)
let pdfPageFrame = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.contentSize.height)
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, pdfPageFrame, nil)
UIGraphicsBeginPDFPageWithInfo(pdfPageFrame, nil)
guard let pdfContext = UIGraphicsGetCurrentContext() else { return "" }
self.layer.render(in: pdfContext)
UIGraphicsEndPDFContext()
self.bounds = originalBounds
// Save pdf data
return self.saveTablePdf(data: pdfData)
}
// Save pdf file in document directory
func saveTablePdf(data: NSMutableData) -> String {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docDirectoryPath = paths[0]
let pdfPath = docDirectoryPath.appendingPathComponent("myPDF.pdf")
if data.write(to: pdfPath, atomically: true) {
return pdfPath.path
} else {
return ""
}
}
}
Then i save the path in this way
myPdfPath = self.tableView.exportAsPdfFromTable()
I tried several ways to show the pdf:
- UIActivityViewController (with this work but not well)
- WKWebView
- SFSafariViewController (only http / https, path file not work)
- UIDocumentInteractionController (not show preview and not work)
The extension used to save the pdf does not work well (not fill print preview page). Generate a 125MB pdf with a title I don't want.
Is there an easy way to do this?
1) TableView (generate pdf on a white background (always even if the tableview is red for example)
2) Print the generated pdf.
Thanks

IMessage MSSticker view created from UIView incorrect sizing

Hey I have been struggling with this for a couple of days now and can't seem to find any documentation out side of the standard grid views for MSStickerView sizes
I am working on an app that creates MSStickerViews dynamically - it does this via converting a UIView into an UIImage saving this to disk then passing the URL to MSSticker before creating the MSStickerView the frame of this is then set to the size of the original view.
The problem I have is that when I drag the MSStickerView into the messages window, the MSStickerView shrinks while being dragged - then when dropped in the messages window, changes to a larger size. I have no idea how to control the size when dragged or the final image size
Heres my code to create an image from a view
extension UIView {
func imageFromView() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
self.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
}
And here's the code to save this to disk
extension UIImage {
func savedPath(name: String) -> URL{
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let filePath = "\(paths[0])/name.png"
let url = URL(fileURLWithPath: filePath)
// Save image.
if let data = self.pngData() {
do {
try data.write(to: url)
} catch let error as NSError {
}
}
return url
}
}
finally here is the code that converts the data path to a Sticker
if let stickerImage = backgroundBox.imageFromView() {
let url = stickerImage.savedPath(name: textBox.text ?? "StickerMCSticker")
if let msSticker = try? MSSticker(contentsOfFileURL: url, localizedDescription: "") {
var newFrame = self.backgroundBox.frame
newFrame.size.width = newFrame.size.width
newFrame.size.height = newFrame.size.height
let stickerView = MSStickerView(frame: newFrame, sticker: msSticker)
self.view.addSubview(stickerView)
print("** sticker frame \(stickerView.frame)")
self.sticker = stickerView
}
}
I wondered first off if there was something I need to do regarding retina sizes, but adding #2x in the file just breaks the image - so am stuck on this - the WWDC sessions seem to show stickers being created from file paths and not altering in size in the transition between drag and drop - any help would be appreciated!
I fixed this issue eventually by getting the frame from the view I was copying's frame then calling sizeToFit()-
init(sticker: MSSticker, size: CGSize) {
let stickerFrame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
self.sticker = MSStickerView(frame: stickerFrame, sticker: sticker)
self.sticker.sizeToFit()
super.init(nibName: nil, bund
as the StickerView was not setting the correct size. Essentially the experience I was seeing was that the sticker size on my view was not accurate with the size of the MSSticker - so the moment the drag was initialized, the real sticker size was implemented (which was different to the frame size / autoLayout I was applying in my view)

Swift: How to implement Markup (Annotations) for PDF like iOS 13's Book app?

I have searched everywhere but can't find an answer. The closest I have been able to find is
func saveAnnotations_forever() {
//let documentAttributes = pdfDocument?.documentAttributes
//let attachmentData = pdfDocument?.dataRepresentation()
let currentPage = pdfView.currentPage
let page_index = pdfDocument?.index(for: currentPage!)
let documentURL = self.pdfDocument?.documentURL// URL to your PDF document.
// Create a `PDFDocument` object using the URL.
let pdfDocument = PDFDocument(url: documentURL!)!
// `page` is of type `PDFPage`.
let page = self.pdfDocument!.page(at: page_index!)!
// Extract the crop box of the PDF. We need this to create an appropriate graphics context.
let bounds = page.bounds(for: .mediaBox)
// Create a `UIGraphicsImageRenderer` to use for drawing an image.
let renderer = UIGraphicsImageRenderer(bounds: bounds, format: UIGraphicsImageRendererFormat.default())
// This method returns an image and takes a block in which you can perform any kind of drawing.
let image = renderer.image { (context) in
// We transform the CTM to match the PDF's coordinate system, but only long enough to draw the page.
context.cgContext.saveGState()
context.cgContext.translateBy(x: 0, y: bounds.height)
context.cgContext.concatenate(CGAffineTransform.init(scaleX: 1, y: -1))
page.draw(with: .mediaBox, to: context.cgContext)
context.cgContext.restoreGState()
and so on to render an annotation and a PDF page together as an image. However, I cannot search through an annotated page's text (since it was rendered as an image) and I cannot erase my previous annotations once saved. iOS's Book's annotations works very well- I want to achieve that, but how?
Thanks in advance.
If you are trying to save an annotation to a PDF, maybe try this code:
// add annotation to the current page
let rect = NSRect(x: 30, y: 30, width: 30, height: 30)
let annotation = PDFAnnotation(bounds: rect, forType: .text, withProperties: nil)
pdfView.currentPage?.addAnnotation(annotation)
// save the PDF to file
if let doc = self.document,
let url = self.document?.documentURL {
doc.write(to: url)
}
You can use the PDFAnnotation documentation to further customize the annotation.

NSPrintOperation—save to location without prompt

I have a GitHub repository that allows the user to save a paginated PDF on Mac from some HTML by loading it into a WebView and using an NSPrintOperation (specifically, an NSPrintSaveJob with showsPrintPanel set to false) lets the user save that PDF to any location on their Mac with a save panel similar to the default NSSavePanel. However, I'm experimenting with the code and I'd like to instead save the created PDF to a particular folder (/Users/owlswipe/Downloads/) without the save panel.
My code to save a PDF from a WebView (with a save panel) is currently this:
let printOpts: [String : AnyObject] = [NSPrintJobDisposition:NSPrintSaveJob as AnyObject]
let printInfo: NSPrintInfo = NSPrintInfo(dictionary: printOpts)
printInfo.paperSize = NSMakeSize(595.22, 841.85)
let printOp: NSPrintOperation = NSPrintOperation(view: webView.mainFrame.frameView.documentView, printInfo: printInfo)
printOp.showsPrintPanel = false
printOp.showsProgressPanel = false
printOp.run()
How can I adapt that code to save the PDF to a preset folder instead of to the user's choice of folders from a save panel?
This piece of code is working for me in Swift 4/Cocoa to accomplish what you want, however there's a bit more code in there as it is rendering the contents of a WKWebView into 8.5" x 11" pages for a PDF.
So for your app, there would be appropriate adjustments for your content stream / print object, but the configuration of the print operation will be the same to get the "no dialog" results you're wanting.
However, to test it out you could just dump your string into the webview and use the code as is. Generated files appear in the root of your "user/documents" directory.
static func createPDF(htmlString: String, streamId: String = "someStream") {
let webView = WebView()
webView.mainFrame.loadHTMLString(htmlString, baseURL: nil)
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURLStr = directoryURL.absoluteString+"\(streamId).pdf"
let outputFilePath = URL(string: directoryURLStr)
let printOpts: [NSPrintInfo.AttributeKey : Any] = [
NSPrintInfo.AttributeKey.jobDisposition : NSPrintInfo.JobDisposition.save,
NSPrintInfo.AttributeKey.jobSavingURL : outputFilePath
]
let printInfo: NSPrintInfo = NSPrintInfo(dictionary: printOpts)
let baseMargin: CGFloat = 9.0; // .125"
printInfo.paperSize = NSMakeSize(612, 792); // 8.5" x 11/2"
printInfo.topMargin = baseMargin
printInfo.leftMargin = baseMargin
printInfo.rightMargin = baseMargin
printInfo.bottomMargin = baseMargin
let printOp: NSPrintOperation = NSPrintOperation(view: webView.mainFrame.frameView.documentView, printInfo: printInfo)
printOp.showsPrintPanel = false
printOp.showsProgressPanel = false
printOp.run()
Swift.print("Document complete: \(outputFilePath!.absoluteString)")
}
}