问题
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.
- 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).
- 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