What are the implications of using Canvas.TextOut? - delphi

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.

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.

Related

Why do TPanel.Canvas.Font properties (accessed with protected hack) differ from TPanel.Font properties?

I'm using the DrawTextRotatedB function from Josef Švejk's excellent answer to the question How to draw text in a canvas vertical + horizontal with Delphi 10.2
to draw text vertically on a TPanel.
That component does not have a public Canvas property, so I'm using the protected hack to access it:
type
THackPanel = class(TPanel);
DrawTextRotated(THackPanel(PnlLeftLeft).Canvas,90, PnlLeftLeft.Width DIV 2, cVertDrawOffset, FLeftVertText)
with definition
procedure DrawTextRotated(ACanvas: TCanvas; Angle, X, Y: Integer; AText: String);
The procedure uses ACanvas.Font properties to draw text using ACanvas.TextOut.
I noticed that inside the procedure these properties were not what I had expected, e.g.
PnlLeftLeft.Font.Size = 20
PnlLeftLeft.Font.Ttyle = [fsBold]
THackPanel(PnlLeftLeft).Canvas.Font.Size = 10
THackPanel(PnlLeftLeft).Canvas.Font.Ttyle = []
It seems that I can easily 'fix' this doing THackPanel(PnlLeftLeft).Canvas.Font := PnlLeftLeft.Font; right before the procedure call,
but I'm still left with the question:
Why don't TPanel.Canvas.Font properties the mirror the TPanel.Font properties?
This is by design.
A complex control may write text with different fonts at different times and locations, and so Canvas.Font – which dictates the font of the next text-drawing operation – may vary even during the painting of a single "frame".
Self.Font, on the other hand, is the "primary font" of the control, which is often displayed in the Object Inspector (being a published property) and affected by the ParentFont property.
For example, a control's painting code might assign Self.Font to Canvas.Font at the beginning of each invocation and then possibly alter it slightly during painting (maybe to draw some parts in italics or boldface or some different colour).

Is it possible to change the inter-character spacing in a Delphi FMX TEdit control?

I am in the process of creating a custom component descended from a FMX TEdit control. One requirement is I need this control to be able to mimic/act like a combed field. This requires a max length and increased spacing between characters so the characters fall between the vertical lines. Please see image below for example.
The max length functionality is already part of the TEdit control but I am unable to find any information on how to increase the spacing between characters. I've looked into Delphi source code and have not come across anything that might be helpful. The font settings I came across were the typical font styles of bold/italic and font alignment of leading/center/trailing.
I also came across TFontStretch under TFontStyleExt but was not able to find out much more about it. Delphi's own website states "Embarcadero Technologies does not currently have any additional information." I'm not even sure this is related to what I'm looking for but I'm shooting from the hip on this one.
If anyone can point me in the right direction it would be much appreciated.
Thank you
I think that the best solution for your would be to use one of Monospaced fonts
If you can't find Monospaced ont that has desired character with to fit properly into your control you might want to use one of many font editing tools that you can find online to make necessary changes to your desired font.
And the best thing about using of Monospaced font is that you can use it in just about every FMX control that allows you to specify which font to use.
Do note that you will probably have to ship this custom font with your application and then dynamically register it at application start and unregister it at application close.
I used this to implement a serial key typing style:
procedure TForm4.Edit1Typing(Sender: TObject);
var
atext,tmp_str,d_str:unicodestring;
index:integer;
begin
if not(edit1.Text.Length>=30)then // 30 is the max length
begin
///////////////// take ' ' out (space between letters)
atext:=edit1.Text;
tmp_str:='';
if not(edit1.Text.Length=0) then
begin
for index := 1 to Length(aText) do
begin
if not(aText[index]=' ')then
begin
tmp_str:=tmp_str+aText[index];
end;
end;
end;
///////////////// now put the data back to the edit with the space
d_str:='';
if not(Length(tmp_str)=0) then
begin
for index := 1 to Length(tmp_str) do
begin
d_str:=d_str+tmp_str[index]+' ';
end;
end;
edit1.Text:=d_str;
edit1.CaretPosition:=length(d_str)-1;
end;
end;
Override your component Ontyping event handler, this code puts a 'space' between letters so they would be drawn within each rectangle.
Make sure that your font size would allow that.
This is the result.

