Answered
Hi
I’ve written a script that does it on the test file from your related question and it works, but you will see there are some complications, and the code runs a bit slow because it is testing the cell heights after each scaling test. Still it does the job I think.
— Mark

/**
* @file Expand Table Rows To Fill Frame.js
*
* @author m1b
* @version 2026-04-11
* @discussion https://community.adobe.com/questions-671/how-to-quickly-adjust-the-bodyrow-to-align-to-the-textframe-bottom-1554513
*/
function main() {
var doc = app.activeDocument;
var items = doc.selection;
for (var i = 0; i < items.length; i++)
expandTableRowsToFillFrame(items[i], true);
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Expand Rows To Fill Frame');
/**
* Scales the rows of the last table in text frame
* to their maximum, without overflowing.
* @author m1b
* @version 2026-04-11
* @param {TextFrame} textFrame - the target text frame.
* @param {Boolean} [onlyBodyRows] - whether to scale only body rows (default: true).
*/
function expandTableRowsToFillFrame(textFrame, onlyBodyRows) {
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
onlyBodyRows = false === onlyBodyRows
if (TextFrame !== textFrame.constructor)
return;
var tf;
var row;
var table = null;
if (
textFrame.characters.lastItem().isValid
&& '\u0016' === textFrame.characters.lastItem().contents
)
table = textFrame.characters.lastItem().tables[0];
if (!textFrame.characters.lastItem().isValid) {
// this text frame might have a table, but not the first part of it
var table = null;
var timeout = 20;
var prev = textFrame.previousTextFrame;
// search for table in previous frames
while (prev && !table && timeout--) {
if (
prev.characters.lastItem().isValid
&& '\u0016' === prev.characters.lastItem().contents
)
table = prev.characters.lastItem().tables[0];
prev = textFrame.previousTextFrame;
}
}
if (!table)
return;
var allRows = table.rows;
var frameRows = [];
var frameHeights = [];
for (var i = 0; i < allRows.length; i++) {
row = allRows[i];
tf = getParentTextFrame(row, false);
if (!tf)
// row is likely overflowing
break;
if (tf === textFrame) {
// found one!
frameRows.push(row);
frameHeights.push(row.height);
}
else if (frameRows.length)
// no need to keep looking
break;
}
// now we have the rows we want to target
var lastRow = frameRows[frameRows.length - 1];
// find an upper bound where rows overflow the frame
var low = 1;
var high = 2;
while (scaleRows(high))
high *= 2;
// binary search for the largest scaleFactor that keeps rows in-frame
var iterations = 50;
var precision = 0.001;
while (high - low > precision && iterations--) {
var mid = (low + high) / 2;
if (scaleRows(mid))
low = mid;
else
high = mid;
}
// apply the best scale
scaleRows(low);
/**
* Scales original height rows by `scaleFactor`, and returns true, when the frameRows still fit in frame.
* @param {Number} scaleFactor - multiply the original heights by this.
* @returns {Boolean}
*/
function scaleRows(scaleFactor) {
for (var i = 0; i < frameRows.length; i++)
if (RowTypes.BODY_ROW === frameRows[i].rowType)
frameRows[i].height = frameHeights[i] * scaleFactor;
// if it fits, return true
return getParentTextFrame(lastRow, false) === textFrame;
};
};
/**
* Return a row's parent text frame.
*
* Notes:
*
* (1) Getting the parent text frame of an overset cell is a
* challenge because there are no insertion points. Here I
* add a zero-width space as the first character so at least
* the first insertionPoint will now reside in the text frame.
*
* (2) Another complication is a bug where indesign will
* display the incorrect height for an autogrowing, overflowing
* cell, and place the cell in the wrong parent text frame.
* (See explanation here: https://community.adobe.com/t5/indesign-discussions/how-can-get-parenttextframe-of-a-cell-was-overflow/m-p/14030313
*
* (3) If your use case is such that returning `undefined` when the
* cell|row is overset is acceptable, then pass `false` to the
* `evenWhenCellIsOverset` parameter. This will be much faster.
*
* @author m1b
* @version 2025-04-05
*
* @param {Cell|Row} row - an Indesign Table Row or Cell.
* @param {Boolean} [evenWhenCellIsOverset] - whether to find the parent text frame of an overset cell (default: true).
* @returns {TextFrame}
*/
function getParentTextFrame(row, evenWhenCellIsOverset) {
if (!row.isValid)
return;
if (
false === evenWhenCellIsOverset
&& row.hasOwnProperty('texts')
)
// the quick way; will return `undefined` if the cell is overset
return row.texts[0].parentTextFrames[0];
if (row.constructor.name == 'Cell')
row = row.rows[0];
var cells = row.cells,
rowTextFrameIDs = {},
rowTextFrame,
parentTextFrames = row;
// find the text frames of the row
while (
!parentTextFrames.hasOwnProperty('parentStory')
|| undefined == parentTextFrames.parent
)
parentTextFrames = parentTextFrames.parent;
if (!parentTextFrames.hasOwnProperty('parentStory'))
return;
parentTextFrames = parentTextFrames.parentStory.textContainers;
for (var i = 0; i < cells.length; i++) {
var cell = cells[i];
// insert zero-width space to expose first
// insertion point in case where cell is overset
cell.insertionPoints[0].contents = '\u200B';
if (cell.insertionPoints[0].parentTextFrames.length > 0)
// store the parent frame index of this cell
rowTextFrameIDs[cell.insertionPoints[0].parentTextFrames[0].id] = true;
// clean up
cell.characters[0].remove();
}
// match it to the *last-most* parentTextFrame to ensure
// that the entire row returns the same (correct) text frame
parentFrameLoop:
for (var j = parentTextFrames.length - 1; j >= 0; j--) {
if (rowTextFrameIDs[parentTextFrames[j].id] == true) {
rowTextFrame = parentTextFrames[j];
break parentFrameLoop;
}
}
if (
rowTextFrame != undefined
&& rowTextFrame.isValid
)
return rowTextFrame;
};
Sign up
Already have an account? Login
To post, reply, or follow discussions, please sign in with your Adobe ID.
Sign inSign in to Adobe Community
To post, reply, or follow discussions, please sign in with your Adobe ID.
Sign inEnter your E-mail address. We'll send you an e-mail with instructions to reset your password.

