I just did some single stepping through the text object code.
Let's say we're using the bluehigh.ttf font (because I am) with a character size of 30 (the default).
The baseline for the text (which characters sit on) will be placed at y=30.
The code loops over every character in the current string and gets it's glyph.
The glyph is a description of the character in the font, and includes the top and bottom offsets from the baseline.
The glyph for the letter "T" has a top of -17 and a bottom of 0. So it sits on the baseline and goes 17 above it.
This is then added to the baseline value of 30. So the bounds of the text object will be top=13, height=17 (that's what you get from the getLocalBounds if "T" is the only letter in the string).
The glyph for the letter "h" has a top of -18 (it's a fraction taller than a T) and bottom of 0, so it's local bounds are top=12 height=18.
The glyph for the letter "p" has a top of -13 and a bottom of 6, it drops below the baseline. It's local bounds will be top=17 height=19.
And so on.
The minimum and maximum top and bottom (and therefore height) are found for all the characters in the current string. That's what getLocalBounds actually returns, for the string "Thp" it would be top=12 (from the "h") and height=24 (the "p" has a bottom of 36).
Hopefully that's correct, I'm running on insomnia and caffeine at the moment.
So overall: local bounds are based on the characters actually in the current string (updates whenever the string changes). Therefore textRect.top + textRect.height / 2.0f will give you the centre of the visible characters, not the centre of all potential (but unused) characters.