DRAWING FROM CENTER

When using a script to create an object, one technique is to define the center point of the object and calculate geometric bounds from this point. The advantage is that the object can then be “drawn” anywhere on the page simply by changing the geometric coordinates for the center point. In this post we will explore this technique from the point of view of writing a script for InDesign using AppleScript.

Define a Page in a Document

First you will need the script to define a page within a document. One way is to use a reference to the active spread of the active window. In the following, we trap the error in the event there is not an active window.

try
tell application "Adobe InDesign CC 2019"
    set myPage to page 1 of active spread of active window
end tell
on error errStr
    activate
    display alert "Error: " & errStr
end try

With the page defined, the script will need to know where within the page to create the item. For example, the center point of the page could be used. Because the script will be doing mathematical calculations, the measurement unit to be used will need to be defined.

MEASUREMENT UNITS

One way to define measurement units is to use view preferences. For this, the current horizontal and vertical measurement units for the document’s view preferences is saved to a variable, and then changed. Later, the variable is used to restore the values when the script is done. You will see this technique used in a number of the scripts provided with InDesign. (See CropMarks.applescript as an example.) The advantage of using view preferences is that you can set different measurement units for vertical and horizontal. A simpler method is to use script preferences. The measurement unit for script preferences affects both dimensions but this only affects script processes. We will use this method.

tell application "Adobe InDesign CC 2019"
    set measurement unit of script preferences to inches decimal
end tell 

PAGE ITEM CENTER PAGE

For demonstration, let’s write a script to create a page item in the center of the page. First, the script needs to define the page’s center x and y coordinates. (We’ll ignore error trapping at this stage.)

Calculate Center

tell application "Adobe InDesign CC 2019"
     set measurement unit of script preferences to inches decimal
     set pageRef to page 1 of active spread of active window
     set gBounds to bounds of pageRef
     set myOrigin to {0, 0}
     set myCenter to my getCenter (gBounds, myOrigin)
end tell
(*Returns center point given bounds and list of x/y offsets from origin*)
on getCenter (gBounds, myOrigin)
    copy gBounds to {y0, x0, y1, x1}
    copy myOrigin to {ox, oy}
    set cx to (x1 - x0)/2 + ox
    set cy to (y1 - y0)/2 + oy
    return {cx, cy}
end getCenter

In case you are wondering why the need to define the myOrigin variable: It is to allow extendability. It provides the opportunity to consider page margins in determining page center. Or, perhaps to use the getCenter handler for instances when you might want to create a page item in the center of another page item that could be located anywhere on the page.

Create Page Item

Now that you have the center point defined, you can create the page item using the following handler:

(*Creates page item given page reference, center point, page item type and parameters*)
on pgItemFromCenter(pageRef, myCenter, itemType, itemWid, itemHgt, objStyleRef)
    set halfWid to itemWid / 2
    set halfHgt to itemHgt / 2
    copy myCenter to {cx, cy}
    set gBounds to {cy - halfHgt, cx - halfWid, cy + halfHgt, cx + halfWid}
    tell application "Adobe InDesign CC 2019"
	tell pageRef
	    set pageItemRef to make itemType with properties {geometric bounds:gBounds, applied object style:objStyleRef}
	end tell
    end tell
    return pageItemRef
end pgItemFromCenter

Change the top portion of the script to read as follows:

set itemWid to 4
set itemHgt to 3
tell application "Adobe InDesign CC 2019"
     set measurement unit of script preferences to inches decimal
     set pageRef to page 1 of active spread of active window
     set gBounds to bounds of pageRef
     set myOrigin to {0, 0}
     set myCenter to my getCenter (gBounds, myOrigin)
    set itemType to rectangle
    set objStyleRef to object style 2 of document 1
    set pgItemRef to my pgItemFromCenter(pageRef, myCenter, itemType, itemWid, itemHgt, objStyleRef)
end tell

Make sure your script includes the getCenter handler.

JUST FOR FUN

To demonstrate the advantage of creating a page item from center we will be brave and use a random set to define the center point. Using random number, the script will define a random location within the confines of the page.

The center point for our page item can be defined by passing the page’s bounds to a handler that, given minimum and maximum values, returns a random number.

Random Center Point

tell application "Adobe InDesign CC 2019"
    set measurement unit of script preferences to points
    set pageRef to page 1 of active spread of active window
    copy bounds of pageRef to {py0, px0, py1, px1}
    set cX to my randomNumber (px0, px1)
    set cY to my randomNumber (py0, py1)
end tell

At the bottom of the script add the following handler:

