Creating CFURL/CFStrings in ASObjC & PDF Annotation

Hi all,

I wrote a couple of PDF manipulation handlers in ASObjC to replace the Automator actions that went squirrelly after macOS 12.3 released (python 2 was removed despite these actions still relying on them).

Everything was going smoothly until I tried to figure out how to add text to a PDF using ASObjC. It seems like it should be straightforward, but it’s getting hung up on how to create a CFURL (in Objective-C, these are transparently bridged from NSURL, but AppleScript can’t seem to bridge the value).

From what I understand, I should be able to call a CoreGraphics function from ASObjC…

use scripting additions
use framework "Foundation"
use framework "PDFKit"
use framework "AppKit"
use framework "CoreGraphics"
use framework "CoreFoundation"
use framework "Quartz"

set pdf_path to "/PATH/TO/PDF.pdf"
set file_URL to current application's NSURL's fileURLWithPath:pdf_path
set PDF_document to current application's PDFDocument's alloc()'s initWithURL:file_URL

set temp_out_path to "/PATH/TO/OUT.pdf"
set temp_out_URL to current application's NSURL's fileURLWithPath:temp_out_path

set graphics_context to current application's CGPDFContextCreateWithURL(temp_out_URL, missing value, missing value)
--> Error: CGPDFContextCreateWithURL unable to set argument 0 - the AppleScript value <NSAppleEventDescriptor: 'obj '{ 'form':'ID  ', 'want':'ocid', 'seld':'optr'($8010D50700600000$), 'from':null() }> could not be coerced to type {__CFURL=}.

PDF_document's writeToFile:temp_out_path

Any help is greatly appreciated.

Do you mean “add text to a PDF” as “adding watermark by text”?

If it is, it should be a hard work.
If you mean “add text annotation” may be more easier.

1 Like

That’s right. It can bridge Cocoa objects (and a handful of types), but not Foundation pointers. You can’t create a context like that because (a) you can’t pass a Core Foundation type, and (b) even if you could, you couldn’t retrieve the result.

1 Like

Just a text annotation.

Thanks Shane. I suppose I will make a little Swift executable that the AppleScript can call.

Just in case you don’t already know that PDFKit has a PDFAnnotation class:

1 Like

Adding some text annotation to PDF does not need to use CFURL/CFStrings.

1 Like

Ah, thanks @Piyomaru & @ionah! I’ll give that a try. Looks promising.

I’ve been playing around with this for a few hours, but I really can’t get it to work properly using PDFAnnotation. It’s not helped by the fact that there’s basically no sample code available from Apple.

I’m running into the same issue described here – it doesn’t appear possible to have the text box size itself to the supplied string, and Preview seems to be especially buggy when rendering it (PDF Expert seems fine?).

The text renders at the wrong size (appears to be about 24 pt, despite being set to 12 pt), and it’s cut off if the height value supplied for the annotation’s bounds is too small (though the exact relationship to font size is not clear). Double-clicking the annotation in Preview seems to irreversibly change some of its attributes but fixes the rendering issues.

Before double-clicking in preview (annotation height set to 10):

Before double-clicking in preview (annotation height set to 20):

After double-clicking in Preview (font now correctly rendering at 12 pt, but text box origin moved and text no longer left-aligned):

The rest of my code seems to work OK.

use scripting additions
use framework "Foundation"
use framework "PDFKit"

-- Get the input PDF.
set pdf_path to "/PATH/TO/PDF.pdf"
set file_URL to current application's NSURL's fileURLWithPath:pdf_path
set PDF_document to current application's PDFDocument's alloc()'s initWithURL:file_URL
set PDF_page to (PDF_document's pageAtIndex:0)
set {page_width, page_height} to item 2 of (PDF_page's boundsForBox:(current application's kPDFDisplayBoxMediaBox))

-- Set the output path.
set temp_out_path to "/PATH/TO/OUT.pdf"
set temp_out_URL to current application's NSURL's fileURLWithPath:temp_out_path