Print fields alignment misaligned on report

I have the following PrintValue code that prints a line to the report (tbasedxreportlink). It prints two fields on one line in the header, the caption and m. The problem is that m is never aligned straight for multiple lines. It always prints all over the place.
How do I get it to align to the right or even print decimal aligned.
Printed Data
Caption One 4,685.33
Caption 2 4.99
Caption three 74,586.88
Caption 4 58.66
Code
procedure PrintValue(Caption, Value: string);
var
m: string;
s: string;
begin
m := FormatFloat(',0.00 ;(,0.00);0.00 ', StrToFloat(Value));
s := Format('%-24s %15s', [Caption, m]);
AReportLink.PrinterPage.PageHeader.LeftTitle.Add(s);
end;
The font used on the report is Segoe UI if it matters.
Thanks
The simplest way is using monospace (fixed-width) font, for example, Courier New or Lucida Console
I found no easy way to format the strings to get the desired effect. The main reason for that is the simplicity of using the LeftTitle, CenterTitle or RightTitle 'boxes' - they only allow simple string text to be inserted. Nothing fancy allowed not to mention the True Type Font issue.
In order to solve the problem I added a tPanel to the screen and dropped the all screen fields I needed to show up on the grid print to it. I added a tdxCustomContainerReportLink to link to that panel. I then used a tdxCompositionReportLink to print both the grid and the tdxCustomContainerReportLink (panel) as individual items when the print button was pressed by overwriting the current link code:
procedure TFrmViewAcct.dxBarBtnPrintClick(Sender: TObject);
begin
dxCmpntPrtrDetail.CurrentLink := dxCmpntPrtrDetailLink2;
inherited;
end;
Thus it prints the grid info then prints what ever is on the panel. Problem solved and you can see how this solution can be flexible.
Yes I could have easily changed to a True Type font but that is an ugly workaround as far as I am concerned especially where standardized fonts need to be observed.

Report Builder Spacing DBMemo Delphi

I am using a DBMemo (TPPDBMemo) component in reportbuilder within delphi. The Stretch property is true but the control doesn't always stretch itself out correctly within the region.
For example if there is lower case text, that dips downward, eg, chars like p,g,q... The bottom part of the text will get cut off if it's on the last line.
I tried adding an event to the onPrint to slightly grow the DBMemo when it prints,
procedure Tfrm.ppDBMemPrint(Sender: TObject);
begin
ppDBMem.Height := ppDBMem.Height + 10;
end;
But this didn't work.
Any thoughts on how this can be achieved. If I could simply add a border to the DBMemo that would be ideal, although I do not see that property anywhere.
Thank you!

Is there a way to disable font anti aliasing when using TextRect (aka ExtTextOut in GDI32) in Delphi?

I'm using a custom gauge, based on the example that came with Delphi (5 Enterprise). For those that don't know, it's like a smooth progress bar, but displays the percentage or value in the centre (vertically and horizontally) of the component.
To make sure the text is readable both when the gauge is filled and when it's empty, the text is displayed using inverted colours.
When font anti-aliasing is used, these inverted colours cause the edge of the font to appear in really crazy colours, ruining the look of the component.
Is there any way to disable font smoothing / anti aliasing for just this one component, or disable it, draw the text, then re-enable it?
My current workaround is to use a font that doesn't get smoothed, like "MS Sans Serif", but I'd like to use the same font as the rest of the UI for consistency.
Specifying NONANTIALIASED_QUALITY in the LOGFONT structure should turn antialiasing off:
procedure SetFontQuality(Font: TFont; Quality: Byte);
var
LogFont: TLogFont;
begin
if GetObject(Font.Handle, SizeOf(TLogFont), #LogFont) = 0 then
RaiseLastOSError;
LogFont.lfQuality := Quality;
Font.Handle := CreateFontIndirect(LogFont);
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
const
FontQualities: array[Boolean] of Byte = (DEFAULT_QUALITY, NONANTIALIASED_QUALITY);
var
Canvas: TCanvas;
begin
Canvas := (Sender as TPaintBox).Canvas;
SetFontQuality(Canvas.Font, FontQualities[CheckBox1.Checked]);
Canvas.TextOut(12, 12, 'Hello, world!');
end;

Resources