Archive for April, 2009

Debug Your Form Layout

Today I want to share with you a work-in-progress of a tool that you can use to debug your form layout.  The sample builds on some concepts from previous blog entries:

  • The form dom debugger that used script access to graphically display the form and data doms
  • The reporter form that loaded/opened a PDF and generated a summary of the contents.
  • The form demonstrating how to calculate layout positions

The motivation for the debugger form is simply that once you step outside the simple layout scenarios, form layout can be a complex area to debug.

The layout debugger form loads and opens an XFA/PDF and will graphically display the form dom and the page layout.  To get a feel for how it works, save this sample form to disk and then open it from the layout debugger form.  Some notes to guide you:

  • Before you opened the form there was a checkbox: "show boilerplate".  This allows you to include/exclude the static text/images/lines/rectangles etc. that occur on your form.  Often these have no impact on layout problems and there is less noise if we do not include them.
  • The left hand side of the page displays the form dom in much the same way that the form dom debugger did.  As before, you can expand/collapse the hierarchy and use the arrow buttons to scroll up or down half a page at a time.
  • The right hand side of the form shows a graphical display of the pages.  Each page is numbered and scaled proportionately.  Each page has its content area(s) drawn in a dashed line.
  • When you mouse into a field in the hierarchy, the pages are highlighted with the extent(s) of the corresponding object.  If the object has split across multiple content areas, it will display as multiple rectangular areas.  You’ll note that when you select the root of the form dom that it spans all pages.
  • There are a set of XFA properties that impact layout.  Things like: presence, layout, break, keep, overflow, etc. Each object in the form dom hierarchy displays the names of any layout properties that do not correspond to their default values.  When you mouse into the hierarchy field, the expanded property values are displayed in the top right corner of the page.
  • The hierarchy is colour coded.  Grey entries are not-splittable.  Green entries are splittable, yellow entries are overflow leader/trailer subforms that were added  by the layout process.  If you scroll to the bottom of the form hierarchy you will see a couple of "hdr" subforms that layout added.  Note that near the top of the hierarchy the first occurrence  of "hdr" appears because of its place in the form dom — not because layout added it.
  • While you are scrolled to the bottom, click on the "Date" field under page 1.  You will see it appear with a yellow warning icon.  This is because it was positioned off the page.  Adding warnings is an area where I need to do much more work.  It should be possible to detect more conditions that frequently cause layout problems.
  • When you click on a hierarchy object that is hidden, nothing in the page display gets highlighted.  That is because hidden objects are excluded from layout.

As I said, this is a work in progress.  But I felt it was far enough along to share.  I have more plans for it.  If you have specific ideas of how to improve it, please let me know.

June Update

There is a follow-up debugger effort that supercedes the sample in this entry. Please have a look here.

Performance of Object Expressions

In a previous post I described how object expressions in JavaScript worked and compared them to using calls to resolveNode() and using FormCalc.  There is a trade off between readability and performance.  From the previous post:

“SOM parser evaluation is faster than JavaScript object evaluation.  If you have an expression that must be executed many, many times, then it is better to wrap it in resolveNode() or use FormCalc.  However, for most forms with modest amounts of script, the difference in overall form performance is negligible. Most of the time ease-of-scripting and code readability are more important.”

We were working on an internal prototype that made me want to quantify the difference in performance between object expressions and SOM expressions.  In order to measure performance I needed a form that would evaluate many, many expressions.
The result is a sample form that simulates John Conway’s famous Game of Life.

The Game of Life Form

A few notes about how the form works:

  • Each cell is a check box field.  You can re-arrange the starting pattern by toggling the fields
  • The grid is initialized by creating nested row/column subforms
  • The row and column subforms are renamed so that the object expressions don’t use indexes.
    e.g. grid.row[3].column[4] becomes grid.row_3.column_4

Most applications that simulate this game access the cells in nested loops.  For this sample, the loops are flattened so that we have very large scripts with hard coded object expressions.  For example, the code to find neighbours of the cell at (3,4) is:

vNeighbourCount = 0;
vNeighbourCount += form1.grid.row_2.column_3.cell.rawValue;
vNeighbourCount += form1.grid.row_3.column_3.cell.rawValue;
vNeighbourCount += form1.grid.row_4.column_3.cell.rawValue;
vNeighbourCount += form1.grid.row_2.column_4.cell.rawValue;
vNeighbourCount += form1.grid.row_4.column_4.cell.rawValue;
vNeighbourCount += form1.grid.row_2.column_5.cell.rawValue;
vNeighbourCount += form1.grid.row_3.column_5.cell.rawValue;
vNeighbourCount += form1.grid.row_4.column_5.cell.rawValue;
if (form1.grid.row_3.column_4.cell.rawValue)
  vNewState = (vNeighbourCount == 2 || vNeighbourCount == 3) ?1:0;
