Template Transformation

Today I am disclosing a trick that will allow you to apply a transformation to your template.  But first a disclaimer: this technique is not a product feature.  It is just a trick that takes advantage of some side effect behaviours.  Ok, ok, it’s a hack.  But it’s potentially a very useful hack.

Motivation

Have you ever wanted to do apply a global change to your template?  Change the fonts of your fields.  Set a colour scheme.  Add a script to all your fields.  You know by now that it can be very tedious to do these things. 

Transformation Techniques

What options do you have today?  For very simple changes, some people will go to their XML Source tab and do a global search/replace.  That’s a bit scary.  I do it when I rename anything that is referenced in script.  But it’s hard to use when you want to be selective in your changes — e.g. apply a font change to all numeric fields.  There is also XSLT.  We have some very technical customers who will save their form as an XDP and apply an XSLT script to make global changes.  These customers have my very humble respect.  XSLT is a very powerful tool, but a very difficult technology to master.

Script

There are a couple of ways I’d like to transform a template.  One choice would be some form of style sheet definition.  My other choice (today’s topic) would be to write JavaScript that updates the template.  We write tons of script that modifies the form DOM at runtime.  Wouldn’t it be great if we could write script that modified the template at design time?  Well, turns out you can.  But as I think I may have mentioned — it’s a bit of a hack.

I’ll explain how it works, but first some background to help you understand the technique.

PDF Generation Process

When you edit a dynamic template and save as a PDF, Designer gives you the illusion that PDF is the native format for the template.  In reality, the native format is XDP.  When Designer opens a dynamic PDF, it extracts the XDP stream stored inside and discards the PDF container.  Then when you save, Designer re-generates the PDF from the XDP.  That would explain the phrasing of the messages you see in the log tab of Designer when you save:

Generating PDF Document…
PDF generated successfully.

0 warnings/errors reported.

The PDF generation process that Designer goes through during save is almost exactly the same process LiveCycle Forms goes through when it generates a PDF from an XDP on the server.  Among the steps during PDF generation:

  1. Embed all required fonts
  2. Embed referenced images
  3. Generate XMP metadata
  4. Run server-side scripts

Hopefully #4 caught your attention.  When Designer saves a dynamic PDF, one of the things it does is execute script. 

Scripting the Template

When any script runs in an XFA form, the default context is the Form DOM.  The Form DOM is a transient DOM that gets created when you open the form and goes away when you close the form.  You can script against the Form DOM as much as you like.  However, in Reader you cannot modify the template DOM.  The template DOM is the source code.  It is what gets saved back in the XDP stream in the <template> element.   For security reasons, we don’t allow forms in Reader to modify their template.  But, this is a Reader-only restriction.  Forms running in the context of the server can modify their template.  This means that script run by Designer when it saves your dynamic PDF has full write-access to the template and any changes the script makes will be preserved in the generated PDF.

Setting up a save-time script

Take a simple example.  This script will update the author information metadata in my template (note that the script is set to run at server):

form1.#subform[0].updateMeta::initialize – (JavaScript, server)
updateMetaData("creator", "John Brinkman");
updateMetaData("contact", "
http://blogs.adobe.com/formfeed/");
updateMetaData("usage",   "No rights reserved");

function updateMetaData(vName, vValue)
{
    var vDesc = xfa.template.["#subform"].desc;
    if (vDesc.nodes.namedItem(vName) == null)
    {
        vDesc.nodes.append(xfa.template.createNode("text", vName));
        vDesc[vName].value = vValue;
    }
}

Now when I save the form and go look at the file/Form Properties/Info I’ll see… no change.  Oops.  The problem is that once Designer saves, it doesn’t re-open the PDF it saved.  It continues operating on the XDP it has in memory.  To see the change you need to save *and* close.  Then when you re-open and look at your file properties you will see the author and contact information updated. (You won’t see the "usage" element in this screen, but it does get embedded in the PDF metadata).

Debugging

For those of you who get their JavaScript right the first time, read no further.  Those like me who need to liberally sprinkle their code with console.println() messages will discover that the console API doesn’t work.  That’s because console is an Acrobat-only object. You can’t code against it on the server.  Instead, you will want to familiarize yourself with the xfa.log object.  To dump a message to the log file, you can code:

xfa.log.message(0, "your message here");

This message will show up in the log tab of designer.  A couple things you’ll want to know about using this command effectively:

  • The log file suppresses messages that have the same text as the previous message
  • For a message to appear with proper line breaks in the log tab, you need to insert carriage return/line feed combinations in your string.  i.e. instead of "line1\nline2" you need to code: "line1\r\nline2".
  • Before saving you should clear the log.

Cleanup

Once your transformation script has run, you don’t need it anymore, and it would be bad form to leave it around.  Add a couple lines to your script so that after it has done its work it will remove itself.

With a log message and cleanup, the sample above becomes:

updateMetaData("creator", "John Brinkman");
updateMetaData("contact", "
http://blogs.adobe.com/formfeed/");
updateMetaData("usage",   "No rights reserved");

// Insert a message in the Designer log tab
xfa.log.message(0, "updated form metadata");

// remove this script once it has run on-save
var vNode = xfa.template.resolveNode("$.." + this.name);
vNode.parent.nodes.remove(vNode);

function updateMetaData(vName, vValue)
{
    var vDesc = xfa.template.["#subform"].desc;
    if (vDesc.nodes.namedItem(vName) == null)
    {
        vDesc.nodes.append(xfa.template.createNode("text", vName));
        vDesc[vName].value = vValue;
    }
}

Of course, you’ll probably want to leave the cleanup code commented out until you have fully debugged your script :-).  It would be a good idea to make sure your imported subform has a visual appearance so that you won’t forget to deal with it.

Once it is working, the updateMeta subform can be added to your fragment library.  When you want to update your metadata, you follow these steps:

  • import the updateMeta subform fragment
  • save
  • close
  • re-open

And if that’s less effort than re-typing the metadata, then you’re in a happy space.

I have attached a sample that does a couple of interesting things:

  • Update all the field widget borders to be .01in thick, solid and blue.
  • Add enter and exit scripts to each field to highlight the border while the field is active.

To use the sample:

  • Open the form in Designer
  • Modify the first line of the script and change it from if (false) to if (true)
  • Save
  • Close
  • Open

When re-opened you’ll see that the subform hosting the update script is gone.  The borders are now solid blue and when you preview and tab through the fields you’ll see that the border changes colour when the field is active.  You’ll also notice that in the sample I use a slightly updated version of the scMessage scripts to display the server message — fixing the carriage return/line feed issue.

Futures

I have been saying that this technique applies to Dynamic XFA-based PDFs.  In fact it works with static as well.  But it does not work for artwork PDFs.  It also does not work when you save as .XDP.  For those environments there is no solution yet.

The technique described here is admittedly awkward, but it does point toward some exciting possible enhancement areas for Designer.  Notably style sheets and Design-time macros.  Nobody is making any commitments, but these are areas of interest to us as we move forward.