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.
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;