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…