Best Of Two Worlds - Acrobat PDF Scripting Using VisualBasic (VBA) & JavaScript

Submitted by Hannes Schmidt on Thu, 07/29/2004 - 11:30.

The other day I was asked by a client of mine to create a convenient macro for adding watermarks or letterheads to Word documents. The first thought that came to my mind was putting a graphics object (the letterhead) into the header or footer of the document. This is exactly what Word does automatically, when the user clicks the Format | Background menu item. Sounds simple. There are only two subtleties. For one, the graphics object should be behind the text, such that it doesn't get in the way of other header or page content. The other subtlety is more intrusive: let's assume that you want the letterhead only on the first page. You can put it into the body text of the first page or you can change the section format to have different headers and footers on the first and subsequent pages. If you want the letterhead on all but the first page you can only use the second option. If you get the document from another source, like a customer or a supplier, the chances are that your document already has differing headers or footers for the pages in a section or that the document already has multiple sections and so on. Too many ifs for a reliable solution, don't you think?

Then I discovered that Adobe Acrobat 6.0 has a neat feature that can be used to put one PDF document on top of or behind the pages of another PDF document. The menu item is Document | Add Watermark&Background. I tried it on a few documents and it worked very well. Problem soved. The only thig left to do was instructing my users to convert their Word documents to PDF and then use Acrobat to add the letterhead PDF. Unfortunately, my users aren't very computer literate. They do not understand how things work; instead they memorize the way they are achieved. It's like with cars, hardly anyone knows these days how a car engine works, but everyone knows how to operate one. Anyway, for someone whose brain works that way, the instructions can't just be "Convert to PDF, merge letterhead PDF using Add Background&Watermark and print". As I didn't want to write another two pages of instructions for dummies, I thought I could stll write a Word macro that automates the task as much as possible.

A VisualBasic for Applications (VBA) program that scripts Adobe Acrobat? Impossible, I thought. But after a few hours of online research (Google has gotten better in the past months), I came up with a rough idea how this could be achieved. From now on, it's getting pretty darn technical.

First of all, there is Adobe's Interapplication Communication for Acrobat (IAC). The reference is available on Adobe's ASN pages (see Resources section at the end of this document) provided that you register for a free ASN web account (only a valid email address needed). On Windows, the Adobe lingo term 'interapplication' means OLE and DDE, on MacOS it stands for AppleScript. OLE objects can be scripted in VBA. There are three revisions of the IAC, each corresponding to one of Acrobat's major versions: 4, 5 and 6. Unfortunately, neither revision provides access to the watermark/background functionality. Well, no access other than executing a menu item, effectively simulating a mouse click:

Dim app As Object
Set app = CreateObject("AcroExch.App")
app.MenuItemExecute( "COMP:AddBack" )

The method MenuItemExecute is documented in the IAC reference; wheras the menu item id is documented in the Acrobat Core API Reference. The above code opens the dialog box that is normally shown after clicking the Document | Add Watermark&Background menu item. There must be a better way.

On his ByteRyte site, Matthew Fitzgerald explains how to use Acrobat's JavaScript API to overlay two PDF pages from different documents. The Acrobat JavaScript Scripting reference is also available freely. Matthew's technique employs template pages - a feature that is only accessible through JavaScript, not through IAC. As briefly mentioned on PlanetPDF, Adobe introduced a somewhat ominous Javascript/IAC bridge with version 6 of Acrobat. Unfortunately, the document that describes how to use this bridge is not publicly available (Programming Acrobat JavaScript Using Visual Basic). I didn't want to pay € 185,- for a 14 page document. After some more searching, I found the key piece of information in the Acrobat Knowledge Base (also on Adobe's ASN partner site). One KB article contained sample VB source code listing a method not documented in the IAC reference: PDDoc.GetJSObject. It didn't take a rocket scientist to figure out what this JSObject was. It's basically a collection of the top-level objects found in Acrobat's JavaScript API (excuse my poor terminology; I'm not a JavaScript person). At the other end of the world, some Python programmer mentions JSObject in a mailing list post, saying that it returns Variants and accepts almost every elementary argument type. From that I concluded that the VB/JS bridge should be invoked as follows:

