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 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
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/"
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:
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.
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!
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)?
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:”
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