AUTOMATE PLACEMENT OF IMAGES WITH CAPTION

The script created in our previous blog post demonstrated how a script could be used to automate the placement of images in a document grid. The file names for the images were read from a plain text file as a list that was parsed to place the images. As another demonstration of how a text file can be used to automate the placement of images in a document, will take this idea and expand it to have the text file provide more than just the name of the files. Consider a file with the file name, the file’s caption, and a short description each listed as a paragraph.

THE SET UP

For our purpose, we will have a text file with six entries. Each of the entries will have three paragraphs to describe the file name, the caption, and description. Each entry will look similar to the following:

BalancingRock.jpg
Balancing Rock
Balancing Rock is one of the unique formations found in Cathedral Valley.

The text file should be saved in the same folder as the images. You can use any collection of images sized approximately 3.6 inches by 2.1 inches.

READING A PLAIN TEXT FILE

To read the plain text file, we will use the following handler:

(*Reads contents of return-delimited plain text file as a list*)
on readFileAsList(fileChoice)
   set fileRef to fileChoice as string
   return read file fileRef using delimiter "\n"
end readFileAsList

The value for the variable fileChoice required for this handler will be passed as the result of a choose file statement at the top of the script. The result of reading the file will be placed in a variable (fileList) that is part of the handler call.

For this the top portion of the script will then read as follows:

set fileChoice to choose file with prompt ("Choose plain text for file information")
set fileList to readFileAsList(fileChoice)
fileList

To test, start a new script in Script Editor. Copy and paste in the code for the top of the script and follow with the readFileAsList handler. Compile and run the script choosing your plain text file in response to the prompt. You should see a list of all of your text items (18 in all). You may have empty items at the end of the list, but this will not be a problem.

DEFINE FILE PATH

In order to place an image from a file into an InDesign document, an alias reference to the file can be used. To create the alias reference for the file, we will need to add the path to its containing folder to its name. By placing the text file inside the same folder as the images, the path to the containing folder (its parent) can be returned from the following handler:

(*Removes the name of the file from the filePath to return path for the parent folder*)
on getParentFolder(filePath)
   set AppleScript's text item delimiters to ":"
   set folderPath to items 1 thru -2 of text items of filePath
   set folderStr to folderPath as string
   set AppleScript's text item delimiters to ""
   return folderStr
end getParentFolder

Test Script

To test this out, add the getParentFolder handler to your script and change the top portion of the script to read as follows:

set fileChoice to choose file with prompt ("Choose plain text for file names")
set fileList to readFileAsList(fileChoice)
set folderPath to getParentFolder (fileChoice as string)
set theTest to (folderPath & ":" & item 1 of fileList) as alias
--add handlers for readFileAsList and getParentFolder here

With the two handlers added, compile and test the script.

When you run this script you shouldn’t get an error but instead an alias reference to the first file in your text file should be the result. You are now ready to go on to place the images and captions into a grid in an InDesign document. Save the script for further reference.

CREATING THE GRID

The demonstration script we will build assumes that the user has a letter size InDesign document (8-1/2 inches by 11 inches) open with margins as follows: top:1.5 inches, left and right: .5 inches, bottom:.75 inch). To get a list of geometric bounds for the grid, the script will use the handlers getLiveBounds and calcGrid from our previous blog.

The unique problem posed by this script is the fact that each cell of the grid will have two items: a rectangle for the image, and a text frame for the image caption. Let’s solve that problem first.

Start a new script. A handler to place the images and captions will depend on values for the number of columns, rows, and spacing along with a value we will call imagePct. This value will be a percentage for calculating the geometric bounds for the individual items within the grid. For the sake of brevity, the values for our variables will be defined at the top of the script.

Add Images With Caption

set hCells to 2 --columns
set vCells to 3 --rows
set gutter to 12 --horizontal spacing
set gap to 12 --vertical spacing
set imagePct to .75 --percentage value represents relative height of image container
set numElements to 3 --number of items to be read for each grid cell
set liveBounds to getLiveBounds()
set gridBounds to calcGrid(liveBounds, hCells, vcells, gutter, gap)
placeImagesWithCaption(gridBounds,imagePct)
(*Creates grid with each cell having a rectangle and a text frame*)
on placeImagesWithCaption(gridBounds, imagePct)
   tell application "Adobe InDesign CC 2019"
	set measurement unit of script preferences to points
	set pageRef to page 1 of active spread of layout window 1 of document 1
	tell pageRef
	   repeat with i from 1 to length of gridBounds
		copy item i of gridBounds to {y0, x0, y1, x1}
		set rectBot to y0 + ((y1 - y0) * imagePct)
		set rectRef to make rectangle with properties ¬
{geometric bounds:{y0, x0, rectBot, x1}, name:"image"}
		set frameRef to make text frame with properties ¬
{geometric bounds:{rectBot, x0, y1, x1}, name:"caption"}
	   end repeat
	end tell
   end tell
