This tutorial shows you how to work with the JavaScript features in Acrobat X. See what the all-new Acrobat DC can do for you.

Download a free trial of the new Acrobat.

Scripting for Actions in Acrobat X

Learn how to use the Actions Command Palette to create an Action that requires scripting in Acrobat X Pro and Suite.

By Thom Parker – November 1, 2011

 

An Action in Acrobat X Pro is a set of pre-defined commands that are applied sequentially to a list of PDF files. In previous versions of Acrobat this was called Batch Processing, and there is a treasure trove of information on creating and using Actions in articles and videos on this site. There is also an Actions Exchange, offering all kinds of fancy Actions -- created by users – that can be downloaded. 

The "commands" that make up an Action are, for the most part, the standard operations on the regular menu items and tools in Acrobat, such as deleting a page or applying security. Anyone who is already familiar with Actions will know these commands are fairly static. They are set up to perform a specific activity, such as deleting page five of a PDF. There is no way to set up the page-delete command to be more flexible, for example, to delete the second-to-last page, for which the specific page number will change with the document length.

To create an Action where the operational parameters are flexible requires scripting, and fortunately, the Actions Command Palette includes a command that runs JavaScript (Figure 1). In fact, most of those fancy Actions on the Actions Exchange are implemented with the "Execute JavaScript" command.  It is worth the time to examine the scripts in these actions.  It is simply not possible to get the kind of advance functionality used in these Actions any other way.  This article will explore the rules and techniques for writing such scripts.   


Figure 1 – The Execute JavaScript command provides the only way to apply automatic, non-interactive flexibility to an Action.

Scripts in the "Execute JavaScript" command

There are some big advantages to running a script in an Action, as well as a number of serious limitations. It's important to understand both sides in order to write efficient, error-free scripts. In addition, as we'll see, to get the most out of an Action Script, a little creativity is needed to work around some of the limitations.

Basic action-script operation:

  1. Privileged operation: Scripts in an Action can run all objects, functions and properties in the Acrobat JavaScript model without limitation. This includes the Identity object and protected file system operations. This is the most important advantage of scripts in an Action. 

  2. The Event Object: In the JavaScript model, all scripts are triggered by some event and have an associated event object. Actions are no different. For an Action, there are four event properties:
    1. event.name – The word "Exec."
    2. event.type – The word "Batch."
    3. event.target – The document object for the current PDF. The script is executed in the context of the current PDF, so the keyword this also points to the document object for the top-level-event script.
    4. event.rc – Return value for the Action. If set to false by the script, the entire Action is aborted. No further files are processed. This is not the most useful return value. The best use is for the case where the script can detect catastrophic failure. Returning false gives the script a graceful way to exit the process and prevent the failure. It also makes it possible to allow the user to abort the Action through a popup alert. This technique is used in the last example in this article.

  3. Document position:   The Action sequence operates on a list of documents. It is often useful for the script to know the position of a document in the list, especially the first and last documents. For example, a script that builds a log of the operations will need to set up the logging process before operating on the first document, then finish up the logging after the last file. This is a typical methodology for any kind of batch processing, and there are many similar techniques that require detecting either the first or the last file. Unfortunately, Acrobat does not provide any method for detecting the document position. This is one of those limitations that will need a creative solution.

  4. Sequence control (branching, skipping steps, etc.): A script can analyze the document and make intelligent decisions about how to proceed. There are times when it would be useful to use this kind of information to control how other (more static) steps in the Action Sequence are processed. For example, to skip a particular step, run an alternate step, to pass information into a command (such as a page number) or to skip the processing of a document altogether. Unfortunately, none of these things is possible. This is another limitation of the Action sequence. Commands in the sequence are executed in the specified (linear) order on every document in the list. If branching and/or skipping command steps is required, then the Action must be composed of a single "Execute JavaScript" command, and all operations must be contained in the script.

  5. Document-view parameters: Documents processed in an Action may or may not be visible in Acrobat, depending on the specific steps used in the Action. For many types of Action sequences, the documents are opened as hidden to improve performance. If the document is hidden, then view parameters such as the page number and zoom level do not exist. It is best not to use these types of parameters in an Action script, unless it is known that the document will be visible in Acrobat.

  6. Menu items:  Executing a menu item in a script can be a very useful shortcut to some needed functionality. However, as a general rule, menu items can only be executed from a script when a document is visible on the screen. As already explained, this is not always the case with an Action. Only use menu-item execution when it is known that the document will be visible in Acrobat.

  7. Performance: The efficiency of an Action becomes an issue if it will be applied to more than a few PDFs at one time. In extreme cases, an Action may run for more than a day. For these situations, it is very important to use the fastest and most robust operations possible, and to include exception handling in the script so that errors are handled gracefully. One problematic JavaScript command is the doc.getPageNthWord() function. In Acrobat 9, this function became between 10 to 25 times slower than in previous versions. This function is necessary to perform custom text searches, but the extreme slowness means that it should be used sparingly.

