Site hosted by Angelfire.com: Build your free website today!

Home
DelphiZeus
5. Device Context and Using Fonts
Basic Drawing Functions

Home



The Programs you create in Delphi have many of the standard windows graphical operations (creating fonts, brushes, pens, and device contexts) done for you automaticly. When you set a font on a TEdit, you do not see the code used to create and set the parameters for that font. You do not have to destroy the fonts, brushes or pens that are used for Canvas drawing. Many VCL components have a "Canvas" to use for graphical procedures like LineTo, TextOut, FillRect, Draw and many others. TCanvas is a way for Delphi to wrap the Windows "Device Context" and many of the drawing functions it supports. You may want to read the Win32 API Help for "Device Contexts" (Help Index "Device Contexts"), which begins with "A device context is a structure that defines a set of graphic objects and their associated attributes, and the graphic modes that affect output.". And click the >_> at the top of the help program to read the sequence of pages. Because there are many types of video cards and monitors and the drivers for them, it would be imposible to write code to cover all the video cards, so windows has a Device Context to translate graphical functions for the video card driver to use. "One of the chief features of the Microsoft Win32 application programming interface (API) is device independence." This Fonts program will demonstrate some essential begining graphical functions using TextOut( ), FillRect( ), Rectangle( ), and Ellipse( ). But this is only a begining, there's a great deal of graphics functions and options not covered here. In this page there will be a short description for some of these functions and objects starting with fonts. But you may want to read the Win32 API Help for fonts also.


Device Context Types
The Device in "Device Context" is a term from early DOS and Windows programming for the video card device or a printer device. A Device Context (DC) means that the windows system will put an API "wrapper" around the device driver for your video card or printer to do graphical operations with that device. The low level code needed to draw a line for the driver of a printer, and the driver of a 1 megabyte video card, and the driver of a 128 megabyte video card, would be very different. The low level driver code may also be very different, even if you just switch your computers display color depth from Full Color to 256 color (if it supports both of these display modes). But by using the API Device Context you can draw a line on on any of these with the LineTo( ) function, without checking for, or setting any device drivers. . . . Since Windows is a "Visual" system, it has extensive Graphics Device Interface (GDI) functions, which will take time to learn. So just take it one step at a time.
    In Win32 API there are four types of device contexts: display, printer, memory (or compatible), and information. Each type has a specific purpose, described by the following table.
Device contextPurpose
Display
Printer
Memory
Information
Supports drawing on a video display.
Supports drawing on a printer or plotter.
Supports drawing on a bitmap.
Supports the retrieval of device data, but no drawing.

To use a DC in GDI functions, you first have to Get or Create a windows system "Device Context Handle" for it, with a function like GetDC( ) or others. If you are drawing in the WM_PAINT message of the Window Proc then the DC handle is obtained with BeginPaint( ). When these functions return a device context handle, Windows initializes the Device Context with default graphic objects (black pen, white brush), attributes (text align is Top-Left, text color is Black), and modes (BkMode is OPAQUE). Any drawing operations on this DC use these defaults unless one of the GDI functions is used to select a new object, change the properties of the current object, or select a new mode.

Device Context's Graphic Objects
A Device Context needs methods to get it's pixel content and change it's pixels (paint and draw). The pen, brush, font, bitmap, palette, region, and path associated with a device context are referred to as the device context's Graphic Objects and are used for drawing and painting. When an application gets or creates a device context, Windows automatically stores a set of default objects in it. (There is no default bitmap or path.) A program can get the attributes of the DC's graphic objects by calling the GetCurrentObject( ) and GetObject( ) functions. You can look at your local API Help or web page at MSDN-GetObject. The program can create and change these objects by selecting a new one into the Device Context by calling the SelectObject( ) function. These new selected objects will be used in drawing functions to change the pixels of the DC. A program must always have a method to call DeleteObject( ) for Every Graphic Object that it creates. Failing to delete objects may causes serious performance problems. These Graphic Objects (Fonts, Pens, Brushes, Bitmaps, Palettes, Regions and Paths) are System objects and the Information Records (memory) for these are in the system's memory, not your programs memory, so if these objects are not Deleted then they will still use memory even after your program closes.

Use Stock Objects
To save on resources, it's a good idea to use GetStockObject( ) to get windows system stock fonts, brushes, or pens when possible. However, there is very little variety avaiable in the stock objects. Look at your local API Help for index "GetStockObject" or the web page
MSDN-GetStockObject, to see the list of stock objects. The BLACK_BRUSH, WHITE_BRUSH, BLACK_PEN, WHITE_PEN and ANSI_VAR_FONT are very useful stock objects.
hFont := GetStockObject(ANSI_VAR_FONT)   will usually get the MS Sans Serif font.
Stock Objects do NOT need to be deleted.

Drawing on a DC
To draw some text on your Main Forms DC you have to get the DC's Handle from the system, then do the text drawing and then Release that DC. Here are three very important Device Context functions -
function GetDC(
         hWnd: HWND // handle of window
         ): HDC; // Handle of Device Context

function TextOut(
         DC: HDC; // Handle of device context 
         X: Integer; // x-coordinate of starting position
         Y: Integer; // y-coordinate of starting position
         Str: PChar; // address of text string
         Count: Integer // number of characters in string
         ): BOOL; // for success, the return value is True

function ReleaseDC(
         hWnd: HWND; // handle of window
         hDC: HDC  // handle of device context 
         ): Integer; // for success, the return value is 1
You might look at your local API Help for these functions or go to these web pages
MSDN-GetDC . . . MSDN-TextOut . . . MSDN-ReleaseDC

Here is an example of code to use these -
procedure DrawText;
  var
  MainDC: HDC;
begin
MainDC := GetDC(hMainForm);
TextOut(MainDC, 20, 30, 'Text on Main Form', 17);
ReleaseDC(hMainForm, MainDC);
end;
This will draw black text on a white background using the system font. Notice that unlike Delphi VCL TCanvas and TFont, the TextOut function does not have parameters for the Font name or size used, the color of the text, or the background color of the text. These text drawing attributes are part of the Logical Font creation and the Device Context (MainDC) and are set to the default values (font=System Font, text color=Black, background color=White, text align=Top-Left, intercharater spacing=0) when you call GetDC( ) to intitialize a Device Context. You will need to assign a font to a new DC to change it from the default system font and assign a font color and other DC charteristics to get the look you want. . To assign a graphic Object, like a hFont, to a Device Context, you would use the API SelectObject( ) function -
function SelectObject(
         DC: HDC; // Handle of Device Context 
         hObj: THandle // Handle of Object
         ): THandle; // Handle of the Object being replaced
You might look at this in your local API Help or web page at MSDN-SelectObject . . . The hObj parameter is the handle of the graphic object to be "Used" with that hDC, in graphic operations that require that type of object. For the Font, Pen, and Brush objects, the selected object will be used when drawing is done that requires that object. The Bitmap, Palette, Region, and Path objects have a different "Meaning" for being selected in to a hDC. There is more about setting the text drawing properties of a Device Context latter in this lesson at Drawing Text on the HDC
Here is some code to show how to change the Font drawing on a DC -
procedure DrawText2;
  var
  MainDC: HDC;
begin
MainDC := GetDC(hMainForm);
  {use DC adjustment fuctions to change the 
  drawing properties of the DC}
SelectObject(MainDC,GetStockObject(ANSI_FIXED_FONT));
  // the ANSI_FIXED_FONT will now be used for font drawing
SetTextColor(MainDC,$0000FF);
  // Text drawing color is now Red
SetBkMode(MainDC,TRANSPARENT);
  // Text background will Not be drawn
TextOut(MainDC, 20, 30, 'Text on Main Form', 17);
ReleaseDC(hMainForm, MainDC);
end;
It is recommended and common practice, when using a Display Device Context (like the MainDC above), to get or create this DC and then release this DC as soon as posible. Since the Video Display is a shared resource, use it only when nessary, getting a DC with GetDC( ) is a very fast operation, so is ReleaseDC( ).
IMPORTANT:   ALWAYS make sure that you call ReleaseDC( ) for each call to GetDC( ).

