Adding a hyperlink to a text string in existing PDF?

I have a PDF with a unique text string in it. I would like to scriptomatically make this arbitrary but unique text string into a hyperlink, and then resave the PDF with this string’s hyperlink active and clickable. The text string’s format does not necessarily need to change to show that it’s a hyperlink.

Does anyone know of an Applescriptable app which can do this other than an Adobe product? Thanks for any help!

Assuming you’re running 10.9 or later, you could always do it without any app:

use scripting additions
use framework "Foundation"
use framework "Quartz"

on makeLinksInPDF:posixPath forString:searchString linkURL:linkURLString
	-- make URL from path
	set theURL to current application's class "NSURL"'s fileURLWithPath:posixPath
	-- make PDF
	set thePDF to current application's PDFDocument's alloc()'s initWithURL:theURL
	-- get list of matches as PDFSelections
	set theSels to (thePDF's findString:searchString withOptions:0) -- find matches as PDFSelections
	repeat with aSel in theSels
		set thePage to (aSel's pages()'s objectAtIndex:0) -- get the page it's on
		set theBounds to (aSel's boundsForPage:thePage) -- get its bounds
		set theLink to (current application's PDFAnnotationLink's alloc()'s initWithBounds:theBounds) -- make link with those bounds
		set theAction to (current application's PDFActionURL's alloc()'s initWithURL:(current application's class "NSURL"'s URLWithString:linkURLString)) -- action to perform
		(theLink's setMouseUpAction:theAction) -- set link's action
		-- set link's appearance
		(theLink's setColor:(current application's NSColor's blueColor()))
		set linkBorder to current application's PDFBorder's alloc()'s init()
		(linkBorder's setLineWidth:1.0)
		(linkBorder's setStyle:0)
		(theLink's setBorder:(linkBorder))
		(theLink's setShouldDisplay:true)
		-- add it to the page
		(thePage's addAnnotation:theLink)
	end repeat
	-- save the modified PDF
	set oldName to theURL's lastPathComponent()'s stringByDeletingPathExtension()
	set newURL to (theURL's URLByDeletingLastPathComponent()'s URLByAppendingPathComponent:(oldName's stringByAppendingString:"-new"))'s URLByAppendingPathExtension:"pdf"
	thePDF's writeToURL:newURL
end makeLinksInPDF:forString:linkURL:

my makeLinksInPDF:"/Users/shane/Desktop/Some.pdf" forString:"whatever" linkURL:"http://www.macosxautomation.com/applescript/apps/"

Hi Shane!
I’ve tried your code even if I still don’t get anything at that “new” (to me) scripting language.
I’ve put a PDF link of mine in lieu of yours, put a string of mine as well and also put another link.

The script stops at:
set theAction to (current application’s PDFActionURL’s alloc()'s initWithURL:(current application’s nsurl’s URLWithString:linkURLString)) – action to perform

The error returned is:
nsurl doesn’t understand the message URLWithString_

URLWithString_(“http://www.editionsjesuites.com”)
the AppleEvent was not handled by any handler (errAEEventNotHandled:-1708)

I’ve probably missed sth, haven’t I?

I suspect the problem is that you have a scripting addition loaded that unfortunately defines the term NSURL. So change this:

		set theAction to (current application's PDFActionURL's alloc()'s initWithURL:(current application's NSURL's URLWithString:linkURLString)) -- action to perform


to:

		set theAction to (current application's PDFActionURL's alloc()'s initWithURL:(current application's class "NSURL"'s URLWithString:linkURLString)) -- action to perform


I’ll edit the original.

This will be wonderful if I can get it to work, Shane! I’m getting an error though:

 [i]Can't get framework \"Foundation\" of «script». Access not allowed." number -1723 from framework "Foundation"[/i]

I’m on 10.9.5 and just downloaded (updated) XCode. Do you know what I’m doing wrong? Thanks!

Under 10.9 you need to save it as a script librariy. There’s a full tutorial here:

macscripter.net/viewtopic.php?id=41638

Basically, you save it as .scptd file, click the Bundle Contents button in Script Editor’s toolbar and click the checkbox AppleScript/Objective-C library, save again, then move to ~/Library/Script Libraries (you have to create this folder). Then you call it like this:

use theLib: script "<name of script lib>"
use scripting additions 

tell theLib to makeLinksInPDF:"/Users/shane/Desktop/Some.pdf" forString:"whatever" linkURL:"http://www.macosxautomation.com/applescript/apps/"

Unbelievable! It works perfectly – thank you so much!!

Now a new but related question: Is it possible to make the searchString an Applescript list of words and match them with a list of linkURLstrings? Honestly I’m not well versed in this new ASObj-C stuff yet to see how it could be done most easily. I tried simply using lists for searchString and linkURLstring in the existing code but it errors. Thanks for any insight or pointers.

It’s a simple AppleScript exercise. You would need to pass the two lists, then loop through them doing a search for each one. Something like:

use scripting additions
use framework "Foundation"
use framework "Quartz"

on makeLinksInPDF:posixPath forStrings:listOfSearchStrings linkURLs:listOfLinkURLStrings
	-- make URL from path
	set theURL to current application's class "NSURL"'s fileURLWithPath:posixPath
	-- make PDF
	set thePDF to current application's PDFDocument's alloc()'s initWithURL:theURL
	repeat with i from 1 to count of listOfSearchStrings
		set searchString to item i of listOfSearchStrings
		set linkURLString to item i of listOfLinkURLStrings
		-- get list of matches as PDFSelections
		set theSels to (thePDF's findString:searchString withOptions:0) -- find matches as PDFSelections
		repeat with aSel in theSels
			set thePage to (aSel's pages()'s objectAtIndex:0) -- get the page it's on
			set theBounds to (aSel's boundsForPage:thePage) -- get its bounds
			set theLink to (current application's PDFAnnotationLink's alloc()'s initWithBounds:theBounds) -- make link with those bounds
			set theAction to (current application's PDFActionURL's alloc()'s initWithURL:(current application's class "NSURL"'s URLWithString:linkURLString)) -- action to perform
			(theLink's setMouseUpAction:theAction) -- set link's action
			-- set link's appearance
			(theLink's setColor:(current application's NSColor's blueColor()))
			set linkBorder to current application's PDFBorder's alloc()'s init()
			(linkBorder's setLineWidth:1.0)
			(linkBorder's setStyle:0)
			(theLink's setBorder:(linkBorder))
			(theLink's setShouldDisplay:true)
			-- add it to the page
			(thePage's addAnnotation:theLink)
		end repeat
	end repeat
	-- save the modified PDF
	set oldName to theURL's lastPathComponent()'s stringByDeletingPathExtension()
	set newURL to (theURL's URLByDeletingLastPathComponent()'s URLByAppendingPathComponent:(oldName's stringByAppendingString:"-new"))'s URLByAppendingPathExtension:"pdf"
	thePDF's writeToURL:newURL
end makeLinksInPDF:forStrings:linkURLs:

Edited for fix in next message

It works, thanks Shane, FANTASTIC!

Notes:

  • I had to change this line:

    set theURL to class “NSURL”'s fileURLWithPath:posixPath

to this:

 	set theURL to current application's class "NSURL"'s fileURLWithPath:posixPath
  • Also, spaces in fileURLWithPath:posixPath don’t seem to be a problem.

set theURL to class “NSURL”'s fileURLWithPath:posixPath

Thanks; I’ve edited it above.

Right. Quoting of paths is needed for shell scripting, but not elsewhere.

I must say this is pure magic to me!
I’m clearly not at ease with this language dealing with foreign syntax, and it’s frustrating to me!
How to know what framework to use, what dictionary it uses, when to use pure AS language and when to use ASOC?
What applications are scriptable that way?
It seems to be much more efficient but yet requires new learnings. :confused:

I tried the same exercise, a year or two ago now, but using Acrobat Javascript instead.
It was nice to get this example working.
Thanks Shane! :slight_smile:

It’s prompted me to dig a little deeper regarding dictionaries etc.

Well the syntax is still that of AppleScript. But it’s using it to call Objective-C.

All good questions, with no simple answers, except maybe the last. There’s no need to use ASObjC unless either there’s no other way, or it’s more efficient.

None. In many cases you use it to bypass applications.

Yes, but that’s not necessarily a bad thing.

Some things, like this example, take a fair bit of effort or knowledge. But in a lot of cases it’s reasonably straight-forward, once you understand the use of classes and methods. You write it with the documentation open, just as the best way to write scripts is with the relevant dictionaries open.

Excuse the shameless plug, but I reckon my book gives a pretty good introduction.

I definitely trust you!
The point is that I write scripts to simplify my workflow or enhance some features in some applications. I’m basicaly a designer. Scripting is a plus to me and the question is like always: is it worth learning this to do that?
In this particular case of modifying a PDF with no particular application open, I’m dreaming! Even if I know -and make use of it- AS already allows us modifying text files or other stuff the same applicationless way.

I suspect that’s a question most of us face (and not just about scripting). But picking up snippets here and there – which is how a lot of us got into scripting – is not too time-consuming.

Yes, but with limitations. ASObjC opens up the use of regular expressions, for example, and the ability to manipulate styled text, or process XML files. If you don’t need them, fine – but it’s good to know that solutions are available.

I’d surely need all of that sooner or later actually. There is also the question of “free” time which is getting shorter and shorter at my end. I’ll keep that in mind anyway and certainly be happy to dig into it. Thanks for all!

Hi Shane:

Your script is still working great. Another related question if you don’t mind: how could the target URLs be changed so that the link opens another PDF or JPG file on a local disk? Is it even possible? I tried various versions of “file:///” to no avail.

Also wondering if a link could jump to a page in the same PDF (so that if a PDF’s Table of Contents were not actually links to its chapters it could be “retro-fitted” to jump to them)?

Thanks for any insight or advice on this!

This is certainly possible, as Acrobat Pro allows you to insert hyperlinks to open local files into PDF’s.

You add these links through a GUI, so I’m not 100% sure when I view this sort of link that it’s showing me the raw syntax you would use to insert a link like this.

But in case it is, it just shows the HFS path proceeded by “File:”

I suspect the “file:///” syntax is for Windows.

  • Tom.

You need to create a file NSURL. So this line:

           set theAction to (current application's PDFActionURL's alloc()'s initWithURL:(current application's class "NSURL"'s URLWithString:linkURLString)) -- action to perform

would become:

           set theAction to (current application's PDFActionURL's alloc()'s initWithURL:(current application's class "NSURL"'s fileURLWithPath:linkURLString)) -- action to perform

That assumes that linkURLString is a POSIX path.