windowswinapi

Draw a vertically centered multi-line string using WinAPI's "DrawText" function


I am trying to draw a word-wrapped string within centered both vertically and horizontally within a bitmap using WinAPI's DrawText function.

The problem is that if text is longer than the available space and "END ELLIPSIS" (...) is added to a cropped string, the reported drawing coordinates returned when using the "DT_CALCRECT" report the uncropped number of lines which messes with the vertical centering calculations.

I read many posts on this, and thought that "Delphi - Draw text multiline in the centre of a rect" may hold the answer, but it didn't (screenshot of the code output using the sample in the linked question http://zoomplayer.com/pix/font_vcenter.jpg). The author of the accepted answer suggested I create a new question so here it is.

For quick-reference, here is a slightly simplified (removing unrelated code) text rendering code from the linked accepted answer:

procedure DrawTextCentered(Canvas: TCanvas; const R: TRect; S: String);
var
  DrawRect: TRect;
  DrawFlags: Cardinal;
begin
  DrawRect := R;
  DrawFlags := DT_END_ELLIPSIS or DT_NOPREFIX or DT_WORDBREAK or
    DT_EDITCONTROL or DT_CENTER;
  DrawText(Canvas.Handle, PChar(S), -1, DrawRect, DrawFlags or DT_CALCRECT);
  DrawRect.Right := R.Right;
  if DrawRect.Bottom < R.Bottom then
    OffsetRect(DrawRect, 0, (R.Bottom - DrawRect.Bottom) div 2)
  else
    DrawRect.Bottom := R.Bottom;
  DrawTextEx(Canvas.Handle, PChar(S), -1, DrawRect, DrawFlags, nil);
end;

As you can see from the screenshot, the problem is after the initial call to DrawText with the "DT_CALCRECT" flag to measure the output height for later vertical centering, rendering the string "Trending in: Worldwide" returns a DrawRect.Bottom value representing 3 lines of text even though only 2 lines are drawn, breaking the vertical centering code.


Solution

  • It took several years, but I finally found a working solution, I hope this helps someone.

    function DrawVerticalCenteredText(Canvas: TCanvas; const Text: WideString; X, Y, Width, Height, Flags : Integer): Integer;
    var
      DrawRect   : TRect;
      cRect      : TRect;
      Format     : UINT;
      fontHeight : Integer;
      fontLines  : Integer;
    begin
      cRect      := Bounds(X, Y, Width, Height);
      DrawRect   := cRect;
    
      fontHeight := Canvas.TextHeight('0');
      fontLines  := Trunc(Height / fontHeight);
    
      Format     := DT_WORDBREAK or DT_NOPREFIX or DT_END_ELLIPSIS or DT_EDITCONTROL or Flags;
    
      // Calculate text height and modify string if necessary
      DrawTextExW(Canvas.Handle, PWideChar(Text), -1, DrawRect, Format or DT_CALCRECT, nil);
    
      DrawRect.Right := cRect.Right;
      if DrawRect.Bottom < cRect.Bottom then
      Begin
        OffsetRect(DrawRect, 0, (cRect.Bottom - DrawRect.Bottom) shr 1);
      End
        else
      Begin
        If DrawRect.Bottom <> cRect.Bottom then
        Begin
          DrawRect.Top := cRect.Top+((Height-(fontHeight*fontLines)) shr 1);
          DrawRect.Bottom := cRect.Bottom;
        End;
      End;
    
      // Draw the text
      Result := DrawTextExW(Canvas.Handle, PWideChar(Text), -1, DrawRect, Format, nil);
    end;