If you are used to using the Delphi TCanvas, then it may be helpful to remind you that any change in the Device Context drawing properties are NOT persistant and will be lost as soon as you call ReleaseDC( ). So if you change the DC's text color to Red and then Release the DC, the next time you call GetDC( ) for the same window, this DC will now have the default black text color, not red.


WM_PAINT message and drawing

The WM_PAINT message is sent to your MessageProc through the GetMessage( ) function when ever the system thinks that something needs to be refreshed by redrawing it or some part of the client area (the non-client area gets the WM_NCPAINT message which you usually do not have to use). Whenever a portion of a client area (main window or control) is covered and the revealed a WM_PAINT message will be sent. The exceptions to this is when the Cursor or a cursor draged icon is over the window, the system saves a bitmap of that area and then redraws that Cursor covered area with the saved bitmap without sending a WM_PAINT. Since the entire client area may not need to be repainted the system uses an internal "paint information record" for each window getting the WM_PAINT, which contains the smallest rectangle that needs to be repainted. This area is called the "invalid" or "Update" region. If a WM_PAINT message is sent and this Invalid Region changes before your app can process the WM_PAINT message, windows will update the Invalid Region, but will not send another WM_PAINT message, so there can only be One WM_PAINT message in your message queue.

Almost every program will process the WM_PAINT message for it's main form. The API has the method of calling two functions BeginPaint( ) and EndPaint( ) to do drawing in the WM_PAINT message (ONLY in the WM_PAINT message). These two functions do NOT work out side of the WM_PAINT message, and they are a matched pair.
  IMPORTANT - ALWAYS have an EndPaint( ) function in the Same WM_PAINT message if you use a BeginPaint( ). Do NOT use BeginPaint( ) or EndPaint( ) anywhere except in the WM_PAINT message.

When you process the WM_PAINT you can to get the Painting information like DC handle and Invalid region (TRect) from a TPaintStruct by calling the BeginPaint( ) function. This BeginPaint will get the system information for the current WM_PAINT message for that window.

function BeginPaint(hWnd: THandle; var lpPaint: TPaintStruct): HDC;
You can look this up in your local API Help or web page at MSDN-BeginPaint .
Also see Help for EndPaint( ) function or web page at MSDN-EndPaint

And for the WM_PAINT message you could use code like this-
var
  paintDC: HDC;
  PaintS: TPaintStruct;
  Str1: String;


if Msg = WM_PAINT then
  begin
  Str1 := 'Hello';
  paintDC := BeginPaint(hWnd, PaintS);
  Rectangle(paintDC,30,26,140,56);
  SelectObject(paintDC, hFont1);
  SetTextColor(paintDC,$000000FF);
  SetBkMode(paintDC,TRANSPARENT);
  TextOut(paintDC, 40, 40,PChar(' this is '+Str1), 9+Length(Str1));
  EndPaint(hWnd,PaintS);
// ALWAYS call EndPaint( ) if you call BeginPaint( ) in WM_PAINT
  end;
This will draw a rectangle (with default brush and pen) and then draw text on that rectangle. hFont is the Handle of the font selected, so the text will use that font and the text color will be Red, from SetTextColor( ), and there will be No text background color painted by the TextOut( ) function, because
SetBkMode(paintDC,TRANSPARENT); prevents it.

Using BeginPaint(hWnd, PaintS);
BeginPaint is different than GetDC, it returns a hDC (which is clipped) to draw on, but it also returns a TPaintStruct Record (see PAINTSTRUCT in API Help) which is in windows.pas as -
  tagPAINTSTRUCT = packed record
    hdc: HDC;
    fErase: BOOL;
    rcPaint: TRect;
    fRestore: BOOL;
    fIncUpdate: BOOL;
    rgbReserved: array[0..31] of Byte;
  end;
  TPaintStruct = tagPAINTSTRUCT;
See your local API Help about PAINTSTRUCT or web page at MSDN-PAINTSTRUCT
The values of fRestore, fIncUpdate, and rgbReserved are reserved for the OS and not used. The HDC is the same value returned by BeginPaint( ). The fErase value will be True if there is no brush (or NULL_BRUSH) assigned to the hbrBackground member of the Window Class Record. The rcPaint TRect can be used to determine where the hDC will be painted. Unlike GetDC( ) the BeginPaint hDC may NOT paint the entire hDC, painting is only allowed on the area of the rectangle, rcPaint. When EndPaint( ) is called the rcRect is set to 0. If you call PostMessage( ) or SendMessge( ) with the WM_PAINT message, NOTHING will happen, no drawing will be done, because the rcPaint will be Zero. To set the rcPaint rectangle to have an invalid area (not be Zero), you will need to use the function InvalidateRect( ).

InvalidatRect( ),   used to Redraw Client Areas
If you read your local API Help for InvalidateRect or web page at MSDN-InvalidatRect, you will see that this is for adding a rectangle to a window's update region and then redrawing the client area of that window. This is a function that is frenquently used when you need to refresh or redraw a window or control. If you set the lpRect to nil, then the entire client area will be redrawn. If you only need a smaller portion redrawn, then you can set a Rectangle to the part of the client area that needs the refreshing, and place it in the lpRect parameter. The bErase parameter is commonly set to True, so the background will also be refreshed, but can be set to False to prevent the WM_ERASEBKGND message from being sent. This is a function you should be familar with, because you will need it to get controls and windows to draw the changes that you make to them.

Some Basic Drawing Fuctions
In the code for this Fonts program there are 3 drawing functions used to draw shapes or fill areas of a DC. Most of the API drawing function's names tell you what they draw or do. There are 2 functions used to draw a shape, the Rectangle function draws a rectangle, and the Ellipse function draws circular shapes -

function   Rectangle(DC: HDC; X1, Y1, X2, Y2: Integer): Boolean;
        MSDN-Rectangle

function   Ellipse(DC: HDC; X1, Y1, X2, Y2: Integer): Boolean;
        MSDN-Ellipse

Both of these functions have same 5 parameters. The HDC to draw on and the 4 integers for the Left, Top, Right, and Bottom dimentions of the shape. The shape drawing will be done by filling the shape with the selected brush of the HDC, and the shape will be outlined (border drawn) with the HDC's pen. Reading the API help for these functions will tell you more. Look at the code in the procedure DrawIt; below in the Fonts program to see how to use these. By setting the HDC's pen or brush to Null, , SelectObject(HDC, GetStockObject(NULL_BRUSH)); will cause the function to not draw the interior (null brush), or the border (null pen).

The FillRect function also draws a rectangle, but it does not draw a border around the rectangle, It has 3 parameters, a HDC, a TRect and a hBrush.

function   FillRect(hDC: HDC; const lprc: TRect; hbr: HBRUSH): Integer;
        MSDN-FillRect

You also need to have a Brush handle in it's parameters, but I will not try and explain brush creation in this lesson, thats in the next one. I will just use the GetStockObject(BLACK_BRUSH) to get the stock black brush. Look at the code in the procedure DrawIt; below.



Fonts
Displaying keyboard typing on a computer monitor, is one of the most basic and essential things that your computer does. It can do this without any operating system, when you first boot up. The Window's OS text display methods need to offer flexibility and creative variety, and be able to work if there are problems, like the font "Name" is not on the computer's OS (or there are NO fonts installed on that computer). So the CreateFont function has many parameters, which are some trouble to deal with, but can give you some variation for your font display.

Fonts are Graphic Objects that you can create to use to write text on a Device Context or set as the display font for a control. Fonts are created and managed by the Windows System, to use a font you need it's "Handle", which is returned by the Font Creation functions. The font creation functions all need several parameters like Font Name, and Font Height to create a font. If you are used to using fonts in Delphi VCL, then you know to set the Font.Name and Font.Height and maybe set the Font.Style to fsBold is all you usually need. The same applies to API Font Creation, the 3 important parameters to set are lfFaceName, lfHeight, and lfWeight - the rest are extra. Here is some Delphi Source code from Graphics.pas for creating or changing a font. You may want to read your local Win32 API Help for
CreateFont MSDN-CreateFont,
CreateFontIndirect, MSDN-CreateFontIndirect
WM_SETFONT, MSDN-WM_SETFONT,
which are highlighted in red in the code below.