end placeImagesWithCaption
(*Returns geometric bounds of usable area inside page margins
Assumes left and right margins are the same*)
on getLiveBounds()
   tell application "Adobe InDesign CC 2019"
	set measurement unit of script preferences to points
	set pageRef to page 1 of active spread of layout window 1 of document 1
	tell pageRef
	   copy bounds to {y0, x0, y1, x1}
	   tell margin preferences
	      copy {left, top, bottom, right} to {lft, tp, bt, rgt}
	   end tell
	   set liveBounds to {y0 + tp, x0 + lft, y1 - bt, x1 - rgt}
	end tell
   end tell
   return liveBounds
end getLiveBounds
(*Calculates grid given number of horizontal and vertical cells, gutter and gap*)
on calcGrid(liveBounds, hCells, vCells, gutter, gap)
 set gridBounds to {}
   copy liveBounds to {py0, px0, py1, px1}
   set celWid to ((px1 - px0) - ((hCells - 1) * gutter)) / hCells
   set celHgt to ((py1 - py0) - ((vCells - 1) * gap)) / vCells
   set x0 to px0
   set y0 to py0
   repeat with i from 1 to vCells
      set y1 to y0 + celHgt
      repeat with j from 1 to hCells
	 set x1 to x0 + celWid
	 set end of gridBounds to {y0, x0, y1, x1}
	 set x0 to x1 + gutter
      end repeat --j loop
      set x0 to px0
      set y0 to y1 + gap
   end repeat --i loop
   return gridBounds
end calcGrid 

Compile and run the script with your InDesign document open. The grid is created but the placeImageWithCaption handler has no provision for placing the images and text. As is, this is a good place to test the script to make sure the grid is being created as it should. Remember to save your InDesign document before running a script. This way you can always revert the document back to its original state (File > Revert).

ADD IMAGES AND TEXT

We can now add the capability to get the image and caption using some of the code from our first test script above. Add the following to the top of the script, before the call to placeImagesWithCaption:

set fileChoice to choose file with prompt ("Choose plain text for file names") 
set filePath to getParentFolder(fileChoice as string)
set fileList to readFileAsList(fileChoice)
set folderPath to getParentFolder(fileChoice as string)

Add the handlers readFileAsList and getParentFolder from our Test Script above to the bottom of your script.

Now for the Hard Part

Our list of image information has three items for each grid cell (image file name, caption, and description), but our list for the geometric bounds of each grid cell is only one item.

What the script needs is a fairly complex repeat loop to place the image and text. First the loop will get the file name, caption, and description from the fileList. Then it will get the geometric bounds for the grid cell items based on a counting variable (theCount). For this, the call to placeItemsWithCaption handler will need to provide three more pieces of information: the fileList, the folderPath, and number of elements in each grid cell (numElements). The call should read as follows:

placeImagesWithCaption (gridBounds, imagesPct, fileList, folderPath, numElements)

With this information, the PlaceImagesWithCaption handler can now read as follows:

(*Creates grid using gridBounds and fill with images from the fileList*)
on placeImagesWithCaption(gridBounds, imagePct, fileList, folderPath, numElements)
   tell application "Adobe InDesign CC 2019"
	set measurement unit of script preferences to points
	set theCount to 1
	tell page 1 of active spread of layout window 1 of document 1
	   repeat with i from 1 to (length of fileList) by numElements
		set fileName to item i of fileList
		set fileCaption to item (i + 1) of fileList
		set fileDesc to item (i + 2) of fileList
		copy item theCount of gridBounds to {y0, x0, y1, x1}
		set rectBot to y0 + ((y1 - y0) * imagePct)
		set rectRef to make rectangle with properties ¬
{geometric bounds:{y0, x0, rectBot, x1}, name:("image" & i)}
		set imageRef to (folderPath & ":" & fileName) as alias
		tell rectRef
		   place imageRef
		   fit given fill proportionally
		end tell
		set theText to fileCaption & return & fileDesc
		make text frame with properties ¬
{geometric bounds:{rectBot, x0, y1, x1}, name:"Caption", contents:theText}
		set theCount to theCount + 1
		if theCount > length of gridBounds then
		   exit repeat
		end if
	   end repeat
	end tell
   end tell