(*Returns a rounded random number given minimum and maximum allowed values*)
on randomNumber(minValue, maxValue)
    set rNumber to random number from minValue to maxValue
    set myRound to round (rNumber)
    return myRound
end randomNumber

Make sure you have a document open and test for the values of cX and cY at the bottom of the script. So far, so good.

Create Page item Using Random

Now let’s see how using create from center works for “drawing” a circle using the random coordinates returned from the randomNumber handler. Using our pgItemFromCenter handler, the top of the script can now read as follows (notice measurement units are now in points):

set itemWid to 144
set itemHgt to 144
tell application "Adobe InDesign CC 2019"
    set measurement unit of script preferences to points
    set pageRef to page 1 of active spread of active window
    copy bounds of pageRef to {py0, px0, py1, px1}
    set itemType to oval
    set objStyleRef to object style 2 of document 1
    set cx to my randomNumber (px0 + itemWid/2, px1 - itemWid/2)
    set cy to my randomNumber (py0 + itemHgt/2, py1 - itemHgt/2)
    set pageItemRef to my pgItemFromCenter (pageRef, {cx, cy}, itemType, itemWid, itemHgt, objStyleRef)
end tell

Make sure your script includes the following handlers: randomNumber and pgItemFromCenter.

Now for the fun. Test your script with a document open. If all goes well, add a repeat loop to create a number of circles at random locations on the page. Get creative. Modify the script to have circles of random sizes. What about random fill colors? Hmmmmm.

ONWARD AND UPWARD

For the scripts above: add a try/on error trap to catch any errors. Add a user dialog handler to the script to have the user define the values needed for the script (item type, item height, item width, object style name). For the Page Item Center Page script add an enabling group for setting myOrigin (offsets for horizontal and vertical). For the Just for Fun script, add an integer editbox for the number of times to repeat. Here’s a handler to get you started. Change the top of your scripts to use the values returned from the user dialog for creating your page item(s) and call the pgItemFromCenter handler.

set dlgName to "Page Item Preferences"
set cancelIt to true
set itemTypeList to {"rectangle", "oval", "text frame"}
set objStyleList to {"[Basic Graphics Frame]", "[Basic Text Frame]"}
set userResponse to userDialog(dlgName, cancelIt, itemTypeList, objStyleList)
(*Provides widgets for choosing item type and style along with width and height*)
on userDialog(dlgName, cancelIt, itemTypeList, objStyleList)
    tell application "Adobe InDesign CC 2019"
	activate
	set wasCancelled to false
	set origLevel to user interaction level of script preferences
	set user interaction level of script preferences to interact with all
	set dlgRef to make dialog with properties {name:dlgName, can cancel:cancelIt}
	tell dlgRef
	    tell (make dialog column)
		tell (make dialog row)
		    make static text with properties {static label:"Page Item Type"}
		    set itemField to make dropdown with properties {string list:itemTypeList, selected index:0}
		end tell
		tell (make dialog row)
		    tell (make dialog column)
			make static text with properties {static label:"Width (inches)"}
			set widField to make measurement editbox with properties ¬
{edit units:inches, min width:72, minimum value:72, maximum value:720}
		    end tell
		    tell (make dialog column)
			make static text with properties {static label:"Height (inches)"}
			set hgtField to make measurement editbox with properties ¬
{edit units:inches, min width:72, minimum value:72, maximum value:720}
		    end tell --column
		end tell --row
		tell (make dialog row)
		    make static text with properties {static label:"Object Style"}
		    set styleField to make dropdown with properties {string list:objStyleList, selected index:0}
		end tell --row
	    end tell --column
	end tell --dlgRef
	set userResponse to show dlgRef
	if userResponse is true then
	    set itemTypeIndex to ((selected index of itemField) + 1)
	    set itemWid to edit value of widField
	    set itemHgt to edit value of hgtField
	    set objStyleIndex to ((selected index of styleField) + 1)
	else --user cancelled
	    set wasCancelled to true
	end if
	destroy dlgRef
	set user interaction level of script preferences to origLevel
	--if cancelled, throw error; otherwise return values
	if wasCancelled then error "User cancelled"--single line if statement
	return {itemTypeIndex, itemWid, itemHgt, objStyleIndex}
    end tell
end userDialog

One thing to keep in mind when using measurement editboxes: the values for minimum and maximum values as well as the value returned is always in points no matter the value set for edit units.

Disclaimer:
Scripts provided are for demonstration and educational purposes. No representation is made as to their accuracy or completeness. Readers are advised to use the code at their own risk.