function TFont.GetHandle: HFont;
var
  LogFont: TLogFont;
begin
  with FResource^ do
  begin
    if Handle = 0 then
    begin
      FontManager.Lock;
      with LogFont do
      try
        if Handle = 0 then
        begin
          lfHeight := Font.Height;
          lfWidth := 0; { have font mapper choose }
          lfEscapement := 0; { only straight fonts }
          lfOrientation := 0; { no rotation }
          if fsBold in Font.Style then
            lfWeight := FW_BOLD
          else
            lfWeight := FW_NORMAL;
          lfItalic := Byte(fsItalic in Font.Style);
          lfUnderline := Byte(fsUnderline in Font.Style);
          lfStrikeOut := Byte(fsStrikeOut in Font.Style);
          lfCharSet := Byte(Font.Charset);
          if AnsiCompareText(Font.Name, 'Default') = 0 then
            StrPCopy(lfFaceName, DefFontData.Name)
          else
            StrPCopy(lfFaceName, Font.Name);
          lfQuality := DEFAULT_QUALITY;
          { Everything else as default }
          lfOutPrecision := OUT_DEFAULT_PRECIS;
          lfClipPrecision := CLIP_DEFAULT_PRECIS;
          case Pitch of
            fpVariable: lfPitchAndFamily := VARIABLE_PITCH;
            fpFixed: lfPitchAndFamily := FIXED_PITCH;
          else
            lfPitchAndFamily := DEFAULT_PITCH;
          end;
          Handle := CreateFontIndirect(LogFont);
        end;
      finally
        FontManager.Unlock;
      end;
    end;
    Result := Handle;
  end;
end;

procedure TWinControl.CMFontChanged(var Message: TMessage);
begin
  inherited;
  if HandleAllocated then Perform(WM_SETFONT, FFont.Handle, 0);
  NotifyControls(CM_PARENTFONTCHANGED);
end;

You must set the parameters for font creation, notice that delphi uses default parameters,
lfQuality := DEFAULT_QUALITY;
lfOutPrecision := OUT_DEFAULT_PRECIS;
lfClipPrecision := CLIP_DEFAULT_PRECIS;
which are the safe choises.

Windows Font Mapping
Physical fonts are the fonts installed on the Window's OS. A logical font is a description of the font you want, described with the parametrs of CreateFont( ) or the CreateFontIndirect( ) functions. Before a program can actually begin drawing text with a logical font, it must find the closest match from the fonts stored on the operating system (physical fonts). The process of finding the physical font that most closely matches the parameters of a logical font is called Font Mapping. Font mapping occurs when a program uses the SelectObject( ) function with a handle identifying a logical font. Font mapping uses a system algorithm that sorts and compares the attributes of the requested logical font against the attributes of available system physical fonts. When the font mapper completes its search and determines the closest possible matching font, the SelectObject( ) function returns and the application can begin drawing text with that selected font. By setting the parameters in CreateFont( ) and CreateFontIndirect( ), you tell the font mapper how to select the logical font. Don't think that once CreateFont( ) has returned a Font Handle that the Physical Font is set, between the time CreateFont( ) is called and SelectObject(hDC, FontHandle ) is called the named font may have been deleted or added to the OS. SelectObject( ) sets a physical font to the HDC, NOT CreateFont( ).

There is some information about creating fons at this web page
MSDN-Font Creation and Selection

Vector and Bitmap Fonts
There are two kinds of "Font" availible in the windows OS, a "bitmap" font and a "vector" font. The bitmap font (raster font) characters are defined by a pixel array (raster bitmap) and have very limited changes for display. The more recent and felexible kind of font is the Vector Font, often refered to as a "True Type" font. This kind is defined as drawing operations and can offer many more display options. I will refer to vector fonts here as "True Type" and raster fonts as "Non True Type".

Font Creation
There are 14 parameters needed for Font Creation, but only 3 are generally used (lpszFace, nHeight, and fnWeight). Please read the explanation below for some of the parameters for fonts, but it is not complicated to create a font, you need to set the Face Name, Height and Weight and the rest can be the default values for a normal font display. But it helps to have an idea of what the other parameters can do so you can have more control of font display. There are several functions that will create a font, let's look at CreateFont( ) first.