end placeImagesWithCaption

Revert your InDesign document and test the script.

ADDING STYLING

When you test the script you will notice one vital thing is missing; styling. The caption and description text needs to be styled and there needs to be a space between the image and the caption. This can easily be corrected by creating an object style to define the styling for both the text frame and its text. Revert your document.

Add a paragraph style for the description (Description). Set it to 10 pt Minion Pro regular on 12 pt leading. Add a paragraph style for the caption (Caption) with 12 pt Minion Pro bold and 3 points of space below (0p3). The Next Style property for the Caption style will be Description. Both styles are set left align with hyphenation off. Next create an object style for the text frame (Captionframe). Set the Paragaph Style for the object style to Caption with Apply Next Style checked. In Text Frame General Options set Inset Spacing for Top to 0p6. Save the document.

Now add the following to the placeImagesWithCaption handler directly below the tell statement to the application:

set objStyle to object style "CaptionFrame" of document 1

Then change the line that creates the text frame to read:

make text frame with properties {geometric bounds:{rectBot, x0, y1, x1}, ¬
name:"Caption", contents:theText, applied object style:objStyle}

Test the modified script with the styles added to the document.

A screen shot of our script result is below. Your page should look similar, of course with different images and text.

…Our completed page

CUSTOM DIALOG

To make this script and the one from our previous blog complete, you will want to add a custom dialog to have the user enter the values for the variables that are currently defined at the top of the script. We will start with our custom dialog template. Add the following to a new script.

set dlgName to "Dialog Name"
set canCancel to true
set dlgLabel to "Label here"
set dlgRef to customDialog(dlgName, canCancel, dlgLabel)
on customDialog(dlgName, canCancel, dlgLabel)
   tell application "Adobe InDesign CC 2019"
      activate
      set origPrefs to user interaction level of script preferences
      set user interaction level of script preferences to interact with all
      set dialogRef to make dialog with properties {name:dlgName, can cancel:canCancel, label:dlgLabel}
      tell dialogRef
	 tell (make dialog column)
	    --set up dialog widgets here
	    tell (make dialog row)
	    end tell --row
	 end tell --column
      end tell --dialog
		
      set userResponse to show dialogRef
      set returnList to {}
      if userResponse is true then
	 --get user responses here
      end if
      destroy dialogRef
      set user interaction level of script preferences to origPrefs
   end tell
   return returnList
end customDialog 

The dialog will need objects (widgets) to get the user’s response for the number of columns, rows, gutter (horizontal space between columns), and gap (vertical space between rows). An integer editbox can be used for columns and rows, while measurement editboxes can best serve for gutter and gap. Add the following to the section starting with –set up dialog widgets here:

--set up dialog widgets here
tell (make dialog row)
   make static text with properties {static label:"Enter values for grid:"}
   tell (make dialog column)
	make static text with properties {static label:"Rows"}
	make static text with properties {static label:"Columns"}
	make static text with properties {static label:"Gutter"}
	make static text with properties {static label:"Gap"}
   end tell
   tell (make dialog column)
	set rowField to make integer editbox with properties {min width:72, edit value:6}
        set colField to make integer editbox with properties {min width:72, edit value:2}
	set gutField to make measurement editbox with properties ¬{min width:72, edit units:points, edit value:10}
	set gapField to make measurement editbox with properties {min width:72, edit units:points, edit value:10}
   end tell
end tell

To return the values entered by the user, add the following in the section near the bottom starting with –get user responses here:

--get user responses here
set rowVal to edit value of rowField as integer
set colVal to edit value of colField as integer
set gutVal to edit value of gutField
set gapVal to edit value of gapField
set returnList to {rowVal, colVal, gutVal, gapVal}

Test the script.

ONWARD AND UPWARD

This dialog will work well for creating a grid for an image without allowance for caption. Once you have the dialog working, you can add it to the script from our previous blog. For our Add Images With Caption script you will need to keep the values for imagePct and numElements defined at the top of the script. You will also need to add the ability to define the object style to your script. As a challenge, think of how you would add the ability to define imagePct, numElements, and the name of the object style to the custom dialog.

For our complete PlaceItemsWithCaption script, we added an enabling group to our custom dialog to allow the user to input these additional values. This demonstration script is our Feature Script for the month of May and can be downloaded from the Feature page of our web site (YourScriptDoctor.com/Feature).

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.