Set app = CreateObject("AcroExch.App")
Set avDoc = app.GetActiveDoc ' get the logical doc Set pdDoc = avDoc.GetPDDoc ' get the physical doc Set jso = pdDoc.GetJSObject ' get the bridge docs = jso.app.activeDocs ' get array of active docs, ' app is the JS handle to Acrobat's Application top level object For Each doc In docs ' iterate docs ... Next

And it worked. At that point, I had everything I needed. The necessary steps are:

  1. Convert the Word document to PDF by printing it to the "Adobe PDF" printer.
  2. Wait until Acrobat opens the distilled PDF.
  3. Obtain the JSObject from the distilled PDF.
  4. Add the first page of another PDF - the background PDF - to the distilled PDF.
  5. Turn the added page into a template page.
  6. Instantiate ("spawn" in Adobe lingo) the template page on some or all of the distilled PDF's pages, effectively merging the template's content into the normal pages' contents.
  7. Remove the template page from the distilled PDF.
  8. Have a glas of milk - for the calcium and vitamins A and D.

Step 4 was tricky because there is no GetActiveDoc() method in the JavaScript API, at least I didn't find one. The functionally similar JS property App.activeDocs returns an array of all open PDF documents. In order to get a JS handle to the distilled PDF I had to iterate this array and compare the name of each document to the name of the document obtained through GetActiveDoc(). This is in fact not very reliable and if you know a better way, drop me a line or leave a comment.

Source Code

Available under GPL here.

Copyright (C) 2004  Hannes Schmidt

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Online Resources

ByteRyte | Using JavaScript to Apply Templates To PDF File is Matthew Fitzgerald's article on using page templates for PDF backgrounds and effects.

Adobe | Adobe PDF - Acrobat 6.0 SDK Documentation lists available SDK documents. Some can be accessed by getting a free ASN web account. For others an ASN developer membership is required for which you have to pay. The documents mentioned in this article are:

Adobe | How To: Call Menuitem Using Javascript/Visual Basic Interface VB sample code with JSObject and a method call on it. Amusing: the workhorse method call is commented out.

PlanetPDF | Developing with Inter-Application Communication (IAC) mentions the VB/Javascript bridge.

Python-Win32 mailing list | COM, Acrobat and JavaScript by Joshua Reynolds says something about JSObject's return and argument types.

Submitted by Anonymous on Mon, 12/06/2010 - 01:29.
Does AcroExch.PDDoc class is available to Adobe Reader??., i.e, can i make use of the methods under this class to work only when Adobe Reader is insatlled in my machine.
Submitted by Anonymous on Thu, 07/31/2008 - 00:59.
Your code is so useful! It's almost impossible to watermark a PDF from VBA because Acrobat is so paranoid about security. Also I have Acrobat 6 Pro, which doesn't include the AddWatermarkFromFile method. But one problem: I want to watermark a huge PDF--over 4,000 pages--and when I use this code it bloats to five times its normal size. Any ideas about how to make it smaller again? I've tried so many other methods .... Thank you. Liz
Submitted by Anonymous on Sun, 06/08/2008 - 22:41.
Please guide me in inserting text or modifying the pagenumbers to custom page numbers Ex: Supppose the page number is 1, it should S1 ..,
Submitted by Hannes Schmidt on Tue, 12/18/2007 - 01:08.

You mean Java, not JavaScript, right? If that's the case, the scripting API's won't get you very far. You might want to try out iText.

-- Hannes

Submitted by Anonymous on Mon, 12/17/2007 - 23:26.
Can u tell me how to get the no of pages in a pdf document using java?
Submitted by Anonymous on Thu, 08/16/2007 - 23:24.

Hi,

Thanks for the code. It works great but how can I get the background PDF to dispaly in the background behind the base PDF? At them moment it goes over the top of the base PDF.

Thanks

Keiran

Submitted by Anonymous on Thu, 03/15/2007 - 15:30.

I am sorry that I am asking my question here but on Acrobat Forum
I could not get any clue about what to do with my problem. What they answer is just commomn phrases. I have found that page and I hope that
may be you know something or know somebody to ask.
If you will have a chance to answer me then my email address is
yelena@thenextround.com.
Thank you.

