Automating Adobe InDesign 2019 - AppleScript


Repeat to the Rescue

Tuesday, September 17, 2019

Repeat to the Rescue

Few are the scripts that can be written that don't use a repeat loop. Imagine what writing a script would be without repeats: there would need to be a statement written for each item to be processed. The number of times statements are processed depends on the repeat. The most simple is to repeat a number of times:

Repeat aNumber Times

This repeat is used when the process within the loop does not change.

repeat aNumber times  --aNumber is an integer
--statements to repeat
end repeat

There are few places where this repeat structure can be used. More often a counting variable is used to determine the number of loops. This variable can be used to make some change within the loop.

Counting Loops: Repeat with

One place a counting loop is used to advantage is in adding incremented numbers to text. In the following the variable i is used as a counting variable. Why the letter i? Some say it represents the word integer, but for whatever reason it is probably the most used variable on the planet.

Adding numbered text to the beginning of paragraphs.

set textStr to "This is line 1" & return & "This is line 2" & return & "This is line 3"
set baseStr to "Item"
tell application "Adobe InDesign CC 2019"
   tell text frame 1 of page 1 of document 1
      set contents to textStr
      repeat with i from 1 to count of paragraphs
	 set insertion point 1 of paragraph i to baseStr & i & ": "
      end repeat
   end tell
end tell

Try this with text placed from a text file.

When the value of a variable within the loop is not incremented or cannot be calculated, a list of values is used. As an example you may remember how a list was used to control where guides were created on a master page in the BizCard_6up script.

masterGuides handler (partial listing)

set vList to {11, 23, 25, 34}
tell application "Adobe InDesign CC 2019"
set docRef to document 1
end tell
masterGuides (docRef, vList)
(*Creates guides on page 1 of master spread 1 using position values in vlist and hList*)
on masterGuides(docRef, vList)
   tell application "Adobe InDesign CC 2019"
	set pageRef to page 1 of master spread 1 of docRef
	tell pageRef
	   repeat with i from 1 to length of vList
		make guide with properties {guide type:ruler, location:item i of vList, orientation:vertical}
	   end repeat
	end tell
   end tell
   return pageRef
end masterGuides

Calculating Bounds

A repeat loop is often used when working with geometric bounds. For instance, to calculate the width of any number of same-size items within a given space the following formula is used:

itemWidth = (availableSpace - (numberItems - 1) * gutterWidth) / numberItems

Of course, the same formula can be used to calculate the height of same-size items vertically.

The following calculates the width of columns within the width of a page and then creates items using the calculation

set gutter to 12
set numCol to 2
tell application "Adobe InDesign CC 2019"
   set measurement unit of script preferences to points
   tell page 1 of document 1
	copy bounds to {py0, px0, py1, px1}
	set avail to px1 - px0
	set itemWid to (avail - (numCol - 1) * gutter) / numCol
	set x0 to px0
	repeat with i from 1 to numCol
	   set x1 to x0 + itemWid
	   make rectangle with properties {geometric bounds:{py0, x0, py1, x1}}
	   set x0 to x1 + gutter
	end repeat
   end tell
end tell
itemWid

You might want to experiment with this by changing the value of numCol.

Working with a Grid

Using the formula above a repeat loop can be used to return a list of the geometric bounds for items to be created in a grid. Here the grid will fit within the margins of the page.

GridBounds

set gutter to 12
set gap to 12
set numCol to 2
set numRow to 2

tell application "Adobe InDesign CC 2019"
   set measurement unit of script preferences to points
   tell page 1 of document 1
	set pageBounds to bounds
	tell margin preferences
	   set pageMargins to {top, left, bottom, right}
	end tell
	set boundsList to my gridBounds(pageBounds, pageMargins, numCol, numRow, gutter, gap)
	repeat with i from 1 to length of boundsList
	   make rectangle with properties {geometric bounds:item i of boundsList}
	end repeat
   end tell
end tell

(*calculates geometric bounds for items within the margins of a page*)
on gridBounds(pageBounds, pageMargins, numCol, numRow, gutter, gap)
   set boundsList to {}
   copy pageBounds to {py0, px0, py1, px1}
   copy pageMargins to {mTop, mLft, mBot, mRgt}
   set availW to px1 - (px0 + mLft + mRgt)
   set itemWid to (availW - (numCol - 1) * gutter) / numCol
   set availH to py1 - (py0 + mTop + mBot)
   set itemHgt to (availH - (numRow - 1) * gap) / numRow
   set x0 to px0 + mLft
   set y0 to py0 + mTop
   repeat with i from 1 to numRow
      set y1 to y0 + itemHgt
      repeat with j from 1 to numCol
	 set x1 to x0 + itemWid
	 set end of boundsList to {y0, x0, y1, x1}
	 set x0 to x1 + gutter
      end repeat
      set x0 to px0 + mLft
      set y0 to y1 + gap
   end repeat
   return boundsList
