Drawing text as large as possible for given area

In my FMX application, I have some text I’d like to draw as large as possible in the available area. If that area resizes or the text or font type changes I need to redraw it.

I’ve done this before with a VCL application, but the measuring and drawing methods seem to be different in FMX and I haven’t been able to port my code across.

The TText component is what I’m using now in FMX to get the vertical and horizontal alignments I need, but automatically adjusting the font size to the largest value without clipping is where I’m stuck.

Please post a copy of your VCL code, to give us something to initially look at.

Lex Edmonds

1 Like

I assume you’ve looked at these functions:

and

This thread also explains more:

Ian, I have been playing around with TextWidth. Where I’m having trouble is working out how to calculate a suitable font size value. I’ll have a re-read of the links provided and see if I get any further. Thank you.

1 Like

This is the drawing procedure from my XE VCL project.

procedure DrawFormText(AForm: TForm; const AText: string);
const
FHeight = 1000;
var
TextRect: TRect;
MaxH, NewH: Integer;
BM: TBitmap;
begin
BM := TBitmap.Create;
try
BM.Canvas.Font.Assign(AForm.Font);
BM.Canvas.Brush.Color := AForm.Color;
TextRect := AForm.ClientRect;
BM.Height := AForm.ClientHeight;
BM.Width := AForm.ClientWidth;
BM.Canvas.FillRect(TextRect);
MaxH := BM.Height - 10;
BM.Canvas.Font.Height := -FHeight;
NewH := MulDiv(FHeight, BM.Width, Round(BM.Canvas.TextWidth(AText) * 1.1));
if NewH > MaxH then
NewH := MaxH;
BM.Canvas.Font.Height := -NewH;
DrawText(BM.Canvas.Handle,Pchar(AText),Length(AText),TextRect,
DT_VCENTER or DT_CENTER or DT_NOCLIP or DT_SINGLELINE);
BitBlt(AForm.Canvas.Handle,0,0,BM.Width,BM.Height,BM.Canvas.Handle,0,0, SRCCOPY);
finally
BM.Free;
end;
end;

Testing this, the value I’m getting back in LineTextWidth doesn’t change with different values set in the first line.

edText.Font.Size := 36; // or 12, 24, etc
LineTextWidth := edText.Canvas.TextWidth(AText);

I’m obviously overlooking something here. This is Delphi 13 BTW.

Scratch the TextWidth issue. That was a dumb error on my part. It now getting the correct result by using edText.Canvas.Font.Size instead of edText.Font.Size.

I’ve gotten this working now in FMX. The last hurdle was that Font and Canvas.Font are not interchangeable and some operations require one or the other, or both depending on whether you’re getting the TextWidth or setting the font size. This is under Windows. I’m yet to compile for Android.

I don’t know if this may be helpful, but here it is. Set up n FMX project with an image in the top quarter of the screen, a label in the 2nd top quarter of the screen (but set its font size to 50 - if you leave it as the default it won’t work). There is only one procedure for the form - resize. The label shows clearly the change in font size on turning the screen from landscape to portrait to landscape. The image shows nothing (other than getting painted beige), and I cannot work out why (and would appreciate ideas).

procedure TFontSizeMainForm.FormResize(Sender: TObject);
var
TextWidth : single;
Rect : tRectf;
Brush : tBrush;
begin
Image1.Height := 200;/// ClientHeight - 10;
Image1.Width := ClientWidth - 10;
Label1.Width := Image1.Width;
if Image1.Bitmap = nil then Image1.Bitmap.Create;

Image1.Bitmap.SetSize(ClientWidth - 10, 200);/// ClientHeight - 10);

Image1.Bitmap.Canvas.Font.Size := 100;

while true do
begin
TextWidth := Image1.Bitmap.Canvas.TextWidth(‘Hello, World!’);
if TextWidth < (Image1.Width - 10) then break;
Image1.Bitmap.Canvas.Font.Size := Image1.Bitmap.Canvas.Font.Size - 1;
end;

Brush := tBrush.Create(tBrushKind.Solid, tAlphaColorRec.Beige);
Rect.Top := 0;
Rect.Left := 0;
Rect.Right := Image1.Width;
Rect.Bottom := Image1.Height;

Image1.Bitmap.Canvas.BeginScene;
Image1.Bitmap.Canvas.FillRect(Rect, 1, Brush);

Image1.Bitmap.Canvas.FillText(Rect, ‘Hello, World!’, false, 1, [TFillTextFlag.RightToLeft], tTextalign.Leading, tTextAlign.Center);
Image1.Bitmap.Canvas.Endscene;

Label1.Text := ‘Hello World!’;
Label1.Font.Size := Image1.Bitmap.Canvas.Font.Size;

Brush.Destroy;
end;

It looks like you’re missing setting the font colour for the text?