I am using
the following code ( I put only necessary lines to be short)
AcroApp = CreateObject("AcroExch.App")
PDDoc = CreateObject("AcroExch.PDDoc")
InsertPDDoc = CreateObject "AcroExch.PDDoc")
PDDoc.Create()
InsertPDDoc.Open(OutPdfPath)
.....
If PDDoc.InsertPages(-1, InsertPDDoc, 0, iNumberOfPagesToInsert, 1) = False Then
MsgBox("Error Inserting Pages ")
Exit For
End If
.......
And this InsertPages(...) work fine for
some documents(that was opened before upon
name InsertPDDoc). But some pdf files the client sent to us are not working.It is
totally depends on pdf file that was opened.

Could you please answer why it is different on different files? The same code could do InsertPages() for one pdf file and could not do for another.
What 'wrong' with that file where it could not do it. I would appreciate any
clue on this.
Thank you.

Submitted by Anonymous on Mon, 11/06/2006 - 14:54.

have you thought of using app.openDoc ? It will return you the document object for the doc you've opened. You can then use the native JS APIs on that document object.

Submitted by Anonymous on Tue, 10/24/2006 - 12:25.

I think the link to core reference is old...
I want to say the names should be listed here:

http://partners.adobe.com/public/developer/en/acrobat/sdk/pdf/plugins/APIReference.pdf

but I can't find them....grrrr

Submitted by Hannes Schmidt on Sun, 10/15/2006 - 09:52.

Didn't you read this? You will have to code it on your own.

-- Hannes

Submitted by ashrafhariri on Sun, 10/15/2006 - 00:28.

Good morning
Did you find a solution for sending the background PDF to back of base PDF?
Regards

Submitted by Hannes Schmidt on Thu, 11/10/2005 - 10:43.

It's a bit more complicated but possible. The first link in the Online Resources section of this page points you to Matthew Fitzgerald's article Using JavaScript to Apply Templates To PDF File. In section A Technique for Adding Watermarks on page 8 of that article you will find the answer to your question.

Submitted by Anonymous on Thu, 11/10/2005 - 00:49.

Can you tell me how I can place the background PDF to back of base PDF? I used above function, but I cannot place the background to back

Submitted by Anonymous on Sun, 10/30/2005 - 21:59.

Hi,
I am using ApplyBackgroundToPDF function in a VB6
and I get an problem trying to create the watermarked PDF.

The problem is BasePDF and BackgroundPDF layer position changed.
For example,
BasePDF(3page) + BackgroundPDF(1page) ==> 3page Background to be seen.

Help...

Submitted by Anonymous (not verified) on Wed, 08/10/2005 - 06:22.

Thank you so much for posting this code. I have been searching for a simple way to add a background, and after a lot of searching, I found your site! I made some modifications to the code that allows it operate on files that are not currently open in Acrobat (more of a transparent process):

Private Function ApplyBackgroundToPDF(BasePDF As String, BackgroundPDF As String)
    Dim pdDoc As Acrobat.CAcroPDDoc
    Dim pdTemplate As Acrobat.CAcroPDDoc
    Dim template As Variant
    Dim lngPage As Long
    
    'Open base document
        Set pdDoc = CreateObject("AcroExch.PDDoc")
        pdDoc.Open BasePDF
        DoEvents
    
    'Open background document
        Set pdTemplate = CreateObject("AcroExch.PDDoc")
        pdTemplate.Open BackgroundPDF
        DoEvents
    
    'Add background document to base document
        pdDoc.InsertPages pdDoc.GetNumPages - 1, pdTemplate, 0, 1, 0
    
    'Create a template from the inserted background document
        Set template = pdDoc.GetJSObject.CreateTemplate("background", pdDoc.GetNumPages - 1)
    
    'Place the template as a background to all pages
        For lngPage = 0 To pdDoc.GetNumPages - 2
            template.Spawn lngPage, True, True
        Next
    
    'Delete last page (used for template creation purposes only)
        pdDoc.DeletePages pdDoc.GetNumPages - 1, pdDoc.GetNumPages - 1
    
    'Save
        pdDoc.Save 1, BasePDF
    
    'Close & Destroy Objects
        pdDoc.Close
        Set pdDoc = Nothing
        
        pdTemplate.Close
        Set pdTemplate = Nothing
End Function