-- Create the annotation.
set annotation_position to {10, 10}
set annotation_height to 20
set PDF_annotation to current application's PDFAnnotation's alloc's initWithBounds:({{item 1 of annotation_position, page_height - (item 2 of annotation_position) - annotation_height}, {200, annotation_height}}) forType:(current application's PDFAnnotationSubtypeFreeText) withProperties:(missing value)

-- Set the annotation's properties.
PDF_annotation's setValue:"HELLO" forKey:"contents"
PDF_annotation's setValue:(current application's NSColor's clearColor()) forKey:"color"
PDF_annotation's setValue:0 forKey:"alignment" -- Left aligned.
PDF_annotation's setValue:(current application's NSFont's fontWithName:"Helvetica" |size|:12) forKey:"font"

-- Save the PDF.
PDF_page's addAnnotation:PDF_annotation
PDF_document's writeToFile:temp_out_path

Can anyone with some experience with PDFAnnotation point me in the right direction?

I’m not sure the problem is a PDFKit issue.
Here with Acrobat and Monterey last versions, the annotation behaves normally on double-clic.

Interesting… thanks for confirming.

I’m starting to think that the problem lies with Preview not saving annotations in a standard format. I know they’ve always been a little buggy when opening the file with different PDF software (I’m on macOS 12.6.3, but the issue is hardly a new one).

@tree_frog

I think I may have a solution:

use scripting additions
use framework "Foundation"
use framework "PDFKit"
use framework "AppKit"

-- define original and destination files
set theFile to current application's NSURL's fileURLWithPath:"/Users/zerafio/Desktop/test1.pdf"
set theDest to current application's NSURL's fileURLWithPath:"/Users/zerafio/Desktop/test2.pdf"

-- make new pdf copying the original
set theDoc to current application's PDFDocument's alloc()'s initWithURL:theFile
set thePage to (theDoc's pageAtIndex:0)
set {{pL, pB}, {pW, pH}} to (thePage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))

-- create the annotation
set theAnnot to current application's PDFAnnotation's alloc's initWithBounds:{{pL + 20, pH - 40}, {pW - 40, 20}} forType:(current application's PDFAnnotationSubtypeWidget) withProperties:(missing value)
theAnnot's setWidgetFieldType:(current application's PDFAnnotationWidgetSubtypeText)
theAnnot's setFieldName:"textField"
theAnnot's setWidgetStringValue:"Mon paletot aussi devenait idéal"
theAnnot's setAlignment:0
theAnnot's setFont:(current application's NSFont's fontWithName:"Helvetica" |size|:12)
theAnnot's setBackgroundColor:(current application's NSColor's clearColor())
thePage's addAnnotation:theAnnot

-- make a new file
theDoc's writeToURL:theDest

-- open the new file in Preview
set theWorkspace to current application's NSWorkspace's sharedWorkspace()
set theConfig to current application's NSWorkspaceOpenConfiguration's new()
theConfig's setActivates:true
set appURL to (theWorkspace's URLForApplicationWithBundleIdentifier:"com.apple.Preview")
(theWorkspace's openURLs:{theDest} withApplicationAtURL:appURL configuration:theConfig completionHandler:(missing value))
1 Like

Thanks Jonas! That works really well.

Jonas’s code above creates an editable text field, and Preview plays nicely with it on macOS 12.6.3.

@ionah Thank you, the script works very well
wondering if it cud be modified to replace existing text in a selected field/ annotation
or to get the bounds of a selected Annotation in a PDF
Cheers

Not easily. The code above operates directly on files, and is not controlling an application (so there’s no concept of “selected” in that case).

Unfortunately, Preview.app is not scriptable, so if that’s the application you’re using then you’re out of luck. If you’re using a different application, then that may be scriptable & you may be able to script it to pull the identifier of the selected annotation.

Thank you @tree_frog, I get it