Unit CanvasFontManager; Interface Uses OS2Def, Classes, Forms, PMWIN, Graphics; Const // This defines the fraction of a pixel that // font character widths will be given in FontWidthPrecisionFactor = 256; Type // a user-oriented specification of a font; // does not include OS/2 API data TFontSpec = record FaceName: string[ 64 ]; PointSize: integer; // if 0 then use x/y size XSize: integer; YSize: integer; Attributes: TFontAttributes; // set of faBold, faItalic etc end; // NOTE: Char widths are in 1/FontWidthPrecisionFactor units TCharWidthArray = array[ #0..#255 ] of longint; TPCharWidthArray = ^ TCharWidthArray; // Used internally for storing full info on font TLogicalFont= Class( TComponent ) pFaceName: pstring; // user-selected name pUseFaceName: pstring; // after substitutions. // Selected bits of FONTMETRICS fsSelection: USHORT; FontType: TFontType; FixedWidth: boolean; PointSize: integer; ID: integer; Attributes: TFontAttributes; // this can be nil if not already fetched pCharWidthArray: TPCharWidthArray; lMaxbaselineExt: LONG; lAveCharWidth: LONG; lMaxCharInc: LONG; lMaxDescender: LONG; destructor Destroy; override; end; TFontFace = class pName: pstring; FixedWidth: boolean; FontType: TFontType; Sizes: TList; // relevant for bitmap fonts only // contains TLogicalFont objects constructor Create; destructor Destroy; override; end; TCanvasFontManager = class protected FCanvas: TCanvas; FLogicalFonts: TList; FCurrentFontSpec: TFontSpec; FDefaultFontSpec: TFontSpec; FCurrentFont: TLogicalFont; FAllowBitmapFonts: boolean; protected function CreateFont( const FontSpec: TFontSpec ): TLogicalFont; function GetFont( const FontSpec: TFontSpec ): TLogicalFont; procedure RegisterFont( Font: TLogicalFont ); procedure SelectFont( Font: TLogicalFont; Scale: longint ); // Retrieve character widths for current font procedure LoadMetrics; // load metrics if needed procedure EnsureMetricsLoaded; public Constructor Create( Canvas: TCanvas; AllowBitmapFonts: boolean ); destructor Destroy; override; // Useful functions: // Set the font for the associated canvas. procedure SetFont( const FontSpec: TFontSpec ); // Retrieve the width of the given char, in the current font function CharWidth( const C: Char ): longint; function AverageCharWidth: longint; function MaximumCharWidth: longint; function IsFixed: boolean; function CharHeight: longint; function CharDescender: longint; procedure DrawString( Var Point: TPoint; const Length: longint; const S: PChar ); property Canvas: TCanvas read FCanvas; property DefaultFontSpec: TFontSpec read FDefaultFontSpec write FDefaultFontSpec; end; // Convert a Sibyl font to a FontSpec (Color is left the same) procedure SibylFontToFontSpec( Font: TFont; Var FontSpec: TFontSpec ); // Thoughts on how it works.... // SelectFont looks for an existing logical font that // matches the request. If found selects that logical font // onto the canvas. // If not found it creates a logical font and selects that onto // the canvas. // For bitmap fonts the logical font definition includes pointsize // For outline fonts the defn is only face+attr; in this case // selectfont also ses the 'CharBox' according to the point size. Implementation uses PMWin, PMGpi, OS2Def, PmDev, SysUtils, StringUtilsUnit; Imports Function GpiQueryCharStringPosAt( PS: Hps; StartPoint: PPointL; Options: ULONG; Count: LONG; TheString: PChar; IncrementsArray: PLONG; CharacterPoints: PPointL ): BOOL; ApiEntry; 'PMGPI' Index 585; Function GpiQueryCharStringPos( PS: Hps; Options: ULONG; Count: LONG; TheString: PChar; IncrementsArray: PLONG; CharacterPoints: PPointL ): BOOL; ApiEntry; 'PMGPI' Index 584; end; Type // A little pretend window to send font name.size // and get definite font info back. (See .CreateFont) TFontWindow = class( TControl ) public procedure CreateWnd; override; property OwnerDraw; Function SetPPFontNameSize( Const FNS: String ): Boolean; end; var FontFaces: TList = nil; // of TFontface FontWindow: TFontWindow; DefaultOutlineFixedFace: TFontFace; DefaultOutlineProportionalFace: TFontFace; // TFontFace //------------------------------------------------------------------------ constructor TFontface.Create; begin Sizes := TList.Create; end; destructor TFontface.Destroy; begin Sizes.Destroy; end; // TLogicalFont //------------------------------------------------------------------------ // frees allocated memory, if any. // Note - does not delete the Gpi Logical Font destructor TLogicalFont.Destroy; begin DisposeStr( pFaceName ); DisposeStr( pUseFaceName ); if pCharWidthArray <> nil then FreeMem( pCharWidthArray, sizeof( TCharWidthArray ) ); inherited Destroy; end; // TFontWindow //------------------------------------------------------------------------ procedure TFontWindow.CreateWnd; begin inherited CreateWnd; end; Function TFontWindow.SetPPFontNameSize( Const FNS: String ): Boolean; Var CS: Cstring; Begin CS := FNS; Result := WinSetPresParam( Handle, PP_FONTNAMESIZE, Length( CS ) + 1, CS ); End; //------------------------------------------------------------------------ // Convert a Sibyl font to a FontSpec //------------------------------------------------------------------------ procedure SibylFontToFontSpec( Font: TFont; Var FontSpec: TFontSpec ); begin FontSpec.FaceName := Font.FaceName; FontSpec.PointSize := Font.PointSize; FontSpec.Attributes := Font.Attributes; end; // Find a font face with the given name //------------------------------------------------------------------------ function FindFaceName( const name: string ): TFontFace; Var FaceIndex: LongInt; Face: TFontFace; begin for FaceIndex := 0 to FontFaces.Count - 1 do begin Face := FontFaces[ FaceIndex ]; if StrEqualIgnoringCase( Face.pName^, Name ) then begin Result := Face; exit; end; end; Result := nil; end; // Return the first font face of type = Outline (scalable) //------------------------------------------------------------------------ function GetFirstOutlineFace( FixedWidth: boolean ): TFontFace; Var FaceIndex: LongInt; Face: TFontFace; begin for FaceIndex := 0 to FontFaces.Count - 1 do begin Face := FontFaces[ FaceIndex ]; if ( Face.FixedWidth = FixedWidth ) and ( Face.FontType = ftOutline ) then begin Result := Face; exit; end; end; Result := nil; end; // Find the bitmap font which best matches the given pointsize. //------------------------------------------------------------------------ function GetClosestBitmapFixedFont( const PointSize: longint ): TLogicalFont; Var FaceIndex: Longint; FontIndex: longint; Face: TFontFace; Font: TLogicalFont; begin Result := nil; for FaceIndex := 0 to FontFaces.Count - 1 do begin Face := FontFaces[ FaceIndex ]; if Face.FontType = ftBitmap then begin for FontIndex := 0 to Face.Sizes.Count - 1 do begin Font := Face.Sizes[ FontIndex ]; if Font.FixedWidth then begin if ( Result = nil ) or ( Abs( Font.PointSize - PointSize ) < Abs( Result.PointSize - PointSize ) ) then Result := Font; end; end; end; end; end; // Pick some nice default fonts. //------------------------------------------------------------------------ procedure GetDefaultFonts; begin // courier new is common and reasonably nice DefaultOutlineFixedFace := FindFaceName( 'Courier New' ); if DefaultOutlineFixedFace = nil then begin DefaultOutlineFixedFace := GetFirstOutlineFace( true ); // first fixed outline face end; DefaultOutlineProportionalFace := FindFaceName( 'Helvetica' ); if DefaultOutlineProportionalFace = nil then begin DefaultOutlineProportionalFace := GetFirstOutlineFace( false ); // first prop outline face end; end; Type TMyFontMetrics = Array[ 0..1 ] Of FONTMETRICS; PMyFontMetrics = ^TMyFontMetrics; // Fetch the global list of font faces and sizes //------------------------------------------------------------------------ procedure GetFontList; Var Count: LongInt; aPS: HPS; T: LongInt; Font: TLogicalFont; Face: TFontFace; pfm: PMyFontMetrics; FamilyName: string; fsDefn: USHORT; Begin FontFaces := TList.Create; aPS := WinGetPS( HWND_DESKTOP ); Count := 0; // Get font count Count := GpiQueryFonts( aPS, QF_PUBLIC, Nil, Count, 0, Nil ); If Count > 0 Then Begin // allocate memory to retrieve all the font data. GetMem( pfm, Count * SizeOf( FONTMETRICS ) ); GpiQueryFonts( aPS, QF_PUBLIC, Nil, Count, SizeOf(FONTMETRICS), pfm^[ 0 ] ); For T := 0 To Count - 1 Do Begin Font := TLogicalFont.Create( Screen ); Font.pFaceName := NewStr( pfm^[ T ].szFaceName ); FamilyName := pfm^[ T ].szFamilyName; // See what type it is. Actually this is not very // useful as various substitutions are normally made. fsDefn := pfm^[ T ].fsDefn; If ( fsDefn And FM_DEFN_OUTLINE ) <> 0 Then Font.FontType := ftOutline else Font.FontType := ftBitmap; if Font.pFaceName^ = 'Helvetica' then Font := Font; Font.PointSize := pfm^[ T ].sNominalPointSize div 10; Font.FixedWidth := pfm^[ T ].fsType And FM_TYPE_FIXED <> 0 ; Font.fsSelection := pfm^[ T ].fsSelection; Font.lMaxbaselineExt := pfm^[ T ].lMaxbaselineExt; Font.lAveCharWidth := pfm^[ T ].lAveCharWidth; Font.lMaxCharInc := pfm^[ T ].lMaxCharInc; Font.ID := -1; // and always shall be so... Face := FindFaceName( Font.pFaceName^ ); if Face = nil then begin // new face found Face := TFontFace.Create; Face.pName := Font.pFaceName; // point to the actual face name string! Face.FixedWidth := Font.FixedWidth; Face.FontType := Font.FontType; FontFaces.Add( Face ); end; Face.Sizes.Add( Font ); End; End; FreeMem( pfm, Count * SizeOf( FONTMETRICS ) ); WinReleasePS( aPS ); // pick some for defaults GetDefaultFonts; FontWindow := TFontWindow.Create( Nil ); FontWindow.OwnerDraw := True; FontWindow.CreateWnd; end; // Add .subscript to font name for attributes //------------------------------------------------------------------------ Function ModifyFontName( const FontName: string; const Attrs: TFontAttributes ): String; Begin Result := FontName; If faItalic in Attrs Then Result := Result + '.Italic'; If faBold in Attrs Then Result := Result + '.Bold'; If faOutline in Attrs Then Result := Result + '.Outline'; If faStrikeOut in Attrs Then Result := Result + '.Strikeout'; If faUnderScore in Attrs Then Result := Result + '.Underscore'; End; // Create a font without attributes //------------------------------------------------------------------------ function CreateFontBasic( const FaceName: string; const PointSize: integer ): TLogicalFont; var PPString: string; PresSpace: HPS; FontInfo: FONTMETRICS; begin Result := TLogicalFont.Create( nil ); if FindFaceName( FaceName ) = nil then exit; Result.PointSize := PointSize; // will use later if the result was an outline font... Result.pFaceName := NewStr( FaceName ); // OK now we have found the font face... PPString := IntToStr( PointSize) + '.' + FaceName; PPString := ModifyFontName( PPString, [] ); If Not FontWindow.SetPPFontNameSize( PPString ) Then // Gurk! Exit; PresSpace := WinGetPS( FontWindow.Handle ); If Not GpiQueryFontMetrics( PresSpace, SizeOf( FONTMETRICS ), FontInfo ) Then begin // Clurkle!? WinReleasePS( PresSpace ); Exit; end; WinReleasePS( PresSpace ); if ( FontInfo.fsDefn And FM_DEFN_OUTLINE ) > 0 then Result.FontType := ftOutline else Result.FontType := ftBitmap; end; // Provide outline substitutes for some common bitmap fonts // From Mozilla/2 source. //------------------------------------------------------------------------ function SubstituteBitmapFontToOutline( const FaceName: string ): string; begin if StrEqualIgnoringCase( FaceName, 'Helv' ) then result := 'Helvetica' else if StrEqualIgnoringCase( FaceName, 'Tms Rmn' ) then result := 'Times New Roman' else if StrEqualIgnoringCase( FaceName, 'System Proportional' ) then result := 'Helvetica' else if StrEqualIgnoringCase( FaceName, 'System Monospaced' ) then result := 'Courier' else if StrEqualIgnoringCase( FaceName, 'System VIO' ) then result := 'Courier' else result := FaceName; // no substitution end; // NOTE!!! Not currently used or working... // Find a font with exact width and height //------------------------------------------------------------------------ function FindXYSizeFont( const Face: TFontFace; const XSize: longint; const YSize: longint ): TLogicalFont; var SizeIndex: longint; F: TLogicalFont; FontInfo: FONTMETRICS; begin for SizeIndex := 0 to Face.Sizes.Count - 1 do begin F := Face.Sizes[ SizeIndex ]; if ( F.lMaxbaselineExt = YSize ) and ( F.lAveCharWidth = XSize ) then begin // found exact match FontInfo.lMaxbaselineExt := F.lMaxbaselineExt; FontInfo.lAveCharWidth := F.lAveCharWidth; Result.FontType := ftBitmap; end; end; end; // Ask OS/2 dummy font window to convert a font spec // into a FONTMETRICS. //------------------------------------------------------------------------ procedure AskOS2FontDetails( const FaceName: string; const PointSize: longint; const Attributes: TFontAttributes; var FontInfo: FONTMETRICS ); var PPString: string; PresSpace: HPS; begin // Hack from Sibyl code - we don't know WTF the algorithm is // for selecting between outline/bitmap and doing substitutions // so send it to a dummy window and find out the resulting details PPString := IntToStr( PointSize ) + '.' + FaceName; PPString := ModifyFontName( PPString, Attributes ); FontWindow.SetPPFontNameSize( PPString ); PresSpace := WinGetPS( FontWindow.Handle ); GpiQueryFontMetrics( PresSpace, SizeOf( FontInfo ), FontInfo ); WinReleasePS( PresSpace ); end; // Look for the best match for the given face, size and attributes. // If FixedWidth is set then makes sure that the result is fixed // (if there is any fixed font on the system at all!) // This uses the OS/2 GPI and therefore makes some substitutions, // such as Helv 8 (bitmap) for Helvetica 8 (outline) //------------------------------------------------------------------------ procedure FindBestFontMatch( const FaceName: string; const PointSize: longint; const Attributes: TFontAttributes; const FixedWidth: boolean; var FontInfo: FONTMETRICS ); var BestBitmapFontMatch: TLogicalFont; begin // First just ask GPI to give us a font AskOS2FontDetails( FaceName, PointSize, Attributes, FontInfo ); if not FixedWidth then // OK, whatever it gave us. exit; // we want a fixed width font... if ( FontInfo.fsType and FM_TYPE_FIXED ) <> 0 then // got a suitable font exit; // the stoopid freaking OS/2 GPI has given us // a proportional font for that size if DefaultOutlineFixedFace <> nil then // use the default fixed width outline face AskOS2FontDetails( DefaultOutlineFixedFace.pName^, PointSize, Attributes, FontInfo ); if ( FontInfo.fsType and FM_TYPE_FIXED ) <> 0 then // got a suitable font exit; // still got a proportional font, // or we didn't have any fixed width outline face // so see what we can find in the way of a bitmap fixed font BestBitmapFontMatch := GetClosestBitmapFixedFont( PointSize ); if BestBitmapFontMatch <> nil then begin FontInfo.lMaxbaseLineExt := BestBitmapFontMatch.lMaxbaselineExt; FontInfo.lAveCharWidth := BestBitmapFontMatch.lAveCharWidth; FontInfo.fsDefn := 0; FontInfo.szFaceName := BestBitmapFontMatch.pFaceName^; end; // else - there are no fixed fonts of any kind on the system. Oh dear. end; //------------------------------------------------------------------------ // Font manager //------------------------------------------------------------------------ // constructor //------------------------------------------------------------------------ constructor TCanvasFontManager.Create( Canvas: TCanvas; AllowBitmapFonts: boolean ); begin inherited Create; if FontFaces = nil then GetFontList; FCanvas := Canvas; FLogicalFonts := TList.Create; FCurrentFontSpec.FaceName := 'notafont'; FCurrentFont := nil; FAllowBitmapFonts := AllowBitmapFonts; // get system default font spec // as default default ;) SibylFontToFontSpec( Screen.DefaultFont, FDefaultFontSpec ); end; // Destructor //------------------------------------------------------------------------ destructor TCanvasFontManager.Destroy; var i: integer; Font: TLogicalFont; rc: ERRORID; begin // select default font so none of our logical fonts are in use if not GpiSetCharSet( FCanvas.Handle, LCID_DEFAULT ) then rc := WinGetLastError( AppHandle ); // delete each logical font and our record of it for i := 0 to FLogicalFonts.Count - 1 do begin Font := FLogicalFonts[ i ]; if not GpiDeleteSetID( FCanvas.Handle, Font.ID ) then rc := WinGetLastError( AppHandle ); Font.Destroy; end; FLogicalFonts.Destroy; inherited Destroy; end; // Create a logical font for the given spec //------------------------------------------------------------------------ function TCanvasFontManager.CreateFont( const FontSpec: TFontSpec ): TLogicalFont; var UseFaceName: string; Face: TFontFace; RemoveBoldFromSelection: boolean; RemoveItalicFromSelection: boolean; UseAttributes: TFontAttributes; MatchAttributes: TFontAttributes; BaseFont: TLogicalFont; BaseFontIsBitmapFont: Boolean; FontInfo: FONTMETRICS; FixedWidth: boolean; begin Face := nil; RemoveBoldFromSelection := false; RemoveItalicFromSelection := false; UseAttributes := FontSpec.Attributes; // see if the originally specified font is a fixed width one. FixedWidth := false; Face := FindFaceName( FontSpec.FaceName ); if Face <> nil then FixedWidth := Face.FixedWidth; Face := nil; if not FAllowBitmapFonts then UseFaceName := SubstituteBitmapFontToOutline( FontSpec.FaceName ) else UseFaceName := FontSpec.FaceName; if FontSpec.Attributes <> [] then begin BaseFontIsBitmapFont := false; if FAllowBitmapFonts then begin // First see if the base font (without attributes) // would be a bitmap font... BaseFont := CreateFontBasic( UseFaceName, FontSpec.PointSize ); if BaseFont <> nil then begin BaseFontIsBitmapFont := BaseFont.FontType = ftBitmap; BaseFont.Destroy; end; end; If not BaseFontIsBitmapFont Then begin // Result is an outline font so look for specific bold/italic fonts if ( faBold in FontSpec.Attributes ) and ( faItalic in FontSpec.Attributes ) then begin Face := FindFaceName( UseFaceName + ' BOLD ITALIC' ); if Face <> nil then begin Exclude( UseAttributes, faBold ); Exclude( UseAttributes, faItalic ); RemoveBoldFromSelection := true; RemoveItalicFromSelection := true; end; end; if Face = nil then if faBold in FontSpec.Attributes then begin Face := FindFaceName( UseFaceName + ' BOLD' ); if Face <> nil then begin Exclude( UseAttributes, faBold ); RemoveBoldFromSelection := true; end; end; if Face = nil then if faItalic in FontSpec.Attributes then begin Face := FindFaceName( UseFaceName + ' ITALIC' ); if Face <> nil then begin Exclude( UseAttributes, faItalic ); RemoveItalicFromSelection := true; end; end; end; end; if Face <> nil then // found a styled face, does it match fixed width? if Face.FixedWidth <> FixedWidth then // no so we don't want to use it. Face := nil; if Face = nil then // didn't find a styled face (or no styles set) // so find unmodified, we will use simulation bits Face := FindFaceName( UseFaceName ); if not FAllowBitmapFonts then if Face.FontType = ftBitmap then // we aren't allowed bitmaps, but that's what this // face is. So use the default outline face of the // appropriate width type if FixedWidth then Face := DefaultOutlineFixedFace else Face := DefaultOutlineProportionalFace; if Face = nil then begin // Could not find the specified font name. Bummer. Result := nil; exit; end; // OK now we have found the font face... Result := TLogicalFont.Create( nil ); Result.PointSize := FontSpec.PointSize; // will use later if the result was an outline font... Result.pFaceName := NewStr( FontSpec.FaceName ); Result.pUseFaceName := NewStr( Face.pName^ ); Result.Attributes := FontSpec.Attributes; Result.fsSelection := 0; Result.FixedWidth := Face.FixedWidth; if FAllowBitmapFonts then begin if BaseFontIsBitmapFont then MatchAttributes := [] else MatchAttributes := UseAttributes; FindBestFontMatch( Face.pName^, FontSpec.PointSize, MatchAttributes, FixedWidth, FontInfo ); AssignStr( Result.pUseFaceName, FontInfo.szFaceName ); // We may actually get a bitmap OR an outline font back If ( FontInfo.fsDefn And FM_DEFN_OUTLINE ) <> 0 Then Result.FontType := ftOutline else Result.FontType := ftBitmap; end else begin // no bitmap fonts please. Result.FontType := ftOutline end; // store the baseline and average char width. // For bitmap fonts, these tell GPI which font we really want // For outline fonts, we are just storing them for later ref. Result.lMaxbaseLineExt := FontInfo.lMaxbaselineExt; Result.lAveCharWidth := FontInfo.lAveCharWidth; Result.lMaxCharInc := FontInfo.lMaxCharInc; // Set style flags with Result do begin If faBold in UseAttributes Then fsSelection := fsSelection or FM_SEL_BOLD; If faItalic in UseAttributes Then fsSelection := fsSelection or FM_SEL_ITALIC; If faUnderScore in UseAttributes Then fsSelection := fsSelection or FM_SEl_UNDERSCORE; If faStrikeOut in UseAttributes Then fsSelection := fsSelection or FM_SEl_STRIKEOUT; If faOutline in UseAttributes Then fsSelection := fsSelection or FM_SEl_OUTlINE; end; Result.pCharWidthArray := Nil; end; // Register the given logical font with GPI and store for later use //------------------------------------------------------------------------ procedure TCanvasFontManager.RegisterFont( Font: TLogicalFont ); var fa: FATTRS; rc: LONG; begin FLogicalFonts.Add( Font ); Font.ID := FLogicalFonts.Count + 1; // add 1 to stay out of Sibyl's way // Initialise GPI font attributes FillChar( fa, SizeOf( FATTRS ), 0 ); fa.usRecordLength := SizeOf( FATTRS ); // Copy facename and 'simulation' attributes from what we obtained // earlier fa.szFaceName := Font.pUseFaceName^; fa.fsSelection := Font.fsSelection; fa.lMatch := 0; // please Mr GPI be helpful and do clever stuff for us, we are ignorant fa.idRegistry := 0; // IBM magic number fa.usCodePage := 0; // use current codepage If Font.FontType = ftOutline then // Outline font wanted fa.fsFontUse := FATTR_FONTUSE_OUTLINE Or FATTR_FONTUSE_TRANSFORMABLE else // bitmap font fa.fsFontUse := 0; // don't need mixing with graphics (for now) fa.fsFontUse := fa.fsFontUse or FATTR_FONTUSE_NOMIX; // copy char cell width/height from the (valid) one we // found earlier in GetFont (will be zero for outline) fa.lMaxbaseLineExt := Font.lMaxbaselineExt; fa.lAveCharWidth := Font.lAveCharWidth; fa.fsType := 0; // create logical font rc := GpiCreateLogFont( FCanvas.Handle, nil, Font.ID, fa ); end; // Select the given (existing) logical font //------------------------------------------------------------------------ procedure TCanvasFontManager.SelectFont( Font: TLogicalFont; Scale: longint ); var aHDC: HDC; xRes: LongInt; yRes: LongInt; aSizeF: SIZEF; begin // Select the logical font GpiSetCharSet( FCanvas.Handle, Font.ID ); if Font.FontType = ftOutline then begin // For outline fonts, also set character Box aHDC := GpiQueryDevice( FCanvas.Handle ); DevQueryCaps( aHDC, CAPS_HORIZONTAL_FONT_RES, 1, xRes ); DevQueryCaps( aHDC, CAPS_VERTICAL_FONT_RES, 1, yRes ); aSizeF.CX := 65536 * xRes* Font.PointSize Div 72 * Scale; aSizeF.CY := 65536 * yRes* Font.PointSize Div 72 * Scale; GpiSetCharBox( FCanvas.Handle, aSizeF ); end; end; // Get a font to match the given spec, creating or re-using an // existing font as needed. //------------------------------------------------------------------------ function TCanvasFontManager.GetFont( const FontSpec: TFontSpec ): TLogicalFont; var AFont: TLogicalFont; FontIndex: integer; begin for FontIndex := 0 to FLogicalFonts.Count - 1 do begin AFont := FLogicalFonts[ FontIndex ]; if AFont.PointSize = FontSpec.PointSize then begin if ( AFont.PointSize > 0 ) or ( ( AFont.lAveCharWidth = FontSpec.XSize ) and ( AFont.lMaxbaselineExt = FontSpec.YSize ) ) then begin if AFont.Attributes = FontSpec.Attributes then begin // search name last since it's the slowest thing if AFont.pFaceName^ = FontSpec.FaceName then begin // Found a logical font already created Result := AFont; // done exit; end; end; end; end; end; // Need to create new logical font Result := CreateFont( FontSpec ); if Result <> nil then begin RegisterFont( Result ); end; end; // Set the current font for the canvas to match the given // spec, creating or re-using fonts as needed. //------------------------------------------------------------------------ procedure TCanvasFontManager.SetFont( const FontSpec: TFontSpec ); var Font: TLogicalFont; DefaultFontSpec: TFontSpec; begin if FCurrentFontSpec = FontSpec then // same font exit; Font := GetFont( FontSpec ); if Font = nil then begin // ack! Pfffbt! Couldn't find the font. // Try to get the default font Font := GetFont( FDefaultFontSpec ); if Font = nil then begin SibylFontToFontSpec( Screen.DefaultFont, DefaultFontSpec ); Font := GetFont( DefaultFontSpec ); if Font = nil then // Jimminy! We can't even get the default system font raise Exception.Create( 'Could not access default font ' + 'in place of ' + FontSpec.FaceName + ' ' + IntToStr( FontSpec.PointSize ) ); end; end; SelectFont( Font, 1 ); FCurrentFontSpec := FontSpec; FCurrentFont := Font; end; // Get the widths of all characters for current font // and other dimensions //------------------------------------------------------------------------ procedure TCanvasFontManager.LoadMetrics; var TheChar: Char; fm: FONTMETRICS; begin // Retrieve all character widths if FCurrentFont.FontType = ftOutline then begin SelectFont( FCurrentFont, FontWidthPrecisionFactor ); end; // allocate memory for storing the char widths GetMem( FCurrentFont.pCharWidthArray, sizeof( TCharWidthArray ) ); if not GpiQueryWidthTable( FCanvas.Handle, 0, 256, FCurrentFont.pCharWidthArray^[ #0 ] ) then begin raise Exception.Create( 'Error getting character width table: ' + 'GpiQueryWidthTable error ' + IntToStr( WinGetLastError( AppHandle ) ) ); end; // Convert all widths to positive! // For unknown reason, sometimes GPI returns negative values... for TheChar := #0 to #255 do begin FCurrentFont.pCharWidthArray^[ TheChar ] := Abs( FCurrentFont.pCharWidthArray^[ TheChar ] ); end; if FCurrentFont.FontType = ftOutline then begin SelectFont( FCurrentFont, 1 ); end else begin // For bitmap fonts, multiply by 256 manually for TheChar := #0 to #255 do begin FCurrentFont.pCharWidthArray^[ TheChar ] := FCurrentFont.pCharWidthArray^[ TheChar ] * FontWidthPrecisionFactor; end; end; GpiQueryFontMetrics( FCanvas.Handle, sizeof( fm ), fm ); FCurrentFont.lMaxbaseLineExt := fm.lMaxbaselineExt; FCurrentFont.lAveCharWidth := fm.lAveCharWidth; FCurrentFont.lMaxCharInc := fm.lMaxCharInc; FCurrentFont.lMaxDescender := fm.lMaxDescender; end; procedure TCanvasFontManager.EnsureMetricsLoaded; begin if FCurrentFont = nil then raise( Exception.Create( 'No font selected before getting font metrics' ) ); if FCurrentFont.pCharWidthArray = Nil then LoadMetrics; end; function TCanvasFontManager.CharWidth( const C: Char ): longint; begin EnsureMetricsLoaded; Result := FCurrentFont.pCharWidthArray^[ C ]; end; function TCanvasFontManager.AverageCharWidth: longint; begin EnsureMetricsLoaded; Result := FCurrentFont.lAveCharWidth; end; function TCanvasFontManager.MaximumCharWidth: longint; begin EnsureMetricsLoaded; Result := FCurrentFont.lMaxCharInc; end; function TCanvasFontManager.CharHeight; begin EnsureMetricsLoaded; Result := FCurrentFont.lMaxBaseLineExt; end; function TCanvasFontManager.CharDescender; begin EnsureMetricsLoaded; Result := FCurrentFont.lMaxDescender; end; function TCanvasFontManager.IsFixed: boolean; begin Result := FCurrentFont.FixedWidth; end; procedure TCanvasFontManager.DrawString( Var Point: TPoint; const Length: longint; const S: PChar ); begin GpiCharStringAt( FCanvas.Handle, Point, Length, S^ ); Point := FCanvas.PenPos; end; end.