Jump to content
Alberto Fornés

Drawing text with GDI

Recommended Posts

I am rewriting an editor where I am looking to display some text on the screen, to which I can zoom and therefore increase or decrease its size, and ensure that what I display on the screen corresponds to what I get when printing what has been drawn. From what I've been reading, GDI doesn't always respect these relationships in terms of text sizes, although when printing it can display the result well. As I show in tests I've done where I want to draw a 10mm high text, when I double the height to 20mm, the width of the text is not equal to double, as seen in the image. Is there a way to get the ratio to hold and equal to what we get in print, thanks.

 

GDI_RENDERING_TEXT.thumb.png.4aba5d29cf1861af07c52ae29fa0f70f.png

 

I use this code to draw the text:

 

procedure TForm3.Button4Click(Sender: TObject);
var
 h: integer;
 TextOutSize: TSize;
 NewFont, OldFont: HFont;     // holds the old and new fonts
begin
 bmp:= TBitmap.Create(Image1.Width,Image1.Height);
 bmp.PixelFormat:= pf32bit;
 try
   bmp.Canvas.Brush.Color:= clWhite;
   bmp.Canvas.Brush.Style:= bsSolid;
   bmp.Canvas.FillRect(Rect(0,0,Image1.Width,Image1.Height));
   bmp.Canvas.Pen.Color:= clSilver;
   d:= 0;
   h:= 0;
   while d<= Image1.Width do
    begin
     if (h = 5) then
      begin
       bmp.Canvas.Pen.Color:= clGray;
       h:= 0;
      end else bmp.Canvas.Pen.Color:= clSilver;
     bmp.Canvas.MoveTo(d,0);
     bmp.Canvas.LineTo(d,Image1.Height);
     Inc(d,10);
     Inc(h);
    end;
   d:= 0;
   h:= 0;
   while d<= Image1.Height do
    begin
     if (h = 5) then
      begin
       bmp.Canvas.Pen.Color:= clGray;
       h:= 0;
      end else bmp.Canvas.Pen.Color:= clSilver;

     bmp.Canvas.MoveTo(0,d);
     bmp.Canvas.LineTo(Image1.Width,d);
     Inc(d,10);
     Inc(h);
    end;
   bmp.Canvas.Brush.Style:= bsClear;
   bmp.Canvas.Font.Color:= clBlack;

   SetMapMode(bmp.Canvas.Handle,MM_ISOTROPIC);
   SetViewportExtEx(bmp.Canvas.Handle,Image1.Width,Image1.Height,0);
   SetWindowExtEx(bmp.Canvas.Handle,Round(Image1.Width * 25.4 / GetDeviceCaps(bmp.Canvas.Handle, LOGPIXELSX)),Round(Image1.Height * 25.4 / GetDeviceCaps(bmp.Canvas.Handle, LOGPIXELSY)),0);

   // Create a 10 mm font 
   NewFont:= CreateFont(10, 0, 0, 0, FW_NORMAL, 0, 0, 0,
                           DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH or
                           FF_DONTCARE, 'Arial');
   OldFont := SelectObject(bmp.Canvas.Handle, NewFont);

   bmp.Canvas.Pen.Width:= 0;
   bmp.Canvas.Pen.Color:= clBlue;

   TextOut(bmp.Canvas.Handle,0,10,txt, Length(txt));
   MoveToEx(bmp.Canvas.Handle,0,10,nil);
   LineTo(bmp.Canvas.Handle,230,10);
   MoveToEx(bmp.Canvas.Handle,0,20,nil);
   LineTo(bmp.Canvas.Handle,230,20);

   GetTextExtentPoint32(bmp.Canvas.Handle, PWideChar(txt), Length(txt), TextOutSize);

   TextOut(bmp.Canvas.Handle,230,10,PWideChar('Width: ' + TextOutSize.cx.ToString), Length('Width: ' + TextOutSize.cx.ToString));

   SelectObject(bmp.Canvas.Handle, OldFont);
   DeleteObject(NewFont);

   //  Create a 20 mm font 
   NewFont:= CreateFont(20, 0, 0, 0, FW_NORMAL, 0, 0, 0,
                           DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH or
                           FF_DONTCARE, 'Arial');

   OldFont := SelectObject(bmp.Canvas.Handle, NewFont);

   bmp.Canvas.Pen.Width:= 0;
   bmp.Canvas.Pen.Color:= clBlue;

   TextOut(bmp.Canvas.Handle,0,30,txt, Length(txt));
   MoveToEx(bmp.Canvas.Handle,0,30,nil);
   LineTo(bmp.Canvas.Handle,230,30);
   MoveToEx(bmp.Canvas.Handle,0,50,nil);
   LineTo(bmp.Canvas.Handle,230,50);

   GetTextExtentPoint32(bmp.Canvas.Handle, PWideChar(txt), Length(txt), TextOutSize);

   TextOut(bmp.Canvas.Handle,230,30,PWideChar('Width: ' + TextOutSize.cx.ToString), Length('Width: ' + TextOutSize.cx.ToString));

   SelectObject(bmp.Canvas.Handle, OldFont);
   DeleteObject(NewFont);

   Image1.Picture.Assign(bmp);
 finally
  FreeAndNil(bmp);
 end;
end;

 

Share this post


Link to post

What you see is due to rounding errors when scaling individual character widths, these accumulate over the text width. If you want true WYSIWYG display you have to basically place each character individually at positions calculated from the font information. The API offers a large group of functions for this purpose, see Font and Text Functions

  • Like 2

Share this post


Link to post
11 minutes ago, PeterBelow said:

What you see is due to rounding errors when scaling individual character widths, these accumulate over the text width. If you want true WYSIWYG display you have to basically place each character individually at positions calculated from the font information. The API offers a large group of functions for this purpose, see Font and Text Functions

Thanks Peter, I will investigate in that direction. Do you have experience about the speed performance to calculate widths and drawing character individually?

Share this post


Link to post

In addition to the pixel grid (or rounding as Peter labeled it) there are other reasons why you can't expect a linear relationship between font and text size; The TrueType font rasterizer takes stuff like hinting and kerning into account when deciding how to rasterize a character and place it relative to the other characters. You should probably look into that if you are doing WYSIWYG.

Here's some light reading to get you started:

 

2 hours ago, Alberto Fornés said:

Do you have experience about the speed performance to calculate widths and drawing character individually?

The performance mostly depends on how smart you are about it (cache what you can) and what method you use to draw the text. If you just use the GDI the performance penalty should be negligible as you will just be doing the same thing as the GDI does internally.

If you want better performance and quality you can use one of the usual graphics libraries; They have the functionality to rasterize and render text.

image.thumb.png.8a88249606b9e961f571548e9b387592.png

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×