Your prescription for increased productivity and profitability
You have a number of items in a document that you want to space out evenly. Of course, you know how to go into Indesign’s align panel and check the appropriate options. But what if you were to want to include distribution as part of a script? The syntax for distribute is not difficult, but there are a lot of options involved. So let’s explore.
The following discussion assumes that you have some items in a document selected and you want to distributed them. To keep things simple, the script will assume there is an active document with page items selected.
For AppleScript, the distribute method is found in the basics suite for its Dictionary. (From within the File menu item for AppleScript Editor, select Open Dictionary…. Select the version of InDesign you are using—hopefully it is InDesign CC 2015.)
As you will see by its listing in the Dictionary, the distribute method uses the document and requires a reference to the page items selected. With this, the method needs a value for a distribute option and one for align distribute bounds. The values for distribute option should be easy to understand. For this example, the script will use horizontal centers. Align distribute bounds defines the area (bounds coordinates) within which the distribution will be calculated.
tell application "Adobe InDesign CC 2015" set docRef to document 1 set selList to selection if length of selList > 1 then tell docRef distribute align distribute items selList distribute option horizontal centers ¬ align distribute bounds item bounds end tell end if end tell
Once you get the basic syntax to work, you will want to experiment. Test the script with the value for distribute option set to horizontal space. Change the value for align distribute bounds to page bounds, then margin bounds.
Closely associated with distribute is the align method. The code for aligning page items is similar to distribute using the align method instead of distribute. Align takes similar arguments: align distribute items, align options, and optional align distribute bounds. Below are some statements you can use for testing. Use these in place of the distribute statements in the script above.
align align distribute items selList align option vertical centers align distribute bounds item bounds align align distribute items selList align option top edges align distribute bounds item bounds
If you want to control the amount of space between page items when they are distributed, the use distribute measurement and absolute distribute measurement options come into play. For this you need to make sure the measurement defined for the script is the same as that set for the document. InDesign gives us the script preference measurement unit.
set gutterWid to 12 tell application "Adobe InDesign CC 2015" set measurement unit of script preferences to points set docRef to document 1 set selList to selection if length of selList > 1 then tell docRef distribute align distribute items selList distribute option horizontal space ¬ absolute distribute measurement gutterWid with use distribute measurement end tell end if end tell
If you are not already familiar with the behavior of the distribute method, you may discover with your tests that use distribute measurement with absolute distribute measurement shifts all of the items relative to the leftmost or topmost item. Similar behavior is is true for align when using top edges and left edges. Right edges and bottom edges shift the items in the opposite directions.
But what if you want your page items to distribute or align to any arbitrary item? For this InDesign has the key object reference which is used with the align distribute bounds option.
So just what is a key object? The key object of a selection can be set manually when the selection is made. Surround the items to be selected. Once you have a selection, click on the item to which you want to reference the alignment (or distribution). The item takes on a wider border to indicate it is the selection key object.
Script documentation for Versions 6.0 and above of InDesign shows support for the key object for both alignment and distribution. It is set using key object for the align distribute bounds option. Your script can verify if a selection key object exists in a script. With page items having a key object selected, run the following script:
tell application "Adobe InDesign CC 2015" set keyObj to missing value tell application "Adobe InDesign CC 2015" set selList to selection if length of selList > 1 then set keyObj to selection key object end if end tell end tell {selList, keyObj}
The result should be a list of the items selected plus a reference to the selection key object. In this example, if the selection key object has not been designated, the value of keyObj will be nothing.
From a script prospective, the selection key object can be set using one of the following:
--using a reference to an item in the selection list tell application "Adobe InDesign CC 2015" set selList to selection if length of selList > 1 then if selection key object is nothing then set selection key object to item 2 of selList end if set keyObj to selection key object end if end tell
(*using a name reference; assuming a page item has been named. In this example the item is named "keyRect"*) try tell application "Adobe InDesign CC 2015" set selList to selection if length of selList < 2 then error "Requires more than 1 page item selection" end if if selection key object is nothing then set keyObj to a reference to ¬ (every item of selection where name of it is "keyRect") if keyObj is {} then set keyObj to missing value if keyObj is missing value then error "No selection key object is identified" end if set selection key object to keyObj else set keyObj to selection key object end if end tell on error (errStr) activate display alert ("Error: " & errStr) end try
Note: This script throws an error if no selection key object is identified.
Using the key object for align distribute bounds should allow the item to be used to reference a distribution or alignment. However if you try the following script you will discover an error.
try tell application "Adobe InDesign CC 2015" set docRef to document 1 set selList to selection if length of selList > 0 then set selItem to selection key object tell docRef to align align distribute items selList align option top edges align distribute bounds key object end if end tell on error (errStr) activate display alert errStr end try
The problem reported is a missing value for a reference parameter. But, to date, I have not discovered a way to add a selection key object reference to the method in AppleScript.
On the other hand, the same script written in ExtendScript (with the reference parameter) runs like a charm. Note: If there is no selection key object, no alignment will take place.
try { var docRef = app.documents.item(0); var selList = app.selection; if (selList.length > 1) { var selItem = docRef.selectionKeyObject; if (!!selItem) { //yes that's 2 'nots' docRef.align (selList, AlignOptions.TOP_EDGES, AlignDistributeBounds.KEY_OBJECT,selItem); } } } catch (e) { alert ("Error " + e); }
The missing reference problem exists when using the distribute method with a key object reference in AppleScript. However the ExtendScript version works without a problem.
var gutterWid = 10; app.scriptPreferences.measurementUnit=MeasurementUnits.POINTS; try { var docRef = app.documents.item(0); var selList = app.selection; if (selList.length > 1) { if (docRef.selectionKeyObject) { var selItem = docRef.selectionKeyObject; } else { var selItem = selList[1]; } docRef.distribute (selList, DistributeOptions.HORIZONTAL_SPACE, AlignDistributeBounds.KEY_OBJECT,false,gutterWid,selItem); } } catch (e) { alert ("Error " + e); }
You will notice that the script assumes there is an active document and that the selection is one of page items rather than text. To make the script bullet-proof you will need to add a routine to make sure the selection is as needed by the script.
If you need to write the rest of your production script in AppleScript and you want to use the key object reference, you can use do script to call the align or distribute method written in JavaScript. Otherwise, you will need to use a workaround similar to the one below that, as written, is limited to working with a horizontal distribution.
To get around the key object problem, the Workaround uses item Bounds for align distribute bounds. If the key object exists, its bounds is used to adjust the position of the page items. Notice that here we use a handler to test the selection.
set gap to 10 try set keyObject to missing value tell application "Adobe InDesign CC 2015" tell script preferences to set measurement to millimeters set docRef to active document --set selList to my pageItemsSelected() tell docRef set selList to selection if length of selList > 2 then set testItem to selection key object if testItem is not nothing then set keyObject to testItem set origBounds to geometric bounds of keyObject end if distribute align distribute items selList distribute option horizontal space align distribute bounds item bounds absolute distribute measurement 5 with use distribute measurement end if end tell if keyObject is not missing value then --using original bounds of selection key object to move selected items set newBounds to geometric bounds of keyObject if (item 2 of newBounds > item 2 of origBounds) then set relMove to (item 2 of newBounds) - (item 2 of origBounds) else set relMove to (item 2 of origBounds) - (item 2 of newBounds) end if move selList by {relMove, 0} end if end tell on error (errStr) activate display alert "Error: " & errStr end try (*Returns reference to page items selected if more than 1; otherwise throws error*) on pageItemsSelected() set errStr to "Requires a selection of 2 or more page items" tell application "Adobe InDesign CC 2015" set selList to selection if length of selList < 2 then error errStr end if set selItem to item 1 of selList if class of selItem is not in {text frame, rectangle, oval, polygon} then error errStr end if end tell return selList end pageItemsSelected
To make this script complete add code to support vertical distribution.
Notice how this script tests for the selection key object. It is important to note that even if your script does not use the key object reference, if the user has created a selection key object, your script may error if you do not provided a test. Set the value of selection key object to nothing if needed to avoid this error.
Of course, the advantage of having code will be realized when it is part of a script designed to do more than just align or distribute objects. Consider a script that will adjust the width of items and then distribute them with equal space between. The following example uses grouping to get the left and right bounds of the combined items. It then changes the width of the items to fill the area with a designated space (gutter) between.
try { var gutterWid = 10; var validTypes = ["TextFrame", "Rectangle", "Oval", "Polygon"] app.scriptPreferences.measurementUnit=MeasurementUnits.POINTS; var selList = app.selection; var numItems = selList.length; var curItem; var iBounds; var curHeight; if (numItems > 1) { var docRef = app.documents.item(0); var selType = selList[0].constructor.name; if (! inArray (selType, validTypes)) { throw ("Requires selection of more than one page items"); } var keyObj = docRef.selectionKeyObject; if (keyObj == null) { alert ("key object is null"); keyObj = selList[1]; } var tempGroup = docRef.groups.add(selList); var gBounds = tempGroup.geometricBounds; var groupWid = gBounds[3] - gBounds[1]; tempGroup.ungroup(); var itemWid = (groupWid - ((numItems - 1) * gutterWid))/numItems; for (var i = 0; i < numItems; i++) { curItem = selList[i]; iBounds = selList[i].geometricBounds; curHeight=iBounds[2] - iBounds[0]; curItem.resize(CoordinateSpaces.innerCoordinates, AnchorPoint.centerAnchor, ResizeMethods.replacingCurrentDimensionsWith, [itemWid, curHeight]); } docRef.distribute (selList, DistributeOptions.HORIZONTAL_SPACE, AlignDistributeBounds.KEY_OBJECT,false,gutterWid,keyObj); } catch (e) { alert ("Error: " + e); } function inArray (selType, validTypes) { var count = validTypes.length; for (var i = 0; i < count; i++) { if (validtypes[i]===selType)"{ return true; } } return false; }
Disclaimer: Script examples are written for demonstration purposes only. Readers are encouraged to use the example code in their own production scripts, but no representation is made that the code is error-free. Make sure your scripts trap for any potential errors.