end gridBounds

The gridBounds handler needs a little explanation as it uses a nested repeat loop. First, it determines the size of the items within the grid using the formula from above. Outside the repeat loop it establishes the values for the first top (y0) and left (x0) coordinates. Within the outside loop (the i loop) it sets the value for the bottom (y1) coordinate. The horizontal coordinate for x1 is then calculated within the inner loop (the j loop). With all four coordinates defined the list is added to the boundsList. The x0 and x1 values continue to be set within the j loop until the number of columns is reached.

When the inner (j loop) completes the x0 and y0 values for the first item in the next row is established (the x0 value is reset and gap added to y1 to create the y0 value). The y1 value is calculated with the next iteration of the i loop. The j loop follows to create x1, x0 values and all four values added to the boundsList. This repeats until the number of rows completes.

To finish the script, the page creates rectangles by repeating through the list of lists (boundsList).

Making Handlers Extensible

How functionality is divided between handlers and the main section of a script is a matter of choice. To make the gridBounds handler more usable, you could calculate the itemWid and itemHgt values in the top portion of the script and pass these values to the handler. Because our BizCard_6up script started with values for the grid items predefined, the getBounds handler could be simplified. The interesting thing about this project is that the document size was determined to cut out 6 cards evenly when margins, gutter, and gap are all 2 picas. Check the math: (the sample document was 48 picas by 44 picas).

itemWid = (48 - 4 - (1 * 2))/2
itemHgt = (44 - 4 - (2 * 2))/3

Here's a challenge: what would the values for gutter, gap, and margins have to be to cut 8 cards out evenly from letter size (8-1/2 x 11) stock?

  • Answer: left and right margins: 3p0
  • top and bottom margins: 4p6
  • Gutter and Gap 3p0

The problem with the getBoundslist handler in the BizCard_6up script is that it assumes the top and left margins will be the same as the gutter and gap. This can be a very dangerous assumption. Instead, it would be better to pass the top margin and left margin values to the handler as part of its call (this could be part of the gridList variable).

With the sizes for the items in the grid defined, the following will create the boundsList for an 8-up business card (assumes the document is 8-1/2 x 11 with margins top and bottom:4.5 picas, left and right: 3 picas).