Scripting techniques

Most of the scripting used in an Action is very straightforward, and is written in exactly the same way as the code placed in a folder-level trusted function. For example, here is a single line of code that places the file name along the bottom edge of all pages in the PDF using the watermark function.

event.target.addWatermarkFromText({
	cText: event.target.documentFileName,
	nFontSize: 10, 
	nVertAlign: app.constants.align.bottom,
	nVertValue: 18
});

The script is simple, but it makes a custom change to the PDF that could not be done with any of the other Action Commands. If the document has an existing watermark, the new footer text is added to that watermark, so the code does not destroy anything already on the PDF. Notice that event.target is used as the document object. In this code, the "this" keyword could also have been used, but using the official, and explicit reference to the document object is a much better practice

Saving to a custom file name:
The Actions Dialog provides several options for saving the files operated on, including options for renaming the files with prefixes and postfixes. However, these additions to the file name are static. A script is the only way to provide a flexible, custom-file-name option. For example, here is a short script that appends the current date to the file name and saves it into a subfolder of the original file.

// Create the Destination save path from the existing file path
var aDstPath = event.target.path.split("/");

// Remove File Name from end
aDstPath.pop();

// Add subfolder
aDstPath.push("result");

// Build File Name addition from the current date
var cExt = util.printd("_ddmmmyyyy",new Date()) + ".pdf";

//Add the extension to the end of the file name 
var cDstName = event.target.documentFileName.replace(/\.pdf$/,cExt);

// Build the full destination Path
aDstPath.push(cDstName);
var cDstPath = aDstPath.join("/");

// Save the file
event.target.saveAs(cDstPath);

It seems like there is a lot to this script, but it is really just a simple set of text manipulations to build the destination file name and path. This script must be the last step in the Action, and the SaveAs portion of the Actions Dialog must be set to "Don't Save", as shown in Figure 1.

Building a report file
This technique is very useful for analyzing documents. It's used by the Create Comment Summary Action in the Actions Exchange. The code in that example is very complex. A simplified version will be presented here. For this example, a script will be created that reports the number of pages for each document run through the Action.  

It was stated earlier that there is no way for the script to know whether a document is the first or last in the process, so a method is needed that does not depend on detecting the beginning or end of the Action process. To deal with this issue, the script will use the report document itself. If no report document is open, then one will be opened. If it is open, then the script will add information to it. In this example, it is assumed there is a generic report document, available in a known location. It is also assumed that the report document contains a list field where the collected data will be placed. Here's the script:

// Test for existence of Report doc by accessing a property 
try{global.oReportDoc.path}
catch(e)
{
	// Perform startup action 
    global.oReportDoc = app.openDoc("/c/reports/PageNumReports.pdf");
    //Clear list
    global.oReportDoc.getField("PageNumList").clearItems();
}
// Collect Data and stuff into Report Doc
var cReportText = event.target.documentFileName + ": " + event.target.numPages + " Pages";
global.oReportDoc.getField("PageNumList").insertItemAt(cReportText);

