Calculating size of text before drawing to a canvas

13,322

Use the (almost) omnipotent DrawText function using an initial rectangle, and the flags DT_WORDBREAK (meaning that the string should be word-wrapped) and DT_CALCRECT:

procedure TForm1.FormPaint(Sender: TObject);
const
  S = 'This is a sample text, I think, is it not?';
var
  r: TRect;
begin
  r := Rect(10, 10, 60, 60);
  DrawText(Canvas.Handle,
    PChar(S),
    Length(S),
    r,
    DT_LEFT or DT_WORDBREAK or DT_CALCRECT);

  DrawText(Canvas.Handle,
    PChar(S),
    Length(S),
    r,
    DT_LEFT or DT_WORDBREAK);
end;

Due to the flag DT_CALCRECT, the first DrawText will not draw anything, but only alter the height of r so that it can contain the entire string S (or reduce the width of r if S happens to fit on a single line; in addition, if S contains a word that does not fit on a single line, the width of r will be increased). Then you can do whatever you wish with r, and then you can draw the string for real.

Try this, for example:

procedure TForm1.FormPaint(Sender: TObject);
const
  S: array[0..3] of string = ('Hi! How are you?',
    'I am fine, thanks. How are you? How are your kids?',
    'Fine!',
    'Glad to hear that!');
  Colors: array[boolean] of TColor = (clMoneyGreen, clSkyBlue);
  Aligns: array[boolean] of integer = (DT_RIGHT, DT_LEFT);
var
  i, y, MaxWidth, RectWidth: integer;
  r, r2: TRect;
begin

  y := 10;
  MaxWidth := ClientWidth div 2;

  for i := low(S) to high(S) do
  begin

    Canvas.Brush.Color := Colors[Odd(i)];

    r := Rect(10, y, MaxWidth, 16);
    DrawText(Canvas.Handle,
      PChar(S[i]),
      Length(S[i]),
      r,
      Aligns[Odd(i)] or DT_WORDBREAK or DT_CALCRECT);

    if not Odd(i) then
    begin
      RectWidth := r.Right - r.Left;
      r.Right := ClientWidth - 10;
      r.Left := r.Right - RectWidth;
    end;

    r2 := Rect(r.Left - 4, r.Top - 4, r.Right + 4, r.Bottom + 4);
    Canvas.RoundRect(r2, 5, 5);

    DrawText(Canvas.Handle,
      PChar(S[i]),
      Length(S[i]),
      r,
      Aligns[Odd(i)] or DT_WORDBREAK);

    y := r.Bottom + 10;

  end;

end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Invalidate;
end;

Screenshot

Share:
13,322

Related videos on Youtube

Jerry Dodge
Author by

Jerry Dodge

I'm a Delphi developer. I work for a software company which does solutions for retail management, including inventory, POS, reporting, BI, Tags, and more. It's been in Delphi since Delphi's been around. I am actively in Stack Overflow monitoring the Delphi tag, and looking for those questions I can answer and also contributing my time to keep Stack Overflow in order. I'm not an expert in anything, a jack of all trades rather. But I love to help people when I'm able to. I've known Delphi since about 2007 now, and before that, I had learned VB6. I havn't gone back to VB since I learned Delphi. I also taught myself QBasic and HTML as a kid. It hasn't been until the past 5 years that I've been diving into programming. Since then I've also become vaguely familiar with ASP.NET with C#, as well as some C# windows apps. But I'm not too fond of the whole .NET idea. .NET is good for web platforms and such, but not for win apps. My latest work has been with Delphi 10 Seattle mobile development. I'm still very raw on the subject, but see a huge potential behind it. My strengths: Understanding the bigger picture of projects Writing Custom Classes, Components, and Controls Code organization (within unit or namespace) Writing purely independent classes (as opposed to cross-referencing units or namespaces) User Friendly UI's Developer Friendly Classes Encapsulating layers of business logic My weaknesses: Lower-level coding (such as Assembly) Platform-specific design (using Firemonkey) Web Design It's always nice to know you're able to do something, even if you never use it.

Updated on June 04, 2022

Comments

  • Jerry Dodge
    Jerry Dodge almost 2 years

    I'm using Delphi 7. I'm more than familiar with using a canvas and drawing text to a canvas, and also using TCanvas.TextHeight etc. The problem arises when I want to implement Word Wrap. Not only do I need the best way to draw text to a canvas and have it automatically wrap to a given width constraint, but I also need to know how high (or how many lines) it will be after it's wrapped. I need to prepare another image before I draw the text, an image which needs to be just big enough to place the wrapped text. This is an attempt to replicate how an iPhone displays SMS messages, with a baloon on either side of the screen in a variable height scrolling box (TScrollingWinControl is my base).

    • David Heffernan
      David Heffernan over 12 years
      Use DrawText and let the system do the word wrapping
  • Server Overflow
    Server Overflow over 10 years
    Hi Andreas - Great answer as usual!