Once a Formatting Rule has been populated with data, it can be processed before final placement using Lua code. Typical uses for this would be to position frames relative to one another, position a series of images, or incorporate other Formatting Rules to build a layout. It’s also possible to create the entire content via Lua using the InDesign Scripting DOM
Frame Positioning
Here’s a simple example. The position of the box marked C should always be below box A and B irrespective of their final depth:
The post processing script is passed a formattingrule argument, which can be used to manipulate elements. Frames are identified by name, defined using the InDesign Script Label. The code to maintain the relative position of frame C is:
1 2 3 4 5 6 7 8 | -- get FRAME C c = formattingrule:getframe("C"); -- get the combined bounds of "A" and "B" bounds = formattingrule:getbounds("A","B"); -- move FRAME C to the bottom of the bounds + 2mm c:moveto(c:left(), bounds.bottom + topoints("2mm")); |
Frame names should generally be unique, as this allows referencing elements of an array by name. Here’s an example using this technique to achieve the same result:
1 2 3 4 5 6 7 | -- Get ALL the frames into an array f = formattingrule:getframes(); -- this time passing FRAME objects to getbounds: bounds = formattingrule:getbounds(f["A"], f["B"]); f["C"]:moveto(f["C"]:left(), bounds.bottom + topoints("2mm")); |
All coordinates given by the getbounds method are in points relative to the top left of the rule, which has an origin of 0,0. Frame positioning can legitimately alter the overall bounds or the Formatting Rule. getbounds takes a variety of arguments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | -- get the bounds of the formatting rule area = formattingrule:getbounds(); -- bounds of all 'cover_image' frames, passing in an array image_area = formattingrule:getbounds(formattingrule:getframes("cover_image.*")); -- bounds of single FRAME area = formattingrule:getbounds(formattingrule:getframe("image_area")); -- bounds of a named frame area = formattingrule:getbounds("image_area"); -- bounds of multiple named frames area = formattingrule:getbounds("Image_Cover1", "Image_Cover2"); -- Accessing the return value error ("l=" .. area.left .. ", r=" .. area.right .. ", t=" .. area.top .. ", b=" .. area.bottom); |
Arranging an Aligning Frames
Methods on the formatting rule allow the easy positioning and alignment of frames or groups of frames.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | -- formatting rule contains a single frame with a script label of "icon" icon = formattingrule:getframe("icon"); -- selection contains all records for the formatting rule sel = SELECTION.root(); -- first item is already paginated, so start at the 2nd for i=2,sel:size() do -- duplicate the frame. If the item is anchored it will be duplicated after. newf = icon:duplicate(); newf:moveto(icon:x(),icon:y()); -- update the content of the frame with the selection item newf:updatecontent(sel:get(i)); end -- get ALL the frames with the script label "icon" named_frames = formattingrule:getframes("icon"); -- arrange them into the area of the formatting rule -- default arrangement is left-right,top-bottom within the bounds of the rule -- optional parameters allow variation in arrangement formattingrule:arrange(named_frames); -- now arrange the tableframe above the icon group. -- notice the first table entry is a frame, the second is a table for frames. formattingrule:arrange( { formattingrule:getframe("tableframe"), named_frames} ); |
Importing Formatting Rules
Other Formatting Rules can be imported and their post processing code is executed independently. Rules placed in this way are not independent, so only update when the parent rule updates. In this example, we insert every child elements into parent, filling left to right, top to bottom.
Parent post processing code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | sel = SELECTION.root(); ypos = 0; xpos = 0; totalbounds = formattingrule:getbounds(); for i=1,sel:size() do local result = formattingrule:placerule("child",0, 0, sel:get(i)); width = result:right() - result:left(); if xpos + width > totalbounds.right then xpos = 0; ypos = ypos + result:bottom() - result:top() end local result = result:moveto(xpos,ypos) xpos = xpos + width; end; |
Which gives the result:
Notice each child is populated using data from the given selection element. A SELECTION is a hierarchy of groups and records based on what’s assigned to the Rule. Refer to the SELECTION class for more information.
A few more examples of placerule:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | -- Starting at x(0) and y(0), create a 4 x 4 grid of the 'redbox' Formatting Rule ypos = 0; xpos = 0; nextypos = 0 for x=1,4 do for y=1,4 do local result = formattingrule:placerule("redbox",xpos, ypos); xpos = result:right(); nextypos = result:bottom(); end xpos = 0; ypos = nextypos; end -- Selectively place a rule based on the contents of a field sel = SELECTION.root(); field_content = sel:getcontent('seite'); if field_content == "10" then formattingrule:placerule("small_green",0,0); else formattingrule:placerule("small_red",0,0); end -- Insert a populated rule for each item in the selection sel = SELECTION.root(); ypos = 0; xpos = 0; for i=1,sel:size() do local result = formattingrule:placerule("art",xpos, ypos, sel:get(i)); ypos = result:bottom(); end; -- for each selected item, place a rule and then arrange it left-right in the formatting rule bounds sel = SELECTION.root(); frame_array = {} for i=1,sel:size() do frame_array[i] = formattingrule:placerule("art",0, 0, sel:get(i)); end; formattingrule:positionframes(frame_array, formattingrule:getbounds(), 0, "left-right", "top-left"); -- Repeat an anchored item for each record icon = formattingrule:getframe("icon"); -- selection contains all records for the formatting rule sel = SELECTION.root(); -- first item is already paginated, so start at the 2nd for i=2,sel:size() do -- duplicate the frame. If the item is anchored it will be duplicated after. newf = icon:duplicate(); -- update the content of the frame with the selection item newf:updatecontent(sel:get(i)); end |
BringToFront/SendToBack
1 2 | b = formattingrule:getframe("b"); b:getdom("SplineItem"):bringToFront(); |
The ‘require’ Keyword
When it becomes cumbersome to edit code in the standard dialog box, external modules can be created and stored as files. The Lua command require scans a number of locations for module code. These include the ‘Scripts’ sub folder in the data source – require(“xxx”) will attempt to load “xxx.lua” from the Scripts folder. When a module cannot be found, an error is displayed listing all potential locations for the file.
Using the InDesign Scripting DOM To Create Content
The InDesign Scripting DOM can be accessed to create or modify virtually any content. So it’s possible to create a Formatting Rule that is an empty text frame, then use Lua code to generate the content for that frame:
1 2 3 4 5 | -- Get the contents of "some field", then use the InDesign DOM to populate the formatting rule selection = SELECTION.root(); field_content = selection:getcontent("some field"); framedom = formattingrule:getdom(); framedom:parentStory():texts():item(0):contents("hello world" .. field_content) |
Further information on the use of the scripting DOM can be found here.
Example using Formatting Rule Post Processing Logic to create a table which mirrors the contents of a named tabular field. The Formatting Rule is an empty text box.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | framedom = formattingrule:getdom(); field = SELECTION:root():getfield("producto_imgs"); table = field:content(); -- create the table mytable = framedom:tables():add( { columnCount = table:colcount(), bodyRowCount = table:rowcount()}); -- assign row and cell properties cell_properties = { bottomEdgeStrokeWeight = 0.25, topEdgeStrokeWeight = 0.25, leftEdgeStrokeWeight = 0.25, rightEdgeStrokeWeight = 0.25} mytable:cells():everyItem():properties(cell_properties) mytable:rows():everyItem():height("1cm"); -- populate the table for row=1,table:rowcount() do for col=1,table:colcount() do insertionPoint = mytable:rows(row-1):cells(col-1):insertionPoints(-1) insertionPoint:insertField(field,row-1,col-1); end end |
The ‘require’ Keyword
When it becomes cumbersome to edit code in the standard dialog box, external modules can be created and stored as files. The Lua command require scans a number of locations for module code. These include the ‘Scripts’ sub folder in the data source – require(“xxx”) will attempt to load “xxx.lua” from the Scripts folder. When a module cannot be found, an error is displayed listing all potential locations for the file.
Using the InDesign Scripting DOM To Create Content
The InDesign Scripting DOM can be accessed to create or modify virtually any content. So it’s possible to create a Formatting Rule that is an empty text frame, then use Lua code to generate the content for that frame:
1 2 3 4 5 | -- Get the contents of "some field", then use the InDesign DOM to populate the formatting rule selection = SELECTION.root(); field_content = selection:getcontent("some field"); framedom = formattingrule:getdom(); framedom:parentStory():texts():item(0):contents("hello world" .. field_content) |
Further information on the use of the scripting DOM can be found here.
Example using Formatting Rule Post Processing Logic to create a table which mirrors the contents of a named tabular field. The Formatting Rule is an empty text box.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | framedom = formattingrule:getdom(); field = SELECTION:root():getfield("producto_imgs"); table = field:content(); -- create the table mytable = framedom:tables():add( { columnCount = table:colcount(), bodyRowCount = table:rowcount()}); -- assign row and cell properties cell_properties = { bottomEdgeStrokeWeight = 0.25, topEdgeStrokeWeight = 0.25, leftEdgeStrokeWeight = 0.25, rightEdgeStrokeWeight = 0.25} mytable:cells():everyItem():properties(cell_properties) mytable:rows():everyItem():height("1cm"); -- populate the table for row=1,table:rowcount() do for col=1,table:colcount() do insertionPoint = mytable:rows(row-1):cells(col-1):insertionPoints(-1) insertionPoint:insertField(field,row-1,col-1); end end |