Copy link to clipboard
Copied
Hi,
I've got a table within a text frame.
I'd like to my script to figure out the table's position (its x & y coordinates).
How do I do that?
Thanks!
Hi @orib7317974, you would think it would be straightforward, but... nope. I've written a script that (I hope) does it. Have a read through and see if you can tell how it works. It was fun to write. You can just add the relevant functions from my script to yours.
- Mark
/**
* Gets the bounds of the selected table.
* @author m1b
* @discussion https://community.adobe.com/t5/indesign-discussions/table-s-position-amp-scripting/m-p/13555533
*/
function main() {
var doc = app.activeDocumen
...
Hi @m1b , I’m not sure if this would always work, but a simpler approach might be to work off of the insertion point before the table (the table’s .storyOffset property). Its baseline minus the table height minus the top border stroke width would get Y, and its horizontalOffset plus 1 point would get X. With some limited testing this seems to work:
var et = app.activeDocument.stories.everyItem().tables.everyItem().getElements();
var pos = getTableXY(et[0])
alert("X: " + pos[0] + " Y: " +
...
Copy link to clipboard
Copied
If there is no text before the table - and no insets in the parent TF - then it's easy:
Table's top-left corner will be the same as TF's.
If there will be some text - then a bit more math is needed 😉
If you can remove insets in one of the cells - easier math - then from the baseline of the "a" you can subtract height of the row/cell and then stroke - to get Y. And from the HorizontalOffset of the 1st InsertionPoint you can get X - after subtracting stroke.
Copy link to clipboard
Copied
Hi @orib7317974, you would think it would be straightforward, but... nope. I've written a script that (I hope) does it. Have a read through and see if you can tell how it works. It was fun to write. You can just add the relevant functions from my script to yours.
- Mark
/**
* Gets the bounds of the selected table.
* @author m1b
* @discussion https://community.adobe.com/t5/indesign-discussions/table-s-position-amp-scripting/m-p/13555533
*/
function main() {
var doc = app.activeDocument,
selectedCell = getCell(app.selection[0]);
if (
selectedCell == undefined
|| !selectedCell.isValid
) {
alert('Please select a table or inside a table and run script again.');
return;
}
var table = selectedCell.parent;
var bounds = getTableBounds(table, 'geometricBounds');
if (!bounds)
return;
// draw a rectangle with the calculated bounds
var justForShowing = doc.layoutWindows[0].activePage.rectangles.add({ geometricBounds: bounds, fillColor: doc.swatches[4] });
justForShowing.transparencySettings.blendingSettings.opacity = 50;
}; // end main
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Get Table Position Demo");
/**
* Gets the table's bounds.
* @author m1b
* @version 2023-02-11
* @param {Table} table - an Indesign Table.
* @param {String} boundsType - can be 'geometricBounds' or 'visibleBounds'.
* @returns {Array<Number>}
*/
function getTableBounds(table, boundsType) {
if (table.cells.lastItem().texts[0].parentTextFrames.length == 0) {
alert('Cannot getTableBounds: table has overset cells.');
return;
}
var topLeft = getCellBounds(table.cells.firstItem(), boundsType),
bottomRight = getCellBounds(table.cells.lastItem(), boundsType);
return [topLeft[0], topLeft[1], bottomRight[2], bottomRight[3]];
}
/**
* Returns the bounds of the cell;
* @author m1b
* @version 2023-06-07
* @param {Cell} cell - an Indesign Table Cell.
* @param {String} [boundsType] - can be 'geometricBounds' or 'visibleBounds' (default: 'geometricBounds').
* @returns {Array<Number>}
*/
function getCellBounds(cell, boundsType) {
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
boundsType = boundsType || 'geometricBounds';
if (!cell.hasOwnProperty('index'))
throw Error('getCellBounds failed: bad cell parameter.');
var table = cell.parent;
while (table.constructor.name != 'Table')
table = table.parent;
table.insertLabel('findMe', '1');
var textFrame = table.parent;
while (textFrame.parent.constructor.name == 'Character') {
// inside anchored frame
textFrame = textFrame.parent.parentTextFrames[0];
}
var cellID = cell.id,
dupTextFrame = textFrame.duplicate(),
dupTable;
// find the table in dup text frame
tableSearch:
for (var i = 0; i < dupTextFrame.tables.length; i++) {
if (dupTextFrame.tables[i].extractLabel('findMe') == '1') {
dupTable = dupTextFrame.tables[i];
break tableSearch;
}
}
if (dupTable == undefined) {
// find the table in anchored text frames
tableSearch:
for (var i = 0; i < dupTextFrame.textFrames.length; i++) {
for (var j = 0; j < dupTextFrame.textFrames[i].tables.length; j++)
if (dupTextFrame.textFrames[i].tables[j].extractLabel('findMe') == '1') {
dupTable = dupTextFrame.textFrames[i].tables[j];
break tableSearch;
}
}
}
if (dupTable == undefined)
throw Error('getCellBounds failed: Could not find table in duplicate.');
// convert cell to graphic, so we can get the bounds
var dupCell = dupTable.cells.itemByID(cellID);
dupCell.convertCellType(CellTypeEnum.GRAPHIC_TYPE_CELL);
var bounds = dupCell.rectangles[0].geometricBounds;
// clean up
table.insertLabel('findMe', '');
dupTextFrame.remove();
// add strokeWidths
if (boundsType === 'visibleBounds') {
bounds[0] -= cell.topEdgeStrokeWeight;
bounds[1] -= cell.leftEdgeStrokeWeight;
bounds[2] += cell.bottomEdgeStrokeWeight;
bounds[3] += cell.rightEdgeStrokeWeight;
}
else if (boundsType === 'geometricBounds') {
bounds[0] -= cell.topEdgeStrokeWeight / 2;
bounds[1] -= cell.leftEdgeStrokeWeight / 2;
bounds[2] += cell.bottomEdgeStrokeWeight / 2;
bounds[3] += cell.rightEdgeStrokeWeight / 2;
}
return bounds;
};
/**
* Attempts to return a Cell, given an object.
* @author m1b
* @version 2023-02-06
* @param {InsertionPoint|Text|Cells|Table} obj - an object related to a Cell.
* @returns {Cell}
*/
function getCell(obj) {
if (obj == undefined)
return;
if (obj.constructor.name == 'Cell')
return obj;
if (obj.parent.constructor.name == 'Cell')
return obj.parent;
if (
obj.hasOwnProperty('cells')
&& obj.cells.length > 0
)
return obj.cells[0];
};
Edit 2023-06-07: added handling of tables in anchored text frames, and multiple tables in same text frame.
Copy link to clipboard
Copied
Hey @Robert at ID-Tasker and @m1b, thank you for these suggestions! @m1b, I really appreciate the time and effort you've put into this, I wasn't expecting somethin like this 🙂 Cheers!
Copy link to clipboard
Copied
Hi @m1b , I’m not sure if this would always work, but a simpler approach might be to work off of the insertion point before the table (the table’s .storyOffset property). Its baseline minus the table height minus the top border stroke width would get Y, and its horizontalOffset plus 1 point would get X. With some limited testing this seems to work:
var et = app.activeDocument.stories.everyItem().tables.everyItem().getElements();
var pos = getTableXY(et[0])
alert("X: " + pos[0] + " Y: " + pos[1])
/**
* Get a table’s X,Y position
* t.storyOffset is the insertion point before the table
* get its baseline - the table h - the top border for Y
* the insertion point’s horizontal position gets X
*
* @ param the table
* @ return x,y as an array
*
*/
function getTableXY(t){
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
var tr = t.rows[0].cells.everyItem().getElements()
var ti = t.topBorderStrokeWeight;
for (var i = 0; i < tr.length; i++){
if (tr[i].topEdgeStrokeWeight > ti) {
ti = tr[i].topEdgeStrokeWeight
}
};
var ty = t.storyOffset.baseline - t.height - ti;
var tx = t.storyOffset.horizontalOffset+1
app.scriptPreferences.measurementUnit = AutoEnum.AUTO_VALUE;
return [tx,ty]
}
Margin guides at 1". The table is pushed to the right of the text frame via a 1" Left Indent, and pushed down via Space After in the paragraph above:
EDIT had X,Y reversed—the returned array is [X,Y]
Copy link to clipboard
Copied
Hi @rob day and @Peter Kahrel, thank you both. Yes that was my initial direction for solving this problem, too, but, prior to having experts like yourselves endorse it, I decided to switch tracks for two reasons: (1) I wasn't confident that I was taking all the edge cases into account and your method seemed a bit shaky in that regard, and (2) my way was more fun to write 🙂
For the OP, and others reading this thread, Rob/Peter's method would be quicker to execute, because it does less DOM manipulation (would only be noticable to a human if performing many of executions of the function) and also calculates the maximum strokewidth (see Rob's example where the top left cell has thinner strokes than some other cells in the row/column).
- Mark
Copy link to clipboard
Copied
I wasn't confident that I was taking all the edge cases into account and your method seemed a bit shaky in that regard
Hi Mark, I’ve tried to break it and haven’t found away maybe you can. Doesn’t seem like the storyOffset insertion point relative to the table can be changed, so I think it reliably find’s the lower left table corner x,y. I was hoping the insertion point’s .ascent property would be at the top of the table including strokes (the UI cursor is) but it always returns as 0.
Copy link to clipboard
Copied
Hi @rob day and @Peter Kahrel for my learning I made a quick version of my script but using your technique. Well it was supposed to be quick. I now realise why I wrote my script the way I did—because my test file happened to show a glitch (I didn't know it was a glitch at the time and just thought the technique was unreliable) which I've reported as a bug. I found that if the last header row of the table has a bottom edge stroke (if greater than 1, I think), it will increase the height of the first body row to accommodate the thickness of the stroke, but thereafter the height property of both the table and the row will not reflect that addition. This meant that the top edge calculation was off by that amount. I've attached my test document if you want to test if you can reproduce the problem on your system.
I've added a "getAnomalousExtraTableHeight" function to address this issue. Hopefully will not always be needed.
Here is the script. It will draw a rectangle showing the table's bounds. Change true to false to show geometricBounds.
/**
* Get selected table's bounds
* @discussion https://community.adobe.com/t5/indesign-discussions/table-s-position-amp-scripting/m-p/13555533
*/
function main() {
var doc = app.activeDocument,
cell = getCell(app.selection[0]);
if (
cell == undefined
|| !cell.isValid
) {
alert('Please select inside a table and run script again.')
return;
}
var bounds = getTableBounds(cell.parent, true);
// just for displaying the bounds
var justForShowing = doc.rectangles.add({ geometricBounds: bounds, strokeColor: doc.swatches[0], fillColor: doc.swatches[4] });
justForShowing.transparencySettings.blendingSettings.opacity = 50;
/**
* Returns the table's bounds.
* @author m1b and rob day
* @param {Table} table - an Indesign table.
* @param {Boolean} [includeStroke] - whether to include stroke bounds (default: true).
* @returns {Array<Number>} - [t, l, b, r]
*/
function getTableBounds(table, includeStroke) {
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
// a 1 pt discrepancy between the table's insertionPoint's horizontalOffset and the table
var horizontalAnomaly = 1;
var topEdgeStrokeWeight = Math.max.apply(null, (table.rows.item(0).cells.everyItem().topEdgeStrokeWeight)),
bottomEdgeStrokeWeight = Math.max.apply(null, (table.rows.item(-1).cells.everyItem().bottomEdgeStrokeWeight)),
leftEdgeStrokeWeight = Math.max.apply(null, (table.columns.item(0).cells.everyItem().leftEdgeStrokeWeight)),
rightEdgeStrokeWeight = Math.max.apply(null, (table.columns.item(-1).cells.everyItem().rightEdgeStrokeWeight)),
// I think this is due to a bug in Indesign
anomalousExtraTableHeight = getAnomalousExtraTableHeight(table),
// calculate the bounds
l = table.storyOffset.horizontalOffset + horizontalAnomaly,
b = table.storyOffset.baseline,
r = l + Number(table.width) + rightEdgeStrokeWeight,
t = b - table.height - ((topEdgeStrokeWeight + bottomEdgeStrokeWeight) / 2) - anomalousExtraTableHeight,
bounds = [t, l, b, r];
// subtract strokeWidths to get the geometric bounds
if (includeStroke === false) {
bounds[0] += topEdgeStrokeWeight / 2;
bounds[1] += leftEdgeStrokeWeight / 2;
bounds[2] -= bottomEdgeStrokeWeight / 2;
bounds[3] -= rightEdgeStrokeWeight / 2;
}
return bounds;
};
/**
* Returns a value based on the bottomEdgeStrokeWeight
* of last header row of table. Because of the bug
* https://indesign.uservoice.com/forums/601180-adobe-indesign-bugs/suggestions/46272076--script-api-indesign-table-height-is-incorrect-wh
* where table.height is incorrect.
* @version 2023-02-07
* @param {Table} table - an Indesign table.
* @returns {Number}
*/
function getAnomalousExtraTableHeight(table) {
var bottomEdgeStrokeWeight,
anotherAnomalousPt = 1;
for (var r = 0; r < table.rows.length; r++)
if (table.rows[r].rowType == RowTypes.HEADER_ROW)
bottomEdgeStrokeWeight = Math.max.apply(null, (table.rows[r].cells.everyItem().bottomEdgeStrokeWeight))
else
break;
return bottomEdgeStrokeWeight ? (bottomEdgeStrokeWeight - anotherAnomalousPt) / 2 : 0;
};
/**
* Attempts to return a Cell, given an object.
* @author m1b
* @version 2023-02-06
* @param {InsertionPoint|Text|Cells|Table} obj - an object related to a Cell.
* @returns {Cell}
*/
function getCell(obj) {
if (obj == undefined)
return;
if (obj.constructor.name == 'Cell')
return obj;
if (obj.parent.constructor.name == 'Cell')
return obj.parent;
if (
obj.hasOwnProperty('cells')
&& obj.cells.length > 0
)
return obj.cells[0];
};
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Draw Rectangle Around Table");
Copy link to clipboard
Copied
Looks like a bug indeed. When you convert the first row to a header row then set the header's bottom stroke, the first row after the headeris made taller to accommodate the stroke. But when you set the first row's bottom stroke, then convert the row to a header, the space isn't added to the first data row.
Copy link to clipboard
Copied
Good spotting! Well that means my script will sometimes incorrectly adjust for the anomaly! I guess I'll go back to my original technique, which seems to bypass the issue. - Mark
Copy link to clipboard
Copied
I've used Rob's method in the past and it works well. I've always wondered about that 1 point horizontal offset, it's probably some artefact.
Copy link to clipboard
Copied
Hi Peter, it seems like a nominal padding —the insertion point before the table seems to be unusual in that you can’t change the kerning amount the way you can with other insertion points.
Copy link to clipboard
Copied
Yes, maybe. Just to be able to show the cursor. Anyway, it never really bothered me, just curious.
Copy link to clipboard
Copied
Hi @orib7317974 ,
see also in this discussion here where I posted a link to the TableCellBox.jsx script by Marc Autret:
Regards,
Uwe Laubender
( Adobe Community Expert )
Copy link to clipboard
Copied
Thank you 🙂