else
  vNewState = (vNeighbourCount == 3) ? 1:0;
form1.grid.row_3.column_4.cell.extras.newState.value = vNewState;

After one pass where we stored the new state for each of the cells in the extras of the cell field, we assign the new state:

form1.grid.row_3.column_4.cell.rawValue =
form1.grid.row_3.column_4.cell.extras.newState.value;

Repeat that code for each of the 2,500 cells in the grid and now the form has around 36,900 lines of JavaScript and 29,405 object references.  That should be good enough to test the performance of object expressions.

Then for comparison purposes, the form has another  script that does the same operation using calls to resolveNode():

vNeighbourCount=0;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_2.column_3.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_3.column_3.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_4.column_3.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_2.column_4.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_4.column_4.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_2.column_5.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_3.column_5.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_4.column_5.cell”).rawValue;

if (this.resolveNode(“form1.grid.row_3.column_4.cell”).rawValue)
  vNewState = (vNeighbourCount == 2 || vNeighbourCount == 3) ?1:0;
else
  vNewState = (vNeighbourCount == 3) ? 1 : 0;
this.resolveNode
  (“form1.grid.row_3.column_4.cell.extras.newState”).value =
vNewState;

And one more comparison where we do the same in FormCalc:

vNeighbourCount = 0
vNeighbourCount = vNeighbourCount + form1.grid.row_2.column_3.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_3.column_3.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_4.column_3.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_2.column_4.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_4.column_4.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_2.column_5.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_3.column_5.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_4.column_5.cell
if (form1.grid.row_3.column_4.cell) then
  vNewState = if(vNeighbourCount == 2 or vNeighbourCount == 3,1,0)
else
  vNewState = if(vNeighbourCount == 3, 1, 0)
endif
form1.grid.row_3.column_4.cell.extras.newState = vNewState;

Results

The good news is, that all three variations are *very* slow — great for measuring performance :-)

The performance results when I ran this on my laptop:

JS with Object Expressions JS with calls to resolveNode() FormCalc
Mlliseconds 7154 4673 1735
Milliseconds
per Expression
0.243 0.159 0.059

Clearly there are differences.  I was surprised at how much faster the FormCalc version was.  I would like to understand that better some day.

But now before you run off and re-code your forms to use resolveNode() or to use FormCalc, we have to put the numbers into perspective. For the slowest variation (object expressions in JavaScript), we evaluated 29,405 expressions in just over 7 seconds.  That means each expression evaluated in roughly 0.24 milliseconds.  I’m going to go out on a limb here and assume that this is fast enough for most forms.  Sure, the FormCalc version does it in  0.06 milliseconds, but your end users will not notice the difference.  Unless, of course, your form is doing something frivolous such as simulating the Game of Life.  The test validated the original assumption: in most cases code readability and maintainability trump any performance issues.

Something Useful

So far this blog post has not uncovered anything particularly useful, and I feel obliged to leave you with something that you can use in your day-to-day form design.

In the previous post that dealt with object expressions, I described how the JavaScript engines deal with “naked field references”.  There is a nuance that you should be aware of:  the JavaScript engines treat a variable with an explicit declaration differently from a variable without a declaration. i.e.

var foo = “hello world”;
xfa.host.messageBox(foo);

is handled differently from

foo = “hello world”;
xfa.host.messageBox(foo);

In the first case, the JavaScript processor will ask the XFA engine if it recognizes “foo”.  The XFA engine says “no” and from that point on, the JavaScript processor will not ask again when it encounters more references to “foo”.  In the second case where the “var” keyword is not used, the JavaScript processor asks the XFA engine for a definition of “foo” each time it encounters “foo” in the script.

Now consider what happens when you code:

for (var i=0; i<10000; i++)
  total += po.nodes.item(i).subtotal.rawValue;

vs.

for (i=0; i<10000; i++)
total += po.nodes.item(i).subtotal.rawValue;

In the first case, the XFA processor evaluates “i” once.  In the second case, the XFA processor evaluates “i” 30,000 times.  Would you notice the difference?  It depends on two factors:

  1. How many iterations in the loop and how many references to the loop counter
  2. The cost of one lookup – how many objects are in scope when we do the evaluation.  When the XFA processor searches for “i”, it does a physical scan through all objects that are within scope of the current context.

The Deep End

If you are writing script for a form that will run on the server, there is a bug you might want to be aware of.  As mentioned, once the JavaScript engine determines that an identifier is a variable or an XFA object, it will not ask again.  On the client this happens on a per-script basis.  However on the server this happens on a form-wide basis.  e.g. if an initialization script uses “foo” as a variable, then it will be assumed to be a variable in all other scripts on the form.  Another reason why my preference is to avoid using common names for variables.  For reliability — and for readability I prefer to prefix my variables e.g. “vIndex”.

