ALIGN AND DISTRIBUTE

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.

DISTRIBUTE

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.

Distribute

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.

ALIGN

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 itemsalign 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

BUT THERE IS MORE

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.

KEY OBJECT

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.

Establishing a Selection Key Object

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.

ExtendScript

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.

AppleScript

   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. 

ONWARD AND UPWARD

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.

Resize and Distribute

   
   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.