I found a solution from looking at Docs, and this tip.
Both of the following work perfectly for me with single-line, multi-line word-wrap (just text wrap, and \n), and multi-line no word wrap (just \n).
Short assumptive version (don't use this):
var totalLines = textField.bottomScrollV - textField.scrollV + textField.maxScrollV;
var metrics = textField.getLineMetrics(0);
var gutter = 2;
var actualHeight = (metrics.ascent + metrics.descent) * totalLines + (totalLines - 1) * metrics.leading + 2 * gutter;
Longer better version where lines have differing metrics (use this):
var gutter = 2;
var totalLines = textField.bottomScrollV - textField.scrollV + textField.maxScrollV;
var actualHeight = 0;
var prevLeading = 0;
for (var i = 0; i < totalLines; i += 1)
{
var metrics = textField.getLineMetrics(i);
actualHeight += metrics.ascent + metrics.descent + prevLeading;
prevLeading = metrics.leading;
}
actualHeight += 2 * gutter;
For a single line test with an inline image where textField height gives me 32, textHeight gives me 39, the calculated height (actualHeight above) is 34. For a multi-line test where height is 97.20, textHeight is 23.79, actualHeight is 97.15. This actual height includes the gutter on both sides, but strips out the trailing leading if there is any.