Field Tab on Full

When a form author needs to capture data where there are several related fields with fixed numbers of characters, they should try to make the separate fields behave like one continuous field.

The best example I can think of is when I’m filling in a multi-part serial number.  The number usually gets broken out into several input fields.  As soon as one input field is full, you are automatically tabbed over to the next field. If you paste a long value into the first field, the pasted value is used to populate all the fields in the sequence.

Occasionally I run into a dialog box that prompts for a serial number but doesn’t give me the auto-tab and auto-paste-fill experience, and I get … angry.  How dare they force me to transcribe a 15 digit number digit-by-digit?

Today’s example shows how to bring the auto-tab and auto-paste-fill experience to a PDF form.  The sample form captures a social security number as a sequence of 3, 2 and 4 characters in 3 separate fields.

Prerequisites

For the fields of the sequence to work in concert, the script needs to know three things:

a) How many characters are in each field
b) which fields participate in the sequence
c) What order are the fields in?

The sample does this by:

  1. Define each field in the sequence as a text field with a maximum number of characters (field.value.text.maxChars).
  2. Define a custom tab order.  The next field is defined in the markup: field.traversal.traverse.ref

The sequence of fields could easily be defined in a different way.  My alternate choice would be to wrap the fields of the sequence in a subform and assume that they are in document order.

Change Event

When a user types or pastes, the change event fires.  The change event checks for:

  • If there’s more text than fits, spread the leftover text among the next fields in the sequence according to their maxChars capacity.
  • If the user’s input has caused the field to reach its maxChars value, set focus to the next field in the sequence.

Futures

There’s more that could be done.  The form could (and should) have tested the digits to make sure they’re valid for a social security number.  On deleting the last character from a field the form could automatically tab to the previous field.

 

I won’t be blogging for a bit.  I will be on vacation all next week.  In the mean time, happy form designing.

Layout Methods to Find Page Positions

This post will be in the deep end for many of you.  But for those who design forms that extract position information from the xfa.layout object, you might well find some answers to longstanding problems — especially if you make use of multiple content areas on your master pages.

This blog entry deals specifically with the functions:
xfa.layout.x(), xfa.layout.y(), xfa.layout.h(), xfa.layout.w().

Return Value

The description for layout.x() says: "Determines the x coordinate of a given form design object".  This is true.  Incomplete, but true.  In fact, the x() and y() methods return a coordinate relative to the parent of the object.  The offset of the root subform is relative to the content area.  To find an absolute page coordinate, you could (in theory) traverse up the hierarchy and add the x coordinate of each ancestor plus the x coordinate of the content area.  But the APIs don’t make this easy.  First of all, it’s not obvious which content area contains your object.  And if your ancestor subform originated on a different page, it will have a different offset parameter.  Finding an absolute page position is complicated.  But not impossible.

Parameters

The layout position methods all take the same set of parameters.
e.g. xfa.layout.x(<object>, <units>, <offset>);

object is self evident.  You pass in a reference to some container (field, subform, draw etc).

units is also easy. "in" will cause the function to return a numeric value representing inches.

offset is … complicated.  The documentation for xfa.layout.x() says:

"An integer representing the number of pages to offset the x coordinate of the object, beginning with the first page the object occurs on. If left blank, the default value is 0."

This is partially true.  It is true only in the case where there is one content area per page.  The full truth is more complicated.  The documentation for xfa.layout.h() says:

"An integer representing the amount to offset the height value of a form design object, beginning with the first page the object occurs on. If left blank, the default value is 0."

Hmm.  Not true at all.  It’s not an offset of the height value.  Someone forgot to copy/paste the part of the description that says "number of pages".  We’ll work toward a more accurate description in the next section…

Multiple Content Areas

For forms with multiple content areas, here is the actual behaviour of the offset parameter:

The offset parameter indicates which relative content area the object appears in.  e.g. If a subform spans 2 pages and 5 content areas, you can call xfa.layout.h with offset values of 0, 1, 2, 3, 4 to return the the height of the object in each of the 5 content areas. 

This is great but, now the problem becomes: how do we find out which actual content area the object is in?  You can’t assume that the object appears in each content area on a page.  A subform with an explicit overflow target might skip a content area on any given page.

Fortunately, there is a heuristic that allows us to infer the content area in which an object appears. 
When you make a call to: xfa.layout.pageContent(0, "", false); you get a list of all objects on a page — including the content area objects.  If a field or subform appears in more than one content area on a page, that object will appear multiple times in the returned list.  The list appears in layout-order. Content area objects appear before the objects that have been placed in that content area.  To find out which content area an object appears in, look back up the list to find the most recent content area.

