Your prescription for increased productivity and profitability
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.
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.
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.
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
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.
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.
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).
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.
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.
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
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.
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.