--measurements are in picas
set gridList to {4, 2, 3, 3, 21, 12, 4.5, 3} --rows, cols, gut, gap, wid, hgt, top margin, left margin}
set marList to {4.5, 3, 4.5, 3}
set presetName to "BizCard_8up"
(*Parameters used for creating the document preset defined in the propList variable: 
page width, page height, marginList (marList), facing pages, master frame, number pages, columnCount*)
set propList to {51, 66, marList, false, true, 1, 1}
set boundsList to getBoundsList (py0, px0

(*Returns a list of lists for creating a grid of page items.*)
on getBoundsList(gridList)
   set boundsList to {}
   copy gridList to {nRows, nCols, gut, gap, wid, hgt, py0, px0}
   set y0 to py0
   set x0 to px0
   repeat with i from 1 to nRows
      set y1 to y0 + hgt
      repeat with j from 1 to nCols
	 set x1 to x0 + wid 
	 set end of boundsList to {y0, x0, y1, x1}
	 set x0 to x1 + gut
      end repeat
      set x0 to px0
      set y0 to y1 + gap
   end repeat
   return boundsList
end getBoundsList 

TestList Script

To test the above make sure you have a document open set as above and add the following to the top of the script:

set wordList to {"One", "Two", "Three"}
tell application "Adobe InDesign CC 2019"
   set measurement unit of script preferences to picas
   set pageRef to page 1 of document 1
   tell pageRef
      repeat with eachItem in boundsList
	 make text frame with properties {geometric bounds:eachItem} 
      end repeat
      repeat with i from 1 to length of wordList
         set insertion point 1 of text frame i to item i of wordList
      end repeat
      set testit to geometric bounds of text frame 1
   end tell
end tell
testIt

Take note of the first repeat loop above. It uses a variable instead of a loop counter. The second repeat loop uses an incrementing variable (i) to add words to some of the text frames. Try it.

Stacking Order

When you ran the TestList script were you surprised to see the text placed starting with the bottom right text frame? When InDesign creates items within a repeat loop, the first item is placed on the bottom of a stack and each succeeding item is placed on top. This puts the item created with the last bounds in the boundsList on top.

The testIt variable was added to verify the geometric bounds of the first indexed item on the page. As expected its values show that this item is located near the bottom right of the page.

To have page items created so that text frames 1 starts at top left, you can use several methods.

Method 1: Use a repeat loop with a counting variable starting from the length of the boundsList backward. Replace the tell pageItem statement block to read:

tell pageRef
   repeat with i from length of boundsList to 1 by -1
	make text frame with properties {geometric bounds:item i of boundsList}
   end repeat
   repeat with i from 1 to length of wordList
	set contents of insertion point 1 of text frame i to item i of wordList
   end repeat
   set testIt to geometric bounds of text frame 1
end tell

Method 2: Change how the bounds are added to the boundsList. In the getBoundsList handler change the line that sets the end of boundsList to {y0, x0, y1, x1} to read:

   set beginning of boundsList to {y0, x0, y1, x1}

This way the last item calculated becomes the first item of the boundsList and the bottom item for the stacking order.

Naming

You can avoid the stacking order problem by naming items as they are created and refer to items by name instead of index. Change the tell application statement block in the above to read as follows:

set imageRef to choose file with prompt "Choose image to place"
tell application "Adobe InDesign CC 2019"
   set pageRef to page 1 of document 1
   tell pageRef
      repeat with i from 1 to length of boundsList
	 set theName to "Image_" & i
	 make rectangle with properties {name:theName, geometric bounds:item i of boundsList}
      end repeat
      place imageRef on rectangle "Image_1"
   end tell
end tell

Take note of how the name for the item is created.

Rest of List

One more way to parse a list within a repeat loop is to use rest of list with the repeat while statement. This is particularly efficient when parsing through extra long lists because the list gets shorter each time through the list. For demonstration, change the tell pageRef statement block above to read as follows:

tell pageRef
   set theCounter to 1
   repeat while boundsList is not {}
      make text frame with properties {geometric bounds:item 1 of boundsList}
	 set boundsList to rest of boundsList
      end repeat
   repeat with eachItem in wordList
      set insertion point 1 of text frame theCounter to eachItem
      set theCounter to theCounter + 1
   end repeat
end tell

For the above you will also want to use beginning of to add bounds for grid items to the boundsList as in Method 2 above.

Upward and Onward

There are a number of other ways repeat loops can be created, but the ones presented above are the most common. When the need arises, use one of these handlers to calculate items needed for a grid. See if you can come up with some creative ways of displaying a number of different sized items within a grid. Hint: The height and width of all items will need to be relative to a base grid value.

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.

Trackback Link
http://www.yourscriptdoctor.com/BlogRetrieve.aspx?BlogID=18424&PostID=1529668&A=Trackback
Trackbacks
Post has no trackbacks.

Recent Posts


Tags

lists dropdowns Previous text frame place point start paragraph GREP Radiobutton Group entire path choose file name Next Style user interaction level Buttons Nested Repeat Loops Autoflow time tab list Document Presets image metadata Check for Styles info for primary text frame repeat loops draw from center text import preferences measurement editboxes import styles Border Panel Placing images Library Folder rest giving up after InDesign template try/on error Multi-state objects import XML coordinates Script panel do script intent file info CALS tables random number script templates Image Events table styles selection Templates Find Text Adding Captions active spread reference to paragraph styles Chaining Paragraph Styles Export to PDF fit Script Preferences file paths Editbox measurements rest of list Create Text Frame stacking order convert to text text item delimiters dialog map tags to styles threaded text frames move file automated workflow Repeat With object styles Read text file as list sort list live bounds Duplicate Character Styles auto tag StandardAdditions Choose file Combobox Document from Template date objects XML tags Text Style Mapping script folder Create Document Image Placement integer editboxes Place text XML Adjust Layout find change options master spread text editbox XML structure Document stylesheets Script Library page items Clearing Overrides radiobutton control import text Find by Attribute Checkbox Control Apply Object Style bookmarks tabs Custom Dialog Styling Text Keyboard Shortcuts grid Enabling Groups Excel lists of lists Write to File next text frame Choose from list Change Text static alignment handlers

Archive