hFont1 := CreateFont(
    nHeight: Integer,        // height of font 
    nWidth: Integer,         // average character width 
    nEscapement: Integer,    // angle of rotation 
    nOrientation: Integer,   // base-line orientation angle 
    fnWeight: Integer,       // font weight 
    fdwItalic: Cardinal,     // italic attribute 
    fdwUnderline: Cardinal,  // underline attribute 
    fdwStrikeOut: Cardinal,  // strikeout attribute 
    fdwCharSet: Cardinal,    // character set identifier 
    fdwOutputPrecision: Cardinal, // output precision 
    fdwClipPrecision: Cardinal, // clipping precision 
    fdwQuality: Cardinal,   // output quality 
    fdwPitchAndFamily: Cardinal, // pitch and family 
    lpszFace: PChar         // typeface name PChar 
The parameters in CreateFont( ) are also in TLogFont used in CreateFontIndirect( ). You may want to look at your local API Help for LOGFONT or web page at MSDN-LOGFONT

var
  FontLog1: TLogFont;

with FontLog1 do
  begin
  lfHeight := -12;
  lfWidth := 0;
  lfEscapement := 0;
  lfOrientation := 0;
  lfWeight := 0;
  lfItalic := 0;
  lfUnderline := 0;
  lfStrikeOut := 0;
  lfCharSet := DEFAULT_CHARSET;
  lfFaceName := 'MS Sans Serif';

  {the params below are used only if the named font is NOT availible,
   then these are used to pick a substitute font, the Pitch and family
   beging the most important, so set them to default if you are unsure}
  lfOutPrecision := OUT_DEFAULT_PRECIS;
  lfClipPrecision := CLIP_DEFAULT_PRECIS;
  lfQuality := DEFAULT_QUALITY;
  lfPitchAndFamily := DEFAULT_PITCH;
  end;
FontHandle := CreateFontIndirect(FontLog1);
You can use the FontLog1 settings above and change the lfHeight and lfFaceName to what you need, and this will work well as long as the font Named is availible on that computer. You should look at the parameter explanations below to give you an Idea of what these are for, but you do Not have to know all the details to create fonts.

Font Creation Parameters

lpszFace
This is the Last parameter in CreateFont( ), but the most important. You set the Font Face name here that you hope will be in the OS fonts, if this face name is availible from the system physical fonts then that font is used and the parameters fdwCharSet, fdwOutputPrecision, fdwClipPrecision, fdwQuality, and fdwPitchAndFamily are all ignored. All of these values are taken from the named font. If the named font is not present in the OS, then these values are used to select a replacement font.

nHeight
The first parameter of CreateFont is nHeight, which is in logical units not points (a font mesurement unit) so you can use positive and negitive values. Positive values matches the value to the Cell height of the font, negative values matches the value to the Character height of the font, See chart below.
Value   Meaning
positiveThe font mapper transforms this value into device units and matches it against the cell height of the available fonts.
0The font mapper uses a default height value when it searches for a match. This is almost never used, since the default value can change.
negativeThe font mapper transforms this value into device units and matches its absolute value against the character height of the available fonts.
This gerenally means a negative value will give larger font display than the positive (-16 will look larger than 16 height). But not always, fonts have their design specifications set in the font file and the font designer can change all aspects of the font they design. So each different font will have a different Cell height to Charater height ratio (and other aspect variables). But for most non-artisic english fonts a negative value is better, matching the Charater Height, not the Cell Height.

nWidth
This second parameter is font width, which is for the average width, in logical units, of characters in the font. If nWidth is zero, the font mapper chooses a close match (normal) value relative to the nHeight. This nWidth value is only used for True Type (vector) fonts, and ignored for non-vector fonts. With True Type fonts you can set the nWidth to more or less than normal to fit more text into an area, or expand text to make it eaiser to read. First you will need to determine what the "normal" nWidth is for the nHeight you have chosen, and then change it to make the font wider or narrower. Also, even if you want "normal" width (set nWidth to zero), you may want to set this nWidth to the normal width value in case your Named font is not available and a substitute font with the same width will be located or at least displayed on screen with a simular width.

nEscapement and nOrientation
The next 2 parameters are for "Angle of Rotation" and "Orientation" often used together to change the angle the font is drawn on the HDC. These parameter's units are tenths of a degree, from 0 to 3600. Only True Type fonts can be rotated, other fonts will ignore these parameters.

fnWeight
The next parameter is Weight, which sets the normal or Bold apperence of a font, this can range form 0 to 1000. Use a weight of zero to get normal text weight, here are some text weight constants. . .
ValueWeight
FW_DONTCARE
FW_THIN
FW_EXTRALIGHT
FW_LIGHT
FW_NORMAL
FW_REGULAR
FW_MEDIUM
FW_SEMIBOLD
FW_BOLD
FW_EXTRABOLD
FW_HEAVY
0
100
200
300
400
400
500
600
700
800
900

The font mapper will not change the font weight to all the values between 1 and 1000, it may only change the displayed text weight on a few numbers in that range, depending on the the version of Windows, the Font and it's size and type. True Type fonts will have more options (size and weight) than non-True Type fonts. In early versions of Windows, any weight below 501 is shown as 400 and any weight above 500 is shown as 700. So the safe choices are 0 (or FW_NORMAL) and FW_BOLD.

fdwItalic, fdwUnderline and fdwStrikeOut
The next 3 parameters can be 0 or 1 to set the font as being Italic, Underlined, or LineThrough (Strike Out). A value of 1 will apply that charateristic.

fdwPitchAndFamily
You may want to match the Pitch and Family to the Font you named in lpszFace. Font Pitch is how the width of each of the font's charaters is determined. With Fixed Pitch (Monospaced), all of the font's charaters are the same width, a "i" is the same width as a "M". A Variable Pitch font sets the width of a character to whatever is needed by that character, so a "i" will be narrower (less wide) than a "M". The values availible here for font pitch are -
DEFAULT_PITCH  // usually variable
FIXED_PITCH
VARIABLE_PITCH
The font Family is a general catagory for the "Features" that a font may have. I cannot explain font design specifiations and naming here, but the primary font feature is Serif and Non-Serif (called Sans Serif, by the font geeks). A Serif font is "New Times Roman" a Sans Serif font is "Arial". The values availible for Font Family are -
FF_DONTCARE  // default, usually will get a Swiss, Sans Serif
FF_DECORATIVE  // special title artisic fonts
FF_MODERN  // a Fixed pitch font
FF_ROMAN  // fonts with Serifs
FF_SCRIPT // fonts made to look like handwriting
FF_SWISS  // fonts without Serifs - Sans Serif
You should at least specify if you want a Fixed pitch or Variable pitch font.

fdwCharSet
Specifies the character set. The following values are predefined:
ANSI_CHARSET
DEFAULT_CHARSET
SYMBOL_CHARSET
SHIFTJIS_CHARSET
GB2312_CHARSET
HANGEUL_CHARSET
CHINESEBIG5_CHARSET
OEM_CHARSET
You might try to usually use the DEFAULT_CHARSET for font creation that might use many different font face names. If you only create one font face name, you should match the fdwCharSet to the Face Name fdwCharSet of your font. English text fonts mostly use a ANSI_CHARSET, however if you have a "Symbol" font face Name that is NOT text characters and have the ANSI_CHARSET, the font mapper will NOT use that font face name, it will use a font that is a ANSI font. The font mapper seems enforce your setting for the chacrater "Set" type you have in the fdwCharSet, no matter what font face name you may use.

fdwOutputPrecision, fdwClipPrecision, fdwQuality
Since these are used when your lpszFace Font name can not be found, setting these to the Defaults are the saftest and most common.

The fdwOutputPrecision parameter is for matching the the way the text is drawn to the values in the logical font's requested height, width, pitch, font type, ect. Use the OUT_DEFAULT_PRECIS, another value to consider is the OUT_TT_ONLY_PRECIS, for True Type fonts.

The fdwClipPrecision is supposed to set how to clip characters that are partially outside the clipping region, but it does not seem to make much difference? Since few fonts draw outside of this region, although some "Artistic" fonts draw all over the place. Use CLIP_DEFAULT_PRECIS.

The fdwQuality is suppose to set how carefully the GDI must attempt to match the logical-font attributes to those of an actual physical font. I think at one time these may have applied to a "Printing" DC text draw, but most printers now have their own settings, but I have never tested to see that. The DEFAULT_QUALITY will do fine. If you only set the font's name and height, then this parameter is irrleavant. But if you set PROOF_QUALITY, then the font mapper may get a vector (TrueType) font. There is also a ANTIALIASED_QUALITY, but it does not seem to change any of the font drawing on screen or bitmap to be anti-Aliased, only to maybe pick a True Type font if your font name is not there.

You can look at the examples in this Fonts program below, to see some other values used.


Drawing Text on the HDC
There are several HDC properties which all text drawing functions will use to determine how the text is drawn. The HDC properties are for -

Text Color
    function SetTextColor( );       MSDN-SetTextColor

Background Color
    function SetBkColor( );       MSDN-SetBkColor

Background Mode
    function SetBkMode( );       MSDN-SetBkMode

Text Alignment
    function SetTextAlign( );       MSDN-SetTextAlign

Intercharacter Spacing
    function SetTextCharacterExtra( );       MSDN-SetTextCharacterExtra

Text Jusification
    function SetTextJustification( );       MSDN-SetTextJustification

I will not try and explain all of these Text-Formatting properties, since there is a good explanation in the API Help, under index "Text-Formatting Attributes". You can see some examples for changing these properties in the DrawIt procedure below.

All of the text drawing functions will use the HDC's selected hFont for the drawing operation. The HDC's text-alignment property will be used to interpret the X and Y position parameters. There are several GDI font drawing functions used in the Fonts Program below. The most basic is the TextOut( ) function.

function   TextOut(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL;
      MSDN-TextOut

Which just draws the text in the Str parameter. The DC parameter is for the hDC to be drawn on, and the X and Y are the position of the Top, Left point to start the text drawing (this is for the default Text-Align of Top-Left). The Count parameter is used to tell the system how may characters you want to draw from the Str. This Count parameter can be very useful when you only want to draw a seleted number of charactes from a long PChar string.

DrawText( ) is a much more versitile function with more text drawing options.

function   DrawText(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer;
      MSDN-DrawText

The API Help for this function will tell you many of the uFormat flags you can use to change how the text will be drawn. This uses a TRect instead of the X and Y integers for text positioning. By using a Rectangle this function can "Wrap" the words in a long string to fit in the rectangle, much like a windows Edit control does. You would use the DT_EDITCONTROL flag in the uFormat parameter for this text wraping. This can also Center the text in this rectangle.

ExtTextOut( ) is also used in the Fonts Program. It has more options than TextOut -

function   ExtTextOut(DC: HDC; X, Y: Integer; Options: Integer; Rect: PRect; Str: PChar; Count: Integer; Dx: PInteger): BOOL;
      MSDN-ExtTextOut

This function has an optional TRect which can be used to set a clipping area. There is also an optional array of Integers to set the distance between adjacent character spacing. An example of this in also in the DrawIt procedure.


Setting Control Fonts
Most Controls use the System font as it's default font, which may seem large if you are used to the Delphi VCL default font of MS Sans Serif. Using SendMessage( ) with the WM_SETFONT message will place that font as the Control's font. You can look at your local API Help for WM_SETFONT or web page at MSDN-WM_SETFONT. If you want the standard font look of MS Sans Serif then the easiest way is to get a Stock Object of ANSI_VAR_FONT. Like this -
SendMessage(hExitBut, WM_SETFONT, GetStockObject(ANSI_VAR_FONT),0);
Some other "Stock Object" Fonts are ANSI_FIXED_FONT, a fixed-pitch (monospaced) system font, DEFAULT_GUI_FONT, this is only avaible in Win95 and looks the same as ANSI_VAR_FONT, and the OEM_FIXED_FONT, this is the monospaced font that a computer uses to boot up and show a Bios info screen, rarely used in GUI. To get a "normal" windows control look, you may want to set your controls font to the Stock Object ANSI_VAR_FONT.





Program Fonts

This Fonts program demonstrates using fonts on a Device Context, and creating and deleting those fonts.
In the previous Programs the Handle variables were set to a HWND or THandle type, which is the correct type for a HWND. But it is often neccessary to compare a Handle to an Integer, so you would need to typecaste the HWND (Cardinal) as an Integer or use abs( ) to prevent the compiler of giving you warning messages. Because the windows system will use Handles as an Integer type in many of it's functions (SendMessage and others), the value of a Handle will not exceed the 2,147,483,647 limit of an Integer, so it is safe to use the Integer type for Handles, instead of the Cardinal type (THandle). I did the same exchange of variable types in the Window's Proc MessageProc( ), I made the Result and all the parameters an Integer type. The Msg (message1) is an UINT type, which is not supported by the variable types in Delphi anyway, Delphi just casts is as a Cardinal type. The value of a Msg should not exceed the 2,147,483,647 Integer limit (the max value for messages is Hex FFFF), so using an integer will be OK here too.

We need to add Commdlg to the uses clause, Commdlg has the constants and functions we need for the ChooseFont( ) Dialog Box. Let's look at the first thing after the main program begin, where a Font is created with CreateFont( ). The CreateFont( ) requires all of the font parameters to be entered for each CreatFont( ) you call, using CreateFontIndirect( ) may save some coding if you create several fonts. It uses a TLogFont record to store the font creation parameters, so you need to only enter values that change from the last font created. For Font3 the lfFaceName is set to 'v8j9k2a5', which will NOT be found, and it's lfPitchAndFamily is set to "VARIABLE_PITCH or FF_DECORATIVE". This will show you what type of font the font mapper picks from availible fonts if it can not find the font name you set. Change FF_DECORATIVE to FF_SCRIPT, and then to FF_SWISS to see if different fonts are picked. In Font4 , 5, 6 and 7 the lfEscapement is set to an angle (tenth of degree) for the text to be rotated. In the MessageProc( ) WM_PAINT message you will see SelectObject( ) and TextOut( ) that will draw these rotated fonts on the main Forms DC. More about WM_PAINT later.

Look at the GetSystemFonts procedure, this uses a SystemParametersInfo( ) function to get Non Client Metrics info, from which we get some system fonts info(Caption Font, Menu Font, Status Font and Message Font). Now look at the GetFont procedure, first we set the parameters in a TChooseFont record. Next we call ChooseFont( ) with this TChooseFont as the parameter, so a Common Dialog, the Choose Font Dialog Box will be shown and the user can pick a font. The new font will be set into hLabel1 using SendMessage( ), since hLabel1's size depends on the Font size we need to resize it.

I have used several "COLORREF" values to set the Text Color in this program, these are in a Hexadecimal notation, like $0000FF for the Red color. If you are not familar with this Hexadecimal notation for colors, just use the values I have given, in the next lesson I will say more about this.


see comments in code for more info
program Fonts;
{this program shows some font creation and useage options
and a some hDC drawing functions to paint text and shapes}

uses
  Windows, Messages, Commdlg;
  {Commdlg is needed for the system Choose Font Dialog}

{$R *.RES}

var
wClass: TWndClass;
hMainForm,
Font1, Font2, Font3, Font4, Font5, Font6, Font7, CaptionFont,
IconFont, MenuFont, StatusFont, MessFont, hLabel1, hLabel2,
hExitBut, hDrawFontBut, hChangeBut, hGetFontBut, OldObj: Integer;{THandle}
{I have changed the type for these Handles from THandle , a Cardinal value,
to Integer, both of these types use 4 bytes, and the value of a Handle should
not exceed the 2,147,483,647 integer max. I did this because in the MessageProc
the Handle value is compared to the LParam, an integer}
mainMsg: TMSG;
FontLog1: TLogFont;
CapFontName, CapHeight: String;
TempDC: HDC;
FormRect: TRect;
Size1: TSize;

const
LabelText = 'Create and Use Fonts Demo';
FontDr = ' Font Drawing ';

procedure ShutDown;
begin
{Be sure to DeleteObject for all fonts}
DeleteObject(Font1);
DeleteObject(Font2);
DeleteObject(Font3);
DeleteObject(Font4);
DeleteObject(Font5);
DeleteObject(Font6);
DeleteObject(Font7);
DeleteObject(IconFont);
DeleteObject(CaptionFont);
DeleteObject(MenuFont);
DeleteObject(StatusFont);
DeleteObject(MessFont);

PostQuitMessage(0);
end;

function Int2Str( Value : Int64 ) : String;
  var
  Minus : Boolean;
begin
   Result := '';
   if Value = 0 then
      Result := '0';
   Minus := Value < 0;
   if Minus then
      Value := -Value;
   while Value > 0 do
   begin
      Result := Char( (Value mod 10) + Integer( '0' ) ) + Result;
      Value := Value div 10;
   end;
   if Minus then
      Result := '-' + Result;
end;

procedure GetSystemFonts;
  var
  NonClMetrics: TNonClientMetrics;
  TempLogF: TLogFont;
begin
NonClMetrics.cbSize := SizeOf(NonClMetrics);
SystemParametersInfo(
    SPI_GETNONCLIENTMETRICS,	// system parameter to query or set
    0,
    @NonClMetrics, // address of NonClientMetrics
    0
   );
{The SystemParametersInfo function queries or sets systemwide parameters
with  SPI_GETNONCLIENTMETRICS  you get the metrics associated with the
standard nonclient area of windows.}

CaptionFont := CreateFontIndirect(NonClMetrics.lfCaptionFont);
CapFontName := String(NonClMetrics.lfCaptionFont.lfFaceName);
MenuFont := CreateFontIndirect(NonClMetrics.lfMenuFont);
StatusFont := CreateFontIndirect(NonClMetrics.lfStatusFont);
MessFont := CreateFontIndirect(NonClMetrics.lfMessageFont);
CapHeight := 'Caption button Height is '+ Int2Str(NonClMetrics.iCaptionHeight);
{see API help for index NONCLIENTMETRICS, to see other info in NonClMetrics}
SystemParametersInfo(SPI_GETICONTITLELOGFONT,SizeOf(TempLogF),@TempLogF,0);
{this gets the font used under icons}
IconFont := CreateFontIndirect(TempLogF);
end;

procedure GetFont;
{this shows how to use the ChooseFont dialog to get
a font and resize a control to the new font}
  var
  ChooseFont1: TChooseFont;
  TempRect: TRect;
begin
GetObject(Font3,SizeOf(FontLog1), @FontLog1);
{this GetObject( ) will produce info for the font, brush
or pen who's handle is sent.
Notice that I have used Font3, the non-Availible v8j9k2a5
so you can see what is displayed in a ChooseFont dialog box for a
font that is NOT there, run the program and do the GetFont twice
to see the difference the second time after you choose a font
the first time}
with ChooseFont1 do
  begin
{this is the Choose Font dialog box info to limit the chooses
availible to the user}
  lStructSize := SizeOf(ChooseFont1);
  {you MUST set the lStructSize}
  hWndOwner := hMainForm;
  hDC := 0;
  {hDC is used mostly for printers}
  lpLogFont := @FontLog1;
  {lpLogFont will be the font selection shown in dialog}

  nSizeMax := 22;
  nSizeMin := 14;
  {because this font will be used in a control that is sized to the font,
  I need to limit the font size}
  Flags := CF_INITTOLOGFONTSTRUCT or CF_FORCEFONTEXIST or CF_LIMITSIZE or CF_SCREENFONTS or CF_SCRIPTSONLY;
  {there are many flages that can be helpful, see Win32 API Help for their definitions}
  lpfnHook := nil;
  end;
{ChooseFont will fill the lpLogFont , FontLog1, with the font info}
if ChooseFont(ChooseFont1) then
  begin
  GetWindowRect(hLabel1, TempRect);
  {we will need to redraw the MainForm's Rect where the hLabel1 was if
  hLabel1 is made smaller}
  ScreenToClient(hMainForm,TempRect.TopLeft);
  ScreenToClient(hMainForm,TempRect.BottomRight);
  {GetWindowRect( ) gets screen Coordinate and we need window points
   ScreenToClient will convert them}
  DeleteObject(Font3);
  Font3 := CreateFontIndirect(FontLog1);
  {FontLog1 has the new font info}
  SendMessage(hLabel1,WM_SETFONT,Font3,0);
  {this control's size should match the Font used so we will have to
  change the size of hLabel1 to the new font}
  TempDC := GetDC(hLabel1);
  OldObj := SelectObject(TempDC,Font3);
  GetTextExtentPoint32(TempDC, LabelText, lstrlen(LabelText), Size1);
  SelectObject(TempDC,OldObj);
  ReleaseDC(hLabel1,TempDC);
  MoveWindow(hLabel1,(FormRect.Right div 2)-((Size1.cx+(Size1.cx div 20)) div 2),
  5,Size1.cx+(Size1.cx div 20),Size1.cy+1,False);
  InvalidateRect(hLabel1,nil,True);
  {since the font has changed, you need to make sure all of the text
  shown is redrawn with InvalidateRect( )}
  InvalidateRect(hMainForm,@TempRect,True);
  {if the font is smaller then the area around the Label will need to be invalidated
  TempRect is used so only part of MainForm is redwawn}
  end;

end;

procedure DrawIt;
{this shows several different types of font drawing
needed for text alignment}
  var
  Rect1: TRect;
  BigFont: THandle;
  ArryInt: Array[0..20] of Integer;
  i: Integer;
  ExText: PChar;
begin
TempDC := GetDC(hMainForm);
SelectObject(TempDC,Font1);
GetTextExtentPoint32(TempDC, FontDr, lstrlen(FontDr), Size1);
SetRect(Rect1,180,FormRect.Bottom-(Size1.cy+8),Size1.cx+ 186,FormRect.Bottom-2);
FillRect(TempDC,Rect1,GetStockObject(BLACK_BRUSH));
{GetStockObject is good to get system Brushs, Pens, and fonts
you do NOT need to call DeleteObject for Stock Objects}
TextOut(TempDC,Rect1.Left+3,Rect1.Top+3, FontDr, lstrlen(FontDr));
SelectObject(TempDC,MenuFont);
SetRect(Rect1,28,170,39,190);
DrawText(TempDC,'this is Menu Font',-1,Rect1,DT_SINGLELINE or DT_CALCRECT);
DrawText(TempDC,'this is Menu Font',-1,Rect1,DT_SINGLELINE);
{DrawText is more versitile than TextOut, here DT_CALCRECT is used to
get the Rect1 size needed for DrawText, Rect1 is used again to draw a
rectangle around this text, there are many other uses of DrawText
which will be shown is later examples}
SetTextAlign(TempDC, TA_RIGHT);
{SetTextAlign( ) with TA_RIGHT will tell windows to use the X position
as the Right side starting point instead of the default left side.
see Win32 API Help for other alignment settings}
TextOut(TempDC,460,256,'right aligned text',18);
{the text is drawn starting at the right, notice that the X position
is the same in the TextOut below and they will be right aligned}
TextOut(TempDC,460,276,'this is over a button',21);
{this will draw on top of the Exit button unless WS_CLIPCHILDREN is
in the Style of the MainForm Window}

SetTextAlign(TempDC, TA_Left);
{restore text align to default Left}
SelectObject(TempDC,StatusFont);
SetTextColor(TempDC,$0000FF);
SetBkColor(TempDC,$99FFFF);
TextOut(TempDC,-8,150,'this is Status Font',19);
{you can use negative positions for drawing}
BigFont := CreateFont(-116,0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
   CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,VARIABLE_PITCH or FF_SWISS,'Arial');
{you can get very large fonts if you need them, even though many font selection boxes
will stop at 72, you can go much larger, -116 above}
SelectObject(TempDC,BigFont);
SetTextColor(TempDC,$0000AA);
SetBkColor(TempDC,$000000);
TextOut(TempDC,1,3,'BIG Font',8);

{2 basic drawing functions Rectangle and Ellipse}
Rectangle(TempDC,114,210,332,304);
{draws a Rectangle with the default pen and brush
black pen and whith brush}
SelectObject(TempDC,GetStockObject(GRAY_BRUSH));
Ellipse(TempDC,118,216,138,230);
Ellipse(TempDC,306,216,327,230);
{draws 2 Ellipses with new gray brush}
SelectObject(TempDC,GetStockObject(NULL_BRUSH));
InflateRect(Rect1,2,2);
{InflateRect, useful win API for Rects, it enlarges or decreases the Rect}
Rectangle(TempDC,Rect1.Left,Rect1.Top,Rect1.Right,Rect1.Bottom);
{this draws a rect around the MenuFont text without filling
it with a brush and covering the text,
because Null_Brush does not draw anything}

SelectObject(TempDC,GetStockObject(ANSI_FIXED_FONT));
SetBkColor(TempDC,$FFFFFF);
SetTextAlign(TempDC,TA_UPDATECP or TA_BASELINE);
{SetTextAlign( ) with TA_UPDATECP will move the Pen Position
to the last text drawing position. This will make drawing text from
variables or different fonts alot easier. You call TextOut with
X and Y set to 0. You need to call MoveToEx to set the begining
point}
MoveToEx(TempDC,152,227, nil);
TextOut(TempDC,0,0,'pen position ',13);
TextOut(TempDC,0,0,'text',4);
MoveToEx(TempDC,120,248, nil);
TextOut(TempDC,0,0,'Different ',10);
SelectObject(TempDC,Font2);
TextOut(TempDC,0,0,'Fonts ',6);
SelectObject(TempDC,GetStockObject(ANSI_VAR_FONT));
TextOut(TempDC,0,0,'on same ',8);
SelectObject(TempDC,Font3);
TextOut(TempDC,0,0,'line',4);
MoveToEx(TempDC,120,270, nil);
SelectObject(TempDC,Font1);
SetTextColor(TempDC,$CF0000);
{since text variables can be of different lengths, TA_UPDATECP can
draw the text and continue the line with the next TextOut}
TextOut(TempDC,0,0,PChar(CapFontName),Length(CapFontName));
SelectObject(TempDC,Font2);
TextOut(TempDC,0,0,PChar(', handle is '+Int2Str(CaptionFont)),Length(Int2Str(CaptionFont))+12);
SetTextAlign(TempDC,TA_TOP);
{reset text align back to Top}
ExText := 'Try to  LOOK  at this';
for i := 0 to 20 do
begin
GetTextExtentPoint32(TempDC, @ExText[i], 1, Size1);
ArryInt[i] := Size1.cx+1;
end;
{ExtTextOut can be used to do complex character positioning.
ArryInt is used in ExtTextOut to set each character's
begining position from the last characters begining position,
So I get each characters width with GetTextExtentPoint32}
for i := 8 to 10 do
ArryInt[i] := 21;
{ArryInt values 8, 9, and 10 are for the characters in LOOK}

{a charater can draw OVER the folowing charater if the
space is not enough, also narrow charaters like "l"
will have a space to the right if the positioning is greater
than needed, , , to see this try
for i := 0 to 20 do
ArryInt[i] := 5
or
ArryInt[i] := 11
if you use ArryInt[i] := 0 then all the characters will be drawn
at the same position on top of each other}
SetRect(Rect1,166,278,252,295);
{this Rect1 will be used in ExtTextOut}
SetTextColor(TempDC,GetSysColor(COLOR_WINDOWTEXT));
SetBkColor(TempDC, GetSysColor(COLOR_HIGHLIGHT));
SetBkMode(TempDC,TRANSPARENT);
ExtTextOut(TempDC,120,278, ETO_OPAQUE,@Rect1,ExText,
21, @ArryInt{use nil here for normal spacing});

{ExtTextOut has more options than TextOut, if ETO_OPAQUE is used then
Rect1 will be filled with the background color, if you put an Array of
Integers in the last parameter, then the character spacing will be set
to the values of the Array, but these array values may difficult
to apply to the character spacing. LOOK will be more spaced}
ReleaseDC(hMainForm,TempDC);
DeleteObject(BigFont);
{Make sure you ReleaseDC and DeleteObject}
end;

procedure ChangeFonts;
begin
SendMessage(hLabel2,WM_SETFONT,Font3,0);
{this font is too large for this static control, but window controls
do NOT check or change anything that's assigned to them. You have to check
and change the font assigned or change the control (size) for the font -
see GetFont procedure above
this WM_SETFONT message does NOT repaint hLabel2 so you need to call
InvalidateRect( )}
InvalidateRect(hLabel2,nil,True);
SendMessage(hExitBut, WM_SETFONT, GetStockObject(ANSI_VAR_FONT),0);
InvalidateRect(hExitBut,nil,True);
SendMessage(hDrawFontBut, WM_SETFONT, GetStockObject(ANSI_FIXED_FONT),0);
{notice the display does NOT show a new font on hDrawFontBut without InvalidateRect}
end;


function MessageProc(hWnd, message1, WParam, LParam: Integer): Integer; stdcall;
{I have changed all the parameters in MessageProc to an Integer type, the UINT is not
supported in Delphi anyway, and the value of a Handle will not go above the 2147483647
integer limit}
  var
  paintDC: HDC;
  PaintS: TPaintStruct;
  Rect1: TRect;
  CharSpace: Integer;
begin
case message1 of
  WM_PAINT: 
    begin
    paintDC := BeginPaint(hWnd, PaintS);
    {the PaintS record has a rcPaint Rect, which has the Area that will be painted
      anything outside this Rect will NOT be drawn, even if it is in these Paint commands}
    if PaintS.rcPaint.Top < 37 then
      begin
      {you can test the rcPaint to see if you need to paint, this is suppose to increase
      the efficientcy of painting, but with modern video displays it may not make much difference}
      SetRect(Rect1,1,1,FormRect.Right-2,37);
      FillRect(paintDC,Rect1,GetStockObject(BLACK_BRUSH));
      end;
    SelectObject(paintDC,CaptionFont);
    {the BkColor defaults to white}
    TextOut(paintDC,190,40,PChar(#149' this is Caption Font '+CapFontName+#153), 
               24+Length(CapFontName));
    SelectObject(paintDC,Font2);
    SetBkColor(paintDC,GetSysColor(COLOR_BTNFACE));
    {set BkColor to match what is being drawn on}
    TextOut(paintDC,32,60,'Compare to text above',21);
    SelectObject(paintDC,Font4);
    TextOut(paintDC,248,114,'Font4  20  Escapement',21);
    SelectObject(paintDC,Font5);
    TextOut(paintDC,250,60,'Font5  -20  Escapement',22);
    SelectObject(paintDC,Font6);
    TextOut(paintDC,3,180,'Font6  90'#176' '#162#169#174#175#164,16);
    SelectObject(paintDC,Font7);
    TextOut(paintDC,480,70,#149' '#147'Font7'#148'  270'#176' 2'#178' 4'#179,21);
    {non Standard charaters #176 can be useful but are not contained in all fonts}
    SelectObject(paintDC,MenuFont);
    TextOut(paintDC,128,130,#149' this is Menu Font'#176,20);
    SelectObject(paintDC,StatusFont);
    SetTextColor(paintDC,$0000FF);
    CharSpace := SetTextCharacterExtra(paintDC, 8);
    {SetTextCharacterExtra( ) changes the amount of space between
      charaters and can be very useful, default CharSpace is 0}
    TextOut(paintDC,128,150,#149'wide spaced Status Font',24);
    SetTextCharacterExtra(paintDC, CharSpace);
    {Reset CharSpace}
    SelectObject(paintDC,MessFont);
    SetBkColor(paintDC,$FFCC99);
    SetBkMode(paintDC,TRANSPARENT);
    {setting the BkMode to TRANSPARENT will prevent the text
      background from being painted, leave this out to see the difference}
    TextOut(paintDC,128,170,#149' this is Message Font',22);
    SetTextColor(paintDC,$000000);
    SetBkMode(paintDC,OPAQUE);
    SelectObject(paintDC,GetStockObject(OEM_FIXED_FONT));
    TextOut(paintDC,128,190,#149'this is OEM Fixed Font'#176,24);
    {non standard charaters #149 may not be the same in non-Microsoft fonts
      like the OEM font}
    EndPaint(hWnd,PaintS);
    {IMPORTANT, ALWAYS call EndPaint if you call BeginPaint in WM_PAINT}
    Result := 0;
    {If you do not Exit, DefWindowProc is called but the rcRect will be 0
        because of EndPaint, so DefWindowProc does not do anything}
    Exit;
    end;

  WM_COMMAND: if lParam = hExitBut then PostMessage(hMainForm,WM_CLOSE,0,0)
    else if lParam = hDrawFontBut then DrawIt
    else if lParam = hChangeBut then ChangeFonts
    else if lParam = hGetFontBut then GetFont;
    {I changed the type for the Handles from a Cardinal to an Integer
        in the var clause above. So I do not need to use abs(hExitBut)}
            
  WM_DESTROY: ShutDown;
  end;
Result := DefWindowProc(hWnd,message1,wParam,lParam);
end;

begin  //  Main Program begin  //  //  //  //

{since Fonts are Window's System Objects I usually create them
first. You must Delete these Objects before your Program ends,
see ShutDown procedure above}

// // // Font Creation // //

{you can use CreateFont and set all the parameters}
Font1:=CreateFont(
    -12,                           // Height
    0,                             // Width
    0,                             // Angle of Rotation
    0,                             // Orientation
    FW_NORMAL,                     // Weight
    0,                             // Italic
    0,                             // Underline
    0,                             // Strike Out
    ANSI_CHARSET,                  // Char Set
    OUT_DEFAULT_PRECIS,            // Precision
    CLIP_DEFAULT_PRECIS,           // Clipping
    DEFAULT_QUALITY,               // Render Quality
    VARIABLE_PITCH or FF_SWISS,    // Pitch & Family
    'MS Sans Serif');              // Font Name

{Or use CreateFontIndirect and fill in a LOGFONT record}
with FontLog1 do
  begin
  lfHeight := -14;
  lfWidth := 0;
{a lfWidth := 0 gets the default width
normal width is about 6 try 5, 7 and 8 too see how it
changes the displayed width of this font}
  {lfWidth := 5;}
  {lfWidth := 7;}
  {lfWidth := 8;}
  lfItalic := 0;
  lfWeight := FW_BOLD;
  {there are many constants for lfWeight,
   use any value between 0 and 1000 but only certain
   values are recogized by the font mapper, use 0 for default normal}
  lfCharSet := DEFAULT_CHARSET;
  {the params below are used only if the named font, lfFaceName, is 
   NOT availible, then these are used to pick a substitute font, the 
   Pitch and family beging the most important}
  lfOutPrecision := OUT_TT_PRECIS;
  {OUT_TT_PRECIS tells the font mapper to use a true type font if
   there are several fonts with the same name}
  lfClipPrecision := CLIP_DEFAULT_PRECIS;
  lfQuality := {DEFAULT_QUALITY}ANTIALIASED_QUALITY;
  {ANTIALIASED_QUALITY will choose a True Type font
   if your face name is not amoung the OS fonts}
  lfPitchAndFamily := VARIABLE_PITCH or FF_ROMAN;
  lfFaceName := 'Times New Roman';
  end;
Font2 := CreateFontIndirect(FontLog1);

with FontLog1 do
{you only need to reset values in FontLog1 that are different
than the previous FontLog1}
  begin
  lfHeight := -20;
  lfWidth := 0;
  {if you change the width above, remember to change it back here}
  lfItalic := 1;
  {you can change the Italic and underline from 0 to 1}
  lfWeight := FW_NORMAL;
  lfPitchAndFamily := VARIABLE_PITCH or FF_DECORATIVE{FF_SCRIPT}{FF_SWISS};
  {since this font will not be found, change the font family to script or
   swiss or roman to see what font will be picked.}
  lfFaceName := 'v8j9k2a5';
  {there will be no Font named v8j9k2a5, this is to show you what happens
  if the font you name is NOT on the computer, you can never tell if a Font
  will be availible, even the "Standard" windows fonts like
  'MS Sans Serif' or 'Arial' may have been deleted, so if a control's
  size or function depends on the font metrics, you may want to make provisions for that,
  see hLabel1 below and the WM_PAINT in the MessageProc }
  end;
Font3 := CreateFontIndirect(FontLog1);

with FontLog1 do
  begin
  lfHeight := -14;
  lfWidth := 0;
  lfItalic := 0;
  lfEscapement := 200;
  {lfEscapement is the amount of rotation in tenths of degree}
  lfOrientation := lfEscapement;
  {set the lfOrientation equal to lfEscapement}
  lfPitchAndFamily := VARIABLE_PITCH or FF_SWISS;
  lfFaceName := 'Arial';
  end;
Font4 := CreateFontIndirect(FontLog1);

with FontLog1 do
  begin
  lfHeight := -14;
  lfEscapement := 3400;
  lfOrientation := lfEscapement;
  end;
Font5 := CreateFontIndirect(FontLog1);

with FontLog1 do
  begin
  lfHeight := -16;
  lfWeight := 0;
  lfEscapement := 900;
  {this font is rotated 90 degrees}
  lfOrientation := lfEscapement;
  end;
Font6 := CreateFontIndirect(FontLog1);

with FontLog1 do
  begin
  lfHeight := -16;
  lfEscapement := 2700;
  lfOrientation := lfEscapement;
  lfPitchAndFamily := VARIABLE_PITCH or FF_ROMAN;
  lfFaceName := 'Times New Roman';
  end;
Font7 := CreateFontIndirect(FontLog1);

GetSystemFonts;

// // // End of Font Creation  //  //  //

// // // Begin the MainForm Creation  //  //  //

wClass.hInstance := hInstance;
with wClass do
  begin
{no Style parametes are given}
    Style := 0;
    hIcon :=         LoadIcon(hInstance,'MAINICON');
    lpfnWndProc :=   @MessageProc;
    hbrBackground := COLOR_BTNFACE+1;
    lpszClassName := 'mainForm Class';
    hCursor :=       LoadCursor(0,IDC_ARROW);
  end;

RegisterClass(wClass);

hMainForm := CreateWindow(
    wClass.lpszClassName,	// pointer to registered class name
    PChar(' Using Fonts - '+CapHeight),	// pointer to window name (title bar Caption here)
    WS_OVERLAPPEDWINDOW {or WS_CLIPCHILDREN},	// window style
{WS_OVERLAPPEDWINDOW is the default standard main window with a
Title bar and system menu and sizing border
WS_CLIPCHILDREN	will prevent drawning over top of child controls see
DrawIt above. Add WS_CLIPCHILDREN and see the difference with the DrawIt procedure}

    (GetSystemMetrics(SM_CXSCREEN) div 2)-276,	// horizontal position of window
    (GetSystemMetrics(SM_CYSCREEN) div 2)-224,	// vertical position of window
    500,	// window width
    356,	// window height
    0,	// handle to parent or owner window
{this is the MAIN window, so it will be the parent}
    0,	// handle to menu or child-window identifier
    hInstance,	// handle to application instance
    nil 	// pointer to window-creation data
   );

TempDC := GetDC(hMainForm);
{I need to get the text size for hLabel1, but it has not been created yet.
So I use a window that has been created, since the font metrics will be the same}
OldObj := SelectObject(TempDC,Font3);
{for a Device Context
(HDC, see Win32 API help for "Device Contexts", like a Canvas.Handle in Delphi)
you need to SelectObjects (fonts, pens, brushes) to use in Drawing on that DC}
GetTextExtentPoint32(TempDC, LabelText, lstrlen(LabelText){25}, Size1);
{GetTextExtentPoint32 gets the size of text for that DC
Size1 is used to get the correct size for Label1, even though this is NOT the
hLabel1 DC, the Text size should be the same for the same font and text Font3}
SelectObject(TempDC,OldObj);
{Restore the default font to your form's HDC, this is
not necessary if you don't use that HDC again, the Font stays
selected even if you release the DC}

ReleaseDC(hMainForm,TempDC);
{to call GetDC and then ReleaseDC may seem unnessary,
why not just call GetDC once when the app starts and then
ReleaseDC once when the app closes,
these GetDC are quick functions and releasing it cuts down
on window's system resources use}

GetClientRect(hMainForm, FormRect);
{Get the FormRect to be used to get positions for painting on the form}

hLabel1 := CreateWindow('Static', LabelText,
         WS_VISIBLE or WS_CHILD or SS_CENTER,(FormRect.Right div 2)-((Size1.cx+(Size1.cx div 20)) div 2),
         5, Size1.cx+(Size1.cx div 20),Size1.cy+1,hMainForm,0,hInstance,nil);
{hLabel1 is assigned a Font name (v8j9k2a5) that will not be found, so I calculate the Label1 size based
on the Size1 from GetTextExtentPoint32() so it will be the correct size for any font used, change the 
font name or font size in Font3 := CreateFontIndirect(FontLog1); and see how it changes Label1}
SendMessage(hLabel1,WM_SETFONT,Font3,0);

hLabel2 := CreateWindow('Static', 'Compare to text below',
         WS_VISIBLE or WS_CHILD or SS_LEFT,32,40,150,16,hMainForm,0,hInstance,nil);
SendMessage(hLabel2,WM_SETFONT,Font2,0);
{Static controls do NOT get user input (keyboard or mouse), I use it here like
Delphi's TLabel. But you can just Draw the text in the WM_PAINT. See "Compare to text Above"
in the paint message}

hExitBut := CreateWindow('Button','Exit',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT or BS_CENTER or WS_GROUP,
    380,280,64,28,hMainForm,0,hInstance,nil);
SendMessage(hExitBut, WM_SETFONT, GetStockObject(SYSTEM_FIXED_FONT),0);

hGetFontBut := CreateWindow('Button','Get new Font',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    20,206,88,27,hMainForm,0,hInstance,nil);
SendMessage(hGetFontBut,WM_SETFONT,Font1,0);

hChangeBut := CreateWindow('Button','Change Fonts',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    20,246,80,27,hMainForm,0,hInstance,nil);
SendMessage(hChangeBut,WM_SETFONT,Font1,0);

hDrawFontBut := CreateWindow('Button','Draw It',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    20,286,74,27,hMainForm,0,hInstance,nil);
SendMessage(hDrawFontBut,WM_SETFONT,Font1,0);

ShowWindow(hMainForm, SW_SHOWNORMAL);
{the WS_VISIBLE style was NOT set in the Main window creation}


while GetMessage(mainMsg,0,0,0) do
  begin
  {GetMessage will return True until it gets a WM_OUIT message}

    TranslateMessage(mainMsg);  // Translate any WM_KEYDOWN keyboard Msg to a WM_CHAR message
    DispatchMessage(mainMsg);   // Send Msg to the WndMessageProc
  end;
CapHeight := '';
CapFontName := '';
// empty strings to release memory

end.
You should change the font creation paramerers and experiment with how a font will change (or not change), when you use different parameter values. Try and change the lfHeight with positive AND negative numbers, change the lfWidth and see how you might use this for special font display (wider or narrower text). Change the lfItalic, lfUnderline, and lfStrikeOut parameters. Be sure to experiment the lfWeight parameter, see if your windows OS will give more font weights than normal and bold. See if changing the lfQuality or lfOutPrecision will change anything when the font is displayed.
It may be useful for you to learn how to get the width and height of a block of text in a certain font. You might try and make a button that will adjust it's size (width and height) to fit the size of the Font that is used for it, like what was done with hLabel1. Since text is a fundamental element of computer display, you should practice using the text drawing functions like
TextOut( )
and
DrawText( )
until you feel you know how they can be used.

                           

Next
We have made a program that use fonts and basic drawing. Next we'll use brushes and pens and try out a Popup window as a splash screen. A Timer is also used.
  Lesson 6, Brushes, Pens and Timers


       

Lesson -     One  Two  Three  Four  Five  Six  Seven  Eight  Nine  Ten  Eleven  Twelve  Thirteen  Fourteen




H O M E