21

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

enter image description here

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:

enter image description here

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:

enter image description here

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%

enter image description here

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.

enter image description here

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

enter image description here

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.

Community
  • 1
  • 1
Craig
  • 1,874
  • 13
  • 41
  • Start a new VCL project, put thre lines of code in the OnPaint handler of the form that would duplicate the problem. Can you duplicate? I can't with W7. If you can't also, there's no question here without a case. – Sertac Akyuz Aug 12 '15 at 15:31
  • 2
    `TextOut` internally uses `ExtTextOut`. – Jerry Dodge Aug 12 '15 at 15:43
  • @SertacAkyuz test demo added, I am using Windows 10. – Craig Aug 12 '15 at 16:00
  • @Craig thanks for the case, don't forget Edit1.Font := FFont. Looks like this doesn't happen in W7. Unlikely it is that you'll get a satisfactory answer.... – Sertac Akyuz Aug 12 '15 at 16:08
  • @JerryDodge might be on to something. `ExtTextOut` has code inside to [control inter-word spacing](https://parnassus.co/drawing-fully-justified-text-to-a-canvas/). Maybe it does not always match `DrawText(Ex)`. If there is a difference, why not use use `DrawText` instead, so you are sure it always matched? Also, note that `DrawTextEx` has the `DT_EDITCONTROL` flag to exactly mimic edit control draw rendering, which again has something to do with the character width - possibly related to what you're seeing. – David Aug 12 '15 at 16:10
  • I find it very hard to see differences in your screenshots. Can you include images where the text is zoomed in, and you can draw a line lining up each type of rendering so we can clearly see where and what the difference is, please? (Also it's quite confusing to have "Blah Blah" highlighted, but actually it's "Some Text" you want us to look at. So, better screenshots please :)) – David Aug 12 '15 at 16:13
  • @SertacAkyuz Edit1 was using the correct font set up from the Object Inspector, have since tried assigning to the same as `FFont` and it looks the same as `DrawText` still. – Craig Aug 12 '15 at 16:17
  • 1
    I vaguely recall having noticed this difference in the past. It's as if those two functions use slightly different kerning algorithms internally. Might be a case of different rendering flags, though. It will be interesting to see what is found out here. – 500 - Internal Server Error Aug 12 '15 at 16:20
  • @DavidM the image showing the `Blah Blah` text was based off my other question hence the Example 2. I have now added a zoomed image. – Craig Aug 12 '15 at 16:29
  • Craig, I added a zoomed-in image that highlights the differences - what I was asking for above ("Can you include images where the text is zoomed in, and you can draw a line lining up each type of rendering so we can clearly see where and what the difference is, please?") You're lucky I had some time while waiting for dinner to cook :) Now it's actually indicated, yes, I can see a 1px kerning difference. I suspect the reason is related to how `ExtTextOut` allows for altering spacing (see my other earlier comment & link.) – David Aug 12 '15 at 17:12
  • (Continued) I'm not sure anyone will be able to tell you for sure, unless they have access to the Windows source code - which some people do. Meanwhile, to solve your problem, is it possible for you to use DrawText instead of Canvas.TextOut, so your editor matches the normal rendering completely accurately? – David Aug 12 '15 at 17:13
  • For the record, I haven't used `TextOut` ever since I learned how to use `DrawText`. – Jerry Dodge Aug 12 '15 at 17:37
  • @Craig - That was required for proper problem reproduction. – Sertac Akyuz Aug 12 '15 at 18:07
  • 1
    Whatever the definite answer is, this is a nice question. – Rudy Velthuis Aug 13 '15 at 15:11

1 Answers1

11

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.

enter image description here

Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • Excellent answer! I wonder why then some people have not been able to reproduce this problem whilst others have? Maybe that will become more clear once I read more into kerning and some of the links posted in the other comments. In fact I would never of even noticed or questioned the difference between TextOut and DrawText had it not been for an issue I had with the custom component I have been working on, it's not even something you would typically look for with a screen magnifier to look for differences, just this one time I needed to and I was a little shocked with what I saw. – Craig Aug 15 '15 at 20:02
  • 1
    Since kerning depends on different things combined, creating reproducible test case can be hard if you don't know exactly what you are looking for. Even using different text for rendering is enough to miss it. In your case using letter `T` was the most visible trigger. – Dalija Prasnikar Aug 15 '15 at 20:25
  • Observed difference also depends on OS version, which doesn't quite fit your explanation. I think you're misinterpreting the documentation of ExtTextOut and *default spacing* is the kerning info of the specific font, which the function fails to honor. IMO what we're observing is simply a bug of the OS. – Sertac Akyuz Aug 15 '15 at 20:44
  • 1
    @Sertac It is possible that between Windows versions interpretation what default spacing between characters mean is changed and it can be either _default kerning_ or _no-kerning_. But also font definitions between Windows versions **do differ**. For instance, Segoe UI on Windows 8.1 is rendered without kerning (and looks the same) using both methods, and Arial is rendered differently. Segoe UI on Windows 10 is rendered with kerning using `DrawText` and without it using `ExtTextOut`. On Windows 8.1 and Windows 10 `ExtTextOut` definitively renders text without kerning -call it a bug or a feature – Dalija Prasnikar Aug 15 '15 at 21:39
  • 1
    @SertacAkyuz Add Windows 7 to the list where `ExtTextOut` renders text without kerning, so I would call it a feature and I didn't misinterpret the documentation. – Dalija Prasnikar Aug 15 '15 at 21:49