UPDATE (14-Mar-2017). — Added the option “Preserve threaded text frames” as suggested by Colin Flashman and Tim Gouder. Turn it on to make sure CleanupPasteboard will not remove any text frame which belongs to a story thread.


Here is a cool script suggested by Rasmus Olsen a few days ago in the InDesign Scripting Forum. The basic purpose is to clear out the temporary components left out of the document bounds. The area to clean up can be extended or restricted via the user interface (see the animation below).

Since the main operation to perform is simple beyond expectations, let's inspect closely the side issues and the underlying objects.

Pasteboard and Spread

WARNING (15-Mar-2017). — This section was relevant in CS3/CS4—at time the original article was released (2009.) Many things have changed from then. In particular, it is no longer true that the parent of a top level PageItem is either a Page or a Spread. Today the Spread object is always the parent of any top level component, whatever its position in the layout, either inside or outside of the page area. So the following lines remains here for historical purpose only.


The most important thing you need to know about the pasteboard abstraction is that, in fact, the InDesign JavaScript DOM doesn't have any Pasteboard object. A document page-item always resides on a Spread, even when its center point oversteps the visible bounds. The Application and the Document objects have a pasteboardPreferences property, which links to a PasteboardPreference object. In it, you will find the minimumSpaceAboveAndBelow property — storing the “minimum space above and below a page”— and some incidental settings. Interestingly, the InDesign SDK API provides a IPasteboard class (‘I’ for ‘Interface’) used to “access the geometry of the document's pasteboard.” The pasteboard is in no way a container, it only provides the base of the coordinate system (Pasteboard coordinate space), as illustrated in the SDK Programming Guide:

Pasteboard coordinates of spread and page bounding boxes

Thus, when we talk about cleaning up the pasteboard, the actual area we refer to is the outer space of the pages. We access this space from the corresponding spread. A Spread object can be seen as a set of one or more adjacent pages. It is also a ‘meta-container’, indexing all objects placed within or out of the page bounds. In other words, the spread is responsible for the page-items which are not visible from a page, the ones the user experiences as the ‘pasteboard items.’ The key fact is that a spread sees both the inner page objects and the outer ones. Suppose you've created a single Rectangle on the active page. Now, duplicate it out of the page and read the meter: mySpread.pageItems.length says 2, while myPage.pageItems.length keeps to 1.

From this point, the question is: How to get exclusively the ‘pasteboard items’? The answer comes from the object hierarchy. Considering a top-level page-item —say myPageItem— relying out of any page, the myPageItem.parent property will refer to the Spread —or MasterSpread— container. So if you need to retrieve the pasteboard items of the active document, use something like this:

// Retrieving spread items in CS4
// (Works no longer in CS6 and later.)
 
var pasteboardItems = [],
    pItems = app.activeDocument.pageItems.everyItem().getElements(),
    i,p;
 
while( i=pItems.pop() )
    {
    p = i.parent.constructor;
    if ( p == Spread || p == MasterSpread )
        {
        pasteboardItems.push(i);
        }
    }
 
alert(pasteboardItems.length + " object(s) are on the pasteboard");
 

Working with ‘Bounds’ and Units

The CleanupPasteboard script has been extended to slug, bleed and “custom offset” areas. A custom offset allows the user to set a “thinking distance” around the page bounds. As a result, we cannot re-use the pasteboard-items approach as it. We must deal with object and page bounds, measurement units and measurementEditboxes.

Although a Spread object is considered as the geometric union of one or several page(s), the native DOM provides no bounds property at this level. If you want to get this functionality in your own scripts, it's easy to prototype a surrogate method based on the page bounds:

Spread.prototype.bounds =
MasterSpread.prototype.bounds =
function()
{ // return the [top,left,bottom,right] bounds of this spread
var bFirst = this.pages.item(0).bounds; // bounds of the first page
var bLast = this.pages.item(-1).bounds; // bounds of the last page
return [ bFirst[0], bFirst[1], bLast[2], bLast[3] ];
}
 
// sample usage
alert( app.activeWindow.activeSpread.bounds() );
 

Our script builds a similar function —see extraBounds inside the Document.prototype.cleanout method,— except that the offset parameter is injected during the calculation.

Another important scripting aspect is that the bounds property always returns the top/bottom values in the current vertical measurement units, and the left/right values in the current horizontal measurement units, depending on the UI context. (At the page-item level, geometricBounds/visibleBounds properties work in the same way.) However, the MeasurementEditbox/MeasurementCombobox controls have a specific behavior: they display the field datas (editContents) in the custom measurement unit specified by the editUnits property, but they interpret the value (editValue) in points. Because of localization and type conversion issues, parsing the editContents string by your own means may be unsafe. So, it is often better to deal with editValue, subject to reconvert the value from points to the required measurement units.

Here is a generic approach using the UnitValue core Javascript class:

var UNITS = (function()
    {
    var r={}, mu=MeasurementUnits;
    r[mu.AGATES]='agt';
    r[mu.CENTIMETERS]='cm';
    r[mu.CICEROS]='ci';
    r[mu.INCHES]='in';
    r[mu.INCHES_DECIMAL]='in';
    r[mu.MILLIMETERS]='mm';
    r[mu.PICAS]='pc';
    r[mu.POINTS]='pt';
    return(r);
    })();
 
// convert myPointsValue from pts to myMeasurementUnits
var uv = UnitValue(myPointsValue, 'pt');
var convertedValue = uv.as(UNITS[myMeasurementUnits]);
 

The CleanupPasteboard script demonstrates this mechanism in the ViewPreference.prototype.toOffsets() method.

CleanupPasteboard Demo

CleanupPasteboard Demo