Finding Absolute Coordinates

Finding the x coordinate of an object involves adding the x coordinates of all the ancestors plus the x coordinate of the content area.  But, as mentioned above, the ancestor subforms may have originated in a different content area.  In order to add up the x coordinates, you have to find out the offset value of your parent object in this content area.  The script code to figure this all out is pretty complex.  I’ve put all the complexity under a script object that you can call:

// This function returns an array of extents for a given object
// There will be one entry in the array for each content area
// an object appears in.
// Each entry is an object with these properties:
//   Extent.page
//   Extent.contentArea  (SOM expression)
//   Extent.x            (absolute page position)
//   Extent.y
//   Extent.w
//   Extent.h  
// All measurements are inches.
function getExtents(vObject)

I have attached a sample form with multiple pages, multiple content areas, and which displays the results of querying layout positions.

Application

Once you’re able to find page positions, how can you use that information? Placing transpromo content is the best example.  I have also developed another sample where I used a different kind of field highlighting.  For each field error, the form places a "highlighter" subform on the master page (the highlighter is an arrow).  When you open the form, click on the "highlight errors" button.

I have another purpose in mind, but that’s the topic for a future blog post…

XFA 3.0: Viewer Preferences

The configuration section of XFA 3.0 has new options for controlling viewer preferences in the PDF files that we generate.

One common request we have heard from users is that they want control over how the user can print documents from Reader.  Specifically, they want to:

  1. Set the default print scaling option to "none"
  2. Disallow the user from changing the scaling option
  3. Choose paper source by PDF page size

In the configuration file, the current viewerPreferences section looks like this:

<viewerPreferences>
<addViewerPreferences/> <!-- 0|1 -->
<duplexOption/> <!-- simplex|duplexFlipShortEdge|duplexFlipLongEdge -->
<numberOfCopies/>
<pageRange/>
</viewerPreferences>

In XFA 3.0 we added three new elements, <printScaling/>, <enforce/> and <pickTrayByPDFSize/> as children of the viewerPreferences element.

<printScaling/> supports the values noScaling or appDefault.  This setting controls the initial value of the ‘Page Scaling’ drop down list that appears in the Reader print dialog. The application default is: "shrink to printable area".  With this setting, we can change the default to "none" (noScaling).

<enforce/> is a space delimited string of viewer preferences to enforce.  By "enforce", we mean setting preferences that the Reader user is not allowed to change. Currently, the PDF spec supports enforced print scaling in Acrobat 9.0 or later. Therefore this list supports only the value ‘printScaling‘.  In the future if/when PDF supports more enforceable preferences, we will expand this list accordingly.

<pickTrayByPDFSize/> is a boolean flag that specifies whether the PDF page size is used to select the input paper tray. When set to 1 (true), the check box in the Reader print dialog associated with the input paper tray is checked. The default is 0 (false), in keeping with the previous behaviour of our products.

This is the last blog entry describing XFA 3.0 enhancements.  It will be a while before we deliver Designer and LiveCycle products that support all the new options (no, I can’t quote specific dates).  Until then, I’ll get back to delivering samples that work with earlier versions of Reader.

Click to Win!

As the readership of this blog goes up, I feel it is only right that the readers be rewarded.

By opening and completing the attached PDF, you get your chance to claim a reward*

 

Interesting xfa.event properties

Today’s sample has some interesting script code that makes use of xfa.event properties during the change event.  The change event on the Amount field shows how you can make use of the change, selStart and selEnd properties.  selStart and selEnd tell you the start and end positions of where the input (represented by xfa.event.change) will be inserted in the old text (xfa.event.prevText).  When no text is selected, selStart and selEnd have the same value.  When the user has highlighted/selected several characters of text, selStart and selEnd will point to the start and end of that range.  In the Amount field of the sample, the change event script modified selStart and selEnd so that the users input would always be inserted to the right of the decimal — preventing them from entering a value greater than 1.

This can also have a more useful application.  For example, the widgets on PDF forms do not support overstrike mode.  You can implement overstrike by adjusting selEnd to include the character to the right of the insert point.  This sample script will cause a field to be in overstrike mode:

 myForm.test[0].testField::change – (JavaScript, client)
// Don’t overstrike if the <control> key is down — likely means they’re doing a paste
if (!xfa.event.modifier)
{
  // Don’t overstrike if they’ve selected text (selStart != selEnd)
  if (xfa.event.selStart == xfa.event.selEnd)
    xfa.event.selEnd++;
}

Happy April.

* Assuming that you have also entered some other contest that gives you a chance at winning a prize.