What are the implications of using Canvas.TextOut?

谁说我不能喝 提交于 2020-07-17 09:30:16

问题


Introduction

My question comes from a rather interesting problem I have been dealing with for the past few days. I recently asked a question regarding Writing a custom property inspector - How to handle inplace editor focus when validating values?

I have since made some nice progress with my control such as adding a divider in the middle to separate between Name and Value rows, and importantly the divider can be used to resize the two columns.

Here is where my problems started, having the inplace editor visible whilst resizing the divider caused a slight slow down on my control. So I further changed the code to only show the inplace editor if the divider is not been resized. So essentially, I used Canvas.TextOut to draw my values as strings, if a row is selected then the Inplace editor is shown above. The inplace editor becomes hidden if the divider is been resized, once the resize operation has complete the inplace editor becomes visible again.

Whilst this solved the slight slowdown issue I mentioned, I was faced with a new problem in that the text from the inplace editor (which is basically a TEdit) differed slightly to the text that I was drawing using Canvas.TextOut


Example 1

The difference is quite subtle but if you look close enough you can just see it:

fig.1 Canvas.TextOut

fig.2 DrawText

You may need to use a screen magnifier to look more closer, but with the SomeText row it is more noticeable in that the spacing between Some and Text and also between the T and e in Text is slightly different.


Example 2

A slightly better example is perhaps comparing between Canvas.TextOut and DrawText to the inplace editor (TEdit) text:

fig.3 Comparison

As you can see the difference here is much more prominent. The string True clearly shows much larger spacing between the text characters when using Canvas.TextOut, where as the DrawText and inplace editor render text exactly alike.

When I was using Canvas.TextOut I was getting all kinds of horrible text mismatches between resizing my inspector divider and showing and hiding the inplace editor. Had I not experimented and tried alternative text drawing methods I don't think I would have ever realised the difference and found a solution. It is important to know that I was using the exact same Font settings when drawing my text to the canvas as the Font I had defined for the inplace editor.

Now that I am using DrawText instead of Canvas.TextOut everything is working in unison with the inplace editor and exactly how I want it to.


Question

My question is what makes Canvas.TextOut render text so differently to DrawText? From my example and dealing with my current problem, it is clear that Canvas.TextOut does not render the text in the same way that a TEdit with the same Font settings does, but DrawText does render text seemingly the correct way.

This makes me question the use of Canvas.TextOut, if it does not render text correctly should I always look to use DrawText instead?


Test Demo

You can test this for yourself with the following code:

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    FFont: TFont;
    FRect: TRect;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FFont       := TFont.Create;
  FFont.Color := clNavy;
  FFont.Name  := 'Segoe UI';
  FFont.Size  := 9;
  FFont.Style := [];
  FRect       := Rect(10, 30, 100, 100);

  Canvas.Font.Assign(FFont);
  Edit1.Font.Assign(FFont);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FFont.Free;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin 
  Canvas.TextOut(10, 10, 'Canvas.TextOut: [True]');
  DrawText(Canvas.Handle, PChar('DrawText: [True]'), Length('DrawText: [True]'), FRect, DT_LEFT);
end;

With the above running on a completely new VCL Project, the result I get is as follows:

fig.4 Test Demo

Again notice the spacing in the string True when using Canvas.TextOut, from my end it is clearly different to DrawText and the way that the TEdit draws its text.

The below is the same image as fig.4 but zoomed in at 400%

fig.5 Test Demo zoomed at 400%

Noticeable differences are seen between the T and e in Text and also T and r in True.

fig.6 The word 'Text' zoomed in at 400% with guidelines

You can see the kerning between the T and e is one pixel closer with DrawText than with Canvas.TextOut (which uses ExtTextOut.)

fig.7 The word True zoomed in at 700% with guidelines

You can see the kerning between the T and r is one pixel closer with DrawText and the Inplace Editor (TEdit) than with Canvas.TextOut (which uses ExtTextOut.)


I have tested several different fonts and here are my findings:

Good:

Arial, Cambria, Candara, Comic Sans MS, Consolas, Courier, Courier New, Fixedsys, Georgia, Lucida Console, Lucida Sans Unicode, Microsoft Sans Serif, Tahoma, Terminal and Times New Roman.

Bad:

Calibri, Corbel, Myriad Pro, Segoe UI, Trebuchet MS and Verdana.

The good fonts are the ones that appear to render text the same way as DrawText and the Inpace Editor (TEdit) controls do using Canvas.TextOut. The bad ones show that Canvas.TextOut renders text slightly different to the other methods.

There may some clue here although I am not too sure, but I am adding it anyway just in case.


回答1:


Observed difference is due to using different WinAPI text rendering functions and their behavior. Specifically character kerning

In typography, kerning (less commonly mortising) is the process of adjusting the spacing between characters in a proportional font, usually to achieve a visually pleasing result. Kerning adjusts the space between individual letter forms, while tracking (letter-spacing) adjusts spacing uniformly over a range of characters.

  1. DrawText

The DrawText function draws formatted text in the specified rectangle. It formats the text according to the specified method (expanding tabs, justifying characters, breaking lines, and so forth).

  1. ExtTextOut (used by Canvas.TextOut)

ExtTextOut declaration:

BOOL ExtTextOut(
  _In_       HDC     hdc,
  _In_       int     X,
  _In_       int     Y,
  _In_       UINT    fuOptions,
  _In_ const RECT    *lprc,
  _In_       LPCTSTR lpString,
  _In_       UINT    cbCount,
  _In_ const INT     *lpDx
);

If the lpDx parameter is NULL, the ExtTextOut function uses the default spacing between characters. The character-cell origins and the contents of the array pointed to by the lpDx parameter are specified in logical units. A character-cell origin is defined as the upper-left corner of the character cell.

Basically DrawText will automatically draw formatted text and that includes adjusting spacing between characters (kerning), while ExtTextOut will by default use default spacing between characters (no-kerning). If you want to adjust spacing between characters you will have to calculate and provide kerning array (lpDx) parameter.

Those differences are especially visible with some character combinations like T and small letters that visually fit under T, or AV where one V fits over A. Different fonts also have different default kernings and that is reason why some fonts have visually same rendering using both functions and some not. Kerning also depends on font size. For instance characters AV rendered with Arial at 9 pt will have same output with both functions, while Arial at 12 pt will result in different outputs.

First line in following image is drawn with no-kerning using ExtTextOut and second line with automatic kerning using DrawText.



来源:https://stackoverflow.com/questions/31968771/what-are-the-implications-of-using-canvas-textout

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!