Posts in Category "Macros"

Accessibility Bug Fix

Recently one of our customers discovered an accessibility bug that caused read order and tab order to diverge. When we investigated it, we discovered that the bug existed in the reader runtime, but that we could work-around the bug by changing the syntax that Designer generates. Fixing the problem in Designer is much preferable, since that doesn’t require upgrading Reader. Of course, waiting for a fix in Designer is not easy either. Fortunately, the macro capability offers us an alternative way to disseminate a fix.  First the usual caveat: macros are an experimental, unsupported product feature.  

The Bug

The bug scenario is when a custom tab order exits a subform; the read order follows the next-in-geographic order, while the tab order follows the explicit custom order.  Described differently, imagine a form with fields named "One", "Two" and "Three" and a subform named "S’.  The geographic order is: S.One, Three, Two, but the custom tab order is S.One, Two, Three. In the Reader runtime, the tab order correctly follows the custom order, but the Reader order gets confused on exiting the subform.

If you’re only interested in correct tab order you will not notice the problem.  However if you are using a screen reader and following read order, you will follow an incorrect order.  (If you examine the resulting form with tools like inspect32 or AccExplorer32 you’ll be able to see the result without actually running a screen reader)

I have included a sample form that illustrates the problem, and a version of the form with the fix applied.

The Fix

Down below, I’ll discuss the technical details of the fix.  But first the macro (rename to FixReadOrder.js) When you install and run the macro, it will scan the entire form searching for all the places where a custom tab order exits a subform. Each of these places will be modified. 

You will need to re-run the macro every time you make edits to the form that modify tab order.

The macro includes some logic so that it will stop functioning in the next version of Designer – given that we expect the bug to be fixed.

The Deep End

Custom tab order is controlled by the <traversal> and <traverse> elements.  The (stripped down) syntax of the original form looks like this:

 1 <subform>
 2  <subform name="S">
 3    <field name="One">
 4      <traversal>
 5        <traverse ref="Two[0]"/>
 6      </traversal>
 7    </field>
 8    <traversal>
 9      <traverse operation="first" ref="One[0]"/>
10    </traversal>
11  </subform>
12  <field name="Three"/>
13  <field name="Two">
14    <traversal>
15      <traverse ref="Three[0]"/>
16    </traversal>
17  </field>
18  <traversal>
19    <traverse operation="first" ref="Subform1[0]"/>
20  </traversal>
21 </subform>

There are two variations of the traverse element used here:

  1. operation="first": specifies the first child of a container (subform)
  2. operation attribute is unspecified (the default) — which means operation="next". Specifies the next element.

The runtime problem occurs when the traverse from field "one" at line 5 does not correctly navigate to field "Two".

Here is the fixed version:

 1 <subform>
 2   <subform name="S">
 3    <field name="One">
 4       <traversal>
 5         <traverse ref="Two[0]"/>
 6       </traversal>
 7     </field>
 8     <traversal>
 9       <traverse operation="first" ref="One[0]"/>
10       <traverse ref="Two[0]"/>
11     </traversal>
12   </subform>
13   <field name="Three"/>
14   <field name="Two">
15     <traversal>
16       <traverse ref="Three[0]"/>
17     </traversal>
18   </field>
19   <traversal>
20     <traverse operation="first" ref="Subform1[0]"/>
21   </traversal>
22 </subform>

The macro added a traverse element at line 10. Now we’ve explicitly told the subform which child to navigate to first and also where to navigate to when the children are finished.  In fact, we end up with two traverse elements pointing to field "Two".  Not surprisingly, the runtime correctly moves to "Two" in both tab order and read order.

Add Trace to Form Script

I continue to spend time playing with the prototype macro capability found in Designer 9 (ES2).  I have a new sample that uses it, but of course before showing you, I need to reiterate that the macro stuff is still an unsupported trial feature.

I wanted to do something to further help people debug their form script.  The usual way this happens today is that you sit down and add console.println() commands to the various scripts that run.  There are a few drawbacks to this mode of debugging:

  • It is tedious to add the debug/trace statements
  • It is not always clear which scripts need to have trace added
  • If you generate too much output, the console window fills up and stops showing content
  • FormCalc scripts cannot access the console object
  • Dumping large amounts of content to the console slows form execution considerably