The document object for the report doc is stored in the global object. The first line detects the existence of the doc object by testing a property of that object. If the document was never opened, or if the user closed the report doc from a previous run, then the JavaScript engine will throw an exception and the catch block will open and initialize the report document. This is just one method. The report doc could have been detected in a different way. For example, the open report doc is also listed in the app.activeDocs property. 

The location of the report document is completely arbitrary; it could be placed anywhere that is convenient. In the Create Comment Summary Action, the report document is created at runtime using the app.newDoc() function. It also could have been done using the Report object. The important bit is to have an open PDF document to act as a repository for the collected data. It's also a good idea to include fields on the report doc for displaying the time and date of the Action run, and maybe even the user name. 

Once the report document is open, data will be added to it every time the Action is run. To create a clean report document, this file should be saved to a new location and closed. The save action can be added to the startup code in the catch block, for example saving to the folder of the first document. This is the best place to put an automatic save. There is no easy way to know when the last document is processed, so the save, if it is added to the code, has to happen when the document is first opened.

Getting user input at the beginning of the process
For some actions it is necessary to ask the user for input parameters in order to run the Action. If the same parameters will be used for all documents processed, then this should happen only once, at the beginning of the process. The challenge, of course, is detecting the beginning of the process. 

The answer to this challenge is provided by the previous example that uses a report document. The report document can be used as a kind of marker to determine the state of the process. If the report document is not open, then the Action has just started and the script displays a popup or custom dialog for collecting data. If the report document is already open, then the script proceeds with the business logic of the Action. This technique works nicely, but it is a bit awkward because the report is still open when the Action is complete. If the user wants to run the Action with new parameters, they must close the report document. Unfortunately, there aren't any other less-awkward solutions.

Here's a short demo script that shortens the number of pages in a PDF to the number of pages specified by the user. 

// Test for existence of Report doc by accessing a property 
var bDoStartup = false;
try{global.oReportDoc.path}
catch(e){
	// Perform startup action 
	bDoStartup = true;
}

// Also test for page length parameter
	if(bDoStartup || !global.nPgLen)
{
	global.oReportDoc = app.openDoc("/c/reports/DocTruncReport.pdf");
	
	// Ask user for input
	var bRedo = true;
	while(event.rc && bRedo){
		global.nPgLen = app.response("Enter Max Length of PDF");
		bRedo = isNaN(global.nPgLen) || (global.nPgLen == 0);
		if(bRedo)
		event.rc = (4==app.alert("Do you want to continue? ",1,2));
	}
}

// Perform page truncation if necessary for current doc
if(event.rc && (event.target.numPages > global.nPgLen))
event.target.deletePages(global.nPgLen, event.target.numPages-1);

The business logic for this script is only two lines of code, i.e., the last two lines in the script. Everything else is preparation. The script starts off by testing for both the existence of the report doc and the global.nPgLen parameter; both are necessary. If either does not exist, then the report document is opened and the user is asked to enter the page length used to truncate the PDFs. The code setup guarantees the user will only be asked to enter this information at the beginning of the Action execution. This is where things get interesting. 

The response box is displayed inside a while loop. This is a simple technique for ensuring the user enters a valid value, in this case, a number. The loop will continue to display the response box until the user either enters a valid number or cancels the Action altogether. This second bit is handled by an alert box that is only displayed if the data entered by the user is invalid. The user is asked if they want to continue the process. If they answer "yes," then the response box is displayed so they can enter another number. If they answer "no", then event.rc is set to false, which aborts the Action. Notice that event.rc is used to qualify both the "redo" loop and the main business logic for the Action. 

All the techniques used here are also used in the Extract Commented Page Action, available in the Actions Exchange. The code in that Action is much more complex than what is shown here, but the technique is the same. In a working script, the popup messages should be more verbose and information about the process should be written to the report document. 



Products covered:

Acrobat X

Related topics:

JavaScript

Top Searches:


0 comments

Comments for this tutorial are now closed.

Comments for this tutorial are now closed.