419 views

Centering text lines top and bottom in a canvas

1. 9 months ago

Patrick B

8 Jan 2019
Edited 9 months ago

I can vertically center 1 or 2 lines of text in a canvas pretty accurately, but when it gets to 3 or 4 (or 5?) I can't seem to get the right recipe to vertically center it.
Here is what I'm using. The 3rd line is in the ballpark but not quite right. 4 tends to run off the top of the canvas.

if ubound(numLines) = 0 then   // there is only 1 line
stringHeight = (g.height + g.TextAscent)/2
elseif ubound(numLines) = 1  then    // there is 2 lines
stringHeight = (g.height + g.TextAscent)/2 -  g.TextAscent
elseif ubound(numLines) = 2  then   // there is 3 lines
stringHeight =  (g.height + g.TextAscent)/2 - 1.5 * g.TextAscent
end if

2. Tim P

8 Jan 2019 Pre-Release Testers Rochester, NY

TextAscent isn't the correct property, it measures from the baseline to the top of the tallest character. StringHeight measures the whole height of the string. Also consider that each line might be a different height.

Perhaps this might be closer? (I haven't tested it, post-editor code)

dim dHeight as Double

for i as Integer = 0 to numLines.Ubound
dim sThis as String = numLines(i)
dHeight = dHeight + g.StringHeight(sThis, g.Width)

next

// dHeight is now the height of all the lines.
3. jim m

8 Jan 2019 Pre-Release Testers, Xojo Pro piDog.com

You could join and then measure the whole string to find the height and then draw based on the top+textascent...

Dim lines() As String=Array("line1","LINE2","----","line3")

Dim joined As String=Join(lines,Chr(13))

Dim firstlineY As Double=g.Height/2-g.StringHeight(joined,g.Width)/2+g.TextAscent

g.DrawString(joined,g.Width/2-g.StringWidth(joined)/2,firstlineY)

and if you also wanted to center each line horizontally...

For i As Integer=0 To lines.Ubound
g.DrawString(lines(i) , g.Width/2-g.StringWidth(lines(i))/2 ,  firstlineY+g.TextHeight*i)
next
4. Patrick B

8 Jan 2019

Tim,

forgot to note that "numLines" is a string array populated by splitting "myText" by a marker | where the lines are to break.
so I use numLines = numLines = split(myText,"|") just to figure out how many lines there are. So I really need to join the lines first. they're already joined in myText.
Would it be dHeight = dHeight + g.StringHeight(myText, g.width) and that would get me the proper line height? Once I do get the proper line height, how do I properly calculate by the number of lines in myText? Is it just a straight dHeight * numLines ?

Jim,

That code might come in handy when I eventually allow each line it to be centered as well. Thanks!
Was also wondering if it were possible to auto-wrap text when painting into a canvas.

5. Dave S

8 Jan 2019 San Diego, California USA
Edited 9 months ago

If all the text is the same font and font size then

dHeight=g.TextHeight * numLines

and if the is the case then to center should be something like this (off the top of my head)

dHeight=g.TextHeight * (eachLine.ubound+1) // assume you split the text
yTop = yCenter - (dHeight/2) + g.textAscent // where YCenter is the point you want to center the text on Vertically
for i=0 to eachLine.ubound // assume you split the text
g.drawstring eachLine(i),x,yTop
yTop=yTop+g.textHeight
next i
6. Patrick B

8 Jan 2019

thanks. I'll play around with that. getting centered vertically will be the key.

7. Tim P

8 Jan 2019 Pre-Release Testers Rochester, NY

I'm going to break out your questions one at a time just so it's easier to answer.

@Patrick Besong Would it be dHeight = dHeight + g.StringHeight(myText, g.width) and that would get me the proper line height?

The original calculation I offered adds the individual line heights (they can be different depending on the characters in the line!) so that at the end of the for loop dHeight is the total correct height for the block of myText.

@Patrick Besong Once I do get the proper line height, how do I properly calculate by the number of lines in myText?

The example I wrote calculates the whole block of text height. I had written my example assuming numLines was your array of strings to write. If you want each individual line, draw the string after the dHeight is calculated for that line within the loop.

@Patrick Besong Is it just a straight dHeight * numLines ?

No, you can't assume that each line height is the same. A line's height is affected by the characters in the line. Letters like y and j extend below the baseline which is why you can't use TextAscent for this calculation. Letters like h and i are taller than n, m, c, v. I'm not a languages expert, but I wouldn't trust that a line would always have letters that are tall.
( @Michel B is probably dying inside reading my butchering of this).

I would recommend calculating and drawing each line individually. This will make it easier for you to implement horizontal alignment later down the road. Insert a DrawString underneath the dHeight calculation in my example loop to get started down that path :)

8. Dave S

8 Jan 2019 San Diego, California USA

@Tim P No, you can't assume that each line height is the same.

if the TextFont is the same the Height is the same.... TextHeight is from top to bottom (below baseline), TextAscent is from top to baseline.... but the metrics of HEIGHT are constant which is why there is a TextHeight property. Width however is a totally different story depending on the font

9. Tim P

8 Jan 2019 Pre-Release Testers Rochester, NY

Ah, I stand corrected! Thanks for the update :) 