I have an object-based canvas where I draw circles, boxes, etc etc
One of the objects is text.
Right now, I have a rectangle area in which to render the text.
I find a suitable font size that fits within the area , and use .drawstring (API1) to draw the text within the rectangle, multiline if need be.
g.drawString(theText, left, top + g.textHeight, width)
Customers are asking for more flexibility - can I center the lines for example?
When using drawstring, I get left justified only.
(RTF would probably be best but too complex for now)
Does anyone have a method that would take long text and render into a rect as centred?
Or suggestions?
Drawing a title centered at the top of a PDF I use:
Var pagewidth As Double = pdf.PageWidth
Var textwidth As Double = pdf.Graphics.TextWidth("System Profile")
g.DrawText("System Profile",(pagewidth - textwidth)/2,30 + ascent)
I solved that in a very old project by parsing the input string and separating each word.
Do a Preprocess to figure out the width of each word and know when to go to the next line. Also calculating the X position of each line.
Then draw each word at the correct X position to allow the line to be centred.
You’ll need to draw the text line by line, each line offset by half its own length from the desired center.
Thanks. I knew this, was looking for a method that already had the code , as I had tried and failed.
I do now have a couple of good suggestions, and will be exprimenting with these shortly.
Thanks to all who reached out.
Public Function CountLines(extends s as String) As Integer
If s="" Then Return 0
Dim m As Integer = Max( CountFields( s, EndOfLine.Macintosh), CountFields( s, EndOfLine.UNIX), CountFields( s, EndOfLine.Windows), CountFields( s, EndOfLine.OSX))
Return m
End Function
Public Function NormalizeEndOfLines(extends s as String) As String
Dim unusedChar As String
Dim foundChar As Boolean = False
For i As Integer = 1 To 27
unusedChar = Chr(i)
If s.InStrB( unusedChar)=0 Then
foundChar = True
Exit For
End If
Next
If foundChar Then
s = ReplaceAllB( s, EndOfLine.Macintosh, unusedChar)
s = ReplaceAllB( s, EndOfLine.UNIX, unusedChar)
s = ReplaceAllB( s, EndOfLine.Windows, unusedChar)
s = ReplaceAllB( s, EndOfLine.OSX, unusedChar)
s = ReplaceAllB( s, unusedChar, EndOfLine.Macintosh)
End If
' check for EndOfLine with nothing after
While s.EndsWithVNS( EndOfLine.Macintosh)
s = Left( s, Len( s)-1)
Wend
Return s
End Function
Public Sub DrawCenteredString(extends graphicsContext as Graphics, textToDisplay as string, rectangleX as Integer, rectangleY as Integer, rectangleWidth as Integer, rectangleHeight as Integer)
// Draws text centered within a specified rectangle, with automatic line wrapping if needed
//
// Parameters:
// - graphicsContext: The Graphics object to draw on (extended by this method)
// - textToDisplay: The string to be drawn (empty strings are ignored)
// - rectangleX: Left edge X coordinate of the containing rectangle
// - rectangleY: Top edge Y coordinate of the containing rectangle
// - rectangleWidth: Width of the containing rectangle (must be > 0)
// - rectangleHeight: Height of the containing rectangle
//
// Behavior:
// - Text is horizontally and vertically centered within the specified rectangle
// - If text is too wide, it will be automatically wrapped to multiple lines
// - If text is too tall for the rectangle, display area is reduced by 4 pixels padding
// - Single lines are drawn using standard centering
// - Multiple lines are drawn with each line individually centered
// Only proceed if we have valid text and a positive width
If textToDisplay <> "" And rectangleWidth > 0 Then
// Normalize line endings to ensure consistent behavior across platforms
textToDisplay = textToDisplay.NormalizeEndOfLines
// Calculate the initial text dimensions
Dim textWidth As Integer = graphicsContext.TextWidth(textToDisplay)
Dim textHeight As Integer = graphicsContext.TextHeight(textToDisplay, rectangleWidth)
Dim availableDisplayWidth As Integer = rectangleWidth
// If text is too tall for the rectangle, reduce display area by 4 pixels padding
If textHeight > rectangleHeight Then
textWidth = rectangleWidth - 4
availableDisplayWidth = textWidth
textHeight = rectangleHeight - 4
End If
// Calculate horizontal center position for the text
Dim centeredX As Integer = Round(rectangleX + ((rectangleWidth - textWidth) / 2))
// Calculate vertical position (using TextHeight for baseline positioning)
Dim centeredY As Integer = rectangleY + graphicsContext.TextHeight
centeredY = Floor(centeredY)
// Determine how many lines the text currently has
Dim numberOfLines As Integer = textToDisplay.CountLines
// Handle single-line text that might be too wide
If numberOfLines = 1 Then
// Check if the text is too wide for the rectangle
If textWidth > rectangleWidth Then
// Calculate how many lines we'll need
numberOfLines = textWidth \ rectangleWidth
If numberOfLines > 1 Then
// Break the text into multiple lines by character count
// Calculate average character width and characters per line
Dim averageCharacterWidth As Integer = textWidth / Len(textToDisplay)
Dim charactersPerLine As Integer = rectangleWidth / averageCharacterWidth
Dim textLines() As String
Dim remainingText As String = textToDisplay
// Split text into chunks of calculated character length
Do
textLines.Append Left(remainingText, charactersPerLine)
remainingText = Right(remainingText, Len(remainingText) - charactersPerLine)
Loop Until Len(remainingText) = 0
// Rejoin the lines with proper line endings
textToDisplay = Join(textLines, EndOfLine)
End If
End If
End If
// Draw the text based on whether it's single or multi-line
If numberOfLines = 1 Then
// Single line: draw directly at calculated center position
graphicsContext.DrawText(textToDisplay, centeredX, centeredY, availableDisplayWidth, True)
Else
// Multi-line: calculate starting Y position and draw each line separately
Dim multiLineStartY As Integer = rectangleY + ((rectangleHeight - textHeight) / 2)
Dim lineHeight As Integer = graphicsContext.TextHeight(textToDisplay, availableDisplayWidth) / numberOfLines
// Draw each line individually, centering each line horizontally
For lineIndex As Integer = 1 To numberOfLines
Dim currentLine As String = NthField(textToDisplay, textToDisplay.DetectEndOfLine, lineIndex)
Dim currentLineWidth As Integer = graphicsContext.TextWidth(currentLine)
// Calculate horizontal center position for this specific line
centeredX = Round(rectangleX + ((rectangleWidth - currentLineWidth) / 2))
// Draw the current line at the calculated position
graphicsContext.DrawText(currentLine, centeredX, multiLineStartY + (lineIndex * lineHeight) - graphicsContext.TextDescent, availableDisplayWidth, True)
Next
End If
End If
End Sub
I logged a request for this a few years ago: #64833
Unfortunately, to split a paragraph into lines with word wrapping that works across multiple scripts / languages is complicated. We have probably spent over 40 days of time getting this working.
On macOS we use NSTextContainerMBS / NSLayoutManagerMBS / NSTextStorageMBS
On MS-Windows with the Gdi Xojo framework we use the Gdi and Uniscribe APIs via Declares
On MS-Windows with the Direct2D Xojo framework we use the Gdi and DirectWrite APIs via Declares