Placing an image over an InDesign table cell

Category: Duck Notes

While working on my upcoming Sudoku book (Sudoku Atlantis) I came up with a new need for placing images in my puzzle grid. All of my publishing is done using Adobe InDesign CS5 with a C# program I use as a GUI generating JavaScript code which is then run in InDesign.

Previously I had simply set up a 9x9 array of rectangles on a separate layer within my InDesign template and manually aligned it with the puzzle grid. This worked well enough but I wanted to have a little more flexibility.

What I want to do is be able to create a rectangle in the exact position and size as a table cell.

I first thought this would be easy since InDesign elements have a "geometricPosition" property. This property returns an array [y1,x1,y2,x2] which gives the coordinates of the upper left and lower right edges of the element. Unfortunately, this only applies to "page items", effectively anything you can pick up and drag to a new position in InDesign. Neither a table nor its cells are position independently of the text frame in which the table has been placed.

After some work I came up with a set of functions in JavaScript to work around this.

The first function is FindTableBounds. By passing in an InDesign table reference, it will return an object which encapsulates the geometric bounds of the object: the upper left and lower right corner positions.

function FindTableBounds(tbl)
{
var textFrame = tbl.parent;
var textBounds = textFrame.geometricBounds;
var tblWidth = tbl.width;
var tblHeight = tbl.height;

// see how we're justified vertically
var pref = textFrame.textFramePreferences;

// the inset is either an equal measure for all four sides or an array.
// we will use it as an array.
var inset = pref.insetSpacing;
if (inset.length != 4) inset = [inset, inset, inset, inset];

// now let's find the top
var y1 = 0;
if (pref.verticalJustification == VerticalJustification.TOP_ALIGN)
{
// against the top edge
y1 = textBounds[0] + inset[0];
}
else if (pref.verticalJustification == VerticalJustification.BOTTOM_ALIGN)
{
// against the bttom edge
y1 = textBounds[2] - inset[2] - tblHeight;
}
else
{
// center alignment
y1 = ((textBounds[0] + inset[0]) + (textBounds[2] - inset[2])) / 2 - tblHeight / 2;
}

// now the left
var just = textFrame.insertionPoints[0].justification;
var x1 = 0;
if (just == Justification.LEFT_JUSTIFIED || just == Justification.LEFT_ALIGN)
{
x1 = textBounds[1] + inset[1];
}
else if (just == Justification.RIGHT_JUSTIFIED || just == Justification.RIGHT_ALIGN)
{
x1 = textBounds[3] - inset[3] - tblWidth;
}
else
{
x1 = ((textBounds[1] + inset[1]) + (textBounds[3] - inset[3])) / 2 - tblWidth / 2;
}
return new ObjectBounds(x1, y1, x1 + tblWidth, y1 + tblHeight);
}

The returned value is a little object class which wraps the object coordinates into an object as opposed to the raw array InDesign uses.

Although this function could be useful on its own, for example to dynamically create a rectangle or other object in front of or behind a table, its use here is for determining the upper left coordinate of the table so we can find out where a cell in it is.

function FindCellBounds(tbl, row, col)
{
var tblBounds = FindTableBounds(tbl);

// are the row and column numbers reasonable?
if (row < 0 || row >= tbl.rows.length) return null;
if (col < 0 || col >= tbl.columns.length) return null;

// get our base positions
var x1 = tblBounds.upperLeft.x;
var y1 = tblBounds.upperLeft.y;
for (var r = 0; r < row; ++r) y1 += tbl.rows[r].height;
for (var c = 0; c < col; ++c) x1 += tbl.columns[c].width;
return new ObjectBounds(x1, y1, x1 + tbl.columns[col].width, y1 + tbl.rows[row].height);
}

And lastly we have the function that will create a rectangle and with the same size and position as a given table cell.


function CreateRectangleAtCell(tbl, row, col, prop)
{
// get the location of the cell
var pos = FindCellBounds(tbl, row, col);
if (pos == null) return null;
var textFrame = tbl.parent;
var pg = textFrame.parentPage;

// add our position settings to the caller's properties
if (prop == null) prop = new Object();
prop.geometricBounds = new Array(pos.upperLeft.y, pos.upperLeft.x, pos.lowerRight.y, pos.lowerRight.x);

// create the rectangle. There is a method for creating rectangles that takes the properties as the third parameter,
// however in most cases this method will fail.
var r = pg.rectangles.add();
r.properties = prop;
return r;
}

This function takes a properties object which can be applied to the rectangle once created to change its color, layer or any of the other assignable properties.

In my scripts, after creating the rectangle I then use the place() method to drop an image into the rectangle.

This code and a demonstration INDD file and script can be found on github at:

https://github.com/mattmayfield/INDDCreateRectangleAtCell