There is no Font.Color (nor Font.Name). Where is font colour hidden please?

Maybe these?

Image1.Bitmap.Canvas.Fill.Color

Image1.Bitmap.Canvas.Font.Family

Image1.Bitmap.Canvas.Fill.Color := tAlphaColorRec.Black;
does nothing.
Nor does
Image1.Bitmap.Canvas.Stroke.Color := tAlphaColorRec.Red

From a Google AI result that I started playing with, mixed with some of your code. It draws, but needs a few things fixed

// Initialize the TImage bitmap size on form creation
if not Assigned(Image1.Bitmap) then
Image1.Bitmap := TBitmap.Create(Round(Image1.Width), Round(Image1.Height));
Image1.Bitmap.SetSize(Round(Image1.Width), Round(Image1.Height));


procedure TfmMain.UpdateImage(S: string);
var
ARect: TRectF;
TextWidth : single;
begin
// Ensure the image has a bitmap to draw on
if not Assigned(Image1.Bitmap) then
Image1.Bitmap := TBitmap.Create(Round(Image1.Width), Round(Image1.Height));

with Image1.Bitmap do
begin
// Drawing operations must be within BeginScene/EndScene
Canvas.BeginScene;
try
// Clear the canvas with a background color (e.g., White)
Canvas.Clear(TAlphaColorRec.White);

  // Configure text properties (font family, size, fill color)
  Canvas.Font.Family := 'Arial';
  Canvas.Font.Size := 100; // Set font size
  Canvas.Fill.Color := TAlphaColorRec.Black; // Set text color

  // Define the rectangle where the text will be placed
  ARect := TRectF.Create(0, 0, Image1.Bitmap.Width, Image1.Bitmap.Height); // Left, Top, Right, Bottom

  while true do
  begin
    TextWidth := Image1.Bitmap.Canvas.TextWidth(S);
    if TextWidth < (Image1.Width -10) then
      break;
    Image1.Bitmap.Canvas.Font.Size := Image1.Bitmap.Canvas.Font.Size - 1;
  end;

  // Call FillText to draw the text
  // Parameters:
  // 1. ARect: The bounding rectangle
  // 2. AText: The string to draw
  // 3. WordWrap: Boolean (False for this example)
  // 4. AOpacity: Single (1 for full opacity)
  // 5. Flags: TFillTextFlags (empty set [] for default reading order)
  // 6. ATextAlign: Horizontal alignment (TTextAlign.Leading is Left alignment)
  // 7. AVTextAlign: Vertical alignment (TTextAlign.Center aligns vertically)
  Canvas.FillText(ARect, S, False, 1, [], TTextAlign.Center, TTextAlign.Center);
finally
  Canvas.EndScene;
end;

end;
end;

Got it! That blasted “Scale” thing complicates the whole thing unnecessarily!

procedure TFontSizeMainForm.FormResize(Sender: TObject);
var
TextWidth : single;
Rect : tRectf;
Brush : tBrush;
Scaley : single;
begin
Image1.Height := 200;
Image1.Width := ClientWidth - 10;
if Image1.Bitmap = nil then Image1.Bitmap.Create;

Image1.Bitmap.SetSize(round(Image1.Width), round(Image1.Height));

// Image1.Bitmap.Canvas.Font.Family := ‘Arial’; - defaults to Roboto
Image1.Bitmap.Canvas.Font.Size := 100; // Set initial font size
Scaley := Image1.Bitmap.Canvas.Scale;

while true do
begin
TextWidth := Image1.Bitmap.Canvas.TextWidth(‘Hello, World!’);
if TextWidth < ((Image1.Width / Scaley) - 10) then break;
Image1.Bitmap.Canvas.Font.Size := Image1.Bitmap.Canvas.Font.Size - 1;
end;

Rect := tRectf.Create(0, 0, Image1.Width, Image1.Height);
Image1.Bitmap.Canvas.Fill.Color := tAlphaColorRec.Black;

Image1.Bitmap.Canvas.BeginScene;

Image1.Bitmap.Canvas.Clear(tAlphaColorRec.Beige);
Image1.Bitmap.Canvas.FillText(Rect, ‘Hello, World!’, false, 1, [TFillTextFlag.RightToLeft], tTextalign.Leading, tTextAlign.Leading);

Image1.Bitmap.Canvas.Endscene;
end;

I ended up deploying a few fonts with my app for the user to choose from.

Good thinking, David!
That started out as a 10-minute mickey mouse programme to give the idea of how to select the right font size. Unfortunately it turned into a 4 hour hair–tearing! I find wtith FMX that the simplest things take vastly more effort than expected, which may some day push me into learning Java (heaven forbid!). However, I now have a useful sample programme for my own reference in the future, and hope it was some use to the person who initiated this particular forum! Thanks for your help.