To help matters along, I have written a macro that will modify scripts in your form and automatically add trace statements.  The injected code does the following:

  • Adds a top-level script object (scTrace) for collecting trace statements
  • scTrace stores trace statements in a JavaScript array — so that the size is not limited by the console
  • Adds a pre-print event that allows the array of trace statements to be dumped to a file
  • Adds a "full" event script to the root subform that can be used with execEvent() to emit strings from FormCalc scripts (this technique first introduced in this blog entry)

As an example, the before and after for a specific JavaScript looks like:


var vTarget =;
if (vTarget.className === "field") {
     vTarget.fillColor = "220,220,255";


scTrace.traceContext("enter:", this);
var vTarget =;
if (vTarget.className === "field") {
     vTarget.fillColor = "220,220,255";

For FormCalc the result is a bit more verbose:


price * units


xfa.event.fullText = "calculate"
xfa.event.newText = $.somExpression
price * units

Note that when you use this debugging technique you must not leave this script in your production forms.  It adds too much overhead.  When you want to trace/debug your form, follow these steps:

  1. Save a copy of your form
  2. Add trace to the copy
  3. Run the copy in Acrobat (not Reader)
  4. When you want a trace dump, print the form.  The pre-print event fires and prompts you to save the trace (when trace has been saved, cancel the print)
  5. debug and migrate changes/fixes back to the original document

The actual macro is here.  You need to rename it to have a .js suffix and put it in a subdirectory under the Scripts directory in your Designer install (see detailed instructions in the original blog entry).

Here is a sample form where trace has been injected.

I’ve found this tracing technique to work well in cases where form performance may be impacted by lots of events and scripts.  Especially in cases where there are dependency loops (see the discussion under "Dependency Loops" in this blog entry).

Note that the macro does not add trace to the indexChange event.  I ran into a scenario where this combination produced some bad behavior, so I elected to exclude that trace.  Hopefully this problem gets sorted out in the next version of Designer.

Updated Accessibility Checker

I recently got some feedback that the accessibility checker I posted for Designer would fail for some forms.

I have updated the script to handle the condition that was causing it to fail.  The new version is in the updated download.   This time I also included the mxml source used to create the SWF dialog — just in case you want to customize it for your own use.

Meanwhile, I also added a check for a new error condition:
"Invalid next node reference: <ref>"

This error will appear in the case where the markup generated for node traversal contains an invalid SOM reference.

If you’re using this checker and have any ideas for improvement, please let me know.  It’s a high priority for us to enable customers to create accessible content.

Usage notes

A couple more notes about the checker, and macros in general:

  • When a macro issues a message, the message appears in the log tab of Designer. If the macro script fails for some reason, this is also where the failure message will appear
  • With the very latest Acrobat update, you might find that the report PDF gets launched with a yellow bar warning.  The only way to get past this right now is to turn off enhanced security In Reader/Acrobat.  Obviously we’ll have to figure out a better solution if/when this capability makes its way to a supported product feature.

An Accessibility Checker for PDF Forms

One of the great things about PDF forms is that they have a really good accessibility story. You can create PDFs and PDF forms that allow the visually impaired user to interact with the document with the help of a screen reader. However, the quality of the experience is entirely in the hands of the PDF author. If your PDF consists of only a scanned page, the accessibility story is very poor. If your PDF form is richly annotated with audible cues, then the accessibility experience should be very good.

BTW, if you need help, here is a good resource for Accessibility Guidelines.

The problem is that your average form author does not test their result with a screen reader. They may understand the guidelines to make a form accessible, but they do not get feedback as to whether the guidelines have been followed correctly.

How well did your form author do with enabling accessibility? I have written an accessibility checker that will give at least part of that answer. I have written a designer macro (see Designer Macros) that will examine a form and validate the common properties that contribute to accessibility. (I need to repeat the usual caveat – Designer macros are a prototype/kick-the-tires/unsupported feature of Designer ES2).

When you run the macro, this is the dialog the designer user sees:

A graphic of a dialog showing accessibility checks that can be run on a form

Some explanation around the various checks:

Fields without captions
A caption is far more valuable in providing context than independent text that may have been placed near the field. With this option the macro will check that all fields have captions except for those that appear in a table cell.

Field with no assist text

There are several sources of data to provide context information for a field: field name, caption, tool tip, and custom (speak) text.  "assist text" refers to either a tool tip or custom text. If the field does not have one of these, then the macro reports a warning.

Images with no alternate text

Similar to above, we insist that an image has either a tool tip or custom text. If an image is explicitly marked with "Screen reader precedence: none" then we won’t report a warning.

Tables with no header row

Screen readers have special treatment for tables. Navigating through a table with a screen reader makes sense only if there is a header row providing some guidance.  In the absence of a header row we’ll issue this warning.

Read order and tab order diverge

Because of the hierarchical nature of form definitions and the way screen readers traverse them, there are limits on the flexibility of read order.  Specifically, it is possible to define a tab order where the order jumps out of a subform and back in again.  Read order does not allow exiting a subform before all content has been read.  The macro makes sure that the tab order for each subform has only one exit.  If there are two exits, then read order will not be able to follow tab order.


Once the script has done its work analyzing the form, it generates a report using the included report template.  The report looks like:

A two column report with one column of SOM expressions and a second column with error descriptions

Ideally the macro mechanism would have access to the warnings palette, but for the time being, a report will have to be enough.

Here is a Zip file with the files needed for the macro, along with a sample form where I managed to break all the accessibility rules.

I am interested in getting feedback from users if there are other useful checks to make the coverage of the macro more extensive.

Track PDF Forms with Omniture

No doubt you noticed that Adobe acquired Omniture — a company that provides online business optimization software — starting with web analytics.  One of the integration possibilities is to help companies track the activity inside their PDF documents — including forms.  What pages did they view? Did they print? save? add annotations? sign?  In the case of a form: what fields did they fill in? What buttons did they click?  How far into the form did they get before they abandoned their session?  Today we’ll work through a sample of adding tracking code to a PDF form.

Tracking code

When you want to track activity on a web side, the Omniture tools offer assistance for instrumenting your html pages.  You give it your tokens and it returns the appropriate script to embed in your source.  Similarly, we can generate code to add to ActionScript, Java and other environments.  In this blog entry I’ll show you how to do the same for your PDF.

A word about privacy

Tracking activity is a sensitive business.  End-users have the right to know that their actions are being tracked.  They also have the right to opt out of tracking.  Adobe Reader has a security policy that protects users.  In practise, what this means is that when a PDF is hosted in the browser, the document may post data as long as it adheres to the cross domain restrictions.  When a PDF is open stand-alone, it can perform http operations only if there is a level of trust.  A couple of ways to establish trust are to use a certified PDF, or the user can explicitly allow http access via the "phone-home" dialog.

Today’s sample, limits the tracking experience to PDF forms that are open in the browser.   Tracking a PDF in standalone Reader isn’t really recommended, because the phone home dialog is too ugly:


Data Insertion API

The API used by HTML JavaScript to do tracking is based on doing an HTTP Get operation from an image resource.  However, there are other APIs.

Omniture exposes a Data Insertion API where you can http post simple XML fragments to the server.  Once you’re logged in with a developer account, you can find this API described at:

The XML grammar used is fairly simple.  The sample form constructs XML ‘pulse’ transactions that look like:

   <channel>PDF Form</channel>

Of course, you can format this data any way you like — as long as the reportSuiteID and the URL that you post to are correct.

A few notes about the various fields we populated:


The API allows us to include up to 50 user defined properties: prop<1> to prop<50>.  In the sample, I’ve included some information about the version of Reader/Acrobat and the platform.  I originally wanted to put this information under <userAgent>, but that value is applicable only to browsers.


When tracking from a web page, the way to identify a visitor is with an IP address or with a script-generated id stored in a cookie.  However inside the Acrobat object model, there is no equivalent property to uniquely identify a visitor.  Ideally we’d be able to establish a constant visitorID between the users session in the browser and their session in Adobe Reader.  There’s some more discussion about establishing a unique visitorID below in "the deep end".


We need something to identify the PDF.  Using the PDF name is not reliable, since PDFs are easily renamed.  The sample below uses the xfa.uuid property.  This value remains constant even if the form is renamed.  For non-xfa PDFs we could use the doc.docID[0] property.


The form uses pageName to encode the action that has taken place.  I adopted a scheme where the string is a combination of "field name : event : additional information"


A way to categorize groups of transactions for better reporting.

The Sample Form

Unfortunately I couldn’t include a fully functioning sample form.  I have an Omniture sandbox set up for my own testing, but would rather not expose it to the world. The visitor namespace used in the example is fictitious.  Instead, I’ve changed the code that would normally post data and instead it will populate a field with the xml that would otherwise have been posted to the server.  To see the sample work — follow the link above and open it in the browser.  Or download it and open it in Designer ES2 preview mode.

Detecting a browser

As stated earlier, the sample form will track user activity only when hosted in the browser.  To detect when we are in the browser we look at the document path from the acroform object model:  If the prefix includes a protocol scheme (e.g. http:) then we know we are hosted in the browser.  (as an aside, Designer uses the browser plugin mechanism for hosting Acrobat/Reader when in preview mode.  When testing the sample form in Designer preview, it will behave as if it were loaded in the browser.  This explains why when you close your designer preview, the form itself doesn’t close — until the next preview.  We get the browser behaviour where the document is kept open for a while in the event that the user navigates back to the page hosting that PDF.)

Designer Macro

When you look at the sample form you’ll see that I’ve injected lots of script to gather and emit pulse data:

  • A hidden Tracker subform that contains a script object, and several other events
  • enter and exit events on every field in order to track when field values change

Manually adding script for tracking would get very tedious.  To make it easier, I wrote a designer macro that will instrument my form for tracking.  The macro dialog looks like:


Once you select the options you want, the macro injects the required script.  If you want to remove the tracking code from your form, de-select all the tracking options and press "Ok".

Here is a zip file with the macro JavaScript, SWF, and MXML.


Posting from an XFA form is pretty straightforward, given that FormCalc includes a built in post() function.  However posting from a non-XFA form is not so easy.  I tried a number of options:

doc.submitForm() — While this uses HTTP post, it also displays the server response.  In this case the Omniture server returns: <status>SUCCESS</status>.

Net.HTTP.request() — cannot be called from within a document. This function is available only in folder-level JavaScript.

Net.SOAP.request() — The documentation makes it look like it could be dumbed down to do a raw post, but in practise this is not the case.

The method I eventually cobbled together was to embed an XFA-based PDF as an attachment to the document I wanted to track.  When the document wanted to initiate tracking, it opened the attachment in the background and called into the tracking functions defined there..

The Deep End

There are several interesting things about the markup injected into the form:


The call to post data is made using the FormCalc post() function.  In order to call post(), I added a "full" event to the tracker subform. We use xfa.event properties to hold the parameters to post() and invoke it with a call to
Tracker.execEvent("full");  This technique is described at: Calling FormCalc From JavaScript.

Multiple events

You might think that adding an enter and exit event to every field object would be a problem if the form happened to have its own enter and exit events.  However, the XFA spec allows fields to have multiple events with the same activity.  i.e. there’s no problem having two enter events. They’ll both fire.  However, Designer will show you only one enter event.


To keep the markup as terse as possible, I made use of protos when injecting script.  The tracking subform contained the source code for the enter and exit events:

  <event activity="enter" name="Track_enter" id="Track_enter"> 
    <script contentType="application/x-javascript"> 
  <event activity="exit" name="Track_exit" id="Track_exit"> 
    <script contentType="application/x-javascript">

Then when adding these events to field objects, the syntax is very terse:

   <event use="#Track_enter"/>
   <event use="#Track_exit"/>

Propagating Events

Instead of adding enter and exit events to every field, I could have used a single propagating enter/exit event for all fields.  But since propagating events are available only since 9.1, I chose to add individual events so that the form would work in older releases of Acrobat/Reader.

Tracking validation errors is a different matter.  In this case there is no easy workaround for older versions of Reader — unless you’ve implemented some kind of validation framework.  In order to track validation failures the form uses the validation state change event.  Any time it fires, the form posts to the Omniture tracking server.  Note that the state change event also uses syntax not exposed by designer:

<event activity="validationState" ref="$form"
       name="event__validationState" listen="refAndDescendents">
   <script contentType="application/x-javascript">

Notice the attribute "ref="$form".  Designer doesn’t expose the ref attribute.  It would default to "$" — the current node.  In our example we’re able to house this logic inside the Tracker subform, but have it monitor validation activity in the rest of the form by pointing it at the root form model.

Ideally the Designer macro would be able to query the target version and then would control whether logic to track validation failures is feasible.

Unique Visitor ID

There is one way to create a persistent id using the Acrobat object model — by way of the global object.  I won’t bore you with all the details about how the global object works, but I will show you how I used it to create a persistent id:

* Effective reporting needs a persistent visitor id — across
* all PDF documents.
* @return a persistent visitor id
function getVisitorID() {
    var sVisitorID = "";
    // We use the global object to store/retrieve a visitor id.
    for (var sVariable in global) {
        // The global object security policy doesn’t
        // allow us to examine the contents
        // of all global variables, but it does allow us
        // to enumerate them.
        // We’re looking for a variable named:
        // _OmnitureTracking_*
        // The trailing digits will be our visitor id.
        if (sVariable.indexOf("_OmnitureTracking_") === 0) {
            sVisitorID = sVariable;
    if (sVisitorID !== "") {
        // Strip off the prefix
        sVisitorID = sVisitorID.replace(/^_\S*_/, "");

    } else {
        // Create a new visitor id
        sVisitorID = Math.ceil(Math.random() * 100000000);
        var sVisitorVar = "_OmnitureTracking_" + sVisitorID;
        // Add this visitorID as a global, and make it persist
        // so that it will be available next time in as well.
        global[sVisitorVar] = "x";
        global.setPersistent(sVisitorVar, true);
    return sVisitorID;

In the scenario where the PDF is being tracked in the context of a web site, we might consider embedding the users web site visitorID into the form data.  Then for the PDF tracking we’d concatenate the two values.


Designer ES2 Macros

Hey, it has been a while since I posted.  Lots of stuff on my plate.  Occasionally they make me do real work around here. 

Have you installed ES2 Designer yet?  If you have, then there is a new experimental feature that you can play with.  Macros.  I want to tell you all about them.  But first the caveats:

  1. ES2 Designer macros are an experimental (prototype) feature
  2. ES2 Designer macros are not officially supported
  3. Macros developed for Designer ES2 are not guaranteed to work in the next release of Designer.

We figured out the architecture for this feature fairly late in the ES2 development cycle.  We think macros have lots of potential, but we didn’t have the resources to finish the job in ES2.  So we’ve put it out there as a prototype.  You get to kick the tires.  Tell us if you like it.  Let us know what enhancements are needed.


Design Macros provide an external plugin interface to Designer, so that 3rd parties like partners or customers can extend the functionality of Designer. Some examples:

  • Rename a field or subform and update all the script references
  • Add metadata to form objects (<extras> or <desc>)
  • Find all scripts that consist entirely of comments
  • Add an onEnter script to all fields

Macro Script

The macro itself consists of a JavaScript file.  The JavaScript in the macro has full access to the template model. (I have a previous blog post that talks about scripting to the template: Template Transformation ).  The basics are that the scripting knowledge  from coding your form script transfers nicely to the Designer environment.

In addition to the template DOM, there’s an object in the root namespace called "designer" that has methods that you can use to communicate directly with the Designer application.

Flash Dialogs

One of the methods on the designer object allows you to launch a flash dialog (.SWF)and allows you to exchange strings with the dialog.  This allows you to build a custom UI.

Installing a Plugin

To install a plugin:

  1. Create a folder named "scripts" in the Designer install directory.
  2. In the scripts folder, create a folder for your plugin, i.e. "MyPlugin"
  3. In the scripts \ MyPlugin folder, create a JavaScript file (this is the actual plugin), i.e. "MyPlugin.js"
  4. Place any SWF files used by the plugin in the same directory.

When Designer starts up, it searches its install directory for a folder called scripts. If this folder is found, Designer will then search each child folder of scripts looking for *.js files.

Every *.js file found (and there can be more than one in the same folder) will appear as a menu entry under the Tools | Scripts menu. This menu entry on the Tools menu appears only if the \scripts directory exists and there is at least one *.js file in a subdirectory of the \scripts folder.

To run the plugin, select the plugin you want to run from the Tools | Scripts menu.

The Designer API

Here follows a description of the methods that are available on the designer object.

* Output a message to the log window in Designer.
* (Note that the log window in designer will not emit any
* duplicate strings)
* @param sMsg The text to push to the log window
void designer.println(sMsg)

* returns the object (or objects) currently selected
* on the canvas or in the hierarchy dialog. If nothing is
* currently selected, the list returned will be empty.
* @return a nodelist
nodelist designer.getSelection()


* Create a new modal dialog window from a provided
* @param sSWF The name of a *.swf file to load.
* Note that the *.swf file must be in
* the same directory that the plugin is installed in. 
* The sSWF parameter should only contain a file name,
* no path information.
* @param nWidth The width of the Flex dialog
* @param nHeight The height of the Flex dialog
* @return a string that comes from the
* Flex application.  When the Flex application terminates, it
* can pass back a string. Commonly used to send back it’s
* closing status, e.g. "OK" or "Cancel"
string designer.showFlexDialog(sSWF, nWidth, nHeight)

* This method writes out a text file (showTextWindow.txt) to
* the system’s temporary directory with the content of sText
* then launches the system’s default *.txt file editor with
* that file as a parameter.
* This method allows a non-modal way of showing output.
* The Flex dialog and the alert dialog are both modal – this
* makes it impossible for a user to interact with the output
* of a plugin at the same time they interact with Designer.
* @param sText The text to show in the system’s default
* text editor.
void designer.showTextWindow(sText)

* This function creates an XDP data file from the 
* supplied data and will launch a PDF file with that data.
* This allows rich reporting from a plugin script.
* Note that this function looks for an installed version of
* Acrobat, and will not work with Reader.

* @param dataPacketString The XML data to be written out
* @param pdfName The base name of the PDF file to display,
* which must be in the plugin directory.
void designer.showXDPinAcrobat(dataPacketString, pdfName);

* This function is used to get data out of the Flex dialog
* invoked by designer.showFlexDialog(). The Flex dialog can
* send data to Designer by calling:
*                          "VariableName", "VariableValue");
* If the Flex dialog makes this external call to Designer, then
* once the dialog is dismissed, "VariableName" is available for
* inspection in the plugin through a call to:
* designer.getDialogString();
* In this particular example, the call would be:
* designer.getDialogString("VariableName");
* The return value would be "VariableValue".
* @param sFieldName The name of the field to inspect.  
* This field is available for inspection only if the Flex
* application made the appropriate ExternalInterface call.
eturn The value of sFieldName or empty if the Flex
* application did not set that value.
string designer.getDialogString(string sFieldName);

* This method is used to push data into the Flex dialog before
* calling designer.showFlexDialog(). If the plugin wants to set
* data inside the Flex dialog, it needs to call
* designer.setDialogString();
* with the data before invoking designer.showFlexDialog().
The Flex application, in turn, needs to call
*"getDialogString", "sFieldName")
* @param sFieldName The name of the variable to set
@param sValue The value of sFieldName.
void designer.setDialogString(sFieldName, sValue)

* Show a message box in Designer with sMsg as the text.
* @param sMsg The message to display in the message box.
void designer.alert(sMsg);

An Example

Over time I hope to share a bunch of sample macros.  But to get started, here is a fairly simple macro that should wet your appetite.

The macro refactor.js will rename a field object.  In addition to renaming the field, it will find all occurrences of that field in scripts and will rename it there as well. It uses refactor.swf as a ui to modify the scripts.

Step 1.

Install the macro.  Place the .js and .swf files below the Designer install.  On my system this looked like:


Step 2.

Open a PDF in Designer ES2.  I used this file to test.

Step 3.

Select the field to rename

Step 4.

Launch the macro:


Step 5.

When the flash dialog pops up, enter a new name for the field. Then use the buttons to find/replace the field name in scripts


When the dialog is dismissed, the form will be updated with all the changes.

Here is the rest of the collateral you’ll need (right click to download):


Whew.  That’s a lot to absorb.  I hope to offer some more samples soon.