Question

I'm working on a project for OSX where the user can pick a collection of documents (from any application) which I need to generate PDF's from. The standard Macintosh Print dialog has a PDF button which has a number of PDF-related commands including "Save as PDF...". However, I need to generate the PDF file without requiring user interactions. I ideally want this to work with any type of document.

Here's the options I've explored so far:

  • Automator actions. There's a PDF library for Automator but it provides actions for working with PDF files, not generating them. There's a Finder action for printing any file but only to a real printer.
  • AppleScript. Some applications have the ability to generate PDF files (for instance, if you send 'save doc in "test.pdf"' to Pages it will generate a PDF (but this only works for Pages - I need support for any type of document).
  • Custom Printer. I could create a virtual printer driver and then use the automator action but I don't like the idea of confusing the user with an extra printer in the print list.

My hope is that there's some way to interact with the active application as if the user was carrying out the following steps:

  1. Do Cmd-P (opens the print dialog)
  2. Click the "PDF" button
  3. Select "Save as PDF..." (second item in menu)
  4. Type in filename in save dialog
  5. Click "Save"

If that's the best approach (is it?) then the real problem is: how do I send UI Events to an external application (keystrokes, mouse events, menu selections) ?

Update: Just to clarify one point: the documents I need to convert to PDF are documents that are created by other applications. For example, the user might pick a Word document or a Numbers spreadsheet or an OmniGraffle drawing or a Web Page. The common denominator is that each of these documents has an associated application and that application knows how to print it (and OSX knows how to render print output to a PDF file).

So, the samples at Cocoa Dev Central don't help because they're about generating a PDF from my application.

Was it helpful?

Solution

I think you could use applescript to open a document and then use applescript UI scripting to invoke print menu.

For example :

tell application "System Events"
        tell window of process "Safari"
            set foremost to true
            keystroke "p" using {command down}
            delay 3
            click menu button "PDF" of sheet 2
            click menu item "Save as PDF…" of menu 1 of menu button "PDF" of sheet 2
            keystroke "my_test.file"
            keystroke return
            delay 10
        end tell

    end tell

OTHER TIPS

Take a look at a program called CUPS-PDF

It is a virtual printer for OS X which does what the "Save As PDF" method does when print through your normal printer except every print job passed through it results in a pdf output.

Once you install it then you could create shell or AppleScripts using the lp command.

For example, once the virtual printer is setup you could print test.txt and have it automatically save as a pdf. To do this using an AppleScript you would use the following code:

do shell script "lp -d CUPS_PDF test.txt"

The CUPS-PDF app saves all output to /Users/Shared/CUPS-PDF. I am not sure if you can change that path but you could retrieve the file in your script and move it.

There are a few caveats though.

First, the lp command cannot print .doc files. I think there are some other third party apps which will allow you to do this though.

Second, the CUPS-PDF app shows in the Printer pane of System Preferences as having the hyphen in its name but CUPS shows the queue name as having an underscore. So, on the command line you need to refer to the CUPS queue name which is CUPS_PDF with an underscore.

Even if you don't find it very useful to build a script via the lp command and still want to involve GUI scripting then having a virtual printer should save you some steps.

you could use cups like this

    on open afile
        set filename to name of (info for afile)
        tell application "Finder"
            set filepath to (container of (afile as alias)) as alias
        end tell
        set filepath to quoted form of POSIX path of filepath
        set tid to AppleScript's text item delimiters
        set AppleScript's text item delimiters to "."
        set filename to text item 1 of filename
        set AppleScript's text item delimiters to tid
        set afile to quoted form of POSIX path of afile
        do shell script "cupsfilter " & afile & " > " & filepath & filename & ".pdf"
    end open

I have created an alias in bash for this:

convert2pdf() {
  /System/Library/Printers/Libraries/convert -f "$1" -o "$2" -j "application/pdf"
}

I typed up the code below with the assistance of Automator (recording an action, and then dragging the specific action out of the "Watch Me Do" window in order to get the Applescript). If you want to print a PDF from an application other than Safari, you might have to run through the same process and tweak this Applescript around the Print dialogue, since each program might have a different Print GUI.

# Convert the current Safari window to a PDF
# by Sebastain Gallese

# props to the following for helping me get frontmost window
# http://stackoverflow.com/questions/480866/get-the-title-of-the-current-active-window-            document-in-mac-os-x

global window_name

# This script works with Safari, you might have
# to tweak it to work with other applications 
set myApplication to "Safari"

# You can name the PDF whatever you want
# Just make sure to delete it or move it or rename it
# Before running the script again
set myPDFName to "mynewpdfile"

tell application myApplication
    activate
    if the (count of windows) is not 0 then
        set window_name to name of front window
    end if
end tell

set timeoutSeconds to 2.0
set uiScript to "keystroke \"p\" using command down"
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "click menu button \"PDF\" of sheet 1 of window \"" & window_name & "\" of application process \"" & myApplication & "\""
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "click menu item 2 of menu 1 of menu button \"PDF\" of sheet 1 of window \"" & window_name & "\" of application process \"" & myApplication & "\""
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "keystroke \"" & myPDFName & "\""
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "keystroke return"
my doWithTimeout(uiScript, timeoutSeconds)

on doWithTimeout(uiScript, timeoutSeconds)
    set endDate to (current date) + timeoutSeconds
    repeat
        try
            run script "tell application \"System Events\"
" & uiScript & "
end tell"
            exit repeat
        on error errorMessage
            if ((current date) > endDate) then
                error "Can not " & uiScript
            end if
        end try
    end repeat
end doWithTimeout
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top